(03ab09991) Load chinese fonts dynamically, removed unnecessary duplicate block from DynamicRenderAtlas
This commit is contained in:
@@ -188,6 +188,7 @@
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Source\Screens\CampaignSetupUI.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Source\Screens\CampaignUI.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Source\Screens\CharacterEditorScreen.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Source\Screens\CreditsPlayer.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Source\Screens\GameScreen.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Source\Screens\LevelEditorScreen.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Source\Screens\LobbyScreen.cs" />
|
||||
|
||||
@@ -91,7 +91,7 @@ namespace Barotrauma
|
||||
|
||||
GUI.SmallFont.DrawString(spriteBatch,
|
||||
currentNode.ID.ToString(),
|
||||
new Vector2(currentNode.DrawPosition.X - 10, -currentNode.DrawPosition.Y - 30),
|
||||
new Vector2(currentNode.DrawPosition.X + 20, -currentNode.DrawPosition.Y - 20),
|
||||
Color.Red);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using FarseerPhysics;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -38,20 +37,16 @@ namespace Barotrauma
|
||||
var currentOrder = ObjectiveManager.CurrentOrder;
|
||||
if (currentOrder != null)
|
||||
{
|
||||
GUI.DrawString(spriteBatch, pos + textOffset, $"ORDER: {currentOrder.DebugTag} ({currentOrder.GetPriority().FormatZeroDecimal()})", Color.White, Color.Black);
|
||||
}
|
||||
else if (ObjectiveManager.WaitTimer > 0)
|
||||
{
|
||||
GUI.DrawString(spriteBatch, pos + textOffset, $"Waiting... {ObjectiveManager.WaitTimer.FormatZeroDecimal()}", Color.White, Color.Black);
|
||||
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().FormatZeroDecimal()})", Color.White, Color.Black);
|
||||
var subObjective = currentObjective.SubObjectives.FirstOrDefault();
|
||||
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().FormatZeroDecimal()})", Color.White, Color.Black);
|
||||
GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(0, 40), $"SUBOBJECTIVE: {subObjective.DebugTag} ({subObjective.GetPriority(ObjectiveManager).FormatZeroDecimal()})", Color.White, Color.Black);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,8 +75,8 @@ namespace Barotrauma
|
||||
|
||||
GUI.SmallFont.DrawString(spriteBatch,
|
||||
currentNode.ID.ToString(),
|
||||
new Vector2(currentNode.DrawPosition.X - 10, -currentNode.DrawPosition.Y - 30),
|
||||
Color.Blue);
|
||||
new Vector2(currentNode.DrawPosition.X + 20, -currentNode.DrawPosition.Y - 20),
|
||||
Color.SkyBlue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,90 +121,7 @@ namespace Barotrauma
|
||||
MainLimb.PullJointWorldAnchorB = Collider.SimPosition;
|
||||
MainLimb.PullJointEnabled = true;
|
||||
}
|
||||
character.SelectedConstruction = character.MemState[0].SelectedItem;
|
||||
}
|
||||
|
||||
if (character.MemState[0].Animation == AnimController.Animation.CPR)
|
||||
{
|
||||
character.AnimController.Anim = AnimController.Animation.CPR;
|
||||
}
|
||||
else if (character.AnimController.Anim == AnimController.Animation.CPR)
|
||||
{
|
||||
character.AnimController.Anim = AnimController.Animation.None;
|
||||
}
|
||||
|
||||
Vector2 newVelocity = Collider.LinearVelocity;
|
||||
Vector2 newPosition = Collider.SimPosition;
|
||||
float newRotation = Collider.Rotation;
|
||||
float newAngularVelocity = Collider.AngularVelocity;
|
||||
Collider.CorrectPosition(character.MemState, out newPosition, out newVelocity, out newRotation, out newAngularVelocity);
|
||||
|
||||
newVelocity = newVelocity.ClampLength(100.0f);
|
||||
if (!MathUtils.IsValid(newVelocity)) { newVelocity = Vector2.Zero; }
|
||||
overrideTargetMovement = newVelocity.LengthSquared() > 0.01f ? newVelocity : Vector2.Zero;
|
||||
|
||||
Collider.LinearVelocity = newVelocity;
|
||||
Collider.AngularVelocity = newAngularVelocity;
|
||||
|
||||
float distSqrd = Vector2.DistanceSquared(newPosition, Collider.SimPosition);
|
||||
float errorTolerance = character.AllowInput ? 0.01f : 0.2f;
|
||||
if (distSqrd > errorTolerance)
|
||||
{
|
||||
if (distSqrd > 10.0f || !character.AllowInput)
|
||||
{
|
||||
Collider.TargetRotation = newRotation;
|
||||
SetPosition(newPosition, lerp: distSqrd < 5.0f, ignorePlatforms: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
Collider.TargetRotation = newRotation;
|
||||
Collider.TargetPosition = newPosition;
|
||||
Collider.MoveToTargetPosition(true);
|
||||
}
|
||||
}
|
||||
|
||||
//unconscious/dead characters can't correct their position using AnimController movement
|
||||
// -> we need to correct it manually
|
||||
if (!character.AllowInput)
|
||||
{
|
||||
float mainLimbDistSqrd = Vector2.DistanceSquared(MainLimb.PullJointWorldAnchorA, Collider.SimPosition);
|
||||
float mainLimbErrorTolerance = 0.1f;
|
||||
//if the main limb is roughly at the correct position and the collider isn't moving (much at least),
|
||||
//don't attempt to correct the position.
|
||||
if (mainLimbDistSqrd > mainLimbErrorTolerance || Collider.LinearVelocity.LengthSquared() > 0.05f)
|
||||
{
|
||||
MainLimb.PullJointWorldAnchorB = Collider.SimPosition;
|
||||
MainLimb.PullJointEnabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
character.MemLocalState.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
//remove states with a timestamp (there may still timestamp-based states
|
||||
//in the list if the controlled character switches from timestamp-based interpolation to ID-based)
|
||||
character.MemState.RemoveAll(m => m.Timestamp > 0.0f);
|
||||
|
||||
for (int i = 0; i < character.MemLocalState.Count; i++)
|
||||
{
|
||||
if (character.Submarine == null)
|
||||
{
|
||||
//transform in-sub coordinates to outside coordinates
|
||||
if (character.MemLocalState[i].Position.Y > lowestSubPos)
|
||||
{
|
||||
character.MemLocalState[i].TransformInToOutside();
|
||||
}
|
||||
}
|
||||
else if (currentHull?.Submarine != null)
|
||||
{
|
||||
//transform outside coordinates to in-sub coordinates
|
||||
if (character.MemLocalState[i].Position.Y < lowestSubPos)
|
||||
{
|
||||
character.MemLocalState[i].TransformOutToInside(currentHull.Submarine);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
character.MemLocalState.Clear();
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace Barotrauma
|
||||
Job.Name, textColor: Job.Prefab.UIColor, font: font);
|
||||
}
|
||||
|
||||
if (personalityTrait != null)
|
||||
if (personalityTrait != null && TextManager.Language == "English")
|
||||
{
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), headerTextArea.RectTransform),
|
||||
TextManager.Get("PersonalityTrait") + ": " + personalityTrait.Name, font: font);
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Barotrauma
|
||||
var skillContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 0.5f), paddedFrame.RectTransform)
|
||||
{ RelativeOffset = new Vector2(0.0f, 0.2f + descriptionBlock.RectTransform.RelativeSize.Y) });
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), skillContainer.RectTransform),
|
||||
TextManager.Get("Skills") + ": ", font: GUI.LargeFont);
|
||||
TextManager.Get("Skills"), font: GUI.LargeFont);
|
||||
foreach (SkillPrefab skill in Skills)
|
||||
{
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), skillContainer.RectTransform),
|
||||
@@ -30,7 +30,7 @@ namespace Barotrauma
|
||||
var itemContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 0.5f), paddedFrame.RectTransform, Anchor.TopRight)
|
||||
{ RelativeOffset = new Vector2(0.0f, 0.2f + descriptionBlock.RectTransform.RelativeSize.Y) });
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), itemContainer.RectTransform),
|
||||
TextManager.Get("Items") + ": ", font: GUI.LargeFont);
|
||||
TextManager.Get("Items", fallBackTag: "mapentitycategory.equipment"), font: GUI.LargeFont);
|
||||
foreach (string itemName in ItemNames)
|
||||
{
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), itemContainer.RectTransform),
|
||||
|
||||
@@ -56,11 +56,11 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
public ScalableFont(XElement element, GraphicsDevice gd = null)
|
||||
: this (element.GetAttributeString("file", ""), (uint)element.GetAttributeInt("size", 14), gd)
|
||||
: this (element.GetAttributeString("file", ""), (uint)element.GetAttributeInt("size", 14), gd, element.GetAttributeBool("dynamicloading", false))
|
||||
{
|
||||
}
|
||||
|
||||
public ScalableFont(string filename, uint size, GraphicsDevice gd = null)
|
||||
public ScalableFont(string filename, uint size, GraphicsDevice gd = null, bool dynamicLoading = false)
|
||||
{
|
||||
if (Lib == null) Lib = new Library();
|
||||
this.filename = filename;
|
||||
@@ -249,15 +249,7 @@ namespace Barotrauma
|
||||
{
|
||||
throw new Exception(filename + ", " + size.ToString() + ", " + (char)character + "; Glyph dimensions exceed texture atlas dimensions");
|
||||
}
|
||||
|
||||
//no more room in current texture atlas, create a new one
|
||||
if (currentDynamicAtlasCoords.Y + glyphHeight + 2 > texDims - 1)
|
||||
{
|
||||
currentDynamicAtlasCoords.X = 0;
|
||||
currentDynamicAtlasCoords.Y = 0;
|
||||
textures.Add(new Texture2D(gd, texDims, texDims, false, SurfaceFormat.Color));
|
||||
}
|
||||
|
||||
|
||||
currentDynamicAtlasNextY = Math.Max(currentDynamicAtlasNextY, glyphHeight + 2);
|
||||
if (currentDynamicAtlasCoords.X + glyphWidth + 2 > texDims - 1)
|
||||
{
|
||||
@@ -270,6 +262,7 @@ namespace Barotrauma
|
||||
{
|
||||
currentDynamicAtlasCoords.X = 0;
|
||||
currentDynamicAtlasCoords.Y = 0;
|
||||
currentDynamicAtlasNextY = 0;
|
||||
textures.Add(new Texture2D(gd, texDims, texDims, false, SurfaceFormat.Color));
|
||||
}
|
||||
|
||||
@@ -317,6 +310,11 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
uint charIndex = text[i];
|
||||
if (DynamicLoading && !texCoords.ContainsKey(charIndex))
|
||||
{
|
||||
DynamicRenderAtlas(graphicsDevice, charIndex);
|
||||
}
|
||||
|
||||
if (texCoords.TryGetValue(charIndex, out GlyphData gd) || texCoords.TryGetValue(9633, out gd)) //9633 = white square
|
||||
{
|
||||
if (gd.texIndex >= 0)
|
||||
@@ -351,7 +349,13 @@ namespace Barotrauma
|
||||
currentPos.Y += baseHeight * 1.8f;
|
||||
continue;
|
||||
}
|
||||
uint charIndex = text[i];
|
||||
|
||||
uint charIndex = text[i];
|
||||
if (DynamicLoading && !texCoords.ContainsKey(charIndex))
|
||||
{
|
||||
DynamicRenderAtlas(graphicsDevice, charIndex);
|
||||
}
|
||||
|
||||
if (texCoords.TryGetValue(charIndex, out GlyphData gd) || texCoords.TryGetValue(9633, out gd)) //9633 = white square
|
||||
{
|
||||
if (gd.texIndex >= 0)
|
||||
@@ -383,6 +387,10 @@ namespace Barotrauma
|
||||
continue;
|
||||
}
|
||||
uint charIndex = text[i];
|
||||
if (DynamicLoading && !texCoords.ContainsKey(charIndex))
|
||||
{
|
||||
DynamicRenderAtlas(graphicsDevice, charIndex);
|
||||
}
|
||||
if (texCoords.TryGetValue(charIndex, out GlyphData gd))
|
||||
{
|
||||
currentLineX += gd.advance;
|
||||
@@ -396,6 +404,10 @@ namespace Barotrauma
|
||||
{
|
||||
Vector2 retVal = Vector2.Zero;
|
||||
retVal.Y = baseHeight * 1.8f;
|
||||
if (DynamicLoading && !texCoords.ContainsKey(c))
|
||||
{
|
||||
DynamicRenderAtlas(graphicsDevice, c);
|
||||
}
|
||||
if (texCoords.TryGetValue(c, out GlyphData gd))
|
||||
{
|
||||
retVal.X = gd.advance;
|
||||
|
||||
@@ -166,9 +166,7 @@ namespace Barotrauma
|
||||
get { return enabled; }
|
||||
set { enabled = value; }
|
||||
}
|
||||
|
||||
public bool TileSprites;
|
||||
|
||||
|
||||
private static GUITextBlock toolTipBlock;
|
||||
|
||||
public Vector2 Center
|
||||
|
||||
@@ -12,6 +12,10 @@ namespace Barotrauma
|
||||
|
||||
private XElement configElement;
|
||||
|
||||
private GraphicsDevice graphicsDevice;
|
||||
|
||||
private ScalableFont defaultFont;
|
||||
|
||||
public ScalableFont Font { get; private set; }
|
||||
public ScalableFont SmallFont { get; private set; }
|
||||
public ScalableFont LargeFont { get; private set; }
|
||||
@@ -83,6 +87,26 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the default font of the currently selected language
|
||||
/// </summary>
|
||||
public ScalableFont LoadCurrentDefaultFont()
|
||||
{
|
||||
defaultFont?.Dispose();
|
||||
defaultFont = null;
|
||||
foreach (XElement subElement in configElement.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "font":
|
||||
defaultFont = LoadFont(subElement, graphicsDevice);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return defaultFont;
|
||||
}
|
||||
|
||||
|
||||
private void RescaleFonts()
|
||||
{
|
||||
foreach (XElement subElement in configElement.Elements())
|
||||
@@ -113,9 +137,10 @@ namespace Barotrauma
|
||||
|
||||
private ScalableFont LoadFont(XElement element, GraphicsDevice graphicsDevice)
|
||||
{
|
||||
string file = GetFontFilePath(element);
|
||||
uint size = GetFontSize(element);
|
||||
return new ScalableFont(file, size, graphicsDevice);
|
||||
string file = GetFontFilePath(element);
|
||||
uint size = GetFontSize(element);
|
||||
bool dynamicLoading = GetFontDynamicLoading(element);
|
||||
return new ScalableFont(file, size, graphicsDevice, dynamicLoading);
|
||||
}
|
||||
|
||||
private uint GetFontSize(XElement element)
|
||||
@@ -146,6 +171,20 @@ namespace Barotrauma
|
||||
return element.GetAttributeString("file", "");
|
||||
}
|
||||
|
||||
private bool GetFontDynamicLoading(XElement element)
|
||||
{
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
if (subElement.Name.ToString().ToLowerInvariant() != "override") { continue; }
|
||||
string language = subElement.GetAttributeString("language", "").ToLowerInvariant();
|
||||
if (GameMain.Config.Language.ToLowerInvariant() == language)
|
||||
{
|
||||
return subElement.GetAttributeBool("dynamicloading", false);
|
||||
}
|
||||
}
|
||||
return element.GetAttributeBool("dynamicloading", false);
|
||||
}
|
||||
|
||||
public GUIComponentStyle GetComponentStyle(string name)
|
||||
{
|
||||
componentStyles.TryGetValue(name.ToLowerInvariant(), out GUIComponentStyle style);
|
||||
|
||||
@@ -5,12 +5,13 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Media;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class LoadingScreen
|
||||
{
|
||||
private Texture2D backgroundTexture, monsterTexture, titleTexture;
|
||||
private Texture2D backgroundTexture;
|
||||
|
||||
private RenderTarget2D renderTarget;
|
||||
|
||||
@@ -43,18 +44,7 @@ namespace Barotrauma
|
||||
|
||||
private object loadMutex = new object();
|
||||
private float? loadState;
|
||||
|
||||
public Vector2 TitleSize
|
||||
{
|
||||
get { return new Vector2(titleTexture.Width, titleTexture.Height); }
|
||||
}
|
||||
|
||||
public float Scale
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
|
||||
public float? LoadState
|
||||
{
|
||||
get
|
||||
@@ -103,7 +93,7 @@ namespace Barotrauma
|
||||
try
|
||||
{
|
||||
DrawSplashScreen(spriteBatch);
|
||||
if (SplashScreen!=null && SplashScreen.IsPlaying) return;
|
||||
if (SplashScreen != null && SplashScreen.IsPlaying) return;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -111,20 +101,27 @@ namespace Barotrauma
|
||||
GameMain.Config.EnableSplashScreen = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var titleStyle = GUI.Style?.GetComponentStyle("TitleText");
|
||||
Sprite titleSprite = null;
|
||||
if (titleStyle != null && titleStyle.Sprites.ContainsKey(GUIComponent.ComponentState.None))
|
||||
{
|
||||
titleSprite = titleStyle.Sprites[GUIComponent.ComponentState.None].First()?.Sprite;
|
||||
}
|
||||
|
||||
drawn = true;
|
||||
|
||||
graphics.SetRenderTarget(renderTarget);
|
||||
|
||||
Scale = GameMain.GraphicsHeight / 1500.0f;
|
||||
float backgroundScale = GameMain.GraphicsHeight / 1500.0f;
|
||||
float titleScale = MathHelper.SmoothStep(0.8f, 1.0f, state / 10.0f) * GameMain.GraphicsHeight / 1000.0f;
|
||||
|
||||
state += deltaTime;
|
||||
|
||||
if (DrawLoadingText)
|
||||
{
|
||||
CenterPosition = new Vector2(GameMain.GraphicsWidth * 0.3f, GameMain.GraphicsHeight / 2.0f);
|
||||
TitlePosition = CenterPosition + new Vector2(-0.0f + (float)Math.Sqrt(state) * 220.0f, 0.0f) * Scale;
|
||||
TitlePosition.X = Math.Min(TitlePosition.X, (float)GameMain.GraphicsWidth / 2.0f);
|
||||
BackgroundPosition = new Vector2(GameMain.GraphicsWidth * 0.3f, GameMain.GraphicsHeight * 0.45f);
|
||||
TitlePosition = new Vector2(GameMain.GraphicsWidth * 0.5f, GameMain.GraphicsHeight * 0.45f);
|
||||
}
|
||||
|
||||
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend);
|
||||
@@ -132,16 +129,10 @@ namespace Barotrauma
|
||||
|
||||
spriteBatch.Draw(backgroundTexture, BackgroundPosition, null, Color.White * Math.Min(state / 5.0f, 1.0f), 0.0f,
|
||||
new Vector2(backgroundTexture.Width / 2.0f, backgroundTexture.Height / 2.0f),
|
||||
Scale * 1.5f, SpriteEffects.None, 0.2f);
|
||||
|
||||
spriteBatch.Draw(monsterTexture,
|
||||
CenterPosition + new Vector2((state % 40) * 100.0f - 1800.0f, (state % 40) * 30.0f - 200.0f) * Scale, null,
|
||||
Color.White, 0.0f, Vector2.Zero, Scale, SpriteEffects.None, 0.1f);
|
||||
|
||||
spriteBatch.Draw(titleTexture,
|
||||
TitlePosition, null,
|
||||
Color.White * Math.Min((state - 1.0f) / 5.0f, 1.0f), 0.0f, new Vector2(titleTexture.Width / 2.0f, titleTexture.Height / 2.0f), Scale, SpriteEffects.None, 0.0f);
|
||||
|
||||
backgroundScale * 1.5f, SpriteEffects.None, 0.2f);
|
||||
|
||||
titleSprite?.Draw(spriteBatch, TitlePosition, Color.White * Math.Min((state - 1.0f) / 5.0f, 1.0f), scale: titleScale);
|
||||
|
||||
spriteBatch.End();
|
||||
|
||||
graphics.SetRenderTarget(null);
|
||||
@@ -154,9 +145,7 @@ namespace Barotrauma
|
||||
|
||||
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend);
|
||||
|
||||
spriteBatch.Draw(titleTexture,
|
||||
TitlePosition, null,
|
||||
Color.White * Math.Min((state - 3.0f) / 5.0f, 1.0f), 0.0f, new Vector2(titleTexture.Width / 2.0f, titleTexture.Height / 2.0f), Scale, SpriteEffects.None, 0.0f);
|
||||
titleSprite?.Draw(spriteBatch, TitlePosition, Color.White * Math.Min((state - 1.0f) / 5.0f, 1.0f), scale: titleScale);
|
||||
|
||||
if (DrawLoadingText)
|
||||
{
|
||||
|
||||
@@ -141,7 +141,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
currentVideo = CreateVideo(scaledVideoResolution);
|
||||
title.Text = TextManager.Get(contentId);
|
||||
title.Text = textSettings != null ? TextManager.Get(contentId) : string.Empty;
|
||||
textContent.Text = textSettings != null ? textSettings.Text : string.Empty;
|
||||
objectiveText.Text = objective;
|
||||
|
||||
|
||||
@@ -74,12 +74,17 @@ namespace Barotrauma
|
||||
public CrewManager(XElement element, bool isSinglePlayer)
|
||||
: this(isSinglePlayer)
|
||||
{
|
||||
if (!isSinglePlayer)
|
||||
if (GameMain.Client != null)
|
||||
{
|
||||
DebugConsole.ThrowError("Cannot add messages to single player chat box in multiplayer mode!\n" + Environment.StackTrace);
|
||||
//let the server create random conversations in MP
|
||||
return;
|
||||
}
|
||||
if (string.IsNullOrEmpty(text)) { return; }
|
||||
List<Character> availableSpeakers = Character.CharacterList.FindAll(c =>
|
||||
c.AIController is HumanAIController &&
|
||||
!c.IsDead &&
|
||||
c.SpeechImpediment <= 100.0f);
|
||||
pendingConversationLines.AddRange(NPCConversation.CreateRandom(availableSpeakers));
|
||||
}
|
||||
|
||||
var characterInfo = new CharacterInfo(subElement);
|
||||
characterInfos.Add(characterInfo);
|
||||
@@ -90,7 +95,6 @@ namespace Barotrauma
|
||||
break;
|
||||
}
|
||||
}
|
||||
ChatBox.AddMessage(ChatMessage.Create(senderName, text, messageType, sender));
|
||||
}
|
||||
|
||||
partial void InitProjectSpecific()
|
||||
@@ -195,7 +199,6 @@ 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);
|
||||
HumanAIController.RefreshTargets(Character.Controlled, order, Character.Controlled.CurrentHull);
|
||||
return true;
|
||||
},
|
||||
UserData = order,
|
||||
@@ -239,27 +242,24 @@ namespace Barotrauma
|
||||
|
||||
public IEnumerable<Character> GetCharacters()
|
||||
{
|
||||
if (character?.Inventory == null) return null;
|
||||
if (characterInfos.Contains(characterInfo))
|
||||
{
|
||||
DebugConsole.ThrowError("Tried to add the same character info to CrewManager twice.\n" + Environment.StackTrace);
|
||||
return;
|
||||
}
|
||||
|
||||
var radioItem = character.Inventory.Items.FirstOrDefault(it => it != null && it.GetComponent<WifiComponent>() != null);
|
||||
if (radioItem == null) return null;
|
||||
if (requireEquipped && !character.HasEquippedItem(radioItem)) return null;
|
||||
|
||||
return radioItem.GetComponent<WifiComponent>();
|
||||
characterInfos.Add(characterInfo);
|
||||
}
|
||||
|
||||
public IEnumerable<CharacterInfo> GetCharacterInfos()
|
||||
{
|
||||
if (GameMain.Client != null)
|
||||
if (character == null)
|
||||
{
|
||||
//let the server create random conversations in MP
|
||||
DebugConsole.ThrowError("Tried to remove a null character from CrewManager.\n" + Environment.StackTrace);
|
||||
return;
|
||||
}
|
||||
List<Character> availableSpeakers = Character.CharacterList.FindAll(c =>
|
||||
c.AIController is HumanAIController &&
|
||||
!c.IsDead &&
|
||||
c.SpeechImpediment <= 100.0f);
|
||||
pendingConversationLines.AddRange(NPCConversation.CreateRandom(availableSpeakers));
|
||||
characters.Remove(character);
|
||||
if (removeInfo) characterInfos.Remove(character.Info);
|
||||
}
|
||||
|
||||
public void AddCharacter(Character character)
|
||||
@@ -633,9 +633,183 @@ namespace Barotrauma
|
||||
{
|
||||
characterListBox.BarScroll = roundedPos;
|
||||
}
|
||||
soundIcon.Visible = !muted && !mutedLocally;
|
||||
soundIconDisabled.Visible = muted || mutedLocally;
|
||||
soundIconDisabled.ToolTip = TextManager.Get(mutedLocally ? "MutedLocally" : "MutedGlobally");
|
||||
var characterArea = new GUIButton(new RectTransform(new Point(characterInfoWidth, frame.Rect.Height), frame.RectTransform, Anchor.CenterLeft), style: "GUITextBox")
|
||||
{
|
||||
UserData = character,
|
||||
Color = frame.Color,
|
||||
SelectedColor = frame.SelectedColor,
|
||||
HoverColor = frame.HoverColor,
|
||||
ToolTip = characterToolTip
|
||||
};
|
||||
|
||||
var soundIcon = new GUIImage(new RectTransform(new Point((int)(characterArea.Rect.Height * 0.5f)), characterArea.RectTransform, Anchor.CenterRight) { AbsoluteOffset = new Point(5, 0) },
|
||||
"GUISoundIcon")
|
||||
{
|
||||
UserData = "soundicon",
|
||||
CanBeFocused = false,
|
||||
Visible = true
|
||||
};
|
||||
soundIcon.Color = new Color(soundIcon.Color, 0.0f);
|
||||
new GUIImage(new RectTransform(new Point((int)(characterArea.Rect.Height * 0.5f)), characterArea.RectTransform, Anchor.CenterRight) { AbsoluteOffset = new Point(5, 0) },
|
||||
"GUISoundIconDisabled")
|
||||
{
|
||||
UserData = "soundicondisabled",
|
||||
CanBeFocused = true,
|
||||
Visible = false
|
||||
};
|
||||
|
||||
if (isSinglePlayer)
|
||||
{
|
||||
characterArea.OnClicked = CharacterClicked;
|
||||
}
|
||||
else
|
||||
{
|
||||
characterArea.CanBeFocused = false;
|
||||
characterArea.CanBeSelected = false;
|
||||
}
|
||||
|
||||
var characterImage = new GUICustomComponent(new RectTransform(new Point(characterArea.Rect.Height), characterArea.RectTransform, Anchor.CenterLeft),
|
||||
onDraw: (sb, component) => character.Info.DrawIcon(sb, component.Rect.Center.ToVector2(), targetAreaSize: component.Rect.Size.ToVector2()))
|
||||
{
|
||||
CanBeFocused = false,
|
||||
HoverColor = Color.White,
|
||||
SelectedColor = Color.White,
|
||||
ToolTip = characterToolTip
|
||||
};
|
||||
|
||||
var characterName = new GUITextBlock(new RectTransform(new Point(characterArea.Rect.Width - characterImage.Rect.Width - soundIcon.Rect.Width - 10, characterArea.Rect.Height),
|
||||
characterArea.RectTransform, Anchor.CenterRight) { AbsoluteOffset = new Point(soundIcon.Rect.Width + 10, 0) },
|
||||
character.Name, textColor: frame.Color, font: GUI.SmallFont, wrap: true)
|
||||
{
|
||||
Color = frame.Color,
|
||||
HoverColor = Color.Transparent,
|
||||
SelectedColor = Color.Transparent,
|
||||
CanBeFocused = false,
|
||||
ToolTip = characterToolTip,
|
||||
AutoScale = true
|
||||
};
|
||||
|
||||
//---------------- order buttons ----------------
|
||||
|
||||
var orderButtonFrame = new GUILayoutGroup(new RectTransform(new Point(100, frame.Rect.Height), frame.RectTransform)
|
||||
{ AbsoluteOffset = new Point(characterInfoWidth + spacing, 0) },
|
||||
isHorizontal: true, childAnchor: Anchor.CenterLeft)
|
||||
{
|
||||
AbsoluteSpacing = (int)(10 * GUI.Scale),
|
||||
UserData = "orderbuttons",
|
||||
CanBeFocused = false
|
||||
};
|
||||
|
||||
//listbox for holding the orders inappropriate for this character
|
||||
//(so we can easily toggle their visibility)
|
||||
var wrongOrderList = new GUIListBox(new RectTransform(new Point(50, orderButtonFrame.Rect.Height), orderButtonFrame.RectTransform), isHorizontal: true, style: null)
|
||||
{
|
||||
ScrollBarEnabled = false,
|
||||
ScrollBarVisible = false,
|
||||
Enabled = false,
|
||||
Spacing = spacing,
|
||||
ClampMouseRectToParent = false
|
||||
};
|
||||
wrongOrderList.Content.ClampMouseRectToParent = false;
|
||||
|
||||
for (int i = 0; i < orders.Count; i++)
|
||||
{
|
||||
var order = orders[i];
|
||||
if (order.TargetAllCharacters) continue;
|
||||
|
||||
RectTransform btnParent = (i >= correctOrderCount + neutralOrderCount) ?
|
||||
wrongOrderList.Content.RectTransform :
|
||||
orderButtonFrame.RectTransform;
|
||||
|
||||
var btn = new GUIButton(new RectTransform(new Point(iconSize, iconSize), btnParent, Anchor.CenterLeft),
|
||||
style: null)
|
||||
{
|
||||
UserData = order
|
||||
};
|
||||
|
||||
new GUIFrame(new RectTransform(new Vector2(1.5f), btn.RectTransform, Anchor.Center), "OuterGlow")
|
||||
{
|
||||
Color = Color.Lerp(order.Color, frame.Color, 0.5f) * 0.8f,
|
||||
HoverColor = Color.Lerp(order.Color, frame.Color, 0.5f) * 1.0f,
|
||||
PressedColor = Color.Lerp(order.Color, frame.Color, 0.5f) * 0.6f,
|
||||
UserData = "selected",
|
||||
CanBeFocused = false,
|
||||
Visible = false
|
||||
};
|
||||
|
||||
var img = new GUIImage(new RectTransform(Vector2.One, btn.RectTransform), order.Prefab.SymbolSprite);
|
||||
img.Scale = iconSize / (float)img.SourceRect.Width;
|
||||
img.Color = Color.Lerp(order.Color, frame.Color, 0.5f);
|
||||
img.ToolTip = order.Name;
|
||||
img.HoverColor = Color.Lerp(img.Color, Color.White, 0.5f);
|
||||
|
||||
btn.OnClicked += (GUIButton button, object userData) =>
|
||||
{
|
||||
if (Character.Controlled == null || Character.Controlled.SpeechImpediment >= 100.0f) return false;
|
||||
|
||||
if (btn.GetChildByUserData("selected").Visible)
|
||||
{
|
||||
SetCharacterOrder(character, Order.PrefabList.Find(o => o.AITag == "dismissed"), null, Character.Controlled);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (order.ItemComponentType != null || order.ItemIdentifiers.Length > 0 || order.Options.Length > 1)
|
||||
{
|
||||
CreateOrderTargetFrame(button, character, order);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetCharacterOrder(character, order, null, Character.Controlled);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
btn.UserData = order;
|
||||
btn.ToolTip = order.Name;
|
||||
|
||||
//divider between different groups of orders
|
||||
if (i == correctOrderCount - 1 || i == correctOrderCount + neutralOrderCount - 1)
|
||||
{
|
||||
//TODO: divider sprite
|
||||
new GUIFrame(new RectTransform(new Point(8, iconSize), orderButtonFrame.RectTransform), style: "GUIButton");
|
||||
}
|
||||
}
|
||||
|
||||
var toggleWrongOrderBtn = new GUIButton(new RectTransform(new Point((int)(30 * GUI.Scale), wrongOrderList.Rect.Height), wrongOrderList.Content.RectTransform),
|
||||
"", style: "UIToggleButton")
|
||||
{
|
||||
UserData = "togglewrongorder",
|
||||
CanBeFocused = false
|
||||
};
|
||||
|
||||
wrongOrderList.RectTransform.NonScaledSize = new Point(
|
||||
wrongOrderList.Content.Children.Sum(c => c.Rect.Width + wrongOrderList.Spacing),
|
||||
wrongOrderList.RectTransform.NonScaledSize.Y);
|
||||
wrongOrderList.RectTransform.SetAsLastChild();
|
||||
|
||||
new GUIFrame(new RectTransform(new Point(
|
||||
wrongOrderList.Rect.Width - toggleWrongOrderBtn.Rect.Width - wrongOrderList.Spacing * 2,
|
||||
wrongOrderList.Rect.Height), wrongOrderList.Content.RectTransform),
|
||||
style: null)
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
|
||||
//scale to fit the content
|
||||
orderButtonFrame.RectTransform.NonScaledSize = new Point(
|
||||
orderButtonFrame.Children.Sum(c => c.Rect.Width + orderButtonFrame.AbsoluteSpacing),
|
||||
orderButtonFrame.RectTransform.NonScaledSize.Y);
|
||||
|
||||
frame.RectTransform.NonScaledSize = new Point(
|
||||
characterInfoWidth + spacing + (orderButtonFrame.Rect.Width - wrongOrderList.Rect.Width),
|
||||
frame.RectTransform.NonScaledSize.Y);
|
||||
|
||||
characterListBox.RectTransform.NonScaledSize = new Point(
|
||||
characterListBox.Content.Children.Max(c => c.Rect.Width) + wrongOrderList.Rect.Width,
|
||||
characterListBox.RectTransform.NonScaledSize.Y);
|
||||
characterListBox.Content.RectTransform.NonScaledSize = characterListBox.RectTransform.NonScaledSize;
|
||||
characterListBox.UpdateScrollBarSize();
|
||||
return frame;
|
||||
}
|
||||
|
||||
private IEnumerable<object> KillCharacterAnim(GUIComponent component)
|
||||
@@ -779,6 +953,12 @@ namespace Barotrauma
|
||||
}
|
||||
return;
|
||||
}
|
||||
List<Character> availableSpeakers = Character.CharacterList.FindAll(c =>
|
||||
c.AIController is HumanAIController &&
|
||||
!c.IsDead &&
|
||||
c.SpeechImpediment <= 100.0f);
|
||||
pendingConversationLines.AddRange(NPCConversation.CreateRandom(availableSpeakers));
|
||||
}
|
||||
|
||||
character.SetOrder(order, option, orderGiver, speak: orderGiver != character);
|
||||
if (IsSinglePlayer)
|
||||
@@ -836,19 +1016,23 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
//only one target (or an order with no particular targets), just show options
|
||||
else
|
||||
|
||||
character.SetOrder(order, option, orderGiver, speak: orderGiver != character);
|
||||
if (IsSinglePlayer)
|
||||
{
|
||||
orderTargetFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.2f + order.Options.Length * 0.1f, 0.18f), GUI.Canvas)
|
||||
{ AbsoluteOffset = new Point(orderButton.Rect.Center.X, orderButton.Rect.Bottom) },
|
||||
isHorizontal: true, childAnchor: Anchor.BottomLeft)
|
||||
orderGiver?.Speak(
|
||||
order.GetChatMessage(character.Name, orderGiver.CurrentHull?.DisplayName, givingOrderToSelf: character == orderGiver, orderOption: option), null);
|
||||
}
|
||||
else if (orderGiver != null)
|
||||
{
|
||||
OrderChatMessage msg = new OrderChatMessage(order, option, order.TargetItemComponent?.Item, character, orderGiver);
|
||||
if (GameMain.Client != null)
|
||||
{
|
||||
UserData = character,
|
||||
Stretch = true
|
||||
};
|
||||
//line connecting the order button to the option buttons
|
||||
//TODO: sprite
|
||||
new GUIFrame(new RectTransform(new Vector2(0.5f, 1.0f), orderTargetFrame.RectTransform), style: null);
|
||||
GameMain.Client.SendChatMessage(msg);
|
||||
}
|
||||
}
|
||||
DisplayCharacterOrder(character, order);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create the UI panel that's used to select the target and options for a given order
|
||||
|
||||
@@ -305,7 +305,7 @@ namespace Barotrauma.Tutorials
|
||||
infoBox = CreateInfoFrame(TextManager.Get(activeContentSegment.Id), tutorialText,
|
||||
activeContentSegment.TextContent.GetAttributeInt("width", 300),
|
||||
activeContentSegment.TextContent.GetAttributeInt("height", 80),
|
||||
activeContentSegment.TextContent.GetAttributeString("anchor", "Center"), true, StopCurrentContentSegment, () => LoadVideo(activeContentSegment, false));
|
||||
activeContentSegment.TextContent.GetAttributeString("anchor", "Center"), true, StopCurrentContentSegment, () => LoadVideo(activeContentSegment));
|
||||
break;
|
||||
case TutorialContentTypes.TextOnly:
|
||||
infoBox = CreateInfoFrame(TextManager.Get(activeContentSegment.Id), tutorialText,
|
||||
@@ -404,7 +404,8 @@ namespace Barotrauma.Tutorials
|
||||
{
|
||||
if (ContentRunning) return;
|
||||
ContentRunning = true;
|
||||
videoPlayer.LoadContent(playableContentPath, new VideoPlayer.VideoSettings(segment.VideoContent), new VideoPlayer.TextSettings(segment.VideoContent), segment.Id, true, callback: () => ContentRunning = false);
|
||||
LoadVideo(segment);
|
||||
//videoPlayer.LoadContent(playableContentPath, new VideoPlayer.VideoSettings(segment.VideoContent), new VideoPlayer.TextSettings(segment.VideoContent), segment.Id, true, callback: () => ContentRunning = false);
|
||||
}
|
||||
|
||||
private void ShowSegmentText(TutorialSegment segment)
|
||||
@@ -419,7 +420,7 @@ namespace Barotrauma.Tutorials
|
||||
|
||||
if (segment.ContentType != TutorialContentTypes.TextOnly)
|
||||
{
|
||||
videoAction = () => LoadVideo(segment, false);
|
||||
videoAction = () => LoadVideo(segment);
|
||||
}
|
||||
|
||||
infoBox = CreateInfoFrame(TextManager.Get(segment.Id), tutorialText,
|
||||
@@ -566,16 +567,16 @@ namespace Barotrauma.Tutorials
|
||||
#endregion
|
||||
|
||||
#region Video
|
||||
protected void LoadVideo(TutorialSegment segment, bool showText = true)
|
||||
protected void LoadVideo(TutorialSegment segment)
|
||||
{
|
||||
if (videoPlayer == null) videoPlayer = new VideoPlayer();
|
||||
if (showText)
|
||||
if (segment.ContentType != TutorialContentTypes.ManualVideo)
|
||||
{
|
||||
videoPlayer.LoadContent(playableContentPath, new VideoPlayer.VideoSettings(segment.VideoContent), new VideoPlayer.TextSettings(segment.VideoContent), segment.Id, true, segment.Objective, StopCurrentContentSegment);
|
||||
}
|
||||
else
|
||||
{
|
||||
videoPlayer.LoadContent(playableContentPath, new VideoPlayer.VideoSettings(segment.VideoContent), null, segment.Id, true, segment.Objective, null);
|
||||
videoPlayer.LoadContent(playableContentPath, new VideoPlayer.VideoSettings(segment.VideoContent), null, segment.Id, true, string.Empty, null);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -139,12 +139,16 @@ namespace Barotrauma
|
||||
{
|
||||
string newLanguage = obj as string;
|
||||
if (newLanguage == Language) return true;
|
||||
|
||||
UnsavedSettings = true;
|
||||
|
||||
Language = newLanguage;
|
||||
ApplySettings();
|
||||
|
||||
new GUIMessageBox(TextManager.Get("RestartRequiredLabel"), TextManager.Get("RestartRequiredLanguage"));
|
||||
var msgBox = new GUIMessageBox(TextManager.Get("RestartRequiredLabel"), TextManager.Get("RestartRequiredLanguage"));
|
||||
//change fonts to the default font of the new language to make sure
|
||||
//they can be displayed when for example changing from English to Chinese
|
||||
var defaultFont = GUI.Style.LoadCurrentDefaultFont();
|
||||
msgBox.Header.Font = defaultFont;
|
||||
msgBox.Text.Font = defaultFont;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -78,8 +78,8 @@ namespace Barotrauma.Items.Components
|
||||
CanBeFocused = false
|
||||
};
|
||||
|
||||
var pumpSpeedText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), rightArea.RectTransform) { RelativeOffset = new Vector2(0.25f, 0.0f) },
|
||||
"", textAlignment: Alignment.BottomLeft);
|
||||
var pumpSpeedText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), rightArea.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.0f) },
|
||||
"", textAlignment: Alignment.BottomLeft, wrap: true);
|
||||
string pumpSpeedStr = TextManager.Get("PumpSpeed");
|
||||
pumpSpeedText.TextGetter = () => { return pumpSpeedStr + ": " + (int)flowPercentage + " %"; };
|
||||
|
||||
@@ -90,7 +90,7 @@ namespace Barotrauma.Items.Components
|
||||
};
|
||||
|
||||
new GUITextBlock(new RectTransform(new Vector2(0.15f, 1.0f), sliderArea.RectTransform),
|
||||
TextManager.Get("PumpOut"), textAlignment: Alignment.Center);
|
||||
TextManager.Get("PumpOut"), textAlignment: Alignment.Center, wrap: true);
|
||||
pumpSpeedSlider = new GUIScrollBar(new RectTransform(new Vector2(0.8f, 1.0f), sliderArea.RectTransform), barSize: 0.25f, style: "GUISlider")
|
||||
{
|
||||
Step = 0.05f,
|
||||
@@ -111,7 +111,7 @@ namespace Barotrauma.Items.Components
|
||||
};
|
||||
|
||||
new GUITextBlock(new RectTransform(new Vector2(0.15f, 1.0f), sliderArea.RectTransform),
|
||||
TextManager.Get("PumpIn"), textAlignment: Alignment.Center);
|
||||
TextManager.Get("PumpIn"), textAlignment: Alignment.Center, wrap: true);
|
||||
}
|
||||
|
||||
public override void OnItemLoaded()
|
||||
|
||||
@@ -117,11 +117,11 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (uiElements[i] is GUIButton button)
|
||||
{
|
||||
button.Text = labels[i];
|
||||
button.Text = customInterfaceElementList[i].Label;
|
||||
}
|
||||
else if (uiElements[i] is GUITickBox tickBox)
|
||||
{
|
||||
tickBox.Text = labels[i];
|
||||
tickBox.Text = customInterfaceElementList[i].Label;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -776,37 +776,6 @@ namespace Barotrauma
|
||||
ic.DrawHUD(spriteBatch, character);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<ColoredText> texts = new List<ColoredText>();
|
||||
public List<ColoredText> GetHUDTexts(Character character)
|
||||
{
|
||||
texts.Clear();
|
||||
foreach (ItemComponent ic in components)
|
||||
{
|
||||
if (string.IsNullOrEmpty(ic.DisplayMsg)) continue;
|
||||
if (!ic.CanBePicked && !ic.CanBeSelected) continue;
|
||||
if (ic is Holdable holdable && !holdable.CanBeDeattached()) continue;
|
||||
|
||||
Color color = Color.Gray;
|
||||
bool hasRequiredSkillsAndItems = ic.HasRequiredSkills(character) && ic.HasRequiredItems(character, false);
|
||||
if (hasRequiredSkillsAndItems)
|
||||
{
|
||||
if (ic is Repairable repairable)
|
||||
{
|
||||
if (Condition < repairable.ShowRepairUIThreshold)
|
||||
{
|
||||
color = Color.Cyan;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
color = Color.Cyan;
|
||||
}
|
||||
}
|
||||
|
||||
texts.Add(new ColoredText(ic.DisplayMsg, color, false));
|
||||
}
|
||||
return texts;
|
||||
}
|
||||
|
||||
|
||||
@@ -638,7 +638,9 @@ namespace Barotrauma
|
||||
|
||||
if (mouseOn && PlayerInput.LeftButtonClicked() && !messageBoxOpen)
|
||||
{
|
||||
//TODO: translate or replace
|
||||
var messageBox = new GUIMessageBox("Mysteries lie ahead...", "This area is unreachable in this version of Barotrauma. Please wait for future updates!");
|
||||
messageBoxOpen = true;
|
||||
CoroutineManager.StartCoroutine(WaitForMessageBoxClosed(messageBox));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,7 +315,7 @@ namespace Barotrauma
|
||||
var dimensionsText = new GUITextBlock(new RectTransform(new Vector2(1, 0), descriptionBox.Content.RectTransform),
|
||||
TextManager.Get("Dimensions"), textAlignment: Alignment.TopLeft, font: GUI.Font, wrap: true)
|
||||
{ CanBeFocused = false };
|
||||
new GUITextBlock(new RectTransform(new Vector2(0.5f, 0.0f), dimensionsText.RectTransform, Anchor.TopRight),
|
||||
new GUITextBlock(new RectTransform(new Vector2(0.45f, 0.0f), dimensionsText.RectTransform, Anchor.TopRight),
|
||||
dimensionsStr, textAlignment: Alignment.TopLeft, font: GUI.Font, wrap: true)
|
||||
{ CanBeFocused = false };
|
||||
dimensionsText.RectTransform.MinSize = new Point(0, dimensionsText.Children.First().Rect.Height);
|
||||
@@ -326,7 +326,7 @@ namespace Barotrauma
|
||||
var crewSizeText = new GUITextBlock(new RectTransform(new Vector2(1, 0), descriptionBox.Content.RectTransform),
|
||||
TextManager.Get("RecommendedCrewSize"), textAlignment: Alignment.TopLeft, font: GUI.Font, wrap: true)
|
||||
{ CanBeFocused = false };
|
||||
new GUITextBlock(new RectTransform(new Vector2(0.5f, 0.0f), crewSizeText.RectTransform, Anchor.TopRight),
|
||||
new GUITextBlock(new RectTransform(new Vector2(0.45f, 0.0f), crewSizeText.RectTransform, Anchor.TopRight),
|
||||
RecommendedCrewSizeMin + " - " + RecommendedCrewSizeMax, textAlignment: Alignment.TopLeft, font: GUI.Font, wrap: true)
|
||||
{ CanBeFocused = false };
|
||||
crewSizeText.RectTransform.MinSize = new Point(0, crewSizeText.Children.First().Rect.Height);
|
||||
@@ -337,7 +337,7 @@ namespace Barotrauma
|
||||
var crewExperienceText = new GUITextBlock(new RectTransform(new Vector2(1, 0), descriptionBox.Content.RectTransform),
|
||||
TextManager.Get("RecommendedCrewExperience"), textAlignment: Alignment.TopLeft, font: GUI.Font, wrap: true)
|
||||
{ CanBeFocused = false };
|
||||
new GUITextBlock(new RectTransform(new Vector2(0.5f, 0.0f), crewExperienceText.RectTransform, Anchor.TopRight),
|
||||
new GUITextBlock(new RectTransform(new Vector2(0.45f, 0.0f), crewExperienceText.RectTransform, Anchor.TopRight),
|
||||
TextManager.Get(RecommendedCrewExperience), textAlignment: Alignment.TopLeft, font: GUI.Font, wrap: true)
|
||||
{ CanBeFocused = false };
|
||||
crewExperienceText.RectTransform.MinSize = new Point(0, crewExperienceText.Children.First().Rect.Height);
|
||||
@@ -348,18 +348,21 @@ namespace Barotrauma
|
||||
var contentPackagesText = new GUITextBlock(new RectTransform(new Vector2(1, 0), descriptionBox.Content.RectTransform),
|
||||
TextManager.Get("RequiredContentPackages"), textAlignment: Alignment.TopLeft, font: GUI.Font)
|
||||
{ CanBeFocused = false };
|
||||
new GUITextBlock(new RectTransform(new Vector2(0.5f, 0.0f), contentPackagesText.RectTransform, Anchor.TopRight),
|
||||
new GUITextBlock(new RectTransform(new Vector2(0.45f, 0.0f), contentPackagesText.RectTransform, Anchor.TopRight),
|
||||
string.Join(", ", RequiredContentPackages), textAlignment: Alignment.TopLeft, font: GUI.Font, wrap: true)
|
||||
{ CanBeFocused = false };
|
||||
contentPackagesText.RectTransform.MinSize = new Point(0, contentPackagesText.Children.First().Rect.Height);
|
||||
}
|
||||
|
||||
GUITextBlock.AutoScaleAndNormalize(descriptionBox.Content.Children.Where(c => c is GUITextBlock).Cast<GUITextBlock>());
|
||||
|
||||
//space
|
||||
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), descriptionBox.Content.RectTransform), style: null);
|
||||
|
||||
if (Description.Length != 0)
|
||||
{
|
||||
new GUITextBlock(new RectTransform(new Vector2(1, 0), descriptionBox.Content.RectTransform), TextManager.Get("SaveSubDialogDescription") + ":", font: GUI.Font, wrap: true) { CanBeFocused = false, ForceUpperCase = true };
|
||||
new GUITextBlock(new RectTransform(new Vector2(1, 0), descriptionBox.Content.RectTransform),
|
||||
TextManager.Get("SaveSubDialogDescription", fallBackTag: "WorkshopItemDescription"), font: GUI.Font, wrap: true) { CanBeFocused = false, ForceUpperCase = true };
|
||||
}
|
||||
|
||||
new GUITextBlock(new RectTransform(new Vector2(1, 0), descriptionBox.Content.RectTransform), Description, font: GUI.Font, wrap: true)
|
||||
|
||||
@@ -214,6 +214,10 @@ namespace Barotrauma
|
||||
sb.AppendLine("Level seed: " + ((Level.Loaded == null) ? "no level loaded" : Level.Loaded.Seed));
|
||||
sb.AppendLine("Loaded submarine: " + ((Submarine.MainSub == null) ? "None" : Submarine.MainSub.Name + " (" + Submarine.MainSub.MD5Hash + ")"));
|
||||
sb.AppendLine("Selected screen: " + (Screen.Selected == null ? "None" : Screen.Selected.ToString()));
|
||||
if (SteamManager.IsInitialized)
|
||||
{
|
||||
sb.AppendLine("SteamManager initialized");
|
||||
}
|
||||
|
||||
if (GameMain.Client != null)
|
||||
{
|
||||
|
||||
@@ -196,6 +196,8 @@ namespace Barotrauma
|
||||
|
||||
private GUILayoutGroup subPreviewContainer;
|
||||
|
||||
private GUILayoutGroup subPreviewContainer;
|
||||
|
||||
private GUIButton loadGameButton;
|
||||
|
||||
public Action<Submarine, string, string> StartNewGame;
|
||||
@@ -465,7 +467,7 @@ namespace Barotrauma
|
||||
{
|
||||
IsFixedSize = false
|
||||
},
|
||||
TextManager.Get("Shuttle"), textAlignment: Alignment.Right, font: GUI.SmallFont)
|
||||
TextManager.Get("Shuttle", fallBackTag: "RespawnShuttle"), textAlignment: Alignment.Right, font: GUI.SmallFont)
|
||||
{
|
||||
TextColor = textBlock.TextColor * 0.8f,
|
||||
ToolTip = textBlock.ToolTip
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Barotrauma
|
||||
{
|
||||
class MainMenuScreen : Screen
|
||||
{
|
||||
public enum Tab { NewGame = 1, LoadGame = 2, HostServer = 3, Settings = 4, Tutorials = 5, JoinServer = 6, CharacterEditor = 7, SubmarineEditor = 8, QuickStartDev = 9, SteamWorkshop = 10 }
|
||||
public enum Tab { NewGame = 1, LoadGame = 2, HostServer = 3, Settings = 4, Tutorials = 5, JoinServer = 6, CharacterEditor = 7, SubmarineEditor = 8, QuickStartDev = 9, SteamWorkshop = 10, Credits = 11 }
|
||||
|
||||
private GUIComponent buttonsParent;
|
||||
|
||||
@@ -35,22 +35,27 @@ namespace Barotrauma
|
||||
private Sprite backgroundSprite;
|
||||
private Sprite backgroundVignette;
|
||||
|
||||
private GUIComponent titleText;
|
||||
|
||||
private CreditsPlayer creditsPlayer;
|
||||
|
||||
#region Creation
|
||||
public MainMenuScreen(GameMain game)
|
||||
{
|
||||
backgroundVignette = new Sprite("Content/UI/MainMenuVignette.png", Vector2.Zero);
|
||||
|
||||
new GUIImage(new RectTransform(new Vector2(0.35f, 0.2f), Frame.RectTransform, Anchor.BottomRight)
|
||||
{ RelativeOffset = new Vector2(0.05f, 0.05f), AbsoluteOffset = new Point(-5, -5) },
|
||||
{ RelativeOffset = new Vector2(0.05f, 0.1f), AbsoluteOffset = new Point(-8, -8) },
|
||||
style: "TitleText")
|
||||
{
|
||||
Color = Color.Black * 0.5f,
|
||||
CanBeFocused = false
|
||||
};
|
||||
new GUIImage(new RectTransform(new Vector2(0.35f, 0.2f), Frame.RectTransform, Anchor.BottomRight) { RelativeOffset = new Vector2(0.05f, 0.05f) },
|
||||
titleText = new GUIImage(new RectTransform(new Vector2(0.35f, 0.2f), Frame.RectTransform, Anchor.BottomRight)
|
||||
{ RelativeOffset = new Vector2(0.05f, 0.1f) },
|
||||
style: "TitleText");
|
||||
|
||||
buttonsParent = new GUILayoutGroup(new RectTransform(new Vector2(0.3f, 0.85f), parent: Frame.RectTransform, anchor: Anchor.BottomLeft, pivot: Pivot.BottomLeft)
|
||||
buttonsParent = new GUILayoutGroup(new RectTransform(new Vector2(0.3f, 0.85f), parent: Frame.RectTransform, anchor: Anchor.CenterLeft)
|
||||
{
|
||||
AbsoluteOffset = new Point(50, 0)
|
||||
})
|
||||
@@ -223,7 +228,7 @@ namespace Barotrauma
|
||||
};
|
||||
|
||||
// === OPTION
|
||||
var optionHolder = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.5f), parent: buttonsParent.RectTransform), isHorizontal: true);
|
||||
var optionHolder = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.8f), parent: buttonsParent.RectTransform), isHorizontal: true);
|
||||
|
||||
new GUIImage(new RectTransform(new Vector2(0.15f, 0.6f), optionHolder.RectTransform), "MainMenuOptionIcon")
|
||||
{
|
||||
@@ -233,9 +238,9 @@ namespace Barotrauma
|
||||
//spacing
|
||||
new GUIFrame(new RectTransform(new Vector2(0.01f, 0.0f), optionHolder.RectTransform), style: null);
|
||||
|
||||
var optionButtons = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 1.0f), parent: optionHolder.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.05f) });
|
||||
var optionButtons = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 1.0f), parent: optionHolder.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.0f) });
|
||||
|
||||
var optionList = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.3f), parent: optionButtons.RectTransform))
|
||||
var optionList = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.25f), parent: optionButtons.RectTransform))
|
||||
{
|
||||
Stretch = false,
|
||||
RelativeSpacing = 0.035f
|
||||
@@ -247,6 +252,13 @@ namespace Barotrauma
|
||||
UserData = Tab.Settings,
|
||||
OnClicked = SelectTab
|
||||
};
|
||||
//TODO: translate
|
||||
new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), optionList.RectTransform), TextManager.Get("CreditsButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton")
|
||||
{
|
||||
ForceUpperCase = true,
|
||||
UserData = Tab.Credits,
|
||||
OnClicked = SelectTab
|
||||
};
|
||||
new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), optionList.RectTransform), TextManager.Get("QuitButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton")
|
||||
{
|
||||
ForceUpperCase = true,
|
||||
@@ -255,9 +267,9 @@ namespace Barotrauma
|
||||
|
||||
//debug button for quickly starting a new round
|
||||
#if DEBUG
|
||||
new GUIButton(new RectTransform(new Vector2(0.8f, 0.1f), buttonsParent.RectTransform, Anchor.TopLeft, Pivot.BottomLeft) { AbsoluteOffset = new Point(0, -40) },
|
||||
new GUIButton(new RectTransform(new Point(300, 30), Frame.RectTransform, Anchor.TopRight) { AbsoluteOffset = new Point(40, 40) },
|
||||
"Quickstart (dev)", style: "GUIButtonLarge", color: Color.Red)
|
||||
{
|
||||
{
|
||||
IgnoreLayoutGroups = true,
|
||||
UserData = Tab.QuickStartDev,
|
||||
OnClicked = (tb, userdata) =>
|
||||
@@ -373,6 +385,8 @@ namespace Barotrauma
|
||||
{
|
||||
if (obj is Tab)
|
||||
{
|
||||
titleText.Visible = true;
|
||||
|
||||
if (GameMain.Config.UnsavedSettings)
|
||||
{
|
||||
var applyBox = new GUIMessageBox(
|
||||
@@ -449,6 +463,9 @@ namespace Barotrauma
|
||||
if (!Steam.SteamManager.IsInitialized) return false;
|
||||
GameMain.SteamWorkshopScreen.Select();
|
||||
break;
|
||||
case Tab.Credits:
|
||||
titleText.Visible = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -702,11 +719,6 @@ namespace Barotrauma
|
||||
|
||||
public override void Update(double deltaTime)
|
||||
{
|
||||
GameMain.TitleScreen.TitlePosition =
|
||||
Vector2.Lerp(GameMain.TitleScreen.TitlePosition, new Vector2(
|
||||
GameMain.TitleScreen.TitleSize.X / 2.0f * GameMain.TitleScreen.Scale + 30.0f,
|
||||
GameMain.TitleScreen.TitleSize.Y / 2.0f * GameMain.TitleScreen.Scale + 30.0f),
|
||||
0.1f);
|
||||
#if !DEBUG
|
||||
#if !OSX
|
||||
if (Steam.SteamManager.USE_STEAM)
|
||||
@@ -780,27 +792,30 @@ namespace Barotrauma
|
||||
GUI.Font.DrawString(spriteBatch, "Barotrauma v" + GameMain.Version, new Vector2(10, GameMain.GraphicsHeight - 20), Color.White * 0.7f);
|
||||
#endif
|
||||
|
||||
Vector2 textPos = new Vector2(GameMain.GraphicsWidth - 10, GameMain.GraphicsHeight - 10);
|
||||
for (int i = legalCrap.Length - 1; i >= 0; i--)
|
||||
if (selectedTab != Tab.Credits)
|
||||
{
|
||||
Vector2 textSize = GUI.SmallFont.MeasureString(legalCrap[i]);
|
||||
bool mouseOn = i == 0 &&
|
||||
PlayerInput.MousePosition.X > textPos.X - textSize.X && PlayerInput.MousePosition.X < textPos.X &&
|
||||
PlayerInput.MousePosition.Y > textPos.Y - textSize.Y && PlayerInput.MousePosition.Y < textPos.Y;
|
||||
|
||||
GUI.SmallFont.DrawString(spriteBatch,
|
||||
legalCrap[i], textPos - textSize,
|
||||
mouseOn ? Color.White : Color.White * 0.7f);
|
||||
|
||||
if (i == 0)
|
||||
Vector2 textPos = new Vector2(GameMain.GraphicsWidth - 10, GameMain.GraphicsHeight - 10);
|
||||
for (int i = legalCrap.Length - 1; i >= 0; i--)
|
||||
{
|
||||
GUI.DrawLine(spriteBatch, textPos, textPos - Vector2.UnitX * textSize.X, mouseOn ? Color.White : Color.White * 0.7f);
|
||||
if (mouseOn && PlayerInput.LeftButtonClicked())
|
||||
Vector2 textSize = GUI.SmallFont.MeasureString(legalCrap[i]);
|
||||
bool mouseOn = i == 0 &&
|
||||
PlayerInput.MousePosition.X > textPos.X - textSize.X && PlayerInput.MousePosition.X < textPos.X &&
|
||||
PlayerInput.MousePosition.Y > textPos.Y - textSize.Y && PlayerInput.MousePosition.Y < textPos.Y;
|
||||
|
||||
GUI.SmallFont.DrawString(spriteBatch,
|
||||
legalCrap[i], textPos - textSize,
|
||||
mouseOn ? Color.White : Color.White * 0.7f);
|
||||
|
||||
if (i == 0)
|
||||
{
|
||||
Process.Start("http://privacypolicy.daedalic.com");
|
||||
GUI.DrawLine(spriteBatch, textPos, textPos - Vector2.UnitX * textSize.X, mouseOn ? Color.White : Color.White * 0.7f);
|
||||
if (mouseOn && PlayerInput.LeftButtonClicked())
|
||||
{
|
||||
Process.Start("http://privacypolicy.daedalic.com");
|
||||
}
|
||||
}
|
||||
textPos.Y -= textSize.Y;
|
||||
}
|
||||
textPos.Y -= textSize.Y;
|
||||
}
|
||||
|
||||
spriteBatch.End();
|
||||
|
||||
@@ -437,9 +437,13 @@ namespace Barotrauma
|
||||
GUITextBlock textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), modeList.Content.RectTransform),
|
||||
mode.Name, style: "ListBoxElement", textAlignment: Alignment.CenterLeft)
|
||||
{
|
||||
ToolTip = mode.Description,
|
||||
UserData = mode
|
||||
};
|
||||
//TODO: translate mission descriptions
|
||||
if (TextManager.Language == "English")
|
||||
{
|
||||
textBlock.ToolTip = mode.Description;
|
||||
}
|
||||
}
|
||||
|
||||
//mission type ------------------------------------------------------------------
|
||||
@@ -1216,7 +1220,7 @@ namespace Barotrauma
|
||||
if (sub.HasTag(SubmarineTag.Shuttle))
|
||||
{
|
||||
new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), frame.RectTransform, Anchor.CenterRight) { RelativeOffset = new Vector2(0.1f, 0.0f) },
|
||||
TextManager.Get("Shuttle"), textAlignment: Alignment.CenterRight, font: GUI.SmallFont)
|
||||
TextManager.Get("Shuttle", fallBackTag: "RespawnShuttle"), textAlignment: Alignment.CenterRight, font: GUI.SmallFont)
|
||||
{
|
||||
TextColor = subTextBlock.TextColor * 0.8f,
|
||||
ToolTip = subTextBlock.ToolTip,
|
||||
|
||||
@@ -1184,7 +1184,6 @@ namespace Barotrauma
|
||||
{
|
||||
OnClicked = SaveSub
|
||||
};
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
@@ -1357,7 +1356,7 @@ namespace Barotrauma
|
||||
if (sub.HasTag(SubmarineTag.Shuttle))
|
||||
{
|
||||
var shuttleText = new GUITextBlock(new RectTransform(new Vector2(0.2f, 1.0f), textBlock.RectTransform, Anchor.CenterRight),
|
||||
TextManager.Get("Shuttle"), font: GUI.SmallFont)
|
||||
TextManager.Get("Shuttle", fallBackTag: "RespawnShuttle"), font: GUI.SmallFont)
|
||||
{
|
||||
TextColor = textBlock.TextColor * 0.8f,
|
||||
ToolTip = textBlock.ToolTip
|
||||
|
||||
@@ -927,6 +927,7 @@ namespace Barotrauma
|
||||
|
||||
textTag = textTag.ToLowerInvariant();
|
||||
var tagTextPairs = TextManager.GetAllTagTextPairs();
|
||||
tagTextPairs.Sort((t1, t2) => { return t1.Value.CompareTo(t2.Value); });
|
||||
foreach (KeyValuePair<string, string> tagTextPair in tagTextPairs)
|
||||
{
|
||||
if (!tagTextPair.Key.StartsWith(textTag)) { continue; }
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Barotrauma
|
||||
{
|
||||
private static Regex csvSplit = new Regex("(?:^|,)(\"(?:[^\"])*\"|[^,]*)", RegexOptions.Compiled); // Handling commas inside data fields surrounded by ""
|
||||
private static List<int> conversationClosingIndent = new List<int>();
|
||||
private static char[] separator = new char[1] { ',' };
|
||||
private static char[] separator = new char[1] { '|' };
|
||||
|
||||
private const string conversationsPath = "Content/NPCConversations";
|
||||
private const string infoTextPath = "Content/Texts";
|
||||
@@ -48,7 +48,7 @@ namespace Barotrauma
|
||||
DebugConsole.ThrowError("NPCConversation Localization .csv to .xml conversion failed for: " + conversationFiles[i]);
|
||||
continue;
|
||||
}
|
||||
string xmlFileFullPath = $"{conversationsPath}/NPCConversations_{language}_NEW.xml";
|
||||
string xmlFileFullPath = $"{conversationsPath}/NpcConversations_{language}_NEW.xml";
|
||||
File.WriteAllLines(xmlFileFullPath, xmlContent);
|
||||
DebugConsole.NewMessage("Conversation localization .xml file successfully created at: " + xmlFileFullPath);
|
||||
}
|
||||
@@ -141,7 +141,8 @@ namespace Barotrauma
|
||||
|
||||
for (int i = 0; i < NPCPersonalityTrait.List.Count; i++) // Traits
|
||||
{
|
||||
string[] split = SplitCSV(csvContent[traitStart + i].Trim(separator));
|
||||
//string[] split = SplitCSV(csvContent[traitStart + i].Trim(separator));
|
||||
string[] split = csvContent[traitStart + i].Split(separator);
|
||||
xmlContent.Add(
|
||||
$"<PersonalityTrait " +
|
||||
$"{GetVariable("name", split[1])}" +
|
||||
@@ -151,8 +152,10 @@ namespace Barotrauma
|
||||
|
||||
for (int i = traitStart + NPCPersonalityTrait.List.Count; i < csvContent.Length; i++) // Conversations
|
||||
{
|
||||
string[] presplit = csvContent[i].Split(','); // Handling speaker index fetching, somehow doesn't work with the regex
|
||||
string[] split = SplitCSV(csvContent[i]);
|
||||
//string[] presplit = csvContent[i].Split(separator); // Handling speaker index fetching, somehow doesn't work with the regex
|
||||
//string[] split = SplitCSV(csvContent[i]);
|
||||
|
||||
string[] split = csvContent[i].Split(separator);
|
||||
|
||||
int emptyFields = 0;
|
||||
|
||||
@@ -173,15 +176,15 @@ namespace Barotrauma
|
||||
continue;
|
||||
}
|
||||
|
||||
string speaker = presplit[1];
|
||||
int depthIndex = int.Parse(presplit[2]);
|
||||
string speaker = split[1];
|
||||
int depthIndex = int.Parse(split[2]);
|
||||
// 3 = original line
|
||||
string line = split[4].Replace("\"", "");
|
||||
string flags = split[5].Replace("\"", "");
|
||||
string allowedJobs = split[6].Replace("\"", "");
|
||||
string speakerTags = split[7].Replace("\"", "");
|
||||
string minIntensity = split[8].Replace("\"", "").Replace(",", ".");
|
||||
string maxIntensity = split[9].Replace("\"", "").Replace(",", ".");
|
||||
string line = split[3].Replace("\"", "");
|
||||
string flags = split[4].Replace("\"", "");
|
||||
string allowedJobs = split[5].Replace("\"", "");
|
||||
string speakerTags = split[6].Replace("\"", "");
|
||||
string minIntensity = split[7].Replace("\"", "").Replace(",", ".");
|
||||
string maxIntensity = split[8].Replace("\"", "").Replace(",", ".");
|
||||
|
||||
string element =
|
||||
$"{GetIndenting(depthIndex)}" +
|
||||
@@ -257,7 +260,7 @@ namespace Barotrauma
|
||||
list.Add("");
|
||||
}
|
||||
|
||||
list.Add(curr.TrimStart(','));
|
||||
list.Add(curr.TrimStart(separator));
|
||||
}
|
||||
|
||||
return list.ToArray();
|
||||
|
||||
@@ -107,24 +107,42 @@ namespace Barotrauma
|
||||
|
||||
text = text.Replace("\n", " \n ");
|
||||
|
||||
string[] words;
|
||||
if (TextManager.NoWhiteSpace)
|
||||
List<string> words = new List<string>();
|
||||
string currWord = "";
|
||||
for (int i = 0; i < text.Length; i++)
|
||||
{
|
||||
words = new string[text.Length];
|
||||
for (int i = 0; i < text.Length; i++)
|
||||
if (isCJK.IsMatch(text[i].ToString()))
|
||||
{
|
||||
words[i] = text[i].ToString();
|
||||
if (currWord.Length > 0)
|
||||
{
|
||||
words.Add(currWord);
|
||||
currWord = "";
|
||||
}
|
||||
words.Add(text[i].ToString());
|
||||
}
|
||||
else if (text[i] == ' ')
|
||||
{
|
||||
if (currWord.Length > 0)
|
||||
{
|
||||
words.Add(currWord);
|
||||
currWord = "";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
currWord += text[i];
|
||||
}
|
||||
}
|
||||
else
|
||||
if (currWord.Length > 0)
|
||||
{
|
||||
words = text.Split(' ');
|
||||
words.Add(currWord);
|
||||
currWord = "";
|
||||
}
|
||||
|
||||
StringBuilder wrappedText = new StringBuilder();
|
||||
float linePos = 0f;
|
||||
Vector2 spaceSize = font.MeasureString(" ") * textScale;
|
||||
for (int i = 0; i < words.Length; ++i)
|
||||
for (int i = 0; i < words.Count; ++i)
|
||||
{
|
||||
if (words[i].Length == 0)
|
||||
{
|
||||
|
||||
@@ -73,6 +73,7 @@
|
||||
<Character file="Content/Characters/Watcher/Watcher.xml" />
|
||||
<Character file="Content/Characters/Hammerhead/Hammerhead.xml" />
|
||||
<Outpost file="Content/Map/Outposts/Outpost.sub" />
|
||||
<Outpost file="Content/Map/Outposts/Outpost2.sub" />
|
||||
<Submarine file="Submarines/Orca.sub" />
|
||||
<Submarine file="Submarines/Typhon.sub" />
|
||||
<Submarine file="Submarines/Selkie.sub" />
|
||||
@@ -83,8 +84,12 @@
|
||||
<Submarine file="Submarines/Remora.sub" />
|
||||
<Submarine file="Submarines/RemoraDrone.sub" />
|
||||
<Text file="Content/Texts/EnglishVanilla.xml" />
|
||||
<Text file="Content/Texts/RussianVanillaTest.xml" />
|
||||
<Text file="Content/Texts/ChineseVanillaTest.xml" />
|
||||
<Text file="Content/Texts/GermanVanilla.xml" />
|
||||
<Text file="Content/Texts/FrenchVanilla.xml" />
|
||||
<Text file="Content/Texts/RussianVanilla.xml" />
|
||||
<Text file="Content/Texts/BrazilianPortugueseVanilla.xml" />
|
||||
<Text file="Content/Texts/SimplifiedChineseVanilla.xml" />
|
||||
<Text file="Content/Texts/TraditionalChineseVanilla.xml" />
|
||||
<UIStyle file="Content/UI/style.xml"/>
|
||||
<Afflictions file="Content/Afflictions.xml"/>
|
||||
<Structure file="Content/Map/StructurePrefabs.xml" />
|
||||
@@ -98,7 +103,13 @@
|
||||
<MapGenerationParameters file="Content/Map/MapGenerationParameters.xml" />
|
||||
<LevelGenerationParameters file="Content/Map/LevelGenerationParameters.xml" />
|
||||
<Missions file="Content/Missions.xml" />
|
||||
<NPCConversations file="Content/NPCConversations/NpcConversations.xml"/>
|
||||
<NPCConversations file="Content/NPCConversations/NpcConversations_English.xml"/>
|
||||
<NPCConversations file="Content/NPCConversations/NpcConversations_German.xml"/>
|
||||
<NPCConversations file="Content/NPCConversations/NpcConversations_French.xml"/>
|
||||
<NPCConversations file="Content/NPCConversations/NpcConversations_Russian.xml"/>
|
||||
<NPCConversations file="Content/NPCConversations/NpcConversations_BrazilianPortuguese.xml"/>
|
||||
<NPCConversations file="Content/NPCConversations/NpcConversations_SimplifiedChinese.xml"/>
|
||||
<NPCConversations file="Content/NPCConversations/NpcConversations_TraditionalChinese.xml"/>
|
||||
<Jobs file="Content/Jobs.xml" />
|
||||
<Sounds file="Content/Sounds/sounds.xml" />
|
||||
<Tutorials file="Content/Tutorials/Tutorials.xml" />
|
||||
|
||||
@@ -36,7 +36,6 @@
|
||||
<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\AIObjectiveFightIntruders.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\AI\Objectives\AIObjectivePumpWater.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\AI\Objectives\AIObjectiveRepairItem.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\AI\Objectives\AIObjectiveRepairItems.cs" />
|
||||
|
||||
@@ -361,6 +361,12 @@
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\Effects\waterbump.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\Fonts\NotoSans\NotoSansCJKsc-Bold.otf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\Fonts\NotoSans\NotoSansCJKsc-Medium.otf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\Fonts\NotoSans\NotoSansTC-Bold.otf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
@@ -499,10 +505,47 @@
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\Map\OutpostWall_C.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\Texts\ChineseVanillaTest.xml">
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\NPCConversations\NpcConversations_BrazilianPortuguese.xml">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\Texts\RussianVanillaTest.xml">
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\NPCConversations\NpcConversations_English.xml">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\NPCConversations\NpcConversations_Finnish.xml" />
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\NPCConversations\NpcConversations_French.xml">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\NPCConversations\NpcConversations_German.xml">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\NPCConversations\NpcConversations_Russian.xml">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\NPCConversations\NpcConversations_SimplifiedChinese.xml">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\NPCConversations\NpcConversations_TraditionalChinese.xml">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\Texts\BrazilianPortugueseVanilla.xml">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\Texts\Credits.xml">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\Texts\FrenchVanilla.xml">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\Texts\GermanVanilla.xml">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\Texts\RussianVanilla.xml">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\Texts\SimplifiedChineseVanilla.xml">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\Texts\TraditionalChineseVanilla.xml">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\Tutorials\TutorialVideos\tutorial_command.mp4">
|
||||
@@ -556,6 +599,9 @@
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\UI\tutorialAtlas.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<None Include="$(MSBuildThisFileDirectory)Concentus_LICENSE">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="$(MSBuildThisFileDirectory)Content\Fonts\BebasNeue-Regular.otf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
@@ -1306,11 +1352,6 @@
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\Map\SubFins.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\NPCConversations\NpcConversationsFinnish.xml">
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\NPCConversations\NpcConversations.xml">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="$(MSBuildThisFileDirectory)Content\Characters\Legacyhusk\DivingSuit.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
@@ -2070,6 +2111,9 @@
|
||||
<None Include="$(MSBuildThisFileDirectory)Content\Items\Weapons\ElectricalDischarger.ogg">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="$(MSBuildThisFileDirectory)Content\Map\Outposts\Outpost2.sub">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="$(MSBuildThisFileDirectory)Content\Sounds\Damage\Gore1.ogg">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
|
||||
@@ -3,8 +3,6 @@ using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -52,6 +50,7 @@ namespace Barotrauma
|
||||
{
|
||||
insideSteering = new IndoorsSteeringManager(this, true, false);
|
||||
outsideSteering = new SteeringManager(this);
|
||||
|
||||
objectiveManager = new AIObjectiveManager(c);
|
||||
updateObjectiveTimer = Rand.Range(0.0f, UpdateObjectiveInterval);
|
||||
InitProjSpecific();
|
||||
@@ -94,10 +93,9 @@ namespace Barotrauma
|
||||
UpdateSpeaking();
|
||||
}
|
||||
|
||||
if (objectiveManager.CurrentObjective == null) { return; }
|
||||
|
||||
objectiveManager.DoCurrentObjective(deltaTime);
|
||||
bool run = objectiveManager.GetCurrentPriority() > AIObjectiveManager.RunPriority;
|
||||
|
||||
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)
|
||||
@@ -164,7 +162,7 @@ namespace Barotrauma
|
||||
{
|
||||
bool oxygenLow = Character.OxygenAvailable < CharacterHealth.LowOxygenThreshold;
|
||||
bool highPressure = Character.CurrentHull == null || Character.CurrentHull.LethalPressure > 0 && Character.PressureProtection <= 0;
|
||||
bool shouldKeepTheGearOn = !ObjectiveManager.IsCurrentObjective<AIObjectiveIdle>();
|
||||
bool shouldKeepTheGearOn = objectiveManager.CurrentObjective.KeepDivingGearOn;
|
||||
|
||||
bool removeDivingSuit = (oxygenLow && !highPressure) || (!shouldKeepTheGearOn && Character.CurrentHull.WaterPercentage < 1 && !Character.IsClimbing && steeringManager == insideSteering && !PathSteering.InStairs);
|
||||
if (removeDivingSuit)
|
||||
@@ -190,7 +188,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!ObjectiveManager.IsCurrentObjective<AIObjectiveExtinguishFires>() && !ObjectiveManager.IsCurrentObjective<AIObjectiveExtinguishFire>())
|
||||
if (!(ObjectiveManager.CurrentOrder is AIObjectiveExtinguishFires) && !(ObjectiveManager.CurrentObjective is AIObjectiveExtinguishFire))
|
||||
{
|
||||
var extinguisherItem = Character.Inventory.FindItemByIdentifier("extinguisher") ?? Character.Inventory.FindItemByTag("extinguisher");
|
||||
if (extinguisherItem != null && Character.HasEquippedItem(extinguisherItem))
|
||||
@@ -199,21 +197,6 @@ namespace Barotrauma
|
||||
extinguisherItem.Drop(Character);
|
||||
}
|
||||
}
|
||||
foreach (var item in Character.Inventory.Items)
|
||||
{
|
||||
if (item == null) { continue; }
|
||||
if (ObjectiveManager.CurrentObjective is AIObjectiveIdle)
|
||||
{
|
||||
if (item.AllowedSlots.Contains(InvSlotType.RightHand | InvSlotType.LeftHand) && Character.HasEquippedItem(item))
|
||||
{
|
||||
// Try to put the weapon in an Any slot, and drop it if that fails
|
||||
if (!item.AllowedSlots.Contains(InvSlotType.Any) || !Character.Inventory.TryPutItem(item, Character, new List<InvSlotType>() { InvSlotType.Any }))
|
||||
{
|
||||
item.Drop(Character);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Character.IsKeyDown(InputType.Aim))
|
||||
{
|
||||
@@ -246,58 +229,41 @@ namespace Barotrauma
|
||||
Order newOrder = null;
|
||||
if (Character.CurrentHull != null)
|
||||
{
|
||||
if (AIObjectiveExtinguishFires.IsValidTarget(Character.CurrentHull))
|
||||
if (Character.CurrentHull.FireSources.Count > 0)
|
||||
{
|
||||
var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportfire");
|
||||
newOrder = new Order(orderPrefab, Character.CurrentHull, null);
|
||||
AddTargets<AIObjectiveExtinguishFires, Hull>(Character, Character.CurrentHull);
|
||||
}
|
||||
|
||||
foreach (var gap in Character.CurrentHull.ConnectedGaps)
|
||||
if (Character.CurrentHull.ConnectedGaps.Any(g => !g.IsRoomToRoom && g.ConnectedDoor == null && g.Open > 0.0f))
|
||||
{
|
||||
if (AIObjectiveFixLeaks.IsValidTarget(gap))
|
||||
{
|
||||
var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportbreach");
|
||||
newOrder = new Order(orderPrefab, Character.CurrentHull, null);
|
||||
AddTargets<AIObjectiveFixLeaks, Gap>(Character, gap);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.CurrentHull != Character.CurrentHull) { continue; }
|
||||
if (AIObjectiveRepairItems.IsValidTarget(item))
|
||||
{
|
||||
var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportbrokendevices");
|
||||
newOrder = new Order(orderPrefab, Character.CurrentHull, item.Repairables?.FirstOrDefault());
|
||||
AddTargets<AIObjectiveRepairItems, Item>(Character, item);
|
||||
}
|
||||
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) { continue; }
|
||||
if (AIObjectiveFightIntruders.IsValidTarget(c))
|
||||
if (c.CurrentHull == Character.CurrentHull && !c.IsDead &&
|
||||
(c.AIController is EnemyAIController || (c.TeamID != Character.TeamID && Character.TeamID != Character.TeamType.FriendlyNPC && c.TeamID != Character.TeamType.FriendlyNPC)))
|
||||
{
|
||||
var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportintruders");
|
||||
newOrder = new Order(orderPrefab, Character.CurrentHull, null);
|
||||
AddTargets<AIObjectiveFightIntruders, Character>(Character, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (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);
|
||||
AddTargets<AIObjectiveRescueAll, Character>(Character, Character);
|
||||
}
|
||||
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?.DisplayName, givingOrderToSelf: false), ChatMessageType.Order);
|
||||
Character.Speak(
|
||||
newOrder.GetChatMessage("", Character.CurrentHull?.DisplayName, givingOrderToSelf: false), ChatMessageType.Order);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -324,7 +290,6 @@ namespace Barotrauma
|
||||
{
|
||||
float damage = attackResult.Damage;
|
||||
if (damage <= 0) { return; }
|
||||
if (ObjectiveManager.CurrentObjective is AIObjectiveFightIntruders) { return; }
|
||||
if (attacker == null || attacker.IsDead || attacker.Removed)
|
||||
{
|
||||
// Ignore damage from falling etc that we shouldn't react to.
|
||||
@@ -372,18 +337,18 @@ namespace Barotrauma
|
||||
{
|
||||
// Replace the old objective with the new.
|
||||
ObjectiveManager.Objectives.Remove(combatObjective);
|
||||
objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker, mode, objectiveManager));
|
||||
objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker, mode));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (delay > 0)
|
||||
{
|
||||
objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker, mode, objectiveManager), delay);
|
||||
objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker, mode), delay);
|
||||
}
|
||||
else
|
||||
{
|
||||
objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker, mode, objectiveManager));
|
||||
objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker, mode));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -394,10 +359,8 @@ namespace Barotrauma
|
||||
CurrentOrderOption = option;
|
||||
CurrentOrder = order;
|
||||
objectiveManager.SetOrder(order, option, orderGiver);
|
||||
if (speak && Character.SpeechImpediment < 100.0f)
|
||||
{
|
||||
Character.Speak(TextManager.Get("DialogAffirmative"), null, 1.0f);
|
||||
}
|
||||
if (speak && Character.SpeechImpediment < 100.0f) Character.Speak(TextManager.Get("DialogAffirmative"), null, 1.0f);
|
||||
|
||||
SetOrderProjSpecific(order);
|
||||
}
|
||||
|
||||
@@ -434,7 +397,7 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Check whether the character has a diving mask in usable condition plus some oxygen.
|
||||
/// </summary>
|
||||
public static bool HasDivingMask(Character character) => HasItem(character, "diving", "oxygensource");
|
||||
public static bool HasDivingGear(Character character) => HasItem(character, "diving", "oxygensource");
|
||||
|
||||
public static bool HasItem(Character character, string tag, string containedTag, float conditionPercentage = 0)
|
||||
{
|
||||
@@ -454,9 +417,12 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (var c in Character.CharacterList)
|
||||
{
|
||||
if (c.AIController is HumanAIController humanAi && humanAi.IsFriendly(character))
|
||||
if (c.TeamID == character.TeamID)
|
||||
{
|
||||
humanAi.RefreshHullSafety(hull);
|
||||
if (c.AIController is HumanAIController humanAi)
|
||||
{
|
||||
humanAi.RefreshHullSafety(hull);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -473,78 +439,13 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public static void RefreshTargets(Character character, Order order, Hull hull)
|
||||
{
|
||||
switch (order.AITag)
|
||||
{
|
||||
case "reportfire":
|
||||
AddTargets<AIObjectiveExtinguishFires, Hull>(character, hull);
|
||||
break;
|
||||
case "reportbreach":
|
||||
foreach (var gap in hull.ConnectedGaps)
|
||||
{
|
||||
if (AIObjectiveFixLeaks.IsValidTarget(gap))
|
||||
{
|
||||
AddTargets<AIObjectiveFixLeaks, Gap>(character, gap);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "reportbrokendevices":
|
||||
foreach (var item in Item.ItemList)
|
||||
{
|
||||
if (item.CurrentHull != hull) { continue; }
|
||||
if (AIObjectiveRepairItems.IsValidTarget(item))
|
||||
{
|
||||
AddTargets<AIObjectiveRepairItems, Item>(character, item);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "reportintruders":
|
||||
foreach (var enemy in Character.CharacterList)
|
||||
{
|
||||
if (enemy.CurrentHull != hull) { continue; }
|
||||
if (AIObjectiveFightIntruders.IsValidTarget(enemy))
|
||||
{
|
||||
AddTargets<AIObjectiveFightIntruders, Character>(character, enemy);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "requestfirstaid":
|
||||
foreach (var c in Character.CharacterList)
|
||||
{
|
||||
if (c.CurrentHull != hull) { continue; }
|
||||
if (AIObjectiveRescueAll.IsValidTarget(c))
|
||||
{
|
||||
AddTargets<AIObjectiveRescueAll, Character>(character, c);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError(order.AITag + " not implemented!");
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddTargets<T1, T2>(Character caller, T2 target) where T1 : AIObjectiveLoop<T2>
|
||||
{
|
||||
foreach (var c in Character.CharacterList)
|
||||
{
|
||||
if (IsFriendly(caller, c) && c.AIController is HumanAIController humanAI)
|
||||
{
|
||||
humanAI.ObjectiveManager.GetObjective<T1>()?.AddTarget(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public float GetHullSafety(Hull hull)
|
||||
{
|
||||
if (hull == null) { return 0; }
|
||||
bool ignoreFire = ObjectiveManager.IsCurrentObjective<AIObjectiveExtinguishFires>() || ObjectiveManager.IsCurrentObjective<AIObjectiveExtinguishFire>();
|
||||
bool ignoreFire = ObjectiveManager.CurrentObjective is AIObjectiveExtinguishFire || ObjectiveManager.CurrentOrder is AIObjectiveExtinguishFires;
|
||||
bool ignoreWater = HasDivingSuit(Character);
|
||||
bool ignoreOxygen = ignoreWater || HasDivingMask(Character);
|
||||
bool ignoreEnemies = ObjectiveManager.IsCurrentObjective<AIObjectiveFightIntruders>();
|
||||
bool ignoreOxygen = ignoreWater || HasDivingGear(Character);
|
||||
bool ignoreEnemies = ObjectiveManager.CurrentObjective is AIObjectiveCombat || ObjectiveManager.CurrentOrder is AIObjectiveCombat;
|
||||
return GetHullSafety(hull, Character, ignoreWater, ignoreOxygen, ignoreFire, ignoreEnemies);
|
||||
}
|
||||
|
||||
@@ -562,7 +463,7 @@ namespace Barotrauma
|
||||
// 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 =>
|
||||
int enemyCount = Character.CharacterList.Count(e =>
|
||||
e.CurrentHull == hull && !e.IsDead && !e.IsUnconscious &&
|
||||
(e.AIController is EnemyAIController || (e.TeamID != character.TeamID && character.TeamID != Character.TeamType.FriendlyNPC && e.TeamID != Character.TeamType.FriendlyNPC)));
|
||||
// The hull safety decreases 90% per enemy up to 100% (TODO: test smaller percentages)
|
||||
@@ -571,8 +472,7 @@ namespace Barotrauma
|
||||
return MathHelper.Clamp(safety * 100, 0, 100);
|
||||
}
|
||||
|
||||
public bool IsFriendly(Character other) => IsFriendly(Character, other);
|
||||
// TODO: If the aliens are quaranteed to be in another team than the player, we wouldn't need to check the species.
|
||||
public static bool IsFriendly(Character me, Character other) => other.TeamID == me.TeamID && other.SpeciesName == me.SpeciesName;
|
||||
public bool IsFriendly(Character other) => other.TeamID == Character.TeamID && other.SpeciesName == Character.SpeciesName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,7 +287,7 @@ namespace Barotrauma
|
||||
character.AnimController.Anim = AnimController.Animation.None;
|
||||
character.SelectedConstruction = null;
|
||||
}
|
||||
if (Vector2.DistanceSquared(pos, currentPath.CurrentNode.SimPosition) < MathUtils.Pow(collider.radius * 4, 2))
|
||||
if (Vector2.DistanceSquared(pos, currentPath.CurrentNode.SimPosition) < MathUtils.Pow(collider.radius * 3, 2))
|
||||
{
|
||||
currentPath.SkipToNextNode();
|
||||
}
|
||||
@@ -302,7 +302,7 @@ namespace Barotrauma
|
||||
bool isAboveFeet = currentPath.CurrentNode.SimPosition.Y > colliderBottom.Y;
|
||||
bool isNotTooHigh = currentPath.CurrentNode.SimPosition.Y < colliderBottom.Y + characterHeight;
|
||||
|
||||
if (horizontalDistance < collider.radius * 4 && isAboveFeet && isNotTooHigh)
|
||||
if (horizontalDistance < collider.radius * 3 && isAboveFeet && isNotTooHigh)
|
||||
{
|
||||
currentPath.SkipToNextNode();
|
||||
}
|
||||
@@ -325,50 +325,6 @@ namespace Barotrauma
|
||||
if (door.IsStuck) { return false; }
|
||||
if (!canOpenDoors || character.LockHands) { return false; }
|
||||
if (door.HasIntegratedButtons)
|
||||
{
|
||||
return door.HasRequiredItems(character, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
bool canUseButton = false;
|
||||
float closestDistance = 0;
|
||||
foreach (var button in door.Item.GetConnectedComponents<Controller>(true))
|
||||
{
|
||||
if (!button.HasRequiredItems(character, false))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// Ignore buttons that are on the wrong side of the door
|
||||
if (door.IsHorizontal)
|
||||
{
|
||||
if (Math.Sign(button.Item.Position.Y - nextNode.Position.Y) != Math.Sign(currentNode.Position.Y - nextNode.Position.Y)) { continue; }
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Math.Sign(button.Item.Position.X - nextNode.Position.X) != Math.Sign(currentNode.Position.X - nextNode.Position.X)) { continue; }
|
||||
}
|
||||
float distance = Vector2.DistanceSquared(button.Item.WorldPosition, currentNode.WorldPosition);
|
||||
// Too far from the current node (can't reach)
|
||||
if (distance > button.Item.InteractDistance * button.Item.InteractDistance)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else if (closestButton == null || distance < closestDistance)
|
||||
{
|
||||
closestButton = button;
|
||||
closestDistance = distance;
|
||||
}
|
||||
canUseButton = true;
|
||||
}
|
||||
return canUseButton;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: use the CanAccessThroughDoor method.
|
||||
private void CheckDoorsInPath()
|
||||
{
|
||||
// TODO: if no doors was found, seek more nodes?
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
Door door = null;
|
||||
bool shouldBeOpen = false;
|
||||
@@ -416,7 +372,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (door == null) { return; }
|
||||
if (door == null) return;
|
||||
|
||||
//toggle the door if it's the previous node and open, or if it's current node and closed
|
||||
if (door.IsOpen != shouldBeOpen)
|
||||
@@ -482,9 +438,24 @@ namespace Barotrauma
|
||||
{
|
||||
penalty = 100.0f;
|
||||
}
|
||||
if (!CanAccessThroughDoor(node.Waypoint, nextNode.Waypoint, out _))
|
||||
else if (!canBreakDoors)
|
||||
{
|
||||
return null;
|
||||
//door closed and the character can't open doors -> node can't be traversed
|
||||
if (!canOpenDoors || character.LockHands) { return null; }
|
||||
|
||||
var doorButtons = nextNode.Waypoint.ConnectedDoor.Item.GetConnectedComponents<Controller>();
|
||||
if (!doorButtons.Any())
|
||||
{
|
||||
if (!nextNode.Waypoint.ConnectedDoor.HasRequiredItems(character, false)) { return null; }
|
||||
}
|
||||
|
||||
foreach (Controller button in doorButtons)
|
||||
{
|
||||
if (Math.Sign(button.Item.Position.X - nextNode.Waypoint.Position.X) !=
|
||||
Math.Sign(node.Position.X - nextNode.Position.X)) { continue; }
|
||||
|
||||
if (!button.HasRequiredItems(character, false)) { return null; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,21 @@ namespace Barotrauma
|
||||
|
||||
public abstract string DebugTag { get; }
|
||||
public virtual bool ForceRun => false;
|
||||
public virtual bool KeepDivingGearOn => false;
|
||||
|
||||
protected readonly List<AIObjective> subObjectives = new List<AIObjective>();
|
||||
public float Priority { get; set; }
|
||||
protected readonly Character character;
|
||||
protected string option;
|
||||
protected bool abandon;
|
||||
|
||||
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;
|
||||
protected SteeringManager SteeringManager => HumanAIController.SteeringManager;
|
||||
|
||||
/// <summary>
|
||||
/// Run the main objective with all subobjectives concurrently?
|
||||
@@ -47,7 +62,6 @@ namespace Barotrauma
|
||||
|
||||
public AIObjective(Character character, AIObjectiveManager objectiveManager, float priorityModifier, string option = null)
|
||||
{
|
||||
this.objectiveManager = objectiveManager;
|
||||
this.character = character;
|
||||
Option = option ?? string.Empty;
|
||||
|
||||
@@ -89,6 +103,11 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (!subObjectives.Contains(CurrentSubObjective))
|
||||
{
|
||||
CurrentSubObjective = null;
|
||||
}
|
||||
|
||||
foreach (AIObjective objective in subObjectives)
|
||||
{
|
||||
objective.TryComplete(deltaTime);
|
||||
@@ -113,40 +132,29 @@ namespace Barotrauma
|
||||
subObjectives.Add(objective);
|
||||
}
|
||||
|
||||
public void SortSubObjectives()
|
||||
public void SortSubObjectives(AIObjectiveManager objectiveManager)
|
||||
{
|
||||
if (subObjectives.None()) { return; }
|
||||
subObjectives.Sort((x, y) => y.GetPriority().CompareTo(x.GetPriority()));
|
||||
if (ConcurrentObjectives)
|
||||
{
|
||||
subObjectives.ForEach(so => SortSubObjectives());
|
||||
}
|
||||
else
|
||||
{
|
||||
subObjectives.First().SortSubObjectives();
|
||||
}
|
||||
subObjectives.Sort((x, y) => y.GetPriority(objectiveManager).CompareTo(x.GetPriority(objectiveManager)));
|
||||
CurrentSubObjective = SubObjectives.First();
|
||||
CurrentSubObjective.SortSubObjectives(objectiveManager);
|
||||
}
|
||||
|
||||
public virtual float GetPriority() => Priority * PriorityModifier;
|
||||
public virtual float GetPriority(AIObjectiveManager objectiveManager) => Priority;
|
||||
|
||||
public virtual void Update(float deltaTime)
|
||||
public virtual void Update(AIObjectiveManager objectiveManager, float deltaTime)
|
||||
{
|
||||
var subObjective = objectiveManager.CurrentObjective?.CurrentSubObjective;
|
||||
if (objectiveManager.CurrentOrder == this)
|
||||
{
|
||||
Priority = AIObjectiveManager.OrderPriority;
|
||||
}
|
||||
else if (objectiveManager.WaitTimer <= 0)
|
||||
else if (objectiveManager.CurrentObjective == this || subObjective == this)
|
||||
{
|
||||
if (objectiveManager.CurrentObjective != null)
|
||||
{
|
||||
if (objectiveManager.CurrentObjective == this || objectiveManager.CurrentObjective.subObjectives.Any(so => so == this))
|
||||
{
|
||||
Priority += Devotion * PriorityModifier * deltaTime;
|
||||
}
|
||||
}
|
||||
Priority = MathHelper.Clamp(Priority, 0, 100);
|
||||
subObjectives.ForEach(so => so.Update(deltaTime));
|
||||
Priority += Devotion * deltaTime;
|
||||
}
|
||||
Priority = MathHelper.Clamp(Priority, 0, 100);
|
||||
subObjectives.ForEach(so => so.Update(objectiveManager, deltaTime));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -166,57 +174,13 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the objective already is created and added in subobjectives. If not, creates it.
|
||||
/// Handles objectives that cannot be completed. If the objective has been removed form the subobjectives, a null value is assigned to the reference.
|
||||
/// Returns true if the objective was created.
|
||||
/// </summary>
|
||||
protected bool TryAddSubObjective<T>(ref T objective, Func<T> constructor, Action onAbandon = null) where T : AIObjective
|
||||
{
|
||||
if (objective != null)
|
||||
{
|
||||
// Sub objective already found, no need to do anything if it remains in the subobjectives
|
||||
// If the sub objective is removed -> it's either completed or impossible to complete.
|
||||
if (!subObjectives.Contains(objective))
|
||||
{
|
||||
if (!objective.CanBeCompleted)
|
||||
{
|
||||
abandon = true;
|
||||
onAbandon?.Invoke();
|
||||
}
|
||||
objective = null;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
objective = constructor();
|
||||
if (!subObjectives.Contains(objective))
|
||||
{
|
||||
AddSubObjective(objective);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: remove
|
||||
protected virtual bool ShouldInterruptSubObjective(AIObjective subObjective) => false;
|
||||
|
||||
public virtual void OnSelected()
|
||||
{
|
||||
// Should we reset steering here?
|
||||
//if (!ConcurrentObjectives)
|
||||
//{
|
||||
// SteeringManager.Reset();
|
||||
//}
|
||||
}
|
||||
|
||||
public virtual void OnSelected() { }
|
||||
public virtual void Reset() { }
|
||||
|
||||
protected abstract void Act(float deltaTime);
|
||||
|
||||
public abstract bool IsCompleted();
|
||||
|
||||
public abstract bool IsDuplicate(AIObjective otherObjective);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class AIObjectiveChargeBatteries : AIObjectiveLoop<PowerContainer>
|
||||
{
|
||||
public override string DebugTag => "charge batteries";
|
||||
private IEnumerable<PowerContainer> batteryList;
|
||||
private readonly IEnumerable<PowerContainer> batteryList;
|
||||
|
||||
public AIObjectiveChargeBatteries(Character character, AIObjectiveManager objectiveManager, string option, float priorityModifier)
|
||||
: base(character, objectiveManager, priorityModifier, option) { }
|
||||
public AIObjectiveChargeBatteries(Character character, string option) : base(character, option)
|
||||
{
|
||||
batteryList = Item.ItemList.Select(i => i.GetComponent<PowerContainer>()).Where(b => b != null);
|
||||
}
|
||||
|
||||
public override bool IsDuplicate(AIObjective otherObjective)
|
||||
{
|
||||
@@ -22,57 +22,37 @@ namespace Barotrauma
|
||||
|
||||
protected override void FindTargets()
|
||||
{
|
||||
base.FindTargets();
|
||||
if (targets.None() && objectiveManager.CurrentOrder == this)
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (targets.None())
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogNoBatteries"), null, 4.0f, "nobatteries", 10.0f);
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool Filter(PowerContainer battery)
|
||||
{
|
||||
if (battery == null) { return false; }
|
||||
var item = battery.Item;
|
||||
if (item.Submarine == null) { return false; }
|
||||
if (item.CurrentHull == null) { return false; }
|
||||
if (item.Submarine.TeamID != character.TeamID) { return false; }
|
||||
if (item.ConditionPercentage <= 0) { return false; }
|
||||
if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(item, true)) { return false; }
|
||||
if (Character.CharacterList.Any(c => c.CurrentHull == item.CurrentHull && !HumanAIController.IsFriendly(c))) { return false; }
|
||||
if (Option == "charge")
|
||||
{
|
||||
if (battery.RechargeRatio >= PowerContainer.aiRechargeTargetRatio - 0.01f) { return false; }
|
||||
}
|
||||
else
|
||||
{
|
||||
if (battery.RechargeRatio <= 0) { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override float TargetEvaluation()
|
||||
{
|
||||
if (Option == "charge")
|
||||
{
|
||||
return targets.Max(t => MathHelper.Lerp(100, 0, Math.Abs(PowerContainer.aiRechargeTargetRatio - t.RechargeRatio)));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
return targets.Max(t => MathHelper.Lerp(0, 100, t.RechargeRatio));
|
||||
targets.Sort((x, y) => x.ChargePercentage.CompareTo(y.ChargePercentage));
|
||||
}
|
||||
}
|
||||
|
||||
protected override IEnumerable<PowerContainer> GetList()
|
||||
{
|
||||
if (batteryList == null)
|
||||
{
|
||||
batteryList = Item.ItemList.Select(i => i.GetComponent<PowerContainer>()).Where(b => b != null);
|
||||
}
|
||||
return batteryList;
|
||||
}
|
||||
|
||||
protected override AIObjective ObjectiveConstructor(PowerContainer battery)
|
||||
=> new AIObjectiveOperateItem(battery, character, objectiveManager, Option, false, priorityModifier: PriorityModifier) { IsLoop = false };
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using FarseerPhysics;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class AIObjectiveCombat : AIObjective
|
||||
{
|
||||
public override string DebugTag => "combat";
|
||||
public bool useCoolDown = true;
|
||||
public override bool KeepDivingGearOn => true;
|
||||
|
||||
const float CoolDown = 10.0f;
|
||||
|
||||
@@ -43,39 +41,29 @@ namespace Barotrauma
|
||||
return _weaponComponent;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly AIObjectiveFindSafety findSafety;
|
||||
private readonly HashSet<RangedWeapon> rangedWeapons = new HashSet<RangedWeapon>();
|
||||
private readonly HashSet<MeleeWeapon> meleeWeapons = new HashSet<MeleeWeapon>();
|
||||
private readonly HashSet<Item> adHocWeapons = new HashSet<Item>();
|
||||
|
||||
private AIObjectiveContainItem reloadWeaponObjective;
|
||||
private AIObjectiveGoTo retreatObjective;
|
||||
private AIObjectiveGoTo followTargetObjective;
|
||||
|
||||
private Hull retreatTarget;
|
||||
private AIObjectiveGoTo retreatObjective;
|
||||
private AIObjectiveFindSafety findSafety;
|
||||
|
||||
private float coolDownTimer;
|
||||
|
||||
public enum CombatMode
|
||||
{
|
||||
Defensive,
|
||||
Offensive,
|
||||
Offensive, // Not implemented
|
||||
Retreat
|
||||
}
|
||||
|
||||
public CombatMode Mode { get; private set; }
|
||||
|
||||
public AIObjectiveCombat(Character character, Character enemy, CombatMode mode, AIObjectiveManager objectiveManager, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
public AIObjectiveCombat(Character character, Character enemy, CombatMode mode) : base(character, "")
|
||||
{
|
||||
Enemy = enemy;
|
||||
coolDownTimer = CoolDown;
|
||||
findSafety = objectiveManager.GetObjective<AIObjectiveFindSafety>();
|
||||
if (findSafety != null)
|
||||
{
|
||||
findSafety.Priority = 0;
|
||||
findSafety.unreachable.Clear();
|
||||
}
|
||||
findSafety = HumanAIController.ObjectiveManager.GetObjective<AIObjectiveFindSafety>();
|
||||
findSafety.Priority = 0;
|
||||
findSafety.unreachable.Clear();
|
||||
Mode = mode;
|
||||
if (Enemy == null)
|
||||
{
|
||||
@@ -85,22 +73,12 @@ namespace Barotrauma
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
if (useCoolDown)
|
||||
{
|
||||
coolDownTimer -= deltaTime;
|
||||
}
|
||||
coolDownTimer -= deltaTime;
|
||||
if (abandon) { return; }
|
||||
Arm(deltaTime);
|
||||
Move(deltaTime);
|
||||
}
|
||||
|
||||
private void Arm(float deltaTime)
|
||||
{
|
||||
switch (Mode)
|
||||
{
|
||||
case CombatMode.Offensive:
|
||||
case CombatMode.Defensive:
|
||||
if (Weapon != null && !character.Inventory.Items.Contains(_weapon) || _weaponComponent != null && !_weaponComponent.HasRequiredContainedItems(false))
|
||||
if (Weapon != null && character.Inventory.Items.Contains(_weapon))
|
||||
{
|
||||
Weapon = null;
|
||||
}
|
||||
@@ -112,65 +90,40 @@ namespace Barotrauma
|
||||
{
|
||||
Mode = CombatMode.Retreat;
|
||||
}
|
||||
if (Equip())
|
||||
else if (Equip(deltaTime))
|
||||
{
|
||||
if (Reload(deltaTime))
|
||||
{
|
||||
Attack(deltaTime);
|
||||
}
|
||||
}
|
||||
// When defensive, try to retreat to safety. TODO: in offsensive mode, engage the target
|
||||
Retreat(deltaTime);
|
||||
break;
|
||||
case CombatMode.Retreat:
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
private void Move(float deltaTime)
|
||||
{
|
||||
switch (Mode)
|
||||
{
|
||||
case CombatMode.Offensive:
|
||||
Engage(deltaTime);
|
||||
break;
|
||||
case CombatMode.Defensive:
|
||||
case CombatMode.Retreat:
|
||||
Retreat(deltaTime);
|
||||
break;
|
||||
case CombatMode.Offensive:
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
private Item GetWeapon()
|
||||
{
|
||||
rangedWeapons.Clear();
|
||||
meleeWeapons.Clear();
|
||||
adHocWeapons.Clear();
|
||||
Item weapon = null;
|
||||
_weaponComponent = null;
|
||||
foreach (var item in character.Inventory.Items)
|
||||
var weapon = character.Inventory.FindItemByTag("weapon");
|
||||
if (weapon == null)
|
||||
{
|
||||
if (item == null) { continue; }
|
||||
foreach (var component in item.Components)
|
||||
foreach (var item in character.Inventory.Items)
|
||||
{
|
||||
if (component is RangedWeapon rw)
|
||||
if (item == null) { continue; }
|
||||
foreach (var component in item.Components)
|
||||
{
|
||||
if (rw.HasRequiredContainedItems(false))
|
||||
if (component is MeleeWeapon || component is RangedWeapon)
|
||||
{
|
||||
rangedWeapons.Add(rw);
|
||||
return item;
|
||||
}
|
||||
}
|
||||
else if (component is MeleeWeapon mw)
|
||||
{
|
||||
if (mw.HasRequiredContainedItems(false))
|
||||
{
|
||||
meleeWeapons.Add(mw);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var effects = component.statusEffectLists;
|
||||
if (effects != null)
|
||||
{
|
||||
@@ -180,10 +133,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (statusEffect.Afflictions.Any())
|
||||
{
|
||||
if (component.HasRequiredContainedItems(false))
|
||||
{
|
||||
adHocWeapons.Add(item);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -191,20 +141,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
var rangedWeapon = rangedWeapons.OrderByDescending(w => w.CombatPriority).FirstOrDefault();
|
||||
var meleeWeapon = meleeWeapons.OrderByDescending(w => w.CombatPriority).FirstOrDefault();
|
||||
if (rangedWeapon != null)
|
||||
{
|
||||
weapon = rangedWeapon.Item;
|
||||
}
|
||||
else if (meleeWeapon != null)
|
||||
{
|
||||
weapon = meleeWeapon.Item;
|
||||
}
|
||||
if (weapon == null)
|
||||
{
|
||||
weapon = adHocWeapons.GetRandom(Rand.RandSync.Server);
|
||||
}
|
||||
return weapon;
|
||||
}
|
||||
|
||||
@@ -219,7 +155,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private bool Equip()
|
||||
private bool Equip(float deltaTime)
|
||||
{
|
||||
if (!character.SelectedItems.Contains(Weapon))
|
||||
{
|
||||
@@ -230,7 +166,8 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
Mode = CombatMode.Retreat;
|
||||
//couldn't equip the item, escape
|
||||
//Abandon(deltaTime);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -239,7 +176,6 @@ namespace Barotrauma
|
||||
|
||||
private void Retreat(float deltaTime)
|
||||
{
|
||||
followTargetObjective = null;
|
||||
if (retreatTarget == null || (retreatObjective != null && !retreatObjective.CanBeCompleted))
|
||||
{
|
||||
retreatTarget = findSafety.FindBestHull(new List<Hull>() { character.CurrentHull });
|
||||
@@ -248,45 +184,12 @@ namespace Barotrauma
|
||||
{
|
||||
if (retreatObjective == null || retreatObjective.Target != retreatTarget)
|
||||
{
|
||||
retreatObjective = new AIObjectiveGoTo(retreatTarget, character, objectiveManager, false, true, priorityModifier: PriorityModifier);
|
||||
retreatObjective = new AIObjectiveGoTo(retreatTarget, character, false, true);
|
||||
}
|
||||
retreatObjective.TryComplete(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
private void Engage(float deltaTime)
|
||||
{
|
||||
retreatTarget = null;
|
||||
retreatObjective = null;
|
||||
if (followTargetObjective == null)
|
||||
{
|
||||
followTargetObjective = new AIObjectiveGoTo(Enemy, character, objectiveManager, repeat: true, getDivingGearIfNeeded: true, priorityModifier: PriorityModifier)
|
||||
{
|
||||
AllowGoingOutside = true,
|
||||
IgnoreIfTargetDead = true,
|
||||
CheckVisibility = true
|
||||
};
|
||||
}
|
||||
if (WeaponComponent is RangedWeapon)
|
||||
{
|
||||
followTargetObjective.CloseEnough = 3;
|
||||
}
|
||||
else if (WeaponComponent is MeleeWeapon mw)
|
||||
{
|
||||
followTargetObjective.CloseEnough = ConvertUnits.ToSimUnits(mw.Range);
|
||||
}
|
||||
else if (WeaponComponent is RepairTool rt)
|
||||
{
|
||||
followTargetObjective.CloseEnough = ConvertUnits.ToSimUnits(rt.Range);
|
||||
}
|
||||
else if (WeaponComponent is RepairTool rt)
|
||||
{
|
||||
SteeringManager.Reset();
|
||||
Mode = CombatMode.Retreat;
|
||||
}
|
||||
followTargetObjective.TryComplete(deltaTime);
|
||||
}
|
||||
|
||||
private bool Reload(float deltaTime)
|
||||
{
|
||||
if (WeaponComponent != null && WeaponComponent.requiredItems.ContainsKey(RelatedItem.RelationType.Contained))
|
||||
@@ -299,7 +202,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (reloadWeaponObjective == null)
|
||||
{
|
||||
reloadWeaponObjective = new AIObjectiveContainItem(character, requiredItem.Identifiers, Weapon.GetComponent<ItemContainer>(), objectiveManager);
|
||||
reloadWeaponObjective = new AIObjectiveContainItem(character, requiredItem.Identifiers, Weapon.GetComponent<ItemContainer>());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -312,7 +215,6 @@ namespace Barotrauma
|
||||
}
|
||||
else if (!reloadWeaponObjective.CanBeCompleted)
|
||||
{
|
||||
SteeringManager.Reset();
|
||||
Mode = CombatMode.Retreat;
|
||||
}
|
||||
else
|
||||
@@ -327,31 +229,14 @@ namespace Barotrauma
|
||||
private IEnumerable<FarseerPhysics.Dynamics.Body> myBodies;
|
||||
private void Attack(float deltaTime)
|
||||
{
|
||||
float squaredDistance = Vector2.DistanceSquared(character.Position, Enemy.Position);
|
||||
character.CursorPosition = Enemy.Position;
|
||||
float engageDistance = 500;
|
||||
if (squaredDistance > engageDistance * engageDistance) { return; }
|
||||
bool canSeeTarget = character.CanSeeCharacter(Enemy);
|
||||
if (!canSeeTarget && character.CurrentHull != Enemy.CurrentHull) { return; }
|
||||
if (Weapon.RequireAimToUse)
|
||||
{
|
||||
bool isOperatingButtons = false;
|
||||
if (SteeringManager == PathSteering)
|
||||
{
|
||||
var door = PathSteering.CurrentPath?.CurrentNode?.ConnectedDoor;
|
||||
if (door != null && !door.IsOpen)
|
||||
{
|
||||
isOperatingButtons = door.HasIntegratedButtons || door.Item.GetConnectedComponents<Controller>(true).Any();
|
||||
}
|
||||
}
|
||||
if (!isOperatingButtons && character.SelectedConstruction == null)
|
||||
{
|
||||
character.SetInput(InputType.Aim, false, true);
|
||||
}
|
||||
character.SetInput(InputType.Aim, false, true);
|
||||
}
|
||||
if (WeaponComponent is MeleeWeapon meleeWeapon)
|
||||
{
|
||||
if (squaredDistance <= meleeWeapon.Range * meleeWeapon.Range)
|
||||
if (Vector2.DistanceSquared(character.Position, Enemy.Position) <= meleeWeapon.Range * meleeWeapon.Range)
|
||||
{
|
||||
character.SetInput(InputType.Shoot, false, true);
|
||||
Weapon.Use(deltaTime, character);
|
||||
@@ -361,7 +246,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (WeaponComponent is RepairTool repairTool)
|
||||
{
|
||||
if (squaredDistance > repairTool.Range * repairTool.Range) { return; }
|
||||
if (Vector2.DistanceSquared(character.Position, Enemy.Position) > repairTool.Range * repairTool.Range) { return; }
|
||||
}
|
||||
if (VectorExtensions.Angle(VectorExtensions.Forward(Weapon.body.TransformedRotation), Enemy.Position - character.Position) < MathHelper.PiOver4)
|
||||
{
|
||||
@@ -369,8 +254,7 @@ namespace Barotrauma
|
||||
{
|
||||
myBodies = character.AnimController.Limbs.Select(l => l.body.FarseerBody);
|
||||
}
|
||||
var collisionCategories = Physics.CollisionCharacter | Physics.CollisionWall;
|
||||
var pickedBody = Submarine.PickBody(character.SimPosition, Enemy.SimPosition, myBodies, collisionCategories);
|
||||
var pickedBody = Submarine.PickBody(character.SimPosition, Enemy.SimPosition, myBodies);
|
||||
if (pickedBody != null)
|
||||
{
|
||||
Character target = null;
|
||||
@@ -392,15 +276,17 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private void Abandon(float deltaTime)
|
||||
{
|
||||
abandon = true;
|
||||
SteeringManager.Reset();
|
||||
}
|
||||
|
||||
public override bool IsCompleted()
|
||||
{
|
||||
bool completed = (Enemy != null && (Enemy.Removed || Enemy.IsDead)) || (useCoolDown && coolDownTimer <= 0);
|
||||
bool completed = (Enemy != null && (Enemy.Removed || Enemy.IsDead)) || coolDownTimer <= 0;
|
||||
if (completed)
|
||||
{
|
||||
if (objectiveManager.CurrentOrder == this && Enemy != null && Enemy.IsDead)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogTargetDown"), null, 3.0f, "targetdown", 30.0f);
|
||||
}
|
||||
if (Weapon != null)
|
||||
{
|
||||
Unequip();
|
||||
@@ -409,12 +295,8 @@ namespace Barotrauma
|
||||
return completed;
|
||||
}
|
||||
|
||||
public override bool CanBeCompleted => !abandon &&
|
||||
(reloadWeaponObjective == null || reloadWeaponObjective.CanBeCompleted) &&
|
||||
(retreatObjective == null || retreatObjective.CanBeCompleted) &&
|
||||
(followTargetObjective == null || followTargetObjective.CanBeCompleted);
|
||||
|
||||
public override float GetPriority() => (Enemy != null && (Enemy.Removed || Enemy.IsDead)) ? 0 : Math.Min(100 * PriorityModifier, 100);
|
||||
public override bool CanBeCompleted => !abandon && (reloadWeaponObjective == null || reloadWeaponObjective.CanBeCompleted) && (retreatObjective == null || retreatObjective.CanBeCompleted);
|
||||
public override float GetPriority(AIObjectiveManager objectiveManager) => (Enemy != null && (Enemy.Removed || Enemy.IsDead)) ? 0 : 100;
|
||||
|
||||
public override bool IsDuplicate(AIObjective otherObjective)
|
||||
{
|
||||
|
||||
@@ -9,9 +9,15 @@ namespace Barotrauma
|
||||
{
|
||||
public override string DebugTag => "contain item";
|
||||
|
||||
public int MinContainedAmount = 1;
|
||||
|
||||
//can either be a tag or an identifier
|
||||
private string[] itemIdentifiers;
|
||||
|
||||
private ItemContainer container;
|
||||
|
||||
public Func<Item, float> GetItemPriority;
|
||||
|
||||
public int MinContainedAmount = 1;
|
||||
public string[] ignoredContainerIdentifiers;
|
||||
|
||||
//can either be a tag or an identifier
|
||||
@@ -22,13 +28,13 @@ namespace Barotrauma
|
||||
private AIObjectiveGetItem getItemObjective;
|
||||
private AIObjectiveGoTo goToObjective;
|
||||
|
||||
public AIObjectiveContainItem(Character character, string itemIdentifier, ItemContainer container, float priorityModifier = 1) : this(character, new string[] { itemIdentifier }, container, priorityModifier) { }
|
||||
public AIObjectiveContainItem(Character character, string itemIdentifier, ItemContainer container)
|
||||
: this(character, new string[] { itemIdentifier }, container)
|
||||
{
|
||||
}
|
||||
|
||||
public AIObjectiveContainItem(Character character, string itemIdentifier, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1)
|
||||
: this(character, new string[] { itemIdentifier }, container, objectiveManager, priorityModifier) { }
|
||||
|
||||
public AIObjectiveContainItem(Character character, string[] itemIdentifiers, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1)
|
||||
: base (character, objectiveManager, priorityModifier)
|
||||
public AIObjectiveContainItem(Character character, string[] itemIdentifiers, ItemContainer container)
|
||||
: base (character, "")
|
||||
{
|
||||
this.itemIdentifiers = itemIdentifiers;
|
||||
for (int i = 0; i < itemIdentifiers.Length; i++)
|
||||
@@ -45,10 +51,22 @@ namespace Barotrauma
|
||||
int containedItemCount = 0;
|
||||
foreach (Item item in container.Inventory.Items)
|
||||
{
|
||||
if (item != null && itemIdentifiers.Any(id => item.Prefab.Identifier == id || item.HasTag(id)))
|
||||
if (item != null && itemIdentifiers.Any(id => item.Prefab.Identifier == id || item.HasTag(id))) containedItemCount++;
|
||||
}
|
||||
|
||||
return containedItemCount >= MinContainedAmount;
|
||||
}
|
||||
|
||||
public override bool CanBeCompleted
|
||||
{
|
||||
get
|
||||
{
|
||||
if (goToObjective != null)
|
||||
{
|
||||
containedItemCount++;
|
||||
}
|
||||
|
||||
return getItemObjective == null || getItemObjective.CanBeCompleted;
|
||||
}
|
||||
return containedItemCount >= MinContainedAmount;
|
||||
}
|
||||
@@ -70,16 +88,17 @@ namespace Barotrauma
|
||||
foreach (string identifier in itemIdentifiers)
|
||||
{
|
||||
itemToContain = character.Inventory.FindItemByIdentifier(identifier) ?? character.Inventory.FindItemByTag(identifier);
|
||||
if (itemToContain != null && itemToContain.Condition > 0.0f) { break; }
|
||||
}
|
||||
if (itemToContain != null && itemToContain.Condition > 0.0f) break;
|
||||
}
|
||||
|
||||
if (itemToContain == null)
|
||||
{
|
||||
TryAddSubObjective(ref getItemObjective, () =>
|
||||
new AIObjectiveGetItem(character, itemIdentifiers, objectiveManager)
|
||||
{
|
||||
GetItemPriority = GetItemPriority,
|
||||
ignoredContainerIdentifiers = ignoredContainerIdentifiers
|
||||
});
|
||||
getItemObjective = new AIObjectiveGetItem(character, itemIdentifiers)
|
||||
{
|
||||
GetItemPriority = GetItemPriority,
|
||||
ignoredContainerIdentifiers = ignoredContainerIdentifiers
|
||||
};
|
||||
AddSubObjective(getItemObjective);
|
||||
return;
|
||||
}
|
||||
if (container.Item.ParentInventory == character.Inventory)
|
||||
@@ -96,8 +115,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
if (container.Item.CurrentHull != character.CurrentHull ||
|
||||
(Vector2.DistanceSquared(character.Position, container.Item.Position) > Math.Pow(container.Item.InteractDistance, 2) && !container.Item.IsInsideTrigger(character.WorldPosition)))
|
||||
if (container.Item.CurrentHull != character.CurrentHull || (Vector2.Distance(character.Position, container.Item.Position) > container.Item.InteractDistance && !container.Item.IsInsideTrigger(character.WorldPosition)))
|
||||
{
|
||||
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(container.Item, character, objectiveManager));
|
||||
return;
|
||||
@@ -109,15 +127,14 @@ namespace Barotrauma
|
||||
|
||||
public override bool IsDuplicate(AIObjective otherObjective)
|
||||
{
|
||||
if (!(otherObjective is AIObjectiveContainItem objective)) { return false; }
|
||||
if (objective.container != container) { return false; }
|
||||
if (objective.itemIdentifiers.Length != itemIdentifiers.Length) { return false; }
|
||||
AIObjectiveContainItem objective = otherObjective as AIObjectiveContainItem;
|
||||
if (objective == null) return false;
|
||||
if (objective.container != container) return false;
|
||||
if (objective.itemIdentifiers.Length != itemIdentifiers.Length) return false;
|
||||
|
||||
for (int i = 0; i < itemIdentifiers.Length; i++)
|
||||
{
|
||||
if (objective.itemIdentifiers[i] != itemIdentifiers[i])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (objective.itemIdentifiers[i] != itemIdentifiers[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -8,53 +8,78 @@ namespace Barotrauma
|
||||
{
|
||||
public override string DebugTag => "decontain item";
|
||||
|
||||
public Func<Item, float> GetItemPriority;
|
||||
|
||||
//can either be a tag or an identifier
|
||||
private readonly string[] itemIdentifiers;
|
||||
private readonly ItemContainer container;
|
||||
private readonly Item targetItem;
|
||||
private string[] itemIdentifiers;
|
||||
|
||||
private ItemContainer container;
|
||||
|
||||
private AIObjectiveGoTo goToObjective;
|
||||
private bool isCompleted;
|
||||
|
||||
public AIObjectiveDecontainItem(Character character, Item targetItem, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
public Func<Item, float> GetItemPriority;
|
||||
|
||||
private AIObjectiveGetItem getItemObjective;
|
||||
private AIObjectiveGoTo goToObjective;
|
||||
private Item targetItem;
|
||||
|
||||
public AIObjectiveDecontainItem(Character character, Item targetItem, ItemContainer container)
|
||||
: base(character, "")
|
||||
{
|
||||
this.targetItem = targetItem;
|
||||
this.container = container;
|
||||
}
|
||||
|
||||
|
||||
public AIObjectiveDecontainItem(Character character, string itemIdentifier, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1)
|
||||
: this(character, new string[] { itemIdentifier }, container, objectiveManager, priorityModifier) { }
|
||||
public AIObjectiveDecontainItem(Character character, string itemIdentifier, ItemContainer container)
|
||||
: this(character, new string[] { itemIdentifier }, container)
|
||||
{
|
||||
}
|
||||
|
||||
public AIObjectiveDecontainItem(Character character, string[] itemIdentifiers, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
public AIObjectiveDecontainItem(Character character, string[] itemIdentifiers, ItemContainer container)
|
||||
: base(character, "")
|
||||
{
|
||||
this.itemIdentifiers = itemIdentifiers;
|
||||
for (int i = 0; i < itemIdentifiers.Length; i++)
|
||||
{
|
||||
itemIdentifiers[i] = itemIdentifiers[i].ToLowerInvariant();
|
||||
}
|
||||
|
||||
this.container = container;
|
||||
}
|
||||
|
||||
public override bool IsCompleted() => isCompleted;
|
||||
public override bool IsCompleted()
|
||||
{
|
||||
return isCompleted;
|
||||
}
|
||||
|
||||
public override float GetPriority()
|
||||
public override bool CanBeCompleted
|
||||
{
|
||||
get
|
||||
{
|
||||
if (goToObjective != null)
|
||||
{
|
||||
return goToObjective.CanBeCompleted;
|
||||
}
|
||||
|
||||
return getItemObjective == null || getItemObjective.CanBeCompleted;
|
||||
}
|
||||
}
|
||||
|
||||
public override float GetPriority(AIObjectiveManager objectiveManager)
|
||||
{
|
||||
if (objectiveManager.CurrentOrder == this)
|
||||
{
|
||||
return AIObjectiveManager.OrderPriority;
|
||||
}
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
if (isCompleted) { return; }
|
||||
if (isCompleted) return;
|
||||
|
||||
Item itemToDecontain = null;
|
||||
|
||||
//get the item that should be de-contained
|
||||
if (targetItem == null)
|
||||
{
|
||||
@@ -63,7 +88,7 @@ namespace Barotrauma
|
||||
foreach (string identifier in itemIdentifiers)
|
||||
{
|
||||
itemToDecontain = container.Inventory.FindItemByIdentifier(identifier) ?? container.Inventory.FindItemByTag(identifier);
|
||||
if (itemToDecontain != null) { break; }
|
||||
if (itemToDecontain != null) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,32 +96,38 @@ namespace Barotrauma
|
||||
{
|
||||
itemToDecontain = targetItem;
|
||||
}
|
||||
|
||||
if (itemToDecontain == null || itemToDecontain.Container != container.Item) // Item not found or already de-contained, consider complete
|
||||
{
|
||||
isCompleted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (itemToDecontain.OwnInventory != character.Inventory && itemToDecontain.ParentInventory != character.Inventory)
|
||||
{
|
||||
if (Vector2.DistanceSquared(character.Position, container.Item.Position) > MathUtils.Pow(container.Item.InteractDistance, 2) && !container.Item.IsInsideTrigger(character.WorldPosition))
|
||||
if (Vector2.Distance(character.Position, container.Item.Position) > container.Item.InteractDistance
|
||||
&& !container.Item.IsInsideTrigger(character.WorldPosition))
|
||||
{
|
||||
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(container.Item, character, objectiveManager));
|
||||
goToObjective = new AIObjectiveGoTo(container.Item, character);
|
||||
AddSubObjective(goToObjective);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
itemToDecontain.Drop(character);
|
||||
isCompleted = true;
|
||||
}
|
||||
|
||||
public override bool IsDuplicate(AIObjective otherObjective)
|
||||
{
|
||||
if (!(otherObjective is AIObjectiveDecontainItem decontainItem)) { return false; }
|
||||
AIObjectiveDecontainItem decontainItem = otherObjective as AIObjectiveDecontainItem;
|
||||
if (decontainItem == null) return false;
|
||||
if (decontainItem.itemIdentifiers != null && itemIdentifiers != null)
|
||||
{
|
||||
if (decontainItem.itemIdentifiers.Length != itemIdentifiers.Length) { return false; }
|
||||
if (decontainItem.itemIdentifiers.Length != itemIdentifiers.Length) return false;
|
||||
for (int i = 0; i < decontainItem.itemIdentifiers.Length; i++)
|
||||
{
|
||||
if (decontainItem.itemIdentifiers[i] != itemIdentifiers[i]) { return false; }
|
||||
if (decontainItem.itemIdentifiers[i] != itemIdentifiers[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -104,6 +135,7 @@ namespace Barotrauma
|
||||
{
|
||||
return decontainItem.targetItem == targetItem;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -11,117 +10,125 @@ namespace Barotrauma
|
||||
{
|
||||
public override string DebugTag => "extinguish fire";
|
||||
public override bool ForceRun => true;
|
||||
public override bool ConcurrentObjectives => true;
|
||||
public override bool KeepDivingGearOn => true;
|
||||
|
||||
private readonly Hull targetHull;
|
||||
private Hull targetHull;
|
||||
|
||||
private AIObjectiveGetItem getExtinguisherObjective;
|
||||
|
||||
private AIObjectiveGoTo gotoObjective;
|
||||
|
||||
private float useExtinquisherTimer;
|
||||
|
||||
public AIObjectiveExtinguishFire(Character character, Hull targetHull, AIObjectiveManager objectiveManager, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
public AIObjectiveExtinguishFire(Character character, Hull targetHull) : base(character, "")
|
||||
{
|
||||
this.targetHull = targetHull;
|
||||
}
|
||||
|
||||
public override float GetPriority()
|
||||
public override float GetPriority(AIObjectiveManager objectiveManager)
|
||||
{
|
||||
if (Character.CharacterList.Any(c => c.CurrentHull == targetHull && !HumanAIController.IsFriendly(c))) { return 0; }
|
||||
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 severity = AIObjectiveExtinguishFires.GetFireSeverity(targetHull);
|
||||
float severityFactor = MathHelper.Lerp(0, 1, severity / 100);
|
||||
float devotion = Math.Max(Priority, 10) / 100;
|
||||
return MathHelper.Lerp(0, 100, MathHelper.Clamp(devotion + severityFactor * distanceFactor, 0, 1));
|
||||
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() => targetHull.FireSources.None();
|
||||
public override bool IsCompleted()
|
||||
{
|
||||
return targetHull.FireSources.Count == 0;
|
||||
}
|
||||
|
||||
public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveExtinguishFire otherExtinguishFire && otherExtinguishFire.targetHull == targetHull;
|
||||
public override bool IsDuplicate(AIObjective otherObjective)
|
||||
{
|
||||
var otherExtinguishFire = otherObjective as AIObjectiveExtinguishFire;
|
||||
return otherExtinguishFire != null && otherExtinguishFire.targetHull == targetHull;
|
||||
}
|
||||
|
||||
public override bool CanBeCompleted
|
||||
{
|
||||
get { return getExtinguisherObjective == null || getExtinguisherObjective.IsCompleted() || getExtinguisherObjective.CanBeCompleted; }
|
||||
}
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
var extinguisherItem = character.Inventory.FindItemByIdentifier("extinguisher") ?? character.Inventory.FindItemByTag("extinguisher");
|
||||
if (extinguisherItem == null || extinguisherItem.Condition <= 0.0f || !character.HasEquippedItem(extinguisherItem))
|
||||
{
|
||||
TryAddSubObjective(ref getExtinguisherObjective, () =>
|
||||
if (getExtinguisherObjective == null)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogFindExtinguisher"), null, 2.0f, "findextinguisher", 30.0f);
|
||||
return new AIObjectiveGetItem(character, "extinguisher", objectiveManager, equip: true);
|
||||
});
|
||||
getExtinguisherObjective = new AIObjectiveGetItem(character, "extinguisher", true);
|
||||
}
|
||||
else
|
||||
{
|
||||
getExtinguisherObjective.TryComplete(deltaTime);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
else
|
||||
|
||||
var extinguisher = extinguisherItem.GetComponent<RepairTool>();
|
||||
if (extinguisher == null)
|
||||
{
|
||||
var extinguisher = extinguisherItem.GetComponent<RepairTool>();
|
||||
if (extinguisher == null)
|
||||
DebugConsole.ThrowError("AIObjectiveExtinguishFire failed - the item \"" + extinguisherItem + "\" has no RepairTool component but is tagged as an extinguisher");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (FireSource fs in targetHull.FireSources)
|
||||
{
|
||||
bool inRange = fs.IsInDamageRange(character, MathHelper.Clamp(fs.DamageRange * 1.5f, extinguisher.Range * 0.5f, extinguisher.Range));
|
||||
bool move = !inRange;
|
||||
if (inRange || useExtinquisherTimer > 0.0f)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("AIObjectiveExtinguishFire failed - the item \"" + extinguisherItem + "\" has no RepairTool component but is tagged as an extinguisher");
|
||||
#endif
|
||||
abandon = true;
|
||||
return;
|
||||
}
|
||||
foreach (FireSource fs in targetHull.FireSources)
|
||||
{
|
||||
bool inRange = fs.IsInDamageRange(character, MathHelper.Clamp(fs.DamageRange * 1.5f, extinguisher.Range * 0.5f, extinguisher.Range));
|
||||
bool move = !inRange;
|
||||
if (inRange || useExtinquisherTimer > 0.0f)
|
||||
useExtinquisherTimer += deltaTime;
|
||||
if (useExtinquisherTimer > 2.0f)
|
||||
{
|
||||
useExtinquisherTimer += deltaTime;
|
||||
if (useExtinquisherTimer > 2.0f)
|
||||
useExtinquisherTimer = 0.0f;
|
||||
}
|
||||
character.AIController.SteeringManager.Reset();
|
||||
character.CursorPosition = fs.Position;
|
||||
if (extinguisher.Item.RequireAimToUse)
|
||||
{
|
||||
character.SetInput(InputType.Aim, false, true);
|
||||
}
|
||||
Limb sightLimb = null;
|
||||
if (character.Inventory.IsInLimbSlot(extinguisherItem, InvSlotType.RightHand))
|
||||
{
|
||||
sightLimb = character.AnimController.GetLimb(LimbType.RightHand);
|
||||
}
|
||||
else if (character.Inventory.IsInLimbSlot(extinguisherItem, InvSlotType.LeftHand))
|
||||
{
|
||||
sightLimb = character.AnimController.GetLimb(LimbType.LeftHand);
|
||||
}
|
||||
if (!character.CanSeeTarget(fs, sightLimb))
|
||||
{
|
||||
move = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
move = false;
|
||||
extinguisher.Use(deltaTime, character);
|
||||
if (!targetHull.FireSources.Contains(fs))
|
||||
{
|
||||
useExtinquisherTimer = 0.0f;
|
||||
}
|
||||
character.AIController.SteeringManager.Reset();
|
||||
character.CursorPosition = fs.Position;
|
||||
if (extinguisher.Item.RequireAimToUse)
|
||||
{
|
||||
bool isOperatingButtons = false;
|
||||
if (SteeringManager == PathSteering)
|
||||
{
|
||||
var door = PathSteering.CurrentPath?.CurrentNode?.ConnectedDoor;
|
||||
if (door != null && !door.IsOpen)
|
||||
{
|
||||
isOperatingButtons = door.HasIntegratedButtons || door.Item.GetConnectedComponents<Controller>(true).Any();
|
||||
}
|
||||
}
|
||||
if (!isOperatingButtons)
|
||||
{
|
||||
character.SetInput(InputType.Aim, false, true);
|
||||
}
|
||||
}
|
||||
Limb sightLimb = null;
|
||||
if (character.Inventory.IsInLimbSlot(extinguisherItem, InvSlotType.RightHand))
|
||||
{
|
||||
sightLimb = character.AnimController.GetLimb(LimbType.RightHand);
|
||||
}
|
||||
else if (character.Inventory.IsInLimbSlot(extinguisherItem, InvSlotType.LeftHand))
|
||||
{
|
||||
sightLimb = character.AnimController.GetLimb(LimbType.LeftHand);
|
||||
}
|
||||
if (!character.CanSeeTarget(fs, sightLimb))
|
||||
{
|
||||
move = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
move = false;
|
||||
extinguisher.Use(deltaTime, character);
|
||||
if (!targetHull.FireSources.Contains(fs))
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogPutOutFire").Replace("[roomname]", targetHull.Name), null, 0, "putoutfire", 10.0f);
|
||||
}
|
||||
character.Speak(TextManager.Get("DialogPutOutFire").Replace("[roomname]", targetHull.Name), null, 0, "putoutfire", 10.0f);
|
||||
}
|
||||
}
|
||||
if (move)
|
||||
{
|
||||
//go to the first firesource
|
||||
TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(fs.Position), character, objectiveManager));
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (move)
|
||||
{
|
||||
//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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +1,62 @@
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class AIObjectiveExtinguishFires : AIObjectiveLoop<Hull>
|
||||
class AIObjectiveExtinguishFires : AIObjective
|
||||
{
|
||||
public override string DebugTag => "extinguish fires";
|
||||
public override bool ForceRun => true;
|
||||
public override bool KeepDivingGearOn => true;
|
||||
|
||||
public AIObjectiveExtinguishFires(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { }
|
||||
private Dictionary<Hull, AIObjectiveExtinguishFire> extinguishObjectives = new Dictionary<Hull, AIObjectiveExtinguishFire>();
|
||||
|
||||
protected override void FindTargets()
|
||||
public AIObjectiveExtinguishFires(Character character) : base(character, "") { }
|
||||
|
||||
public override float GetPriority(AIObjectiveManager objectiveManager)
|
||||
{
|
||||
base.FindTargets();
|
||||
if (targets.None() && objectiveManager.CurrentOrder == this)
|
||||
if (character.Submarine == null) { return 0; }
|
||||
int fireCount = character.Submarine.GetHulls(true).Sum(h => h.FireSources.Count);
|
||||
if (objectiveManager.CurrentOrder == this && fireCount > 0)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogNoFire"), null, 3.0f, "nofire", 30.0f);
|
||||
return AIObjectiveManager.OrderPriority;
|
||||
}
|
||||
|
||||
return MathHelper.Clamp(fireCount * 20, 0, 100);
|
||||
}
|
||||
|
||||
public override bool IsCompleted() => false;
|
||||
public override bool CanBeCompleted => true;
|
||||
|
||||
public override bool IsDuplicate(AIObjective otherObjective)
|
||||
{
|
||||
return otherObjective is AIObjectiveExtinguishFires;
|
||||
}
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
SyncRemovedObjectives(extinguishObjectives, Hull.hullList);
|
||||
if (character.Submarine == null) { return; }
|
||||
foreach (Hull hull in Hull.hullList)
|
||||
{
|
||||
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))
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool Filter(Hull target)
|
||||
{
|
||||
if (target == null) { return false; }
|
||||
if (target.FireSources.None()) { return false; }
|
||||
if (target.Submarine == null) { return false; }
|
||||
if (target.Submarine.TeamID != character.TeamID) { return false; }
|
||||
if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(target, true)) { return false; }
|
||||
//if (Character.CharacterList.Any(c => c.CurrentHull == target && !HumanAIController.IsFriendly(c))) { return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override float TargetEvaluation() => objectiveManager.CurrentObjective == this ? 100 : targets.Sum(t => GetFireSeverity(t));
|
||||
|
||||
public static float GetFireSeverity(Hull hull) => hull.FireSources.Sum(fs => fs.Size.X);
|
||||
|
||||
public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveExtinguishFires;
|
||||
protected override IEnumerable<Hull> GetList() => Hull.hullList;
|
||||
|
||||
protected override AIObjective ObjectiveConstructor(Hull target) => new AIObjectiveExtinguishFire(character, target, objectiveManager, PriorityModifier);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -10,10 +9,8 @@ namespace Barotrauma
|
||||
public override string DebugTag => "find diving gear";
|
||||
public override bool ForceRun => true;
|
||||
|
||||
private readonly string gearTag;
|
||||
|
||||
private AIObjectiveGetItem getDivingGear;
|
||||
private AIObjectiveContainItem getOxygen;
|
||||
private AIObjective subObjective;
|
||||
private string gearTag;
|
||||
|
||||
public override bool IsCompleted()
|
||||
{
|
||||
@@ -24,16 +21,15 @@ namespace Barotrauma
|
||||
{
|
||||
var containedItems = character.Inventory.Items[i].ContainedItems;
|
||||
if (containedItems == null) { continue; }
|
||||
return containedItems.Any(it => (it.Prefab.Identifier == "oxygentank" || it.HasTag("oxygensource")) && it.Condition > 0.0f);
|
||||
|
||||
var oxygenTank = containedItems.FirstOrDefault(it => (it.Prefab.Identifier == "oxygentank" || it.HasTag("oxygensource")) && it.Condition > 0.0f);
|
||||
if (oxygenTank != null) { return true; }
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public override float GetPriority() => MathHelper.Clamp(100 - character.OxygenAvailable, 0, 100);
|
||||
public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveFindDivingGear;
|
||||
|
||||
public AIObjectiveFindDivingGear(Character character, bool needDivingSuit, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier)
|
||||
public AIObjectiveFindDivingGear(Character character, bool needDivingSuit) : base(character, "")
|
||||
{
|
||||
gearTag = needDivingSuit ? "divingsuit" : "diving";
|
||||
}
|
||||
@@ -46,23 +42,16 @@ namespace Barotrauma
|
||||
TryAddSubObjective(ref getDivingGear, () =>
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogGetDivingGear"), null, 0.0f, "getdivinggear", 30.0f);
|
||||
return new AIObjectiveGetItem(character, gearTag, objectiveManager, equip: true);
|
||||
});
|
||||
subObjective = new AIObjectiveGetItem(character, gearTag, true);
|
||||
}
|
||||
}
|
||||
if (getDivingGear != null) { return; }
|
||||
var containedItems = item.ContainedItems;
|
||||
if (containedItems == null)
|
||||
{
|
||||
var containedItems = item.ContainedItems;
|
||||
if (containedItems == null)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("AIObjectiveFindDivingGear failed - the item \"" + item + "\" has no proper inventory");
|
||||
#endif
|
||||
abandon = true;
|
||||
return;
|
||||
}
|
||||
// Drop empty tanks
|
||||
if (containedItems == null) { return; }
|
||||
//check if there's an oxygen tank in the mask/suit
|
||||
foreach (Item containedItem in containedItems)
|
||||
{
|
||||
if (containedItem == null) { continue; }
|
||||
@@ -70,16 +59,26 @@ namespace Barotrauma
|
||||
{
|
||||
containedItem.Drop(character);
|
||||
}
|
||||
}
|
||||
if (containedItems.None(it => (it.Prefab.Identifier == "oxygentank" || it.HasTag("oxygensource")) && it.Condition > 0.0f))
|
||||
{
|
||||
TryAddSubObjective(ref getOxygen, () =>
|
||||
else if (containedItem.Prefab.Identifier == "oxygentank" || containedItem.HasTag("oxygensource"))
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogGetOxygenTank"), null, 0, "getoxygentank", 30.0f);
|
||||
return new AIObjectiveContainItem(character, new string[] { "oxygentank", "oxygensource" }, item.GetComponent<ItemContainer>(), objectiveManager);
|
||||
});
|
||||
//we've got an oxygen source inside the mask/suit, all good
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!(subObjective is AIObjectiveContainItem) || subObjective.IsCompleted())
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogGetOxygenTank"), null, 0, "getoxygentank", 30.0f);
|
||||
subObjective = new AIObjectiveContainItem(character, new string[] { "oxygentank", "oxygensource" }, item.GetComponent<ItemContainer>());
|
||||
}
|
||||
}
|
||||
if (subObjective != null)
|
||||
{
|
||||
subObjective.TryComplete(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool CanBeCompleted => subObjective == null || subObjective.CanBeCompleted;
|
||||
public override float GetPriority(AIObjectiveManager objectiveManager) => MathHelper.Clamp(100 - character.OxygenAvailable, 0, 100);
|
||||
public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveFindDivingGear;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace Barotrauma
|
||||
{
|
||||
public override string DebugTag => "find safety";
|
||||
public override bool ForceRun => true;
|
||||
public override bool KeepDivingGearOn => true;
|
||||
|
||||
// TODO: expose?
|
||||
const float priorityIncrease = 25;
|
||||
@@ -17,7 +18,7 @@ namespace Barotrauma
|
||||
const float SearchHullInterval = 3.0f;
|
||||
const float clearUnreachableInterval = 30;
|
||||
|
||||
public readonly HashSet<Hull> unreachable = new HashSet<Hull>();
|
||||
public readonly List<Hull> unreachable = new List<Hull>();
|
||||
|
||||
private float currenthullSafety;
|
||||
private float unreachableClearTimer;
|
||||
@@ -26,15 +27,49 @@ namespace Barotrauma
|
||||
private AIObjectiveGoTo goToObjective;
|
||||
private AIObjectiveFindDivingGear divingGearObjective;
|
||||
|
||||
public AIObjectiveFindSafety(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { }
|
||||
public AIObjectiveFindSafety(Character character) : base(character, "") { }
|
||||
|
||||
public override bool IsCompleted() => false;
|
||||
public override bool CanBeCompleted => true;
|
||||
|
||||
public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveFindSafety;
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
var currentHull = character.AnimController.CurrentHull;
|
||||
if (HumanAIController.NeedsDivingGear(currentHull) && divingGearObjective == null)
|
||||
{
|
||||
bool needsDivingSuit = currentHull == null || currentHull.WaterPercentage > 90;
|
||||
bool hasEquipment = needsDivingSuit ? HumanAIController.HasDivingSuit(character) : HumanAIController.HasDivingGear(character);
|
||||
if (!hasEquipment)
|
||||
{
|
||||
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 and can be completed, wait for it to complete.
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
divingGearObjective = null;
|
||||
// Reduce the timer so that we get a safe hull target faster.
|
||||
searchHullTimer = Math.Min(1, searchHullTimer);
|
||||
}
|
||||
}
|
||||
|
||||
if (currenthullSafety < HumanAIController.HULL_SAFETY_THRESHOLD)
|
||||
{
|
||||
searchHullTimer = Math.Min(1, searchHullTimer);
|
||||
}
|
||||
|
||||
if (unreachableClearTimer > 0)
|
||||
{
|
||||
unreachableClearTimer -= deltaTime;
|
||||
@@ -44,753 +79,74 @@ namespace Barotrauma
|
||||
unreachableClearTimer = clearUnreachableInterval;
|
||||
unreachable.Clear();
|
||||
}
|
||||
|
||||
if (searchHullTimer > 0.0f)
|
||||
{
|
||||
unreachableClearTimer = clearUnreachableInterval;
|
||||
unreachable.Clear();
|
||||
}
|
||||
if (character.CurrentHull == null)
|
||||
{
|
||||
currenthullSafety = 0;
|
||||
Priority = 100;
|
||||
return;
|
||||
var bestHull = FindBestHull();
|
||||
if (bestHull != null && bestHull != currentHull)
|
||||
{
|
||||
if (goToObjective != null)
|
||||
{
|
||||
if (goToObjective.Target != bestHull)
|
||||
{
|
||||
// If we need diving gear, we should already have it, if possible.
|
||||
goToObjective = new AIObjectiveGoTo(bestHull, character, getDivingGearIfNeeded: false)
|
||||
{
|
||||
AllowGoingOutside = HumanAIController.HasDivingSuit(character)
|
||||
};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
goToObjective = new AIObjectiveGoTo(bestHull, character, getDivingGearIfNeeded: false)
|
||||
{
|
||||
AllowGoingOutside = HumanAIController.HasDivingSuit(character)
|
||||
};
|
||||
}
|
||||
}
|
||||
searchHullTimer = SearchHullInterval;
|
||||
}
|
||||
if (character.OxygenAvailable < CharacterHealth.LowOxygenThreshold) { Priority = 100; }
|
||||
currenthullSafety = HumanAIController.GetHullSafety(character.CurrentHull);
|
||||
if (currenthullSafety > HumanAIController.HULL_SAFETY_THRESHOLD)
|
||||
{
|
||||
Priority -= priorityDecrease * deltaTime;
|
||||
goToObjective.TryComplete(deltaTime);
|
||||
if (!goToObjective.CanBeCompleted)
|
||||
{
|
||||
if (!unreachable.Contains(goToObjective.Target))
|
||||
{
|
||||
unreachable.Add(goToObjective.Target as Hull);
|
||||
}
|
||||
goToObjective = null;
|
||||
HumanAIController.ObjectiveManager.GetObjective<AIObjectiveIdle>().Wander(deltaTime);
|
||||
//SteeringManager.SteeringWander();
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (currentHull != null)
|
||||
{
|
||||
float dangerFactor = (100 - currenthullSafety) / 100;
|
||||
Priority += dangerFactor * priorityIncrease * deltaTime;
|
||||
}
|
||||
Priority = MathHelper.Clamp(Priority, 0, 100);
|
||||
if (divingGearObjective != null && !divingGearObjective.IsCompleted() && divingGearObjective.CanBeCompleted)
|
||||
{
|
||||
Priority = Math.Max(Priority, Math.Min(AIObjectiveManager.OrderPriority + 20, 100));
|
||||
}
|
||||
}
|
||||
//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)
|
||||
{
|
||||
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(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
|
||||
}
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
var currentHull = character.AnimController.CurrentHull;
|
||||
bool needsDivingGear = HumanAIController.NeedsDivingGear(currentHull);
|
||||
bool needsDivingSuit = needsDivingGear && (currentHull == null || currentHull.WaterPercentage > 90);
|
||||
bool needsEquipment = false;
|
||||
if (needsDivingSuit)
|
||||
{
|
||||
needsEquipment = !HumanAIController.HasDivingSuit(character);
|
||||
}
|
||||
else if (needsDivingGear)
|
||||
{
|
||||
needsEquipment = !HumanAIController.HasDivingMask(character);
|
||||
}
|
||||
if (needsEquipment)
|
||||
{
|
||||
TryAddSubObjective(ref divingGearObjective,
|
||||
() => new AIObjectiveFindDivingGear(character, needsDivingSuit, objectiveManager),
|
||||
onAbandon: () => searchHullTimer = Math.Min(1, searchHullTimer));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (divingGearObjective != null && divingGearObjective.IsCompleted())
|
||||
{
|
||||
// Reset the devotion.
|
||||
Priority = 0;
|
||||
divingGearObjective = null;
|
||||
}
|
||||
if (currenthullSafety < HumanAIController.HULL_SAFETY_THRESHOLD)
|
||||
{
|
||||
searchHullTimer = Math.Min(1, searchHullTimer);
|
||||
}
|
||||
if (searchHullTimer > 0.0f)
|
||||
{
|
||||
searchHullTimer -= deltaTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
searchHullTimer = SearchHullInterval;
|
||||
var bestHull = FindBestHull();
|
||||
if (bestHull != null && bestHull != currentHull)
|
||||
{
|
||||
if (goToObjective?.Target != bestHull)
|
||||
{
|
||||
goToObjective = null;
|
||||
}
|
||||
TryAddSubObjective(ref goToObjective, () =>
|
||||
new AIObjectiveGoTo(bestHull, character, objectiveManager, getDivingGearIfNeeded: false)
|
||||
{
|
||||
// If we need diving gear, we should already have it, if possible.
|
||||
AllowGoingOutside = HumanAIController.HasDivingSuit(character)
|
||||
}, onAbandon: () => unreachable.Add(goToObjective.Target as Hull));
|
||||
}
|
||||
TryAddSubObjective(ref goToObjective, () =>
|
||||
new AIObjectiveGoTo(bestHull, character, objectiveManager, getDivingGearIfNeeded: false)
|
||||
{
|
||||
// If we need diving gear, we should already have it, if possible.
|
||||
AllowGoingOutside = HumanAIController.HasDivingSuit(character)
|
||||
}, onAbandon: () => unreachable.Add(goToObjective.Target as Hull));
|
||||
}
|
||||
if (goToObjective != null) { return; }
|
||||
if (currentHull == null) { return; }
|
||||
//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)
|
||||
{
|
||||
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(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
|
||||
}
|
||||
foreach (Character enemy in Character.CharacterList)
|
||||
{
|
||||
//don't run from friendly NPCs
|
||||
if (enemy.TeamID == Character.TeamType.FriendlyNPC) { continue; }
|
||||
//friendly NPCs don't run away from anything but characters controlled by EnemyAIController (= monsters)
|
||||
if (character.TeamID == Character.TeamType.FriendlyNPC && !(enemy.AIController is EnemyAIController)) { continue; }
|
||||
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(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
|
||||
}
|
||||
}
|
||||
if (escapeVel != Vector2.Zero)
|
||||
{
|
||||
//only move if we haven't reached the edge of the room
|
||||
if ((escapeVel.X < 0 && character.Position.X > currentHull.Rect.X + 50) ||
|
||||
(escapeVel.X > 0 && character.Position.X < currentHull.Rect.Right - 50))
|
||||
{
|
||||
character.AIController.SteeringManager.SteeringManual(deltaTime, escapeVel);
|
||||
}
|
||||
else
|
||||
{
|
||||
character.AnimController.TargetDir = escapeVel.X < 0.0f ? Direction.Right : Direction.Left;
|
||||
character.AIController.SteeringManager.Reset();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
character.AIController.SteeringManager.Reset();
|
||||
}
|
||||
if (goToObjective != null) { return; }
|
||||
if (currentHull == null) { return; }
|
||||
//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)
|
||||
{
|
||||
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(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
|
||||
}
|
||||
foreach (Character enemy in Character.CharacterList)
|
||||
{
|
||||
//don't run from friendly NPCs
|
||||
if (enemy.TeamID == Character.TeamType.FriendlyNPC) { continue; }
|
||||
//friendly NPCs don't run away from anything but characters controlled by EnemyAIController (= monsters)
|
||||
if (character.TeamID == Character.TeamType.FriendlyNPC && !(enemy.AIController is EnemyAIController)) { continue; }
|
||||
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(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
|
||||
}
|
||||
}
|
||||
if (escapeVel != Vector2.Zero)
|
||||
{
|
||||
//only move if we haven't reached the edge of the room
|
||||
if ((escapeVel.X < 0 && character.Position.X > currentHull.Rect.X + 50) ||
|
||||
(escapeVel.X > 0 && character.Position.X < currentHull.Rect.Right - 50))
|
||||
{
|
||||
character.AIController.SteeringManager.SteeringManual(deltaTime, escapeVel);
|
||||
}
|
||||
else
|
||||
{
|
||||
character.AnimController.TargetDir = escapeVel.X < 0.0f ? Direction.Right : Direction.Left;
|
||||
character.AIController.SteeringManager.Reset();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
character.AIController.SteeringManager.Reset();
|
||||
}
|
||||
if (goToObjective != null) { return; }
|
||||
if (currentHull == null) { return; }
|
||||
//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)
|
||||
{
|
||||
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(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
|
||||
}
|
||||
foreach (Character enemy in Character.CharacterList)
|
||||
{
|
||||
//don't run from friendly NPCs
|
||||
if (enemy.TeamID == Character.TeamType.FriendlyNPC) { continue; }
|
||||
//friendly NPCs don't run away from anything but characters controlled by EnemyAIController (= monsters)
|
||||
if (character.TeamID == Character.TeamType.FriendlyNPC && !(enemy.AIController is EnemyAIController)) { continue; }
|
||||
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(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
|
||||
}
|
||||
}
|
||||
if (escapeVel != Vector2.Zero)
|
||||
{
|
||||
//only move if we haven't reached the edge of the room
|
||||
if ((escapeVel.X < 0 && character.Position.X > currentHull.Rect.X + 50) ||
|
||||
(escapeVel.X > 0 && character.Position.X < currentHull.Rect.Right - 50))
|
||||
{
|
||||
character.AIController.SteeringManager.SteeringManual(deltaTime, escapeVel);
|
||||
}
|
||||
else
|
||||
{
|
||||
character.AnimController.TargetDir = escapeVel.X < 0.0f ? Direction.Right : Direction.Left;
|
||||
character.AIController.SteeringManager.Reset();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
character.AIController.SteeringManager.Reset();
|
||||
}
|
||||
if (goToObjective != null) { return; }
|
||||
if (currentHull == null) { return; }
|
||||
//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)
|
||||
{
|
||||
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(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
|
||||
}
|
||||
foreach (Character enemy in Character.CharacterList)
|
||||
{
|
||||
//don't run from friendly NPCs
|
||||
if (enemy.TeamID == Character.TeamType.FriendlyNPC) { continue; }
|
||||
//friendly NPCs don't run away from anything but characters controlled by EnemyAIController (= monsters)
|
||||
if (character.TeamID == Character.TeamType.FriendlyNPC && !(enemy.AIController is EnemyAIController)) { continue; }
|
||||
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(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
|
||||
}
|
||||
}
|
||||
if (escapeVel != Vector2.Zero)
|
||||
{
|
||||
//only move if we haven't reached the edge of the room
|
||||
if ((escapeVel.X < 0 && character.Position.X > currentHull.Rect.X + 50) ||
|
||||
(escapeVel.X > 0 && character.Position.X < currentHull.Rect.Right - 50))
|
||||
{
|
||||
character.AIController.SteeringManager.SteeringManual(deltaTime, escapeVel);
|
||||
}
|
||||
else
|
||||
{
|
||||
character.AnimController.TargetDir = escapeVel.X < 0.0f ? Direction.Right : Direction.Left;
|
||||
character.AIController.SteeringManager.Reset();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
character.AIController.SteeringManager.Reset();
|
||||
}
|
||||
if (goToObjective != null) { return; }
|
||||
if (currentHull == null) { return; }
|
||||
//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)
|
||||
{
|
||||
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(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
|
||||
}
|
||||
foreach (Character enemy in Character.CharacterList)
|
||||
{
|
||||
//don't run from friendly NPCs
|
||||
if (enemy.TeamID == Character.TeamType.FriendlyNPC) { continue; }
|
||||
//friendly NPCs don't run away from anything but characters controlled by EnemyAIController (= monsters)
|
||||
if (character.TeamID == Character.TeamType.FriendlyNPC && !(enemy.AIController is EnemyAIController)) { continue; }
|
||||
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(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
|
||||
}
|
||||
}
|
||||
if (escapeVel != Vector2.Zero)
|
||||
{
|
||||
//only move if we haven't reached the edge of the room
|
||||
if ((escapeVel.X < 0 && character.Position.X > currentHull.Rect.X + 50) ||
|
||||
(escapeVel.X > 0 && character.Position.X < currentHull.Rect.Right - 50))
|
||||
{
|
||||
character.AIController.SteeringManager.SteeringManual(deltaTime, escapeVel);
|
||||
}
|
||||
else
|
||||
{
|
||||
character.AnimController.TargetDir = escapeVel.X < 0.0f ? Direction.Right : Direction.Left;
|
||||
character.AIController.SteeringManager.Reset();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
character.AIController.SteeringManager.Reset();
|
||||
}
|
||||
if (goToObjective != null) { return; }
|
||||
if (currentHull == null) { return; }
|
||||
//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)
|
||||
{
|
||||
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(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
|
||||
}
|
||||
foreach (Character enemy in Character.CharacterList)
|
||||
{
|
||||
//don't run from friendly NPCs
|
||||
if (enemy.TeamID == Character.TeamType.FriendlyNPC) { continue; }
|
||||
//friendly NPCs don't run away from anything but characters controlled by EnemyAIController (= monsters)
|
||||
if (character.TeamID == Character.TeamType.FriendlyNPC && !(enemy.AIController is EnemyAIController)) { continue; }
|
||||
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(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
|
||||
}
|
||||
}
|
||||
if (escapeVel != Vector2.Zero)
|
||||
{
|
||||
//only move if we haven't reached the edge of the room
|
||||
if ((escapeVel.X < 0 && character.Position.X > currentHull.Rect.X + 50) ||
|
||||
(escapeVel.X > 0 && character.Position.X < currentHull.Rect.Right - 50))
|
||||
{
|
||||
character.AIController.SteeringManager.SteeringManual(deltaTime, escapeVel);
|
||||
}
|
||||
else
|
||||
{
|
||||
character.AnimController.TargetDir = escapeVel.X < 0.0f ? Direction.Right : Direction.Left;
|
||||
character.AIController.SteeringManager.Reset();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
character.AIController.SteeringManager.Reset();
|
||||
}
|
||||
if (goToObjective != null) { return; }
|
||||
if (currentHull == null) { return; }
|
||||
//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)
|
||||
{
|
||||
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(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
|
||||
}
|
||||
foreach (Character enemy in Character.CharacterList)
|
||||
{
|
||||
//don't run from friendly NPCs
|
||||
if (enemy.TeamID == Character.TeamType.FriendlyNPC) { continue; }
|
||||
//friendly NPCs don't run away from anything but characters controlled by EnemyAIController (= monsters)
|
||||
if (character.TeamID == Character.TeamType.FriendlyNPC && !(enemy.AIController is EnemyAIController)) { continue; }
|
||||
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(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
|
||||
}
|
||||
}
|
||||
if (escapeVel != Vector2.Zero)
|
||||
{
|
||||
//only move if we haven't reached the edge of the room
|
||||
if ((escapeVel.X < 0 && character.Position.X > currentHull.Rect.X + 50) ||
|
||||
(escapeVel.X > 0 && character.Position.X < currentHull.Rect.Right - 50))
|
||||
{
|
||||
character.AIController.SteeringManager.SteeringManual(deltaTime, escapeVel);
|
||||
}
|
||||
else
|
||||
{
|
||||
character.AnimController.TargetDir = escapeVel.X < 0.0f ? Direction.Right : Direction.Left;
|
||||
character.AIController.SteeringManager.Reset();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
character.AIController.SteeringManager.Reset();
|
||||
}
|
||||
if (goToObjective != null) { return; }
|
||||
if (currentHull == null) { return; }
|
||||
//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)
|
||||
{
|
||||
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(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
|
||||
}
|
||||
foreach (Character enemy in Character.CharacterList)
|
||||
{
|
||||
//don't run from friendly NPCs
|
||||
if (enemy.TeamID == Character.TeamType.FriendlyNPC) { continue; }
|
||||
//friendly NPCs don't run away from anything but characters controlled by EnemyAIController (= monsters)
|
||||
if (character.TeamID == Character.TeamType.FriendlyNPC && !(enemy.AIController is EnemyAIController)) { continue; }
|
||||
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(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
|
||||
}
|
||||
}
|
||||
if (escapeVel != Vector2.Zero)
|
||||
{
|
||||
//only move if we haven't reached the edge of the room
|
||||
if ((escapeVel.X < 0 && character.Position.X > currentHull.Rect.X + 50) ||
|
||||
(escapeVel.X > 0 && character.Position.X < currentHull.Rect.Right - 50))
|
||||
{
|
||||
character.AIController.SteeringManager.SteeringManual(deltaTime, escapeVel);
|
||||
}
|
||||
else
|
||||
{
|
||||
character.AnimController.TargetDir = escapeVel.X < 0.0f ? Direction.Right : Direction.Left;
|
||||
character.AIController.SteeringManager.Reset();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
character.AIController.SteeringManager.Reset();
|
||||
}
|
||||
if (goToObjective != null) { return; }
|
||||
if (currentHull == null) { return; }
|
||||
//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)
|
||||
{
|
||||
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(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
|
||||
}
|
||||
foreach (Character enemy in Character.CharacterList)
|
||||
{
|
||||
//don't run from friendly NPCs
|
||||
if (enemy.TeamID == Character.TeamType.FriendlyNPC) { continue; }
|
||||
//friendly NPCs don't run away from anything but characters controlled by EnemyAIController (= monsters)
|
||||
if (character.TeamID == Character.TeamType.FriendlyNPC && !(enemy.AIController is EnemyAIController)) { continue; }
|
||||
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(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
|
||||
}
|
||||
}
|
||||
if (escapeVel != Vector2.Zero)
|
||||
{
|
||||
//only move if we haven't reached the edge of the room
|
||||
if ((escapeVel.X < 0 && character.Position.X > currentHull.Rect.X + 50) ||
|
||||
(escapeVel.X > 0 && character.Position.X < currentHull.Rect.Right - 50))
|
||||
{
|
||||
character.AIController.SteeringManager.SteeringManual(deltaTime, escapeVel);
|
||||
}
|
||||
else
|
||||
{
|
||||
character.AnimController.TargetDir = escapeVel.X < 0.0f ? Direction.Right : Direction.Left;
|
||||
character.AIController.SteeringManager.Reset();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
character.AIController.SteeringManager.Reset();
|
||||
}
|
||||
if (goToObjective != null) { return; }
|
||||
if (currentHull == null) { return; }
|
||||
//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)
|
||||
{
|
||||
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(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
|
||||
}
|
||||
foreach (Character enemy in Character.CharacterList)
|
||||
{
|
||||
//don't run from friendly NPCs
|
||||
if (enemy.TeamID == Character.TeamType.FriendlyNPC) { continue; }
|
||||
//friendly NPCs don't run away from anything but characters controlled by EnemyAIController (= monsters)
|
||||
if (character.TeamID == Character.TeamType.FriendlyNPC && !(enemy.AIController is EnemyAIController)) { continue; }
|
||||
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(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
|
||||
}
|
||||
}
|
||||
if (escapeVel != Vector2.Zero)
|
||||
{
|
||||
//only move if we haven't reached the edge of the room
|
||||
if ((escapeVel.X < 0 && character.Position.X > currentHull.Rect.X + 50) ||
|
||||
(escapeVel.X > 0 && character.Position.X < currentHull.Rect.Right - 50))
|
||||
{
|
||||
character.AIController.SteeringManager.SteeringManual(deltaTime, escapeVel);
|
||||
}
|
||||
else
|
||||
{
|
||||
character.AnimController.TargetDir = escapeVel.X < 0.0f ? Direction.Right : Direction.Left;
|
||||
character.AIController.SteeringManager.Reset();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
character.AIController.SteeringManager.Reset();
|
||||
}
|
||||
if (goToObjective != null) { return; }
|
||||
if (currentHull == null) { return; }
|
||||
//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)
|
||||
{
|
||||
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(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
|
||||
}
|
||||
foreach (Character enemy in Character.CharacterList)
|
||||
{
|
||||
//don't run from friendly NPCs
|
||||
if (enemy.TeamID == Character.TeamType.FriendlyNPC) { continue; }
|
||||
//friendly NPCs don't run away from anything but characters controlled by EnemyAIController (= monsters)
|
||||
if (character.TeamID == Character.TeamType.FriendlyNPC && !(enemy.AIController is EnemyAIController)) { continue; }
|
||||
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(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
|
||||
}
|
||||
}
|
||||
if (escapeVel != Vector2.Zero)
|
||||
{
|
||||
//only move if we haven't reached the edge of the room
|
||||
if ((escapeVel.X < 0 && character.Position.X > currentHull.Rect.X + 50) ||
|
||||
(escapeVel.X > 0 && character.Position.X < currentHull.Rect.Right - 50))
|
||||
{
|
||||
character.AIController.SteeringManager.SteeringManual(deltaTime, escapeVel);
|
||||
}
|
||||
else
|
||||
{
|
||||
character.AnimController.TargetDir = escapeVel.X < 0.0f ? Direction.Right : Direction.Left;
|
||||
character.AIController.SteeringManager.Reset();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
character.AIController.SteeringManager.Reset();
|
||||
}
|
||||
if (goToObjective != null) { return; }
|
||||
if (currentHull == null) { return; }
|
||||
//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)
|
||||
{
|
||||
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(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
|
||||
}
|
||||
foreach (Character enemy in Character.CharacterList)
|
||||
{
|
||||
//don't run from friendly NPCs
|
||||
if (enemy.TeamID == Character.TeamType.FriendlyNPC) { continue; }
|
||||
//friendly NPCs don't run away from anything but characters controlled by EnemyAIController (= monsters)
|
||||
if (character.TeamID == Character.TeamType.FriendlyNPC && !(enemy.AIController is EnemyAIController)) { continue; }
|
||||
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(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
|
||||
}
|
||||
}
|
||||
if (escapeVel != Vector2.Zero)
|
||||
{
|
||||
//only move if we haven't reached the edge of the room
|
||||
if ((escapeVel.X < 0 && character.Position.X > currentHull.Rect.X + 50) ||
|
||||
(escapeVel.X > 0 && character.Position.X < currentHull.Rect.Right - 50))
|
||||
{
|
||||
character.AIController.SteeringManager.SteeringManual(deltaTime, escapeVel);
|
||||
}
|
||||
else
|
||||
{
|
||||
character.AnimController.TargetDir = escapeVel.X < 0.0f ? Direction.Right : Direction.Left;
|
||||
character.AIController.SteeringManager.Reset();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
character.AIController.SteeringManager.Reset();
|
||||
}
|
||||
if (goToObjective != null) { return; }
|
||||
if (currentHull == null) { return; }
|
||||
//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)
|
||||
{
|
||||
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(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
|
||||
}
|
||||
foreach (Character enemy in Character.CharacterList)
|
||||
{
|
||||
//don't run from friendly NPCs
|
||||
if (enemy.TeamID == Character.TeamType.FriendlyNPC) { continue; }
|
||||
//friendly NPCs don't run away from anything but characters controlled by EnemyAIController (= monsters)
|
||||
if (character.TeamID == Character.TeamType.FriendlyNPC && !(enemy.AIController is EnemyAIController)) { continue; }
|
||||
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(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
|
||||
}
|
||||
}
|
||||
if (escapeVel != Vector2.Zero)
|
||||
{
|
||||
//only move if we haven't reached the edge of the room
|
||||
if ((escapeVel.X < 0 && character.Position.X > currentHull.Rect.X + 50) ||
|
||||
(escapeVel.X > 0 && character.Position.X < currentHull.Rect.Right - 50))
|
||||
{
|
||||
character.AIController.SteeringManager.SteeringManual(deltaTime, escapeVel);
|
||||
}
|
||||
else
|
||||
{
|
||||
character.AnimController.TargetDir = escapeVel.X < 0.0f ? Direction.Right : Direction.Left;
|
||||
character.AIController.SteeringManager.Reset();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
character.AIController.SteeringManager.Reset();
|
||||
}
|
||||
if (goToObjective != null) { return; }
|
||||
if (currentHull == null) { return; }
|
||||
//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)
|
||||
{
|
||||
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(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
|
||||
}
|
||||
foreach (Character enemy in Character.CharacterList)
|
||||
{
|
||||
//don't run from friendly NPCs
|
||||
if (enemy.TeamID == Character.TeamType.FriendlyNPC) { continue; }
|
||||
//friendly NPCs don't run away from anything but characters controlled by EnemyAIController (= monsters)
|
||||
if (character.TeamID == Character.TeamType.FriendlyNPC && !(enemy.AIController is EnemyAIController)) { continue; }
|
||||
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(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
|
||||
}
|
||||
}
|
||||
if (escapeVel != Vector2.Zero)
|
||||
{
|
||||
//only move if we haven't reached the edge of the room
|
||||
if ((escapeVel.X < 0 && character.Position.X > currentHull.Rect.X + 50) ||
|
||||
(escapeVel.X > 0 && character.Position.X < currentHull.Rect.Right - 50))
|
||||
{
|
||||
character.AIController.SteeringManager.SteeringManual(deltaTime, escapeVel);
|
||||
}
|
||||
else
|
||||
{
|
||||
character.AnimController.TargetDir = escapeVel.X < 0.0f ? Direction.Right : Direction.Left;
|
||||
character.AIController.SteeringManager.Reset();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
character.AIController.SteeringManager.Reset();
|
||||
}
|
||||
if (goToObjective != null) { return; }
|
||||
if (currentHull == null) { return; }
|
||||
//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)
|
||||
{
|
||||
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(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
|
||||
}
|
||||
foreach (Character enemy in Character.CharacterList)
|
||||
{
|
||||
//don't run from friendly NPCs
|
||||
if (enemy.TeamID == Character.TeamType.FriendlyNPC) { continue; }
|
||||
//friendly NPCs don't run away from anything but characters controlled by EnemyAIController (= monsters)
|
||||
if (character.TeamID == Character.TeamType.FriendlyNPC && !(enemy.AIController is EnemyAIController)) { continue; }
|
||||
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(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
|
||||
}
|
||||
}
|
||||
if (escapeVel != Vector2.Zero)
|
||||
{
|
||||
//only move if we haven't reached the edge of the room
|
||||
if ((escapeVel.X < 0 && character.Position.X > currentHull.Rect.X + 50) ||
|
||||
(escapeVel.X > 0 && character.Position.X < currentHull.Rect.Right - 50))
|
||||
{
|
||||
character.AIController.SteeringManager.SteeringManual(deltaTime, escapeVel);
|
||||
}
|
||||
else
|
||||
{
|
||||
character.AnimController.TargetDir = escapeVel.X < 0.0f ? Direction.Right : Direction.Left;
|
||||
character.AIController.SteeringManager.Reset();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
character.AIController.SteeringManager.Reset();
|
||||
}
|
||||
if (goToObjective != null) { return; }
|
||||
if (currentHull == null) { return; }
|
||||
//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)
|
||||
{
|
||||
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(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
|
||||
}
|
||||
foreach (Character enemy in Character.CharacterList)
|
||||
{
|
||||
//don't run from friendly NPCs
|
||||
if (enemy.TeamID == Character.TeamType.FriendlyNPC) { continue; }
|
||||
//friendly NPCs don't run away from anything but characters controlled by EnemyAIController (= monsters)
|
||||
if (character.TeamID == Character.TeamType.FriendlyNPC && !(enemy.AIController is EnemyAIController)) { continue; }
|
||||
if (enemy.CurrentHull == currentHull && !enemy.IsDead && !enemy.IsUnconscious &&
|
||||
|
||||
if (enemy.CurrentHull == currentHull && !enemy.IsDead && !enemy.IsUnconscious &&
|
||||
(enemy.AIController is EnemyAIController || enemy.TeamID != character.TeamID))
|
||||
{
|
||||
Vector2 dir = character.Position - enemy.Position;
|
||||
@@ -798,6 +154,7 @@ namespace Barotrauma
|
||||
escapeVel += new Vector2(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
|
||||
}
|
||||
}
|
||||
|
||||
if (escapeVel != Vector2.Zero)
|
||||
{
|
||||
//only move if we haven't reached the edge of the room
|
||||
@@ -894,5 +251,36 @@ namespace Barotrauma
|
||||
}
|
||||
return bestHull;
|
||||
}
|
||||
|
||||
public override bool IsDuplicate(AIObjective otherObjective)
|
||||
{
|
||||
return (otherObjective is AIObjectiveFindSafety);
|
||||
}
|
||||
|
||||
public override void Update(AIObjectiveManager objectiveManager, float deltaTime)
|
||||
{
|
||||
if (character.CurrentHull == null)
|
||||
{
|
||||
currenthullSafety = 0;
|
||||
Priority = 5;
|
||||
return;
|
||||
}
|
||||
if (character.OxygenAvailable < CharacterHealth.LowOxygenThreshold) { Priority = 100; }
|
||||
currenthullSafety = HumanAIController.GetHullSafety(character.CurrentHull);
|
||||
if (currenthullSafety > HumanAIController.HULL_SAFETY_THRESHOLD)
|
||||
{
|
||||
Priority -= priorityDecrease * deltaTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
float dangerFactor = (100 - currenthullSafety) / 100;
|
||||
Priority += dangerFactor * priorityIncrease * deltaTime;
|
||||
}
|
||||
Priority = MathHelper.Clamp(Priority, 0, 100);
|
||||
if (divingGearObjective != null && !divingGearObjective.IsCompleted() && divingGearObjective.CanBeCompleted)
|
||||
{
|
||||
Priority = Math.Max(Priority, AIObjectiveManager.OrderPriority + 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,24 +3,28 @@ using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class AIObjectiveFixLeak : AIObjective
|
||||
{
|
||||
public override string DebugTag => "fix leak";
|
||||
|
||||
public override bool KeepDivingGearOn => true;
|
||||
public override bool ForceRun => true;
|
||||
|
||||
public Gap Leak { get; private set; }
|
||||
private readonly Gap leak;
|
||||
|
||||
private AIObjectiveFindDivingGear findDivingGear;
|
||||
private AIObjectiveGetItem getWeldingTool;
|
||||
private AIObjectiveContainItem refuelObjective;
|
||||
private AIObjectiveGoTo gotoObjective;
|
||||
private AIObjectiveOperateItem operateObjective;
|
||||
|
||||
public Gap Leak
|
||||
{
|
||||
get { return leak; }
|
||||
}
|
||||
|
||||
public AIObjectiveFixLeak(Gap leak, Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base (character, objectiveManager, priorityModifier)
|
||||
public AIObjectiveFixLeak(Gap leak, Character character) : base (character, "")
|
||||
{
|
||||
Leak = leak;
|
||||
}
|
||||
@@ -30,16 +34,17 @@ namespace Barotrauma
|
||||
return Leak.Open <= 0.0f || Leak.Removed;
|
||||
}
|
||||
|
||||
public override float GetPriority()
|
||||
public override bool CanBeCompleted => !abandon && base.CanBeCompleted;
|
||||
|
||||
public override float GetPriority(AIObjectiveManager objectiveManager)
|
||||
{
|
||||
if (Leak.Open == 0.0f) { return 0.0f; }
|
||||
// Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally)
|
||||
float dist = Math.Abs(character.WorldPosition.X - Leak.WorldPosition.X) + Math.Abs(character.WorldPosition.Y - Leak.WorldPosition.Y) * 2.0f;
|
||||
float distanceFactor = MathHelper.Lerp(1, 0.25f, MathUtils.InverseLerp(0, 10000, dist));
|
||||
float severity = AIObjectiveFixLeaks.GetLeakSeverity(Leak);
|
||||
float max = Math.Min((AIObjectiveManager.OrderPriority - 1), 90);
|
||||
float devotion = Math.Min(Priority, 10) / 100;
|
||||
return MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + severity * distanceFactor * PriorityModifier, 0, 1));
|
||||
if (leak.Open == 0.0f) { return 0.0f; }
|
||||
|
||||
float leakSize = (leak.IsHorizontal ? leak.Rect.Height : leak.Rect.Width) * Math.Max(leak.Open, 0.1f);
|
||||
|
||||
float dist = Vector2.DistanceSquared(character.SimPosition, leak.SimPosition);
|
||||
dist = Math.Max(dist / 100.0f, 1.0f);
|
||||
return Math.Min(leakSize / dist, 40.0f);
|
||||
}
|
||||
|
||||
public override bool IsDuplicate(AIObjective otherObjective)
|
||||
@@ -50,447 +55,105 @@ namespace Barotrauma
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
if (!Leak.IsRoomToRoom)
|
||||
if (!leak.IsRoomToRoom)
|
||||
{
|
||||
if (!HumanAIController.HasDivingSuit(character))
|
||||
if (findDivingGear == null)
|
||||
{
|
||||
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, true, objectiveManager));
|
||||
findDivingGear = new AIObjectiveFindDivingGear(character, true);
|
||||
AddSubObjective(findDivingGear);
|
||||
}
|
||||
else if (!findDivingGear.CanBeCompleted)
|
||||
{
|
||||
abandon = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var weldingTool = character.Inventory.FindItemByTag("weldingtool");
|
||||
|
||||
if (weldingTool == null)
|
||||
{
|
||||
TryAddSubObjective(ref getWeldingTool, () => new AIObjectiveGetItem(character, "weldingtool", objectiveManager, true));
|
||||
AddSubObjective(new AIObjectiveGetItem(character, "weldingtool", true));
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
var containedItems = weldingTool.ContainedItems;
|
||||
if (containedItems == null)
|
||||
if (containedItems == null) return;
|
||||
|
||||
var fuelTank = containedItems.FirstOrDefault(i => i.HasTag("weldingfueltank") && i.Condition > 0.0f);
|
||||
if (fuelTank == null)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no proper inventory");
|
||||
#endif
|
||||
abandon = true;
|
||||
AddSubObjective(new AIObjectiveContainItem(character, "weldingfueltank", weldingTool.GetComponent<ItemContainer>()));
|
||||
return;
|
||||
}
|
||||
// Drop empty tanks
|
||||
foreach (Item containedItem in containedItems)
|
||||
}
|
||||
|
||||
var repairTool = weldingTool.GetComponent<RepairTool>();
|
||||
if (repairTool == null) { return; }
|
||||
|
||||
Vector2 gapDiff = leak.WorldPosition - character.WorldPosition;
|
||||
|
||||
// TODO: use the collider size/reach?
|
||||
if (!character.AnimController.InWater && Math.Abs(gapDiff.X) < 100 && gapDiff.Y < 0.0f && gapDiff.Y > -150)
|
||||
{
|
||||
HumanAIController.AnimController.Crouching = true;
|
||||
}
|
||||
|
||||
float reach = ConvertUnits.ToSimUnits(repairTool.Range);
|
||||
bool canReach = ConvertUnits.ToSimUnits(gapDiff.Length()) < reach;
|
||||
if (canReach)
|
||||
{
|
||||
Limb sightLimb = null;
|
||||
if (character.Inventory.IsInLimbSlot(repairTool.Item, InvSlotType.RightHand))
|
||||
{
|
||||
if (containedItem == null) { continue; }
|
||||
if (containedItem.Condition <= 0.0f)
|
||||
sightLimb = character.AnimController.GetLimb(LimbType.RightHand);
|
||||
}
|
||||
else if (character.Inventory.IsInLimbSlot(repairTool.Item, InvSlotType.LeftHand))
|
||||
{
|
||||
sightLimb = character.AnimController.GetLimb(LimbType.LeftHand);
|
||||
}
|
||||
canReach = character.CanSeeTarget(leak, sightLimb);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (gotoObjective != null)
|
||||
{
|
||||
// Check if the objective is already removed -> completed/impossible
|
||||
if (!subObjectives.Contains(gotoObjective))
|
||||
{
|
||||
containedItem.Drop(character);
|
||||
if (!gotoObjective.CanBeCompleted)
|
||||
{
|
||||
abandon = true;
|
||||
}
|
||||
gotoObjective = null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (containedItems.None(i => i.HasTag("weldingfueltank") && i.Condition > 0.0f))
|
||||
else
|
||||
{
|
||||
TryAddSubObjective(ref refuelObjective, () => new AIObjectiveContainItem(character, "weldingfueltank", weldingTool.GetComponent<ItemContainer>(), objectiveManager));
|
||||
return;
|
||||
gotoObjective = new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character)
|
||||
{
|
||||
CloseEnough = reach
|
||||
};
|
||||
if (!subObjectives.Contains(gotoObjective))
|
||||
{
|
||||
AddSubObjective(gotoObjective);
|
||||
}
|
||||
}
|
||||
canReach = character.CanSeeTarget(Leak, sightLimb);
|
||||
}
|
||||
var repairTool = weldingTool.GetComponent<RepairTool>();
|
||||
if (repairTool == null)
|
||||
if (gotoObjective == null || gotoObjective.IsCompleted())
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no RepairTool component but is tagged as a welding tool");
|
||||
#endif
|
||||
abandon = true;
|
||||
return;
|
||||
}
|
||||
Vector2 gapDiff = Leak.WorldPosition - character.WorldPosition;
|
||||
// TODO: use the collider size/reach?
|
||||
if (!character.AnimController.InWater && Math.Abs(gapDiff.X) < 100 && gapDiff.Y < 0.0f && gapDiff.Y > -150)
|
||||
{
|
||||
HumanAIController.AnimController.Crouching = true;
|
||||
}
|
||||
float reach = ConvertUnits.ToSimUnits(repairTool.Range);
|
||||
bool canOperate = ConvertUnits.ToSimUnits(gapDiff.Length()) < reach;
|
||||
if (canOperate)
|
||||
{
|
||||
TryAddSubObjective(ref operateObjective, () => new AIObjectiveOperateItem(repairTool, character, objectiveManager, option: "", requireEquip: true, operateTarget: Leak));
|
||||
}
|
||||
else
|
||||
{
|
||||
TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach * 0.75f });
|
||||
}
|
||||
var repairTool = weldingTool.GetComponent<RepairTool>();
|
||||
if (repairTool == null)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no RepairTool component but is tagged as a welding tool");
|
||||
#endif
|
||||
abandon = true;
|
||||
return;
|
||||
}
|
||||
Vector2 gapDiff = Leak.WorldPosition - character.WorldPosition;
|
||||
// TODO: use the collider size/reach?
|
||||
if (!character.AnimController.InWater && Math.Abs(gapDiff.X) < 100 && gapDiff.Y < 0.0f && gapDiff.Y > -150)
|
||||
{
|
||||
HumanAIController.AnimController.Crouching = true;
|
||||
}
|
||||
float reach = ConvertUnits.ToSimUnits(repairTool.Range);
|
||||
bool canOperate = ConvertUnits.ToSimUnits(gapDiff.Length()) < reach;
|
||||
if (canOperate)
|
||||
{
|
||||
TryAddSubObjective(ref operateObjective, () => new AIObjectiveOperateItem(repairTool, character, objectiveManager, option: "", requireEquip: true, operateTarget: Leak));
|
||||
}
|
||||
else
|
||||
{
|
||||
TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach * 0.75f });
|
||||
}
|
||||
var repairTool = weldingTool.GetComponent<RepairTool>();
|
||||
if (repairTool == null)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no RepairTool component but is tagged as a welding tool");
|
||||
#endif
|
||||
abandon = true;
|
||||
return;
|
||||
}
|
||||
Vector2 gapDiff = Leak.WorldPosition - character.WorldPosition;
|
||||
// TODO: use the collider size/reach?
|
||||
if (!character.AnimController.InWater && Math.Abs(gapDiff.X) < 100 && gapDiff.Y < 0.0f && gapDiff.Y > -150)
|
||||
{
|
||||
HumanAIController.AnimController.Crouching = true;
|
||||
}
|
||||
float reach = ConvertUnits.ToSimUnits(repairTool.Range);
|
||||
bool canOperate = ConvertUnits.ToSimUnits(gapDiff.Length()) < reach;
|
||||
if (canOperate)
|
||||
{
|
||||
TryAddSubObjective(ref operateObjective, () => new AIObjectiveOperateItem(repairTool, character, objectiveManager, option: "", requireEquip: true, operateTarget: Leak));
|
||||
}
|
||||
else
|
||||
{
|
||||
TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach * 0.75f });
|
||||
}
|
||||
var repairTool = weldingTool.GetComponent<RepairTool>();
|
||||
if (repairTool == null)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no RepairTool component but is tagged as a welding tool");
|
||||
#endif
|
||||
abandon = true;
|
||||
return;
|
||||
}
|
||||
Vector2 gapDiff = Leak.WorldPosition - character.WorldPosition;
|
||||
// TODO: use the collider size/reach?
|
||||
if (!character.AnimController.InWater && Math.Abs(gapDiff.X) < 100 && gapDiff.Y < 0.0f && gapDiff.Y > -150)
|
||||
{
|
||||
HumanAIController.AnimController.Crouching = true;
|
||||
}
|
||||
float reach = ConvertUnits.ToSimUnits(repairTool.Range);
|
||||
bool canOperate = ConvertUnits.ToSimUnits(gapDiff.Length()) < reach;
|
||||
if (canOperate)
|
||||
{
|
||||
TryAddSubObjective(ref operateObjective, () => new AIObjectiveOperateItem(repairTool, character, objectiveManager, option: "", requireEquip: true, operateTarget: Leak));
|
||||
}
|
||||
else
|
||||
{
|
||||
TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach * 0.75f });
|
||||
}
|
||||
var repairTool = weldingTool.GetComponent<RepairTool>();
|
||||
if (repairTool == null)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no RepairTool component but is tagged as a welding tool");
|
||||
#endif
|
||||
abandon = true;
|
||||
return;
|
||||
}
|
||||
Vector2 gapDiff = Leak.WorldPosition - character.WorldPosition;
|
||||
// TODO: use the collider size/reach?
|
||||
if (!character.AnimController.InWater && Math.Abs(gapDiff.X) < 100 && gapDiff.Y < 0.0f && gapDiff.Y > -150)
|
||||
{
|
||||
HumanAIController.AnimController.Crouching = true;
|
||||
}
|
||||
float reach = ConvertUnits.ToSimUnits(repairTool.Range);
|
||||
bool canOperate = ConvertUnits.ToSimUnits(gapDiff.Length()) < reach;
|
||||
if (canOperate)
|
||||
{
|
||||
TryAddSubObjective(ref operateObjective, () => new AIObjectiveOperateItem(repairTool, character, objectiveManager, option: "", requireEquip: true, operateTarget: Leak));
|
||||
}
|
||||
else
|
||||
{
|
||||
TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach * 0.75f });
|
||||
}
|
||||
var repairTool = weldingTool.GetComponent<RepairTool>();
|
||||
if (repairTool == null)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no RepairTool component but is tagged as a welding tool");
|
||||
#endif
|
||||
abandon = true;
|
||||
return;
|
||||
}
|
||||
Vector2 gapDiff = Leak.WorldPosition - character.WorldPosition;
|
||||
// TODO: use the collider size/reach?
|
||||
if (!character.AnimController.InWater && Math.Abs(gapDiff.X) < 100 && gapDiff.Y < 0.0f && gapDiff.Y > -150)
|
||||
{
|
||||
HumanAIController.AnimController.Crouching = true;
|
||||
}
|
||||
float reach = ConvertUnits.ToSimUnits(repairTool.Range);
|
||||
bool canOperate = ConvertUnits.ToSimUnits(gapDiff.Length()) < reach;
|
||||
if (canOperate)
|
||||
{
|
||||
TryAddSubObjective(ref operateObjective, () => new AIObjectiveOperateItem(repairTool, character, objectiveManager, option: "", requireEquip: true, operateTarget: Leak));
|
||||
}
|
||||
else
|
||||
{
|
||||
TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach * 0.75f });
|
||||
}
|
||||
var repairTool = weldingTool.GetComponent<RepairTool>();
|
||||
if (repairTool == null)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no RepairTool component but is tagged as a welding tool");
|
||||
#endif
|
||||
abandon = true;
|
||||
return;
|
||||
}
|
||||
Vector2 gapDiff = Leak.WorldPosition - character.WorldPosition;
|
||||
// TODO: use the collider size/reach?
|
||||
if (!character.AnimController.InWater && Math.Abs(gapDiff.X) < 100 && gapDiff.Y < 0.0f && gapDiff.Y > -150)
|
||||
{
|
||||
HumanAIController.AnimController.Crouching = true;
|
||||
}
|
||||
float reach = ConvertUnits.ToSimUnits(repairTool.Range);
|
||||
bool canOperate = ConvertUnits.ToSimUnits(gapDiff.Length()) < reach;
|
||||
if (canOperate)
|
||||
{
|
||||
TryAddSubObjective(ref operateObjective, () => new AIObjectiveOperateItem(repairTool, character, objectiveManager, option: "", requireEquip: true, operateTarget: Leak));
|
||||
}
|
||||
else
|
||||
{
|
||||
TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach * 0.75f });
|
||||
}
|
||||
var repairTool = weldingTool.GetComponent<RepairTool>();
|
||||
if (repairTool == null)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no RepairTool component but is tagged as a welding tool");
|
||||
#endif
|
||||
abandon = true;
|
||||
return;
|
||||
}
|
||||
Vector2 gapDiff = Leak.WorldPosition - character.WorldPosition;
|
||||
// TODO: use the collider size/reach?
|
||||
if (!character.AnimController.InWater && Math.Abs(gapDiff.X) < 100 && gapDiff.Y < 0.0f && gapDiff.Y > -150)
|
||||
{
|
||||
HumanAIController.AnimController.Crouching = true;
|
||||
}
|
||||
float reach = ConvertUnits.ToSimUnits(repairTool.Range);
|
||||
bool canOperate = ConvertUnits.ToSimUnits(gapDiff.Length()) < reach;
|
||||
if (canOperate)
|
||||
{
|
||||
TryAddSubObjective(ref operateObjective, () => new AIObjectiveOperateItem(repairTool, character, objectiveManager, option: "", requireEquip: true, operateTarget: Leak));
|
||||
}
|
||||
else
|
||||
{
|
||||
TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach * 0.75f });
|
||||
}
|
||||
var repairTool = weldingTool.GetComponent<RepairTool>();
|
||||
if (repairTool == null)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no RepairTool component but is tagged as a welding tool");
|
||||
#endif
|
||||
abandon = true;
|
||||
return;
|
||||
}
|
||||
Vector2 gapDiff = Leak.WorldPosition - character.WorldPosition;
|
||||
// TODO: use the collider size/reach?
|
||||
if (!character.AnimController.InWater && Math.Abs(gapDiff.X) < 100 && gapDiff.Y < 0.0f && gapDiff.Y > -150)
|
||||
{
|
||||
HumanAIController.AnimController.Crouching = true;
|
||||
}
|
||||
float reach = ConvertUnits.ToSimUnits(repairTool.Range);
|
||||
bool canOperate = ConvertUnits.ToSimUnits(gapDiff.Length()) < reach;
|
||||
if (canOperate)
|
||||
{
|
||||
TryAddSubObjective(ref operateObjective, () => new AIObjectiveOperateItem(repairTool, character, objectiveManager, option: "", requireEquip: true, operateTarget: Leak));
|
||||
}
|
||||
else
|
||||
{
|
||||
TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach * 0.75f });
|
||||
}
|
||||
var repairTool = weldingTool.GetComponent<RepairTool>();
|
||||
if (repairTool == null)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no RepairTool component but is tagged as a welding tool");
|
||||
#endif
|
||||
abandon = true;
|
||||
return;
|
||||
}
|
||||
Vector2 gapDiff = Leak.WorldPosition - character.WorldPosition;
|
||||
// TODO: use the collider size/reach?
|
||||
if (!character.AnimController.InWater && Math.Abs(gapDiff.X) < 100 && gapDiff.Y < 0.0f && gapDiff.Y > -150)
|
||||
{
|
||||
HumanAIController.AnimController.Crouching = true;
|
||||
}
|
||||
float reach = ConvertUnits.ToSimUnits(repairTool.Range);
|
||||
bool canOperate = ConvertUnits.ToSimUnits(gapDiff.Length()) < reach;
|
||||
if (canOperate)
|
||||
{
|
||||
TryAddSubObjective(ref operateObjective, () => new AIObjectiveOperateItem(repairTool, character, objectiveManager, option: "", requireEquip: true, operateTarget: Leak));
|
||||
}
|
||||
else
|
||||
{
|
||||
TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach * 0.75f });
|
||||
}
|
||||
var repairTool = weldingTool.GetComponent<RepairTool>();
|
||||
if (repairTool == null)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no RepairTool component but is tagged as a welding tool");
|
||||
#endif
|
||||
abandon = true;
|
||||
return;
|
||||
}
|
||||
Vector2 gapDiff = Leak.WorldPosition - character.WorldPosition;
|
||||
// TODO: use the collider size/reach?
|
||||
if (!character.AnimController.InWater && Math.Abs(gapDiff.X) < 100 && gapDiff.Y < 0.0f && gapDiff.Y > -150)
|
||||
{
|
||||
HumanAIController.AnimController.Crouching = true;
|
||||
}
|
||||
float reach = ConvertUnits.ToSimUnits(repairTool.Range);
|
||||
bool canOperate = ConvertUnits.ToSimUnits(gapDiff.Length()) < reach;
|
||||
if (canOperate)
|
||||
{
|
||||
TryAddSubObjective(ref operateObjective, () => new AIObjectiveOperateItem(repairTool, character, objectiveManager, option: "", requireEquip: true, operateTarget: Leak));
|
||||
}
|
||||
else
|
||||
{
|
||||
TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach * 0.75f });
|
||||
}
|
||||
var repairTool = weldingTool.GetComponent<RepairTool>();
|
||||
if (repairTool == null)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no RepairTool component but is tagged as a welding tool");
|
||||
#endif
|
||||
abandon = true;
|
||||
return;
|
||||
}
|
||||
Vector2 gapDiff = Leak.WorldPosition - character.WorldPosition;
|
||||
// TODO: use the collider size/reach?
|
||||
if (!character.AnimController.InWater && Math.Abs(gapDiff.X) < 100 && gapDiff.Y < 0.0f && gapDiff.Y > -150)
|
||||
{
|
||||
HumanAIController.AnimController.Crouching = true;
|
||||
}
|
||||
float reach = ConvertUnits.ToSimUnits(repairTool.Range);
|
||||
bool canOperate = ConvertUnits.ToSimUnits(gapDiff.Length()) < reach;
|
||||
if (canOperate)
|
||||
{
|
||||
TryAddSubObjective(ref operateObjective, () => new AIObjectiveOperateItem(repairTool, character, objectiveManager, option: "", requireEquip: true, operateTarget: Leak));
|
||||
}
|
||||
else
|
||||
{
|
||||
TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach * 0.75f });
|
||||
}
|
||||
var repairTool = weldingTool.GetComponent<RepairTool>();
|
||||
if (repairTool == null)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no RepairTool component but is tagged as a welding tool");
|
||||
#endif
|
||||
abandon = true;
|
||||
return;
|
||||
}
|
||||
Vector2 gapDiff = Leak.WorldPosition - character.WorldPosition;
|
||||
// TODO: use the collider size/reach?
|
||||
if (!character.AnimController.InWater && Math.Abs(gapDiff.X) < 100 && gapDiff.Y < 0.0f && gapDiff.Y > -150)
|
||||
{
|
||||
HumanAIController.AnimController.Crouching = true;
|
||||
}
|
||||
float reach = ConvertUnits.ToSimUnits(repairTool.Range);
|
||||
bool canOperate = ConvertUnits.ToSimUnits(gapDiff.Length()) < reach;
|
||||
if (canOperate)
|
||||
{
|
||||
TryAddSubObjective(ref operateObjective, () => new AIObjectiveOperateItem(repairTool, character, objectiveManager, option: "", requireEquip: true, operateTarget: Leak));
|
||||
}
|
||||
else
|
||||
{
|
||||
TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach * 0.75f });
|
||||
}
|
||||
var repairTool = weldingTool.GetComponent<RepairTool>();
|
||||
if (repairTool == null)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no RepairTool component but is tagged as a welding tool");
|
||||
#endif
|
||||
abandon = true;
|
||||
return;
|
||||
}
|
||||
Vector2 gapDiff = Leak.WorldPosition - character.WorldPosition;
|
||||
// TODO: use the collider size/reach?
|
||||
if (!character.AnimController.InWater && Math.Abs(gapDiff.X) < 100 && gapDiff.Y < 0.0f && gapDiff.Y > -150)
|
||||
{
|
||||
HumanAIController.AnimController.Crouching = true;
|
||||
}
|
||||
float reach = ConvertUnits.ToSimUnits(repairTool.Range);
|
||||
bool canOperate = ConvertUnits.ToSimUnits(gapDiff.Length()) < reach;
|
||||
if (canOperate)
|
||||
{
|
||||
TryAddSubObjective(ref operateObjective, () => new AIObjectiveOperateItem(repairTool, character, objectiveManager, option: "", requireEquip: true, operateTarget: Leak));
|
||||
}
|
||||
else
|
||||
{
|
||||
TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach * 0.75f });
|
||||
}
|
||||
var repairTool = weldingTool.GetComponent<RepairTool>();
|
||||
if (repairTool == null)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no RepairTool component but is tagged as a welding tool");
|
||||
#endif
|
||||
abandon = true;
|
||||
return;
|
||||
}
|
||||
Vector2 gapDiff = Leak.WorldPosition - character.WorldPosition;
|
||||
// TODO: use the collider size/reach?
|
||||
if (!character.AnimController.InWater && Math.Abs(gapDiff.X) < 100 && gapDiff.Y < 0.0f && gapDiff.Y > -150)
|
||||
{
|
||||
HumanAIController.AnimController.Crouching = true;
|
||||
}
|
||||
float reach = ConvertUnits.ToSimUnits(repairTool.Range);
|
||||
bool canOperate = ConvertUnits.ToSimUnits(gapDiff.Length()) < reach;
|
||||
if (canOperate)
|
||||
{
|
||||
TryAddSubObjective(ref operateObjective, () => new AIObjectiveOperateItem(repairTool, character, objectiveManager, option: "", requireEquip: true, operateTarget: Leak));
|
||||
}
|
||||
else
|
||||
{
|
||||
TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach * 0.75f });
|
||||
}
|
||||
var repairTool = weldingTool.GetComponent<RepairTool>();
|
||||
if (repairTool == null)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no RepairTool component but is tagged as a welding tool");
|
||||
#endif
|
||||
abandon = true;
|
||||
return;
|
||||
}
|
||||
Vector2 gapDiff = Leak.WorldPosition - character.WorldPosition;
|
||||
// TODO: use the collider size/reach?
|
||||
if (!character.AnimController.InWater && Math.Abs(gapDiff.X) < 100 && gapDiff.Y < 0.0f && gapDiff.Y > -150)
|
||||
{
|
||||
HumanAIController.AnimController.Crouching = true;
|
||||
}
|
||||
float reach = ConvertUnits.ToSimUnits(repairTool.Range);
|
||||
bool canOperate = ConvertUnits.ToSimUnits(gapDiff.Length()) < reach;
|
||||
if (canOperate)
|
||||
{
|
||||
TryAddSubObjective(ref operateObjective, () => new AIObjectiveOperateItem(repairTool, character, objectiveManager, option: "", requireEquip: true, operateTarget: Leak));
|
||||
}
|
||||
else
|
||||
{
|
||||
TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach * 0.75f });
|
||||
}
|
||||
if (operateObjective == null)
|
||||
{
|
||||
operateObjective = new AIObjectiveOperateItem(repairTool, character, "", true, leak);
|
||||
AddSubObjective(operateObjective);
|
||||
}
|
||||
else if (!subObjectives.Contains(operateObjective))
|
||||
{
|
||||
operateObjective = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2 GetStandPosition()
|
||||
|
||||
@@ -9,41 +9,60 @@ namespace Barotrauma
|
||||
class AIObjectiveFixLeaks : AIObjectiveLoop<Gap>
|
||||
{
|
||||
public override string DebugTag => "fix leaks";
|
||||
public override bool KeepDivingGearOn => true;
|
||||
public override bool ForceRun => true;
|
||||
|
||||
public AIObjectiveFixLeaks(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { }
|
||||
public AIObjectiveFixLeaks(Character character) : base (character, "") { }
|
||||
|
||||
protected override bool Filter(Gap gap)
|
||||
{
|
||||
if (character.Submarine == null) { return 0; }
|
||||
if (targets.None()) { return 0; }
|
||||
if (objectiveManager.CurrentOrder == this)
|
||||
{
|
||||
return AIObjectiveManager.OrderPriority;
|
||||
}
|
||||
return MathHelper.Lerp(0, AIObjectiveManager.OrderPriority, targets.Average(t => Average(t)));
|
||||
}
|
||||
|
||||
protected override void FindTargets()
|
||||
{
|
||||
base.FindTargets();
|
||||
if (targets.None() && objectiveManager.CurrentOrder == this)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogNoLeaks"), null, 3.0f, "noleaks", 30.0f);
|
||||
}
|
||||
targets.Sort((x, y) => GetGapFixPriority(y).CompareTo(GetGapFixPriority(x)));
|
||||
}
|
||||
|
||||
protected override bool Filter(Gap gap)
|
||||
{
|
||||
if (gap == null) { return false; }
|
||||
if (gap.ConnectedWall == null || gap.ConnectedDoor != null || gap.Open <= 0 || gap.linkedTo.All(l => l == null)) { return false; }
|
||||
if (gap.Submarine == null) { return false; }
|
||||
if (gap.Submarine.TeamID != character.TeamID) { return false; }
|
||||
if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(gap, true)) { return false; }
|
||||
return true;
|
||||
bool ignore = ignoreList.Contains(gap) || gap.ConnectedWall == null || gap.ConnectedDoor != null || gap.Open <= 0 || gap.linkedTo.All(l => l == null);
|
||||
if (!ignore)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
public static float GetLeakSeverity(Gap leak)
|
||||
private float GetGapFixPriority(Gap gap)
|
||||
{
|
||||
if (leak == null) { return 0; }
|
||||
float sizeFactor = MathHelper.Lerp(1, 10, MathUtils.InverseLerp(0, 200, (leak.IsHorizontal ? leak.Rect.Width : leak.Rect.Height)));
|
||||
float severity = sizeFactor * leak.Open;
|
||||
if (!leak.IsRoomToRoom) { severity *= 50; }
|
||||
return MathHelper.Min(severity, 100);
|
||||
if (gap == null) return 0.0f;
|
||||
|
||||
//larger gap -> higher priority
|
||||
float gapPriority = (gap.IsHorizontal ? gap.Rect.Width : gap.Rect.Height) * gap.Open;
|
||||
|
||||
//prioritize gaps that are close
|
||||
gapPriority /= Math.Max(Vector2.Distance(character.WorldPosition, gap.WorldPosition), 1.0f);
|
||||
|
||||
//gaps to outside are much higher priority
|
||||
if (!gap.IsRoomToRoom) gapPriority *= 10.0f;
|
||||
|
||||
return gapPriority;
|
||||
|
||||
}
|
||||
|
||||
public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveFixLeaks;
|
||||
protected override float TargetEvaluation() => targets.Max(t => GetLeakSeverity(t));
|
||||
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, objectiveManager, PriorityModifier);
|
||||
protected override AIObjective ObjectiveConstructor(Gap gap) => new AIObjectiveFixLeak(gap, character);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,9 +11,6 @@ namespace Barotrauma
|
||||
{
|
||||
public override string DebugTag => "get item";
|
||||
|
||||
private readonly bool equip;
|
||||
private readonly HashSet<Item> ignoredItems = new HashSet<Item>();
|
||||
|
||||
public Func<Item, float> GetItemPriority;
|
||||
|
||||
//can be either tags or identifiers
|
||||
@@ -23,8 +20,14 @@ namespace Barotrauma
|
||||
public string[] ignoredContainerIdentifiers;
|
||||
private AIObjectiveGoTo goToObjective;
|
||||
private float currItemPriority;
|
||||
private bool equip;
|
||||
|
||||
public override float GetPriority()
|
||||
private HashSet<Item> ignoredItems = new HashSet<Item>();
|
||||
|
||||
private bool canBeCompleted = true;
|
||||
public override bool CanBeCompleted => canBeCompleted;
|
||||
|
||||
public override float GetPriority(AIObjectiveManager objectiveManager)
|
||||
{
|
||||
if (objectiveManager.CurrentOrder == this)
|
||||
{
|
||||
@@ -33,19 +36,16 @@ namespace Barotrauma
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
public AIObjectiveGetItem(Character character, Item targetItem, AIObjectiveManager objectiveManager, bool equip = false, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
public AIObjectiveGetItem(Character character, Item targetItem, bool equip = false) : base(character, "")
|
||||
{
|
||||
currSearchIndex = -1;
|
||||
this.equip = equip;
|
||||
this.targetItem = targetItem;
|
||||
}
|
||||
|
||||
public AIObjectiveGetItem(Character character, string itemIdentifier, AIObjectiveManager objectiveManager, bool equip = false, float priorityModifier = 1)
|
||||
: this(character, new string[] { itemIdentifier }, objectiveManager, equip, priorityModifier) { }
|
||||
public AIObjectiveGetItem(Character character, string itemIdentifier, bool equip = false) : this(character, new string[] { itemIdentifier }, equip) { }
|
||||
|
||||
public AIObjectiveGetItem(Character character, string[] itemIdentifiers, AIObjectiveManager objectiveManager, bool equip = false, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
public AIObjectiveGetItem(Character character, string[] itemIdentifiers, bool equip = false) : base(character, "")
|
||||
{
|
||||
currSearchIndex = -1;
|
||||
this.equip = equip;
|
||||
@@ -54,15 +54,20 @@ namespace Barotrauma
|
||||
{
|
||||
itemIdentifiers[i] = itemIdentifiers[i].ToLowerInvariant();
|
||||
}
|
||||
|
||||
CheckInventory();
|
||||
}
|
||||
|
||||
private void CheckInventory()
|
||||
{
|
||||
if (itemIdentifiers == null) { return; }
|
||||
if (itemIdentifiers == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < character.Inventory.Items.Length; i++)
|
||||
{
|
||||
if (character.Inventory.Items[i] == null || character.Inventory.Items[i].Condition <= 0.0f) { continue; }
|
||||
if (character.Inventory.Items[i] == null || character.Inventory.Items[i].Condition <= 0.0f) continue;
|
||||
if (itemIdentifiers.Any(id => character.Inventory.Items[i].Prefab.Identifier == id || character.Inventory.Items[i].HasTag(id)))
|
||||
{
|
||||
targetItem = character.Inventory.Items[i];
|
||||
@@ -76,7 +81,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (Item containedItem in containedItems)
|
||||
{
|
||||
if (containedItem == null || containedItem.Condition <= 0.0f) { continue; }
|
||||
if (containedItem == null || containedItem.Condition <= 0.0f) continue;
|
||||
if (itemIdentifiers.Any(id => containedItem.Prefab.Identifier == id || containedItem.HasTag(id)))
|
||||
{
|
||||
targetItem = containedItem;
|
||||
@@ -94,11 +99,13 @@ namespace Barotrauma
|
||||
FindTargetItem();
|
||||
if (targetItem == null || moveToTarget == null)
|
||||
{
|
||||
objectiveManager.GetObjective<AIObjectiveIdle>()?.Wander(deltaTime);
|
||||
HumanAIController.ObjectiveManager.GetObjective<AIObjectiveIdle>().Wander(deltaTime);
|
||||
//SteeringManager.SteeringWander();
|
||||
return;
|
||||
}
|
||||
|
||||
if (moveToTarget.CurrentHull == character.CurrentHull &&
|
||||
Vector2.DistanceSquared(character.Position, moveToTarget.Position) < MathUtils.Pow(targetItem.InteractDistance, 2))
|
||||
Vector2.DistanceSquared(character.Position, moveToTarget.Position) < MathUtils.Pow(targetItem.InteractDistance * 2, 2))
|
||||
{
|
||||
int targetSlot = -1;
|
||||
if (equip)
|
||||
@@ -116,7 +123,8 @@ namespace Barotrauma
|
||||
for (int i = 0; i < character.Inventory.Items.Length; i++)
|
||||
{
|
||||
//slot not needed by the item, continue
|
||||
if (!slots.HasFlag(character.Inventory.SlotTypes[i])) { continue; }
|
||||
if (!slots.HasFlag(character.Inventory.SlotTypes[i])) continue;
|
||||
|
||||
targetSlot = i;
|
||||
//slot free, continue
|
||||
if (character.Inventory.Items[i] == null) { continue; }
|
||||
@@ -128,6 +136,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
targetItem.TryInteract(character, false, true);
|
||||
|
||||
if (targetSlot > -1 && !character.HasEquippedItem(targetItem))
|
||||
{
|
||||
character.Inventory.TryPutItem(targetItem, targetSlot, false, false, character);
|
||||
@@ -135,20 +144,23 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
TryAddSubObjective(ref goToObjective,
|
||||
constructor: () =>
|
||||
{
|
||||
//check if we're already looking for a diving gear
|
||||
bool gettingDivingGear = (targetItem != null && targetItem.Prefab.Identifier == "divingsuit" || targetItem.HasTag("diving")) ||
|
||||
(itemIdentifiers != null && (itemIdentifiers.Contains("diving") || itemIdentifiers.Contains("divingsuit")));
|
||||
return new AIObjectiveGoTo(moveToTarget, character, objectiveManager, repeat: false, getDivingGearIfNeeded: !gettingDivingGear);
|
||||
},
|
||||
onAbandon: () =>
|
||||
{
|
||||
targetItem = null;
|
||||
moveToTarget = null;
|
||||
ignoredItems.Add(targetItem);
|
||||
});
|
||||
if (goToObjective == null || moveToTarget != goToObjective.Target)
|
||||
{
|
||||
//check if we're already looking for a diving gear
|
||||
bool gettingDivingGear = (targetItem != null && targetItem.Prefab.Identifier == "divingsuit" || targetItem.HasTag("diving")) ||
|
||||
(itemIdentifiers != null && (itemIdentifiers.Contains("diving") || itemIdentifiers.Contains("divingsuit")));
|
||||
|
||||
//don't attempt to get diving gear to reach the destination if the item we're trying to get is diving gear
|
||||
goToObjective = new AIObjectiveGoTo(moveToTarget, character, false, !gettingDivingGear);
|
||||
}
|
||||
|
||||
goToObjective.TryComplete(deltaTime);
|
||||
if (!goToObjective.CanBeCompleted)
|
||||
{
|
||||
targetItem = null;
|
||||
moveToTarget = null;
|
||||
ignoredItems.Add(targetItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,10 +176,11 @@ namespace Barotrauma
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage($"{character.Name}: Cannot find the item, because neither identifiers nor item is was defined.");
|
||||
#endif
|
||||
abandon = true;
|
||||
canBeCompleted = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 10 && currSearchIndex < Item.ItemList.Count - 1; i++)
|
||||
{
|
||||
currSearchIndex++;
|
||||
@@ -176,18 +189,15 @@ namespace Barotrauma
|
||||
if (item.Submarine == null) { continue; }
|
||||
else if (item.Submarine.TeamID != character.TeamID) { continue; }
|
||||
else if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(item, true)) { continue; }
|
||||
|
||||
if (item.CurrentHull == null || item.Condition <= 0.0f) { continue; }
|
||||
if (itemIdentifiers.None(id => item.Prefab.Identifier == id || item.HasTag(id))) { continue; }
|
||||
|
||||
if (ignoredContainerIdentifiers != null && item.Container != null)
|
||||
{
|
||||
if (ignoredContainerIdentifiers.Contains(item.ContainerIdentifier)) { continue; }
|
||||
}
|
||||
|
||||
// TODO: need to exclude items that already are in the inventory?
|
||||
//// Don't allow to get items in rooms that have a fire (except fire extinguishers) or an enemy inside (except weapons)
|
||||
//if (!itemIdentifiers.Contains("extinguisher") && item.CurrentHull.FireSources.Count > 0 ||
|
||||
// item.GetComponent<MeleeWeapon>() == null && item.GetComponent<RangedWeapon>() == null && Character.CharacterList.Any(c => c.CurrentHull == item.CurrentHull && !HumanAIController.IsFriendly(c))) { continue; }
|
||||
|
||||
//if the item is inside a character's inventory, don't steal it unless the character is dead
|
||||
if (item.ParentInventory is CharacterInventory)
|
||||
{
|
||||
@@ -209,9 +219,11 @@ namespace Barotrauma
|
||||
itemPriority -= Vector2.Distance((rootContainer ?? item).Position, character.Position) * 0.01f;
|
||||
//ignore if the item has a lower priority than the currently selected one
|
||||
if (moveToTarget != null && itemPriority < currItemPriority) { continue; }
|
||||
|
||||
currItemPriority = itemPriority;
|
||||
targetItem = item;
|
||||
moveToTarget = rootContainer ?? item;
|
||||
|
||||
}
|
||||
//if searched through all the items and a target wasn't found, can't be completed
|
||||
if (currSearchIndex >= Item.ItemList.Count - 1 && targetItem == null)
|
||||
@@ -219,21 +231,21 @@ namespace Barotrauma
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage($"{character.Name}: Cannot find the item with the following identifier(s): {string.Join(", ", itemIdentifiers)}");
|
||||
#endif
|
||||
abandon = true;
|
||||
canBeCompleted = false;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool IsDuplicate(AIObjective otherObjective)
|
||||
{
|
||||
AIObjectiveGetItem getItem = otherObjective as AIObjectiveGetItem;
|
||||
if (getItem == null) { return false; }
|
||||
if (getItem.equip != equip) { return false; }
|
||||
if (getItem == null) return false;
|
||||
if (getItem.equip != equip) return false;
|
||||
if (getItem.itemIdentifiers != null && itemIdentifiers != null)
|
||||
{
|
||||
if (getItem.itemIdentifiers.Length != itemIdentifiers.Length) { return false; }
|
||||
if (getItem.itemIdentifiers.Length != itemIdentifiers.Length) return false;
|
||||
for (int i = 0; i < getItem.itemIdentifiers.Length; i++)
|
||||
{
|
||||
if (getItem.itemIdentifiers[i] != itemIdentifiers[i]) { return false; }
|
||||
if (getItem.itemIdentifiers[i] != itemIdentifiers[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -251,10 +263,7 @@ namespace Barotrauma
|
||||
foreach (string itemName in itemIdentifiers)
|
||||
{
|
||||
var matchingItem = character.Inventory.FindItemByTag(itemName) ?? character.Inventory.FindItemByIdentifier(itemName);
|
||||
if (matchingItem != null && (!equip || character.HasEquippedItem(matchingItem)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (matchingItem != null && (!equip || character.HasEquippedItem(matchingItem))) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -9,23 +9,29 @@ namespace Barotrauma
|
||||
{
|
||||
public override string DebugTag => "go to";
|
||||
|
||||
private AIObjectiveFindDivingGear findDivingGear;
|
||||
|
||||
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;
|
||||
private bool getDivingGearIfNeeded;
|
||||
|
||||
public float CloseEnough { get; set; } = 0.5f;
|
||||
public bool IgnoreIfTargetDead { get; set; }
|
||||
public bool AllowGoingOutside { get; set; }
|
||||
public bool CheckVisibility { get; set; }
|
||||
public float CloseEnough = 0.5f;
|
||||
|
||||
public override float GetPriority()
|
||||
public bool IgnoreIfTargetDead;
|
||||
|
||||
public bool AllowGoingOutside = false;
|
||||
|
||||
public override float GetPriority(AIObjectiveManager objectiveManager)
|
||||
{
|
||||
if (FollowControlledCharacter && Character.Controlled == null) { return 0.0f; }
|
||||
if (Target != null && Target.Removed) { return 0.0f; }
|
||||
if (IgnoreIfTargetDead && Target is Character character && character.IsDead) { return 0.0f; }
|
||||
if (IgnoreIfTargetDead && Target is Character character && character.IsDead) { return 0.0f; }
|
||||
|
||||
if (objectiveManager.CurrentOrder == this)
|
||||
{
|
||||
return AIObjectiveManager.OrderPriority;
|
||||
@@ -33,6 +39,44 @@ namespace Barotrauma
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
public override bool CanBeCompleted
|
||||
{
|
||||
get
|
||||
{
|
||||
bool canComplete = !cannotReach && !abandon;
|
||||
if (canComplete)
|
||||
{
|
||||
if (FollowControlledCharacter && Character.Controlled == null)
|
||||
{
|
||||
canComplete = false;
|
||||
}
|
||||
else if (Target != null && Target.Removed)
|
||||
{
|
||||
canComplete = false;
|
||||
}
|
||||
else if (!repeat && waitUntilPathUnreachable < 0)
|
||||
{
|
||||
if (SteeringManager == PathSteering && PathSteering.CurrentPath != null)
|
||||
{
|
||||
canComplete = !PathSteering.CurrentPath.Unreachable;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!canComplete)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage($"{character.Name}: Cannot reach the target: {(Target != null ? Target.ToString() : TargetPos.ToString())}", Color.Yellow);
|
||||
#endif
|
||||
if (HumanAIController.ObjectiveManager.CurrentOrder != null)
|
||||
{
|
||||
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;
|
||||
@@ -66,36 +110,27 @@ namespace Barotrauma
|
||||
{
|
||||
if (FollowControlledCharacter)
|
||||
{
|
||||
if (Character.Controlled == null)
|
||||
{
|
||||
abandon = true;
|
||||
return;
|
||||
}
|
||||
if (Character.Controlled == null) { return; }
|
||||
Target = Character.Controlled;
|
||||
}
|
||||
|
||||
if (Target == character)
|
||||
{
|
||||
character.AIController.SteeringManager.Reset();
|
||||
abandon = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
waitUntilPathUnreachable -= deltaTime;
|
||||
|
||||
if (!character.IsClimbing)
|
||||
{
|
||||
character.SelectedConstruction = null;
|
||||
}
|
||||
if (Target != null)
|
||||
{
|
||||
if (Target.Removed)
|
||||
{
|
||||
abandon = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
character.AIController.SelectTarget(Target.AiTarget);
|
||||
}
|
||||
}
|
||||
|
||||
if (Target != null) { character.AIController.SelectTarget(Target.AiTarget); }
|
||||
|
||||
Vector2 currTargetPos = Vector2.Zero;
|
||||
|
||||
if (Target == null)
|
||||
{
|
||||
currTargetPos = targetPos;
|
||||
@@ -110,12 +145,8 @@ namespace Barotrauma
|
||||
currTargetPos -= character.Submarine.SimPosition;
|
||||
}
|
||||
}
|
||||
bool sightCheck = true;
|
||||
if (CheckVisibility && Target != null)
|
||||
{
|
||||
sightCheck = Target is Character ch ? character.CanSeeCharacter(ch) : character.CanSeeTarget(Target);
|
||||
}
|
||||
if (sightCheck && Vector2.DistanceSquared(currTargetPos, character.SimPosition) < CloseEnough * CloseEnough)
|
||||
|
||||
if (Vector2.DistanceSquared(currTargetPos, character.SimPosition) < CloseEnough * CloseEnough)
|
||||
{
|
||||
character.AIController.SteeringManager.Reset();
|
||||
character.AnimController.TargetDir = currTargetPos.X > character.SimPosition.X ? Direction.Right : Direction.Left;
|
||||
@@ -123,50 +154,31 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
bool isInside = character.CurrentHull != null;
|
||||
bool insideSteering = SteeringManager == PathSteering && PathSteering.CurrentPath != null && !PathSteering.IsPathDirty;
|
||||
bool insideSteering = SteeringManager == PathSteering && PathSteering.CurrentPath != null;
|
||||
bool targetIsOutside = (Target != null && Target.Submarine == null) || (insideSteering && PathSteering.CurrentPath.HasOutdoorsNodes);
|
||||
if (isInside && targetIsOutside && !AllowGoingOutside)
|
||||
{
|
||||
abandon = true;
|
||||
}
|
||||
else if (!repeat && waitUntilPathUnreachable < 0)
|
||||
{
|
||||
if (SteeringManager == PathSteering && PathSteering.CurrentPath != null)
|
||||
{
|
||||
abandon = PathSteering.CurrentPath.Unreachable;
|
||||
}
|
||||
}
|
||||
if (abandon)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage($"{character.Name}: Cannot reach the target: {(Target != null ? Target.ToString() : TargetPos.ToString())}", Color.Yellow);
|
||||
#endif
|
||||
if (objectiveManager.CurrentOrder != null)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogCannotReach"), identifier: "cannotreach", minDurationBetweenSimilar: 10.0f);
|
||||
}
|
||||
character.AIController.SteeringManager.Reset();
|
||||
cannotReach = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
character.AIController.SteeringManager.SteeringSeek(currTargetPos);
|
||||
if (getDivingGearIfNeeded)
|
||||
{
|
||||
var targetHull = Target is Hull h ? h : Target is Item i ? i.CurrentHull : Target is Character c ? c.CurrentHull : character.CurrentHull;
|
||||
bool needsDivingGear = HumanAIController.NeedsDivingGear(targetHull);
|
||||
bool needsDivingSuit = needsDivingGear && (targetIsOutside || targetHull.WaterPercentage > 90);
|
||||
bool needsEquipment = false;
|
||||
if (needsDivingSuit)
|
||||
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))
|
||||
{
|
||||
needsEquipment = !HumanAIController.HasDivingSuit(character);
|
||||
}
|
||||
else if (needsDivingGear)
|
||||
{
|
||||
needsEquipment = !HumanAIController.HasDivingMask(character);
|
||||
}
|
||||
if (needsEquipment)
|
||||
{
|
||||
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit, objectiveManager));
|
||||
if (findDivingGear == null)
|
||||
{
|
||||
findDivingGear = new AIObjectiveFindDivingGear(character, true);
|
||||
AddSubObjective(findDivingGear);
|
||||
}
|
||||
else if (!findDivingGear.CanBeCompleted)
|
||||
{
|
||||
abandon = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -177,30 +189,40 @@ namespace Barotrauma
|
||||
{
|
||||
if (repeat) { return false; }
|
||||
bool completed = false;
|
||||
|
||||
if (Target is Item item)
|
||||
{
|
||||
if (item.IsInsideTrigger(character.WorldPosition)) { completed = true; }
|
||||
if (item.IsInsideTrigger(character.WorldPosition)) completed = true;
|
||||
}
|
||||
else if (Target is Character targetCharacter)
|
||||
{
|
||||
if (character.CanInteractWith(targetCharacter)) { completed = true; }
|
||||
if (character.CanInteractWith(targetCharacter)) completed = true;
|
||||
}
|
||||
|
||||
completed = completed || Vector2.DistanceSquared(Target != null ? Target.SimPosition : targetPos, character.SimPosition) < CloseEnough * CloseEnough;
|
||||
if (completed) { character.AIController.SteeringManager.Reset(); }
|
||||
|
||||
if (completed) character.AIController.SteeringManager.Reset();
|
||||
|
||||
return completed;
|
||||
}
|
||||
|
||||
public override bool IsDuplicate(AIObjective otherObjective)
|
||||
{
|
||||
if (!(otherObjective is AIObjectiveGoTo objective)) { return false; }
|
||||
if (objective.Target == Target) { return true; }
|
||||
return objective.targetPos == targetPos;
|
||||
}
|
||||
AIObjectiveGoTo objective = otherObjective as AIObjectiveGoTo;
|
||||
if (objective == null) return false;
|
||||
|
||||
if (objective.Target == Target) return true;
|
||||
|
||||
private void CalculateCloseEnough()
|
||||
{
|
||||
float interactionDistance = Target is Item i ? ConvertUnits.ToSimUnits(i.InteractDistance * 0.9f) : 0;
|
||||
CloseEnough = Math.Max(interactionDistance, CloseEnough);
|
||||
}
|
||||
|
||||
private void CalculateCloseEnough()
|
||||
{
|
||||
float interactionDistance = Target is Item i ? ConvertUnits.ToSimUnits(i.InteractDistance) : 0;
|
||||
CloseEnough = Math.Max(interactionDistance, CloseEnough);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace Barotrauma
|
||||
private readonly List<Hull> targetHulls = new List<Hull>(20);
|
||||
private readonly List<float> hullWeights = new List<float>(20);
|
||||
|
||||
public AIObjectiveIdle(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier)
|
||||
public void SetRandom()
|
||||
{
|
||||
standStillTimer = Rand.Range(-10.0f, 10.0f);
|
||||
walkDuration = Rand.Range(0.0f, 10.0f);
|
||||
@@ -38,27 +38,6 @@ namespace Barotrauma
|
||||
public override bool IsCompleted() => false;
|
||||
public override bool CanBeCompleted => true;
|
||||
|
||||
public override bool IsLoop { get => true; set => throw new System.Exception("Trying to set the value for IsLoop from: " + System.Environment.StackTrace); }
|
||||
|
||||
private float randomTimer;
|
||||
private float randomUpdateInterval = 5;
|
||||
public float Random { get; private set; }
|
||||
|
||||
public void SetRandom()
|
||||
{
|
||||
Random = Rand.Range(0.5f, 1.5f);
|
||||
randomTimer = randomUpdateInterval;
|
||||
}
|
||||
|
||||
public override float GetPriority()
|
||||
{
|
||||
return 1;
|
||||
float max = Math.Min(Math.Min(AIObjectiveManager.RunPriority, AIObjectiveManager.OrderPriority) - 1, 100);
|
||||
float initiative = character.GetSkillLevel("initiative");
|
||||
Priority = MathHelper.Lerp(1, max, MathUtils.InverseLerp(100, 0, initiative * Random));
|
||||
return Priority;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (objectiveManager.CurrentObjective == this)
|
||||
@@ -99,7 +78,7 @@ namespace Barotrauma
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
if (PathSteering == null) { return; }
|
||||
if (PathSteering == null) return;
|
||||
|
||||
//don't keep dragging others when idling
|
||||
if (character.SelectedCharacter != null)
|
||||
@@ -111,10 +90,17 @@ namespace Barotrauma
|
||||
character.SelectedConstruction = null;
|
||||
}
|
||||
|
||||
bool currentHullForbidden = IsForbidden(character.CurrentHull);
|
||||
if (!currentHullForbidden && !character.AnimController.InWater && !character.IsClimbing && HumanAIController.ObjectiveManager.WaitTimer > 0)
|
||||
{
|
||||
SteeringManager.Reset();
|
||||
return;
|
||||
}
|
||||
|
||||
bool currentTargetIsInvalid = currentTarget == null || IsForbidden(currentTarget) ||
|
||||
(PathSteering.CurrentPath != null && PathSteering.CurrentPath.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull)));
|
||||
|
||||
if (currentTargetIsInvalid || currentTarget == null && IsForbidden(character.CurrentHull))
|
||||
if (currentTargetIsInvalid || (currentTarget == null && currentHullForbidden))
|
||||
{
|
||||
newTargetTimer = 0;
|
||||
standStillTimer = 0;
|
||||
@@ -151,6 +137,7 @@ namespace Barotrauma
|
||||
{
|
||||
//choose a random available hull
|
||||
var randomHull = ToolBox.SelectWeightedRandom(targetHulls, hullWeights, Rand.RandSync.Unsynced);
|
||||
|
||||
bool isCurrentHullOK = !HumanAIController.UnsafeHulls.Contains(character.CurrentHull) && !IsForbidden(character.CurrentHull);
|
||||
if (isCurrentHullOK)
|
||||
{
|
||||
@@ -158,8 +145,7 @@ namespace Barotrauma
|
||||
// Only do this when the current hull is ok, because otherwise would block all paths from the current hull to the target hull.
|
||||
var path = PathSteering.PathFinder.FindPath(character.SimPosition, randomHull.SimPosition);
|
||||
if (path.Unreachable ||
|
||||
path.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull) ||
|
||||
IsForbidden(n.CurrentHull)))
|
||||
path.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull) || IsForbidden(n.CurrentHull)))
|
||||
{
|
||||
//can't go to this room, remove it from the list and try another room next frame
|
||||
int index = targetHulls.IndexOf(randomHull);
|
||||
@@ -282,6 +268,7 @@ namespace Barotrauma
|
||||
|
||||
private void FindTargetHulls()
|
||||
{
|
||||
bool isCurrentHullOK = !HumanAIController.UnsafeHulls.Contains(character.CurrentHull) && !IsForbidden(character.CurrentHull);
|
||||
targetHulls.Clear();
|
||||
hullWeights.Clear();
|
||||
foreach (var hull in Hull.hullList)
|
||||
@@ -313,7 +300,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsForbidden(Hull hull)
|
||||
private bool IsForbidden(Hull hull)
|
||||
{
|
||||
if (hull == null) { return true; }
|
||||
string hullName = hull.RoomName?.ToLowerInvariant();
|
||||
|
||||
@@ -7,34 +7,18 @@ namespace Barotrauma
|
||||
{
|
||||
abstract class AIObjectiveLoop<T> : AIObjective
|
||||
{
|
||||
protected HashSet<T> targets = new HashSet<T>();
|
||||
protected List<T> targets = new List<T>();
|
||||
protected Dictionary<T, AIObjective> objectives = new Dictionary<T, AIObjective>();
|
||||
protected HashSet<T> ignoreList = new HashSet<T>();
|
||||
private float ignoreListTimer;
|
||||
private float targetUpdateTimer;
|
||||
|
||||
private static AIObjectiveLoop<T> instance;
|
||||
|
||||
// By default, doesn't clear the list automatically
|
||||
protected virtual float IgnoreListClearInterval => 0;
|
||||
protected virtual float TargetUpdateInterval => 2;
|
||||
|
||||
public HashSet<T> ReportedTargets { get; private set; } = new HashSet<T>();
|
||||
|
||||
public bool AddTarget(T target)
|
||||
public AIObjectiveLoop(Character character, string option) : base(character, option)
|
||||
{
|
||||
if (Filter(target))
|
||||
{
|
||||
ReportedTargets.Add(target);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public AIObjectiveLoop(Character character, AIObjectiveManager objectiveManager, float priorityModifier, string option = null)
|
||||
: base(character, objectiveManager, priorityModifier, option)
|
||||
{
|
||||
instance = this;
|
||||
Reset();
|
||||
}
|
||||
|
||||
@@ -42,11 +26,9 @@ namespace Barotrauma
|
||||
public override bool IsCompleted() => false;
|
||||
public override bool CanBeCompleted => true;
|
||||
|
||||
public override bool IsLoop { get => true; set => throw new System.Exception("Trying to set the value for IsLoop from: " + System.Environment.StackTrace); }
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
public override void Update(AIObjectiveManager objectiveManager, float deltaTime)
|
||||
{
|
||||
base.Update(deltaTime);
|
||||
base.Update(objectiveManager, deltaTime);
|
||||
if (IgnoreListClearInterval > 0)
|
||||
{
|
||||
if (ignoreListTimer > IgnoreListClearInterval)
|
||||
@@ -97,29 +79,15 @@ namespace Barotrauma
|
||||
|
||||
public override void OnSelected()
|
||||
{
|
||||
base.OnSelected();
|
||||
if (HumanAIController.ObjectiveManager.CurrentOrder == this)
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
Reset();
|
||||
}
|
||||
|
||||
public override float GetPriority()
|
||||
public override float GetPriority(AIObjectiveManager objectiveManager)
|
||||
{
|
||||
if (character.Submarine == null) { return 0; }
|
||||
if (targets.None()) { return 0; }
|
||||
// Allow the target value to be more than 100.
|
||||
float targetValue = TargetEvaluation();
|
||||
// If the target value is less than 1% of the max value, let's just treat it as zero.
|
||||
if (targetValue < 1) { return 0; }
|
||||
if (objectiveManager.CurrentOrder == this)
|
||||
{
|
||||
return AIObjectiveManager.OrderPriority;
|
||||
}
|
||||
float max = MathHelper.Min(AIObjectiveManager.OrderPriority - 1, 90);
|
||||
float devotion = MathHelper.Min(10, Priority);
|
||||
float value = MathHelper.Clamp((devotion + targetValue * PriorityModifier) / 100, 0, 1);
|
||||
return MathHelper.Lerp(0, max, value);
|
||||
float avg = targets.Average(t => Average(t));
|
||||
return MathHelper.Lerp(0, AIObjectiveManager.OrderPriority + 20, avg / 100);
|
||||
}
|
||||
|
||||
protected void UpdateTargets()
|
||||
@@ -131,19 +99,12 @@ namespace Barotrauma
|
||||
|
||||
protected virtual void FindTargets()
|
||||
{
|
||||
foreach (T target in GetList())
|
||||
foreach (T item in GetList())
|
||||
{
|
||||
// The bots always find targets when the objective is an order.
|
||||
if (objectiveManager.CurrentOrder != this)
|
||||
if (Filter(item)) { continue; }
|
||||
if (!targets.Contains(item))
|
||||
{
|
||||
// Battery or pump states cannot currently be reported (not implemented) and therefore we must ignore them -> the bots always know if they require attention.
|
||||
bool ignore = this is AIObjectiveChargeBatteries || this is AIObjectivePumpWater;
|
||||
if (!ignore && !ReportedTargets.Contains(target)) { continue; }
|
||||
}
|
||||
if (!Filter(target)) { continue; }
|
||||
if (!ignoreList.Contains(target))
|
||||
{
|
||||
targets.Add(target);
|
||||
targets.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -155,7 +116,6 @@ namespace Barotrauma
|
||||
if (!objectives.TryGetValue(target, out AIObjective objective))
|
||||
{
|
||||
objective = ObjectiveConstructor(target);
|
||||
objective.Completed += () => ReportedTargets.Remove(target);
|
||||
objectives.Add(target, objective);
|
||||
AddSubObjective(objective);
|
||||
}
|
||||
@@ -166,12 +126,8 @@ namespace Barotrauma
|
||||
/// List of all possible items of the specified type. Used for filtering the removed objectives.
|
||||
/// </summary>
|
||||
protected abstract IEnumerable<T> GetList();
|
||||
|
||||
protected abstract float TargetEvaluation();
|
||||
|
||||
protected abstract float Average(T target);
|
||||
protected abstract AIObjective ObjectiveConstructor(T target);
|
||||
protected abstract bool Filter(T target);
|
||||
|
||||
public static bool IsValidTarget(T target) => instance == null ? false : instance.Filter(target);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,29 +9,27 @@ namespace Barotrauma
|
||||
class AIObjectiveManager
|
||||
{
|
||||
// TODO: expose
|
||||
public const float OrderPriority = 70;
|
||||
public const float RunPriority = 50;
|
||||
public const float OrderPriority = 50.0f;
|
||||
// Constantly increases the priority of the selected objective, unless overridden
|
||||
public const float baseDevotion = 2;
|
||||
|
||||
public List<AIObjective> Objectives { get; private set; } = new List<AIObjective>();
|
||||
public List<AIObjective> Objectives { get; private set; }
|
||||
|
||||
private Character character;
|
||||
|
||||
/// <summary>
|
||||
/// When set above zero, the character will stand still doing nothing until the timer runs out. Does not affect orders.
|
||||
/// When set above zero, the character will stand still doing nothing until the timer runs out. Only affects idling.
|
||||
/// </summary>
|
||||
public float WaitTimer;
|
||||
|
||||
public AIObjective CurrentOrder { get; private set; }
|
||||
public AIObjective CurrentObjective { get; private set; }
|
||||
|
||||
public bool IsCurrentObjective<T>() where T : AIObjective => CurrentObjective is T;
|
||||
|
||||
public AIObjectiveManager(Character character)
|
||||
{
|
||||
this.character = character;
|
||||
CreateAutonomousObjectives();
|
||||
|
||||
Objectives = new List<AIObjective>();
|
||||
}
|
||||
|
||||
public void AddObjective(AIObjective objective)
|
||||
@@ -48,30 +46,6 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
public Dictionary<AIObjective, CoroutineHandle> DelayedObjectives { get; private set; } = new Dictionary<AIObjective, CoroutineHandle>();
|
||||
|
||||
public void CreateAutonomousObjectives()
|
||||
{
|
||||
Objectives.Clear();
|
||||
AddObjective(new AIObjectiveFindSafety(character, this), delay: Rand.Value() / 2);
|
||||
AddObjective(new AIObjectiveIdle(character, this), delay: Rand.Value() / 2);
|
||||
int objectiveCount = Objectives.Count;
|
||||
foreach (var automaticOrder in character.Info.Job.Prefab.AutomaticOrders)
|
||||
{
|
||||
var orderPrefab = Order.PrefabList.Find(o => o.AITag == automaticOrder.aiTag);
|
||||
if (orderPrefab == null) { throw new Exception("Could not find a matching prefab by ai tag: " + automaticOrder.aiTag); }
|
||||
// TODO: Similar code is used in CrewManager:815-> DRY
|
||||
var matchingItems = orderPrefab.ItemIdentifiers.Any() ?
|
||||
Item.ItemList.FindAll(it => orderPrefab.ItemIdentifiers.Contains(it.Prefab.Identifier) || it.HasTag(orderPrefab.ItemIdentifiers)) :
|
||||
Item.ItemList.FindAll(it => it.Components.Any(ic => ic.GetType() == orderPrefab.ItemComponentType));
|
||||
matchingItems.RemoveAll(it => it.Submarine != character.Submarine);
|
||||
var item = matchingItems.GetRandom();
|
||||
var order = new Order(orderPrefab, item ?? character.CurrentHull as Entity, item?.Components.FirstOrDefault(ic => ic.GetType() == orderPrefab.ItemComponentType));
|
||||
AddObjective(CreateObjective(order, automaticOrder.option, character, automaticOrder.priorityModifier), delay: Rand.Value() / 2);
|
||||
objectiveCount++;
|
||||
}
|
||||
WaitTimer = Math.Max(WaitTimer, Rand.Range(0.5f, 1f) * objectiveCount);
|
||||
}
|
||||
|
||||
public void AddObjective(AIObjective objective, float delay, Action callback = null)
|
||||
{
|
||||
if (DelayedObjectives.TryGetValue(objective, out CoroutineHandle coroutine))
|
||||
@@ -101,7 +75,7 @@ namespace Barotrauma
|
||||
{
|
||||
var previousObjective = CurrentObjective;
|
||||
var firstObjective = Objectives.FirstOrDefault();
|
||||
if (CurrentOrder != null && firstObjective != null && CurrentOrder.GetPriority() > firstObjective.GetPriority())
|
||||
if (CurrentOrder != null && firstObjective != null && CurrentOrder.GetPriority(this) > firstObjective.GetPriority(this))
|
||||
{
|
||||
CurrentObjective = CurrentOrder;
|
||||
}
|
||||
@@ -112,24 +86,18 @@ namespace Barotrauma
|
||||
if (previousObjective != CurrentObjective)
|
||||
{
|
||||
CurrentObjective?.OnSelected();
|
||||
GetObjective<AIObjectiveIdle>()?.SetRandom();
|
||||
}
|
||||
return CurrentObjective;
|
||||
}
|
||||
|
||||
public float GetCurrentPriority()
|
||||
{
|
||||
return CurrentObjective == null ? 0.0f : CurrentObjective.GetPriority();
|
||||
return CurrentObjective == null ? 0.0f : CurrentObjective.GetPriority(this);
|
||||
}
|
||||
|
||||
public void UpdateObjectives(float deltaTime)
|
||||
{
|
||||
CurrentOrder?.Update(deltaTime);
|
||||
if (WaitTimer > 0)
|
||||
{
|
||||
WaitTimer -= deltaTime;
|
||||
return;
|
||||
}
|
||||
CurrentOrder?.Update(this, deltaTime);
|
||||
for (int i = 0; i < Objectives.Count; i++)
|
||||
{
|
||||
var objective = Objectives[i];
|
||||
@@ -149,7 +117,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
objective.Update(deltaTime);
|
||||
objective.Update(this, deltaTime);
|
||||
}
|
||||
}
|
||||
GetCurrentObjective();
|
||||
@@ -159,41 +127,15 @@ namespace Barotrauma
|
||||
{
|
||||
if (Objectives.Any())
|
||||
{
|
||||
Objectives.Sort((x, y) => y.GetPriority().CompareTo(x.GetPriority()));
|
||||
Objectives.Sort((x, y) => y.GetPriority(this).CompareTo(x.GetPriority(this)));
|
||||
}
|
||||
CurrentObjective?.SortSubObjectives();
|
||||
CurrentObjective?.SortSubObjectives(this);
|
||||
}
|
||||
|
||||
public void DoCurrentObjective(float deltaTime)
|
||||
{
|
||||
if (WaitTimer <= 0)
|
||||
{
|
||||
CurrentObjective?.TryComplete(deltaTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (CurrentOrder != null)
|
||||
{
|
||||
CurrentOrder.TryComplete(deltaTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (character.AIController is HumanAIController humanAI && humanAI.SteeringManager != null)
|
||||
{
|
||||
if (!character.AnimController.InWater &&
|
||||
!character.IsClimbing &&
|
||||
!humanAI.UnsafeHulls.Contains(character.CurrentHull) &&
|
||||
!AIObjectiveIdle.IsForbidden(character.CurrentHull))
|
||||
{
|
||||
humanAI.SteeringManager.Reset();
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentObjective?.TryComplete(deltaTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (WaitTimer > 0.0f) { WaitTimer -= deltaTime; }
|
||||
CurrentObjective?.TryComplete(deltaTime);
|
||||
}
|
||||
|
||||
public void SetOrder(AIObjective objective)
|
||||
@@ -203,22 +145,13 @@ namespace Barotrauma
|
||||
|
||||
public void SetOrder(Order order, string option, Character orderGiver)
|
||||
{
|
||||
CurrentOrder = CreateObjective(order, option, orderGiver);
|
||||
if (CurrentOrder == null)
|
||||
{
|
||||
// Recreate objectives, because some of them may be removed, if impossible to complete (e.g. due to path finding)
|
||||
CreateAutonomousObjectives();
|
||||
}
|
||||
}
|
||||
CurrentOrder = null;
|
||||
if (order == null) return;
|
||||
|
||||
public AIObjective CreateObjective(Order order, string option, Character orderGiver, float priorityModifier = 1)
|
||||
{
|
||||
if (order == null) { return null; }
|
||||
AIObjective newObjective;
|
||||
switch (order.AITag.ToLowerInvariant())
|
||||
{
|
||||
case "follow":
|
||||
newObjective = new AIObjectiveGoTo(orderGiver, character, this, repeat: true, priorityModifier: priorityModifier)
|
||||
CurrentOrder = new AIObjectiveGoTo(orderGiver, character, true)
|
||||
{
|
||||
CloseEnough = 1.5f,
|
||||
AllowGoingOutside = true,
|
||||
@@ -227,41 +160,38 @@ namespace Barotrauma
|
||||
};
|
||||
break;
|
||||
case "wait":
|
||||
newObjective = new AIObjectiveGoTo(character, character, this, repeat: true, priorityModifier: priorityModifier)
|
||||
CurrentOrder = new AIObjectiveGoTo(character, character, true)
|
||||
{
|
||||
AllowGoingOutside = true
|
||||
};
|
||||
break;
|
||||
case "fixleaks":
|
||||
newObjective = new AIObjectiveFixLeaks(character, this, priorityModifier);
|
||||
CurrentOrder = new AIObjectiveFixLeaks(character);
|
||||
break;
|
||||
case "chargebatteries":
|
||||
newObjective = new AIObjectiveChargeBatteries(character, this, option, priorityModifier);
|
||||
CurrentOrder = new AIObjectiveChargeBatteries(character, option);
|
||||
break;
|
||||
case "rescue":
|
||||
newObjective = new AIObjectiveRescueAll(character, this, priorityModifier);
|
||||
CurrentOrder = new AIObjectiveRescueAll(character);
|
||||
break;
|
||||
case "repairsystems":
|
||||
newObjective = new AIObjectiveRepairItems(character, this, priorityModifier) { RequireAdequateSkills = option != "all" };
|
||||
CurrentOrder = new AIObjectiveRepairItems(character) { RequireAdequateSkills = option != "all" };
|
||||
break;
|
||||
case "pumpwater":
|
||||
newObjective = new AIObjectivePumpWater(character, this, option, priorityModifier: priorityModifier);
|
||||
CurrentOrder = new AIObjectivePumpWater(character, option);
|
||||
break;
|
||||
case "extinguishfires":
|
||||
newObjective = new AIObjectiveExtinguishFires(character, this, priorityModifier);
|
||||
break;
|
||||
case "fightintruders":
|
||||
newObjective = new AIObjectiveFightIntruders(character, this, priorityModifier);
|
||||
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 null; }
|
||||
newObjective = new AIObjectiveOperateItem(order.TargetItemComponent, character, this, option, requireEquip: false, useController: order.UseController, priorityModifier: priorityModifier) { IsLoop = true };
|
||||
if (order.TargetItemComponent == null) return;
|
||||
CurrentOrder = new AIObjectiveOperateItem(order.TargetItemComponent, character, option, false, null, order.UseController);
|
||||
break;
|
||||
default:
|
||||
if (order.TargetItemComponent == null) { return null; }
|
||||
newObjective = new AIObjectiveOperateItem(order.TargetItemComponent, character, this, option, requireEquip: false, useController: order.UseController, priorityModifier: priorityModifier) { IsLoop = true };
|
||||
if (order.TargetItemComponent == null) return;
|
||||
CurrentOrder = new AIObjectiveOperateItem(order.TargetItemComponent, character, option, false, null, order.UseController);
|
||||
break;
|
||||
}
|
||||
return newObjective;
|
||||
|
||||
@@ -10,6 +10,8 @@ namespace Barotrauma
|
||||
{
|
||||
public override string DebugTag => "operate item";
|
||||
|
||||
private ItemComponent component, controller;
|
||||
|
||||
private ItemComponent component, controller;
|
||||
private Entity operateTarget;
|
||||
private bool isCompleted;
|
||||
@@ -18,25 +20,35 @@ namespace Barotrauma
|
||||
private AIObjectiveGoTo goToObjective;
|
||||
private AIObjectiveGetItem getItemObjective;
|
||||
|
||||
public override bool CanBeCompleted => base.CanBeCompleted && (!useController || controller != null);
|
||||
private bool useController;
|
||||
|
||||
private AIObjectiveGoTo gotoObjective;
|
||||
|
||||
public override bool CanBeCompleted
|
||||
{
|
||||
get
|
||||
{
|
||||
if (gotoObjective != null && !gotoObjective.CanBeCompleted) return false;
|
||||
|
||||
if (useController && controller == null) return false;
|
||||
|
||||
return canBeCompleted;
|
||||
}
|
||||
}
|
||||
|
||||
public Entity OperateTarget => operateTarget;
|
||||
public ItemComponent Component => component;
|
||||
|
||||
public override float GetPriority()
|
||||
public ItemComponent Component => component;
|
||||
|
||||
public override float GetPriority(AIObjectiveManager objectiveManager)
|
||||
{
|
||||
if (component.Item.ConditionPercentage <= 0) { return 0; }
|
||||
if (gotoObjective != null && !gotoObjective.CanBeCompleted) { return 0; }
|
||||
if (objectiveManager.CurrentOrder == this)
|
||||
{
|
||||
return AIObjectiveManager.OrderPriority;
|
||||
}
|
||||
if (component.Item.CurrentHull == null) { return 0; }
|
||||
if (component.Item.CurrentHull.FireSources.Count > 0) { return 0; }
|
||||
if (Character.CharacterList.Any(c => c.CurrentHull == component.Item.CurrentHull && !HumanAIController.IsFriendly(c))) { return 0; }
|
||||
float devotion = MathHelper.Min(10, Priority);
|
||||
float value = devotion + AIObjectiveManager.OrderPriority * PriorityModifier;
|
||||
float max = MathHelper.Min((AIObjectiveManager.OrderPriority - 1), 90);
|
||||
return MathHelper.Clamp(value, 0, max);
|
||||
return base.GetPriority(objectiveManager);
|
||||
}
|
||||
|
||||
public AIObjectiveOperateItem(ItemComponent item, Character character, AIObjectiveManager objectiveManager, string option, bool requireEquip, Entity operateTarget = null, bool useController = false, float priorityModifier = 1)
|
||||
@@ -46,131 +58,55 @@ namespace Barotrauma
|
||||
this.requireEquip = requireEquip;
|
||||
this.operateTarget = operateTarget;
|
||||
this.useController = useController;
|
||||
|
||||
if (useController)
|
||||
{
|
||||
controller = component.Item.GetConnectedComponents<Controller>().FirstOrDefault();
|
||||
var controllers = component.Item.GetConnectedComponents<Controller>();
|
||||
if (controllers.Any()) controller = controllers[0];
|
||||
}
|
||||
|
||||
canBeCompleted = true;
|
||||
}
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
ItemComponent target = useController ? controller : component;
|
||||
|
||||
if (useController && controller == null)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogCantFindController").Replace("[item]", component.Item.Name), null, 2.0f, "cantfindcontroller", 30.0f);
|
||||
abandon = true;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (target.CanBeSelected)
|
||||
{
|
||||
bool inSameRoom = character.CurrentHull == target.Item.CurrentHull;
|
||||
bool withinReach = target.Item.IsInsideTrigger(character.WorldPosition) || Vector2.DistanceSquared(character.Position, target.Item.Position) < MathUtils.Pow(target.Item.InteractDistance, 2);
|
||||
if (inSameRoom && withinReach)
|
||||
{
|
||||
if (character.SelectedConstruction != target.Item)
|
||||
if (character.CurrentHull == target.Item.CurrentHull)
|
||||
{
|
||||
isCompleted = true;
|
||||
}
|
||||
if (component.AIOperate(deltaTime, character, this))
|
||||
{
|
||||
isCompleted = true;
|
||||
}
|
||||
if (component.AIOperate(deltaTime, character, this))
|
||||
{
|
||||
isCompleted = true;
|
||||
}
|
||||
if (component.AIOperate(deltaTime, character, this))
|
||||
{
|
||||
isCompleted = true;
|
||||
}
|
||||
if (component.AIOperate(deltaTime, character, this))
|
||||
{
|
||||
isCompleted = true;
|
||||
}
|
||||
if (component.AIOperate(deltaTime, character, this))
|
||||
{
|
||||
isCompleted = true;
|
||||
}
|
||||
if (component.AIOperate(deltaTime, character, this))
|
||||
{
|
||||
isCompleted = true;
|
||||
}
|
||||
if (component.AIOperate(deltaTime, character, this))
|
||||
{
|
||||
isCompleted = true;
|
||||
}
|
||||
if (component.AIOperate(deltaTime, character, this))
|
||||
{
|
||||
isCompleted = true;
|
||||
}
|
||||
if (component.AIOperate(deltaTime, character, this))
|
||||
{
|
||||
isCompleted = true;
|
||||
}
|
||||
if (component.AIOperate(deltaTime, character, this))
|
||||
{
|
||||
isCompleted = true;
|
||||
}
|
||||
if (component.AIOperate(deltaTime, character, this))
|
||||
{
|
||||
isCompleted = true;
|
||||
}
|
||||
if (component.AIOperate(deltaTime, character, this))
|
||||
{
|
||||
isCompleted = true;
|
||||
}
|
||||
if (component.AIOperate(deltaTime, character, this))
|
||||
{
|
||||
isCompleted = true;
|
||||
}
|
||||
if (component.AIOperate(deltaTime, character, this))
|
||||
{
|
||||
isCompleted = true;
|
||||
}
|
||||
if (component.AIOperate(deltaTime, character, this))
|
||||
{
|
||||
isCompleted = true;
|
||||
}
|
||||
if (component.AIOperate(deltaTime, character, this))
|
||||
{
|
||||
isCompleted = true;
|
||||
}
|
||||
if (component.AIOperate(deltaTime, character, this))
|
||||
{
|
||||
isCompleted = true;
|
||||
}
|
||||
if (component.AIOperate(deltaTime, character, this))
|
||||
{
|
||||
isCompleted = true;
|
||||
}
|
||||
if (component.AIOperate(deltaTime, character, this))
|
||||
{
|
||||
isCompleted = true;
|
||||
}
|
||||
if (component.AIOperate(deltaTime, character, this))
|
||||
{
|
||||
isCompleted = true;
|
||||
}
|
||||
if (component.AIOperate(deltaTime, character, this))
|
||||
{
|
||||
isCompleted = true;
|
||||
}
|
||||
if (component.AIOperate(deltaTime, character, this))
|
||||
{
|
||||
isCompleted = true;
|
||||
if (character.SelectedConstruction != target.Item && target.CanBeSelected)
|
||||
{
|
||||
target.Item.TryInteract(character, false, true);
|
||||
}
|
||||
if (component.AIOperate(deltaTime, character, this))
|
||||
{
|
||||
isCompleted = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(target.Item, character, objectiveManager));
|
||||
}
|
||||
|
||||
AddSubObjective(gotoObjective = new AIObjectiveGoTo(target.Item, character));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (component.Item.GetComponent<Pickable>() == null)
|
||||
{
|
||||
//controller/target can't be selected and the item cannot be picked -> objective can't be completed
|
||||
abandon = true;
|
||||
canBeCompleted = false;
|
||||
return;
|
||||
}
|
||||
else if (!character.Inventory.Items.Contains(component.Item))
|
||||
@@ -190,9 +126,11 @@ namespace Barotrauma
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < character.Inventory.Capacity; i++)
|
||||
{
|
||||
if (character.Inventory.SlotTypes[i] == InvSlotType.Any || !holdable.AllowedSlots.Any(s => s.HasFlag(character.Inventory.SlotTypes[i])))
|
||||
if (character.Inventory.SlotTypes[i] == InvSlotType.Any ||
|
||||
!holdable.AllowedSlots.Any(s => s.HasFlag(character.Inventory.SlotTypes[i])))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -226,8 +164,10 @@ namespace Barotrauma
|
||||
|
||||
public override bool IsDuplicate(AIObjective otherObjective)
|
||||
{
|
||||
if (!(otherObjective is AIObjectiveOperateItem operateItem)) { return false; }
|
||||
return (operateItem.component == component || otherObjective.Option == Option);
|
||||
AIObjectiveOperateItem operateItem = otherObjective as AIObjectiveOperateItem;
|
||||
if (operateItem == null) return false;
|
||||
|
||||
return (operateItem.component == component ||otherObjective.Option == Option);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,73 +1,68 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class AIObjectivePumpWater : AIObjectiveLoop<Pump>
|
||||
{
|
||||
public override string DebugTag => "pump water";
|
||||
private IEnumerable<Pump> pumpList;
|
||||
public override bool KeepDivingGearOn => true;
|
||||
private readonly IEnumerable<Pump> pumpList;
|
||||
|
||||
public AIObjectivePumpWater(Character character, AIObjectiveManager objectiveManager, string option, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier, option) { }
|
||||
public AIObjectivePumpWater(Character character, string option) : base(character, option)
|
||||
{
|
||||
pumpList = character.Submarine.GetItems(true).Select(i => i.GetComponent<Pump>()).Where(p => p != null);
|
||||
}
|
||||
|
||||
public override float GetPriority(AIObjectiveManager objectiveManager)
|
||||
{
|
||||
if (character.Submarine == null) { return 0; }
|
||||
if (objectiveManager.CurrentOrder == this && targets.Count > 0)
|
||||
{
|
||||
return AIObjectiveManager.OrderPriority;
|
||||
}
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
if (Option == null) { return; }
|
||||
base.FindTargets();
|
||||
if (targets.None() && objectiveManager.CurrentOrder == this)
|
||||
if (option == null) { return; }
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogNoPumps"), null, 3.0f, "nopumps", 30.0f);
|
||||
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)
|
||||
{
|
||||
if (!ignoreList.Contains(pump))
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool Filter(Pump pump)
|
||||
{
|
||||
if (pump == null) { return false; }
|
||||
if (pump.Item.HasTag("ballast")) { return false; }
|
||||
if (pump.Item.Submarine == null) { return false; }
|
||||
if (pump.Item.CurrentHull == null) { return false; }
|
||||
if (pump.Item.Submarine.TeamID != character.TeamID) { return false; }
|
||||
if (pump.Item.CurrentHull.FireSources.Count > 0 || Character.CharacterList.Any(c => c.CurrentHull == pump.Item.CurrentHull && !HumanAIController.IsFriendly(c))) { return false; }
|
||||
if (pump.Item.ConditionPercentage <= 0) { return false; }
|
||||
if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(pump.Item, true)) { return false; }
|
||||
if (Option == "stoppumping")
|
||||
{
|
||||
if (!pump.IsActive || MathUtils.NearlyEqual(pump.FlowPercentage, 0)) { return false; }
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!pump.Item.InWater) { return false; }
|
||||
if (pump.IsActive && pump.FlowPercentage <= -99.9f) { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
protected override IEnumerable<Pump> GetList()
|
||||
{
|
||||
if (pumpList == null)
|
||||
{
|
||||
pumpList = character.Submarine.GetItems(true).Select(i => i.GetComponent<Pump>()).Where(p => p != null);
|
||||
}
|
||||
return pumpList;
|
||||
}
|
||||
|
||||
protected override AIObjective ObjectiveConstructor(Pump pump) => new AIObjectiveOperateItem(pump, character, objectiveManager, Option, false) { IsLoop = false };
|
||||
protected override float TargetEvaluation()
|
||||
{
|
||||
if (Option == "stoppumping")
|
||||
{
|
||||
return targets.Max(t => MathHelper.Lerp(0, 100, Math.Abs(t.FlowPercentage / 100)));
|
||||
}
|
||||
else
|
||||
{
|
||||
return targets.Max(t => MathHelper.Lerp(100, 0, Math.Abs(-t.FlowPercentage / 100)));
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,37 +11,37 @@ namespace Barotrauma
|
||||
{
|
||||
public override string DebugTag => "repair item";
|
||||
|
||||
public override bool KeepDivingGearOn => true;
|
||||
|
||||
public Item Item { get; private set; }
|
||||
|
||||
private AIObjectiveGoTo goToObjective;
|
||||
private float previousCondition = -1;
|
||||
private RepairTool repairTool;
|
||||
|
||||
public AIObjectiveRepairItem(Character character, Item item, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier)
|
||||
private float previousCondition = -1;
|
||||
|
||||
public AIObjectiveRepairItem(Character character, Item item) : base(character, "")
|
||||
{
|
||||
Item = item;
|
||||
}
|
||||
|
||||
public override float GetPriority()
|
||||
public override float GetPriority(AIObjectiveManager objectiveManager)
|
||||
{
|
||||
// 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; }
|
||||
if (Item.CurrentHull == null) { return 0; }
|
||||
if (Item.CurrentHull.FireSources.Count > 0) { return 0; }
|
||||
if (Character.CharacterList.Any(c => c.CurrentHull == Item.CurrentHull && !HumanAIController.IsFriendly(c))) { 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 / Item.MaxCondition);
|
||||
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 devotion = (Math.Min(Priority, 10) + isSelected) / 100;
|
||||
float max = MathHelper.Min(AIObjectiveManager.OrderPriority - 1, 90);
|
||||
return MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + damagePriority * distanceFactor * successFactor * PriorityModifier, 0, 1));
|
||||
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()
|
||||
{
|
||||
bool isCompleted = Item.IsFullCondition;
|
||||
@@ -59,6 +59,15 @@ namespace Barotrauma
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
if (goToObjective != null && !subObjectives.Contains(goToObjective))
|
||||
{
|
||||
if (!goToObjective.IsCompleted() && !goToObjective.CanBeCompleted)
|
||||
{
|
||||
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))
|
||||
@@ -68,14 +77,12 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (RelatedItem requiredItem in kvp.Value)
|
||||
{
|
||||
AddSubObjective(new AIObjectiveGetItem(character, requiredItem.Identifiers, objectiveManager, true));
|
||||
AddSubObjective(new AIObjectiveGetItem(character, requiredItem.Identifiers, true));
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Only continue when the get item sub objectives have been completed.
|
||||
if (subObjectives.Any(so => so is AIObjectiveGetItem)) { return; }
|
||||
if (repairTool == null)
|
||||
{
|
||||
FindRepairTool();
|
||||
@@ -107,31 +114,31 @@ namespace Barotrauma
|
||||
{
|
||||
// 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("DialogCannotRepair").Replace("[itemname]", Item.Name), null, 0.0f, "cannotrepair", 10.0f);
|
||||
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)
|
||||
{
|
||||
// If cannot reach the item, approach it.
|
||||
TryAddSubObjective(ref goToObjective,
|
||||
constructor: () =>
|
||||
{
|
||||
previousCondition = -1;
|
||||
var objective = new AIObjectiveGoTo(Item, character, objectiveManager);
|
||||
if (repairTool != null)
|
||||
{
|
||||
objective.CloseEnough = ConvertUnits.ToSimUnits(repairTool.Range) * 0.75f;
|
||||
}
|
||||
return objective;
|
||||
},
|
||||
onAbandon: () => character.Speak(TextManager.Get("DialogCannotRepair").Replace("[itemname]", Item.Name), null, 0.0f, "cannotrepair", 10.0f));
|
||||
previousCondition = -1;
|
||||
if (goToObjective != null)
|
||||
{
|
||||
subObjectives.Remove(goToObjective);
|
||||
}
|
||||
goToObjective = new AIObjectiveGoTo(Item, character);
|
||||
if (repairTool != null)
|
||||
{
|
||||
//goToObjective.CloseEnough = (HumanAIController.AnimController.ArmLength + ConvertUnits.ToSimUnits(repairTool.Range)) * 0.75f;
|
||||
goToObjective.CloseEnough = ConvertUnits.ToSimUnits(repairTool.Range);
|
||||
}
|
||||
AddSubObjective(goToObjective);
|
||||
}
|
||||
}
|
||||
|
||||
private RepairTool repairTool;
|
||||
private void FindRepairTool()
|
||||
{
|
||||
foreach (Repairable repairable in Item.Repairables)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
@@ -8,25 +7,18 @@ namespace Barotrauma
|
||||
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, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { }
|
||||
public AIObjectiveRepairItems(Character character) : base(character, "") { }
|
||||
|
||||
// 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 FindTargets()
|
||||
{
|
||||
base.FindTargets();
|
||||
if (targets.None() && objectiveManager.CurrentOrder == this)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogNoRepairTargets"), null, 3.0f, "norepairtargets", 30.0f);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void CreateObjectives()
|
||||
{
|
||||
foreach (var item in targets)
|
||||
@@ -46,32 +38,37 @@ namespace Barotrauma
|
||||
|
||||
protected override bool Filter(Item item)
|
||||
{
|
||||
if (item == null) { return false; }
|
||||
if (item.IsFullCondition) { return false; }
|
||||
if (item.CurrentHull == null) { return false; }
|
||||
if (item.Submarine == null) { return false; }
|
||||
if (item.Submarine.TeamID != character.TeamID) { return false; }
|
||||
if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(item, true)) { return false; }
|
||||
if (item.Repairables.None()) { return false; }
|
||||
if (item.CurrentHull.FireSources.Count > 0) { return false; }
|
||||
// Don't repair items in rooms that have enemies inside.
|
||||
if (Character.CharacterList.Any(c => c.CurrentHull == item.CurrentHull && !HumanAIController.IsFriendly(c))) { return false; }
|
||||
foreach (Repairable repairable in item.Repairables)
|
||||
bool ignore = ignoreList.Contains(item) || item.IsFullCondition;
|
||||
if (!ignore)
|
||||
{
|
||||
if (!objectives.ContainsKey(item) && item.Condition > repairable.ShowRepairUIThreshold)
|
||||
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
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (RequireAdequateSkills && !repairable.HasRequiredSkills(character))
|
||||
{
|
||||
return false;
|
||||
if (item.Repairables.None()) { ignore = true; }
|
||||
else
|
||||
{
|
||||
foreach (Repairable repairable in item.Repairables)
|
||||
{
|
||||
if (!objectives.ContainsKey(item) && item.Condition > repairable.ShowRepairUIThreshold)
|
||||
{
|
||||
ignore = true;
|
||||
}
|
||||
else if (RequireAdequateSkills && !repairable.HasRequiredSkills(character))
|
||||
{
|
||||
ignore = true;
|
||||
}
|
||||
if (ignore) { break; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return ignore;
|
||||
}
|
||||
|
||||
protected override float TargetEvaluation() => targets.Max(t => 100 - t.ConditionPercentage);
|
||||
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, objectiveManager, PriorityModifier);
|
||||
protected override AIObjective ObjectiveConstructor(Item item) => new AIObjectiveRepairItem(character, item);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,11 +7,11 @@ using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
// TODO: refactor
|
||||
class AIObjectiveRescue : AIObjective
|
||||
{
|
||||
public override string DebugTag => "rescue";
|
||||
public override bool ForceRun => true;
|
||||
public override bool KeepDivingGearOn => true;
|
||||
|
||||
const float TreatmentDelay = 0.5f;
|
||||
|
||||
@@ -37,8 +37,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public AIObjectiveRescue(Character character, Character targetCharacter, AIObjectiveManager objectiveManager, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
public AIObjectiveRescue(Character character, Character targetCharacter)
|
||||
: base(character, "")
|
||||
{
|
||||
Debug.Assert(character != targetCharacter);
|
||||
this.targetCharacter = targetCharacter;
|
||||
@@ -59,7 +59,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (!character.CanInteractWith(targetCharacter))
|
||||
{
|
||||
AddSubObjective(goToObjective = new AIObjectiveGoTo(targetCharacter, character, objectiveManager));
|
||||
AddSubObjective(goToObjective = new AIObjectiveGoTo(targetCharacter, character));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -68,7 +68,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
AddSubObjective(new AIObjectiveFindSafety(character, objectiveManager));
|
||||
AddSubObjective(new AIObjectiveFindSafety(character));
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -76,7 +76,7 @@ namespace Barotrauma
|
||||
//target not in water -> we can start applying treatment
|
||||
if (!character.CanInteractWith(targetCharacter))
|
||||
{
|
||||
AddSubObjective(goToObjective = new AIObjectiveGoTo(targetCharacter, character, objectiveManager));
|
||||
AddSubObjective(goToObjective = new AIObjectiveGoTo(targetCharacter, character));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -93,7 +93,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: remove and replace with the priority system
|
||||
protected override bool ShouldInterruptSubObjective(AIObjective subObjective)
|
||||
{
|
||||
if (subObjective is AIObjectiveFindSafety)
|
||||
@@ -188,7 +187,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
character.DeselectCharacter();
|
||||
AddSubObjective(new AIObjectiveGetItem(character, suitableItemIdentifiers.ToArray(), objectiveManager, equip: true));
|
||||
AddSubObjective(new AIObjectiveGetItem(character, suitableItemIdentifiers.ToArray(), true));
|
||||
}
|
||||
|
||||
character.AnimController.Anim = AnimController.Animation.CPR;
|
||||
@@ -230,9 +229,8 @@ namespace Barotrauma
|
||||
return isCompleted || targetCharacter.IsDead;
|
||||
}
|
||||
|
||||
public override float GetPriority()
|
||||
public override float GetPriority(AIObjectiveManager objectiveManager)
|
||||
{
|
||||
// TODO: review
|
||||
if (targetCharacter.AnimController.CurrentHull == null || targetCharacter.IsDead) { return 0.0f; }
|
||||
|
||||
Vector2 diff = targetCharacter.WorldPosition - character.WorldPosition;
|
||||
|
||||
@@ -4,19 +4,18 @@ using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class AIObjectiveRescueAll : AIObjectiveLoop<Character>
|
||||
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, AIObjectiveManager objectiveManager, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier) { }
|
||||
|
||||
public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveRescueAll;
|
||||
|
||||
protected override void FindTargets()
|
||||
public AIObjectiveRescueAll(Character character) : base (character, "")
|
||||
{
|
||||
base.FindTargets();
|
||||
if (targets.None() && objectiveManager.CurrentOrder == this)
|
||||
@@ -36,14 +35,39 @@ namespace Barotrauma
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override IEnumerable<Character> GetList() => Character.CharacterList;
|
||||
public override float GetPriority(AIObjectiveManager objectiveManager)
|
||||
{
|
||||
if (character.Submarine == null) { return 0; }
|
||||
GetRescueTargets();
|
||||
if (!rescueTargets.Any()) { return 0.0f; }
|
||||
|
||||
if (objectiveManager.CurrentOrder == this)
|
||||
{
|
||||
return AIObjectiveManager.OrderPriority;
|
||||
}
|
||||
|
||||
protected override AIObjective ObjectiveConstructor(Character target) => new AIObjectiveRescue(character, target, objectiveManager, PriorityModifier);
|
||||
//if there are targets to rescue, the priority is slightly less
|
||||
//than the priority of explicit orders given to the character
|
||||
return AIObjectiveManager.OrderPriority - 5.0f;
|
||||
}
|
||||
|
||||
private void GetRescueTargets()
|
||||
{
|
||||
rescueTargets = Character.CharacterList.FindAll(c =>
|
||||
c.AIController is HumanAIController &&
|
||||
c.TeamID == character.TeamID &&
|
||||
c != character &&
|
||||
!c.IsDead &&
|
||||
c.Vitality / c.MaxVitality < VitalityThreshold);
|
||||
}
|
||||
|
||||
protected override float TargetEvaluation()
|
||||
{
|
||||
// TODO: sorting criteria
|
||||
return 100;
|
||||
}
|
||||
|
||||
public override bool IsCompleted() => false;
|
||||
public override bool CanBeCompleted => true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +64,8 @@ namespace Barotrauma
|
||||
private Order(XElement orderElement)
|
||||
{
|
||||
AITag = orderElement.GetAttributeString("aitag", "");
|
||||
Name = TextManager.Get("OrderName." + AITag, true) ?? "Name not found";
|
||||
Name = TextManager.Get("OrderName." + AITag, true) ?? orderElement.GetAttributeString("name", "Name not found");
|
||||
DoingText = TextManager.Get("OrderNameDoing." + AITag, true) ?? orderElement.GetAttributeString("doingtext", "");
|
||||
|
||||
string targetItemType = orderElement.GetAttributeString("targetitemtype", "");
|
||||
if (!string.IsNullOrWhiteSpace(targetItemType))
|
||||
@@ -126,6 +127,7 @@ namespace Barotrauma
|
||||
|
||||
Name = prefab.Name;
|
||||
AITag = prefab.AITag;
|
||||
DoingText = prefab.DoingText;
|
||||
ItemComponentType = prefab.ItemComponentType;
|
||||
Options = prefab.Options;
|
||||
SymbolSprite = prefab.SymbolSprite;
|
||||
@@ -140,7 +142,8 @@ namespace Barotrauma
|
||||
{
|
||||
if (UseController)
|
||||
{
|
||||
ConnectedController = targetItem.Item.GetConnectedComponents<Controller>().FirstOrDefault();
|
||||
var controllers = targetItem.Item.GetConnectedComponents<Controller>();
|
||||
if (controllers.Count > 0) ConnectedController = controllers[0];
|
||||
}
|
||||
TargetEntity = targetItem.Item;
|
||||
TargetItemComponent = targetItem;
|
||||
|
||||
@@ -233,7 +233,7 @@ namespace Barotrauma
|
||||
//}
|
||||
|
||||
var body = Submarine.PickBody(end, node.Waypoint.SimPosition, null,
|
||||
Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionStairs );
|
||||
Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionStairs | Physics.CollisionPlatform);
|
||||
|
||||
if (body != null)
|
||||
{
|
||||
|
||||
@@ -24,10 +24,7 @@ namespace Barotrauma
|
||||
{
|
||||
public static List<JobPrefab> List;
|
||||
|
||||
public readonly XElement Items;
|
||||
public readonly List<string> ItemNames = new List<string>();
|
||||
public readonly List<SkillPrefab> Skills = new List<SkillPrefab>();
|
||||
public readonly List<AutonomousObjective> AutomaticOrders = new List<AutonomousObjective>();
|
||||
public List<SkillPrefab> Skills;
|
||||
|
||||
[Serialize("1,1,1,1", false)]
|
||||
public Color UIColor
|
||||
@@ -121,6 +118,14 @@ namespace Barotrauma
|
||||
|
||||
public XElement ClothingElement { get; private set; }
|
||||
|
||||
public JobPrefab(XElement element)
|
||||
{
|
||||
SerializableProperty.DeserializeProperties(this, element);
|
||||
Name = TextManager.Get("JobName." + Identifier);
|
||||
Description = TextManager.Get("JobDescription." + Identifier);
|
||||
|
||||
public XElement ClothingElement { get; private set; }
|
||||
|
||||
public JobPrefab(XElement element)
|
||||
{
|
||||
SerializableProperty.DeserializeProperties(this, element);
|
||||
|
||||
@@ -173,8 +173,8 @@ namespace Barotrauma
|
||||
var spawnedCharacter = Character.Create(characterInfo, watchmanSpawnpoint.WorldPosition,
|
||||
Level.Loaded.Seed + (outpost == Level.Loaded.StartOutpost ? "start" : "end"));
|
||||
InitializeWatchman(spawnedCharacter);
|
||||
var objectiveManager = (spawnedCharacter.AIController as HumanAIController)?.ObjectiveManager;
|
||||
objectiveManager?.SetOrder(new AIObjectiveGoTo(watchmanSpawnpoint, spawnedCharacter, objectiveManager, repeat: true, getDivingGearIfNeeded: false));
|
||||
(spawnedCharacter.AIController as HumanAIController)?.ObjectiveManager.SetOrder(
|
||||
new AIObjectiveGoTo(watchmanSpawnpoint, spawnedCharacter, repeat: true, getDivingGearIfNeeded: false));
|
||||
if (watchmanJob != null)
|
||||
{
|
||||
spawnedCharacter.GiveJobItems();
|
||||
|
||||
@@ -26,8 +26,6 @@ namespace Barotrauma.Items.Components
|
||||
private bool autoOrientGap;
|
||||
|
||||
private bool isStuck;
|
||||
public bool IsStuck => isStuck;
|
||||
|
||||
private float resetPredictionTimer;
|
||||
|
||||
private Rectangle doorRect;
|
||||
@@ -229,7 +227,8 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
msg = msg ?? (HasIntegratedButtons ? accessDeniedTxt : cannotOpenText);
|
||||
}
|
||||
return isBroken || base.HasRequiredItems(character, addMessage, msg);
|
||||
if (isBroken) { return true; }
|
||||
return base.HasRequiredItems(character, addMessage, msg);
|
||||
}
|
||||
|
||||
public override bool Pick(Character picker)
|
||||
|
||||
@@ -281,7 +281,7 @@ namespace Barotrauma.Items.Components
|
||||
float dist = fromItemToLeak.Length();
|
||||
|
||||
//too far away -> consider this done and hope the AI is smart enough to move closer
|
||||
if (dist > Range * 3.0f) { return true; }
|
||||
if (dist > Range * 5.0f) return true;
|
||||
|
||||
// TODO: use the collider size?
|
||||
if (!character.AnimController.InWater && character.AnimController is HumanoidAnimController &&
|
||||
@@ -344,19 +344,7 @@ namespace Barotrauma.Items.Components
|
||||
character.CursorPosition = leak.Position + VectorExtensions.Forward(Item.body.TransformedRotation + (float)Math.Sin(sinTime), dist);
|
||||
if (item.RequireAimToUse)
|
||||
{
|
||||
bool isOperatingButtons = false;
|
||||
if (character.AIController.SteeringManager is IndoorsSteeringManager indoorSteering)
|
||||
{
|
||||
var door = indoorSteering.CurrentPath?.CurrentNode?.ConnectedDoor;
|
||||
if (door != null && !door.IsOpen)
|
||||
{
|
||||
isOperatingButtons = door.HasIntegratedButtons || door.Item.GetConnectedComponents<Controller>(true).Any();
|
||||
}
|
||||
}
|
||||
if (!isOperatingButtons)
|
||||
{
|
||||
character.SetInput(InputType.Aim, false, true);
|
||||
}
|
||||
character.SetInput(InputType.Aim, false, true);
|
||||
}
|
||||
// Press the trigger only when the tool is approximately facing the target.
|
||||
var angle = VectorExtensions.Angle(VectorExtensions.Forward(item.body.TransformedRotation), fromItemToLeak);
|
||||
@@ -412,8 +400,7 @@ namespace Barotrauma.Items.Components
|
||||
object value = property.GetValue(target);
|
||||
if (value.GetType() == typeof(float))
|
||||
{
|
||||
var progressBar = user.UpdateHUDProgressBar(door, door.Item.WorldPosition, (float)value / 100, Color.DarkGray * 0.5f, Color.White);
|
||||
if (progressBar != null) { progressBar.Size = new Vector2(60.0f, 20.0f); }
|
||||
user.UpdateHUDProgressBar(door, door.Item.WorldPosition, (float)value / 100, Color.DarkGray * 0.5f, Color.White);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -715,6 +715,25 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
if (targetItem.Prefab.DeconstructItems.Any())
|
||||
{
|
||||
inputContainer.Inventory.RemoveItem(targetItem);
|
||||
Entity.Spawner.AddToRemoveQueue(targetItem);
|
||||
MoveInputQueue();
|
||||
PutItemsToLinkedContainer();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (outputContainer.Inventory.Items.All(i => i != null))
|
||||
{
|
||||
targetItem.Drop(dropper: null);
|
||||
}
|
||||
else
|
||||
{
|
||||
outputContainer.Inventory.TryPutItem(targetItem, user: null, createNetworkEvent: true);
|
||||
}
|
||||
}
|
||||
|
||||
if (targetItem.Prefab.DeconstructItems.Any())
|
||||
{
|
||||
inputContainer.Inventory.RemoveItem(targetItem);
|
||||
|
||||
@@ -499,7 +499,7 @@ namespace Barotrauma.Items.Components
|
||||
//load more fuel if the current maximum output is only 50% of the current load
|
||||
if (NeedMoreFuel(minimumOutputRatio: 0.5f))
|
||||
{
|
||||
var containFuelObjective = new AIObjectiveContainItem(character, new string[] { "fuelrod", "reactorfuel" }, item.GetComponent<ItemContainer>(), objective.objectiveManager)
|
||||
var containFuelObjective = new AIObjectiveContainItem(character, new string[] { "fuelrod", "reactorfuel" }, item.GetComponent<ItemContainer>())
|
||||
{
|
||||
MinContainedAmount = item.ContainedItems.Count(i => i != null && i.Prefab.Identifier == "fuelrod" || i.HasTag("reactorfuel")) + 1,
|
||||
GetItemPriority = (Item fuelItem) =>
|
||||
|
||||
@@ -212,33 +212,6 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2? PosToMaintain
|
||||
{
|
||||
get { return posToMaintain; }
|
||||
set { posToMaintain = value; }
|
||||
}
|
||||
|
||||
struct ObstacleDebugInfo
|
||||
{
|
||||
public Vector2 Point1;
|
||||
public Vector2 Point2;
|
||||
|
||||
public Vector2? Intersection;
|
||||
|
||||
public float Dot;
|
||||
|
||||
public Vector2 AvoidStrength;
|
||||
|
||||
public ObstacleDebugInfo(GraphEdge edge, Vector2? intersection, float dot, Vector2 avoidStrength)
|
||||
{
|
||||
Point1 = edge.Point1;
|
||||
Point2 = edge.Point2;
|
||||
Intersection = intersection;
|
||||
Dot = dot;
|
||||
AvoidStrength = avoidStrength;
|
||||
}
|
||||
}
|
||||
|
||||
//edge point 1, edge point 2, avoid strength
|
||||
private List<ObstacleDebugInfo> debugDrawObstacles = new List<ObstacleDebugInfo>();
|
||||
|
||||
|
||||
@@ -235,12 +235,12 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (string.IsNullOrEmpty(objective.Option) || objective.Option.ToLowerInvariant() == "charge")
|
||||
{
|
||||
if (Math.Abs(rechargeSpeed - maxRechargeSpeed * aiRechargeTargetRatio) > 0.05f)
|
||||
if (Math.Abs(rechargeSpeed - maxRechargeSpeed * 0.5f) > 0.05f)
|
||||
{
|
||||
#if SERVER
|
||||
item.CreateServerEvent(this);
|
||||
#endif
|
||||
RechargeSpeed = maxRechargeSpeed * aiRechargeTargetRatio;
|
||||
RechargeSpeed = maxRechargeSpeed * 0.5f;
|
||||
#if CLIENT
|
||||
rechargeSpeedSlider.BarScroll = RechargeSpeed / Math.Max(maxRechargeSpeed, 1.0f);
|
||||
#endif
|
||||
|
||||
@@ -415,7 +415,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (batteryToLoad.RechargeSpeed < batteryToLoad.MaxRechargeSpeed * 0.4f)
|
||||
{
|
||||
objective.AddSubObjective(new AIObjectiveOperateItem(batteryToLoad, character, objective.objectiveManager, option: "", requireEquip: false));
|
||||
objective.AddSubObjective(new AIObjectiveOperateItem(batteryToLoad, character, "", false));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -454,11 +454,11 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (container.Inventory.Items[0] != null && container.Inventory.Items[0].Condition <= 0.0f)
|
||||
{
|
||||
var removeShellObjective = new AIObjectiveDecontainItem(character, container.Inventory.Items[0], container, objective.objectiveManager);
|
||||
var removeShellObjective = new AIObjectiveDecontainItem(character, container.Inventory.Items[0], container);
|
||||
objective.AddSubObjective(removeShellObjective);
|
||||
}
|
||||
|
||||
var containShellObjective = new AIObjectiveContainItem(character, container.ContainableItems[0].Identifiers[0], container, objective.objectiveManager);
|
||||
var containShellObjective = new AIObjectiveContainItem(character, container.ContainableItems[0].Identifiers[0], container);
|
||||
character?.Speak(TextManager.Get("DialogLoadTurret").Replace("[itemname]", item.Name), null, 0.0f, "loadturret", 30.0f);
|
||||
containShellObjective.MinContainedAmount = usableProjectileCount + 1;
|
||||
containShellObjective.ignoredContainerIdentifiers = new string[] { containerItem.prefab.Identifier };
|
||||
|
||||
@@ -1552,6 +1552,10 @@ namespace Barotrauma
|
||||
{
|
||||
ApplyStatusEffects(!waterProof && inWater ? ActionType.InWater : ActionType.NotInWater, deltaTime);
|
||||
}
|
||||
if (!broken)
|
||||
{
|
||||
ApplyStatusEffects(!waterProof && inWater ? ActionType.InWater : ActionType.NotInWater, deltaTime);
|
||||
}
|
||||
ApplyStatusEffects(!waterProof && inWater ? ActionType.InWater : ActionType.NotInWater, deltaTime);
|
||||
|
||||
if (body == null || !body.Enabled || !inWater || ParentInventory != null || Removed) { return; }
|
||||
@@ -1688,9 +1692,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Note: This function generates garbage and might be a bit too heavy to be used once per frame.
|
||||
/// </summary>
|
||||
public List<T> GetConnectedComponents<T>(bool recursive = false) where T : ItemComponent
|
||||
{
|
||||
List<T> connectedComponents = new List<T>();
|
||||
@@ -1743,9 +1744,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Note: This function generates garbage and might be a bit too heavy to be used once per frame.
|
||||
/// </summary>
|
||||
public List<T> GetConnectedComponentsRecursive<T>(Connection c) where T : ItemComponent
|
||||
{
|
||||
List<T> connectedComponents = new List<T>();
|
||||
@@ -2084,6 +2082,29 @@ namespace Barotrauma
|
||||
if (remove) { Spawner?.AddToRemoveQueue(this); }
|
||||
}
|
||||
|
||||
List<ColoredText> texts = new List<ColoredText>();
|
||||
public List<ColoredText> GetHUDTexts(Character character)
|
||||
{
|
||||
texts.Clear();
|
||||
foreach (ItemComponent ic in components)
|
||||
{
|
||||
if (string.IsNullOrEmpty(ic.DisplayMsg)) continue;
|
||||
if (!ic.CanBePicked && !ic.CanBeSelected) continue;
|
||||
if (ic is Holdable holdable && !holdable.CanBeDeattached()) continue;
|
||||
|
||||
Color color = Color.Gray;
|
||||
bool hasRequiredSkillsAndItems = ic.HasRequiredSkills(character) && ic.HasRequiredItems(character, false);
|
||||
if (hasRequiredSkillsAndItems)
|
||||
{
|
||||
color = Color.Cyan;
|
||||
}
|
||||
|
||||
texts.Add(new ColoredText(ic.DisplayMsg, color, false));
|
||||
}
|
||||
|
||||
if (remove) { Spawner?.AddToRemoveQueue(this); }
|
||||
}
|
||||
|
||||
public bool Combine(Item item)
|
||||
{
|
||||
bool isCombined = false;
|
||||
|
||||
@@ -436,6 +436,25 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public string DisplayName
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
private string roomName;
|
||||
[Editable, Serialize("", true, translationTextTag: "RoomName.")]
|
||||
public string RoomName
|
||||
{
|
||||
get { return roomName; }
|
||||
set
|
||||
{
|
||||
if (roomName == value) { return; }
|
||||
roomName = value;
|
||||
DisplayName = TextManager.Get(roomName, returnNull: true) ?? roomName;
|
||||
}
|
||||
}
|
||||
|
||||
public override Rectangle Rect
|
||||
{
|
||||
get
|
||||
|
||||
@@ -109,6 +109,11 @@ namespace Barotrauma
|
||||
if (!ParseTexturePath(path, file)) { return; }
|
||||
Name = SourceElement.GetAttributeString("name", null);
|
||||
Vector4 sourceVector = SourceElement.GetAttributeVector4("sourcerect", Vector4.Zero);
|
||||
var overrideElement = GetLocalizationOverrideElement();
|
||||
if (overrideElement != null && overrideElement.Attribute("sourcerect") != null)
|
||||
{
|
||||
sourceVector = overrideElement.GetAttributeVector4("sourcerect", Vector4.Zero);
|
||||
}
|
||||
preMultipliedAlpha = preMultiplyAlpha ?? SourceElement.GetAttributeBool("premultiplyalpha", true);
|
||||
bool shouldReturn = false;
|
||||
if (!lazyLoad)
|
||||
@@ -240,8 +245,12 @@ namespace Barotrauma
|
||||
}
|
||||
if (SourceElement != null)
|
||||
{
|
||||
Vector4 sourceVector = SourceElement.GetAttributeVector4("sourcerect", Vector4.Zero);
|
||||
sourceRect = new Rectangle((int)sourceVector.X, (int)sourceVector.Y, (int)sourceVector.Z, (int)sourceVector.W);
|
||||
sourceRect = SourceElement.GetAttributeRect("sourcerect", Rectangle.Empty);
|
||||
var overrideElement = GetLocalizationOverrideElement();
|
||||
if (overrideElement != null && overrideElement.Attribute("sourcerect") != null)
|
||||
{
|
||||
sourceRect = overrideElement.GetAttributeRect("sourcerect", Rectangle.Empty);
|
||||
}
|
||||
size = SourceElement.GetAttributeVector2("size", Vector2.One);
|
||||
size.X *= sourceRect.Width;
|
||||
size.Y *= sourceRect.Height;
|
||||
@@ -256,6 +265,12 @@ namespace Barotrauma
|
||||
if (file == "")
|
||||
{
|
||||
file = SourceElement.GetAttributeString("texture", "");
|
||||
var overrideElement = GetLocalizationOverrideElement();
|
||||
if (overrideElement != null)
|
||||
{
|
||||
string overrideFile = overrideElement.GetAttributeString("texture", "");
|
||||
if (!string.IsNullOrEmpty(overrideFile)) { file = overrideFile; }
|
||||
}
|
||||
}
|
||||
if (file == "")
|
||||
{
|
||||
@@ -273,6 +288,22 @@ namespace Barotrauma
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private XElement GetLocalizationOverrideElement()
|
||||
{
|
||||
foreach (XElement subElement in SourceElement.Elements())
|
||||
{
|
||||
if (subElement.Name.ToString().ToLowerInvariant() == "override")
|
||||
{
|
||||
string language = subElement.GetAttributeString("language", "");
|
||||
if (TextManager.Language.ToLower() == language.ToLower())
|
||||
{
|
||||
return subElement;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,14 +16,6 @@ namespace Barotrauma
|
||||
private static string[] serverMessageCharacters = new string[] { "~", "[", "]", "=" };
|
||||
|
||||
public static string Language;
|
||||
public static bool NoWhiteSpace
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!textPacks.ContainsKey(Language)) { return false; }
|
||||
return textPacks[Language].Any(t => t.NoWhiteSpace);
|
||||
}
|
||||
}
|
||||
|
||||
private static HashSet<string> availableLanguages = new HashSet<string>();
|
||||
public static IEnumerable<string> AvailableLanguages
|
||||
@@ -87,7 +79,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public static string Get(string textTag, bool returnNull = false)
|
||||
public static string Get(string textTag, bool returnNull = false, string fallBackTag = null)
|
||||
{
|
||||
if (!textPacks.ContainsKey(Language))
|
||||
{
|
||||
@@ -102,7 +94,16 @@ namespace Barotrauma
|
||||
foreach (TextPack textPack in textPacks[Language])
|
||||
{
|
||||
string text = textPack.Get(textTag);
|
||||
if (text != null) return text;
|
||||
if (text != null) { return text; }
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(fallBackTag))
|
||||
{
|
||||
foreach (TextPack textPack in textPacks[Language])
|
||||
{
|
||||
string text = textPack.Get(fallBackTag);
|
||||
if (text != null) { return text; }
|
||||
}
|
||||
}
|
||||
|
||||
//if text was not found and we're using a language other than English, see if we can find an English version
|
||||
|
||||
@@ -11,9 +11,7 @@ namespace Barotrauma
|
||||
public readonly string Language;
|
||||
|
||||
private Dictionary<string, List<string>> texts;
|
||||
|
||||
public readonly bool NoWhiteSpace;
|
||||
|
||||
|
||||
private readonly string filePath;
|
||||
|
||||
public TextPack(string filePath)
|
||||
@@ -25,7 +23,6 @@ namespace Barotrauma
|
||||
if (doc == null || doc.Root == null) return;
|
||||
|
||||
Language = doc.Root.GetAttributeString("language", "Unknown");
|
||||
NoWhiteSpace = doc.Root.GetAttributeBool("nowhitespace", false);
|
||||
|
||||
foreach (XElement subElement in doc.Root.Elements())
|
||||
{
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user