(61d00a474) v0.9.7.1
This commit is contained in:
@@ -0,0 +1,409 @@
|
||||
using Barotrauma.Networking;
|
||||
using Lidgren.Network;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using System;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class Camera
|
||||
{
|
||||
public static bool FollowSub = true;
|
||||
|
||||
private float? defaultZoom;
|
||||
public float DefaultZoom
|
||||
{
|
||||
get { return defaultZoom ?? (GameMain.Config == null || GameMain.Config.EnableMouseLook ? 1.3f : 1.0f); }
|
||||
set
|
||||
{
|
||||
defaultZoom = MathHelper.Clamp(value, 0.5f, 2.0f);
|
||||
}
|
||||
}
|
||||
|
||||
private float zoomSmoothness = 8.0f;
|
||||
public float ZoomSmoothness
|
||||
{
|
||||
get { return zoomSmoothness; }
|
||||
set { zoomSmoothness = Math.Max(value, 0.01f); }
|
||||
}
|
||||
private float moveSmoothness = 8.0f;
|
||||
public float MoveSmoothness
|
||||
{
|
||||
get { return moveSmoothness; }
|
||||
set { moveSmoothness = Math.Max(value, 0.01f); }
|
||||
}
|
||||
|
||||
private float minZoom = 0.1f;
|
||||
public float MinZoom
|
||||
{
|
||||
get { return minZoom;}
|
||||
set { minZoom = MathHelper.Clamp(value, 0.01f, 10.0f); }
|
||||
}
|
||||
|
||||
private float maxZoom = 2.0f;
|
||||
public float MaxZoom
|
||||
{
|
||||
get { return maxZoom; }
|
||||
set { maxZoom = MathHelper.Clamp(value, 1.0f, 10.0f); }
|
||||
}
|
||||
|
||||
private float zoom;
|
||||
|
||||
private float offsetAmount;
|
||||
|
||||
private Matrix transform, shaderTransform, viewMatrix;
|
||||
private Vector2 position;
|
||||
private float rotation;
|
||||
|
||||
private float angularVelocity;
|
||||
private float angularDamping;
|
||||
private float angularSpring;
|
||||
|
||||
private Vector2 prevPosition;
|
||||
private float prevZoom;
|
||||
|
||||
public float Shake;
|
||||
private Vector2 shakePosition;
|
||||
private float shakeTimer;
|
||||
|
||||
//the area of the world inside the camera view
|
||||
private Rectangle worldView;
|
||||
|
||||
private float globalZoomScale = 1.0f;
|
||||
|
||||
private Point resolution;
|
||||
|
||||
private Vector2 targetPos;
|
||||
|
||||
//used to smooth out the movement when in freecam
|
||||
private float targetZoom;
|
||||
private Vector2 velocity;
|
||||
|
||||
public float Zoom
|
||||
{
|
||||
get { return zoom; }
|
||||
set
|
||||
{
|
||||
zoom = MathHelper.Clamp(value, GameMain.DebugDraw ? 0.01f : MinZoom, MaxZoom);
|
||||
|
||||
Vector2 center = WorldViewCenter;
|
||||
float newWidth = resolution.X / zoom;
|
||||
float newHeight = resolution.Y / zoom;
|
||||
|
||||
worldView = new Rectangle(
|
||||
(int)(center.X - newWidth / 2.0f),
|
||||
(int)(center.Y + newHeight / 2.0f),
|
||||
(int)newWidth,
|
||||
(int)newHeight);
|
||||
|
||||
//UpdateTransform();
|
||||
}
|
||||
}
|
||||
|
||||
public float Rotation
|
||||
{
|
||||
get { return rotation; }
|
||||
set
|
||||
{
|
||||
if (!MathUtils.IsValid(value)) return;
|
||||
rotation = value;
|
||||
}
|
||||
}
|
||||
|
||||
public float AngularVelocity
|
||||
{
|
||||
get { return angularVelocity; }
|
||||
set
|
||||
{
|
||||
if (!MathUtils.IsValid(value)) return;
|
||||
angularVelocity = value;
|
||||
}
|
||||
}
|
||||
|
||||
public float OffsetAmount
|
||||
{
|
||||
get { return offsetAmount; }
|
||||
set { offsetAmount = value; }
|
||||
}
|
||||
|
||||
public Point Resolution
|
||||
{
|
||||
get { return resolution; }
|
||||
}
|
||||
|
||||
public Rectangle WorldView
|
||||
{
|
||||
get { return worldView; }
|
||||
}
|
||||
|
||||
public Vector2 WorldViewCenter
|
||||
{
|
||||
get
|
||||
{
|
||||
return new Vector2(
|
||||
worldView.X + worldView.Width / 2.0f,
|
||||
worldView.Y - worldView.Height / 2.0f);
|
||||
}
|
||||
}
|
||||
|
||||
public Matrix Transform
|
||||
{
|
||||
get { return transform; }
|
||||
}
|
||||
|
||||
public Matrix ShaderTransform
|
||||
{
|
||||
get { return shaderTransform; }
|
||||
}
|
||||
|
||||
public Camera()
|
||||
{
|
||||
zoom = prevZoom = targetZoom = 1.0f;
|
||||
rotation = 0.0f;
|
||||
position = Vector2.Zero;
|
||||
|
||||
CreateMatrices();
|
||||
GameMain.Instance.OnResolutionChanged += () => { CreateMatrices(); };
|
||||
|
||||
UpdateTransform(false);
|
||||
}
|
||||
|
||||
public Vector2 TargetPos
|
||||
{
|
||||
get { return targetPos; }
|
||||
set { targetPos = value; }
|
||||
}
|
||||
|
||||
public Vector2 GetPosition()
|
||||
{
|
||||
return position;
|
||||
}
|
||||
|
||||
// Auxiliary function to move the camera
|
||||
public void Translate(Vector2 amount)
|
||||
{
|
||||
position += amount;
|
||||
}
|
||||
|
||||
public void ClientWrite(IWriteMessage msg)
|
||||
{
|
||||
if (Character.Controlled != null && !Character.Controlled.IsDead) { return; }
|
||||
|
||||
msg.Write((byte)ClientNetObject.SPECTATING_POS);
|
||||
msg.Write(position.X);
|
||||
msg.Write(position.Y);
|
||||
}
|
||||
|
||||
private void CreateMatrices()
|
||||
{
|
||||
resolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
|
||||
worldView = new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight);
|
||||
viewMatrix = Matrix.CreateTranslation(new Vector3(GameMain.GraphicsWidth / 2.0f, GameMain.GraphicsHeight / 2.0f, 0));
|
||||
|
||||
globalZoomScale = (float)Math.Pow(new Vector2(resolution.X, resolution.Y).Length() / new Vector2(1920, 1080).Length(), 2);
|
||||
}
|
||||
|
||||
public void UpdateTransform(bool interpolate = true)
|
||||
{
|
||||
Vector2 interpolatedPosition = interpolate ? Timing.Interpolate(prevPosition, position) : position;
|
||||
|
||||
float interpolatedZoom = interpolate ? Timing.Interpolate(prevZoom, zoom) : zoom;
|
||||
|
||||
worldView.X = (int)(interpolatedPosition.X - worldView.Width / 2.0);
|
||||
worldView.Y = (int)(interpolatedPosition.Y + worldView.Height / 2.0);
|
||||
|
||||
transform = Matrix.CreateTranslation(
|
||||
new Vector3(-interpolatedPosition.X, interpolatedPosition.Y, 0)) *
|
||||
Matrix.CreateScale(new Vector3(interpolatedZoom, interpolatedZoom, 1)) *
|
||||
Matrix.CreateRotationZ(rotation) * viewMatrix;
|
||||
|
||||
shaderTransform = Matrix.CreateTranslation(
|
||||
new Vector3(
|
||||
-interpolatedPosition.X - resolution.X / interpolatedZoom / 2.0f,
|
||||
-interpolatedPosition.Y - resolution.Y / interpolatedZoom / 2.0f, 0)) *
|
||||
Matrix.CreateScale(new Vector3(interpolatedZoom, interpolatedZoom, 1)) *
|
||||
|
||||
viewMatrix * Matrix.CreateRotationZ(-rotation);
|
||||
|
||||
if (Character.Controlled == null)
|
||||
{
|
||||
GameMain.SoundManager.ListenerPosition = new Vector3(WorldViewCenter.X, WorldViewCenter.Y, -(100.0f / zoom));
|
||||
}
|
||||
else
|
||||
{
|
||||
GameMain.SoundManager.ListenerPosition = new Vector3(Character.Controlled.WorldPosition.X, Character.Controlled.WorldPosition.Y, -(100.0f / zoom));
|
||||
}
|
||||
|
||||
|
||||
if (!interpolate)
|
||||
{
|
||||
prevPosition = position;
|
||||
prevZoom = zoom;
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2 previousOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Resets to false each time the MoveCamera method is called.
|
||||
/// </summary>
|
||||
public bool Freeze { get; set; }
|
||||
|
||||
public void MoveCamera(float deltaTime, bool allowMove = true, bool allowZoom = true)
|
||||
{
|
||||
prevPosition = position;
|
||||
prevZoom = zoom;
|
||||
|
||||
float moveSpeed = 20.0f / zoom;
|
||||
|
||||
Vector2 moveCam = Vector2.Zero;
|
||||
if (targetPos == Vector2.Zero)
|
||||
{
|
||||
Vector2 moveInput = Vector2.Zero;
|
||||
if (allowMove && GUI.KeyboardDispatcher.Subscriber == null)
|
||||
{
|
||||
if (PlayerInput.KeyDown(Keys.LeftShift)) moveSpeed *= 2.0f;
|
||||
if (PlayerInput.KeyDown(Keys.LeftControl)) moveSpeed *= 0.5f;
|
||||
|
||||
if (GameMain.Config.KeyBind(InputType.Left).IsDown()) moveInput.X -= 1.0f;
|
||||
if (GameMain.Config.KeyBind(InputType.Right).IsDown()) moveInput.X += 1.0f;
|
||||
if (GameMain.Config.KeyBind(InputType.Down).IsDown()) moveInput.Y -= 1.0f;
|
||||
if (GameMain.Config.KeyBind(InputType.Up).IsDown()) moveInput.Y += 1.0f;
|
||||
}
|
||||
|
||||
velocity = Vector2.Lerp(velocity, moveInput, deltaTime * 10.0f);
|
||||
moveCam = velocity * moveSpeed * deltaTime * 60.0f;
|
||||
|
||||
if (Screen.Selected == GameMain.GameScreen && FollowSub)
|
||||
{
|
||||
var closestSub = Submarine.FindClosest(WorldViewCenter);
|
||||
if (closestSub != null)
|
||||
{
|
||||
moveCam += FarseerPhysics.ConvertUnits.ToDisplayUnits(closestSub.Velocity * deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
if (allowZoom && GUI.MouseOn == null)
|
||||
{
|
||||
Vector2 mouseInWorld = ScreenToWorld(PlayerInput.MousePosition);
|
||||
Vector2 diffViewCenter;
|
||||
diffViewCenter = ((mouseInWorld - Position) * Zoom);
|
||||
targetZoom = MathHelper.Clamp(
|
||||
targetZoom + (PlayerInput.ScrollWheelSpeed / 1000.0f) * zoom,
|
||||
GameMain.DebugDraw ? MinZoom * 0.1f : MinZoom,
|
||||
MaxZoom);
|
||||
|
||||
Zoom = MathHelper.Lerp(Zoom, targetZoom, deltaTime * 10.0f);
|
||||
if (!PlayerInput.KeyDown(Keys.F)) Position = mouseInWorld - (diffViewCenter / Zoom);
|
||||
}
|
||||
}
|
||||
else if (allowMove)
|
||||
{
|
||||
Vector2 mousePos = PlayerInput.MousePosition;
|
||||
Vector2 offset = mousePos - resolution.ToVector2() / 2;
|
||||
offset.X = offset.X / (resolution.X * 0.4f);
|
||||
offset.Y = -offset.Y / (resolution.Y * 0.3f);
|
||||
if (offset.LengthSquared() > 1.0f) offset.Normalize();
|
||||
offset *= offsetAmount;
|
||||
// Freeze the camera movement by default, when the cursor is on top of an ui element.
|
||||
// Setting a positive value to the OffsetAmount, will override this behaviour.
|
||||
if (GUI.MouseOn != null && offsetAmount > 0)
|
||||
{
|
||||
Freeze = true;
|
||||
}
|
||||
if (CharacterHealth.OpenHealthWindow != null || CrewManager.IsCommandInterfaceOpen)
|
||||
{
|
||||
offset *= 0;
|
||||
Freeze = false;
|
||||
}
|
||||
if (Freeze)
|
||||
{
|
||||
offset = previousOffset;
|
||||
}
|
||||
else
|
||||
{
|
||||
previousOffset = offset;
|
||||
}
|
||||
|
||||
//how much to zoom out (zoom completely out when offset is 1000)
|
||||
float zoomOutAmount = Math.Min(offset.Length() / 1000.0f, 1.0f);
|
||||
//zoom amount when resolution is not taken into account
|
||||
float unscaledZoom = MathHelper.Lerp(DefaultZoom, MinZoom, zoomOutAmount);
|
||||
//zoom with resolution taken into account (zoom further out on smaller resolutions)
|
||||
float scaledZoom = unscaledZoom * globalZoomScale;
|
||||
|
||||
//an ad-hoc way of allowing the players to have roughly the same maximum view distance regardless of the resolution,
|
||||
//while still keeping the zoom around 1.0 when not looking further away (because otherwise we'd always be downsampling
|
||||
//on lower resolutions, which doesn't look that good)
|
||||
float newZoom = MathHelper.Lerp(unscaledZoom, scaledZoom,
|
||||
(GameMain.Config == null || GameMain.Config.EnableMouseLook) ? (float)Math.Sqrt(zoomOutAmount) : 0.3f);
|
||||
|
||||
Zoom += (newZoom - zoom) / ZoomSmoothness;
|
||||
|
||||
//force targetzoom to the current zoom value, so the camera stays at the same zoom when switching to freecam
|
||||
targetZoom = Zoom;
|
||||
|
||||
Vector2 diff = (targetPos + offset) - position;
|
||||
|
||||
moveCam = diff / MoveSmoothness;
|
||||
}
|
||||
rotation += angularVelocity * deltaTime;
|
||||
angularVelocity *= (1.0f - angularDamping);
|
||||
angularVelocity += -rotation * angularSpring;
|
||||
|
||||
angularDamping = 0.05f;
|
||||
angularSpring = 0.2f;
|
||||
|
||||
if (Shake < 0.01f)
|
||||
{
|
||||
shakePosition = Vector2.Zero;
|
||||
shakeTimer = 0.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
shakeTimer += deltaTime * 5.0f;
|
||||
Vector2 noisePos = new Vector2((float)PerlinNoise.CalculatePerlin(shakeTimer, shakeTimer, 0) - 0.5f, (float)PerlinNoise.CalculatePerlin(shakeTimer, shakeTimer, 0.5f) - 0.5f);
|
||||
|
||||
shakePosition = noisePos * Shake * 2.0f;
|
||||
Shake = MathHelper.Lerp(Shake, 0.0f, deltaTime * 2.0f);
|
||||
}
|
||||
|
||||
Translate(moveCam + shakePosition);
|
||||
Freeze = false;
|
||||
}
|
||||
|
||||
public void StopMovement()
|
||||
{
|
||||
targetZoom = zoom;
|
||||
velocity = Vector2.Zero;
|
||||
angularVelocity = 0.0f;
|
||||
rotation = 0.0f;
|
||||
}
|
||||
|
||||
public Vector2 Position
|
||||
{
|
||||
get { return position; }
|
||||
set
|
||||
{
|
||||
if (!MathUtils.IsValid(value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
position = value;
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2 ScreenToWorld(Vector2 coords)
|
||||
{
|
||||
Vector2 worldCoords = Vector2.Transform(coords, Matrix.Invert(transform));
|
||||
return new Vector2(worldCoords.X, -worldCoords.Y);
|
||||
}
|
||||
|
||||
public Vector2 WorldToScreen(Vector2 coords)
|
||||
{
|
||||
coords.Y = -coords.Y;
|
||||
//Vector2 screenCoords = Vector2.Transform(coords, transform);
|
||||
return Vector2.Transform(coords, transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
// TODO decide what folder this falls under. Utils? GUI? or just leave where it is. Also probably a better class name name [<- no need to create a separate namespace, maybe a folder?]
|
||||
/// <summary>
|
||||
/// A class used for handling special key actions in chat boxes.
|
||||
/// For example tab completion or up/down arrow key history.
|
||||
/// </summary>
|
||||
public class ChatManager
|
||||
{
|
||||
private readonly bool loop;
|
||||
|
||||
// Maximum items we want to store in the history
|
||||
private readonly short maxCount = 10;
|
||||
|
||||
// List of previously stored messages
|
||||
private readonly List<string> messageList = new List<string> { string.Empty };
|
||||
|
||||
/// Keep track of the registered fields so we don't register them twice
|
||||
/// I couldn't figure out where to register this in <see cref="NetLobbyScreen"/> where it wouldn't register twice
|
||||
/// It's probably not the most optimal way of doing this so feel free to change this
|
||||
/// <seealso cref="NetLobbyScreen.Select"/> where I'm utilizing this
|
||||
private readonly List<GUITextBox> registers = new List<GUITextBox>();
|
||||
|
||||
// Selector index
|
||||
private int index;
|
||||
|
||||
// Local changes we've made into previously stored messages
|
||||
private string[] localChanges;
|
||||
|
||||
public ChatManager(bool loop, short maxCount)
|
||||
{
|
||||
this.loop = loop;
|
||||
this.maxCount = maxCount;
|
||||
localChanges = new string[maxCount];
|
||||
}
|
||||
|
||||
public ChatManager()
|
||||
{
|
||||
localChanges = new string[maxCount];
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Registers special input actions to the selected input field
|
||||
/// </summary>
|
||||
/// <param name="element">GUI Element we want to register</param>
|
||||
/// <param name="manager">Instance</param>
|
||||
public static void RegisterKeys(GUITextBox element, ChatManager manager)
|
||||
{
|
||||
// If already registered then don't register it again
|
||||
if (manager.registers.Any(p => element == p)) { return; }
|
||||
element.OnKeyHit += (sender, key) =>
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
// Up/Down Arrow key history action
|
||||
case Keys.Up:
|
||||
case Keys.Down:
|
||||
{
|
||||
// Up arrow key? go up. Down arrow key? go down. Everything else gets binned
|
||||
Direction direction = key == Keys.Up ? Direction.Up : (key == Keys.Down ? Direction.Down : Direction.Other);
|
||||
|
||||
string newMessage = manager.SelectMessage(direction, element.Text);
|
||||
// Don't do anything if we didn't find anything
|
||||
if (newMessage == null) { return; }
|
||||
|
||||
element.Text = newMessage;
|
||||
break;
|
||||
}
|
||||
case Keys.Tab:
|
||||
// TODO tab completion behavior, maybe?
|
||||
break;
|
||||
}
|
||||
};
|
||||
manager.registers.Add(element);
|
||||
}
|
||||
|
||||
// Store a new message
|
||||
public void Store(string message)
|
||||
{
|
||||
Clear();
|
||||
var strip = StripMessage(message);
|
||||
if (string.IsNullOrWhiteSpace(strip)) { return; }
|
||||
|
||||
if (messageList.Count > 1 && messageList[1] == message) { return; }
|
||||
|
||||
// insert to the second position as the first position is reserved for the original message if any
|
||||
messageList.Insert(1, message);
|
||||
// we don't want to add too many messages
|
||||
if (messageList.Count > maxCount)
|
||||
{
|
||||
messageList.RemoveAt(messageList.Count - 1);
|
||||
}
|
||||
|
||||
// [It's also possible to lambdas too in short methods, if you like: string StripMessage(string text) => ChatMessage.GetChatMessageCommand(text, out string msg);]
|
||||
static string StripMessage(string text)
|
||||
{
|
||||
ChatMessage.GetChatMessageCommand(text, out string msg);
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call this function whenever we should stop doing special stuff and return normal behavior.
|
||||
/// For example when you deselect the chat box.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
index = 0;
|
||||
localChanges = new string[maxCount];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scroll up or down on the message history and return a message
|
||||
/// </summary>
|
||||
/// <param name="direction">Direction we want to scroll the stack</param>
|
||||
/// <param name="original">Leftover text that is in the chat box when we override it</param>
|
||||
/// <returns>A message or null</returns>
|
||||
private string SelectMessage(Direction direction, string original)
|
||||
{
|
||||
var originalIndex = index;
|
||||
while (true)
|
||||
{
|
||||
if (direction == Direction.Other)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// temporarily save our changes in case we fat-finger and want to go back
|
||||
localChanges[index] = original;
|
||||
|
||||
var dir = (int) direction;
|
||||
|
||||
var nextIndex = (index + dir);
|
||||
|
||||
if (loop && messageList.Count > 1)
|
||||
{
|
||||
nextIndex = LoopAround(nextIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (nextIndex > messageList.Count - 1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (nextIndex >= 0 && EntryAt(nextIndex) == original && nextIndex != originalIndex && originalIndex != 0)
|
||||
{
|
||||
index = nextIndex;
|
||||
continue;
|
||||
}
|
||||
|
||||
return nextIndex < 0 ? localChanges.FirstOrDefault() : EntryAt(index = nextIndex);
|
||||
|
||||
string EntryAt(int i)
|
||||
{
|
||||
// if we've previously edited the entry then give us that, else give us the original message
|
||||
return localChanges[i] ?? messageList[i];
|
||||
}
|
||||
|
||||
int LoopAround(int next)
|
||||
{
|
||||
if (next > (messageList.Count - 1))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (next < 1)
|
||||
{
|
||||
return messageList.Count - 1;
|
||||
}
|
||||
|
||||
return next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Used for Up/Down arrow key action
|
||||
private enum Direction
|
||||
{
|
||||
Up = 1,
|
||||
Down = -1,
|
||||
Other = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,120 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace EventInput
|
||||
{
|
||||
public class CharacterEventArgs : EventArgs
|
||||
{
|
||||
private readonly char character;
|
||||
private readonly long lParam;
|
||||
|
||||
public CharacterEventArgs(char character, long lParam)
|
||||
{
|
||||
this.character = character;
|
||||
this.lParam = lParam;
|
||||
}
|
||||
|
||||
public char Character
|
||||
{
|
||||
get { return character; }
|
||||
}
|
||||
|
||||
public long Param
|
||||
{
|
||||
get { return lParam; }
|
||||
}
|
||||
|
||||
public long RepeatCount
|
||||
{
|
||||
get { return lParam & 0xffff; }
|
||||
}
|
||||
|
||||
public bool ExtendedKey
|
||||
{
|
||||
get { return (lParam & (1 << 24)) > 0; }
|
||||
}
|
||||
|
||||
public bool AltPressed
|
||||
{
|
||||
get { return (lParam & (1 << 29)) > 0; }
|
||||
}
|
||||
|
||||
public bool PreviousState
|
||||
{
|
||||
get { return (lParam & (1 << 30)) > 0; }
|
||||
}
|
||||
|
||||
public bool TransitionState
|
||||
{
|
||||
get { return (lParam & (1 << 31)) > 0; }
|
||||
}
|
||||
}
|
||||
|
||||
public class KeyEventArgs : EventArgs
|
||||
{
|
||||
private Keys keyCode;
|
||||
|
||||
public KeyEventArgs(Keys keyCode)
|
||||
{
|
||||
this.keyCode = keyCode;
|
||||
}
|
||||
|
||||
public Keys KeyCode
|
||||
{
|
||||
get { return keyCode; }
|
||||
}
|
||||
}
|
||||
|
||||
public delegate void CharEnteredHandler(object sender, CharacterEventArgs e);
|
||||
public delegate void KeyEventHandler(object sender, KeyEventArgs e);
|
||||
|
||||
public static class EventInput
|
||||
{
|
||||
/// <summary>
|
||||
/// Event raised when a Character has been entered.
|
||||
/// </summary>
|
||||
public static event CharEnteredHandler CharEntered;
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when a key has been pressed down. May fire multiple times due to keyboard repeat.
|
||||
/// </summary>
|
||||
public static event KeyEventHandler KeyDown;
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when a key has been released.
|
||||
/// </summary>
|
||||
public static event KeyEventHandler KeyUp;
|
||||
|
||||
static bool initialized;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the TextInput with the given GameWindow.
|
||||
/// </summary>
|
||||
/// <param name="window">The XNA window to which text input should be linked.</param>
|
||||
public static void Initialize(GameWindow window)
|
||||
{
|
||||
if (initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
window.TextInput += ReceiveInput;
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
private static void ReceiveInput(object sender, TextInputEventArgs e)
|
||||
{
|
||||
OnCharEntered(e.Character);
|
||||
KeyDown?.Invoke(sender, new KeyEventArgs(e.Key));
|
||||
}
|
||||
|
||||
public static void OnCharEntered(char character)
|
||||
{
|
||||
CharEntered?.Invoke(null, new CharacterEventArgs(character, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
#if WINDOWS
|
||||
using System.Windows;
|
||||
#endif
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
|
||||
namespace EventInput
|
||||
{
|
||||
public interface IKeyboardSubscriber
|
||||
{
|
||||
void ReceiveTextInput(char inputChar);
|
||||
void ReceiveTextInput(string text);
|
||||
void ReceiveCommandInput(char command);
|
||||
void ReceiveSpecialInput(Keys key);
|
||||
|
||||
bool Selected { get; set; } //or Focused
|
||||
}
|
||||
|
||||
public class KeyboardDispatcher
|
||||
{
|
||||
public KeyboardDispatcher(GameWindow window)
|
||||
{
|
||||
EventInput.Initialize(window);
|
||||
EventInput.CharEntered += EventInput_CharEntered;
|
||||
EventInput.KeyDown += EventInput_KeyDown;
|
||||
}
|
||||
|
||||
public void EventInput_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (_subscriber == null)
|
||||
return;
|
||||
|
||||
_subscriber.ReceiveSpecialInput(e.KeyCode);
|
||||
}
|
||||
|
||||
void EventInput_CharEntered(object sender, CharacterEventArgs e)
|
||||
{
|
||||
if (_subscriber == null)
|
||||
return;
|
||||
if (char.IsControl(e.Character))
|
||||
{
|
||||
_subscriber.ReceiveCommandInput(e.Character);
|
||||
// Doesn't work as expected. Not sure why this should be run in a separate thread.
|
||||
//#if WINDOWS
|
||||
// //ctrl-v
|
||||
// if (e.Character == 0x16) // 22
|
||||
// {
|
||||
// //XNA runs in Multiple Thread Apartment state, which cannot recieve clipboard
|
||||
// Thread thread = new Thread(PasteThread);
|
||||
// thread.SetApartmentState(ApartmentState.STA);
|
||||
// thread.Start();
|
||||
// thread.Join();
|
||||
// _subscriber.ReceiveTextInput(_pasteResult);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// _subscriber.ReceiveCommandInput(e.Character);
|
||||
// }
|
||||
//#else
|
||||
// _subscriber.ReceiveCommandInput(e.Character);
|
||||
//#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
_subscriber.ReceiveTextInput(e.Character);
|
||||
}
|
||||
}
|
||||
|
||||
IKeyboardSubscriber _subscriber;
|
||||
public IKeyboardSubscriber Subscriber
|
||||
{
|
||||
get { return _subscriber; }
|
||||
set
|
||||
{
|
||||
if (_subscriber == value) return;
|
||||
if (_subscriber != null)
|
||||
_subscriber.Selected = false;
|
||||
_subscriber = value;
|
||||
if (value != null)
|
||||
value.Selected = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class EventManager
|
||||
{
|
||||
private Graph intensityGraph;
|
||||
private Graph targetIntensityGraph;
|
||||
private float intensityGraphUpdateInterval;
|
||||
private float lastIntensityUpdate;
|
||||
|
||||
public void DebugDraw(SpriteBatch spriteBatch)
|
||||
{
|
||||
foreach (ScriptedEvent ev in activeEvents)
|
||||
{
|
||||
Vector2 drawPos = ev.DebugDrawPos;
|
||||
drawPos.Y = -drawPos.Y;
|
||||
|
||||
var textOffset = new Vector2(-150, 0);
|
||||
ShapeExtensions.DrawCircle(spriteBatch, drawPos, 600, 6, Color.White, thickness: 20);
|
||||
GUI.DrawString(spriteBatch, drawPos + textOffset, ev.ToString(), Color.White, Color.Black, 0, GUI.LargeFont);
|
||||
}
|
||||
}
|
||||
|
||||
public void DebugDrawHUD(SpriteBatch spriteBatch, int y)
|
||||
{
|
||||
GUI.DrawString(spriteBatch, new Vector2(10, y), "EventManager", Color.White, Color.Black * 0.6f, 0, GUI.SmallFont);
|
||||
GUI.DrawString(spriteBatch, new Vector2(15, y + 20), "Event cooldown: " + eventCoolDown, Color.White, Color.Black * 0.6f, 0, GUI.SmallFont);
|
||||
GUI.DrawString(spriteBatch, new Vector2(15, y + 35), "Current intensity: " + (int)Math.Round(currentIntensity * 100), Color.Lerp(Color.White, GUI.Style.Red, currentIntensity), Color.Black * 0.6f, 0, GUI.SmallFont);
|
||||
GUI.DrawString(spriteBatch, new Vector2(15, y + 50), "Target intensity: " + (int)Math.Round(targetIntensity * 100), Color.Lerp(Color.White, GUI.Style.Red, targetIntensity), Color.Black * 0.6f, 0, GUI.SmallFont);
|
||||
|
||||
GUI.DrawString(spriteBatch, new Vector2(15, y + 65), "AvgHealth: " + (int)Math.Round(avgCrewHealth * 100), Color.Lerp(GUI.Style.Red, GUI.Style.Green, avgCrewHealth), Color.Black * 0.6f, 0, GUI.SmallFont);
|
||||
GUI.DrawString(spriteBatch, new Vector2(15, y + 80), "AvgHullIntegrity: " + (int)Math.Round(avgHullIntegrity * 100), Color.Lerp(GUI.Style.Red, GUI.Style.Green, avgHullIntegrity), Color.Black * 0.6f, 0, GUI.SmallFont);
|
||||
GUI.DrawString(spriteBatch, new Vector2(15, y + 95), "FloodingAmount: " + (int)Math.Round(floodingAmount * 100), Color.Lerp(GUI.Style.Green, GUI.Style.Red, floodingAmount), Color.Black * 0.6f, 0, GUI.SmallFont);
|
||||
GUI.DrawString(spriteBatch, new Vector2(15, y + 110), "FireAmount: " + (int)Math.Round(fireAmount * 100), Color.Lerp(GUI.Style.Green, GUI.Style.Red, fireAmount), Color.Black * 0.6f, 0, GUI.SmallFont);
|
||||
GUI.DrawString(spriteBatch, new Vector2(15, y + 125), "EnemyDanger: " + (int)Math.Round(enemyDanger * 100), Color.Lerp(GUI.Style.Green, GUI.Style.Red, enemyDanger), Color.Black * 0.6f, 0, GUI.SmallFont);
|
||||
|
||||
#if DEBUG
|
||||
if (PlayerInput.KeyDown(Microsoft.Xna.Framework.Input.Keys.LeftAlt) &&
|
||||
PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.T))
|
||||
{
|
||||
eventCoolDown = 1.0f;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (intensityGraph == null)
|
||||
{
|
||||
intensityGraph = new Graph();
|
||||
targetIntensityGraph = new Graph();
|
||||
}
|
||||
|
||||
intensityGraphUpdateInterval = 5.0f;
|
||||
if (Timing.TotalTime > lastIntensityUpdate + intensityGraphUpdateInterval)
|
||||
{
|
||||
intensityGraph.Update(currentIntensity);
|
||||
targetIntensityGraph.Update(targetIntensity);
|
||||
lastIntensityUpdate = (float)Timing.TotalTime;
|
||||
}
|
||||
|
||||
Rectangle graphRect = new Rectangle(15, y + 150, 150, 50);
|
||||
|
||||
GUI.DrawRectangle(spriteBatch, graphRect, Color.Black * 0.5f, true);
|
||||
intensityGraph.Draw(spriteBatch, graphRect, 1.0f, 0.0f, Color.Lerp(Color.White, GUI.Style.Red, currentIntensity));
|
||||
targetIntensityGraph.Draw(spriteBatch, graphRect, 1.0f, 0.0f, Color.Lerp(Color.White, GUI.Style.Red, targetIntensity) * 0.5f);
|
||||
|
||||
GUI.DrawLine(spriteBatch,
|
||||
new Vector2(graphRect.Right, graphRect.Y + graphRect.Height * (1.0f - eventThreshold)),
|
||||
new Vector2(graphRect.Right + 5, graphRect.Y + graphRect.Height * (1.0f - eventThreshold)), Color.Orange, 0, 1);
|
||||
|
||||
y = graphRect.Bottom + 20;
|
||||
if (eventCoolDown > 0.0f)
|
||||
{
|
||||
GUI.DrawString(spriteBatch, new Vector2(graphRect.X, y), "Event cooldown active: " + (int)eventCoolDown, Color.LightGreen * 0.8f, null, 0, GUI.SmallFont);
|
||||
y += 15;
|
||||
}
|
||||
else if (currentIntensity > eventThreshold)
|
||||
{
|
||||
GUI.DrawString(spriteBatch, new Vector2(graphRect.X, y),
|
||||
"Intensity too high for new events: " + (int)(currentIntensity * 100) + "%/" + (int)(eventThreshold * 100) + "%", Color.LightGreen * 0.8f, null, 0, GUI.SmallFont);
|
||||
y += 15;
|
||||
}
|
||||
foreach (ScriptedEventSet eventSet in pendingEventSets)
|
||||
{
|
||||
float distanceTraveled = MathHelper.Clamp(
|
||||
(Submarine.MainSub.WorldPosition.X - level.StartPosition.X) / (level.EndPosition.X - level.StartPosition.X),
|
||||
0.0f, 1.0f);
|
||||
|
||||
GUI.DrawString(spriteBatch, new Vector2(graphRect.X, y), "New event (ID " + eventSet.DebugIdentifier + ") after: ", Color.Orange * 0.8f, null, 0, GUI.SmallFont);
|
||||
y += 12;
|
||||
|
||||
if ((Submarine.MainSub == null || distanceTraveled < eventSet.MinDistanceTraveled) &&
|
||||
roundDuration < eventSet.MinMissionTime)
|
||||
{
|
||||
GUI.DrawString(spriteBatch, new Vector2(graphRect.X, y),
|
||||
" " + (int)(eventSet.MinDistanceTraveled * 100.0f) + "% travelled (current: " + (int)(distanceTraveled * 100.0f) + " %)",
|
||||
Color.Orange * 0.8f, null, 0, GUI.SmallFont);
|
||||
y += 12;
|
||||
}
|
||||
if (CurrentIntensity < eventSet.MinIntensity || CurrentIntensity > eventSet.MaxIntensity)
|
||||
{
|
||||
GUI.DrawString(spriteBatch, new Vector2(graphRect.X, y),
|
||||
" intensity between " + ((int)eventSet.MinIntensity) + " and " + ((int)eventSet.MaxIntensity),
|
||||
Color.Orange * 0.8f, null, 0, GUI.SmallFont);
|
||||
y += 12;
|
||||
}
|
||||
if (roundDuration < eventSet.MinMissionTime)
|
||||
{
|
||||
GUI.DrawString(spriteBatch, new Vector2(graphRect.X, y),
|
||||
" " + (int)(eventSet.MinMissionTime - roundDuration) + " s",
|
||||
Color.Orange * 0.8f, null, 0, GUI.SmallFont);
|
||||
}
|
||||
|
||||
y += 15;
|
||||
}
|
||||
|
||||
|
||||
GUI.DrawString(spriteBatch, new Vector2(graphRect.X, y), "Current events: ", Color.White * 0.9f, null, 0, GUI.SmallFont);
|
||||
y += 12;
|
||||
foreach (ScriptedEvent scriptedEvent in activeEvents)
|
||||
{
|
||||
if (scriptedEvent.IsFinished) { continue; }
|
||||
GUI.DrawString(spriteBatch, new Vector2(graphRect.X + 5, y), scriptedEvent.ToString(), Color.White * 0.8f, null, 0, GUI.SmallFont);
|
||||
y += 12;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class CargoMission : Mission
|
||||
{
|
||||
public override void ClientReadInitial(IReadMessage msg)
|
||||
{
|
||||
ushort itemCount = msg.ReadUInt16();
|
||||
for (int i = 0; i < itemCount; i++)
|
||||
{
|
||||
items.Add(Item.ReadSpawnData(msg));
|
||||
}
|
||||
if (items.Contains(null))
|
||||
{
|
||||
throw new System.Exception("Error in CargoMission.ClientReadInitial: item list contains null (mission: " + Prefab.Identifier + ")");
|
||||
}
|
||||
if (items.Count != itemCount)
|
||||
{
|
||||
throw new System.Exception("Error in CargoMission.ClientReadInitial: item count does not match the server count (" + itemCount + " != " + items.Count + "mission: " + Prefab.Identifier + ")");
|
||||
}
|
||||
if (requiredDeliveryAmount == 0) { requiredDeliveryAmount = items.Count; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class CombatMission
|
||||
{
|
||||
public override string Description
|
||||
{
|
||||
get
|
||||
{
|
||||
if (descriptions == null) return "";
|
||||
|
||||
if (GameMain.Client?.Character == null)
|
||||
{
|
||||
//non-team-specific description
|
||||
return descriptions[0];
|
||||
}
|
||||
|
||||
//team specific
|
||||
return descriptions[GameMain.Client.Character.TeamID == Character.TeamType.Team1 ? 1 : 2];
|
||||
}
|
||||
}
|
||||
|
||||
public override void ClientReadInitial(IReadMessage msg)
|
||||
{
|
||||
//do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class Mission
|
||||
{
|
||||
partial void ShowMessageProjSpecific(int missionState)
|
||||
{
|
||||
int messageIndex = missionState - 1;
|
||||
if (messageIndex >= Headers.Count && messageIndex >= Messages.Count) { return; }
|
||||
if (messageIndex < 0) { return; }
|
||||
|
||||
string header = messageIndex < Headers.Count ? Headers[messageIndex] : "";
|
||||
string message = messageIndex < Messages.Count ? Messages[messageIndex] : "";
|
||||
|
||||
new GUIMessageBox(header, message, buttons: new string[0], type: GUIMessageBox.Type.InGame, icon: Prefab.Icon)
|
||||
{
|
||||
IconColor = Prefab.IconColor
|
||||
};
|
||||
}
|
||||
|
||||
public void ClientRead(IReadMessage msg)
|
||||
{
|
||||
State = msg.ReadInt16();
|
||||
}
|
||||
|
||||
public abstract void ClientReadInitial(IReadMessage msg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class MissionMode : GameMode
|
||||
{
|
||||
public override void ShowStartMessage()
|
||||
{
|
||||
if (mission == null) return;
|
||||
|
||||
new GUIMessageBox(mission.Name, mission.Description, new string[0], type: GUIMessageBox.Type.InGame, icon: mission.Prefab.Icon)
|
||||
{
|
||||
IconColor = mission.Prefab.IconColor,
|
||||
UserData = "missionstartmessage"
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class MissionPrefab
|
||||
{
|
||||
public Sprite Icon
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public Color IconColor
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
partial void InitProjSpecific(XElement element)
|
||||
{
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
if (!subElement.Name.ToString().Equals("icon", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
Icon = new Sprite(subElement);
|
||||
IconColor = subElement.GetAttributeColor("color", Color.White);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class MonsterMission : Mission
|
||||
{
|
||||
public override void ClientReadInitial(IReadMessage msg)
|
||||
{
|
||||
byte monsterCount = msg.ReadByte();
|
||||
for (int i = 0; i < monsterCount; i++)
|
||||
{
|
||||
monsters.Add(Character.ReadSpawnData(msg));
|
||||
}
|
||||
if (monsters.Contains(null))
|
||||
{
|
||||
throw new System.Exception("Error in MonsterMission.ClientReadInitial: monster list contains null (mission: " + Prefab.Identifier + ")");
|
||||
}
|
||||
if (monsters.Count != monsterCount)
|
||||
{
|
||||
throw new System.Exception("Error in MonsterMission.ClientReadInitial: monster count does not match the server count (" + monsterCount + " != " + monsters.Count + "mission: " + Prefab.Identifier + ")");
|
||||
}
|
||||
InitializeMonsters(monsters);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using Barotrauma.Networking;
|
||||
using FarseerPhysics;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class SalvageMission : Mission
|
||||
{
|
||||
public override void ClientReadInitial(IReadMessage msg)
|
||||
{
|
||||
item = Item.ReadSpawnData(msg);
|
||||
if (item == null)
|
||||
{
|
||||
throw new System.Exception("Error in SalvageMission.ClientReadInitial: spawned item was null (mission: " + Prefab.Identifier + ")");
|
||||
}
|
||||
|
||||
item.body.FarseerBody.BodyType = BodyType.Kinematic;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
|
||||
namespace Barotrauma.Extensions
|
||||
{
|
||||
public static class ColorExtensions
|
||||
{
|
||||
public static Color Multiply(this Color color, float value, bool onlyAlpha = false)
|
||||
{
|
||||
return onlyAlpha ?
|
||||
new Color(color.R, color.G, color.B, (byte)(color.A * value)) :
|
||||
new Color((byte)(color.R * value), (byte)(color.G * value), (byte)(color.B * value), (byte)(color.A * value));
|
||||
}
|
||||
|
||||
public static Color Opaque(this Color color)
|
||||
{
|
||||
return new Color(color.R, color.G, color.B, (byte)255);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,618 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using SharpFont;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class ColorData
|
||||
{
|
||||
public int StartIndex, EndIndex;
|
||||
public Color Color;
|
||||
|
||||
private const char colorDefinitionIndicator = '‖';
|
||||
private const char lineChangeIndicator = '\n';
|
||||
private const string colorDefinitionStartString = "‖color:";
|
||||
private const string coloringEndDefinition = "‖color:end‖";
|
||||
|
||||
public static List<ColorData> GetColorData(string text, out string sanitizedText)
|
||||
{
|
||||
List<ColorData> textColors = null;
|
||||
if (text != null && text.IndexOf(colorDefinitionIndicator) != -1 && text.Contains(colorDefinitionStartString))
|
||||
{
|
||||
textColors = new List<ColorData>();
|
||||
List<int> lineChangeIndexes = null;
|
||||
|
||||
int currentIndex = text.IndexOf(lineChangeIndicator);
|
||||
if (currentIndex != -1)
|
||||
{
|
||||
lineChangeIndexes = new List<int>();
|
||||
lineChangeIndexes.Add(currentIndex);
|
||||
int startIndex = currentIndex + 1;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (startIndex >= text.Length) break;
|
||||
currentIndex = text.IndexOf(lineChangeIndicator, startIndex);
|
||||
if (currentIndex == -1) break;
|
||||
lineChangeIndexes.Add(currentIndex);
|
||||
startIndex = currentIndex + 1;
|
||||
}
|
||||
}
|
||||
|
||||
while (text.IndexOf(colorDefinitionStartString) != -1)
|
||||
{
|
||||
ColorData colorData = new ColorData();
|
||||
|
||||
int colorDefinitionStartIndex = text.IndexOf(colorDefinitionStartString);
|
||||
int colorDefinitionEndIndex = text.IndexOf(colorDefinitionIndicator, colorDefinitionStartIndex + 1);
|
||||
|
||||
string[] colorDefinition = text.Substring(colorDefinitionStartIndex + colorDefinitionStartString.Length, colorDefinitionEndIndex - colorDefinitionStartIndex - colorDefinitionStartString.Length).Split(',');
|
||||
|
||||
colorData.StartIndex = colorDefinitionStartIndex;
|
||||
colorData.Color = new Color(int.Parse(colorDefinition[0]), int.Parse(colorDefinition[1]), int.Parse(colorDefinition[2]));
|
||||
text = text.Remove(colorDefinitionStartIndex, colorDefinitionEndIndex - colorDefinitionStartIndex + 1);
|
||||
colorData.EndIndex = text.IndexOf(coloringEndDefinition);
|
||||
text = text.Remove(colorData.EndIndex, coloringEndDefinition.Length);
|
||||
|
||||
if (lineChangeIndexes != null)
|
||||
{
|
||||
for (int i = 0; i < lineChangeIndexes.Count; i++)
|
||||
{
|
||||
if (colorData.StartIndex > lineChangeIndexes[i])
|
||||
{
|
||||
colorData.StartIndex--;
|
||||
colorData.EndIndex--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
textColors.Add(colorData);
|
||||
}
|
||||
}
|
||||
|
||||
sanitizedText = text;
|
||||
return textColors;
|
||||
}
|
||||
}
|
||||
public class ScalableFont : IDisposable
|
||||
{
|
||||
private static List<ScalableFont> FontList = new List<ScalableFont>();
|
||||
private static Library Lib = null;
|
||||
private static object mutex = new object();
|
||||
|
||||
private string filename;
|
||||
private Face face;
|
||||
private uint size;
|
||||
private int baseHeight;
|
||||
//private int lineHeight;
|
||||
private Dictionary<uint, GlyphData> texCoords;
|
||||
private List<Texture2D> textures;
|
||||
private GraphicsDevice graphicsDevice;
|
||||
|
||||
private Vector2 currentDynamicAtlasCoords;
|
||||
private int currentDynamicAtlasNextY;
|
||||
uint[] currentDynamicPixelBuffer;
|
||||
|
||||
public bool DynamicLoading
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public bool IsCJK
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public uint Size
|
||||
{
|
||||
get
|
||||
{
|
||||
return size;
|
||||
}
|
||||
set
|
||||
{
|
||||
size = value;
|
||||
if (graphicsDevice != null) RenderAtlas(graphicsDevice, charRanges, texDims, baseChar);
|
||||
}
|
||||
}
|
||||
|
||||
private uint[] charRanges;
|
||||
private int texDims;
|
||||
private uint baseChar;
|
||||
|
||||
private struct GlyphData
|
||||
{
|
||||
public int texIndex;
|
||||
public Vector2 drawOffset;
|
||||
public float advance;
|
||||
public Rectangle texCoords;
|
||||
}
|
||||
|
||||
public ScalableFont(XElement element, GraphicsDevice gd = null)
|
||||
: this(
|
||||
element.GetAttributeString("file", ""),
|
||||
(uint)element.GetAttributeInt("size", 14),
|
||||
gd,
|
||||
element.GetAttributeBool("dynamicloading", false),
|
||||
element.GetAttributeBool("iscjk", false))
|
||||
{
|
||||
}
|
||||
|
||||
public ScalableFont(string filename, uint size, GraphicsDevice gd = null, bool dynamicLoading = false, bool isCJK = false)
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
if (Lib == null) Lib = new Library();
|
||||
this.filename = filename;
|
||||
this.face = null;
|
||||
foreach (ScalableFont font in FontList)
|
||||
{
|
||||
if (font.filename == filename)
|
||||
{
|
||||
this.face = font.face;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (this.face == null)
|
||||
{
|
||||
this.face = new Face(Lib, filename);
|
||||
}
|
||||
this.size = size;
|
||||
this.textures = new List<Texture2D>();
|
||||
this.texCoords = new Dictionary<uint, GlyphData>();
|
||||
this.DynamicLoading = dynamicLoading;
|
||||
this.IsCJK = isCJK;
|
||||
this.graphicsDevice = gd;
|
||||
|
||||
if (gd != null && !dynamicLoading)
|
||||
{
|
||||
RenderAtlas(gd);
|
||||
}
|
||||
|
||||
FontList.Add(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renders the font into at least one texture atlas, which is simply a collection of all glyphs in the ranges defined by charRanges.
|
||||
/// Don't call this too often or with very large sizes.
|
||||
/// </summary>
|
||||
/// <param name="gd">Graphics device, required to create textures.</param>
|
||||
/// <param name="charRanges">Character ranges between each even element with their corresponding odd element. Default is 0x20 to 0xFFFF.</param>
|
||||
/// <param name="texDims">Texture dimensions. Default is 512x512.</param>
|
||||
/// <param name="baseChar">Base character used to shift all other characters downwards when rendering. Defaults to T.</param>
|
||||
public void RenderAtlas(GraphicsDevice gd, uint[] charRanges = null, int texDims = 1024, uint baseChar = 0x54)
|
||||
{
|
||||
if (DynamicLoading) { return; }
|
||||
|
||||
if (charRanges == null)
|
||||
{
|
||||
charRanges = new uint[] { 0x20, 0xFFFF };
|
||||
}
|
||||
this.charRanges = charRanges;
|
||||
this.texDims = texDims;
|
||||
this.baseChar = baseChar;
|
||||
|
||||
textures.ForEach(t => t.Dispose());
|
||||
textures.Clear();
|
||||
texCoords.Clear();
|
||||
|
||||
uint[] pixelBuffer = new uint[texDims * texDims];
|
||||
for (int i = 0; i < texDims * texDims; i++)
|
||||
{
|
||||
pixelBuffer[i] = 0;
|
||||
}
|
||||
|
||||
CrossThread.RequestExecutionOnMainThread(() =>
|
||||
{
|
||||
textures.Add(new Texture2D(gd, texDims, texDims, false, SurfaceFormat.Color));
|
||||
});
|
||||
int texIndex = 0;
|
||||
|
||||
Vector2 currentCoords = Vector2.Zero;
|
||||
int nextY = 0;
|
||||
|
||||
lock (mutex)
|
||||
{
|
||||
face.SetPixelSizes(0, size);
|
||||
face.LoadGlyph(face.GetCharIndex(baseChar), LoadFlags.Default, LoadTarget.Normal);
|
||||
baseHeight = face.Glyph.Metrics.Height.ToInt32();
|
||||
|
||||
for (int i = 0; i < charRanges.Length; i += 2)
|
||||
{
|
||||
uint start = charRanges[i];
|
||||
uint end = charRanges[i + 1];
|
||||
for (uint j = start; j <= end; j++)
|
||||
{
|
||||
uint glyphIndex = face.GetCharIndex(j);
|
||||
if (glyphIndex == 0) continue;
|
||||
face.LoadGlyph(glyphIndex, LoadFlags.Default, LoadTarget.Normal);
|
||||
if (face.Glyph.Metrics.Width == 0 || face.Glyph.Metrics.Height == 0)
|
||||
{
|
||||
if (face.Glyph.Metrics.HorizontalAdvance > 0)
|
||||
{
|
||||
//glyph is empty, but char still applies advance
|
||||
GlyphData blankData = new GlyphData();
|
||||
blankData.advance = (float)face.Glyph.Metrics.HorizontalAdvance;
|
||||
blankData.texIndex = -1; //indicates no texture because the glyph is empty
|
||||
texCoords.Add(j, blankData);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
//stacktrace doesn't really work that well when RenderGlyph throws an exception
|
||||
face.Glyph.RenderGlyph(RenderMode.Normal);
|
||||
byte[] bitmap = face.Glyph.Bitmap.BufferData;
|
||||
int glyphWidth = face.Glyph.Bitmap.Width;
|
||||
int glyphHeight = bitmap.Length / glyphWidth;
|
||||
|
||||
//if (glyphHeight>lineHeight) lineHeight=glyphHeight;
|
||||
|
||||
if (glyphWidth > texDims - 1 || glyphHeight > texDims - 1)
|
||||
{
|
||||
throw new Exception(filename + ", " + size.ToString() + ", " + (char)j + "; Glyph dimensions exceed texture atlas dimensions");
|
||||
}
|
||||
|
||||
nextY = Math.Max(nextY, glyphHeight + 2);
|
||||
|
||||
if (currentCoords.X + glyphWidth + 2 > texDims - 1)
|
||||
{
|
||||
currentCoords.X = 0;
|
||||
currentCoords.Y += nextY;
|
||||
nextY = 0;
|
||||
}
|
||||
if (currentCoords.Y + glyphHeight + 2 > texDims - 1)
|
||||
{
|
||||
currentCoords.X = 0;
|
||||
currentCoords.Y = 0;
|
||||
CrossThread.RequestExecutionOnMainThread(() =>
|
||||
{
|
||||
textures[texIndex].SetData<uint>(pixelBuffer);
|
||||
textures.Add(new Texture2D(gd, texDims, texDims, false, SurfaceFormat.Color));
|
||||
});
|
||||
texIndex++;
|
||||
for (int k = 0; k < texDims * texDims; k++)
|
||||
{
|
||||
pixelBuffer[k] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
GlyphData newData = new GlyphData
|
||||
{
|
||||
advance = (float)face.Glyph.Metrics.HorizontalAdvance,
|
||||
texIndex = texIndex,
|
||||
texCoords = new Rectangle((int)currentCoords.X, (int)currentCoords.Y, glyphWidth, glyphHeight),
|
||||
drawOffset = new Vector2(face.Glyph.BitmapLeft, baseHeight * 14 / 10 - face.Glyph.BitmapTop)
|
||||
};
|
||||
texCoords.Add(j, newData);
|
||||
|
||||
for (int y = 0; y < glyphHeight; y++)
|
||||
{
|
||||
for (int x = 0; x < glyphWidth; x++)
|
||||
{
|
||||
byte byteColor = bitmap[x + y * glyphWidth];
|
||||
pixelBuffer[((int)currentCoords.X + x) + ((int)currentCoords.Y + y) * texDims] = (uint)(byteColor << 24 | 0x00ffffff);
|
||||
}
|
||||
}
|
||||
|
||||
currentCoords.X += glyphWidth + 2;
|
||||
}
|
||||
CrossThread.RequestExecutionOnMainThread(() =>
|
||||
{
|
||||
textures[texIndex].SetData<uint>(pixelBuffer);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void DynamicRenderAtlas(GraphicsDevice gd, uint character, int texDims = 1024, uint baseChar = 0x54)
|
||||
{
|
||||
if (System.Threading.Thread.CurrentThread != GameMain.MainThread)
|
||||
{
|
||||
CrossThread.RequestExecutionOnMainThread(() =>
|
||||
{
|
||||
DynamicRenderAtlas(gd, character, texDims, baseChar);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] bitmap;
|
||||
int glyphWidth; int glyphHeight;
|
||||
Fixed26Dot6 horizontalAdvance;
|
||||
Vector2 drawOffset;
|
||||
|
||||
lock (mutex)
|
||||
{
|
||||
if (texCoords.ContainsKey(character)) { return; }
|
||||
if (textures.Count == 0)
|
||||
{
|
||||
this.texDims = texDims;
|
||||
this.baseChar = baseChar;
|
||||
face.SetPixelSizes(0, size);
|
||||
face.LoadGlyph(face.GetCharIndex(baseChar), LoadFlags.Default, LoadTarget.Normal);
|
||||
baseHeight = face.Glyph.Metrics.Height.ToInt32();
|
||||
textures.Add(new Texture2D(gd, texDims, texDims, false, SurfaceFormat.Color));
|
||||
}
|
||||
|
||||
uint glyphIndex = face.GetCharIndex(character);
|
||||
if (glyphIndex == 0) { return; }
|
||||
|
||||
face.SetPixelSizes(0, size);
|
||||
face.LoadGlyph(glyphIndex, LoadFlags.Default, LoadTarget.Normal);
|
||||
if (face.Glyph.Metrics.Width == 0 || face.Glyph.Metrics.Height == 0)
|
||||
{
|
||||
if (face.Glyph.Metrics.HorizontalAdvance > 0)
|
||||
{
|
||||
//glyph is empty, but char still applies advance
|
||||
GlyphData blankData = new GlyphData();
|
||||
blankData.advance = (float)face.Glyph.Metrics.HorizontalAdvance;
|
||||
blankData.texIndex = -1; //indicates no texture because the glyph is empty
|
||||
texCoords.Add(character, blankData);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//stacktrace doesn't really work that well when RenderGlyph throws an exception
|
||||
face.Glyph.RenderGlyph(RenderMode.Normal);
|
||||
bitmap = (byte[])face.Glyph.Bitmap.BufferData.Clone();
|
||||
glyphWidth = face.Glyph.Bitmap.Width;
|
||||
glyphHeight = bitmap.Length / glyphWidth;
|
||||
horizontalAdvance = face.Glyph.Metrics.HorizontalAdvance;
|
||||
drawOffset = new Vector2(face.Glyph.BitmapLeft, baseHeight * 14 / 10 - face.Glyph.BitmapTop);
|
||||
|
||||
if (glyphWidth > texDims - 1 || glyphHeight > texDims - 1)
|
||||
{
|
||||
throw new Exception(filename + ", " + size.ToString() + ", " + (char)character + "; Glyph dimensions exceed texture atlas dimensions");
|
||||
}
|
||||
|
||||
currentDynamicAtlasNextY = Math.Max(currentDynamicAtlasNextY, glyphHeight + 2);
|
||||
if (currentDynamicAtlasCoords.X + glyphWidth + 2 > texDims - 1)
|
||||
{
|
||||
currentDynamicAtlasCoords.X = 0;
|
||||
currentDynamicAtlasCoords.Y += currentDynamicAtlasNextY;
|
||||
currentDynamicAtlasNextY = 0;
|
||||
}
|
||||
//no more room in current texture atlas, create a new one
|
||||
if (currentDynamicAtlasCoords.Y + glyphHeight + 2 > texDims - 1)
|
||||
{
|
||||
currentDynamicAtlasCoords.X = 0;
|
||||
currentDynamicAtlasCoords.Y = 0;
|
||||
currentDynamicAtlasNextY = 0;
|
||||
textures.Add(new Texture2D(gd, texDims, texDims, false, SurfaceFormat.Color));
|
||||
currentDynamicPixelBuffer = null;
|
||||
}
|
||||
|
||||
GlyphData newData = new GlyphData
|
||||
{
|
||||
advance = (float)horizontalAdvance,
|
||||
texIndex = textures.Count - 1,
|
||||
texCoords = new Rectangle((int)currentDynamicAtlasCoords.X, (int)currentDynamicAtlasCoords.Y, glyphWidth, glyphHeight),
|
||||
drawOffset = drawOffset
|
||||
};
|
||||
texCoords.Add(character, newData);
|
||||
|
||||
if (currentDynamicPixelBuffer == null)
|
||||
{
|
||||
currentDynamicPixelBuffer = new uint[texDims * texDims];
|
||||
textures[newData.texIndex].GetData<uint>(currentDynamicPixelBuffer, 0, texDims * texDims);
|
||||
}
|
||||
|
||||
for (int y = 0; y < glyphHeight; y++)
|
||||
{
|
||||
for (int x = 0; x < glyphWidth; x++)
|
||||
{
|
||||
byte byteColor = bitmap[x + y * glyphWidth];
|
||||
currentDynamicPixelBuffer[((int)currentDynamicAtlasCoords.X + x) + ((int)currentDynamicAtlasCoords.Y + y) * texDims] = (uint)(byteColor << 24 | 0x00ffffff);
|
||||
}
|
||||
}
|
||||
textures[newData.texIndex].SetData<uint>(currentDynamicPixelBuffer);
|
||||
|
||||
currentDynamicAtlasCoords.X += glyphWidth + 2;
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawString(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects se, float layerDepth)
|
||||
{
|
||||
if (textures.Count == 0 && !DynamicLoading) { return; }
|
||||
|
||||
int lineNum = 0;
|
||||
Vector2 currentPos = position;
|
||||
Vector2 advanceUnit = rotation == 0.0f ? Vector2.UnitX : new Vector2((float)Math.Cos(rotation), (float)Math.Sin(rotation));
|
||||
for (int i = 0; i < text.Length; i++)
|
||||
{
|
||||
if (text[i] == '\n')
|
||||
{
|
||||
lineNum++;
|
||||
currentPos = position;
|
||||
currentPos.X -= baseHeight * 1.8f * lineNum * advanceUnit.Y * scale.Y;
|
||||
currentPos.Y += baseHeight * 1.8f * lineNum * advanceUnit.X * scale.Y;
|
||||
continue;
|
||||
}
|
||||
|
||||
uint charIndex = text[i];
|
||||
if (DynamicLoading)
|
||||
{
|
||||
DynamicRenderAtlas(graphicsDevice, charIndex);
|
||||
}
|
||||
|
||||
if (texCoords.TryGetValue(charIndex, out GlyphData gd) || texCoords.TryGetValue(9633, out gd)) //9633 = white square
|
||||
{
|
||||
if (gd.texIndex >= 0)
|
||||
{
|
||||
Texture2D tex = textures[gd.texIndex];
|
||||
Vector2 drawOffset;
|
||||
drawOffset.X = gd.drawOffset.X * advanceUnit.X * scale.X - gd.drawOffset.Y * advanceUnit.Y * scale.Y;
|
||||
drawOffset.Y = gd.drawOffset.X * advanceUnit.Y * scale.Y + gd.drawOffset.Y * advanceUnit.X * scale.X;
|
||||
|
||||
sb.Draw(tex, currentPos + drawOffset, gd.texCoords, color, rotation, origin, scale, se, layerDepth);
|
||||
}
|
||||
currentPos += gd.advance * advanceUnit * scale.X;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawString(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, float scale, SpriteEffects se, float layerDepth)
|
||||
{
|
||||
DrawString(sb, text, position, color, rotation, origin, new Vector2(scale), se, layerDepth);
|
||||
}
|
||||
|
||||
public void DrawString(SpriteBatch sb, string text, Vector2 position, Color color)
|
||||
{
|
||||
if (textures.Count == 0 && !DynamicLoading) { return; }
|
||||
|
||||
Vector2 currentPos = position;
|
||||
for (int i = 0; i < text.Length; i++)
|
||||
{
|
||||
if (text[i] == '\n')
|
||||
{
|
||||
currentPos.X = position.X;
|
||||
currentPos.Y += baseHeight * 1.8f;
|
||||
continue;
|
||||
}
|
||||
|
||||
uint charIndex = text[i];
|
||||
if (DynamicLoading)
|
||||
{
|
||||
DynamicRenderAtlas(graphicsDevice, charIndex);
|
||||
}
|
||||
|
||||
if (texCoords.TryGetValue(charIndex, out GlyphData gd) || texCoords.TryGetValue(9633, out gd)) //9633 = white square
|
||||
{
|
||||
if (gd.texIndex >= 0)
|
||||
{
|
||||
Texture2D tex = textures[gd.texIndex];
|
||||
sb.Draw(tex, currentPos + gd.drawOffset, gd.texCoords, color);
|
||||
}
|
||||
currentPos.X += gd.advance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawStringWithColors(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, float scale, SpriteEffects se, float layerDepth, List<ColorData> colorData)
|
||||
{
|
||||
DrawStringWithColors(sb, text, position, color, rotation, origin, new Vector2(scale), se, layerDepth, colorData);
|
||||
}
|
||||
|
||||
public void DrawStringWithColors(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects se, float layerDepth, List<ColorData> colorData)
|
||||
{
|
||||
if (textures.Count == 0 && !DynamicLoading) { return; }
|
||||
|
||||
int lineNum = 0;
|
||||
Vector2 currentPos = position;
|
||||
Vector2 advanceUnit = rotation == 0.0f ? Vector2.UnitX : new Vector2((float)Math.Cos(rotation), (float)Math.Sin(rotation));
|
||||
|
||||
int colorDataIndex = 0;
|
||||
ColorData currentColorData = colorData[colorDataIndex];
|
||||
|
||||
for (int i = 0; i < text.Length; i++)
|
||||
{
|
||||
if (text[i] == '\n')
|
||||
{
|
||||
lineNum++;
|
||||
currentPos = position;
|
||||
currentPos.X -= baseHeight * 1.8f * lineNum * advanceUnit.Y * scale.Y;
|
||||
currentPos.Y += baseHeight * 1.8f * lineNum * advanceUnit.X * scale.Y;
|
||||
continue;
|
||||
}
|
||||
|
||||
uint charIndex = text[i];
|
||||
if (DynamicLoading && !texCoords.ContainsKey(charIndex))
|
||||
{
|
||||
DynamicRenderAtlas(graphicsDevice, charIndex);
|
||||
}
|
||||
|
||||
Color currentTextColor;
|
||||
|
||||
if (currentColorData != null && i > currentColorData.EndIndex + lineNum)
|
||||
{
|
||||
colorDataIndex++;
|
||||
currentColorData = colorDataIndex < colorData.Count ? colorData[colorDataIndex] : null;
|
||||
}
|
||||
|
||||
if (currentColorData != null && currentColorData.StartIndex + lineNum <= i && i <= currentColorData.EndIndex + lineNum)
|
||||
{
|
||||
currentTextColor = currentColorData.Color;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentTextColor = color;
|
||||
}
|
||||
|
||||
if (texCoords.TryGetValue(charIndex, out GlyphData gd) || texCoords.TryGetValue(9633, out gd)) //9633 = white square
|
||||
{
|
||||
if (gd.texIndex >= 0)
|
||||
{
|
||||
Texture2D tex = textures[gd.texIndex];
|
||||
Vector2 drawOffset;
|
||||
drawOffset.X = gd.drawOffset.X * advanceUnit.X * scale.X - gd.drawOffset.Y * advanceUnit.Y * scale.Y;
|
||||
drawOffset.Y = gd.drawOffset.X * advanceUnit.Y * scale.Y + gd.drawOffset.Y * advanceUnit.X * scale.X;
|
||||
|
||||
sb.Draw(tex, currentPos + drawOffset, gd.texCoords, currentTextColor, rotation, origin, scale, se, layerDepth);
|
||||
}
|
||||
currentPos += gd.advance * advanceUnit * scale.X;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2 MeasureString(string text)
|
||||
{
|
||||
if (text == null)
|
||||
{
|
||||
return Vector2.Zero;
|
||||
}
|
||||
|
||||
float currentLineX = 0.0f;
|
||||
Vector2 retVal = Vector2.Zero;
|
||||
retVal.Y = baseHeight * 1.8f;
|
||||
for (int i = 0; i < text.Length; i++)
|
||||
{
|
||||
if (text[i] == '\n')
|
||||
{
|
||||
currentLineX = 0.0f;
|
||||
retVal.Y += baseHeight * 1.8f;
|
||||
continue;
|
||||
}
|
||||
uint charIndex = text[i];
|
||||
if (DynamicLoading && !texCoords.ContainsKey(charIndex))
|
||||
{
|
||||
DynamicRenderAtlas(graphicsDevice, charIndex);
|
||||
}
|
||||
if (texCoords.TryGetValue(charIndex, out GlyphData gd))
|
||||
{
|
||||
currentLineX += gd.advance;
|
||||
}
|
||||
retVal.X = Math.Max(retVal.X, currentLineX);
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
public Vector2 MeasureChar(char c)
|
||||
{
|
||||
Vector2 retVal = Vector2.Zero;
|
||||
retVal.Y = baseHeight * 1.8f;
|
||||
if (DynamicLoading && !texCoords.ContainsKey(c))
|
||||
{
|
||||
DynamicRenderAtlas(graphicsDevice, c);
|
||||
}
|
||||
if (texCoords.TryGetValue(c, out GlyphData gd))
|
||||
{
|
||||
retVal.X = gd.advance;
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
FontList.Remove(this);
|
||||
foreach (Texture2D texture in textures)
|
||||
{
|
||||
texture.Dispose();
|
||||
}
|
||||
textures.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,409 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class ChatBox
|
||||
{
|
||||
public const string RadioChatString = "r; ";
|
||||
|
||||
private GUIListBox chatBox;
|
||||
private Point screenResolution;
|
||||
|
||||
public readonly ChatManager ChatManager = new ChatManager();
|
||||
|
||||
public bool IsSinglePlayer { get; private set; }
|
||||
|
||||
private bool _toggleOpen = true;
|
||||
public bool ToggleOpen
|
||||
{
|
||||
get { return _toggleOpen; }
|
||||
set
|
||||
{
|
||||
_toggleOpen = GameMain.Config.ChatOpen = value;
|
||||
if (value) hideableElements.Visible = true;
|
||||
}
|
||||
}
|
||||
private float openState;
|
||||
|
||||
public bool CloseAfterMessageSent;
|
||||
|
||||
private float prevUIScale;
|
||||
|
||||
//individual message texts that pop up when the chatbox is hidden
|
||||
const float PopupMessageDuration = 5.0f;
|
||||
private float popupMessageTimer;
|
||||
private Queue<GUIComponent> popupMessages = new Queue<GUIComponent>();
|
||||
|
||||
public GUITextBox.OnEnterHandler OnEnterMessage
|
||||
{
|
||||
get { return InputBox.OnEnterPressed; }
|
||||
set { InputBox.OnEnterPressed = value; }
|
||||
}
|
||||
|
||||
public GUIFrame GUIFrame { get; private set; }
|
||||
|
||||
public GUITextBox InputBox { get; private set; }
|
||||
|
||||
public GUIButton ToggleButton;
|
||||
|
||||
private GUIButton showNewMessagesButton;
|
||||
|
||||
private GUIFrame hideableElements;
|
||||
|
||||
public const int ToggleButtonWidthRaw = 30;
|
||||
private int popupMessageOffset;
|
||||
|
||||
public ChatBox(GUIComponent parent, bool isSinglePlayer)
|
||||
{
|
||||
this.IsSinglePlayer = isSinglePlayer;
|
||||
|
||||
screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
|
||||
|
||||
int toggleButtonWidth = (int)(ToggleButtonWidthRaw * GUI.Scale);
|
||||
GUIFrame = new GUIFrame(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.ChatBoxArea, parent.RectTransform), style: null);
|
||||
|
||||
hideableElements = new GUIFrame(new RectTransform(Vector2.One, GUIFrame.RectTransform), style: null);
|
||||
|
||||
var chatBoxHolder = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.875f), hideableElements.RectTransform), style: "ChatBox");
|
||||
chatBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.95f), chatBoxHolder.RectTransform, Anchor.CenterRight), style: null);
|
||||
|
||||
InputBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.125f), hideableElements.RectTransform, Anchor.BottomLeft),
|
||||
style: "ChatTextBox")
|
||||
{
|
||||
OverflowClip = true,
|
||||
Font = GUI.SmallFont,
|
||||
MaxTextLength = ChatMessage.MaxLength
|
||||
};
|
||||
|
||||
ChatManager.RegisterKeys(InputBox, ChatManager);
|
||||
|
||||
InputBox.OnDeselected += (gui, Keys) =>
|
||||
{
|
||||
ChatManager.Clear();
|
||||
ChatMessage.GetChatMessageCommand(InputBox.Text, out var message);
|
||||
if (string.IsNullOrEmpty(message))
|
||||
{
|
||||
if (CloseAfterMessageSent)
|
||||
{
|
||||
_toggleOpen = false;
|
||||
CloseAfterMessageSent = false;
|
||||
}
|
||||
}
|
||||
|
||||
//gui.Text = "";
|
||||
};
|
||||
|
||||
var chatSendButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.7f), InputBox.RectTransform, Anchor.CenterRight, scaleBasis: ScaleBasis.BothHeight), style: "GUIButtonToggleRight");
|
||||
chatSendButton.OnClicked += (GUIButton btn, object userdata) =>
|
||||
{
|
||||
InputBox.OnEnterPressed(InputBox, InputBox.Text);
|
||||
return true;
|
||||
};
|
||||
chatSendButton.RectTransform.AbsoluteOffset = new Point((int)(InputBox.Rect.Height * 0.15f), 0);
|
||||
InputBox.TextBlock.RectTransform.MaxSize
|
||||
= new Point((int)(InputBox.Rect.Width - chatSendButton.Rect.Width * 1.25f - InputBox.TextBlock.Padding.Z), int.MaxValue);
|
||||
|
||||
showNewMessagesButton = new GUIButton(new RectTransform(new Vector2(1f, 0.075f), GUIFrame.RectTransform, Anchor.BottomCenter) { RelativeOffset = new Vector2(0.0f, 0.125f) }, TextManager.Get("chat.shownewmessages"));
|
||||
showNewMessagesButton.OnClicked += (GUIButton btn, object userdata) =>
|
||||
{
|
||||
chatBox.ScrollBar.BarScrollValue = 1f;
|
||||
showNewMessagesButton.Visible = false;
|
||||
return true;
|
||||
};
|
||||
|
||||
showNewMessagesButton.Visible = false;
|
||||
ToggleOpen = GameMain.Config.ChatOpen;
|
||||
}
|
||||
|
||||
public bool TypingChatMessage(GUITextBox textBox, string text)
|
||||
{
|
||||
string command = ChatMessage.GetChatMessageCommand(text, out _);
|
||||
if (IsSinglePlayer)
|
||||
{
|
||||
//radio is the only allowed special message type in single player
|
||||
if (command != "r" && command != "radio")
|
||||
{
|
||||
command = "";
|
||||
}
|
||||
}
|
||||
|
||||
switch (command)
|
||||
{
|
||||
case "r":
|
||||
case "radio":
|
||||
textBox.TextColor = ChatMessage.MessageColor[(int)ChatMessageType.Radio];
|
||||
break;
|
||||
case "d":
|
||||
case "dead":
|
||||
textBox.TextColor = ChatMessage.MessageColor[(int)ChatMessageType.Dead];
|
||||
break;
|
||||
default:
|
||||
if (Character.Controlled != null && (Character.Controlled.IsDead || Character.Controlled.SpeechImpediment >= 100.0f))
|
||||
{
|
||||
textBox.TextColor = ChatMessage.MessageColor[(int)ChatMessageType.Dead];
|
||||
}
|
||||
else if (command != "") //PMing
|
||||
{
|
||||
textBox.TextColor = ChatMessage.MessageColor[(int)ChatMessageType.Private];
|
||||
}
|
||||
else
|
||||
{
|
||||
textBox.TextColor = ChatMessage.MessageColor[(int)ChatMessageType.Default];
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void AddMessage(ChatMessage message)
|
||||
{
|
||||
while (chatBox.Content.CountChildren > 60)
|
||||
{
|
||||
chatBox.RemoveChild(chatBox.Content.Children.First());
|
||||
}
|
||||
|
||||
float prevSize = chatBox.BarSize;
|
||||
|
||||
string displayedText = message.TranslatedText;
|
||||
string senderName = "";
|
||||
Color senderColor = Color.White;
|
||||
if (!string.IsNullOrWhiteSpace(message.SenderName))
|
||||
{
|
||||
senderName = (message.Type == ChatMessageType.Private ? "[PM] " : "") + message.SenderName;
|
||||
}
|
||||
if (message.Sender?.Info?.Job != null)
|
||||
{
|
||||
senderColor = Color.Lerp(message.Sender.Info.Job.Prefab.UIColor, Color.White, 0.25f);
|
||||
}
|
||||
|
||||
var msgHolder = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.0f), chatBox.Content.RectTransform, Anchor.TopCenter), style: null,
|
||||
color: ((chatBox.Content.CountChildren % 2) == 0) ? Color.Transparent : Color.Black * 0.1f);
|
||||
|
||||
GUITextBlock senderNameBlock = new GUITextBlock(new RectTransform(new Vector2(0.98f, 0.0f), msgHolder.RectTransform) { AbsoluteOffset = new Point((int)(5 * GUI.Scale), 0) },
|
||||
ChatMessage.GetTimeStamp(), textColor: Color.LightGray, font: GUI.SmallFont, textAlignment: Alignment.TopLeft, style: null)
|
||||
{
|
||||
CanBeFocused = true
|
||||
};
|
||||
if (!string.IsNullOrEmpty(senderName))
|
||||
{
|
||||
new GUITextBlock(new RectTransform(new Vector2(0.8f, 1.0f), senderNameBlock.RectTransform) { AbsoluteOffset = new Point((int)(senderNameBlock.TextSize.X), 0) },
|
||||
senderName, textColor: senderColor, font: GUI.SmallFont, textAlignment: Alignment.TopLeft, style: null)
|
||||
{
|
||||
CanBeFocused = true
|
||||
};
|
||||
}
|
||||
|
||||
var msgText =new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), msgHolder.RectTransform)
|
||||
{ AbsoluteOffset = new Point((int)(10 * GUI.Scale), senderNameBlock == null ? 0 : senderNameBlock.Rect.Height) },
|
||||
displayedText, textColor: message.Color, font: GUI.SmallFont, textAlignment: Alignment.TopLeft, style: null, wrap: true,
|
||||
color: ((chatBox.Content.CountChildren % 2) == 0) ? Color.Transparent : Color.Black * 0.1f)
|
||||
{
|
||||
UserData = message.SenderName,
|
||||
CanBeFocused = true
|
||||
};
|
||||
|
||||
if (message is OrderChatMessage orderChatMsg &&
|
||||
Character.Controlled != null &&
|
||||
orderChatMsg.TargetCharacter == Character.Controlled)
|
||||
{
|
||||
msgHolder.Flash(Color.OrangeRed * 0.6f, flashDuration: 5.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
msgHolder.Flash(Color.Yellow * 0.6f);
|
||||
}
|
||||
msgHolder.RectTransform.SizeChanged += Recalculate;
|
||||
Recalculate();
|
||||
void Recalculate()
|
||||
{
|
||||
msgHolder.RectTransform.SizeChanged -= Recalculate;
|
||||
//resize the holder to match the size of the message and add some spacing
|
||||
msgText.RectTransform.MaxSize = new Point(msgHolder.Rect.Width - msgText.RectTransform.AbsoluteOffset.X, int.MaxValue);
|
||||
senderNameBlock.RectTransform.MaxSize = new Point(msgHolder.Rect.Width - senderNameBlock.RectTransform.AbsoluteOffset.X, int.MaxValue);
|
||||
msgHolder.Children.ForEach(c => (c as GUITextBlock)?.CalculateHeightFromText());
|
||||
msgHolder.RectTransform.Resize(new Point(msgHolder.Rect.Width, msgHolder.Children.Sum(c => c.Rect.Height) + (int)(10 * GUI.Scale)), resizeChildren: false);
|
||||
msgHolder.RectTransform.SizeChanged += Recalculate;
|
||||
chatBox.RecalculateChildren();
|
||||
chatBox.UpdateScrollBarSize();
|
||||
}
|
||||
|
||||
CoroutineManager.StartCoroutine(UpdateMessageAnimation(msgHolder, 0.5f));
|
||||
|
||||
chatBox.UpdateScrollBarSize();
|
||||
|
||||
if (chatBox.ScrollBar.Visible && chatBox.ScrollBar.BarScroll < 1f)
|
||||
{
|
||||
showNewMessagesButton.Visible = true;
|
||||
}
|
||||
|
||||
if (!ToggleOpen)
|
||||
{
|
||||
var popupMsg = new GUIFrame(new RectTransform(Vector2.One, GUIFrame.RectTransform), style: "GUIToolTip")
|
||||
{
|
||||
Visible = false,
|
||||
CanBeFocused = false
|
||||
};
|
||||
var content = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), popupMsg.RectTransform, Anchor.Center));
|
||||
Vector2 senderTextSize = Vector2.Zero;
|
||||
if (!string.IsNullOrEmpty(senderName))
|
||||
{
|
||||
var senderText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform),
|
||||
senderName, textColor: senderColor, style: null, font: GUI.SmallFont)
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
senderTextSize = senderText.Font.MeasureString(senderText.WrappedText);
|
||||
senderText.RectTransform.MinSize = new Point(0, senderText.Rect.Height);
|
||||
}
|
||||
var msgPopupText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform),
|
||||
displayedText, textColor: message.Color, font: GUI.SmallFont, textAlignment: Alignment.BottomLeft, style: null, wrap: true)
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
msgPopupText.RectTransform.MinSize = new Point(0, msgPopupText.Rect.Height);
|
||||
Vector2 msgSize = msgPopupText.Font.MeasureString(msgPopupText.WrappedText);
|
||||
int textWidth = (int)Math.Max(msgSize.X + msgPopupText.Padding.X + msgPopupText.Padding.Z, senderTextSize.X) + 10;
|
||||
popupMsg.RectTransform.Resize(new Point((int)(textWidth / content.RectTransform.RelativeSize.X) , (int)((senderTextSize.Y + msgSize.Y) / content.RectTransform.RelativeSize.Y)), resizeChildren: true);
|
||||
popupMsg.RectTransform.IsFixedSize = true;
|
||||
content.Recalculate();
|
||||
popupMessages.Enqueue(popupMsg);
|
||||
}
|
||||
|
||||
if ((prevSize == 1.0f && chatBox.BarScroll == 0.0f) || (prevSize < 1.0f && chatBox.BarScroll == 1.0f)) chatBox.BarScroll = 1.0f;
|
||||
|
||||
GUISoundType soundType = GUISoundType.ChatMessage;
|
||||
if (message.Type == ChatMessageType.Radio)
|
||||
{
|
||||
soundType = GUISoundType.RadioMessage;
|
||||
}
|
||||
else if (message.Type == ChatMessageType.Dead)
|
||||
{
|
||||
soundType = GUISoundType.DeadMessage;
|
||||
}
|
||||
|
||||
GUI.PlayUISound(soundType);
|
||||
}
|
||||
|
||||
public void SetVisibility(bool visible)
|
||||
{
|
||||
GUIFrame.Parent.Visible = visible;
|
||||
}
|
||||
|
||||
private IEnumerable<object> UpdateMessageAnimation(GUIComponent message, float animDuration)
|
||||
{
|
||||
float timer = 0.0f;
|
||||
while (timer < animDuration)
|
||||
{
|
||||
timer += CoroutineManager.DeltaTime;
|
||||
float wavePhase = timer / animDuration * MathHelper.TwoPi;
|
||||
message.RectTransform.ScreenSpaceOffset =
|
||||
new Point((int)(Math.Sin(wavePhase) * (1.0f - timer / animDuration) * 50.0f), 0);
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
message.RectTransform.ScreenSpaceOffset = Point.Zero;
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
|
||||
private void SetUILayout()
|
||||
{
|
||||
GUIFrame.RectTransform.AbsoluteOffset = Point.Zero;
|
||||
GUIFrame.RectTransform.RelativeOffset = new Vector2(
|
||||
HUDLayoutSettings.ChatBoxArea.X / (float)GameMain.GraphicsWidth,
|
||||
HUDLayoutSettings.ChatBoxArea.Y / (float)GameMain.GraphicsHeight);
|
||||
GUIFrame.RectTransform.NonScaledSize = HUDLayoutSettings.ChatBoxArea.Size;
|
||||
|
||||
int toggleButtonWidth = (int)(ToggleButtonWidthRaw * GUI.Scale);
|
||||
GUIFrame.RectTransform.NonScaledSize -= new Point(toggleButtonWidth, 0);
|
||||
GUIFrame.RectTransform.AbsoluteOffset += new Point(toggleButtonWidth, 0);
|
||||
|
||||
popupMessageOffset = GameMain.GameSession.CrewManager.ReportButtonFrame.Rect.Width + GUIFrame.Rect.Width + (int)(20 * GUI.Scale);
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
if (GameMain.GraphicsWidth != screenResolution.X || GameMain.GraphicsHeight != screenResolution.Y || prevUIScale != GUI.Scale)
|
||||
{
|
||||
SetUILayout();
|
||||
screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
|
||||
prevUIScale = GUI.Scale;
|
||||
}
|
||||
|
||||
//hide chatbox when accessing the inventory of another character to prevent overlaps
|
||||
if (Character.Controlled?.SelectedCharacter?.Inventory != null &&
|
||||
Character.Controlled.SelectedCharacter.CanInventoryBeAccessed)
|
||||
{
|
||||
SetVisibility(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetVisibility(true);
|
||||
}
|
||||
|
||||
if (showNewMessagesButton.Visible && chatBox.ScrollBar.BarScroll == 1f)
|
||||
{
|
||||
showNewMessagesButton.Visible = false;
|
||||
}
|
||||
|
||||
if (ToggleButton != null)
|
||||
{
|
||||
ToggleButton.RectTransform.AbsoluteOffset = new Point(GUIFrame.Rect.Right, GUIFrame.Rect.Y + HUDLayoutSettings.ChatBoxArea.Height - ToggleButton.Rect.Height);
|
||||
}
|
||||
|
||||
if (ToggleOpen)
|
||||
{
|
||||
openState += deltaTime * 5.0f;
|
||||
//delete all popup messages when the chatbox is open
|
||||
while (popupMessages.Count > 0)
|
||||
{
|
||||
var popupMsg = popupMessages.Dequeue();
|
||||
popupMsg.Parent.RemoveChild(popupMsg);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
openState -= deltaTime * 5.0f;
|
||||
|
||||
//make the first popup message visible
|
||||
var popupMsg = popupMessages.Count > 0 ? popupMessages.Peek() : null;
|
||||
if (popupMsg != null)
|
||||
{
|
||||
popupMsg.Visible = true;
|
||||
//popup messages appear and disappear faster when there's more pending messages
|
||||
popupMessageTimer += deltaTime * popupMessages.Count * popupMessages.Count;
|
||||
if (popupMessageTimer > PopupMessageDuration)
|
||||
{
|
||||
//move the message out of the screen and delete it
|
||||
popupMsg.RectTransform.ScreenSpaceOffset =
|
||||
new Point((int)MathHelper.SmoothStep(popupMessageOffset, 10, (popupMessageTimer - PopupMessageDuration) * 5.0f), 0);
|
||||
if (popupMessageTimer > PopupMessageDuration + 1.0f)
|
||||
{
|
||||
popupMessageTimer = 0.0f;
|
||||
popupMsg.Parent.RemoveChild(popupMsg);
|
||||
popupMessages.Dequeue();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//move the message on the screen
|
||||
popupMsg.RectTransform.ScreenSpaceOffset = new Point(
|
||||
(int)MathHelper.SmoothStep(0, popupMessageOffset, popupMessageTimer * 5.0f), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
openState = MathHelper.Clamp(openState, 0.0f, 1.0f);
|
||||
int hiddenBoxOffset = -(GUIFrame.Rect.Width);
|
||||
GUIFrame.RectTransform.AbsoluteOffset =
|
||||
new Point((int)MathHelper.SmoothStep(hiddenBoxOffset, 0, openState), 0);
|
||||
hideableElements.Visible = openState > 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public enum TransitionMode
|
||||
{
|
||||
Linear,
|
||||
Smooth,
|
||||
Smoother,
|
||||
EaseIn,
|
||||
EaseOut,
|
||||
Exponential
|
||||
}
|
||||
|
||||
public enum SpriteFallBackState
|
||||
{
|
||||
None,
|
||||
Hover,
|
||||
Pressed,
|
||||
Selected,
|
||||
HoverSelected,
|
||||
Toggle
|
||||
}
|
||||
|
||||
public class GUIComponentStyle
|
||||
{
|
||||
public readonly Vector4 Padding;
|
||||
|
||||
public readonly Color Color;
|
||||
public readonly Color HoverColor;
|
||||
public readonly Color SelectedColor;
|
||||
public readonly Color PressedColor;
|
||||
public readonly Color DisabledColor;
|
||||
|
||||
public readonly Color TextColor;
|
||||
public readonly Color HoverTextColor;
|
||||
public readonly Color SelectedTextColor;
|
||||
public readonly Color DisabledTextColor;
|
||||
|
||||
public readonly float SpriteCrossFadeTime;
|
||||
public readonly float ColorCrossFadeTime;
|
||||
public readonly TransitionMode TransitionMode;
|
||||
|
||||
public readonly string Font;
|
||||
public readonly bool ForceUpperCase;
|
||||
|
||||
public readonly Color OutlineColor;
|
||||
|
||||
public readonly XElement Element;
|
||||
|
||||
public readonly Dictionary<GUIComponent.ComponentState, List<UISprite>> Sprites;
|
||||
|
||||
public SpriteFallBackState FallBackState;
|
||||
|
||||
public Dictionary<string, GUIComponentStyle> ChildStyles;
|
||||
|
||||
public readonly GUIStyle Style;
|
||||
|
||||
public readonly string Name;
|
||||
|
||||
public int? Width { get; private set; }
|
||||
public int? Height { get; private set; }
|
||||
|
||||
public GUIComponentStyle(XElement element, GUIStyle style)
|
||||
{
|
||||
Name = element.Name.LocalName;
|
||||
|
||||
Style = style;
|
||||
Element = element;
|
||||
|
||||
Sprites = new Dictionary<GUIComponent.ComponentState, List<UISprite>>();
|
||||
foreach (GUIComponent.ComponentState state in Enum.GetValues(typeof(GUIComponent.ComponentState)))
|
||||
{
|
||||
Sprites[state] = new List<UISprite>();
|
||||
}
|
||||
|
||||
ChildStyles = new Dictionary<string, GUIComponentStyle>();
|
||||
|
||||
Padding = element.GetAttributeVector4("padding", Vector4.Zero);
|
||||
|
||||
Color = element.GetAttributeColor("color", Color.Transparent);
|
||||
HoverColor = element.GetAttributeColor("hovercolor", Color);
|
||||
SelectedColor = element.GetAttributeColor("selectedcolor", Color);
|
||||
DisabledColor = element.GetAttributeColor("disabledcolor", Color);
|
||||
PressedColor = element.GetAttributeColor("pressedcolor", Color);
|
||||
OutlineColor = element.GetAttributeColor("outlinecolor", Color.Transparent);
|
||||
|
||||
TextColor = element.GetAttributeColor("textcolor", Color.Black);
|
||||
HoverTextColor = element.GetAttributeColor("hovertextcolor", TextColor);
|
||||
DisabledTextColor = element.GetAttributeColor("disabledtextcolor", TextColor);
|
||||
SelectedTextColor = element.GetAttributeColor("selectedtextcolor", TextColor);
|
||||
SpriteCrossFadeTime = element.GetAttributeFloat("spritefadetime", SpriteCrossFadeTime);
|
||||
ColorCrossFadeTime = element.GetAttributeFloat("colorfadetime", ColorCrossFadeTime);
|
||||
|
||||
if (Enum.TryParse(element.GetAttributeString("colortransition", string.Empty), ignoreCase: true, out TransitionMode transition))
|
||||
{
|
||||
TransitionMode = transition;
|
||||
}
|
||||
if (Enum.TryParse(element.GetAttributeString("fallbackstate", GUIComponent.ComponentState.None.ToString()), ignoreCase: true, out SpriteFallBackState s))
|
||||
{
|
||||
FallBackState = s;
|
||||
}
|
||||
|
||||
Font = element.GetAttributeString("font", "");
|
||||
ForceUpperCase = element.GetAttributeBool("forceuppercase", false);
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "sprite":
|
||||
UISprite newSprite = new UISprite(subElement);
|
||||
|
||||
GUIComponent.ComponentState spriteState = GUIComponent.ComponentState.None;
|
||||
if (subElement.Attribute("state") != null)
|
||||
{
|
||||
string stateStr = subElement.GetAttributeString("state", "None");
|
||||
Enum.TryParse(stateStr, out spriteState);
|
||||
Sprites[spriteState].Add(newSprite);
|
||||
//use the same sprite for Hover and HoverSelected if latter is not specified
|
||||
if (spriteState == GUIComponent.ComponentState.HoverSelected && !Sprites.ContainsKey(GUIComponent.ComponentState.HoverSelected))
|
||||
{
|
||||
Sprites[GUIComponent.ComponentState.HoverSelected].Add(newSprite);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (GUIComponent.ComponentState state in Enum.GetValues(typeof(GUIComponent.ComponentState)))
|
||||
{
|
||||
Sprites[state].Add(newSprite);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "size":
|
||||
break;
|
||||
default:
|
||||
string styleName = subElement.Name.ToString().ToLowerInvariant();
|
||||
if (ChildStyles.ContainsKey(styleName))
|
||||
{
|
||||
DebugConsole.ThrowError("UI style \"" + element.Name.ToString() + "\" contains multiple child styles with the same name (\"" + styleName + "\")!");
|
||||
ChildStyles[styleName] = new GUIComponentStyle(subElement, style);
|
||||
}
|
||||
else
|
||||
{
|
||||
ChildStyles.Add(styleName, new GUIComponentStyle(subElement, style));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
GetSize(element);
|
||||
}
|
||||
|
||||
public void GetSize(XElement element)
|
||||
{
|
||||
Point size = new Point(0, 0);
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
if (!subElement.Name.ToString().Equals("size", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
Point maxResolution = subElement.GetAttributePoint("maxresolution", new Point(int.MaxValue, int.MaxValue));
|
||||
if (GameMain.GraphicsWidth <= maxResolution.X && GameMain.GraphicsHeight <= maxResolution.Y)
|
||||
{
|
||||
size = new Point(
|
||||
subElement.GetAttributeInt("width", 0),
|
||||
subElement.GetAttributeInt("height", 0));
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (size.X > 0) { Width = size.X; }
|
||||
if (size.Y > 0) { Height = size.Y; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,405 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public static class FileSelection
|
||||
{
|
||||
private static bool open;
|
||||
public static bool Open
|
||||
{
|
||||
get
|
||||
{
|
||||
return open;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value && backgroundFrame == null) { Init(); }
|
||||
if (!value)
|
||||
{
|
||||
fileSystemWatcher?.Dispose();
|
||||
fileSystemWatcher = null;
|
||||
}
|
||||
open = value;
|
||||
}
|
||||
}
|
||||
|
||||
private static GUIFrame backgroundFrame;
|
||||
private static GUIFrame window;
|
||||
private static GUIListBox sidebar;
|
||||
private static GUIListBox fileList;
|
||||
private static GUITextBox directoryBox;
|
||||
private static GUITextBox filterBox;
|
||||
private static GUITextBox fileBox;
|
||||
private static GUIDropDown fileTypeDropdown;
|
||||
private static GUIButton openButton;
|
||||
|
||||
private static FileSystemWatcher fileSystemWatcher;
|
||||
|
||||
private static string currentFileTypePattern;
|
||||
|
||||
private static readonly string[] ignoredDrivePrefixes = new string[]
|
||||
{
|
||||
"/sys/", "/snap/"
|
||||
};
|
||||
|
||||
private static string currentDirectory;
|
||||
public static string CurrentDirectory
|
||||
{
|
||||
get
|
||||
{
|
||||
return currentDirectory;
|
||||
}
|
||||
set
|
||||
{
|
||||
string[] dirSplit = value.Replace('\\', '/').Split('/');
|
||||
List<string> dirs = new List<string>();
|
||||
for (int i = 0; i < dirSplit.Length; i++)
|
||||
{
|
||||
if (dirSplit[i].Trim() == "..")
|
||||
{
|
||||
if (dirs.Count > 1)
|
||||
{
|
||||
dirs.RemoveAt(dirs.Count - 1);
|
||||
}
|
||||
}
|
||||
else if (dirSplit[i].Trim() != ".")
|
||||
{
|
||||
dirs.Add(dirSplit[i]);
|
||||
}
|
||||
}
|
||||
currentDirectory = string.Join("/", dirs);
|
||||
if (!currentDirectory.EndsWith("/"))
|
||||
{
|
||||
currentDirectory += "/";
|
||||
}
|
||||
fileSystemWatcher?.Dispose();
|
||||
fileSystemWatcher = new FileSystemWatcher(currentDirectory)
|
||||
{
|
||||
Filter = "*",
|
||||
NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName
|
||||
};
|
||||
fileSystemWatcher.Created += OnFileSystemChanges;
|
||||
fileSystemWatcher.Deleted += OnFileSystemChanges;
|
||||
fileSystemWatcher.Renamed += OnFileSystemChanges;
|
||||
fileSystemWatcher.EnableRaisingEvents = true;
|
||||
RefreshFileList();
|
||||
}
|
||||
}
|
||||
|
||||
public static Action<string> OnFileSelected
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
private static void OnFileSystemChanges(object sender, FileSystemEventArgs e)
|
||||
{
|
||||
switch (e.ChangeType)
|
||||
{
|
||||
case WatcherChangeTypes.Created:
|
||||
{
|
||||
var itemFrame = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), fileList.Content.RectTransform), e.Name)
|
||||
{
|
||||
UserData = (bool?)Directory.Exists(e.FullPath)
|
||||
};
|
||||
if ((itemFrame.UserData as bool?) ?? false)
|
||||
{
|
||||
itemFrame.Text += "/";
|
||||
}
|
||||
fileList.Content.RectTransform.SortChildren(SortFiles);
|
||||
}
|
||||
break;
|
||||
case WatcherChangeTypes.Deleted:
|
||||
{
|
||||
var itemFrame = fileList.Content.FindChild(c => (c is GUITextBlock tb) && (tb.Text == e.Name || tb.Text == e.Name + "/"));
|
||||
if (itemFrame != null) { fileList.RemoveChild(itemFrame); }
|
||||
}
|
||||
break;
|
||||
case WatcherChangeTypes.Renamed:
|
||||
{
|
||||
RenamedEventArgs renameArgs = e as RenamedEventArgs;
|
||||
var itemFrame = fileList.Content.FindChild(c => (c is GUITextBlock tb) && (tb.Text == renameArgs.OldName || tb.Text == renameArgs.OldName + "/")) as GUITextBlock;
|
||||
itemFrame.UserData = (bool?)Directory.Exists(e.FullPath);
|
||||
itemFrame.Text = renameArgs.Name;
|
||||
if ((itemFrame.UserData as bool?) ?? false)
|
||||
{
|
||||
itemFrame.Text += "/";
|
||||
}
|
||||
fileList.Content.RectTransform.SortChildren(SortFiles);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static int SortFiles(RectTransform r1, RectTransform r2)
|
||||
{
|
||||
string file1 = (r1.GUIComponent as GUITextBlock)?.Text ?? "";
|
||||
string file2 = (r2.GUIComponent as GUITextBlock)?.Text ?? "";
|
||||
bool dir1 = (r1.GUIComponent.UserData as bool?) ?? false;
|
||||
bool dir2 = (r2.GUIComponent.UserData as bool?) ?? false;
|
||||
if (dir1 && !dir2)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if (!dir1 && dir2)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return string.Compare(file1, file2);
|
||||
}
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
backgroundFrame = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas), style: null)
|
||||
{
|
||||
Color = Color.Black * 0.5f,
|
||||
HoverColor = Color.Black * 0.5f,
|
||||
SelectedColor = Color.Black * 0.5f,
|
||||
PressedColor = Color.Black * 0.5f,
|
||||
};
|
||||
|
||||
window = new GUIFrame(new RectTransform(Vector2.One * 0.8f, backgroundFrame.RectTransform, Anchor.Center));
|
||||
|
||||
var horizontalLayout = new GUILayoutGroup(new RectTransform(Vector2.One * 0.9f, window.RectTransform, Anchor.Center), true);
|
||||
sidebar = new GUIListBox(new RectTransform(new Vector2(0.29f, 1.0f), horizontalLayout.RectTransform));
|
||||
|
||||
var drives = DriveInfo.GetDrives();
|
||||
foreach (var drive in drives)
|
||||
{
|
||||
if (drive.DriveType == DriveType.Ram) { continue; }
|
||||
if (ignoredDrivePrefixes.Any(p => drive.Name.StartsWith(p))) { continue; }
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), sidebar.Content.RectTransform), drive.Name.Replace('\\','/'));
|
||||
}
|
||||
|
||||
sidebar.OnSelected = (child, userdata) =>
|
||||
{
|
||||
CurrentDirectory = (child as GUITextBlock).Text;
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
//spacing between sidebar and fileListLayout
|
||||
new GUIFrame(new RectTransform(new Vector2(0.01f, 1.0f), horizontalLayout.RectTransform), style: null);
|
||||
|
||||
var fileListLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.7f, 1.0f), horizontalLayout.RectTransform));
|
||||
var firstRow = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.04f), fileListLayout.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft);
|
||||
new GUIButton(new RectTransform(new Vector2(0.05f, 1.0f), firstRow.RectTransform), "^")
|
||||
{
|
||||
OnClicked = MoveToParentDirectory
|
||||
};
|
||||
directoryBox = new GUITextBox(new RectTransform(new Vector2(0.7f, 1.0f), firstRow.RectTransform))
|
||||
{
|
||||
OverflowClip = true,
|
||||
OnEnterPressed = (tb, txt) =>
|
||||
{
|
||||
if (Directory.Exists(txt))
|
||||
{
|
||||
CurrentDirectory = txt;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
tb.Text = CurrentDirectory;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
filterBox = new GUITextBox(new RectTransform(new Vector2(0.25f, 1.0f), firstRow.RectTransform))
|
||||
{
|
||||
OverflowClip = true
|
||||
};
|
||||
firstRow.RectTransform.MinSize = new Point(0, firstRow.RectTransform.Children.Max(c => c.MinSize.Y));
|
||||
|
||||
filterBox.OnTextChanged += (txtbox, txt) =>
|
||||
{
|
||||
RefreshFileList();
|
||||
return true;
|
||||
};
|
||||
//spacing between rows
|
||||
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), fileListLayout.RectTransform), style: null);
|
||||
|
||||
fileList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.85f), fileListLayout.RectTransform))
|
||||
{
|
||||
OnSelected = (child, userdata) =>
|
||||
{
|
||||
if (userdata == null) { return false; }
|
||||
|
||||
var fileName = (child as GUITextBlock).Text;
|
||||
fileBox.Text = fileName;
|
||||
if (PlayerInput.DoubleClicked())
|
||||
{
|
||||
bool isDir = (userdata as bool?).Value;
|
||||
if (isDir)
|
||||
{
|
||||
CurrentDirectory += fileName;
|
||||
}
|
||||
else
|
||||
{
|
||||
OnFileSelected?.Invoke(CurrentDirectory + fileName);
|
||||
Open = false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
//spacing between rows
|
||||
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), fileListLayout.RectTransform), style: null);
|
||||
|
||||
var thirdRow = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.04f), fileListLayout.RectTransform), true);
|
||||
fileBox = new GUITextBox(new RectTransform(new Vector2(0.7f, 1.0f), thirdRow.RectTransform))
|
||||
{
|
||||
OnEnterPressed = (tb, txt) => openButton?.OnClicked?.Invoke(openButton, null) ?? false
|
||||
};
|
||||
|
||||
fileTypeDropdown = new GUIDropDown(new RectTransform(new Vector2(0.3f, 1.0f), thirdRow.RectTransform), dropAbove: true)
|
||||
{
|
||||
OnSelected = (child, userdata) =>
|
||||
{
|
||||
currentFileTypePattern = (child as GUITextBlock).UserData as string;
|
||||
RefreshFileList();
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
fileTypeDropdown.Select(4);
|
||||
|
||||
//spacing between rows
|
||||
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), fileListLayout.RectTransform), style: null);
|
||||
var fourthRow = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.04f), fileListLayout.RectTransform), true);
|
||||
|
||||
//padding for open/cancel buttons
|
||||
new GUIFrame(new RectTransform(new Vector2(0.7f, 1.0f), fourthRow.RectTransform), style: null);
|
||||
|
||||
openButton = new GUIButton(new RectTransform(new Vector2(0.15f, 1.0f), fourthRow.RectTransform), TextManager.Get("opensubbutton"))
|
||||
{
|
||||
OnClicked = (btn, obj) =>
|
||||
{
|
||||
if (Directory.Exists(Path.Combine(CurrentDirectory, fileBox.Text)))
|
||||
{
|
||||
CurrentDirectory += fileBox.Text;
|
||||
}
|
||||
if (!File.Exists(CurrentDirectory + fileBox.Text)) { return false; }
|
||||
OnFileSelected?.Invoke(CurrentDirectory + fileBox.Text);
|
||||
Open = false;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
new GUIButton(new RectTransform(new Vector2(0.15f, 1.0f), fourthRow.RectTransform), TextManager.Get("cancel"))
|
||||
{
|
||||
OnClicked = (btn, obj) =>
|
||||
{
|
||||
Open = false;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
CurrentDirectory = Directory.GetCurrentDirectory();
|
||||
}
|
||||
|
||||
public static void ClearFileTypeFilters()
|
||||
{
|
||||
if (backgroundFrame == null) { Init(); }
|
||||
fileTypeDropdown.ClearChildren();
|
||||
}
|
||||
|
||||
public static void AddFileTypeFilter(string name, string pattern)
|
||||
{
|
||||
if (backgroundFrame == null) { Init(); }
|
||||
fileTypeDropdown.AddItem(name + " (" + pattern + ")", pattern);
|
||||
}
|
||||
|
||||
public static void SelectFileTypeFilter(string pattern)
|
||||
{
|
||||
if (backgroundFrame == null) { Init(); }
|
||||
fileTypeDropdown.SelectItem(pattern);
|
||||
}
|
||||
|
||||
public static void RefreshFileList()
|
||||
{
|
||||
fileList.Content.ClearChildren();
|
||||
fileList.BarScroll = 0.0f;
|
||||
|
||||
try
|
||||
{
|
||||
var directories = Directory.EnumerateDirectories(currentDirectory, "*" + filterBox.Text + "*");
|
||||
foreach (var directory in directories)
|
||||
{
|
||||
string txt = directory;
|
||||
if (txt.StartsWith(currentDirectory)) { txt = txt.Substring(currentDirectory.Length); }
|
||||
if (!txt.EndsWith("/")) { txt += "/"; }
|
||||
var itemFrame = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), fileList.Content.RectTransform), txt)
|
||||
{
|
||||
UserData = (bool?)true
|
||||
};
|
||||
var folderIcon = new GUIImage(new RectTransform(new Point((int)(itemFrame.Rect.Height * 0.8f)), itemFrame.RectTransform, Anchor.CenterLeft)
|
||||
{
|
||||
AbsoluteOffset = new Point((int)(itemFrame.Rect.Height * 0.25f), 0)
|
||||
}, style: "OpenButton", scaleToFit: true);
|
||||
itemFrame.Padding = new Vector4(folderIcon.Rect.Width * 1.5f, itemFrame.Padding.Y, itemFrame.Padding.Z, itemFrame.Padding.W);
|
||||
}
|
||||
|
||||
IEnumerable<string> files = null;
|
||||
foreach (string pattern in currentFileTypePattern.Split(','))
|
||||
{
|
||||
string patternTrimmed = pattern.Trim();
|
||||
patternTrimmed = "*" + filterBox.Text + "*" + patternTrimmed;
|
||||
if (files == null)
|
||||
{
|
||||
files = Directory.EnumerateFiles(currentDirectory, patternTrimmed);
|
||||
}
|
||||
else
|
||||
{
|
||||
files = files.Concat(Directory.EnumerateFiles(currentDirectory, patternTrimmed));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
string txt = file;
|
||||
if (txt.StartsWith(currentDirectory)) { txt = txt.Substring(currentDirectory.Length); }
|
||||
var itemFrame = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), fileList.Content.RectTransform), txt)
|
||||
{
|
||||
UserData = (bool?)false
|
||||
};
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), fileList.Content.RectTransform), "Could not list items in directory: " + e.Message)
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
}
|
||||
|
||||
directoryBox.Text = currentDirectory;
|
||||
fileBox.Text = "";
|
||||
fileList.Deselect();
|
||||
}
|
||||
|
||||
public static bool MoveToParentDirectory(GUIButton button, object userdata)
|
||||
{
|
||||
string dir = CurrentDirectory;
|
||||
if (dir.EndsWith("/")) { dir = dir.Substring(0, dir.Length - 1); }
|
||||
int index = dir.LastIndexOf("/");
|
||||
if (index < 0) { return false; }
|
||||
CurrentDirectory = CurrentDirectory.Substring(0, index+1);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void AddToGUIUpdateList()
|
||||
{
|
||||
if (!Open) { return; }
|
||||
backgroundFrame?.AddToGUIUpdateList();
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,256 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class GUIButton : GUIComponent
|
||||
{
|
||||
protected GUITextBlock textBlock;
|
||||
public GUITextBlock TextBlock { get { return textBlock; } }
|
||||
protected GUIFrame frame;
|
||||
public GUIFrame Frame { get { return frame; } }
|
||||
|
||||
public delegate bool OnClickedHandler(GUIButton button, object obj);
|
||||
public OnClickedHandler OnClicked;
|
||||
|
||||
public delegate bool OnPressedHandler();
|
||||
public OnPressedHandler OnPressed;
|
||||
|
||||
public delegate bool OnButtonDownHandler();
|
||||
public OnButtonDownHandler OnButtonDown;
|
||||
|
||||
public bool CanBeSelected = true;
|
||||
|
||||
public override bool Enabled
|
||||
{
|
||||
get
|
||||
{
|
||||
return enabled;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (value == enabled) { return; }
|
||||
enabled = frame.Enabled = textBlock.Enabled = value;
|
||||
}
|
||||
}
|
||||
|
||||
public override Color Color
|
||||
{
|
||||
get { return base.Color; }
|
||||
set
|
||||
{
|
||||
base.Color = value;
|
||||
frame.Color = value;
|
||||
}
|
||||
}
|
||||
|
||||
public override Color HoverColor
|
||||
{
|
||||
get { return base.HoverColor; }
|
||||
set
|
||||
{
|
||||
base.HoverColor = value;
|
||||
frame.HoverColor = value;
|
||||
}
|
||||
}
|
||||
|
||||
public override Color SelectedColor
|
||||
{
|
||||
get
|
||||
{
|
||||
return base.SelectedColor;
|
||||
}
|
||||
set
|
||||
{
|
||||
base.SelectedColor = value;
|
||||
frame.SelectedColor = value;
|
||||
}
|
||||
}
|
||||
|
||||
public override Color PressedColor
|
||||
{
|
||||
get
|
||||
{
|
||||
return base.PressedColor;
|
||||
}
|
||||
set
|
||||
{
|
||||
base.PressedColor = value;
|
||||
frame.PressedColor = value;
|
||||
}
|
||||
}
|
||||
|
||||
public override Color OutlineColor
|
||||
{
|
||||
get { return base.OutlineColor; }
|
||||
set
|
||||
{
|
||||
base.OutlineColor = value;
|
||||
if (frame != null) frame.OutlineColor = value;
|
||||
}
|
||||
}
|
||||
|
||||
public Color TextColor
|
||||
{
|
||||
get { return textBlock.TextColor; }
|
||||
set { textBlock.TextColor = value; }
|
||||
}
|
||||
|
||||
public Color HoverTextColor
|
||||
{
|
||||
get { return textBlock.HoverTextColor; }
|
||||
set { textBlock.HoverTextColor = value; }
|
||||
}
|
||||
|
||||
public override float FlashTimer
|
||||
{
|
||||
get { return Frame.FlashTimer; }
|
||||
}
|
||||
|
||||
public override ScalableFont Font
|
||||
{
|
||||
get
|
||||
{
|
||||
return (textBlock == null) ? GUI.Font : textBlock.Font;
|
||||
}
|
||||
set
|
||||
{
|
||||
base.Font = value;
|
||||
if (textBlock != null) textBlock.Font = value;
|
||||
}
|
||||
}
|
||||
|
||||
public string Text
|
||||
{
|
||||
get { return textBlock.Text; }
|
||||
set { textBlock.Text = value; }
|
||||
}
|
||||
|
||||
public bool ForceUpperCase
|
||||
{
|
||||
get { return textBlock.ForceUpperCase; }
|
||||
set { textBlock.ForceUpperCase = value; }
|
||||
}
|
||||
|
||||
public override string ToolTip
|
||||
{
|
||||
get
|
||||
{
|
||||
return base.ToolTip;
|
||||
}
|
||||
set
|
||||
{
|
||||
base.ToolTip = value;
|
||||
textBlock.ToolTip = value;
|
||||
}
|
||||
}
|
||||
|
||||
public GUIButton(RectTransform rectT, string text = "", Alignment textAlignment = Alignment.Center, string style = "", Color? color = null) : base(style, rectT)
|
||||
{
|
||||
CanBeFocused = true;
|
||||
HoverCursor = CursorState.Hand;
|
||||
|
||||
frame = new GUIFrame(new RectTransform(Vector2.One, rectT), style) { CanBeFocused = false };
|
||||
if (style != null) { GUI.Style.Apply(frame, style == "" ? "GUIButton" : style); }
|
||||
if (color.HasValue)
|
||||
{
|
||||
this.color = frame.Color = color.Value;
|
||||
}
|
||||
textBlock = new GUITextBlock(new RectTransform(Vector2.One, rectT, Anchor.Center), text, textAlignment: textAlignment, style: null)
|
||||
{
|
||||
TextColor = this.style == null ? Color.Black : this.style.TextColor,
|
||||
HoverTextColor = this.style == null ? Color.Black : this.style.HoverTextColor,
|
||||
SelectedTextColor = this.style == null ? Color.Black : this.style.SelectedTextColor,
|
||||
CanBeFocused = false
|
||||
};
|
||||
if (rectT.Rect.Height == 0 && !string.IsNullOrEmpty(text))
|
||||
{
|
||||
RectTransform.Resize(new Point(RectTransform.Rect.Width, (int)Font.MeasureString(textBlock.Text).Y));
|
||||
RectTransform.MinSize = textBlock.RectTransform.MinSize = new Point(0, System.Math.Max(rectT.MinSize.Y, Rect.Height));
|
||||
TextBlock.SetTextPos();
|
||||
}
|
||||
GUI.Style.Apply(textBlock, "", this);
|
||||
|
||||
//if the text is in chinese/korean/japanese and we're not using a CJK-compatible font,
|
||||
//use the default CJK font as a fallback
|
||||
if (TextManager.IsCJK(textBlock.Text) && !textBlock.Font.IsCJK)
|
||||
{
|
||||
textBlock.Font = GUI.CJKFont;
|
||||
}
|
||||
|
||||
Enabled = true;
|
||||
}
|
||||
|
||||
public override void ApplyStyle(GUIComponentStyle style)
|
||||
{
|
||||
base.ApplyStyle(style);
|
||||
|
||||
if (frame != null) { frame.ApplyStyle(style); }
|
||||
}
|
||||
|
||||
public override void Flash(Color? color = null, float flashDuration = 1.5f, bool useRectangleFlash = false, bool useCircularFlash = false, Vector2? flashRectInflate = null)
|
||||
{
|
||||
Frame.Flash(color, flashDuration, useRectangleFlash, useCircularFlash, flashRectInflate);
|
||||
}
|
||||
|
||||
protected override void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
//do nothing
|
||||
}
|
||||
|
||||
protected override void Update(float deltaTime)
|
||||
{
|
||||
if (!Visible) return;
|
||||
base.Update(deltaTime);
|
||||
if (Rect.Contains(PlayerInput.MousePosition) && CanBeSelected && CanBeFocused && Enabled && GUI.IsMouseOn(this))
|
||||
{
|
||||
State = Selected ?
|
||||
ComponentState.HoverSelected :
|
||||
ComponentState.Hover;
|
||||
if (PlayerInput.PrimaryMouseButtonDown())
|
||||
{
|
||||
OnButtonDown?.Invoke();
|
||||
}
|
||||
if (PlayerInput.PrimaryMouseButtonHeld())
|
||||
{
|
||||
if (OnPressed != null)
|
||||
{
|
||||
if (OnPressed())
|
||||
{
|
||||
State = ComponentState.Pressed;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
State = ComponentState.Pressed;
|
||||
}
|
||||
}
|
||||
else if (PlayerInput.PrimaryMouseButtonClicked())
|
||||
{
|
||||
GUI.PlayUISound(GUISoundType.Click);
|
||||
if (OnClicked != null)
|
||||
{
|
||||
if (OnClicked(this, UserData))
|
||||
{
|
||||
State = ComponentState.Selected;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Selected = !Selected;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
State = Selected ? ComponentState.Selected : ComponentState.None;
|
||||
}
|
||||
|
||||
foreach (GUIComponent child in Children)
|
||||
{
|
||||
child.State = State;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class GUICanvas : RectTransform
|
||||
{
|
||||
protected GUICanvas() : base(Vector2.One, parent: null) { }
|
||||
|
||||
private static GUICanvas _instance;
|
||||
public static GUICanvas Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
_instance = new GUICanvas();
|
||||
if (GameMain.Instance != null)
|
||||
{
|
||||
GameMain.Instance.OnResolutionChanged += RecalculateSize;
|
||||
}
|
||||
}
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
// Turn public, if there is a need to call this manually.
|
||||
private static void RecalculateSize()
|
||||
{
|
||||
Instance.Resize(Vector2.One, resizeChildren: true);
|
||||
Instance.GetAllChildren().Select(c => c.GUIComponent as GUITextBlock).ForEach(t => t?.SetTextPos());
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
/// <summary>
|
||||
/// GUIComponent that can be used to render custom content on the UI
|
||||
/// </summary>
|
||||
class GUICustomComponent : GUIComponent
|
||||
{
|
||||
public Action<SpriteBatch, GUICustomComponent> OnDraw;
|
||||
public Action<float, GUICustomComponent> OnUpdate;
|
||||
|
||||
public bool HideElementsOutsideFrame;
|
||||
|
||||
public GUICustomComponent(RectTransform rectT, Action<SpriteBatch, GUICustomComponent> onDraw = null, Action<float, GUICustomComponent> onUpdate = null) : base(null, rectT)
|
||||
{
|
||||
OnDraw = onDraw;
|
||||
OnUpdate = onUpdate;
|
||||
}
|
||||
|
||||
protected override void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
if (!Visible) return;
|
||||
|
||||
Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
|
||||
if (HideElementsOutsideFrame)
|
||||
{
|
||||
spriteBatch.End();
|
||||
spriteBatch.GraphicsDevice.ScissorRectangle = Rectangle.Intersect(prevScissorRect, Rect);
|
||||
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
|
||||
}
|
||||
|
||||
OnDraw?.Invoke(spriteBatch, this);
|
||||
|
||||
if (HideElementsOutsideFrame)
|
||||
{
|
||||
spriteBatch.End();
|
||||
spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect;
|
||||
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Update(float deltaTime)
|
||||
{
|
||||
if (Visible) OnUpdate?.Invoke(deltaTime, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,433 @@
|
||||
using EventInput;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class GUIDropDown : GUIComponent, IKeyboardSubscriber
|
||||
{
|
||||
public delegate bool OnSelectedHandler(GUIComponent selected, object obj = null);
|
||||
public OnSelectedHandler OnSelected;
|
||||
public OnSelectedHandler OnDropped;
|
||||
|
||||
private readonly GUIButton button;
|
||||
private readonly GUIImage icon;
|
||||
private readonly GUIListBox listBox;
|
||||
|
||||
private RectTransform currentHighestParent;
|
||||
private List<RectTransform> parentHierarchy = new List<RectTransform>();
|
||||
|
||||
private bool selectMultiple;
|
||||
|
||||
public bool Dropped { get; set; }
|
||||
|
||||
public object SelectedItemData
|
||||
{
|
||||
get
|
||||
{
|
||||
if (listBox.SelectedComponent == null) return null;
|
||||
return listBox.SelectedComponent.UserData;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Enabled
|
||||
{
|
||||
get { return listBox.Enabled; }
|
||||
set { listBox.Enabled = value; }
|
||||
}
|
||||
|
||||
public bool ButtonEnabled
|
||||
{
|
||||
get { return button.Enabled; }
|
||||
set
|
||||
{
|
||||
button.Enabled = value;
|
||||
if (icon != null) { icon.Enabled = value; }
|
||||
}
|
||||
}
|
||||
|
||||
public GUIComponent SelectedComponent
|
||||
{
|
||||
get { return listBox.SelectedComponent; }
|
||||
}
|
||||
|
||||
// TODO: fix implicit hiding
|
||||
public bool Selected
|
||||
{
|
||||
get
|
||||
{
|
||||
return Dropped;
|
||||
}
|
||||
set
|
||||
{
|
||||
Dropped = value;
|
||||
}
|
||||
}
|
||||
|
||||
public GUIListBox ListBox
|
||||
{
|
||||
get { return listBox; }
|
||||
}
|
||||
|
||||
public object SelectedData
|
||||
{
|
||||
get
|
||||
{
|
||||
return listBox.SelectedComponent?.UserData;
|
||||
}
|
||||
}
|
||||
|
||||
public int SelectedIndex
|
||||
{
|
||||
get
|
||||
{
|
||||
if (listBox.SelectedComponent == null) return -1;
|
||||
return listBox.Content.GetChildIndex(listBox.SelectedComponent);
|
||||
}
|
||||
}
|
||||
|
||||
public Color ButtonTextColor
|
||||
{
|
||||
get { return button.TextColor; }
|
||||
set { button.TextColor = value; }
|
||||
}
|
||||
|
||||
public void ReceiveTextInput(char inputChar)
|
||||
{
|
||||
GUI.KeyboardDispatcher.Subscriber = null;
|
||||
}
|
||||
public void ReceiveTextInput(string text) { }
|
||||
public void ReceiveCommandInput(char command) { }
|
||||
|
||||
public void ReceiveSpecialInput(Keys key)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
case Keys.Up:
|
||||
case Keys.Down:
|
||||
listBox.ReceiveSpecialInput(key);
|
||||
GUI.KeyboardDispatcher.Subscriber = this;
|
||||
break;
|
||||
case Keys.Enter:
|
||||
case Keys.Space:
|
||||
case Keys.Escape:
|
||||
GUI.KeyboardDispatcher.Subscriber = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private List<object> selectedDataMultiple = new List<object>();
|
||||
public IEnumerable<object> SelectedDataMultiple
|
||||
{
|
||||
get { return selectedDataMultiple; }
|
||||
}
|
||||
|
||||
private List<int> selectedIndexMultiple = new List<int>();
|
||||
public IEnumerable<int> SelectedIndexMultiple
|
||||
{
|
||||
get { return selectedIndexMultiple; }
|
||||
}
|
||||
|
||||
public string Text
|
||||
{
|
||||
get { return button.Text; }
|
||||
set { button.Text = value; }
|
||||
}
|
||||
|
||||
public override string ToolTip
|
||||
{
|
||||
get
|
||||
{
|
||||
return base.ToolTip;
|
||||
}
|
||||
set
|
||||
{
|
||||
base.ToolTip = value;
|
||||
button.ToolTip = value;
|
||||
listBox.ToolTip = value;
|
||||
}
|
||||
}
|
||||
|
||||
public GUIDropDown(RectTransform rectT, string text = "", int elementCount = 4, string style = "", bool selectMultiple = false, bool dropAbove = false) : base(style, rectT)
|
||||
{
|
||||
HoverCursor = CursorState.Hand;
|
||||
CanBeFocused = true;
|
||||
|
||||
this.selectMultiple = selectMultiple;
|
||||
|
||||
button = new GUIButton(new RectTransform(Vector2.One, rectT), text, Alignment.CenterLeft, style: "GUIDropDown")
|
||||
{
|
||||
OnClicked = OnClicked
|
||||
};
|
||||
GUI.Style.Apply(button, "", this);
|
||||
button.TextBlock.SetTextPos();
|
||||
|
||||
Anchor listAnchor = dropAbove ? Anchor.TopCenter : Anchor.BottomCenter;
|
||||
Pivot listPivot = dropAbove ? Pivot.BottomCenter : Pivot.TopCenter;
|
||||
listBox = new GUIListBox(new RectTransform(new Point(Rect.Width, Rect.Height * MathHelper.Clamp(elementCount, 2, 10)), rectT, listAnchor, listPivot)
|
||||
|
||||
{ IsFixedSize = false }, style: null)
|
||||
{
|
||||
Enabled = !selectMultiple,
|
||||
OnSelected = SelectItem
|
||||
};
|
||||
GUI.Style.Apply(listBox, "GUIListBox", this);
|
||||
GUI.Style.Apply(listBox.ContentBackground, "GUIListBox", this);
|
||||
|
||||
if (button.Style.ChildStyles.ContainsKey("dropdownicon"))
|
||||
{
|
||||
icon = new GUIImage(new RectTransform(new Vector2(0.6f, 0.6f), button.RectTransform, Anchor.CenterRight, scaleBasis: ScaleBasis.BothHeight) { AbsoluteOffset = new Point(5, 0) }, null, scaleToFit: true);
|
||||
icon.ApplyStyle(button.Style.ChildStyles["dropdownicon"]);
|
||||
}
|
||||
|
||||
currentHighestParent = FindHighestParent();
|
||||
currentHighestParent.GUIComponent.OnAddedToGUIUpdateList += AddListBoxToGUIUpdateList;
|
||||
rectT.ParentChanged += (RectTransform newParent) =>
|
||||
{
|
||||
currentHighestParent.GUIComponent.OnAddedToGUIUpdateList -= AddListBoxToGUIUpdateList;
|
||||
if (newParent != null)
|
||||
{
|
||||
currentHighestParent = FindHighestParent();
|
||||
currentHighestParent.GUIComponent.OnAddedToGUIUpdateList += AddListBoxToGUIUpdateList;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Finds the component after which the listbox should be drawn
|
||||
/// //(= the component highest in the hierarchy, to get the listbox
|
||||
/// //to be rendered on top of all of it's children)
|
||||
/// </summary>
|
||||
private RectTransform FindHighestParent()
|
||||
{
|
||||
parentHierarchy.Clear();
|
||||
|
||||
//collect entire parent hierarchy to a list
|
||||
parentHierarchy = new List<RectTransform>() { RectTransform.Parent };
|
||||
RectTransform parent = parentHierarchy.Last();
|
||||
while (parent?.Parent != null)
|
||||
{
|
||||
parentHierarchy.Add(parent.Parent);
|
||||
parent = parent.Parent;
|
||||
}
|
||||
|
||||
//find the highest parent that has a guicomponent with a style
|
||||
//(and so should be rendered and not just some empty parent/root element used for constructing a layout)
|
||||
for (int i = parentHierarchy.Count - 1; i > 0; i--)
|
||||
{
|
||||
if (parentHierarchy[i] is GUICanvas ||
|
||||
parentHierarchy[i].GUIComponent == null ||
|
||||
parentHierarchy[i].GUIComponent.Style == null ||
|
||||
parentHierarchy[i].GUIComponent == Screen.Selected?.Frame)
|
||||
{
|
||||
parentHierarchy.RemoveAt(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
return parentHierarchy.Last();
|
||||
}
|
||||
|
||||
public void AddItem(string text, object userData = null, string toolTip = "")
|
||||
{
|
||||
if (selectMultiple)
|
||||
{
|
||||
var frame = new GUIFrame(new RectTransform(new Point(button.Rect.Width, button.Rect.Height), listBox.Content.RectTransform)
|
||||
{ IsFixedSize = false }, style: "ListBoxElement")
|
||||
{
|
||||
UserData = userData,
|
||||
ToolTip = toolTip
|
||||
};
|
||||
|
||||
new GUITickBox(new RectTransform(new Point((int)(button.Rect.Height * 0.8f)), frame.RectTransform, anchor: Anchor.CenterLeft), text)
|
||||
{
|
||||
UserData = userData,
|
||||
ToolTip = toolTip,
|
||||
OnSelected = (GUITickBox tb) =>
|
||||
{
|
||||
List<string> texts = new List<string>();
|
||||
selectedDataMultiple.Clear();
|
||||
selectedIndexMultiple.Clear();
|
||||
int i = 0;
|
||||
foreach (GUIComponent child in ListBox.Content.Children)
|
||||
{
|
||||
var tickBox = child.GetChild<GUITickBox>();
|
||||
if (tickBox.Selected)
|
||||
{
|
||||
selectedDataMultiple.Add(child.UserData);
|
||||
selectedIndexMultiple.Add(i);
|
||||
texts.Add(tickBox.Text);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
button.Text = string.Join(", ", texts);
|
||||
// TODO: The callback is called at least twice, remove this?
|
||||
OnSelected?.Invoke(tb.Parent, tb.Parent.UserData);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
new GUITextBlock(new RectTransform(new Point(button.Rect.Width, button.Rect.Height), listBox.Content.RectTransform)
|
||||
{ IsFixedSize = false }, text, style: "ListBoxElement")
|
||||
{
|
||||
UserData = userData,
|
||||
ToolTip = toolTip
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public override void ClearChildren()
|
||||
{
|
||||
listBox.ClearChildren();
|
||||
}
|
||||
|
||||
public IEnumerable<GUIComponent> GetChildren()
|
||||
{
|
||||
return listBox.Content.Children;
|
||||
}
|
||||
|
||||
private bool SelectItem(GUIComponent component, object obj)
|
||||
{
|
||||
if (selectMultiple)
|
||||
{
|
||||
foreach (GUIComponent child in ListBox.Content.Children)
|
||||
{
|
||||
var tickBox = child.GetChild<GUITickBox>();
|
||||
if (obj == child.UserData) { tickBox.Selected = true; }
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GUITextBlock textBlock = component as GUITextBlock;
|
||||
if (textBlock == null)
|
||||
{
|
||||
textBlock = component.GetChild<GUITextBlock>();
|
||||
if (textBlock == null) return false;
|
||||
}
|
||||
button.Text = textBlock.Text;
|
||||
}
|
||||
Dropped = false;
|
||||
// TODO: OnSelected can be called multiple times and when it shouldn't be called -> turn into an event so that nobody else can call it.
|
||||
OnSelected?.Invoke(component, component.UserData);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void SelectItem(object userData)
|
||||
{
|
||||
if (selectMultiple)
|
||||
{
|
||||
SelectItem(listBox.Content.FindChild(userData), userData);
|
||||
}
|
||||
else
|
||||
{
|
||||
listBox.Select(userData);
|
||||
}
|
||||
}
|
||||
|
||||
public void Select(int index)
|
||||
{
|
||||
if (selectMultiple)
|
||||
{
|
||||
var child = listBox.Content.GetChild(index);
|
||||
if (child != null)
|
||||
{
|
||||
SelectItem(null, child.UserData);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
listBox.Select(index);
|
||||
}
|
||||
}
|
||||
|
||||
private bool wasOpened;
|
||||
|
||||
private bool OnClicked(GUIComponent component, object obj)
|
||||
{
|
||||
if (wasOpened) return false;
|
||||
|
||||
wasOpened = true;
|
||||
Dropped = !Dropped;
|
||||
if (Dropped && Enabled)
|
||||
{
|
||||
OnDropped?.Invoke(this, userData);
|
||||
listBox.UpdateScrollBarSize();
|
||||
|
||||
GUI.KeyboardDispatcher.Subscriber = this;
|
||||
}
|
||||
else if (GUI.KeyboardDispatcher.Subscriber == this)
|
||||
{
|
||||
GUI.KeyboardDispatcher.Subscriber = null;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void AddListBoxToGUIUpdateList(GUIComponent parent)
|
||||
{
|
||||
//the parent is not our parent anymore :(
|
||||
//can happen when subscribed to a parent higher in the hierarchy (instead of the direct parent),
|
||||
//and somewhere between this component and the higher parent a component was removed
|
||||
for (int i = 1; i < parentHierarchy.Count; i++)
|
||||
{
|
||||
if (!parentHierarchy[i].IsParentOf(parentHierarchy[i - 1], recursive: false))
|
||||
{
|
||||
parent.OnAddedToGUIUpdateList -= AddListBoxToGUIUpdateList;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (Dropped)
|
||||
{
|
||||
listBox.AddToGUIUpdateList(false, UpdateOrder);
|
||||
}
|
||||
}
|
||||
|
||||
public override void DrawManually(SpriteBatch spriteBatch, bool alsoChildren = false, bool recursive = true)
|
||||
{
|
||||
if (!Visible) return;
|
||||
|
||||
AutoDraw = false;
|
||||
Draw(spriteBatch);
|
||||
if (alsoChildren)
|
||||
{
|
||||
button.DrawManually(spriteBatch, alsoChildren, recursive);
|
||||
}
|
||||
}
|
||||
|
||||
public override void AddToGUIUpdateList(bool ignoreChildren = false, int order = 0)
|
||||
{
|
||||
base.AddToGUIUpdateList(true, order);
|
||||
if (!ignoreChildren)
|
||||
{
|
||||
button.AddToGUIUpdateList(false, order);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Update(float deltaTime)
|
||||
{
|
||||
if (!Visible) return;
|
||||
wasOpened = false;
|
||||
base.Update(deltaTime);
|
||||
if (Dropped && PlayerInput.PrimaryMouseButtonClicked())
|
||||
{
|
||||
Rectangle listBoxRect = listBox.Rect;
|
||||
if (!listBoxRect.Contains(PlayerInput.MousePosition) && !button.Rect.Contains(PlayerInput.MousePosition))
|
||||
{
|
||||
Dropped = false;
|
||||
if (GUI.KeyboardDispatcher.Subscriber == this)
|
||||
{
|
||||
GUI.KeyboardDispatcher.Subscriber = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class GUIFrame : GUIComponent
|
||||
{
|
||||
public GUIFrame(RectTransform rectT, string style = "", Color? color = null) : base(style, rectT)
|
||||
{
|
||||
Enabled = true;
|
||||
if (color.HasValue)
|
||||
{
|
||||
this.color = color.Value;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
if (!Visible) return;
|
||||
|
||||
Color currColor = GetColor(State);
|
||||
|
||||
if (sprites == null || !sprites.Any(s => s.Value.Any())) GUI.DrawRectangle(spriteBatch, Rect, currColor * (currColor.A/255.0f), true);
|
||||
base.Draw(spriteBatch);
|
||||
|
||||
if (OutlineColor != Color.Transparent)
|
||||
{
|
||||
GUI.DrawRectangle(spriteBatch, Rect, OutlineColor * (OutlineColor.A/255.0f), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class GUIImage : GUIComponent
|
||||
{
|
||||
public float Rotation;
|
||||
|
||||
private Sprite sprite;
|
||||
|
||||
private Rectangle sourceRect;
|
||||
|
||||
private bool crop;
|
||||
|
||||
private bool scaleToFit;
|
||||
|
||||
public bool Crop
|
||||
{
|
||||
get
|
||||
{
|
||||
return crop;
|
||||
}
|
||||
set
|
||||
{
|
||||
crop = value;
|
||||
if (crop)
|
||||
{
|
||||
sourceRect.Width = Math.Min(sprite.SourceRect.Width, Rect.Width);
|
||||
sourceRect.Height = Math.Min(sprite.SourceRect.Height, Rect.Height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public float Scale
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public Rectangle SourceRect
|
||||
{
|
||||
get { return sourceRect; }
|
||||
set { sourceRect = value; }
|
||||
}
|
||||
|
||||
public Sprite Sprite
|
||||
{
|
||||
get { return sprite; }
|
||||
set
|
||||
{
|
||||
if (sprite == value) return;
|
||||
sprite = value;
|
||||
sourceRect = sprite.SourceRect;
|
||||
if (scaleToFit) RecalculateScale();
|
||||
}
|
||||
}
|
||||
|
||||
public BlendState BlendState;
|
||||
|
||||
public ComponentState? OverrideState = null;
|
||||
|
||||
public GUIImage(RectTransform rectT, string style, bool scaleToFit = false)
|
||||
: this(rectT, null, null, scaleToFit, style)
|
||||
{
|
||||
}
|
||||
|
||||
public GUIImage(RectTransform rectT, Sprite sprite, Rectangle? sourceRect = null, bool scaleToFit = false)
|
||||
: this(rectT, sprite, sourceRect, scaleToFit, null)
|
||||
{
|
||||
}
|
||||
|
||||
private GUIImage(RectTransform rectT, Sprite sprite, Rectangle? sourceRect, bool scaleToFit, string style) : base(style, rectT)
|
||||
{
|
||||
this.scaleToFit = scaleToFit;
|
||||
sprite?.EnsureLazyLoaded();
|
||||
Sprite = sprite;
|
||||
if (sourceRect.HasValue)
|
||||
{
|
||||
this.sourceRect = sourceRect.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.sourceRect = sprite == null ? Rectangle.Empty : sprite.SourceRect;
|
||||
}
|
||||
if (style == null)
|
||||
{
|
||||
color = hoverColor = selectedColor = pressedColor = disabledColor = Color.White;
|
||||
}
|
||||
if (!scaleToFit)
|
||||
{
|
||||
Scale = 1.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
rectT.SizeChanged += RecalculateScale;
|
||||
}
|
||||
Enabled = true;
|
||||
}
|
||||
|
||||
protected override void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
if (!Visible) return;
|
||||
|
||||
if (Parent != null) { State = Parent.State; }
|
||||
if (OverrideState != null) { State = OverrideState.Value; }
|
||||
|
||||
Color currentColor = GetColor(State);
|
||||
|
||||
if (BlendState != null)
|
||||
{
|
||||
spriteBatch.End();
|
||||
spriteBatch.Begin(blendState: BlendState, samplerState: GUI.SamplerState);
|
||||
}
|
||||
|
||||
if (style != null)
|
||||
{
|
||||
foreach (UISprite uiSprite in style.Sprites[State])
|
||||
{
|
||||
if (Math.Abs(Rotation) > float.Epsilon)
|
||||
{
|
||||
float scale = Math.Min(Rect.Width / uiSprite.Sprite.size.X, Rect.Height / uiSprite.Sprite.size.Y);
|
||||
spriteBatch.Draw(uiSprite.Sprite.Texture, Rect.Center.ToVector2(), uiSprite.Sprite.SourceRect, currentColor * (currentColor.A / 255.0f), Rotation, uiSprite.Sprite.size / 2,
|
||||
Scale * scale, SpriteEffects, 0.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
uiSprite.Draw(spriteBatch, Rect, currentColor * (currentColor.A / 255.0f), SpriteEffects);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (sprite?.Texture != null)
|
||||
{
|
||||
spriteBatch.Draw(sprite.Texture, Rect.Center.ToVector2(), sourceRect, currentColor * (currentColor.A / 255.0f), Rotation, sprite.size / 2,
|
||||
Scale, SpriteEffects, 0.0f);
|
||||
}
|
||||
|
||||
if (BlendState != null)
|
||||
{
|
||||
spriteBatch.End();
|
||||
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
|
||||
}
|
||||
}
|
||||
|
||||
private void RecalculateScale()
|
||||
{
|
||||
Scale = sprite == null || sprite.SourceRect.Width == 0 || sprite.SourceRect.Height == 0 ?
|
||||
1.0f :
|
||||
Math.Min(RectTransform.Rect.Width / (float)sprite.SourceRect.Width, RectTransform.Rect.Height / (float)sprite.SourceRect.Height);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class GUILayoutGroup : GUIComponent
|
||||
{
|
||||
private bool isHorizontal;
|
||||
public bool IsHorizontal
|
||||
{
|
||||
get { return isHorizontal; }
|
||||
set
|
||||
{
|
||||
isHorizontal = value;
|
||||
needsToRecalculate = true;
|
||||
}
|
||||
}
|
||||
|
||||
private bool stretch;
|
||||
/// <summary>
|
||||
/// Note that stretching cannot be undone, because the previous child sizes are not stored.
|
||||
/// </summary>
|
||||
public bool Stretch
|
||||
{
|
||||
get { return stretch; }
|
||||
set
|
||||
{
|
||||
stretch = value;
|
||||
needsToRecalculate = true;
|
||||
}
|
||||
}
|
||||
|
||||
private int absoluteSpacing;
|
||||
public int AbsoluteSpacing
|
||||
{
|
||||
get { return absoluteSpacing; }
|
||||
set
|
||||
{
|
||||
absoluteSpacing = MathHelper.Clamp(value, 0, int.MaxValue);
|
||||
needsToRecalculate = true;
|
||||
}
|
||||
}
|
||||
|
||||
private float relativeSpacing;
|
||||
public float RelativeSpacing
|
||||
{
|
||||
get { return relativeSpacing; }
|
||||
set
|
||||
{
|
||||
relativeSpacing = MathHelper.Clamp(value, -1, 1);
|
||||
needsToRecalculate = true;
|
||||
}
|
||||
}
|
||||
|
||||
private Anchor childAnchor;
|
||||
public Anchor ChildAnchor
|
||||
{
|
||||
get { return childAnchor; }
|
||||
set
|
||||
{
|
||||
childAnchor = value;
|
||||
needsToRecalculate = true;
|
||||
}
|
||||
}
|
||||
|
||||
private bool needsToRecalculate;
|
||||
public bool NeedsToRecalculate
|
||||
{
|
||||
get { return needsToRecalculate; }
|
||||
set
|
||||
{
|
||||
if (value) { needsToRecalculate = true; }
|
||||
}
|
||||
}
|
||||
|
||||
public GUILayoutGroup(RectTransform rectT, bool isHorizontal = false, Anchor childAnchor = Anchor.TopLeft) : base(null, rectT)
|
||||
{
|
||||
CanBeFocused = false;
|
||||
|
||||
this.isHorizontal = isHorizontal;
|
||||
this.childAnchor = childAnchor;
|
||||
rectT.ChildrenChanged += (child) => needsToRecalculate = true;
|
||||
rectT.ScaleChanged += () => needsToRecalculate = true;
|
||||
rectT.SizeChanged += () => needsToRecalculate = true;
|
||||
}
|
||||
|
||||
public void Recalculate()
|
||||
{
|
||||
float stretchFactor = 1.0f;
|
||||
if (stretch && RectTransform.Children.Count() > 0)
|
||||
{
|
||||
foreach (RectTransform child in RectTransform.Children)
|
||||
{
|
||||
if (child.GUIComponent.IgnoreLayoutGroups) { continue; }
|
||||
if (child.ScaleBasis == ScaleBasis.BothHeight) { child.MinSize = new Point(child.Rect.Height, child.MinSize.Y); }
|
||||
if (child.ScaleBasis == ScaleBasis.BothWidth) { child.MinSize = new Point(child.MinSize.X, child.Rect.Width); }
|
||||
if (child.ScaleBasis == ScaleBasis.Smallest)
|
||||
{
|
||||
if (Rect.Width < Rect.Height)
|
||||
{
|
||||
child.MinSize = new Point(child.MinSize.X, child.Rect.Width);
|
||||
}
|
||||
else
|
||||
{
|
||||
child.MinSize = new Point(child.Rect.Height, child.MinSize.Y);
|
||||
}
|
||||
}
|
||||
if (child.ScaleBasis == ScaleBasis.Largest)
|
||||
{
|
||||
if (Rect.Width > Rect.Height)
|
||||
{
|
||||
child.MinSize = new Point(child.MinSize.X, child.Rect.Width);
|
||||
}
|
||||
else
|
||||
{
|
||||
child.MinSize = new Point(child.Rect.Height, child.MinSize.Y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float minSize = RectTransform.Children
|
||||
.Where(c => !c.GUIComponent.IgnoreLayoutGroups)
|
||||
.Sum(c => isHorizontal ? (c.IsFixedSize ? c.NonScaledSize.X : c.MinSize.X) : (c.IsFixedSize ? c.NonScaledSize.Y : c.MinSize.Y));
|
||||
|
||||
float totalSize = RectTransform.Children
|
||||
.Where(c => !c.GUIComponent.IgnoreLayoutGroups)
|
||||
.Sum(c => isHorizontal ?
|
||||
(c.IsFixedSize ? c.Rect.Width : MathHelper.Clamp(c.Rect.Width, c.MinSize.X, c.MaxSize.X)) :
|
||||
(c.IsFixedSize ? c.Rect.Height : MathHelper.Clamp(c.Rect.Height, c.MinSize.Y, c.MaxSize.Y)));
|
||||
|
||||
float thisSize = (isHorizontal ? Rect.Width : Rect.Height);
|
||||
|
||||
totalSize +=
|
||||
(RectTransform.Children.Count(c => !c.GUIComponent.IgnoreLayoutGroups) - 1) *
|
||||
(absoluteSpacing + relativeSpacing * thisSize);
|
||||
|
||||
stretchFactor = totalSize <= 0.0f || minSize >= thisSize ?
|
||||
1.0f :
|
||||
(thisSize - minSize) / (totalSize - minSize);
|
||||
}
|
||||
|
||||
int absPos = 0;
|
||||
float relPos = 0;
|
||||
foreach (var child in RectTransform.Children)
|
||||
{
|
||||
if (child.GUIComponent.IgnoreLayoutGroups) { continue; }
|
||||
child.SetPosition(childAnchor);
|
||||
if (isHorizontal)
|
||||
{
|
||||
child.RelativeOffset = new Vector2(relPos, child.RelativeOffset.Y);
|
||||
child.AbsoluteOffset = new Point(absPos, child.AbsoluteOffset.Y);
|
||||
if (child.IsFixedSize)
|
||||
{
|
||||
absPos += child.NonScaledSize.X + absoluteSpacing;
|
||||
}
|
||||
else
|
||||
{
|
||||
absPos += (int)(MathHelper.Clamp(child.Rect.Width * stretchFactor, child.MinSize.X, child.MaxSize.X) + (absoluteSpacing * stretchFactor));
|
||||
if (stretch)
|
||||
{
|
||||
child.RelativeSize = new Vector2(child.RelativeSize.X * stretchFactor, child.RelativeSize.Y);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
child.RelativeOffset = new Vector2(child.RelativeOffset.X, relPos);
|
||||
child.AbsoluteOffset = new Point(child.AbsoluteOffset.X, absPos);
|
||||
if (child.IsFixedSize)
|
||||
{
|
||||
absPos += child.NonScaledSize.Y + absoluteSpacing;
|
||||
}
|
||||
else
|
||||
{
|
||||
absPos += (int)(MathHelper.Clamp(child.Rect.Height * stretchFactor, child.MinSize.Y, child.MaxSize.Y) + (absoluteSpacing * stretchFactor));
|
||||
if (stretch)
|
||||
{
|
||||
child.RelativeSize = new Vector2(child.RelativeSize.X, child.RelativeSize.Y * stretchFactor);
|
||||
}
|
||||
}
|
||||
}
|
||||
relPos += relativeSpacing * stretchFactor;
|
||||
}
|
||||
needsToRecalculate = false;
|
||||
}
|
||||
|
||||
protected override void Update(float deltaTime)
|
||||
{
|
||||
base.Update(deltaTime);
|
||||
if (needsToRecalculate)
|
||||
{
|
||||
Recalculate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,818 @@
|
||||
using EventInput;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class GUIListBox : GUIComponent, IKeyboardSubscriber
|
||||
{
|
||||
protected List<GUIComponent> selected;
|
||||
|
||||
public delegate bool OnSelectedHandler(GUIComponent component, object obj);
|
||||
public OnSelectedHandler OnSelected;
|
||||
|
||||
public delegate object CheckSelectedHandler();
|
||||
public CheckSelectedHandler CheckSelected;
|
||||
|
||||
public delegate void OnRearrangedHandler(GUIListBox listBox, object obj);
|
||||
public OnRearrangedHandler OnRearranged;
|
||||
|
||||
/// <summary>
|
||||
/// A frame drawn behind the content of the listbox
|
||||
/// </summary>
|
||||
public GUIFrame ContentBackground { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// A frame that contains the contents of the listbox. The frame itself is not rendered.
|
||||
/// </summary>
|
||||
public GUIFrame Content { get; private set; }
|
||||
public GUIScrollBar ScrollBar { get; private set; }
|
||||
|
||||
private Dictionary<GUIComponent, bool> childVisible = new Dictionary<GUIComponent, bool>();
|
||||
|
||||
private int totalSize;
|
||||
private bool childrenNeedsRecalculation;
|
||||
private bool scrollBarNeedsRecalculation;
|
||||
private bool dimensionsNeedsRecalculation;
|
||||
|
||||
// TODO: Define in styles?
|
||||
private int ScrollBarSize
|
||||
{
|
||||
get
|
||||
{
|
||||
//use the average of the "desired" size and the scaled size
|
||||
//scaling the bar linearly with the resolution tends to make them too large on large resolutions
|
||||
float desiredSize = 25.0f;
|
||||
float scaledSize = desiredSize * GUI.Scale;
|
||||
return (int)((desiredSize + scaledSize) / 2.0f);
|
||||
}
|
||||
}
|
||||
|
||||
public bool SelectMultiple;
|
||||
|
||||
public bool HideChildrenOutsideFrame = true;
|
||||
|
||||
private bool useGridLayout;
|
||||
|
||||
public bool UseGridLayout
|
||||
{
|
||||
get { return useGridLayout; }
|
||||
set
|
||||
{
|
||||
if (useGridLayout == value) return;
|
||||
useGridLayout = value;
|
||||
childrenNeedsRecalculation = true;
|
||||
scrollBarNeedsRecalculation = true;
|
||||
}
|
||||
}
|
||||
|
||||
private Vector4? overridePadding;
|
||||
public Vector4 Padding
|
||||
{
|
||||
get
|
||||
{
|
||||
if (overridePadding.HasValue) { return overridePadding.Value; }
|
||||
if (Style == null) { return Vector4.Zero; }
|
||||
return Style.Padding;
|
||||
}
|
||||
set { overridePadding = value; }
|
||||
}
|
||||
|
||||
public GUIComponent SelectedComponent
|
||||
{
|
||||
get
|
||||
{
|
||||
return selected.FirstOrDefault();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: fix implicit hiding
|
||||
public bool Selected { get; set; }
|
||||
|
||||
public List<GUIComponent> AllSelected
|
||||
{
|
||||
get { return selected; }
|
||||
}
|
||||
|
||||
public object SelectedData
|
||||
{
|
||||
get
|
||||
{
|
||||
return SelectedComponent?.UserData;
|
||||
}
|
||||
}
|
||||
|
||||
public int SelectedIndex
|
||||
{
|
||||
get
|
||||
{
|
||||
if (SelectedComponent == null) return -1;
|
||||
return Content.RectTransform.GetChildIndex(SelectedComponent.RectTransform);
|
||||
}
|
||||
}
|
||||
|
||||
public float BarScroll
|
||||
{
|
||||
get { return ScrollBar.BarScroll; }
|
||||
set { ScrollBar.BarScroll = value; }
|
||||
}
|
||||
|
||||
public float BarSize
|
||||
{
|
||||
get { return ScrollBar.BarSize; }
|
||||
}
|
||||
|
||||
public float TotalSize
|
||||
{
|
||||
get { return totalSize; }
|
||||
}
|
||||
|
||||
public int Spacing { get; set; }
|
||||
|
||||
public override Color Color
|
||||
{
|
||||
get
|
||||
{
|
||||
return base.Color;
|
||||
}
|
||||
set
|
||||
{
|
||||
base.Color = value;
|
||||
|
||||
Content.Color = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disables the scroll bar without hiding it.
|
||||
/// </summary>
|
||||
public bool ScrollBarEnabled { get; set; } = true;
|
||||
public bool KeepSpaceForScrollBar { get; set; }
|
||||
|
||||
public bool ScrollBarVisible
|
||||
{
|
||||
get
|
||||
{
|
||||
return ScrollBar.Visible;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (ScrollBar.Visible == value) { return; }
|
||||
ScrollBar.Visible = value;
|
||||
dimensionsNeedsRecalculation = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Automatically hides the scroll bar when the content fits in.
|
||||
/// </summary>
|
||||
public bool AutoHideScrollBar { get; set; } = true;
|
||||
private bool IsScrollBarOnDefaultSide { get; set; }
|
||||
|
||||
public bool CanDragElements { get; set; } = false;
|
||||
private GUIComponent draggedElement;
|
||||
private Rectangle draggedReferenceRectangle;
|
||||
private Point draggedReferenceOffset;
|
||||
|
||||
public GUIComponent DraggedElement => draggedElement;
|
||||
|
||||
/// <param name="isScrollBarOnDefaultSide">For horizontal listbox, default side is on the bottom. For vertical, it's on the right.</param>
|
||||
public GUIListBox(RectTransform rectT, bool isHorizontal = false, Color? color = null, string style = "", bool isScrollBarOnDefaultSide = true) : base(style, rectT)
|
||||
{
|
||||
CanBeFocused = true;
|
||||
selected = new List<GUIComponent>();
|
||||
ContentBackground = new GUIFrame(new RectTransform(Vector2.One, rectT), style)
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
Content = new GUIFrame(new RectTransform(Vector2.One, ContentBackground.RectTransform), style: null)
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
Content.RectTransform.ChildrenChanged += (_) =>
|
||||
{
|
||||
scrollBarNeedsRecalculation = true;
|
||||
childrenNeedsRecalculation = true;
|
||||
};
|
||||
if (style != null)
|
||||
{
|
||||
GUI.Style.Apply(ContentBackground, "", this);
|
||||
}
|
||||
if (color.HasValue)
|
||||
{
|
||||
this.color = color.Value;
|
||||
}
|
||||
IsScrollBarOnDefaultSide = isScrollBarOnDefaultSide;
|
||||
Point size;
|
||||
Anchor anchor;
|
||||
if (isHorizontal)
|
||||
{
|
||||
size = new Point((int)(Rect.Width - Padding.X - Padding.Z), (int)(ScrollBarSize * GUI.Scale));
|
||||
anchor = isScrollBarOnDefaultSide ? Anchor.BottomCenter : Anchor.TopCenter;
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Should this be multiplied by the GUI.Scale as well?
|
||||
size = new Point(ScrollBarSize, (int)(Rect.Height - Padding.Y - Padding.W));
|
||||
anchor = isScrollBarOnDefaultSide ? Anchor.CenterRight : Anchor.CenterLeft;
|
||||
}
|
||||
ScrollBar = new GUIScrollBar(
|
||||
new RectTransform(size, rectT, anchor)
|
||||
{
|
||||
AbsoluteOffset = isHorizontal ?
|
||||
new Point(0, IsScrollBarOnDefaultSide ? (int)Padding.W : (int)Padding.Y) :
|
||||
new Point(IsScrollBarOnDefaultSide ? (int)Padding.Z : (int)Padding.X, 0)
|
||||
},
|
||||
isHorizontal: isHorizontal);
|
||||
UpdateScrollBarSize();
|
||||
Enabled = true;
|
||||
ScrollBar.BarScroll = 0.0f;
|
||||
RectTransform.ScaleChanged += () => dimensionsNeedsRecalculation = true;
|
||||
RectTransform.SizeChanged += () => dimensionsNeedsRecalculation = true;
|
||||
UpdateDimensions();
|
||||
}
|
||||
|
||||
private void UpdateDimensions()
|
||||
{
|
||||
dimensionsNeedsRecalculation = false;
|
||||
ContentBackground.RectTransform.Resize(Rect.Size);
|
||||
bool reduceScrollbarSize = KeepSpaceForScrollBar ? ScrollBarEnabled : ScrollBarVisible;
|
||||
Point contentSize = reduceScrollbarSize ? CalculateFrameSize(ScrollBar.IsHorizontal, ScrollBarSize) : Rect.Size;
|
||||
Content.RectTransform.Resize(new Point((int)(contentSize.X - Padding.X - Padding.Z), (int)(contentSize.Y - Padding.Y - Padding.W)));
|
||||
if (!IsScrollBarOnDefaultSide) { Content.RectTransform.SetPosition(Anchor.BottomRight); }
|
||||
Content.RectTransform.AbsoluteOffset = new Point(
|
||||
IsScrollBarOnDefaultSide ? (int)Padding.X : (int)Padding.Z,
|
||||
IsScrollBarOnDefaultSide ? (int)Padding.Y : (int)Padding.W);
|
||||
ScrollBar.RectTransform.Resize(ScrollBar.IsHorizontal ?
|
||||
new Point((int)(Rect.Width - Padding.X - Padding.Z), ScrollBarSize) :
|
||||
new Point(ScrollBarSize, (int)(Rect.Height - Padding.Y - Padding.W)));
|
||||
ScrollBar.RectTransform.AbsoluteOffset = ScrollBar.IsHorizontal ?
|
||||
new Point(0, (int)Padding.W) :
|
||||
new Point((int)Padding.Z, 0);
|
||||
UpdateScrollBarSize();
|
||||
}
|
||||
|
||||
public void Select(object userData, bool force = false, bool autoScroll = true)
|
||||
{
|
||||
var children = Content.Children;
|
||||
int i = 0;
|
||||
foreach (GUIComponent child in children)
|
||||
{
|
||||
if ((child.UserData != null && child.UserData.Equals(userData)) ||
|
||||
(child.UserData == null && userData == null))
|
||||
{
|
||||
Select(i, force, autoScroll);
|
||||
if (!SelectMultiple) return;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
private Point CalculateFrameSize(bool isHorizontal, int scrollBarSize)
|
||||
=> isHorizontal ? new Point(Rect.Width, Rect.Height - scrollBarSize) : new Point(Rect.Width - scrollBarSize, Rect.Height);
|
||||
|
||||
private void RepositionChildren()
|
||||
{
|
||||
int x = 0, y = 0;
|
||||
if (ScrollBar.BarSize < 1.0f)
|
||||
{
|
||||
if (ScrollBar.IsHorizontal)
|
||||
{
|
||||
x -= (int)((totalSize - Content.Rect.Width) * ScrollBar.BarScroll);
|
||||
}
|
||||
else
|
||||
{
|
||||
y -= (int)((totalSize - Content.Rect.Height) * ScrollBar.BarScroll);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < Content.CountChildren; i++)
|
||||
{
|
||||
GUIComponent child = Content.GetChild(i);
|
||||
if (!child.Visible) { continue; }
|
||||
if (RectTransform != null)
|
||||
{
|
||||
if (child != draggedElement && (child.RectTransform.AbsoluteOffset.X != x || child.RectTransform.AbsoluteOffset.Y != y))
|
||||
{
|
||||
child.RectTransform.AbsoluteOffset = new Point(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
if (useGridLayout)
|
||||
{
|
||||
if (ScrollBar.IsHorizontal)
|
||||
{
|
||||
if (y + child.Rect.Height + Spacing > Content.Rect.Height)
|
||||
{
|
||||
y = 0;
|
||||
x += child.Rect.Width + Spacing;
|
||||
if (child != draggedElement && (child.RectTransform.AbsoluteOffset.X != x || child.RectTransform.AbsoluteOffset.Y != y))
|
||||
{
|
||||
child.RectTransform.AbsoluteOffset = new Point(x, y);
|
||||
}
|
||||
y += child.Rect.Height + Spacing;
|
||||
}
|
||||
else
|
||||
{
|
||||
y += child.Rect.Height + Spacing;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (x + child.Rect.Width + Spacing > Content.Rect.Width)
|
||||
{
|
||||
x = 0;
|
||||
y += child.Rect.Height + Spacing;
|
||||
if (child != draggedElement && (child.RectTransform.AbsoluteOffset.X != x || child.RectTransform.AbsoluteOffset.Y != y))
|
||||
{
|
||||
child.RectTransform.AbsoluteOffset = new Point(x, y);
|
||||
}
|
||||
x += child.Rect.Width + Spacing;
|
||||
}
|
||||
else
|
||||
{
|
||||
x += child.Rect.Width + Spacing;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ScrollBar.IsHorizontal)
|
||||
{
|
||||
x += child.Rect.Width + Spacing;
|
||||
}
|
||||
else
|
||||
{
|
||||
y += child.Rect.Height + Spacing;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateChildrenRect()
|
||||
{
|
||||
//dragging
|
||||
if (CanDragElements && draggedElement != null)
|
||||
{
|
||||
if (!PlayerInput.LeftButtonHeld())
|
||||
{
|
||||
OnRearranged?.Invoke(this, draggedElement.UserData);
|
||||
draggedElement = null;
|
||||
RepositionChildren();
|
||||
}
|
||||
else
|
||||
{
|
||||
draggedElement.RectTransform.AbsoluteOffset = draggedReferenceOffset + new Point(0, (int)PlayerInput.MousePosition.Y - draggedReferenceRectangle.Center.Y);
|
||||
|
||||
int index = Content.RectTransform.GetChildIndex(draggedElement.RectTransform);
|
||||
int currIndex = index;
|
||||
|
||||
while (currIndex > 0 && PlayerInput.MousePosition.Y < draggedReferenceRectangle.Top)
|
||||
{
|
||||
currIndex--;
|
||||
draggedReferenceRectangle.Y -= draggedReferenceRectangle.Height;
|
||||
draggedReferenceOffset.Y -= draggedReferenceRectangle.Height;
|
||||
}
|
||||
while (currIndex < Content.CountChildren - 1 && PlayerInput.MousePosition.Y > draggedReferenceRectangle.Bottom)
|
||||
{
|
||||
currIndex++;
|
||||
draggedReferenceRectangle.Y += draggedReferenceRectangle.Height;
|
||||
draggedReferenceOffset.Y += draggedReferenceRectangle.Height;
|
||||
}
|
||||
|
||||
if (currIndex != index)
|
||||
{
|
||||
draggedElement.RectTransform.RepositionChildInHierarchy(currIndex);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < Content.CountChildren; i++)
|
||||
{
|
||||
var child = Content.RectTransform.GetChild(i)?.GUIComponent;
|
||||
if (child == null) continue;
|
||||
|
||||
// selecting
|
||||
if (Enabled && CanBeFocused && child.CanBeFocused && (GUI.IsMouseOn(child)) && child.Rect.Contains(PlayerInput.MousePosition))
|
||||
{
|
||||
child.State = ComponentState.Hover;
|
||||
if (PlayerInput.PrimaryMouseButtonClicked())
|
||||
{
|
||||
Select(i, autoScroll: false);
|
||||
}
|
||||
|
||||
if (CanDragElements && PlayerInput.LeftButtonDown() && GUI.MouseOn == child)
|
||||
{
|
||||
draggedElement = child;
|
||||
draggedReferenceRectangle = child.Rect;
|
||||
draggedReferenceOffset = child.RectTransform.AbsoluteOffset;
|
||||
}
|
||||
}
|
||||
else if (selected.Contains(child))
|
||||
{
|
||||
child.State = ComponentState.Selected;
|
||||
|
||||
if (CheckSelected != null)
|
||||
{
|
||||
if (CheckSelected() != child.UserData) selected.Remove(child);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
child.State = ComponentState.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void AddToGUIUpdateList(bool ignoreChildren = false, int order = 0)
|
||||
{
|
||||
if (!Visible) { return; }
|
||||
|
||||
if (!ignoreChildren)
|
||||
{
|
||||
foreach (GUIComponent child in Children)
|
||||
{
|
||||
if (child == Content || child == ScrollBar || child == ContentBackground) { continue; }
|
||||
child.AddToGUIUpdateList(ignoreChildren, order);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (GUIComponent child in Content.Children)
|
||||
{
|
||||
if (!childVisible.ContainsKey(child)) { childVisible[child] = child.Visible; }
|
||||
if (childVisible[child] != child.Visible)
|
||||
{
|
||||
childVisible[child] = child.Visible;
|
||||
childrenNeedsRecalculation = true;
|
||||
scrollBarNeedsRecalculation = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (childrenNeedsRecalculation)
|
||||
{
|
||||
RecalculateChildren();
|
||||
childVisible.Clear();
|
||||
}
|
||||
|
||||
UpdateOrder = order;
|
||||
GUI.AddToUpdateList(this);
|
||||
|
||||
if (ignoreChildren)
|
||||
{
|
||||
OnAddedToGUIUpdateList?.Invoke(this);
|
||||
return;
|
||||
}
|
||||
|
||||
int lastVisible = 0;
|
||||
for (int i = 0; i < Content.CountChildren; i++)
|
||||
{
|
||||
var child = Content.GetChild(i);
|
||||
if (!child.Visible) continue;
|
||||
if (!IsChildInsideFrame(child))
|
||||
{
|
||||
if (lastVisible > 0) break;
|
||||
continue;
|
||||
}
|
||||
lastVisible = i;
|
||||
child.AddToGUIUpdateList(false, order);
|
||||
}
|
||||
if (ScrollBar.Enabled)
|
||||
{
|
||||
ScrollBar.AddToGUIUpdateList(false, order);
|
||||
}
|
||||
OnAddedToGUIUpdateList?.Invoke(this);
|
||||
}
|
||||
|
||||
public void RecalculateChildren()
|
||||
{
|
||||
foreach (GUIComponent child in Content.Children)
|
||||
{
|
||||
ClampChildMouseRects(child);
|
||||
}
|
||||
RepositionChildren();
|
||||
childrenNeedsRecalculation = false;
|
||||
}
|
||||
|
||||
private void ClampChildMouseRects(GUIComponent child)
|
||||
{
|
||||
child.ClampMouseRectToParent = true;
|
||||
|
||||
//no need to go through grandchildren if the child is a GUIListBox, it handles this by itself
|
||||
if (child is GUIListBox) { return; }
|
||||
|
||||
foreach (GUIComponent grandChild in child.Children)
|
||||
{
|
||||
ClampChildMouseRects(grandChild);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Update(float deltaTime)
|
||||
{
|
||||
if (!Visible) { return; }
|
||||
|
||||
UpdateChildrenRect();
|
||||
RepositionChildren();
|
||||
|
||||
if (scrollBarNeedsRecalculation)
|
||||
{
|
||||
UpdateScrollBarSize();
|
||||
}
|
||||
if ((GUI.IsMouseOn(this) || GUI.IsMouseOn(ScrollBar)) && PlayerInput.ScrollWheelSpeed != 0)
|
||||
{
|
||||
ScrollBar.BarScroll -= (PlayerInput.ScrollWheelSpeed / 500.0f) * BarSize;
|
||||
}
|
||||
ScrollBar.Enabled = ScrollBarEnabled && BarSize < 1.0f;
|
||||
if (AutoHideScrollBar)
|
||||
{
|
||||
ScrollBarVisible = ScrollBar.Enabled;
|
||||
}
|
||||
if (dimensionsNeedsRecalculation)
|
||||
{
|
||||
UpdateDimensions();
|
||||
}
|
||||
}
|
||||
|
||||
public void SelectNext(bool force = false, bool autoScroll = true)
|
||||
{
|
||||
int index = SelectedIndex + 1;
|
||||
while (index < Content.CountChildren)
|
||||
{
|
||||
if (Content.GetChild(index).Visible)
|
||||
{
|
||||
Select(index, force, autoScroll);
|
||||
break;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
public void SelectPrevious(bool force = false, bool autoScroll = true)
|
||||
{
|
||||
int index = SelectedIndex - 1;
|
||||
while (index >= 0)
|
||||
{
|
||||
if (Content.GetChild(index).Visible)
|
||||
{
|
||||
Select(index, force, autoScroll);
|
||||
break;
|
||||
}
|
||||
index--;
|
||||
}
|
||||
}
|
||||
|
||||
public void Select(int childIndex, bool force = false, bool autoScroll = true)
|
||||
{
|
||||
if (childIndex >= Content.CountChildren || childIndex < 0) { return; }
|
||||
|
||||
GUIComponent child = Content.GetChild(childIndex);
|
||||
|
||||
bool wasSelected = true;
|
||||
if (OnSelected != null)
|
||||
{
|
||||
// TODO: The callback is called twice, fix this!
|
||||
wasSelected = force || OnSelected(child, child.UserData);
|
||||
}
|
||||
|
||||
if (!wasSelected) { return; }
|
||||
|
||||
if (SelectMultiple)
|
||||
{
|
||||
if (selected.Contains(child))
|
||||
{
|
||||
selected.Remove(child);
|
||||
}
|
||||
else
|
||||
{
|
||||
selected.Add(child);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
selected.Clear();
|
||||
selected.Add(child);
|
||||
}
|
||||
|
||||
// Ensure that the selected element is visible. This may not be the case, if the selection is run from code. (e.g. if we have two list boxes that are synced)
|
||||
// TODO: This method only works when moving one item up/down (e.g. when using the up and down arrows)
|
||||
if (autoScroll)
|
||||
{
|
||||
if (ScrollBar.IsHorizontal)
|
||||
{
|
||||
if (child.Rect.X < MouseRect.X)
|
||||
{
|
||||
//child outside the left edge of the frame -> move left
|
||||
ScrollBar.BarScroll -= (float)(MouseRect.X - child.Rect.X) / (totalSize - Content.Rect.Width);
|
||||
}
|
||||
else if (child.Rect.Right > MouseRect.Right)
|
||||
{
|
||||
//child outside the right edge of the frame -> move right
|
||||
ScrollBar.BarScroll += (float)(child.Rect.Right - MouseRect.Right) / (totalSize - Content.Rect.Width);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (child.Rect.Y < MouseRect.Y)
|
||||
{
|
||||
//child above the top of the frame -> move up
|
||||
ScrollBar.BarScroll -= (float)(MouseRect.Y - child.Rect.Y) / (totalSize - Content.Rect.Height);
|
||||
}
|
||||
else if (child.Rect.Bottom > MouseRect.Bottom)
|
||||
{
|
||||
//child below the bottom of the frame -> move down
|
||||
ScrollBar.BarScroll += (float)(child.Rect.Bottom - MouseRect.Bottom) / (totalSize - Content.Rect.Height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If one of the children is the subscriber, we don't want to register, because it will unregister the child.
|
||||
if (RectTransform.GetAllChildren().None(rt => rt.GUIComponent == GUI.KeyboardDispatcher.Subscriber))
|
||||
{
|
||||
Selected = true;
|
||||
GUI.KeyboardDispatcher.Subscriber = this;
|
||||
}
|
||||
}
|
||||
|
||||
public void Deselect()
|
||||
{
|
||||
Selected = false;
|
||||
if (GUI.KeyboardDispatcher.Subscriber == this)
|
||||
{
|
||||
GUI.KeyboardDispatcher.Subscriber = null;
|
||||
}
|
||||
selected.Clear();
|
||||
}
|
||||
|
||||
public void UpdateScrollBarSize()
|
||||
{
|
||||
scrollBarNeedsRecalculation = false;
|
||||
if (Content == null) { return; }
|
||||
|
||||
totalSize = 0;
|
||||
var children = Content.Children.Where(c => c.Visible);
|
||||
if (useGridLayout)
|
||||
{
|
||||
int pos = 0;
|
||||
foreach (GUIComponent child in children)
|
||||
{
|
||||
if (ScrollBar.IsHorizontal)
|
||||
{
|
||||
if (pos + child.Rect.Height + Spacing > Content.Rect.Height)
|
||||
{
|
||||
pos = 0;
|
||||
totalSize += child.Rect.Width + Spacing;
|
||||
}
|
||||
pos += child.Rect.Height + Spacing;
|
||||
|
||||
if (child == children.Last())
|
||||
{
|
||||
totalSize += child.Rect.Width + Spacing;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pos + child.Rect.Width + Spacing > Content.Rect.Width)
|
||||
{
|
||||
pos = 0;
|
||||
totalSize += child.Rect.Height + Spacing;
|
||||
}
|
||||
pos += child.Rect.Width + Spacing;
|
||||
|
||||
if (child == children.Last())
|
||||
{
|
||||
totalSize += child.Rect.Height + Spacing;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (GUIComponent child in children)
|
||||
{
|
||||
totalSize += (ScrollBar.IsHorizontal) ? child.Rect.Width : child.Rect.Height;
|
||||
}
|
||||
totalSize += Content.CountChildren * Spacing;
|
||||
}
|
||||
|
||||
float minScrollBarSize = 20.0f;
|
||||
ScrollBar.BarSize = ScrollBar.IsHorizontal ?
|
||||
Math.Max(Math.Min(Content.Rect.Width / (float)totalSize, 1.0f), minScrollBarSize / Content.Rect.Width) :
|
||||
Math.Max(Math.Min(Content.Rect.Height / (float)totalSize, 1.0f), minScrollBarSize / Content.Rect.Height);
|
||||
}
|
||||
|
||||
public override void ClearChildren()
|
||||
{
|
||||
Content.ClearChildren();
|
||||
selected.Clear();
|
||||
}
|
||||
|
||||
public override void RemoveChild(GUIComponent child)
|
||||
{
|
||||
if (child == null) { return; }
|
||||
child.RectTransform.Parent = null;
|
||||
if (selected.Contains(child)) selected.Remove(child);
|
||||
UpdateScrollBarSize();
|
||||
}
|
||||
|
||||
public override void DrawChildren(SpriteBatch spriteBatch, bool recursive)
|
||||
{
|
||||
//do nothing (the children have to be drawn in the Draw method after the ScissorRectangle has been set)
|
||||
return;
|
||||
}
|
||||
|
||||
protected override void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
if (!Visible) { return; }
|
||||
|
||||
ContentBackground.DrawManually(spriteBatch, alsoChildren: false);
|
||||
|
||||
Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
|
||||
RasterizerState prevRasterizerState = spriteBatch.GraphicsDevice.RasterizerState;
|
||||
if (HideChildrenOutsideFrame)
|
||||
{
|
||||
spriteBatch.End();
|
||||
spriteBatch.GraphicsDevice.ScissorRectangle = Rectangle.Intersect(prevScissorRect, Content.Rect);
|
||||
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
|
||||
}
|
||||
|
||||
var children = Content.Children;
|
||||
int lastVisible = 0;
|
||||
|
||||
int i = 0;
|
||||
foreach (GUIComponent child in Content.Children)
|
||||
{
|
||||
if (!child.Visible) continue;
|
||||
if (!IsChildInsideFrame(child))
|
||||
{
|
||||
if (lastVisible > 0) break;
|
||||
continue;
|
||||
}
|
||||
lastVisible = i;
|
||||
child.DrawManually(spriteBatch, alsoChildren: true, recursive: true);
|
||||
i++;
|
||||
}
|
||||
|
||||
if (HideChildrenOutsideFrame)
|
||||
{
|
||||
spriteBatch.End();
|
||||
spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect;
|
||||
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: prevRasterizerState);
|
||||
}
|
||||
|
||||
if (ScrollBarVisible)
|
||||
{
|
||||
ScrollBar.DrawManually(spriteBatch, alsoChildren: true, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsChildInsideFrame(GUIComponent child)
|
||||
{
|
||||
if (child == null) { return false; }
|
||||
|
||||
if (ScrollBar.IsHorizontal)
|
||||
{
|
||||
if (child.Rect.Right < Content.Rect.X) { return false; }
|
||||
if (child.Rect.X > Content.Rect.Right) { return false; }
|
||||
}
|
||||
else
|
||||
{
|
||||
if (child.Rect.Bottom < Content.Rect.Y) { return false; }
|
||||
if (child.Rect.Y > Content.Rect.Bottom) { return false; }
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ReceiveTextInput(char inputChar)
|
||||
{
|
||||
GUI.KeyboardDispatcher.Subscriber = null;
|
||||
}
|
||||
public void ReceiveTextInput(string text) { }
|
||||
public void ReceiveCommandInput(char command) { }
|
||||
|
||||
public void ReceiveSpecialInput(Keys key)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
case Keys.Down:
|
||||
SelectNext();
|
||||
break;
|
||||
case Keys.Up:
|
||||
SelectPrevious();
|
||||
break;
|
||||
case Keys.Enter:
|
||||
case Keys.Space:
|
||||
case Keys.Escape:
|
||||
GUI.KeyboardDispatcher.Subscriber = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class GUIMessage
|
||||
{
|
||||
private ColoredText coloredText;
|
||||
private Vector2 pos;
|
||||
|
||||
private float lifeTime;
|
||||
|
||||
private Vector2 size;
|
||||
|
||||
public readonly bool WorldSpace;
|
||||
|
||||
public string Text
|
||||
{
|
||||
get { return coloredText.Text; }
|
||||
}
|
||||
|
||||
public Color Color
|
||||
{
|
||||
get { return coloredText.Color; }
|
||||
}
|
||||
|
||||
public Vector2 Pos
|
||||
{
|
||||
get { return pos; }
|
||||
set { pos = value; }
|
||||
}
|
||||
|
||||
public Vector2 Velocity
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public Vector2 Size
|
||||
{
|
||||
get { return size; }
|
||||
}
|
||||
|
||||
public Vector2 Origin;
|
||||
|
||||
public float Timer;
|
||||
|
||||
public float LifeTime
|
||||
{
|
||||
get { return lifeTime; }
|
||||
}
|
||||
|
||||
public ScalableFont Font
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public GUIMessage(string text, Color color, float lifeTime, ScalableFont font = null)
|
||||
{
|
||||
coloredText = new ColoredText(text, color, false);
|
||||
this.lifeTime = lifeTime;
|
||||
Timer = lifeTime;
|
||||
|
||||
size = font.MeasureString(text);
|
||||
Origin = new Vector2(0, size.Y * 0.5f);
|
||||
|
||||
Font = font;
|
||||
}
|
||||
|
||||
public GUIMessage(string text, Color color, Vector2 worldPosition, Vector2 velocity, float lifeTime, Alignment textAlignment = Alignment.Center, ScalableFont font = null)
|
||||
{
|
||||
coloredText = new ColoredText(text, color, false);
|
||||
WorldSpace = true;
|
||||
pos = worldPosition;
|
||||
Timer = lifeTime;
|
||||
Velocity = velocity;
|
||||
this.lifeTime = lifeTime;
|
||||
|
||||
Font = font;
|
||||
|
||||
size = font.MeasureString(text);
|
||||
|
||||
Origin = new Vector2((int)(0.5f * size.X), (int)(0.5f * size.Y));
|
||||
if (textAlignment.HasFlag(Alignment.Left))
|
||||
Origin.X -= size.X * 0.5f;
|
||||
|
||||
if (textAlignment.HasFlag(Alignment.Right))
|
||||
Origin.X += size.X * 0.5f;
|
||||
|
||||
if (textAlignment.HasFlag(Alignment.Top))
|
||||
Origin.Y -= size.Y * 0.5f;
|
||||
|
||||
if (textAlignment.HasFlag(Alignment.Bottom))
|
||||
Origin.Y += size.Y * 0.5f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,306 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class GUIMessageBox : GUIFrame
|
||||
{
|
||||
public static List<GUIComponent> MessageBoxes = new List<GUIComponent>();
|
||||
private static int DefaultWidth
|
||||
{
|
||||
get { return Math.Max(400, 400 * (GameMain.GraphicsWidth / 1920)); }
|
||||
}
|
||||
|
||||
private float inGameCloseTimer = 0.0f;
|
||||
private const float inGameCloseTime = 15f;
|
||||
|
||||
public enum Type
|
||||
{
|
||||
Default,
|
||||
InGame
|
||||
}
|
||||
|
||||
public List<GUIButton> Buttons { get; private set; } = new List<GUIButton>();
|
||||
//public GUIFrame BackgroundFrame { get; private set; }
|
||||
public GUILayoutGroup Content { get; private set; }
|
||||
public GUIFrame InnerFrame { get; private set; }
|
||||
public GUITextBlock Header { get; private set; }
|
||||
public GUITextBlock Text { get; private set; }
|
||||
public string Tag { get; private set; }
|
||||
|
||||
public GUIImage Icon
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public Color IconColor
|
||||
{
|
||||
get { return Icon == null ? Color.White : Icon.Color; }
|
||||
set
|
||||
{
|
||||
if (Icon == null) { return; }
|
||||
Icon.Color = value;
|
||||
}
|
||||
}
|
||||
|
||||
private bool alwaysVisible;
|
||||
|
||||
private float openState;
|
||||
private bool closing;
|
||||
|
||||
private Type type;
|
||||
|
||||
public static GUIComponent VisibleBox => MessageBoxes.LastOrDefault();
|
||||
|
||||
public GUIMessageBox(string headerText, string text, Vector2? relativeSize = null, Point? minSize = null)
|
||||
: this(headerText, text, new string[] { "OK" }, relativeSize, minSize)
|
||||
{
|
||||
this.Buttons[0].OnClicked = Close;
|
||||
}
|
||||
|
||||
public GUIMessageBox(string headerText, string text, string[] buttons, Vector2? relativeSize = null, Point? minSize = null, Alignment textAlignment = Alignment.TopLeft, Type type = Type.Default, string tag = "", Sprite icon = null)
|
||||
: base(new RectTransform(Vector2.One, GUI.Canvas, Anchor.Center), style: GUI.Style.GetComponentStyle("GUIMessageBox." + type) != null ? "GUIMessageBox." + type : "GUIMessageBox")
|
||||
{
|
||||
int width = (int)(DefaultWidth * (type == Type.Default ? 1.0f : 1.5f)), height = 0;
|
||||
if (relativeSize.HasValue)
|
||||
{
|
||||
width = (int)(GameMain.GraphicsWidth * relativeSize.Value.X);
|
||||
height = (int)(GameMain.GraphicsHeight * relativeSize.Value.Y);
|
||||
}
|
||||
if (minSize.HasValue)
|
||||
{
|
||||
width = Math.Max(width, minSize.Value.X);
|
||||
if (height > 0)
|
||||
{
|
||||
height = Math.Max(height, minSize.Value.Y);
|
||||
}
|
||||
}
|
||||
|
||||
InnerFrame = new GUIFrame(new RectTransform(new Point(width, height), RectTransform, type == Type.InGame ? Anchor.TopCenter : Anchor.Center) { IsFixedSize = false }, style: null);
|
||||
GUI.Style.Apply(InnerFrame, "", this);
|
||||
this.type = type;
|
||||
Tag = tag;
|
||||
|
||||
if (type == Type.Default)
|
||||
{
|
||||
Content = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.85f), InnerFrame.RectTransform, Anchor.Center)) { AbsoluteSpacing = 5 };
|
||||
|
||||
Header = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), Content.RectTransform),
|
||||
headerText, font: GUI.SubHeadingFont, textAlignment: Alignment.Center, wrap: true);
|
||||
GUI.Style.Apply(Header, "", this);
|
||||
Header.RectTransform.MinSize = new Point(0, Header.Rect.Height);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
Text = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), Content.RectTransform), text, textAlignment: textAlignment, wrap: true);
|
||||
GUI.Style.Apply(Text, "", this);
|
||||
Text.RectTransform.NonScaledSize = Text.RectTransform.MinSize = Text.RectTransform.MaxSize =
|
||||
new Point(Text.Rect.Width, Text.Rect.Height);
|
||||
Text.RectTransform.IsFixedSize = true;
|
||||
}
|
||||
|
||||
var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.15f), Content.RectTransform, Anchor.BottomCenter), childAnchor: Anchor.TopCenter)
|
||||
{
|
||||
AbsoluteSpacing = 5,
|
||||
IgnoreLayoutGroups = true
|
||||
};
|
||||
|
||||
int buttonSize = 35;
|
||||
var buttonStyle = GUI.Style.GetComponentStyle("GUIButton");
|
||||
if (buttonStyle != null && buttonStyle.Height.HasValue)
|
||||
{
|
||||
buttonSize = buttonStyle.Height.Value;
|
||||
}
|
||||
|
||||
buttonContainer.RectTransform.NonScaledSize = buttonContainer.RectTransform.MinSize = buttonContainer.RectTransform.MaxSize =
|
||||
new Point(buttonContainer.Rect.Width, (int)((buttonSize + 5) * buttons.Length));
|
||||
buttonContainer.RectTransform.IsFixedSize = true;
|
||||
|
||||
if (height == 0)
|
||||
{
|
||||
height += Header.Rect.Height + Content.AbsoluteSpacing;
|
||||
height += (Text == null ? 0 : Text.Rect.Height) + Content.AbsoluteSpacing;
|
||||
height += buttonContainer.Rect.Height + 20;
|
||||
if (minSize.HasValue) { height = Math.Max(height, minSize.Value.Y); }
|
||||
|
||||
InnerFrame.RectTransform.NonScaledSize =
|
||||
new Point(InnerFrame.Rect.Width, (int)Math.Max(height / Content.RectTransform.RelativeSize.Y, height + (int)(50 * GUI.yScale)));
|
||||
Content.RectTransform.NonScaledSize =
|
||||
new Point(Content.Rect.Width, height);
|
||||
}
|
||||
|
||||
Buttons = new List<GUIButton>(buttons.Length);
|
||||
for (int i = 0; i < buttons.Length; i++)
|
||||
{
|
||||
var button = new GUIButton(new RectTransform(new Vector2(0.6f, 1.0f / buttons.Length), buttonContainer.RectTransform), buttons[i]);
|
||||
Buttons.Add(button);
|
||||
}
|
||||
}
|
||||
else if (type == Type.InGame)
|
||||
{
|
||||
InnerFrame.RectTransform.AbsoluteOffset = new Point(0, GameMain.GraphicsHeight);
|
||||
alwaysVisible = true;
|
||||
CanBeFocused = false;
|
||||
GUI.Style.Apply(InnerFrame, "", this);
|
||||
|
||||
var horizontalLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.98f, 0.95f), InnerFrame.RectTransform, Anchor.Center),
|
||||
isHorizontal: true, childAnchor: Anchor.CenterLeft)
|
||||
{
|
||||
Stretch = true,
|
||||
RelativeSpacing = 0.02f
|
||||
};
|
||||
if (icon != null)
|
||||
{
|
||||
Icon = new GUIImage(new RectTransform(new Vector2(0.2f, 0.95f), horizontalLayoutGroup.RectTransform), icon, scaleToFit: true);
|
||||
}
|
||||
|
||||
Content = new GUILayoutGroup(new RectTransform(new Vector2(icon != null ? 0.65f : 0.85f, 1.0f), horizontalLayoutGroup.RectTransform));
|
||||
|
||||
var buttonContainer = new GUIFrame(new RectTransform(new Vector2(0.15f, 1.0f), horizontalLayoutGroup.RectTransform), style: null);
|
||||
Buttons = new List<GUIButton>(1)
|
||||
{
|
||||
new GUIButton(new RectTransform(new Vector2(0.5f, 0.5f), buttonContainer.RectTransform, Anchor.Center),
|
||||
style: "GUIButtonHorizontalArrow")
|
||||
{
|
||||
OnClicked = Close
|
||||
}
|
||||
};
|
||||
|
||||
Header = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), Content.RectTransform), headerText, wrap: true);
|
||||
GUI.Style.Apply(Header, "", this);
|
||||
Header.RectTransform.MinSize = new Point(0, Header.Rect.Height);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
Text = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), Content.RectTransform), text, textAlignment: textAlignment, wrap: true);
|
||||
GUI.Style.Apply(Text, "", this);
|
||||
Content.Recalculate();
|
||||
Text.RectTransform.NonScaledSize = Text.RectTransform.MinSize = Text.RectTransform.MaxSize =
|
||||
new Point(Text.Rect.Width, Text.Rect.Height);
|
||||
Text.RectTransform.IsFixedSize = true;
|
||||
}
|
||||
|
||||
if (height == 0)
|
||||
{
|
||||
height += Header.Rect.Height + Content.AbsoluteSpacing;
|
||||
height += (Text == null ? 0 : Text.Rect.Height) + Content.AbsoluteSpacing;
|
||||
if (minSize.HasValue) { height = Math.Max(height, minSize.Value.Y); }
|
||||
|
||||
InnerFrame.RectTransform.NonScaledSize =
|
||||
new Point(InnerFrame.Rect.Width, (int)Math.Max(height / Content.RectTransform.RelativeSize.Y, height + (int)(50 * GUI.yScale)));
|
||||
Content.RectTransform.NonScaledSize =
|
||||
new Point(Content.Rect.Width, height);
|
||||
}
|
||||
Buttons[0].RectTransform.MaxSize = new Point(Math.Min(Buttons[0].Rect.Width, Buttons[0].Rect.Height));
|
||||
}
|
||||
|
||||
MessageBoxes.Add(this);
|
||||
}
|
||||
|
||||
public static void AddActiveToGUIUpdateList()
|
||||
{
|
||||
for (int i = 0; i < MessageBoxes.Count; i++)
|
||||
{
|
||||
if (MessageBoxes[i] is GUIMessageBox alwaysVisibleMsgBox && alwaysVisibleMsgBox.alwaysVisible)
|
||||
{
|
||||
alwaysVisibleMsgBox.AddToGUIUpdateList();
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (int i = MessageBoxes.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (MessageBoxes[i].UserData as string == "verificationprompt" ||
|
||||
MessageBoxes[i].UserData as string == "bugreporter")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!(MessageBoxes[i] is GUIMessageBox msgBox) || !msgBox.alwaysVisible)
|
||||
{
|
||||
MessageBoxes[i].AddToGUIUpdateList();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Update(float deltaTime)
|
||||
{
|
||||
if (type == Type.InGame)
|
||||
{
|
||||
Vector2 initialPos = new Vector2(0.0f, GameMain.GraphicsHeight);
|
||||
Vector2 defaultPos = new Vector2(0.0f, HUDLayoutSettings.InventoryAreaLower.Y - InnerFrame.Rect.Height - 20 * GUI.Scale);
|
||||
Vector2 endPos = new Vector2(GameMain.GraphicsWidth, defaultPos.Y);
|
||||
|
||||
/*for (int i = MessageBoxes.IndexOf(this); i >= 0; i--)
|
||||
{
|
||||
if (MessageBoxes[i] is GUIMessageBox otherMsgBox && otherMsgBox != this && otherMsgBox.type == type && !otherMsgBox.closing)
|
||||
{
|
||||
defaultPos = new Vector2(
|
||||
Math.Max(otherMsgBox.InnerFrame.RectTransform.AbsoluteOffset.X + 10 * GUI.Scale, defaultPos.X),
|
||||
Math.Max(otherMsgBox.InnerFrame.RectTransform.AbsoluteOffset.Y + 10 * GUI.Scale, defaultPos.Y));
|
||||
}
|
||||
}*/
|
||||
|
||||
if (!closing)
|
||||
{
|
||||
InnerFrame.RectTransform.AbsoluteOffset = Vector2.SmoothStep(initialPos, defaultPos, openState).ToPoint();
|
||||
openState = Math.Min(openState + deltaTime * 2.0f, 1.0f);
|
||||
|
||||
inGameCloseTimer += deltaTime;
|
||||
|
||||
if (inGameCloseTimer >= inGameCloseTime)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
openState += deltaTime * 2.0f;
|
||||
InnerFrame.RectTransform.AbsoluteOffset = Vector2.SmoothStep(defaultPos, endPos, openState - 1.0f).ToPoint();
|
||||
if (openState >= 2.0f)
|
||||
{
|
||||
if (Parent != null) { Parent.RemoveChild(this); }
|
||||
if (MessageBoxes.Contains(this)) { MessageBoxes.Remove(this); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void Close()
|
||||
{
|
||||
if (type == Type.InGame)
|
||||
{
|
||||
closing = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Parent != null) { Parent.RemoveChild(this); }
|
||||
if (MessageBoxes.Contains(this)) { MessageBoxes.Remove(this); }
|
||||
}
|
||||
}
|
||||
|
||||
public bool Close(GUIButton button, object obj)
|
||||
{
|
||||
Close();
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void CloseAll()
|
||||
{
|
||||
MessageBoxes.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parent does not matter. It's overridden.
|
||||
/// </summary>
|
||||
public void AddButton(RectTransform rectT, string text, GUIButton.OnClickedHandler onClick)
|
||||
{
|
||||
rectT.Parent = RectTransform;
|
||||
Buttons.Add(new GUIButton(rectT, text) { OnClicked = onClick });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,417 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class GUINumberInput : GUIComponent
|
||||
{
|
||||
public enum NumberType
|
||||
{
|
||||
Int, Float
|
||||
}
|
||||
|
||||
public delegate void OnValueChangedHandler(GUINumberInput numberInput);
|
||||
public OnValueChangedHandler OnValueChanged;
|
||||
|
||||
public GUITextBox TextBox { get; private set; }
|
||||
|
||||
private GUIButton plusButton, minusButton;
|
||||
|
||||
private NumberType inputType;
|
||||
public NumberType InputType
|
||||
{
|
||||
get { return inputType; }
|
||||
set
|
||||
{
|
||||
if (inputType == value) { return; }
|
||||
inputType = value;
|
||||
if (inputType == NumberType.Int ||
|
||||
(inputType == NumberType.Float && MinValueFloat > float.MinValue && MaxValueFloat < float.MaxValue))
|
||||
{
|
||||
ShowPlusMinusButtons();
|
||||
}
|
||||
else
|
||||
{
|
||||
HidePlusMinusButtons();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private float? minValueFloat, maxValueFloat;
|
||||
public float? MinValueFloat
|
||||
{
|
||||
get { return minValueFloat; }
|
||||
set
|
||||
{
|
||||
minValueFloat = value;
|
||||
ClampFloatValue();
|
||||
if (inputType == NumberType.Int ||
|
||||
(inputType == NumberType.Float && MinValueFloat > float.MinValue && MaxValueFloat < float.MaxValue))
|
||||
{
|
||||
ShowPlusMinusButtons();
|
||||
}
|
||||
else
|
||||
{
|
||||
HidePlusMinusButtons();
|
||||
}
|
||||
}
|
||||
}
|
||||
public float? MaxValueFloat
|
||||
{
|
||||
get { return maxValueFloat; }
|
||||
set
|
||||
{
|
||||
maxValueFloat = value;
|
||||
ClampFloatValue();
|
||||
if (inputType == NumberType.Int ||
|
||||
(inputType == NumberType.Float && MinValueFloat > float.MinValue && MaxValueFloat < float.MaxValue))
|
||||
{
|
||||
ShowPlusMinusButtons();
|
||||
}
|
||||
else
|
||||
{
|
||||
HidePlusMinusButtons();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private float floatValue;
|
||||
public float FloatValue
|
||||
{
|
||||
get { return floatValue; }
|
||||
set
|
||||
{
|
||||
if (MathUtils.NearlyEqual(value, floatValue)) return;
|
||||
floatValue = value;
|
||||
ClampFloatValue();
|
||||
float newValue = floatValue;
|
||||
UpdateText();
|
||||
//UpdateText may remove decimals from the value, force to full accuracy
|
||||
floatValue = newValue;
|
||||
OnValueChanged?.Invoke(this);
|
||||
}
|
||||
}
|
||||
|
||||
private int decimalsToDisplay = 1;
|
||||
public int DecimalsToDisplay
|
||||
{
|
||||
get { return decimalsToDisplay; }
|
||||
set
|
||||
{
|
||||
decimalsToDisplay = value;
|
||||
UpdateText();
|
||||
}
|
||||
}
|
||||
|
||||
private int? minValueInt, maxValueInt;
|
||||
public int? MinValueInt
|
||||
{
|
||||
get { return minValueInt; }
|
||||
set
|
||||
{
|
||||
minValueInt = value;
|
||||
ClampIntValue();
|
||||
}
|
||||
}
|
||||
public int? MaxValueInt
|
||||
{
|
||||
get { return maxValueInt; }
|
||||
set
|
||||
{
|
||||
maxValueInt = value;
|
||||
ClampIntValue();
|
||||
}
|
||||
}
|
||||
|
||||
private int intValue;
|
||||
public int IntValue
|
||||
{
|
||||
get { return intValue; }
|
||||
set
|
||||
{
|
||||
if (value == intValue) return;
|
||||
intValue = value;
|
||||
UpdateText();
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Enabled
|
||||
{
|
||||
get => base.Enabled;
|
||||
set
|
||||
{
|
||||
plusButton.Enabled = true;
|
||||
minusButton.Enabled = true;
|
||||
if (InputType == NumberType.Int) { ClampIntValue(); } else { ClampFloatValue(); }
|
||||
TextBox.Enabled = value;
|
||||
if (!value)
|
||||
{
|
||||
plusButton.Enabled = false;
|
||||
minusButton.Enabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override ScalableFont Font
|
||||
{
|
||||
get
|
||||
{
|
||||
return base.Font;
|
||||
}
|
||||
set
|
||||
{
|
||||
base.Font = value;
|
||||
if (TextBox != null) { TextBox.Font = value; }
|
||||
}
|
||||
}
|
||||
|
||||
public GUILayoutGroup LayoutGroup
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public float valueStep;
|
||||
|
||||
private float pressedTimer;
|
||||
private float pressedDelay = 0.5f;
|
||||
private bool IsPressedTimerRunning { get { return pressedTimer > 0; } }
|
||||
|
||||
public GUINumberInput(RectTransform rectT, NumberType inputType, string style = "", Alignment textAlignment = Alignment.Center, float? relativeButtonAreaWidth = null) : base(style, rectT)
|
||||
{
|
||||
LayoutGroup = new GUILayoutGroup(new RectTransform(Vector2.One, rectT), isHorizontal: true, childAnchor: Anchor.CenterLeft) { Stretch = true };
|
||||
|
||||
float _relativeButtonAreaWidth = relativeButtonAreaWidth ?? MathHelper.Clamp(Rect.Height / (float)Rect.Width, 0.1f, 0.25f);
|
||||
|
||||
TextBox = new GUITextBox(new RectTransform(new Vector2(1.0f - _relativeButtonAreaWidth, 1.0f), LayoutGroup.RectTransform), textAlignment: textAlignment, style: "GUITextBoxNoIcon")
|
||||
{
|
||||
ClampText = false
|
||||
};
|
||||
TextBox.CaretColor = TextBox.TextColor;
|
||||
TextBox.OnTextChanged += TextChanged;
|
||||
|
||||
var buttonArea = new GUIFrame(new RectTransform(new Vector2(_relativeButtonAreaWidth, 1.0f), LayoutGroup.RectTransform, Anchor.CenterRight), style: null);
|
||||
plusButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.5f), buttonArea.RectTransform), style: null);
|
||||
GUI.Style.Apply(plusButton, "PlusButton", this);
|
||||
plusButton.OnButtonDown += () =>
|
||||
{
|
||||
pressedTimer = pressedDelay;
|
||||
return true;
|
||||
};
|
||||
plusButton.OnClicked += (button, data) =>
|
||||
{
|
||||
IncreaseValue();
|
||||
return true;
|
||||
};
|
||||
plusButton.OnPressed += () =>
|
||||
{
|
||||
if (!IsPressedTimerRunning)
|
||||
{
|
||||
IncreaseValue();
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
minusButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.5f), buttonArea.RectTransform, Anchor.BottomRight), style: null);
|
||||
GUI.Style.Apply(minusButton, "MinusButton", this);
|
||||
minusButton.OnButtonDown += () =>
|
||||
{
|
||||
pressedTimer = pressedDelay;
|
||||
return true;
|
||||
};
|
||||
minusButton.OnClicked += (button, data) =>
|
||||
{
|
||||
ReduceValue();
|
||||
return true;
|
||||
};
|
||||
minusButton.OnPressed += () =>
|
||||
{
|
||||
if (!IsPressedTimerRunning)
|
||||
{
|
||||
ReduceValue();
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
if (inputType != NumberType.Int)
|
||||
{
|
||||
HidePlusMinusButtons();
|
||||
}
|
||||
|
||||
if (inputType == NumberType.Int)
|
||||
{
|
||||
UpdateText();
|
||||
TextBox.OnEnterPressed += (txtBox, txt) =>
|
||||
{
|
||||
UpdateText();
|
||||
TextBox.Deselect();
|
||||
return true;
|
||||
};
|
||||
TextBox.OnDeselected += (txtBox, key) => UpdateText();
|
||||
}
|
||||
else if (inputType == NumberType.Float)
|
||||
{
|
||||
UpdateText();
|
||||
TextBox.OnDeselected += (txtBox, key) => UpdateText();
|
||||
TextBox.OnEnterPressed += (txtBox, txt) =>
|
||||
{
|
||||
UpdateText();
|
||||
TextBox.Deselect();
|
||||
return true;
|
||||
};
|
||||
}
|
||||
InputType = inputType;
|
||||
switch (InputType)
|
||||
{
|
||||
case NumberType.Int:
|
||||
TextBox.textFilterFunction = text => new string(text.Where(c => char.IsNumber(c) || c == '-').ToArray());
|
||||
break;
|
||||
case NumberType.Float:
|
||||
TextBox.textFilterFunction = text => new string(text.Where(c => char.IsDigit(c) || c == '.' || c == '-').ToArray());
|
||||
break;
|
||||
}
|
||||
|
||||
RectTransform.MinSize = TextBox.RectTransform.MinSize;
|
||||
LayoutGroup.Recalculate();
|
||||
}
|
||||
|
||||
private void HidePlusMinusButtons()
|
||||
{
|
||||
plusButton.Parent.Visible = false;
|
||||
plusButton.Parent.IgnoreLayoutGroups = true;
|
||||
TextBox.RectTransform.RelativeSize = Vector2.One;
|
||||
LayoutGroup.Recalculate();
|
||||
}
|
||||
|
||||
private void ShowPlusMinusButtons()
|
||||
{
|
||||
plusButton.Parent.Visible = true;
|
||||
plusButton.Parent.IgnoreLayoutGroups = false;
|
||||
TextBox.RectTransform.RelativeSize = new Vector2(1.0f - plusButton.Parent.RectTransform.RelativeSize.X, 1.0f);
|
||||
LayoutGroup.Recalculate();
|
||||
}
|
||||
|
||||
private void ReduceValue()
|
||||
{
|
||||
if (inputType == NumberType.Int)
|
||||
{
|
||||
IntValue -= valueStep > 0 ? (int)valueStep : 1;
|
||||
}
|
||||
else if (maxValueFloat.HasValue && minValueFloat.HasValue)
|
||||
{
|
||||
FloatValue -= valueStep > 0 ? valueStep : Round();
|
||||
}
|
||||
}
|
||||
|
||||
private void IncreaseValue()
|
||||
{
|
||||
if (inputType == NumberType.Int)
|
||||
{
|
||||
IntValue += valueStep > 0 ? (int)valueStep : 1;
|
||||
}
|
||||
else if (inputType == NumberType.Float)
|
||||
{
|
||||
FloatValue += valueStep > 0 ? valueStep : Round();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates one percent between the range as the increment/decrement.
|
||||
/// This value is rounded so that the bigger it is, the less decimals are used (min 0, max 3).
|
||||
/// Return value is clamped between 0.1f and 1000.
|
||||
/// </summary>
|
||||
private float Round()
|
||||
{
|
||||
if (!maxValueFloat.HasValue || !minValueFloat.HasValue) return 0;
|
||||
float onePercent = MathHelper.Lerp(minValueFloat.Value, maxValueFloat.Value, 0.01f);
|
||||
float diff = maxValueFloat.Value - minValueFloat.Value;
|
||||
int decimals = (int)MathHelper.Lerp(3, 0, MathUtils.InverseLerp(10, 1000, diff));
|
||||
return MathHelper.Clamp((float)Math.Round(onePercent, decimals), 0.1f, 1000);
|
||||
}
|
||||
|
||||
private bool TextChanged(GUITextBox textBox, string text)
|
||||
{
|
||||
switch (InputType)
|
||||
{
|
||||
case NumberType.Int:
|
||||
int newIntValue = IntValue;
|
||||
if (string.IsNullOrWhiteSpace(text) || text == "-")
|
||||
{
|
||||
intValue = 0;
|
||||
}
|
||||
else if (int.TryParse(text, out newIntValue))
|
||||
{
|
||||
intValue = newIntValue;
|
||||
}
|
||||
ClampIntValue();
|
||||
break;
|
||||
case NumberType.Float:
|
||||
float newFloatValue = FloatValue;
|
||||
if (string.IsNullOrWhiteSpace(text) || text == "-")
|
||||
{
|
||||
floatValue = 0;
|
||||
}
|
||||
else if (float.TryParse(text, NumberStyles.Any, CultureInfo.InvariantCulture, out newFloatValue))
|
||||
{
|
||||
floatValue = newFloatValue;
|
||||
}
|
||||
ClampFloatValue();
|
||||
break;
|
||||
}
|
||||
OnValueChanged?.Invoke(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ClampFloatValue()
|
||||
{
|
||||
if (MinValueFloat != null)
|
||||
{
|
||||
floatValue = Math.Max(floatValue, MinValueFloat.Value);
|
||||
minusButton.Enabled = floatValue > MinValueFloat;
|
||||
}
|
||||
if (MaxValueFloat != null)
|
||||
{
|
||||
floatValue = Math.Min(floatValue, MaxValueFloat.Value);
|
||||
plusButton.Enabled = floatValue < MaxValueFloat;
|
||||
}
|
||||
}
|
||||
|
||||
private void ClampIntValue()
|
||||
{
|
||||
if (MinValueInt != null)
|
||||
{
|
||||
intValue = Math.Max(intValue, MinValueInt.Value);
|
||||
minusButton.Enabled = intValue > MinValueInt;
|
||||
}
|
||||
if (MaxValueInt != null)
|
||||
{
|
||||
intValue = Math.Min(intValue, MaxValueInt.Value);
|
||||
plusButton.Enabled = intValue < MaxValueInt;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateText()
|
||||
{
|
||||
switch (InputType)
|
||||
{
|
||||
case NumberType.Float:
|
||||
TextBox.Text = FloatValue.Format(decimalsToDisplay);
|
||||
break;
|
||||
case NumberType.Int:
|
||||
TextBox.Text = IntValue.ToString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Update(float deltaTime)
|
||||
{
|
||||
base.Update(deltaTime);
|
||||
if (IsPressedTimerRunning)
|
||||
{
|
||||
pressedTimer -= deltaTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class GUIProgressBar : GUIComponent
|
||||
{
|
||||
private bool isHorizontal;
|
||||
private readonly GUIFrame frame, slider;
|
||||
private float barSize;
|
||||
private readonly bool showFrame;
|
||||
|
||||
public delegate float ProgressGetterHandler();
|
||||
public ProgressGetterHandler ProgressGetter;
|
||||
|
||||
public bool IsHorizontal
|
||||
{
|
||||
get { return isHorizontal; }
|
||||
set { isHorizontal = value; }
|
||||
}
|
||||
|
||||
public float BarSize
|
||||
{
|
||||
get { return barSize; }
|
||||
set
|
||||
{
|
||||
if (!MathUtils.IsValid(value))
|
||||
{
|
||||
GameAnalyticsManager.AddErrorEventOnce(
|
||||
"GUIProgressBar.BarSize_setter",
|
||||
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
|
||||
"Attempted to set the BarSize of a GUIProgressBar to an invalid value (" + value + ")\n" + Environment.StackTrace);
|
||||
return;
|
||||
}
|
||||
barSize = MathHelper.Clamp(value, 0.0f, 1.0f);
|
||||
//UpdateRect();
|
||||
}
|
||||
}
|
||||
|
||||
public GUIProgressBar(RectTransform rectT, float barSize, Color? color = null, string style = "", bool showFrame = true) : base(style, rectT)
|
||||
{
|
||||
if (color.HasValue)
|
||||
{
|
||||
this.color = color.Value;
|
||||
}
|
||||
isHorizontal = (Rect.Width > Rect.Height);
|
||||
frame = new GUIFrame(new RectTransform(Vector2.One, rectT));
|
||||
GUI.Style.Apply(frame, "", this);
|
||||
slider = new GUIFrame(new RectTransform(Vector2.One, rectT));
|
||||
GUI.Style.Apply(slider, "Slider", this);
|
||||
this.showFrame = showFrame;
|
||||
this.barSize = barSize;
|
||||
Enabled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the area the slider should be drawn inside
|
||||
/// </summary>
|
||||
/// <param name="fillAmount">0 = empty, 1 = full</param>
|
||||
public Rectangle GetSliderRect(float fillAmount)
|
||||
{
|
||||
Rectangle sliderArea = new Rectangle(
|
||||
frame.Rect.X + (int)style.Padding.X,
|
||||
frame.Rect.Y + (int)style.Padding.Y,
|
||||
(int)(frame.Rect.Width - style.Padding.X - style.Padding.Z),
|
||||
(int)(frame.Rect.Height - style.Padding.Y - style.Padding.W));
|
||||
|
||||
Vector4 sliceBorderSizes = Vector4.Zero;
|
||||
if (slider.sprites.ContainsKey(slider.State) && (slider.sprites[slider.State].First()?.Slice ?? false))
|
||||
{
|
||||
var slices = slider.sprites[slider.State].First().Slices;
|
||||
sliceBorderSizes = new Vector4(slices[0].Width, slices[0].Height, slices[8].Width, slices[8].Height);
|
||||
sliceBorderSizes *= slider.sprites[slider.State].First().GetSliceBorderScale(sliderArea.Size);
|
||||
}
|
||||
|
||||
Rectangle sliderRect = IsHorizontal ?
|
||||
new Rectangle(
|
||||
sliderArea.X + (int)sliceBorderSizes.X,
|
||||
sliderArea.Y,
|
||||
(int)((sliderArea.Width - sliceBorderSizes.X - sliceBorderSizes.Z) * fillAmount),
|
||||
sliderArea.Height)
|
||||
:
|
||||
new Rectangle(
|
||||
sliderArea.X,
|
||||
(int)(sliderArea.Bottom - (sliderArea.Height - sliceBorderSizes.Y - sliceBorderSizes.W) * fillAmount - sliceBorderSizes.W),
|
||||
sliderArea.Width,
|
||||
(int)((sliderArea.Height - sliceBorderSizes.Y - sliceBorderSizes.W) * fillAmount));
|
||||
|
||||
sliderRect.Width = Math.Max(sliderRect.Width, 1);
|
||||
sliderRect.Height = Math.Max(sliderRect.Height, 1);
|
||||
|
||||
return sliderRect;
|
||||
}
|
||||
|
||||
protected override void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
if (!Visible) { return; }
|
||||
|
||||
if (ProgressGetter != null)
|
||||
{
|
||||
float newSize = MathHelper.Clamp(ProgressGetter(), 0.0f, 1.0f);
|
||||
if (!MathUtils.IsValid(newSize))
|
||||
{
|
||||
GameAnalyticsManager.AddErrorEventOnce(
|
||||
"GUIProgressBar.Draw:GetProgress",
|
||||
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
|
||||
"ProgressGetter of a GUIProgressBar (" + ProgressGetter.Target.ToString() + " - " + ProgressGetter.Method.ToString() + ") returned an invalid value (" + newSize + ")\n" + Environment.StackTrace);
|
||||
}
|
||||
else
|
||||
{
|
||||
BarSize = newSize;
|
||||
}
|
||||
}
|
||||
|
||||
var sliderRect = GetSliderRect(barSize);
|
||||
|
||||
slider.RectTransform.AbsoluteOffset = new Point((int)style.Padding.X, (int)style.Padding.Y);
|
||||
slider.RectTransform.MaxSize = new Point(
|
||||
(int)(Rect.Width - style.Padding.X + style.Padding.Z),
|
||||
(int)(Rect.Height - style.Padding.Y + style.Padding.W));
|
||||
frame.Visible = showFrame;
|
||||
slider.Visible = true;
|
||||
|
||||
if (showFrame)
|
||||
{
|
||||
if (AutoDraw)
|
||||
{
|
||||
frame.DrawAuto(spriteBatch);
|
||||
}
|
||||
else
|
||||
{
|
||||
frame.DrawManually(spriteBatch);
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
|
||||
if (BarSize <= 1.0f)
|
||||
{
|
||||
spriteBatch.End();
|
||||
spriteBatch.GraphicsDevice.ScissorRectangle = Rectangle.Intersect(prevScissorRect, sliderRect);
|
||||
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
|
||||
}
|
||||
|
||||
Color currColor = GetColor(State);
|
||||
|
||||
slider.Color = currColor;
|
||||
if (AutoDraw)
|
||||
{
|
||||
slider.DrawAuto(spriteBatch);
|
||||
}
|
||||
else
|
||||
{
|
||||
slider.DrawManually(spriteBatch);
|
||||
}
|
||||
//hide the slider, we've already drawn it manually
|
||||
frame.Visible = false;
|
||||
slider.Visible = false;
|
||||
if (BarSize <= 1.0f)
|
||||
{
|
||||
spriteBatch.End();
|
||||
spriteBatch.Begin(SpriteSortMode.Deferred, rasterizerState: GameMain.ScissorTestEnable);
|
||||
spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class GUIRadioButtonGroup : GUIComponent
|
||||
{
|
||||
private Dictionary<int, GUITickBox> radioButtons; //TODO: use children list instead?
|
||||
|
||||
public GUIRadioButtonGroup() : base(null)
|
||||
{
|
||||
radioButtons = new Dictionary<int, GUITickBox>();
|
||||
selected = null;
|
||||
}
|
||||
|
||||
public override bool Enabled
|
||||
{
|
||||
get => base.Enabled;
|
||||
set
|
||||
{
|
||||
base.Enabled = value;
|
||||
foreach(KeyValuePair<int, GUITickBox> rbPair in radioButtons)
|
||||
{
|
||||
rbPair.Value.Enabled = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddRadioButton(int key, GUITickBox radioButton)
|
||||
{
|
||||
if (selected == key) radioButton.Selected = true;
|
||||
else if (radioButton.Selected) selected = key;
|
||||
|
||||
radioButton.SetRadioButtonGroup(this);
|
||||
radioButtons.Add((int)key, radioButton);
|
||||
}
|
||||
|
||||
public delegate void RadioButtonGroupDelegate(GUIRadioButtonGroup rbg, int? val);
|
||||
public RadioButtonGroupDelegate OnSelect = null;
|
||||
|
||||
public void SelectRadioButton(GUITickBox radioButton)
|
||||
{
|
||||
foreach (KeyValuePair<int, GUITickBox> rbPair in radioButtons)
|
||||
{
|
||||
if (radioButton == rbPair.Value)
|
||||
{
|
||||
Selected = rbPair.Key;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// intentional hiding?
|
||||
private new int? selected;
|
||||
public new int? Selected
|
||||
{
|
||||
get
|
||||
{
|
||||
return selected;
|
||||
}
|
||||
set
|
||||
{
|
||||
OnSelect?.Invoke(this, value);
|
||||
if (selected != null && selected.Equals(value)) { return; }
|
||||
selected = value;
|
||||
foreach (KeyValuePair<int, GUITickBox> radioButton in radioButtons)
|
||||
{
|
||||
if (radioButton.Key.Equals(value))
|
||||
{
|
||||
radioButton.Value.Selected = true;
|
||||
}
|
||||
else if (radioButton.Value.Selected)
|
||||
{
|
||||
radioButton.Value.Selected = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public GUITickBox SelectedRadioButton
|
||||
{
|
||||
get
|
||||
{
|
||||
return selected.HasValue ? radioButtons[selected.Value] : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,339 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class GUIScrollBar : GUIComponent
|
||||
{
|
||||
public static GUIScrollBar DraggingBar
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
private bool isHorizontal;
|
||||
|
||||
public GUIFrame Frame { get; private set; }
|
||||
public GUIButton Bar { get; private set; }
|
||||
private float barSize;
|
||||
private float barScroll;
|
||||
|
||||
private float step;
|
||||
|
||||
private Vector2? dragStartPos;
|
||||
|
||||
public delegate bool OnMovedHandler(GUIScrollBar scrollBar, float barScroll);
|
||||
public OnMovedHandler OnMoved;
|
||||
public OnMovedHandler OnReleased;
|
||||
|
||||
public bool IsBooleanSwitch;
|
||||
|
||||
public override string ToolTip
|
||||
{
|
||||
get { return base.ToolTip; }
|
||||
set
|
||||
{
|
||||
base.ToolTip = value;
|
||||
Frame.ToolTip = value;
|
||||
Bar.ToolTip = value;
|
||||
}
|
||||
}
|
||||
|
||||
private float minValue;
|
||||
public float MinValue
|
||||
{
|
||||
get { return minValue; }
|
||||
set
|
||||
{
|
||||
minValue = MathHelper.Clamp(value, 0.0f, 1.0f);
|
||||
BarScroll = Math.Max(minValue, barScroll);
|
||||
}
|
||||
}
|
||||
|
||||
private float maxValue = 1.0f;
|
||||
public float MaxValue
|
||||
{
|
||||
get { return maxValue; }
|
||||
set
|
||||
{
|
||||
maxValue = MathHelper.Clamp(value, 0.0f, 1.0f);
|
||||
BarScroll = Math.Min(maxValue, barScroll);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsHorizontal
|
||||
{
|
||||
get { return isHorizontal; }
|
||||
/*set
|
||||
{
|
||||
if (isHorizontal == value) return;
|
||||
isHorizontal = value;
|
||||
UpdateRect();
|
||||
}*/
|
||||
}
|
||||
|
||||
public override bool Enabled
|
||||
{
|
||||
get { return enabled; }
|
||||
set
|
||||
{
|
||||
enabled = value;
|
||||
Bar.Enabled = value;
|
||||
Children.ForEach(c => c.Enabled = value);
|
||||
if (!enabled)
|
||||
{
|
||||
Bar.Selected = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Vector4 Padding
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Frame?.Style == null) return Vector4.Zero;
|
||||
return Frame.Style.Padding;
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2 range;
|
||||
public Vector2 Range
|
||||
{
|
||||
get
|
||||
{
|
||||
return range;
|
||||
}
|
||||
set
|
||||
{
|
||||
float oldBarScrollValue = BarScrollValue;
|
||||
range = value;
|
||||
BarScrollValue = oldBarScrollValue;
|
||||
}
|
||||
}
|
||||
|
||||
public delegate float ScrollConversion(GUIScrollBar scrollBar, float f);
|
||||
public ScrollConversion ScrollToValue = null;
|
||||
public ScrollConversion ValueToScroll = null;
|
||||
|
||||
public float BarScrollValue
|
||||
{
|
||||
get
|
||||
{
|
||||
if (ScrollToValue == null) return (BarScroll * (Range.Y - Range.X)) + Range.X;
|
||||
return ScrollToValue(this, BarScroll);
|
||||
}
|
||||
set
|
||||
{
|
||||
if (ValueToScroll == null) BarScroll = (value - Range.X) / (Range.Y - Range.X);
|
||||
else BarScroll = ValueToScroll(this, value);
|
||||
}
|
||||
}
|
||||
|
||||
public float BarScroll
|
||||
{
|
||||
get { return step == 0.0f ? barScroll : MathUtils.RoundTowardsClosest(barScroll, step); }
|
||||
set
|
||||
{
|
||||
if (float.IsNaN(value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
barScroll = MathHelper.Clamp(value, minValue, maxValue);
|
||||
int newX = Bar.RectTransform.AbsoluteOffset.X;
|
||||
int newY = Bar.RectTransform.AbsoluteOffset.Y;
|
||||
float newScroll = step == 0.0f ? barScroll : MathUtils.RoundTowardsClosest(barScroll, step);
|
||||
if (isHorizontal)
|
||||
{
|
||||
newX = (int)(Padding.X + newScroll * (Frame.Rect.Width - Bar.Rect.Width - Padding.X - Padding.Z));
|
||||
newX = MathHelper.Clamp(newX, (int)Padding.X, Frame.Rect.Width - Bar.Rect.Width - (int)Padding.Z);
|
||||
}
|
||||
else
|
||||
{
|
||||
newY = (int)(Padding.Y + newScroll * (Frame.Rect.Height - Bar.Rect.Height - Padding.Y - Padding.W));
|
||||
newY = MathHelper.Clamp(newY, (int)Padding.Y, Frame.Rect.Height - Bar.Rect.Height - (int)Padding.W);
|
||||
}
|
||||
|
||||
Bar.RectTransform.AbsoluteOffset = new Point(newX, newY);
|
||||
}
|
||||
}
|
||||
|
||||
public float Step
|
||||
{
|
||||
get
|
||||
{
|
||||
return step;
|
||||
}
|
||||
set
|
||||
{
|
||||
step = MathHelper.Clamp(value, 0.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
public float StepValue
|
||||
{
|
||||
get
|
||||
{
|
||||
return step * (Range.Y - Range.X);
|
||||
}
|
||||
set
|
||||
{
|
||||
Step = value / (Range.Y - Range.X);
|
||||
}
|
||||
}
|
||||
|
||||
public float BarSize
|
||||
{
|
||||
get { return barSize; }
|
||||
set
|
||||
{
|
||||
barSize = Math.Min(Math.Max(value, 0.0f), 1.0f);
|
||||
UpdateRect();
|
||||
}
|
||||
}
|
||||
|
||||
public GUIScrollBar(RectTransform rectT, float barSize = 1, Color? color = null, string style = "", bool? isHorizontal = null) : base(style, rectT)
|
||||
{
|
||||
CanBeFocused = true;
|
||||
this.isHorizontal = isHorizontal ?? (Rect.Width > Rect.Height);
|
||||
Frame = new GUIFrame(new RectTransform(Vector2.One, rectT));
|
||||
GUI.Style.Apply(Frame, IsHorizontal ? "GUIFrameHorizontal" : "GUIFrameVertical", this);
|
||||
this.barSize = barSize;
|
||||
|
||||
Bar = new GUIButton(new RectTransform(Vector2.One, rectT, IsHorizontal ? Anchor.CenterLeft : Anchor.TopCenter), color: color, style: null);
|
||||
|
||||
switch (style)
|
||||
{
|
||||
case "":
|
||||
HoverCursor = CursorState.Default;
|
||||
Bar.HoverCursor = CursorState.Default;
|
||||
break;
|
||||
case "GUISlider":
|
||||
HoverCursor = CursorState.Default;
|
||||
Bar.HoverCursor = CursorState.Hand;
|
||||
break;
|
||||
default:
|
||||
HoverCursor = CursorState.Hand;
|
||||
Bar.HoverCursor = CursorState.Hand;
|
||||
break;
|
||||
}
|
||||
|
||||
GUI.Style.Apply(Bar, IsHorizontal ? "GUIButtonHorizontal" : "GUIButtonVertical", this);
|
||||
Bar.OnPressed = SelectBar;
|
||||
enabled = true;
|
||||
UpdateRect();
|
||||
BarScroll = 0.0f;
|
||||
|
||||
rectT.SizeChanged += UpdateRect;
|
||||
rectT.ScaleChanged += UpdateRect;
|
||||
Bar.RectTransform.SizeChanged += () => { BarScroll = barScroll; };
|
||||
}
|
||||
|
||||
private void UpdateRect()
|
||||
{
|
||||
Vector4 padding = Frame.Style.Padding;
|
||||
var newSize = new Point((int)(Rect.Size.X - padding.X - padding.Z), (int)(Rect.Size.Y - padding.Y - padding.W));
|
||||
newSize = IsHorizontal ? newSize.Multiply(new Vector2(BarSize, 1)) : newSize.Multiply(new Vector2(1, BarSize));
|
||||
Bar.RectTransform.Resize(newSize);
|
||||
BarScroll = barScroll;
|
||||
}
|
||||
|
||||
protected override void Update(float deltaTime)
|
||||
{
|
||||
if (!Visible) { return; }
|
||||
|
||||
if (!enabled) { return; }
|
||||
|
||||
Frame.State = GUI.MouseOn == Frame ? ComponentState.Hover : ComponentState.None;
|
||||
if (Frame.State == ComponentState.Hover && PlayerInput.PrimaryMouseButtonHeld())
|
||||
{
|
||||
Frame.State = ComponentState.Pressed;
|
||||
}
|
||||
|
||||
if (IsBooleanSwitch &&
|
||||
(!PlayerInput.PrimaryMouseButtonHeld() || (GUI.MouseOn != this && !IsParentOf(GUI.MouseOn))))
|
||||
{
|
||||
int dir = Math.Sign(barScroll - (minValue + maxValue) / 2.0f);
|
||||
if (dir == 0) dir = 1;
|
||||
if ((barScroll <= maxValue && dir > 0) ||
|
||||
(barScroll > minValue && dir < 0))
|
||||
{
|
||||
BarScroll += dir * 0.1f;
|
||||
}
|
||||
}
|
||||
|
||||
if (DraggingBar == this)
|
||||
{
|
||||
GUI.ForceMouseOn(this);
|
||||
if (dragStartPos == null) { dragStartPos = PlayerInput.MousePosition; }
|
||||
|
||||
if (!PlayerInput.PrimaryMouseButtonHeld())
|
||||
{
|
||||
if (IsBooleanSwitch && GUI.MouseOn == Bar && Vector2.Distance(dragStartPos.Value, PlayerInput.MousePosition) < 5)
|
||||
{
|
||||
BarScroll = BarScroll > 0.5f ? 0.0f : 1.0f;
|
||||
OnMoved?.Invoke(this, BarScroll);
|
||||
}
|
||||
OnReleased?.Invoke(this, BarScroll);
|
||||
DraggingBar = null;
|
||||
dragStartPos = null;
|
||||
|
||||
}
|
||||
if ((isHorizontal && PlayerInput.MousePosition.X > Rect.X && PlayerInput.MousePosition.X < Rect.Right) ||
|
||||
(!isHorizontal && PlayerInput.MousePosition.Y > Rect.Y && PlayerInput.MousePosition.Y < Rect.Bottom))
|
||||
{
|
||||
MoveButton(PlayerInput.MouseSpeed);
|
||||
}
|
||||
}
|
||||
else if (GUI.MouseOn == Frame)
|
||||
{
|
||||
if (PlayerInput.PrimaryMouseButtonClicked())
|
||||
{
|
||||
DraggingBar?.OnReleased?.Invoke(DraggingBar, DraggingBar.BarScroll);
|
||||
if (IsBooleanSwitch)
|
||||
{
|
||||
MoveButton(new Vector2(
|
||||
Math.Sign(PlayerInput.MousePosition.X - Bar.Rect.Center.X) * Rect.Width,
|
||||
Math.Sign(PlayerInput.MousePosition.Y - Bar.Rect.Center.Y) * Rect.Height));
|
||||
}
|
||||
else
|
||||
{
|
||||
MoveButton(new Vector2(
|
||||
Math.Sign(PlayerInput.MousePosition.X - Bar.Rect.Center.X) * Bar.Rect.Width,
|
||||
Math.Sign(PlayerInput.MousePosition.Y - Bar.Rect.Center.Y) * Bar.Rect.Height));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool SelectBar()
|
||||
{
|
||||
if (!enabled || !PlayerInput.PrimaryMouseButtonDown()) { return false; }
|
||||
if (barSize >= 1.0f) { return false; }
|
||||
|
||||
DraggingBar = this;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void MoveButton(Vector2 moveAmount)
|
||||
{
|
||||
float newScroll = barScroll;
|
||||
if (isHorizontal)
|
||||
{
|
||||
moveAmount.Y = 0.0f;
|
||||
newScroll += moveAmount.X / (Frame.Rect.Width - Bar.Rect.Width - Padding.X - Padding.Z);
|
||||
}
|
||||
else
|
||||
{
|
||||
moveAmount.X = 0.0f;
|
||||
newScroll += moveAmount.Y / (Frame.Rect.Height - Bar.Rect.Height - Padding.Y - Padding.W);
|
||||
}
|
||||
|
||||
BarScroll = newScroll;
|
||||
|
||||
if (moveAmount != Vector2.Zero && OnMoved != null) { OnMoved(this, BarScroll); }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,431 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class GUIStyle
|
||||
{
|
||||
private Dictionary<string, GUIComponentStyle> componentStyles;
|
||||
|
||||
private XElement configElement;
|
||||
|
||||
private GraphicsDevice graphicsDevice;
|
||||
|
||||
private ScalableFont defaultFont;
|
||||
|
||||
public ScalableFont Font { get; private set; }
|
||||
public ScalableFont GlobalFont { get; private set; }
|
||||
public ScalableFont UnscaledSmallFont { get; private set; }
|
||||
public ScalableFont SmallFont { get; private set; }
|
||||
public ScalableFont LargeFont { get; private set; }
|
||||
public ScalableFont SubHeadingFont { get; private set; }
|
||||
public ScalableFont DigitalFont { get; private set; }
|
||||
public ScalableFont HotkeyFont { get; private set; }
|
||||
|
||||
public Dictionary<ScalableFont, bool> ForceFontUpperCase
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = new Dictionary<ScalableFont, bool>();
|
||||
|
||||
public readonly Sprite[] CursorSprite = new Sprite[7];
|
||||
|
||||
public UISprite UIGlow { get; private set; }
|
||||
public UISprite UIGlowCircular { get; private set; }
|
||||
|
||||
public SpriteSheet FocusIndicator { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// General green color used for elements whose colors are set from code
|
||||
/// </summary>
|
||||
public Color Green { get; private set; } = Color.LightGreen;
|
||||
|
||||
/// <summary>
|
||||
/// General red color used for elements whose colors are set from code
|
||||
/// </summary>
|
||||
public Color Orange { get; private set; } = Color.Orange;
|
||||
|
||||
/// <summary>
|
||||
/// General red color used for elements whose colors are set from code
|
||||
/// </summary>
|
||||
public Color Red { get; private set; } = Color.Red;
|
||||
|
||||
/// <summary>
|
||||
/// General blue color used for elements whose colors are set from code
|
||||
/// </summary>
|
||||
public Color Blue { get; private set; } = Color.Blue;
|
||||
|
||||
public Color ColorInventoryEmpty { get; private set; } = Color.Red;
|
||||
public Color ColorInventoryHalf { get; private set; } = Color.Orange;
|
||||
public Color ColorInventoryFull { get; private set; } = Color.LightGreen;
|
||||
public Color ColorInventoryBackground { get; private set; } = Color.Gray;
|
||||
|
||||
public Color TextColor { get; private set; } = Color.White * 0.8f;
|
||||
public Color TextColorBright { get; private set; } = Color.White * 0.9f;
|
||||
public Color TextColorDark { get; private set; } = Color.Black * 0.9f;
|
||||
public Color TextColorDim { get; private set; } = Color.White * 0.6f;
|
||||
|
||||
// Inventory
|
||||
public Color EquipmentSlotIconColor { get; private set; } = new Color(99, 70, 64);
|
||||
|
||||
// Health HUD
|
||||
public Color BuffColorLow { get; private set; } = Color.LightGreen;
|
||||
public Color BuffColorMedium { get; private set; } = Color.Green;
|
||||
public Color BuffColorHigh { get; private set; } = Color.DarkGreen;
|
||||
|
||||
public Color DebuffColorLow { get; private set; } = Color.DarkSalmon;
|
||||
public Color DebuffColorMedium { get; private set; } = Color.Red;
|
||||
public Color DebuffColorHigh { get; private set; } = Color.DarkRed;
|
||||
|
||||
public Color HealthBarColorLow { get; private set; } = Color.Red;
|
||||
public Color HealthBarColorMedium { get; private set; } = Color.Orange;
|
||||
public Color HealthBarColorHigh { get; private set; } = new Color(78, 114, 88);
|
||||
|
||||
public Color EquipmentIndicatorNotEquipped { get; private set; } = Color.Gray;
|
||||
public Color EquipmentIndicatorEquipped { get; private set; } = new Color(105, 202, 125);
|
||||
public Color EquipmentIndicatorRunningOut { get; private set; } = new Color(202, 105, 105);
|
||||
|
||||
public static Point ItemFrameMargin => new Point(50, 56).Multiply(GUI.SlicedSpriteScale);
|
||||
public static Point ItemFrameOffset => new Point(0, 3).Multiply(GUI.SlicedSpriteScale);
|
||||
|
||||
public GUIStyle(XElement element, GraphicsDevice graphicsDevice)
|
||||
{
|
||||
this.graphicsDevice = graphicsDevice;
|
||||
componentStyles = new Dictionary<string, GUIComponentStyle>();
|
||||
configElement = element;
|
||||
foreach (XElement subElement in configElement.Elements())
|
||||
{
|
||||
var name = subElement.Name.ToString().ToLowerInvariant();
|
||||
switch (name)
|
||||
{
|
||||
case "cursor":
|
||||
if (subElement.HasElements)
|
||||
{
|
||||
foreach (var children in subElement.Descendants())
|
||||
{
|
||||
var index = children.GetAttributeInt("state", (int)CursorState.Default);
|
||||
CursorSprite[index] = new Sprite(children);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CursorSprite[(int)CursorState.Default] = new Sprite(subElement);
|
||||
}
|
||||
break;
|
||||
case "green":
|
||||
Green = subElement.GetAttributeColor("color", Green);
|
||||
break;
|
||||
case "orange":
|
||||
Orange = subElement.GetAttributeColor("color", Orange);
|
||||
break;
|
||||
case "red":
|
||||
Red = subElement.GetAttributeColor("color", Red);
|
||||
break;
|
||||
case "blue":
|
||||
Blue = subElement.GetAttributeColor("color", Blue);
|
||||
break;
|
||||
case "colorinventoryempty":
|
||||
ColorInventoryEmpty = subElement.GetAttributeColor("color", ColorInventoryEmpty);
|
||||
break;
|
||||
case "colorinventoryhalf":
|
||||
ColorInventoryHalf = subElement.GetAttributeColor("color", ColorInventoryHalf);
|
||||
break;
|
||||
case "colorinventoryfull":
|
||||
ColorInventoryFull = subElement.GetAttributeColor("color", ColorInventoryFull);
|
||||
break;
|
||||
case "colorinventorybackground":
|
||||
ColorInventoryBackground = subElement.GetAttributeColor("color", ColorInventoryBackground);
|
||||
break;
|
||||
case "textcolordark":
|
||||
TextColorDark = subElement.GetAttributeColor("color", TextColorDark);
|
||||
break;
|
||||
case "textcolorbright":
|
||||
TextColorBright = subElement.GetAttributeColor("color", TextColorBright);
|
||||
break;
|
||||
case "textcolordim":
|
||||
TextColorDim = subElement.GetAttributeColor("color", TextColorDim);
|
||||
break;
|
||||
case "textcolornormal":
|
||||
case "textcolor":
|
||||
TextColor = subElement.GetAttributeColor("color", TextColor);
|
||||
break;
|
||||
case "equipmentsloticoncolor":
|
||||
EquipmentSlotIconColor = subElement.GetAttributeColor("color", EquipmentSlotIconColor);
|
||||
break;
|
||||
case "buffcolorlow":
|
||||
BuffColorLow = subElement.GetAttributeColor("color", BuffColorLow);
|
||||
break;
|
||||
case "buffcolormedium":
|
||||
BuffColorMedium = subElement.GetAttributeColor("color", BuffColorMedium);
|
||||
break;
|
||||
case "buffcolorhigh":
|
||||
BuffColorHigh = subElement.GetAttributeColor("color", BuffColorHigh);
|
||||
break;
|
||||
case "debuffcolorlow":
|
||||
DebuffColorLow = subElement.GetAttributeColor("color", DebuffColorLow);
|
||||
break;
|
||||
case "debuffcolormedium":
|
||||
DebuffColorMedium = subElement.GetAttributeColor("color", DebuffColorMedium);
|
||||
break;
|
||||
case "debuffcolorhigh":
|
||||
DebuffColorHigh = subElement.GetAttributeColor("color", DebuffColorHigh);
|
||||
break;
|
||||
case "healthbarcolorlow":
|
||||
HealthBarColorLow = subElement.GetAttributeColor("color", HealthBarColorLow);
|
||||
break;
|
||||
case "healthbarcolormedium":
|
||||
HealthBarColorMedium = subElement.GetAttributeColor("color", HealthBarColorMedium);
|
||||
break;
|
||||
case "healthbarcolorhigh":
|
||||
HealthBarColorHigh = subElement.GetAttributeColor("color", HealthBarColorHigh);
|
||||
break;
|
||||
case "equipmentindicatornotequipped":
|
||||
EquipmentIndicatorNotEquipped = subElement.GetAttributeColor("color", EquipmentIndicatorNotEquipped);
|
||||
break;
|
||||
case "equipmentindicatorequipped":
|
||||
EquipmentIndicatorEquipped = subElement.GetAttributeColor("color", EquipmentIndicatorEquipped);
|
||||
break;
|
||||
case "equipmentindicatorrunningout":
|
||||
EquipmentIndicatorRunningOut = subElement.GetAttributeColor("color", EquipmentIndicatorRunningOut);
|
||||
break;
|
||||
case "uiglow":
|
||||
UIGlow = new UISprite(subElement);
|
||||
break;
|
||||
case "uiglowcircular":
|
||||
UIGlowCircular = new UISprite(subElement);
|
||||
break;
|
||||
case "focusindicator":
|
||||
FocusIndicator = new SpriteSheet(subElement);
|
||||
break;
|
||||
case "font":
|
||||
Font = LoadFont(subElement, graphicsDevice);
|
||||
ForceFontUpperCase[Font] = subElement.GetAttributeBool("forceuppercase", false);
|
||||
break;
|
||||
case "globalfont":
|
||||
GlobalFont = LoadFont(subElement, graphicsDevice);
|
||||
ForceFontUpperCase[GlobalFont] = subElement.GetAttributeBool("forceuppercase", false);
|
||||
break;
|
||||
case "unscaledsmallfont":
|
||||
UnscaledSmallFont = LoadFont(subElement, graphicsDevice);
|
||||
ForceFontUpperCase[UnscaledSmallFont] = subElement.GetAttributeBool("forceuppercase", false);
|
||||
break;
|
||||
case "smallfont":
|
||||
SmallFont = LoadFont(subElement, graphicsDevice);
|
||||
ForceFontUpperCase[SmallFont] = subElement.GetAttributeBool("forceuppercase", false);
|
||||
break;
|
||||
case "largefont":
|
||||
LargeFont = LoadFont(subElement, graphicsDevice);
|
||||
ForceFontUpperCase[LargeFont] = subElement.GetAttributeBool("forceuppercase", false);
|
||||
break;
|
||||
case "digitalfont":
|
||||
DigitalFont = LoadFont(subElement, graphicsDevice);
|
||||
ForceFontUpperCase[DigitalFont] = subElement.GetAttributeBool("forceuppercase", false);
|
||||
break;
|
||||
case "hotkeyfont":
|
||||
HotkeyFont = LoadFont(subElement, graphicsDevice);
|
||||
ForceFontUpperCase[HotkeyFont] = subElement.GetAttributeBool("forceuppercase", false);
|
||||
break;
|
||||
case "objectivetitle":
|
||||
case "subheading":
|
||||
SubHeadingFont = LoadFont(subElement, graphicsDevice);
|
||||
ForceFontUpperCase[SubHeadingFont] = subElement.GetAttributeBool("forceuppercase", false);
|
||||
break;
|
||||
default:
|
||||
GUIComponentStyle componentStyle = new GUIComponentStyle(subElement, this);
|
||||
componentStyles.Add(subElement.Name.ToString().ToLowerInvariant(), componentStyle);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (GlobalFont == null)
|
||||
{
|
||||
GlobalFont = Font;
|
||||
DebugConsole.NewMessage("Global font not defined in the current UI style file. The global font is used to render western symbols when using Chinese/Japanese/Korean localization. Using default font instead...", Color.Orange);
|
||||
}
|
||||
|
||||
GameMain.Instance.OnResolutionChanged += () => { RescaleElements(); };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the default font of the currently selected language
|
||||
/// </summary>
|
||||
public ScalableFont LoadCurrentDefaultFont()
|
||||
{
|
||||
defaultFont?.Dispose();
|
||||
defaultFont = null;
|
||||
foreach (XElement subElement in configElement.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "font":
|
||||
defaultFont = LoadFont(subElement, graphicsDevice);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return defaultFont;
|
||||
}
|
||||
|
||||
|
||||
private void RescaleElements()
|
||||
{
|
||||
if (configElement == null) { return; }
|
||||
if (configElement.Elements() == null) { return; }
|
||||
foreach (XElement subElement in configElement.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "font":
|
||||
if (Font == null) { continue; }
|
||||
Font.Size = GetFontSize(subElement);
|
||||
break;
|
||||
case "smallfont":
|
||||
if (SmallFont == null) { continue; }
|
||||
SmallFont.Size = GetFontSize(subElement);
|
||||
break;
|
||||
case "largefont":
|
||||
if (LargeFont == null) { continue; }
|
||||
LargeFont.Size = GetFontSize(subElement);
|
||||
break;
|
||||
case "hotkeyfont":
|
||||
if (HotkeyFont == null) { continue; }
|
||||
HotkeyFont.Size = GetFontSize(subElement);
|
||||
break;
|
||||
case "objectivetitle":
|
||||
case "subheading":
|
||||
if (SubHeadingFont == null) { continue; }
|
||||
SubHeadingFont.Size = GetFontSize(subElement);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var componentStyle in componentStyles.Values)
|
||||
{
|
||||
componentStyle.GetSize(componentStyle.Element);
|
||||
foreach (var childStyle in componentStyle.ChildStyles.Values)
|
||||
{
|
||||
childStyle.GetSize(childStyle.Element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ScalableFont LoadFont(XElement element, GraphicsDevice graphicsDevice)
|
||||
{
|
||||
string file = GetFontFilePath(element);
|
||||
uint size = GetFontSize(element);
|
||||
bool dynamicLoading = GetFontDynamicLoading(element);
|
||||
bool isCJK = GetIsCJK(element);
|
||||
return new ScalableFont(file, size, graphicsDevice, dynamicLoading, isCJK);
|
||||
}
|
||||
|
||||
private uint GetFontSize(XElement element, uint defaultSize = 14)
|
||||
{
|
||||
//check if any of the language override fonts want to override the font size as well
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
if (!subElement.Name.ToString().Equals("override", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
if (GameMain.Config.Language.Equals(subElement.GetAttributeString("language", ""), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
uint overrideFontSize = GetFontSize(subElement, 0);
|
||||
if (overrideFontSize > 0) { return overrideFontSize; }
|
||||
}
|
||||
}
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
if (!subElement.Name.ToString().Equals("size", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
Point maxResolution = subElement.GetAttributePoint("maxresolution", new Point(int.MaxValue, int.MaxValue));
|
||||
if (GameMain.GraphicsWidth <= maxResolution.X && GameMain.GraphicsHeight <= maxResolution.Y)
|
||||
{
|
||||
return (uint)subElement.GetAttributeInt("size", 14);
|
||||
}
|
||||
}
|
||||
return defaultSize;
|
||||
}
|
||||
|
||||
private string GetFontFilePath(XElement element)
|
||||
{
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
if (!subElement.Name.ToString().Equals("override", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
if (GameMain.Config.Language.Equals(subElement.GetAttributeString("language", ""), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return subElement.GetAttributeString("file", "");
|
||||
}
|
||||
}
|
||||
return element.GetAttributeString("file", "");
|
||||
}
|
||||
|
||||
private bool GetFontDynamicLoading(XElement element)
|
||||
{
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
if (!subElement.Name.ToString().Equals("override", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
if (GameMain.Config.Language.Equals(subElement.GetAttributeString("language", ""), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return subElement.GetAttributeBool("dynamicloading", false);
|
||||
}
|
||||
}
|
||||
return element.GetAttributeBool("dynamicloading", false);
|
||||
}
|
||||
|
||||
private bool GetIsCJK(XElement element)
|
||||
{
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
if (!subElement.Name.ToString().Equals("override", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
if (GameMain.Config.Language.Equals(subElement.GetAttributeString("language", ""), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return subElement.GetAttributeBool("iscjk", false);
|
||||
}
|
||||
}
|
||||
return element.GetAttributeBool("iscjk", false);
|
||||
}
|
||||
|
||||
public GUIComponentStyle GetComponentStyle(string name)
|
||||
{
|
||||
componentStyles.TryGetValue(name.ToLowerInvariant(), out GUIComponentStyle style);
|
||||
return style;
|
||||
}
|
||||
|
||||
public void Apply(GUIComponent targetComponent, string styleName = "", GUIComponent parent = null)
|
||||
{
|
||||
GUIComponentStyle componentStyle = null;
|
||||
if (parent != null)
|
||||
{
|
||||
GUIComponentStyle parentStyle = parent.Style;
|
||||
|
||||
if (parent.Style == null)
|
||||
{
|
||||
string parentStyleName = parent.GetType().Name.ToLowerInvariant();
|
||||
|
||||
if (!componentStyles.TryGetValue(parentStyleName, out parentStyle))
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't find a GUI style \""+ parentStyleName + "\"");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
string childStyleName = string.IsNullOrEmpty(styleName) ? targetComponent.GetType().Name : styleName;
|
||||
parentStyle.ChildStyles.TryGetValue(childStyleName.ToLowerInvariant(), out componentStyle);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (string.IsNullOrEmpty(styleName))
|
||||
{
|
||||
styleName = targetComponent.GetType().Name;
|
||||
}
|
||||
if (!componentStyles.TryGetValue(styleName.ToLowerInvariant(), out componentStyle))
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't find a GUI style \""+ styleName+"\"");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
targetComponent.ApplyStyle(componentStyle);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,535 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class GUITextBlock : GUIComponent
|
||||
{
|
||||
protected string text;
|
||||
|
||||
protected Alignment textAlignment;
|
||||
|
||||
private float textScale = 1;
|
||||
|
||||
protected Vector2 textPos;
|
||||
protected Vector2 origin;
|
||||
|
||||
protected Color textColor, disabledTextColor, selectedTextColor;
|
||||
|
||||
private string wrappedText;
|
||||
private string censoredText;
|
||||
|
||||
public delegate string TextGetterHandler();
|
||||
public TextGetterHandler TextGetter;
|
||||
|
||||
public bool Wrap;
|
||||
private bool playerInput;
|
||||
|
||||
public bool RoundToNearestPixel = true;
|
||||
|
||||
private bool overflowClipActive;
|
||||
public bool OverflowClip;
|
||||
|
||||
public bool OverflowClipActive
|
||||
{
|
||||
get { return overflowClipActive; }
|
||||
}
|
||||
|
||||
private float textDepth;
|
||||
|
||||
private ScalableFont originalFont;
|
||||
|
||||
public Vector2 TextOffset { get; set; }
|
||||
|
||||
private Vector4 padding;
|
||||
public Vector4 Padding
|
||||
{
|
||||
get { return padding; }
|
||||
set
|
||||
{
|
||||
padding = value;
|
||||
SetTextPos();
|
||||
}
|
||||
}
|
||||
|
||||
public override ScalableFont Font
|
||||
{
|
||||
get
|
||||
{
|
||||
return base.Font;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (base.Font == value) { return; }
|
||||
base.Font = originalFont = value;
|
||||
if (text != null && GUI.Style.ForceFontUpperCase.ContainsKey(Font) && GUI.Style.ForceFontUpperCase[Font])
|
||||
{
|
||||
Text = text.ToUpper();
|
||||
}
|
||||
SetTextPos();
|
||||
}
|
||||
}
|
||||
|
||||
public string Text
|
||||
{
|
||||
get { return text; }
|
||||
set
|
||||
{
|
||||
string newText = forceUpperCase || (GUI.Style.ForceFontUpperCase.ContainsKey(Font) && GUI.Style.ForceFontUpperCase[Font]) || (style != null && style.ForceUpperCase) ?
|
||||
value?.ToUpper() :
|
||||
value;
|
||||
|
||||
if (Text == newText) { return; }
|
||||
|
||||
//reset scale, it gets recalculated in SetTextPos
|
||||
if (autoScaleHorizontal || autoScaleVertical) { textScale = 1.0f; }
|
||||
|
||||
text = newText;
|
||||
wrappedText = newText;
|
||||
if (TextManager.IsCJK(text))
|
||||
{
|
||||
//switch to fallback CJK font
|
||||
if (!Font.IsCJK) { base.Font = GUI.CJKFont; }
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Font == GUI.CJKFont) { base.Font = originalFont; }
|
||||
}
|
||||
SetTextPos();
|
||||
}
|
||||
}
|
||||
|
||||
public string WrappedText
|
||||
{
|
||||
get { return wrappedText; }
|
||||
}
|
||||
|
||||
public float TextDepth
|
||||
{
|
||||
get { return textDepth; }
|
||||
set { textDepth = MathHelper.Clamp(value, 0.0f, 1.0f); }
|
||||
}
|
||||
|
||||
public Vector2 TextPos
|
||||
{
|
||||
get { return textPos; }
|
||||
set { textPos = value; }
|
||||
}
|
||||
|
||||
public float TextScale
|
||||
{
|
||||
get { return textScale; }
|
||||
set
|
||||
{
|
||||
if (value != textScale)
|
||||
{
|
||||
textScale = value;
|
||||
SetTextPos();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool autoScaleHorizontal, autoScaleVertical;
|
||||
|
||||
/// <summary>
|
||||
/// When enabled, the text is automatically scaled down to fit the textblock horizontally.
|
||||
/// </summary>
|
||||
public bool AutoScaleHorizontal
|
||||
{
|
||||
get { return autoScaleHorizontal; }
|
||||
set
|
||||
{
|
||||
if (autoScaleHorizontal == value) { return; }
|
||||
autoScaleHorizontal = value;
|
||||
if (autoScaleHorizontal)
|
||||
{
|
||||
SetTextPos();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When enabled, the text is automatically scaled down to fit the textblock vertically.
|
||||
/// </summary>
|
||||
public bool AutoScaleVertical
|
||||
{
|
||||
get { return autoScaleVertical; }
|
||||
set
|
||||
{
|
||||
if (autoScaleVertical == value) { return; }
|
||||
autoScaleVertical = value;
|
||||
if (autoScaleVertical)
|
||||
{
|
||||
SetTextPos();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool forceUpperCase;
|
||||
public bool ForceUpperCase
|
||||
{
|
||||
get { return forceUpperCase; }
|
||||
set
|
||||
{
|
||||
if (forceUpperCase == value) { return; }
|
||||
|
||||
forceUpperCase = value;
|
||||
if (forceUpperCase ||
|
||||
(style != null && style.ForceUpperCase) ||
|
||||
(GUI.Style.ForceFontUpperCase.ContainsKey(Font) && GUI.Style.ForceFontUpperCase[Font]))
|
||||
{
|
||||
Text = text?.ToUpper();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2 Origin
|
||||
{
|
||||
get { return origin; }
|
||||
}
|
||||
|
||||
public Vector2 TextSize
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public Color TextColor
|
||||
{
|
||||
get { return textColor; }
|
||||
set { textColor = value; }
|
||||
}
|
||||
|
||||
private Color? hoverTextColor;
|
||||
public Color HoverTextColor
|
||||
{
|
||||
get { return hoverTextColor ?? textColor; }
|
||||
set { hoverTextColor = value; }
|
||||
}
|
||||
|
||||
public Color SelectedTextColor
|
||||
{
|
||||
get { return selectedTextColor; }
|
||||
set { selectedTextColor = value; }
|
||||
}
|
||||
|
||||
public Alignment TextAlignment
|
||||
{
|
||||
get { return textAlignment; }
|
||||
set
|
||||
{
|
||||
if (textAlignment == value) return;
|
||||
textAlignment = value;
|
||||
SetTextPos();
|
||||
}
|
||||
}
|
||||
|
||||
public bool Censor
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public string CensoredText
|
||||
{
|
||||
get { return censoredText; }
|
||||
}
|
||||
|
||||
private List<ColorData> colorData = null;
|
||||
private bool hasColorHighlight = false;
|
||||
|
||||
/// <summary>
|
||||
/// This is the new constructor.
|
||||
/// If the rectT height is set 0, the height is calculated from the text.
|
||||
/// </summary>
|
||||
public GUITextBlock(RectTransform rectT, string text, Color? textColor = null, ScalableFont font = null,
|
||||
Alignment textAlignment = Alignment.Left, bool wrap = false, string style = "", Color? color = null, bool playerInput = false)
|
||||
: base(style, rectT)
|
||||
{
|
||||
if (color.HasValue)
|
||||
{
|
||||
this.color = color.Value;
|
||||
}
|
||||
if (textColor.HasValue)
|
||||
{
|
||||
OverrideTextColor(textColor.Value);
|
||||
}
|
||||
|
||||
//if the text is in chinese/korean/japanese and we're not using a CJK-compatible font,
|
||||
//use the default CJK font as a fallback
|
||||
var selectedFont = originalFont = font ?? GUI.Font;
|
||||
if (TextManager.IsCJK(text) && !selectedFont.IsCJK)
|
||||
{
|
||||
selectedFont = GUI.CJKFont;
|
||||
}
|
||||
this.Font = selectedFont;
|
||||
this.textAlignment = textAlignment;
|
||||
this.Wrap = wrap;
|
||||
this.Text = text ?? "";
|
||||
this.playerInput = playerInput;
|
||||
if (rectT.Rect.Height == 0 && !string.IsNullOrEmpty(text))
|
||||
{
|
||||
CalculateHeightFromText();
|
||||
}
|
||||
SetTextPos();
|
||||
|
||||
RectTransform.ScaleChanged += SetTextPos;
|
||||
RectTransform.SizeChanged += SetTextPos;
|
||||
|
||||
Enabled = true;
|
||||
Censor = false;
|
||||
}
|
||||
public GUITextBlock(RectTransform rectT, List<ColorData> colorData, string text, Color? textColor = null, ScalableFont font = null, Alignment textAlignment = Alignment.Left, bool wrap = false, string style = "", Color? color = null, bool playerInput = false)
|
||||
: this(rectT, text, textColor, font, textAlignment, wrap, style, color, playerInput)
|
||||
{
|
||||
this.colorData = colorData;
|
||||
hasColorHighlight = colorData != null;
|
||||
}
|
||||
|
||||
public void CalculateHeightFromText(int padding = 0)
|
||||
{
|
||||
if (wrappedText == null) { return; }
|
||||
RectTransform.Resize(new Point(RectTransform.Rect.Width, (int)Font.MeasureString(wrappedText).Y + padding));
|
||||
}
|
||||
|
||||
public override void ApplyStyle(GUIComponentStyle componentStyle)
|
||||
{
|
||||
if (componentStyle == null) { return; }
|
||||
base.ApplyStyle(componentStyle);
|
||||
padding = componentStyle.Padding;
|
||||
|
||||
textColor = componentStyle.TextColor;
|
||||
hoverTextColor = componentStyle.HoverTextColor;
|
||||
disabledTextColor = componentStyle.DisabledTextColor;
|
||||
selectedTextColor = componentStyle.SelectedTextColor;
|
||||
|
||||
switch (componentStyle.Font)
|
||||
{
|
||||
case "font":
|
||||
Font = componentStyle.Style.Font;
|
||||
break;
|
||||
case "smallfont":
|
||||
Font = componentStyle.Style.SmallFont;
|
||||
break;
|
||||
case "largefont":
|
||||
Font = componentStyle.Style.LargeFont;
|
||||
break;
|
||||
case "objectivetitle":
|
||||
case "subheading":
|
||||
Font = componentStyle.Style.SubHeadingFont;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetTextPos()
|
||||
{
|
||||
if (text == null) { return; }
|
||||
|
||||
censoredText = string.IsNullOrEmpty(text) ? "" : new string('\u2022', text.Length);
|
||||
|
||||
var rect = Rect;
|
||||
|
||||
overflowClipActive = false;
|
||||
wrappedText = text;
|
||||
|
||||
TextSize = MeasureText(text);
|
||||
|
||||
if (Wrap && rect.Width > 0)
|
||||
{
|
||||
wrappedText = ToolBox.WrapText(text, rect.Width - padding.X - padding.Z, Font, textScale, playerInput);
|
||||
TextSize = MeasureText(wrappedText);
|
||||
}
|
||||
else if (OverflowClip)
|
||||
{
|
||||
overflowClipActive = TextSize.X > rect.Width - padding.X - padding.Z;
|
||||
}
|
||||
|
||||
Vector2 minSize = new Vector2(
|
||||
Math.Max(rect.Width - padding.X - padding.Z, 5.0f),
|
||||
Math.Max(rect.Height - padding.Y - padding.W, 5.0f));
|
||||
if (!autoScaleHorizontal) { minSize.X = float.MaxValue; }
|
||||
if (!Wrap && !autoScaleVertical) { minSize.Y = float.MaxValue; }
|
||||
|
||||
if ((autoScaleHorizontal || autoScaleVertical) && textScale > 0.1f &&
|
||||
(TextSize.X * textScale > minSize.X || TextSize.Y * textScale > minSize.Y))
|
||||
{
|
||||
TextScale = Math.Max(0.1f, Math.Min(minSize.X / TextSize.X, minSize.Y / TextSize.Y)) - 0.01f;
|
||||
return;
|
||||
}
|
||||
|
||||
textPos = new Vector2(padding.X + (rect.Width - padding.Z - padding.X) / 2.0f, padding.Y + (rect.Height - padding.Y - padding.W) / 2.0f);
|
||||
origin = TextSize * 0.5f;
|
||||
|
||||
if (textAlignment.HasFlag(Alignment.Left) && !overflowClipActive)
|
||||
{
|
||||
textPos.X = padding.X;
|
||||
origin.X = 0;
|
||||
}
|
||||
if (textAlignment.HasFlag(Alignment.Right) || overflowClipActive)
|
||||
{
|
||||
textPos.X = rect.Width - padding.Z;
|
||||
origin.X = TextSize.X;
|
||||
}
|
||||
if (textAlignment.HasFlag(Alignment.Top))
|
||||
{
|
||||
textPos.Y = padding.Y;
|
||||
origin.Y = 0;
|
||||
}
|
||||
if (textAlignment.HasFlag(Alignment.Bottom))
|
||||
{
|
||||
textPos.Y = rect.Height - padding.W;
|
||||
origin.Y = TextSize.Y;
|
||||
}
|
||||
|
||||
origin.X = (int)(origin.X);
|
||||
origin.Y = (int)(origin.Y);
|
||||
|
||||
textPos.X = (int)textPos.X;
|
||||
textPos.Y = (int)textPos.Y;
|
||||
}
|
||||
|
||||
private Vector2 MeasureText(string text)
|
||||
{
|
||||
if (Font == null) return Vector2.Zero;
|
||||
|
||||
if (string.IsNullOrEmpty(text))
|
||||
{
|
||||
return Font.MeasureString(" ");
|
||||
}
|
||||
|
||||
Vector2 size = Vector2.Zero;
|
||||
while (size == Vector2.Zero)
|
||||
{
|
||||
try { size = Font.MeasureString(string.IsNullOrEmpty(text) ? " " : text); }
|
||||
catch { text = text.Substring(0, text.Length - 1); }
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
protected override void SetAlpha(float a)
|
||||
{
|
||||
base.SetAlpha(a);
|
||||
textColor = new Color(textColor.R, textColor.G, textColor.B, a);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the color for all the states.
|
||||
/// </summary>
|
||||
public void OverrideTextColor(Color color)
|
||||
{
|
||||
textColor = color;
|
||||
hoverTextColor = color;
|
||||
selectedTextColor = color;
|
||||
disabledTextColor = color;
|
||||
}
|
||||
|
||||
protected override void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
if (!Visible) { return; }
|
||||
|
||||
Color currColor = GetColor(State);
|
||||
|
||||
var rect = Rect;
|
||||
|
||||
base.Draw(spriteBatch);
|
||||
|
||||
if (TextGetter != null) { Text = TextGetter(); }
|
||||
|
||||
Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
|
||||
if (overflowClipActive)
|
||||
{
|
||||
spriteBatch.End();
|
||||
Rectangle scissorRect = new Rectangle(rect.X + (int)padding.X, rect.Y, rect.Width - (int)padding.X - (int)padding.Z, rect.Height);
|
||||
spriteBatch.GraphicsDevice.ScissorRectangle = scissorRect;
|
||||
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(text))
|
||||
{
|
||||
Vector2 pos = rect.Location.ToVector2() + textPos + TextOffset;
|
||||
if (RoundToNearestPixel)
|
||||
{
|
||||
pos.X = (int)pos.X;
|
||||
pos.Y = (int)pos.Y;
|
||||
}
|
||||
|
||||
Color currentTextColor = State == ComponentState.Hover || State == ComponentState.HoverSelected ? HoverTextColor : TextColor;
|
||||
if (!enabled)
|
||||
{
|
||||
currentTextColor = disabledTextColor;
|
||||
}
|
||||
else if (State == ComponentState.Selected)
|
||||
{
|
||||
currentTextColor = selectedTextColor;
|
||||
}
|
||||
|
||||
if (!hasColorHighlight)
|
||||
{
|
||||
Font.DrawString(spriteBatch,
|
||||
Censor ? censoredText : (Wrap ? wrappedText : text),
|
||||
pos,
|
||||
currentTextColor * (currentTextColor.A / 255.0f),
|
||||
0.0f, origin, TextScale,
|
||||
SpriteEffects.None, textDepth);
|
||||
}
|
||||
else
|
||||
{
|
||||
Font.DrawStringWithColors(spriteBatch, Censor ? censoredText : (Wrap ? wrappedText : text), pos,
|
||||
currentTextColor * (currentTextColor.A / 255.0f), 0.0f, origin, TextScale, SpriteEffects.None, textDepth, colorData);
|
||||
}
|
||||
}
|
||||
|
||||
if (overflowClipActive)
|
||||
{
|
||||
spriteBatch.End();
|
||||
spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect;
|
||||
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
|
||||
}
|
||||
|
||||
if (OutlineColor.A * currColor.A > 0.0f) GUI.DrawRectangle(spriteBatch, rect, OutlineColor * (currColor.A / 255.0f), false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the text scale of the GUITextBlocks so that they all use the same scale and can fit the text within the block.
|
||||
/// </summary>
|
||||
public static void AutoScaleAndNormalize(params GUITextBlock[] textBlocks)
|
||||
{
|
||||
AutoScaleAndNormalize(textBlocks.AsEnumerable<GUITextBlock>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the text scale of the GUITextBlocks so that they all use the same scale and can fit the text within the block.
|
||||
/// </summary>
|
||||
public static void AutoScaleAndNormalize(bool scaleHorizontal = true, bool scaleVertical = false, params GUITextBlock[] textBlocks)
|
||||
{
|
||||
AutoScaleAndNormalize(textBlocks.AsEnumerable<GUITextBlock>(), scaleHorizontal, scaleVertical);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the text scale of the GUITextBlocks so that they all use the same scale and can fit the text within the block.
|
||||
/// </summary>
|
||||
public static void AutoScaleAndNormalize(IEnumerable<GUITextBlock> textBlocks, bool scaleHorizontal = true, bool scaleVertical = false, float? defaultScale = null)
|
||||
{
|
||||
if (!textBlocks.Any()) { return; }
|
||||
float minScale = Math.Max(textBlocks.First().TextScale, 1.0f);
|
||||
foreach (GUITextBlock textBlock in textBlocks)
|
||||
{
|
||||
if (defaultScale.HasValue) { textBlock.TextScale = defaultScale.Value; }
|
||||
textBlock.AutoScaleHorizontal = scaleHorizontal;
|
||||
textBlock.AutoScaleVertical = scaleVertical;
|
||||
minScale = Math.Min(textBlock.TextScale, minScale);
|
||||
}
|
||||
|
||||
foreach (GUITextBlock textBlock in textBlocks)
|
||||
{
|
||||
textBlock.AutoScaleHorizontal = false;
|
||||
textBlock.AutoScaleVertical = false;
|
||||
textBlock.TextScale = minScale;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,956 @@
|
||||
using EventInput;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
|
||||
public delegate void TextBoxEvent(GUITextBox sender, Keys key);
|
||||
|
||||
public class GUITextBox : GUIComponent, IKeyboardSubscriber
|
||||
{
|
||||
public event TextBoxEvent OnSelected;
|
||||
public event TextBoxEvent OnDeselected;
|
||||
|
||||
bool caretVisible;
|
||||
float caretTimer;
|
||||
|
||||
private readonly GUIFrame frame;
|
||||
private readonly GUITextBlock textBlock;
|
||||
private readonly GUIImage icon;
|
||||
|
||||
public Func<string, string> textFilterFunction;
|
||||
|
||||
public delegate bool OnEnterHandler(GUITextBox textBox, string text);
|
||||
public OnEnterHandler OnEnterPressed;
|
||||
|
||||
public event TextBoxEvent OnKeyHit;
|
||||
|
||||
public delegate bool OnTextChangedHandler(GUITextBox textBox, string text);
|
||||
/// <summary>
|
||||
/// Don't set the Text property on delegates that register to this event, because modifying the Text will launch this event -> stack overflow.
|
||||
/// If the event launches, the text should already be up to date!
|
||||
/// </summary>
|
||||
public event OnTextChangedHandler OnTextChanged;
|
||||
|
||||
public bool CaretEnabled { get; set; }
|
||||
public Color? CaretColor { get; set; }
|
||||
public bool DeselectAfterMessage = true;
|
||||
|
||||
private int? maxTextLength;
|
||||
|
||||
private int _caretIndex;
|
||||
private int CaretIndex
|
||||
{
|
||||
get { return _caretIndex; }
|
||||
set
|
||||
{
|
||||
_caretIndex = value;
|
||||
caretPosDirty = true;
|
||||
}
|
||||
}
|
||||
private bool caretPosDirty;
|
||||
protected Vector2 caretPos;
|
||||
public Vector2 CaretScreenPos => Rect.Location.ToVector2() + caretPos;
|
||||
|
||||
private bool isSelecting;
|
||||
private string selectedText = string.Empty;
|
||||
private int selectedCharacters;
|
||||
private int selectionStartIndex;
|
||||
private int selectionEndIndex;
|
||||
private bool IsLeftToRight => selectionStartIndex <= selectionEndIndex;
|
||||
private Vector2 selectionStartPos;
|
||||
private Vector2 selectionEndPos;
|
||||
private Vector2 selectionRectSize;
|
||||
|
||||
private readonly Memento<string> memento = new Memento<string>();
|
||||
|
||||
public GUITextBlock.TextGetterHandler TextGetter
|
||||
{
|
||||
get { return textBlock.TextGetter; }
|
||||
set { textBlock.TextGetter = value; }
|
||||
}
|
||||
|
||||
private new bool selected;
|
||||
public new bool Selected
|
||||
{
|
||||
get
|
||||
{
|
||||
return selected;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (!selected && value)
|
||||
{
|
||||
Select();
|
||||
}
|
||||
else if (selected && !value)
|
||||
{
|
||||
Deselect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Wrap
|
||||
{
|
||||
get { return textBlock.Wrap; }
|
||||
set
|
||||
{
|
||||
textBlock.Wrap = value;
|
||||
}
|
||||
}
|
||||
|
||||
public GUITextBlock TextBlock
|
||||
{
|
||||
get { return textBlock; }
|
||||
}
|
||||
|
||||
//should the text be limited to the size of the box
|
||||
//ignored when MaxTextLength is set or text wrapping is enabled
|
||||
public bool ClampText
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public int? MaxTextLength
|
||||
{
|
||||
get { return maxTextLength; }
|
||||
set
|
||||
{
|
||||
textBlock.OverflowClip = value != null;
|
||||
maxTextLength = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool OverflowClip
|
||||
{
|
||||
get { return textBlock.OverflowClip; }
|
||||
set { textBlock.OverflowClip = value; }
|
||||
}
|
||||
|
||||
public override bool Enabled
|
||||
{
|
||||
get { return enabled; }
|
||||
set
|
||||
{
|
||||
enabled = frame.Enabled = textBlock.Enabled = value;
|
||||
if (icon != null) { icon.Enabled = value; }
|
||||
if (!enabled && Selected)
|
||||
{
|
||||
Deselect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Censor
|
||||
{
|
||||
get { return textBlock.Censor; }
|
||||
set { textBlock.Censor = value; }
|
||||
}
|
||||
|
||||
public override string ToolTip
|
||||
{
|
||||
get
|
||||
{
|
||||
return base.ToolTip;
|
||||
}
|
||||
set
|
||||
{
|
||||
base.ToolTip = value;
|
||||
textBlock.ToolTip = value;
|
||||
}
|
||||
}
|
||||
|
||||
public override ScalableFont Font
|
||||
{
|
||||
get { return textBlock?.Font ?? base.Font; }
|
||||
set
|
||||
{
|
||||
base.Font = value;
|
||||
if (textBlock == null) { return; }
|
||||
textBlock.Font = value;
|
||||
}
|
||||
}
|
||||
|
||||
public override Color Color
|
||||
{
|
||||
get { return color; }
|
||||
set
|
||||
{
|
||||
color = value;
|
||||
textBlock.Color = color;
|
||||
}
|
||||
}
|
||||
|
||||
public Color TextColor
|
||||
{
|
||||
get { return textBlock.TextColor; }
|
||||
set { textBlock.TextColor = value; }
|
||||
}
|
||||
|
||||
public override Color HoverColor
|
||||
{
|
||||
get
|
||||
{
|
||||
return base.HoverColor;
|
||||
}
|
||||
set
|
||||
{
|
||||
base.HoverColor = value;
|
||||
textBlock.HoverColor = value;
|
||||
}
|
||||
}
|
||||
|
||||
public Vector4 Padding
|
||||
{
|
||||
get { return textBlock.Padding; }
|
||||
set { textBlock.Padding = value; }
|
||||
}
|
||||
|
||||
// TODO: should this be defined in the stylesheet?
|
||||
public Color SelectionColor { get; set; } = Color.White * 0.25f;
|
||||
|
||||
public string Text
|
||||
{
|
||||
get
|
||||
{
|
||||
return textBlock.Text;
|
||||
}
|
||||
set
|
||||
{
|
||||
SetText(value, store: false);
|
||||
CaretIndex = Text.Length;
|
||||
OnTextChanged?.Invoke(this, Text);
|
||||
}
|
||||
}
|
||||
|
||||
public string WrappedText
|
||||
{
|
||||
get { return textBlock.WrappedText; }
|
||||
}
|
||||
|
||||
public bool Readonly { get; set; }
|
||||
|
||||
public GUITextBox(RectTransform rectT, string text = "", Color? textColor = null, ScalableFont font = null,
|
||||
Alignment textAlignment = Alignment.Left, bool wrap = false, string style = "", Color? color = null, bool createClearButton = false)
|
||||
: base(style, rectT)
|
||||
{
|
||||
HoverCursor = CursorState.IBeam;
|
||||
CanBeFocused = true;
|
||||
|
||||
this.color = color ?? Color.White;
|
||||
frame = new GUIFrame(new RectTransform(Vector2.One, rectT, Anchor.Center), style, color);
|
||||
GUI.Style.Apply(frame, style == "" ? "GUITextBox" : style);
|
||||
textBlock = new GUITextBlock(new RectTransform(Vector2.One, frame.RectTransform, Anchor.CenterLeft), text, textColor, font, textAlignment, wrap, playerInput: true);
|
||||
GUI.Style.Apply(textBlock, "", this);
|
||||
CaretEnabled = true;
|
||||
caretPosDirty = true;
|
||||
|
||||
new GUICustomComponent(new RectTransform(Vector2.One, frame.RectTransform), onDraw: DrawCaretAndSelection);
|
||||
|
||||
int clearButtonWidth = 0;
|
||||
if (createClearButton)
|
||||
{
|
||||
var clearButton = new GUIButton(new RectTransform(new Vector2(0.6f, 0.6f), frame.RectTransform, Anchor.CenterRight, scaleBasis: ScaleBasis.BothHeight) { AbsoluteOffset = new Point(5, 0) }, style: "GUICancelButton")
|
||||
{
|
||||
OnClicked = (bt, userdata) =>
|
||||
{
|
||||
Text = "";
|
||||
frame.Flash(Color.White);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
textBlock.RectTransform.MaxSize = new Point(frame.Rect.Width - clearButton.Rect.Height - clearButton.RectTransform.AbsoluteOffset.X * 2, int.MaxValue);
|
||||
clearButtonWidth = (int)(clearButton.Rect.Width * 1.2f);
|
||||
}
|
||||
|
||||
if (this.style != null && this.style.ChildStyles.ContainsKey("textboxicon"))
|
||||
{
|
||||
icon = new GUIImage(new RectTransform(new Vector2(0.6f, 0.6f), frame.RectTransform, Anchor.CenterRight, scaleBasis: ScaleBasis.BothHeight) { AbsoluteOffset = new Point(5 + clearButtonWidth, 0) }, null, scaleToFit: true);
|
||||
icon.ApplyStyle(this.style.ChildStyles["textboxicon"]);
|
||||
textBlock.RectTransform.MaxSize = new Point(frame.Rect.Width - icon.Rect.Height - clearButtonWidth - icon.RectTransform.AbsoluteOffset.X * 2, int.MaxValue);
|
||||
}
|
||||
Font = textBlock.Font;
|
||||
|
||||
Enabled = true;
|
||||
|
||||
rectT.SizeChanged += () =>
|
||||
{
|
||||
if (icon != null) { textBlock.RectTransform.MaxSize = new Point(frame.Rect.Width - icon.Rect.Height - icon.RectTransform.AbsoluteOffset.X * 2, int.MaxValue); }
|
||||
caretPosDirty = true;
|
||||
};
|
||||
rectT.ScaleChanged += () =>
|
||||
{
|
||||
if (icon != null) { textBlock.RectTransform.MaxSize = new Point(frame.Rect.Width - icon.Rect.Height - icon.RectTransform.AbsoluteOffset.X * 2, int.MaxValue); }
|
||||
caretPosDirty = true;
|
||||
};
|
||||
}
|
||||
|
||||
private bool SetText(string text, bool store = true)
|
||||
{
|
||||
if (textFilterFunction != null)
|
||||
{
|
||||
text = textFilterFunction(text);
|
||||
}
|
||||
if (textBlock.Text == text) { return false; }
|
||||
textBlock.Text = text;
|
||||
if (textBlock.Text == null) textBlock.Text = "";
|
||||
if (textBlock.Text != "" && !Wrap)
|
||||
{
|
||||
if (maxTextLength != null)
|
||||
{
|
||||
if (textBlock.Text.Length > maxTextLength)
|
||||
{
|
||||
textBlock.Text = textBlock.Text.Substring(0, (int)maxTextLength);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
while (ClampText && textBlock.Text.Length>0 && Font.MeasureString(textBlock.Text).X > (int)(textBlock.Rect.Width - textBlock.Padding.X - textBlock.Padding.Z))
|
||||
{
|
||||
textBlock.Text = textBlock.Text.Substring(0, textBlock.Text.Length - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (store)
|
||||
{
|
||||
memento.Store(textBlock.Text);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void CalculateCaretPos()
|
||||
{
|
||||
string textDrawn = Censor ? textBlock.CensoredText : textBlock.WrappedText;
|
||||
if (textDrawn.Contains("\n"))
|
||||
{
|
||||
string[] lines = textDrawn.Split('\n');
|
||||
int totalIndex = 0;
|
||||
for (int i = 0; i < lines.Length; i++)
|
||||
{
|
||||
int currentLineLength = lines[i].Length;
|
||||
totalIndex += currentLineLength;
|
||||
// The caret is on this line
|
||||
if (CaretIndex < totalIndex || totalIndex == textBlock.Text.Length)
|
||||
{
|
||||
int diff = totalIndex - CaretIndex;
|
||||
int index = currentLineLength - diff;
|
||||
Vector2 lineTextSize = Font.MeasureString(lines[i].Substring(0, index));
|
||||
Vector2 lastLineSize = Font.MeasureString(lines[i]);
|
||||
float totalTextHeight = Font.MeasureString(textDrawn.Substring(0, totalIndex)).Y;
|
||||
caretPos = new Vector2(lineTextSize.X, totalTextHeight - lastLineSize.Y) + textBlock.TextPos - textBlock.Origin;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
textDrawn = Censor ? textBlock.CensoredText : textBlock.Text;
|
||||
Vector2 textSize = Font.MeasureString(textDrawn.Substring(0, CaretIndex));
|
||||
caretPos = new Vector2(textSize.X, 0) + textBlock.TextPos - textBlock.Origin;
|
||||
}
|
||||
caretPosDirty = false;
|
||||
}
|
||||
|
||||
protected List<Tuple<Vector2, int>> GetAllPositions()
|
||||
{
|
||||
float halfHeight = Font.MeasureString("T").Y * 0.5f;
|
||||
string textDrawn = Censor ? textBlock.CensoredText : textBlock.WrappedText;
|
||||
var positions = new List<Tuple<Vector2, int>>();
|
||||
if (textDrawn.Contains("\n"))
|
||||
{
|
||||
string[] lines = textDrawn.Split('\n');
|
||||
int index = 0;
|
||||
int totalIndex = 0;
|
||||
for (int i = 0; i < lines.Length; i++)
|
||||
{
|
||||
string line = lines[i];
|
||||
totalIndex += line.Length;
|
||||
float totalTextHeight = Font.MeasureString(textDrawn.Substring(0, totalIndex)).Y;
|
||||
for (int j = 0; j <= line.Length; j++)
|
||||
{
|
||||
Vector2 lineTextSize = Font.MeasureString(line.Substring(0, j));
|
||||
Vector2 indexPos = new Vector2(lineTextSize.X + textBlock.Padding.X, totalTextHeight + textBlock.Padding.Y - halfHeight);
|
||||
//DebugConsole.NewMessage($"index: {index}, pos: {indexPos}", Color.AliceBlue);
|
||||
positions.Add(new Tuple<Vector2, int>(indexPos, index + j));
|
||||
}
|
||||
index = totalIndex;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
textDrawn = Censor ? textBlock.CensoredText : textBlock.Text;
|
||||
for (int i = 0; i <= textBlock.Text.Length; i++)
|
||||
{
|
||||
Vector2 textSize = Font.MeasureString(textDrawn.Substring(0, i));
|
||||
Vector2 indexPos = new Vector2(textSize.X + textBlock.Padding.X, textSize.Y + textBlock.Padding.Y - halfHeight) + textBlock.TextPos - textBlock.Origin;
|
||||
//DebugConsole.NewMessage($"index: {i}, pos: {indexPos}", Color.WhiteSmoke);
|
||||
positions.Add(new Tuple<Vector2, int>(indexPos, i));
|
||||
}
|
||||
}
|
||||
return positions;
|
||||
}
|
||||
|
||||
public int GetCaretIndexFromScreenPos(Vector2 pos)
|
||||
{
|
||||
return GetCaretIndexFromLocalPos(pos - textBlock.Rect.Location.ToVector2());
|
||||
}
|
||||
|
||||
public int GetCaretIndexFromLocalPos(Vector2 pos)
|
||||
{
|
||||
var positions = GetAllPositions();
|
||||
if (positions.Count==0) { return 0; }
|
||||
float halfHeight = Font.MeasureString("T").Y * 0.5f;
|
||||
|
||||
var currPosition = positions[0];
|
||||
|
||||
for (int i=1;i<positions.Count;i++)
|
||||
{
|
||||
var p1 = positions[i];
|
||||
var p2 = currPosition;
|
||||
|
||||
float diffY = Math.Abs(p1.Item1.Y - pos.Y) - Math.Abs(p2.Item1.Y - pos.Y);
|
||||
if (diffY < -3.0f)
|
||||
{
|
||||
currPosition = p1; continue;
|
||||
}
|
||||
else if (diffY > 3.0f)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
diffY = Math.Abs(p1.Item1.Y - pos.Y);
|
||||
if (diffY < halfHeight)
|
||||
{
|
||||
//we are on this line, select the nearest character
|
||||
float diffX = Math.Abs(p1.Item1.X - pos.X) - Math.Abs(p2.Item1.X - pos.X);
|
||||
if (diffX < -1.0f)
|
||||
{
|
||||
currPosition = p1; continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//we are on a different line, preserve order
|
||||
if (p1.Item2 < p2.Item2)
|
||||
{
|
||||
if (p1.Item1.Y > pos.Y) { currPosition = p1; }
|
||||
}
|
||||
else if (p1.Item2 > p2.Item2)
|
||||
{
|
||||
if (p1.Item1.Y < pos.Y) { currPosition = p1; }
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
//GUI.AddMessage($"index: {posIndex.Item2}, pos: {posIndex.Item1}", Color.WhiteSmoke);
|
||||
return currPosition != null ? currPosition.Item2 : textBlock.Text.Length;
|
||||
}
|
||||
|
||||
public void Select(int forcedCaretIndex = -1)
|
||||
{
|
||||
if (memento.Current == null)
|
||||
{
|
||||
memento.Store(Text);
|
||||
}
|
||||
CaretIndex = forcedCaretIndex == - 1 ? GetCaretIndexFromScreenPos(PlayerInput.MousePosition) : forcedCaretIndex;
|
||||
ClearSelection();
|
||||
selected = true;
|
||||
GUI.KeyboardDispatcher.Subscriber = this;
|
||||
OnSelected?.Invoke(this, Keys.None);
|
||||
}
|
||||
|
||||
public void Deselect()
|
||||
{
|
||||
memento.Clear();
|
||||
selected = false;
|
||||
|
||||
if (GUI.KeyboardDispatcher.Subscriber == this)
|
||||
{
|
||||
GUI.KeyboardDispatcher.Subscriber = null;
|
||||
}
|
||||
OnDeselected?.Invoke(this, Keys.None);
|
||||
}
|
||||
|
||||
public override void Flash(Color? color = null, float flashDuration = 1.5f, bool useRectangleFlash = false, bool useCircularFlash = false, Vector2? flashRectOffset = null)
|
||||
{
|
||||
frame.Flash(color, flashDuration, useRectangleFlash, useCircularFlash, flashRectOffset);
|
||||
}
|
||||
|
||||
protected override void Update(float deltaTime)
|
||||
{
|
||||
if (!Visible) return;
|
||||
|
||||
if (flashTimer > 0.0f) flashTimer -= deltaTime;
|
||||
if (!Enabled) { return; }
|
||||
if (MouseRect.Contains(PlayerInput.MousePosition) && (GUI.MouseOn == null || (!(GUI.MouseOn is GUIButton) && GUI.IsMouseOn(this))))
|
||||
{
|
||||
State = ComponentState.Hover;
|
||||
if (PlayerInput.PrimaryMouseButtonDown())
|
||||
{
|
||||
Select();
|
||||
}
|
||||
else
|
||||
{
|
||||
isSelecting = PlayerInput.PrimaryMouseButtonHeld();
|
||||
}
|
||||
if (PlayerInput.DoubleClicked())
|
||||
{
|
||||
SelectAll();
|
||||
}
|
||||
if (isSelecting)
|
||||
{
|
||||
if (!MathUtils.NearlyEqual(PlayerInput.MouseSpeed.X, 0))
|
||||
{
|
||||
CaretIndex = GetCaretIndexFromScreenPos(PlayerInput.MousePosition);
|
||||
CalculateCaretPos();
|
||||
CalculateSelection();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((PlayerInput.LeftButtonClicked() || PlayerInput.RightButtonClicked()) && selected) Deselect();
|
||||
isSelecting = false;
|
||||
State = ComponentState.None;
|
||||
}
|
||||
if (!isSelecting)
|
||||
{
|
||||
isSelecting = PlayerInput.KeyDown(Keys.LeftShift) || PlayerInput.KeyDown(Keys.RightShift);
|
||||
}
|
||||
|
||||
if (CaretEnabled)
|
||||
{
|
||||
if (textBlock.OverflowClipActive)
|
||||
{
|
||||
if (CaretScreenPos.X < textBlock.Rect.X + textBlock.Padding.X)
|
||||
{
|
||||
textBlock.TextPos = new Vector2(textBlock.TextPos.X + ((textBlock.Rect.X + textBlock.Padding.X) - CaretScreenPos.X), textBlock.TextPos.Y);
|
||||
CalculateCaretPos();
|
||||
}
|
||||
else if (CaretScreenPos.X > textBlock.Rect.Right - textBlock.Padding.Z)
|
||||
{
|
||||
textBlock.TextPos = new Vector2(textBlock.TextPos.X - (CaretScreenPos.X - (textBlock.Rect.Right - textBlock.Padding.Z)), textBlock.TextPos.Y);
|
||||
CalculateCaretPos();
|
||||
}
|
||||
}
|
||||
caretTimer += deltaTime;
|
||||
caretVisible = ((caretTimer * 1000.0f) % 1000) < 500;
|
||||
if (caretVisible && caretPosDirty)
|
||||
{
|
||||
CalculateCaretPos();
|
||||
}
|
||||
}
|
||||
|
||||
if (GUI.KeyboardDispatcher.Subscriber == this)
|
||||
{
|
||||
State = ComponentState.Selected;
|
||||
Character.DisableControls = true;
|
||||
if (OnEnterPressed != null && PlayerInput.KeyHit(Keys.Enter))
|
||||
{
|
||||
OnEnterPressed(this, Text);
|
||||
}
|
||||
}
|
||||
else if (Selected)
|
||||
{
|
||||
Deselect();
|
||||
}
|
||||
|
||||
textBlock.State = State;
|
||||
}
|
||||
|
||||
private void DrawCaretAndSelection(SpriteBatch spriteBatch, GUICustomComponent customComponent)
|
||||
{
|
||||
if (!Visible) { return; }
|
||||
if (Selected)
|
||||
{
|
||||
if (caretVisible )
|
||||
{
|
||||
GUI.DrawLine(spriteBatch,
|
||||
new Vector2(Rect.X + (int)caretPos.X + 2, Rect.Y + caretPos.Y + 3),
|
||||
new Vector2(Rect.X + (int)caretPos.X + 2, Rect.Y + caretPos.Y + Font.MeasureString("I").Y - 3),
|
||||
CaretColor ?? textBlock.TextColor * (textBlock.TextColor.A / 255.0f));
|
||||
}
|
||||
if (selectedCharacters > 0)
|
||||
{
|
||||
DrawSelectionRect(spriteBatch);
|
||||
}
|
||||
//GUI.DrawString(spriteBatch, new Vector2(100, 0), selectedCharacters.ToString(), Color.LightBlue, Color.Black);
|
||||
//GUI.DrawString(spriteBatch, new Vector2(100, 20), selectionStartIndex.ToString(), Color.White, Color.Black);
|
||||
//GUI.DrawString(spriteBatch, new Vector2(140, 20), selectionEndIndex.ToString(), Color.White, Color.Black);
|
||||
//GUI.DrawString(spriteBatch, new Vector2(100, 40), selectedText.ToString(), Color.Yellow, Color.Black);
|
||||
//GUI.DrawString(spriteBatch, new Vector2(100, 60), $"caret index: {CaretIndex.ToString()}", GUI.Style.Red, Color.Black);
|
||||
//GUI.DrawString(spriteBatch, new Vector2(100, 80), $"caret pos: {caretPos.ToString()}", GUI.Style.Red, Color.Black);
|
||||
//GUI.DrawString(spriteBatch, new Vector2(100, 100), $"caret screen pos: {CaretScreenPos.ToString()}", GUI.Style.Red, Color.Black);
|
||||
//GUI.DrawString(spriteBatch, new Vector2(100, 120), $"text start pos: {(textBlock.TextPos - textBlock.Origin).ToString()}", Color.White, Color.Black);
|
||||
//GUI.DrawString(spriteBatch, new Vector2(100, 140), $"cursor pos: {PlayerInput.MousePosition.ToString()}", Color.White, Color.Black);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawSelectionRect(SpriteBatch spriteBatch)
|
||||
{
|
||||
if (textBlock.WrappedText.Contains("\n"))
|
||||
{
|
||||
// Multiline selection
|
||||
string[] lines = textBlock.WrappedText.Split('\n');
|
||||
int totalIndex = 0;
|
||||
int previousCharacters = 0;
|
||||
Vector2 offset = textBlock.TextPos - textBlock.Origin;
|
||||
for (int i = 0; i < lines.Length; i++)
|
||||
{
|
||||
string currentLine = lines[i];
|
||||
int currentLineLength = currentLine.Length;
|
||||
totalIndex += currentLineLength;
|
||||
bool containsSelection = IsLeftToRight
|
||||
? selectionStartIndex < totalIndex && selectionEndIndex > previousCharacters
|
||||
: selectionEndIndex < totalIndex && selectionStartIndex > previousCharacters;
|
||||
if (containsSelection)
|
||||
{
|
||||
Vector2 currentLineSize = Font.MeasureString(currentLine);
|
||||
if ((IsLeftToRight && selectionStartIndex < previousCharacters && selectionEndIndex > totalIndex)
|
||||
|| !IsLeftToRight && selectionEndIndex < previousCharacters && selectionStartIndex > totalIndex)
|
||||
{
|
||||
// select the whole line
|
||||
Vector2 topLeft = offset + new Vector2(0, currentLineSize.Y * i);
|
||||
GUI.DrawRectangle(spriteBatch, Rect.Location.ToVector2() + topLeft, currentLineSize, SelectionColor, isFilled: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (IsLeftToRight)
|
||||
{
|
||||
bool selectFromTheBeginning = selectionStartIndex <= previousCharacters;
|
||||
int startIndex = selectFromTheBeginning ? 0 : Math.Abs(selectionStartIndex - previousCharacters);
|
||||
int endIndex = Math.Abs(selectionEndIndex - previousCharacters);
|
||||
int characters = Math.Min(endIndex - startIndex, currentLineLength - startIndex);
|
||||
Vector2 selectedTextSize = Font.MeasureString(currentLine.Substring(startIndex, characters));
|
||||
Vector2 topLeft = selectFromTheBeginning
|
||||
? new Vector2(offset.X, offset.Y + currentLineSize.Y * i)
|
||||
: new Vector2(selectionStartPos.X, offset.Y + currentLineSize.Y * i);
|
||||
GUI.DrawRectangle(spriteBatch, Rect.Location.ToVector2() + topLeft, selectedTextSize, SelectionColor, isFilled: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
bool selectFromTheBeginning = selectionStartIndex >= totalIndex;
|
||||
bool selectFromTheStart = selectionEndIndex <= previousCharacters;
|
||||
int startIndex = selectFromTheBeginning ? currentLineLength : Math.Abs(selectionStartIndex - previousCharacters);
|
||||
int endIndex = selectFromTheStart ? 0 : Math.Abs(selectionEndIndex - previousCharacters);
|
||||
int characters = Math.Min(Math.Abs(endIndex - startIndex), currentLineLength);
|
||||
Vector2 selectedTextSize = Font.MeasureString(currentLine.Substring(endIndex, characters));
|
||||
Vector2 topLeft = selectFromTheBeginning
|
||||
? new Vector2(offset.X + currentLineSize.X - selectedTextSize.X, offset.Y + currentLineSize.Y * i)
|
||||
: new Vector2(selectionStartPos.X - selectedTextSize.X, offset.Y + currentLineSize.Y * i);
|
||||
GUI.DrawRectangle(spriteBatch, Rect.Location.ToVector2() + topLeft, selectedTextSize, SelectionColor, isFilled: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
previousCharacters = totalIndex;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Single line selection
|
||||
Vector2 topLeft = IsLeftToRight ? selectionStartPos : selectionEndPos;
|
||||
GUI.DrawRectangle(spriteBatch, Rect.Location.ToVector2() + topLeft, selectionRectSize, SelectionColor, isFilled: true);
|
||||
}
|
||||
}
|
||||
|
||||
public void ReceiveTextInput(char inputChar)
|
||||
{
|
||||
ReceiveTextInput(inputChar.ToString());
|
||||
}
|
||||
|
||||
public void ReceiveTextInput(string input)
|
||||
{
|
||||
if (Readonly) { return; }
|
||||
if (selectedCharacters > 0)
|
||||
{
|
||||
RemoveSelectedText();
|
||||
}
|
||||
Vector2 textPos = textBlock.TextPos;
|
||||
bool wasOverflowClipActive = textBlock.OverflowClipActive;
|
||||
if (SetText(Text.Insert(CaretIndex, input)))
|
||||
{
|
||||
CaretIndex = Math.Min(Text.Length, CaretIndex + input.Length);
|
||||
OnTextChanged?.Invoke(this, Text);
|
||||
if (textBlock.OverflowClipActive && wasOverflowClipActive && !MathUtils.NearlyEqual(textBlock.TextPos, textPos))
|
||||
{
|
||||
textBlock.TextPos = textPos + Vector2.UnitX * Font.MeasureString(input).X;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ReceiveCommandInput(char command)
|
||||
{
|
||||
if (Text == null) Text = "";
|
||||
|
||||
switch (command)
|
||||
{
|
||||
case '\b' when !Readonly: //backspace
|
||||
if (PlayerInput.KeyDown(Keys.LeftControl) || PlayerInput.KeyDown(Keys.RightControl))
|
||||
{
|
||||
SetText(string.Empty, false);
|
||||
CaretIndex = Text.Length;
|
||||
}
|
||||
else if (selectedCharacters > 0)
|
||||
{
|
||||
RemoveSelectedText();
|
||||
}
|
||||
else if (Text.Length > 0 && CaretIndex > 0)
|
||||
{
|
||||
CaretIndex--;
|
||||
SetText(Text.Remove(CaretIndex, 1));
|
||||
CalculateCaretPos();
|
||||
ClearSelection();
|
||||
}
|
||||
OnTextChanged?.Invoke(this, Text);
|
||||
break;
|
||||
case (char)0x3: // ctrl-c
|
||||
CopySelectedText();
|
||||
break;
|
||||
case (char)0x16 when !Readonly: // ctrl-v
|
||||
string text = GetCopiedText();
|
||||
RemoveSelectedText();
|
||||
if (SetText(Text.Insert(CaretIndex, text)))
|
||||
{
|
||||
CaretIndex = Math.Min(Text.Length, CaretIndex + text.Length);
|
||||
OnTextChanged?.Invoke(this, Text);
|
||||
}
|
||||
break;
|
||||
case (char)0x18: // ctrl-x
|
||||
CopySelectedText();
|
||||
if (!Readonly)
|
||||
{
|
||||
RemoveSelectedText();
|
||||
}
|
||||
break;
|
||||
case (char)0x1: // ctrl-a
|
||||
SelectAll();
|
||||
break;
|
||||
case (char)0x1A when !Readonly: // ctrl-z
|
||||
text = memento.Undo();
|
||||
if (text != Text)
|
||||
{
|
||||
ClearSelection();
|
||||
SetText(text, false);
|
||||
CaretIndex = Text.Length;
|
||||
OnTextChanged?.Invoke(this, Text);
|
||||
}
|
||||
break;
|
||||
case (char)0x12 when !Readonly: // ctrl-r
|
||||
text = memento.Redo();
|
||||
if (text != Text)
|
||||
{
|
||||
ClearSelection();
|
||||
SetText(text, false);
|
||||
CaretIndex = Text.Length;
|
||||
OnTextChanged?.Invoke(this, Text);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void ReceiveSpecialInput(Keys key)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
case Keys.Left:
|
||||
if (isSelecting)
|
||||
{
|
||||
InitSelectionStart();
|
||||
}
|
||||
CaretIndex = Math.Max(CaretIndex - 1, 0);
|
||||
caretTimer = 0;
|
||||
HandleSelection();
|
||||
break;
|
||||
case Keys.Right:
|
||||
if (isSelecting)
|
||||
{
|
||||
InitSelectionStart();
|
||||
}
|
||||
CaretIndex = Math.Min(CaretIndex + 1, Text.Length);
|
||||
caretTimer = 0;
|
||||
HandleSelection();
|
||||
break;
|
||||
case Keys.Up:
|
||||
if (isSelecting)
|
||||
{
|
||||
InitSelectionStart();
|
||||
}
|
||||
float lineHeight = Font.MeasureString("T").Y;
|
||||
int newIndex = GetCaretIndexFromLocalPos(new Vector2(caretPos.X, caretPos.Y-lineHeight));
|
||||
CaretIndex = newIndex;
|
||||
caretTimer = 0;
|
||||
HandleSelection();
|
||||
break;
|
||||
case Keys.Down:
|
||||
if (isSelecting)
|
||||
{
|
||||
InitSelectionStart();
|
||||
}
|
||||
lineHeight = Font.MeasureString("T").Y;
|
||||
newIndex = GetCaretIndexFromLocalPos(new Vector2(caretPos.X, caretPos.Y+lineHeight));
|
||||
CaretIndex = newIndex;
|
||||
caretTimer = 0;
|
||||
HandleSelection();
|
||||
break;
|
||||
case Keys.Delete when !Readonly:
|
||||
if (selectedCharacters > 0)
|
||||
{
|
||||
RemoveSelectedText();
|
||||
}
|
||||
else if (Text.Length > 0 && CaretIndex < Text.Length)
|
||||
{
|
||||
SetText(Text.Remove(CaretIndex, 1));
|
||||
OnTextChanged?.Invoke(this, Text);
|
||||
caretPosDirty = true;
|
||||
}
|
||||
break;
|
||||
case Keys.Tab:
|
||||
// Select the next text box.
|
||||
var editor = RectTransform.GetParents().Select(p => p.GUIComponent as SerializableEntityEditor).FirstOrDefault(e => e != null);
|
||||
if (editor == null) { break; }
|
||||
var allTextBoxes = GetAndSortTextBoxes(editor).ToList();
|
||||
if (allTextBoxes.Any())
|
||||
{
|
||||
int currentIndex = allTextBoxes.IndexOf(this);
|
||||
int nextIndex = Math.Min(allTextBoxes.Count - 1, currentIndex + 1);
|
||||
var next = allTextBoxes[nextIndex];
|
||||
if (next != this)
|
||||
{
|
||||
next.Select();
|
||||
next.Flash(Color.White * 0.5f, 0.5f);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Select the first text box in the next editor that has text boxes.
|
||||
var listBox = RectTransform.GetParents().Select(p => p.GUIComponent as GUIListBox).FirstOrDefault(lb => lb != null);
|
||||
if (listBox == null) { break; }
|
||||
// TODO: The get's out of focus if the selection is out of view.
|
||||
// Not sure how's that possible, but it seems to work when the auto scroll is disabled and you handle the scrolling manually.
|
||||
listBox.SelectNext();
|
||||
while (SelectNextTextBox(listBox) == null)
|
||||
{
|
||||
var previous = listBox.SelectedComponent;
|
||||
listBox.SelectNext();
|
||||
if (listBox.SelectedComponent == previous) { break; }
|
||||
}
|
||||
}
|
||||
}
|
||||
IEnumerable<GUITextBox> GetAndSortTextBoxes(GUIComponent parent) => parent.GetAllChildren<GUITextBox>().OrderBy(t => t.Rect.Y).ThenBy(t => t.Rect.X);
|
||||
GUITextBox SelectNextTextBox(GUIListBox listBox)
|
||||
{
|
||||
var textBoxes = GetAndSortTextBoxes(listBox.SelectedComponent);
|
||||
if (textBoxes.Any())
|
||||
{
|
||||
var next = textBoxes.First();
|
||||
next.Select();
|
||||
next.Flash(Color.White * 0.5f, 0.5f);
|
||||
return next;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
}
|
||||
OnKeyHit?.Invoke(this, key);
|
||||
void HandleSelection()
|
||||
{
|
||||
if (isSelecting)
|
||||
{
|
||||
InitSelectionStart();
|
||||
CalculateSelection();
|
||||
}
|
||||
else
|
||||
{
|
||||
ClearSelection();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SelectAll()
|
||||
{
|
||||
CaretIndex = 0;
|
||||
CalculateCaretPos();
|
||||
selectionStartPos = caretPos;
|
||||
selectionStartIndex = 0;
|
||||
CaretIndex = Text.Length;
|
||||
CalculateSelection();
|
||||
}
|
||||
|
||||
private void CopySelectedText()
|
||||
{
|
||||
Clipboard.SetText(selectedText);
|
||||
}
|
||||
|
||||
private void ClearSelection()
|
||||
{
|
||||
selectedCharacters = 0;
|
||||
selectionStartIndex = -1;
|
||||
selectionEndIndex = -1;
|
||||
selectedText = string.Empty;
|
||||
}
|
||||
|
||||
private string GetCopiedText()
|
||||
{
|
||||
string t;
|
||||
t = Clipboard.GetText();
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
private void RemoveSelectedText()
|
||||
{
|
||||
if (selectedText.Length == 0) { return; }
|
||||
|
||||
selectionStartIndex = Math.Max(0, Math.Min(selectionEndIndex, Math.Min(selectionStartIndex, Text.Length - 1)));
|
||||
int selectionLength = Math.Min(Text.Length - selectionStartIndex, selectedText.Length);
|
||||
SetText(Text.Remove(selectionStartIndex, selectionLength));
|
||||
CaretIndex = Math.Min(Text.Length, selectionStartIndex);
|
||||
|
||||
ClearSelection();
|
||||
OnTextChanged?.Invoke(this, Text);
|
||||
}
|
||||
|
||||
private void InitSelectionStart()
|
||||
{
|
||||
if (caretPosDirty)
|
||||
{
|
||||
CalculateCaretPos();
|
||||
}
|
||||
if (selectionStartIndex == -1)
|
||||
{
|
||||
selectionStartIndex = CaretIndex;
|
||||
selectionStartPos = caretPos;
|
||||
}
|
||||
}
|
||||
|
||||
private void CalculateSelection()
|
||||
{
|
||||
string textDrawn = Censor ? textBlock.CensoredText : textBlock.WrappedText;
|
||||
InitSelectionStart();
|
||||
selectionEndIndex = CaretIndex;
|
||||
selectionEndPos = caretPos;
|
||||
selectedCharacters = Math.Abs(selectionStartIndex - selectionEndIndex);
|
||||
if (IsLeftToRight)
|
||||
{
|
||||
selectedText = Text.Substring(selectionStartIndex, selectedCharacters);
|
||||
selectionRectSize = Font.MeasureString(textDrawn.Substring(selectionStartIndex, selectedCharacters));
|
||||
}
|
||||
else
|
||||
{
|
||||
selectedText = Text.Substring(selectionEndIndex, selectedCharacters);
|
||||
selectionRectSize = Font.MeasureString(textDrawn.Substring(selectionEndIndex, selectedCharacters));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class GUITickBox : GUIComponent
|
||||
{
|
||||
private GUILayoutGroup layoutGroup;
|
||||
private GUIFrame box;
|
||||
private GUITextBlock text;
|
||||
|
||||
public delegate bool OnSelectedHandler(GUITickBox obj);
|
||||
public OnSelectedHandler OnSelected;
|
||||
|
||||
public static int size = 20;
|
||||
|
||||
private GUIRadioButtonGroup radioButtonGroup;
|
||||
|
||||
public override bool Selected
|
||||
{
|
||||
get { return selected; }
|
||||
set
|
||||
{
|
||||
if (value == selected) { return; }
|
||||
if (radioButtonGroup != null && radioButtonGroup.SelectedRadioButton == this)
|
||||
{
|
||||
selected = true;
|
||||
return;
|
||||
}
|
||||
|
||||
selected = value;
|
||||
State = selected ? ComponentState.Selected : ComponentState.None;
|
||||
if (value && radioButtonGroup != null)
|
||||
{
|
||||
radioButtonGroup.SelectRadioButton(this);
|
||||
}
|
||||
|
||||
OnSelected?.Invoke(this);
|
||||
}
|
||||
}
|
||||
|
||||
public override ComponentState State
|
||||
{
|
||||
get
|
||||
{
|
||||
return base.State;
|
||||
}
|
||||
set
|
||||
{
|
||||
base.State = value;
|
||||
box.State = TextBlock.State = value;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Enabled
|
||||
{
|
||||
get
|
||||
{
|
||||
return enabled;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (value == enabled) { return; }
|
||||
enabled = box.Enabled = TextBlock.Enabled = value;
|
||||
}
|
||||
}
|
||||
|
||||
public Color TextColor
|
||||
{
|
||||
get { return text.TextColor; }
|
||||
set { text.TextColor = value; }
|
||||
}
|
||||
|
||||
/*public override Rectangle MouseRect
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!CanBeFocused) return Rectangle.Empty;
|
||||
Rectangle union = Rectangle.Union(box.Rect, TextBlock.Rect);
|
||||
Vector2 textPos = TextBlock.Rect.Location.ToVector2() + TextBlock.TextPos + TextBlock.TextOffset;
|
||||
Vector2 textSize = TextBlock.Font.MeasureString(TextBlock.Text);
|
||||
union = Rectangle.Union(union, new Rectangle(textPos.ToPoint(), textSize.ToPoint()));
|
||||
union = Rectangle.Union(union, Rect);
|
||||
return ClampMouseRectToParent ? ClampRect(union) : union;
|
||||
}
|
||||
}*/
|
||||
|
||||
public override ScalableFont Font
|
||||
{
|
||||
get
|
||||
{
|
||||
return base.Font;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
base.Font = value;
|
||||
if (text != null) text.Font = value;
|
||||
}
|
||||
}
|
||||
|
||||
public GUIFrame Box
|
||||
{
|
||||
get { return box; }
|
||||
}
|
||||
|
||||
public GUITextBlock TextBlock
|
||||
{
|
||||
get { return text; }
|
||||
}
|
||||
|
||||
public override string ToolTip
|
||||
{
|
||||
get { return base.ToolTip; }
|
||||
set
|
||||
{
|
||||
base.ToolTip = value;
|
||||
box.ToolTip = value;
|
||||
text.ToolTip = value;
|
||||
}
|
||||
}
|
||||
|
||||
public string Text
|
||||
{
|
||||
get { return text.Text; }
|
||||
set { text.Text = value; }
|
||||
}
|
||||
|
||||
public GUITickBox(RectTransform rectT, string label, ScalableFont font = null, string style = "") : base(null, rectT)
|
||||
{
|
||||
CanBeFocused = true;
|
||||
HoverCursor = CursorState.Hand;
|
||||
|
||||
layoutGroup = new GUILayoutGroup(new RectTransform(Vector2.One, rectT), true);
|
||||
|
||||
box = new GUIFrame(new RectTransform(Vector2.One, layoutGroup.RectTransform, Anchor.CenterLeft, scaleBasis: ScaleBasis.BothHeight)
|
||||
{
|
||||
IsFixedSize = true
|
||||
}, string.Empty, Color.DarkGray)
|
||||
{
|
||||
HoverColor = Color.Gray,
|
||||
SelectedColor = Color.DarkGray,
|
||||
CanBeFocused = false
|
||||
};
|
||||
GUI.Style.Apply(box, style == "" ? "GUITickBox" : style);
|
||||
if (box.RectTransform.MinSize.Y > 0)
|
||||
{
|
||||
RectTransform.MinSize = box.RectTransform.MinSize;
|
||||
RectTransform.MaxSize = box.RectTransform.MaxSize;
|
||||
RectTransform.Resize(new Point(RectTransform.NonScaledSize.X, RectTransform.MinSize.Y));
|
||||
box.RectTransform.MinSize = new Point(box.RectTransform.MinSize.Y);
|
||||
box.RectTransform.Resize(box.RectTransform.MinSize);
|
||||
}
|
||||
Vector2 textBlockScale = new Vector2((float)(Rect.Width - Rect.Height) / (float)Math.Max(Rect.Width, 1.0), 1.0f);
|
||||
text = new GUITextBlock(new RectTransform(textBlockScale, layoutGroup.RectTransform, Anchor.CenterLeft), label, font: font, textAlignment: Alignment.CenterLeft)
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
GUI.Style.Apply(text, "GUITextBlock", this);
|
||||
Enabled = true;
|
||||
|
||||
ResizeBox();
|
||||
|
||||
rectT.ScaleChanged += ResizeBox;
|
||||
rectT.SizeChanged += ResizeBox;
|
||||
}
|
||||
|
||||
public void SetRadioButtonGroup(GUIRadioButtonGroup rbg)
|
||||
{
|
||||
radioButtonGroup = rbg;
|
||||
}
|
||||
|
||||
private void ResizeBox()
|
||||
{
|
||||
Vector2 textBlockScale = new Vector2(Math.Max(Rect.Width - box.Rect.Width, 0.0f) / Math.Max(Rect.Width, 1.0f), 1.0f);
|
||||
text.RectTransform.RelativeSize = textBlockScale;
|
||||
box.RectTransform.MinSize = new Point(Rect.Height);
|
||||
box.RectTransform.Resize(box.RectTransform.MinSize);
|
||||
text.SetTextPos();
|
||||
}
|
||||
|
||||
protected override void Update(float deltaTime)
|
||||
{
|
||||
if (!Visible) { return; }
|
||||
|
||||
base.Update(deltaTime);
|
||||
|
||||
if (GUI.MouseOn == this && Enabled)
|
||||
{
|
||||
State = Selected ?
|
||||
ComponentState.HoverSelected :
|
||||
ComponentState.Hover;
|
||||
|
||||
if (PlayerInput.PrimaryMouseButtonHeld())
|
||||
{
|
||||
State = ComponentState.Selected;
|
||||
}
|
||||
|
||||
if (PlayerInput.PrimaryMouseButtonClicked())
|
||||
{
|
||||
if (radioButtonGroup == null)
|
||||
{
|
||||
Selected = !Selected;
|
||||
}
|
||||
else if (!selected)
|
||||
{
|
||||
Selected = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (selected)
|
||||
{
|
||||
State = ComponentState.Selected;
|
||||
}
|
||||
else
|
||||
{
|
||||
State = ComponentState.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class Graph
|
||||
{
|
||||
private float[] values;
|
||||
|
||||
public Graph(int arraySize = 100)
|
||||
{
|
||||
values = new float[arraySize];
|
||||
}
|
||||
|
||||
public float LargestValue()
|
||||
{
|
||||
float maxValue = 0.0f;
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
if (values[i] > maxValue) maxValue = values[i];
|
||||
}
|
||||
return maxValue;
|
||||
}
|
||||
|
||||
public float Average()
|
||||
{
|
||||
return values.Length == 0 ? 0.0f : values.Average();
|
||||
}
|
||||
|
||||
public void Update(float newValue)
|
||||
{
|
||||
for (int i = values.Length - 1; i > 0; i--)
|
||||
{
|
||||
values[i] = values[i - 1];
|
||||
}
|
||||
values[0] = newValue;
|
||||
}
|
||||
|
||||
public void Draw(SpriteBatch spriteBatch, Rectangle rect, float? maxVal, float xOffset, Color color)
|
||||
{
|
||||
float graphMaxVal = 1.0f;
|
||||
if (maxVal == null)
|
||||
{
|
||||
graphMaxVal = LargestValue();
|
||||
}
|
||||
else if (maxVal > 0.0f)
|
||||
{
|
||||
graphMaxVal = (float)maxVal;
|
||||
}
|
||||
|
||||
GUI.DrawRectangle(spriteBatch, rect, Color.White);
|
||||
|
||||
if (values.Length == 0) return;
|
||||
|
||||
float lineWidth = (float)rect.Width / (float)(values.Length - 2);
|
||||
float yScale = (float)rect.Height / graphMaxVal;
|
||||
|
||||
Vector2 prevPoint = new Vector2(rect.Right, rect.Bottom - (values[1] + (values[0] - values[1]) * xOffset) * yScale);
|
||||
float currX = rect.Right - ((xOffset - 1.0f) * lineWidth);
|
||||
for (int i = 1; i < values.Length - 1; i++)
|
||||
{
|
||||
currX -= lineWidth;
|
||||
Vector2 newPoint = new Vector2(currX, rect.Bottom - values[i] * yScale);
|
||||
GUI.DrawLine(spriteBatch, prevPoint, newPoint - new Vector2(1.0f, 0), color);
|
||||
prevPoint = newPoint;
|
||||
}
|
||||
|
||||
Vector2 lastPoint = new Vector2(rect.X,
|
||||
rect.Bottom - (values[values.Length - 1] + (values[values.Length - 2] - values[values.Length - 1]) * xOffset) * yScale);
|
||||
|
||||
GUI.DrawLine(spriteBatch, prevPoint, lastPoint, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
static class HUDLayoutSettings
|
||||
{
|
||||
public static bool DebugDraw;
|
||||
|
||||
private static int inventoryTopY;
|
||||
public static int InventoryTopY
|
||||
{
|
||||
get { return inventoryTopY; }
|
||||
set
|
||||
{
|
||||
if (value == inventoryTopY) return;
|
||||
inventoryTopY = value;
|
||||
CreateAreas();
|
||||
}
|
||||
}
|
||||
|
||||
public static Rectangle ButtonAreaTop
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
public static Rectangle MessageAreaTop
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
public static Rectangle CrewArea
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
public static Rectangle ChatBoxArea
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
public static Rectangle ObjectiveAnchor
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
public static Rectangle InventoryAreaLower
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
/*public static Rectangle HealthBarAreaRight
|
||||
{
|
||||
get; private set;
|
||||
}*/
|
||||
public static Rectangle HealthBarArea
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
public static Rectangle BottomRightInfoArea
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
public static Rectangle AfflictionAreaLeft
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
public static Rectangle HealthWindowAreaLeft
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
public static Rectangle PortraitArea
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
public static int Padding
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
static HUDLayoutSettings()
|
||||
{
|
||||
if (GameMain.Instance != null)
|
||||
{
|
||||
GameMain.Instance.OnResolutionChanged += CreateAreas;
|
||||
GameMain.Config.OnHUDScaleChanged += CreateAreas;
|
||||
CreateAreas();
|
||||
CharacterInfo.Init();
|
||||
}
|
||||
}
|
||||
|
||||
public static RectTransform ToRectTransform(Rectangle rect, RectTransform parent)
|
||||
{
|
||||
return new RectTransform(new Vector2(rect.Width / (float)GameMain.GraphicsWidth, rect.Height / (float)GameMain.GraphicsHeight), parent)
|
||||
{
|
||||
RelativeOffset = new Vector2(rect.X / (float)GameMain.GraphicsWidth, rect.Y / (float)GameMain.GraphicsHeight)
|
||||
};
|
||||
}
|
||||
|
||||
public static void CreateAreas()
|
||||
{
|
||||
Padding = (int)(11 * GUI.Scale);
|
||||
|
||||
if (inventoryTopY == 0) { inventoryTopY = GameMain.GraphicsHeight - 30; }
|
||||
|
||||
//slice from the top of the screen for misc buttons (info, end round, server controls)
|
||||
ButtonAreaTop = new Rectangle(Padding, Padding, GameMain.GraphicsWidth - Padding * 2, (int)(50 * GUI.Scale));
|
||||
|
||||
int infoAreaWidth = (int)(142 * GUI.Scale);
|
||||
int infoAreaHeight = (int)(98 * GUI.Scale);
|
||||
int portraitSize = (int)(infoAreaHeight * 0.95f);
|
||||
BottomRightInfoArea = new Rectangle(GameMain.GraphicsWidth - Padding * 2 - infoAreaWidth, GameMain.GraphicsHeight - Padding * 2 - infoAreaHeight, infoAreaWidth, infoAreaHeight);
|
||||
PortraitArea = new Rectangle(GameMain.GraphicsWidth - portraitSize, BottomRightInfoArea.Bottom - portraitSize + Padding / 2, portraitSize, portraitSize);
|
||||
|
||||
//horizontal slices at the corners of the screen for health bar and affliction icons
|
||||
int afflictionAreaHeight = (int)(50 * GUI.Scale);
|
||||
int healthBarWidth = BottomRightInfoArea.Width + CharacterInventory.SlotSize.X + CharacterInventory.Spacing * 2 + CharacterInventory.HideButtonWidth;
|
||||
int healthBarHeight = (int)(50f * GUI.Scale);
|
||||
HealthBarArea = new Rectangle(BottomRightInfoArea.X - (healthBarWidth - BottomRightInfoArea.Width) + (int)(2 * GUI.Scale), BottomRightInfoArea.Y - healthBarHeight + (int)(10 * GUI.Scale), healthBarWidth, healthBarHeight);
|
||||
AfflictionAreaLeft = new Rectangle(HealthBarArea.X, HealthBarArea.Y - Padding - afflictionAreaHeight, HealthBarArea.Width, afflictionAreaHeight);
|
||||
|
||||
//HealthBarAreaRight = new Rectangle(Padding, GameMain.GraphicsHeight - healthBarHeight - Padding, healthBarWidth, healthBarHeight);
|
||||
/*if (HealthBarAreaRight.Y + healthBarHeight * 0.75f < PortraitArea.Y)
|
||||
{
|
||||
HealthBarAreaRight = new Rectangle(GameMain.GraphicsWidth - Padding - healthBarWidth, HealthBarAreaRight.Y, HealthBarAreaRight.Width, HealthBarAreaRight.Height);
|
||||
}*/
|
||||
//AfflictionAreaRight = new Rectangle(HealthBarAreaRight.X, HealthBarAreaRight.Y + healthBarHeight + Padding, healthBarWidth, afflictionAreaHeight);
|
||||
|
||||
int messageAreaWidth = GameMain.GraphicsWidth / 3;
|
||||
MessageAreaTop = new Rectangle((GameMain.GraphicsWidth - messageAreaWidth) / 2, ButtonAreaTop.Bottom, messageAreaWidth, ButtonAreaTop.Height);
|
||||
|
||||
bool isFourByThree = GUI.IsFourByThree();
|
||||
int chatBoxWidth = !isFourByThree ? (int)(475 * GUI.Scale) : (int)(375 * GUI.Scale);
|
||||
int chatBoxHeight = (int)Math.Max(GameMain.GraphicsHeight * 0.25f, 150);
|
||||
ChatBoxArea = new Rectangle(Padding, GameMain.GraphicsHeight - Padding - chatBoxHeight, chatBoxWidth, chatBoxHeight);
|
||||
|
||||
int objectiveAnchorWidth = (int)(250 * GUI.Scale);
|
||||
int objectiveAnchorOffsetY = (int)(150 * GUI.Scale);
|
||||
ObjectiveAnchor = new Rectangle(Padding, ChatBoxArea.Y - objectiveAnchorOffsetY, objectiveAnchorWidth, 0);
|
||||
|
||||
CrewArea = new Rectangle(Padding, Padding, (int)Math.Max(400 * GUI.Scale, 220), ObjectiveAnchor.Top - Padding * 2);
|
||||
|
||||
InventoryAreaLower = new Rectangle(Padding, inventoryTopY, GameMain.GraphicsWidth - Padding * 2, GameMain.GraphicsHeight - inventoryTopY);
|
||||
|
||||
int healthWindowWidth = (int)(GameMain.GraphicsWidth * 0.5f);
|
||||
int healthWindowHeight = (int)(GameMain.GraphicsWidth * 0.5f * 0.65f);
|
||||
int healthWindowX = GameMain.GraphicsWidth / 2 - healthWindowWidth / 2;
|
||||
int healthWindowY = GameMain.GraphicsHeight / 2 - healthWindowHeight / 2;
|
||||
|
||||
HealthWindowAreaLeft = new Rectangle(healthWindowX, healthWindowY, healthWindowWidth, healthWindowHeight);
|
||||
}
|
||||
|
||||
public static void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
GUI.DrawRectangle(spriteBatch, ButtonAreaTop, Color.White * 0.5f);
|
||||
GUI.DrawRectangle(spriteBatch, MessageAreaTop, GUI.Style.Orange * 0.5f);
|
||||
GUI.DrawRectangle(spriteBatch, CrewArea, Color.Blue * 0.5f);
|
||||
GUI.DrawRectangle(spriteBatch, ChatBoxArea, Color.Cyan * 0.5f);
|
||||
GUI.DrawRectangle(spriteBatch, HealthBarArea, Color.Red * 0.5f);
|
||||
GUI.DrawRectangle(spriteBatch, AfflictionAreaLeft, Color.Red * 0.5f);
|
||||
GUI.DrawRectangle(spriteBatch, InventoryAreaLower, Color.Yellow * 0.5f);
|
||||
GUI.DrawRectangle(spriteBatch, HealthWindowAreaLeft, Color.Red * 0.5f);
|
||||
GUI.DrawRectangle(spriteBatch, BottomRightInfoArea, Color.Green * 0.5f);
|
||||
}
|
||||
}
|
||||
|
||||
public static class HUD
|
||||
{
|
||||
public static bool CloseHUD(Rectangle rect)
|
||||
{
|
||||
// Always close when hitting escape
|
||||
if (PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.Escape)) { return true; }
|
||||
|
||||
//don't close when the cursor is on a UI element
|
||||
if (GUI.MouseOn != null) { return false; }
|
||||
|
||||
//don't close when hovering over an inventory element
|
||||
if (Inventory.IsMouseOnInventory()) { return false; }
|
||||
|
||||
bool input = PlayerInput.PrimaryMouseButtonDown() || PlayerInput.SecondaryMouseButtonClicked();
|
||||
return input && !rect.Contains(PlayerInput.MousePosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,387 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Media;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class LoadingScreen
|
||||
{
|
||||
private readonly Texture2D defaultBackgroundTexture, overlay;
|
||||
private readonly SpriteSheet decorativeGraph, decorativeMap;
|
||||
private Texture2D currentBackgroundTexture;
|
||||
private Sprite noiseSprite;
|
||||
|
||||
private string randText = "";
|
||||
|
||||
private Sprite languageSelectionCursor;
|
||||
private ScalableFont languageSelectionFont, languageSelectionFontCJK;
|
||||
|
||||
private Video currSplashScreen;
|
||||
private DateTime videoStartTime;
|
||||
|
||||
public struct PendingSplashScreen
|
||||
{
|
||||
public string Filename;
|
||||
public float Gain;
|
||||
public PendingSplashScreen(string filename, float gain)
|
||||
{
|
||||
Filename = filename;
|
||||
Gain = gain;
|
||||
}
|
||||
}
|
||||
|
||||
private Queue<PendingSplashScreen> pendingSplashScreens = new Queue<PendingSplashScreen>();
|
||||
/// <summary>
|
||||
/// Triplet.first = filepath, Triplet.second = resolution, Triplet.third = audio gain
|
||||
/// </summary>
|
||||
public Queue<PendingSplashScreen> PendingSplashScreens
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (loadMutex)
|
||||
{
|
||||
return pendingSplashScreens;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
lock (loadMutex)
|
||||
{
|
||||
pendingSplashScreens = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool PlayingSplashScreen
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (loadMutex)
|
||||
{
|
||||
return currSplashScreen != null || pendingSplashScreens.Count > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string selectedTip;
|
||||
|
||||
private readonly object loadMutex = new object();
|
||||
private float? loadState;
|
||||
|
||||
public float? LoadState
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (loadMutex)
|
||||
{
|
||||
return loadState;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
lock (loadMutex)
|
||||
{
|
||||
loadState = value;
|
||||
DrawLoadingText = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool DrawLoadingText
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public bool WaitForLanguageSelection
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public LoadingScreen(GraphicsDevice graphics)
|
||||
{
|
||||
defaultBackgroundTexture = TextureLoader.FromFile("Content/Map/LocationPortraits/AlienRuins.png");
|
||||
|
||||
decorativeMap = new SpriteSheet("Content/Map/MapHUD.png", 6, 5, Vector2.Zero, sourceRect: new Rectangle(0, 0, 2048, 640));
|
||||
decorativeGraph = new SpriteSheet("Content/Map/MapHUD.png", 4, 10, Vector2.Zero, sourceRect: new Rectangle(1025, 1259, 1024, 732));
|
||||
|
||||
overlay = TextureLoader.FromFile("Content/UI/LoadingScreenOverlay.png");
|
||||
noiseSprite = new Sprite("Content/UI/noise.png", Vector2.Zero);
|
||||
DrawLoadingText = true;
|
||||
selectedTip = TextManager.Get("LoadingScreenTip", true);
|
||||
}
|
||||
|
||||
public void Draw(SpriteBatch spriteBatch, GraphicsDevice graphics, float deltaTime)
|
||||
{
|
||||
if (GameMain.Config.EnableSplashScreen)
|
||||
{
|
||||
try
|
||||
{
|
||||
DrawSplashScreen(spriteBatch, graphics);
|
||||
if (currSplashScreen != null || PendingSplashScreens.Count > 0) { return; }
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Playing splash screen video failed", e);
|
||||
GameMain.Config.EnableSplashScreen = false;
|
||||
}
|
||||
}
|
||||
|
||||
var titleStyle = GUI.Style?.GetComponentStyle("TitleText");
|
||||
Sprite titleSprite = null;
|
||||
if (!WaitForLanguageSelection && titleStyle != null && titleStyle.Sprites.ContainsKey(GUIComponent.ComponentState.None))
|
||||
{
|
||||
titleSprite = titleStyle.Sprites[GUIComponent.ComponentState.None].First()?.Sprite;
|
||||
}
|
||||
|
||||
drawn = true;
|
||||
|
||||
currentBackgroundTexture ??= defaultBackgroundTexture;
|
||||
|
||||
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, samplerState: GUI.SamplerState);
|
||||
|
||||
float scale = (GameMain.GraphicsWidth / (float)currentBackgroundTexture.Width) * 1.2f;
|
||||
float paddingX = currentBackgroundTexture.Width * scale - GameMain.GraphicsWidth;
|
||||
float paddingY = currentBackgroundTexture.Height * scale - GameMain.GraphicsHeight;
|
||||
|
||||
double noiseT = (Timing.TotalTime * 0.02f);
|
||||
Vector2 pos = new Vector2((float)PerlinNoise.CalculatePerlin(noiseT, noiseT, 0) - 0.5f, (float)PerlinNoise.CalculatePerlin(noiseT, noiseT, 0.5f) - 0.5f);
|
||||
pos = new Vector2(pos.X * paddingX, pos.Y * paddingY);
|
||||
|
||||
spriteBatch.Draw(currentBackgroundTexture,
|
||||
new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight) / 2 + pos,
|
||||
null, Color.White, 0.0f, new Vector2(currentBackgroundTexture.Width / 2, currentBackgroundTexture.Height / 2),
|
||||
scale, SpriteEffects.None, 0.0f);
|
||||
|
||||
spriteBatch.Draw(overlay, new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight), null, Color.White, 0.0f, Vector2.Zero, SpriteEffects.None, 0.0f);
|
||||
|
||||
float noiseStrength = (float)PerlinNoise.CalculatePerlin(noiseT, noiseT, 0);
|
||||
float noiseScale = (float)PerlinNoise.CalculatePerlin(noiseT * 5.0f, noiseT * 2.0f, 0) * 4.0f;
|
||||
noiseSprite.DrawTiled(spriteBatch, Vector2.Zero, new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight),
|
||||
startOffset: new Point(Rand.Range(0, noiseSprite.SourceRect.Width), Rand.Range(0, noiseSprite.SourceRect.Height)),
|
||||
color: Color.White * noiseStrength * 0.1f,
|
||||
textureScale: Vector2.One * noiseScale);
|
||||
|
||||
titleSprite?.Draw(spriteBatch, new Vector2(GameMain.GraphicsWidth * 0.05f, GameMain.GraphicsHeight * 0.125f),
|
||||
Color.White, origin: new Vector2(0.0f, titleSprite.SourceRect.Height / 2.0f),
|
||||
scale: GameMain.GraphicsHeight / 2000.0f);
|
||||
|
||||
if (WaitForLanguageSelection)
|
||||
{
|
||||
DrawLanguageSelectionPrompt(spriteBatch, graphics);
|
||||
}
|
||||
else if (DrawLoadingText)
|
||||
{
|
||||
if (TextManager.Initialized)
|
||||
{
|
||||
string loadText;
|
||||
if (LoadState == 100.0f)
|
||||
{
|
||||
#if DEBUG
|
||||
if (GameMain.Config.AutomaticQuickStartEnabled && GameMain.FirstLoad)
|
||||
{
|
||||
loadText = "QUICKSTARTING ...";
|
||||
}
|
||||
else
|
||||
{
|
||||
#endif
|
||||
loadText = TextManager.Get("PressAnyKey");
|
||||
#if DEBUG
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
loadText = TextManager.Get("Loading");
|
||||
if (LoadState != null)
|
||||
{
|
||||
loadText += " " + (int)LoadState + " %";
|
||||
}
|
||||
}
|
||||
if (GUI.LargeFont != null)
|
||||
{
|
||||
GUI.LargeFont.DrawString(spriteBatch, loadText.ToUpper(),
|
||||
new Vector2(GameMain.GraphicsWidth / 2.0f - GUI.LargeFont.MeasureString(loadText.ToUpper()).X / 2.0f, GameMain.GraphicsHeight * 0.75f),
|
||||
Color.White);
|
||||
}
|
||||
}
|
||||
|
||||
if (GUI.Font != null && selectedTip != null)
|
||||
{
|
||||
string wrappedTip = ToolBox.WrapText(selectedTip, GameMain.GraphicsWidth * 0.5f, GUI.Font);
|
||||
string[] lines = wrappedTip.Split('\n');
|
||||
float lineHeight = GUI.Font.MeasureString(selectedTip).Y;
|
||||
|
||||
for (int i = 0; i < lines.Length; i++)
|
||||
{
|
||||
GUI.Font.DrawString(spriteBatch, lines[i],
|
||||
new Vector2((int)(GameMain.GraphicsWidth / 2.0f - GUI.Font.MeasureString(lines[i]).X / 2.0f), (int)(GameMain.GraphicsHeight * 0.8f + i * lineHeight)), Color.White);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
spriteBatch.End();
|
||||
|
||||
spriteBatch.Begin(blendState: BlendState.Additive);
|
||||
|
||||
Vector2 decorativeScale = new Vector2(GameMain.GraphicsHeight / 1080.0f);
|
||||
|
||||
float noiseVal = (float)PerlinNoise.CalculatePerlin(Timing.TotalTime * 0.25f, Timing.TotalTime * 0.5f, 0);
|
||||
decorativeGraph.Draw(spriteBatch, (int)(decorativeGraph.FrameCount * noiseVal),
|
||||
new Vector2(GameMain.GraphicsWidth * 0.001f, GameMain.GraphicsHeight * 0.24f),
|
||||
Color.White, Vector2.Zero, 0.0f, decorativeScale, SpriteEffects.FlipVertically);
|
||||
|
||||
decorativeMap.Draw(spriteBatch, (int)(decorativeMap.FrameCount * noiseVal),
|
||||
new Vector2(GameMain.GraphicsWidth * 0.99f, GameMain.GraphicsHeight * 0.66f),
|
||||
Color.White, decorativeMap.FrameSize.ToVector2(), 0.0f, decorativeScale);
|
||||
|
||||
if (noiseVal < 0.2f)
|
||||
{
|
||||
//SCP-CB reference
|
||||
randText = (new string[] { "NIL", "black white gray", "Sometimes we would have had time to scream", "e8m106]af", "NO" }).GetRandom();
|
||||
}
|
||||
else if (noiseVal < 0.3f)
|
||||
{
|
||||
randText = ToolBox.RandomSeed(9);
|
||||
}
|
||||
else if (noiseVal < 0.5f)
|
||||
{
|
||||
randText =
|
||||
Rand.Int(100).ToString().PadLeft(2, '0') + " " +
|
||||
Rand.Int(100).ToString().PadLeft(2, '0') + " " +
|
||||
Rand.Int(100).ToString().PadLeft(2, '0') + " " +
|
||||
Rand.Int(100).ToString().PadLeft(2, '0');
|
||||
}
|
||||
|
||||
GUI.LargeFont?.DrawString(spriteBatch, randText,
|
||||
new Vector2(GameMain.GraphicsWidth - decorativeMap.FrameSize.X * decorativeScale.X * 0.8f, GameMain.GraphicsHeight * 0.57f),
|
||||
Color.White * (1.0f - noiseVal));
|
||||
|
||||
spriteBatch.End();
|
||||
}
|
||||
|
||||
private void DrawLanguageSelectionPrompt(SpriteBatch spriteBatch, GraphicsDevice graphicsDevice)
|
||||
{
|
||||
if (languageSelectionFont == null)
|
||||
{
|
||||
languageSelectionFont = new ScalableFont("Content/Fonts/NotoSans/NotoSans-Bold.ttf",
|
||||
(uint)(30 * (GameMain.GraphicsHeight / 1080.0f)), graphicsDevice);
|
||||
}
|
||||
if (languageSelectionFontCJK == null)
|
||||
{
|
||||
languageSelectionFontCJK = new ScalableFont("Content/Fonts/NotoSans/NotoSansCJKsc-Bold.otf",
|
||||
(uint)(30 * (GameMain.GraphicsHeight / 1080.0f)), graphicsDevice, dynamicLoading: true);
|
||||
}
|
||||
if (languageSelectionCursor == null)
|
||||
{
|
||||
languageSelectionCursor = new Sprite("Content/UI/cursor.png", Vector2.Zero);
|
||||
}
|
||||
|
||||
Vector2 textPos = new Vector2(GameMain.GraphicsWidth / 2, GameMain.GraphicsHeight * 0.3f);
|
||||
Vector2 textSpacing = new Vector2(0.0f, (GameMain.GraphicsHeight * 0.5f) / TextManager.AvailableLanguages.Count());
|
||||
foreach (string language in TextManager.AvailableLanguages)
|
||||
{
|
||||
string localizedLanguageName = TextManager.GetTranslatedLanguageName(language);
|
||||
var font = TextManager.IsCJK(localizedLanguageName) ? languageSelectionFontCJK : languageSelectionFont;
|
||||
|
||||
Vector2 textSize = font.MeasureString(localizedLanguageName);
|
||||
bool hover =
|
||||
Math.Abs(PlayerInput.MousePosition.X - textPos.X) < textSize.X / 2 &&
|
||||
Math.Abs(PlayerInput.MousePosition.Y - textPos.Y) < textSpacing.Y / 2;
|
||||
|
||||
font.DrawString(spriteBatch, localizedLanguageName, textPos - textSize / 2,
|
||||
hover ? Color.White : Color.White * 0.6f);
|
||||
if (hover && PlayerInput.PrimaryMouseButtonClicked())
|
||||
{
|
||||
GameMain.Config.Language = language;
|
||||
//reload tip in the selected language
|
||||
selectedTip = TextManager.Get("LoadingScreenTip", true);
|
||||
GameMain.Config.SetDefaultBindings(legacy: false);
|
||||
GameMain.Config.CheckBindings(useDefaults: true);
|
||||
WaitForLanguageSelection = false;
|
||||
languageSelectionFont?.Dispose(); languageSelectionFont = null;
|
||||
languageSelectionFontCJK?.Dispose(); languageSelectionFontCJK = null;
|
||||
break;
|
||||
}
|
||||
|
||||
textPos += textSpacing;
|
||||
}
|
||||
|
||||
languageSelectionCursor.Draw(spriteBatch, PlayerInput.LatestMousePosition, scale: 0.5f);
|
||||
}
|
||||
|
||||
private void DrawSplashScreen(SpriteBatch spriteBatch, GraphicsDevice graphics)
|
||||
{
|
||||
if (currSplashScreen == null && PendingSplashScreens.Count == 0) { return; }
|
||||
|
||||
if (currSplashScreen == null)
|
||||
{
|
||||
var newSplashScreen = PendingSplashScreens.Dequeue();
|
||||
string fileName = newSplashScreen.Filename;
|
||||
try
|
||||
{
|
||||
currSplashScreen = Video.Load(graphics, GameMain.SoundManager, fileName);
|
||||
currSplashScreen.AudioGain = newSplashScreen.Gain;
|
||||
videoStartTime = DateTime.Now;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
GameMain.Config.EnableSplashScreen = false;
|
||||
DebugConsole.ThrowError("Playing the splash screen \"" + fileName + "\" failed.", e);
|
||||
PendingSplashScreens.Clear();
|
||||
currSplashScreen = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (currSplashScreen.IsPlaying)
|
||||
{
|
||||
spriteBatch.Begin();
|
||||
spriteBatch.Draw(currSplashScreen.GetTexture(), new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight), Color.White);
|
||||
spriteBatch.End();
|
||||
|
||||
if (DateTime.Now > videoStartTime + new TimeSpan(0, 0, 0, 0, milliseconds: 500) && GameMain.WindowActive && (PlayerInput.KeyHit(Keys.Space) || PlayerInput.KeyHit(Keys.Enter) || PlayerInput.PrimaryMouseButtonDown()))
|
||||
{
|
||||
currSplashScreen.Dispose(); currSplashScreen = null;
|
||||
}
|
||||
}
|
||||
else if (DateTime.Now > videoStartTime + new TimeSpan(0, 0, 0, 0, milliseconds: 1500))
|
||||
{
|
||||
currSplashScreen.Dispose(); currSplashScreen = null;
|
||||
}
|
||||
}
|
||||
|
||||
bool drawn;
|
||||
public IEnumerable<object> DoLoading(IEnumerable<object> loader)
|
||||
{
|
||||
drawn = false;
|
||||
LoadState = null;
|
||||
selectedTip = TextManager.Get("LoadingScreenTip", true);
|
||||
currentBackgroundTexture = LocationType.List.GetRandom()?.GetPortrait(Rand.Int(int.MaxValue))?.Texture;
|
||||
|
||||
while (!drawn)
|
||||
{
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
CoroutineManager.StartCoroutine(loader);
|
||||
|
||||
yield return CoroutineStatus.Running;
|
||||
|
||||
while (CoroutineManager.IsCoroutineRunning(loader.ToString()))
|
||||
{
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
LoadState = 100.0f;
|
||||
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class ParamsEditor
|
||||
{
|
||||
private static ParamsEditor _instance;
|
||||
public static ParamsEditor Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
_instance = new ParamsEditor();
|
||||
}
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
public GUIComponent Parent { get; private set; }
|
||||
public GUIListBox EditorBox { get; private set; }
|
||||
/// <summary>
|
||||
/// Uses Linq queries. Don't use too frequently or reimplement.
|
||||
/// </summary>
|
||||
public IEnumerable<SerializableEntityEditor> FindEntityEditors() => EditorBox.Content.RectTransform.Children
|
||||
.Select(c => c.GUIComponent as SerializableEntityEditor)
|
||||
.Where(c => c != null);
|
||||
|
||||
public GUIListBox CreateEditorBox(RectTransform rectT = null)
|
||||
{
|
||||
rectT = rectT ?? new RectTransform(new Vector2(0.25f, 1f), GUI.Canvas) { MinSize = new Point(340, GameMain.GraphicsHeight) };
|
||||
rectT.SetPosition(Anchor.TopRight);
|
||||
Parent = new GUIFrame(rectT, null, Color);
|
||||
EditorBox = new GUIListBox(new RectTransform(Vector2.One * 0.98f, rectT, Anchor.Center), color: Color.Black, style: null)
|
||||
{
|
||||
Spacing = 10,
|
||||
AutoHideScrollBar = true,
|
||||
KeepSpaceForScrollBar = true
|
||||
};
|
||||
return EditorBox;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
EditorBox.ClearChildren();
|
||||
}
|
||||
|
||||
public ParamsEditor(RectTransform rectT = null)
|
||||
{
|
||||
EditorBox = CreateEditorBox();
|
||||
}
|
||||
|
||||
public static Color Color = new Color(20, 20, 20, 255);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,851 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Barotrauma.Extensions;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public enum Anchor
|
||||
{
|
||||
TopLeft, TopCenter, TopRight,
|
||||
CenterLeft, Center, CenterRight,
|
||||
BottomLeft, BottomCenter, BottomRight
|
||||
}
|
||||
|
||||
public enum Pivot
|
||||
{
|
||||
TopLeft, TopCenter, TopRight,
|
||||
CenterLeft, Center, CenterRight,
|
||||
BottomLeft, BottomCenter, BottomRight
|
||||
}
|
||||
|
||||
public enum ScaleBasis
|
||||
{
|
||||
Normal,
|
||||
BothWidth, BothHeight,
|
||||
Smallest, Largest
|
||||
}
|
||||
|
||||
public class RectTransform
|
||||
{
|
||||
#region Fields and Properties
|
||||
/// <summary>
|
||||
/// Should be assigned only by GUIComponent.
|
||||
/// Note that RectTransform is created first and the GUIComponent after that.
|
||||
/// This means the GUIComponent is not set before the GUIComponent is initialized.
|
||||
/// </summary>
|
||||
public GUIComponent GUIComponent { get; set; }
|
||||
|
||||
private RectTransform parent;
|
||||
public RectTransform Parent
|
||||
{
|
||||
get { return parent; }
|
||||
set
|
||||
{
|
||||
if (parent == value || value == this) { return; }
|
||||
// Remove the child from the old parent
|
||||
RemoveFromHierarchy(displayErrors: false);
|
||||
parent = value;
|
||||
if (parent != null && !parent.children.Contains(this))
|
||||
{
|
||||
parent.children.Add(this);
|
||||
RecalculateAll(false, true, true);
|
||||
Parent.ChildrenChanged?.Invoke(this);
|
||||
}
|
||||
ParentChanged?.Invoke(parent);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<RectTransform> children = new List<RectTransform>();
|
||||
public IEnumerable<RectTransform> Children => children;
|
||||
|
||||
public int CountChildren => children.Count;
|
||||
|
||||
private Vector2 relativeSize = Vector2.One;
|
||||
/// <summary>
|
||||
/// Relative to the parent rect.
|
||||
/// </summary>
|
||||
public Vector2 RelativeSize
|
||||
{
|
||||
get { return relativeSize; }
|
||||
set
|
||||
{
|
||||
if (relativeSize.NearlyEquals(value)) { return; }
|
||||
relativeSize = value;
|
||||
RecalculateAll(resize: true, scale: false, withChildren: true);
|
||||
}
|
||||
}
|
||||
|
||||
private Point? minSize;
|
||||
/// <summary>
|
||||
/// Min size in pixels.
|
||||
/// Does not affect scaling.
|
||||
/// </summary>
|
||||
public Point MinSize
|
||||
{
|
||||
get { return minSize ?? Point.Zero; }
|
||||
set
|
||||
{
|
||||
if (minSize == value) { return; }
|
||||
minSize = value;
|
||||
RecalculateAll(true, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
private static Point maxPoint = new Point(int.MaxValue, int.MaxValue);
|
||||
private Point? maxSize;
|
||||
|
||||
/// <summary>
|
||||
/// Max size in pixels.
|
||||
/// Does not affect scaling.
|
||||
/// </summary>
|
||||
public Point MaxSize
|
||||
{
|
||||
get { return maxSize ?? maxPoint; }
|
||||
set
|
||||
{
|
||||
if (maxSize == value) { return; }
|
||||
maxSize = value;
|
||||
RecalculateAll(true, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
private Point nonScaledSize;
|
||||
/// <summary>
|
||||
/// Size before scale multiplications.
|
||||
/// </summary>
|
||||
public Point NonScaledSize
|
||||
{
|
||||
get { return nonScaledSize; }
|
||||
set
|
||||
{
|
||||
if (nonScaledSize == value) { return; }
|
||||
nonScaledSize = value.Clamp(MinSize, MaxSize);
|
||||
RecalculateRelativeSize();
|
||||
RecalculateAnchorPoint();
|
||||
RecalculatePivotOffset();
|
||||
RecalculateChildren(resize: true, scale: false);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Size after scale multiplications.
|
||||
/// </summary>
|
||||
public Point ScaledSize => NonScaledSize.Multiply(Scale);
|
||||
|
||||
/// <summary>
|
||||
/// Applied to all RectTransforms.
|
||||
/// The elements are not automatically resized, if the global scale changes.
|
||||
/// You have to manually call RecalculateScale() for all elements after changing the global scale.
|
||||
/// This is because there is currently no easy way to inform all the elements without having a reference to them.
|
||||
/// Having a reference (static list, or event) is problematic, because deconstructing the elements is not handled manually.
|
||||
/// This means that the uncleared references would bloat the memory.
|
||||
/// We could recalculate the scale each time it's needed,
|
||||
/// but in that case the calculation would need to be very lightweight and garbage free, which it currently is not.
|
||||
/// </summary>
|
||||
public static Vector2 globalScale = Vector2.One;
|
||||
|
||||
private Vector2 localScale = Vector2.One;
|
||||
public Vector2 LocalScale
|
||||
{
|
||||
get { return localScale; }
|
||||
set
|
||||
{
|
||||
if (localScale.NearlyEquals(value)) { return; }
|
||||
localScale = value;
|
||||
RecalculateAll(resize: false, scale: true, withChildren: true);
|
||||
ScaleChanged?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2 Scale { get; private set; }
|
||||
|
||||
private Vector2 relativeOffset = Vector2.Zero;
|
||||
private Point absoluteOffset = Point.Zero;
|
||||
private Point screenSpaceOffset = Point.Zero;
|
||||
/// <summary>
|
||||
/// Defined as portions of the parent size.
|
||||
/// Also the direction of the offset is relative, calculated away from the anchor point.
|
||||
/// </summary>
|
||||
public Vector2 RelativeOffset
|
||||
{
|
||||
get { return relativeOffset; }
|
||||
set
|
||||
{
|
||||
if (relativeOffset.NearlyEquals(value)) { return; }
|
||||
relativeOffset = value;
|
||||
RecalculateChildren(false, false);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Absolute in pixels but relative to the anchor point.
|
||||
/// Calculated away from the anchor point, like a padding.
|
||||
/// Use RelativeOffset to set an amount relative to the parent size.
|
||||
/// </summary>
|
||||
public Point AbsoluteOffset
|
||||
{
|
||||
get { return absoluteOffset; }
|
||||
set
|
||||
{
|
||||
if (absoluteOffset == value) { return; }
|
||||
absoluteOffset = value;
|
||||
recalculateRect = true;
|
||||
RecalculateChildren(false, false);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Screen space offset. From top left corner. In pixels.
|
||||
/// </summary>
|
||||
public Point ScreenSpaceOffset
|
||||
{
|
||||
get { return screenSpaceOffset; }
|
||||
set
|
||||
{
|
||||
if (screenSpaceOffset == value) { return; }
|
||||
screenSpaceOffset = value;
|
||||
recalculateRect = true;
|
||||
RecalculateChildren(false, false);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Calculated from the selected pivot. In pixels.
|
||||
/// </summary>
|
||||
public Point PivotOffset { get; private set; }
|
||||
/// <summary>
|
||||
/// Screen space point in pixels.
|
||||
/// </summary>
|
||||
public Point AnchorPoint { get; private set; }
|
||||
|
||||
public Point TopLeft
|
||||
{
|
||||
get
|
||||
{
|
||||
Point absoluteOffset = ConvertOffsetRelativeToAnchor(AbsoluteOffset, Anchor);
|
||||
Point relativeOffset = ParentRect.MultiplySize(RelativeOffset);
|
||||
relativeOffset = ConvertOffsetRelativeToAnchor(relativeOffset, Anchor);
|
||||
return AnchorPoint + PivotOffset + absoluteOffset + relativeOffset + ScreenSpaceOffset;
|
||||
}
|
||||
}
|
||||
|
||||
protected Point NonScaledTopLeft
|
||||
{
|
||||
get
|
||||
{
|
||||
Point absoluteOffset = ConvertOffsetRelativeToAnchor(AbsoluteOffset, Anchor);
|
||||
Point relativeOffset = NonScaledParentRect.MultiplySize(RelativeOffset);
|
||||
relativeOffset = ConvertOffsetRelativeToAnchor(relativeOffset, Anchor);
|
||||
return AnchorPoint + PivotOffset + absoluteOffset + relativeOffset + ScreenSpaceOffset;
|
||||
}
|
||||
}
|
||||
|
||||
private bool recalculateRect = true;
|
||||
private Rectangle _rect;
|
||||
public Rectangle Rect
|
||||
{
|
||||
get
|
||||
{
|
||||
if (recalculateRect)
|
||||
{
|
||||
_rect = new Rectangle(TopLeft, ScaledSize);
|
||||
recalculateRect = false;
|
||||
}
|
||||
return _rect;
|
||||
}
|
||||
}
|
||||
public Rectangle ParentRect => Parent != null ? Parent.Rect : ScreenRect;
|
||||
|
||||
protected Rectangle NonScaledRect => new Rectangle(NonScaledTopLeft, NonScaledSize);
|
||||
protected Rectangle NonScaledParentRect => parent != null ? Parent.NonScaledRect : ScreenRect;
|
||||
protected Rectangle ScreenRect => new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight);
|
||||
|
||||
private Pivot pivot;
|
||||
/// <summary>
|
||||
/// Does not automatically calculate children.
|
||||
/// Note also that if you change the pivot point with this property, the pivot does not automatically match the anchor.
|
||||
/// You can use SetPosition to change everything automatcally or MatchPivotToAnchor to match the pivot to anchor.
|
||||
/// </summary>
|
||||
public Pivot Pivot
|
||||
{
|
||||
get { return pivot; }
|
||||
set
|
||||
{
|
||||
if (pivot == value) { return; }
|
||||
pivot = value;
|
||||
RecalculatePivotOffset();
|
||||
}
|
||||
}
|
||||
|
||||
private Anchor anchor;
|
||||
/// <summary>
|
||||
/// Does not automatically calculate children.
|
||||
/// Note also that if you change the anchor point with this property, the pivot does not automatically match the anchor.
|
||||
/// You can use SetPosition to change everything automatically or MatchPivotToAnchor to match the pivot to anchor.
|
||||
/// </summary>
|
||||
public Anchor Anchor
|
||||
{
|
||||
get { return anchor; }
|
||||
set
|
||||
{
|
||||
if (anchor == value) { return; }
|
||||
anchor = value;
|
||||
RecalculateAnchorPoint();
|
||||
}
|
||||
}
|
||||
|
||||
private ScaleBasis _scaleBasis;
|
||||
public ScaleBasis ScaleBasis
|
||||
{
|
||||
get { return _scaleBasis; }
|
||||
set
|
||||
{
|
||||
_scaleBasis = value;
|
||||
RecalculateAbsoluteSize();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsLastChild
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Parent == null) { return false; }
|
||||
var last = Parent.Children.LastOrDefault();
|
||||
if (last == null) { return false; }
|
||||
return last == this;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsFirstChild
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Parent == null) { return false; }
|
||||
var first = Parent.Children.FirstOrDefault();
|
||||
if (first == null) { return false; }
|
||||
return first == this;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
public event Action<RectTransform> ParentChanged;
|
||||
/// <summary>
|
||||
/// The element provided as the argument is the changed child. It may be new in the hierarchy or just repositioned.
|
||||
/// </summary>
|
||||
public event Action<RectTransform> ChildrenChanged;
|
||||
public event Action ScaleChanged;
|
||||
public event Action SizeChanged;
|
||||
#endregion
|
||||
|
||||
#region Initialization
|
||||
public RectTransform(Vector2 relativeSize, RectTransform parent, Anchor anchor = Anchor.TopLeft, Pivot? pivot = null, Point? minSize = null, Point? maxSize = null, ScaleBasis scaleBasis = ScaleBasis.Normal)
|
||||
{
|
||||
Init(parent, anchor, pivot);
|
||||
_scaleBasis = scaleBasis;
|
||||
this.relativeSize = relativeSize;
|
||||
this.minSize = minSize;
|
||||
this.maxSize = maxSize;
|
||||
RecalculateScale();
|
||||
RecalculateAbsoluteSize();
|
||||
RecalculateAnchorPoint();
|
||||
RecalculatePivotOffset();
|
||||
parent?.ChildrenChanged?.Invoke(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// By default, elements defined with an absolute size (in pixels) will scale with the parent.
|
||||
/// This can be changed by setting IsFixedSize to true.
|
||||
/// </summary>
|
||||
public RectTransform(Point absoluteSize, RectTransform parent = null, Anchor anchor = Anchor.TopLeft, Pivot? pivot = null, ScaleBasis scaleBasis = ScaleBasis.Normal, bool isFixedSize = false)
|
||||
{
|
||||
Init(parent, anchor, pivot);
|
||||
_scaleBasis = scaleBasis;
|
||||
this.nonScaledSize = absoluteSize;
|
||||
RecalculateScale();
|
||||
RecalculateRelativeSize();
|
||||
if (scaleBasis != ScaleBasis.Normal)
|
||||
{
|
||||
RecalculateAbsoluteSize();
|
||||
}
|
||||
RecalculateAnchorPoint();
|
||||
RecalculatePivotOffset();
|
||||
IsFixedSize = isFixedSize;
|
||||
parent?.ChildrenChanged?.Invoke(this);
|
||||
}
|
||||
|
||||
public static RectTransform Load(XElement element, RectTransform parent, Anchor defaultAnchor = Anchor.TopLeft)
|
||||
{
|
||||
Enum.TryParse(element.GetAttributeString("anchor", defaultAnchor.ToString()), out Anchor anchor);
|
||||
Enum.TryParse(element.GetAttributeString("pivot", anchor.ToString()), out Pivot pivot);
|
||||
|
||||
Point? minSize = null, maxSize = null;
|
||||
ScaleBasis scaleBasis = ScaleBasis.Normal;
|
||||
if (element.Attribute("minsize") != null)
|
||||
{
|
||||
minSize = element.GetAttributePoint("minsize", Point.Zero);
|
||||
}
|
||||
if (element.Attribute("maxsize") != null)
|
||||
{
|
||||
maxSize = element.GetAttributePoint("maxsize", new Point(1000, 1000));
|
||||
}
|
||||
string sb = element.GetAttributeString("scalebasis", null);
|
||||
if (sb != null)
|
||||
{
|
||||
Enum.TryParse(sb, ignoreCase: true, out scaleBasis);
|
||||
}
|
||||
RectTransform rectTransform;
|
||||
if (element.Attribute("absolutesize") != null)
|
||||
{
|
||||
rectTransform = new RectTransform(element.GetAttributePoint("absolutesize", new Point(1000, 1000)), parent, anchor, pivot, scaleBasis)
|
||||
{
|
||||
minSize = minSize,
|
||||
maxSize = maxSize
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
rectTransform = new RectTransform(element.GetAttributeVector2("relativesize", Vector2.One), parent, anchor, pivot, minSize, maxSize, scaleBasis);
|
||||
}
|
||||
rectTransform.RelativeOffset = element.GetAttributeVector2("relativeoffset", Vector2.Zero);
|
||||
rectTransform.AbsoluteOffset = element.GetAttributePoint("absoluteoffset", Point.Zero);
|
||||
return rectTransform;
|
||||
}
|
||||
|
||||
private void Init(RectTransform parent = null, Anchor anchor = Anchor.TopLeft, Pivot? pivot = null)
|
||||
{
|
||||
this.parent = parent;
|
||||
parent?.children.Add(this);
|
||||
Anchor = anchor;
|
||||
Pivot = pivot ?? MatchPivotToAnchor(Anchor);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Protected methods
|
||||
protected void RecalculateScale()
|
||||
{
|
||||
var scale = LocalScale * globalScale;
|
||||
var parents = GetParents();
|
||||
Scale = parents.Any() ? parents.Select(rt => rt.LocalScale).Aggregate((parent, child) => parent * child) * scale : scale;
|
||||
recalculateRect = true;
|
||||
ScaleChanged?.Invoke();
|
||||
}
|
||||
|
||||
protected void RecalculatePivotOffset()
|
||||
{
|
||||
PivotOffset = CalculatePivotOffset(Pivot, ScaledSize);
|
||||
recalculateRect = true;
|
||||
}
|
||||
|
||||
protected void RecalculateAnchorPoint()
|
||||
{
|
||||
AnchorPoint = CalculateAnchorPoint(Anchor, ParentRect);
|
||||
recalculateRect = true;
|
||||
}
|
||||
|
||||
protected void RecalculateRelativeSize()
|
||||
{
|
||||
relativeSize = new Vector2(NonScaledSize.X, NonScaledSize.Y) / new Vector2(NonScaledParentRect.Width, NonScaledParentRect.Height);
|
||||
recalculateRect = true;
|
||||
SizeChanged?.Invoke();
|
||||
}
|
||||
|
||||
protected void RecalculateAbsoluteSize()
|
||||
{
|
||||
Point size = NonScaledParentRect.Size;
|
||||
switch (ScaleBasis)
|
||||
{
|
||||
case ScaleBasis.BothWidth:
|
||||
size.Y = size.X;
|
||||
break;
|
||||
case ScaleBasis.BothHeight:
|
||||
size.X = size.Y;
|
||||
break;
|
||||
case ScaleBasis.Smallest:
|
||||
if (size.X < size.Y)
|
||||
{
|
||||
size.Y = size.X;
|
||||
}
|
||||
else
|
||||
{
|
||||
size.X = size.Y;
|
||||
}
|
||||
break;
|
||||
case ScaleBasis.Largest:
|
||||
if (size.X > size.Y)
|
||||
{
|
||||
size.Y = size.X;
|
||||
}
|
||||
else
|
||||
{
|
||||
size.X = size.Y;
|
||||
}
|
||||
break;
|
||||
}
|
||||
size = size.Multiply(RelativeSize);
|
||||
nonScaledSize = size.Clamp(MinSize, MaxSize);
|
||||
recalculateRect = true;
|
||||
SizeChanged?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If false, the element will resize if the parent is resized (with the children).
|
||||
/// If true, the element will resize only when explicitly resized.
|
||||
/// Note that scaling always affects the elements.
|
||||
/// </summary>
|
||||
public bool IsFixedSize { get; set; }
|
||||
|
||||
protected void RecalculateAll(bool resize, bool scale = true, bool withChildren = true)
|
||||
{
|
||||
if (scale)
|
||||
{
|
||||
RecalculateScale();
|
||||
}
|
||||
if (resize && !IsFixedSize)
|
||||
{
|
||||
RecalculateAbsoluteSize();
|
||||
}
|
||||
RecalculateAnchorPoint();
|
||||
RecalculatePivotOffset();
|
||||
if (withChildren)
|
||||
{
|
||||
RecalculateChildren(resize, scale);
|
||||
}
|
||||
}
|
||||
|
||||
private bool RemoveFromHierarchy(bool displayErrors = true)
|
||||
{
|
||||
if (Parent == null)
|
||||
{
|
||||
if (displayErrors)
|
||||
{
|
||||
DebugConsole.ThrowError("Parent null" + Environment.StackTrace);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (!Parent.children.Contains(this))
|
||||
{
|
||||
if (displayErrors)
|
||||
{
|
||||
DebugConsole.ThrowError("The children of the parent does not contain this child. This should not be possible! " + Environment.StackTrace);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (!Parent.children.Remove(this))
|
||||
{
|
||||
if (displayErrors)
|
||||
{
|
||||
DebugConsole.ThrowError("Unable to remove the child from the parent. " + Environment.StackTrace);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Public instance methods
|
||||
public void SetPosition(Anchor anchor, Pivot? pivot = null)
|
||||
{
|
||||
Anchor = anchor;
|
||||
Pivot = pivot ?? MatchPivotToAnchor(anchor);
|
||||
ScreenSpaceOffset = Point.Zero;
|
||||
recalculateRect = true;
|
||||
RecalculateChildren(false, false);
|
||||
}
|
||||
|
||||
public void Resize(Point absoluteSize, bool resizeChildren = true)
|
||||
{
|
||||
nonScaledSize = absoluteSize.Clamp(MinSize, MaxSize);
|
||||
RecalculateRelativeSize();
|
||||
RecalculateAll(resize: false, scale: false, withChildren: false);
|
||||
RecalculateChildren(resizeChildren, false);
|
||||
}
|
||||
|
||||
public void Resize(Vector2 relativeSize, bool resizeChildren = true)
|
||||
{
|
||||
this.relativeSize = relativeSize;
|
||||
RecalculateAll(resize: true, scale: false, withChildren: false);
|
||||
RecalculateChildren(resizeChildren, false);
|
||||
}
|
||||
|
||||
public void ChangeScale(Vector2 newScale)
|
||||
{
|
||||
LocalScale = newScale;
|
||||
}
|
||||
|
||||
public void ResetScale()
|
||||
{
|
||||
ChangeScale(Vector2.One);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Currently this needs to be manually called only when the global scale changes.
|
||||
/// If the local scale changes, the scale is automatically recalculated.
|
||||
/// </summary>
|
||||
public void RecalculateScale(bool withChildren)
|
||||
{
|
||||
RecalculateScale();
|
||||
if (withChildren)
|
||||
{
|
||||
RecalculateChildren(resize: false, scale: true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Manipulates ScreenSpaceOffset.
|
||||
/// If you want to manipulate some other offset, access the property setters directly.
|
||||
/// </summary>
|
||||
public void Translate(Point translation)
|
||||
{
|
||||
ScreenSpaceOffset += translation;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all parent elements in the hierarchy.
|
||||
/// </summary>
|
||||
public IEnumerable<RectTransform> GetParents()
|
||||
{
|
||||
var parents = new List<RectTransform>();
|
||||
if (Parent != null)
|
||||
{
|
||||
parents.Add(Parent);
|
||||
return parents.Concat(Parent.GetParents());
|
||||
}
|
||||
else
|
||||
{
|
||||
return parents;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all child elements in the hierarchy.
|
||||
/// </summary>
|
||||
public IEnumerable<RectTransform> GetAllChildren()
|
||||
{
|
||||
return children.Concat(children.SelectManyRecursive(c => c.children));
|
||||
}
|
||||
|
||||
public int GetChildIndex(RectTransform rectT)
|
||||
{
|
||||
return children.IndexOf(rectT);
|
||||
}
|
||||
|
||||
public RectTransform GetChild(int index)
|
||||
{
|
||||
return children[index];
|
||||
}
|
||||
|
||||
public bool IsParentOf(RectTransform rectT, bool recursive = true)
|
||||
{
|
||||
return children.Contains(rectT) || (recursive && children.Any(c => c.IsParentOf(rectT)));
|
||||
}
|
||||
|
||||
public void ClearChildren()
|
||||
{
|
||||
children.ForEachMod(c => c.Parent = null);
|
||||
}
|
||||
|
||||
public void SortChildren(Comparison<RectTransform> comparison)
|
||||
{
|
||||
children.Sort(comparison);
|
||||
RecalculateAll(false, false, true);
|
||||
Parent.ChildrenChanged?.Invoke(this);
|
||||
}
|
||||
|
||||
public void SetAsLastChild()
|
||||
{
|
||||
if (IsLastChild) { return; }
|
||||
if (!RemoveFromHierarchy(displayErrors: true)) { return; }
|
||||
parent.children.Add(this);
|
||||
RecalculateAll(false, true, true);
|
||||
parent.ChildrenChanged?.Invoke(this);
|
||||
}
|
||||
|
||||
public void SetAsFirstChild()
|
||||
{
|
||||
if (IsFirstChild) { return; }
|
||||
RepositionChildInHierarchy(0);
|
||||
}
|
||||
|
||||
public bool RepositionChildInHierarchy(int index)
|
||||
{
|
||||
if (!RemoveFromHierarchy(displayErrors: true)) { return false; }
|
||||
try
|
||||
{
|
||||
Parent.children.Insert(index, this);
|
||||
}
|
||||
catch (ArgumentOutOfRangeException e)
|
||||
{
|
||||
DebugConsole.ThrowError(e.ToString());
|
||||
return false;
|
||||
}
|
||||
RecalculateAll(false, true, true);
|
||||
Parent.ChildrenChanged?.Invoke(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void RecalculateChildren(bool resize, bool scale = true)
|
||||
{
|
||||
for (int i = 0; i < children.Count; i++)
|
||||
{
|
||||
children[i].RecalculateAll(resize, scale, withChildren: true);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddChildrenToGUIUpdateList(bool ignoreChildren = false, int order = 0)
|
||||
{
|
||||
for (int i = 0; i < children.Count; i++)
|
||||
{
|
||||
children[i].GUIComponent.AddToGUIUpdateList(ignoreChildren, order);
|
||||
}
|
||||
}
|
||||
|
||||
public void MatchPivotToAnchor() => MatchPivotToAnchor(Anchor);
|
||||
|
||||
|
||||
private Point? animTargetPos;
|
||||
public Point AnimTargetPos
|
||||
{
|
||||
get { return animTargetPos ?? AbsoluteOffset; }
|
||||
}
|
||||
|
||||
public void MoveOverTime(Point targetPos, float duration)
|
||||
{
|
||||
animTargetPos = targetPos;
|
||||
CoroutineManager.StartCoroutine(DoMoveAnimation(targetPos, duration));
|
||||
}
|
||||
public void ScaleOverTime(Point targetSize, float duration)
|
||||
{
|
||||
CoroutineManager.StartCoroutine(DoScaleAnimation(targetSize, duration));
|
||||
}
|
||||
|
||||
private IEnumerable<object> DoMoveAnimation(Point targetPos, float duration)
|
||||
{
|
||||
Vector2 startPos = AbsoluteOffset.ToVector2();
|
||||
float t = 0.0f;
|
||||
while (t < duration && duration > 0.0f)
|
||||
{
|
||||
t += CoroutineManager.DeltaTime;
|
||||
AbsoluteOffset = Vector2.SmoothStep(startPos, targetPos.ToVector2(), t / duration).ToPoint();
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
AbsoluteOffset = targetPos;
|
||||
animTargetPos = null;
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
private IEnumerable<object> DoScaleAnimation(Point targetSize, float duration)
|
||||
{
|
||||
Vector2 startSize = NonScaledSize.ToVector2();
|
||||
float t = 0.0f;
|
||||
while (t < duration && duration > 0.0f)
|
||||
{
|
||||
t += CoroutineManager.DeltaTime;
|
||||
NonScaledSize = Vector2.SmoothStep(startSize, targetSize.ToVector2(), t / duration).ToPoint();
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
NonScaledSize = targetSize;
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Static methods
|
||||
public static Pivot MatchPivotToAnchor(Anchor anchor)
|
||||
{
|
||||
if (!Enum.TryParse(anchor.ToString(), out Pivot pivot))
|
||||
{
|
||||
throw new Exception($"[RectTransform] Cannot match pivot to anchor {anchor}");
|
||||
}
|
||||
return pivot;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the offset so that the direction is always away from the anchor point.
|
||||
/// </summary>
|
||||
public static Point ConvertOffsetRelativeToAnchor(Point offset, Anchor anchor)
|
||||
{
|
||||
switch (anchor)
|
||||
{
|
||||
case Anchor.BottomRight:
|
||||
return offset.Inverse();
|
||||
case Anchor.BottomLeft:
|
||||
case Anchor.BottomCenter:
|
||||
return new Point(offset.X, -offset.Y);
|
||||
case Anchor.TopRight:
|
||||
case Anchor.CenterRight:
|
||||
return new Point(-offset.X, offset.Y);
|
||||
default:
|
||||
return offset;
|
||||
}
|
||||
}
|
||||
|
||||
public static Point CalculatePivotOffset(Pivot pivot, Point size)
|
||||
{
|
||||
int width = size.X;
|
||||
int height = size.Y;
|
||||
switch (pivot)
|
||||
{
|
||||
case Pivot.TopLeft:
|
||||
return Point.Zero;
|
||||
case Pivot.TopCenter:
|
||||
return new Point(-width / 2, 0);
|
||||
case Pivot.TopRight:
|
||||
return new Point(-width, 0);
|
||||
case Pivot.CenterLeft:
|
||||
return new Point(0, -height / 2);
|
||||
case Pivot.Center:
|
||||
return size.Divide(2).Inverse();
|
||||
case Pivot.CenterRight:
|
||||
return new Point(-width, -height / 2);
|
||||
case Pivot.BottomLeft:
|
||||
return new Point(0, -height);
|
||||
case Pivot.BottomCenter:
|
||||
return new Point(-width / 2, -height);
|
||||
case Pivot.BottomRight:
|
||||
return new Point(-width, -height);
|
||||
default:
|
||||
throw new NotImplementedException(pivot.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public static Point CalculateAnchorPoint(Anchor anchor, Rectangle parent)
|
||||
{
|
||||
switch (anchor)
|
||||
{
|
||||
case Anchor.TopLeft:
|
||||
return parent.Location;
|
||||
case Anchor.TopCenter:
|
||||
return new Point(parent.Center.X, parent.Top);
|
||||
case Anchor.TopRight:
|
||||
return new Point(parent.Right, parent.Top);
|
||||
case Anchor.CenterLeft:
|
||||
return new Point(parent.Left, parent.Center.Y);
|
||||
case Anchor.Center:
|
||||
return parent.Center;
|
||||
case Anchor.CenterRight:
|
||||
return new Point(parent.Right, parent.Center.Y);
|
||||
case Anchor.BottomLeft:
|
||||
return new Point(parent.Left, parent.Bottom);
|
||||
case Anchor.BottomCenter:
|
||||
return new Point(parent.Center.X, parent.Bottom);
|
||||
case Anchor.BottomRight:
|
||||
return new Point(parent.Right, parent.Bottom);
|
||||
default:
|
||||
throw new NotImplementedException(anchor.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The elements are not automatically resized, if the global scale changes.
|
||||
/// You have to manually call RecalculateScale() for all elements after changing the global scale.
|
||||
/// This is because there is currently no easy way to inform all the elements without having a reference to them.
|
||||
/// Having a reference (static list, or event) is problematic, because deconstructing the elements is not handled manually.
|
||||
/// This means that the uncleared references would bloat the memory.
|
||||
/// We could recalculate the scale each time it's needed,
|
||||
/// but in that case the calculation would need to be very lightweight and garbage free, which it currently is not.
|
||||
/// </summary>
|
||||
public static void ResetGlobalScale()
|
||||
{
|
||||
globalScale = Vector2.One;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,327 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
/// <summary>
|
||||
/// Sprite batch extensions for drawing primitive shapes
|
||||
/// Modified from: https://github.com/craftworkgames/MonoGame.Extended/blob/develop/Source/MonoGame.Extended/ShapeExtensions.cs
|
||||
/// </summary>
|
||||
public static class ShapeExtensions
|
||||
{
|
||||
private static Texture2D _whitePixelTexture;
|
||||
|
||||
private static Texture2D GetTexture(SpriteBatch spriteBatch)
|
||||
{
|
||||
if (_whitePixelTexture == null)
|
||||
{
|
||||
CrossThread.RequestExecutionOnMainThread(() =>
|
||||
{
|
||||
_whitePixelTexture = new Texture2D(spriteBatch.GraphicsDevice, 1, 1, false, SurfaceFormat.Color);
|
||||
_whitePixelTexture.SetData(new[] { Color.White });
|
||||
});
|
||||
}
|
||||
|
||||
return _whitePixelTexture;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws a closed polygon from a <see cref="Polygon" /> shape
|
||||
/// </summary>
|
||||
public static void DrawPolygon(this SpriteBatch spriteBatch, Vector2 position, Polygon polygon, Color color,
|
||||
float thickness = 1f)
|
||||
{
|
||||
DrawPolygon(spriteBatch, position, polygon.Vertices, color, thickness);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws a closed polygon from an array of points
|
||||
/// </summary>
|
||||
public static void DrawPolygon(this SpriteBatch spriteBatch, Vector2 offset, IReadOnlyList<Vector2> points, Color color,
|
||||
float thickness = 1f)
|
||||
{
|
||||
if (points.Count == 0)
|
||||
return;
|
||||
|
||||
if (points.Count == 1)
|
||||
{
|
||||
DrawPoint(spriteBatch, points[0], color, (int)thickness);
|
||||
return;
|
||||
}
|
||||
|
||||
var texture = GetTexture(spriteBatch);
|
||||
|
||||
for (var i = 0; i < points.Count - 1; i++)
|
||||
DrawPolygonEdge(spriteBatch, texture, points[i] + offset, points[i + 1] + offset, color, thickness);
|
||||
|
||||
DrawPolygonEdge(spriteBatch, texture, points[points.Count - 1] + offset, points[0] + offset, color,
|
||||
thickness);
|
||||
}
|
||||
|
||||
private static void DrawPolygonEdge(SpriteBatch spriteBatch, Texture2D texture, Vector2 point1, Vector2 point2,
|
||||
Color color, float thickness)
|
||||
{
|
||||
var length = Vector2.Distance(point1, point2);
|
||||
var angle = (float)Math.Atan2(point2.Y - point1.Y, point2.X - point1.X);
|
||||
var scale = new Vector2(length, thickness);
|
||||
spriteBatch.Draw(GetTexture(spriteBatch), point1, null, color, angle, Vector2.Zero, scale, SpriteEffects.None, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws a line from point1 to point2 with an offset
|
||||
/// </summary>
|
||||
public static void DrawLine(this SpriteBatch spriteBatch, float x1, float y1, float x2, float y2, Color color,
|
||||
float thickness = 1f)
|
||||
{
|
||||
DrawLine(spriteBatch, new Vector2(x1, y1), new Vector2(x2, y2), color, thickness);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws a line from point1 to point2 with an offset
|
||||
/// </summary>
|
||||
public static void DrawLine(this SpriteBatch spriteBatch, Vector2 point1, Vector2 point2, Color color,
|
||||
float thickness = 1f)
|
||||
{
|
||||
// calculate the distance between the two vectors
|
||||
var distance = Vector2.Distance(point1, point2);
|
||||
|
||||
// calculate the angle between the two vectors
|
||||
var angle = (float)Math.Atan2(point2.Y - point1.Y, point2.X - point1.X);
|
||||
|
||||
DrawLine(spriteBatch, point1, distance, angle, color, thickness);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws a line from point1 to point2 with an offset
|
||||
/// </summary>
|
||||
public static void DrawLine(this SpriteBatch spriteBatch, Vector2 point, float length, float angle, Color color,
|
||||
float thickness = 1f)
|
||||
{
|
||||
var origin = new Vector2(0f, 0.5f);
|
||||
var scale = new Vector2(length, thickness);
|
||||
spriteBatch.Draw(GetTexture(spriteBatch), point, null, color, angle, origin, scale, SpriteEffects.None, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws a point at the specified x, y position. The center of the point will be at the position.
|
||||
/// </summary>
|
||||
public static void DrawPoint(this SpriteBatch spriteBatch, float x, float y, Color color, float size = 1f)
|
||||
{
|
||||
DrawPoint(spriteBatch, new Vector2(x, y), color, size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws a point at the specified position. The center of the point will be at the position.
|
||||
/// </summary>
|
||||
public static void DrawPoint(this SpriteBatch spriteBatch, Vector2 position, Color color, float size = 1f)
|
||||
{
|
||||
var scale = Vector2.One * size;
|
||||
var offset = new Vector2(0.5f) - new Vector2(size * 0.5f);
|
||||
spriteBatch.Draw(GetTexture(spriteBatch), position + offset, null, color, 0.0f, Vector2.Zero, Vector2.One, SpriteEffects.None, 0);
|
||||
}
|
||||
|
||||
public static void DrawCircle(this SpriteBatch spriteBatch, Vector2 center, float radius, int sides, Color color,
|
||||
float thickness = 1f)
|
||||
{
|
||||
DrawPolygon(spriteBatch, center, CreateCircle(radius, sides), color, thickness);
|
||||
}
|
||||
|
||||
public static void DrawCircle(this SpriteBatch spriteBatch, float x, float y, float radius, int sides,
|
||||
Color color, float thickness = 1f)
|
||||
{
|
||||
DrawPolygon(spriteBatch, new Vector2(x, y), CreateCircle(radius, sides), color, thickness);
|
||||
}
|
||||
|
||||
public static void DrawSector(this SpriteBatch spriteBatch, Vector2 center, float radius, float radians, int sides, Color color, float offset = 0, float thickness = 1)
|
||||
{
|
||||
DrawPolygon(spriteBatch, center, CreateSector(radius, sides, radians, offset), color, thickness);
|
||||
}
|
||||
|
||||
private static Vector2[] CreateSector(double radius, int sides, float radians, float offset = 0)
|
||||
{
|
||||
//circle sectors need one extra point at the center
|
||||
var points = new Vector2[radians < MathHelper.TwoPi ? sides + 1 : sides];
|
||||
var step = radians / sides;
|
||||
|
||||
double theta = offset;
|
||||
for (var i = 0; i < sides; i++)
|
||||
{
|
||||
points[i] = new Vector2((float)Math.Cos(theta), (float)Math.Sin(theta)) * (float)radius;
|
||||
theta += step;
|
||||
}
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
private static Vector2[] CreateCircle(double radius, int sides)
|
||||
{
|
||||
return CreateSector(radius, sides, MathHelper.TwoPi);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Original source: https://github.com/craftworkgames/MonoGame.Extended/blob/develop/Source/MonoGame.Extended/Shapes/Polygon.cs
|
||||
/// </summary>
|
||||
public class Polygon : IEquatable<Polygon>
|
||||
{
|
||||
public Polygon(IEnumerable<Vector2> vertices)
|
||||
{
|
||||
_localVertices = vertices.ToArray();
|
||||
_transformedVertices = _localVertices;
|
||||
_offset = Vector2.Zero;
|
||||
_rotation = 0;
|
||||
_scale = Vector2.One;
|
||||
_isDirty = false;
|
||||
}
|
||||
|
||||
private readonly Vector2[] _localVertices;
|
||||
private Vector2[] _transformedVertices;
|
||||
private Vector2 _offset;
|
||||
private float _rotation;
|
||||
private Vector2 _scale;
|
||||
private bool _isDirty;
|
||||
|
||||
public Vector2[] Vertices
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_isDirty)
|
||||
{
|
||||
_transformedVertices = GetTransformedVertices();
|
||||
_isDirty = false;
|
||||
}
|
||||
|
||||
return _transformedVertices;
|
||||
}
|
||||
}
|
||||
|
||||
public float Left
|
||||
{
|
||||
get { return Vertices.Min(v => v.X); }
|
||||
}
|
||||
|
||||
public float Right
|
||||
{
|
||||
get { return Vertices.Max(v => v.X); }
|
||||
}
|
||||
|
||||
public float Top
|
||||
{
|
||||
get { return Vertices.Min(v => v.Y); }
|
||||
}
|
||||
|
||||
public float Bottom
|
||||
{
|
||||
get { return Vertices.Max(v => v.Y); }
|
||||
}
|
||||
|
||||
public void Offset(Vector2 amount)
|
||||
{
|
||||
_offset += amount;
|
||||
_isDirty = true;
|
||||
}
|
||||
|
||||
public void Rotate(float amount)
|
||||
{
|
||||
_rotation += amount;
|
||||
_isDirty = true;
|
||||
}
|
||||
|
||||
public void Scale(Vector2 amount)
|
||||
{
|
||||
_scale += amount;
|
||||
_isDirty = true;
|
||||
}
|
||||
|
||||
private Vector2[] GetTransformedVertices()
|
||||
{
|
||||
var newVertices = new Vector2[_localVertices.Length];
|
||||
var isScaled = _scale != Vector2.One;
|
||||
|
||||
for (var i = 0; i < _localVertices.Length; i++)
|
||||
{
|
||||
var p = _localVertices[i];
|
||||
|
||||
if (isScaled)
|
||||
p *= _scale;
|
||||
|
||||
// ReSharper disable once CompareOfFloatsByEqualityOperator
|
||||
if (_rotation != 0)
|
||||
{
|
||||
var cos = (float)Math.Cos(_rotation);
|
||||
var sin = (float)Math.Sin(_rotation);
|
||||
p = new Vector2(cos * p.X - sin * p.Y, sin * p.X + cos * p.Y);
|
||||
}
|
||||
|
||||
newVertices[i] = p + _offset;
|
||||
}
|
||||
|
||||
return newVertices;
|
||||
}
|
||||
|
||||
public Polygon TransformedCopy(Vector2 offset, float rotation, Vector2 scale)
|
||||
{
|
||||
var polygon = new Polygon(_localVertices);
|
||||
polygon.Offset(offset);
|
||||
polygon.Rotate(rotation);
|
||||
polygon.Scale(scale - Vector2.One);
|
||||
return new Polygon(polygon.Vertices);
|
||||
}
|
||||
|
||||
public bool Contains(Vector2 point)
|
||||
{
|
||||
return Contains(point.X, point.Y);
|
||||
}
|
||||
|
||||
public bool Contains(float x, float y)
|
||||
{
|
||||
var intersects = 0;
|
||||
var vertices = Vertices;
|
||||
|
||||
for (var i = 0; i < vertices.Length; i++)
|
||||
{
|
||||
var x1 = vertices[i].X;
|
||||
var y1 = vertices[i].Y;
|
||||
var x2 = vertices[(i + 1) % vertices.Length].X;
|
||||
var y2 = vertices[(i + 1) % vertices.Length].Y;
|
||||
|
||||
if ((((y1 <= y) && (y < y2)) || ((y2 <= y) && (y < y1))) && (x < (x2 - x1) / (y2 - y1) * (y - y1) + x1))
|
||||
intersects++;
|
||||
}
|
||||
|
||||
return (intersects & 1) == 1;
|
||||
}
|
||||
|
||||
public static bool operator ==(Polygon a, Polygon b)
|
||||
{
|
||||
return a.Equals(b);
|
||||
}
|
||||
|
||||
public static bool operator !=(Polygon a, Polygon b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj)) return false;
|
||||
return obj is Polygon && Equals((Polygon)obj);
|
||||
}
|
||||
|
||||
public bool Equals(Polygon other)
|
||||
{
|
||||
return Vertices.SequenceEqual(other.Vertices);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
return Vertices.Aggregate(27, (current, v) => current + 13 * current + v.GetHashCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class UISprite
|
||||
{
|
||||
public Sprite Sprite
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public bool Tile
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public bool Slice
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public Rectangle[] Slices
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public bool MaintainAspectRatio
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// How much the borders of a sliced sprite are allowed to scale
|
||||
/// You may for example want to prevent a 1-pixel border from scaling down (and disappearing) on small resolutions
|
||||
/// </summary>
|
||||
private readonly float minBorderScale = 0.1f, maxBorderScale = 10.0f;
|
||||
|
||||
public bool CrossFadeIn { get; private set; } = true;
|
||||
public bool CrossFadeOut { get; private set; } = true;
|
||||
|
||||
public TransitionMode TransitionMode { get; private set; }
|
||||
|
||||
public UISprite(XElement element)
|
||||
{
|
||||
Sprite = new Sprite(element);
|
||||
MaintainAspectRatio = element.GetAttributeBool("maintainaspectratio", false);
|
||||
Tile = element.GetAttributeBool("tile", true);
|
||||
CrossFadeIn = element.GetAttributeBool("crossfadein", CrossFadeIn);
|
||||
CrossFadeOut = element.GetAttributeBool("crossfadeout", CrossFadeOut);
|
||||
string transitionMode = element.GetAttributeString("transition", string.Empty);
|
||||
if (Enum.TryParse(transitionMode, ignoreCase: true, out TransitionMode transition))
|
||||
{
|
||||
TransitionMode = transition;
|
||||
}
|
||||
|
||||
Vector4 sliceVec = element.GetAttributeVector4("slice", Vector4.Zero);
|
||||
if (sliceVec != Vector4.Zero)
|
||||
{
|
||||
minBorderScale = element.GetAttributeFloat("minborderscale", 0.1f);
|
||||
maxBorderScale = element.GetAttributeFloat("minborderscale", 10.0f);
|
||||
|
||||
Rectangle slice = new Rectangle((int)sliceVec.X, (int)sliceVec.Y, (int)(sliceVec.Z - sliceVec.X), (int)(sliceVec.W - sliceVec.Y));
|
||||
|
||||
Slice = true;
|
||||
Slices = new Rectangle[9];
|
||||
|
||||
//top-left
|
||||
Slices[0] = new Rectangle(Sprite.SourceRect.Location, slice.Location - Sprite.SourceRect.Location);
|
||||
//top-mid
|
||||
Slices[1] = new Rectangle(slice.Location.X, Slices[0].Y, slice.Width, Slices[0].Height);
|
||||
//top-right
|
||||
Slices[2] = new Rectangle(slice.Right, Slices[0].Y, Sprite.SourceRect.Right - slice.Right, Slices[0].Height);
|
||||
|
||||
//mid-left
|
||||
Slices[3] = new Rectangle(Slices[0].X, slice.Y, Slices[0].Width, slice.Height);
|
||||
//center
|
||||
Slices[4] = slice;
|
||||
//mid-right
|
||||
Slices[5] = new Rectangle(Slices[2].X, slice.Y, Slices[2].Width, slice.Height);
|
||||
|
||||
//bottom-left
|
||||
Slices[6] = new Rectangle(Slices[0].X, slice.Bottom, Slices[0].Width, Sprite.SourceRect.Bottom - slice.Bottom);
|
||||
//bottom-mid
|
||||
Slices[7] = new Rectangle(Slices[1].X, slice.Bottom, Slices[1].Width, Sprite.SourceRect.Bottom - slice.Bottom);
|
||||
//bottom-right
|
||||
Slices[8] = new Rectangle(Slices[2].X, slice.Bottom, Slices[2].Width, Sprite.SourceRect.Bottom - slice.Bottom);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the scale of the sliced sprite's borders when it's draw inside an area of a specific size
|
||||
/// </summary>
|
||||
public float GetSliceBorderScale(Point drawSize)
|
||||
{
|
||||
if (!Slice) { return 1.0f; }
|
||||
|
||||
Vector2 scale = new Vector2(
|
||||
MathHelper.Clamp((float)drawSize.X / (Slices[0].Height + Slices[6].Height), 0, 1),
|
||||
MathHelper.Clamp((float)drawSize.Y / (Slices[0].Width + Slices[2].Width), 0, 1));
|
||||
return MathHelper.Clamp(Math.Min(Math.Min(scale.X, scale.Y), GUI.SlicedSpriteScale), minBorderScale, maxBorderScale);
|
||||
}
|
||||
|
||||
public void Draw(SpriteBatch spriteBatch, Rectangle rect, Color color, SpriteEffects spriteEffects = SpriteEffects.None)
|
||||
{
|
||||
if (Sprite.Texture == null)
|
||||
{
|
||||
GUI.DrawRectangle(spriteBatch, rect, Color.Magenta);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Slice)
|
||||
{
|
||||
Vector2 pos = new Vector2(rect.X, rect.Y);
|
||||
|
||||
float scale = GetSliceBorderScale(rect.Size);
|
||||
int centerHeight = rect.Height - (int)((Slices[0].Height + Slices[6].Height) * scale);
|
||||
int centerWidth = rect.Width - (int)((Slices[0].Width + Slices[2].Width) * scale);
|
||||
|
||||
for (int x = 0; x < 3; x++)
|
||||
{
|
||||
int width = (int)(x == 1 ? centerWidth : Slices[x].Width * scale);
|
||||
if (width <= 0) { continue; }
|
||||
for (int y = 0; y < 3; y++)
|
||||
{
|
||||
int height = (int)(y == 1 ? centerHeight : Slices[x + y * 3].Height * scale);
|
||||
if (height <= 0) { continue; }
|
||||
|
||||
spriteBatch.Draw(Sprite.Texture,
|
||||
new Rectangle((int)pos.X, (int)pos.Y, width, height),
|
||||
Slices[x + y * 3],
|
||||
color);
|
||||
|
||||
pos.Y += height;
|
||||
}
|
||||
pos.X += width;
|
||||
pos.Y = rect.Y;
|
||||
}
|
||||
}
|
||||
else if (Tile)
|
||||
{
|
||||
Vector2 startPos = new Vector2(rect.X, rect.Y);
|
||||
Sprite.DrawTiled(spriteBatch, startPos, new Vector2(rect.Width, rect.Height), null, color);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (MaintainAspectRatio)
|
||||
{
|
||||
float scale = Math.Min((float)rect.Width / Sprite.SourceRect.Width, (float)rect.Height / Sprite.SourceRect.Height);
|
||||
|
||||
spriteBatch.Draw(Sprite.Texture, rect.Center.ToVector2(),
|
||||
Sprite.SourceRect,
|
||||
color,
|
||||
rotation: 0.0f,
|
||||
origin: Sprite.size / 2.0f,
|
||||
scale: scale,
|
||||
effects: spriteEffects, layerDepth: 0.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
spriteBatch.Draw(Sprite.Texture, rect, Sprite.SourceRect, color, 0, Vector2.Zero, spriteEffects, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Media;
|
||||
using System.IO;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class VideoPlayer
|
||||
{
|
||||
public bool IsPlaying;
|
||||
|
||||
private Video currentVideo;
|
||||
private string filePath;
|
||||
|
||||
private GUIFrame background, videoFrame, textFrame;
|
||||
private GUITextBlock title, textContent, objectiveTitle, objectiveText;
|
||||
private GUICustomComponent videoView;
|
||||
private GUIButton okButton;
|
||||
|
||||
private Color backgroundColor = new Color(0f, 0f, 0f, 0.8f);
|
||||
private Action callbackOnStop;
|
||||
|
||||
private Point scaledVideoResolution;
|
||||
private readonly int borderSize = 20;
|
||||
private readonly Point buttonSize = new Point(120, 30);
|
||||
private readonly int titleHeight = 30;
|
||||
private readonly int objectiveFrameHeight = 60;
|
||||
private readonly int textHeight = 25;
|
||||
|
||||
private bool useTextOnRightSide = false;
|
||||
|
||||
public class TextSettings
|
||||
{
|
||||
public string Text;
|
||||
public int Width;
|
||||
|
||||
public TextSettings(XElement element)
|
||||
{
|
||||
Text = TextManager.GetFormatted(element.GetAttributeString("text", string.Empty), true);
|
||||
Width = element.GetAttributeInt("width", 450);
|
||||
}
|
||||
}
|
||||
|
||||
public class VideoSettings
|
||||
{
|
||||
public string File;
|
||||
|
||||
public VideoSettings(XElement element)
|
||||
{
|
||||
File = element.GetAttributeString("file", string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
public VideoPlayer() // GUI elements with size set to Point.Zero are resized based on content
|
||||
{
|
||||
int screenWidth = (int)(GameMain.GraphicsWidth * 0.55f);
|
||||
scaledVideoResolution = new Point(screenWidth, (int)(screenWidth / 16f * 9f));
|
||||
|
||||
int width = scaledVideoResolution.X;
|
||||
int height = scaledVideoResolution.Y;
|
||||
|
||||
background = new GUIFrame(new RectTransform(Point.Zero, GUI.Canvas, Anchor.Center), style: null, color: backgroundColor);
|
||||
videoFrame = new GUIFrame(new RectTransform(Point.Zero, background.RectTransform, Anchor.Center, Pivot.Center), style: "InnerFrame");
|
||||
|
||||
if (useTextOnRightSide)
|
||||
{
|
||||
textFrame = new GUIFrame(new RectTransform(Point.Zero, videoFrame.RectTransform, Anchor.CenterLeft, Pivot.CenterLeft), "TextFrame");
|
||||
}
|
||||
else
|
||||
{
|
||||
textFrame = new GUIFrame(new RectTransform(Point.Zero, videoFrame.RectTransform, Anchor.TopCenter, Pivot.TopCenter), "TextFrame");
|
||||
}
|
||||
|
||||
videoView = new GUICustomComponent(new RectTransform(Point.Zero, videoFrame.RectTransform, Anchor.Center), (spriteBatch, guiCustomComponent) => { DrawVideo(spriteBatch, guiCustomComponent.Rect); });
|
||||
title = new GUITextBlock(new RectTransform(Point.Zero, textFrame.RectTransform, Anchor.TopLeft, Pivot.TopLeft), string.Empty, font: GUI.LargeFont, textColor: new Color(253, 174, 0), textAlignment: Alignment.Left);
|
||||
|
||||
textContent = new GUITextBlock(new RectTransform(Point.Zero, textFrame.RectTransform, Anchor.TopLeft, Pivot.TopLeft), string.Empty, font: GUI.Font, textAlignment: Alignment.TopLeft);
|
||||
|
||||
objectiveTitle = new GUITextBlock(new RectTransform(new Vector2(1f, 0f), textFrame.RectTransform, Anchor.TopCenter, Pivot.TopCenter), string.Empty, font: GUI.SubHeadingFont, textAlignment: Alignment.CenterRight, textColor: Color.White);
|
||||
objectiveTitle.Text = TextManager.Get("Tutorial.NewObjective");
|
||||
objectiveText = new GUITextBlock(new RectTransform(Point.Zero, textFrame.RectTransform, Anchor.TopCenter, Pivot.TopCenter), string.Empty, font: GUI.SubHeadingFont, textColor: new Color(4, 180, 108), textAlignment: Alignment.CenterRight);
|
||||
|
||||
objectiveTitle.Visible = objectiveText.Visible = false;
|
||||
}
|
||||
|
||||
public void Play()
|
||||
{
|
||||
IsPlaying = true;
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
IsPlaying = false;
|
||||
if (currentVideo == null) return;
|
||||
currentVideo.Dispose();
|
||||
currentVideo = null;
|
||||
}
|
||||
|
||||
private bool DisposeVideo(GUIButton button, object userData)
|
||||
{
|
||||
Stop();
|
||||
callbackOnStop?.Invoke();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if (currentVideo == null) return;
|
||||
if (currentVideo.IsPlaying) return;
|
||||
|
||||
currentVideo.Play();
|
||||
}
|
||||
|
||||
public void AddToGUIUpdateList(bool ignoreChildren = false, int order = 0)
|
||||
{
|
||||
if (!IsPlaying) return;
|
||||
background.AddToGUIUpdateList(ignoreChildren, order);
|
||||
}
|
||||
|
||||
public void LoadContent(string contentPath, VideoSettings videoSettings, TextSettings textSettings, string contentId, bool startPlayback, string objective = "", Action callback = null)
|
||||
{
|
||||
callbackOnStop = callback;
|
||||
filePath = contentPath + videoSettings.File;
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
DebugConsole.ThrowError("No video found at: " + filePath);
|
||||
DisposeVideo(null, null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentVideo != null)
|
||||
{
|
||||
currentVideo.Dispose();
|
||||
currentVideo = null;
|
||||
}
|
||||
|
||||
currentVideo = CreateVideo();
|
||||
title.Text = textSettings != null ? TextManager.Get(contentId) : string.Empty;
|
||||
textContent.Text = textSettings != null ? textSettings.Text : string.Empty;
|
||||
objectiveText.Text = objective;
|
||||
|
||||
AdjustFrames(videoSettings, textSettings);
|
||||
|
||||
if (startPlayback) Play();
|
||||
}
|
||||
|
||||
private void AdjustFrames(VideoSettings videoSettings, TextSettings textSettings)
|
||||
{
|
||||
int screenWidth = (int)(GameMain.GraphicsWidth * 0.55f);
|
||||
scaledVideoResolution = new Point(screenWidth, (int)(screenWidth / 16f * 9f));
|
||||
|
||||
background.RectTransform.NonScaledSize = Point.Zero;
|
||||
videoFrame.RectTransform.NonScaledSize = Point.Zero;
|
||||
videoView.RectTransform.NonScaledSize = Point.Zero;
|
||||
|
||||
title.RectTransform.NonScaledSize = Point.Zero;
|
||||
textFrame.RectTransform.NonScaledSize = Point.Zero;
|
||||
textContent.RectTransform.NonScaledSize = Point.Zero;
|
||||
|
||||
objectiveText.RectTransform.NonScaledSize = Point.Zero;
|
||||
|
||||
title.TextScale = textContent.TextScale = objectiveText.TextScale = objectiveTitle.TextScale = GUI.Scale;
|
||||
|
||||
int scaledBorderSize = (int)(borderSize * GUI.Scale);
|
||||
int scaledTextWidth = 0;
|
||||
if (textSettings != null) scaledTextWidth = useTextOnRightSide ? (int)(textSettings.Width * GUI.Scale) : scaledVideoResolution.X / 2;
|
||||
int scaledTitleHeight = (int)(titleHeight * GUI.Scale);
|
||||
int scaledTextHeight = (int)(textHeight * GUI.Scale);
|
||||
int scaledObjectiveFrameHeight = (int)(objectiveFrameHeight * GUI.Scale);
|
||||
|
||||
Point scaledButtonSize = new Point((int)(buttonSize.X * GUI.Scale), (int)(buttonSize.Y * GUI.Scale));
|
||||
|
||||
background.RectTransform.NonScaledSize = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
|
||||
|
||||
videoFrame.RectTransform.NonScaledSize = scaledVideoResolution + new Point(scaledBorderSize, scaledBorderSize);
|
||||
videoView.RectTransform.NonScaledSize = scaledVideoResolution;
|
||||
|
||||
title.RectTransform.NonScaledSize = new Point(scaledTextWidth, scaledTitleHeight);
|
||||
title.RectTransform.AbsoluteOffset = new Point((int)(5 * GUI.Scale), (int)(10 * GUI.Scale));
|
||||
|
||||
if (textSettings != null && !string.IsNullOrEmpty(textSettings.Text))
|
||||
{
|
||||
textSettings.Text = ToolBox.WrapText(textSettings.Text, scaledTextWidth, GUI.Font);
|
||||
int wrappedHeight = textSettings.Text.Split('\n').Length * scaledTextHeight;
|
||||
|
||||
textFrame.RectTransform.NonScaledSize = new Point(scaledTextWidth + scaledBorderSize, wrappedHeight + scaledBorderSize + scaledButtonSize.Y + scaledTitleHeight);
|
||||
|
||||
if (useTextOnRightSide)
|
||||
{
|
||||
textFrame.RectTransform.AbsoluteOffset = new Point(scaledVideoResolution.X + scaledBorderSize * 2, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
textFrame.RectTransform.AbsoluteOffset = new Point(0, scaledVideoResolution.Y + scaledBorderSize * 2);
|
||||
}
|
||||
|
||||
textContent.RectTransform.NonScaledSize = new Point(scaledTextWidth, wrappedHeight);
|
||||
textContent.RectTransform.AbsoluteOffset = new Point(0, scaledBorderSize + scaledTitleHeight);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(objectiveText.Text))
|
||||
{
|
||||
int scaledXOffset = (int)(-10 * GUI.Scale);
|
||||
|
||||
objectiveTitle.RectTransform.AbsoluteOffset = new Point(scaledXOffset, textContent.RectTransform.Rect.Height + (int)(scaledTextHeight * 1.95f));
|
||||
objectiveText.RectTransform.AbsoluteOffset = new Point(scaledXOffset, textContent.RectTransform.Rect.Height + objectiveTitle.Rect.Height + (int)(scaledTextHeight * 2.25f));
|
||||
|
||||
textFrame.RectTransform.NonScaledSize += new Point(0, scaledObjectiveFrameHeight);
|
||||
objectiveText.RectTransform.NonScaledSize = new Point(textFrame.Rect.Width, scaledTextHeight);
|
||||
objectiveTitle.Visible = objectiveText.Visible = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
textFrame.RectTransform.NonScaledSize += new Point(0, scaledBorderSize);
|
||||
objectiveTitle.Visible = objectiveText.Visible = false;
|
||||
}
|
||||
|
||||
if (okButton != null)
|
||||
{
|
||||
textFrame.RemoveChild(okButton);
|
||||
okButton = null;
|
||||
}
|
||||
|
||||
if (textSettings != null)
|
||||
{
|
||||
if (useTextOnRightSide)
|
||||
{
|
||||
int totalFrameWidth = videoFrame.Rect.Width + textFrame.Rect.Width + scaledBorderSize * 2;
|
||||
int xOffset = videoFrame.Rect.Width / 2 + scaledBorderSize - (videoFrame.Rect.Width / 2 - textFrame.Rect.Width / 2);
|
||||
videoFrame.RectTransform.AbsoluteOffset = new Point(-xOffset, (int)(50 * GUI.Scale));
|
||||
}
|
||||
else
|
||||
{
|
||||
int totalFrameHeight = videoFrame.Rect.Height + textFrame.Rect.Height + scaledBorderSize * 2;
|
||||
int yOffset = videoFrame.Rect.Height / 2 + scaledBorderSize - (videoFrame.Rect.Height / 2 - textFrame.Rect.Height / 2);
|
||||
videoFrame.RectTransform.AbsoluteOffset = new Point(0, -yOffset);
|
||||
}
|
||||
|
||||
okButton = new GUIButton(new RectTransform(scaledButtonSize, textFrame.RectTransform, Anchor.BottomRight, Pivot.BottomRight) { AbsoluteOffset = new Point(scaledBorderSize, scaledBorderSize) }, TextManager.Get("OK"))
|
||||
{
|
||||
OnClicked = DisposeVideo
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
videoFrame.RectTransform.AbsoluteOffset = new Point(0, (int)(100 * GUI.Scale));
|
||||
|
||||
okButton = new GUIButton(new RectTransform(scaledButtonSize, videoFrame.RectTransform, Anchor.TopLeft, Pivot.TopLeft) { AbsoluteOffset = new Point(scaledBorderSize, scaledBorderSize) }, TextManager.Get("Back"))
|
||||
{
|
||||
OnClicked = DisposeVideo
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private Video CreateVideo()
|
||||
{
|
||||
Video video = null;
|
||||
|
||||
try
|
||||
{
|
||||
video = Video.Load(GameMain.Instance.GraphicsDevice, GameMain.SoundManager, filePath);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Error loading video content " + filePath + "!", e);
|
||||
}
|
||||
|
||||
return video;
|
||||
}
|
||||
|
||||
private void DrawVideo(SpriteBatch spriteBatch, Rectangle rect)
|
||||
{
|
||||
if (!IsPlaying) return;
|
||||
spriteBatch.Draw(currentVideo.GetTexture(), rect, Color.White);
|
||||
}
|
||||
|
||||
public void Remove()
|
||||
{
|
||||
if (currentVideo != null)
|
||||
{
|
||||
currentVideo.Dispose();
|
||||
currentVideo = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class Widget
|
||||
{
|
||||
public enum Shape
|
||||
{
|
||||
Rectangle,
|
||||
Circle,
|
||||
Cross
|
||||
}
|
||||
|
||||
public Shape shape;
|
||||
public string tooltip;
|
||||
public bool showTooltip = true;
|
||||
public Rectangle DrawRect => new Rectangle((int)(DrawPos.X - (float)size / 2), (int)(DrawPos.Y - (float)size / 2), size, size);
|
||||
public Rectangle InputRect
|
||||
{
|
||||
get
|
||||
{
|
||||
var inputRect = DrawRect;
|
||||
inputRect.Inflate(inputAreaMargin, inputAreaMargin);
|
||||
return inputRect;
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2 DrawPos { get; set; }
|
||||
public int size = 10;
|
||||
/// <summary>
|
||||
/// Used only for circles.
|
||||
/// </summary>
|
||||
public int sides = 40;
|
||||
/// <summary>
|
||||
/// Currently used only for rectangles.
|
||||
/// </summary>
|
||||
public bool isFilled;
|
||||
public int inputAreaMargin;
|
||||
public Color color = GUI.Style.Red;
|
||||
public Color? secondaryColor;
|
||||
public Color textColor = Color.White;
|
||||
public Color textBackgroundColor = Color.Black * 0.5f;
|
||||
public readonly string id;
|
||||
|
||||
public event Action Selected;
|
||||
public event Action Deselected;
|
||||
public event Action Hovered;
|
||||
public event Action MouseUp;
|
||||
public event Action MouseDown;
|
||||
public event Action<float> MouseHeld;
|
||||
public event Action<float> PreUpdate;
|
||||
public event Action<float> PostUpdate;
|
||||
public event Action<SpriteBatch, float> PreDraw;
|
||||
public event Action<SpriteBatch, float> PostDraw;
|
||||
|
||||
public bool RequireMouseOn = true;
|
||||
|
||||
public Action refresh;
|
||||
|
||||
public object data;
|
||||
|
||||
public bool IsSelected => enabled && selectedWidgets.Contains(this);
|
||||
public bool IsControlled => IsSelected && PlayerInput.PrimaryMouseButtonHeld();
|
||||
public bool IsMouseOver => GUI.MouseOn == null && InputRect.Contains(PlayerInput.MousePosition);
|
||||
private bool enabled = true;
|
||||
public bool Enabled
|
||||
{
|
||||
get { return enabled; }
|
||||
set
|
||||
{
|
||||
enabled = value;
|
||||
if (!enabled && selectedWidgets.Contains(this))
|
||||
{
|
||||
selectedWidgets.Remove(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool multiselect;
|
||||
public static bool EnableMultiSelect
|
||||
{
|
||||
get { return multiselect; }
|
||||
set
|
||||
{
|
||||
multiselect = value;
|
||||
if (!multiselect && selectedWidgets.Multiple())
|
||||
{
|
||||
selectedWidgets = selectedWidgets.Take(1).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
public Vector2? tooltipOffset;
|
||||
|
||||
public Widget linkedWidget;
|
||||
|
||||
public static List<Widget> selectedWidgets = new List<Widget>();
|
||||
|
||||
public Widget(string id, int size, Shape shape)
|
||||
{
|
||||
this.id = id;
|
||||
this.size = size;
|
||||
this.shape = shape;
|
||||
}
|
||||
|
||||
public virtual void Update(float deltaTime)
|
||||
{
|
||||
PreUpdate?.Invoke(deltaTime);
|
||||
if (!enabled) { return; }
|
||||
if (IsMouseOver || (!RequireMouseOn && selectedWidgets.Contains(this) && PlayerInput.PrimaryMouseButtonHeld()))
|
||||
{
|
||||
Hovered?.Invoke();
|
||||
if (RequireMouseOn || PlayerInput.PrimaryMouseButtonDown())
|
||||
{
|
||||
if ((multiselect && !selectedWidgets.Contains(this)) || selectedWidgets.None())
|
||||
{
|
||||
selectedWidgets.Add(this);
|
||||
Selected?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (selectedWidgets.Contains(this))
|
||||
{
|
||||
selectedWidgets.Remove(this);
|
||||
Deselected?.Invoke();
|
||||
}
|
||||
if (IsSelected)
|
||||
{
|
||||
if (PlayerInput.PrimaryMouseButtonDown())
|
||||
{
|
||||
MouseDown?.Invoke();
|
||||
}
|
||||
if (PlayerInput.PrimaryMouseButtonHeld())
|
||||
{
|
||||
MouseHeld?.Invoke(deltaTime);
|
||||
}
|
||||
if (PlayerInput.PrimaryMouseButtonClicked())
|
||||
{
|
||||
MouseUp?.Invoke();
|
||||
}
|
||||
}
|
||||
PostUpdate?.Invoke(deltaTime);
|
||||
}
|
||||
|
||||
public virtual void Draw(SpriteBatch spriteBatch, float deltaTime)
|
||||
{
|
||||
PreDraw?.Invoke(spriteBatch, deltaTime);
|
||||
var drawRect = DrawRect;
|
||||
switch (shape)
|
||||
{
|
||||
case Shape.Rectangle:
|
||||
if (secondaryColor.HasValue)
|
||||
{
|
||||
GUI.DrawRectangle(spriteBatch, drawRect, secondaryColor.Value, isFilled, thickness: 2);
|
||||
}
|
||||
GUI.DrawRectangle(spriteBatch, drawRect, color, isFilled, thickness: IsSelected ? 3 : 1);
|
||||
break;
|
||||
case Shape.Circle:
|
||||
if (secondaryColor.HasValue)
|
||||
{
|
||||
ShapeExtensions.DrawCircle(spriteBatch, DrawPos, size / 2, sides, secondaryColor.Value, thickness: 2);
|
||||
}
|
||||
ShapeExtensions.DrawCircle(spriteBatch, DrawPos, size / 2, sides, color, thickness: IsSelected ? 3 : 1);
|
||||
break;
|
||||
case Shape.Cross:
|
||||
float halfSize = size / 2;
|
||||
if (secondaryColor.HasValue)
|
||||
{
|
||||
GUI.DrawLine(spriteBatch, DrawPos + Vector2.UnitY * halfSize, DrawPos - Vector2.UnitY * halfSize, secondaryColor.Value, width: 2);
|
||||
GUI.DrawLine(spriteBatch, DrawPos + Vector2.UnitX * halfSize, DrawPos - Vector2.UnitX * halfSize, secondaryColor.Value, width: 2);
|
||||
}
|
||||
GUI.DrawLine(spriteBatch, DrawPos + Vector2.UnitY * halfSize, DrawPos - Vector2.UnitY * halfSize, color, width: IsSelected ? 3 : 1);
|
||||
GUI.DrawLine(spriteBatch, DrawPos + Vector2.UnitX * halfSize, DrawPos - Vector2.UnitX * halfSize, color, width: IsSelected ? 3 : 1);
|
||||
break;
|
||||
default: throw new NotImplementedException(shape.ToString());
|
||||
}
|
||||
if (IsSelected)
|
||||
{
|
||||
if (showTooltip && !string.IsNullOrEmpty(tooltip))
|
||||
{
|
||||
var offset = tooltipOffset ?? new Vector2(size, -size / 2);
|
||||
GUI.DrawString(spriteBatch, DrawPos + offset, tooltip, textColor, textBackgroundColor);
|
||||
}
|
||||
}
|
||||
PostDraw?.Invoke(spriteBatch, deltaTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,10 @@
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class GameMode
|
||||
{
|
||||
public virtual void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
abstract partial class CampaignMode : GameMode
|
||||
{
|
||||
public override void ShowStartMessage()
|
||||
{
|
||||
if (Mission == null) return;
|
||||
|
||||
new GUIMessageBox(Mission.Name, Mission.Description, new string[0], type: GUIMessageBox.Type.InGame, icon: Mission.Prefab.Icon)
|
||||
{
|
||||
IconColor = Mission.Prefab.IconColor,
|
||||
UserData = "missionstartmessage"
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
+248
@@ -0,0 +1,248 @@
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class MultiPlayerCampaign : CampaignMode
|
||||
{
|
||||
public bool SuppressStateSending = false;
|
||||
|
||||
private UInt16 startWatchmanID, endWatchmanID;
|
||||
|
||||
public static void StartCampaignSetup(IEnumerable<string> saveFiles)
|
||||
{
|
||||
var parent = GameMain.NetLobbyScreen.CampaignSetupFrame;
|
||||
parent.ClearChildren();
|
||||
parent.Visible = true;
|
||||
GameMain.NetLobbyScreen.HighlightMode(2);
|
||||
|
||||
var layout = new GUILayoutGroup(new RectTransform(Vector2.One, parent.RectTransform, Anchor.Center))
|
||||
{
|
||||
Stretch = true
|
||||
};
|
||||
|
||||
var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.07f), layout.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.1f) }, isHorizontal: true)
|
||||
{
|
||||
RelativeSpacing = 0.02f
|
||||
};
|
||||
|
||||
var campaignContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.9f), layout.RectTransform, Anchor.BottomLeft), style: "InnerFrame")
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
|
||||
var newCampaignContainer = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.95f), campaignContainer.RectTransform, Anchor.Center), style: null);
|
||||
var loadCampaignContainer = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.95f), campaignContainer.RectTransform, Anchor.Center), style: null);
|
||||
|
||||
var campaignSetupUI = new CampaignSetupUI(true, newCampaignContainer, loadCampaignContainer, null, saveFiles);
|
||||
|
||||
var newCampaignButton = new GUIButton(new RectTransform(new Vector2(0.5f, 1.0f), buttonContainer.RectTransform),
|
||||
TextManager.Get("NewCampaign"), style: "GUITabButton")
|
||||
{
|
||||
Selected = true
|
||||
};
|
||||
|
||||
var loadCampaignButton = new GUIButton(new RectTransform(new Vector2(0.5f, 1.00f), buttonContainer.RectTransform),
|
||||
TextManager.Get("LoadCampaign"), style: "GUITabButton");
|
||||
|
||||
newCampaignButton.OnClicked = (btn, obj) =>
|
||||
{
|
||||
newCampaignButton.Selected = true;
|
||||
loadCampaignButton.Selected = false;
|
||||
newCampaignContainer.Visible = true;
|
||||
loadCampaignContainer.Visible = false;
|
||||
return true;
|
||||
};
|
||||
loadCampaignButton.OnClicked = (btn, obj) =>
|
||||
{
|
||||
newCampaignButton.Selected = false;
|
||||
loadCampaignButton.Selected = true;
|
||||
newCampaignContainer.Visible = false;
|
||||
loadCampaignContainer.Visible = true;
|
||||
return true;
|
||||
};
|
||||
loadCampaignContainer.Visible = false;
|
||||
|
||||
GUITextBlock.AutoScaleAndNormalize(newCampaignButton.TextBlock, loadCampaignButton.TextBlock);
|
||||
|
||||
campaignSetupUI.StartNewGame = GameMain.Client.SetupNewCampaign;
|
||||
campaignSetupUI.LoadGame = GameMain.Client.SetupLoadCampaign;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
base.Update(deltaTime);
|
||||
|
||||
if (startWatchmanID > 0 && startWatchman == null)
|
||||
{
|
||||
startWatchman = Entity.FindEntityByID(startWatchmanID) as Character;
|
||||
if (startWatchman != null) { InitializeWatchman(startWatchman); }
|
||||
}
|
||||
if (endWatchmanID > 0 && endWatchman == null)
|
||||
{
|
||||
endWatchman = Entity.FindEntityByID(endWatchmanID) as Character;
|
||||
if (endWatchman != null) { InitializeWatchman(endWatchman); }
|
||||
}
|
||||
}
|
||||
|
||||
protected override void WatchmanInteract(Character watchman, Character interactor)
|
||||
{
|
||||
if ((watchman.Submarine == Level.Loaded.StartOutpost && !Submarine.MainSub.AtStartPosition) ||
|
||||
(watchman.Submarine == Level.Loaded.EndOutpost && !Submarine.MainSub.AtEndPosition))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (GUIMessageBox.MessageBoxes.Any(mbox => mbox.UserData as string == "watchmanprompt"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (GameMain.Client != null && interactor == Character.Controlled)
|
||||
{
|
||||
var msgBox = new GUIMessageBox("", TextManager.GetWithVariable("CampaignEnterOutpostPrompt", "[locationname]",
|
||||
Submarine.MainSub.AtStartPosition ? Map.CurrentLocation.Name : Map.SelectedLocation.Name),
|
||||
new string[] { TextManager.Get("Yes"), TextManager.Get("No") })
|
||||
{
|
||||
UserData = "watchmanprompt"
|
||||
};
|
||||
msgBox.Buttons[0].OnClicked = (btn, userdata) =>
|
||||
{
|
||||
GameMain.Client.RequestRoundEnd();
|
||||
return true;
|
||||
};
|
||||
msgBox.Buttons[0].OnClicked += msgBox.Close;
|
||||
msgBox.Buttons[1].OnClicked += msgBox.Close;
|
||||
}
|
||||
}
|
||||
|
||||
public void ClientWrite(IWriteMessage msg)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(map.Locations.Count < UInt16.MaxValue);
|
||||
|
||||
msg.Write(map.SelectedLocationIndex == -1 ? UInt16.MaxValue : (UInt16)map.SelectedLocationIndex);
|
||||
msg.Write(map.SelectedMissionIndex == -1 ? byte.MaxValue : (byte)map.SelectedMissionIndex);
|
||||
msg.Write(PurchasedHullRepairs);
|
||||
msg.Write(PurchasedItemRepairs);
|
||||
msg.Write(PurchasedLostShuttles);
|
||||
|
||||
msg.Write((UInt16)CargoManager.PurchasedItems.Count);
|
||||
foreach (PurchasedItem pi in CargoManager.PurchasedItems)
|
||||
{
|
||||
msg.Write(pi.ItemPrefab.Identifier);
|
||||
msg.Write((UInt16)pi.Quantity);
|
||||
}
|
||||
}
|
||||
|
||||
//static because we may need to instantiate the campaign if it hasn't been done yet
|
||||
public static void ClientRead(IReadMessage msg)
|
||||
{
|
||||
byte campaignID = msg.ReadByte();
|
||||
UInt16 updateID = msg.ReadUInt16();
|
||||
UInt16 saveID = msg.ReadUInt16();
|
||||
string mapSeed = msg.ReadString();
|
||||
UInt16 currentLocIndex = msg.ReadUInt16();
|
||||
UInt16 selectedLocIndex = msg.ReadUInt16();
|
||||
byte selectedMissionIndex = msg.ReadByte();
|
||||
|
||||
UInt16 startWatchmanID = msg.ReadUInt16();
|
||||
UInt16 endWatchmanID = msg.ReadUInt16();
|
||||
|
||||
int money = msg.ReadInt32();
|
||||
bool purchasedHullRepairs = msg.ReadBoolean();
|
||||
bool purchasedItemRepairs = msg.ReadBoolean();
|
||||
bool purchasedLostShuttles = msg.ReadBoolean();
|
||||
|
||||
UInt16 purchasedItemCount = msg.ReadUInt16();
|
||||
List<PurchasedItem> purchasedItems = new List<PurchasedItem>();
|
||||
for (int i = 0; i < purchasedItemCount; i++)
|
||||
{
|
||||
string itemPrefabIdentifier = msg.ReadString();
|
||||
UInt16 itemQuantity = msg.ReadUInt16();
|
||||
purchasedItems.Add(new PurchasedItem(ItemPrefab.Prefabs[itemPrefabIdentifier], itemQuantity));
|
||||
}
|
||||
|
||||
bool hasCharacterData = msg.ReadBoolean();
|
||||
CharacterInfo myCharacterInfo = null;
|
||||
if (hasCharacterData)
|
||||
{
|
||||
myCharacterInfo = CharacterInfo.ClientRead(CharacterPrefab.HumanSpeciesName, msg);
|
||||
}
|
||||
|
||||
MultiPlayerCampaign campaign = GameMain.GameSession?.GameMode as MultiPlayerCampaign;
|
||||
if (campaign == null || campaignID != campaign.CampaignID)
|
||||
{
|
||||
string savePath = SaveUtil.CreateSavePath(SaveUtil.SaveType.Multiplayer);
|
||||
|
||||
GameMain.GameSession = new GameSession(null, savePath,
|
||||
GameModePreset.List.Find(g => g.Identifier == "multiplayercampaign"));
|
||||
|
||||
campaign = ((MultiPlayerCampaign)GameMain.GameSession.GameMode);
|
||||
campaign.CampaignID = campaignID;
|
||||
campaign.GenerateMap(mapSeed);
|
||||
GameMain.NetLobbyScreen.ToggleCampaignMode(true);
|
||||
}
|
||||
|
||||
|
||||
//server has a newer save file
|
||||
if (NetIdUtils.IdMoreRecent(saveID, campaign.PendingSaveID))
|
||||
{
|
||||
/*//stop any active campaign save transfers, they're outdated now
|
||||
List<FileReceiver.FileTransferIn> saveTransfers =
|
||||
GameMain.Client.FileReceiver.ActiveTransfers.FindAll(t => t.FileType == FileTransferType.CampaignSave);
|
||||
|
||||
foreach (var transfer in saveTransfers)
|
||||
{
|
||||
GameMain.Client.FileReceiver.StopTransfer(transfer);
|
||||
}
|
||||
|
||||
GameMain.Client.RequestFile(FileTransferType.CampaignSave, null, null);*/
|
||||
campaign.PendingSaveID = saveID;
|
||||
}
|
||||
|
||||
if (NetIdUtils.IdMoreRecent(updateID, campaign.lastUpdateID))
|
||||
{
|
||||
campaign.SuppressStateSending = true;
|
||||
|
||||
//we need to have the latest save file to display location/mission/store
|
||||
if (campaign.LastSaveID == saveID)
|
||||
{
|
||||
campaign.Map.SetLocation(currentLocIndex == UInt16.MaxValue ? -1 : currentLocIndex);
|
||||
campaign.Map.SelectLocation(selectedLocIndex == UInt16.MaxValue ? -1 : selectedLocIndex);
|
||||
campaign.Map.SelectMission(selectedMissionIndex);
|
||||
campaign.CargoManager.SetPurchasedItems(purchasedItems);
|
||||
}
|
||||
|
||||
campaign.startWatchmanID = startWatchmanID;
|
||||
campaign.endWatchmanID = endWatchmanID;
|
||||
|
||||
campaign.Money = money;
|
||||
campaign.PurchasedHullRepairs = purchasedHullRepairs;
|
||||
campaign.PurchasedItemRepairs = purchasedItemRepairs;
|
||||
campaign.PurchasedLostShuttles = purchasedLostShuttles;
|
||||
|
||||
if (myCharacterInfo != null)
|
||||
{
|
||||
GameMain.Client.CharacterInfo = myCharacterInfo;
|
||||
GameMain.NetLobbyScreen.SetCampaignCharacterInfo(myCharacterInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
GameMain.NetLobbyScreen.SetCampaignCharacterInfo(null);
|
||||
}
|
||||
|
||||
campaign.lastUpdateID = updateID;
|
||||
campaign.SuppressStateSending = false;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Save(XElement element)
|
||||
{
|
||||
//do nothing, the clients get the save files from the server
|
||||
}
|
||||
}
|
||||
}
|
||||
+443
@@ -0,0 +1,443 @@
|
||||
using Barotrauma.Tutorials;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class SinglePlayerCampaign : CampaignMode
|
||||
{
|
||||
private GUIButton endRoundButton;
|
||||
|
||||
private bool crewDead;
|
||||
private float endTimer;
|
||||
|
||||
private bool savedOnStart;
|
||||
|
||||
private List<Submarine> subsToLeaveBehind;
|
||||
|
||||
private Submarine leavingSub;
|
||||
private bool atEndPosition;
|
||||
|
||||
public SinglePlayerCampaign(GameModePreset preset, object param)
|
||||
: base(preset, param)
|
||||
{
|
||||
int buttonHeight = (int)(HUDLayoutSettings.ButtonAreaTop.Height * 0.7f);
|
||||
endRoundButton = new GUIButton(HUDLayoutSettings.ToRectTransform(new Rectangle(HUDLayoutSettings.ButtonAreaTop.Right - 200, HUDLayoutSettings.ButtonAreaTop.Center.Y - buttonHeight / 2, 200, buttonHeight), GUICanvas.Instance),
|
||||
TextManager.Get("EndRound"), textAlignment: Alignment.Center)
|
||||
{
|
||||
Font = GUI.SmallFont,
|
||||
OnClicked = (btn, userdata) => { TryEndRound(GetLeavingSub()); return true; }
|
||||
};
|
||||
|
||||
foreach (JobPrefab jobPrefab in JobPrefab.Prefabs)
|
||||
{
|
||||
for (int i = 0; i < jobPrefab.InitialCount; i++)
|
||||
{
|
||||
var variant = Rand.Range(0, jobPrefab.Variants);
|
||||
CrewManager.AddCharacterInfo(new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: jobPrefab, variant: variant));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Start()
|
||||
{
|
||||
base.Start();
|
||||
CargoManager.CreateItems();
|
||||
|
||||
if (!savedOnStart)
|
||||
{
|
||||
SaveUtil.SaveGame(GameMain.GameSession.SavePath);
|
||||
savedOnStart = true;
|
||||
}
|
||||
|
||||
crewDead = false;
|
||||
endTimer = 5.0f;
|
||||
isRunning = true;
|
||||
CrewManager.InitSinglePlayerRound();
|
||||
}
|
||||
|
||||
public bool TryHireCharacter(Location location, CharacterInfo characterInfo)
|
||||
{
|
||||
if (Money < characterInfo.Salary) { return false; }
|
||||
|
||||
location.RemoveHireableCharacter(characterInfo);
|
||||
CrewManager.AddCharacterInfo(characterInfo);
|
||||
Money -= characterInfo.Salary;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void FireCharacter(CharacterInfo characterInfo)
|
||||
{
|
||||
CrewManager.RemoveCharacterInfo(characterInfo);
|
||||
}
|
||||
|
||||
private Submarine GetLeavingSub()
|
||||
{
|
||||
if (Character.Controlled?.Submarine == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
//allow leaving if inside an outpost, and the submarine is either docked to it or close enough
|
||||
return GetLeavingSubAtOutpost(Level.Loaded.StartOutpost) ?? GetLeavingSubAtOutpost(Level.Loaded.EndOutpost);
|
||||
|
||||
Submarine GetLeavingSubAtOutpost(Submarine outpost)
|
||||
{
|
||||
//controlled character has to be inside the outpost
|
||||
if (Character.Controlled.Submarine != outpost) { return null; }
|
||||
|
||||
//if there's a sub docked to the outpost, we can leave the level
|
||||
if (outpost.DockedTo.Any())
|
||||
{
|
||||
var dockedSub = outpost.DockedTo.FirstOrDefault();
|
||||
return dockedSub.DockedTo.Contains(Submarine.MainSub) ? Submarine.MainSub : dockedSub;
|
||||
}
|
||||
|
||||
//nothing docked, check if there's a sub close enough to the outpost
|
||||
Submarine closestSub = Submarine.FindClosest(outpost.WorldPosition, ignoreOutposts: true);
|
||||
if (closestSub == null) { return null; }
|
||||
|
||||
if (outpost == Level.Loaded.StartOutpost)
|
||||
{
|
||||
if (!closestSub.AtStartPosition) { return null; }
|
||||
}
|
||||
else if (outpost == Level.Loaded.EndOutpost)
|
||||
{
|
||||
if (!closestSub.AtEndPosition) { return null; }
|
||||
}
|
||||
return closestSub.DockedTo.Contains(Submarine.MainSub) ? Submarine.MainSub : closestSub;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
if (!isRunning|| GUI.DisableHUD || GUI.DisableUpperHUD) return;
|
||||
|
||||
if (Submarine.MainSub == null) return;
|
||||
|
||||
Submarine leavingSub = GetLeavingSub();
|
||||
if (leavingSub == null)
|
||||
{
|
||||
endRoundButton.Visible = false;
|
||||
}
|
||||
else if (leavingSub.AtEndPosition)
|
||||
{
|
||||
endRoundButton.Text = ToolBox.LimitString(TextManager.GetWithVariable("EnterLocation", "[locationname]", Map.SelectedLocation.Name), endRoundButton.Font, endRoundButton.Rect.Width - 5);
|
||||
endRoundButton.Visible = true;
|
||||
}
|
||||
else if (leavingSub.AtStartPosition)
|
||||
{
|
||||
endRoundButton.Text = ToolBox.LimitString(TextManager.GetWithVariable("EnterLocation", "[locationname]", Map.CurrentLocation.Name), endRoundButton.Font, endRoundButton.Rect.Width - 5);
|
||||
endRoundButton.Visible = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
endRoundButton.Visible = false;
|
||||
}
|
||||
|
||||
endRoundButton.DrawManually(spriteBatch);
|
||||
}
|
||||
|
||||
public override void AddToGUIUpdateList()
|
||||
{
|
||||
if (!isRunning) return;
|
||||
|
||||
base.AddToGUIUpdateList();
|
||||
CrewManager.AddToGUIUpdateList();
|
||||
endRoundButton.AddToGUIUpdateList();
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (!isRunning) { return; }
|
||||
|
||||
base.Update(deltaTime);
|
||||
|
||||
if (!GUI.DisableHUD && !GUI.DisableUpperHUD)
|
||||
{
|
||||
endRoundButton.UpdateManually(deltaTime);
|
||||
}
|
||||
|
||||
if (!crewDead)
|
||||
{
|
||||
if (!CrewManager.GetCharacters().Any(c => !c.IsDead)) crewDead = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
endTimer -= deltaTime;
|
||||
if (endTimer <= 0.0f) { EndRound(leavingSub: null); }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected override void WatchmanInteract(Character watchman, Character interactor)
|
||||
{
|
||||
if (interactor != null)
|
||||
{
|
||||
interactor.FocusedCharacter = null;
|
||||
}
|
||||
|
||||
Submarine leavingSub = GetLeavingSub();
|
||||
if (leavingSub == null)
|
||||
{
|
||||
CreateDialog(new List<Character> { watchman }, "WatchmanInteractNoLeavingSub", 5.0f);
|
||||
return;
|
||||
}
|
||||
|
||||
CreateDialog(new List<Character> { watchman }, "WatchmanInteract", 1.0f);
|
||||
|
||||
if (GUIMessageBox.MessageBoxes.Any(mbox => mbox.UserData as string == "watchmanprompt"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
var msgBox = new GUIMessageBox("", TextManager.GetWithVariable("CampaignEnterOutpostPrompt", "[locationname]",
|
||||
leavingSub.AtStartPosition ? Map.CurrentLocation.Name : Map.SelectedLocation.Name),
|
||||
new string[] { TextManager.Get("Yes"), TextManager.Get("No") })
|
||||
{
|
||||
UserData = "watchmanprompt"
|
||||
};
|
||||
msgBox.Buttons[0].OnClicked = (btn, userdata) =>
|
||||
{
|
||||
if (!isRunning) { return true; }
|
||||
TryEndRound(GetLeavingSub());
|
||||
return true;
|
||||
};
|
||||
msgBox.Buttons[0].OnClicked += msgBox.Close;
|
||||
msgBox.Buttons[1].OnClicked += msgBox.Close;
|
||||
}
|
||||
|
||||
public override void End(string endMessage = "")
|
||||
{
|
||||
isRunning = false;
|
||||
|
||||
bool success = CrewManager.GetCharacters().Any(c => !c.IsDead);
|
||||
crewDead = false;
|
||||
|
||||
if (success)
|
||||
{
|
||||
if (subsToLeaveBehind == null || leavingSub == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Leaving submarine not selected -> selecting the closest one");
|
||||
|
||||
leavingSub = GetLeavingSub();
|
||||
|
||||
subsToLeaveBehind = GetSubsToLeaveBehind(leavingSub);
|
||||
}
|
||||
}
|
||||
|
||||
GameMain.GameSession.EndRound("");
|
||||
|
||||
if (success)
|
||||
{
|
||||
if (leavingSub != Submarine.MainSub && !leavingSub.DockedTo.Contains(Submarine.MainSub))
|
||||
{
|
||||
Submarine.MainSub = leavingSub;
|
||||
|
||||
GameMain.GameSession.Submarine = leavingSub;
|
||||
|
||||
foreach (Submarine sub in subsToLeaveBehind)
|
||||
{
|
||||
MapEntity.mapEntityList.RemoveAll(e => e.Submarine == sub && e is LinkedSubmarine);
|
||||
LinkedSubmarine.CreateDummy(leavingSub, sub);
|
||||
}
|
||||
}
|
||||
|
||||
if (atEndPosition)
|
||||
{
|
||||
Map.MoveToNextLocation();
|
||||
}
|
||||
else
|
||||
{
|
||||
Map.SelectLocation(-1);
|
||||
}
|
||||
Map.ProgressWorld();
|
||||
|
||||
//save and remove all items that are in someone's inventory
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (c.Info == null || c.Inventory == null) { continue; }
|
||||
var inventoryElement = new XElement("inventory");
|
||||
|
||||
// Recharge headset batteries
|
||||
var headset = c.Inventory.FindItemByIdentifier("headset");
|
||||
if (headset != null)
|
||||
{
|
||||
var battery = headset.OwnInventory.FindItemByTag("loadable");
|
||||
if (battery != null)
|
||||
{
|
||||
battery.Condition = battery.MaxCondition;
|
||||
}
|
||||
}
|
||||
|
||||
c.SaveInventory(c.Inventory, inventoryElement);
|
||||
c.Info.InventoryData = inventoryElement;
|
||||
c.Inventory?.DeleteAllItems();
|
||||
}
|
||||
SaveUtil.SaveGame(GameMain.GameSession.SavePath);
|
||||
}
|
||||
|
||||
if (!success)
|
||||
{
|
||||
var summaryScreen = GUIMessageBox.VisibleBox;
|
||||
|
||||
if (summaryScreen != null)
|
||||
{
|
||||
summaryScreen = summaryScreen.Children.First();
|
||||
var buttonArea = summaryScreen.Children.First().FindChild("buttonarea");
|
||||
buttonArea.ClearChildren();
|
||||
|
||||
|
||||
summaryScreen.RemoveChild(summaryScreen.Children.FirstOrDefault(c => c is GUIButton));
|
||||
|
||||
var okButton = new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), buttonArea.RectTransform),
|
||||
TextManager.Get("LoadGameButton"))
|
||||
{
|
||||
OnClicked = (GUIButton button, object obj) =>
|
||||
{
|
||||
GameMain.GameSession.LoadPrevious();
|
||||
GameMain.LobbyScreen.Select();
|
||||
GUIMessageBox.MessageBoxes.RemoveAll(c => c?.UserData as string == "roundsummary");
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
var quitButton = new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), buttonArea.RectTransform),
|
||||
TextManager.Get("QuitButton"));
|
||||
quitButton.OnClicked += GameMain.LobbyScreen.QuitToMainMenu;
|
||||
quitButton.OnClicked += (GUIButton button, object obj) =>
|
||||
{
|
||||
GUIMessageBox.MessageBoxes.RemoveAll(c => c?.UserData as string == "roundsummary");
|
||||
return true;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
CrewManager.EndRound();
|
||||
for (int i = Character.CharacterList.Count - 1; i >= 0; i--)
|
||||
{
|
||||
Character.CharacterList[i].Remove();
|
||||
}
|
||||
|
||||
Submarine.Unload();
|
||||
|
||||
GameMain.LobbyScreen.Select();
|
||||
}
|
||||
|
||||
private bool TryEndRound(Submarine leavingSub)
|
||||
{
|
||||
if (leavingSub == null) { return false; }
|
||||
|
||||
this.leavingSub = leavingSub;
|
||||
subsToLeaveBehind = GetSubsToLeaveBehind(leavingSub);
|
||||
atEndPosition = leavingSub.AtEndPosition;
|
||||
|
||||
if (subsToLeaveBehind.Any())
|
||||
{
|
||||
string msg = TextManager.Get(subsToLeaveBehind.Count == 1 ? "LeaveSubBehind" : "LeaveSubsBehind");
|
||||
|
||||
var msgBox = new GUIMessageBox(TextManager.Get("Warning"), msg, new string[] { TextManager.Get("Yes"), TextManager.Get("No") });
|
||||
msgBox.Buttons[0].OnClicked += (btn, userdata) => { EndRound(leavingSub); return true; } ;
|
||||
msgBox.Buttons[0].OnClicked += msgBox.Close;
|
||||
msgBox.Buttons[0].UserData = Submarine.Loaded.FindAll(s => !subsToLeaveBehind.Contains(s));
|
||||
|
||||
msgBox.Buttons[1].OnClicked += msgBox.Close;
|
||||
}
|
||||
else
|
||||
{
|
||||
EndRound(leavingSub);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool EndRound(Submarine leavingSub)
|
||||
{
|
||||
isRunning = false;
|
||||
|
||||
//var cinematic = new RoundEndCinematic(leavingSub, GameMain.GameScreen.Cam, 5.0f);
|
||||
|
||||
SoundPlayer.OverrideMusicType = CrewManager.GetCharacters().Any(c => !c.IsDead) ? "endround" : "crewdead";
|
||||
SoundPlayer.OverrideMusicDuration = 18.0f;
|
||||
|
||||
//CoroutineManager.StartCoroutine(EndCinematic(cinematic), "EndCinematic");
|
||||
End("");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*private IEnumerable<object> EndCinematic(RoundEndCinematic cinematic)
|
||||
{
|
||||
while (cinematic.Running)
|
||||
{
|
||||
if (Submarine.MainSub == null) yield return CoroutineStatus.Success;
|
||||
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
if (Submarine.MainSub != null) End("");
|
||||
|
||||
yield return CoroutineStatus.Success;
|
||||
}*/
|
||||
|
||||
public static SinglePlayerCampaign Load(XElement element)
|
||||
{
|
||||
SinglePlayerCampaign campaign = new SinglePlayerCampaign(GameModePreset.List.Find(gm => gm.Identifier == "singleplayercampaign"), null);
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "crew":
|
||||
GameMain.GameSession.CrewManager = new CrewManager(subElement, true);
|
||||
break;
|
||||
case "map":
|
||||
campaign.map = Map.LoadNew(subElement);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
campaign.Money = element.GetAttributeInt("money", 0);
|
||||
campaign.CheatsEnabled = element.GetAttributeBool("cheatsenabled", false);
|
||||
campaign.InitialSuppliesSpawned = element.GetAttributeBool("initialsuppliesspawned", false);
|
||||
if (campaign.CheatsEnabled)
|
||||
{
|
||||
DebugConsole.CheatsEnabled = true;
|
||||
#if USE_STEAM
|
||||
if (!SteamAchievementManager.CheatsEnabled)
|
||||
{
|
||||
SteamAchievementManager.CheatsEnabled = true;
|
||||
new GUIMessageBox("Cheats enabled", "Cheat commands have been enabled on the campaign. You will not receive Steam Achievements until you restart the game.");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
//backwards compatibility with older save files
|
||||
if (campaign.map == null)
|
||||
{
|
||||
string mapSeed = element.GetAttributeString("mapseed", "a");
|
||||
campaign.GenerateMap(mapSeed);
|
||||
campaign.map.SetLocation(element.GetAttributeInt("currentlocation", 0));
|
||||
}
|
||||
|
||||
campaign.savedOnStart = true;
|
||||
|
||||
return campaign;
|
||||
}
|
||||
|
||||
public override void Save(XElement element)
|
||||
{
|
||||
XElement modeElement = new XElement("SinglePlayerCampaign",
|
||||
// Refunds the money when save & quitting from the map if there are items selected in the store
|
||||
new XAttribute("money", Money + (CargoManager != null ? CargoManager.GetTotalItemCost() : 0)),
|
||||
new XAttribute("cheatsenabled", CheatsEnabled),
|
||||
new XAttribute("initialsuppliesspawned", InitialSuppliesSpawned));
|
||||
CrewManager.Save(modeElement);
|
||||
Map.Save(modeElement);
|
||||
element.Add(modeElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
+680
@@ -0,0 +1,680 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Tutorials
|
||||
{
|
||||
class BasicTutorial : ScenarioTutorial
|
||||
{
|
||||
public BasicTutorial(XElement element)
|
||||
: base(element)
|
||||
{
|
||||
}
|
||||
|
||||
public override IEnumerable<object> UpdateState()
|
||||
{
|
||||
Character Controlled = Character.Controlled;
|
||||
if (Controlled == null) yield return CoroutineStatus.Success;
|
||||
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
var wire = item.GetComponent<Wire>();
|
||||
if (wire != null && wire.Connections.Any(c => c != null))
|
||||
{
|
||||
wire.Locked = true;
|
||||
}
|
||||
}
|
||||
|
||||
//remove all characters except the controlled one to prevent any unintended monster attacks
|
||||
var existingCharacters = Character.CharacterList.FindAll(c => c != Controlled);
|
||||
foreach (Character c in existingCharacters)
|
||||
{
|
||||
c.Remove();
|
||||
}
|
||||
|
||||
yield return new WaitForSeconds(4.0f);
|
||||
|
||||
infoBox = CreateInfoFrame("", "Use WASD to move and the mouse to look around");
|
||||
|
||||
yield return new WaitForSeconds(5.0f);
|
||||
|
||||
//-----------------------------------
|
||||
|
||||
infoBox = CreateInfoFrame("", "Open the door at your right side by highlighting the button next to it with your cursor and pressing E");
|
||||
|
||||
Door tutorialDoor = Item.ItemList.Find(i => i.HasTag("tutorialdoor")).GetComponent<Door>();
|
||||
|
||||
while (!tutorialDoor.IsOpen && Controlled.WorldPosition.X < tutorialDoor.Item.WorldPosition.X)
|
||||
{
|
||||
yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
yield return new WaitForSeconds(2.0f);
|
||||
|
||||
//-----------------------------------
|
||||
|
||||
infoBox = CreateInfoFrame("", "Hold W or S to walk up or down stairs. Use shift to run.", hasButton: true);
|
||||
|
||||
while (infoBox != null)
|
||||
{
|
||||
yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
//-----------------------------------
|
||||
|
||||
infoBox = CreateInfoFrame("", "At the moment the submarine has no power, which means that crucial systems such as the oxygen generator or the engine aren't running. Let's fix this: go to the upper left corner of the submarine, where you'll find a nuclear reactor.");
|
||||
|
||||
Reactor reactor = Item.ItemList.Find(i => i.HasTag("tutorialreactor")).GetComponent<Reactor>();
|
||||
//reactor.MeltDownTemp = 20000.0f;
|
||||
|
||||
while (Vector2.Distance(Controlled.Position, reactor.Item.Position) > 200.0f)
|
||||
{
|
||||
yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
infoBox = CreateInfoFrame("", "The reactor requires fuel rods to generate power. You can grab one from the steel cabinet by walking next to it and pressing E.");
|
||||
|
||||
while (Controlled.SelectedConstruction == null || Controlled.SelectedConstruction.Prefab.Identifier != "steelcabinet")
|
||||
{
|
||||
yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
infoBox = CreateInfoFrame("", "Pick up one of the fuel rods either by double-clicking or dragging and dropping it into your inventory.");
|
||||
|
||||
while (!HasItem("fuelrod"))
|
||||
{
|
||||
yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
infoBox = CreateInfoFrame("", "Select the reactor by walking next to it and pressing E.");
|
||||
|
||||
while (Controlled.SelectedConstruction != reactor.Item)
|
||||
{
|
||||
yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running;
|
||||
}
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
|
||||
infoBox = CreateInfoFrame("", "Load the fuel rod into the reactor by dropping it into any of the 5 slots.");
|
||||
|
||||
while (reactor.AvailableFuel <= 0.0f)
|
||||
{
|
||||
yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
infoBox = CreateInfoFrame("", "The reactor is now fueled up. Try turning it on by increasing the fission rate.");
|
||||
|
||||
while (reactor.FissionRate <= 0.0f)
|
||||
{
|
||||
yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running;
|
||||
}
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
|
||||
infoBox = CreateInfoFrame("", "The reactor core has started generating heat, which in turn generates power for the submarine. The power generation is very low at the moment,"
|
||||
+ " because the reactor is set to shut itself down when the temperature rises above 500 degrees Celsius. You can adjust the temperature limit by changing the \"Shutdown Temperature\" in the control panel.", hasButton: true);
|
||||
|
||||
//TODO: reimplement
|
||||
/*while (infoBox != null)
|
||||
{
|
||||
reactor.ShutDownTemp = Math.Min(reactor.ShutDownTemp, 5000.0f);
|
||||
yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running;
|
||||
}
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
|
||||
infoBox = CreateInfoFrame("The amount of power generated by the reactor should be kept close to the amount of power consumed by the devices in the submarine. "
|
||||
+ "If there isn't enough power, devices won't function properly (or at all), and if there's too much power, some devices may be damaged."
|
||||
+ " Try to raise the temperature of the reactor close to 3000 degrees by adjusting the fission and cooling rates.", true);
|
||||
|
||||
while (Math.Abs(reactor.Temperature - 3000.0f) > 100.0f)
|
||||
{
|
||||
reactor.AutoTemp = false;
|
||||
reactor.ShutDownTemp = Math.Min(reactor.ShutDownTemp, 5000.0f);
|
||||
yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running;
|
||||
}
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
|
||||
infoBox = CreateInfoFrame("Looks like we're up and running! Now you should turn on the \"Automatic temperature control\", which will make the reactor "
|
||||
+ "automatically adjust the temperature to a suitable level. Even though it's an easy way to keep the reactor up and running most of the time, "
|
||||
+ "you should keep in mind that it changes the temperature very slowly and carefully, which may cause issues if there are sudden changes in grid load.");
|
||||
|
||||
while (!reactor.AutoTemp)
|
||||
{
|
||||
yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running;
|
||||
}*/
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
|
||||
infoBox = CreateInfoFrame("", "That's the basics of operating the reactor! Now that there's power available for the engines, it's time to get the submarine moving. "
|
||||
+ "Deselect the reactor by pressing E and head to the command room at the right edge of the vessel.");
|
||||
|
||||
Steering steering = Item.ItemList.Find(i => i.HasTag("tutorialsteering")).GetComponent<Steering>();
|
||||
Sonar sonar = steering.Item.GetComponent<Sonar>();
|
||||
|
||||
while (Vector2.Distance(Controlled.Position, steering.Item.Position) > 150.0f)
|
||||
{
|
||||
yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
CoroutineManager.StartCoroutine(KeepReactorRunning(reactor));
|
||||
|
||||
infoBox = CreateInfoFrame("", "Select the navigation terminal by walking next to it and pressing E.");
|
||||
|
||||
while (Controlled.SelectedConstruction != steering.Item)
|
||||
{
|
||||
yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running;
|
||||
}
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
|
||||
infoBox = CreateInfoFrame("", "There seems to be something wrong with the navigation terminal." +
|
||||
" There's nothing on the monitor, so it's probably out of power. The reactor must still be"
|
||||
+ " running or the lights would've gone out, so it's most likely a problem with the wiring."
|
||||
+ " Deselect the terminal by pressing E to start checking the wiring.");
|
||||
|
||||
while (Controlled.SelectedConstruction == steering.Item)
|
||||
{
|
||||
yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running;
|
||||
}
|
||||
yield return new WaitForSeconds(1.0f);
|
||||
|
||||
infoBox = CreateInfoFrame("", "You need a screwdriver to check the wiring of the terminal."
|
||||
+ " Equip a screwdriver by pulling it to either of the slots with a hand symbol, and then use it on the terminal by left clicking.");
|
||||
|
||||
while (Controlled.SelectedConstruction != steering.Item ||
|
||||
Controlled.SelectedItems.FirstOrDefault(i => i != null && i.Prefab.Identifier == "screwdriver") == null)
|
||||
{
|
||||
yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
|
||||
infoBox = CreateInfoFrame("", "Here you can see all the wires connected to the terminal. Apparently there's no wire"
|
||||
+ " going into the to the power connection - that's why the monitor isn't working."
|
||||
+ " You should find a piece of wire to connect it. Try searching some of the cabinets scattered around the sub.");
|
||||
|
||||
while (!HasItem("wire"))
|
||||
{
|
||||
yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
infoBox = CreateInfoFrame("", "Head back to the navigation terminal to fix the wiring.");
|
||||
|
||||
PowerTransfer junctionBox = Item.ItemList.Find(i => i != null && i.HasTag("tutorialjunctionbox")).GetComponent<PowerTransfer>();
|
||||
|
||||
while ((Controlled.SelectedConstruction != junctionBox.Item &&
|
||||
Controlled.SelectedConstruction != steering.Item) ||
|
||||
Controlled.SelectedItems.FirstOrDefault(i => i != null && i.Prefab.Identifier == "screwdriver") == null)
|
||||
{
|
||||
yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
if (Controlled.SelectedItems.FirstOrDefault(i => i != null && i.GetComponent<Wire>() != null) == null)
|
||||
{
|
||||
infoBox = CreateInfoFrame("", "Equip the wire by dragging it to one of the slots with a hand symbol.");
|
||||
|
||||
while (Controlled.SelectedItems.FirstOrDefault(i => i != null && i.GetComponent<Wire>() != null) == null)
|
||||
{
|
||||
yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running;
|
||||
}
|
||||
}
|
||||
|
||||
infoBox = CreateInfoFrame("", "You can see the equipped wire at the middle of the connection panel. Drag it to the power connector.");
|
||||
|
||||
var steeringConnection = steering.Item.Connections.Find(c => c.Name.Contains("power"));
|
||||
|
||||
while (steeringConnection.Wires.FirstOrDefault(w => w != null) == null)
|
||||
{
|
||||
yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running;
|
||||
|
||||
}
|
||||
|
||||
infoBox = CreateInfoFrame("", "Now you have to connect the other end of the wire to a power source. "
|
||||
+ "The junction box in the room just below the command room should do.");
|
||||
|
||||
while (Controlled.SelectedConstruction != null)
|
||||
{
|
||||
yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
yield return new WaitForSeconds(2.0f);
|
||||
|
||||
infoBox = CreateInfoFrame("", "You can now move the other end of the wire around, and attach it on the wall by left clicking or "
|
||||
+ "remove the previous attachment by right clicking. Or if you don't care for neatly laid out wiring, you can just "
|
||||
+ "run it straight to the junction box.");
|
||||
|
||||
while (Controlled.SelectedConstruction == null || Controlled.SelectedConstruction.GetComponent<PowerTransfer>() == null)
|
||||
{
|
||||
yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
infoBox = CreateInfoFrame("", "Connect the wire to the junction box by pulling it to the power connection, the same way you did with the navigation terminal.");
|
||||
|
||||
while (sonar.Voltage < 0.1f)
|
||||
{
|
||||
yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
infoBox = CreateInfoFrame("", "Great! Now we should be able to get moving.");
|
||||
|
||||
|
||||
while (Controlled.SelectedConstruction != steering.Item)
|
||||
{
|
||||
yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
infoBox = CreateInfoFrame("", "You can take a look at the area around the sub by selecting the \"Active Sonar\" checkbox.");
|
||||
|
||||
while (!sonar.IsActive)
|
||||
{
|
||||
yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running;
|
||||
}
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
|
||||
infoBox = CreateInfoFrame("", "The blue rectangle in the middle is the submarine, and the flickering shapes outside it are the walls of an underwater cavern. "
|
||||
+ "Try moving the submarine by clicking somewhere on the monitor and dragging the pointer to the direction you want to go to.");
|
||||
|
||||
while (steering.TargetVelocity == Vector2.Zero && steering.TargetVelocity.Length() < 50.0f)
|
||||
{
|
||||
yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running;
|
||||
}
|
||||
yield return new WaitForSeconds(4.0f);
|
||||
|
||||
infoBox = CreateInfoFrame("", "The submarine moves up and down by pumping water in and out of the two ballast tanks at the bottom of the submarine. "
|
||||
+ "The engine at the back of the sub moves it forwards and backwards.", hasButton: true);
|
||||
|
||||
while (infoBox != null)
|
||||
{
|
||||
yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
infoBox = CreateInfoFrame("", "Steer the submarine downwards, heading further into the cavern.");
|
||||
|
||||
while (Submarine.MainSub.WorldPosition.Y > 32000.0f)
|
||||
{
|
||||
yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running;
|
||||
}
|
||||
yield return new WaitForSeconds(1.0f);
|
||||
|
||||
var moloch = Character.Create("moloch", steering.Item.WorldPosition + new Vector2(3000.0f, -500.0f), "");
|
||||
|
||||
moloch.PlaySound(CharacterSound.SoundType.Attack);
|
||||
|
||||
yield return new WaitForSeconds(1.0f);
|
||||
|
||||
infoBox = CreateInfoFrame("", "Uh-oh... Something enormous just appeared on the sonar.");
|
||||
|
||||
List<Structure> windows = new List<Structure>();
|
||||
foreach (Structure s in Structure.WallList)
|
||||
{
|
||||
if (s.CastShadow || !s.HasBody) continue;
|
||||
|
||||
if (s.Rect.Right > steering.Item.CurrentHull.Rect.Right) windows.Add(s);
|
||||
}
|
||||
|
||||
float slowdownTimer = 1.0f;
|
||||
bool broken = false;
|
||||
do
|
||||
{
|
||||
steering.TargetVelocity = Vector2.Zero;
|
||||
|
||||
slowdownTimer = Math.Max(0.0f, slowdownTimer - CoroutineManager.DeltaTime * 0.3f);
|
||||
Submarine.MainSub.Velocity *= slowdownTimer;
|
||||
|
||||
moloch.AIController.SelectTarget(steering.Item.CurrentHull.AiTarget);
|
||||
Vector2 steeringDir = windows[0].WorldPosition - moloch.WorldPosition;
|
||||
if (steeringDir != Vector2.Zero) steeringDir = Vector2.Normalize(steeringDir);
|
||||
|
||||
moloch.AIController.SteeringManager.SteeringManual(CoroutineManager.DeltaTime, steeringDir * 100.0f);
|
||||
|
||||
foreach (Structure window in windows)
|
||||
{
|
||||
for (int i = 0; i < window.SectionCount; i++)
|
||||
{
|
||||
if (!window.SectionIsLeaking(i)) continue;
|
||||
broken = true;
|
||||
break;
|
||||
}
|
||||
if (broken) break;
|
||||
}
|
||||
if (broken) break;
|
||||
|
||||
yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running;
|
||||
} while (!broken);
|
||||
|
||||
//fix everything except the command windows
|
||||
foreach (Structure w in Structure.WallList)
|
||||
{
|
||||
bool isWindow = windows.Contains(w);
|
||||
|
||||
for (int i = 0; i < w.SectionCount; i++)
|
||||
{
|
||||
if (!w.SectionIsLeaking(i)) continue;
|
||||
|
||||
if (isWindow)
|
||||
{
|
||||
//decrease window damage to slow down the leaking
|
||||
w.AddDamage(i, -w.SectionDamage(i) * 0.48f);
|
||||
}
|
||||
else
|
||||
{
|
||||
w.AddDamage(i, -100000.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Submarine.MainSub.GodMode = true;
|
||||
|
||||
var capacitor1 = Item.ItemList.Find(i => i.HasTag("capacitor1")).GetComponent<PowerContainer>();
|
||||
var capacitor2 = Item.ItemList.Find(i => i.HasTag("capacitor1")).GetComponent<PowerContainer>();
|
||||
CoroutineManager.StartCoroutine(KeepEnemyAway(moloch, new PowerContainer[] { capacitor1, capacitor2 }));
|
||||
|
||||
infoBox = CreateInfoFrame("", "The hull has been breached! Close all the doors to the command room to stop the water from flooding the entire sub!");
|
||||
|
||||
Door commandDoor1 = Item.ItemList.Find(i => i.HasTag("commanddoor1")).GetComponent<Door>();
|
||||
Door commandDoor2 = Item.ItemList.Find(i => i.HasTag("commanddoor2")).GetComponent<Door>();
|
||||
|
||||
//wait until the player is out of the room and the doors are closed
|
||||
while (Controlled.WorldPosition.X > commandDoor1.Item.WorldPosition.X ||
|
||||
(commandDoor1.IsOpen || commandDoor2.IsOpen))
|
||||
{
|
||||
//prevent the hull from filling up completely and crushing the player
|
||||
steering.Item.CurrentHull.WaterVolume = Math.Min(steering.Item.CurrentHull.WaterVolume, steering.Item.CurrentHull.Volume * 0.9f);
|
||||
yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
|
||||
infoBox = CreateInfoFrame("", "You should quickly find yourself a diving mask or a diving suit. " +
|
||||
"There are some in the room next to the airlock.");
|
||||
|
||||
bool divingMaskSelected = false;
|
||||
|
||||
while (!HasItem("divingmask") && !HasItem("divingsuit"))
|
||||
{
|
||||
if (!divingMaskSelected &&
|
||||
Controlled.FocusedItem != null && Controlled.FocusedItem.Prefab.Identifier == "divingsuit")
|
||||
{
|
||||
infoBox = CreateInfoFrame("", "There can only be one item in each inventory slot, so you need to take off "
|
||||
+ "the jumpsuit if you wish to wear a diving suit.");
|
||||
|
||||
divingMaskSelected = true;
|
||||
}
|
||||
|
||||
yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
if (HasItem("divingmask"))
|
||||
{
|
||||
infoBox = CreateInfoFrame("", "The diving mask will let you breathe underwater, but it won't protect from the water pressure outside the sub. " +
|
||||
"It should be fine for the situation at hand, but you still need to find an oxygen tank and drag it into the same slot as the mask." +
|
||||
"You should grab one or two from one of the cabinets.");
|
||||
}
|
||||
else if (HasItem("divingsuit"))
|
||||
{
|
||||
infoBox = CreateInfoFrame("", "In addition to letting you breathe underwater, the suit will protect you from the water pressure outside the sub " +
|
||||
"(unlike the diving mask). However, you still need to drag an oxygen tank into the same slot as the suit to supply oxygen. " +
|
||||
"You should grab one or two from one of the cabinets.");
|
||||
}
|
||||
|
||||
while (!HasItem("oxygentank"))
|
||||
{
|
||||
yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
yield return new WaitForSeconds(5.0f);
|
||||
|
||||
infoBox = CreateInfoFrame("", "Now you should stop the creature attacking the submarine before it does any more damage. Head to the railgun room at the upper right corner of the sub.");
|
||||
|
||||
var railGun = Item.ItemList.Find(i => i.GetComponent<Turret>() != null);
|
||||
|
||||
while (Vector2.Distance(Controlled.Position, railGun.Position) > 500)
|
||||
{
|
||||
yield return new WaitForSeconds(1.0f);
|
||||
}
|
||||
|
||||
infoBox = CreateInfoFrame("", "The railgun requires a large power surge to fire. The reactor can't provide a surge large enough, so we need to use the "
|
||||
+ " supercapacitors in the railgun room. The capacitors need to be charged first; select them and crank up the recharge rate.");
|
||||
|
||||
while (capacitor1.RechargeSpeed < 0.5f && capacitor2.RechargeSpeed < 0.5f)
|
||||
{
|
||||
yield return new WaitForSeconds(1.0f);
|
||||
}
|
||||
|
||||
infoBox = CreateInfoFrame("", "The capacitors take some time to recharge, so now is a good " +
|
||||
"time to head to the room below and load some shells for the railgun.");
|
||||
|
||||
|
||||
var loader = Item.ItemList.Find(i => i.Prefab.Identifier == "railgunloader").GetComponent<ItemContainer>();
|
||||
|
||||
while (Math.Abs(Controlled.Position.Y - loader.Item.Position.Y) > 80)
|
||||
{
|
||||
yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
infoBox = CreateInfoFrame("", "Grab one of the shells. You can load it by selecting the railgun loader and dragging the shell to. "
|
||||
+ "one of the free slots. You need two hands to carry a shell, so make sure you don't have anything else in either hand.");
|
||||
|
||||
while (loader.Item.ContainedItems.FirstOrDefault(i => i != null && i.Prefab.Identifier == "railgunshell") == null)
|
||||
{
|
||||
//TODO: reimplement
|
||||
//moloch.Health = 50.0f;
|
||||
|
||||
capacitor1.Charge += 5.0f;
|
||||
capacitor2.Charge += 5.0f;
|
||||
yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
infoBox = CreateInfoFrame("", "Now we're ready to shoot! Select the railgun controller.");
|
||||
|
||||
while (Controlled.SelectedConstruction == null || Controlled.SelectedConstruction.Prefab.Identifier != "railguncontroller")
|
||||
{
|
||||
yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
moloch.AnimController.SetPosition(ConvertUnits.ToSimUnits(Controlled.WorldPosition + Vector2.UnitY * 600.0f));
|
||||
|
||||
infoBox = CreateInfoFrame("", "Use the right mouse button to aim and wait for the creature to come closer. When you're ready to shoot, "
|
||||
+ "press the left mouse button.");
|
||||
|
||||
while (!moloch.IsDead)
|
||||
{
|
||||
if (moloch.WorldPosition.Y > Controlled.WorldPosition.Y + 600.0f)
|
||||
{
|
||||
moloch.AIController.SteeringManager.SteeringManual(CoroutineManager.DeltaTime, Controlled.WorldPosition - moloch.WorldPosition);
|
||||
}
|
||||
|
||||
moloch.AIController.SelectTarget(Controlled.AiTarget);
|
||||
yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
Submarine.MainSub.GodMode = false;
|
||||
|
||||
infoBox = CreateInfoFrame("", "The creature has died. Now you should fix the damages in the control room: " +
|
||||
"Grab a welding tool from the closet in the railgun room.");
|
||||
|
||||
while (!HasItem("weldingtool"))
|
||||
{
|
||||
yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
infoBox = CreateInfoFrame("", "The welding tool requires fuel to work. Grab a welding fuel tank and attach it to the tool " +
|
||||
"by dragging it into the same slot.");
|
||||
|
||||
do
|
||||
{
|
||||
var weldingTool = Controlled.Inventory.Items.FirstOrDefault(i => i != null && i.Prefab.Identifier == "weldingtool");
|
||||
if (weldingTool != null &&
|
||||
weldingTool.ContainedItems.FirstOrDefault(contained => contained != null && contained.Prefab.Identifier == "weldingfueltank") != null) break;
|
||||
|
||||
yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running;
|
||||
} while (true);
|
||||
|
||||
|
||||
infoBox = CreateInfoFrame("", "You can aim with the tool using the right mouse button and weld using the left button. " +
|
||||
"Head to the command room to fix the leaks there.");
|
||||
|
||||
do
|
||||
{
|
||||
broken = false;
|
||||
foreach (Structure window in windows)
|
||||
{
|
||||
for (int i = 0; i < window.SectionCount; i++)
|
||||
{
|
||||
if (!window.SectionIsLeaking(i)) continue;
|
||||
broken = true;
|
||||
break;
|
||||
}
|
||||
if (broken) break;
|
||||
}
|
||||
|
||||
yield return new WaitForSeconds(1.0f);
|
||||
} while (broken);
|
||||
|
||||
infoBox = CreateInfoFrame("", "The hull is fixed now, but there's still quite a bit of water inside the sub. It should be pumped out "
|
||||
+ "using the bilge pump in the room at the bottom of the submarine.");
|
||||
|
||||
Pump pump = Item.ItemList.Find(i => i.HasTag("tutorialpump")).GetComponent<Pump>();
|
||||
|
||||
while (Vector2.Distance(Controlled.Position, pump.Item.Position) > 100.0f)
|
||||
{
|
||||
yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
infoBox = CreateInfoFrame("", "The two pumps inside the ballast tanks "
|
||||
+ "are connected straight to the navigation terminal and can't be manually controlled unless you mess with their wiring, " +
|
||||
"so you should only use the pump in the middle room to pump out the water. Select it, turn it on and adjust the pumping speed " +
|
||||
"to start pumping water out.", hasButton: true);
|
||||
|
||||
while (infoBox != null)
|
||||
{
|
||||
yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
|
||||
bool brokenMsgShown = false;
|
||||
|
||||
Item brokenBox = null;
|
||||
|
||||
while (pump.FlowPercentage > 0.0f || pump.CurrFlow <= 0.0f || !pump.IsActive)
|
||||
{
|
||||
if (!brokenMsgShown && pump.Voltage < pump.MinVoltage && Controlled.SelectedConstruction == pump.Item)
|
||||
{
|
||||
brokenMsgShown = true;
|
||||
|
||||
infoBox = CreateInfoFrame("", "Looks like the pump isn't getting any power. The water must have short-circuited some of the junction "
|
||||
+ "boxes. You can check which boxes are broken by selecting them.");
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (Controlled.SelectedConstruction!=null &&
|
||||
Controlled.SelectedConstruction.GetComponent<PowerTransfer>() != null &&
|
||||
Controlled.SelectedConstruction.Condition == 0.0f)
|
||||
{
|
||||
brokenBox = Controlled.SelectedConstruction;
|
||||
|
||||
infoBox = CreateInfoFrame("", "Here's our problem: this junction box is broken. Luckily engineers are adept at fixing electrical devices - "
|
||||
+ "you just need to find a spare wire and click the \"Fix\"-button to repair the box.");
|
||||
break;
|
||||
}
|
||||
|
||||
if (pump.Voltage > pump.MinVoltage) break;
|
||||
|
||||
yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running;
|
||||
}
|
||||
}
|
||||
|
||||
if (brokenBox != null && brokenBox.ConditionPercentage > 50.0f && pump.Voltage < pump.MinVoltage)
|
||||
{
|
||||
yield return new WaitForSeconds(1.0f);
|
||||
|
||||
if (pump.Voltage < pump.MinVoltage)
|
||||
{
|
||||
infoBox = CreateInfoFrame("", "The pump is still not running. Check if there are more broken junction boxes between the pump and the reactor.");
|
||||
}
|
||||
brokenBox = null;
|
||||
}
|
||||
|
||||
yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
infoBox = CreateInfoFrame("", "The pump is up and running. Wait for the water to be drained out.");
|
||||
|
||||
while (pump.Item.CurrentHull.WaterVolume > 1000.0f)
|
||||
{
|
||||
yield return Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
infoBox = CreateInfoFrame("", "That was all there is to this tutorial! Now you should be able to handle " +
|
||||
"most of the basic tasks on board the submarine.");
|
||||
|
||||
Completed = true;
|
||||
|
||||
yield return new WaitForSeconds(4.0f);
|
||||
|
||||
Controlled = null;
|
||||
GameMain.GameScreen.Cam.TargetPos = Vector2.Zero;
|
||||
GameMain.LightManager.LosEnabled = false;
|
||||
|
||||
var cinematic = new RoundEndCinematic(Submarine.MainSub, GameMain.GameScreen.Cam, 5.0f);
|
||||
|
||||
while (cinematic.Running)
|
||||
{
|
||||
yield return Controlled != null && Controlled.IsDead ? CoroutineStatus.Success : CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
Submarine.Unload();
|
||||
GameMain.MainMenuScreen.Select();
|
||||
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
|
||||
private bool HasItem(string itemIdentifier)
|
||||
{
|
||||
if (Character.Controlled == null) return false;
|
||||
|
||||
return Character.Controlled.Inventory.FindItemByIdentifier(itemIdentifier) != null;
|
||||
}
|
||||
|
||||
protected IEnumerable<object> KeepReactorRunning(Reactor reactor)
|
||||
{
|
||||
do
|
||||
{
|
||||
//TODO: reimplement
|
||||
/*reactor.AutoTemp = true;
|
||||
reactor.ShutDownTemp = 5000.0f;*/
|
||||
|
||||
yield return CoroutineStatus.Running;
|
||||
} while (Item.ItemList.Contains(reactor.Item));
|
||||
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// keeps the enemy away from the sub until the capacitors are loaded
|
||||
/// </summary>
|
||||
private IEnumerable<object> KeepEnemyAway(Character enemy, PowerContainer[] capacitors)
|
||||
{
|
||||
do
|
||||
{
|
||||
if (enemy == null || Character.Controlled == null) break;
|
||||
|
||||
//TODO: reimplement
|
||||
//enemy.Health = 50.0f;
|
||||
|
||||
enemy.AIController.State = AIState.Idle;
|
||||
|
||||
Vector2 targetPos = Character.Controlled.WorldPosition + new Vector2(0.0f, 3000.0f);
|
||||
|
||||
Vector2 steering = targetPos - enemy.WorldPosition;
|
||||
if (steering != Vector2.Zero) steering = Vector2.Normalize(steering);
|
||||
|
||||
enemy.AIController.Steering = steering;
|
||||
|
||||
yield return CoroutineStatus.Running;
|
||||
} while (capacitors.FirstOrDefault(c => c.Charge > 0.4f) == null);
|
||||
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+290
@@ -0,0 +1,290 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma.Tutorials
|
||||
{
|
||||
class CaptainTutorial : ScenarioTutorial
|
||||
{
|
||||
// Room 1
|
||||
private float shakeTimer = 1f;
|
||||
private float shakeAmount = 20f;
|
||||
|
||||
// Room 2
|
||||
private MotionSensor captain_equipmentObjectiveSensor;
|
||||
private ItemContainer captain_equipmentCabinet;
|
||||
private Door captain_firstDoor;
|
||||
private LightComponent captain_firstDoorLight;
|
||||
|
||||
// Room 3
|
||||
private Character captain_medic;
|
||||
private MotionSensor captain_medicObjectiveSensor;
|
||||
private Vector2 captain_medicSpawnPos;
|
||||
private Door tutorial_submarineDoor;
|
||||
private LightComponent tutorial_submarineDoorLight;
|
||||
|
||||
// Submarine
|
||||
private MotionSensor captain_enteredSubmarineSensor;
|
||||
private Steering captain_navConsole;
|
||||
private CustomInterface captain_navConsoleCustomInterface;
|
||||
private Sonar captain_sonar;
|
||||
private Item captain_statusMonitor;
|
||||
private Character captain_security;
|
||||
private Character captain_mechanic;
|
||||
private Character captain_engineer;
|
||||
private Reactor tutorial_submarineReactor;
|
||||
private Door tutorial_lockedDoor_1;
|
||||
private Door tutorial_lockedDoor_2;
|
||||
|
||||
// Variables
|
||||
private Character captain;
|
||||
private string radioSpeakerName;
|
||||
private Sprite captain_steerIcon;
|
||||
private Color captain_steerIconColor;
|
||||
|
||||
public CaptainTutorial(XElement element) : base(element)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Start()
|
||||
{
|
||||
base.Start();
|
||||
|
||||
captain = Character.Controlled;
|
||||
radioSpeakerName = TextManager.Get("Tutorial.Radio.Watchman");
|
||||
GameMain.GameSession.CrewManager.AllowCharacterSwitch = false;
|
||||
|
||||
var revolver = FindOrGiveItem(captain, "revolver");
|
||||
revolver.Unequip(captain);
|
||||
captain.Inventory.RemoveItem(revolver);
|
||||
|
||||
var captainscap =
|
||||
captain.Inventory.FindItemByIdentifier("captainscap1") ??
|
||||
captain.Inventory.FindItemByIdentifier("captainscap2") ??
|
||||
captain.Inventory.FindItemByIdentifier("captainscap3");
|
||||
|
||||
if (captainscap != null)
|
||||
{
|
||||
captainscap.Unequip(captain);
|
||||
captain.Inventory.RemoveItem(captainscap);
|
||||
}
|
||||
|
||||
var captainsuniform =
|
||||
captain.Inventory.FindItemByIdentifier("captainsuniform1") ??
|
||||
captain.Inventory.FindItemByIdentifier("captainsuniform2") ??
|
||||
captain.Inventory.FindItemByIdentifier("captainsuniform3");
|
||||
if (captainsuniform != null)
|
||||
{
|
||||
captainsuniform.Unequip(captain);
|
||||
captain.Inventory.RemoveItem(captainsuniform);
|
||||
}
|
||||
|
||||
var steerOrder = Order.GetPrefab("steer");
|
||||
captain_steerIcon = steerOrder.SymbolSprite;
|
||||
captain_steerIconColor = steerOrder.Color;
|
||||
|
||||
// Room 2
|
||||
captain_equipmentObjectiveSensor = Item.ItemList.Find(i => i.HasTag("captain_equipmentobjectivesensor")).GetComponent<MotionSensor>();
|
||||
captain_equipmentCabinet = Item.ItemList.Find(i => i.HasTag("captain_equipmentcabinet")).GetComponent<ItemContainer>();
|
||||
captain_firstDoor = Item.ItemList.Find(i => i.HasTag("captain_firstdoor")).GetComponent<Door>();
|
||||
captain_firstDoorLight = Item.ItemList.Find(i => i.HasTag("captain_firstdoorlight")).GetComponent<LightComponent>();
|
||||
|
||||
SetDoorAccess(captain_firstDoor, captain_firstDoorLight, true);
|
||||
|
||||
// Room 3
|
||||
captain_medicObjectiveSensor = Item.ItemList.Find(i => i.HasTag("captain_medicobjectivesensor")).GetComponent<MotionSensor>();
|
||||
captain_medicSpawnPos = Item.ItemList.Find(i => i.HasTag("captain_medicspawnpos")).WorldPosition;
|
||||
tutorial_submarineDoor = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoor")).GetComponent<Door>();
|
||||
tutorial_submarineDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoorlight")).GetComponent<LightComponent>();
|
||||
var medicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("medicaldoctor"));
|
||||
captain_medic = Character.Create(medicInfo, captain_medicSpawnPos, "medicaldoctor");
|
||||
captain_medic.GiveJobItems(null);
|
||||
captain_medic.CanSpeak = captain_medic.AIController.Enabled = false;
|
||||
SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, false);
|
||||
|
||||
// Submarine
|
||||
captain_enteredSubmarineSensor = Item.ItemList.Find(i => i.HasTag("captain_enteredsubmarinesensor")).GetComponent<MotionSensor>();
|
||||
tutorial_submarineReactor = Item.ItemList.Find(i => i.HasTag("engineer_submarinereactor")).GetComponent<Reactor>();
|
||||
captain_navConsole = Item.ItemList.Find(i => i.HasTag("command")).GetComponent<Steering>();
|
||||
captain_navConsoleCustomInterface = Item.ItemList.Find(i => i.HasTag("command")).GetComponent<CustomInterface>();
|
||||
captain_sonar = captain_navConsole.Item.GetComponent<Sonar>();
|
||||
captain_statusMonitor = Item.ItemList.Find(i => i.HasTag("captain_statusmonitor"));
|
||||
|
||||
tutorial_submarineReactor.CanBeSelected = false;
|
||||
tutorial_submarineReactor.IsActive = tutorial_submarineReactor.AutoTemp = false;
|
||||
|
||||
tutorial_lockedDoor_1 = Item.ItemList.Find(i => i.HasTag("tutorial_lockeddoor_1")).GetComponent<Door>();
|
||||
tutorial_lockedDoor_2 = Item.ItemList.Find(i => i.HasTag("tutorial_lockeddoor_2")).GetComponent<Door>();
|
||||
SetDoorAccess(tutorial_lockedDoor_1, null, false);
|
||||
SetDoorAccess(tutorial_lockedDoor_2, null, false);
|
||||
|
||||
var mechanicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("mechanic"));
|
||||
captain_mechanic = Character.Create(mechanicInfo, WayPoint.GetRandom(SpawnType.Human, mechanicInfo.Job, Submarine.MainSub).WorldPosition, "mechanic");
|
||||
captain_mechanic.GiveJobItems();
|
||||
|
||||
var securityInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("securityofficer"));
|
||||
captain_security = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, securityInfo.Job, Submarine.MainSub).WorldPosition, "securityofficer");
|
||||
captain_security.GiveJobItems();
|
||||
|
||||
var engineerInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("engineer"));
|
||||
captain_engineer = Character.Create(engineerInfo, WayPoint.GetRandom(SpawnType.Human, engineerInfo.Job, Submarine.MainSub).WorldPosition, "engineer");
|
||||
captain_engineer.GiveJobItems();
|
||||
|
||||
captain_mechanic.CanSpeak = captain_security.CanSpeak = captain_engineer.CanSpeak = false;
|
||||
captain_mechanic.AIController.Enabled = captain_security.AIController.Enabled = captain_engineer.AIController.Enabled = false;
|
||||
}
|
||||
|
||||
public override IEnumerable<object> UpdateState()
|
||||
{
|
||||
while (GameMain.Instance.LoadingScreenOpen) yield return null;
|
||||
|
||||
// Room 1
|
||||
while (shakeTimer > 0.0f) // Wake up, shake
|
||||
{
|
||||
shakeTimer -= 0.1f;
|
||||
GameMain.GameScreen.Cam.Shake = shakeAmount;
|
||||
yield return new WaitForSeconds(0.1f, false);
|
||||
}
|
||||
|
||||
// Room 2
|
||||
do { yield return null; } while (!captain_firstDoor.IsOpen);
|
||||
captain_medic.AIController.Enabled = true;
|
||||
|
||||
// Room 3
|
||||
do { yield return null; } while (!captain_medicObjectiveSensor.MotionDetected);
|
||||
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(captain_medic.Info.DisplayName, TextManager.Get("Captain.Radio.Medic"), ChatMessageType.Radio, null);
|
||||
yield return new WaitForSeconds(2f, false);
|
||||
GameMain.GameSession.CrewManager.ToggleCrewListOpen = true;
|
||||
GameMain.GameSession.CrewManager.AddCharacter(captain_medic);
|
||||
TriggerTutorialSegment(0, GameMain.Config.KeyBindText(InputType.Command));
|
||||
do
|
||||
{
|
||||
yield return null;
|
||||
// TODO: Rework order highlighting for new command UI
|
||||
// GameMain.GameSession.CrewManager.HighlightOrderButton(captain_medic, "follow", highlightColor, new Vector2(5, 5));
|
||||
}
|
||||
while (!HasOrder(captain_medic, "follow"));
|
||||
SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, true);
|
||||
RemoveCompletedObjective(segments[0]);
|
||||
|
||||
// Submarine
|
||||
do { yield return null; } while (!captain_enteredSubmarineSensor.MotionDetected);
|
||||
yield return new WaitForSeconds(3f, false);
|
||||
captain_mechanic.AIController.Enabled = captain_security.AIController.Enabled = captain_engineer.AIController.Enabled = true;
|
||||
TriggerTutorialSegment(1, GameMain.Config.KeyBindText(InputType.Command));
|
||||
GameMain.GameSession.CrewManager.AddCharacter(captain_mechanic);
|
||||
do
|
||||
{
|
||||
yield return null;
|
||||
// TODO: Rework order highlighting for new command UI
|
||||
// GameMain.GameSession.CrewManager.HighlightOrderButton(captain_mechanic, "repairsystems", highlightColor, new Vector2(5, 5));
|
||||
//HighlightOrderOption("jobspecific");
|
||||
}
|
||||
while (!HasOrder(captain_mechanic, "repairsystems"));
|
||||
RemoveCompletedObjective(segments[1]);
|
||||
yield return new WaitForSeconds(2f, false);
|
||||
TriggerTutorialSegment(2, GameMain.Config.KeyBindText(InputType.Command));
|
||||
GameMain.GameSession.CrewManager.AddCharacter(captain_security);
|
||||
do
|
||||
{
|
||||
yield return null;
|
||||
// TODO: Rework order highlighting for new command UI
|
||||
// GameMain.GameSession.CrewManager.HighlightOrderButton(captain_security, "operateweapons", highlightColor, new Vector2(5, 5));
|
||||
HighlightOrderOption("fireatwill");
|
||||
}
|
||||
while (!HasOrder(captain_security, "operateweapons", "fireatwill"));
|
||||
RemoveCompletedObjective(segments[2]);
|
||||
yield return new WaitForSeconds(4f, false);
|
||||
TriggerTutorialSegment(3, GameMain.Config.KeyBindText(InputType.Command));
|
||||
GameMain.GameSession.CrewManager.AddCharacter(captain_engineer);
|
||||
do
|
||||
{
|
||||
yield return null;
|
||||
// TODO: Rework order highlighting for new command UI
|
||||
// GameMain.GameSession.CrewManager.HighlightOrderButton(captain_engineer, "operatereactor", highlightColor, new Vector2(5, 5));
|
||||
HighlightOrderOption("powerup");
|
||||
}
|
||||
while (!HasOrder(captain_engineer, "operatereactor", "powerup"));
|
||||
RemoveCompletedObjective(segments[3]);
|
||||
tutorial_submarineReactor.CanBeSelected = true;
|
||||
do { yield return null; } while (!tutorial_submarineReactor.IsActive); // Wait until reactor on
|
||||
TriggerTutorialSegment(4);
|
||||
while (ContentRunning) yield return null;
|
||||
captain.AddActiveObjectiveEntity(captain_navConsole.Item, captain_steerIcon, captain_steerIconColor);
|
||||
SetHighlight(captain_navConsole.Item, true);
|
||||
SetHighlight(captain_sonar.Item, true);
|
||||
SetHighlight(captain_statusMonitor, true);
|
||||
captain_navConsole.UseAutoDocking = false;
|
||||
do
|
||||
{
|
||||
//captain_navConsoleCustomInterface.HighlightElement(0, uiHighlightColor, duration: 1.0f, pulsateAmount: 0.0f);
|
||||
yield return new WaitForSeconds(1.0f, false);
|
||||
} while (Submarine.MainSub.DockedTo.Any());
|
||||
RemoveCompletedObjective(segments[4]);
|
||||
yield return new WaitForSeconds(2f, false);
|
||||
TriggerTutorialSegment(5); // Navigate to destination
|
||||
do
|
||||
{
|
||||
if (IsSelectedItem(captain_navConsole.Item))
|
||||
{
|
||||
if (captain_sonar.SonarModeSwitch.Frame.FlashTimer <= 0)
|
||||
{
|
||||
captain_sonar.SonarModeSwitch.Frame.Flash(highlightColor, 1.5f, false, false, new Vector2(2.5f, 2.5f));
|
||||
}
|
||||
}
|
||||
yield return null;
|
||||
} while (captain_sonar.CurrentMode != Sonar.Mode.Active);
|
||||
do { yield return null; } while (Vector2.Distance(Submarine.MainSub.WorldPosition, Level.Loaded.EndPosition) > 4000f);
|
||||
RemoveCompletedObjective(segments[5]);
|
||||
captain_navConsole.UseAutoDocking = true;
|
||||
yield return new WaitForSeconds(4f, false);
|
||||
TriggerTutorialSegment(6); // Docking
|
||||
do
|
||||
{
|
||||
//captain_navConsoleCustomInterface.HighlightElement(0, uiHighlightColor, duration: 1.0f, pulsateAmount: 0.0f);
|
||||
yield return new WaitForSeconds(1.0f, false);
|
||||
} while (!Submarine.MainSub.AtEndPosition || Submarine.MainSub.DockedTo.Any());
|
||||
RemoveCompletedObjective(segments[6]);
|
||||
yield return new WaitForSeconds(3f, false);
|
||||
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.GetWithVariable("Captain.Radio.Complete", "[OUTPOSTNAME]", GameMain.GameSession.EndLocation.Name), ChatMessageType.Radio, null);
|
||||
SetHighlight(captain_navConsole.Item, false);
|
||||
SetHighlight(captain_sonar.Item, false);
|
||||
SetHighlight(captain_statusMonitor, false);
|
||||
captain.RemoveActiveObjectiveEntity(captain_navConsole.Item);
|
||||
|
||||
CoroutineManager.StartCoroutine(TutorialCompleted());
|
||||
}
|
||||
|
||||
private void HighlightOrderOption(string option)
|
||||
{
|
||||
if (GameMain.GameSession.CrewManager.OrderOptionButtons.Count == 0) return;
|
||||
var order = GameMain.GameSession.CrewManager.OrderOptionButtons[0].UserData as Order;
|
||||
|
||||
int orderIndex = 0;
|
||||
for (int i = 0; i < GameMain.GameSession.CrewManager.OrderOptionButtons.Count; i++)
|
||||
{
|
||||
if (orderIndex >= order.Options.Length)
|
||||
{
|
||||
orderIndex = 0;
|
||||
}
|
||||
if (order.Options[orderIndex] == option)
|
||||
{
|
||||
if (GameMain.GameSession.CrewManager.OrderOptionButtons[i].FlashTimer <= 0)
|
||||
{
|
||||
GameMain.GameSession.CrewManager.OrderOptionButtons[i].Flash(highlightColor);
|
||||
}
|
||||
}
|
||||
|
||||
orderIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsSelectedItem(Item item)
|
||||
{
|
||||
return captain?.SelectedConstruction == item;
|
||||
}
|
||||
}
|
||||
}
|
||||
+520
@@ -0,0 +1,520 @@
|
||||
/*using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
using System;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Barotrauma.Items.Components;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Tutorials
|
||||
{
|
||||
class ContextualTutorial : Tutorial
|
||||
{
|
||||
public ContextualTutorial(XElement element) : base(element)
|
||||
{
|
||||
//Name = "ContextualTutorial";
|
||||
}
|
||||
|
||||
public static bool Selected = false;
|
||||
|
||||
private Steering navConsole;
|
||||
private Reactor reactor;
|
||||
private Sonar sonar;
|
||||
private Vector2 subStartingPosition;
|
||||
private List<Character> crew;
|
||||
private Character mechanic;
|
||||
private Character engineer;
|
||||
private Character injuredMember = null;
|
||||
|
||||
private List<Pair<Character, float>> characterTimeOnSonar;
|
||||
private float requiredTimeOnSonar = 5f;
|
||||
|
||||
private float tutorialTimer;
|
||||
|
||||
private bool disableTutorialOnDeficiencyFound = true;
|
||||
|
||||
private float floodTutorialTimer = 0.0f;
|
||||
private const float floodTutorialDelay = 2.0f;
|
||||
private float medicalTutorialTimer = 0.0f;
|
||||
private const float medicalTutorialDelay = 2.0f;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
for (int i = 0; i < segments.Count; i++)
|
||||
{
|
||||
segments[i].IsTriggered = false;
|
||||
}
|
||||
|
||||
characterTimeOnSonar = new List<Pair<Character, float>>();
|
||||
}
|
||||
|
||||
public void LoadPartiallyComplete(XElement element)
|
||||
{
|
||||
int[] completedSegments = element.GetAttributeIntArray("completedsegments", null);
|
||||
|
||||
if (completedSegments == null || completedSegments.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (completedSegments.Length == segments.Count) // Completed all segments
|
||||
{
|
||||
Stop();
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < completedSegments.Length; i++)
|
||||
{
|
||||
segments[completedSegments[i]].IsTriggered = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void SavePartiallyComplete(XElement element)
|
||||
{
|
||||
XElement tutorialElement = new XElement("contextualtutorial");
|
||||
tutorialElement.Add(new XAttribute("completedsegments", GetCompletedSegments()));
|
||||
element.Add(tutorialElement);
|
||||
}
|
||||
|
||||
private string GetCompletedSegments()
|
||||
{
|
||||
string completedSegments = string.Empty;
|
||||
|
||||
for (int i = 0; i < segments.Count; i++)
|
||||
{
|
||||
if (segments[i].IsTriggered)
|
||||
{
|
||||
completedSegments += i + ",";
|
||||
}
|
||||
}
|
||||
|
||||
if (completedSegments.Length > 0)
|
||||
{
|
||||
completedSegments = completedSegments.TrimEnd(',');
|
||||
}
|
||||
|
||||
return completedSegments;
|
||||
}
|
||||
|
||||
public override void Start()
|
||||
{
|
||||
if (!Initialized) return;
|
||||
|
||||
base.Start();
|
||||
injuredMember = null;
|
||||
activeContentSegment = null;
|
||||
tutorialTimer = floodTutorialTimer = medicalTutorialTimer = 0.0f;
|
||||
subStartingPosition = Vector2.Zero;
|
||||
characterTimeOnSonar.Clear();
|
||||
|
||||
subStartingPosition = Submarine.MainSub.WorldPosition;
|
||||
navConsole = Item.ItemList.Find(i => i.HasTag("command"))?.GetComponent<Steering>();
|
||||
sonar = navConsole?.Item.GetComponent<Sonar>();
|
||||
reactor = Item.ItemList.Find(i => i.HasTag("reactor"))?.GetComponent<Reactor>();
|
||||
|
||||
#if DEBUG
|
||||
if (reactor == null || navConsole == null || sonar == null)
|
||||
{
|
||||
infoBox = CreateInfoFrame("Error", "Submarine not compatible with the tutorial:"
|
||||
+ "\nReactor - " + (reactor != null ? "OK" : "Tag 'reactor' not found")
|
||||
+ "\nNavigation Console - " + (navConsole != null ? "OK" : "Tag 'command' not found")
|
||||
+ "\nSonar - " + (sonar != null ? "OK" : "Not found under Navigation Console"), hasButton: true);
|
||||
CoroutineManager.StartCoroutine(WaitForErrorClosed());
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
if (disableTutorialOnDeficiencyFound)
|
||||
{
|
||||
if (reactor == null || navConsole == null || sonar == null)
|
||||
{
|
||||
Stop();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (navConsole == null) segments[2].IsTriggered = true; // Disable navigation console usage tutorial
|
||||
if (reactor == null) segments[5].IsTriggered = true; // Disable reactor usage tutorial
|
||||
if (sonar == null) segments[6].IsTriggered = true; // Disable enemy on sonar tutorial
|
||||
}
|
||||
|
||||
crew = GameMain.GameSession.CrewManager.GetCharacters().ToList();
|
||||
mechanic = CrewMemberWithJob("mechanic");
|
||||
engineer = CrewMemberWithJob("engineer");
|
||||
|
||||
Completed = true; // Trigger completed at start to prevent the contextual tutorial from automatically activating on starting new campaigns after this one
|
||||
started = true;
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
private IEnumerable<object> WaitForErrorClosed()
|
||||
{
|
||||
while (infoBox != null) yield return null;
|
||||
Stop();
|
||||
}
|
||||
#endif
|
||||
|
||||
public override void Stop()
|
||||
{
|
||||
base.Stop();
|
||||
characterTimeOnSonar = null;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
base.Update(deltaTime);
|
||||
|
||||
if (!started || ContentRunning) return;
|
||||
|
||||
deltaTime *= 0.5f;
|
||||
|
||||
for (int i = 0; i < segments.Count; i++)
|
||||
{
|
||||
if (segments[i].IsTriggered || HasObjective(segments[i])) continue;
|
||||
if (CheckContextualTutorials(i, deltaTime)) // Found a relevant tutorial, halt finding new ones
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool CheckContextualTutorials(int index, float deltaTime)
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0: // Welcome: Game Start [Text]
|
||||
if (tutorialTimer < 1.0f)
|
||||
{
|
||||
tutorialTimer += deltaTime;
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 1: // Command Reactor: 2 seconds after 'Welcome' dismissed and only if no command given to start reactor [Video]
|
||||
if (!segments[0].IsTriggered) return false;
|
||||
if (tutorialTimer < 3.0f)
|
||||
{
|
||||
tutorialTimer += deltaTime;
|
||||
|
||||
if (HasOrder("operatereactor"))
|
||||
{
|
||||
segments[index].IsTriggered = true;
|
||||
tutorialTimer = 2.5f;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 2: // Nav Console: 2 seconds after 'Command Reactor' dismissed or if nav console is activated [Video]
|
||||
if (!IsReactorPoweredUp()) return false; // Do not advance tutorial based on this segment if reactor has not been powered up
|
||||
if (Character.Controlled?.SelectedConstruction != navConsole.Item)
|
||||
{
|
||||
if (tutorialTimer < 4.5f)
|
||||
{
|
||||
tutorialTimer += deltaTime;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
tutorialTimer = 4.5f;
|
||||
}
|
||||
|
||||
TriggerTutorialSegment(index, GameMain.GameSession.EndLocation.Name);
|
||||
return true;
|
||||
case 3: // Objective: Travel ~150 meters and while sub is not flooding [Text]
|
||||
if (Vector2.Distance(subStartingPosition, Submarine.MainSub.WorldPosition) < 8000f || IsFlooding())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else // Called earlier than others due to requiring specific args
|
||||
{
|
||||
TriggerTutorialSegment(index, GameMain.GameSession.EndLocation.Name);
|
||||
return true;
|
||||
}
|
||||
case 4: // Flood: Hull is breached and sub is taking on water [Video]
|
||||
if (!IsFlooding())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (floodTutorialTimer < floodTutorialDelay)
|
||||
{
|
||||
floodTutorialTimer += deltaTime;
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 5: // Reactor: Player uses reactor for the first time [Video]
|
||||
if (Character.Controlled?.SelectedConstruction != reactor.Item)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 6: // Enemy on Sonar: Player witnesses creature signal on sonar for 5 seconds [Video]
|
||||
if (!HasEnemyOnSonarForDuration(deltaTime))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 7: // Degrading1: Any equipment degrades to 50% health or less and player has not assigned any crew to perform maintenance [Text]
|
||||
if ((mechanic == null || mechanic.IsDead) && (engineer == null || engineer.IsDead)) // Both engineer and mechanic are dead or do not exist -> do not display
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool degradedEquipmentFound = false;
|
||||
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (!item.Repairables.Any() || item.Condition > 50.0f) continue;
|
||||
degradedEquipmentFound = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (degradedEquipmentFound)
|
||||
{
|
||||
if (HasOrder("repairsystems", "jobspecific"))
|
||||
{
|
||||
segments[index].IsTriggered = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 8: // Medical: Crewmember is injured but not killed [Video]
|
||||
|
||||
if (injuredMember == null)
|
||||
{
|
||||
for (int i = 0; i < crew.Count; i++)
|
||||
{
|
||||
Character member = crew[i];
|
||||
if (member.Vitality < member.MaxVitality && !member.IsDead)
|
||||
{
|
||||
injuredMember = member;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
else if (medicalTutorialTimer < medicalTutorialDelay)
|
||||
{
|
||||
medicalTutorialTimer += deltaTime;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
TriggerTutorialSegment(index, new string[] { injuredMember.Info.DisplayName,
|
||||
(injuredMember.Info.Gender == Gender.Male) ? TextManager.Get("PronounPossessiveMale").ToLower() : TextManager.Get("PronounPossessiveFemale").ToLower() });
|
||||
return true;
|
||||
}
|
||||
case 9: // Approach1: Destination is within ~100m [Video]
|
||||
if (Vector2.Distance(Submarine.MainSub.WorldPosition, Level.Loaded.EndPosition) > 8000f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
TriggerTutorialSegment(index, GameMain.GameSession.EndLocation.Name);
|
||||
return true;
|
||||
}
|
||||
case 10: // Approach2: Sub is docked [Text]
|
||||
if (!Submarine.MainSub.AtEndPosition || Submarine.MainSub.DockedTo.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
TriggerTutorialSegment(index);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void CheckActiveObjectives(TutorialSegment objective, float deltaTime)
|
||||
{
|
||||
switch(objective.Id)
|
||||
{
|
||||
case "ReactorCommand": // Reactor commanded
|
||||
if (!IsReactorPoweredUp())
|
||||
{
|
||||
if (!HasOrder("operatereactor")) return;
|
||||
}
|
||||
break;
|
||||
case "NavConsole": // traveled 50 meters
|
||||
if (Vector2.Distance(subStartingPosition, Submarine.MainSub.WorldPosition) < 4000f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case "Flood": // Hull breaches repaired
|
||||
if (IsFlooding()) return;
|
||||
break;
|
||||
case "Medical":
|
||||
if (injuredMember != null && !injuredMember.IsDead)
|
||||
{
|
||||
if (injuredMember.CharacterHealth.DroppedItem == null) return;
|
||||
}
|
||||
break;
|
||||
case "EnemyOnSonar": // Enemy dispatched
|
||||
if (HasEnemyOnSonarForDuration(deltaTime))
|
||||
{
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case "Degrading": // Fixed
|
||||
if (mechanic != null && !mechanic.IsDead)
|
||||
{
|
||||
HumanAIController humanAI = mechanic.AIController as HumanAIController;
|
||||
if (mechanic.CurrentOrder?.AITag != "repairsystems" || humanAI.CurrentOrderOption != "jobspecific")
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (engineer != null && !engineer.IsDead)
|
||||
{
|
||||
HumanAIController humanAI = engineer.AIController as HumanAIController;
|
||||
if (engineer.CurrentOrder?.AITag != "repairsystems" || humanAI.CurrentOrderOption != "jobspecific")
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case "Approach1": // Wait until docked
|
||||
if (!Submarine.MainSub.AtEndPosition || Submarine.MainSub.DockedTo.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
RemoveCompletedObjective(objective);
|
||||
}
|
||||
|
||||
private bool IsReactorPoweredUp()
|
||||
{
|
||||
float load = 0.0f;
|
||||
List<Connection> connections = reactor.Item.Connections;
|
||||
if (connections != null && connections.Count > 0)
|
||||
{
|
||||
foreach (Connection connection in connections)
|
||||
{
|
||||
if (!connection.IsPower) continue;
|
||||
foreach (Connection recipient in connection.Recipients)
|
||||
{
|
||||
if (!(recipient.Item is Item it)) continue;
|
||||
|
||||
PowerTransfer pt = it.GetComponent<PowerTransfer>();
|
||||
if (pt == null) continue;
|
||||
|
||||
load = Math.Max(load, pt.PowerLoad);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Math.Abs(load + reactor.CurrPowerConsumption) < 10;
|
||||
}
|
||||
|
||||
private Character CrewMemberWithJob(string job)
|
||||
{
|
||||
job = job.ToLowerInvariant();
|
||||
for (int i = 0; i < crew.Count; i++)
|
||||
{
|
||||
if (crew[i].Info.Job.Prefab.Identifier.ToLowerInvariant() == job) return crew[i];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool HasOrder(string aiTag, string option = null)
|
||||
{
|
||||
for (int i = 0; i < crew.Count; i++)
|
||||
{
|
||||
if (crew[i].CurrentOrder?.AITag == aiTag)
|
||||
{
|
||||
if (option == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
HumanAIController humanAI = crew[i].AIController as HumanAIController;
|
||||
return humanAI.CurrentOrderOption == option;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool IsFlooding()
|
||||
{
|
||||
foreach (Gap gap in Gap.GapList)
|
||||
{
|
||||
if (gap.ConnectedWall == null || gap.IsRoomToRoom) continue;
|
||||
if (gap.ConnectedDoor != null || gap.Open <= 0.0f) continue;
|
||||
if (gap.Submarine == null) continue;
|
||||
if (gap.Submarine.IsOutpost) continue;
|
||||
if (gap.Submarine != Submarine.MainSub) continue;
|
||||
if (gap.FlowTargetHull == null || gap.FlowTargetHull.WaterPercentage <= 0.0f) continue;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool HasEnemyOnSonarForDuration(float deltaTime)
|
||||
{
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (c.AnimController.CurrentHull != null || !c.Enabled || !(c.AIController is EnemyAIController)) continue;
|
||||
if (sonar.DetectSubmarineWalls && c.AnimController.CurrentHull == null && sonar.Item.CurrentHull != null) continue;
|
||||
if (Vector2.DistanceSquared(c.WorldPosition, sonar.Item.WorldPosition) > sonar.Range * sonar.Range)
|
||||
{
|
||||
for (int i = 0; i < characterTimeOnSonar.Count; i++)
|
||||
{
|
||||
if (characterTimeOnSonar[i].First == c)
|
||||
{
|
||||
characterTimeOnSonar.RemoveAt(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
Pair<Character, float> pair = characterTimeOnSonar.Find(ct => ct.First == c);
|
||||
if (pair != null)
|
||||
{
|
||||
pair.Second += deltaTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
characterTimeOnSonar.Add(new Pair<Character, float>(c, deltaTime));
|
||||
}
|
||||
}
|
||||
|
||||
return characterTimeOnSonar.Find(ct => ct.Second >= requiredTimeOnSonar && !ct.First.IsDead) != null;
|
||||
}
|
||||
|
||||
protected override void TriggerTutorialSegment(int index, params object[] args)
|
||||
{
|
||||
base.TriggerTutorialSegment(index, args);
|
||||
|
||||
for (int i = 0; i < segments.Count; i++)
|
||||
{
|
||||
if (!segments[i].IsTriggered) return;
|
||||
}
|
||||
|
||||
CoroutineManager.StartCoroutine(WaitToStop()); // Completed
|
||||
}
|
||||
|
||||
private IEnumerable<object> WaitToStop()
|
||||
{
|
||||
while (ContentRunning) yield return null;
|
||||
Stop();
|
||||
}
|
||||
}
|
||||
}*/
|
||||
+453
@@ -0,0 +1,453 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Tutorials
|
||||
{
|
||||
class DoctorTutorial : ScenarioTutorial
|
||||
{
|
||||
// Room 1
|
||||
private float shakeTimer = 1f;
|
||||
private float shakeAmount = 20f;
|
||||
|
||||
private string radioSpeakerName;
|
||||
private Character doctor;
|
||||
|
||||
private ItemContainer doctor_suppliesCabinet;
|
||||
private ItemContainer doctor_medBayCabinet;
|
||||
private Character patient1, patient2;
|
||||
private List<Character> subPatients;
|
||||
private Hull medBay;
|
||||
|
||||
private Door doctor_firstDoor;
|
||||
private Door doctor_secondDoor;
|
||||
private Door doctor_thirdDoor;
|
||||
private Door tutorial_upperFinalDoor;
|
||||
private Door tutorial_lockedDoor_2;
|
||||
|
||||
private LightComponent doctor_firstDoorLight;
|
||||
private LightComponent doctor_secondDoorLight;
|
||||
private LightComponent doctor_thirdDoorLight;
|
||||
private Door tutorial_submarineDoor;
|
||||
private LightComponent tutorial_submarineDoorLight;
|
||||
|
||||
// Variables
|
||||
private Sprite doctor_firstAidIcon;
|
||||
private Color doctor_firstAidIconColor;
|
||||
|
||||
public DoctorTutorial(XElement element) : base(element)
|
||||
{
|
||||
}
|
||||
public override void Start()
|
||||
{
|
||||
base.Start();
|
||||
|
||||
var firstAidOrder = Order.GetPrefab("requestfirstaid");
|
||||
doctor_firstAidIcon = firstAidOrder.SymbolSprite;
|
||||
doctor_firstAidIconColor = firstAidOrder.Color;
|
||||
|
||||
subPatients = new List<Character>();
|
||||
radioSpeakerName = TextManager.Get("Tutorial.Radio.Speaker");
|
||||
doctor = Character.Controlled;
|
||||
|
||||
var bandages = FindOrGiveItem(doctor, "antibleeding1");
|
||||
bandages.Unequip(doctor);
|
||||
doctor.Inventory.RemoveItem(bandages);
|
||||
|
||||
var syringegun = FindOrGiveItem(doctor, "syringegun");
|
||||
syringegun.Unequip(doctor);
|
||||
doctor.Inventory.RemoveItem(syringegun);
|
||||
|
||||
var antibiotics = FindOrGiveItem(doctor, "antibiotics");
|
||||
antibiotics.Unequip(doctor);
|
||||
doctor.Inventory.RemoveItem(antibiotics);
|
||||
|
||||
var morphine = FindOrGiveItem(doctor, "antidama1");
|
||||
morphine.Unequip(doctor);
|
||||
doctor.Inventory.RemoveItem(morphine);
|
||||
|
||||
doctor_suppliesCabinet = Item.ItemList.Find(i => i.HasTag("doctor_suppliescabinet"))?.GetComponent<ItemContainer>();
|
||||
doctor_medBayCabinet = Item.ItemList.Find(i => i.HasTag("doctor_medbaycabinet"))?.GetComponent<ItemContainer>();
|
||||
|
||||
var patientHull1 = WayPoint.WayPointList.Find(wp => wp.IdCardDesc == "waitingroom").CurrentHull;
|
||||
var patientHull2 = WayPoint.WayPointList.Find(wp => wp.IdCardDesc == "airlock").CurrentHull;
|
||||
medBay = WayPoint.WayPointList.Find(wp => wp.IdCardDesc == "medbay").CurrentHull;
|
||||
|
||||
var assistantInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("assistant"));
|
||||
patient1 = Character.Create(assistantInfo, patientHull1.WorldPosition, "1");
|
||||
patient1.GiveJobItems(null);
|
||||
patient1.CanSpeak = false;
|
||||
patient1.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.Burn, 45.0f) }, stun: 0, playSound: false);
|
||||
patient1.AIController.Enabled = false;
|
||||
|
||||
assistantInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("assistant"));
|
||||
patient2 = Character.Create(assistantInfo, patientHull2.WorldPosition, "2");
|
||||
patient2.GiveJobItems(null);
|
||||
patient2.CanSpeak = false;
|
||||
patient2.AIController.Enabled = false;
|
||||
|
||||
var mechanicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("engineer"));
|
||||
var subPatient1 = Character.Create(mechanicInfo, WayPoint.GetRandom(SpawnType.Human, mechanicInfo.Job, Submarine.MainSub).WorldPosition, "3");
|
||||
subPatient1.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.Burn, 40.0f) }, stun: 0, playSound: false);
|
||||
subPatients.Add(subPatient1);
|
||||
|
||||
var securityInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("securityofficer"));
|
||||
var subPatient2 = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, securityInfo.Job, Submarine.MainSub).WorldPosition, "3");
|
||||
subPatient2.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.InternalDamage, 40.0f) }, stun: 0, playSound: false);
|
||||
subPatients.Add(subPatient2);
|
||||
|
||||
var engineerInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("engineer"));
|
||||
var subPatient3 = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, engineerInfo.Job, Submarine.MainSub).WorldPosition, "3");
|
||||
subPatient3.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.Burn, 20.0f) }, stun: 0, playSound: false);
|
||||
subPatients.Add(subPatient3);
|
||||
|
||||
doctor_firstDoor = Item.ItemList.Find(i => i.HasTag("doctor_firstdoor")).GetComponent<Door>();
|
||||
doctor_secondDoor = Item.ItemList.Find(i => i.HasTag("doctor_seconddoor")).GetComponent<Door>();
|
||||
doctor_thirdDoor = Item.ItemList.Find(i => i.HasTag("doctor_thirddoor")).GetComponent<Door>();
|
||||
tutorial_upperFinalDoor = Item.ItemList.Find(i => i.HasTag("tutorial_upperfinaldoor")).GetComponent<Door>();
|
||||
doctor_firstDoorLight = Item.ItemList.Find(i => i.HasTag("doctor_firstdoorlight")).GetComponent<LightComponent>();
|
||||
doctor_secondDoorLight = Item.ItemList.Find(i => i.HasTag("doctor_seconddoorlight")).GetComponent<LightComponent>();
|
||||
doctor_thirdDoorLight = Item.ItemList.Find(i => i.HasTag("doctor_thirddoorlight")).GetComponent<LightComponent>();
|
||||
SetDoorAccess(doctor_firstDoor, doctor_firstDoorLight, false);
|
||||
SetDoorAccess(doctor_secondDoor, doctor_secondDoorLight, false);
|
||||
SetDoorAccess(doctor_thirdDoor, doctor_thirdDoorLight, false);
|
||||
tutorial_submarineDoor = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoor")).GetComponent<Door>();
|
||||
tutorial_submarineDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoorlight")).GetComponent<LightComponent>();
|
||||
SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, false);
|
||||
tutorial_lockedDoor_2 = Item.ItemList.Find(i => i.HasTag("tutorial_lockeddoor_2")).GetComponent<Door>();
|
||||
SetDoorAccess(tutorial_lockedDoor_2, null, true);
|
||||
|
||||
|
||||
foreach (var patient in subPatients)
|
||||
{
|
||||
patient.CanSpeak = false;
|
||||
patient.AIController.Enabled = false;
|
||||
patient.GiveJobItems();
|
||||
}
|
||||
|
||||
Item reactorItem = Item.ItemList.Find(i => i.Submarine == Submarine.MainSub && i.GetComponent<Reactor>() != null);
|
||||
reactorItem.GetComponent<Reactor>().AutoTemp = true;
|
||||
}
|
||||
|
||||
public override IEnumerable<object> UpdateState()
|
||||
{
|
||||
while (GameMain.Instance.LoadingScreenOpen) yield return null;
|
||||
|
||||
// explosions and radio messages ------------------------------------------------------
|
||||
|
||||
yield return new WaitForSeconds(3.0f, false);
|
||||
|
||||
//SoundPlayer.PlayDamageSound("StructureBlunt", 10, Character.Controlled.WorldPosition);
|
||||
//// Room 1
|
||||
//while (shakeTimer > 0.0f) // Wake up, shake
|
||||
//{
|
||||
// shakeTimer -= 0.1f;
|
||||
// GameMain.GameScreen.Cam.Shake = shakeAmount;
|
||||
// yield return new WaitForSeconds(0.1f);
|
||||
//}
|
||||
//yield return new WaitForSeconds(2.5f);
|
||||
//GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.WakeUp"), ChatMessageType.Radio, null);
|
||||
|
||||
//yield return new WaitForSeconds(2.5f);
|
||||
|
||||
doctor.SetStun(1.5f);
|
||||
var explosion = new Explosion(range: 100, force: 10, damage: 0, structureDamage: 0);
|
||||
explosion.DisableParticles();
|
||||
GameMain.GameScreen.Cam.Shake = shakeAmount;
|
||||
explosion.Explode(Character.Controlled.WorldPosition - Vector2.UnitX * 25, null);
|
||||
SoundPlayer.PlayDamageSound("StructureBlunt", 10, Character.Controlled.WorldPosition - Vector2.UnitX * 25);
|
||||
|
||||
yield return new WaitForSeconds(0.5f, false);
|
||||
|
||||
doctor.DamageLimb(
|
||||
Character.Controlled.WorldPosition,
|
||||
doctor.AnimController.GetLimb(LimbType.Torso),
|
||||
new List<Affliction> { new Affliction(AfflictionPrefab.InternalDamage, 10.0f) },
|
||||
stun: 3.0f, playSound: true, attackImpulse: 0.0f);
|
||||
|
||||
shakeTimer = 0.5f;
|
||||
while (shakeTimer > 0.0f) // Wake up, shake
|
||||
{
|
||||
shakeTimer -= 0.1f;
|
||||
GameMain.GameScreen.Cam.Shake = shakeAmount;
|
||||
yield return new WaitForSeconds(0.1f, false);
|
||||
}
|
||||
|
||||
yield return new WaitForSeconds(3.0f, false);
|
||||
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Doctor.Radio.KnockedDown"), ChatMessageType.Radio, null);
|
||||
|
||||
// first tutorial segment, get medical supplies ------------------------------------------------------
|
||||
|
||||
yield return new WaitForSeconds(1.5f, false);
|
||||
SetHighlight(doctor_suppliesCabinet.Item, true);
|
||||
|
||||
/*while (doctor.CurrentHull != doctor_suppliesCabinet.Item.CurrentHull)
|
||||
{
|
||||
yield return new WaitForSeconds(2.0f);
|
||||
}*/
|
||||
|
||||
TriggerTutorialSegment(0, GameMain.Config.KeyBindText(InputType.Select), GameMain.Config.KeyBindText(InputType.Deselect), GameMain.Config.KeyBindText(InputType.ToggleInventory)); // Medical supplies objective
|
||||
|
||||
do
|
||||
{
|
||||
for (int i = 0; i < doctor_suppliesCabinet.Inventory.Items.Length; i++)
|
||||
{
|
||||
if (doctor_suppliesCabinet.Inventory.Items[i] != null)
|
||||
{
|
||||
HighlightInventorySlot(doctor_suppliesCabinet.Inventory, i, highlightColor, .5f, .5f, 0f);
|
||||
}
|
||||
}
|
||||
if (doctor.SelectedConstruction == doctor_suppliesCabinet.Item)
|
||||
{
|
||||
for (int i = 0; i < doctor.Inventory.slots.Length; i++)
|
||||
{
|
||||
if (doctor.Inventory.Items[i] == null) HighlightInventorySlot(doctor.Inventory, i, highlightColor, .5f, .5f, 0f);
|
||||
}
|
||||
}
|
||||
yield return null;
|
||||
} while (doctor.Inventory.FindItemByIdentifier("antidama1") == null); // Wait until looted
|
||||
yield return new WaitForSeconds(1.0f, false);
|
||||
|
||||
SetHighlight(doctor_suppliesCabinet.Item, false);
|
||||
RemoveCompletedObjective(segments[0]);
|
||||
|
||||
yield return new WaitForSeconds(1.0f, false);
|
||||
|
||||
// 2nd tutorial segment, treat self -------------------------------------------------------------------------
|
||||
|
||||
TriggerTutorialSegment(1, GameMain.Config.KeyBindText(InputType.Health)); // Open health interface
|
||||
while (CharacterHealth.OpenHealthWindow == null)
|
||||
{
|
||||
doctor.CharacterHealth.HealthBarPulsateTimer = 1.0f;
|
||||
yield return null;
|
||||
}
|
||||
yield return null;
|
||||
RemoveCompletedObjective(segments[1]);
|
||||
yield return new WaitForSeconds(1.0f, false);
|
||||
TriggerTutorialSegment(2); //Treat self
|
||||
while (doctor.CharacterHealth.GetAfflictionStrength("damage") > 0.01f)
|
||||
{
|
||||
if (CharacterHealth.OpenHealthWindow == null)
|
||||
{
|
||||
doctor.CharacterHealth.HealthBarPulsateTimer = 1.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
HighlightInventorySlot(doctor.Inventory, "antidama1", highlightColor, .5f, .5f, 0f);
|
||||
}
|
||||
|
||||
yield return null;
|
||||
}
|
||||
|
||||
RemoveCompletedObjective(segments[2]);
|
||||
SetDoorAccess(doctor_firstDoor, doctor_firstDoorLight, true);
|
||||
|
||||
while (CharacterHealth.OpenHealthWindow != null)
|
||||
{
|
||||
yield return new WaitForSeconds(1.0f, false);
|
||||
}
|
||||
|
||||
// treat patient --------------------------------------------------------------------------------------------
|
||||
|
||||
//patient 1 requests first aid
|
||||
var newOrder = new Order(Order.GetPrefab("requestfirstaid"), patient1.CurrentHull, null, orderGiver: patient1);
|
||||
doctor.AddActiveObjectiveEntity(patient1, doctor_firstAidIcon, doctor_firstAidIconColor);
|
||||
//GameMain.GameSession.CrewManager.AddOrder(newOrder, newOrder.FadeOutTime);
|
||||
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(patient1.Name, newOrder.GetChatMessage("", patient1.CurrentHull?.DisplayName, givingOrderToSelf: false), ChatMessageType.Order, null);
|
||||
|
||||
while (doctor.CurrentHull != patient1.CurrentHull)
|
||||
{
|
||||
yield return new WaitForSeconds(1.0f, false);
|
||||
}
|
||||
yield return new WaitForSeconds(0.0f, false);
|
||||
|
||||
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Doctor.Radio.AssistantBurns"), ChatMessageType.Radio, null);
|
||||
GameMain.GameSession.CrewManager.AllowCharacterSwitch = false;
|
||||
GameMain.GameSession.CrewManager.AddCharacter(doctor);
|
||||
GameMain.GameSession.CrewManager.AddCharacter(patient1);
|
||||
GameMain.GameSession.CrewManager.ToggleCrewListOpen = true;
|
||||
patient1.CharacterHealth.UseHealthWindow = false;
|
||||
|
||||
yield return new WaitForSeconds(3.0f, false);
|
||||
patient1.AIController.Enabled = true;
|
||||
doctor.RemoveActiveObjectiveEntity(patient1);
|
||||
TriggerTutorialSegment(3, GameMain.Config.KeyBindText(InputType.Command)); // Get the patient to medbay
|
||||
|
||||
while (patient1.CurrentOrder == null || patient1.CurrentOrder.Identifier != "follow")
|
||||
{
|
||||
// TODO: Rework order highlighting for new command UI
|
||||
// GameMain.GameSession.CrewManager.HighlightOrderButton(patient1, "follow", highlightColor, new Vector2(5, 5));
|
||||
yield return null;
|
||||
}
|
||||
|
||||
SetDoorAccess(doctor_secondDoor, doctor_secondDoorLight, true);
|
||||
|
||||
while (patient1.CurrentHull != medBay)
|
||||
{
|
||||
yield return new WaitForSeconds(1.0f, false);
|
||||
}
|
||||
RemoveCompletedObjective(segments[3]);
|
||||
SetHighlight(doctor_medBayCabinet.Item, true);
|
||||
SetDoorAccess(doctor_thirdDoor, doctor_thirdDoorLight, true);
|
||||
patient1.CharacterHealth.UseHealthWindow = true;
|
||||
|
||||
yield return new WaitForSeconds(2.0f, false);
|
||||
|
||||
TriggerTutorialSegment(4, GameMain.Config.KeyBindText(InputType.Health)); // treat burns
|
||||
|
||||
do
|
||||
{
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
if (doctor_medBayCabinet.Inventory.Items[i] != null)
|
||||
{
|
||||
HighlightInventorySlot(doctor_medBayCabinet.Inventory, i, highlightColor, .5f, .5f, 0f);
|
||||
}
|
||||
}
|
||||
if (doctor.SelectedConstruction == doctor_medBayCabinet.Item)
|
||||
{
|
||||
for (int i = 0; i < doctor.Inventory.slots.Length; i++)
|
||||
{
|
||||
if (doctor.Inventory.Items[i] == null) HighlightInventorySlot(doctor.Inventory, i, highlightColor, .5f, .5f, 0f);
|
||||
}
|
||||
}
|
||||
yield return null;
|
||||
} while (doctor.Inventory.FindItemByIdentifier("antibleeding1") == null); // Wait until looted
|
||||
SetHighlight(doctor_medBayCabinet.Item, false);
|
||||
SetHighlight(patient1, true);
|
||||
|
||||
while (patient1.CharacterHealth.GetAfflictionStrength("burn") > 0.01f)
|
||||
{
|
||||
if (CharacterHealth.OpenHealthWindow == null)
|
||||
{
|
||||
doctor.CharacterHealth.HealthBarPulsateTimer = 1.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
HighlightInventorySlot(doctor.Inventory, "antibleeding1", highlightColor, .5f, .5f, 0f);
|
||||
}
|
||||
yield return null;
|
||||
|
||||
}
|
||||
RemoveCompletedObjective(segments[4]);
|
||||
SetHighlight(patient1, false);
|
||||
yield return new WaitForSeconds(1.0f, false);
|
||||
|
||||
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Doctor.Radio.AssistantBurnsHealed"), ChatMessageType.Radio, null);
|
||||
|
||||
// treat unconscious patient ------------------------------------------------------
|
||||
|
||||
//patient calls for help
|
||||
//patient2.CanSpeak = true;
|
||||
yield return new WaitForSeconds(2.0f, false);
|
||||
newOrder = new Order(Order.GetPrefab("requestfirstaid"), patient2.CurrentHull, null, orderGiver: patient2);
|
||||
doctor.AddActiveObjectiveEntity(patient2, doctor_firstAidIcon, doctor_firstAidIconColor);
|
||||
//GameMain.GameSession.CrewManager.AddOrder(newOrder, newOrder.FadeOutTime);
|
||||
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(patient2.Name, newOrder.GetChatMessage("", patient1.CurrentHull?.DisplayName, givingOrderToSelf: false), ChatMessageType.Order, null);
|
||||
patient2.AIController.Enabled = true;
|
||||
patient2.Oxygen = -50;
|
||||
CoroutineManager.StartCoroutine(KeepPatientAlive(patient2), "KeepPatient2Alive");
|
||||
|
||||
/*while (doctor.CurrentHull != patient2.CurrentHull)
|
||||
{
|
||||
yield return new WaitForSeconds(1.0f);
|
||||
}*/
|
||||
do { yield return null; } while (!tutorial_upperFinalDoor.IsOpen);
|
||||
yield return new WaitForSeconds(2.0f, false);
|
||||
|
||||
TriggerTutorialSegment(5, GameMain.Config.KeyBindText(InputType.Health)); // perform CPR
|
||||
SetHighlight(patient2, true);
|
||||
while (patient2.IsUnconscious)
|
||||
{
|
||||
if (CharacterHealth.OpenHealthWindow != null && doctor.AnimController.Anim != AnimController.Animation.CPR)
|
||||
{
|
||||
//Disabled pulse until it's replaced by a better effect
|
||||
//CharacterHealth.OpenHealthWindow.CPRButton.Pulsate(Vector2.One, Vector2.One * 1.5f, 1.0f);
|
||||
if (CharacterHealth.OpenHealthWindow.CPRButton.FlashTimer <= 0.0f)
|
||||
{
|
||||
CharacterHealth.OpenHealthWindow.CPRButton.Flash(highlightColor);
|
||||
}
|
||||
}
|
||||
yield return null;
|
||||
}
|
||||
RemoveCompletedObjective(segments[5]);
|
||||
SetHighlight(patient2, false);
|
||||
doctor.RemoveActiveObjectiveEntity(patient2);
|
||||
CoroutineManager.StopCoroutines("KeepPatient2Alive");
|
||||
|
||||
SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, true);
|
||||
|
||||
while (doctor.Submarine != Submarine.MainSub)
|
||||
{
|
||||
yield return new WaitForSeconds(1.0f, false);
|
||||
}
|
||||
|
||||
subPatients[2].Oxygen = -50;
|
||||
CoroutineManager.StartCoroutine(KeepPatientAlive(subPatients[2]), "KeepPatient3Alive");
|
||||
|
||||
yield return new WaitForSeconds(5.0f, false);
|
||||
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Doctor.Radio.EnteredSub"), ChatMessageType.Radio, null);
|
||||
|
||||
yield return new WaitForSeconds(3.0f, false);
|
||||
TriggerTutorialSegment(6, GameMain.Config.KeyBindText(InputType.Health)); // give treatment to anyone in need
|
||||
|
||||
foreach (var patient in subPatients)
|
||||
{
|
||||
//patient.CanSpeak = true;
|
||||
patient.AIController.Enabled = true;
|
||||
SetHighlight(patient, true);
|
||||
}
|
||||
|
||||
double subEnterTime = Timing.TotalTime;
|
||||
|
||||
bool[] patientCalledHelp = new bool[] { false, false, false };
|
||||
|
||||
while (subPatients.Any(p => p.Vitality < p.MaxVitality * 0.9f && !p.IsDead))
|
||||
{
|
||||
for (int i = 0; i < subPatients.Count; i++)
|
||||
{
|
||||
//make patients call for help to make sure the player finds them
|
||||
//(within 1 minute intervals of entering the sub)
|
||||
if (!patientCalledHelp[i] && Timing.TotalTime > subEnterTime + 60 * (i + 1))
|
||||
{
|
||||
doctor.AddActiveObjectiveEntity(subPatients[i], doctor_firstAidIcon, doctor_firstAidIconColor);
|
||||
newOrder = new Order(Order.GetPrefab("requestfirstaid"), subPatients[i].CurrentHull, null, orderGiver: subPatients[i]);
|
||||
string message = newOrder.GetChatMessage("", subPatients[i].CurrentHull?.DisplayName, givingOrderToSelf: false);
|
||||
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(subPatients[i].Name, message, ChatMessageType.Order, null);
|
||||
patientCalledHelp[i] = true;
|
||||
}
|
||||
|
||||
if (subPatients[i].ExternalHighlight && subPatients[i].Vitality >= subPatients[i].MaxVitality * 0.9f)
|
||||
{
|
||||
doctor.RemoveActiveObjectiveEntity(subPatients[i]);
|
||||
SetHighlight(subPatients[i], false);
|
||||
}
|
||||
}
|
||||
yield return new WaitForSeconds(1.0f, false);
|
||||
}
|
||||
RemoveCompletedObjective(segments[6]);
|
||||
foreach (var patient in subPatients)
|
||||
{
|
||||
SetHighlight(patient, false);
|
||||
doctor.RemoveActiveObjectiveEntity(patient);
|
||||
}
|
||||
|
||||
// END TUTORIAL
|
||||
CoroutineManager.StartCoroutine(TutorialCompleted());
|
||||
}
|
||||
|
||||
public IEnumerable<object> KeepPatientAlive(Character patient)
|
||||
{
|
||||
while (patient != null && !patient.Removed)
|
||||
{
|
||||
patient.Oxygen = Math.Max(patient.Oxygen, -50);
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Tutorials
|
||||
{
|
||||
class EditorTutorial : Tutorial
|
||||
{
|
||||
public EditorTutorial(XElement element)
|
||||
: base (element)
|
||||
{
|
||||
}
|
||||
|
||||
public override IEnumerable<object> UpdateState()
|
||||
{
|
||||
/*infoBox = CreateInfoFrame("Use the mouse wheel to zoom in and out, and WASD to move the camera around.", true);
|
||||
|
||||
while (infoBox != null)
|
||||
{
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
infoBox = CreateInfoFrame("Press \"Structure\" at the left side of the screen to start placing some walls.");
|
||||
|
||||
while (GameMain.SubEditorScreen.SelectedTab != (int)MapEntityCategory.Structure)
|
||||
{
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
infoBox = CreateInfoFrame("Select \"topwall\" from the list.", true);
|
||||
|
||||
while (MapEntityPrefab.Selected == null || MapEntityPrefab.Selected.Name != "topwall")
|
||||
{
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
infoBox = CreateInfoFrame("You can now create a horizontal wall by clicking and dragging. When you're done, right click to stop creating walls.");*/
|
||||
|
||||
|
||||
|
||||
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
+603
@@ -0,0 +1,603 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma.Tutorials
|
||||
{
|
||||
class EngineerTutorial : ScenarioTutorial
|
||||
{
|
||||
// Other tutorial items
|
||||
private LightComponent tutorial_securityFinalDoorLight;
|
||||
private LightComponent tutorial_mechanicFinalDoorLight;
|
||||
private Steering tutorial_submarineSteering;
|
||||
|
||||
// Room 1
|
||||
private float shakeTimer = 1f;
|
||||
private float shakeAmount = 20f;
|
||||
|
||||
// Room 2
|
||||
private MotionSensor engineer_equipmentObjectiveSensor;
|
||||
private ItemContainer engineer_equipmentCabinet;
|
||||
private Door engineer_firstDoor;
|
||||
private LightComponent engineer_firstDoorLight;
|
||||
|
||||
// Room 3
|
||||
private MotionSensor engineer_reactorObjectiveSensor;
|
||||
private Powered tutorial_oxygenGenerator;
|
||||
private Reactor engineer_reactor;
|
||||
private Door engineer_secondDoor;
|
||||
private LightComponent engineer_secondDoorLight;
|
||||
|
||||
// Room 4
|
||||
private MotionSensor engineer_repairJunctionBoxObjectiveSensor;
|
||||
private Item engineer_brokenJunctionBox;
|
||||
private Door engineer_thirdDoor;
|
||||
private LightComponent engineer_thirdDoorLight;
|
||||
|
||||
// Room 5
|
||||
private MotionSensor engineer_disconnectedJunctionBoxObjectiveSensor;
|
||||
private PowerTransfer[] engineer_disconnectedJunctionBoxes;
|
||||
private ConnectionPanel[] engineer_disconnectedConnectionPanels;
|
||||
private Item engineer_wire_1;
|
||||
private Powered engineer_lamp_1;
|
||||
private Item engineer_wire_2;
|
||||
private Powered engineer_lamp_2;
|
||||
private Door engineer_fourthDoor;
|
||||
private LightComponent engineer_fourthDoorLight;
|
||||
|
||||
// Room 6
|
||||
private Pump engineer_workingPump;
|
||||
private Door tutorial_lockedDoor_1;
|
||||
|
||||
// Submarine
|
||||
private Door tutorial_submarineDoor;
|
||||
private LightComponent tutorial_submarineDoorLight;
|
||||
private MotionSensor tutorial_enteredSubmarineSensor;
|
||||
private Item engineer_submarineJunctionBox_1;
|
||||
private Item engineer_submarineJunctionBox_2;
|
||||
private Item engineer_submarineJunctionBox_3;
|
||||
private Reactor engineer_submarineReactor;
|
||||
|
||||
// Variables
|
||||
private string radioSpeakerName;
|
||||
private Character engineer;
|
||||
private int[] reactorLoads = new int[5] { 1500, 3000, 2000, 5000, 3500 };
|
||||
private float reactorLoadChangeTime = 2f;
|
||||
private float reactorLoadError = 200f;
|
||||
private bool reactorOperatedProperly;
|
||||
private const float waterVolumeBeforeOpening = 15f;
|
||||
private Sprite engineer_repairIcon;
|
||||
private Color engineer_repairIconColor;
|
||||
private Sprite engineer_reactorIcon;
|
||||
private Color engineer_reactorIconColor;
|
||||
private bool wiringActive = false;
|
||||
|
||||
public EngineerTutorial(XElement element) : base(element)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public override void Start()
|
||||
{
|
||||
base.Start();
|
||||
|
||||
radioSpeakerName = TextManager.Get("Tutorial.Radio.Speaker");
|
||||
engineer = Character.Controlled;
|
||||
|
||||
var toolbox = FindOrGiveItem(engineer, "toolbox");
|
||||
toolbox.Unequip(engineer);
|
||||
engineer.Inventory.RemoveItem(toolbox);
|
||||
|
||||
var repairOrder = Order.GetPrefab("repairsystems");
|
||||
engineer_repairIcon = repairOrder.SymbolSprite;
|
||||
engineer_repairIconColor = repairOrder.Color;
|
||||
|
||||
var reactorOrder = Order.GetPrefab("operatereactor");
|
||||
engineer_reactorIcon = reactorOrder.SymbolSprite;
|
||||
engineer_reactorIconColor = reactorOrder.Color;
|
||||
|
||||
// Other tutorial items
|
||||
tutorial_securityFinalDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_securityfinaldoorlight")).GetComponent<LightComponent>();
|
||||
tutorial_mechanicFinalDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_mechanicfinaldoorlight")).GetComponent<LightComponent>();
|
||||
tutorial_submarineSteering = Item.ItemList.Find(i => i.HasTag("command")).GetComponent<Steering>();
|
||||
|
||||
tutorial_submarineSteering.CanBeSelected = false;
|
||||
foreach (ItemComponent ic in tutorial_submarineSteering.Item.Components)
|
||||
{
|
||||
ic.CanBeSelected = false;
|
||||
}
|
||||
|
||||
SetDoorAccess(null, tutorial_securityFinalDoorLight, false);
|
||||
SetDoorAccess(null, tutorial_mechanicFinalDoorLight, false);
|
||||
|
||||
// Room 2
|
||||
engineer_equipmentObjectiveSensor = Item.ItemList.Find(i => i.HasTag("engineer_equipmentobjectivesensor")).GetComponent<MotionSensor>();
|
||||
engineer_equipmentCabinet = Item.ItemList.Find(i => i.HasTag("engineer_equipmentcabinet")).GetComponent<ItemContainer>();
|
||||
engineer_firstDoor = Item.ItemList.Find(i => i.HasTag("engineer_firstdoor")).GetComponent<Door>();
|
||||
engineer_firstDoorLight = Item.ItemList.Find(i => i.HasTag("engineer_firstdoorlight")).GetComponent<LightComponent>();
|
||||
|
||||
SetDoorAccess(engineer_firstDoor, engineer_firstDoorLight, false);
|
||||
|
||||
// Room 3
|
||||
engineer_reactorObjectiveSensor = Item.ItemList.Find(i => i.HasTag("engineer_reactorobjectivesensor")).GetComponent<MotionSensor>();
|
||||
tutorial_oxygenGenerator = Item.ItemList.Find(i => i.HasTag("tutorial_oxygengenerator")).GetComponent<Powered>();
|
||||
engineer_reactor = Item.ItemList.Find(i => i.HasTag("engineer_reactor")).GetComponent<Reactor>();
|
||||
engineer_reactor.FireDelay = engineer_reactor.MeltdownDelay = float.PositiveInfinity;
|
||||
engineer_reactor.FuelConsumptionRate = 0.0f;
|
||||
engineer_reactor.PowerOn = true;
|
||||
reactorOperatedProperly = false;
|
||||
|
||||
engineer_secondDoor = Item.ItemList.Find(i => i.HasTag("engineer_seconddoor")).GetComponent<Door>(); ;
|
||||
engineer_secondDoorLight = Item.ItemList.Find(i => i.HasTag("engineer_seconddoorlight")).GetComponent<LightComponent>();
|
||||
|
||||
SetDoorAccess(engineer_secondDoor, engineer_secondDoorLight, false);
|
||||
|
||||
// Room 4
|
||||
engineer_repairJunctionBoxObjectiveSensor = Item.ItemList.Find(i => i.HasTag("engineer_repairjunctionboxobjectivesensor")).GetComponent<MotionSensor>();
|
||||
engineer_brokenJunctionBox = Item.ItemList.Find(i => i.HasTag("engineer_brokenjunctionbox"));
|
||||
engineer_thirdDoor = Item.ItemList.Find(i => i.HasTag("engineer_thirddoor")).GetComponent<Door>();
|
||||
engineer_thirdDoorLight = Item.ItemList.Find(i => i.HasTag("engineer_thirddoorlight")).GetComponent<LightComponent>();
|
||||
|
||||
engineer_brokenJunctionBox.Indestructible = false;
|
||||
engineer_brokenJunctionBox.Condition = 0f;
|
||||
|
||||
SetDoorAccess(engineer_thirdDoor, engineer_thirdDoorLight, false);
|
||||
|
||||
// Room 5
|
||||
engineer_disconnectedJunctionBoxObjectiveSensor = Item.ItemList.Find(i => i.HasTag("engineer_disconnectedjunctionboxobjectivesensor")).GetComponent<MotionSensor>();
|
||||
|
||||
engineer_disconnectedJunctionBoxes = new PowerTransfer[4];
|
||||
engineer_disconnectedConnectionPanels = new ConnectionPanel[4];
|
||||
|
||||
for (int i = 0; i < engineer_disconnectedJunctionBoxes.Length; i++)
|
||||
{
|
||||
engineer_disconnectedJunctionBoxes[i] = Item.ItemList.Find(item => item.HasTag($"engineer_disconnectedjunctionbox_{i + 1}")).GetComponent<PowerTransfer>();
|
||||
engineer_disconnectedConnectionPanels[i] = engineer_disconnectedJunctionBoxes[i].Item.GetComponent<ConnectionPanel>();
|
||||
engineer_disconnectedConnectionPanels[i].Locked = false;
|
||||
|
||||
for (int j = 0; j < engineer_disconnectedJunctionBoxes[i].PowerConnections.Count; j++)
|
||||
{
|
||||
foreach (Wire wire in engineer_disconnectedJunctionBoxes[i].PowerConnections[j].Wires)
|
||||
{
|
||||
if (wire == null) continue;
|
||||
wire.Locked = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
engineer_wire_1 = Item.ItemList.Find(i => i.HasTag("engineer_wire_1"));
|
||||
engineer_wire_1.SpriteColor = Color.Transparent;
|
||||
engineer_wire_2 = Item.ItemList.Find(i => i.HasTag("engineer_wire_2"));
|
||||
engineer_wire_2.SpriteColor = Color.Transparent;
|
||||
engineer_lamp_1 = Item.ItemList.Find(i => i.HasTag("engineer_lamp_1")).GetComponent<Powered>();
|
||||
engineer_lamp_2 = Item.ItemList.Find(i => i.HasTag("engineer_lamp_2")).GetComponent<Powered>();
|
||||
engineer_fourthDoor = Item.ItemList.Find(i => i.HasTag("engineer_fourthdoor")).GetComponent<Door>();
|
||||
engineer_fourthDoorLight = Item.ItemList.Find(i => i.HasTag("engineer_fourthdoorlight")).GetComponent<LightComponent>();
|
||||
SetDoorAccess(engineer_fourthDoor, engineer_fourthDoorLight, false);
|
||||
|
||||
// Room 6
|
||||
engineer_workingPump = Item.ItemList.Find(i => i.HasTag("engineer_workingpump")).GetComponent<Pump>();
|
||||
engineer_workingPump.Item.CurrentHull.WaterVolume += engineer_workingPump.Item.CurrentHull.Volume;
|
||||
engineer_workingPump.IsActive = true;
|
||||
tutorial_lockedDoor_1 = Item.ItemList.Find(i => i.HasTag("tutorial_lockeddoor_1")).GetComponent<Door>();
|
||||
SetDoorAccess(tutorial_lockedDoor_1, null, true);
|
||||
|
||||
// Submarine
|
||||
tutorial_submarineDoor = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoor")).GetComponent<Door>();
|
||||
tutorial_submarineDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoorlight")).GetComponent<LightComponent>();
|
||||
SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, true);
|
||||
|
||||
tutorial_enteredSubmarineSensor = Item.ItemList.Find(i => i.HasTag("tutorial_enteredsubmarinesensor")).GetComponent<MotionSensor>();
|
||||
engineer_submarineJunctionBox_1 = Item.ItemList.Find(i => i.HasTag("engineer_submarinejunctionbox_1"));
|
||||
engineer_submarineJunctionBox_2 = Item.ItemList.Find(i => i.HasTag("engineer_submarinejunctionbox_2"));
|
||||
engineer_submarineJunctionBox_3 = Item.ItemList.Find(i => i.HasTag("engineer_submarinejunctionbox_3"));
|
||||
engineer_submarineReactor = Item.ItemList.Find(i => i.HasTag("engineer_submarinereactor")).GetComponent<Reactor>();
|
||||
engineer_submarineReactor.PowerOn = true;
|
||||
engineer_submarineReactor.IsActive = engineer_submarineReactor.AutoTemp = false;
|
||||
|
||||
engineer_submarineJunctionBox_1.Indestructible = false;
|
||||
engineer_submarineJunctionBox_1.Condition = 0f;
|
||||
engineer_submarineJunctionBox_2.Indestructible = false;
|
||||
engineer_submarineJunctionBox_2.Condition = 0f;
|
||||
engineer_submarineJunctionBox_3.Indestructible = false;
|
||||
engineer_submarineJunctionBox_3.Condition = 0f;
|
||||
}
|
||||
|
||||
public override IEnumerable<object> UpdateState()
|
||||
{
|
||||
while (GameMain.Instance.LoadingScreenOpen) yield return null;
|
||||
|
||||
// Room 1
|
||||
SoundPlayer.PlayDamageSound("StructureBlunt", 10, Character.Controlled.WorldPosition);
|
||||
while (shakeTimer > 0.0f) // Wake up, shake
|
||||
{
|
||||
shakeTimer -= 0.1f;
|
||||
GameMain.GameScreen.Cam.Shake = shakeAmount;
|
||||
yield return new WaitForSeconds(0.1f, false);
|
||||
}
|
||||
|
||||
//// Remove
|
||||
//for (int i = 0; i < engineer_disconnectedJunctionBoxes.Length; i++)
|
||||
//{
|
||||
// SetHighlight(engineer_disconnectedJunctionBoxes[i].Item, true);
|
||||
//}
|
||||
//do { CheckGhostWires(); HandleJunctionBoxWiringHighlights(); yield return null; } while (engineer_workingPump.Voltage < engineer_workingPump.MinVoltage); // Wait until connected all the way to the pump
|
||||
//CheckGhostWires();
|
||||
//// Remove
|
||||
|
||||
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.WakeUp"), ChatMessageType.Radio, null);
|
||||
SetHighlight(engineer_equipmentCabinet.Item, true);
|
||||
|
||||
// Room 2
|
||||
do { yield return null; } while (!engineer_equipmentObjectiveSensor.MotionDetected);
|
||||
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.Equipment"), ChatMessageType.Radio, null);
|
||||
yield return new WaitForSeconds(0.5f, false);
|
||||
TriggerTutorialSegment(0, GameMain.Config.KeyBindText(InputType.Select), GameMain.Config.KeyBindText(InputType.Deselect), GameMain.Config.KeyBindText(InputType.ToggleInventory)); // Retrieve equipment
|
||||
bool firstSlotRemoved = false;
|
||||
bool secondSlotRemoved = false;
|
||||
bool thirdSlotRemoved = false;
|
||||
bool fourthSlotRemoved = false;
|
||||
do
|
||||
{
|
||||
if (IsSelectedItem(engineer_equipmentCabinet.Item))
|
||||
{
|
||||
if (!firstSlotRemoved)
|
||||
{
|
||||
HighlightInventorySlot(engineer_equipmentCabinet.Inventory, 0, highlightColor, .5f, .5f, 0f);
|
||||
if (engineer_equipmentCabinet.Inventory.Items[0] == null) firstSlotRemoved = true;
|
||||
}
|
||||
|
||||
if (!secondSlotRemoved)
|
||||
{
|
||||
HighlightInventorySlot(engineer_equipmentCabinet.Inventory, 1, highlightColor, .5f, .5f, 0f);
|
||||
if (engineer_equipmentCabinet.Inventory.Items[1] == null) secondSlotRemoved = true;
|
||||
}
|
||||
|
||||
if (!thirdSlotRemoved)
|
||||
{
|
||||
HighlightInventorySlot(engineer_equipmentCabinet.Inventory, 2, highlightColor, .5f, .5f, 0f);
|
||||
if (engineer_equipmentCabinet.Inventory.Items[2] == null) thirdSlotRemoved = true;
|
||||
}
|
||||
|
||||
if (!fourthSlotRemoved)
|
||||
{
|
||||
HighlightInventorySlot(engineer_equipmentCabinet.Inventory, 3, highlightColor, .5f, .5f, 0f);
|
||||
if (engineer_equipmentCabinet.Inventory.Items[2] == null) fourthSlotRemoved = true;
|
||||
}
|
||||
|
||||
for (int i = 0; i < engineer.Inventory.slots.Length; i++)
|
||||
{
|
||||
if (engineer.Inventory.Items[i] == null) HighlightInventorySlot(engineer.Inventory, i, highlightColor, .5f, .5f, 0f);
|
||||
}
|
||||
}
|
||||
|
||||
yield return null;
|
||||
} while (!engineer_equipmentCabinet.Inventory.IsEmpty()); // Wait until looted
|
||||
RemoveCompletedObjective(segments[0]);
|
||||
SetHighlight(engineer_equipmentCabinet.Item, false);
|
||||
SetHighlight(engineer_reactor.Item, true);
|
||||
SetDoorAccess(engineer_firstDoor, engineer_firstDoorLight, true);
|
||||
|
||||
// Room 3
|
||||
do { yield return null; } while (!IsSelectedItem(engineer_reactor.Item));
|
||||
yield return new WaitForSeconds(0.5f, false);
|
||||
TriggerTutorialSegment(1);
|
||||
do
|
||||
{
|
||||
if (IsSelectedItem(engineer_reactor.Item))
|
||||
{
|
||||
engineer_reactor.AutoTemp = false;
|
||||
if (engineer_reactor.PowerButton.FlashTimer <= 0)
|
||||
{
|
||||
engineer_reactor.PowerButton.Flash(highlightColor, 1.5f, false);
|
||||
}
|
||||
}
|
||||
yield return null;
|
||||
} while (!engineer_reactor.PowerOn);
|
||||
do
|
||||
{
|
||||
if (IsSelectedItem(engineer_reactor.Item) && engineer_reactor.Item.OwnInventory.slots != null)
|
||||
{
|
||||
engineer_reactor.AutoTemp = false;
|
||||
HighlightInventorySlot(engineer.Inventory, "fuelrod", highlightColor, 0.5f, 0.5f, 0f);
|
||||
|
||||
for (int i = 0; i < engineer_reactor.Item.OwnInventory.slots.Length; i++)
|
||||
{
|
||||
HighlightInventorySlot(engineer_reactor.Item.OwnInventory, i, highlightColor, 0.5f, 0.5f, 0f);
|
||||
}
|
||||
}
|
||||
yield return null;
|
||||
} while (engineer_reactor.AvailableFuel == 0);
|
||||
CoroutineManager.StartCoroutine(ReactorOperatedProperly());
|
||||
do
|
||||
{
|
||||
if (IsSelectedItem(engineer_reactor.Item))
|
||||
{
|
||||
engineer_reactor.AutoTemp = false;
|
||||
if (engineer_reactor.FissionRateScrollBar.FlashTimer <= 0)
|
||||
{
|
||||
engineer_reactor.FissionRateScrollBar.Flash(highlightColor, 1.5f);
|
||||
}
|
||||
|
||||
if (engineer_reactor.TurbineOutputScrollBar.FlashTimer <= 0)
|
||||
{
|
||||
engineer_reactor.TurbineOutputScrollBar.Flash(highlightColor, 1.5f);
|
||||
}
|
||||
}
|
||||
yield return null;
|
||||
} while (!reactorOperatedProperly);
|
||||
yield return new WaitForSeconds(2f, false);
|
||||
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.ReactorStable"), ChatMessageType.Radio, null);
|
||||
do
|
||||
{
|
||||
if (IsSelectedItem(engineer_reactor.Item))
|
||||
{
|
||||
if (engineer_reactor.AutoTempSwitch.FlashTimer <= 0)
|
||||
{
|
||||
engineer_reactor.AutoTempSwitch.Flash(highlightColor, 1.5f, false, false, new Vector2(10, 10));
|
||||
}
|
||||
}
|
||||
yield return null;
|
||||
} while (!engineer_reactor.AutoTemp);
|
||||
|
||||
float wait = 1.5f;
|
||||
do
|
||||
{
|
||||
yield return new WaitForSeconds(0.1f, false);
|
||||
wait -= 0.1f;
|
||||
engineer_reactor.AutoTemp = true;
|
||||
} while (wait > 0.0f);
|
||||
engineer.SelectedConstruction = null;
|
||||
engineer_reactor.CanBeSelected = false;
|
||||
RemoveCompletedObjective(segments[1]);
|
||||
SetHighlight(engineer_reactor.Item, false);
|
||||
SetHighlight(engineer_brokenJunctionBox, true);
|
||||
SetDoorAccess(engineer_secondDoor, engineer_secondDoorLight, true);
|
||||
|
||||
// Room 4
|
||||
do { yield return null; } while (!engineer_secondDoor.IsOpen);
|
||||
yield return new WaitForSeconds(1f, false);
|
||||
Repairable repairableJunctionBoxComponent = engineer_brokenJunctionBox.GetComponent<Repairable>();
|
||||
TriggerTutorialSegment(2, GameMain.Config.KeyBindText(InputType.Select)); // Repair the junction box
|
||||
do
|
||||
{
|
||||
if (!engineer.HasEquippedItem("screwdriver"))
|
||||
{
|
||||
HighlightInventorySlot(engineer.Inventory, "screwdriver", highlightColor, .5f, .5f, 0f);
|
||||
}
|
||||
else if (IsSelectedItem(engineer_brokenJunctionBox) && repairableJunctionBoxComponent.CurrentFixer == null)
|
||||
{
|
||||
if (repairableJunctionBoxComponent.RepairButton.FlashTimer <= 0)
|
||||
{
|
||||
repairableJunctionBoxComponent.RepairButton.Flash();
|
||||
}
|
||||
}
|
||||
yield return null;
|
||||
} while (!engineer_brokenJunctionBox.IsFullCondition); // Wait until repaired
|
||||
SetHighlight(engineer_brokenJunctionBox, false);
|
||||
RemoveCompletedObjective(segments[2]);
|
||||
SetDoorAccess(engineer_thirdDoor, engineer_thirdDoorLight, true);
|
||||
for (int i = 0; i < engineer_disconnectedJunctionBoxes.Length; i++)
|
||||
{
|
||||
SetHighlight(engineer_disconnectedJunctionBoxes[i].Item, true);
|
||||
}
|
||||
|
||||
// Room 5
|
||||
do { yield return null; } while (!engineer_thirdDoor.IsOpen);
|
||||
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.FaultyWiring"), ChatMessageType.Radio, null);
|
||||
yield return new WaitForSeconds(2f, false);
|
||||
TriggerTutorialSegment(3, GameMain.Config.KeyBindText(InputType.Use), GameMain.Config.KeyBindText(InputType.Deselect)); // Connect the junction boxes
|
||||
do { CheckGhostWires(); HandleJunctionBoxWiringHighlights(); yield return null; } while (engineer_workingPump.Voltage < engineer_workingPump.MinVoltage); // Wait until connected all the way to the pump
|
||||
CheckGhostWires();
|
||||
for (int i = 0; i < engineer_disconnectedJunctionBoxes.Length; i++)
|
||||
{
|
||||
SetHighlight(engineer_disconnectedJunctionBoxes[i].Item, false);
|
||||
}
|
||||
RemoveCompletedObjective(segments[3]);
|
||||
do { yield return null; } while (engineer_workingPump.Item.CurrentHull.WaterPercentage > waterVolumeBeforeOpening); // Wait until drained
|
||||
wiringActive = false;
|
||||
SetDoorAccess(engineer_fourthDoor, engineer_fourthDoorLight, true);
|
||||
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.ChangeOfPlans"), ChatMessageType.Radio, null);
|
||||
|
||||
// Submarine
|
||||
do { yield return null; } while (!tutorial_enteredSubmarineSensor.MotionDetected);
|
||||
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.Submarine"), ChatMessageType.Radio, null);
|
||||
yield return new WaitForSeconds(2f, false);
|
||||
TriggerTutorialSegment(4); // Repair junction box
|
||||
while (ContentRunning) yield return null;
|
||||
SetHighlight(engineer_submarineJunctionBox_1, true);
|
||||
SetHighlight(engineer_submarineJunctionBox_2, true);
|
||||
SetHighlight(engineer_submarineJunctionBox_3, true);
|
||||
engineer.AddActiveObjectiveEntity(engineer_submarineJunctionBox_1, engineer_repairIcon, engineer_repairIconColor);
|
||||
engineer.AddActiveObjectiveEntity(engineer_submarineJunctionBox_2, engineer_repairIcon, engineer_repairIconColor);
|
||||
engineer.AddActiveObjectiveEntity(engineer_submarineJunctionBox_3, engineer_repairIcon, engineer_repairIconColor);
|
||||
// Remove highlights when each individual machine is repaired
|
||||
do { CheckJunctionBoxHighlights(); yield return null; } while (!engineer_submarineJunctionBox_1.IsFullCondition || !engineer_submarineJunctionBox_2.IsFullCondition || !engineer_submarineJunctionBox_3.IsFullCondition);
|
||||
CheckJunctionBoxHighlights();
|
||||
RemoveCompletedObjective(segments[4]);
|
||||
yield return new WaitForSeconds(2f, false);
|
||||
|
||||
TriggerTutorialSegment(5); // Powerup reactor
|
||||
SetHighlight(engineer_submarineReactor.Item, true);
|
||||
engineer.AddActiveObjectiveEntity(engineer_submarineReactor.Item, engineer_reactorIcon, engineer_reactorIconColor);
|
||||
do { yield return null; } while (!IsReactorPoweredUp(engineer_submarineReactor)); // Wait until ~matches load
|
||||
engineer.RemoveActiveObjectiveEntity(engineer_submarineReactor.Item);
|
||||
SetHighlight(engineer_submarineReactor.Item, false);
|
||||
RemoveCompletedObjective(segments[5]);
|
||||
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.Complete"), ChatMessageType.Radio, null);
|
||||
|
||||
yield return new WaitForSeconds(4f, false);
|
||||
|
||||
CoroutineManager.StartCoroutine(TutorialCompleted());
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
base.Update(deltaTime);
|
||||
|
||||
if (wiringActive)
|
||||
{
|
||||
for (int i = 0; i < engineer_disconnectedJunctionBoxes.Length; i++)
|
||||
{
|
||||
for (int j = 0; j < engineer_disconnectedJunctionBoxes[i].PowerConnections.Count; j++)
|
||||
{
|
||||
engineer_disconnectedJunctionBoxes[i].PowerConnections[j].UpdateFlashTimer(deltaTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsSelectedItem(Item item)
|
||||
{
|
||||
return engineer?.SelectedConstruction == item;
|
||||
}
|
||||
|
||||
private IEnumerable<object> ReactorOperatedProperly()
|
||||
{
|
||||
float timer;
|
||||
|
||||
for (int i = 0; i < reactorLoads.Length; i++)
|
||||
{
|
||||
timer = reactorLoadChangeTime;
|
||||
tutorial_oxygenGenerator.PowerConsumption = reactorLoads[i];
|
||||
while (timer > 0)
|
||||
{
|
||||
yield return new WaitForSeconds(0.1f, false);
|
||||
if (IsReactorPoweredUp(engineer_reactor))
|
||||
{
|
||||
timer -= 0.1f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reactorOperatedProperly = true;
|
||||
}
|
||||
|
||||
private void CheckGhostWires()
|
||||
{
|
||||
Color wireColor =
|
||||
Color.Orange *
|
||||
MathHelper.Lerp(0.25f, 0.75f, (float)(Math.Sin((Timing.TotalTime * 4.0f)) + 1.0f) / 2.0f);
|
||||
|
||||
if (engineer_wire_1 != null)
|
||||
{
|
||||
engineer_wire_1.SpriteColor = wireColor;
|
||||
if (engineer_lamp_1.Voltage > engineer_lamp_1.MinVoltage)
|
||||
{
|
||||
engineer_wire_1.Remove();
|
||||
engineer_wire_1 = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (engineer_wire_2 != null)
|
||||
{
|
||||
engineer_wire_2.SpriteColor = wireColor;
|
||||
if (engineer_lamp_2.Voltage > engineer_lamp_2.MinVoltage)
|
||||
{
|
||||
engineer_wire_2.Remove();
|
||||
engineer_wire_2 = null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void HandleJunctionBoxWiringHighlights()
|
||||
{
|
||||
Item selected = engineer.SelectedConstruction;
|
||||
|
||||
if (!engineer.HasEquippedItem("screwdriver"))
|
||||
{
|
||||
HighlightInventorySlot(engineer.Inventory, "screwdriver", highlightColor, 0.5f, 0.5f, 0f);
|
||||
}
|
||||
|
||||
int selectedIndex = -1;
|
||||
|
||||
if (selected != null)
|
||||
{
|
||||
for (int i = 0; i < engineer_disconnectedJunctionBoxes.Length; i++)
|
||||
{
|
||||
if (selected == engineer_disconnectedJunctionBoxes[i].Item)
|
||||
{
|
||||
selectedIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wiringActive = selectedIndex != -1;
|
||||
|
||||
if (!engineer.HasEquippedItem("wire"))
|
||||
{
|
||||
HighlightInventorySlotWithTag(engineer.Inventory, "wire", highlightColor, 0.5f, 0.5f, 0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!wiringActive) return;
|
||||
for (int i = 0; i < engineer_disconnectedConnectionPanels[selectedIndex].Connections.Count; i++)
|
||||
{
|
||||
var connection = engineer_disconnectedConnectionPanels[selectedIndex].Connections[i];
|
||||
if (connection.IsPower && connection.FlashTimer <= 0)
|
||||
{
|
||||
foreach (Wire wire in engineer_disconnectedConnectionPanels[selectedIndex].Connections[i].Wires)
|
||||
{
|
||||
if (wire == null) continue;
|
||||
if (!wire.Locked)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
connection.Flash(highlightColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckJunctionBoxHighlights()
|
||||
{
|
||||
if (engineer_submarineJunctionBox_1.IsFullCondition && engineer_submarineJunctionBox_1.ExternalHighlight)
|
||||
{
|
||||
SetHighlight(engineer_submarineJunctionBox_1, false);
|
||||
engineer.RemoveActiveObjectiveEntity(engineer_submarineJunctionBox_1);
|
||||
}
|
||||
if (engineer_submarineJunctionBox_2.IsFullCondition && engineer_submarineJunctionBox_2.ExternalHighlight)
|
||||
{
|
||||
SetHighlight(engineer_submarineJunctionBox_2, false);
|
||||
engineer.RemoveActiveObjectiveEntity(engineer_submarineJunctionBox_2);
|
||||
}
|
||||
if (engineer_submarineJunctionBox_3.IsFullCondition && engineer_submarineJunctionBox_3.ExternalHighlight)
|
||||
{
|
||||
SetHighlight(engineer_submarineJunctionBox_3, false);
|
||||
engineer.RemoveActiveObjectiveEntity(engineer_submarineJunctionBox_3);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsReactorPoweredUp(Reactor reactor)
|
||||
{
|
||||
float load = 0.0f;
|
||||
List<Connection> connections = reactor.Item.Connections;
|
||||
if (connections != null && connections.Count > 0)
|
||||
{
|
||||
foreach (Connection connection in connections)
|
||||
{
|
||||
if (!connection.IsPower) continue;
|
||||
foreach (Connection recipient in connection.Recipients)
|
||||
{
|
||||
if (!(recipient.Item is Item it)) continue;
|
||||
|
||||
PowerTransfer pt = it.GetComponent<PowerTransfer>();
|
||||
if (pt == null) continue;
|
||||
|
||||
load = Math.Max(load, pt.PowerLoad);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Math.Abs(load + reactor.CurrPowerConsumption) < reactorLoadError;
|
||||
}
|
||||
}
|
||||
}
|
||||
+639
@@ -0,0 +1,639 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma.Tutorials
|
||||
{
|
||||
class MechanicTutorial : ScenarioTutorial
|
||||
{
|
||||
// Other tutorial items
|
||||
private LightComponent tutorial_securityFinalDoorLight;
|
||||
private Door tutorial_upperFinalDoor;
|
||||
private Steering tutorial_submarineSteering;
|
||||
|
||||
// Room 1
|
||||
private float shakeTimer = 1f;
|
||||
private float shakeAmount = 20f;
|
||||
private Door mechanic_firstDoor;
|
||||
private LightComponent mechanic_firstDoorLight;
|
||||
|
||||
// Room 2
|
||||
private MotionSensor mechanic_equipmentObjectiveSensor;
|
||||
private ItemContainer mechanic_equipmentCabinet;
|
||||
private Door mechanic_secondDoor;
|
||||
private LightComponent mechanic_secondDoorLight;
|
||||
|
||||
// Room 3
|
||||
private MotionSensor mechanic_weldingObjectiveSensor;
|
||||
private Pump mechanic_workingPump;
|
||||
private Door mechanic_thirdDoor;
|
||||
private LightComponent mechanic_thirdDoorLight;
|
||||
private Structure mechanic_brokenWall_1;
|
||||
private Hull mechanic_brokenhull_1;
|
||||
|
||||
// Room 4
|
||||
private MotionSensor mechanic_craftingObjectiveSensor;
|
||||
private Deconstructor mechanic_deconstructor;
|
||||
private Fabricator mechanic_fabricator;
|
||||
private ItemContainer mechanic_craftingCabinet;
|
||||
private Door mechanic_fourthDoor;
|
||||
private LightComponent mechanic_fourthDoorLight;
|
||||
|
||||
// Room 5
|
||||
private MotionSensor mechanic_fireSensor;
|
||||
private DummyFireSource mechanic_fire;
|
||||
private Door mechanic_fifthDoor;
|
||||
private LightComponent mechanic_fifthDoorLight;
|
||||
|
||||
// Room 6
|
||||
private MotionSensor mechanic_divingSuitObjectiveSensor;
|
||||
private ItemContainer mechanic_divingSuitContainer;
|
||||
private ItemContainer mechanic_oxygenContainer;
|
||||
private Door tutorial_mechanicFinalDoor;
|
||||
private LightComponent tutorial_mechanicFinalDoorLight;
|
||||
|
||||
// Room 7
|
||||
private Pump mechanic_brokenPump;
|
||||
private Structure mechanic_brokenWall_2;
|
||||
private Hull mechanic_brokenhull_2;
|
||||
private Door tutorial_submarineDoor;
|
||||
private LightComponent tutorial_submarineDoorLight;
|
||||
|
||||
// Submarine
|
||||
private MotionSensor tutorial_enteredSubmarineSensor;
|
||||
private Engine mechanic_submarineEngine;
|
||||
private Pump mechanic_ballastPump_1;
|
||||
private Pump mechanic_ballastPump_2;
|
||||
|
||||
// Variables
|
||||
private const float waterVolumeBeforeOpening = 15f;
|
||||
private string radioSpeakerName;
|
||||
private Character mechanic;
|
||||
private Sprite mechanic_repairIcon;
|
||||
private Color mechanic_repairIconColor;
|
||||
private Sprite mechanic_weldIcon;
|
||||
|
||||
public MechanicTutorial(XElement element) : base(element)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public override void Start()
|
||||
{
|
||||
base.Start();
|
||||
|
||||
radioSpeakerName = TextManager.Get("Tutorial.Radio.Speaker");
|
||||
mechanic = Character.Controlled;
|
||||
|
||||
var toolbox = FindOrGiveItem(mechanic, "toolbox");
|
||||
toolbox.Unequip(mechanic);
|
||||
mechanic.Inventory.RemoveItem(toolbox);
|
||||
|
||||
var crowbar = FindOrGiveItem(mechanic, "crowbar");
|
||||
crowbar.Unequip(mechanic);
|
||||
mechanic.Inventory.RemoveItem(crowbar);
|
||||
|
||||
var repairOrder = Order.GetPrefab("repairsystems");
|
||||
mechanic_repairIcon = repairOrder.SymbolSprite;
|
||||
mechanic_repairIconColor = repairOrder.Color;
|
||||
mechanic_weldIcon = new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(1, 256, 127, 127), new Vector2(0.5f, 0.5f));
|
||||
|
||||
// Other tutorial items
|
||||
tutorial_securityFinalDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_securityfinaldoorlight")).GetComponent<LightComponent>();
|
||||
tutorial_upperFinalDoor = Item.ItemList.Find(i => i.HasTag("tutorial_upperfinaldoor")).GetComponent<Door>();
|
||||
tutorial_submarineSteering = Item.ItemList.Find(i => i.HasTag("command")).GetComponent<Steering>();
|
||||
|
||||
tutorial_submarineSteering.CanBeSelected = false;
|
||||
foreach (ItemComponent ic in tutorial_submarineSteering.Item.Components)
|
||||
{
|
||||
ic.CanBeSelected = false;
|
||||
}
|
||||
|
||||
SetDoorAccess(null, tutorial_securityFinalDoorLight, false);
|
||||
SetDoorAccess(tutorial_upperFinalDoor, null, false);
|
||||
|
||||
// Room 1
|
||||
mechanic_firstDoor = Item.ItemList.Find(i => i.HasTag("mechanic_firstdoor")).GetComponent<Door>();
|
||||
mechanic_firstDoorLight = Item.ItemList.Find(i => i.HasTag("mechanic_firstdoorlight")).GetComponent<LightComponent>();
|
||||
|
||||
SetDoorAccess(mechanic_firstDoor, mechanic_firstDoorLight, false);
|
||||
|
||||
// Room 2
|
||||
mechanic_equipmentObjectiveSensor = Item.ItemList.Find(i => i.HasTag("mechanic_equipmentobjectivesensor")).GetComponent<MotionSensor>();
|
||||
mechanic_equipmentCabinet = Item.ItemList.Find(i => i.HasTag("mechanic_equipmentcabinet")).GetComponent<ItemContainer>();
|
||||
mechanic_secondDoor = Item.ItemList.Find(i => i.HasTag("mechanic_seconddoor")).GetComponent<Door>();
|
||||
mechanic_secondDoorLight = Item.ItemList.Find(i => i.HasTag("mechanic_seconddoorlight")).GetComponent<LightComponent>();
|
||||
|
||||
SetDoorAccess(mechanic_secondDoor, mechanic_secondDoorLight, false);
|
||||
|
||||
// Room 3
|
||||
mechanic_weldingObjectiveSensor = Item.ItemList.Find(i => i.HasTag("mechanic_weldingobjectivesensor")).GetComponent<MotionSensor>();
|
||||
mechanic_workingPump = Item.ItemList.Find(i => i.HasTag("mechanic_workingpump")).GetComponent<Pump>();
|
||||
mechanic_thirdDoor = Item.ItemList.Find(i => i.HasTag("mechanic_thirddoor")).GetComponent<Door>();
|
||||
mechanic_thirdDoorLight = Item.ItemList.Find(i => i.HasTag("mechanic_thirddoorlight")).GetComponent<LightComponent>();
|
||||
mechanic_brokenWall_1 = Structure.WallList.Find(i => i.SpecialTag == "mechanic_brokenwall_1");
|
||||
//mechanic_ladderSensor = Item.ItemList.Find(i => i.HasTag("mechanic_laddersensor")).GetComponent<MotionSensor>();
|
||||
|
||||
SetDoorAccess(mechanic_thirdDoor, mechanic_thirdDoorLight, false);
|
||||
mechanic_brokenWall_1.Indestructible = false;
|
||||
mechanic_brokenWall_1.SpriteColor = Color.White;
|
||||
for (int i = 0; i < mechanic_brokenWall_1.SectionCount; i++)
|
||||
{
|
||||
mechanic_brokenWall_1.AddDamage(i, 165);
|
||||
}
|
||||
mechanic_brokenhull_1 = mechanic_brokenWall_1.Sections[0].gap.FlowTargetHull;
|
||||
|
||||
// Room 4
|
||||
mechanic_craftingObjectiveSensor = Item.ItemList.Find(i => i.HasTag("mechanic_craftingobjectivesensor")).GetComponent<MotionSensor>();
|
||||
mechanic_deconstructor = Item.ItemList.Find(i => i.HasTag("mechanic_deconstructor")).GetComponent<Deconstructor>();
|
||||
mechanic_fabricator = Item.ItemList.Find(i => i.HasTag("mechanic_fabricator")).GetComponent<Fabricator>();
|
||||
mechanic_craftingCabinet = Item.ItemList.Find(i => i.HasTag("mechanic_craftingcabinet")).GetComponent<ItemContainer>();
|
||||
mechanic_fourthDoor = Item.ItemList.Find(i => i.HasTag("mechanic_fourthdoor")).GetComponent<Door>();
|
||||
mechanic_fourthDoorLight = Item.ItemList.Find(i => i.HasTag("mechanic_fourthdoorlight")).GetComponent<LightComponent>();
|
||||
|
||||
SetDoorAccess(mechanic_fourthDoor, mechanic_fourthDoorLight, false);
|
||||
|
||||
// Room 5
|
||||
mechanic_fifthDoor = Item.ItemList.Find(i => i.HasTag("mechanic_fifthdoor")).GetComponent<Door>();
|
||||
mechanic_fifthDoorLight = Item.ItemList.Find(i => i.HasTag("mechanic_fifthdoorlight")).GetComponent<LightComponent>();
|
||||
mechanic_fireSensor = Item.ItemList.Find(i => i.HasTag("mechanic_firesensor")).GetComponent<MotionSensor>();
|
||||
|
||||
SetDoorAccess(mechanic_fifthDoor, mechanic_fifthDoorLight, false);
|
||||
|
||||
// Room 6
|
||||
mechanic_divingSuitObjectiveSensor = Item.ItemList.Find(i => i.HasTag("mechanic_divingsuitobjectivesensor")).GetComponent<MotionSensor>();
|
||||
mechanic_divingSuitContainer = Item.ItemList.Find(i => i.HasTag("mechanic_divingsuitcontainer")).GetComponent<ItemContainer>();
|
||||
for (int i = 0; i < mechanic_divingSuitContainer.Inventory.Items.Length; i++)
|
||||
{
|
||||
foreach (ItemComponent ic in mechanic_divingSuitContainer.Inventory.Items[i].Components)
|
||||
{
|
||||
ic.CanBePicked = true;
|
||||
}
|
||||
}
|
||||
mechanic_oxygenContainer = Item.ItemList.Find(i => i.HasTag("mechanic_oxygencontainer")).GetComponent<ItemContainer>();
|
||||
for (int i = 0; i < mechanic_oxygenContainer.Inventory.Items.Length; i++)
|
||||
{
|
||||
foreach (ItemComponent ic in mechanic_oxygenContainer.Inventory.Items[i].Components)
|
||||
{
|
||||
ic.CanBePicked = true;
|
||||
}
|
||||
}
|
||||
tutorial_mechanicFinalDoor = Item.ItemList.Find(i => i.HasTag("tutorial_mechanicfinaldoor")).GetComponent<Door>();
|
||||
tutorial_mechanicFinalDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_mechanicfinaldoorlight")).GetComponent<LightComponent>();
|
||||
|
||||
SetDoorAccess(tutorial_mechanicFinalDoor, tutorial_mechanicFinalDoorLight, false);
|
||||
|
||||
// Room 7
|
||||
mechanic_brokenPump = Item.ItemList.Find(i => i.HasTag("mechanic_brokenpump")).GetComponent<Pump>();
|
||||
mechanic_brokenPump.Item.Indestructible = false;
|
||||
mechanic_brokenPump.Item.Condition = 0;
|
||||
mechanic_brokenPump.CanBeSelected = false;
|
||||
mechanic_brokenPump.Item.GetComponent<Repairable>().CanBeSelected = false;
|
||||
mechanic_brokenWall_2 = Structure.WallList.Find(i => i.SpecialTag == "mechanic_brokenwall_2");
|
||||
tutorial_submarineDoor = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoor")).GetComponent<Door>();
|
||||
tutorial_submarineDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoorlight")).GetComponent<LightComponent>();
|
||||
|
||||
mechanic_brokenWall_2.Indestructible = false;
|
||||
mechanic_brokenWall_2.SpriteColor = Color.White;
|
||||
for (int i = 0; i < mechanic_brokenWall_2.SectionCount; i++)
|
||||
{
|
||||
mechanic_brokenWall_2.AddDamage(i, 165);
|
||||
}
|
||||
mechanic_brokenhull_2 = mechanic_brokenWall_2.Sections[0].gap.FlowTargetHull;
|
||||
SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, false);
|
||||
|
||||
// Submarine
|
||||
tutorial_enteredSubmarineSensor = Item.ItemList.Find(i => i.HasTag("tutorial_enteredsubmarinesensor")).GetComponent<MotionSensor>();
|
||||
mechanic_submarineEngine = Item.ItemList.Find(i => i.HasTag("mechanic_submarineengine")).GetComponent<Engine>();
|
||||
mechanic_submarineEngine.Item.Indestructible = false;
|
||||
mechanic_submarineEngine.Item.Condition = 0f;
|
||||
mechanic_ballastPump_1 = Item.ItemList.Find(i => i.HasTag("mechanic_ballastpump_1")).GetComponent<Pump>();
|
||||
mechanic_ballastPump_1.Item.Indestructible = false;
|
||||
mechanic_ballastPump_1.Item.Condition = 0f;
|
||||
mechanic_ballastPump_2 = Item.ItemList.Find(i => i.HasTag("mechanic_ballastpump_2")).GetComponent<Pump>();
|
||||
mechanic_ballastPump_2.Item.Indestructible = false;
|
||||
mechanic_ballastPump_2.Item.Condition = 0f;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
mechanic_brokenhull_1.WaterVolume = MathHelper.Clamp(mechanic_brokenhull_1.WaterVolume, 0, mechanic_brokenhull_1.Volume * 0.85f);
|
||||
base.Update(deltaTime);
|
||||
}
|
||||
|
||||
public override IEnumerable<object> UpdateState()
|
||||
{
|
||||
while (GameMain.Instance.LoadingScreenOpen) yield return null;
|
||||
|
||||
// Room 1
|
||||
SoundPlayer.PlayDamageSound("StructureBlunt", 10, Character.Controlled.WorldPosition);
|
||||
while (shakeTimer > 0.0f) // Wake up, shake
|
||||
{
|
||||
shakeTimer -= 0.1f;
|
||||
GameMain.GameScreen.Cam.Shake = shakeAmount;
|
||||
yield return new WaitForSeconds(0.1f, false);
|
||||
}
|
||||
yield return new WaitForSeconds(2.5f, false);
|
||||
|
||||
mechanic_fabricator.RemoveFabricationRecipes(new List<string>() { "extinguisher", "wrench", "weldingtool", "weldingfuel", "divingmask", "railgunshell", "nuclearshell", "uex", "harpoongun" });
|
||||
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.WakeUp"), ChatMessageType.Radio, null);
|
||||
|
||||
yield return new WaitForSeconds(2.5f, false);
|
||||
TriggerTutorialSegment(0, GameMain.Config.KeyBindText(InputType.Up), GameMain.Config.KeyBindText(InputType.Left), GameMain.Config.KeyBindText(InputType.Down), GameMain.Config.KeyBindText(InputType.Right), GameMain.Config.KeyBindText(InputType.Select), GameMain.Config.KeyBindText(InputType.Select)); // Open door objective
|
||||
yield return new WaitForSeconds(0.0f, false);
|
||||
SetDoorAccess(mechanic_firstDoor, mechanic_firstDoorLight, true);
|
||||
SetHighlight(mechanic_firstDoor.Item, true);
|
||||
do { yield return null; } while (!mechanic_firstDoor.IsOpen);
|
||||
SetHighlight(mechanic_firstDoor.Item, false);
|
||||
yield return new WaitForSeconds(1.5f, false);
|
||||
RemoveCompletedObjective(segments[0]);
|
||||
|
||||
// Room 2
|
||||
yield return new WaitForSeconds(0.0f, false);
|
||||
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.Equipment"), ChatMessageType.Radio, null);
|
||||
do { yield return null; } while (!mechanic_equipmentObjectiveSensor.MotionDetected);
|
||||
TriggerTutorialSegment(1, GameMain.Config.KeyBindText(InputType.Select), GameMain.Config.KeyBindText(InputType.Deselect), GameMain.Config.KeyBindText(InputType.ToggleInventory)); // Equipment & inventory objective
|
||||
SetHighlight(mechanic_equipmentCabinet.Item, true);
|
||||
bool firstSlotRemoved = false;
|
||||
bool secondSlotRemoved = false;
|
||||
bool thirdSlotRemoved = false;
|
||||
do
|
||||
{
|
||||
if (IsSelectedItem(mechanic_equipmentCabinet.Item))
|
||||
{
|
||||
if (!firstSlotRemoved)
|
||||
{
|
||||
HighlightInventorySlot(mechanic_equipmentCabinet.Inventory, 0, highlightColor, .5f, .5f, 0f);
|
||||
if (mechanic_equipmentCabinet.Inventory.Items[0] == null) firstSlotRemoved = true;
|
||||
}
|
||||
|
||||
if (!secondSlotRemoved)
|
||||
{
|
||||
HighlightInventorySlot(mechanic_equipmentCabinet.Inventory, 1, highlightColor, .5f, .5f, 0f);
|
||||
if (mechanic_equipmentCabinet.Inventory.Items[1] == null) secondSlotRemoved = true;
|
||||
}
|
||||
|
||||
if (!thirdSlotRemoved)
|
||||
{
|
||||
HighlightInventorySlot(mechanic_equipmentCabinet.Inventory, 2, highlightColor, .5f, .5f, 0f);
|
||||
if (mechanic_equipmentCabinet.Inventory.Items[2] == null) thirdSlotRemoved = true;
|
||||
}
|
||||
|
||||
for (int i = 0; i < mechanic.Inventory.slots.Length; i++)
|
||||
{
|
||||
if (mechanic.Inventory.Items[i] == null) HighlightInventorySlot(mechanic.Inventory, i, highlightColor, .5f, .5f, 0f);
|
||||
}
|
||||
}
|
||||
|
||||
yield return null;
|
||||
} while (mechanic.Inventory.FindItemByIdentifier("divingmask") == null || mechanic.Inventory.FindItemByIdentifier("weldingtool") == null || mechanic.Inventory.FindItemByIdentifier("wrench") == null); // Wait until looted
|
||||
SetHighlight(mechanic_equipmentCabinet.Item, false);
|
||||
yield return new WaitForSeconds(1.5f, false);
|
||||
RemoveCompletedObjective(segments[1]);
|
||||
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.Breach"), ChatMessageType.Radio, null);
|
||||
|
||||
// Room 3
|
||||
do { yield return null; } while (!mechanic_weldingObjectiveSensor.MotionDetected);
|
||||
TriggerTutorialSegment(2, GameMain.Config.KeyBindText(InputType.Aim), GameMain.Config.KeyBindText(InputType.Shoot), GameMain.Config.KeyBindText(InputType.ToggleInventory)); // Welding objective
|
||||
do
|
||||
{
|
||||
if (!mechanic.HasEquippedItem("divingmask"))
|
||||
{
|
||||
HighlightInventorySlot(mechanic.Inventory, "divingmask", highlightColor, .5f, .5f, 0f);
|
||||
}
|
||||
|
||||
if (!mechanic.HasEquippedItem("weldingtool"))
|
||||
{
|
||||
HighlightInventorySlot(mechanic.Inventory, "weldingtool", highlightColor, .5f, .5f, 0f);
|
||||
}
|
||||
yield return null;
|
||||
} while (!mechanic.HasEquippedItem("divingmask") || !mechanic.HasEquippedItem("weldingtool")); // Wait until equipped
|
||||
SetDoorAccess(mechanic_secondDoor, mechanic_secondDoorLight, true);
|
||||
mechanic.AddActiveObjectiveEntity(mechanic_brokenWall_1, mechanic_weldIcon, mechanic_repairIconColor);
|
||||
do { yield return null; } while (WallHasDamagedSections(mechanic_brokenWall_1)); // Highlight until repaired
|
||||
mechanic.RemoveActiveObjectiveEntity(mechanic_brokenWall_1);
|
||||
RemoveCompletedObjective(segments[2]);
|
||||
yield return new WaitForSeconds(1f, false);
|
||||
TriggerTutorialSegment(3, GameMain.Config.KeyBindText(InputType.Select)); // Pump objective
|
||||
SetHighlight(mechanic_workingPump.Item, true);
|
||||
do
|
||||
{
|
||||
yield return null;
|
||||
if (IsSelectedItem(mechanic_workingPump.Item))
|
||||
{
|
||||
if (mechanic_workingPump.PowerButton.FlashTimer <= 0)
|
||||
{
|
||||
mechanic_workingPump.PowerButton.Flash(uiHighlightColor, 1.5f, true);
|
||||
}
|
||||
}
|
||||
} while (mechanic_workingPump.FlowPercentage >= 0 || !mechanic_workingPump.IsActive); // Highlight until draining
|
||||
SetHighlight(mechanic_workingPump.Item, false);
|
||||
do { yield return null; } while (mechanic_brokenhull_1.WaterPercentage > waterVolumeBeforeOpening); // Unlock door once drained
|
||||
RemoveCompletedObjective(segments[3]);
|
||||
SetDoorAccess(mechanic_thirdDoor, mechanic_thirdDoorLight, true);
|
||||
//TriggerTutorialSegment(11, GameMain.Config.KeyBind(InputType.Select), GameMain.Config.KeyBind(InputType.Up), GameMain.Config.KeyBind(InputType.Down), GameMain.Config.KeyBind(InputType.Select)); // Ladder objective
|
||||
//do { yield return null; } while (!mechanic_ladderSensor.MotionDetected);
|
||||
//RemoveCompletedObjective(segments[11]);
|
||||
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.News"), ChatMessageType.Radio, null);
|
||||
yield return new WaitForSeconds(1f, false);
|
||||
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.Fire"), ChatMessageType.Radio, null);
|
||||
|
||||
// Room 4
|
||||
do { yield return null; } while (!mechanic_thirdDoor.IsOpen);
|
||||
yield return new WaitForSeconds(1f, false);
|
||||
mechanic_fire = new DummyFireSource(new Vector2(20f, 2f), Item.ItemList.Find(i => i.HasTag("mechanic_fire")).WorldPosition);
|
||||
//do { yield return null; } while (!mechanic_craftingObjectiveSensor.MotionDetected);
|
||||
TriggerTutorialSegment(4); // Deconstruct
|
||||
|
||||
SetHighlight(mechanic_craftingCabinet.Item, true);
|
||||
|
||||
bool gotOxygenTank = false;
|
||||
bool gotSodium = false;
|
||||
do
|
||||
{
|
||||
if (mechanic.SelectedConstruction == mechanic_craftingCabinet.Item)
|
||||
{
|
||||
for (int i = 0; i < mechanic.Inventory.slots.Length; i++)
|
||||
{
|
||||
if (mechanic.Inventory.Items[i] == null) HighlightInventorySlot(mechanic.Inventory, i, highlightColor, .5f, .5f, 0f);
|
||||
}
|
||||
|
||||
if (mechanic.Inventory.FindItemByIdentifier("oxygentank") == null && mechanic.Inventory.FindItemByIdentifier("aluminium") == null)
|
||||
{
|
||||
for (int i = 0; i < mechanic_craftingCabinet.Inventory.Items.Length; i++)
|
||||
{
|
||||
Item item = mechanic_craftingCabinet.Inventory.Items[i];
|
||||
if (item != null && item.prefab.Identifier == "oxygentank")
|
||||
{
|
||||
HighlightInventorySlot(mechanic_craftingCabinet.Inventory, i, highlightColor, .5f, .5f, 0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mechanic.Inventory.FindItemByIdentifier("sodium") == null)
|
||||
{
|
||||
for (int i = 0; i < mechanic_craftingCabinet.Inventory.Items.Length; i++)
|
||||
{
|
||||
Item item = mechanic_craftingCabinet.Inventory.Items[i];
|
||||
if (item != null && item.prefab.Identifier == "sodium")
|
||||
{
|
||||
HighlightInventorySlot(mechanic_craftingCabinet.Inventory, i, highlightColor, .5f, .5f, 0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!gotOxygenTank && mechanic.Inventory.FindItemByIdentifier("oxygentank") != null)
|
||||
{
|
||||
gotOxygenTank = true;
|
||||
}
|
||||
if (!gotSodium && mechanic.Inventory.FindItemByIdentifier("sodium") != null)
|
||||
{
|
||||
gotSodium = true;
|
||||
}
|
||||
yield return null;
|
||||
} while (!gotOxygenTank || !gotSodium); // Wait until looted
|
||||
|
||||
yield return new WaitForSeconds(1.0f, false);
|
||||
SetHighlight(mechanic_craftingCabinet.Item, false);
|
||||
SetHighlight(mechanic_deconstructor.Item, true);
|
||||
do
|
||||
{
|
||||
if (IsSelectedItem(mechanic_deconstructor.Item))
|
||||
{
|
||||
if (mechanic_deconstructor.OutputContainer.Inventory.FindItemByIdentifier("aluminium") != null)
|
||||
{
|
||||
HighlightInventorySlot(mechanic_deconstructor.OutputContainer.Inventory, "aluminium", highlightColor, .5f, .5f, 0f);
|
||||
|
||||
for (int i = 0; i < mechanic.Inventory.slots.Length; i++)
|
||||
{
|
||||
if (mechanic.Inventory.Items[i] == null) HighlightInventorySlot(mechanic.Inventory, i, highlightColor, .5f, .5f, 0f);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (mechanic.Inventory.FindItemByIdentifier("oxygentank") != null && mechanic_deconstructor.InputContainer.Inventory.FindItemByIdentifier("oxygentank") == null)
|
||||
{
|
||||
HighlightInventorySlot(mechanic.Inventory, "oxygentank", highlightColor, .5f, .5f, 0f);
|
||||
|
||||
if (mechanic_deconstructor.InputContainer.Inventory.slots != null)
|
||||
{
|
||||
for (int i = 0; i < mechanic_deconstructor.InputContainer.Inventory.slots.Length; i++)
|
||||
{
|
||||
HighlightInventorySlot(mechanic_deconstructor.InputContainer.Inventory, i, highlightColor, .5f, .5f, 0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mechanic_deconstructor.InputContainer.Inventory.FindItemByIdentifier("oxygentank") != null && !mechanic_deconstructor.IsActive)
|
||||
{
|
||||
if (mechanic_deconstructor.ActivateButton.FlashTimer <= 0)
|
||||
{
|
||||
mechanic_deconstructor.ActivateButton.Flash(highlightColor, 1.5f, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
yield return null;
|
||||
} while (
|
||||
mechanic.Inventory.FindItemByIdentifier("aluminium") == null &&
|
||||
mechanic_fabricator.InputContainer.Inventory.FindItemByIdentifier("aluminium") == null); // Wait until aluminium obtained
|
||||
|
||||
SetHighlight(mechanic_deconstructor.Item, false);
|
||||
RemoveCompletedObjective(segments[4]);
|
||||
yield return new WaitForSeconds(1f, false);
|
||||
TriggerTutorialSegment(5); // Fabricate
|
||||
SetHighlight(mechanic_fabricator.Item, true);
|
||||
do
|
||||
{
|
||||
if (IsSelectedItem(mechanic_fabricator.Item))
|
||||
{
|
||||
if (mechanic_fabricator.SelectedItem?.TargetItem.Identifier != "extinguisher")
|
||||
{
|
||||
mechanic_fabricator.HighlightRecipe("extinguisher", highlightColor);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (mechanic_fabricator.OutputContainer.Inventory.FindItemByIdentifier("extinguisher") != null)
|
||||
{
|
||||
HighlightInventorySlot(mechanic_fabricator.OutputContainer.Inventory, "extinguisher", highlightColor, .5f, .5f, 0f);
|
||||
|
||||
/*for (int i = 0; i < mechanic.Inventory.slots.Length; i++)
|
||||
{
|
||||
if (mechanic.Inventory.Items[i] == null) HighlightInventorySlot(mechanic.Inventory, i, highlightColor, .5f, .5f, 0f);
|
||||
}*/
|
||||
}
|
||||
else if (mechanic_fabricator.InputContainer.Inventory.FindItemByIdentifier("aluminium") != null && mechanic_fabricator.InputContainer.Inventory.FindItemByIdentifier("sodium") != null && !mechanic_fabricator.IsActive)
|
||||
{
|
||||
if (mechanic_fabricator.ActivateButton.FlashTimer <= 0)
|
||||
{
|
||||
mechanic_fabricator.ActivateButton.Flash(highlightColor, 1.5f, false);
|
||||
}
|
||||
}
|
||||
else if (mechanic.Inventory.FindItemByIdentifier("aluminium") != null || mechanic.Inventory.FindItemByIdentifier("sodium") != null)
|
||||
{
|
||||
HighlightInventorySlot(mechanic.Inventory, "aluminium", highlightColor, .5f, .5f, 0f);
|
||||
HighlightInventorySlot(mechanic.Inventory, "sodium", highlightColor, .5f, .5f, 0f);
|
||||
|
||||
if (mechanic_fabricator.InputContainer.Inventory.Items[0] == null)
|
||||
{
|
||||
HighlightInventorySlot(mechanic_fabricator.InputContainer.Inventory, 0, highlightColor, .5f, .5f, 0f);
|
||||
}
|
||||
|
||||
if (mechanic_fabricator.InputContainer.Inventory.Items[1] == null)
|
||||
{
|
||||
HighlightInventorySlot(mechanic_fabricator.InputContainer.Inventory, 1, highlightColor, .5f, .5f, 0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
yield return null;
|
||||
} while (mechanic.Inventory.FindItemByIdentifier("extinguisher") == null); // Wait until extinguisher is created
|
||||
RemoveCompletedObjective(segments[5]);
|
||||
SetHighlight(mechanic_fabricator.Item, false);
|
||||
SetDoorAccess(mechanic_fourthDoor, mechanic_fourthDoorLight, true);
|
||||
|
||||
// Room 5
|
||||
do { yield return null; } while (!mechanic_fireSensor.MotionDetected);
|
||||
TriggerTutorialSegment(6, GameMain.Config.KeyBindText(InputType.Aim), GameMain.Config.KeyBindText(InputType.Shoot)); // Using the extinguisher
|
||||
do { yield return null; } while (!mechanic_fire.Removed); // Wait until extinguished
|
||||
yield return new WaitForSeconds(3f, false);
|
||||
RemoveCompletedObjective(segments[6]);
|
||||
|
||||
if (mechanic.HasEquippedItem("extinguisher")) // do not trigger if dropped already
|
||||
{
|
||||
TriggerTutorialSegment(7);
|
||||
do
|
||||
{
|
||||
HighlightInventorySlot(mechanic.Inventory, "extinguisher", highlightColor, 0.5f, 0.5f, 0f);
|
||||
yield return null;
|
||||
} while (mechanic.HasEquippedItem("extinguisher"));
|
||||
RemoveCompletedObjective(segments[7]);
|
||||
}
|
||||
SetDoorAccess(mechanic_fifthDoor, mechanic_fifthDoorLight, true);
|
||||
|
||||
// Room 6
|
||||
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.Diving"), ChatMessageType.Radio, null);
|
||||
do { yield return null; } while (!mechanic_divingSuitObjectiveSensor.MotionDetected);
|
||||
TriggerTutorialSegment(8); // Dangers of pressure, equip diving suit objective
|
||||
SetHighlight(mechanic_divingSuitContainer.Item, true);
|
||||
do
|
||||
{
|
||||
if (IsSelectedItem(mechanic_divingSuitContainer.Item))
|
||||
{
|
||||
if (mechanic_divingSuitContainer.Inventory.slots != null)
|
||||
{
|
||||
for (int i = 0; i < mechanic_divingSuitContainer.Inventory.slots.Length; i++)
|
||||
{
|
||||
HighlightInventorySlot(mechanic_divingSuitContainer.Inventory, i, highlightColor, 0.5f, 0.5f, 0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
yield return null;
|
||||
} while (!mechanic.HasEquippedItem("divingsuit"));
|
||||
SetHighlight(mechanic_divingSuitContainer.Item, false);
|
||||
RemoveCompletedObjective(segments[8]);
|
||||
SetDoorAccess(tutorial_mechanicFinalDoor, tutorial_mechanicFinalDoorLight, true);
|
||||
|
||||
// Room 7
|
||||
mechanic.AddActiveObjectiveEntity(mechanic_brokenWall_2, mechanic_weldIcon, mechanic_repairIconColor);
|
||||
do { yield return null; } while (WallHasDamagedSections(mechanic_brokenWall_2));
|
||||
mechanic.RemoveActiveObjectiveEntity(mechanic_brokenWall_2);
|
||||
yield return new WaitForSeconds(2f, false);
|
||||
|
||||
TriggerTutorialSegment(9, GameMain.Config.KeyBindText(InputType.Use)); // Repairing machinery (pump)
|
||||
SetHighlight(mechanic_brokenPump.Item, true);
|
||||
mechanic_brokenPump.CanBeSelected = true;
|
||||
Repairable repairablePumpComponent = mechanic_brokenPump.Item.GetComponent<Repairable>();
|
||||
repairablePumpComponent.CanBeSelected = true;
|
||||
do
|
||||
{
|
||||
yield return null;
|
||||
if (!mechanic_brokenPump.Item.IsFullCondition)
|
||||
{
|
||||
if (!mechanic.HasEquippedItem("wrench"))
|
||||
{
|
||||
HighlightInventorySlot(mechanic.Inventory, "wrench", highlightColor, 0.5f, 0.5f, 0f);
|
||||
}
|
||||
else if (IsSelectedItem(mechanic_brokenPump.Item) && repairablePumpComponent.CurrentFixer == null)
|
||||
{
|
||||
if (repairablePumpComponent.RepairButton.FlashTimer <= 0)
|
||||
{
|
||||
repairablePumpComponent.RepairButton.Flash();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (IsSelectedItem(mechanic_brokenPump.Item))
|
||||
{
|
||||
if (mechanic_brokenPump.PowerButton.FlashTimer <= 0)
|
||||
{
|
||||
mechanic_brokenPump.PowerButton.Flash(uiHighlightColor, 1.5f, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (!mechanic_brokenPump.Item.IsFullCondition || mechanic_brokenPump.FlowPercentage >= 0 || !mechanic_brokenPump.IsActive);
|
||||
RemoveCompletedObjective(segments[9]);
|
||||
SetHighlight(mechanic_brokenPump.Item, false);
|
||||
do { yield return null; } while (mechanic_brokenhull_2.WaterPercentage > waterVolumeBeforeOpening);
|
||||
SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, true);
|
||||
|
||||
// Submarine
|
||||
do { yield return null; } while (!tutorial_enteredSubmarineSensor.MotionDetected);
|
||||
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.Submarine"), ChatMessageType.Radio, null);
|
||||
TriggerTutorialSegment(10); // Repairing ballast pumps, engine
|
||||
while (ContentRunning) yield return null;
|
||||
mechanic.AddActiveObjectiveEntity(mechanic_ballastPump_1.Item, mechanic_repairIcon, mechanic_repairIconColor);
|
||||
mechanic.AddActiveObjectiveEntity(mechanic_ballastPump_2.Item, mechanic_repairIcon, mechanic_repairIconColor);
|
||||
mechanic.AddActiveObjectiveEntity(mechanic_submarineEngine.Item, mechanic_repairIcon, mechanic_repairIconColor);
|
||||
SetHighlight(mechanic_ballastPump_1.Item, true);
|
||||
SetHighlight(mechanic_ballastPump_2.Item, true);
|
||||
SetHighlight(mechanic_submarineEngine.Item, true);
|
||||
// Remove highlights when each individual machine is repaired
|
||||
do { CheckHighlights(); yield return null; } while (!mechanic_ballastPump_1.Item.IsFullCondition || !mechanic_ballastPump_2.Item.IsFullCondition || !mechanic_submarineEngine.Item.IsFullCondition);
|
||||
CheckHighlights();
|
||||
RemoveCompletedObjective(segments[10]);
|
||||
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.Complete"), ChatMessageType.Radio, null);
|
||||
|
||||
// END TUTORIAL
|
||||
CoroutineManager.StartCoroutine(TutorialCompleted());
|
||||
}
|
||||
|
||||
private bool IsSelectedItem(Item item)
|
||||
{
|
||||
return mechanic?.SelectedConstruction == item;
|
||||
}
|
||||
|
||||
private bool WallHasDamagedSections(Structure wall)
|
||||
{
|
||||
for (int i = 0; i < wall.SectionCount; i++)
|
||||
{
|
||||
if (wall.Sections[i].damage > 0) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void CheckHighlights()
|
||||
{
|
||||
if (mechanic_ballastPump_1.Item.IsFullCondition && mechanic_ballastPump_1.Item.ExternalHighlight)
|
||||
{
|
||||
SetHighlight(mechanic_ballastPump_1.Item, false);
|
||||
mechanic.RemoveActiveObjectiveEntity(mechanic_ballastPump_1.Item);
|
||||
}
|
||||
if (mechanic_ballastPump_2.Item.IsFullCondition && mechanic_ballastPump_2.Item.ExternalHighlight)
|
||||
{
|
||||
SetHighlight(mechanic_ballastPump_2.Item, false);
|
||||
mechanic.RemoveActiveObjectiveEntity(mechanic_ballastPump_2.Item);
|
||||
}
|
||||
if (mechanic_submarineEngine.Item.IsFullCondition && mechanic_submarineEngine.Item.ExternalHighlight)
|
||||
{
|
||||
SetHighlight(mechanic_submarineEngine.Item, false);
|
||||
mechanic.RemoveActiveObjectiveEntity(mechanic_submarineEngine.Item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+476
@@ -0,0 +1,476 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Xml.Linq;
|
||||
using System.Linq;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma.Tutorials
|
||||
{
|
||||
class OfficerTutorial : ScenarioTutorial
|
||||
{
|
||||
// Other tutorial items
|
||||
private LightComponent tutorial_mechanicFinalDoorLight;
|
||||
private Steering tutorial_submarineSteering;
|
||||
|
||||
// Room 1
|
||||
private float shakeTimer = 1f;
|
||||
private float shakeAmount = 20f;
|
||||
|
||||
// Room 2
|
||||
private MotionSensor officer_equipmentObjectiveSensor;
|
||||
private ItemContainer officer_equipmentCabinet;
|
||||
private Door officer_firstDoor;
|
||||
private LightComponent officer_firstDoorLight;
|
||||
|
||||
// Room 3
|
||||
private MotionSensor officer_crawlerSensor;
|
||||
private Character officer_crawler;
|
||||
private Vector2 officer_crawlerSpawnPos;
|
||||
private Door officer_secondDoor;
|
||||
private LightComponent officer_secondDoorLight;
|
||||
|
||||
// Room 4
|
||||
private MotionSensor officer_somethingBigSensor;
|
||||
private ItemContainer officer_coilgunLoader;
|
||||
private ItemContainer officer_ammoShelf_1;
|
||||
private ItemContainer officer_ammoShelf_2;
|
||||
private PowerContainer officer_superCapacitor;
|
||||
private Item officer_coilgunPeriscope;
|
||||
private Character officer_hammerhead;
|
||||
private Vector2 officer_hammerheadSpawnPos;
|
||||
private Door officer_thirdDoor;
|
||||
private LightComponent officer_thirdDoorLight;
|
||||
|
||||
// Room 5
|
||||
private MotionSensor officer_rangedWeaponSensor;
|
||||
private ItemContainer officer_rangedWeaponCabinet;
|
||||
private ItemContainer officer_rangedWeaponHolder;
|
||||
private Door officer_fourthDoor;
|
||||
private LightComponent officer_fourthDoorLight;
|
||||
|
||||
// Room 6
|
||||
private MotionSensor officer_mudraptorObjectiveSensor;
|
||||
private Vector2 officer_mudraptorSpawnPos;
|
||||
private Character officer_mudraptor;
|
||||
private Door tutorial_securityFinalDoor;
|
||||
private LightComponent tutorial_securityFinalDoorLight;
|
||||
|
||||
// Submarine
|
||||
private Door tutorial_submarineDoor;
|
||||
private LightComponent tutorial_submarineDoorLight;
|
||||
private MotionSensor tutorial_enteredSubmarineSensor;
|
||||
private Item officer_subAmmoBox_1;
|
||||
private Item officer_subAmmoBox_2;
|
||||
private ItemContainer officer_subAmmoShelf;
|
||||
private ItemContainer officer_subLoader_1;
|
||||
private ItemContainer officer_subLoader_2;
|
||||
private PowerContainer officer_subSuperCapacitor_1;
|
||||
private PowerContainer officer_subSuperCapacitor_2;
|
||||
|
||||
// Variables
|
||||
private string radioSpeakerName;
|
||||
private Character officer;
|
||||
private float superCapacitorRechargeRate = 10;
|
||||
private Sprite officer_gunIcon;
|
||||
private Color officer_gunIconColor;
|
||||
|
||||
public OfficerTutorial(XElement element) : base(element)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Start()
|
||||
{
|
||||
base.Start();
|
||||
|
||||
radioSpeakerName = TextManager.Get("Tutorial.Radio.Speaker");
|
||||
officer = Character.Controlled;
|
||||
|
||||
var handcuffs = FindOrGiveItem(officer, "handcuffs");
|
||||
handcuffs.Unequip(officer);
|
||||
officer.Inventory.RemoveItem(handcuffs);
|
||||
|
||||
var stunbaton = FindOrGiveItem(officer, "stunbaton");
|
||||
stunbaton.Unequip(officer);
|
||||
officer.Inventory.RemoveItem(stunbaton);
|
||||
|
||||
var smg = FindOrGiveItem(officer, "smg");
|
||||
smg.Unequip(officer);
|
||||
officer.Inventory.RemoveItem(smg);
|
||||
|
||||
var divingknife = FindOrGiveItem(officer, "divingknife");
|
||||
divingknife.Unequip(officer);
|
||||
officer.Inventory.RemoveItem(divingknife);
|
||||
|
||||
var steroids = FindOrGiveItem(officer, "steroids");
|
||||
steroids.Unequip(officer);
|
||||
officer.Inventory.RemoveItem(steroids);
|
||||
|
||||
var ballistichelmet =
|
||||
officer.Inventory.FindItemByIdentifier("ballistichelmet1") ??
|
||||
officer.Inventory.FindItemByIdentifier("ballistichelmet2") ??
|
||||
FindOrGiveItem(officer, "ballistichelmet3");
|
||||
ballistichelmet.Unequip(officer);
|
||||
officer.Inventory.RemoveItem(ballistichelmet);
|
||||
|
||||
var bodyarmor = FindOrGiveItem(officer, "bodyarmor");
|
||||
bodyarmor.Unequip(officer);
|
||||
officer.Inventory.RemoveItem(bodyarmor);
|
||||
|
||||
var gunOrder = Order.GetPrefab("operateweapons");
|
||||
officer_gunIcon = gunOrder.SymbolSprite;
|
||||
officer_gunIconColor = gunOrder.Color;
|
||||
|
||||
var bandage = FindOrGiveItem(officer, "antibleeding1");
|
||||
bandage.Unequip(officer);
|
||||
officer.Inventory.RemoveItem(bandage);
|
||||
FindOrGiveItem(officer, "antibleeding1");
|
||||
|
||||
// Other tutorial items
|
||||
tutorial_mechanicFinalDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_mechanicfinaldoorlight")).GetComponent<LightComponent>();
|
||||
tutorial_submarineSteering = Item.ItemList.Find(i => i.HasTag("command")).GetComponent<Steering>();
|
||||
|
||||
tutorial_submarineSteering.CanBeSelected = false;
|
||||
foreach (ItemComponent ic in tutorial_submarineSteering.Item.Components)
|
||||
{
|
||||
ic.CanBeSelected = false;
|
||||
}
|
||||
|
||||
SetDoorAccess(null, tutorial_mechanicFinalDoorLight, false);
|
||||
|
||||
// Room 2
|
||||
officer_equipmentObjectiveSensor = Item.ItemList.Find(i => i.HasTag("officer_equipmentobjectivesensor")).GetComponent<MotionSensor>();
|
||||
officer_equipmentCabinet = Item.ItemList.Find(i => i.HasTag("officer_equipmentcabinet")).GetComponent<ItemContainer>();
|
||||
officer_firstDoor = Item.ItemList.Find(i => i.HasTag("officer_firstdoor")).GetComponent<Door>();
|
||||
officer_firstDoorLight = Item.ItemList.Find(i => i.HasTag("officer_firstdoorlight")).GetComponent<LightComponent>();
|
||||
|
||||
SetDoorAccess(officer_firstDoor, officer_firstDoorLight, false);
|
||||
|
||||
// Room 3
|
||||
officer_crawlerSensor = Item.ItemList.Find(i => i.HasTag("officer_crawlerobjectivesensor")).GetComponent<MotionSensor>();
|
||||
officer_crawlerSpawnPos = Item.ItemList.Find(i => i.HasTag("officer_crawlerspawn")).WorldPosition;
|
||||
officer_secondDoor = Item.ItemList.Find(i => i.HasTag("officer_seconddoor")).GetComponent<Door>();
|
||||
officer_secondDoorLight = Item.ItemList.Find(i => i.HasTag("officer_seconddoorlight")).GetComponent<LightComponent>();
|
||||
|
||||
SetDoorAccess(officer_secondDoor, officer_secondDoorLight, false);
|
||||
|
||||
// Room 4
|
||||
officer_somethingBigSensor = Item.ItemList.Find(i => i.HasTag("officer_somethingbigobjectivesensor")).GetComponent<MotionSensor>();
|
||||
officer_coilgunLoader = Item.ItemList.Find(i => i.HasTag("officer_coilgunloader")).GetComponent<ItemContainer>();
|
||||
officer_superCapacitor = Item.ItemList.Find(i => i.HasTag("officer_supercapacitor")).GetComponent<PowerContainer>();
|
||||
officer_coilgunPeriscope = Item.ItemList.Find(i => i.HasTag("officer_coilgunperiscope"));
|
||||
officer_hammerheadSpawnPos = Item.ItemList.Find(i => i.HasTag("officer_hammerheadspawn")).WorldPosition;
|
||||
officer_thirdDoor = Item.ItemList.Find(i => i.HasTag("officer_thirddoor")).GetComponent<Door>();
|
||||
officer_thirdDoorLight = Item.ItemList.Find(i => i.HasTag("officer_thirddoorlight")).GetComponent<LightComponent>();
|
||||
officer_ammoShelf_1 = Item.ItemList.Find(i => i.HasTag("officer_ammoshelf_1")).GetComponent<ItemContainer>();
|
||||
officer_ammoShelf_2 = Item.ItemList.Find(i => i.HasTag("officer_ammoshelf_2")).GetComponent<ItemContainer>();
|
||||
|
||||
SetDoorAccess(officer_thirdDoor, officer_thirdDoorLight, false);
|
||||
|
||||
// Room 5
|
||||
officer_rangedWeaponSensor = Item.ItemList.Find(i => i.HasTag("officer_rangedweaponobjectivesensor")).GetComponent<MotionSensor>();
|
||||
officer_rangedWeaponCabinet = Item.ItemList.Find(i => i.HasTag("officer_rangedweaponcabinet")).GetComponent<ItemContainer>();
|
||||
officer_rangedWeaponHolder = Item.ItemList.Find(i => i.HasTag("officer_rangedweaponholder")).GetComponent<ItemContainer>();
|
||||
officer_fourthDoor = Item.ItemList.Find(i => i.HasTag("officer_fourthdoor")).GetComponent<Door>();
|
||||
officer_fourthDoorLight = Item.ItemList.Find(i => i.HasTag("officer_fourthdoorlight")).GetComponent<LightComponent>();
|
||||
|
||||
SetDoorAccess(officer_fourthDoor, officer_fourthDoorLight, false);
|
||||
|
||||
// Room 6
|
||||
officer_mudraptorObjectiveSensor = Item.ItemList.Find(i => i.HasTag("officer_mudraptorobjectivesensor")).GetComponent<MotionSensor>();
|
||||
officer_mudraptorSpawnPos = Item.ItemList.Find(i => i.HasTag("officer_mudraptorspawn")).WorldPosition;
|
||||
tutorial_securityFinalDoor = Item.ItemList.Find(i => i.HasTag("tutorial_securityfinaldoor")).GetComponent<Door>();
|
||||
tutorial_securityFinalDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_securityfinaldoorlight")).GetComponent<LightComponent>();
|
||||
|
||||
SetDoorAccess(tutorial_securityFinalDoor, tutorial_securityFinalDoorLight, false);
|
||||
|
||||
// Submarine
|
||||
tutorial_submarineDoor = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoor")).GetComponent<Door>();
|
||||
tutorial_submarineDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoorlight")).GetComponent<LightComponent>();
|
||||
tutorial_enteredSubmarineSensor = Item.ItemList.Find(i => i.HasTag("tutorial_enteredsubmarinesensor")).GetComponent<MotionSensor>();
|
||||
officer_subAmmoBox_1 = Item.ItemList.Find(i => i.HasTag("officer_subammobox_1"));
|
||||
officer_subAmmoBox_2 = Item.ItemList.Find(i => i.HasTag("officer_subammobox_2"));
|
||||
officer_subLoader_1 = Item.ItemList.Find(i => i.HasTag("officer_subloader_1")).GetComponent<ItemContainer>();
|
||||
officer_subLoader_2 = Item.ItemList.Find(i => i.HasTag("officer_subloader_2")).GetComponent<ItemContainer>();
|
||||
officer_subSuperCapacitor_1 = Item.ItemList.Find(i => i.HasTag("officer_subsupercapacitor_1")).GetComponent<PowerContainer>();
|
||||
officer_subSuperCapacitor_2 = Item.ItemList.Find(i => i.HasTag("officer_subsupercapacitor_2")).GetComponent<PowerContainer>();
|
||||
officer_subAmmoShelf = Item.ItemList.Find(i => i.HasTag("officer_subammoshelf")).GetComponent<ItemContainer>();
|
||||
SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, true);
|
||||
}
|
||||
|
||||
public override IEnumerable<object> UpdateState()
|
||||
{
|
||||
while (GameMain.Instance.LoadingScreenOpen) yield return null;
|
||||
|
||||
yield return new WaitForSeconds(0.01f);
|
||||
|
||||
// Room 1
|
||||
SoundPlayer.PlayDamageSound("StructureBlunt", 10, Character.Controlled.WorldPosition);
|
||||
while (shakeTimer > 0.0f) // Wake up, shake
|
||||
{
|
||||
shakeTimer -= 0.1f;
|
||||
GameMain.GameScreen.Cam.Shake = shakeAmount;
|
||||
yield return new WaitForSeconds(0.1f, false);
|
||||
}
|
||||
|
||||
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Officer.Radio.WakeUp"), ChatMessageType.Radio, null);
|
||||
|
||||
// Room 2
|
||||
do { yield return null; } while (!officer_equipmentObjectiveSensor.MotionDetected);
|
||||
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Officer.Radio.Equipment"), ChatMessageType.Radio, null);
|
||||
yield return new WaitForSeconds(3f, false);
|
||||
//TriggerTutorialSegment(0, GameMain.Config.KeyBind(InputType.Select), GameMain.Config.KeyBind(InputType.Deselect)); // Retrieve equipment
|
||||
SetHighlight(officer_equipmentCabinet.Item, true);
|
||||
bool firstSlotRemoved = false;
|
||||
bool secondSlotRemoved = false;
|
||||
bool thirdSlotRemoved = false;
|
||||
do
|
||||
{
|
||||
if (IsSelectedItem(officer_equipmentCabinet.Item))
|
||||
{
|
||||
if (!firstSlotRemoved)
|
||||
{
|
||||
HighlightInventorySlot(officer_equipmentCabinet.Inventory, 0, highlightColor, .5f, .5f, 0f);
|
||||
if (officer_equipmentCabinet.Inventory.Items[0] == null) firstSlotRemoved = true;
|
||||
}
|
||||
|
||||
if (!secondSlotRemoved)
|
||||
{
|
||||
HighlightInventorySlot(officer_equipmentCabinet.Inventory, 1, highlightColor, .5f, .5f, 0f);
|
||||
if (officer_equipmentCabinet.Inventory.Items[1] == null) secondSlotRemoved = true;
|
||||
}
|
||||
|
||||
if (!thirdSlotRemoved)
|
||||
{
|
||||
HighlightInventorySlot(officer_equipmentCabinet.Inventory, 2, highlightColor, .5f, .5f, 0f);
|
||||
if (officer_equipmentCabinet.Inventory.Items[2] == null) thirdSlotRemoved = true;
|
||||
}
|
||||
|
||||
for (int i = 0; i < officer.Inventory.slots.Length; i++)
|
||||
{
|
||||
if (officer.Inventory.Items[i] == null) HighlightInventorySlot(officer.Inventory, i, highlightColor, .5f, .5f, 0f);
|
||||
}
|
||||
}
|
||||
|
||||
yield return null;
|
||||
} while (!officer_equipmentCabinet.Inventory.IsEmpty()); // Wait until looted
|
||||
//RemoveCompletedObjective(segments[0]);
|
||||
SetHighlight(officer_equipmentCabinet.Item, false);
|
||||
do { yield return null; } while (IsSelectedItem(officer_equipmentCabinet.Item));
|
||||
TriggerTutorialSegment(1, GameMain.Config.KeyBindText(InputType.Aim), GameMain.Config.KeyBindText(InputType.Shoot)); // Equip melee weapon & armor
|
||||
do
|
||||
{
|
||||
if (!officer.HasEquippedItem("stunbaton"))
|
||||
{
|
||||
HighlightInventorySlot(officer.Inventory, "stunbaton", highlightColor, .5f, .5f, 0f);
|
||||
}
|
||||
if (!officer.HasEquippedItem("bodyarmor"))
|
||||
{
|
||||
HighlightInventorySlot(officer.Inventory, "bodyarmor", highlightColor, .5f, .5f, 0f);
|
||||
}
|
||||
if (!officer.HasEquippedItem("ballistichelmet1"))
|
||||
{
|
||||
HighlightInventorySlot(officer.Inventory, "ballistichelmet1", highlightColor, .5f, .5f, 0f);
|
||||
}
|
||||
yield return new WaitForSeconds(1f, false);
|
||||
} while (!officer.HasEquippedItem("stunbaton") || !officer.HasEquippedItem("bodyarmor") || !officer.HasEquippedItem("ballistichelmet1"));
|
||||
RemoveCompletedObjective(segments[1]);
|
||||
SetDoorAccess(officer_firstDoor, officer_firstDoorLight, true);
|
||||
|
||||
// Room 3
|
||||
do { yield return null; } while (!officer_crawlerSensor.MotionDetected);
|
||||
TriggerTutorialSegment(2);
|
||||
officer_crawler = SpawnMonster("crawler", officer_crawlerSpawnPos);
|
||||
do { yield return null; } while (!officer_crawler.IsDead);
|
||||
RemoveCompletedObjective(segments[2]);
|
||||
Heal(officer);
|
||||
yield return new WaitForSeconds(1f, false);
|
||||
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Officer.Radio.CrawlerDead"), ChatMessageType.Radio, null);
|
||||
SetDoorAccess(officer_secondDoor, officer_secondDoorLight, true);
|
||||
|
||||
// Room 4
|
||||
do { yield return null; } while (!officer_somethingBigSensor.MotionDetected);
|
||||
TriggerTutorialSegment(3); // Arm railgun
|
||||
do
|
||||
{
|
||||
SetHighlight(officer_coilgunLoader.Item, officer_coilgunLoader.Inventory.Items[0] == null || officer_coilgunLoader.Inventory.Items[0].Condition == 0);
|
||||
HighlightInventorySlot(officer_coilgunLoader.Inventory, 0, highlightColor, .5f, .5f, 0f);
|
||||
SetHighlight(officer_superCapacitor.Item, officer_superCapacitor.RechargeSpeed < superCapacitorRechargeRate);
|
||||
SetHighlight(officer_ammoShelf_1.Item, officer_coilgunLoader.Item.ExternalHighlight );
|
||||
SetHighlight(officer_ammoShelf_2.Item, officer_coilgunLoader.Item.ExternalHighlight );
|
||||
if (IsSelectedItem(officer_coilgunLoader.Item))
|
||||
{
|
||||
HighlightInventorySlot(officer.Inventory, "coilgunammobox", highlightColor, .5f, .5f, 0f);
|
||||
}
|
||||
yield return null;
|
||||
} while (officer_coilgunLoader.Inventory.Items[0] == null || officer_superCapacitor.RechargeSpeed < superCapacitorRechargeRate || officer_coilgunLoader.Inventory.Items[0].Condition == 0);
|
||||
SetHighlight(officer_coilgunLoader.Item, false);
|
||||
SetHighlight(officer_superCapacitor.Item, false);
|
||||
SetHighlight(officer_ammoShelf_1.Item, false);
|
||||
SetHighlight(officer_ammoShelf_2.Item, false);
|
||||
RemoveCompletedObjective(segments[3]);
|
||||
yield return new WaitForSeconds(2f, false);
|
||||
TriggerTutorialSegment(4, GameMain.Config.KeyBindText(InputType.Select), GameMain.Config.KeyBindText(InputType.Shoot), GameMain.Config.KeyBindText(InputType.Deselect)); // Kill hammerhead
|
||||
officer_hammerhead = SpawnMonster("hammerhead", officer_hammerheadSpawnPos);
|
||||
officer_hammerhead.AIController.SelectTarget(officer.AiTarget);
|
||||
SetHighlight(officer_coilgunPeriscope, true);
|
||||
float originalDistance = Vector2.Distance(officer_coilgunPeriscope.WorldPosition, officer_hammerheadSpawnPos);
|
||||
do
|
||||
{
|
||||
float distance = Vector2.Distance(officer_coilgunPeriscope.WorldPosition, officer_hammerhead.WorldPosition);
|
||||
if (distance > originalDistance * 1.5f)
|
||||
{
|
||||
// Don't let the Hammerhead go too far.
|
||||
officer_hammerhead.TeleportTo(officer_hammerheadSpawnPos + new Vector2(0, -1000));
|
||||
}
|
||||
if (distance > originalDistance)
|
||||
{
|
||||
// Ensure that the Hammerhead targets the player
|
||||
officer_hammerhead.AIController.SelectTarget(officer.AiTarget);
|
||||
/*var ai = officer_hammerhead.AIController as EnemyAIController;
|
||||
ai.sight = 2.0f;*/
|
||||
}
|
||||
yield return null;
|
||||
}
|
||||
while(!officer_hammerhead.IsDead);
|
||||
Heal(officer);
|
||||
SetHighlight(officer_coilgunPeriscope, false);
|
||||
RemoveCompletedObjective(segments[4]);
|
||||
yield return new WaitForSeconds(1f, false);
|
||||
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Officer.Radio.HammerheadDead"), ChatMessageType.Radio, null);
|
||||
SetDoorAccess(officer_thirdDoor, officer_thirdDoorLight, true);
|
||||
|
||||
// Room 5
|
||||
//do { yield return null; } while (!officer_rangedWeaponSensor.MotionDetected);
|
||||
do { yield return null; } while (!officer_thirdDoor.IsOpen);
|
||||
yield return new WaitForSeconds(3f, false);
|
||||
TriggerTutorialSegment(5, GameMain.Config.KeyBindText(InputType.Aim), GameMain.Config.KeyBindText(InputType.Shoot)); // Ranged weapons
|
||||
SetHighlight(officer_rangedWeaponHolder.Item, true);
|
||||
do { yield return null; } while (!officer_rangedWeaponHolder.Inventory.IsEmpty()); // Wait until looted
|
||||
SetHighlight(officer_rangedWeaponHolder.Item, false);
|
||||
do
|
||||
{
|
||||
HighlightInventorySlot(officer.Inventory, "harpoongun", highlightColor, 0.5f, 0.5f, 0f);
|
||||
yield return null;
|
||||
} while (!officer.HasEquippedItem("harpoongun")); // Wait until equipped
|
||||
ItemContainer harpoonGunChamber = officer.Inventory.FindItemByIdentifier("harpoongun").GetComponent<ItemContainer>();
|
||||
SetHighlight(officer_rangedWeaponCabinet.Item, true);
|
||||
do
|
||||
{
|
||||
if (IsSelectedItem(officer_rangedWeaponCabinet.Item))
|
||||
{
|
||||
if (officer_rangedWeaponCabinet.Inventory.slots != null)
|
||||
{
|
||||
for (int i = 0; i < officer_rangedWeaponCabinet.Inventory.Items.Length; i++)
|
||||
{
|
||||
if (officer_rangedWeaponCabinet.Inventory.Items[i] == null) continue;
|
||||
if (officer_rangedWeaponCabinet.Inventory.Items[i].Prefab.Identifier == "spear")
|
||||
{
|
||||
HighlightInventorySlot(officer_rangedWeaponCabinet.Inventory, i, highlightColor, 0.5f, 0.5f, 0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < officer.Inventory.Items.Length; i++)
|
||||
{
|
||||
if (officer.Inventory.Items[i] == null) continue;
|
||||
if (officer.Inventory.Items[i].Prefab.Identifier == "spear")
|
||||
{
|
||||
HighlightInventorySlot(officer.Inventory, i, highlightColor, 0.5f, 0.5f, 0f);
|
||||
}
|
||||
}
|
||||
|
||||
if (officer.Inventory.FindItemByIdentifier("spear") != null || (IsSelectedItem(officer_rangedWeaponCabinet.Item) && officer_rangedWeaponCabinet.Inventory.FindItemByIdentifier("spear") != null))
|
||||
{
|
||||
HighlightInventorySlot(officer.Inventory, "harpoongun", highlightColor, 0.5f, 0.5f, 0f);
|
||||
}
|
||||
yield return null;
|
||||
} while (!harpoonGunChamber.Inventory.IsFull()); // Wait until all six harpoons loaded
|
||||
RemoveCompletedObjective(segments[5]);
|
||||
SetHighlight(officer_rangedWeaponCabinet.Item, false);
|
||||
SetDoorAccess(officer_fourthDoor, officer_fourthDoorLight, true);
|
||||
|
||||
// Room 6
|
||||
do { yield return null; } while (!officer_mudraptorObjectiveSensor.MotionDetected);
|
||||
TriggerTutorialSegment(6);
|
||||
officer_mudraptor = SpawnMonster("mudraptor", officer_mudraptorSpawnPos);
|
||||
do { yield return null; } while (!officer_mudraptor.IsDead);
|
||||
Heal(officer);
|
||||
RemoveCompletedObjective(segments[6]);
|
||||
SetDoorAccess(tutorial_securityFinalDoor, tutorial_securityFinalDoorLight, true);
|
||||
|
||||
// Submarine
|
||||
do { yield return null; } while (!tutorial_enteredSubmarineSensor.MotionDetected);
|
||||
TriggerTutorialSegment(7);
|
||||
while (ContentRunning) yield return null;
|
||||
officer.AddActiveObjectiveEntity(officer_subAmmoBox_1, officer_gunIcon, officer_gunIconColor);
|
||||
officer.AddActiveObjectiveEntity(officer_subAmmoBox_2, officer_gunIcon, officer_gunIconColor);
|
||||
officer.AddActiveObjectiveEntity(officer_subSuperCapacitor_1.Item, officer_gunIcon, officer_gunIconColor);
|
||||
officer.AddActiveObjectiveEntity(officer_subSuperCapacitor_2.Item, officer_gunIcon, officer_gunIconColor);
|
||||
SetHighlight(officer_subSuperCapacitor_1.Item, true);
|
||||
SetHighlight(officer_subSuperCapacitor_2.Item, true);
|
||||
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Officer.Radio.Submarine"), ChatMessageType.Radio, null);
|
||||
do
|
||||
{
|
||||
SetHighlight(officer_subLoader_1.Item, officer_subLoader_1.Inventory.Items[0] == null || officer_subLoader_1.Inventory.Items[0].Condition == 0);
|
||||
SetHighlight(officer_subLoader_2.Item, officer_subLoader_2.Inventory.Items[0] == null || officer_subLoader_2.Inventory.Items[0].Condition == 0);
|
||||
HighlightInventorySlot(officer_subLoader_1.Inventory, 0, highlightColor, .5f, .5f, 0f);
|
||||
HighlightInventorySlot(officer_subLoader_2.Inventory, 0, highlightColor, .5f, .5f, 0f);
|
||||
|
||||
if (officer_subSuperCapacitor_1.Item.ExternalHighlight && officer_subSuperCapacitor_1.RechargeSpeed >= superCapacitorRechargeRate)
|
||||
{
|
||||
SetHighlight(officer_subSuperCapacitor_1.Item, false);
|
||||
officer.RemoveActiveObjectiveEntity(officer_subSuperCapacitor_1.Item);
|
||||
}
|
||||
|
||||
if (officer_subSuperCapacitor_2.Item.ExternalHighlight && officer_subSuperCapacitor_2.RechargeSpeed >= superCapacitorRechargeRate)
|
||||
{
|
||||
SetHighlight(officer_subSuperCapacitor_2.Item, false);
|
||||
officer.RemoveActiveObjectiveEntity(officer_subSuperCapacitor_2.Item);
|
||||
}
|
||||
|
||||
SetHighlight(officer_subAmmoBox_1, officer_subAmmoBox_1.ParentInventory != officer_subLoader_1.Inventory && officer_subAmmoBox_1.ParentInventory != officer_subLoader_2.Inventory);
|
||||
SetHighlight(officer_subAmmoBox_2, officer_subAmmoBox_2.ParentInventory != officer_subLoader_1.Inventory && officer_subAmmoBox_2.ParentInventory != officer_subLoader_2.Inventory);
|
||||
SetHighlight(officer_subAmmoShelf.Item, officer_subLoader_1.Item.ExternalHighlight || officer_subLoader_2.Item.ExternalHighlight);
|
||||
if (officer_subAmmoBox_1.ParentInventory == officer_subLoader_1.Inventory || officer_subAmmoBox_1.ParentInventory == officer_subLoader_2.Inventory) officer.RemoveActiveObjectiveEntity(officer_subAmmoBox_1);
|
||||
if (officer_subAmmoBox_2.ParentInventory == officer_subLoader_1.Inventory || officer_subAmmoBox_2.ParentInventory == officer_subLoader_2.Inventory) officer.RemoveActiveObjectiveEntity(officer_subAmmoBox_2);
|
||||
yield return null;
|
||||
} while (officer_subLoader_1.Item.ExternalHighlight || officer_subLoader_2.Item.ExternalHighlight || officer_subSuperCapacitor_1.Item.ExternalHighlight || officer_subSuperCapacitor_2.Item.ExternalHighlight);
|
||||
SetHighlight(officer_subLoader_1.Item, false);
|
||||
SetHighlight(officer_subLoader_2.Item, false);
|
||||
SetHighlight(officer_subSuperCapacitor_1.Item, false);
|
||||
SetHighlight(officer_subSuperCapacitor_2.Item, false);
|
||||
SetHighlight(officer_subAmmoBox_1, false);
|
||||
SetHighlight(officer_subAmmoBox_2, false);
|
||||
SetHighlight(officer_subAmmoShelf.Item, false);
|
||||
officer.RemoveActiveObjectiveEntity(officer_subSuperCapacitor_1.Item);
|
||||
officer.RemoveActiveObjectiveEntity(officer_subSuperCapacitor_2.Item);
|
||||
officer.RemoveActiveObjectiveEntity(officer_subAmmoBox_1);
|
||||
officer.RemoveActiveObjectiveEntity(officer_subAmmoBox_2);
|
||||
RemoveCompletedObjective(segments[7]);
|
||||
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Officer.Radio.Complete"), ChatMessageType.Radio, null);
|
||||
|
||||
yield return new WaitForSeconds(4f, false);
|
||||
CoroutineManager.StartCoroutine(TutorialCompleted());
|
||||
}
|
||||
|
||||
private bool IsSelectedItem(Item item)
|
||||
{
|
||||
return officer?.SelectedConstruction == item;
|
||||
}
|
||||
|
||||
private Character SpawnMonster(string speciesName, Vector2 pos)
|
||||
{
|
||||
var character = Character.Create(speciesName, pos, ToolBox.RandomSeed(8));
|
||||
var ai = character.AIController as EnemyAIController;
|
||||
ai.TargetOutposts = true;
|
||||
character.CharacterHealth.SetVitality(character.Health / 2);
|
||||
character.AnimController.Limbs.Where(l => l.attack != null).Select(l => l.attack).ForEach(a => a.AfterAttack = AIBehaviorAfterAttack.FallBack);
|
||||
return character;
|
||||
}
|
||||
}
|
||||
}
|
||||
+313
@@ -0,0 +1,313 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Tutorials
|
||||
{
|
||||
class ScenarioTutorial : Tutorial
|
||||
{
|
||||
private CoroutineHandle tutorialCoroutine;
|
||||
|
||||
private Character character;
|
||||
private string spawnSub;
|
||||
private SpawnType spawnPointType;
|
||||
private string submarinePath;
|
||||
private string startOutpostPath;
|
||||
private string endOutpostPath;
|
||||
private string levelSeed;
|
||||
private string levelParams;
|
||||
|
||||
private Submarine startOutpost = null;
|
||||
private Submarine endOutpost = null;
|
||||
private bool currentTutorialCompleted = false;
|
||||
private float fadeOutTime = 3f;
|
||||
protected float waitBeforeFade = 4f;
|
||||
|
||||
// Colors
|
||||
protected Color highlightColor = Color.OrangeRed;
|
||||
protected Color uiHighlightColor = new Color(150, 50, 0);
|
||||
protected Color buttonHighlightColor = new Color(255, 100, 0);
|
||||
protected Color inaccessibleColor = GUI.Style.Red;
|
||||
protected Color accessibleColor = GUI.Style.Green;
|
||||
|
||||
public ScenarioTutorial(XElement element) : base(element)
|
||||
{
|
||||
submarinePath = element.GetAttributeString("submarinepath", "");
|
||||
startOutpostPath = element.GetAttributeString("startoutpostpath", "");
|
||||
endOutpostPath = element.GetAttributeString("endoutpostpath", "");
|
||||
|
||||
levelSeed = element.GetAttributeString("levelseed", "tuto");
|
||||
levelParams = element.GetAttributeString("levelparams", "");
|
||||
|
||||
spawnSub = element.GetAttributeString("spawnsub", "");
|
||||
Enum.TryParse(element.GetAttributeString("spawnpointtype", "Human"), true, out spawnPointType);
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
currentTutorialCompleted = false;
|
||||
GameMain.Instance.ShowLoading(Loading());
|
||||
}
|
||||
|
||||
private IEnumerable<object> Loading()
|
||||
{
|
||||
Submarine.MainSub = Submarine.Load(submarinePath, "", true);
|
||||
|
||||
LevelGenerationParams generationParams = LevelGenerationParams.LevelParams.Find(p => p.Name == levelParams);
|
||||
|
||||
yield return CoroutineStatus.Running;
|
||||
|
||||
GameMain.GameSession = new GameSession(Submarine.MainSub, "",
|
||||
GameModePreset.List.Find(g => g.Identifier == "tutorial"));
|
||||
(GameMain.GameSession.GameMode as TutorialMode).Tutorial = this;
|
||||
|
||||
if (generationParams != null)
|
||||
{
|
||||
Biome biome = LevelGenerationParams.GetBiomes().Find(b => generationParams.AllowedBiomes.Contains(b));
|
||||
|
||||
if (startOutpostPath != string.Empty)
|
||||
{
|
||||
startOutpost = Submarine.Load(startOutpostPath, "", false);
|
||||
}
|
||||
|
||||
if (endOutpostPath != string.Empty)
|
||||
{
|
||||
endOutpost = Submarine.Load(endOutpostPath, "", false);
|
||||
}
|
||||
|
||||
Level tutorialLevel = new Level(levelSeed, 0, 0, generationParams, biome, startOutpost, endOutpost);
|
||||
GameMain.GameSession.StartRound(tutorialLevel);
|
||||
}
|
||||
else
|
||||
{
|
||||
GameMain.GameSession.StartRound(levelSeed);
|
||||
}
|
||||
|
||||
GameMain.GameSession.EventManager.ActiveEvents.Clear();
|
||||
GameMain.GameSession.EventManager.Enabled = false;
|
||||
GameMain.GameScreen.Select();
|
||||
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
|
||||
public override void Start()
|
||||
{
|
||||
base.Start();
|
||||
|
||||
Submarine.MainSub.GodMode = true;
|
||||
|
||||
CharacterInfo charInfo = configElement.Element("Character") == null ?
|
||||
new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("engineer")) :
|
||||
new CharacterInfo(configElement.Element("Character"));
|
||||
|
||||
WayPoint wayPoint = GetSpawnPoint(charInfo);
|
||||
|
||||
if (wayPoint == null)
|
||||
{
|
||||
DebugConsole.ThrowError("A waypoint with the spawntype \"" + spawnPointType + "\" is required for the tutorial event");
|
||||
return;
|
||||
}
|
||||
|
||||
character = Character.Create(charInfo, wayPoint.WorldPosition, "", false, false);
|
||||
Character.Controlled = character;
|
||||
character.GiveJobItems(null);
|
||||
|
||||
var idCard = character.Inventory.FindItemByIdentifier("idcard");
|
||||
if (idCard == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Item prefab \"ID Card\" not found!");
|
||||
return;
|
||||
}
|
||||
idCard.AddTag("com");
|
||||
idCard.AddTag("eng");
|
||||
|
||||
List<Entity> entities = Entity.GetEntityList();
|
||||
|
||||
for (int i = 0; i < entities.Count; i++)
|
||||
{
|
||||
if (entities[i] is Item)
|
||||
{
|
||||
Door door = (entities[i] as Item).GetComponent<Door>();
|
||||
if (door != null)
|
||||
{
|
||||
door.CanBeWelded = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tutorialCoroutine = CoroutineManager.StartCoroutine(UpdateState());
|
||||
}
|
||||
|
||||
public override void AddToGUIUpdateList()
|
||||
{
|
||||
if (!currentTutorialCompleted)
|
||||
{
|
||||
base.AddToGUIUpdateList();
|
||||
}
|
||||
}
|
||||
|
||||
private WayPoint GetSpawnPoint(CharacterInfo charInfo)
|
||||
{
|
||||
Submarine spawnSub = null;
|
||||
|
||||
if (this.spawnSub != string.Empty)
|
||||
{
|
||||
switch (this.spawnSub)
|
||||
{
|
||||
case "startoutpost":
|
||||
spawnSub = startOutpost;
|
||||
break;
|
||||
|
||||
case "endoutpost":
|
||||
spawnSub = endOutpost;
|
||||
break;
|
||||
|
||||
default:
|
||||
spawnSub = Submarine.MainSub;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return WayPoint.GetRandom(spawnPointType, charInfo.Job, spawnSub);
|
||||
}
|
||||
|
||||
protected bool HasOrder(Character character, string identifier, string option = null)
|
||||
{
|
||||
if (character.CurrentOrder?.Identifier == identifier)
|
||||
{
|
||||
if (option == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
HumanAIController humanAI = character.AIController as HumanAIController;
|
||||
return humanAI.CurrentOrderOption == option;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void SetHighlight(Item item, bool state)
|
||||
{
|
||||
if (item.ExternalHighlight == state) return;
|
||||
item.SpriteColor = (state) ? highlightColor : Color.White;
|
||||
item.ExternalHighlight = state;
|
||||
}
|
||||
|
||||
protected void SetHighlight(Structure structure, bool state)
|
||||
{
|
||||
structure.SpriteColor = (state) ? highlightColor : Color.White;
|
||||
structure.ExternalHighlight = state;
|
||||
}
|
||||
|
||||
protected void SetHighlight(Character character, bool state)
|
||||
{
|
||||
character.ExternalHighlight = state;
|
||||
}
|
||||
|
||||
protected void SetDoorAccess(Door door, LightComponent light, bool state)
|
||||
{
|
||||
if (state && door != null) door.requiredItems.Clear();
|
||||
if (light != null) light.LightColor = (state) ? accessibleColor : inaccessibleColor;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
base.Update(deltaTime);
|
||||
if (character != null)
|
||||
{
|
||||
if (character.Oxygen < 1)
|
||||
{
|
||||
character.Oxygen = 1;
|
||||
}
|
||||
if (character.IsDead)
|
||||
{
|
||||
CoroutineManager.StartCoroutine(Dead());
|
||||
}
|
||||
else if (Character.Controlled == null)
|
||||
{
|
||||
if (tutorialCoroutine != null)
|
||||
{
|
||||
CoroutineManager.StopCoroutines(tutorialCoroutine);
|
||||
}
|
||||
infoBox = null;
|
||||
}
|
||||
else if (Character.Controlled.IsDead)
|
||||
{
|
||||
CoroutineManager.StartCoroutine(Dead());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Stop()
|
||||
{
|
||||
if (tutorialCoroutine != null)
|
||||
{
|
||||
CoroutineManager.StopCoroutines(tutorialCoroutine);
|
||||
}
|
||||
base.Stop();
|
||||
}
|
||||
|
||||
private IEnumerable<object> Dead()
|
||||
{
|
||||
GUI.PreventPauseMenuToggle = true;
|
||||
Character.Controlled = character = null;
|
||||
Stop();
|
||||
|
||||
yield return new WaitForSeconds(3.0f);
|
||||
|
||||
var messageBox = new GUIMessageBox(TextManager.Get("Tutorial.TryAgainHeader"), TextManager.Get("Tutorial.TryAgain"), new string[] { TextManager.Get("Yes"), TextManager.Get("No") });
|
||||
|
||||
messageBox.Buttons[0].OnClicked += Restart;
|
||||
messageBox.Buttons[0].OnClicked += messageBox.Close;
|
||||
|
||||
|
||||
messageBox.Buttons[1].OnClicked = GameMain.MainMenuScreen.ReturnToMainMenu;
|
||||
messageBox.Buttons[1].OnClicked += messageBox.Close;
|
||||
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
|
||||
protected IEnumerable<object> TutorialCompleted()
|
||||
{
|
||||
GUI.PreventPauseMenuToggle = true;
|
||||
|
||||
Character.Controlled.ClearInputs();
|
||||
Character.Controlled = null;
|
||||
|
||||
yield return new WaitForSeconds(waitBeforeFade);
|
||||
|
||||
var endCinematic = new RoundEndCinematic(Submarine.MainSub, GameMain.GameScreen.Cam, fadeOutTime);
|
||||
currentTutorialCompleted = Completed = true;
|
||||
while (endCinematic.Running) yield return null;
|
||||
Stop();
|
||||
GameMain.MainMenuScreen.ReturnToMainMenu(null, null);
|
||||
}
|
||||
|
||||
protected void Heal(Character character)
|
||||
{
|
||||
character.SetAllDamage(0.0f, 0.0f, 0.0f);
|
||||
character.Oxygen = 100.0f;
|
||||
character.Bloodloss = 0.0f;
|
||||
character.SetStun(0.0f, true);
|
||||
}
|
||||
|
||||
protected Item FindOrGiveItem(Character character, string identifier)
|
||||
{
|
||||
var item = character.Inventory.FindItemByIdentifier(identifier);
|
||||
if (item != null && !item.Removed) { return item; }
|
||||
|
||||
ItemPrefab itemPrefab = MapEntityPrefab.Find(name: null, identifier: identifier) as ItemPrefab;
|
||||
item = new Item(itemPrefab, Vector2.Zero, submarine: null);
|
||||
character.Inventory.TryPutItem(item, character, item.AllowedSlots);
|
||||
return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,639 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma.Tutorials
|
||||
{
|
||||
abstract class Tutorial
|
||||
{
|
||||
#region Tutorial variables
|
||||
public static bool Initialized = false;
|
||||
public static bool ContentRunning = false;
|
||||
public static List<Tutorial> Tutorials;
|
||||
|
||||
protected bool started = false;
|
||||
protected GUIComponent infoBox;
|
||||
private Action infoBoxClosedCallback;
|
||||
protected XElement configElement;
|
||||
|
||||
protected VideoPlayer videoPlayer;
|
||||
protected enum TutorialContentTypes { None = 0, Video = 1, ManualVideo = 2, TextOnly = 3 };
|
||||
protected string playableContentPath;
|
||||
protected Point screenResolution;
|
||||
protected float prevUIScale;
|
||||
|
||||
private GUIFrame holderFrame, objectiveFrame;
|
||||
private List<TutorialSegment> activeObjectives = new List<TutorialSegment>();
|
||||
private string objectiveTranslated;
|
||||
|
||||
protected TutorialSegment activeContentSegment;
|
||||
protected List<TutorialSegment> segments;
|
||||
|
||||
protected class TutorialSegment
|
||||
{
|
||||
public string Id;
|
||||
public string Objective;
|
||||
public TutorialContentTypes ContentType;
|
||||
public XElement TextContent;
|
||||
public XElement VideoContent;
|
||||
public bool IsTriggered;
|
||||
public GUIButton ReplayButton;
|
||||
public GUITextBlock LinkedTitle, LinkedText;
|
||||
public object[] Args;
|
||||
|
||||
public TutorialSegment(XElement config)
|
||||
{
|
||||
Id = config.GetAttributeString("id", "Missing ID");
|
||||
Objective = TextManager.Get(config.GetAttributeString("objective", string.Empty), true);
|
||||
Enum.TryParse(config.GetAttributeString("contenttype", "None"), true, out ContentType);
|
||||
IsTriggered = config.GetAttributeBool("istriggered", false);
|
||||
|
||||
switch (ContentType)
|
||||
{
|
||||
case TutorialContentTypes.None:
|
||||
break;
|
||||
case TutorialContentTypes.Video:
|
||||
case TutorialContentTypes.ManualVideo:
|
||||
VideoContent = config.Element("Video");
|
||||
TextContent = config.Element("Text");
|
||||
break;
|
||||
case TutorialContentTypes.TextOnly:
|
||||
TextContent = config.Element("Text");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string Identifier
|
||||
{
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
|
||||
public string DisplayName
|
||||
{
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
|
||||
private bool completed;
|
||||
public bool Completed
|
||||
{
|
||||
get { return completed; }
|
||||
protected set
|
||||
{
|
||||
if (completed == value) return;
|
||||
completed = value;
|
||||
GameMain.Config.SaveNewPlayerConfig();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Tutorial Controls
|
||||
public static void Init()
|
||||
{
|
||||
Tutorials = new List<Tutorial>();
|
||||
foreach (ContentFile file in GameMain.Instance.GetFilesOfType(ContentType.Tutorials))
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(file.Path);
|
||||
if (doc?.Root == null) continue;
|
||||
|
||||
foreach (XElement element in doc.Root.Elements())
|
||||
{
|
||||
Tutorial newTutorial = Load(element);
|
||||
if (newTutorial != null) Tutorials.Add(newTutorial);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Tutorial Load(XElement element)
|
||||
{
|
||||
Type t;
|
||||
string type = element.Name.ToString().ToLowerInvariant();
|
||||
try
|
||||
{
|
||||
// Get the type of a specified class.
|
||||
t = Type.GetType("Barotrauma.Tutorials." + type + "", false, true);
|
||||
if (t == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Could not find tutorial type \"" + type + "\"");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Could not find tutorial type \"" + type + "\"", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
ConstructorInfo constructor;
|
||||
try
|
||||
{
|
||||
if (!t.IsSubclassOf(typeof(Tutorial))) return null;
|
||||
constructor = t.GetConstructor(new Type[] { typeof(XElement) });
|
||||
if (constructor == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Could not find the constructor of tutorial type \"" + type + "\"");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Could not find the constructor of tutorial type \"" + type + "\"", e);
|
||||
return null;
|
||||
}
|
||||
Tutorial tutorial = null;
|
||||
try
|
||||
{
|
||||
object component = constructor.Invoke(new object[] { element });
|
||||
tutorial = (Tutorial)component;
|
||||
}
|
||||
catch (TargetInvocationException e)
|
||||
{
|
||||
DebugConsole.ThrowError("Error while loading tutorial of the type " + t + ".", e.InnerException);
|
||||
}
|
||||
|
||||
return tutorial;
|
||||
}
|
||||
|
||||
public Tutorial(XElement element)
|
||||
{
|
||||
configElement = element;
|
||||
Identifier = element.GetAttributeString("identifier", "unknown");
|
||||
DisplayName = TextManager.Get(Identifier);
|
||||
completed = GameMain.Config.CompletedTutorialNames.Contains(Identifier);
|
||||
playableContentPath = element.GetAttributeString("playablecontentpath", "");
|
||||
|
||||
segments = new List<TutorialSegment>();
|
||||
|
||||
foreach (var segment in element.Elements("Segment"))
|
||||
{
|
||||
segments.Add(new TutorialSegment(segment));
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Initialize()
|
||||
{
|
||||
if (Initialized) return;
|
||||
Initialized = true;
|
||||
videoPlayer = new VideoPlayer();
|
||||
}
|
||||
|
||||
public virtual void Start()
|
||||
{
|
||||
activeObjectives.Clear();
|
||||
objectiveTranslated = TextManager.Get("Tutorial.Objective");
|
||||
CreateObjectiveFrame();
|
||||
|
||||
// Setup doors: Clear all requirements, unless the door is setup as locked.
|
||||
foreach (var item in Item.ItemList)
|
||||
{
|
||||
var door = item.GetComponent<Door>();
|
||||
if (door != null)
|
||||
{
|
||||
if (door.requiredItems.Values.None(ris => ris.None(ri => ri.Identifiers.None(i => i == "locked"))))
|
||||
{
|
||||
door.requiredItems.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void AddToGUIUpdateList()
|
||||
{
|
||||
if (GameMain.GraphicsWidth != screenResolution.X || GameMain.GraphicsHeight != screenResolution.Y || prevUIScale != GUI.Scale)
|
||||
{
|
||||
CreateObjectiveFrame();
|
||||
}
|
||||
|
||||
if (objectiveFrame != null && activeObjectives.Count > 0)
|
||||
{
|
||||
objectiveFrame.AddToGUIUpdateList(order: -1);
|
||||
}
|
||||
|
||||
if (infoBox != null) infoBox.AddToGUIUpdateList(order: 100);
|
||||
if (videoPlayer != null) videoPlayer.AddToGUIUpdateList(order: 100);
|
||||
}
|
||||
|
||||
public virtual void Update(float deltaTime)
|
||||
{
|
||||
if (videoPlayer != null)
|
||||
{
|
||||
videoPlayer.Update();
|
||||
}
|
||||
|
||||
if (activeObjectives != null)
|
||||
{
|
||||
for (int i = 0; i < activeObjectives.Count; i++)
|
||||
{
|
||||
CheckActiveObjectives(activeObjectives[i], deltaTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void CloseActiveContentGUI()
|
||||
{
|
||||
if (videoPlayer.IsPlaying)
|
||||
{
|
||||
videoPlayer.Stop();
|
||||
}
|
||||
else if (infoBox != null)
|
||||
{
|
||||
CloseInfoFrame(null, null);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual IEnumerable<object> UpdateState()
|
||||
{
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
|
||||
protected bool Restart(GUIButton button, object obj)
|
||||
{
|
||||
GUI.PreventPauseMenuToggle = false;
|
||||
TutorialMode.StartTutorial(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected virtual void TriggerTutorialSegment(int index, params object[] args)
|
||||
{
|
||||
Inventory.draggingItem = null;
|
||||
ContentRunning = true;
|
||||
activeContentSegment = segments[index];
|
||||
segments[index].Args = args;
|
||||
|
||||
string tutorialText = TextManager.GetFormatted(activeContentSegment.TextContent.GetAttributeString("tag", ""), true, args);
|
||||
tutorialText = TextManager.ParseInputTypes(tutorialText);
|
||||
string objectiveText = string.Empty;
|
||||
|
||||
if (!string.IsNullOrEmpty(activeContentSegment.Objective))
|
||||
{
|
||||
if (args.Length == 0)
|
||||
{
|
||||
objectiveText = activeContentSegment.Objective;
|
||||
}
|
||||
else
|
||||
{
|
||||
objectiveText = string.Format(activeContentSegment.Objective, args);
|
||||
}
|
||||
objectiveText = TextManager.ParseInputTypes(objectiveText);
|
||||
activeContentSegment.Objective = objectiveText;
|
||||
}
|
||||
else
|
||||
{
|
||||
activeContentSegment.IsTriggered = true; // Complete at this stage only if no related objective
|
||||
}
|
||||
|
||||
|
||||
switch (activeContentSegment.ContentType)
|
||||
{
|
||||
case TutorialContentTypes.None:
|
||||
break;
|
||||
case TutorialContentTypes.Video:
|
||||
infoBox = CreateInfoFrame(TextManager.Get(activeContentSegment.Id), tutorialText,
|
||||
activeContentSegment.TextContent.GetAttributeInt("width", 300),
|
||||
activeContentSegment.TextContent.GetAttributeInt("height", 80),
|
||||
activeContentSegment.TextContent.GetAttributeString("anchor", "Center"), true, () => LoadVideo(activeContentSegment));
|
||||
break;
|
||||
case TutorialContentTypes.ManualVideo:
|
||||
infoBox = CreateInfoFrame(TextManager.Get(activeContentSegment.Id), tutorialText,
|
||||
activeContentSegment.TextContent.GetAttributeInt("width", 300),
|
||||
activeContentSegment.TextContent.GetAttributeInt("height", 80),
|
||||
activeContentSegment.TextContent.GetAttributeString("anchor", "Center"), true, StopCurrentContentSegment, () => LoadVideo(activeContentSegment));
|
||||
break;
|
||||
case TutorialContentTypes.TextOnly:
|
||||
infoBox = CreateInfoFrame(TextManager.Get(activeContentSegment.Id), tutorialText,
|
||||
activeContentSegment.TextContent.GetAttributeInt("width", 300),
|
||||
activeContentSegment.TextContent.GetAttributeInt("height", 80),
|
||||
activeContentSegment.TextContent.GetAttributeString("anchor", "Center"), true, StopCurrentContentSegment);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Stop()
|
||||
{
|
||||
started = ContentRunning = Initialized = false;
|
||||
infoBox = null;
|
||||
if (videoPlayer != null)
|
||||
{
|
||||
videoPlayer.Remove();
|
||||
videoPlayer = null;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Objectives
|
||||
private void CreateObjectiveFrame()
|
||||
{
|
||||
holderFrame = new GUIFrame(new RectTransform(new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight), GUI.Canvas, Anchor.Center));
|
||||
objectiveFrame = new GUIFrame(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.ObjectiveAnchor, holderFrame.RectTransform), style: null);
|
||||
|
||||
for (int i = 0; i < activeObjectives.Count; i++)
|
||||
{
|
||||
CreateObjectiveGUI(activeObjectives[i], i, activeObjectives[i].ContentType);
|
||||
}
|
||||
|
||||
screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
|
||||
prevUIScale = GUI.Scale;
|
||||
}
|
||||
|
||||
protected void StopCurrentContentSegment()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(activeContentSegment.Objective))
|
||||
{
|
||||
AddNewObjective(activeContentSegment, activeContentSegment.ContentType);
|
||||
}
|
||||
|
||||
activeContentSegment = null;
|
||||
ContentRunning = false;
|
||||
}
|
||||
|
||||
protected virtual void CheckActiveObjectives(TutorialSegment objective, float deltaTime)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected bool HasObjective(TutorialSegment segment)
|
||||
{
|
||||
return activeObjectives.Contains(segment);
|
||||
}
|
||||
|
||||
protected void AddNewObjective(TutorialSegment segment, TutorialContentTypes type)
|
||||
{
|
||||
activeObjectives.Add(segment);
|
||||
CreateObjectiveGUI(segment, activeObjectives.Count - 1, type);
|
||||
}
|
||||
|
||||
private void CreateObjectiveGUI(TutorialSegment segment, int index, TutorialContentTypes type)
|
||||
{
|
||||
string objectiveText = TextManager.ParseInputTypes(segment.Objective);
|
||||
Point replayButtonSize = new Point((int)(GUI.LargeFont.MeasureString(objectiveText).X), (int)(GUI.LargeFont.MeasureString(objectiveText).Y * 1.45f));
|
||||
|
||||
segment.ReplayButton = new GUIButton(new RectTransform(replayButtonSize, objectiveFrame.RectTransform, Anchor.TopLeft, Pivot.TopLeft) { AbsoluteOffset = new Point(0, (replayButtonSize.Y + (int)(20f * GUI.Scale)) * index) }, style: null);
|
||||
segment.ReplayButton.OnClicked += (GUIButton btn, object userdata) =>
|
||||
{
|
||||
if (type == TutorialContentTypes.Video)
|
||||
{
|
||||
ReplaySegmentVideo(segment);
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowSegmentText(segment);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
string objectiveTitleText = TextManager.ParseInputTypes(objectiveTranslated);
|
||||
int yOffset = (int)((GUI.SubHeadingFont.MeasureString(objectiveTitleText).Y + 5));
|
||||
segment.LinkedTitle = new GUITextBlock(new RectTransform(new Point((int)GUI.SubHeadingFont.MeasureString(objectiveTitleText).X, yOffset), segment.ReplayButton.RectTransform, Anchor.CenterLeft, Pivot.BottomLeft) /*{ AbsoluteOffset = new Point((int)(-10 * GUI.Scale), 0) }*/,
|
||||
objectiveTitleText, textColor: Color.White, font: GUI.SubHeadingFont, textAlignment: Alignment.CenterLeft)
|
||||
{
|
||||
ForceUpperCase = true
|
||||
};
|
||||
|
||||
segment.LinkedText = new GUITextBlock(new RectTransform(new Point((int)GUI.LargeFont.MeasureString(objectiveText).X, yOffset), segment.ReplayButton.RectTransform, Anchor.CenterLeft, Pivot.TopLeft) /*{ AbsoluteOffset = new Point((int)(10 * GUI.Scale), 0) }*/,
|
||||
objectiveText, textColor: new Color(4, 180, 108), font: GUI.LargeFont, textAlignment: Alignment.CenterLeft);
|
||||
|
||||
segment.LinkedTitle.Color = segment.LinkedTitle.HoverColor = segment.LinkedTitle.PressedColor = segment.LinkedTitle.SelectedColor = Color.Transparent;
|
||||
segment.LinkedText.Color = segment.LinkedText.HoverColor = segment.LinkedText.PressedColor = segment.LinkedText.SelectedColor = Color.Transparent;
|
||||
segment.ReplayButton.Color = segment.ReplayButton.HoverColor = segment.ReplayButton.PressedColor = segment.ReplayButton.SelectedColor = Color.Transparent;
|
||||
}
|
||||
|
||||
private void ReplaySegmentVideo(TutorialSegment segment)
|
||||
{
|
||||
if (ContentRunning) return;
|
||||
Inventory.draggingItem = null;
|
||||
ContentRunning = true;
|
||||
LoadVideo(segment);
|
||||
//videoPlayer.LoadContent(playableContentPath, new VideoPlayer.VideoSettings(segment.VideoContent), new VideoPlayer.TextSettings(segment.VideoContent), segment.Id, true, callback: () => ContentRunning = false);
|
||||
}
|
||||
|
||||
private void ShowSegmentText(TutorialSegment segment)
|
||||
{
|
||||
if (ContentRunning) return;
|
||||
Inventory.draggingItem = null;
|
||||
ContentRunning = true;
|
||||
|
||||
string tutorialText = TextManager.GetFormatted(segment.TextContent.GetAttributeString("tag", ""), true, segment.Args);
|
||||
|
||||
Action videoAction = null;
|
||||
|
||||
if (segment.ContentType != TutorialContentTypes.TextOnly)
|
||||
{
|
||||
videoAction = () => LoadVideo(segment);
|
||||
}
|
||||
|
||||
infoBox = CreateInfoFrame(TextManager.Get(segment.Id), tutorialText,
|
||||
segment.TextContent.GetAttributeInt("width", 300),
|
||||
segment.TextContent.GetAttributeInt("height", 80),
|
||||
segment.TextContent.GetAttributeString("anchor", "Center"), true, () => ContentRunning = false, videoAction);
|
||||
}
|
||||
|
||||
protected void RemoveCompletedObjective(TutorialSegment segment)
|
||||
{
|
||||
if (!HasObjective(segment)) return;
|
||||
segment.IsTriggered = true;
|
||||
segment.ReplayButton.OnClicked = null;
|
||||
|
||||
int checkMarkHeight = (int)(segment.ReplayButton.Rect.Height * 1.2f);
|
||||
int checkMarkWidth = (int)(checkMarkHeight * 0.93f);
|
||||
|
||||
Color color = new Color(4, 180, 108);
|
||||
|
||||
int objectiveTextWidth = segment.LinkedText.Rect.Width;
|
||||
int objectiveTitleWidth = segment.LinkedTitle.Rect.Width;
|
||||
|
||||
RectTransform rectTA;
|
||||
if (objectiveTextWidth > objectiveTitleWidth)
|
||||
{
|
||||
rectTA = new RectTransform(new Point(checkMarkWidth, checkMarkHeight), segment.ReplayButton.RectTransform, Anchor.BottomRight, Pivot.BottomRight);
|
||||
rectTA.AbsoluteOffset = new Point(-rectTA.Rect.Width - (int)(25 * GUI.Scale), 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
rectTA = new RectTransform(new Point(checkMarkWidth, checkMarkHeight), segment.ReplayButton.RectTransform, Anchor.BottomRight, Pivot.BottomRight);
|
||||
rectTA.AbsoluteOffset = new Point(-rectTA.Rect.Width - (int)(25 * GUI.Scale) - (objectiveTitleWidth - objectiveTextWidth), 0);
|
||||
}
|
||||
|
||||
GUIImage checkmark = new GUIImage(rectTA, "CheckMark");
|
||||
checkmark.Color = checkmark.SelectedColor = checkmark.HoverColor = checkmark.PressedColor = color;
|
||||
|
||||
RectTransform rectTB = new RectTransform(new Vector2(1.0f, .8f), segment.LinkedText.RectTransform, Anchor.Center, Pivot.Center);
|
||||
GUIImage stroke = new GUIImage(rectTB, "Stroke");
|
||||
stroke.Color = stroke.SelectedColor = stroke.HoverColor = stroke.PressedColor = color;
|
||||
|
||||
CoroutineManager.StartCoroutine(WaitForObjectiveEnd(segment));
|
||||
}
|
||||
|
||||
private IEnumerable<object> WaitForObjectiveEnd(TutorialSegment objective)
|
||||
{
|
||||
yield return new WaitForSeconds(2.0f);
|
||||
objectiveFrame.RemoveChild(objective.ReplayButton);
|
||||
activeObjectives.Remove(objective);
|
||||
|
||||
for (int i = 0; i < activeObjectives.Count; i++)
|
||||
{
|
||||
activeObjectives[i].ReplayButton.RectTransform.AbsoluteOffset = new Point(0, (activeObjectives[i].ReplayButton.Rect.Height + 20) * i);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region InfoFrame
|
||||
protected bool CloseInfoFrame(GUIButton button, object userData)
|
||||
{
|
||||
infoBox = null;
|
||||
infoBoxClosedCallback?.Invoke();
|
||||
return true;
|
||||
}
|
||||
|
||||
protected GUIComponent CreateInfoFrame(string title, string text, int width = 300, int height = 80, string anchorStr = "", bool hasButton = false, Action callback = null, Action showVideo = null)
|
||||
{
|
||||
if (hasButton) height += 60;
|
||||
|
||||
Anchor anchor = Anchor.TopRight;
|
||||
|
||||
if (anchorStr != string.Empty)
|
||||
{
|
||||
Enum.TryParse(anchorStr, out anchor);
|
||||
}
|
||||
|
||||
width = (int)(width * GUI.Scale);
|
||||
height = (int)(height * GUI.Scale);
|
||||
|
||||
string wrappedText = ToolBox.WrapText(text, width, GUI.Font);
|
||||
height += (int)GUI.Font.MeasureString(wrappedText).Y;
|
||||
|
||||
if (title.Length > 0)
|
||||
{
|
||||
height += (int)GUI.Font.MeasureString(title).Y + (int)(150 * GUI.Scale);
|
||||
}
|
||||
|
||||
var background = new GUIFrame(new RectTransform(new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight), GUI.Canvas, Anchor.Center), style: null, Color.Black * 0.5f);
|
||||
|
||||
var infoBlock = new GUIFrame(new RectTransform(new Point(width, height), background.RectTransform, anchor));
|
||||
infoBlock.Flash(GUI.Style.Green);
|
||||
|
||||
var infoContent = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.9f), infoBlock.RectTransform, Anchor.Center))
|
||||
{
|
||||
Stretch = true,
|
||||
AbsoluteSpacing = 5
|
||||
};
|
||||
|
||||
if (title.Length > 0)
|
||||
{
|
||||
var titleBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContent.RectTransform),
|
||||
title, font: GUI.LargeFont, textAlignment: Alignment.Center, textColor: new Color(253, 174, 0));
|
||||
titleBlock.RectTransform.IsFixedSize = true;
|
||||
}
|
||||
|
||||
List<ColorData> colorData = ColorData.GetColorData(text, out text);
|
||||
GUITextBlock textBlock;
|
||||
if (colorData == null)
|
||||
{
|
||||
textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContent.RectTransform), " " + text, wrap: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContent.RectTransform), colorData, " " + text, wrap: true);
|
||||
}
|
||||
|
||||
textBlock.RectTransform.IsFixedSize = true;
|
||||
infoBoxClosedCallback = callback;
|
||||
|
||||
if (hasButton)
|
||||
{
|
||||
var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.15f), infoContent.RectTransform), isHorizontal: true)
|
||||
{
|
||||
RelativeSpacing = 0.1f
|
||||
};
|
||||
buttonContainer.RectTransform.IsFixedSize = true;
|
||||
|
||||
if (showVideo != null)
|
||||
{
|
||||
buttonContainer.Stretch = true;
|
||||
var videoButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), buttonContainer.RectTransform),
|
||||
TextManager.Get("Video"), style: "GUIButtonLarge")
|
||||
{
|
||||
OnClicked = (GUIButton button, object obj) =>
|
||||
{
|
||||
showVideo();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
buttonContainer.Stretch = false;
|
||||
buttonContainer.ChildAnchor = Anchor.Center;
|
||||
}
|
||||
|
||||
var okButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), buttonContainer.RectTransform),
|
||||
TextManager.Get("OK"), style: "GUIButtonLarge")
|
||||
{
|
||||
OnClicked = CloseInfoFrame
|
||||
};
|
||||
}
|
||||
|
||||
infoBlock.RectTransform.NonScaledSize = new Point(infoBlock.Rect.Width, (int)(infoContent.Children.Sum(c => c.Rect.Height + infoContent.AbsoluteSpacing) / infoContent.RectTransform.RelativeSize.Y));
|
||||
|
||||
GUI.PlayUISound(GUISoundType.UIMessage);
|
||||
|
||||
return background;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Video
|
||||
protected void LoadVideo(TutorialSegment segment)
|
||||
{
|
||||
if (videoPlayer == null) videoPlayer = new VideoPlayer();
|
||||
if (segment.ContentType != TutorialContentTypes.ManualVideo)
|
||||
{
|
||||
videoPlayer.LoadContent(playableContentPath, new VideoPlayer.VideoSettings(segment.VideoContent), new VideoPlayer.TextSettings(segment.VideoContent), segment.Id, true, segment.Objective, StopCurrentContentSegment);
|
||||
}
|
||||
else
|
||||
{
|
||||
videoPlayer.LoadContent(playableContentPath, new VideoPlayer.VideoSettings(segment.VideoContent), null, segment.Id, true, string.Empty, null);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Highlights
|
||||
protected void HighlightInventorySlot(Inventory inventory, string identifier, Color color, float fadeInDuration, float fadeOutDuration, float scaleUpAmount)
|
||||
{
|
||||
if (inventory.slots == null) { return; }
|
||||
for (int i = 0; i < inventory.Items.Length; i++)
|
||||
{
|
||||
if (inventory.Items[i] != null && inventory.Items[i].Prefab.Identifier == identifier)
|
||||
{
|
||||
HighlightInventorySlot(inventory, i, color, fadeInDuration, fadeOutDuration, scaleUpAmount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void HighlightInventorySlotWithTag(Inventory inventory, string tag, Color color, float fadeInDuration, float fadeOutDuration, float scaleUpAmount)
|
||||
{
|
||||
if (inventory.slots == null) { return; }
|
||||
for (int i = 0; i < inventory.Items.Length; i++)
|
||||
{
|
||||
if (inventory.Items[i] != null && inventory.Items[i].HasTag(tag))
|
||||
{
|
||||
HighlightInventorySlot(inventory, i, color, fadeInDuration, fadeOutDuration, scaleUpAmount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void HighlightInventorySlot(Inventory inventory, int index, Color color, float fadeInDuration, float fadeOutDuration, float scaleUpAmount)
|
||||
{
|
||||
if (inventory.slots == null || index < 0 || inventory.slots[index].HighlightTimer > 0) return;
|
||||
inventory.slots[index].ShowBorderHighlight(color, fadeInDuration, fadeOutDuration, scaleUpAmount);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
using Barotrauma.Tutorials;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class TutorialMode : GameMode
|
||||
{
|
||||
public Tutorial Tutorial;
|
||||
|
||||
public static void StartTutorial(Tutorial tutorial)
|
||||
{
|
||||
tutorial.Initialize();
|
||||
}
|
||||
|
||||
public TutorialMode(GameModePreset preset, object param)
|
||||
: base(preset, param)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Start()
|
||||
{
|
||||
base.Start();
|
||||
GameMain.GameSession.CrewManager = new CrewManager(true);
|
||||
Tutorial.Start();
|
||||
}
|
||||
|
||||
public override void AddToGUIUpdateList()
|
||||
{
|
||||
base.AddToGUIUpdateList();
|
||||
Tutorial.AddToGUIUpdateList();
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
base.Update(deltaTime);
|
||||
Tutorial.Update(deltaTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class GameSession
|
||||
{
|
||||
private InfoFrameTab selectedTab;
|
||||
private GUIFrame infoFrame;
|
||||
|
||||
private readonly List<GUIButton> tabButtons = new List<GUIButton>();
|
||||
|
||||
private GUIFrame infoFrameContent;
|
||||
public RoundSummary RoundSummary { get; private set; }
|
||||
public static bool IsInfoFrameOpen => GameMain.GameSession?.infoFrame != null;
|
||||
|
||||
private bool ToggleInfoFrame()
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetLobbyScreen != null)
|
||||
{
|
||||
if (GameMain.NetLobbyScreen.HeadSelectionList != null) { GameMain.NetLobbyScreen.HeadSelectionList.Visible = false; }
|
||||
if (GameMain.NetLobbyScreen.JobSelectionFrame != null) { GameMain.NetLobbyScreen.JobSelectionFrame.Visible = false; }
|
||||
}
|
||||
if (infoFrame == null)
|
||||
{
|
||||
CreateInfoFrame();
|
||||
SelectInfoFrameTab(null, selectedTab);
|
||||
}
|
||||
else
|
||||
{
|
||||
infoFrame = null;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void CreateInfoFrame()
|
||||
{
|
||||
int width = 600, height = 400;
|
||||
|
||||
tabButtons.Clear();
|
||||
|
||||
infoFrame = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas), style: "GUIBackgroundBlocker");
|
||||
|
||||
var innerFrame = new GUIFrame(new RectTransform(new Vector2(0.5f, 0.35f), infoFrame.RectTransform, Anchor.Center) { MinSize = new Point(width, height), RelativeOffset = new Vector2(0.0f, 0.033f) });
|
||||
|
||||
var paddedFrame = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.9f), innerFrame.RectTransform, Anchor.Center), style: null);
|
||||
var buttonArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.08f), paddedFrame.RectTransform), isHorizontal: true)
|
||||
{
|
||||
RelativeSpacing = 0.01f
|
||||
};
|
||||
infoFrameContent = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.85f), paddedFrame.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.08f) }, style: "InnerFrame");
|
||||
|
||||
var crewButton = new GUIButton(new RectTransform(new Vector2(0.2f, 1.0f), buttonArea.RectTransform), TextManager.Get("Crew"), style: "GUITabButton")
|
||||
{
|
||||
UserData = InfoFrameTab.Crew,
|
||||
OnClicked = SelectInfoFrameTab
|
||||
};
|
||||
tabButtons.Add(crewButton);
|
||||
|
||||
var missionButton = new GUIButton(new RectTransform(new Vector2(0.2f, 1.0f), buttonArea.RectTransform), TextManager.Get("Mission"), style: "GUITabButton")
|
||||
{
|
||||
UserData = InfoFrameTab.Mission,
|
||||
OnClicked = SelectInfoFrameTab
|
||||
};
|
||||
tabButtons.Add(missionButton);
|
||||
|
||||
if (GameMain.NetworkMember != null)
|
||||
{
|
||||
var myCharacterButton = new GUIButton(new RectTransform(new Vector2(0.2f, 1.0f), buttonArea.RectTransform), TextManager.Get("MyCharacter"), style: "GUITabButton")
|
||||
{
|
||||
UserData = InfoFrameTab.MyCharacter,
|
||||
OnClicked = SelectInfoFrameTab
|
||||
};
|
||||
tabButtons.Add(myCharacterButton);
|
||||
}
|
||||
|
||||
/*TODO: fix
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
var manageButton = new GUIButton(new RectTransform(new Vector2(0.2f, 1.0f), buttonArea.RectTransform), TextManager.Get("ManagePlayers"))
|
||||
{
|
||||
UserData = InfoFrameTab.ManagePlayers,
|
||||
OnClicked = SelectInfoFrameTab
|
||||
};
|
||||
}*/
|
||||
|
||||
}
|
||||
|
||||
private bool SelectInfoFrameTab(GUIButton button, object userData)
|
||||
{
|
||||
selectedTab = (InfoFrameTab)userData;
|
||||
|
||||
CreateInfoFrame();
|
||||
tabButtons.ForEach(tb => tb.Selected = (InfoFrameTab)tb.UserData == selectedTab);
|
||||
|
||||
switch (selectedTab)
|
||||
{
|
||||
case InfoFrameTab.Crew:
|
||||
CrewManager.CreateCrewListFrame(CrewManager.GetCharacters(), infoFrameContent);
|
||||
break;
|
||||
case InfoFrameTab.Mission:
|
||||
CreateMissionInfo(infoFrameContent);
|
||||
break;
|
||||
case InfoFrameTab.MyCharacter:
|
||||
if (GameMain.NetworkMember == null) { return false; }
|
||||
GameMain.NetLobbyScreen.CreatePlayerFrame(infoFrameContent);
|
||||
break;
|
||||
case InfoFrameTab.ManagePlayers:
|
||||
//TODO: fix
|
||||
//GameMain.Server.ManagePlayersFrame(infoFrameContent);
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void CreateMissionInfo(GUIFrame infoFrame)
|
||||
{
|
||||
infoFrameContent.ClearChildren();
|
||||
|
||||
var isTraitor = GameMain.Client?.Character?.IsTraitor ?? false;
|
||||
|
||||
var missionFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, isTraitor ? 0.95f : 0.45f), infoFrameContent.RectTransform))
|
||||
{
|
||||
RelativeSpacing = 0.05f
|
||||
};
|
||||
|
||||
if (Mission != null)
|
||||
{
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionFrame.RectTransform), Mission.Name, font: GUI.LargeFont);
|
||||
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionFrame.RectTransform), TextManager.GetWithVariable("MissionReward", "[reward]", Mission.Reward.ToString()));
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionFrame.RectTransform), Mission.Description, wrap: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionFrame.RectTransform, Anchor.TopCenter), TextManager.Get("NoMission"), font: GUI.LargeFont);
|
||||
}
|
||||
if (isTraitor)
|
||||
{
|
||||
var traitorFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.45f), infoFrameContent.RectTransform, Anchor.BottomLeft))
|
||||
{
|
||||
RelativeSpacing = 0.05f
|
||||
};
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), traitorFrame.RectTransform), TextManager.Get("Traitors"), font: GUI.LargeFont);
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), traitorFrame.RectTransform), GameMain.Client.Character.TraitorCurrentObjective, wrap: true);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddToGUIUpdateList()
|
||||
{
|
||||
if (GUI.DisableHUD) return;
|
||||
GameMode?.AddToGUIUpdateList();
|
||||
infoFrame?.AddToGUIUpdateList();
|
||||
|
||||
if (GameMain.NetworkMember != null)
|
||||
{
|
||||
GameMain.NetLobbyScreen?.HeadSelectionList?.AddToGUIUpdateList();
|
||||
GameMain.NetLobbyScreen?.JobSelectionFrame?.AddToGUIUpdateList();
|
||||
}
|
||||
}
|
||||
|
||||
partial void UpdateProjSpecific(float deltaTime)
|
||||
{
|
||||
if (GUI.DisableHUD) return;
|
||||
|
||||
if (PlayerInput.KeyDown(InputType.InfoTab) &&
|
||||
(GUI.KeyboardDispatcher.Subscriber == null || GUI.KeyboardDispatcher.Subscriber is GUIListBox))
|
||||
{
|
||||
if (infoFrame == null)
|
||||
{
|
||||
ToggleInfoFrame();
|
||||
}
|
||||
}
|
||||
else if (infoFrame != null)
|
||||
{
|
||||
ToggleInfoFrame();
|
||||
}
|
||||
|
||||
if (GameMain.NetworkMember != null)
|
||||
{
|
||||
if (GameMain.NetLobbyScreen?.HeadSelectionList != null)
|
||||
{
|
||||
if (PlayerInput.PrimaryMouseButtonDown() && !GUI.IsMouseOn(GameMain.NetLobbyScreen.HeadSelectionList))
|
||||
{
|
||||
if (GameMain.NetLobbyScreen.HeadSelectionList != null) { GameMain.NetLobbyScreen.HeadSelectionList.Visible = false; }
|
||||
}
|
||||
}
|
||||
if (GameMain.NetLobbyScreen?.JobSelectionFrame != null)
|
||||
{
|
||||
if (PlayerInput.PrimaryMouseButtonDown() && !GUI.IsMouseOn(GameMain.NetLobbyScreen.JobSelectionFrame))
|
||||
{
|
||||
GameMain.NetLobbyScreen.JobList.Deselect();
|
||||
if (GameMain.NetLobbyScreen.JobSelectionFrame != null) { GameMain.NetLobbyScreen.JobSelectionFrame.Visible = false; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
if (GUI.DisableHUD) return;
|
||||
|
||||
GameMode?.Draw(spriteBatch);
|
||||
//infoFrame?.DrawManually(spriteBatch);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class RoundSummary
|
||||
{
|
||||
private Location startLocation, endLocation;
|
||||
|
||||
private GameSession gameSession;
|
||||
|
||||
private Mission selectedMission;
|
||||
|
||||
public RoundSummary(GameSession gameSession)
|
||||
{
|
||||
this.gameSession = gameSession;
|
||||
|
||||
startLocation = gameSession.StartLocation;
|
||||
endLocation = gameSession.EndLocation;
|
||||
|
||||
selectedMission = gameSession.Mission;
|
||||
}
|
||||
|
||||
public GUIFrame CreateSummaryFrame(string endMessage)
|
||||
{
|
||||
bool singleplayer = GameMain.NetworkMember == null;
|
||||
bool gameOver = gameSession.CrewManager.GetCharacters().All(c => c.IsDead || c.IsUnconscious);
|
||||
bool progress = Submarine.MainSub.AtEndPosition;
|
||||
if (!singleplayer)
|
||||
{
|
||||
SoundPlayer.OverrideMusicType = gameOver ? "crewdead" : "endround";
|
||||
SoundPlayer.OverrideMusicDuration = 18.0f;
|
||||
}
|
||||
|
||||
GUIFrame frame = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas), style: "GUIBackgroundBlocker")
|
||||
{
|
||||
UserData = "roundsummary"
|
||||
};
|
||||
|
||||
int width = 760, height = 500;
|
||||
GUIFrame innerFrame = new GUIFrame(new RectTransform(new Vector2(0.4f, 0.5f), frame.RectTransform, Anchor.Center, minSize: new Point(width, height)));
|
||||
var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), innerFrame.RectTransform, Anchor.Center))
|
||||
{
|
||||
Stretch = true,
|
||||
RelativeSpacing = 0.03f
|
||||
};
|
||||
|
||||
GUIListBox infoTextBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.7f), paddedFrame.RectTransform))
|
||||
{
|
||||
Spacing = (int)(5 * GUI.Scale)
|
||||
};
|
||||
|
||||
//spacing
|
||||
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), infoTextBox.Content.RectTransform), style: null);
|
||||
|
||||
string summaryText = TextManager.GetWithVariables(gameOver ? "RoundSummaryGameOver" :
|
||||
(progress ? "RoundSummaryProgress" : "RoundSummaryReturn"), new string[2] { "[sub]", "[location]" },
|
||||
new string[2] { Submarine.MainSub.Name, progress ? GameMain.GameSession.EndLocation.Name : GameMain.GameSession.StartLocation.Name });
|
||||
|
||||
var infoText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoTextBox.Content.RectTransform),
|
||||
summaryText, wrap: true);
|
||||
|
||||
GUIComponent endText = null;
|
||||
if (!string.IsNullOrWhiteSpace(endMessage))
|
||||
{
|
||||
endText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoTextBox.Content.RectTransform),
|
||||
TextManager.GetServerMessage(endMessage), wrap: true);
|
||||
}
|
||||
|
||||
//don't show the mission info if the mission was not completed and there's no localized "mission failed" text available
|
||||
if (GameMain.GameSession.Mission != null)
|
||||
{
|
||||
string message = GameMain.GameSession.Mission.Completed ? GameMain.GameSession.Mission.SuccessMessage : GameMain.GameSession.Mission.FailureMessage;
|
||||
if (!string.IsNullOrEmpty(message))
|
||||
{
|
||||
//spacing
|
||||
var spacingTransform = new RectTransform(new Vector2(1.0f, 0.1f), infoTextBox.Content.RectTransform);
|
||||
|
||||
new GUIFrame(spacingTransform, style: null);
|
||||
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoTextBox.Content.RectTransform),
|
||||
TextManager.AddPunctuation(':', TextManager.Get("Mission"), GameMain.GameSession.Mission.Name),
|
||||
font: GUI.LargeFont);
|
||||
|
||||
var missionInfo = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoTextBox.Content.RectTransform),
|
||||
message, wrap: true);
|
||||
|
||||
if (GameMain.GameSession.Mission.Completed && singleplayer)
|
||||
{
|
||||
var missionReward = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoTextBox.Content.RectTransform),
|
||||
TextManager.GetWithVariable("MissionReward", "[reward]", GameMain.GameSession.Mission.Reward.ToString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform),
|
||||
TextManager.Get("RoundSummaryCrewStatus"), font: GUI.LargeFont);
|
||||
|
||||
GUIListBox characterListBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.4f), paddedFrame.RectTransform, minSize: new Point(0, 75)), isHorizontal: true);
|
||||
|
||||
foreach (CharacterInfo characterInfo in gameSession.CrewManager.GetCharacterInfos())
|
||||
{
|
||||
if (GameMain.GameSession.Mission is CombatMission &&
|
||||
characterInfo.TeamID != GameMain.GameSession.WinningTeam)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var characterFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.2f, 1.0f), characterListBox.Content.RectTransform, minSize: new Point(170, 0)))
|
||||
{
|
||||
CanBeFocused = false,
|
||||
Stretch = true
|
||||
};
|
||||
|
||||
characterInfo.CreateCharacterFrame(characterFrame,
|
||||
characterInfo.Job != null ? (characterInfo.Name + '\n' + "(" + characterInfo.Job.Name + ")") : characterInfo.Name, null);
|
||||
|
||||
string statusText = TextManager.Get("StatusOK");
|
||||
Color statusColor = Color.DarkGreen;
|
||||
|
||||
Character character = characterInfo.Character;
|
||||
if (character == null || character.IsDead)
|
||||
{
|
||||
if (characterInfo.CauseOfDeath == null)
|
||||
{
|
||||
statusText = TextManager.Get("CauseOfDeathDescription.Unknown");
|
||||
}
|
||||
else if (characterInfo.CauseOfDeath.Type == CauseOfDeathType.Affliction && characterInfo.CauseOfDeath.Affliction == null)
|
||||
{
|
||||
string errorMsg = "Character \"" + character.Name + "\" had an invalid cause of death (the type of the cause of death was Affliction, but affliction was not specified).";
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
GameAnalyticsManager.AddErrorEventOnce("RoundSummary:InvalidCauseOfDeath", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
|
||||
statusText = TextManager.Get("CauseOfDeathDescription.Unknown");
|
||||
}
|
||||
else
|
||||
{
|
||||
statusText = characterInfo.CauseOfDeath.Type == CauseOfDeathType.Affliction ?
|
||||
characterInfo.CauseOfDeath.Affliction.CauseOfDeathDescription :
|
||||
TextManager.Get("CauseOfDeathDescription." + characterInfo.CauseOfDeath.Type.ToString());
|
||||
}
|
||||
statusColor = Color.DarkRed;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (character.IsUnconscious)
|
||||
{
|
||||
statusText = TextManager.Get("Unconscious");
|
||||
statusColor = Color.DarkOrange;
|
||||
}
|
||||
else if (character.Vitality / character.MaxVitality < 0.8f)
|
||||
{
|
||||
statusText = TextManager.Get("Injured");
|
||||
statusColor = Color.DarkOrange;
|
||||
}
|
||||
}
|
||||
|
||||
var textHolder = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.2f), characterFrame.RectTransform, Anchor.BottomCenter), style: "InnerGlow", color: statusColor);
|
||||
new GUITextBlock(new RectTransform(Vector2.One, textHolder.RectTransform, Anchor.Center),
|
||||
statusText, Color.White,
|
||||
textAlignment: Alignment.Center,
|
||||
wrap: true, font: GUI.SmallFont, style: null);
|
||||
}
|
||||
|
||||
new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), paddedFrame.RectTransform), isHorizontal: true, childAnchor: Anchor.BottomRight)
|
||||
{
|
||||
RelativeSpacing = 0.05f,
|
||||
UserData = "buttonarea"
|
||||
};
|
||||
|
||||
paddedFrame.Recalculate();
|
||||
foreach (GUIComponent child in infoTextBox.Content.Children)
|
||||
{
|
||||
child.CanBeFocused = false;
|
||||
if (child is GUITextBlock textBlock)
|
||||
{
|
||||
textBlock.CalculateHeightFromText();
|
||||
}
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,264 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Lights;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class Door : Pickable, IDrawableComponent, IServerSerializable
|
||||
{
|
||||
private ConvexHull convexHull;
|
||||
private ConvexHull convexHull2;
|
||||
|
||||
private float shake;
|
||||
private float shakeTimer;
|
||||
private Vector2 shakePos;
|
||||
|
||||
//openState when the vertices of the convex hull were last calculated
|
||||
private float lastConvexHullState;
|
||||
|
||||
[Serialize("1,1", false, description: "The scale of the shadow-casting area of the door (relative to the actual size of the door).")]
|
||||
public Vector2 ShadowScale
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public Vector2 DrawSize
|
||||
{
|
||||
//use the extents of the item as the draw size
|
||||
get { return Vector2.Zero; }
|
||||
}
|
||||
|
||||
private Vector2[] GetConvexHullCorners(Rectangle rect)
|
||||
{
|
||||
Point shadowSize = rect.Size.Multiply(ShadowScale);
|
||||
Vector2 center = new Vector2(rect.Center.X, rect.Y - rect.Height / 2);
|
||||
|
||||
Vector2[] corners = new Vector2[4];
|
||||
corners[0] = center + new Vector2(-shadowSize.X, -shadowSize.Y) / 2;
|
||||
corners[1] = center + new Vector2(-shadowSize.X, shadowSize.Y) / 2;
|
||||
corners[2] = center + new Vector2(shadowSize.X, shadowSize.Y) / 2;
|
||||
corners[3] = center + new Vector2(shadowSize.X, -shadowSize.Y) / 2;
|
||||
|
||||
return corners;
|
||||
}
|
||||
|
||||
private void UpdateConvexHulls()
|
||||
{
|
||||
doorRect = new Rectangle(
|
||||
item.Rect.Center.X - (int)(doorSprite.size.X / 2 * item.Scale),
|
||||
item.Rect.Y - item.Rect.Height / 2 + (int)(doorSprite.size.Y / 2.0f * item.Scale),
|
||||
(int)(doorSprite.size.X * item.Scale),
|
||||
(int)(doorSprite.size.Y * item.Scale));
|
||||
|
||||
Rectangle rect = doorRect;
|
||||
if (IsHorizontal)
|
||||
{
|
||||
rect.Width = (int)(rect.Width * (1.0f - openState));
|
||||
}
|
||||
else
|
||||
{
|
||||
rect.Height = (int)(rect.Height * (1.0f - openState));
|
||||
}
|
||||
|
||||
if (Window.Height > 0 && Window.Width > 0)
|
||||
{
|
||||
rect.Height = -(int)(Window.Y * item.Scale);
|
||||
|
||||
rect.Y += (int)(doorRect.Height * openState);
|
||||
rect.Height = Math.Max(rect.Height - (rect.Y - doorRect.Y), 0);
|
||||
rect.Y = Math.Min(doorRect.Y, rect.Y);
|
||||
|
||||
if (convexHull2 != null)
|
||||
{
|
||||
Rectangle rect2 = doorRect;
|
||||
rect2.Y += (int)(Window.Y * item.Scale - Window.Height * item.Scale);
|
||||
|
||||
rect2.Y += (int)(doorRect.Height * openState);
|
||||
rect2.Y = Math.Min(doorRect.Y, rect2.Y);
|
||||
rect2.Height = rect2.Y - (doorRect.Y - (int)(doorRect.Height * (1.0f - openState)));
|
||||
|
||||
if (rect2.Height == 0)
|
||||
{
|
||||
convexHull2.Enabled = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
convexHull2.Enabled = true;
|
||||
convexHull2.SetVertices(GetConvexHullCorners(rect2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (convexHull == null) return;
|
||||
|
||||
if (rect.Height == 0 || rect.Width == 0)
|
||||
{
|
||||
convexHull.Enabled = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
convexHull.Enabled = true;
|
||||
convexHull.SetVertices(GetConvexHullCorners(rect));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
partial void UpdateProjSpecific(float deltaTime)
|
||||
{
|
||||
if (shakeTimer > 0.0f)
|
||||
{
|
||||
shakeTimer -= deltaTime;
|
||||
Vector2 noisePos = new Vector2((float)PerlinNoise.CalculatePerlin(shakeTimer * 10.0f, shakeTimer * 10.0f, 0) - 0.5f, (float)PerlinNoise.CalculatePerlin(shakeTimer * 10.0f, shakeTimer * 10.0f, 0.5f) - 0.5f);
|
||||
shakePos = noisePos * shake * 2.0f;
|
||||
shake = Math.Min(shake, shakeTimer * 10.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
shakePos = Vector2.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1)
|
||||
{
|
||||
Color color = item.SpriteColor;
|
||||
if (brokenSprite == null)
|
||||
{
|
||||
//broken doors turn black if no broken sprite has been configured
|
||||
color *= (item.Condition / item.Prefab.Health);
|
||||
color.A = 255;
|
||||
}
|
||||
|
||||
if (stuck > 0.0f && weldedSprite != null)
|
||||
{
|
||||
Vector2 weldSpritePos = new Vector2(item.Rect.Center.X, item.Rect.Y - item.Rect.Height / 2.0f) + shakePos;
|
||||
if (item.Submarine != null) weldSpritePos += item.Submarine.DrawPosition;
|
||||
weldSpritePos.Y = -weldSpritePos.Y;
|
||||
|
||||
weldedSprite.Draw(spriteBatch,
|
||||
weldSpritePos, item.SpriteColor * (stuck / 100.0f), scale: item.Scale);
|
||||
}
|
||||
|
||||
if (openState >= 1.0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsHorizontal)
|
||||
{
|
||||
Vector2 pos = new Vector2(item.Rect.X, item.Rect.Y - item.Rect.Height / 2) + shakePos;
|
||||
if (item.Submarine != null) pos += item.Submarine.DrawPosition;
|
||||
pos.Y = -pos.Y;
|
||||
|
||||
if (brokenSprite == null || !IsBroken)
|
||||
{
|
||||
spriteBatch.Draw(doorSprite.Texture, pos,
|
||||
new Rectangle((int) (doorSprite.SourceRect.X + doorSprite.size.X * openState),
|
||||
(int) doorSprite.SourceRect.Y,
|
||||
(int) (doorSprite.size.X * (1.0f - openState)), (int) doorSprite.size.Y),
|
||||
color, 0.0f, doorSprite.Origin, item.Scale, SpriteEffects.None, doorSprite.Depth);
|
||||
}
|
||||
|
||||
if (brokenSprite != null && item.Health < item.Prefab.Health)
|
||||
{
|
||||
Vector2 scale = scaleBrokenSprite ? new Vector2(1.0f, 1.0f - item.Health / item.Prefab.Health) : Vector2.One;
|
||||
float alpha = fadeBrokenSprite ? 1.0f - item.Health / item.Prefab.Health : 1.0f;
|
||||
spriteBatch.Draw(brokenSprite.Texture, pos,
|
||||
new Rectangle((int)(brokenSprite.SourceRect.X + brokenSprite.size.X * openState), brokenSprite.SourceRect.Y,
|
||||
(int)(brokenSprite.size.X * (1.0f - openState)), (int)brokenSprite.size.Y),
|
||||
color * alpha, 0.0f, brokenSprite.Origin, scale * item.Scale, SpriteEffects.None,
|
||||
brokenSprite.Depth);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector2 pos = new Vector2(item.Rect.Center.X, item.Rect.Y) + shakePos;
|
||||
if (item.Submarine != null) pos += item.Submarine.DrawPosition;
|
||||
pos.Y = -pos.Y;
|
||||
|
||||
if (brokenSprite == null || !IsBroken)
|
||||
{
|
||||
spriteBatch.Draw(doorSprite.Texture, pos,
|
||||
new Rectangle(doorSprite.SourceRect.X,
|
||||
(int) (doorSprite.SourceRect.Y + doorSprite.size.Y * openState),
|
||||
(int) doorSprite.size.X, (int) (doorSprite.size.Y * (1.0f - openState))),
|
||||
color, 0.0f, doorSprite.Origin, item.Scale, SpriteEffects.None, doorSprite.Depth);
|
||||
}
|
||||
|
||||
if (brokenSprite != null && item.Health < item.Prefab.Health)
|
||||
{
|
||||
Vector2 scale = scaleBrokenSprite ? new Vector2(1.0f - item.Health / item.Prefab.Health, 1.0f) : Vector2.One;
|
||||
float alpha = fadeBrokenSprite ? 1.0f - item.Health / item.Prefab.Health : 1.0f;
|
||||
spriteBatch.Draw(brokenSprite.Texture, pos,
|
||||
new Rectangle(brokenSprite.SourceRect.X, (int)(brokenSprite.SourceRect.Y + brokenSprite.size.Y * openState),
|
||||
(int)brokenSprite.size.X, (int)(brokenSprite.size.Y * (1.0f - openState))),
|
||||
color * alpha, 0.0f, brokenSprite.Origin, scale * item.Scale, SpriteEffects.None, brokenSprite.Depth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
partial void OnFailedToOpen()
|
||||
{
|
||||
if (shakeTimer <= 0.0f)
|
||||
{
|
||||
PlaySound(ActionType.OnFailure);
|
||||
shake = 5.0f;
|
||||
shakeTimer = 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
partial void SetState(bool open, bool isNetworkMessage, bool sendNetworkMessage, bool forcedOpen)
|
||||
{
|
||||
if ((IsStuck && !isNetworkMessage) ||
|
||||
(PredictedState == null && isOpen == open) ||
|
||||
(PredictedState != null && isOpen == PredictedState.Value && isOpen == open))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (GameMain.Client != null && !isNetworkMessage)
|
||||
{
|
||||
bool stateChanged = open != PredictedState;
|
||||
|
||||
//clients can "predict" that the door opens/closes when a signal is received
|
||||
//the prediction will be reset after 1 second, setting the door to a state
|
||||
//sent by the server, or reverting it back to its old state if no msg from server was received
|
||||
PredictedState = open;
|
||||
resetPredictionTimer = CorrectionDelay;
|
||||
if (stateChanged) PlaySound(forcedOpen ? ActionType.OnPicked : ActionType.OnUse);
|
||||
}
|
||||
else
|
||||
{
|
||||
isOpen = open;
|
||||
if (!isNetworkMessage || open != PredictedState)
|
||||
{
|
||||
StopPicking(null);
|
||||
PlaySound(forcedOpen ? ActionType.OnPicked : ActionType.OnUse);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
|
||||
{
|
||||
base.ClientRead(type, msg, sendingTime);
|
||||
|
||||
bool open = msg.ReadBoolean();
|
||||
bool forcedOpen = msg.ReadBoolean();
|
||||
SetState(open, isNetworkMessage: true, sendNetworkMessage: false, forcedOpen: forcedOpen);
|
||||
Stuck = msg.ReadRangedSingle(0.0f, 100.0f, 8);
|
||||
UInt16 lastUserID = msg.ReadUInt16();
|
||||
Character user = lastUserID == 0 ? null : Entity.FindEntityByID(lastUserID) as Character;
|
||||
if (user != lastUser)
|
||||
{
|
||||
lastUser = user;
|
||||
toggleCooldownTimer = ToggleCoolDown;
|
||||
}
|
||||
|
||||
if (isStuck) { OpenState = 0.0f; }
|
||||
PredictedState = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class ElectricalDischarger : Powered
|
||||
{
|
||||
private static SpriteSheet electricitySprite;
|
||||
|
||||
private int frameOffset;
|
||||
|
||||
partial void InitProjSpecific()
|
||||
{
|
||||
if (electricitySprite == null)
|
||||
{
|
||||
electricitySprite = new SpriteSheet("Content/Lights/Electricity.png", 4, 4, new Vector2(0.5f, 0.0f));
|
||||
}
|
||||
}
|
||||
|
||||
partial void DischargeProjSpecific()
|
||||
{
|
||||
PlaySound(ActionType.OnUse);
|
||||
foreach (Node node in nodes)
|
||||
{
|
||||
GameMain.ParticleManager.CreateParticle("swirlysmoke", node.WorldPosition, Vector2.Zero);
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawElectricity(SpriteBatch spriteBatch)
|
||||
{
|
||||
for (int i = 0; i < nodes.Count; i++)
|
||||
{
|
||||
if (nodes[i].Length <= 1.0f) continue;
|
||||
var node = nodes[i];
|
||||
electricitySprite.Draw(spriteBatch,
|
||||
(i + frameOffset) % electricitySprite.FrameCount,
|
||||
new Vector2(node.WorldPosition.X, -node.WorldPosition.Y),
|
||||
Color.Lerp(Color.LightBlue, Color.White, Rand.Range(0.0f, 1.0f)),
|
||||
electricitySprite.Origin, -node.Angle - MathHelper.PiOver2,
|
||||
new Vector2(
|
||||
Math.Min(node.Length / electricitySprite.FrameSize.X, 1.0f) * Rand.Range(0.5f, 2.0f),
|
||||
node.Length / electricitySprite.FrameSize.Y) * Rand.Range(1.0f, 1.2f));
|
||||
}
|
||||
|
||||
if (GameMain.DebugDraw)
|
||||
{
|
||||
for (int i = 0; i < nodes.Count; i++)
|
||||
{
|
||||
if (nodes[i].Length <= 1.0f) continue;
|
||||
GUI.DrawRectangle(spriteBatch, new Vector2(nodes[i].WorldPosition.X, -nodes[i].WorldPosition.Y), Vector2.One * 5, Color.LightCyan, isFilled: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class Holdable : IDrawableComponent
|
||||
{
|
||||
public Vector2 DrawSize
|
||||
{
|
||||
get { return item.Rect.Size.ToVector2(); }
|
||||
}
|
||||
|
||||
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1)
|
||||
{
|
||||
if (!IsActive || picker == null || !CanBeAttached() || !picker.IsKeyDown(InputType.Aim) || picker != Character.Controlled) { return; }
|
||||
|
||||
Vector2 gridPos = picker.Position;
|
||||
Vector2 roundedGridPos = new Vector2(
|
||||
MathUtils.RoundTowardsClosest(picker.Position.X, Submarine.GridSize.X),
|
||||
MathUtils.RoundTowardsClosest(picker.Position.Y, Submarine.GridSize.Y));
|
||||
Vector2 attachPos = GetAttachPosition(picker);
|
||||
|
||||
if (item.Submarine == null)
|
||||
{
|
||||
Structure attachTarget = Structure.GetAttachTarget(item.WorldPosition);
|
||||
if (attachTarget != null)
|
||||
{
|
||||
if (attachTarget.Submarine != null)
|
||||
{
|
||||
//set to submarine-relative position
|
||||
gridPos += attachTarget.Submarine.Position;
|
||||
roundedGridPos += attachTarget.Submarine.Position;
|
||||
attachPos += attachTarget.Submarine.Position;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
gridPos += item.Submarine.Position;
|
||||
roundedGridPos += item.Submarine.Position;
|
||||
attachPos += item.Submarine.Position;
|
||||
}
|
||||
|
||||
Submarine.DrawGrid(spriteBatch, 14, gridPos, roundedGridPos, alpha: 0.7f);
|
||||
|
||||
item.Sprite.Draw(
|
||||
spriteBatch,
|
||||
new Vector2(attachPos.X, -attachPos.Y),
|
||||
item.SpriteColor * 0.5f,
|
||||
0.0f, item.Scale, SpriteEffects.None, 0.0f);
|
||||
|
||||
GUI.DrawRectangle(spriteBatch, new Vector2(attachPos.X - 2, -attachPos.Y - 2), Vector2.One * 5, GUI.Style.Red, thickness: 3);
|
||||
}
|
||||
|
||||
public void ClientWrite(IWriteMessage msg, object[] extraData = null)
|
||||
{
|
||||
if (!attachable || body == null) { return; }
|
||||
|
||||
Vector2 attachPos = (Vector2)extraData[2];
|
||||
msg.Write(attachPos.X);
|
||||
msg.Write(attachPos.Y);
|
||||
}
|
||||
|
||||
public override void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
|
||||
{
|
||||
base.ClientRead(type, msg, sendingTime);
|
||||
bool shouldBeAttached = msg.ReadBoolean();
|
||||
Vector2 simPosition = new Vector2(msg.ReadSingle(), msg.ReadSingle());
|
||||
|
||||
if (!attachable)
|
||||
{
|
||||
DebugConsole.ThrowError("Received an attachment event for an item that's not attachable.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (shouldBeAttached)
|
||||
{
|
||||
if (!attached)
|
||||
{
|
||||
Drop(false, null);
|
||||
item.SetTransform(simPosition, 0.0f);
|
||||
AttachToWall();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (attached)
|
||||
{
|
||||
DropConnectedWires(null);
|
||||
|
||||
if (body != null)
|
||||
{
|
||||
item.body = body;
|
||||
item.body.Enabled = true;
|
||||
}
|
||||
IsActive = false;
|
||||
|
||||
DeattachFromWall();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
using Barotrauma.Particles;
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class RangedWeapon : ItemComponent
|
||||
{
|
||||
private Sprite crosshairSprite, crosshairPointerSprite;
|
||||
|
||||
private Vector2 crosshairPos, crosshairPointerPos;
|
||||
|
||||
private float currentCrossHairScale, currentCrossHairPointerScale;
|
||||
|
||||
private readonly List<ParticleEmitter> particleEmitters = new List<ParticleEmitter>();
|
||||
|
||||
[Serialize(1.0f, false, description: "The scale of the crosshair sprite (if there is one).")]
|
||||
public float CrossHairScale
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
partial void InitProjSpecific(XElement element)
|
||||
{
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "crosshair":
|
||||
{
|
||||
string texturePath = subElement.GetAttributeString("texture", "");
|
||||
crosshairSprite = new Sprite(subElement, texturePath.Contains("/") ? "" : Path.GetDirectoryName(item.Prefab.FilePath));
|
||||
}
|
||||
break;
|
||||
case "crosshairpointer":
|
||||
{
|
||||
string texturePath = subElement.GetAttributeString("texture", "");
|
||||
crosshairPointerSprite = new Sprite(subElement, texturePath.Contains("/") ? "" : Path.GetDirectoryName(item.Prefab.FilePath));
|
||||
}
|
||||
break;
|
||||
case "particleemitter":
|
||||
particleEmitters.Add(new ParticleEmitter(subElement));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void UpdateHUD(Character character, float deltaTime, Camera cam)
|
||||
{
|
||||
currentCrossHairScale = currentCrossHairPointerScale = cam == null ? 1.0f : cam.Zoom;
|
||||
if (crosshairSprite != null)
|
||||
{
|
||||
Vector2 aimRefWorldPos = character.AimRefPosition;
|
||||
if (character.Submarine != null) { aimRefWorldPos += character.Submarine.Position; }
|
||||
Vector2 itemPos = cam.WorldToScreen(aimRefWorldPos);
|
||||
float rotation = (item.body.Dir == 1.0f) ? item.body.Rotation : item.body.Rotation - MathHelper.Pi;
|
||||
Vector2 barrelDir = new Vector2((float)Math.Cos(rotation), -(float)Math.Sin(rotation));
|
||||
|
||||
Vector2 mouseDiff = itemPos - PlayerInput.MousePosition;
|
||||
crosshairPos = new Vector2(
|
||||
MathHelper.Clamp(itemPos.X + barrelDir.X * mouseDiff.Length(), 0, GameMain.GraphicsWidth),
|
||||
MathHelper.Clamp(itemPos.Y + barrelDir.Y * mouseDiff.Length(), 0, GameMain.GraphicsHeight));
|
||||
|
||||
float spread = GetSpread(character);
|
||||
Projectile projectile = FindProjectile();
|
||||
if (projectile != null)
|
||||
{
|
||||
spread += MathHelper.ToRadians(projectile.Spread);
|
||||
}
|
||||
|
||||
float crossHairDist = Vector2.Distance(item.WorldPosition, cam.ScreenToWorld(crosshairPos));
|
||||
float spreadDist = (float)Math.Sin(spread) * crossHairDist;
|
||||
|
||||
currentCrossHairPointerScale = MathHelper.Clamp(spreadDist / Math.Min(crosshairSprite.size.X, crosshairSprite.size.Y), 0.1f, 10.0f);
|
||||
}
|
||||
currentCrossHairScale *= CrossHairScale;
|
||||
crosshairPointerPos = PlayerInput.MousePosition;
|
||||
}
|
||||
|
||||
public override void DrawHUD(SpriteBatch spriteBatch, Character character)
|
||||
{
|
||||
if (crosshairSprite == null) { return; }
|
||||
if (character == null || !character.IsKeyDown(InputType.Aim)) { return; }
|
||||
|
||||
//camera focused on some other item/device, don't draw the crosshair
|
||||
if (character.ViewTarget != null && (character.ViewTarget is Item item) && item.Prefab.FocusOnSelected) { return; }
|
||||
|
||||
GUI.HideCursor = (crosshairSprite != null || crosshairPointerSprite != null) &&
|
||||
GUI.MouseOn == null && !Inventory.IsMouseOnInventory() && !GameMain.Instance.Paused;
|
||||
if (GUI.HideCursor)
|
||||
{
|
||||
crosshairSprite?.Draw(spriteBatch, crosshairPos, Color.White, 0, currentCrossHairScale);
|
||||
crosshairPointerSprite?.Draw(spriteBatch, crosshairPointerPos, 0, currentCrossHairPointerScale);
|
||||
}
|
||||
}
|
||||
|
||||
partial void LaunchProjSpecific()
|
||||
{
|
||||
Vector2 particlePos = item.WorldPosition + ConvertUnits.ToDisplayUnits(TransformedBarrelPos);
|
||||
float rotation = -item.body.Rotation;
|
||||
if (item.body.Dir < 0.0f) { rotation += MathHelper.Pi; }
|
||||
foreach (ParticleEmitter emitter in particleEmitters)
|
||||
{
|
||||
emitter.Emit(1.0f, particlePos, hullGuess: null, angle: rotation, particleRotation: rotation);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void RemoveComponentSpecific()
|
||||
{
|
||||
crosshairSprite?.Remove();
|
||||
crosshairSprite = null;
|
||||
crosshairPointerSprite?.Remove();
|
||||
crosshairSprite = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,530 @@
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.Sounds;
|
||||
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;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
enum SoundSelectionMode
|
||||
{
|
||||
Random,
|
||||
CharacterSpecific,
|
||||
ItemSpecific,
|
||||
All
|
||||
}
|
||||
|
||||
class ItemSound
|
||||
{
|
||||
public readonly RoundSound RoundSound;
|
||||
public readonly ActionType Type;
|
||||
|
||||
public string VolumeProperty;
|
||||
|
||||
public float VolumeMultiplier
|
||||
{
|
||||
get { return RoundSound.Volume; }
|
||||
}
|
||||
|
||||
public float Range
|
||||
{
|
||||
get { return RoundSound.Range; }
|
||||
}
|
||||
|
||||
public readonly bool Loop;
|
||||
|
||||
public ItemSound(RoundSound sound, ActionType type, bool loop = false)
|
||||
{
|
||||
this.RoundSound = sound;
|
||||
this.Type = type;
|
||||
this.Loop = loop;
|
||||
}
|
||||
}
|
||||
|
||||
partial class ItemComponent : ISerializableEntity
|
||||
{
|
||||
public bool HasSounds
|
||||
{
|
||||
get { return sounds.Count > 0; }
|
||||
}
|
||||
|
||||
private bool[] hasSoundsOfType;
|
||||
private Dictionary<ActionType, List<ItemSound>> sounds;
|
||||
private Dictionary<ActionType, SoundSelectionMode> soundSelectionModes;
|
||||
|
||||
public GUILayoutSettings DefaultLayout { get; protected set; }
|
||||
public GUILayoutSettings AlternativeLayout { get; protected set; }
|
||||
|
||||
public class GUILayoutSettings
|
||||
{
|
||||
public Vector2? RelativeSize { get; private set; }
|
||||
public Point? AbsoluteSize { get; private set; }
|
||||
public Vector2? RelativeOffset { get; private set; }
|
||||
public Point? AbsoluteOffset { get; private set; }
|
||||
public Anchor? Anchor { get; private set; }
|
||||
public Pivot? Pivot { get; private set; }
|
||||
|
||||
public static GUILayoutSettings Load(XElement element)
|
||||
{
|
||||
var layout = new GUILayoutSettings();
|
||||
var relativeSize = XMLExtensions.GetAttributeVector2(element, "relativesize", Vector2.Zero);
|
||||
var absoluteSize = XMLExtensions.GetAttributePoint(element, "absolutesize", new Point(-1000, -1000));
|
||||
var relativeOffset = XMLExtensions.GetAttributeVector2(element, "relativeoffset", Vector2.Zero);
|
||||
var absoluteOffset = XMLExtensions.GetAttributePoint(element, "absoluteoffset", new Point(-1000, -1000));
|
||||
if (relativeSize.Length() > 0)
|
||||
{
|
||||
layout.RelativeSize = relativeSize;
|
||||
}
|
||||
if (absoluteSize.X > 0 && absoluteSize.Y > 0)
|
||||
{
|
||||
layout.AbsoluteSize = absoluteSize;
|
||||
}
|
||||
if (relativeOffset.Length() > 0)
|
||||
{
|
||||
layout.RelativeOffset = relativeOffset;
|
||||
}
|
||||
if (absoluteOffset.X > -1000 && absoluteOffset.Y > -1000)
|
||||
{
|
||||
layout.AbsoluteOffset = absoluteOffset;
|
||||
}
|
||||
if (Enum.TryParse(XMLExtensions.GetAttributeString(element, "anchor", ""), out Anchor a))
|
||||
{
|
||||
layout.Anchor = a;
|
||||
}
|
||||
if (Enum.TryParse(XMLExtensions.GetAttributeString(element, "pivot", ""), out Pivot p))
|
||||
{
|
||||
layout.Pivot = p;
|
||||
}
|
||||
return layout;
|
||||
}
|
||||
|
||||
public void ApplyTo(RectTransform target)
|
||||
{
|
||||
if (RelativeOffset.HasValue)
|
||||
{
|
||||
target.RelativeOffset = RelativeOffset.Value;
|
||||
}
|
||||
else if (AbsoluteOffset.HasValue)
|
||||
{
|
||||
target.AbsoluteOffset = AbsoluteOffset.Value;
|
||||
}
|
||||
if (RelativeSize.HasValue)
|
||||
{
|
||||
target.RelativeSize = RelativeSize.Value;
|
||||
}
|
||||
else if (AbsoluteSize.HasValue)
|
||||
{
|
||||
target.NonScaledSize = AbsoluteSize.Value;
|
||||
}
|
||||
if (Anchor.HasValue)
|
||||
{
|
||||
target.Anchor = Anchor.Value;
|
||||
}
|
||||
if (Pivot.HasValue)
|
||||
{
|
||||
target.Pivot = Pivot.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
target.Pivot = RectTransform.MatchPivotToAnchor(target.Anchor);
|
||||
}
|
||||
target.RecalculateChildren(true, true);
|
||||
}
|
||||
}
|
||||
|
||||
public GUIFrame GuiFrame { get; protected set; }
|
||||
|
||||
[Serialize(false, false)]
|
||||
public bool AllowUIOverlap
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
private ItemComponent linkToUIComponent;
|
||||
[Serialize("", false)]
|
||||
public string LinkUIToComponent
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0, false)]
|
||||
public int HudPriority
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
private bool useAlternativeLayout;
|
||||
public bool UseAlternativeLayout
|
||||
{
|
||||
get { return useAlternativeLayout; }
|
||||
set
|
||||
{
|
||||
if (AlternativeLayout != null)
|
||||
{
|
||||
if (value == useAlternativeLayout) { return; }
|
||||
useAlternativeLayout = value;
|
||||
if (useAlternativeLayout)
|
||||
{
|
||||
AlternativeLayout?.ApplyTo(GuiFrame.RectTransform);
|
||||
}
|
||||
else
|
||||
{
|
||||
DefaultLayout?.ApplyTo(GuiFrame.RectTransform);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private bool shouldMuffleLooping;
|
||||
private float lastMuffleCheckTime;
|
||||
private ItemSound loopingSound;
|
||||
private SoundChannel loopingSoundChannel;
|
||||
private List<SoundChannel> playingOneshotSoundChannels = new List<SoundChannel>();
|
||||
|
||||
public void UpdateSounds()
|
||||
{
|
||||
if (!isActive || item.Condition <= 0.0f)
|
||||
{
|
||||
StopSounds(ActionType.OnActive);
|
||||
}
|
||||
|
||||
if (loopingSound != null && loopingSoundChannel != null && loopingSoundChannel.IsPlaying)
|
||||
{
|
||||
if (Timing.TotalTime > lastMuffleCheckTime + 0.2f)
|
||||
{
|
||||
shouldMuffleLooping = SoundPlayer.ShouldMuffleSound(Character.Controlled, item.WorldPosition, loopingSound.Range, Character.Controlled?.CurrentHull);
|
||||
lastMuffleCheckTime = (float)Timing.TotalTime;
|
||||
}
|
||||
loopingSoundChannel.Muffled = shouldMuffleLooping;
|
||||
float targetGain = GetSoundVolume(loopingSound);
|
||||
float gainDiff = targetGain - loopingSoundChannel.Gain;
|
||||
loopingSoundChannel.Gain += Math.Abs(gainDiff) < 0.1f ? gainDiff : Math.Sign(gainDiff) * 0.1f;
|
||||
loopingSoundChannel.Position = new Vector3(item.WorldPosition, 0.0f);
|
||||
}
|
||||
for (int i = 0; i < playingOneshotSoundChannels.Count; i++)
|
||||
{
|
||||
if (!playingOneshotSoundChannels[i].IsPlaying)
|
||||
{
|
||||
playingOneshotSoundChannels[i].Dispose();
|
||||
playingOneshotSoundChannels[i] = null;
|
||||
}
|
||||
}
|
||||
playingOneshotSoundChannels.RemoveAll(ch => ch == null);
|
||||
foreach (SoundChannel channel in playingOneshotSoundChannels)
|
||||
{
|
||||
channel.Position = new Vector3(item.WorldPosition, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
public void PlaySound(ActionType type, Character user = null)
|
||||
{
|
||||
if (!hasSoundsOfType[(int)type]) { return; }
|
||||
|
||||
if (loopingSound != null)
|
||||
{
|
||||
float targetGain = 0.0f;
|
||||
if (Vector3.DistanceSquared(GameMain.SoundManager.ListenerPosition, new Vector3(item.WorldPosition, 0.0f)) > loopingSound.Range * loopingSound.Range ||
|
||||
(targetGain = GetSoundVolume(loopingSound)) <= 0.0001f)
|
||||
{
|
||||
if (loopingSoundChannel != null)
|
||||
{
|
||||
loopingSoundChannel.FadeOutAndDispose(); loopingSoundChannel = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (loopingSoundChannel != null && loopingSoundChannel.Sound != loopingSound.RoundSound.Sound)
|
||||
{
|
||||
loopingSoundChannel.FadeOutAndDispose(); loopingSoundChannel = null;
|
||||
}
|
||||
if (loopingSoundChannel == null || !loopingSoundChannel.IsPlaying)
|
||||
{
|
||||
loopingSoundChannel = loopingSound.RoundSound.Sound.Play(
|
||||
new Vector3(item.WorldPosition, 0.0f),
|
||||
0.01f,
|
||||
SoundPlayer.ShouldMuffleSound(Character.Controlled, item.WorldPosition, loopingSound.Range, Character.Controlled?.CurrentHull));
|
||||
loopingSoundChannel.Looping = true;
|
||||
//TODO: tweak
|
||||
loopingSoundChannel.Near = loopingSound.Range * 0.4f;
|
||||
loopingSoundChannel.Far = loopingSound.Range;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ItemSound itemSound = null;
|
||||
var matchingSounds = sounds[type];
|
||||
if (loopingSoundChannel == null || !loopingSoundChannel.IsPlaying)
|
||||
{
|
||||
SoundSelectionMode soundSelectionMode = soundSelectionModes[type];
|
||||
int index;
|
||||
if (soundSelectionMode == SoundSelectionMode.CharacterSpecific && user != null)
|
||||
{
|
||||
index = user.ID % matchingSounds.Count;
|
||||
}
|
||||
else if (soundSelectionMode == SoundSelectionMode.ItemSpecific)
|
||||
{
|
||||
index = item.ID % matchingSounds.Count;
|
||||
}
|
||||
else if (soundSelectionMode == SoundSelectionMode.All)
|
||||
{
|
||||
foreach (ItemSound sound in matchingSounds)
|
||||
{
|
||||
PlaySound(sound, item.WorldPosition, user);
|
||||
}
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
index = Rand.Int(matchingSounds.Count);
|
||||
}
|
||||
|
||||
itemSound = matchingSounds[index];
|
||||
PlaySound(matchingSounds[index], item.WorldPosition, user);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void PlaySound(ItemSound itemSound, Vector2 position, Character user = null)
|
||||
{
|
||||
if (Vector2.DistanceSquared(new Vector2(GameMain.SoundManager.ListenerPosition.X, GameMain.SoundManager.ListenerPosition.Y), position) > itemSound.Range * itemSound.Range)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (itemSound.Loop)
|
||||
{
|
||||
loopingSound = itemSound;
|
||||
if (loopingSoundChannel != null && loopingSoundChannel.Sound != loopingSound.RoundSound.Sound)
|
||||
{
|
||||
loopingSoundChannel.FadeOutAndDispose(); loopingSoundChannel = null;
|
||||
}
|
||||
if (loopingSoundChannel == null || !loopingSoundChannel.IsPlaying)
|
||||
{
|
||||
float volume = GetSoundVolume(itemSound);
|
||||
if (volume <= 0.0001f) { return; }
|
||||
loopingSoundChannel = loopingSound.RoundSound.Sound.Play(
|
||||
new Vector3(position.X, position.Y, 0.0f),
|
||||
0.01f,
|
||||
muffle: SoundPlayer.ShouldMuffleSound(Character.Controlled, position, loopingSound.Range, Character.Controlled?.CurrentHull));
|
||||
loopingSoundChannel.Looping = true;
|
||||
//TODO: tweak
|
||||
loopingSoundChannel.Near = loopingSound.Range * 0.4f;
|
||||
loopingSoundChannel.Far = loopingSound.Range;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
float volume = GetSoundVolume(itemSound);
|
||||
if (volume <= 0.0001f) { return; }
|
||||
var channel = SoundPlayer.PlaySound(itemSound.RoundSound.Sound, position, volume, itemSound.Range, item.CurrentHull);
|
||||
if (channel != null) { playingOneshotSoundChannels.Add(channel); }
|
||||
}
|
||||
}
|
||||
|
||||
public void StopSounds(ActionType type)
|
||||
{
|
||||
if (loopingSound == null) { return; }
|
||||
|
||||
if (loopingSound.Type != type) { return; }
|
||||
|
||||
if (loopingSoundChannel != null)
|
||||
{
|
||||
loopingSoundChannel.FadeOutAndDispose();
|
||||
loopingSoundChannel = null;
|
||||
loopingSound = null;
|
||||
}
|
||||
}
|
||||
|
||||
private float GetSoundVolume(ItemSound sound)
|
||||
{
|
||||
if (sound == null) { return 0.0f; }
|
||||
if (sound.VolumeProperty == "") { return sound.VolumeMultiplier; }
|
||||
|
||||
if (SerializableProperties.TryGetValue(sound.VolumeProperty, out SerializableProperty property))
|
||||
{
|
||||
float newVolume = 0.0f;
|
||||
try
|
||||
{
|
||||
newVolume = (float)property.GetValue(this);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
newVolume *= sound.VolumeMultiplier;
|
||||
|
||||
if (!MathUtils.IsValid(newVolume))
|
||||
{
|
||||
DebugConsole.Log("Invalid sound volume (item " + item.Name + ", " + GetType().ToString() + "): " + newVolume);
|
||||
GameAnalyticsManager.AddErrorEventOnce(
|
||||
"ItemComponent.PlaySound:" + item.Name + GetType().ToString(),
|
||||
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
|
||||
"Invalid sound volume (item " + item.Name + ", " + GetType().ToString() + "): " + newVolume);
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return MathHelper.Clamp(newVolume, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
public virtual bool ShouldDrawHUD(Character character)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public ItemComponent GetLinkUIToComponent()
|
||||
{
|
||||
if (string.IsNullOrEmpty(LinkUIToComponent))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
foreach (ItemComponent component in item.Components)
|
||||
{
|
||||
if (component.name.ToLower() == LinkUIToComponent.ToLower())
|
||||
{
|
||||
linkToUIComponent = component;
|
||||
}
|
||||
}
|
||||
if (linkToUIComponent == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to link the component \"" + Name + "\" to \"" + LinkUIToComponent + "\" in the item \"" + item.Name + "\" - component with a matching name not found.");
|
||||
}
|
||||
return linkToUIComponent;
|
||||
}
|
||||
|
||||
public virtual void DrawHUD(SpriteBatch spriteBatch, Character character) { }
|
||||
|
||||
public virtual void AddToGUIUpdateList()
|
||||
{
|
||||
GuiFrame?.AddToGUIUpdateList();
|
||||
}
|
||||
|
||||
public virtual void UpdateHUD(Character character, float deltaTime, Camera cam) { }
|
||||
|
||||
public virtual void CreateEditingHUD(SerializableEntityEditor editor)
|
||||
{
|
||||
}
|
||||
|
||||
private bool LoadElemProjSpecific(XElement subElement)
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "guiframe":
|
||||
if (subElement.Attribute("rect") != null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in item config \"" + item.ConfigFile + "\" - GUIFrame defined as rect, use RectTransform instead.");
|
||||
break;
|
||||
}
|
||||
|
||||
Color? color = null;
|
||||
if (subElement.Attribute("color") != null) color = subElement.GetAttributeColor("color", Color.White);
|
||||
string style = subElement.Attribute("style") == null ?
|
||||
null : subElement.GetAttributeString("style", "");
|
||||
|
||||
GuiFrame = new GUIFrame(RectTransform.Load(subElement, GUI.Canvas, Anchor.Center), style, color);
|
||||
DefaultLayout = GUILayoutSettings.Load(subElement);
|
||||
break;
|
||||
case "alternativelayout":
|
||||
AlternativeLayout = GUILayoutSettings.Load(subElement);
|
||||
break;
|
||||
case "itemsound":
|
||||
case "sound":
|
||||
string filePath = subElement.GetAttributeString("file", "");
|
||||
|
||||
if (filePath == "") filePath = subElement.GetAttributeString("sound", "");
|
||||
|
||||
if (filePath == "")
|
||||
{
|
||||
DebugConsole.ThrowError("Error when instantiating item \"" + item.Name + "\" - sound with no file path set");
|
||||
break;
|
||||
}
|
||||
|
||||
if (!filePath.Contains("/") && !filePath.Contains("\\") && !filePath.Contains(Path.DirectorySeparatorChar))
|
||||
{
|
||||
filePath = Path.Combine(Path.GetDirectoryName(item.Prefab.FilePath), filePath);
|
||||
}
|
||||
|
||||
ActionType type;
|
||||
try
|
||||
{
|
||||
type = (ActionType)Enum.Parse(typeof(ActionType), subElement.GetAttributeString("type", ""), true);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Invalid sound type in " + subElement + "!", e);
|
||||
break;
|
||||
}
|
||||
|
||||
RoundSound sound = Submarine.LoadRoundSound(subElement);
|
||||
if (sound == null) { break; }
|
||||
ItemSound itemSound = new ItemSound(sound, type, subElement.GetAttributeBool("loop", false))
|
||||
{
|
||||
VolumeProperty = subElement.GetAttributeString("volumeproperty", "").ToLowerInvariant()
|
||||
};
|
||||
|
||||
if (soundSelectionModes == null) soundSelectionModes = new Dictionary<ActionType, SoundSelectionMode>();
|
||||
if (!soundSelectionModes.ContainsKey(type) || soundSelectionModes[type] == SoundSelectionMode.Random)
|
||||
{
|
||||
SoundSelectionMode selectionMode = SoundSelectionMode.Random;
|
||||
Enum.TryParse(subElement.GetAttributeString("selectionmode", "Random"), out selectionMode);
|
||||
soundSelectionModes[type] = selectionMode;
|
||||
}
|
||||
|
||||
List<ItemSound> soundList = null;
|
||||
if (!sounds.TryGetValue(itemSound.Type, out soundList))
|
||||
{
|
||||
soundList = new List<ItemSound>();
|
||||
sounds.Add(itemSound.Type, soundList);
|
||||
hasSoundsOfType[(int)itemSound.Type] = true;
|
||||
}
|
||||
|
||||
soundList.Add(itemSound);
|
||||
break;
|
||||
default:
|
||||
return false; //unknown element
|
||||
}
|
||||
return true; //element processed
|
||||
}
|
||||
|
||||
//Starts a coroutine that will read the correct state of the component from the NetBuffer when correctionTimer reaches zero.
|
||||
protected void StartDelayedCorrection(ServerNetObject type, IReadMessage buffer, float sendingTime, bool waitForMidRoundSync = false)
|
||||
{
|
||||
if (delayedCorrectionCoroutine != null) CoroutineManager.StopCoroutines(delayedCorrectionCoroutine);
|
||||
|
||||
delayedCorrectionCoroutine = CoroutineManager.StartCoroutine(DoDelayedCorrection(type, buffer, sendingTime, waitForMidRoundSync));
|
||||
}
|
||||
|
||||
private IEnumerable<object> DoDelayedCorrection(ServerNetObject type, IReadMessage buffer, float sendingTime, bool waitForMidRoundSync)
|
||||
{
|
||||
while (GameMain.Client != null &&
|
||||
(correctionTimer > 0.0f || (waitForMidRoundSync && GameMain.Client.MidRoundSyncing)))
|
||||
{
|
||||
correctionTimer -= CoroutineManager.DeltaTime;
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
if (item.Removed || GameMain.Client == null)
|
||||
{
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
|
||||
((IServerSerializable)this).ClientRead(type, buffer, sendingTime);
|
||||
|
||||
correctionTimer = 0.0f;
|
||||
delayedCorrectionCoroutine = null;
|
||||
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class ItemContainer : ItemComponent, IDrawableComponent
|
||||
{
|
||||
private Sprite inventoryTopSprite;
|
||||
private Sprite inventoryBackSprite;
|
||||
private Sprite inventoryBottomSprite;
|
||||
|
||||
private GUICustomComponent guiCustomComponent;
|
||||
|
||||
private Point prevResolution;
|
||||
|
||||
public Sprite InventoryTopSprite
|
||||
{
|
||||
get { return inventoryTopSprite; }
|
||||
}
|
||||
public Sprite InventoryBackSprite
|
||||
{
|
||||
get { return inventoryBackSprite; }
|
||||
}
|
||||
public Sprite InventoryBottomSprite
|
||||
{
|
||||
get { return inventoryBottomSprite; }
|
||||
}
|
||||
|
||||
public Sprite ContainedStateIndicator
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
[Editable]
|
||||
#endif
|
||||
[Serialize("0.0,0.0", false, description: "The position where the contained items get drawn at (offset from the upper left corner of the sprite in pixels).")]
|
||||
public Vector2 ItemPos { get; set; }
|
||||
|
||||
#if DEBUG
|
||||
[Editable]
|
||||
#endif
|
||||
[Serialize("0.0,0.0", false, description: "The interval at which the contained items are spaced apart from each other (in pixels).")]
|
||||
public Vector2 ItemInterval { get; set; }
|
||||
[Serialize(100, false, description: "How many items are placed in a row before starting a new row.")]
|
||||
public int ItemsPerRow { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Depth at which the contained sprites are drawn. If not set, the original depth of the item sprites is used.
|
||||
/// </summary>
|
||||
[Serialize(-1.0f, false, description: "Depth at which the contained sprites are drawn. If not set, the original depth of the item sprites is used.")]
|
||||
public float ContainedSpriteDepth { get; set; }
|
||||
|
||||
[Serialize(null, false, description: "An optional text displayed above the item's inventory.")]
|
||||
public string UILabel { get; set; }
|
||||
|
||||
[Serialize(true, false, description: "Should an indicator displaying the state of the contained items be displayed on this item's inventory slot. "+
|
||||
"If this item can only contain one item, the indicator will display the condition of the contained item, otherwise it will indicate how full the item is.")]
|
||||
public bool ShowContainedStateIndicator { get; set; }
|
||||
|
||||
[Serialize(false, false, description: "If enabled, the condition of this item is displayed in the indicator that would normally show the state of the contained items." +
|
||||
" May be useful for items such as ammo boxes and magazines that spawn projectiles as needed," +
|
||||
" and use the condition to determine how many projectiles can be spawned in total.")]
|
||||
public bool ShowConditionInContainedStateIndicator
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, false, description: "Should the inventory of this item be kept open when the item is equipped by a character.")]
|
||||
public bool KeepOpenWhenEquipped { get; set; }
|
||||
[Serialize(false, false, description: "Can the inventory of this item be moved around on the screen by the player.")]
|
||||
public bool MovableFrame { get; set; }
|
||||
|
||||
public Vector2 DrawSize
|
||||
{
|
||||
//use the extents of the item as the draw size
|
||||
get { return Vector2.Zero; }
|
||||
}
|
||||
|
||||
partial void InitProjSpecific(XElement element)
|
||||
{
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "topsprite":
|
||||
inventoryTopSprite = new Sprite(subElement);
|
||||
break;
|
||||
case "backsprite":
|
||||
inventoryBackSprite = new Sprite(subElement);
|
||||
break;
|
||||
case "bottomsprite":
|
||||
inventoryBottomSprite = new Sprite(subElement);
|
||||
break;
|
||||
case "containedstateindicator":
|
||||
ContainedStateIndicator = new Sprite(subElement);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (GuiFrame == null)
|
||||
{
|
||||
//if a GUIFrame is not defined in the xml,
|
||||
//we create a full-screen frame and let the inventory position itself on it
|
||||
GuiFrame = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas), style: null)
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
guiCustomComponent = new GUICustomComponent(new RectTransform(Vector2.One, GuiFrame.RectTransform),
|
||||
onDraw: (SpriteBatch spriteBatch, GUICustomComponent component) => { Inventory.Draw(spriteBatch); },
|
||||
onUpdate: null)
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
//if a GUIFrame has been defined, draw the inventory inside it
|
||||
CreateGUI();
|
||||
prevResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateGUI()
|
||||
{
|
||||
var content = new GUIFrame(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center) { AbsoluteOffset = GUIStyle.ItemFrameOffset },
|
||||
style: null)
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
|
||||
string labelText = GetUILabel();
|
||||
GUITextBlock label = null;
|
||||
if (!string.IsNullOrEmpty(labelText))
|
||||
{
|
||||
label = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform, Anchor.TopCenter),
|
||||
labelText, font: GUI.SubHeadingFont, textAlignment: Alignment.Center, wrap: true);
|
||||
}
|
||||
|
||||
float minInventoryAreaSize = 0.5f;
|
||||
guiCustomComponent = new GUICustomComponent(
|
||||
new RectTransform(new Vector2(1.0f, label == null ? 1.0f : Math.Max(1.0f - label.RectTransform.RelativeSize.Y, minInventoryAreaSize)), content.RectTransform, Anchor.BottomCenter),
|
||||
onDraw: (SpriteBatch spriteBatch, GUICustomComponent component) => { Inventory.Draw(spriteBatch); },
|
||||
onUpdate: null)
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
Inventory.RectTransform = guiCustomComponent.RectTransform;
|
||||
}
|
||||
|
||||
public string GetUILabel()
|
||||
{
|
||||
if (UILabel == string.Empty) { return string.Empty; }
|
||||
if (UILabel != null)
|
||||
{
|
||||
return TextManager.Get("UILabel." + UILabel);
|
||||
}
|
||||
else
|
||||
{
|
||||
return item?.Name;
|
||||
}
|
||||
}
|
||||
|
||||
public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1)
|
||||
{
|
||||
if (hideItems || (item.body != null && !item.body.Enabled)) { return; }
|
||||
|
||||
if ((prevResolution.X > 0 && prevResolution.Y > 0) &&
|
||||
(prevResolution.X != GameMain.GraphicsWidth || prevResolution.Y != GameMain.GraphicsHeight))
|
||||
{
|
||||
GuiFrame.ClearChildren();
|
||||
CreateGUI();
|
||||
|
||||
prevResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
|
||||
}
|
||||
|
||||
DrawContainedItems(spriteBatch, itemDepth);
|
||||
}
|
||||
|
||||
public void DrawContainedItems(SpriteBatch spriteBatch, float itemDepth)
|
||||
{
|
||||
Vector2 transformedItemPos = ItemPos * item.Scale;
|
||||
Vector2 transformedItemInterval = ItemInterval * item.Scale;
|
||||
|
||||
if (item.body == null)
|
||||
{
|
||||
if (item.FlippedX)
|
||||
{
|
||||
transformedItemPos.X = -transformedItemPos.X;
|
||||
transformedItemPos.X += item.Rect.Width;
|
||||
transformedItemInterval.X = -transformedItemInterval.X;
|
||||
}
|
||||
if (item.FlippedY)
|
||||
{
|
||||
transformedItemPos.Y = -transformedItemPos.Y;
|
||||
transformedItemPos.Y -= item.Rect.Height;
|
||||
transformedItemInterval.Y = -transformedItemInterval.Y;
|
||||
}
|
||||
transformedItemPos += new Vector2(item.Rect.X, item.Rect.Y);
|
||||
if (item.Submarine != null) { transformedItemPos += item.Submarine.DrawPosition; }
|
||||
}
|
||||
else
|
||||
{
|
||||
Matrix transform = Matrix.CreateRotationZ(item.body.Rotation);
|
||||
if (item.body.Dir == -1.0f)
|
||||
{
|
||||
transformedItemPos.X = -transformedItemPos.X;
|
||||
transformedItemInterval.X = -transformedItemInterval.X;
|
||||
}
|
||||
transformedItemPos = Vector2.Transform(transformedItemPos, transform);
|
||||
transformedItemInterval = Vector2.Transform(transformedItemInterval, transform);
|
||||
|
||||
transformedItemPos += item.DrawPosition;
|
||||
}
|
||||
|
||||
Vector2 currentItemPos = transformedItemPos;
|
||||
|
||||
SpriteEffects spriteEffects = SpriteEffects.None;
|
||||
if ((item.body != null && item.body.Dir == -1) || item.FlippedX) { spriteEffects |= SpriteEffects.FlipHorizontally; }
|
||||
if (item.FlippedY) { spriteEffects |= SpriteEffects.FlipVertically; }
|
||||
|
||||
int i = 0;
|
||||
foreach (Item containedItem in Inventory.Items)
|
||||
{
|
||||
if (containedItem == null) continue;
|
||||
|
||||
if (AutoInteractWithContained)
|
||||
{
|
||||
containedItem.IsHighlighted = item.IsHighlighted;
|
||||
item.IsHighlighted = false;
|
||||
}
|
||||
|
||||
Vector2 origin = containedItem.Sprite.Origin;
|
||||
if (item.FlippedX) { origin.X = containedItem.Sprite.SourceRect.Width - origin.X; }
|
||||
if (item.FlippedY) { origin.Y = containedItem.Sprite.SourceRect.Height - origin.Y; }
|
||||
|
||||
float containedSpriteDepth = ContainedSpriteDepth < 0.0f ? containedItem.Sprite.Depth : ContainedSpriteDepth;
|
||||
containedSpriteDepth = itemDepth + (containedSpriteDepth - item.SpriteDepth) / 10000.0f;
|
||||
|
||||
containedItem.Sprite.Draw(
|
||||
spriteBatch,
|
||||
new Vector2(currentItemPos.X, -currentItemPos.Y),
|
||||
containedItem.GetSpriteColor(),
|
||||
origin,
|
||||
-(containedItem.body == null ? 0.0f : containedItem.body.DrawRotation),
|
||||
containedItem.Scale,
|
||||
spriteEffects,
|
||||
depth: containedSpriteDepth);
|
||||
|
||||
foreach (ItemContainer ic in containedItem.GetComponents<ItemContainer>())
|
||||
{
|
||||
if (ic.hideItems) continue;
|
||||
ic.DrawContainedItems(spriteBatch, containedSpriteDepth);
|
||||
}
|
||||
|
||||
i++;
|
||||
if (Math.Abs(ItemInterval.X) > 0.001f && Math.Abs(ItemInterval.Y) > 0.001f)
|
||||
{
|
||||
//interval set on both axes -> use a grid layout
|
||||
currentItemPos.X += transformedItemInterval.X;
|
||||
if (i % ItemsPerRow == 0)
|
||||
{
|
||||
currentItemPos.X = transformedItemPos.X;
|
||||
currentItemPos.Y += transformedItemInterval.Y;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
currentItemPos += transformedItemInterval;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void UpdateHUD(Character character, float deltaTime, Camera cam)
|
||||
{
|
||||
if (Inventory.RectTransform != null)
|
||||
{
|
||||
guiCustomComponent.RectTransform.Parent = Inventory.RectTransform;
|
||||
}
|
||||
|
||||
//if the item is in the character's inventory, no need to update the item's inventory
|
||||
//because the player can see it by hovering the cursor over the item
|
||||
guiCustomComponent.Visible = item.ParentInventory?.Owner != character && DrawInventory;
|
||||
if (!guiCustomComponent.Visible) return;
|
||||
|
||||
Inventory.Update(deltaTime, cam);
|
||||
}
|
||||
|
||||
/*public override void DrawHUD(SpriteBatch spriteBatch, Character character)
|
||||
{
|
||||
//if the item is in the character's inventory, no need to draw the item's inventory
|
||||
//because the player can see it by hovering the cursor over the item
|
||||
if (item.ParentInventory?.Owner == character || !DrawInventory) return;
|
||||
|
||||
Inventory.Draw(spriteBatch);
|
||||
}*/
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class ItemLabel : ItemComponent, IDrawableComponent
|
||||
{
|
||||
private GUITextBlock textBlock;
|
||||
|
||||
private Color textColor;
|
||||
|
||||
private float scrollAmount;
|
||||
private string scrollingText;
|
||||
private float scrollPadding;
|
||||
private int scrollIndex;
|
||||
private bool needsScrolling;
|
||||
|
||||
private float[] charWidths;
|
||||
|
||||
[Serialize("0,0,0,0", true, description: "The amount of padding around the text in pixels (left,top,right,bottom). ")]
|
||||
public Vector4 Padding
|
||||
{
|
||||
get { return TextBlock.Padding; }
|
||||
set { TextBlock.Padding = value; }
|
||||
}
|
||||
|
||||
private string text;
|
||||
[Serialize("", true, translationTextTag: "Label.", description: "The text displayed in the label."), Editable(100)]
|
||||
public string Text
|
||||
{
|
||||
get { return text; }
|
||||
set
|
||||
{
|
||||
if (value == text || item.Rect.Width < 5) return;
|
||||
|
||||
if (TextBlock.Rect.Width != item.Rect.Width || textBlock.Rect.Height != item.Rect.Height)
|
||||
{
|
||||
textBlock = null;
|
||||
}
|
||||
|
||||
text = value;
|
||||
DisplayText = TextManager.Get(text, returnNull: true) ?? value;
|
||||
TextBlock.Text = DisplayText;
|
||||
if (Screen.Selected == GameMain.SubEditorScreen && Scrollable)
|
||||
{
|
||||
TextBlock.Text = ToolBox.LimitString(DisplayText, textBlock.Font, item.Rect.Width);
|
||||
}
|
||||
SetScrollingText();
|
||||
}
|
||||
}
|
||||
|
||||
public string DisplayText
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Editable, Serialize("0,0,0,255", true, description: "The color of the text displayed on the label (R,G,B,A).")]
|
||||
public Color TextColor
|
||||
{
|
||||
get { return textColor; }
|
||||
set
|
||||
{
|
||||
if (textBlock != null) textBlock.TextColor = value;
|
||||
textColor = value;
|
||||
}
|
||||
}
|
||||
|
||||
[Editable(0.0f, 10.0f), Serialize(1.0f, true, description: "The scale of the text displayed on the label.")]
|
||||
public float TextScale
|
||||
{
|
||||
get { return textBlock == null ? 1.0f : textBlock.TextScale; }
|
||||
set
|
||||
{
|
||||
if (textBlock != null) textBlock.TextScale = MathHelper.Clamp(value, 0.1f, 10.0f);
|
||||
}
|
||||
}
|
||||
|
||||
private bool scrollable;
|
||||
[Serialize(false, true, description: "Should the text scroll horizontally across the item if it's too long to be displayed all at once.")]
|
||||
public bool Scrollable
|
||||
{
|
||||
get { return scrollable; }
|
||||
set
|
||||
{
|
||||
scrollable = value;
|
||||
IsActive = value;
|
||||
TextBlock.Wrap = !scrollable;
|
||||
TextBlock.TextAlignment = scrollable ? Alignment.CenterLeft : Alignment.Center;
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(20.0f, true, description: "How fast the text scrolls across the item (only valid if Scrollable is set to true).")]
|
||||
public float ScrollSpeed
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
private GUITextBlock TextBlock
|
||||
{
|
||||
get
|
||||
{
|
||||
if (textBlock == null)
|
||||
{
|
||||
textBlock = new GUITextBlock(new RectTransform(item.Rect.Size), "",
|
||||
textColor: textColor, font: GUI.UnscaledSmallFont, textAlignment: Alignment.Center, wrap: true, style: null)
|
||||
{
|
||||
TextDepth = item.SpriteDepth - 0.00001f,
|
||||
RoundToNearestPixel = false,
|
||||
TextScale = TextScale
|
||||
};
|
||||
}
|
||||
return textBlock;
|
||||
}
|
||||
}
|
||||
|
||||
public ItemLabel(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
}
|
||||
|
||||
private void SetScrollingText()
|
||||
{
|
||||
if (!scrollable) return;
|
||||
|
||||
float totalWidth = textBlock.Font.MeasureString(DisplayText).X;
|
||||
float textAreaWidth = Math.Max(textBlock.Rect.Width - textBlock.Padding.X - textBlock.Padding.Z, 0);
|
||||
if (totalWidth >= textAreaWidth)
|
||||
{
|
||||
//add enough spaces to fill the rect
|
||||
//(so the text can scroll entirely out of view before we reset it back to start)
|
||||
needsScrolling = true;
|
||||
float spaceWidth = textBlock.Font.MeasureChar(' ').X;
|
||||
scrollingText = new string(' ', (int)Math.Ceiling(textAreaWidth / spaceWidth)) + DisplayText;
|
||||
}
|
||||
else
|
||||
{
|
||||
//whole text can fit in the textblock, no need to scroll
|
||||
needsScrolling = false;
|
||||
scrollingText = DisplayText;
|
||||
scrollAmount = 0.0f;
|
||||
scrollIndex = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
//calculate character widths
|
||||
scrollPadding = 0;
|
||||
charWidths = new float[scrollingText.Length];
|
||||
for (int i = 0; i < scrollingText.Length; i++)
|
||||
{
|
||||
float charWidth = TextBlock.Font.MeasureChar(scrollingText[i]).X;
|
||||
scrollPadding = Math.Max(charWidth, scrollPadding);
|
||||
charWidths[i] = charWidth;
|
||||
}
|
||||
|
||||
scrollIndex = MathHelper.Clamp(scrollIndex, 0, DisplayText.Length);
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
if (!scrollable) return;
|
||||
|
||||
if (scrollingText == null)
|
||||
{
|
||||
SetScrollingText();
|
||||
}
|
||||
|
||||
if (!needsScrolling) return;
|
||||
|
||||
scrollAmount -= deltaTime * ScrollSpeed;
|
||||
|
||||
float currLength = 0;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
float textAreaWidth = textBlock.Rect.Width - textBlock.Padding.X - textBlock.Padding.Z;
|
||||
for (int i = scrollIndex; i < scrollingText.Length; i++)
|
||||
{
|
||||
//first character is out of view -> skip to next character
|
||||
if (i == scrollIndex && scrollAmount < -charWidths[i])
|
||||
{
|
||||
scrollIndex++;
|
||||
scrollAmount = 0;
|
||||
if (scrollIndex >= scrollingText.Length) //reached the last character, reset
|
||||
{
|
||||
scrollIndex = 0;
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
//reached the right edge, stop adding more character
|
||||
if (scrollAmount + (currLength + charWidths[i] + scrollPadding) >= textAreaWidth)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
currLength += charWidths[i];
|
||||
sb.Append(scrollingText[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TextBlock.Text = sb.ToString();
|
||||
}
|
||||
|
||||
public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1)
|
||||
{
|
||||
var drawPos = new Vector2(
|
||||
item.DrawPosition.X - item.Rect.Width / 2.0f,
|
||||
-(item.DrawPosition.Y + item.Rect.Height / 2.0f));
|
||||
|
||||
Rectangle worldRect = item.WorldRect;
|
||||
if (worldRect.X > Screen.Selected.Cam.WorldView.Right ||
|
||||
worldRect.Right < Screen.Selected.Cam.WorldView.X ||
|
||||
worldRect.Y < Screen.Selected.Cam.WorldView.Y - Screen.Selected.Cam.WorldView.Height ||
|
||||
worldRect.Y - worldRect.Height > Screen.Selected.Cam.WorldView.Y)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
textBlock.TextDepth = item.SpriteDepth - 0.0001f;
|
||||
textBlock.TextOffset = drawPos - textBlock.Rect.Location.ToVector2() + new Vector2(scrollAmount + scrollPadding, 0.0f);
|
||||
textBlock.DrawManually(spriteBatch);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class Ladder : ItemComponent, IDrawableComponent
|
||||
{
|
||||
public float BackgroundSpriteDepth
|
||||
{
|
||||
get { return item.GetDrawDepth() + 0.1f; }
|
||||
}
|
||||
|
||||
public Vector2 DrawSize
|
||||
{
|
||||
//use the extents of the item as the draw size
|
||||
get { return Vector2.Zero; }
|
||||
}
|
||||
|
||||
private Sprite backgroundSprite;
|
||||
|
||||
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1)
|
||||
{
|
||||
if (backgroundSprite == null) { return; }
|
||||
|
||||
backgroundSprite.DrawTiled(spriteBatch,
|
||||
new Vector2(item.DrawPosition.X - item.Rect.Width / 2, -(item.DrawPosition.Y + item.Rect.Height / 2)) - backgroundSprite.Origin,
|
||||
new Vector2(backgroundSprite.size.X, item.Rect.Height), color: item.Color,
|
||||
depth: BackgroundSpriteDepth);
|
||||
}
|
||||
|
||||
partial void InitProjSpecific(XElement element)
|
||||
{
|
||||
var backgroundSpriteElement = element.GetChildElement("backgroundsprite");
|
||||
if (backgroundSpriteElement != null)
|
||||
{
|
||||
backgroundSprite = new Sprite(backgroundSpriteElement);
|
||||
}
|
||||
}
|
||||
|
||||
partial void RemoveProjSpecific()
|
||||
{
|
||||
backgroundSprite?.Remove();
|
||||
backgroundSprite = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class LevelResource : ItemComponent, IServerSerializable
|
||||
{
|
||||
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
|
||||
{
|
||||
deattachTimer = msg.ReadSingle();
|
||||
if (deattachTimer >= DeattachDuration)
|
||||
{
|
||||
holdable.DeattachFromWall();
|
||||
trigger.Enabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Lights;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class LightComponent : Powered, IServerSerializable, IDrawableComponent
|
||||
{
|
||||
public Vector2 DrawSize
|
||||
{
|
||||
get { return new Vector2(light.Range * 2, light.Range * 2); }
|
||||
}
|
||||
|
||||
private LightSource light;
|
||||
public LightSource Light
|
||||
{
|
||||
get { return light; }
|
||||
}
|
||||
|
||||
public override void OnScaleChanged()
|
||||
{
|
||||
light.SpriteScale = Vector2.One * item.Scale;
|
||||
light.Position = ParentBody != null ? ParentBody.Position : item.Position;
|
||||
}
|
||||
|
||||
partial void SetLightSourceState(bool enabled, float brightness)
|
||||
{
|
||||
if (light == null) { return; }
|
||||
light.Enabled = enabled;
|
||||
light.Color = LightColor.Multiply(brightness);
|
||||
}
|
||||
|
||||
public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1)
|
||||
{
|
||||
if (light.LightSprite != null && (item.body == null || item.body.Enabled) && lightBrightness > 0.0f && IsOn)
|
||||
{
|
||||
light.LightSprite.Draw(spriteBatch, new Vector2(item.DrawPosition.X, -item.DrawPosition.Y), lightColor * lightBrightness, 0.0f, item.Scale, SpriteEffects.None, item.SpriteDepth - 0.0001f);
|
||||
}
|
||||
}
|
||||
|
||||
public override void FlipX(bool relativeToSub)
|
||||
{
|
||||
if (light?.LightSprite != null && item.Prefab.CanSpriteFlipX && item.body == null)
|
||||
{
|
||||
light.LightSpriteEffect = light.LightSpriteEffect == SpriteEffects.None ?
|
||||
SpriteEffects.FlipHorizontally : SpriteEffects.None;
|
||||
}
|
||||
}
|
||||
|
||||
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
|
||||
{
|
||||
IsOn = msg.ReadBoolean();
|
||||
}
|
||||
|
||||
protected override void RemoveComponentSpecific()
|
||||
{
|
||||
base.RemoveComponentSpecific();
|
||||
light.Remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class Controller : ItemComponent
|
||||
{
|
||||
public override void DrawHUD(SpriteBatch spriteBatch, Character character)
|
||||
{
|
||||
if (focusTarget != null && character.ViewTarget == focusTarget)
|
||||
{
|
||||
foreach (ItemComponent ic in focusTarget.Components)
|
||||
{
|
||||
ic.DrawHUD(spriteBatch, character);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool crewAreaOriginalState;
|
||||
private bool chatBoxOriginalState;
|
||||
private bool isHUDsHidden;
|
||||
|
||||
partial void HideHUDs(bool value)
|
||||
{
|
||||
if (isHUDsHidden == value) { return; }
|
||||
if (value == true)
|
||||
{
|
||||
ToggleCrewArea(false, storeOriginalState: true);
|
||||
ToggleChatBox(false, storeOriginalState: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
ToggleCrewArea(crewAreaOriginalState, storeOriginalState: false);
|
||||
ToggleChatBox(chatBoxOriginalState, storeOriginalState: false);
|
||||
}
|
||||
isHUDsHidden = value;
|
||||
}
|
||||
|
||||
private void ToggleCrewArea(bool value, bool storeOriginalState)
|
||||
{
|
||||
var crewManager = GameMain.GameSession?.CrewManager;
|
||||
if (crewManager == null) { return; }
|
||||
|
||||
if (storeOriginalState)
|
||||
{
|
||||
crewAreaOriginalState = crewManager.ToggleCrewListOpen;
|
||||
}
|
||||
crewManager.ToggleCrewListOpen = value;
|
||||
}
|
||||
|
||||
private void ToggleChatBox(bool value, bool storeOriginalState)
|
||||
{
|
||||
var crewManager = GameMain.GameSession?.CrewManager;
|
||||
if (crewManager == null) { return; }
|
||||
|
||||
if (crewManager.IsSinglePlayer)
|
||||
{
|
||||
if (crewManager.ChatBox != null)
|
||||
{
|
||||
if (storeOriginalState)
|
||||
{
|
||||
chatBoxOriginalState = crewManager.ChatBox.ToggleOpen;
|
||||
}
|
||||
crewManager.ChatBox.ToggleOpen = value;
|
||||
}
|
||||
}
|
||||
else if (GameMain.Client != null)
|
||||
{
|
||||
if (storeOriginalState)
|
||||
{
|
||||
chatBoxOriginalState = GameMain.Client.ChatBox.ToggleOpen;
|
||||
}
|
||||
GameMain.Client.ChatBox.ToggleOpen = value;
|
||||
}
|
||||
}
|
||||
|
||||
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
|
||||
{
|
||||
state = msg.ReadBoolean();
|
||||
ushort userID = msg.ReadUInt16();
|
||||
if (userID == 0)
|
||||
{
|
||||
if (user != null)
|
||||
{
|
||||
IsActive = false;
|
||||
CancelUsing(user);
|
||||
user = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Character newUser = Entity.FindEntityByID(userID) as Character;
|
||||
if (newUser != user)
|
||||
{
|
||||
CancelUsing(user);
|
||||
}
|
||||
user = newUser;
|
||||
IsActive = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class Deconstructor : Powered, IServerSerializable, IClientSerializable
|
||||
{
|
||||
public GUIButton ActivateButton
|
||||
{
|
||||
get { return activateButton; }
|
||||
}
|
||||
private GUIButton activateButton;
|
||||
private GUIComponent inputInventoryHolder, outputInventoryHolder;
|
||||
private GUICustomComponent inputInventoryOverlay;
|
||||
|
||||
private GUIComponent inSufficientPowerWarning;
|
||||
|
||||
private bool pendingState;
|
||||
|
||||
partial void InitProjSpecific(XElement element)
|
||||
{
|
||||
CreateGUI();
|
||||
GameMain.Instance.OnResolutionChanged += RecreateGUI;
|
||||
}
|
||||
|
||||
private void RecreateGUI()
|
||||
{
|
||||
GuiFrame.ClearChildren();
|
||||
CreateGUI();
|
||||
OnItemLoadedProjSpecific();
|
||||
}
|
||||
|
||||
private void CreateGUI()
|
||||
{
|
||||
var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.90f, 0.80f), GuiFrame.RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter)
|
||||
{
|
||||
Stretch = true,
|
||||
RelativeSpacing = 0.08f
|
||||
};
|
||||
|
||||
var topFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.5f), paddedFrame.RectTransform), style: null);
|
||||
|
||||
// === INPUT LABEL === //
|
||||
var inputLabelArea = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.15f), topFrame.RectTransform, Anchor.TopCenter), childAnchor: Anchor.CenterLeft, isHorizontal: true)
|
||||
{
|
||||
Stretch = true,
|
||||
RelativeSpacing = 0.05f
|
||||
};
|
||||
var inputLabel = new GUITextBlock(new RectTransform(Vector2.One, inputLabelArea.RectTransform), TextManager.Get("uilabel.input"), font: GUI.SubHeadingFont) { Padding = Vector4.Zero };
|
||||
inputLabel.RectTransform.Resize(new Point((int) inputLabel.Font.MeasureString(inputLabel.Text).X, inputLabel.RectTransform.Rect.Height));
|
||||
new GUIFrame(new RectTransform(Vector2.One, inputLabelArea.RectTransform), style: "HorizontalLine");
|
||||
|
||||
var inputArea = new GUILayoutGroup(new RectTransform(new Vector2(1f, 1f), topFrame.RectTransform, Anchor.CenterLeft), childAnchor: Anchor.BottomLeft, isHorizontal: true) { Stretch = true, RelativeSpacing = 0.05f };
|
||||
|
||||
// === INPUT SLOTS === //
|
||||
inputInventoryHolder = new GUIFrame(new RectTransform(new Vector2(0.7f, 1f), inputArea.RectTransform), style: null);
|
||||
inputInventoryOverlay = new GUICustomComponent(new RectTransform(Vector2.One, inputInventoryHolder.RectTransform), DrawOverLay, null) { CanBeFocused = false };
|
||||
|
||||
// === ACTIVATE BUTTON === //
|
||||
var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.4f, 0.7f), inputArea.RectTransform), childAnchor: Anchor.CenterLeft);
|
||||
activateButton = new GUIButton(new RectTransform(new Vector2(0.95f, 0.8f), buttonContainer.RectTransform), TextManager.Get("DeconstructorDeconstruct"), style: "DeviceButton")
|
||||
{
|
||||
TextBlock = { AutoScaleHorizontal = true },
|
||||
OnClicked = ToggleActive
|
||||
};
|
||||
inSufficientPowerWarning = new GUITextBlock(new RectTransform(Vector2.One, activateButton.RectTransform),
|
||||
TextManager.Get("DeconstructorNoPower"), textColor: GUI.Style.Orange, textAlignment: Alignment.Center, color: Color.Black, style: "OuterGlow")
|
||||
{
|
||||
HoverColor = Color.Black,
|
||||
IgnoreLayoutGroups = true,
|
||||
Visible = false,
|
||||
CanBeFocused = false
|
||||
};
|
||||
|
||||
// === OUTPUT AREA === //
|
||||
var bottomFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.5f), paddedFrame.RectTransform), style: null);
|
||||
|
||||
// === OUTPUT LABEL === //
|
||||
var outputLabelArea = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.15f), bottomFrame.RectTransform, Anchor.TopCenter), childAnchor: Anchor.CenterLeft, isHorizontal: true)
|
||||
{
|
||||
Stretch = true,
|
||||
RelativeSpacing = 0.05f
|
||||
};
|
||||
var outputLabel = new GUITextBlock(new RectTransform(new Vector2(0f, 1.0f), outputLabelArea.RectTransform), TextManager.Get("uilabel.output"), font: GUI.SubHeadingFont) { Padding = Vector4.Zero };
|
||||
outputLabel.RectTransform.Resize(new Point((int) outputLabel.Font.MeasureString(outputLabel.Text).X, outputLabel.RectTransform.Rect.Height));
|
||||
new GUIFrame(new RectTransform(Vector2.One, outputLabelArea.RectTransform), style: "HorizontalLine");
|
||||
|
||||
// === OUTPUT SLOTS === //
|
||||
outputInventoryHolder = new GUIFrame(new RectTransform(new Vector2(1f, 1f), bottomFrame.RectTransform, Anchor.CenterLeft), style: null);
|
||||
}
|
||||
|
||||
public override bool Select(Character character)
|
||||
{
|
||||
// TODO, This works fine as of now but if GUI.PreventElementOverlap ever gets fixed this block of code may become obsolete or detrimental.
|
||||
// Only do this if there's only one linked component. If you link more containers then may
|
||||
// GUI.PreventElementOverlap have mercy on your HUD layout
|
||||
if (item.linkedTo.Count(entity => entity is Item item && item.DisplaySideBySideWhenLinked) == 1)
|
||||
{
|
||||
foreach (MapEntity linkedTo in item.linkedTo)
|
||||
{
|
||||
if (!(linkedTo is Item linkedItem)) continue;
|
||||
if (!linkedItem.Components.Any()) continue;
|
||||
|
||||
var itemContainer = linkedItem.Components.First();
|
||||
if (itemContainer == null) { continue; }
|
||||
|
||||
if (!itemContainer.Item.DisplaySideBySideWhenLinked) continue;
|
||||
|
||||
// how much spacing do we want between the components
|
||||
var padding = (int) (8 * GUI.Scale);
|
||||
// Move the linked container to the right and move the fabricator to the left
|
||||
itemContainer.GuiFrame.RectTransform.AbsoluteOffset = new Point(GuiFrame.Rect.Width / -2 - padding, 0);
|
||||
GuiFrame.RectTransform.AbsoluteOffset = new Point(itemContainer.GuiFrame.Rect.Width / 2 + padding, 0);
|
||||
}
|
||||
}
|
||||
return base.Select(character);
|
||||
}
|
||||
|
||||
partial void OnItemLoadedProjSpecific()
|
||||
{
|
||||
inputContainer.AllowUIOverlap = true;
|
||||
inputContainer.Inventory.RectTransform = inputInventoryHolder.RectTransform;
|
||||
outputContainer.AllowUIOverlap = true;
|
||||
outputContainer.Inventory.RectTransform = outputInventoryHolder.RectTransform;
|
||||
}
|
||||
|
||||
private void DrawOverLay(SpriteBatch spriteBatch, GUICustomComponent overlayComponent)
|
||||
{
|
||||
overlayComponent.RectTransform.SetAsLastChild();
|
||||
var lastSlot = inputContainer.Inventory.slots.Last();
|
||||
|
||||
GUI.DrawRectangle(spriteBatch,
|
||||
new Rectangle(
|
||||
lastSlot.Rect.X, lastSlot.Rect.Y + (int)(lastSlot.Rect.Height * (1.0f - progressState)),
|
||||
lastSlot.Rect.Width, (int)(lastSlot.Rect.Height * progressState)),
|
||||
GUI.Style.Green * 0.5f, isFilled: true);
|
||||
}
|
||||
|
||||
public override void UpdateHUD(Character character, float deltaTime, Camera cam)
|
||||
{
|
||||
inSufficientPowerWarning.Visible = CurrPowerConsumption > 0 && !hasPower;
|
||||
}
|
||||
|
||||
private bool ToggleActive(GUIButton button, object obj)
|
||||
{
|
||||
if (GameMain.Client != null)
|
||||
{
|
||||
pendingState = !IsActive;
|
||||
item.CreateClientEvent(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetActive(!IsActive, Character.Controlled);
|
||||
currPowerConsumption = IsActive ? powerConsumption : 0.0f;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ClientWrite(IWriteMessage msg, object[] extraData = null)
|
||||
{
|
||||
msg.Write(pendingState);
|
||||
}
|
||||
|
||||
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
|
||||
{
|
||||
SetActive(msg.ReadBoolean());
|
||||
progressTimer = msg.ReadSingle();
|
||||
}
|
||||
|
||||
protected override void RemoveComponentSpecific()
|
||||
{
|
||||
GameMain.Instance.OnResolutionChanged -= RecreateGUI;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class Engine : Powered, IDrawableComponent
|
||||
{
|
||||
private float spriteIndex;
|
||||
|
||||
private SpriteSheet propellerSprite;
|
||||
|
||||
private GUITickBox powerIndicator;
|
||||
private GUIScrollBar forceSlider;
|
||||
private GUITickBox autoControlIndicator;
|
||||
|
||||
public float AnimSpeed
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public Vector2 DrawSize
|
||||
{
|
||||
//use the extents of the item as the draw size
|
||||
get { return Vector2.Zero; }
|
||||
}
|
||||
|
||||
partial void InitProjSpecific(XElement element)
|
||||
{
|
||||
var paddedFrame = new GUIFrame(new RectTransform(new Vector2(0.85f, 0.65f), GuiFrame.RectTransform, Anchor.Center)
|
||||
{
|
||||
RelativeOffset = new Vector2(0, 0.04f)
|
||||
}, style: null);
|
||||
|
||||
var lightsArea = new GUIFrame(new RectTransform(new Vector2(1, 0.38f), paddedFrame.RectTransform, Anchor.TopLeft), style: null);
|
||||
powerIndicator = new GUITickBox(new RectTransform(new Vector2(0.45f, 0.8f), lightsArea.RectTransform, Anchor.Center, Pivot.CenterRight)
|
||||
{
|
||||
RelativeOffset = new Vector2(-0.05f, 0)
|
||||
}, TextManager.Get("EnginePowered"), font: GUI.SubHeadingFont, style: "IndicatorLightGreen")
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
autoControlIndicator = new GUITickBox(new RectTransform(new Vector2(0.45f, 0.8f), lightsArea.RectTransform, Anchor.Center, Pivot.CenterLeft)
|
||||
{
|
||||
RelativeOffset = new Vector2(0.05f, 0)
|
||||
}, TextManager.Get("PumpAutoControl", fallBackTag: "ReactorAutoControl"), font: GUI.SubHeadingFont, style: "IndicatorLightYellow")
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
powerIndicator.TextBlock.Wrap = autoControlIndicator.TextBlock.Wrap = true;
|
||||
powerIndicator.TextBlock.OverrideTextColor(GUI.Style.TextColor);
|
||||
autoControlIndicator.TextBlock.OverrideTextColor(GUI.Style.TextColor);
|
||||
GUITextBlock.AutoScaleAndNormalize(powerIndicator.TextBlock, autoControlIndicator.TextBlock);
|
||||
|
||||
var sliderArea = new GUIFrame(new RectTransform(new Vector2(1, 0.6f), paddedFrame.RectTransform, Anchor.BottomLeft), style: null);
|
||||
string powerLabel = TextManager.Get("EngineForce");
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.3f), sliderArea.RectTransform, Anchor.TopCenter), "", textColor: GUI.Style.TextColor, font: GUI.SubHeadingFont, textAlignment: Alignment.Center)
|
||||
{
|
||||
AutoScaleHorizontal = true,
|
||||
TextGetter = () => { return TextManager.AddPunctuation(':', powerLabel, (int)(targetForce) + " %"); }
|
||||
};
|
||||
forceSlider = new GUIScrollBar(new RectTransform(new Vector2(0.95f, 0.45f), sliderArea.RectTransform, Anchor.Center), barSize: 0.1f, style: "DeviceSlider")
|
||||
{
|
||||
Step = 0.05f,
|
||||
OnMoved = (GUIScrollBar scrollBar, float barScroll) =>
|
||||
{
|
||||
float newTargetForce = barScroll * 200.0f - 100.0f;
|
||||
if (Math.Abs(newTargetForce - targetForce) < 0.01) { return false; }
|
||||
|
||||
targetForce = newTargetForce;
|
||||
|
||||
if (GameMain.Client != null)
|
||||
{
|
||||
correctionTimer = CorrectionDelay;
|
||||
item.CreateClientEvent(this);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
var textsArea = new GUIFrame(new RectTransform(new Vector2(1, 0.25f), sliderArea.RectTransform, Anchor.BottomCenter), style: null);
|
||||
var backwardsLabel = new GUITextBlock(new RectTransform(new Vector2(0.4f, 1.0f), textsArea.RectTransform, Anchor.CenterLeft), TextManager.Get("EngineBackwards"),
|
||||
textColor: GUI.Style.TextColor, font: GUI.SubHeadingFont, textAlignment: Alignment.CenterLeft);
|
||||
var forwardsLabel = new GUITextBlock(new RectTransform(new Vector2(0.4f, 1.0f), textsArea.RectTransform, Anchor.CenterRight), TextManager.Get("EngineForwards"),
|
||||
textColor: GUI.Style.TextColor, font: GUI.SubHeadingFont, textAlignment: Alignment.CenterRight);
|
||||
GUITextBlock.AutoScaleAndNormalize(backwardsLabel, forwardsLabel);
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "propellersprite":
|
||||
propellerSprite = new SpriteSheet(subElement);
|
||||
AnimSpeed = subElement.GetAttributeFloat("animspeed", 1.0f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void UpdateHUD(Character character, float deltaTime, Camera cam)
|
||||
{
|
||||
powerIndicator.Selected = hasPower && IsActive;
|
||||
autoControlIndicator.Selected = controlLockTimer > 0.0f;
|
||||
forceSlider.Enabled = controlLockTimer <= 0.0f;
|
||||
|
||||
if (!PlayerInput.PrimaryMouseButtonHeld())
|
||||
{
|
||||
float newScroll = (targetForce + 100.0f) / 200.0f;
|
||||
if (Math.Abs(newScroll - forceSlider.BarScroll) > 0.01f)
|
||||
{
|
||||
forceSlider.BarScroll = newScroll;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
partial void UpdateAnimation(float deltaTime)
|
||||
{
|
||||
if (propellerSprite == null) { return; }
|
||||
spriteIndex += (force / 100.0f) * AnimSpeed * deltaTime;
|
||||
if (spriteIndex < 0)
|
||||
{
|
||||
spriteIndex = propellerSprite.FrameCount;
|
||||
}
|
||||
if (spriteIndex >= propellerSprite.FrameCount)
|
||||
{
|
||||
spriteIndex = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1)
|
||||
{
|
||||
if (propellerSprite != null)
|
||||
{
|
||||
Vector2 drawPos = item.DrawPosition;
|
||||
drawPos += PropellerPos;
|
||||
drawPos.Y = -drawPos.Y;
|
||||
|
||||
propellerSprite.Draw(spriteBatch, (int)Math.Floor(spriteIndex), drawPos, Color.White, propellerSprite.Origin, 0.0f, Vector2.One);
|
||||
}
|
||||
|
||||
if (editing)
|
||||
{
|
||||
Vector2 drawPos = item.DrawPosition;
|
||||
drawPos += PropellerPos;
|
||||
drawPos.Y = -drawPos.Y;
|
||||
GUI.DrawRectangle(spriteBatch, drawPos - Vector2.One * 10, Vector2.One * 20, GUI.Style.Red);
|
||||
}
|
||||
}
|
||||
|
||||
public void ClientWrite(IWriteMessage msg, object[] extraData = null)
|
||||
{
|
||||
//targetForce can only be adjusted at 10% intervals -> no need for more accuracy than this
|
||||
msg.WriteRangedInteger((int)(targetForce / 10.0f), -10, 10);
|
||||
}
|
||||
|
||||
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
|
||||
{
|
||||
if (correctionTimer > 0.0f)
|
||||
{
|
||||
StartDelayedCorrection(type, msg.ExtractBits(5), sendingTime);
|
||||
return;
|
||||
}
|
||||
|
||||
targetForce = msg.ReadRangedInteger(-10, 10) * 10.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user