diff --git a/Barotrauma/BarotraumaClient/ClientCode.projitems b/Barotrauma/BarotraumaClient/ClientCode.projitems index 991ce2d39..96e169d05 100644 --- a/Barotrauma/BarotraumaClient/ClientCode.projitems +++ b/Barotrauma/BarotraumaClient/ClientCode.projitems @@ -223,6 +223,7 @@ Never + diff --git a/Barotrauma/BarotraumaClient/LinuxClient.csproj b/Barotrauma/BarotraumaClient/LinuxClient.csproj index c3c50da75..0b95e4306 100644 --- a/Barotrauma/BarotraumaClient/LinuxClient.csproj +++ b/Barotrauma/BarotraumaClient/LinuxClient.csproj @@ -247,6 +247,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -268,6 +271,12 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + PreserveNewest diff --git a/Barotrauma/BarotraumaClient/Source/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaClient/Source/Characters/Health/CharacterHealth.cs index afa1d3daf..90748c3f2 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/Health/CharacterHealth.cs @@ -606,7 +606,7 @@ namespace Barotrauma .ThenByDescending(a => a.Strength).FirstOrDefault(); if (affliction.DamagePerSecond > 0 || affliction.Strength > 0) { - var limbHealth = GetMathingLimbHealth(affliction); + var limbHealth = GetMatchingLimbHealth(affliction); if (limbHealth != null) { selectedLimbIndex = limbHealths.IndexOf(limbHealth); diff --git a/Barotrauma/BarotraumaClient/Source/EventInput/EventInput.cs b/Barotrauma/BarotraumaClient/Source/EventInput/EventInput.cs index e156dd669..ee3619634 100644 --- a/Barotrauma/BarotraumaClient/Source/EventInput/EventInput.cs +++ b/Barotrauma/BarotraumaClient/Source/EventInput/EventInput.cs @@ -256,4 +256,4 @@ namespace EventInput } #endif } -} +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaClient/Source/GUI/GUIStyle.cs b/Barotrauma/BarotraumaClient/Source/GUI/GUIStyle.cs index 9a02dd655..e93f22c76 100644 --- a/Barotrauma/BarotraumaClient/Source/GUI/GUIStyle.cs +++ b/Barotrauma/BarotraumaClient/Source/GUI/GUIStyle.cs @@ -118,27 +118,21 @@ namespace Barotrauma switch (subElement.Name.ToString().ToLowerInvariant()) { case "font": - if (Font == null) { continue; } Font.Size = GetFontSize(subElement); break; case "smallfont": - if (SmallFont == null) { continue; } SmallFont.Size = GetFontSize(subElement); break; case "largefont": - if (LargeFont == null) { continue; } LargeFont.Size = GetFontSize(subElement); break; case "objectivetitle": - if (ObjectiveTitleFont == null) { continue; } ObjectiveTitleFont.Size = GetFontSize(subElement); break; case "objectivename": - if (ObjectiveNameFont == null) { continue; } ObjectiveNameFont.Size = GetFontSize(subElement); break; case "videotitle": - if (VideoTitleFont == null) { continue; } VideoTitleFont.Size = GetFontSize(subElement); break; } diff --git a/Barotrauma/BarotraumaClient/Source/GameMain.cs b/Barotrauma/BarotraumaClient/Source/GameMain.cs index 0ce77a2bb..75b3fc23e 100644 --- a/Barotrauma/BarotraumaClient/Source/GameMain.cs +++ b/Barotrauma/BarotraumaClient/Source/GameMain.cs @@ -236,6 +236,8 @@ namespace Barotrauma GraphicsDeviceManager.PreferredBackBufferWidth = GraphicsWidth; GraphicsDeviceManager.PreferredBackBufferHeight = GraphicsHeight; + + GraphicsDeviceManager.ApplyChanges(); } public void ResetViewPort() @@ -253,7 +255,7 @@ namespace Barotrauma { base.Initialize(); - RequestGraphicsSettings(); + ApplyGraphicsSettings(); ScissorTestEnable = new RasterizerState() { ScissorTestEnable = true }; @@ -292,38 +294,6 @@ namespace Barotrauma #endif loadingCoroutine = CoroutineManager.StartCoroutine(Load(canLoadInSeparateThread), "", canLoadInSeparateThread); - -#if WINDOWS - var gameForm = (System.Windows.Forms.Form)System.Windows.Forms.Form.FromHandle(Window.Handle); - gameForm.Activated += new EventHandler(HandleFocus); - gameForm.Deactivate += new EventHandler(HandleDefocus); - if (WindowActive) { HandleFocus(null, null); } -#endif - } - -#if WINDOWS - private void HandleFocus(object sender, EventArgs e) - { - CoroutineManager.StopCoroutines("FocusCoroutine"); - CoroutineManager.StartCoroutine(FocusCoroutine(),"FocusCoroutine"); - } - - private IEnumerable FocusCoroutine() - { - yield return new WaitForSeconds(0.01f); - ApplyGraphicsSettings(); - yield return CoroutineStatus.Success; - } - - private void HandleDefocus(object sender, EventArgs e) - { - CoroutineManager.StopCoroutines("FocusCoroutine"); - GraphicsDeviceManager.IsFullScreen = false; - GraphicsDeviceManager.ApplyChanges(); - } -#endif - - loadingCoroutine = CoroutineManager.StartCoroutine(Load(canLoadInSeparateThread), "", canLoadInSeparateThread); } private void InitUserStats() diff --git a/Barotrauma/BarotraumaClient/Source/GameSettings.cs b/Barotrauma/BarotraumaClient/Source/GameSettings.cs index 859933c16..212acdc56 100644 --- a/Barotrauma/BarotraumaClient/Source/GameSettings.cs +++ b/Barotrauma/BarotraumaClient/Source/GameSettings.cs @@ -841,7 +841,7 @@ namespace Barotrauma GraphicsWidth = mode.Width; GraphicsHeight = mode.Height; - GameMain.Instance.RequestGraphicsSettings(); + GameMain.Instance.ApplyGraphicsSettings(); UnsavedSettings = true; return true; @@ -980,7 +980,7 @@ namespace Barotrauma if (GameMain.WindowMode != GameMain.Config.WindowMode) { - GameMain.Instance.RequestGraphicsSettings(); + GameMain.Instance.ApplyGraphicsSettings(); } if (GameMain.GraphicsWidth != GameMain.Config.GraphicsWidth || GameMain.GraphicsHeight != GameMain.Config.GraphicsHeight) diff --git a/Barotrauma/BarotraumaClient/Source/Screens/SteamWorkshopScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/SteamWorkshopScreen.cs index a1799b683..4ac368ed7 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/SteamWorkshopScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/SteamWorkshopScreen.cs @@ -967,7 +967,7 @@ namespace Barotrauma { try { - OpenFileDialog ofd = new OpenFileDialog() + Barotrauma.OpenFileDialog ofd = new Barotrauma.OpenFileDialog() { Multiselect = true, InitialDirectory = Path.GetFullPath(SteamManager.WorkshopItemStagingFolder), @@ -1078,7 +1078,7 @@ namespace Barotrauma { try { - OpenFileDialog ofd = new OpenFileDialog() + Barotrauma.OpenFileDialog ofd = new Barotrauma.OpenFileDialog() { InitialDirectory = Path.GetFullPath(SteamManager.WorkshopItemStagingFolder), Title = TextManager.Get("workshopitemaddfiles"), diff --git a/Barotrauma/BarotraumaClient/Source/Screens/SubEditorScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/SubEditorScreen.cs index c45c37df1..baac2f33d 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/SubEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/SubEditorScreen.cs @@ -1074,7 +1074,7 @@ namespace Barotrauma { OnClicked = (btn, userdata) => { - OpenFileDialog ofd = new OpenFileDialog() + Barotrauma.OpenFileDialog ofd = new Barotrauma.OpenFileDialog() { InitialDirectory = Path.GetFullPath(Submarine.SavePath), Filter = "PNG file|*.png", diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/HumanAIController.cs index e1587cdfc..dbf996ddf 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/HumanAIController.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/HumanAIController.cs @@ -202,22 +202,18 @@ namespace Barotrauma 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)) { bool oxygenLow = Character.OxygenAvailable < CharacterHealth.LowOxygenThreshold; bool highPressure = Character.CurrentHull == null || Character.CurrentHull.LethalPressure > 0 && Character.PressureProtection <= 0; bool shouldKeepTheGearOn = !ObjectiveManager.IsCurrentObjective(); - - // Don't allow to drop the diving suit in water or while climbing or if the current path has stairs - bool removeDivingSuit = - (oxygenLow && !highPressure) || - (!shouldKeepTheGearOn && - Character.CurrentHull.WaterPercentage < 1 && - !Character.IsClimbing && - steeringManager == insideSteering && - !PathSteering.InStairs); - + bool removeDivingSuit = oxygenLow && !highPressure; + if (!removeDivingSuit) + { + bool targetHasNoSuit = objectiveManager.CurrentOrder is AIObjectiveGoTo gtObj && gtObj.mimic && !HasDivingSuit(gtObj.Target as Character); + bool canDropTheSuit = Character.CurrentHull.WaterPercentage < 1 && !Character.IsClimbing && steeringManager == insideSteering && !PathSteering.InStairs; + removeDivingSuit = (!shouldKeepTheGearOn || targetHasNoSuit) && canDropTheSuit; + } if (removeDivingSuit) { var divingSuit = Character.Inventory.FindItemByIdentifier("divingsuit") ?? Character.Inventory.FindItemByTag("divingsuit"); @@ -227,7 +223,8 @@ namespace Barotrauma divingSuit.Drop(Character); } } - bool takeMaskOff = oxygenLow || (!shouldKeepTheGearOn && Character.CurrentHull.WaterPercentage < 20); + bool targetHasNoMask = objectiveManager.CurrentOrder is AIObjectiveGoTo gotoObjective && gotoObjective.mimic && !HasDivingMask(gotoObjective.Target as Character); + bool takeMaskOff = oxygenLow || (!shouldKeepTheGearOn && Character.CurrentHull.WaterPercentage < 20) || targetHasNoMask; if (takeMaskOff) { var mask = Character.Inventory.FindItemByIdentifier("divingmask"); @@ -336,7 +333,7 @@ namespace Barotrauma if (AIObjectiveFixLeaks.IsValidTarget(gap, Character)) { AddTargets(Character, gap); - if (newOrder == null) + if (newOrder == null && !gap.IsRoomToRoom) { var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportbreach"); newOrder = new Order(orderPrefab, hull, null, orderGiver: Character); diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindSafety.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindSafety.cs index 851a0cd01..3cdc8780b 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindSafety.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindSafety.cs @@ -122,9 +122,8 @@ namespace Barotrauma goToObjective = null; } TryAddSubObjective(ref goToObjective, - constructor: () => new AIObjectiveGoTo(currentSafeHull, character, objectiveManager, getDivingGearIfNeeded: false) + constructor: () => new AIObjectiveGoTo(currentSafeHull, character, objectiveManager, getDivingGearIfNeeded: true) { - // If we need diving gear, we should already have it, if possible. AllowGoingOutside = HumanAIController.HasDivingSuit(character) }, onAbandon: () => unreachable.Add(goToObjective.Target as Hull)); diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Params/Ragdoll/RagdollParams.cs b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Params/Ragdoll/RagdollParams.cs index 0ca685008..cec8bfc44 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Params/Ragdoll/RagdollParams.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Params/Ragdoll/RagdollParams.cs @@ -36,7 +36,8 @@ namespace Barotrauma [Serialize(1.0f, true), Editable(MIN_SCALE, MAX_SCALE, DecimalCount = 3)] public float JointScale { get; set; } - [Serialize(1f, true), Editable(DecimalCount = 2)] + // Don't show in the editor, because shouldn't be edited in runtime. Requires that the limb scale and the collider sizes are adjusted. TODO: automatize. + [Serialize(1f, false)] public float TextureScale { get; set; } [Serialize(45f, true), Editable(0f, 1000f)] diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaShared/Source/Characters/Health/CharacterHealth.cs index 4f5769f34..6425a776e 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Health/CharacterHealth.cs @@ -232,13 +232,13 @@ namespace Barotrauma } private LimbHealth GetMatchingLimbHealth(Limb limb) => limbHealths[limb.HealthIndex]; - private LimbHealth GetMathingLimbHealth(Affliction affliction) => GetMatchingLimbHealth(Character.AnimController.GetLimb(affliction.Prefab.IndicatorLimb)); + private LimbHealth GetMatchingLimbHealth(Affliction affliction) => GetMatchingLimbHealth(Character.AnimController.GetLimb(affliction.Prefab.IndicatorLimb)); /// /// Returns the limb afflictions and non-limbspecific afflictions that are set to be displayed on this limb. /// private IEnumerable GetMatchingAfflictions(LimbHealth limb, Func predicate) - => limb.Afflictions.Where(predicate).Union(afflictions.Where(a => predicate(a) && GetMathingLimbHealth(a) == limb)); + => limb.Afflictions.Where(predicate).Union(afflictions.Where(a => predicate(a) && GetMatchingLimbHealth(a) == limb)); public Affliction GetAffliction(string afflictionType, bool allowLimbAfflictions = true) { diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RepairTool.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RepairTool.cs index e39877bdb..a61ce2b23 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RepairTool.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RepairTool.cs @@ -40,6 +40,9 @@ namespace Barotrauma.Items.Components [Serialize(false, false)] public bool RepairThroughWalls { get; set; } + [Serialize(false, false)] + public bool RepairMultiple { get; set; } + public Vector2 TransformedBarrelPos { get @@ -158,12 +161,22 @@ namespace Barotrauma.Items.Components private void Repair(Vector2 rayStart, Vector2 rayEnd, float deltaTime, Character user, float degreeOfSuccess, List ignoredBodies) { var collisionCategories = Physics.CollisionWall | Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionLevel | Physics.CollisionRepair; - if (RepairThroughWalls) + if (RepairMultiple) { var bodies = Submarine.PickBodies(rayStart, rayEnd, ignoredBodies, collisionCategories, ignoreSensors: false, allowInsideFixture: true); + Type lastHitType = null; foreach (Body body in bodies) { - FixBody(user, deltaTime, degreeOfSuccess, body); + Type bodyType = body.UserData?.GetType(); + if (!RepairThroughWalls && bodyType != null && bodyType != lastHitType) + { + //stop the ray if it already hit a door/wall and is now about to hit some other type of entity + if (lastHitType == typeof(Item) || lastHitType == typeof(Structure)) { break; } + } + if (FixBody(user, deltaTime, degreeOfSuccess, body)) + { + if (bodyType != null) { lastHitType = bodyType; } + } } } else @@ -202,19 +215,19 @@ namespace Barotrauma.Items.Components } } - private void FixBody(Character user, float deltaTime, float degreeOfSuccess, Body targetBody) + private bool FixBody(Character user, float deltaTime, float degreeOfSuccess, Body targetBody) { - if (targetBody?.UserData == null) { return; } + if (targetBody?.UserData == null) { return false; } pickedPosition = Submarine.LastPickedPosition; if (targetBody.UserData is Structure targetStructure) { - if (!fixableEntities.Contains("structure") && !fixableEntities.Contains(targetStructure.Prefab.Identifier)) return; - if (targetStructure.IsPlatform) return; + if (!fixableEntities.Contains("structure") && !fixableEntities.Contains(targetStructure.Prefab.Identifier)) { return false; } + if (targetStructure.IsPlatform) { return false; } int sectionIndex = targetStructure.FindSectionIndex(ConvertUnits.ToDisplayUnits(pickedPosition)); - if (sectionIndex < 0) return; + if (sectionIndex < 0) { return false; } FixStructureProjSpecific(user, deltaTime, targetStructure, sectionIndex); targetStructure.AddDamage(sectionIndex, -StructureFixAmount * degreeOfSuccess, user); @@ -239,12 +252,14 @@ namespace Barotrauma.Items.Components targetCharacter.LastDamageSource = item; ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnUse, new List() { targetCharacter }); FixCharacterProjSpecific(user, deltaTime, targetCharacter); + return true; } else if (targetBody.UserData is Limb targetLimb) { targetLimb.character.LastDamageSource = item; ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnUse, new List() { targetLimb.character, targetLimb }); FixCharacterProjSpecific(user, deltaTime, targetLimb.character); + return true; } else if (targetBody.UserData is Item targetItem) { @@ -269,6 +284,7 @@ namespace Barotrauma.Items.Components #endif } FixItemProjSpecific(user, deltaTime, targetItem, prevCondition); + return true; } return false; } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Item.cs b/Barotrauma/BarotraumaShared/Source/Items/Item.cs index da2ae77e2..e60801429 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Item.cs @@ -2033,8 +2033,8 @@ namespace Barotrauma XElement element = new XElement("Item"); element.Add( - new XAttribute("name", prefab.Name), - new XAttribute("identifier", prefab.Identifier), + new XAttribute("name", Prefab.OriginalName), + new XAttribute("identifier", Prefab.Identifier), new XAttribute("ID", ID)); if (FlippedX) element.Add(new XAttribute("flippedx", true)); diff --git a/Barotrauma/BarotraumaShared/Source/Items/ItemPrefab.cs b/Barotrauma/BarotraumaShared/Source/Items/ItemPrefab.cs index 15f3074a6..39c924710 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/ItemPrefab.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/ItemPrefab.cs @@ -145,6 +145,11 @@ namespace Barotrauma private List fabricationRecipeElements = new List(); + /// + /// Original, non-translated name as defined in the xml + /// + public readonly string OriginalName; + public string ConfigFile { get { return configFile; } @@ -440,7 +445,7 @@ namespace Barotrauma configFile = filePath; ConfigElement = element; - string nonTranslatedName = element.GetAttributeString("name", ""); + OriginalName = element.GetAttributeString("name", ""); identifier = element.GetAttributeString("identifier", ""); //nameidentifier can be used to make multiple items use the same names and descriptions @@ -448,11 +453,11 @@ namespace Barotrauma if (string.IsNullOrEmpty(nameIdentifier)) { - name = TextManager.Get("EntityName." + identifier, true) ?? nonTranslatedName; + name = TextManager.Get("EntityName." + identifier, true) ?? OriginalName; } else { - name = TextManager.Get("EntityName." + nameIdentifier, true) ?? nonTranslatedName; + name = TextManager.Get("EntityName." + nameIdentifier, true) ?? OriginalName; } if (name == "") { DebugConsole.ThrowError("Unnamed item in " + filePath + "!"); } @@ -462,7 +467,7 @@ namespace Barotrauma Aliases = new HashSet (element.GetAttributeStringArray("aliases", null, convertToLowerInvariant: true) ?? element.GetAttributeStringArray("Aliases", new string[0], convertToLowerInvariant: true)); - Aliases.Add(nonTranslatedName.ToLowerInvariant()); + Aliases.Add(OriginalName.ToLowerInvariant()); if (!Enum.TryParse(element.GetAttributeString("category", "Misc"), true, out MapEntityCategory category)) { diff --git a/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs b/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs index f9620770f..2cb8f3192 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs @@ -730,7 +730,15 @@ namespace Barotrauma return closestBody; } - public static List PickBodies(Vector2 rayStart, Vector2 rayEnd, IEnumerable ignoredBodies = null, Category? collisionCategory = null, bool ignoreSensors = true, Predicate customPredicate = null, bool allowInsideFixture = false) + private static readonly Dictionary bodyDist = new Dictionary(); + private static readonly List bodies = new List(); + + /// + /// Returns a list of physics bodies the ray intersects with, sorted according to distance (the closest body is at the beginning of the list). + /// + /// Can be used to filter the bodies based on some condition. If the predicate returns false, the body isignored. + /// Should fixtures that the start of the ray is inside be returned + public static IEnumerable PickBodies(Vector2 rayStart, Vector2 rayEnd, IEnumerable ignoredBodies = null, Category? collisionCategory = null, bool ignoreSensors = true, Predicate customPredicate = null, bool allowInsideFixture = false) { if (Vector2.DistanceSquared(rayStart, rayEnd) < 0.00001f) { @@ -738,20 +746,25 @@ namespace Barotrauma } float closestFraction = 1.0f; - List bodies = new List(); + bodies.Clear(); + bodyDist.Clear(); GameMain.World.RayCast((fixture, point, normal, fraction) => { if (!CheckFixtureCollision(fixture, ignoredBodies, collisionCategory, ignoreSensors, customPredicate)) { return -1; } - if (fixture.Body != null) { bodies.Add(fixture.Body); } + if (fixture.Body != null) + { + bodies.Add(fixture.Body); + bodyDist[fixture.Body] = fraction; + } if (fraction < closestFraction) { lastPickedPosition = rayStart + (rayEnd - rayStart) * fraction; lastPickedFraction = fraction; lastPickedNormal = normal; } - - return fraction; + //continue + return -1; }, rayStart, rayEnd); if (allowInsideFixture) @@ -770,10 +783,12 @@ namespace Barotrauma lastPickedFraction = 0.0f; lastPickedNormal = Vector2.Normalize(rayEnd - rayStart); bodies.Add(fixture.Body); + bodyDist[fixture.Body] = 0.0f; return false; }, ref aabb); } + bodies.Sort((b1, b2) => { return bodyDist[b1].CompareTo(bodyDist[b2]); }); return bodies; }