Updated to MonoGame 3.6 + Directory refactor
- Barotrauma's projects are in the Barotrauma directory - All libraries are in the Libraries directory - MonoGame is now managed by NuGet, rather than referenced from the installed files (TODO: consider using PCL for easier cross-platform development?) - NuGet libraries are not included in the repo, as getting the latest versions automatically should be preferred - Removed Content/effects.mgfx as it didn't seem to be used anywhere - Removed some references to Subsurface directory - Renamed Launcher2 to Launcher
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
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 }
|
||||
public enum SteeringState { Wander, Seek, Escape }
|
||||
|
||||
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,50 @@
|
||||
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 readonly Entity Entity;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,520 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Xml.Linq;
|
||||
using FarseerPhysics;
|
||||
using Lidgren.Network;
|
||||
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;
|
||||
|
||||
private SteeringManager outsideSteering, insideSteering;
|
||||
|
||||
private float updateTargetsTimer;
|
||||
|
||||
private float raycastTimer;
|
||||
|
||||
//a timer for attacks such as biting that last for a specific amount of time
|
||||
//the duration is determined by the attackDuration of the attacking limb
|
||||
private float attackTimer;
|
||||
|
||||
//a "cooldown time" after an attack during which the Character doesn't try to attack again
|
||||
private float attackCoolDown;
|
||||
private float coolDownTimer;
|
||||
|
||||
//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;
|
||||
|
||||
private AITarget selectedAiTarget;
|
||||
private AITargetMemory selectedTargetMemory;
|
||||
private float targetValue;
|
||||
|
||||
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, "attackrooms", 0.0f) / 100.0f;
|
||||
attackHumans = ToolBox.GetAttributeFloat(aiElement, "attackhumans", 0.0f) / 100.0f;
|
||||
attackWeaker = ToolBox.GetAttributeFloat(aiElement, "attackweaker", 0.0f) / 100.0f;
|
||||
attackStronger = ToolBox.GetAttributeFloat(aiElement, "attackstronger", 0.0f) / 100.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);
|
||||
|
||||
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
|
||||
{
|
||||
state = (targetValue > 0.0f) ? AiState.Attack : AiState.Escape;
|
||||
}
|
||||
//if (coolDownTimer >= 0.0f) return;
|
||||
}
|
||||
|
||||
steeringManager = Character.Submarine == null ? outsideSteering : insideSteering;
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case AiState.None:
|
||||
UpdateNone(deltaTime);
|
||||
break;
|
||||
case AiState.Attack:
|
||||
UpdateAttack(deltaTime);
|
||||
break;
|
||||
}
|
||||
|
||||
steeringManager.Update();
|
||||
}
|
||||
|
||||
private void UpdateNone(float deltaTime)
|
||||
{
|
||||
//wander around randomly
|
||||
if (Character.Submarine==null && SimPosition.Y < ConvertUnits.ToSimUnits(SubmarineBody.DamageDepth*0.5f))
|
||||
{
|
||||
steeringManager.SteeringManual(deltaTime, Vector2.UnitY);
|
||||
}
|
||||
else
|
||||
{
|
||||
steeringManager.SteeringAvoid(deltaTime, 0.1f);
|
||||
steeringManager.SteeringWander(0.5f);
|
||||
}
|
||||
|
||||
attackingLimb = null;
|
||||
attackTimer = 0.0f;
|
||||
|
||||
coolDownTimer -= deltaTime;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
if (steeringManager is IndoorsSteeringManager)
|
||||
{
|
||||
var indoorsSteering = (IndoorsSteeringManager)steeringManager;
|
||||
if (indoorsSteering.CurrentPath!=null && (indoorsSteering.CurrentPath.Finished || indoorsSteering.CurrentPath.Unreachable))
|
||||
{
|
||||
steeringManager.SteeringManual(deltaTime, attackSimPosition - attackLimb.SimPosition);
|
||||
}
|
||||
}
|
||||
|
||||
if (attackingLimb != null) UpdateLimbAttack(deltaTime, attackingLimb, attackSimPosition);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateCoolDown(Vector2 attackPosition, float deltaTime)
|
||||
{
|
||||
coolDownTimer -= deltaTime;
|
||||
attackingLimb = null;
|
||||
|
||||
if (selectedAiTarget.Entity is Hull ||
|
||||
Vector2.Distance(attackPosition, Character.AnimController.Limbs[0].SimPosition) < ConvertUnits.ToSimUnits(500.0f))
|
||||
{
|
||||
steeringManager.SteeringSeek(attackPosition, -0.8f);
|
||||
steeringManager.SteeringAvoid(deltaTime, 1.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
steeringManager.SteeringSeek(attackPosition, -0.5f);
|
||||
steeringManager.SteeringAvoid(deltaTime, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
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.1f && attackWhenProvoked)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
//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;
|
||||
|
||||
IDamageable targetDamageable = target.Entity as IDamageable;
|
||||
if (targetDamageable!=null && targetDamageable.Health <= 0.0f) continue;
|
||||
|
||||
Character targetCharacter = target.Entity as Character;
|
||||
|
||||
//ignore the aitarget if it is the Character itself
|
||||
if (targetCharacter == character) continue;
|
||||
|
||||
if (targetCharacter!=null)
|
||||
{
|
||||
if (attackHumans == 0.0f || targetCharacter.SpeciesName != "human") continue;
|
||||
|
||||
valueModifier = attackHumans;
|
||||
}
|
||||
else if (target.Entity!=null && attackRooms != 0.0f)
|
||||
{
|
||||
//skip the target if it's the room the Character is inside of
|
||||
if (character.AnimController.CurrentHull != null && character.AnimController.CurrentHull == target.Entity as Hull) continue;
|
||||
|
||||
valueModifier = attackRooms;
|
||||
}
|
||||
|
||||
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.1f;
|
||||
|
||||
//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 (targetDamageable != null)
|
||||
{
|
||||
valueModifier = valueModifier / targetDamageable.Health;
|
||||
}
|
||||
else if (closestStructure!=null)
|
||||
{
|
||||
valueModifier = valueModifier / ((IDamageable)closestStructure).Health;
|
||||
}
|
||||
else
|
||||
{
|
||||
valueModifier = valueModifier / 1000.0f;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
+89
@@ -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,44 @@
|
||||
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 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);
|
||||
|
||||
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, LimbType rightHandTarget = LimbType.RightHand, LimbType leftHandTarget = LimbType.LeftHand) { }
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,341 @@
|
||||
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 bool rotateTowardsMovement;
|
||||
|
||||
private bool mirror, flip;
|
||||
|
||||
private float flipTimer;
|
||||
|
||||
private float? footRotation;
|
||||
|
||||
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));
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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, 25.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
Collider.SmoothRotate(HeadAngle * Dir, 25.0f);
|
||||
MainLimb.body.SmoothRotate(HeadAngle * Dir, 25.0f);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
/*if (Limbs[i] == MainLimb) continue;
|
||||
|
||||
float dist = (MainLimb.SimPosition - Limbs[i].SimPosition).Length();
|
||||
|
||||
Vector2 limbPos = MainLimb.SimPosition - Vector2.Normalize(movement) * dist;
|
||||
|
||||
Limbs[i].body.ApplyForce(((limbPos - Limbs[i].SimPosition) * 3.0f - Limbs[i].LinearVelocity * 3.0f) * Limbs[i].Mass);*/
|
||||
}
|
||||
|
||||
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);
|
||||
if (movement == Vector2.Zero) return;
|
||||
|
||||
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.body.ApplyTorque(head.Mass * Dir * (float)Math.Sin(walkPos) * 5.0f);
|
||||
if (tail != null) tail.body.ApplyTorque(tail.Mass * -Dir * (float)Math.Sin(walkPos) * 5.0f);
|
||||
|
||||
walkPos += deltaTime * 5.0f;
|
||||
|
||||
Vector2 centerOfMass = GetCenterOfMass();
|
||||
|
||||
foreach (Limb limb in Limbs)
|
||||
{
|
||||
if (limb.type == LimbType.Head || limb.type == LimbType.Tail) continue;
|
||||
|
||||
limb.body.ApplyForce((centerOfMass - limb.SimPosition) * (float)Math.Sin(walkPos) * 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
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,155 @@
|
||||
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 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);
|
||||
|
||||
Force = ToolBox.GetAttributeFloat(element,"force", 0.0f);
|
||||
TargetForce = ToolBox.GetAttributeFloat(element, "targetforce", 0.0f);
|
||||
|
||||
Torque = ToolBox.GetAttributeFloat(element, "torque", 0.0f);
|
||||
|
||||
Stun = ToolBox.GetAttributeFloat(element, "stun", 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);
|
||||
|
||||
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,617 @@
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
msg.ReadPadBits();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
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();
|
||||
if (causeOfDeath == CauseOfDeath.Pressure)
|
||||
{
|
||||
Implode(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
Kill(causeOfDeath, true);
|
||||
}
|
||||
}
|
||||
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,502 @@
|
||||
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
|
||||
};
|
||||
|
||||
partial class Limb
|
||||
{
|
||||
private const float LimbDensity = 15;
|
||||
private const float LimbAngularDamping = 7;
|
||||
|
||||
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 readonly Vector2 armorSector;
|
||||
private readonly float armorValue;
|
||||
|
||||
//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;
|
||||
|
||||
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; }
|
||||
}
|
||||
|
||||
//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); }
|
||||
}
|
||||
|
||||
private float scale;
|
||||
|
||||
public float AttackTimer;
|
||||
|
||||
//public float Damage
|
||||
//{
|
||||
// get { return damage; }
|
||||
// set
|
||||
// {
|
||||
// damage = Math.Max(value, 0.0f);
|
||||
// if (damage >=maxHealth) Character.Kill();
|
||||
// }
|
||||
//}
|
||||
|
||||
//public float MaxHealth
|
||||
//{
|
||||
// get { return maxHealth; }
|
||||
//}
|
||||
|
||||
//public float Bleeding
|
||||
//{
|
||||
// get { return bleeding; }
|
||||
// set { bleeding = MathHelper.Clamp(value, 0.0f, 100.0f); }
|
||||
//}
|
||||
|
||||
public List<WearableSprite> WearingItems
|
||||
{
|
||||
get { return wearingItems; }
|
||||
set { wearingItems = value; }
|
||||
}
|
||||
|
||||
//public WearableSprite WearingItemSprite
|
||||
//{
|
||||
// get { return wearingItemSprite; }
|
||||
// set { wearingItemSprite = value; }
|
||||
//}
|
||||
|
||||
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);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
//Bleeding += bleedingAmount;
|
||||
//Damage += amount;
|
||||
|
||||
#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);
|
||||
}
|
||||
|
||||
for (int i = 0; i < bloodAmount; i++)
|
||||
{
|
||||
Vector2 particleVel = SimPosition - position;
|
||||
if (particleVel != Vector2.Zero) particleVel = Vector2.Normalize(particleVel);
|
||||
|
||||
GameMain.ParticleManager.CreateParticle("blood",
|
||||
WorldPosition,
|
||||
particleVel * Rand.Range(100.0f, 300.0f), 0.0f, character.AnimController.CurrentHull);
|
||||
}
|
||||
|
||||
for (int i = 0; i < bloodAmount / 2; i++)
|
||||
{
|
||||
GameMain.ParticleManager.CreateParticle("waterblood", WorldPosition, Vector2.Zero, 0.0f, character.AnimController.CurrentHull);
|
||||
}
|
||||
#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 (character.IsDead) return;
|
||||
|
||||
soundTimer -= deltaTime;
|
||||
|
||||
//if (MathUtils.RandomFloat(0.0f, 1000.0f) < Bleeding)
|
||||
//{
|
||||
// Game1.particleManager.CreateParticle(
|
||||
// !inWater ? "blood" : "waterblood",
|
||||
// SimPosition, Vector2.Zero);
|
||||
//}
|
||||
}
|
||||
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 = Limb.SoundInterval;
|
||||
}
|
||||
}
|
||||
|
||||
Vector2 diff = attackPosition - SimPosition;
|
||||
if (diff.LengthSquared() > 0.00001f)
|
||||
{
|
||||
Vector2 pos = pullJoint == null ? body.SimPosition : pullJoint.WorldAnchorA;
|
||||
body.ApplyLinearImpulse(Mass * attack.Force *
|
||||
Vector2.Normalize(attackPosition - SimPosition), pos);
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public enum ContentType
|
||||
{
|
||||
None,
|
||||
Jobs,
|
||||
Item,
|
||||
Character,
|
||||
Structure,
|
||||
Executable,
|
||||
LocationTypes,
|
||||
LevelGenerationParameters,
|
||||
RandomEvents,
|
||||
Missions,
|
||||
BackgroundCreaturePrefabs, BackgroundSpritePrefabs
|
||||
}
|
||||
|
||||
public class ContentPackage
|
||||
{
|
||||
|
||||
public static string Folder = "Data/ContentPackages/";
|
||||
|
||||
public static List<ContentPackage> list = new List<ContentPackage>();
|
||||
|
||||
|
||||
string name;
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return name; }
|
||||
}
|
||||
|
||||
public string Path
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
private Md5Hash md5Hash;
|
||||
|
||||
public Md5Hash MD5hash
|
||||
{
|
||||
get
|
||||
{
|
||||
if (md5Hash == null) CalculateHash();
|
||||
return md5Hash;
|
||||
}
|
||||
}
|
||||
|
||||
public List<ContentFile> files;
|
||||
|
||||
private ContentPackage()
|
||||
{
|
||||
files = new List<ContentFile>();
|
||||
}
|
||||
|
||||
public ContentPackage(string filePath)
|
||||
: this()
|
||||
{
|
||||
XDocument doc = ToolBox.TryLoadXml(filePath);
|
||||
|
||||
Path = filePath;
|
||||
|
||||
if (doc==null)
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't load content package \""+filePath+"\"!");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
name = ToolBox.GetAttributeString(doc.Root, "name", "");
|
||||
|
||||
foreach (XElement subElement in doc.Root.Elements())
|
||||
{
|
||||
ContentType type = (ContentType)Enum.Parse(typeof(ContentType), subElement.Name.ToString(), true);
|
||||
files.Add(new ContentFile(ToolBox.GetAttributeString(subElement, "file", ""), type));
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
public static ContentPackage CreatePackage(string name)
|
||||
{
|
||||
ContentPackage newPackage = new ContentPackage("Content/Data/"+name);
|
||||
newPackage.name = name;
|
||||
newPackage.Path = Folder + name;
|
||||
list.Add(newPackage);
|
||||
|
||||
return newPackage;
|
||||
}
|
||||
|
||||
public ContentFile AddFile(string path, ContentType type)
|
||||
{
|
||||
if (files.Find(file => file.path == path && file.type == type) != null) return null;
|
||||
|
||||
ContentFile cf = new ContentFile(path, type);
|
||||
files.Add(cf);
|
||||
|
||||
return cf;
|
||||
}
|
||||
|
||||
public void RemoveFile(ContentFile file)
|
||||
{
|
||||
files.Remove(file);
|
||||
}
|
||||
|
||||
public void Save(string filePath)
|
||||
{
|
||||
XDocument doc = new XDocument();
|
||||
doc.Add(new XElement("contentpackage",
|
||||
new XAttribute("name", name),
|
||||
new XAttribute("path", Path)));
|
||||
|
||||
//doc.Root.Add(
|
||||
// new XElement("jobs", new XAttribute("file", JobFile)),
|
||||
// new XElement("structures", new XAttribute("file", StructureFile)));
|
||||
|
||||
foreach (ContentFile file in files)
|
||||
{
|
||||
doc.Root.Add(new XElement(file.type.ToString(), new XAttribute("file", file.path)));
|
||||
}
|
||||
|
||||
//foreach (string itemFile in itemFiles)
|
||||
//{
|
||||
// doc.Root.Add(new XElement("item", new XAttribute("file", itemFile)));
|
||||
//}
|
||||
doc.Save(System.IO.Path.Combine(filePath, name+".xml"));
|
||||
}
|
||||
|
||||
private void CalculateHash()
|
||||
{
|
||||
List<byte[]> hashes = new List<byte[]>();
|
||||
|
||||
//foreach (ContentFile file in files)
|
||||
//{
|
||||
// if (file.path.EndsWith(".xml", true, System.Globalization.CultureInfo.InvariantCulture))
|
||||
// {
|
||||
// XDocument doc = ToolBox.TryLoadXml(file.path);
|
||||
// sb.Append(doc.ToString());
|
||||
// }
|
||||
//}
|
||||
var md5 = MD5.Create();
|
||||
foreach (ContentFile file in files)
|
||||
{
|
||||
if (file.type == ContentType.Executable) continue;
|
||||
|
||||
try
|
||||
{
|
||||
using (var stream = File.OpenRead(file.path))
|
||||
{
|
||||
hashes.Add(md5.ComputeHash(stream));
|
||||
}
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Error while calculating content package hash: ", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//string str = sb.ToString();
|
||||
byte[] bytes = new byte[hashes.Count*16];
|
||||
for (int i = 0; i < hashes.Count; i++ )
|
||||
{
|
||||
hashes[i].CopyTo(bytes, i*16);
|
||||
}
|
||||
//System.Buffer.BlockCopy(str.ToCharArray(), 0, bytes, 0, bytes.Length);
|
||||
|
||||
md5Hash = new Md5Hash(bytes);
|
||||
}
|
||||
|
||||
public List<string> GetFilesOfType(ContentType type)
|
||||
{
|
||||
List<ContentFile> contentFiles = files.FindAll(f => f.type == type);
|
||||
|
||||
List<string> filePaths = new List<string>();
|
||||
foreach (ContentFile contentFile in contentFiles)
|
||||
{
|
||||
filePaths.Add(contentFile.path);
|
||||
}
|
||||
return filePaths;
|
||||
}
|
||||
|
||||
public static void LoadAll(string folder)
|
||||
{
|
||||
if (!Directory.Exists(folder))
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(folder);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
string[] files = Directory.GetFiles(folder, "*.xml");
|
||||
|
||||
list.Clear();
|
||||
|
||||
foreach (string filePath in files)
|
||||
{
|
||||
ContentPackage package = new ContentPackage(filePath);
|
||||
list.Add(package);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ContentFile
|
||||
{
|
||||
public string path;
|
||||
public ContentType type;
|
||||
|
||||
public ContentFile(string path, ContentType type)
|
||||
{
|
||||
Directory.GetCurrentDirectory();
|
||||
//Path.get
|
||||
this.path = path;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
enum CoroutineStatus
|
||||
{
|
||||
Running, Success, Failure
|
||||
}
|
||||
|
||||
class CoroutineHandle
|
||||
{
|
||||
public readonly IEnumerator<object> Coroutine;
|
||||
public readonly string Name;
|
||||
|
||||
public CoroutineHandle(IEnumerator<object> coroutine, string name = "")
|
||||
{
|
||||
Coroutine = coroutine;
|
||||
Name = string.IsNullOrWhiteSpace(name) ? coroutine.ToString() : name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Keeps track of all running coroutines, and runs them till the end.
|
||||
static class CoroutineManager
|
||||
{
|
||||
static readonly List<CoroutineHandle> Coroutines = new List<CoroutineHandle>();
|
||||
|
||||
public static float UnscaledDeltaTime, DeltaTime;
|
||||
|
||||
public static CoroutineHandle StartCoroutine(IEnumerable<object> func, string name = "")
|
||||
{
|
||||
var handle = new CoroutineHandle(func.GetEnumerator(), name);
|
||||
Coroutines.Add(handle);
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
public static bool IsCoroutineRunning(string name)
|
||||
{
|
||||
return Coroutines.Any(c => c.Name == name);
|
||||
}
|
||||
|
||||
public static bool IsCoroutineRunning(CoroutineHandle handle)
|
||||
{
|
||||
return Coroutines.Contains(handle);
|
||||
}
|
||||
|
||||
public static void StopCoroutines(string name)
|
||||
{
|
||||
Coroutines.RemoveAll(c => c.Name == name);
|
||||
}
|
||||
|
||||
public static void StopCoroutines(CoroutineHandle handle)
|
||||
{
|
||||
Coroutines.RemoveAll(c => c == handle);
|
||||
}
|
||||
private static bool IsDone(CoroutineHandle handle)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (handle.Coroutine.Current != null)
|
||||
{
|
||||
WaitForSeconds wfs = handle.Coroutine.Current as WaitForSeconds;
|
||||
if (wfs != null)
|
||||
{
|
||||
if (!wfs.CheckFinished(UnscaledDeltaTime)) return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
switch ((CoroutineStatus)handle.Coroutine.Current)
|
||||
{
|
||||
case CoroutineStatus.Success:
|
||||
return true;
|
||||
|
||||
case CoroutineStatus.Failure:
|
||||
DebugConsole.ThrowError("Coroutine \"" + handle.Name + "\" has failed");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handle.Coroutine.MoveNext();
|
||||
return false;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Coroutine " + handle.Name + " threw an exception: " + e.Message + "\n" + e.StackTrace.ToString());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Updating just means stepping through all the coroutines
|
||||
public static void Update(float unscaledDeltaTime, float deltaTime)
|
||||
{
|
||||
UnscaledDeltaTime = unscaledDeltaTime;
|
||||
DeltaTime = deltaTime;
|
||||
|
||||
foreach (var x in Coroutines.ToList())
|
||||
if(IsDone(x))
|
||||
Coroutines.Remove(x);
|
||||
}
|
||||
}
|
||||
|
||||
class WaitForSeconds
|
||||
{
|
||||
float timer;
|
||||
|
||||
public WaitForSeconds(float time)
|
||||
{
|
||||
timer = time;
|
||||
}
|
||||
|
||||
public bool CheckFinished(float deltaTime)
|
||||
{
|
||||
timer -= deltaTime;
|
||||
return timer<=0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,628 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.Items.Components;
|
||||
using System.Text;
|
||||
using FarseerPhysics;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
struct ColoredText
|
||||
{
|
||||
public string Text;
|
||||
public Color Color;
|
||||
|
||||
public readonly string Time;
|
||||
|
||||
public ColoredText(string text, Color color)
|
||||
{
|
||||
this.Text = text;
|
||||
this.Color = color;
|
||||
|
||||
Time = DateTime.Now.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
static partial class DebugConsole
|
||||
{
|
||||
const int MaxMessages = 200;
|
||||
|
||||
public static List<ColoredText> Messages = new List<ColoredText>();
|
||||
|
||||
private static string[] SplitCommand(string command)
|
||||
{
|
||||
command = command.Trim();
|
||||
|
||||
List<string> commands = new List<string>();
|
||||
int escape = 0;
|
||||
bool inQuotes = false;
|
||||
string piece = "";
|
||||
|
||||
for (int i=0;i<command.Length;i++)
|
||||
{
|
||||
if (command[i] == '\\')
|
||||
{
|
||||
if (escape == 0) escape = 2;
|
||||
else piece += '\\';
|
||||
}
|
||||
else if (command[i] == '"')
|
||||
{
|
||||
if (escape == 0) inQuotes = !inQuotes;
|
||||
else piece += '"';
|
||||
}
|
||||
else if (command[i] == ' ' && !inQuotes)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(piece)) commands.Add(piece);
|
||||
piece = "";
|
||||
}
|
||||
else if (escape == 0) piece += command[i];
|
||||
|
||||
if (escape > 0) escape--;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(piece)) commands.Add(piece); //add final piece
|
||||
|
||||
return commands.ToArray();
|
||||
}
|
||||
|
||||
public static void ExecuteCommand(string command, GameMain game)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(command)) return;
|
||||
string[] commands = SplitCommand(command);
|
||||
|
||||
if (!commands[0].ToLowerInvariant().Equals("admin"))
|
||||
{
|
||||
NewMessage(command, Color.White);
|
||||
}
|
||||
|
||||
#if !DEBUG
|
||||
#if CLIENT
|
||||
if (GameMain.Client != null && !IsCommandPermitted(commands[0].ToLowerInvariant(), GameMain.Client))
|
||||
{
|
||||
ThrowError("You're not permitted to use the command \"" + commands[0].ToLowerInvariant()+"\"!");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
switch (commands[0].ToLowerInvariant())
|
||||
{
|
||||
case "clientlist":
|
||||
if (GameMain.Server == null) break;
|
||||
DebugConsole.NewMessage("***************", Color.Cyan);
|
||||
foreach (Client c in GameMain.Server.ConnectedClients)
|
||||
{
|
||||
DebugConsole.NewMessage("- " + c.ID.ToString() + ": " + c.name + ", " + c.Connection.RemoteEndPoint.Address.ToString(),Color.Cyan);
|
||||
}
|
||||
DebugConsole.NewMessage("***************", Color.Cyan);
|
||||
break;
|
||||
case "help":
|
||||
NewMessage("menu: go to main menu", Color.Cyan);
|
||||
NewMessage("game: enter the \"game screen\"", Color.Cyan);
|
||||
NewMessage("edit: switch to submarine editor", Color.Cyan);
|
||||
NewMessage("edit [submarine name]: load a submarine and switch to submarine editor", Color.Cyan);
|
||||
NewMessage("load [submarine name]: load a submarine", Color.Cyan);
|
||||
NewMessage("save [submarine name]: save the current submarine using the specified name", Color.Cyan);
|
||||
|
||||
NewMessage(" ", Color.Cyan);
|
||||
|
||||
NewMessage("spawn [creaturename] [near/inside/outside]: spawn a creature at a random spawnpoint (use the second parameter to only select spawnpoints near/inside/outside the submarine)", Color.Cyan);
|
||||
NewMessage("spawnitem [itemname] [cursor/inventory]: spawn an item at the position of the cursor, in the inventory of the controlled character or at a random spawnpoint if the last parameter is omitted", Color.Cyan);
|
||||
|
||||
NewMessage(" ", Color.Cyan);
|
||||
|
||||
NewMessage("lights: disable lighting", Color.Cyan);
|
||||
NewMessage("los: disable the line of sight effect", Color.Cyan);
|
||||
NewMessage("freecam: detach the camera from the controlled character", Color.Cyan);
|
||||
NewMessage("control [character name]: start controlling the specified character", Color.Cyan);
|
||||
|
||||
NewMessage(" ", Color.Cyan);
|
||||
|
||||
NewMessage("water: allows adding water into rooms or removing it by holding the left/right mouse buttons", Color.Cyan);
|
||||
NewMessage("fire: allows putting up fires by left clicking", Color.Cyan);
|
||||
|
||||
NewMessage(" ", Color.Cyan);
|
||||
|
||||
NewMessage("teleport: teleport the controlled character to the position of the cursor", Color.Cyan);
|
||||
NewMessage("teleport [character name]: teleport the specified character to the position of the cursor", Color.Cyan);
|
||||
NewMessage("heal: restore the controlled character to full health", Color.Cyan);
|
||||
NewMessage("heal [character name]: restore the specified character to full health", Color.Cyan);
|
||||
NewMessage("revive: bring the controlled character back from the dead", Color.Cyan);
|
||||
NewMessage("revive [character name]: bring the specified character back from the dead", Color.Cyan);
|
||||
NewMessage("killmonsters: immediately kills all AI-controlled enemies in the level", Color.Cyan);
|
||||
|
||||
NewMessage(" ", Color.Cyan);
|
||||
|
||||
NewMessage("fixwalls: fixes all the walls", Color.Cyan);
|
||||
NewMessage("fixitems: fixes every item/device in the sub", Color.Cyan);
|
||||
NewMessage("oxygen: replenishes the oxygen in every room to 100%", Color.Cyan);
|
||||
NewMessage("power [amount]: immediately sets the temperature of the reactor to the specified value", Color.Cyan);
|
||||
|
||||
NewMessage(" ", Color.Cyan);
|
||||
|
||||
NewMessage("kick [name]: kick a player out from the server", Color.Cyan);
|
||||
NewMessage("ban [name]: kick and ban the player from the server", Color.Cyan);
|
||||
NewMessage("banip [IP address]: ban the IP address from the server", Color.Cyan);
|
||||
NewMessage("debugdraw: toggles the \"debug draw mode\"", Color.Cyan);
|
||||
NewMessage("netstats: toggles the visibility of the network statistics panel", Color.Cyan);
|
||||
|
||||
break;
|
||||
case "createfilelist":
|
||||
UpdaterUtil.SaveFileList("filelist.xml");
|
||||
break;
|
||||
case "spawn":
|
||||
case "spawncharacter":
|
||||
if (commands.Length == 1) return;
|
||||
|
||||
Character spawnedCharacter = null;
|
||||
|
||||
Vector2 spawnPosition = Vector2.Zero;
|
||||
WayPoint spawnPoint = null;
|
||||
|
||||
if (commands.Length > 2)
|
||||
{
|
||||
switch (commands[2].ToLowerInvariant())
|
||||
{
|
||||
case "inside":
|
||||
spawnPoint = WayPoint.GetRandom(SpawnType.Human, null, Submarine.MainSub);
|
||||
break;
|
||||
case "outside":
|
||||
spawnPoint = WayPoint.GetRandom(SpawnType.Enemy);
|
||||
break;
|
||||
case "near":
|
||||
case "close":
|
||||
float closestDist = -1.0f;
|
||||
foreach (WayPoint wp in WayPoint.WayPointList)
|
||||
{
|
||||
if (wp.Submarine != null) continue;
|
||||
|
||||
//don't spawn inside hulls
|
||||
if (Hull.FindHull(wp.WorldPosition, null) != null) continue;
|
||||
|
||||
float dist = Vector2.Distance(wp.WorldPosition, GameMain.GameScreen.Cam.WorldViewCenter);
|
||||
|
||||
if (closestDist < 0.0f || dist < closestDist)
|
||||
{
|
||||
spawnPoint = wp;
|
||||
closestDist = dist;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "cursor":
|
||||
spawnPosition = GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition);
|
||||
break;
|
||||
default:
|
||||
spawnPoint = WayPoint.GetRandom(commands[1].ToLowerInvariant() == "human" ? SpawnType.Human : SpawnType.Enemy);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
spawnPoint = WayPoint.GetRandom(commands[1].ToLowerInvariant() == "human" ? SpawnType.Human : SpawnType.Enemy);
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(commands[1])) return;
|
||||
|
||||
if (spawnPoint != null) spawnPosition = spawnPoint.WorldPosition;
|
||||
|
||||
if (commands[1].ToLowerInvariant()=="human")
|
||||
{
|
||||
spawnedCharacter = Character.Create(Character.HumanConfigFile, spawnPosition);
|
||||
|
||||
#if CLIENT
|
||||
if (GameMain.GameSession != null)
|
||||
{
|
||||
SinglePlayerMode mode = GameMain.GameSession.gameMode as SinglePlayerMode;
|
||||
if (mode != null)
|
||||
{
|
||||
Character.Controlled = spawnedCharacter;
|
||||
GameMain.GameSession.CrewManager.AddCharacter(Character.Controlled);
|
||||
GameMain.GameSession.CrewManager.SelectCharacter(null, Character.Controlled);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
spawnedCharacter = Character.Create(
|
||||
"Content/Characters/"
|
||||
+ commands[1].First().ToString().ToUpper() + commands[1].Substring(1)
|
||||
+ "/" + commands[1].ToLower() + ".xml", spawnPosition);
|
||||
}
|
||||
|
||||
break;
|
||||
case "spawnitem":
|
||||
if (commands.Length < 2) return;
|
||||
|
||||
Vector2? spawnPos = null;
|
||||
Inventory spawnInventory = null;
|
||||
|
||||
int extraParams = 0;
|
||||
switch (commands.Last())
|
||||
{
|
||||
case "cursor":
|
||||
extraParams = 1;
|
||||
spawnPos = GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition);
|
||||
break;
|
||||
case "inventory":
|
||||
extraParams = 1;
|
||||
spawnInventory = Character.Controlled == null ? null : Character.Controlled.Inventory;
|
||||
break;
|
||||
default:
|
||||
extraParams = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
string itemName = string.Join(" ", commands.Skip(1).Take(commands.Length - extraParams - 1)).ToLowerInvariant();
|
||||
|
||||
var itemPrefab = MapEntityPrefab.list.Find(ip => ip.Name.ToLowerInvariant() == itemName) as ItemPrefab;
|
||||
if (itemPrefab == null)
|
||||
{
|
||||
ThrowError("Item \""+itemName+"\" not found!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (spawnPos == null && spawnInventory == null)
|
||||
{
|
||||
var wp = WayPoint.GetRandom(SpawnType.Human, null, Submarine.MainSub);
|
||||
spawnPos = wp == null ? Vector2.Zero : wp.WorldPosition;
|
||||
}
|
||||
|
||||
if (spawnPos != null)
|
||||
{
|
||||
Item.Spawner.AddToSpawnQueue(itemPrefab, (Vector2)spawnPos);
|
||||
|
||||
}
|
||||
else if (spawnInventory != null)
|
||||
{
|
||||
Item.Spawner.AddToSpawnQueue(itemPrefab, spawnInventory);
|
||||
}
|
||||
|
||||
break;
|
||||
case "disablecrewai":
|
||||
HumanAIController.DisableCrewAI = !HumanAIController.DisableCrewAI;
|
||||
break;
|
||||
case "enablecrewai":
|
||||
HumanAIController.DisableCrewAI = false;
|
||||
break;
|
||||
/*case "admin":
|
||||
if (commands.Length < 2) break;
|
||||
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
GameMain.Server.AdminAuthPass = commands[1];
|
||||
|
||||
}
|
||||
else if (GameMain.Client != null)
|
||||
{
|
||||
GameMain.Client.RequestAdminAuth(commands[1]);
|
||||
}
|
||||
break;*/
|
||||
case "kick":
|
||||
if (GameMain.NetworkMember == null || commands.Length < 2) break;
|
||||
GameMain.NetworkMember.KickPlayer(string.Join(" ", commands.Skip(1)), false);
|
||||
|
||||
break;
|
||||
case "kickid":
|
||||
if (GameMain.Server == null || commands.Length < 2) break;
|
||||
|
||||
{
|
||||
int id = 0;
|
||||
int.TryParse(commands[1], out id);
|
||||
GameMain.Server.KickPlayer(id, false);
|
||||
}
|
||||
break;
|
||||
case "banid":
|
||||
if (GameMain.Server == null || commands.Length < 2) break;
|
||||
|
||||
{
|
||||
int id = 0;
|
||||
int.TryParse(commands[1], out id);
|
||||
GameMain.Server.KickPlayer(id, true);
|
||||
}
|
||||
|
||||
break;
|
||||
case "ban":
|
||||
if (GameMain.NetworkMember == null || commands.Length < 2) break;
|
||||
GameMain.NetworkMember.KickPlayer(string.Join(" ", commands.Skip(1)), true);
|
||||
|
||||
break;
|
||||
case "banip":
|
||||
{
|
||||
if (GameMain.Server == null || commands.Length < 2) break;
|
||||
|
||||
var client = GameMain.Server.ConnectedClients.Find(c => c.Connection.RemoteEndPoint.Address.ToString() == commands[1]);
|
||||
if (client == null)
|
||||
{
|
||||
GameMain.Server.BanList.BanPlayer("Unnamed", commands[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
GameMain.Server.KickClient(client, true);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "teleportcharacter":
|
||||
case "teleport":
|
||||
var tpCharacter = FindMatchingCharacter(commands, false);
|
||||
|
||||
if (commands.Length < 2)
|
||||
{
|
||||
tpCharacter = Character.Controlled;
|
||||
}
|
||||
|
||||
if (tpCharacter != null)
|
||||
{
|
||||
var cam = GameMain.GameScreen.Cam;
|
||||
tpCharacter.AnimController.CurrentHull = null;
|
||||
tpCharacter.Submarine = null;
|
||||
tpCharacter.AnimController.SetPosition(ConvertUnits.ToSimUnits(cam.ScreenToWorld(PlayerInput.MousePosition)));
|
||||
tpCharacter.AnimController.FindHull(cam.ScreenToWorld(PlayerInput.MousePosition), true);
|
||||
}
|
||||
break;
|
||||
case "godmode":
|
||||
if (Submarine.MainSub == null) return;
|
||||
|
||||
Submarine.MainSub.GodMode = !Submarine.MainSub.GodMode;
|
||||
break;
|
||||
case "lockx":
|
||||
Submarine.LockX = !Submarine.LockX;
|
||||
break;
|
||||
case "locky":
|
||||
Submarine.LockY = !Submarine.LockY;
|
||||
break;
|
||||
case "dumpids":
|
||||
try
|
||||
{
|
||||
int count = commands.Length < 2 ? 10 : int.Parse(commands[1]);
|
||||
Entity.DumpIds(count);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case "heal":
|
||||
Character healedCharacter = null;
|
||||
if (commands.Length == 1)
|
||||
{
|
||||
healedCharacter = Character.Controlled;
|
||||
}
|
||||
else
|
||||
{
|
||||
healedCharacter = FindMatchingCharacter(commands);
|
||||
}
|
||||
|
||||
if (healedCharacter != null)
|
||||
{
|
||||
healedCharacter.AddDamage(CauseOfDeath.Damage, -healedCharacter.MaxHealth, null);
|
||||
healedCharacter.Oxygen = 100.0f;
|
||||
healedCharacter.Bleeding = 0.0f;
|
||||
healedCharacter.SetStun(0.0f, true);
|
||||
}
|
||||
|
||||
break;
|
||||
case "revive":
|
||||
Character revivedCharacter = null;
|
||||
if (commands.Length == 1)
|
||||
{
|
||||
revivedCharacter = Character.Controlled;
|
||||
}
|
||||
else
|
||||
{
|
||||
revivedCharacter = FindMatchingCharacter(commands);
|
||||
}
|
||||
|
||||
if (revivedCharacter != null)
|
||||
{
|
||||
revivedCharacter.Revive(false);
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
foreach (Client c in GameMain.Server.ConnectedClients)
|
||||
{
|
||||
if (c.Character != revivedCharacter) continue;
|
||||
//clients stop controlling the character when it dies, force control back
|
||||
GameMain.Server.SetClientCharacter(c, revivedCharacter);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "freeze":
|
||||
if (Character.Controlled != null) Character.Controlled.AnimController.Frozen = !Character.Controlled.AnimController.Frozen;
|
||||
break;
|
||||
case "freecamera":
|
||||
case "freecam":
|
||||
Character.Controlled = null;
|
||||
GameMain.GameScreen.Cam.TargetPos = Vector2.Zero;
|
||||
break;
|
||||
case "editwater":
|
||||
case "water":
|
||||
if (GameMain.Client == null) Hull.EditWater = !Hull.EditWater;
|
||||
|
||||
break;
|
||||
case "explosion":
|
||||
Vector2 explosionPos = GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition);
|
||||
float range = 500, force = 10, damage=50;
|
||||
if (commands.Length > 1) float.TryParse(commands[1], out range);
|
||||
if (commands.Length > 2) float.TryParse(commands[2], out force);
|
||||
if (commands.Length > 3) float.TryParse(commands[3], out damage);
|
||||
new Explosion(range, force, damage, damage).Explode(explosionPos);
|
||||
break;
|
||||
case "fire":
|
||||
if (GameMain.Client == null) Hull.EditFire = !Hull.EditFire;
|
||||
|
||||
break;
|
||||
case "fixitems":
|
||||
foreach (Item it in Item.ItemList)
|
||||
{
|
||||
it.Condition = 100.0f;
|
||||
}
|
||||
break;
|
||||
case "fixhull":
|
||||
case "fixwalls":
|
||||
foreach (Structure w in Structure.WallList)
|
||||
{
|
||||
for (int i = 0 ; i < w.SectionCount; i++)
|
||||
{
|
||||
w.AddDamage(i, -100000.0f);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "power":
|
||||
Item reactorItem = Item.ItemList.Find(i => i.GetComponent<Reactor>() != null);
|
||||
if (reactorItem == null) return;
|
||||
|
||||
float power = 5000.0f;
|
||||
if (commands.Length>1) float.TryParse(commands[1], out power);
|
||||
|
||||
var reactor = reactorItem.GetComponent<Reactor>();
|
||||
reactor.ShutDownTemp = power == 0 ? 0 : 7000.0f;
|
||||
reactor.AutoTemp = true;
|
||||
reactor.Temperature = power;
|
||||
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
reactorItem.CreateServerEvent(reactor);
|
||||
}
|
||||
break;
|
||||
case "oxygen":
|
||||
case "air":
|
||||
foreach (Hull hull in Hull.hullList)
|
||||
{
|
||||
hull.OxygenPercentage = 100.0f;
|
||||
}
|
||||
break;
|
||||
|
||||
case "killmonsters":
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (!(c.AIController is EnemyAIController)) continue;
|
||||
c.AddDamage(CauseOfDeath.Damage, 10000.0f, null);
|
||||
}
|
||||
break;
|
||||
case "netstats":
|
||||
if (GameMain.Server == null) return;
|
||||
|
||||
GameMain.Server.ShowNetStats = !GameMain.Server.ShowNetStats;
|
||||
break;
|
||||
|
||||
default:
|
||||
if (!ExecProjSpecific(commands)) NewMessage("Command not found", Color.Red);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static Character FindMatchingCharacter(string[] commands, bool ignoreRemotePlayers = false)
|
||||
{
|
||||
if (commands.Length < 2) return null;
|
||||
|
||||
int characterIndex;
|
||||
string characterName;
|
||||
if (int.TryParse(commands.Last(), out characterIndex) && commands.Length > 2)
|
||||
{
|
||||
characterName = string.Join(" ", commands.Skip(1).Take(commands.Length - 2)).ToLowerInvariant();
|
||||
}
|
||||
else
|
||||
{
|
||||
characterName = string.Join(" ", commands.Skip(1)).ToLowerInvariant();
|
||||
characterIndex = -1;
|
||||
}
|
||||
|
||||
var matchingCharacters = Character.CharacterList.FindAll(c => (!ignoreRemotePlayers || !c.IsRemotePlayer) && c.Name.ToLowerInvariant() == characterName);
|
||||
|
||||
if (!matchingCharacters.Any())
|
||||
{
|
||||
NewMessage("Matching characters not found", Color.Red);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (characterIndex == -1)
|
||||
{
|
||||
if (matchingCharacters.Count > 1)
|
||||
{
|
||||
NewMessage(
|
||||
"Found multiple matching characters. " +
|
||||
"Use \"" + commands[0] + " [charactername] [0-" + (matchingCharacters.Count - 1) + "]\" to choose a specific character.",
|
||||
Color.LightGray);
|
||||
}
|
||||
return matchingCharacters[0];
|
||||
}
|
||||
else if (characterIndex < 0 || characterIndex >= matchingCharacters.Count)
|
||||
{
|
||||
ThrowError("Character index out of range. Select an index between 0 and " + (matchingCharacters.Count - 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
return matchingCharacters[characterIndex];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void NewMessage(string msg, Color color)
|
||||
{
|
||||
if (String.IsNullOrEmpty((msg))) return;
|
||||
|
||||
Messages.Add(new ColoredText(msg, color));
|
||||
#if SERVER
|
||||
//TODO: REMOVE
|
||||
Console.ForegroundColor = XnaToConsoleColor.Convert(color);
|
||||
Console.WriteLine(msg);
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
#endif
|
||||
|
||||
if (Messages.Count > MaxMessages)
|
||||
{
|
||||
Messages.RemoveRange(0, Messages.Count - MaxMessages);
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
//listbox not created yet, don't attempt to add
|
||||
if (listBox == null) return;
|
||||
|
||||
if (listBox.children.Count > MaxMessages)
|
||||
{
|
||||
listBox.children.RemoveRange(0, listBox.children.Count - MaxMessages);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var textBlock = new GUITextBlock(new Rectangle(0, 0, listBox.Rect.Width, 0), msg, "", Alignment.TopLeft, Alignment.Left, null, true, GUI.SmallFont);
|
||||
textBlock.CanBeFocused = false;
|
||||
textBlock.TextColor = color;
|
||||
|
||||
listBox.AddChild(textBlock);
|
||||
listBox.BarScroll = 1.0f;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
selectedIndex = listBox.children.Count;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void Log(string message)
|
||||
{
|
||||
if (GameSettings.VerboseLogging) NewMessage(message, Color.Gray);
|
||||
}
|
||||
|
||||
public static void ThrowError(string error, Exception e = null)
|
||||
{
|
||||
if (e != null)
|
||||
{
|
||||
error += " {" + e.Message + "}\n" + e.StackTrace;
|
||||
}
|
||||
System.Diagnostics.Debug.WriteLine(error);
|
||||
NewMessage(error, Color.Red);
|
||||
#if CLIENT
|
||||
isOpen = true;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class ArtifactEvent : ScriptedEvent
|
||||
{
|
||||
private ItemPrefab itemPrefab;
|
||||
|
||||
private Item item;
|
||||
|
||||
private int state;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "ScriptedEvent (" + (itemPrefab==null ? "null" : itemPrefab.Name) + ")";
|
||||
}
|
||||
|
||||
public ArtifactEvent(XElement element)
|
||||
: base(element)
|
||||
{
|
||||
string itemName = ToolBox.GetAttributeString(element, "itemname", "");
|
||||
|
||||
itemPrefab = ItemPrefab.list.Find(ip => ip.Name == itemName) as ItemPrefab;
|
||||
|
||||
if (itemPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in SalvageMission: couldn't find an item prefab with the name "+itemName);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
|
||||
Vector2 position = Level.Loaded.GetRandomItemPos(
|
||||
Level.PositionType.Cave | Level.PositionType.MainPath | Level.PositionType.Ruin, 500.0f, 30.0f);
|
||||
|
||||
item = new Item(itemPrefab, position, null);
|
||||
item.MoveWithLevel = true;
|
||||
item.body.FarseerBody.IsKinematic = true;
|
||||
|
||||
//try to find a nearby artifact holder (or any alien itemcontainer) and place the artifact inside it
|
||||
foreach (Item it in Item.ItemList)
|
||||
{
|
||||
if (it.Submarine != null || !it.HasTag("alien")) continue;
|
||||
|
||||
if (Math.Abs(item.WorldPosition.X - it.WorldPosition.X) > 2000.0f) continue;
|
||||
if (Math.Abs(item.WorldPosition.Y - it.WorldPosition.Y) > 2000.0f) continue;
|
||||
|
||||
var itemContainer = it.GetComponent<Items.Components.ItemContainer>();
|
||||
if (itemContainer == null) continue;
|
||||
|
||||
itemContainer.Combine(item);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case 0:
|
||||
if (item.ParentInventory!=null) item.body.FarseerBody.IsKinematic = false;
|
||||
if (item.CurrentHull == null) return;
|
||||
|
||||
state = 1;
|
||||
break;
|
||||
case 1:
|
||||
if (!Submarine.MainSub.AtEndPosition && !Submarine.MainSub.AtStartPosition) return;
|
||||
|
||||
Finished();
|
||||
state = 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class CargoMission : Mission
|
||||
{
|
||||
private XElement itemConfig;
|
||||
|
||||
private List<Item> items;
|
||||
|
||||
private int requiredDeliveryAmount;
|
||||
|
||||
public CargoMission(XElement element, Location[] locations)
|
||||
: base(element, locations)
|
||||
{
|
||||
itemConfig = element.Element("Items");
|
||||
|
||||
requiredDeliveryAmount = ToolBox.GetAttributeInt(element, "requireddeliveryamount", 0);
|
||||
}
|
||||
|
||||
private void InitItems()
|
||||
{
|
||||
items = new List<Item>();
|
||||
|
||||
if (itemConfig==null)
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to initialize items for cargo mission (itemConfig == null)");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (XElement subElement in itemConfig.Elements())
|
||||
{
|
||||
LoadItemAsChild(subElement, null);
|
||||
}
|
||||
|
||||
if (requiredDeliveryAmount == 0) requiredDeliveryAmount = items.Count;
|
||||
}
|
||||
|
||||
private void LoadItemAsChild(XElement element, Item parent)
|
||||
{
|
||||
string itemName = ToolBox.GetAttributeString(element, "name", "");
|
||||
|
||||
ItemPrefab itemPrefab = ItemPrefab.list.Find(ip => ip.Name == itemName) as ItemPrefab;
|
||||
if (itemPrefab==null)
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't spawn item for cargo mission: item prefab \""+element.Name.ToString()+"\" not found");
|
||||
return;
|
||||
}
|
||||
|
||||
WayPoint cargoSpawnPos = WayPoint.GetRandom(SpawnType.Cargo, null, Submarine.MainSub, true);
|
||||
if (cargoSpawnPos==null)
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't spawn items for cargo mission, cargo spawnpoint not found");
|
||||
return;
|
||||
}
|
||||
|
||||
var cargoRoom = cargoSpawnPos.CurrentHull;
|
||||
|
||||
if (cargoRoom == null)
|
||||
{
|
||||
DebugConsole.ThrowError("A waypoint marked as Cargo must be placed inside a room!");
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2 position = new Vector2(
|
||||
cargoSpawnPos.Position.X + Rand.Range(-20.0f, 20.0f, Rand.RandSync.Server),
|
||||
cargoRoom.Rect.Y - cargoRoom.Rect.Height + itemPrefab.Size.Y / 2);
|
||||
|
||||
var item = new Item(itemPrefab, position, cargoRoom.Submarine);
|
||||
item.FindHull();
|
||||
|
||||
|
||||
items.Add(item);
|
||||
|
||||
if (parent != null) parent.Combine(item);
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
int amount = ToolBox.GetAttributeInt(subElement, "amount", 1);
|
||||
for (int i = 0; i < amount; i++)
|
||||
{
|
||||
LoadItemAsChild(subElement, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Start(Level level)
|
||||
{
|
||||
InitItems();
|
||||
}
|
||||
|
||||
public override void End()
|
||||
{
|
||||
if (Submarine.MainSub != null && Submarine.MainSub.AtEndPosition)
|
||||
{
|
||||
int deliveredItemCount = items.Count(i => i.CurrentHull != null && i.Condition > 0.0f);
|
||||
|
||||
if (deliveredItemCount >= requiredDeliveryAmount)
|
||||
{
|
||||
GiveReward();
|
||||
|
||||
completed = true;
|
||||
}
|
||||
}
|
||||
|
||||
items.ForEach(i => i.Remove());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class CombatMission : Mission
|
||||
{
|
||||
private Submarine[] subs;
|
||||
private List<Character>[] crews;
|
||||
|
||||
private int state = 0;
|
||||
private int winner = -1;
|
||||
|
||||
private string[] descriptions;
|
||||
|
||||
private static string[] teamNames = { "Team A", "Team B" };
|
||||
|
||||
private bool initialized = false;
|
||||
|
||||
public override bool AllowRespawn
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public override string Description
|
||||
{
|
||||
get
|
||||
{
|
||||
if (GameMain.NetworkMember==null || GameMain.NetworkMember.Character==null)
|
||||
{
|
||||
//non-team-specific description
|
||||
return descriptions[0];
|
||||
}
|
||||
|
||||
//team specific
|
||||
return descriptions[GameMain.NetworkMember.Character.TeamID];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override string SuccessMessage
|
||||
{
|
||||
get
|
||||
{
|
||||
if (winner == -1) return "";
|
||||
|
||||
return successMessage
|
||||
.Replace("[loser]", teamNames[1 - winner])
|
||||
.Replace("[winner]", teamNames[winner]);
|
||||
}
|
||||
}
|
||||
|
||||
public CombatMission(XElement element, Location[] locations)
|
||||
: base(element, locations)
|
||||
{
|
||||
descriptions = new string[]
|
||||
{
|
||||
ToolBox.GetAttributeString(element, "descriptionneutral", ""),
|
||||
ToolBox.GetAttributeString(element, "description1", ""),
|
||||
ToolBox.GetAttributeString(element, "description2", "")
|
||||
};
|
||||
|
||||
for (int i = 0; i < descriptions.Length; i++)
|
||||
{
|
||||
for (int n = 0; n < 2; n++)
|
||||
{
|
||||
descriptions[i] = descriptions[i].Replace("[location" + (n + 1) + "]", locations[n].Name);
|
||||
}
|
||||
}
|
||||
|
||||
teamNames = new string[]
|
||||
{
|
||||
ToolBox.GetAttributeString(element, "teamname1", "Team A"),
|
||||
ToolBox.GetAttributeString(element, "teamname2", "Team B")
|
||||
};
|
||||
}
|
||||
|
||||
public static string GetTeamName(int teamID)
|
||||
{
|
||||
//team IDs start from 1, while teamName array starts from 0
|
||||
teamID--;
|
||||
|
||||
if (teamID < 0 || teamID >= teamNames.Length)
|
||||
{
|
||||
return "Team " + teamID;
|
||||
}
|
||||
|
||||
return teamNames[teamID];
|
||||
}
|
||||
|
||||
public override bool AssignTeamIDs(List<Client> clients, out byte hostTeam)
|
||||
{
|
||||
List<Client> randList = new List<Client>(clients);
|
||||
for (int i = 0; i < randList.Count; i++)
|
||||
{
|
||||
Client a = randList[i];
|
||||
int oi = Rand.Range(0, randList.Count - 1);
|
||||
Client b = randList[oi];
|
||||
randList[i] = b;
|
||||
randList[oi] = a;
|
||||
}
|
||||
int halfPlayers = randList.Count / 2;
|
||||
for (int i = 0; i < randList.Count; i++)
|
||||
{
|
||||
if (i < halfPlayers)
|
||||
{
|
||||
randList[i].TeamID = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
randList[i].TeamID = 2;
|
||||
}
|
||||
}
|
||||
if (halfPlayers * 2 == randList.Count)
|
||||
{
|
||||
hostTeam = (byte)Rand.Range(1, 2);
|
||||
}
|
||||
else if (halfPlayers * 2 < randList.Count)
|
||||
{
|
||||
hostTeam = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
hostTeam = 2;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void Start(Level level)
|
||||
{
|
||||
if (GameMain.NetworkMember == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Combat missions cannot be played in the single player mode.");
|
||||
return;
|
||||
}
|
||||
|
||||
subs = new Submarine[] { Submarine.MainSubs[0], Submarine.MainSubs[1] };
|
||||
subs[1].SetPosition(Level.Loaded.EndPosition - new Vector2(0.0f, 2000.0f));
|
||||
subs[1].FlipX();
|
||||
|
||||
crews = new List<Character>[] { new List<Character>(), new List<Character>() };
|
||||
|
||||
foreach (Submarine submarine in Submarine.Loaded)
|
||||
{
|
||||
//hide all subs from radar to make sneak attacks possible
|
||||
submarine.OnRadar = false;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (!initialized)
|
||||
{
|
||||
crews[0].Clear();
|
||||
crews[1].Clear();
|
||||
foreach (Character character in Character.CharacterList)
|
||||
{
|
||||
if (character.TeamID == 1)
|
||||
{
|
||||
crews[0].Add(character);
|
||||
}
|
||||
else if (character.TeamID == 2)
|
||||
{
|
||||
crews[1].Add(character);
|
||||
}
|
||||
}
|
||||
|
||||
if (GameMain.Client != null)
|
||||
{
|
||||
//no characters in one of the teams, the client may not have received all spawn messages yet
|
||||
if (crews[0].Count == 0 || crews[1].Count == 0) return;
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
bool[] teamDead =
|
||||
{
|
||||
crews[0].All(c => c.IsDead || c.IsUnconscious),
|
||||
crews[1].All(c => c.IsDead || c.IsUnconscious)
|
||||
};
|
||||
|
||||
if (state == 0)
|
||||
{
|
||||
for (int i = 0; i < teamDead.Length; i++)
|
||||
{
|
||||
if (!teamDead[i] && teamDead[1-i])
|
||||
{
|
||||
//make sure nobody in the other team can be revived because that would be pretty weird
|
||||
crews[1-i].ForEach(c => { if (!c.IsDead) c.Kill(CauseOfDeath.Damage); });
|
||||
|
||||
winner = i;
|
||||
|
||||
#if CLIENT
|
||||
ShowMessage(i);
|
||||
#endif
|
||||
state = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (winner>=0 && subs[winner] != null &&
|
||||
(winner == 0 && subs[winner].AtStartPosition) || (winner == 1 && subs[winner].AtEndPosition) &&
|
||||
crews[winner].Any(c => !c.IsDead && c.Submarine == subs[winner]))
|
||||
{
|
||||
#if CLIENT
|
||||
GameMain.GameSession.CrewManager.WinningTeam = winner+1;
|
||||
#endif
|
||||
if (GameMain.Server != null) GameMain.Server.EndGame();
|
||||
}
|
||||
}
|
||||
|
||||
if (teamDead[0] && teamDead[1])
|
||||
{
|
||||
#if CLIENT
|
||||
GameMain.GameSession.CrewManager.WinningTeam = 0;
|
||||
#endif
|
||||
winner = -1;
|
||||
if (GameMain.Server != null) GameMain.Server.EndGame();
|
||||
}
|
||||
}
|
||||
|
||||
public override void End()
|
||||
{
|
||||
if (GameMain.NetworkMember == null) return;
|
||||
|
||||
if (winner > -1)
|
||||
{
|
||||
GiveReward();
|
||||
completed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class Mission
|
||||
{
|
||||
public static List<string> MissionTypes = new List<string>() { "Random" };
|
||||
|
||||
private string name;
|
||||
|
||||
private string description;
|
||||
|
||||
protected bool completed;
|
||||
|
||||
protected string successMessage;
|
||||
protected string failureMessage;
|
||||
|
||||
protected string radarLabel;
|
||||
|
||||
protected List<string> headers;
|
||||
protected List<string> messages;
|
||||
|
||||
private int reward;
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return name; }
|
||||
}
|
||||
|
||||
public virtual string Description
|
||||
{
|
||||
get { return description; }
|
||||
}
|
||||
|
||||
public int Reward
|
||||
{
|
||||
get { return reward; }
|
||||
}
|
||||
|
||||
public bool Completed
|
||||
{
|
||||
get { return completed; }
|
||||
set { completed = value; }
|
||||
}
|
||||
|
||||
public virtual bool AllowRespawn
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public virtual string RadarLabel
|
||||
{
|
||||
get { return radarLabel; }
|
||||
}
|
||||
|
||||
public virtual Vector2 RadarPosition
|
||||
{
|
||||
get { return Vector2.Zero; }
|
||||
}
|
||||
|
||||
virtual public string SuccessMessage
|
||||
{
|
||||
get { return successMessage; }
|
||||
}
|
||||
|
||||
public string FailureMessage
|
||||
{
|
||||
get { return failureMessage; }
|
||||
}
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
var files = GameMain.SelectedPackage.GetFilesOfType(ContentType.Missions);
|
||||
foreach (string file in files)
|
||||
{
|
||||
XDocument doc = ToolBox.TryLoadXml(file);
|
||||
if (doc == null || doc.Root == null) continue;
|
||||
|
||||
foreach (XElement element in doc.Root.Elements())
|
||||
{
|
||||
string missionTypeName = element.Name.ToString();
|
||||
missionTypeName = missionTypeName.Replace("Mission", "");
|
||||
|
||||
if (!MissionTypes.Contains(missionTypeName)) MissionTypes.Add(missionTypeName);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public Mission(XElement element, Location[] locations)
|
||||
{
|
||||
name = ToolBox.GetAttributeString(element, "name", "");
|
||||
|
||||
description = ToolBox.GetAttributeString(element, "description", "");
|
||||
|
||||
reward = ToolBox.GetAttributeInt(element, "reward", 1);
|
||||
|
||||
successMessage = ToolBox.GetAttributeString(element, "successmessage",
|
||||
"Mission completed successfully");
|
||||
failureMessage = ToolBox.GetAttributeString(element, "failuremessage",
|
||||
"Mission failed");
|
||||
|
||||
radarLabel = ToolBox.GetAttributeString(element, "radarlabel", "");
|
||||
|
||||
messages = new List<string>();
|
||||
headers = new List<string>();
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
if (subElement.Name.ToString().ToLowerInvariant() != "message") continue;
|
||||
headers.Add(ToolBox.GetAttributeString(subElement, "header", ""));
|
||||
messages.Add(ToolBox.GetAttributeString(subElement, "text", ""));
|
||||
}
|
||||
|
||||
for (int n = 0; n < 2; n++)
|
||||
{
|
||||
description = description.Replace("[location" + (n + 1) + "]", locations[n].Name);
|
||||
|
||||
successMessage = successMessage.Replace("[location" + (n + 1) + "]", locations[n].Name);
|
||||
failureMessage = failureMessage.Replace("[location" + (n + 1) + "]", locations[n].Name);
|
||||
|
||||
for (int m = 0; m < messages.Count; m++)
|
||||
{
|
||||
messages[m] = messages[m].Replace("[location" + (n + 1) + "]", locations[n].Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Mission LoadRandom(Location[] locations, MTRandom rand, string missionType = "", bool isSinglePlayer = false)
|
||||
{
|
||||
missionType = missionType.ToLowerInvariant();
|
||||
|
||||
var files = GameMain.SelectedPackage.GetFilesOfType(ContentType.Missions);
|
||||
string configFile = files[rand.Next(files.Count)];
|
||||
|
||||
XDocument doc = ToolBox.TryLoadXml(configFile);
|
||||
if (doc == null) return null;
|
||||
|
||||
int eventCount = doc.Root.Elements().Count();
|
||||
//int[] commonness = new int[eventCount];
|
||||
float[] eventProbability = new float[eventCount];
|
||||
|
||||
float probabilitySum = 0.0f;
|
||||
|
||||
List<XElement> matchingElements = new List<XElement>();
|
||||
|
||||
if (missionType == "random")
|
||||
{
|
||||
matchingElements = doc.Root.Elements().ToList();
|
||||
}
|
||||
else if (missionType == "none")
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else if (string.IsNullOrWhiteSpace(missionType))
|
||||
{
|
||||
matchingElements = doc.Root.Elements().ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
matchingElements = doc.Root.Elements().ToList().FindAll(m => m.Name.ToString().ToLowerInvariant().Replace("mission", "") == missionType);
|
||||
}
|
||||
|
||||
if (isSinglePlayer)
|
||||
{
|
||||
matchingElements.RemoveAll(m => ToolBox.GetAttributeBool(m, "multiplayeronly", false));
|
||||
}
|
||||
else
|
||||
{
|
||||
matchingElements.RemoveAll(m => ToolBox.GetAttributeBool(m, "singleplayeronly", false));
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
foreach (XElement element in matchingElements)
|
||||
{
|
||||
eventProbability[i] = ToolBox.GetAttributeInt(element, "commonness", 1);
|
||||
|
||||
probabilitySum += eventProbability[i];
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
float randomNumber = (float)rand.NextDouble() * probabilitySum;
|
||||
|
||||
i = 0;
|
||||
foreach (XElement element in matchingElements)
|
||||
{
|
||||
if (randomNumber <= eventProbability[i])
|
||||
{
|
||||
Type t;
|
||||
string type = element.Name.ToString();
|
||||
|
||||
try
|
||||
{
|
||||
t = Type.GetType("Barotrauma." + type, true, true);
|
||||
if (t == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in " + configFile + "! Could not find a mission class of the type \"" + type + "\".");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
DebugConsole.ThrowError("Error in " + configFile + "! Could not find a mission class of the type \"" + type + "\".");
|
||||
continue;
|
||||
}
|
||||
|
||||
ConstructorInfo constructor = t.GetConstructor(new[] { typeof(XElement), typeof(Location[]) });
|
||||
|
||||
object instance = constructor.Invoke(new object[] { element, locations });
|
||||
|
||||
Mission mission = (Mission)instance;
|
||||
|
||||
return mission;
|
||||
}
|
||||
|
||||
randomNumber -= eventProbability[i];
|
||||
i++;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public virtual void Start(Level level) { }
|
||||
|
||||
public virtual void Update(float deltaTime) { }
|
||||
|
||||
public virtual bool AssignTeamIDs(List<Networking.Client> clients, out byte hostTeam)
|
||||
{
|
||||
clients.ForEach(c => c.TeamID = 1);
|
||||
hostTeam = 1;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// End the mission and give a reward if it was completed successfully
|
||||
/// </summary>
|
||||
public virtual void End()
|
||||
{
|
||||
completed = true;
|
||||
|
||||
GiveReward();
|
||||
}
|
||||
|
||||
public void GiveReward()
|
||||
{
|
||||
#if CLIENT
|
||||
var mode = GameMain.GameSession.gameMode as SinglePlayerMode;
|
||||
if (mode == null) return;
|
||||
|
||||
mode.Money += reward;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class MonsterMission : Mission
|
||||
{
|
||||
private string monsterFile;
|
||||
|
||||
private int state;
|
||||
|
||||
private Character monster;
|
||||
|
||||
private Vector2 radarPosition;
|
||||
|
||||
public override Vector2 RadarPosition
|
||||
{
|
||||
get { return monster != null && !monster.IsDead ? radarPosition : Vector2.Zero; }
|
||||
}
|
||||
|
||||
public MonsterMission(XElement element, Location[] locations)
|
||||
: base(element, locations)
|
||||
{
|
||||
monsterFile = ToolBox.GetAttributeString(element, "monsterfile", "");
|
||||
}
|
||||
|
||||
public override void Start(Level level)
|
||||
{
|
||||
Vector2 spawnPos = Level.Loaded.GetRandomInterestingPosition(true, Level.PositionType.MainPath, true);
|
||||
|
||||
monster = Character.Create(monsterFile, spawnPos, null, GameMain.Client != null);
|
||||
monster.Enabled = false;
|
||||
radarPosition = spawnPos;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case 0:
|
||||
if (monster.Enabled)
|
||||
{
|
||||
radarPosition = monster.Position;
|
||||
}
|
||||
|
||||
if (!monster.IsDead) return;
|
||||
#if CLIENT
|
||||
ShowMessage(state);
|
||||
#endif
|
||||
state = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override void End()
|
||||
{
|
||||
if (!monster.IsDead) return;
|
||||
|
||||
GiveReward();
|
||||
|
||||
completed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class SalvageMission : Mission
|
||||
{
|
||||
private ItemPrefab itemPrefab;
|
||||
|
||||
private Item item;
|
||||
|
||||
private Level.PositionType spawnPositionType;
|
||||
|
||||
private int state;
|
||||
|
||||
public override Vector2 RadarPosition
|
||||
{
|
||||
get
|
||||
{
|
||||
return state>0 ? Vector2.Zero : ConvertUnits.ToDisplayUnits(item.SimPosition);
|
||||
}
|
||||
}
|
||||
|
||||
public SalvageMission(XElement element, Location[] locations)
|
||||
: base(element, locations)
|
||||
{
|
||||
string itemName = ToolBox.GetAttributeString(element, "itemname", "");
|
||||
|
||||
itemPrefab = ItemPrefab.list.Find(ip => ip.Name == itemName) as ItemPrefab;
|
||||
|
||||
string spawnPositionTypeStr = ToolBox.GetAttributeString(element, "spawntype", "");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(spawnPositionTypeStr) ||
|
||||
!Enum.TryParse<Level.PositionType>(spawnPositionTypeStr, true, out spawnPositionType))
|
||||
{
|
||||
spawnPositionType = Level.PositionType.Cave | Level.PositionType.Ruin;
|
||||
}
|
||||
|
||||
if (itemPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in SalvageMission: couldn't find an item prefab with the name "+itemName);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Start(Level level)
|
||||
{
|
||||
Vector2 position = Level.Loaded.GetRandomItemPos(spawnPositionType, 100.0f, 30.0f);
|
||||
|
||||
item = new Item(itemPrefab, position, null);
|
||||
item.MoveWithLevel = true;
|
||||
item.body.FarseerBody.IsKinematic = true;
|
||||
|
||||
if (item.HasTag("alien"))
|
||||
{
|
||||
//try to find a nearby artifact holder (or any alien itemcontainer) and place the artifact inside it
|
||||
foreach (Item it in Item.ItemList)
|
||||
{
|
||||
if (it.Submarine != null || !it.HasTag("alien")) continue;
|
||||
|
||||
if (Math.Abs(item.WorldPosition.X - it.WorldPosition.X) > 2000.0f) continue;
|
||||
if (Math.Abs(item.WorldPosition.Y - it.WorldPosition.Y) > 2000.0f) continue;
|
||||
|
||||
var itemContainer = it.GetComponent<Items.Components.ItemContainer>();
|
||||
if (itemContainer == null) continue;
|
||||
|
||||
itemContainer.Combine(item);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case 0:
|
||||
//item.body.LinearVelocity = Vector2.Zero;
|
||||
if (item.ParentInventory!=null) item.body.FarseerBody.IsKinematic = false;
|
||||
if (item.CurrentHull == null) return;
|
||||
|
||||
#if CLIENT
|
||||
ShowMessage(state);
|
||||
#endif
|
||||
state = 1;
|
||||
break;
|
||||
case 1:
|
||||
if (!Submarine.MainSub.AtEndPosition && !Submarine.MainSub.AtStartPosition) return;
|
||||
#if CLIENT
|
||||
ShowMessage(state);
|
||||
#endif
|
||||
state = 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override void End()
|
||||
{
|
||||
if (item.CurrentHull == null || !item.CurrentHull.Submarine.AtEndPosition || item.Removed) return;
|
||||
item.Remove();
|
||||
|
||||
GiveReward();
|
||||
|
||||
completed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class MonsterEvent : ScriptedEvent
|
||||
{
|
||||
private string characterFile;
|
||||
|
||||
private int minAmount, maxAmount;
|
||||
|
||||
private Character[] monsters;
|
||||
|
||||
private bool spawnDeep;
|
||||
|
||||
private bool disallowed;
|
||||
|
||||
private Level.PositionType spawnPosType;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "ScriptedEvent (" + characterFile + ")";
|
||||
}
|
||||
|
||||
private bool isActive;
|
||||
public override bool IsActive
|
||||
{
|
||||
get
|
||||
{
|
||||
return isActive;
|
||||
}
|
||||
}
|
||||
|
||||
public MonsterEvent(XElement element)
|
||||
: base (element)
|
||||
{
|
||||
characterFile = ToolBox.GetAttributeString(element, "characterfile", "");
|
||||
|
||||
minAmount = ToolBox.GetAttributeInt(element, "minamount", 1);
|
||||
maxAmount = Math.Max(ToolBox.GetAttributeInt(element, "maxamount", 1), minAmount);
|
||||
|
||||
var spawnPosTypeStr = ToolBox.GetAttributeString(element, "spawntype", "");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(spawnPosTypeStr) ||
|
||||
!Enum.TryParse<Level.PositionType>(spawnPosTypeStr, true, out spawnPosType))
|
||||
{
|
||||
spawnPosType = Level.PositionType.MainPath;
|
||||
}
|
||||
|
||||
spawnDeep = ToolBox.GetAttributeBool(element, "spawndeep", false);
|
||||
|
||||
if (GameMain.NetworkMember != null)
|
||||
{
|
||||
List<string> monsterNames = GameMain.NetworkMember.monsterEnabled.Keys.ToList();
|
||||
string tryKey = monsterNames.Find(s => characterFile.ToLower().Contains(s.ToLower()));
|
||||
if (!string.IsNullOrWhiteSpace(tryKey))
|
||||
{
|
||||
if (!GameMain.NetworkMember.monsterEnabled[tryKey]) disallowed = true; //spawn was disallowed by host
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
|
||||
SpawnMonsters();
|
||||
}
|
||||
|
||||
private void SpawnMonsters()
|
||||
{
|
||||
if (disallowed) return;
|
||||
|
||||
Vector2 spawnPos = Level.Loaded.GetRandomInterestingPosition(true, spawnPosType, true);
|
||||
|
||||
int amount = Rand.Range(minAmount, maxAmount, Rand.RandSync.Server);
|
||||
|
||||
monsters = new Character[amount];
|
||||
|
||||
if (spawnDeep) spawnPos.Y -= Level.Loaded.Size.Y;
|
||||
|
||||
for (int i = 0; i < amount; i++)
|
||||
{
|
||||
spawnPos.X += Rand.Range(-0.5f, 0.5f, Rand.RandSync.Server);
|
||||
spawnPos.Y += Rand.Range(-0.5f, 0.5f, Rand.RandSync.Server);
|
||||
monsters[i] = Character.Create(characterFile, spawnPos, null, GameMain.Client != null);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (disallowed)
|
||||
{
|
||||
Finished();
|
||||
return;
|
||||
}
|
||||
if (monsters == null) SpawnMonsters();
|
||||
|
||||
if (isFinished) return;
|
||||
|
||||
isActive = false;
|
||||
|
||||
Entity targetEntity = null;
|
||||
if (Character.Controlled != null)
|
||||
{
|
||||
targetEntity = Character.Controlled;
|
||||
}
|
||||
else
|
||||
{
|
||||
targetEntity = Submarine.FindClosest(GameMain.GameScreen.Cam.WorldViewCenter);
|
||||
}
|
||||
|
||||
bool monstersDead = true;
|
||||
foreach (Character monster in monsters)
|
||||
{
|
||||
if (!monster.IsDead)
|
||||
{
|
||||
monstersDead = false;
|
||||
|
||||
if (targetEntity != null && Vector2.DistanceSquared(monster.WorldPosition, targetEntity.WorldPosition) < 5000.0f * 5000.0f)
|
||||
{
|
||||
isActive = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (monstersDead) Finished();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
namespace Barotrauma
|
||||
{
|
||||
class PropertyTask : Task
|
||||
{
|
||||
Item item;
|
||||
|
||||
|
||||
public delegate bool IsFinishedHandler();
|
||||
private IsFinishedHandler IsFinishedChecker;
|
||||
|
||||
public PropertyTask(Item item, IsFinishedHandler isFinished, float priority, string name)
|
||||
: base(priority, name)
|
||||
{
|
||||
if (taskManager == null) return;
|
||||
|
||||
this.item = item;
|
||||
IsFinishedChecker = isFinished;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (IsFinishedChecker())
|
||||
{
|
||||
Finished();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
namespace Barotrauma
|
||||
{
|
||||
class RepairTask : Task
|
||||
{
|
||||
Item item;
|
||||
|
||||
public RepairTask(Item item, float priority, string name)
|
||||
: base(priority, name)
|
||||
{
|
||||
if (taskManager == null) return;
|
||||
|
||||
this.item = item;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (item.Condition > 50.0f) Finished();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class ScriptedEvent
|
||||
{
|
||||
protected string name;
|
||||
protected string description;
|
||||
|
||||
protected int commonness;
|
||||
protected int difficulty;
|
||||
|
||||
protected bool isFinished;
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return name; }
|
||||
}
|
||||
|
||||
public string Description
|
||||
{
|
||||
get { return description; }
|
||||
}
|
||||
|
||||
public int Commonness
|
||||
{
|
||||
get { return commonness; }
|
||||
}
|
||||
|
||||
public string MusicType
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public virtual bool IsActive
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public bool IsFinished
|
||||
{
|
||||
get { return isFinished; }
|
||||
}
|
||||
|
||||
public int Difficulty
|
||||
{
|
||||
get { return difficulty; }
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "ScriptedEvent ("+name+")";
|
||||
}
|
||||
|
||||
public ScriptedEvent(XElement element)
|
||||
{
|
||||
name = ToolBox.GetAttributeString(element, "name", "");
|
||||
description = ToolBox.GetAttributeString(element, "description", "");
|
||||
|
||||
difficulty = ToolBox.GetAttributeInt(element, "difficulty", 1);
|
||||
commonness = ToolBox.GetAttributeInt(element, "commonness", 1);
|
||||
|
||||
MusicType = ToolBox.GetAttributeString(element, "musictype", "default");
|
||||
}
|
||||
|
||||
|
||||
public static ScriptedEvent LoadRandom(Random rand)
|
||||
{
|
||||
var configFiles = GameMain.Config.SelectedContentPackage.GetFilesOfType(ContentType.RandomEvents);
|
||||
|
||||
if (!configFiles.Any())
|
||||
{
|
||||
DebugConsole.ThrowError("No config files for random events found in the selected content package");
|
||||
return null;
|
||||
}
|
||||
|
||||
string configFile = configFiles[0];
|
||||
|
||||
XDocument doc = ToolBox.TryLoadXml(configFile);
|
||||
if (doc == null) return null;
|
||||
|
||||
int eventCount = doc.Root.Elements().Count();
|
||||
//int[] commonness = new int[eventCount];
|
||||
float[] eventProbability = new float[eventCount];
|
||||
|
||||
float probabilitySum = 0.0f;
|
||||
|
||||
int i = 0;
|
||||
foreach (XElement element in doc.Root.Elements())
|
||||
{
|
||||
eventProbability[i] = ToolBox.GetAttributeInt(element, "commonness", 1);
|
||||
|
||||
//if the event has been previously selected, it's less likely to be selected now
|
||||
//int previousEventIndex = previousEvents.FindIndex(x => x == i);
|
||||
//if (previousEventIndex >= 0)
|
||||
//{
|
||||
// //how many shifts ago was the event last selected
|
||||
// int eventDist = eventCount - previousEventIndex;
|
||||
|
||||
// float weighting = (1.0f / eventDist) * PreviouslyUsedWeight;
|
||||
|
||||
// eventProbability[i] *= weighting;
|
||||
//}
|
||||
|
||||
probabilitySum += eventProbability[i];
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
float randomNumber = (float)rand.NextDouble() * probabilitySum;
|
||||
|
||||
i = 0;
|
||||
foreach (XElement element in doc.Root.Elements())
|
||||
{
|
||||
if (randomNumber <= eventProbability[i])
|
||||
{
|
||||
Type t;
|
||||
string type = element.Name.ToString();
|
||||
|
||||
try
|
||||
{
|
||||
t = Type.GetType("Barotrauma." + type, true, true);
|
||||
if (t == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in " + configFile + "! Could not find an event class of the type \"" + type + "\".");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
DebugConsole.ThrowError("Error in " + configFile + "! Could not find an event class of the type \"" + type + "\".");
|
||||
continue;
|
||||
}
|
||||
|
||||
ConstructorInfo constructor = t.GetConstructor(new[] { typeof(XElement) });
|
||||
object instance = null;
|
||||
try
|
||||
{
|
||||
instance = constructor.Invoke(new object[] { element });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DebugConsole.ThrowError(ex.InnerException!=null ? ex.InnerException.ToString() : ex.ToString());
|
||||
}
|
||||
|
||||
//previousEvents.Add(i);
|
||||
|
||||
return (ScriptedEvent)instance;
|
||||
}
|
||||
|
||||
randomNumber -= eventProbability[i];
|
||||
i++;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public virtual void Init()
|
||||
{
|
||||
isFinished = false;
|
||||
}
|
||||
|
||||
public virtual void Update(float deltaTime)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void Finished()
|
||||
{
|
||||
isFinished = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
namespace Barotrauma
|
||||
{
|
||||
class ScriptedTask : Task
|
||||
{
|
||||
private ScriptedEvent scriptedEvent;
|
||||
|
||||
public override bool IsStarted
|
||||
{
|
||||
get
|
||||
{
|
||||
return scriptedEvent.IsActive;
|
||||
}
|
||||
}
|
||||
|
||||
public ScriptedTask(ScriptedEvent scriptedEvent)
|
||||
: base(scriptedEvent.Difficulty, scriptedEvent.Name)
|
||||
{
|
||||
if (taskManager == null) return;
|
||||
|
||||
this.musicType = scriptedEvent.MusicType;
|
||||
|
||||
this.scriptedEvent = scriptedEvent;
|
||||
scriptedEvent.Init();
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
scriptedEvent.Update(deltaTime);
|
||||
if (scriptedEvent.IsFinished) Finished();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class Task
|
||||
{
|
||||
|
||||
protected string name;
|
||||
|
||||
private float priority;
|
||||
|
||||
protected string musicType;
|
||||
|
||||
protected TaskManager taskManager;
|
||||
|
||||
protected bool isFinished;
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return name; }
|
||||
}
|
||||
|
||||
public float Priority
|
||||
{
|
||||
get { return priority; }
|
||||
}
|
||||
|
||||
public string MusicType
|
||||
{
|
||||
get { return musicType; }
|
||||
}
|
||||
|
||||
public bool IsFinished
|
||||
{
|
||||
get { return isFinished; }
|
||||
}
|
||||
|
||||
public virtual bool IsStarted
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public Task(float priority, string name)
|
||||
{
|
||||
if (GameMain.GameSession==null || GameMain.GameSession.TaskManager == null) return;
|
||||
|
||||
taskManager = GameMain.GameSession.TaskManager;
|
||||
musicType = "repair";
|
||||
this.priority = priority;
|
||||
this.name = name;
|
||||
|
||||
taskManager.AddTask(this);
|
||||
}
|
||||
|
||||
public virtual void Update(float deltaTime)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected virtual void Finished()
|
||||
{
|
||||
isFinished = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class TaskManager
|
||||
{
|
||||
const float CriticalPriority = 50.0f;
|
||||
|
||||
private List<Task> tasks;
|
||||
|
||||
public List<Task> Tasks
|
||||
{
|
||||
get { return tasks; }
|
||||
}
|
||||
|
||||
public bool CriticalTasks
|
||||
{
|
||||
get
|
||||
{
|
||||
return tasks.Any(task => task.Priority >= CriticalPriority);
|
||||
}
|
||||
}
|
||||
|
||||
public TaskManager(GameSession session)
|
||||
{
|
||||
tasks = new List<Task>();
|
||||
}
|
||||
|
||||
public void AddTask(Task newTask)
|
||||
{
|
||||
if (tasks.Contains(newTask)) return;
|
||||
|
||||
tasks.Add(newTask);
|
||||
}
|
||||
|
||||
public void StartShift(Level level)
|
||||
{
|
||||
CreateScriptedEvents(level);
|
||||
}
|
||||
|
||||
|
||||
public void EndShift()
|
||||
{
|
||||
tasks.Clear();
|
||||
}
|
||||
|
||||
private void CreateScriptedEvents(Level level)
|
||||
{
|
||||
MTRandom rand = new MTRandom(ToolBox.StringToInt(level.Seed));
|
||||
|
||||
float totalDifficulty = level.Difficulty;
|
||||
|
||||
int tries = 0;
|
||||
while (tries < 5)
|
||||
{
|
||||
ScriptedEvent scriptedEvent = ScriptedEvent.LoadRandom(rand);
|
||||
if (scriptedEvent==null || scriptedEvent.Difficulty > totalDifficulty)
|
||||
{
|
||||
tries++;
|
||||
continue;
|
||||
}
|
||||
DebugConsole.Log("Created scripted event " + scriptedEvent.ToString());
|
||||
|
||||
AddTask(new ScriptedTask(scriptedEvent));
|
||||
totalDifficulty -= scriptedEvent.Difficulty;
|
||||
tries = 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
foreach (Task task in tasks)
|
||||
{
|
||||
if (!task.IsFinished)
|
||||
{
|
||||
task.Update(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
tasks.RemoveAll(t => t.IsFinished);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class FrameCounter
|
||||
{
|
||||
public long TotalFrames { get; private set; }
|
||||
public double TotalSeconds { get; private set; }
|
||||
public double AverageFramesPerSecond { get; private set; }
|
||||
public double CurrentFramesPerSecond { get; private set; }
|
||||
|
||||
public const int MaximumSamples = 10;
|
||||
|
||||
private Queue<double> sampleBuffer = new Queue<double>();
|
||||
|
||||
public bool Update(double deltaTime)
|
||||
{
|
||||
//float deltaTime = stopwatch.ElapsedMilliseconds / 1000.0f;
|
||||
|
||||
if (deltaTime == 0.0f) { return false; }
|
||||
//stopwatch.Restart();
|
||||
|
||||
CurrentFramesPerSecond = (1.0 / deltaTime);
|
||||
|
||||
sampleBuffer.Enqueue(CurrentFramesPerSecond);
|
||||
|
||||
if (sampleBuffer.Count > MaximumSamples)
|
||||
{
|
||||
sampleBuffer.Dequeue();
|
||||
AverageFramesPerSecond = sampleBuffer.Average(i => i);
|
||||
}
|
||||
else
|
||||
{
|
||||
AverageFramesPerSecond = CurrentFramesPerSecond;
|
||||
}
|
||||
|
||||
if (AverageFramesPerSecond < 0 || AverageFramesPerSecond > 500) { }
|
||||
|
||||
TotalFrames++;
|
||||
TotalSeconds += deltaTime;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class CargoManager
|
||||
{
|
||||
private List<ItemPrefab> purchasedItems;
|
||||
|
||||
public CargoManager()
|
||||
{
|
||||
purchasedItems = new List<ItemPrefab>();
|
||||
}
|
||||
|
||||
public void AddItem(ItemPrefab item)
|
||||
{
|
||||
purchasedItems.Add(item);
|
||||
}
|
||||
|
||||
public void CreateItems()
|
||||
{
|
||||
WayPoint wp = WayPoint.GetRandom(SpawnType.Cargo, null, Submarine.MainSub);
|
||||
|
||||
if (wp==null)
|
||||
{
|
||||
DebugConsole.ThrowError("The submarine must have a waypoint marked as Cargo for bought items to be placed correctly!");
|
||||
return;
|
||||
}
|
||||
|
||||
Hull cargoRoom = Hull.FindHull(wp.WorldPosition);
|
||||
|
||||
if (cargoRoom == null)
|
||||
{
|
||||
DebugConsole.ThrowError("A waypoint marked as Cargo must be placed inside a room!");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (ItemPrefab prefab in purchasedItems)
|
||||
{
|
||||
Vector2 position = new Vector2(
|
||||
Rand.Range(cargoRoom.Rect.X + 20, cargoRoom.Rect.Right - 20),
|
||||
cargoRoom.Rect.Y - cargoRoom.Rect.Height + prefab.Size.Y/2);
|
||||
|
||||
new Item(prefab, position, wp.Submarine);
|
||||
}
|
||||
|
||||
purchasedItems.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class GameMode
|
||||
{
|
||||
public static List<GameModePreset> PresetList = new List<GameModePreset>();
|
||||
|
||||
protected DateTime startTime;
|
||||
|
||||
//public readonly bool IsSinglePlayer;
|
||||
|
||||
protected bool isRunning;
|
||||
|
||||
//protected string name;
|
||||
|
||||
protected GameModePreset preset;
|
||||
|
||||
private string endMessage;
|
||||
|
||||
public virtual Mission Mission
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
|
||||
public bool IsRunning
|
||||
{
|
||||
get { return isRunning; }
|
||||
}
|
||||
|
||||
public bool IsSinglePlayer
|
||||
{
|
||||
get { return preset.IsSinglePlayer; }
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return preset.Name; }
|
||||
}
|
||||
|
||||
public string EndMessage
|
||||
{
|
||||
get { return endMessage; }
|
||||
}
|
||||
|
||||
public GameModePreset Preset
|
||||
{
|
||||
get { return preset; }
|
||||
}
|
||||
|
||||
public GameMode(GameModePreset preset, object param)
|
||||
{
|
||||
this.preset = preset;
|
||||
}
|
||||
|
||||
public virtual void Start()
|
||||
{
|
||||
startTime = DateTime.Now;
|
||||
//if (duration!=TimeSpan.Zero)
|
||||
//{
|
||||
// endTime = startTime + duration;
|
||||
// this.duration = duration;
|
||||
|
||||
// timerBar = new GUIProgressBar(new Rectangle(GameMain.GraphicsWidth - 120, 20, 100, 25), Color.Gold, 0.0f, null);
|
||||
//}
|
||||
|
||||
endMessage = "The round has ended!";
|
||||
|
||||
isRunning = true;
|
||||
}
|
||||
|
||||
public virtual void MsgBox() { }
|
||||
|
||||
public virtual void AddToGUIUpdateList() { }
|
||||
|
||||
public virtual void Update(float deltaTime)
|
||||
{
|
||||
//if (!isRunning) return;
|
||||
|
||||
//if (duration != TimeSpan.Zero)
|
||||
//{
|
||||
// double elapsedTime = (DateTime.Now - startTime).TotalSeconds;
|
||||
// timerBar.BarSize = (float)(elapsedTime / duration.TotalSeconds);
|
||||
//}
|
||||
//if (DateTime.Now >= endTime)
|
||||
//{
|
||||
// End(endMessage);
|
||||
//}
|
||||
}
|
||||
|
||||
public virtual void End(string endMessage = "")
|
||||
{
|
||||
isRunning = false;
|
||||
|
||||
if (endMessage != "" || this.endMessage == null) this.endMessage = endMessage;
|
||||
|
||||
GameMain.GameSession.EndShift(endMessage);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class GameModePreset
|
||||
{
|
||||
public static List<GameModePreset> list = new List<GameModePreset>();
|
||||
|
||||
public ConstructorInfo Constructor;
|
||||
public string Name;
|
||||
public bool IsSinglePlayer;
|
||||
|
||||
public string Description;
|
||||
|
||||
public GameModePreset(string name, Type type, bool isSinglePlayer = false)
|
||||
{
|
||||
this.Name = name;
|
||||
|
||||
Constructor = type.GetConstructor(new Type[] { typeof(GameModePreset), typeof(object) });
|
||||
|
||||
IsSinglePlayer = isSinglePlayer;
|
||||
|
||||
list.Add(this);
|
||||
}
|
||||
|
||||
public GameMode Instantiate(object param)
|
||||
{
|
||||
object[] lobject = new object[] { this, param };
|
||||
return (GameMode)Constructor.Invoke(lobject);
|
||||
}
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
#if CLIENT
|
||||
new GameModePreset("Single Player", typeof(SinglePlayerMode), true);
|
||||
new GameModePreset("Tutorial", typeof(TutorialMode), true);
|
||||
#endif
|
||||
|
||||
var mode = new GameModePreset("SandBox", typeof(GameMode), false);
|
||||
mode.Description = "A game mode with no specific objectives.";
|
||||
|
||||
//mode = new GameModePreset("Traitor", typeof(TraitorMode), false);
|
||||
//mode.Description = "One of the players is selected as a traitor and given a secret objective. "
|
||||
// + "The rest of the crew will win if they reach the end of the level or kill the traitor "
|
||||
// + "before the objective is completed.";
|
||||
|
||||
mode = new GameModePreset("Mission", typeof(MissionMode), false);
|
||||
mode.Description = "The crew must work together to complete a specific task, such as retrieving "
|
||||
+ "an alien artifact or killing a creature that's terrorizing nearby outposts. The game ends "
|
||||
+ "when the task is completed or everyone in the crew has died.";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class MissionMode : GameMode
|
||||
{
|
||||
private Mission mission;
|
||||
|
||||
public override Mission Mission
|
||||
{
|
||||
get
|
||||
{
|
||||
return mission;
|
||||
}
|
||||
}
|
||||
|
||||
public MissionMode(GameModePreset preset, object param)
|
||||
: base(preset, param)
|
||||
{
|
||||
Location[] locations = { GameMain.GameSession.StartLocation, GameMain.GameSession.EndLocation };
|
||||
|
||||
MTRandom rand = new MTRandom(ToolBox.StringToInt(GameMain.NetLobbyScreen.LevelSeed));
|
||||
mission = Mission.LoadRandom(locations, rand, param as string);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
using Barotrauma.Networking;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class TraitorManager
|
||||
{
|
||||
public Character TraitorCharacter
|
||||
{
|
||||
get { return traitorCharacter; }
|
||||
}
|
||||
|
||||
public Character TargetCharacter
|
||||
{
|
||||
get { return targetCharacter; }
|
||||
}
|
||||
|
||||
private Character traitorCharacter, targetCharacter;
|
||||
|
||||
public TraitorManager(GameServer server)
|
||||
{
|
||||
Start(server);
|
||||
}
|
||||
|
||||
private void Start(GameServer server)
|
||||
{
|
||||
if (server == null) return;
|
||||
|
||||
List<Character> characters = new List<Character>();
|
||||
foreach (Client client in server.ConnectedClients)
|
||||
{
|
||||
if (client.Character != null)
|
||||
characters.Add(client.Character);
|
||||
}
|
||||
|
||||
if (server.Character!= null) characters.Add(server.Character);
|
||||
|
||||
if (characters.Count < 2)
|
||||
{
|
||||
traitorCharacter = null;
|
||||
targetCharacter = null;
|
||||
return;
|
||||
}
|
||||
|
||||
int traitorIndex = Rand.Range(0, characters.Count);
|
||||
|
||||
int targetIndex = Rand.Range(0, characters.Count);
|
||||
while (targetIndex == traitorIndex)
|
||||
{
|
||||
targetIndex = Rand.Range(0, characters.Count);
|
||||
}
|
||||
|
||||
traitorCharacter = characters[traitorIndex];
|
||||
targetCharacter = characters[targetIndex];
|
||||
|
||||
#if CLIENT
|
||||
if (server.Character == null)
|
||||
{
|
||||
new GUIMessageBox("New traitor", traitorCharacter.Name + " is the traitor and the target is " + targetCharacter.Name+".");
|
||||
}
|
||||
else if (server.Character == traitorCharacter)
|
||||
{
|
||||
CreateStartPopUp(traitorCharacter.Name);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public string GetEndMessage()
|
||||
{
|
||||
if (GameMain.Server == null || traitorCharacter == null || targetCharacter == null) return "";
|
||||
|
||||
string endMessage = "";
|
||||
|
||||
if (targetCharacter.IsDead && !traitorCharacter.IsDead)
|
||||
{
|
||||
endMessage = traitorCharacter.Name + " was a traitor! ";
|
||||
endMessage += (traitorCharacter.Info.Gender == Gender.Male) ? "His" : "Her";
|
||||
endMessage += " task was to assassinate " + targetCharacter.Name + ". The task was successful.";
|
||||
}
|
||||
else if (targetCharacter.IsDead && traitorCharacter.IsDead)
|
||||
{
|
||||
endMessage = traitorCharacter.Name + " was a traitor! ";
|
||||
endMessage += (traitorCharacter.Info.Gender == Gender.Male) ? "His" : "Her";
|
||||
endMessage += " task was to assassinate " + targetCharacter.Name + ". The task was successful, but luckily the bastard didn't make it out alive either.";
|
||||
}
|
||||
else if (traitorCharacter.IsDead)
|
||||
{
|
||||
endMessage = traitorCharacter.Name + " was a traitor! ";
|
||||
endMessage += (traitorCharacter.Info.Gender == Gender.Male) ? "His" : "Her";
|
||||
endMessage += " task was to assassinate " + targetCharacter.Name + ", but ";
|
||||
endMessage += (traitorCharacter.Info.Gender == Gender.Male) ? "he" : "she";
|
||||
endMessage += " got " + ((traitorCharacter.Info.Gender == Gender.Male) ? "himself" : "herself");
|
||||
endMessage += " killed before completing it.";
|
||||
}
|
||||
else
|
||||
{
|
||||
endMessage = traitorCharacter.Name + " was a traitor! ";
|
||||
endMessage += (traitorCharacter.Info.Gender == Gender.Male) ? "His" : "Her";
|
||||
endMessage += " task was to assassinate " + targetCharacter.Name + ". ";
|
||||
endMessage += (Submarine.MainSub.AtEndPosition) ?
|
||||
"The task was unsuccessful - the submarine has reached its destination." :
|
||||
"The task was unsuccessful.";
|
||||
}
|
||||
|
||||
return endMessage;
|
||||
}
|
||||
|
||||
//public void CharacterLeft(Character character)
|
||||
//{
|
||||
// if (character != traitorCharacter && character != targetCharacter) return;
|
||||
|
||||
// if (character == traitorCharacter)
|
||||
// {
|
||||
// string endMessage = "The traitor has disconnected from the server.";
|
||||
// End(endMessage);
|
||||
// }
|
||||
// else if (character == targetCharacter)
|
||||
// {
|
||||
// string endMessage = "The traitor's target has disconnected from the server.";
|
||||
// End(endMessage);
|
||||
// }
|
||||
//}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
using System;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class GameSession
|
||||
{
|
||||
public enum InfoFrameTab { Crew, Mission, ManagePlayers };
|
||||
|
||||
public readonly TaskManager TaskManager;
|
||||
|
||||
public readonly GameMode gameMode;
|
||||
|
||||
//two locations used as the start and end in the MP mode
|
||||
private Location[] dummyLocations;
|
||||
|
||||
private string saveFile;
|
||||
|
||||
private Submarine submarine;
|
||||
|
||||
#if CLIENT
|
||||
public CrewManager CrewManager;
|
||||
#endif
|
||||
|
||||
private Mission currentMission;
|
||||
|
||||
public Mission Mission
|
||||
{
|
||||
get
|
||||
{
|
||||
return currentMission;
|
||||
}
|
||||
}
|
||||
|
||||
private Level level;
|
||||
|
||||
public Level Level
|
||||
{
|
||||
get { return level; }
|
||||
}
|
||||
|
||||
public Location StartLocation
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Map != null) return Map.CurrentLocation;
|
||||
|
||||
if (dummyLocations==null)
|
||||
{
|
||||
CreateDummyLocations();
|
||||
}
|
||||
|
||||
return dummyLocations[0];
|
||||
}
|
||||
}
|
||||
|
||||
public Location EndLocation
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Map != null) return Map.SelectedLocation;
|
||||
|
||||
if (dummyLocations == null)
|
||||
{
|
||||
CreateDummyLocations();
|
||||
}
|
||||
|
||||
return dummyLocations[1];
|
||||
}
|
||||
}
|
||||
|
||||
public Submarine Submarine
|
||||
{
|
||||
get { return submarine; }
|
||||
set { submarine = value; }
|
||||
}
|
||||
|
||||
public string SaveFile
|
||||
{
|
||||
get { return saveFile; }
|
||||
}
|
||||
|
||||
public GameSession(Submarine submarine, string saveFile, GameModePreset gameModePreset = null, string missionType="")
|
||||
{
|
||||
Submarine.MainSub = submarine;
|
||||
|
||||
GameMain.GameSession = this;
|
||||
|
||||
TaskManager = new TaskManager(this);
|
||||
|
||||
this.saveFile = saveFile;
|
||||
|
||||
#if CLIENT
|
||||
CrewManager = new CrewManager();
|
||||
|
||||
infoButton = new GUIButton(new Rectangle(10, 10, 100, 20), "Info", "", null);
|
||||
infoButton.OnClicked = ToggleInfoFrame;
|
||||
#endif
|
||||
|
||||
if (gameModePreset != null) gameMode = gameModePreset.Instantiate(missionType);
|
||||
this.submarine = submarine;
|
||||
}
|
||||
|
||||
public GameSession(Submarine selectedSub, string saveFile, XDocument doc)
|
||||
: this(selectedSub, saveFile)
|
||||
{
|
||||
Submarine.MainSub = submarine;
|
||||
|
||||
GameMain.GameSession = this;
|
||||
|
||||
selectedSub.Name = ToolBox.GetAttributeString(doc.Root, "submarine", selectedSub.Name);
|
||||
|
||||
#if CLIENT
|
||||
CrewManager = new CrewManager();
|
||||
|
||||
foreach (XElement subElement in doc.Root.Elements())
|
||||
{
|
||||
if (subElement.Name.ToString().ToLowerInvariant() != "gamemode") continue;
|
||||
|
||||
gameMode = new SinglePlayerMode(subElement);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private void CreateDummyLocations()
|
||||
{
|
||||
dummyLocations = new Location[2];
|
||||
|
||||
string seed = "";
|
||||
if (GameMain.GameSession != null && GameMain.GameSession.Level != null)
|
||||
{
|
||||
seed = GameMain.GameSession.Level.Seed;
|
||||
}
|
||||
else if (GameMain.NetLobbyScreen != null)
|
||||
{
|
||||
seed = GameMain.NetLobbyScreen.LevelSeed;
|
||||
}
|
||||
|
||||
MTRandom rand = new MTRandom(ToolBox.StringToInt(seed));
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
dummyLocations[i] = Location.CreateRandom(new Vector2((float)rand.NextDouble() * 10000.0f, (float)rand.NextDouble() * 10000.0f));
|
||||
}
|
||||
}
|
||||
|
||||
public void StartShift(string levelSeed, bool loadSecondSub = false)
|
||||
{
|
||||
Level randomLevel = Level.CreateRandom(levelSeed);
|
||||
|
||||
StartShift(randomLevel, true, loadSecondSub);
|
||||
}
|
||||
|
||||
public void StartShift(Level level, bool reloadSub = true, bool loadSecondSub = false)
|
||||
{
|
||||
#if CLIENT
|
||||
GameMain.LightManager.LosEnabled = GameMain.NetworkMember == null || GameMain.NetworkMember.CharacterInfo != null;
|
||||
#endif
|
||||
|
||||
this.level = level;
|
||||
|
||||
if (submarine==null)
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't start game session, submarine not selected");
|
||||
return;
|
||||
}
|
||||
|
||||
if (reloadSub || Submarine.MainSub != submarine) submarine.Load(true);
|
||||
Submarine.MainSub = submarine;
|
||||
if (loadSecondSub)
|
||||
{
|
||||
if (Submarine.MainSubs[1] == null)
|
||||
{
|
||||
Submarine.MainSubs[1] = new Submarine(Submarine.MainSub.FilePath,Submarine.MainSub.MD5Hash.Hash,true);
|
||||
Submarine.MainSubs[1].Load(false);
|
||||
}
|
||||
else if (reloadSub)
|
||||
{
|
||||
Submarine.MainSubs[1].Load(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (level != null)
|
||||
{
|
||||
level.Generate();
|
||||
|
||||
submarine.SetPosition(submarine.FindSpawnPos(level.StartPosition - new Vector2(0.0f, 2000.0f)));
|
||||
|
||||
#if CLIENT
|
||||
GameMain.GameScreen.BackgroundCreatureManager.SpawnSprites(80);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (gameMode.Mission != null)
|
||||
{
|
||||
currentMission = gameMode.Mission;
|
||||
}
|
||||
|
||||
if (gameMode!=null) gameMode.Start();
|
||||
|
||||
if (gameMode.Mission != null) Mission.Start(Level.Loaded);
|
||||
|
||||
TaskManager.StartShift(level);
|
||||
|
||||
if (gameMode != null) gameMode.MsgBox();
|
||||
|
||||
Entity.Spawner = new EntitySpawner();
|
||||
|
||||
#if CLIENT
|
||||
shiftSummary = new ShiftSummary(this);
|
||||
|
||||
GameMain.GameScreen.ColorFade(Color.Black, Color.TransparentBlack, 5.0f);
|
||||
SoundPlayer.SwitchMusic();
|
||||
#endif
|
||||
}
|
||||
|
||||
public void EndShift(string endMessage)
|
||||
{
|
||||
if (Mission != null) Mission.End();
|
||||
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
//CoroutineManager.StartCoroutine(GameMain.Server.EndGame(endMessage));
|
||||
|
||||
}
|
||||
else if (GameMain.Client == null)
|
||||
{
|
||||
//Submarine.Unload();
|
||||
GameMain.LobbyScreen.Select();
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
if (shiftSummary != null)
|
||||
{
|
||||
GUIFrame summaryFrame = shiftSummary.CreateSummaryFrame(endMessage);
|
||||
GUIMessageBox.MessageBoxes.Add(summaryFrame);
|
||||
var okButton = new GUIButton(new Rectangle(0, 0, 100, 30), "Ok", Alignment.BottomRight, "", summaryFrame.children[0]);
|
||||
okButton.OnClicked = (GUIButton button, object obj) => { GUIMessageBox.MessageBoxes.Remove(summaryFrame); return true; };
|
||||
}
|
||||
#endif
|
||||
|
||||
TaskManager.EndShift();
|
||||
|
||||
currentMission = null;
|
||||
|
||||
StatusEffect.StopAll();
|
||||
}
|
||||
|
||||
public void KillCharacter(Character character)
|
||||
{
|
||||
#if CLIENT
|
||||
CrewManager.KillCharacter(character);
|
||||
#endif
|
||||
}
|
||||
|
||||
public void ReviveCharacter(Character character)
|
||||
{
|
||||
#if CLIENT
|
||||
CrewManager.ReviveCharacter(character);
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
static class InfoTextManager
|
||||
{
|
||||
|
||||
private static Dictionary<string, List<string>> infoTexts;
|
||||
|
||||
static InfoTextManager()
|
||||
{
|
||||
LoadInfoTexts(Path.Combine("Content", "InfoTexts.xml"));
|
||||
}
|
||||
|
||||
|
||||
private static void LoadInfoTexts(string file)
|
||||
{
|
||||
infoTexts = new Dictionary<string, List<string>>();
|
||||
|
||||
XDocument doc = ToolBox.TryLoadXml(file);
|
||||
if (doc == null || doc.Root == null) return;
|
||||
|
||||
foreach (XElement subElement in doc.Root.Elements())
|
||||
{
|
||||
string infoName = subElement.Name.ToString().ToLowerInvariant();
|
||||
List<string> infoList = null;
|
||||
if (!infoTexts.TryGetValue(infoName, out infoList))
|
||||
{
|
||||
infoList = new List<string>();
|
||||
infoTexts.Add(infoName, infoList);
|
||||
}
|
||||
|
||||
infoList.Add(subElement.ElementInnerText());
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetInfoText(string infoName)
|
||||
{
|
||||
List<string> infoList = null;
|
||||
if (!infoTexts.TryGetValue(infoName.ToLowerInvariant(), out infoList) || !infoList.Any())
|
||||
{
|
||||
#if DEBUG
|
||||
return "Info text \"" + infoName + "\" not found";
|
||||
#else
|
||||
return "";
|
||||
#endif
|
||||
}
|
||||
|
||||
string text = infoList[Rand.Int(infoList.Count)];
|
||||
|
||||
#if CLIENT
|
||||
foreach (InputType inputType in Enum.GetValues(typeof(InputType)))
|
||||
{
|
||||
text = text.Replace("[" + inputType.ToString() + "]", GameMain.Config.KeyBind(inputType).ToString());
|
||||
}
|
||||
#endif
|
||||
|
||||
if (Submarine.MainSub != null) text = text.Replace("[sub]", Submarine.MainSub.Name);
|
||||
if (GameMain.GameSession != null && GameMain.GameSession.StartLocation != null)
|
||||
{
|
||||
text = text.Replace("[location]", GameMain.GameSession.StartLocation.Name);
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
|
||||
public partial class GameSettings
|
||||
{
|
||||
|
||||
public ContentPackage SelectedContentPackage { get; set; }
|
||||
|
||||
public string MasterServerUrl { get; set; }
|
||||
public bool AutoCheckUpdates { get; set; }
|
||||
public bool WasGameUpdated { get; set; }
|
||||
|
||||
public static bool VerboseLogging { get; set; }
|
||||
|
||||
public GameSettings(string filePath)
|
||||
{
|
||||
ContentPackage.LoadAll(ContentPackage.Folder);
|
||||
|
||||
Load(filePath);
|
||||
}
|
||||
|
||||
public void Load(string filePath)
|
||||
{
|
||||
XDocument doc = ToolBox.TryLoadXml(filePath);
|
||||
|
||||
if (doc == null)
|
||||
{
|
||||
DebugConsole.ThrowError("No config file found");
|
||||
|
||||
MasterServerUrl = "";
|
||||
|
||||
SelectedContentPackage = ContentPackage.list.Any() ? ContentPackage.list[0] : new ContentPackage("");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
MasterServerUrl = ToolBox.GetAttributeString(doc.Root, "masterserverurl", "");
|
||||
|
||||
AutoCheckUpdates = ToolBox.GetAttributeBool(doc.Root, "autocheckupdates", true);
|
||||
WasGameUpdated = ToolBox.GetAttributeBool(doc.Root, "wasgameupdated", false);
|
||||
|
||||
VerboseLogging = ToolBox.GetAttributeBool(doc.Root, "verboselogging", false);
|
||||
|
||||
InitProjSpecific(doc);
|
||||
|
||||
foreach (XElement subElement in doc.Root.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "contentpackage":
|
||||
string path = ToolBox.GetAttributeString(subElement, "path", "");
|
||||
|
||||
|
||||
SelectedContentPackage = ContentPackage.list.Find(cp => cp.Path == path);
|
||||
|
||||
if (SelectedContentPackage == null) SelectedContentPackage = new ContentPackage(path);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
partial void InitProjSpecific(XDocument doc);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
interface IPropertyObject
|
||||
{
|
||||
string Name
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
Dictionary<string, ObjectProperty> ObjectProperties
|
||||
{
|
||||
get;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,348 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Barotrauma.Networking;
|
||||
using Lidgren.Network;
|
||||
using System.Collections.Generic;
|
||||
using Barotrauma.Items.Components;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
[Flags]
|
||||
public enum InvSlotType
|
||||
{
|
||||
None = 0, Any = 1, RightHand = 2, LeftHand = 4, Head = 8, Torso = 16, Legs = 32, Face=64
|
||||
};
|
||||
|
||||
partial class CharacterInventory : Inventory
|
||||
{
|
||||
private Character character;
|
||||
|
||||
public static InvSlotType[] limbSlots = new InvSlotType[] {
|
||||
InvSlotType.Head, InvSlotType.Torso, InvSlotType.Legs, InvSlotType.LeftHand, InvSlotType.RightHand, InvSlotType.Face,
|
||||
InvSlotType.Any, InvSlotType.Any, InvSlotType.Any, InvSlotType.Any, InvSlotType.Any,
|
||||
InvSlotType.Any, InvSlotType.Any, InvSlotType.Any, InvSlotType.Any, InvSlotType.Any};
|
||||
|
||||
public CharacterInventory(int capacity, Character character)
|
||||
: base(character, capacity)
|
||||
{
|
||||
this.character = character;
|
||||
|
||||
InitProjSpecific();
|
||||
}
|
||||
|
||||
partial void InitProjSpecific();
|
||||
|
||||
private bool UseItemOnSelf(int slotIndex)
|
||||
{
|
||||
if (Items[slotIndex] == null) return false;
|
||||
|
||||
#if CLIENT
|
||||
if (GameMain.Client != null)
|
||||
{
|
||||
GameMain.Client.CreateEntityEvent(Items[slotIndex], new object[] { NetEntityEvent.Type.ApplyStatusEffect });
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
GameMain.Server.CreateEntityEvent(Items[slotIndex], new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnUse, character.ID });
|
||||
}
|
||||
|
||||
Items[slotIndex].ApplyStatusEffects(ActionType.OnUse, 1.0f, character);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public int FindLimbSlot(InvSlotType limbSlot)
|
||||
{
|
||||
for (int i = 0; i < Items.Length; i++)
|
||||
{
|
||||
if (limbSlots[i] == limbSlot) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public bool IsInLimbSlot(Item item, InvSlotType limbSlot)
|
||||
{
|
||||
for (int i = 0; i<Items.Length; i++)
|
||||
{
|
||||
if (Items[i] == item && limbSlots[i] == limbSlot) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool CanBePut(Item item, int i)
|
||||
{
|
||||
return base.CanBePut(item, i) && item.AllowedSlots.Contains(limbSlots[i]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If there is room, puts the item in the inventory and returns true, otherwise returns false
|
||||
/// </summary>
|
||||
public override bool TryPutItem(Item item, List<InvSlotType> allowedSlots = null, bool createNetworkEvent = true)
|
||||
{
|
||||
if (allowedSlots == null || !allowedSlots.Any()) return false;
|
||||
|
||||
for (int i = 0; i < capacity; i++)
|
||||
{
|
||||
//already in the inventory and in a suitable slot
|
||||
if (Items[i] == item && allowedSlots.Any(a => a.HasFlag(limbSlots[i])))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
//try to place the item in LimBlot.Any slot if that's allowed
|
||||
if (allowedSlots.Contains(InvSlotType.Any))
|
||||
{
|
||||
for (int i = 0; i < capacity; i++)
|
||||
{
|
||||
if (Items[i] != null || limbSlots[i] != InvSlotType.Any) continue;
|
||||
|
||||
PutItem(item, i, true, createNetworkEvent);
|
||||
item.Unequip(character);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool placed = false;
|
||||
foreach (InvSlotType allowedSlot in allowedSlots)
|
||||
{
|
||||
//check if all the required slots are free
|
||||
bool free = true;
|
||||
for (int i = 0; i < capacity; i++)
|
||||
{
|
||||
if (allowedSlot.HasFlag(limbSlots[i]) && Items[i]!=null && Items[i]!=item)
|
||||
{
|
||||
free = false;
|
||||
#if CLIENT
|
||||
if (slots != null) slots[i].ShowBorderHighlight(Color.Red, 0.1f, 0.9f);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
if (!free) continue;
|
||||
|
||||
for (int i = 0; i < capacity; i++)
|
||||
{
|
||||
if (allowedSlot.HasFlag(limbSlots[i]) && Items[i] == null)
|
||||
{
|
||||
PutItem(item, i, !placed, createNetworkEvent);
|
||||
item.Equip(character);
|
||||
placed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (placed)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return placed;
|
||||
}
|
||||
|
||||
public override bool TryPutItem(Item item, int index, bool allowSwapping, bool createNetworkEvent = true)
|
||||
{
|
||||
//there's already an item in the slot
|
||||
if (Items[index] != null)
|
||||
{
|
||||
if (Items[index] == item) return false;
|
||||
|
||||
bool combined = false;
|
||||
if (Items[index].Combine(item))
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(Items[index] != null);
|
||||
|
||||
Inventory otherInventory = Items[index].ParentInventory;
|
||||
if (otherInventory != null && otherInventory.Owner!=null)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
combined = true;
|
||||
}
|
||||
//if moving the item between slots in the same inventory
|
||||
else if (item.ParentInventory == this && allowSwapping)
|
||||
{
|
||||
int currentIndex = Array.IndexOf(Items, item);
|
||||
|
||||
Item existingItem = Items[index];
|
||||
|
||||
Items[currentIndex] = null;
|
||||
Items[index] = null;
|
||||
//if the item in the slot can be moved to the slot of the moved item
|
||||
if (TryPutItem(existingItem, currentIndex, false, createNetworkEvent) &&
|
||||
TryPutItem(item, index, false, createNetworkEvent))
|
||||
{
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
Items[currentIndex] = null;
|
||||
Items[index] = null;
|
||||
|
||||
//swapping the items failed -> move them back to where they were
|
||||
TryPutItem(item, currentIndex, false, createNetworkEvent);
|
||||
TryPutItem(existingItem, index, false, createNetworkEvent);
|
||||
}
|
||||
}
|
||||
|
||||
return combined;
|
||||
}
|
||||
|
||||
if (limbSlots[index] == InvSlotType.Any)
|
||||
{
|
||||
if (!item.AllowedSlots.Contains(InvSlotType.Any)) return false;
|
||||
if (Items[index] != null) return Items[index] == item;
|
||||
|
||||
PutItem(item, index, true, createNetworkEvent);
|
||||
return true;
|
||||
}
|
||||
|
||||
InvSlotType placeToSlots = InvSlotType.None;
|
||||
|
||||
bool slotsFree = true;
|
||||
List<InvSlotType> allowedSlots = item.AllowedSlots;
|
||||
foreach (InvSlotType allowedSlot in allowedSlots)
|
||||
{
|
||||
if (!allowedSlot.HasFlag(limbSlots[index])) continue;
|
||||
|
||||
for (int i = 0; i < capacity; i++)
|
||||
{
|
||||
if (allowedSlot.HasFlag(limbSlots[i]) && Items[i] != null && Items[i] != item)
|
||||
{
|
||||
slotsFree = false;
|
||||
break;
|
||||
}
|
||||
|
||||
placeToSlots = allowedSlot;
|
||||
}
|
||||
}
|
||||
|
||||
if (!slotsFree) return false;
|
||||
|
||||
return TryPutItem(item, new List<InvSlotType>() {placeToSlots}, createNetworkEvent);
|
||||
}
|
||||
|
||||
protected override void PutItem(Item item, int i, bool removeItem = true, bool createNetworkEvent = true)
|
||||
{
|
||||
base.PutItem(item, i, removeItem, createNetworkEvent);
|
||||
CreateSlots();
|
||||
}
|
||||
|
||||
public override void RemoveItem(Item item)
|
||||
{
|
||||
base.RemoveItem(item);
|
||||
CreateSlots();
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, bool subInventory = false)
|
||||
{
|
||||
base.Update(deltaTime);
|
||||
|
||||
if (doubleClickedItem != null)
|
||||
{
|
||||
if (doubleClickedItem.ParentInventory != this)
|
||||
{
|
||||
TryPutItem(doubleClickedItem, doubleClickedItem.AllowedSlots, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (character.SelectedConstruction != null)
|
||||
{
|
||||
var selectedContainer = character.SelectedConstruction.GetComponent<ItemContainer>();
|
||||
if (selectedContainer != null && selectedContainer.Inventory != null)
|
||||
{
|
||||
selectedContainer.Inventory.TryPutItem(doubleClickedItem, doubleClickedItem.AllowedSlots, true);
|
||||
}
|
||||
}
|
||||
else if (character.SelectedCharacter != null && character.SelectedCharacter.Inventory != null)
|
||||
{
|
||||
character.SelectedCharacter.Inventory.TryPutItem(doubleClickedItem, doubleClickedItem.AllowedSlots, true);
|
||||
}
|
||||
else //doubleclicked and no other inventory is selected
|
||||
{
|
||||
//not equipped -> attempt to equip
|
||||
if (IsInLimbSlot(doubleClickedItem, InvSlotType.Any))
|
||||
{
|
||||
TryPutItem(doubleClickedItem, doubleClickedItem.AllowedSlots.FindAll(i => i != InvSlotType.Any), true);
|
||||
}
|
||||
//equipped -> attempt to unequip
|
||||
else if (doubleClickedItem.AllowedSlots.Contains(InvSlotType.Any))
|
||||
{
|
||||
TryPutItem(doubleClickedItem, new List<InvSlotType>() { InvSlotType.Any }, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedSlot > -1)
|
||||
{
|
||||
UpdateSubInventory(deltaTime, selectedSlot);
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
if (character == Character.Controlled)
|
||||
{
|
||||
for (int i = 0; i < capacity; i++)
|
||||
{
|
||||
if (selectedSlot != i &&
|
||||
Items[i] != null && Items[i].CanUseOnSelf && character.HasSelectedItem(Items[i]))
|
||||
{
|
||||
//-3 because selected items are in slots 3 and 4 (hands)
|
||||
useOnSelfButton[i - 3].Update(deltaTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
//cancel dragging if too far away from the container of the dragged item
|
||||
if (draggingItem != null)
|
||||
{
|
||||
var rootContainer = draggingItem.GetRootContainer();
|
||||
var rootInventory = draggingItem.ParentInventory;
|
||||
|
||||
if (rootContainer != null)
|
||||
{
|
||||
rootInventory = rootContainer.ParentInventory != null ?
|
||||
rootContainer.ParentInventory : rootContainer.GetComponent<Items.Components.ItemContainer>().Inventory;
|
||||
}
|
||||
|
||||
if (rootInventory != null &&
|
||||
rootInventory.Owner != Character.Controlled &&
|
||||
rootInventory.Owner != Character.Controlled.SelectedConstruction &&
|
||||
rootInventory.Owner != Character.Controlled.SelectedCharacter)
|
||||
{
|
||||
draggingItem = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
doubleClickedItem = null;
|
||||
}
|
||||
|
||||
private void MergeSlots()
|
||||
{
|
||||
for (int i = 0; i < capacity-1; i++)
|
||||
{
|
||||
if (slots[i].Disabled || Items[i] == null) continue;
|
||||
|
||||
for (int n = i+1; n < capacity; n++)
|
||||
{
|
||||
if (Items[n] == Items[i])
|
||||
{
|
||||
slots[i].Rect = Rectangle.Union(slots[i].Rect, slots[n].Rect);
|
||||
slots[n].Disabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
selectedSlot = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,786 @@
|
||||
using Barotrauma.Networking;
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using FarseerPhysics.Dynamics.Joints;
|
||||
using FarseerPhysics.Factories;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
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.Items.Components
|
||||
{
|
||||
partial class DockingPort : ItemComponent, IDrawableComponent, IServerSerializable
|
||||
{
|
||||
public static List<DockingPort> list = new List<DockingPort>();
|
||||
|
||||
private Sprite overlaySprite;
|
||||
|
||||
private Vector2 distanceTolerance;
|
||||
|
||||
private DockingPort dockingTarget;
|
||||
|
||||
private float dockingState;
|
||||
private int dockingDir;
|
||||
|
||||
private Joint joint;
|
||||
|
||||
private Hull[] hulls;
|
||||
private ushort?[] hullIds;
|
||||
|
||||
private Door door;
|
||||
|
||||
private Body[] bodies;
|
||||
|
||||
private Body doorBody;
|
||||
|
||||
private Gap gap;
|
||||
private ushort? gapId;
|
||||
|
||||
private bool docked;
|
||||
|
||||
public int DockingDir
|
||||
{
|
||||
get { return dockingDir; }
|
||||
set { dockingDir = value; }
|
||||
}
|
||||
|
||||
[HasDefaultValue("32.0,32.0", false)]
|
||||
public string DistanceTolerance
|
||||
{
|
||||
get { return ToolBox.Vector2ToString(distanceTolerance); }
|
||||
set { distanceTolerance = ToolBox.ParseToVector2(value); }
|
||||
}
|
||||
|
||||
[HasDefaultValue(32.0f, false)]
|
||||
public float DockedDistance
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[HasDefaultValue(true, false)]
|
||||
public bool IsHorizontal
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public DockingPort DockingTarget
|
||||
{
|
||||
get { return dockingTarget; }
|
||||
set { dockingTarget = value; }
|
||||
}
|
||||
|
||||
public bool Docked
|
||||
{
|
||||
get
|
||||
{
|
||||
return docked;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (!docked && value)
|
||||
{
|
||||
if (dockingTarget == null) AttemptDock();
|
||||
if (dockingTarget == null) return;
|
||||
|
||||
docked = true;
|
||||
}
|
||||
else if (docked && !value)
|
||||
{
|
||||
Undock();
|
||||
}
|
||||
|
||||
//base.IsActive = value;
|
||||
}
|
||||
}
|
||||
|
||||
public DockingPort(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
// isOpen = false;
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
string texturePath = ToolBox.GetAttributeString(subElement, "texture", "");
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "sprite":
|
||||
overlaySprite = new Sprite(subElement, texturePath.Contains("/") ? "" : Path.GetDirectoryName(item.Prefab.ConfigFile));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
IsActive = true;
|
||||
|
||||
hullIds = new ushort?[2];
|
||||
|
||||
list.Add(this);
|
||||
}
|
||||
|
||||
public override void FlipX()
|
||||
{
|
||||
base.FlipX();
|
||||
|
||||
if (dockingTarget != null)
|
||||
{
|
||||
if (joint != null)
|
||||
{
|
||||
CreateJoint(joint is WeldJoint);
|
||||
LinkHullsToGap();
|
||||
}
|
||||
else if (dockingTarget.joint != null)
|
||||
{
|
||||
if (!GameMain.World.BodyList.Contains(dockingTarget.joint.BodyA) ||
|
||||
!GameMain.World.BodyList.Contains(dockingTarget.joint.BodyB))
|
||||
{
|
||||
dockingTarget.CreateJoint(dockingTarget.joint is WeldJoint);
|
||||
}
|
||||
dockingTarget.LinkHullsToGap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private DockingPort FindAdjacentPort()
|
||||
{
|
||||
foreach (DockingPort port in list)
|
||||
{
|
||||
if (port == this || port.item.Submarine == item.Submarine) continue;
|
||||
|
||||
if (Math.Abs(port.item.WorldPosition.X - item.WorldPosition.X) > distanceTolerance.X) continue;
|
||||
if (Math.Abs(port.item.WorldPosition.Y - item.WorldPosition.Y) > distanceTolerance.Y) continue;
|
||||
|
||||
return port;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void AttemptDock()
|
||||
{
|
||||
var adjacentPort = FindAdjacentPort();
|
||||
|
||||
if (adjacentPort != null) Dock(adjacentPort);
|
||||
}
|
||||
|
||||
public void Dock(DockingPort target)
|
||||
{
|
||||
if (item.Submarine.DockedTo.Contains(target.item.Submarine)) return;
|
||||
|
||||
if (dockingTarget != null)
|
||||
{
|
||||
Undock();
|
||||
}
|
||||
|
||||
if (target.item.Submarine == item.Submarine)
|
||||
{
|
||||
DebugConsole.ThrowError("Error - tried to dock a submarine to itself");
|
||||
dockingTarget = null;
|
||||
return;
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
PlaySound(ActionType.OnUse, item.WorldPosition);
|
||||
#endif
|
||||
|
||||
if (!item.linkedTo.Contains(target.item)) item.linkedTo.Add(target.item);
|
||||
if (!target.item.linkedTo.Contains(item)) target.item.linkedTo.Add(item);
|
||||
|
||||
if (!target.item.Submarine.DockedTo.Contains(item.Submarine)) target.item.Submarine.DockedTo.Add(item.Submarine);
|
||||
if (!item.Submarine.DockedTo.Contains(target.item.Submarine)) item.Submarine.DockedTo.Add(target.item.Submarine);
|
||||
|
||||
dockingTarget = target;
|
||||
dockingTarget.dockingTarget = this;
|
||||
|
||||
docked = true;
|
||||
dockingTarget.Docked = true;
|
||||
|
||||
if (Character.Controlled != null &&
|
||||
(Character.Controlled.Submarine == dockingTarget.item.Submarine || Character.Controlled.Submarine == item.Submarine))
|
||||
{
|
||||
GameMain.GameScreen.Cam.Shake = Vector2.Distance(dockingTarget.item.Submarine.Velocity, item.Submarine.Velocity);
|
||||
}
|
||||
|
||||
dockingDir = IsHorizontal ?
|
||||
Math.Sign(dockingTarget.item.WorldPosition.X - item.WorldPosition.X) :
|
||||
Math.Sign(dockingTarget.item.WorldPosition.Y - item.WorldPosition.Y);
|
||||
dockingTarget.dockingDir = -dockingDir;
|
||||
|
||||
|
||||
foreach (WayPoint wp in WayPoint.WayPointList)
|
||||
{
|
||||
if (wp.Submarine != item.Submarine || wp.SpawnType != SpawnType.Path) continue;
|
||||
|
||||
if (!Submarine.RectContains(item.Rect, wp.Position)) continue;
|
||||
|
||||
foreach (WayPoint wp2 in WayPoint.WayPointList)
|
||||
{
|
||||
if (wp2.Submarine != dockingTarget.item.Submarine || wp2.SpawnType != SpawnType.Path) continue;
|
||||
|
||||
if (!Submarine.RectContains(dockingTarget.item.Rect, wp2.Position)) continue;
|
||||
|
||||
wp.linkedTo.Add(wp2);
|
||||
wp2.linkedTo.Add(wp);
|
||||
}
|
||||
}
|
||||
|
||||
CreateJoint(false);
|
||||
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void Lock(bool isNetworkMessage)
|
||||
{
|
||||
if (GameMain.Client != null && !isNetworkMessage) return;
|
||||
|
||||
if (dockingTarget==null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error - attempted to lock a docking port that's not connected to anything");
|
||||
return;
|
||||
}
|
||||
else if (joint is WeldJoint)
|
||||
{
|
||||
//DebugConsole.ThrowError("Error - attempted to lock a docking port that's already locked");
|
||||
return;
|
||||
}
|
||||
|
||||
dockingDir = IsHorizontal ?
|
||||
Math.Sign(dockingTarget.item.WorldPosition.X - item.WorldPosition.X) :
|
||||
Math.Sign(dockingTarget.item.WorldPosition.Y - item.WorldPosition.Y);
|
||||
dockingTarget.dockingDir = -dockingDir;
|
||||
|
||||
#if CLIENT
|
||||
PlaySound(ActionType.OnSecondaryUse, item.WorldPosition);
|
||||
#endif
|
||||
|
||||
ConnectWireBetweenPorts();
|
||||
|
||||
CreateJoint(true);
|
||||
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
|
||||
if (!item.linkedTo.Any(e => e is Hull) && !dockingTarget.item.linkedTo.Any(e => e is Hull))
|
||||
{
|
||||
CreateHull();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void CreateJoint(bool useWeldJoint)
|
||||
{
|
||||
if (joint != null)
|
||||
{
|
||||
GameMain.World.RemoveJoint(joint);
|
||||
joint = null;
|
||||
}
|
||||
|
||||
Vector2 offset = (IsHorizontal ?
|
||||
Vector2.UnitX * dockingDir :
|
||||
Vector2.UnitY * dockingDir);
|
||||
offset *= DockedDistance * 0.5f;
|
||||
|
||||
Vector2 pos1 = item.WorldPosition + offset;
|
||||
|
||||
Vector2 pos2 = dockingTarget.item.WorldPosition - offset;
|
||||
|
||||
if (useWeldJoint)
|
||||
{
|
||||
joint = JointFactory.CreateWeldJoint(GameMain.World,
|
||||
item.Submarine.PhysicsBody.FarseerBody, dockingTarget.item.Submarine.PhysicsBody.FarseerBody,
|
||||
ConvertUnits.ToSimUnits(pos1), FarseerPhysics.ConvertUnits.ToSimUnits(pos2), true);
|
||||
|
||||
((WeldJoint)joint).FrequencyHz = 1.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
var distanceJoint = JointFactory.CreateDistanceJoint(GameMain.World,
|
||||
item.Submarine.PhysicsBody.FarseerBody, dockingTarget.item.Submarine.PhysicsBody.FarseerBody,
|
||||
ConvertUnits.ToSimUnits(pos1), FarseerPhysics.ConvertUnits.ToSimUnits(pos2), true);
|
||||
|
||||
distanceJoint.Length = 0.01f;
|
||||
distanceJoint.Frequency = 1.0f;
|
||||
distanceJoint.DampingRatio = 0.8f;
|
||||
|
||||
joint = distanceJoint;
|
||||
}
|
||||
|
||||
|
||||
joint.CollideConnected = true;
|
||||
}
|
||||
|
||||
private void ConnectWireBetweenPorts()
|
||||
{
|
||||
Wire wire = item.GetComponent<Wire>();
|
||||
if (wire == null) return;
|
||||
|
||||
wire.Hidden = true;
|
||||
wire.Locked = true;
|
||||
|
||||
if (Item.Connections == null) return;
|
||||
|
||||
var powerConnection = Item.Connections.Find(c => c.IsPower);
|
||||
if (powerConnection == null) return;
|
||||
|
||||
if (dockingTarget == null || dockingTarget.item.Connections == null) return;
|
||||
var recipient = dockingTarget.item.Connections.Find(c => c.IsPower);
|
||||
if (recipient == null) return;
|
||||
|
||||
wire.RemoveConnection(item);
|
||||
wire.RemoveConnection(dockingTarget.item);
|
||||
|
||||
|
||||
powerConnection.TryAddLink(wire);
|
||||
wire.Connect(powerConnection, false, false);
|
||||
recipient.TryAddLink(wire);
|
||||
wire.Connect(recipient, false, false);
|
||||
}
|
||||
|
||||
private void CreateDoorBody()
|
||||
{
|
||||
doorBody = BodyFactory.CreateRectangle(GameMain.World,
|
||||
dockingTarget.door.Body.width,
|
||||
dockingTarget.door.Body.height,
|
||||
1.0f,
|
||||
dockingTarget.door);
|
||||
|
||||
doorBody.CollisionCategories = Physics.CollisionWall;
|
||||
doorBody.BodyType = BodyType.Static;
|
||||
doorBody.SetTransform(
|
||||
ConvertUnits.ToSimUnits(item.Position + (dockingTarget.door.Item.WorldPosition - item.WorldPosition)),
|
||||
0.0f);
|
||||
}
|
||||
|
||||
private void CreateHull()
|
||||
{
|
||||
var hullRects = new Rectangle[] { item.WorldRect, dockingTarget.item.WorldRect };
|
||||
var subs = new Submarine[] { item.Submarine, dockingTarget.item.Submarine };
|
||||
|
||||
hulls = new Hull[2];
|
||||
bodies = new Body[4];
|
||||
|
||||
if (dockingTarget.door != null)
|
||||
{
|
||||
CreateDoorBody();
|
||||
}
|
||||
|
||||
if (door != null)
|
||||
{
|
||||
dockingTarget.CreateDoorBody();
|
||||
}
|
||||
|
||||
if (IsHorizontal)
|
||||
{
|
||||
if (hullRects[0].Center.X > hullRects[1].Center.X)
|
||||
{
|
||||
hullRects = new Rectangle[] { dockingTarget.item.WorldRect, item.WorldRect };
|
||||
subs = new Submarine[] { dockingTarget.item.Submarine,item.Submarine };
|
||||
}
|
||||
|
||||
hullRects[0] = new Rectangle(hullRects[0].Center.X, hullRects[0].Y, ((int)DockedDistance / 2), hullRects[0].Height);
|
||||
hullRects[1] = new Rectangle(hullRects[1].Center.X - ((int)DockedDistance / 2), hullRects[1].Y, ((int)DockedDistance / 2), hullRects[1].Height);
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
hullRects[i].Location -= MathUtils.ToPoint((subs[i].WorldPosition - subs[i].HiddenSubPosition));
|
||||
hulls[i] = new Hull(MapEntityPrefab.list.Find(m => m.Name == "Hull"), hullRects[i], subs[i]);
|
||||
hulls[i].AddToGrid(subs[i]);
|
||||
|
||||
for (int j = 0; j < 2; j++)
|
||||
{
|
||||
bodies[i + j * 2] = BodyFactory.CreateEdge(GameMain.World,
|
||||
ConvertUnits.ToSimUnits(new Vector2(hullRects[i].X, hullRects[i].Y - hullRects[i].Height * j)),
|
||||
ConvertUnits.ToSimUnits(new Vector2(hullRects[i].Right, hullRects[i].Y - hullRects[i].Height * j)));
|
||||
}
|
||||
}
|
||||
|
||||
gap = new Gap(new Rectangle(hullRects[0].Right - 2, hullRects[0].Y, 4, hullRects[0].Height), true, subs[0]);
|
||||
if (gapId != null) gap.ID = (ushort)gapId;
|
||||
|
||||
LinkHullsToGap();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (hullRects[0].Center.Y > hullRects[1].Center.Y)
|
||||
{
|
||||
hullRects = new Rectangle[] { dockingTarget.item.WorldRect, item.WorldRect };
|
||||
subs = new Submarine[] { dockingTarget.item.Submarine, item.Submarine };
|
||||
}
|
||||
|
||||
hullRects[0] = new Rectangle(hullRects[0].X, hullRects[0].Y + (int)(-hullRects[0].Height+DockedDistance)/2, hullRects[0].Width, ((int)DockedDistance / 2));
|
||||
hullRects[1] = new Rectangle(hullRects[1].X, hullRects[1].Y - hullRects[1].Height/2, hullRects[1].Width, ((int)DockedDistance / 2));
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
hullRects[i].Location -= MathUtils.ToPoint((subs[i].WorldPosition - subs[i].HiddenSubPosition));
|
||||
hulls[i] = new Hull(MapEntityPrefab.list.Find(m => m.Name == "Hull"), hullRects[i], subs[i]);
|
||||
hulls[i].AddToGrid(subs[i]);
|
||||
|
||||
if (hullIds[i] != null) hulls[i].ID = (ushort)hullIds[i];
|
||||
}
|
||||
|
||||
gap = new Gap(new Rectangle(hullRects[0].X, hullRects[0].Y+2, hullRects[0].Width, 4), false, subs[0]);
|
||||
if (gapId != null) gap.ID = (ushort)gapId;
|
||||
|
||||
LinkHullsToGap();
|
||||
}
|
||||
|
||||
item.linkedTo.Add(hulls[0]);
|
||||
item.linkedTo.Add(hulls[1]);
|
||||
|
||||
hullIds[0] = hulls[0].ID;
|
||||
hullIds[1] = hulls[1].ID;
|
||||
|
||||
gap.DisableHullRechecks = true;
|
||||
gapId = gap.ID;
|
||||
|
||||
item.linkedTo.Add(gap);
|
||||
|
||||
foreach (Body body in bodies)
|
||||
{
|
||||
if (body == null) continue;
|
||||
body.BodyType = BodyType.Static;
|
||||
body.Friction = 0.5f;
|
||||
|
||||
body.CollisionCategories = Physics.CollisionWall;
|
||||
}
|
||||
}
|
||||
|
||||
private void LinkHullsToGap()
|
||||
{
|
||||
if (gap == null || hulls == null || hulls[0] == null || hulls[1] == null)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("Failed to link dockingport hulls to gap");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
gap.linkedTo.Clear();
|
||||
|
||||
if (IsHorizontal)
|
||||
{
|
||||
if (hulls[0].WorldRect.X < hulls[1].WorldRect.X)
|
||||
{
|
||||
gap.linkedTo.Add(hulls[0]);
|
||||
gap.linkedTo.Add(hulls[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
gap.linkedTo.Add(hulls[1]);
|
||||
gap.linkedTo.Add(hulls[0]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (hulls[0].WorldRect.Y > hulls[1].WorldRect.Y)
|
||||
{
|
||||
gap.linkedTo.Add(hulls[0]);
|
||||
gap.linkedTo.Add(hulls[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
gap.linkedTo.Add(hulls[1]);
|
||||
gap.linkedTo.Add(hulls[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Undock()
|
||||
{
|
||||
if (dockingTarget == null || !docked) return;
|
||||
|
||||
#if CLIENT
|
||||
PlaySound(ActionType.OnUse, item.WorldPosition);
|
||||
#endif
|
||||
|
||||
dockingTarget.item.Submarine.DockedTo.Remove(item.Submarine);
|
||||
item.Submarine.DockedTo.Remove(dockingTarget.item.Submarine);
|
||||
|
||||
//remove all waypoint links between this sub and the dockingtarget
|
||||
foreach (WayPoint wp in WayPoint.WayPointList)
|
||||
{
|
||||
if (wp.Submarine != item.Submarine || wp.SpawnType != SpawnType.Path) continue;
|
||||
|
||||
for (int i = wp.linkedTo.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var wp2 = wp.linkedTo[i] as WayPoint;
|
||||
if (wp2 == null) continue;
|
||||
|
||||
if (wp.Submarine == dockingTarget.item.Submarine)
|
||||
{
|
||||
wp.linkedTo.RemoveAt(i);
|
||||
wp2.linkedTo.Remove(wp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
item.linkedTo.Clear();
|
||||
|
||||
docked = false;
|
||||
|
||||
dockingTarget.Undock();
|
||||
dockingTarget = null;
|
||||
|
||||
if (doorBody != null)
|
||||
{
|
||||
GameMain.World.RemoveBody(doorBody);
|
||||
doorBody = null;
|
||||
}
|
||||
|
||||
var wire = item.GetComponent<Wire>();
|
||||
if (wire != null)
|
||||
{
|
||||
wire.Drop(null);
|
||||
}
|
||||
|
||||
if (joint != null)
|
||||
{
|
||||
GameMain.World.RemoveJoint(joint);
|
||||
joint = null;
|
||||
}
|
||||
|
||||
if (hulls != null)
|
||||
{
|
||||
hulls[0].Remove();
|
||||
hulls[1].Remove();
|
||||
hulls = null;
|
||||
}
|
||||
|
||||
if (gap != null)
|
||||
{
|
||||
gap.Remove();
|
||||
gap = null;
|
||||
}
|
||||
|
||||
hullIds[0] = null;
|
||||
hullIds[1] = null;
|
||||
|
||||
gapId = null;
|
||||
|
||||
if (bodies!=null)
|
||||
{
|
||||
foreach (Body body in bodies)
|
||||
{
|
||||
if (body == null) continue;
|
||||
GameMain.World.RemoveBody(body);
|
||||
}
|
||||
bodies = null;
|
||||
}
|
||||
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
if (dockingTarget == null)
|
||||
{
|
||||
dockingState = MathHelper.Lerp(dockingState, 0.0f, deltaTime * 10.0f);
|
||||
if (dockingState < 0.01f) docked = false;
|
||||
|
||||
item.SendSignal(0, "0", "state_out", null);
|
||||
item.SendSignal(0, (FindAdjacentPort() != null) ? "1" : "0", "proximity_sensor", null);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!docked)
|
||||
{
|
||||
Dock(dockingTarget);
|
||||
if (dockingTarget == null) return;
|
||||
}
|
||||
|
||||
if (joint is DistanceJoint)
|
||||
{
|
||||
item.SendSignal(0, "0", "state_out", null);
|
||||
dockingState = MathHelper.Lerp(dockingState, 0.5f, deltaTime * 10.0f);
|
||||
|
||||
if (Vector2.Distance(joint.WorldAnchorA, joint.WorldAnchorB) < 0.05f)
|
||||
{
|
||||
Lock(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (dockingTarget.door != null && doorBody != null)
|
||||
{
|
||||
doorBody.Enabled = dockingTarget.door.Body.Enabled;
|
||||
}
|
||||
|
||||
item.SendSignal(0, "1", "state_out", null);
|
||||
|
||||
dockingState = MathHelper.Lerp(dockingState, 1.0f, deltaTime * 10.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void RemoveComponentSpecific()
|
||||
{
|
||||
list.Remove(this);
|
||||
}
|
||||
|
||||
public override void OnMapLoaded()
|
||||
{
|
||||
foreach (Item it in Item.ItemList)
|
||||
{
|
||||
if (it.Submarine != item.Submarine) continue;
|
||||
|
||||
var doorComponent = it.GetComponent<Door>();
|
||||
if (doorComponent == null) continue;
|
||||
|
||||
if (Vector2.Distance(item.Position, doorComponent.Item.Position) < Submarine.GridSize.X)
|
||||
{
|
||||
this.door = doorComponent;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!item.linkedTo.Any()) return;
|
||||
|
||||
List<MapEntity> linked = new List<MapEntity>(item.linkedTo);
|
||||
foreach (MapEntity entity in linked)
|
||||
{
|
||||
var hull = entity as Hull;
|
||||
if (hull != null)
|
||||
{
|
||||
hull.Remove();
|
||||
item.linkedTo.Remove(hull);
|
||||
continue;
|
||||
}
|
||||
|
||||
var gap = entity as Gap;
|
||||
if (gap != null)
|
||||
{
|
||||
gap.Remove();
|
||||
continue;
|
||||
}
|
||||
|
||||
Item linkedItem = entity as Item;
|
||||
if (linkedItem == null) continue;
|
||||
|
||||
var dockingPort = linkedItem.GetComponent<DockingPort>();
|
||||
if (dockingPort != null)
|
||||
{
|
||||
Dock(dockingPort);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f)
|
||||
{
|
||||
if (GameMain.Client != null) return;
|
||||
|
||||
bool wasDocked = docked;
|
||||
DockingPort prevDockingTarget = dockingTarget;
|
||||
|
||||
switch (connection.Name)
|
||||
{
|
||||
case "toggle":
|
||||
Docked = !docked;
|
||||
break;
|
||||
case "set_active":
|
||||
case "set_state":
|
||||
Docked = signal != "0";
|
||||
break;
|
||||
}
|
||||
|
||||
if (sender != null && docked != wasDocked)
|
||||
{
|
||||
if (docked)
|
||||
{
|
||||
if (item.Submarine != null && dockingTarget?.item?.Submarine != null)
|
||||
GameServer.Log(sender.Name + " docked " + item.Submarine.Name + " to " + dockingTarget.item.Submarine.Name, ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (item.Submarine != null && prevDockingTarget?.item?.Submarine != null)
|
||||
GameServer.Log(sender.Name + " undocked " + item.Submarine.Name + " from " + prevDockingTarget.item.Submarine.Name, ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ServerWrite(Lidgren.Network.NetBuffer msg, Client c, object[] extraData = null)
|
||||
{
|
||||
msg.Write(docked);
|
||||
|
||||
if (docked)
|
||||
{
|
||||
msg.Write(dockingTarget.item.ID);
|
||||
|
||||
if (hullIds[0] != null && hullIds[1] != null && gapId != null)
|
||||
{
|
||||
msg.Write(true);
|
||||
msg.Write((ushort)hullIds[0]);
|
||||
msg.Write((ushort)hullIds[1]);
|
||||
msg.Write((ushort)gapId);
|
||||
}
|
||||
else
|
||||
{
|
||||
msg.Write(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ClientRead(ServerNetObject type, Lidgren.Network.NetBuffer msg, float sendingTime)
|
||||
{
|
||||
bool isDocked = msg.ReadBoolean();
|
||||
|
||||
if (isDocked)
|
||||
{
|
||||
ushort dockingTargetID = msg.ReadUInt16();
|
||||
|
||||
bool isLocked = msg.ReadBoolean();
|
||||
|
||||
if (isLocked)
|
||||
{
|
||||
hullIds[0] = msg.ReadUInt16();
|
||||
hullIds[1] = msg.ReadUInt16();
|
||||
gapId = msg.ReadUInt16();
|
||||
}
|
||||
|
||||
Entity targetEntity = Entity.FindEntityByID(dockingTargetID);
|
||||
if (targetEntity == null || !(targetEntity is Item))
|
||||
{
|
||||
DebugConsole.ThrowError("Invalid docking port network event (can't dock to " + targetEntity.ToString() + ")");
|
||||
return;
|
||||
}
|
||||
|
||||
dockingTarget = (targetEntity as Item).GetComponent<DockingPort>();
|
||||
if (dockingTarget == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Invalid docking port network event (" + targetEntity + " doesn't have a docking port component)");
|
||||
return;
|
||||
}
|
||||
|
||||
Dock(dockingTarget);
|
||||
|
||||
if (isLocked)
|
||||
{
|
||||
Lock(true);
|
||||
|
||||
hulls[0].ID = (ushort)hullIds[0];
|
||||
hulls[1].ID = (ushort)hullIds[1];
|
||||
gap.ID = (ushort)gapId;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Undock();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,445 @@
|
||||
using Barotrauma.Networking;
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
#if CLIENT
|
||||
using Barotrauma.Lights;
|
||||
#endif
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class Door : ItemComponent, IDrawableComponent, IServerSerializable
|
||||
{
|
||||
private Gap linkedGap;
|
||||
|
||||
private Rectangle window;
|
||||
|
||||
private bool isOpen;
|
||||
|
||||
private float openState;
|
||||
|
||||
private PhysicsBody body;
|
||||
|
||||
private Sprite doorSprite, weldedSprite;
|
||||
|
||||
private bool isHorizontal;
|
||||
|
||||
private bool isStuck;
|
||||
|
||||
private bool? predictedState;
|
||||
private float resetPredictionTimer;
|
||||
|
||||
public PhysicsBody Body
|
||||
{
|
||||
get { return body; }
|
||||
}
|
||||
|
||||
private float stuck;
|
||||
public float Stuck
|
||||
{
|
||||
get { return stuck; }
|
||||
set
|
||||
{
|
||||
if (isOpen) return;
|
||||
stuck = MathHelper.Clamp(value, 0.0f, 100.0f);
|
||||
if (stuck == 0.0f) isStuck = false;
|
||||
if (stuck == 100.0f) isStuck = true;
|
||||
}
|
||||
}
|
||||
|
||||
public Gap LinkedGap
|
||||
{
|
||||
get
|
||||
{
|
||||
if (linkedGap != null) return linkedGap;
|
||||
|
||||
foreach (MapEntity e in item.linkedTo)
|
||||
{
|
||||
linkedGap = e as Gap;
|
||||
if (linkedGap != null)
|
||||
{
|
||||
linkedGap.PassAmbientLight = window != Rectangle.Empty;
|
||||
return linkedGap;
|
||||
}
|
||||
}
|
||||
Rectangle rect = item.Rect;
|
||||
if (isHorizontal)
|
||||
{
|
||||
rect.Y += 5;
|
||||
rect.Height += 10;
|
||||
}
|
||||
else
|
||||
{
|
||||
rect.X -= 5;
|
||||
rect.Width += 10;
|
||||
}
|
||||
|
||||
linkedGap = new Gap(rect, Item.Submarine);
|
||||
linkedGap.Submarine = item.Submarine;
|
||||
linkedGap.PassAmbientLight = window != Rectangle.Empty;
|
||||
linkedGap.Open = openState;
|
||||
item.linkedTo.Add(linkedGap);
|
||||
return linkedGap;
|
||||
}
|
||||
}
|
||||
|
||||
[HasDefaultValue("0.0,0.0,0.0,0.0", false)]
|
||||
public string Window
|
||||
{
|
||||
get { return ToolBox.Vector4ToString(new Vector4(window.X, window.Y, window.Width, window.Height)); }
|
||||
set
|
||||
{
|
||||
Vector4 vector = ToolBox.ParseToVector4(value);
|
||||
if (vector.Z!=0.0f || vector.W !=0.0f)
|
||||
{
|
||||
window = new Rectangle((int)vector.X, (int)vector.Y, (int)vector.Z, (int)vector.W);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Rectangle WindowRect
|
||||
{
|
||||
get { return window; }
|
||||
}
|
||||
|
||||
[Editable, HasDefaultValue(false, true)]
|
||||
public bool IsOpen
|
||||
{
|
||||
get { return isOpen; }
|
||||
set
|
||||
{
|
||||
isOpen = value;
|
||||
OpenState = (isOpen) ? 1.0f : 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
private Rectangle doorRect;
|
||||
|
||||
public float OpenState
|
||||
{
|
||||
get { return openState; }
|
||||
set
|
||||
{
|
||||
|
||||
float prevValue = openState;
|
||||
openState = MathHelper.Clamp(value, 0.0f, 1.0f);
|
||||
if (openState == prevValue) return;
|
||||
|
||||
#if CLIENT
|
||||
UpdateConvexHulls();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public Door(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
//Vector2 position = new Vector2(newRect.X, newRect.Y);
|
||||
|
||||
isHorizontal = ToolBox.GetAttributeBool(element, "horizontal", false);
|
||||
|
||||
// isOpen = false;
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
string texturePath = ToolBox.GetAttributeString(subElement, "texture", "");
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "sprite":
|
||||
doorSprite = new Sprite(subElement, texturePath.Contains("/") ? "" : Path.GetDirectoryName(item.Prefab.ConfigFile));
|
||||
break;
|
||||
case "weldedsprite":
|
||||
weldedSprite = new Sprite(subElement, texturePath.Contains("/") ? "" : Path.GetDirectoryName(item.Prefab.ConfigFile));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
doorRect = new Rectangle(
|
||||
item.Rect.Center.X - (int)(doorSprite.size.X / 2),
|
||||
item.Rect.Y - item.Rect.Height/2 + (int)(doorSprite.size.Y / 2.0f),
|
||||
(int)doorSprite.size.X,
|
||||
(int)doorSprite.size.Y);
|
||||
|
||||
body = new PhysicsBody(
|
||||
ConvertUnits.ToSimUnits(Math.Max(doorRect.Width, 1)),
|
||||
ConvertUnits.ToSimUnits(Math.Max(doorRect.Height, 1)),
|
||||
0.0f,
|
||||
1.5f);
|
||||
|
||||
body.UserData = item;
|
||||
body.CollisionCategories = Physics.CollisionWall;
|
||||
body.BodyType = BodyType.Static;
|
||||
body.SetTransform(
|
||||
ConvertUnits.ToSimUnits(new Vector2(doorRect.Center.X, doorRect.Y - doorRect.Height / 2)),
|
||||
0.0f);
|
||||
body.Friction = 0.5f;
|
||||
|
||||
|
||||
//string spritePath = Path.GetDirectoryName(item.Prefab.ConfigFile) + "\\"+ ToolBox.GetAttributeString(element, "sprite", "");
|
||||
|
||||
IsActive = true;
|
||||
}
|
||||
|
||||
public override void Move(Vector2 amount)
|
||||
{
|
||||
base.Move(amount);
|
||||
|
||||
//LinkedGap.Move(amount);
|
||||
|
||||
body.SetTransform(body.SimPosition + ConvertUnits.ToSimUnits(amount), 0.0f);
|
||||
|
||||
#if CLIENT
|
||||
UpdateConvexHulls();
|
||||
#endif
|
||||
|
||||
//convexHull.Move(amount);
|
||||
//if (convexHull2 != null) convexHull2.Move(amount);
|
||||
}
|
||||
|
||||
|
||||
public override bool Pick(Character picker)
|
||||
{
|
||||
isOpen = !isOpen;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool Select(Character character)
|
||||
{
|
||||
//can only be selected if the item is broken
|
||||
return item.Condition <= 0.0f;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
bool isClosing = false;
|
||||
if (!isStuck)
|
||||
{
|
||||
if (predictedState == null)
|
||||
{
|
||||
OpenState += deltaTime * ((isOpen) ? 2.0f : -2.0f);
|
||||
isClosing = openState > 0.0f && openState < 1.0f && !isOpen;
|
||||
}
|
||||
else
|
||||
{
|
||||
OpenState += deltaTime * (((bool)predictedState) ? 2.0f : -2.0f);
|
||||
isClosing = openState > 0.0f && openState < 1.0f && !(bool)predictedState;
|
||||
|
||||
resetPredictionTimer -= deltaTime;
|
||||
if (resetPredictionTimer <= 0.0f)
|
||||
{
|
||||
predictedState = null;
|
||||
}
|
||||
}
|
||||
|
||||
LinkedGap.Open = openState;
|
||||
}
|
||||
|
||||
if (isClosing)
|
||||
{
|
||||
PushCharactersAway();
|
||||
}
|
||||
else
|
||||
{
|
||||
body.Enabled = openState < 1.0f;
|
||||
}
|
||||
|
||||
//don't use the predicted state here, because it might set
|
||||
//other items to an incorrect state if the prediction is wrong
|
||||
item.SendSignal(0, (isOpen) ? "1" : "0", "state_out", null);
|
||||
}
|
||||
|
||||
public override void UpdateBroken(float deltaTime, Camera cam)
|
||||
{
|
||||
body.Enabled = false;
|
||||
|
||||
linkedGap.Open = 1.0f;
|
||||
}
|
||||
|
||||
public override void OnMapLoaded()
|
||||
{
|
||||
LinkedGap.ConnectedDoor = this;
|
||||
LinkedGap.Open = openState;
|
||||
|
||||
#if CLIENT
|
||||
Vector2[] corners = GetConvexHullCorners(Rectangle.Empty);
|
||||
|
||||
convexHull = new ConvexHull(corners, Color.Black, item);
|
||||
if (window != Rectangle.Empty) convexHull2 = new ConvexHull(corners, Color.Black, item);
|
||||
|
||||
UpdateConvexHulls();
|
||||
#endif
|
||||
}
|
||||
|
||||
protected override void RemoveComponentSpecific()
|
||||
{
|
||||
base.RemoveComponentSpecific();
|
||||
|
||||
if (body != null)
|
||||
{
|
||||
body.Remove();
|
||||
body = null;
|
||||
}
|
||||
|
||||
|
||||
if (linkedGap!=null) linkedGap.Remove();
|
||||
|
||||
doorSprite.Remove();
|
||||
if (weldedSprite != null) weldedSprite.Remove();
|
||||
|
||||
#if CLIENT
|
||||
if (convexHull!=null) convexHull.Remove();
|
||||
if (convexHull2 != null) convexHull2.Remove();
|
||||
#endif
|
||||
}
|
||||
|
||||
private void PushCharactersAway()
|
||||
{
|
||||
//push characters out of the doorway when the door is closing/opening
|
||||
Vector2 simPos = ConvertUnits.ToSimUnits(new Vector2(item.Rect.X, item.Rect.Y));
|
||||
|
||||
Vector2 currSize = isHorizontal ?
|
||||
new Vector2(item.Rect.Width * (1.0f - openState), doorSprite.size.Y) :
|
||||
new Vector2(doorSprite.size.X, item.Rect.Height * (1.0f - openState));
|
||||
|
||||
Vector2 simSize = ConvertUnits.ToSimUnits(currSize);
|
||||
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
int dir = isHorizontal ? Math.Sign(c.SimPosition.Y - item.SimPosition.Y) : Math.Sign(c.SimPosition.X - item.SimPosition.X);
|
||||
|
||||
List<PhysicsBody> bodies = c.AnimController.Limbs.Select(l => l.body).ToList();
|
||||
bodies.Add(c.AnimController.Collider);
|
||||
|
||||
foreach (PhysicsBody body in bodies)
|
||||
{
|
||||
float diff = 0.0f;
|
||||
|
||||
if (isHorizontal)
|
||||
{
|
||||
if (body.SimPosition.X < simPos.X || body.SimPosition.X > simPos.X + simSize.X) continue;
|
||||
|
||||
diff = body.SimPosition.Y - item.SimPosition.Y;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (body.SimPosition.Y > simPos.Y || body.SimPosition.Y < simPos.Y - simSize.Y) continue;
|
||||
|
||||
diff = body.SimPosition.X - item.SimPosition.X;
|
||||
}
|
||||
|
||||
if (Math.Sign(diff) != dir)
|
||||
{
|
||||
#if CLIENT
|
||||
SoundPlayer.PlayDamageSound(DamageSoundType.LimbBlunt, 1.0f, body);
|
||||
#endif
|
||||
|
||||
if (isHorizontal)
|
||||
{
|
||||
body.SetTransform(new Vector2(body.SimPosition.X, item.SimPosition.Y + dir * simSize.Y * 2.0f), body.Rotation);
|
||||
body.ApplyLinearImpulse(new Vector2(isOpen ? 0.0f : 1.0f, dir * 2.0f));
|
||||
}
|
||||
else
|
||||
{
|
||||
body.SetTransform(new Vector2(item.SimPosition.X + dir * simSize.X * 1.2f, body.SimPosition.Y), body.Rotation);
|
||||
body.ApplyLinearImpulse(new Vector2(dir * 0.5f, isOpen ? 0.0f : -1.0f));
|
||||
}
|
||||
}
|
||||
|
||||
if (isHorizontal)
|
||||
{
|
||||
if (Math.Abs(body.SimPosition.Y - item.SimPosition.Y) > simSize.Y * 0.5f) continue;
|
||||
|
||||
body.ApplyLinearImpulse(new Vector2(isOpen ? 0.0f : 1.0f, dir * 0.5f));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Math.Abs(body.SimPosition.X - item.SimPosition.X) > simSize.X * 0.5f) continue;
|
||||
|
||||
body.ApplyLinearImpulse(new Vector2(dir * 0.5f, isOpen ? 0.0f : -1.0f));
|
||||
}
|
||||
|
||||
c.SetStun(0.2f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f)
|
||||
{
|
||||
if (isStuck) return;
|
||||
|
||||
bool wasOpen = predictedState == null ? isOpen : predictedState.Value;
|
||||
|
||||
if (connection.Name == "toggle")
|
||||
{
|
||||
SetState(!wasOpen, false, true);
|
||||
}
|
||||
else if (connection.Name == "set_state")
|
||||
{
|
||||
SetState(signal != "0", false, true);
|
||||
}
|
||||
|
||||
bool newState = predictedState == null ? isOpen : predictedState.Value;
|
||||
if (sender != null && wasOpen != newState)
|
||||
{
|
||||
GameServer.Log(sender.Name + (newState ? " opened " : " closed ") + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetState(bool open, bool isNetworkMessage, bool sendNetworkMessage = false)
|
||||
{
|
||||
if (isStuck || (predictedState == null && isOpen == open) || (predictedState != null && isOpen == predictedState.Value)) return;
|
||||
|
||||
if (GameMain.Client != null && !isNetworkMessage)
|
||||
{
|
||||
//clients can "predict" that the door opens/closes when a signal is received
|
||||
|
||||
//the prediction will be reset after 1 second, setting the door to a state
|
||||
//sent by the server, or reverting it back to its old state if no msg from server was received
|
||||
|
||||
#if CLIENT
|
||||
if (open != predictedState) PlaySound(ActionType.OnUse, item.WorldPosition);
|
||||
#endif
|
||||
|
||||
predictedState = open;
|
||||
resetPredictionTimer = CorrectionDelay;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
#if CLIENT
|
||||
if (!isNetworkMessage || open != predictedState) PlaySound(ActionType.OnUse, item.WorldPosition);
|
||||
#endif
|
||||
|
||||
isOpen = open;
|
||||
}
|
||||
|
||||
//opening a partially stuck door makes it less stuck
|
||||
if (isOpen) stuck = MathHelper.Clamp(stuck - 30.0f, 0.0f, 100.0f);
|
||||
|
||||
if (sendNetworkMessage)
|
||||
{
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void ServerWrite(Lidgren.Network.NetBuffer msg, Barotrauma.Networking.Client c, object[] extraData = null)
|
||||
{
|
||||
msg.Write(isOpen);
|
||||
msg.WriteRangedSingle(stuck, 0.0f, 100.0f, 8);
|
||||
}
|
||||
|
||||
public void ClientRead(ServerNetObject type, Lidgren.Network.NetBuffer msg, float sendingTime)
|
||||
{
|
||||
SetState(msg.ReadBoolean(), true);
|
||||
Stuck = msg.ReadRangedSingle(0.0f, 100.0f, 8);
|
||||
|
||||
predictedState = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,360 @@
|
||||
using System.Xml.Linq;
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using Barotrauma.Networking;
|
||||
using Lidgren.Network;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
class Holdable : Pickable, IServerSerializable
|
||||
{
|
||||
//the position(s) in the item that the Character grabs
|
||||
protected Vector2[] handlePos;
|
||||
|
||||
private InputType prevPickKey;
|
||||
private string prevMsg;
|
||||
|
||||
//the distance from the holding characters elbow to center of the physics body of the item
|
||||
protected Vector2 holdPos;
|
||||
|
||||
protected Vector2 aimPos;
|
||||
|
||||
//protected bool aimable;
|
||||
|
||||
private bool attachable, attached, attachedByDefault;
|
||||
private PhysicsBody body;
|
||||
|
||||
//the angle in which the Character holds the item
|
||||
protected float holdAngle;
|
||||
|
||||
[HasDefaultValue(false, true)]
|
||||
public bool Attached
|
||||
{
|
||||
get { return attached && item.ParentInventory == null; }
|
||||
set { attached = value; }
|
||||
}
|
||||
|
||||
[HasDefaultValue(false, false)]
|
||||
public bool ControlPose
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[HasDefaultValue(false, false)]
|
||||
public bool Attachable
|
||||
{
|
||||
get { return attachable; }
|
||||
set { attachable = value; }
|
||||
}
|
||||
|
||||
[HasDefaultValue(false, false)]
|
||||
public bool AttachedByDefault
|
||||
{
|
||||
get { return attachedByDefault; }
|
||||
set { attachedByDefault = value; }
|
||||
}
|
||||
|
||||
[HasDefaultValue("0.0,0.0", false)]
|
||||
public string HoldPos
|
||||
{
|
||||
get { return ToolBox.Vector2ToString(ConvertUnits.ToDisplayUnits(holdPos)); }
|
||||
set { holdPos = ConvertUnits.ToSimUnits(ToolBox.ParseToVector2(value)); }
|
||||
}
|
||||
|
||||
[HasDefaultValue("0.0,0.0", false)]
|
||||
public string AimPos
|
||||
{
|
||||
get { return ToolBox.Vector2ToString(ConvertUnits.ToDisplayUnits(aimPos)); }
|
||||
set { aimPos = ConvertUnits.ToSimUnits(ToolBox.ParseToVector2(value)); }
|
||||
}
|
||||
|
||||
[HasDefaultValue(0.0f, false)]
|
||||
public float HoldAngle
|
||||
{
|
||||
get { return MathHelper.ToDegrees(holdAngle); }
|
||||
set { holdAngle = MathHelper.ToRadians(value); }
|
||||
}
|
||||
|
||||
public Holdable(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
body = item.body;
|
||||
|
||||
handlePos = new Vector2[2];
|
||||
|
||||
for (int i = 1; i < 3; i++)
|
||||
{
|
||||
handlePos[i - 1] = ToolBox.GetAttributeVector2(element, "handle" + i, Vector2.Zero);
|
||||
|
||||
handlePos[i - 1] = ConvertUnits.ToSimUnits(handlePos[i - 1]);
|
||||
}
|
||||
|
||||
canBePicked = true;
|
||||
|
||||
if (attachable)
|
||||
{
|
||||
prevMsg = Msg;
|
||||
prevPickKey = PickKey;
|
||||
|
||||
DeattachFromWall();
|
||||
}
|
||||
|
||||
if ((Screen.Selected == GameMain.EditMapScreen)) Use(1.0f);
|
||||
}
|
||||
|
||||
public override void Drop(Character dropper)
|
||||
{
|
||||
DropConnectedWires(dropper);
|
||||
|
||||
if (attachable)
|
||||
{
|
||||
DeattachFromWall();
|
||||
|
||||
if (body != null)
|
||||
{
|
||||
item.body = body;
|
||||
}
|
||||
}
|
||||
|
||||
if (item.body != null) item.body.Enabled = true;
|
||||
IsActive = false;
|
||||
|
||||
if (picker == null)
|
||||
{
|
||||
if (dropper == null) return;
|
||||
picker = dropper;
|
||||
}
|
||||
if (picker.Inventory == null) return;
|
||||
|
||||
item.Submarine = picker.Submarine;
|
||||
if (item.body != null)
|
||||
{
|
||||
item.body.ResetDynamics();
|
||||
item.SetTransform(picker.SimPosition, 0.0f);
|
||||
}
|
||||
|
||||
picker.DeselectItem(item);
|
||||
picker.Inventory.RemoveItem(item);
|
||||
picker = null;
|
||||
}
|
||||
|
||||
public override void Equip(Character character)
|
||||
{
|
||||
picker = character;
|
||||
|
||||
if (character != null) item.Submarine = character.Submarine;
|
||||
|
||||
if (item.body == null)
|
||||
{
|
||||
if (body != null)
|
||||
{
|
||||
item.body = body;
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!item.body.Enabled)
|
||||
{
|
||||
Limb rightHand = picker.AnimController.GetLimb(LimbType.RightHand);
|
||||
item.SetTransform(rightHand.SimPosition, 0.0f);
|
||||
}
|
||||
|
||||
bool alreadySelected = character.HasSelectedItem(item);
|
||||
if (picker.TrySelectItem(item))
|
||||
{
|
||||
item.body.Enabled = true;
|
||||
IsActive = true;
|
||||
|
||||
if (!alreadySelected) Networking.GameServer.Log(character.Name + " equipped " + item.Name, Networking.ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Unequip(Character character)
|
||||
{
|
||||
if (picker == null) return;
|
||||
|
||||
picker.DeselectItem(item);
|
||||
|
||||
Networking.GameServer.Log(character.Name + " unequipped " + item.Name, Networking.ServerLog.MessageType.ItemInteraction);
|
||||
|
||||
item.body.Enabled = false;
|
||||
IsActive = false;
|
||||
}
|
||||
|
||||
public override bool Pick(Character picker)
|
||||
{
|
||||
if (!attachable)
|
||||
{
|
||||
return base.Pick(picker);
|
||||
}
|
||||
|
||||
if (Attached)
|
||||
{
|
||||
return base.Pick(picker);
|
||||
}
|
||||
else
|
||||
{
|
||||
//not attached -> pick the item instantly, ignoring picking time
|
||||
return OnPicked(picker);
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool OnPicked(Character picker)
|
||||
{
|
||||
if (base.OnPicked(picker))
|
||||
{
|
||||
DeattachFromWall();
|
||||
|
||||
if (GameMain.Server != null && attachable)
|
||||
{
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void AttachToWall()
|
||||
{
|
||||
if (!attachable) return;
|
||||
|
||||
var containedItems = item.ContainedItems;
|
||||
if (containedItems != null)
|
||||
{
|
||||
foreach (Item contained in containedItems)
|
||||
{
|
||||
if (contained.body == null) continue;
|
||||
contained.SetTransform(item.SimPosition, contained.body.Rotation);
|
||||
}
|
||||
}
|
||||
|
||||
item.body.Enabled = false;
|
||||
item.body = null;
|
||||
|
||||
Msg = prevMsg;
|
||||
PickKey = prevPickKey;
|
||||
|
||||
attached = true;
|
||||
}
|
||||
|
||||
private void DeattachFromWall()
|
||||
{
|
||||
if (!attachable) return;
|
||||
|
||||
attached = false;
|
||||
|
||||
//make the item pickable with the default pick key and with no specific tools/items when it's deattached
|
||||
requiredItems.Clear();
|
||||
Msg = "";
|
||||
PickKey = InputType.Select;
|
||||
}
|
||||
|
||||
public override bool Use(float deltaTime, Character character = null)
|
||||
{
|
||||
if (!attachable || item.body == null) return true;
|
||||
if (character != null)
|
||||
{
|
||||
if (!character.IsKeyDown(InputType.Aim)) return false;
|
||||
if (character != null && GameMain.Server != null)
|
||||
{
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
item.Drop();
|
||||
}
|
||||
|
||||
AttachToWall();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void UpdateBroken(float deltaTime, Camera cam)
|
||||
{
|
||||
Update(deltaTime, cam);
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
if (item.body == null || !item.body.Enabled) return;
|
||||
if (picker == null || !picker.HasSelectedItem(item))
|
||||
{
|
||||
IsActive = false;
|
||||
return;
|
||||
}
|
||||
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime, picker);
|
||||
|
||||
if (item.body.Dir != picker.AnimController.Dir) Flip(item);
|
||||
|
||||
item.Submarine = picker.Submarine;
|
||||
|
||||
picker.AnimController.HoldItem(deltaTime, item, handlePos, holdPos, aimPos, picker.IsKeyDown(InputType.Aim), holdAngle);
|
||||
}
|
||||
|
||||
protected void Flip(Item item)
|
||||
{
|
||||
handlePos[0].X = -handlePos[0].X;
|
||||
handlePos[1].X = -handlePos[1].X;
|
||||
item.body.Dir = -item.body.Dir;
|
||||
}
|
||||
|
||||
public override void OnMapLoaded()
|
||||
{
|
||||
//prevRequiredItems = new List<RelatedItem>(requiredItems);
|
||||
|
||||
if (!attachable) return;
|
||||
|
||||
if (Attached)
|
||||
{
|
||||
Use(1.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (item.ParentInventory != null)
|
||||
{
|
||||
if (body != null)
|
||||
{
|
||||
item.body = body;
|
||||
body.Enabled = false;
|
||||
}
|
||||
}
|
||||
attached = false;
|
||||
|
||||
DeattachFromWall();
|
||||
}
|
||||
}
|
||||
|
||||
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
|
||||
{
|
||||
msg.Write(Attached);
|
||||
}
|
||||
|
||||
public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime)
|
||||
{
|
||||
bool isAttached = msg.ReadBoolean();
|
||||
if (isAttached)
|
||||
{
|
||||
item.Drop();
|
||||
AttachToWall();
|
||||
}
|
||||
else
|
||||
{
|
||||
DropConnectedWires(null);
|
||||
|
||||
if (body != null)
|
||||
{
|
||||
item.body = body;
|
||||
item.body.Enabled = true;
|
||||
}
|
||||
IsActive = false;
|
||||
|
||||
DeattachFromWall();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using FarseerPhysics.Dynamics.Contacts;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
class MeleeWeapon : Holdable
|
||||
{
|
||||
private float hitPos;
|
||||
|
||||
private bool hitting;
|
||||
|
||||
private Attack attack;
|
||||
|
||||
private float range;
|
||||
|
||||
private Character user;
|
||||
|
||||
private float reload;
|
||||
|
||||
private float reloadTimer;
|
||||
|
||||
[HasDefaultValue(0.0f, false)]
|
||||
public float Range
|
||||
{
|
||||
get { return ConvertUnits.ToDisplayUnits(range); }
|
||||
set { range = ConvertUnits.ToSimUnits(value); }
|
||||
}
|
||||
|
||||
[HasDefaultValue(0.5f, false)]
|
||||
public float Reload
|
||||
{
|
||||
get { return reload; }
|
||||
set { reload = Math.Max(0.0f, value); }
|
||||
}
|
||||
|
||||
public MeleeWeapon(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
//throwForce = ToolBox.GetAttributeFloat(element, "throwforce", 1.0f);
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
if (subElement.Name.ToString().ToLowerInvariant() != "attack") continue;
|
||||
attack = new Attack(subElement);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Use(float deltaTime, Character character = null)
|
||||
{
|
||||
if (character == null || reloadTimer>0.0f) return false;
|
||||
if (!character.IsKeyDown(InputType.Aim) || hitting) return false;
|
||||
|
||||
//don't allow hitting if the character is already hitting with another weapon
|
||||
for (int i = 0; i < 2; i++ )
|
||||
{
|
||||
if (character.SelectedItems[i] == null || character.SelectedItems[i] == Item) continue;
|
||||
|
||||
var otherWeapon = character.SelectedItems[i].GetComponent<MeleeWeapon>();
|
||||
if (otherWeapon == null) continue;
|
||||
|
||||
if (otherWeapon.hitting) return false;
|
||||
}
|
||||
|
||||
SetUser(character);
|
||||
|
||||
if (hitPos < MathHelper.Pi * 0.69f) return false;
|
||||
|
||||
reloadTimer = reload;
|
||||
|
||||
item.body.FarseerBody.CollisionCategories = Physics.CollisionProjectile;
|
||||
item.body.FarseerBody.CollidesWith = Physics.CollisionCharacter | Physics.CollisionWall;
|
||||
item.body.FarseerBody.OnCollision += OnCollision;
|
||||
|
||||
foreach (Limb l in character.AnimController.Limbs)
|
||||
{
|
||||
//item.body.FarseerBody.IgnoreCollisionWith(l.body.FarseerBody);
|
||||
|
||||
if (character.AnimController.InWater) continue;
|
||||
if (l.type == LimbType.LeftFoot || l.type == LimbType.LeftThigh || l.type == LimbType.LeftLeg) continue;
|
||||
|
||||
if (l.type == LimbType.Head || l.type == LimbType.Torso)
|
||||
{
|
||||
l.body.ApplyLinearImpulse(new Vector2(character.AnimController.Dir * 7.0f, -4.0f));
|
||||
}
|
||||
else
|
||||
{
|
||||
l.body.ApplyLinearImpulse(new Vector2(character.AnimController.Dir * 5.0f, -2.0f));
|
||||
}
|
||||
}
|
||||
|
||||
hitting = true;
|
||||
|
||||
IsActive = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void Drop(Character dropper)
|
||||
{
|
||||
base.Drop(dropper);
|
||||
|
||||
hitting = false;
|
||||
hitPos = 0.0f;
|
||||
}
|
||||
|
||||
public override void UpdateBroken(float deltaTime, Camera cam)
|
||||
{
|
||||
Update(deltaTime, cam);
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
if (!item.body.Enabled) return;
|
||||
if (!picker.HasSelectedItem(item)) IsActive = false;
|
||||
|
||||
reloadTimer -= deltaTime;
|
||||
|
||||
if (!picker.IsKeyDown(InputType.Aim) && !hitting) hitPos = 0.0f;
|
||||
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime, picker);
|
||||
|
||||
if (item.body.Dir != picker.AnimController.Dir) Flip(item);
|
||||
|
||||
AnimController ac = picker.AnimController;
|
||||
|
||||
if (!hitting)
|
||||
{
|
||||
if (picker.IsKeyDown(InputType.Aim))
|
||||
{
|
||||
hitPos = Math.Min(hitPos+deltaTime*5.0f, MathHelper.Pi*0.7f);
|
||||
|
||||
ac.HoldItem(deltaTime, item, handlePos, new Vector2(0.6f, -0.1f), new Vector2(-0.3f, 0.2f), false, hitPos);
|
||||
}
|
||||
else
|
||||
{
|
||||
ac.HoldItem(deltaTime, item, handlePos, new Vector2(hitPos, 0.0f), aimPos, false, holdAngle);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//Vector2 diff = Vector2.Normalize(picker.CursorPosition - ac.RefLimb.Position);
|
||||
//diff.X = diff.X * ac.Dir;
|
||||
|
||||
hitPos -= deltaTime*15.0f;
|
||||
|
||||
//angl = -hitPos * 2.0f;
|
||||
// System.Diagnostics.Debug.WriteLine("<1.0f "+hitPos);
|
||||
|
||||
|
||||
|
||||
ac.HoldItem(deltaTime, item, handlePos, new Vector2(0.6f, -0.1f), new Vector2(-0.3f, 0.2f), false, hitPos);
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// System.Diagnostics.Debug.WriteLine(">1.0f " + hitPos);
|
||||
// ac.HoldItem(deltaTime, item, handlePos, new Vector2(0.5f, 0.2f), new Vector2(1.0f, 0.2f), false, 0.0f);
|
||||
//}
|
||||
|
||||
if (hitPos < -MathHelper.PiOver4*1.2f)
|
||||
{
|
||||
RestoreCollision();
|
||||
hitting = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void SetUser(Character character)
|
||||
{
|
||||
if (user == character) return;
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
foreach (Limb limb in user.AnimController.Limbs)
|
||||
{
|
||||
try
|
||||
{
|
||||
item.body.FarseerBody.RestoreCollisionWith(limb.body.FarseerBody);
|
||||
}
|
||||
|
||||
catch
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Limb limb in character.AnimController.Limbs)
|
||||
{
|
||||
item.body.FarseerBody.IgnoreCollisionWith(limb.body.FarseerBody);
|
||||
}
|
||||
|
||||
user = character;
|
||||
}
|
||||
|
||||
private void RestoreCollision()
|
||||
{
|
||||
item.body.FarseerBody.OnCollision -= OnCollision;
|
||||
|
||||
item.body.CollisionCategories = Physics.CollisionItem;
|
||||
item.body.CollidesWith = Physics.CollisionWall;
|
||||
|
||||
//foreach (Limb l in picker.AnimController.Limbs)
|
||||
//{
|
||||
// item.body.FarseerBody.RestoreCollisionWith(l.body.FarseerBody);
|
||||
//}
|
||||
}
|
||||
|
||||
|
||||
private bool OnCollision(Fixture f1, Fixture f2, Contact contact)
|
||||
{
|
||||
Character target = null;
|
||||
|
||||
Limb limb = f2.Body.UserData as Limb;
|
||||
if (limb != null)
|
||||
{
|
||||
if (limb.character == picker) return false;
|
||||
target = limb.character;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (target == null)
|
||||
{
|
||||
target = f2.Body.UserData as Character;
|
||||
}
|
||||
|
||||
if (target == null) return false;
|
||||
|
||||
if (attack != null) attack.DoDamage(user, target, item.WorldPosition, 1.0f);
|
||||
|
||||
RestoreCollision();
|
||||
hitting = false;
|
||||
|
||||
if (GameMain.Client != null) return true;
|
||||
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
GameMain.Server.CreateEntityEvent(item, new object[] { Networking.NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnUse, target.ID });
|
||||
|
||||
string logStr = picker?.Name + " used " + item.Name;
|
||||
if (item.ContainedItems != null && item.ContainedItems.Length > 0)
|
||||
{
|
||||
logStr += "(" + string.Join(", ", item.ContainedItems.Select(i => i?.Name)) + ")";
|
||||
}
|
||||
logStr += " on " + target + ".";
|
||||
Networking.GameServer.Log(logStr, Networking.ServerLog.MessageType.Attack);
|
||||
}
|
||||
|
||||
ApplyStatusEffects(ActionType.OnUse, 1.0f, limb.character);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
class Pickable : ItemComponent
|
||||
{
|
||||
protected Character picker;
|
||||
|
||||
protected List<InvSlotType> allowedSlots;
|
||||
|
||||
private float pickTimer;
|
||||
|
||||
|
||||
public List<InvSlotType> AllowedSlots
|
||||
{
|
||||
get { return allowedSlots; }
|
||||
}
|
||||
|
||||
public Character Picker
|
||||
{
|
||||
get { return picker; }
|
||||
}
|
||||
|
||||
public Pickable(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
allowedSlots = new List<InvSlotType>();
|
||||
|
||||
string slotString = ToolBox.GetAttributeString(element, "slots", "Any");
|
||||
string[] slotCombinations = slotString.Split(',');
|
||||
foreach (string slotCombination in slotCombinations)
|
||||
{
|
||||
string[] slots = slotCombination.Split('+');
|
||||
InvSlotType allowedSlot = InvSlotType.None;
|
||||
foreach (string slot in slots)
|
||||
{
|
||||
if (slot.ToLowerInvariant() == "bothhands")
|
||||
{
|
||||
allowedSlot = InvSlotType.LeftHand | InvSlotType.RightHand;
|
||||
}
|
||||
else
|
||||
{
|
||||
allowedSlot = allowedSlot | (InvSlotType)Enum.Parse(typeof(InvSlotType), slot.Trim());
|
||||
}
|
||||
}
|
||||
allowedSlots.Add(allowedSlot);
|
||||
}
|
||||
|
||||
canBePicked = true;
|
||||
}
|
||||
|
||||
public override bool Pick(Character picker)
|
||||
{
|
||||
//return if someone is already trying to pick the item
|
||||
if (pickTimer>0.0f) return false;
|
||||
if (picker == null || picker.Inventory == null) return false;
|
||||
|
||||
if (PickingTime>0.0f)
|
||||
{
|
||||
CoroutineManager.StartCoroutine(WaitForPick(picker, PickingTime));
|
||||
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return OnPicked(picker);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual bool OnPicked(Character picker)
|
||||
{
|
||||
if (picker.Inventory.TryPutItem(item, allowedSlots))
|
||||
{
|
||||
if (!picker.HasSelectedItem(item) && item.body != null) item.body.Enabled = false;
|
||||
this.picker = picker;
|
||||
|
||||
for (int i = item.linkedTo.Count - 1; i >= 0; i--)
|
||||
{
|
||||
item.linkedTo[i].RemoveLinked(item);
|
||||
}
|
||||
item.linkedTo.Clear();
|
||||
|
||||
DropConnectedWires(picker);
|
||||
|
||||
ApplyStatusEffects(ActionType.OnPicked, 1.0f, picker);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private IEnumerable<object> WaitForPick(Character picker, float requiredTime)
|
||||
{
|
||||
var leftHand = picker.AnimController.GetLimb(LimbType.LeftHand);
|
||||
var rightHand = picker.AnimController.GetLimb(LimbType.RightHand);
|
||||
|
||||
pickTimer = 0.0f;
|
||||
while (pickTimer < requiredTime && Screen.Selected != GameMain.EditMapScreen)
|
||||
{
|
||||
if (picker.IsKeyDown(InputType.Aim) ||
|
||||
!item.IsInPickRange(picker.WorldPosition) ||
|
||||
picker.Stun > 0.0f || picker.IsDead)
|
||||
{
|
||||
StopPicking(picker);
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
picker.UpdateHUDProgressBar(
|
||||
this,
|
||||
item.WorldPosition,
|
||||
pickTimer / requiredTime,
|
||||
Color.Red, Color.Green);
|
||||
#endif
|
||||
|
||||
picker.AnimController.Anim = AnimController.Animation.UsingConstruction;
|
||||
|
||||
picker.AnimController.TargetMovement = Vector2.Zero;
|
||||
|
||||
leftHand.Disabled = true;
|
||||
leftHand.pullJoint.Enabled = true;
|
||||
leftHand.pullJoint.WorldAnchorB = item.SimPosition + Vector2.UnitY * ((pickTimer / 10.0f) % 0.1f);
|
||||
|
||||
rightHand.Disabled = true;
|
||||
rightHand.pullJoint.Enabled = true;
|
||||
rightHand.pullJoint.WorldAnchorB = item.SimPosition + Vector2.UnitY * ((pickTimer / 10.0f) % 0.1f);
|
||||
|
||||
pickTimer += CoroutineManager.DeltaTime;
|
||||
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
StopPicking(picker);
|
||||
|
||||
if (!picker.IsRemotePlayer) OnPicked(picker);
|
||||
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
|
||||
private void StopPicking(Character picker)
|
||||
{
|
||||
picker.AnimController.Anim = AnimController.Animation.None;
|
||||
pickTimer = 0.0f;
|
||||
}
|
||||
|
||||
protected void DropConnectedWires(Character character)
|
||||
{
|
||||
Vector2 pos = character == null ? item.SimPosition : character.SimPosition;
|
||||
|
||||
var connectionPanel = item.GetComponent<ConnectionPanel>();
|
||||
if (connectionPanel == null) return;
|
||||
|
||||
foreach (Connection c in connectionPanel.Connections)
|
||||
{
|
||||
foreach (Wire w in c.Wires)
|
||||
{
|
||||
if (w == null) continue;
|
||||
|
||||
w.Item.Drop(character);
|
||||
w.Item.SetTransform(pos, 0.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Drop(Character dropper)
|
||||
{
|
||||
if (picker == null)
|
||||
{
|
||||
picker = dropper;
|
||||
}
|
||||
|
||||
Vector2 bodyDropPos = Vector2.Zero;
|
||||
|
||||
if (picker == null || picker.Inventory == null)
|
||||
{
|
||||
if (item.ParentInventory != null && item.ParentInventory.Owner != null)
|
||||
{
|
||||
bodyDropPos = item.ParentInventory.Owner.SimPosition;
|
||||
|
||||
if (item.body != null) item.body.ResetDynamics();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DropConnectedWires(picker);
|
||||
|
||||
item.Submarine = picker.Submarine;
|
||||
bodyDropPos = picker.SimPosition;
|
||||
|
||||
picker.Inventory.RemoveItem(item);
|
||||
picker = null;
|
||||
}
|
||||
|
||||
if (item.body != null && !item.body.Enabled)
|
||||
{
|
||||
item.body.ResetDynamics();
|
||||
item.SetTransform(bodyDropPos, 0.0f);
|
||||
item.body.Enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
#if CLIENT
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Barotrauma.Particles;
|
||||
#endif
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
class Propulsion : ItemComponent
|
||||
{
|
||||
enum UsableIn
|
||||
{
|
||||
Air,Water,Both
|
||||
};
|
||||
|
||||
private float force;
|
||||
|
||||
private string particles;
|
||||
|
||||
private float useState;
|
||||
|
||||
private UsableIn usableIn;
|
||||
|
||||
[HasDefaultValue(0.0f, false)]
|
||||
public float Force
|
||||
{
|
||||
get { return force; }
|
||||
set { force = value; }
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
[HasDefaultValue("", false)]
|
||||
public string Particles
|
||||
{
|
||||
get { return particles; }
|
||||
set { particles = value; }
|
||||
}
|
||||
#endif
|
||||
|
||||
public Propulsion(Item item, XElement element)
|
||||
: base(item,element)
|
||||
{
|
||||
switch (ToolBox.GetAttributeString(element, "usablein", "both").ToLowerInvariant())
|
||||
{
|
||||
case "air":
|
||||
usableIn = UsableIn.Air;
|
||||
break;
|
||||
case "water":
|
||||
usableIn = UsableIn.Water;
|
||||
break;
|
||||
case "both":
|
||||
default:
|
||||
usableIn = UsableIn.Both;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Use(float deltaTime, Character character = null)
|
||||
{
|
||||
if (character == null) return false;
|
||||
if (!character.IsKeyDown(InputType.Aim) || character.Stun>0.0f) return false;
|
||||
|
||||
IsActive = true;
|
||||
useState = 0.1f;
|
||||
|
||||
|
||||
if (character.AnimController.InWater)
|
||||
{
|
||||
if (usableIn == UsableIn.Air) return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (usableIn == UsableIn.Water) return true;
|
||||
}
|
||||
|
||||
Vector2 dir = Vector2.Normalize(character.CursorPosition - character.Position);
|
||||
|
||||
Vector2 propulsion = dir * force;
|
||||
|
||||
if (character.AnimController.InWater) character.AnimController.TargetMovement = dir;
|
||||
|
||||
foreach (Limb limb in character.AnimController.Limbs)
|
||||
{
|
||||
if (limb.WearingItems.Find(w => w.WearableComponent.Item == this.item)==null) continue;
|
||||
|
||||
limb.body.ApplyForce(propulsion);
|
||||
}
|
||||
|
||||
character.AnimController.Collider.ApplyForce(propulsion);
|
||||
|
||||
if (character.SelectedItems[0] == item) character.AnimController.GetLimb(LimbType.RightHand).body.ApplyForce(propulsion);
|
||||
if (character.SelectedItems[1] == item) character.AnimController.GetLimb(LimbType.LeftHand).body.ApplyForce(propulsion);
|
||||
|
||||
#if CLIENT
|
||||
if (!string.IsNullOrWhiteSpace(particles))
|
||||
{
|
||||
GameMain.ParticleManager.CreateParticle(particles, item.WorldPosition,
|
||||
item.body.Rotation + ((item.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi), 0.0f, item.CurrentHull);
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
useState -= deltaTime;
|
||||
|
||||
if (useState <= 0.0f) IsActive = false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
class RangedWeapon : ItemComponent
|
||||
{
|
||||
private float reload, reloadTimer;
|
||||
|
||||
private Vector2 barrelPos;
|
||||
|
||||
[HasDefaultValue("0.0,0.0", false)]
|
||||
public string BarrelPos
|
||||
{
|
||||
get { return ToolBox.Vector2ToString(ConvertUnits.ToDisplayUnits(barrelPos)); }
|
||||
set { barrelPos = ConvertUnits.ToSimUnits(ToolBox.ParseToVector2(value)); }
|
||||
}
|
||||
|
||||
[HasDefaultValue(1.0f, false)]
|
||||
public float Reload
|
||||
{
|
||||
get { return reload; }
|
||||
set { reload = Math.Max(value, 0.0f); }
|
||||
}
|
||||
|
||||
public Vector2 TransformedBarrelPos
|
||||
{
|
||||
get
|
||||
{
|
||||
Matrix bodyTransform = Matrix.CreateRotationZ(item.body.Rotation);
|
||||
Vector2 flippedPos = barrelPos;
|
||||
if (item.body.Dir < 0.0f) flippedPos.X = -flippedPos.X;
|
||||
return (Vector2.Transform(flippedPos, bodyTransform) + item.body.SimPosition);
|
||||
}
|
||||
}
|
||||
|
||||
public RangedWeapon(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
//barrelPos = ToolBox.GetAttributeVector2(element, "barrelpos", Vector2.Zero);
|
||||
//barrelPos = ConvertUnits.ToSimUnits(barrelPos);
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
reloadTimer -= deltaTime;
|
||||
|
||||
if (reloadTimer < 0.0f)
|
||||
{
|
||||
reloadTimer = 0.0f;
|
||||
IsActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Use(float deltaTime, Character character = null)
|
||||
{
|
||||
if (character == null) return false;
|
||||
if (!character.IsKeyDown(InputType.Aim) || reloadTimer > 0.0f) return false;
|
||||
IsActive = true;
|
||||
reloadTimer = reload;
|
||||
|
||||
List<Body> limbBodies = new List<Body>();
|
||||
foreach (Limb l in character.AnimController.Limbs)
|
||||
{
|
||||
limbBodies.Add(l.body.FarseerBody);
|
||||
}
|
||||
|
||||
float degreeOfFailure = (100.0f - DegreeOfSuccess(character))/100.0f;
|
||||
|
||||
degreeOfFailure *= degreeOfFailure;
|
||||
|
||||
if (degreeOfFailure > Rand.Range(0.0f, 1.0f))
|
||||
{
|
||||
ApplyStatusEffects(ActionType.OnFailure, 1.0f, character);
|
||||
}
|
||||
|
||||
Item[] containedItems = item.ContainedItems;
|
||||
if (containedItems != null)
|
||||
{
|
||||
foreach (Item projectile in containedItems)
|
||||
{
|
||||
if (projectile == null) continue;
|
||||
//find the projectile-itemcomponent of the projectile,
|
||||
//and add the limbs of the shooter to the list of bodies to be ignored
|
||||
//so that the player can't shoot himself
|
||||
Projectile projectileComponent= projectile.GetComponent<Projectile>();
|
||||
if (projectileComponent == null) continue;
|
||||
|
||||
projectile.body.ResetDynamics();
|
||||
projectile.SetTransform(TransformedBarrelPos,
|
||||
((item.body.Dir == 1.0f) ? item.body.Rotation : item.body.Rotation - MathHelper.Pi)
|
||||
+ Rand.Range(-degreeOfFailure, degreeOfFailure));
|
||||
|
||||
projectile.Use(deltaTime);
|
||||
projectileComponent.User = character;
|
||||
|
||||
projectile.body.ApplyTorque(projectile.body.Mass * degreeOfFailure * Rand.Range(-10.0f, 10.0f));
|
||||
|
||||
//recoil
|
||||
item.body.ApplyLinearImpulse(
|
||||
new Vector2((float)Math.Cos(projectile.body.Rotation), (float)Math.Sin(projectile.body.Rotation)) * item.body.Mass * -50.0f);
|
||||
|
||||
projectileComponent.IgnoredBodies = limbBodies;
|
||||
|
||||
item.RemoveContained(projectile);
|
||||
|
||||
Rope rope = item.GetComponent<Rope>();
|
||||
if (rope != null) rope.Attach(projectile);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
class RepairTool : ItemComponent
|
||||
{
|
||||
private readonly List<string> fixableEntities;
|
||||
|
||||
private float range;
|
||||
|
||||
private Vector2 pickedPosition;
|
||||
|
||||
private Vector2 barrelPos;
|
||||
|
||||
private string particles;
|
||||
|
||||
private float activeTimer;
|
||||
|
||||
[HasDefaultValue(0.0f, false)]
|
||||
public float Range
|
||||
{
|
||||
get { return range; }
|
||||
set { range = value; }
|
||||
}
|
||||
|
||||
[HasDefaultValue(0.0f, false)]
|
||||
public float StructureFixAmount
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
[HasDefaultValue(0.0f, false)]
|
||||
public float LimbFixAmount
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
[HasDefaultValue(0.0f, false)]
|
||||
public float ExtinquishAmount
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
[HasDefaultValue("", false)]
|
||||
public string Particles
|
||||
{
|
||||
get { return particles; }
|
||||
set { particles = value; }
|
||||
}
|
||||
|
||||
[HasDefaultValue(0.0f, false)]
|
||||
public float ParticleSpeed
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
[HasDefaultValue("0.0,0.0", false)]
|
||||
public string BarrelPos
|
||||
{
|
||||
get { return ToolBox.Vector2ToString(barrelPos); }
|
||||
set { barrelPos = ToolBox.ParseToVector2(value); }
|
||||
}
|
||||
|
||||
public Vector2 TransformedBarrelPos
|
||||
{
|
||||
get
|
||||
{
|
||||
Matrix bodyTransform = Matrix.CreateRotationZ(item.body.Rotation);
|
||||
Vector2 flippedPos = barrelPos;
|
||||
if (item.body.Dir < 0.0f) flippedPos.X = -flippedPos.X;
|
||||
return (Vector2.Transform(flippedPos, bodyTransform));
|
||||
}
|
||||
}
|
||||
|
||||
public RepairTool(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
this.item = item;
|
||||
|
||||
fixableEntities = new List<string>();
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "fixable":
|
||||
fixableEntities.Add(subElement.Attribute("name").Value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
activeTimer -= deltaTime;
|
||||
if (activeTimer <= 0.0f) IsActive = false;
|
||||
}
|
||||
|
||||
public override bool Use(float deltaTime, Character character = null)
|
||||
{
|
||||
if (character == null) return false;
|
||||
if (!character.IsKeyDown(InputType.Aim)) return false;
|
||||
|
||||
//if (DoesUseFail(Character)) return false;
|
||||
|
||||
//targetPosition = targetPosition.X, -targetPosition.Y);
|
||||
|
||||
float degreeOfSuccess = DegreeOfSuccess(character)/100.0f;
|
||||
|
||||
if (Rand.Range(0.0f, 0.5f) > degreeOfSuccess)
|
||||
{
|
||||
ApplyStatusEffects(ActionType.OnFailure, deltaTime, character);
|
||||
return false;
|
||||
}
|
||||
|
||||
Vector2 targetPosition = item.WorldPosition;
|
||||
targetPosition += new Vector2(
|
||||
(float)Math.Cos(item.body.Rotation),
|
||||
(float)Math.Sin(item.body.Rotation)) * range * item.body.Dir;
|
||||
|
||||
List<Body> ignoredBodies = new List<Body>();
|
||||
foreach (Limb limb in character.AnimController.Limbs)
|
||||
{
|
||||
if (Rand.Range(0.0f, 0.5f) > degreeOfSuccess) continue;
|
||||
ignoredBodies.Add(limb.body.FarseerBody);
|
||||
}
|
||||
|
||||
IsActive = true;
|
||||
activeTimer = 0.1f;
|
||||
|
||||
Vector2 rayStart = ConvertUnits.ToSimUnits(item.WorldPosition);
|
||||
Vector2 rayEnd = ConvertUnits.ToSimUnits(targetPosition);
|
||||
|
||||
if (character.Submarine == null)
|
||||
{
|
||||
foreach (Submarine sub in Submarine.Loaded)
|
||||
{
|
||||
Repair(rayStart - sub.SimPosition, rayEnd - sub.SimPosition, deltaTime, character, degreeOfSuccess, ignoredBodies);
|
||||
}
|
||||
Repair(rayStart, rayEnd, deltaTime, character, degreeOfSuccess, ignoredBodies);
|
||||
}
|
||||
else
|
||||
{
|
||||
Repair(rayStart - character.Submarine.SimPosition, rayEnd - character.Submarine.SimPosition, deltaTime, character, degreeOfSuccess, ignoredBodies);
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
GameMain.ParticleManager.CreateParticle(particles, item.WorldPosition + TransformedBarrelPos,
|
||||
-item.body.Rotation + ((item.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi), ParticleSpeed);
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void Repair(Vector2 rayStart, Vector2 rayEnd, float deltaTime, Character user, float degreeOfSuccess, List<Body> ignoredBodies)
|
||||
{
|
||||
if (ExtinquishAmount > 0.0f && item.CurrentHull != null)
|
||||
{
|
||||
Vector2 displayPos = ConvertUnits.ToDisplayUnits(rayStart + (rayEnd - rayStart) * Submarine.LastPickedFraction * 0.9f);
|
||||
|
||||
displayPos += item.CurrentHull.Submarine.Position;
|
||||
|
||||
Hull hull = Hull.FindHull(displayPos, item.CurrentHull);
|
||||
if (hull != null)
|
||||
{
|
||||
hull.Extinguish(deltaTime, ExtinquishAmount, displayPos);
|
||||
if (hull != item.CurrentHull)
|
||||
{
|
||||
item.CurrentHull.Extinguish(deltaTime, ExtinquishAmount, displayPos);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Body targetBody = Submarine.PickBody(rayStart, rayEnd, ignoredBodies);
|
||||
|
||||
if (targetBody == null || targetBody.UserData == null) return;
|
||||
|
||||
pickedPosition = Submarine.LastPickedPosition;
|
||||
|
||||
Structure targetStructure;
|
||||
Limb targetLimb;
|
||||
Item targetItem;
|
||||
if ((targetStructure = (targetBody.UserData as Structure)) != null)
|
||||
{
|
||||
if (!fixableEntities.Contains("structure") && !fixableEntities.Contains(targetStructure.Name)) return;
|
||||
if (targetStructure.IsPlatform) return;
|
||||
|
||||
int sectionIndex = targetStructure.FindSectionIndex(ConvertUnits.ToDisplayUnits(pickedPosition));
|
||||
if (sectionIndex < 0) return;
|
||||
|
||||
#if CLIENT
|
||||
Vector2 progressBarPos = targetStructure.SectionPosition(sectionIndex);
|
||||
if (targetStructure.Submarine != null)
|
||||
{
|
||||
progressBarPos += targetStructure.Submarine.DrawPosition;
|
||||
}
|
||||
|
||||
var progressBar = user.UpdateHUDProgressBar(
|
||||
targetStructure,
|
||||
progressBarPos,
|
||||
1.0f - targetStructure.SectionDamage(sectionIndex) / targetStructure.Health,
|
||||
Color.Red, Color.Green);
|
||||
|
||||
if (progressBar != null) progressBar.Size = new Vector2(60.0f, 20.0f);
|
||||
#endif
|
||||
|
||||
targetStructure.AddDamage(sectionIndex, -StructureFixAmount * degreeOfSuccess);
|
||||
|
||||
//if the next section is small enough, apply the effect to it as well
|
||||
//(to make it easier to fix a small "left-over" section)
|
||||
for (int i = -1; i < 2; i += 2)
|
||||
{
|
||||
int nextSectionLength = targetStructure.SectionLength(sectionIndex + i);
|
||||
if ((sectionIndex == 1 && i == -1) ||
|
||||
(sectionIndex == targetStructure.SectionCount - 2 && i == 1) ||
|
||||
(nextSectionLength > 0 && nextSectionLength < Structure.wallSectionSize * 0.3f))
|
||||
{
|
||||
//targetStructure.HighLightSection(sectionIndex + i);
|
||||
targetStructure.AddDamage(sectionIndex + i, -StructureFixAmount * degreeOfSuccess);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ((targetLimb = (targetBody.UserData as Limb)) != null)
|
||||
{
|
||||
targetLimb.character.AddDamage(CauseOfDeath.Damage, -LimbFixAmount * degreeOfSuccess, user);
|
||||
}
|
||||
else if ((targetItem = (targetBody.UserData as Item)) != null)
|
||||
{
|
||||
targetItem.IsHighlighted = true;
|
||||
|
||||
ApplyStatusEffects(ActionType.OnUse, targetItem.AllPropertyObjects, deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
|
||||
{
|
||||
Gap leak = objective.OperateTarget as Gap;
|
||||
if (leak == null) return true;
|
||||
|
||||
float dist = Vector2.Distance(leak.WorldPosition, item.WorldPosition);
|
||||
|
||||
//too far away -> consider this done and hope the AI is smart enough to move closer
|
||||
if (dist > range * 5.0f) return true;
|
||||
|
||||
//steer closer if almost in range
|
||||
if (dist > range)
|
||||
{
|
||||
Vector2 standPos = leak.isHorizontal ?
|
||||
new Vector2(Math.Sign(item.WorldPosition.X - leak.WorldPosition.X), 0.0f)
|
||||
: new Vector2(0.0f, Math.Sign(item.WorldPosition.Y - leak.WorldPosition.Y));
|
||||
|
||||
standPos = leak.WorldPosition + standPos * range;
|
||||
|
||||
character.AIController.SteeringManager.SteeringManual(deltaTime, (standPos - character.WorldPosition) / 1000.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
//close enough -> stop moving
|
||||
character.AIController.SteeringManager.Reset();
|
||||
}
|
||||
|
||||
character.CursorPosition = leak.Position;
|
||||
character.SetInput(InputType.Aim, false, true);
|
||||
|
||||
Use(deltaTime, character);
|
||||
|
||||
return leak.Open <= 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
class Throwable : Holdable
|
||||
{
|
||||
float throwForce;
|
||||
|
||||
float throwPos;
|
||||
|
||||
bool throwing;
|
||||
|
||||
[HasDefaultValue(1.0f, false)]
|
||||
public float ThrowForce
|
||||
{
|
||||
get { return throwForce; }
|
||||
set { throwForce = value; }
|
||||
}
|
||||
|
||||
public Throwable(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
//throwForce = ToolBox.GetAttributeFloat(element, "throwforce", 1.0f);
|
||||
}
|
||||
|
||||
public override bool Use(float deltaTime, Character character = null)
|
||||
{
|
||||
if (character == null) return false;
|
||||
if (!character.IsKeyDown(InputType.Aim) || throwing) return false;
|
||||
|
||||
//Vector2 diff = Vector2.Normalize(Character.CursorPosition - Character.AnimController.RefLimb.Position);
|
||||
|
||||
//if (Character.SelectedItems[1]==item)
|
||||
//{
|
||||
// Limb leftHand = Character.AnimController.GetLimb(LimbType.LeftHand);
|
||||
// leftHand.body.ApplyLinearImpulse(diff * 20.0f);
|
||||
// leftHand.Disabled = true;
|
||||
//}
|
||||
|
||||
//if (Character.SelectedItems[0] == item)
|
||||
//{
|
||||
// Limb rightHand = Character.AnimController.GetLimb(LimbType.RightHand);
|
||||
// rightHand.body.ApplyLinearImpulse(diff * 20.0f);
|
||||
// rightHand.Disabled = true;
|
||||
//}
|
||||
|
||||
throwing = true;
|
||||
|
||||
IsActive = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void SecondaryUse(float deltaTime, Character character = null)
|
||||
{
|
||||
if (throwing) return;
|
||||
}
|
||||
|
||||
public override void Drop(Character dropper)
|
||||
{
|
||||
base.Drop(dropper);
|
||||
|
||||
throwing = false;
|
||||
throwPos = 0.0f;
|
||||
}
|
||||
|
||||
public override void UpdateBroken(float deltaTime, Camera cam)
|
||||
{
|
||||
Update(deltaTime, cam);
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
if (!item.body.Enabled) return;
|
||||
if (!picker.HasSelectedItem(item)) IsActive = false;
|
||||
|
||||
if (!picker.IsKeyDown(InputType.Aim) && !throwing) throwPos = 0.0f;
|
||||
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime, picker);
|
||||
|
||||
if (item.body.Dir != picker.AnimController.Dir) Flip(item);
|
||||
|
||||
AnimController ac = picker.AnimController;
|
||||
|
||||
item.Submarine = picker.Submarine;
|
||||
|
||||
if (!throwing)
|
||||
{
|
||||
if (picker.IsKeyDown(InputType.Aim))
|
||||
{
|
||||
throwPos = (float)System.Math.Min(throwPos+deltaTime*5.0f, MathHelper.Pi*0.7f);
|
||||
|
||||
ac.HoldItem(deltaTime, item, handlePos, new Vector2(0.6f, -0.0f), new Vector2(-0.3f, 0.2f), false, throwPos);
|
||||
}
|
||||
else
|
||||
{
|
||||
ac.HoldItem(deltaTime, item, handlePos, new Vector2(throwPos, 0.0f), aimPos, false, 0.0f);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
//Vector2 diff = Vector2.Normalize(picker.CursorPosition - ac.RefLimb.Position);
|
||||
//diff.X = diff.X * ac.Dir;
|
||||
|
||||
throwPos -= deltaTime*15.0f;
|
||||
|
||||
//angl = -hitPos * 2.0f;
|
||||
// System.Diagnostics.Debug.WriteLine("<1.0f "+hitPos);
|
||||
|
||||
|
||||
|
||||
ac.HoldItem(deltaTime, item, handlePos, new Vector2(0.6f, 0.0f), new Vector2(-0.3f, 0.2f), false, throwPos);
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// System.Diagnostics.Debug.WriteLine(">1.0f " + hitPos);
|
||||
// ac.HoldItem(deltaTime, item, handlePos, new Vector2(0.5f, 0.2f), new Vector2(1.0f, 0.2f), false, 0.0f);
|
||||
//}
|
||||
|
||||
|
||||
if (throwPos < -0.0)
|
||||
{
|
||||
|
||||
Vector2 throwVector = picker.CursorWorldPosition - picker.WorldPosition;
|
||||
throwVector = Vector2.Normalize(throwVector);
|
||||
|
||||
item.Drop();
|
||||
item.body.ApplyLinearImpulse(throwVector * throwForce * item.body.Mass * 3.0f);
|
||||
|
||||
ac.GetLimb(LimbType.Head).body.ApplyLinearImpulse(throwVector*10.0f);
|
||||
ac.GetLimb(LimbType.Torso).body.ApplyLinearImpulse(throwVector * 10.0f);
|
||||
|
||||
Limb rightHand = ac.GetLimb(LimbType.RightHand);
|
||||
item.body.AngularVelocity = rightHand.body.AngularVelocity;
|
||||
|
||||
throwing = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,581 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
using Lidgren.Network;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Barotrauma.Networking;
|
||||
using System.IO;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
interface IDrawableComponent
|
||||
{
|
||||
#if CLIENT
|
||||
void Draw(SpriteBatch spriteBatch, bool editing);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The base class for components holding the different functionalities of the item
|
||||
/// </summary>
|
||||
partial class ItemComponent : IPropertyObject
|
||||
{
|
||||
protected Item item;
|
||||
|
||||
protected string name;
|
||||
|
||||
private bool isActive;
|
||||
|
||||
protected bool characterUsable;
|
||||
|
||||
protected bool canBePicked;
|
||||
protected bool canBeSelected;
|
||||
|
||||
public bool WasUsed;
|
||||
|
||||
public readonly Dictionary<ActionType, List<StatusEffect>> statusEffectLists;
|
||||
|
||||
public List<RelatedItem> requiredItems;
|
||||
|
||||
public List<Skill> requiredSkills;
|
||||
|
||||
public ItemComponent Parent;
|
||||
|
||||
protected const float CorrectionDelay = 1.0f;
|
||||
protected CoroutineHandle delayedCorrectionCoroutine;
|
||||
protected float correctionTimer;
|
||||
|
||||
private string msg;
|
||||
|
||||
[HasDefaultValue(0.0f, false)]
|
||||
public float PickingTime
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public readonly Dictionary<string, ObjectProperty> properties;
|
||||
public Dictionary<string, ObjectProperty> ObjectProperties
|
||||
{
|
||||
get { return properties; }
|
||||
}
|
||||
|
||||
public virtual bool IsActive
|
||||
{
|
||||
get { return isActive; }
|
||||
set
|
||||
{
|
||||
#if CLIENT
|
||||
if (!value && isActive)
|
||||
{
|
||||
StopSounds(ActionType.OnActive);
|
||||
}
|
||||
#endif
|
||||
|
||||
isActive = value;
|
||||
}
|
||||
}
|
||||
|
||||
private bool drawable = true;
|
||||
|
||||
public bool Drawable
|
||||
{
|
||||
get { return drawable; }
|
||||
set
|
||||
{
|
||||
if (value == drawable) return;
|
||||
if (!(this is IDrawableComponent))
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't make \""+this+"\" drawable (the component doesn't implement the IDrawableComponent interface)");
|
||||
return;
|
||||
}
|
||||
|
||||
drawable = value;
|
||||
if (drawable)
|
||||
{
|
||||
if (!item.drawableComponents.Contains((IDrawableComponent)this))
|
||||
item.drawableComponents.Add((IDrawableComponent)this);
|
||||
}
|
||||
else
|
||||
{
|
||||
item.drawableComponents.Remove((IDrawableComponent)this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[HasDefaultValue(false, false)]
|
||||
public bool CanBePicked
|
||||
{
|
||||
get { return canBePicked; }
|
||||
set { canBePicked = value; }
|
||||
}
|
||||
|
||||
[HasDefaultValue(false, false)]
|
||||
public bool DrawHudWhenEquipped
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[HasDefaultValue(false, false)]
|
||||
public bool CanBeSelected
|
||||
{
|
||||
get { return canBeSelected; }
|
||||
set { canBeSelected = value; }
|
||||
}
|
||||
|
||||
public InputType PickKey
|
||||
{
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
|
||||
public InputType SelectKey
|
||||
{
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
|
||||
[HasDefaultValue(false, false)]
|
||||
public bool DeleteOnUse
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public Item Item
|
||||
{
|
||||
get { return item; }
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return name; }
|
||||
}
|
||||
|
||||
[HasDefaultValue("", false)]
|
||||
public string Msg
|
||||
{
|
||||
get { return msg; }
|
||||
set { msg = value; }
|
||||
}
|
||||
|
||||
public ItemComponent(Item item, XElement element)
|
||||
{
|
||||
this.item = item;
|
||||
|
||||
properties = ObjectProperty.GetProperties(this);
|
||||
|
||||
//canBePicked = ToolBox.GetAttributeBool(element, "canbepicked", false);
|
||||
//canBeSelected = ToolBox.GetAttributeBool(element, "canbeselected", false);
|
||||
|
||||
//msg = ToolBox.GetAttributeString(element, "msg", "");
|
||||
|
||||
requiredItems = new List<RelatedItem>();
|
||||
|
||||
requiredSkills = new List<Skill>();
|
||||
|
||||
#if CLIENT
|
||||
sounds = new Dictionary<ActionType, List<ItemSound>>();
|
||||
#endif
|
||||
|
||||
SelectKey = InputType.Select;
|
||||
|
||||
try
|
||||
{
|
||||
string selectKeyStr = ToolBox.GetAttributeString(element, "selectkey", "Select");
|
||||
selectKeyStr = ToolBox.ConvertInputType(selectKeyStr);
|
||||
SelectKey = (InputType)Enum.Parse(typeof(InputType), selectKeyStr, true);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Invalid select key in " + element + "!", e);
|
||||
}
|
||||
|
||||
PickKey = InputType.Select;
|
||||
|
||||
try
|
||||
{
|
||||
string pickKeyStr = ToolBox.GetAttributeString(element, "selectkey", "Select");
|
||||
pickKeyStr = ToolBox.ConvertInputType(pickKeyStr);
|
||||
PickKey = (InputType)Enum.Parse(typeof(InputType),pickKeyStr, true);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Invalid pick key in " + element + "!", e);
|
||||
}
|
||||
|
||||
properties = ObjectProperty.InitProperties(this, element);
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "requireditem":
|
||||
case "requireditems":
|
||||
RelatedItem ri = RelatedItem.Load(subElement);
|
||||
if (ri != null) requiredItems.Add(ri);
|
||||
break;
|
||||
|
||||
case "requiredskill":
|
||||
case "requiredskills":
|
||||
string skillName = ToolBox.GetAttributeString(subElement, "name", "");
|
||||
requiredSkills.Add(new Skill(skillName, ToolBox.GetAttributeInt(subElement, "level", 0)));
|
||||
break;
|
||||
case "statuseffect":
|
||||
var statusEffect = StatusEffect.Load(subElement);
|
||||
|
||||
if (statusEffectLists == null) statusEffectLists = new Dictionary<ActionType, List<StatusEffect>>();
|
||||
|
||||
List<StatusEffect> effectList;
|
||||
if (!statusEffectLists.TryGetValue(statusEffect.type, out effectList))
|
||||
{
|
||||
effectList = new List<StatusEffect>();
|
||||
statusEffectLists.Add(statusEffect.type, effectList);
|
||||
}
|
||||
|
||||
effectList.Add(statusEffect);
|
||||
|
||||
break;
|
||||
default:
|
||||
if (LoadElemProjSpecific(subElement)) break;
|
||||
ItemComponent ic = Load(subElement, item, item.ConfigFile, false);
|
||||
if (ic == null) break;
|
||||
|
||||
ic.Parent = this;
|
||||
item.components.Add(ic);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Move(Vector2 amount) { }
|
||||
|
||||
/// <summary>a Character has picked the item</summary>
|
||||
public virtual bool Pick(Character picker)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public virtual bool Select(Character character)
|
||||
{
|
||||
return CanBeSelected;
|
||||
}
|
||||
|
||||
/// <summary>a Character has dropped the item</summary>
|
||||
public virtual void Drop(Character dropper) { }
|
||||
|
||||
/// <returns>true if the operation was completed</returns>
|
||||
public virtual bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//called when isActive is true and condition > 0.0f
|
||||
public virtual void Update(float deltaTime, Camera cam) { }
|
||||
|
||||
//called when isActive is true and condition == 0.0f
|
||||
public virtual void UpdateBroken(float deltaTime, Camera cam)
|
||||
{
|
||||
#if CLIENT
|
||||
StopSounds(ActionType.OnActive);
|
||||
#endif
|
||||
}
|
||||
|
||||
//called when the item is equipped and left mouse button is pressed
|
||||
//returns true if the item was used succesfully (not out of ammo, reloading, etc)
|
||||
public virtual bool Use(float deltaTime, Character character = null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//called when the item is equipped and right mouse button is pressed
|
||||
public virtual void SecondaryUse(float deltaTime, Character character = null) { }
|
||||
|
||||
//called when the item is placed in a "limbslot"
|
||||
public virtual void Equip(Character character) { }
|
||||
|
||||
//called then the item is dropped or dragged out of a "limbslot"
|
||||
public virtual void Unequip(Character character) { }
|
||||
|
||||
public virtual void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f)
|
||||
{
|
||||
|
||||
switch (connection.Name)
|
||||
{
|
||||
case "activate":
|
||||
case "use":
|
||||
item.Use(1.0f);
|
||||
break;
|
||||
case "toggle":
|
||||
IsActive = !isActive;
|
||||
break;
|
||||
case "set_active":
|
||||
case "set_state":
|
||||
IsActive = signal != "0";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual bool Combine(Item item)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Remove()
|
||||
{
|
||||
#if CLIENT
|
||||
if (loopingSound != null)
|
||||
{
|
||||
Sounds.SoundManager.Stop(loopingSoundIndex);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (delayedCorrectionCoroutine != null)
|
||||
{
|
||||
CoroutineManager.StopCoroutines(delayedCorrectionCoroutine);
|
||||
delayedCorrectionCoroutine = null;
|
||||
}
|
||||
|
||||
RemoveComponentSpecific();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove the component so that it doesn't appear to exist in the game world (stop sounds, remove bodies etc)
|
||||
/// but don't reset anything that's required for cloning the item
|
||||
/// </summary>
|
||||
public void ShallowRemove()
|
||||
{
|
||||
#if CLIENT
|
||||
if (loopingSound != null)
|
||||
{
|
||||
Sounds.SoundManager.Stop(loopingSoundIndex);
|
||||
}
|
||||
#endif
|
||||
|
||||
ShallowRemoveComponentSpecific();
|
||||
}
|
||||
|
||||
protected virtual void ShallowRemoveComponentSpecific()
|
||||
{
|
||||
RemoveComponentSpecific();
|
||||
}
|
||||
|
||||
protected virtual void RemoveComponentSpecific()
|
||||
{ }
|
||||
|
||||
public bool HasRequiredSkills(Character character)
|
||||
{
|
||||
Skill temp;
|
||||
return HasRequiredSkills(character, out temp);
|
||||
}
|
||||
|
||||
public bool HasRequiredSkills(Character character, out Skill insufficientSkill)
|
||||
{
|
||||
foreach (Skill skill in requiredSkills)
|
||||
{
|
||||
int characterLevel = character.GetSkillLevel(skill.Name);
|
||||
if (characterLevel < skill.Level)
|
||||
{
|
||||
insufficientSkill = skill;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
insufficientSkill = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns 0.0f-1.0f based on how well the Character can use the itemcomponent
|
||||
/// </summary>
|
||||
/// <returns>0.5f if all the skills meet the skill requirements exactly, 1.0f if they're way above and 0.0f if way less</returns>
|
||||
protected float DegreeOfSuccess(Character character)
|
||||
{
|
||||
if (requiredSkills.Count == 0) return 100.0f;
|
||||
|
||||
float[] skillSuccess = new float[requiredSkills.Count];
|
||||
|
||||
for (int i = 0; i < requiredSkills.Count; i++ )
|
||||
{
|
||||
int characterLevel = character.GetSkillLevel(requiredSkills[i].Name);
|
||||
|
||||
skillSuccess[i] = (characterLevel - requiredSkills[i].Level);
|
||||
}
|
||||
|
||||
float average = skillSuccess.Average();
|
||||
|
||||
return (average+100.0f)/2.0f;
|
||||
}
|
||||
|
||||
public virtual void FlipX() { }
|
||||
|
||||
public bool HasRequiredContainedItems(bool addMessage)
|
||||
{
|
||||
List<RelatedItem> requiredContained = requiredItems.FindAll(ri=> ri.Type == RelatedItem.RelationType.Contained);
|
||||
|
||||
if (!requiredContained.Any()) return true;
|
||||
|
||||
Item[] containedItems = item.ContainedItems;
|
||||
if (containedItems == null || !containedItems.Any()) return false;
|
||||
|
||||
foreach (RelatedItem ri in requiredContained)
|
||||
{
|
||||
Item containedItem = Array.Find(containedItems, x => x != null && x.Condition > 0.0f && ri.MatchesItem(x));
|
||||
if (containedItem == null)
|
||||
{
|
||||
#if CLIENT
|
||||
if (addMessage && !string.IsNullOrEmpty(ri.Msg)) GUI.AddMessage(ri.Msg, Color.Red);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool HasRequiredItems(Character character, bool addMessage)
|
||||
{
|
||||
if (!requiredItems.Any()) return true;
|
||||
|
||||
foreach (RelatedItem ri in requiredItems)
|
||||
{
|
||||
if (!ri.Type.HasFlag(RelatedItem.RelationType.Equipped) && !ri.Type.HasFlag(RelatedItem.RelationType.Picked)) continue;
|
||||
|
||||
bool hasItem = false;
|
||||
if (ri.Type.HasFlag(RelatedItem.RelationType.Equipped))
|
||||
{
|
||||
if (character.SelectedItems.FirstOrDefault(it => it != null && it.Condition > 0.0f && ri.MatchesItem(it)) != null) hasItem = true;
|
||||
}
|
||||
if (!hasItem && ri.Type.HasFlag(RelatedItem.RelationType.Picked))
|
||||
{
|
||||
if (character.Inventory.Items.FirstOrDefault(x => x!=null && x.Condition>0.0f && ri.MatchesItem(x))!=null) hasItem = true;
|
||||
}
|
||||
if (!hasItem)
|
||||
{
|
||||
#if CLIENT
|
||||
if (addMessage && !string.IsNullOrEmpty(ri.Msg)) GUI.AddMessage(ri.Msg, Color.Red);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ApplyStatusEffects(ActionType type, float deltaTime, Character character = null)
|
||||
{
|
||||
if (statusEffectLists == null) return;
|
||||
|
||||
List<StatusEffect> statusEffects;
|
||||
if (!statusEffectLists.TryGetValue(type, out statusEffects)) return;
|
||||
|
||||
foreach (StatusEffect effect in statusEffects)
|
||||
{
|
||||
item.ApplyStatusEffect(effect, type, deltaTime, character);
|
||||
}
|
||||
}
|
||||
|
||||
public void ApplyStatusEffects(ActionType type, List<IPropertyObject> targets, float deltaTime)
|
||||
{
|
||||
if (statusEffectLists == null) return;
|
||||
|
||||
List<StatusEffect> statusEffects;
|
||||
if (!statusEffectLists.TryGetValue(type, out statusEffects)) return;
|
||||
|
||||
foreach (StatusEffect effect in statusEffects)
|
||||
{
|
||||
effect.Apply(type, deltaTime, item, targets);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Load(XElement componentElement)
|
||||
{
|
||||
if (componentElement == null) return;
|
||||
|
||||
foreach (XAttribute attribute in componentElement.Attributes())
|
||||
{
|
||||
ObjectProperty property = null;
|
||||
if (!properties.TryGetValue(attribute.Name.ToString().ToLowerInvariant(), out property)) continue;
|
||||
|
||||
property.TrySetValue(attribute.Value);
|
||||
}
|
||||
|
||||
List<RelatedItem> prevRequiredItems = new List<RelatedItem>(requiredItems);
|
||||
requiredItems.Clear();
|
||||
|
||||
foreach (XElement subElement in componentElement.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "requireditem":
|
||||
RelatedItem newRequiredItem = RelatedItem.Load(subElement);
|
||||
|
||||
if (newRequiredItem == null) continue;
|
||||
|
||||
var prevRequiredItem = prevRequiredItems.Find(ri => ri.JoinedNames == newRequiredItem.JoinedNames);
|
||||
if (prevRequiredItem!=null)
|
||||
{
|
||||
newRequiredItem.statusEffects = prevRequiredItem.statusEffects;
|
||||
newRequiredItem.Msg = prevRequiredItem.Msg;
|
||||
}
|
||||
|
||||
requiredItems.Add(newRequiredItem);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void OnMapLoaded() { }
|
||||
|
||||
public static ItemComponent Load(XElement element, Item item, string file, bool errorMessages = true)
|
||||
{
|
||||
Type t;
|
||||
string type = element.Name.ToString().ToLowerInvariant();
|
||||
try
|
||||
{
|
||||
// Get the type of a specified class.
|
||||
t = Type.GetType("Barotrauma.Items.Components." + type + "", false, true);
|
||||
if (t == null)
|
||||
{
|
||||
if (errorMessages) DebugConsole.ThrowError("Could not find the component \"" + type + "\" (" + file + ")");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (errorMessages) DebugConsole.ThrowError("Could not find the component \"" + type + "\" (" + file + ")", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
ConstructorInfo constructor;
|
||||
try
|
||||
{
|
||||
if (t != typeof(ItemComponent) && !t.IsSubclassOf(typeof(ItemComponent))) return null;
|
||||
constructor = t.GetConstructor(new Type[] { typeof(Item), typeof(XElement) });
|
||||
if (constructor == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Could not find the constructor of the component \"" + type + "\" (" + file + ")");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Could not find the constructor of the component \"" + type + "\" (" + file + ")", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
object[] lobject = new object[] { item, element };
|
||||
object component = constructor.Invoke(lobject);
|
||||
|
||||
ItemComponent ic = (ItemComponent)component;
|
||||
ic.name = element.Name.ToString();
|
||||
|
||||
return ic;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class ItemContainer : ItemComponent, IDrawableComponent
|
||||
{
|
||||
public const int MaxInventoryCount = 4;
|
||||
|
||||
List<RelatedItem> containableItems;
|
||||
public ItemInventory Inventory;
|
||||
|
||||
private List<Pair<Item, StatusEffect>> itemsWithStatusEffects;
|
||||
|
||||
//how many items can be contained
|
||||
[HasDefaultValue(5, false)]
|
||||
public int Capacity
|
||||
{
|
||||
get { return capacity; }
|
||||
set { capacity = Math.Max(value, 1); }
|
||||
}
|
||||
private int capacity;
|
||||
|
||||
[HasDefaultValue(true, false)]
|
||||
public bool HideItems
|
||||
{
|
||||
get { return hideItems; }
|
||||
set
|
||||
{
|
||||
hideItems = value;
|
||||
Drawable = !hideItems;
|
||||
}
|
||||
}
|
||||
private bool hideItems;
|
||||
|
||||
[HasDefaultValue(false, false)]
|
||||
public bool DrawInventory
|
||||
{
|
||||
get { return drawInventory; }
|
||||
set { drawInventory = value; }
|
||||
}
|
||||
private bool drawInventory;
|
||||
|
||||
//the position of the first item in the container
|
||||
[HasDefaultValue("0.0,0.0", false)]
|
||||
public string ItemPos
|
||||
{
|
||||
get { return ToolBox.Vector2ToString(itemPos); }
|
||||
set { itemPos = ToolBox.ParseToVector2(value); }
|
||||
}
|
||||
private Vector2 itemPos;
|
||||
|
||||
//item[i].Pos = itemPos + itemInterval*i
|
||||
[HasDefaultValue("0.0,0.0", false)]
|
||||
public string ItemInterval
|
||||
{
|
||||
get { return ToolBox.Vector2ToString(itemInterval); }
|
||||
set { itemInterval = ToolBox.ParseToVector2(value); }
|
||||
}
|
||||
private Vector2 itemInterval;
|
||||
|
||||
[HasDefaultValue(0.0f, false)]
|
||||
public float ItemRotation
|
||||
{
|
||||
get { return MathHelper.ToDegrees(itemRotation); }
|
||||
set { itemRotation = MathHelper.ToRadians(value); }
|
||||
}
|
||||
private float itemRotation;
|
||||
|
||||
|
||||
[HasDefaultValue("0.5,0.9", false)]
|
||||
public string HudPos
|
||||
{
|
||||
get { return ToolBox.Vector2ToString(hudPos); }
|
||||
set
|
||||
{
|
||||
hudPos = ToolBox.ParseToVector2(value);
|
||||
//inventory.CenterPos = hudPos;
|
||||
}
|
||||
}
|
||||
private Vector2 hudPos;
|
||||
|
||||
[HasDefaultValue(5, false)]
|
||||
public int SlotsPerRow
|
||||
{
|
||||
get { return slotsPerRow; }
|
||||
set { slotsPerRow = value; }
|
||||
}
|
||||
private int slotsPerRow;
|
||||
|
||||
public List<RelatedItem> ContainableItems
|
||||
{
|
||||
get { return containableItems; }
|
||||
}
|
||||
|
||||
public ItemContainer(Item item, XElement element)
|
||||
: base (item, element)
|
||||
{
|
||||
Inventory = new ItemInventory(item, this, capacity, hudPos, slotsPerRow);
|
||||
containableItems = new List<RelatedItem>();
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "containable":
|
||||
RelatedItem containable = RelatedItem.Load(subElement);
|
||||
if (containable == null) continue;
|
||||
|
||||
containableItems.Add(containable);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
itemsWithStatusEffects = new List<Pair<Item, StatusEffect>>();
|
||||
}
|
||||
|
||||
public void OnItemContained(Item containedItem)
|
||||
{
|
||||
item.SetContainedItemPositions();
|
||||
|
||||
RelatedItem ri = containableItems.Find(x => x.MatchesItem(containedItem));
|
||||
if (ri != null)
|
||||
{
|
||||
foreach (StatusEffect effect in ri.statusEffects)
|
||||
{
|
||||
itemsWithStatusEffects.Add(Pair<Item, StatusEffect>.Create(containedItem, effect));
|
||||
}
|
||||
}
|
||||
|
||||
//no need to Update() if this item has no statuseffects and no physics body
|
||||
IsActive = itemsWithStatusEffects.Count > 0 || containedItem.body != null;
|
||||
}
|
||||
|
||||
public void OnItemRemoved(Item item)
|
||||
{
|
||||
itemsWithStatusEffects.RemoveAll(i => i.First == item);
|
||||
|
||||
//deactivate if the inventory is empty
|
||||
IsActive = itemsWithStatusEffects.Count > 0 || item.body != null;
|
||||
}
|
||||
|
||||
public bool CanBeContained(Item item)
|
||||
{
|
||||
if (containableItems.Count == 0) return true;
|
||||
return (containableItems.Find(x => x.MatchesItem(item)) != null);
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
if (item.body != null &&
|
||||
item.body.Enabled &&
|
||||
item.body.FarseerBody.Awake)
|
||||
{
|
||||
item.SetContainedItemPositions();
|
||||
}
|
||||
|
||||
foreach (Pair<Item, StatusEffect> itemAndEffect in itemsWithStatusEffects)
|
||||
{
|
||||
Item contained = itemAndEffect.First;
|
||||
if (contained.Condition < 0.0f) continue;
|
||||
|
||||
StatusEffect effect = itemAndEffect.Second;
|
||||
|
||||
if (effect.Targets.HasFlag(StatusEffect.TargetType.This))
|
||||
effect.Apply(ActionType.OnContaining, deltaTime, item, item.AllPropertyObjects);
|
||||
if (effect.Targets.HasFlag(StatusEffect.TargetType.Contained))
|
||||
effect.Apply(ActionType.OnContaining, deltaTime, item, contained.AllPropertyObjects);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Pick(Character picker)
|
||||
{
|
||||
return (picker != null);
|
||||
}
|
||||
|
||||
|
||||
public override bool Combine(Item item)
|
||||
{
|
||||
if (!containableItems.Any(x => x.MatchesItem(item))) return false;
|
||||
|
||||
if (Inventory.TryPutItem(item))
|
||||
{
|
||||
IsActive = true;
|
||||
if (hideItems && item.body != null) item.body.Enabled = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void OnMapLoaded()
|
||||
{
|
||||
if (itemIds == null) return;
|
||||
|
||||
for (ushort i = 0; i < itemIds.Length; i++)
|
||||
{
|
||||
Item item = MapEntity.FindEntityByID(itemIds[i]) as Item;
|
||||
if (item == null) continue;
|
||||
|
||||
Inventory.TryPutItem(item, i, false, false);
|
||||
}
|
||||
|
||||
itemIds = null;
|
||||
}
|
||||
|
||||
protected override void ShallowRemoveComponentSpecific()
|
||||
{
|
||||
}
|
||||
|
||||
protected override void RemoveComponentSpecific()
|
||||
{
|
||||
foreach (Item item in Inventory.Items)
|
||||
{
|
||||
if (item == null) continue;
|
||||
item.Remove();
|
||||
}
|
||||
}
|
||||
|
||||
public override void Load(XElement componentElement)
|
||||
{
|
||||
base.Load(componentElement);
|
||||
|
||||
string containedString = ToolBox.GetAttributeString(componentElement, "contained", "");
|
||||
|
||||
string[] itemIdStrings = containedString.Split(',');
|
||||
|
||||
itemIds = new ushort[itemIdStrings.Length];
|
||||
for (int i = 0; i < itemIdStrings.Length; i++)
|
||||
{
|
||||
ushort id = 0;
|
||||
if (!ushort.TryParse(itemIdStrings[i], out id)) continue;
|
||||
|
||||
itemIds[i] = id;
|
||||
}
|
||||
}
|
||||
|
||||
ushort[] itemIds;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class ItemLabel : ItemComponent, IDrawableComponent
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
class Ladder : ItemComponent
|
||||
{
|
||||
|
||||
public Ladder(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool Select(Character character)
|
||||
{
|
||||
if (character == null) return false;
|
||||
|
||||
character.AnimController.Anim = AnimController.Animation.Climbing;
|
||||
//picker.SelectedConstruction = item;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Dynamics.Joints;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
struct LimbPos
|
||||
{
|
||||
public LimbType limbType;
|
||||
public Vector2 position;
|
||||
|
||||
public LimbPos(LimbType limbType, Vector2 position)
|
||||
{
|
||||
this.limbType = limbType;
|
||||
this.position = position;
|
||||
}
|
||||
}
|
||||
|
||||
class Controller : ItemComponent
|
||||
{
|
||||
//where the limbs of the user should be positioned when using the controller
|
||||
private List<LimbPos> limbPositions;
|
||||
|
||||
private Direction dir;
|
||||
|
||||
//the position where the user walks to when using the controller
|
||||
//(relative to the position of the item)
|
||||
private Vector2 userPos;
|
||||
|
||||
private Camera cam;
|
||||
|
||||
private Character character;
|
||||
|
||||
public Vector2 UserPos
|
||||
{
|
||||
get { return userPos; }
|
||||
set { userPos = value; }
|
||||
}
|
||||
|
||||
public Controller(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
limbPositions = new List<LimbPos>();
|
||||
|
||||
userPos = ToolBox.GetAttributeVector2(element, "UserPos", Vector2.Zero);
|
||||
|
||||
Enum.TryParse<Direction>(ToolBox.GetAttributeString(element, "direction", "None"), out dir);
|
||||
|
||||
foreach (XElement el in element.Elements())
|
||||
{
|
||||
if (el.Name != "limbposition") continue;
|
||||
|
||||
LimbPos lp = new LimbPos();
|
||||
|
||||
try
|
||||
{
|
||||
lp.limbType = (LimbType)Enum.Parse(typeof(LimbType), el.Attribute("limb").Value, true);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in " + element + ": " + e.Message, e);
|
||||
}
|
||||
|
||||
lp.position = ToolBox.GetAttributeVector2(el, "position", Vector2.Zero);
|
||||
|
||||
limbPositions.Add(lp);
|
||||
}
|
||||
|
||||
IsActive = true;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
this.cam = cam;
|
||||
|
||||
if (character == null
|
||||
|| character.IsDead
|
||||
|| character.Stun > 0.0f
|
||||
|| character.SelectedConstruction != item
|
||||
|| Vector2.Distance(character.Position, item.Position) > item.PickDistance * 2.0f)
|
||||
{
|
||||
if (character != null)
|
||||
{
|
||||
CancelUsing(character);
|
||||
character = null;
|
||||
}
|
||||
IsActive = false;
|
||||
return;
|
||||
}
|
||||
|
||||
character.AnimController.Anim = AnimController.Animation.UsingConstruction;
|
||||
|
||||
if (userPos != Vector2.Zero)
|
||||
{
|
||||
Vector2 diff = (item.WorldPosition + userPos) - character.WorldPosition;
|
||||
|
||||
if (character.AnimController.InWater)
|
||||
{
|
||||
if (diff.Length() > 30.0f)
|
||||
{
|
||||
character.AnimController.TargetMovement = Vector2.Clamp(diff*0.01f, -Vector2.One, Vector2.One);
|
||||
character.AnimController.TargetDir = diff.X > 0.0f ? Direction.Right : Direction.Left;
|
||||
}
|
||||
else
|
||||
{
|
||||
character.AnimController.TargetMovement = Vector2.Zero;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
diff.Y = 0.0f;
|
||||
if (diff != Vector2.Zero && diff.Length() > 10.0f)
|
||||
{
|
||||
character.AnimController.TargetMovement = Vector2.Normalize(diff);
|
||||
character.AnimController.TargetDir = diff.X > 0.0f ? Direction.Right : Direction.Left;
|
||||
return;
|
||||
}
|
||||
character.AnimController.TargetMovement = Vector2.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime, character);
|
||||
|
||||
if (limbPositions.Count == 0) return;
|
||||
|
||||
character.AnimController.Anim = AnimController.Animation.UsingConstruction;
|
||||
|
||||
character.AnimController.ResetPullJoints();
|
||||
|
||||
if (dir != 0) character.AnimController.TargetDir = dir;
|
||||
|
||||
foreach (LimbPos lb in limbPositions)
|
||||
{
|
||||
Limb limb = character.AnimController.GetLimb(lb.limbType);
|
||||
if (limb == null) continue;
|
||||
|
||||
limb.Disabled = true;
|
||||
|
||||
if (limb.pullJoint == null) continue;
|
||||
|
||||
Vector2 position = ConvertUnits.ToSimUnits(lb.position + new Vector2(item.Rect.X, item.Rect.Y));
|
||||
limb.pullJoint.Enabled = true;
|
||||
limb.pullJoint.WorldAnchorB = position;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public override bool Use(float deltaTime, Character activator = null)
|
||||
{
|
||||
if (character == null || activator != character || character.SelectedConstruction != item)
|
||||
{
|
||||
character = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
item.SendSignal(0, "1", "trigger_out", character);
|
||||
|
||||
ApplyStatusEffects(ActionType.OnUse, 1.0f, activator);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void SecondaryUse(float deltaTime, Character character = null)
|
||||
{
|
||||
if (this.character == null || this.character != character || this.character.SelectedConstruction != item)
|
||||
{
|
||||
character = null;
|
||||
return;
|
||||
}
|
||||
|
||||
Entity focusTarget = null;
|
||||
|
||||
if (character == null) return;
|
||||
|
||||
|
||||
foreach (Connection c in item.Connections)
|
||||
{
|
||||
if (c.Name != "position_out") continue;
|
||||
|
||||
foreach (Connection c2 in c.Recipients)
|
||||
{
|
||||
if (c2 == null || c2.Item == null || !c2.Item.Prefab.FocusOnSelected) continue;
|
||||
|
||||
focusTarget = c2.Item;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (focusTarget == null)
|
||||
{
|
||||
item.SendSignal(0, ToolBox.Vector2ToString(character.CursorWorldPosition), "position_out", character);
|
||||
return;
|
||||
}
|
||||
|
||||
character.ViewTarget = focusTarget;
|
||||
#if CLIENT
|
||||
if (character == Character.Controlled && cam != null)
|
||||
{
|
||||
Lights.LightManager.ViewTarget = focusTarget;
|
||||
cam.TargetPos = focusTarget.WorldPosition;
|
||||
|
||||
cam.OffsetAmount = MathHelper.Lerp(cam.OffsetAmount, (focusTarget as Item).Prefab.OffsetOnSelected, deltaTime*10.0f);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!character.IsRemotePlayer || character.ViewTarget == focusTarget)
|
||||
{
|
||||
item.SendSignal(0, ToolBox.Vector2ToString(character.CursorWorldPosition), "position_out", character);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Pick(Character picker)
|
||||
{
|
||||
item.SendSignal(0, "1", "signal_out", picker);
|
||||
|
||||
#if CLIENT
|
||||
PlaySound(ActionType.OnUse, item.WorldPosition);
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void CancelUsing(Character character)
|
||||
{
|
||||
foreach (LimbPos lb in limbPositions)
|
||||
{
|
||||
Limb limb = character.AnimController.GetLimb(lb.limbType);
|
||||
if (limb == null) continue;
|
||||
|
||||
limb.Disabled = false;
|
||||
|
||||
limb.pullJoint.Enabled = false;
|
||||
}
|
||||
|
||||
if (character.SelectedConstruction == this.item) character.SelectedConstruction = null;
|
||||
|
||||
character.AnimController.Anim = AnimController.Animation.None;
|
||||
}
|
||||
|
||||
public override bool Select(Character activator)
|
||||
{
|
||||
if (activator == null) return false;
|
||||
|
||||
//someone already using the item
|
||||
if (character != null)
|
||||
{
|
||||
if (character == activator)
|
||||
{
|
||||
IsActive = false;
|
||||
CancelUsing(character);
|
||||
character = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
character = activator;
|
||||
|
||||
IsActive = true;
|
||||
}
|
||||
|
||||
item.SendSignal(0, "1", "signal_out", character);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void FlipX()
|
||||
{
|
||||
if (dir != Direction.None)
|
||||
{
|
||||
dir = dir == Direction.Left ? Direction.Right : Direction.Left;
|
||||
}
|
||||
|
||||
userPos.X = -UserPos.X;
|
||||
|
||||
for (int i = 0; i < limbPositions.Count; i++)
|
||||
{
|
||||
float diff = (item.Rect.X + limbPositions[i].position.X) - item.Rect.Center.X;
|
||||
|
||||
Vector2 flippedPos =
|
||||
new Vector2(
|
||||
item.Rect.Center.X - diff - item.Rect.X,
|
||||
limbPositions[i].position.Y);
|
||||
|
||||
limbPositions[i] = new LimbPos(limbPositions[i].limbType, flippedPos);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
using Barotrauma.Networking;
|
||||
using Lidgren.Network;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class Deconstructor : Powered, IServerSerializable, IClientSerializable
|
||||
{
|
||||
float progressTimer;
|
||||
|
||||
ItemContainer container;
|
||||
|
||||
public Deconstructor(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
#if CLIENT
|
||||
progressBar = new GUIProgressBar(new Rectangle(0,0,200,20), Color.Green, "", 0.0f, Alignment.BottomCenter, GuiFrame);
|
||||
|
||||
activateButton = new GUIButton(new Rectangle(0, 0, 200, 20), "Deconstruct", Alignment.TopCenter, "", GuiFrame);
|
||||
activateButton.OnClicked = ToggleActive;
|
||||
#endif
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
if (container == null || container.Inventory.Items.All(i => i == null))
|
||||
{
|
||||
SetActive(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (voltage < minVoltage) return;
|
||||
|
||||
if (powerConsumption == 0.0f) voltage = 1.0f;
|
||||
|
||||
progressTimer += deltaTime*voltage;
|
||||
Voltage -= deltaTime * 10.0f;
|
||||
|
||||
var targetItem = container.Inventory.Items.FirstOrDefault(i => i != null);
|
||||
#if CLIENT
|
||||
progressBar.BarSize = Math.Min(progressTimer / targetItem.Prefab.DeconstructTime, 1.0f);
|
||||
#endif
|
||||
if (progressTimer>targetItem.Prefab.DeconstructTime)
|
||||
{
|
||||
var containers = item.GetComponents<ItemContainer>();
|
||||
if (containers.Count < 2)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in Deconstructor.Update: Deconstructors must have two ItemContainer components!");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (DeconstructItem deconstructProduct in targetItem.Prefab.DeconstructItems)
|
||||
{
|
||||
if (deconstructProduct.RequireFullCondition && targetItem.Condition < 100.0f) continue;
|
||||
|
||||
var itemPrefab = MapEntityPrefab.list.FirstOrDefault(ip => ip.Name.ToLowerInvariant() == deconstructProduct.ItemPrefabName.ToLowerInvariant()) as ItemPrefab;
|
||||
if (itemPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Tried to deconstruct item \"" + targetItem.Name + "\" but couldn't find item prefab \"" + deconstructProduct + "\"!");
|
||||
continue;
|
||||
}
|
||||
|
||||
//container full, drop the items outside the deconstructor
|
||||
if (containers[1].Inventory.Items.All(i => i != null))
|
||||
{
|
||||
Entity.Spawner.AddToSpawnQueue(itemPrefab, item.Position, item.Submarine);
|
||||
}
|
||||
else
|
||||
{
|
||||
Entity.Spawner.AddToSpawnQueue(itemPrefab, containers[1].Inventory);
|
||||
}
|
||||
}
|
||||
|
||||
container.Inventory.RemoveItem(targetItem);
|
||||
Entity.Spawner.AddToRemoveQueue(targetItem);
|
||||
|
||||
if (container.Inventory.Items.Any(i => i != null))
|
||||
{
|
||||
progressTimer = 0.0f;
|
||||
#if CLIENT
|
||||
progressBar.BarSize = 0.0f;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetActive(bool active, Character user = null)
|
||||
{
|
||||
container = item.GetComponent<ItemContainer>();
|
||||
if (container == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in Deconstructor.Activate: Deconstructors must have two ItemContainer components");
|
||||
return;
|
||||
}
|
||||
|
||||
if (container.Inventory.Items.All(i => i == null)) active = false;
|
||||
|
||||
IsActive = active;
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
GameServer.Log(user.Name + (IsActive ? " activated " : " deactivated ") + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
if (!IsActive)
|
||||
{
|
||||
progressBar.BarSize = 0.0f;
|
||||
progressTimer = 0.0f;
|
||||
|
||||
activateButton.Text = "Deconstruct";
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
activateButton.Text = "Cancel";
|
||||
}
|
||||
#endif
|
||||
|
||||
container.Inventory.Locked = IsActive;
|
||||
}
|
||||
|
||||
public void ServerRead(ClientNetObject type, NetBuffer msg, Client c)
|
||||
{
|
||||
bool active = msg.ReadBoolean();
|
||||
|
||||
item.CreateServerEvent(this);
|
||||
|
||||
if (item.CanClientAccess(c))
|
||||
{
|
||||
SetActive(active, c.Character);
|
||||
}
|
||||
}
|
||||
|
||||
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
|
||||
{
|
||||
msg.Write(IsActive);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class Engine : Powered
|
||||
{
|
||||
private float force;
|
||||
|
||||
private float targetForce;
|
||||
|
||||
private float maxForce;
|
||||
|
||||
//[Editable, HasDefaultValue(1.0f, true)]
|
||||
//public float PowerPerForce
|
||||
//{
|
||||
// get { return powerPerForce; }
|
||||
// set
|
||||
// {
|
||||
// powerPerForce = Math.Max(0.0f, value);
|
||||
// }
|
||||
//}
|
||||
|
||||
[Editable, HasDefaultValue(2000.0f, true)]
|
||||
public float MaxForce
|
||||
{
|
||||
get { return maxForce; }
|
||||
set
|
||||
{
|
||||
maxForce = Math.Max(0.0f, value);
|
||||
}
|
||||
}
|
||||
|
||||
public float Force
|
||||
{
|
||||
get { return force;}
|
||||
set { force = MathHelper.Clamp(value, -100.0f, 100.0f); }
|
||||
}
|
||||
|
||||
public Engine(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
IsActive = true;
|
||||
|
||||
#if CLIENT
|
||||
var button = new GUIButton(new Rectangle(160, 50, 30, 30), "-", "", GuiFrame);
|
||||
button.OnClicked = (GUIButton btn, object obj) =>
|
||||
{
|
||||
targetForce -= 1.0f;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
button = new GUIButton(new Rectangle(200, 50, 30, 30), "+", "", GuiFrame);
|
||||
button.OnClicked = (GUIButton btn, object obj) =>
|
||||
{
|
||||
targetForce += 1.0f;
|
||||
|
||||
return true;
|
||||
};
|
||||
#endif
|
||||
}
|
||||
|
||||
public float CurrentVolume
|
||||
{
|
||||
get { return Math.Abs((force / 100.0f) * (voltage / minVoltage)); }
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
base.Update(deltaTime, cam);
|
||||
|
||||
currPowerConsumption = Math.Abs(targetForce)/100.0f * powerConsumption;
|
||||
|
||||
if (powerConsumption == 0.0f) voltage = 1.0f;
|
||||
|
||||
Force = MathHelper.Lerp(force, (voltage < minVoltage) ? 0.0f : targetForce, 0.1f);
|
||||
if (Math.Abs(Force) > 1.0f)
|
||||
{
|
||||
Vector2 currForce = new Vector2((force / 100.0f) * maxForce * (voltage / minVoltage), 0.0f);
|
||||
|
||||
item.Submarine.ApplyForce(currForce);
|
||||
|
||||
if (item.CurrentHull != null)
|
||||
{
|
||||
item.CurrentHull.AiTarget.SoundRange = Math.Max(currForce.Length(), item.CurrentHull.AiTarget.SoundRange);
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
GameMain.ParticleManager.CreateParticle("bubbles", item.WorldPosition - (Vector2.UnitX * item.Rect.Width/2),
|
||||
-currForce / 5.0f + new Vector2(Rand.Range(-100.0f, 100.0f), Rand.Range(-50f, 50f)),
|
||||
0.0f, item.CurrentHull);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
voltage = 0.0f;
|
||||
}
|
||||
|
||||
public override void UpdateBroken(float deltaTime, Camera cam)
|
||||
{
|
||||
force = MathHelper.Lerp(force, 0.0f, 0.1f);
|
||||
}
|
||||
|
||||
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power=0.0f)
|
||||
{
|
||||
base.ReceiveSignal(stepsTaken, signal, connection, source, sender, power);
|
||||
|
||||
if (connection.Name == "set_force")
|
||||
{
|
||||
float tempForce;
|
||||
if (float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out tempForce))
|
||||
{
|
||||
targetForce = MathHelper.Clamp(tempForce, -100.0f, 100.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,312 @@
|
||||
using Barotrauma.Networking;
|
||||
using Lidgren.Network;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
class FabricableItem
|
||||
{
|
||||
public readonly ItemPrefab TargetItem;
|
||||
|
||||
public readonly List<Tuple<ItemPrefab, int>> RequiredItems;
|
||||
|
||||
public readonly float RequiredTime;
|
||||
|
||||
public readonly List<Skill> RequiredSkills;
|
||||
|
||||
public FabricableItem(XElement element)
|
||||
{
|
||||
string name = ToolBox.GetAttributeString(element, "name", "");
|
||||
|
||||
TargetItem = ItemPrefab.list.Find(ip => ip.Name.ToLowerInvariant() == name.ToLowerInvariant()) as ItemPrefab;
|
||||
if (TargetItem == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in fabricable item "+name+"! Item \"" + element.Name + "\" not found.");
|
||||
return;
|
||||
}
|
||||
|
||||
RequiredSkills = new List<Skill>();
|
||||
|
||||
RequiredTime = ToolBox.GetAttributeFloat(element, "requiredtime", 1.0f);
|
||||
|
||||
RequiredItems = new List<Tuple<ItemPrefab, int>>();
|
||||
|
||||
string[] requiredItemNames = ToolBox.GetAttributeString(element, "requireditems", "").Split(',');
|
||||
foreach (string requiredItemName in requiredItemNames)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(requiredItemName)) continue;
|
||||
|
||||
ItemPrefab requiredItem = ItemPrefab.list.Find(ip => ip.Name.ToLowerInvariant() == requiredItemName.Trim().ToLowerInvariant()) as ItemPrefab;
|
||||
if (requiredItem == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in fabricable item " + name + "! Required item \"" + requiredItemName + "\" not found.");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
var existing = RequiredItems.Find(r => r.Item1 == requiredItem);
|
||||
|
||||
if (existing == null)
|
||||
{
|
||||
|
||||
RequiredItems.Add(new Tuple<ItemPrefab, int>(requiredItem, 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
RequiredItems.Remove(existing);
|
||||
RequiredItems.Add(new Tuple<ItemPrefab, int>(requiredItem, existing.Item2+1));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "requiredskill":
|
||||
RequiredSkills.Add(new Skill(
|
||||
ToolBox.GetAttributeString(subElement, "name", ""),
|
||||
ToolBox.GetAttributeInt(subElement, "level", 0)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
partial class Fabricator : Powered, IServerSerializable, IClientSerializable
|
||||
{
|
||||
private List<FabricableItem> fabricableItems;
|
||||
|
||||
private FabricableItem fabricatedItem;
|
||||
private float timeUntilReady;
|
||||
|
||||
//used for checking if contained items have changed
|
||||
//(in which case we need to recheck which items can be fabricated)
|
||||
private Item[] prevContainedItems;
|
||||
|
||||
public Fabricator(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
fabricableItems = new List<FabricableItem>();
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
if (subElement.Name.ToString() != "fabricableitem") continue;
|
||||
|
||||
FabricableItem fabricableItem = new FabricableItem(subElement);
|
||||
if (fabricableItem.TargetItem != null) fabricableItems.Add(fabricableItem);
|
||||
}
|
||||
|
||||
InitProjSpecific();
|
||||
}
|
||||
|
||||
partial void InitProjSpecific();
|
||||
|
||||
public override bool Select(Character character)
|
||||
{
|
||||
CheckFabricableItems(character);
|
||||
#if CLIENT
|
||||
if (itemList.Selected != null)
|
||||
{
|
||||
SelectItem(itemList.Selected, itemList.Selected.UserData);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
return base.Select(character);
|
||||
}
|
||||
|
||||
public override bool Pick(Character picker)
|
||||
{
|
||||
return (picker != null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// check which of the items can be fabricated by the character
|
||||
/// and update the text colors of the item list accordingly
|
||||
/// </summary>
|
||||
private void CheckFabricableItems(Character character)
|
||||
{
|
||||
#if CLIENT
|
||||
foreach (GUIComponent child in itemList.children)
|
||||
{
|
||||
var itemPrefab = child.UserData as FabricableItem;
|
||||
if (itemPrefab == null) continue;
|
||||
|
||||
bool canBeFabricated = CanBeFabricated(itemPrefab, character);
|
||||
|
||||
|
||||
child.GetChild<GUITextBlock>().TextColor = Color.White * (canBeFabricated ? 1.0f : 0.5f);
|
||||
child.GetChild<GUIImage>().Color = itemPrefab.TargetItem.SpriteColor * (canBeFabricated ? 1.0f : 0.5f);
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
var itemContainer = item.GetComponent<ItemContainer>();
|
||||
prevContainedItems = new Item[itemContainer.Inventory.Items.Length];
|
||||
itemContainer.Inventory.Items.CopyTo(prevContainedItems, 0);
|
||||
}
|
||||
|
||||
private void StartFabricating(FabricableItem selectedItem, Character user = null)
|
||||
{
|
||||
if (selectedItem == null) return;
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
GameServer.Log(user.Name + " started fabricating " + selectedItem.TargetItem.Name + " in " + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
itemList.Enabled = false;
|
||||
|
||||
activateButton.Text = "Cancel";
|
||||
#endif
|
||||
|
||||
fabricatedItem = selectedItem;
|
||||
IsActive = true;
|
||||
|
||||
timeUntilReady = fabricatedItem.RequiredTime;
|
||||
|
||||
var containers = item.GetComponents<ItemContainer>();
|
||||
containers[0].Inventory.Locked = true;
|
||||
containers[1].Inventory.Locked = true;
|
||||
|
||||
currPowerConsumption = powerConsumption;
|
||||
}
|
||||
|
||||
private void CancelFabricating(Character user = null)
|
||||
{
|
||||
if (fabricatedItem != null && user != null)
|
||||
{
|
||||
GameServer.Log(user.Name + " cancelled the fabrication of " + fabricatedItem.TargetItem.Name + " in " + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
|
||||
IsActive = false;
|
||||
fabricatedItem = null;
|
||||
|
||||
currPowerConsumption = 0.0f;
|
||||
|
||||
#if CLIENT
|
||||
itemList.Enabled = true;
|
||||
if (activateButton != null)
|
||||
{
|
||||
activateButton.Text = "Create";
|
||||
}
|
||||
if (progressBar != null) progressBar.BarSize = 0.0f;
|
||||
#endif
|
||||
|
||||
timeUntilReady = 0.0f;
|
||||
|
||||
var containers = item.GetComponents<ItemContainer>();
|
||||
containers[0].Inventory.Locked = false;
|
||||
containers[1].Inventory.Locked = false;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
#if CLIENT
|
||||
if (progressBar!=null)
|
||||
{
|
||||
progressBar.BarSize = fabricatedItem == null ? 0.0f : (fabricatedItem.RequiredTime - timeUntilReady) / fabricatedItem.RequiredTime;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (voltage < minVoltage) return;
|
||||
|
||||
if (powerConsumption == 0) voltage = 1.0f;
|
||||
|
||||
timeUntilReady -= deltaTime*voltage;
|
||||
|
||||
voltage -= deltaTime * 10.0f;
|
||||
|
||||
if (timeUntilReady > 0.0f) return;
|
||||
|
||||
var containers = item.GetComponents<ItemContainer>();
|
||||
if (containers.Count < 2)
|
||||
{
|
||||
DebugConsole.ThrowError("Error while fabricating a new item: fabricators must have two ItemContainer components");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (Tuple<ItemPrefab, int> ip in fabricatedItem.RequiredItems)
|
||||
{
|
||||
for (int i = 0; i < ip.Item2; i++)
|
||||
{
|
||||
var requiredItem = containers[0].Inventory.Items.FirstOrDefault(it => it != null && it.Prefab == ip.Item1);
|
||||
if (requiredItem == null) continue;
|
||||
|
||||
Entity.Spawner.AddToRemoveQueue(requiredItem);
|
||||
containers[0].Inventory.RemoveItem(requiredItem);
|
||||
}
|
||||
}
|
||||
|
||||
if (containers[1].Inventory.Items.All(i => i != null))
|
||||
{
|
||||
Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, item.Position, item.Submarine);
|
||||
}
|
||||
else
|
||||
{
|
||||
Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, containers[1].Inventory);
|
||||
}
|
||||
|
||||
CancelFabricating(null);
|
||||
}
|
||||
|
||||
private bool CanBeFabricated(FabricableItem fabricableItem, Character user)
|
||||
{
|
||||
if (fabricableItem == null) return false;
|
||||
|
||||
if (user != null &&
|
||||
fabricableItem.RequiredSkills.Any(skill => user.GetSkillLevel(skill.Name) < skill.Level))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ItemContainer container = item.GetComponent<ItemContainer>();
|
||||
foreach (Tuple<ItemPrefab, int> ip in fabricableItem.RequiredItems)
|
||||
{
|
||||
if (Array.FindAll(container.Inventory.Items, it => it != null && it.Prefab == ip.Item1).Length < ip.Item2) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ServerRead(ClientNetObject type, NetBuffer msg, Client c)
|
||||
{
|
||||
int itemIndex = msg.ReadRangedInteger(-1, fabricableItems.Count - 1);
|
||||
|
||||
item.CreateServerEvent(this);
|
||||
|
||||
if (!item.CanClientAccess(c)) return;
|
||||
|
||||
if (itemIndex == -1)
|
||||
{
|
||||
CancelFabricating(c.Character);
|
||||
}
|
||||
else
|
||||
{
|
||||
//if already fabricating the selected item, return
|
||||
if (fabricatedItem != null && fabricableItems.IndexOf(fabricatedItem) == itemIndex) return;
|
||||
if (itemIndex < 0 || itemIndex >= fabricableItems.Count) return;
|
||||
|
||||
#if CLIENT
|
||||
SelectItem(null, fabricableItems[itemIndex]);
|
||||
#endif
|
||||
StartFabricating(fabricableItems[itemIndex], c.Character);
|
||||
}
|
||||
}
|
||||
|
||||
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
|
||||
{
|
||||
int itemIndex = fabricatedItem == null ? -1 : fabricableItems.IndexOf(fabricatedItem);
|
||||
msg.WriteRangedInteger(-1, fabricableItems.Count - 1, itemIndex);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class MiniMap : Powered
|
||||
{
|
||||
class HullData
|
||||
{
|
||||
public float? Oxygen;
|
||||
public float? Water;
|
||||
}
|
||||
|
||||
private DateTime resetDataTime;
|
||||
|
||||
bool hasPower;
|
||||
|
||||
[Editable, HasDefaultValue(false, true)]
|
||||
public bool RequireWaterDetectors
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Editable, HasDefaultValue(true, true)]
|
||||
public bool RequireOxygenDetectors
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Editable, HasDefaultValue(false, true)]
|
||||
public bool ShowHullIntegrity
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
|
||||
private Dictionary<Hull, HullData> hullDatas;
|
||||
|
||||
public MiniMap(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
IsActive = true;
|
||||
|
||||
hullDatas = new Dictionary<Hull, HullData>();
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
//periodically reset all hull data
|
||||
//(so that outdated hull info won't be shown if detectors stop sending signals)
|
||||
if (DateTime.Now > resetDataTime)
|
||||
{
|
||||
hullDatas.Clear();
|
||||
resetDataTime = DateTime.Now + new TimeSpan(0, 0, 1);
|
||||
}
|
||||
|
||||
currPowerConsumption = powerConsumption;
|
||||
|
||||
hasPower = voltage > minVoltage;
|
||||
|
||||
voltage = 0.0f;
|
||||
}
|
||||
|
||||
public override bool Pick(Character picker)
|
||||
{
|
||||
if (picker == null) return false;
|
||||
|
||||
//picker.SelectedConstruction = item;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0)
|
||||
{
|
||||
base.ReceiveSignal(stepsTaken, signal, connection, source, sender, power);
|
||||
|
||||
if (sender == null || sender.CurrentHull == null) return;
|
||||
|
||||
Hull senderHull = sender.CurrentHull;
|
||||
|
||||
HullData hullData;
|
||||
if (!hullDatas.TryGetValue(senderHull, out hullData))
|
||||
{
|
||||
hullData = new HullData();
|
||||
hullDatas.Add(senderHull, hullData);
|
||||
}
|
||||
|
||||
switch (connection.Name)
|
||||
{
|
||||
case "water_data_in":
|
||||
//cheating a bit because water detectors don't actually send the water level
|
||||
if (source.GetComponent<WaterDetector>() == null)
|
||||
{
|
||||
hullData.Water = Rand.Range(0.0f, 1.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
hullData.Water = Math.Min(senderHull.Volume / senderHull.FullVolume, 1.0f);
|
||||
}
|
||||
break;
|
||||
case "oxygen_data_in":
|
||||
float oxy;
|
||||
|
||||
if (!float.TryParse(signal, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out oxy))
|
||||
{
|
||||
oxy = Rand.Range(0.0f, 100.0f);
|
||||
}
|
||||
|
||||
hullData.Oxygen = oxy;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
class OxygenGenerator : Powered
|
||||
{
|
||||
PropertyTask powerUpTask;
|
||||
|
||||
float powerDownTimer;
|
||||
|
||||
bool running;
|
||||
|
||||
private float generatedAmount;
|
||||
|
||||
List<Vent> ventList;
|
||||
|
||||
private float totalHullVolume;
|
||||
|
||||
public bool IsRunning()
|
||||
{
|
||||
return (running && item.Condition>0.0f);
|
||||
}
|
||||
|
||||
public float CurrFlow
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Editable, HasDefaultValue(100.0f, true)]
|
||||
public float GeneratedAmount
|
||||
{
|
||||
get { return generatedAmount; }
|
||||
set { generatedAmount = MathHelper.Clamp(value, -10000.0f, 10000.0f); }
|
||||
}
|
||||
|
||||
public OxygenGenerator(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
IsActive = true;
|
||||
|
||||
//item.linkedTo.CollectionChanged += delegate { GetVents(); };
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
base.Update(deltaTime, cam);
|
||||
|
||||
CurrFlow = 0.0f;
|
||||
currPowerConsumption = powerConsumption;
|
||||
|
||||
if (item.CurrentHull == null) return;
|
||||
|
||||
if (voltage < minVoltage)
|
||||
{
|
||||
powerDownTimer += deltaTime;
|
||||
running = false;
|
||||
if ((powerUpTask==null || powerUpTask.IsFinished) && powerDownTimer>5.0f)
|
||||
{
|
||||
powerUpTask = new PropertyTask(item, IsRunning, 50.0f, "Turn on the oxygen generator");
|
||||
}
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
powerDownTimer = 0.0f;
|
||||
}
|
||||
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
|
||||
|
||||
running = true;
|
||||
|
||||
CurrFlow = Math.Min(voltage, 1.0f) * generatedAmount*100.0f;
|
||||
//item.CurrentHull.Oxygen += CurrFlow * deltaTime;
|
||||
|
||||
UpdateVents(CurrFlow);
|
||||
|
||||
voltage -= deltaTime;
|
||||
}
|
||||
|
||||
public override void UpdateBroken(float deltaTime, Camera cam)
|
||||
{
|
||||
powerDownTimer += deltaTime;
|
||||
}
|
||||
|
||||
private void GetVents()
|
||||
{
|
||||
ventList.Clear();
|
||||
|
||||
foreach (MapEntity entity in item.linkedTo)
|
||||
{
|
||||
Item linkedItem = entity as Item;
|
||||
if (linkedItem == null) continue;
|
||||
|
||||
Vent vent = linkedItem.GetComponent<Vent>();
|
||||
if (vent == null) continue;
|
||||
|
||||
ventList.Add(vent);
|
||||
if (linkedItem.CurrentHull!=null) totalHullVolume += linkedItem.CurrentHull.FullVolume;
|
||||
}
|
||||
}
|
||||
|
||||
//public override void OnMapLoaded()
|
||||
//{
|
||||
// GetVents();
|
||||
//}
|
||||
|
||||
private void UpdateVents(float deltaOxygen)
|
||||
{
|
||||
if (ventList == null)
|
||||
{
|
||||
ventList = new List<Vent>();
|
||||
GetVents();
|
||||
}
|
||||
|
||||
if (!ventList.Any() || totalHullVolume == 0.0f) return;
|
||||
|
||||
foreach (Vent v in ventList)
|
||||
{
|
||||
if (v.Item.CurrentHull == null) continue;
|
||||
|
||||
v.OxygenFlow = deltaOxygen * (v.Item.CurrentHull.FullVolume / totalHullVolume);
|
||||
v.IsActive = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using System.Globalization;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class Pump : Powered, IServerSerializable, IClientSerializable
|
||||
{
|
||||
private float flowPercentage;
|
||||
private float maxFlow;
|
||||
|
||||
private float? targetLevel;
|
||||
|
||||
public Hull hull1;
|
||||
|
||||
[HasDefaultValue(0.0f, true)]
|
||||
public float FlowPercentage
|
||||
{
|
||||
get { return flowPercentage; }
|
||||
set
|
||||
{
|
||||
if (!MathUtils.IsValid(flowPercentage)) return;
|
||||
flowPercentage = MathHelper.Clamp(value,-100.0f,100.0f);
|
||||
flowPercentage = MathUtils.Round(flowPercentage, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
[HasDefaultValue(80.0f, false)]
|
||||
public float MaxFlow
|
||||
{
|
||||
get { return maxFlow; }
|
||||
set { maxFlow = value; }
|
||||
}
|
||||
|
||||
float currFlow;
|
||||
public float CurrFlow
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!IsActive) return 0.0f;
|
||||
return Math.Abs(currFlow);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool IsActive
|
||||
{
|
||||
get
|
||||
{
|
||||
return base.IsActive;
|
||||
}
|
||||
set
|
||||
{
|
||||
base.IsActive = value;
|
||||
|
||||
#if CLIENT
|
||||
if (isActiveTickBox != null) isActiveTickBox.Selected = value;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public Pump(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
GetHull();
|
||||
|
||||
InitProjSpecific();
|
||||
}
|
||||
|
||||
partial void InitProjSpecific();
|
||||
|
||||
public override void Move(Vector2 amount)
|
||||
{
|
||||
base.Move(amount);
|
||||
|
||||
GetHull();
|
||||
}
|
||||
|
||||
public override void OnMapLoaded()
|
||||
{
|
||||
GetHull();
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
currFlow = 0.0f;
|
||||
|
||||
if (targetLevel != null)
|
||||
{
|
||||
float hullPercentage = 0.0f;
|
||||
if (hull1 != null) hullPercentage = (hull1.Volume / hull1.FullVolume) * 100.0f;
|
||||
FlowPercentage = ((float)targetLevel - hullPercentage) * 10.0f;
|
||||
}
|
||||
|
||||
currPowerConsumption = powerConsumption * Math.Abs(flowPercentage / 100.0f);
|
||||
|
||||
if (voltage < minVoltage) return;
|
||||
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
|
||||
|
||||
if (hull1 == null) return;
|
||||
|
||||
float powerFactor = (currPowerConsumption==0.0f) ? 1.0f : voltage;
|
||||
//flowPercentage = maxFlow * powerFactor;
|
||||
|
||||
currFlow = (flowPercentage / 100.0f) * maxFlow * powerFactor;
|
||||
|
||||
hull1.Volume += currFlow;
|
||||
if (hull1.Volume > hull1.FullVolume) hull1.Pressure += 0.5f;
|
||||
|
||||
//if (hull2 != null)
|
||||
//{
|
||||
// hull2.Volume -= currFlow;
|
||||
// if (hull2.Volume > hull1.FullVolume) hull2.Pressure += 0.5f;
|
||||
//}
|
||||
|
||||
voltage = 0.0f;
|
||||
}
|
||||
|
||||
private void GetHull()
|
||||
{
|
||||
hull1 = Hull.FindHull(item.WorldPosition, item.CurrentHull);
|
||||
}
|
||||
|
||||
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power=0.0f)
|
||||
{
|
||||
base.ReceiveSignal(stepsTaken, signal, connection, source, sender, power);
|
||||
|
||||
if (connection.Name == "toggle")
|
||||
{
|
||||
IsActive = !IsActive;
|
||||
}
|
||||
else if (connection.Name == "set_active")
|
||||
{
|
||||
IsActive = (signal != "0");
|
||||
}
|
||||
else if (connection.Name == "set_speed")
|
||||
{
|
||||
float tempSpeed;
|
||||
if (float.TryParse(signal, NumberStyles.Any, CultureInfo.InvariantCulture, out tempSpeed))
|
||||
{
|
||||
flowPercentage = MathHelper.Clamp(tempSpeed, -100.0f, 100.0f);
|
||||
}
|
||||
}
|
||||
else if (connection.Name == "set_targetlevel")
|
||||
{
|
||||
float tempTarget;
|
||||
if (float.TryParse(signal, NumberStyles.Any, CultureInfo.InvariantCulture, out tempTarget))
|
||||
{
|
||||
targetLevel = MathHelper.Clamp((tempTarget+100.0f)/2.0f, 0.0f, 100.0f);
|
||||
}
|
||||
}
|
||||
|
||||
if (!IsActive) currPowerConsumption = 0.0f;
|
||||
}
|
||||
|
||||
public void ServerRead(ClientNetObject type, Lidgren.Network.NetBuffer msg, Client c)
|
||||
{
|
||||
float newFlowPercentage = msg.ReadRangedInteger(-10, 10) * 10.0f;
|
||||
bool newIsActive = msg.ReadBoolean();
|
||||
|
||||
if (item.CanClientAccess(c))
|
||||
{
|
||||
if (newFlowPercentage != FlowPercentage)
|
||||
{
|
||||
GameServer.Log(c.Character + " set the pumping speed of " + item.Name + " to " + (int)(newFlowPercentage) + " %", ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
if (newIsActive != IsActive)
|
||||
{
|
||||
GameServer.Log(c.Character + (newIsActive ? " turned on " : " turned off ") + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
|
||||
FlowPercentage = newFlowPercentage;
|
||||
IsActive = newIsActive;
|
||||
}
|
||||
|
||||
//notify all clients of the changed state
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
|
||||
public void ServerWrite(Lidgren.Network.NetBuffer msg, Client c, object[] extraData = null)
|
||||
{
|
||||
//flowpercentage can only be adjusted at 10% intervals -> no need for more accuracy than this
|
||||
msg.WriteRangedInteger(-10, 10, (int)(flowPercentage / 10.0f));
|
||||
msg.Write(IsActive);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
using Barotrauma.Networking;
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
using Voronoi2;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class Radar : Powered, IServerSerializable, IClientSerializable
|
||||
{
|
||||
private float range;
|
||||
|
||||
private float pingState;
|
||||
|
||||
private readonly Sprite pingCircle, screenOverlay;
|
||||
|
||||
private readonly Sprite radarBlip;
|
||||
|
||||
private float prevPingRadius;
|
||||
|
||||
float prevPassivePingRadius;
|
||||
|
||||
private Vector2 center;
|
||||
private float displayRadius;
|
||||
private float displayScale;
|
||||
|
||||
private float displayBorderSize;
|
||||
|
||||
[HasDefaultValue(10000.0f, false)]
|
||||
public float Range
|
||||
{
|
||||
get { return range; }
|
||||
set { range = MathHelper.Clamp(value, 0.0f, 100000.0f); }
|
||||
}
|
||||
|
||||
[HasDefaultValue(false, false)]
|
||||
public bool DetectSubmarineWalls
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public override bool IsActive
|
||||
{
|
||||
get
|
||||
{
|
||||
return base.IsActive;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
base.IsActive = value;
|
||||
#if CLIENT
|
||||
if (isActiveTickBox != null) isActiveTickBox.Selected = value;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public Radar(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
#if CLIENT
|
||||
radarBlips = new List<RadarBlip>();
|
||||
#endif
|
||||
|
||||
displayBorderSize = ToolBox.GetAttributeFloat(element, "displaybordersize", 0.0f);
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "pingcircle":
|
||||
pingCircle = new Sprite(subElement);
|
||||
break;
|
||||
case "screenoverlay":
|
||||
screenOverlay = new Sprite(subElement);
|
||||
break;
|
||||
case "blip":
|
||||
radarBlip = new Sprite(subElement);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
isActiveTickBox = new GUITickBox(new Rectangle(0, 0, 20, 20), "Active Sonar", Alignment.TopLeft, GuiFrame);
|
||||
isActiveTickBox.OnSelected = (GUITickBox box) =>
|
||||
{
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
else if (GameMain.Client != null)
|
||||
{
|
||||
item.CreateClientEvent(this);
|
||||
correctionTimer = CorrectionDelay;
|
||||
}
|
||||
IsActive = box.Selected;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
GuiFrame.CanBeFocused = false;
|
||||
#endif
|
||||
|
||||
IsActive = false;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
currPowerConsumption = powerConsumption;
|
||||
|
||||
base.Update(deltaTime, cam);
|
||||
|
||||
if (voltage >= minVoltage || powerConsumption <= 0.0f)
|
||||
{
|
||||
pingState = pingState + deltaTime * 0.5f;
|
||||
if (pingState > 1.0f)
|
||||
{
|
||||
if (item.CurrentHull != null) item.CurrentHull.AiTarget.SoundRange = Math.Max(Range * pingState, item.CurrentHull.AiTarget.SoundRange);
|
||||
item.Use(deltaTime);
|
||||
pingState = 0.0f;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
pingState = 0.0f;
|
||||
}
|
||||
|
||||
Voltage -= deltaTime;
|
||||
}
|
||||
|
||||
public override bool Use(float deltaTime, Character character = null)
|
||||
{
|
||||
return pingState > 1.0f;
|
||||
}
|
||||
|
||||
protected override void RemoveComponentSpecific()
|
||||
{
|
||||
if (pingCircle!=null) pingCircle.Remove();
|
||||
if (screenOverlay != null) screenOverlay.Remove();
|
||||
}
|
||||
|
||||
public void ServerRead(ClientNetObject type, Lidgren.Network.NetBuffer msg, Barotrauma.Networking.Client c)
|
||||
{
|
||||
bool isActive = msg.ReadBoolean();
|
||||
|
||||
if (!item.CanClientAccess(c)) return;
|
||||
|
||||
IsActive = isActive;
|
||||
#if CLIENT
|
||||
isActiveTickBox.Selected = IsActive;
|
||||
#endif
|
||||
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
|
||||
public void ServerWrite(Lidgren.Network.NetBuffer msg, Barotrauma.Networking.Client c, object[] extraData = null)
|
||||
{
|
||||
msg.Write(IsActive);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,430 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
using Lidgren.Network;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class Reactor : Powered, IDrawableComponent, IServerSerializable, IClientSerializable
|
||||
{
|
||||
const float NetworkUpdateInterval = 0.5f;
|
||||
|
||||
//the rate at which the reactor is being run un
|
||||
//higher rates generate more power (and heat)
|
||||
private float fissionRate;
|
||||
|
||||
//the rate at which the heat is being dissipated
|
||||
private float coolingRate;
|
||||
|
||||
private float temperature;
|
||||
|
||||
//is automatic temperature control on
|
||||
//(adjusts the cooling rate automatically to keep the
|
||||
//amount of power generated balanced with the load)
|
||||
private bool autoTemp;
|
||||
|
||||
//the temperature after which fissionrate is automatically
|
||||
//turned down and cooling increased
|
||||
private float shutDownTemp;
|
||||
|
||||
private float fireTemp, meltDownTemp;
|
||||
|
||||
//how much power is provided to the grid per 1 temperature unit
|
||||
private float powerPerTemp;
|
||||
|
||||
private int graphSize = 25;
|
||||
|
||||
private float graphTimer;
|
||||
|
||||
private int updateGraphInterval = 500;
|
||||
|
||||
private float[] fissionRateGraph;
|
||||
private float[] coolingRateGraph;
|
||||
private float[] tempGraph;
|
||||
private float[] loadGraph;
|
||||
|
||||
private float load;
|
||||
|
||||
private PropertyTask powerUpTask;
|
||||
|
||||
private bool unsentChanges;
|
||||
private float sendUpdateTimer;
|
||||
|
||||
private Character lastUser;
|
||||
private float? nextServerLogWriteTime;
|
||||
private float lastServerLogWriteTime;
|
||||
|
||||
[Editable, HasDefaultValue(9500.0f, true)]
|
||||
public float MeltDownTemp
|
||||
{
|
||||
get { return meltDownTemp; }
|
||||
set
|
||||
{
|
||||
meltDownTemp = Math.Max(0.0f, value);
|
||||
}
|
||||
}
|
||||
|
||||
[Editable, HasDefaultValue(9000.0f, true)]
|
||||
public float FireTemp
|
||||
{
|
||||
get { return fireTemp; }
|
||||
set
|
||||
{
|
||||
fireTemp = Math.Max(0.0f, value);
|
||||
}
|
||||
}
|
||||
|
||||
[Editable, HasDefaultValue(1.0f, true)]
|
||||
public float PowerPerTemp
|
||||
{
|
||||
get { return powerPerTemp; }
|
||||
set
|
||||
{
|
||||
powerPerTemp = Math.Max(0.0f, value);
|
||||
}
|
||||
}
|
||||
|
||||
[HasDefaultValue(0.0f, true)]
|
||||
public float FissionRate
|
||||
{
|
||||
get { return fissionRate; }
|
||||
set
|
||||
{
|
||||
if (!MathUtils.IsValid(value)) return;
|
||||
fissionRate = MathHelper.Clamp(value, 0.0f, 100.0f);
|
||||
}
|
||||
}
|
||||
|
||||
[HasDefaultValue(0.0f, true)]
|
||||
public float CoolingRate
|
||||
{
|
||||
get { return coolingRate; }
|
||||
set
|
||||
{
|
||||
if (!MathUtils.IsValid(value)) return;
|
||||
coolingRate = MathHelper.Clamp(value, 0.0f, 100.0f);
|
||||
}
|
||||
}
|
||||
|
||||
[HasDefaultValue(0.0f, true)]
|
||||
public float Temperature
|
||||
{
|
||||
get { return temperature; }
|
||||
set
|
||||
{
|
||||
if (!MathUtils.IsValid(value)) return;
|
||||
temperature = MathHelper.Clamp(value, 0.0f, 10000.0f);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsRunning()
|
||||
{
|
||||
return (temperature > 0.0f);
|
||||
}
|
||||
|
||||
[HasDefaultValue(false, true)]
|
||||
public bool AutoTemp
|
||||
{
|
||||
get { return autoTemp; }
|
||||
set
|
||||
{
|
||||
autoTemp = value;
|
||||
#if CLIENT
|
||||
if (autoTempTickBox!=null) autoTempTickBox.Selected = value;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public float ExtraCooling { get; set; }
|
||||
|
||||
public float AvailableFuel { get; set; }
|
||||
|
||||
[HasDefaultValue(500.0f, true)]
|
||||
public float ShutDownTemp
|
||||
{
|
||||
get { return shutDownTemp; }
|
||||
set { shutDownTemp = MathHelper.Clamp(value, 0.0f, 10000.0f); }
|
||||
}
|
||||
|
||||
public Reactor(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
fissionRateGraph = new float[graphSize];
|
||||
coolingRateGraph = new float[graphSize];
|
||||
tempGraph = new float[graphSize];
|
||||
loadGraph = new float[graphSize];
|
||||
|
||||
shutDownTemp = 500.0f;
|
||||
|
||||
powerPerTemp = 1.0f;
|
||||
|
||||
IsActive = true;
|
||||
|
||||
InitProjSpecific();
|
||||
}
|
||||
|
||||
partial void InitProjSpecific();
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
if (GameMain.Server != null && nextServerLogWriteTime != null)
|
||||
{
|
||||
if (Timing.TotalTime >= (float)nextServerLogWriteTime)
|
||||
{
|
||||
GameServer.Log(lastUser + " adjusted reactor settings: " +
|
||||
"Temperature: " + (int)temperature +
|
||||
", Fission rate: " + (int)fissionRate +
|
||||
", Cooling rate: " + (int)coolingRate +
|
||||
", Cooling rate: " + coolingRate +
|
||||
", Shutdown temp: " + shutDownTemp +
|
||||
(autoTemp ? ", Autotemp ON" : ", Autotemp OFF"),
|
||||
ServerLog.MessageType.ItemInteraction);
|
||||
|
||||
nextServerLogWriteTime = null;
|
||||
lastServerLogWriteTime = (float)Timing.TotalTime;
|
||||
}
|
||||
}
|
||||
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
|
||||
|
||||
fissionRate = Math.Min(fissionRate, AvailableFuel);
|
||||
|
||||
float heat = 80 * fissionRate * (AvailableFuel/2000.0f);
|
||||
float heatDissipation = 50 * coolingRate + Math.Max(ExtraCooling, 5.0f);
|
||||
|
||||
float deltaTemp = (((heat - heatDissipation) * 5) - temperature) / 10000.0f;
|
||||
Temperature = temperature + deltaTemp;
|
||||
|
||||
if (temperature>fireTemp && temperature-deltaTemp<fireTemp)
|
||||
{
|
||||
#if CLIENT
|
||||
Vector2 baseVel = Rand.Vector(300.0f);
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
var particle = GameMain.ParticleManager.CreateParticle("spark", item.WorldPosition,
|
||||
baseVel + Rand.Vector(100.0f), 0.0f, item.CurrentHull);
|
||||
|
||||
if (particle != null) particle.Size *= Rand.Range(0.5f, 1.0f);
|
||||
}
|
||||
#endif
|
||||
|
||||
new FireSource(item.WorldPosition);
|
||||
}
|
||||
|
||||
if (temperature > meltDownTemp)
|
||||
{
|
||||
MeltDown();
|
||||
return;
|
||||
}
|
||||
else if (temperature == 0.0f)
|
||||
{
|
||||
if (powerUpTask == null || powerUpTask.IsFinished)
|
||||
{
|
||||
powerUpTask = new PropertyTask(item, IsRunning, 50.0f, "Power up the reactor");
|
||||
}
|
||||
}
|
||||
|
||||
load = 0.0f;
|
||||
|
||||
List<Connection> connections = item.Connections;
|
||||
if (connections != null && connections.Count > 0)
|
||||
{
|
||||
foreach (Connection connection in connections)
|
||||
{
|
||||
if (!connection.IsPower) continue;
|
||||
foreach (Connection recipient in connection.Recipients)
|
||||
{
|
||||
Item it = recipient.Item as Item;
|
||||
if (it == null) continue;
|
||||
|
||||
PowerTransfer pt = it.GetComponent<PowerTransfer>();
|
||||
if (pt == null) continue;
|
||||
|
||||
load = Math.Max(load,pt.PowerLoad);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//item.Condition -= temperature * deltaTime * 0.00005f;
|
||||
|
||||
if (temperature > shutDownTemp)
|
||||
{
|
||||
CoolingRate += 0.5f;
|
||||
FissionRate -= 0.5f;
|
||||
}
|
||||
else if (autoTemp)
|
||||
{
|
||||
//take deltaTemp into account to slow down the change in temperature when getting closer to the desired value
|
||||
float target = temperature + deltaTemp * 100.0f;
|
||||
|
||||
//-1.0f in order to gradually turn down both rates when the target temperature is reached
|
||||
FissionRate += (MathHelper.Clamp(load - target, -10.0f, 10.0f) - 1.0f) * deltaTime;
|
||||
CoolingRate += (MathHelper.Clamp(target - load, -5.0f, 5.0f) - 1.0f) * deltaTime;
|
||||
}
|
||||
|
||||
//the power generated by the reactor is equal to the temperature
|
||||
currPowerConsumption = -temperature*powerPerTemp;
|
||||
|
||||
if (item.CurrentHull != null)
|
||||
{
|
||||
//the sound can be heard from 20 000 display units away when running at full power
|
||||
item.CurrentHull.SoundRange = Math.Max(temperature * 2, item.CurrentHull.AiTarget.SoundRange);
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
UpdateGraph(deltaTime);
|
||||
#endif
|
||||
|
||||
ExtraCooling = 0.0f;
|
||||
AvailableFuel = 0.0f;
|
||||
|
||||
item.SendSignal(0, ((int)temperature).ToString(), "temperature_out", null);
|
||||
|
||||
sendUpdateTimer = Math.Max(sendUpdateTimer - deltaTime, 0.0f);
|
||||
|
||||
if (unsentChanges && sendUpdateTimer<= 0.0f)
|
||||
{
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
#if CLIENT
|
||||
else if (GameMain.Client != null)
|
||||
{
|
||||
item.CreateClientEvent(this);
|
||||
}
|
||||
#endif
|
||||
|
||||
sendUpdateTimer = NetworkUpdateInterval;
|
||||
unsentChanges = false;
|
||||
}
|
||||
}
|
||||
|
||||
public override void UpdateBroken(float deltaTime, Camera cam)
|
||||
{
|
||||
base.UpdateBroken(deltaTime, cam);
|
||||
|
||||
Temperature -= deltaTime * 1000.0f;
|
||||
FissionRate -= deltaTime * 10.0f;
|
||||
CoolingRate -= deltaTime * 10.0f;
|
||||
|
||||
currPowerConsumption = -temperature;
|
||||
|
||||
#if CLIENT
|
||||
UpdateGraph(deltaTime);
|
||||
#endif
|
||||
|
||||
ExtraCooling = 0.0f;
|
||||
}
|
||||
|
||||
private void MeltDown()
|
||||
{
|
||||
if (item.Condition <= 0.0f) return;
|
||||
|
||||
GameServer.Log("Reactor meltdown!", ServerLog.MessageType.ItemInteraction);
|
||||
|
||||
new RepairTask(item, 60.0f, "Reactor meltdown!");
|
||||
item.Condition = 0.0f;
|
||||
|
||||
var containedItems = item.ContainedItems;
|
||||
if (containedItems == null) return;
|
||||
|
||||
foreach (Item containedItem in item.ContainedItems)
|
||||
{
|
||||
if (containedItem == null) continue;
|
||||
containedItem.Condition = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Pick(Character picker)
|
||||
{
|
||||
return picker != null;
|
||||
}
|
||||
|
||||
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
|
||||
{
|
||||
switch (objective.Option.ToLowerInvariant())
|
||||
{
|
||||
case "power up":
|
||||
float tempDiff = load - temperature;
|
||||
|
||||
shutDownTemp = Math.Min(load + 1000.0f, 7500.0f);
|
||||
|
||||
//temperature too high/low
|
||||
if (Math.Abs(tempDiff)>500.0f)
|
||||
{
|
||||
AutoTemp = false;
|
||||
FissionRate += deltaTime * 100.0f * Math.Sign(tempDiff);
|
||||
CoolingRate -= deltaTime * 100.0f * Math.Sign(tempDiff);
|
||||
}
|
||||
//temperature OK
|
||||
else
|
||||
{
|
||||
AutoTemp = true;
|
||||
}
|
||||
|
||||
break;
|
||||
case "shutdown":
|
||||
|
||||
shutDownTemp = 0.0f;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power)
|
||||
{
|
||||
switch (connection.Name)
|
||||
{
|
||||
case "shutdown":
|
||||
if (shutDownTemp > 0.0f)
|
||||
{
|
||||
unsentChanges = true;
|
||||
shutDownTemp = 0.0f;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void ServerRead(ClientNetObject type, NetBuffer msg, Client c)
|
||||
{
|
||||
bool autoTemp = msg.ReadBoolean();
|
||||
float shutDownTemp = msg.ReadRangedSingle(0.0f, 10000.0f, 15);
|
||||
float coolingRate = msg.ReadRangedSingle(0.0f, 100.0f, 8);
|
||||
float fissionRate = msg.ReadRangedSingle(0.0f, 100.0f, 8);
|
||||
|
||||
if (!item.CanClientAccess(c)) return;
|
||||
|
||||
AutoTemp = autoTemp;
|
||||
ShutDownTemp = shutDownTemp;
|
||||
|
||||
CoolingRate = coolingRate;
|
||||
FissionRate = fissionRate;
|
||||
|
||||
lastUser = c.Character;
|
||||
if (nextServerLogWriteTime == null)
|
||||
{
|
||||
nextServerLogWriteTime = Math.Max(lastServerLogWriteTime + 1.0f, (float)Timing.TotalTime);
|
||||
}
|
||||
|
||||
//need to create a server event to notify all clients of the changed state
|
||||
unsentChanges = true;
|
||||
}
|
||||
|
||||
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
|
||||
{
|
||||
msg.WriteRangedSingle(temperature, 0.0f, 10000.0f, 16);
|
||||
|
||||
msg.Write(autoTemp);
|
||||
msg.WriteRangedSingle(shutDownTemp, 0.0f, 10000.0f, 15);
|
||||
|
||||
msg.WriteRangedSingle(coolingRate, 0.0f, 100.0f, 8);
|
||||
msg.WriteRangedSingle(fissionRate, 0.0f, 100.0f, 8);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,434 @@
|
||||
using Barotrauma.Networking;
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
using Voronoi2;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class Steering : Powered, IServerSerializable, IClientSerializable
|
||||
{
|
||||
private const float AutopilotRayCastInterval = 0.5f;
|
||||
|
||||
private Vector2 currVelocity;
|
||||
private Vector2 targetVelocity;
|
||||
|
||||
private bool autoPilot;
|
||||
|
||||
private Vector2? posToMaintain;
|
||||
|
||||
private SteeringPath steeringPath;
|
||||
|
||||
private PathFinder pathFinder;
|
||||
|
||||
private float networkUpdateTimer;
|
||||
private bool unsentChanges;
|
||||
|
||||
private float autopilotRayCastTimer;
|
||||
|
||||
private Vector2 avoidStrength;
|
||||
|
||||
private float neutralBallastLevel;
|
||||
|
||||
public bool AutoPilot
|
||||
{
|
||||
get { return autoPilot; }
|
||||
set
|
||||
{
|
||||
if (value == autoPilot) return;
|
||||
|
||||
autoPilot = value;
|
||||
#if CLIENT
|
||||
autopilotTickBox.Selected = value;
|
||||
|
||||
maintainPosTickBox.Enabled = autoPilot;
|
||||
levelEndTickBox.Enabled = autoPilot;
|
||||
levelStartTickBox.Enabled = autoPilot;
|
||||
#endif
|
||||
if (autoPilot)
|
||||
{
|
||||
if (pathFinder == null) pathFinder = new PathFinder(WayPoint.WayPointList, false);
|
||||
#if CLIENT
|
||||
ToggleMaintainPosition(maintainPosTickBox);
|
||||
#endif
|
||||
}
|
||||
#if CLIENT
|
||||
else
|
||||
{
|
||||
maintainPosTickBox.Selected = false;
|
||||
levelEndTickBox.Selected = false;
|
||||
levelStartTickBox.Selected = false;
|
||||
|
||||
posToMaintain = null;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
[Editable, HasDefaultValue(0.5f, true)]
|
||||
public float NeutralBallastLevel
|
||||
{
|
||||
get { return neutralBallastLevel; }
|
||||
set
|
||||
{
|
||||
neutralBallastLevel = MathHelper.Clamp(value, 0.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2 TargetVelocity
|
||||
{
|
||||
get { return targetVelocity;}
|
||||
set
|
||||
{
|
||||
if (!MathUtils.IsValid(value)) return;
|
||||
targetVelocity.X = MathHelper.Clamp(value.X, -100.0f, 100.0f);
|
||||
targetVelocity.Y = MathHelper.Clamp(value.Y, -100.0f, 100.0f);
|
||||
}
|
||||
}
|
||||
|
||||
public SteeringPath SteeringPath
|
||||
{
|
||||
get { return steeringPath; }
|
||||
}
|
||||
|
||||
public Steering(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
IsActive = true;
|
||||
|
||||
InitProjSpecific();
|
||||
}
|
||||
|
||||
partial void InitProjSpecific();
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
if (unsentChanges)
|
||||
{
|
||||
networkUpdateTimer -= deltaTime;
|
||||
if (networkUpdateTimer <= 0.0f)
|
||||
{
|
||||
#if CLIENT
|
||||
if (GameMain.Client != null)
|
||||
{
|
||||
item.CreateClientEvent(this);
|
||||
correctionTimer = CorrectionDelay;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
|
||||
networkUpdateTimer = 0.5f;
|
||||
unsentChanges = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (voltage < minVoltage && powerConsumption > 0.0f) return;
|
||||
|
||||
if (autoPilot)
|
||||
{
|
||||
UpdateAutoPilot(deltaTime);
|
||||
}
|
||||
|
||||
item.SendSignal(0, targetVelocity.X.ToString(CultureInfo.InvariantCulture), "velocity_x_out", null);
|
||||
|
||||
float targetLevel = -targetVelocity.Y;
|
||||
|
||||
targetLevel += (neutralBallastLevel - 0.5f) * 100.0f;
|
||||
|
||||
item.SendSignal(0, targetLevel.ToString(CultureInfo.InvariantCulture), "velocity_y_out", null);
|
||||
|
||||
|
||||
voltage -= deltaTime;
|
||||
}
|
||||
|
||||
private void UpdateAutoPilot(float deltaTime)
|
||||
{
|
||||
if (posToMaintain != null)
|
||||
{
|
||||
SteerTowardsPosition((Vector2)posToMaintain);
|
||||
return;
|
||||
}
|
||||
|
||||
autopilotRayCastTimer -= deltaTime;
|
||||
|
||||
steeringPath.CheckProgress(ConvertUnits.ToSimUnits(item.Submarine.WorldPosition), 10.0f);
|
||||
|
||||
if (autopilotRayCastTimer <= 0.0f && steeringPath.NextNode != null)
|
||||
{
|
||||
Vector2 diff = Vector2.Normalize(ConvertUnits.ToSimUnits(steeringPath.NextNode.Position - item.Submarine.WorldPosition));
|
||||
|
||||
bool nextVisible = true;
|
||||
for (int x = -1; x < 2; x += 2)
|
||||
{
|
||||
for (int y = -1; y < 2; y += 2)
|
||||
{
|
||||
Vector2 cornerPos =
|
||||
new Vector2(item.Submarine.Borders.Width * x, item.Submarine.Borders.Height * y) / 2.0f;
|
||||
|
||||
cornerPos = ConvertUnits.ToSimUnits(cornerPos * 1.2f + item.Submarine.WorldPosition);
|
||||
|
||||
float dist = Vector2.Distance(cornerPos, steeringPath.NextNode.SimPosition);
|
||||
|
||||
if (Submarine.PickBody(cornerPos, cornerPos + diff * dist, null, Physics.CollisionLevel) == null) continue;
|
||||
|
||||
nextVisible = false;
|
||||
x = 2;
|
||||
y = 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (nextVisible) steeringPath.SkipToNextNode();
|
||||
|
||||
autopilotRayCastTimer = AutopilotRayCastInterval;
|
||||
}
|
||||
|
||||
if (steeringPath.CurrentNode != null)
|
||||
{
|
||||
SteerTowardsPosition(steeringPath.CurrentNode.WorldPosition);
|
||||
}
|
||||
|
||||
float avoidRadius = Math.Max(item.Submarine.Borders.Width, item.Submarine.Borders.Height) * 2.0f;
|
||||
avoidRadius = Math.Max(avoidRadius, 2000.0f);
|
||||
|
||||
Vector2 newAvoidStrength = Vector2.Zero;
|
||||
|
||||
//steer away from nearby walls
|
||||
var closeCells = Level.Loaded.GetCells(item.Submarine.WorldPosition, 4);
|
||||
foreach (VoronoiCell cell in closeCells)
|
||||
{
|
||||
foreach (GraphEdge edge in cell.edges)
|
||||
{
|
||||
var intersection = MathUtils.GetLineIntersection(edge.point1, edge.point2, item.Submarine.WorldPosition, cell.Center);
|
||||
if (intersection != null)
|
||||
{
|
||||
Vector2 diff = item.Submarine.WorldPosition - (Vector2)intersection;
|
||||
|
||||
//far enough -> ignore
|
||||
if (diff.Length() > avoidRadius) continue;
|
||||
|
||||
float dot = item.Submarine.Velocity == Vector2.Zero ?
|
||||
0.0f : Vector2.Dot(item.Submarine.Velocity, -Vector2.Normalize(diff));
|
||||
|
||||
//not heading towards the wall -> ignore
|
||||
if (dot < 0.5) continue;
|
||||
|
||||
Vector2 change = (Vector2.Normalize(diff) * Math.Max((avoidRadius - diff.Length()), 0.0f)) / avoidRadius;
|
||||
|
||||
newAvoidStrength += change * dot;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
avoidStrength = Vector2.Lerp(avoidStrength, newAvoidStrength, deltaTime * 10.0f);
|
||||
|
||||
targetVelocity += avoidStrength * 100.0f;
|
||||
|
||||
//steer away from other subs
|
||||
foreach (Submarine sub in Submarine.Loaded)
|
||||
{
|
||||
if (sub == item.Submarine) continue;
|
||||
if (item.Submarine.DockedTo.Contains(sub)) continue;
|
||||
|
||||
float thisSize = Math.Max(item.Submarine.Borders.Width, item.Submarine.Borders.Height);
|
||||
float otherSize = Math.Max(sub.Borders.Width, sub.Borders.Height);
|
||||
|
||||
Vector2 diff = item.Submarine.WorldPosition - sub.WorldPosition;
|
||||
|
||||
float dist = diff == Vector2.Zero ? 0.0f : diff.Length();
|
||||
|
||||
//far enough -> ignore
|
||||
if (dist > thisSize + otherSize) continue;
|
||||
|
||||
diff = Vector2.Normalize(diff);
|
||||
|
||||
float dot = item.Submarine.Velocity == Vector2.Zero ?
|
||||
0.0f : Vector2.Dot(Vector2.Normalize(item.Submarine.Velocity), -Vector2.Normalize(diff));
|
||||
|
||||
//heading away -> ignore
|
||||
if (dot < 0.0f) continue;
|
||||
|
||||
targetVelocity += diff * 200.0f;
|
||||
}
|
||||
|
||||
//clamp velocity magnitude to 100.0f
|
||||
float velMagnitude = targetVelocity.Length();
|
||||
if (velMagnitude > 100.0f)
|
||||
{
|
||||
targetVelocity *= 100.0f / velMagnitude;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void UpdatePath()
|
||||
{
|
||||
if (pathFinder == null) pathFinder = new PathFinder(WayPoint.WayPointList, false);
|
||||
|
||||
Vector2 target;
|
||||
if (LevelEndSelected)
|
||||
{
|
||||
target = ConvertUnits.ToSimUnits(Level.Loaded.EndPosition);
|
||||
}
|
||||
else
|
||||
{
|
||||
target = ConvertUnits.ToSimUnits(Level.Loaded.StartPosition);
|
||||
}
|
||||
|
||||
|
||||
steeringPath = pathFinder.FindPath(ConvertUnits.ToSimUnits(item.WorldPosition), target);
|
||||
}
|
||||
|
||||
public void SetDestinationLevelStart()
|
||||
{
|
||||
AutoPilot = true;
|
||||
|
||||
MaintainPos = false;
|
||||
posToMaintain = null;
|
||||
|
||||
LevelEndSelected = false;
|
||||
|
||||
if (!LevelStartSelected)
|
||||
{
|
||||
LevelStartSelected = true;
|
||||
UpdatePath();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetDestinationLevelEnd()
|
||||
{
|
||||
AutoPilot = false;
|
||||
|
||||
MaintainPos = false;
|
||||
posToMaintain = null;
|
||||
|
||||
LevelStartSelected = false;
|
||||
|
||||
if (!LevelEndSelected)
|
||||
{
|
||||
LevelEndSelected = true;
|
||||
UpdatePath();
|
||||
}
|
||||
}
|
||||
private void SteerTowardsPosition(Vector2 worldPosition)
|
||||
{
|
||||
float prediction = 10.0f;
|
||||
|
||||
Vector2 futurePosition = ConvertUnits.ToDisplayUnits(item.Submarine.Velocity) * prediction;
|
||||
Vector2 targetSpeed = ((worldPosition - item.Submarine.WorldPosition) - futurePosition);
|
||||
|
||||
if (targetSpeed.Length()>500.0f)
|
||||
{
|
||||
targetSpeed = Vector2.Normalize(targetSpeed);
|
||||
TargetVelocity = targetSpeed * 100.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
TargetVelocity = targetSpeed / 5.0f;
|
||||
}
|
||||
}
|
||||
|
||||
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power=0.0f)
|
||||
{
|
||||
if (connection.Name == "velocity_in")
|
||||
{
|
||||
currVelocity = ToolBox.ParseToVector2(signal, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
base.ReceiveSignal(stepsTaken, signal, connection, source, sender, power);
|
||||
}
|
||||
}
|
||||
|
||||
public void ServerRead(ClientNetObject type, Lidgren.Network.NetBuffer msg, Barotrauma.Networking.Client c)
|
||||
{
|
||||
bool autoPilot = msg.ReadBoolean();
|
||||
Vector2 newTargetVelocity = targetVelocity;
|
||||
bool maintainPos = false;
|
||||
Vector2? newPosToMaintain = null;
|
||||
bool headingToStart = false;
|
||||
|
||||
if (autoPilot)
|
||||
{
|
||||
maintainPos = msg.ReadBoolean();
|
||||
if (maintainPos)
|
||||
{
|
||||
newPosToMaintain = new Vector2(
|
||||
msg.ReadFloat(),
|
||||
msg.ReadFloat());
|
||||
}
|
||||
else
|
||||
{
|
||||
headingToStart = msg.ReadBoolean();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
newTargetVelocity = new Vector2(msg.ReadFloat(), msg.ReadFloat());
|
||||
}
|
||||
|
||||
if (!item.CanClientAccess(c)) return;
|
||||
|
||||
AutoPilot = autoPilot;
|
||||
|
||||
if (!AutoPilot)
|
||||
{
|
||||
targetVelocity = newTargetVelocity;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
MaintainPos = newPosToMaintain != null;
|
||||
posToMaintain = newPosToMaintain;
|
||||
|
||||
if (posToMaintain == null)
|
||||
{
|
||||
LevelStartSelected = headingToStart;
|
||||
LevelEndSelected = !headingToStart;
|
||||
UpdatePath();
|
||||
}
|
||||
else
|
||||
{
|
||||
LevelStartSelected = false;
|
||||
LevelEndSelected = false;
|
||||
}
|
||||
}
|
||||
|
||||
//notify all clients of the changed state
|
||||
unsentChanges = true;
|
||||
}
|
||||
|
||||
public void ServerWrite(Lidgren.Network.NetBuffer msg, Barotrauma.Networking.Client c, object[] extraData = null)
|
||||
{
|
||||
msg.Write(autoPilot);
|
||||
|
||||
if (!autoPilot)
|
||||
{
|
||||
//no need to write steering info if autopilot is controlling
|
||||
msg.Write(targetVelocity.X);
|
||||
msg.Write(targetVelocity.Y);
|
||||
}
|
||||
else
|
||||
{
|
||||
msg.Write(posToMaintain != null);
|
||||
if (posToMaintain != null)
|
||||
{
|
||||
msg.Write(((Vector2)posToMaintain).X);
|
||||
msg.Write(((Vector2)posToMaintain).Y);
|
||||
}
|
||||
else
|
||||
{
|
||||
msg.Write(LevelStartSelected);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
class Vent : ItemComponent
|
||||
{
|
||||
private float oxygenFlow;
|
||||
|
||||
public float OxygenFlow
|
||||
{
|
||||
get { return oxygenFlow; }
|
||||
set { oxygenFlow = Math.Max(value, 0.0f); }
|
||||
}
|
||||
|
||||
public Vent (Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
if (item.CurrentHull == null) return;
|
||||
|
||||
if (item.InWater) return;
|
||||
|
||||
item.CurrentHull.Oxygen += oxygenFlow * deltaTime;
|
||||
OxygenFlow -= deltaTime * 1000.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Barotrauma.Networking;
|
||||
using Lidgren.Network;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class PowerContainer : Powered, IDrawableComponent, IServerSerializable, IClientSerializable
|
||||
{
|
||||
//[power/min]
|
||||
private float capacity;
|
||||
|
||||
private float charge;
|
||||
|
||||
private float rechargeVoltage, outputVoltage;
|
||||
|
||||
//how fast the battery can be recharged
|
||||
private float maxRechargeSpeed;
|
||||
|
||||
//how fast it's currently being recharged (can be changed, so that
|
||||
//charging can be slowed down or disabled if there's a shortage of power)
|
||||
private float rechargeSpeed;
|
||||
|
||||
private float maxOutput;
|
||||
|
||||
private float lastSentCharge;
|
||||
|
||||
public float CurrPowerOutput
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Editable, HasDefaultValue(10.0f, true)]
|
||||
public float MaxOutPut
|
||||
{
|
||||
set { maxOutput = value; }
|
||||
get { return maxOutput; }
|
||||
}
|
||||
|
||||
[HasDefaultValue(10.0f, true), Editable]
|
||||
public float Capacity
|
||||
{
|
||||
get { return capacity; }
|
||||
set { capacity = Math.Max(value, 1.0f); }
|
||||
}
|
||||
|
||||
[Editable, HasDefaultValue(0.0f, true)]
|
||||
public float Charge
|
||||
{
|
||||
get { return charge; }
|
||||
set
|
||||
{
|
||||
if (!MathUtils.IsValid(value)) return;
|
||||
charge = MathHelper.Clamp(value, 0.0f, capacity);
|
||||
|
||||
if (Math.Abs(charge - lastSentCharge) / capacity > 1.0f)
|
||||
{
|
||||
if (GameMain.Server != null) item.CreateServerEvent(this);
|
||||
lastSentCharge = charge;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[HasDefaultValue(10.0f, true), Editable]
|
||||
public float RechargeSpeed
|
||||
{
|
||||
get { return rechargeSpeed; }
|
||||
set
|
||||
{
|
||||
if (!MathUtils.IsValid(value)) return;
|
||||
rechargeSpeed = MathHelper.Clamp(value, 0.0f, maxRechargeSpeed);
|
||||
rechargeSpeed = MathUtils.RoundTowardsClosest(rechargeSpeed, Math.Max(maxRechargeSpeed * 0.1f, 1.0f));
|
||||
}
|
||||
}
|
||||
|
||||
[HasDefaultValue(10.0f, false), Editable]
|
||||
public float MaxRechargeSpeed
|
||||
{
|
||||
get { return maxRechargeSpeed; }
|
||||
set { maxRechargeSpeed = Math.Max(value, 1.0f); }
|
||||
}
|
||||
|
||||
public PowerContainer(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
//capacity = ToolBox.GetAttributeFloat(element, "capacity", 10.0f);
|
||||
//maxRechargeSpeed = ToolBox.GetAttributeFloat(element, "maxinput", 10.0f);
|
||||
//maxOutput = ToolBox.GetAttributeFloat(element, "maxoutput", 10.0f);
|
||||
|
||||
IsActive = true;
|
||||
|
||||
InitProjSpecific();
|
||||
}
|
||||
|
||||
partial void InitProjSpecific();
|
||||
|
||||
public override bool Pick(Character picker)
|
||||
{
|
||||
if (picker == null) return false;
|
||||
|
||||
//picker.SelectedConstruction = (picker.SelectedConstruction == item) ? null : item;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
float chargeRatio = (float)(Math.Sqrt(charge / capacity));
|
||||
float gridPower = 0.0f;
|
||||
float gridLoad = 0.0f;
|
||||
|
||||
//if (item.linkedTo.Count == 0) return;
|
||||
|
||||
foreach (Connection c in item.Connections)
|
||||
{
|
||||
if (c.Name == "power_in") continue;
|
||||
foreach (Connection c2 in c.Recipients)
|
||||
{
|
||||
PowerTransfer pt = c2.Item.GetComponent<PowerTransfer>();
|
||||
if (pt == null || !pt.IsActive) continue;
|
||||
|
||||
gridLoad += pt.PowerLoad;
|
||||
gridPower -= pt.CurrPowerConsumption;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//float gridRate = voltage;
|
||||
|
||||
if (chargeRatio > 0.0f)
|
||||
{
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
|
||||
}
|
||||
|
||||
//recharge
|
||||
//if (gridRate >= chargeRate)
|
||||
//{
|
||||
if (charge >= capacity)
|
||||
{
|
||||
rechargeVoltage = 0.0f;
|
||||
charge = capacity;
|
||||
|
||||
CurrPowerConsumption = 0.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
currPowerConsumption = MathHelper.Lerp(currPowerConsumption, rechargeSpeed, 0.05f);
|
||||
Charge += currPowerConsumption * rechargeVoltage / 3600.0f;
|
||||
}
|
||||
|
||||
//}
|
||||
|
||||
//provide power to the grid
|
||||
if (gridLoad > 0.0f)
|
||||
{
|
||||
if (charge <= 0.0f)
|
||||
{
|
||||
CurrPowerOutput = 0.0f;
|
||||
charge = 0.0f;
|
||||
return;
|
||||
}
|
||||
|
||||
if (gridPower < gridLoad)
|
||||
{
|
||||
CurrPowerOutput = MathHelper.Lerp(
|
||||
CurrPowerOutput,
|
||||
Math.Min(maxOutput * chargeRatio, gridLoad),
|
||||
deltaTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrPowerOutput = MathHelper.Lerp(CurrPowerOutput, 0.0f, deltaTime);
|
||||
}
|
||||
|
||||
Charge -= CurrPowerOutput / 3600.0f;
|
||||
}
|
||||
|
||||
rechargeVoltage = 0.0f;
|
||||
outputVoltage = 0.0f;
|
||||
}
|
||||
|
||||
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
|
||||
{
|
||||
RechargeSpeed = maxRechargeSpeed * 0.5f;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power)
|
||||
{
|
||||
if (!connection.IsPower) return;
|
||||
|
||||
if (connection.Name == "power_in")
|
||||
{
|
||||
rechargeVoltage = power;
|
||||
}
|
||||
else
|
||||
{
|
||||
outputVoltage = power;
|
||||
}
|
||||
}
|
||||
|
||||
public void ServerRead(ClientNetObject type, NetBuffer msg, Client c)
|
||||
{
|
||||
float newRechargeSpeed = msg.ReadRangedInteger(0,10) / 10.0f * maxRechargeSpeed;
|
||||
|
||||
if (item.CanClientAccess(c))
|
||||
{
|
||||
RechargeSpeed = newRechargeSpeed;
|
||||
GameServer.Log(c.Character + " set the recharge speed of "+item.Name+" to "+ (int)((rechargeSpeed / maxRechargeSpeed) * 100.0f) + " %", ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
|
||||
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
|
||||
{
|
||||
msg.WriteRangedInteger(0, 10, (int)(rechargeSpeed / MaxRechargeSpeed * 10));
|
||||
|
||||
float chargeRatio = MathHelper.Clamp(charge / capacity, 0.0f, 1.0f);
|
||||
msg.WriteRangedSingle(chargeRatio, 0.0f, 1.0f, 8);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class PowerTransfer : Powered
|
||||
{
|
||||
static float fullPower;
|
||||
static float fullLoad;
|
||||
|
||||
//private bool updated;
|
||||
|
||||
private int updateTimer;
|
||||
|
||||
const float FireProbability = 0.15f;
|
||||
|
||||
//affects how fast changes in power/load are carried over the grid
|
||||
static float inertia = 5.0f;
|
||||
|
||||
static HashSet<Powered> connectedList = new HashSet<Powered>();
|
||||
|
||||
private List<Connection> powerConnections;
|
||||
|
||||
private float powerLoad;
|
||||
|
||||
public float PowerLoad
|
||||
{
|
||||
get { return powerLoad; }
|
||||
}
|
||||
|
||||
public PowerTransfer(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
IsActive = true;
|
||||
|
||||
powerConnections = new List<Connection>();
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
if (updateTimer > 0)
|
||||
{
|
||||
//this junction box has already been updated this frame
|
||||
updateTimer--;
|
||||
return;
|
||||
}
|
||||
|
||||
//reset and recalculate the power generated/consumed
|
||||
//by the constructions connected to the grid
|
||||
fullPower = 0.0f;
|
||||
fullLoad = 0.0f;
|
||||
|
||||
connectedList.Clear();
|
||||
|
||||
CheckJunctions(deltaTime);
|
||||
updateTimer = 0;
|
||||
|
||||
foreach (Powered p in connectedList)
|
||||
{
|
||||
PowerTransfer pt = p as PowerTransfer;
|
||||
if (pt == null) continue;
|
||||
|
||||
pt.powerLoad += (fullLoad - pt.powerLoad) / inertia;
|
||||
pt.currPowerConsumption += (-fullPower - pt.currPowerConsumption) / inertia;
|
||||
pt.Item.SendSignal(0, "", "power", null, fullPower / Math.Max(fullLoad, 1.0f));
|
||||
pt.Item.SendSignal(0, "", "power_out", null, fullPower / Math.Max(fullLoad, 1.0f));
|
||||
|
||||
//damage the item if voltage is too high
|
||||
//(except if running as a client)
|
||||
if (GameMain.Client != null) continue;
|
||||
if (-pt.currPowerConsumption < Math.Max(pt.powerLoad * Rand.Range(1.9f,2.1f), 200.0f)) continue;
|
||||
|
||||
float prevCondition = pt.item.Condition;
|
||||
pt.item.Condition -= deltaTime * 10.0f;
|
||||
|
||||
if (pt.item.Condition <= 0.0f && prevCondition > 0.0f)
|
||||
{
|
||||
#if CLIENT
|
||||
sparkSounds[Rand.Int(sparkSounds.Length)].Play(1.0f, 600.0f, pt.item.WorldPosition);
|
||||
|
||||
Vector2 baseVel = Rand.Vector(300.0f);
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
var particle = GameMain.ParticleManager.CreateParticle("spark", pt.item.WorldPosition,
|
||||
baseVel + Rand.Vector(100.0f), 0.0f, item.CurrentHull);
|
||||
|
||||
if (particle != null) particle.Size *= Rand.Range(0.5f, 1.0f);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (FireProbability > 0.0f && Rand.Int((int)(1.0f / FireProbability)) == 1)
|
||||
{
|
||||
new FireSource(pt.item.WorldPosition);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Pick(Character picker)
|
||||
{
|
||||
return picker != null;
|
||||
}
|
||||
|
||||
//a recursive function that goes through all the junctions and adds up
|
||||
//all the generated/consumed power of the constructions connected to the grid
|
||||
private void CheckJunctions(float deltaTime)
|
||||
{
|
||||
updateTimer = 1;
|
||||
connectedList.Add(this);
|
||||
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
|
||||
|
||||
foreach (Connection c in powerConnections)
|
||||
{
|
||||
var recipients = c.Recipients;
|
||||
|
||||
foreach (Connection recipient in recipients)
|
||||
{
|
||||
if (recipient == null) continue;
|
||||
|
||||
Item it = recipient.Item;
|
||||
if (it == null) continue;
|
||||
|
||||
if (it.Condition <= 0.0f) continue;
|
||||
|
||||
foreach (Powered powered in it.GetComponents<Powered>())
|
||||
{
|
||||
if (powered == null || !powered.IsActive) continue;
|
||||
|
||||
if (connectedList.Contains(powered)) continue;
|
||||
|
||||
PowerTransfer powerTransfer = powered as PowerTransfer;
|
||||
if (powerTransfer != null)
|
||||
{
|
||||
powerTransfer.CheckJunctions(deltaTime);
|
||||
continue;
|
||||
}
|
||||
|
||||
PowerContainer powerContainer = powered as PowerContainer;
|
||||
if (powerContainer != null)
|
||||
{
|
||||
if (recipient.Name == "power_in")
|
||||
{
|
||||
fullLoad += powerContainer.CurrPowerConsumption;
|
||||
}
|
||||
else
|
||||
{
|
||||
fullPower += powerContainer.CurrPowerOutput;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
connectedList.Add(powered);
|
||||
//positive power consumption = the construction requires power -> increase load
|
||||
if (powered.CurrPowerConsumption > 0.0f)
|
||||
{
|
||||
fullLoad += powered.CurrPowerConsumption;
|
||||
}
|
||||
else if (powered.CurrPowerConsumption < 0.0f)
|
||||
//negative power consumption = the construction is a
|
||||
//generator/battery or another junction box
|
||||
{
|
||||
fullPower -= powered.CurrPowerConsumption;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnMapLoaded()
|
||||
{
|
||||
var connections = item.Connections;
|
||||
if (connections == null)
|
||||
{
|
||||
IsActive = false;
|
||||
return;
|
||||
}
|
||||
|
||||
powerConnections = connections.FindAll(c => c.IsPower);
|
||||
if (powerConnections.Count == 0) IsActive = false;
|
||||
}
|
||||
|
||||
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power)
|
||||
{
|
||||
base.ReceiveSignal(stepsTaken, signal, connection, source, sender, power);
|
||||
|
||||
if (connection.Name.Length > 5 && connection.Name.Substring(0, 6).ToLowerInvariant() == "signal")
|
||||
{
|
||||
connection.SendSignal(stepsTaken, signal, source, sender, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class Powered : ItemComponent
|
||||
{
|
||||
//the amount of power CURRENTLY consumed by the item
|
||||
//negative values mean that the item is providing power to connected items
|
||||
protected float currPowerConsumption;
|
||||
|
||||
//current voltage of the item (load / power)
|
||||
protected float voltage;
|
||||
|
||||
//the minimum voltage required for the item to work
|
||||
protected float minVoltage;
|
||||
|
||||
//the maximum amount of power the item can draw from connected items
|
||||
protected float powerConsumption;
|
||||
|
||||
[Editable, HasDefaultValue(0.5f, true)]
|
||||
public float MinVoltage
|
||||
{
|
||||
get { return minVoltage; }
|
||||
set { minVoltage = value; }
|
||||
}
|
||||
|
||||
[Editable, HasDefaultValue(0.0f, true)]
|
||||
public float PowerConsumption
|
||||
{
|
||||
get { return powerConsumption; }
|
||||
set { powerConsumption = value; }
|
||||
}
|
||||
|
||||
|
||||
[HasDefaultValue(false,true)]
|
||||
public override bool IsActive
|
||||
{
|
||||
get { return base.IsActive; }
|
||||
set
|
||||
{
|
||||
base.IsActive = value;
|
||||
if (!value) currPowerConsumption = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
[HasDefaultValue(0.0f, true)]
|
||||
public float CurrPowerConsumption
|
||||
{
|
||||
get {return currPowerConsumption; }
|
||||
set { currPowerConsumption = value; }
|
||||
}
|
||||
|
||||
[HasDefaultValue(0.0f, true)]
|
||||
public float Voltage
|
||||
{
|
||||
get { return voltage; }
|
||||
set { voltage = Math.Max(0.0f, value); }
|
||||
}
|
||||
|
||||
public Powered(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
#if CLIENT
|
||||
if (powerOnSound == null)
|
||||
{
|
||||
powerOnSound = Sound.Load("Content/Items/Electricity/powerOn.ogg", false);
|
||||
}
|
||||
|
||||
if (sparkSounds == null)
|
||||
{
|
||||
sparkSounds = new Sound[4];
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
sparkSounds[i] = Sound.Load("Content/Items/Electricity/zap" + (i + 1) + ".ogg", false);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0)
|
||||
{
|
||||
if (currPowerConsumption == 0.0f) voltage = 0.0f;
|
||||
if (connection.IsPower) voltage = power;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
if (currPowerConsumption == 0.0f) return;
|
||||
|
||||
#if CLIENT
|
||||
if (voltage > minVoltage)
|
||||
{
|
||||
if (!powerOnSoundPlayed)
|
||||
{
|
||||
powerOnSound.Play(1.0f, 600.0f, item.WorldPosition);
|
||||
powerOnSoundPlayed = true;
|
||||
}
|
||||
}
|
||||
else if (voltage < 0.1f)
|
||||
{
|
||||
powerOnSoundPlayed = false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using FarseerPhysics.Dynamics.Contacts;
|
||||
using FarseerPhysics.Dynamics.Joints;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
class Projectile : ItemComponent
|
||||
{
|
||||
private float launchImpulse;
|
||||
|
||||
private bool doesStick;
|
||||
private PrismaticJoint stickJoint;
|
||||
private Body stickTarget;
|
||||
|
||||
private Attack attack;
|
||||
|
||||
public List<Body> IgnoredBodies;
|
||||
|
||||
public Character User;
|
||||
|
||||
[HasDefaultValue(10.0f, false)]
|
||||
public float LaunchImpulse
|
||||
{
|
||||
get { return launchImpulse; }
|
||||
set { launchImpulse = value; }
|
||||
}
|
||||
|
||||
[HasDefaultValue(false, false)]
|
||||
public bool CharacterUsable
|
||||
{
|
||||
get { return characterUsable; }
|
||||
set { characterUsable = value; }
|
||||
}
|
||||
|
||||
[HasDefaultValue(false, false)]
|
||||
public bool DoesStick
|
||||
{
|
||||
get { return doesStick; }
|
||||
set { doesStick = value; }
|
||||
}
|
||||
|
||||
public Projectile(Item item, XElement element)
|
||||
: base (item, element)
|
||||
{
|
||||
IgnoredBodies = new List<Body>();
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
if (subElement.Name.ToString().ToLowerInvariant() != "attack") continue;
|
||||
attack = new Attack(subElement);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Use(float deltaTime, Character character = null)
|
||||
{
|
||||
if (character != null && !characterUsable) return false;
|
||||
|
||||
Launch(new Vector2(
|
||||
(float)Math.Cos(item.body.Rotation),
|
||||
(float)Math.Sin(item.body.Rotation)) * launchImpulse * item.body.Mass);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void Launch(Vector2 impulse)
|
||||
{
|
||||
item.Drop();
|
||||
|
||||
item.body.Enabled = true;
|
||||
item.body.ApplyLinearImpulse(impulse);
|
||||
|
||||
item.body.FarseerBody.OnCollision += OnProjectileCollision;
|
||||
item.body.FarseerBody.IsBullet = true;
|
||||
|
||||
item.body.CollisionCategories = Physics.CollisionProjectile;
|
||||
item.body.CollidesWith = Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionLevel;
|
||||
|
||||
IsActive = true;
|
||||
|
||||
if (stickJoint == null || !doesStick) return;
|
||||
|
||||
if (stickTarget != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
item.body.FarseerBody.RestoreCollisionWith(stickTarget);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("Failed to restore collision with stickTarget", e);
|
||||
#endif
|
||||
}
|
||||
|
||||
stickTarget = null;
|
||||
}
|
||||
GameMain.World.RemoveJoint(stickJoint);
|
||||
stickJoint = null;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
|
||||
|
||||
if (stickJoint != null && stickJoint.JointTranslation < 0.01f)
|
||||
{
|
||||
if (stickTarget != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
item.body.FarseerBody.RestoreCollisionWith(stickTarget);
|
||||
}
|
||||
catch
|
||||
{
|
||||
//the body that the projectile was stuck to has been removed
|
||||
}
|
||||
|
||||
stickTarget = null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
GameMain.World.RemoveJoint(stickJoint);
|
||||
}
|
||||
catch
|
||||
{
|
||||
//the body that the projectile was stuck to has been removed
|
||||
}
|
||||
|
||||
stickJoint = null;
|
||||
|
||||
IsActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool OnProjectileCollision(Fixture f1, Fixture f2, Contact contact)
|
||||
{
|
||||
if (IgnoredBodies.Contains(f2.Body)) return false;
|
||||
|
||||
if (f2.CollisionCategories == Physics.CollisionCharacter && !(f2.Body.UserData is Limb))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
AttackResult attackResult = new AttackResult(0.0f, 0.0f);
|
||||
if (attack != null)
|
||||
{
|
||||
var submarine = f2.Body.UserData as Submarine;
|
||||
if (submarine != null)
|
||||
{
|
||||
item.Move(-submarine.Position);
|
||||
item.Submarine = submarine;
|
||||
item.body.Submarine = submarine;
|
||||
//item.FindHull();
|
||||
return true;
|
||||
}
|
||||
|
||||
Limb limb;
|
||||
Structure structure;
|
||||
if ((limb = (f2.Body.UserData as Limb)) != null)
|
||||
{
|
||||
attackResult = attack.DoDamage(User, limb.character, item.WorldPosition, 1.0f);
|
||||
}
|
||||
else if ((structure = (f2.Body.UserData as Structure)) != null)
|
||||
{
|
||||
attackResult = attack.DoDamage(User, structure, item.WorldPosition, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
ApplyStatusEffects(ActionType.OnUse, 1.0f);
|
||||
ApplyStatusEffects(ActionType.OnImpact, 1.0f);
|
||||
|
||||
IsActive = false;
|
||||
|
||||
item.body.FarseerBody.OnCollision -= OnProjectileCollision;
|
||||
|
||||
item.body.FarseerBody.IsBullet = false;
|
||||
item.body.CollisionCategories = Physics.CollisionItem;
|
||||
item.body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel;
|
||||
|
||||
IgnoredBodies.Clear();
|
||||
|
||||
f2.Body.ApplyLinearImpulse(item.body.LinearVelocity * item.body.Mass);
|
||||
|
||||
if (attackResult.HitArmor)
|
||||
{
|
||||
item.body.LinearVelocity *= 0.1f;
|
||||
}
|
||||
else if (doesStick)
|
||||
{
|
||||
Vector2 normal = contact.Manifold.LocalNormal;
|
||||
Vector2 dir = new Vector2(
|
||||
(float)Math.Cos(item.body.Rotation),
|
||||
(float)Math.Sin(item.body.Rotation));
|
||||
|
||||
if (Vector2.Dot(f1.Body.LinearVelocity, normal) < 0.0f) return StickToTarget(f2.Body, dir);
|
||||
}
|
||||
else
|
||||
{
|
||||
item.body.LinearVelocity *= 0.5f;
|
||||
}
|
||||
|
||||
var containedItems = item.ContainedItems;
|
||||
if (containedItems != null)
|
||||
{
|
||||
foreach (Item contained in containedItems)
|
||||
{
|
||||
if (contained.body != null)
|
||||
{
|
||||
contained.SetTransform(item.SimPosition, contained.body.Rotation);
|
||||
}
|
||||
contained.Condition = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
return f2.CollisionCategories != Physics.CollisionCharacter;
|
||||
}
|
||||
|
||||
private bool StickToTarget(Body targetBody, Vector2 axis)
|
||||
{
|
||||
if (stickJoint != null) return false;
|
||||
|
||||
stickJoint = new PrismaticJoint(targetBody, item.body.FarseerBody, item.body.SimPosition, axis, true);
|
||||
stickJoint.MotorEnabled = true;
|
||||
stickJoint.MaxMotorForce = 30.0f;
|
||||
|
||||
stickJoint.LimitEnabled = true;
|
||||
stickJoint.UpperLimit = ConvertUnits.ToSimUnits(item.Sprite.size.X*0.7f);
|
||||
|
||||
item.body.FarseerBody.IgnoreCollisionWith(targetBody);
|
||||
stickTarget = targetBody;
|
||||
GameMain.World.AddJoint(stickJoint);
|
||||
|
||||
IsActive = true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,298 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Collision.Shapes;
|
||||
using FarseerPhysics.Common;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using FarseerPhysics.Dynamics.Joints;
|
||||
using FarseerPhysics.Factories;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class Rope : ItemComponent, IDrawableComponent
|
||||
{
|
||||
PhysicsBody[] ropeBodies;
|
||||
RevoluteJoint[] ropeJoints;
|
||||
|
||||
DistanceJoint gunJoint;
|
||||
|
||||
float pullForce;
|
||||
|
||||
Sprite sprite;
|
||||
|
||||
float reload;
|
||||
|
||||
float prevDir;
|
||||
|
||||
float sectionLength;
|
||||
|
||||
Item projectile;
|
||||
|
||||
Vector2 projectileAnchor;
|
||||
|
||||
private Vector2 BarrelPos
|
||||
{
|
||||
get
|
||||
{
|
||||
Vector2 barrelPos = Vector2.Zero;
|
||||
|
||||
//RangedWeapon weapon = item.GetComponent<RangedWeapon>();
|
||||
//if (weapon != null) barrelPos = weapon.barrelPos;
|
||||
|
||||
return barrelPos;
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2 TransformedBarrelPos
|
||||
{
|
||||
get
|
||||
{
|
||||
Vector2 barrelPos = Vector2.Zero;
|
||||
|
||||
RangedWeapon weapon = item.GetComponent<RangedWeapon>();
|
||||
if (weapon != null) barrelPos = weapon.TransformedBarrelPos;
|
||||
|
||||
return barrelPos;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public Rope(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
string spritePath = ToolBox.GetAttributeString(element, "sprite", "");
|
||||
if (spritePath == "") DebugConsole.ThrowError("Sprite "+spritePath+" in "+element+" not found!");
|
||||
|
||||
float length = ConvertUnits.ToSimUnits(ToolBox.GetAttributeFloat(element, "length", 200.0f));
|
||||
|
||||
pullForce = ToolBox.GetAttributeFloat(element, "pullforce", 10.0f);
|
||||
|
||||
projectileAnchor = Vector2.Zero;
|
||||
projectileAnchor.X = ToolBox.GetAttributeFloat(element, "projectileanchorx", 0.0f);
|
||||
projectileAnchor.Y = ToolBox.GetAttributeFloat(element, "projectileanchory", 0.0f);
|
||||
projectileAnchor = ConvertUnits.ToSimUnits(projectileAnchor);
|
||||
|
||||
characterUsable = ToolBox.GetAttributeBool(element, "characterusable", false);
|
||||
|
||||
sprite = new Sprite(spritePath, new Vector2(0.5f,0.5f));
|
||||
sectionLength = ConvertUnits.ToSimUnits(sprite.size.X);
|
||||
|
||||
|
||||
Path ropePath = new Path();
|
||||
ropePath.Add(item.body.SimPosition);
|
||||
ropePath.Add(item.body.SimPosition + new Vector2(length, 0.0f));
|
||||
ropePath.Closed = false;
|
||||
|
||||
Vertices box = PolygonTools.CreateRectangle(sectionLength, 0.05f);
|
||||
PolygonShape shape = new PolygonShape(box, 5);
|
||||
|
||||
List<Body>ropeList = PathManager.EvenlyDistributeShapesAlongPath(GameMain.World, ropePath, shape, BodyType.Dynamic, (int)(length/sectionLength));
|
||||
|
||||
ropeBodies = new PhysicsBody[ropeList.Count];
|
||||
for (int i = 0; i<ropeBodies.Length; i++)
|
||||
{
|
||||
ropeList[i].Mass = 0.01f;
|
||||
ropeList[i].Enabled = false;
|
||||
//only collide with the map
|
||||
ropeList[i].CollisionCategories = Physics.CollisionItem;
|
||||
ropeList[i].CollidesWith = Physics.CollisionWall;
|
||||
|
||||
//ropeBodies[i] = new PhysicsBody(ropeList[i]);
|
||||
}
|
||||
|
||||
List<RevoluteJoint> joints = PathManager.AttachBodiesWithRevoluteJoint(GameMain.World, ropeList,
|
||||
new Vector2(-sectionLength/2, 0.0f), new Vector2(sectionLength/2, 0.0f), false, false);
|
||||
|
||||
ropeJoints = new RevoluteJoint[joints.Count+1];
|
||||
//ropeJoints[0] = JointFactory.CreateRevoluteJoint(Game1.world, item.body, ropeList[0], new Vector2(0f, -0.0f));
|
||||
for (int i = 0; i < joints.Count; i++)
|
||||
{
|
||||
var distanceJoint = JointFactory.CreateDistanceJoint(GameMain.World, ropeList[i], ropeList[i + 1]);
|
||||
|
||||
distanceJoint.Length = sectionLength;
|
||||
distanceJoint.DampingRatio = 1.0f;
|
||||
ropeJoints[i] = joints[i];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public override void SecondaryUse(float deltaTime, Character character = null)
|
||||
{
|
||||
if (reload > 0.0f) return;
|
||||
|
||||
bool first = true;
|
||||
for (int i = 0; i < ropeBodies.Length - 1; i++)
|
||||
{
|
||||
if (ropeBodies[i].UserData == null || (bool)ropeBodies[i].UserData) continue;
|
||||
|
||||
if (first)
|
||||
{
|
||||
Vector2 dist = gunJoint.WorldAnchorA - ropeJoints[i].WorldAnchorA;
|
||||
float length = dist.Length();
|
||||
|
||||
if (gunJoint.Length < 0.011 && length*0.5f<sectionLength)
|
||||
{
|
||||
NextSection(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
gunJoint.Length = Math.Max(gunJoint.Length-0.01f,0.01f);
|
||||
gunJoint.Frequency = 30;
|
||||
gunJoint.DampingRatio = 0.05f;
|
||||
//gunJoint.MotorEnabled = true;
|
||||
//gunJoint.MotorSpeed = -150.0f;
|
||||
//ropeBodies[i + 1].ApplyForce(dist / length * pullForce);
|
||||
//ropeJoints[0].LocalAnchorA = new Vector2(ropeJoints[0].LocalAnchorA.X-0.05f,ropeJoints[0].LocalAnchorA.Y);
|
||||
//ropeBodies[i].SmoothRotate(item.body.Rotation);
|
||||
}
|
||||
|
||||
first = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
//Vector2 dist = ropeBodies[i].Position - ropeBodies[i + 1].Position;
|
||||
//float length = dist.Length();
|
||||
|
||||
//ropeBodies[i + 1].ApplyForce(dist / length * pullForce * 0.1f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void NextSection(int i)
|
||||
{
|
||||
gunJoint.Length = sectionLength;
|
||||
ropeBodies[i].UserData = true;
|
||||
ropeBodies[i].Enabled = false;
|
||||
|
||||
//if (ropeJoints[0] != null) Game1.world.RemoveJoint(ropeJoints[0]);
|
||||
//ropeJoints[0] = JointFactory.CreateRevoluteJoint(Game1.world,
|
||||
// item.body.FarseerBody, ropeBodies[i + 1].FarseerBody,
|
||||
// BarrelPos, new Vector2(-sectionLength / 2, 0.0f));
|
||||
|
||||
AttachGunJoint(ropeBodies[i + 1].FarseerBody);
|
||||
|
||||
if (i == ropeBodies.Length - 2)
|
||||
{
|
||||
item.Combine(projectile);
|
||||
ropeBodies[ropeBodies.Length - 1].Enabled = false;
|
||||
IsActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
if (reload>0.0f) reload -= deltaTime;
|
||||
|
||||
//for (int i = 0; i < ropeBodies.Length - 1; i++)
|
||||
//{
|
||||
// if (ropeBodies[i].UserData == null || (bool)ropeBodies[i].UserData == true) continue;
|
||||
// ropeBodies[i].SmoothRotate(item.body.Rotation);
|
||||
//}
|
||||
|
||||
int len = 1;
|
||||
for (int i = 0; i < ropeBodies.Length - 1; i++)
|
||||
{
|
||||
if (ropeBodies[i].UserData == null || (bool)ropeBodies[i].UserData) continue;
|
||||
len++;
|
||||
}
|
||||
|
||||
if (Vector2.Distance(TransformedBarrelPos, projectile.SimPosition)>len*sectionLength)
|
||||
{
|
||||
Vector2 stopForce = projectile.SimPosition - ropeBodies[ropeBodies.Length-1].SimPosition;
|
||||
stopForce = Vector2.Normalize(stopForce);
|
||||
|
||||
float dotProduct = Vector2.Dot(stopForce, Vector2.Normalize(projectile.body.LinearVelocity));
|
||||
|
||||
if (dotProduct<0)
|
||||
projectile.body.ApplyLinearImpulse(-stopForce*dotProduct * projectile.body.LinearVelocity.Length() * projectile.body.Mass);
|
||||
}
|
||||
|
||||
if (item.body.Dir!=prevDir)
|
||||
{
|
||||
gunJoint.LocalAnchorA =
|
||||
new Vector2(
|
||||
-gunJoint.LocalAnchorA.X,
|
||||
BarrelPos.Y);
|
||||
|
||||
prevDir = -prevDir;
|
||||
}
|
||||
|
||||
if (!projectile.body.Enabled || !item.body.Enabled)
|
||||
{
|
||||
//attempt to recontain the projectile in the launcher
|
||||
//eq automatically reload a spear into a speargun when picking the spear up
|
||||
if (!projectile.body.Enabled) item.Combine(projectile);
|
||||
|
||||
foreach (PhysicsBody b in ropeBodies)
|
||||
{
|
||||
b.Enabled = false;
|
||||
}
|
||||
foreach (var joint in ropeJoints)
|
||||
{
|
||||
if (joint != null) joint.Enabled = false;
|
||||
}
|
||||
IsActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void Attach(Item projectile)
|
||||
{
|
||||
reload = 0.5f;
|
||||
IsActive = true;
|
||||
|
||||
this.projectile = projectile;
|
||||
//Projectile projectileComponent = projectile.GetComponent<Projectile>();
|
||||
|
||||
foreach (PhysicsBody b in ropeBodies)
|
||||
{
|
||||
b.SetTransform(item.body.SimPosition, 0.0f);
|
||||
b.UserData = false;
|
||||
b.Enabled = true;
|
||||
}
|
||||
|
||||
foreach (var joint in ropeJoints)
|
||||
{
|
||||
if (joint!=null) joint.Enabled = true;
|
||||
}
|
||||
|
||||
ropeBodies[ropeBodies.Length - 1].SetTransform(projectile.body.SimPosition, projectile.body.Rotation);
|
||||
|
||||
//attach projectile to the last section of the rope
|
||||
if (ropeJoints[ropeJoints.Length-1] != null) GameMain.World.RemoveJoint(ropeJoints[ropeJoints.Length-1]);
|
||||
ropeJoints[ropeJoints.Length - 1] = JointFactory.CreateRevoluteJoint(GameMain.World,
|
||||
projectile.body.FarseerBody, ropeBodies[ropeBodies.Length - 1].FarseerBody,
|
||||
projectileAnchor, new Vector2(sectionLength / 2, 0.0f));
|
||||
|
||||
AttachGunJoint(ropeBodies[0].FarseerBody);
|
||||
|
||||
prevDir = item.body.Dir;
|
||||
}
|
||||
|
||||
private void AttachGunJoint(Body body)
|
||||
{
|
||||
float rotation = (item.body.Dir == -1.0f) ? item.body.Rotation - MathHelper.Pi : item.body.Rotation;
|
||||
body.SetTransform(TransformedBarrelPos, rotation);
|
||||
//Vector2 axis = new Vector2((float)Math.Cos(rotation), (float)Math.Sin(rotation));
|
||||
|
||||
if (gunJoint != null) GameMain.World.RemoveJoint(gunJoint);
|
||||
gunJoint = new DistanceJoint(item.body.FarseerBody, body, BarrelPos,
|
||||
new Vector2(sectionLength / 2, 0.0f));
|
||||
|
||||
gunJoint.Length = sectionLength;
|
||||
|
||||
//gunJoint.LocalAnchorA = BarrelPos;
|
||||
//gunJoint.LocalAnchorB = new Vector2(sectionLength / 2, 0.0f);
|
||||
//gunJoint.UpperLimit = sectionLength;
|
||||
//gunJoint.LowerLimit = 0.0f;
|
||||
//gunJoint.LimitEnabled = true;
|
||||
//gunJoint.ReferenceAngle = 0.0f;
|
||||
|
||||
GameMain.World.AddJoint(gunJoint);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
class AndComponent : ItemComponent
|
||||
{
|
||||
protected string output, falseOutput;
|
||||
|
||||
//an array to keep track of how long ago a non-zero signal was received on both inputs
|
||||
protected float[] timeSinceReceived;
|
||||
|
||||
//the output is sent if both inputs have received a signal within the timeframe
|
||||
protected float timeFrame;
|
||||
|
||||
[InGameEditable, HasDefaultValue(0.0f, true)]
|
||||
public float TimeFrame
|
||||
{
|
||||
get { return timeFrame; }
|
||||
set
|
||||
{
|
||||
timeFrame = Math.Max(0.0f, value);
|
||||
}
|
||||
}
|
||||
|
||||
[InGameEditable, HasDefaultValue("1", true)]
|
||||
public string Output
|
||||
{
|
||||
get { return output; }
|
||||
set { output = value; }
|
||||
}
|
||||
|
||||
[InGameEditable, HasDefaultValue("", true)]
|
||||
public string FalseOutput
|
||||
{
|
||||
get { return falseOutput; }
|
||||
set { falseOutput = value; }
|
||||
}
|
||||
|
||||
public AndComponent(Item item, XElement element)
|
||||
: base (item, element)
|
||||
{
|
||||
timeSinceReceived = new float[] { Math.Max(timeFrame*2.0f,0.1f), Math.Max(timeFrame*2.0f, 0.1f) };
|
||||
|
||||
//output = "1";
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
bool sendOutput = true;
|
||||
for (int i = 0; i<timeSinceReceived.Length; i++)
|
||||
{
|
||||
if (timeSinceReceived[i] > timeFrame) sendOutput = false;
|
||||
timeSinceReceived[i] += deltaTime;
|
||||
}
|
||||
|
||||
string signalOut = sendOutput ? output : falseOutput;
|
||||
if (string.IsNullOrEmpty(signalOut)) return;
|
||||
|
||||
item.SendSignal(0, signalOut, "signal_out", null);
|
||||
}
|
||||
|
||||
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power=0.0f)
|
||||
{
|
||||
switch (connection.Name)
|
||||
{
|
||||
case "signal_in1":
|
||||
if (signal == "0") return;
|
||||
timeSinceReceived[0] = 0.0f;
|
||||
IsActive = true;
|
||||
break;
|
||||
case "signal_in2":
|
||||
if (signal == "0") return;
|
||||
timeSinceReceived[1] = 0.0f;
|
||||
IsActive = true;
|
||||
break;
|
||||
case "set_output":
|
||||
output = signal;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class Connection
|
||||
{
|
||||
//how many wires can be linked to a single connector
|
||||
public const int MaxLinked = 5;
|
||||
|
||||
public readonly string Name;
|
||||
|
||||
public Wire[] Wires;
|
||||
|
||||
private Item item;
|
||||
|
||||
public readonly bool IsOutput;
|
||||
|
||||
private static Wire draggingConnected;
|
||||
|
||||
private List<StatusEffect> effects;
|
||||
|
||||
public readonly ushort[] wireId;
|
||||
|
||||
public bool IsPower
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public List<Connection> Recipients
|
||||
{
|
||||
get
|
||||
{
|
||||
List<Connection> recipients = new List<Connection>();
|
||||
for (int i = 0; i < MaxLinked; i++)
|
||||
{
|
||||
if (Wires[i] == null) continue;
|
||||
Connection recipient = Wires[i].OtherConnection(this);
|
||||
if (recipient != null) recipients.Add(recipient);
|
||||
}
|
||||
return recipients;
|
||||
}
|
||||
}
|
||||
|
||||
public Item Item
|
||||
{
|
||||
get { return item; }
|
||||
}
|
||||
|
||||
public Connection(XElement element, Item item)
|
||||
{
|
||||
|
||||
#if CLIENT
|
||||
if (connector == null)
|
||||
{
|
||||
panelTexture = Sprite.LoadTexture("Content/Items/connectionpanel.png");
|
||||
|
||||
connector = new Sprite(panelTexture, new Rectangle(470, 102, 19, 43), Vector2.Zero, 0.0f);
|
||||
connector.Origin = new Vector2(9.5f, 10.0f);
|
||||
|
||||
wireVertical = new Sprite(panelTexture, new Rectangle(408, 1, 11, 102), Vector2.Zero, 0.0f);
|
||||
}
|
||||
#endif
|
||||
|
||||
this.item = item;
|
||||
|
||||
//recipient = new Connection[MaxLinked];
|
||||
Wires = new Wire[MaxLinked];
|
||||
|
||||
IsOutput = (element.Name.ToString() == "output");
|
||||
Name = ToolBox.GetAttributeString(element, "name", (IsOutput) ? "output" : "input");
|
||||
|
||||
IsPower = Name == "power_in" || Name == "power" || Name == "power_out";
|
||||
|
||||
effects = new List<StatusEffect>();
|
||||
|
||||
wireId = new ushort[MaxLinked];
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "link":
|
||||
int index = -1;
|
||||
for (int i = 0; i < MaxLinked; i++)
|
||||
{
|
||||
if (wireId[i] < 1) index = i;
|
||||
}
|
||||
if (index == -1) break;
|
||||
|
||||
int id = ToolBox.GetAttributeInt(subElement, "w", 0);
|
||||
if (id < 0) id = 0;
|
||||
wireId[index] = (ushort)id;
|
||||
|
||||
break;
|
||||
|
||||
case "statuseffect":
|
||||
effects.Add(StatusEffect.Load(subElement));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int FindEmptyIndex()
|
||||
{
|
||||
for (int i = 0; i < MaxLinked; i++)
|
||||
{
|
||||
if (Wires[i] == null) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
//public int FindLinkIndex(Item item)
|
||||
//{
|
||||
// for (int i = 0; i < MaxLinked; i++)
|
||||
// {
|
||||
// if (item == null && recipient[i] == null) return i;
|
||||
// if (recipient[i]!=null && recipient[i].item == item) return i;
|
||||
// }
|
||||
// return -1;
|
||||
//}
|
||||
|
||||
public int FindWireIndex(Item wireItem)
|
||||
{
|
||||
for (int i = 0; i < MaxLinked; i++)
|
||||
{
|
||||
if (Wires[i] == null && wireItem == null) return i;
|
||||
if (Wires[i] != null && Wires[i].Item == wireItem) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public void TryAddLink(Wire wire)
|
||||
{
|
||||
for (int i = 0; i < MaxLinked; i++)
|
||||
{
|
||||
if (Wires[i] == null)
|
||||
{
|
||||
Wires[i] = wire;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddLink(int index, Wire wire)
|
||||
{
|
||||
Wires[index] = wire;
|
||||
}
|
||||
|
||||
public void SendSignal(int stepsTaken, string signal, Item source, Character sender, float power)
|
||||
{
|
||||
for (int i = 0; i < MaxLinked; i++)
|
||||
{
|
||||
if (Wires[i] == null) continue;
|
||||
|
||||
Connection recipient = Wires[i].OtherConnection(this);
|
||||
if (recipient == null) continue;
|
||||
if (recipient.item == this.item || recipient.item == source) continue;
|
||||
|
||||
foreach (ItemComponent ic in recipient.item.components)
|
||||
{
|
||||
ic.ReceiveSignal(stepsTaken, signal, recipient, item, sender, power);
|
||||
}
|
||||
|
||||
foreach (StatusEffect effect in recipient.effects)
|
||||
{
|
||||
|
||||
//effect.Apply(ActionType.OnUse, 1.0f, recipient.item, recipient.item);
|
||||
recipient.item.ApplyStatusEffect(effect, ActionType.OnUse, 1.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearConnections()
|
||||
{
|
||||
for (int i = 0; i < MaxLinked; i++)
|
||||
{
|
||||
if (Wires[i] == null) continue;
|
||||
|
||||
Wires[i].RemoveConnection(this);
|
||||
Wires[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void ConnectLinked()
|
||||
{
|
||||
if (wireId == null) return;
|
||||
|
||||
for (int i = 0; i < MaxLinked; i++)
|
||||
{
|
||||
if (wireId[i] == 0) continue;
|
||||
|
||||
Item wireItem = MapEntity.FindEntityByID(wireId[i]) as Item;
|
||||
|
||||
if (wireItem == null) continue;
|
||||
Wires[i] = wireItem.GetComponent<Wire>();
|
||||
|
||||
if (Wires[i] != null)
|
||||
{
|
||||
if (Wires[i].Item.body != null) Wires[i].Item.body.Enabled = false;
|
||||
Wires[i].Connect(this, false, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
using Lidgren.Network;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class ConnectionPanel : ItemComponent, IServerSerializable, IClientSerializable
|
||||
{
|
||||
public static Wire HighlightedWire;
|
||||
|
||||
public List<Connection> Connections;
|
||||
|
||||
Character user;
|
||||
|
||||
public ConnectionPanel(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
Connections = new List<Connection>();
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString())
|
||||
{
|
||||
case "input":
|
||||
Connections.Add(new Connection(subElement, item));
|
||||
break;
|
||||
case "output":
|
||||
Connections.Add(new Connection(subElement, item));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
IsActive = true;
|
||||
}
|
||||
|
||||
public override void OnMapLoaded()
|
||||
{
|
||||
foreach (Connection c in Connections)
|
||||
{
|
||||
c.ConnectLinked();
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
if (user != null && user.SelectedConstruction != item) user = null;
|
||||
}
|
||||
|
||||
public override bool Select(Character picker)
|
||||
{
|
||||
//attaching wires to items with a body is not allowed
|
||||
//(signal items remove their bodies when attached to a wall)
|
||||
if (item.body != null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
user = picker;
|
||||
IsActive = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool Use(float deltaTime, Character character = null)
|
||||
{
|
||||
if (character == null || character!=user) return false;
|
||||
|
||||
var powered = item.GetComponent<Powered>();
|
||||
if (powered != null)
|
||||
{
|
||||
if (powered.Voltage < 0.1f) return false;
|
||||
}
|
||||
|
||||
float degreeOfSuccess = DegreeOfSuccess(character);
|
||||
if (Rand.Range(0.0f, 50.0f) < degreeOfSuccess) return false;
|
||||
|
||||
character.SetStun(5.0f);
|
||||
|
||||
item.ApplyStatusEffects(ActionType.OnFailure, 1.0f, character);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void Load(XElement element)
|
||||
{
|
||||
base.Load(element);
|
||||
|
||||
List<Connection> loadedConnections = new List<Connection>();
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString())
|
||||
{
|
||||
case "input":
|
||||
loadedConnections.Add(new Connection(subElement, item));
|
||||
break;
|
||||
case "output":
|
||||
loadedConnections.Add(new Connection(subElement, item));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i<loadedConnections.Count && i<Connections.Count; i++)
|
||||
{
|
||||
loadedConnections[i].wireId.CopyTo(Connections[i].wireId, 0);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void RemoveComponentSpecific()
|
||||
{
|
||||
foreach (Connection c in Connections)
|
||||
{
|
||||
foreach (Wire wire in c.Wires)
|
||||
{
|
||||
if (wire == null) continue;
|
||||
|
||||
if (wire.OtherConnection(c) == null) //wire not connected to anything else
|
||||
{
|
||||
wire.Item.Drop(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
wire.RemoveConnection(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ClientWrite(NetBuffer msg, object[] extraData = null)
|
||||
{
|
||||
foreach (Connection connection in Connections)
|
||||
{
|
||||
Wire[] wires = Array.FindAll(connection.Wires, w => w != null);
|
||||
msg.WriteRangedInteger(0, Connection.MaxLinked, wires.Length);
|
||||
for (int i = 0; i < wires.Length; i++)
|
||||
{
|
||||
msg.Write(wires[i].Item.ID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ServerRead(ClientNetObject type, NetBuffer msg, Client c)
|
||||
{
|
||||
int[] wireCounts = new int[Connections.Count];
|
||||
Wire[,] wires = new Wire[Connections.Count, Connection.MaxLinked];
|
||||
|
||||
//read wire IDs for each connection
|
||||
for (int i = 0; i < Connections.Count; i++)
|
||||
{
|
||||
wireCounts[i] = msg.ReadRangedInteger(0, Connection.MaxLinked);
|
||||
for (int j = 0; j < wireCounts[i]; j++)
|
||||
{
|
||||
ushort wireId = msg.ReadUInt16();
|
||||
|
||||
Item wireItem = MapEntity.FindEntityByID(wireId) as Item;
|
||||
if (wireItem == null) continue;
|
||||
|
||||
Wire wireComponent = wireItem.GetComponent<Wire>();
|
||||
if (wireComponent != null)
|
||||
{
|
||||
wires[i, j] = wireComponent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
item.CreateServerEvent<ConnectionPanel>(this);
|
||||
|
||||
//check if the character can access this connectionpanel
|
||||
//and all the wires they're trying to connect
|
||||
if (!item.CanClientAccess(c)) return;
|
||||
foreach (Wire wire in wires)
|
||||
{
|
||||
if (wire != null)
|
||||
{
|
||||
//wire not found in any of the connections yet (client is trying to connect a new wire)
|
||||
// -> we need to check if the client has access to it
|
||||
if (!Connections.Any(connection => connection.Wires.Contains(wire)))
|
||||
{
|
||||
if (!wire.Item.CanClientAccess(c)) return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Networking.GameServer.Log(item.Name + " rewired by " + c.Character.Name, ServerLog.MessageType.ItemInteraction);
|
||||
|
||||
//update the connections
|
||||
for (int i = 0; i < Connections.Count; i++)
|
||||
{
|
||||
Connections[i].ClearConnections();
|
||||
|
||||
for (int j = 0; j < wireCounts[i]; j++)
|
||||
{
|
||||
if (wires[i, j] == null) continue;
|
||||
|
||||
Connections[i].Wires[j] = wires[i,j];
|
||||
wires[i, j].Connect(Connections[i], true);
|
||||
|
||||
var otherConnection = Connections[i].Wires[j].OtherConnection(Connections[i]);
|
||||
|
||||
Networking.GameServer.Log(
|
||||
item.Name + " (" + Connections[i].Name + ") -> " +
|
||||
(otherConnection == null ? "none" : otherConnection.Item.Name + " (" + (otherConnection.Name) + ")"), ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
|
||||
{
|
||||
ClientWrite(msg, extraData);
|
||||
}
|
||||
|
||||
public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime)
|
||||
{
|
||||
foreach (Connection connection in Connections)
|
||||
{
|
||||
connection.ClearConnections();
|
||||
int wireCount = msg.ReadRangedInteger(0, Connection.MaxLinked);
|
||||
for (int i = 0; i < wireCount; i++)
|
||||
{
|
||||
ushort wireId = msg.ReadUInt16();
|
||||
|
||||
Item wireItem = MapEntity.FindEntityByID(wireId) as Item;
|
||||
if (wireItem == null) continue;
|
||||
|
||||
Wire wireComponent = wireItem.GetComponent<Wire>();
|
||||
if (wireComponent == null) continue;
|
||||
|
||||
connection.Wires[i] = wireComponent;
|
||||
wireComponent.Connect(connection, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user