409d4d9...aeafa16 (merge human-ai)
This commit is contained in:
@@ -15,7 +15,6 @@
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\AI\AITarget.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\AI\EnemyAIController.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\AI\HumanAIController.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\Animation\HumanoidAnimParams.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\Animation\Ragdoll.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\Attack.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\Character.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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<string> lines = new List<string>();
|
||||
foreach (MapEntityPrefab me in MapEntityPrefab.List)
|
||||
{
|
||||
lines.Add("<EntityName." + me.Identifier + ">" + me.Name + "</" + me.Identifier + ".Name>");
|
||||
lines.Add("<EntityDescription." + me.Identifier + ">" + me.Description + "</" + me.Identifier + ".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) =>
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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<Items.Components.Ladder>() == 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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Should report buttons be visible on the screen atm?
|
||||
/// </summary>
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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); }
|
||||
}
|
||||
|
||||
@@ -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 ?
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -240,22 +240,23 @@ namespace Barotrauma
|
||||
var holdable = GetComponent<Holdable>();
|
||||
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; }
|
||||
|
||||
|
||||
@@ -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: () =>
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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++)
|
||||
|
||||
@@ -1571,8 +1571,6 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.Status });
|
||||
|
||||
item.NeedsPositionUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>(T ic) where T : ItemComponent, IServerSerializable
|
||||
|
||||
@@ -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))
|
||||
{
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\AI\Objectives\AIObjectiveGetItem.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\AI\Objectives\AIObjectiveGoTo.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\AI\Objectives\AIObjectiveIdle.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\AI\Objectives\AIObjectiveLoop.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\AI\Objectives\AIObjectiveManager.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\AI\Objectives\AIObjectiveOperateItem.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\AI\Objectives\AIObjectivePumpWater.cs" />
|
||||
@@ -47,7 +48,6 @@
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\Animation\AnimController.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\Animation\FishAnimController.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\Animation\HumanoidAnimController.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\Animation\HumanoidAnimParams.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\Animation\Params\Animation\AnimationParams.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\Animation\Params\Animation\FishAnimations.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\Animation\Params\Animation\HumanoidAnimations.cs" />
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
<Folder Include="$(MSBuildThisFileDirectory)Content\Characters\Tigerthresherboss\" />
|
||||
<Folder Include="$(MSBuildThisFileDirectory)Content\Characters\Tigerthresher\Animations\" />
|
||||
<Folder Include="$(MSBuildThisFileDirectory)Content\Characters\Watcher\Animations\" />
|
||||
<Folder Include="$(MSBuildThisFileDirectory)Content\Fonts\" />
|
||||
<Folder Include="$(MSBuildThisFileDirectory)Content\BackgroundCreatures\" />
|
||||
<Folder Include="$(MSBuildThisFileDirectory)Data\Saves\" />
|
||||
</ItemGroup>
|
||||
@@ -343,6 +342,96 @@
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\Effects\waterbump.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\Fonts\NotoSans\LICENSE_OFL.txt">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\Fonts\NotoSans\NotoSans-Black.ttf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\Fonts\NotoSans\NotoSans-Bold.ttf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\Fonts\NotoSans\NotoSans-ExtraBold.ttf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\Fonts\NotoSans\NotoSans-Medium.ttf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\Fonts\NotoSans\NotoSans-Regular.ttf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\Fonts\NotoSans\NotoSans-SemiBold.ttf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\Fonts\Roboto\LICENSE.txt">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\Fonts\Roboto\Roboto-Black.ttf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\Fonts\Roboto\Roboto-BlackItalic.ttf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\Fonts\Roboto\Roboto-Bold.ttf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\Fonts\Roboto\Roboto-BoldItalic.ttf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\Fonts\Roboto\Roboto-Italic.ttf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\Fonts\Roboto\Roboto-Light.ttf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\Fonts\Roboto\Roboto-LightItalic.ttf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\Fonts\Roboto\Roboto-Medium.ttf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\Fonts\Roboto\Roboto-MediumItalic.ttf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\Fonts\Roboto\Roboto-Regular.ttf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\Fonts\Roboto\Roboto-Thin.ttf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\Fonts\Roboto\Roboto-ThinItalic.ttf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\Fonts\WorkSans\OFL.txt">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\Fonts\WorkSans\WorkSans-Black.ttf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\Fonts\WorkSans\WorkSans-Bold.ttf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\Fonts\WorkSans\WorkSans-ExtraBold.ttf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\Fonts\WorkSans\WorkSans-ExtraLight.ttf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\Fonts\WorkSans\WorkSans-Light.ttf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\Fonts\WorkSans\WorkSans-Medium.ttf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\Fonts\WorkSans\WorkSans-Regular.ttf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\Fonts\WorkSans\WorkSans-SemiBold.ttf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\Fonts\WorkSans\WorkSans-Thin.ttf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\Items\Alien\AlienArtifact1.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
@@ -1808,6 +1897,9 @@
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\UI\UI_Atlas2.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\UI\UI_MainMenu.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Data\clientpermissions.xml">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
@@ -3017,4 +3109,4 @@
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Hull> UnsafeHulls { get; private set; } = new HashSet<Hull>();
|
||||
|
||||
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<Items.Components.Ladder>() != 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>() { 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;
|
||||
|
||||
/// <summary>
|
||||
/// Check whether the character has a diving suit in usable condition plus some oxygen.
|
||||
/// </summary>
|
||||
public static bool HasDivingSuit(Character character) => HasItem(character, "divingsuit", "oxygensource");
|
||||
|
||||
/// <summary>
|
||||
/// Check whether the character has a diving mask in usable condition plus some oxygen.
|
||||
/// </summary>
|
||||
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)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the hull safety for all ai characters in the team.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ namespace Barotrauma
|
||||
{
|
||||
interface ISteerable
|
||||
{
|
||||
|
||||
Vector2 Steering
|
||||
{
|
||||
get;
|
||||
@@ -25,8 +24,5 @@ namespace Barotrauma
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<AIObjective> 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<AIObjective> subObjectives = new List<AIObjective>();
|
||||
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<AIObjective> 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<AIObjective>();
|
||||
this.character = character;
|
||||
this.option = option;
|
||||
|
||||
@@ -38,7 +48,36 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the subobjectives in the given collection are removed from the subobjectives. And if so, removes it also from the dictionary.
|
||||
/// </summary>
|
||||
protected void SyncRemovedObjectives<T1, T2>(Dictionary<T1, T2> dictionary, IEnumerable<T1> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<PowerContainer>
|
||||
{
|
||||
private List<PowerContainer> availableBatteries;
|
||||
public override string DebugTag => "charge batteries";
|
||||
private readonly IEnumerable<PowerContainer> 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<PowerContainer>();
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.Submarine == null) continue;
|
||||
if (item.Prefab.Identifier != "battery" && !item.HasTag("battery")) continue;
|
||||
|
||||
var powerContainer = item.GetComponent<PowerContainer>();
|
||||
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<PowerContainer>()).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<PowerContainer>();
|
||||
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<PowerContainer> GetList() => batteryList;
|
||||
protected override AIObjective ObjectiveConstructor(PowerContainer battery) => new AIObjectiveOperateItem(battery, character, Option, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Hull, AIObjectiveExtinguishFire> extinguishObjectives = new Dictionary<Hull, AIObjectiveExtinguishFire>();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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, "")
|
||||
{
|
||||
|
||||
@@ -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<Hull> unreachable;
|
||||
private List<Hull> unreachable = new List<Hull>();
|
||||
|
||||
private float currenthullSafety;
|
||||
|
||||
private float searchHullTimer;
|
||||
|
||||
private AIObjectiveGoTo goToObjective;
|
||||
private AIObjective divingGearObjective;
|
||||
|
||||
public float? OverrideCurrentHullSafety;
|
||||
|
||||
public AIObjectiveFindSafety(Character character)
|
||||
: base(character, "")
|
||||
{
|
||||
unreachable = new List<Hull>();
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<RepairTool>();
|
||||
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()
|
||||
|
||||
@@ -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<Gap>
|
||||
{
|
||||
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<AIObjectiveFixLeak> 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<AIObjectiveFixLeak>(); }
|
||||
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<Gap> GetList() => Gap.GapList;
|
||||
protected override AIObjective ObjectiveConstructor(Gap gap) => new AIObjectiveFixLeak(gap, character);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ namespace Barotrauma
|
||||
{
|
||||
class AIObjectiveGetItem : AIObjective
|
||||
{
|
||||
public override string DebugTag => "get item";
|
||||
|
||||
public Func<Item, float> 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)
|
||||
|
||||
@@ -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<Ladder>() == 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Ladder>() == 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<Hull> targetHulls = new List<Hull>(20);
|
||||
private readonly List<float> hullWeights = new List<float>(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<Hull> targetHulls = new List<Hull>(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)
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
abstract class AIObjectiveLoop<T> : AIObjective
|
||||
{
|
||||
protected List<T> targets = new List<T>();
|
||||
protected Dictionary<T, AIObjective> objectives = new Dictionary<T, AIObjective>();
|
||||
protected HashSet<T> ignoreList = new HashSet<T>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List of all possible items of the specified type. Used for filtering the removed objectives.
|
||||
/// </summary>
|
||||
protected abstract IEnumerable<T> GetList();
|
||||
protected abstract float Average(T target);
|
||||
protected abstract AIObjective ObjectiveConstructor(T target);
|
||||
protected abstract bool Filter(T target);
|
||||
}
|
||||
}
|
||||
@@ -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<AIObjective> objectives;
|
||||
public List<AIObjective> Objectives { get; private set; }
|
||||
|
||||
private Character character;
|
||||
|
||||
private AIObjective currentOrder;
|
||||
|
||||
/// <summary>
|
||||
/// 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)
|
||||
/// </summary>
|
||||
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<AIObjective>();
|
||||
Objectives = new List<AIObjective>();
|
||||
}
|
||||
|
||||
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<AIObjective, CoroutineHandle> DelayedObjectives { get; private set; } = new Dictionary<AIObjective, CoroutineHandle>();
|
||||
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<AIObjective, CoroutineHandle> DelayedObjectives { get; private set; } = new Dictionary<AIObjective, CoroutineHandle>();
|
||||
@@ -64,7 +75,7 @@ namespace Barotrauma
|
||||
|
||||
public T GetObjective<T>() 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<Steering>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Pump>
|
||||
{
|
||||
private const float FindPumpsInterval = 5.0f;
|
||||
public override string DebugTag => "pump water";
|
||||
public override bool KeepDivingGearOn => true;
|
||||
private readonly IEnumerable<Pump> pumpList;
|
||||
|
||||
private string orderOption;
|
||||
private List<Pump> 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<Pump>()).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<Steering>(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<Pump>();
|
||||
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<Pump>();
|
||||
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<Steering>(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<Pump> GetList() => pumpList;
|
||||
protected override AIObjective ObjectiveConstructor(Pump pump) => new AIObjectiveOperateItem(pump, character, Option, false);
|
||||
protected override float Average(Pump target) => 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Item>
|
||||
{
|
||||
public override string DebugTag => "repair items";
|
||||
public override bool KeepDivingGearOn => true;
|
||||
|
||||
/// <summary>
|
||||
/// Should the character only attempt to fix items they have the skills to fix, or any damaged item
|
||||
/// </summary>
|
||||
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<Item> GetList() => Item.ItemList;
|
||||
protected override AIObjective ObjectiveConstructor(Item item) => new AIObjectiveRepairItem(character, item);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<Character> rescueTargets;
|
||||
|
||||
public AIObjectiveRescueAll(Character character)
|
||||
: base (character, "")
|
||||
public AIObjectiveRescueAll(Character character) : base (character, "")
|
||||
{
|
||||
rescueTargets = new List<Character>();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<Holdable>();
|
||||
|
||||
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++)
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies temporary limits to the speed (damage).
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
},
|
||||
() =>
|
||||
|
||||
@@ -6,13 +6,13 @@ namespace Barotrauma.Extensions
|
||||
public static class VectorExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -20,7 +20,7 @@ namespace Barotrauma.Extensions
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -31,6 +31,22 @@ namespace Barotrauma.Extensions
|
||||
return -Forward(radians, length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a forward pointing vector based on the rotation (in radians). TODO: remove when the implications have been neutralized
|
||||
/// </summary>
|
||||
public static Vector2 ForwardFlipped(float radians, float length = 1)
|
||||
{
|
||||
return new Vector2((float)Math.Sin(radians), (float)Math.Cos(radians)) * length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a backward pointing vector based on the rotation (in radians). TODO: remove when the implications have been neutralized
|
||||
/// </summary>
|
||||
public static Vector2 BackwardFlipped(float radians, float length = 1)
|
||||
{
|
||||
return -ForwardFlipped(radians, length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a normalized perpendicular vector to the right from a forward vector.
|
||||
/// </summary>
|
||||
|
||||
@@ -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<string>();
|
||||
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<string>();
|
||||
|
||||
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<string> contentPackagePaths)
|
||||
{
|
||||
var missingPackagePaths = new List<string>();
|
||||
var incompatiblePackages = new List<ContentPackage>();
|
||||
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()
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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<Powered, Connection> 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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -160,6 +160,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public float WaterPercentage => MathUtils.Percentage(WaterVolume, Volume);
|
||||
|
||||
public float OxygenPercentage
|
||||
{
|
||||
get { return oxygen / Volume * 100.0f; }
|
||||
|
||||
@@ -30,6 +30,12 @@ namespace Barotrauma
|
||||
public readonly string Name;
|
||||
|
||||
public readonly List<LocationTypeChange> CanChangeTo = new List<LocationTypeChange>();
|
||||
|
||||
public bool UseInMainMenu
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public List<string> 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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
private readonly List<Submarine> connectedSubs = new List<Submarine>(2);
|
||||
/// <summary>
|
||||
/// Returns a list of all submarines that are connected to this one via docking ports, including this sub.
|
||||
/// </summary>
|
||||
public List<Submarine> GetConnectedSubs()
|
||||
{
|
||||
List<Submarine> connectedSubs = new List<Submarine> { 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<Hull> GetHulls(bool alsoFromConnectedSubs) => GetEntities(alsoFromConnectedSubs, Hull.hullList);
|
||||
public List<Gap> GetGaps(bool alsoFromConnectedSubs) => GetEntities(alsoFromConnectedSubs, Gap.GapList);
|
||||
public List<Item> GetItems(bool alsoFromConnectedSubs) => GetEntities(alsoFromConnectedSubs, Item.ItemList);
|
||||
|
||||
public List<T> GetEntities<T>(bool includingConnectedSubs, List<T> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the sub whose borders contain the position
|
||||
/// </summary>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,6 +242,11 @@ namespace Barotrauma
|
||||
get { return body.Rotation; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Takes flipping (Dir) into account.
|
||||
/// </summary>
|
||||
public float TransformedRotation => Dir < 0 ? Rotation - MathHelper.Pi : Rotation;
|
||||
|
||||
public Vector2 LinearVelocity
|
||||
{
|
||||
get { return body.LinearVelocity; }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
BIN
Barotrauma/BarotraumaShared/Submarines/Humpback_LadderTest.sub
Normal file
BIN
Barotrauma/BarotraumaShared/Submarines/Humpback_LadderTest.sub
Normal file
Binary file not shown.
BIN
Barotrauma/BarotraumaShared/Submarines/Remora_LadderTest.sub
Normal file
BIN
Barotrauma/BarotraumaShared/Submarines/Remora_LadderTest.sub
Normal file
Binary file not shown.
Reference in New Issue
Block a user