Files
LuaCsForBarotraumaEP/Barotrauma/BarotraumaClient/Source/Characters/Character.cs
Joonas Rikkonen 044fd3344b 2f107db...5202af9
2019-03-18 21:42:26 +02:00

696 lines
28 KiB
C#

using Barotrauma.Networking;
using Barotrauma.Particles;
using Barotrauma.Sounds;
using FarseerPhysics;
using FarseerPhysics.Dynamics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
partial class Character : Entity, IDamageable, ISerializableEntity, IClientSerializable, IServerSerializable
{
public static bool DisableControls;
public static bool DebugDrawInteract;
protected float soundTimer;
protected float soundInterval;
protected float hudInfoTimer;
protected bool hudInfoVisible;
private float findFocusedTimer;
protected float lastRecvPositionUpdateTime;
private float hudInfoHeight;
private List<CharacterSound> sounds;
//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;
if (controlled != null) controlled.Enabled = true;
CharacterHealth.OpenHealthWindow = null;
}
}
private Dictionary<object, HUDProgressBar> hudProgressBars;
public Dictionary<object, HUDProgressBar> HUDProgressBars
{
get { return hudProgressBars; }
}
private float blurStrength;
public float BlurStrength
{
get { return blurStrength; }
set { blurStrength = MathHelper.Clamp(value, 0.0f, 1.0f); }
}
private float distortStrength;
public float DistortStrength
{
get { return distortStrength; }
set { distortStrength = MathHelper.Clamp(value, 0.0f, 1.0f); }
}
private float radialDistortStrength;
public float RadialDistortStrength
{
get { return radialDistortStrength; }
set { radialDistortStrength = MathHelper.Clamp(value, 0.0f, 100.0f); }
}
private float chromaticAberrationStrength;
public float ChromaticAberrationStrength
{
get { return chromaticAberrationStrength; }
set { chromaticAberrationStrength = MathHelper.Clamp(value, 0.0f, 100.0f); }
}
public string BloodDecalName
{
get;
private set;
}
private List<ParticleEmitter> bloodEmitters = new List<ParticleEmitter>();
public IEnumerable<ParticleEmitter> BloodEmitters
{
get { return bloodEmitters; }
}
private List<ParticleEmitter> damageEmitters = new List<ParticleEmitter>();
public IEnumerable<ParticleEmitter> DamageEmitters
{
get { return damageEmitters; }
}
private List<ParticleEmitter> gibEmitters = new List<ParticleEmitter>();
public IEnumerable<ParticleEmitter> GibEmitters
{
get { return gibEmitters; }
}
partial void InitProjSpecific(XDocument doc)
{
soundInterval = doc.Root.GetAttributeFloat("soundinterval", 10.0f);
soundTimer = Rand.Range(0.0f, soundInterval);
BloodDecalName = doc.Root.GetAttributeString("blooddecal", "");
sounds = new List<CharacterSound>();
foreach (XElement subElement in doc.Root.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "sound":
sounds.Add(new CharacterSound(subElement));
break;
case "damageemitter":
damageEmitters.Add(new ParticleEmitter(subElement));
break;
case "bloodemitter":
bloodEmitters.Add(new ParticleEmitter(subElement));
break;
case "gibemitter":
gibEmitters.Add(new ParticleEmitter(subElement));
break;
}
}
hudProgressBars = new Dictionary<object, HUDProgressBar>();
}
partial void UpdateLimbLightSource(Limb limb)
{
if (limb.LightSource != null)
{
limb.LightSource.Enabled = enabled;
}
}
/// <summary>
/// Control the Character according to player input
/// </summary>
public void ControlLocalPlayer(float deltaTime, Camera cam, bool moveCam = true)
{
if (DisableControls)
{
foreach (Key key in keys)
{
if (key == null) continue;
key.Reset();
}
}
else
{
for (int i = 0; i < keys.Length; i++)
{
keys[i].SetState();
}
if (moveCam)
{
if (needsAir &&
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);
}
if (IsHumanoid)
{
cam.OffsetAmount = MathHelper.Lerp(cam.OffsetAmount, 250.0f, deltaTime);
}
else
{
//increased visibility range when controlling large a non-humanoid
cam.OffsetAmount = MathHelper.Lerp(cam.OffsetAmount, MathHelper.Clamp(Mass, 250.0f, 800.0f), deltaTime);
}
}
cursorPosition = cam.ScreenToWorld(PlayerInput.MousePosition);
if (AnimController.CurrentHull?.Submarine != null)
{
cursorPosition -= AnimController.CurrentHull.Submarine.Position;
}
Vector2 mouseSimPos = ConvertUnits.ToSimUnits(cursorPosition);
if (GUI.PauseMenuOpen)
{
cam.OffsetAmount = 0.0f;
}
else if (SelectedConstruction != null && SelectedConstruction.Components.Any(ic => (ic.GuiFrame != null && GUI.IsMouseOn(ic.GuiFrame))))
{
cam.OffsetAmount = 0.0f;
}
else if (Lights.LightManager.ViewTarget == this && Vector2.DistanceSquared(AnimController.Limbs[0].SimPosition, mouseSimPos) > 1.0f)
{
if (GUI.PauseMenuOpen || IsUnconscious)
{
if (deltaTime > 0.0f) cam.OffsetAmount = 0.0f;
}
else if (Lights.LightManager.ViewTarget == this && Vector2.DistanceSquared(AnimController.Limbs[0].SimPosition, mouseSimPos) > 1.0f)
{
Body body = Submarine.CheckVisibility(AnimController.Limbs[0].SimPosition, mouseSimPos);
Structure structure = body == null ? null : body.UserData as Structure;
float sightDist = Submarine.LastPickedFraction;
if (body?.UserData is Structure && !((Structure)body.UserData).CastShadow)
{
sightDist = 1.0f;
}
cam.OffsetAmount = MathHelper.Lerp(cam.OffsetAmount, Math.Max(250.0f, sightDist * 500.0f), 0.05f);
}
}
DoInteractionUpdate(deltaTime, mouseSimPos);
}
DisableControls = false;
}
partial void UpdateControlled(float deltaTime, Camera cam)
{
if (controlled != this) return;
ControlLocalPlayer(deltaTime, cam);
Lights.LightManager.ViewTarget = this;
CharacterHUD.Update(deltaTime, this, cam);
bool removeProgressBars = false;
foreach (HUDProgressBar progressBar in hudProgressBars.Values)
{
if (progressBar.FadeTimer <= 0.0f)
{
removeProgressBars = true;
continue;
}
progressBar.Update(deltaTime);
}
if (removeProgressBars)
{
// TODO: this generates garbage, can we fix anything here?
foreach (var pb in hudProgressBars.Where(pb => pb.Value.FadeTimer <= 0.0f).ToList())
{
hudProgressBars.Remove(pb.Key);
}
}
}
partial void OnAttackedProjSpecific(Character attacker, AttackResult attackResult)
{
if (attackResult.Damage <= 0) { return; }
if (soundTimer < soundInterval * 0.5f)
{
PlaySound(CharacterSound.SoundType.Damage);
soundTimer = soundInterval;
}
}
partial void KillProjSpecific(CauseOfDeathType causeOfDeath, Affliction causeOfDeathAffliction)
{
if (GameMain.NetworkMember != null && controlled == this)
{
string chatMessage = CauseOfDeath.Type == CauseOfDeathType.Affliction ?
CauseOfDeath.Affliction.SelfCauseOfDeathDescription :
TextManager.Get("Self_CauseOfDeathDescription." + CauseOfDeath.Type.ToString());
if (GameMain.Client != null) chatMessage += " " + TextManager.Get("DeathChatNotification");
GameMain.NetworkMember.AddChatMessage(chatMessage, ChatMessageType.Dead);
GameMain.LightManager.LosEnabled = false;
controlled = null;
}
PlaySound(CharacterSound.SoundType.Die);
}
partial void DisposeProjSpecific()
{
if (controlled == this) controlled = null;
if (GameMain.GameSession?.CrewManager != null &&
GameMain.GameSession.CrewManager.GetCharacters().Contains(this))
{
GameMain.GameSession.CrewManager.RemoveCharacter(this);
}
if (GameMain.Client?.Character == this) GameMain.Client.Character = null;
if (Lights.LightManager.ViewTarget == this) Lights.LightManager.ViewTarget = null;
}
private List<Item> debugInteractablesInRange = new List<Item>();
private List<Item> debugInteractablesAtCursor = new List<Item>();
private List<Pair<Item, float>> debugInteractablesNearCursor = new List<Pair<Item, float>>();
/// <summary>
/// Finds the front (lowest depth) interactable item at a position. "Interactable" in this case means that the character can "reach" the item.
/// </summary>
/// <param name="character">The Character who is looking for the interactable item, only items that are close enough to this character are returned</param>
/// <param name="simPosition">The item at the simPosition, with the lowest depth, is returned</param>
/// <param name="allowFindingNearestItem">If this is true and an item cannot be found at simPosition then a nearest item will be returned if possible</param>
/// <param name="hull">If a hull is specified, only items within that hull are returned</param>
public Item FindItemAtPosition(Vector2 simPosition, float aimAssistModifier = 0.0f, Item[] ignoredItems = null)
{
if (Submarine != null)
{
simPosition += Submarine.SimPosition;
}
debugInteractablesInRange.Clear();
debugInteractablesAtCursor.Clear();
debugInteractablesNearCursor.Clear();
//reduce the amount of aim assist if an item has been selected
//= can't switch selection to another item without deselecting the current one first UNLESS the cursor is directly on the item
//otherwise it would be too easy to accidentally switch the selected item when rewiring items
float aimAssistAmount = SelectedConstruction == null ? 100.0f * aimAssistModifier : 1.0f;
Vector2 displayPosition = ConvertUnits.ToDisplayUnits(simPosition);
//use the list of visible entities if it exists
var entityList = Submarine.VisibleEntities ?? Item.ItemList;
Item closestItem = null;
float closestItemDistance = aimAssistAmount;
foreach (MapEntity entity in entityList)
{
if (!(entity is Item item))
{
continue;
}
if (item.body != null && !item.body.Enabled) continue;
if (item.ParentInventory != null) continue;
if (ignoredItems != null && ignoredItems.Contains(item)) continue;
float distanceToItem = float.PositiveInfinity;
if (item.IsInsideTrigger(displayPosition, out Rectangle transformedTrigger))
{
debugInteractablesAtCursor.Add(item);
//distance is between 0-1 when the cursor is directly on the item
distanceToItem =
Math.Abs(transformedTrigger.Center.X - displayPosition.X) / transformedTrigger.Width +
Math.Abs((transformedTrigger.Y - transformedTrigger.Height / 2.0f) - displayPosition.Y) / transformedTrigger.Height;
//modify the distance based on the size of the trigger (preferring smaller items)
distanceToItem *= MathHelper.Lerp(0.05f, 2.0f, (transformedTrigger.Width + transformedTrigger.Height) / 250.0f);
}
else
{
Rectangle itemDisplayRect = new Rectangle(item.InteractionRect.X, item.InteractionRect.Y - item.InteractionRect.Height, item.InteractionRect.Width, item.InteractionRect.Height);
if (itemDisplayRect.Contains(displayPosition))
{
debugInteractablesAtCursor.Add(item);
//distance is between 0-1 when the cursor is directly on the item
distanceToItem =
Math.Abs(itemDisplayRect.Center.X - displayPosition.X) / itemDisplayRect.Width +
Math.Abs(itemDisplayRect.Center.Y - displayPosition.Y) / itemDisplayRect.Height;
//modify the distance based on the size of the item (preferring smaller ones)
distanceToItem *= MathHelper.Lerp(0.05f, 2.0f, (itemDisplayRect.Width + itemDisplayRect.Height) / 250.0f);
}
else
{
if (closestItemDistance < 2.0f) { continue; }
//get the point on the itemDisplayRect which is closest to the cursor
Vector2 rectIntersectionPoint = new Vector2(
MathHelper.Clamp(displayPosition.X, itemDisplayRect.X, itemDisplayRect.Right),
MathHelper.Clamp(displayPosition.Y, itemDisplayRect.Y, itemDisplayRect.Bottom));
distanceToItem = 2.0f + Vector2.Distance(rectIntersectionPoint, displayPosition);
}
}
if (distanceToItem > closestItemDistance) { continue; }
if (!CanInteractWith(item)) { continue; }
debugInteractablesNearCursor.Add(new Pair<Item, float>(item, 1.0f - distanceToItem / (100.0f * aimAssistModifier)));
closestItem = item;
closestItemDistance = distanceToItem;
}
return closestItem;
}
private Character FindCharacterAtPosition(Vector2 mouseSimPos, float maxDist = 150.0f)
{
Character closestCharacter = null;
float closestDist = 0.0f;
maxDist = ConvertUnits.ToSimUnits(maxDist);
foreach (Character c in CharacterList)
{
if (!CanInteractWith(c)) continue;
float dist = Vector2.DistanceSquared(mouseSimPos, c.SimPosition);
if (dist < maxDist * maxDist && (closestCharacter == null || dist < closestDist))
{
closestCharacter = c;
closestDist = dist;
}
/*FarseerPhysics.Common.Transform transform;
c.AnimController.Collider.FarseerBody.GetTransform(out transform);
for (int i = 0; i < c.AnimController.Collider.FarseerBody.FixtureList.Count; i++)
{
if (c.AnimController.Collider.FarseerBody.FixtureList[i].Shape.TestPoint(ref transform, ref mouseSimPos))
{
Console.WriteLine("Hit: " + i);
closestCharacter = c;
}
}*/
}
return closestCharacter;
}
partial void UpdateProjSpecific(float deltaTime, Camera cam)
{
if (!IsDead && !IsUnconscious)
{
if (soundTimer > 0)
{
soundTimer -= deltaTime;
}
else if (AIController != null)
{
switch (AIController.State)
{
case AIController.AIState.Attack:
PlaySound(CharacterSound.SoundType.Attack);
break;
default:
PlaySound(CharacterSound.SoundType.Idle);
break;
}
}
}
if (info != null || Vitality < MaxVitality * 0.98f)
{
hudInfoTimer -= deltaTime;
if (hudInfoTimer <= 0.0f)
{
if (controlled == null)
{
hudInfoVisible = true;
}
//if the character is not in the camera view, the name can't be visible and we can avoid the expensive visibility checks
else if (WorldPosition.X < cam.WorldView.X || WorldPosition.X > cam.WorldView.Right ||
WorldPosition.Y > cam.WorldView.Y || WorldPosition.Y < cam.WorldView.Y - cam.WorldView.Height)
{
hudInfoVisible = false;
}
else
{
//Ideally it shouldn't send the character entirely if we can't see them but /shrug, this isn't the most hacker-proof game atm
hudInfoVisible = controlled.CanSeeCharacter(this, controlled.ViewTarget == null ? controlled.WorldPosition : controlled.ViewTarget.WorldPosition);
}
hudInfoTimer = Rand.Range(0.5f, 1.0f);
}
}
if (controlled == this)
{
CharacterHealth.UpdateHUD(deltaTime);
}
}
public static void AddAllToGUIUpdateList()
{
for (int i = 0; i < CharacterList.Count; i++)
{
CharacterList[i].AddToGUIUpdateList();
}
}
public virtual void AddToGUIUpdateList()
{
if (controlled == this)
{
CharacterHUD.AddToGUIUpdateList(this);
CharacterHealth.AddToGUIUpdateList();
}
}
public void Draw(SpriteBatch spriteBatch, Camera cam)
{
if (!Enabled) return;
AnimController.Draw(spriteBatch, cam);
}
public void DrawHUD(SpriteBatch spriteBatch, Camera cam, bool drawHealth = true)
{
CharacterHUD.Draw(spriteBatch, this, cam);
if (drawHealth) CharacterHealth.DrawHUD(spriteBatch);
}
public virtual void DrawFront(SpriteBatch spriteBatch, Camera cam)
{
if (!Enabled) return;
if (GameMain.DebugDraw)
{
AnimController.DebugDraw(spriteBatch);
if (aiTarget != null) aiTarget.Draw(spriteBatch);
}
if (GUI.DisableHUD) return;
Vector2 pos = DrawPosition;
pos.Y += hudInfoHeight;
if (CurrentHull != null && DrawPosition.Y > CurrentHull.WorldRect.Y - 130.0f)
{
float lowerAmount = DrawPosition.Y - (CurrentHull.WorldRect.Y - 130.0f);
hudInfoHeight = MathHelper.Lerp(hudInfoHeight, 100.0f - lowerAmount, 0.1f);
hudInfoHeight = Math.Max(hudInfoHeight, 20.0f);
}
else
{
hudInfoHeight = MathHelper.Lerp(hudInfoHeight, 100.0f, 0.1f);
}
pos.Y = -pos.Y;
if (speechBubbleTimer > 0.0f)
{
GUI.SpeechBubbleIcon.Draw(spriteBatch, pos - Vector2.UnitY * 30,
speechBubbleColor * Math.Min(speechBubbleTimer, 1.0f), 0.0f,
Math.Min(speechBubbleTimer, 1.0f));
}
if (this == controlled)
{
if (DebugDrawInteract)
{
Vector2 cursorPos = cam.ScreenToWorld(PlayerInput.MousePosition);
cursorPos.Y = -cursorPos.Y;
foreach (Item item in debugInteractablesAtCursor)
{
GUI.DrawLine(spriteBatch, cursorPos,
new Vector2(item.DrawPosition.X, -item.DrawPosition.Y), Color.LightGreen, width: 4);
}
foreach (Item item in debugInteractablesInRange)
{
GUI.DrawLine(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y),
new Vector2(item.DrawPosition.X, -item.DrawPosition.Y), Color.White * 0.1f, width: 4);
}
foreach (Pair<Item, float> item in debugInteractablesNearCursor)
{
GUI.DrawLine(spriteBatch,
cursorPos,
new Vector2(item.First.DrawPosition.X, -item.First.DrawPosition.Y),
ToolBox.GradientLerp(item.Second, Color.Red, Color.Orange, Color.Green), width: 2);
}
}
return;
}
float hoverRange = 300.0f;
float fadeOutRange = 200.0f;
float cursorDist = Vector2.Distance(WorldPosition, cam.ScreenToWorld(PlayerInput.MousePosition));
float hudInfoAlpha = MathHelper.Clamp(1.0f - (cursorDist - (hoverRange - fadeOutRange)) / fadeOutRange, 0.2f, 1.0f);
if (!GUI.DisableCharacterNames && hudInfoVisible && info != null &&
(controlled == null || this != controlled.focusedCharacter))
{
string name = Info.DisplayName;
if (controlled == null && name != Info.Name) name += " " + TextManager.Get("Disguised");
Vector2 namePos = new Vector2(pos.X, pos.Y - 10.0f - (5.0f / cam.Zoom)) - GUI.Font.MeasureString(name) * 0.5f / cam.Zoom;
Vector2 screenSize = new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
Vector2 viewportSize = new Vector2(cam.WorldView.Width, cam.WorldView.Height);
namePos.X -= cam.WorldView.X; namePos.Y += cam.WorldView.Y;
namePos *= screenSize / viewportSize;
namePos.X = (float)Math.Floor(namePos.X); namePos.Y = (float)Math.Floor(namePos.Y);
namePos *= viewportSize / screenSize;
namePos.X += cam.WorldView.X; namePos.Y -= cam.WorldView.Y;
Color nameColor = Color.White;
if (Controlled != null && TeamID != Controlled.TeamID)
{
nameColor = TeamID == TeamType.FriendlyNPC ? Color.SkyBlue : Color.Red;
}
GUI.Font.DrawString(spriteBatch, 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);
GUI.Font.DrawString(spriteBatch, name, namePos, nameColor * hudInfoAlpha, 0.0f, Vector2.Zero, 1.0f / cam.Zoom, SpriteEffects.None, 0.0f);
if (GameMain.DebugDraw)
{
GUI.Font.DrawString(spriteBatch, ID.ToString(), namePos - new Vector2(0.0f, 20.0f), Color.White);
}
}
if (IsDead) return;
if (Vitality < MaxVitality * 0.98f && hudInfoVisible)
{
Vector2 healthBarPos = new Vector2(pos.X - 50, -pos.Y);
GUI.DrawProgressBar(spriteBatch, healthBarPos, new Vector2(100.0f, 15.0f),
Vitality / MaxVitality,
Color.Lerp(Color.Red, Color.Green, Vitality / MaxVitality) * 0.8f * hudInfoAlpha,
new Color(0.5f, 0.57f, 0.6f, 1.0f) * hudInfoAlpha);
}
}
/// <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)
{
if (controlled != this) return null;
if (!hudProgressBars.TryGetValue(linkedObject, out HUDProgressBar 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;
}
private SoundChannel soundChannel;
public void PlaySound(CharacterSound.SoundType soundType)
{
if (sounds == null || sounds.Count == 0) { return; }
if (soundChannel != null && soundChannel.IsPlaying) { return; }
var matchingSounds = sounds.Where(s =>
s.Type == soundType &&
(s.Gender == Gender.None || (info != null && info.Gender == s.Gender)));
if (!matchingSounds.Any()) { return; }
var matchingSoundsList = matchingSounds.ToList();
var selectedSound = matchingSoundsList[Rand.Int(matchingSoundsList.Count)];
soundChannel = SoundPlayer.PlaySound(selectedSound.Sound, selectedSound.Volume, selectedSound.Range, AnimController.WorldPosition, CurrentHull);
soundTimer = soundInterval;
}
partial void ImplodeFX()
{
Vector2 centerOfMass = AnimController.GetCenterOfMass();
SoundPlayer.PlaySound("implode", 1.0f, 150.0f, WorldPosition);
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)));
GameMain.ParticleManager.CreateParticle("gib",
WorldPosition + Rand.Vector(Rand.Range(0.0f, 50.0f)),
Rand.Range(0.0f, MathHelper.TwoPi),
Rand.Range(200.0f, 700.0f), null);
}
for (int i = 0; i < 30; i++)
{
GameMain.ParticleManager.CreateParticle("heavygib",
WorldPosition + Rand.Vector(Rand.Range(0.0f, 50.0f)),
Rand.Range(0.0f, MathHelper.TwoPi),
Rand.Range(50.0f, 500.0f), null);
}
}
}
}