v0.11.0.9

This commit is contained in:
Joonas Rikkonen
2020-12-09 16:34:16 +02:00
parent bbf06f0984
commit f433a7ba10
325 changed files with 13947 additions and 3652 deletions
@@ -0,0 +1,49 @@
#nullable enable
namespace Barotrauma.MapCreatures.Behavior
{
class BallastFloraStateMachine
{
private readonly BallastFloraBehavior parent;
public BallastFloraStateMachine(BallastFloraBehavior parent)
{
this.parent = parent;
}
private IBallastFloraState? lastState;
public IBallastFloraState? State;
public void EnterState(IBallastFloraState newState)
{
lastState = State;
State?.Exit();
newState.Enter();
State = newState;
}
public void Update(float deltaTime)
{
if (State == null)
{
EnterState(new GrowIdleState(parent));
return;
}
State.Update(deltaTime);
switch (State.GetState())
{
case ExitState.Running:
break;
case ExitState.ReturnLast when lastState != null && lastState.GetState() == ExitState.Running:
EnterState(lastState);
break;
default:
EnterState(new GrowIdleState(parent));
break;
}
}
}
}
@@ -0,0 +1,126 @@
using Barotrauma.Items.Components;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma.MapCreatures.Behavior
{
class DefendWithPumpState : IBallastFloraState
{
private readonly BallastFloraBranch targetBranch;
private readonly List<Pump> allAvailablePumps = new List<Pump>();
private readonly List<Door> allAvailableDoors = new List<Door>();
private readonly List<Pump> targetPumps = new List<Pump>();
private readonly List<Door> jammedDoors = new List<Door>();
private bool isFinished;
private float timer = 10f;
private bool filled;
private bool tryDrown;
private readonly Character attacker;
public DefendWithPumpState(BallastFloraBranch branch, List<Item> items, Character attacker)
{
targetBranch = branch;
this.attacker = attacker;
foreach (Item item in items)
{
if (item.GetComponent<Pump>() is { } pump)
{
allAvailablePumps.Add(pump);
}
if (item.GetComponent<Door>() is { } door)
{
allAvailableDoors.Add(door);
}
}
}
public ExitState GetState()
{
if (isFinished) { return ExitState.ReturnLast; }
if (targetBranch.CurrentHull == null) { return ExitState.ReturnLast; }
return timer < 0 ? ExitState.ReturnLast : ExitState.Running;
}
public void Enter()
{
foreach (Pump pump in allAvailablePumps)
{
if (pump.Item.CurrentHull == targetBranch.CurrentHull)
{
targetPumps.Add(pump);
SetPump(pump);
pump.Hijacked = true;
}
}
if (!targetPumps.Any() || targetPumps.All(p => !p.HasPower))
{
isFinished = true;
}
// lock the doors if the attacker is in the same hull as the ballast flora to try to drown them
if (targetBranch.CurrentHull != null && attacker != null && attacker.CurrentHull == targetBranch.CurrentHull)
{
foreach (Door door in allAvailableDoors)
{
if (door.LinkedGap != null && door.LinkedGap.linkedTo.Contains(targetBranch.CurrentHull))
{
door.TrySetState(false, false, true);
door.IsJammed = true;
jammedDoors.Add(door);
}
}
tryDrown = true;
}
}
public void Exit()
{
foreach (Pump pump in targetPumps)
{
pump.Hijacked = false;
}
foreach (Door door in jammedDoors)
{
door.IsJammed = false;
}
}
private void SetPump(Pump pump)
{
if (pump.TargetLevel != null)
{
pump.TargetLevel = 100f;
}
else
{
pump.FlowPercentage = 100f;
}
}
public void Update(float deltaTime)
{
foreach (Pump pump in targetPumps)
{
SetPump(pump);
}
if (tryDrown && !filled)
{
// keep the ballast filled for extra 10 seconds
if (targetBranch.CurrentHull == null || targetBranch.CurrentHull.WaterPercentage >= 95f)
{
filled = true;
timer += 10f;
}
}
timer -= deltaTime;
}
}
}
@@ -0,0 +1,187 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using FarseerPhysics;
using FarseerPhysics.Dynamics;
using Microsoft.Xna.Framework;
namespace Barotrauma.MapCreatures.Behavior
{
internal class GrowIdleState: IBallastFloraState
{
public readonly BallastFloraBehavior Behavior;
private float growthTimer;
public GrowIdleState(BallastFloraBehavior behavior)
{
Behavior = behavior;
}
public virtual ExitState GetState() => ExitState.Running;
public virtual void Enter()
{
foreach (BallastFloraBranch branch in Behavior.Branches.Where(b => b.CanGrowMore()))
{
if (TryScanTargets(branch)) { return; }
}
}
public void Exit() { }
private bool TryScanTargets(BallastFloraBranch branch)
{
if (ScanForTargets(branch) is { } newTarget)
{
Behavior.StateMachine.EnterState(new GrowToTargetState(Behavior, branch, newTarget));
return true;
}
return false;
}
public void Update(float deltaTime)
{
if (growthTimer > 0)
{
growthTimer -= Behavior.GetGrowthSpeed(deltaTime);
}
else
{
Grow();
UpdateIgnoredTargets();
growthTimer = Behavior.GrowthWarps > 0 ? 0f : 5f;
}
}
protected virtual void Grow()
{
List<BallastFloraBranch> newTiles = GrowRandomly();
#if DEBUG || UNSTABLE
Behavior.debugSearchLines.Clear();
#endif
if (newTiles.Any(TryScanTargets)) { return; }
}
public void UpdateIgnoredTargets()
{
Behavior.IgnoredTargets.ForEachMod(pair =>
{
var (item, delay) = pair;
if (delay <= 0)
{
Behavior.IgnoredTargets.Remove(item);
}
else
{
Behavior.IgnoredTargets[item] = --delay;
}
});
}
private List<BallastFloraBranch> GrowRandomly()
{
List<BallastFloraBranch> newBranches = new List<BallastFloraBranch>();
List<BallastFloraBranch> newList = new List<BallastFloraBranch>(Behavior.Branches);
foreach (BallastFloraBranch branch in newList)
{
if (branch.FailedGrowthAttempts > 8 || !branch.CanGrowMore()) { continue; }
if (Rand.Range(0, Behavior.Branches.Count(tile => tile.CanGrowMore())) != 0) { continue; }
TileSide side = branch.GetRandomFreeSide();
if (side == TileSide.None) { continue; }
Behavior.TryGrowBranch(branch, side, out List<BallastFloraBranch> result);
newBranches.AddRange(result);
}
return newBranches;
}
private Item? ScanForTargets(VineTile branch)
{
Hull parent = Behavior.Parent;
Vector2 worldPos = Behavior.GetWorldPosition() + branch.Position;
Vector2 pos = parent.Position + Behavior.Offset + branch.Position;
Vector2 diameter = ConvertUnits.ToSimUnits(new Vector2(branch.Rect.Width / 2f, branch.Rect.Height / 2f));
Vector2 topLeft = ConvertUnits.ToSimUnits(pos) - diameter;
Vector2 bottomRight = ConvertUnits.ToSimUnits(pos) + diameter;
int highestPriority = 0;
Item? currentItem = null;
foreach (Item item in Item.ItemList.Where(it => !Behavior.ClaimedTargets.Contains(it)))
{
if (Behavior.IgnoredTargets.ContainsKey(item)) { continue; }
int priority = 0;
foreach (BallastFloraBehavior.AITarget target in Behavior.Targets)
{
if (!target.Matches(item) || target.Priority <= highestPriority) { continue; }
priority = target.Priority;
break;
}
if (priority == 0) { continue; }
if (item.Submarine != parent.Submarine || Vector2.Distance(worldPos, item.WorldPosition) > Behavior.Sight) { continue; }
Vector2 itemSimPos = ConvertUnits.ToSimUnits(item.Position);
#if DEBUG || UNSTABLE
Tuple<Vector2, Vector2> debugLine1 = Tuple.Create(parent.Position - ConvertUnits.ToDisplayUnits(topLeft), parent.Position - ConvertUnits.ToDisplayUnits(itemSimPos - diameter));
Tuple<Vector2, Vector2> debugLine2 = Tuple.Create(parent.Position - ConvertUnits.ToDisplayUnits(bottomRight), parent.Position - ConvertUnits.ToDisplayUnits(itemSimPos + diameter));
Behavior.debugSearchLines.Add(debugLine2);
Behavior.debugSearchLines.Add(debugLine1);
#endif
Body? body1 = Submarine.CheckVisibility(itemSimPos - diameter, topLeft);
if (Blocks(body1, item)) { continue; }
Body? body2 = Submarine.CheckVisibility(itemSimPos + diameter, bottomRight);
if (Blocks(body2, item)) { continue; }
highestPriority = priority;
currentItem = item;
}
if (currentItem != null)
{
foreach (BallastFloraBranch existingBranch in Behavior.Branches)
{
if (Behavior.BranchContainsTarget(existingBranch, currentItem))
{
Behavior.ClaimTarget(currentItem, existingBranch);
return null;
}
}
return currentItem;
}
return null;
static bool Blocks(Body body, Item target)
{
if (body == null) { return false; }
switch (body.UserData)
{
case Submarine _:
case Structure _:
case Item it when it != target:
return true;
default:
return false;
}
}
}
}
}
@@ -0,0 +1,86 @@
using System;
using System.Collections.Generic;
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
namespace Barotrauma.MapCreatures.Behavior
{
class GrowToTargetState: GrowIdleState
{
public readonly List<BallastFloraBranch> TargetBranches = new List<BallastFloraBranch>();
public readonly Item Target;
private bool isFinished;
public GrowToTargetState(BallastFloraBehavior behavior, BallastFloraBranch starter, Item target) : base(behavior)
{
Target = target;
TargetBranches.Add(starter);
}
// do nothing
public override void Enter() { }
public override ExitState GetState() => isFinished ? ExitState.Terminate : ExitState.Running;
protected override void Grow()
{
if (Target == null || Target.Removed)
{
isFinished = true;
return;
}
GrowTowardsTarget();
}
private void GrowTowardsTarget()
{
bool succeeded = false;
List<BallastFloraBranch> newList = new List<BallastFloraBranch>(TargetBranches);
foreach (BallastFloraBranch branch in newList)
{
if (branch.FailedGrowthAttempts > 8 || !branch.CanGrowMore()) { continue; }
// Get what side gets us closest to the target
TileSide side = GetClosestSide(branch, Target.WorldPosition);
if (branch.IsSideBlocked(side)) { continue; }
succeeded |= Behavior.TryGrowBranch(branch, side, out List<BallastFloraBranch> newBranches);
TargetBranches.AddRange(newBranches);
foreach (BallastFloraBranch newBranch in newBranches)
{
Rectangle worldRect = newBranch.Rect;
worldRect.Location = Behavior.GetWorldPosition().ToPoint() + worldRect.Location;
if (Behavior.BranchContainsTarget(newBranch, Target))
{
Behavior.ClaimTarget(Target, newBranch);
isFinished = true;
return;
}
}
}
if (!succeeded)
{
if (!Behavior.IgnoredTargets.ContainsKey(Target))
{
Behavior.IgnoredTargets.Add(Target, 1);
}
isFinished = true;
}
}
private TileSide GetClosestSide(VineTile tile, Vector2 targetPos)
{
var (distX, distY) = tile.Position + Behavior.GetWorldPosition() - targetPos;
int absDistX = (int) Math.Abs(distX), absDistY = (int) Math.Abs(distY);
return absDistX > absDistY ? distX > 0 ? TileSide.Left : TileSide.Right : distY > 0 ? TileSide.Bottom : TileSide.Top;
}
}
}
@@ -0,0 +1,21 @@
#if CLIENT
using Microsoft.Xna.Framework.Graphics;
#endif
namespace Barotrauma.MapCreatures.Behavior
{
enum ExitState
{
Running, // State is running
Terminate, // State has exited
ReturnLast // Return to the last running state if any
}
interface IBallastFloraState
{
public void Enter();
public void Exit();
public void Update(float deltaTime);
public ExitState GetState();
}
}