Files
LuaCsForBarotraumaEP/Subsurface/Source/Characters/Character.cs
Regalis c314b37029 Some classes for syncing entity state changes. Similar to the NetworkEvents in the old netcode, but the logic is split into separate classes which prevent the server from reading updates for entities that aren't IClientSerializable.
todo: add NetEntityEventManagers to server & client, some logic to prevent sending events that don't need to be sent (e.g. duplicate event state updates)
2016-11-12 20:56:06 +02:00

2144 lines
71 KiB
C#

using FarseerPhysics;
using FarseerPhysics.Dynamics;
using FarseerPhysics.Dynamics.Joints;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Barotrauma.Networking;
using Barotrauma.Particles;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
class Character : Entity, IDamageable, IPropertyObject, IClientSerializable, IServerSerializable
{
public static List<Character> CharacterList = new List<Character>();
public static bool DisableControls;
private UInt32 netStateID;
public UInt32 NetStateID
{
get { return netStateID; }
}
private byte dequeuedInput = 0;
private byte prevDequeuedInput = 0;
private int isStillCountdown = 5;
private List<byte> memInput = new List<byte>();
private List<Vector2> memMousePos = new List<Vector2>();
private List<PosInfo> memPos = new List<PosInfo>();
private List<PosInfo> memLocalPos = new List<PosInfo>();
//the Character that the player is currently controlling
private static Character controlled;
public static Character Controlled
{
get { return controlled; }
set
{
if (controlled == value) return;
controlled = value;
CharacterHUD.Reset();
}
}
public List<Item> SpawnItems = new List<Item>();
private bool enabled;
public bool Enabled
{
get
{
return enabled;
}
set
{
enabled = value;
foreach (Limb limb in AnimController.Limbs)
{
limb.body.Enabled = enabled;
}
}
}
public Hull PreviousHull = null;
public Hull CurrentHull = null;
public readonly bool IsRemotePlayer;
private CharacterInventory inventory;
private UInt32 LastNetworkUpdateID = 0;
//public int LargeUpdateTimer;
public readonly Dictionary<string, ObjectProperty> Properties;
public Dictionary<string, ObjectProperty> ObjectProperties
{
get { return Properties; }
}
protected Key[] keys;
private Item selectedConstruction;
private Item[] selectedItems;
public byte TeamID = 0;
public AnimController AnimController;
private Vector2 cursorPosition;
protected bool needsAir;
protected float oxygen, oxygenAvailable;
protected float drowningTime;
protected float health;
protected float minHealth, maxHealth;
protected Item closestItem;
private Character closestCharacter, selectedCharacter;
private Dictionary<object, HUDProgressBar> hudProgressBars;
protected bool isDead;
private CauseOfDeath lastAttackCauseOfDeath;
private CauseOfDeath causeOfDeath;
public readonly bool IsHumanoid;
//the name of the species (e.q. human)
public readonly string SpeciesName;
protected float soundTimer;
protected float soundInterval;
private float bleeding;
private Sound[] sounds;
private float[] soundRange;
//which AIstate each sound is for
private AIController.AiState[] soundStates;
private float attackCoolDown;
public Entity ViewTarget
{
get;
private set;
}
private CharacterInfo info;
public CharacterInfo Info
{
get
{
return info;
}
set
{
if (info != null && info != value) info.Remove();
info = value;
if (info != null) info.Character = this;
}
}
public string Name
{
get
{
return info != null && !string.IsNullOrWhiteSpace(info.Name) ? info.Name : SpeciesName;
}
}
public string ConfigPath
{
get;
private set;
}
public float Mass
{
get { return AnimController.Mass; }
}
public CharacterInventory Inventory
{
get { return inventory; }
}
private Color speechBubbleColor;
private float speechBubbleTimer;
private float lockHandsTimer;
public bool LockHands
{
get
{
return lockHandsTimer > 0.0f;
}
set
{
lockHandsTimer = MathHelper.Clamp(lockHandsTimer + (value ? 1.0f : -0.5f), 0.0f, 10.0f);
}
}
public Vector2 CursorPosition
{
get { return cursorPosition; }
set
{
if (!MathUtils.IsValid(value)) return;
cursorPosition = value;
}
}
public Vector2 CursorWorldPosition
{
get { return Submarine == null ? cursorPosition : cursorPosition + Submarine.Position; }
}
public List<PosInfo> MemPos
{
get { return memPos; }
}
public List<PosInfo> MemLocalPos
{
get { return memLocalPos; }
}
public Character ClosestCharacter
{
get { return closestCharacter; }
}
public Character SelectedCharacter
{
get { return selectedCharacter; }
}
private float lowPassMultiplier;
public float LowPassMultiplier
{
get { return lowPassMultiplier; }
set { lowPassMultiplier = MathHelper.Clamp(value, 0.0f, 1.0f); }
}
private float obstructVisionAmount;
public bool ObstructVision
{
get
{
return obstructVisionAmount > 0.5f;
}
set
{
obstructVisionAmount = 1.0f;
}
}
public float SoundRange
{
get { return aiTarget.SoundRange; }
}
public float SightRange
{
get { return aiTarget.SightRange; }
}
private float pressureProtection;
public float PressureProtection
{
get { return pressureProtection; }
set
{
pressureProtection = MathHelper.Clamp(value, 0.0f, 100.0f);
}
}
public bool IsUnconscious
{
get { return (needsAir && oxygen <= 0.0f) || health <= 0.0f; }
}
public bool NeedsAir
{
get { return needsAir; }
set { needsAir = value; }
}
public float Oxygen
{
get { return oxygen; }
set
{
if (!MathUtils.IsValid(value)) return;
oxygen = MathHelper.Clamp(value, -100.0f, 100.0f);
if (oxygen == -100.0f) Kill(AnimController.InWater ? CauseOfDeath.Drowning : CauseOfDeath.Suffocation);
}
}
public float OxygenAvailable
{
get { return oxygenAvailable; }
set { oxygenAvailable = MathHelper.Clamp(value, 0.0f, 100.0f); }
}
public float Stun
{
get { return AnimController.StunTimer; }
set { StartStun(value); }
}
public float Health
{
get { return health; }
set
{
if (!MathUtils.IsValid(value)) return;
health = MathHelper.Clamp(value, minHealth, maxHealth);
}
}
public float MaxHealth
{
get { return maxHealth; }
}
public float Bleeding
{
get { return bleeding; }
set
{
if (!MathUtils.IsValid(value)) return;
bleeding = Math.Max(value, 0.0f);
}
}
public Dictionary<object, HUDProgressBar> HUDProgressBars
{
get { return hudProgressBars; }
}
public HuskInfection huskInfection;
public float HuskInfectionState
{
get
{
return huskInfection == null ? 0.0f : huskInfection.IncubationTimer;
}
set
{
if (ConfigPath != humanConfigFile) return;
if (value <= 0.0f)
{
if (huskInfection != null && huskInfection.State == HuskInfection.InfectionState.Active) return;
huskInfection = null;
}
else
{
if (huskInfection == null) huskInfection = new HuskInfection(this);
huskInfection.IncubationTimer = MathHelper.Clamp(value, 0.0f, 1.0f);
}
}
}
public bool CanSpeak
{
get
{
return !IsUnconscious && Stun <= 0.0f && (huskInfection == null || huskInfection.CanSpeak);
}
}
public bool DoesBleed
{
get;
private set;
}
public float BleedingDecreaseSpeed
{
get;
private set;
}
public float PressureTimer
{
get;
private set;
}
public float DisableImpactDamageTimer
{
get;
set;
}
public float SpeedMultiplier
{
get;
set;
}
public Item[] SelectedItems
{
get { return selectedItems; }
}
public Item SelectedConstruction
{
get { return selectedConstruction; }
set { selectedConstruction = value; }
}
public Item ClosestItem
{
get { return closestItem; }
}
public virtual AIController AIController
{
get { return null; }
}
public bool IsDead
{
get { return isDead; }
}
public CauseOfDeath CauseOfDeath
{
get { return causeOfDeath; }
}
public bool CanBeSelected
{
get
{
return isDead || Stun > 0.0f || LockHands;
}
}
public override Vector2 SimPosition
{
get { return AnimController.Collider.SimPosition; }
}
public override Vector2 Position
{
get { return ConvertUnits.ToDisplayUnits(SimPosition); }
}
public override Vector2 DrawPosition
{
get { return AnimController.MainLimb.body.DrawPosition; }
}
public delegate void OnDeathHandler(Character character, CauseOfDeath causeOfDeath);
public OnDeathHandler OnDeath;
public static Character Create(CharacterInfo characterInfo, Vector2 position, bool isRemotePlayer = false, bool hasAi=true)
{
return Create(characterInfo.File, position, characterInfo, isRemotePlayer, hasAi);
}
public static Character Create(string file, Vector2 position, CharacterInfo characterInfo = null, bool isRemotePlayer = false, bool hasAi=true)
{
#if LINUX
if (!System.IO.File.Exists(file))
{
//if the file was not found, attempt to convert the name of the folder to upper case
var splitPath = file.Split('/');
if (splitPath.Length > 2)
{
splitPath[splitPath.Length-2] =
splitPath[splitPath.Length-2].First().ToString().ToUpper() + splitPath[splitPath.Length-2].Substring(1);
file = string.Join("/", splitPath);
}
if (!System.IO.File.Exists(file))
{
DebugConsole.ThrowError("Spawning a character failed - file \""+file+"\" not found!");
return null;
}
}
#else
if (!System.IO.File.Exists(file))
{
DebugConsole.ThrowError("Spawning a character failed - file \""+file+"\" not found!");
return null;
}
#endif
if (file != humanConfigFile)
{
var enemyCharacter = new AICharacter(file, position, characterInfo, isRemotePlayer);
var ai = new EnemyAIController(enemyCharacter, file);
enemyCharacter.SetAI(ai);
enemyCharacter.minHealth = 0.0f;
return enemyCharacter;
}
else if (hasAi)
{
var aiCharacter = new AICharacter(file, position, characterInfo, isRemotePlayer);
var ai = new HumanAIController(aiCharacter);
aiCharacter.SetAI(ai);
aiCharacter.minHealth = -100.0f;
return aiCharacter;
}
var character = new Character(file, position, characterInfo, isRemotePlayer);
character.minHealth = -100.0f;
return character;
}
protected Character(string file, Vector2 position, CharacterInfo characterInfo = null, bool isRemotePlayer = false)
: base(null)
{
keys = new Key[Enum.GetNames(typeof(InputType)).Length];
for (int i = 0; i < Enum.GetNames(typeof(InputType)).Length; i++)
{
keys[i] = new Key(GameMain.Config.KeyBind((InputType)i));
}
ConfigPath = file;
selectedItems = new Item[2];
hudProgressBars = new Dictionary<object, HUDProgressBar>();
IsRemotePlayer = isRemotePlayer;
oxygen = 100.0f;
oxygenAvailable = 100.0f;
aiTarget = new AITarget(this);
lowPassMultiplier = 1.0f;
Properties = ObjectProperty.GetProperties(this);
Info = characterInfo;
if (file == humanConfigFile && characterInfo == null)
{
Info = new CharacterInfo(file);
}
XDocument doc = ToolBox.TryLoadXml(file);
if (doc == null || doc.Root == null) return;
SpeciesName = ToolBox.GetAttributeString(doc.Root, "name", "Unknown");
IsHumanoid = ToolBox.GetAttributeBool(doc.Root, "humanoid", false);
if (IsHumanoid)
{
AnimController = new HumanoidAnimController(this, doc.Root.Element("ragdoll"));
AnimController.TargetDir = Direction.Right;
inventory = new CharacterInventory(16, this);
}
else
{
AnimController = new FishAnimController(this, doc.Root.Element("ragdoll"));
PressureProtection = 100.0f;
}
AnimController.SetPosition(ConvertUnits.ToSimUnits(position));
maxHealth = ToolBox.GetAttributeFloat(doc.Root, "health", 100.0f);
health = maxHealth;
DoesBleed = ToolBox.GetAttributeBool(doc.Root, "doesbleed", true);
BleedingDecreaseSpeed = ToolBox.GetAttributeFloat(doc.Root, "bleedingdecreasespeed", 0.05f);
needsAir = ToolBox.GetAttributeBool(doc.Root, "needsair", false);
drowningTime = ToolBox.GetAttributeFloat(doc.Root, "drowningtime", 10.0f);
soundInterval = ToolBox.GetAttributeFloat(doc.Root, "soundinterval", 10.0f);
var soundElements = doc.Root.Elements("sound").ToList();
if (soundElements.Any())
{
sounds = new Sound[soundElements.Count];
soundStates = new AIController.AiState[soundElements.Count];
soundRange = new float[soundElements.Count];
int i = 0;
foreach (XElement soundElement in soundElements)
{
sounds[i] = Sound.Load(soundElement.Attribute("file").Value);
soundRange[i] = ToolBox.GetAttributeFloat(soundElement, "range", 1000.0f);
if (soundElement.Attribute("state") == null)
{
soundStates[i] = AIController.AiState.None;
}
else
{
soundStates[i] = (AIController.AiState)Enum.Parse(
typeof(AIController.AiState), soundElement.Attribute("state").Value, true);
}
i++;
}
}
if (file == humanConfigFile)
{
if (Info.PickedItemIDs.Any())
{
for (ushort i = 0; i < Info.PickedItemIDs.Count; i++ )
{
if (Info.PickedItemIDs[i] == 0) continue;
Item item = FindEntityByID(Info.PickedItemIDs[i]) as Item;
System.Diagnostics.Debug.Assert(item != null);
if (item == null) continue;
item.Pick(this, true, true, true);
inventory.TryPutItem(item, i, false);
}
}
}
AnimController.FindHull(null);
if (AnimController.CurrentHull != null) Submarine = AnimController.CurrentHull.Submarine;
CharacterList.Add(this);
Enabled = true;
}
private static string humanConfigFile;
public static string HumanConfigFile
{
get
{
if (string.IsNullOrEmpty(humanConfigFile))
{
var characterFiles = GameMain.SelectedPackage.GetFilesOfType(ContentType.Character);
humanConfigFile = characterFiles.Find(c => c.EndsWith("human.xml"));
if (humanConfigFile == null)
{
DebugConsole.ThrowError("Couldn't find a config file for humans from the selected content package!");
DebugConsole.ThrowError("(The config file must end with \"human.xml\")");
return "";
}
}
return humanConfigFile;
}
}
public bool IsKeyHit(InputType inputType)
{
if (GameMain.Server != null && Character.Controlled != this)
{
switch (inputType)
{
case InputType.Left:
return ((dequeuedInput & 0x1) > 0) && !((prevDequeuedInput & 0x1) > 0);
//break;
case InputType.Right:
return ((dequeuedInput & 0x2) > 0) && !((prevDequeuedInput & 0x2) > 0);
//break;
case InputType.Up:
return ((dequeuedInput & 0x4) > 0) && !((prevDequeuedInput & 0x4) > 0);
//break;
case InputType.Down:
return ((dequeuedInput & 0x8) > 0) && !((prevDequeuedInput & 0x8) > 0);
//break;
case InputType.Run:
return ((dequeuedInput & 0x20) > 0) && !((prevDequeuedInput & 0x20) > 0);
//break;
case InputType.Select:
return ((dequeuedInput & 0x40) > 0) && !((prevDequeuedInput & 0x40) > 0);
//break;
default:
return false;
//break;
}
}
return keys[(int)inputType].Hit;
}
public bool IsKeyDown(InputType inputType)
{
if (GameMain.Server!=null && Character.Controlled!=this)
{
bool retVal = false;
switch (inputType)
{
case InputType.Left:
retVal = (dequeuedInput & 0x1) > 0;
break;
case InputType.Right:
retVal = (dequeuedInput & 0x2) > 0;
break;
case InputType.Up:
retVal = (dequeuedInput & 0x4) > 0;
break;
case InputType.Down:
retVal = (dequeuedInput & 0x8) > 0;
break;
case InputType.Run:
retVal = (dequeuedInput & 0x20) > 0;
break;
case InputType.Select:
retVal = (dequeuedInput & 0x40) > 0;
break;
}
return retVal;
}
return keys[(int)inputType].Held;
}
public void SetInput(InputType inputType, bool hit, bool held)
{
keys[(int)inputType].Hit = hit;
keys[(int)inputType].Held = held;
}
public void ClearInput(InputType inputType)
{
keys[(int)inputType].Hit = false;
keys[(int)inputType].Held = false;
}
public void ClearInputs()
{
foreach (Key key in keys)
{
key.Hit = false;
key.Held = false;
}
}
public override string ToString()
{
return (info != null && !string.IsNullOrWhiteSpace(info.Name)) ? info.Name : SpeciesName;
}
public void GiveJobItems(WayPoint spawnPoint)
{
if (info == null || info.Job == null) return;
info.Job.GiveJobItems(this, spawnPoint);
}
public int GetSkillLevel(string skillName)
{
return (Info==null || Info.Job==null) ? 0 : Info.Job.GetSkillLevel(skillName);
}
float findClosestTimer;
public Vector2 GetTargetMovement()
{
Vector2 targetMovement = Vector2.Zero;
if (IsKeyDown(InputType.Left)) targetMovement.X -= 1.0f;
if (IsKeyDown(InputType.Right)) targetMovement.X += 1.0f;
if (IsKeyDown(InputType.Up)) targetMovement.Y += 1.0f;
if (IsKeyDown(InputType.Down)) targetMovement.Y -= 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 (IsKeyDown(InputType.Run))
{
//can't run if
// - not a humanoid
// - dragging someone
// - crouching
// - moving backwards
if (AnimController is HumanoidAnimController &&
selectedCharacter == null &&
!((HumanoidAnimController)AnimController).Crouching &&
Math.Sign(targetMovement.X) != -Math.Sign(AnimController.Dir))
{
targetMovement *= 3.0f;
}
}
targetMovement *= SpeedMultiplier;
SpeedMultiplier = 1.0f;
return targetMovement;
}
public void Control(float deltaTime, Camera cam)
{
if (isDead || AnimController.StunTimer>0.0f) return;
Vector2 targetMovement = GetTargetMovement();
AnimController.TargetMovement = targetMovement;
AnimController.IgnorePlatforms = AnimController.TargetMovement.Y < 0.0f;
if (AnimController is HumanoidAnimController)
{
((HumanoidAnimController) AnimController).Crouching = IsKeyDown(InputType.Crouch);
}
if (AnimController.onGround &&
!AnimController.InWater &&
AnimController.Anim != AnimController.Animation.UsingConstruction &&
AnimController.Anim != AnimController.Animation.CPR)
{
Limb head = AnimController.GetLimb(LimbType.Head);
if (cursorPosition.X < head.Position.X - 10.0f)
{
AnimController.TargetDir = Direction.Left;
}
else if (cursorPosition.X > head.Position.X + 10.0f)
{
AnimController.TargetDir = Direction.Right;
}
}
if (GameMain.Server != null && Character.Controlled != this)
{
if ((dequeuedInput & 0x10) > 0)
{
AnimController.TargetDir = Direction.Left;
}
else
{
AnimController.TargetDir = Direction.Right;
}
}
else if (GameMain.Client != null && Character.controlled != this)
{
if (memPos.Count > 0)
{
AnimController.TargetDir = memPos[0].Direction;
}
}
if (attackCoolDown >0.0f)
{
attackCoolDown -= deltaTime;
}
else if (IsKeyDown(InputType.Attack))
{
var attackLimb = AnimController.Limbs.FirstOrDefault(l => l.attack != null);
if (attackLimb != null)
{
Vector2 attackPos =
attackLimb.SimPosition + Vector2.Normalize(cursorPosition - attackLimb.Position) * ConvertUnits.ToSimUnits(attackLimb.attack.Range);
var body = Submarine.PickBody(
attackLimb.SimPosition,
attackPos,
AnimController.Limbs.Select(l => l.body.FarseerBody).ToList(),
Physics.CollisionCharacter | Physics.CollisionWall);
IDamageable attackTarget = null;
if (body != null)
{
attackPos = Submarine.LastPickedPosition;
if (body.UserData is Submarine)
{
var sub = ((Submarine)body.UserData);
body = Submarine.PickBody(
attackLimb.SimPosition - ((Submarine)body.UserData).SimPosition,
attackPos - ((Submarine)body.UserData).SimPosition,
AnimController.Limbs.Select(l => l.body.FarseerBody).ToList(),
Physics.CollisionWall);
if (body != null)
{
attackPos = Submarine.LastPickedPosition + sub.SimPosition;
attackTarget = body.UserData as IDamageable;
}
}
else
{
if (body.UserData is IDamageable)
{
attackTarget = (IDamageable)body.UserData;
}
else if (body.UserData is Limb)
{
attackTarget = ((Limb)body.UserData).character;
}
}
}
attackLimb.UpdateAttack(deltaTime, attackPos, attackTarget);
if (attackLimb.AttackTimer > attackLimb.attack.Duration)
{
attackLimb.AttackTimer = 0.0f;
attackCoolDown = 1.0f;
}
}
}
for (int i = 0; i < selectedItems.Length; i++ )
{
if (selectedItems[i] == null) continue;
if (i == 1 && selectedItems[0] == selectedItems[1]) continue;
if (IsKeyDown(InputType.Use)) selectedItems[i].Use(deltaTime, this);
if (IsKeyDown(InputType.Aim) && selectedItems[i] != null) selectedItems[i].SecondaryUse(deltaTime, this);
}
if (selectedConstruction != null)
{
if (IsKeyDown(InputType.Use)) selectedConstruction.Use(deltaTime, this);
if (selectedConstruction != null && IsKeyDown(InputType.Aim)) selectedConstruction.SecondaryUse(deltaTime, this);
}
if (selectedCharacter!=null)
{
if (Vector2.Distance(selectedCharacter.WorldPosition, WorldPosition) > 300.0f || !selectedCharacter.CanBeSelected)
{
DeselectCharacter();
}
}
if (IsRemotePlayer)
{
foreach (Key key in keys)
{
key.ResetHit();
}
}
}
public bool HasEquippedItem(Item item)
{
return !inventory.IsInLimbSlot(item, InvSlotType.Any);
}
public bool HasSelectedItem(Item item)
{
return selectedItems.Contains(item);
}
public bool TrySelectItem(Item item)
{
bool rightHand = inventory.IsInLimbSlot(item, InvSlotType.RightHand);
bool leftHand = inventory.IsInLimbSlot(item, InvSlotType.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 bool CanAccessInventory(Inventory inventory)
{
if (inventory.Owner is Character && inventory.Owner != this)
{
var owner = (Character)inventory.Owner;
return owner.isDead || owner.IsUnconscious || owner.Stun > 0.0f || owner.LockHands;
}
if (inventory.Owner is Item)
{
var owner = (Item)inventory.Owner;
if (!CanAccessItem(owner))
{
return false;
}
}
return true;
}
public bool CanAccessItem(Item item)
{
if (item.ParentInventory != null)
{
return CanAccessInventory(item.ParentInventory);
}
float maxDist = item.PickDistance * 1.2f;
if (maxDist <= 0.01f)
{
maxDist = 150.0f;
}
if (Vector2.Distance(WorldPosition, item.WorldPosition) < maxDist ||
item.IsInsideTrigger(WorldPosition))
{
return true;
}
return item.GetComponent<Items.Components.Ladder>() != null;
}
private Item FindClosestItem(Vector2 mouseSimPos, out float distance)
{
distance = 0.0f;
Limb torso = AnimController.GetLimb(LimbType.Torso);
if (torso == null) return null;
Vector2 pos = (torso.body.TargetPosition != Vector2.Zero) ? torso.body.TargetPosition : torso.SimPosition;
Vector2 pickPos = mouseSimPos;
if (Submarine != null)
{
pos += Submarine.SimPosition;
pickPos += Submarine.SimPosition;
}
if (selectedConstruction != null) pickPos = ConvertUnits.ToSimUnits(selectedConstruction.WorldPosition);
return Item.FindPickable(pos, pickPos, AnimController.CurrentHull, selectedItems, out distance);
}
private Character FindClosestCharacter(Vector2 mouseSimPos, float maxDist = 150.0f)
{
Character closestCharacter = null;
float closestDist = 0.0f;
maxDist = ConvertUnits.ToSimUnits(maxDist);
foreach (Character c in CharacterList)
{
if (c == this || !c.enabled) continue;
if (Vector2.Distance(SimPosition, c.SimPosition) > maxDist) continue;
float dist = Vector2.Distance(mouseSimPos, c.SimPosition);
if (dist < maxDist && (closestCharacter==null || dist<closestDist))
{
closestCharacter = c;
closestDist = dist;
continue;
}
}
return closestCharacter;
}
private void SelectCharacter(Character character)
{
if (character == null) return;
selectedCharacter = character;
}
private void DeselectCharacter()
{
if (selectedCharacter == null) return;
foreach (Limb limb in selectedCharacter.AnimController.Limbs)
{
if (limb.pullJoint != null) limb.pullJoint.Enabled = false;
}
selectedCharacter = null;
}
/// <summary>
/// Control the Character according to player input
/// </summary>
public void ControlLocalPlayer(float deltaTime, Camera cam, bool moveCam = true)
{
if (!DisableControls)
{
for (int i = 0; i < keys.Length; i++ )
{
keys[i].SetState();
}
}
else
{
foreach (Key key in keys)
{
if (key == null) continue;
key.Reset();
}
}
if (moveCam && needsAir)
{
if (pressureProtection < 80.0f &&
(AnimController.CurrentHull == null || AnimController.CurrentHull.LethalPressure > 50.0f))
{
float pressure = AnimController.CurrentHull == null ? 100.0f : AnimController.CurrentHull.LethalPressure;
cam.Zoom = MathHelper.Lerp(cam.Zoom,
(pressure / 50.0f) * Rand.Range(1.0f, 1.05f),
(pressure - 50.0f) / 50.0f);
}
cam.OffsetAmount = MathHelper.Lerp(cam.OffsetAmount, 250.0f, 0.05f);
}
cursorPosition = cam.ScreenToWorld(PlayerInput.MousePosition);
if (AnimController.CurrentHull != null && AnimController.CurrentHull.Submarine != null)
{
cursorPosition -= AnimController.CurrentHull.Submarine.Position;
}
Vector2 mouseSimPos = ConvertUnits.ToSimUnits(cursorPosition);
if (Lights.LightManager.ViewTarget == this && Vector2.Distance(AnimController.Limbs[0].SimPosition, mouseSimPos) > 1.0f)
{
Body body = Submarine.PickBody(AnimController.Limbs[0].SimPosition, mouseSimPos);
Structure structure = null;
if (body != null) structure = body.UserData as Structure;
if (structure != null)
{
if (!structure.CastShadow && moveCam)
{
cam.OffsetAmount = MathHelper.Lerp(cam.OffsetAmount, 500.0f, 0.05f);
}
}
}
if (!LockHands)
{
//find the closest item if selectkey has been hit, or if the Character is being
//controlled by the player (in order to highlight it)
if (findClosestTimer <= 0.0f || Screen.Selected == GameMain.EditMapScreen)
{
closestCharacter = FindClosestCharacter(mouseSimPos);
if (closestCharacter != null && closestCharacter.info==null)
{
closestCharacter = null;
}
float closestItemDist = 0.0f;
closestItem = FindClosestItem(mouseSimPos, out closestItemDist);
if (closestCharacter != null && closestItem != null)
{
if (Vector2.Distance(closestCharacter.SimPosition, mouseSimPos) < ConvertUnits.ToSimUnits(closestItemDist))
{
if (selectedConstruction != closestItem) closestItem = null;
}
else
{
closestCharacter = null;
}
}
findClosestTimer = 0.1f;
}
else
{
findClosestTimer -= deltaTime;
}
if (selectedCharacter == null && closestItem != null)
{
closestItem.IsHighlighted = true;
if (!LockHands && closestItem.Pick(this))
{
}
}
if (IsKeyHit(InputType.Select))
{
if (selectedCharacter != null)
{
DeselectCharacter();
}
else if (closestCharacter != null && closestCharacter.IsHumanoid && closestCharacter.CanBeSelected)
{
SelectCharacter(closestCharacter);
}
}
}
else
{
if (selectedCharacter != null) DeselectCharacter();
selectedConstruction = null;
closestItem = null;
closestCharacter = null;
}
DisableControls = false;
}
public static void UpdateAnimAll(float deltaTime)
{
foreach (Character c in CharacterList)
{
if (!c.Enabled) continue;
c.AnimController.UpdateAnim(deltaTime);
}
}
public static void UpdateAll(Camera cam, float deltaTime)
{
//if (NewCharacterQueue.Count>0)
//{
// new Character(NewCharacterQueue.Dequeue(), Vector2.Zero);
//}
for (int i = 0; i<CharacterList.Count; i++)
{
CharacterList[i].Update(cam, deltaTime);
}
}
public virtual void Update(Camera cam, float deltaTime)
{
if (!Enabled) return;
PreviousHull = CurrentHull;
CurrentHull = Hull.FindHull(WorldPosition, CurrentHull, true);
//if (PreviousHull != CurrentHull && Character.Controlled == this) Hull.DetectItemVisibility(this); //WIP item culling
speechBubbleTimer = Math.Max(0.0f, speechBubbleTimer - deltaTime);
obstructVisionAmount = Math.Max(obstructVisionAmount - deltaTime, 0.0f);
if (inventory!=null)
{
foreach (Item item in inventory.Items)
{
if (item == null || item.body == null || item.body.Enabled) continue;
item.SetTransform(SimPosition, 0.0f);
item.Submarine = Submarine;
}
}
if (huskInfection != null) huskInfection.Update(deltaTime, this);
if (isDead) return;
if (this != Character.Controlled)
{
if (GameMain.Server != null)
{
if (!IsDead)
{
if (memInput.Count > 0)
{
AnimController.Frozen = false;
prevDequeuedInput = dequeuedInput;
dequeuedInput = memInput[memInput.Count - 1];
cursorPosition = memMousePos[memMousePos.Count - 1];
memInput.RemoveAt(memInput.Count - 1);
memMousePos.RemoveAt(memMousePos.Count - 1);
if (dequeuedInput == 0)
{
if (isStillCountdown<=0)
{
while (memInput.Count>5 && memInput[memInput.Count-1]==0)
{
//remove inputs where the player is not moving at all
//helps the server catch up, shouldn't affect final position
memInput.RemoveAt(memInput.Count - 1);
memMousePos.RemoveAt(memMousePos.Count - 1);
}
isStillCountdown = 15;
}
else
{
isStillCountdown--;
}
} else
{
isStillCountdown = 15;
}
DebugConsole.NewMessage(Convert.ToString(memInput.Count), Color.Lime);
}
else
{
AnimController.Frozen = true;
return;
}
}
}
}
else
{
if (GameMain.Client != null)
{
memLocalPos.Add(new PosInfo(SimPosition, AnimController.TargetDir, LastNetworkUpdateID));
byte newInput = 0;
newInput |= IsKeyDown(InputType.Left) ? (byte)0x1 : (byte)0;
newInput |= IsKeyDown(InputType.Right) ? (byte)0x2 : (byte)0;
newInput |= IsKeyDown(InputType.Up) ? (byte)0x4 : (byte)0;
newInput |= IsKeyDown(InputType.Down) ? (byte)0x8 : (byte)0;
newInput |= (AnimController.TargetDir == Direction.Left) ? (byte)0x10 : (byte)0;
newInput |= IsKeyDown(InputType.Run) ? (byte)0x20 : (byte)0;
newInput |= IsKeyHit(InputType.Select) ? (byte)0x40 : (byte)0;
memInput.Insert(0, newInput);
memMousePos.Insert(0, closestItem!=null ? closestItem.Position : cursorPosition);
LastNetworkUpdateID++;
if (memInput.Count > 60)
{
memInput.RemoveRange(60, memInput.Count - 60);
memMousePos.RemoveRange(60, memMousePos.Count - 60);
}
}
}
/*if (networkUpdateSent)
{
foreach (Key key in keys)
{
key.DequeueHit();
key.DequeueHeld();
}
networkUpdateSent = false;
}*/
DisableImpactDamageTimer -= deltaTime;
if (needsAir)
{
bool protectedFromPressure = PressureProtection > 0.0f;
protectedFromPressure = protectedFromPressure && WorldPosition.Y > SubmarineBody.DamageDepth;
if (!protectedFromPressure &&
(AnimController.CurrentHull == null || AnimController.CurrentHull.LethalPressure >= 80.0f))
{
PressureTimer += ((AnimController.CurrentHull == null) ?
100.0f : AnimController.CurrentHull.LethalPressure) * deltaTime;
if (PressureTimer >= 100.0f)
{
if (controlled == this) cam.Zoom = 5.0f;
Implode();
return;
}
}
else
{
PressureTimer = 0.0f;
}
}
if (controlled == this)
{
Lights.LightManager.ViewTarget = this;
CharacterHUD.Update(deltaTime, this);
foreach (HUDProgressBar progressBar in hudProgressBars.Values)
{
progressBar.Update(deltaTime);
}
foreach (var pb in hudProgressBars.Where(pb => pb.Value.FadeTimer<=0.0f).ToList())
{
hudProgressBars.Remove(pb.Key);
}
}
if (IsUnconscious)
{
UpdateUnconscious(deltaTime);
return;
}
if (controlled == this)
{
ControlLocalPlayer(deltaTime, cam);
}
if (controlled == this ||
!(this is AICharacter) ||
!((AICharacter)this).AIController.Enabled)
{
Control(deltaTime, cam);
}
if (controlled != this && !(this is AICharacter))
{
Vector2 mouseSimPos = ConvertUnits.ToSimUnits(cursorPosition);
if (!LockHands)
{
//find the closest item if selectkey has been hit, or if the Character is being
//controlled by the player (in order to highlight it)
if (findClosestTimer <= 0.0f || IsKeyHit(InputType.Select))
{
closestCharacter = FindClosestCharacter(mouseSimPos);
if (closestCharacter != null && closestCharacter.info == null)
{
closestCharacter = null;
}
float closestItemDist = 0.0f;
closestItem = FindClosestItem(mouseSimPos, out closestItemDist);
if (closestCharacter != null && closestItem != null)
{
if (Vector2.Distance(closestCharacter.SimPosition, mouseSimPos) < ConvertUnits.ToSimUnits(closestItemDist))
{
if (selectedConstruction != closestItem) closestItem = null;
}
else
{
closestCharacter = null;
}
}
findClosestTimer = 0.1f;
}
else
{
findClosestTimer -= deltaTime;
}
if (selectedCharacter == null && closestItem != null)
{
closestItem.IsHighlighted = true;
if (!LockHands && closestItem.Pick(this))
{
}
}
if (IsKeyHit(InputType.Select))
{
if (selectedCharacter != null)
{
DeselectCharacter();
}
else if (closestCharacter != null && closestCharacter.IsHumanoid && closestCharacter.CanBeSelected)
{
SelectCharacter(closestCharacter);
}
}
}
else
{
if (selectedCharacter != null) DeselectCharacter();
selectedConstruction = null;
closestItem = null;
closestCharacter = null;
}
}
if (selectedCharacter != null && AnimController.Anim == AnimController.Animation.CPR)
{
if (GameMain.Client == null) selectedCharacter.Oxygen += (GetSkillLevel("Medical") / 10.0f) * deltaTime;
}
UpdateSightRange();
if (aiTarget != null) aiTarget.SoundRange = 0.0f;
lowPassMultiplier = MathHelper.Lerp(lowPassMultiplier, 1.0f, 0.1f);
if (needsAir) UpdateOxygen(deltaTime);
Health -= bleeding * deltaTime;
Bleeding -= BleedingDecreaseSpeed * deltaTime;
if (health <= minHealth) Kill(CauseOfDeath.Bloodloss);
if (!IsDead) LockHands = false;
}
private void UpdateOxygen(float deltaTime)
{
Oxygen += deltaTime * (oxygenAvailable < 30.0f ? -5.0f : 10.0f);
PressureProtection -= deltaTime * 100.0f;
float hullAvailableOxygen = 0.0f;
if (!AnimController.HeadInWater && AnimController.CurrentHull != null)
{
hullAvailableOxygen = AnimController.CurrentHull.OxygenPercentage;
AnimController.CurrentHull.Oxygen -= Hull.OxygenConsumptionSpeed * deltaTime;
}
OxygenAvailable += Math.Sign(hullAvailableOxygen - oxygenAvailable) * deltaTime * 50.0f;
}
private void UpdateUnconscious(float deltaTime)
{
Stun = Math.Max(5.0f, Stun);
AnimController.ResetPullJoints();
selectedConstruction = null;
if (oxygen <= 0.0f) Oxygen -= deltaTime * 0.5f;
if (health <= 0.0f)
{
AddDamage(bleeding > 0.5f ? CauseOfDeath.Bloodloss : CauseOfDeath.Damage, Math.Max(bleeding, 1.0f) * deltaTime, null);
}
}
private void UpdateSightRange()
{
if (aiTarget == null) return;
aiTarget.SightRange = 0.0f;
aiTarget.SightRange = Mass*10.0f + AnimController.Collider.LinearVelocity.Length()*500.0f;
}
public void ShowSpeechBubble(float duration, Color color)
{
speechBubbleTimer = Math.Max(speechBubbleTimer, duration);
speechBubbleColor = color;
}
public void Draw(SpriteBatch spriteBatch)
{
if (!Enabled) return;
AnimController.Draw(spriteBatch);
}
public void DrawHUD(SpriteBatch spriteBatch, Camera cam)
{
CharacterHUD.Draw(spriteBatch, this, cam);
}
public virtual void DrawFront(SpriteBatch spriteBatch, Camera cam)
{
if (!Enabled) return;
if (GameMain.DebugDraw)
{
AnimController.DebugDraw(spriteBatch);
if (aiTarget != null) aiTarget.Draw(spriteBatch);
}
if (memPos != null && memPos.Count > 0)
{
PosInfo serverPos = memPos.Last();
Vector2 remoteVec = ConvertUnits.ToDisplayUnits(serverPos.Position);
if (Submarine!=null)
{
remoteVec += Submarine.DrawPosition;
}
remoteVec.Y = -remoteVec.Y;
PosInfo localPos = memLocalPos.Find(m => m.ID == serverPos.ID);
Vector2 localVec = ConvertUnits.ToDisplayUnits(localPos.Position);
if (Submarine != null)
{
localVec += Submarine.DrawPosition;
}
localVec.Y = -localVec.Y;
GUI.DrawLine(spriteBatch, remoteVec, localVec, Color.Yellow,0,10);
}
Vector2 mouseDrawPos = CursorWorldPosition;
mouseDrawPos.Y = -mouseDrawPos.Y;
GUI.DrawLine(spriteBatch, mouseDrawPos - new Vector2(0, 5), mouseDrawPos + new Vector2(0, 5), Color.Red, 0, 10);
Vector2 closestItemPos = closestItem != null ? closestItem.DrawPosition : Vector2.Zero;
closestItemPos.Y = -closestItemPos.Y;
GUI.DrawLine(spriteBatch, closestItemPos - new Vector2(0, 5), closestItemPos + new Vector2(0, 5), Color.Lime, 0, 10);
if (this == controlled) return;
Vector2 pos = DrawPosition;
pos.Y = -pos.Y;
if (info != null)
{
Vector2 namePos = new Vector2(pos.X, pos.Y - 110.0f - (5.0f/cam.Zoom)) - GUI.Font.MeasureString(Info.Name) * 0.5f / cam.Zoom;
Color nameColor = Color.White;
if (Character.Controlled != null && TeamID!=Character.Controlled.TeamID)
{
nameColor = Color.Red;
}
spriteBatch.DrawString(GUI.Font, Info.Name, namePos + new Vector2(1.0f/cam.Zoom, 1.0f / cam.Zoom), Color.Black, 0.0f,Vector2.Zero, 1.0f / cam.Zoom,SpriteEffects.None,0.001f);
spriteBatch.DrawString(GUI.Font, Info.Name, namePos, nameColor, 0.0f, Vector2.Zero, 1.0f/cam.Zoom, SpriteEffects.None, 0.0f);
if (GameMain.DebugDraw)
{
spriteBatch.DrawString(GUI.Font, ID.ToString(), namePos - new Vector2(0.0f, 20.0f), Color.White);
}
}
if (isDead) return;
if (health < maxHealth * 0.98f)
{
Vector2 healthBarPos = new Vector2(pos.X - 50, DrawPosition.Y + 100.0f);
GUI.DrawProgressBar(spriteBatch, healthBarPos, new Vector2(100.0f, 15.0f), health / maxHealth, Color.Lerp(Color.Red, Color.Green, health / maxHealth) * 0.8f);
}
if (speechBubbleTimer > 0.0f)
{
GUI.SpeechBubbleIcon.Draw(spriteBatch, pos - Vector2.UnitY * 100.0f,
speechBubbleColor * Math.Min(speechBubbleTimer, 1.0f), 0.0f,
Math.Min((float)speechBubbleTimer, 1.0f));
}
}
/// <summary>
/// Creates a progress bar that's "linked" to the specified object (or updates an existing one if there's one already linked to the object)
/// The progress bar will automatically fade out after 1 sec if the method hasn't been called during that time
/// </summary>
public HUDProgressBar UpdateHUDProgressBar(object linkedObject, Vector2 worldPosition, float progress, Color emptyColor, Color fullColor)
{
HUDProgressBar progressBar = null;
if (!hudProgressBars.TryGetValue(linkedObject, out progressBar))
{
progressBar = new HUDProgressBar(worldPosition, Submarine, emptyColor, fullColor);
hudProgressBars.Add(linkedObject, progressBar);
}
progressBar.WorldPosition = worldPosition;
progressBar.FadeTimer = Math.Max(progressBar.FadeTimer, 1.0f);
progressBar.Progress = progress;
return progressBar;
}
public void PlaySound(AIController.AiState state)
{
if (sounds == null || !sounds.Any()) return;
var matchingSoundStates = soundStates.Where(x => x == state).ToList();
int selectedSound = Rand.Int(matchingSoundStates.Count);
int n = 0;
for (int i = 0; i < sounds.Length; i++)
{
if (soundStates[i] != state) continue;
if (n == selectedSound && sounds[i]!=null)
{
sounds[i].Play(1.0f, soundRange[i], AnimController.Limbs[0].WorldPosition);
return;
}
n++;
}
}
public virtual void AddDamage(CauseOfDeath causeOfDeath, float amount, IDamageable attacker)
{
Health = health-amount;
if (amount > 0.0f)
{
lastAttackCauseOfDeath = causeOfDeath;
if (controlled == this) CharacterHUD.TakeDamage(amount);
}
if (health <= minHealth) Kill(causeOfDeath);
}
public virtual AttackResult AddDamage(IDamageable attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound = false)
{
var attackResult = AddDamage(worldPosition, attack.DamageType, attack.GetDamage(deltaTime), attack.GetBleedingDamage(deltaTime), attack.Stun, playSound, attack.TargetForce);
var attackingCharacter = attacker as Character;
if (attackingCharacter != null && attackingCharacter.AIController == null)
{
GameServer.Log(Name + " attacked by " + attackingCharacter.Name+". Damage: "+attackResult.Damage+" Bleeding damage: "+attackResult.Bleeding, Color.Orange);
}
return attackResult;
}
public AttackResult AddDamage(Vector2 worldPosition, DamageType damageType, float amount, float bleedingAmount, float stun, bool playSound, float attackForce = 0.0f)
{
StartStun(stun);
Limb closestLimb = null;
float closestDistance = 0.0f;
foreach (Limb limb in AnimController.Limbs)
{
float distance = Vector2.Distance(worldPosition, limb.WorldPosition);
if (closestLimb == null || distance < closestDistance)
{
closestLimb = limb;
closestDistance = distance;
}
}
if (Math.Abs(attackForce) > 0.0f)
{
closestLimb.body.ApplyForce((closestLimb.WorldPosition - worldPosition) * attackForce);
}
AttackResult attackResult = closestLimb.AddDamage(worldPosition, damageType, amount, bleedingAmount, playSound);
AddDamage(damageType == DamageType.Burn ? CauseOfDeath.Burn : causeOfDeath, attackResult.Damage, null);
//health -= attackResult.Damage;
//if (health <= 0.0f && damageType == DamageType.Burn) Kill(CauseOfDeath.Burn);
if (DoesBleed)
{
Bleeding += attackResult.Bleeding;
}
return attackResult;
}
public void StartStun(float stunTimer, bool allowStunDecrease = false)
{
if ((stunTimer <= 0.0f && !allowStunDecrease) || !MathUtils.IsValid(stunTimer)) return;
if (Math.Sign(stunTimer) != Math.Sign(AnimController.StunTimer)) AnimController.ResetPullJoints();
AnimController.StunTimer = Math.Max(AnimController.StunTimer, stunTimer);
selectedConstruction = null;
}
private void Implode(bool isNetworkMessage = false)
{
if (!isNetworkMessage)
{
if (GameMain.NetworkMember != null && controlled != this) return;
}
health = minHealth;
BreakJoints();
Kill(CauseOfDeath.Pressure, isNetworkMessage);
}
public void BreakJoints()
{
Vector2 centerOfMass = AnimController.GetCenterOfMass();
foreach (Limb limb in AnimController.Limbs)
{
limb.AddDamage(limb.SimPosition, DamageType.Blunt, 500.0f, 0.0f, false);
Vector2 diff = centerOfMass - limb.SimPosition;
if (diff == Vector2.Zero) continue;
limb.body.ApplyLinearImpulse(diff * 10.0f);
// limb.Damage = 100.0f;
}
SoundPlayer.PlayDamageSound(DamageSoundType.Implode, 50.0f, AnimController.Collider);
for (int i = 0; i < 10; i++)
{
Particle p = GameMain.ParticleManager.CreateParticle("waterblood",
ConvertUnits.ToDisplayUnits(centerOfMass) + Rand.Vector(5.0f),
Rand.Vector(10.0f));
if (p != null) p.Size *= 2.0f;
GameMain.ParticleManager.CreateParticle("bubbles",
ConvertUnits.ToDisplayUnits(centerOfMass) + Rand.Vector(5.0f),
new Vector2(Rand.Range(-50f, 50f), Rand.Range(-100f, 50f)));
}
foreach (var joint in AnimController.limbJoints)
{
joint.LimitEnabled = false;
}
}
public void Kill(CauseOfDeath causeOfDeath, bool isNetworkMessage = false)
{
if (isDead) return;
if (GameMain.NetworkMember != null)
{
//if the Character is controlled by this client/server, let others know that the Character has died
if (Character.controlled == this)
{
string chatMessage = InfoTextManager.GetInfoText("Self_CauseOfDeath." + causeOfDeath.ToString());
if (GameMain.Client!=null) chatMessage += " Your chat messages will only be visible to other dead players.";
GameMain.NetworkMember.AddChatMessage(chatMessage, ChatMessageType.Dead);
GameMain.LightManager.LosEnabled = false;
controlled = null;
}
//if it's an ai Character, only let the server kill it
else if (GameMain.Server != null && this is AICharacter)
{
}
//don't kill the Character unless received a message about the Character dying
else if (!isNetworkMessage)
{
return;
}
}
GameServer.Log(Name+" has died (Cause of death: "+causeOfDeath+")", Color.Red);
if (OnDeath != null) OnDeath(this, causeOfDeath);
//CoroutineManager.StartCoroutine(DeathAnim(GameMain.GameScreen.Cam));
//health = 0.0f;
isDead = true;
this.causeOfDeath = causeOfDeath;
AnimController.movement = Vector2.Zero;
AnimController.TargetMovement = Vector2.Zero;
for (int i = 0; i < selectedItems.Length; i++ )
{
if (selectedItems[i] != null) selectedItems[i].Drop(this);
}
if (aiTarget!=null)
{
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;
}
if (GameMain.GameSession != null)
{
GameMain.GameSession.KillCharacter(this);
}
}
public void Revive(bool isNetworkMessage)
{
isDead = false;
aiTarget = new AITarget(this);
health = Math.Max(maxHealth * 0.1f, health);
foreach (RevoluteJoint joint in AnimController.limbJoints)
{
joint.MotorEnabled = true;
}
if (GameMain.GameSession != null)
{
GameMain.GameSession.ReviveCharacter(this);
}
}
public override void Remove()
{
base.Remove();
if (info != null) info.Remove();
CharacterList.Remove(this);
if (controlled == this) controlled = null;
if (GameMain.Client != null && GameMain.Client.Character == this) GameMain.Client.Character = null;
if (aiTarget != null) aiTarget.Remove();
if (AnimController != null) AnimController.Remove();
}
public virtual void ClientWrite(NetBuffer msg)
{
if (GameMain.Server != null) return;
msg.Write((byte)ClientNetObject.CHARACTER_INPUT);
if (memInput.Count > 60)
{
memInput.RemoveRange(60,memInput.Count - 60);
memMousePos.RemoveRange(60,memMousePos.Count - 60);
}
msg.Write(LastNetworkUpdateID);
byte inputCount = Math.Min((byte)memInput.Count, (byte)60);
msg.Write(inputCount);
for (int i = 0; i < inputCount; i++)
{
msg.Write(memInput[i]);
if ((memInput[i] & 0x40) > 0)
{
msg.Write(memMousePos[i].X);
msg.Write(memMousePos[i].Y);
}
}
}
public virtual void ServerRead(NetIncomingMessage msg, Client c)
{
if (GameMain.Server == null) return;
UInt32 networkUpdateID = msg.ReadUInt32();
byte inputCount = msg.ReadByte();
for (int i = 0; i < inputCount; i++)
{
byte newInput = msg.ReadByte();
Vector2 newMousePos = Position;
if ((newInput & 0x40) > 0)
{
newMousePos.X = msg.ReadSingle();
newMousePos.Y = msg.ReadSingle();
}
if ((i < ((long)networkUpdateID - (long)LastNetworkUpdateID)) && (i < 60))
{
memInput.Insert(i, newInput);
memMousePos.Insert(i, newMousePos);
}
}
if (networkUpdateID > LastNetworkUpdateID)
{
LastNetworkUpdateID = networkUpdateID;
}
if (memInput.Count > 60)
{
//deleting inputs from the queue here means the server is way behind and data needs to be dropped
//we'll make the server drop down to 30 inputs for good measure
memInput.RemoveRange(30, memInput.Count - 30);
memMousePos.RemoveRange(30, memMousePos.Count - 30);
}
}
public virtual void ServerWrite(NetBuffer msg, Client c)
{
if (GameMain.Server == null) return;
msg.Write(ID);
if (this == c.Character)
{
//length of the message
msg.Write((byte)(4+4+4+1));
msg.Write(true);
msg.Write((UInt32)(LastNetworkUpdateID - memInput.Count));
}
else
{
//length of the message
msg.Write((byte)(4+4+1));
msg.Write(false);
}
msg.Write(AnimController.TargetDir == Direction.Right);
msg.Write(SimPosition.X);
msg.Write(SimPosition.Y);
msg.WritePadBits();
}
public virtual void ClientRead(NetIncomingMessage msg, float sendingTime)
{
if (GameMain.Server != null) return;
UInt32 networkUpdateID = 0;
if (msg.ReadBoolean())
{
networkUpdateID = msg.ReadUInt32();
}
bool facingRight = msg.ReadBoolean();
Vector2 pos = new Vector2(msg.ReadFloat(), msg.ReadFloat());
var posInfo =
GameMain.NetworkMember.Character == this ?
new PosInfo(pos, facingRight ? Direction.Right : Direction.Left, networkUpdateID) :
new PosInfo(pos, facingRight ? Direction.Right : Direction.Left, sendingTime);
int index = 0;
if (GameMain.NetworkMember.Character == this)
{
while (index < memPos.Count && posInfo.ID > memPos[index].ID)
{
index++;
}
}
else
{
while (index < memPos.Count && posInfo.Timestamp > memPos[index].Timestamp)
{
index++;
}
}
memPos.Insert(index, posInfo);
}
public void WriteSpawnData(NetBuffer msg)
{
if (GameMain.Server == null) return;
msg.Write(Info == null);
msg.Write(ID);
msg.Write(ConfigPath);
msg.Write(WorldPosition.X);
msg.Write(WorldPosition.Y);
msg.Write(Enabled);
//character with no characterinfo (e.g. some monster)
if (Info == null) return;
Client ownerClient = GameMain.Server.ConnectedClients.Find(c => c.Character == this);
if (ownerClient != null)
{
msg.Write(true);
msg.Write(ownerClient.ID);
}
else if (GameMain.Server.Character == this)
{
msg.Write(true);
msg.Write((byte)0);
}
else
{
msg.Write(false);
}
msg.Write(Info.Name);
msg.Write(this is AICharacter);
msg.Write(Info.Gender == Gender.Female);
msg.Write((byte)Info.HeadSpriteId);
msg.Write(Info.Job == null ? "" : Info.Job.Name);
}
public static Character ReadSpawnData(NetIncomingMessage inc, bool spawn=true)
{
if (GameMain.Server != null) return null;
bool noInfo = inc.ReadBoolean();
ushort id = inc.ReadUInt16();
string configPath = inc.ReadString();
Vector2 position = new Vector2(inc.ReadFloat(), inc.ReadFloat());
bool enabled = inc.ReadBoolean();
Character character = null;
if (noInfo)
{
if (!spawn) return null;
character = Character.Create(configPath, position, null, true);
character.ID = id;
}
else
{
bool hasOwner = inc.ReadBoolean();
int ownerId = hasOwner ? inc.ReadByte() : -1;
string newName = inc.ReadString();
bool hasAi = inc.ReadBoolean();
bool isFemale = inc.ReadBoolean();
int headSpriteID = inc.ReadByte();
string jobName = inc.ReadString();
if (!spawn) return null;
JobPrefab jobPrefab = JobPrefab.List.Find(jp => jp.Name == jobName);
CharacterInfo ch = new CharacterInfo(configPath, newName, isFemale ? Gender.Female : Gender.Male, jobPrefab);
ch.HeadSpriteId = headSpriteID;
character = Character.Create(configPath, position, ch, GameMain.Client.MyCharacterID != id, hasAi);
character.ID = id;
if (GameMain.Client.MyCharacterID == id)
{
GameMain.Client.Character = character;
Controlled = character;
character.memInput.Clear();
character.memPos.Clear();
character.memLocalPos.Clear();
}
if (configPath == Character.HumanConfigFile)
{
GameMain.GameSession.CrewManager.characters.Add(character);
}
}
character.Enabled = enabled;
return character;
}
}
}