Merge branch 'dedicated-server' (TODO: make sure I didn't break anything)

Conflicts:
	Barotrauma/Barotrauma.csproj
	Barotrauma/BarotraumaShared/Source/Characters/AI/AIController.cs
	Barotrauma/BarotraumaShared/Source/Characters/AI/EnemyAIController.cs
	Barotrauma/BarotraumaShared/Source/Characters/AICharacter.cs
	Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs
	Barotrauma/BarotraumaShared/Source/Characters/Attack.cs
	Barotrauma/BarotraumaShared/Source/Characters/Character.cs
	Barotrauma/BarotraumaShared/Source/Characters/CharacterNetworking.cs
	Barotrauma/BarotraumaShared/Source/Characters/Limb.cs
	Barotrauma/BarotraumaShared/Source/Events/MonsterEvent.cs
	Barotrauma/BarotraumaShared/Source/Map/Explosion.cs
This commit is contained in:
Joonas Rikkonen
2017-06-30 21:02:52 +03:00
1311 changed files with 22015 additions and 91216 deletions
@@ -0,0 +1,67 @@
using Lidgren.Network;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma
{
partial class AIController : ISteerable
{
public enum AIState { None, Attack, GoTo, Escape, Eat }
public bool Enabled;
public readonly Character Character;
protected AIState state;
protected SteeringManager steeringManager;
public SteeringManager SteeringManager
{
get { return steeringManager; }
}
public Vector2 Steering
{
get { return Character.AnimController.TargetMovement; }
set { Character.AnimController.TargetMovement = value; }
}
public Vector2 SimPosition
{
get { return Character.SimPosition; }
}
public Vector2 WorldPosition
{
get { return Character.WorldPosition; }
}
public Vector2 Velocity
{
get { return Character.AnimController.Collider.LinearVelocity; }
}
public AIState State
{
get { return state; }
set { state = value; }
}
public AIController (Character c)
{
Character = c;
Enabled = true;
}
public virtual void OnAttacked(IDamageable attacker, float amount) { }
public virtual void SelectTarget(AITarget target) { }
public virtual void Update(float deltaTime) { }
//protected Structure lastStructurePicked;
}
}
@@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma
{
partial class AITarget
{
public static List<AITarget> List = new List<AITarget>();
public Entity Entity
{
get;
private set;
}
private float soundRange;
private float sightRange;
public float SoundRange
{
get { return soundRange; }
set { soundRange = Math.Max(value, 0.0f); }
}
public float SightRange
{
get { return sightRange; }
set { sightRange = Math.Max(value, 0.0f); }
}
public Vector2 WorldPosition
{
get { return Entity.WorldPosition; }
}
public Vector2 SimPosition
{
get { return Entity.SimPosition; }
}
public AITarget(Entity e)
{
Entity = e;
List.Add(this);
}
public void Remove()
{
List.Remove(this);
Entity = null;
}
}
}
@@ -0,0 +1,750 @@
using System;
using System.Collections.Generic;
using System.Xml.Linq;
using FarseerPhysics;
using Microsoft.Xna.Framework;
using FarseerPhysics.Dynamics;
using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma
{
partial class EnemyAIController : AIController
{
private const float UpdateTargetsInterval = 0.5f;
private const float RaycastInterval = 1.0f;
private bool attackWhenProvoked;
//the preference to attack a specific type of target (-1.0 - 1.0)
//0.0 = doesn't attack targets of the type
//positive values = attacks targets of this type
//negative values = escapes targets of this type
private float attackRooms, attackHumans, attackWeaker, attackStronger, eatDeadPriority;
//determines which characters are considered weaker/stronger
private float combatStrength;
private SteeringManager outsideSteering, insideSteering;
private float updateTargetsTimer;
private float raycastTimer;
//a "cooldown time" after an attack during which the Character doesn't try to attack again
private float attackCoolDown;
private float coolDownTimer;
private bool attachToWalls;
//a point in a wall which the Character is currently targeting
private Vector2 wallAttackPos;
//the entity (a wall) which the Character is targeting
private IDamageable targetEntity;
//the limb selected for the current attack
private Limb attackingLimb;
//flee when the health is below this value
private float fleeHealthThreshold;
private AITarget selectedAiTarget;
private AITargetMemory selectedTargetMemory;
private float targetValue;
private float eatTimer;
private Dictionary<AITarget, AITargetMemory> targetMemories;
//the eyesight of the NPC (0.0 = blind, 1.0 = sees every target within sightRange)
private float sight;
//how far the NPC can hear targets from (0.0 = deaf, 1.0 = hears every target within soundRange)
private float hearing;
public AITarget SelectedAiTarget
{
get { return selectedAiTarget; }
}
public EnemyAIController(Character c, string file) : base(c)
{
targetMemories = new Dictionary<AITarget, AITargetMemory>();
XDocument doc = ToolBox.TryLoadXml(file);
if (doc == null || doc.Root == null) return;
XElement aiElement = doc.Root.Element("ai");
if (aiElement == null) return;
attackRooms = ToolBox.GetAttributeFloat(aiElement, 0.0f, "attackrooms", "attackpriorityrooms") / 100.0f;
attackHumans = ToolBox.GetAttributeFloat(aiElement, 0.0f, "attackhumans", "attackpriorityhumans") / 100.0f;
attackWeaker = ToolBox.GetAttributeFloat(aiElement, 0.0f, "attackweaker", "attackpriorityweaker") / 100.0f;
attackStronger = ToolBox.GetAttributeFloat(aiElement, 0.0f, "attackstronger", "attackprioritystronger") / 100.0f;
eatDeadPriority = ToolBox.GetAttributeFloat(aiElement, "eatpriority", 0.0f) / 100.0f;
combatStrength = ToolBox.GetAttributeFloat(aiElement, "combatstrength", 1.0f);
attackCoolDown = ToolBox.GetAttributeFloat(aiElement, "attackcooldown", 5.0f);
sight = ToolBox.GetAttributeFloat(aiElement, "sight", 0.0f);
hearing = ToolBox.GetAttributeFloat(aiElement, "hearing", 0.0f);
attackWhenProvoked = ToolBox.GetAttributeBool(aiElement, "attackwhenprovoked", false);
fleeHealthThreshold = ToolBox.GetAttributeFloat(aiElement, "fleehealththreshold", 0.0f);
attachToWalls = ToolBox.GetAttributeBool(aiElement, "attachtowalls", false);
outsideSteering = new SteeringManager(this);
insideSteering = new IndoorsSteeringManager(this, false);
steeringManager = outsideSteering;
state = AIState.None;
}
public override void SelectTarget(AITarget target)
{
selectedAiTarget = target;
selectedTargetMemory = FindTargetMemory(target);
targetValue = 100.0f;
}
public override void Update(float deltaTime)
{
bool ignorePlatforms = (-Character.AnimController.TargetMovement.Y > Math.Abs(Character.AnimController.TargetMovement.X));
if (steeringManager is IndoorsSteeringManager)
{
var currPath = ((IndoorsSteeringManager)steeringManager).CurrentPath;
if (currPath != null && currPath.CurrentNode != null)
{
if (currPath.CurrentNode.SimPosition.Y < Character.AnimController.GetColliderBottom().Y)
{
ignorePlatforms = true;
}
}
}
Character.AnimController.IgnorePlatforms = ignorePlatforms;
if (Character.AnimController is HumanoidAnimController)
{
if (Math.Abs(Character.AnimController.movement.X) > 0.1f && !Character.AnimController.InWater)
{
Character.AnimController.TargetDir = Character.AnimController.movement.X > 0.0f ? Direction.Right : Direction.Left;
}
}
if (updateTargetsTimer > 0.0)
{
updateTargetsTimer -= deltaTime;
}
else
{
UpdateTargets(Character);
updateTargetsTimer = UpdateTargetsInterval;
if (selectedAiTarget == null)
{
state = AIState.None;
}
else if ((selectedAiTarget.Entity is Character) && ((Character)selectedAiTarget.Entity).IsDead)
{
if (state != AIState.Eat)
{
eatTimer = 0.0f;
state = AIState.Eat;
}
}
else
{
state = (targetValue < 0.0f || Character.Health < fleeHealthThreshold) ? AIState.Escape : AIState.Attack;
}
//if (coolDownTimer >= 0.0f) return;
}
steeringManager = Character.Submarine == null ? outsideSteering : insideSteering;
bool run = false;
switch (state)
{
case AIState.None:
UpdateNone(deltaTime);
break;
case AIState.Attack:
run = coolDownTimer <= 0.0f;
UpdateAttack(deltaTime);
break;
case AIState.Eat:
UpdateEating(deltaTime);
break;
case AIState.Escape:
run = true;
UpdateEscape(deltaTime);
break;
default:
throw new NotImplementedException();
}
if (run)
{
steeringManager.Update(Character.AnimController.InWater ?
Character.AnimController.SwimSpeedMultiplier : Character.AnimController.RunSpeedMultiplier);
}
else
{
steeringManager.Update();
}
}
#region Idle
private void UpdateNone(float deltaTime)
{
attackingLimb = null;
coolDownTimer -= deltaTime;
if (Character.Submarine == null && SimPosition.Y < ConvertUnits.ToSimUnits(SubmarineBody.DamageDepth * 0.5f))
{
//steer straight up if very deep
steeringManager.SteeringManual(deltaTime, Vector2.UnitY);
return;
}
if (attachToWalls && Character.Submarine == null)
{
raycastTimer -= deltaTime;
//check if there are any walls nearby the character could attach to
if (raycastTimer < 1.0f)
{
wallAttackPos = Vector2.Zero;
var cells = Level.Loaded.GetCells(WorldPosition, 1);
if (cells.Count > 0)
{
Body closestBody = Submarine.CheckVisibility(Character.SimPosition, ConvertUnits.ToSimUnits(cells[0].Center));
if (closestBody != null && closestBody.UserData is Voronoi2.VoronoiCell)
{
wallAttackPos = Submarine.LastPickedPosition;
}
}
raycastTimer = RaycastInterval;
}
}
else
{
wallAttackPos = Vector2.Zero;
}
if (wallAttackPos == Vector2.Zero)
{
//wander around randomly
steeringManager.SteeringAvoid(deltaTime, 0.1f);
steeringManager.SteeringWander(0.5f);
return;
}
float dist = Vector2.Distance(SimPosition, wallAttackPos);
if (dist < Math.Max(Math.Max(Character.AnimController.Collider.radius, Character.AnimController.Collider.width), Character.AnimController.Collider.height) * 1.2f)
{
//close enough to a wall -> attach
Character.AnimController.Collider.MoveToPos(wallAttackPos, 1.0f);
steeringManager.Reset();
}
else
{
//move closer to the wall
steeringManager.SteeringAvoid(deltaTime, 0.1f);
steeringManager.SteeringSeek(wallAttackPos);
}
}
#endregion
#region Escape
private void UpdateEscape(float deltaTime)
{
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(SimPosition - selectedAiTarget.SimPosition) * 5);
SteeringManager.SteeringWander(1.0f);
SteeringManager.SteeringAvoid(deltaTime, 2f);
}
#endregion
#region Attack
private void UpdateAttack(float deltaTime)
{
if (selectedAiTarget == null || selectedAiTarget.Entity == null || selectedAiTarget.Entity.Removed)
{
state = AIState.None;
return;
}
selectedTargetMemory.Priority -= deltaTime;
Vector2 attackSimPosition = Character.Submarine == null ? ConvertUnits.ToSimUnits(selectedAiTarget.WorldPosition) : selectedAiTarget.SimPosition;
if (wallAttackPos != Vector2.Zero && targetEntity != null)
{
attackSimPosition = wallAttackPos;
if (selectedAiTarget.Entity != null && Character.Submarine == null && selectedAiTarget.Entity.Submarine != null) attackSimPosition += ConvertUnits.ToSimUnits(selectedAiTarget.Entity.Submarine.Position);
}
else if (selectedAiTarget.Entity is Character)
{
//target the closest limb if the target is a character
float closestDist = Vector2.DistanceSquared(selectedAiTarget.SimPosition, SimPosition);
foreach (Limb limb in ((Character)selectedAiTarget.Entity).AnimController.Limbs)
{
if (limb == null) continue;
float dist = Vector2.DistanceSquared(limb.SimPosition, SimPosition);
if (dist < closestDist)
{
closestDist = dist;
attackSimPosition = limb.SimPosition;
}
}
}
if (Math.Abs(Character.AnimController.movement.X) > 0.1f && !Character.AnimController.InWater)
{
Character.AnimController.TargetDir = Character.SimPosition.X < attackSimPosition.X ? Direction.Right : Direction.Left;
}
if (coolDownTimer > 0.0f)
{
UpdateCoolDown(attackSimPosition, deltaTime);
return;
}
if (raycastTimer > 0.0)
{
raycastTimer -= deltaTime;
}
else
{
GetTargetEntity();
raycastTimer = RaycastInterval;
}
//steeringManager.SteeringAvoid(deltaTime, 1.0f);
Limb attackLimb = attackingLimb;
//check if any of the limbs is close enough to attack the target
if (attackingLimb == null)
{
foreach (Limb limb in Character.AnimController.Limbs)
{
if (limb.attack == null) continue;
attackLimb = limb;
if (ConvertUnits.ToDisplayUnits(Vector2.Distance(limb.SimPosition, attackSimPosition)) > limb.attack.Range) continue;
attackingLimb = limb;
break;
}
if (Character.IsRemotePlayer)
{
if (!Character.IsKeyDown(InputType.Attack)) return;
}
}
if (attackLimb != null)
{
steeringManager.SteeringSeek(attackSimPosition - (attackLimb.SimPosition - SimPosition), 3);
if (steeringManager is IndoorsSteeringManager)
{
var indoorsSteering = (IndoorsSteeringManager)steeringManager;
if (indoorsSteering.CurrentPath != null)
{
if (indoorsSteering.CurrentPath.Unreachable)
{
//wander around randomly and decrease the priority faster if no path is found
if (selectedTargetMemory != null) selectedTargetMemory.Priority -= deltaTime * 10.0f;
steeringManager.SteeringWander();
}
else if (indoorsSteering.CurrentPath.Finished)
{
steeringManager.SteeringManual(deltaTime, attackSimPosition - attackLimb.SimPosition);
}
}
}
if (attackingLimb != null) UpdateLimbAttack(deltaTime, attackingLimb, attackSimPosition);
}
}
private void GetTargetEntity()
{
targetEntity = null;
if (Character.AnimController.CurrentHull != null)
{
wallAttackPos = Vector2.Zero;
return;
}
//check if there's a wall between the target and the Character
Vector2 rayStart = Character.SimPosition;
Vector2 rayEnd = selectedAiTarget.SimPosition;
if (selectedAiTarget.Entity.Submarine!=null && Character.Submarine==null)
{
rayStart -= ConvertUnits.ToSimUnits(selectedAiTarget.Entity.Submarine.Position);
}
Body closestBody = Submarine.CheckVisibility(rayStart, rayEnd);
if (Submarine.LastPickedFraction == 1.0f || closestBody == null)
{
wallAttackPos = Vector2.Zero;
return;
}
Structure wall = closestBody.UserData as Structure;
if (wall == null)
{
wallAttackPos = Submarine.LastPickedPosition;
if (selectedAiTarget.Entity.Submarine!=null && Character.Submarine == null) wallAttackPos -= ConvertUnits.ToSimUnits(selectedAiTarget.Entity.Submarine.Position);
}
else
{
int sectionIndex = wall.FindSectionIndex(ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition));
float sectionDamage = wall.SectionDamage(sectionIndex);
for (int i = sectionIndex - 2; i <= sectionIndex + 2; i++)
{
if (wall.SectionBodyDisabled(i))
{
sectionIndex = i;
break;
}
if (wall.SectionDamage(i) > sectionDamage) sectionIndex = i;
}
wallAttackPos = wall.SectionPosition(sectionIndex);
//if (wall.Submarine != null) wallAttackPos += wall.Submarine.Position;
wallAttackPos = ConvertUnits.ToSimUnits(wallAttackPos);
}
targetEntity = closestBody.UserData as IDamageable;
}
public override void OnAttacked(IDamageable attacker, float amount)
{
updateTargetsTimer = Math.Min(updateTargetsTimer, 0.1f);
coolDownTimer *= 0.1f;
if (amount > 0.0f && attackWhenProvoked)
{
if (!(attacker is AICharacter) || (((AICharacter)attacker).AIController is HumanAIController))
{
attackHumans = 100.0f;
attackRooms = 100.0f;
}
}
if (attacker == null || attacker.AiTarget == null) return;
AITargetMemory targetMemory = FindTargetMemory(attacker.AiTarget);
targetMemory.Priority += amount;
}
private void UpdateLimbAttack(float deltaTime, Limb limb, Vector2 attackPosition)
{
var damageTarget = (wallAttackPos != Vector2.Zero && targetEntity != null) ? targetEntity : selectedAiTarget.Entity as IDamageable;
limb.UpdateAttack(deltaTime, attackPosition, damageTarget);
if (limb.AttackTimer >= limb.attack.Duration)
{
wallAttackPos = Vector2.Zero;
limb.AttackTimer = 0.0f;
coolDownTimer = attackCoolDown;
}
}
private void UpdateCoolDown(Vector2 attackPosition, float deltaTime)
{
coolDownTimer -= deltaTime;
attackingLimb = null;
float dist = Vector2.Distance(attackPosition, Character.SimPosition);
if (dist < ConvertUnits.ToSimUnits(500.0f))
{
steeringManager.SteeringSeek(attackPosition, -0.8f);
steeringManager.SteeringManual(deltaTime, Vector2.Normalize(Character.SimPosition - attackPosition) * (1.0f - (dist / 500.0f)));
}
steeringManager.SteeringAvoid(deltaTime, 1.0f);
}
#endregion
#region Eat
private void UpdateEating(float deltaTime)
{
if (selectedAiTarget == null || selectedAiTarget.Entity == null || selectedAiTarget.Entity.Removed)
{
state = AIState.None;
return;
}
Limb mouthLimb = Array.Find(Character.AnimController.Limbs, l => l != null && l.MouthPos.HasValue);
if (mouthLimb == null) mouthLimb = Character.AnimController.GetLimb(LimbType.Head);
if (mouthLimb == null)
{
DebugConsole.ThrowError("Character \"" + Character.SpeciesName + "\" failed to eat a target (a head or a limb with a mouthpos required)");
state = AIState.None;
return;
}
Character targetCharacter = selectedAiTarget.Entity as Character;
float eatSpeed = Character.Mass / targetCharacter.Mass * 0.1f;
eatTimer += deltaTime * eatSpeed;
Vector2 mouthPos = mouthLimb.SimPosition;
if (mouthLimb.MouthPos.HasValue)
{
float cos = (float)Math.Cos(mouthLimb.Rotation);
float sin = (float)Math.Sin(mouthLimb.Rotation);
mouthPos += new Vector2(
mouthLimb.MouthPos.Value.X * cos - mouthLimb.MouthPos.Value.Y * sin,
mouthLimb.MouthPos.Value.X * sin + mouthLimb.MouthPos.Value.Y * cos);
}
Vector2 attackSimPosition = Character.Submarine == null ? ConvertUnits.ToSimUnits(selectedAiTarget.WorldPosition) : selectedAiTarget.SimPosition;
Vector2 limbDiff = attackSimPosition - mouthPos;
float limbDist = limbDiff.Length();
if (limbDist < 1.0f)
{
//pull the target character to the position of the mouth
//(+ make the force fluctuate to waggle the character a bit)
targetCharacter.AnimController.MainLimb.MoveToPos(mouthPos, (float)(Math.Sin(eatTimer) + 10.0f));
targetCharacter.AnimController.MainLimb.body.SmoothRotate(mouthLimb.Rotation);
targetCharacter.AnimController.Collider.MoveToPos(mouthPos, (float)(Math.Sin(eatTimer) + 10.0f));
//pull the character's mouth to the target character (again with a fluctuating force)
float pullStrength = (float)(Math.Sin(eatTimer) * Math.Max(Math.Sin(eatTimer * 0.5f), 0.0f));
steeringManager.SteeringManual(deltaTime, limbDiff * pullStrength);
mouthLimb.body.ApplyForce(limbDiff * mouthLimb.Mass * 50.0f * pullStrength);
if (eatTimer % 1.0f < 0.5f && (eatTimer - deltaTime * eatSpeed) % 1.0f > 0.5f)
{
//apply damage to the target character to get some blood particles flying
targetCharacter.AnimController.MainLimb.AddDamage(targetCharacter.SimPosition, DamageType.None, Rand.Range(10.0f, 25.0f), 10.0f, false);
//keep severing joints until there is only one limb left
LimbJoint[] nonSeveredJoints = Array.FindAll(targetCharacter.AnimController.LimbJoints, l => !l.IsSevered);
if (nonSeveredJoints.Length == 0)
{
//only one limb left, the character is now full eaten
Entity.Spawner.AddToRemoveQueue(targetCharacter);
selectedAiTarget = null;
state = AIState.None;
}
else //sever a random joint
{
targetCharacter.AnimController.SeverLimbJoint(nonSeveredJoints[Rand.Int(nonSeveredJoints.Length)]);
}
}
}
else if (limbDist < 2.0f)
{
steeringManager.SteeringManual(deltaTime, limbDiff);
Character.AnimController.Collider.ApplyForce(limbDiff * mouthLimb.Mass * 50.0f, mouthPos);
}
else
{
steeringManager.SteeringSeek(attackSimPosition + (mouthPos - SimPosition), 3);
}
}
#endregion
#region Targeting
//goes through all the AItargets, evaluates how preferable it is to attack the target,
//whether the Character can see/hear the target and chooses the most preferable target within
//sight/hearing range
public void UpdateTargets(Character character)
{
var prevAiTarget = selectedAiTarget;
selectedAiTarget = null;
selectedTargetMemory = null;
targetValue = 0.0f;
UpdateTargetMemories();
foreach (AITarget target in AITarget.List)
{
if (Level.Loaded != null && target.WorldPosition.Y > Level.Loaded.Size.Y)
{
continue;
}
float valueModifier = 0.0f;
float dist = 0.0f;
Character targetCharacter = target.Entity as Character;
//ignore the aitarget if it is the Character itself
if (targetCharacter == character) continue;
if (targetCharacter != null)
{
if (targetCharacter.IsDead)
{
if (eatDeadPriority == 0.0f) continue;
valueModifier = eatDeadPriority;
}
else if (targetCharacter.SpeciesName == "human")
{
if (attackHumans == 0.0f) continue;
valueModifier = attackHumans;
}
else
{
EnemyAIController enemy = targetCharacter.AIController as EnemyAIController;
if (enemy != null)
{
if (enemy.combatStrength > combatStrength)
{
valueModifier = attackStronger;
}
else if (enemy.combatStrength < combatStrength)
{
valueModifier = attackWeaker;
}
else
{
continue;
}
}
}
}
else if (target.Entity!=null && attackRooms != 0.0f)
{
IDamageable targetDamageable = target.Entity as IDamageable;
if (targetDamageable != null && targetDamageable.Health <= 0.0f) continue;
//skip the target if it's a room and the character is already inside a sub
if (character.AnimController.CurrentHull != null && target.Entity is Hull) continue;
valueModifier = attackRooms;
}
if (valueModifier == 0.0f) continue;
dist = Vector2.Distance(character.WorldPosition, target.WorldPosition);
//if the target has been within range earlier, the character will notice it more easily
//(i.e. remember where the target was)
if (targetMemories.ContainsKey(target)) dist *= 0.5f;
//ignore target if it's too far to see or hear
if (dist > target.SightRange * sight && dist > target.SoundRange * hearing) continue;
AITargetMemory targetMemory = FindTargetMemory(target);
valueModifier = valueModifier * targetMemory.Priority / dist;
if (Math.Abs(valueModifier) > Math.Abs(targetValue))
{
Vector2 rayStart = character.AnimController.Limbs[0].SimPosition;
Vector2 rayEnd = target.SimPosition;
if (target.Entity.Submarine != null && character.Submarine==null)
{
rayStart -= ConvertUnits.ToSimUnits(target.Entity.Submarine.Position);
}
Body closestBody = Submarine.CheckVisibility(rayStart, rayEnd);
Structure closestStructure = (closestBody == null) ? null : closestBody.UserData as Structure;
if (selectedAiTarget == null || Math.Abs(valueModifier) > Math.Abs(targetValue))
{
selectedAiTarget = target;
selectedTargetMemory = targetMemory;
targetValue = valueModifier;
}
}
}
if (selectedAiTarget != prevAiTarget)
{
wallAttackPos = Vector2.Zero;
}
}
//find the targetMemory that corresponds to some AItarget or create if there isn't one yet
private AITargetMemory FindTargetMemory(AITarget target)
{
AITargetMemory memory = null;
if (targetMemories.TryGetValue(target, out memory))
{
return memory;
}
memory = new AITargetMemory(100.0f);
targetMemories.Add(target, memory);
return memory;
}
//go through all the targetmemories and delete ones that don't
//have a corresponding AItarget or whose priority is 0.0f
private void UpdateTargetMemories()
{
List<AITarget> toBeRemoved = new List<AITarget>();
foreach(KeyValuePair<AITarget, AITargetMemory> memory in targetMemories)
{
memory.Value.Priority += 0.5f;
if (Math.Abs(memory.Value.Priority) < 1.0f || !AITarget.List.Contains(memory.Key)) toBeRemoved.Add(memory.Key);
}
foreach (AITarget target in toBeRemoved)
{
targetMemories.Remove(target);
}
}
#endregion
}
//the "memory" of the Character
//keeps track of how preferable it is to attack a specific target
//(if the Character can't inflict much damage the target, the priority decreases
//and if the target attacks the Character, the priority increases)
class AITargetMemory
{
private float priority;
public float Priority
{
get { return priority; }
set { priority = MathHelper.Clamp(value, 1.0f, 100.0f); }
}
public AITargetMemory(float priority)
{
this.priority = priority;
}
}
}
@@ -0,0 +1,160 @@
using Microsoft.Xna.Framework;
using System;
namespace Barotrauma
{
partial class HumanAIController : AIController
{
public static bool DisableCrewAI;
const float UpdateObjectiveInterval = 0.5f;
private AIObjectiveManager objectiveManager;
private AITarget selectedAiTarget;
private float updateObjectiveTimer;
public Order CurrentOrder
{
get;
private set;
}
public string CurrentOrderOption
{
get;
private set;
}
public HumanAIController(Character c) : base(c)
{
steeringManager = new IndoorsSteeringManager(this, true);
objectiveManager = new AIObjectiveManager(c);
objectiveManager.AddObjective(new AIObjectiveFindSafety(c));
objectiveManager.AddObjective(new AIObjectiveIdle(c));
updateObjectiveTimer = Rand.Range(0.0f, UpdateObjectiveInterval);
InitProjSpecific();
}
partial void InitProjSpecific();
public override void Update(float deltaTime)
{
if (DisableCrewAI || Character.IsUnconscious) return;
Character.ClearInputs();
//steeringManager = Character.AnimController.CurrentHull == null ? outdoorsSteeringManager : indoorsSteeringManager;
if (updateObjectiveTimer>0.0f)
{
updateObjectiveTimer -= deltaTime;
}
else
{
objectiveManager.UpdateObjectives();
updateObjectiveTimer = UpdateObjectiveInterval;
}
objectiveManager.DoCurrentObjective(deltaTime);
float currObjectivePriority = objectiveManager.GetCurrentPriority(Character);
float moveSpeed = MathHelper.Clamp(currObjectivePriority/10.0f, 1.0f, 3.0f);
steeringManager.Update(moveSpeed);
bool ignorePlatforms = Character.AnimController.TargetMovement.Y < -0.5f &&
(-Character.AnimController.TargetMovement.Y > Math.Abs(Character.AnimController.TargetMovement.X));
var currPath = (steeringManager as IndoorsSteeringManager).CurrentPath;
if (currPath != null && currPath.CurrentNode != null)
{
if (currPath.CurrentNode.SimPosition.Y < Character.AnimController.GetColliderBottom().Y)
{
ignorePlatforms = true;
}
}
Character.AnimController.IgnorePlatforms = ignorePlatforms;
(Character.AnimController as HumanoidAnimController).Crouching = false;
if (!Character.AnimController.InWater)
{
Character.AnimController.TargetMovement = new Vector2(
Character.AnimController.TargetMovement.X,
MathHelper.Clamp(Character.AnimController.TargetMovement.Y, -1.0f, 1.0f)) * Character.SpeedMultiplier;
Character.SpeedMultiplier = 1.0f;
}
if (Character.SelectedConstruction != null && Character.SelectedConstruction.GetComponent<Items.Components.Ladder>()!=null)
{
if (currPath != null && currPath.CurrentNode != null && currPath.CurrentNode.Ladders != null)
{
Character.AnimController.TargetMovement = new Vector2( 0.0f, Math.Sign(Character.AnimController.TargetMovement.Y));
}
}
//suit can be taken off if there character is inside a hull and there's air in the room
bool canTakeOffSuit = Character.AnimController.CurrentHull != null &&
Character.AnimController.CurrentHull.OxygenPercentage > 30.0f &&
Character.AnimController.CurrentHull.Volume < Character.AnimController.CurrentHull.FullVolume * 0.3f;
//the suit can be taken off and the character is running out of oxygen (couldn't find a tank for the suit?) or idling
//-> take the suit off
if (canTakeOffSuit && (Character.Oxygen < 50.0f || objectiveManager.CurrentObjective is AIObjectiveIdle))
{
var divingSuit = Character.Inventory.FindItem("Diving Suit");
if (divingSuit != null) divingSuit.Drop(Character);
}
if (Character.IsKeyDown(InputType.Aim))
{
var cursorDiffX = Character.CursorPosition.X - Character.Position.X;
if (cursorDiffX > 10.0f)
{
Character.AnimController.TargetDir = Direction.Right;
}
else if (cursorDiffX < -10.0f)
{
Character.AnimController.TargetDir = Direction.Left;
}
if (Character.SelectedConstruction != null) Character.SelectedConstruction.SecondaryUse(deltaTime, Character);
}
else if (Math.Abs(Character.AnimController.TargetMovement.X) > 0.1f && !Character.AnimController.InWater)
{
Character.AnimController.TargetDir = Character.AnimController.TargetMovement.X > 0.0f ? Direction.Right : Direction.Left;
}
}
public override void OnAttacked(IDamageable attacker, float amount)
{
if (amount <= 0.0f) return;
var enemy = attacker as Character;
if (enemy == null || enemy == Character) return;
objectiveManager.AddObjective(new AIObjectiveCombat(Character, enemy));
}
public void SetOrder(Order order, string option)
{
CurrentOrderOption = option;
CurrentOrder = order;
objectiveManager.SetOrder(order, option);
SetOrderProjSpecific(order);
}
partial void SetOrderProjSpecific(Order order);
public override void SelectTarget(AITarget target)
{
selectedAiTarget = target;
}
}
}
@@ -0,0 +1,32 @@
using Microsoft.Xna.Framework;
namespace Barotrauma
{
interface ISteerable
{
Vector2 Steering
{
get;
set;
}
Vector2 Velocity
{
get;
}
Vector2 SimPosition
{
get;
}
Vector2 WorldPosition
{
get;
}
}
}
@@ -0,0 +1,313 @@
using System;
using System.Linq;
using Microsoft.Xna.Framework;
using Barotrauma.Items.Components;
namespace Barotrauma
{
class IndoorsSteeringManager : SteeringManager
{
private PathFinder pathFinder;
private SteeringPath currentPath;
private bool canOpenDoors;
private Character character;
private Vector2 currentTarget;
private float findPathTimer;
public SteeringPath CurrentPath
{
get { return currentPath; }
}
public PathFinder PathFinder
{
get { return pathFinder; }
}
public Vector2 CurrentTarget
{
get { return currentTarget; }
}
public IndoorsSteeringManager(ISteerable host, bool canOpenDoors)
: base(host)
{
pathFinder = new PathFinder(WayPoint.WayPointList.FindAll(wp => wp.SpawnType == SpawnType.Path), true);
pathFinder.GetNodePenalty = GetNodePenalty;
this.canOpenDoors = canOpenDoors;
character = (host as AIController).Character;
findPathTimer = Rand.Range(0.0f, 1.0f);
}
public override void Update(float speed = 1)
{
base.Update(speed);
findPathTimer -= 1.0f / 60.0f;
}
public void SetPath(SteeringPath path)
{
currentPath = path;
if (path.Nodes.Any()) currentTarget = path.Nodes[path.Nodes.Count - 1].SimPosition;
findPathTimer = 1.0f;
}
protected override Vector2 DoSteeringSeek(Vector2 target, float speed = 1)
{
//find a new path if one hasn't been found yet or the target is different from the current target
if (currentPath == null || Vector2.Distance(target, currentTarget)>1.0f || findPathTimer < -5.0f)
{
if (findPathTimer > 0.0f) return Vector2.Zero;
currentTarget = target;
Vector2 pos = host.SimPosition;
if (character != null && character.Submarine == null)
{
var targetHull = Hull.FindHull(FarseerPhysics.ConvertUnits.ToDisplayUnits(target), null, false);
if (targetHull!=null && targetHull.Submarine != null)
{
pos -= targetHull.SimPosition;
}
}
currentPath = pathFinder.FindPath(pos, target);
findPathTimer = Rand.Range(1.0f,1.2f);
return DiffToCurrentNode();
}
Vector2 diff = DiffToCurrentNode();
var collider = character.AnimController.Collider;
//if not in water and the waypoint is between the top and bottom of the collider, no need to move vertically
if (!character.AnimController.InWater &&
character.AnimController.Anim != AnimController.Animation.Climbing &&
diff.Y < collider.height / 2 + collider.radius)
{
diff.Y = 0.0f;
}
if (diff.LengthSquared() < 0.001f) return -host.Steering;
return Vector2.Normalize(diff) * speed;
}
private Vector2 DiffToCurrentNode()
{
if (currentPath == null || currentPath.Finished || currentPath.Unreachable) return Vector2.Zero;
if (currentPath.Finished)
{
Vector2 pos2 = host.SimPosition;
if (character != null && character.Submarine == null && CurrentPath.Nodes.Last().Submarine != null)
{
//todo: take multiple subs into account
pos2 -= CurrentPath.Nodes.Last().Submarine.SimPosition;
}
return currentTarget-pos2;
}
if (canOpenDoors && !character.LockHands) CheckDoorsInPath();
Vector2 pos = host.SimPosition;
if (character != null && currentPath.CurrentNode != null)
{
if (CurrentPath.CurrentNode.Submarine != null)
{
if (character.Submarine == null)
{
pos -= CurrentPath.CurrentNode.Submarine.SimPosition;
}
else if (character.Submarine != currentPath.CurrentNode.Submarine)
{
pos -= FarseerPhysics.ConvertUnits.ToSimUnits(currentPath.CurrentNode.Submarine.Position-character.Submarine.Position);
}
}
}
if (currentPath.CurrentNode!= null && currentPath.CurrentNode.Ladders!=null)
{
if (character.SelectedConstruction != currentPath.CurrentNode.Ladders.Item && currentPath.CurrentNode.Ladders.Item.IsInsideTrigger(character.WorldPosition))
{
currentPath.CurrentNode.Ladders.Item.Pick(character, false, true);
}
}
var collider = character.AnimController.Collider;
Vector2 colliderBottom = character.AnimController.GetColliderBottom();
if (Math.Abs(collider.SimPosition.X - currentPath.CurrentNode.SimPosition.X) < collider.radius * 2 &&
currentPath.CurrentNode.SimPosition.Y > colliderBottom.Y &&
currentPath.CurrentNode.SimPosition.Y < colliderBottom.Y + 1.5f)
{
currentPath.SkipToNextNode();
}
if (currentPath.CurrentNode == null) return Vector2.Zero;
if (character.AnimController.Anim == AnimController.Animation.Climbing)
{
Vector2 diff = currentPath.CurrentNode.SimPosition - pos;
if (currentPath.CurrentNode.Ladders != null)
{
//climbing ladders -> don't move horizontally
diff.X = 0.0f;
//at the same height as the waypoint
if (Math.Abs(collider.SimPosition.Y - currentPath.CurrentNode.SimPosition.Y) < collider.height / 2 + collider.radius)
{
float heightFromFloor = character.AnimController.GetColliderBottom().Y - character.AnimController.FloorY;
//we can safely skip to the next waypoint if the character is at a safe height above the floor,
//or if the next waypoint in the path is also on ladders
if ((heightFromFloor > 0.0f && heightFromFloor < collider.height) ||
(currentPath.NextNode != null && currentPath.NextNode.Ladders != null))
{
currentPath.SkipToNextNode();
}
}
}
character.AnimController.IgnorePlatforms = false;
return diff;
}
return currentPath.CurrentNode.SimPosition - pos;
}
private void CheckDoorsInPath()
{
for (int i = 0; i < 2; i++)
{
WayPoint node = null;
WayPoint nextNode = null;
if (i==0)
{
node = currentPath.CurrentNode;
nextNode = currentPath.NextNode;
}
else
{
node = currentPath.PrevNode;
nextNode = currentPath.CurrentNode;
}
if (node == null || node.ConnectedGap == null || node.ConnectedGap.ConnectedDoor == null) continue;
if (nextNode == null) continue;
var door = node.ConnectedGap.ConnectedDoor;
bool shouldBeOpen = false;
if (door.LinkedGap.isHorizontal)
{
int currentDir = Math.Sign(nextNode.WorldPosition.X - door.Item.WorldPosition.X);
shouldBeOpen = (door.Item.WorldPosition.X - character.WorldPosition.X) * currentDir > -50.0f;
}
else
{
int currentDir = Math.Sign(nextNode.WorldPosition.Y - door.Item.WorldPosition.Y);
shouldBeOpen = (door.Item.WorldPosition.Y - character.WorldPosition.Y) * currentDir > -80.0f;
}
//toggle the door if it's the previous node and open, or if it's current node and closed
if (door.IsOpen != shouldBeOpen)
{
var buttons = door.Item.GetConnectedComponents<Controller>(true);
Controller closestButton = null;
float closestDist = 0.0f;
foreach (Controller controller in buttons)
{
float dist = Vector2.Distance(controller.Item.WorldPosition, character.WorldPosition);
if (dist > controller.Item.PickDistance * 2.0f) continue;
if (dist < closestDist || closestButton == null)
{
closestButton = controller;
closestDist = dist;
}
}
if (closestButton != null)
{
if (!closestButton.HasRequiredItems(character, false) && shouldBeOpen)
{
currentPath.Unreachable = true;
return;
}
closestButton.Item.Pick(character, false, true);
break;
}
}
}
}
private float? GetNodePenalty(PathNode node, PathNode nextNode)
{
if (character == null) return 0.0f;
float penalty = 0.0f;
if (nextNode.Waypoint.ConnectedGap != null && nextNode.Waypoint.ConnectedGap.Open < 0.9f)
{
if (nextNode.Waypoint.ConnectedGap.ConnectedDoor == null)
{
penalty = 100.0f;
}
//door closed and the character can't open doors -> node can't be traversed
if (!canOpenDoors || character.LockHands) return null;
var doorButtons = nextNode.Waypoint.ConnectedGap.ConnectedDoor.Item.GetConnectedComponents<Controller>();
if (!doorButtons.Any()) 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;
}
}
if (node.Waypoint!=null && node.Waypoint.CurrentHull!=null)
{
var hull = node.Waypoint.CurrentHull;
if (hull.FireSources.Count > 0)
{
foreach (FireSource fs in hull.FireSources)
{
penalty += fs.Size.X * 10.0f;
}
}
if (character.NeedsAir && hull.Volume / hull.Rect.Width > 100.0f) penalty += 500.0f;
if (character.PressureProtection < 10.0f && hull.Volume > hull.FullVolume) penalty += 1000.0f;
}
return penalty;
}
}
}
@@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Barotrauma
{
class AIObjective
{
protected List<AIObjective> subObjectives;
protected float priority;
protected Character character;
protected string option;
public virtual bool IsCompleted()
{
return false;
}
public virtual bool CanBeCompleted
{
get { return true; }
}
public string Option
{
get { return option; }
}
public AIObjective(Character character, string option)
{
subObjectives = new List<AIObjective>();
this.character = character;
this.option = option;
#if DEBUG
IsDuplicate(null);
#endif
}
/// <summary>
/// makes the character act according to the objective, or according to any subobjectives that
/// need to be completed before this one
/// </summary>
public void TryComplete(float deltaTime)
{
subObjectives.RemoveAll(s => s.IsCompleted() || !s.CanBeCompleted);
foreach (AIObjective objective in subObjectives)
{
objective.TryComplete(deltaTime);
return;
}
Act(deltaTime);
}
protected virtual void Act(float deltaTime) { }
public void AddSubObjective(AIObjective objective)
{
if (subObjectives.Any(o => o.IsDuplicate(objective))) return;
subObjectives.Add(objective);
}
public virtual float GetPriority(Character character)
{
return 0.0f;
}
public virtual bool IsDuplicate(AIObjective otherObjective)
{
#if DEBUG
throw new NotImplementedException();
#else
return (this.GetType() == otherObjective.GetType());
#endif
}
}
}
@@ -0,0 +1,130 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Barotrauma
{
class AIObjectiveCombat : AIObjective
{
const float CoolDown = 10.0f;
private Character enemy;
private AIObjectiveFindSafety escapeObjective;
float coolDownTimer;
private readonly float enemyStrength;
public AIObjectiveCombat (Character character, Character enemy)
: base(character, "")
{
this.enemy = enemy;
foreach (Limb limb in enemy.AnimController.Limbs)
{
if (limb.attack == null) continue;
enemyStrength += limb.attack.GetDamage(1.0f);
}
coolDownTimer = CoolDown;
}
protected override void Act(float deltaTime)
{
coolDownTimer -= deltaTime;
var weapon = character.Inventory.FindItem("weapon");
if (weapon==null)
{
Escape(deltaTime);
}
else
{
if (!character.SelectedItems.Contains(weapon))
{
character.Inventory.TryPutItem(weapon, 3, false);
weapon.Equip(character);
}
character.CursorPosition = enemy.Position;
character.SetInput(InputType.Aim, false, true);
Vector2 enemyDiff = Vector2.Normalize(enemy.Position - character.Position);
float weaponAngle = ((weapon.body.Dir == 1.0f) ? weapon.body.Rotation : weapon.body.Rotation - MathHelper.Pi);
Vector2 weaponDir = new Vector2((float)Math.Cos(weaponAngle), (float)Math.Sin(weaponAngle));
if (Vector2.Dot(enemyDiff, weaponDir) < 0.9f) return;
List<FarseerPhysics.Dynamics.Body> ignoredBodies = new List<FarseerPhysics.Dynamics.Body>();
foreach (Limb limb in character.AnimController.Limbs)
{
ignoredBodies.Add(limb.body.FarseerBody);
}
var pickedBody = Submarine.PickBody(character.SimPosition, enemy.SimPosition, ignoredBodies);
if (pickedBody != null && !(pickedBody.UserData is Limb)) return;
weapon.Use(deltaTime, character);
}
}
private void Escape(float deltaTime)
{
if (escapeObjective == null)
{
escapeObjective = new AIObjectiveFindSafety(character);
}
if (enemy.AnimController.CurrentHull == character.AnimController.CurrentHull)
{
escapeObjective.OverrideCurrentHullSafety = 0.0f;
}
else
{
escapeObjective.OverrideCurrentHullSafety = null;
}
escapeObjective.TryComplete(deltaTime);
//if (Vector2.Distance(character.SimPosition, enemy.SimPosition) < 3.0f)
//{
// character.AIController.SteeringManager.SteeringManual(deltaTime,
// new Vector2(Math.Sign(character.SimPosition.X - enemy.SimPosition.X), 0.0f));
// coolDownTimer = CoolDown;
//}
}
public override bool IsCompleted()
{
return enemy.IsDead || coolDownTimer <= 0.0f;
}
public override float GetPriority(Character character)
{
//clamp the strength to the health of this character
//(it doesn't make a difference whether the enemy does 200 or 600 damage, it's one hit kill anyway)
float enemyDanger = Math.Min(enemyStrength, character.Health) + enemy.Health / 10.0f;
EnemyAIController enemyAI = enemy.AIController as EnemyAIController;
if (enemyAI != null)
{
if (enemyAI.SelectedAiTarget == character.AiTarget) enemyDanger *= 2.0f;
}
return Math.Max(enemyDanger, AIObjectiveManager.OrderPriority);
}
public override bool IsDuplicate(AIObjective otherObjective)
{
AIObjectiveCombat objective = otherObjective as AIObjectiveCombat;
if (objective == null) return false;
return objective.enemy == enemy;
}
}
}
@@ -0,0 +1,90 @@
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Barotrauma
{
class AIObjectiveContainItem: AIObjective
{
private string itemName;
private ItemContainer container;
bool isCompleted;
public bool IgnoreAlreadyContainedItems;
public AIObjectiveContainItem(Character character, string itemName, ItemContainer container)
: base (character, "")
{
this.itemName = itemName;
this.container = container;
//check if the container has room for more items
//canBeCompleted = false;
//foreach (Item contained in container.inventory.Items)
//{
// if (contained != null) continue;
// canBeCompleted = true;
// break;
//}
}
public override bool IsCompleted()
{
return isCompleted || container.Inventory.FindItem(itemName)!=null;
}
protected override void Act(float deltaTime)
{
if (isCompleted) return;
//get the item that should be contained
var itemToContain = character.Inventory.FindItem(itemName);
if (itemToContain == null)
{
var getItem = new AIObjectiveGetItem(character, itemName);
getItem.IgnoreContainedItems = IgnoreAlreadyContainedItems;
AddSubObjective(getItem);
return;
}
if (container.Item.ParentInventory == character.Inventory)
{
var containedItems = container.Inventory.Items;
//if there's already something in the mask (empty oxygen tank?), drop it
var existingItem = containedItems.FirstOrDefault(i => i != null);
if (existingItem != null) existingItem.Drop(character);
character.Inventory.RemoveItem(itemToContain);
container.Inventory.TryPutItem(itemToContain, null);
}
else
{
if (Vector2.Distance(character.Position, container.Item.Position) > container.Item.PickDistance
&& !container.Item.IsInsideTrigger(character.Position))
{
AddSubObjective(new AIObjectiveGoTo(container.Item, character));
return;
}
container.Combine(itemToContain);
}
isCompleted = true;
}
public override bool IsDuplicate(AIObjective otherObjective)
{
AIObjectiveContainItem objective = otherObjective as AIObjectiveContainItem;
if (objective == null) return false;
return objective.itemName == itemName && objective.container == container;
}
}
}
@@ -0,0 +1,89 @@
using Barotrauma.Items.Components;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Barotrauma
{
class AIObjectiveFindDivingGear : AIObjective
{
private AIObjective subObjective;
private string gearName;
public override bool IsCompleted()
{
var item = character.Inventory.FindItem(gearName);
if (item == null) return false;
var containedItems = item.ContainedItems;
var oxygenTank = Array.Find(containedItems, i => i.Name == "Oxygen Tank" && i.Condition > 0.0f);
return oxygenTank != null;
}
public AIObjectiveFindDivingGear(Character character, bool needDivingSuit)
: base(character, "")
{
gearName = needDivingSuit ? "Diving Suit" : "diving";
}
protected override void Act(float deltaTime)
{
var item = character.Inventory.FindItem(gearName);
if (item == null)
{
//get a diving mask/suit first
if (!(subObjective is AIObjectiveGetItem))
{
subObjective = new AIObjectiveGetItem(character, gearName, true);
}
}
else
{
var containedItems = item.ContainedItems;
if (containedItems == null) return;
//check if there's an oxygen tank in the mask
var oxygenTank = Array.Find(containedItems, i => i.Name == "Oxygen Tank");
if (oxygenTank != null)
{
if (oxygenTank.Condition > 0.0f)
{
return;
}
else
{
oxygenTank.Drop();
}
}
if (!(subObjective is AIObjectiveContainItem) || subObjective.IsCompleted())
{
subObjective = new AIObjectiveContainItem(character, "Oxygen Tank", item.GetComponent<ItemContainer>());
}
}
if (subObjective != null)
{
subObjective.TryComplete(deltaTime);
//isCompleted = subObjective.IsCompleted();
}
}
public override float GetPriority(Character character)
{
if (character.AnimController.CurrentHull == null) return 100.0f;
return 100.0f - character.Oxygen;
}
public override bool IsDuplicate(AIObjective otherObjective)
{
return otherObjective is AIObjectiveFindDivingGear;
}
}
}
@@ -0,0 +1,216 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
class AIObjectiveFindSafety : AIObjective
{
const float SearchHullInterval = 3.0f;
const float MinSafety = 50.0f;
private AIObjectiveGoTo goToObjective;
private List<Hull> unreachable;
private float currenthullSafety;
private float searchHullTimer;
private AIObjective divingGearObjective;
public float? OverrideCurrentHullSafety;
public AIObjectiveFindSafety(Character character)
: base(character, "")
{
unreachable = new List<Hull>();
}
protected override void Act(float deltaTime)
{
var currentHull = character.AnimController.CurrentHull;
currenthullSafety = OverrideCurrentHullSafety == null ?
GetHullSafety(currentHull, character) : (float)OverrideCurrentHullSafety;
if (currentHull != null)
{
if (NeedsDivingGear())
{
if (!FindDivingGear(deltaTime)) return;
}
if (currenthullSafety > MinSafety)
{
if (Math.Abs(currentHull.WorldPosition.X - character.WorldPosition.X) > 100.0f)
{
character.AIController.SteeringManager.SteeringSeek(currentHull.SimPosition, 0.5f);
}
else
{
character.AIController.SteeringManager.Reset();
}
character.AIController.SelectTarget(null);
goToObjective = null;
return;
}
}
if (searchHullTimer > 0.0f)
{
searchHullTimer -= deltaTime;
}
else
{
var bestHull = FindBestHull();
if (bestHull != null)
{
goToObjective = new AIObjectiveGoTo(bestHull, character);
}
searchHullTimer = SearchHullInterval;
}
if (goToObjective != null)
{
var pathSteering = character.AIController.SteeringManager as IndoorsSteeringManager;
if (pathSteering!=null && pathSteering.CurrentPath!= null &&
pathSteering.CurrentPath.Unreachable && !unreachable.Contains(goToObjective.Target))
{
unreachable.Add(goToObjective.Target as Hull);
}
goToObjective.TryComplete(deltaTime);
}
}
private bool FindDivingGear(float deltaTime)
{
if (divingGearObjective==null)
{
divingGearObjective = new AIObjectiveFindDivingGear(character, false);
}
if (divingGearObjective.IsCompleted()) return true;
divingGearObjective.TryComplete(deltaTime);
return divingGearObjective.IsCompleted();
}
private Hull FindBestHull()
{
Hull bestHull = null;
float bestValue = currenthullSafety;
foreach (Hull hull in Hull.hullList)
{
if (hull == character.AnimController.CurrentHull || unreachable.Contains(hull)) continue;
float hullValue = GetHullSafety(hull, character);
hullValue -= (float)Math.Sqrt(Math.Abs(character.Position.X - hull.Position.X));
hullValue -= (float)Math.Sqrt(Math.Abs(character.Position.Y - hull.Position.Y) * 2.0f);
if (bestHull == null || hullValue > bestValue)
{
bestHull = hull;
bestValue = hullValue;
}
}
return bestHull;
}
public override bool IsDuplicate(AIObjective otherObjective)
{
return (otherObjective is AIObjectiveFindSafety);
}
private bool NeedsDivingGear()
{
var currentHull = character.AnimController.CurrentHull;
if (currentHull == null) return true;
//there's lots of water in the room -> get a suit
if (currentHull.Volume / currentHull.FullVolume > 0.5f) return true;
if (currentHull.OxygenPercentage < 30.0f) return true;
return false;
}
public override float GetPriority(Character character)
{
if (character.Oxygen < 80.0f)
{
return 150.0f - character.Oxygen;
}
if (character.AnimController.CurrentHull == null) return 5.0f;
currenthullSafety = GetHullSafety(character.AnimController.CurrentHull, character);
priority = 100.0f - currenthullSafety;
var nearbyHulls = character.AnimController.CurrentHull.GetConnectedHulls(3);
foreach (Hull hull in nearbyHulls)
{
foreach (FireSource fireSource in hull.FireSources)
{
//increase priority if almost within damage range of a fire
if (character.Position.X > fireSource.Position.X - fireSource.DamageRange * 2 &&
character.Position.X < fireSource.Position.X + fireSource.Size.X + fireSource.DamageRange * 2 &&
character.Position.Y > hull.Rect.Y - hull.Rect.Height &&
character.Position.Y < hull.Rect.Y)
{
priority += Math.Max(fireSource.Size.X, 50.0f);
}
}
}
if (NeedsDivingGear())
{
if (divingGearObjective != null && !divingGearObjective.IsCompleted()) priority += 20.0f;
}
return priority;
}
public static float GetHullSafety(Hull hull, Character character)
{
if (hull == null) return 0.0f;
float waterPercentage = (hull.Volume / hull.FullVolume) * 100.0f;
float fireAmount = 0.0f;
var nearbyHulls = hull.GetConnectedHulls(3);
foreach (Hull hull2 in nearbyHulls)
{
foreach (FireSource fireSource in hull2.FireSources)
{
//increase priority if almost within damage range of a fire
if (character.Position.X > fireSource.Position.X - fireSource.DamageRange * 2 &&
character.Position.X < fireSource.Position.X + fireSource.Size.X + fireSource.DamageRange * 2 &&
character.Position.Y > hull2.Rect.Y - hull2.Rect.Height &&
character.Position.Y < hull2.Rect.Y)
{
fireAmount += Math.Max(fireSource.Size.X, 50.0f);
}
}
}
float safety = 100.0f - fireAmount;
if (waterPercentage > 30.0f && character.OxygenAvailable <= 0.0f) safety -= waterPercentage;
if (hull.OxygenPercentage < 30.0f) safety -= (30.0f - hull.OxygenPercentage) * 5.0f;
return safety;
}
}
}
@@ -0,0 +1,98 @@
using Barotrauma.Items.Components;
using FarseerPhysics;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Barotrauma
{
class AIObjectiveFixLeak : AIObjective
{
private Gap leak;
public Gap Leak
{
get { return leak; }
}
public AIObjectiveFixLeak(Gap leak, Character character)
: base (character, "")
{
this.leak = leak;
}
public override float GetPriority(Character character)
{
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)
{
AIObjectiveFixLeak fixLeak = otherObjective as AIObjectiveFixLeak;
if (fixLeak == null) return false;
return fixLeak.leak == leak;
}
protected override void Act(float deltaTime)
{
var weldingTool = character.Inventory.FindItem("Welding Tool");
if (weldingTool == null)
{
subObjectives.Add(new AIObjectiveGetItem(character, "Welding Tool", true));
return;
}
else
{
var containedItems = weldingTool.ContainedItems;
if (containedItems == null) return;
var fuelTank = Array.Find(containedItems, i => i.Name == "Welding Fuel Tank" && i.Condition > 0.0f);
if (fuelTank == null)
{
AddSubObjective(new AIObjectiveContainItem(character, "Welding Fuel Tank", weldingTool.GetComponent<ItemContainer>()));
}
}
var repairTool = weldingTool.GetComponent<RepairTool>();
if (repairTool == null) return;
if (Vector2.Distance(character.WorldPosition, leak.WorldPosition) > 300.0f)
{
AddSubObjective(new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character));
}
else
{
AddSubObjective(new AIObjectiveOperateItem(repairTool, character, "", leak));
}
}
private Vector2 GetStandPosition()
{
Vector2 standPos = leak.Position;
var hull = leak.FlowTargetHull;
if (hull == null) return standPos;
if (leak.isHorizontal)
{
standPos += Vector2.UnitX * Math.Sign(hull.Position.X - leak.Position.X) * leak.Rect.Width;
}
else
{
standPos += Vector2.UnitY * Math.Sign(hull.Position.Y - leak.Position.Y) * leak.Rect.Height;
}
return standPos;
}
}
}
@@ -0,0 +1,101 @@
using System;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
class AIObjectiveFixLeaks : AIObjective
{
const float UpdateGapListInterval = 10.0f;
private float updateGapListTimer;
private AIObjectiveIdle idleObjective;
private List<AIObjectiveFixLeak> objectiveList;
public AIObjectiveFixLeaks(Character character)
: base (character, "")
{
objectiveList = new List<AIObjectiveFixLeak>();
}
public override bool IsCompleted()
{
return false;
}
protected override void Act(float deltaTime)
{
updateGapListTimer -= deltaTime;
if (updateGapListTimer<=0.0f)
{
UpdateGapList();
updateGapListTimer = UpdateGapListInterval;
}
if (objectiveList.Any())
{
objectiveList[objectiveList.Count - 1].TryComplete(deltaTime);
if (!objectiveList[objectiveList.Count - 1].CanBeCompleted ||
objectiveList[objectiveList.Count - 1].IsCompleted())
{
objectiveList.RemoveAt(objectiveList.Count - 1);
}
}
else
{
if (idleObjective == null) idleObjective = new AIObjectiveIdle(character);
idleObjective.TryComplete(deltaTime);
}
}
private void UpdateGapList()
{
objectiveList.Clear();
foreach (Gap gap in Gap.GapList)
{
if (gap.ConnectedWall == null) continue;
if (gap.ConnectedDoor != null || gap.Open <= 0.0f) continue;
float gapPriority = GetGapFixPriority(gap);
int index = 0;
while (index < objectiveList.Count &&
GetGapFixPriority(objectiveList[index].Leak) < gapPriority)
{
index++;
}
objectiveList.Insert(index, new AIObjectiveFixLeak(gap, character));
}
}
private float GetGapFixPriority(Gap gap)
{
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)
{
return otherObjective is AIObjectiveFixLeaks;
}
}
}
@@ -0,0 +1,183 @@
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Barotrauma
{
class AIObjectiveGetItem : AIObjective
{
private string itemName;
private Item targetItem, moveToTarget;
private int currSearchIndex;
private bool canBeCompleted;
public bool IgnoreContainedItems;
private AIObjectiveGoTo goToObjective;
private bool equip;
public override bool CanBeCompleted
{
get { return canBeCompleted; }
}
public AIObjectiveGetItem(Character character, Item targetItem, bool equip = false)
: base(character, "")
{
canBeCompleted = true;
this.equip = equip;
currSearchIndex = 0;
this.targetItem = targetItem;
}
public AIObjectiveGetItem(Character character, string itemName, bool equip=false)
: base (character, "")
{
canBeCompleted = true;
this.equip = equip;
currSearchIndex = 0;
this.itemName = itemName;
}
protected override void Act(float deltaTime)
{
FindTargetItem();
if (targetItem == null || moveToTarget == null) return;
if (Vector2.Distance(character.Position, moveToTarget.Position) < targetItem.PickDistance*2.0f)
{
int targetSlot = -1;
if (equip)
{
var pickable = targetItem.GetComponent<Pickable>();
if (pickable == null)
{
canBeCompleted = false;
return;
}
//check if all the slots required by the item are free
foreach (InvSlotType slots in pickable.AllowedSlots)
{
if (slots.HasFlag(InvSlotType.Any)) continue;
for (int i = 0; i<character.Inventory.Items.Length; i++)
{
//slot not needed by the item, continue
if (!slots.HasFlag(CharacterInventory.limbSlots[i])) continue;
targetSlot = i;
//slot free, continue
if (character.Inventory.Items[i] == null) continue;
//try to move the existing item to LimbSlot.Any and continue if successful
if (character.Inventory.TryPutItem(character.Inventory.Items[i], new List<InvSlotType>() { InvSlotType.Any })) continue;
//if everything else fails, simply drop the existing item
character.Inventory.Items[i].Drop();
}
}
}
targetItem.Pick(character, false, true);
if (targetSlot > -1 && character.Inventory.IsInLimbSlot(targetItem, InvSlotType.Any))
{
character.Inventory.TryPutItem(targetItem, targetSlot, false);
}
}
else
{
if (goToObjective == null)
{
bool gettingDivingGear = itemName == "diving" || itemName == "Diving Gear";
goToObjective = new AIObjectiveGoTo(moveToTarget, character, false, !gettingDivingGear);
}
goToObjective.TryComplete(deltaTime);
}
}
/// <summary>
/// searches for an item that matches the desired item and adds a goto subobjective if one is found
/// </summary>
private void FindTargetItem()
{
if (itemName == null)
{
if (targetItem == null) canBeCompleted = false;
return;
}
float currDist = moveToTarget == null ? 0.0f : Vector2.DistanceSquared(moveToTarget.Position, character.Position);
for (int i = 0; i < 10 && currSearchIndex < Item.ItemList.Count - 2; i++)
{
currSearchIndex++;
var item = Item.ItemList[currSearchIndex];
if (item.CurrentHull == null || item.Condition <= 0.0f) continue;
if (IgnoreContainedItems && item.Container != null) continue;
if (item.Name != itemName && !item.HasTag(itemName)) continue;
//if the item is inside a character's inventory, don't steal it
if (item.ParentInventory is CharacterInventory) continue;
//if the item is inside an item, which is inside a character's inventory, don't steal it
if (item.ParentInventory != null && item.ParentInventory.Owner is Item)
{
if (((Item)item.ParentInventory.Owner).ParentInventory is CharacterInventory) continue;
}
//ignore if item is further away than the currently targeted item
Item rootContainer = item.GetRootContainer();
if (moveToTarget != null && Vector2.DistanceSquared((rootContainer ?? item).Position, character.Position) > currDist) continue;
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 && targetItem == null) canBeCompleted = false;
}
public override bool IsDuplicate(AIObjective otherObjective)
{
AIObjectiveGetItem getItem = otherObjective as AIObjectiveGetItem;
if (getItem == null) return false;
return (getItem.itemName == itemName);
}
public override bool IsCompleted()
{
if (itemName!=null)
{
return character.Inventory.FindItem(itemName) != null;
}
else if (targetItem!= null)
{
return character.Inventory.Items.Contains(targetItem);
}
else
{
return false;
}
}
}
}
@@ -0,0 +1,150 @@
using Barotrauma.Items.Components;
using FarseerPhysics;
using Microsoft.Xna.Framework;
using System;
namespace Barotrauma
{
class AIObjectiveGoTo : AIObjective
{
private Entity target;
private Vector2 targetPos;
private bool repeat;
//how long until the path to the target is declared unreachable
private float waitUntilPathUnreachable;
private bool getDivingGearIfNeeded;
public override bool CanBeCompleted
{
get
{
if (repeat || waitUntilPathUnreachable > 0.0f) return true;
var pathSteering = character.AIController.SteeringManager as IndoorsSteeringManager;
//path doesn't exist (= hasn't been searched for yet), assume for now that the target is reachable
if (pathSteering.CurrentPath == null) return true;
return (!pathSteering.CurrentPath.Unreachable);
}
}
public Entity Target
{
get { return target; }
}
public AIObjectiveGoTo(Entity target, Character character, bool repeat = false, bool getDivingGearIfNeeded = true)
: base (character, "")
{
this.target = target;
this.repeat = repeat;
waitUntilPathUnreachable = 5.0f;
this.getDivingGearIfNeeded = getDivingGearIfNeeded;
}
public AIObjectiveGoTo(Vector2 simPos, Character character, bool repeat = false, bool getDivingGearIfNeeded = true)
: base(character, "")
{
this.targetPos = simPos;
this.repeat = repeat;
waitUntilPathUnreachable = 5.0f;
this.getDivingGearIfNeeded = getDivingGearIfNeeded;
}
protected override void Act(float deltaTime)
{
if (target == character)
{
character.AIController.SteeringManager.Reset();
return;
}
waitUntilPathUnreachable -= deltaTime;
if (character.SelectedConstruction!=null && character.SelectedConstruction.GetComponent<Ladder>()==null)
{
character.SelectedConstruction = null;
}
if (target != null) character.AIController.SelectTarget(target.AiTarget);
Vector2 currTargetPos = Vector2.Zero;
if (target == null)
{
currTargetPos = targetPos;
}
else
{
currTargetPos = target.SimPosition;
//if character is outside the sub and target isn't, transform the position
if (character.Submarine != null && target.Submarine == null)
{
currTargetPos -= character.Submarine.SimPosition;
}
}
if (Vector2.Distance(currTargetPos, character.SimPosition) < 1.0f)
{
character.AIController.SteeringManager.Reset();
character.AnimController.TargetDir = currTargetPos.X > character.SimPosition.X ? Direction.Right : Direction.Left;
}
else
{
character.AIController.SteeringManager.SteeringSeek(currTargetPos);
var indoorsSteering = character.AIController.SteeringManager as IndoorsSteeringManager;
if (indoorsSteering.CurrentPath==null || indoorsSteering.CurrentPath.Unreachable)
{
indoorsSteering.SteeringWander();
}
else if (getDivingGearIfNeeded && indoorsSteering.CurrentPath != null && indoorsSteering.CurrentPath.HasOutdoorsNodes)
{
AddSubObjective(new AIObjectiveFindDivingGear(character, true));
}
}
}
public override bool IsCompleted()
{
if (repeat) return false;
bool completed = false;
float allowedDistance = 0.5f;
var item = target as Item;
if (item != null)
{
allowedDistance = Math.Max(ConvertUnits.ToSimUnits(item.PickDistance), allowedDistance);
if (item.IsInsideTrigger(character.WorldPosition)) completed = true;
}
completed = completed || Vector2.Distance(target != null ? target.SimPosition : targetPos, character.SimPosition) < allowedDistance;
if (completed) character.AIController.SteeringManager.Reset();
return completed;
}
public override bool IsDuplicate(AIObjective otherObjective)
{
AIObjectiveGoTo objective = otherObjective as AIObjectiveGoTo;
if (objective == null) return false;
if (objective.target == target) return true;
return (objective.targetPos == targetPos);
}
}
}
@@ -0,0 +1,149 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
class AIObjectiveIdle : AIObjective
{
const float WallAvoidDistance = 150.0f;
AITarget currentTarget;
private float newTargetTimer;
public AIObjectiveIdle(Character character) : base(character, "")
{
}
public override float GetPriority(Character character)
{
return 1.0f;
}
protected override void Act(float deltaTime)
{
var pathSteering = character.AIController.SteeringManager as IndoorsSteeringManager;
if (pathSteering==null)
{
return;
}
if (newTargetTimer <= 0.0f)
{
currentTarget = FindRandomTarget();
if (currentTarget != null)
{
Vector2 pos = character.SimPosition;
if (character != null && character.Submarine == null) pos -= Submarine.MainSub.SimPosition;
var path = pathSteering.PathFinder.FindPath(pos, currentTarget.SimPosition);
if (path.Cost > 200.0f && character.AnimController.CurrentHull!=null) return;
pathSteering.SetPath(path);
}
newTargetTimer = currentTarget == null ? 5.0f : 15.0f;
}
newTargetTimer -= deltaTime;
//wander randomly
// - if reached the end of the path
// - if the target is unreachable
// - if the path requires going outside
if (pathSteering==null || (pathSteering.CurrentPath != null &&
(pathSteering.CurrentPath.NextNode == null || pathSteering.CurrentPath.Unreachable || pathSteering.CurrentPath.HasOutdoorsNodes)))
{
//steer away from edges of the hull
if (character.AnimController.CurrentHull!=null)
{
float leftDist = character.Position.X - character.AnimController.CurrentHull.Rect.X;
float rightDist = character.AnimController.CurrentHull.Rect.Right - character.Position.X;
if (leftDist < WallAvoidDistance && rightDist < WallAvoidDistance)
{
if (Math.Abs(rightDist - leftDist) > WallAvoidDistance / 2)
{
pathSteering.SteeringManual(deltaTime, Vector2.UnitX * Math.Sign(rightDist - leftDist));
}
else
{
pathSteering.Reset();
return;
}
}
else if (leftDist < WallAvoidDistance)
{
pathSteering.SteeringManual(deltaTime, Vector2.UnitX * (WallAvoidDistance-leftDist)/WallAvoidDistance);
pathSteering.WanderAngle = 0.0f;
return;
}
else if (rightDist < WallAvoidDistance)
{
pathSteering.SteeringManual(deltaTime, -Vector2.UnitX * (WallAvoidDistance-rightDist)/WallAvoidDistance);
pathSteering.WanderAngle = MathHelper.Pi;
return;
}
}
if (character.AnimController.InWater)
{
character.AIController.SteeringManager.SteeringManual(deltaTime, new Vector2(0.0f, 0.5f));
}
else
{
character.AIController.SteeringManager.SteeringWander();
//reset vertical steering to prevent dropping down from platforms etc
character.AIController.SteeringManager.ResetY();
}
return;
}
if (currentTarget == null) return;
character.AIController.SteeringManager.SteeringSeek(currentTarget.SimPosition, 2.0f);
}
private AITarget FindRandomTarget()
{
if (Rand.Int(5)==1)
{
var idCard = character.Inventory.FindItem("ID Card");
if (idCard==null) return null;
foreach (WayPoint wp in WayPoint.WayPointList)
{
if (wp.SpawnType != SpawnType.Human || wp.CurrentHull==null) continue;
foreach (string tag in wp.IdCardTags)
{
if (idCard.HasTag(tag)) return wp.CurrentHull.AiTarget;
}
}
}
else
{
List<Hull> targetHulls = new List<Hull>(Hull.hullList);
//ignore all hulls with fires or water in them
targetHulls.RemoveAll(h => h.FireSources.Any() || (h.Volume/h.FullVolume)>0.1f);
if (!targetHulls.Any()) return null;
return targetHulls[Rand.Range(0, targetHulls.Count)].AiTarget;
}
return null;
}
public override bool IsDuplicate(AIObjective otherObjective)
{
return (otherObjective is AIObjectiveIdle);
}
}
}
@@ -0,0 +1,95 @@
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
class AIObjectiveManager
{
public const float OrderPriority = 50.0f;
private List<AIObjective> objectives;
private Character character;
private AIObjective currentObjective;
public AIObjective CurrentObjective
{
get
{
if (currentObjective != null) return currentObjective;
return objectives.Any() ? objectives[0] : null;
}
}
public AIObjectiveManager(Character character)
{
this.character = character;
objectives = new List<AIObjective>();
}
public void AddObjective(AIObjective objective)
{
if (objectives.Find(o => o.IsDuplicate(objective)) != null) return;
objectives.Add(objective);
}
public float GetCurrentPriority(Character character)
{
if (currentObjective != null) return OrderPriority;
return (CurrentObjective == null) ? 0.0f : CurrentObjective.GetPriority(character);
}
public void UpdateObjectives()
{
if (!objectives.Any()) return;
//remove completed objectives and ones that can't be completed
objectives = objectives.FindAll(o => !o.IsCompleted() && o.CanBeCompleted);
//sort objectives according to priority
objectives.Sort((x, y) => y.GetPriority(character).CompareTo(x.GetPriority(character)));
}
public void DoCurrentObjective(float deltaTime)
{
if (currentObjective != null && (!objectives.Any() || objectives[0].GetPriority(character) < OrderPriority))
{
currentObjective.TryComplete(deltaTime);
return;
}
if (!objectives.Any()) return;
objectives[0].TryComplete(deltaTime);
}
public void SetOrder(Order order, string option)
{
if (order == null) return;
currentObjective = null;
switch (order.Name.ToLowerInvariant())
{
case "follow":
currentObjective = new AIObjectiveGoTo(Character.Controlled, character, true);
break;
case "wait":
currentObjective = new AIObjectiveGoTo(character, character, true);
break;
case "fixleaks":
case "fix leaks":
currentObjective = new AIObjectiveFixLeaks(character);
break;
default:
if (order.TargetItem == null) return;
currentObjective = new AIObjectiveOperateItem(order.TargetItem, character, option, null, order.UseController);
break;
}
}
}
}
@@ -0,0 +1,96 @@
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Barotrauma
{
class AIObjectiveOperateItem : AIObjective
{
private ItemComponent component, controller;
private Entity operateTarget;
private bool isCompleted;
private bool canBeCompleted;
public override bool CanBeCompleted
{
get
{
return canBeCompleted;
}
}
public Entity OperateTarget
{
get { return operateTarget; }
}
public AIObjectiveOperateItem(ItemComponent item, Character character, string option, Entity operateTarget = null, bool useController = false)
:base (character, option)
{
this.component = item;
this.operateTarget = operateTarget;
if (useController)
{
var controllers = item.Item.GetConnectedComponents<Controller>();
if (controllers.Any()) controller = controllers[0];
}
canBeCompleted = true;
}
protected override void Act(float deltaTime)
{
ItemComponent target = controller == null ? component : controller;
if (target.CanBeSelected)
{
if (Vector2.Distance(character.Position, target.Item.Position) < target.Item.PickDistance
|| target.Item.IsInsideTrigger(character.WorldPosition))
{
if (character.SelectedConstruction != target.Item && target.CanBeSelected)
{
target.Item.Pick(character, false, true);
}
if (component.AIOperate(deltaTime, character, this)) isCompleted = true;
return;
}
AddSubObjective(new AIObjectiveGoTo(target.Item, character));
}
else
{
if (!character.Inventory.Items.Contains(component.Item))
{
AddSubObjective(new AIObjectiveGetItem(character, component.Item, true));
}
else
{
if (component.AIOperate(deltaTime, character, this)) isCompleted = true;
}
}
}
public override bool IsCompleted()
{
return isCompleted;
}
public override bool IsDuplicate(AIObjective otherObjective)
{
AIObjectiveOperateItem operateItem = otherObjective as AIObjectiveOperateItem;
if (operateItem == null) return false;
return (operateItem.component == component);
}
}
}
@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
namespace Barotrauma
{
class AIObjectiveRescue : AIObjective
{
private readonly Character targetCharacter;
public AIObjectiveRescue(Character character, Character targetCharacter)
: base (character, "")
{
Debug.Assert(character != targetCharacter);
this.targetCharacter = targetCharacter;
}
public override bool IsDuplicate(AIObjective otherObjective)
{
AIObjectiveRescue rescueObjective = otherObjective as AIObjectiveRescue;
return rescueObjective != null && rescueObjective.targetCharacter == targetCharacter;
}
public override float GetPriority(Character character)
{
if (targetCharacter.AnimController.CurrentHull == null) return 0.0f;
float distance = Vector2.DistanceSquared(character.WorldPosition, targetCharacter.WorldPosition);
return targetCharacter.IsDead ? 1000.0f / distance : 10000.0f / distance;
}
}
}
@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Barotrauma
{
class AIObjectiveRescueAll : AIObjective
{
private List<Character> rescueTargets;
public AIObjectiveRescueAll(Character character)
: base (character, "")
{
rescueTargets = new List<Character>();
}
public override bool IsDuplicate(AIObjective otherObjective)
{
return true;
}
public override float GetPriority(Character character)
{
GetRescueTargets();
//if there are targets to rescue, the priority is slightly less
//than the priority of explicit orders given to the character
return rescueTargets.Any() ? AIObjectiveManager.OrderPriority - 5.0f : 0.0f;
}
private void GetRescueTargets()
{
rescueTargets = Character.CharacterList.FindAll(c =>
c.AIController is HumanAIController &&
c != character &&
(c.IsDead || c.IsUnconscious) &&
c.AnimController.CurrentHull != null &&
AIObjectiveFindSafety.GetHullSafety(c.AnimController.CurrentHull, c) < 50.0f);
}
protected override void Act(float deltaTime)
{
foreach (Character target in rescueTargets)
{
AddSubObjective(new AIObjectiveRescue(character, target));
}
}
}
}
@@ -0,0 +1,139 @@
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml.Linq;
namespace Barotrauma
{
class Order
{
private static string ConfigFile = Path.Combine("Content", "Orders.xml");
public static List<Order> PrefabList;
public readonly string Name;
public readonly string DoingText;
public readonly Sprite SymbolSprite;
public readonly Type ItemComponentType;
public readonly string ItemName;
public readonly Color Color;
public readonly bool UseController;
public ItemComponent TargetItem;
public readonly string[] Options;
static Order()
{
PrefabList = new List<Order>();
XDocument doc = ToolBox.TryLoadXml(ConfigFile);
if (doc == null || doc.Root == null) return;
foreach (XElement orderElement in doc.Root.Elements())
{
if (orderElement.Name.ToString().ToLowerInvariant() != "order") continue;
PrefabList.Add(new Order(orderElement));
}
//PrefabList.Add(new Order("Follow", "Following"));
//PrefabList.Add(new Order("Dismiss", "Dismissed"));
//PrefabList.Add(new Order("Wait", "Wait"));
//PrefabList.Add(new Order("Operate Reactor", "Operating reactor", typeof(Reactor), new string[] {"Power up", "Shutdown"}));
//PrefabList.Add(new Order("Operate Railgun", "Operating railgun", typeof(Turret), new string[] { "Fire at will", "Hold fire" }));
}
private Order(XElement orderElement)
{
Name = ToolBox.GetAttributeString(orderElement, "name", "Name not found");
DoingText = ToolBox.GetAttributeString(orderElement, "doingtext", "");
string targetItemName = ToolBox.GetAttributeString(orderElement, "targetitemtype", "");
if (!string.IsNullOrWhiteSpace(targetItemName))
{
try
{
ItemComponentType = Type.GetType("Barotrauma.Items.Components." + targetItemName, true, true);
}
catch (Exception e)
{
DebugConsole.ThrowError("Error in " + ConfigFile + ", item component type " + targetItemName + " not found", e);
}
}
ItemName = ToolBox.GetAttributeString(orderElement, "targetitemname", "");
Color = new Color(ToolBox.GetAttributeVector4(orderElement, "color", new Vector4(1.0f, 1.0f, 1.0f, 1.0f)));
UseController = ToolBox.GetAttributeBool(orderElement, "usecontroller", false);
string optionStr = ToolBox.GetAttributeString(orderElement, "options", "");
if (string.IsNullOrWhiteSpace(optionStr))
{
Options = new string[0];
}
else
{
Options = optionStr.Split(',');
for (int i = 0; i<Options.Length; i++)
{
Options[i] = Options[i].Trim();
}
}
foreach (XElement subElement in orderElement.Elements())
{
if (subElement.Name.ToString().ToLowerInvariant() != "sprite") continue;
SymbolSprite = new Sprite(subElement);
break;
}
}
private Order(string name, string doingText, Type itemComponentType, string[] parameters = null)
{
Name = name;
DoingText = doingText;
ItemComponentType = itemComponentType;
Options = parameters == null ? new string[0] : parameters;
}
public Order(Order prefab, ItemComponent targetItem)
{
Name = prefab.Name;
DoingText = prefab.DoingText;
ItemComponentType = prefab.ItemComponentType;
Options = prefab.Options;
SymbolSprite = prefab.SymbolSprite;
Color = prefab.Color;
UseController = prefab.UseController;
TargetItem = targetItem;
}
private Order(string name, string doingText, string[] parameters = null)
:this (name,doingText, null, parameters)
{
}
}
}
@@ -0,0 +1,387 @@
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
class PathNode
{
private WayPoint wayPoint;
private int wayPointID;
public int state;
public PathNode Parent;
private Vector2 position;
public float F,G,H;
public List<PathNode> connections;
public List<float> distances;
public WayPoint Waypoint
{
get { return wayPoint; }
}
public Vector2 Position
{
get {return position;}
}
public PathNode(WayPoint wayPoint)
{
this.wayPoint = wayPoint;
this.position = wayPoint.SimPosition;
wayPointID = wayPoint.ID;
connections = new List<PathNode>();
}
public static List<PathNode> GenerateNodes(List<WayPoint> wayPoints)
{
var nodes = new Dictionary<int, PathNode>();
foreach (WayPoint wayPoint in wayPoints)
{
if (wayPoint == null) continue;
if (nodes.ContainsKey(wayPoint.ID))
{
DebugConsole.ThrowError("Error in PathFinder.GenerateNodes (duplicate ID \"" + wayPoint.ID + "\")");
continue;
}
nodes.Add(wayPoint.ID, new PathNode(wayPoint));
}
foreach (KeyValuePair<int,PathNode> node in nodes)
{
foreach (MapEntity linked in node.Value.wayPoint.linkedTo)
{
PathNode connectedNode = null;
nodes.TryGetValue(linked.ID, out connectedNode);
if (connectedNode == null) continue;
node.Value.connections.Add(connectedNode);
}
}
var nodeList = nodes.Values.ToList();
nodeList.RemoveAll(n => n.connections.Count == 0);
foreach (PathNode node in nodeList)
{
node.distances = new List<float>();
for (int i = 0; i< node.connections.Count; i++)
{
node.distances.Add(Vector2.Distance(node.position, node.connections[i].position));
}
}
return nodeList;
}
}
class PathFinder
{
public delegate float? GetNodePenaltyHandler(PathNode node, PathNode prevNode);
public GetNodePenaltyHandler GetNodePenalty;
private List<PathNode> nodes;
private bool insideSubmarine;
public PathFinder(List<WayPoint> wayPoints, bool insideSubmarine = false)
{
nodes = PathNode.GenerateNodes(wayPoints.FindAll(w => w.MoveWithLevel != insideSubmarine));
foreach (WayPoint wp in wayPoints)
{
wp.linkedTo.CollectionChanged += WaypointLinksChanged;
}
this.insideSubmarine = insideSubmarine;
}
void WaypointLinksChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (Submarine.Unloading) return;
var waypoints = sender as IEnumerable<MapEntity>;
foreach (MapEntity me in waypoints)
{
WayPoint wp = me as WayPoint;
if (me == null) continue;
var node = nodes.Find(n => n.Waypoint == wp);
if (node == null) return;
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove)
{
for (int i = node.connections.Count - 1; i >= 0; i--)
{
//remove connection if the waypoint isn't connected anymore
if (wp.linkedTo.FirstOrDefault(l => l == node.connections[i].Waypoint) == null)
{
node.connections.RemoveAt(i);
node.distances.RemoveAt(i);
}
}
}
else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
{
for (int i = 0; i < wp.linkedTo.Count; i++)
{
WayPoint connected = wp.linkedTo[i] as WayPoint;
if (connected == null) continue;
//already connected, continue
if (node.connections.Any(n => n.Waypoint == connected)) continue;
var matchingNode = nodes.Find(n => n.Waypoint == connected);
if (matchingNode == null)
{
#if DEBUG
DebugConsole.ThrowError("Waypoint connections were changed, no matching path node found in PathFinder");
#endif
return;
}
node.connections.Add(matchingNode);
node.distances.Add(Vector2.Distance(node.Position, matchingNode.Position));
}
}
}
}
public SteeringPath FindPath(Vector2 start, Vector2 end)
{
System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
sw.Start();
float closestDist = 0.0f;
PathNode startNode = null;
foreach (PathNode node in nodes)
{
Vector2 nodePos = node.Position;
float dist = System.Math.Abs(start.X - nodePos.X) +
System.Math.Abs(start.Y - nodePos.Y) * 10.0f; //higher cost for vertical movement
//prefer nodes that are closer to the end position
dist += Vector2.Distance(end, nodePos) / 10.0f;
if (dist<closestDist || startNode==null)
{
//if searching for a path inside the sub, make sure the waypoint is visible
if (insideSubmarine)
{
var body = Submarine.PickBody(
start, node.Waypoint.SimPosition, null,
Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionStairs | Physics.CollisionPlatform);
if (body != null)
{
if (body.UserData is Structure && !((Structure)body.UserData).IsPlatform) continue;
if (body.UserData is Item && body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall)) continue;
}
}
closestDist = dist;
startNode = node;
}
}
if (startNode == null)
{
DebugConsole.NewMessage("Pathfinding error, couldn't find a start node", Color.DarkRed);
return new SteeringPath();
}
closestDist = 0.0f;
PathNode endNode = null;
foreach (PathNode node in nodes)
{
Vector2 nodePos = node.Position;
float dist = Vector2.Distance(end, nodePos);
if (dist < closestDist || endNode == null)
{
//if searching for a path inside the sub, make sure the waypoint is visible
if (insideSubmarine)
{
var body = Submarine.CheckVisibility(end, node.Waypoint.SimPosition);
if (body != null && body.UserData is Structure) continue;
}
closestDist = dist;
endNode = node;
}
}
if (endNode == null)
{
DebugConsole.NewMessage("Pathfinding error, couldn't find an end node", Color.DarkRed);
return new SteeringPath();
}
var path = FindPath(startNode,endNode);
sw.Stop();
System.Diagnostics.Debug.WriteLine("findpath: " + sw.ElapsedMilliseconds+" ms");
return path;
}
public SteeringPath FindPath(WayPoint start, WayPoint end)
{
PathNode startNode=null, endNode=null;
foreach (PathNode node in nodes)
{
if (node.Waypoint == start)
{
startNode = node;
if (endNode != null) break;
}
if (node.Waypoint == end)
{
endNode = node;
if (startNode != null) break;
}
}
if (startNode == null || endNode == null)
{
DebugConsole.NewMessage("Pathfinding error, couldn't find matching pathnodes to waypoints", Color.DarkRed);
return new SteeringPath();
}
return FindPath(startNode, endNode);
}
private SteeringPath FindPath(PathNode start, PathNode end)
{
if (start == end)
{
var path1 = new SteeringPath();
path1.AddNode(start.Waypoint);
return path1;
}
foreach (PathNode node in nodes)
{
node.state = 0;
node.F = 0.0f;
node.G = 0.0f;
node.H = 0.0f;
}
start.state = 1;
while (true)
{
PathNode currNode = null;
float dist = 10000.0f;
foreach (PathNode node in nodes)
{
if (node.state != 1) continue;
if (node.F < dist)
{
dist = node.F;
currNode = node;
}
}
if (currNode == null || currNode == end) break;
currNode.state = 2;
for (int i = 0; i < currNode.connections.Count; i++)
{
PathNode nextNode = currNode.connections[i];
//a node that hasn't been searched yet
if (nextNode.state==0)
{
nextNode.H = Vector2.Distance(nextNode.Position,end.Position);
if (GetNodePenalty != null)
{
float? nodePenalty =GetNodePenalty(currNode, nextNode);
if (nodePenalty == null)
{
nextNode.state = -1;
continue;
}
nextNode.H += (float)nodePenalty;
}
nextNode.G = currNode.G + currNode.distances[i];
nextNode.F = nextNode.G + nextNode.H;
nextNode.Parent = currNode;
nextNode.state = 1;
}
//node that has been searched
else if (nextNode.state==1)
{
float tempG = currNode.G + currNode.distances[i];
//only use if this new route is better than the
//route the node was a part of
if (tempG < nextNode.G)
{
nextNode.G = tempG;
nextNode.F = nextNode.G + nextNode.H;
nextNode.Parent = currNode;
}
}
}
}
if (end.state == 0 || end.Parent == null)
{
//path not found
return new SteeringPath(true);
}
SteeringPath path = new SteeringPath();
List<WayPoint> finalPath = new List<WayPoint>();
PathNode pathNode = end;
while (pathNode != start && pathNode != null)
{
finalPath.Add(pathNode.Waypoint);
//there was one bug report that seems to have been caused by this loop never terminating:
//couldn't reproduce or figure out what caused it, but here's a workaround that prevents the game from crashing in case it happens again
if (finalPath.Count > nodes.Count)
{
DebugConsole.ThrowError("Pathfinding error: constructing final path failed");
return new SteeringPath(true);
}
path.Cost += pathNode.F;
pathNode = pathNode.Parent;
}
finalPath.Add(start.Waypoint);
finalPath.Reverse();
foreach (WayPoint wayPoint in finalPath)
{
path.AddNode(wayPoint);
}
return path;
}
}
}
@@ -0,0 +1,190 @@
using System;
using Microsoft.Xna.Framework;
using FarseerPhysics.Dynamics;
namespace Barotrauma
{
class SteeringManager
{
protected const float CircleDistance = 2.5f;
protected const float CircleRadius = 0.3f;
protected const float RayCastInterval = 0.5f;
protected ISteerable host;
private Vector2 steering;
//the steering amount when avoiding obstacles
//(needs a separate variable because it's only updated when a raycast is done to detect any nearby obstacles)
private Vector2 avoidSteering;
private float rayCastTimer;
private float wanderAngle;
public float WanderAngle
{
get { return wanderAngle; }
set { wanderAngle = value; }
}
public SteeringManager(ISteerable host)
{
this.host = host;
wanderAngle = Rand.Range(0.0f, MathHelper.TwoPi);
}
public void SteeringSeek(Vector2 targetSimPos, float speed = 1.0f)
{
steering += DoSteeringSeek(targetSimPos, speed);
}
public void SteeringWander(float speed = 1.0f)
{
steering += DoSteeringWander(speed);
}
public void SteeringAvoid(float deltaTime, float speed)
{
steering += DoSteeringAvoid(deltaTime, speed);
}
public void SteeringManual(float deltaTime, Vector2 velocity)
{
steering += velocity;
}
public void Reset()
{
steering = Vector2.Zero;
}
public void ResetX()
{
steering.X = 0.0f;
}
public void ResetY()
{
steering.Y = 0.0f;
}
public virtual void Update(float speed = 1.0f)
{
if (steering == Vector2.Zero || !MathUtils.IsValid(steering))
{
steering = Vector2.Zero;
host.Steering = Vector2.Zero;
return;
}
float steeringSpeed = steering.Length();
if (steeringSpeed>speed)
{
steering = Vector2.Normalize(steering) * Math.Abs(speed);
}
host.Steering = steering;
}
protected virtual Vector2 DoSteeringSeek(Vector2 target, float speed = 1.0f)
{
Vector2 targetVel = target - host.SimPosition;
if (targetVel.LengthSquared() < 0.00001f) return Vector2.Zero;
targetVel = Vector2.Normalize(targetVel) * speed;
Vector2 newSteering = targetVel - host.Steering;
if (newSteering==Vector2.Zero) return Vector2.Zero;
float steeringSpeed = (newSteering + host.Steering).Length();
if (steeringSpeed > Math.Abs(speed))
{
newSteering = Vector2.Normalize(newSteering)*Math.Abs(speed);
}
return newSteering;
}
protected virtual Vector2 DoSteeringWander(float speed = 1.0f)
{
Vector2 circleCenter = (host.Steering == Vector2.Zero) ? new Vector2(speed, 0.0f) : host.Steering;
circleCenter = Vector2.Normalize(circleCenter) * CircleDistance;
Vector2 displacement = new Vector2(
(float)Math.Cos(wanderAngle),
(float)Math.Sin(wanderAngle));
displacement = displacement * CircleRadius;
float angleChange = 1.5f;
wanderAngle += Rand.Range(0.0f, 1.0f) * angleChange - angleChange * 0.5f;
Vector2 newSteering = circleCenter + displacement;
float steeringSpeed = (newSteering + host.Steering).Length();
if (steeringSpeed > speed)
{
newSteering = Vector2.Normalize(newSteering) * speed;
}
return newSteering;
}
protected virtual Vector2 DoSteeringAvoid(float deltaTime, float speed = 1.0f)
{
if (steering == Vector2.Zero || host.Steering == Vector2.Zero) return Vector2.Zero;
float maxDistance = 2.0f;
Vector2 ahead = host.SimPosition + Vector2.Normalize(host.Steering) * maxDistance;
if (rayCastTimer <= 0.0f)
{
rayCastTimer = RayCastInterval;
Body closestBody = Submarine.CheckVisibility(host.SimPosition, ahead);
if (closestBody == null)
{
avoidSteering = Vector2.Zero;
return Vector2.Zero;
}
else
{
Structure closestStructure = closestBody.UserData as Structure;
if (closestStructure != null)
{
Vector2 obstaclePosition = Submarine.LastPickedPosition;
if (closestStructure.IsHorizontal)
{
obstaclePosition.Y = closestStructure.SimPosition.Y;
}
else
{
obstaclePosition.X = closestStructure.SimPosition.X;
}
avoidSteering = Vector2.Normalize(Submarine.LastPickedPosition - obstaclePosition);
}
else if (closestBody.UserData is Item)
{
Item item = (Item)closestBody.UserData;
avoidSteering = Vector2.Normalize(Submarine.LastPickedPosition - item.SimPosition);
}
else
{
avoidSteering = Vector2.Normalize(host.SimPosition - Submarine.LastPickedPosition);
}
}
}
else
{
rayCastTimer -= deltaTime;
}
return avoidSteering * speed;
}
}
}
@@ -0,0 +1,104 @@
using System.Collections.Generic;
using Microsoft.Xna.Framework;
namespace Barotrauma
{
class SteeringPath
{
private List<WayPoint> nodes;
int currentIndex;
public bool Unreachable
{
get;
set;
}
public SteeringPath(bool unreachable = false)
{
nodes = new List<WayPoint>();
Unreachable = unreachable;
}
public void AddNode(WayPoint node)
{
if (node == null) return;
nodes.Add(node);
if (node.CurrentHull == null) HasOutdoorsNodes = true;
}
public bool HasOutdoorsNodes
{
get;
private set;
}
public int CurrentIndex
{
get { return currentIndex; }
}
public float Cost
{
get;
set;
}
public WayPoint PrevNode
{
get
{
if (currentIndex-1 < 0 || currentIndex-1 > nodes.Count - 1) return null;
return nodes[currentIndex-1];
}
}
public WayPoint CurrentNode
{
get
{
if (currentIndex < 0 || currentIndex > nodes.Count - 1) return null;
return nodes[currentIndex];
}
}
public List<WayPoint> Nodes
{
get { return nodes; }
}
public WayPoint NextNode
{
get
{
if (currentIndex+1 < 0 || currentIndex+1 > nodes.Count - 1) return null;
return nodes[currentIndex+1];
}
}
public bool Finished
{
get { return currentIndex >= nodes.Count; }
}
public void SkipToNextNode()
{
currentIndex++;
}
public WayPoint CheckProgress(Vector2 simPosition, float minSimDistance = 0.1f)
{
if (nodes.Count == 0 || currentIndex>nodes.Count-1) return null;
if (Vector2.Distance(simPosition, nodes[currentIndex].SimPosition) < minSimDistance) currentIndex++;
return CurrentNode;
}
public void ClearPath()
{
nodes.Clear();
}
}
}
@@ -0,0 +1,69 @@
using Microsoft.Xna.Framework;
using System;
namespace Barotrauma
{
partial class AICharacter : Character
{
private AIController aiController;
public override AIController AIController
{
get { return aiController; }
}
public AICharacter(string file, Vector2 position, CharacterInfo characterInfo = null, bool isNetworkPlayer = false)
: base(file, position, characterInfo, isNetworkPlayer)
{
}
partial void InitProjSpecific();
public void SetAI(AIController aiController)
{
this.aiController = aiController;
}
public override void Update(float deltaTime, Camera cam)
{
base.Update(deltaTime, cam);
if (!Enabled || IsRemotePlayer) return;
float dist = Vector2.DistanceSquared(cam.WorldViewCenter, WorldPosition);
if (dist > 8000.0f * 8000.0f)
{
AnimController.SimplePhysicsEnabled = true;
}
else if (dist < 7000.0f * 7000.0f)
{
AnimController.SimplePhysicsEnabled = false;
}
if (IsDead || Health <= 0.0f || IsUnconscious || Stun > 0.0f) return;
if (Controlled == this || !aiController.Enabled) return;
SoundUpdate(deltaTime);
aiController.Update(deltaTime);
}
partial void SoundUpdate(float deltaTime);
public override void AddDamage(CauseOfDeath causeOfDeath, float amount, IDamageable attacker)
{
base.AddDamage(causeOfDeath, amount, attacker);
if (attacker!=null) aiController.OnAttacked(attacker, amount);
}
public override AttackResult AddDamage(IDamageable attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound = false)
{
AttackResult result = base.AddDamage(attacker, worldPosition, attack, deltaTime, playSound);
aiController.OnAttacked(attacker, (result.Damage + result.Bleeding) / Math.Max(Health, 1.0f));
return result;
}
}
}
@@ -0,0 +1,59 @@
using System.Xml.Linq;
using FarseerPhysics;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
namespace Barotrauma
{
class AnimController : Ragdoll
{
public enum Animation { None, Climbing, UsingConstruction, Struggle, CPR };
public Animation Anim;
protected Character character;
protected float walkSpeed, swimSpeed;
protected float walkPos;
protected readonly Vector2 stepSize;
protected readonly float legTorque;
public float RunSpeedMultiplier
{
get;
private set;
}
public float SwimSpeedMultiplier
{
get;
private set;
}
public AnimController(Character character, XElement element)
: base(character, element)
{
this.character = character;
stepSize = ToolBox.GetAttributeVector2(element, "stepsize", Vector2.One);
stepSize = ConvertUnits.ToSimUnits(stepSize);
walkSpeed = ToolBox.GetAttributeFloat(element, "walkspeed", 1.0f);
swimSpeed = ToolBox.GetAttributeFloat(element, "swimspeed", 1.0f);
RunSpeedMultiplier = ToolBox.GetAttributeFloat(element, "runspeedmultiplier", 2f);
SwimSpeedMultiplier = ToolBox.GetAttributeFloat(element, "swimspeedmultiplier", 1.5f);
legTorque = ToolBox.GetAttributeFloat(element, "legtorque", 0.0f);
}
public virtual void UpdateAnim(float deltaTime) { }
public virtual void HoldItem(float deltaTime, Item item, Vector2[] handlePos, Vector2 holdPos, Vector2 aimPos, bool aim, float holdAngle) { }
public virtual void DragCharacter(Character target) { }
}
}
@@ -0,0 +1,344 @@
using System;
using System.Linq;
using System.Xml.Linq;
using FarseerPhysics;
using FarseerPhysics.Dynamics.Joints;
using Microsoft.Xna.Framework;
namespace Barotrauma
{
class FishAnimController : AnimController
{
//amplitude and wave length of the "sine wave" swimming animation
//if amplitude = 0, sine wave animation isn't used
private float waveAmplitude;
private float waveLength;
private float steerTorque;
private bool rotateTowardsMovement;
private bool mirror, flip;
private float flipTimer;
private float? footRotation;
private float deathAnimTimer, deathAnimDuration = 5.0f;
public FishAnimController(Character character, XElement element)
: base(character, element)
{
waveAmplitude = ConvertUnits.ToSimUnits(ToolBox.GetAttributeFloat(element, "waveamplitude", 0.0f));
waveLength = ConvertUnits.ToSimUnits(ToolBox.GetAttributeFloat(element, "wavelength", 0.0f));
steerTorque = ToolBox.GetAttributeFloat(element, "steertorque", 25.0f);
flip = ToolBox.GetAttributeBool(element, "flip", true);
mirror = ToolBox.GetAttributeBool(element, "mirror", false);
float footRot = ToolBox.GetAttributeFloat(element, "footrotation", float.NaN);
if (float.IsNaN(footRot))
{
footRotation = null;
}
else
{
footRotation = MathHelper.ToRadians(footRot);
}
rotateTowardsMovement = ToolBox.GetAttributeBool(element, "rotatetowardsmovement", true);
}
public override void UpdateAnim(float deltaTime)
{
if (Frozen) return;
if (character.IsDead || character.IsUnconscious || character.Stun > 0.0f)
{
Collider.FarseerBody.FixedRotation = false;
if (character.IsRemotePlayer)
{
MainLimb.pullJoint.WorldAnchorB = Collider.SimPosition;
MainLimb.pullJoint.Enabled = true;
}
else
{
Collider.LinearVelocity = (MainLimb.SimPosition - Collider.SimPosition) * 60.0f;
Collider.SmoothRotate(MainLimb.Rotation);
}
if (character.IsDead && deathAnimTimer < deathAnimDuration)
{
deathAnimTimer += deltaTime;
UpdateDying(deltaTime);
}
return;
}
//re-enable collider
if (!Collider.FarseerBody.Enabled)
{
var lowestLimb = FindLowestLimb();
Collider.SetTransform(new Vector2(
Collider.SimPosition.X,
Math.Max(lowestLimb.SimPosition.Y + (Collider.radius + Collider.height / 2), Collider.SimPosition.Y)),
0.0f);
Collider.FarseerBody.Enabled = true;
}
ResetPullJoints();
if (strongestImpact > 0.0f)
{
character.Stun = MathHelper.Clamp(strongestImpact * 0.5f, character.Stun, 5.0f);
strongestImpact = 0.0f;
}
if (inWater)
{
Collider.FarseerBody.FixedRotation = false;
UpdateSineAnim(deltaTime);
}
else if (currentHull != null && CanEnterSubmarine)
{
if (Math.Abs(MathUtils.GetShortestAngle(Collider.Rotation, 0.0f)) > 0.001f)
{
//rotate collider back upright
Collider.AngularVelocity = MathUtils.GetShortestAngle(Collider.Rotation, 0.0f) * 60.0f;
Collider.FarseerBody.FixedRotation = false;
}
else
{
Collider.FarseerBody.FixedRotation = true;
}
UpdateWalkAnim(deltaTime);
}
if (mirror || !inWater)
{
if (!character.IsRemotePlayer)
{
//targetDir = (movement.X > 0.0f) ? Direction.Right : Direction.Left;
if (targetMovement.X > 0.1f && targetMovement.X > Math.Abs(targetMovement.Y) * 0.5f)
{
TargetDir = Direction.Right;
}
else if (targetMovement.X < -0.1f && targetMovement.X < -Math.Abs(targetMovement.Y) * 0.5f)
{
TargetDir = Direction.Left;
}
}
}
else
{
Limb head = GetLimb(LimbType.Head);
if (head == null) head = GetLimb(LimbType.Torso);
float rotation = MathUtils.WrapAngleTwoPi(head.Rotation);
rotation = MathHelper.ToDegrees(rotation);
if (rotation < 0.0f) rotation += 360;
if (rotation > 20 && rotation < 160)
{
TargetDir = Direction.Left;
}
else if (rotation > 200 && rotation < 340)
{
TargetDir = Direction.Right;
}
}
if (!flip) return;
flipTimer += deltaTime;
if (TargetDir != Direction.None && TargetDir != dir)
{
if (flipTimer>1.0f || character.IsRemotePlayer)
{
Flip();
if (mirror || !inWater) Mirror();
flipTimer = 0.0f;
}
}
}
void UpdateSineAnim(float deltaTime)
{
movement = TargetMovement*swimSpeed;
MainLimb.pullJoint.Enabled = true;
MainLimb.pullJoint.WorldAnchorB = Collider.SimPosition;
if (movement.LengthSquared() < 0.00001f) return;
float movementAngle = MathUtils.VectorToAngle(movement) - MathHelper.PiOver2;
if (rotateTowardsMovement)
{
Collider.SmoothRotate(movementAngle, 25.0f);
MainLimb.body.SmoothRotate(movementAngle, steerTorque);
}
else
{
Collider.SmoothRotate(HeadAngle * Dir, 25.0f);
MainLimb.body.SmoothRotate(HeadAngle * Dir, steerTorque);
}
Limb tail = GetLimb(LimbType.Tail);
if (tail != null && waveAmplitude > 0.0f)
{
walkPos -= movement.Length();
float waveRotation = (float)Math.Sin(walkPos / waveLength);
tail.body.ApplyTorque(waveRotation * tail.Mass * 100.0f * waveAmplitude);
}
for (int i = 0; i < Limbs.Length; i++)
{
if (Limbs[i].SteerForce <= 0.0f) continue;
Vector2 pullPos = Limbs[i].pullJoint == null ? Limbs[i].SimPosition : Limbs[i].pullJoint.WorldAnchorA;
Limbs[i].body.ApplyForce(movement * Limbs[i].SteerForce * Limbs[i].Mass, pullPos);
}
Collider.LinearVelocity = Vector2.Lerp(Collider.LinearVelocity, movement, 0.5f);
floorY = Limbs[0].SimPosition.Y;
}
void UpdateWalkAnim(float deltaTime)
{
movement = MathUtils.SmoothStep(movement, TargetMovement * walkSpeed, 0.2f);
float mainLimbHeight, mainLimbAngle;
if (MainLimb.type == LimbType.Torso)
{
mainLimbHeight = TorsoPosition;
mainLimbAngle = torsoAngle;
}
else
{
mainLimbHeight = HeadPosition;
mainLimbAngle = headAngle;
}
MainLimb.body.SmoothRotate(mainLimbAngle * Dir, 50.0f);
Collider.LinearVelocity = new Vector2(
movement.X,
Collider.LinearVelocity.Y > 0.0f ? Collider.LinearVelocity.Y * 0.5f : Collider.LinearVelocity.Y);
MainLimb.MoveToPos(GetColliderBottom() + Vector2.UnitY * mainLimbHeight, 10.0f);
MainLimb.pullJoint.Enabled = true;
MainLimb.pullJoint.WorldAnchorB = GetColliderBottom() + Vector2.UnitY * mainLimbHeight;
walkPos -= MainLimb.LinearVelocity.X * 0.05f;
Vector2 transformedStepSize = new Vector2(
(float)Math.Cos(walkPos) * stepSize.X * 3.0f,
(float)Math.Sin(walkPos) * stepSize.Y * 2.0f);
foreach (Limb limb in Limbs)
{
switch (limb.type)
{
case LimbType.LeftFoot:
case LimbType.RightFoot:
Vector2 footPos = new Vector2(limb.SimPosition.X, MainLimb.SimPosition.Y - mainLimbHeight);
if (limb.RefJointIndex>-1)
{
RevoluteJoint refJoint = LimbJoints[limb.RefJointIndex];
footPos.X = refJoint.WorldAnchorA.X;
}
footPos.X += limb.StepOffset.X * Dir;
footPos.Y += limb.StepOffset.Y;
if (limb.type == LimbType.LeftFoot)
{
limb.MoveToPos(footPos +new Vector2(
transformedStepSize.X + movement.X * 0.1f,
(transformedStepSize.Y > 0.0f) ? transformedStepSize.Y : 0.0f),
8.0f);
}
else if (limb.type == LimbType.RightFoot)
{
limb.MoveToPos(footPos + new Vector2(
-transformedStepSize.X + movement.X * 0.1f,
(-transformedStepSize.Y > 0.0f) ? -transformedStepSize.Y : 0.0f),
8.0f);
}
if (footRotation != null) limb.body.SmoothRotate((float)footRotation * Dir, 50.0f);
break;
case LimbType.LeftLeg:
case LimbType.RightLeg:
if (legTorque != 0.0f) limb.body.ApplyTorque(limb.Mass * legTorque * Dir);
break;
}
}
}
void UpdateDying(float deltaTime)
{
Limb head = GetLimb(LimbType.Head);
Limb tail = GetLimb(LimbType.Tail);
if (head != null && !head.IsSevered) head.body.ApplyTorque((float)(Math.Sqrt(head.Mass) * Dir * Math.Sin(walkPos)) * 10.0f);
if (tail != null && !tail.IsSevered) tail.body.ApplyTorque((float)(Math.Sqrt(tail.Mass) * -Dir * (float)Math.Sin(walkPos)) * 10.0f);
walkPos += deltaTime * 5.0f;
Vector2 centerOfMass = GetCenterOfMass();
foreach (Limb limb in Limbs)
{
if (limb.type == LimbType.Head || limb.type == LimbType.Tail || limb.IsSevered) continue;
limb.body.ApplyForce((centerOfMass - limb.SimPosition) * (float)(Math.Sin(walkPos) * Math.Sqrt(limb.Mass)) * 10.0f);
}
}
public override void Flip()
{
base.Flip();
foreach (Limb l in Limbs)
{
if (!l.DoesFlip) continue;
l.body.SetTransform(l.SimPosition,
-l.body.Rotation);
}
}
private void Mirror()
{
Vector2 centerOfMass = GetCenterOfMass();
foreach (Limb l in Limbs)
{
TrySetLimbPosition(l,
centerOfMass,
new Vector2(centerOfMass.X - (l.SimPosition.X - centerOfMass.X), l.SimPosition.Y),
true);
}
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,173 @@
using Microsoft.Xna.Framework;
using System;
using System.Xml.Linq;
using System.Collections.Generic;
namespace Barotrauma
{
enum CauseOfDeath
{
Damage, Bloodloss, Pressure, Suffocation, Drowning, Burn, Husk, Disconnected
}
public enum DamageType { None, Blunt, Slash, Burn }
struct AttackResult
{
public readonly float Damage;
public readonly float Bleeding;
public readonly bool HitArmor;
public AttackResult(float damage, float bleeding, bool hitArmor=false)
{
this.Damage = damage;
this.Bleeding = bleeding;
this.HitArmor = hitArmor;
}
}
partial class Attack
{
public readonly float Range;
public readonly float Duration;
public readonly DamageType DamageType;
private readonly float structureDamage;
private readonly float damage;
private readonly float bleedingDamage;
private readonly List<StatusEffect> statusEffects;
public readonly float Force;
public readonly float Torque;
public readonly float TargetForce;
public readonly float SeverLimbsProbability;
//the indices of the limbs Force is applied on
//(if none, force is applied only to the limb the attack is attached to)
public readonly List<int> ApplyForceOnLimbs;
public readonly float Stun;
private float priority;
public float GetDamage(float deltaTime)
{
return (Duration == 0.0f) ? damage : damage * deltaTime;
}
public float GetBleedingDamage(float deltaTime)
{
return (Duration == 0.0f) ? bleedingDamage : bleedingDamage * deltaTime;
}
public float GetStructureDamage(float deltaTime)
{
return (Duration == 0.0f) ? structureDamage : structureDamage * deltaTime;
}
public Attack(float damage, float structureDamage, float bleedingDamage, float range = 0.0f)
{
Range = range;
this.damage = damage;
this.structureDamage = structureDamage;
this.bleedingDamage = bleedingDamage;
}
public Attack(XElement element)
{
try
{
DamageType = (DamageType)Enum.Parse(typeof(DamageType), ToolBox.GetAttributeString(element, "damagetype", "None"), true);
}
catch
{
DamageType = DamageType.None;
}
damage = ToolBox.GetAttributeFloat(element, "damage", 0.0f);
structureDamage = ToolBox.GetAttributeFloat(element, "structuredamage", 0.0f);
bleedingDamage = ToolBox.GetAttributeFloat(element, "bleedingdamage", 0.0f);
Stun = ToolBox.GetAttributeFloat(element, "stun", 0.0f);
SeverLimbsProbability = ToolBox.GetAttributeFloat(element, "severlimbsprobability", 0.0f);
Force = ToolBox.GetAttributeFloat(element,"force", 0.0f);
TargetForce = ToolBox.GetAttributeFloat(element, "targetforce", 0.0f);
Torque = ToolBox.GetAttributeFloat(element, "torque", 0.0f);
Range = ToolBox.GetAttributeFloat(element, "range", 0.0f);
Duration = ToolBox.GetAttributeFloat(element, "duration", 0.0f);
priority = ToolBox.GetAttributeFloat(element, "priority", 1.0f);
InitProjSpecific(element);
string limbIndicesStr = ToolBox.GetAttributeString(element, "applyforceonlimbs", "");
if (!string.IsNullOrWhiteSpace(limbIndicesStr))
{
ApplyForceOnLimbs = new List<int>();
foreach (string limbIndexStr in limbIndicesStr.Split(','))
{
int limbIndex;
if (int.TryParse(limbIndexStr, out limbIndex))
{
ApplyForceOnLimbs.Add(limbIndex);
}
}
}
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "statuseffect":
if (statusEffects == null)
{
statusEffects = new List<StatusEffect>();
}
statusEffects.Add(StatusEffect.Load(subElement));
break;
}
}
}
partial void InitProjSpecific(XElement element);
public AttackResult DoDamage(IDamageable attacker, IDamageable target, Vector2 worldPosition, float deltaTime, bool playSound = true)
{
DamageParticles(worldPosition);
var attackResult = target.AddDamage(attacker, worldPosition, this, deltaTime, playSound);
var effectType = attackResult.Damage > 0.0f ? ActionType.OnUse : ActionType.OnFailure;
if (statusEffects == null)
{
return attackResult;
}
foreach (StatusEffect effect in statusEffects)
{
if (effect.Targets.HasFlag(StatusEffect.TargetType.This) && attacker is Character)
{
effect.Apply(effectType, deltaTime, (Character)attacker, (Character)attacker);
}
if (effect.Targets.HasFlag(StatusEffect.TargetType.Character) && target is Character)
{
effect.Apply(effectType, deltaTime, (Character)target, (Character)target);
}
}
return attackResult;
}
partial void DamageParticles(Vector2 worldPosition);
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,319 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
public enum Gender { None, Male, Female };
partial class CharacterInfo
{
public string Name;
public Character Character;
public readonly string File;
public Job Job;
private List<ushort> pickedItems;
public ushort? HullID = null;
private Vector2[] headSpriteRange;
private Gender gender;
public int Salary;
private int headSpriteId;
private Sprite headSprite;
public bool StartItemsGiven;
public List<ushort> PickedItemIDs
{
get { return pickedItems; }
}
public Sprite HeadSprite
{
get
{
if (headSprite == null) LoadHeadSprite();
return headSprite;
}
}
public List<string> SpriteTags
{
get;
private set;
}
public int HeadSpriteId
{
get { return headSpriteId; }
set
{
int oldId = headSpriteId;
headSpriteId = value;
Vector2 spriteRange = headSpriteRange[gender == Gender.Male ? 0 : 1];
if (headSpriteId < (int)spriteRange.X) headSpriteId = (int)(spriteRange.Y);
if (headSpriteId > (int)spriteRange.Y) headSpriteId = (int)(spriteRange.X);
if (headSpriteId != oldId) headSprite = null;
}
}
public Gender Gender
{
get { return gender; }
set
{
if (gender == value) return;
gender = value;
int genderIndex = (this.gender == Gender.Female) ? 1 : 0;
if (headSpriteRange[genderIndex] != Vector2.Zero)
{
HeadSpriteId = Rand.Range((int)headSpriteRange[genderIndex].X, (int)headSpriteRange[genderIndex].Y + 1);
}
else
{
HeadSpriteId = 0;
}
LoadHeadSprite();
}
}
public CharacterInfo(string file, string name = "", Gender gender = Gender.None, JobPrefab jobPrefab = null)
{
this.File = file;
headSpriteRange = new Vector2[2];
pickedItems = new List<ushort>();
SpriteTags = new List<string>();
//ID = -1;
XDocument doc = ToolBox.TryLoadXml(file);
if (doc == null) return;
if (ToolBox.GetAttributeBool(doc.Root, "genders", false))
{
if (gender == Gender.None)
{
float femaleRatio = ToolBox.GetAttributeFloat(doc.Root, "femaleratio", 0.5f);
this.gender = (Rand.Range(0.0f, 1.0f, Rand.RandSync.Server) < femaleRatio) ? Gender.Female : Gender.Male;
}
else
{
this.gender = gender;
}
}
headSpriteRange[0] = ToolBox.GetAttributeVector2(doc.Root, "headid", Vector2.Zero);
headSpriteRange[1] = headSpriteRange[0];
if (headSpriteRange[0] == Vector2.Zero)
{
headSpriteRange[0] = ToolBox.GetAttributeVector2(doc.Root, "maleheadid", Vector2.Zero);
headSpriteRange[1] = ToolBox.GetAttributeVector2(doc.Root, "femaleheadid", Vector2.Zero);
}
int genderIndex = (this.gender == Gender.Female) ? 1 : 0;
if (headSpriteRange[genderIndex] != Vector2.Zero)
{
HeadSpriteId = Rand.Range((int)headSpriteRange[genderIndex].X, (int)headSpriteRange[genderIndex].Y + 1);
}
this.Job = (jobPrefab == null) ? Job.Random() : new Job(jobPrefab);
if (!string.IsNullOrEmpty(name))
{
this.Name = name;
return;
}
name = "";
if (doc.Root.Element("name") != null)
{
string firstNamePath = ToolBox.GetAttributeString(doc.Root.Element("name"), "firstname", "");
if (firstNamePath != "")
{
firstNamePath = firstNamePath.Replace("[GENDER]", (this.gender == Gender.Female) ? "f" : "");
this.Name = ToolBox.GetRandomLine(firstNamePath);
}
string lastNamePath = ToolBox.GetAttributeString(doc.Root.Element("name"), "lastname", "");
if (lastNamePath != "")
{
lastNamePath = lastNamePath.Replace("[GENDER]", (this.gender == Gender.Female) ? "f" : "");
if (this.Name != "") this.Name += " ";
this.Name += ToolBox.GetRandomLine(lastNamePath);
}
}
Salary = CalculateSalary();
}
private void LoadHeadSprite()
{
XDocument doc = ToolBox.TryLoadXml(File);
if (doc == null) return;
XElement ragdollElement = doc.Root.Element("ragdoll");
foreach (XElement limbElement in ragdollElement.Elements())
{
if (ToolBox.GetAttributeString(limbElement, "type", "").ToLowerInvariant() != "head") continue;
XElement spriteElement = limbElement.Element("sprite");
string spritePath = spriteElement.Attribute("texture").Value;
spritePath = spritePath.Replace("[GENDER]", (this.gender == Gender.Female) ? "f" : "");
spritePath = spritePath.Replace("[HEADID]", HeadSpriteId.ToString());
string fileName = Path.GetFileNameWithoutExtension(spritePath);
//go through the files in the directory to find a matching sprite
var files = Directory.GetFiles(Path.GetDirectoryName(spritePath)).ToList();
foreach (string file in files)
{
string fileWithoutTags = Path.GetFileNameWithoutExtension(file);
fileWithoutTags = fileWithoutTags.Split('[', ']').First();
if (fileWithoutTags != fileName) continue;
headSprite = new Sprite(spriteElement, "", file);
//extract the tags out of the filename
SpriteTags = file.Split('[', ']').Skip(1).ToList();
if (SpriteTags.Any())
{
SpriteTags.RemoveAt(SpriteTags.Count-1);
}
break;
}
break;
}
}
public void UpdateCharacterItems()
{
pickedItems.Clear();
foreach (Item item in Character.Inventory.Items)
{
pickedItems.Add(item == null ? (ushort)0 : item.ID);
}
}
public CharacterInfo(XElement element)
{
Name = ToolBox.GetAttributeString(element, "name", "unnamed");
string genderStr = ToolBox.GetAttributeString(element, "gender", "male").ToLowerInvariant();
gender = (genderStr == "m") ? Gender.Male : Gender.Female;
File = ToolBox.GetAttributeString(element, "file", "");
Salary = ToolBox.GetAttributeInt(element, "salary", 1000);
headSpriteId = ToolBox.GetAttributeInt(element, "headspriteid", 1);
StartItemsGiven = ToolBox.GetAttributeBool(element, "startitemsgiven", false);
int hullId = ToolBox.GetAttributeInt(element, "hull", -1);
if (hullId > 0 && hullId <= ushort.MaxValue) this.HullID = (ushort)hullId;
pickedItems = new List<ushort>();
string pickedItemString = ToolBox.GetAttributeString(element, "items", "");
if (!string.IsNullOrEmpty(pickedItemString))
{
string[] itemIds = pickedItemString.Split(',');
foreach (string s in itemIds)
{
pickedItems.Add((ushort)int.Parse(s));
}
}
foreach (XElement subElement in element.Elements())
{
if (subElement.Name.ToString().ToLowerInvariant() != "job") continue;
Job = new Job(subElement);
break;
}
}
private int CalculateSalary()
{
if (Name == null || Job == null) return 0;
int salary = Math.Abs(Name.GetHashCode()) % 100;
foreach (Skill skill in Job.Skills)
{
salary += skill.Level * 10;
}
return salary;
}
public virtual XElement Save(XElement parentElement)
{
XElement charElement = new XElement("Character");
charElement.Add(
new XAttribute("name", Name),
new XAttribute("file", File),
new XAttribute("gender", gender == Gender.Male ? "m" : "f"),
new XAttribute("salary", Salary),
new XAttribute("headspriteid", HeadSpriteId),
new XAttribute("startitemsgiven", StartItemsGiven));
if (Character != null)
{
if (Character.Inventory != null)
{
UpdateCharacterItems();
}
if (Character.AnimController.CurrentHull != null)
{
HullID = Character.AnimController.CurrentHull.ID;
charElement.Add(new XAttribute("hull", Character.AnimController.CurrentHull.ID));
}
}
if (pickedItems.Count > 0)
{
charElement.Add(new XAttribute("items", string.Join(",", pickedItems)));
}
Job.Save(charElement);
parentElement.Add(charElement);
return charElement;
}
public void Remove()
{
Character = null;
//if (headSprite != null)
//{
// headSprite.Remove();
// headSprite = null;
//}
}
}
}
@@ -0,0 +1,634 @@
using Barotrauma.Networking;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
class CharacterStateInfo : PosInfo
{
public readonly Direction Direction;
public readonly Entity Interact; //the entity being interacted with
public readonly AnimController.Animation Animation;
public CharacterStateInfo(Vector2 pos, float time, Direction dir, Entity interact, AnimController.Animation animation = AnimController.Animation.None)
: this(pos, 0, time, dir, interact, animation)
{
}
public CharacterStateInfo(Vector2 pos, UInt16 ID, Direction dir, Entity interact, AnimController.Animation animation = AnimController.Animation.None)
: this(pos, ID, 0.0f, dir, interact, animation)
{
}
protected CharacterStateInfo(Vector2 pos, UInt16 ID, float time, Direction dir, Entity interact, AnimController.Animation animation = AnimController.Animation.None)
: base(pos, ID, time)
{
Direction = dir;
Interact = interact;
Animation = animation;
}
}
partial class Character
{
[Flags]
private enum InputNetFlags : ushort
{
None = 0x0,
Left = 0x1,
Right = 0x2,
Up = 0x4,
Down = 0x8,
FacingLeft = 0x10,
Run = 0x20,
Crouch = 0x40,
Select = 0x80,
Use = 0x100,
Aim = 0x200,
Attack = 0x400,
MaxVal = 0x7FF
}
private InputNetFlags dequeuedInput = 0;
private InputNetFlags prevDequeuedInput = 0;
public UInt16 LastNetworkUpdateID = 0;
/// <summary>
/// ID of the last inputs the server has processed
/// </summary>
public UInt16 LastProcessedID;
private struct NetInputMem
{
public InputNetFlags states; //keys pressed/other boolean states at this step
public UInt16 intAim; //aim angle, represented as an unsigned short where 0=0º, 65535=just a bit under 360º
public UInt16 interact; //id of the entity being interacted with
public UInt16 networkUpdateID;
}
private List<NetInputMem> memInput = new List<NetInputMem>();
private List<CharacterStateInfo> memState = new List<CharacterStateInfo>();
private List<CharacterStateInfo> memLocalState = new List<CharacterStateInfo>();
private bool networkUpdateSent;
public bool isSynced = false;
public List<CharacterStateInfo> MemState
{
get { return memState; }
}
public List<CharacterStateInfo> MemLocalState
{
get { return memLocalState; }
}
public void ResetNetState()
{
memInput.Clear();
memState.Clear();
memLocalState.Clear();
LastNetworkUpdateID = 0;
LastProcessedID = 0;
}
private void UpdateNetInput()
{
if (this != Character.Controlled)
{
if (GameMain.Client != null)
{
//freeze AI characters if more than 1 seconds have passed since last update from the server
if (lastRecvPositionUpdateTime < NetTime.Now - 1.0f)
{
AnimController.Frozen = true;
memState.Clear();
//hide after 2 seconds
if (lastRecvPositionUpdateTime < NetTime.Now - 2.0f)
{
Enabled = false;
return;
}
}
}
else if (GameMain.Server != null && (!(this is AICharacter) || IsRemotePlayer))
{
if (!AllowInput)
{
AnimController.Frozen = false;
}
else if (memInput.Count == 0)
{
AnimController.Frozen = true;
}
else
{
AnimController.Frozen = false;
prevDequeuedInput = dequeuedInput;
LastProcessedID = memInput[memInput.Count - 1].networkUpdateID;
dequeuedInput = memInput[memInput.Count - 1].states;
double aimAngle = ((double)memInput[memInput.Count - 1].intAim / 65535.0) * 2.0 * Math.PI;
cursorPosition = (ViewTarget == null ? AnimController.Collider.Position : ViewTarget.Position)
+ new Vector2((float)Math.Cos(aimAngle), (float)Math.Sin(aimAngle)) * 60.0f;
var closestEntity = Entity.FindEntityByID(memInput[memInput.Count - 1].interact);
if (closestEntity is Item)
{
closestItem = (Item)closestEntity;
closestCharacter = null;
}
else if (closestEntity is Character)
{
closestCharacter = (Character)closestEntity;
closestItem = null;
}
memInput.RemoveAt(memInput.Count - 1);
TransformCursorPos();
if ((dequeuedInput == InputNetFlags.None || dequeuedInput == InputNetFlags.FacingLeft) && Math.Abs(AnimController.Collider.LinearVelocity.X) < 0.005f && Math.Abs(AnimController.Collider.LinearVelocity.Y) < 0.2f)
{
while (memInput.Count > 5 && memInput[memInput.Count - 1].states == dequeuedInput)
{
//remove inputs where the player is not moving at all
//helps the server catch up, shouldn't affect final position
LastProcessedID = memInput[memInput.Count - 1].networkUpdateID;
memInput.RemoveAt(memInput.Count - 1);
}
}
}
}
}
else if (GameMain.Client != null)
{
var posInfo = new CharacterStateInfo(
SimPosition,
LastNetworkUpdateID,
AnimController.TargetDir,
selectedCharacter == null ? (Entity)selectedConstruction : (Entity)selectedCharacter,
AnimController.Anim);
memLocalState.Add(posInfo);
InputNetFlags newInput = InputNetFlags.None;
if (IsKeyDown(InputType.Left)) newInput |= InputNetFlags.Left;
if (IsKeyDown(InputType.Right)) newInput |= InputNetFlags.Right;
if (IsKeyDown(InputType.Up)) newInput |= InputNetFlags.Up;
if (IsKeyDown(InputType.Down)) newInput |= InputNetFlags.Down;
if (IsKeyDown(InputType.Run)) newInput |= InputNetFlags.Run;
if (IsKeyDown(InputType.Crouch)) newInput |= InputNetFlags.Crouch;
if (IsKeyHit(InputType.Select)) newInput |= InputNetFlags.Select; //TODO: clean up the way this input is registered
if (IsKeyDown(InputType.Use)) newInput |= InputNetFlags.Use;
if (IsKeyDown(InputType.Aim)) newInput |= InputNetFlags.Aim;
if (IsKeyDown(InputType.Attack)) newInput |= InputNetFlags.Attack;
if (AnimController.TargetDir == Direction.Left) newInput |= InputNetFlags.FacingLeft;
Vector2 relativeCursorPos = cursorPosition - (ViewTarget == null ? AnimController.Collider.Position : ViewTarget.Position);
relativeCursorPos.Normalize();
UInt16 intAngle = (UInt16)(65535.0 * Math.Atan2(relativeCursorPos.Y, relativeCursorPos.X) / (2.0 * Math.PI));
NetInputMem newMem = new NetInputMem();
newMem.states = newInput;
newMem.intAim = intAngle;
if (closestItem != null)
{
newMem.interact = closestItem.ID;
}
else if (closestCharacter != null)
{
newMem.interact = closestCharacter.ID;
}
memInput.Insert(0, newMem);
LastNetworkUpdateID++;
if (memInput.Count > 60)
{
memInput.RemoveRange(60, memInput.Count - 60);
}
}
else //this == Character.Controlled && GameMain.Client == null
{
AnimController.Frozen = false;
}
if (networkUpdateSent)
{
foreach (Key key in keys)
{
key.DequeueHit();
key.DequeueHeld();
}
networkUpdateSent = false;
}
}
public virtual void ServerRead(ClientNetObject type, NetBuffer msg, Client c)
{
if (GameMain.Server == null) return;
switch (type)
{
case ClientNetObject.CHARACTER_INPUT:
if (c.Character != this)
{
#if DEBUG
DebugConsole.Log("Received a character update message from a client who's not controlling the character");
#endif
return;
}
UInt16 networkUpdateID = msg.ReadUInt16();
byte inputCount = msg.ReadByte();
if (AllowInput) Enabled = true;
for (int i = 0; i < inputCount; i++)
{
InputNetFlags newInput = (InputNetFlags)msg.ReadRangedInteger(0, (int)InputNetFlags.MaxVal);
UInt16 newAim = 0;
UInt16 newInteract = 0;
if (newInput.HasFlag(InputNetFlags.Aim))
{
newAim = msg.ReadUInt16();
}
if (newInput.HasFlag(InputNetFlags.Select) || newInput.HasFlag(InputNetFlags.Use))
{
newInteract = msg.ReadUInt16();
}
if (AllowInput)
{
if (NetIdUtils.IdMoreRecent((ushort)(networkUpdateID - i), LastNetworkUpdateID) && (i < 60))
{
NetInputMem newMem = new NetInputMem();
newMem.states = newInput;
newMem.intAim = newAim;
newMem.interact = newInteract;
newMem.networkUpdateID = (ushort)(networkUpdateID - i);
memInput.Insert(i, newMem);
}
}
}
if (NetIdUtils.IdMoreRecent(networkUpdateID, LastNetworkUpdateID))
{
LastNetworkUpdateID = networkUpdateID;
}
if (memInput.Count > 60)
{
//deleting inputs from the queue here means the server is way behind and data needs to be dropped
//we'll make the server drop down to 30 inputs for good measure
memInput.RemoveRange(30, memInput.Count - 30);
}
break;
case ClientNetObject.ENTITY_STATE:
int eventType = msg.ReadRangedInteger(0,2);
switch (eventType)
{
case 0:
inventory.ServerRead(type, msg, c);
break;
case 1:
if (c.Character != this)
{
#if DEBUG
DebugConsole.Log("Received a character update message from a client who's not controlling the character");
#endif
return;
}
bool doingCPR = msg.ReadBoolean();
AnimController.Anim = doingCPR ? AnimController.Animation.CPR : AnimController.Animation.None;
break;
case 2:
if (c.Character != this)
{
#if DEBUG
DebugConsole.Log("Received a character update message from a client who's not controlling the character");
#endif
return;
}
if (IsUnconscious)
{
Kill(lastAttackCauseOfDeath);
}
break;
}
break;
}
msg.ReadPadBits();
}
public virtual void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
{
if (GameMain.Server == null) return;
if (extraData != null)
{
switch ((NetEntityEvent.Type)extraData[0])
{
case NetEntityEvent.Type.InventoryState:
msg.WriteRangedInteger(0, 2, 0);
inventory.ClientWrite(msg, extraData);
break;
case NetEntityEvent.Type.Control:
msg.WriteRangedInteger(0, 2, 1);
Client owner = ((Client)extraData[1]);
msg.Write(owner == null ? (byte)0 : owner.ID);
break;
case NetEntityEvent.Type.Status:
msg.WriteRangedInteger(0, 2, 2);
WriteStatus(msg);
break;
}
msg.WritePadBits();
}
else
{
msg.Write(ID);
NetBuffer tempBuffer = new NetBuffer();
if (this == c.Character)
{
tempBuffer.Write(true);
if (LastNetworkUpdateID < memInput.Count + 1)
{
tempBuffer.Write((UInt16)0);
}
else
{
tempBuffer.Write((UInt16)(LastNetworkUpdateID - memInput.Count - 1));
}
}
else
{
tempBuffer.Write(false);
bool aiming = false;
bool use = false;
bool attack = false;
if (IsRemotePlayer)
{
aiming = dequeuedInput.HasFlag(InputNetFlags.Aim);
use = dequeuedInput.HasFlag(InputNetFlags.Use);
attack = dequeuedInput.HasFlag(InputNetFlags.Attack);
}
else if (keys != null)
{
aiming = keys[(int)InputType.Aim].GetHeldQueue;
use = keys[(int)InputType.Use].GetHeldQueue;
attack = keys[(int)InputType.Attack].GetHeldQueue;
networkUpdateSent = true;
}
tempBuffer.Write(aiming);
tempBuffer.Write(use);
bool hasAttackLimb = AnimController.Limbs.Any(l => l != null && l.attack != null);
tempBuffer.Write(hasAttackLimb);
if (hasAttackLimb) tempBuffer.Write(attack);
if (aiming)
{
Vector2 relativeCursorPos = cursorPosition - (ViewTarget == null ? AnimController.Collider.Position : ViewTarget.Position);
tempBuffer.Write((UInt16)(65535.0 * Math.Atan2(relativeCursorPos.Y, relativeCursorPos.X) / (2.0 * Math.PI)));
}
tempBuffer.Write(AnimController.TargetDir == Direction.Right);
}
if (selectedCharacter != null || selectedConstruction != null)
{
tempBuffer.Write(true);
tempBuffer.Write(selectedCharacter != null ? selectedCharacter.ID : selectedConstruction.ID);
if (selectedCharacter != null)
{
tempBuffer.Write(AnimController.Anim == AnimController.Animation.CPR);
}
}
else
{
tempBuffer.Write(false);
}
tempBuffer.Write(SimPosition.X);
tempBuffer.Write(SimPosition.Y);
tempBuffer.WritePadBits();
msg.Write((byte)tempBuffer.LengthBytes);
msg.Write(tempBuffer);
}
}
private void WriteStatus(NetBuffer msg)
{
if (GameMain.Client != null)
{
DebugConsole.ThrowError("Client attempted to write character status to a networked message");
return;
}
msg.Write(isDead);
if (isDead)
{
msg.Write((byte)causeOfDeath);
List<int> severedJointIndices = new List<int>();
for (int i = 0; i < AnimController.LimbJoints.Length; i++)
{
if (AnimController.LimbJoints[i] != null && AnimController.LimbJoints[i].IsSevered)
{
severedJointIndices.Add(i);
}
}
msg.Write((byte)severedJointIndices.Count);
foreach (int jointIndex in severedJointIndices)
{
msg.Write((byte)jointIndex);
}
}
else
{
msg.WriteRangedSingle(health, minHealth, maxHealth, 8);
msg.Write(oxygen < 100.0f);
if (oxygen < 100.0f)
{
msg.WriteRangedSingle(oxygen, -100.0f, 100.0f, 8);
}
msg.Write(bleeding > 0.0f);
if (bleeding > 0.0f)
{
msg.WriteRangedSingle(bleeding, 0.0f, 5.0f, 8);
}
msg.Write(Stun > 0.0f);
if (Stun > 0.0f)
{
msg.WriteRangedSingle(MathHelper.Clamp(Stun, 0.0f, MaxStun), 0.0f, MaxStun, 8);
}
msg.Write(HuskInfectionState > 0.0f);
}
}
private void ReadStatus(NetBuffer msg)
{
if (GameMain.Server != null)
{
DebugConsole.ThrowError("Server attempted to read character status from a networked message");
return;
}
bool isDead = msg.ReadBoolean();
if (isDead)
{
causeOfDeath = (CauseOfDeath)msg.ReadByte();
byte severedLimbCount = msg.ReadByte();
if (causeOfDeath == CauseOfDeath.Pressure)
{
Implode(true);
}
else
{
Kill(causeOfDeath, true);
}
for (int i = 0; i < severedLimbCount; i++)
{
int severedJointIndex = msg.ReadByte();
AnimController.SeverLimbJoint(AnimController.LimbJoints[severedJointIndex]);
}
}
else
{
health = msg.ReadRangedSingle(minHealth, maxHealth, 8);
bool lowOxygen = msg.ReadBoolean();
if (lowOxygen)
{
Oxygen = msg.ReadRangedSingle(-100.0f, 100.0f, 8);
}
else
{
Oxygen = 100.0f;
}
bool isBleeding = msg.ReadBoolean();
if (isBleeding)
{
bleeding = msg.ReadRangedSingle(0.0f, 5.0f, 8);
}
else
{
bleeding = 0.0f;
}
bool stunned = msg.ReadBoolean();
if (stunned)
{
float newStunTimer = msg.ReadRangedSingle(0.0f, 60.0f, 8);
SetStun(newStunTimer, true, true);
}
else
{
SetStun(0.0f, true, true);
}
bool huskInfected = msg.ReadBoolean();
if (huskInfected)
{
HuskInfectionState = Math.Max(HuskInfectionState, 0.01f);
}
else
{
HuskInfectionState = 0.0f;
}
}
}
public void WriteSpawnData(NetBuffer msg)
{
if (GameMain.Server == null) return;
msg.Write(Info == null);
msg.Write(ID);
msg.Write(ConfigPath);
msg.Write(WorldPosition.X);
msg.Write(WorldPosition.Y);
msg.Write(Enabled);
//character with no characterinfo (e.g. some monster)
if (Info == null) return;
Client ownerClient = GameMain.Server.ConnectedClients.Find(c => c.Character == this);
if (ownerClient != null)
{
msg.Write(true);
msg.Write(ownerClient.ID);
}
else if (GameMain.Server.Character == this)
{
msg.Write(true);
msg.Write((byte)0);
}
else
{
msg.Write(false);
}
msg.Write(Info.Name);
msg.Write(TeamID);
msg.Write(this is AICharacter);
msg.Write(Info.Gender == Gender.Female);
msg.Write((byte)Info.HeadSpriteId);
if (info.Job != null)
{
msg.Write(Info.Job.Name);
msg.Write((byte)info.Job.Skills.Count);
foreach (Skill skill in info.Job.Skills)
{
msg.Write(skill.Name);
msg.WriteRangedInteger(0, 100, MathHelper.Clamp(skill.Level, 0, 100));
}
}
else
{
msg.Write("");
}
}
}
}
@@ -0,0 +1,52 @@
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma
{
class DelayedEffect : StatusEffect
{
public static List<DelayedEffect> List = new List<DelayedEffect>();
private float delay;
private float startTimer;
private Entity entity;
private List<IPropertyObject> targets;
public float StartTimer
{
get { return startTimer; }
}
public DelayedEffect(XElement element)
: base(element)
{
delay = ToolBox.GetAttributeFloat(element, "delay", 1.0f);
}
public override void Apply(ActionType type, float deltaTime, Entity entity, List<IPropertyObject> targets)
{
if (this.type != type) return;
startTimer = delay;
this.entity = entity;
this.targets = targets;
List.Add(this);
}
public void Update(float deltaTime)
{
startTimer -= deltaTime;
if (startTimer > 0.0f) return;
base.Apply(1.0f, entity, targets);
List.Remove(this);
}
}
}
@@ -0,0 +1,177 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
namespace Barotrauma
{
partial class HuskInfection
{
public enum InfectionState
{
Dormant, Transition, Active
}
const float IncubationDuration = 300.0f;
private InfectionState state;
private float incubationTimer;
public float IncubationTimer
{
get { return incubationTimer; }
set
{
incubationTimer = MathHelper.Clamp(value, 0.0f, 1.0f);
}
}
public InfectionState State
{
get { return state; }
}
public bool CanSpeak
{
get { return IncubationTimer < 0.5f; }
}
public HuskInfection(Character character)
{
character.OnDeath += CharacterDead;
}
public void Update(float deltaTime, Character character)
{
float prevTimer = IncubationTimer;
if (IncubationTimer < 0.5f)
{
UpdateDormantState(deltaTime, character);
}
else if (IncubationTimer < 1.0f)
{
UpdateTransitionState(deltaTime, character);
}
else
{
UpdateActiveState(deltaTime, character);
}
UpdateProjSpecific(prevTimer,character);
}
partial void UpdateProjSpecific(float prevTimer, Character character);
private void UpdateDormantState(float deltaTime, Character character)
{
float prevTimer = IncubationTimer;
state = InfectionState.Dormant;
IncubationTimer += deltaTime / IncubationDuration;
if (Character.Controlled != character) return;
}
private void UpdateTransitionState(float deltaTime, Character character)
{
IncubationTimer += deltaTime / IncubationDuration;
state = InfectionState.Transition;
}
private void UpdateActiveState(float deltaTime, Character character)
{
if (state != InfectionState.Active)
{
ActivateHusk(character);
state = InfectionState.Active;
}
character.AddDamage(CauseOfDeath.Husk, 0.5f*deltaTime, null);
}
private void ActivateHusk(Character character)
{
character.NeedsAir = false;
AttachHuskAppendage(character);
}
private void AttachHuskAppendage(Character character)
{
XDocument doc = ToolBox.TryLoadXml(Path.Combine("Content", "Characters", "Human", "huskappendage.xml"));
if (doc == null || doc.Root == null) return;
var limbElement = doc.Root.Element("limb");
if (limbElement == null)
{
DebugConsole.ThrowError("Error in huskappendage.xml - limb element not found");
return;
}
var jointElement = doc.Root.Element("joint");
if (jointElement == null)
{
DebugConsole.ThrowError("Error in huskappendage.xml - joint element not found");
return;
}
character.SetStun(0.5f);
if (character.AnimController.Dir < 1.0f)
{
character.AnimController.Flip();
}
var torso = character.AnimController.GetLimb(LimbType.Torso);
var newLimb = new Limb(character, limbElement);
newLimb.body.Submarine = character.Submarine;
newLimb.body.SetTransform(torso.SimPosition, torso.Rotation);
character.AnimController.AddLimb(newLimb);
character.AnimController.AddJoint(jointElement);
}
public void Remove(Character character)
{
if (character != null)
character.OnDeath -= CharacterDead;
}
private void CharacterDead(Character character, CauseOfDeath causeOfDeath)
{
if (GameMain.Client != null) return;
var husk = Character.Create(
Path.Combine("Content", "Characters", "Human", "humanhusk.xml"),
character.WorldPosition,
character.Info,
false, true);
foreach (Limb limb in husk.AnimController.Limbs)
{
if (limb.type == LimbType.None)
{
limb.body.SetTransform(character.SimPosition, 0.0f);
continue;
}
var matchingLimb = character.AnimController.GetLimb(limb.type);
limb.body.SetTransform(matchingLimb.SimPosition, matchingLimb.Rotation);
}
for (int i = 0; i < character.Inventory.Items.Length; i++)
{
if (character.Inventory.Items[i] == null) continue;
husk.Inventory.TryPutItem(character.Inventory.Items[i], i, true);
}
character.Enabled = false;
Entity.Spawner.AddToRemoveQueue(character);
}
}
}
@@ -0,0 +1,158 @@
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
class Job
{
private readonly JobPrefab prefab;
private Dictionary<string, Skill> skills;
public string Name
{
get { return prefab.Name; }
}
public string Description
{
get { return prefab.Description; }
}
public JobPrefab Prefab
{
get { return prefab; }
}
public XElement SpawnItems
{
get { return prefab.Items; }
}
//public List<bool> EquipSpawnItem
//{
// get { return prefab.EquipItem; }
//}
public List<Skill> Skills
{
get { return skills.Values.ToList(); }
}
public Job(JobPrefab jobPrefab)
{
prefab = jobPrefab;
skills = new Dictionary<string, Skill>();
foreach (SkillPrefab skillPrefab in prefab.Skills)
{
skills.Add(skillPrefab.Name, new Skill(skillPrefab));
}
}
public Job(XElement element)
{
string name = ToolBox.GetAttributeString(element, "name", "").ToLowerInvariant();
prefab = JobPrefab.List.Find(jp => jp.Name.ToLowerInvariant() == name);
skills = new Dictionary<string, Skill>();
foreach (XElement subElement in element.Elements())
{
if (subElement.Name.ToString().ToLowerInvariant() != "skill") continue;
string skillName = ToolBox.GetAttributeString(subElement, "name", "");
if (string.IsNullOrEmpty(name)) continue;
skills.Add(
skillName,
new Skill(skillName, ToolBox.GetAttributeInt(subElement, "level", 0)));
}
}
public static Job Random()
{
JobPrefab prefab = JobPrefab.List[Rand.Int(JobPrefab.List.Count - 1, Rand.RandSync.Server)];
return new Job(prefab);
}
public int GetSkillLevel(string skillName)
{
Skill skill = null;
skills.TryGetValue(skillName, out skill);
return (skill==null) ? 0 : skill.Level;
}
public void GiveJobItems(Character character, WayPoint spawnPoint)
{
if (SpawnItems == null) return;
foreach (XElement itemElement in SpawnItems.Elements())
{
InitializeJobItem(character, spawnPoint, itemElement);
}
}
private void InitializeJobItem(Character character, WayPoint spawnPoint, XElement itemElement, Item parentItem = null)
{
string itemName = ToolBox.GetAttributeString(itemElement, "name", "");
ItemPrefab itemPrefab = ItemPrefab.list.Find(ip => ip.Name == itemName) as ItemPrefab;
if (itemPrefab == null)
{
DebugConsole.ThrowError("Tried to spawn \"" + Name + "\" with the item \"" + itemName + "\". Matching item prefab not found.");
return;
}
Item item = new Item(itemPrefab, character.Position, null);
if (GameMain.Server != null && Entity.Spawner != null)
{
Entity.Spawner.CreateNetworkEvent(item, false);
}
if (ToolBox.GetAttributeBool(itemElement, "equip", false))
{
List<InvSlotType> allowedSlots = new List<InvSlotType>(item.AllowedSlots);
allowedSlots.Remove(InvSlotType.Any);
character.Inventory.TryPutItem(item, allowedSlots);
}
else
{
character.Inventory.TryPutItem(item, item.AllowedSlots);
}
if (item.Prefab.Name == "ID Card" && spawnPoint != null)
{
foreach (string s in spawnPoint.IdCardTags)
{
item.AddTag(s);
}
}
if (parentItem != null) parentItem.Combine(item);
foreach (XElement childItemElement in itemElement.Elements())
{
InitializeJobItem(character, spawnPoint, childItemElement, item);
}
}
public XElement Save(XElement parentElement)
{
XElement jobElement = new XElement("job");
jobElement.Add(new XAttribute("name", Name));
foreach (KeyValuePair<string, Skill> skill in skills)
{
jobElement.Add(new XElement("skill", new XAttribute("name", skill.Value.Name), new XAttribute("level", skill.Value.Level)));
}
parentElement.Add(jobElement);
return jobElement;
}
}
}
@@ -0,0 +1,122 @@
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Globalization;
using System.Xml.Linq;
using System.Linq;
namespace Barotrauma
{
partial class JobPrefab
{
public static List<JobPrefab> List;
public readonly XElement Items;
public readonly List<string> ItemNames;
public List<SkillPrefab> Skills;
public string Name
{
get;
private set;
}
public string Description
{
get;
private set;
}
//if set to true, a client that has chosen this as their preferred job will get it no matter what
public bool AllowAlways
{
get;
private set;
}
//how many crew members can have the job (only one captain etc)
public int MaxNumber
{
get;
private set;
}
//how many crew members are REQUIRED to have the job
//(i.e. if one captain is required, one captain is chosen even if all the players have set captain to lowest preference)
public int MinNumber
{
get;
private set;
}
public float Commonness
{
get;
private set;
}
public JobPrefab(XElement element)
{
Name = ToolBox.GetAttributeString(element, "name", "name not found");
Description = ToolBox.GetAttributeString(element, "description", "");
MinNumber = ToolBox.GetAttributeInt(element, "minnumber", 0);
MaxNumber = ToolBox.GetAttributeInt(element, "maxnumber", 10);
Commonness = ToolBox.GetAttributeInt(element, "commonness", 10);
AllowAlways = ToolBox.GetAttributeBool(element, "allowalways", false);
ItemNames = new List<string>();
Skills = new List<SkillPrefab>();
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "items":
Items = subElement;
foreach (XElement itemElement in subElement.Elements())
{
string itemName = ToolBox.GetAttributeString(itemElement, "name", "");
if (!string.IsNullOrWhiteSpace(itemName)) ItemNames.Add(itemName);
}
break;
case "skills":
foreach (XElement skillElement in subElement.Elements())
{
Skills.Add(new SkillPrefab(skillElement));
}
break;
}
}
Skills.Sort((x,y) => y.LevelRange.X.CompareTo(x.LevelRange.X));
}
public static JobPrefab Random()
{
return List[Rand.Int(List.Count)];
}
public static void LoadAll(List<string> filePaths)
{
List = new List<JobPrefab>();
foreach (string filePath in filePaths)
{
XDocument doc = ToolBox.TryLoadXml(filePath);
if (doc == null || doc.Root == null) return;
foreach (XElement element in doc.Root.Elements())
{
JobPrefab job = new JobPrefab(element);
List.Add(job);
}
}
}
}
}
@@ -0,0 +1,55 @@
using Microsoft.Xna.Framework;
using System;
namespace Barotrauma
{
class Skill
{
SkillPrefab prefab;
string name;
int level;
static string[] levelNames = new string[] {
"Untrained", "Incompetent", "Novice",
"Adequate", "Competent", "Proficient",
"Professional", "Master", "Legendary" };
public string Name
{
get { return name; }
}
public int Level
{
get { return level; }
set { level = MathHelper.Clamp(value, 0, 100); }
}
public Skill(SkillPrefab prefab)
{
this.prefab = prefab;
this.name = prefab.Name;
this.level = (int)Rand.Range(prefab.LevelRange.X, prefab.LevelRange.Y);
}
public Skill(string name, int level)
{
this.name = name;
this.level = level;
}
/// <summary>
/// returns the "name" of some skill level (0-10 -> untrained, etc)
/// </summary>
public static string GetLevelName(int level)
{
level = MathHelper.Clamp(level, 0, 100);
int scaledLevel = (int)Math.Floor((level / 100.0f) * levelNames.Length);
return levelNames[Math.Min(scaledLevel, levelNames.Length - 1)];
}
}
}
@@ -0,0 +1,47 @@
using Microsoft.Xna.Framework;
using System.Xml.Linq;
namespace Barotrauma
{
class SkillPrefab
{
private string name;
private string description;
private Vector2 levelRange;
public string Name
{
get { return name; }
}
public string Description
{
get { return description; }
}
public Vector2 LevelRange
{
get { return levelRange; }
}
public SkillPrefab(XElement element)
{
name = ToolBox.GetAttributeString(element, "name", "");
var levelString = ToolBox.GetAttributeString(element, "level", "");
if (levelString.Contains(","))
{
levelRange = ToolBox.ParseToVector2(levelString, false);
}
else
{
float skillLevel = float.Parse(levelString, System.Globalization.CultureInfo.InvariantCulture);
levelRange = new Vector2(skillLevel, skillLevel);
}
}
}
}
@@ -0,0 +1,536 @@
using System;
using System.Xml.Linq;
using FarseerPhysics;
using FarseerPhysics.Dynamics;
using FarseerPhysics.Dynamics.Joints;
using Microsoft.Xna.Framework;
//using Microsoft.Xna.Framework.Graphics;
using Barotrauma.Items.Components;
using System.Collections.Generic;
using System.Linq;
using System.IO;
namespace Barotrauma
{
public enum LimbType
{
None, LeftHand, RightHand, LeftArm, RightArm,
LeftLeg, RightLeg, LeftFoot, RightFoot, Head, Torso, Tail, Legs, RightThigh, LeftThigh, Waist
};
class LimbJoint : RevoluteJoint
{
public bool IsSevered;
public bool CanBeSevered;
public readonly Limb LimbA, LimbB;
public LimbJoint(Limb limbA, Limb limbB, Vector2 anchor1, Vector2 anchor2)
: base(limbA.body.FarseerBody, limbB.body.FarseerBody, anchor1, anchor2)
{
CollideConnected = false;
MotorEnabled = true;
MaxMotorTorque = 0.25f;
LimbA = limbA;
LimbB = limbB;
}
}
partial class Limb
{
private const float LimbDensity = 15;
private const float LimbAngularDamping = 7;
//how long it takes for severed limbs to fade out
private const float SeveredFadeOutTime = 10.0f;
public readonly Character character;
//the physics body of the limb
public PhysicsBody body;
private readonly int refJointIndex;
private readonly float steerForce;
private readonly bool doesFlip;
protected readonly Vector2 stepOffset;
public Sprite sprite, damagedSprite;
public bool inWater;
public FixedMouseJoint pullJoint;
public readonly LimbType type;
public readonly bool ignoreCollisions;
private float damage, burnt;
private bool isSevered;
private float severedFadeOutTimer;
private readonly Vector2 armorSector;
private readonly float armorValue;
public Vector2? MouthPos;
//a timer for delaying when a hitsound/attacksound can be played again
public float SoundTimer;
public const float SoundInterval = 0.4f;
public readonly Attack attack;
private Direction dir;
private List<WearableSprite> wearingItems;
private Vector2 animTargetPos;
private float scale;
public float AttackTimer;
public bool IsSevered
{
get { return isSevered; }
set
{
isSevered = value;
if (isSevered)
{
damage = 100.0f;
}
}
}
public bool DoesFlip
{
get { return doesFlip; }
}
public Vector2 WorldPosition
{
get { return character.Submarine == null ? Position : Position + character.Submarine.Position; }
}
public Vector2 Position
{
get { return ConvertUnits.ToDisplayUnits(body.SimPosition); }
}
public Vector2 SimPosition
{
get { return body.SimPosition; }
}
public float Rotation
{
get { return body.Rotation; }
}
public float Scale
{
get { return scale; }
}
//where an animcontroller is trying to pull the limb, only used for debug visualization
public Vector2 AnimTargetPos
{
get { return animTargetPos; }
}
public float SteerForce
{
get { return steerForce; }
}
public float Mass
{
get { return body.Mass; }
}
public bool Disabled { get; set; }
public Vector2 LinearVelocity
{
get { return body.LinearVelocity; }
}
public float Dir
{
get { return ((dir == Direction.Left) ? -1.0f : 1.0f); }
set { dir = (value==-1.0f) ? Direction.Left : Direction.Right; }
}
public int RefJointIndex
{
get { return refJointIndex; }
}
public Vector2 StepOffset
{
get { return stepOffset; }
}
public float Burnt
{
get { return burnt; }
set { burnt = MathHelper.Clamp(value,0.0f,100.0f); }
}
public List<WearableSprite> WearingItems
{
get { return wearingItems; }
}
public Limb (Character character, XElement element, float scale = 1.0f)
{
this.character = character;
wearingItems = new List<WearableSprite>();
dir = Direction.Right;
doesFlip = ToolBox.GetAttributeBool(element, "flip", false);
this.scale = scale;
body = new PhysicsBody(element, scale);
if (ToolBox.GetAttributeBool(element, "ignorecollisions", false))
{
body.CollisionCategories = Category.None;
body.CollidesWith = Category.None;
ignoreCollisions = true;
}
else
{
//limbs don't collide with each other
body.CollisionCategories = Physics.CollisionCharacter;
body.CollidesWith = Physics.CollisionAll & ~Physics.CollisionCharacter & ~Physics.CollisionItem;
}
body.UserData = this;
refJointIndex = -1;
Vector2 pullJointPos = Vector2.Zero;
if (element.Attribute("type") != null)
{
try
{
type = (LimbType)Enum.Parse(typeof(LimbType), element.Attribute("type").Value, true);
}
catch
{
type = LimbType.None;
DebugConsole.ThrowError("Error in "+element+"! \""+element.Attribute("type").Value+"\" is not a valid limb type");
}
pullJointPos = ToolBox.GetAttributeVector2(element, "pullpos", Vector2.Zero) * scale;
pullJointPos = ConvertUnits.ToSimUnits(pullJointPos);
stepOffset = ToolBox.GetAttributeVector2(element, "stepoffset", Vector2.Zero) * scale;
stepOffset = ConvertUnits.ToSimUnits(stepOffset);
refJointIndex = ToolBox.GetAttributeInt(element, "refjoint", -1);
}
else
{
type = LimbType.None;
}
pullJoint = new FixedMouseJoint(body.FarseerBody, pullJointPos);
pullJoint.Enabled = false;
pullJoint.MaxForce = ((type == LimbType.LeftHand || type == LimbType.RightHand) ? 400.0f : 150.0f) * body.Mass;
GameMain.World.AddJoint(pullJoint);
steerForce = ToolBox.GetAttributeFloat(element, "steerforce", 0.0f);
//maxHealth = Math.Max(ToolBox.GetAttributeFloat(element, "health", 100.0f),1.0f);
armorSector = ToolBox.GetAttributeVector2(element, "armorsector", Vector2.Zero);
armorSector.X = MathHelper.ToRadians(armorSector.X);
armorSector.Y = MathHelper.ToRadians(armorSector.Y);
armorValue = Math.Max(ToolBox.GetAttributeFloat(element, "armor", 0.0f), 0.0f);
if (element.Attribute("mouthpos") != null)
{
MouthPos = ConvertUnits.ToSimUnits(ToolBox.GetAttributeVector2(element, "mouthpos", Vector2.Zero));
}
body.BodyType = BodyType.Dynamic;
body.FarseerBody.AngularDamping = LimbAngularDamping;
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "sprite":
string spritePath = subElement.Attribute("texture").Value;
string spritePathWithTags = spritePath;
if (character.Info != null)
{
spritePath = spritePath.Replace("[GENDER]", (character.Info.Gender == Gender.Female) ? "f" : "");
spritePath = spritePath.Replace("[HEADID]", character.Info.HeadSpriteId.ToString());
if (character.Info.HeadSprite != null && character.Info.SpriteTags.Any())
{
string tags = "";
character.Info.SpriteTags.ForEach(tag => tags += "[" + tag + "]");
spritePathWithTags = Path.Combine(
Path.GetDirectoryName(spritePath),
Path.GetFileNameWithoutExtension(spritePath) + tags + Path.GetExtension(spritePath));
}
}
if (File.Exists(spritePathWithTags))
{
sprite = new Sprite(subElement, "", spritePathWithTags);
}
else
{
sprite = new Sprite(subElement, "", spritePath);
}
break;
case "damagedsprite":
string damagedSpritePath = subElement.Attribute("texture").Value;
if (character.Info != null)
{
damagedSpritePath = damagedSpritePath.Replace("[GENDER]", (character.Info.Gender == Gender.Female) ? "f" : "");
damagedSpritePath = damagedSpritePath.Replace("[HEADID]", character.Info.HeadSpriteId.ToString());
}
damagedSprite = new Sprite(subElement, "", damagedSpritePath);
break;
case "attack":
attack = new Attack(subElement);
break;
}
}
InitProjSpecific(element);
}
partial void InitProjSpecific(XElement element);
public void MoveToPos(Vector2 pos, float force, bool pullFromCenter=false)
{
Vector2 pullPos = body.SimPosition;
if (pullJoint!=null && !pullFromCenter)
{
pullPos = pullJoint.WorldAnchorA;
}
animTargetPos = pos;
body.MoveToPos(pos, force, pullPos);
}
public AttackResult AddDamage(Vector2 position, DamageType damageType, float amount, float bleedingAmount, bool playSound)
{
bool hitArmor = false;
float totalArmorValue = 0.0f;
if (armorValue > 0.0f && SectorHit(armorSector, position))
{
hitArmor = true;
totalArmorValue += armorValue;
}
foreach (WearableSprite wearable in wearingItems)
{
if (wearable.WearableComponent.ArmorValue > 0.0f &&
SectorHit(wearable.WearableComponent.ArmorSectorLimits, position))
{
hitArmor = true;
totalArmorValue += wearable.WearableComponent.ArmorValue;
}
}
#if CLIENT
float bloodAmount = hitArmor || bleedingAmount <= 0.0f ? 0 : (int)Math.Min((int)(amount * 2.0f), 20);
DamageSoundType damageSoundType = (damageType == DamageType.Blunt) ? DamageSoundType.LimbBlunt : DamageSoundType.LimbSlash;
if (hitArmor)
{
totalArmorValue = Math.Max(totalArmorValue, 0.0f);
damageSoundType = DamageSoundType.LimbArmor;
amount = Math.Max(0.0f, amount - totalArmorValue);
bleedingAmount = Math.Max(0.0f, bleedingAmount - totalArmorValue);
}
if (playSound)
{
SoundPlayer.PlayDamageSound(damageSoundType, amount, position);
}
float bloodParticleAmount = hitArmor || bleedingAmount <= 0.0f ? 0 : (int)Math.Min(amount / 5, 10);
float bloodParticleSize = MathHelper.Clamp(amount / 50.0f, 0.1f, 1.0f);
for (int i = 0; i < bloodParticleAmount; i++)
{
var blood = GameMain.ParticleManager.CreateParticle(inWater ? "waterblood" : "blood", WorldPosition, Vector2.Zero, 0.0f, character.AnimController.CurrentHull);
if (blood != null)
{
blood.Size *= bloodParticleSize;
}
}
#endif
damage += Math.Max(amount,bleedingAmount) / character.MaxHealth * 100.0f;
return new AttackResult(amount, bleedingAmount, hitArmor);
}
public bool SectorHit(Vector2 armorSector, Vector2 simPosition)
{
if (armorSector == Vector2.Zero) return false;
float rot = body.Rotation;
if (Dir == -1) rot -= MathHelper.Pi;
Vector2 armorLimits = new Vector2(rot - armorSector.X * Dir, rot - armorSector.Y * Dir);
float mid = (armorLimits.X + armorLimits.Y) / 2.0f;
float angleDiff = MathUtils.GetShortestAngle(MathUtils.VectorToAngle(simPosition - SimPosition), mid);
return (Math.Abs(angleDiff) < (armorSector.Y - armorSector.X) / 2.0f);
}
public void Update(float deltaTime)
{
UpdateProjSpecific();
if (!character.IsDead) damage = Math.Max(0.0f, damage - deltaTime * 0.1f);
if (burnt > 0.0f) Burnt -= deltaTime;
if (LinearVelocity.X > 500.0f)
{
//DebugConsole.ThrowError("CHARACTER EXPLODED");
body.ResetDynamics();
body.SetTransform(character.SimPosition, 0.0f);
}
if (inWater)
{
body.ApplyWaterForces();
}
if (isSevered)
{
severedFadeOutTimer += deltaTime;
if (severedFadeOutTimer > SeveredFadeOutTime)
{
body.Enabled = false;
}
}
if (character.IsDead) return;
damage = Math.Max(0.0f, damage - deltaTime * 0.1f);
SoundTimer -= deltaTime;
}
partial void UpdateProjSpecific();
public void ActivateDamagedSprite()
{
damage = 100.0f;
}
public void UpdateAttack(float deltaTime, Vector2 attackPosition, IDamageable damageTarget)
{
float dist = ConvertUnits.ToDisplayUnits(Vector2.Distance(SimPosition, attackPosition));
AttackTimer += deltaTime;
body.ApplyTorque(Mass * character.AnimController.Dir * attack.Torque);
if (dist < attack.Range * 0.5f)
{
if (AttackTimer >= attack.Duration && damageTarget != null)
{
attack.DoDamage(character, damageTarget, WorldPosition, 1.0f, (SoundTimer <= 0.0f));
SoundTimer = SoundInterval;
}
}
Vector2 diff = attackPosition - SimPosition;
if (diff.LengthSquared() < 0.00001f) return;
if (attack.ApplyForceOnLimbs != null)
{
foreach (int limbIndex in attack.ApplyForceOnLimbs)
{
if (limbIndex < 0 || limbIndex >= character.AnimController.Limbs.Length) continue;
Limb limb = character.AnimController.Limbs[limbIndex];
Vector2 forcePos = limb.pullJoint == null ? limb.body.SimPosition : limb.pullJoint.WorldAnchorA;
limb.body.ApplyLinearImpulse(
limb.Mass * attack.Force * Vector2.Normalize(attackPosition - SimPosition), forcePos);
}
}
else
{
Vector2 forcePos = pullJoint == null ? body.SimPosition : pullJoint.WorldAnchorA;
body.ApplyLinearImpulse(Mass * attack.Force *
Vector2.Normalize(attackPosition - SimPosition), forcePos);
}
}
public void Remove()
{
if (sprite != null)
{
sprite.Remove();
sprite = null;
}
if (damagedSprite != null)
{
damagedSprite.Remove();
damagedSprite = null;
}
if (body != null)
{
body.Remove();
body = null;
}
#if CLIENT
if (hitSound != null)
{
hitSound.Remove();
hitSound = null;
}
if (LightSource != null)
{
LightSource.Remove();
}
#endif
}
}
}
@@ -0,0 +1,352 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
#if CLIENT
using Barotrauma.Particles;
#endif
namespace Barotrauma
{
partial class StatusEffect
{
[Flags]
public enum TargetType
{
This = 1, Parent = 2, Character = 4, Contained = 8, Nearby = 16, UseTarget = 32, Hull = 64
}
private TargetType targetTypes;
private HashSet<string> targetNames;
private List<RelatedItem> requiredItems;
#if CLIENT
private List<ParticleEmitterPrefab> particleEmitters;
private Sound sound;
#endif
public string[] propertyNames;
private object[] propertyEffects;
private bool setValue;
private bool disableDeltaTime;
private HashSet<string> onContainingNames;
private readonly float duration;
private readonly bool useItem;
public readonly ActionType type;
private Explosion explosion;
public readonly float FireSize;
public TargetType Targets
{
get { return targetTypes; }
}
public HashSet<string> TargetNames
{
get { return targetNames; }
}
public HashSet<string> OnContainingNames
{
get { return onContainingNames; }
}
public static StatusEffect Load(XElement element)
{
if (element.Attribute("delay")!=null)
{
return new DelayedEffect(element);
}
return new StatusEffect(element);
}
protected StatusEffect(XElement element)
{
requiredItems = new List<RelatedItem>();
#if CLIENT
particleEmitters = new List<ParticleEmitterPrefab>();
#endif
IEnumerable<XAttribute> attributes = element.Attributes();
List<XAttribute> propertyAttributes = new List<XAttribute>();
foreach (XAttribute attribute in attributes)
{
switch (attribute.Name.ToString())
{
case "type":
try
{
type = (ActionType)Enum.Parse(typeof(ActionType), attribute.Value, true);
}
catch
{
string[] split = attribute.Value.Split('=');
type = (ActionType)Enum.Parse(typeof(ActionType), split[0], true);
string[] containingNames = split[1].Split(',');
onContainingNames = new HashSet<string>();
for (int i = 0; i < containingNames.Length; i++)
{
onContainingNames.Add(containingNames[i].Trim());
}
}
break;
case "target":
string[] Flags = attribute.Value.Split(',');
foreach (string s in Flags)
{
targetTypes |= (TargetType)Enum.Parse(typeof(TargetType), s, true);
}
break;
case "disabledeltatime":
disableDeltaTime = ToolBox.GetAttributeBool(attribute, false);
break;
case "setvalue":
setValue = ToolBox.GetAttributeBool(attribute, false);
break;
case "targetnames":
string[] names = attribute.Value.Split(',');
targetNames = new HashSet<string>();
for (int i=0; i < names.Length; i++ )
{
targetNames.Add(names[i].Trim());
}
break;
case "duration":
duration = ToolBox.GetAttributeFloat(attribute, 0.0f);
break;
#if CLIENT
case "sound":
sound = Sound.Load(attribute.Value.ToString());
break;
#endif
default:
propertyAttributes.Add(attribute);
break;
}
}
int count = propertyAttributes.Count;
propertyNames = new string[count];
propertyEffects = new object[count];
int n = 0;
foreach (XAttribute attribute in propertyAttributes)
{
propertyNames[n] = attribute.Name.ToString().ToLowerInvariant();
propertyEffects[n] = ToolBox.GetAttributeObject(attribute);
n++;
}
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "explosion":
explosion = new Explosion(subElement);
break;
case "fire":
FireSize = ToolBox.GetAttributeFloat(subElement,"size",10.0f);
break;
case "use":
case "useitem":
useItem = true;
break;
case "requireditem":
case "requireditems":
RelatedItem newRequiredItem = RelatedItem.Load(subElement);
if (newRequiredItem == null) continue;
requiredItems.Add(newRequiredItem);
break;
#if CLIENT
case "particleemitter":
particleEmitters.Add(new ParticleEmitterPrefab(subElement));
break;
#endif
}
}
}
private bool HasRequiredItems(Entity entity)
{
if (requiredItems == null) return true;
foreach (RelatedItem requiredItem in requiredItems)
{
Item item = entity as Item;
if (item != null)
{
if (!requiredItem.CheckRequirements(null, item)) return false;
}
Character character = entity as Character;
if (character != null)
{
if (!requiredItem.CheckRequirements(character, null)) return false;
}
}
return true;
}
public virtual void Apply(ActionType type, float deltaTime, Entity entity, IPropertyObject target)
{
if (this.type != type || !HasRequiredItems(entity)) return;
if (targetNames != null && !targetNames.Contains(target.Name)) return;
List<IPropertyObject> targets = new List<IPropertyObject>();
targets.Add(target);
Apply(deltaTime, entity, targets);
}
public virtual void Apply(ActionType type, float deltaTime, Entity entity, List<IPropertyObject> targets)
{
if (this.type != type || !HasRequiredItems(entity)) return;
Apply(deltaTime, entity, targets);
}
protected void Apply(float deltaTime, Entity entity, List<IPropertyObject> targets)
{
#if CLIENT
if (sound != null) sound.Play(1.0f, 1000.0f, entity.WorldPosition);
#endif
if (useItem)
{
foreach (Item item in targets.FindAll(t => t is Item).Cast<Item>())
{
item.Use(deltaTime, targets.FirstOrDefault(t => t is Character) as Character);
}
}
foreach (IPropertyObject target in targets)
{
for (int i = 0; i < propertyNames.Length; i++)
{
ObjectProperty property;
if (!target.ObjectProperties.TryGetValue(propertyNames[i], out property)) continue;
if (duration > 0.0f)
{
CoroutineManager.StartCoroutine(
ApplyToPropertyOverDuration(duration, property, propertyEffects[i]), "statuseffect");
}
else
{
ApplyToProperty(property, propertyEffects[i], deltaTime);
}
}
}
if (explosion != null) explosion.Explode(entity.WorldPosition);
Hull hull = null;
if (entity is Character)
{
hull = ((Character)entity).AnimController.CurrentHull;
}
else if (entity is Item)
{
hull = ((Item)entity).CurrentHull;
}
if (FireSize > 0.0f)
{
var fire = new FireSource(entity.WorldPosition, hull);
fire.Size = new Vector2(FireSize, fire.Size.Y);
}
#if CLIENT
foreach (ParticleEmitterPrefab emitter in particleEmitters)
{
emitter.Emit(entity.WorldPosition, hull);
}
#endif
}
private IEnumerable<object> ApplyToPropertyOverDuration(float duration, ObjectProperty property, object value)
{
float timer = duration;
while (timer > 0.0f)
{
ApplyToProperty(property, value, CoroutineManager.UnscaledDeltaTime);
timer -= CoroutineManager.DeltaTime;
yield return CoroutineStatus.Running;
}
yield return CoroutineStatus.Success;
}
private void ApplyToProperty(ObjectProperty property, object value, float deltaTime)
{
if (disableDeltaTime || setValue) deltaTime = 1.0f;
Type type = value.GetType();
if (type == typeof(float) ||
(type == typeof(int) && property.GetValue() is float))
{
float floatValue = Convert.ToSingle(value) * deltaTime;
if (!setValue) floatValue += (float)property.GetValue();
property.TrySetValue(floatValue);
}
else if (type == typeof(int) && value is int)
{
int intValue = (int)((int)value * deltaTime);
if (!setValue) intValue += (int)property.GetValue();
property.TrySetValue(intValue);
}
else if (type == typeof(bool) && value is bool)
{
property.TrySetValue((bool)value);
}
else if (type == typeof(string))
{
property.TrySetValue((string)value);
}
else
{
DebugConsole.ThrowError("Couldn't apply value "+value.ToString()+" ("+type+") to property \""+property.Name+"\" ("+property.GetValue().GetType()+")! "
+"Make sure the type of the value set in the config files matches the type of the property.");
}
}
public static void UpdateAll(float deltaTime)
{
for (int i = DelayedEffect.List.Count-1; i>= 0; i--)
{
DelayedEffect.List[i].Update(deltaTime);
}
}
public static void StopAll()
{
CoroutineManager.StopCoroutines("statuseffect");
}
}
}