This commit is contained in:
Regalis
2015-07-31 21:05:55 +03:00
parent 23d847a4ac
commit 85b0cda4ca
181 changed files with 4455 additions and 4073 deletions
@@ -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,47 @@
using System.Collections.Generic;
using Microsoft.Xna.Framework;
namespace Subsurface
{
class AITarget
{
public static List<AITarget> List = new List<AITarget>();
public Entity Entity;
protected float soundRange;
protected float sightRange;
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,540 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Xml.Linq;
using FarseerPhysics;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using FarseerPhysics.Dynamics;
namespace Subsurface
{
class EnemyAIController : AIController
{
private const float UpdateTargetsInterval = 5.0f;
private const float RaycastInterval = 1.0f;
//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 float raycastTimer;
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 IDamageable 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)
{
UpdateCoolDown(attackPosition, deltaTime);
return;
}
if (raycastTimer > 0.0)
{
raycastTimer -= deltaTime;
}
else
{
GetTargetEntity();
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 == AttackType.None) continue;
if (Vector2.Distance(limb.SimPosition, attackPosition) > limb.attack.Range) continue;
attackingLimb = limb;
break;
}
return;
}
UpdateLimbAttack(deltaTime, attackingLimb, attackPosition);
}
private void UpdateCoolDown(Vector2 attackPosition, float deltaTime)
{
coolDownTimer -= deltaTime;
attackingLimb = null;
//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);
}
}
private void GetTargetEntity()
{
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;
Body closestBody = Submarine.CheckVisibility(rayStart, rayEnd);
if (Submarine.LastPickedFraction == 1.0f || closestBody == null)
{
wallAttackPos = Vector2.Zero;
return;
}
Structure wall = closestBody.UserData as Structure;
if (wall == null)
{
wallAttackPos = Submarine.LastPickedPosition;
}
else
{
int sectionIndex = wall.FindSectionIndex(ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition));
float sectionDamage = wall.SectionDamage(sectionIndex);
for (int i = sectionIndex - 2; i <= sectionIndex + 2; i++)
{
if (wall.SectionHasHole(i))
{
sectionIndex = i;
break;
}
if (wall.SectionDamage(i) > sectionDamage) sectionIndex = i;
}
wallAttackPos = wall.SectionPosition(sectionIndex);
wallAttackPos = ConvertUnits.ToSimUnits(wallAttackPos);
}
targetEntity = closestBody.UserData as IDamageable;
}
private void UpdateLimbAttack(float deltaTime, Limb limb, Vector2 attackPosition)
{
IDamageable damageTarget = null;
switch (limb.attack.Type)
{
case AttackType.PinchCW:
case AttackType.PinchCCW:
float dir = (limb.attack.Type == AttackType.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;
}
}
//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 && Rand.Range(1,3, false)==1)
{
selectedTarget = null;
character.AnimController.TargetMovement = -character.AnimController.TargetMovement;
state = AiState.None;
return;
}
distanceAccumulator = 0.0f;
selectedTarget = null;
selectedTargetMemory = null;
targetValue = 0.0f;
UpdateTargetMemories();
foreach (AITarget target in AITarget.List)
{
float valueModifier = 0.0f;
float dist = 0.0f;
IDamageable targetDamageable = target.Entity as IDamageable;
if (targetDamageable!=null && targetDamageable.Health <= 0.0f) continue;
Character targetCharacter = target.Entity as Character;
//ignore the aitarget if it is the character itself
if (targetCharacter == character) continue;
if (targetCharacter!=null)
{
if (attackHumans == 0.0f || targetCharacter.SpeciesName != "human") continue;
valueModifier = attackHumans;
}
else if (target.Entity!=null && attackRooms!=0.0f)
{
//skip the target if it's the room the character is inside of
if (character.AnimController.CurrentHull != null && character.AnimController.CurrentHull == target.Entity as Hull) continue;
valueModifier = attackRooms;
}
dist = Vector2.Distance(
character.AnimController.limbs[0].SimPosition,
target.Position);
dist = ConvertUnits.ToDisplayUnits(dist);
AITargetMemory targetMemory = FindTargetMemory(target);
valueModifier = valueModifier * targetMemory.Priority / dist;
//dist -= targetMemory.Priority;
if (Math.Abs(valueModifier) > Math.Abs(targetValue) && (dist < target.SightRange * sight || dist < target.SoundRange * hearing))
{
Vector2 rayStart = character.AnimController.limbs[0].SimPosition;
Vector2 rayEnd = target.Position;
Body closestBody = Submarine.CheckVisibility(rayStart, rayEnd);
Structure closestStructure = (closestBody == null) ? null : closestBody.UserData as Structure;
//if (targetCharacter != null)
//{
// //if target is a character that isn't visible, ignore
// if (closestStructure != null) continue;
// //prefer targets with low health
// valueModifier = valueModifier / targetCharacter.Health;
//}
//else
//{
if (targetDamageable != null)
{
valueModifier = valueModifier / targetDamageable.Health;
}
else if (closestStructure!=null)
{
valueModifier = valueModifier / (closestStructure as IDamageable).Health;
}
else
{
valueModifier = valueModifier / 1000.0f;
}
//}
//float newTargetValue = valueModifier/dist;
if (selectedTarget == null || Math.Abs(valueModifier) > Math.Abs(targetValue))
{
selectedTarget = target;
selectedTargetMemory = targetMemory;
targetValue = valueModifier;
Debug.WriteLine(selectedTarget.Entity+": "+targetValue);
}
}
}
//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 as Entity).ID);
}
public override void ReadNetworkData(NetIncomingMessage message)
{
AiState newState = AiState.None;
Vector2 newWallAttackPos;
float wanderAngle;
float updateTargetsTimer, raycastTimer, coolDownTimer;
int targetID;
try
{
newState = (AiState)(message.ReadByte());
newWallAttackPos = new Vector2(message.ReadFloat(), message.ReadFloat());
wanderAngle = MathUtils.WrapAngleTwoPi(message.ReadFloat());
updateTargetsTimer = MathHelper.Clamp(message.ReadFloat(), 0.0f, UpdateTargetsInterval);
raycastTimer = MathHelper.Clamp(message.ReadFloat(), 0.0f, RaycastInterval);
coolDownTimer = MathHelper.Clamp(message.ReadFloat(), 0.0f, attackCoolDown);
targetID = message.ReadInt32();
}
catch { return; }
wallAttackPos = newWallAttackPos;
steeringManager.WanderAngle = wanderAngle;
this.updateTargetsTimer = updateTargetsTimer;
this.raycastTimer = raycastTimer;
this.coolDownTimer = coolDownTimer;
if (targetID > -1)
targetEntity = Entity.FindEntityByID(targetID) as IDamageable;
}
}
//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,240 @@
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
namespace Subsurface
{
class PathNode
{
private WayPoint wayPoint;
private int wayPointID;
public int state;
public PathNode Parent;
private Vector2 position;
public float F,G,H;
public List<PathNode> connections;
public float[] distances;
public WayPoint Waypoint
{
get { return wayPoint; }
}
public Vector2 Position
{
get {return position;}
}
public PathNode(WayPoint wayPoint)
{
this.wayPoint = wayPoint;
this.position = wayPoint.SimPosition;
wayPointID = wayPoint.ID;
connections = new List<PathNode>();
}
public static List<PathNode> GenerateNodes(List<WayPoint> wayPoints)
{
var nodes = new Dictionary<int, PathNode>();
foreach (WayPoint wayPoint in wayPoints)
{
nodes.Add(wayPoint.ID, new PathNode(wayPoint));
}
foreach (KeyValuePair<int,PathNode> node in nodes)
{
foreach (MapEntity linked in node.Value.wayPoint.linkedTo)
{
PathNode connectedNode = null;
nodes.TryGetValue(linked.ID, out connectedNode);
if (connectedNode == null) continue;
node.Value.connections.Add(connectedNode);
}
}
var nodeList = nodes.Values.ToList();
foreach (PathNode node in nodeList)
{
node.distances = new float[node.connections.Count];
for (int i = 0; i< node.distances.Length; i++)
{
node.distances[i] = Vector2.Distance(node.position, node.connections[i].position);
}
}
return nodeList;
}
}
class PathFinder
{
List<PathNode> nodes;
private bool insideSubmarine;
public PathFinder(List<WayPoint> wayPoints, bool insideSubmarine = false)
{
nodes = PathNode.GenerateNodes(wayPoints.FindAll(w => w.MoveWithLevel != insideSubmarine));
this.insideSubmarine = insideSubmarine;
}
public SteeringPath FindPath(Vector2 start, Vector2 end)
{
float closestDist = 0.0f;
PathNode startNode = null;
foreach (PathNode node in nodes)
{
float dist = Vector2.Distance(start,node.Position);
if (dist<closestDist || startNode==null)
{
closestDist = dist;
startNode = node;
}
}
closestDist = 0.0f;
PathNode endNode = null;
foreach (PathNode node in nodes)
{
float dist = Vector2.Distance(end, node.Position);
if (dist < closestDist || endNode == null)
{
closestDist = dist;
endNode = node;
}
}
if (startNode == null || endNode == null)
{
DebugConsole.ThrowError("Pathfinding error, couldn't find pathnodes");
return new SteeringPath();
}
return FindPath(startNode,endNode);
}
public SteeringPath FindPath(WayPoint start, WayPoint end)
{
PathNode startNode=null, endNode=null;
foreach (PathNode node in nodes)
{
if (node.Waypoint == start)
{
startNode = node;
if (endNode != null) break;
}
if (node.Waypoint == end)
{
endNode = node;
if (startNode != null) break;
}
if (startNode==null || endNode==null)
{
DebugConsole.ThrowError("Pathfinding error, couldn't find matching pathnodes to waypoints");
return new SteeringPath();;
}
}
return FindPath(startNode, endNode);
}
private SteeringPath FindPath(PathNode start, PathNode end)
{
foreach (PathNode node in nodes)
{
node.state = 0;
node.F = 0.0f;
node.G = 0.0f;
node.H = 0.0f;
}
start.state = 1;
while (true)
{
PathNode currNode = null;
float dist = 10000.0f;
foreach (PathNode node in nodes)
{
if (node.state != 1) continue;
if (node.F < dist)
{
dist = node.F;
currNode = node;
}
}
if (currNode == null || currNode == end) break;
currNode.state = 2;
for (int i = 0; i < currNode.connections.Count; i++)
{
PathNode nextNode = currNode.connections[i];
//a node that hasn't been searched yet
if (nextNode.state==0)
{
nextNode.H = Vector2.Distance(nextNode.Position,end.Position);
nextNode.G = currNode.G + currNode.distances[i];
nextNode.F = nextNode.G + nextNode.H;
nextNode.Parent = currNode;
nextNode.state = 1;
}
//node that has been searched
else if (nextNode.state==1)
{
float tempG = currNode.G + currNode.distances[i];
//only use if this new route is better than the
//route the node was a part of
if (tempG < nextNode.G)
{
nextNode.G = tempG;
nextNode.F = nextNode.G + nextNode.H;
nextNode.Parent = currNode;
}
}
}
}
if (end.state==0)
{
//path not found
return new SteeringPath();
}
SteeringPath path = new SteeringPath();
List<WayPoint> finalPath = new List<WayPoint>();
PathNode pathNode = end;
while (pathNode != start && pathNode != null)
{
finalPath.Add(pathNode.Waypoint);
pathNode = pathNode.Parent;
}
finalPath.Reverse();
foreach (WayPoint wayPoint in finalPath)
{
path.AddNode(wayPoint);
}
return path;
}
}
}
@@ -0,0 +1,151 @@
using System;
using Microsoft.Xna.Framework;
using FarseerPhysics.Dynamics;
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 = Rand.Range(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 += Rand.Range(0.0f, 1.0f) * angleChange - angleChange * 0.5f;
Vector2 newSteering = circleCenter + displacement;
float steeringSpeed = (newSteering + host.Steering).Length();
if (steeringSpeed > speed)
{
newSteering = Vector2.Normalize(newSteering) * speed;
}
return newSteering;
}
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;
Body closestBody = Submarine.CheckVisibility(host.Position, ahead);
if (closestBody == null)
{
avoidSteering = Vector2.Zero;
return Vector2.Zero;
}
else
{
Structure closestStructure = closestBody.UserData as Structure;
if (closestStructure!=null)
{
Vector2 obstaclePosition = Submarine.LastPickedPosition;
if (closestStructure.IsHorizontal)
{
obstaclePosition.Y = closestStructure.SimPosition.Y;
}
else
{
obstaclePosition.X = closestStructure.SimPosition.X;
}
avoidSteering = Vector2.Normalize(Submarine.LastPickedPosition - obstaclePosition);
}
}
}
else
{
rayCastTimer -= deltaTime;
}
return avoidSteering * speed;
}
}
}
@@ -0,0 +1,41 @@
using System.Collections.Generic;
using Microsoft.Xna.Framework;
namespace Subsurface
{
class SteeringPath
{
private Queue<WayPoint> nodes;
WayPoint currentNode;
public SteeringPath()
{
nodes = new Queue<WayPoint>();
}
public void AddNode(WayPoint node)
{
if (node == null) return;
nodes.Enqueue(node);
}
public WayPoint CurrentNode
{
get { return currentNode; }
}
public WayPoint GetNode(Vector2 pos, float minDistance = 0.1f)
{
if (nodes.Count == 0) return null;
if (currentNode == null || Vector2.Distance(pos, currentNode.SimPosition) < 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
{
public bool IsStanding;
public enum Animation { None, Climbing, UsingConstruction, Struggle };
public Animation Anim;
public Direction TargetDir;
protected Character character;
protected float walkSpeed, swimSpeed;
//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) { }
}
}
+116
View File
@@ -0,0 +1,116 @@
using Microsoft.Xna.Framework;
using System;
using System.Xml.Linq;
namespace Subsurface
{
public enum DamageType { None, Blunt, Slash }
public enum AttackType
{
None, PinchCW, PinchCCW
}
struct AttackResult
{
public readonly float Damage;
public readonly float Bleeding;
public readonly bool HitArmor;
public AttackResult(float damage, float bleeding, bool hitArmor=false)
{
this.Damage = damage;
this.Bleeding = bleeding;
this.HitArmor = hitArmor;
}
}
class Attack
{
public readonly AttackType 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 = (AttackType)Enum.Parse(typeof(AttackType), element.Attribute("type").Value, true);
}
catch
{
Type = AttackType.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, "structuredamage", 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 AttackResult DoDamage(IDamageable target, Vector2 position, float deltaTime, bool playSound = true)
{
float damageAmount = 0.0f;
//DamageSoundType damageSoundType = DamageSoundType.None;
if (target as Character == null)
{
damageAmount = StructureDamage;
//damageSoundType = (damageType == DamageType.Blunt) ? DamageSoundType.StructureBlunt: DamageSoundType.StructureSlash;
}
else
{
damageAmount = Damage;
//damageSoundType = (damageType == DamageType.Blunt) ? DamageSoundType.LimbBlunt : DamageSoundType.LimbSlash;
}
//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)
{
return target.AddDamage(position, DamageType, damageAmount, bleedingAmount, Stun, playSound);
}
else
{
return new AttackResult(0.0f, 0.0f);
}
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,279 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Xml.Linq;
namespace Subsurface
{
public enum Gender { None, Male, Female };
class CharacterInfo
{
public string Name;
public Character Character;
public readonly string File;
public Job Job;
private List<int> pickedItems;
private Vector2[] headSpriteRange;
private Gender gender;
public int Salary;
public int HeadSpriteId;
private Sprite headSprite;
public bool StartItemsGiven;
public List<int> PickedItemIDs
{
get { return pickedItems; }
}
public Sprite HeadSprite
{
get
{
if (headSprite == null) LoadHeadSprite();
return headSprite;
}
}
public Gender Gender
{
get { return gender; }
set
{
if (gender == value) return;
gender = value;
int genderIndex = (this.gender == Gender.Female) ? 1 : 0;
if (headSpriteRange[genderIndex] != Vector2.Zero)
{
HeadSpriteId = Rand.Range((int)headSpriteRange[genderIndex].X, (int)headSpriteRange[genderIndex].Y + 1);
}
else
{
HeadSpriteId = 0;
}
LoadHeadSprite();
}
}
public CharacterInfo(string file, string name = "", Gender gender = Gender.None, JobPrefab jobPrefab = null)
{
this.File = file;
headSpriteRange = new Vector2[2];
pickedItems = new List<int>();
//ID = -1;
XDocument doc = ToolBox.TryLoadXml(file);
if (doc == null) return;
if (ToolBox.GetAttributeBool(doc.Root, "genders", false))
{
if (gender == Gender.None)
{
float femaleRatio = ToolBox.GetAttributeFloat(doc.Root, "femaleratio", 0.5f);
this.gender = (Rand.Range(0.0f, 1.0f, false) < femaleRatio) ? Gender.Female : Gender.Male;
}
else
{
this.gender = gender;
}
}
headSpriteRange[0] = ToolBox.GetAttributeVector2(doc.Root, "headid", Vector2.Zero);
headSpriteRange[1] = headSpriteRange[0];
if (headSpriteRange[0] == Vector2.Zero)
{
headSpriteRange[0] = ToolBox.GetAttributeVector2(doc.Root, "maleheadid", Vector2.Zero);
headSpriteRange[1] = ToolBox.GetAttributeVector2(doc.Root, "femaleheadid", Vector2.Zero);
}
int genderIndex = (this.gender == Gender.Female) ? 1 : 0;
if (headSpriteRange[genderIndex] != Vector2.Zero)
{
HeadSpriteId = Rand.Range((int)headSpriteRange[genderIndex].X, (int)headSpriteRange[genderIndex].Y + 1);
}
this.Job = (jobPrefab == null) ? Job.Random() : new Job(jobPrefab);
if (!string.IsNullOrEmpty(name))
{
this.Name = name;
return;
}
if (doc.Root.Element("name") != null)
{
string firstNamePath = ToolBox.GetAttributeString(doc.Root.Element("name"), "firstname", "");
if (firstNamePath != "")
{
firstNamePath = firstNamePath.Replace("[GENDER]", (this.gender == Gender.Female) ? "f" : "");
this.Name = ToolBox.GetRandomLine(firstNamePath);
}
string lastNamePath = ToolBox.GetAttributeString(doc.Root.Element("name"), "lastname", "");
if (lastNamePath != "")
{
lastNamePath = lastNamePath.Replace("[GENDER]", (this.gender == Gender.Female) ? "f" : "");
if (this.Name != "") this.Name += " ";
this.Name += ToolBox.GetRandomLine(lastNamePath);
}
}
Salary = CalculateSalary();
}
private void LoadHeadSprite()
{
XDocument doc = ToolBox.TryLoadXml(File);
if (doc == null) return;
XElement ragdollElement = doc.Root.Element("ragdoll");
foreach (XElement limbElement in ragdollElement.Elements())
{
if (ToolBox.GetAttributeString(limbElement, "type", "").ToLower() != "head") continue;
XElement spriteElement = limbElement.Element("sprite");
string spritePath = spriteElement.Attribute("texture").Value;
spritePath = spritePath.Replace("[GENDER]", (this.gender == Gender.Female) ? "f" : "");
spritePath = spritePath.Replace("[HEADID]", HeadSpriteId.ToString());
headSprite = new Sprite(spriteElement, "", spritePath);
break;
}
}
public GUIFrame CreateInfoFrame(Rectangle rect)
{
GUIFrame frame = new GUIFrame(rect, Color.Transparent);
frame.Padding = new Vector4(10.0f,10.0f,10.0f,10.0f);
return CreateInfoFrame(frame);
}
public GUIFrame CreateInfoFrame(GUIFrame frame)
{
GUIImage image = new GUIImage(new Rectangle(0,0,30,30), HeadSprite, Alignment.TopLeft, frame);
int x = 0, y = 0;
new GUITextBlock(new Rectangle(x+80, y, 200, 20), Name, GUI.style, frame);
y += 20;
new GUITextBlock(new Rectangle(x+80, y, 200, 20), Job.Name, GUI.style, frame);
y += 30;
var skills = Job.Skills;
skills.Sort((s1, s2) => -s1.Level.CompareTo(s2.Level));
new GUITextBlock(new Rectangle(x, y, 200, 20), "Skills:", GUI.style, frame);
y += 20;
foreach (Skill skill in skills)
{
Color textColor = Color.White * (0.5f + skill.Level/200.0f);
new GUITextBlock(new Rectangle(x+20, y, 200, 20), skill.Name, Color.Transparent, textColor, Alignment.Left, GUI.style, frame);
new GUITextBlock(new Rectangle(x + 20, y, 200, 20), skill.Level.ToString(), Color.Transparent, textColor, Alignment.Right, GUI.style, frame);
y += 20;
}
return frame;
}
public void UpdateCharacterItems()
{
pickedItems.Clear();
foreach (Item item in Character.Inventory.items)
{
if (item == null) continue;
pickedItems.Add(item.ID);
}
}
public CharacterInfo(XElement element)
{
Name = ToolBox.GetAttributeString(element, "name", "unnamed");
string genderStr = ToolBox.GetAttributeString(element, "gender", "male").ToLower();
gender = (genderStr == "m") ? Gender.Male : Gender.Female;
File = ToolBox.GetAttributeString(element, "file", "");
Salary = ToolBox.GetAttributeInt(element, "salary", 1000);
HeadSpriteId = ToolBox.GetAttributeInt(element, "headspriteid", 1);
StartItemsGiven = ToolBox.GetAttributeBool(element, "startitemsgiven", false);
pickedItems = new List<int>();
string pickedItemString = ToolBox.GetAttributeString(element, "items", "");
if (!string.IsNullOrEmpty(pickedItemString))
{
string[] itemIds = pickedItemString.Split(',');
foreach (string s in itemIds)
{
pickedItems.Add(int.Parse(s));
}
}
foreach (XElement subElement in element.Elements())
{
if (subElement.Name.ToString().ToLower() != "job") continue;
Job = new Job(subElement);
break;
}
}
private int CalculateSalary()
{
if (Name == null || Job == null) return 0;
int salary = Math.Abs(Name.GetHashCode()) % 100;
foreach (Skill skill in Job.Skills)
{
salary += skill.Level * 10;
}
return salary;
}
public virtual XElement Save(XElement parentElement)
{
XElement charElement = new XElement("character");
charElement.Add(
new XAttribute("name", Name),
new XAttribute("file", File),
new XAttribute("gender", gender == Gender.Male ? "m" : "f"),
new XAttribute("salary", Salary),
new XAttribute("headspriteid", HeadSpriteId),
new XAttribute("startitemsgiven", StartItemsGiven));
if (Character != null && Character.Inventory != null)
{
UpdateCharacterItems();
}
if (pickedItems.Count > 0)
{
charElement.Add(new XAttribute("items", string.Join(",", pickedItems)));
}
Job.Save(charElement);
parentElement.Add(charElement);
return charElement;
}
}
}
@@ -0,0 +1,53 @@
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Xml.Linq;
namespace Subsurface
{
class DelayedEffect : StatusEffect
{
public static List<DelayedEffect> List = new List<DelayedEffect>();
private float delay;
private float timer;
private Entity entity;
private List<IPropertyObject> targets;
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, Entity entity, List<IPropertyObject> targets)
{
if (this.type != type) return;
timer = delay;
this.entity = entity;
this.targets = targets;
List.Add(this);
}
public void Update(float deltaTime)
{
timer -= deltaTime;
if (timer > 0.0f) return;
base.Apply(1.0f, entity, targets);
List.Remove(this);
}
}
}
@@ -0,0 +1,381 @@
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 rotateTowardsMovement;
private bool flip;
private float flipTimer;
private float? footRotation;
public FishAnimController(Character character, XElement element)
: base(character, element)
{
waveAmplitude = ConvertUnits.ToSimUnits(ToolBox.GetAttributeFloat(element, "waveamplitude", 0.0f));
waveLength = ConvertUnits.ToSimUnits(ToolBox.GetAttributeFloat(element, "wavelength", 0.0f));
flip = ToolBox.GetAttributeBool(element, "flip", false);
walkSpeed = ToolBox.GetAttributeFloat(element, "walkspeed", 1.0f);
swimSpeed = ToolBox.GetAttributeFloat(element, "swimspeed", 1.0f);
float footRot = ToolBox.GetAttributeFloat(element,"footrotation", float.NaN);
if (!float.IsNaN(footRot))
{
footRotation = MathHelper.ToRadians(footRot);
}
rotateTowardsMovement = ToolBox.GetAttributeBool(element, "rotatetowardsmovement", true);
}
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)*0.5f)
{
TargetDir = Direction.Right;
}
else if (movement.X < -0.1f && movement.X < -Math.Abs(movement.Y)*0.5f)
{
TargetDir = Direction.Left;
}
}
else
{
Limb head = GetLimb(LimbType.Head);
float rotation = MathUtils.WrapAngleTwoPi(head.Rotation);
rotation = MathHelper.ToDegrees(rotation);
if (rotation < 0.0f) rotation += 360;
if (rotation > 20 && rotation < 160)
{
TargetDir = Direction.Left;
}
else if (rotation > 200 && rotation < 340)
{
TargetDir = Direction.Right;
}
}
//if (stunTimer > gameTime.TotalGameTime.TotalMilliseconds) return;
flipTimer += deltaTime;
if (TargetDir != dir)
{
if (flipTimer>1.0f)
{
Flip();
if (flip) Mirror();
flipTimer = 0.0f;
}
}
}
void UpdateSineAnim(float deltaTime)
{
movement = MathUtils.SmoothStep(movement, TargetMovement*swimSpeed, 1.0f);
if (movement == Vector2.Zero) return;
if (!inWater) movement.Y = Math.Min(0.0f, movement.Y);
float movementAngle = MathUtils.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 = MathUtils.GetShortestAngle(tail.body.Rotation, movementAngle + waveRotation);
tail.body.ApplyTorque(angle * tail.Mass);
//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 = (rotateTowardsMovement) ?
head.body.Rotation+ MathUtils.GetShortestAngle(head.body.Rotation, movementAngle) :
HeadAngle*Dir;
head.body.SmoothRotate(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 = MathUtils.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, 10.0f);
}
else
{
colliderLimb = head;
colliderHeight = HeadPosition;
if (onGround) colliderLimb.body.SmoothRotate(HeadAngle*Dir, 100.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 (!onGround) return;
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;
if (Math.Abs(colliderPos.Y - floorY) < colliderHeight * 1.2f)
{
colliderLimb.Move(new Vector2(colliderPos.X + movement.X * 0.2f, floorY + colliderHeight), 5.0f);
}
float walkCycleSpeed = head.LinearVelocity.X * 0.05f;
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);
}
if (footRotation!=null) limb.body.SmoothRotate((float)footRotation*Dir, 50.0f);
break;
case LimbType.LeftLeg:
case LimbType.RightLeg:
if (legTorque!=0.0f) limb.body.ApplyTorque(limb.Mass*legTorque*Dir);
break;
}
}
}
void UpdateStruggling()
{
Limb head = GetLimb(LimbType.Head);
Limb tail = GetLimb(LimbType.Tail);
if (head != null) head.body.ApplyTorque(head.Mass * Dir * 0.1f);
if (tail != null) 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,826 @@
using System;
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;
if (inWater) stairs = null;
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:
if (inWater) return -1;
Structure structure = fixture.Body.UserData as Structure;
if (stairs == null && structure!=null)
{
if (LowestLimb.SimPosition.Y < structure.SimPosition.Y)
{
return -1;
}
else
{
stairs = structure;
}
}
break;
case Physics.CollisionPlatform:
Structure platform = fixture.Body.UserData as Structure;
if (IgnorePlatforms || LowestLimb.Position.Y < platform.Rect.Y) 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;
if (GetLimb(LimbType.Torso).inWater) inWater = true;
//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)
{
TargetMovement = new Vector2(MathHelper.Clamp(TargetMovement.X, -2.0f, 2.0f), TargetMovement.Y) ;
if ((TargetMovement.X>0.0f && stairs.StairDirection == Direction.Right) ||
TargetMovement.X < 0.0f && stairs.StairDirection == Direction.Left)
{
TargetMovement *= 1.35f;
}
else
{
TargetMovement /= 1.2f;
}
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 = MathUtils.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 =
MathUtils.SmoothStep(torso.SimPosition,
new Vector2(footMid + movement.X * 0.35f, colliderPos.Y + TorsoPosition), getUpSpeed);
head.pullJoint.Enabled = true;
head.pullJoint.WorldAnchorB =
MathUtils.SmoothStep(head.SimPosition,
new Vector2(footMid + movement.X * 0.4f, colliderPos.Y + HeadPosition), getUpSpeed);
}
//moving horizontally
if (TargetMovement.X != 0.0f)
{
//progress the walking animation
walkPos -= (walkCycleSpeed / runningModifier)*0.8f;
MoveLimb(leftFoot,
colliderPos + new Vector2(
stepSize.X,
(stepSize.Y > 0.0f) ? stepSize.Y : -0.15f),
15.0f, true);
MoveLimb(rightFoot,
colliderPos + new Vector2(
-stepSize.X,
(-stepSize.Y > 0.0f) ? -stepSize.Y : -0.15f),
15.0f, true);
if (Math.Sign(stepSize.X) == Math.Sign(Dir))
{
leftFoot.body.SmoothRotate(leftLeg.body.Rotation + MathHelper.PiOver2 * Dir * 1.6f, 20.0f * runningModifier);
}
else if (Math.Sign(-stepSize.X) == Math.Sign(Dir))
{
rightFoot.body.SmoothRotate(rightLeg.body.Rotation + MathHelper.PiOver2 * Dir * 1.6f, 20 * runningModifier);
}
if (walkPosY > 0.0f)
{
GetLimb(LimbType.LeftThigh).body.ApplyTorque(-walkPosY * Dir * Math.Abs(movement.X) * -5.0f);
}
else
{
GetLimb(LimbType.RightThigh).body.ApplyTorque(walkPosY * Dir * Math.Abs(movement.X) * -5.0f);
}
//calculate the positions of hands
handPos = torso.SimPosition;
handPos.X = -walkPosX * 0.1f * runningModifier;
float lowerY = -0.6f + runningModifier/3.5f;
handPos.Y = lowerY + (float)(Math.Abs(Math.Sin(walkPos - Math.PI * 1.5f) * 0.1)) / runningModifier;
Vector2 posAdditon = new Vector2(movement.X*0.07f, 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, 0.05f, true);
//rightHand.body.ApplyLinearImpulse((handPos - rightHand.Position));
rightHand.body.SmoothRotate(0.0f, 5.0f);
var rightArm = GetLimb(LimbType.RightArm);
rightArm.body.SmoothRotate(0.0f, 20.0f);
}
if (!leftHand.Disabled)
{
//MoveLimb(leftHand, handPos, 0.05f, true);
//leftHand.body.ApplyLinearImpulse((handPos - leftHand.Position));
leftHand.body.SmoothRotate(0.0f, 5.0f);
var leftArm = GetLimb(LimbType.LeftArm);
leftArm.body.SmoothRotate(0.0f, 20.0f);
}
}
}
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.Rect.Y - currentHull.Surface > 50.0f) && !head.inWater)
{
surfaceLimiter = (ConvertUnits.ToDisplayUnits(head.SimPosition.Y)-surfaceY);
surfaceLimiter = Math.Max(1.0f, surfaceLimiter);
if (surfaceLimiter > 20.0f) return;
}
Limb torso = GetLimb(LimbType.Torso);
Limb leftHand = GetLimb(LimbType.LeftHand);
Limb rightHand = GetLimb(LimbType.RightHand);
Limb leftFoot = GetLimb(LimbType.LeftFoot);
Limb rightFoot = GetLimb(LimbType.RightFoot);
Limb leftLeg = GetLimb(LimbType.LeftLeg);
Limb rightLeg = GetLimb(LimbType.RightLeg);
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 = MathUtils.SmoothStep(movement, TargetMovement, 0.3f);
//dont try to move upwards if head is already out of water
if (surfaceLimiter > 1.0f && TargetMovement.Y > 0.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);
//float legCorrection = MathUtils.GetShortestAngle(leftLeg.Rotation, torso.body.Rotation);
//leftLeg.body.ApplyTorque(legCorrection);
//legCorrection = MathUtils.GetShortestAngle(rightLeg.Rotation, torso.body.Rotation);
//rightLeg.body.ApplyTorque(legCorrection);
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 && TargetMovement.Y>0)
{
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 = MathUtils.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.5f + movement.Y * 0.1f - ladderSimPos.Y);
MoveLimb(leftHand,
new Vector2(handPos.X,
MathUtils.Round(handPos.Y - stepHeight, stepHeight * 2.0f) + stepHeight + ladderSimPos.Y),
5.2f);
MoveLimb(rightHand,
new Vector2(handPos.X,
MathUtils.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.05f,
head.SimPosition.Y - stepHeight * 2.7f - ladderSimPos.Y);
//if (movement.Y < 0) footPos.Y += 0.05f;
MoveLimb(leftFoot,
new Vector2(footPos.X,
MathUtils.Round(footPos.Y + stepHeight, stepHeight * 2.0f) - stepHeight + ladderSimPos.Y),
15.5f, true);
MoveLimb(rightFoot,
new Vector2(footPos.X,
MathUtils.Round(footPos.Y, stepHeight * 2.0f) + ladderSimPos.Y),
15.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 * -8.0f);
rightLeg.body.ApplyTorque(Dir * -8.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.4f) * movementFactor;
torso.body.ApplyForce(climbForce * 40.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*1.5f) ||
(TargetMovement.Y > 0.0f && handPos.Y > 0.3f))
{
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 = MathUtils.VectorToAngle(new Vector2(diff.X, diff.Y * Dir)) - torso.body.Rotation * Dir;
holdAngle = MathHelper.Clamp(MathUtils.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 = limbJoints[2].WorldAnchorA;
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(MathUtils.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 = 37.0f;
//arm length
float b = 28.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 = MathUtils.VectorToAngle((transformedHoldPos + transformedHandlePos[i]) - shoulderPos)+MathHelper.PiOver2;
float armAngle = MathUtils.SolveTriangleSSS(a, b, c);
float handAngle = MathUtils.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),
MathUtils.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,
MathUtils.WrapAnglePi(l.body.Rotation * (l.DoesFlip ? -1.0f : 1.0f)));
break;
}
}
}
}
}
+129
View File
@@ -0,0 +1,129 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
namespace Subsurface
{
class Skill
{
string name;
int level;
public string Name
{
get { return name; }
}
public int Level
{
get { return level; }
}
public Skill(string name, int level)
{
this.name = name;
this.level = level;
}
}
class Job
{
private JobPrefab prefab;
private Dictionary<string, Skill> skills;
public string Name
{
get { return prefab.Name; }
}
public string Description
{
get { return prefab.Description; }
}
public JobPrefab Prefab
{
get { return prefab; }
}
public List<string> SpawnItemNames
{
get { return prefab.ItemNames; }
}
public List<Skill> Skills
{
get { return skills.Values.ToList(); }
}
//public List<float> SkillLevels
//{
// get { return skills.Values.ToList(); }
//}
public Job(JobPrefab jobPrefab)
{
prefab = jobPrefab;
skills = new Dictionary<string, Skill>();
foreach (KeyValuePair<string, Vector2> skill in prefab.Skills)
{
skills.Add(
skill.Key,
new Skill( skill.Key, (int)Rand.Range(skill.Value.X, skill.Value.Y, false)));
}
}
public Job(XElement element)
{
string name = ToolBox.GetAttributeString(element, "name", "").ToLower();
prefab = JobPrefab.List.Find(jp => jp.Name.ToLower() == name);
skills = new Dictionary<string, Skill>();
foreach (XElement subElement in element.Elements())
{
if (subElement.Name.ToString().ToLower() != "skill") continue;
string skillName = ToolBox.GetAttributeString(subElement, "name", "");
if (string.IsNullOrEmpty(name)) continue;
skills.Add(
skillName,
new Skill(skillName, ToolBox.GetAttributeInt(subElement, "level", 0)));
}
}
public static Job Random()
{
JobPrefab prefab = JobPrefab.List[Rand.Int(JobPrefab.List.Count-1, false)];
return new Job(prefab);
}
public int GetSkillLevel(string skillName)
{
Skill skill = null;
skills.TryGetValue(skillName, out skill);
return (skill==null) ? 0 : skill.Level;
}
public virtual XElement Save(XElement parentElement)
{
XElement jobElement = new XElement("job");
jobElement.Add(new XAttribute("name", Name));
foreach (KeyValuePair<string, Skill> skill in skills)
{
jobElement.Add(new XElement("skill", new XAttribute("name", skill.Value.Name), new XAttribute("level", skill.Value.Level)));
}
parentElement.Add(jobElement);
return jobElement;
}
}
}
@@ -0,0 +1,129 @@
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Globalization;
using System.Xml.Linq;
namespace Subsurface
{
class JobPrefab
{
public static List<JobPrefab> List;
string name;
string description;
//how many crew members can have the job (only one captain etc)
private int maxNumber;
//how many crew members are REQUIRED to have a job
//(i.e. if one captain is required, one captain is chosen even if all the players have set captain to lowest preference)
private int minNumber;
//if set to true, a client that has chosen this as their preferred job will get it no matter what
public bool AllowAlways
{
get;
private set;
}
//names of the items the character spawns with
public List<string> ItemNames;
public Dictionary<string, Vector2> Skills;
public string Name
{
get { return name; }
}
public string Description
{
get { return description; }
}
public int MaxNumber
{
get { return maxNumber; }
}
public int MinNumber
{
get { return minNumber; }
}
public JobPrefab(XElement element)
{
name = element.Name.ToString();
description = ToolBox.GetAttributeString(element, "description", "");
minNumber = ToolBox.GetAttributeInt(element, "minnumber", 0);
maxNumber = ToolBox.GetAttributeInt(element, "maxnumber", 10);
AllowAlways = ToolBox.GetAttributeBool(element, "allowalways", false);
ItemNames = new List<string>();
Skills = new Dictionary<string, Vector2>();
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLower())
{
case "item":
string itemName = ToolBox.GetAttributeString(subElement, "name", "");
if (!string.IsNullOrEmpty(itemName)) ItemNames.Add(itemName);
break;
case "skills":
LoadSkills(subElement);
break;
}
}
}
public static JobPrefab Random()
{
return List[Rand.Int(List.Count)];
}
private void LoadSkills(XElement element)
{
foreach (XElement subElement in element.Elements())
{
string skillName = ToolBox.GetAttributeString(subElement, "name", "");
if (string.IsNullOrEmpty(skillName) || Skills.ContainsKey(skillName)) continue;
var levelString = ToolBox.GetAttributeString(subElement, "level", "");
if (levelString.Contains(","))
{
Skills.Add(skillName, ToolBox.ParseToVector2(levelString, false));
}
else
{
float skillLevel = float.Parse(levelString, CultureInfo.InvariantCulture);
Skills.Add(skillName, new Vector2(skillLevel, skillLevel));
}
}
}
public static void LoadAll(List<string> filePaths)
{
List = new List<JobPrefab>();
foreach (string filePath in filePaths)
{
XDocument doc = ToolBox.TryLoadXml(filePath);
if (doc == null) return;
foreach (XElement element in doc.Root.Elements())
{
JobPrefab job = new JobPrefab(element);
List.Add(job);
}
}
}
}
}
+458
View File
@@ -0,0 +1,458 @@
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 readonly float maxHealth;
//private float damage;
//private float bleeding;
public readonly float impactTolerance;
private readonly Vector2 armorSector;
private readonly float armorValue;
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 Position
{
get { return ConvertUnits.ToDisplayUnits(body.Position); }
}
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 MaxHealth
//{
// get { return maxHealth; }
//}
//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", 8.0f);
body.UserData = this;
refJointIndex = -1;
if (element.Attribute("type") != null)
{
try
{
type = (LimbType)Enum.Parse(typeof(LimbType), element.Attribute("type").Value, true);
}
catch
{
type = LimbType.None;
DebugConsole.ThrowError("Error in "+element+"! ''"+element.Attribute("type").Value+"'' is not a valid limb type");
}
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);
armorSector = ToolBox.GetAttributeVector2(element, "armorsector", Vector2.Zero);
armorSector.X = MathHelper.ToRadians(armorSector.X);
armorSector.Y = MathHelper.ToRadians(armorSector.Y);
armorValue = Math.Max(ToolBox.GetAttributeFloat(element, "armor", 1.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" : "");
spritePath = spritePath.Replace("[HEADID]", character.Info.HeadSpriteId.ToString());
}
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 AttackResult AddDamage(Vector2 position, DamageType damageType, float amount, float bleedingAmount, bool playSound)
{
DamageSoundType damageSoundType = (damageType == DamageType.Blunt) ? DamageSoundType.LimbBlunt : DamageSoundType.LimbSlash;
bool hitArmor = false;
if (armorSector != Vector2.Zero)
{
float rot = body.Rotation;
if (Dir == -1) rot -= MathHelper.Pi;
Vector2 armorLimits = new Vector2(rot-armorSector.X*Dir, rot-armorSector.Y*Dir);
float mid = (armorLimits.X + armorLimits.Y) / 2.0f;
float angleDiff = MathUtils.GetShortestAngle(MathUtils.VectorToAngle(position - SimPosition), mid);
if (Math.Abs(angleDiff) < (armorSector.Y - armorSector.X) / 2.0f)
{
hitArmor = true;
damageSoundType = DamageSoundType.LimbArmor;
amount /= armorValue;
bleedingAmount /= armorValue;
}
}
if (playSound)
{
AmbientSoundManager.PlayDamageSound(damageSoundType, amount, position);
}
//Bleeding += bleedingAmount;
//Damage += amount;
float bloodAmount = hitArmor ? 0 : (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 = SimPosition - position;
if (particleVel != Vector2.Zero) particleVel = Vector2.Normalize(particleVel);
Game1.ParticleManager.CreateParticle("blood",
SimPosition,
particleVel * Rand.Range(1.0f, 3.0f));
}
for (int i = 0; i < bloodAmount / 2; i++)
{
Game1.ParticleManager.CreateParticle("waterblood", SimPosition, Vector2.Zero);
}
return new AttackResult(amount, bleedingAmount, hitArmor);
}
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 (MathUtils.RandomFloat(0.0f, 1000.0f) < Bleeding)
//{
// Game1.particleManager.CreateParticle(
// !inWater ? "blood" : "waterblood",
// SimPosition, Vector2.Zero);
//}
}
public void Draw(SpriteBatch spriteBatch)
{
Color color = Color.White;// 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 (!Game1.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();
}
}
}
+673
View File
@@ -0,0 +1,673 @@
using System;
using System.Collections.Generic;
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>();
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 float Mass
{
get;
private set;
}
public Vector2 TargetMovement
{
get
{
return (correctionMovement == Vector2.Zero) ? targetMovement : correctionMovement;
}
set
{
if (float.IsNaN(value.X) || float.IsNaN(value.Y))
{
targetMovement = Vector2.Zero;
return;
}
targetMovement.X = MathHelper.Clamp(value.X, -3.0f, 3.0f);
targetMovement.Y = MathHelper.Clamp(value.Y, -3.0f, 3.0f);
}
}
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;
Mass += limb.Mass;
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.Position.Y < structure.Rect.Y) return false;
}
else if (structure.StairDirection!=Direction.None)
{
if (inWater || !(targetMovement.Y>Math.Abs(targetMovement.X/2.0f)) && lowestLimb.Position.Y < structure.Rect.Y - structure.Rect.Height + 50.0f)
{
stairs = null;
return false;
}
if (targetMovement.Y >= 0.0f && lowestLimb.SimPosition.Y > ConvertUnits.ToSimUnits(structure.Rect.Y - Submarine.GridSize.Y * 8.0f))
{
stairs = null;
return false;
}
Limb limb = f1.Body.UserData as Limb;
if (limb != null && (limb.type == LimbType.LeftFoot || limb.type == LimbType.RightFoot))
{
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;
Vector2 avgVelocity = Vector2.Zero;
foreach (Limb limb in limbs)
{
avgVelocity += limb.LinearVelocity;
}
avgVelocity = avgVelocity / limbs.Count();
float impact = Vector2.Dot((f1.Body.LinearVelocity + avgVelocity) / 2.0f, -normal);
if (Game1.Server != null) impact = impact / 2.0f;
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), impact * 100.0f, l.body.FarseerBody);
if (impact > l.impactTolerance)
{
character.Health -= (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);
}
if (!Game1.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);
GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)-pos.Y, 5, 5), Color.White, true);
pos = ConvertUnits.ToDisplayUnits(joint.WorldAnchorB);
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 (currentHull.Volume>currentHull.FullVolume*0.95f || ConvertUnits.ToSimUnits(currentHull.Surface)-floorY> HeadPosition*0.95f)
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 && Submarine.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
Subsurface.Particles.Particle splash = Game1.ParticleManager.CreateParticle("watersplash",
new Vector2(limb.SimPosition.X, ConvertUnits.ToSimUnits(limbHull.Surface)),
new Vector2(0.0f, Math.Abs(-limb.LinearVelocity.Y * 0.1f)),
0.0f);
if (splash != null) splash.yLimits = ConvertUnits.ToSimUnits(
new Vector2(
limbHull.Rect.Y,
limbHull.Rect.Y - limbHull.Rect.Height));
Game1.ParticleManager.CreateParticle("bubbles",
new Vector2(limb.SimPosition.X, ConvertUnits.ToSimUnits(limbHull.Surface)),
limb.LinearVelocity*0.001f,
0.0f);
//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)/1000.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,248 @@
using FarseerPhysics;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Subsurface
{
class StatusEffect
{
[Flags]
public enum TargetType
{
This = 1, Parent = 2, Character = 4, Contained = 8, Nearby = 16, UseTarget=32
}
private TargetType targetTypes;
private string[] targetNames;
public string[] propertyNames;
private object[] propertyEffects;
private bool disableDeltaTime;
private string[] onContainingNames;
public readonly ActionType type;
private Explosion explosion;
private Sound sound;
public TargetType Targets
{
get { return targetTypes; }
}
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>();
disableDeltaTime = ToolBox.GetAttributeBool(element, "disabledeltatime", false);
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)
{
targetTypes |= (TargetType)Enum.Parse(typeof(TargetType), 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)
//{
// if (this.type == type) Apply(deltaTime, character, item);
//}
public virtual void Apply(ActionType type, float deltaTime, Entity entity, IPropertyObject target)
{
if (targetNames != null && !targetNames.Contains(target.Name)) return;
List<IPropertyObject> targets = new List<IPropertyObject>();
targets.Add(target);
if (this.type == type) Apply(deltaTime, entity, targets);
}
public virtual void Apply(ActionType type, float deltaTime, Entity entity, List<IPropertyObject> targets)
{
if (this.type == type) Apply(deltaTime, entity, targets);
}
protected virtual void Apply(float deltaTime, Entity entity, List<IPropertyObject> targets)
{
if (explosion != null) explosion.Explode(entity.SimPosition);
if (sound != null) sound.Play(1.0f, 1000.0f, ConvertUnits.ToDisplayUnits(entity.SimPosition));
for (int i = 0; i < propertyNames.Count(); i++)
{
ObjectProperty property;
foreach (IPropertyObject target in targets)
{
if (targetNames!=null && !targetNames.Contains(target.Name)) continue;
if (!target.ObjectProperties.TryGetValue(propertyNames[i], out property)) continue;
ApplyToProperty(property, propertyEffects[i], deltaTime);
}
}
}
//protected virtual void Apply(float deltaTime, Character character, Item item)
//{
// 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)
{
if (disableDeltaTime) deltaTime = 1.0f;
Type type = value.GetType();
if (type == typeof(float))
{
property.TrySetValue((float)property.GetValue() + (float)value * deltaTime);
}
else 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);
}
}
}
}