First commit
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
using Lidgren.Network;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Subsurface
|
||||
{
|
||||
class AIController : ISteerable
|
||||
{
|
||||
|
||||
public enum AiState { None, Attack, GoTo, Escape }
|
||||
public enum SteeringState { Wander, Seek, Escape }
|
||||
|
||||
public Character character;
|
||||
|
||||
protected AiState state;
|
||||
|
||||
protected SteeringManager steeringManager;
|
||||
|
||||
public Vector2 Steering
|
||||
{
|
||||
get { return character.animController.TargetMovement; }
|
||||
set { character.animController.TargetMovement = value; }
|
||||
}
|
||||
|
||||
public Vector2 Position
|
||||
{
|
||||
get { return character.animController.limbs[0].SimPosition; }
|
||||
}
|
||||
|
||||
public Vector2 Velocity
|
||||
{
|
||||
get { return character.animController.limbs[0].LinearVelocity; }
|
||||
}
|
||||
|
||||
public AiState State
|
||||
{
|
||||
get { return state; }
|
||||
}
|
||||
|
||||
public AIController (Character c)
|
||||
{
|
||||
character = c;
|
||||
|
||||
steeringManager = new SteeringManager(this);
|
||||
}
|
||||
|
||||
public virtual void Update(float deltaTime) { }
|
||||
|
||||
//protected Structure lastStructurePicked;
|
||||
|
||||
public virtual void FillNetworkData(NetOutgoingMessage message) { }
|
||||
public virtual void ReadNetworkData(NetIncomingMessage message) { }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Subsurface
|
||||
{
|
||||
class AITarget
|
||||
{
|
||||
public static List<AITarget> list = new List<AITarget>();
|
||||
|
||||
|
||||
protected float soundRange;
|
||||
protected float sightRange;
|
||||
|
||||
public Entity entity;
|
||||
|
||||
public float SoundRange
|
||||
{
|
||||
get
|
||||
{
|
||||
return soundRange;
|
||||
}
|
||||
set { soundRange = value; }
|
||||
}
|
||||
|
||||
public float SightRange
|
||||
{
|
||||
get { return sightRange; }
|
||||
set { sightRange = value; }
|
||||
}
|
||||
|
||||
public Vector2 Position
|
||||
{
|
||||
get { return entity.SimPosition; }
|
||||
}
|
||||
|
||||
public AITarget(Entity e)
|
||||
{
|
||||
entity = e;
|
||||
list.Add(this);
|
||||
}
|
||||
|
||||
public void Remove()
|
||||
{
|
||||
list.Remove(this);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,506 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Xml.Linq;
|
||||
using FarseerPhysics;
|
||||
using Lidgren.Network;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Subsurface
|
||||
{
|
||||
|
||||
class EnemyAIController : AIController
|
||||
{ //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;
|
||||
private float attackHumans;
|
||||
private float attackWeaker;
|
||||
private float attackStronger;
|
||||
|
||||
|
||||
|
||||
private float updateTargetsTimer;
|
||||
private const float UpdateTargetsInterval = 5.0f;
|
||||
|
||||
private float raycastTimer;
|
||||
private const float RaycastInterval = 1.0f;
|
||||
|
||||
private Vector2 prevPosition;
|
||||
private float distanceAccumulator;
|
||||
|
||||
//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 MapEntity targetEntity;
|
||||
|
||||
//the limb selected for the current attack
|
||||
private Limb attackingLimb;
|
||||
|
||||
private AITarget selectedTarget;
|
||||
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 EnemyAIController(Character c, string file) : base(c)
|
||||
{
|
||||
targetMemories = new Dictionary<AITarget, AITargetMemory>();
|
||||
|
||||
XDocument doc = ToolBox.TryLoadXml(file);
|
||||
if (doc == 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);
|
||||
|
||||
state = AiState.None;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
UpdateDistanceAccumulator();
|
||||
|
||||
character.animController.IgnorePlatforms = (-character.animController.TargetMovement.Y > Math.Abs(character.animController.TargetMovement.X));
|
||||
|
||||
if (updateTargetsTimer > 0.0)
|
||||
{
|
||||
updateTargetsTimer -= deltaTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("updatetargets");
|
||||
UpdateTargets(character);
|
||||
updateTargetsTimer = UpdateTargetsInterval;
|
||||
|
||||
if (selectedTarget == null)
|
||||
{
|
||||
state = AiState.None;
|
||||
}
|
||||
else
|
||||
{
|
||||
state = (targetValue > 0.0f) ? AiState.Attack : AiState.Escape;
|
||||
}
|
||||
//if (coolDownTimer >= 0.0f) return;
|
||||
}
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case AiState.None:
|
||||
UpdateNone(deltaTime);
|
||||
break;
|
||||
case AiState.Attack:
|
||||
UpdateAttack(deltaTime);
|
||||
break;
|
||||
}
|
||||
|
||||
steeringManager.Update();
|
||||
}
|
||||
|
||||
private void UpdateNone(float deltaTime)
|
||||
{
|
||||
//wander around randomly
|
||||
//UpdateSteeringWander(deltaTime, 0.8f);
|
||||
steeringManager.SteeringWander(0.8f);
|
||||
steeringManager.SteeringAvoid(deltaTime, 1.0f);
|
||||
|
||||
attackingLimb = null;
|
||||
attackTimer = 0.0f;
|
||||
|
||||
coolDownTimer -= deltaTime;
|
||||
}
|
||||
|
||||
private void UpdateDistanceAccumulator()
|
||||
{
|
||||
Limb limb = character.animController.limbs[0];
|
||||
distanceAccumulator += (limb.SimPosition - prevPosition).Length();
|
||||
|
||||
prevPosition = limb.body.Position;
|
||||
}
|
||||
|
||||
private void UpdateAttack(float deltaTime)
|
||||
{
|
||||
|
||||
if (selectedTarget == null)
|
||||
{
|
||||
state = AiState.None;
|
||||
return;
|
||||
}
|
||||
|
||||
selectedTargetMemory.Priority -= deltaTime;
|
||||
|
||||
Vector2 attackPosition = selectedTarget.Position;
|
||||
if (wallAttackPos != Vector2.Zero) attackPosition = wallAttackPos;
|
||||
|
||||
if (coolDownTimer>0.0f)
|
||||
{
|
||||
coolDownTimer -= deltaTime;
|
||||
|
||||
//System.Diagnostics.Debug.WriteLine("cooldown");
|
||||
|
||||
if (selectedTarget.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);
|
||||
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (raycastTimer > 0.0)
|
||||
{
|
||||
raycastTimer -= deltaTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
targetEntity = null;
|
||||
//check if there's a wall between the target and the character
|
||||
Vector2 rayStart = character.animController.limbs[0].SimPosition;
|
||||
Vector2 rayEnd = selectedTarget.Position;
|
||||
Structure closestStructure = Map.CheckVisibility(rayStart, rayEnd);
|
||||
if (Map.LastPickedFraction == 1.0f || closestStructure == null)
|
||||
{
|
||||
wallAttackPos = Vector2.Zero;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
|
||||
Structure wall = closestStructure as Structure;
|
||||
if (wall==null)
|
||||
{
|
||||
wallAttackPos = Map.LastPickedPosition;
|
||||
}
|
||||
else
|
||||
{
|
||||
int sectionIndex = wall.FindSectionIndex(ConvertUnits.ToDisplayUnits(Map.LastPickedPosition));
|
||||
|
||||
float sectionDamage = wall.SectionDamage(sectionIndex);
|
||||
for (int i = sectionIndex-2; i<=sectionIndex+2; i++)
|
||||
{
|
||||
if (wall.SectionHasHole(i))
|
||||
{
|
||||
sectionIndex = i;
|
||||
break;
|
||||
}
|
||||
if (wall.SectionDamage(i) > sectionDamage) sectionIndex = i;
|
||||
}
|
||||
wallAttackPos = wall.SectionPosition(sectionIndex);
|
||||
wallAttackPos = ConvertUnits.ToSimUnits(wallAttackPos);
|
||||
}
|
||||
|
||||
|
||||
targetEntity = closestStructure;
|
||||
}
|
||||
|
||||
raycastTimer = RaycastInterval;
|
||||
}
|
||||
|
||||
steeringManager.SteeringSeek(attackPosition);
|
||||
|
||||
//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 || limb.attack.type == Attack.Type.None) continue;
|
||||
if (Vector2.Distance(limb.SimPosition, attackPosition) > limb.attack.range) continue;
|
||||
|
||||
attackingLimb = limb;
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateLimbAttack(deltaTime, attackingLimb, attackPosition);
|
||||
|
||||
}
|
||||
|
||||
private void UpdateLimbAttack(float deltaTime, Limb limb, Vector2 attackPosition)
|
||||
{
|
||||
//DamageType damageType = DamageType.None;
|
||||
// float damage = 0.0f;
|
||||
|
||||
//bool hasAttacked = false;
|
||||
|
||||
IDamageable damageTarget = null;
|
||||
|
||||
switch (limb.attack.type)
|
||||
{
|
||||
case Attack.Type.PinchCW:
|
||||
case Attack.Type.PinchCCW:
|
||||
|
||||
float dir = (limb.attack.type == Attack.Type.PinchCW) ? 1.0f : -1.0f;
|
||||
float dist = Vector2.Distance(limb.SimPosition, attackPosition);
|
||||
|
||||
if (wallAttackPos != Vector2.Zero && targetEntity != null)
|
||||
{
|
||||
damageTarget = targetEntity as IDamageable;
|
||||
}
|
||||
else
|
||||
{
|
||||
damageTarget = selectedTarget.entity as IDamageable;
|
||||
}
|
||||
|
||||
attackTimer += deltaTime*0.05f;
|
||||
|
||||
if (damageTarget == null)
|
||||
{
|
||||
attackTimer = limb.attack.duration;
|
||||
break;
|
||||
}
|
||||
|
||||
if (dist < limb.attack.range * 0.5f)
|
||||
{
|
||||
attackTimer += deltaTime;
|
||||
limb.body.ApplyTorque(limb.Mass * 50.0f * character.animController.Dir * dir);
|
||||
|
||||
limb.attack.DoDamage(damageTarget, limb.SimPosition, deltaTime, (limb.soundTimer <= 0.0f));
|
||||
|
||||
limb.soundTimer = Limb.SoundInterval;
|
||||
}
|
||||
else
|
||||
{
|
||||
//limb.body.ApplyTorque(limb.Mass * -20.0f * character.animController.Dir * dir);
|
||||
}
|
||||
|
||||
limb.body.ApplyLinearImpulse(limb.Mass * 10.0f *
|
||||
Vector2.Normalize(attackPosition - limb.SimPosition));
|
||||
|
||||
steeringManager.SteeringSeek(attackPosition + (limb.SimPosition-Position), 5.0f);
|
||||
|
||||
break;
|
||||
default:
|
||||
attackTimer = limb.attack.duration;
|
||||
break;
|
||||
}
|
||||
|
||||
if (attackTimer >= limb.attack.duration)
|
||||
{
|
||||
attackTimer = 0.0f;
|
||||
if (Vector2.Distance(limb.SimPosition, attackPosition)<5.0) coolDownTimer = attackCoolDown;
|
||||
|
||||
System.Diagnostics.Debug.WriteLine("cooldown: " + coolDownTimer);
|
||||
}
|
||||
}
|
||||
|
||||
//private float GetAttackDamage(Limb limb, float deltaTime)
|
||||
//{
|
||||
// return (limb.attack.duration == 0.0f) ? limb.attack.damage : limb.attack.damage * deltaTime;
|
||||
//}
|
||||
|
||||
//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)
|
||||
{
|
||||
|
||||
if (distanceAccumulator<5.0f && Game1.random.Next(1,3)==1)
|
||||
{
|
||||
selectedTarget = null;
|
||||
character.animController.TargetMovement = -character.animController.TargetMovement;
|
||||
state = AiState.None;
|
||||
return;
|
||||
}
|
||||
distanceAccumulator = 0.0f;
|
||||
|
||||
selectedTarget = null;
|
||||
selectedTargetMemory = null;
|
||||
this.targetValue = 0.0f;
|
||||
|
||||
UpdateTargetMemories();
|
||||
|
||||
foreach (AITarget target in AITarget.list)
|
||||
{
|
||||
float valueModifier = 0.0f;
|
||||
float dist = 0.0f;
|
||||
|
||||
Character targetCharacter = target.entity as Character;
|
||||
|
||||
//ignore the aitarget if it is the character itself
|
||||
if (targetCharacter == character) continue;
|
||||
|
||||
if (targetCharacter!=null)
|
||||
{
|
||||
if (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.animController.limbs[0].SimPosition,
|
||||
target.Position);
|
||||
dist = ConvertUnits.ToDisplayUnits(dist);
|
||||
|
||||
AITargetMemory targetMemory = FindTargetMemory(target);
|
||||
|
||||
valueModifier *= targetMemory.Priority;
|
||||
//dist -= targetMemory.Priority;
|
||||
|
||||
if (valueModifier != 0.0f && (dist < target.SightRange * sight || dist < target.SoundRange * hearing))
|
||||
{
|
||||
//if the target is a character, check if it is visible
|
||||
if (targetCharacter != null)
|
||||
{
|
||||
Vector2 rayStart = character.animController.limbs[0].SimPosition;
|
||||
Vector2 rayEnd = target.Position;
|
||||
|
||||
Structure closestStructure = Map.CheckVisibility(rayStart, rayEnd);
|
||||
//System.Diagnostics.Debug.WriteLine("closestfraction: " + closestFraction);
|
||||
//if not visible, ignore this AItarget
|
||||
if (closestStructure != null) continue;
|
||||
}
|
||||
|
||||
float newTargetValue = valueModifier/dist;
|
||||
if (selectedTarget == null || Math.Abs(newTargetValue) > Math.Abs(this.targetValue))
|
||||
{
|
||||
selectedTarget = target;
|
||||
selectedTargetMemory = targetMemory;
|
||||
targetValue = newTargetValue;
|
||||
Debug.WriteLine(selectedTarget);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//selectedTarget = bestTarget;
|
||||
//selectedTargetMemory = targetMemory;
|
||||
//this.targetValue = bestTargetValue;
|
||||
}
|
||||
|
||||
//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 (memory.Value.Priority == 0.0f || !AITarget.list.Contains(memory.Key)) toBeRemoved.Add(memory.Key);
|
||||
}
|
||||
|
||||
foreach (AITarget target in toBeRemoved)
|
||||
{
|
||||
targetMemories.Remove(target);
|
||||
}
|
||||
}
|
||||
|
||||
public override void FillNetworkData(NetOutgoingMessage message)
|
||||
{
|
||||
message.Write((byte)state);
|
||||
|
||||
message.Write(wallAttackPos.X);
|
||||
message.Write(wallAttackPos.Y);
|
||||
|
||||
message.Write(steeringManager.WanderAngle);
|
||||
message.Write(updateTargetsTimer);
|
||||
message.Write(raycastTimer);
|
||||
message.Write(coolDownTimer);
|
||||
|
||||
message.Write(targetEntity==null ? -1 : targetEntity.ID);
|
||||
}
|
||||
|
||||
public override void ReadNetworkData(NetIncomingMessage message)
|
||||
{
|
||||
state = (AiState)(message.ReadByte());
|
||||
|
||||
wallAttackPos.X = message.ReadFloat();
|
||||
wallAttackPos.Y = message.ReadFloat();
|
||||
|
||||
float wanderAngle = message.ReadFloat();
|
||||
float updateTargetsTimer = message.ReadFloat();
|
||||
float raycastTimer = message.ReadFloat();
|
||||
float coolDownTimer = message.ReadFloat();
|
||||
|
||||
int targetID = message.ReadInt32();
|
||||
|
||||
steeringManager.WanderAngle = wanderAngle;
|
||||
this.updateTargetsTimer = updateTargetsTimer;
|
||||
this.raycastTimer = raycastTimer;
|
||||
this.coolDownTimer = coolDownTimer;
|
||||
|
||||
if (targetID>-1)
|
||||
targetEntity = Entity.FindEntityByID(targetID) as MapEntity;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//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 AITarget target;
|
||||
private float priority;
|
||||
|
||||
//public AITarget Target
|
||||
//{
|
||||
// get { return target; }
|
||||
//}
|
||||
|
||||
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,27 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Subsurface
|
||||
{
|
||||
interface ISteerable
|
||||
{
|
||||
|
||||
Vector2 Steering
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
Vector2 Velocity
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
|
||||
|
||||
Vector2 Position
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
using System;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Subsurface
|
||||
{
|
||||
class SteeringManager
|
||||
{
|
||||
private const float CircleDistance = 2.5f;
|
||||
private const float CircleRadius = 0.3f;
|
||||
|
||||
private const float RayCastInterval = 0.5f;
|
||||
|
||||
private 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 = ToolBox.RandomFloat(0.0f, MathHelper.TwoPi);
|
||||
}
|
||||
|
||||
public void SteeringSeek(Vector2 target, float speed = 1.0f)
|
||||
{
|
||||
steering += DoSteeringSeek(target, speed);
|
||||
}
|
||||
|
||||
public void SteeringWander(float speed = 1.0f)
|
||||
{
|
||||
steering += DoSteeringWander(speed);
|
||||
}
|
||||
|
||||
public void SteeringAvoid(float deltaTime, float speed)
|
||||
{
|
||||
steering += DoSteeringAvoid(deltaTime, speed);
|
||||
}
|
||||
|
||||
public void Update(float speed = 1.0f)
|
||||
{
|
||||
float steeringSpeed = steering.Length();
|
||||
if (steeringSpeed>speed)
|
||||
{
|
||||
steering = Vector2.Normalize(steering) * Math.Abs(speed);
|
||||
}
|
||||
|
||||
host.Steering = steering;
|
||||
}
|
||||
|
||||
private Vector2 DoSteeringSeek(Vector2 target, float speed = 1.0f)
|
||||
{
|
||||
Vector2 targetVel = target - host.Position;
|
||||
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;
|
||||
}
|
||||
|
||||
private Vector2 DoSteeringWander(float speed = 1.0f)
|
||||
{
|
||||
Vector2 circleCenter = (host.Velocity == Vector2.Zero) ? new Vector2(speed, 0.0f) : host.Velocity;
|
||||
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 += ToolBox.RandomFloat(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;
|
||||
}
|
||||
|
||||
private 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.Position + Vector2.Normalize(host.Steering)*MaxDistance;
|
||||
|
||||
if (rayCastTimer <= 0.0f)
|
||||
{
|
||||
rayCastTimer = RayCastInterval;
|
||||
Structure closestStructure = Map.CheckVisibility(host.Position, ahead);
|
||||
if (closestStructure == null)
|
||||
{
|
||||
avoidSteering = Vector2.Zero;
|
||||
return Vector2.Zero;
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector2 obstaclePosition = Map.LastPickedPosition;
|
||||
if (closestStructure.IsHorizontal)
|
||||
{
|
||||
obstaclePosition.Y = closestStructure.SimPosition.Y;
|
||||
}
|
||||
else
|
||||
{
|
||||
obstaclePosition.X = closestStructure.SimPosition.X;
|
||||
}
|
||||
|
||||
avoidSteering = Vector2.Normalize(Map.LastPickedPosition - obstaclePosition);
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
rayCastTimer -= deltaTime;
|
||||
}
|
||||
|
||||
return avoidSteering * speed;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Subsurface
|
||||
{
|
||||
class SteeringPath
|
||||
{
|
||||
private Queue<Vector2> nodes;
|
||||
|
||||
const float minDistance = 0.1f;
|
||||
|
||||
Vector2 currentNode;
|
||||
|
||||
public SteeringPath()
|
||||
{
|
||||
nodes = new Queue<Vector2>();
|
||||
}
|
||||
|
||||
public void AddNode(Vector2 node)
|
||||
{
|
||||
if (node == Vector2.Zero) return;
|
||||
nodes.Enqueue(node);
|
||||
}
|
||||
|
||||
public Vector2 CurrentNode
|
||||
{
|
||||
get { return currentNode; }
|
||||
}
|
||||
|
||||
public Vector2 GetNode(Vector2 pos)
|
||||
{
|
||||
if (nodes.Count == 0) return Vector2.Zero;
|
||||
if (currentNode==null || currentNode==Vector2.Zero || Vector2.Distance(pos, currentNode)<minDistance) currentNode = nodes.Dequeue();
|
||||
|
||||
return currentNode;
|
||||
}
|
||||
|
||||
public void ClearPath()
|
||||
{
|
||||
nodes.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using System.Xml.Linq;
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Subsurface
|
||||
{
|
||||
class AnimController : Ragdoll
|
||||
{
|
||||
protected Character character;
|
||||
|
||||
public bool isStanding;
|
||||
|
||||
public enum Animation { None, Climbing, UsingConstruction, Struggle };
|
||||
public Animation anim;
|
||||
|
||||
protected float walkSpeed, swimSpeed;
|
||||
|
||||
public Direction targetDir;
|
||||
|
||||
//how large impacts the character can take before being stunned
|
||||
//protected float impactTolerance;
|
||||
|
||||
protected float stunTimer;
|
||||
|
||||
protected float walkPos;
|
||||
|
||||
protected readonly Vector2 stepSize;
|
||||
protected readonly float legTorque;
|
||||
protected readonly Vector2 stepOffset;
|
||||
|
||||
public float StunTimer
|
||||
{
|
||||
get { return stunTimer; }
|
||||
set { stunTimer = value; }
|
||||
}
|
||||
|
||||
public AnimController(Character character, XElement element)
|
||||
: base(character, element)
|
||||
{
|
||||
this.character = character;
|
||||
|
||||
stepSize = ToolBox.GetAttributeVector2(element, "stepsize", Vector2.One);
|
||||
stepSize = ConvertUnits.ToSimUnits(stepSize);
|
||||
|
||||
stepOffset = ToolBox.GetAttributeVector2(element, "stepoffset", Vector2.One);
|
||||
stepOffset = ConvertUnits.ToSimUnits(stepOffset);
|
||||
|
||||
//impactTolerance = ToolBox.GetAttributeFloat(element, "impacttolerance", 10.0f);
|
||||
|
||||
legTorque = ToolBox.GetAttributeFloat(element, "legtorque", 0.0f);
|
||||
}
|
||||
|
||||
public virtual void UpdateAnim(float deltaTime) { }
|
||||
|
||||
public virtual void HoldItem(float deltaTime, Camera cam, Item item, Vector2[] handlePos, Vector2 holdPos, Vector2 aimPos, float holdAngle) { }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,963 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Dynamics.Joints;
|
||||
using Lidgren.Network;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Subsurface.Networking;
|
||||
using Subsurface.Particles;
|
||||
|
||||
namespace Subsurface
|
||||
{
|
||||
class Character : Entity, IDamageable
|
||||
{
|
||||
public static List<Character> characterList = new List<Character>();
|
||||
|
||||
public static Queue<CharacterInfo> newCharacterQueue = new Queue<CharacterInfo>();
|
||||
|
||||
public static bool disableControls;
|
||||
|
||||
//the character that the player is currently controlling
|
||||
private static Character controlled;
|
||||
|
||||
public static Character Controlled
|
||||
{
|
||||
get { return controlled; }
|
||||
set { controlled = value; }
|
||||
}
|
||||
|
||||
public readonly bool IsNetworkPlayer;
|
||||
|
||||
private Inventory inventory;
|
||||
|
||||
public double lastNetworkUpdate;
|
||||
|
||||
public byte largeUpdateTimer;
|
||||
|
||||
public readonly Dictionary<string, ObjectProperty> properties;
|
||||
|
||||
protected Key selectKeyHit;
|
||||
protected Key actionKeyHit;
|
||||
protected Key actionKeyDown;
|
||||
protected Key secondaryKeyHit;
|
||||
protected Key secondaryKeyDown;
|
||||
|
||||
private Item selectedConstruction;
|
||||
private Item[] selectedItems;
|
||||
|
||||
public AnimController animController;
|
||||
private AIController aiController;
|
||||
|
||||
private Vector2 cursorPosition;
|
||||
|
||||
protected bool needsAir;
|
||||
protected float oxygen;
|
||||
protected float drowningTime;
|
||||
|
||||
protected Item closestItem;
|
||||
|
||||
protected bool isDead;
|
||||
|
||||
bool isHumanoid;
|
||||
|
||||
//the name of the species (e.q. human)
|
||||
public readonly string speciesName;
|
||||
|
||||
public CharacterInfo info;
|
||||
|
||||
protected float soundTimer;
|
||||
protected float soundInterval;
|
||||
|
||||
private float blood;
|
||||
|
||||
private Sound[] sounds;
|
||||
//which AIstate each sound is for
|
||||
private AIController.AiState[] soundStates;
|
||||
|
||||
public Inventory Inventory
|
||||
{
|
||||
get { return inventory; }
|
||||
}
|
||||
|
||||
public Vector2 CursorPosition
|
||||
{
|
||||
get { return cursorPosition; }
|
||||
}
|
||||
|
||||
public float SoundRange
|
||||
{
|
||||
get { return aiTarget.SoundRange; }
|
||||
}
|
||||
|
||||
public float SightRange
|
||||
{
|
||||
get { return aiTarget.SightRange; }
|
||||
}
|
||||
private float pressureProtection;
|
||||
public float PressureProtection
|
||||
{
|
||||
get { return pressureProtection; }
|
||||
set
|
||||
{
|
||||
//if (!value && pressureProtection) Debug.WriteLine("pp set: " + value);
|
||||
pressureProtection = MathHelper.Clamp(value, 0.0f, 100.0f);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public float Oxygen
|
||||
{
|
||||
get { return oxygen; }
|
||||
set
|
||||
{
|
||||
oxygen = MathHelper.Clamp(value, 0.0f, 100.0f);
|
||||
if (oxygen == 0.0f) Kill();
|
||||
}
|
||||
}
|
||||
|
||||
public float Blood
|
||||
{
|
||||
get { return blood; }
|
||||
set
|
||||
{
|
||||
blood = MathHelper.Clamp(value, 0.0f, 100.0f);
|
||||
if (blood == 0.0f) Kill();
|
||||
}
|
||||
}
|
||||
|
||||
public Item[] SelectedItems
|
||||
{
|
||||
get { return selectedItems; }
|
||||
}
|
||||
|
||||
public bool HasSelectedItem(Item item)
|
||||
{
|
||||
return selectedItems.Contains(item);
|
||||
}
|
||||
|
||||
public bool TrySelectItem(Item item)
|
||||
{
|
||||
bool rightHand = ((CharacterInventory)inventory).IsInLimbSlot(item, LimbSlot.RightHand);
|
||||
bool leftHand = ((CharacterInventory)inventory).IsInLimbSlot(item, LimbSlot.LeftHand);
|
||||
|
||||
bool selected = false;
|
||||
if (rightHand && SelectedItems[0] == null)
|
||||
{
|
||||
selectedItems[0] = item;
|
||||
selected = true;
|
||||
}
|
||||
if (leftHand && SelectedItems[1] == null)
|
||||
{
|
||||
selectedItems[1] = item;
|
||||
selected = true;
|
||||
}
|
||||
|
||||
return selected;
|
||||
}
|
||||
|
||||
public bool TrySelectItem(Item item, int index)
|
||||
{
|
||||
if (selectedItems[index] != null) return false;
|
||||
|
||||
selectedItems[index] = item;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void DeselectItem(Item item)
|
||||
{
|
||||
for (int i = 0; i < selectedItems.Length; i++)
|
||||
{
|
||||
if (selectedItems[i] == item) selectedItems[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
public Item SelectedConstruction
|
||||
{
|
||||
get { return selectedConstruction; }
|
||||
set { selectedConstruction = value; }
|
||||
}
|
||||
|
||||
public Item ClosestItem
|
||||
{
|
||||
get { return closestItem; }
|
||||
}
|
||||
|
||||
public Key SelectKeyHit
|
||||
{
|
||||
get { return selectKeyHit; }
|
||||
}
|
||||
|
||||
public Key ActionKeyHit
|
||||
{
|
||||
get { return actionKeyHit; }
|
||||
}
|
||||
|
||||
public Key ActionKeyDown
|
||||
{
|
||||
get { return actionKeyDown; }
|
||||
}
|
||||
|
||||
public Key SecondaryKeyHit
|
||||
{
|
||||
get { return secondaryKeyHit; }
|
||||
}
|
||||
|
||||
public Key SecondaryKeyDown
|
||||
{
|
||||
get { return secondaryKeyDown; }
|
||||
}
|
||||
|
||||
public bool IsDead
|
||||
{
|
||||
get { return isDead; }
|
||||
}
|
||||
|
||||
public override Vector2 SimPosition
|
||||
{
|
||||
get { return animController.limbs[0].SimPosition; }
|
||||
}
|
||||
|
||||
public Vector2 Position
|
||||
{
|
||||
get { return ConvertUnits.ToDisplayUnits(animController.limbs[0].SimPosition); }
|
||||
}
|
||||
|
||||
public Character(string file) : this(file, Vector2.Zero, null)
|
||||
{
|
||||
}
|
||||
|
||||
public Character(string file, Vector2 position)
|
||||
: this(file, position, null)
|
||||
{
|
||||
}
|
||||
|
||||
public Character(CharacterInfo characterInfo, Vector2 position, bool isNetworkPlayer = false)
|
||||
: this(characterInfo.file, position, characterInfo, isNetworkPlayer)
|
||||
{
|
||||
}
|
||||
|
||||
public Character(string file, Vector2 position, CharacterInfo characterInfo = null, bool isNetworkPlayer = false)
|
||||
{
|
||||
selectKeyHit = new Key(false);
|
||||
actionKeyDown = new Key(true);
|
||||
actionKeyHit = new Key(false);
|
||||
secondaryKeyHit = new Key(false);
|
||||
secondaryKeyDown = new Key(true);
|
||||
|
||||
selectedItems = new Item[2];
|
||||
|
||||
IsNetworkPlayer = isNetworkPlayer;
|
||||
|
||||
oxygen = 100.0f;
|
||||
blood = 100.0f;
|
||||
aiTarget = new AITarget(this);
|
||||
|
||||
properties = ObjectProperty.GetProperties(this);
|
||||
|
||||
XDocument doc = ToolBox.TryLoadXml(file);
|
||||
if (doc == null) return;
|
||||
|
||||
speciesName = ToolBox.GetAttributeString(doc.Root, "name", "Unknown");
|
||||
|
||||
isHumanoid = ToolBox.GetAttributeBool(doc.Root, "humanoid", false);
|
||||
|
||||
info = characterInfo ?? new CharacterInfo(file);
|
||||
|
||||
if (isHumanoid)
|
||||
{
|
||||
animController = new HumanoidAnimController(this, doc.Root.Element("ragdoll"));
|
||||
animController.targetDir = Direction.Right;
|
||||
inventory = new CharacterInventory(10, this);
|
||||
}
|
||||
else
|
||||
{
|
||||
animController = new FishAnimController(this, doc.Root.Element("ragdoll"));
|
||||
PressureProtection = 100.0f;
|
||||
//FishAnimController fishAnim = (FishAnimController)animController;
|
||||
|
||||
aiController = new EnemyAIController(this, file);
|
||||
}
|
||||
|
||||
foreach (Limb limb in animController.limbs)
|
||||
{
|
||||
limb.body.SetTransform(position+limb.SimPosition, 0.0f);
|
||||
//limb.prevPosition = ConvertUnits.ToDisplayUnits(position);
|
||||
}
|
||||
|
||||
needsAir = ToolBox.GetAttributeBool(doc.Root, "needsair", false);
|
||||
drowningTime = ToolBox.GetAttributeFloat(doc.Root, "drowningtime", 10.0f);
|
||||
|
||||
soundInterval = ToolBox.GetAttributeFloat(doc.Root, "soundinterval", 10.0f);
|
||||
|
||||
var xSounds = doc.Root.Elements("sound");
|
||||
if (xSounds.Count() > 0)
|
||||
{
|
||||
sounds = new Sound[xSounds.Count()];
|
||||
soundStates = new AIController.AiState[xSounds.Count()];
|
||||
int i = 0;
|
||||
foreach (XElement xSound in xSounds)
|
||||
{
|
||||
sounds[i] = Sound.Load(xSound.Attribute("file").Value);
|
||||
if (xSound.Attribute("state") == null)
|
||||
{
|
||||
soundStates[i] = AIController.AiState.None;
|
||||
}
|
||||
else
|
||||
{
|
||||
soundStates[i] = (AIController.AiState)Enum.Parse(
|
||||
typeof(AIController.AiState), xSound.Attribute("state").Value, true);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
animController.FindHull();
|
||||
|
||||
if (this.info.ID >= 0)
|
||||
{
|
||||
ID = this.info.ID;
|
||||
}
|
||||
|
||||
characterList.Add(this);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Control the characte
|
||||
/// </summary>
|
||||
public void Control(Camera cam, bool forcePick=false)
|
||||
{
|
||||
if (isDead) return;
|
||||
|
||||
//find the closest item if selectkey has been hit, or if the character is being
|
||||
//controlled by the player (in order to highlight it)
|
||||
//closestItem = null;
|
||||
if (controlled==this)
|
||||
{
|
||||
Vector2 mouseSimPos = ConvertUnits.ToSimUnits(cam.ScreenToWorld(PlayerInput.MousePosition));
|
||||
closestItem = FindClosestItem(mouseSimPos);
|
||||
|
||||
if (closestItem != null)
|
||||
{
|
||||
closestItem.IsHighlighted = true;
|
||||
if (selectKeyHit.State && closestItem.Pick(this, forcePick))
|
||||
{
|
||||
new NetworkEvent(NetworkEventType.PickItem, ID, true, closestItem.ID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < selectedItems.Length; i++ )
|
||||
{
|
||||
if (selectedItems[i] == null) continue;
|
||||
if (i == 1 && selectedItems[0] == selectedItems[1]) continue;
|
||||
|
||||
if (actionKeyDown.State) selectedItems[i].Use(this);
|
||||
if (secondaryKeyDown.State && selectedItems[i] != null) selectedItems[i].SecondaryUse(this);
|
||||
|
||||
}
|
||||
|
||||
if (selectedConstruction != null)
|
||||
{
|
||||
if (actionKeyDown.State) selectedConstruction.Use(this);
|
||||
if (secondaryKeyDown.State) selectedConstruction.SecondaryUse(this);
|
||||
}
|
||||
|
||||
if (IsNetworkPlayer)
|
||||
{
|
||||
selectKeyHit.Reset();
|
||||
actionKeyHit.Reset();
|
||||
actionKeyDown.Reset();
|
||||
secondaryKeyHit.Reset();
|
||||
secondaryKeyDown.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
private Item FindClosestItem(Vector2 mouseSimPos)
|
||||
{
|
||||
Limb torso = animController.GetLimb(LimbType.Torso);
|
||||
Vector2 pos = (torso.body.TargetPosition != Vector2.Zero) ? torso.body.TargetPosition : torso.SimPosition;
|
||||
|
||||
return Item.FindPickable(pos, selectedConstruction == null ? mouseSimPos : selectedConstruction.SimPosition, null, selectedItems);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Control the character according to player input
|
||||
/// </summary>
|
||||
public void ControlLocalPlayer(Camera cam, bool moveCam = true)
|
||||
{
|
||||
if (isDead) return;
|
||||
|
||||
Limb head = animController.GetLimb(LimbType.Head);
|
||||
|
||||
Lights.LightManager.viewPos = ConvertUnits.ToDisplayUnits(head.SimPosition);
|
||||
|
||||
Vector2 targetMovement = Vector2.Zero;
|
||||
|
||||
if (!disableControls)
|
||||
{
|
||||
if (PlayerInput.KeyDown(Keys.W)) targetMovement.Y += 1.0f;
|
||||
if (PlayerInput.KeyDown(Keys.S)) targetMovement.Y -= 1.0f;
|
||||
if (PlayerInput.KeyDown(Keys.A)) targetMovement.X -= 1.0f;
|
||||
if (PlayerInput.KeyDown(Keys.D)) targetMovement.X += 1.0f;
|
||||
|
||||
//the vertical component is only used for falling through platforms and climbing ladders when not in water,
|
||||
//so the movement can't be normalized or the character would walk slower when pressing down/up
|
||||
if (animController.InWater)
|
||||
{
|
||||
float length = targetMovement.Length();
|
||||
if (length > 0.0f) targetMovement = targetMovement / length;
|
||||
}
|
||||
|
||||
if (Keyboard.GetState().IsKeyDown(Keys.LeftShift) && Math.Sign(targetMovement.X) == Math.Sign(animController.Dir))
|
||||
targetMovement *= 3.0f;
|
||||
|
||||
selectKeyHit.SetState(PlayerInput.KeyHit(Keys.E));
|
||||
actionKeyHit.SetState(PlayerInput.LeftButtonClicked());
|
||||
actionKeyDown.SetState(PlayerInput.GetMouseState.LeftButton == ButtonState.Pressed);
|
||||
secondaryKeyHit.SetState(PlayerInput.RightButtonClicked());
|
||||
secondaryKeyDown.SetState(PlayerInput.GetMouseState.RightButton == ButtonState.Pressed);
|
||||
}
|
||||
else
|
||||
{
|
||||
selectKeyHit.SetState(false);
|
||||
actionKeyHit.SetState(false);
|
||||
actionKeyDown.SetState(false);
|
||||
secondaryKeyHit.SetState(false);
|
||||
secondaryKeyDown.SetState(false);
|
||||
}
|
||||
|
||||
animController.TargetMovement = targetMovement;
|
||||
animController.isStanding = true;
|
||||
|
||||
if (moveCam)
|
||||
{
|
||||
cam.TargetPos = ConvertUnits.ToDisplayUnits(animController.limbs[0].SimPosition);
|
||||
cam.OffsetAmount = 250.0f;
|
||||
}
|
||||
|
||||
cursorPosition = cam.ScreenToWorld(PlayerInput.MousePosition);
|
||||
Vector2 mouseSimPos = ConvertUnits.ToSimUnits(cursorPosition);
|
||||
|
||||
if (animController.onGround &&
|
||||
!animController.InWater &&
|
||||
animController.anim != AnimController.Animation.UsingConstruction)
|
||||
{
|
||||
if (mouseSimPos.X < head.SimPosition.X-1.0f)
|
||||
{
|
||||
animController.targetDir = Direction.Left;
|
||||
}
|
||||
else if (mouseSimPos.X > head.SimPosition.X + 1.0f)
|
||||
{
|
||||
animController.targetDir = Direction.Right;
|
||||
}
|
||||
}
|
||||
|
||||
disableControls = false;
|
||||
}
|
||||
|
||||
|
||||
public static void UpdateAnimAll(float deltaTime)
|
||||
{
|
||||
foreach (Character c in characterList)
|
||||
{
|
||||
if (c.isDead) continue;
|
||||
c.animController.UpdateAnim(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
public static void UpdateAll(Camera cam, float deltaTime)
|
||||
{
|
||||
if (newCharacterQueue.Count>0)
|
||||
{
|
||||
new Character(newCharacterQueue.Dequeue(), Vector2.Zero);
|
||||
}
|
||||
|
||||
foreach (Character c in characterList)
|
||||
{
|
||||
c.Update(cam, deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
public void Update(Camera cam, float deltaTime)
|
||||
{
|
||||
if (isDead) return;
|
||||
|
||||
Debug.WriteLine("pp: " + PressureProtection);
|
||||
if (PressureProtection==0.0f &&
|
||||
(animController.CurrentHull == null || animController.CurrentHull.LethalPressure >= 100.0f))
|
||||
{
|
||||
Implode();
|
||||
return;
|
||||
}
|
||||
|
||||
if (controlled == this) ControlLocalPlayer(cam);
|
||||
|
||||
Control(cam);
|
||||
|
||||
UpdateSightRange();
|
||||
aiTarget.SoundRange = 0.0f;
|
||||
|
||||
if (needsAir)
|
||||
{
|
||||
if (animController.HeadInWater)
|
||||
{
|
||||
Oxygen -= deltaTime*100.0f / drowningTime;
|
||||
}
|
||||
else if (animController.CurrentHull != null)
|
||||
{
|
||||
float hullOxygen = animController.CurrentHull.OxygenPercentage;
|
||||
hullOxygen -= 30.0f;
|
||||
|
||||
Oxygen += deltaTime * 100.0f * (hullOxygen / 500.0f);
|
||||
|
||||
animController.CurrentHull.Oxygen -= Hull.OxygenConsumptionSpeed * deltaTime;
|
||||
}
|
||||
PressureProtection -= deltaTime*100.0f;
|
||||
}
|
||||
|
||||
if (soundTimer > 0)
|
||||
{
|
||||
soundTimer -= deltaTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
PlaySound((aiController == null) ? AIController.AiState.None : aiController.State);
|
||||
soundTimer = soundInterval;
|
||||
}
|
||||
|
||||
foreach (Limb limb in animController.limbs)
|
||||
{
|
||||
Blood = blood - limb.Bleeding * deltaTime * 0.1f;
|
||||
}
|
||||
|
||||
if (aiController != null) aiController.Update(deltaTime);
|
||||
}
|
||||
|
||||
private void UpdateSightRange()
|
||||
{
|
||||
aiTarget.SightRange = 0.0f;
|
||||
|
||||
//distance is approximated based on the mass of the character
|
||||
//(which corresponds to size because all the characters have the same limb density)
|
||||
foreach (Limb limb in animController.limbs)
|
||||
{
|
||||
aiTarget.SightRange += limb.Mass * 1000.0f;
|
||||
}
|
||||
//the faster the character is moving, the easier it is to see it
|
||||
Limb torso = animController.GetLimb(LimbType.Torso);
|
||||
if (torso !=null)
|
||||
{
|
||||
aiTarget.SightRange += torso.LinearVelocity.Length() * 500.0f;
|
||||
}
|
||||
}
|
||||
|
||||
public void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
animController.Draw(spriteBatch);
|
||||
|
||||
if (IsNetworkPlayer)
|
||||
{
|
||||
Vector2 pos = new Vector2(Position.X, -Position.Y - 50.0f) - GUI.font.MeasureString(info.name) * 0.5f;
|
||||
spriteBatch.DrawString(GUI.font, info.name, pos - new Vector2(1.0f, 1.0f), Color.Black);
|
||||
spriteBatch.DrawString(GUI.font, info.name, pos, Color.White);
|
||||
}
|
||||
|
||||
//spriteBatch.DrawString(GUI.font, ID.ToString(), ConvertUnits.ToDisplayUnits(animController.limbs[0].Position), Color.White);
|
||||
//GUI.DrawLine(spriteBatch, ConvertUnits.ToDisplayUnits(animController.limbs[0].SimPosition.X, animController.limbs[0].SimPosition.Y),
|
||||
// ConvertUnits.ToDisplayUnits(animController.limbs[0].SimPosition.X, animController.limbs[0].SimPosition.Y) +
|
||||
// ConvertUnits.ToDisplayUnits(animController.targetMovement.X, animController.targetMovement.Y), Color.Green);
|
||||
}
|
||||
|
||||
public void PlaySound(AIController.AiState state)
|
||||
{
|
||||
if (sounds == null || sounds.Count()==0) return;
|
||||
var matchingSoundStates = soundStates.Where(x => x == state).ToList();
|
||||
|
||||
int selectedSound = Game1.localRandom.Next(matchingSoundStates.Count());
|
||||
|
||||
int n = 0;
|
||||
for (int i = 0; i < sounds.Count(); i++)
|
||||
{
|
||||
if (soundStates[i] != state) continue;
|
||||
if (n == selectedSound)
|
||||
{
|
||||
sounds[i].Play(1.0f, 2000.0f,
|
||||
animController.limbs[0].body.FarseerBody);
|
||||
Debug.WriteLine("playing: " + sounds[i]);
|
||||
return;
|
||||
}
|
||||
n++;
|
||||
}
|
||||
}
|
||||
|
||||
public void AddDamage(Vector2 position, float amount, float bleedingAmount, float stun)
|
||||
{
|
||||
int bloodAmount = 0;
|
||||
|
||||
animController.StunTimer = Math.Max(animController.StunTimer, stun);
|
||||
|
||||
Limb closestLimb = null;
|
||||
float closestDistance = 0.0f;
|
||||
foreach (Limb limb in animController.limbs)
|
||||
{
|
||||
float distance = Vector2.Distance(position, limb.SimPosition);
|
||||
if (closestLimb == null || distance < closestDistance)
|
||||
{
|
||||
closestLimb = limb;
|
||||
closestDistance = distance;
|
||||
}
|
||||
}
|
||||
|
||||
Vector2 pull = position - closestLimb.SimPosition;
|
||||
if (pull != Vector2.Zero) pull = Vector2.Normalize(pull);
|
||||
closestLimb.body.ApplyForce(pull*Math.Min(amount*100.0f, 100.0f));
|
||||
|
||||
closestLimb.Bleeding += bleedingAmount;
|
||||
closestLimb.Damage += amount;
|
||||
bloodAmount = (int)Math.Min((int)(amount*2.0f),20);
|
||||
//if (closestLimb.Damage>=100.0f)
|
||||
//{
|
||||
// bloodAmount *= 2;
|
||||
// foreach (var joint in animController.limbJoints)
|
||||
// {
|
||||
// if (!(joint.BodyA == closestLimb.body.FarseerBody) && !(joint.BodyB == closestLimb.body.FarseerBody)) continue;
|
||||
|
||||
// joint.Enabled = false;
|
||||
// break;
|
||||
// }
|
||||
//}
|
||||
|
||||
for (int i = 0; i < bloodAmount; i++)
|
||||
{
|
||||
Vector2 particleVel = closestLimb.SimPosition-position;
|
||||
if (particleVel != Vector2.Zero) particleVel = Vector2.Normalize(particleVel);
|
||||
|
||||
Game1.particleManager.CreateParticle(
|
||||
closestLimb.SimPosition,
|
||||
ToolBox.RandomFloat(0.0f, 3.1f),
|
||||
particleVel * ToolBox.RandomFloat(1.0f,3.0f),
|
||||
"blood");
|
||||
}
|
||||
|
||||
for (int i = 0; i < bloodAmount / 2; i++)
|
||||
{
|
||||
Game1.particleManager.CreateParticle(closestLimb.SimPosition,
|
||||
0.0f,
|
||||
Vector2.Zero, "waterblood");
|
||||
}
|
||||
}
|
||||
|
||||
public void Stun()
|
||||
{
|
||||
for (int i = 0; i < selectedItems.Length; i++ )
|
||||
{
|
||||
if (selectedItems[i] == null) continue;
|
||||
selectedItems[i].Drop();
|
||||
selectedItems[i] = null;
|
||||
}
|
||||
|
||||
selectedConstruction = null;
|
||||
}
|
||||
|
||||
private void Implode()
|
||||
{
|
||||
Limb torso= animController.GetLimb(LimbType.Torso);
|
||||
if (torso == null) torso = animController.GetLimb(LimbType.Head);
|
||||
|
||||
Vector2 centerOfMass = Vector2.Zero;
|
||||
float totalMass = 0.0f;
|
||||
foreach (Limb limb in animController.limbs)
|
||||
{
|
||||
centerOfMass += limb.Mass * limb.SimPosition;
|
||||
totalMass += limb.Mass;
|
||||
}
|
||||
|
||||
centerOfMass /= totalMass;
|
||||
|
||||
foreach (Limb limb in animController.limbs)
|
||||
{
|
||||
Vector2 diff = centerOfMass - limb.SimPosition;
|
||||
if (diff == Vector2.Zero) continue;
|
||||
limb.body.ApplyLinearImpulse(diff * 10.0f);
|
||||
limb.Damage = 100.0f;
|
||||
}
|
||||
|
||||
AmbientSoundManager.PlayDamageSound(DamageSoundType.Implode, 50.0f, torso.body.FarseerBody);
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
Particle p = Game1.particleManager.CreateParticle(
|
||||
torso.SimPosition + new Vector2(ToolBox.RandomFloat(-0.5f, 0.5f), ToolBox.RandomFloat(-0.5f, 0.5f)),
|
||||
0.0f,
|
||||
Vector2.Zero, "waterblood");
|
||||
if (p!=null) p.Size *= 2.0f;
|
||||
|
||||
Game1.particleManager.CreateParticle(
|
||||
torso.SimPosition,
|
||||
0.0f,
|
||||
new Vector2(ToolBox.RandomFloat(-0.5f, 0.5f), ToolBox.RandomFloat(-1.0f,0.5f)),
|
||||
"bubbles");
|
||||
}
|
||||
|
||||
foreach (var joint in animController.limbJoints)
|
||||
{
|
||||
joint.LimitEnabled = false;
|
||||
}
|
||||
Kill(true);
|
||||
}
|
||||
|
||||
public void Kill(bool networkMessage = false)
|
||||
{
|
||||
if (isDead) return;
|
||||
|
||||
//if the game is run by a client, characters are only killed when the server says so
|
||||
if (Game1.client != null)
|
||||
{
|
||||
if (networkMessage)
|
||||
{
|
||||
new NetworkEvent(NetworkEventType.KillCharacter, ID, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (Game1.server != null)
|
||||
{
|
||||
new NetworkEvent(NetworkEventType.KillCharacter, ID, false);
|
||||
}
|
||||
|
||||
if (Game1.gameSession.crewManager!=null)
|
||||
{
|
||||
Game1.gameSession.crewManager.KillCharacter(this);
|
||||
}
|
||||
|
||||
isDead = true;
|
||||
animController.movement = Vector2.Zero;
|
||||
animController.TargetMovement = Vector2.Zero;
|
||||
|
||||
for (int i = 0; i < selectedItems.Length; i++ )
|
||||
{
|
||||
if (selectedItems[i] != null) selectedItems[i].Drop(this);
|
||||
}
|
||||
|
||||
|
||||
aiTarget.Remove();
|
||||
aiTarget = null;
|
||||
|
||||
foreach (Limb limb in animController.limbs)
|
||||
{
|
||||
if (limb.pullJoint == null) continue;
|
||||
limb.pullJoint.Enabled = false;
|
||||
}
|
||||
|
||||
foreach (RevoluteJoint joint in animController.limbJoints)
|
||||
{
|
||||
joint.MotorEnabled = false;
|
||||
joint.MaxMotorTorque = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
public override void FillNetworkData(NetworkEventType type, NetOutgoingMessage message, object data)
|
||||
{
|
||||
if (type == NetworkEventType.PickItem)
|
||||
{
|
||||
message.Write((int)data);
|
||||
Debug.WriteLine("pickitem");
|
||||
return;
|
||||
}
|
||||
else if (type == NetworkEventType.KillCharacter)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
//if (type == Networking.NetworkEventType.KeyHit)
|
||||
//{
|
||||
// message.Write(selectKeyHit.Dequeue);
|
||||
message.Write(actionKeyDown.Dequeue);
|
||||
message.Write(secondaryKeyDown.Dequeue);
|
||||
//}
|
||||
|
||||
message.Write(NetTime.Now);
|
||||
|
||||
// Write byte = move direction
|
||||
message.Write(animController.TargetMovement.X);
|
||||
message.Write(animController.TargetMovement.Y);
|
||||
|
||||
message.Write(animController.targetDir==Direction.Right);
|
||||
|
||||
message.Write(cursorPosition.X);
|
||||
message.Write(cursorPosition.Y);
|
||||
|
||||
message.Write(largeUpdateTimer <= 0);
|
||||
|
||||
if (largeUpdateTimer<=0)
|
||||
{
|
||||
foreach (Limb limb in animController.limbs)
|
||||
{
|
||||
message.Write(limb.body.Position.X);
|
||||
message.Write(limb.body.Position.Y);
|
||||
|
||||
message.Write(limb.body.LinearVelocity.X);
|
||||
message.Write(limb.body.LinearVelocity.Y);
|
||||
|
||||
message.Write(limb.body.Rotation);
|
||||
message.Write(limb.body.AngularVelocity);
|
||||
}
|
||||
|
||||
message.Write(animController.StunTimer);
|
||||
|
||||
largeUpdateTimer = 5;
|
||||
}
|
||||
else
|
||||
{
|
||||
Limb torso = animController.GetLimb(LimbType.Torso);
|
||||
message.Write(torso.body.Position.X);
|
||||
message.Write(torso.body.Position.Y);
|
||||
|
||||
largeUpdateTimer = (byte)Math.Max(0, largeUpdateTimer-1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (aiController != null) aiController.FillNetworkData(message);
|
||||
|
||||
}
|
||||
|
||||
public override void ReadNetworkData(NetworkEventType type, NetIncomingMessage message)
|
||||
{
|
||||
if (type == NetworkEventType.PickItem)
|
||||
{
|
||||
int itemId = message.ReadInt32();
|
||||
Item item = FindEntityByID(itemId) as Item;
|
||||
if (item != null)
|
||||
{
|
||||
Debug.WriteLine("pickitem "+itemId );
|
||||
item.Pick(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
}
|
||||
|
||||
//DebugConsole.ThrowError("pickitem");
|
||||
|
||||
return;
|
||||
}
|
||||
else if (type == NetworkEventType.KillCharacter)
|
||||
{
|
||||
Kill(true);
|
||||
if (Game1.client != null && controlled == this)
|
||||
{
|
||||
Game1.client.AddChatMessage("YOU HAVE DIED. Your chat messages will only be visible to other dead players.", ChatMessageType.Dead);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//if (type == Networking.NetworkEventType.KeyHit)
|
||||
//{
|
||||
// selectKeyHit.State = message.ReadBoolean();
|
||||
|
||||
//}
|
||||
actionKeyDown.State = message.ReadBoolean();
|
||||
secondaryKeyDown.State = message.ReadBoolean();
|
||||
|
||||
double sendingTime = message.ReadDouble();
|
||||
|
||||
Vector2 targetMovement = Vector2.Zero;
|
||||
|
||||
targetMovement.X = message.ReadFloat();
|
||||
targetMovement.Y = message.ReadFloat();
|
||||
|
||||
animController.isStanding = true;
|
||||
|
||||
bool targetDir = message.ReadBoolean();
|
||||
|
||||
Vector2 cursorPos = Vector2.Zero;
|
||||
cursorPos.X = message.ReadFloat();
|
||||
cursorPos.Y = message.ReadFloat();
|
||||
|
||||
if (sendingTime > lastNetworkUpdate)
|
||||
{
|
||||
cursorPosition = cursorPos;
|
||||
|
||||
animController.TargetMovement= targetMovement;
|
||||
animController.targetDir = (targetDir) ? Direction.Right : Direction.Left;
|
||||
|
||||
if (message.ReadBoolean())
|
||||
{
|
||||
foreach (Limb limb in animController.limbs)
|
||||
{
|
||||
Vector2 pos = Vector2.Zero;
|
||||
pos.X = message.ReadFloat();
|
||||
pos.Y = message.ReadFloat();
|
||||
|
||||
Vector2 vel = Vector2.Zero;
|
||||
vel.X = message.ReadFloat();
|
||||
vel.Y = message.ReadFloat();
|
||||
|
||||
float rotation = message.ReadFloat();
|
||||
float angularVel = message.ReadFloat();
|
||||
|
||||
if (limb.body == null) continue;
|
||||
|
||||
if (vel != Vector2.Zero && vel.Length() > 100.0f) { }
|
||||
|
||||
if (pos != Vector2.Zero && pos.Length() > 100.0f) { }
|
||||
|
||||
limb.body.TargetVelocity = vel;
|
||||
limb.body.TargetPosition = pos;// +vel * (float)(deltaTime / 60.0);
|
||||
limb.body.TargetRotation = rotation;// +angularVel * (float)(deltaTime / 60.0);
|
||||
limb.body.TargetAngularVelocity = angularVel;
|
||||
}
|
||||
|
||||
animController.StunTimer = message.ReadFloat();
|
||||
|
||||
largeUpdateTimer = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector2 pos = Vector2.Zero;
|
||||
pos.X = message.ReadFloat();
|
||||
pos.Y = message.ReadFloat();
|
||||
|
||||
Limb torso = animController.GetLimb(LimbType.Torso);
|
||||
torso.body.TargetPosition = pos;
|
||||
|
||||
largeUpdateTimer = 0;
|
||||
}
|
||||
|
||||
if (aiController != null) aiController.ReadNetworkData(message);
|
||||
|
||||
lastNetworkUpdate = sendingTime;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Remove()
|
||||
{
|
||||
base.Remove();
|
||||
|
||||
characterList.Remove(this);
|
||||
|
||||
if (controlled == this) controlled = null;
|
||||
|
||||
if (Game1.client!=null && Game1.client.Character == this) Game1.client.Character = null;
|
||||
|
||||
if (aiTarget != null)
|
||||
aiTarget.Remove();
|
||||
|
||||
if (animController!=null)
|
||||
animController.Remove();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Subsurface
|
||||
{
|
||||
public enum Gender { None, Male, Female };
|
||||
|
||||
class CharacterInfo
|
||||
{
|
||||
//the name of the character (e.q. Urist McEngineer)
|
||||
public string name;
|
||||
|
||||
public readonly string file;
|
||||
|
||||
public int ID;
|
||||
|
||||
public Gender gender;
|
||||
|
||||
public int salary;
|
||||
|
||||
public string GenderString()
|
||||
{
|
||||
return gender.ToString();
|
||||
}
|
||||
|
||||
public CharacterInfo(string file, string name = "", Gender gender = Gender.None)
|
||||
{
|
||||
this.file = file;
|
||||
|
||||
ID = -1;
|
||||
|
||||
XDocument doc = ToolBox.TryLoadXml(file);
|
||||
if (doc == null) return;
|
||||
|
||||
salary = 500;
|
||||
|
||||
if (ToolBox.GetAttributeBool(doc.Root, "genders", false))
|
||||
{
|
||||
if (gender==Gender.None)
|
||||
{
|
||||
float femaleRatio = ToolBox.GetAttributeFloat(doc.Root, "femaleratio", 0.5f);
|
||||
this.gender = (Game1.random.NextDouble() < femaleRatio) ? Gender.Female : Gender.Male;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.gender = gender;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(name))
|
||||
{
|
||||
this.name = name;
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Subsurface
|
||||
{
|
||||
class DelayedEffect : StatusEffect
|
||||
{
|
||||
public static List<DelayedEffect> list = new List<DelayedEffect>();
|
||||
|
||||
float delay;
|
||||
|
||||
float timer;
|
||||
|
||||
private Item item;
|
||||
|
||||
private Character character;
|
||||
|
||||
private Limb limb;
|
||||
|
||||
public float Timer
|
||||
{
|
||||
get { return timer; }
|
||||
}
|
||||
|
||||
public DelayedEffect(XElement element)
|
||||
: base(element)
|
||||
{
|
||||
delay = ToolBox.GetAttributeFloat(element, "delay", 1.0f);
|
||||
}
|
||||
|
||||
public override void Apply(ActionType type, float deltaTime, Item item, Character character = null, Limb limb = null)
|
||||
{
|
||||
if (this.type != type) return;
|
||||
|
||||
this.item = item;
|
||||
this.character = character;
|
||||
this.limb = limb;
|
||||
|
||||
this.timer = delay;
|
||||
|
||||
list.Add(this);
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
timer -= deltaTime;
|
||||
|
||||
if (timer > 0.0f) return;
|
||||
|
||||
base.Apply(1.0f, character, item, limb);
|
||||
list.Remove(this);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,353 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Dynamics.Joints;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Subsurface
|
||||
{
|
||||
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 flip;
|
||||
|
||||
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", false);
|
||||
|
||||
walkSpeed = ToolBox.GetAttributeFloat(element, "walkspeed", 1.0f);
|
||||
swimSpeed = ToolBox.GetAttributeFloat(element, "swimspeed", 1.0f);
|
||||
}
|
||||
|
||||
public override void UpdateAnim(float deltaTime)
|
||||
{
|
||||
ResetPullJoints();
|
||||
|
||||
if (strongestImpact > 0.0f)
|
||||
{
|
||||
stunTimer = MathHelper.Clamp(strongestImpact * 0.5f, stunTimer, 5.0f);
|
||||
strongestImpact = 0.0f;
|
||||
}
|
||||
|
||||
if (stunTimer>0.0f)
|
||||
{
|
||||
UpdateStruggling();
|
||||
stunTimer -= deltaTime;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (inWater)
|
||||
{
|
||||
UpdateSineAnim(deltaTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateWalkAnim(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (flip)
|
||||
{
|
||||
//targetDir = (movement.X > 0.0f) ? Direction.Right : Direction.Left;
|
||||
if (movement.X > 0.1f && movement.X > Math.Abs(movement.Y))
|
||||
{
|
||||
targetDir = Direction.Right;
|
||||
}
|
||||
else if (movement.X < -0.1f && movement.X < -Math.Abs(movement.Y))
|
||||
{
|
||||
targetDir = Direction.Left;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Limb head = GetLimb(LimbType.Head);
|
||||
float rotation = ToolBox.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 (stunTimer > gameTime.TotalGameTime.TotalMilliseconds) return;
|
||||
|
||||
if (targetDir != dir)
|
||||
{
|
||||
Flip();
|
||||
if (flip) Mirror();
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateSineAnim(float deltaTime)
|
||||
{
|
||||
movement = ToolBox.SmoothStep(movement, TargetMovement*swimSpeed, 1.0f);
|
||||
if (movement == Vector2.Zero) return;
|
||||
|
||||
if (!inWater) movement.Y = Math.Min(0.0f, movement.Y);
|
||||
|
||||
float movementAngle = ToolBox.VectorToAngle(movement)+MathHelper.PiOver2;
|
||||
|
||||
Limb tail = GetLimb(LimbType.Tail);
|
||||
if (tail != null && waveAmplitude>0.0f)
|
||||
{
|
||||
walkPos -= movement.Length();
|
||||
|
||||
float waveRotation = (float)Math.Sin(walkPos / waveLength)*waveAmplitude;
|
||||
|
||||
float angle = ToolBox.GetShortestAngle(tail.body.Rotation, movementAngle + waveRotation);
|
||||
|
||||
//limbs[tailIndex].body.ApplyTorque((Math.Sign(angle) + Math.Max(Math.Min(angle * 10.0f, 10.0f), -10.0f)) * limbs[tailIndex].body.Mass);
|
||||
//limbs[tailIndex].body.ApplyTorque(-limbs[tailIndex].body.AngularVelocity * 0.5f * limbs[tailIndex].body.Mass);
|
||||
}
|
||||
|
||||
Vector2 steerForce = Vector2.Zero;
|
||||
|
||||
Limb head = GetLimb(LimbType.Head);
|
||||
if (head != null)
|
||||
{
|
||||
float angle = ToolBox.GetShortestAngle(head.body.Rotation, movementAngle);
|
||||
|
||||
|
||||
head.body.SmoothRotate(head.body.Rotation+angle, 25.0f);
|
||||
|
||||
//rotate head towards the angle of movement
|
||||
//float torque = (Math.Sign(angle)*10.0f + MathHelper.Clamp(angle * 10.0f, -10.0f, 10.0f));
|
||||
//angular drag
|
||||
//torque -= head.body.AngularVelocity * 0.5f;
|
||||
//head.body.ApplyTorque(torque * head.body.Mass);
|
||||
|
||||
|
||||
//the movement vector if going to the direction of the head
|
||||
//Vector2 headMovement = new Vector2(
|
||||
// (float)Math.Cos(head.body.Rotation - MathHelper.PiOver2),
|
||||
// (float)Math.Sin(head.body.Rotation - MathHelper.PiOver2));
|
||||
//headMovement *= movement.Length();
|
||||
|
||||
//the movement angle is between direction of the head and the direction
|
||||
//where the character is actually trying to go
|
||||
|
||||
//current * (float)alpha + previous * (1.0f - (float)alpha);
|
||||
|
||||
|
||||
steerForce = (movement * 50.0f - head.LinearVelocity * 30.0f);
|
||||
// force += (headMovement - movement) * Math.Min(head.LinearVelocity.Length()/movement.Length(), 1.0f);
|
||||
|
||||
if (!inWater) steerForce.Y = 0.0f;
|
||||
}
|
||||
|
||||
for (int i = 0; i < limbs.Count(); i++)
|
||||
{
|
||||
if (steerForce!=Vector2.Zero)
|
||||
limbs[i].body.ApplyForce(steerForce * limbs[i].SteerForce * limbs[i].Mass);
|
||||
|
||||
if (limbs[i].type != LimbType.Torso) continue;
|
||||
|
||||
float dist = (limbs[0].SimPosition - limbs[i].SimPosition).Length();
|
||||
|
||||
Vector2 limbPos = limbs[0].SimPosition - Vector2.Normalize(movement) * dist;
|
||||
|
||||
limbs[i].body.ApplyForce(((limbPos - limbs[i].SimPosition) * 3.0f - limbs[i].LinearVelocity * 3.0f) * limbs[i].Mass);
|
||||
}
|
||||
|
||||
if (!inWater)
|
||||
{
|
||||
UpdateWalkAnim(deltaTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
floorY = limbs[0].SimPosition.Y;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateWalkAnim(float deltaTime)
|
||||
{
|
||||
movement = ToolBox.SmoothStep(movement, TargetMovement * walkSpeed, 0.2f);
|
||||
if (movement == Vector2.Zero) return;
|
||||
|
||||
Limb colliderLimb;
|
||||
float colliderHeight;
|
||||
|
||||
Limb torso = GetLimb(LimbType.Torso);
|
||||
Limb head = GetLimb(LimbType.Head);
|
||||
|
||||
if (torso!=null)
|
||||
{
|
||||
colliderLimb = torso;
|
||||
colliderHeight = TorsoPosition;
|
||||
|
||||
colliderLimb.body.SmoothRotate(TorsoAngle*Dir, 5.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
colliderLimb = head;
|
||||
colliderHeight = HeadPosition;
|
||||
|
||||
colliderLimb.body.SmoothRotate(HeadAngle*Dir, 10.0f);
|
||||
}
|
||||
|
||||
Vector2 colliderPos = colliderLimb.SimPosition;
|
||||
|
||||
Vector2 rayStart = colliderPos;
|
||||
Vector2 rayEnd = rayStart - new Vector2(0.0f, colliderHeight);
|
||||
if (stairs != null) rayEnd.Y -= 0.5f;
|
||||
|
||||
//do a raytrace straight down from the torso to figure
|
||||
//out whether the ragdoll is standing on ground
|
||||
float closestFraction = 1;
|
||||
//Structure closestStructure = null;
|
||||
Game1.world.RayCast((fixture, point, normal, fraction) =>
|
||||
{
|
||||
//other limbs and bodies with no collision detection are ignored
|
||||
if (fixture == null ||
|
||||
fixture.CollisionCategories == Physics.CollisionCharacter ||
|
||||
fixture.CollisionCategories == Physics.CollisionNone ||
|
||||
fixture.CollisionCategories == Physics.CollisionMisc) return -1;
|
||||
|
||||
Structure structure = fixture.Body.UserData as Structure;
|
||||
if (structure != null)
|
||||
{
|
||||
if (structure.StairDirection != Direction.None && (stairs == null)) return -1;
|
||||
if (structure.IsPlatform && (IgnorePlatforms || stairs != null)) return -1;
|
||||
}
|
||||
|
||||
onGround = true;
|
||||
onFloorTimer = 0.05f;
|
||||
|
||||
if (fraction < closestFraction) closestFraction = fraction;
|
||||
return 1;
|
||||
}
|
||||
, rayStart, rayEnd);
|
||||
|
||||
//the ragdoll "stays on ground" for 50 millisecs after separation
|
||||
if (onFloorTimer <= 0.0f)
|
||||
{
|
||||
onGround = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
onFloorTimer -= deltaTime;
|
||||
}
|
||||
|
||||
if (closestFraction == 1) //raycast didn't hit anything
|
||||
floorY = (currentHull == null || onGround) ? -1000.0f : ConvertUnits.ToSimUnits(currentHull.Rect.Y - currentHull.Rect.Height);
|
||||
else
|
||||
floorY = rayStart.Y + (rayEnd.Y - rayStart.Y) * closestFraction;
|
||||
|
||||
if (Math.Abs(colliderPos.Y - floorY)<colliderHeight*1.2f && onGround)
|
||||
{
|
||||
colliderLimb.Move(new Vector2(colliderPos.X + movement.X * 0.2f, floorY + colliderHeight), 5.0f);
|
||||
}
|
||||
|
||||
float walkCycleSpeed = head.LinearVelocity.X * 0.08f;
|
||||
|
||||
walkPos -= walkCycleSpeed;
|
||||
|
||||
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, colliderPos.Y - colliderHeight);
|
||||
|
||||
if (limb.RefJointIndex>-1)
|
||||
{
|
||||
RevoluteJoint refJoint = limbJoints[limb.RefJointIndex];
|
||||
footPos.X = refJoint.WorldAnchorA.X;
|
||||
}
|
||||
footPos.X += stepOffset.X * Dir;
|
||||
footPos.Y += stepOffset.Y;
|
||||
|
||||
if (limb.type == LimbType.LeftFoot)
|
||||
{
|
||||
limb.Move(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.Move(footPos +new Vector2(
|
||||
-transformedStepSize.X + movement.X * 0.1f,
|
||||
(-transformedStepSize.Y > 0.0f) ? -transformedStepSize.Y : 0.0f),
|
||||
8.0f);
|
||||
}
|
||||
break;
|
||||
case LimbType.LeftLeg:
|
||||
case LimbType.RightLeg:
|
||||
if (legTorque!=0.0f) limb.body.ApplyTorque(limb.Mass*legTorque*Dir);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateStruggling()
|
||||
{
|
||||
Limb head = GetLimb(LimbType.Head);
|
||||
Limb tail = GetLimb(LimbType.Tail);
|
||||
|
||||
head.body.ApplyTorque(head.Mass * Dir * 0.1f);
|
||||
tail.body.ApplyTorque(tail.Mass * -Dir * 0.1f);
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
float leftX = limbs[0].SimPosition.X, rightX = limbs[0].SimPosition.X;
|
||||
for (int i = 1; i < limbs.Count(); i++ )
|
||||
{
|
||||
if (limbs[i].SimPosition.X < leftX)
|
||||
{
|
||||
leftX = limbs[i].SimPosition.X;
|
||||
}
|
||||
else if (limbs[i].SimPosition.X > rightX)
|
||||
{
|
||||
rightX = limbs[i].SimPosition.X;
|
||||
}
|
||||
}
|
||||
|
||||
float midX = (leftX + rightX) / 2.0f;
|
||||
|
||||
foreach (Limb l in limbs)
|
||||
{
|
||||
Vector2 newPos = new Vector2(midX - (l.SimPosition.X - midX), l.SimPosition.Y);
|
||||
l.body.SetTransform(newPos, l.body.Rotation);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,787 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Subsurface
|
||||
{
|
||||
class HumanoidAnimController : AnimController
|
||||
{
|
||||
public HumanoidAnimController(Character character, XElement element)
|
||||
: base(character, element)
|
||||
{
|
||||
}
|
||||
|
||||
public override void UpdateAnim(float deltaTime)
|
||||
{
|
||||
Vector2 colliderPos = GetLimb(LimbType.Torso).SimPosition;
|
||||
|
||||
Vector2 rayStart = colliderPos; // at the bottom of the player sprite
|
||||
Vector2 rayEnd = rayStart - new Vector2(0.0f, TorsoPosition);
|
||||
if (stairs != null) rayEnd.Y -= 0.5f;
|
||||
if (anim != Animation.UsingConstruction) ResetPullJoints();
|
||||
|
||||
//do a raytrace straight down from the torso to figure
|
||||
//out whether the ragdoll is standing on ground
|
||||
float closestFraction = 1;
|
||||
Structure closestStructure = null;
|
||||
Game1.world.RayCast((fixture, point, normal, fraction) =>
|
||||
{
|
||||
switch (fixture.CollisionCategories)
|
||||
{
|
||||
case Physics.CollisionStairs:
|
||||
Structure structure = fixture.Body.UserData as Structure;
|
||||
if (stairs == null)
|
||||
{
|
||||
if (LowestLimb.SimPosition.Y<structure.SimPosition.Y)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
stairs = structure;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Physics.CollisionPlatform:
|
||||
if (IgnorePlatforms || stairs != null) return -1;
|
||||
break;
|
||||
case Physics.CollisionWall:
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
|
||||
onGround = true;
|
||||
if (fraction < closestFraction)
|
||||
{
|
||||
closestFraction = fraction;
|
||||
|
||||
Structure structure = fixture.Body.UserData as Structure;
|
||||
if (structure != null) closestStructure = structure;
|
||||
}
|
||||
onFloorTimer = 0.05f;
|
||||
return closestFraction;
|
||||
}
|
||||
, rayStart, rayEnd);
|
||||
|
||||
if (closestStructure != null && closestStructure.StairDirection != Direction.None)
|
||||
{
|
||||
stairs = closestStructure;
|
||||
}
|
||||
else
|
||||
{
|
||||
stairs = null;
|
||||
}
|
||||
|
||||
//the ragdoll "stays on ground" for 50 millisecs after separation
|
||||
if (onFloorTimer <= 0.0f)
|
||||
{
|
||||
onGround = false;
|
||||
|
||||
//TODO: joku järkevämpi systeemi
|
||||
//if (!inWater && lastTimeOnFloor + 200 < gameTime.TotalGameTime.Milliseconds)
|
||||
// stunTimer = Math.Max(stunTimer, (float)gameTime.TotalGameTime.TotalMilliseconds + 100.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
onFloorTimer -= deltaTime;
|
||||
}
|
||||
|
||||
if (closestFraction == 1) //raycast didn't hit anything
|
||||
floorY = (currentHull == null) ? -1000.0f : ConvertUnits.ToSimUnits(currentHull.Rect.Y - currentHull.Rect.Height);
|
||||
else
|
||||
floorY = rayStart.Y + (rayEnd.Y - rayStart.Y) * closestFraction;
|
||||
|
||||
IgnorePlatforms = (TargetMovement.Y < 0.0f);
|
||||
|
||||
//stun (= disable the animations) if the ragdoll receives a large enough impact
|
||||
if (strongestImpact > 0.0f)
|
||||
{
|
||||
character.Stun();
|
||||
stunTimer = MathHelper.Clamp(strongestImpact * 0.5f, stunTimer, 5.0f);
|
||||
}
|
||||
strongestImpact = 0.0f;
|
||||
|
||||
if (stunTimer > 0)
|
||||
{
|
||||
UpdateStruggling();
|
||||
stunTimer -= deltaTime;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (anim)
|
||||
{
|
||||
case Animation.Climbing:
|
||||
UpdateClimbing();
|
||||
break;
|
||||
case Animation.UsingConstruction:
|
||||
break;
|
||||
default:
|
||||
if (inWater)
|
||||
UpdateSwimming();
|
||||
else if (isStanding)
|
||||
UpdateStanding();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (targetDir != dir) Flip();
|
||||
|
||||
foreach (Limb limb in limbs)
|
||||
{
|
||||
limb.Disabled = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void UpdateStanding()
|
||||
{
|
||||
Vector2 handPos;
|
||||
|
||||
Limb leftFoot = GetLimb(LimbType.LeftFoot);
|
||||
Limb rightFoot = GetLimb(LimbType.RightFoot);
|
||||
Limb head = GetLimb(LimbType.Head);
|
||||
Limb torso = GetLimb(LimbType.Torso);
|
||||
|
||||
Limb leftHand = GetLimb(LimbType.LeftHand);
|
||||
Limb rightHand = GetLimb(LimbType.RightHand);
|
||||
|
||||
Limb leftLeg = GetLimb(LimbType.LeftLeg);
|
||||
Limb rightLeg = GetLimb(LimbType.RightLeg);
|
||||
|
||||
float getUpSpeed = 0.3f;
|
||||
float walkCycleSpeed = head.LinearVelocity.X * 0.08f;
|
||||
if (stairs != null)
|
||||
{
|
||||
if (TargetMovement!=Vector2.Zero && TargetMovement.Length()>3.0f)
|
||||
{
|
||||
TargetMovement /= TargetMovement.Length() / 3.0f;
|
||||
}
|
||||
walkCycleSpeed *= 1.5f;
|
||||
}
|
||||
|
||||
Vector2 colliderPos = new Vector2(torso.SimPosition.X, floorY);
|
||||
|
||||
float walkPosX = (float)Math.Cos(walkPos);
|
||||
float walkPosY = (float)Math.Sin(walkPos);
|
||||
float runningModifier = (float)Math.Max(Math.Abs(movement.X) / 1.5f, 1.0);
|
||||
|
||||
Vector2 stepSize = new Vector2(
|
||||
this.stepSize.X * walkPosX * runningModifier,
|
||||
this.stepSize.Y * walkPosY * runningModifier * runningModifier);
|
||||
|
||||
float footMid = (leftFoot.SimPosition.X + rightFoot.SimPosition.X) / 2.0f;
|
||||
|
||||
movement = ToolBox.SmoothStep(movement, TargetMovement, 0.5f);
|
||||
movement.Y = 0.0f;
|
||||
|
||||
//place the anchors of the head and the torso to make the ragdoll stand
|
||||
if (onGround && LowestLimb != null && (LowestLimb.SimPosition.Y-floorY < 0.5f || stairs != null) && head !=null)
|
||||
{
|
||||
getUpSpeed = Math.Max(getUpSpeed * (head.SimPosition.Y - colliderPos.Y), 0.25f);
|
||||
|
||||
if (stairs != null)
|
||||
{
|
||||
if (LowestLimb.SimPosition.Y < stairs.SimPosition.Y) IgnorePlatforms = true;
|
||||
|
||||
torso.pullJoint.Enabled = true;
|
||||
torso.pullJoint.WorldAnchorB = new Vector2(
|
||||
MathHelper.SmoothStep(torso.SimPosition.X, footMid + movement.X * 0.35f, getUpSpeed * 0.8f),
|
||||
MathHelper.SmoothStep(torso.SimPosition.Y, colliderPos.Y + TorsoPosition - Math.Abs(walkPosX * 0.05f), getUpSpeed * 3.0f));
|
||||
|
||||
|
||||
head.pullJoint.Enabled = true;
|
||||
head.pullJoint.WorldAnchorB = new Vector2(
|
||||
MathHelper.SmoothStep(head.SimPosition.X, footMid + movement.X * 0.4f, getUpSpeed * 0.8f),
|
||||
MathHelper.SmoothStep(head.SimPosition.Y, colliderPos.Y + HeadPosition - Math.Abs(walkPosX * 0.05f), getUpSpeed * 3.0f));
|
||||
}
|
||||
else
|
||||
{
|
||||
torso.pullJoint.Enabled = true;
|
||||
torso.pullJoint.WorldAnchorB =
|
||||
ToolBox.SmoothStep(torso.SimPosition,
|
||||
new Vector2(footMid + movement.X * 0.35f, colliderPos.Y + TorsoPosition - Math.Abs(walkPosX * 0.05f)), getUpSpeed);
|
||||
|
||||
head.pullJoint.Enabled = true;
|
||||
head.pullJoint.WorldAnchorB =
|
||||
ToolBox.SmoothStep(head.SimPosition,
|
||||
new Vector2(footMid + movement.X * 0.4f, colliderPos.Y + HeadPosition - Math.Abs(walkPosX * 0.05f)), getUpSpeed);
|
||||
}
|
||||
|
||||
|
||||
//moving horizontally
|
||||
if (TargetMovement.X != 0.0f)
|
||||
{
|
||||
//progress the walking animation
|
||||
walkPos -= (walkCycleSpeed / runningModifier);
|
||||
|
||||
MoveLimb(leftFoot,
|
||||
colliderPos + new Vector2(
|
||||
stepSize.X,
|
||||
(stepSize.Y > 0.0f) ? stepSize.Y : -0.15f),
|
||||
10.0f, true);
|
||||
|
||||
MoveLimb(rightFoot,
|
||||
colliderPos + new Vector2(
|
||||
-stepSize.X,
|
||||
(-stepSize.Y > 0.0f) ? -stepSize.Y : -0.15f),
|
||||
10.0f, true);
|
||||
|
||||
if (Math.Sign(stepSize.X) == Math.Sign(Dir))
|
||||
{
|
||||
leftFoot.body.SmoothRotate(leftLeg.body.Rotation + MathHelper.PiOver2 * Dir * 1.3f, 20.0f * runningModifier);
|
||||
}
|
||||
else if (Math.Sign(-stepSize.X) == Math.Sign(Dir))
|
||||
{
|
||||
rightFoot.body.SmoothRotate(rightLeg.body.Rotation + MathHelper.PiOver2 * Dir * 1.3f, 20 * runningModifier);
|
||||
}
|
||||
|
||||
if (walkPosY > 0.0f)
|
||||
{
|
||||
GetLimb(LimbType.LeftThigh).body.ApplyTorque(-walkPosY * Dir * Math.Abs(movement.X) * -2.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
GetLimb(LimbType.RightThigh).body.ApplyTorque(walkPosY * Dir * Math.Abs(movement.X) * -2.0f);
|
||||
}
|
||||
|
||||
//calculate the positions of hands
|
||||
handPos = torso.SimPosition;
|
||||
handPos.X = -walkPosX * 0.25f;
|
||||
|
||||
float lowerY = -0.4f / runningModifier;
|
||||
handPos.Y = lowerY + (float)(Math.Abs(Math.Sin(walkPos - Math.PI * 1.5f) * 0.1)) * runningModifier;
|
||||
|
||||
Vector2 posAdditon = new Vector2(movement.X*0.05f, 0.0f);
|
||||
if (stairs!=null)
|
||||
{
|
||||
if ((stairs.StairDirection == Direction.Right && movement.X < 0.0f) ||
|
||||
(stairs.StairDirection == Direction.Left && movement.X > 0.0f))
|
||||
{
|
||||
posAdditon.Y -= 0.1f;
|
||||
}
|
||||
else
|
||||
{
|
||||
posAdditon.Y += 0.1f;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!rightHand.Disabled)
|
||||
{
|
||||
rightHand.body.ApplyTorque(walkPosY * runningModifier * Dir);
|
||||
MoveLimb(rightHand, torso.SimPosition + posAdditon +
|
||||
new Vector2(
|
||||
-handPos.X,
|
||||
(Math.Sign(walkPosX) == Math.Sign(Dir)) ? handPos.Y : lowerY),
|
||||
15.0f, true);
|
||||
}
|
||||
|
||||
if (!leftHand.Disabled)
|
||||
{
|
||||
leftHand.body.ApplyTorque(-walkPosY * runningModifier * Dir);
|
||||
MoveLimb(leftHand, torso.SimPosition + posAdditon +
|
||||
new Vector2(
|
||||
handPos.X,
|
||||
(Math.Sign(walkPosX) == Math.Sign(-Dir)) ? handPos.Y : lowerY),
|
||||
15.0f, true);
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
//add torque to the head to do a subtle "breathing" effect
|
||||
//head.body.ApplyTorque((float)Math.Sin(gameTime.TotalGameTime.TotalMilliseconds / 300) * 0.2f);
|
||||
|
||||
//standing still -> "attach" the feet to the ground
|
||||
|
||||
float movementFactor = (movement.X / 4.0f) * movement.X * Math.Sign(movement.X);
|
||||
|
||||
Vector2 footPos = new Vector2(
|
||||
colliderPos.X + movementFactor - Dir * 0.05f,
|
||||
colliderPos.Y - 0.2f - Math.Abs(movementFactor));
|
||||
|
||||
MoveLimb(leftFoot, footPos, 2.5f);
|
||||
MoveLimb(rightFoot, footPos, 2.5f);
|
||||
|
||||
leftFoot.body.SmoothRotate(Dir * MathHelper.PiOver2, 5.0f);
|
||||
rightFoot.body.SmoothRotate(Dir * MathHelper.PiOver2, 5.0f);
|
||||
|
||||
handPos = torso.SimPosition;
|
||||
handPos.X += movement.X;
|
||||
handPos.Y -= 0.4f;
|
||||
if (!rightHand.Disabled)
|
||||
{
|
||||
MoveLimb(rightHand, handPos, 1.5f, true);
|
||||
}
|
||||
|
||||
if (!leftHand.Disabled)
|
||||
{
|
||||
MoveLimb(leftHand, handPos, 1.5f, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
Limb leg = (i == 0) ? rightFoot : leftFoot;
|
||||
|
||||
if (leg.SimPosition.Y < torso.SimPosition.Y) continue;
|
||||
|
||||
leg.body.ApplyTorque(-Dir * leg.Mass * 20.0f);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void UpdateSwimming()
|
||||
{
|
||||
IgnorePlatforms = true;
|
||||
|
||||
Vector2 footPos, handPos;
|
||||
|
||||
float surfaceLimiter = 1.0f;
|
||||
|
||||
Limb head = GetLimb(LimbType.Head);
|
||||
|
||||
if (currentHull != null && currentHull.Volume < currentHull.FullVolume)
|
||||
{
|
||||
surfaceLimiter = (ConvertUnits.ToDisplayUnits(head.SimPosition.Y)-surfaceY);
|
||||
surfaceLimiter = Math.Max(1.0f, surfaceLimiter);
|
||||
if (surfaceLimiter > 20.0f) return;
|
||||
}
|
||||
|
||||
Limb leftFoot = GetLimb(LimbType.LeftFoot);
|
||||
Limb rightFoot = GetLimb(LimbType.RightFoot);
|
||||
Limb torso = GetLimb(LimbType.Torso);
|
||||
Limb leftHand = GetLimb(LimbType.LeftHand);
|
||||
Limb rightHand = GetLimb(LimbType.RightHand);
|
||||
|
||||
|
||||
float rotation = MathHelper.WrapAngle(torso.Rotation);
|
||||
rotation = MathHelper.ToDegrees(rotation);
|
||||
if (rotation < 0.0f) rotation += 360;
|
||||
|
||||
if (!character.IsNetworkPlayer)
|
||||
{
|
||||
if (rotation > 20 && rotation < 170)
|
||||
targetDir = Direction.Left;
|
||||
else if (rotation > 190 && rotation < 340)
|
||||
targetDir = Direction.Right;
|
||||
}
|
||||
|
||||
if (TargetMovement == Vector2.Zero) return;
|
||||
|
||||
float targetSpeed = TargetMovement.Length();
|
||||
if (targetSpeed > 0.0f) TargetMovement /= targetSpeed;
|
||||
|
||||
//if trying to head to the opposite direction, apply torque
|
||||
//to the torso to flip the ragdoll around
|
||||
if (Math.Sign(TargetMovement.X) != Dir && TargetMovement.X != 0.0f)
|
||||
{
|
||||
float torque = torso.Mass * 10.0f;
|
||||
torque *= (rotation > 90 && rotation < 270) ? -Dir : Dir;
|
||||
|
||||
torso.body.ApplyTorque(torque);
|
||||
}
|
||||
|
||||
movement = ToolBox.SmoothStep(movement, TargetMovement, 0.3f);
|
||||
|
||||
//dont try to move upwards if head is already out of water
|
||||
if (surfaceLimiter > 1.0f)
|
||||
{
|
||||
if (TargetMovement.X == 0.0f)
|
||||
{
|
||||
head.body.ApplyForce(head.Mass * new Vector2(-Dir * 5.1f, -5.0f));
|
||||
torso.body.ApplyForce(torso.Mass *new Vector2(-Dir*5.1f, -15.0f));
|
||||
leftFoot.body.ApplyForce(leftFoot.Mass *new Vector2(0.0f, -80.0f));
|
||||
rightFoot.body.ApplyForce(rightFoot.Mass *new Vector2(0.0f, -80.0f));
|
||||
}
|
||||
else
|
||||
{
|
||||
TargetMovement = new Vector2(
|
||||
(float)Math.Sqrt(targetSpeed * targetSpeed - TargetMovement.Y * TargetMovement.Y)
|
||||
* Math.Sign(TargetMovement.X),
|
||||
Math.Max(TargetMovement.Y, TargetMovement.Y * 0.2f));
|
||||
|
||||
head.body.ApplyTorque(Dir * 0.1f);
|
||||
}
|
||||
|
||||
movement.Y = movement.Y - (surfaceLimiter - 1.0f) * 0.01f;
|
||||
}
|
||||
|
||||
head.body.ApplyForce((new Vector2(movement.X,
|
||||
movement.Y / surfaceLimiter + 0.2f) - head.body.LinearVelocity * 0.2f) *
|
||||
20.0f * head.body.Mass);
|
||||
|
||||
torso.body.ApplyForce((new Vector2(movement.X,
|
||||
movement.Y / surfaceLimiter + 0.2f) - torso.body.LinearVelocity * 0.2f) * 10.0f * torso.body.Mass);
|
||||
|
||||
walkPos += movement.Length() * 0.15f;
|
||||
footPos = (leftFoot.SimPosition + rightFoot.SimPosition) / 2.0f;
|
||||
|
||||
Vector2 transformedFootPos = new Vector2((float)Math.Sin(walkPos) * 0.3f, 0.0f);
|
||||
transformedFootPos = Vector2.Transform(
|
||||
transformedFootPos,
|
||||
Matrix.CreateRotationZ(torso.body.Rotation));
|
||||
|
||||
MoveLimb(leftFoot, footPos + transformedFootPos, 2.5f);
|
||||
MoveLimb(rightFoot, footPos - transformedFootPos, 2.5f);
|
||||
|
||||
Vector2 feetExtendForce = new Vector2(
|
||||
(float)-Math.Sin(torso.body.Rotation),
|
||||
(float)Math.Cos(torso.body.Rotation));
|
||||
|
||||
leftFoot.body.ApplyForce(feetExtendForce);
|
||||
rightFoot.body.ApplyForce(feetExtendForce);
|
||||
|
||||
leftFoot.body.ApplyTorque(leftFoot.body.Mass * -Dir);
|
||||
rightFoot.body.ApplyTorque(rightFoot.body.Mass * -Dir);
|
||||
|
||||
handPos = (torso.SimPosition + head.SimPosition) / 2.0f;
|
||||
|
||||
//if (!rightHand.Disabled) rightHand.body.ApplyTorque(leftHand.body.Mass * Dir);
|
||||
//if (!leftHand.Disabled) leftHand.body.ApplyTorque(leftHand.body.Mass * Dir);
|
||||
|
||||
//at the surface, not moving sideways -> hands just float around
|
||||
if (!headInWater && TargetMovement.X == 0.0f)
|
||||
{
|
||||
handPos.X = handPos.X + Dir * 0.6f;
|
||||
|
||||
float wobbleAmount = 0.05f;
|
||||
|
||||
if (!rightHand.Disabled)
|
||||
{
|
||||
MoveLimb(rightHand, new Vector2(
|
||||
handPos.X + (float)Math.Sin(walkPos / 1.5f) * wobbleAmount,
|
||||
handPos.Y + (float)Math.Sin(walkPos / 3.5f) * wobbleAmount - 0.0f), 1.5f);
|
||||
}
|
||||
|
||||
if (!leftHand.Disabled)
|
||||
{
|
||||
MoveLimb(leftHand, new Vector2(
|
||||
handPos.X + (float)Math.Sin(walkPos / 2.0f) * wobbleAmount,
|
||||
handPos.Y + (float)Math.Sin(walkPos / 3.0f) * wobbleAmount - 0.0f), 1.5f);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
handPos += head.LinearVelocity * 0.1f;
|
||||
|
||||
float handCyclePos = walkPos / 2.0f;
|
||||
float handPosX = (float)Math.Cos(handCyclePos * Dir) * 0.4f;
|
||||
float handPosY = (float)Math.Sin(handCyclePos * Dir) * 0.7f;
|
||||
handPosY = MathHelper.Clamp(handPosY, -0.6f, 0.6f);
|
||||
|
||||
Matrix rotationMatrix = Matrix.CreateRotationZ(torso.Rotation);
|
||||
|
||||
if (!rightHand.Disabled)
|
||||
{
|
||||
Vector2 rightHandPos = new Vector2(-handPosX, -handPosY);
|
||||
rightHandPos.X = (Dir == 1.0f) ? Math.Max(0.2f, rightHandPos.X) : Math.Min(-0.2f, rightHandPos.X);
|
||||
rightHandPos = Vector2.Transform(rightHandPos, rotationMatrix);
|
||||
|
||||
MoveLimb(rightHand, handPos + rightHandPos, 3.5f);
|
||||
}
|
||||
|
||||
if (!leftHand.Disabled)
|
||||
{
|
||||
Vector2 leftHandPos = new Vector2(handPosX, handPosY);
|
||||
leftHandPos.X = (Dir == 1.0f) ? Math.Max(0.2f, leftHandPos.X) : Math.Min(-0.2f, leftHandPos.X);
|
||||
leftHandPos = Vector2.Transform(leftHandPos, rotationMatrix);
|
||||
|
||||
MoveLimb(leftHand, handPos + leftHandPos, 3.5f);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
void UpdateClimbing()
|
||||
{
|
||||
if (character.SelectedConstruction == null)
|
||||
{
|
||||
anim = Animation.None;
|
||||
return;
|
||||
}
|
||||
|
||||
onGround = false;
|
||||
IgnorePlatforms = true;
|
||||
|
||||
movement = ToolBox.SmoothStep(movement, TargetMovement, 0.3f);
|
||||
|
||||
Vector2 footPos, handPos;
|
||||
|
||||
Limb leftFoot = GetLimb(LimbType.LeftFoot);
|
||||
Limb rightFoot = GetLimb(LimbType.RightFoot);
|
||||
Limb head = GetLimb(LimbType.Head);
|
||||
Limb torso = GetLimb(LimbType.Torso);
|
||||
|
||||
Limb waist = GetLimb(LimbType.Waist);
|
||||
|
||||
Limb leftHand = GetLimb(LimbType.LeftHand);
|
||||
Limb rightHand = GetLimb(LimbType.RightHand);
|
||||
|
||||
Vector2 ladderSimPos = ConvertUnits.ToSimUnits(
|
||||
character.SelectedConstruction.Rect.X + character.SelectedConstruction.Rect.Width / 2.0f,
|
||||
character.SelectedConstruction.Rect.Y);
|
||||
|
||||
MoveLimb(head, new Vector2(ladderSimPos.X - 0.27f * Dir, head.SimPosition.Y + 0.05f), 10.5f);
|
||||
MoveLimb(torso, new Vector2(ladderSimPos.X - 0.27f * Dir, torso.SimPosition.Y), 10.5f);
|
||||
MoveLimb(waist, new Vector2(ladderSimPos.X - 0.35f * Dir, waist.SimPosition.Y), 10.5f);
|
||||
|
||||
float stepHeight = ConvertUnits.ToSimUnits(30.0f);
|
||||
|
||||
handPos = new Vector2(
|
||||
ladderSimPos.X,
|
||||
head.SimPosition.Y + 0.3f + movement.Y * 0.1f - ladderSimPos.Y);
|
||||
|
||||
MoveLimb(leftHand,
|
||||
new Vector2(handPos.X,
|
||||
ToolBox.Round(handPos.Y - stepHeight, stepHeight * 2.0f) + stepHeight + ladderSimPos.Y),
|
||||
5.2f);
|
||||
|
||||
MoveLimb(rightHand,
|
||||
new Vector2(handPos.X,
|
||||
ToolBox.Round(handPos.Y, stepHeight * 2.0f) + ladderSimPos.Y),
|
||||
5.2f);
|
||||
|
||||
leftHand.body.ApplyTorque(Dir * 2.0f);
|
||||
rightHand.body.ApplyTorque(Dir * 2.0f);
|
||||
|
||||
footPos = new Vector2(
|
||||
handPos.X + Dir*0.1f,
|
||||
head.SimPosition.Y - stepHeight * 2.7f - ladderSimPos.Y);
|
||||
|
||||
MoveLimb(leftFoot,
|
||||
new Vector2(footPos.X,
|
||||
ToolBox.Round(footPos.Y + stepHeight, stepHeight * 2.0f) - stepHeight + ladderSimPos.Y),
|
||||
7.5f, true);
|
||||
|
||||
MoveLimb(rightFoot,
|
||||
new Vector2(footPos.X,
|
||||
ToolBox.Round(footPos.Y, stepHeight * 2.0f) + ladderSimPos.Y),
|
||||
7.5f, true);
|
||||
|
||||
//apply torque to the legs to make the knees bend
|
||||
Limb leftLeg = GetLimb(LimbType.LeftLeg);
|
||||
Limb rightLeg = GetLimb(LimbType.RightLeg);
|
||||
|
||||
leftLeg.body.ApplyTorque(Dir * -3.0f);
|
||||
rightLeg.body.ApplyTorque(Dir * -3.0f);
|
||||
|
||||
//apply forces to the head and the torso to move the character up/down
|
||||
float movementFactor = (handPos.Y / stepHeight) * (float)Math.PI;
|
||||
movementFactor = 0.8f + (float)Math.Abs(Math.Sin(movementFactor));
|
||||
|
||||
Vector2 climbForce = new Vector2(0.0f, movement.Y + 0.5f) * movementFactor;
|
||||
torso.body.ApplyForce(climbForce * 65.0f * torso.Mass);
|
||||
head.body.SmoothRotate(0.0f);
|
||||
|
||||
Rectangle trigger = character.SelectedConstruction.Prefab.triggers.First();
|
||||
trigger = character.SelectedConstruction.TransformTrigger(trigger);
|
||||
|
||||
//stop climbing if:
|
||||
// - going too fast (can't grab a ladder while falling)
|
||||
// - moving sideways
|
||||
// - reached the top or bottom of the ladder
|
||||
if (Math.Abs(torso.LinearVelocity.Y) > 5.0f ||
|
||||
TargetMovement.X != 0.0f ||
|
||||
(TargetMovement.Y < 0.0f && ConvertUnits.ToSimUnits(trigger.Height) + handPos.Y < HeadPosition*2.0f) ||
|
||||
(TargetMovement.Y > 0.0f && -handPos.Y < ConvertUnits.ToSimUnits(10.0f)))
|
||||
{
|
||||
anim = Animation.None;
|
||||
character.SelectedConstruction = null;
|
||||
IgnorePlatforms = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void UpdateStruggling()
|
||||
{
|
||||
Limb leftLeg = GetLimb(LimbType.LeftFoot);
|
||||
Limb rightLeg = GetLimb(LimbType.RightFoot);
|
||||
Limb torso = GetLimb(LimbType.Torso);
|
||||
|
||||
walkPos += 0.2f;
|
||||
|
||||
if (inWater) return;
|
||||
|
||||
Vector2 footPos = torso.body.Position+ new Vector2(TorsoPosition*Dir,0.0f);
|
||||
|
||||
MoveLimb(leftLeg, footPos, 0.7f);
|
||||
MoveLimb(rightLeg, footPos, 0.7f);
|
||||
}
|
||||
|
||||
public override void HoldItem(float deltaTime, Camera cam, Item item, Vector2[] handlePos, Vector2 holdPos, Vector2 aimPos, float holdAngle)
|
||||
{
|
||||
//calculate the handle positions
|
||||
Matrix itemTransfrom = Matrix.CreateRotationZ(item.body.Rotation);
|
||||
Vector2[] transformedHandlePos = new Vector2[2];
|
||||
transformedHandlePos[0] = Vector2.Transform(handlePos[0], itemTransfrom);
|
||||
transformedHandlePos[1] = Vector2.Transform(handlePos[1], itemTransfrom);
|
||||
|
||||
Limb head = GetLimb(LimbType.Head);
|
||||
Limb torso = GetLimb(LimbType.Torso);
|
||||
Limb leftHand = GetLimb(LimbType.LeftHand);
|
||||
Limb leftArm = GetLimb(LimbType.LeftArm);
|
||||
Limb rightHand = GetLimb(LimbType.RightHand);
|
||||
Limb rightArm = GetLimb(LimbType.RightArm);
|
||||
|
||||
Vector2 itemPos = character.SecondaryKeyDown.State ? aimPos : holdPos;
|
||||
|
||||
float itemAngle;
|
||||
if (character.SecondaryKeyDown.State && itemPos != Vector2.Zero)
|
||||
{
|
||||
Vector2 mousePos = ConvertUnits.ToSimUnits(character.CursorPosition);
|
||||
|
||||
Vector2 diff = (mousePos - torso.SimPosition) * Dir;
|
||||
|
||||
holdAngle = ToolBox.VectorToAngle(new Vector2(diff.X, diff.Y * Dir)) - torso.body.Rotation * Dir;
|
||||
holdAngle = MathHelper.Clamp(ToolBox.WrapAnglePi(holdAngle), -1.3f, 1.0f);
|
||||
|
||||
itemAngle = (torso.body.Rotation + holdAngle * Dir);
|
||||
|
||||
head.body.SmoothRotate(itemAngle);
|
||||
|
||||
if (TargetMovement == Vector2.Zero && inWater)
|
||||
{
|
||||
torso.body.SmoothRotate(0.2f * Dir);
|
||||
torso.body.ApplyForce(torso.body.LinearVelocity * -0.5f);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
itemAngle = (torso.body.Rotation + holdAngle * Dir);
|
||||
}
|
||||
|
||||
Vector2 shoulderPos = head.SimPosition + (torso.SimPosition - head.SimPosition) *0.9f;
|
||||
Vector2 transformedHoldPos = shoulderPos;
|
||||
|
||||
if (itemPos == Vector2.Zero)
|
||||
{
|
||||
if (character.SelectedItems[0] == item)
|
||||
{
|
||||
transformedHoldPos = rightHand.pullJoint.WorldAnchorA - transformedHandlePos[0];
|
||||
itemAngle = (rightHand.Rotation + (holdAngle - MathHelper.PiOver2) * Dir);
|
||||
//rightHand.Disabled = true;
|
||||
}
|
||||
if (character.SelectedItems[1] == item)
|
||||
{
|
||||
transformedHoldPos = leftHand.pullJoint.WorldAnchorA - transformedHandlePos[1];
|
||||
itemAngle = (leftHand.Rotation + (holdAngle - MathHelper.PiOver2) * Dir);
|
||||
//leftHand.Disabled = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (character.SelectedItems[0] == item)
|
||||
{
|
||||
rightHand.Disabled = true;
|
||||
}
|
||||
if (character.SelectedItems[1] == item)
|
||||
{
|
||||
leftHand.Disabled = true;
|
||||
}
|
||||
|
||||
|
||||
itemPos.X = itemPos.X * Dir;
|
||||
|
||||
Matrix torsoTransform = Matrix.CreateRotationZ(itemAngle);
|
||||
|
||||
transformedHoldPos += Vector2.Transform(itemPos, torsoTransform);
|
||||
}
|
||||
|
||||
|
||||
Vector2 bodyVelocity = torso.body.LinearVelocity / 60.0f;
|
||||
|
||||
item.body.ResetDynamics();
|
||||
item.body.SetTransform(ToolBox.SmoothStep(item.body.Position, transformedHoldPos + bodyVelocity, 0.5f), itemAngle);
|
||||
|
||||
//item.body.SmoothRotate(itemAngle, 50.0f);
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
if (character.SelectedItems[i] != item) continue;
|
||||
if (itemPos == Vector2.Zero) continue;
|
||||
|
||||
Limb hand = (i == 0) ? rightHand : leftHand;
|
||||
Limb arm = (i == 0) ? rightArm : leftArm;
|
||||
|
||||
//hand length
|
||||
float a = 24.0f;
|
||||
|
||||
//arm length
|
||||
float b = 34.0f;
|
||||
|
||||
//distance from shoulder to holdpos
|
||||
float c = ConvertUnits.ToDisplayUnits(Vector2.Distance(transformedHoldPos + transformedHandlePos[i], shoulderPos));
|
||||
c = MathHelper.Clamp(a + b - 1, b-a, c);
|
||||
|
||||
float ang2 = ToolBox.VectorToAngle((transformedHoldPos + transformedHandlePos[i]) - shoulderPos)+MathHelper.PiOver2;
|
||||
|
||||
float armAngle = ToolBox.SolveTriangleSSS(a, b, c);
|
||||
float handAngle = ToolBox.SolveTriangleSSS(b, a, c);
|
||||
|
||||
arm.body.SmoothRotate((ang2 - armAngle * Dir), 20.0f);
|
||||
hand.body.SmoothRotate((ang2 + handAngle * Dir), 100.0f);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public override void Flip()
|
||||
{
|
||||
base.Flip();
|
||||
|
||||
Limb torso = GetLimb(LimbType.Torso);
|
||||
|
||||
Vector2 difference;
|
||||
|
||||
Matrix torsoTransform = Matrix.CreateRotationZ(torso.Rotation);
|
||||
|
||||
for (int i = 0; i < character.SelectedItems.Length; i++)
|
||||
{
|
||||
if (character.SelectedItems[i] != null)
|
||||
{
|
||||
difference = character.SelectedItems[i].body.Position - torso.SimPosition;
|
||||
difference = Vector2.Transform(difference, torsoTransform);
|
||||
difference.Y = -difference.Y;
|
||||
|
||||
character.SelectedItems[i].body.SetTransform(
|
||||
torso.SimPosition + Vector2.Transform(difference, -torsoTransform),
|
||||
ToolBox.WrapAngleTwoPi(-character.SelectedItems[i].body.Rotation));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Limb l in limbs)
|
||||
{
|
||||
switch (l.type)
|
||||
{
|
||||
case LimbType.LeftHand:
|
||||
case LimbType.LeftArm:
|
||||
case LimbType.RightHand:
|
||||
case LimbType.RightArm:
|
||||
difference = l.body.Position - torso.SimPosition;
|
||||
difference = Vector2.Transform(difference, torsoTransform);
|
||||
difference.Y = -difference.Y;
|
||||
|
||||
l.body.SetTransform(torso.SimPosition + Vector2.Transform(difference, -torsoTransform), -l.body.Rotation);
|
||||
break;
|
||||
default:
|
||||
if (!inWater) l.body.SetTransform(l.body.Position,
|
||||
ToolBox.WrapAnglePi(l.body.Rotation * (l.DoesFlip ? -1.0f : 1.0f)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Subsurface.Characters
|
||||
{
|
||||
class Job
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Subsurface
|
||||
{
|
||||
class Job
|
||||
{
|
||||
public static List<Job> jobList;
|
||||
|
||||
string name;
|
||||
string description;
|
||||
|
||||
//names of the items the character spawns with
|
||||
public List<string> itemNames;
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return name; }
|
||||
}
|
||||
|
||||
public Job(XElement element)
|
||||
{
|
||||
name = element.Name.ToString();
|
||||
|
||||
description = ToolBox.GetAttributeString(element, "description", "");
|
||||
|
||||
itemNames = new List<string>();
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString())
|
||||
{
|
||||
case "item":
|
||||
string itemName = ToolBox.GetAttributeString(subElement, "name", "");
|
||||
if (!string.IsNullOrEmpty(itemName)) itemNames.Add(itemName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void LoadAll(string filePath)
|
||||
{
|
||||
jobList = new List<Job>();
|
||||
|
||||
XDocument doc = ToolBox.TryLoadXml(filePath);
|
||||
|
||||
foreach (XElement element in doc.Root.Elements())
|
||||
{
|
||||
Job job = new Job(element);
|
||||
jobList.Add(job);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,362 @@
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using FarseerPhysics.Dynamics.Joints;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace Subsurface
|
||||
{
|
||||
public enum LimbType
|
||||
{
|
||||
None, LeftHand, RightHand, LeftArm, RightArm,
|
||||
LeftLeg, RightLeg, LeftFoot, RightFoot, Head, Torso, Tail, Legs, RightThigh, LeftThigh, Waist
|
||||
};
|
||||
|
||||
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 Texture2D bodyShapeTexture;
|
||||
|
||||
private readonly int refJointIndex;
|
||||
|
||||
private readonly float steerForce;
|
||||
|
||||
private readonly bool doesFlip;
|
||||
|
||||
public Sprite sprite;
|
||||
|
||||
public bool inWater;
|
||||
|
||||
public FixedMouseJoint pullJoint;
|
||||
|
||||
public readonly LimbType type;
|
||||
|
||||
public readonly bool ignoreCollisions;
|
||||
|
||||
private float maxHealth;
|
||||
private float damage;
|
||||
private float bleeding;
|
||||
|
||||
public readonly float impactTolerance;
|
||||
|
||||
Sound hitSound;
|
||||
//a timer for delaying when a hitsound/attacksound can be played again
|
||||
public float soundTimer;
|
||||
public const float SoundInterval = 0.2f;
|
||||
|
||||
public readonly Attack attack;
|
||||
|
||||
private Direction dir;
|
||||
|
||||
private Item wearingItem;
|
||||
private Sprite wearingItemSprite;
|
||||
|
||||
private Vector2 animTargetPos;
|
||||
|
||||
public Texture2D BodyShapeTexture
|
||||
{
|
||||
get { return bodyShapeTexture; }
|
||||
}
|
||||
|
||||
public bool DoesFlip
|
||||
{
|
||||
get { return doesFlip; }
|
||||
}
|
||||
|
||||
public Vector2 SimPosition
|
||||
{
|
||||
get { return body.Position; }
|
||||
}
|
||||
|
||||
public float Rotation
|
||||
{
|
||||
get { return body.Rotation; }
|
||||
}
|
||||
|
||||
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 Sound HitSound
|
||||
{
|
||||
get { return hitSound; }
|
||||
}
|
||||
|
||||
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 float Damage
|
||||
{
|
||||
get { return damage; }
|
||||
set
|
||||
{
|
||||
damage = Math.Max(value, 0.0f);
|
||||
if (damage >=maxHealth) character.Kill();
|
||||
}
|
||||
}
|
||||
|
||||
public float Bleeding
|
||||
{
|
||||
get { return bleeding; }
|
||||
set { bleeding = MathHelper.Clamp(value, 0.0f, 100.0f); }
|
||||
}
|
||||
|
||||
public Item WearingItem
|
||||
{
|
||||
get { return wearingItem; }
|
||||
set { wearingItem = value; }
|
||||
}
|
||||
|
||||
public Sprite WearingItemSprite
|
||||
{
|
||||
get { return wearingItemSprite; }
|
||||
set { wearingItemSprite = value; }
|
||||
}
|
||||
|
||||
public Limb (Character character, XElement element)
|
||||
{
|
||||
this.character = character;
|
||||
|
||||
dir = Direction.Right;
|
||||
|
||||
doesFlip = ToolBox.GetAttributeBool(element, "flip", false);
|
||||
|
||||
body = new PhysicsBody(element);
|
||||
|
||||
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.CollisionMisc;
|
||||
}
|
||||
|
||||
impactTolerance = ToolBox.GetAttributeFloat(element, "impacttolerance", 20.0f);
|
||||
|
||||
body.UserData = this;
|
||||
|
||||
refJointIndex = -1;
|
||||
|
||||
if (element.Attribute("type") != null)
|
||||
{
|
||||
type = (LimbType)Enum.Parse(typeof(LimbType), element.Attribute("type").Value, true);
|
||||
|
||||
Vector2 jointPos = ToolBox.GetAttributeVector2(element, "pullpos", Vector2.Zero);
|
||||
|
||||
jointPos = ConvertUnits.ToSimUnits(jointPos);
|
||||
|
||||
refJointIndex = ToolBox.GetAttributeInt(element, "refjoint", -1);
|
||||
|
||||
pullJoint = new FixedMouseJoint(body.FarseerBody, jointPos);
|
||||
pullJoint.Enabled = false;
|
||||
pullJoint.MaxForce = 150.0f * body.Mass;
|
||||
|
||||
Game1.world.AddJoint(pullJoint);
|
||||
}
|
||||
else
|
||||
{
|
||||
type = LimbType.None;
|
||||
}
|
||||
|
||||
steerForce = ToolBox.GetAttributeFloat(element, "steerforce", 0.0f);
|
||||
|
||||
maxHealth = Math.Max(ToolBox.GetAttributeFloat(element, "health", 100.0f),1.0f);
|
||||
|
||||
body.BodyType = BodyType.Dynamic;
|
||||
body.FarseerBody.AngularDamping = LimbAngularDamping;
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString())
|
||||
{
|
||||
case "sprite":
|
||||
string spritePath = subElement.Attribute("texture").Value;
|
||||
|
||||
if (character.info!=null)
|
||||
spritePath = spritePath.Replace("[GENDER]", (character.info.gender == Gender.Female) ? "f" : "");
|
||||
|
||||
sprite = new Sprite(subElement, "", spritePath);
|
||||
break;
|
||||
case "attack":
|
||||
attack = new Attack(subElement);
|
||||
break;
|
||||
case "sound":
|
||||
hitSound = Sound.Load(ToolBox.GetAttributeString(subElement, "file", ""));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Move(Vector2 pos, float amount, bool pullFromCenter=false)
|
||||
{
|
||||
Vector2 pullPos = body.Position;
|
||||
if (pullJoint!=null && !pullFromCenter)
|
||||
{
|
||||
pullPos = pullJoint.WorldAnchorA;
|
||||
}
|
||||
|
||||
animTargetPos = pos;
|
||||
|
||||
Vector2 vel = body.LinearVelocity;
|
||||
Vector2 deltaPos = pos - pullPos;
|
||||
deltaPos *= amount;
|
||||
body.ApplyLinearImpulse((deltaPos - vel * 0.5f) * body.Mass, pullPos);
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
if (LinearVelocity.X>100.0f)
|
||||
{
|
||||
DebugConsole.ThrowError("CHARACTER EXPLODED");
|
||||
foreach (Limb limb in character.animController.limbs)
|
||||
{
|
||||
limb.body.ResetDynamics();
|
||||
limb.body.SetTransform(body.Position, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
if (inWater)
|
||||
{
|
||||
//buoyancy
|
||||
Vector2 buoyancy = new Vector2(0, Mass * 9.6f);
|
||||
|
||||
//drag
|
||||
Vector2 velDir = Vector2.Normalize(LinearVelocity);
|
||||
|
||||
Vector2 line = new Vector2((float)Math.Cos(body.Rotation), (float)Math.Sin(body.Rotation));
|
||||
line *= ConvertUnits.ToSimUnits(sprite.size.Y);
|
||||
|
||||
Vector2 normal = new Vector2(-line.Y, line.X);
|
||||
normal = Vector2.Normalize(-normal);
|
||||
|
||||
float dragDot = Vector2.Dot(normal, velDir);
|
||||
Vector2 dragForce = Vector2.Zero;
|
||||
if (dragDot > 0)
|
||||
{
|
||||
float vel = LinearVelocity.Length();
|
||||
float drag = dragDot * vel * vel
|
||||
* ConvertUnits.ToSimUnits(sprite.size.Y);
|
||||
dragForce = drag * -velDir;
|
||||
if (dragForce.Length() > 100.0f) { }
|
||||
}
|
||||
|
||||
body.ApplyForce(dragForce + buoyancy);
|
||||
body.ApplyTorque(body.AngularVelocity * body.Mass * -0.05f);
|
||||
}
|
||||
|
||||
if (character.IsDead) return;
|
||||
|
||||
soundTimer -= deltaTime;
|
||||
|
||||
if (ToolBox.RandomFloat(0.0f, 1000.0f) < Bleeding)
|
||||
{
|
||||
Game1.particleManager.CreateParticle(SimPosition,
|
||||
MathHelper.Pi,
|
||||
ToolBox.RandomFloat(0.0f, 0.0f), !inWater ? "blood" : "waterblood");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void Draw(SpriteBatch spriteBatch, bool debugDraw)
|
||||
{
|
||||
Color color = new Color(1.0f, 1.0f - damage / maxHealth, 1.0f - damage / maxHealth);
|
||||
|
||||
body.Dir = Dir;
|
||||
body.Draw(spriteBatch, sprite, color);
|
||||
|
||||
if (wearingItem != null)
|
||||
{
|
||||
SpriteEffects spriteEffect = (dir == Direction.Right) ? SpriteEffects.None : SpriteEffects.FlipHorizontally;
|
||||
wearingItemSprite.Draw(spriteBatch,
|
||||
new Vector2(body.DrawPosition.X, -body.DrawPosition.Y),
|
||||
color,
|
||||
-body.DrawRotation,
|
||||
1.0f, spriteEffect);
|
||||
}
|
||||
|
||||
if (!debugDraw) return;
|
||||
|
||||
if (pullJoint!=null)
|
||||
{
|
||||
Vector2 pos = ConvertUnits.ToDisplayUnits(pullJoint.WorldAnchorB);
|
||||
GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)pos.Y, 5, 5), Color.Red, true);
|
||||
}
|
||||
|
||||
if (bodyShapeTexture == null)
|
||||
{
|
||||
switch (body.bodyShape)
|
||||
{
|
||||
case PhysicsBody.Shape.Rectangle:
|
||||
bodyShapeTexture = GUI.CreateRectangle(
|
||||
(int)ConvertUnits.ToDisplayUnits(body.width),
|
||||
(int)ConvertUnits.ToDisplayUnits(body.height));
|
||||
break;
|
||||
|
||||
case PhysicsBody.Shape.Capsule:
|
||||
bodyShapeTexture = GUI.CreateCapsule(
|
||||
(int)ConvertUnits.ToDisplayUnits(body.radius),
|
||||
(int)ConvertUnits.ToDisplayUnits(body.height));
|
||||
break;
|
||||
case PhysicsBody.Shape.Circle:
|
||||
bodyShapeTexture = GUI.CreateCircle((int)ConvertUnits.ToDisplayUnits(body.radius));
|
||||
break;
|
||||
}
|
||||
}
|
||||
spriteBatch.Draw(
|
||||
bodyShapeTexture,
|
||||
new Vector2(body.DrawPosition.X, -body.DrawPosition.Y),
|
||||
null,
|
||||
Color.White,
|
||||
-body.DrawRotation,
|
||||
new Vector2(bodyShapeTexture.Width / 2, bodyShapeTexture.Height / 2), 1.0f, SpriteEffects.None, 0.0f);
|
||||
}
|
||||
|
||||
|
||||
public void Remove()
|
||||
{
|
||||
sprite.Remove();
|
||||
body.Remove();
|
||||
if (hitSound!=null) hitSound.Remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Subsurface
|
||||
{
|
||||
|
||||
|
||||
|
||||
class Attack
|
||||
{
|
||||
public enum DamageType { None, Blunt, Slash };
|
||||
|
||||
public enum Type
|
||||
{
|
||||
None, PinchCW, PinchCCW
|
||||
};
|
||||
public readonly Type type;
|
||||
public readonly float range;
|
||||
public readonly float duration;
|
||||
|
||||
public readonly DamageType damageType;
|
||||
|
||||
public readonly float structureDamage;
|
||||
public readonly float damage;
|
||||
public readonly float bleedingDamage;
|
||||
|
||||
public readonly float stun;
|
||||
|
||||
private float priority;
|
||||
|
||||
public Attack(XElement element)
|
||||
{
|
||||
try
|
||||
{
|
||||
type = (Type)Enum.Parse(typeof(Type), element.Attribute("type").Value, true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
type = Type.None;
|
||||
}
|
||||
|
||||
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, "damage", 0.0f);
|
||||
bleedingDamage = ToolBox.GetAttributeFloat(element, "bleedingdamage", 0.0f);
|
||||
|
||||
stun = ToolBox.GetAttributeFloat(element, "stun", 0.0f);
|
||||
|
||||
|
||||
range = FarseerPhysics.ConvertUnits.ToSimUnits(ToolBox.GetAttributeFloat(element, "range", 0.0f));
|
||||
|
||||
duration = ToolBox.GetAttributeFloat(element, "duration", 0.0f);
|
||||
|
||||
priority = ToolBox.GetAttributeFloat(element, "priority", 1.0f);
|
||||
}
|
||||
|
||||
public void DoDamage(IDamageable target, Vector2 position, float deltaTime, bool playSound=true)
|
||||
{
|
||||
float damageAmount = 0.0f;
|
||||
DamageSoundType damageSoundType = DamageSoundType.None;
|
||||
|
||||
if (target as Structure == null)
|
||||
{
|
||||
damageAmount = damage;
|
||||
damageSoundType = (damageType == DamageType.Blunt) ? DamageSoundType.LimbBlunt : DamageSoundType.LimbSlash;
|
||||
}
|
||||
else
|
||||
{
|
||||
damageAmount = structureDamage;
|
||||
damageSoundType = (damageType == DamageType.Blunt) ? DamageSoundType.StructureBlunt: DamageSoundType.StructureSlash;
|
||||
}
|
||||
|
||||
if (playSound) AmbientSoundManager.PlayDamageSound(damageSoundType, damageAmount, position);
|
||||
|
||||
if (duration > 0.0f) damageAmount *= deltaTime;
|
||||
float bleedingAmount = (duration == 0.0f) ? bleedingDamage : bleedingDamage * deltaTime;
|
||||
|
||||
if (damageAmount>0.0f) target.AddDamage(position, damageAmount, bleedingAmount, stun);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,643 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using FarseerPhysics.Dynamics.Contacts;
|
||||
using FarseerPhysics.Dynamics.Joints;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace Subsurface
|
||||
{
|
||||
class Ragdoll
|
||||
{
|
||||
public static List<Ragdoll> list = new List<Ragdoll>();
|
||||
|
||||
public static bool DebugDraw = false;
|
||||
|
||||
protected Hull currentHull;
|
||||
|
||||
public Limb[] limbs;
|
||||
private Dictionary<LimbType, Limb> limbDictionary;
|
||||
public RevoluteJoint[] limbJoints;
|
||||
|
||||
Character character;
|
||||
|
||||
private Limb lowestLimb;
|
||||
|
||||
protected float strongestImpact;
|
||||
|
||||
float headPosition, headAngle;
|
||||
float torsoPosition, torsoAngle;
|
||||
|
||||
protected double onFloorTimer;
|
||||
|
||||
//the movement speed of the ragdoll
|
||||
public Vector2 movement;
|
||||
//the target speed towards which movement is interpolated
|
||||
private Vector2 targetMovement;
|
||||
|
||||
//a movement vector that overrides targetmovement if trying to steer
|
||||
//a character to the position sent by server in multiplayer mode
|
||||
public Vector2 correctionMovement;
|
||||
|
||||
protected float floorY;
|
||||
protected float surfaceY;
|
||||
|
||||
protected bool inWater, headInWater;
|
||||
public bool onGround;
|
||||
private bool ignorePlatforms;
|
||||
|
||||
protected Structure stairs;
|
||||
|
||||
protected Direction dir;
|
||||
|
||||
//private byte ID;
|
||||
|
||||
public Limb LowestLimb
|
||||
{
|
||||
get { return lowestLimb; }
|
||||
}
|
||||
|
||||
public Vector2 TargetMovement
|
||||
{
|
||||
get { return (correctionMovement == Vector2.Zero) ? targetMovement : correctionMovement; }
|
||||
set { targetMovement = value; }
|
||||
}
|
||||
|
||||
public float HeadPosition
|
||||
{
|
||||
get { return headPosition; }
|
||||
}
|
||||
|
||||
public float HeadAngle
|
||||
{
|
||||
get { return headAngle; }
|
||||
}
|
||||
|
||||
public float TorsoPosition
|
||||
{
|
||||
get { return torsoPosition; }
|
||||
}
|
||||
|
||||
public float TorsoAngle
|
||||
{
|
||||
get { return torsoAngle; }
|
||||
}
|
||||
|
||||
public float Dir
|
||||
{
|
||||
get { return ((dir == Direction.Left) ? -1.0f : 1.0f); }
|
||||
}
|
||||
|
||||
public bool InWater
|
||||
{
|
||||
get { return inWater; }
|
||||
}
|
||||
|
||||
public bool HeadInWater
|
||||
{
|
||||
get { return headInWater; }
|
||||
}
|
||||
|
||||
public Hull CurrentHull
|
||||
{
|
||||
get { return currentHull;}
|
||||
}
|
||||
|
||||
public bool IgnorePlatforms
|
||||
{
|
||||
get { return ignorePlatforms; }
|
||||
set
|
||||
{
|
||||
if (ignorePlatforms == value) return;
|
||||
ignorePlatforms = value;
|
||||
|
||||
foreach (Limb l in limbs)
|
||||
{
|
||||
if (l.ignoreCollisions) continue;
|
||||
|
||||
l.body.CollidesWith = (ignorePlatforms) ?
|
||||
Physics.CollisionWall | Physics.CollisionProjectile | Physics.CollisionStairs
|
||||
: Physics.CollisionAll & ~Physics.CollisionCharacter & ~Physics.CollisionMisc;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public float StrongestImpact
|
||||
{
|
||||
get { return strongestImpact; }
|
||||
set { strongestImpact = Math.Max(value, strongestImpact); }
|
||||
}
|
||||
|
||||
public Structure Stairs
|
||||
{
|
||||
get { return stairs; }
|
||||
}
|
||||
|
||||
public Ragdoll(Character character, XElement element)
|
||||
{
|
||||
list.Add(this);
|
||||
|
||||
this.character = character;
|
||||
|
||||
dir = Direction.Right;
|
||||
|
||||
//int limbAmount = ;
|
||||
limbs = new Limb[element.Elements("limb").Count()];
|
||||
limbJoints = new RevoluteJoint[element.Elements("joint").Count()];
|
||||
limbDictionary = new Dictionary<LimbType, Limb>();
|
||||
|
||||
headPosition = ToolBox.GetAttributeFloat(element, "headposition", 50.0f);
|
||||
headPosition = ConvertUnits.ToSimUnits(headPosition);
|
||||
headAngle = MathHelper.ToRadians(ToolBox.GetAttributeFloat(element, "headangle", 0.0f));
|
||||
|
||||
torsoPosition = ToolBox.GetAttributeFloat(element, "torsoposition", 50.0f);
|
||||
torsoPosition = ConvertUnits.ToSimUnits(torsoPosition);
|
||||
torsoAngle = MathHelper.ToRadians(ToolBox.GetAttributeFloat(element, "torsoangle", 0.0f));
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString())
|
||||
{
|
||||
case "limb":
|
||||
byte ID = Convert.ToByte(subElement.Attribute("id").Value);
|
||||
|
||||
Limb limb = new Limb(character, subElement);
|
||||
|
||||
limb.body.FarseerBody.OnCollision += OnLimbCollision;
|
||||
|
||||
limbs[ID] = limb;
|
||||
if (!limbDictionary.ContainsKey(limb.type)) limbDictionary.Add(limb.type, limb);
|
||||
break;
|
||||
case "joint":
|
||||
Byte limb1ID = Convert.ToByte(subElement.Attribute("limb1").Value);
|
||||
Byte limb2ID = Convert.ToByte(subElement.Attribute("limb2").Value);
|
||||
|
||||
Vector2 limb1Pos = ToolBox.GetAttributeVector2(subElement, "limb1anchor", Vector2.Zero);
|
||||
limb1Pos = ConvertUnits.ToSimUnits(limb1Pos);
|
||||
|
||||
Vector2 limb2Pos = ToolBox.GetAttributeVector2(subElement, "limb2anchor", Vector2.Zero);
|
||||
limb2Pos = ConvertUnits.ToSimUnits(limb2Pos);
|
||||
|
||||
RevoluteJoint joint = new RevoluteJoint(limbs[limb1ID].body.FarseerBody, limbs[limb2ID].body.FarseerBody, limb1Pos, limb2Pos);
|
||||
|
||||
joint.CollideConnected = false;
|
||||
|
||||
if (subElement.Attribute("lowerlimit")!=null)
|
||||
{
|
||||
joint.LimitEnabled = true;
|
||||
joint.LowerLimit = float.Parse(subElement.Attribute("lowerlimit").Value) * ((float)Math.PI / 180.0f);
|
||||
joint.UpperLimit = float.Parse(subElement.Attribute("upperlimit").Value) * ((float)Math.PI / 180.0f);
|
||||
}
|
||||
|
||||
joint.MotorEnabled = true;
|
||||
joint.MaxMotorTorque = 0.25f;
|
||||
|
||||
Game1.world.AddJoint(joint);
|
||||
|
||||
for (int i = 0; i < limbJoints.Length; i++ )
|
||||
{
|
||||
if (limbJoints[i] != null) continue;
|
||||
|
||||
limbJoints[i] = joint;
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
foreach (var joint in limbJoints)
|
||||
{
|
||||
|
||||
joint.BodyB.SetTransform(
|
||||
joint.BodyA.Position+joint.LocalAnchorA-joint.LocalAnchorB,
|
||||
(joint.LowerLimit+joint.UpperLimit)/2.0f);
|
||||
}
|
||||
|
||||
float startDepth = 0.1f;
|
||||
float increment = 0.0001f;
|
||||
|
||||
foreach (Character otherCharacter in Character.characterList)
|
||||
{
|
||||
if (otherCharacter==character) continue;
|
||||
startDepth+=increment;
|
||||
}
|
||||
|
||||
foreach (Limb limb in limbs)
|
||||
{
|
||||
limb.sprite.Depth = startDepth + limb.sprite.Depth * 0.00001f;
|
||||
}
|
||||
}
|
||||
|
||||
public bool OnLimbCollision(Fixture f1, Fixture f2, Contact contact)
|
||||
{
|
||||
Structure structure = f2.Body.UserData as Structure;
|
||||
|
||||
//always collides with bodies other than structures
|
||||
if (structure == null)
|
||||
{
|
||||
CalculateImpact(f1, f2, contact);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (structure.IsPlatform)
|
||||
{
|
||||
if (ignorePlatforms) return false;
|
||||
|
||||
//the collision is ignored if the lowest limb is under the platform
|
||||
if (lowestLimb==null || lowestLimb.SimPosition.Y < f2.Body.Position.Y) return false;
|
||||
}
|
||||
else if (structure.StairDirection!=Direction.None)
|
||||
{
|
||||
if (inWater || (!(targetMovement.Y>Math.Abs(targetMovement.X/2.0f)) && lowestLimb.body.Position.Y < ConvertUnits.ToSimUnits(structure.Rect.Y - structure.Rect.Height) + 0.5f))
|
||||
{
|
||||
stairs = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
Limb limb = f1.Body.UserData as Limb;
|
||||
if (limb != null && (limb.type == LimbType.LeftFoot || limb.type == LimbType.RightFoot))
|
||||
{
|
||||
Debug.WriteLine(contact.Manifold.LocalNormal.Y);
|
||||
if (contact.Manifold.LocalNormal.Y >= 0.0f)
|
||||
{
|
||||
stairs = structure;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
stairs = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
CalculateImpact(f1, f2, contact);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void CalculateImpact(Fixture f1, Fixture f2, Contact contact)
|
||||
{
|
||||
Vector2 normal = contact.Manifold.LocalNormal;
|
||||
float impact = Vector2.Dot(f1.Body.LinearVelocity, -normal);
|
||||
|
||||
|
||||
Limb l = (Limb)f1.Body.UserData;
|
||||
|
||||
if (impact > 1.0f && l.HitSound != null && l.soundTimer<=0.0f) l.HitSound.Play(Math.Min(impact / 5.0f, 1.0f));
|
||||
|
||||
if (impact > l.impactTolerance)
|
||||
{
|
||||
l.Damage += (impact-l.impactTolerance*0.1f);
|
||||
strongestImpact = Math.Max(strongestImpact, impact - l.impactTolerance);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public virtual void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
|
||||
|
||||
|
||||
foreach (Limb limb in limbs)
|
||||
{
|
||||
limb.Draw(spriteBatch, DebugDraw);
|
||||
}
|
||||
|
||||
if (!DebugDraw) return;
|
||||
|
||||
foreach (Limb limb in limbs)
|
||||
{
|
||||
|
||||
if (limb.pullJoint != null)
|
||||
{
|
||||
Vector2 pos = ConvertUnits.ToDisplayUnits(limb.pullJoint.WorldAnchorA);
|
||||
pos.Y = -pos.Y;
|
||||
GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)pos.Y, 5, 5), Color.Red, true, 0.01f);
|
||||
|
||||
if (limb.AnimTargetPos == Vector2.Zero) continue;
|
||||
|
||||
Vector2 pos2 = ConvertUnits.ToDisplayUnits(limb.AnimTargetPos);
|
||||
pos2.Y = -pos2.Y;
|
||||
GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos2.X, (int)pos2.Y, 5, 5), Color.Blue, true, 0.01f);
|
||||
|
||||
GUI.DrawLine(spriteBatch, pos, pos2, Color.Green);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (RevoluteJoint joint in limbJoints)
|
||||
{
|
||||
Vector2 pos = ConvertUnits.ToDisplayUnits(joint.WorldAnchorA);
|
||||
pos.Y = -pos.Y;
|
||||
GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)-pos.Y, 5, 5), Color.White, true);
|
||||
|
||||
pos = ConvertUnits.ToDisplayUnits(joint.WorldAnchorB);
|
||||
pos.Y = -pos.Y;
|
||||
GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)-pos.Y, 5, 5), Color.White, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public virtual void Flip()
|
||||
{
|
||||
dir = (dir == Direction.Left) ? Direction.Right : Direction.Left;
|
||||
|
||||
for (int i = 0; i < limbJoints.Count(); i++)
|
||||
{
|
||||
float lowerLimit = -limbJoints[i].UpperLimit;
|
||||
float upperLimit = -limbJoints[i].LowerLimit;
|
||||
|
||||
limbJoints[i].LowerLimit = lowerLimit;
|
||||
limbJoints[i].UpperLimit = upperLimit;
|
||||
|
||||
limbJoints[i].LocalAnchorA = new Vector2(-limbJoints[i].LocalAnchorA.X, limbJoints[i].LocalAnchorA.Y);
|
||||
limbJoints[i].LocalAnchorB = new Vector2(-limbJoints[i].LocalAnchorB.X, limbJoints[i].LocalAnchorB.Y);
|
||||
}
|
||||
|
||||
|
||||
for (int i = 0; i < limbs.Count(); i++)
|
||||
{
|
||||
if (limbs[i] == null) continue;
|
||||
|
||||
Vector2 spriteOrigin = limbs[i].sprite.Origin;
|
||||
spriteOrigin.X = limbs[i].sprite.SourceRect.Width - spriteOrigin.X;
|
||||
limbs[i].sprite.Origin = spriteOrigin;
|
||||
|
||||
limbs[i].Dir = Dir;
|
||||
|
||||
if (limbs[i].pullJoint == null) continue;
|
||||
|
||||
limbs[i].pullJoint.LocalAnchorA =
|
||||
new Vector2(
|
||||
-limbs[i].pullJoint.LocalAnchorA.X,
|
||||
limbs[i].pullJoint.LocalAnchorA.Y);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="pullFromCenter">if false, force is applied to the position of pullJoint</param>
|
||||
protected void MoveLimb(Limb limb, Vector2 pos, float amount, bool pullFromCenter = false)
|
||||
{
|
||||
limb.Move(pos, amount, pullFromCenter);
|
||||
}
|
||||
|
||||
public void ResetPullJoints()
|
||||
{
|
||||
for (int i = 0; i < limbs.Count(); i++)
|
||||
{
|
||||
if (limbs[i] == null) continue;
|
||||
if (limbs[i].pullJoint == null) continue;
|
||||
limbs[i].pullJoint.Enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void UpdateAll(float deltaTime)
|
||||
{
|
||||
foreach (Ragdoll r in list)
|
||||
{
|
||||
r.Update(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
public void FindHull()
|
||||
{
|
||||
Limb torso = GetLimb(LimbType.Torso);
|
||||
if (torso==null) torso = GetLimb(LimbType.Head);
|
||||
|
||||
currentHull = Hull.FindHull(
|
||||
ConvertUnits.ToDisplayUnits(torso.SimPosition),
|
||||
currentHull);
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
UpdateNetplayerPosition();
|
||||
|
||||
Vector2 flowForce = Vector2.Zero;
|
||||
|
||||
FindLowestLimb();
|
||||
|
||||
FindHull();
|
||||
|
||||
//ragdoll isn't in any room -> it's in the water
|
||||
if (currentHull == null)
|
||||
{
|
||||
inWater = true;
|
||||
headInWater = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
flowForce = GetFlowForce();
|
||||
|
||||
inWater = false;
|
||||
headInWater = false;
|
||||
|
||||
if (ConvertUnits.ToSimUnits(currentHull.Surface)-floorY> HeadPosition)
|
||||
inWater = true;
|
||||
|
||||
}
|
||||
|
||||
foreach (Limb limb in limbs)
|
||||
{
|
||||
Vector2 limbPosition = ConvertUnits.ToDisplayUnits(limb.SimPosition);
|
||||
|
||||
//find the room which the limb is in
|
||||
//the room where the ragdoll is in is used as the "guess", meaning that it's checked first
|
||||
Hull limbHull = Hull.FindHull(limbPosition, currentHull);
|
||||
|
||||
bool prevInWater = limb.inWater;
|
||||
limb.inWater = false;
|
||||
|
||||
if (limbHull==null)
|
||||
{
|
||||
//limb isn't in any room -> it's in the water
|
||||
limb.inWater = true;
|
||||
}
|
||||
else if (limbHull.Volume>0.0f && Map.RectContains(limbHull.Rect, limbPosition))
|
||||
{
|
||||
|
||||
if (limbPosition.Y < limbHull.Surface)
|
||||
{
|
||||
limb.inWater = true;
|
||||
|
||||
if (flowForce.Length() > 0.01f)
|
||||
{
|
||||
limb.body.ApplyForce(flowForce);
|
||||
if (flowForce.Length() > 15.0f) surfaceY = limbHull.Surface;
|
||||
}
|
||||
|
||||
surfaceY = limbHull.Surface;
|
||||
|
||||
if (limb.type == LimbType.Head)
|
||||
{
|
||||
headInWater = true;
|
||||
surfaceY = limbHull.Surface;
|
||||
}
|
||||
}
|
||||
//the limb has gone through the surface of the water
|
||||
if (Math.Abs(limb.LinearVelocity.Y) > 3.0 && inWater != prevInWater)
|
||||
{
|
||||
|
||||
//create a splash particle
|
||||
Game1.particleManager.CreateParticle(
|
||||
new Vector2(limb.SimPosition.X, ConvertUnits.ToSimUnits(limbHull.Surface)),
|
||||
0.0f,
|
||||
new Vector2(0.0f, Math.Abs(limb.LinearVelocity.Y*0.1f)),
|
||||
"watersplash");
|
||||
Game1.particleManager.CreateParticle(
|
||||
new Vector2(limb.SimPosition.X, ConvertUnits.ToSimUnits(limbHull.Surface)),
|
||||
0.0f,
|
||||
limb.LinearVelocity*0.001f,
|
||||
"bubbles");
|
||||
|
||||
|
||||
|
||||
//if the character dropped into water, create a wave
|
||||
if (limb.LinearVelocity.Y<0.0f)
|
||||
{
|
||||
//1.0 when the limb is parallel to the surface of the water
|
||||
// = big splash and a large impact
|
||||
float parallel = (float)Math.Abs(Math.Sin(limb.Rotation));
|
||||
Vector2 impulse = Vector2.Multiply(limb.LinearVelocity, -parallel * limb.Mass);
|
||||
//limb.body.ApplyLinearImpulse(impulse);
|
||||
int n = (int)((limbPosition.X - limbHull.Rect.X) / Hull.WaveWidth);
|
||||
limbHull.WaveVel[n] = Math.Min(impulse.Y * 1.0f, 5.0f);
|
||||
StrongestImpact = ((impulse.Length() * 0.5f) - limb.impactTolerance);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
limb.Update(deltaTime);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void UpdateNetplayerPosition()
|
||||
{
|
||||
Limb refLimb = GetLimb(LimbType.Torso);
|
||||
if (refLimb== null) refLimb = GetLimb(LimbType.Head);
|
||||
|
||||
if (refLimb.body.TargetPosition == Vector2.Zero) return;
|
||||
|
||||
//if the limb is further away than resetdistance, all limbs are immediately snapped to their targetpositions
|
||||
float resetDistance = 1.5f;
|
||||
|
||||
//if the limb is closer than alloweddistance, limb positions aren't updated
|
||||
float allowedDistance = 0.1f;
|
||||
|
||||
float dist = Vector2.Distance(limbs[0].body.Position, refLimb.body.TargetPosition);
|
||||
bool resetAll = (dist > resetDistance && character.largeUpdateTimer == 1);
|
||||
|
||||
Vector2 newMovement = (refLimb.body.TargetPosition - refLimb.body.Position);
|
||||
|
||||
if (newMovement == Vector2.Zero || newMovement.Length() < allowedDistance)
|
||||
{
|
||||
refLimb.body.TargetPosition = Vector2.Zero;
|
||||
correctionMovement = Vector2.Zero;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (inWater)
|
||||
{
|
||||
foreach (Limb limb in limbs)
|
||||
{
|
||||
if (limb.body.TargetPosition == Vector2.Zero) continue;
|
||||
|
||||
limb.body.SetTransform(limb.SimPosition + newMovement * 0.1f, limb.Rotation);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
correctionMovement = Vector2.Normalize(newMovement) * ((targetMovement == Vector2.Zero) ? 1.0f : targetMovement.Length());
|
||||
}
|
||||
}
|
||||
|
||||
if (resetAll)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("resetall");
|
||||
|
||||
foreach (Limb limb in limbs)
|
||||
{
|
||||
if (limb.body.TargetPosition == Vector2.Zero) continue;
|
||||
|
||||
limb.body.LinearVelocity = limb.body.TargetVelocity;
|
||||
limb.body.AngularVelocity = limb.body.TargetAngularVelocity;
|
||||
|
||||
limb.body.SetTransform(limb.body.TargetPosition, limb.body.TargetRotation);
|
||||
limb.body.TargetPosition = Vector2.Zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private Vector2 GetFlowForce()
|
||||
{
|
||||
Vector2 limbPos = ConvertUnits.ToDisplayUnits(limbs[0].SimPosition);
|
||||
|
||||
Vector2 force = Vector2.Zero;
|
||||
foreach (MapEntity e in MapEntity.mapEntityList)
|
||||
{
|
||||
Gap gap = e as Gap;
|
||||
if (gap == null || gap.FlowTargetHull!=currentHull ||gap.FlowForce == Vector2.Zero) continue;
|
||||
|
||||
Vector2 gapPos = gap.SimPosition;
|
||||
|
||||
float dist = Vector2.Distance(limbPos, gapPos);
|
||||
|
||||
force += Vector2.Normalize(gap.FlowForce)*(Math.Max(gap.FlowForce.Length() - dist, 0.0f)/500.0f);
|
||||
}
|
||||
|
||||
if (force.Length() > 20.0f) return force;
|
||||
return force;
|
||||
}
|
||||
|
||||
public Limb GetLimb(LimbType limbType)
|
||||
{
|
||||
Limb limb = null;
|
||||
limbDictionary.TryGetValue(limbType, out limb);
|
||||
return limb;
|
||||
}
|
||||
|
||||
public void FindLowestLimb()
|
||||
{
|
||||
//find the lowest limb
|
||||
lowestLimb = null;
|
||||
foreach (Limb limb in limbs)
|
||||
{
|
||||
if (lowestLimb == null)
|
||||
lowestLimb = limb;
|
||||
else if (limb.SimPosition.Y < lowestLimb.SimPosition.Y)
|
||||
lowestLimb = limb;
|
||||
}
|
||||
}
|
||||
|
||||
public void Remove()
|
||||
{
|
||||
foreach (Limb l in limbs) l.Remove();
|
||||
foreach (RevoluteJoint joint in limbJoints)
|
||||
{
|
||||
Game1.world.RemoveJoint(joint);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Subsurface
|
||||
{
|
||||
class StatusEffect
|
||||
{
|
||||
|
||||
[Flags]
|
||||
public enum Target
|
||||
{
|
||||
This = 1, Parent = 2, Character = 4, Contained = 8, Nearby = 16
|
||||
}
|
||||
|
||||
private Target targets;
|
||||
private string[] targetNames;
|
||||
|
||||
public string[] propertyNames;
|
||||
private object[] propertyEffects;
|
||||
|
||||
private string[] onContainingNames;
|
||||
|
||||
public readonly ActionType type;
|
||||
|
||||
private Explosion explosion;
|
||||
|
||||
private Sound sound;
|
||||
|
||||
public Target Targets
|
||||
{
|
||||
get { return targets; }
|
||||
}
|
||||
|
||||
public string[] TargetNames
|
||||
{
|
||||
get { return targetNames; }
|
||||
}
|
||||
|
||||
public string[] OnContainingNames
|
||||
{
|
||||
get { return onContainingNames; }
|
||||
}
|
||||
|
||||
public static StatusEffect Load(XElement element)
|
||||
{
|
||||
if (element.Attribute("delay")!=null)
|
||||
{
|
||||
return new DelayedEffect(element);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new StatusEffect(element);
|
||||
}
|
||||
}
|
||||
|
||||
protected StatusEffect(XElement element)
|
||||
{
|
||||
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 string[containingNames.Count()];
|
||||
for (int i =0; i < containingNames.Count(); i++)
|
||||
{
|
||||
onContainingNames[i] = containingNames[i].Trim();
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case "target":
|
||||
string[] Flags = attribute.Value.Split(',');
|
||||
foreach (string s in Flags)
|
||||
{
|
||||
targets |= (Target)Enum.Parse(typeof(Target), s, true);
|
||||
}
|
||||
|
||||
break;
|
||||
case "targetnames":
|
||||
string[] names = attribute.Value.Split(',');
|
||||
targetNames = new string[names.Count()];
|
||||
for (int i=0; i < names.Count(); i++ )
|
||||
{
|
||||
targetNames[i] = names[i].Trim();
|
||||
}
|
||||
break;
|
||||
case "sound":
|
||||
sound = Sound.Load(attribute.Value.ToString());
|
||||
break;
|
||||
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().ToLower();
|
||||
propertyEffects[n] = ToolBox.GetAttributeObject(attribute);
|
||||
n++;
|
||||
}
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLower())
|
||||
{
|
||||
case "explosion":
|
||||
explosion = new Explosion(subElement);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//oxygen = ToolBox.GetAttributeFloat(element, "oxygen", 0.0f);
|
||||
|
||||
|
||||
//deteriorateOnActive = ToolBox.GetAttributeFloat(element, "deteriorateonactive", 0.0f);
|
||||
//deteriorateOnUse = ToolBox.GetAttributeFloat(element, "deteriorateonuse", 0.0f);
|
||||
}
|
||||
|
||||
|
||||
public virtual void Apply(ActionType type, float deltaTime, Item item, Character character = null, Limb limb = null)
|
||||
{
|
||||
if (this.type == type) Apply(deltaTime, character, item);
|
||||
|
||||
}
|
||||
|
||||
|
||||
protected virtual void Apply(float deltaTime, Character character, Item item, Limb limb = null)
|
||||
{
|
||||
if (explosion != null) explosion.Explode(item.SimPosition);
|
||||
|
||||
if (sound != null) sound.Play(1.0f, 1000.0f, item.body.FarseerBody);
|
||||
|
||||
for (int i = 0; i < propertyNames.Count(); i++)
|
||||
{
|
||||
ObjectProperty property;
|
||||
|
||||
if (character!=null && character.properties.TryGetValue(propertyNames[i], out property))
|
||||
{
|
||||
ApplyToProperty(property, propertyEffects[i], deltaTime);
|
||||
}
|
||||
|
||||
if (item == null) continue;
|
||||
|
||||
if (item.properties.TryGetValue(propertyNames[i], out property))
|
||||
{
|
||||
ApplyToProperty(property, propertyEffects[i], deltaTime);
|
||||
}
|
||||
|
||||
foreach (ItemComponent ic in item.components)
|
||||
{
|
||||
if (!ic.properties.TryGetValue(propertyNames[i], out property)) continue;
|
||||
ApplyToProperty(property, propertyEffects[i], deltaTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void ApplyToProperty(ObjectProperty property, object value, float deltaTime)
|
||||
{
|
||||
|
||||
Type type = value.GetType();
|
||||
if (type == typeof(float))
|
||||
{
|
||||
property.TrySetValue((float)property.GetValue() + (float)value * deltaTime);
|
||||
}
|
||||
if (type == typeof(int))
|
||||
{
|
||||
property.TrySetValue((int)property.GetValue() + (int)value * deltaTime);
|
||||
}
|
||||
else if (type == typeof(bool))
|
||||
{
|
||||
property.TrySetValue((bool)value);
|
||||
}
|
||||
else if (type == typeof(string))
|
||||
{
|
||||
property.TrySetValue((string)value);
|
||||
}
|
||||
}
|
||||
|
||||
public static void UpdateAll(float deltaTime)
|
||||
{
|
||||
for (int i = DelayedEffect.list.Count-1; i>= 0; i--)
|
||||
{
|
||||
DelayedEffect.list[i].Update(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user