(61d00a474) v0.9.7.1
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class AIController : ISteerable
|
||||
{
|
||||
public virtual void DebugDraw(SpriteBatch spriteBatch) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class AITarget
|
||||
{
|
||||
public static bool ShowAITargets;
|
||||
|
||||
public void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
if (!ShowAITargets) { return; }
|
||||
var pos = new Vector2(WorldPosition.X, -WorldPosition.Y);
|
||||
if (soundRange > 0.0f)
|
||||
{
|
||||
Color color;
|
||||
if (Entity is Character)
|
||||
{
|
||||
color = Color.Yellow;
|
||||
}
|
||||
else if (Entity is Item)
|
||||
{
|
||||
color = Color.Orange;
|
||||
}
|
||||
else
|
||||
{
|
||||
color = Color.OrangeRed;
|
||||
}
|
||||
ShapeExtensions.DrawCircle(spriteBatch, pos, SoundRange, 100, color, thickness: 1 / Screen.Selected.Cam.Zoom);
|
||||
ShapeExtensions.DrawCircle(spriteBatch, pos, 3, 8, color, thickness: 2 / Screen.Selected.Cam.Zoom);
|
||||
GUI.DrawLine(spriteBatch, pos, pos + Vector2.UnitY * SoundRange, color, width: (int)(1 / Screen.Selected.Cam.Zoom) + 1);
|
||||
}
|
||||
if (sightRange > 0.0f)
|
||||
{
|
||||
Color color;
|
||||
if (Entity is Character)
|
||||
{
|
||||
color = Color.CornflowerBlue;
|
||||
}
|
||||
else if (Entity is Item)
|
||||
{
|
||||
color = Color.CadetBlue;
|
||||
}
|
||||
else
|
||||
{
|
||||
//color = Color.WhiteSmoke;
|
||||
// disable the indicators for structures, because they clutter the debug view
|
||||
return;
|
||||
}
|
||||
ShapeExtensions.DrawCircle(spriteBatch, pos, SightRange, 100, color, thickness: 1 / Screen.Selected.Cam.Zoom);
|
||||
ShapeExtensions.DrawCircle(spriteBatch, pos, 6, 8, color, thickness: 2 / Screen.Selected.Cam.Zoom);
|
||||
GUI.DrawLine(spriteBatch, pos, pos + Vector2.UnitY * SightRange, color, width: (int)(1 / Screen.Selected.Cam.Zoom) + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Dynamics.Joints;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class EnemyAIController : AIController
|
||||
{
|
||||
public override void DebugDraw(SpriteBatch spriteBatch)
|
||||
{
|
||||
if (Character.IsDead) return;
|
||||
|
||||
Vector2 pos = Character.WorldPosition;
|
||||
pos.Y = -pos.Y;
|
||||
|
||||
if (State == AIState.Idle && PreviousState == AIState.Attack)
|
||||
{
|
||||
var target = _selectedAiTarget ?? _lastAiTarget;
|
||||
if (target != null && target.Entity != null)
|
||||
{
|
||||
var memory = GetTargetMemory(target);
|
||||
Vector2 targetPos = memory.Location;
|
||||
targetPos.Y = -targetPos.Y;
|
||||
GUI.DrawLine(spriteBatch, pos, targetPos, Color.White * 0.5f, 0, 4);
|
||||
GUI.DrawString(spriteBatch, pos - Vector2.UnitY * 60.0f, $"{target.Entity.ToString()} ({memory.Priority.FormatZeroDecimal()})", Color.White, Color.Black);
|
||||
}
|
||||
}
|
||||
else if (SelectedAiTarget?.Entity != null)
|
||||
{
|
||||
Vector2 targetPos = SelectedAiTarget.WorldPosition;
|
||||
if (State == AIState.Attack)
|
||||
{
|
||||
targetPos = attackWorldPos;
|
||||
}
|
||||
targetPos.Y = -targetPos.Y;
|
||||
GUI.DrawLine(spriteBatch, pos, targetPos, GUI.Style.Red * 0.5f, 0, 4);
|
||||
if (wallTarget != null)
|
||||
{
|
||||
Vector2 wallTargetPos = wallTarget.Position;
|
||||
if (wallTarget.Structure.Submarine != null) { wallTargetPos += wallTarget.Structure.Submarine.Position; }
|
||||
wallTargetPos.Y = -wallTargetPos.Y;
|
||||
GUI.DrawRectangle(spriteBatch, wallTargetPos - new Vector2(10.0f, 10.0f), new Vector2(20.0f, 20.0f), Color.Orange, false);
|
||||
GUI.DrawLine(spriteBatch, pos, wallTargetPos, Color.Orange * 0.5f, 0, 5);
|
||||
}
|
||||
GUI.DrawString(spriteBatch, pos - Vector2.UnitY * 60.0f, $"{SelectedAiTarget.Entity.ToString()} ({GetTargetMemory(SelectedAiTarget).Priority.FormatZeroDecimal()})", GUI.Style.Red, Color.Black);
|
||||
GUI.DrawString(spriteBatch, pos - Vector2.UnitY * 40.0f, $"({targetValue.FormatZeroDecimal()})", GUI.Style.Red, Color.Black);
|
||||
}
|
||||
|
||||
/*GUI.Font.DrawString(spriteBatch, targetValue.ToString(), pos - Vector2.UnitY * 80.0f, GUI.Style.Red);
|
||||
GUI.Font.DrawString(spriteBatch, "updatetargets: " + MathUtils.Round(updateTargetsTimer, 0.1f), pos - Vector2.UnitY * 100.0f, GUI.Style.Red);
|
||||
GUI.Font.DrawString(spriteBatch, "cooldown: " + MathUtils.Round(coolDownTimer, 0.1f), pos - Vector2.UnitY * 120.0f, GUI.Style.Red);*/
|
||||
|
||||
Color stateColor = Color.White;
|
||||
switch (State)
|
||||
{
|
||||
case AIState.Attack:
|
||||
stateColor = IsCoolDownRunning ? Color.Orange : GUI.Style.Red;
|
||||
break;
|
||||
case AIState.Escape:
|
||||
stateColor = Color.LightBlue;
|
||||
break;
|
||||
case AIState.Flee:
|
||||
stateColor = Color.White;
|
||||
break;
|
||||
case AIState.Eat:
|
||||
stateColor = Color.Brown;
|
||||
break;
|
||||
}
|
||||
GUI.DrawString(spriteBatch, pos - Vector2.UnitY * 80.0f, State.ToString(), stateColor, Color.Black);
|
||||
|
||||
if (LatchOntoAI != null)
|
||||
{
|
||||
foreach (Joint attachJoint in LatchOntoAI.AttachJoints)
|
||||
{
|
||||
GUI.DrawLine(spriteBatch,
|
||||
ConvertUnits.ToDisplayUnits(new Vector2(attachJoint.WorldAnchorA.X, -attachJoint.WorldAnchorA.Y)),
|
||||
ConvertUnits.ToDisplayUnits(new Vector2(attachJoint.WorldAnchorB.X, -attachJoint.WorldAnchorB.Y)), GUI.Style.Green, 0, 4);
|
||||
}
|
||||
|
||||
if (LatchOntoAI.WallAttachPos.HasValue)
|
||||
{
|
||||
//GUI.DrawLine(spriteBatch, pos,
|
||||
// ConvertUnits.ToDisplayUnits(new Vector2(LatchOntoAI.WallAttachPos.Value.X, -LatchOntoAI.WallAttachPos.Value.Y)), GUI.Style.Green, 0, 3);
|
||||
}
|
||||
}
|
||||
|
||||
if (steeringManager is IndoorsSteeringManager pathSteering)
|
||||
{
|
||||
var path = pathSteering.CurrentPath;
|
||||
if (path != null)
|
||||
{
|
||||
if (path.CurrentNode != null)
|
||||
{
|
||||
GUI.DrawLine(spriteBatch, pos,
|
||||
new Vector2(path.CurrentNode.DrawPosition.X, -path.CurrentNode.DrawPosition.Y),
|
||||
Color.DarkViolet, 0, 3);
|
||||
|
||||
GUI.DrawString(spriteBatch, pos - new Vector2(0, 100), "Path cost: " + path.Cost.FormatZeroDecimal(), Color.White, Color.Black * 0.5f);
|
||||
}
|
||||
for (int i = 1; i < path.Nodes.Count; i++)
|
||||
{
|
||||
var previousNode = path.Nodes[i - 1];
|
||||
var currentNode = path.Nodes[i];
|
||||
GUI.DrawLine(spriteBatch,
|
||||
new Vector2(currentNode.DrawPosition.X, -currentNode.DrawPosition.Y),
|
||||
new Vector2(previousNode.DrawPosition.X, -previousNode.DrawPosition.Y),
|
||||
GUI.Style.Red * 0.5f, 0, 3);
|
||||
|
||||
GUI.SmallFont.DrawString(spriteBatch,
|
||||
currentNode.ID.ToString(),
|
||||
new Vector2(currentNode.DrawPosition.X - 10, -currentNode.DrawPosition.Y - 30),
|
||||
GUI.Style.Red);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (steeringManager.AvoidDir.LengthSquared() > 0.0001f)
|
||||
{
|
||||
Vector2 hitPos = ConvertUnits.ToDisplayUnits(steeringManager.AvoidRayCastHitPosition);
|
||||
hitPos.Y = -hitPos.Y;
|
||||
|
||||
GUI.DrawLine(spriteBatch, hitPos, hitPos + new Vector2(steeringManager.AvoidDir.X, -steeringManager.AvoidDir.Y) * 100, GUI.Style.Red, width: 5);
|
||||
//GUI.DrawLine(spriteBatch, pos, ConvertUnits.ToDisplayUnits(steeringManager.AvoidLookAheadPos.X, -steeringManager.AvoidLookAheadPos.Y), Color.Orange, width: 4);
|
||||
}
|
||||
}
|
||||
GUI.DrawLine(spriteBatch, pos, pos + ConvertUnits.ToDisplayUnits(new Vector2(Character.AnimController.TargetMovement.X, -Character.AnimController.TargetMovement.Y)), Color.SteelBlue, width: 2);
|
||||
GUI.DrawLine(spriteBatch, pos, pos + ConvertUnits.ToDisplayUnits(new Vector2(Steering.X, -Steering.Y)), Color.Blue, width: 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using FarseerPhysics;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class HumanAIController : AIController
|
||||
{
|
||||
partial void InitProjSpecific()
|
||||
{
|
||||
/*if (GameMain.GameSession != null && GameMain.GameSession.CrewManager != null)
|
||||
{
|
||||
CurrentOrder = Order.GetPrefab("dismissed");
|
||||
objectiveManager.SetOrder(CurrentOrder, "", null);
|
||||
GameMain.GameSession.CrewManager.SetCharacterOrder(Character, CurrentOrder, null, null);
|
||||
}*/
|
||||
}
|
||||
|
||||
public override void DebugDraw(Microsoft.Xna.Framework.Graphics.SpriteBatch spriteBatch)
|
||||
{
|
||||
Vector2 pos = Character.WorldPosition;
|
||||
pos.Y = -pos.Y;
|
||||
Vector2 textOffset = new Vector2(-40, -160);
|
||||
|
||||
if (SelectedAiTarget?.Entity != null)
|
||||
{
|
||||
//GUI.DrawLine(spriteBatch, pos, new Vector2(SelectedAiTarget.WorldPosition.X, -SelectedAiTarget.WorldPosition.Y), GUI.Style.Red);
|
||||
//GUI.DrawString(spriteBatch, pos + textOffset, $"AI TARGET: {SelectedAiTarget.Entity.ToString()}", Color.White, Color.Black);
|
||||
}
|
||||
|
||||
GUI.DrawString(spriteBatch, pos + textOffset, Character.Name, Color.White, Color.Black);
|
||||
|
||||
if (ObjectiveManager != null)
|
||||
{
|
||||
var currentOrder = ObjectiveManager.CurrentOrder;
|
||||
if (currentOrder != null)
|
||||
{
|
||||
GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(0, 20), $"ORDER: {currentOrder.DebugTag} ({currentOrder.Priority.FormatZeroDecimal()})", Color.White, Color.Black);
|
||||
}
|
||||
else if (ObjectiveManager.WaitTimer > 0)
|
||||
{
|
||||
GUI.DrawString(spriteBatch, pos + new Vector2(0, 20), $"Waiting... {ObjectiveManager.WaitTimer.FormatZeroDecimal()}", Color.White, Color.Black);
|
||||
}
|
||||
var currentObjective = ObjectiveManager.CurrentObjective;
|
||||
if (currentObjective != null)
|
||||
{
|
||||
if (currentOrder == null)
|
||||
{
|
||||
GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(0, 20), $"MAIN OBJECTIVE: {currentObjective.DebugTag} ({currentObjective.Priority.FormatZeroDecimal()})", Color.White, Color.Black);
|
||||
}
|
||||
var subObjective = currentObjective.CurrentSubObjective;
|
||||
if (subObjective != null)
|
||||
{
|
||||
GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(0, 40), $"SUBOBJECTIVE: {subObjective.DebugTag} ({subObjective.Priority.FormatZeroDecimal()})", Color.White, Color.Black);
|
||||
}
|
||||
var activeObjective = ObjectiveManager.GetActiveObjective();
|
||||
if (activeObjective != null)
|
||||
{
|
||||
GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(0, 60), $"ACTIVE OBJECTIVE: {activeObjective.DebugTag} ({activeObjective.Priority.FormatZeroDecimal()})", Color.White, Color.Black);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (steeringManager is IndoorsSteeringManager pathSteering)
|
||||
{
|
||||
var path = pathSteering.CurrentPath;
|
||||
if (path != null)
|
||||
{
|
||||
for (int i = 1; i < path.Nodes.Count; i++)
|
||||
{
|
||||
var previousNode = path.Nodes[i - 1];
|
||||
var currentNode = path.Nodes[i];
|
||||
GUI.DrawLine(spriteBatch,
|
||||
new Vector2(currentNode.DrawPosition.X, -currentNode.DrawPosition.Y),
|
||||
new Vector2(previousNode.DrawPosition.X, -previousNode.DrawPosition.Y),
|
||||
Color.Blue * 0.5f, 0, 3);
|
||||
|
||||
GUI.SmallFont.DrawString(spriteBatch,
|
||||
currentNode.ID.ToString(),
|
||||
new Vector2(currentNode.DrawPosition.X - 10, -currentNode.DrawPosition.Y - 30),
|
||||
Color.Blue);
|
||||
}
|
||||
if (path.CurrentNode != null)
|
||||
{
|
||||
GUI.DrawLine(spriteBatch, pos,
|
||||
new Vector2(path.CurrentNode.DrawPosition.X, -path.CurrentNode.DrawPosition.Y),
|
||||
Color.BlueViolet, 0, 3);
|
||||
|
||||
GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(0, 80), "Path cost: " + path.Cost.FormatZeroDecimal(), Color.White, Color.Black * 0.5f);
|
||||
}
|
||||
}
|
||||
}
|
||||
GUI.DrawLine(spriteBatch, pos, pos + ConvertUnits.ToDisplayUnits(new Vector2(Character.AnimController.TargetMovement.X, -Character.AnimController.TargetMovement.Y)), Color.SteelBlue, width: 2);
|
||||
GUI.DrawLine(spriteBatch, pos, pos + ConvertUnits.ToDisplayUnits(new Vector2(Steering.X, -Steering.Y)), Color.Blue, width: 3);
|
||||
|
||||
//if (Character.IsKeyDown(InputType.Aim))
|
||||
//{
|
||||
// GUI.DrawLine(spriteBatch, pos, new Vector2(Character.CursorWorldPosition.X, -Character.CursorWorldPosition.Y), Color.Yellow, width: 4);
|
||||
//}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class AICharacter : Character
|
||||
{
|
||||
public override void DrawFront(Microsoft.Xna.Framework.Graphics.SpriteBatch spriteBatch, Camera cam)
|
||||
{
|
||||
base.DrawFront(spriteBatch, cam);
|
||||
if (GameMain.DebugDraw && !IsDead) aiController.DebugDraw(spriteBatch);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,576 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.SpriteDeformations;
|
||||
using Barotrauma.Extensions;
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using FarseerPhysics.Dynamics.Joints;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using Barotrauma.Particles;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
abstract partial class Ragdoll
|
||||
{
|
||||
public HashSet<SpriteDeformation> SpriteDeformations { get; protected set; } = new HashSet<SpriteDeformation>();
|
||||
|
||||
/// <summary>
|
||||
/// Inversed draw order, which is used for drawing the limbs in 3d (deformable sprites).
|
||||
/// </summary>
|
||||
protected Limb[] inversedLimbDrawOrder;
|
||||
|
||||
partial void UpdateNetPlayerPositionProjSpecific(float deltaTime, float lowestSubPos)
|
||||
{
|
||||
if (character != GameMain.Client.Character || !character.CanMove)
|
||||
{
|
||||
//remove states without a timestamp (there may still be ID-based states
|
||||
//in the list when the controlled character switches to timestamp-based interpolation)
|
||||
character.MemState.RemoveAll(m => m.Timestamp == 0.0f);
|
||||
|
||||
//use simple interpolation for other players' characters and characters that can't move
|
||||
if (character.MemState.Count > 0)
|
||||
{
|
||||
CharacterStateInfo serverPos = character.MemState.Last();
|
||||
if (!character.isSynced)
|
||||
{
|
||||
SetPosition(serverPos.Position, false);
|
||||
Collider.LinearVelocity = Vector2.Zero;
|
||||
character.MemLocalState.Clear();
|
||||
character.LastNetworkUpdateID = serverPos.ID;
|
||||
character.isSynced = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (character.MemState[0].SelectedCharacter == null || character.MemState[0].SelectedCharacter.Removed)
|
||||
{
|
||||
character.DeselectCharacter();
|
||||
}
|
||||
else if (character.MemState[0].SelectedCharacter != null)
|
||||
{
|
||||
character.SelectCharacter(character.MemState[0].SelectedCharacter);
|
||||
}
|
||||
|
||||
if (character.MemState[0].SelectedItem == null || character.MemState[0].SelectedItem.Removed)
|
||||
{
|
||||
character.SelectedConstruction = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (character.SelectedConstruction != character.MemState[0].SelectedItem)
|
||||
{
|
||||
foreach (var ic in character.MemState[0].SelectedItem.Components)
|
||||
{
|
||||
if (ic.CanBeSelected) ic.Select(character);
|
||||
}
|
||||
}
|
||||
character.SelectedConstruction = character.MemState[0].SelectedItem;
|
||||
}
|
||||
|
||||
if (character.MemState[0].Animation == AnimController.Animation.CPR)
|
||||
{
|
||||
character.AnimController.Anim = AnimController.Animation.CPR;
|
||||
}
|
||||
else if (character.AnimController.Anim == AnimController.Animation.CPR)
|
||||
{
|
||||
character.AnimController.Anim = AnimController.Animation.None;
|
||||
}
|
||||
|
||||
Vector2 newVelocity = Collider.LinearVelocity;
|
||||
Vector2 newPosition = Collider.SimPosition;
|
||||
float newRotation = Collider.Rotation;
|
||||
float newAngularVelocity = Collider.AngularVelocity;
|
||||
Collider.CorrectPosition(character.MemState, out newPosition, out newVelocity, out newRotation, out newAngularVelocity);
|
||||
|
||||
newVelocity = newVelocity.ClampLength(100.0f);
|
||||
if (!MathUtils.IsValid(newVelocity)) { newVelocity = Vector2.Zero; }
|
||||
overrideTargetMovement = newVelocity.LengthSquared() > 0.01f ? newVelocity : Vector2.Zero;
|
||||
|
||||
Collider.LinearVelocity = newVelocity;
|
||||
Collider.AngularVelocity = newAngularVelocity;
|
||||
|
||||
float distSqrd = Vector2.DistanceSquared(newPosition, Collider.SimPosition);
|
||||
float errorTolerance = character.CanMove ? 0.01f : 0.2f;
|
||||
if (distSqrd > errorTolerance)
|
||||
{
|
||||
if (distSqrd > 10.0f || !character.CanMove)
|
||||
{
|
||||
Collider.TargetRotation = newRotation;
|
||||
SetPosition(newPosition, lerp: distSqrd < 5.0f, ignorePlatforms: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
Collider.TargetRotation = newRotation;
|
||||
Collider.TargetPosition = newPosition;
|
||||
Collider.MoveToTargetPosition(true);
|
||||
}
|
||||
}
|
||||
|
||||
//immobilized characters can't correct their position using AnimController movement
|
||||
// -> we need to correct it manually
|
||||
if (!character.CanMove)
|
||||
{
|
||||
float mainLimbDistSqrd = Vector2.DistanceSquared(MainLimb.PullJointWorldAnchorA, Collider.SimPosition);
|
||||
float mainLimbErrorTolerance = 0.1f;
|
||||
//if the main limb is roughly at the correct position and the collider isn't moving (much at least),
|
||||
//don't attempt to correct the position.
|
||||
if (mainLimbDistSqrd > mainLimbErrorTolerance || Collider.LinearVelocity.LengthSquared() > 0.05f)
|
||||
{
|
||||
MainLimb.PullJointWorldAnchorB = Collider.SimPosition;
|
||||
MainLimb.PullJointEnabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
character.MemLocalState.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
//remove states with a timestamp (there may still timestamp-based states
|
||||
//in the list if the controlled character switches from timestamp-based interpolation to ID-based)
|
||||
character.MemState.RemoveAll(m => m.Timestamp > 0.0f);
|
||||
|
||||
for (int i = 0; i < character.MemLocalState.Count; i++)
|
||||
{
|
||||
if (character.Submarine == null)
|
||||
{
|
||||
//transform in-sub coordinates to outside coordinates
|
||||
if (character.MemLocalState[i].Position.Y > lowestSubPos)
|
||||
{
|
||||
character.MemLocalState[i].TransformInToOutside();
|
||||
}
|
||||
}
|
||||
else if (currentHull?.Submarine != null)
|
||||
{
|
||||
//transform outside coordinates to in-sub coordinates
|
||||
if (character.MemLocalState[i].Position.Y < lowestSubPos)
|
||||
{
|
||||
character.MemLocalState[i].TransformOutToInside(currentHull.Submarine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (character.MemState.Count < 1) return;
|
||||
|
||||
overrideTargetMovement = Vector2.Zero;
|
||||
|
||||
CharacterStateInfo serverPos = character.MemState.Last();
|
||||
|
||||
if (!character.isSynced)
|
||||
{
|
||||
SetPosition(serverPos.Position, false);
|
||||
Collider.LinearVelocity = Vector2.Zero;
|
||||
character.MemLocalState.Clear();
|
||||
character.LastNetworkUpdateID = serverPos.ID;
|
||||
character.isSynced = true;
|
||||
return;
|
||||
}
|
||||
|
||||
int localPosIndex = character.MemLocalState.FindIndex(m => m.ID == serverPos.ID);
|
||||
if (localPosIndex > -1)
|
||||
{
|
||||
CharacterStateInfo localPos = character.MemLocalState[localPosIndex];
|
||||
|
||||
//the entity we're interacting with doesn't match the server's
|
||||
if (localPos.SelectedCharacter != serverPos.SelectedCharacter)
|
||||
{
|
||||
if (serverPos.SelectedCharacter == null || serverPos.SelectedCharacter.Removed)
|
||||
{
|
||||
character.DeselectCharacter();
|
||||
}
|
||||
else if (serverPos.SelectedCharacter != null)
|
||||
{
|
||||
character.SelectCharacter(serverPos.SelectedCharacter);
|
||||
}
|
||||
}
|
||||
if (localPos.SelectedItem != serverPos.SelectedItem)
|
||||
{
|
||||
if (serverPos.SelectedItem == null || serverPos.SelectedItem.Removed)
|
||||
{
|
||||
character.SelectedConstruction = null;
|
||||
}
|
||||
else if (serverPos.SelectedItem != null)
|
||||
{
|
||||
if (character.SelectedConstruction != serverPos.SelectedItem)
|
||||
{
|
||||
serverPos.SelectedItem.TryInteract(character, true, true);
|
||||
}
|
||||
character.SelectedConstruction = serverPos.SelectedItem;
|
||||
}
|
||||
}
|
||||
|
||||
if (localPos.Animation != serverPos.Animation)
|
||||
{
|
||||
if (serverPos.Animation == AnimController.Animation.CPR)
|
||||
{
|
||||
character.AnimController.Anim = AnimController.Animation.CPR;
|
||||
}
|
||||
else if (character.AnimController.Anim == AnimController.Animation.CPR)
|
||||
{
|
||||
character.AnimController.Anim = AnimController.Animation.None;
|
||||
}
|
||||
}
|
||||
|
||||
Hull serverHull = Hull.FindHull(ConvertUnits.ToDisplayUnits(serverPos.Position), character.CurrentHull, serverPos.Position.Y < lowestSubPos);
|
||||
Hull clientHull = Hull.FindHull(ConvertUnits.ToDisplayUnits(localPos.Position), serverHull, localPos.Position.Y < lowestSubPos);
|
||||
|
||||
if (serverHull != null && clientHull != null && serverHull.Submarine != clientHull.Submarine)
|
||||
{
|
||||
//hull subs don't match => teleport the camera to the other sub
|
||||
character.Submarine = serverHull.Submarine;
|
||||
character.CurrentHull = CurrentHull = serverHull;
|
||||
SetPosition(serverPos.Position);
|
||||
character.MemLocalState.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector2 positionError = serverPos.Position - localPos.Position;
|
||||
float rotationError = serverPos.Rotation.HasValue && localPos.Rotation.HasValue ?
|
||||
serverPos.Rotation.Value - localPos.Rotation.Value :
|
||||
0.0f;
|
||||
|
||||
for (int i = localPosIndex; i < character.MemLocalState.Count; i++)
|
||||
{
|
||||
Hull pointHull = Hull.FindHull(ConvertUnits.ToDisplayUnits(character.MemLocalState[i].Position), clientHull, character.MemLocalState[i].Position.Y < lowestSubPos);
|
||||
if (pointHull != clientHull && ((pointHull == null) || (clientHull == null) || (pointHull.Submarine == clientHull.Submarine))) break;
|
||||
character.MemLocalState[i].Translate(positionError, rotationError);
|
||||
}
|
||||
|
||||
float errorMagnitude = positionError.Length();
|
||||
if (errorMagnitude > 0.5f)
|
||||
{
|
||||
character.MemLocalState.Clear();
|
||||
SetPosition(serverPos.Position, lerp: true, ignorePlatforms: false);
|
||||
}
|
||||
else if (errorMagnitude > 0.01f)
|
||||
{
|
||||
Collider.TargetPosition = Collider.SimPosition + positionError;
|
||||
Collider.TargetRotation = Collider.Rotation + rotationError;
|
||||
Collider.MoveToTargetPosition(lerp: true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (character.MemLocalState.Count > 120) character.MemLocalState.RemoveRange(0, character.MemLocalState.Count - 120);
|
||||
character.MemState.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
partial void ImpactProjSpecific(float impact, Body body)
|
||||
{
|
||||
float volume = MathHelper.Clamp(impact - 3.0f, 0.5f, 1.0f);
|
||||
|
||||
if (body.UserData is Limb limb && character.Stun <= 0f)
|
||||
{
|
||||
if (impact > 3.0f) { PlayImpactSound(limb); }
|
||||
}
|
||||
else if (body.UserData is Limb || body == Collider.FarseerBody)
|
||||
{
|
||||
if (!character.IsRemotePlayer && impact > ImpactTolerance)
|
||||
{
|
||||
SoundPlayer.PlayDamageSound("LimbBlunt", strongestImpact, Collider);
|
||||
}
|
||||
}
|
||||
if (Character.Controlled == character)
|
||||
{
|
||||
GameMain.GameScreen.Cam.Shake = Math.Min(Math.Max(strongestImpact, GameMain.GameScreen.Cam.Shake), 3.0f);
|
||||
}
|
||||
}
|
||||
|
||||
public void PlayImpactSound(Limb limb)
|
||||
{
|
||||
limb.LastImpactSoundTime = (float)Timing.TotalTime;
|
||||
if (!string.IsNullOrWhiteSpace(limb.HitSoundTag))
|
||||
{
|
||||
bool inWater = limb.inWater;
|
||||
if (character.CurrentHull != null &&
|
||||
character.CurrentHull.Surface > character.CurrentHull.Rect.Y - character.CurrentHull.Rect.Height &&
|
||||
limb.SimPosition.Y < ConvertUnits.ToSimUnits(character.CurrentHull.Rect.Y - character.CurrentHull.Rect.Height) + limb.body.GetMaxExtent())
|
||||
{
|
||||
inWater = true;
|
||||
}
|
||||
SoundPlayer.PlaySound(inWater ? "footstep_water" : limb.HitSoundTag, limb.WorldPosition, hullGuess: character.CurrentHull);
|
||||
}
|
||||
foreach (WearableSprite wearable in limb.WearingItems)
|
||||
{
|
||||
if (limb.type == wearable.Limb && !string.IsNullOrWhiteSpace(wearable.Sound))
|
||||
{
|
||||
SoundPlayer.PlaySound(wearable.Sound, limb.WorldPosition, hullGuess: character.CurrentHull);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
partial void Splash(Limb limb, Hull limbHull)
|
||||
{
|
||||
//create a splash particle
|
||||
for (int i = 0; i < MathHelper.Clamp(Math.Abs(limb.LinearVelocity.Y), 1.0f, 5.0f); i++)
|
||||
{
|
||||
var splash = GameMain.ParticleManager.CreateParticle("watersplash",
|
||||
new Vector2(limb.WorldPosition.X, limbHull.WorldSurface),
|
||||
new Vector2(0.0f, Math.Abs(-limb.LinearVelocity.Y * 20.0f)) + Rand.Vector(Math.Abs(limb.LinearVelocity.Y * 10)),
|
||||
Rand.Range(0.0f, MathHelper.TwoPi), limbHull);
|
||||
|
||||
if (splash != null)
|
||||
{
|
||||
splash.Size *= MathHelper.Clamp(Math.Abs(limb.LinearVelocity.Y) * 0.1f, 1.0f, 2.0f);
|
||||
}
|
||||
}
|
||||
|
||||
GameMain.ParticleManager.CreateParticle("bubbles",
|
||||
new Vector2(limb.WorldPosition.X, limbHull.WorldSurface),
|
||||
limb.LinearVelocity * 0.001f,
|
||||
0.0f, limbHull);
|
||||
|
||||
//if the Character dropped into water, create a wave
|
||||
if (limb.LinearVelocity.Y < 0.0f)
|
||||
{
|
||||
if (splashSoundTimer <= 0.0f)
|
||||
{
|
||||
SoundPlayer.PlaySplashSound(limb.WorldPosition, Math.Abs(limb.LinearVelocity.Y) + Rand.Range(-5.0f, 0.0f));
|
||||
splashSoundTimer = 0.5f;
|
||||
}
|
||||
|
||||
//+ some extra bubbles to follow the character underwater
|
||||
GameMain.ParticleManager.CreateParticle("bubbles",
|
||||
new Vector2(limb.WorldPosition.X, limbHull.WorldSurface),
|
||||
limb.LinearVelocity * 10.0f,
|
||||
0.0f, limbHull);
|
||||
}
|
||||
}
|
||||
|
||||
partial void SetupDrawOrder()
|
||||
{
|
||||
//make sure every character gets drawn at a distinct "layer"
|
||||
//(instead of having some of the limbs appear behind and some in front of other characters)
|
||||
float startDepth = 0.1f;
|
||||
float increment = 0.001f;
|
||||
foreach (Character otherCharacter in Character.CharacterList)
|
||||
{
|
||||
if (otherCharacter == character) continue;
|
||||
startDepth += increment;
|
||||
}
|
||||
//make sure each limb has a distinct depth value
|
||||
List<Limb> depthSortedLimbs = Limbs.OrderBy(l => l.ActiveSprite == null ? 0.0f : l.ActiveSprite.Depth).ToList();
|
||||
foreach (Limb limb in Limbs)
|
||||
{
|
||||
if (limb.ActiveSprite != null)
|
||||
limb.ActiveSprite.Depth = startDepth + depthSortedLimbs.IndexOf(limb) * 0.00001f;
|
||||
}
|
||||
depthSortedLimbs.Reverse();
|
||||
inversedLimbDrawOrder = depthSortedLimbs.ToArray();
|
||||
}
|
||||
|
||||
partial void UpdateProjSpecific(float deltaTime, Camera cam)
|
||||
{
|
||||
if (!character.IsVisible) { return; }
|
||||
|
||||
LimbJoints.ForEach(j => j.UpdateDeformations(deltaTime));
|
||||
foreach (var deformation in SpriteDeformations)
|
||||
{
|
||||
if (character.IsDead && deformation.Params.StopWhenHostIsDead) { continue; }
|
||||
if (deformation.Params.UseMovementSine)
|
||||
{
|
||||
if (this is AnimController animator)
|
||||
{
|
||||
deformation.Phase = MathUtils.WrapAngleTwoPi(animator.WalkPos * deformation.Params.Frequency + MathHelper.Pi * deformation.Params.SineOffset);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
deformation.Update(deltaTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
partial void FlipProjSpecific()
|
||||
{
|
||||
foreach (Limb limb in Limbs)
|
||||
{
|
||||
if (limb == null || limb.IsSevered || limb.ActiveSprite == null) continue;
|
||||
|
||||
Vector2 spriteOrigin = limb.ActiveSprite.Origin;
|
||||
spriteOrigin.X = limb.ActiveSprite.SourceRect.Width - spriteOrigin.X;
|
||||
limb.ActiveSprite.Origin = spriteOrigin;
|
||||
}
|
||||
}
|
||||
|
||||
partial void SeverLimbJointProjSpecific(LimbJoint limbJoint, bool playSound)
|
||||
{
|
||||
foreach (Limb limb in new Limb[] { limbJoint.LimbA, limbJoint.LimbB })
|
||||
{
|
||||
float gibParticleAmount = MathHelper.Clamp(limb.Mass / character.AnimController.Mass, 0.1f, 1.0f);
|
||||
foreach (ParticleEmitter emitter in character.GibEmitters)
|
||||
{
|
||||
if (inWater && emitter.Prefab.ParticlePrefab.DrawTarget == ParticlePrefab.DrawTargetType.Air) continue;
|
||||
if (!inWater && emitter.Prefab.ParticlePrefab.DrawTarget == ParticlePrefab.DrawTargetType.Water) continue;
|
||||
|
||||
emitter.Emit(1.0f, limb.WorldPosition, character.CurrentHull, amountMultiplier: gibParticleAmount);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(character.BloodDecalName))
|
||||
{
|
||||
character.CurrentHull?.AddDecal(character.BloodDecalName, limb.WorldPosition, MathHelper.Clamp(limb.Mass, 0.5f, 2.0f));
|
||||
}
|
||||
}
|
||||
|
||||
if (playSound)
|
||||
{
|
||||
SoundPlayer.PlayDamageSound("Gore", 1.0f, limbJoint.LimbA.body);
|
||||
}
|
||||
}
|
||||
|
||||
public void Draw(SpriteBatch spriteBatch, Camera cam)
|
||||
{
|
||||
if (simplePhysicsEnabled) { return; }
|
||||
|
||||
Collider.UpdateDrawPosition();
|
||||
|
||||
if (Limbs == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to draw a ragdoll, limbs have been removed. Character: \"" + character.Name + "\", removed: " + character.Removed + "\n" + Environment.StackTrace);
|
||||
GameAnalyticsManager.AddErrorEventOnce("Ragdoll.Draw:LimbsRemoved",
|
||||
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
|
||||
"Failed to draw a ragdoll, limbs have been removed. Character: \"" + character.Name + "\", removed: " + character.Removed + "\n" + Environment.StackTrace);
|
||||
return;
|
||||
}
|
||||
|
||||
Color? color = null;
|
||||
if (character.ExternalHighlight)
|
||||
{
|
||||
color = Color.Lerp(Color.White, GUI.Style.Orange, (float)Math.Sin(Timing.TotalTime * 3.5f));
|
||||
}
|
||||
|
||||
float depthOffset = GetDepthOffset();
|
||||
for (int i = 0; i < limbs.Length; i++)
|
||||
{
|
||||
if (depthOffset != 0.0f) { inversedLimbDrawOrder[i].ActiveSprite.Depth += depthOffset; }
|
||||
inversedLimbDrawOrder[i].Draw(spriteBatch, cam, color);
|
||||
if (depthOffset != 0.0f) { inversedLimbDrawOrder[i].ActiveSprite.Depth -= depthOffset; }
|
||||
}
|
||||
LimbJoints.ForEach(j => j.Draw(spriteBatch));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Offset added to the default draw depth of the character's limbs. For example, climbing on ladders affects the depth of the character to get it to render behind the ladders.
|
||||
/// </summary>
|
||||
public float GetDepthOffset()
|
||||
{
|
||||
float depthOffset = 0.0f;
|
||||
var ladder = character.SelectedConstruction?.GetComponent<Ladder>();
|
||||
if (ladder != null)
|
||||
{
|
||||
float maxDepth = 0.0f;
|
||||
float minDepth = 1.0f;
|
||||
foreach (Limb limb in Limbs)
|
||||
{
|
||||
var activeSprite = limb.ActiveSprite;
|
||||
if (activeSprite != null)
|
||||
{
|
||||
maxDepth = Math.Max(activeSprite.Depth, maxDepth);
|
||||
minDepth = Math.Min(activeSprite.Depth, minDepth);
|
||||
}
|
||||
}
|
||||
if (character.WorldPosition.X < character.SelectedConstruction.WorldPosition.X)
|
||||
{
|
||||
//at the left side of the ladder, needs to be drawn in front of the rungs
|
||||
depthOffset = Math.Max(ladder.BackgroundSpriteDepth - 0.01f - maxDepth, 0.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
//at the right side of the ladder, needs to be drawn behind the rungs
|
||||
depthOffset = Math.Max(ladder.BackgroundSpriteDepth + 0.01f - minDepth, 0.0f);
|
||||
}
|
||||
}
|
||||
return depthOffset;
|
||||
}
|
||||
|
||||
public void DebugDraw(SpriteBatch spriteBatch)
|
||||
{
|
||||
if (!GameMain.DebugDraw || !character.Enabled) return;
|
||||
if (simplePhysicsEnabled) return;
|
||||
|
||||
foreach (Limb limb in Limbs)
|
||||
{
|
||||
if (limb.PullJointEnabled)
|
||||
{
|
||||
Vector2 pos = ConvertUnits.ToDisplayUnits(limb.PullJointWorldAnchorA);
|
||||
if (currentHull?.Submarine != null) pos += currentHull.Submarine.DrawPosition;
|
||||
pos.Y = -pos.Y;
|
||||
GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)pos.Y, 5, 5), GUI.Style.Red, true, 0.01f);
|
||||
}
|
||||
|
||||
limb.body.DebugDraw(spriteBatch, inWater ? (currentHull == null ? Color.Blue : Color.Cyan) : Color.White);
|
||||
}
|
||||
|
||||
Collider.DebugDraw(spriteBatch, frozen ? GUI.Style.Red : (inWater ? Color.SkyBlue : Color.Gray));
|
||||
GUI.Font.DrawString(spriteBatch, Collider.LinearVelocity.X.FormatSingleDecimal(), new Vector2(Collider.DrawPosition.X, -Collider.DrawPosition.Y), Color.Orange);
|
||||
|
||||
foreach (RevoluteJoint joint in LimbJoints)
|
||||
{
|
||||
Vector2 pos = ConvertUnits.ToDisplayUnits(joint.WorldAnchorA);
|
||||
GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)-pos.Y, 5, 5), Color.White, true);
|
||||
|
||||
pos = ConvertUnits.ToDisplayUnits(joint.WorldAnchorB);
|
||||
GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)-pos.Y, 5, 5), Color.White, true);
|
||||
}
|
||||
|
||||
foreach (Limb limb in Limbs)
|
||||
{
|
||||
if (limb.body.TargetPosition != null)
|
||||
{
|
||||
Vector2 pos = ConvertUnits.ToDisplayUnits((Vector2)limb.body.TargetPosition);
|
||||
if (currentHull?.Submarine != null) pos += currentHull.Submarine.DrawPosition;
|
||||
pos.Y = -pos.Y;
|
||||
|
||||
GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X - 10, (int)pos.Y - 10, 20, 20), Color.Cyan, false, 0.01f);
|
||||
GUI.DrawLine(spriteBatch, pos, new Vector2(limb.WorldPosition.X, -limb.WorldPosition.Y), Color.Cyan);
|
||||
}
|
||||
}
|
||||
|
||||
if (this is HumanoidAnimController humanoid)
|
||||
{
|
||||
Vector2 pos = ConvertUnits.ToDisplayUnits(humanoid.RightHandIKPos);
|
||||
GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)-pos.Y, 4, 4), GUI.Style.Green, true);
|
||||
pos = ConvertUnits.ToDisplayUnits(humanoid.LeftHandIKPos);
|
||||
GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)-pos.Y, 4, 4), GUI.Style.Green, true);
|
||||
}
|
||||
|
||||
if (character.MemState.Count > 1)
|
||||
{
|
||||
Vector2 prevPos = ConvertUnits.ToDisplayUnits(character.MemState[0].Position);
|
||||
if (currentHull?.Submarine != null) prevPos += currentHull.Submarine.DrawPosition;
|
||||
prevPos.Y = -prevPos.Y;
|
||||
|
||||
for (int i = 1; i < character.MemState.Count; i++)
|
||||
{
|
||||
Vector2 currPos = ConvertUnits.ToDisplayUnits(character.MemState[i].Position);
|
||||
if (currentHull?.Submarine != null) currPos += currentHull.Submarine.DrawPosition;
|
||||
currPos.Y = -currPos.Y;
|
||||
|
||||
GUI.DrawRectangle(spriteBatch, new Rectangle((int)currPos.X - 3, (int)currPos.Y - 3, 6, 6), Color.Cyan * 0.6f, true, 0.01f);
|
||||
GUI.DrawLine(spriteBatch, prevPos, currPos, Color.Cyan * 0.6f, 0, 3);
|
||||
|
||||
prevPos = currPos;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentHull != null)
|
||||
{
|
||||
Vector2 displayFloorPos = ConvertUnits.ToDisplayUnits(new Vector2(Collider.SimPosition.X, floorY));
|
||||
if (currentHull?.Submarine != null) { displayFloorPos += currentHull.Submarine.DrawPosition; }
|
||||
displayFloorPos.Y = -displayFloorPos.Y;
|
||||
GUI.DrawLine(spriteBatch, displayFloorPos, displayFloorPos + new Vector2(floorNormal.X, -floorNormal.Y) * 50.0f, Color.Cyan * 0.5f, 0, 2);
|
||||
}
|
||||
|
||||
if (IgnorePlatforms)
|
||||
{
|
||||
GUI.DrawLine(spriteBatch,
|
||||
new Vector2(Collider.DrawPosition.X, -Collider.DrawPosition.Y),
|
||||
new Vector2(Collider.DrawPosition.X, -Collider.DrawPosition.Y + 50),
|
||||
Color.Orange, 0, 5);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using Barotrauma.Sounds;
|
||||
using Barotrauma.Particles;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class Attack
|
||||
{
|
||||
[Serialize("StructureBlunt", true), Editable()]
|
||||
public string StructureSoundType { get; private set; }
|
||||
|
||||
private RoundSound sound;
|
||||
|
||||
private ParticleEmitter particleEmitter;
|
||||
|
||||
partial void InitProjSpecific(XElement element)
|
||||
{
|
||||
if (element.Attribute("sound") != null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in attack ("+element+") - sounds should be defined as child elements, not as attributes.");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "particleemitter":
|
||||
particleEmitter = new ParticleEmitter(subElement);
|
||||
break;
|
||||
case "sound":
|
||||
sound = Submarine.LoadRoundSound(subElement);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
partial void DamageParticles(float deltaTime, Vector2 worldPosition)
|
||||
{
|
||||
if (particleEmitter != null)
|
||||
{
|
||||
particleEmitter.Emit(deltaTime, worldPosition);
|
||||
}
|
||||
|
||||
if (sound != null)
|
||||
{
|
||||
SoundPlayer.PlaySound(sound.Sound, worldPosition, sound.Volume, sound.Range);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,878 @@
|
||||
using Barotrauma.Items.Components;
|
||||
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
|
||||
{
|
||||
public static bool DisableControls;
|
||||
|
||||
public static bool DebugDrawInteract;
|
||||
|
||||
protected float soundTimer;
|
||||
protected float soundInterval;
|
||||
protected float hudInfoTimer;
|
||||
protected bool hudInfoVisible;
|
||||
|
||||
private float pressureParticleTimer;
|
||||
|
||||
private float findFocusedTimer;
|
||||
|
||||
protected float lastRecvPositionUpdateTime;
|
||||
|
||||
private float hudInfoHeight;
|
||||
|
||||
private List<CharacterSound> sounds;
|
||||
|
||||
public bool ExternalHighlight;
|
||||
|
||||
/// <summary>
|
||||
/// Is the character currently visible on the camera. Refresh the value by calling DoVisibilityCheck.
|
||||
/// </summary>
|
||||
public bool IsVisible
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = true;
|
||||
|
||||
//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;
|
||||
private readonly List<KeyValuePair<object, HUDProgressBar>> progressBarRemovals = new List<KeyValuePair<object, HUDProgressBar>>();
|
||||
|
||||
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 => Params.BloodDecal;
|
||||
|
||||
private readonly List<ParticleEmitter> bloodEmitters = new List<ParticleEmitter>();
|
||||
public IEnumerable<ParticleEmitter> BloodEmitters
|
||||
{
|
||||
get { return bloodEmitters; }
|
||||
}
|
||||
|
||||
private readonly List<ParticleEmitter> damageEmitters = new List<ParticleEmitter>();
|
||||
public IEnumerable<ParticleEmitter> DamageEmitters
|
||||
{
|
||||
get { return damageEmitters; }
|
||||
}
|
||||
|
||||
private readonly List<ParticleEmitter> gibEmitters = new List<ParticleEmitter>();
|
||||
public IEnumerable<ParticleEmitter> GibEmitters
|
||||
{
|
||||
get { return gibEmitters; }
|
||||
}
|
||||
|
||||
public class ObjectiveEntity
|
||||
{
|
||||
public Entity Entity;
|
||||
public Sprite Sprite;
|
||||
public Color Color;
|
||||
|
||||
public ObjectiveEntity(Entity entity, Sprite sprite, Color? color = null)
|
||||
{
|
||||
Entity = entity;
|
||||
Sprite = sprite;
|
||||
if (color.HasValue)
|
||||
{
|
||||
Color = color.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
Color = Color.White;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<ObjectiveEntity> activeObjectiveEntities = new List<ObjectiveEntity>();
|
||||
public IEnumerable<ObjectiveEntity> ActiveObjectiveEntities
|
||||
{
|
||||
get { return activeObjectiveEntities; }
|
||||
}
|
||||
|
||||
partial void InitProjSpecific(XElement mainElement)
|
||||
{
|
||||
soundInterval = mainElement.GetAttributeFloat("soundinterval", 10.0f);
|
||||
soundTimer = Rand.Range(0.0f, soundInterval);
|
||||
|
||||
sounds = new List<CharacterSound>();
|
||||
Params.Sounds.ForEach(s => sounds.Add(new CharacterSound(s)));
|
||||
|
||||
foreach (XElement subElement in mainElement.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
private bool wasFiring;
|
||||
|
||||
/// <summary>
|
||||
/// Control the Character according to player input
|
||||
/// </summary>
|
||||
public void ControlLocalPlayer(float deltaTime, Camera cam, bool moveCam = true)
|
||||
{
|
||||
if (DisableControls || GUI.PauseMenuOpen || GUI.SettingsMenuOpen)
|
||||
{
|
||||
foreach (Key key in keys)
|
||||
{
|
||||
if (key == null) continue;
|
||||
key.Reset();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
wasFiring |= keys[(int)InputType.Aim].Held && keys[(int)InputType.Shoot].Held;
|
||||
for (int i = 0; i < keys.Length; i++)
|
||||
{
|
||||
keys[i].SetState();
|
||||
}
|
||||
//if we were firing (= pressing the aim and shoot keys at the same time)
|
||||
//and the fire key is the same as Select or Use, reset the key to prevent accidentally selecting/using items
|
||||
if (wasFiring && !keys[(int)InputType.Shoot].Held)
|
||||
{
|
||||
if (GameMain.Config.KeyBind(InputType.Shoot).Equals(GameMain.Config.KeyBind(InputType.Select)))
|
||||
{
|
||||
keys[(int)InputType.Select].Reset();
|
||||
}
|
||||
if (GameMain.Config.KeyBind(InputType.Shoot).Equals(GameMain.Config.KeyBind(InputType.Use)))
|
||||
{
|
||||
keys[(int)InputType.Use].Reset();
|
||||
}
|
||||
wasFiring = false;
|
||||
}
|
||||
|
||||
float targetOffsetAmount = 0.0f;
|
||||
if (moveCam)
|
||||
{
|
||||
if (NeedsAir &&
|
||||
pressureProtection < 80.0f &&
|
||||
(AnimController.CurrentHull == null || AnimController.CurrentHull.LethalPressure > 0.0f))
|
||||
{
|
||||
float pressure = AnimController.CurrentHull == null ? 100.0f : AnimController.CurrentHull.LethalPressure;
|
||||
if (pressure > 0.0f)
|
||||
{
|
||||
float zoomInEffectStrength = MathHelper.Clamp(pressure / 100.0f, 0.1f, 1.0f);
|
||||
cam.Zoom = MathHelper.Lerp(cam.Zoom,
|
||||
cam.DefaultZoom + (Math.Max(pressure, 10) / 150.0f) * Rand.Range(0.9f, 1.1f),
|
||||
zoomInEffectStrength);
|
||||
|
||||
pressureParticleTimer += pressure * deltaTime;
|
||||
if (pressureParticleTimer > 10.0f)
|
||||
{
|
||||
Particle p = GameMain.ParticleManager.CreateParticle("waterblood", WorldPosition + Rand.Vector(5.0f), Rand.Vector(10.0f));
|
||||
pressureParticleTimer = 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (IsHumanoid)
|
||||
{
|
||||
cam.OffsetAmount = 250.0f;// MathHelper.Lerp(cam.OffsetAmount, 250.0f, deltaTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
//increased visibility range when controlling large a non-humanoid
|
||||
cam.OffsetAmount = MathHelper.Clamp(Mass, 250.0f, 1500.0f);
|
||||
}
|
||||
}
|
||||
|
||||
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 = targetOffsetAmount = 0.0f;
|
||||
}
|
||||
else if (Lights.LightManager.ViewTarget is Item item && item.Prefab.FocusOnSelected)
|
||||
{
|
||||
cam.OffsetAmount = targetOffsetAmount = item.Prefab.OffsetOnSelected;
|
||||
}
|
||||
else if (SelectedConstruction != null && ViewTarget == null &&
|
||||
SelectedConstruction.Components.Any(ic => ic?.GuiFrame != null && ic.ShouldDrawHUD(this)))
|
||||
{
|
||||
cam.OffsetAmount = targetOffsetAmount = 0.0f;
|
||||
cursorPosition =
|
||||
SelectedConstruction.Position +
|
||||
new Vector2(cursorPosition.X % 10.0f, cursorPosition.Y % 10.0f); //apply a little bit of movement to the cursor pos to prevent AFK kicking
|
||||
}
|
||||
else if (!GameMain.Config.EnableMouseLook)
|
||||
{
|
||||
cam.OffsetAmount = targetOffsetAmount = 0.0f;
|
||||
}
|
||||
else if (Lights.LightManager.ViewTarget == this)
|
||||
{
|
||||
if (GUI.PauseMenuOpen || IsUnconscious)
|
||||
{
|
||||
if (deltaTime > 0.0f)
|
||||
{
|
||||
cam.OffsetAmount = targetOffsetAmount = 0.0f;
|
||||
}
|
||||
}
|
||||
else if (Vector2.DistanceSquared(AnimController.Limbs[0].SimPosition, mouseSimPos) > 1.0f)
|
||||
{
|
||||
Body body = Submarine.CheckVisibility(AnimController.Limbs[0].SimPosition, mouseSimPos);
|
||||
Structure structure = body?.UserData as Structure;
|
||||
|
||||
float sightDist = Submarine.LastPickedFraction;
|
||||
if (body?.UserData is Structure && !((Structure)body.UserData).CastShadow)
|
||||
{
|
||||
sightDist = 1.0f;
|
||||
}
|
||||
targetOffsetAmount = Math.Max(250.0f, sightDist * 500.0f);
|
||||
}
|
||||
}
|
||||
|
||||
cam.OffsetAmount = MathHelper.Lerp(cam.OffsetAmount, targetOffsetAmount, 0.05f);
|
||||
DoInteractionUpdate(deltaTime, mouseSimPos);
|
||||
}
|
||||
|
||||
if (!GUI.PauseMenuOpen && !GUI.SettingsMenuOpen)
|
||||
{
|
||||
if (SelectedConstruction != null &&
|
||||
(SelectedConstruction.ActiveHUDs.Any(ic => ic.GuiFrame != null && HUD.CloseHUD(ic.GuiFrame.Rect)) ||
|
||||
((ViewTarget as Item)?.Prefab.FocusOnSelected ?? false) && PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.Escape)))
|
||||
{
|
||||
if (GameMain.Client != null)
|
||||
{
|
||||
//emulate a Select input to get the character to deselect the item server-side
|
||||
//keys[(int)InputType.Select].Hit = true;
|
||||
keys[(int)InputType.Deselect].Hit = true;
|
||||
}
|
||||
//reset focus to prevent us from accidentally interacting with another entity
|
||||
focusedItem = null;
|
||||
FocusedCharacter = null;
|
||||
findFocusedTimer = 0.2f;
|
||||
SelectedConstruction = null;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (hudProgressBars.Any())
|
||||
{
|
||||
foreach (var progressBar in hudProgressBars)
|
||||
{
|
||||
if (progressBar.Value.FadeTimer <= 0.0f)
|
||||
{
|
||||
progressBarRemovals.Add(progressBar);
|
||||
continue;
|
||||
}
|
||||
progressBar.Value.Update(deltaTime);
|
||||
}
|
||||
if (progressBarRemovals.Any())
|
||||
{
|
||||
progressBarRemovals.ForEach(pb => hudProgressBars.Remove(pb.Key));
|
||||
progressBarRemovals.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
partial void OnAttackedProjSpecific(Character attacker, AttackResult attackResult)
|
||||
{
|
||||
if (attackResult.Damage <= 1.0f || IsDead) { 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(), fallBackTag: "Self_CauseOfDeathDescription.Damage");
|
||||
|
||||
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();
|
||||
|
||||
bool draggingItemToWorld = CharacterInventory.DraggingItemToWorld;
|
||||
|
||||
//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 = Math.Max(aimAssistAmount, 2.0f);
|
||||
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;
|
||||
|
||||
if (draggingItemToWorld)
|
||||
{
|
||||
if (item.OwnInventory == null ||
|
||||
!item.OwnInventory.CanBePut(CharacterInventory.draggingItem) ||
|
||||
!CanAccessInventory(item.OwnInventory))
|
||||
{
|
||||
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, checkVisibility: false)) 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;
|
||||
}
|
||||
|
||||
public bool ShouldLockHud()
|
||||
{
|
||||
if (this != controlled) { return false; }
|
||||
|
||||
//lock if using a controller, except if we're also using a connection panel in the same item
|
||||
return
|
||||
SelectedConstruction != null &&
|
||||
SelectedConstruction?.GetComponent<Controller>()?.User == this &&
|
||||
SelectedConstruction?.GetComponent<ConnectionPanel>()?.User != this;
|
||||
}
|
||||
|
||||
|
||||
partial void UpdateProjSpecific(float deltaTime, Camera cam)
|
||||
{
|
||||
if (!enabled) { return; }
|
||||
|
||||
if (!IsDead && !IsUnconscious)
|
||||
{
|
||||
if (soundTimer > 0)
|
||||
{
|
||||
soundTimer -= deltaTime;
|
||||
}
|
||||
else if (AIController != null)
|
||||
{
|
||||
switch (AIController.State)
|
||||
{
|
||||
case 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);
|
||||
}
|
||||
}
|
||||
|
||||
CharacterHealth.UpdateClientSpecific(deltaTime);
|
||||
if (controlled == this)
|
||||
{
|
||||
CharacterHealth.UpdateHUD(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
partial void SetOrderProjSpecific(Order order, string orderOption)
|
||||
{
|
||||
GameMain.GameSession?.CrewManager?.DisplayCharacterOrder(this, order, orderOption);
|
||||
}
|
||||
|
||||
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 DoVisibilityCheck(Camera cam)
|
||||
{
|
||||
IsVisible = false;
|
||||
if (!Enabled || AnimController.SimplePhysicsEnabled) { return; }
|
||||
|
||||
foreach (Limb limb in AnimController.Limbs)
|
||||
{
|
||||
float maxExtent = ConvertUnits.ToDisplayUnits(limb.body.GetMaxExtent());
|
||||
if (limb.LightSource != null) { maxExtent = Math.Max(limb.LightSource.Range, maxExtent); }
|
||||
if (limb.body.DrawPosition.X < cam.WorldView.X - maxExtent || limb.body.DrawPosition.X > cam.WorldView.Right + maxExtent) { continue; }
|
||||
if (limb.body.DrawPosition.Y < cam.WorldView.Y - cam.WorldView.Height - maxExtent || limb.body.DrawPosition.Y > cam.WorldView.Y + maxExtent) { continue; }
|
||||
IsVisible = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
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 (GUI.DisableHUD) return;
|
||||
|
||||
if (Controlled != null &&
|
||||
Controlled != this &&
|
||||
Submarine != null &&
|
||||
Controlled.Submarine == Submarine &&
|
||||
GameMain.Config.LosMode != LosMode.None)
|
||||
{
|
||||
float yPos = Controlled.AnimController.FloorY - 1.5f;
|
||||
|
||||
if (Controlled.AnimController.Stairs != null)
|
||||
{
|
||||
yPos = Controlled.AnimController.Stairs.SimPosition.Y - Controlled.AnimController.Stairs.RectHeight * 0.5f;
|
||||
}
|
||||
|
||||
foreach (var ladder in Ladder.List)
|
||||
{
|
||||
if (CanInteractWith(ladder.Item) && Controlled.CanInteractWith(ladder.Item))
|
||||
{
|
||||
float xPos = ladder.Item.SimPosition.X;
|
||||
if (Math.Abs(xPos - SimPosition.X) < 3.0)
|
||||
{
|
||||
yPos = ladder.Item.SimPosition.Y - ladder.Item.RectHeight * 0.5f;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (AnimController.FloorY < yPos) { 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, GUI.Style.Red, GUI.Style.Orange, GUI.Style.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 : GUI.Style.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 (CharacterHealth.DisplayedVitality < MaxVitality * 0.98f && hudInfoVisible)
|
||||
{
|
||||
hudInfoAlpha = Math.Max(hudInfoAlpha, Math.Min(CharacterHealth.DamageOverlayTimer, 1.0f));
|
||||
|
||||
Vector2 healthBarPos = new Vector2(pos.X - 50, -pos.Y);
|
||||
GUI.DrawProgressBar(spriteBatch, healthBarPos, new Vector2(100.0f, 15.0f),
|
||||
CharacterHealth.DisplayedVitality / MaxVitality,
|
||||
Color.Lerp(GUI.Style.Red, GUI.Style.Green, CharacterHealth.DisplayedVitality / 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, AnimController.WorldPosition, selectedSound.Volume, selectedSound.Range, CurrentHull);
|
||||
soundTimer = soundInterval;
|
||||
}
|
||||
|
||||
public void AddActiveObjectiveEntity(Entity entity, Sprite sprite, Color? color = null)
|
||||
{
|
||||
if (activeObjectiveEntities.Any(aoe => aoe.Entity == entity)) return;
|
||||
ObjectiveEntity objectiveEntity = new ObjectiveEntity(entity, sprite, color);
|
||||
activeObjectiveEntities.Add(objectiveEntity);
|
||||
}
|
||||
|
||||
public void RemoveActiveObjectiveEntity(Entity entity)
|
||||
{
|
||||
ObjectiveEntity found = activeObjectiveEntities.Find(aoe => aoe.Entity == entity);
|
||||
if (found == null) return;
|
||||
activeObjectiveEntities.Remove(found);
|
||||
}
|
||||
|
||||
partial void ImplodeFX()
|
||||
{
|
||||
Vector2 centerOfMass = AnimController.GetCenterOfMass();
|
||||
|
||||
SoundPlayer.PlaySound("implode", WorldPosition);
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
Particle p = GameMain.ParticleManager.CreateParticle("waterblood",
|
||||
WorldPosition + 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,462 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class CharacterHUD
|
||||
{
|
||||
private static Dictionary<Entity, int> orderIndicatorCount = new Dictionary<Entity, int>();
|
||||
const float ItemOverlayDelay = 1.0f;
|
||||
private static Item focusedItem;
|
||||
private static float focusedItemOverlayTimer;
|
||||
|
||||
private static List<Item> brokenItems = new List<Item>();
|
||||
private static float brokenItemsCheckTimer;
|
||||
|
||||
private static Dictionary<string, string> cachedHudTexts = new Dictionary<string, string>();
|
||||
|
||||
private static GUIFrame hudFrame;
|
||||
public static GUIFrame HUDFrame
|
||||
{
|
||||
|
||||
get
|
||||
{
|
||||
if (hudFrame == null)
|
||||
{
|
||||
hudFrame = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas), style: null)
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
}
|
||||
return hudFrame;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool ShouldDrawInventory(Character character)
|
||||
{
|
||||
return
|
||||
character?.Inventory != null &&
|
||||
character.AllowInput &&
|
||||
!character.LockHands &&
|
||||
character.SelectedConstruction?.GetComponent<Controller>()?.User != character;
|
||||
}
|
||||
|
||||
private static string GetCachedHudText(string textTag, string keyBind)
|
||||
{
|
||||
if (cachedHudTexts.TryGetValue(textTag + keyBind, out string text))
|
||||
{
|
||||
return text;
|
||||
}
|
||||
text = TextManager.GetWithVariable(textTag, "[key]", keyBind);
|
||||
cachedHudTexts.Add(textTag + keyBind, text);
|
||||
return text;
|
||||
}
|
||||
|
||||
public static void AddToGUIUpdateList(Character character)
|
||||
{
|
||||
if (GUI.DisableHUD) return;
|
||||
|
||||
if (!character.IsUnconscious && character.Stun <= 0.0f)
|
||||
{
|
||||
if (character.Inventory != null)
|
||||
{
|
||||
for (int i = 0; i < character.Inventory.Items.Length - 1; i++)
|
||||
{
|
||||
var item = character.Inventory.Items[i];
|
||||
if (item == null || character.Inventory.SlotTypes[i] == InvSlotType.Any) continue;
|
||||
|
||||
foreach (ItemComponent ic in item.Components)
|
||||
{
|
||||
if (ic.DrawHudWhenEquipped) ic.AddToGUIUpdateList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (character.IsHumanoid && character.SelectedCharacter != null)
|
||||
{
|
||||
character.SelectedCharacter.CharacterHealth.AddToGUIUpdateList();
|
||||
}
|
||||
}
|
||||
|
||||
HUDFrame.AddToGUIUpdateList();
|
||||
}
|
||||
|
||||
public static void Update(float deltaTime, Character character, Camera cam)
|
||||
{
|
||||
if (GUI.DisableHUD) { return; }
|
||||
|
||||
if (!character.IsUnconscious && character.Stun <= 0.0f)
|
||||
{
|
||||
if (character.Info != null && !character.ShouldLockHud() && character.SelectedCharacter == null)
|
||||
{
|
||||
bool mouseOnPortrait = HUDLayoutSettings.BottomRightInfoArea.Contains(PlayerInput.MousePosition) && GUI.MouseOn == null;
|
||||
if (mouseOnPortrait && PlayerInput.PrimaryMouseButtonClicked())
|
||||
{
|
||||
CharacterHealth.OpenHealthWindow = character.CharacterHealth;
|
||||
}
|
||||
}
|
||||
|
||||
if (character.Inventory != null)
|
||||
{
|
||||
if (!LockInventory(character))
|
||||
{
|
||||
character.Inventory.Update(deltaTime, cam);
|
||||
}
|
||||
for (int i = 0; i < character.Inventory.Items.Length - 1; i++)
|
||||
{
|
||||
var item = character.Inventory.Items[i];
|
||||
if (item == null || character.Inventory.SlotTypes[i] == InvSlotType.Any) continue;
|
||||
|
||||
foreach (ItemComponent ic in item.Components)
|
||||
{
|
||||
if (ic.DrawHudWhenEquipped) ic.UpdateHUD(character, deltaTime, cam);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (character.IsHumanoid && character.SelectedCharacter != null && character.SelectedCharacter.Inventory != null)
|
||||
{
|
||||
if (character.SelectedCharacter.CanInventoryBeAccessed)
|
||||
{
|
||||
character.SelectedCharacter.Inventory.Update(deltaTime, cam);
|
||||
}
|
||||
character.SelectedCharacter.CharacterHealth.UpdateHUD(deltaTime);
|
||||
}
|
||||
|
||||
Inventory.UpdateDragging();
|
||||
}
|
||||
|
||||
if (focusedItem != null)
|
||||
{
|
||||
if (character.FocusedItem != null)
|
||||
{
|
||||
focusedItemOverlayTimer = Math.Min(focusedItemOverlayTimer + deltaTime, ItemOverlayDelay + 1.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
focusedItemOverlayTimer = Math.Max(focusedItemOverlayTimer - deltaTime, 0.0f);
|
||||
if (focusedItemOverlayTimer <= 0.0f) focusedItem = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (brokenItemsCheckTimer > 0.0f)
|
||||
{
|
||||
brokenItemsCheckTimer -= deltaTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
brokenItems.Clear();
|
||||
brokenItemsCheckTimer = 1.0f;
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (!item.Repairables.Any(r => item.ConditionPercentage <= r.AIRepairThreshold)) { continue; }
|
||||
if (Submarine.VisibleEntities != null && !Submarine.VisibleEntities.Contains(item)) { continue; }
|
||||
|
||||
Vector2 diff = item.WorldPosition - character.WorldPosition;
|
||||
if (Submarine.CheckVisibility(character.SimPosition, character.SimPosition + ConvertUnits.ToSimUnits(diff)) == null)
|
||||
{
|
||||
brokenItems.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Draw(SpriteBatch spriteBatch, Character character, Camera cam)
|
||||
{
|
||||
if (GUI.DisableHUD) { return; }
|
||||
|
||||
character.CharacterHealth.Alignment = Alignment.Right;
|
||||
|
||||
if (GameMain.GameSession?.CrewManager != null)
|
||||
{
|
||||
orderIndicatorCount.Clear();
|
||||
foreach (Pair<Order, float> timedOrder in GameMain.GameSession.CrewManager.ActiveOrders)
|
||||
{
|
||||
DrawOrderIndicator(spriteBatch, cam, character, timedOrder.First, MathHelper.Clamp(timedOrder.Second / 10.0f, 0.2f, 1.0f));
|
||||
}
|
||||
|
||||
if (character.CurrentOrder != null)
|
||||
{
|
||||
DrawOrderIndicator(spriteBatch, cam, character, character.CurrentOrder, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Character.ObjectiveEntity objectiveEntity in character.ActiveObjectiveEntities)
|
||||
{
|
||||
DrawObjectiveIndicator(spriteBatch, cam, character, objectiveEntity, 1.0f);
|
||||
}
|
||||
|
||||
foreach (Item brokenItem in brokenItems)
|
||||
{
|
||||
float dist = Vector2.Distance(character.WorldPosition, brokenItem.WorldPosition);
|
||||
Vector2 drawPos = brokenItem.DrawPosition;
|
||||
float alpha = Math.Min((1000.0f - dist) / 1000.0f * 2.0f, 1.0f);
|
||||
if (alpha <= 0.0f) continue;
|
||||
GUI.DrawIndicator(spriteBatch, drawPos, cam, 100.0f, GUI.BrokenIcon,
|
||||
Color.Lerp(GUI.Style.Red, GUI.Style.Orange * 0.5f, brokenItem.Condition / brokenItem.MaxCondition) * alpha);
|
||||
}
|
||||
|
||||
if (!character.IsUnconscious && character.Stun <= 0.0f)
|
||||
{
|
||||
if (character.FocusedCharacter != null && character.FocusedCharacter.CanBeSelected)
|
||||
{
|
||||
DrawCharacterHoverTexts(spriteBatch, cam, character);
|
||||
}
|
||||
|
||||
float circleSize;
|
||||
if (character.FocusedItem != null)
|
||||
{
|
||||
if (focusedItem != character.FocusedItem)
|
||||
{
|
||||
focusedItemOverlayTimer = Math.Min(1.0f, focusedItemOverlayTimer);
|
||||
}
|
||||
focusedItem = character.FocusedItem;
|
||||
}
|
||||
|
||||
if (focusedItem != null && focusedItemOverlayTimer > ItemOverlayDelay)
|
||||
{
|
||||
Vector2 circlePos = cam.WorldToScreen(focusedItem.DrawPosition);
|
||||
circleSize = Math.Max(focusedItem.Rect.Width, focusedItem.Rect.Height) * 1.5f;
|
||||
circleSize = MathHelper.Clamp(circleSize, 45.0f, 100.0f) * Math.Min((focusedItemOverlayTimer - 1.0f) * 5.0f, 1.0f);
|
||||
if (circleSize > 0.0f)
|
||||
{
|
||||
Vector2 scale = new Vector2(circleSize / GUI.Style.FocusIndicator.FrameSize.X);
|
||||
GUI.Style.FocusIndicator.Draw(spriteBatch,
|
||||
(int)((focusedItemOverlayTimer - 1.0f) * GUI.Style.FocusIndicator.FrameCount * 3.0f),
|
||||
circlePos,
|
||||
Color.LightBlue * 0.3f,
|
||||
origin: GUI.Style.FocusIndicator.FrameSize.ToVector2() / 2,
|
||||
rotate: (float)Timing.TotalTime,
|
||||
scale: scale);
|
||||
}
|
||||
|
||||
if (!GUI.DisableItemHighlights && !Inventory.DraggingItemToWorld)
|
||||
{
|
||||
var hudTexts = focusedItem.GetHUDTexts(character);
|
||||
|
||||
int dir = Math.Sign(focusedItem.WorldPosition.X - character.WorldPosition.X);
|
||||
|
||||
Vector2 textSize = GUI.Font.MeasureString(focusedItem.Name);
|
||||
Vector2 largeTextSize = GUI.SubHeadingFont.MeasureString(focusedItem.Name);
|
||||
|
||||
Vector2 startPos = cam.WorldToScreen(focusedItem.DrawPosition);
|
||||
startPos.Y -= (hudTexts.Count + 1) * textSize.Y;
|
||||
if (focusedItem.Sprite != null)
|
||||
{
|
||||
startPos.X += (int)(circleSize * 0.4f * dir);
|
||||
startPos.Y -= (int)(circleSize * 0.4f);
|
||||
}
|
||||
|
||||
Vector2 textPos = startPos;
|
||||
if (dir == -1) { textPos.X -= largeTextSize.X; }
|
||||
|
||||
float alpha = MathHelper.Clamp((focusedItemOverlayTimer - ItemOverlayDelay) * 2.0f, 0.0f, 1.0f);
|
||||
|
||||
GUI.DrawString(spriteBatch, textPos, focusedItem.Name, GUI.Style.TextColor * alpha, Color.Black * alpha * 0.7f, 2, font: GUI.SubHeadingFont);
|
||||
startPos.X += dir * 10.0f * GUI.Scale;
|
||||
textPos.X += dir * 10.0f * GUI.Scale;
|
||||
textPos.Y += largeTextSize.Y;
|
||||
foreach (ColoredText coloredText in hudTexts)
|
||||
{
|
||||
if (dir == -1) textPos.X = (int)(startPos.X - GUI.SmallFont.MeasureString(coloredText.Text).X);
|
||||
GUI.DrawString(spriteBatch, textPos, coloredText.Text, coloredText.Color * alpha, Color.Black * alpha * 0.7f, 2, GUI.SmallFont);
|
||||
textPos.Y += textSize.Y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (HUDProgressBar progressBar in character.HUDProgressBars.Values)
|
||||
{
|
||||
progressBar.Draw(spriteBatch, cam);
|
||||
}
|
||||
}
|
||||
|
||||
if (character.SelectedConstruction != null &&
|
||||
(character.CanInteractWith(Character.Controlled.SelectedConstruction) || Screen.Selected == GameMain.SubEditorScreen))
|
||||
{
|
||||
character.SelectedConstruction.DrawHUD(spriteBatch, cam, Character.Controlled);
|
||||
}
|
||||
|
||||
if (character.Inventory != null)
|
||||
{
|
||||
for (int i = 0; i < character.Inventory.Items.Length - 1; i++)
|
||||
{
|
||||
var item = character.Inventory.Items[i];
|
||||
if (item == null || character.Inventory.SlotTypes[i] == InvSlotType.Any) continue;
|
||||
|
||||
foreach (ItemComponent ic in item.Components)
|
||||
{
|
||||
if (ic.DrawHudWhenEquipped) ic.DrawHUD(spriteBatch, character);
|
||||
}
|
||||
}
|
||||
}
|
||||
bool mouseOnPortrait = false;
|
||||
if (character.Stun <= 0.1f && !character.IsDead)
|
||||
{
|
||||
if (CharacterHealth.OpenHealthWindow == null && character.SelectedCharacter == null)
|
||||
{
|
||||
if (character.Info != null && !character.ShouldLockHud())
|
||||
{
|
||||
character.Info.DrawBackground(spriteBatch);
|
||||
character.Info.DrawJobIcon(spriteBatch,
|
||||
new Rectangle(
|
||||
(int)(HUDLayoutSettings.BottomRightInfoArea.X + HUDLayoutSettings.BottomRightInfoArea.Width * 0.05f),
|
||||
(int)(HUDLayoutSettings.BottomRightInfoArea.Y + HUDLayoutSettings.BottomRightInfoArea.Height * 0.1f),
|
||||
(int)(HUDLayoutSettings.BottomRightInfoArea.Width / 2),
|
||||
(int)(HUDLayoutSettings.BottomRightInfoArea.Height * 0.7f)));
|
||||
character.Info.DrawPortrait(spriteBatch, HUDLayoutSettings.PortraitArea.Location.ToVector2(), new Vector2(-12 * GUI.Scale, 4 * GUI.Scale), targetWidth: HUDLayoutSettings.PortraitArea.Width, true);
|
||||
}
|
||||
mouseOnPortrait = HUDLayoutSettings.BottomRightInfoArea.Contains(PlayerInput.MousePosition) && !character.ShouldLockHud();
|
||||
if (mouseOnPortrait)
|
||||
{
|
||||
GUI.UIGlow.Draw(spriteBatch, HUDLayoutSettings.BottomRightInfoArea, GUI.Style.Green * 0.5f);
|
||||
}
|
||||
}
|
||||
if (ShouldDrawInventory(character))
|
||||
{
|
||||
character.Inventory.Locked = LockInventory(character);
|
||||
character.Inventory.DrawOwn(spriteBatch);
|
||||
character.Inventory.CurrentLayout = CharacterHealth.OpenHealthWindow == null && character.SelectedCharacter == null ?
|
||||
CharacterInventory.Layout.Default :
|
||||
CharacterInventory.Layout.Right;
|
||||
}
|
||||
}
|
||||
|
||||
if (!character.IsUnconscious && character.Stun <= 0.0f)
|
||||
{
|
||||
if (character.IsHumanoid && character.SelectedCharacter != null && character.SelectedCharacter.Inventory != null)
|
||||
{
|
||||
if (character.SelectedCharacter.CanInventoryBeAccessed)
|
||||
{
|
||||
///character.Inventory.CurrentLayout = Alignment.Left;
|
||||
character.SelectedCharacter.Inventory.CurrentLayout = CharacterInventory.Layout.Left;
|
||||
character.SelectedCharacter.Inventory.DrawOwn(spriteBatch);
|
||||
}
|
||||
else
|
||||
{
|
||||
//character.Inventory.CurrentLayout = (CharacterHealth.OpenHealthWindow == null) ? Alignment.Center : Alignment.Left;
|
||||
}
|
||||
if (CharacterHealth.OpenHealthWindow == character.SelectedCharacter.CharacterHealth)
|
||||
{
|
||||
character.SelectedCharacter.CharacterHealth.Alignment = Alignment.Left;
|
||||
character.SelectedCharacter.CharacterHealth.DrawStatusHUD(spriteBatch);
|
||||
}
|
||||
}
|
||||
else if (character.Inventory != null)
|
||||
{
|
||||
//character.Inventory.CurrentLayout = (CharacterHealth.OpenHealthWindow == null) ? Alignment.Center : Alignment.Left;
|
||||
}
|
||||
}
|
||||
|
||||
if (mouseOnPortrait)
|
||||
{
|
||||
GUIComponent.DrawToolTip(
|
||||
spriteBatch,
|
||||
character.Info?.Job == null ? character.DisplayName : character.Name + " (" + character.Info.Job.Name + ")",
|
||||
HUDLayoutSettings.PortraitArea);
|
||||
}
|
||||
}
|
||||
|
||||
private static void DrawCharacterHoverTexts(SpriteBatch spriteBatch, Camera cam, Character character)
|
||||
{
|
||||
foreach (Item item in character.Inventory.Items)
|
||||
{
|
||||
var statusHUD = item?.GetComponent<StatusHUD>();
|
||||
if (statusHUD != null && statusHUD.IsActive && statusHUD.VisibleCharacters.Contains(character.FocusedCharacter))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Vector2 startPos = character.DrawPosition + (character.FocusedCharacter.DrawPosition - character.DrawPosition) * 0.7f;
|
||||
startPos = cam.WorldToScreen(startPos);
|
||||
|
||||
string focusName = character.FocusedCharacter.DisplayName;
|
||||
if (character.FocusedCharacter.Info != null)
|
||||
{
|
||||
focusName = character.FocusedCharacter.Info.DisplayName;
|
||||
}
|
||||
Vector2 textPos = startPos;
|
||||
Vector2 textSize = GUI.Font.MeasureString(focusName);
|
||||
Vector2 largeTextSize = GUI.SubHeadingFont.MeasureString(focusName);
|
||||
|
||||
textPos -= new Vector2(textSize.X / 2, textSize.Y);
|
||||
|
||||
Color nameColor = GUI.Style.TextColor;
|
||||
if (character.TeamID != character.FocusedCharacter.TeamID)
|
||||
{
|
||||
nameColor = character.FocusedCharacter.TeamID == Character.TeamType.FriendlyNPC ? Color.SkyBlue : GUI.Style.Red;
|
||||
}
|
||||
|
||||
GUI.DrawString(spriteBatch, textPos, focusName, nameColor, Color.Black * 0.7f, 2, GUI.SubHeadingFont);
|
||||
textPos.X += 10.0f * GUI.Scale;
|
||||
textPos.Y += GUI.SubHeadingFont.MeasureString(focusName).Y;
|
||||
if (character.FocusedCharacter.CanBeDragged)
|
||||
{
|
||||
GUI.DrawString(spriteBatch, textPos, GetCachedHudText("GrabHint", GameMain.Config.KeyBindText(InputType.Grab)),
|
||||
GUI.Style.Green, Color.Black, 2, GUI.SmallFont);
|
||||
textPos.Y += largeTextSize.Y;
|
||||
}
|
||||
if (character.FocusedCharacter.CharacterHealth.UseHealthWindow && character.CanInteractWith(character.FocusedCharacter, 160f, false))
|
||||
{
|
||||
GUI.DrawString(spriteBatch, textPos, GetCachedHudText("HealHint", GameMain.Config.KeyBindText(InputType.Health)),
|
||||
GUI.Style.Green, Color.Black, 2, GUI.SmallFont);
|
||||
textPos.Y += textSize.Y;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(character.FocusedCharacter.customInteractHUDText))
|
||||
{
|
||||
GUI.DrawString(spriteBatch, textPos, character.FocusedCharacter.customInteractHUDText, GUI.Style.Green, Color.Black, 2, GUI.SmallFont);
|
||||
textPos.Y += textSize.Y;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool LockInventory(Character character)
|
||||
{
|
||||
if (character?.Inventory == null || !character.AllowInput || character.LockHands) { return true; }
|
||||
|
||||
return character.ShouldLockHud();
|
||||
}
|
||||
|
||||
private static void DrawOrderIndicator(SpriteBatch spriteBatch, Camera cam, Character character, Order order, float iconAlpha = 1.0f)
|
||||
{
|
||||
if (order.TargetAllCharacters)
|
||||
{
|
||||
if (order.OrderGiver != character && !order.HasAppropriateJob(character))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Entity target = order.ConnectedController != null ? order.ConnectedController.Item : order.TargetEntity;
|
||||
if (target == null) { return; }
|
||||
|
||||
//don't show the indicator if far away and not inside the same sub
|
||||
//prevents exploiting the indicators in locating the sub
|
||||
if (character.Submarine != target.Submarine &&
|
||||
Vector2.DistanceSquared(character.WorldPosition, target.WorldPosition) > 1000.0f * 1000.0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!orderIndicatorCount.ContainsKey(target)) { orderIndicatorCount.Add(target, 0); }
|
||||
|
||||
Vector2 drawPos = target.DrawPosition + Vector2.UnitX * order.SymbolSprite.size.X * 1.5f * orderIndicatorCount[target];
|
||||
GUI.DrawIndicator(spriteBatch, drawPos, cam, 100.0f, order.SymbolSprite, order.Color * iconAlpha);
|
||||
|
||||
orderIndicatorCount[target] = orderIndicatorCount[target] + 1;
|
||||
}
|
||||
|
||||
private static void DrawObjectiveIndicator(SpriteBatch spriteBatch, Camera cam, Character character, Character.ObjectiveEntity objectiveEntity, float iconAlpha = 1.0f)
|
||||
{
|
||||
if (objectiveEntity == null) return;
|
||||
|
||||
Vector2 drawPos = objectiveEntity.Entity.WorldPosition;// + Vector2.UnitX * objectiveEntity.Sprite.size.X * 1.5f;
|
||||
GUI.DrawIndicator(spriteBatch, drawPos, cam, 100.0f, objectiveEntity.Sprite, objectiveEntity.Color * iconAlpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,331 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Networking;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class CharacterInfo
|
||||
{
|
||||
private static Sprite infoAreaPortraitBG;
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
infoAreaPortraitBG = GUI.Style.GetComponentStyle("InfoAreaPortraitBG")?.Sprites[GUIComponent.ComponentState.None][0].Sprite;
|
||||
new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(833, 298, 142, 98), null, 0);
|
||||
}
|
||||
|
||||
|
||||
public GUIFrame CreateInfoFrame(GUIFrame frame)
|
||||
{
|
||||
var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), frame.RectTransform, Anchor.TopCenter) { RelativeOffset = new Vector2(0.0f, 0.1f) })
|
||||
{
|
||||
Stretch = true,
|
||||
RelativeSpacing = 0.03f
|
||||
};
|
||||
|
||||
var headerArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.4f), paddedFrame.RectTransform), isHorizontal: true)
|
||||
{
|
||||
RelativeSpacing = 0.05f,
|
||||
Stretch = true
|
||||
};
|
||||
|
||||
new GUICustomComponent(new RectTransform(new Vector2(0.25f, 1.0f), headerArea.RectTransform),
|
||||
onDraw: (sb, component) => DrawIcon(sb, component.Rect.Center.ToVector2(), targetAreaSize: component.Rect.Size.ToVector2()));
|
||||
|
||||
ScalableFont font = paddedFrame.Rect.Width < 280 ? GUI.SmallFont : GUI.Font;
|
||||
|
||||
var headerTextArea = new GUILayoutGroup(new RectTransform(new Vector2(0.75f, 1.0f), headerArea.RectTransform))
|
||||
{
|
||||
RelativeSpacing = 0.05f,
|
||||
Stretch = true
|
||||
};
|
||||
|
||||
Color? nameColor = null;
|
||||
if (Job != null) { nameColor = Job.Prefab.UIColor; }
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), headerTextArea.RectTransform),
|
||||
Name, textColor: nameColor, font: GUI.LargeFont)
|
||||
{
|
||||
Padding = Vector4.Zero,
|
||||
AutoScaleHorizontal = true
|
||||
};
|
||||
|
||||
if (Job != null)
|
||||
{
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), headerTextArea.RectTransform),
|
||||
Job.Name, textColor: Job.Prefab.UIColor, font: font);
|
||||
}
|
||||
|
||||
if (personalityTrait != null)
|
||||
{
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), headerTextArea.RectTransform),
|
||||
TextManager.AddPunctuation(':', TextManager.Get("PersonalityTrait"), TextManager.Get("personalitytrait." + personalityTrait.Name.Replace(" ", ""))), font: font);
|
||||
}
|
||||
|
||||
//spacing
|
||||
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), paddedFrame.RectTransform), style: null);
|
||||
|
||||
if (Job != null)
|
||||
{
|
||||
var skills = Job.Skills;
|
||||
skills.Sort((s1, s2) => -s1.Level.CompareTo(s2.Level));
|
||||
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform),
|
||||
TextManager.Get("Skills") + ":", font: font);
|
||||
|
||||
foreach (Skill skill in skills)
|
||||
{
|
||||
Color textColor = Color.White * (0.5f + skill.Level / 200.0f);
|
||||
|
||||
var skillName = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform),
|
||||
TextManager.Get("SkillName." + skill.Identifier), textColor: textColor, font: font);
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), skillName.RectTransform),
|
||||
((int)skill.Level).ToString(), textColor: textColor, font: font, textAlignment: Alignment.CenterRight);
|
||||
}
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
public GUIFrame CreateCharacterFrame(GUIComponent parent, string text, object userData)
|
||||
{
|
||||
GUIFrame frame = new GUIFrame(new RectTransform(new Point(parent.Rect.Width, 40), parent.RectTransform) { IsFixedSize = false }, "ListBoxElement")
|
||||
{
|
||||
UserData = userData
|
||||
};
|
||||
|
||||
Color? textColor = null;
|
||||
if (Job != null) { textColor = Job.Prefab.UIColor; }
|
||||
|
||||
GUITextBlock textBlock = new GUITextBlock(new RectTransform(Vector2.One, frame.RectTransform, Anchor.CenterLeft) { AbsoluteOffset = new Point(40, 0) }, text, textColor: textColor, font: GUI.SmallFont);
|
||||
new GUICustomComponent(new RectTransform(new Point(frame.Rect.Height, frame.Rect.Height), frame.RectTransform, Anchor.CenterLeft) { IsFixedSize = false },
|
||||
onDraw: (sb, component) => DrawIcon(sb, component.Rect.Center.ToVector2(), targetAreaSize: component.Rect.Size.ToVector2()));
|
||||
return frame;
|
||||
}
|
||||
|
||||
partial void OnSkillChanged(string skillIdentifier, float prevLevel, float newLevel, Vector2 textPopupPos)
|
||||
{
|
||||
if (newLevel - prevLevel > 0.1f)
|
||||
{
|
||||
GUI.AddMessage(
|
||||
"+" + ((int)((newLevel - prevLevel) * 100.0f)).ToString() + " XP",
|
||||
GUI.Style.Green,
|
||||
textPopupPos,
|
||||
Vector2.UnitY * 10.0f);
|
||||
}
|
||||
else if (prevLevel % 0.1f > 0.05f && newLevel % 0.1f < 0.05f)
|
||||
{
|
||||
GUI.AddMessage(
|
||||
"+10 XP",
|
||||
GUI.Style.Green,
|
||||
textPopupPos,
|
||||
Vector2.UnitY * 10.0f);
|
||||
}
|
||||
|
||||
if ((int)newLevel > (int)prevLevel)
|
||||
{
|
||||
GUI.AddMessage(
|
||||
TextManager.GetWithVariables("SkillIncreased", new string[3] { "[name]", "[skillname]", "[newlevel]" },
|
||||
new string[3] { Name, TextManager.Get("SkillName." + skillIdentifier), ((int)newLevel).ToString() },
|
||||
new bool[3] { false, true, false }), GUI.Style.Green);
|
||||
}
|
||||
}
|
||||
|
||||
partial void LoadAttachmentSprites(bool omitJob)
|
||||
{
|
||||
if (attachmentSprites == null)
|
||||
{
|
||||
attachmentSprites = new List<WearableSprite>();
|
||||
}
|
||||
if (!IsAttachmentsLoaded)
|
||||
{
|
||||
LoadHeadAttachments();
|
||||
}
|
||||
FaceAttachment?.Elements("sprite").ForEach(s => attachmentSprites.Add(new WearableSprite(s, WearableType.FaceAttachment)));
|
||||
BeardElement?.Elements("sprite").ForEach(s => attachmentSprites.Add(new WearableSprite(s, WearableType.Beard)));
|
||||
MoustacheElement?.Elements("sprite").ForEach(s => attachmentSprites.Add(new WearableSprite(s, WearableType.Moustache)));
|
||||
HairElement?.Elements("sprite").ForEach(s => attachmentSprites.Add(new WearableSprite(s, WearableType.Hair)));
|
||||
if (omitJob)
|
||||
{
|
||||
JobPrefab.NoJobElement?.Element("PortraitClothing")?.Elements("sprite").ForEach(s => attachmentSprites.Add(new WearableSprite(s, WearableType.JobIndicator)));
|
||||
}
|
||||
else
|
||||
{
|
||||
Job?.Prefab.ClothingElement?.Elements("sprite").ForEach(s => attachmentSprites.Add(new WearableSprite(s, WearableType.JobIndicator)));
|
||||
}
|
||||
}
|
||||
|
||||
// Doesn't work if the head's source rect does not start at 0,0.
|
||||
public static Point CalculateOffset(Sprite sprite, Point offset) => sprite.SourceRect.Size * offset;
|
||||
|
||||
public void CalculateHeadPosition(Sprite sprite)
|
||||
{
|
||||
if (sprite == null) { return; }
|
||||
if (Head.SheetIndex == null) { return; }
|
||||
Point location = CalculateOffset(sprite, Head.SheetIndex.Value.ToPoint());
|
||||
sprite.SourceRect = new Rectangle(location, sprite.SourceRect.Size);
|
||||
}
|
||||
|
||||
public void DrawBackground(SpriteBatch spriteBatch)
|
||||
{
|
||||
if (infoAreaPortraitBG == null) { return; }
|
||||
infoAreaPortraitBG.Draw(spriteBatch, HUDLayoutSettings.BottomRightInfoArea.Location.ToVector2(), Color.White, Vector2.Zero, 0.0f,
|
||||
scale: new Vector2(
|
||||
HUDLayoutSettings.BottomRightInfoArea.Width / (float)infoAreaPortraitBG.SourceRect.Width,
|
||||
HUDLayoutSettings.BottomRightInfoArea.Height / (float)infoAreaPortraitBG.SourceRect.Height));
|
||||
}
|
||||
|
||||
public void DrawPortrait(SpriteBatch spriteBatch, Vector2 screenPos, Vector2 offset, float targetWidth, bool flip = false)
|
||||
{
|
||||
if (Portrait != null)
|
||||
{
|
||||
// Scale down the head sprite 10%
|
||||
float scale = targetWidth * 0.9f / Portrait.size.X;
|
||||
if (Head.SheetIndex.HasValue)
|
||||
{
|
||||
Portrait.SourceRect = new Rectangle(CalculateOffset(Portrait, Head.SheetIndex.Value.ToPoint()), Portrait.SourceRect.Size);
|
||||
}
|
||||
Portrait.Draw(spriteBatch, screenPos + offset, Color.White, Portrait.Origin, scale: scale, spriteEffect: flip ? SpriteEffects.FlipHorizontally : SpriteEffects.None);
|
||||
if (AttachmentSprites != null)
|
||||
{
|
||||
float depthStep = 0.000001f;
|
||||
foreach (var attachment in AttachmentSprites)
|
||||
{
|
||||
DrawAttachmentSprite(spriteBatch, attachment, Portrait, screenPos + offset, scale, depthStep, flip ? SpriteEffects.FlipHorizontally : SpriteEffects.None);
|
||||
depthStep += depthStep;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawIcon(SpriteBatch spriteBatch, Vector2 screenPos, Vector2 targetAreaSize)
|
||||
{
|
||||
var headSprite = HeadSprite;
|
||||
if (headSprite != null)
|
||||
{
|
||||
float scale = Math.Min(targetAreaSize.X / headSprite.size.X, targetAreaSize.Y / headSprite.size.Y);
|
||||
if (Head.SheetIndex.HasValue)
|
||||
{
|
||||
headSprite.SourceRect = new Rectangle(CalculateOffset(headSprite, Head.SheetIndex.Value.ToPoint()), headSprite.SourceRect.Size);
|
||||
}
|
||||
headSprite.Draw(spriteBatch, screenPos, scale: scale);
|
||||
if (AttachmentSprites != null)
|
||||
{
|
||||
float depthStep = 0.000001f;
|
||||
foreach (var attachment in AttachmentSprites)
|
||||
{
|
||||
DrawAttachmentSprite(spriteBatch, attachment, headSprite, screenPos, scale, depthStep);
|
||||
depthStep += depthStep;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawJobIcon(SpriteBatch spriteBatch, Vector2 pos, float scale = 1.0f)
|
||||
{
|
||||
var icon = Job?.Prefab?.Icon;
|
||||
if (icon == null) { return; }
|
||||
icon.Draw(spriteBatch, pos, Job.Prefab.UIColor, scale: scale);
|
||||
}
|
||||
public void DrawJobIcon(SpriteBatch spriteBatch, Rectangle area)
|
||||
{
|
||||
var icon = Job?.Prefab?.Icon;
|
||||
if (icon == null) { return; }
|
||||
icon.Draw(spriteBatch,
|
||||
area.Center.ToVector2(),
|
||||
Job.Prefab.UIColor,
|
||||
scale: Math.Min(area.Width / (float)icon.SourceRect.Width, area.Height / (float)icon.SourceRect.Height));
|
||||
}
|
||||
|
||||
private void DrawAttachmentSprite(SpriteBatch spriteBatch, WearableSprite attachment, Sprite head, Vector2 drawPos, float scale, float depthStep, SpriteEffects spriteEffects = SpriteEffects.None)
|
||||
{
|
||||
if (attachment.InheritSourceRect)
|
||||
{
|
||||
if (attachment.SheetIndex.HasValue)
|
||||
{
|
||||
attachment.Sprite.SourceRect = new Rectangle(CalculateOffset(head, attachment.SheetIndex.Value), head.SourceRect.Size);
|
||||
}
|
||||
else if (Head.SheetIndex.HasValue)
|
||||
{
|
||||
attachment.Sprite.SourceRect = new Rectangle(CalculateOffset(head, Head.SheetIndex.Value.ToPoint()), head.SourceRect.Size);
|
||||
}
|
||||
else
|
||||
{
|
||||
attachment.Sprite.SourceRect = head.SourceRect;
|
||||
}
|
||||
}
|
||||
Vector2 origin = attachment.Sprite.Origin;
|
||||
if (attachment.InheritOrigin)
|
||||
{
|
||||
origin = head.Origin;
|
||||
attachment.Sprite.Origin = origin;
|
||||
}
|
||||
else
|
||||
{
|
||||
origin = attachment.Sprite.Origin;
|
||||
}
|
||||
float depth = attachment.Sprite.Depth;
|
||||
if (attachment.InheritLimbDepth)
|
||||
{
|
||||
depth = head.Depth - depthStep;
|
||||
}
|
||||
attachment.Sprite.Draw(spriteBatch, drawPos, Color.White, origin, rotate: 0, scale: scale, depth: depth, spriteEffect: spriteEffects);
|
||||
}
|
||||
|
||||
public static CharacterInfo ClientRead(string speciesName, IReadMessage inc)
|
||||
{
|
||||
ushort infoID = inc.ReadUInt16();
|
||||
string newName = inc.ReadString();
|
||||
int gender = inc.ReadByte();
|
||||
int race = inc.ReadByte();
|
||||
int headSpriteID = inc.ReadByte();
|
||||
int hairIndex = inc.ReadByte();
|
||||
int beardIndex = inc.ReadByte();
|
||||
int moustacheIndex = inc.ReadByte();
|
||||
int faceAttachmentIndex = inc.ReadByte();
|
||||
string ragdollFile = inc.ReadString();
|
||||
|
||||
string jobIdentifier = inc.ReadString();
|
||||
int variant = inc.ReadByte();
|
||||
|
||||
JobPrefab jobPrefab = null;
|
||||
Dictionary<string, float> skillLevels = new Dictionary<string, float>();
|
||||
if (!string.IsNullOrEmpty(jobIdentifier))
|
||||
{
|
||||
jobPrefab = JobPrefab.Get(jobIdentifier);
|
||||
byte skillCount = inc.ReadByte();
|
||||
for (int i = 0; i < skillCount; i++)
|
||||
{
|
||||
string skillIdentifier = inc.ReadString();
|
||||
float skillLevel = inc.ReadSingle();
|
||||
skillLevels.Add(skillIdentifier, skillLevel);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: animations
|
||||
CharacterInfo ch = new CharacterInfo(speciesName, newName, jobPrefab, ragdollFile, variant)
|
||||
{
|
||||
ID = infoID,
|
||||
};
|
||||
ch.RecreateHead(headSpriteID,(Race)race, (Gender)gender, hairIndex, beardIndex, moustacheIndex, faceAttachmentIndex);
|
||||
if (ch.Job != null)
|
||||
{
|
||||
foreach (KeyValuePair<string, float> skill in skillLevels)
|
||||
{
|
||||
Skill matchingSkill = ch.Job.Skills.Find(s => s.Identifier == skill.Key);
|
||||
if (matchingSkill == null)
|
||||
{
|
||||
ch.Job.Skills.Add(new Skill(skill.Key, skill.Value));
|
||||
continue;
|
||||
}
|
||||
matchingSkill.Level = skill.Value;
|
||||
}
|
||||
ch.Job.Skills.RemoveAll(s => !skillLevels.ContainsKey(s.Identifier));
|
||||
}
|
||||
return ch;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,507 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class Character
|
||||
{
|
||||
partial void UpdateNetInput()
|
||||
{
|
||||
if (GameMain.Client != null)
|
||||
{
|
||||
if (this != Controlled)
|
||||
{
|
||||
if (GameMain.Client.EndCinematic != null) // Freezes the characters during the ending cinematic
|
||||
{
|
||||
AnimController.Frozen = true;
|
||||
memState.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
//freeze AI characters if more than x seconds have passed since last update from the server
|
||||
if (lastRecvPositionUpdateTime < Lidgren.Network.NetTime.Now - NetConfig.FreezeCharacterIfPositionDataMissingDelay)
|
||||
{
|
||||
AnimController.Frozen = true;
|
||||
memState.Clear();
|
||||
//hide after y seconds
|
||||
if (lastRecvPositionUpdateTime < Lidgren.Network.NetTime.Now - NetConfig.DisableCharacterIfPositionDataMissingDelay)
|
||||
{
|
||||
Enabled = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var posInfo = new CharacterStateInfo(
|
||||
SimPosition,
|
||||
AnimController.Collider.Rotation,
|
||||
LastNetworkUpdateID,
|
||||
AnimController.TargetDir,
|
||||
SelectedCharacter,
|
||||
SelectedConstruction,
|
||||
AnimController.Anim);
|
||||
|
||||
memLocalState.Add(posInfo);
|
||||
|
||||
InputNetFlags newInput = InputNetFlags.None;
|
||||
if (IsKeyDown(InputType.Left)) newInput |= InputNetFlags.Left;
|
||||
if (IsKeyDown(InputType.Right)) newInput |= InputNetFlags.Right;
|
||||
if (IsKeyDown(InputType.Up)) newInput |= InputNetFlags.Up;
|
||||
if (IsKeyDown(InputType.Down)) newInput |= InputNetFlags.Down;
|
||||
if (IsKeyDown(InputType.Run)) newInput |= InputNetFlags.Run;
|
||||
if (IsKeyDown(InputType.Crouch)) newInput |= InputNetFlags.Crouch;
|
||||
if (IsKeyHit(InputType.Select)) newInput |= InputNetFlags.Select; //TODO: clean up the way this input is registered
|
||||
if (IsKeyHit(InputType.Deselect)) newInput |= InputNetFlags.Deselect;
|
||||
if (IsKeyHit(InputType.Health)) newInput |= InputNetFlags.Health;
|
||||
if (IsKeyHit(InputType.Grab)) newInput |= InputNetFlags.Grab;
|
||||
if (IsKeyDown(InputType.Use)) newInput |= InputNetFlags.Use;
|
||||
if (IsKeyDown(InputType.Aim)) newInput |= InputNetFlags.Aim;
|
||||
if (IsKeyDown(InputType.Shoot)) newInput |= InputNetFlags.Shoot;
|
||||
if (IsKeyDown(InputType.Attack)) newInput |= InputNetFlags.Attack;
|
||||
if (IsKeyDown(InputType.Ragdoll)) newInput |= InputNetFlags.Ragdoll;
|
||||
|
||||
if (AnimController.TargetDir == Direction.Left) newInput |= InputNetFlags.FacingLeft;
|
||||
|
||||
Vector2 relativeCursorPos = cursorPosition - AimRefPosition;
|
||||
relativeCursorPos.Normalize();
|
||||
UInt16 intAngle = (UInt16)(65535.0 * Math.Atan2(relativeCursorPos.Y, relativeCursorPos.X) / (2.0 * Math.PI));
|
||||
|
||||
NetInputMem newMem = new NetInputMem
|
||||
{
|
||||
states = newInput,
|
||||
intAim = intAngle
|
||||
};
|
||||
if (focusedItem != null && !CharacterInventory.DraggingItemToWorld &&
|
||||
(!newMem.states.HasFlag(InputNetFlags.Grab) && !newMem.states.HasFlag(InputNetFlags.Health)))
|
||||
{
|
||||
newMem.interact = focusedItem.ID;
|
||||
}
|
||||
else if (FocusedCharacter != null)
|
||||
{
|
||||
newMem.interact = FocusedCharacter.ID;
|
||||
}
|
||||
|
||||
memInput.Insert(0, newMem);
|
||||
LastNetworkUpdateID++;
|
||||
if (memInput.Count > 60)
|
||||
{
|
||||
memInput.RemoveRange(60, memInput.Count - 60);
|
||||
}
|
||||
}
|
||||
}
|
||||
else //this == Character.Controlled && GameMain.Client == null
|
||||
{
|
||||
AnimController.Frozen = false;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void ClientWrite(IWriteMessage msg, object[] extraData = null)
|
||||
{
|
||||
if (extraData != null)
|
||||
{
|
||||
switch ((NetEntityEvent.Type)extraData[0])
|
||||
{
|
||||
case NetEntityEvent.Type.InventoryState:
|
||||
msg.WriteRangedInteger(0, 0, 3);
|
||||
Inventory.ClientWrite(msg, extraData);
|
||||
break;
|
||||
case NetEntityEvent.Type.Treatment:
|
||||
msg.WriteRangedInteger(1, 0, 3);
|
||||
msg.Write(AnimController.Anim == AnimController.Animation.CPR);
|
||||
break;
|
||||
case NetEntityEvent.Type.Status:
|
||||
msg.WriteRangedInteger(2, 0, 3);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
msg.Write((byte)ClientNetObject.CHARACTER_INPUT);
|
||||
|
||||
if (memInput.Count > 60)
|
||||
{
|
||||
memInput.RemoveRange(60, memInput.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.WriteRangedInteger((int)memInput[i].states, 0, (int)InputNetFlags.MaxVal);
|
||||
msg.Write(memInput[i].intAim);
|
||||
if (memInput[i].states.HasFlag(InputNetFlags.Select) ||
|
||||
memInput[i].states.HasFlag(InputNetFlags.Deselect) ||
|
||||
memInput[i].states.HasFlag(InputNetFlags.Use) ||
|
||||
memInput[i].states.HasFlag(InputNetFlags.Health) ||
|
||||
memInput[i].states.HasFlag(InputNetFlags.Grab))
|
||||
{
|
||||
msg.Write(memInput[i].interact);
|
||||
}
|
||||
}
|
||||
}
|
||||
msg.WritePadBits();
|
||||
}
|
||||
|
||||
public virtual void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case ServerNetObject.ENTITY_POSITION:
|
||||
bool facingRight = AnimController.Dir > 0.0f;
|
||||
|
||||
lastRecvPositionUpdateTime = (float)Lidgren.Network.NetTime.Now;
|
||||
|
||||
AnimController.Frozen = false;
|
||||
Enabled = true;
|
||||
|
||||
UInt16 networkUpdateID = 0;
|
||||
if (msg.ReadBoolean())
|
||||
{
|
||||
networkUpdateID = msg.ReadUInt16();
|
||||
}
|
||||
else
|
||||
{
|
||||
bool aimInput = msg.ReadBoolean();
|
||||
keys[(int)InputType.Aim].Held = aimInput;
|
||||
keys[(int)InputType.Aim].SetState(false, aimInput);
|
||||
|
||||
bool shootInput = msg.ReadBoolean();
|
||||
keys[(int)InputType.Shoot].Held = shootInput;
|
||||
keys[(int)InputType.Shoot].SetState(false, shootInput);
|
||||
|
||||
bool useInput = msg.ReadBoolean();
|
||||
keys[(int)InputType.Use].Held = useInput;
|
||||
keys[(int)InputType.Use].SetState(false, useInput);
|
||||
|
||||
if (AnimController is HumanoidAnimController)
|
||||
{
|
||||
bool crouching = msg.ReadBoolean();
|
||||
keys[(int)InputType.Crouch].Held = crouching;
|
||||
keys[(int)InputType.Crouch].SetState(false, crouching);
|
||||
}
|
||||
|
||||
bool attackInput = msg.ReadBoolean();
|
||||
keys[(int)InputType.Attack].Held = attackInput;
|
||||
keys[(int)InputType.Attack].SetState(false, attackInput);
|
||||
|
||||
double aimAngle = msg.ReadUInt16() / 65535.0 * 2.0 * Math.PI;
|
||||
cursorPosition = AimRefPosition + new Vector2((float)Math.Cos(aimAngle), (float)Math.Sin(aimAngle)) * 500.0f;
|
||||
TransformCursorPos();
|
||||
|
||||
bool ragdollInput = msg.ReadBoolean();
|
||||
keys[(int)InputType.Ragdoll].Held = ragdollInput;
|
||||
keys[(int)InputType.Ragdoll].SetState(false, ragdollInput);
|
||||
|
||||
facingRight = msg.ReadBoolean();
|
||||
}
|
||||
|
||||
bool entitySelected = msg.ReadBoolean();
|
||||
Character selectedCharacter = null;
|
||||
Item selectedItem = null;
|
||||
|
||||
AnimController.Animation animation = AnimController.Animation.None;
|
||||
if (entitySelected)
|
||||
{
|
||||
ushort characterID = msg.ReadUInt16();
|
||||
ushort itemID = msg.ReadUInt16();
|
||||
selectedCharacter = FindEntityByID(characterID) as Character;
|
||||
selectedItem = FindEntityByID(itemID) as Item;
|
||||
if (characterID != NullEntityID)
|
||||
{
|
||||
bool doingCpr = msg.ReadBoolean();
|
||||
if (doingCpr && SelectedCharacter != null)
|
||||
{
|
||||
animation = AnimController.Animation.CPR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vector2 pos = new Vector2(
|
||||
msg.ReadSingle(),
|
||||
msg.ReadSingle());
|
||||
float MaxVel = NetConfig.MaxPhysicsBodyVelocity;
|
||||
Vector2 linearVelocity = new Vector2(
|
||||
msg.ReadRangedSingle(-MaxVel, MaxVel, 12),
|
||||
msg.ReadRangedSingle(-MaxVel, MaxVel, 12));
|
||||
linearVelocity = NetConfig.Quantize(linearVelocity, -MaxVel, MaxVel, 12);
|
||||
|
||||
bool fixedRotation = msg.ReadBoolean();
|
||||
float? rotation = null;
|
||||
float? angularVelocity = null;
|
||||
if (!fixedRotation)
|
||||
{
|
||||
rotation = msg.ReadSingle();
|
||||
float MaxAngularVel = NetConfig.MaxPhysicsBodyAngularVelocity;
|
||||
angularVelocity = msg.ReadRangedSingle(-MaxAngularVel, MaxAngularVel, 8);
|
||||
angularVelocity = NetConfig.Quantize(angularVelocity.Value, -MaxAngularVel, MaxAngularVel, 8);
|
||||
}
|
||||
|
||||
bool readStatus = msg.ReadBoolean();
|
||||
if (readStatus)
|
||||
{
|
||||
ReadStatus(msg);
|
||||
}
|
||||
|
||||
msg.ReadPadBits();
|
||||
|
||||
int index = 0;
|
||||
if (GameMain.Client.Character == this && CanMove)
|
||||
{
|
||||
var posInfo = new CharacterStateInfo(
|
||||
pos, rotation,
|
||||
networkUpdateID,
|
||||
facingRight ? Direction.Right : Direction.Left,
|
||||
selectedCharacter, selectedItem, animation);
|
||||
|
||||
while (index < memState.Count && NetIdUtils.IdMoreRecent(posInfo.ID, memState[index].ID))
|
||||
index++;
|
||||
memState.Insert(index, posInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
var posInfo = new CharacterStateInfo(
|
||||
pos, rotation,
|
||||
linearVelocity, angularVelocity,
|
||||
sendingTime, facingRight ? Direction.Right : Direction.Left,
|
||||
selectedCharacter, selectedItem, animation);
|
||||
|
||||
while (index < memState.Count && posInfo.Timestamp > memState[index].Timestamp)
|
||||
index++;
|
||||
memState.Insert(index, posInfo);
|
||||
}
|
||||
|
||||
break;
|
||||
case ServerNetObject.ENTITY_EVENT:
|
||||
|
||||
int eventType = msg.ReadRangedInteger(0, 3);
|
||||
switch (eventType)
|
||||
{
|
||||
case 0:
|
||||
if (Inventory == null)
|
||||
{
|
||||
string errorMsg = "Received an inventory update message for an entity with no inventory (" + Name + ", removed: " + Removed + ")";
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
GameAnalyticsManager.AddErrorEventOnce("CharacterNetworking.ClientRead:NoInventory" + ID, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
|
||||
|
||||
//read anyway to prevent messing up reading the rest of the message
|
||||
UInt16 lastEventID = msg.ReadUInt16();
|
||||
byte itemCount = msg.ReadByte();
|
||||
for (int i = 0; i < itemCount; i++)
|
||||
{
|
||||
msg.ReadUInt16();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Inventory.ClientRead(type, msg, sendingTime);
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
byte ownerID = msg.ReadByte();
|
||||
ResetNetState();
|
||||
if (ownerID == GameMain.Client.ID)
|
||||
{
|
||||
if (controlled != null)
|
||||
{
|
||||
LastNetworkUpdateID = controlled.LastNetworkUpdateID;
|
||||
}
|
||||
|
||||
if (!IsDead) { Controlled = this; }
|
||||
IsRemotePlayer = false;
|
||||
GameMain.Client.HasSpawned = true;
|
||||
GameMain.Client.Character = this;
|
||||
GameMain.LightManager.LosEnabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (controlled == this)
|
||||
{
|
||||
Controlled = null;
|
||||
IsRemotePlayer = ownerID > 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
ReadStatus(msg);
|
||||
break;
|
||||
case 3:
|
||||
int skillCount = msg.ReadByte();
|
||||
for (int i = 0; i < skillCount; i++)
|
||||
{
|
||||
string skillIdentifier = msg.ReadString();
|
||||
float skillLevel = msg.ReadSingle();
|
||||
info?.SetSkillLevel(skillIdentifier, skillLevel, WorldPosition + Vector2.UnitY * 150.0f);
|
||||
}
|
||||
break;
|
||||
}
|
||||
msg.ReadPadBits();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static Character ReadSpawnData(IReadMessage inc)
|
||||
{
|
||||
DebugConsole.Log("Reading character spawn data");
|
||||
|
||||
if (GameMain.Client == null) return null;
|
||||
|
||||
bool noInfo = inc.ReadBoolean();
|
||||
ushort id = inc.ReadUInt16();
|
||||
string speciesName = inc.ReadString();
|
||||
string seed = inc.ReadString();
|
||||
|
||||
Vector2 position = new Vector2(inc.ReadSingle(), inc.ReadSingle());
|
||||
|
||||
bool enabled = inc.ReadBoolean();
|
||||
|
||||
DebugConsole.Log("Received spawn data for " + speciesName);
|
||||
|
||||
Character character = null;
|
||||
if (noInfo)
|
||||
{
|
||||
character = Create(speciesName, position, seed, null, true);
|
||||
character.ID = id;
|
||||
bool containsStatusData = inc.ReadBoolean();
|
||||
if (containsStatusData)
|
||||
{
|
||||
character.ReadStatus(inc);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bool hasOwner = inc.ReadBoolean();
|
||||
int ownerId = hasOwner ? inc.ReadByte() : -1;
|
||||
byte teamID = inc.ReadByte();
|
||||
bool hasAi = inc.ReadBoolean();
|
||||
string infoSpeciesName = inc.ReadString();
|
||||
|
||||
CharacterInfo info = CharacterInfo.ClientRead(infoSpeciesName, inc);
|
||||
|
||||
character = Create(speciesName, position, seed, info, GameMain.Client.ID != ownerId, hasAi);
|
||||
character.ID = id;
|
||||
character.TeamID = (TeamType)teamID;
|
||||
|
||||
// Check if the character has a current order
|
||||
if (inc.ReadBoolean())
|
||||
{
|
||||
int orderPrefabIndex = inc.ReadByte();
|
||||
Entity targetEntity = FindEntityByID(inc.ReadUInt16());
|
||||
Character orderGiver = inc.ReadBoolean() ? FindEntityByID(inc.ReadUInt16()) as Character : null;
|
||||
int orderOptionIndex = inc.ReadByte();
|
||||
|
||||
if (orderPrefabIndex >= 0 && orderPrefabIndex < Order.PrefabList.Count)
|
||||
{
|
||||
var orderPrefab = Order.PrefabList[orderPrefabIndex];
|
||||
if ((orderPrefab.ItemComponentType == null && orderPrefab.ItemIdentifiers.None()) ||
|
||||
(targetEntity != null && (targetEntity as Item).Components.Any(c => c?.GetType() == orderPrefab.ItemComponentType)))
|
||||
{
|
||||
character.SetOrder(
|
||||
new Order(orderPrefab, targetEntity, (targetEntity as Item)?.Components.FirstOrDefault(c => c?.GetType() == orderPrefab.ItemComponentType), orderGiver: orderGiver),
|
||||
orderOptionIndex >= 0 && orderOptionIndex < orderPrefab.Options.Length ? orderPrefab.Options[orderOptionIndex] : null,
|
||||
orderGiver, speak: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError("Could not set order \"" + orderPrefab.Identifier + "\" for character \"" + character.Name + "\" because required target entity was not found.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError("Invalid order prefab index - index (" + orderPrefabIndex + ") out of bounds.");
|
||||
}
|
||||
}
|
||||
|
||||
bool containsStatusData = inc.ReadBoolean();
|
||||
if (containsStatusData)
|
||||
{
|
||||
character.ReadStatus(inc);
|
||||
}
|
||||
|
||||
if (character.IsHuman && character.TeamID != TeamType.FriendlyNPC && !character.IsDead)
|
||||
{
|
||||
CharacterInfo duplicateCharacterInfo = GameMain.GameSession.CrewManager.GetCharacterInfos().FirstOrDefault(c => c.ID == info.ID);
|
||||
GameMain.GameSession.CrewManager.RemoveCharacterInfo(duplicateCharacterInfo);
|
||||
GameMain.GameSession.CrewManager.AddCharacter(character);
|
||||
}
|
||||
|
||||
if (GameMain.Client.ID == ownerId)
|
||||
{
|
||||
GameMain.Client.HasSpawned = true;
|
||||
GameMain.Client.Character = character;
|
||||
if (!character.IsDead) { Controlled = character; }
|
||||
|
||||
GameMain.LightManager.LosEnabled = true;
|
||||
|
||||
character.memInput.Clear();
|
||||
character.memState.Clear();
|
||||
character.memLocalState.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
character.Enabled = Controlled == character || enabled;
|
||||
|
||||
return character;
|
||||
}
|
||||
|
||||
private void ReadStatus(IReadMessage msg)
|
||||
{
|
||||
bool isDead = msg.ReadBoolean();
|
||||
if (isDead)
|
||||
{
|
||||
CauseOfDeathType causeOfDeathType = (CauseOfDeathType)msg.ReadRangedInteger(0, Enum.GetValues(typeof(CauseOfDeathType)).Length - 1);
|
||||
AfflictionPrefab causeOfDeathAffliction = null;
|
||||
if (causeOfDeathType == CauseOfDeathType.Affliction)
|
||||
{
|
||||
string afflictionName = msg.ReadString();
|
||||
if (!AfflictionPrefab.Prefabs.ContainsKey(afflictionName))
|
||||
{
|
||||
string errorMsg = $"Error in CharacterNetworking.ReadStatus: affliction not found ({afflictionName})";
|
||||
causeOfDeathType = CauseOfDeathType.Unknown;
|
||||
GameAnalyticsManager.AddErrorEventOnce("CharacterNetworking.ReadStatus:AfflictionIndexOutOfBounts", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
|
||||
}
|
||||
else
|
||||
{
|
||||
causeOfDeathAffliction = AfflictionPrefab.Prefabs[afflictionName];
|
||||
}
|
||||
}
|
||||
|
||||
byte severedLimbCount = msg.ReadByte();
|
||||
if (!IsDead)
|
||||
{
|
||||
if (causeOfDeathType == CauseOfDeathType.Pressure)
|
||||
{
|
||||
Implode(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
Kill(causeOfDeathType, causeOfDeathAffliction?.Instantiate(1.0f), true);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < severedLimbCount; i++)
|
||||
{
|
||||
int severedJointIndex = msg.ReadByte();
|
||||
if (severedJointIndex < 0 || severedJointIndex >= AnimController.LimbJoints.Length)
|
||||
{
|
||||
string errorMsg = $"Error in CharacterNetworking.ReadStatus: severed joint index out of bounds (index: {severedJointIndex}, joint count: {AnimController.LimbJoints.Length})";
|
||||
GameAnalyticsManager.AddErrorEventOnce("CharacterNetworking.ReadStatus:JointIndexOutOfBounts", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
|
||||
}
|
||||
else
|
||||
{
|
||||
AnimController.SeverLimbJoint(AnimController.LimbJoints[severedJointIndex]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (IsDead) { Revive(); }
|
||||
CharacterHealth.ClientRead(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using Barotrauma.Sounds;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class CharacterSound
|
||||
{
|
||||
public enum SoundType
|
||||
{
|
||||
Idle, Attack, Die, Damage
|
||||
}
|
||||
|
||||
private readonly RoundSound roundSound;
|
||||
public readonly CharacterParams.SoundParams Params;
|
||||
|
||||
public SoundType Type => Params.State;
|
||||
public Gender Gender => Params.Gender;
|
||||
public float Volume => roundSound == null ? 0.0f : roundSound.Volume;
|
||||
public float Range => roundSound == null ? 0.0f : roundSound.Range;
|
||||
public Sound Sound => roundSound?.Sound;
|
||||
|
||||
public CharacterSound(CharacterParams.SoundParams soundParams)
|
||||
{
|
||||
Params = soundParams;
|
||||
roundSound = Submarine.LoadRoundSound(soundParams.Element);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class HUDProgressBar
|
||||
{
|
||||
private float progress;
|
||||
|
||||
public float Progress
|
||||
{
|
||||
get { return progress; }
|
||||
set { progress = MathHelper.Clamp(value, 0.0f, 1.0f); }
|
||||
}
|
||||
|
||||
public float FadeTimer;
|
||||
|
||||
private Color fullColor, emptyColor;
|
||||
|
||||
private Vector2 worldPosition;
|
||||
|
||||
public Vector2 WorldPosition
|
||||
{
|
||||
get
|
||||
{
|
||||
return worldPosition;
|
||||
}
|
||||
set
|
||||
{
|
||||
worldPosition = value;
|
||||
if (parentSub != null)
|
||||
{
|
||||
worldPosition -= parentSub.DrawPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2 Size;
|
||||
|
||||
private Submarine parentSub;
|
||||
|
||||
public HUDProgressBar(Vector2 worldPosition, Submarine parentSubmarine = null)
|
||||
: this(worldPosition, parentSubmarine, GUI.Style.Red, GUI.Style.Green)
|
||||
{
|
||||
}
|
||||
|
||||
public HUDProgressBar(Vector2 worldPosition, Submarine parentSubmarine, Color emptyColor, Color fullColor)
|
||||
{
|
||||
this.emptyColor = emptyColor;
|
||||
this.fullColor = fullColor;
|
||||
|
||||
parentSub = parentSubmarine;
|
||||
|
||||
WorldPosition = worldPosition;
|
||||
|
||||
Size = new Vector2(100.0f, 20.0f);
|
||||
|
||||
FadeTimer = 1.0f;
|
||||
}
|
||||
|
||||
public void Update(float deltatime)
|
||||
{
|
||||
FadeTimer -= deltatime;
|
||||
}
|
||||
|
||||
public void Draw(SpriteBatch spriteBatch, Camera cam)
|
||||
{
|
||||
float a = Math.Min(FadeTimer, 1.0f);
|
||||
|
||||
Vector2 pos = new Vector2(WorldPosition.X - Size.X / 2, WorldPosition.Y + Size.Y / 2);
|
||||
|
||||
if (parentSub != null)
|
||||
{
|
||||
pos += parentSub.DrawPosition;
|
||||
}
|
||||
|
||||
pos = cam.WorldToScreen(pos);
|
||||
|
||||
GUI.DrawProgressBar(spriteBatch,
|
||||
new Vector2(pos.X, -pos.Y),
|
||||
Size, progress,
|
||||
Color.Lerp(emptyColor, fullColor, progress) * a,
|
||||
Color.White * a * 0.8f);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class AfflictionHusk : Affliction
|
||||
{
|
||||
partial void UpdateMessages(float prevStrength, Character character)
|
||||
{
|
||||
if (Strength < Prefab.MaxStrength * 0.5f)
|
||||
{
|
||||
if (prevStrength % 10.0f > 0.05f && Strength % 10.0f < 0.05f)
|
||||
{
|
||||
GUI.AddMessage(TextManager.Get("HuskDormant"), GUI.Style.Red);
|
||||
}
|
||||
}
|
||||
else if (Strength < Prefab.MaxStrength)
|
||||
{
|
||||
if (state == InfectionState.Dormant && Character.Controlled == character)
|
||||
{
|
||||
GUI.AddMessage(TextManager.Get("HuskCantSpeak"), GUI.Style.Red);
|
||||
}
|
||||
}
|
||||
else if (state != InfectionState.Active && Character.Controlled == character)
|
||||
{
|
||||
GUI.AddMessage(TextManager.GetWithVariable("HuskActivate", "[Attack]", GameMain.Config.KeyBindText(InputType.Attack)),
|
||||
GUI.Style.Red);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class AfflictionPsychosis : Affliction
|
||||
{
|
||||
class FakeFireSource
|
||||
{
|
||||
public Vector2 Size;
|
||||
public Vector2 Position;
|
||||
public Hull Hull;
|
||||
|
||||
public float LifeTime;
|
||||
}
|
||||
|
||||
const int MaxFakeFireSources = 10;
|
||||
private float minFakeFireSourceInterval = 10.0f, maxFakeFireSourceInterval = 200.0f;
|
||||
private float createFireSourceTimer;
|
||||
private List<FakeFireSource> fakeFireSources = new List<FakeFireSource>();
|
||||
|
||||
enum FloodType
|
||||
{
|
||||
Minor,
|
||||
Major,
|
||||
HideFlooding
|
||||
}
|
||||
|
||||
private float minSoundInterval = 10.0f, maxSoundInterval = 60.0f;
|
||||
private FloodType currentFloodType;
|
||||
private float soundTimer;
|
||||
|
||||
private float minFloodInterval = 30.0f, maxFloodInterval = 180.0f;
|
||||
private float createFloodTimer;
|
||||
private float currentFloodState;
|
||||
private float currentFloodDuration;
|
||||
|
||||
partial void UpdateProjSpecific(CharacterHealth characterHealth, Limb targetLimb, float deltaTime)
|
||||
{
|
||||
if (Character.Controlled != characterHealth.Character) return;
|
||||
UpdateFloods(deltaTime);
|
||||
UpdateSounds(characterHealth.Character, deltaTime);
|
||||
UpdateFires(characterHealth.Character, deltaTime);
|
||||
}
|
||||
|
||||
private void UpdateSounds(Character character, float deltaTime)
|
||||
{
|
||||
if (soundTimer < MathHelper.Lerp(maxSoundInterval, minSoundInterval, Strength / 100.0f))
|
||||
{
|
||||
soundTimer += deltaTime;
|
||||
return;
|
||||
}
|
||||
|
||||
float impactStrength = MathHelper.Lerp(0.1f, 1.0f, Strength / 100.0f);
|
||||
SoundPlayer.PlayDamageSound("StructureBlunt", Rand.Range(10.0f, 1000.0f), character.WorldPosition + Rand.Vector(500.0f));
|
||||
GameMain.GameScreen.Cam.Shake = impactStrength * 10.0f;
|
||||
GameMain.GameScreen.Cam.AngularVelocity = Rand.Range(-impactStrength, impactStrength);
|
||||
soundTimer = 0.0f;
|
||||
}
|
||||
|
||||
private void UpdateFloods(float deltaTime)
|
||||
{
|
||||
if (currentFloodDuration > 0.0f)
|
||||
{
|
||||
currentFloodDuration -= deltaTime;
|
||||
switch (currentFloodType)
|
||||
{
|
||||
case FloodType.Minor:
|
||||
currentFloodState += deltaTime;
|
||||
//lerp the water surface in all hulls 50 units above the floor within 10 seconds
|
||||
foreach (Hull hull in Hull.hullList)
|
||||
{
|
||||
hull.DrawSurface = hull.Rect.Y - hull.Rect.Height + MathHelper.Lerp(0.0f, 50.0f, currentFloodState / 10.0f);
|
||||
}
|
||||
break;
|
||||
case FloodType.Major:
|
||||
currentFloodState += deltaTime;
|
||||
//create a full flood in 10 seconds
|
||||
foreach (Hull hull in Hull.hullList)
|
||||
{
|
||||
hull.DrawSurface = hull.Rect.Y - MathHelper.Lerp(hull.Rect.Height, 0.0f, currentFloodState / 10.0f);
|
||||
}
|
||||
break;
|
||||
case FloodType.HideFlooding:
|
||||
//hide water inside hulls (the player can't see which hulls are flooded)
|
||||
foreach (Hull hull in Hull.hullList)
|
||||
{
|
||||
hull.DrawSurface = hull.Rect.Y - hull.Rect.Height;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (createFloodTimer < MathHelper.Lerp(maxFloodInterval, minFloodInterval, Strength / 100.0f))
|
||||
{
|
||||
createFloodTimer += deltaTime;
|
||||
return;
|
||||
}
|
||||
|
||||
//probability of a fake flood goes from 0%-100%
|
||||
if (Rand.Range(0.0f, 100.0f) < Strength)
|
||||
{
|
||||
if (Rand.Range(0.0f, 1.0f) < 0.5f)
|
||||
{
|
||||
currentFloodType = FloodType.HideFlooding;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentFloodType = Strength < 50.0f ? FloodType.Minor : FloodType.Major;
|
||||
}
|
||||
currentFloodDuration = Rand.Range(20.0f, 100.0f);
|
||||
}
|
||||
createFloodTimer = 0.0f;
|
||||
}
|
||||
|
||||
private void UpdateFires(Character character, float deltaTime)
|
||||
{
|
||||
createFireSourceTimer += deltaTime;
|
||||
if (fakeFireSources.Count < MaxFakeFireSources &&
|
||||
character.Submarine != null &&
|
||||
createFireSourceTimer > MathHelper.Lerp(maxFakeFireSourceInterval, minFakeFireSourceInterval, Strength / 100.0f))
|
||||
{
|
||||
Hull fireHull = Hull.hullList.GetRandom(h => h.Submarine == character.Submarine);
|
||||
|
||||
fakeFireSources.Add(new FakeFireSource()
|
||||
{
|
||||
Size = Vector2.One * 20.0f,
|
||||
Hull = fireHull,
|
||||
Position = new Vector2(Rand.Range(0.0f, fireHull.Rect.Width), 30.0f),
|
||||
LifeTime = MathHelper.Lerp(10.0f, 100.0f, Strength / 100.0f)
|
||||
});
|
||||
createFireSourceTimer = 0.0f;
|
||||
}
|
||||
|
||||
foreach (FakeFireSource fakeFireSource in fakeFireSources)
|
||||
{
|
||||
if (fakeFireSource.Hull.Surface > fakeFireSource.Hull.Rect.Y - fakeFireSource.Hull.Rect.Height + fakeFireSource.Position.Y)
|
||||
{
|
||||
fakeFireSource.LifeTime -= deltaTime * 10.0f;
|
||||
}
|
||||
|
||||
fakeFireSource.LifeTime -= deltaTime;
|
||||
float growAmount = deltaTime * 5.0f;
|
||||
fakeFireSource.Size.X += growAmount;
|
||||
fakeFireSource.Position.X = MathHelper.Clamp(fakeFireSource.Position.X - growAmount / 2.0f, 0.0f, fakeFireSource.Hull.Rect.Width);
|
||||
fakeFireSource.Position.Y = MathHelper.Clamp(fakeFireSource.Position.Y, 0.0f, fakeFireSource.Hull.Rect.Height);
|
||||
fakeFireSource.Size.X = Math.Min(fakeFireSource.Hull.Rect.Width - fakeFireSource.Position.X, fakeFireSource.Size.X);
|
||||
fakeFireSource.Size.Y = Math.Min(fakeFireSource.Hull.Rect.Height - fakeFireSource.Position.Y, fakeFireSource.Size.Y);
|
||||
|
||||
FireSource.EmitParticles(
|
||||
fakeFireSource.Size,
|
||||
fakeFireSource.Hull.WorldRect.Location.ToVector2() + fakeFireSource.Position - Vector2.UnitY * fakeFireSource.Hull.Rect.Height,
|
||||
fakeFireSource.Hull,
|
||||
0.5f);
|
||||
}
|
||||
|
||||
fakeFireSources.RemoveAll(fs => fs.LifeTime <= 0.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class DamageModifier
|
||||
{
|
||||
[Serialize("", false), Editable]
|
||||
public string DamageSound
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Linq;
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class JobPrefab : IPrefab, IDisposable
|
||||
{
|
||||
public GUIButton CreateInfoFrame(int variant)
|
||||
{
|
||||
int width = 500, height = 400;
|
||||
|
||||
GUIButton backFrame = new GUIButton(new RectTransform(Vector2.One, GUI.Canvas), style: "GUIBackgroundBlocker");
|
||||
GUIFrame frame = new GUIFrame(new RectTransform(new Point(width, height), backFrame.RectTransform, Anchor.Center));
|
||||
GUIFrame paddedFrame = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.9f), frame.RectTransform, Anchor.Center), style: null);
|
||||
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), paddedFrame.RectTransform), Name, font: GUI.LargeFont);
|
||||
|
||||
var descriptionBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.15f) },
|
||||
Description, font: GUI.SmallFont, wrap: true);
|
||||
|
||||
var skillContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 0.5f), paddedFrame.RectTransform)
|
||||
{ RelativeOffset = new Vector2(0.0f, 0.2f + descriptionBlock.RectTransform.RelativeSize.Y) });
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), skillContainer.RectTransform),
|
||||
TextManager.Get("Skills"), font: GUI.LargeFont);
|
||||
foreach (SkillPrefab skill in Skills)
|
||||
{
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), skillContainer.RectTransform),
|
||||
" - " + TextManager.AddPunctuation(':', TextManager.Get("SkillName." + skill.Identifier), (int)skill.LevelRange.X + " - " + (int)skill.LevelRange.Y),
|
||||
font: GUI.SmallFont);
|
||||
}
|
||||
|
||||
/*if (!ItemIdentifiers.TryGetValue(variant, out var itemIdentifiers)) { return backFrame; }
|
||||
var itemContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 0.5f), paddedFrame.RectTransform, Anchor.TopRight)
|
||||
{ RelativeOffset = new Vector2(0.0f, 0.2f + descriptionBlock.RectTransform.RelativeSize.Y) })
|
||||
{
|
||||
Stretch = true
|
||||
};
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), itemContainer.RectTransform),
|
||||
TextManager.Get("Items", fallBackTag: "mapentitycategory.equipment"), font: GUI.LargeFont);
|
||||
foreach (string identifier in itemIdentifiers.Distinct())
|
||||
{
|
||||
if (!(MapEntityPrefab.Find(name: null, identifier: identifier) is ItemPrefab itemPrefab)) { continue; }
|
||||
int count = itemIdentifiers.Count(i => i == identifier);
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), itemContainer.RectTransform),
|
||||
" - " + (count == 1 ? itemPrefab.Name : itemPrefab.Name + " x" + count),
|
||||
font: GUI.SmallFont);
|
||||
}*/
|
||||
|
||||
return backFrame;
|
||||
}
|
||||
|
||||
|
||||
public class OutfitPreview
|
||||
{
|
||||
/// <summary>
|
||||
/// Pair.First = sprite, Pair.Second = draw offset
|
||||
/// </summary>
|
||||
public readonly List<Pair<Sprite, Vector2>> Sprites;
|
||||
public Vector2 Dimensions;
|
||||
|
||||
public OutfitPreview()
|
||||
{
|
||||
Sprites = new List<Pair<Sprite, Vector2>>();
|
||||
Dimensions = Vector2.One;
|
||||
}
|
||||
|
||||
public void AddSprite(Sprite sprite, Vector2 drawOffset)
|
||||
{
|
||||
Sprites.Add(new Pair<Sprite, Vector2>(sprite, drawOffset));
|
||||
}
|
||||
}
|
||||
|
||||
public List<OutfitPreview> GetJobOutfitSprites(Gender gender, bool useInventoryIcon, out Vector2 maxDimensions)
|
||||
{
|
||||
List<OutfitPreview> outfitPreviews = new List<OutfitPreview>();
|
||||
maxDimensions = Vector2.One;
|
||||
|
||||
var equipIdentifiers = Element.GetChildElements("ItemSet").Elements().Where(e => e.GetAttributeBool("outfit", false)).Select(e => e.GetAttributeString("identifier", ""));
|
||||
|
||||
var outfitPrefabs = ItemPrefab.Prefabs.Where(itemPrefab => equipIdentifiers.Contains(itemPrefab.Identifier)).ToList();
|
||||
if (!outfitPrefabs.Any()) { return null; }
|
||||
|
||||
for (int i = 0; i < outfitPrefabs.Count; i++)
|
||||
{
|
||||
var outfitPreview = new OutfitPreview();
|
||||
|
||||
if (!ItemSets.TryGetValue(i, out var itemSetElement)) { continue; }
|
||||
var previewElement = itemSetElement.GetChildElement("PreviewSprites");
|
||||
if (previewElement == null || useInventoryIcon)
|
||||
{
|
||||
if (outfitPrefabs[i] is ItemPrefab prefab && prefab.InventoryIcon != null)
|
||||
{
|
||||
outfitPreview.AddSprite(prefab.InventoryIcon, Vector2.Zero);
|
||||
outfitPreview.Dimensions = prefab.InventoryIcon.SourceRect.Size.ToVector2();
|
||||
maxDimensions.X = MathHelper.Max(maxDimensions.X, outfitPreview.Dimensions.X);
|
||||
maxDimensions.Y = MathHelper.Max(maxDimensions.Y, outfitPreview.Dimensions.Y);
|
||||
}
|
||||
outfitPreviews.Add(outfitPreview);
|
||||
continue;
|
||||
}
|
||||
|
||||
var children = previewElement.Elements().ToList();
|
||||
for (int n = 0; n < children.Count; n++)
|
||||
{
|
||||
XElement spriteElement = children[n];
|
||||
string spriteTexture = spriteElement.GetAttributeString("texture", "").Replace("[GENDER]", (gender == Gender.Female) ? "female" : "male");
|
||||
var sprite = new Sprite(spriteElement, file: spriteTexture);
|
||||
sprite.size = new Vector2(sprite.SourceRect.Width, sprite.SourceRect.Height);
|
||||
outfitPreview.AddSprite(sprite, children[n].GetAttributeVector2("offset", Vector2.Zero));
|
||||
}
|
||||
|
||||
outfitPreview.Dimensions = previewElement.GetAttributeVector2("dims", Vector2.One);
|
||||
maxDimensions.X = MathHelper.Max(maxDimensions.X, outfitPreview.Dimensions.X);
|
||||
maxDimensions.Y = MathHelper.Max(maxDimensions.Y, outfitPreview.Dimensions.Y);
|
||||
|
||||
outfitPreviews.Add(outfitPreview);
|
||||
}
|
||||
|
||||
return outfitPreviews;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,893 @@
|
||||
using Barotrauma.Lights;
|
||||
using Barotrauma.Particles;
|
||||
using Barotrauma.SpriteDeformations;
|
||||
using Barotrauma.Extensions;
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Dynamics.Joints;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using SpriteParams = Barotrauma.RagdollParams.SpriteParams;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class LimbJoint : RevoluteJoint
|
||||
{
|
||||
public void UpdateDeformations(float deltaTime)
|
||||
{
|
||||
float diff = Math.Abs(UpperLimit - LowerLimit);
|
||||
float strength = MathHelper.Lerp(0, 1, MathUtils.InverseLerp(0, MathHelper.Pi, diff));
|
||||
float jointAngle = this.JointAngle * strength;
|
||||
|
||||
JointBendDeformation limbADeformation = LimbA.Deformations.Find(d => d is JointBendDeformation) as JointBendDeformation;
|
||||
JointBendDeformation limbBDeformation = LimbB.Deformations.Find(d => d is JointBendDeformation) as JointBendDeformation;
|
||||
|
||||
if (limbADeformation != null && limbBDeformation != null)
|
||||
{
|
||||
UpdateBend(LimbA, limbADeformation, this.LocalAnchorA, -jointAngle);
|
||||
UpdateBend(LimbB, limbBDeformation, this.LocalAnchorB, jointAngle);
|
||||
}
|
||||
|
||||
void UpdateBend(Limb limb, JointBendDeformation deformation, Vector2 localAnchor, float angle)
|
||||
{
|
||||
deformation.Scale = limb.DeformSprite.Size;
|
||||
|
||||
Vector2 displayAnchor = ConvertUnits.ToDisplayUnits(localAnchor);
|
||||
displayAnchor.Y = -displayAnchor.Y;
|
||||
Vector2 refPos = displayAnchor + limb.DeformSprite.Origin;
|
||||
|
||||
refPos.X /= limb.DeformSprite.Size.X;
|
||||
refPos.Y /= limb.DeformSprite.Size.Y;
|
||||
|
||||
if (Math.Abs(localAnchor.X) > Math.Abs(localAnchor.Y))
|
||||
{
|
||||
if (localAnchor.X > 0.0f)
|
||||
{
|
||||
deformation.BendRightRefPos = refPos;
|
||||
deformation.BendRight = angle;
|
||||
}
|
||||
else
|
||||
{
|
||||
deformation.BendLeftRefPos = refPos;
|
||||
deformation.BendLeft = angle;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (localAnchor.Y > 0.0f)
|
||||
{
|
||||
deformation.BendUpRefPos = refPos;
|
||||
deformation.BendUp = angle;
|
||||
}
|
||||
else
|
||||
{
|
||||
deformation.BendDownRefPos = refPos;
|
||||
deformation.BendDown = angle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
// TODO: move this into the character editor
|
||||
//var mouthPos = ragdoll.GetMouthPosition();
|
||||
//if (mouthPos != null)
|
||||
//{
|
||||
// var pos = ConvertUnits.ToDisplayUnits(mouthPos.Value);
|
||||
// pos.Y = -pos.Y;
|
||||
// ShapeExtensions.DrawPoint(spriteBatch, pos, GUI.Style.Red, size: 5);
|
||||
//}
|
||||
return;
|
||||
// A debug visualisation on the bezier curve between limbs.
|
||||
var start = LimbA.WorldPosition;
|
||||
var end = LimbB.WorldPosition;
|
||||
var jointAPos = ConvertUnits.ToDisplayUnits(LocalAnchorA);
|
||||
var control = start + Vector2.Transform(jointAPos, Matrix.CreateRotationZ(LimbA.Rotation));
|
||||
start.Y = -start.Y;
|
||||
end.Y = -end.Y;
|
||||
control.Y = -control.Y;
|
||||
//GUI.DrawRectangle(spriteBatch, start, Vector2.One * 5, Color.White, true);
|
||||
//GUI.DrawRectangle(spriteBatch, end, Vector2.One * 5, Color.Black, true);
|
||||
//GUI.DrawRectangle(spriteBatch, control, Vector2.One * 5, Color.Black, true);
|
||||
//GUI.DrawLine(spriteBatch, start, end, Color.White);
|
||||
//GUI.DrawLine(spriteBatch, start, control, Color.Black);
|
||||
//GUI.DrawLine(spriteBatch, control, end, Color.Black);
|
||||
GUI.DrawBezierWithDots(spriteBatch, start, end, control, 1000, GUI.Style.Red);
|
||||
}
|
||||
}
|
||||
|
||||
partial class Limb
|
||||
{
|
||||
//minimum duration between hit/attack sounds
|
||||
public const float SoundInterval = 0.4f;
|
||||
public float LastAttackSoundTime, LastImpactSoundTime;
|
||||
|
||||
private float wetTimer;
|
||||
private float dripParticleTimer;
|
||||
|
||||
/// <summary>
|
||||
/// Note that different limbs can share the same deformations.
|
||||
/// Use ragdoll.SpriteDeformations for a collection that cannot have duplicates.
|
||||
/// </summary>
|
||||
public List<SpriteDeformation> Deformations { get; private set; } = new List<SpriteDeformation>();
|
||||
|
||||
public Sprite Sprite { get; protected set; }
|
||||
|
||||
protected DeformableSprite _deformSprite;
|
||||
|
||||
public DeformableSprite DeformSprite
|
||||
{
|
||||
get
|
||||
{
|
||||
var conditionalSprite = ConditionalSprites.FirstOrDefault(c => c.IsActive && c.DeformableSprite != null);
|
||||
if (conditionalSprite != null)
|
||||
{
|
||||
return conditionalSprite.DeformableSprite;
|
||||
}
|
||||
else
|
||||
{
|
||||
return _deformSprite;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<DecorativeSprite> DecorativeSprites { get; private set; } = new List<DecorativeSprite>();
|
||||
|
||||
public Sprite ActiveSprite
|
||||
{
|
||||
get
|
||||
{
|
||||
var conditionalSprite = ConditionalSprites.FirstOrDefault(c => c.IsActive && c.ActiveSprite != null);
|
||||
if (conditionalSprite != null)
|
||||
{
|
||||
return conditionalSprite.ActiveSprite;
|
||||
}
|
||||
else
|
||||
{
|
||||
return _deformSprite != null ? _deformSprite.Sprite : Sprite;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public WearableSprite HuskSprite { get; private set; }
|
||||
public WearableSprite HerpesSprite { get; private set; }
|
||||
|
||||
public void LoadHuskSprite() => HuskSprite = GetWearableSprite(WearableType.Husk);
|
||||
public void LoadHerpesSprite() => HerpesSprite = GetWearableSprite(WearableType.Herpes);
|
||||
|
||||
public float TextureScale => Params.Ragdoll.TextureScale;
|
||||
|
||||
public Sprite DamagedSprite { get; private set; }
|
||||
|
||||
public List<ConditionalSprite> ConditionalSprites { get; private set; } = new List<ConditionalSprite>();
|
||||
private Dictionary<DecorativeSprite, SpriteState> spriteAnimState = new Dictionary<DecorativeSprite, SpriteState>();
|
||||
private Dictionary<int, List<DecorativeSprite>> DecorativeSpriteGroups = new Dictionary<int, List<DecorativeSprite>>();
|
||||
|
||||
class SpriteState
|
||||
{
|
||||
public float RotationState;
|
||||
public float OffsetState;
|
||||
public bool IsActive = true;
|
||||
}
|
||||
|
||||
public Color InitialLightSourceColor
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public float? InitialLightSpriteAlpha
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public LightSource LightSource
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
private float damageOverlayStrength;
|
||||
public float DamageOverlayStrength
|
||||
{
|
||||
get { return damageOverlayStrength; }
|
||||
set { damageOverlayStrength = MathHelper.Clamp(value, 0.0f, 100.0f); }
|
||||
}
|
||||
|
||||
private float burnOverLayStrength;
|
||||
public float BurnOverlayStrength
|
||||
{
|
||||
get { return burnOverLayStrength; }
|
||||
set { burnOverLayStrength = MathHelper.Clamp(value, 0.0f, 100.0f); }
|
||||
}
|
||||
|
||||
public string HitSoundTag => Params?.Sound?.Tag;
|
||||
|
||||
private List<WearableSprite> wearableTypeHidingSprites = new List<WearableSprite>();
|
||||
private List<WearableType> wearableTypesToHide = new List<WearableType>();
|
||||
private bool enableHuskSprite;
|
||||
public bool EnableHuskSprite
|
||||
{
|
||||
get
|
||||
{
|
||||
return enableHuskSprite;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (HuskSprite != null && value != enableHuskSprite)
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
List<WearableSprite> otherWearablesWithHusk = new List<WearableSprite>() { HuskSprite };
|
||||
otherWearablesWithHusk.AddRange(OtherWearables);
|
||||
OtherWearables = otherWearablesWithHusk;
|
||||
UpdateWearableTypesToHide();
|
||||
}
|
||||
else
|
||||
{
|
||||
OtherWearables.Remove(HuskSprite);
|
||||
UpdateWearableTypesToHide();
|
||||
}
|
||||
}
|
||||
enableHuskSprite = value;
|
||||
}
|
||||
}
|
||||
|
||||
partial void InitProjSpecific(XElement element)
|
||||
{
|
||||
for (int i = 0; i < Params.decorativeSpriteParams.Count; i++)
|
||||
{
|
||||
var param = Params.decorativeSpriteParams[i];
|
||||
var decorativeSprite = new DecorativeSprite(param.Element, file: GetSpritePath(param.Element, param));
|
||||
DecorativeSprites.Add(decorativeSprite);
|
||||
int groupID = decorativeSprite.RandomGroupID;
|
||||
if (!DecorativeSpriteGroups.ContainsKey(groupID))
|
||||
{
|
||||
DecorativeSpriteGroups.Add(groupID, new List<DecorativeSprite>());
|
||||
}
|
||||
DecorativeSpriteGroups[groupID].Add(decorativeSprite);
|
||||
spriteAnimState.Add(decorativeSprite, new SpriteState());
|
||||
}
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "sprite":
|
||||
Sprite = new Sprite(subElement, file: GetSpritePath(subElement, Params.normalSpriteParams));
|
||||
break;
|
||||
case "damagedsprite":
|
||||
DamagedSprite = new Sprite(subElement, file: GetSpritePath(subElement, Params.damagedSpriteParams));
|
||||
break;
|
||||
case "conditionalsprite":
|
||||
var conditionalSprite = new ConditionalSprite(subElement, character, file: GetSpritePath(subElement, null));
|
||||
ConditionalSprites.Add(conditionalSprite);
|
||||
if (conditionalSprite.DeformableSprite != null)
|
||||
{
|
||||
CreateDeformations(subElement.GetChildElement("deformablesprite"));
|
||||
}
|
||||
break;
|
||||
case "deformablesprite":
|
||||
_deformSprite = new DeformableSprite(subElement, filePath: GetSpritePath(subElement, Params.deformSpriteParams));
|
||||
CreateDeformations(subElement);
|
||||
break;
|
||||
case "lightsource":
|
||||
LightSource = new LightSource(subElement)
|
||||
{
|
||||
ParentBody = body,
|
||||
SpriteScale = Vector2.One * Scale * TextureScale
|
||||
};
|
||||
InitialLightSourceColor = LightSource.Color;
|
||||
InitialLightSpriteAlpha = LightSource.OverrideLightSpriteAlpha;
|
||||
break;
|
||||
}
|
||||
|
||||
void CreateDeformations(XElement e)
|
||||
{
|
||||
foreach (XElement animationElement in e.GetChildElements("spritedeformation"))
|
||||
{
|
||||
int sync = animationElement.GetAttributeInt("sync", -1);
|
||||
SpriteDeformation deformation = null;
|
||||
if (sync > -1)
|
||||
{
|
||||
// if the element is marked with the sync attribute, use a deformation of the same type with the same sync value, if there is one already.
|
||||
string typeName = animationElement.GetAttributeString("type", "").ToLowerInvariant();
|
||||
deformation = ragdoll.Limbs
|
||||
.Where(l => l != null)
|
||||
.SelectMany(l => l.Deformations)
|
||||
.Where(d => d.TypeName == typeName && d.Sync == sync)
|
||||
.FirstOrDefault();
|
||||
}
|
||||
if (deformation == null)
|
||||
{
|
||||
deformation = SpriteDeformation.Load(animationElement, character.SpeciesName);
|
||||
if (deformation != null)
|
||||
{
|
||||
ragdoll.SpriteDeformations.Add(deformation);
|
||||
}
|
||||
}
|
||||
if (deformation != null)
|
||||
{
|
||||
Deformations.Add(deformation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RecreateSprites()
|
||||
{
|
||||
if (Sprite != null)
|
||||
{
|
||||
Sprite.Remove();
|
||||
var source = Sprite.SourceElement;
|
||||
Sprite = new Sprite(source, file: GetSpritePath(source, Params.normalSpriteParams));
|
||||
}
|
||||
if (_deformSprite != null)
|
||||
{
|
||||
_deformSprite.Remove();
|
||||
var source = _deformSprite.Sprite.SourceElement;
|
||||
_deformSprite = new DeformableSprite(source, filePath: GetSpritePath(source, Params.deformSpriteParams));
|
||||
}
|
||||
if (DamagedSprite != null)
|
||||
{
|
||||
DamagedSprite.Remove();
|
||||
var source = DamagedSprite.SourceElement;
|
||||
DamagedSprite = new Sprite(source, file: GetSpritePath(source, Params.damagedSpriteParams));
|
||||
}
|
||||
for (int i = 0; i < ConditionalSprites.Count; i++)
|
||||
{
|
||||
var conditionalSprite = ConditionalSprites[i];
|
||||
var source = conditionalSprite.ActiveSprite.SourceElement;
|
||||
conditionalSprite.Remove();
|
||||
ConditionalSprites[i] = new ConditionalSprite(source, character, file: GetSpritePath(source, null));
|
||||
}
|
||||
for (int i = 0; i < DecorativeSprites.Count; i++)
|
||||
{
|
||||
var decorativeSprite = DecorativeSprites[i];
|
||||
decorativeSprite.Remove();
|
||||
var source = decorativeSprite.Sprite.SourceElement;
|
||||
DecorativeSprites[i] = new DecorativeSprite(source, file: GetSpritePath(source, Params.decorativeSpriteParams[i]));
|
||||
}
|
||||
}
|
||||
|
||||
private void CalculateHeadPosition(Sprite sprite)
|
||||
{
|
||||
if (type != LimbType.Head) { return; }
|
||||
character.Info?.CalculateHeadPosition(sprite);
|
||||
}
|
||||
|
||||
private string GetSpritePath(XElement element, SpriteParams spriteParams)
|
||||
{
|
||||
string texturePath = element.GetAttributeString("texture", null);
|
||||
if (string.IsNullOrWhiteSpace(texturePath) && spriteParams != null)
|
||||
{
|
||||
texturePath = spriteParams.Ragdoll.Texture;
|
||||
}
|
||||
return GetSpritePath(texturePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the full path of a limb sprite, taking into account tags, gender and head id
|
||||
/// </summary>
|
||||
private string GetSpritePath(string texturePath)
|
||||
{
|
||||
string spritePath = texturePath;
|
||||
string spritePathWithTags = spritePath;
|
||||
if (character.Info != null && character.IsHumanoid)
|
||||
{
|
||||
spritePath = spritePath.Replace("[GENDER]", (character.Info.Gender == Gender.Female) ? "female" : "male");
|
||||
spritePath = spritePath.Replace("[RACE]", character.Info.Race.ToString().ToLowerInvariant());
|
||||
spritePath = spritePath.Replace("[HEADID]", character.Info.HeadSpriteId.ToString());
|
||||
|
||||
if (character.Info.HeadSprite != null && character.Info.SpriteTags.Any())
|
||||
{
|
||||
string tags = "";
|
||||
character.Info.SpriteTags.ForEach(tag => tags += "[" + tag + "]");
|
||||
|
||||
spritePathWithTags = Path.Combine(
|
||||
Path.GetDirectoryName(spritePath),
|
||||
Path.GetFileNameWithoutExtension(spritePath) + tags + Path.GetExtension(spritePath));
|
||||
}
|
||||
}
|
||||
return File.Exists(spritePathWithTags) ? spritePathWithTags : spritePath;
|
||||
}
|
||||
|
||||
partial void LoadParamsProjSpecific()
|
||||
{
|
||||
bool isFlipped = dir == Direction.Left;
|
||||
Sprite?.LoadParams(Params.normalSpriteParams, isFlipped);
|
||||
DamagedSprite?.LoadParams(Params.damagedSpriteParams, isFlipped);
|
||||
_deformSprite?.Sprite.LoadParams(Params.deformSpriteParams, isFlipped);
|
||||
for (int i = 0; i < DecorativeSprites.Count; i++)
|
||||
{
|
||||
DecorativeSprites[i].Sprite?.LoadParams(Params.decorativeSpriteParams[i], isFlipped);
|
||||
}
|
||||
}
|
||||
|
||||
partial void AddDamageProjSpecific(Vector2 simPosition, List<Affliction> afflictions, bool playSound, List<DamageModifier> appliedDamageModifiers)
|
||||
{
|
||||
float bleedingDamage = character.CharacterHealth.DoesBleed ? afflictions.FindAll(a => a is AfflictionBleeding).Sum(a => a.GetVitalityDecrease(character.CharacterHealth)) : 0;
|
||||
float damage = afflictions.FindAll(a => a.Prefab.AfflictionType == "damage").Sum(a => a.GetVitalityDecrease(character.CharacterHealth));
|
||||
float damageMultiplier = 1;
|
||||
foreach (DamageModifier damageModifier in appliedDamageModifiers)
|
||||
{
|
||||
damageMultiplier *= damageModifier.DamageMultiplier;
|
||||
}
|
||||
if (playSound)
|
||||
{
|
||||
string damageSoundType = (bleedingDamage > damage) ? "LimbSlash" : "LimbBlunt";
|
||||
foreach (DamageModifier damageModifier in appliedDamageModifiers)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(damageModifier.DamageSound))
|
||||
{
|
||||
damageSoundType = damageModifier.DamageSound;
|
||||
break;
|
||||
}
|
||||
}
|
||||
SoundPlayer.PlayDamageSound(damageSoundType, Math.Max(damage, bleedingDamage), WorldPosition);
|
||||
}
|
||||
|
||||
// spawn damage particles
|
||||
float damageParticleAmount = Math.Min(damage / 10, 1.0f) * damageMultiplier;
|
||||
if (damageParticleAmount > 0.001f)
|
||||
{
|
||||
foreach (ParticleEmitter emitter in character.DamageEmitters)
|
||||
{
|
||||
if (inWater && emitter.Prefab.ParticlePrefab.DrawTarget == ParticlePrefab.DrawTargetType.Air) continue;
|
||||
if (!inWater && emitter.Prefab.ParticlePrefab.DrawTarget == ParticlePrefab.DrawTargetType.Water) continue;
|
||||
|
||||
emitter.Emit(1.0f, WorldPosition, character.CurrentHull, amountMultiplier: damageParticleAmount);
|
||||
}
|
||||
}
|
||||
|
||||
if (bleedingDamage > 0)
|
||||
{
|
||||
float bloodParticleAmount = Math.Min(bleedingDamage / 5, 1.0f) * damageMultiplier;
|
||||
float bloodParticleSize = MathHelper.Clamp(bleedingDamage / 5, 0.1f, 1.0f);
|
||||
|
||||
foreach (ParticleEmitter emitter in character.BloodEmitters)
|
||||
{
|
||||
if (inWater && emitter.Prefab.ParticlePrefab.DrawTarget == ParticlePrefab.DrawTargetType.Air) continue;
|
||||
if (!inWater && emitter.Prefab.ParticlePrefab.DrawTarget == ParticlePrefab.DrawTargetType.Water) continue;
|
||||
|
||||
emitter.Emit(1.0f, WorldPosition, character.CurrentHull, sizeMultiplier: bloodParticleSize, amountMultiplier: bloodParticleAmount);
|
||||
}
|
||||
|
||||
if (bloodParticleAmount > 0 && character.CurrentHull != null && !string.IsNullOrEmpty(character.BloodDecalName))
|
||||
{
|
||||
character.CurrentHull.AddDecal(character.BloodDecalName, WorldPosition, MathHelper.Clamp(bloodParticleSize, 0.5f, 1.0f));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
partial void UpdateProjSpecific(float deltaTime)
|
||||
{
|
||||
if (!body.Enabled) return;
|
||||
|
||||
if (!character.IsDead)
|
||||
{
|
||||
DamageOverlayStrength -= deltaTime;
|
||||
BurnOverlayStrength -= deltaTime;
|
||||
}
|
||||
|
||||
if (inWater)
|
||||
{
|
||||
wetTimer = 1.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
wetTimer -= deltaTime * 0.1f;
|
||||
if (wetTimer > 0.0f)
|
||||
{
|
||||
dripParticleTimer += wetTimer * deltaTime * Mass * (wetTimer > 0.9f ? 50.0f : 5.0f);
|
||||
if (dripParticleTimer > 1.0f)
|
||||
{
|
||||
float dropRadius = body.BodyShape == PhysicsBody.Shape.Rectangle ? Math.Min(body.width, body.height) : body.radius;
|
||||
GameMain.ParticleManager.CreateParticle(
|
||||
"waterdrop",
|
||||
WorldPosition + Rand.Vector(Rand.Range(0.0f, ConvertUnits.ToDisplayUnits(dropRadius))),
|
||||
ConvertUnits.ToDisplayUnits(body.LinearVelocity),
|
||||
0, character.CurrentHull);
|
||||
dripParticleTimer = 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (LightSource != null)
|
||||
{
|
||||
LightSource.ParentSub = body.Submarine;
|
||||
LightSource.Rotation = (dir == Direction.Right) ? body.Rotation : body.Rotation - MathHelper.Pi;
|
||||
if (LightSource.LightSprite != null)
|
||||
{
|
||||
LightSource.LightSprite.Depth = ActiveSprite.Depth;
|
||||
}
|
||||
}
|
||||
|
||||
UpdateSpriteStates(deltaTime);
|
||||
}
|
||||
|
||||
public void Draw(SpriteBatch spriteBatch, Camera cam, Color? overrideColor = null)
|
||||
{
|
||||
float brightness = 1.0f - (burnOverLayStrength / 100.0f) * 0.5f;
|
||||
Color color = new Color(brightness, brightness, brightness);
|
||||
|
||||
color = overrideColor ?? color;
|
||||
|
||||
if (isSevered)
|
||||
{
|
||||
if (severedFadeOutTimer > SeveredFadeOutTime)
|
||||
{
|
||||
if (LightSource != null) { LightSource.Enabled = false; }
|
||||
return;
|
||||
}
|
||||
else if (severedFadeOutTimer > SeveredFadeOutTime - 1.0f)
|
||||
{
|
||||
color *= SeveredFadeOutTime - severedFadeOutTimer;
|
||||
}
|
||||
}
|
||||
|
||||
body.Dir = Dir;
|
||||
|
||||
float herpesStrength = character.CharacterHealth.GetAfflictionStrength("spaceherpes");
|
||||
|
||||
bool hideLimb = Params.Hide ||
|
||||
OtherWearables.Any(w => w.HideLimb) ||
|
||||
wearingItems.Any(w => w != null && w.HideLimb);
|
||||
|
||||
var activeSprite = ActiveSprite;
|
||||
if (type == LimbType.Head)
|
||||
{
|
||||
CalculateHeadPosition(activeSprite);
|
||||
}
|
||||
|
||||
body.UpdateDrawPosition();
|
||||
|
||||
if (!hideLimb)
|
||||
{
|
||||
var deformSprite = DeformSprite;
|
||||
if (deformSprite != null)
|
||||
{
|
||||
if (Deformations != null && Deformations.Any())
|
||||
{
|
||||
var deformation = SpriteDeformation.GetDeformation(Deformations, deformSprite.Size);
|
||||
deformSprite.Deform(deformation);
|
||||
}
|
||||
else
|
||||
{
|
||||
deformSprite.Reset();
|
||||
}
|
||||
body.Draw(deformSprite, cam, Vector2.One * Scale * TextureScale, color, Params.MirrorHorizontally);
|
||||
}
|
||||
else
|
||||
{
|
||||
body.Draw(spriteBatch, activeSprite, color, null, Scale * TextureScale, Params.MirrorHorizontally, Params.MirrorVertically);
|
||||
}
|
||||
}
|
||||
SpriteEffects spriteEffect = (dir == Direction.Right) ? SpriteEffects.None : SpriteEffects.FlipHorizontally;
|
||||
if (LightSource != null)
|
||||
{
|
||||
LightSource.LightSpriteEffect = (dir == Direction.Right) ? SpriteEffects.None : SpriteEffects.FlipVertically;
|
||||
}
|
||||
if (damageOverlayStrength > 0.0f && DamagedSprite != null && !hideLimb)
|
||||
{
|
||||
DamagedSprite.Draw(spriteBatch,
|
||||
new Vector2(body.DrawPosition.X, -body.DrawPosition.Y),
|
||||
color * Math.Min(damageOverlayStrength, 1.0f), activeSprite.Origin,
|
||||
-body.DrawRotation,
|
||||
Scale, spriteEffect, activeSprite.Depth - 0.0000015f);
|
||||
}
|
||||
foreach (var decorativeSprite in DecorativeSprites)
|
||||
{
|
||||
if (!spriteAnimState[decorativeSprite].IsActive) { continue; }
|
||||
float rotation = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState);
|
||||
Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState) * Scale;
|
||||
var ca = (float)Math.Cos(-body.Rotation);
|
||||
var sa = (float)Math.Sin(-body.Rotation);
|
||||
Vector2 transformedOffset = new Vector2(ca * offset.X + sa * offset.Y, -sa * offset.X + ca * offset.Y);
|
||||
decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(body.DrawPosition.X + transformedOffset.X, -(body.DrawPosition.Y + transformedOffset.Y)), color,
|
||||
-body.Rotation + rotation, Scale, spriteEffect,
|
||||
depth: decorativeSprite.Sprite.Depth);
|
||||
}
|
||||
float depthStep = 0.000001f;
|
||||
float step = depthStep;
|
||||
WearableSprite onlyDrawable = wearingItems.Find(w => w.HideOtherWearables);
|
||||
if (Params.MirrorHorizontally)
|
||||
{
|
||||
spriteEffect = spriteEffect == SpriteEffects.None ? SpriteEffects.FlipHorizontally : SpriteEffects.None;
|
||||
}
|
||||
if (Params.MirrorVertically)
|
||||
{
|
||||
spriteEffect |= SpriteEffects.FlipVertically;
|
||||
}
|
||||
if (onlyDrawable == null)
|
||||
{
|
||||
if (HerpesSprite != null && !wearableTypesToHide.Contains(WearableType.Herpes))
|
||||
{
|
||||
DrawWearable(HerpesSprite, depthStep, spriteBatch, color * Math.Min(herpesStrength / 10.0f, 1.0f), spriteEffect);
|
||||
depthStep += step;
|
||||
}
|
||||
foreach (WearableSprite wearable in OtherWearables)
|
||||
{
|
||||
if (wearableTypesToHide.Contains(wearable.Type)) { continue; }
|
||||
DrawWearable(wearable, depthStep, spriteBatch, color, spriteEffect);
|
||||
//if there are multiple sprites on this limb, make the successive ones be drawn in front
|
||||
depthStep += step;
|
||||
}
|
||||
}
|
||||
foreach (WearableSprite wearable in WearingItems)
|
||||
{
|
||||
if (onlyDrawable != null && onlyDrawable != wearable) continue;
|
||||
DrawWearable(wearable, depthStep, spriteBatch, color, spriteEffect);
|
||||
//if there are multiple sprites on this limb, make the successive ones be drawn in front
|
||||
depthStep += step;
|
||||
}
|
||||
|
||||
if (GameMain.DebugDraw)
|
||||
{
|
||||
if (pullJoint != null)
|
||||
{
|
||||
Vector2 pos = ConvertUnits.ToDisplayUnits(pullJoint.WorldAnchorB);
|
||||
GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)-pos.Y, 5, 5), GUI.Style.Red, true);
|
||||
}
|
||||
var bodyDrawPos = body.DrawPosition;
|
||||
bodyDrawPos.Y = -bodyDrawPos.Y;
|
||||
if (IsStuck)
|
||||
{
|
||||
Vector2 from = ConvertUnits.ToDisplayUnits(attachJoint.WorldAnchorA);
|
||||
from.Y = -from.Y;
|
||||
Vector2 to = ConvertUnits.ToDisplayUnits(attachJoint.WorldAnchorB);
|
||||
to.Y = -to.Y;
|
||||
var localFront = body.GetLocalFront(Params.GetSpriteOrientation());
|
||||
var front = ConvertUnits.ToDisplayUnits(body.FarseerBody.GetWorldPoint(localFront));
|
||||
front.Y = -front.Y;
|
||||
GUI.DrawLine(spriteBatch, bodyDrawPos, front, Color.Yellow, width: 2);
|
||||
GUI.DrawLine(spriteBatch, from, to, GUI.Style.Red, width: 1);
|
||||
GUI.DrawRectangle(spriteBatch, new Rectangle((int)from.X, (int)from.Y, 12, 12), Color.White, true);
|
||||
GUI.DrawRectangle(spriteBatch, new Rectangle((int)to.X, (int)to.Y, 12, 12), Color.White, true);
|
||||
GUI.DrawRectangle(spriteBatch, new Rectangle((int)from.X, (int)from.Y, 10, 10), Color.Blue, true);
|
||||
GUI.DrawRectangle(spriteBatch, new Rectangle((int)to.X, (int)to.Y, 10, 10), GUI.Style.Red, true);
|
||||
GUI.DrawRectangle(spriteBatch, new Rectangle((int)front.X, (int)front.Y, 10, 10), Color.Yellow, true);
|
||||
|
||||
//Vector2 mainLimbFront = ConvertUnits.ToDisplayUnits(ragdoll.MainLimb.body.FarseerBody.GetWorldPoint(ragdoll.MainLimb.body.GetFrontLocal(MathHelper.ToRadians(limbParams.Orientation))));
|
||||
//mainLimbFront.Y = -mainLimbFront.Y;
|
||||
//var mainLimbDrawPos = ragdoll.MainLimb.body.DrawPosition;
|
||||
//mainLimbDrawPos.Y = -mainLimbDrawPos.Y;
|
||||
//GUI.DrawLine(spriteBatch, mainLimbDrawPos, mainLimbFront, Color.White, width: 5);
|
||||
//GUI.DrawRectangle(spriteBatch, new Rectangle((int)mainLimbFront.X, (int)mainLimbFront.Y, 10, 10), Color.Yellow, true);
|
||||
}
|
||||
//DrawDamageModifiers(spriteBatch, cam, bodyDrawPos, isScreenSpace: false);
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateWearableTypesToHide()
|
||||
{
|
||||
wearableTypeHidingSprites.Clear();
|
||||
if (WearingItems != null && WearingItems.Count > 0)
|
||||
{
|
||||
wearableTypeHidingSprites.AddRange(
|
||||
WearingItems.FindAll(w => w.HideWearablesOfType != null && w.HideWearablesOfType.Count > 0));
|
||||
}
|
||||
if (OtherWearables != null && OtherWearables.Count > 0)
|
||||
{
|
||||
wearableTypeHidingSprites.AddRange(
|
||||
OtherWearables.FindAll(w => w.HideWearablesOfType != null && w.HideWearablesOfType.Count > 0));
|
||||
}
|
||||
|
||||
wearableTypesToHide.Clear();
|
||||
if (wearableTypeHidingSprites.Count > 0)
|
||||
{
|
||||
foreach (WearableSprite sprite in wearableTypeHidingSprites)
|
||||
{
|
||||
foreach (WearableType type in sprite.HideWearablesOfType)
|
||||
{
|
||||
if (!wearableTypesToHide.Contains(type))
|
||||
{
|
||||
wearableTypesToHide.Add(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateSpriteStates(float deltaTime)
|
||||
{
|
||||
foreach (int spriteGroup in DecorativeSpriteGroups.Keys)
|
||||
{
|
||||
for (int i = 0; i < DecorativeSpriteGroups[spriteGroup].Count; i++)
|
||||
{
|
||||
var decorativeSprite = DecorativeSpriteGroups[spriteGroup][i];
|
||||
if (decorativeSprite == null) { continue; }
|
||||
if (spriteGroup > 0)
|
||||
{
|
||||
// TODO
|
||||
//int activeSpriteIndex = ID % DecorativeSpriteGroups[spriteGroup].Count;
|
||||
//if (i != activeSpriteIndex)
|
||||
//{
|
||||
// spriteAnimState[decorativeSprite].IsActive = false;
|
||||
// continue;
|
||||
//}
|
||||
}
|
||||
|
||||
//check if the sprite is active (whether it should be drawn or not)
|
||||
var spriteState = spriteAnimState[decorativeSprite];
|
||||
spriteState.IsActive = true;
|
||||
foreach (PropertyConditional conditional in decorativeSprite.IsActiveConditionals)
|
||||
{
|
||||
if (!conditional.Matches(this))
|
||||
{
|
||||
spriteState.IsActive = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!spriteState.IsActive) { continue; }
|
||||
|
||||
//check if the sprite should be animated
|
||||
bool animate = true;
|
||||
foreach (PropertyConditional conditional in decorativeSprite.AnimationConditionals)
|
||||
{
|
||||
if (!conditional.Matches(this)) { animate = false; break; }
|
||||
}
|
||||
if (!animate) { continue; }
|
||||
spriteState.OffsetState += deltaTime;
|
||||
spriteState.RotationState += deltaTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawDamageModifiers(SpriteBatch spriteBatch, Camera cam, Vector2 startPos, bool isScreenSpace)
|
||||
{
|
||||
foreach (var modifier in DamageModifiers)
|
||||
{
|
||||
//Vector2 up = VectorExtensions.Backward(-body.TransformedRotation + Params.GetSpriteOrientation() * Dir);
|
||||
//int width = 4;
|
||||
//if (!isScreenSpace)
|
||||
//{
|
||||
// width = (int)Math.Round(width / cam.Zoom);
|
||||
//}
|
||||
//GUI.DrawLine(spriteBatch, startPos, startPos + Vector2.Normalize(up) * size, GUI.Style.Red, width: width);
|
||||
Color color = modifier.DamageMultiplier > 1 ? GUI.Style.Red : GUI.Style.Green;
|
||||
float size = ConvertUnits.ToDisplayUnits(body.GetSize().Length() / 2);
|
||||
if (isScreenSpace)
|
||||
{
|
||||
size *= cam.Zoom;
|
||||
}
|
||||
int thickness = 2;
|
||||
if (!isScreenSpace)
|
||||
{
|
||||
thickness = (int)Math.Round(thickness / cam.Zoom);
|
||||
}
|
||||
float bodyRotation = -body.Rotation;
|
||||
float constantOffset = -MathHelper.PiOver2;
|
||||
Vector2 armorSector = modifier.ArmorSectorInRadians;
|
||||
float armorSectorSize = Math.Abs(armorSector.X - armorSector.Y);
|
||||
float radians = armorSectorSize * Dir;
|
||||
float armorSectorOffset = armorSector.X * Dir;
|
||||
float finalOffset = bodyRotation + constantOffset + armorSectorOffset;
|
||||
ShapeExtensions.DrawSector(spriteBatch, startPos, size, radians, 40, color, finalOffset, thickness);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawWearable(WearableSprite wearable, float depthStep, SpriteBatch spriteBatch, Color color, SpriteEffects spriteEffect)
|
||||
{
|
||||
var sprite = ActiveSprite;
|
||||
if (wearable.InheritSourceRect)
|
||||
{
|
||||
if (wearable.SheetIndex.HasValue)
|
||||
{
|
||||
wearable.Sprite.SourceRect = new Rectangle(CharacterInfo.CalculateOffset(sprite, wearable.SheetIndex.Value), sprite.SourceRect.Size);
|
||||
}
|
||||
else if (type == LimbType.Head && character.Info != null && character.Info.Head.SheetIndex.HasValue)
|
||||
{
|
||||
wearable.Sprite.SourceRect = new Rectangle(CharacterInfo.CalculateOffset(sprite, character.Info.Head.SheetIndex.Value.ToPoint()), sprite.SourceRect.Size);
|
||||
}
|
||||
else
|
||||
{
|
||||
wearable.Sprite.SourceRect = sprite.SourceRect;
|
||||
}
|
||||
}
|
||||
|
||||
Vector2 origin = wearable.Sprite.Origin;
|
||||
if (wearable.InheritOrigin)
|
||||
{
|
||||
origin = sprite.Origin;
|
||||
wearable.Sprite.Origin = origin;
|
||||
}
|
||||
else
|
||||
{
|
||||
origin = wearable.Sprite.Origin;
|
||||
// If the wearable inherits the origin, flipping is already handled.
|
||||
if (body.Dir == -1.0f)
|
||||
{
|
||||
origin.X = wearable.Sprite.SourceRect.Width - origin.X;
|
||||
}
|
||||
}
|
||||
|
||||
float depth = wearable.Sprite.Depth;
|
||||
|
||||
if (wearable.InheritLimbDepth)
|
||||
{
|
||||
depth = sprite.Depth - depthStep;
|
||||
Limb depthLimb = (wearable.DepthLimb == LimbType.None) ? this : character.AnimController.GetLimb(wearable.DepthLimb);
|
||||
if (depthLimb != null)
|
||||
{
|
||||
depth = depthLimb.ActiveSprite.Depth - depthStep;
|
||||
}
|
||||
}
|
||||
var wearableItemComponent = wearable.WearableComponent;
|
||||
Color wearableColor = Color.White;
|
||||
if (wearableItemComponent != null)
|
||||
{
|
||||
// Draw outer cloths on top of inner cloths.
|
||||
if (wearableItemComponent.AllowedSlots.Contains(InvSlotType.OuterClothes))
|
||||
{
|
||||
depth -= depthStep;
|
||||
}
|
||||
wearableColor = wearableItemComponent.Item.GetSpriteColor();
|
||||
}
|
||||
float textureScale = wearable.InheritTextureScale ? TextureScale : 1;
|
||||
|
||||
wearable.Sprite.Draw(spriteBatch,
|
||||
new Vector2(body.DrawPosition.X, -body.DrawPosition.Y),
|
||||
new Color((color.R * wearableColor.R) / (255.0f * 255.0f), (color.G * wearableColor.G) / (255.0f * 255.0f), (color.B * wearableColor.B) / (255.0f * 255.0f)) * ((color.A * wearableColor.A) / (255.0f * 255.0f)),
|
||||
origin, -body.DrawRotation,
|
||||
Scale * textureScale, spriteEffect, depth);
|
||||
}
|
||||
|
||||
private WearableSprite GetWearableSprite(WearableType type, bool random = false)
|
||||
{
|
||||
var info = character.Info;
|
||||
if (info == null) { return null; }
|
||||
XElement element;
|
||||
if (random)
|
||||
{
|
||||
element = info.FilterByTypeAndHeadID(character.Info.FilterElementsByGenderAndRace(character.Info.Wearables), type)?.GetRandom(Rand.RandSync.ClientOnly);
|
||||
}
|
||||
else
|
||||
{
|
||||
element = info.FilterByTypeAndHeadID(character.Info.FilterElementsByGenderAndRace(character.Info.Wearables), type)?.FirstOrDefault();
|
||||
}
|
||||
if (element != null)
|
||||
{
|
||||
return new WearableSprite(element.Element("sprite"), type);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
partial void RemoveProjSpecific()
|
||||
{
|
||||
Sprite?.Remove();
|
||||
Sprite = null;
|
||||
|
||||
DamagedSprite?.Remove();
|
||||
DamagedSprite = null;
|
||||
|
||||
_deformSprite?.Sprite?.Remove();
|
||||
_deformSprite = null;
|
||||
|
||||
DecorativeSprites.ForEach(s => s.Remove());
|
||||
ConditionalSprites.Clear();
|
||||
|
||||
ConditionalSprites.ForEach(s => s.Remove());
|
||||
ConditionalSprites.Clear();
|
||||
|
||||
LightSource?.Remove();
|
||||
LightSource = null;
|
||||
|
||||
OtherWearables?.ForEach(w => w.Sprite.Remove());
|
||||
OtherWearables = null;
|
||||
|
||||
HuskSprite?.Sprite.Remove();
|
||||
HuskSprite = null;
|
||||
|
||||
HerpesSprite?.Sprite.Remove();
|
||||
HerpesSprite = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user