409d4d9...aeafa16 (merge human-ai)

This commit is contained in:
Joonas Rikkonen
2019-03-18 22:52:17 +02:00
parent 80ab27df22
commit 3301bed442
88 changed files with 2334 additions and 1657 deletions

View File

@@ -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" />

View File

@@ -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;

View File

@@ -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

View File

@@ -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)
{

View File

@@ -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)

View File

@@ -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);
}
}
}

View File

@@ -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) =>
{

View File

@@ -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);
}

View File

@@ -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)
{

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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); }
}

View File

@@ -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 ?

View File

@@ -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;
}

View File

@@ -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; }

View File

@@ -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: () =>
{

View File

@@ -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);

View File

@@ -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);

View File

@@ -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++)

View File

@@ -1571,8 +1571,6 @@ namespace Barotrauma
}
GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.Status });
item.NeedsPositionUpdate = true;
}
}

View File

@@ -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

View File

@@ -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))
{

View File

@@ -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" />

View File

@@ -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>

View File

@@ -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));
}
}

View File

@@ -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);
}
}
}

View File

@@ -4,7 +4,6 @@ namespace Barotrauma
{
interface ISteerable
{
Vector2 Steering
{
get;
@@ -25,8 +24,5 @@ namespace Barotrauma
{
get;
}
}
}

View File

@@ -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();
}

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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

View File

@@ -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;

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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, "")
{

View File

@@ -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);
}
}
}

View File

@@ -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()

View File

@@ -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);
}
}

View File

@@ -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)

View File

@@ -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;
}
}
}
}
}

View File

@@ -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)

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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++)
{

View File

@@ -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;

View File

@@ -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;

View File

@@ -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)

View File

@@ -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);
}
},
() =>

View File

@@ -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>

View File

@@ -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()
{

View File

@@ -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;
}

View File

@@ -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)

View File

@@ -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;

View File

@@ -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);

View File

@@ -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)

View File

@@ -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)

View File

@@ -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);

View File

@@ -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; }

View File

@@ -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)
{

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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)

View File

@@ -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)
{

View File

@@ -160,6 +160,8 @@ namespace Barotrauma
}
}
public float WaterPercentage => MathUtils.Percentage(WaterVolume, Volume);
public float OxygenPercentage
{
get { return oxygen / Volume * 100.0f; }

View File

@@ -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

View File

@@ -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);

View File

@@ -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>

View File

@@ -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;
}
}

View File

@@ -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; }

View File

@@ -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

View File

@@ -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

View File

@@ -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;