First commit

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