From 3301bed442710b133c46ece88c5b6d0f5661ca2f Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Mon, 18 Mar 2019 22:52:17 +0200 Subject: [PATCH] 409d4d9...aeafa16 (merge human-ai) --- .../BarotraumaClient/ClientCode.projitems | 1 - .../Source/Characters/AI/EnemyAIController.cs | 3 +- .../Source/Characters/AI/HumanAIController.cs | 85 ++-- .../Source/Characters/Animation/Ragdoll.cs | 2 +- .../Source/Characters/CharacterHUD.cs | 2 +- .../Source/Characters/Limb.cs | 10 +- .../BarotraumaClient/Source/DebugConsole.cs | 183 ++++++++ .../Source/GUI/LoadingScreen.cs | 2 +- .../Source/GameSession/CrewManager.cs | 90 ++-- .../GameModes/Tutorials/BasicTutorial.cs | 6 +- .../GameModes/Tutorials/ContextualTutorial.cs | 2 +- .../BarotraumaClient/Source/GameSettings.cs | 22 + .../Source/Items/Components/ItemComponent.cs | 57 +++ .../Items/Components/Machines/MiniMap.cs | 4 +- .../Source/Items/Components/Machines/Sonar.cs | 8 +- .../Source/Items/Components/RepairTool.cs | 2 +- .../Source/Items/Components/Repairable.cs | 8 +- .../Source/Items/Inventory.cs | 10 +- .../BarotraumaClient/Source/Items/Item.cs | 11 +- .../Source/Screens/CharacterEditorScreen.cs | 53 ++- .../Source/Screens/MainMenuScreen.cs | 305 ++++++++----- .../Source/Screens/ServerListScreen.cs | 4 +- .../Source/Sounds/SoundManager.cs | 5 + .../BarotraumaServer/Source/DebugConsole.cs | 2 - .../BarotraumaServer/Source/Items/Item.cs | 72 +++- .../Source/Networking/GameServer.cs | 14 +- .../BarotraumaShared/SharedCode.projitems | 2 +- .../BarotraumaShared/SharedContent.projitems | 96 ++++- .../Source/Characters/AI/EnemyAIController.cs | 48 +-- .../Source/Characters/AI/HumanAIController.cs | 290 ++++++++++--- .../Source/Characters/AI/ISteerable.cs | 4 - .../Characters/AI/IndoorsSteeringManager.cs | 55 +-- .../Source/Characters/AI/LatchOntoAI.cs | 4 +- .../Characters/AI/Objectives/AIObjective.cs | 104 ++++- .../Objectives/AIObjectiveChargeBatteries.cs | 80 ++-- .../AI/Objectives/AIObjectiveCombat.cs | 5 + .../AI/Objectives/AIObjectiveContainItem.cs | 2 + .../AI/Objectives/AIObjectiveDecontainItem.cs | 2 + .../Objectives/AIObjectiveExtinguishFire.cs | 43 +- .../Objectives/AIObjectiveExtinguishFires.cs | 47 +- .../Objectives/AIObjectiveFindDivingGear.cs | 5 + .../AI/Objectives/AIObjectiveFindSafety.cs | 294 ++++++------- .../AI/Objectives/AIObjectiveFixLeak.cs | 69 ++- .../AI/Objectives/AIObjectiveFixLeaks.cs | 147 ++----- .../AI/Objectives/AIObjectiveGetItem.cs | 31 +- .../AI/Objectives/AIObjectiveGoTo.cs | 108 ++--- .../AI/Objectives/AIObjectiveIdle.cs | 190 +++++---- .../AI/Objectives/AIObjectiveLoop.cs | 122 ++++++ .../AI/Objectives/AIObjectiveManager.cs | 139 ++++-- .../AI/Objectives/AIObjectiveOperateItem.cs | 10 +- .../AI/Objectives/AIObjectivePumpWater.cs | 112 ++--- .../AI/Objectives/AIObjectiveRepairItem.cs | 135 +++--- .../AI/Objectives/AIObjectiveRepairItems.cs | 92 ++-- .../AI/Objectives/AIObjectiveRescue.cs | 6 +- .../AI/Objectives/AIObjectiveRescueAll.cs | 17 +- .../Source/Characters/AI/PathFinder.cs | 2 +- .../Source/Characters/AI/SteeringManager.cs | 37 +- .../Animation/HumanoidAnimController.cs | 9 +- .../Source/Characters/Character.cs | 25 +- .../Characters/Health/CharacterHealth.cs | 4 +- .../Source/Characters/Limb.cs | 22 +- .../BarotraumaShared/Source/DebugConsole.cs | 15 +- .../Source/Extensions/VectorExtensions.cs | 26 +- .../BarotraumaShared/Source/GameSettings.cs | 401 ++---------------- .../Source/Items/Components/Door.cs | 2 +- .../Items/Components/Holdable/RepairTool.cs | 28 +- .../Source/Items/Components/ItemComponent.cs | 4 +- .../Items/Components/Machines/Engine.cs | 4 +- .../Items/Components/Machines/Fabricator.cs | 2 +- .../Items/Components/Machines/MiniMap.cs | 2 +- .../Components/Machines/OxygenGenerator.cs | 4 +- .../Source/Items/Components/Machines/Pump.cs | 4 +- .../Items/Components/Machines/Reactor.cs | 4 +- .../Items/Components/Power/PowerContainer.cs | 8 +- .../Items/Components/Power/PowerTransfer.cs | 2 +- .../Source/Items/Components/Repairable.cs | 14 +- .../BarotraumaShared/Source/Items/Item.cs | 29 +- .../BarotraumaShared/Source/Map/Hull.cs | 2 + .../Source/Map/Map/LocationType.cs | 7 + .../BarotraumaShared/Source/Map/Map/Map.cs | 7 + .../BarotraumaShared/Source/Map/Submarine.cs | 71 ++-- .../Source/Networking/ServerLog.cs | 10 +- .../Source/Physics/PhysicsBody.cs | 5 + .../BarotraumaShared/Source/PlayerInput.cs | 10 + .../BarotraumaShared/Source/TextManager.cs | 4 +- .../Source/Utils/MathUtils.cs | 5 + .../Submarines/Humpback_LadderTest.sub | Bin 0 -> 77229 bytes .../Submarines/Remora_LadderTest.sub | Bin 0 -> 136935 bytes 88 files changed, 2334 insertions(+), 1657 deletions(-) create mode 100644 Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveLoop.cs create mode 100644 Barotrauma/BarotraumaShared/Submarines/Humpback_LadderTest.sub create mode 100644 Barotrauma/BarotraumaShared/Submarines/Remora_LadderTest.sub diff --git a/Barotrauma/BarotraumaClient/ClientCode.projitems b/Barotrauma/BarotraumaClient/ClientCode.projitems index 0c41252dd..3864fe96e 100644 --- a/Barotrauma/BarotraumaClient/ClientCode.projitems +++ b/Barotrauma/BarotraumaClient/ClientCode.projitems @@ -15,7 +15,6 @@ - diff --git a/Barotrauma/BarotraumaClient/Source/Characters/AI/EnemyAIController.cs b/Barotrauma/BarotraumaClient/Source/Characters/AI/EnemyAIController.cs index ae5be6c5d..87b0b4133 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/AI/EnemyAIController.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/AI/EnemyAIController.cs @@ -65,10 +65,11 @@ namespace Barotrauma { GUI.DrawLine(spriteBatch, pos, ConvertUnits.ToDisplayUnits(new Vector2(latchOntoAI.WallAttachPos.Value.X, -latchOntoAI.WallAttachPos.Value.Y)), Color.Orange * 0.6f, 0, 3); - } } + GUI.DrawLine(spriteBatch, pos, pos + ConvertUnits.ToDisplayUnits(new Vector2(Steering.X, -Steering.Y)), Color.Blue, width: 3); + IndoorsSteeringManager pathSteering = steeringManager as IndoorsSteeringManager; if (pathSteering == null || pathSteering.CurrentPath == null || pathSteering.CurrentPath.CurrentNode == null) return; diff --git a/Barotrauma/BarotraumaClient/Source/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaClient/Source/Characters/AI/HumanAIController.cs index ef94cab43..9f108885f 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/AI/HumanAIController.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/AI/HumanAIController.cs @@ -1,6 +1,5 @@ -using Barotrauma.Networking; -using Microsoft.Xna.Framework; -using System.Linq; +using Microsoft.Xna.Framework; +using FarseerPhysics; namespace Barotrauma { @@ -23,34 +22,70 @@ namespace Barotrauma public override void DebugDraw(Microsoft.Xna.Framework.Graphics.SpriteBatch spriteBatch) { + Vector2 pos = Character.WorldPosition; + pos.Y = -pos.Y; + Vector2 textOffset = new Vector2(-40, -120); + if (SelectedAiTarget?.Entity != null) { - GUI.DrawLine(spriteBatch, - new Vector2(Character.DrawPosition.X, -Character.DrawPosition.Y), - new Vector2(SelectedAiTarget.WorldPosition.X, -SelectedAiTarget.WorldPosition.Y), Color.Red); + //GUI.DrawLine(spriteBatch, pos, new Vector2(SelectedAiTarget.WorldPosition.X, -SelectedAiTarget.WorldPosition.Y), Color.Red); + //GUI.DrawString(spriteBatch, pos + textOffset, $"AI TARGET: {SelectedAiTarget.Entity.ToString()}", Color.White, Color.Black); } - IndoorsSteeringManager pathSteering = steeringManager as IndoorsSteeringManager; - if (pathSteering == null || pathSteering.CurrentPath == null || pathSteering.CurrentPath.CurrentNode == null) return; - - GUI.DrawLine(spriteBatch, - new Vector2(Character.DrawPosition.X, -Character.DrawPosition.Y), - new Vector2(pathSteering.CurrentPath.CurrentNode.DrawPosition.X, -pathSteering.CurrentPath.CurrentNode.DrawPosition.Y), - Color.LightGreen * 0.5f, 0, 3); - - - for (int i = 1; i < pathSteering.CurrentPath.Nodes.Count; i++) + if (ObjectiveManager != null) { - GUI.DrawLine(spriteBatch, - new Vector2(pathSteering.CurrentPath.Nodes[i].DrawPosition.X, -pathSteering.CurrentPath.Nodes[i].DrawPosition.Y), - new Vector2(pathSteering.CurrentPath.Nodes[i - 1].DrawPosition.X, -pathSteering.CurrentPath.Nodes[i - 1].DrawPosition.Y), - Color.LightGreen * 0.5f, 0, 3); - - GUI.SmallFont.DrawString(spriteBatch, - pathSteering.CurrentPath.Nodes[i].ID.ToString(), - new Vector2(pathSteering.CurrentPath.Nodes[i].DrawPosition.X, -pathSteering.CurrentPath.Nodes[i].DrawPosition.Y - 10), - Color.LightGreen); + var currentOrder = ObjectiveManager.CurrentOrder; + if (currentOrder != null) + { + GUI.DrawString(spriteBatch, pos + textOffset, $"ORDER: {currentOrder.DebugTag} ({currentOrder.GetPriority(ObjectiveManager).FormatZeroDecimal()})", Color.White, Color.Black); + } + var currentObjective = ObjectiveManager.CurrentObjective; + if (currentObjective != null) + { + GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(0, 20), $"OBJECTIVE: {currentObjective.DebugTag} ({currentObjective.GetPriority(ObjectiveManager).FormatZeroDecimal()})", Color.White, Color.Black); + var subObjective = currentObjective.CurrentSubObjective; + if (subObjective != null) + { + GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(0, 40), $"SUBOBJECTIVE: {subObjective.DebugTag} ({subObjective.GetPriority(ObjectiveManager).FormatZeroDecimal()})", Color.White, Color.Black); + } + } } + + if (steeringManager is IndoorsSteeringManager pathSteering) + { + var path = pathSteering.CurrentPath; + if (path != null) + { + if (path.CurrentNode != null) + { + GUI.DrawLine(spriteBatch, pos, + new Vector2(path.CurrentNode.DrawPosition.X, -path.CurrentNode.DrawPosition.Y), + Color.BlueViolet, 0, 3); + + GUI.DrawString(spriteBatch, pos + textOffset - new Vector2(0, 20), "Path cost: " + path.Cost.FormatZeroDecimal(), Color.White, Color.Black * 0.5f); + } + for (int i = 1; i < path.Nodes.Count; i++) + { + var previousNode = path.Nodes[i - 1]; + var currentNode = path.Nodes[i]; + GUI.DrawLine(spriteBatch, + new Vector2(currentNode.DrawPosition.X, -currentNode.DrawPosition.Y), + new Vector2(previousNode.DrawPosition.X, -previousNode.DrawPosition.Y), + Color.Blue * 0.5f, 0, 3); + + GUI.SmallFont.DrawString(spriteBatch, + currentNode.ID.ToString(), + new Vector2(currentNode.DrawPosition.X, -currentNode.DrawPosition.Y - 10), + Color.LightGreen); + } + } + } + GUI.DrawLine(spriteBatch, pos, pos + ConvertUnits.ToDisplayUnits(new Vector2(Steering.X, -Steering.Y)), Color.Blue, width: 3); + + //if (Character.IsKeyDown(InputType.Aim)) + //{ + // GUI.DrawLine(spriteBatch, pos, new Vector2(Character.CursorWorldPosition.X, -Character.CursorWorldPosition.Y), Color.Yellow, width: 4); + //} } //TODO: move this to the shared project, otherwise bots won't be able to report things in multiplayer diff --git a/Barotrauma/BarotraumaClient/Source/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaClient/Source/Characters/Animation/Ragdoll.cs index c491acea1..5c3dd9749 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/Animation/Ragdoll.cs @@ -441,7 +441,7 @@ namespace Barotrauma } Collider.DebugDraw(spriteBatch, frozen ? Color.Red : (inWater ? Color.SkyBlue : Color.Gray)); - GUI.Font.DrawString(spriteBatch, Collider.LinearVelocity.X.ToString(), new Vector2(Collider.DrawPosition.X, -Collider.DrawPosition.Y), Color.Orange); + GUI.Font.DrawString(spriteBatch, Collider.LinearVelocity.X.FormatSingleDecimal(), new Vector2(Collider.DrawPosition.X, -Collider.DrawPosition.Y), Color.Orange); foreach (RevoluteJoint joint in LimbJoints) { diff --git a/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs b/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs index 5ef2693ce..7c5967485 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs @@ -171,7 +171,7 @@ namespace Barotrauma float alpha = Math.Min((1000.0f - dist) / 1000.0f * 2.0f, 1.0f); if (alpha <= 0.0f) continue; GUI.DrawIndicator(spriteBatch, drawPos, cam, 100.0f, GUI.BrokenIcon, - Color.Lerp(Color.DarkRed, Color.Orange * 0.5f, brokenItem.Condition / 100.0f) * alpha); + Color.Lerp(Color.DarkRed, Color.Orange * 0.5f, brokenItem.Condition / brokenItem.MaxCondition) * alpha); } if (!character.IsUnconscious && character.Stun <= 0.0f) diff --git a/Barotrauma/BarotraumaClient/Source/Characters/Limb.cs b/Barotrauma/BarotraumaClient/Source/Characters/Limb.cs index 82cd7aab6..0db66467c 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/Limb.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/Limb.cs @@ -492,16 +492,12 @@ namespace Barotrauma } foreach (var modifier in damageModifiers) { - //float midAngle = MathUtils.GetMidAngle(modifier.ArmorSector.X, modifier.ArmorSector.Y); - //float spritesheetOrientation = MathHelper.ToRadians(limbParams.Ragdoll.SpritesheetOrientation); - //float offset = -body.Rotation - (midAngle + spritesheetOrientation) * Dir; - float offset = GetArmorSectorRotationOffset(modifier.ArmorSector, -body.Rotation); - Vector2 forward = Vector2.Transform(-Vector2.UnitY, Matrix.CreateRotationZ(offset)); + float rotation = -body.TransformedRotation + GetArmorSectorRotationOffset(modifier.ArmorSector) * Dir; + Vector2 forward = VectorExtensions.Forward(rotation); float size = ConvertUnits.ToDisplayUnits(body.GetSize().Length() / 2); color = modifier.DamageMultiplier > 1 ? Color.Red : Color.GreenYellow; GUI.DrawLine(spriteBatch, bodyDrawPos, bodyDrawPos + Vector2.Normalize(forward) * size, color, width: (int)Math.Round(4 / cam.Zoom)); - if (Dir == -1) { offset += MathHelper.Pi; } - ShapeExtensions.DrawSector(spriteBatch, bodyDrawPos, size, GetArmorSectorSize(modifier.ArmorSector) * Dir, 40, color, offset + MathHelper.Pi, thickness: 2 / cam.Zoom); + ShapeExtensions.DrawSector(spriteBatch, bodyDrawPos, size, GetArmorSectorSize(modifier.ArmorSector) * Dir, 40, color, rotation + MathHelper.Pi, thickness: 2 / cam.Zoom); } } } diff --git a/Barotrauma/BarotraumaClient/Source/DebugConsole.cs b/Barotrauma/BarotraumaClient/Source/DebugConsole.cs index 4ab36dc55..81342069c 100644 --- a/Barotrauma/BarotraumaClient/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaClient/Source/DebugConsole.cs @@ -3063,6 +3063,189 @@ namespace Barotrauma TextManager.WriteToCSV(); NPCConversation.WriteToCSV(); })); +#endif + + commands.Add(new Command("cleanbuild", "", (string[] args) => + { + GameMain.Config.MusicVolume = 0.5f; + GameMain.Config.SoundVolume = 0.5f; + NewMessage("Music and sound volume set to 0.5", Color.Green); + + commands.Add(new Command("camerasettings", "camerasettings [defaultzoom] [zoomsmoothness] [movesmoothness] [minzoom] [maxzoom]: debug command for testing camera settings. The values default to 1.1, 8.0, 8.0, 0.1 and 2.0.", (string[] args) => + { + float defaultZoom = Screen.Selected.Cam.DefaultZoom; + if (args.Length > 0) float.TryParse(args[0], NumberStyles.Number, CultureInfo.InvariantCulture, out defaultZoom); + + float zoomSmoothness = Screen.Selected.Cam.ZoomSmoothness; + if (args.Length > 1) float.TryParse(args[1], NumberStyles.Number, CultureInfo.InvariantCulture, out zoomSmoothness); + float moveSmoothness = Screen.Selected.Cam.MoveSmoothness; + if (args.Length > 2) float.TryParse(args[2], NumberStyles.Number, CultureInfo.InvariantCulture, out moveSmoothness); + + float minZoom = Screen.Selected.Cam.MinZoom; + if (args.Length > 3) float.TryParse(args[3], NumberStyles.Number, CultureInfo.InvariantCulture, out minZoom); + float maxZoom = Screen.Selected.Cam.MaxZoom; + if (args.Length > 4) float.TryParse(args[4], NumberStyles.Number, CultureInfo.InvariantCulture, out maxZoom); + + Screen.Selected.Cam.DefaultZoom = defaultZoom; + Screen.Selected.Cam.ZoomSmoothness = zoomSmoothness; + Screen.Selected.Cam.MoveSmoothness = moveSmoothness; + Screen.Selected.Cam.MinZoom = minZoom; + Screen.Selected.Cam.MaxZoom = maxZoom; + })); + + commands.Add(new Command("waterparams", "waterparams [distortionscalex] [distortionscaley] [distortionstrengthx] [distortionstrengthy] [bluramount]: default 0.5 0.5 0.5 0.5 1", (string[] args) => + { + float distortScaleX = 0.5f, distortScaleY = 0.5f; + float distortStrengthX = 0.5f, distortStrengthY = 0.5f; + float blurAmount = 0.0f; + if (args.Length > 0) float.TryParse(args[0], NumberStyles.Number, CultureInfo.InvariantCulture, out distortScaleX); + if (args.Length > 1) float.TryParse(args[1], NumberStyles.Number, CultureInfo.InvariantCulture, out distortScaleY); + if (args.Length > 2) float.TryParse(args[2], NumberStyles.Number, CultureInfo.InvariantCulture, out distortStrengthX); + if (args.Length > 3) float.TryParse(args[3], NumberStyles.Number, CultureInfo.InvariantCulture, out distortStrengthY); + if (args.Length > 4) float.TryParse(args[4], NumberStyles.Number, CultureInfo.InvariantCulture, out blurAmount); + WaterRenderer.DistortionScale = new Vector2(distortScaleX, distortScaleY); + WaterRenderer.DistortionStrength = new Vector2(distortStrengthX, distortStrengthY); + WaterRenderer.BlurAmount = blurAmount; + })); + + + commands.Add(new Command("refreshrect", "Updates the dimensions of the selected items to match the ones defined in the prefab. Applied only in the subeditor.", (string[] args) => + { + //TODO: maybe do this automatically during loading when possible? + if (Screen.Selected == GameMain.SubEditorScreen) + { + if (!MapEntity.SelectedAny) + { + ThrowError("You have to select item(s) first!"); + } + else + { + foreach (var mapEntity in MapEntity.SelectedList) + { + if (mapEntity is Item item) + { + item.Rect = new Rectangle(item.Rect.X, item.Rect.Y, + (int)(item.Prefab.sprite.size.X * item.Prefab.Scale), + (int)(item.Prefab.sprite.size.Y * item.Prefab.Scale)); + } + else if (mapEntity is Structure structure) + { + if (!structure.ResizeHorizontal) + { + structure.Rect = new Rectangle(structure.Rect.X, structure.Rect.Y, + (int)structure.Prefab.ScaledSize.X, + structure.Rect.Height); + } + if (!structure.ResizeVertical) + { + structure.Rect = new Rectangle(structure.Rect.X, structure.Rect.Y, + structure.Rect.Width, + (int)structure.Prefab.ScaledSize.Y); + } + } + } + } + } + }, isCheat: false)); +#endif + + GameMain.Config.SaveNewPlayerConfig(); + + commands.Add(new Command("loadtexts", "loadtexts [sourcefile] [destinationfile]: Loads all lines of text from a given .txt file and inserts them sequientially into the elements of an xml file. If the file paths are omitted, EnglishVanilla.txt and EnglishVanilla.xml are used.", (string[] args) => + { + string sourcePath = args.Length > 0 ? args[0] : "Content/Texts/EnglishVanilla.txt"; + string destinationPath = args.Length > 1 ? args[1] : "Content/Texts/EnglishVanilla.xml"; + + string[] lines; + try + { + lines = File.ReadAllLines(sourcePath); + } + catch (Exception e) + { + ThrowError("Reading the file \"" + sourcePath + "\" failed.", e); + return; + } + var doc = XMLExtensions.TryLoadXml(destinationPath); + int i = 0; + foreach (XElement element in doc.Root.Elements()) + { + if (i >= lines.Length) + { + ThrowError("Error while loading texts to the xml file. The xml has more elements than the number of lines in the text file."); + return; + } + element.Value = lines[i]; + i++; + } + doc.Save(destinationPath); + }, + () => + { + var files = TextManager.GetTextFiles().Select(f => f.Replace("\\", "/")); + return new string[][] + { + files.Where(f => Path.GetExtension(f)==".txt").ToArray(), + files.Where(f => Path.GetExtension(f)==".xml").ToArray() + }; + })); + + commands.Add(new Command("updatetextfile", "updatetextfile [sourcefile] [destinationfile]: Inserts all the xml elements that are only present in the source file into the destination file. Can be used to update outdated translation files more easily.", (string[] args) => + { + if (args.Length < 2) return; + string sourcePath = args[0]; + string destinationPath = args[1]; + + var sourceDoc = XMLExtensions.TryLoadXml(sourcePath); + var destinationDoc = XMLExtensions.TryLoadXml(destinationPath); + + XElement destinationElement = destinationDoc.Root.Elements().First(); + foreach (XElement element in sourceDoc.Root.Elements()) + { + if (destinationDoc.Root.Element(element.Name) == null) + { + element.Value = "!!!!!!!!!!!!!" + element.Value; + destinationElement.AddAfterSelf(element); + } + XNode nextNode = destinationElement.NextNode; + while ((!(nextNode is XElement) || nextNode == element) && nextNode != null) nextNode = nextNode.NextNode; + destinationElement = nextNode as XElement; + } + destinationDoc.Save(destinationPath); + }, + () => + { + var files = TextManager.GetTextFiles().Where(f => Path.GetExtension(f) == ".xml").Select(f => f.Replace("\\", "/")).ToArray(); + return new string[][] + { + files, + files + }; + })); + + commands.Add(new Command("dumpentitytexts", "dumpentitytexts [filepath]: gets the names and descriptions of all entity prefabs and writes them into a file along with xml tags that can be used in translation files. If the filepath is omitted, the file is written to Content/Texts/EntityTexts.txt", (string[] args) => + { + string filePath = args.Length > 0 ? args[0] : "Content/Texts/EntityTexts.txt"; + List lines = new List(); + foreach (MapEntityPrefab me in MapEntityPrefab.List) + { + lines.Add("" + me.Name + ""); + lines.Add("" + me.Description + ""); + } + File.WriteAllLines(filePath, lines); + })); +#if DEBUG + commands.Add(new Command("checkduplicates", "Checks the given language for duplicate translation keys and writes to file.", (string[] args) => + { + if (args.Length != 1) return; + TextManager.CheckForDuplicates(args[0]); + })); + + commands.Add(new Command("writetocsv", "Writes the default language (English) to a .csv file.", (string[] args) => + { + TextManager.WriteToCSV(); + NPCConversation.WriteToCSV(); + })); commands.Add(new Command("loadtexts", "loadtexts [sourcefile] [destinationfile]: Loads all lines of text from a given .txt file and inserts them sequientially into the elements of an xml file. If the file paths are omitted, EnglishVanilla.txt and EnglishVanilla.xml are used.", (string[] args) => { diff --git a/Barotrauma/BarotraumaClient/Source/GUI/LoadingScreen.cs b/Barotrauma/BarotraumaClient/Source/GUI/LoadingScreen.cs index 3792df034..9a5dcf5d6 100644 --- a/Barotrauma/BarotraumaClient/Source/GUI/LoadingScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/GUI/LoadingScreen.cs @@ -178,7 +178,7 @@ namespace Barotrauma if (GUI.LargeFont != null) { - GUI.LargeFont.DrawString(spriteBatch, loadText, + GUI.LargeFont.DrawString(spriteBatch, loadText.ToUpper(), new Vector2(GameMain.GraphicsWidth / 2.0f - GUI.LargeFont.MeasureString(loadText).X / 2.0f, GameMain.GraphicsHeight * 0.7f), Color.White); } diff --git a/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs b/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs index bb67baa90..202dc9965 100644 --- a/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs +++ b/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs @@ -85,6 +85,28 @@ namespace Barotrauma return true; }; + characterListBox = new GUIListBox(new RectTransform(new Point(100, (int)(crewArea.Rect.Height - scrollButtonSize.Y * 1.6f)), crewArea.RectTransform, Anchor.CenterLeft), false, Color.Transparent, null) + { + //Spacing = (int)(3 * GUI.Scale), + ScrollBarEnabled = false, + ScrollBarVisible = false, + CanBeFocused = false + }; + + scrollButtonUp = new GUIButton(new RectTransform(scrollButtonSize, crewArea.RectTransform, Anchor.TopLeft, Pivot.TopLeft), "", Alignment.Center, "GUIButtonVerticalArrow") + { + Visible = false, + UserData = -1, + OnClicked = ScrollCharacterList + }; + scrollButtonDown = new GUIButton(new RectTransform(scrollButtonSize, crewArea.RectTransform, Anchor.BottomLeft, Pivot.BottomLeft), "", Alignment.Center, "GUIButtonVerticalArrow") + { + Visible = false, + UserData = 1, + OnClicked = ScrollCharacterList + }; + scrollButtonDown.Children.ForEach(c => c.SpriteEffects = SpriteEffects.FlipVertically); + var characterInfo = new CharacterInfo(subElement); characterInfos.Add(characterInfo); foreach (XElement invElement in subElement.Elements()) @@ -143,6 +165,16 @@ namespace Barotrauma prevUIScale = GUI.Scale; } + + #endregion + + #region Character list management + + public Rectangle GetCharacterListArea() + { + return characterListBox.Rect; + } + partial void InitProjectSpecific() { guiFrame = new GUIFrame(new RectTransform(Vector2.One, GUICanvas.Instance), null, Color.Transparent) @@ -243,6 +275,7 @@ namespace Barotrauma { if (Character.Controlled == null || Character.Controlled.SpeechImpediment >= 100.0f) return false; SetCharacterOrder(null, order, null, Character.Controlled); + HumanAIController.PropagateHullSafety(Character.Controlled, Character.Controlled.CurrentHull); return true; }, UserData = order, @@ -333,8 +366,6 @@ namespace Barotrauma DebugConsole.ThrowError("Tried to add the same character info to CrewManager twice.\n" + Environment.StackTrace); return; } - if (characterInfos.Contains(revivedCharacter.Info)) AddCharacter(revivedCharacter); - } characterInfos.Add(characterInfo); } @@ -1216,9 +1247,7 @@ namespace Barotrauma characters.Contains(Character.Controlled)) { //deselect construction unless it's the ladders the character is climbing - if (Character.Controlled != null && - Character.Controlled.SelectedConstruction != null && - Character.Controlled.SelectedConstruction.GetComponent() == null) + if (Character.Controlled != null && !Character.Controlled.IsClimbing) { Character.Controlled.SelectedConstruction = null; } @@ -1329,31 +1358,6 @@ namespace Barotrauma if (GameMain.NetworkMember != null) GameMain.Client.SelectCrewCharacter(character, previewPlayer); - ToggleReportButton("reportintruders", hasIntruders); - - foreach (GUIComponent reportButton in reportButtonFrame.Children) - { - var highlight = reportButton.GetChildByUserData("highlighted"); - if (highlight.Visible) - { - highlight.RectTransform.LocalScale = new Vector2(1.25f + (float)Math.Sin(Timing.TotalTime * 5.0f) * 0.25f); - } - } - } - else - { - reportButtonFrame.Visible = false; - } - } - - /// - /// Should report buttons be visible on the screen atm? - /// - private bool ReportButtonsVisible() - { - return CharacterHealth.OpenHealthWindow == null; - } - private bool ReportButtonClicked(GUIButton button, object userData) { //order targeted to all characters @@ -1425,18 +1429,20 @@ namespace Barotrauma return CharacterHealth.OpenHealthWindow == null; } - private bool ReportButtonClicked(GUIButton button, object userData) - { - //order targeted to all characters - Order order = userData as Order; - if (order.TargetAllCharacters) - { - if (Character.Controlled == null || Character.Controlled.CurrentHull == null) return false; - AddOrder(new Order(order.Prefab, Character.Controlled.CurrentHull, null), order.Prefab.FadeOutTime); - SetCharacterOrder(null, order, "", Character.Controlled); - } - return true; - } + // TODO: remove? not used at all + //private bool ReportButtonClicked(GUIButton button, object userData) + //{ + // //order targeted to all characters + // Order order = userData as Order; + // if (order.TargetAllCharacters) + // { + // if (Character.Controlled == null || Character.Controlled.CurrentHull == null) return false; + // AddOrder(new Order(order.Prefab, Character.Controlled.CurrentHull, null), order.Prefab.FadeOutTime); + // HumanAIController.PropagateHullSafety(Character.Controlled, Character.Controlled.CurrentHull); + // SetCharacterOrder(null, order, "", Character.Controlled); + // } + // return true; + //} private void ToggleReportButton(string orderAiTag, bool enabled) { diff --git a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/BasicTutorial.cs b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/BasicTutorial.cs index a4c34f693..c06fc83f9 100644 --- a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/BasicTutorial.cs +++ b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/BasicTutorial.cs @@ -583,8 +583,8 @@ namespace Barotrauma.Tutorials yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running; } } - - if (brokenBox != null && brokenBox.Condition > brokenBox.Prefab.Health / 2.0f && pump.Voltage < pump.MinVoltage) + + if (brokenBox != null && brokenBox.ConditionPercentage > 50.0f && pump.Voltage < pump.MinVoltage) { yield return new WaitForSeconds(1.0f); @@ -670,7 +670,7 @@ namespace Barotrauma.Tutorials Vector2 steering = targetPos - enemy.WorldPosition; if (steering != Vector2.Zero) steering = Vector2.Normalize(steering); - enemy.AIController.Steering = steering * enemy.AnimController.GetCurrentSpeed(true); + enemy.AIController.Steering = steering; yield return CoroutineStatus.Running; } while (capacitors.FirstOrDefault(c => c.Charge > 0.4f) == null); diff --git a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/ContextualTutorial.cs b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/ContextualTutorial.cs index bea1057a0..843b7566e 100644 --- a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/ContextualTutorial.cs +++ b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/ContextualTutorial.cs @@ -325,7 +325,7 @@ namespace Barotrauma.Tutorials foreach (Item item in Item.ItemList) { - if (!item.Repairables.Any() || item.Condition > item.Prefab.Health / 2.0f) continue; + if (!item.Repairables.Any() || item.ConditionPercentage > 50) continue; degradedEquipmentFound = true; break; } diff --git a/Barotrauma/BarotraumaClient/Source/GameSettings.cs b/Barotrauma/BarotraumaClient/Source/GameSettings.cs index 00085898f..a111ddc03 100644 --- a/Barotrauma/BarotraumaClient/Source/GameSettings.cs +++ b/Barotrauma/BarotraumaClient/Source/GameSettings.cs @@ -626,6 +626,28 @@ namespace Barotrauma new GUIMessageBox(TextManager.Get("RestartRequiredLabel"), TextManager.Get("RestartRequiredLanguage")); + return true; + }; + + //spacing + new GUIFrame(new RectTransform(new Vector2(1.0f, 0.02f), generalLayoutGroup.RectTransform), style: null); + + new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), buttonArea.RectTransform, Anchor.BottomLeft), + TextManager.Get("Cancel")) + { + IgnoreLayoutGroups = true, + OnClicked = (x, y) => + { + if (UnsavedSettings) + { + LoadPlayerConfig(); + } + if (Screen.Selected == GameMain.MainMenuScreen) GameMain.MainMenuScreen.ReturnToMainMenu(null, null); + GUI.SettingsMenuOpen = false; + return true; + } + }; + //spacing new GUIFrame(new RectTransform(new Vector2(1.0f, 0.02f), generalLayoutGroup.RectTransform), style: null); diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/ItemComponent.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/ItemComponent.cs index 72589ccf5..94a9eee32 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/ItemComponent.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/ItemComponent.cs @@ -184,6 +184,63 @@ namespace Barotrauma.Items.Components } } } + + public void ApplyTo(RectTransform target) + { + if (RelativeOffset.HasValue) + { + target.RelativeOffset = RelativeOffset.Value; + } + else if (AbsoluteOffset.HasValue) + { + target.AbsoluteOffset = AbsoluteOffset.Value; + } + if (RelativeSize.HasValue) + { + target.RelativeSize = RelativeSize.Value; + } + else if (AbsoluteSize.HasValue) + { + target.NonScaledSize = AbsoluteSize.Value; + } + if (Anchor.HasValue) + { + target.Anchor = Anchor.Value; + } + if (Pivot.HasValue) + { + target.Pivot = Pivot.Value; + } + else + { + target.Pivot = RectTransform.MatchPivotToAnchor(target.Anchor); + } + target.RecalculateChildren(true, true); + } + } + + public GUIFrame GuiFrame { get; protected set; } + + [Serialize(false, false)] + public bool AllowUIOverlap + { + get; + set; + } + + private ItemComponent linkToUIComponent; + [Serialize("", false)] + public string LinkUIToComponent + { + get; + set; + } + + [Serialize(0, false)] + public int HudPriority + { + get; + private set; } private bool shouldMuffleLooping; diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/MiniMap.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/MiniMap.cs index 3dc0983d1..b0ad89489 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/MiniMap.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/MiniMap.cs @@ -78,8 +78,8 @@ namespace Barotrauma.Items.Components { CreateHUD(); } - - float distort = 1.0f - item.Condition / item.Prefab.Health; + + float distort = 1.0f - item.Condition / item.MaxCondition; foreach (HullData hullData in hullDatas.Values) { hullData.DistortionTimer -= deltaTime; diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Sonar.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Sonar.cs index be6007236..da6fe39d0 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Sonar.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Sonar.cs @@ -199,8 +199,8 @@ namespace Barotrauma.Items.Components zoomSlider.OnMoved(zoomSlider, zoomSlider.BarScroll); } } - - float distort = 1.0f - item.Condition / item.Prefab.Health; + + float distort = 1.0f - item.Condition / item.MaxCondition; for (int i = sonarBlips.Count - 1; i >= 0; i--) { sonarBlips[i].FadeTimer -= deltaTime * MathHelper.Lerp(0.5f, 2.0f, distort); @@ -897,8 +897,8 @@ namespace Barotrauma.Items.Components private void DrawBlip(SpriteBatch spriteBatch, SonarBlip blip, Vector2 transducerPos, Vector2 center, float strength) { strength = MathHelper.Clamp(strength, 0.0f, 1.0f); - - float distort = 1.0f - item.Condition / item.Prefab.Health; + + float distort = 1.0f - item.Condition / item.MaxCondition; Vector2 pos = (blip.Position - transducerPos) * displayScale * zoom; pos.Y = -pos.Y; diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/RepairTool.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/RepairTool.cs index d465bbc39..9b14effac 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/RepairTool.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/RepairTool.cs @@ -108,7 +108,7 @@ namespace Barotrauma.Items.Components var progressBar = user.UpdateHUDProgressBar( targetItem, progressBarPos, - targetItem.Prefab.Health <= 0.0f ? 0.0f : targetItem.Condition / targetItem.Prefab.Health, + targetItem.Condition / item.MaxCondition, Color.Red, Color.Green); if (progressBar != null) { progressBar.Size = new Vector2(60.0f, 20.0f); } } diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Repairable.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Repairable.cs index af3aad768..b1871cfe0 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Repairable.cs @@ -27,7 +27,7 @@ namespace Barotrauma.Items.Components public override bool ShouldDrawHUD(Character character) { if (!HasRequiredItems(character, false) || character.SelectedConstruction != item) return false; - return (item.Condition < ShowRepairUIThreshold || (currentFixer == character && item.Condition < item.Prefab.Health)); + return (item.Condition < ShowRepairUIThreshold || (currentFixer == character && !item.IsFullCondition)); } partial void InitProjSpecific(XElement element) @@ -90,7 +90,7 @@ namespace Barotrauma.Items.Components { for (int i = 0; i < particleEmitters.Count; i++) { - if (item.Condition >= particleEmitterConditionRanges[i].X && item.Condition <= particleEmitterConditionRanges[i].Y) + if (item.ConditionPercentage >= particleEmitterConditionRanges[i].X && item.ConditionPercentage <= particleEmitterConditionRanges[i].Y) { particleEmitters[i].Emit(deltaTime, item.WorldPosition, item.CurrentHull); } @@ -101,8 +101,8 @@ namespace Barotrauma.Items.Components { IsActive = true; - progressBar.BarSize = item.Condition / item.Prefab.Health; - progressBar.Color = ToolBox.GradientLerp(item.Condition / item.Prefab.Health, Color.Red, Color.Orange, Color.Green); + progressBar.BarSize = item.Condition / item.MaxCondition; + progressBar.Color = ToolBox.GradientLerp(progressBar.BarSize, Color.Red, Color.Orange, Color.Green); repairButton.Enabled = currentFixer == null; repairButton.Text = currentFixer == null ? diff --git a/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs b/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs index 5835221a6..96adb2e97 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs @@ -784,12 +784,12 @@ namespace Barotrauma if (item != null && drawItem) { - if (item.Condition < item.Prefab.Health && (itemContainer == null || !itemContainer.ShowConditionInContainedStateIndicator)) + if (!item.IsFullCondition && (itemContainer == null || !itemContainer.ShowConditionInContainedStateIndicator)) { GUI.DrawRectangle(spriteBatch, new Rectangle(rect.X, rect.Bottom - 8, rect.Width, 8), Color.Black * 0.8f, true); GUI.DrawRectangle(spriteBatch, - new Rectangle(rect.X, rect.Bottom - 8, (int)(rect.Width * item.Condition / item.Prefab.Health), 8), - Color.Lerp(Color.Red, Color.Green, item.Condition / item.Prefab.Health) * 0.8f, true); + new Rectangle(rect.X, rect.Bottom - 8, (int)(rect.Width * item.Condition / item.MaxCondition), 8), + Color.Lerp(Color.Red, Color.Green, item.Condition / item.MaxCondition) * 0.8f, true); } if (itemContainer != null) @@ -797,12 +797,12 @@ namespace Barotrauma float containedState = 0.0f; if (itemContainer.ShowConditionInContainedStateIndicator) { - containedState = item.Condition / item.Prefab.Health; + containedState = item.Condition / item.MaxCondition; } else { containedState = itemContainer.Inventory.Capacity == 1 ? - (itemContainer.Inventory.Items[0] == null ? 0.0f : itemContainer.Inventory.Items[0].Condition / item.Prefab.Health) : + (itemContainer.Inventory.Items[0] == null ? 0.0f : itemContainer.Inventory.Items[0].Condition / itemContainer.Inventory.Items[0].MaxCondition) : itemContainer.Inventory.Items.Count(i => i != null) / (float)itemContainer.Inventory.capacity; } diff --git a/Barotrauma/BarotraumaClient/Source/Items/Item.cs b/Barotrauma/BarotraumaClient/Source/Items/Item.cs index cdda06bfc..9c7b97434 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Item.cs @@ -240,22 +240,23 @@ namespace Barotrauma var holdable = GetComponent(); if (holdable != null && holdable.Picker?.AnimController != null) { + float depthStep = 0.000001f; if (holdable.Picker.SelectedItems[0] == this) { Limb holdLimb = holdable.Picker.AnimController.GetLimb(LimbType.RightHand); - depth = holdLimb.ActiveSprite.Depth + 0.000001f; + depth = holdLimb.ActiveSprite.Depth + depthStep * 2; foreach (WearableSprite wearableSprite in holdLimb.WearingItems) { - if (!wearableSprite.InheritLimbDepth && wearableSprite.Sprite != null) depth = Math.Min(wearableSprite.Sprite.Depth, depth); + if (!wearableSprite.InheritLimbDepth && wearableSprite.Sprite != null) depth = Math.Max(wearableSprite.Sprite.Depth + depthStep, depth); } } else if (holdable.Picker.SelectedItems[1] == this) { Limb holdLimb = holdable.Picker.AnimController.GetLimb(LimbType.LeftHand); - depth = holdLimb.ActiveSprite.Depth - 0.000001f; + depth = holdLimb.ActiveSprite.Depth - depthStep * 2; foreach (WearableSprite wearableSprite in holdLimb.WearingItems) { - if (!wearableSprite.InheritLimbDepth && wearableSprite.Sprite != null) depth = Math.Max(wearableSprite.Sprite.Depth, depth); + if (!wearableSprite.InheritLimbDepth && wearableSprite.Sprite != null) depth = Math.Min(wearableSprite.Sprite.Depth - depthStep, depth); } } } @@ -844,7 +845,7 @@ namespace Barotrauma msg.WritePadBits(); } - partial void UpdateNetPosition() + partial void UpdateNetPosition(float deltaTime) { if (GameMain.Client == null) { return; } diff --git a/Barotrauma/BarotraumaClient/Source/Screens/CharacterEditorScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/CharacterEditorScreen.cs index fd3e9fedc..523b82cc7 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/CharacterEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/CharacterEditorScreen.cs @@ -401,6 +401,43 @@ namespace Barotrauma UpdateSourceRect(limb, newRect); } } + UpdateJointCreation(); + if (PlayerInput.KeyHit(Keys.Left)) + { + foreach (var limb in selectedLimbs) + { + var newRect = limb.ActiveSprite.SourceRect; + newRect.X--; + UpdateSourceRect(limb, newRect); + } + } + if (PlayerInput.KeyHit(Keys.Right)) + { + foreach (var limb in selectedLimbs) + { + var newRect = limb.ActiveSprite.SourceRect; + newRect.X++; + UpdateSourceRect(limb, newRect); + } + } + if (PlayerInput.KeyHit(Keys.Down)) + { + foreach (var limb in selectedLimbs) + { + var newRect = limb.ActiveSprite.SourceRect; + newRect.Y++; + UpdateSourceRect(limb, newRect); + } + } + if (PlayerInput.KeyHit(Keys.Up)) + { + foreach (var limb in selectedLimbs) + { + var newRect = limb.ActiveSprite.SourceRect; + newRect.Y--; + UpdateSourceRect(limb, newRect); + } + } } if (!isFreezed) { @@ -2551,7 +2588,7 @@ namespace Barotrauma { var sourceRect = targetLimb.ActiveSprite.SourceRect; Vector2 size = sourceRect.Size.ToVector2() * Cam.Zoom * targetLimb.Scale * targetLimb.TextureScale; - Vector2 up = VectorExtensions.Backward(targetLimb.Rotation); + Vector2 up = VectorExtensions.BackwardFlipped(targetLimb.Rotation); Vector2 left = up.Right(); Vector2 limbScreenPos = SimToScreen(targetLimb.SimPosition); var offset = targetLimb.ActiveSprite.RelativeOrigin.X * left + targetLimb.ActiveSprite.RelativeOrigin.Y * up; @@ -2656,12 +2693,12 @@ namespace Barotrauma //Vector2 centerOfMass = character.AnimController.GetCenterOfMass(); Vector2 simSpaceForward = Vector2.Transform(Vector2.UnitY, Matrix.CreateRotationZ(collider.Rotation)); //Vector2 simSpaceLeft = Vector2.Transform(-Vector2.UnitX, Matrix.CreateRotationZ(collider.Rotation)); - Vector2 screenSpaceForward = VectorExtensions.Backward(collider.Rotation, 1); + Vector2 screenSpaceForward = VectorExtensions.BackwardFlipped(collider.Rotation, 1); Vector2 screenSpaceLeft = screenSpaceForward.Right(); // The forward vector is left or right in screen space when the unit is not swimming. Cannot rely on the collider here, because the rotation may vary on ground. Vector2 forward = animParams.IsSwimAnimation ? screenSpaceForward : Vector2.UnitX * dir; Vector2 GetSimSpaceForward() => animParams.IsSwimAnimation ? Vector2.Transform(Vector2.UnitY, Matrix.CreateRotationZ(collider.Rotation)) : Vector2.UnitX * character.AnimController.Dir; - Vector2 GetScreenSpaceForward() => animParams.IsSwimAnimation ? VectorExtensions.Backward(collider.Rotation, 1) : Vector2.UnitX * character.AnimController.Dir; + Vector2 GetScreenSpaceForward() => animParams.IsSwimAnimation ? VectorExtensions.BackwardFlipped(collider.Rotation, 1) : Vector2.UnitX * character.AnimController.Dir; bool ShowCycleWidget() => PlayerInput.KeyDown(Keys.LeftAlt) && (CurrentAnimation is IHumanAnimation || CurrentAnimation is GroundedMovementParams); bool altDown = PlayerInput.KeyDown(Keys.LeftAlt); @@ -3196,7 +3233,7 @@ namespace Barotrauma private Vector2[] GetLimbPhysicRect(Limb limb) { Vector2 size = ConvertUnits.ToDisplayUnits(limb.body.GetSize()) * Cam.Zoom; - Vector2 up = VectorExtensions.Backward(limb.Rotation); + Vector2 up = VectorExtensions.BackwardFlipped(limb.Rotation); Vector2 limbScreenPos = SimToScreen(limb.SimPosition); corners = MathUtils.GetImaginaryRect(corners, up, limbScreenPos, size); return corners; @@ -3343,7 +3380,7 @@ namespace Barotrauma DrawJointLimitWidgets(spriteBatch, limb, joint, tformedJointPos, autoFreeze: true, allowPairEditing: true, rotationOffset: limb.Rotation); } // Is the direction inversed incorrectly? - Vector2 to = tformedJointPos + VectorExtensions.Forward(joint.LimbB.Rotation + MathHelper.ToRadians(-RagdollParams.SpritesheetOrientation), 20); + Vector2 to = tformedJointPos + VectorExtensions.ForwardFlipped(joint.LimbB.Rotation + MathHelper.ToRadians(-RagdollParams.SpritesheetOrientation), 20); GUI.DrawLine(spriteBatch, tformedJointPos, to, Color.Magenta, width: 2); var dotSize = new Vector2(5, 5); var rect = new Rectangle((tformedJointPos - dotSize / 2).ToPoint(), dotSize.ToPoint()); @@ -3361,7 +3398,7 @@ namespace Barotrauma } Vector2 input = ConvertUnits.ToSimUnits(scaledMouseSpeed) / Cam.Zoom; input.Y = -input.Y; - input = input.TransformVector(VectorExtensions.Forward(limb.Rotation)); + input = input.TransformVector(VectorExtensions.ForwardFlipped(limb.Rotation)); if (joint.BodyA == limb.body.FarseerBody) { joint.LocalAnchorA += input; @@ -4028,7 +4065,7 @@ namespace Barotrauma angle = 0; } float drawAngle = clockWise ? -angle : angle; - var widgetDrawPos = drawPos + VectorExtensions.Forward(MathHelper.ToRadians(drawAngle) + rotationOffset, circleRadius); + var widgetDrawPos = drawPos + VectorExtensions.ForwardFlipped(MathHelper.ToRadians(drawAngle) + rotationOffset, circleRadius); GUI.DrawLine(spriteBatch, drawPos, widgetDrawPos, color); DrawWidget(spriteBatch, widgetDrawPos, WidgetType.Rectangle, widgetSize, color, toolTip, () => { @@ -4044,7 +4081,7 @@ namespace Barotrauma GUI.DrawString(spriteBatch, drawPos, angle.FormatZeroDecimal(), Color.Black, backgroundColor: color, font: GUI.SmallFont); } onClick(angle); - var zeroPos = drawPos + VectorExtensions.Forward(rotationOffset, circleRadius); + var zeroPos = drawPos + VectorExtensions.ForwardFlipped(rotationOffset, circleRadius); GUI.DrawLine(spriteBatch, drawPos, zeroPos, Color.Red, width: 3); }, autoFreeze, onHovered: () => { diff --git a/Barotrauma/BarotraumaClient/Source/Screens/MainMenuScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/MainMenuScreen.cs index 487aaffec..ca9b6c880 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/MainMenuScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/MainMenuScreen.cs @@ -33,22 +33,203 @@ namespace Barotrauma private Tab selectedTab; + private Sprite backgroundSprite; + #region Creation public MainMenuScreen(GameMain game) { - buttonsParent = new GUILayoutGroup(new RectTransform(new Vector2(0.15f, 0.5f), parent: Frame.RectTransform, anchor: Anchor.BottomLeft) + buttonsParent = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 0.85f), parent: Frame.RectTransform, anchor: Anchor.BottomLeft, pivot: Pivot.BottomLeft) { - RelativeOffset = new Vector2(0, 0.1f), + RelativeOffset = new Vector2(0, 0), AbsoluteOffset = new Point(50, 0) }) { Stretch = true, RelativeSpacing = 0.02f }; + + // === CAMPAIGN + var campaignHolder = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), parent: buttonsParent.RectTransform) { RelativeOffset = new Vector2(0.1f, 0.0f) }, isHorizontal: true); + + new GUIImage(new RectTransform(new Vector2(0.2f, 0.7f), campaignHolder.RectTransform), "MainMenuCampaignIcon") + { + CanBeFocused = false + }; + + //spacing + new GUIFrame(new RectTransform(new Vector2(0.02f, 0.0f), campaignHolder.RectTransform), style: null); + + var campaignNavigation = new GUILayoutGroup(new RectTransform(new Vector2(0.75f, 0.75f), parent: campaignHolder.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.25f) }); + + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), campaignNavigation.RectTransform), + TextManager.Get("CampaignLabel"), textAlignment: Alignment.Left, font: GUI.LargeFont, textColor: Color.Black, style: "MainMenuGUITextBlock") { ForceUpperCase = true }; + + var campaignButtons = new GUIFrame(new RectTransform(new Vector2(1.0f, 1.0f), parent: campaignNavigation.RectTransform), style: "MainMenuGUIFrame"); + + var campaignList = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.15f), parent: campaignButtons.RectTransform)) + { + Stretch = false, + RelativeSpacing = 0.035f + }; + + new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), campaignList.RectTransform), TextManager.Get("LoadGameButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton") + { + ForceUpperCase = true, + UserData = Tab.LoadGame, + OnClicked = (tb, userdata) => + { + SelectTab(tb, userdata); + return true; + } + }; + + new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), campaignList.RectTransform), TextManager.Get("NewGameButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton") + { + ForceUpperCase = true, + UserData = Tab.NewGame, + OnClicked = (tb, userdata) => + { + SelectTab(tb, userdata); + return true; + } + }; + + // === MULTIPLAYER + var multiplayerHolder = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), parent: buttonsParent.RectTransform) { RelativeOffset = new Vector2(0.05f, 0.0f) }, isHorizontal: true); + + new GUIImage(new RectTransform(new Vector2(0.2f, 0.7f), multiplayerHolder.RectTransform), "MainMenuMultiplayerIcon") + { + CanBeFocused = false + }; + + //spacing + new GUIFrame(new RectTransform(new Vector2(0.02f, 0.0f), multiplayerHolder.RectTransform), style: null); + + var multiplayerNavigation = new GUILayoutGroup(new RectTransform(new Vector2(0.75f, 0.75f), parent: multiplayerHolder.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.25f) }); + + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), multiplayerNavigation.RectTransform), + TextManager.Get("MultiplayerLabel"), textAlignment: Alignment.Left, font: GUI.LargeFont, textColor: Color.Black, style: "MainMenuGUITextBlock") { ForceUpperCase = true }; + + var multiplayerButtons = new GUIFrame(new RectTransform(new Vector2(1.0f, 1.0f), parent: multiplayerNavigation.RectTransform), style: "MainMenuGUIFrame"); + + var multiplayerList = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.15f), parent: multiplayerButtons.RectTransform)) + { + Stretch = false, + RelativeSpacing = 0.035f + }; + + joinServerButton = new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), multiplayerList.RectTransform), TextManager.Get("JoinServerButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton") + { + ForceUpperCase = true, + UserData = Tab.JoinServer, + OnClicked = (tb, userdata) => + { + SelectTab(tb, userdata); + return true; + } + }; + hostServerButton = new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), multiplayerList.RectTransform), TextManager.Get("HostServerButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton") + { + ForceUpperCase = true, + UserData = Tab.HostServer, + OnClicked = (tb, userdata) => + { + SelectTab(tb, userdata); + return true; + } + }; + + // === CUSTOMIZE + var customizeHolder = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), parent: buttonsParent.RectTransform) { RelativeOffset = new Vector2(0.15f, 0.0f) }, isHorizontal: true); + + new GUIImage(new RectTransform(new Vector2(0.2f, 0.7f), customizeHolder.RectTransform), "MainMenuCustomizeIcon") + { + CanBeFocused = false + }; + + //spacing + new GUIFrame(new RectTransform(new Vector2(0.02f, 0.0f), customizeHolder.RectTransform), style: null); + + var customizeNavigation = new GUILayoutGroup(new RectTransform(new Vector2(0.75f, 0.75f), parent: customizeHolder.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.25f) }); + + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), customizeNavigation.RectTransform), + TextManager.Get("CustomizeLabel"), textAlignment: Alignment.Left, font: GUI.LargeFont, textColor: Color.Black, style: "MainMenuGUITextBlock") { ForceUpperCase = true }; + + var customizeButtons = new GUIFrame(new RectTransform(new Vector2(1.0f, 1.0f), parent: customizeNavigation.RectTransform), style: "MainMenuGUIFrame"); + + var customizeList = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.15f), parent: customizeButtons.RectTransform)) + { + Stretch = false, + RelativeSpacing = 0.035f + }; + + new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), customizeList.RectTransform), TextManager.Get("SubEditorButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton") + { + ForceUpperCase = true, + UserData = Tab.SubmarineEditor, + OnClicked = (tb, userdata) => + { + SelectTab(tb, userdata); + return true; + } + }; + + new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), customizeList.RectTransform), TextManager.Get("CharacterEditorButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton") + { + ForceUpperCase = true, + UserData = Tab.CharacterEditor, + OnClicked = (tb, userdata) => + { + SelectTab(tb, userdata); + return true; + } + }; + + if (Steam.SteamManager.USE_STEAM) + { + steamWorkshopButton = new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), customizeList.RectTransform), TextManager.Get("SteamWorkshopButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton") + { + ForceUpperCase = true, + Enabled = false, + UserData = Tab.SteamWorkshop, + OnClicked = SelectTab + }; + } + + // === OPTION + var optionHolder = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 0.5f), parent: buttonsParent.RectTransform), isHorizontal: true); + + new GUIImage(new RectTransform(new Vector2(0.15f, 0.6f), optionHolder.RectTransform), "MainMenuOptionIcon") + { + CanBeFocused = false + }; + + //spacing + new GUIFrame(new RectTransform(new Vector2(0.01f, 0.0f), optionHolder.RectTransform), style: null); + + var optionButtons = new GUILayoutGroup(new RectTransform(new Vector2(0.55f, 1.0f), parent: optionHolder.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.15f) }); + + var optionList = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.15f), parent: optionButtons.RectTransform)) + { + Stretch = false, + RelativeSpacing = 0.035f + }; + + new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), optionList.RectTransform), TextManager.Get("SettingsButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton") + { + ForceUpperCase = true, + UserData = Tab.Settings, + OnClicked = SelectTab + }; + new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), optionList.RectTransform), TextManager.Get("QuitButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton") + { + ForceUpperCase = true, + OnClicked = QuitClicked + }; //debug button for quickly starting a new round #if DEBUG - new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonsParent.RectTransform, Anchor.TopCenter, Pivot.BottomCenter) { AbsoluteOffset = new Point(0, -40) }, + new GUIButton(new RectTransform(new Vector2(0.5f, 0.1f), buttonsParent.RectTransform, Anchor.TopLeft, Pivot.BottomLeft) { AbsoluteOffset = new Point(0, -40) }, "Quickstart (dev)", style: "GUIButtonLarge", color: Color.Red) { IgnoreLayoutGroups = true, @@ -70,94 +251,6 @@ namespace Barotrauma OnClicked = SelectTab, Enabled = false };*/ - - new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), buttonsParent.RectTransform), style: null); //spacing - - new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonsParent.RectTransform), TextManager.Get("NewGameButton"), style: "GUIButtonLarge") - { - UserData = Tab.NewGame, - OnClicked = (tb, userdata) => - { - SelectTab(tb, userdata); - return true; - } - }; - new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonsParent.RectTransform), TextManager.Get("LoadGameButton"), style: "GUIButtonLarge") - { - UserData = Tab.LoadGame, - OnClicked = (tb, userdata) => - { - SelectTab(tb, userdata); - return true; - } - }; - - new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), buttonsParent.RectTransform), style: null); //spacing - - joinServerButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonsParent.RectTransform), TextManager.Get("JoinServerButton"), style: "GUIButtonLarge") - { - UserData = Tab.JoinServer, - OnClicked = (tb, userdata) => - { - SelectTab(tb, userdata); - return true; - } - }; - hostServerButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonsParent.RectTransform), TextManager.Get("HostServerButton"), style: "GUIButtonLarge") - { - UserData = Tab.HostServer, - OnClicked = (tb, userdata) => - { - SelectTab(tb, userdata); - return true; - } - }; - - - new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), buttonsParent.RectTransform), style: null); //spacing - - new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonsParent.RectTransform), TextManager.Get("SubEditorButton"), style: "GUIButtonLarge") - { - UserData = Tab.SubmarineEditor, - OnClicked = (tb, userdata) => - { - SelectTab(tb, userdata); - return true; - } - }; - - new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonsParent.RectTransform), TextManager.Get("CharacterEditorButton"), style: "GUIButtonLarge") - { - UserData = Tab.CharacterEditor, - OnClicked = (tb, userdata) => - { - SelectTab(tb, userdata); - return true; - } - }; - - if (Steam.SteamManager.USE_STEAM) - { - steamWorkshopButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonsParent.RectTransform), TextManager.Get("SteamWorkshopButton"), style: "GUIButtonLarge") - { - Enabled = false, - UserData = Tab.SteamWorkshop, - OnClicked = SelectTab - }; - } - - new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), buttonsParent.RectTransform), style: null); //spacing - - new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonsParent.RectTransform), TextManager.Get("SettingsButton"), style: "GUIButtonLarge") - { - UserData = Tab.Settings, - OnClicked = SelectTab - }; - new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonsParent.RectTransform), TextManager.Get("QuitButton"), style: "GUIButtonLarge") - { - OnClicked = QuitClicked - }; - /* var buttons = GUI.CreateButtons(9, new Vector2(1, 0.04f), buttonsParent.RectTransform, anchor: Anchor.BottomLeft, minSize: minButtonSize, maxSize: maxButtonSize, relativeSpacing: 0.005f, extraSpacing: i => i % 2 == 0 ? 20 : 0); @@ -172,9 +265,9 @@ namespace Barotrauma var pivot = Pivot.Center; menuTabs = new GUIFrame[Enum.GetValues(typeof(Tab)).Length + 1]; - menuTabs[(int)Tab.NewGame] = new GUIFrame(new RectTransform(relativeSize, Frame.RectTransform, anchor, pivot, minSize, maxSize)); + menuTabs[(int)Tab.NewGame] = new GUIFrame(new RectTransform(relativeSize, GUI.Canvas, anchor, pivot, minSize, maxSize)); var paddedNewGame = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.9f), menuTabs[(int)Tab.NewGame].RectTransform, Anchor.Center), style: null); - menuTabs[(int)Tab.LoadGame] = new GUIFrame(new RectTransform(relativeSize, Frame.RectTransform, anchor, pivot, minSize, maxSize)); + menuTabs[(int)Tab.LoadGame] = new GUIFrame(new RectTransform(relativeSize, GUI.Canvas, anchor, pivot, minSize, maxSize)); var paddedLoadGame = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.9f), menuTabs[(int)Tab.LoadGame].RectTransform, Anchor.Center), style: null); campaignSetupUI = new CampaignSetupUI(false, paddedNewGame, paddedLoadGame) @@ -185,13 +278,13 @@ namespace Barotrauma var hostServerScale = new Vector2(0.7f, 1.0f); menuTabs[(int)Tab.HostServer] = new GUIFrame(new RectTransform( - Vector2.Multiply(relativeSize, hostServerScale), Frame.RectTransform, anchor, pivot, minSize.Multiply(hostServerScale), maxSize.Multiply(hostServerScale))); + Vector2.Multiply(relativeSize, hostServerScale), GUI.Canvas, anchor, pivot, minSize.Multiply(hostServerScale), maxSize.Multiply(hostServerScale))); CreateHostServerFields(); //---------------------------------------------------------------------- - menuTabs[(int)Tab.Tutorials] = new GUIFrame(new RectTransform(relativeSize, Frame.RectTransform, anchor, pivot, minSize, maxSize)); + menuTabs[(int)Tab.Tutorials] = new GUIFrame(new RectTransform(relativeSize, GUI.Canvas, anchor, pivot, minSize, maxSize)); //PLACEHOLDER var tutorialList = new GUIListBox( @@ -540,8 +633,7 @@ namespace Barotrauma public override void AddToGUIUpdateList() { - Frame.AddToGUIUpdateList(ignoreChildren: true); - buttonsParent.AddToGUIUpdateList(); + Frame.AddToGUIUpdateList(); if (selectedTab > 0 && menuTabs[(int)selectedTab] != null) { menuTabs[(int)selectedTab].AddToGUIUpdateList(); @@ -572,12 +664,21 @@ namespace Barotrauma #endif } + public void DrawBackground(GraphicsDevice graphics, SpriteBatch spriteBatch) + { + graphics.Clear(Color.Black); + + if (backgroundSprite == null) + { + backgroundSprite = (LocationType.List.Where(l => l.UseInMainMenu).GetRandom()).GetPortrait(0); + } + + if (backgroundSprite != null) { GUI.DrawBackgroundSprite(spriteBatch, backgroundSprite); } + } + public override void Draw(double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch) { - graphics.Clear(Color.CornflowerBlue); - - GameMain.TitleScreen.DrawLoadingText = false; - GameMain.TitleScreen.Draw(spriteBatch, graphics, (float)deltaTime); + DrawBackground(graphics, spriteBatch); spriteBatch.Begin(SpriteSortMode.Deferred, null, null, null, GameMain.ScissorTestEnable); @@ -672,7 +773,7 @@ namespace Barotrauma }; GUIComponent parent = paddedFrame; - new GUITextBlock(new RectTransform(textLabelSize, parent.RectTransform), TextManager.Get("HostServerButton"), textAlignment: Alignment.Center, font: GUI.LargeFont); + new GUITextBlock(new RectTransform(textLabelSize, parent.RectTransform), TextManager.Get("HostServerButton"), textAlignment: Alignment.Center, font: GUI.LargeFont) { ForceUpperCase = true }; var label = new GUITextBlock(new RectTransform(textLabelSize, parent.RectTransform), TextManager.Get("ServerName"), textAlignment: textAlignment); serverNameBox = new GUITextBox(new RectTransform(textFieldSize, label.RectTransform, Anchor.CenterRight), textAlignment: textAlignment); diff --git a/Barotrauma/BarotraumaClient/Source/Screens/ServerListScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/ServerListScreen.cs index 52d7a1059..aca7905bb 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/ServerListScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/ServerListScreen.cs @@ -53,7 +53,7 @@ namespace Barotrauma menu = new GUIFrame(new RectTransform(new Point(width, height), GUI.Canvas, Anchor.Center)); new GUITextBlock(new RectTransform(new Vector2(0.95f, 0.133f), menu.RectTransform, Anchor.TopCenter), - TextManager.Get("JoinServer"), textAlignment: Alignment.Left, font: GUI.LargeFont); + TextManager.Get("JoinServer"), textAlignment: Alignment.Left, font: GUI.LargeFont) { ForceUpperCase = true }; var paddedFrame = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.95f), menu.RectTransform, Anchor.Center) { RelativeOffset = new Vector2(0.0f, 0.03f) }, style: null); @@ -733,7 +733,7 @@ namespace Barotrauma graphics.Clear(Color.CornflowerBlue); GameMain.TitleScreen.DrawLoadingText = false; - GameMain.TitleScreen.Draw(spriteBatch, graphics, (float)deltaTime); + GameMain.MainMenuScreen.DrawBackground(graphics, spriteBatch); spriteBatch.Begin(SpriteSortMode.Deferred, null, null, null, GameMain.ScissorTestEnable); diff --git a/Barotrauma/BarotraumaClient/Source/Sounds/SoundManager.cs b/Barotrauma/BarotraumaClient/Source/Sounds/SoundManager.cs index 549648e81..e299e79cd 100644 --- a/Barotrauma/BarotraumaClient/Source/Sounds/SoundManager.cs +++ b/Barotrauma/BarotraumaClient/Source/Sounds/SoundManager.cs @@ -269,6 +269,7 @@ namespace Barotrauma.Sounds //we couldn't get a free source to assign to this channel! return -1; } + } #if DEBUG public void DebugSource(int ind) @@ -413,6 +414,10 @@ namespace Barotrauma.Sounds { categoryModifiers[category].Second = muffle; } + else + { + categoryModifiers[category].Second = muffle; + } } for (int i = 0; i < playingChannels.Length; i++) diff --git a/Barotrauma/BarotraumaServer/Source/DebugConsole.cs b/Barotrauma/BarotraumaServer/Source/DebugConsole.cs index a1fa5069d..18a8733c4 100644 --- a/Barotrauma/BarotraumaServer/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaServer/Source/DebugConsole.cs @@ -1571,8 +1571,6 @@ namespace Barotrauma } GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.Status }); - - item.NeedsPositionUpdate = true; } } diff --git a/Barotrauma/BarotraumaServer/Source/Items/Item.cs b/Barotrauma/BarotraumaServer/Source/Items/Item.cs index a7276d891..5f88e71eb 100644 --- a/Barotrauma/BarotraumaServer/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaServer/Source/Items/Item.cs @@ -9,8 +9,6 @@ namespace Barotrauma { partial class Item : MapEntity, IDamageable, ISerializableEntity, IServerSerializable, IClientSerializable { - private bool prevBodyAwake; - public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null) { string errorMsg = ""; @@ -255,17 +253,71 @@ namespace Barotrauma } - partial void UpdateNetPosition() + partial void UpdateNetPosition(float deltaTime) { - if (parentInventory != null) return; - - if (prevBodyAwake != body.FarseerBody.Awake || - Vector2.DistanceSquared(lastSentPos, SimPosition) > NetConfig.ItemPosUpdateDistance * NetConfig.ItemPosUpdateDistance) + if (parentInventory != null || body == null || !body.Enabled || Removed) { - needsPositionUpdate = true; + PositionUpdateInterval = float.PositiveInfinity; + return; + } + + //gradually increase the interval of position updates + PositionUpdateInterval += deltaTime; + + float maxInterval = 30.0f; + + float velSqr = body.LinearVelocity.LengthSquared(); + if (velSqr > 10.0f * 10.0f) + { + //over 10 m/s (projectile, thrown item or similar) -> send updates very frequently + maxInterval = 0.1f; + } + else if (velSqr > 1.0f) + { + //over 1 m/s + maxInterval = 0.25f; + } + else if (velSqr > 0.05f * 0.05f) + { + //over 0.05 m/s + maxInterval = 1.0f; } - prevBodyAwake = body.FarseerBody.Awake; + PositionUpdateInterval = Math.Min(PositionUpdateInterval, maxInterval); + } + + public float GetPositionUpdateInterval(Client recipient) + { + if (PositionUpdateInterval == float.PositiveInfinity) + { + return float.PositiveInfinity; + } + + if (recipient.Character == null || recipient.Character.IsDead) + { + //less frequent updates for clients who aren't controlling a character (max 2 updates/sec) + return Math.Max(PositionUpdateInterval, 0.5f); + } + else + { + float distSqr = Vector2.DistanceSquared(recipient.Character.WorldPosition, WorldPosition); + if (distSqr > 20000.0f * 20000.0f) + { + //don't send position updates at all if >20 000 units away + return float.PositiveInfinity; + } + else if (distSqr > 10000.0f * 10000.0f) + { + //drop the update rate to 10% if too far to see the item + return PositionUpdateInterval * 10; + } + else if (distSqr > 1000.0f * 1000.0f) + { + //halve the update rate if the client is far away (but still close enough to possibly see the item) + return PositionUpdateInterval * 2; + } + return PositionUpdateInterval; + } } public void ServerWritePosition(NetBuffer msg, Client c, object[] extraData = null) @@ -277,8 +329,6 @@ namespace Barotrauma msg.Write((byte)tempBuffer.LengthBytes); msg.Write(tempBuffer); msg.WritePadBits(); - - lastSentPos = SimPosition; } public void CreateServerEvent(T ic) where T : ItemComponent, IServerSerializable diff --git a/Barotrauma/BarotraumaServer/Source/Networking/GameServer.cs b/Barotrauma/BarotraumaServer/Source/Networking/GameServer.cs index cbf3b4c60..9eb5a5e2f 100644 --- a/Barotrauma/BarotraumaServer/Source/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaServer/Source/Networking/GameServer.cs @@ -614,10 +614,6 @@ namespace Barotrauma.Networking } } - foreach (Item item in Item.ItemList) - { - item.NeedsPositionUpdate = false; - } foreach (Character character in Character.CharacterList) { if (character.healthUpdateTimer <= 0.0f) @@ -1229,7 +1225,10 @@ namespace Barotrauma.Networking foreach (Item item in Item.ItemList) { - if (!item.NeedsPositionUpdate) continue; + if (item.PositionUpdateInterval == float.PositiveInfinity) { continue; } + float updateInterval = item.GetPositionUpdateInterval(c); + c.PositionUpdateLastSent.TryGetValue(item.ID, out float lastSent); + if (lastSent > NetTime.Now - item.PositionUpdateInterval) { continue; } if (!c.PendingPositionUpdates.Contains(item)) c.PendingPositionUpdates.Enqueue(item); } } @@ -2023,8 +2022,6 @@ namespace Barotrauma.Networking targetmsg += $"/\n/ServerMessage.Reason/: /{reason}"; } - Log(msg, ServerLog.MessageType.ServerMessage); - if (client.SteamID > 0) { SteamManager.StopAuthSession(client.SteamID); } client.Connection.Disconnect(targetmsg); @@ -2278,8 +2275,7 @@ namespace Barotrauma.Networking if (type.Value != ChatMessageType.MessageBox) { - string myReceivedMessage = TextManager.GetServerMessage(message); - + string myReceivedMessage = type == ChatMessageType.Server || type == ChatMessageType.Error ? TextManager.GetServerMessage(message) : message; if (!string.IsNullOrWhiteSpace(myReceivedMessage) && (targetClient == null || senderClient == null)) { diff --git a/Barotrauma/BarotraumaShared/SharedCode.projitems b/Barotrauma/BarotraumaShared/SharedCode.projitems index 73a0eb8dd..f1aa81dab 100644 --- a/Barotrauma/BarotraumaShared/SharedCode.projitems +++ b/Barotrauma/BarotraumaShared/SharedCode.projitems @@ -33,6 +33,7 @@ + @@ -47,7 +48,6 @@ - diff --git a/Barotrauma/BarotraumaShared/SharedContent.projitems b/Barotrauma/BarotraumaShared/SharedContent.projitems index ffaadaf7a..d5ac9408f 100644 --- a/Barotrauma/BarotraumaShared/SharedContent.projitems +++ b/Barotrauma/BarotraumaShared/SharedContent.projitems @@ -23,7 +23,6 @@ - @@ -343,6 +342,96 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + PreserveNewest @@ -1808,6 +1897,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -3017,4 +3109,4 @@ PreserveNewest - + \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/EnemyAIController.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/EnemyAIController.cs index 625444e44..7c7164b11 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/EnemyAIController.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/EnemyAIController.cs @@ -356,7 +356,6 @@ namespace Barotrauma private void UpdateIdle(float deltaTime) { - float speed = Character.AnimController.GetCurrentSpeed(false); if (Character.Submarine == null && SimPosition.Y < ConvertUnits.ToSimUnits(Character.CharacterHealth.CrushDepth * 0.75f)) { //steer straight up if very deep @@ -370,17 +369,17 @@ namespace Barotrauma { Vector2 targetSimPos = Character.Submarine == null ? ConvertUnits.ToSimUnits(SelectedAiTarget.WorldPosition) : SelectedAiTarget.SimPosition; - steeringManager.SteeringAvoid(deltaTime, colliderSize * 3.0f, speed); - steeringManager.SteeringSeek(targetSimPos, speed); + steeringManager.SteeringAvoid(deltaTime, colliderSize * 3.0f); + steeringManager.SteeringSeek(targetSimPos); } else { //wander around randomly if (Character.Submarine == null) { - steeringManager.SteeringAvoid(deltaTime, colliderSize * 5.0f, speed); + steeringManager.SteeringAvoid(deltaTime, colliderSize * 5.0f); } - steeringManager.SteeringWander(speed / 2); + steeringManager.SteeringWander(0.5f); } } @@ -398,12 +397,11 @@ namespace Barotrauma Vector2 escapeDir = Vector2.Normalize(SimPosition - SelectedAiTarget.SimPosition); if (!MathUtils.IsValid(escapeDir)) escapeDir = Vector2.UnitY; - float speed = Character.AnimController.GetCurrentSpeed(useMaxSpeed: true); - SteeringManager.SteeringManual(deltaTime, escapeDir * speed); - SteeringManager.SteeringWander(speed); + SteeringManager.SteeringManual(deltaTime, escapeDir); + SteeringManager.SteeringWander(); if (Character.CurrentHull == null) { - SteeringManager.SteeringAvoid(deltaTime, colliderSize * 3.0f, speed); + SteeringManager.SteeringAvoid(deltaTime, colliderSize * 3.0f); } } @@ -419,8 +417,6 @@ namespace Barotrauma return; } - float speed = Character.AnimController.GetCurrentSpeed(true); - selectedTargetMemory.Priority -= deltaTime * 0.1f; Vector2 attackSimPosition = Character.Submarine == null ? ConvertUnits.ToSimUnits(SelectedAiTarget.WorldPosition) : SelectedAiTarget.SimPosition; @@ -447,9 +443,12 @@ namespace Barotrauma if (wallTarget != null) { attackSimPosition = ConvertUnits.ToSimUnits(wallTarget.Position); - if (Character.Submarine == null && SelectedAiTarget.Entity?.Submarine != null) attackSimPosition += ConvertUnits.ToSimUnits(SelectedAiTarget.Entity.Submarine.Position); + if (Character.Submarine == null && SelectedAiTarget.Entity?.Submarine != null) + { + attackSimPosition += ConvertUnits.ToSimUnits(SelectedAiTarget.Entity.Submarine.Position); + } } - else if (SelectedAiTarget.Entity is Character) + else if (SelectedAiTarget.Entity is Character c) { //target the closest limb if the target is a character float closestDist = Vector2.DistanceSquared(SelectedAiTarget.SimPosition, SimPosition) * 10.0f; @@ -503,11 +502,11 @@ namespace Barotrauma Character.AnimController.ReleaseStuckLimbs(); if (steeringManager is IndoorsSteeringManager) { - steeringManager.SteeringManual(deltaTime, targetPos - Character.WorldPosition); + steeringManager.SteeringManual(deltaTime, Vector2.Normalize(targetPos - Character.WorldPosition)); } else { - steeringManager.SteeringSeek(ConvertUnits.ToSimUnits(targetPos), speed); + steeringManager.SteeringSeek(ConvertUnits.ToSimUnits(targetPos)); } return; } @@ -518,11 +517,12 @@ namespace Barotrauma //steer through the door manually if it's open or broken if (door?.LinkedGap?.FlowTargetHull != null && !door.LinkedGap.IsRoomToRoom && (door.IsOpen || door.Item.Condition <= 0.0f)) { + var velocity = Vector2.Normalize(door.LinkedGap.FlowTargetHull.WorldPosition - Character.WorldPosition); if (door.LinkedGap.IsHorizontal) { if (Character.WorldPosition.Y < door.Item.WorldRect.Y && Character.WorldPosition.Y > door.Item.WorldRect.Y - door.Item.Rect.Height) { - var velocity = Vector2.UnitX * (door.LinkedGap.FlowTargetHull.WorldPosition.X - Character.WorldPosition.X) * speed; + velocity.Y = 0; steeringManager.SteeringManual(deltaTime, velocity); return; } @@ -531,7 +531,7 @@ namespace Barotrauma { if (Character.WorldPosition.X < door.Item.WorldRect.X && Character.WorldPosition.X > door.Item.WorldRect.Right) { - var velocity = Vector2.UnitY * (door.LinkedGap.FlowTargetHull.WorldPosition.Y - Character.WorldPosition.Y) * speed; + velocity.X = 0; steeringManager.SteeringManual(deltaTime, velocity); return; } @@ -638,10 +638,10 @@ namespace Barotrauma Vector2 targetingVector = Vector2.Normalize(steeringVector) * attackingLimb.attack.Range; // Offset the position a bit so that we don't overshoot the movement. Vector2 steerPos = attackSimPosition + targetingVector; - steeringManager.SteeringSeek(steerPos, speed); + steeringManager.SteeringSeek(steerPos, 10); if (Character.CurrentHull == null) { - SteeringManager.SteeringAvoid(deltaTime, colliderSize * 1.5f, speed); + SteeringManager.SteeringAvoid(deltaTime, colliderSize * 1.5f); } if (steeringManager is IndoorsSteeringManager indoorsSteering) @@ -656,7 +656,7 @@ namespace Barotrauma } else if (indoorsSteering.CurrentPath.Finished) { - steeringManager.SteeringManual(deltaTime, steeringVector); + steeringManager.SteeringManual(deltaTime, Vector2.Normalize(steeringVector)); } else if (indoorsSteering.CurrentPath.CurrentNode?.ConnectedDoor != null) { @@ -800,7 +800,7 @@ namespace Barotrauma if (attacker == null || attacker.AiTarget == null) return; AITargetMemory targetMemory = FindTargetMemory(attacker.AiTarget); - targetMemory.Priority += GetRelativeDamage(attackResult.Damage, Character.Vitality) * aggressionhurt; ; + targetMemory.Priority += GetRelativeDamage(attackResult.Damage, Character.Vitality) * aggressionhurt; } // 10 dmg, 100 health -> 0.1 @@ -838,7 +838,7 @@ namespace Barotrauma steeringManager.SteeringManual(deltaTime, attackDir * (1.0f - (dist / 500.0f))); } - steeringManager.SteeringAvoid(deltaTime, colliderSize * 3.0f, Character.AnimController.GetCurrentSpeed(false)); + steeringManager.SteeringAvoid(deltaTime, colliderSize * 3.0f); } #endregion @@ -870,12 +870,12 @@ namespace Barotrauma if (limbDist < 2.0f) { Character.SelectCharacter(SelectedAiTarget.Entity as Character); - steeringManager.SteeringManual(deltaTime, limbDiff); + steeringManager.SteeringManual(deltaTime, Vector2.Normalize(limbDiff)); Character.AnimController.Collider.ApplyForce(limbDiff * mouthLimb.Mass * 50.0f, mouthPos); } else { - steeringManager.SteeringSeek(attackSimPosition - (mouthPos - SimPosition), Character.AnimController.GetCurrentSpeed(useMaxSpeed: true)); + steeringManager.SteeringSeek(attackSimPosition - (mouthPos - SimPosition)); } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/HumanAIController.cs index 167a0025e..73c7d98f4 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/HumanAIController.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/HumanAIController.cs @@ -1,6 +1,7 @@ using Barotrauma.Networking; using Microsoft.Xna.Framework; using System; +using System.Collections.Generic; using System.Linq; namespace Barotrauma @@ -19,8 +20,16 @@ namespace Barotrauma private float crouchRaycastTimer; const float CrouchRaycastInterval = 1.0f; + public const float HULL_SAFETY_THRESHOLD = 50; + + // TODO: update the list when someone gives a report + public HashSet UnsafeHulls { get; private set; } = new HashSet(); + private SteeringManager outsideSteering, insideSteering; + public IndoorsSteeringManager PathSteering => insideSteering as IndoorsSteeringManager; + public HumanoidAnimController AnimController => Character.AnimController as HumanoidAnimController; + public override AIObjectiveManager ObjectiveManager { get { return objectiveManager; } @@ -68,17 +77,18 @@ namespace Barotrauma steeringManager = outsideSteering; } - (Character.AnimController as HumanoidAnimController).Crouching = shouldCrouch; + AnimController.Crouching = shouldCrouch; CheckCrouching(deltaTime); Character.ClearInputs(); - + + objectiveManager.UpdateObjectives(deltaTime); if (updateObjectiveTimer > 0.0f) { updateObjectiveTimer -= deltaTime; } else { - objectiveManager.UpdateObjectives(); + objectiveManager.SortObjectives(); updateObjectiveTimer = UpdateObjectiveInterval; } @@ -89,69 +99,107 @@ namespace Barotrauma } objectiveManager.DoCurrentObjective(deltaTime); - - float currObjectivePriority = objectiveManager.GetCurrentPriority(); - bool run = currObjectivePriority > 30.0f; - steeringManager.Update(Character.AnimController.GetCurrentSpeed(run)); + bool run = objectiveManager.GetCurrentPriority() > AIObjectiveManager.OrderPriority; + if (ObjectiveManager.CurrentObjective is AIObjectiveGoTo goTo && goTo.Target != null) + { + if (Vector2.DistanceSquared(Character.SimPosition, goTo.Target.SimPosition) > 3 * 3) + { + run = true; + } + } + if (!run) + { + run = objectiveManager.CurrentObjective.ForceRun; + } + if (run) + { + run = !AnimController.Crouching && !AnimController.IsMovingBackwards; + } + float currentSpeed = Character.AnimController.GetCurrentSpeed(run); + steeringManager.Update(currentSpeed); bool ignorePlatforms = Character.AnimController.TargetMovement.Y < -0.5f && (-Character.AnimController.TargetMovement.Y > Math.Abs(Character.AnimController.TargetMovement.X)); - var currPath = (steeringManager as IndoorsSteeringManager)?.CurrentPath; - if (currPath != null && currPath.CurrentNode != null) + if (steeringManager == insideSteering) { - if (currPath.CurrentNode.SimPosition.Y < Character.AnimController.GetColliderBottom().Y) + var currPath = PathSteering.CurrentPath; + if (currPath != null && currPath.CurrentNode != null) { - ignorePlatforms = true; + if (currPath.CurrentNode.SimPosition.Y < Character.AnimController.GetColliderBottom().Y) + { + ignorePlatforms = true; + } } } Character.AnimController.IgnorePlatforms = ignorePlatforms; + if (Character.IsClimbing) + { + Character.AnimController.TargetMovement = new Vector2(0.0f, Math.Sign(Character.AnimController.TargetMovement.Y)); + } + + Vector2 targetMovement = AnimController.TargetMovement; + if (!Character.AnimController.InWater) { - Vector2 targetMovement = new Vector2( + targetMovement = new Vector2( Character.AnimController.TargetMovement.X, MathHelper.Clamp(Character.AnimController.TargetMovement.Y, -1.0f, 1.0f)); - - float maxSpeed = Character.GetCurrentMaxSpeed(run); - targetMovement.X = MathHelper.Clamp(targetMovement.X, -maxSpeed, maxSpeed); - targetMovement.Y = MathHelper.Clamp(targetMovement.Y, -maxSpeed, maxSpeed); - - //apply speed multiplier if - // a. it's boosting the movement speed and the character is trying to move fast (= running) - // b. it's a debuff that decreases movement speed - - float speedMultiplier = Character.SpeedMultiplier; - if (run || speedMultiplier <= 0.0f) targetMovement *= speedMultiplier; - - Character.ResetSpeedMultiplier(); // Reset, items will set the value before the next update - - Character.AnimController.TargetMovement = targetMovement; } - if (Character.AnimController.Anim == AnimController.Animation.Climbing && - Character.SelectedConstruction != null && - Character.SelectedConstruction.GetComponent() != null) + float maxSpeed = Character.ApplyTemporarySpeedLimits(currentSpeed); + targetMovement.X = MathHelper.Clamp(targetMovement.X, -maxSpeed, maxSpeed); + targetMovement.Y = MathHelper.Clamp(targetMovement.Y, -maxSpeed, maxSpeed); + + //apply speed multiplier if + // a. it's boosting the movement speed and the character is trying to move fast (= running) + // b. it's a debuff that decreases movement speed + float speedMultiplier = Character.SpeedMultiplier; + if (run || speedMultiplier <= 0.0f) targetMovement *= speedMultiplier; + Character.ResetSpeedMultiplier(); // Reset, items will set the value before the next update + Character.AnimController.TargetMovement = targetMovement; + + if (!NeedsDivingGear(Character.CurrentHull)) { - if (currPath != null && currPath.CurrentNode != null && currPath.CurrentNode.Ladders != null) + bool oxygenLow = Character.OxygenAvailable < CharacterHealth.LowOxygenThreshold; + bool highPressure = Character.CurrentHull == null || Character.CurrentHull.LethalPressure > 0 && Character.PressureProtection <= 0; + bool shouldKeepTheGearOn = objectiveManager.CurrentObjective.KeepDivingGearOn; + + bool removeDivingSuit = (oxygenLow && !highPressure) || (!shouldKeepTheGearOn && Character.CurrentHull.WaterPercentage < 1 && !Character.IsClimbing && steeringManager == insideSteering && !PathSteering.InStairs); + if (removeDivingSuit) { - Character.AnimController.TargetMovement = new Vector2(0.0f, Math.Sign(Character.AnimController.TargetMovement.Y)); + var divingSuit = Character.Inventory.FindItemByIdentifier("divingsuit") ?? Character.Inventory.FindItemByTag("divingsuit"); + if (divingSuit != null) + { + // TODO: take the item where it was taken from? + divingSuit.Drop(Character); + } + } + bool takeMaskOff = oxygenLow || (!shouldKeepTheGearOn && Character.CurrentHull.WaterPercentage < 20); + if (takeMaskOff) + { + var mask = Character.Inventory.FindItemByIdentifier("divingmask"); + if (mask != null) + { + // Try to put the mask in an Any slot, and drop it if that fails + if (!mask.AllowedSlots.Contains(InvSlotType.Any) || !Character.Inventory.TryPutItem(mask, Character, new List() { InvSlotType.Any })) + { + mask.Drop(); + } + } } } - - //suit can be taken off if there character is inside a hull and there's air in the room - bool canTakeOffSuit = Character.AnimController.CurrentHull != null && - Character.AnimController.CurrentHull.OxygenPercentage > 30.0f && - Character.AnimController.CurrentHull.WaterVolume < Character.AnimController.CurrentHull.Volume * 0.3f; - - //the suit can be taken off and the character is running out of oxygen (couldn't find a tank for the suit?) or idling - //-> take the suit off - if (canTakeOffSuit && (Character.Oxygen < 50.0f || objectiveManager.CurrentObjective is AIObjectiveIdle)) + if (!(ObjectiveManager.CurrentOrder is AIObjectiveExtinguishFires) && !(ObjectiveManager.CurrentObjective is AIObjectiveExtinguishFire)) { - var divingSuit = Character.Inventory.FindItemByIdentifier("divingsuit") ?? Character.Inventory.FindItemByTag("divingsuit"); - if (divingSuit != null) divingSuit.Drop(Character); + var extinguisherItem = Character.Inventory.FindItemByIdentifier("extinguisher") ?? Character.Inventory.FindItemByTag("extinguisher"); + if (extinguisherItem != null && Character.HasEquippedItem(extinguisherItem)) + { + // TODO: take the item where it was taken from? + extinguisherItem.Drop(); + } } if (Character.IsKeyDown(InputType.Aim)) @@ -173,14 +221,61 @@ namespace Barotrauma { Character.AnimController.TargetDir = Character.AnimController.TargetMovement.X > 0.0f ? Direction.Right : Direction.Left; } + + if (Character.CurrentHull != null) + { + PropagateHullSafety(Character, Character.CurrentHull); + } + } + + protected void ReportProblems() + { + Order newOrder = null; + if (Character.CurrentHull != null) + { + if (Character.CurrentHull.FireSources.Count > 0) + { + var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportfire"); + newOrder = new Order(orderPrefab, Character.CurrentHull, null); + } + + if (Character.CurrentHull.ConnectedGaps.Any(g => !g.IsRoomToRoom && g.ConnectedDoor == null && g.Open > 0.0f)) + { + var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportbreach"); + newOrder = new Order(orderPrefab, Character.CurrentHull, null); + } + + foreach (Character c in Character.CharacterList) + { + if (c.CurrentHull == Character.CurrentHull && !c.IsDead && + (c.AIController is EnemyAIController || c.TeamID != Character.TeamID)) + { + var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportintruders"); + newOrder = new Order(orderPrefab, Character.CurrentHull, null); + } + } + } + + if (Character.CurrentHull != null && (Character.Bleeding > 1.0f || Character.Vitality < Character.MaxVitality * 0.1f)) + { + var orderPrefab = Order.PrefabList.Find(o => o.AITag == "requestfirstaid"); + newOrder = new Order(orderPrefab, Character.CurrentHull, null); + } + + if (newOrder != null) + { + if (GameMain.GameSession?.CrewManager != null && GameMain.GameSession.CrewManager.AddOrder(newOrder, newOrder.FadeOutTime)) + { + Character.Speak( + newOrder.GetChatMessage("", Character.CurrentHull?.RoomName, givingOrderToSelf: false), ChatMessageType.Order); + } + } } private void ReportProblems() { if (GameMain.Client != null) return; - partial void ReportProblems(); - private void UpdateSpeaking() { if (Character.Oxygen < 20.0f) @@ -204,11 +299,26 @@ namespace Barotrauma float totalDamage = attackResult.Damage; if (totalDamage <= 0.0f || attacker == null) return; - if (attacker.AnimController.Anim == AnimController.Animation.CPR && attacker.SelectedCharacter == Character) + if (attacker.SpeciesName == Character.SpeciesName) { - // Don't attack characters that damage you while doing cpr, because let's assume that they are helping you. - // Should not cancel any existing ai objectives (so that if the character attacked you and then helped, we still would want to retaliate). - return; + if (!attacker.IsRemotePlayer && Character.Controlled != attacker && attacker.AIController != null && attacker.AIController.Enabled) + { + // Don't react to damage done by friendly ai, because we know that it's accidental + return; + } + if (attacker.AnimController.Anim == Barotrauma.AnimController.Animation.CPR && attacker.SelectedCharacter == Character) + { + // Don't attack characters that damage you while doing cpr, because let's assume that they are helping you. + // Should not cancel any existing ai objectives (so that if the character attacked you and then helped, we still would want to retaliate). + return; + } + float currentVitality = Character.CharacterHealth.Vitality; + float dmgPercentage = totalDamage / currentVitality * 100; + if (dmgPercentage < currentVitality / 10) + { + // Don't react to a minor amount of (accidental) dmg done by friendly characters + return; + } } objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker), Rand.Range(0.5f, 1, Rand.RandSync.Unsynced), () => @@ -251,5 +361,87 @@ namespace Barotrauma float minCeilingDist = Character.AnimController.Collider.height / 2 + Character.AnimController.Collider.radius + 0.1f; shouldCrouch = Submarine.PickBody(startPos, startPos + Vector2.UnitY * minCeilingDist, null, Physics.CollisionWall) != null; } + + public static bool NeedsDivingGear(Hull hull) => hull == null || hull.OxygenPercentage < 50 || hull.WaterPercentage > 50; + + /// + /// Check whether the character has a diving suit in usable condition plus some oxygen. + /// + public static bool HasDivingSuit(Character character) => HasItem(character, "divingsuit", "oxygensource"); + + /// + /// Check whether the character has a diving mask in usable condition plus some oxygen. + /// + public static bool HasDivingGear(Character character) => HasItem(character, "diving", "oxygensource"); + + public static bool HasItem(Character character, string tag, string containedTag, float conditionPercentage = 0) + { + var item = character.Inventory.FindItemByTag(tag); + return item != null && + item.ConditionPercentage > conditionPercentage && + character.HasEquippedItem(item) && + (containedTag == null || + (item.ContainedItems != null && + item.ContainedItems.Any(i => i.HasTag(containedTag) && i.ConditionPercentage > conditionPercentage))); + } + + /// + /// Updates the hull safety for all ai characters in the team. + /// + public static void PropagateHullSafety(Character character, Hull hull) + { + foreach (var c in Character.CharacterList) + { + if (c.TeamID == character.TeamID) + { + if (c.AIController is HumanAIController humanAi) + { + humanAi.RefreshHullSafety(hull); + } + } + } + } + + private void RefreshHullSafety(Hull hull) + { + if (GetHullSafety(hull) > HULL_SAFETY_THRESHOLD) + { + UnsafeHulls.Remove(hull); + } + else + { + UnsafeHulls.Add(hull); + } + } + + public float GetHullSafety(Hull hull) + { + if (hull == null) { return 0; } + bool ignoreFire = ObjectiveManager.CurrentObjective is AIObjectiveExtinguishFire || ObjectiveManager.CurrentOrder is AIObjectiveExtinguishFires; + bool ignoreWater = HasDivingSuit(Character); + bool ignoreOxygen = ignoreWater || HasDivingGear(Character); + return GetHullSafety(hull, Character, ignoreWater, ignoreOxygen, ignoreFire, ignoreEnemies: false); + } + + public static float GetHullSafety(Hull hull, Character character, bool ignoreWater = false, bool ignoreOxygen = false, bool ignoreFire = false, bool ignoreEnemies = false) + { + if (hull == null) { return 0; } + if (hull.LethalPressure > 0 && character.PressureProtection <= 0) { return 0; } + float oxygenFactor = ignoreOxygen ? 1 : MathHelper.Lerp(0.25f, 1, hull.OxygenPercentage / 100); + float waterFactor = ignoreWater ? 1 : MathHelper.Lerp(1, 0.25f, hull.WaterPercentage / 100); + if (!character.NeedsAir) + { + oxygenFactor = 1; + waterFactor = 1; + } + // Even the smallest fire reduces the safety by 50% + float fire = hull.FireSources.Count * 0.5f + hull.FireSources.Sum(fs => fs.DamageRange) / hull.Size.X; + float fireFactor = ignoreFire ? 1 : MathHelper.Lerp(1, 0, MathHelper.Clamp(fire, 0, 1)); + int enemyCount = Character.CharacterList.Count(e => e.CurrentHull == hull && !e.IsDead && !e.IsUnconscious && (e.AIController is EnemyAIController || e.TeamID != character.TeamID)); + // The hull safety decreases 90% per enemy up to 100% (TODO: test smaller percentages) + float enemyFactor = ignoreEnemies ? 1 : MathHelper.Lerp(1, 0, MathHelper.Clamp(enemyCount * 0.9f, 0, 1)); + float safety = oxygenFactor * waterFactor * fireFactor * enemyFactor; + return MathHelper.Clamp(safety * 100, 0, 100); + } } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/ISteerable.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/ISteerable.cs index 2917f1531..a5b808a6a 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/ISteerable.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/ISteerable.cs @@ -4,7 +4,6 @@ namespace Barotrauma { interface ISteerable { - Vector2 Steering { get; @@ -25,8 +24,5 @@ namespace Barotrauma { get; } - - - } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/IndoorsSteeringManager.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/IndoorsSteeringManager.cs index 28bfb0362..723db7700 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/IndoorsSteeringManager.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/IndoorsSteeringManager.cs @@ -43,8 +43,13 @@ namespace Barotrauma private set; } - public IndoorsSteeringManager(ISteerable host, bool canOpenDoors, bool canBreakDoors) - : base(host) + public bool InLadders => currentPath != null && currentPath.CurrentNode != null && currentPath.CurrentNode.Ladders != null; + private bool IsNextNodeLadder => currentPath.NextNode != null && currentPath.CurrentNode.Ladders != null; + private bool IsNextLadderSameAsCurrent => IsNextNodeLadder && currentPath.CurrentNode.Ladders == currentPath.NextNode.Ladders; + + public bool InStairs => currentPath != null && currentPath.CurrentNode != null && currentPath.CurrentNode.Stairs != null; + + public IndoorsSteeringManager(ISteerable host, bool canOpenDoors, bool canBreakDoors) : base(host) { pathFinder = new PathFinder(WayPoint.WayPointList.FindAll(wp => wp.SpawnType == SpawnType.Path), true); pathFinder.GetNodePenalty = GetNodePenalty; @@ -73,7 +78,7 @@ namespace Barotrauma IsPathDirty = false; } - protected override Vector2 DoSteeringSeek(Vector2 target, float speed) + protected override Vector2 DoSteeringSeek(Vector2 target, float weight) { //find a new path if one hasn't been found yet or the target is different from the current target if (currentPath == null || Vector2.Distance(target, currentTarget) > 1.0f || findPathTimer < -1.0f) @@ -105,16 +110,14 @@ namespace Barotrauma var collider = character.AnimController.Collider; //if not in water and the waypoint is between the top and bottom of the collider, no need to move vertically - if (!character.AnimController.InWater && - character.AnimController.Anim != AnimController.Animation.Climbing && - diff.Y < collider.height / 2 + collider.radius) + if (!character.AnimController.InWater && !character.IsClimbing && diff.Y < collider.height / 2 + collider.radius) { diff.Y = 0.0f; } if (diff.LengthSquared() < 0.001f) return -host.Steering; - return Vector2.Normalize(diff) * speed; + return Vector2.Normalize(diff) * weight; } private Vector2 DiffToCurrentNode() @@ -155,9 +158,7 @@ namespace Barotrauma } //only humanoids can climb ladders - if (currentPath.CurrentNode != null && - currentPath.CurrentNode.Ladders != null && - character.AnimController is HumanoidAnimController) + if (character.AnimController is HumanoidAnimController && InLadders && IsNextLadderSameAsCurrent) { if (character.SelectedConstruction != currentPath.CurrentNode.Ladders.Item && currentPath.CurrentNode.Ladders.Item.IsInsideTrigger(character.WorldPosition)) @@ -167,13 +168,15 @@ namespace Barotrauma } var collider = character.AnimController.Collider; - - if (character.AnimController.Anim == AnimController.Animation.Climbing) + if (character.IsClimbing) { Vector2 diff = currentPath.CurrentNode.SimPosition - pos; - //climbing ladders -> don't move horizontally - diff.X = 0.0f; + if (IsNextLadderSameAsCurrent) + { + //climbing ladders -> don't move horizontally + diff.X = 0.0f; + } //at the same height as the waypoint if (Math.Abs(collider.SimPosition.Y - currentPath.CurrentNode.SimPosition.Y) < (collider.height / 2 + collider.radius) * 1.25f) @@ -183,27 +186,23 @@ namespace Barotrauma { diff.Y = Math.Max(diff.Y, 1.0f); } - - //we can safely skip to the next waypoint if the character is at a safe height above the floor, - //or if the next waypoint in the path is also on ladders - if ((heightFromFloor > 0.0f && heightFromFloor < collider.height * 1.5f) || - (currentPath.NextNode != null && currentPath.NextNode.Ladders != null)) + + bool aboveFloor = heightFromFloor > 0.0f && heightFromFloor < collider.height * 1.5f; + if (aboveFloor || IsNextNodeLadder) { - if (currentPath.NextNode != null && currentPath.NextNode.Ladders == null) + if (!IsNextLadderSameAsCurrent) { character.AnimController.Anim = AnimController.Animation.None; } currentPath.SkipToNextNode(); } } - else + else if (IsNextLadderSameAsCurrent) { //if the current node is below the character and the next one is above (or vice versa) //and both are on ladders, we can skip directly to the next one - //e.g. no point in going down to reach the starting point of a path when we could go directly to the one above - if (currentPath.CurrentNode.Ladders != null && currentPath.CurrentNode.Ladders == currentPath.NextNode?.Ladders && - Math.Sign(currentPath.CurrentNode.WorldPosition.Y - character.WorldPosition.Y) != Math.Sign(currentPath.NextNode.WorldPosition.Y - character.WorldPosition.Y)) + if (Math.Sign(currentPath.CurrentNode.WorldPosition.Y - character.WorldPosition.Y) != Math.Sign(currentPath.NextNode.WorldPosition.Y - character.WorldPosition.Y)) { currentPath.SkipToNextNode(); } @@ -222,9 +221,11 @@ namespace Barotrauma else { Vector2 colliderBottom = character.AnimController.GetColliderBottom(); - if (Math.Abs(collider.SimPosition.X - currentPath.CurrentNode.SimPosition.X) < collider.radius * 2 && - currentPath.CurrentNode.SimPosition.Y > colliderBottom.Y && - currentPath.CurrentNode.SimPosition.Y < colliderBottom.Y + 1.5f) + float horizontalDistance = Math.Abs(collider.SimPosition.X - currentPath.CurrentNode.SimPosition.X); + bool isAboveVerticalPosition = currentPath.CurrentNode.SimPosition.Y > colliderBottom.Y; + bool isNotTooHigh = currentPath.CurrentNode.SimPosition.Y < colliderBottom.Y + 1.5f; + + if (horizontalDistance < collider.radius * 2 && isAboveVerticalPosition && isNotTooHigh) { currentPath.SkipToNextNode(); } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/LatchOntoAI.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/LatchOntoAI.cs index 25d64e1e0..200e20a5a 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/LatchOntoAI.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/LatchOntoAI.cs @@ -180,8 +180,8 @@ namespace Barotrauma { //move closer to the wall DeattachFromBody(); - enemyAI.SteeringManager.SteeringAvoid(deltaTime, 1.0f, character.AnimController.GetCurrentSpeed(false) * 0.1f); - enemyAI.SteeringManager.SteeringSeek(wallAttachPos, character.AnimController.GetCurrentSpeed(true)); + enemyAI.SteeringManager.SteeringAvoid(deltaTime, 1.0f, 0.1f); + enemyAI.SteeringManager.SteeringSeek(wallAttachPos); } } break; diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjective.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjective.cs index e22fb2b92..eb5aa57bd 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjective.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjective.cs @@ -1,20 +1,31 @@ using System; using System.Collections.Generic; using System.Linq; +using Microsoft.Xna.Framework; +using Barotrauma.Extensions; namespace Barotrauma { abstract class AIObjective { - protected readonly List subObjectives; + public virtual float Devotion => AIObjectiveManager.baseDevotion; + + public abstract string DebugTag { get; } + public virtual bool ForceRun => false; + public virtual bool KeepDivingGearOn => false; + + protected readonly List subObjectives = new List(); protected float priority; protected readonly Character character; protected string option; + protected bool abandon; - public virtual bool CanBeCompleted - { - get { return true; } - } + public virtual bool CanBeCompleted => !abandon && subObjectives.All(so => so.CanBeCompleted); + public IEnumerable SubObjectives => subObjectives; + public AIObjective CurrentSubObjective { get; private set; } + + protected HumanAIController HumanAIController => character.AIController as HumanAIController; + protected IndoorsSteeringManager PathSteering => HumanAIController.PathSteering; public string Option { @@ -23,7 +34,6 @@ namespace Barotrauma public AIObjective(Character character, string option) { - subObjectives = new List(); this.character = character; this.option = option; @@ -38,7 +48,36 @@ namespace Barotrauma /// public void TryComplete(float deltaTime) { - subObjectives.RemoveAll(s => s.IsCompleted() || !s.CanBeCompleted || ShouldInterruptSubObjective(s)); + for (int i = 0; i < subObjectives.Count; i++) + { + var subObjective = subObjectives[i]; + if (subObjective.IsCompleted()) + { +#if DEBUG + DebugConsole.NewMessage($"Removing subobjective {subObjective.DebugTag} of {DebugTag}, because it is completed."); +#endif + subObjectives.Remove(subObjective); + } + else if (!subObjective.CanBeCompleted) + { +#if DEBUG + DebugConsole.NewMessage($"Removing subobjective {subObjective.DebugTag} of {DebugTag}, because it cannot be completed."); +#endif + subObjectives.Remove(subObjective); + } + else if (subObjective.ShouldInterruptSubObjective(subObjective)) + { +#if DEBUG + DebugConsole.NewMessage($"Removing subobjective {subObjective.DebugTag} of {DebugTag}, because it is interrupted."); +#endif + subObjectives.Remove(subObjective); + } + } + + if (!subObjectives.Contains(CurrentSubObjective)) + { + CurrentSubObjective = null; + } foreach (AIObjective objective in subObjectives) { @@ -56,32 +95,53 @@ namespace Barotrauma subObjectives.Add(objective); } - public AIObjective GetCurrentSubObjective() - { - AIObjective currentSubObjective = this; - while (currentSubObjective.subObjectives.Count > 0) - { - currentSubObjective = subObjectives[0]; - } - return currentSubObjective; - } - public void SortSubObjectives(AIObjectiveManager objectiveManager) { - if (!subObjectives.Any()) return; + if (subObjectives.None()) { return; } subObjectives.Sort((x, y) => y.GetPriority(objectiveManager).CompareTo(x.GetPriority(objectiveManager))); - subObjectives[0].SortSubObjectives(objectiveManager); + CurrentSubObjective = SubObjectives.First(); + CurrentSubObjective.SortSubObjectives(objectiveManager); } - protected virtual bool ShouldInterruptSubObjective(AIObjective subObjective) + public virtual float GetPriority(AIObjectiveManager objectiveManager) => priority; + + public virtual void Update(AIObjectiveManager objectiveManager, float deltaTime) { - return false; + var subObjective = objectiveManager.CurrentObjective?.CurrentSubObjective; + if (objectiveManager.CurrentOrder == this) + { + priority = AIObjectiveManager.OrderPriority; + } + else if (objectiveManager.CurrentObjective == this || subObjective == this) + { + priority += Devotion * deltaTime; + } + priority = MathHelper.Clamp(priority, 0, 100); + subObjectives.ForEach(so => so.Update(objectiveManager, deltaTime)); } + /// + /// Checks if the subobjectives in the given collection are removed from the subobjectives. And if so, removes it also from the dictionary. + /// + protected void SyncRemovedObjectives(Dictionary dictionary, IEnumerable collection) where T2 : AIObjective + { + foreach (T1 key in collection) + { + if (dictionary.TryGetValue(key, out T2 objective)) + { + if (!subObjectives.Contains(objective)) + { + dictionary.Remove(key); + } + } + } + } + + protected virtual bool ShouldInterruptSubObjective(AIObjective subObjective) => false; + protected abstract void Act(float deltaTime); public abstract bool IsCompleted(); - public abstract float GetPriority(AIObjectiveManager objectiveManager); public abstract bool IsDuplicate(AIObjective otherObjective); } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveChargeBatteries.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveChargeBatteries.cs index 944246d6d..b0224241d 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveChargeBatteries.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveChargeBatteries.cs @@ -1,66 +1,58 @@ using Barotrauma.Items.Components; +using Barotrauma.Extensions; using System.Collections.Generic; +using System.Linq; namespace Barotrauma { - class AIObjectiveChargeBatteries : AIObjective + class AIObjectiveChargeBatteries : AIObjectiveLoop { - private List availableBatteries; + public override string DebugTag => "charge batteries"; + private readonly IEnumerable batteryList; - private string orderOption; - - public AIObjectiveChargeBatteries(Character character, string option) - : base(character, option) + public AIObjectiveChargeBatteries(Character character, string option) : base(character, option) { - orderOption = option; - - availableBatteries = new List(); - foreach (Item item in Item.ItemList) - { - if (item.Submarine == null) continue; - if (item.Prefab.Identifier != "battery" && !item.HasTag("battery")) continue; - - var powerContainer = item.GetComponent(); - availableBatteries.Add(powerContainer); - } - - if (availableBatteries.Count == 0) - { - character?.Speak(TextManager.Get("DialogNoBatteries"), null, 4.0f, "nobatteries", 10.0f); - } - } - - public override float GetPriority(AIObjectiveManager objectiveManager) - { - if (objectiveManager.CurrentOrder == this) - { - return AIObjectiveManager.OrderPriority; - } - - return 1.0f; - } - - public override bool IsCompleted() - { - return false; + batteryList = Item.ItemList.Select(i => i.GetComponent()).Where(b => b != null); } public override bool IsDuplicate(AIObjective otherObjective) { - return otherObjective is AIObjectiveChargeBatteries other && other.orderOption == orderOption; + return otherObjective is AIObjectiveChargeBatteries other && other.Option == Option; } - protected override void Act(float deltaTime) + protected override void FindTargets() { - if (availableBatteries.Count == 0) + foreach (Item item in Item.ItemList) { - AddSubObjective(new AIObjectiveIdle(character)); - return; + if (item.Prefab.Identifier != "battery" && !item.HasTag("battery")) { continue; } + if (item.Submarine == null) { continue; } + if (item.Submarine.TeamID != character.TeamID) { continue; } + if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(item, true)) { continue; } + var battery = item.GetComponent(); + if (battery != null) + { + if (!ignoreList.Contains(battery)) + { + if (!targets.Contains(battery)) + { + targets.Add(battery); + } + } + } } - foreach (PowerContainer battery in availableBatteries) + if (targets.None()) { - AddSubObjective(new AIObjectiveOperateItem(battery, character, orderOption, false)); + character.Speak(TextManager.Get("DialogNoBatteries"), null, 4.0f, "nobatteries", 10.0f); + } + else + { + targets.Sort((x, y) => x.ChargePercentage.CompareTo(y.ChargePercentage)); } } + + protected override bool Filter(PowerContainer battery) => true; + protected override float Average(PowerContainer battery) => 100 - battery.ChargePercentage; + protected override IEnumerable GetList() => batteryList; + protected override AIObjective ObjectiveConstructor(PowerContainer battery) => new AIObjectiveOperateItem(battery, character, Option, false); } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveCombat.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveCombat.cs index fbb961699..0463c7ef2 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveCombat.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveCombat.cs @@ -8,6 +8,10 @@ namespace Barotrauma { class AIObjectiveCombat : AIObjective { + public override string DebugTag => "combat"; + public override bool ForceRun => true; + public override bool KeepDivingGearOn => true; + const float CoolDown = 10.0f; //the largest amount of damage the enemy has inflicted on this character @@ -117,6 +121,7 @@ namespace Barotrauma private void Escape(float deltaTime) { + // TODO: just let the find safety run? if (escapeObjective == null) { escapeObjective = new AIObjectiveFindSafety(character); diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveContainItem.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveContainItem.cs index 7ed31326a..e85af365b 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveContainItem.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveContainItem.cs @@ -7,6 +7,8 @@ namespace Barotrauma { class AIObjectiveContainItem: AIObjective { + public override string DebugTag => "contain item"; + public int MinContainedAmount = 1; //can either be a tag or an identifier diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveDecontainItem.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveDecontainItem.cs index f5b32653a..a73d7ec57 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveDecontainItem.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveDecontainItem.cs @@ -6,6 +6,8 @@ namespace Barotrauma { class AIObjectiveDecontainItem : AIObjective { + public override string DebugTag => "decontain item"; + //can either be a tag or an identifier private string[] itemIdentifiers; diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveExtinguishFire.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveExtinguishFire.cs index 863504bfe..fd208bb1a 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveExtinguishFire.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveExtinguishFire.cs @@ -8,6 +8,10 @@ namespace Barotrauma { class AIObjectiveExtinguishFire : AIObjective { + public override string DebugTag => "extinguish fire"; + public override bool ForceRun => true; + public override bool KeepDivingGearOn => true; + private Hull targetHull; private AIObjectiveGetItem getExtinguisherObjective; @@ -16,15 +20,19 @@ namespace Barotrauma private float useExtinquisherTimer; - public AIObjectiveExtinguishFire(Character character, Hull targetHull) : - base(character, "") + public AIObjectiveExtinguishFire(Character character, Hull targetHull) : base(character, "") { this.targetHull = targetHull; } public override float GetPriority(AIObjectiveManager objectiveManager) { - return targetHull.FireSources.Sum(fs => fs.Size.X * 0.1f); + if (gotoObjective != null && !gotoObjective.CanBeCompleted) { return 0; } + // Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally) + float dist = Math.Abs(character.WorldPosition.X - targetHull.WorldPosition.X) + Math.Abs(character.WorldPosition.Y - targetHull.WorldPosition.Y) * 2.0f; + float distanceFactor = MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 10000, dist)); + float severityFactor = MathHelper.Lerp(0, 1, MathHelper.Clamp(targetHull.FireSources.Sum(fs => fs.Size.X) / targetHull.Size.X, 0, 1)); + return MathHelper.Clamp(priority * severityFactor * distanceFactor, 0, 100); } public override bool IsCompleted() @@ -40,7 +48,7 @@ namespace Barotrauma public override bool CanBeCompleted { - get { return getExtinguisherObjective == null || getExtinguisherObjective.CanBeCompleted; } + get { return getExtinguisherObjective == null || getExtinguisherObjective.IsCompleted() || getExtinguisherObjective.CanBeCompleted; } } protected override void Act(float deltaTime) @@ -68,9 +76,11 @@ namespace Barotrauma return; } - foreach (FireSource fs in targetHull.FireSources.ToList()) + foreach (FireSource fs in targetHull.FireSources) { - if (fs.IsInDamageRange(character, MathHelper.Clamp(fs.DamageRange * 1.5f, extinguisher.Range * 0.5f, extinguisher.Range)) || useExtinquisherTimer > 0.0f) + // TODO: check if in the same room? + bool inRange = fs.IsInDamageRange(character, MathHelper.Clamp(fs.DamageRange * 1.5f, extinguisher.Range * 0.5f, extinguisher.Range)); + if (!character.IsClimbing && (inRange || useExtinquisherTimer > 0.0f)) { useExtinquisherTimer += deltaTime; if (useExtinquisherTimer > 2.0f) useExtinquisherTimer = 0.0f; @@ -86,20 +96,19 @@ namespace Barotrauma } return; } - } - - foreach (FireSource fs in targetHull.FireSources) - { - //go to the first firesource - if (gotoObjective == null || !gotoObjective.CanBeCompleted || gotoObjective.IsCompleted()) - { - gotoObjective = new AIObjectiveGoTo(ConvertUnits.ToSimUnits(fs.Position), character); - } else { - gotoObjective.TryComplete(deltaTime); + //go to the first firesource + if (gotoObjective == null || !gotoObjective.CanBeCompleted || gotoObjective.IsCompleted()) + { + gotoObjective = new AIObjectiveGoTo(ConvertUnits.ToSimUnits(fs.Position), character); + } + else + { + gotoObjective.TryComplete(deltaTime); + } + break; } - break; } } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveExtinguishFires.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveExtinguishFires.cs index 3aa727bcb..4da5093e8 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveExtinguishFires.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveExtinguishFires.cs @@ -1,32 +1,34 @@ using System.Linq; +using System.Collections.Generic; +using Barotrauma.Extensions; +using Microsoft.Xna.Framework; namespace Barotrauma { class AIObjectiveExtinguishFires : AIObjective { - public AIObjectiveExtinguishFires(Character character) : - base(character, "") - { - if (!Hull.hullList.Any(h => h.FireSources.Count > 0)) - { - character?.Speak(TextManager.Get("DialogNoFire"), null, 3.0f, "nofire", 30.0f); - } - } + public override string DebugTag => "extinguish fires"; + public override bool ForceRun => true; + public override bool KeepDivingGearOn => true; + + private Dictionary extinguishObjectives = new Dictionary(); + + public AIObjectiveExtinguishFires(Character character) : base(character, "") { } public override float GetPriority(AIObjectiveManager objectiveManager) { - if (objectiveManager.CurrentObjective == this) + if (character.Submarine == null) { return 0; } + int fireCount = character.Submarine.GetHulls(true).Sum(h => h.FireSources.Count); + if (objectiveManager.CurrentOrder == this && fireCount > 0) { return AIObjectiveManager.OrderPriority; } - return Hull.hullList.Count(h => h.FireSources.Count > 0) * 10; + return MathHelper.Clamp(fireCount * 20, 0, 100); } - public override bool IsCompleted() - { - return !Hull.hullList.Any(h => h.FireSources.Count > 0); - } + public override bool IsCompleted() => false; + public override bool CanBeCompleted => true; public override bool IsDuplicate(AIObjective otherObjective) { @@ -35,13 +37,26 @@ namespace Barotrauma protected override void Act(float deltaTime) { + SyncRemovedObjectives(extinguishObjectives, Hull.hullList); + if (character.Submarine == null) { return; } foreach (Hull hull in Hull.hullList) { - if (hull.FireSources.Count > 0) + if (hull.FireSources.None()) { continue; } + if (hull.Submarine == null) { continue; } + if (hull.Submarine.TeamID != character.TeamID) { continue; } + // If the character is inside, only take connected hulls into account. + if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(hull, true)) { continue; } + if (!extinguishObjectives.TryGetValue(hull, out AIObjectiveExtinguishFire objective)) { - AddSubObjective(new AIObjectiveExtinguishFire(character, hull)); + objective = new AIObjectiveExtinguishFire(character, hull); + extinguishObjectives.Add(hull, objective); + AddSubObjective(objective); } } + if (extinguishObjectives.None()) + { + character?.Speak(TextManager.Get("DialogNoFire"), null, 3.0f, "nofire", 30.0f); + } } } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindDivingGear.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindDivingGear.cs index 0781d7654..51569bd04 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindDivingGear.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindDivingGear.cs @@ -6,6 +6,9 @@ namespace Barotrauma { class AIObjectiveFindDivingGear : AIObjective { + public override string DebugTag => "find diving gear"; + public override bool ForceRun => true; + private AIObjective subObjective; private string gearTag; @@ -28,6 +31,8 @@ namespace Barotrauma return false; } + public override bool CanBeCompleted => subObjective == null || subObjective.CanBeCompleted; + public AIObjectiveFindDivingGear(Character character, bool needDivingSuit) : base(character, "") { diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindSafety.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindSafety.cs index 874a43cae..e7beb84bf 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindSafety.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindSafety.cs @@ -2,100 +2,127 @@ using System; using System.Collections.Generic; using System.Linq; +using Barotrauma.Items.Components; namespace Barotrauma { class AIObjectiveFindSafety : AIObjective { + public override string DebugTag => "find safety"; + public override bool ForceRun => true; + public override bool KeepDivingGearOn => true; + + // TODO: expose? + const float priorityIncrease = 25; + const float priorityDecrease = 10; const float SearchHullInterval = 3.0f; - const float MinSafety = 50.0f; - private AIObjectiveGoTo goToObjective; - - private List unreachable; + private List unreachable = new List(); private float currenthullSafety; private float searchHullTimer; + private AIObjectiveGoTo goToObjective; private AIObjective divingGearObjective; public float? OverrideCurrentHullSafety; - public AIObjectiveFindSafety(Character character) - : base(character, "") - { - unreachable = new List(); - } + public AIObjectiveFindSafety(Character character) : base(character, "") { } - public override bool IsCompleted() - { - return false; - } + public override bool IsCompleted() => false; + public override bool CanBeCompleted => true; protected override void Act(float deltaTime) { - var currentHull = character.AnimController.CurrentHull; - - currenthullSafety = OverrideCurrentHullSafety == null ? - GetHullSafety(currentHull, character) : (float)OverrideCurrentHullSafety; - - if (NeedsDivingGear()) + var currentHull = character.AnimController.CurrentHull; + if (HumanAIController.NeedsDivingGear(currentHull)) { - if (!FindDivingGear(deltaTime)) return; + bool needsDivingSuit = currentHull == null || currentHull.WaterPercentage > 90; + bool hasEquipment = needsDivingSuit ? HumanAIController.HasDivingSuit(character) : HumanAIController.HasDivingGear(character); + if ((divingGearObjective == null || !divingGearObjective.CanBeCompleted) && !hasEquipment) + { + // If the previous objective cannot be completed, create a new and try again. + divingGearObjective = new AIObjectiveFindDivingGear(character, needsDivingSuit); + } + } + if (divingGearObjective != null) + { + divingGearObjective.TryComplete(deltaTime); + if (divingGearObjective.IsCompleted()) + { + divingGearObjective = null; + priority = 0; + } + else if (divingGearObjective.CanBeCompleted) + { + // If diving gear objective is active, wait for it to complete. + return; + } } - if (searchHullTimer > 0.0f) { searchHullTimer -= deltaTime; } - else + else if (currenthullSafety < HumanAIController.HULL_SAFETY_THRESHOLD) { var bestHull = FindBestHull(); - if (bestHull != null) + if (bestHull != null && bestHull != currentHull) { - goToObjective = new AIObjectiveGoTo(bestHull, character) + if (goToObjective != null) { - AllowGoingOutside = true - }; + if (goToObjective.Target != bestHull) + { + goToObjective = new AIObjectiveGoTo(bestHull, character) + { + AllowGoingOutside = true + }; + } + } + else + { + goToObjective = new AIObjectiveGoTo(bestHull, character) + { + AllowGoingOutside = true + }; + } } - searchHullTimer = SearchHullInterval; } if (goToObjective != null) { goToObjective.TryComplete(deltaTime); - - if (character.AIController.SteeringManager is IndoorsSteeringManager pathSteering && - pathSteering.CurrentPath != null && - pathSteering.CurrentPath.Unreachable && !unreachable.Contains(goToObjective.Target)) + if (!goToObjective.CanBeCompleted) { - unreachable.Add(goToObjective.Target as Hull); + if (!unreachable.Contains(goToObjective.Target)) + { + unreachable.Add(goToObjective.Target as Hull); + } goToObjective = null; } } - //goto objective doesn't exist (a safe hull not found, or a path to a safe hull not found) - // -> attempt to manually steer away from hazards else if (currentHull != null) { + //goto objective doesn't exist (a safe hull not found, or a path to a safe hull not found) + // -> attempt to manually steer away from hazards Vector2 escapeVel = Vector2.Zero; foreach (FireSource fireSource in currentHull.FireSources) { - int dir = Math.Sign(character.Position.X - fireSource.Position.X); - + Vector2 dir = character.Position - fireSource.Position; float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(fireSource.Position, character.Position), 0.1f, 10.0f); - escapeVel += new Vector2(dir * distMultiplier, 0.0f); + escapeVel += new Vector2(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier); } - foreach (Character enemy in Character.CharacterList) + foreach (Character enemy in Character.CharacterList) { if (enemy.CurrentHull == currentHull && !enemy.IsDead && !enemy.IsUnconscious && (enemy.AIController is EnemyAIController || enemy.TeamID != character.TeamID)) { + Vector2 dir = character.Position - enemy.Position; float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(enemy.Position, character.Position), 0.1f, 10.0f); - escapeVel += new Vector2(Math.Sign(character.Position.X - enemy.Position.X) * distMultiplier, 0.0f); + escapeVel += new Vector2(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier); } } @@ -120,40 +147,70 @@ namespace Barotrauma } } - private bool FindDivingGear(float deltaTime) - { - if (divingGearObjective == null) - { - divingGearObjective = new AIObjectiveFindDivingGear(character, false); - } - - if (divingGearObjective.IsCompleted()) return true; - - divingGearObjective.TryComplete(deltaTime); - return divingGearObjective.IsCompleted(); - } - private Hull FindBestHull() { - Hull bestHull = null; + Hull bestHull = character.CurrentHull; float bestValue = currenthullSafety; - foreach (Hull hull in Hull.hullList) { - if (hull == character.AnimController.CurrentHull || unreachable.Contains(hull)) continue; + if (unreachable.Contains(hull)) { continue; } + if (hull.Submarine == null) { continue; } + float hullSafety = 0; + if (character.Submarine == null) + { + // Outside + if (hull.RoomName?.ToLowerInvariant() == "airlock") + { + hullSafety = 100; + } + else + { + // TODO: could also target gaps that get us inside? + foreach (Item item in Item.ItemList) + { + if (item.CurrentHull == hull && item.HasTag("airlock")) + { + hullSafety = 100; + break; + } + } + } - float hullValue = GetHullSafety(hull, character); - //slight preference over hulls that are closer - hullValue -= (float)Math.Sqrt(Math.Abs(character.Position.X - hull.Position.X)) * 0.1f; - hullValue -= (float)Math.Sqrt(Math.Abs(character.Position.Y - hull.Position.Y)) * 0.2f; - - if (bestHull == null || hullValue > bestValue) + // Huge preference for closer targets + float distanceFactor = MathHelper.Lerp(1, 0.2f, MathUtils.InverseLerp(0, 100000, Vector2.Distance(character.WorldPosition, hull.WorldPosition))); + hullSafety *= distanceFactor; + // If the target is not inside a friendly submarine, considerably reduce the hull safety. + if (hull.Submarine.TeamID != character.TeamID && hull.Submarine.TeamID != Character.TeamType.FriendlyNPC) + { + hullSafety /= 10; + } + } + else + { + // Inside + // Not connected. + if (!character.Submarine.GetConnectedSubs().Contains(hull.Submarine)) { continue; } + hullSafety = HumanAIController.GetHullSafety(hull, character); + var path = PathSteering.PathFinder.FindPath(character.SimPosition, hull.SimPosition); + int unsafeNodes = path.Nodes.Count(n => n.CurrentHull != character.CurrentHull && HumanAIController.UnsafeHulls.Contains(n.CurrentHull)); + // Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally) + float dist = Math.Abs(character.WorldPosition.X - hull.WorldPosition.X) + Math.Abs(character.WorldPosition.Y - hull.WorldPosition.Y) * 2.0f; + float distanceFactor = MathHelper.Lerp(1, 0.9f, MathUtils.InverseLerp(0, 10000, dist)); + hullSafety *= distanceFactor; + // Each unsafe node reduces the hull safety value. + hullSafety /= 1 + unsafeNodes; + // If the target is not inside a friendly submarine, considerably reduce the hull safety. + if (!character.Submarine.IsEntityFoundOnThisSub(hull, true)) + { + hullSafety /= 10; + } + } + if (hullSafety > bestValue) { bestHull = hull; - bestValue = hullValue; + bestValue = hullSafety; } } - return bestHull; } @@ -162,113 +219,30 @@ namespace Barotrauma return (otherObjective is AIObjectiveFindSafety); } - private bool NeedsDivingGear() + public override void Update(AIObjectiveManager objectiveManager, float deltaTime) { - var currentHull = character.AnimController.CurrentHull; - if (currentHull == null) return true; - - //there's lots of water in the room -> get a suit - if (currentHull.WaterVolume / currentHull.Volume > 0.5f) return true; - - if (currentHull.OxygenPercentage < 30.0f) return true; - - return false; - } - - public override float GetPriority(AIObjectiveManager objectiveManager) - { - if (character.Oxygen < 80.0f) + if (character.CurrentHull == null) { - return 150.0f - character.Oxygen; + currenthullSafety = 0; + priority = 5; + return; } - - if (character.CurrentHull == null) return 5.0f; - currenthullSafety = GetHullSafety(character.CurrentHull, character); - priority = 100.0f - currenthullSafety; - - //var nearbyHulls = character.CurrentHull.GetConnectedHulls(3); - - //increase priority slightly if there's a fire in the room - //(will increase more heavily if near the damage range of the fire) - if (character.CurrentHull.FireSources.Count > 0) + if (character.OxygenAvailable < CharacterHealth.LowOxygenThreshold) { priority = 100; } + currenthullSafety = OverrideCurrentHullSafety ?? HumanAIController.GetHullSafety(character.CurrentHull); + if (currenthullSafety > HumanAIController.HULL_SAFETY_THRESHOLD) { - priority += 5.0f; - } - - /*foreach (Hull hull in nearbyHulls) - { - foreach (FireSource fireSource in hull.FireSources) - { - //heavily increase priority if almost within damage range of a fire - if (fireSource.IsInDamageRange(character, fireSource.DamageRange * 1.25f)) - { - priority += Math.Max(fireSource.Size.X, 50.0f); - } - } - }*/ - - if (NeedsDivingGear()) - { - if (divingGearObjective != null && !divingGearObjective.IsCompleted()) priority += 20.0f; - } - - return priority; - } - - public static float GetHullSafety(Hull hull, Character character) - { - if (hull == null) return 0.0f; - - float safety = 100.0f; - - float waterPercentage = (hull.WaterVolume / hull.Volume) * 100.0f; - if (hull.LethalPressure > 0.0f && character.PressureProtection <= 0.0f) - { - safety -= 100.0f; - } - else if (character.OxygenAvailable <= 0.0f) - { - safety -= waterPercentage; + priority -= priorityDecrease * deltaTime; } else { - safety -= waterPercentage * 0.1f; + float dangerFactor = (100 - currenthullSafety) / 100; + priority += dangerFactor * priorityIncrease * deltaTime; } - - if (hull.OxygenPercentage < 30.0f) safety -= (30.0f - hull.OxygenPercentage) * 5.0f; - - if (safety <= 0.0f) return 0.0f; - - bool extinguishFires = - character.AIController.ObjectiveManager?.CurrentOrder is AIObjectiveExtinguishFires || - character.AIController.ObjectiveManager?.CurrentOrder is AIObjectiveExtinguishFire; - - float fireAmount = 0.0f; - var nearbyHulls = hull.GetConnectedHulls(3); - foreach (Hull hull2 in nearbyHulls) + priority = MathHelper.Clamp(priority, 0, 100); + if (divingGearObjective != null && !divingGearObjective.IsCompleted() && divingGearObjective.CanBeCompleted) { - foreach (FireSource fireSource in hull2.FireSources) - { - //increase priority if near the damage range of a fire - //if extinguishing fires, the character can go closer the damage range - if (fireSource.IsInDamageRange(character, fireSource.DamageRange * (extinguishFires ? 1.25f : 5.0f))) - { - fireAmount += Math.Max(fireSource.Size.X, AIObjectiveManager.OrderPriority + 1.0f); - } - } + priority = Math.Max(priority, AIObjectiveManager.OrderPriority + 10); } - safety -= fireAmount; - - foreach (Character enemy in Character.CharacterList) - { - if (enemy.CurrentHull == hull && !enemy.IsDead && !enemy.IsUnconscious && - (enemy.AIController is EnemyAIController || enemy.TeamID != character.TeamID)) - { - safety -= 10.0f; - } - } - - return MathHelper.Clamp(safety, 0.0f, 100.0f); } } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeak.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeak.cs index 436a1bbee..517076479 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeak.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeak.cs @@ -8,17 +8,22 @@ namespace Barotrauma { class AIObjectiveFixLeak : AIObjective { + public override string DebugTag => "fix leak"; + + public override bool KeepDivingGearOn => true; + private readonly Gap leak; - private bool pathUnreachable; + private AIObjectiveFindDivingGear findDivingGear; + private AIObjectiveGoTo gotoObjective; + private AIObjectiveOperateItem operateObjective; public Gap Leak { get { return leak; } } - public AIObjectiveFixLeak(Gap leak, Character character) - : base (character, "") + public AIObjectiveFixLeak(Gap leak, Character character) : base (character, "") { this.leak = leak; } @@ -28,10 +33,11 @@ namespace Barotrauma return leak.Open <= 0.0f || leak.Removed || pathUnreachable; } + public override bool CanBeCompleted => !abandon && base.CanBeCompleted; + public override float GetPriority(AIObjectiveManager objectiveManager) { if (leak.Open == 0.0f) { return 0.0f; } - if (pathUnreachable) { return 0.0f; } float leakSize = (leak.IsHorizontal ? leak.Rect.Height : leak.Rect.Width) * Math.Max(leak.Open, 0.1f); @@ -49,6 +55,20 @@ namespace Barotrauma protected override void Act(float deltaTime) { + if (!leak.IsRoomToRoom) + { + if (findDivingGear == null) + { + findDivingGear = new AIObjectiveFindDivingGear(character, true); + AddSubObjective(findDivingGear); + } + else if (!findDivingGear.CanBeCompleted) + { + abandon = true; + return; + } + } + var weldingTool = character.Inventory.FindItemByTag("weldingtool"); if (weldingTool == null) @@ -72,31 +92,48 @@ namespace Barotrauma var repairTool = weldingTool.GetComponent(); if (repairTool == null) { return; } - Vector2 standPosition = GetStandPosition(); - Vector2 gapDiff = leak.WorldPosition - character.WorldPosition; + var humanoidController = character.AnimController as HumanoidAnimController; - if (!character.AnimController.InWater && character.AnimController is HumanoidAnimController && - Math.Abs(gapDiff.X) < 100.0f && gapDiff.Y < 0.0f && gapDiff.Y > -150.0f) + // TODO: use the collider size/reach? + if (!character.AnimController.InWater && humanoidController != null && Math.Abs(gapDiff.X) < 100 && gapDiff.Y < 0.0f && gapDiff.Y > -150) { ((HumanoidAnimController)character.AnimController).Crouching = true; } - if (Math.Abs(gapDiff.X) > 100.0f || Math.Abs(gapDiff.Y) > 150.0f) + float armLength = humanoidController != null ? ConvertUnits.ToDisplayUnits(humanoidController.ArmLength) : 100; + bool cannotReach = gapDiff.Length() > armLength + repairTool.Range; + if (cannotReach) { - var gotoObjective = new AIObjectiveGoTo(ConvertUnits.ToSimUnits(standPosition), character); - if (!gotoObjective.IsCompleted()) + if (gotoObjective != null) { - pathUnreachable = !gotoObjective.CanBeCompleted; - if (!pathUnreachable) + // Check if the objective is already removed -> completed/impossible + if (!subObjectives.Contains(gotoObjective)) + { + gotoObjective = null; + } + } + else + { + gotoObjective = new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character); + if (!subObjectives.Contains(gotoObjective)) { AddSubObjective(gotoObjective); } - return; } } - - AddSubObjective(new AIObjectiveOperateItem(repairTool, character, "", true, leak)); + if (gotoObjective == null || gotoObjective.IsCompleted()) + { + if (operateObjective == null) + { + operateObjective = new AIObjectiveOperateItem(repairTool, character, "", true, leak); + AddSubObjective(operateObjective); + } + else if (!subObjectives.Contains(operateObjective)) + { + operateObjective = null; + } + } } private Vector2 GetStandPosition() diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeaks.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeaks.cs index 6d229ad46..690db42c8 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeaks.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeaks.cs @@ -1,142 +1,45 @@ using Microsoft.Xna.Framework; using System; -using System.Collections.Generic; using System.Linq; +using Barotrauma.Extensions; +using System.Collections.Generic; namespace Barotrauma { - class AIObjectiveFixLeaks : AIObjective + class AIObjectiveFixLeaks : AIObjectiveLoop { - const float UpdateGapListInterval = 5.0f; + public override string DebugTag => "fix leaks"; + public override bool KeepDivingGearOn => true; - private double lastGapUpdate; - - private AIObjectiveIdle idleObjective; - - private AIObjectiveFindDivingGear findDivingGear; - - private List objectiveList; - - public AIObjectiveFixLeaks(Character character) - : base (character, "") - { - } - - public override bool IsCompleted() - { - if (Timing.TotalTime > lastGapUpdate + UpdateGapListInterval || objectiveList == null) - { - UpdateGapList(); - lastGapUpdate = Timing.TotalTime; - } - - return objectiveList.Count == 0; - } + public AIObjectiveFixLeaks(Character character) : base (character, "") { } public override float GetPriority(AIObjectiveManager objectiveManager) { - if (Timing.TotalTime > lastGapUpdate + UpdateGapListInterval || objectiveList == null) + if (character.Submarine == null) { return 0; } + if (targets.None()) { return 0; } + if (objectiveManager.CurrentOrder == this) { - UpdateGapList(); - lastGapUpdate = Timing.TotalTime; + return AIObjectiveManager.OrderPriority; } - - float priority = 0.0f; - foreach (AIObjectiveFixLeak fixObjective in objectiveList) - { - //gaps from outside to inside significantly increase the priority - if (!fixObjective.Leak.IsRoomToRoom) - { - priority = Math.Max(priority + fixObjective.Leak.Open * 100.0f, 50.0f); - } - else - { - priority += fixObjective.Leak.Open * 10.0f; - } - - if (priority >= 100.0f) break; - } - - return Math.Min(priority, 100.0f); + return MathHelper.Lerp(0, AIObjectiveManager.OrderPriority, targets.Average(t => Average(t))); } - protected override void Act(float deltaTime) + protected override void FindTargets() { - if (Timing.TotalTime > lastGapUpdate + UpdateGapListInterval || objectiveList == null) - { - UpdateGapList(); - lastGapUpdate = Timing.TotalTime; - } - - if (objectiveList.Any()) - { - if (!objectiveList[objectiveList.Count - 1].Leak.IsRoomToRoom) - { - if (findDivingGear == null) findDivingGear = new AIObjectiveFindDivingGear(character, true); - - if (!findDivingGear.IsCompleted() && findDivingGear.CanBeCompleted) - { - findDivingGear.TryComplete(deltaTime); - return; - } - } - - objectiveList[objectiveList.Count - 1].TryComplete(deltaTime); - - if (!objectiveList[objectiveList.Count - 1].CanBeCompleted || - objectiveList[objectiveList.Count - 1].IsCompleted()) - { - objectiveList.RemoveAt(objectiveList.Count - 1); - } - } - else - { - if (idleObjective == null) idleObjective = new AIObjectiveIdle(character); - idleObjective.TryComplete(deltaTime); - } + base.FindTargets(); + targets.Sort((x, y) => GetGapFixPriority(y).CompareTo(GetGapFixPriority(x))); } - private void UpdateGapList() + protected override bool Filter(Gap gap) { - if (objectiveList == null) { objectiveList = new List(); } - objectiveList.Clear(); - - foreach (Gap gap in Gap.GapList) + bool ignore = ignoreList.Contains(gap) || gap.ConnectedWall == null || gap.ConnectedDoor != null || gap.Open <= 0 || gap.linkedTo.All(l => l == null); + if (!ignore) { - if (gap.ConnectedWall == null) { continue; } - if (gap.ConnectedDoor != null || gap.Open <= 0.0f) { continue; } - //not linked to a hull -> ignore - if (gap.linkedTo.All(l => l == null)) { continue; } - - if (character.TeamID == Character.TeamType.None) - { - //TODO: make sure the gap isn't in another sub (outpost, respawn shuttle...?) - if (gap.Submarine == null) continue; - } - else - { - //prevent characters from attempting to fix leaks in the enemy sub - //team 1 plays in sub 0, team 2 in sub 1 - Submarine mySub = Submarine.MainSub; - if (character.TeamID == Character.TeamType.Team2 && Submarine.MainSubs.Length > 1) - { - mySub = Submarine.MainSubs[1]; - } - - if (gap.Submarine != mySub) continue; - } - - float gapPriority = GetGapFixPriority(gap); - - int index = 0; - while (index < objectiveList.Count && - GetGapFixPriority(objectiveList[index].Leak) < gapPriority) - { - index++; - } - - objectiveList.Insert(index, new AIObjectiveFixLeak(gap, character)); + if (gap.Submarine == null) { ignore = true; } + else if (gap.Submarine.TeamID != character.TeamID) { ignore = true; } + else if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(gap, true)) { ignore = true; } } + return ignore; } private float GetGapFixPriority(Gap gap) @@ -156,9 +59,9 @@ namespace Barotrauma } - public override bool IsDuplicate(AIObjective otherObjective) - { - return otherObjective is AIObjectiveFixLeaks; - } + public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveFixLeaks; + protected override float Average(Gap gap) => gap.Open; + protected override IEnumerable GetList() => Gap.GapList; + protected override AIObjective ObjectiveConstructor(Gap gap) => new AIObjectiveFixLeak(gap, character); } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGetItem.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGetItem.cs index 215a9921f..d3ded6a94 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGetItem.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGetItem.cs @@ -8,6 +8,8 @@ namespace Barotrauma { class AIObjectiveGetItem : AIObjective { + public override string DebugTag => "get item"; + public Func GetItemPriority; //can be either tags or identifiers @@ -17,8 +19,6 @@ namespace Barotrauma private int currSearchIndex; - private bool canBeCompleted; - public bool IgnoreContainedItems; private AIObjectiveGoTo goToObjective; @@ -27,10 +27,8 @@ namespace Barotrauma private bool equip; - public override bool CanBeCompleted - { - get { return canBeCompleted; } - } + private bool canBeCompleted = true; + public override bool CanBeCompleted => canBeCompleted; public override float GetPriority(AIObjectiveManager objectiveManager) { @@ -45,7 +43,6 @@ namespace Barotrauma public AIObjectiveGetItem(Character character, Item targetItem, bool equip = false) : base(character, "") { - canBeCompleted = true; currSearchIndex = -1; this.equip = equip; this.targetItem = targetItem; @@ -59,7 +56,6 @@ namespace Barotrauma public AIObjectiveGetItem(Character character, string[] itemIdentifiers, bool equip = false) : base(character, "") { - canBeCompleted = true; currSearchIndex = -1; this.equip = equip; this.itemIdentifiers = itemIdentifiers; @@ -112,6 +108,7 @@ namespace Barotrauma FindTargetItem(); if (targetItem == null || moveToTarget == null) { + // TODO: cannot be completed? character?.AIController?.SteeringManager?.Reset(); return; } @@ -184,12 +181,16 @@ namespace Barotrauma { if (itemIdentifiers == null) { - if (targetItem == null) canBeCompleted = false; + if (targetItem == null) + { +#if DEBUG + DebugConsole.NewMessage($"{character.Name}: Cannot find the item, because neither identifiers nor item is was defined."); +#endif + canBeCompleted = false; + } return; } - float currDist = moveToTarget == null ? 0.0f : Vector2.DistanceSquared(moveToTarget.Position, character.Position); - for (int i = 0; i < 10 && currSearchIndex < Item.ItemList.Count - 1; i++) { currSearchIndex++; @@ -234,7 +235,13 @@ namespace Barotrauma } //if searched through all the items and a target wasn't found, can't be completed - if (currSearchIndex >= Item.ItemList.Count - 1 && targetItem == null) canBeCompleted = false; + if (currSearchIndex >= Item.ItemList.Count - 1 && targetItem == null) + { +#if DEBUG + DebugConsole.NewMessage($"{character.Name}: Cannot find the item with the following identifier(s): {string.Join(", ", itemIdentifiers)}"); +#endif + canBeCompleted = false; + } } public override bool IsDuplicate(AIObjective otherObjective) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGoTo.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGoTo.cs index 72a350091..5a88f2646 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGoTo.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGoTo.cs @@ -1,5 +1,4 @@ -using Barotrauma.Items.Components; -using FarseerPhysics; +using FarseerPhysics; using Microsoft.Xna.Framework; using System; @@ -7,9 +6,14 @@ namespace Barotrauma { class AIObjectiveGoTo : AIObjective { + public override string DebugTag => "go to"; + + private AIObjectiveFindDivingGear findDivingGear; + private Vector2 targetPos; private bool repeat; + private bool cannotReach; //how long until the path to the target is declared unreachable private float waitUntilPathUnreachable; @@ -40,24 +44,36 @@ namespace Barotrauma { get { - if (FollowControlledCharacter && Character.Controlled == null) { return false; } - - if (Target != null && Target.Removed) { return false; } - - if (repeat || waitUntilPathUnreachable > 0.0f) { return true; } - var pathSteering = character.AIController.SteeringManager as IndoorsSteeringManager; - - //path doesn't exist (= hasn't been searched for yet), assume for now that the target is reachable - if (pathSteering?.CurrentPath == null) { return true; } - - if (!AllowGoingOutside && pathSteering.CurrentPath.HasOutdoorsNodes) { return false; } - - return !pathSteering.CurrentPath.Unreachable; + bool canComplete = !cannotReach && !abandon; + if (FollowControlledCharacter && Character.Controlled == null) { canComplete = false; } + else if (Target != null && Target.Removed) { canComplete = false; } + else if (repeat || waitUntilPathUnreachable > 0.0f) { canComplete = true; } + else if (character.AIController.SteeringManager is IndoorsSteeringManager pathSteering) + { + //path doesn't exist (= hasn't been searched for yet), assume for now that the target is reachable TODO: add a timer? + if (pathSteering.CurrentPath == null) { canComplete = true; } + else if (!AllowGoingOutside && pathSteering.CurrentPath.HasOutdoorsNodes) { canComplete = false; } + if (canComplete) + { + canComplete = !pathSteering.CurrentPath.Unreachable; + } + } + if (!canComplete) + { +#if DEBUG + DebugConsole.NewMessage($"{character.Name}: Cannot reach the target."); +#endif + character.Speak(TextManager.Get("DialogCannotReach"), identifier: "cannotreach", minDurationBetweenSimilar: 10.0f); + character.AIController.SteeringManager.Reset(); + } + return canComplete; } } public Entity Target { get; private set; } + public Vector2 TargetPos => Target != null ? Target.SimPosition : targetPos; + public bool FollowControlledCharacter; public AIObjectiveGoTo(Entity target, Character character, bool repeat = false, bool getDivingGearIfNeeded = true) @@ -97,7 +113,7 @@ namespace Barotrauma waitUntilPathUnreachable -= deltaTime; - if (character.SelectedConstruction != null && character.SelectedConstruction.GetComponent() == null) + if (!character.IsClimbing) { character.SelectedConstruction = null; } @@ -128,48 +144,32 @@ namespace Barotrauma } else { - if (!AllowGoingOutside) + var indoorSteering = character.AIController.SteeringManager as IndoorsSteeringManager; + bool targetIsOutside = (Target != null && Target.Submarine == null) || (indoorSteering != null && indoorSteering.CurrentPath != null && indoorSteering.CurrentPath.HasOutdoorsNodes); + if (targetIsOutside && !AllowGoingOutside) { - //if path is up-to-date and contains outdoors nodes, this path is unreachable - var pathSteering = character.AIController.SteeringManager as IndoorsSteeringManager; - if (pathSteering?.CurrentPath != null && - Vector2.Distance(pathSteering.CurrentTarget, currTargetPos) < 1.0f && - pathSteering.CurrentPath.HasOutdoorsNodes) - { - waitUntilPathUnreachable = 0.0f; - character.AIController.SteeringManager.Reset(); - return; - } + cannotReach = true; } - - float normalSpeed = character.AnimController.GetCurrentSpeed(false); - character.AIController.SteeringManager.SteeringSeek(currTargetPos, normalSpeed); - if (getDivingGearIfNeeded && Target?.Submarine == null && AllowGoingOutside) + else { - if (indoorsSteering.CurrentPath == null || indoorsSteering.CurrentPath.Unreachable) + character.AIController.SteeringManager.SteeringSeek(currTargetPos); + if (getDivingGearIfNeeded) { - indoorsSteering.SteeringWander(normalSpeed); - } - else if (AllowGoingOutside && - getDivingGearIfNeeded && - indoorsSteering.CurrentPath != null && - indoorsSteering.CurrentPath.HasOutdoorsNodes) - { - AddSubObjective(new AIObjectiveFindDivingGear(character, true)); - } - } - else if (character.AIController.SteeringManager is IndoorsSteeringManager indoorsSteering) - { - if (indoorsSteering.CurrentPath == null || indoorsSteering.CurrentPath.Unreachable) - { - indoorsSteering.SteeringWander(normalSpeed); - } - else if (AllowGoingOutside && - getDivingGearIfNeeded && - indoorsSteering.CurrentPath != null && - indoorsSteering.CurrentPath.HasOutdoorsNodes) - { - AddSubObjective(new AIObjectiveFindDivingGear(character, true)); + if (targetIsOutside || + Target is Hull h && HumanAIController.NeedsDivingGear(h) || + Target is Item i && HumanAIController.NeedsDivingGear(i.CurrentHull) || + Target is Character c && HumanAIController.NeedsDivingGear(c.CurrentHull)) + { + if (findDivingGear == null) + { + findDivingGear = new AIObjectiveFindDivingGear(character, true); + AddSubObjective(findDivingGear); + } + else if (!findDivingGear.CanBeCompleted) + { + abandon = true; + } + } } } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveIdle.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveIdle.cs index 7541a0a43..8c2fd39ac 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveIdle.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveIdle.cs @@ -1,5 +1,4 @@ -using Barotrauma.Items.Components; -using FarseerPhysics; +using FarseerPhysics; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; @@ -9,26 +8,24 @@ namespace Barotrauma { class AIObjectiveIdle : AIObjective { + public override string DebugTag => "idle"; + const float WallAvoidDistance = 150.0f; - private AITarget currentTarget; + private Hull currentTarget; private float newTargetTimer; private float standStillTimer; private float walkDuration; - private AIObjectiveFindSafety findSafety; - public AIObjectiveIdle(Character character) : base(character, "") { standStillTimer = Rand.Range(-10.0f, 10.0f); walkDuration = Rand.Range(0.0f, 10.0f); } - public override bool IsCompleted() - { - return false; - } + public override bool IsCompleted() => false; + public override bool CanBeCompleted => true; public override float GetPriority(AIObjectiveManager objectiveManager) { @@ -37,64 +34,59 @@ namespace Barotrauma protected override void Act(float deltaTime) { - var pathSteering = character.AIController.SteeringManager as IndoorsSteeringManager; - if (pathSteering == null) return; + if (PathSteering == null) return; //don't keep dragging others when idling if (character.SelectedCharacter != null) { character.DeselectCharacter(); } - if (character.SelectedConstruction != null && character.SelectedConstruction.GetComponent() == null) + if (!character.IsClimbing) { character.SelectedConstruction = null; } - - if (character.AnimController.InWater) - { - //attempt to find a safer place if in water - if (findSafety == null) findSafety = new AIObjectiveFindSafety(character); - findSafety.TryComplete(deltaTime); - return; - } + if (currentTarget == null && (IsForbidden(character.CurrentHull) || HumanAIController.UnsafeHulls.Contains(character.CurrentHull))) + { + newTargetTimer = 0; + standStillTimer = 0; + } + if (character.AnimController.InWater || character.IsClimbing) + { + standStillTimer = 0; + } if (newTargetTimer <= 0.0f) { - currentTarget = FindRandomTarget(); + currentTarget = FindRandomHull(); if (currentTarget != null) { - Vector2 pos = character.SimPosition; - if (character != null && character.Submarine == null) { pos -= Submarine.MainSub.SimPosition; } - - string errorMsg = "(Character " + character.Name + " idling, target " - + ((currentTarget.Entity is Hull hull && hull.RoomName != null) ? hull.RoomName : currentTarget.Entity.ToString()) + ")"; - - var path = pathSteering.PathFinder.FindPath(pos, currentTarget.SimPosition, errorMsg); - if (path.Cost > 1000.0f && character.AnimController.CurrentHull!=null) return; - - pathSteering.SetPath(path); + string errorMsg = null; +#if DEBUG + bool isRoomNameFound = currentTarget.RoomName != null; + errorMsg = "(Character " + character.Name + " idling, target " + (isRoomNameFound ? currentTarget.RoomName : currentTarget.ToString()) + ")"; +#endif + var path = PathSteering.PathFinder.FindPath(character.SimPosition, currentTarget.SimPosition, errorMsg); + PathSteering.SetPath(path); } - newTargetTimer = currentTarget == null ? 5.0f : 15.0f; } newTargetTimer -= deltaTime; - //wander randomly // - if reached the end of the path // - if the target is unreachable // - if the path requires going outside - if (pathSteering == null || (pathSteering.CurrentPath != null && - (pathSteering.CurrentPath.NextNode == null || pathSteering.CurrentPath.Unreachable || pathSteering.CurrentPath.HasOutdoorsNodes))) + if (PathSteering == null || (PathSteering.CurrentPath != null && + (PathSteering.CurrentPath.NextNode == null || PathSteering.CurrentPath.Unreachable || PathSteering.CurrentPath.HasOutdoorsNodes))) { standStillTimer -= deltaTime; if (standStillTimer > 0.0f) { walkDuration = Rand.Range(1.0f, 5.0f); - pathSteering.Reset(); + PathSteering.Reset(); return; } @@ -104,7 +96,7 @@ namespace Barotrauma } //steer away from edges of the hull - if (character.AnimController.CurrentHull != null) + if (character.AnimController.CurrentHull != null && !character.IsClimbing) { float leftDist = character.Position.X - character.AnimController.CurrentHull.Rect.X; float rightDist = character.AnimController.CurrentHull.Rect.Right - character.Position.X; @@ -113,95 +105,119 @@ namespace Barotrauma { if (Math.Abs(rightDist - leftDist) > WallAvoidDistance / 2) { - pathSteering.SteeringManual(deltaTime, Vector2.UnitX * Math.Sign(rightDist - leftDist)); + PathSteering.SteeringManual(deltaTime, Vector2.UnitX * Math.Sign(rightDist - leftDist)); } else { - pathSteering.Reset(); + PathSteering.Reset(); return; } } else if (leftDist < WallAvoidDistance) { - pathSteering.SteeringManual(deltaTime, Vector2.UnitX * (WallAvoidDistance-leftDist)/WallAvoidDistance); - pathSteering.WanderAngle = 0.0f; + PathSteering.SteeringManual(deltaTime, Vector2.UnitX * (WallAvoidDistance-leftDist) / WallAvoidDistance); + PathSteering.WanderAngle = 0.0f; return; } else if (rightDist < WallAvoidDistance) { - pathSteering.SteeringManual(deltaTime, -Vector2.UnitX * (WallAvoidDistance-rightDist)/WallAvoidDistance); - pathSteering.WanderAngle = MathHelper.Pi; + PathSteering.SteeringManual(deltaTime, -Vector2.UnitX * (WallAvoidDistance-rightDist) / WallAvoidDistance); + PathSteering.WanderAngle = MathHelper.Pi; return; } } - character.AIController.SteeringManager.SteeringWander(character.AnimController.GetCurrentSpeed(false)); - //reset vertical steering to prevent dropping down from platforms etc - character.AIController.SteeringManager.ResetY(); + character.AIController.SteeringManager.SteeringWander(); + if (!character.IsClimbing) + { + //reset vertical steering to prevent dropping down from platforms etc + character.AIController.SteeringManager.ResetY(); + } return; } - - if (currentTarget?.Entity == null) return; - if (currentTarget.Entity.Removed) + + if (currentTarget != null) { - currentTarget = null; - return; + character.AIController.SteeringManager.SteeringSeek(currentTarget.SimPosition); } - character.AIController.SteeringManager.SteeringSeek(currentTarget.SimPosition, character.AnimController.GetCurrentSpeed(true)); } - private AITarget FindRandomTarget() - { - //random chance of navigating back to the room where the character spawned - if (Rand.Int(5) == 1) - { - var idCard = character.Inventory.FindItemByIdentifier("idcard"); - if (idCard == null) return null; + private readonly List targetHulls = new List(20); + private readonly List hullWeights = new List(20); + private Hull FindRandomHull() + { + var idCard = character.Inventory.FindItemByIdentifier("idcard"); + Hull targetHull = null; + //random chance of navigating back to the room where the character spawned + if (Rand.Int(5) == 1 && idCard != null) + { foreach (WayPoint wp in WayPoint.WayPointList) { - if (wp.SpawnType != SpawnType.Human || wp.CurrentHull == null) continue; + if (wp.SpawnType != SpawnType.Human || wp.CurrentHull == null) { continue; } foreach (string tag in wp.IdCardTags) { - if (idCard.HasTag(tag)) return wp.CurrentHull.AiTarget; + if (idCard.HasTag(tag)) + { + targetHull = wp.CurrentHull; + } } } } - else + if (targetHull == null) { - List targetHulls = new List(Hull.hullList); - //ignore all hulls with fires or water in them - targetHulls.RemoveAll(h => h.FireSources.Any() || h.WaterVolume / h.Volume > 0.1f); - if (character.Submarine != null) + targetHulls.Clear(); + hullWeights.Clear(); + foreach (var hull in Hull.hullList) { - targetHulls.RemoveAll(h => h.Submarine != character.Submarine); - } - - //remove ballast hulls - foreach (Item item in Item.ItemList) - { - if (item.HasTag("ballast") && targetHulls.Contains(item.CurrentHull)) + if (HumanAIController.UnsafeHulls.Contains(hull)) { continue; } + if (hull.Submarine == null) { continue; } + if (hull.Submarine.TeamID != character.TeamID) { continue; } + // If the character is inside, only take connected hulls into account. + if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(hull, true)) { continue; } + if (IsForbidden(hull)) { continue; } + // Ignore hulls that are too low to stand inside + if (character.AnimController is HumanoidAnimController animController) { - targetHulls.Remove(item.CurrentHull); + if (hull.CeilingHeight < ConvertUnits.ToDisplayUnits(animController.HeadPosition.Value)) + { + continue; + } + } + // Check that there is no unsafe or forbidden hulls on the way to the target + var path = PathSteering.PathFinder.FindPath(character.SimPosition, hull.SimPosition); + if (path.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull) || IsForbidden(n.CurrentHull))) { continue; } + + // If we want to do a steering check, we should do it here, before setting the path + //if (path.Cost > 1000.0f) { continue; } + + if (!targetHulls.Contains(hull)) + { + targetHulls.Add(hull); + hullWeights.Add(hull.Volume); } } - - //ignore hulls that are too low to stand inside - if (character.AnimController is HumanoidAnimController animController) - { - float minHeight = ConvertUnits.ToDisplayUnits(animController.HeadPosition.Value); - targetHulls.RemoveAll(h => h.CeilingHeight < minHeight); - } - if (!targetHulls.Any()) return null; - - //prefer larger hulls - var targetHull = ToolBox.SelectWeightedRandom(targetHulls, targetHulls.Select(h => h.Volume).ToList(), Rand.RandSync.Unsynced); - return targetHull?.AiTarget; + return ToolBox.SelectWeightedRandom(targetHulls, hullWeights, Rand.RandSync.Unsynced); } + return targetHull; + } - return null; + private bool IsForbidden(Hull hull) + { + if (hull == null) { return true; } + string hullName = hull.RoomName?.ToLowerInvariant(); + bool isForbidden = hullName == "ballast" || hullName == "airlock"; + foreach (Item item in Item.ItemList) + { + if (item.CurrentHull == hull && (item.HasTag("ballast") || item.HasTag("airlock"))) + { + isForbidden = true; + break; + } + } + return isForbidden; } public override bool IsDuplicate(AIObjective otherObjective) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveLoop.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveLoop.cs new file mode 100644 index 000000000..070d8c2c8 --- /dev/null +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveLoop.cs @@ -0,0 +1,122 @@ +using System.Collections.Generic; +using System.Linq; +using Barotrauma.Extensions; +using Microsoft.Xna.Framework; + +namespace Barotrauma +{ + abstract class AIObjectiveLoop : AIObjective + { + protected List targets = new List(); + protected Dictionary objectives = new Dictionary(); + protected HashSet ignoreList = new HashSet(); + protected readonly float ignoreListClearInterval = 30; + private float ignoreListTimer; + protected readonly float targetUpdateInterval = 2; + private float targetUpdateTimer; + + public AIObjectiveLoop(Character character, string option) : base(character, option) + { + FindTargets(); + CreateObjectives(); + } + + protected override void Act(float deltaTime) { } + public override bool IsCompleted() => false; + public override bool CanBeCompleted => true; + + public override void Update(AIObjectiveManager objectiveManager, float deltaTime) + { + base.Update(objectiveManager, deltaTime); + if (ignoreListTimer > ignoreListClearInterval) + { + ignoreList.Clear(); + ignoreListTimer = 0; + UpdateTargets(); + } + else + { + ignoreListTimer += deltaTime; + } + if (targetUpdateTimer > targetUpdateInterval) + { + targetUpdateTimer = 0; + UpdateTargets(); + } + else + { + targetUpdateTimer += deltaTime; + } + // Sync objectives, subobjectives and targets + foreach (var objective in objectives) + { + var target = objective.Key; + if (!objective.Value.CanBeCompleted) + { + ignoreList.Add(target); + } + if (!targets.Contains(target)) + { + subObjectives.Remove(objective.Value); + } + } + SyncRemovedObjectives(objectives, GetList()); + if (objectives.None()) + { + CreateObjectives(); + } + } + + public override float GetPriority(AIObjectiveManager objectiveManager) + { + if (character.Submarine == null) { return 0; } + if (targets.None()) { return 0; } + float avg = targets.Average(t => Average(t)); + if (objectiveManager.CurrentOrder == this) + { + return AIObjectiveManager.OrderPriority - MathHelper.Max(0, AIObjectiveManager.OrderPriority - avg); + } + return MathHelper.Lerp(0, AIObjectiveManager.OrderPriority, avg / 100); + } + + protected void UpdateTargets() + { + targets.Clear(); + FindTargets(); + CreateObjectives(); + } + + protected virtual void FindTargets() + { + foreach (T item in GetList()) + { + if (Filter(item)) { continue; } + if (!targets.Contains(item)) + { + targets.Add(item); + } + } + } + + protected virtual void CreateObjectives() + { + foreach (T target in targets) + { + if (!objectives.TryGetValue(target, out AIObjective objective)) + { + objective = ObjectiveConstructor(target); + objectives.Add(target, objective); + AddSubObjective(objective); + } + } + } + + /// + /// List of all possible items of the specified type. Used for filtering the removed objectives. + /// + protected abstract IEnumerable GetList(); + protected abstract float Average(T target); + protected abstract AIObjective ObjectiveConstructor(T target); + protected abstract bool Filter(T target); + } +} diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveManager.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveManager.cs index 7e01d3111..b456d9f62 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveManager.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveManager.cs @@ -2,47 +2,58 @@ using System; using System.Collections.Generic; using System.Linq; +using Barotrauma.Extensions; namespace Barotrauma { class AIObjectiveManager { + // TODO: expose public const float OrderPriority = 50.0f; + // Constantly increases the priority of the selected objective, unless overridden + public const float baseDevotion = 2; - private List objectives; + public List Objectives { get; private set; } private Character character; - private AIObjective currentOrder; - /// /// When set above zero, the character will stand still doing nothing until the timer runs out (assuming they don't a high priority order active) /// public float WaitTimer; - public AIObjective CurrentOrder - { - get { return currentOrder; } - } - - public AIObjective CurrentObjective - { - get; - private set; - } + public AIObjective CurrentOrder { get; private set; } + public AIObjective CurrentObjective { get; private set; } public AIObjectiveManager(Character character) { this.character = character; - objectives = new List(); + Objectives = new List(); } public void AddObjective(AIObjective objective) { - if (objectives.Find(o => o.IsDuplicate(objective)) != null) return; + if (Objectives.Find(o => o.IsDuplicate(objective)) != null) return; - objectives.Add(objective); + Objectives.Add(objective); + } + + public Dictionary DelayedObjectives { get; private set; } = new Dictionary(); + public void AddObjective(AIObjective objective, float delay, Action callback = null) + { + if (DelayedObjectives.TryGetValue(objective, out CoroutineHandle coroutine)) + { + CoroutineManager.StopCoroutines(coroutine); + DelayedObjectives.Remove(objective); + } + coroutine = CoroutineManager.InvokeAfter(() => + { + DelayedObjectives.Remove(objective); + AddObjective(objective); + callback?.Invoke(); + }, delay); + DelayedObjectives.Add(objective, coroutine); } public Dictionary DelayedObjectives { get; private set; } = new Dictionary(); @@ -64,7 +75,7 @@ namespace Barotrauma public T GetObjective() where T : AIObjective { - foreach (AIObjective objective in objectives) + foreach (AIObjective objective in Objectives) { if (objective is T) return (T)objective; } @@ -73,62 +84,85 @@ namespace Barotrauma private AIObjective GetCurrentObjective() { - if (CurrentOrder != null && - (objectives.Count == 0 || currentOrder.GetPriority(this) > objectives[0].GetPriority(this))) + var firstObjective = Objectives.FirstOrDefault(); + if (CurrentOrder != null && firstObjective != null && CurrentOrder.GetPriority(this) > firstObjective.GetPriority(this)) { - return CurrentOrder; + CurrentObjective = CurrentOrder; } - - return objectives.Count == 0 ? null : objectives[0]; + else + { + CurrentObjective = firstObjective; + } + return CurrentObjective; } public float GetCurrentPriority() { - var currentObjective = GetCurrentObjective(); - return currentObjective == null ? 0.0f : currentObjective.GetPriority(this); + return CurrentObjective == null ? 0.0f : CurrentObjective.GetPriority(this); } - public void UpdateObjectives() + public void UpdateObjectives(float deltaTime) { - if (!objectives.Any()) return; - - //remove completed objectives and ones that can't be completed - objectives = objectives.FindAll(o => !o.IsCompleted() && o.CanBeCompleted); - - //sort objectives according to priority - objectives.Sort((x, y) => y.GetPriority(this).CompareTo(x.GetPriority(this))); - GetCurrentObjective()?.SortSubObjectives(this); + CurrentOrder?.Update(this, deltaTime); + for (int i = 0; i < Objectives.Count; i++) + { + var objective = Objectives[i]; + if (objective.IsCompleted()) + { +#if DEBUG + DebugConsole.NewMessage($"Removing objective {objective.DebugTag}, because it is completed."); +#endif + Objectives.Remove(objective); + } + else if (!objective.CanBeCompleted) + { +#if DEBUG + DebugConsole.NewMessage($"Removing objective {objective.DebugTag}, because it cannot be completed."); +#endif + Objectives.Remove(objective); + } + else + { + objective.Update(this, deltaTime); + } + } + GetCurrentObjective(); } + public void SortObjectives() + { + if (Objectives.Any()) + { + Objectives.Sort((x, y) => y.GetPriority(this).CompareTo(x.GetPriority(this))); + } + CurrentObjective?.SortSubObjectives(this); + } public void DoCurrentObjective(float deltaTime) { - CurrentObjective = GetCurrentObjective(); - if (CurrentObjective == null || (CurrentObjective.GetPriority(this) < OrderPriority && WaitTimer > 0.0f)) { WaitTimer -= deltaTime; character.AIController.SteeringManager.Reset(); return; } - CurrentObjective?.TryComplete(deltaTime); } public void SetOrder(AIObjective objective) { - currentOrder = objective; + CurrentOrder = objective; } public void SetOrder(Order order, string option, Character orderGiver) { - currentOrder = null; + CurrentOrder = null; if (order == null) return; switch (order.AITag.ToLowerInvariant()) { case "follow": - currentOrder = new AIObjectiveGoTo(orderGiver, character, true) + CurrentOrder = new AIObjectiveGoTo(orderGiver, character, true) { CloseEnough = 1.5f, AllowGoingOutside = true, @@ -137,13 +171,34 @@ namespace Barotrauma }; break; case "wait": - currentOrder = new AIObjectiveGoTo(character, character, true) + CurrentOrder = new AIObjectiveGoTo(character, character, true) { AllowGoingOutside = true }; break; case "fixleaks": - currentOrder = new AIObjectiveFixLeaks(character); + CurrentOrder = new AIObjectiveFixLeaks(character); + break; + case "chargebatteries": + CurrentOrder = new AIObjectiveChargeBatteries(character, option); + break; + case "rescue": + CurrentOrder = new AIObjectiveRescueAll(character); + break; + case "repairsystems": + CurrentOrder = new AIObjectiveRepairItems(character) { RequireAdequateSkills = option != "all" }; + break; + case "pumpwater": + CurrentOrder = new AIObjectivePumpWater(character, option); + break; + case "extinguishfires": + CurrentOrder = new AIObjectiveExtinguishFires(character); + break; + case "steer": + var steering = (order?.TargetEntity as Item)?.GetComponent(); + if (steering != null) steering.PosToMaintain = steering.Item.Submarine?.WorldPosition; + if (order.TargetItemComponent == null) return; + CurrentOrder = new AIObjectiveOperateItem(order.TargetItemComponent, character, option, false, null, order.UseController); break; case "chargebatteries": currentOrder = new AIObjectiveChargeBatteries(character, option); @@ -168,7 +223,7 @@ namespace Barotrauma break; default: if (order.TargetItemComponent == null) return; - currentOrder = new AIObjectiveOperateItem(order.TargetItemComponent, character, option, false, null, order.UseController); + CurrentOrder = new AIObjectiveOperateItem(order.TargetItemComponent, character, option, false, null, order.UseController); break; } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveOperateItem.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveOperateItem.cs index ed7b506b5..6b244961a 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveOperateItem.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveOperateItem.cs @@ -7,6 +7,8 @@ namespace Barotrauma { class AIObjectiveOperateItem : AIObjective { + public override string DebugTag => "operate item"; + private ItemComponent component, controller; private Entity operateTarget; @@ -38,14 +40,16 @@ namespace Barotrauma get { return operateTarget; } } + public ItemComponent Component => component; + public override float GetPriority(AIObjectiveManager objectiveManager) { + if (gotoObjective != null && !gotoObjective.CanBeCompleted) { return 0; } if (objectiveManager.CurrentOrder == this) { return AIObjectiveManager.OrderPriority; } - - return 1.0f; + return base.GetPriority(objectiveManager); } public AIObjectiveOperateItem(ItemComponent item, Character character, string option, bool requireEquip, Entity operateTarget = null, bool useController = false) @@ -158,7 +162,7 @@ namespace Barotrauma AIObjectiveOperateItem operateItem = otherObjective as AIObjectiveOperateItem; if (operateItem == null) return false; - return (operateItem.component == component); + return (operateItem.component == component ||otherObjective.Option == Option); } } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectivePumpWater.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectivePumpWater.cs index 24e612f1f..5409a82d5 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectivePumpWater.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectivePumpWater.cs @@ -1,108 +1,68 @@ using Barotrauma.Items.Components; -using System; using System.Collections.Generic; using System.Linq; -using System.Text; namespace Barotrauma { - class AIObjectivePumpWater : AIObjective + class AIObjectivePumpWater : AIObjectiveLoop { - private const float FindPumpsInterval = 5.0f; + public override string DebugTag => "pump water"; + public override bool KeepDivingGearOn => true; + private readonly IEnumerable pumpList; - private string orderOption; - private List pumps; - private float lastFindPumpsTime; - - public AIObjectivePumpWater(Character character, string option) - : base(character, option) + public AIObjectivePumpWater(Character character, string option) : base(character, option) { - orderOption = option; + pumpList = character.Submarine.GetItems(true).Select(i => i.GetComponent()).Where(p => p != null); } public override float GetPriority(AIObjectiveManager objectiveManager) { - if (Timing.TotalTime >= lastFindPumpsTime + FindPumpsInterval) - { - FindPumps(); - } - - if (objectiveManager.CurrentOrder == this && pumps.Count > 0) + if (character.Submarine == null) { return 0; } + if (objectiveManager.CurrentOrder == this && targets.Count > 0) { return AIObjectiveManager.OrderPriority; } - return 0.0f; } - public override bool IsCompleted() + public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectivePumpWater && otherObjective.Option == Option; + + //availablePumps = allPumps.Where(p => !p.Item.HasTag("ballast") && p.Item.Connections.None(c => c.IsPower && p.Item.GetConnectedComponentsRecursive(c).None())).ToList(); + protected override void FindTargets() { - return false; - } - - public override bool IsDuplicate(AIObjective otherObjective) - { - return otherObjective is AIObjectivePumpWater; - } - - protected override void Act(float deltaTime) - { - if (Timing.TotalTime < lastFindPumpsTime + FindPumpsInterval) - { - return; - } - - FindPumps(); - } - - private void FindPumps() - { - lastFindPumpsTime = (float)Timing.TotalTime; - - pumps = new List(); + if (option == null) { return; } foreach (Item item in Item.ItemList) { - //don't attempt to use pumps outside the sub + if (item.HasTag("ballast")) { continue; } if (item.Submarine == null) { continue; } - + if (item.Submarine.TeamID != character.TeamID) { continue; } + if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(item, true)) { continue; } var pump = item.GetComponent(); - if (pump == null) continue; - - if (item.HasTag("ballast")) continue; - - //if the pump is connected to an item with a steering component, it must be a ballast pump - //(This may not work correctly if the signals are passed through some fancy circuit or a wifi component, - //which is why sub creators are encouraged to tag the ballast pumps) - bool connectedToSteering = false; - foreach (Connection c in item.Connections) + if (pump != null) { - if (c.IsPower) continue; - if (item.GetConnectedComponentsRecursive(c).Count > 0) + if (!ignoreList.Contains(pump)) { - connectedToSteering = true; - break; + if (option == "stoppumping") + { + if (!pump.IsActive || pump.FlowPercentage == 0.0f) { continue; } + } + else + { + if (!pump.Item.InWater) { continue; } + if (pump.IsActive && pump.FlowPercentage <= -90.0f) { continue; } + } + if (!targets.Contains(pump)) + { + targets.Add(pump); + } } } - if (connectedToSteering) continue; - - if (orderOption.ToLowerInvariant() == "stop pumping") - { - if (!pump.IsActive || pump.FlowPercentage == 0.0f) continue; - } - else - { - if (!pump.Item.InWater) continue; - if (pump.IsActive && pump.FlowPercentage <= -90.0f) continue; - } - - pumps.Add(pump); - } - - - foreach (Pump pump in pumps) - { - AddSubObjective(new AIObjectiveOperateItem(pump, character, orderOption, false)); } } + + protected override bool Filter(Pump pump) => true; + protected override IEnumerable GetList() => pumpList; + protected override AIObjective ObjectiveConstructor(Pump pump) => new AIObjectiveOperateItem(pump, character, Option, false); + protected override float Average(Pump target) => 0; } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItem.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItem.cs index 88ae75ca1..8a6b47f1f 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItem.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItem.cs @@ -2,100 +2,125 @@ using Microsoft.Xna.Framework; using System; using System.Linq; +using Barotrauma.Extensions; namespace Barotrauma { class AIObjectiveRepairItem : AIObjective { - private Item item; - - public AIObjectiveRepairItem(Character character, Item item) - : base(character, "") + public override string DebugTag => "repair item"; + + public override bool KeepDivingGearOn => true; + + public Item Item { get; private set; } + + private AIObjectiveGoTo goToObjective; + + private float previousCondition = -1; + + public AIObjectiveRepairItem(Character character, Item item) : base(character, "") { - this.item = item; + Item = item; } public override float GetPriority(AIObjectiveManager objectiveManager) { - bool insufficientSkills = true; - bool repairablesFound = false; - foreach (Repairable repairable in item.Repairables) - { - if (item.Condition > repairable.ShowRepairUIThreshold) { continue; } - if (repairable.DegreeOfSuccess(character) >= 0.5f) { insufficientSkills = false; } - repairablesFound = true; - } - - if (!repairablesFound) { return 0.0f; } - - float priority = item.Prefab.Health - item.Condition; - //vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally) - float dist = - Math.Abs(character.WorldPosition.X - item.WorldPosition.X) + - Math.Abs(character.WorldPosition.Y - item.WorldPosition.Y) * 2.0f; - - //heavily increase the priority if the item is already selected - //so characters don't keep switching between nearby damaged items - if (character.SelectedConstruction == item) - { - priority += 50.0f; - } - if (insufficientSkills) - { - return MathHelper.Lerp(0.0f, 50.0f, priority / 100.0f / Math.Max(dist / 100.0f, 1.0f)); - } - else - { - return MathHelper.Lerp(50.0f, 100.0f, priority / 100.0f / Math.Max(dist / 100.0f, 1.0f)); - } + // TODO: priority list? + if (Item.Repairables.None()) { return 0; } + // Ignore items that are being repaired by someone else. + if (Item.Repairables.Any(r => r.CurrentFixer != null && r.CurrentFixer != character)) { return 0; } + // Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally) + float dist = Math.Abs(character.WorldPosition.X - Item.WorldPosition.X) + Math.Abs(character.WorldPosition.Y - Item.WorldPosition.Y) * 2.0f; + float distanceFactor = MathHelper.Lerp(1, 0.5f, MathUtils.InverseLerp(0, 10000, dist)); + float damagePriority = MathHelper.Lerp(1, 0, (Item.Condition + 10) / Item.MaxCondition); + float successFactor = MathHelper.Lerp(0, 1, Item.Repairables.Average(r => r.DegreeOfSuccess(character))); + float isSelected = character.SelectedConstruction == Item ? 50 : 0; + float baseLevel = Math.Max(priority + isSelected, 1); + return MathHelper.Clamp(baseLevel * damagePriority * distanceFactor * successFactor, 0, 100); } + public override bool CanBeCompleted => !abandon; + public override bool IsCompleted() { - foreach (Repairable repairable in item.Repairables) + bool isCompleted = Item.IsFullCondition; + if (isCompleted) { - if (item.Condition < Math.Max(repairable.ShowRepairUIThreshold, item.Prefab.Health * 0.98f)) return false; + character?.Speak(TextManager.Get("DialogItemRepaired").Replace("[itemname]", Item.Name), null, 0.0f, "itemrepaired", 10.0f); } - - character?.Speak(TextManager.Get("DialogItemRepaired").Replace("[itemname]", item.Name), null, 0.0f, "itemrepaired", 10.0f); - return true; + return isCompleted; } public override bool IsDuplicate(AIObjective otherObjective) { - return otherObjective is AIObjectiveRepairItem repairObjective && repairObjective.item == item; + return otherObjective is AIObjectiveRepairItem repairObjective && repairObjective.Item == Item; } protected override void Act(float deltaTime) { - foreach (Repairable repairable in item.Repairables) + if (goToObjective != null && !subObjectives.Contains(goToObjective)) { - //make sure we have all the items required to fix the target item - foreach (var kvp in repairable.requiredItems) + if (!goToObjective.IsCompleted() && !goToObjective.CanBeCompleted) { - foreach (RelatedItem requiredItem in kvp.Value) + abandon = true; + character?.Speak(TextManager.Get("DialogCannotRepair").Replace("[itemname]", Item.Name), null, 0.0f, "cannotrepair", 10.0f); + } + goToObjective = null; + } + foreach (Repairable repairable in Item.Repairables) + { + if (!repairable.HasRequiredItems(character, false)) + { + //make sure we have all the items required to fix the target item + foreach (var kvp in repairable.requiredItems) { - if (!character.Inventory.Items.Any(it => it != null && requiredItem.MatchesItem(it))) + foreach (RelatedItem requiredItem in kvp.Value) { AddSubObjective(new AIObjectiveGetItem(character, requiredItem.Identifiers, true)); - return; } } + return; } } - - if (character.CanInteractWith(item)) + if (character.CanInteractWith(Item)) { - foreach (Repairable repairable in item.Repairables) + foreach (Repairable repairable in Item.Repairables) { - if (character.SelectedConstruction != item) { item.TryInteract(character, true, true); } - repairable.CurrentFixer = character; + if (repairable.CurrentFixer != null && repairable.CurrentFixer != character) + { + // Someone else is repairing the target. Abandon the objective if the other is better at this then us. + abandon = repairable.DegreeOfSuccess(character) < repairable.DegreeOfSuccess(repairable.CurrentFixer); + } + if (!abandon) + { + if (character.SelectedConstruction != Item) + { + Item.TryInteract(character, true, true); + } + if (previousCondition == -1) + { + previousCondition = Item.Condition; + } + else if (Item.Condition < previousCondition) + { + // If the current condition is less than the previous condition, we can't complete the task, so let's abandon it. The item is probably deteriorating at a greater speed than we can repair it. + abandon = true; + character?.Speak(TextManager.Get("DialogRepairFailed").Replace("[itemname]", Item.Name), null, 0.0f, "repairfailed", 10.0f); + } + } + repairable.CurrentFixer = abandon && repairable.CurrentFixer == character ? null : character; break; } } - else + else if (goToObjective == null || goToObjective.Target != Item) { - AddSubObjective(new AIObjectiveGoTo(item, character)); + previousCondition = -1; + if (goToObjective != null) + { + subObjectives.Remove(goToObjective); + } + goToObjective = new AIObjectiveGoTo(Item, character); + AddSubObjective(goToObjective); } } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItems.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItems.cs index 59108f431..4433311ba 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItems.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItems.cs @@ -1,68 +1,68 @@ -using Barotrauma.Items.Components; -using System; -using System.Collections.Generic; -using System.Text; +using System.Collections.Generic; +using Barotrauma.Items.Components; +using Barotrauma.Extensions; namespace Barotrauma { - class AIObjectiveRepairItems : AIObjective + class AIObjectiveRepairItems : AIObjectiveLoop { + public override string DebugTag => "repair items"; + public override bool KeepDivingGearOn => true; + /// /// Should the character only attempt to fix items they have the skills to fix, or any damaged item /// public bool RequireAdequateSkills; - public AIObjectiveRepairItems(Character character) - : base(character, "") - { - } + public AIObjectiveRepairItems(Character character) : base(character, "") { } - public override float GetPriority(AIObjectiveManager objectiveManager) + // TODO: This can allow two active repair items objectives, if RequireAdequateSkills is not at the same value. We don't want that. + public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveRepairItems repairItems && repairItems.RequireAdequateSkills == RequireAdequateSkills; + + protected override void CreateObjectives() { - GetBrokenItems(); - if (subObjectives.Count > 0 && objectiveManager.CurrentOrder == this) + foreach (var item in targets) { - return AIObjectiveManager.OrderPriority; - } - - return 1.0f; - } - - public override bool IsCompleted() - { - return false; - } - - public override bool IsDuplicate(AIObjective otherObjective) - { - return otherObjective is AIObjectiveRepairItems repairItems && repairItems.RequireAdequateSkills == RequireAdequateSkills; - } - - protected override void Act(float deltaTime) - { - GetBrokenItems(); - } - - private void GetBrokenItems() - { - foreach (Item item in Item.ItemList) - { - //ignore items that are in full condition - if (item.Condition >= item.Prefab.Health) continue; foreach (Repairable repairable in item.Repairables) { - //ignore ones that are already fixed - if (item.Condition > repairable.ShowRepairUIThreshold) continue; - - if (RequireAdequateSkills) + if (!objectives.TryGetValue(item, out AIObjective objective)) { - if (!repairable.HasRequiredSkills(character)) { continue; } + objective = ObjectiveConstructor(item); + objectives.Add(item, objective); + AddSubObjective(objective); } - - AddSubObjective(new AIObjectiveRepairItem(character, item)); break; } } } + + protected override bool Filter(Item item) + { + bool ignore = ignoreList.Contains(item) || item.IsFullCondition; + if (!ignore) + { + if (item.Submarine == null) { ignore = true; } + else if (item.Submarine.TeamID != character.TeamID) { ignore = true; } + else if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(item, true)) { ignore = true; } + else + { + if (item.Repairables.None()) { ignore = true; } + else + { + foreach (Repairable repairable in item.Repairables) + { + if (item.Condition > repairable.ShowRepairUIThreshold) { ignore = true; } + else if (RequireAdequateSkills && !repairable.HasRequiredSkills(character)) { ignore = true; } + if (ignore) { break; } + } + } + } + } + return ignore; + } + + protected override float Average(Item item) => 100 - item.ConditionPercentage; + protected override IEnumerable GetList() => Item.ItemList; + protected override AIObjective ObjectiveConstructor(Item item) => new AIObjectiveRepairItem(character, item); } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescue.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescue.cs index cf609ca12..943d7110d 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescue.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescue.cs @@ -9,6 +9,10 @@ namespace Barotrauma { class AIObjectiveRescue : AIObjective { + public override string DebugTag => "rescue"; + public override bool ForceRun => true; + public override bool KeepDivingGearOn => true; + const float TreatmentDelay = 0.5f; private readonly Character targetCharacter; @@ -102,7 +106,7 @@ namespace Barotrauma } return !character.AnimController.InWater && !targetCharacter.AnimController.InWater && - AIObjectiveFindSafety.GetHullSafety(character.CurrentHull, character) > 50.0f; + HumanAIController.GetHullSafety(character.CurrentHull, character) > HumanAIController.HULL_SAFETY_THRESHOLD; } return false; diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescueAll.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescueAll.cs index e7fc7efcc..ec4aec869 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescueAll.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescueAll.cs @@ -5,13 +5,16 @@ namespace Barotrauma { class AIObjectiveRescueAll : AIObjective { + public override string DebugTag => "rescue all"; + + public override bool KeepDivingGearOn => true; + //only treat characters whose vitality is below this (0.8 = 80% of max vitality) public const float VitalityThreshold = 0.8f; private List rescueTargets; - public AIObjectiveRescueAll(Character character) - : base (character, "") + public AIObjectiveRescueAll(Character character) : base (character, "") { rescueTargets = new List(); } @@ -23,10 +26,11 @@ namespace Barotrauma public override float GetPriority(AIObjectiveManager objectiveManager) { + if (character.Submarine == null) { return 0; } GetRescueTargets(); if (!rescueTargets.Any()) { return 0.0f; } - if (objectiveManager.CurrentObjective == this) + if (objectiveManager.CurrentOrder == this) { return AIObjectiveManager.OrderPriority; } @@ -40,6 +44,7 @@ namespace Barotrauma { rescueTargets = Character.CharacterList.FindAll(c => c.AIController is HumanAIController && + c.TeamID == character.TeamID && c != character && !c.IsDead && c.Vitality / c.MaxVitality < VitalityThreshold); @@ -53,9 +58,7 @@ namespace Barotrauma } } - public override bool IsCompleted() - { - return false; - } + public override bool IsCompleted() => false; + public override bool CanBeCompleted => true; } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/PathFinder.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/PathFinder.cs index c66933f65..a6b21cd00 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/PathFinder.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/PathFinder.cs @@ -155,7 +155,7 @@ namespace Barotrauma } } - public SteeringPath FindPath(Vector2 start, Vector2 end, string errorMsgStr) + public SteeringPath FindPath(Vector2 start, Vector2 end, string errorMsgStr = null) { float closestDist = 0.0f; PathNode startNode = null; diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/SteeringManager.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/SteeringManager.cs index c274cc564..4c7ed024f 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/SteeringManager.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/SteeringManager.cs @@ -33,19 +33,19 @@ namespace Barotrauma wanderAngle = Rand.Range(0.0f, MathHelper.TwoPi); } - public void SteeringSeek(Vector2 targetSimPos, float speed) + public void SteeringSeek(Vector2 targetSimPos, float weight = 1) { - steering += DoSteeringSeek(targetSimPos, speed); + steering += DoSteeringSeek(targetSimPos, weight); } - public void SteeringWander(float speed) + public void SteeringWander(float weight = 1) { - steering += DoSteeringWander(speed); + steering += DoSteeringWander(weight); } - public void SteeringAvoid(float deltaTime, float lookAheadDistance, float speed) + public void SteeringAvoid(float deltaTime, float lookAheadDistance, float weight = 1) { - steering += DoSteeringAvoid(deltaTime, lookAheadDistance, speed); + steering += DoSteeringAvoid(deltaTime, lookAheadDistance, weight); } public void SteeringManual(float deltaTime, Vector2 velocity) @@ -76,39 +76,36 @@ namespace Barotrauma host.Steering = Vector2.Zero; return; } - - float steeringSpeed = steering.Length(); - if (steeringSpeed > speed) + if (steering.LengthSquared() > speed * speed) { steering = Vector2.Normalize(steering) * Math.Abs(speed); } - host.Steering = steering; } - protected virtual Vector2 DoSteeringSeek(Vector2 target, float speed) + protected virtual Vector2 DoSteeringSeek(Vector2 target, float weight) { Vector2 targetVel = target - host.SimPosition; if (targetVel.LengthSquared() < 0.00001f) return Vector2.Zero; - targetVel = Vector2.Normalize(targetVel) * speed; + targetVel = Vector2.Normalize(targetVel) * weight; Vector2 newSteering = targetVel - host.Steering; if (newSteering == Vector2.Zero) return Vector2.Zero; float steeringSpeed = (newSteering + host.Steering).Length(); - if (steeringSpeed > Math.Abs(speed)) + if (steeringSpeed > Math.Abs(weight)) { - newSteering = Vector2.Normalize(newSteering) * Math.Abs(speed); + newSteering = Vector2.Normalize(newSteering) * Math.Abs(weight); } return newSteering; } - protected virtual Vector2 DoSteeringWander(float speed) + protected virtual Vector2 DoSteeringWander(float weight) { - Vector2 circleCenter = (host.Steering == Vector2.Zero) ? Rand.Vector(speed) : host.Steering; + Vector2 circleCenter = (host.Steering == Vector2.Zero) ? Rand.Vector(weight) : host.Steering; circleCenter = Vector2.Normalize(circleCenter) * CircleDistance; Vector2 displacement = new Vector2( @@ -122,15 +119,15 @@ namespace Barotrauma Vector2 newSteering = circleCenter + displacement; float steeringSpeed = (newSteering + host.Steering).Length(); - if (steeringSpeed > speed) + if (steeringSpeed > weight) { - newSteering = Vector2.Normalize(newSteering) * speed; + newSteering = Vector2.Normalize(newSteering) * weight; } return newSteering; } - protected virtual Vector2 DoSteeringAvoid(float deltaTime, float lookAheadDistance, float speed) + protected virtual Vector2 DoSteeringAvoid(float deltaTime, float lookAheadDistance, float weight) { if (steering == Vector2.Zero || host.Steering == Vector2.Zero) return Vector2.Zero; @@ -187,7 +184,7 @@ namespace Barotrauma if (dist > maxDistance) return Vector2.Zero; - return -diff * (1.0f - dist / maxDistance) * speed; + return -diff * (1.0f - dist / maxDistance) * weight; } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs b/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs index 3d61a8240..57e1501f6 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Animation/HumanoidAnimController.cs @@ -130,6 +130,9 @@ namespace Barotrauma public bool Crouching; private float upperArmLength = 0.0f, forearmLength = 0.0f; + + public float ArmLength => upperArmLength + forearmLength; + public Vector2 RightHandIKPos { get; @@ -1623,7 +1626,7 @@ namespace Barotrauma Holdable holdable = item.GetComponent(); - if (Anim != Animation.Climbing && !usingController && character.Stun <= 0.0f && aim && itemPos != Vector2.Zero) + if (!character.IsClimbing && !usingController && character.Stun <= 0.0f && aim && itemPos != Vector2.Zero) { Vector2 mousePos = ConvertUnits.ToSimUnits(character.SmoothedCursorPosition); @@ -1653,7 +1656,7 @@ namespace Barotrauma } Vector2 transformedHoldPos = shoulder.WorldAnchorA; - if (itemPos == Vector2.Zero || Anim == Animation.Climbing || usingController) + if (itemPos == Vector2.Zero || character.IsClimbing || usingController) { if (character.SelectedItems[0] == item) { @@ -1742,7 +1745,7 @@ namespace Barotrauma item.SetTransform(currItemPos, itemAngle + itemAngleRelativeToHoldAngle * Dir); - if (Anim == Animation.Climbing) return; + if (character.IsClimbing) return; for (int i = 0; i < 2; i++) { diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs index de7b09800..31d6c2941 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs @@ -988,6 +988,8 @@ namespace Barotrauma public Vector2? OverrideMovement { get; set; } public bool ForceRun { get; set; } + public bool IsClimbing => AnimController.Anim == AnimController.Animation.Climbing; + public Vector2 GetTargetMovement() { Vector2 targetMovement = Vector2.Zero; @@ -1023,8 +1025,9 @@ namespace Barotrauma !AnimController.IsMovingBackwards; } - targetMovement *= AnimController.GetCurrentSpeed(run); - float maxSpeed = GetCurrentMaxSpeed(run); + float currentSpeed = AnimController.GetCurrentSpeed(run); + targetMovement *= currentSpeed; + float maxSpeed = ApplyTemporarySpeedLimits(currentSpeed); targetMovement.X = MathHelper.Clamp(targetMovement.X, -maxSpeed, maxSpeed); targetMovement.Y = MathHelper.Clamp(targetMovement.Y, -maxSpeed, maxSpeed); @@ -1084,31 +1087,23 @@ namespace Barotrauma speedMultipliers.Clear(); } - /// - /// Applies temporary limits to the speed (damage). - /// - public float GetCurrentMaxSpeed(bool run) + public float ApplyTemporarySpeedLimits(float speed) { - float currMaxSpeed = AnimController.GetCurrentSpeed(run); - - //? - //currMaxSpeed *= 1.5f; - var leftFoot = AnimController.GetLimb(LimbType.LeftFoot); if (leftFoot != null) { float footAfflictionStrength = CharacterHealth.GetAfflictionStrength("damage", leftFoot, true); - currMaxSpeed *= MathHelper.Lerp(1.0f, 0.25f, MathHelper.Clamp(footAfflictionStrength / 100.0f, 0.0f, 1.0f)); + speed *= MathHelper.Lerp(1.0f, 0.25f, MathHelper.Clamp(footAfflictionStrength / 100.0f, 0.0f, 1.0f)); } var rightFoot = AnimController.GetLimb(LimbType.RightFoot); if (rightFoot != null) { float footAfflictionStrength = CharacterHealth.GetAfflictionStrength("damage", rightFoot, true); - currMaxSpeed *= MathHelper.Lerp(1.0f, 0.25f, MathHelper.Clamp(footAfflictionStrength / 100.0f, 0.0f, 1.0f)); + speed *= MathHelper.Lerp(1.0f, 0.25f, MathHelper.Clamp(footAfflictionStrength / 100.0f, 0.0f, 1.0f)); } - return currMaxSpeed; + return speed; } public void Control(float deltaTime, Camera cam) @@ -2055,7 +2050,7 @@ namespace Barotrauma prevAiChatMessages.Add(dummyMsg); } - public void Speak(string message, ChatMessageType? messageType, float delay = 0.0f, string identifier = "", float minDurationBetweenSimilar = 0.0f) + public void Speak(string message, ChatMessageType? messageType = null, float delay = 0.0f, string identifier = "", float minDurationBetweenSimilar = 0.0f) { if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) return; diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaShared/Source/Characters/Health/CharacterHealth.cs index dad69cb1f..17a67ca3f 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Health/CharacterHealth.cs @@ -76,8 +76,8 @@ namespace Barotrauma } } - const float InsufficientOxygenThreshold = 30.0f; - const float LowOxygenThreshold = 50.0f; + public const float InsufficientOxygenThreshold = 30.0f; + public const float LowOxygenThreshold = 50.0f; protected float minVitality, maxVitality; public bool Unkillable; diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Limb.cs b/Barotrauma/BarotraumaShared/Source/Characters/Limb.cs index c8a1738b9..bb595803c 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Limb.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Limb.cs @@ -441,28 +441,18 @@ namespace Barotrauma public bool SectorHit(Vector2 armorSector, Vector2 simPosition) { if (armorSector == Vector2.Zero) { return false; } - - // Can't get this to work properly. - //float rot = body.Rotation; - //if (Dir == -1) { rot -= MathHelper.Pi; } - //Vector2 armorLimits = new Vector2(rot - armorSector.X * Dir, rot - armorSector.Y * Dir); - //float mid = (armorLimits.X + armorLimits.Y) / 2; - //float angleDiff = MathUtils.GetShortestAngle(MathUtils.VectorToAngle(simPosition - SimPosition), mid); - //return (Math.Abs(angleDiff) < (armorSector.Y - armorSector.X) / 2); - - // Alternative implementation - float offset = GetArmorSectorRotationOffset(armorSector, body.Rotation); - Vector2 forward = Vector2.Transform(-Vector2.UnitY, Matrix.CreateRotationZ(offset)); - float hitAngle = VectorExtensions.Angle(forward, simPosition - SimPosition); - float sectorSize = MathHelper.ToDegrees(GetArmorSectorSize(armorSector)); + float rotation = body.TransformedRotation; + float offset = (MathHelper.PiOver2 - GetArmorSectorRotationOffset(armorSector)) * Dir; + float hitAngle = VectorExtensions.Angle(VectorExtensions.Forward(rotation + offset), SimPosition - simPosition); + float sectorSize = GetArmorSectorSize(armorSector); return hitAngle < sectorSize / 2; } - protected float GetArmorSectorRotationOffset(Vector2 armorSector, float bodyRotation) + protected float GetArmorSectorRotationOffset(Vector2 armorSector) { float midAngle = MathUtils.GetMidAngle(armorSector.X, armorSector.Y); float spritesheetOrientation = MathHelper.ToRadians(limbParams.Ragdoll.SpritesheetOrientation); - return bodyRotation + (midAngle + spritesheetOrientation) * Dir; + return midAngle + spritesheetOrientation; } protected float GetArmorSectorSize(Vector2 armorSector) diff --git a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs index ef55fe457..64803cc93 100644 --- a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs @@ -216,10 +216,19 @@ namespace Barotrauma commands.Add(new Command("spawnitem", "spawnitem [itemname] [cursor/inventory/cargo/random/[name]]: Spawn an item at the position of the cursor, in the inventory of the controlled character, in the inventory of the client with the given name, or at a random spawnpoint if the last parameter is omitted or \"random\".", (string[] args) => { - SpawnItem(args, GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition), Character.Controlled, out string errorMsg); - if (!string.IsNullOrWhiteSpace(errorMsg)) + try { - ThrowError(errorMsg); + SpawnItem(args, GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition), Character.Controlled, out string errorMsg); + if (!string.IsNullOrWhiteSpace(errorMsg)) + { + ThrowError(errorMsg); + } + } + catch (Exception e) + { + string errorMsg = "Failed to spawn an item. Arguments: \"" + string.Join(" ", args) + "\"."; + ThrowError(errorMsg, e); + GameAnalyticsManager.AddErrorEventOnce("DebugConsole.SpawnItem:Error", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); } }, () => diff --git a/Barotrauma/BarotraumaShared/Source/Extensions/VectorExtensions.cs b/Barotrauma/BarotraumaShared/Source/Extensions/VectorExtensions.cs index 3e5068084..3ea2039a7 100644 --- a/Barotrauma/BarotraumaShared/Source/Extensions/VectorExtensions.cs +++ b/Barotrauma/BarotraumaShared/Source/Extensions/VectorExtensions.cs @@ -6,13 +6,13 @@ namespace Barotrauma.Extensions public static class VectorExtensions { /// - /// Unity's Angle implementation. - /// Returns the angle in degrees. - /// 0 - 180. + /// Unity's Angle implementation without the conversion to degrees. + /// Returns the angle in radians between two vectors. + /// 0 - Pi. /// public static float Angle(this Vector2 from, Vector2 to) { - return (float)Math.Acos(MathHelper.Clamp(Vector2.Dot(Vector2.Normalize(from), Vector2.Normalize(to)), -1f, 1f)) * 57.29578f; + return (float)Math.Acos(MathHelper.Clamp(Vector2.Dot(Vector2.Normalize(from), Vector2.Normalize(to)), -1f, 1f)); } /// @@ -20,7 +20,7 @@ namespace Barotrauma.Extensions /// public static Vector2 Forward(float radians, float length = 1) { - return new Vector2((float)Math.Sin(radians), (float)Math.Cos(radians)) * length; + return new Vector2((float)Math.Cos(radians), (float)Math.Sin(radians)) * length; } /// @@ -31,6 +31,22 @@ namespace Barotrauma.Extensions return -Forward(radians, length); } + /// + /// Creates a forward pointing vector based on the rotation (in radians). TODO: remove when the implications have been neutralized + /// + public static Vector2 ForwardFlipped(float radians, float length = 1) + { + return new Vector2((float)Math.Sin(radians), (float)Math.Cos(radians)) * length; + } + + /// + /// Creates a backward pointing vector based on the rotation (in radians). TODO: remove when the implications have been neutralized + /// + public static Vector2 BackwardFlipped(float radians, float length = 1) + { + return -ForwardFlipped(radians, length); + } + /// /// Creates a normalized perpendicular vector to the right from a forward vector. /// diff --git a/Barotrauma/BarotraumaShared/Source/GameSettings.cs b/Barotrauma/BarotraumaShared/Source/GameSettings.cs index 586ed84fa..49e1ca3b0 100644 --- a/Barotrauma/BarotraumaShared/Source/GameSettings.cs +++ b/Barotrauma/BarotraumaShared/Source/GameSettings.cs @@ -71,6 +71,14 @@ namespace Barotrauma public bool MuteOnFocusLost { get; set; } + public int ParticleLimit { get; set; } + + public float LightMapScale { get; set; } + public bool SpecularityEnabled { get; set; } + public bool ChromaticAberrationEnabled { get; set; } + + public bool MuteOnFocusLost { get; set; } + public enum VoiceMode { Disabled, @@ -397,6 +405,8 @@ namespace Barotrauma AimAssistAmount = doc.Root.GetAttributeFloat("aimassistamount", 0.5f); + AimAssistAmount = doc.Root.GetAttributeFloat("aimassistamount", 0.5f); + AimAssistAmount = doc.Root.GetAttributeFloat("aimassistamount", 0.5f); keyMapping = new KeyOrMouse[Enum.GetNames(typeof(InputType)).Length]; @@ -640,6 +650,34 @@ namespace Barotrauma TextManager.LoadTextPacks(SelectedContentPackages); + //display error messages after all content packages have been loaded + //to make sure the package that contains text files has been loaded before we attempt to use TextManager + foreach (string missingPackagePath in missingPackagePaths) + { + DebugConsole.ThrowError(TextManager.Get("ContentPackageNotFound").Replace("[packagepath]", missingPackagePath)); + } + foreach (ContentPackage incompatiblePackage in incompatiblePackages) + { + DebugConsole.ThrowError(TextManager.Get(incompatiblePackage.GameVersion <= new Version(0, 0, 0, 0) ? "IncompatibleContentPackageUnknownVersion" : "IncompatibleContentPackage") + .Replace("[packagename]", incompatiblePackage.Name) + .Replace("[packageversion]", incompatiblePackage.GameVersion.ToString()) + .Replace("[gameversion]", GameMain.Version.ToString())); + } + foreach (ContentPackage contentPackage in SelectedContentPackages) + { + foreach (ContentFile file in contentPackage.Files) + { + if (!System.IO.File.Exists(file.Path)) + { + DebugConsole.ThrowError("Error in content package \"" + contentPackage.Name + "\" - file \"" + file.Path + "\" not found."); + continue; + } + ToolBox.IsProperFilenameCase(file.Path); + } + } + + TextManager.LoadTextPacks(SelectedContentPackages); + //display error messages after all content packages have been loaded //to make sure the package that contains text files has been loaded before we attempt to use TextManager foreach (string missingPackagePath in missingPackagePaths) @@ -1046,369 +1084,6 @@ namespace Barotrauma } #endregion - #region Save PlayerConfig - public void SaveNewPlayerConfig() - { - XDocument doc = new XDocument(); - UnsavedSettings = false; - - if (doc.Root == null) - { - doc.Add(new XElement("config")); - } - - doc.Root.Add( - new XAttribute("language", TextManager.Language), - new XAttribute("masterserverurl", MasterServerUrl), - new XAttribute("autocheckupdates", AutoCheckUpdates), - new XAttribute("musicvolume", musicVolume), - new XAttribute("soundvolume", soundVolume), - new XAttribute("verboselogging", VerboseLogging), - new XAttribute("savedebugconsolelogs", SaveDebugConsoleLogs), - new XAttribute("enablesplashscreen", EnableSplashScreen), - new XAttribute("usesteammatchmaking", useSteamMatchmaking), - new XAttribute("quickstartsub", QuickStartSubmarineName), - new XAttribute("requiresteamauthentication", requireSteamAuthentication), - new XAttribute("autoupdateworkshopitems", AutoUpdateWorkshopItems), - new XAttribute("aimassistamount", aimAssistAmount)); - - if (!ShowUserStatisticsPrompt) - { - doc.Root.Add(new XAttribute("senduserstatistics", sendUserStatistics)); - } - - XElement gMode = doc.Root.Element("graphicsmode"); - if (gMode == null) - { - gMode = new XElement("graphicsmode"); - doc.Root.Add(gMode); - } - if (GraphicsWidth == 0 || GraphicsHeight == 0) - { - gMode.ReplaceAttributes(new XAttribute("displaymode", windowMode)); - } - else - { - gMode.ReplaceAttributes( - new XAttribute("width", GraphicsWidth), - new XAttribute("height", GraphicsHeight), - new XAttribute("vsync", VSyncEnabled), - new XAttribute("displaymode", windowMode)); - } - - XElement gSettings = doc.Root.Element("graphicssettings"); - if (gSettings == null) - { - gSettings = new XElement("graphicssettings"); - doc.Root.Add(gSettings); - } - - gSettings.ReplaceAttributes( - new XAttribute("particlelimit", ParticleLimit), - new XAttribute("lightmapscale", LightMapScale), - new XAttribute("specularity", SpecularityEnabled), - new XAttribute("chromaticaberration", ChromaticAberrationEnabled), - new XAttribute("losmode", LosMode), - new XAttribute("hudscale", HUDScale), - new XAttribute("inventoryscale", InventoryScale)); - - foreach (ContentPackage contentPackage in SelectedContentPackages) - { - if (contentPackage.Path.Contains(vanillaContentPackagePath)) - { - doc.Root.Add(new XElement("contentpackage", new XAttribute("path", contentPackage.Path))); - break; - } - } - - var keyMappingElement = new XElement("keymapping"); - doc.Root.Add(keyMappingElement); - for (int i = 0; i < keyMapping.Length; i++) - { - if (keyMapping[i].MouseButton == null) - { - keyMappingElement.Add(new XAttribute(((InputType)i).ToString(), keyMapping[i].Key)); - } - else - { - keyMappingElement.Add(new XAttribute(((InputType)i).ToString(), keyMapping[i].MouseButton)); - } - } - - var gameplay = new XElement("gameplay"); - var jobPreferences = new XElement("jobpreferences"); - foreach (string jobName in JobPreferences) - { - jobPreferences.Add(new XElement("job", new XAttribute("identifier", jobName))); - } - gameplay.Add(jobPreferences); - doc.Root.Add(gameplay); - - var playerElement = new XElement("player", - new XAttribute("name", defaultPlayerName ?? ""), - new XAttribute("headindex", CharacterHeadIndex), - new XAttribute("gender", CharacterGender), - new XAttribute("race", CharacterRace), - new XAttribute("hairindex", CharacterHairIndex), - new XAttribute("beardindex", CharacterBeardIndex), - new XAttribute("moustacheindex", CharacterMoustacheIndex), - new XAttribute("faceattachmentindex", CharacterFaceAttachmentIndex)); - doc.Root.Add(playerElement); - - XmlWriterSettings settings = new XmlWriterSettings - { - Indent = true, - OmitXmlDeclaration = true, - NewLineOnAttributes = true - }; - - try - { - using (var writer = XmlWriter.Create(savePath, settings)) - { - doc.WriteTo(writer); - writer.Flush(); - } - } - catch (Exception e) - { - DebugConsole.ThrowError("Saving game settings failed.", e); - GameAnalyticsManager.AddErrorEventOnce("GameSettings.Save:SaveFailed", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, - "Saving game settings failed.\n" + e.Message + "\n" + e.StackTrace); - } - } - #endregion - - #region Load PlayerConfig - // TODO: DRY - public void LoadPlayerConfig() - { - XDocument doc = XMLExtensions.LoadXml(playerSavePath); - - if (doc == null || doc.Root == null) - { - ShowUserStatisticsPrompt = true; - SaveNewPlayerConfig(); - return; - } - - Language = doc.Root.GetAttributeString("language", Language); - AutoCheckUpdates = doc.Root.GetAttributeBool("autocheckupdates", AutoCheckUpdates); - sendUserStatistics = doc.Root.GetAttributeBool("senduserstatistics", true); - - XElement graphicsMode = doc.Root.Element("graphicsmode"); - GraphicsWidth = graphicsMode.GetAttributeInt("width", GraphicsWidth); - GraphicsHeight = graphicsMode.GetAttributeInt("height", GraphicsHeight); - VSyncEnabled = graphicsMode.GetAttributeBool("vsync", VSyncEnabled); - - XElement graphicsSettings = doc.Root.Element("graphicssettings"); - ParticleLimit = graphicsSettings.GetAttributeInt("particlelimit", ParticleLimit); - LightMapScale = MathHelper.Clamp(graphicsSettings.GetAttributeFloat("lightmapscale", LightMapScale), 0.1f, 1.0f); - SpecularityEnabled = graphicsSettings.GetAttributeBool("specularity", SpecularityEnabled); - ChromaticAberrationEnabled = graphicsSettings.GetAttributeBool("chromaticaberration", ChromaticAberrationEnabled); - HUDScale = graphicsSettings.GetAttributeFloat("hudscale", HUDScale); - InventoryScale = graphicsSettings.GetAttributeFloat("inventoryscale", InventoryScale); - var losModeStr = graphicsSettings.GetAttributeString("losmode", "Transparent"); - if (!Enum.TryParse(losModeStr, out losMode)) - { - losMode = LosMode.Transparent; - } - -#if CLIENT - if (GraphicsWidth == 0 || GraphicsHeight == 0) - { - GraphicsWidth = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width; - GraphicsHeight = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height; - } -#endif - - var windowModeStr = graphicsMode.GetAttributeString("displaymode", "Fullscreen"); - if (!Enum.TryParse(windowModeStr, out windowMode)) - { - windowMode = WindowMode.Fullscreen; - } - - XElement audioSettings = doc.Root.Element("audio"); - if (audioSettings != null) - { - SoundVolume = audioSettings.GetAttributeFloat("soundvolume", SoundVolume); - MusicVolume = audioSettings.GetAttributeFloat("musicvolume", MusicVolume); - VoiceChatVolume = audioSettings.GetAttributeFloat("voicechatvolume", VoiceChatVolume); - string voiceSettingStr = audioSettings.GetAttributeString("voicesetting", "Disabled"); - VoiceCaptureDevice = audioSettings.GetAttributeString("voicecapturedevice", ""); - NoiseGateThreshold = audioSettings.GetAttributeFloat("noisegatethreshold", -45); - var voiceSetting = VoiceMode.Disabled; - if (Enum.TryParse(voiceSettingStr, out voiceSetting)) - { - VoiceSetting = voiceSetting; - } - } - - useSteamMatchmaking = doc.Root.GetAttributeBool("usesteammatchmaking", useSteamMatchmaking); - requireSteamAuthentication = doc.Root.GetAttributeBool("requiresteamauthentication", requireSteamAuthentication); - - EnableSplashScreen = doc.Root.GetAttributeBool("enablesplashscreen", EnableSplashScreen); - - AimAssistAmount = doc.Root.GetAttributeFloat("aimassistamount", AimAssistAmount); - - foreach (XElement subElement in doc.Root.Elements()) - { - switch (subElement.Name.ToString().ToLowerInvariant()) - { - case "keymapping": - foreach (XAttribute attribute in subElement.Attributes()) - { - if (Enum.TryParse(attribute.Name.ToString(), true, out InputType inputType)) - { - if (int.TryParse(attribute.Value.ToString(), out int mouseButton)) - { - keyMapping[(int)inputType] = new KeyOrMouse(mouseButton); - } - else - { - if (Enum.TryParse(attribute.Value.ToString(), true, out Keys key)) - { - keyMapping[(int)inputType] = new KeyOrMouse(key); - } - } - } - } - break; - case "gameplay": - jobPreferences = new List(); - foreach (XElement ele in subElement.Element("jobpreferences").Elements("job")) - { - string jobIdentifier = ele.GetAttributeString("identifier", ""); - if (string.IsNullOrEmpty(jobIdentifier)) continue; - jobPreferences.Add(jobIdentifier); - } - break; - case "player": - defaultPlayerName = subElement.GetAttributeString("name", defaultPlayerName); - CharacterHeadIndex = subElement.GetAttributeInt("headindex", CharacterHeadIndex); - if (Enum.TryParse(subElement.GetAttributeString("gender", "none"), true, out Gender g)) - { - CharacterGender = g; - } - if (Enum.TryParse(subElement.GetAttributeString("race", "white"), true, out Race r)) - { - CharacterRace = r; - } - else - { - CharacterRace = Race.White; - } - CharacterHairIndex = subElement.GetAttributeInt("hairindex", CharacterHairIndex); - CharacterBeardIndex = subElement.GetAttributeInt("beardindex", CharacterBeardIndex); - CharacterMoustacheIndex = subElement.GetAttributeInt("moustacheindex", CharacterMoustacheIndex); - CharacterFaceAttachmentIndex = subElement.GetAttributeInt("faceattachmentindex", CharacterFaceAttachmentIndex); - break; - case "tutorials": - foreach (XElement tutorialElement in subElement.Elements()) - { - CompletedTutorialNames.Add(tutorialElement.GetAttributeString("name", "")); - } - break; - } - } - - foreach (InputType inputType in Enum.GetValues(typeof(InputType))) - { - if (keyMapping[(int)inputType] == null) - { - DebugConsole.ThrowError("Key binding for the input type \"" + inputType + " not set!"); - keyMapping[(int)inputType] = new KeyOrMouse(Keys.D1); - } - } - - UnsavedSettings = false; - - selectedContentPackagePaths = new HashSet(); - - foreach (XElement subElement in doc.Root.Elements()) - { - switch (subElement.Name.ToString().ToLowerInvariant()) - { - case "contentpackage": - string path = System.IO.Path.GetFullPath(subElement.GetAttributeString("path", "")); - selectedContentPackagePaths.Add(path); - break; - } - } - - LoadContentPackages(selectedContentPackagePaths); - } - - public void ReloadContentPackages() - { - LoadContentPackages(selectedContentPackagePaths); - } - - private void LoadContentPackages(IEnumerable contentPackagePaths) - { - var missingPackagePaths = new List(); - var incompatiblePackages = new List(); - SelectedContentPackages.Clear(); - foreach (string path in contentPackagePaths) - { - var matchingContentPackage = ContentPackage.List.Find(cp => System.IO.Path.GetFullPath(cp.Path) == path); - - if (matchingContentPackage == null) - { - missingPackagePaths.Add(path); - } - else if (!matchingContentPackage.IsCompatible()) - { - incompatiblePackages.Add(matchingContentPackage); - } - else - { - SelectedContentPackages.Add(matchingContentPackage); - } - } - - TextManager.LoadTextPacks(SelectedContentPackages); - - foreach (ContentPackage contentPackage in SelectedContentPackages) - { - foreach (ContentFile file in contentPackage.Files) - { - if (!System.IO.File.Exists(file.Path)) - { - DebugConsole.ThrowError("Error in content package \"" + contentPackage.Name + "\" - file \"" + file.Path + "\" not found."); - continue; - } - ToolBox.IsProperFilenameCase(file.Path); - } - } - if (!SelectedContentPackages.Any()) - { - var availablePackage = ContentPackage.List.FirstOrDefault(cp => cp.IsCompatible() && cp.CorePackage); - if (availablePackage != null) - { - SelectedContentPackages.Add(availablePackage); - } - } - - //save to get rid of the invalid selected packages in the config file - if (missingPackagePaths.Count > 0 || incompatiblePackages.Count > 0) { SaveNewPlayerConfig(); } - - //display error messages after all content packages have been loaded - //to make sure the package that contains text files has been loaded before we attempt to use TextManager - foreach (string missingPackagePath in missingPackagePaths) - { - DebugConsole.ThrowError(TextManager.Get("ContentPackageNotFound").Replace("[packagepath]", missingPackagePath)); - } - foreach (ContentPackage incompatiblePackage in incompatiblePackages) - { - DebugConsole.ThrowError(TextManager.Get(incompatiblePackage.GameVersion <= new Version(0, 0, 0, 0) ? "IncompatibleContentPackageUnknownVersion" : "IncompatibleContentPackage") - .Replace("[packagename]", incompatiblePackage.Name) - .Replace("[packageversion]", incompatiblePackage.GameVersion.ToString()) - .Replace("[gameversion]", GameMain.Version.ToString())); - } - } - #endregion - #region Save PlayerConfig public void SaveNewPlayerConfig() { diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs index 7a79ef6b7..1fe61d323 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs @@ -240,7 +240,7 @@ namespace Barotrauma.Items.Components if (isBroken) { //the door has to be restored to 50% health before collision detection on the body is re-enabled - if (item.Condition > item.Prefab.Health / 2.0f) + if (item.ConditionPercentage > 50.0f) { IsBroken = false; } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RepairTool.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RepairTool.cs index 62bffa330..400e69223 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RepairTool.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RepairTool.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Xml.Linq; +using Barotrauma.Extensions; #if CLIENT using Barotrauma.Particles; #endif @@ -260,15 +261,16 @@ namespace Barotrauma.Items.Components { Gap leak = objective.OperateTarget as Gap; if (leak == null) return true; - - float dist = Vector2.Distance(leak.WorldPosition, item.WorldPosition); + + Vector2 fromItemToLeak = leak.WorldPosition - item.WorldPosition; + float dist = fromItemToLeak.Length(); //too far away -> consider this done and hope the AI is smart enough to move closer if (dist > Range * 5.0f) return true; - Vector2 gapDiff = leak.WorldPosition - item.WorldPosition; + // TODO: use the collider size? if (!character.AnimController.InWater && character.AnimController is HumanoidAnimController && - Math.Abs(gapDiff.X) < 100.0f && gapDiff.Y < 0.0f && gapDiff.Y > -150.0f) + Math.Abs(fromItemToLeak.X) < 100.0f && fromItemToLeak.Y < 0.0f && fromItemToLeak.Y > -150.0f) { ((HumanoidAnimController)character.AnimController).Crouching = true; } @@ -276,16 +278,15 @@ namespace Barotrauma.Items.Components //steer closer if almost in range if (dist > Range) { - Vector2 standPos = leak.IsHorizontal ? - new Vector2(Math.Sign(-gapDiff.X), 0.0f) - : new Vector2(0.0f, Math.Sign(-gapDiff.Y) * 0.5f); - + Vector2 standPos = leak.IsHorizontal ? new Vector2(Math.Sign(-fromItemToLeak.X), 0.0f) : new Vector2(0.0f, Math.Sign(-fromItemToLeak.Y) * 0.5f); standPos = leak.WorldPosition + standPos * Range; - - character.AIController.SteeringManager.SteeringManual(deltaTime, (standPos - character.WorldPosition) / 1000.0f); + // TODO: check if too close to the stand pos -> move away so that the tool can hit the target and not through it? + Vector2 dir = Vector2.Normalize(standPos - character.WorldPosition); + character.AIController.SteeringManager.SteeringManual(deltaTime, dir / 2); } else { + // TODO: sometimes stuck here, if too close to the target //close enough -> stop moving character.AIController.SteeringManager.Reset(); } @@ -293,8 +294,13 @@ namespace Barotrauma.Items.Components character.CursorPosition = leak.Position; character.SetInput(InputType.Aim, false, true); - Use(deltaTime, character); + if (VectorExtensions.Angle(VectorExtensions.Forward(item.body.TransformedRotation), fromItemToLeak) < MathHelper.PiOver4) + { + // Press the trigger only when the tool is approximately facing the target. + Use(deltaTime, character); + } + // TODO: fix until the wall is fixed? bool leakFixed = leak.Open <= 0.0f || leak.Removed; if (leakFixed && leak.FlowTargetHull != null) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/ItemComponent.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/ItemComponent.cs index ea74d350a..24018f2f9 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/ItemComponent.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/ItemComponent.cs @@ -399,9 +399,9 @@ namespace Barotrauma.Items.Components { float transferAmount = 0.0f; if (this.Item.Condition <= item.Condition) - transferAmount = Math.Min(item.Condition, this.item.Prefab.Health - this.item.Condition); + transferAmount = Math.Min(item.Condition, this.item.MaxCondition - this.item.Condition); else - transferAmount = -Math.Min(this.item.Condition, item.Prefab.Health - item.Condition); + transferAmount = -Math.Min(this.item.Condition, item.MaxCondition - item.Condition); if (transferAmount == 0.0f) return false; diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Engine.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Engine.cs index 3ff834832..ae727e482 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Engine.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Engine.cs @@ -80,7 +80,7 @@ namespace Barotrauma.Items.Components currPowerConsumption = Math.Abs(targetForce) / 100.0f * powerConsumption; //pumps consume more power when in a bad condition - currPowerConsumption *= MathHelper.Lerp(2.0f, 1.0f, item.Condition / item.Prefab.Health); + currPowerConsumption *= MathHelper.Lerp(2.0f, 1.0f, item.Condition / item.MaxCondition); if (powerConsumption == 0.0f) voltage = 1.0f; @@ -92,7 +92,7 @@ namespace Barotrauma.Items.Components { Vector2 currForce = new Vector2((force / 100.0f) * maxForce * Math.Min(voltage / minVoltage, 1.0f), 0.0f); //less effective when in a bad condition - currForce *= MathHelper.Lerp(0.5f, 2.0f, item.Condition / item.Prefab.Health); + currForce *= MathHelper.Lerp(0.5f, 2.0f, item.Condition / item.MaxCondition); item.Submarine.ApplyForce(currForce); diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Fabricator.cs index 851a4a03c..49e2b38a0 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Fabricator.cs @@ -237,7 +237,7 @@ namespace Barotrauma.Items.Components outputContainer.Inventory.Locked = true; currPowerConsumption = powerConsumption; - currPowerConsumption *= MathHelper.Lerp(2.0f, 1.0f, item.Condition / item.Prefab.Health); + currPowerConsumption *= MathHelper.Lerp(2.0f, 1.0f, item.Condition / item.MaxCondition); } private void CancelFabricating(Character user = null) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/MiniMap.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/MiniMap.cs index 541cc917f..53a54e414 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/MiniMap.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/MiniMap.cs @@ -73,7 +73,7 @@ namespace Barotrauma.Items.Components } currPowerConsumption = powerConsumption; - currPowerConsumption *= MathHelper.Lerp(2.0f, 1.0f, item.Condition / item.Prefab.Health); + currPowerConsumption *= MathHelper.Lerp(2.0f, 1.0f, item.Condition / item.MaxCondition); hasPower = voltage > minVoltage; if (hasPower) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/OxygenGenerator.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/OxygenGenerator.cs index bab32f5ba..ae2249034 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/OxygenGenerator.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/OxygenGenerator.cs @@ -42,7 +42,7 @@ namespace Barotrauma.Items.Components CurrFlow = 0.0f; currPowerConsumption = powerConsumption; //consume more power when in a bad condition - currPowerConsumption *= MathHelper.Lerp(2.0f, 1.0f, item.Condition / item.Prefab.Health); + currPowerConsumption *= MathHelper.Lerp(2.0f, 1.0f, item.Condition / item.MaxCondition); if (powerConsumption <= 0.0f) { @@ -63,7 +63,7 @@ namespace Barotrauma.Items.Components CurrFlow = Math.Min(voltage, 1.0f) * generatedAmount * 100.0f; //less effective when in bad condition - CurrFlow *= MathHelper.Lerp(0.5f, 1.0f, item.Condition / item.Prefab.Health); + CurrFlow *= MathHelper.Lerp(0.5f, 1.0f, item.Condition / item.MaxCondition); UpdateVents(CurrFlow); diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Pump.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Pump.cs index fdb692453..9eec99bba 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Pump.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Pump.cs @@ -66,7 +66,7 @@ namespace Barotrauma.Items.Components currPowerConsumption = powerConsumption * Math.Abs(flowPercentage / 100.0f); //pumps consume more power when in a bad condition - currPowerConsumption *= MathHelper.Lerp(2.0f, 1.0f, item.Condition / item.Prefab.Health); + currPowerConsumption *= MathHelper.Lerp(2.0f, 1.0f, item.Condition / item.MaxCondition); if (voltage < minVoltage) return; @@ -82,7 +82,7 @@ namespace Barotrauma.Items.Components currFlow = flowPercentage / 100.0f * maxFlow * powerFactor; //less effective when in a bad condition - currFlow *= MathHelper.Lerp(0.5f, 1.0f, item.Condition / item.Prefab.Health); + currFlow *= MathHelper.Lerp(0.5f, 1.0f, item.Condition / item.MaxCondition); item.CurrentHull.WaterVolume += currFlow; if (item.CurrentHull.WaterVolume > item.CurrentHull.Volume) { item.CurrentHull.Pressure += 0.5f; } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Reactor.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Reactor.cs index 958396eb9..741306c51 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Reactor.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Reactor.cs @@ -319,7 +319,7 @@ namespace Barotrauma.Items.Components { item.SendSignal(0, "1", "meltdown_warning", null); //faster meltdown if the item is in a bad condition - meltDownTimer += MathHelper.Lerp(deltaTime * 2.0f, deltaTime, item.Condition / item.Prefab.Health); + meltDownTimer += MathHelper.Lerp(deltaTime * 2.0f, deltaTime, item.Condition / item.MaxCondition); if (meltDownTimer > MeltdownDelay) { @@ -336,7 +336,7 @@ namespace Barotrauma.Items.Components if (temperature > optimalTemperature.Y) { float prevFireTimer = fireTimer; - fireTimer += MathHelper.Lerp(deltaTime * 2.0f, deltaTime, item.Condition / item.Prefab.Health); + fireTimer += MathHelper.Lerp(deltaTime * 2.0f, deltaTime, item.Condition / item.MaxCondition); if (fireTimer >= FireDelay && prevFireTimer < fireDelay) { diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerContainer.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerContainer.cs index 7d1175df3..b8c51184d 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerContainer.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerContainer.cs @@ -90,6 +90,8 @@ namespace Barotrauma.Items.Components } } } + + public float ChargePercentage => MathUtils.Percentage(Charge, Capacity); [Serialize(10.0f, true), Editable(ToolTip = "How fast the device can be recharged. "+ "For example, a recharge speed of 100 kW and a capacity of 1000 kW*min would mean it takes 10 minutes to fully charge the device.")] @@ -215,12 +217,6 @@ namespace Barotrauma.Items.Components power: gridLoad <= 0.0f ? 1.0f : CurrPowerOutput / gridLoad); } - foreach (Pair connected in directlyConnected) - { - connected.First.ReceiveSignal(0, "", connected.Second, source: item, sender: null, - power: gridLoad <= 0.0f ? 1.0f : CurrPowerOutput / gridLoad); - } - rechargeVoltage = 0.0f; outputVoltage = 0.0f; } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerTransfer.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerTransfer.cs index e2c055c74..5f0ff5fed 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerTransfer.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerTransfer.cs @@ -176,7 +176,7 @@ namespace Barotrauma.Items.Components #endif //items in a bad condition are more sensitive to overvoltage - float maxOverVoltage = MathHelper.Lerp(Math.Min(OverloadVoltage, 1.0f), OverloadVoltage, item.Condition / item.Prefab.Health); + float maxOverVoltage = MathHelper.Lerp(Math.Min(OverloadVoltage, 1.0f), OverloadVoltage, item.Condition / item.MaxCondition); //if the item can't be fixed, don't allow it to break if (!item.Repairables.Any() || !CanBeOverloaded) continue; diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Repairable.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Repairable.cs index 87fc129b7..3f33c4df2 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Repairable.cs @@ -69,7 +69,7 @@ namespace Barotrauma.Items.Components get { return currentFixer; } set { - if (currentFixer == value || item.Condition >= item.Prefab.Health) return; + if (currentFixer == value || item.IsFullCondition) return; if (currentFixer != null) currentFixer.AnimController.Anim = AnimController.Animation.None; currentFixer = value; } @@ -138,7 +138,7 @@ namespace Barotrauma.Items.Components return; } - if (CurrentFixer.SelectedConstruction != item || !currentFixer.CanInteractWith(item)) + if (Item.IsFullCondition || CurrentFixer.SelectedConstruction != item || !currentFixer.CanInteractWith(item)) { currentFixer.AnimController.Anim = AnimController.Animation.None; currentFixer = null; @@ -159,18 +159,18 @@ namespace Barotrauma.Items.Components CurrentFixer.WorldPosition + Vector2.UnitY * 100.0f); } - bool wasBroken = item.Condition < item.Prefab.Health; + bool wasBroken = !item.IsFullCondition; float fixDuration = MathHelper.Lerp(fixDurationLowSkill, fixDurationHighSkill, successFactor); if (fixDuration <= 0.0f) { - item.Condition = item.Prefab.Health; + item.Condition = item.MaxCondition; } else { - item.Condition += deltaTime / (fixDuration / item.Prefab.Health); + item.Condition += deltaTime / (fixDuration / item.MaxCondition); } - if (wasBroken && item.Condition >= item.Prefab.Health) + if (wasBroken && item.IsFullCondition) { SteamAchievementManager.OnItemRepaired(item, currentFixer); deteriorationTimer = Rand.Range(MinDeteriorationDelay, MaxDeteriorationDelay); @@ -184,7 +184,7 @@ namespace Barotrauma.Items.Components private void UpdateFixAnimation(Character character) { - character.AnimController.UpdateUseItem(false, item.WorldPosition + new Vector2(0.0f, 100.0f) * ((item.Condition / 100.0f) % 0.1f)); + character.AnimController.UpdateUseItem(false, item.WorldPosition + new Vector2(0.0f, 100.0f) * ((item.Condition / item.MaxCondition) % 0.1f)); } public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Item.cs b/Barotrauma/BarotraumaShared/Source/Items/Item.cs index d4ab496d2..2290db26a 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Item.cs @@ -60,8 +60,6 @@ namespace Barotrauma public readonly XElement StaticBodyConfig; - private Vector2 lastSentPos; - private bool needsPositionUpdate; private float lastSentCondition; @@ -202,18 +200,11 @@ namespace Barotrauma } } - public bool NeedsPositionUpdate + public float PositionUpdateInterval { - get - { - if (body == null || !body.Enabled) return false; - return needsPositionUpdate; - } - set - { - needsPositionUpdate = value; - } - } + get; + private set; + } = float.PositiveInfinity; protected Color spriteColor; [Editable, Serialize("1.0,1.0,1.0,1.0", true)] @@ -252,6 +243,10 @@ namespace Barotrauma get { return spriteColor; } } + public bool IsFullCondition => Condition >= MaxCondition; + public float MaxCondition => Prefab.Health; + public float ConditionPercentage => MathUtils.Percentage(Condition, MaxCondition); + //the default value should be Prefab.Health, but because we can't use it in the attribute, //we'll just use NaN (which does nothing) and set the default value in the constructor/load [Serialize(float.NaN, false), Editable] @@ -1051,10 +1046,10 @@ namespace Barotrauma return; } } - - UpdateNetPosition(); } + UpdateNetPosition(deltaTime); + inWater = IsInWater(); bool waterProof = WaterProof; if (inWater) @@ -1596,8 +1591,6 @@ namespace Barotrauma parentInventory.RemoveItem(this); parentInventory = null; } - - lastSentPos = SimPosition; } public void Equip(Character character) @@ -1816,7 +1809,7 @@ namespace Barotrauma } } - partial void UpdateNetPosition(); + partial void UpdateNetPosition(float deltaTime); public static Item Load(XElement element, Submarine submarine) { diff --git a/Barotrauma/BarotraumaShared/Source/Map/Hull.cs b/Barotrauma/BarotraumaShared/Source/Map/Hull.cs index 61e67f772..b624bb247 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Hull.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Hull.cs @@ -160,6 +160,8 @@ namespace Barotrauma } } + public float WaterPercentage => MathUtils.Percentage(WaterVolume, Volume); + public float OxygenPercentage { get { return oxygen / Volume * 100.0f; } diff --git a/Barotrauma/BarotraumaShared/Source/Map/Map/LocationType.cs b/Barotrauma/BarotraumaShared/Source/Map/Map/LocationType.cs index 470830d07..9b11b8c59 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Map/LocationType.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Map/LocationType.cs @@ -30,6 +30,12 @@ namespace Barotrauma public readonly string Name; public readonly List CanChangeTo = new List(); + + public bool UseInMainMenu + { + get; + private set; + } public List NameFormats { @@ -57,6 +63,7 @@ namespace Barotrauma Identifier = element.GetAttributeString("identifier", element.Name.ToString()); Name = TextManager.Get("LocationName." + Identifier); nameFormats = TextManager.GetAll("LocationNameFormat." + Identifier); + UseInMainMenu = element.GetAttributeBool("useinmainmenu", false); string nameFile = element.GetAttributeString("namefile", "Content/Map/locationNames.txt"); try diff --git a/Barotrauma/BarotraumaShared/Source/Map/Map/Map.cs b/Barotrauma/BarotraumaShared/Source/Map/Map/Map.cs index c3d5d2926..5e0b5c4dc 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Map/Map.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Map/Map.cs @@ -430,6 +430,13 @@ namespace Barotrauma } CurrentLocation.SelectedMissionIndex = missionIndex; + //the destination must be the same as the destination of the mission + if (CurrentLocation.SelectedMission != null && + CurrentLocation.SelectedMission.Locations[1] != SelectedLocation) + { + SelectLocation(CurrentLocation.SelectedMission.Locations[1]); + } + SelectedLocation = location; SelectedConnection = connections.Find(c => c.Locations.Contains(CurrentLocation) && c.Locations.Contains(SelectedLocation)); OnLocationSelected?.Invoke(SelectedLocation, SelectedConnection); diff --git a/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs b/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs index 4d39310f9..92e6c3a45 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs @@ -10,7 +10,6 @@ using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.Linq; -using System.Reflection; using System.Xml.Linq; using Voronoi2; @@ -401,7 +400,8 @@ namespace Barotrauma IsOutpost = true; ShowSonarMarker = false; PhysicsBody.FarseerBody.IsStatic = true; - + TeamID = Character.TeamType.FriendlyNPC; + foreach (MapEntity me in MapEntity.mapEntityList) { if (me.Submarine != this) { continue; } @@ -458,11 +458,15 @@ namespace Barotrauma } /// - /// Returns a list of all submarines that are connected to this one via docking ports. + /// Don't use this directly, because the list is updated only when GetConnectedSubs() is called. The method is called so frequently that we don't want to create new list here. + /// + private readonly List connectedSubs = new List(2); + /// + /// Returns a list of all submarines that are connected to this one via docking ports, including this sub. /// public List GetConnectedSubs() { - List connectedSubs = new List { this }; + connectedSubs.Clear(); GetConnectedSubsRecursive(connectedSubs); return connectedSubs; @@ -518,45 +522,6 @@ namespace Barotrauma { maxX = Math.Min(maxX, ruin.Area.X - 100.0f); } - else - { - maxX = Math.Min(maxX, ruin.Area.X - 100.0f); - } - else - { - maxX = Math.Min(maxX, ruin.Area.X - 100.0f); - } - else - { - maxX = Math.Min(maxX, ruin.Area.X - 100.0f); - } - - if (entity.IsVisible(worldView)) { visibleEntities.Add(entity); } - } - - if (minX < 0.0f && maxX > Level.Loaded.Size.X) - { - //no walls found at either side, just use the initial spawnpos and hope for the best - } - else if (minX < 0) - { - //no wall found at the left side, spawn to the left from the right-side wall - spawnPos.X = maxX - minWidth - 100.0f; - } - else if (maxX > Level.Loaded.Size.X) - { - //no wall found at right side, spawn to the right from the left-side wall - spawnPos.X = minX + minWidth + 100.0f; - } - else - { - //walls found at both sides, use their midpoint - spawnPos.X = (minX + maxX) / 2; - } - - if (minX < 0.0f && maxX > Level.Loaded.Size.X) - { - //no walls found at either side, just use the initial spawnpos and hope for the best } if (minX < 0.0f && maxX > Level.Loaded.Size.X) @@ -1026,6 +991,26 @@ namespace Barotrauma return closest; } + public List GetHulls(bool alsoFromConnectedSubs) => GetEntities(alsoFromConnectedSubs, Hull.hullList); + public List GetGaps(bool alsoFromConnectedSubs) => GetEntities(alsoFromConnectedSubs, Gap.GapList); + public List GetItems(bool alsoFromConnectedSubs) => GetEntities(alsoFromConnectedSubs, Item.ItemList); + + public List GetEntities(bool includingConnectedSubs, List list) where T : MapEntity + { + return list.FindAll(e => IsEntityFoundOnThisSub(e, includingConnectedSubs)); + } + + public bool IsEntityFoundOnThisSub(MapEntity entity, bool includingConnectedSubs) + { + if (entity.Submarine == this) { return true; } + if (entity.Submarine == null) { return false; } + if (includingConnectedSubs) + { + return GetConnectedSubs().Any(s => s == entity.Submarine && entity.Submarine.TeamID == TeamID); + } + return false; + } + /// /// Finds the sub whose borders contain the position /// diff --git a/Barotrauma/BarotraumaShared/Source/Networking/ServerLog.cs b/Barotrauma/BarotraumaShared/Source/Networking/ServerLog.cs index c5fd053ea..172b6d5bc 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/ServerLog.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/ServerLog.cs @@ -15,7 +15,15 @@ namespace Barotrauma.Networking public LogMessage(string text, MessageType type) { - Text = "[" + DateTime.Now.ToString() + "] " + TextManager.GetServerMessage(text); + if (type.HasFlag(MessageType.Chat)) + { + Text = $"[{DateTime.Now.ToString()}] {text}"; + } + else + { + Text = $"[{DateTime.Now.ToString()}] {TextManager.GetServerMessage(text)}"; + } + Type = type; } } diff --git a/Barotrauma/BarotraumaShared/Source/Physics/PhysicsBody.cs b/Barotrauma/BarotraumaShared/Source/Physics/PhysicsBody.cs index da8cf4fe4..2c9f0ed38 100644 --- a/Barotrauma/BarotraumaShared/Source/Physics/PhysicsBody.cs +++ b/Barotrauma/BarotraumaShared/Source/Physics/PhysicsBody.cs @@ -242,6 +242,11 @@ namespace Barotrauma get { return body.Rotation; } } + /// + /// Takes flipping (Dir) into account. + /// + public float TransformedRotation => Dir < 0 ? Rotation - MathHelper.Pi : Rotation; + public Vector2 LinearVelocity { get { return body.LinearVelocity; } diff --git a/Barotrauma/BarotraumaShared/Source/PlayerInput.cs b/Barotrauma/BarotraumaShared/Source/PlayerInput.cs index dac0e7d44..fc56c8fc7 100644 --- a/Barotrauma/BarotraumaShared/Source/PlayerInput.cs +++ b/Barotrauma/BarotraumaShared/Source/PlayerInput.cs @@ -160,6 +160,16 @@ namespace Barotrauma } #endif + public void SetState() + { + hit = binding.IsHit(); + if (hit) hitQueue = true; + + held = binding.IsDown(); + if (held) heldQueue = true; + } +#endif + public bool Hit { get diff --git a/Barotrauma/BarotraumaShared/Source/TextManager.cs b/Barotrauma/BarotraumaShared/Source/TextManager.cs index 2cadb1070..a4b006c84 100644 --- a/Barotrauma/BarotraumaShared/Source/TextManager.cs +++ b/Barotrauma/BarotraumaShared/Source/TextManager.cs @@ -141,8 +141,6 @@ namespace Barotrauma // Format: ServerMessage.Identifier1/ServerMessage.Indentifier2~[variable1]=value~[variable2]=value public static string GetServerMessage(string serverMessage) { - if (serverMessage.Contains(" ")) return serverMessage; // Spaces found, do not translate - if (!textPacks.ContainsKey(Language)) { DebugConsole.ThrowError("No text packs available for the selected language (" + Language + ")! Switching to English..."); @@ -159,6 +157,8 @@ namespace Barotrauma { if (!IsServerMessageWithVariables(messages[i])) // No variables, try to translate { + if (messages[i].Contains(" ")) continue; // Spaces found, do not translate + string msg = Get(messages[i], true); if (msg != null) // If a translation was found, otherwise use the original diff --git a/Barotrauma/BarotraumaShared/Source/Utils/MathUtils.cs b/Barotrauma/BarotraumaShared/Source/Utils/MathUtils.cs index 3e241d477..31760c6d5 100644 --- a/Barotrauma/BarotraumaShared/Source/Utils/MathUtils.cs +++ b/Barotrauma/BarotraumaShared/Source/Utils/MathUtils.cs @@ -17,6 +17,11 @@ namespace Barotrauma static class MathUtils { + public static float Percentage(float portion, float total) + { + return portion / total * 100; + } + public static int PositiveModulo(int i, int n) { return (i % n + n) % n; diff --git a/Barotrauma/BarotraumaShared/Submarines/Humpback_LadderTest.sub b/Barotrauma/BarotraumaShared/Submarines/Humpback_LadderTest.sub new file mode 100644 index 0000000000000000000000000000000000000000..49cd08b94490ad8b85d504b04dbda365076eb011 GIT binary patch literal 77229 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 W57Fz^$#0u~Mf$NLI(&{0q5dDgQZh3D literal 0 HcmV?d00001 diff --git a/Barotrauma/BarotraumaShared/Submarines/Remora_LadderTest.sub b/Barotrauma/BarotraumaShared/Submarines/Remora_LadderTest.sub new file mode 100644 index 0000000000000000000000000000000000000000..6489fcfb9311a1fa7e0e76ab752009fcd7e2a707 GIT binary patch literal 136935 zcmV(oK=HpHiwFP!000040BoDblB?R*rC;U5?yBPAUBrJHc<()(3cNQ0VV}O(w^5y{ z%!UL46zd!#J<(9AQ@@wbm z-#>r;`K9&t?;r5bKlOXUQLfv6_k;f-|3m!;dyj6%a{9aPJqU$R^nXDxO8)t0E%SMN zUn$BWU(0iApK@&9^RWN_OX>CRAN>D$sl1kQZObWt2g<(N{QupS+Pe9B_;+vD_t<&* zmD6uY^FFQ1?eD0^0&CK(dEXUAL4>7f8bM%=M=%y98H5sem;ynBVWB_&ENgkR<=Kw! zx&QuY1IBbtP!;Q$et!(tZ+O3cpV@ym+4nboAG!XQ5vt<$Pz+Be84y}g%a$Jh)@AazK zUP{k=uV`6E$j9&EkFAskBuwO=OK5OZm8J0*(=Y<#ByEF&jDSH-&|X#g!&OyfSwH<# z$t{S;6f`mzGSva@h;>lsQF}Jlx}cwiax31BG!V=>=<%o^Dk`g86!G}#-qR9NsR9Y9 zmWNd_4B<SqM#0aeI|f_(_}|lAgUPA+U*Vx-@WS`C+!C)`e5Bcp%s-SDt5*n7iEw z4Mya^YQ~76`beudW$AalfygW)FhIOpDTsx%`1ZSB1e##o!@~SYb%qy6pknUHHzOMx zO~q$f(W-ZfrsnUn2f2_h4_BlT=N`a{>0Ub!2Q~&NrVH-s3+Yg@Z*CVO zXc?1H0P7N>z0`Gwt+L#U^YPjhbBT$EaA6t(8mPfF}Ky*l#Kx7AMM zcSC72mUK<>gMEijdCtGmCJ-H+dM8Fh!8*TU32nbz@#@|g;(JTIyZ_!@$_|3m84rzn zA<*HeV}H(kOy6~y3LK@8v2Uh4x2yFBEo)e(Ph6s=zT^u$lkAt>qlH_&quEn-Rn8my zG6qZNDt|<2iAKgM_42ha9D)vGMui%VwKGY}Z!ISYoHs+HC-CfK{`1+e=lZ$qVnbm+ zHpYXTVE=qIZ0#$zXSkJS)9-9{xrbz=TH^>-1y8MMVhl~lFFSfRG~<4W(C%(4xjR`% z?FkJnM~7;zjpgRZ6cIOflbgz?DB06=UHD-hCZ`%2=xdlU3TX3b<8eQiDHnb)FgEF^ z3Q{`l*qlN}yRIJ|(z=|-w0&5tkunTCOSaEx={7~wqUW<1Q_b! z9M@TGRZwoP>$}#WqI>t?rccCBl8B<304N?muxs70Gb@EZ*1w7hD_5k#9CubhF$T(6 zNn>56TwB9$szDj-`1L81M~utMJaNv+PwAY3tVTdt!mrb{=uxi$6Fo@7f^Nv&6Ws+j zYN4YwkJQ-haI@Kte^O*8L_U?4c<;YuMEfOMUhHo?XqTB5+qm7bB)_xbCB4)e&BhYc z_V7CGo)Sv!tn;1hEgo&V8p8eyFFnk$jgmO52ehouYbRRwQa23D`~%`@SCUd_0ZmUKTzdO7t0oiOI@mfNJ(RkoM(?}aO7FYl?(;k9mZDLT^F&h^Y(Z|21Dt;Q&F%tn=Sso%o;71Wt>V=; zxTlf6kSf$MDGIrO5di*sLp zfstVTqcUFPpY;;dOQe4VQ1%T~mO<7vw>*?hQY4DxjJsVTXTQEt{U~UyO+#d4R{&3GDGH(fJM~}Z({?DuEoGB(-wWJ1 z3gnz!ezsq~ts^6VizN0!jyu0)Zl>s=O(>?viUmdqZwTe`aA@$@Xm>6hJENIg%BeNm zi-dlw_gXtbdr;;Y@poc5g+1-os_O$#4Z(G%W;ln8^MU^K?pMp%Zapbf^<v`aF|tD)oS9n-wLA zC0;sA@|e$}>{V3LT?-PGsspAF+Q{sVfrXEc{XO+{@8UE`FLAiH=3>HCA;gG8f@g15 z4~iFN2n*-vj6xmk1ro%t8__S@3p`sVaWxf@(;Ft9Jh?)U*&)*bBd#G%ASxibt?xC4 zyJ53siX_doBWiNSyq@{SQ`--&iD~!0jOZO#%WOuB%?ohBK!6^;>Q`9L@mM+Jmo}PM4BJSskII z_Jd)VWbVY2#mKb&)QS-!Q*PG61HSlgyNWXjJ5ojl_U8nChB^*UPOSI)2F$a^Th@}F z!w~N`AApjB3>dZ+C3{TUdoA=*H;l z-2P|COx{Yqc2M(U&C_(ru|nW6;i9{r@oNbbhJRI$T5?W5FMI%3LFg-}@U~&CJz2{l5|5jhQ+E31A$0|oBPp{ zYmQdQ2u2aP76NBHB_pDF+TM<&dQW#Nm5d$-8wU}=EkTsGAP`H&q6H*^`XM9O>MG2y zqg-{MU4*y5U4K$hQu$xpl9xrT_;w|}!_Os`+OVvz==!0j(653>tl0TSS|g%Kk#nfo zsGf$S>wHns1GDRar%$5K?+oLy_B}M{C+Mjo#WKv?Yo}heVGqNNeHiWe3fcI@-6eR# ztT%T*-FZb$>xBpJpY&rV=65Fr4~D)qfvGH|?bU9h?h4VrCl{~aqauTRJ=J3rd#u{9 z%3XWT@b?ogn51l*r)2m_>kSWLF<3ZhGsBGBXi*sGJsE8+pSX!Xg)r=${?}w}pB8xf zv)9jA>O~IYzT>PK`1Lat10_2NXhki?riBH$Wt|j&J1rrdObJPU;(I@ROp)cgkBq8G z$z8Uk{#3o!&?Ff($F>l6k}^R#Qwu1~kn5nRCINS21}j zMlxI4x#Fe-)Y11wRvAPiXHGxU=0UfW75MSM@GbmxO#e-uK@<`b)yeDKlU7<}N-nv- z&`0mW;g-YM%#dMlOLz|0GO(X(=fsJ?>Ml6%OotEU=!xG5h_!hao~I$Z_|sxIje5bN zlSN3UV`zZenT-tsmDj!kmM}v|)LMhJJrYzmu)gnyDAH-+56lOrUPpWyL}+%H8Ee4o0!1h;7(u`l^-#Akp@s-9i9rTHnVGYx|j%y@J zlGXLD^<(sVaWvMzV1&Uq@|bAM-(CUOM<{FVo+rIx)?)V8Iw`kR9uwuTT7j~-zb0=K z+|LqHCi@j*N>3}U_h^+aCG=L!)75!OL9bV+0MI5aD>rj~Uu9z&z{9sejZbmP%mdrF zU&1x_qRx9E0wx#S+8FZSNcssDr;k80JNxUfA|D6nGxkVxFsq~|%k}#B59yd8vi3d3 z`RkX0!3Re<0SS7dEa6(Hsq-lYD+t4(W93lk4@qtt$O+$}H$MUDmd2%v)(`4IdoKSG zji;{Pvhqt9e{0XzV`vCl&ol0aYbY?6D7#;tE7-311j{laHXV~_w6UpZ`K0Z^f7g7g z*CS3#PMp_hFCi?y+!U$;8x_V#dqdRgHEL}!yy9mJNOG)_^xSPFu*+^xcgTt)au^ta z04x2{a-IW1u-xl9Kl_=AZ5}elcsm7zQRoWIs8?XB>3{vV zTUt7enOkt*emkP*XXuu~&wKd!j&EYGjW z@r(Fri*GOEX89_=b|E{K#c@U~&p5@mzp^C?Dc)wKw)ive^_TjHc@f9+YEGOvTOrP- zAOBXaF|n=oe2g2)oDV(ghoXMOzWRm4ZaTr@7*1CHE*!Ee8;-Cw(I&|w0k|PRh8LqB zTDer?+wc}5r^(DF8tih^dEl!`~Ws69!)WZG~6 z=idcqJWoWc$V*dDbj%vq!#=BUUX!j^Ab*!1eqc*>*VJ?(xhA&7cZe_oZ{ zbfKhbdJMn?O?mjEyX*&Grl`uh<%@r!uKAR?!Bb&_)-6av_adD^=(ln{@tvv{?mpZg}!?;Pd^;Wp+=>0ap~(grE) zx>@Har@t})dK`A--k)6*&eC-0ZrcU+uh%FxW3D&{$#cPwqn!6&ffg!Tn`GY9-S|iT zFu+dv2L%fIWL-$sRHrI!r_G8ecD7C7(xvQE2YbPn#t&H%1A+gs0cDwsV@hUIf0GWi z>rn)CMH66rkOssoy8B|N^Ly~*)5TCsum^{KNV0v=vR^M;HGy*$Ifvp>c#aRmDg?{I zGXyW0L^7As8Tt8QxBgx9K69rRL<3qCuszgKTy}QnN2x1ji3`X{T8rmjM)Eo*WC*6A zfI?{UH=+iN-YL}}5Nan#Di9i{D6J65+oPmOO8HUiPg2sy*GdM}Ob+;w&pJ5nS2jcgHRiB&F|Y0d zAqNM_7t@_{Zen51Rqj#Sl)cj`TE*V0iuu%DnGe^5;RxrrHh@~AFI>n63V}B?icJO) zsCpPk*y6#3D@dN%tgb9<%9bf&Xv$I7Z$<^{F@2_AkD;z*ch5M-IG2D{CeXC*zZsB=^A%3sjL2IPW zRJ`pg2Tbd48Ivc`%ESHp1g7}?B$Et9k|*BLW@q~E;mX_wZ#NG)`c9{^?gM0x80vRF z`h9OR|C@w${%BM~iIIU0QE8j*XD*QW3%el^xTE_{cFHVniU@BEc?gJBxk8NLz`L_A zoyFPMDm9?c_ITt5&K>URlkxTS6sz=iGCIC%+VZS|_FrjA-5oyqsYkQ*heh3^mV=z* z+T$WxzRlfHp%#&*IK~uE9xeS{HjQvFQjaxpCs~$lQ7lgwSB~w`B&&CAzZ7prsKYFc zgMJ+Aw=B#fDtLQX{ZUA%;33$6;@h=k3gEp;`O zB&}gNN$QNT)9NsYCOnY{!_Z#Ng5=#?ifk<4K2zSCYIv^V!HiQ`TsjNs;8NkM)tfpH z9e60>?I%6({2;wIsFJ zYpLzvRT1p%RiJ!tfIM6fF*K_RdUDys1=^~6N4t1u_c{2nmqq6^}?Fe(y*VMl0$TumN}rZC%l@UuEDuDi^458=+JmWIPQ>T~FOZ zujR~JhOG7kQ(Fsruy(_v!y)a0vOEel(n}i|+ni0YVYHRpKNMEVevW3z=d>Eck)TA> zT(6y%>>PK#S6qQ)h*)o8EDVwQTNX^unhd5W35*ng+}K-Xchc2=e{y9RPKEou;I;v{ zC@dl!M-_FSj|;ylX<7$*M$vSt4oa52&lK!U{`w)&=3TV0$}yO?(xVr*d8Vj)mp%xn_n$yY^i&)(HI z0xr7)51q!gv93)znTsyMSoDx|<_-C4DU6F*yc~pi?!mydM zCv?fF^AE&#XIJ)Puq;^05F`@W5`C<#t5$cP?^IjRWrYQW8FKWq?mnhpuKg@->4cDu z(2y`{q^W8!#M)B~q-xgBYVt7$h{D)cJH423vO5GJ+oycTVARs_tv6xK>0K6cyCo-_ zUy;+rrtE&7@|V`pptfauhaa+Z4AGg3IO0AkNLHmY6p{jcQ}HL2@T{7@DFLIfj7|(}Zd)FLEd-#cT~aO%(NzJ6%KUh&dj|28pP9q#!$9 zo;ROM42=@MThU*&6qd#(T6k9XHl02e7G2CW1N_Yl8h4YE*aBd?%bhaptR`2o z@YvBtulaPy2_vWhW{%ZAgVZX>eKYTufzQLqV)L!LL$8T|;d3z}L7I{p>I=2f* ztV@2JU*`fmXJBlbAAGXpk31Ap3hCdPsjn<7iIJ*#@@K~R3J>3X#y&tTQ%AJobL3|D zwsKD>&rwoDX5#6I9dNP(dBzvkm_%;Ic!KiYl4d27lY`vAu2q~GVff9LC7Z<`77As#Wx`~agA7pm1|_+i zPCUac$wrWelD%N1!~iQ*Rsm~XmxtqW_4dZna%JIUfpEcNYE+)gLpJJ(Fq~*2IUBDP z+yQB=N8c;}#f>rYZLuPke&{qiut3}hID;S?3~acw7>YenLlz?tN20-3F(SY1=WuU6 z)XWcGO77T?VS2>baxoE7`f$+OW<|fJ#=X%km#>h7kwYFm3Q?*`;?M*ZgnOA98BHGY z?uzzm;;KfYjslXmH16)`uYTk8N|4!t{Va6rvg{-EV`-px61?XhMVqH0io6Nhf#>kC zr3L)tb7R8Lmiza&ACaR36P&XRA+u);edXwG@8wCwq;bZPX-JM$U2?vD#H?Et@|t_S z#Rf*db0*~`b&XPUbkZ`F`v=EOzr%pN%)9yKY zV#SRKp%`5SX{_5aJ{x`81TcSv@+fV0up^2fUs42c7|ZitpT6Q+l-vD_(CGD=b2|X% z_7w8No_nbI#wT_s)ot_Vep;vT=AhN1r%#}wRo8Erw%bV0Vb}4;Oyk%??`h9AG*mb# zR=?DUFL$#ynYr@nm4}`$IQi4vqTaY+psH}z5Th1+#ZSCPaVfb&(j?|dKFbD(2ODUc z$X$7RjTD~v0sfZl@aPiYD$G!a))h7G-|v;+7~4UUqaYE?)=7s zXw-%;3#ZSIFjnj<7SDIt5n3^)_mmQC!r1tNp?gzFS3P7b5k#@V`tOS}iUr2|`XzED z71VrqFHN6@QCq}5)js~u{=xJI-{){nl`p$wL4;=Caai>+Bjq5mlI+UvM@%3W%Z~)8 zf9G-w_tj9!_6Zo2>{+ms6Ci1kmtFGRqY z`_uLVN*<@2CKxo^tYC%ysi*?YosuoDL;P!b-BNVG?yOG;%X};k6u7KOPjGi`NTk)i zo~1efPzPND5_0XVKon-VnnI$Rn}i-JJ__+^)ZOc8dq2#py7Xmld5`Q~g1bPGw+WeD z&47OGt3K%%Fy8|>e*l?L7kc067DaiGHu>gA6P75~oa)TmENYd7lhhWLVM<6(%du;E z%^fp9%f!K)!IXf&wM)ZiVXhkv8W_yguPz9^aFyMc@cplORu{mkU=gKVeGX1BiVn;q z8$qn}A;+-9QMe$F>}%!L;&H8lv3l}I8#=ZWDC12W(kP$n^@jo*XYn0^$<~`NNfw zAhD(N(pu@I0^0}(c-3W6%`)U6HVbd2y+Y0JXyWm7yO6M*StlE3Hm%Pf#y^B+rfI#^ z(0*P~ZY%)R*hAOI{8s^wal7AA$y~C~@z5=M-apfaZ?!-@W*vEo`%PNc^x|<3@?w`GRqhfZeP$1$n-B->5g)Ff@modRQ^iZqcIm9z!ZE@vpfM(N0T zLa4W$SSbeLq@6z4gwtQtUQ0u^R#pOlgC~eLuRxxAX~X`mX<&#jz{)7_vX?(NQHR07 zhq)r1nd&p5qC8zPsHZ-v9<`QE!;;!&ua5H{_kn<>zu0*@1|#O2`nN>D>RlhLl^q9F zmZetU17~Hm(6)@R?+0%gDhHD>cD*QpbhnyhVqc}F{17eDyE(*(mLmHh1rWfV9xSuP z;nX+H!4?_io@DZ2lqwAnx_u*|4Ye~V9A0xMv1Z$5`3j-Xf)XHgcOE8QM}OcR2vOU{ zhp`yw*mhXVoaP1PKIviArE23>rD=yfI)1}(ASOV84li$iW$T+4C=Y1izjx^H#K5|s z0`K)r%xw3-xJjN%I=g!s2ri9*a!UoB3XmiUTx$jpd_xdKXxLlgDr+j2^Oswf%-2KL z!*0=2%~;;hf|pvzW82FjffXRQ)fn^gBFLM4S`&$@&pfApeMn{!5=Fa);-a!nxyplP zA`HULMf}47ipE4@ba4zd0!ww;vStcP}xg_<5R%-&6x3HkmUKRcH(+4%8)q5 zdu?`+U|UA=Km}ZQ%Rc^0{79=8K0{Gu5bM(xwDhUE(BI-^oX036T!y7@u@nJ8+VDH& z4d07p8&|fj*wIC?^LOhmka8YS2*m7?xtRgyUKxwX`8cdASiJvoYmC?jvJg!BT|htx zn|P9_n-Hj?0`3Auvmx3VWX z<5gPADLsPeLPJC>e6&h7c;O`gqn_`Xgj&+8;rF}1^8O0w1pm_Zwy49; zjxPd|T&dAy#A8~O=$_07A#d-Fk5fUKM!;(46?uSYW*)%qlku6636 zUYKu02_X0*Or|<|N_~(%2`7>F7IK7grq;M9r|Or2GI2;?)GCrH_)e%@nJBv-Usu!Y z)RvOa8Zu)AZ6L=sks-G0hbQdBSxfrhLcC{8p@2Yl@&HT198h!xIJ++#78i)aS*{mF z0b5+t>E3Luf7DZ)LSNZinavZ)zaWgshyV&k00xpri2#a%n7Zc>Z z3!vLXFM)!~`WhvOVwU&%lU6yx*}}X!y8R8my^XS1jibpCJQJdBhnAZw>c99rz|ut2 z77AMP>IZ_;sbz(r39r}34{^$~yL)LVgl%rWb+&dbTwl_MMM(N;tIu&&D3%xntnJ_) zpg0b7urWFH6o)>F>Px}lSc`+34`^EjqeQjf9E}cnmE<~{_?yz8BPT^Uy+l#e8~1nl zZc=x}?Uii3li%d3>XJ2jl33`Yud3~~>d|y3mOx$enc&)W*6v6wUUQ+f4QIvN zcik7RX!;QyJm9R;BV+zF_lKkRcMX?p1s-j<5ulIB9Rb&Kj&NO`0W64y(LIq# zP8)~D$X@`;Pil=Enu}PPKm*RwZw4@cI5#pp$5OF0G2(a2#&1Es6*byQogK`WK2(l= z`riEB{L2fXUw7(|X1g&-s7R~O!)l=3Z;p34tv$sIww;^PQ-JoYL}fo9cZ-NgmqbL7 zf>u4dD0x6w_WcFr)~BbH zuL(ZnB7=<0Z0G}_te_=QPBW?2<+~C3rhVLzXB}!m%imB^DI2TpN@`5Wp536f@(ItQ zzzbIr-?CK6DTIa&5&6neOQU3A#f9&?dZ`qc-h+D(zB*|OjSmJR?TiE z=HnsUDv^*;!&>e`02o&|;RAcD6__Z5TwC?k?8Axzo}j#kMTQvf@oJA=v3<}2A`mL2 zC@v^os|;*|{Vp#Xc-JE!H)h2K{AqT*%7DYEDpaLR2;git>0bukd88ROq$T!p)JZ|! zC+oBnOPko|!VrHPVSZ8FrD0d{#VCcPWv4YF`&hA2Yv(PJ*JXT_YI=HZC%A7oETLS6 zM6#XUD+{vqL&_))4gbi^5Ja=?{#4vS_n2dbI1V)5+?@RM7g-alHF+6apHCUKNI7{6 z#6$vhAMy4zKx`pg${e9bwq|FmO6ya6Of$^f`lpc+dsU|WvgGqwGYQSg(guf8_LJL1 zSakXCy>Nn6@?*`*yr|H1&0tPNmGQ^N%J9Z3q66fnRcL7y8z@ilt2AC}RVbY>y4x8Q3r6a% zh}iPVL@JcgAn0b6B{2^}fQ8z4^}d-cw1iLO)hvVA5y>l(q_qc<-BQQ8?qxGRrZ!om z)dEj(ksq+Ky{K|Vv%0CIr~^|!vq2c2(N0FhoxZbE4H zu7=%;Mz}ER9_ej7-CIf#d%f*)jfHSyGEF`P79m0=binUOU~B-lOUWt=jgrUm_e4oD z)^;LOc+S$4x|dEEvJ}+1-UQ%ogsb?Suxe<=D1^kool^v%BLoVK*MtYSb|-PkL>6c&T6)aq0=5S=vd^mTGf3d2CamW7C`(!OQEs6 z&v4LaZpuvXg|cRhab;Jv`VbP83GbbK?IsOTyv4NEJ{+)k^kWV@x{R#K+J!3@YRJ>3 zvn1}{yna#&>!0LdpQ6p+L*paW{V*W@CJ_GV()6CcB!_ha(I;DZC1e4k`l(!k@=EQZ zZZU~6v>a=~6a7};Yvid%-VVsZ1=?+sH{eZx=6SwX zW@~m;cPtPI4#Ng2Rlg}Ejbj{hi~QG4uYQtd233+4?0*74kucIn<@W$AE`yEKai{cH8~Br?$*Q6o0Eo?M{=y6ERQLzrP#*=FAtpAeK|Ny*f{1ha*dBJ%&EpzGmhBfXE~0DVjl7OHp?M(UpBt)F@r(0 z4p2DQtfeNp;hMo&t;{nM=W?1Fn7Q$s0BPx^ViMu*ZU4+)Nn|d9S4P6?xx9{XBx1$V zZ7Gj$9#J+d9(|zfp*}q))lv8QoimZ2jshACpm5TP4>&)#P8+reuyCx?G;P1{_1Q`j zgdSxBBpBEcG!Gs#nqJfxFzSPyY{y{n>%k&raYV8s`I4jTir#_L1nqX%_9(i0b`iu0 z2ydQ9`K2D>vSm!=3{GssPD(`%PcY@;^Ld(a9nR*{4rq=I61lUSMXH|nJc_`xfzMrJ zXB5a0^9bzR>le>uEbYe9d}iIh`d|Xn6O+2L1P&y7fj$Ev-ziLL8zaBNCw*c+9;Pc`@^C|6xX5jjDtSj=BX2a=i!UjWW;tMjq}VK4hiyfjT$W$UX!F zkoJ)8z~TxMFy2!ci7~~6v?0FE0v7w~tDTYXUFFc1jqBu3b9Y4eOkFJjr~<&p^CP$s z?h*7VhzQPAcvh`O&LgH)_s53696n1Qr`{8jqo(;h?qWCP>2N+oQI)g=Ig9Ckc37dYm?p%%pQG z(ac_*``MUFZDm&0$OfI@xIuetRfN2y^O5uC`)xWD3!k?XAJW4{BwC5`Hy!LQ-Yc9m zH2j(1?`%nE#i2sA*OS?x5rb;8{Opc8_Urc1)Q3ogPljt1++w6nJ^gMF1i8zOrftDHOQG|X^+e!TtC$l2fZih0 z11S4WZ)5Tp=UY%G8JC39!u&LZDOo zGTp!bycY}~I(vhASWDW4Qh#e0~X$pr+?Sw@7Pj6z8`WMYq8|Y6|m-C-Kh?hk^M2 zL_xm6hyPSs{DLWhRxb#Yz$` zFc6{`Fr8CTbQM-^h%I7^AnZ`vzGvb(dA%j!o+kM)_^n#U%m$t#*RB*Cr)l@g91!h- zh30nO@9aVp_2G!@LxI3>tYNycpLlpI3y-sIqQOgf7x=_>h&cSY1u zN>LSOB$nNdslsZ0nRM(-W)GtIm{olt6CY$KID%AEb^S2~qfmo94(z3%=b}E0d4Rq1 zNz(em$(rPtzpe}P&PMCT=~}}+$8#qpw0_gAaUP1j-Z!kf#_Ui}sDbWe4n}zKC(=q2 z)eq1ssSgZunK=dG{DP42aC82cYw5a2za%*)p?-)F*ab^6iZ@@RR-+ z{z`59pnoM$G_vz6oMhG7TSp+1bu4GNO}`{0d<-Bavl2YEf&qNHUN{Y4UcJ!Q#7Yv0 zW$lGL>oC#{0T?JvWdH;~MQZ*0epieJprMD~8LiyXD-h7@8`kb+?TB@@t{0eh54f&j zUtTkdxJ)8$3dd1x*&Rfc5>QuW1)BdU#DizQ5u1vg8f)N35yAZ-;NWMDkM)dC|K!v+ z4H1bWnPz5X1lz;)#B(a+nR{-Y82oE!cU_wi$)T!eI%d?`46g(OlQkCn*=&W3rX@1& zE&J{SfAn&7z<&6a|AEY^{6PU6r+|s*nciziJ$eADV2N!0P>BcWbG;5XhflOL&rD8q#!Lt7WnS-ErSCQNH%RUhh-a9JK5>hCAVY%=t% zcQ&5xSU7oYkRaHnxi7W2eAV8$4Z?-yOB}cM<>ikXHyE0-Vs1r9W9y#pSa~E2mzX$( z@|ogPqjRY2n<%|;qFpsH-0nBjOoI}HCxNd*M;v#|7d-Z5VI`OE#J?_qNhTS?+9?33 zxDTNuH3|=<;_&%D9G%6Mt56U{KZpTt!{F}0X1Kcq`1(h`VXu`=$h}pk_Rd0j?g8Do zLjpXpms%*^hDV{Imi)4euEY6(Q)q_NR?naa<4R6GiJxR|O>slL0`}wj@*iY=xDKLbepN zFFN)kY+@bRZfk;^yWMn+OmMpebg9)i9yNSXgjL;gut+Pd@A_Nn4E55T4NaKh=IJW> zC^5eG`vlAO@q{B@=cjFXZcU1H!Rdqk%So>X*i*^BgZ#M*DC?$`z|q4pRD@x9Dgv+1D)lc`)b!Gv{4Z-TuPpRMSyZ@13GPiL{(S8WxA5 zLco{tYFmtyl^`N#$k!+4?IC~vha!yS%81+vV1%7?=C^qY3QZrJFH9^@8jCwL_kHcP zsK@NUzAwb{Yh0k)jrgMIZ3;#DU7f|YD;eq^R$Uc|hLy{)eJ-$f2%^(zfW1TL3#UQ6z|2r$hSeUPYpIwNZ z^yy295aM|dvi9i)`MhfDhjf0xx%4F%>K&Mxx1z6Do}Nu#yDpf&2OG6Ack|o!gzhk7 z1N|d%xlb(Pp0nqqZm@=rMlJ`xKp||t=pQOXV$HJE-j6E~^djemWTDpt=*M||sUKYs z!bk1C?v%nr)%?VRaeYjvXE7!*l0Gn;CP_$#h)L=ibwn-G9TT%ZWZiAQ$?8_zm`HIK z%sh$?x)(+H{2>hK8%=(T^1_Y#GW+zFhJsJG7yY8AHw~KN zlz$oZJ8RJ(dM0nce(z^?FiJceDUZmoLrvt3ZZm4#5atUih54+Fog}l2^Sz21UioPX;|=_P znl#x7)~_PmZV2sMQg;EWkS!rJX-OE?9&YB=p zl8G|kM2rlO!@)d@SdHL05eWCp!mta+x0m%USk@jT&x~2hmO778R2e1Tk{&3%cSdms zwmNBfDxHF6lKn#v#`sPi4fOutf8ENAeaEqH$Nz;1YeCbEmPgIWiCAxYT6i~KYmK5C zm-7wa-I6|)tw(y}r07CNq!XrfQZq0Zcsc|XWcTX*3=zurSW1chrW^|&QE11`XHh|| zCIVjCzk24)KdfEfFoPQ;s>kCv@*=93{3~CO*r_s^yWleshklZKn4s?7k|0~v-gk1} zcvwr#o)@Kz)%m&uEpWTy`r?Oer)N6^C9dK=n&5-LK3vgogt=S(P3I*mx2lXs z-w*lNaM+#fz8dQn&eWs3KEjf*v|@EREMt&G+S36i{KujzQCj38WE-AQKlE^yn>h;2 z_Xw%t^Uhu=YJ3aBU%S(?_>zeqpOb*EU*|)cuoec%{m>mLmRnZEu3@SdQXVLZmhSyg zlm$x#MVHi1lfR~P8i&$94usN&{nbogv*F!gsg*DoNEL^vY5?06SY4bX2iN%XEC2Q~ z@HbZUe>wAYBJXfH_K;c#%8E<5*BpJv)ja*6d{#EvWWNrA%H-FlQ#-a4hmMDXT#U~_ z?}^Fa#szad(z7%(8y$U%Y;K53bl326DU#tK{4y;U|J8p=T7_%@-?#waG}G0=afBx{ z`y)uOUm-P6OQHkh#ieN$e}XV0*mNnpca;!@Fo!jFfE1Yfy#rz z-z74DZ9veF`Ty|g))&G)TKi8)t9k82fbHW*b)Y&I&ijE&)mYhrh2u=oJ_#Uo%p zgj4Lz%WEvjTiU2ngu}gS3*-wS~Qm$m<_=7#g~aep+Wi zaiGc?$}#E6)jyZLnz$O8EzuQ(9#tQx(BQJ@0XWXBDIwSLyc>+v;ka{0%f)Fm;tj!h z))oC@pN9DJ%c}qY!H;XsW{R1N&=~6e|z%4P?Gfp<9V zwh++`y+OvP9oh#Y$kLu8oW&=G7o|x-oi}t$$phgPCOiYF#FNeudJI1T8C2 z*NmOaih#8aa>brW7A|jZVH>JqHj!~ZP3(H=O`SC+vQoXyDyR1*pJ-PmmE7uCqAZ?j z`FRv^vfp~iYe&bXmgydGw8m!(vjD+{-`wjR(z(tACG9bKGa`Kng^vsb+pdnvUjOox z**bu6xobcx~*hpQv!K1+v>Arw!&(wQxRt=;)eozQcAIvHu z8&QnF1{d`b4q}eP%X6UoMm4qb*~qXg>aHEgjI+GqT6OL#*6hIOQJ+BM1O|jrdOJ>{tDoH1(qG4y-1c zS=;)s${mfe*#2ZDbF&;EBiS*{@kg0h>zn{F=+S=tu*<`U^Wn8xnb)4qqV_t5Q1@P| zgty&}?e&A^!AJe>mdi7I+%5uF-=DW8MWu;`jzp=Agf89FHAKo!fug|pvcI8*KhgwI zRCB!CkhSL1q@wv#+Y2L^UhF+xb_?mTr!Y^^AEmc?D7|t1EBndEDIjj14SDp?LOlLg z6?0GD$Xf^CHhS^$Y2Gx#sKV;A&k&wy1;}4~r$|vyRhZO&`qI5&6k*K3I}i@FY>f;W zm(wW93D#I|VB%c^8rmKbDb(sCHXTNK3qZVVP_0tk zHg29fs7N1nqdHS4C?SCx^cV~`yI(&z<;!GcdP_Y~`x!43ysp3u#844_lE81HO6Pb1 z%05k#SWHWBI-e>r%J{JcKV#PLJ^+4hMw{f5>`Hrntt z?=ReOWjQ_=T4h*q+A>^FVKjbXk&C+SA?HUYI{Z;VV}%X~*b0KdhLz1+1PtST`qCY4F2R6kXH#``z$8aA%~^zvtOJ zCl;2!As!s<_!`n9N3X*TQxq;{?onYwXi3CrSilacwvNONfcAH9S(F4*yDZA001F9? z_2zENzNMf+y~sB(osB!Oc82-CUzB#OL{S74h64m+k^mip+RTa8g!&PiBhftSSC^&p9-=|y#}UHbGf{*i17~>YH3^&c9gZel{i%2y`u1-f>m@U3nNw1^!w@#T5|9LPpuG=ezN#YL0AQ6R-yFi6`oB_@(WF!J#GcJI#U{KYNR zBtazSHTIcLgij)`+Sq%6wFM51SXv#PML#M1NO`BNf49b1wEZ)_M%jquG436i-W>hC z#@8Z&T(!_69nyP+KhU((4Blw@B|~wx!Raq$)oNJXX9Yik<8K71lQ%!Prhx%EQx^OGP2 z`Pk#0hEXnKEy*L0uhX=F?|bZ0Cn;3jOXp{`>HQ&gy~GeOb4^AikU<9WX1GkOi6zC+wFS zvj^sgGonBMoAFUIn#+sX$#3#PkqUt=pSt0p+L0J2L@6evEfkG^-d759^N>O6^La9? zw!A42#YOL}$WaFK{0fNo_R5J<>eZMW5CyHKflOREH@;d$C(JV z(M4^Q`it-%1quvI24;c4NEVhp2LUu-*wqxC=r zO9*_HGUP|oA#-QOOo4YzuSMs^A1=KiX^^3)IFFZ(vkV~sF@3kaGEsDl#>K8%)=J0d z6}}VUf`{q9HQu=jr#x44>xx%lqsmP{1*LjS00!*`*dz~PuHVz(Re=h2R9~E7hv-#^ z9syD}VqO#GVU-ev|HC_D>i<4%E9NR+THdNdXS*c1q`dL!~feK#Bs3MbZs9eft z566ST^}MH~@-VUV{VZn)*Y%-FLCnb$kbQaak4Nn?pFLpr}Z-vc&%0kbUxSHh(rRQ1`Ske6p2Y9#$u< zAR-f?ipBF^bk}rI?ho85e?=Nhypo|Sb;^92Lc^7b<$`EiSquJ zy|$2)t#VU~7t*{^?y_v%K}~!zwsZQLAAm18AUtD+CB7%RB{;|lco8$oPKT6 zpn%*#jB)BIBp+w8xcOG5EqA9S2l7-z_uh|RGpas+06CrBj1ld*F0OA_${Wj63a5eo z@i3+qSurA~=Hp^!xmB_oDT!@9yn`ie%vBlFCaK9C#8iHJ_C?d5YrC`EiZONwMbgg1 z=360IzXtKujAWDs%|6w+`q26ap>*c9sVU|CT9wEEzoSU|GX3z@k4#{XXc4)bvi{h< zyN)U-M;74t{R%+JppeLM^@d{UhFAq>|M(T8w%T7yx~lpfxX3e!K27(XuMD}~Y-N}w zggetaJMo2i;~e;rfDd@U3FgR|KIxpg*C+tmwXbrS9=ZnYZc8xD)5#9}MD$5FkEV&| zBlV+3tuTDXgQwNAb_`$sHy}f$WaiMbPkr?cfX+(QD$J%Az9G=57@mga)R&{zkx~p= zqw@7RCC45$d5lqs^BhuP&vY=>W4P*Fh-z=In?uV>MLL9usf3;G-IbdgUnAYi5SHb2 z!SA|DMU2$yEX49+M~X6eo(J%(gRLZJD4l{*VJMG6agBA|S+OfdL(+P9=18$aVJQtP z_f0F2i_S+k9i^4Kwimg$4;mE*%yY(4vOGA4ROnH%X<2iLa?E!9>Z^_)RovSMoe0UP zEbZ3YhoWR8(nUS_H5TQ4a4mgv9UKbi0=_GelGwv^@LkISBAGZtW+FOGzU!WLq7o!j zlL%>gw=$uq;-e}57$>WH(DD@^pxeNV>$30i!|YVLvUdWyS|0>EwS@DrU%boO;Ps9* zC#dCEXa@1c{$U;9ST?Y3@Urw11)uNSQ(QQhYPZ zUbMJ?QWax5C|R0RJ#c({QB9qXktLJ^+rVtilNbMLGoHnn-^ooMK4M=2}u- zgPUmuIN?v~BC^d@9MyxODP?o7V(|;$4rf~N5Lj(NQC^M#Ni7Y3R=~|(W!q>UOLyrh zEP(SsZqsjC6@weZ7Ipwui+mAmQUpgMWY2DztQIaTT)&a#T$-8>6Ny@yO_867UaqVK zP+~2Fc$C?10fGl}+%M9$eLN`&e1Uiyet-OU!{6{Lb7H?5NWm)s`Cw6bMj%POh`ubH zV&{y#m?;dW1SqJMq#VtHnO3%gB|Gv*DE2u)Emxx~nTKB!kDHZz7ZRn>f zjfdeKbA$%XFHkdIKt6!S5*Sx`{VvvLa%E`L`WG6fVqD)qF{$uuUQ7q*A%D7Z&3aHTviE9<10O zMD5M2yg>8%pwfbH;-zX`3z83}!@QC=zZXXZUaTO%gf#DmEmumoq&^=%VR|$XY_M@NNWpakM5Wg9+KQcgh=qF>V&MeTCzz$7d z@yHigi|{+?wnj=#f0<-|18*V@m`N|u9)3nzW}s~4!zM}TY0h;qr^KB0p3G%sjgdi= z-}-qbakWZBE1v3M!ZGzrZh5|bSf1x1(>98t#p!;n_e-TO`?LTkj6mN;Lvcz2@WD)V zG&or*puphlg_cf1!SB2%z9!03i7rnbJP_%T;?#W?igIVOx7_uVpzx%#6BYF6Re%8l zuh@aA0QEu5HrK>w(8iyG5^$2%xCveAr1#o{<16N2IX=b`EIT@vS!7BCEMz0KEOS4O z4%wT)6mx_oTU8i{8X^if+7_^qQ42q#E4&JLp9F^G*v~}e1C?W(;85i`JojU zv+c0v(gNcKLknDpmy1lPlFtYOvNvP5QuTMljWeJ)(8S^J=?vpWzep@ixZ@#6w?WeV zwpgH0@p7D84prTSaJZe?@<3rZm&e|;4WQvl5VXJnM8xOK?sQ4GLr_46^5PLTN!!)x zrQ~UEG6*BUIrH~#YQQmS7_|A;ALQ4y8!0VgT3%#Ttc*XUZJI{AxY5JkLq#gsNd9ix zB0QS-ZZM(0BSBFJT}<_W@8zYwGHwFPzu#`#8JC1bBggTm%X5wq4!s2PE($*-Tv5m@ zb->0hVc{bT-BVJhE$cyTiG0NgLv$1HUn^!PRF+nR9|=$&om!M7iQ`D1AIk5_$=_$k z@3HezlF921+B~CVo^%HZv%v=LE3IinxA=7tcnh2Y8z|WR-S7kGKyWQX(r{a18S1Tg z_!ff8NJ1?bY-ar0C&>tZG*`lSj-u9A(Vq*Fv?KG(J+q&*{rqS{%4eeqa5%BC+Q5E4 z19IeZqd|oF9KV{0Qt-_eL|-+60lE`=BYWuDYkS=mKC3!^<)Z+z_gJJ}xN$~BGzOoS zcO5OUeHQd(WW&AYft8m^cK&Xy zip6oe<)dAJ@&3z3477hZleNkr2er6AnY#WAsC-avLO!E| zdX>hF7Hk}ownemN|v>vvAQ_Lnn44fVO0;aBOkIAiW)koAr$ zqrUu{qC_7^8U@dBZS049elSc+iY4(~{<1o2%-KDFyZ{f${Y&dZX)#nH9_f`{)Q(Np z5QioquZi!-{(G4tkF$;N4vfKCRbo+=$f@6mDkDHHp2r*?>U2Gcu2A=;8QT_KI^9=# z1DK9uO9U&>u_^`Z@Z$PId^hvgODTfSSx$6d86xx+f?zqi5BkoOW(G-Q*&z+*myw=_S8y z*LbH3+Aorplf8OT`uFIDc3u(@vvge3KIl8&D%n@WIHFF8uh>@{2tf|P!Z~sPq*)F3 zPXn>#X&M4*D$GrH{>}tjjVB9zY}ObD!cB2}V;q*r$JeN%i9Mt8qq*`JF?4eg2 zus_t3l^sadPOXA}w|a4#%{YYm!%vA)E4}Gon>een!aOSK{}g9B?V74LPnEL7fEGm% zjTtk+U3N_F#EZ9jW%RpZg(DkC>Q_tV{ZNU63I3#N>pPq*76GWUPn&LD8&dMZ#uXmP zUsSAu?Y7&{CqQ+!0_<(pyzg*b>XJsBR7pv?;_q8MAoo$X?(Ri9yD~rQ6w~u!t!V8! z_h1d?haW`;^_pabOiOJiv_3;Z9cRy+|D)hMRQ49TwM)u`-!phzx~>ArL9xUtcAEox z{mQlI7e~ki!l7XF-*Sz`FR(7t9Q2QzcSD9LX#&pLox z>BUxidVIhWWTc(Tzmu}p82oY|E1UD`9!yVW7TZw%M7zu&-Zb|`8+TXRAK=H{ei2wE z3s7?6<#$2G{adb?T0Ja(_uUM9UZ&Q~7hmkbkr@y;6A|HB;o3qqAK|jHU^-OJ{u<7j zx%BFLZFHY;cHL7Ts+Ms1rGpQK6jsM@H&1~swNFc~xyXb`P}ye|hdPJ|Glk#a@VBl% z>9~l^SAgM7=wY_{Zn0gOjx6%kl8lPrjx#pI13v6s?Q658Ve{839}0{%#^2hdhh)`# zJ!ZZCt_!d+wdz@RsBQlG+)PP<`or2ptMI^AX@rH?_O{lE)-WVF&TYcoRv$a-A%wU6 zz<_3PF;;!Z$T8=(pIN!Wqw|pISp6Em{3oT5#|(kOj48OIANb`ai=7C$Cnv*i9Gu&K z?=pmVFJw-jyz>C2lCGO6VtXz2m|&GmZmb8PmXvZkclBL!zC9!7ZhhF55p3V8GuS1N zkdoZ#U-Y|fo-pjCRHdLaM>S=1c`>q<>(l8=$mv@oj^C^ag=U!Lu{5)C!mGV$Kag zDS2HyFRw+^`_{UB5HpW551go%gF#U@xkCS&Z0RM>k&gFkR9$wBOtp40m0`Guqk~k` zTj=zb-aX_df_xP1&C&6N?n&~lUJg$OD$dr8&cW&->qTEiCrjxlFhmOmOHx%-pk#_ckiF#XkoKjfO-;Ik8fY5D0H zMrfC;U_d*s2+pZstG*WXrP-pPh>YD(g{=a?kx^_rWwc z@^85m)LLcv_jS8d^4yr<*6n)q(X)B!?eVCt9%wX;CxH0D)*OiLcnbm8?m>pzB~n*o zK+_!T^dpU+{AsV>?@cH}{q1e4c^y8pi7xcv5HU=ecj10doI|e+L@GKdl6+%cZyVIP zIxF8f<6VJBGrfk5Rh6O)aiJg`^DstRLYVuk8V?;T?<8OlBHudeSUI)45$^*D`b!~5 zB9hD0PF=K#EReSQ-fq7lolyFOXI_CxVt18j;ApZecY9)ivnTHPAP1uHa1Ni-0%zSp zW^bz1z6R|oyWAV8E>Z{_*J!-JBKN8G7|>Qp>!8d7@*)dfOY5hX{($}yTTGun9*Lx0 zyknD0V?+{XBQ5)E4TR^`!pjc42QE$37C|`E{ylamE?cY+`MsRub*FpF_Unn^q_E^= zMbQz`JO*0Jn+3Irh=zt34qxM#C;Ib#iL%LbDZ*r~&j!X?=q;DMD6SJ}iV7a$4a~DL^ zg1{+<5TQNJzv$f%`q*L`GzIgTyw2Hj9Z{nDuCRjqd{1Yn(rRH~z8z!e=&}!1Rpoiw zbPB%jRLs9TTYzjvC*!w?oaQEzchrpP8v>Y*Iqc3bRf>$vl7ga{}|*>VS}( zgTT?nFTi?v+#&s%{!;u$gw2<~rZ|5Nv8rd3@SZlws%WgpO4^?Uj(VmO$fPvb&pY+F ze2i?&)yV+XV&&kLaq&ySkcBT~qG{XlW#Q<@xYKe*L{9z?h^Sg(>q`F0z`hr`vY}!w zu1BIDMr}HTVAsaQaA1|6OvM5M=x_UdfDQ(JIw-wM;&{3GoGZ?q@-N!k8zZ=IdX#+%Jp|E#-0YRF3Q)N0&!r0^g}d_TXd__B;M}N3a0atc!{_Sc1ecr zWrGCnuB54Ea6M1`HKcC64kvqdX8jsp3~;)V-kMy>suD+jyV{Wn7=H3 zi_i7Auk{MOC1P?E8}z5bYf0CRb_^*>T7I^U`>n98iA5=5(oe*Bf)l&@9%^Q;4lf}1 zv3b6AXaK0jZhUB~F$L9nK#)&#v56^ygZnLIo+1E)cuCO3=w+em20GcY-?tB-ztbX0 zpR(=p1DvQR;_S5}dK;qyVdo%uQ_q^c^98XUnjTyxNNNkbW&u^#15}6~Ing*NeL>E{^Jlf10_+I!j$Ngz6+4&x$?A+#0 zzVaNhJu*5o@|))toxYyBaR*u3;!nAk(#!11 z8o@FB{XSjS3(Tx1eBqr{sS9!Na-)PIdlg!)Ln7TPt6z-e;Y=iOLYDC6(+x_^m3EMQ zHp33l$wwE#K{NxEpu}=B_88N*4|eu-?!{21k49Dd%5wZ`If?FD4@5nDr)25hJ#kGL zk8B4eFC(q-qH3=M>jv~=u#6{PxTs;bf=p=1ldxt;U!&WhomZw?2OG^CcCx3*1mJ&Gv^k3yc8HknpBSw z&j5Xo+(5t^q0eRZr|KE!>zZ;+4u{r$WYKfx97J$0SE$y(;CD!uIk>-}u3@|-pF-aj zNetT+W-`cjGa7Y&j}NXZnsE#bj5A)1f`mvHh*;eo)8K5Q4^LIpc8DLsg~5)BU3%d! zSon1=C}2?r%FU;QRc)N7XawV6OSQwhP|Lh^c~|;Kj{Act00Z5g z>`BsKexP}K$&~|UbAWES+;~t3r1eXJahoA&^;o?}t3P_`Gm@8IzQKx&?E;1F`MDBBg&F-3A#)iujf!b_m=i*k3bmYS4JD19 zTe{smL4pL&5nM0;el`f|+hkaU3VG`!xaWs<*?^^i)fa~*_pA?OkpSz9w(-Q&M5Xy} zF2@hxs>zZ=&h45PXhNtWvD1|L+?V}HR{HgV)^_znt5hGq)8SgWsLkMl+DKSu^jhX3 z{&0z2QEG3@eFGTeP5{=n23N;_(zPpXS7NXuS&xQH8VjNO2d51bN$eqxW5mpR*B_j5 zBH9_E)F}F%TU1omt_)}EqBLHFP1Q+pab|_Zu|B#fgJQZ}V>5jm_)&)n;RaEKH7BylAiyO1ZdqV=c@L*LZPy*vcg_IoABjLk%Q~k|&^9e6Lkf=js4~QFG-v66 z^q++xB(4S=OgY7Rc;A|hLoCjV7feN~h3L=w@zR>v773#0`I=>d??0|6O9YzzyHA1q z^bwnXVj9|RcgS`YhnDzgZA4h-D+!ToS=`m}f}c5Ccmov1AQAAK`?e6~qv0nolYf6c zx$ls@4C-)ud`=4Z#mz_l7l!BE%j2MYCoB2%O6ItEOJwKb zMPjx0q5JBvgY+@`71k6U7=o21(j03W*Q|;OweUDc1>tEdxmz_3+j;LEH-{D_oD81K z%eJ!(&S2|_L=0g0X24WUe~)=kINfYJJn{|z0esi@&EMrg8bvq6lP8!81UcJm?;;0QR?rqq1r)B!0GX zki$*>8h_7xG#dHhJKLciU-BCKsj-W9#HiDN#6h8hm4|NfQ`eiv`sZ;~fdES3Hw6X% z9?+DiCjVJ1**h-@lJSN_^IP8?Km2+Z1Ru`>n;h&3Bv3j^GDxy8MWH7djPhj)*lQ24NYV)F z8Y+#5E^sN(+Mgb&agObNr~`2nwHOd1lzDOI!03fA{m^iv@`NM-VVvp4yjdR7`s^8N zpl21=bU_a1mlvE=)3nTE4ND9}!8KMY(Z|7a0D;nPZb0yFv`724>y|q4p_h3KM!ivlgE#9C3a8Zr_*X`7^*m*v^{<$fT7zai6+6y&KxzC+p6%@XfzubzOM!H zAcR${T$I}Y6o10Cye#pc+P+y}O>4iteeDp#cDVrEynb9p%s9eZXTq^^($*7sHtWQPt4y9X( z3e7KMTn&~IslZ-n6|3`RGGM69bUN*fsdP3*L)-eI!5{Y?CuZy?LNa@UfYH7W#F_Oj z)(p~Cs6q*}z-X3d#2Y34QNO}IQWJ9NNmq>N^F;^iRy=l=;t#(lsvD*XH{eaTMwXaX zz4U!LeA%ThD!0Syq45_(JqHwDMI0sDW1K1xepH1y$zz@cslZ#bshfwPAZWuydyvhi z7Nhboq1*O9t26*K^(%s(t8}oc@G@RoBic4f-i}_r*HdQ3UkN05pE4P;i>}IVU5#T_U}TKwZUXjxdkkX3Wcjz?0ed(0(5@8&e*v| zf%V|P50A}@R#Bs|%ZpRFw05DMNa^ihR&>qx$7gf=o^bG#H?xl=|r%)6aqw(M8 zn1ItkMYAlYy}=smracFf!nGQu?&hS-2@%^CSI6cu^kS5@m0pivYelD#$}?SC=IMYc zktT?IcXJhQiRc6KR$IHFf7K88t%`xirPcMmsvuA*U8y@c`r^kNP9tQ62%)55w*lR# z2=|sPlxUc*IM7%@Y5{sMC&^swY$2YG(XM;6)|BLF$(e~fuO0go*?0)CVaL;ju z`n*sX&_C0-VA>>XRjeq`Wa|cE!!tUvG4zN-t(^#U{qsT$GH8AikncuO-x+VB$QEWs zn=jp!<}`DL>o@N?%;akH3env^CR`x)TpUk)Yt`|94$C72kTsu#Tc1O{kOYPMl)T_& z?MELSa11r@Z>@QqcI}~($=3G=UDv}io9BX1X8(TeXiYTtb~Y0^n2NK$ei3t$bT<$k zYeGnM&E0$^97;RRvJ)qj_RR61sw01ACW#gMg6~MXEh$MR75dJ1D+;y*)Cf zuTUI9oK@KJkE8R*ZWV~4;DQ*C(@^A`1PGXsbIz>(<3IE{wt((_?^a>a-0Wr};`gVN z0rF8$z7djnA=aSOzaN~b@EtZg{uHs;#Kip3>pQ`FX;y5nwh>vuVa>6hN&vT((`Qwv zf{#_d3R>|_D%a*2N3H@h#)G(7!5%5AVaCuv5}WIT+`D0F*sfQkVs(GWAuLb58h1AA zPdQiMiO06RQ}s?hp@~sbI&jgG>1u}`+@$@WUGB3jdL-zE%E}$$Z?jrsxv|N((u*Bu z<0p+#TbIwmWfd2Tq`BonCe1(2R{Mhw!?6u!z19vvUX2nIY%=c5l1w)6j4GwmJ>kpl zM0=)%goQb^4(70It5^(N`y)Dot0D?t_V!5$nB@v@K13jFv)u1)w%rLe2wT%uI2FPoTmgT;4wJ zjb;QEo))6yWk%G&oCwbS7CZmxwS|^Q08j|_QvmLY7@v>go8f0}3=66oVh9lkR4_ZC z34Z$gIKI{$xA5d~18c%X2Dc))*05XEHPw6LoSe|OH}(#NiiAhDG;086o`koHWnw9l zqKQ|Cxx_AC%6=x6w@O>$P%)Kxkf?+y$aP`xekYgwY&EqrguJJkSBQahs~9P00>Bl^ zT3Gl-5({YoVShUxU3q2>&?jkv!34}!Zm`=1lv3-?rNno6r*Jg@m$9q2uO}$U=DSdf z@K|QrA77-fFly9QBfFmyOrc=IqK=R84`$rQU)vznCjHKoi?}}O#e)D2JjGV__c_q+ zvnD1jZ{}GEuxK4i17pC(2b7L4#FNL}c(^F%Xj%`CQzm7J@6X710%-oFF6;72q|&H3pLkN~CT?H+7^v18fu* zId7X=euEgB+kI0Z`KW)4Bda18Pl|L>Cm_KM6}mkM^itBe{GJFZ00@ZcYBp|_V%wD; zQ0Vn}5vR*;001WvNm|{La%R$RF=8^M??uIg5Q|nEF=+pM7LLm-N-v5&=I@B$x3hOf zDGJhVBmyJ&yp{ni1W9Y)@x>?P@`IT5$kkM1&BKMmi?nW*HxUKd*)~8-e_QqW%?xM* z!Q{HE;1!zSptc{xaXXnM=Ma0*1@x+P0nJ5nVEjOpH3PTM>ibZ+MT4~%c%jE}ghepS zgbxg|yy;4zM^V>yo=+ayij?%*{zP z;lFgUF*^a^P?m9vTuBTckKf|rK#~Ecj~lV+#=KojirWV)ScuZ2=7_27rN>IWh>Uib zk^Bo_;_uD!nu({@=UMku8jo_XGhY-W=PWcwlxVM!_(5|lvd?||uyn*VYQ>?DqI|v( zZCoIxkyo&&T}yRu2qc0=;?ax>_e#8J1SH|n0kKIJkkw(bQezZSe`h^4dslV|%B(5I zQTA;ua{W!C+-G{ufz_VK3kNhfj?%vsm_C=LSB-(B{_iINsLn$tu9%#3RiTzlyENz< zNSD9sCo5fO^wF0SD9Y7SVLsD(Stsf2n{BEQz?}$2?ykA`H1)4P=X^)!=2L2RkPkx= zt|+8&`;#}-->oi3cAQr1vU3l$r~ivkRtA%M>r zzQUupHp?m~)gMqw`CYy;CJ@A1=<5a(MzAfD93;7{8yOf#w)lFXnm&~Lx_4=$Rj)!Y zk6yc=>oLb6ld~_$Ehy88i@vuW(S|_|F#d!~)&8D-hqaKbXIRYy@BvFh?T1{L=97b& zOX-}QB2Tm0?P4z=#vH#X@m<$h`vo_-mZmg`W3pIMm#A@ZIOFkEOIp|fu=TYc?gCLj z{e8Cq05BZiElCs+zb?SQ$DvwhJu5yx7nTQe1d!*UB`CbkE4L#s+M3KBPl4ZZ%s2Y^ zc?-J36xawf=wC@DI^w6oqw>o-qC6-H``sybD_U61xwc3lyL9K~c>c{FrbR+NUQ`tM zbB9KDFN&t zIrBB6ukBm-hNZejxs@_L8R~zg6%St;&~>Y2?5u_?l7>3EH}wU2D?CKVC6E$>Upe-< zP9kN#z$YM2HghUqy(3c!pjoQE;G0C2H(ZNX<+yiguJ?PX71$SzIDJ)8gonO6iAkFU z&J$!;=_E(|L8o{9$pDOM7W9+UYu^8c&`B#NsWC&K>S4cWYL{xj{F%QlisLMWk|Lb= zABrL*GwFTfG4A))zicXbU6j@&dIDxO`G{q`nsBtZQgXpi{PwiVPNp)df#fLBN{sRw}oPZTTsPlAC_5cH!Qn!3xDNy@>u!a zLvj`4nf-X;i({4fBrGN*E1uu_q?eB4=qAOkEXfwua}VX>^8RGvc~^<%W1al-191cX z$g}ev#52h*I@9iARh}`(mh$y2zds}2Z8(TMl0Yp*NabexoXX}F`nqv!M694ph!8hY z0PVbq>p(q8g3mJ?HnI^)d7Ko=O=Pt>{%E&v1Pr`~XeqXVv zgR{5;f(&hNt}ISe52E-xCIB}-m9IQ`6|H^LTV{|HGQa&^(xkbNUOx^(SzakYyJ+%n zgAIz%OPoF^5rb##szCggT=&k;(zi8XCa#qTJ=6dqQLRtt%l&LNjcWHjh?Hi9AJhfiV#3QW!oHMl0aZS-pxRes-pzV zo%1zCP2mgU(BPD)e8;n6joQ4{abDPBl<@plhXx6*4o$tp3&`99wNsPZ)?ME~Nf;yi zX!x|1{sdZ@=CqE$^hy~oNxB0`lAsd#dY0No(sEk)mhUvJu(oawUrJgf;Hbjs(y3)k zDRmKw5>WW2qJB|DBfVqGwjDeVg#o9W;=q`*swlS6<8}MnZ1>Ye&~se}L8G^yozYiq z^Ler>F#H&oZJjmkR5(REth}4&!ZGFjkVJ%H6u-8pglN~dlV%%4MIe<1sUU;5Y=gHz zD)KIj+PM3E7)DDEw4V8_ZAW7oBmvC%6iy^^YcHZsrmb#D?Qbt|ibq-PWA#-FgC#TC z(PBMY=L;7l3J*c_4zhbbmP6e+$sPue0e=5Zs&xA$l7O}1iKc~Hu68$(F=^B7ws=yx z{&K9^sJnUXAVFLxKNq$b-iI$JcP_kKUe7sOIueUsg$w%?rm<**^^0)*nbNo9Hho#7 z_uqYR9F;7sC@PECeXKZ(ZLYO+US{VX;yv8*P{od z$tdl!u<)ArNm#~T=0g*hQcTNzQ`K#@O;X@HC)jkA5jW`vAssRxj_mks1k36;3oKuSzH(g%0FQj@zKeBhaqh9r zU;{QuT%8b$W-8OaJYS8mJfTuaKy58>kSQ8I>)O@SBPjB#>xwy1UJ|92T|QsuY|hy@ zNtL2|34Nr`s{29mvw~Qy70dS6lzwng0=XTG>7&n*>f;TKyuJo=IFEkm-=1Vv%;a>p zbjJb^KA?&#KQZ6{|DzC{rD1yx0Kkb)aX#EZ)ShiD5OKG<33II&&+9N%6e5jNZ8h4` z1uK_mYAMcQI+inYcIL`!wkH_77Eu{PMZl| z3Ca!T#@|3W=yM)c>0^s4sDE9g-ajPZ_O>vDxmF-XBhu=({L*+5@{C${Powb+B|Yzg z$oMM+rf-(ABHqGHh#S0iPfnFCEnbA?9VOPwYrXT4%w+TiMEe$=*4MV7c1)9+!y!?Mv+Y?vT(gwyw@+ds}t*-JxbhDc}HR;?=ea9+3RhFw5^&F z?`wr!me^59oAyohu2Tz@Zewxr3^+N;pRCkQS7lUg_T}vlaD{gmR2MHC|Kv22D?&Ix=#)Zlo#;O9YUbTM$^jv>!M!xNZy6G$~5NCD7n zXoD6QI>7NeQ1A!rp8d54kj|CDrrEy1tpIa zH$|d?Zod||Wgs4Y!Y$6|$(8qAsP)M#gd)!Wh=<^gtP#mkeItugqHRnZ!Vb6$66Ys&;&qSM0$R)KTwy1p_DJp?=MF5@{f5yA2 zfA7ryqL7+GniIv0#biu2G_gB)%|1g}*C}Q<*Z7^fk{xka#gOFF%jp+6>SBKdAA*qM zMi%oia{rDXvpyndavw^ftq+YVcP@37v_ce&TzU9GWg@o-disia%j;0Kqp=$DMDYP1 zlZ#*OkdBCOL`hHPw+~8j8y&}^YBdPS-cYDJp)|zqMaL2y4(Xl8#JeDW^00z1!E$ga zi~jf|--W73gj%*aBt_ zS7vcp+PWh)e?!Wb?XvXyScPJX#qcxjstae76LASJAOWyt`=<)2{Sv{Xc>6x}^zU5R zuEhHOsZ+8 zOz!A@#ZsWZof`S|^5<@)GBjXs2oWeheibn-gJU1Y0=tZURJq+Kw>zs@7e4Xts6dBz z^E@|5+zd+i^|P#pRs!@Wj(x{NJ!c*NI*#bk1wIo%{ZL;uRFjl9R&=0oC8*6|NWm zgHNThYn)`O|LZ5=Qq~j=kAkuX;qTv4PgSi;Z_1?Kk>o_~eX09wvx=-S6*So}*AK&}r!}zgbxcXrF+UHEdG%j>|dDlN!Bme~kWY$Ppe#m)A^IDu3a`)hlixI4R zr#scR4=+4f-8s%A8X!FTr}voW@J}%Ki1dRInh~3vumSOB3hF*BS2*e@$^OJA*;|v% z`7?`{uVo|Qen`R~oJc%jsTRLEpnUmJgeulv&DK12Qf?$3?35zK9hOX=n#f{U=^m^Y|=lK;^&Ipcz&_R8i&Wt!cw9&(#dJV!Ck?HMG zqiF@GAjcQQb6Z!hMhIMt)QS*c@ZZi^-qZ&wtnI2XKbXgh9Woci5KZY=A~Jd;#vgPR zURb(pqW)Pqy(v-(GFYY$8;i1G@ABjh7?Yv$_v{|YeT!Mpxq(vz*mt;6WzT|-32skn znP2_UrS4p}kT&GnF1Zcif5bj(i&`(Cy1p4>pzv%q6Os3v!crvyW8t&N4@((qFlt?4 z%h9zL{m}Y#xTyROKG5S)_CW?-INfrWFw%>D`)fUrZ1Pc4OQOENDn%JaFqa$M32MR- z0ao#4`+JU*lsXJ34i2KwWDw91MS5ldEfNq}_}Pe6?-sW#HLaj`gt3R|`>g?=|FZKX zx(WmY`7=nieK6^V7;yM^9n-I}rmVwV*f$(s{uYrSQivOZ`-?_>LUCEFvN69DHkdp( zfvxuCEB5bvdRvUsjWgjbYU$4pHd@74HA3j52+mV84Ha8)X@)?_mP;igKpwDak*DQ|`GF=aZ0fCcCpr9u_;^4h{)% z4P3fGiwuk(Dg`(?iuKG^7G+NWcEP~}w=jFrQ1kS6^*ZeT(8K;!dRzm`h}P#)2fFT-sQQGF?tgD={j3n+4Z9!sP}EN&ep zcd4mRs|9jAs2MBS(E9#fb)^WLybrV4~ z0WFBrKCa*_9cT_^9UQ<;+*Z`_g9Hr^?Bf7;B;UMvDS39XVc&mm3mU2eWC<43lVgeo z5m;s{CnqST)~zYd{HkV(5)tlQ9w3tHU4obIkRv~m-zBdi@<71N@I{SBbibE8V7XE@ zkMi}%B@qEZi`5_@y1pw2q_QGlMNtc+R&i!~3d|Z5@<|MZjl4#nN?uebvJQfoc7%i! zy87J|x7G-QB5+pG^UQVv%H`)w?ki(LZ(@@c2>hx^VWoJcBKje3mCI=GTbKuO-Db>M zh?)F2XC>V0B%$Z$DH$9^`ar^thlKzsv&dcICqX7W#NrRlMa@X;*(M)mTdOKfDREUz z;pj;GBrHxo3It`^R&b8tWPRJn1_TRZWTynfNgVGRA0~;O{udVKB_67NSK0>KgrNm9AtC;VLnE90_0|vm{#OMCTP@3;obk-r^@P?-cv^RwL4BOah=UDul(N~Yoc zPds^}Cg~^tHqvV2qAA^*ysX3MiHyt&0R727nc(?d#mev8=HC30M{)Z{N$jC6AZj{T z!LPomNAeKN>8E4WaVxDMo>{uT-48!Z9rL4Fx2-i^FE#*t)=${m?C3h`XD?LgFUln; z^$F10kt!(cJ%`vTGCx{guCa>rrhp%;id!O?Z)DtWt8$jTW1c zoTM%#66y6Bg|Zw-ewHtPp-!%Pmc#0bpeB+m&P9b$n*O7VbFlZXDj{1^c(O}=2c+zo zv~!M*9U@WU`kIo%Ahl)Sw43O8gt^SNw4}3h9GVLpHTpK3^=OTU_xw`SyQ#OVy+8C1 zv&aPJkkV_P^cPCx-q{+1W1G-U6IFS;*M9EmK(>aLa}IN~VO^-`sOSpizqtjilM{)P zgcUWVC3<2P*Ao4_@5hfMU6CrjFD@_Z;oJP6#$l^%&uNRM0e7c{lNX(}wqd%P{N~${ zqZAr14koG}Y}d|eaQ04#6|$yl^KLjXlUy1#vFAAC7D!juFqCAlTiHXx43y6~iK z^bo_50(t!;`p121?vpV=1ldeUB?+uJD11qtA^2}|j3>oW!y0LWr?mn!KNLlKJF+H_ zWabW!_3EmJWQmWq-S-|rsh@IXZ=I9kS{Q1mm;zx6c0gqAO2c0k;UJ8}??4{A z?B?G5PP^BRX6q2UblcLkyjn&nAmWutj@YoIP$I$;Cxbr2(1?7Pm%wY?a98p|bp55p zfxXtRT-D)%DBB}Alntreh%wyceQ*O#M`7fy4FcwEE7=P+I#<3#HLYy}efJD%i6Vk{ z?{`G(PaCWFvE6;LsjomdI`=D)jZkBPD#K<7!#&?{)9){glSSA4+PbN5eCW~y#bW2` z3TN#AAFkVBz-kf#QcCQCK0lKF#2-a}kcp$EPuH2c$CsfYJ8F=Mqolk0a0={Lo@SXI zhGd=ya#0Lq%8qb0-ren_Z$rk{=27|L^quiYNE)-x+-0LF&T+qsPFp6@EjA$&erwpP zX3smnhbVtB^$AXlBrdKU-=XoUCLAk+XsXMg_q}Xpm&!&NDb2}*e6W=~v~yq;sAjc0 zoKNnI7Eqo6tBojM7Mb#Uuh1f-%G=07Aqh?hKo%_SJ#5L9^~&na%#Cu7oESGZtf17>2C5hwCGg!mHo{4<{o+D!YsvhW-> z5|3bI@aJ^(chk?@^D;1bMFE@=Na|T%$-W7J6EOKUucHWjc#nyBnHL1z)+<*XVWxkm zC4QWt{e6BWddeAb^P76BXzcLpTKVG(B1{>E+H>gP_0sZ8lY@v0Mm?If04uZegBF13xmtoAmmogQ>NY zGqJ-tgiY}6FMvaiXTWUXoJQH|)sQcZ2W?v*$M7yx`L$w|8E@1SYmB-@6AyPV>DF?f zTe`c5>wrp>34);kRxn6Y7VWSuU6`ObtIeX#DbJ2DvZ7F*XQNfT{@J2xK);|?OiBYH z{e5olvJa#)>nNg-ag+37biCUBrU=C?f;KV+=Ve{97Y=w^@_Jm}BoCe8*P1N>8L^I! z@|QD25KP^%$n7M#m7efHzpU|kF4ZsGT0CcN!ICD^Yq%4f?(6fBQ!{WrQVdfRc;Smc zp_SnC8k=gy&7h$2{Zhw{qFA`ghk}Ib0$p|hwM0*K6<0W`>)KG2+dxA9Dp|I2+vFr4 z4&{%Hv^0-GuR8csT8&W47~wa4{1z8wy7-b!K3M$Egj?YV;TG+FLAd#m8JcYbrgi~A zd@jIg?=|vv!;gKDAN(cmUpY)wU9LspM4T50J_~MnwZ!e`{je4V!UATBZd5=T`aF^d zY$39kYwux1IM=^}wE`T-nwKD{eVH@aH5ehhx|yVySY8(qd(GGve)SaJX)5&07JflE zbwBgjqn4b&4(HJH&o$err;1D$6K0AX5JJXi=N)^!h)?*vR;b0Lctf$$ObPjZz`@>ALZD%5YR_y1Y zJ$bQtaX7g?Qd*r33*{-j_7FH+i|z)^8MvL$E+=W5cvw-yEwneso?GZ^%0JItuke@A z(hIWX7IglvZgPL(Tw@8mk%BAB1gc?zQXJsXQ)2CxMY-MBr2@K|<`{d2B#z{VIUHJe zzQ{z?6bLlj3?E>PTMuj!(O~!#m~#al95{4Sb!(|8ec{v^+Jp`L*4X!f{OGW&kthd< z*f1r+LS11jQMAAmzCalqX5RD2!3zJlPC-ERMLYaPJD+U1{R)DNlS6;2H#mj+Q~?Fm zm{xO}AF4^WJ$9y@ZVk^of04u@GvW+I-?Rw{kqk7e<(c zgf(f~p!SglSF}%e)d%_z!&fqBJD&2yfn$!~qvsSWsm{O{FFz%XcxTIf*3H%L#gON3I!niDTQ)iPK)`{Oo@34=8f z3|{N*=pMnk0~p++y;8y=(}Z>>A-_qTmZag(MIp)?BfPeQ{|IHrA2zaPV5}e+WVf&@ zbu2_$@#sI!&jL*p(J(;G!CD1rF)tAJJZ#KpI(W7QJ$*Q-zb3bN9&JCEPVvFm`N53d)G zf!)-c z>UO?`HWHF}7q)B_k3ig!Ik?h3!5ioJL_ftzp;c2tlvFGw`nbXnc7vTkYJ|g=g7JyX z9vA3t7{gg_-EhtTU75;L@`TS|2o!>VXn}BPr0^i{sJdX`Z3OK zcSA1o?T6~SD7v%TD-c58XO@+ne{TS}em}`?VxyB22Dci>+hbc!-BYddz$T#ME3f0%=h$auK4Cx@y4%se zo)=%+CIVOX3S(2c&>pd`t8L`sLYoH3=Qb|tm%BzEvs4;|45k=`x@fTFG}n($;Q5p% zJsc$&;O$K;?(0d_?}g*k-v)j}(KBcSHZckq-a&aIQtY#U%kxSUXNHpHw6DNUc9ZHHn>47;%h{AnJl3R6I{lgZnSNXM*jCXtxg- ze`x(vbM#VqQQ%|CUUISfG7ImpJ&#~0RBJXD$--Op%#^?wf>0L`vy`Fd8rB{6vuqFb zu?`?mYwv@*#Xh7_ZoesJ@u}IW3(5?$tC%nr!yo3urV*_vy@XTqME%^Qd_1-tz7wYyqwaRnenzl6XnCu6K(BPBwPxz`0`JS9YBcQYlu$0Q& zYo`q&C9UiH?Jf31FNdKCIh+z*Y42F+9AoBx57J{s|HK>fBf0Almj#wq=_u^q(qz?H z=R$^Zxilg)Zqg_CPLs5vizy+?Y5d4!zea)vNCt^H(?pLDAvqXh^+uECc}}Kq_BXO@P1Bf?P&CszypOeLt206@P8+=awF-A{!-@MWMrR}r~AmJ zaNz*PRmzTrRGydTA`xV?opHS7sK^7&b^;$P#U)H0$YUD5XhH*@vH7iFlY8Tl7~KWo z?=eS>!N_!)@MJ?pQrz@hTytbd_UQ<%@Cer82q;@bnsq^fuO^w?7GNRUVhd*F9by(Q636>f%$mkH^MMM zeMzHPBmgZ_uqNaEA(x^T6EJf5{h}~>3%T(^UiKdgf>6-`4(B&78G(3il%Iq z&<-Ji@rynVlkyB#=NSB+h6%_?1|U?iOlQQt(}DjZo4ycJW^C z;Q5hL5_z6?QFmczha(D-R!ao9J(d@3&DQ(;%|D&ac|ct{LvQ|EH(j-xrDo5Wmd4VR zNjh-m&180`7i^y{CbcdM$UKR>2hhaPqdW=_;4Rq73G)Fos9eEI*V50R##^uSY57cM zHb4Qd3bP(5#ustfXeswHd?2c+-)Wx(NsnL(psVAa|)gk=bPf8h|8rEb)fzC%VHf{wp< znLwvW{C(Amjz@C;;a724Zs`8(K-ictZ6wPJM(qoX`imbJYbn|daHq5YMH6NdXn6zK zMy~e+zh#4&&-BiO+ndlLvxBsBmi8h&D1A-}{2n~v+6v7D$A)M;YR$;v7LsbwfSkEq z3tP6gLlN!1gfLUe%c+RIBBkF^}bI-%>+(-EV6MWyf z!YQ_=2;4vB>!#eRT6y5*mao%~T^ba5VKC{f4&GS9x^_>Hw+_=0ytxTZL(zZtXEn?2 zKa|kXF&bElYp0W`c05c8NZ+EB;iMl>ei;}l$ zNFJa1r9eo`$>AIDbK6K&1qDwm|4X^pUJOF7Ac+^|A7A@IwK7^@>5FVZy&> zNNI{vXuX{^aClNbsKk+Mhy{+jX_@n3U&>vjt z87uo4HB$`ZZGyajWKz-S*F(0hN2ceBjDpjZ_bRWDSIT^X;>-D~!M*~btwr1hN7!J`vLacjj)$=lYElNyFMWOj$@@riT z8D5h>v0qEqtj`WjV6qxlo!uvH``HfMDkz@IwOJL0P zW?}-~`CWHh zbW8c*B@p&&oo=FuOMiyYeIfl8_d#JDcbICv*+#aOl;tzIQX4tynr3SLX2OVjeH&fj zOx!Z9!nA<2@NLFP`f0Vh;l{Ab!+jRCCNH|EXUfd`i5rnQxi~`0BU{oZ%E)v6g@Ro+mJ+nJV~{(gRRnEKhk?YCCi z6pxBi`*3EX8<_k1+M!T~jxJZ>pawdpP|g?qHI)o}p;3j58~W`Y51Fac4p_|xAp?Bu zBmjLM_}S6G%k^@V0{&4>3%$kCv?>-W4w+f6e9uAkq|k07*iM< zK$2H&JIsLOAKmu>ouYIe5GDP@aKC!#UT^_WLN)dP1Cla5yF;xgWO6u6D7v5|c$$A3 z^S(}iENOO-2bFXCv!7Jc-Dq>6N_kg-r7QyemBZVFHYY(sngj*0Gh9vfMIQk7hi4>> zrbBEZYRd}F=b}RGWo<+(5)kW?1(mJA%2E6>2*P2k=SP-c#hxI@IapbsiS?ksJRFn@ zHBAG-S%8K5B3o-9LUG20H^J_IdX(dmGX%%snKNCR- zn}QM?r_4GN&`B$vrfxb#OibQq2V9(>BlQMR%2?D7Nq=7iodypyv{-HCQ#7WVtg}%& zibKO`8h;F0omM+2l4N56b?HAhFc*HHKG#B)fGfqreN^nX#k#d9uDE zTsm+ba5DHbT=Jx#Pjvc=+NX`tfbqySc8xZik2wKPU=CF?n zm_>HQij_kj1Z5ND(G#PqAMfQ*`v0UAh#rLU(}5TrL?D_UJ@Pu-oKrnYW%58ssPG<` ztXgMp2Zh(>MfW3F&^%>Rspywk?HT?)eRs6tk!9yDN3b_tz~}NcookH`2qc?V1K3jC zAH=U}Z|t3~CZ^(${iZ9xn^JC5gQRp4CD)vL*%btfj&7!g0G{o_w@=)Tw#2H#>oAKg z$5kd#l1KsBWnHv)dyfm%#(dD=@63G+SAHSvC%)TwYF28QaDeJZy)d_izI&-R zUiP??c?jwDB^emKp1M02)?&TxiqnMGe34(`0rz@5onL$^2pUkzK@HUO1J3~X8NFFl z_Xkzcbn0-a+PSk>ZZiCrF4!Shw;R5y#|I$+{8_b)^5&dmlknT-QNQ?raV(dT5DcFLXpe-4qCW zB0CM+9=hB~&K#gYwl3|nl|fFGE9sJMXnQ2?UD6?m;~3I18MH(e9>2LBu+^ycl#35Z z^@OOv>VkpH0Lm(SHN|bw9cJC*ONK!h?_>PTS;8l;WvEyq&EqIhdU3ckO9D!l?g+eh z=|7|ok_|z&16buJeN+!&k@K~`&IzkMunTBL>YiWfDwTD(4oVV%=f*gS{iGF+x?bUL z673Hu9HBU874e&1NS8C>>y=|l{?s|~5o6RclJ8fDq++WCI}e|sk-69#oLDV>mS22# zIRL0)`~K~Tu7=MySY=RNVJ@DX&VQ$Y#uC}h!Cm2$*QV%K+~-xCMv9D}=w3)82KET8 z<+OueDD*2V3{0LKJsDioN2psDUVFO?EY1a;A8&p#t9c|FjeA%uzEQHkSUkP$*~)|i zCz5aPxF5xq>F1<6G?QQH|Bm?e*(IV>3?3;e&1AdE`O z@-3QbjmZzw@F%}qKlzJS{9wGSExWG4c%nPkVyCew8p<>l&>HmR9lD&D&qFRs-;hkdEHf+%doIbQQ@C|DTC_&6vLG4?iXox@gX}CfaqjMDbrjfLRVHf@MBtX1$lcB|LivZgeQ!BN4#lKPj*V?#9fRGn^2)|O^lpKCD z6*!e%OXQw>n)D2Zg#N|)u;Xbl{sc3wCyVS$cREG1SO&>;%w4>C7x+qqu29=f#`V6q zx^W&|_$z)%G-Dj*rs9gw7N2as?DHVq$8(Y%c2AMZj&3J>9K+9#j0Xm03yf_)X?5^6 z>>t(PWS~%KRZX2fX(&E%F9ZJaFMJX<7P(~~MTh;a(CDVN#pUz}zIp{wS<~yTW)2`* zzmVdy#Bc&LWAzyx{NXR-p0)vh=6A1>-TRCkhUyS;rqS_ zK^q#(CBuV+XOyQ6V%S_bXm80kM$xs91Gz(K^Pru_j}aampqkE-`Q3bbUpMkKMOE4| zOWOxFwF&u6TMCzj3k|9-%h4P}5jD=>jn&E*XQSqKI&(Qh#XT-c*KZjk57gUl^0P=y z1ZHDDfwM6sU(r{N;fdrmDpt5C3rD9y?fbeIdk}k4U|mQ7xSrE2}^MX+r$+8HXHlQi~l$b4qj}~t-KTS{Y{NN^7H^$-T>_GdZi0R7VtmUf1g>^ZWouITwCJ-2kG(;RWs@ArzXDh6*Zw+Aa;7O%(uuf%}UfY zcsZK5Q8@cOh~029)wo9W3aIAPPm6lhn~!Iobm$=2>7^99y-Ow8&z}*lj?q=oYf^L` zY#~!EGm2m=^xeO!}Y4ulq-$jxt3N^7ZrTV3m#YZ#< z{g-dvY!DqTh?MQPxv15K7#hxyOSd601LFTQ+7^15LI^U%4E+cfkGW`EqnGfFlGrLe zjZyxDfY`eOc{gtU>E+XT*Hvf~2GqH{b;dAyR5{&eP$c#9{}TsKff#n1t_Aq=(Lls*hkw_ji%ZtopJKGAFDeC#$R|>eDlCqTPrr zv_TR8;WRN6(+E~9wV~Au`naepxpo~lz$G41Tj5EvVL!cy%1z+#lWL^NoCc7o!zt%P z3EG;Y`6~ICz9FWShhxt@-wGSi>ZG_ZNyeFA8W6lvp8oG`kK41rFIcusIT!KQ)$c~` zz-~UdZ?`Da8;Fqt8-Pe2D8Ij$jKT`;v|R!M#T#i5)=WF9Uf(G$qqCl}00Ukvg+0Yz zdu;Xq3RI--6(xMSYhAi8*KSWS3;uW%SxPQ@-@}zb9 z0P@XxQ;kK^f?6JH#^FulIh*H4F6%miyi9>xkbLfkLz^HXk3QAQEQm|mSWcobv9{ZQ zl9LOzgA78^f>We(s-xUd)GH&BzWfg-xRqhMFBi$&NoivYVDT1n2VP^#3&4UP$I^(o zmWm?e3lW270BV!5-vs&KAh_ZWH8j9T)W;G=NXs+fGc^||b|HddP;fK)N))4vOR0y_ zM2EBscq+@HsD+8<-b%nuR}w(_jV%y7Lg4js5cg<*=R6xD8xx&nJs3a`b5I$gIEez( zqb0Q~LCIBsVlJ1{tS*vGI6+~zpUlSqJtxcdS(W~uDNA79N=3s&m$X@S~nDd~| z&o2=iwO%TlEAa3A4BWO?ZnCWC>X!!!8S(d9Ir$}2olKk&Y3S=KC-;|i%X%E$1vVGY2d1Qj*QyM6dX571645cF#ojWH-?r|d^OfT73b=N3EH6_n%SnbJI)9 z!1==WourwxCeS0ujZ@$msHb1Tp-BgIa_+B-vlxpZ(Q2>NiScR)Lr0OpCAC4Zd^wjN7}Il^#b?H*O`0v@~# zES;y1Bf$%-m?k&Dg*b(5aiw|V1LKj7HLrsQp)fB~Q&Vd71+K!(-%#}2%Df*Sjw?A>?% zp#cozxVXqrG`KsNS=M=pc+0}s=6 zP-)sjJC0R@(1oZ+=dgv2C=pJjcMgBnk&xFKV;JaXr(`fDV-sId`Q9&@s`TsCJc`Lg zW`{*#+7b7QxrMUbOMTGofrO`l`c<+Pp~;QV2=DwS$%PZ*6XV9RO10gv>hEwXm(*wX z?Fy21AEr9OKpT17*e`ubG>gwgL=2=g)enzB8uLbzaYj(fmnL5P2!FowV`iPU!BSC6 zE9BJ81^#!XI{<_PjP)o_#Rh4+j3#YmegC7xZ(Mj8J(Kzex8mPBldA9~YWoW(hpris zx);g=W&!auLsSNRwHU%E+mEQ0S(dFaKdBDcF{bbLs^1_(|2~B+VTv05sOpGvUwVGM@_AHKKJfP3GNxs!3Mz* zLp2{q^<(ariiqp$A6vI7<2J%(t9gA~i#lW}^W zEld2846M1{o~ki zK61l0?<`mOo%opvo`2)GT?Q@DQ9XT=kPa_T(2Q>S_`NlIzx-}m)Wo7KcpiIw5+WX--fb`A3OL2e13v>GwO@*AhDR9a)dLv7JMsEcLhn=UI zO*wZ(G!oprDLI#x;8ITo7{2`CuF#gxiVGb~ZF}>24HCDpPM*kqKKwZSN;IzZY%VOh zwKVrzF~I7}oewrg+oz;`v3*eNM}mNLhy6?(cV>mNYAy5KyVh3ZKo$nVe9({|jj~%^L4@M%Xf((VT^AZ@)Y2{(PE_Myt z>NGy8{vX#BD02alJO^Zrf3XEeB4bunlnHApE?n0ai*VeZ$p_i5@0s&)OgU<|UGzDP zv=J8U4zlBMRZmx3x`u#8-#}2d`v3y07GV$2Q;VOri8rO-BA=s}8BU+^w%dTcNiLBE zOXFqmuA;WiO-pI#X3W@xAQlX*7QwQ38hmVjl{mYQaFS z%SqB0vOFyY<76RpMtwjVf@zQ%ka4K@m6T<0XM-o%D?eQNO9(s3X_k1MiJFJWk%cqT z^B$P*ch!P3JUqZFm_yXZkcv39g?q z+RaLalNXE25;Rft1H_0cm}xu!-{qncGtSdSO2djRy zEq_H<@)KgARqZWOgowc2zOztIL@%C>2iwBI!7Q@`9BX*~?Ku=c06-J{(hcROtK}q> zh%Or(iMw5;tXQTP1NAV2l4s~m8HGhN{*|+(5 zC{p#CXh<*nhHLD}ARO5GEb_&`e0)_dVJIO%uanIs@pm~R{_mqjpO^VRI0^ok$GIkR z#Og%4S7pg-Y54kpJKt?>D&qA0D*TFmkzjZ)oNO1=epdX$S-i2~m#YgRD66Cq;Ppdc z$4m*m8y6gF4Zr9|Z3|QNeK8n7b$l>``mVsTCTsZgyiOwTtm`I((TzIPnt#^+TYfZt zd7kTL&&`M-&q`*&0PXD@UCOtl)?agT`TsuJ5JS8wS$_(F6#7jg=Zp35(=|LLzFV%~$%6V{$R0nAk6hPm6!J)NXa=moQJQv5?=icPMscImirkzzC) zl{f5_5ui?p^^)WLvFM`)`ZffGP5#S z6=;JqQRF2#W(|0l#T>wwK&#;l570D>VJmHp$?G&cNs?iyfDQR5OXP&Aq$S*CTY9L@ z66@pr6nHQ6A{2T?;ES`wZmDk_>)!#r{!cI>j%r{ptG{UbY?p!-9uR}xPQ&pc$8@D5 zI(e_r)SK>>HCR_h?SQ8>=9@&FA2x4FZFLud5pRhX7>D>ZOx4s8$8yPN-}z23)OHJ7 z*?wEc8CB}9Zy~*Nh-3^2jq7Tq<9GK1&xu0yL_jR(&8!`(^s0UQF8bEIhzH>xbYN5p z*`uNeC|6yCccnIbZdSAz2{Qj5#le*sP+mjkzOJ}xos?J*1%PZq(Z;-Ja}2~oB+Xb? zhtKwSwU4zgHyCs-yhYa>CPPdowq-FP#JPeEAPG$b+z360opt_Q9a$nuk_JHWC=VK4 zBh;Y!&>Y0(kyZ+znwcODAQYjxssF@^3`aN6 z%-M+Z1#*H=tWBe~El9VXY==M?EanM9+JjOiqO|ZQ+BhMQ`s*S4UY+8eck}04WW&0)^)F+;BAvI^ZLngeOL3D0 zjoaMc524?q#~kFyz22%J05vyym@Nh+lgS5JtH#eZ5})rpaI6HgGbw7bC96Y|&>RmO9>Z+^Z%h$qiqbfX>jU=Z`9H6iP{JKky2@cDk7 z<9~vl*tUhns?xqUM>IMSwBZVp z5t)sQWzBc6+|Gmd3BW;mZ6D8XZ(txzkWj7w53+EQ+2L9=M~1MF_9G)l8m)niEig!P z^w%N@>cCh3On{FD|1hl5=UHrBWPv3P81Wo^dn^%~V@MK=`Bdi8V{h^^TK&+*NMd2}e-fJGp$(YGZo z*|qfv9P!&QXds9rDW_o!=SeSmdc^E93SiHdL1Pf;aT)}s-(bks<6B{hUyTayF_EVi zmCKK7s2QM3mOEokKt@n}#EKuu-6!a(wLl2wsSV~Q_|MC;1s}J*&Jt3{O7w&PLhGd{ z8=RB2{k*RjM{6{U5H3l`dWH);EOI*0tVM9!nVx|YGQ`i=rYTIZjA{I+=-Mv4h3OLukLPzC-yzEY(#T|NE07>sg64dHX&v zQmGh+zMFZ!=-fZ7n)S3|D3ICWLT_qVBVUEG&7AO zbWKEX$8-VwQy2sdwo}5%7(HZtSFw9B010BY;SZ*W!f7NJrJrv-t?07zuO@FZIYoHG z<)%OmXb#G*8u>c94;A^X@+d37Fs(7+|LYott6wnv&DgbsZNS7SY;gcWWPz2}o$LtR z6kYvz!%|J3&BU5bBvtOuC_(FGOa*_K(+}10g9rW}hX$d4nCtdQJcBV{<29^(bS_z z7;eDGLjZ7uk5LhRKNO*Eyf|jj;$%)%cxm8b%U&5I8OtjR`prdt9$t=u#x`Xa-1cWx z65%#ld4sPU5_$O<8;?KdBU(5J*Uc85g1iV9Y+l~~8?*6WOd_wU+?~Z3&VJgfGuJRD zC7o%3qh-Y}=?}rzbc=)Go0yg~n2F(Z8zYFy<3s@k zd0Q|8syh3oHJN2E7J+pqjasrh_JH9Oo>SO2GqOaah-O`SwY|A+y-Hq`;5w5#laKE2 z{@Q7P0al}k1ZC$gs~C|D6SbF@`@K>(q|YssR6|BJuYsX^X8u3klN`2R%RGfhKhDt- z%CCyurPVF8IsA68Qofmn&N774Yn274epPB%pIn37z{hOU?MKQOL{U?xfjI0HRovoj zekk|x(TamVKo%cLL|NK`zOvdaYz+Iw_E{oJrqU1j+TfFxOyRC>_!j*q4#C;8FD)^g zUlT;&$IC}z^P%BO#H=Aw^GalAm*}o(}RdZ*WRzV}=OF`MzZvBm)%C6>vvJy*OMPNl5_RJ}_7+ zdc2{8OCV`H3HpYlqGlY@F)(~&&^vF9&*8wmW~1*b%C9Tq;rTYb^1$p}h8w$z1Ozt2 zFk*}NVKR&CLvnpq8|~7K%=mi5it`un`Ht+Z)}ay${jlB+lA{u#JS^H0fl9h(O90Ie z9~8LlLgU6lQecottU$4&%!<=H4piCO;>=(8=2X(zg2)5RH3Utq_k3S9nE4~?Z`lqt*A=L(C4j6Znaq^>Sm``kdyeKH14;2?-ZqE|M>RXusIm!+R}PCl{%vZ z!=Q(ZShydg@1^u?=_fd%|7U(M2TJ~oS3jW+z(^y>*{7`RFyBu|N*0QEP5HZ5gMe-_ zrm!@K==)hEnB;#Bup0S^*(5A~vcaPB=9xa_QBV|s+DFGB^+t@2_r(@}383Qb*lsVJ z@t41hR;QE&^ubN)Q$wGo2jo7H^-4cvyR=onqx01Pz%Ec!tMD@5FJT++H$3s1@V|Ax=75F9@Gn%^S&cAFAI)@sa*^$q64XKWG z?7a4l$B=EVz=LhWSycYu;ar}>vyjAkyZ?Zam9f<`Vx~#ysg!Uj+vZ|lftRVA~NwkblJa`&7x4$ zUaWJ5*%I0|Ovk%A=GgcZgz9k79xl6eSl4ST@TDDbk6T$d+eUhyo2u)TuDvFsl$Rx( zuSSXU695)^E93k%!8R#=vn<9)JLF1@S;a2To^lHSywD<7QKSk(OSHpt6a0RdPQEMls| zJ?hVhr!O!vip+9V7YM4dDIaS%4&&={zo7#k7(;LHS~i-NL4ADP@ks$jGZ0pnP=(lQ z?pQig@?7X9^v&}}SSTGUo2y0m!Sm%Au8-t|O4~0fTA3e>YC6)C&SKdoMQY~Rr~T3V zVVs*mq=uQ@J%`3m`1sbR>mD}-6=7J+t#tNt@>Y;U_g$E|OM;6Q_-mS}663T$zy683 zgb7FC2fi)zP?jH7A>yb`?J^hFH)yh6nt<3j24gU)CRp1q?Xuj8krxT^5~H&?|9$N2 zt`|YV_cIjdH-I<|R|Kgs1F936;3{d{We6 zY71W4FE!5cY2m@N#Mh*D2#ks2;Kw9sTt5n2i0tn!zHk+!z1WX>mH7ynYBNXmKX&}c z6Rp6k@4@rgwgjpOY5C?C`9CeokFDfX4K`QGGehksTE!ETho#NV~j_6iA1!eB4zi1xAX7q!-GAYB`!IG*dz zBtI&320wb>pl85oZwIiJHxyuaYZ$yGCw5P2QS5$yJrx`3Rv{%Eor9GUWZJCwx|tA= zNa{CE33ECN{AWU)lDuR<9}9oC_p{Vr(`E!mRQhhS1YwvPE55Q14;ms_V*Svzh{dB1 zrqH#Z{zh2{&on8Jz4^^hulkK22(kN9W5krhHc6dMlVcBcbByy7vefTUtg2V%*D#g`4U*mz+`Y-6R`y}_w6vCM!JN6 zu@g`h+>8yy7IX{l@uap>O0bKMz2QYTmofzHbS@TtLZx)A-W{J;4Tjh?Cl21BNPtz%A3yrSyKEf`jp#r%{&-b*<8a@k|ZIlxc9Sm&BS$co2M8qb@Y!b6NC!;rU2xjkZec&<5jsLlbB3ghk?H)kh78zZaG{8m{|bZCHdV{`mXIT zmSom;KcD?~*fPK0@#$4Vb_l_fl8@Hck?TRf9>jf)Kv#8~XB+Qcw;^4Nx58KsV-Szo zh#5rn&ru8C>KmC_7f-{3M+tK+=)m`i+c9UIaIZPa(gq{V85h8Pb0P7O%69t!G+!FE z6F0jR+^NK|0aNm$a&Vc!QweF8yJCMZ3wpCVLVXM{pH!%an_SEyr6yzAvFoI6~%6m)}-Ch<6O_Ytfr9i zrIGPQoUPUvX1`KL>X~GSM+@&ke(serruRLs2|<;D4Sq$aI3pWW0fo6Yt^^~Jx#W-Yxl0gKsrFdM-muDvdf7CFSaMG^U3+3 zcN$s1IE5sdU!GCP1w7U7rp@_mcxuz_^FpZnU2-liA5)7hrtmAE<}KwaF(5#}cqZET zIB`*+@tfAL@2@zV2;AAOsC)GQo*1Kdj8I3MSG_NXhZp6Fn%^R`G~+aSn5;TqG0AXm zdk=*m))g@mar!mXIgc#F+=Tjdf%Rn#&reiw8khO}2-gB8@a(cYLUZx5ywn3iNPp=` z=CrXrzTUcdmg`b5hXG+~f)eERBO#5IkHhf&b-IG9IhPA58w&2`HQ3J;64pFc?ps!v z&dQti10%u07&w=@OA5^fX;)IUd~gy z7znG$w}v!I!gw(&^v6PHHX}4XMkD0fg)92j$A@&At!gQJspxHC0%y7QyAlhglueD~ z*$9Z-&h(yRQFm8$gno!xZa=j%{#4%8pkGcbydA!f)7^r24X3S`0`~KZO-AKW5sW4P z$v`&0f8~ykGF2S2aam58b{se2(!F!%LJiY$l$DD0uwf-LdKtDy82unBiwuC|)n~2z z@H#uq6cE@vcS`JSvZ0t#$_jk2bxbNz<#;;hXhzwR)`u5Pj6Jl$irJUJ3!S2X*y-u& zsy%fVC;qId)Ls54bRqTYg^e%1ubTCuIaXiOVuz^>xB7CK^ev-_o+-1^>i07kNe98A z-#i5y^$e9MSnaD)ABzu#bRmmW&Up)^=K7Ic zam@SH(uQuX63d_JeJc|?Jtvw#^0!&a-}l>*?uT6dP)++nXgYc z29@|wfgn6L_O)7^T*dFOtYAR=Oz-T+Px@Q9)0~15bW8`Go=KZ0cO-|vCjYlB7vnOs z5({z^+?A|ceg?gQd|hAs@km0JZ&RD|Ngka(L<=lISp zBZtnY#G%lfPS8sbk@0iSm=Jn+V(9iXc+&WL)_l%1hlISM)o-J zJLt~$LLeoj%Z&-kb?-gDlxsOlG^i?1r4io_yE{nJxRThRz_E~Ps;kidu?C&!gFmUh zcb>xx5-dt0L0lGhB8nh0$36G6bN>}z{!~2wOrO+4Nf^m3H?3F0td-ybDz)^vQig?Y znxb!L*KE6KP(Y3v5(^V%JY;0D4@Oi{A#)}x`$y^ePx4VJB$An2WgQb(3ZV`(T0h+m z+;M3)zX4fJ7k9?L0U7xHx`;PfHH#?Pl{b}@Sq9wX1{Oi4+58?0Xl(g^?7r_Vf$aip7%@Bky&FwHd|nSid{m7F4lDDjD~)e{rtj zJXZrg4*M(c2J68*gwD!pK4)xRPhivn-Q;XQ^-0EeKlBK8 zGZT9%qaH9`PCl%*pDHc(gj{dwpvCh!+!6E2pnef&D9aqBdfsB%oL{=x`-EfUFGLE9_c2Ueu{=fU7qDdS6Y>lV-ow zP7#e61DLMpxAzSd^+?+rGE^Cr~I7kb1 zw|3hS@!$U>Ew#Z63R>kIJeeN0Xmep#T;X%NwFR()YHG_AOLf6&qHfa#gGE!E)r@pL z=X=dkM|`}4N!cmzZhe%m_#@EVnB0{D>xn@E4uD4@CrjATfZQnzQRrcgwok7u5YUI~BoDySJCoo776tp%0H$F$kbpOFGsrp; z9O$!+)!P>xdyxwPL4?m{w@otyTyR++a6U8la0+*ZuLD#s+K7a@)r`}+S^BN>ND8}C zWoba=ob1T5mR0@VKJmRsbN)4(ln*dK_Yy6PmE}pQZ%u+1RIlNiS}He;q&Y4 z*pxcEYoG~Gt$F1~+usf{*+z5P%~_#&l!6L`xBlj;&Pd>uEHI{u!PV`>=JasrR3{bh z5^MuMq{m&$#|5UZxJ8a&D0rLsR)P-IwcjDUnt&g20O31=eEPM*j=(R(^{Ph&lKEMQ zC4k(leB;+=mwK8Ohj_jgsejY1&^2jXjuBX{dWMiQ2PPINxk0P=j^l>UalKVe7PJ$D z+$TdCW!6em;$XtROK!t;lQtRml^8^>lYV7C>F(mELp!0jd9mHv?fr@}^?=4c$ostv$>zrI}Z3*_*2N+sA0rfzyeGwHSQ_JzHq_Rc0Xc zsHXINyfEH;N}PIJ26S;Lv}KBk4Pb2f_QjkSC;HjyL0|fbD-|wqSgyJ)P0GXq4~y6^ zukX&;v5bR*AtY_Cia=MD9nIHdGPq>(3~`N_5yf3 zT`zzv8PSbHwsADNnk?kCM2R92ilq!cQvax6JU>85o!Kw97_c*~Te~1y(u`iI97b3@ zgk$DS))w$(GRW3q%J|4ayY_-%`({RVmk>9?++{+;8}jH-y5cbQH&Q1o;57rXCxh>i z{SKHO5d_($?7p}KF#$qD#GF-A;nM_tNe*QV4VFH~CMaYyJwDrSVr`6)|L|lb4T`ui z?4T@}F@CPN9a;_gg<)k^Hx|gXTJ*6y0rB8(N3(^{@39{;@nBvcs`(Cx$5LVnO(3w8 zr~T>Y-%Hv84$pt=y=imeI@dP({ZyU*K=t_;v!hvZ<=Lky^VsQ(olc&5YnKBKkT5kv zNcz{mk`1<@F&N?y1M9vwjf26qv@Tt1(9+Ut>!NhlFuW$DcoVI>w0=T=>Uhj5g^Thf7cqL9>4lkR$ z;#Mlf-`9U$g~T;&20T68{W)xPPJUk%$V>Ucb=w^xFL9~yPPb1_Pb$CmyR3Ba{n-AE zUrw4w8*S<|>CfxJ-E9SzOFuL}ZnJ~O#sw-iiVf_VH9Do2?Z(@mw`WAEjhoY>x1FNB zTWuDvuDcIsmD7h^+&DP6-)zy-^TO-p??c{bs9SN;+um!pJNsvyO0Du8x1LJ77w66M z!pm7_T^l<-RTYKr1``g~`#r>gdUBBL*lxWwjp1I`m;*VpWJ>2cx z9GrB|H;?zv_nzJ=msd<4?qAXJHNPn#BsYG(9M_pF=^lMMyFXR?zfU@+ySIPDd#Szi z$KF2tSvz_@e`sE9-(I}ZACLFX^$Y3LNulVx@qO(E*E-kjBd!Pd{c)jQDsx2-st$Gc z_V#a&>wmt#Uu&=G=kKeR-frb-Ftu=73le1GSxcqLy>Zn~9??(f3GkLI7lvmIym&#%tiW0U+AS}nd*-Mhx_QRB8( zcyR8{TFuJk&ij#Fe3v)qt@5~g`ijwYgLi4K)-Ih~H{N!(pLa?xwN0sxx1aX4MX`Cu z_b;#N<@$N0iyohyx;B!u(YnbWdzZg%%B@3fY`MD+f6&eCgL8Y;{e8c8ambIImzTHN z#=gUMN|%3*#nVpd`_2CE}*ylN2mKI`1tMR^7qqu{ox-2gSqJ_L19dKK#1dJUo#n2S5L8 zY!?dr`|0_E^PqjJx9`BaNT&cO?By@|t}4d?C`(I(a1 z+rzKg30LQj&U@YY^Z2WN);oE9{=NP1ENkfS{h)bSdpi+w|MBwf(7iltUB1+=wy)6p z&!4+D8xOdqZhNhx{qOJ1-50+3d|CeS9UZgx`sw9Q(X2k~((YxYE7(DWoGSO`__?>g z|Ma?9+kf7!ZnN`W`+R?!Np|!@?$^5Y%crZ&vZ}kC%fjjY-uuy;{d9CweRw|BDqE^Q zcf0%IZLfCrr*+yr`F_O3CAxFF*AHhU@nhq3=jC#@EK0?l*A01g@cZhpe)D!vEgmy; zE$$!1W&79N*}d90eruxJdhPz#mAda;e&4Q@uaJ8A!`gcnce2%Z*g5N59<&cicO}_s zZ#-RX?3WLp_ZlY~&6CHUm+hYO^Zn1ZM%M|Erv%+ymCq{ol`D1rP})U1$4C2>xAJC_ zyi_+2O9$UiN&U5be0*Hnyz1gZ)w(-B)QP?zm@H2lr>!^!)4y^TP49 zHfcAVKfJ>_JDr^;v3a)d>@>F3UWt{iD)-M7eB5*S)5cLrz3%opKZ$dDy8n2-tBn)M z(cRhi{X=}OSoipkefaCCbo+Suym4_a z|J1~R|7^`~8ize($TR_V-(I zyM6BLH?A}&M4SA)*37x-e#h5$xb^;8J4S!@pF4NeA6MTimlC0xZA&LI~cXDug+=j69)!6+* zpZD*sdX4?!@3VvA_gd{Ix+`5?mD^97_q*3F#wVqtZoU2chX1Kui0hxV9rwNRLiJ7V zuOA=lKhaz7y`xU6;;^-Q_&|<#9>mjj=g=v?pS|3X0@-*%+q8FZxq0+(c727mH&Es6 z{q?%y++KEW5AHV(?|N(I5+oi=g)=btHQBH_{qbc-*mJ0);(g~tFBGRPVs5` zr+j#1m6KBWkG%Zdt-tPkuO1xkJykZ|ZfNI1?br70t20_fqS5K@f8V+3-Mh8-$DfBU z+ok8jUTv?apRcj=qvX1seNB0r)t6qEZ2V{*l(u&&k5A`&ch^6kPF}j__I9b-_<476 z`QRMGe#$?EiH6+tt2*Zb-B?e!%=1y_v{&Y`1zP}u7XnNiMz46^W*em^cdT?9sCu;L-_l;d_ zY;HHYqWrUkZc7!qP3|kN3O9et=jP-4Zi`p-$EbB!KdDx?uX;aToz33s#*b>b^Ij}n zU!7i*Uw$=iZr^S;e$rd_$NO8C?LS_TBYAQz+1qCOqJ916pmlNV!Ls?SO0;IDwo=a)7szxl^V|Z{?Xn`W#j1Q)eR+W zO@+m~YR%rM{;KWYHm|Sg!4Gm@zr5HeU6<|FUTX+e=e!U$?u)Q&Mn_>ZPUu`d8>DE)Fp>6JFgEnrQQ8sT`m67 z;rH{iLjA^N`-k5MF(0trJ?Un2NwXOZxUhTGp%lC(+*I%WJt{w|zOrNiJ#PRki zeiOegHVz%M|K6+cz2o!lWA$eHxo~w#Ps?xO>iOhh_oTLeULrf+fBmj>@Y(Ncv3**z zi{j@=-E2s3`?mMzv3%F6zn)$1ckeIjd%W~;ez*H{jk<4TM;tugUGDsN+oQ$W@nv~` zSCH>VCwr_{wmZ%J-pNzvWtPq4V!IY$Ik- z$}Nnkg>CvRSFO}v+@kps`<5$LiuzKOdda)Zf%b~sZ#fNwO2;iUsttX4!nqZB>3i2- z_01ct_e!BrkNc87)p06yw>9Al#Y(5rsOv{W=v%Jil-mD(D>P~~r(XP)D^}Xgs`K8~ z4Sn?f?Umj7;K^@V-QX#UZnIPV_Z#|0=}!tyRks5B{il2Id!=6a?lg5*{MN>?+jBZ@ ztLW;c>Bnye&F!CUyuFv)dgOHfHswaI+^y<+cifV`(+`jI^xMb_$)!t2_AJYqzTV#d)l{orYU$4nA&H3NKm?+8Z<1(;ej0A6@gIX>tEa zoocnw^M&P_-m8~_hiU7>Z@JAu%Xb^~x^9PliVLUiR&&+H(1lXC*_GSVotFFG>w@ms zVoNu$^?wE(uOFb_srrW+#35XDx33umF>HXoLZ#m9cKZ1Dy7s?H zJ-mVbf`LP>_wP4O)zA+&yt-svq^bQqn-oH8ipO ze;S5u@6Wc;P(b(d(>b{n;3qplx0r^&#m3+#cR-D#JdVxt%O0mYWndvwbhaP976_;ICe9@4+( zqj`iUBX_r6@Bvyi_ZZVkj{nx036vI3U1jx8jjD`K?;9N7H<<8jcg{*0PJ^~CgXMJ7 zD3!D#ak_6gw_egjV)Z{MHoV(gQPYYJ{l%h&)|U5wFtvbKQrGzYmYS&!~Bw@IEwNmf@Ee#8SR6mNHuqnTsHZ zoX1S{tw=A5OqSGeoEAHQ$nl!#Hkn+veNp82AIxJ350iMxV`?(#tup6bcUs*-r>imM z3uVV?R|>g1{lK{^1bGd@ zL@lj&NYENv_^lxfElxa0q%56a@+hy6Zimv+(d}AR>7bxBg88jc3lvW3V@Ie#d!$eS z7c8l8M8MO`_Gy|(vdqE>h)rsoc_U|ec}_H%D6r%SzI^ zHKEqgrdlSUHdAu~rQ;c&S!~V>PzzNk>FXTRnrwk>CI`Wez>O$jY0McV>NV-9>iH?} zCKMWZ{Es!!wk`dilrhvmQg5I@!wuvjXkb(FB?3qQ)b>_56 z?r>|W=A&WtMJNkrVU%XQyyvo8d{G9Jv}dw!leG$~XsgT+6Kn-q#Uq9jV#>-hlXJD~ z&61I*9x=wd7em%i%xbfy+k~NoONGj&i}fLki3P!=JkukKD{c%XZ%!yX%>H2&#v;*X zEk0v)5QIg>6xhd`8D$mQpORu&u3S5*Jvoz3XRDpbX(nutVHsnLWR7K$H%5dG z3&w^pmpa@|aL66p_9qh43~?+v2Fk-A02Y{L6Ku(&hFpcsk+vTMwe2OI4k0Q^1(}@X zon=}y96Q2~>3SiVEVX8axt$Pd9I-wVWNprRwg4_4;x?m4vzNj$TZ=+>(M$KpdW$AQ zj)wAK)Ym;;cER!@j)k+=#*@!<{BN>sxKzVXGp5cWHP5WVnPTU#fZl97v-wiV4Fe^I zCUcBgz%tjIkGSpm{SSbbKP+&t#@0bG6B-cCdT7&tE#LD~6c{wp&;I!O=RXc> zYu#1q#YWF9=Jpz+rL}tfQ^m%hvQ{K2DFyQLd6hrOHmFo(Gn}~^TYdxPD6kco(~DLo zH(g~ah&J8&`0`SCwDOQI%PED24jQe3n=@C<{qJt8(R*|f79J}2U;Z|s=#Y8jHKFYmJtS{~! zV&avC%QBkFcqK2Vlqrj;C-^tjg%rnCB=TW%EAJ)q+ruPhDvJWg0mlKytqR9sWTLn+ zg>os5KSkys!1#KzA0)P!jJa?T8uo%Xe2g*2v$xp{5FHR55Z&BF$Kz;@V+hYApa9_k;Q`^T3gOwq+Ybz#;c0oVQ_R4Hd9UH_N#5khIEF(M7!DW? z7;beK4%1loR3zVVa5BRU?^u&1qb|&~H47fcFrG3P2F3%%1IAkw#=xsd3K>`X;98laUQJfv?$l9qnvUW!1V=)b@HJc4t#&MjqVMZ1> z4mfU=I1Z6Gjzek9$SO2BVQ*m^N5e|33XAazCcO2Zz;VEFz;Ua>aYAHj>BB@T3}F_j z?jXl=l&#i`qkK>SMFB;vC`DmJRE!;_PsL3xU_|G!(Y}P)A{DI|Qhi<<@h>qFe7%Sb zj4xoe6=$~C6{(pr%66(TjPHZhdP?LTbCr+w3C=XK`#{g1w3= zv6MCXojDoHE2?S)Yx$2p2Y2yk{ro|Hno|GXz&E3>*=YA`yqLe7da3F|we4fF(X9tH zV9X~WJ$QebCosSBP&;^p!3n2Q8~b^;9sPLX7)eB#5vUl+r!nfj40^SeiGo)~D&SL!tXaTIxTagGfjP^7^Fg7z39-)oNz zGtP^P&A&#G)9E;ca){RKjVeCRsM>Q)v$XTva&@mKbep~JPV$%sy|!Hba-V<`I?e4$ zsqR$&tJGs6f>SbwhuvTQTJ!tAiA*;d7SpD-c7V;EF<`r8VB}^_9|~kV4V!EU!urOV z`73h7rAWh@pd#u`NQ=&3qX8n<2$6Z?84zkPs1H2PmvIpu_-@3&pP$(n6^$P z073v^A`p^LCwsh@5&$s(5CE85A5)Pw10nz*0B|M%4&0M80O)Nt4B;7oU+Vm~XVyH$ zCH{E}qDC_R>~J$6j)|r1O>PqZvR7>?{k%V~ufoK?{&F!dUe$Ix=9+%qlkOe2@*2V+ zI9;qfJ-IFY+tKf(mp2BferuWPms*w|ir=vjiTG1P``jkZ4IFndPu0cUW@L??f8BZV z3(p&!IMS;(I)71KFiFR3X(*3ahV`UI(;XEu9`F6S|*5PBF-QYB_y)`kn6`KAGzc{s!9!xM#uKgX8#O zhC%^@YmtQ9Go|Q&KN==f9hk~>?2EXL4;xTK0ojFQhk@*X?6OF9Bfi)NxehIu>+qLw z9Sf)qGr=uyzigm7pgN$sOj8}7>{8CabbPk$CO*h>Tz(D9iIC;YR*JxK;JpINWuN6H z@7XmA%gtaJroeJv6ZmH`WI0NGmV>xIuotk`SH%72EI^Wkz2@hWne!)@8n&FIg!BnW zp8%R#h4cv?w?P+YaVOGCoEY|CCm4P6lU<@*MINNeVnUgv|AAb^Ot3`eauvOLnS_fi zY%1OmKCJ>%GWcPDAHXje{7e<$wBbkQhaapISTp>Yf?opl@yAlB#?C3wAHXj;{Ry_^ z9Z=clK{~Y%Vlo^uz#-r;xfST)P#>g@Lk>6u941%Cl4|(re`pwO{1E&En1g`78SrOz zH%SwJ67UE3ONc)ob_4ZGQ^B7C`~m*vaEH^vpAGl}{3XO6)4gaAsQ(>+F%1lc`!)dv z0fR|xzz~d6FgV*voX{W6OCl{;i392BkW-MQ)Tfx0IBbTMf|D~hZ8qCZ#~)1Hl>BSf z;n-mWIxiUmrh^o9V7et>I;K)vU(FO=B-7bUq{Vczm8S{keDjjqKGQ*(IxrY8*jJ>f zGdXzy;4GB#7lSP@QQcoM8?s{Ja@B2?UfHb?D{qpg`^o9* zcDNuXZ*yg;`$~M`Kzz^Uz1%5)M4nOe$_`&Sd zn&Hpj&eRn*2dSj1+hMsRpg(}$bowJ~%cns);6a)a5i$TG05Q24=mF7~gQ)|N0uTX+ zGbm)U-=M_yR$6<_FajnaAaE80nmuPyM4$}Ua03DXfzuG^18<;&Q%M{4!jphNK;Z0d zacT$z4g>@yMj*!~X`Psy;Q<^|L7{~3AfRw|TQG!Us>$m#%t)Y=LrNzkuS1$arV^rJ zlGjOU+ep}Ch6=_hq;!(6DX(tRFe!m@0!#%eqNz;ukwka0dGd7Vx!4VpI7(J$It zm4^8*R7gl(2L=NM`?}%nJLF}l))C6yiUci0R_qHkh~5|wHnFmBVO5%6@y~W zOt#EsA~qJAJU{B`es=OY4b$hi5RfdA9kR$?c$Q+~Wem954`!#<41Wf9W|rO@q?5c(G0-2tFFF00oMDd!>3|1Q${99hF8~pMnA8mPfN0FY zw1G%$03rZ!28C>r*C~;yC9e|&CLthj76h7vrBo3p0fB(PqzLqZH&DXqqzxFvwV zHA7(3Elvx86gUtNm>7ZF4wKjWa7+V*-dr}I5KuUyEf~Tv&E$2AM7T4pM3B7>+3S#J zkgbfU@j0D1wdD0stxdnu$1^&~*VJ7PH+&LG0m(t)I*{BFkQ_;^=sKBUql%A{9A;^e z+-zCvA<1dmA9B`#ynwvEE@wTxl=V^aT42UHM#F{^B&0I3EHbAUa4=2cla7qEm#YMRbfWwz@h-X%XFQdFo-> z0aqLn)q%i(z`ia~o$<+QV`i#BJW61TEUq4=(s||+q^d)zIuO-rq^gg&WJ70E?3u|+ zq-Wu^5gTukoBP?d)p6h?QU#?9GsG2{i_8jUrlz(8QfXDT#u%^%*h_*v?w3_h6MJZW z?7@`OT7l0)9YG%Y(kZEqY#=_sUQ*&CY>W5nse4dNsfsmH073v^Qp-<+&@%wj2B8EH z0tgdpVM@0I;i*-zMgojLfM0U>A(k%uIKU6!mkfSB?E1PlmDFG_2np~5_|517r&T_k zf57wu_^lQExJ`n@^ga~RfglA40t9EW{~}OKGb>%mAR0pywy1z)O~^`z6oTyJI>jVw ziqz_+E1{BK>f_0pN~D%(FeA|gW*-p@hz_#Sf#{Zi=$M*Ze|VOxbhXgp=}M+n0d%IU zbR~n}GGVq)bdZ$}1O^256AYd?| z;ioZZ-qXgQumOXB!Gt|rgy1v#;CmmSL z9r6S4trz$ru5W7Kn>*$Q;9D>7aq$l~@YDbHftb!lC1Cdf0B5rMLLjD^hc1SyqeFTo z=YDI$;pCQvTh4jqPa-oaLLl;BkJ%Q*T4;_du`$WfNYDCB6f{9M0 zMRc>}p^Gr5Kyk=J2Lb~E`?@@IHk`@%v3$)@0$XGry1+x`6Xc;o9y$=!isYdWyJSOW zMC_T#Tg}YIYdrQYJT1mcI3tRp1wtSfBr8;rxv;G0Cj*-@A!#K8a~r@1;7b5LuSjs( z;8Syh4`!s+3VjB3W(oG()2ga20fVlt=0tk~Df5I=2 zo;nCQfDk~KR1HhNK6+XS!JL5+2rjrsZ=U2RdzV$+1 z!~sqXeV};(eKXWX_cPM_NK6NT^G|&M0@n(G1KTfx#8gw#d4R+`wJm)&$kc?CbjTmb zOrldvN;;cdnQoSpbT&C@D(y^7np(BH*@%t>6TW#afaoA49f)oTh>pn_>X}bVNuPl; zQy@B)S_0{8Dd{XoF33yl6CI?a1Azg7eML$-mMNA-A0@Cwrld1DWIka?Nr#klAga|! zNgsB}hHMxTduFm-HXE% zGAGYCpbyZO2z?5rk3Kd(`e0IOz2K)|ClK=Cr_#wvXB5Z}(3h0_IAUAoU0|OC1x|?s z37`;AnA8CDP^jNiM3t}s0YPud8XyP|oW=eNp_pcN zx{bm@c!WdJCS<2WDnX`lo?^1ol|3;zV3zE3HA6|_6p}U-Nh{%CMxsMu#ZLu92ifUB zbW1>V98WIHIRnvgEEY<1c4|a7TXwn~?!{zN$W8|W0|NWH>~xMNTUWC%%3lPw$n11m z44F@moetUQKvXM|ovvbY&}|fZX7W?JKR1{2#ZP^vO+RJ6`2dm z3O_B_%S%ctJ=mrIJ^)_=@R|95)WJvQ1|Q5wtrhwV>dc(HhrX0j(`^jY2jEMncTG;P z@NK5#(* zeaX=0qpq)bQ%MW>A-YL zz;sNow3v>G8LF?Rkd%&6EB85DQaTC?FAB~fD;*dN80_n^(#ddM=f`Rbk22UIv(gb8 zHlQFa9n#W)sa7K`eZ(spIulz?9i>TZEcX5?Ey_zaGm65BNn8=g3dsytWG*(FHxpA{ z-D*KeYRSQf0R8}f$?zxGC*Tjyk3X24S~CD@+<7JAJOHMYpB@$u0Rja4O;x~N)#^ct zh>ys0(t|zA7k~&rOsI){APV*gKvV!k0Ag~DY$~V=%+pF&l3_gxFbM&G(*fw$&`cA6 z8~_LaOb9?9cLNQaP91B(0Du6%Ih^9u0QiBZGyrhT0LWxmNxhH8G(bqgMcV*E0AW%) zFhpY-AhczuifyXVCnSInKsfG_AA*^%0cHTR&xV<#0h?iNYkVHi17@6*B%HOa#JBU_3N|fJ!LdUvT39fXUG&itCY&ID{iXh z=YZlc5uOB%jrG3DRK?|M$hy~z1^)f{nqQBzjmv- zr_2!QG~8MfxDB{%k=)j4l(f8Kh;4@Tjc(_^c1P>!coNm$@J7j;#Fxl15IBsDU?rFy zir^PyVpuPMUqH2kI(Ww>V+xy;DU8?@|KQ3kjsd0~rcqpmsml^ud8+K>z$Bb$4_AY3 zk_sOB3czw{VVTE7gN|aN;YXO-qEv%YvEV&ViP|zV82$lL`XJn5>>~&bX3&H!Ek*#J zfY0&x>PaNPA_3?>`Cd~ek z;Z&RoCWA_$m{M&I+B4Xi0Lk@&BvO4yV%rBIrhUr9l#{do83uZYGV{W~kpRdzK%xO4 z`?e)Eb9DZJLTLe#hml94vjyN0@HiHa9xeKKlzkWu-bHF?4DuNm7Zzs{&^Q_y+XLNJ-h?g<3IkXM zI33_N05aB!esIKCS{zFvm=##-dE zh;sryW2^uYIPI9kSOK21gu>dlE1ihe?ISmT_pj10RQI zkmBJpXF7-sJRF6sJf|>C33wcbN3U3&32h1uLNDFT%gPK6xIihOG2CgLGAmC71la+N zW6?<10FA1je90p3X-xTW717m{AuG>7Spyv7ayS(70_a}Cuor`5N`(g=H1i4#9q}|m zlw3tZl!RkT07wC(<3Y-U#11awrqVGXY;ZugL<-9-IEe<)DYrPpD*>V75i02*U04S5 zjm#F2BM+LwBiR(7IkZB}l<#2>BLZy3U^AG4l!GZqtb%BfN-LF}hpFtI$M3BX2TTGc z$6-GA@tEcZ2#yq*?vry~%VeNgk+cWI=2Q zKo|o;N_-Id=*z4Azi9+0c_jHl(;&tGLIB}d5EABt5DmgWEO^h`X&^C7xv-f<0f~Ub z@uf9{@3suqmgNV@X%eSn9C98>v>;f+2GV!|iesTD{pikM-EH0uqL5o89ViCNbZmhv z21o=XjzyxGt}$zLGy!@mbG!tg@Bo=gLt1h3bU+!b!?BUDxP;V50Ofd4Dj$@ZA`KR8 zD*EqoE1mQT78b*$wt%TZK;n2Lnozek6U~|hliY(D7U4lEksKCyFNz~ez>FXOaV!w2 z@PNpPhd-s&_TW7g7}AgsZL@6$14aZOj&)tAU;1JT(?Kl^TcF_JAHGGXn!0mQB1ndn ztfVrJm`hcet zYos(RtA@;W9#A+4;5ZhJL=D(+fI>4R6CR`k#~`#L%tn_8LOX!Q@o2OM;Bq`J zF&W^pUym2{OGSGJ9xl!L?CH2nv_AzNX)_L{v>8Y zO=XAgYMSGn5S}W!I6+uUz^1PN(3pHPo0BcBU*3U93(qsA!UF@6IRji{tB-?ECYN7hz&5@M|COjo3e)e5gp0wMz*b(!8JsJ?_!?nc(=YYWhs<8O zgM(xnrVP*?z&{)jkpU7JI>CUNg7NE0sqR#ByJffVk_$F}A4YuJ`%b}pr4W9@z+bm1 z4AE+TEzcRVu+aY{gZ~J4#}9zN5&oeeFcRJ^u*2Uqevh6qW|0B$I_q0 zJQo>$F8{}Yvbfy@{}M8 zbITMYUwJ3P^i=3w6v~dac(m(4&3@44YMqAutP@;OqsshUbK7mF}%cM!PL(Gh7(c8N#tR^Zfj0q@4P>i4$*I6;5#ZJ^y5zbLzM5RRuXvT~T-VAC+ zgo1r}&5@Zb#Rw-Uc{8JOjBZJZkg_NT-3Yo7bmKbfMo(8QQa6S*Gi}X1T-u-+voUlt zry0G~yQYA(c^4>QA?sNk#;_!uDd8%#S_G8{DiKuT8mmOA7OE133|6T~QvgH4{9;Ci zZst^?!v4l1iktUT5^9AKA^McX)eJj<0s7VteTw<$Qz-{0gbep|V8qgf-`Zr&#u#D} z3^6doR>2S(XJmP5XF`5$oWU09#2M_cnYjoLagpS}MFhAAxM)SWNZJeJqGVRN2z)}D zGpOB}ospY4tK2JY%oy@FpJY?FZApkf6ylF+{6S0t@HYv6rg)c!zjW4n2p-`-jXeob z3BcbZ{K@|2yZRvIxI|>vQ{+`IMC+VyJV4!_%Io*QK6F87N>bh!M@o1rTUbQQ5GH`~#%mNL z(D-2fhOT*R_xFkGA9*V;Y$_s0)Aznkwm=p~7F^g!qliHm9}pV1X^g46ZNm8Gz~5%e zX3?XRP-%+@YifvE`I$Bz23W2;EQdDS@@jixX7(-&j`ALpDpP+aJmOOWv&qmKP7E0S zR+fGe055=dg4QQW!%xGPn4>8eJgbz-oj4!3YEF(g!8VWXa72+s)cSbAUT=kNa7zQw z?ccKCv^(vxQ*89w!4-=wr}yZVEA?W#`xt&)X`2V_|M6&^;>q|n+97-Q5YX>-uc^pu zB(l7FXn?*ps=9HmH(E3r8$Hu0^>(+`^qa50_1dlKt};WW({O7|@TtM4UZhXmX_U0Q z$2!#Zl&OZ%0;27X({U%381+WUXXYi@Tt?I{)$TFO%x7g_XIuUH?svL#^EQtZ+E84q zmGX@VK{E(JgSuQ^b@}n-o1Ti=Or&q;U)Xu&>Jc2rR4sGzFRDVrROMCdTobDDdMo6r zn)Bva>SnFEx$Tex;GwjkaZ_zL#c;q4J_!tdtxBFWu$ryLqw`qN&ug58f3WGBwxfmk z>IO)j&rL{g6an!8@l7Q@50r6JZye9yDGV4CPbXP2pW7lo=(Y^ex!>^E+HF`&;OuzS zfRpGCGT}?+p+9|<2M#kR2-V5}>cnM*{W9lFVeQ*&nJjx#AZ}`s{j8}x;Y_8z53)>J z;d)oW0XGV)i6yosreyNq$(tK}!GcGQX{0zMqgEbc49YMYgEw=^us_d?y^I}>Z2fQA zu|+N;$vYe|4M<|IyH@0&6+tVmf>xY2xag^=Meea7Bk3;86sTmN6tgjQGpH1)O}6sX z=tXI=uu|S}N=CNUU|9i(1t6Z4aWvvvsV;{Nf}j!#1aIQVK#8Ch-&Y?tr~JKxZzq&P*XbD;K-fBeTP4 zOPj(*kJ%Zz*;9+WUt++#o3*kMr@zG`vqP^`FxcS{R3fOvHCKtAidyLI$H9~(mNs~b z*%`W-Q;7r%&r$T=wVy~3{)8PK!+IwKftLs>5me&3t3*0l$?ns=#9*B=7GCreflkcE z*v*_y#F8M-PxKG8qlhKqpWrEm`#xfc3Al=&7eOzsxnA`2)FSnw3S31YGzDq?0kxQk zp_@6iC~QA^>b+~FDAZ>}PgSrUS11K45mX|m#C2DRDm7Pe<^^cMR1P7`!WhU1$Oy=2 zoymx!#b!5n_v^<9Y;Gw?Zpg^U&765oDB04Mxp@<8i(skWQeKaZ!V)D6^daa&(1)N8 z(WK&FpP>)Km^s536k=wEZsrssX1+%B-nB~9coC`VL@FX>yC|glfldUSxb8ZU3cgUC z=-qF?K}f+QPIks__LL%F{)T)0fyr$kW>=}y=W$qIrzs`DRRpyNYH`igBI9VGYLSKY z{uNi?DQ0HmW=<(8n;S>bylFpCDf9`U(=h$i+o;{&dlsa!Wnh7eC5A6+Rg7?D^XFYk>vE5^XsBtCg6b4USvF?3LQN&lbiMRa zjh~boU7yndYg33_7u2$L0cc#Qe(qy2femmW{Xm$T36I?ce9$MfX6K zjfq|@7o+&g5EP0hF<)9Wux$x6l0sNyD}L;P`ao$XOjMZg*Zh7x(IRkU`NA6FRp^$h z8t@1nS8lFdpw`Jvi&US%;5JCd?o^_1m1)qZjmUA5g$J@?JW5vE${IXtcR1ug=I2>L9(crm4nlo;e&$#BqH$rn!#L;h(w`K z0=ST)1n&u39&eULuffCxlV{q&f3S4Te~8T8fJnw2$eSqTXFS62{9HG1dWpvK3e*9% zIae)A<^pKw%!0|&;Z@ThnWCs>*1G1(sDMn#bF&e0*tiBJyC;}=Si7u9%5jzn2U3J! zihegn!WOc2s;nLU87$Pl#khj_ ztB>;r_fG)W7rO|*lUcAff>8uJr?BVZ!V%0cD3Qxl4(NCY3Ow6Y3QB@3en(}I9M`!c~hGRHgXP~Q&M>wa@0RprF- z&Cv9DVHzqwK(0R? z(2m~Dmb)SQcTXCJ+07V==R1lRzhoPaU9G9xw7wYLmG(%3K10(wfieBbW5R14F6NRs zm(C+q<=fbj4{|+m9*cHCfuRt;0GkPvGnBKTBU2?5_rC)vca3p`%2BQ3_!Y(1?-raY z#*qRu-%)CI>728<+8JgwzEt-N2yt-n*YD!D)F>L{X?=0E(E1{Ps9&Ln`Vj-81|q!3 z>##T@U>a5tRSoj^MnJR(CNBk{*(D;$pNbE}|L$xoFKnDq;2F%T(Mp)EO@AvVi`vHC z4*1!xb{l7PnlR|wv7*y*gM;N~s8_MsoAL%9f>nS!!^$c{7J2c0WYc8n`I>2n(vosk zB-KzFNZKKA!A2|exLw!p^y6p*3#=1rWwlR~Ic0a%#UDeYYVO2aZWBajm&rRN$p29d zV~U`wUNq-yo;Pg&Dsg3b*zt7*Ayt95W!~qcp`PWL5%myfN;w+FRdQ}bcR-=b+Jpc? z0D+#sn5wiieM_ITUYzVz2?=#{>fw;a#{5Nm*U6U-CSfz@DH)2gM=+HDc9n7|HAZWF z2cP{bc;XEiStf1^zkvl)Gqh#x;YkHcnr%{SyZabrYTh5ab&5zj>r2-4$o4VqIqL2Z zDu`p$5~Lt72s3;$Co7H_KwO! z$|;4kU?mR!b#?et7MEOQT&r1#$@f*MU}5zbsvpi&5kXo&bBau$0zv{s4PVrZ9V;h4 zkW2JIWGgvwjPsELk`V&Ork`BnWT8P8JT`d5*Ov_&YVaMN)YX|=u^fRWCM|f$7)?_G z3T{+93FDH=gTUo6v3)g9TYmr7O+%#hjMcYWh@dg+lvGW5IEF<5{o3Y2mo>1I{d{*Wpr= zXz_rnVT%t_pW+UVK$YO58Bz;N)q8XE3t`T=?k(cD0HVAEJXzLY^!_ZQIO~{j1?M=C zEC;=jMRB>tmW^8_5t*(xkUf(~lmt;q$+Ke7+gityZo!#U@|zvF|=v}X&l z2UAlNqAJCZ&y~UCz!kyIDWWKuH<1EmXpL+mxv8{TS4|4QIKgu!uz@t<9O{AN4b-Ws zfpA55kV<=p4g|K}WeSmJT=jEY3BK0mq30A@J4EJ1<8h&M?ywpm_Y)#2IzB1hFMMNt ztf$mr)RrroMRMgI9>o+PiZuy0DJO!L<{@U^#DUcD_-XiDYT@07b^18U$NC#qq z6YwBuI6vrb#doDEcO14NFTB4g6{l6S%%qFdpbdXIMjCOyjH?b`nf4z}M@Zap1!bkMkCbmUL9KAiJb=&))Rn>0gF_Swy*eVSSPHvcgt&u8; zq&O~BL*WFv;kSfHFXu>SM$0}Ma)1PCop)K7nNSMvPB}Rx;i)o;dU-7VjYRtAjO*xY z3lp8O7A-=non2Tx1*_{ssdGqHmLiEv%r1*eT=FM=6^qEsGy(Wubv&CX5_`m|&-~Q% zd$HH*_TwJy`7{@!S7fr9v@)+UnN$zgH_Ykl5S9iS^(6_*WC?S#7?4`SAHm}7awBiG*TX28tZhlle~Q>^E7V3^sFZ|?dV zS0^mhuM3?_O%|PQ1NXIz(tPr#RL3Gqxk;-ObT<7SB*#};l{NE5?e8gIqqlxSU?{p? zSF(#BO7D(~E)m;W12yshBL`Jj6U~hZ&)RS6=~(Fb7V|>pmHv$;E(_A6Gx9R~pjJ&M z&ZO%39*E&ixW=`Y)RAf`K-vL^HV4wO)HpT;e|*GoV-Xti@_?&UyfieTy&A@2WPCP( z)LPcxPu6?XPb>wzMp-kt6dYf|SsrUvZdi*`_0YMDmJHJcDF3_oFToX$T*}&Y+>JMO(?CUd)1dhtl59c@!1thmo>@o*ZCNv40zY#Fa+sdJ;4M@I6$ZNE zd)$pzv05vs3$bGk@ch1{KZ%PPB@o|(ihEcn4C>WUdiq>#7>WYOyFCF5uW%5^v8Vnx zF!*Q>pin=N4dkRXj1tG#%Rrn_yt_OKkuiy95iQa8Sk-q(wF{H9a+&(El-k$eq>zP0 zmV1u7z*1tO-nV#g$ajA`WK?eK_^`3(S#t8&_sV1JnpG8<4HH*XM2}NJ|HBKDlWOIA z(pnDvQOKYm7J5t`>m`eNDT(Mw>>gZoJ{AX;SX%2;15P7rvAca_feRCywQ)NtSZX^N zUG4w~^Do&%t4>JUe zr*o=+#$?AQkd1VA^54(!tgH+@yK1|bqKgi6x-W*YYeZAe$No1$o!k}=gd0#UWK|MJ?0~*wy9bM_F%)_GEivOyqSpNi8m$633CjEZE{f&3J8NE2tRl zhRB>48wTOX+MnXu;!WhD+Oebz!u!_2x0X~5$d>8wP5jq5)pZM#Mjd~)v8VJwQSB~# zQF!*g25-Tdxmt^w49(3vCM)=yH0668A6IOEyU20Vb8QQ1yqET+geM-DB=+2QLA;Jf zbZgLsy$Iz2V?2A}A6fE11#xFO94Z4kF9Hp0CT9&b=fo^k39EVOdaG_4rV!<}!|ytA zoAh z9kcrvCmF1=?X?xd(z9J^|#H1ywsOn+tO zb=Y48c^kxUm8K?TBY-k##QFvA_~Qtc*Xu~V7i={#6Nvv80>3&*Ul&{vLm#hPs#%@T zmd{tgp|^XP(8@*a_}fVtm7yWqYqnro0}z4jT-8w9%>*i;Gq9Rng-(F@LQZb!gDieE ztDwJq=rzfd30r0k+Pxv;yd3rop&E5&=!>9DlpxIvNx#Q`m>TzJ1bs**kPAk_W|Qje z){V0xg-m;$a=%W=E<_qWmH77?{|65PyCEbjA-2RQz9(^aH$w0ETCOO%y3>F?7dW&! zR_!(D84i~wY`dH(W3SuN$i#&Lj$51Qjfm5%WIY&u=Zug+$}@6mwI$0!E9t4B%zu{q z!*id*E}n|U;$K;ColA|gjjW9hPh1k9zo4{ZQq<^zX9B^m65Y9x$pAa z5iL@ax=IXjTXs{L^1+(k{F{bksZpOSBQ9ChG4Bk8lkx{G@K9@f{%AO|pM#%f&*`@l z{P-JwyyP}cxf_7K0Nz~gG`c$Bv73Uq-Yy^)X>&g+2tgQBK0#I~C&?#7WkvrvuM4~v zOauTeH2&VZ3VzC;WL^t4=N!H}2?*3b|H4p0F|o~R^>~Y()1<@6({>B4K)mMu znA#|C+1?`jXy*@H*`9K`zPd6oNsLgc8dmZ6YMS(Ymqc^ zdY4s=Q1DeQxFH;>Zbf972hmBBo;CBS4$Y>D3isvDt7D(^-44 z40#c9G4UfkXDG6!euT+)1WvfJM1@KqPJpa(zkj7OEgrBfKX%-g4 z_M7}!@tN$w7E|5M=E)JM*BVU`R$y0X_Aey%S7gAR8L^+yQk||eP;ue1V6$k}r!7!z z(4}Whkv5tQH^Rb%rlBHSQC(GzI%w*?^mKUF*%UVw)+uGdf8hW94eYP9cs%pvlu-T| zvHsn=n=~Z3kgs5`mIeNM)SYSJdQXSsB>2wgF66?ZU%;awA#SVCbkkMuG(3>FC{9BC zg37-M8fUR847hQds&QBatgCZI#)J_6V1ITcVMgZyBeroCL-sqd-HN;L|L@vMNgb)_ zzbE>%^EDQ6|DThwg#61UvYl?rgRl$MonOKKc~B8iKU^G8<%9^Wax@WiVBey5}hQnd^&igVNUsc zoi!L((vVSbToohvq=%Ve(a_fQ*ss)vHin>}yo3GNXN25p$e|RCm~fv>B||%E1WO|W zXdPXVE>f+Y=Ck6bW(RD2co0mBtW1ks)o921Mf*jk`bFKeRJkC<;E&2JNQs#hYybc_ z0PP!+e2rRi!cAg9{p{;}ut?fG^ScTNZf@NCqU& zCZNcXl0(GTnC6r%#Gb-dX00hvT{x3?KTnS8gAV)4wsZ76)o_6h*zj+sts8vKZQE1k zf}4J6!??Wm?YEZuhsu7zzdS_eWEcWya>DvsY6Xp0R!!eQoDA^{O8D|##*+mAS=~`MQaK3G|DBkL+ z8eEeB#Hv~@O0@D6Q^$#Rn$YuGYpigaB2smfnP~9}7D=uZ0BAmphjU1uD6Tx7bYP7i z0SO_sn!agRB4B%JGmSU(d$E>fRTN3KWwvUqNYBIWs&&Kos_FbU|C)voz1Oar+zYxd{`t9XUE& zvfYPGI2_K5bE&q+*+@A?8B^53kAX^E9DFHpDXB54`~jp#E7+1ZjQM0%o$bs#S!TY! zG%DTp2OX80Vm!-*Oh)twQh72sf#@!jf3PgWl3%Ps@XDkmX;NaHt)lkQ&wsn?{P_!Q zO_^+#70(_^>%8h8nZ2zQ=jGzP?GADbs8cgZP@^l&b(Ce8ZOA~?8@Wd?LkqhAX^G5cyb`DdA6eJFW667#MZ=rJ*#!ahTpo(plnZqSJ%$D6j0c6w z8xV@9;>v)@Yv48jJ%F2QVKQc>DCRju+S%a4F4lS@2r2$Ld4OKm>OhK~@MCtdfa}iG z(Mla&$H3n(qAJ|@Ls{A>*7mhlw#=iYe9ZAfRz=HYV0^uAn}5qHzz!qY`X0cZFWC4O zKZy>HMk>a}b44u1gj!1Dnbw+d4=-OdX>VDMEl*&O`*tKU1)qbojU~6<%60|mF`Oq1 zFXzz;oq-&2CZx@w= zzkaK0wi2>Q-6~etW~83!7`i^$O0E~sl_0mC=rGrzEKt2yQ3_6JVjOh(*WL`y1b)C9 zcFQy9Y8!Z3Qm#YOJQIm}nlN@IBXQ&jnj)9Hna{5dRmt=Err}FIx0G;n(JAI|=a^xI zw%tUS52qf-TLY3wM_I5avIVh4!uTM~-`UyL?92X?nLC)+xX-=|XU@>4wjBMWouS#X zl;rf=nta>Eor?SWD?_ujI$gcveqeC2*mQP!>Ho6+3x$^5�CU&V5sCz1OTNS58`B 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

ro(z9Hb!>Am`pD}vIm6#k0P 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