(61d00a474) v0.9.7.1
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,613 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection.Metadata;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class Fabricator : Powered, IServerSerializable, IClientSerializable
|
||||
{
|
||||
private GUIListBox itemList;
|
||||
|
||||
private GUIFrame selectedItemFrame;
|
||||
private GUIFrame selectedItemReqsFrame;
|
||||
|
||||
public GUIButton ActivateButton
|
||||
{
|
||||
get { return activateButton; }
|
||||
}
|
||||
private GUIButton activateButton;
|
||||
|
||||
private GUITextBox itemFilterBox;
|
||||
|
||||
private GUIComponent outputSlot;
|
||||
private GUIComponent inputInventoryHolder, outputInventoryHolder;
|
||||
|
||||
public FabricationRecipe SelectedItem
|
||||
{
|
||||
get { return selectedItem; }
|
||||
}
|
||||
private FabricationRecipe selectedItem;
|
||||
|
||||
private GUIComponent inSufficientPowerWarning;
|
||||
|
||||
private FabricationRecipe pendingFabricatedItem;
|
||||
|
||||
private Pair<Rectangle, string> tooltip;
|
||||
|
||||
partial void InitProjSpecific()
|
||||
{
|
||||
CreateGUI();
|
||||
GameMain.Instance.OnResolutionChanged += RecreateGUI;
|
||||
}
|
||||
|
||||
private void RecreateGUI()
|
||||
{
|
||||
GuiFrame.ClearChildren();
|
||||
CreateGUI();
|
||||
OnItemLoadedProjSpecific();
|
||||
}
|
||||
|
||||
private void CreateGUI()
|
||||
{
|
||||
var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), GuiFrame.RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter);
|
||||
|
||||
// === LABEL === //
|
||||
new GUITextBlock(new RectTransform(new Vector2(1f, 0.05f), paddedFrame.RectTransform), item.Name, font: GUI.SubHeadingFont)
|
||||
{
|
||||
TextAlignment = Alignment.Center,
|
||||
AutoScaleVertical = true
|
||||
};
|
||||
|
||||
var mainFrame = new GUILayoutGroup(new RectTransform(new Vector2(1f, 1f), paddedFrame.RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter)
|
||||
{
|
||||
RelativeSpacing = 0.02f
|
||||
};
|
||||
|
||||
// === TOP AREA ===
|
||||
var topFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.65f), mainFrame.RectTransform), style: "InnerFrameDark");
|
||||
|
||||
// === ITEM LIST ===
|
||||
var itemListFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), topFrame.RectTransform), childAnchor: Anchor.Center);
|
||||
var paddedItemFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.9f), itemListFrame.RectTransform))
|
||||
{
|
||||
Stretch = true,
|
||||
RelativeSpacing = 0.03f
|
||||
};
|
||||
var filterArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.15f), paddedItemFrame.RectTransform), isHorizontal: true)
|
||||
{
|
||||
Stretch = true,
|
||||
RelativeSpacing = 0.03f,
|
||||
UserData = "filterarea"
|
||||
};
|
||||
new GUITextBlock(new RectTransform(new Vector2(0.2f, 1f), filterArea.RectTransform), TextManager.Get("serverlog.filter"), font: GUI.SubHeadingFont)
|
||||
{
|
||||
Padding = Vector4.Zero,
|
||||
AutoScaleVertical = true
|
||||
};
|
||||
itemFilterBox = new GUITextBox(new RectTransform(new Vector2(0.8f, 1.0f), filterArea.RectTransform), createClearButton: true);
|
||||
itemFilterBox.OnTextChanged += (textBox, text) =>
|
||||
{
|
||||
FilterEntities(text);
|
||||
return true;
|
||||
};
|
||||
filterArea.RectTransform.MaxSize = new Point(int.MaxValue, itemFilterBox.Rect.Height);
|
||||
|
||||
itemList = new GUIListBox(new RectTransform(new Vector2(1f, 0.9f), paddedItemFrame.RectTransform), style: null)
|
||||
{
|
||||
OnSelected = (component, userdata) =>
|
||||
{
|
||||
selectedItem = userdata as FabricationRecipe;
|
||||
if (selectedItem != null) SelectItem(Character.Controlled, selectedItem);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// === SEPARATOR === //
|
||||
new GUIFrame(new RectTransform(new Vector2(0.01f, 0.9f), topFrame.RectTransform, Anchor.Center), style: "VerticalLine");
|
||||
|
||||
// === OUTPUT AREA === //
|
||||
var outputArea = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1f), topFrame.RectTransform, Anchor.TopRight), childAnchor: Anchor.Center);
|
||||
var paddedOutputArea = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), outputArea.RectTransform));
|
||||
var outputTopArea = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.5F), paddedOutputArea.RectTransform, Anchor.Center), isHorizontal: true);
|
||||
// === OUTPUT SLOT === //
|
||||
outputSlot = new GUIFrame(new RectTransform(new Vector2(0.4f, 1f), outputTopArea.RectTransform), style: null);
|
||||
outputInventoryHolder = new GUIFrame(new RectTransform(new Vector2(1f, 1.2f), outputSlot.RectTransform, Anchor.BottomCenter), style: null);
|
||||
new GUICustomComponent(new RectTransform(Vector2.One, outputInventoryHolder.RectTransform), DrawOutputOverLay) { CanBeFocused = false };
|
||||
// === DESCRIPTION === //
|
||||
selectedItemFrame = new GUIFrame(new RectTransform(new Vector2(0.6f, 1f), outputTopArea.RectTransform), style: null);
|
||||
// === REQUIREMENTS === //
|
||||
selectedItemReqsFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.5f), paddedOutputArea.RectTransform), style: null);
|
||||
|
||||
// === BOTTOM AREA === //
|
||||
var bottomFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.3f), mainFrame.RectTransform), style: null);
|
||||
|
||||
// === SEPARATOR === //
|
||||
var separatorArea = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.15f), bottomFrame.RectTransform, Anchor.TopCenter), childAnchor: Anchor.CenterLeft, isHorizontal: true)
|
||||
{
|
||||
Stretch = true,
|
||||
RelativeSpacing = 0.03f
|
||||
};
|
||||
var inputLabel = new GUITextBlock(new RectTransform(Vector2.One, separatorArea.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, separatorArea.RectTransform), style: "HorizontalLine");
|
||||
|
||||
// === INPUT AREA === //
|
||||
var inputArea = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 1f), bottomFrame.RectTransform, Anchor.BottomCenter), isHorizontal: true, childAnchor: Anchor.BottomLeft);
|
||||
|
||||
// === INPUT SLOTS === //
|
||||
inputInventoryHolder = new GUIFrame(new RectTransform(new Vector2(0.7f, 1f), inputArea.RectTransform), style: null);
|
||||
new GUICustomComponent(new RectTransform(Vector2.One, inputInventoryHolder.RectTransform), DrawInputOverLay) { CanBeFocused = false };
|
||||
|
||||
// === ACTIVATE BUTTON === //
|
||||
var buttonFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.3f, 0.8f), inputArea.RectTransform), childAnchor: Anchor.CenterRight);
|
||||
activateButton = new GUIButton(new RectTransform(new Vector2(1f, 0.6f), buttonFrame.RectTransform),
|
||||
TextManager.Get("FabricatorCreate"), style: "DeviceButton")
|
||||
{
|
||||
OnClicked = StartButtonClicked,
|
||||
UserData = selectedItem,
|
||||
Enabled = false
|
||||
};
|
||||
// === POWER WARNING === //
|
||||
inSufficientPowerWarning = new GUITextBlock(new RectTransform(Vector2.One, activateButton.RectTransform),
|
||||
TextManager.Get("FabricatorNoPower"), textColor: GUI.Style.Orange, textAlignment: Alignment.Center, color: Color.Black, style: "OuterGlow", wrap: true)
|
||||
{
|
||||
HoverColor = Color.Black,
|
||||
IgnoreLayoutGroups = true,
|
||||
Visible = false,
|
||||
CanBeFocused = false
|
||||
};
|
||||
CreateRecipes();
|
||||
}
|
||||
|
||||
partial void CreateRecipes()
|
||||
{
|
||||
itemList.Content.RectTransform.ClearChildren();
|
||||
|
||||
foreach (FabricationRecipe fi in fabricationRecipes)
|
||||
{
|
||||
var frame = new GUIFrame(new RectTransform(new Point(itemList.Rect.Width, (int)(40 * GUI.yScale)), itemList.Content.RectTransform), style: null)
|
||||
{
|
||||
UserData = fi,
|
||||
HoverColor = Color.Gold * 0.2f,
|
||||
SelectedColor = Color.Gold * 0.5f,
|
||||
ToolTip = fi.TargetItem.Description
|
||||
};
|
||||
|
||||
var container = new GUILayoutGroup(new RectTransform(Vector2.One, frame.RectTransform),
|
||||
childAnchor: Anchor.CenterLeft, isHorizontal: true) { RelativeSpacing = 0.02f };
|
||||
|
||||
var itemIcon = fi.TargetItem.InventoryIcon ?? fi.TargetItem.sprite;
|
||||
if (itemIcon != null)
|
||||
{
|
||||
new GUIImage(new RectTransform(new Point(frame.Rect.Height,frame.Rect.Height), container.RectTransform),
|
||||
itemIcon, scaleToFit: true)
|
||||
{
|
||||
Color = fi.TargetItem.InventoryIconColor,
|
||||
ToolTip = fi.TargetItem.Description
|
||||
};
|
||||
}
|
||||
|
||||
new GUITextBlock(new RectTransform(new Vector2(0.85f, 1f), container.RectTransform), fi.DisplayName)
|
||||
{
|
||||
Padding = Vector4.Zero,
|
||||
AutoScaleVertical = true,
|
||||
ToolTip = fi.TargetItem.Description
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
partial void OnItemLoadedProjSpecific()
|
||||
{
|
||||
inputContainer.AllowUIOverlap = true;
|
||||
inputContainer.Inventory.RectTransform = inputInventoryHolder.RectTransform;
|
||||
outputContainer.AllowUIOverlap = true;
|
||||
outputContainer.Inventory.RectTransform = outputInventoryHolder.RectTransform;
|
||||
}
|
||||
|
||||
partial void SelectProjSpecific(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);
|
||||
}
|
||||
}
|
||||
|
||||
var nonItems = itemList.Content.Children.Where(c => !(c.UserData is FabricationRecipe)).ToList();
|
||||
nonItems.ForEach(i => itemList.Content.RemoveChild(i));
|
||||
|
||||
itemList.Content.RectTransform.SortChildren((c1, c2) =>
|
||||
{
|
||||
var item1 = c1.GUIComponent.UserData as FabricationRecipe;
|
||||
var item2 = c2.GUIComponent.UserData as FabricationRecipe;
|
||||
|
||||
bool hasSkills1 = DegreeOfSuccess(character, item1.RequiredSkills) >= 0.5f;
|
||||
bool hasSkills2 = DegreeOfSuccess(character, item2.RequiredSkills) >= 0.5f;
|
||||
|
||||
if (hasSkills1 != hasSkills2)
|
||||
{
|
||||
return hasSkills1 ? -1 : 1;
|
||||
}
|
||||
|
||||
return string.Compare(item1.DisplayName, item2.DisplayName);
|
||||
});
|
||||
|
||||
var sufficientSkillsText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), itemList.Content.RectTransform),
|
||||
TextManager.Get("fabricatorsufficientskills", returnNull: true) ?? "Sufficient skills to fabricate", textColor: GUI.Style.Green, font: GUI.SubHeadingFont)
|
||||
{
|
||||
AutoScaleHorizontal = true,
|
||||
CanBeFocused = false
|
||||
};
|
||||
sufficientSkillsText.RectTransform.SetAsFirstChild();
|
||||
|
||||
var insufficientSkillsText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), itemList.Content.RectTransform),
|
||||
TextManager.Get("fabricatorinsufficientskills", returnNull: true) ?? "Insufficient skills to fabricate", textColor: Color.Orange, font: GUI.SubHeadingFont)
|
||||
{
|
||||
AutoScaleHorizontal = true,
|
||||
CanBeFocused = false
|
||||
};
|
||||
var firstinSufficient = itemList.Content.Children.FirstOrDefault(c => c.UserData is FabricationRecipe fabricableItem && DegreeOfSuccess(character, fabricableItem.RequiredSkills) < 0.5f);
|
||||
if (firstinSufficient != null)
|
||||
{
|
||||
insufficientSkillsText.RectTransform.RepositionChildInHierarchy(itemList.Content.RectTransform.GetChildIndex(firstinSufficient.RectTransform));
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawInputOverLay(SpriteBatch spriteBatch, GUICustomComponent overlayComponent)
|
||||
{
|
||||
overlayComponent.RectTransform.SetAsLastChild();
|
||||
|
||||
FabricationRecipe targetItem = fabricatedItem ?? selectedItem;
|
||||
if (targetItem != null)
|
||||
{
|
||||
int slotIndex = 0;
|
||||
|
||||
var missingItems = new List<FabricationRecipe.RequiredItem>();
|
||||
foreach (FabricationRecipe.RequiredItem requiredItem in targetItem.RequiredItems)
|
||||
{
|
||||
for (int i = 0; i < requiredItem.Amount; i++)
|
||||
{
|
||||
missingItems.Add(requiredItem);
|
||||
}
|
||||
}
|
||||
foreach (Item item in inputContainer.Inventory.Items)
|
||||
{
|
||||
if (item == null) { continue; }
|
||||
missingItems.Remove(missingItems.FirstOrDefault(mi => mi.ItemPrefab == item.prefab));
|
||||
}
|
||||
|
||||
var availableIngredients = GetAvailableIngredients();
|
||||
|
||||
foreach (FabricationRecipe.RequiredItem requiredItem in missingItems)
|
||||
{
|
||||
while (slotIndex < inputContainer.Capacity && inputContainer.Inventory.Items[slotIndex] != null)
|
||||
{
|
||||
slotIndex++;
|
||||
}
|
||||
|
||||
//highlight suitable ingredients in linked inventories
|
||||
foreach (Item item in availableIngredients)
|
||||
{
|
||||
if (item.ParentInventory != inputContainer.Inventory && IsItemValidIngredient(item, requiredItem))
|
||||
{
|
||||
int availableSlotIndex = Array.IndexOf(item.ParentInventory.Items, item);
|
||||
//slots are null if the inventory has never been displayed
|
||||
//(linked item, but the UI is not set to be displayed at the same time)
|
||||
if (item.ParentInventory.slots != null)
|
||||
{
|
||||
if (item.ParentInventory.slots[availableSlotIndex].HighlightTimer <= 0.0f)
|
||||
{
|
||||
item.ParentInventory.slots[availableSlotIndex].ShowBorderHighlight(GUI.Style.Green, 0.5f, 0.5f);
|
||||
if (slotIndex < inputContainer.Capacity)
|
||||
{
|
||||
inputContainer.Inventory.slots[slotIndex].ShowBorderHighlight(GUI.Style.Green, 0.5f, 0.5f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (slotIndex >= inputContainer.Capacity) { break; }
|
||||
|
||||
var itemIcon = requiredItem.ItemPrefab.InventoryIcon ?? requiredItem.ItemPrefab.sprite;
|
||||
Rectangle slotRect = inputContainer.Inventory.slots[slotIndex].Rect;
|
||||
itemIcon.Draw(
|
||||
spriteBatch,
|
||||
slotRect.Center.ToVector2(),
|
||||
color: requiredItem.ItemPrefab.InventoryIconColor * 0.3f,
|
||||
scale: Math.Min(slotRect.Width / itemIcon.size.X, slotRect.Height / itemIcon.size.Y));
|
||||
|
||||
if (slotRect.Contains(PlayerInput.MousePosition))
|
||||
{
|
||||
string toolTipText = requiredItem.ItemPrefab.Name;
|
||||
if (!string.IsNullOrEmpty(requiredItem.ItemPrefab.Description))
|
||||
{
|
||||
toolTipText += '\n' + requiredItem.ItemPrefab.Description;
|
||||
}
|
||||
tooltip = new Pair<Rectangle, string>(slotRect, toolTipText);
|
||||
}
|
||||
|
||||
slotIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawOutputOverLay(SpriteBatch spriteBatch, GUICustomComponent overlayComponent)
|
||||
{
|
||||
overlayComponent.RectTransform.SetAsLastChild();
|
||||
|
||||
if (outputContainer.Inventory.Items.First() != null) { return; }
|
||||
|
||||
FabricationRecipe targetItem = fabricatedItem ?? selectedItem;
|
||||
if (targetItem != null)
|
||||
{
|
||||
var itemIcon = targetItem.TargetItem.InventoryIcon ?? targetItem.TargetItem.sprite;
|
||||
|
||||
Rectangle slotRect = outputContainer.Inventory.slots[0].Rect;
|
||||
|
||||
if (fabricatedItem != null)
|
||||
{
|
||||
GUI.DrawRectangle(spriteBatch,
|
||||
new Rectangle(
|
||||
slotRect.X, slotRect.Y + (int)(slotRect.Height * (1.0f - progressState)),
|
||||
slotRect.Width, (int)(slotRect.Height * progressState)),
|
||||
GUI.Style.Green * 0.5f, isFilled: true);
|
||||
}
|
||||
|
||||
itemIcon.Draw(
|
||||
spriteBatch,
|
||||
slotRect.Center.ToVector2(),
|
||||
color: targetItem.TargetItem.InventoryIconColor * 0.4f,
|
||||
scale: Math.Min(slotRect.Width / itemIcon.size.X, slotRect.Height / itemIcon.size.Y) * 0.9f);
|
||||
}
|
||||
|
||||
if (tooltip != null)
|
||||
{
|
||||
GUIComponent.DrawToolTip(spriteBatch, tooltip.Second, tooltip.First);
|
||||
tooltip = null;
|
||||
}
|
||||
}
|
||||
|
||||
private bool FilterEntities(string filter)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(filter))
|
||||
{
|
||||
itemList.Content.Children.ForEach(c => c.Visible = true);
|
||||
return true;
|
||||
}
|
||||
|
||||
filter = filter.ToLower();
|
||||
foreach (GUIComponent child in itemList.Content.Children)
|
||||
{
|
||||
FabricationRecipe recipe = child.UserData as FabricationRecipe;
|
||||
if (recipe?.DisplayName == null) { continue; }
|
||||
child.Visible = recipe.DisplayName.ToLower().Contains(filter);
|
||||
}
|
||||
itemList.UpdateScrollBarSize();
|
||||
itemList.BarScroll = 0.0f;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool ClearFilter()
|
||||
{
|
||||
FilterEntities("");
|
||||
itemList.UpdateScrollBarSize();
|
||||
itemList.BarScroll = 0.0f;
|
||||
itemFilterBox.Text = "";
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool SelectItem(Character user, FabricationRecipe selectedItem)
|
||||
{
|
||||
selectedItemFrame.ClearChildren();
|
||||
selectedItemReqsFrame.ClearChildren();
|
||||
|
||||
var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.9f), selectedItemFrame.RectTransform, Anchor.Center)) { RelativeSpacing = 0.03f };
|
||||
var paddedReqFrame = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.9f), selectedItemReqsFrame.RectTransform, Anchor.Center)) { RelativeSpacing = 0.03f };
|
||||
|
||||
/*var itemIcon = selectedItem.TargetItem.InventoryIcon ?? selectedItem.TargetItem.sprite;
|
||||
if (itemIcon != null)
|
||||
{
|
||||
GUIImage img = new GUIImage(new RectTransform(new Point(40, 40), paddedFrame.RectTransform),
|
||||
itemIcon, scaleToFit: true)
|
||||
{
|
||||
Color = selectedItem.TargetItem.InventoryIconColor
|
||||
};
|
||||
}*/
|
||||
var nameBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform),
|
||||
selectedItem.TargetItem.Name, textAlignment: Alignment.CenterLeft, textColor: Color.Aqua, font: GUI.SubHeadingFont)
|
||||
{
|
||||
AutoScaleHorizontal = true
|
||||
};
|
||||
|
||||
nameBlock.Padding = new Vector4(0, nameBlock.Padding.Y, nameBlock.Padding.Z, nameBlock.Padding.W);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(selectedItem.TargetItem.Description))
|
||||
{
|
||||
var description = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform),
|
||||
selectedItem.TargetItem.Description,
|
||||
font: GUI.SmallFont, wrap: true);
|
||||
description.Padding = new Vector4(0, description.Padding.Y, description.Padding.Z, description.Padding.W);
|
||||
|
||||
while (description.Rect.Height + nameBlock.Rect.Height > paddedFrame.Rect.Height)
|
||||
{
|
||||
var lines = description.WrappedText.Split('\n');
|
||||
var newString = string.Join('\n', lines.Take(lines.Length - 1));
|
||||
description.Text = newString.Substring(0, newString.Length - 4) + "...";
|
||||
description.CalculateHeightFromText();
|
||||
description.ToolTip = selectedItem.TargetItem.Description;
|
||||
}
|
||||
}
|
||||
|
||||
List<Skill> inadequateSkills = new List<Skill>();
|
||||
if (user != null)
|
||||
{
|
||||
inadequateSkills = selectedItem.RequiredSkills.FindAll(skill => user.GetSkillLevel(skill.Identifier) < skill.Level);
|
||||
}
|
||||
|
||||
if (selectedItem.RequiredSkills.Any())
|
||||
{
|
||||
string text = "";
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedReqFrame.RectTransform),
|
||||
TextManager.Get("FabricatorRequiredSkills"), textColor: inadequateSkills.Any() ? GUI.Style.Red : GUI.Style.Green, font: GUI.SubHeadingFont)
|
||||
{
|
||||
AutoScaleHorizontal = true,
|
||||
};
|
||||
foreach (Skill skill in selectedItem.RequiredSkills)
|
||||
{
|
||||
text += TextManager.Get("SkillName." + skill.Identifier) + " " + TextManager.Get("Lvl").ToLower() + " " + skill.Level;
|
||||
if (skill != selectedItem.RequiredSkills.Last()) { text += "\n"; }
|
||||
}
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedReqFrame.RectTransform), text, font: GUI.SmallFont);
|
||||
}
|
||||
|
||||
float degreeOfSuccess = user == null ? 0.0f : DegreeOfSuccess(user, selectedItem.RequiredSkills);
|
||||
if (degreeOfSuccess > 0.5f) { degreeOfSuccess = 1.0f; }
|
||||
|
||||
float requiredTime = user == null ? selectedItem.RequiredTime : GetRequiredTime(selectedItem, user);
|
||||
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedReqFrame.RectTransform),
|
||||
TextManager.Get("FabricatorRequiredTime") , textColor: ToolBox.GradientLerp(degreeOfSuccess, GUI.Style.Red, Color.Yellow, GUI.Style.Green), font: GUI.SubHeadingFont)
|
||||
{
|
||||
AutoScaleHorizontal = true,
|
||||
};
|
||||
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedReqFrame.RectTransform), ToolBox.SecondsToReadableTime(requiredTime),
|
||||
font: GUI.SmallFont);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void HighlightRecipe(string identifier, Color color)
|
||||
{
|
||||
foreach (GUIComponent child in itemList.Content.Children)
|
||||
{
|
||||
FabricationRecipe recipe = child.UserData as FabricationRecipe;
|
||||
if (recipe?.DisplayName == null) { continue; }
|
||||
if (recipe.TargetItem.Identifier == identifier)
|
||||
{
|
||||
if (child.FlashTimer > 0.0f) return;
|
||||
child.Flash(color, 1.5f, false);
|
||||
|
||||
for (int i = 0; i < child.CountChildren; i++)
|
||||
{
|
||||
var grandChild = child.GetChild(i);
|
||||
if (grandChild is GUITextBlock) continue;
|
||||
grandChild.Flash(color, 1.5f, false);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool StartButtonClicked(GUIButton button, object obj)
|
||||
{
|
||||
if (selectedItem == null) { return false; }
|
||||
if (!outputContainer.Inventory.IsEmpty())
|
||||
{
|
||||
outputSlot.Flash(GUI.Style.Red);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (GameMain.Client != null)
|
||||
{
|
||||
pendingFabricatedItem = fabricatedItem != null ? null : selectedItem;
|
||||
item.CreateClientEvent(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (fabricatedItem == null)
|
||||
{
|
||||
StartFabricating(selectedItem, Character.Controlled);
|
||||
}
|
||||
else
|
||||
{
|
||||
CancelFabricating(Character.Controlled);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void UpdateHUD(Character character, float deltaTime, Camera cam)
|
||||
{
|
||||
activateButton.Enabled = false;
|
||||
inSufficientPowerWarning.Visible = currPowerConsumption > 0 && !hasPower;
|
||||
|
||||
var availableIngredients = GetAvailableIngredients();
|
||||
if (character != null)
|
||||
{
|
||||
foreach (GUIComponent child in itemList.Content.Children)
|
||||
{
|
||||
var itemPrefab = child.UserData as FabricationRecipe;
|
||||
if (itemPrefab == null) continue;
|
||||
|
||||
bool canBeFabricated = CanBeFabricated(itemPrefab, availableIngredients);
|
||||
if (itemPrefab == selectedItem)
|
||||
{
|
||||
activateButton.Enabled = canBeFabricated;
|
||||
}
|
||||
|
||||
var childContainer = child.GetChild<GUILayoutGroup>();
|
||||
|
||||
childContainer.GetChild<GUITextBlock>().TextColor = Color.White * (canBeFabricated ? 1.0f : 0.5f);
|
||||
childContainer.GetChild<GUIImage>().Color = itemPrefab.TargetItem.InventoryIconColor * (canBeFabricated ? 1.0f : 0.5f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ClientWrite(IWriteMessage msg, object[] extraData = null)
|
||||
{
|
||||
int itemIndex = pendingFabricatedItem == null ? -1 : fabricationRecipes.IndexOf(pendingFabricatedItem);
|
||||
msg.WriteRangedInteger(itemIndex, -1, fabricationRecipes.Count - 1);
|
||||
}
|
||||
|
||||
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
|
||||
{
|
||||
int itemIndex = msg.ReadRangedInteger(-1, fabricationRecipes.Count - 1);
|
||||
UInt16 userID = msg.ReadUInt16();
|
||||
Character user = Entity.FindEntityByID(userID) as Character;
|
||||
|
||||
if (itemIndex == -1 || user == null)
|
||||
{
|
||||
CancelFabricating();
|
||||
}
|
||||
else
|
||||
{
|
||||
//if already fabricating the selected item, return
|
||||
if (fabricatedItem != null && fabricationRecipes.IndexOf(fabricatedItem) == itemIndex) { return; }
|
||||
if (itemIndex < 0 || itemIndex >= fabricationRecipes.Count) { return; }
|
||||
|
||||
SelectItem(user, fabricationRecipes[itemIndex]);
|
||||
StartFabricating(fabricationRecipes[itemIndex], user);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void RemoveComponentSpecific()
|
||||
{
|
||||
GameMain.Instance.OnResolutionChanged -= RecreateGUI;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,331 @@
|
||||
using Barotrauma.Extensions;
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class MiniMap : Powered
|
||||
{
|
||||
private GUIFrame submarineContainer;
|
||||
|
||||
private GUIFrame hullInfoFrame;
|
||||
|
||||
private GUITextBlock hullNameText, hullBreachText, hullAirQualityText, hullWaterText;
|
||||
|
||||
private string noPowerTip = "";
|
||||
|
||||
private readonly List<Submarine> displayedSubs = new List<Submarine>();
|
||||
|
||||
partial void InitProjSpecific(XElement element)
|
||||
{
|
||||
noPowerTip = TextManager.Get("SteeringNoPowerTip");
|
||||
|
||||
GuiFrame.RectTransform.RelativeOffset = new Vector2(0.05f, 0.0f);
|
||||
GuiFrame.CanBeFocused = true;
|
||||
new GUICustomComponent(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center) { AbsoluteOffset = GUIStyle.ItemFrameOffset },
|
||||
DrawHUDBack, null);
|
||||
submarineContainer = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.9f), GuiFrame.RectTransform, Anchor.Center), style: null);
|
||||
|
||||
new GUICustomComponent(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center) { AbsoluteOffset = GUIStyle.ItemFrameOffset },
|
||||
DrawHUDFront, null)
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
|
||||
hullInfoFrame = new GUIFrame(new RectTransform(new Vector2(0.13f, 0.13f), GUI.Canvas, minSize: new Point(250, 150)),
|
||||
style: "GUIToolTip")
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
var hullInfoContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.9f), hullInfoFrame.RectTransform, Anchor.Center))
|
||||
{
|
||||
Stretch = true,
|
||||
RelativeSpacing = 0.05f
|
||||
};
|
||||
|
||||
hullNameText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.4f), hullInfoContainer.RectTransform), "") { Wrap = true };
|
||||
hullBreachText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.3f), hullInfoContainer.RectTransform), "") { Wrap = true };
|
||||
hullAirQualityText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.3f), hullInfoContainer.RectTransform), "") { Wrap = true };
|
||||
hullWaterText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.3f), hullInfoContainer.RectTransform), "") { Wrap = true };
|
||||
|
||||
hullInfoFrame.Children.ForEach(c => { c.CanBeFocused = false; c.Children.ForEach(c2 => c2.CanBeFocused = false); });
|
||||
}
|
||||
|
||||
public override void AddToGUIUpdateList()
|
||||
{
|
||||
base.AddToGUIUpdateList();
|
||||
hullInfoFrame.AddToGUIUpdateList(order: 1);
|
||||
}
|
||||
|
||||
public override void OnMapLoaded()
|
||||
{
|
||||
base.OnMapLoaded();
|
||||
CreateHUD();
|
||||
}
|
||||
|
||||
private void CreateHUD()
|
||||
{
|
||||
submarineContainer.ClearChildren();
|
||||
|
||||
if (item.Submarine == null) return;
|
||||
|
||||
item.Submarine.CreateMiniMap(submarineContainer);
|
||||
displayedSubs.Clear();
|
||||
displayedSubs.Add(item.Submarine);
|
||||
displayedSubs.AddRange(item.Submarine.DockedTo);
|
||||
}
|
||||
|
||||
public override void FlipX(bool relativeToSub)
|
||||
{
|
||||
CreateHUD();
|
||||
}
|
||||
|
||||
public override void UpdateHUD(Character character, float deltaTime, Camera cam)
|
||||
{
|
||||
//recreate HUD if the subs we should display have changed
|
||||
if ((item.Submarine == null && displayedSubs.Count > 0) || //item not inside a sub anymore, but display is still showing subs
|
||||
!displayedSubs.Contains(item.Submarine) || //current sub not displayer
|
||||
item.Submarine.DockedTo.Any(s => !displayedSubs.Contains(s)) || //some of the docked subs not diplayed
|
||||
displayedSubs.Any(s => s != item.Submarine && !item.Submarine.DockedTo.Contains(s))) //displaying a sub that shouldn't be displayed
|
||||
{
|
||||
CreateHUD();
|
||||
}
|
||||
|
||||
float distort = 1.0f - item.Condition / item.MaxCondition;
|
||||
foreach (HullData hullData in hullDatas.Values)
|
||||
{
|
||||
hullData.DistortionTimer -= deltaTime;
|
||||
if (hullData.DistortionTimer <= 0.0f)
|
||||
{
|
||||
hullData.Distort = Rand.Range(0.0f, 1.0f) < distort * distort;
|
||||
if (hullData.Distort)
|
||||
{
|
||||
hullData.Oxygen = Rand.Range(0.0f, 100.0f);
|
||||
hullData.Water = Rand.Range(0.0f, 1.0f);
|
||||
}
|
||||
hullData.DistortionTimer = Rand.Range(1.0f, 10.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawHUDFront(SpriteBatch spriteBatch, GUICustomComponent container)
|
||||
{
|
||||
if (Voltage < MinVoltage)
|
||||
{
|
||||
Vector2 textSize = GUI.Font.MeasureString(noPowerTip);
|
||||
Vector2 textPos = GuiFrame.Rect.Center.ToVector2();
|
||||
|
||||
GUI.DrawString(spriteBatch, textPos - textSize / 2, noPowerTip,
|
||||
GUI.Style.Orange * (float)Math.Abs(Math.Sin(Timing.TotalTime)), Color.Black * 0.8f, font: GUI.SubHeadingFont);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (GUIComponent child in submarineContainer.Children.First().Children)
|
||||
{
|
||||
if (child.UserData is Hull hull)
|
||||
{
|
||||
if (hull.Submarine == null || !hull.Submarine.IsOutpost) { continue; }
|
||||
string text = TextManager.GetWithVariable("MiniMapOutpostDockingInfo", "[outpost]", hull.Submarine.Name);
|
||||
Vector2 textSize = GUI.Font.MeasureString(text);
|
||||
Vector2 textPos = child.Center;
|
||||
if (textPos.X + textSize.X / 2 > submarineContainer.Rect.Right)
|
||||
textPos.X -= ((textPos.X + textSize.X / 2) - submarineContainer.Rect.Right) + 10 * GUI.xScale;
|
||||
if (textPos.X - textSize.X / 2 < submarineContainer.Rect.X)
|
||||
textPos.X += (submarineContainer.Rect.X - (textPos.X - textSize.X / 2)) + 10 * GUI.xScale;
|
||||
GUI.DrawString(spriteBatch, textPos - textSize / 2, text,
|
||||
GUI.Style.Orange * (float)Math.Abs(Math.Sin(Timing.TotalTime)), Color.Black * 0.8f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawHUDBack(SpriteBatch spriteBatch, GUICustomComponent container)
|
||||
{
|
||||
Hull mouseOnHull = null;
|
||||
hullInfoFrame.Visible = false;
|
||||
|
||||
foreach (Hull hull in Hull.hullList)
|
||||
{
|
||||
var hullFrame = submarineContainer.Children.First().FindChild(hull);
|
||||
if (hullFrame == null) { continue; }
|
||||
|
||||
if (GUI.MouseOn == hullFrame || hullFrame.IsParentOf(GUI.MouseOn))
|
||||
{
|
||||
mouseOnHull = hull;
|
||||
}
|
||||
if (item.Submarine == null || !hasPower)
|
||||
{
|
||||
hullFrame.Color = Color.DarkCyan * 0.3f;
|
||||
hullFrame.Children.First().Color = Color.DarkCyan * 0.3f;
|
||||
}
|
||||
}
|
||||
|
||||
if (Voltage < MinVoltage)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float scale = 1.0f;
|
||||
HashSet<Submarine> subs = new HashSet<Submarine>();
|
||||
foreach (Hull hull in Hull.hullList)
|
||||
{
|
||||
if (hull.Submarine == null) continue;
|
||||
var hullFrame = submarineContainer.Children.First().FindChild(hull);
|
||||
if (hullFrame == null) continue;
|
||||
|
||||
hullDatas.TryGetValue(hull, out HullData hullData);
|
||||
if (hullData == null)
|
||||
{
|
||||
hullData = new HullData();
|
||||
GetLinkedHulls(hull, hullData.LinkedHulls);
|
||||
hullDatas.Add(hull, hullData);
|
||||
}
|
||||
|
||||
Color neutralColor = Color.DarkCyan;
|
||||
if (hull.RoomName != null)
|
||||
{
|
||||
if (hull.RoomName.Contains("ballast") || hull.RoomName.Contains("Ballast") ||
|
||||
hull.RoomName.Contains("airlock") || hull.RoomName.Contains("Airlock"))
|
||||
{
|
||||
neutralColor = new Color(9, 80, 159);
|
||||
}
|
||||
}
|
||||
|
||||
if (hullData.Distort)
|
||||
{
|
||||
hullFrame.Children.First().Color = Color.Lerp(Color.Black, Color.DarkGray * 0.5f, Rand.Range(0.0f, 1.0f));
|
||||
hullFrame.Color = neutralColor * 0.5f;
|
||||
continue;
|
||||
}
|
||||
|
||||
subs.Add(hull.Submarine);
|
||||
scale = Math.Min(
|
||||
hullFrame.Parent.Rect.Width / (float)hull.Submarine.Borders.Width,
|
||||
hullFrame.Parent.Rect.Height / (float)hull.Submarine.Borders.Height);
|
||||
|
||||
Color borderColor = neutralColor;
|
||||
|
||||
float? gapOpenSum = 0.0f;
|
||||
if (ShowHullIntegrity)
|
||||
{
|
||||
gapOpenSum = hull.ConnectedGaps.Where(g => !g.IsRoomToRoom).Sum(g => g.Open);
|
||||
borderColor = Color.Lerp(neutralColor, GUI.Style.Red, Math.Min((float)gapOpenSum, 1.0f));
|
||||
}
|
||||
|
||||
float? oxygenAmount = null;
|
||||
if (!RequireOxygenDetectors || hullData?.Oxygen != null)
|
||||
{
|
||||
oxygenAmount = RequireOxygenDetectors ? hullData.Oxygen : hull.OxygenPercentage;
|
||||
GUI.DrawRectangle(
|
||||
spriteBatch, hullFrame.Rect,
|
||||
Color.Lerp(GUI.Style.Red * 0.5f, GUI.Style.Green * 0.3f, (float)oxygenAmount / 100.0f),
|
||||
true);
|
||||
}
|
||||
|
||||
float? waterAmount = null;
|
||||
if (!RequireWaterDetectors || hullData.Water != null)
|
||||
{
|
||||
waterAmount = RequireWaterDetectors ? hullData.Water : Math.Min(hull.WaterVolume / hull.Volume, 1.0f);
|
||||
if (hullFrame.Rect.Height * waterAmount > 3.0f)
|
||||
{
|
||||
Rectangle waterRect = new Rectangle(
|
||||
hullFrame.Rect.X, (int)(hullFrame.Rect.Y + hullFrame.Rect.Height * (1.0f - waterAmount)),
|
||||
hullFrame.Rect.Width, (int)(hullFrame.Rect.Height * waterAmount));
|
||||
|
||||
waterRect.Inflate(-3, -3);
|
||||
|
||||
GUI.DrawRectangle(spriteBatch, waterRect, new Color(85, 136, 147), true);
|
||||
GUI.DrawLine(spriteBatch, new Vector2(waterRect.X, waterRect.Y), new Vector2(waterRect.Right, waterRect.Y), Color.LightBlue);
|
||||
}
|
||||
}
|
||||
|
||||
if (mouseOnHull == hull ||
|
||||
hullData.LinkedHulls.Contains(mouseOnHull))
|
||||
{
|
||||
borderColor = Color.Lerp(borderColor, Color.White, 0.5f);
|
||||
hullFrame.Children.First().Color = Color.White;
|
||||
hullFrame.Color = borderColor;
|
||||
}
|
||||
else
|
||||
{
|
||||
hullFrame.Children.First().Color = neutralColor * 0.8f;
|
||||
}
|
||||
|
||||
if (mouseOnHull == hull)
|
||||
{
|
||||
hullInfoFrame.RectTransform.ScreenSpaceOffset = hullFrame.Rect.Center;
|
||||
if (hullInfoFrame.Rect.Right > GameMain.GraphicsWidth) { hullInfoFrame.RectTransform.ScreenSpaceOffset -= new Point(hullInfoFrame.Rect.Width, 0); }
|
||||
if (hullInfoFrame.Rect.Bottom > GameMain.GraphicsHeight) { hullInfoFrame.RectTransform.ScreenSpaceOffset -= new Point(0, hullInfoFrame.Rect.Height); }
|
||||
|
||||
hullInfoFrame.Visible = true;
|
||||
hullNameText.Text = hull.DisplayName;
|
||||
|
||||
foreach (Hull linkedHull in hullData.LinkedHulls)
|
||||
{
|
||||
gapOpenSum += linkedHull.ConnectedGaps.Where(g => !g.IsRoomToRoom).Sum(g => g.Open);
|
||||
oxygenAmount += linkedHull.OxygenPercentage;
|
||||
waterAmount += Math.Min(linkedHull.WaterVolume / linkedHull.Volume, 1.0f);
|
||||
}
|
||||
oxygenAmount /= (hullData.LinkedHulls.Count + 1);
|
||||
waterAmount /= (hullData.LinkedHulls.Count + 1);
|
||||
|
||||
hullBreachText.Text = gapOpenSum > 0.1f ? TextManager.Get("MiniMapHullBreach") : "";
|
||||
hullBreachText.TextColor = GUI.Style.Red;
|
||||
|
||||
hullAirQualityText.Text = oxygenAmount == null ? TextManager.Get("MiniMapAirQualityUnavailable") :
|
||||
TextManager.AddPunctuation(':', TextManager.Get("MiniMapAirQuality"), + (int)oxygenAmount + " %");
|
||||
hullAirQualityText.TextColor = oxygenAmount == null ? GUI.Style.Red : Color.Lerp(GUI.Style.Red, Color.LightGreen, (float)oxygenAmount / 100.0f);
|
||||
|
||||
hullWaterText.Text = waterAmount == null ? TextManager.Get("MiniMapWaterLevelUnavailable") :
|
||||
TextManager.AddPunctuation(':', TextManager.Get("MiniMapWaterLevel"), (int)(waterAmount * 100.0f) + " %");
|
||||
hullWaterText.TextColor = waterAmount == null ? GUI.Style.Red : Color.Lerp(Color.LightGreen, GUI.Style.Red, (float)waterAmount);
|
||||
}
|
||||
|
||||
hullFrame.Color = borderColor;
|
||||
}
|
||||
|
||||
foreach (Submarine sub in subs)
|
||||
{
|
||||
if (sub.HullVertices == null) { continue; }
|
||||
|
||||
Rectangle worldBorders = sub.GetDockedBorders();
|
||||
worldBorders.Location += sub.WorldPosition.ToPoint();
|
||||
|
||||
scale = Math.Min(
|
||||
submarineContainer.Rect.Width / (float)worldBorders.Width,
|
||||
submarineContainer.Rect.Height / (float)worldBorders.Height) * 0.9f;
|
||||
|
||||
float displayScale = ConvertUnits.ToDisplayUnits(scale);
|
||||
Vector2 offset = ConvertUnits.ToSimUnits(sub.WorldPosition - new Vector2(worldBorders.Center.X, worldBorders.Y - worldBorders.Height / 2));
|
||||
Vector2 center = container.Rect.Center.ToVector2();
|
||||
|
||||
for (int i = 0; i < sub.HullVertices.Count; i++)
|
||||
{
|
||||
Vector2 start = (sub.HullVertices[i] + offset) * displayScale;
|
||||
start.Y = -start.Y;
|
||||
Vector2 end = (sub.HullVertices[(i + 1) % sub.HullVertices.Count] + offset) * displayScale;
|
||||
end.Y = -end.Y;
|
||||
GUI.DrawLine(spriteBatch, center + start, center + end, Color.DarkCyan * Rand.Range(0.3f, 0.35f), width: (int)(10 * GUI.Scale));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void GetLinkedHulls(Hull hull, List<Hull> linkedHulls)
|
||||
{
|
||||
foreach (var linkedEntity in hull.linkedTo)
|
||||
{
|
||||
if (linkedEntity is Hull linkedHull)
|
||||
{
|
||||
if (linkedHulls.Contains(linkedHull)) { continue; }
|
||||
linkedHulls.Add(linkedHull);
|
||||
GetLinkedHulls(linkedHull, linkedHulls);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.Particles;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class Pump : Powered, IServerSerializable, IClientSerializable
|
||||
{
|
||||
public GUIButton PowerButton { get; private set; }
|
||||
|
||||
private GUIScrollBar pumpSpeedSlider;
|
||||
private GUITickBox powerLight;
|
||||
private GUITickBox autoControlIndicator;
|
||||
|
||||
private List<Pair<Vector2, ParticleEmitter>> pumpOutEmitters = new List<Pair<Vector2, ParticleEmitter>>();
|
||||
private List<Pair<Vector2, ParticleEmitter>> pumpInEmitters = new List<Pair<Vector2, ParticleEmitter>>();
|
||||
|
||||
partial void InitProjSpecific(XElement element)
|
||||
{
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "pumpoutemitter":
|
||||
pumpOutEmitters.Add(new Pair<Vector2, ParticleEmitter>(
|
||||
subElement.GetAttributeVector2("position", Vector2.Zero),
|
||||
new ParticleEmitter(subElement)));
|
||||
break;
|
||||
case "pumpinemitter":
|
||||
pumpInEmitters.Add(new Pair<Vector2, ParticleEmitter>(
|
||||
subElement.GetAttributeVector2("position", Vector2.Zero),
|
||||
new ParticleEmitter(subElement)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (GuiFrame == null) { return; }
|
||||
|
||||
GUIFrame paddedFrame = new GUIFrame(new RectTransform(new Vector2(0.85f, 0.65f), GuiFrame.RectTransform, Anchor.Center)
|
||||
{
|
||||
RelativeOffset = new Vector2(0, 0.04f)
|
||||
}, style: null);
|
||||
|
||||
// Power button
|
||||
float powerButtonSize = 1f;
|
||||
var powerArea = new GUIFrame(new RectTransform(new Vector2(0.3f, 1) * powerButtonSize, paddedFrame.RectTransform, Anchor.CenterLeft), style: null);
|
||||
var paddedPowerArea = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.8f), powerArea.RectTransform, Anchor.Center), style: "PowerButtonFrame");
|
||||
var powerLightArea = new GUIFrame(new RectTransform(new Vector2(0.87f, 0.2f), powerArea.RectTransform, Anchor.TopRight), style: null);
|
||||
powerLight = new GUITickBox(new RectTransform(Vector2.One, powerLightArea.RectTransform, Anchor.Center),
|
||||
TextManager.Get("PowerLabel"), font: GUI.SubHeadingFont, style: "IndicatorLightPower")
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
powerLight.TextBlock.AutoScaleHorizontal = true;
|
||||
powerLight.TextBlock.OverrideTextColor(GUI.Style.TextColor);
|
||||
PowerButton = new GUIButton(new RectTransform(new Vector2(0.8f, 0.75f), paddedPowerArea.RectTransform, Anchor.TopCenter)
|
||||
{
|
||||
RelativeOffset = new Vector2(0, 0.1f)
|
||||
}, style: "PowerButton")
|
||||
{
|
||||
OnClicked = (button, data) =>
|
||||
{
|
||||
targetLevel = null;
|
||||
IsActive = !IsActive;
|
||||
if (GameMain.Client != null)
|
||||
{
|
||||
correctionTimer = CorrectionDelay;
|
||||
item.CreateClientEvent(this);
|
||||
}
|
||||
powerLight.Selected = IsActive;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
var rightArea = new GUIFrame(new RectTransform(new Vector2(0.65f, 1), paddedFrame.RectTransform, Anchor.CenterRight), style: null);
|
||||
|
||||
autoControlIndicator = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.25f), rightArea.RectTransform, Anchor.TopLeft),
|
||||
TextManager.Get("PumpAutoControl", fallBackTag: "ReactorAutoControl"), font: GUI.SubHeadingFont, style: "IndicatorLightYellow")
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
autoControlIndicator.TextBlock.AutoScaleHorizontal = true;
|
||||
autoControlIndicator.TextBlock.OverrideTextColor(GUI.Style.TextColor);
|
||||
|
||||
var sliderArea = new GUIFrame(new RectTransform(new Vector2(1, 0.65f), rightArea.RectTransform, Anchor.BottomLeft), style: null);
|
||||
var pumpSpeedText = new GUITextBlock(new RectTransform(new Vector2(1, 0.3f), sliderArea.RectTransform, Anchor.TopLeft), "",
|
||||
textColor: GUI.Style.TextColor, textAlignment: Alignment.CenterLeft, wrap: false, font: GUI.SubHeadingFont)
|
||||
{
|
||||
AutoScaleHorizontal = true
|
||||
};
|
||||
string pumpSpeedStr = TextManager.Get("PumpSpeed");
|
||||
pumpSpeedText.TextGetter = () => { return TextManager.AddPunctuation(':', pumpSpeedStr, (int)flowPercentage + " %"); };
|
||||
pumpSpeedSlider = new GUIScrollBar(new RectTransform(new Vector2(1, 0.35f), sliderArea.RectTransform, Anchor.Center), barSize: 0.1f, style: "DeviceSlider")
|
||||
{
|
||||
Step = 0.05f,
|
||||
OnMoved = (GUIScrollBar scrollBar, float barScroll) =>
|
||||
{
|
||||
float newValue = barScroll * 200.0f - 100.0f;
|
||||
if (Math.Abs(newValue - FlowPercentage) < 0.1f) { return false; }
|
||||
|
||||
FlowPercentage = newValue;
|
||||
|
||||
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 outLabel = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), textsArea.RectTransform, Anchor.CenterLeft), TextManager.Get("PumpOut"),
|
||||
textColor: GUI.Style.TextColor, textAlignment: Alignment.CenterLeft, wrap: false, font: GUI.SubHeadingFont);
|
||||
var inLabel = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), textsArea.RectTransform, Anchor.CenterRight), TextManager.Get("PumpIn"),
|
||||
textColor: GUI.Style.TextColor, textAlignment: Alignment.CenterRight, wrap: false, font: GUI.SubHeadingFont);
|
||||
GUITextBlock.AutoScaleAndNormalize(outLabel, inLabel);
|
||||
}
|
||||
|
||||
public override void OnItemLoaded()
|
||||
{
|
||||
base.OnItemLoaded();
|
||||
if (pumpSpeedSlider != null)
|
||||
{
|
||||
pumpSpeedSlider.BarScroll = (flowPercentage + 100.0f) / 200.0f;
|
||||
}
|
||||
}
|
||||
|
||||
partial void UpdateProjSpecific(float deltaTime)
|
||||
{
|
||||
if (FlowPercentage < 0.0f)
|
||||
{
|
||||
foreach (Pair<Vector2, ParticleEmitter> pumpOutEmitter in pumpOutEmitters)
|
||||
{
|
||||
//only emit "pump out" particles when underwater
|
||||
Vector2 particlePos = item.Rect.Location.ToVector2() + pumpOutEmitter.First;
|
||||
if (item.CurrentHull != null && item.CurrentHull.Surface < particlePos.Y) continue;
|
||||
|
||||
pumpOutEmitter.Second.Emit(deltaTime, item.WorldRect.Location.ToVector2() + pumpOutEmitter.First * item.Scale, item.CurrentHull,
|
||||
velocityMultiplier: MathHelper.Lerp(0.5f, 1.0f, -FlowPercentage / 100.0f));
|
||||
}
|
||||
}
|
||||
else if (FlowPercentage > 0.0f)
|
||||
{
|
||||
foreach (Pair<Vector2, ParticleEmitter> pumpInEmitter in pumpInEmitters)
|
||||
{
|
||||
pumpInEmitter.Second.Emit(deltaTime, item.WorldRect.Location.ToVector2() + pumpInEmitter.First * item.Scale, item.CurrentHull,
|
||||
velocityMultiplier: MathHelper.Lerp(0.5f, 1.0f, FlowPercentage / 100.0f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private float flickerTimer;
|
||||
private readonly float flickerFrequency = 1;
|
||||
public override void UpdateHUD(Character character, float deltaTime, Camera cam)
|
||||
{
|
||||
pumpSpeedLockTimer -= deltaTime;
|
||||
isActiveLockTimer -= deltaTime;
|
||||
autoControlIndicator.Selected = pumpSpeedLockTimer > 0.0f || isActiveLockTimer > 0.0f;
|
||||
PowerButton.Enabled = isActiveLockTimer <= 0.0f;
|
||||
if (HasPower)
|
||||
{
|
||||
flickerTimer = 0;
|
||||
powerLight.Selected = IsActive;
|
||||
}
|
||||
else if (IsActive)
|
||||
{
|
||||
flickerTimer += deltaTime;
|
||||
if (flickerTimer > flickerFrequency)
|
||||
{
|
||||
flickerTimer = 0;
|
||||
powerLight.Selected = !powerLight.Selected;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
flickerTimer = 0;
|
||||
powerLight.Selected = false;
|
||||
}
|
||||
pumpSpeedSlider.Enabled = pumpSpeedLockTimer <= 0.0f && IsActive;
|
||||
if (!PlayerInput.PrimaryMouseButtonHeld())
|
||||
{
|
||||
float pumpSpeedScroll = (FlowPercentage + 100.0f) / 200.0f;
|
||||
if (Math.Abs(pumpSpeedScroll - pumpSpeedSlider.BarScroll) > 0.01f)
|
||||
{
|
||||
pumpSpeedSlider.BarScroll = pumpSpeedScroll;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ClientWrite(IWriteMessage msg, object[] extraData = null)
|
||||
{
|
||||
//flowpercentage can only be adjusted at 10% intervals -> no need for more accuracy than this
|
||||
msg.WriteRangedInteger((int)(flowPercentage / 10.0f), -10, 10);
|
||||
msg.Write(IsActive);
|
||||
}
|
||||
|
||||
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
|
||||
{
|
||||
if (correctionTimer > 0.0f)
|
||||
{
|
||||
StartDelayedCorrection(type, msg.ExtractBits(5 + 1), sendingTime);
|
||||
return;
|
||||
}
|
||||
|
||||
FlowPercentage = msg.ReadRangedInteger(-10, 10) * 10.0f;
|
||||
IsActive = msg.ReadBoolean();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,749 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class Reactor : Powered, IServerSerializable, IClientSerializable
|
||||
{
|
||||
public GUIButton AutoTempSwitch { get; private set; }
|
||||
|
||||
public GUIButton PowerButton { get; private set; }
|
||||
private GUITickBox powerLight;
|
||||
private GUITickBox autoTempLight;
|
||||
|
||||
private const int GraphSize = 25;
|
||||
private float graphTimer;
|
||||
private readonly int updateGraphInterval = 500;
|
||||
|
||||
private Sprite fissionRateMeter, turbineOutputMeter;
|
||||
private Sprite meterPointer;
|
||||
private Sprite sectorSprite;
|
||||
|
||||
private Sprite tempMeterFrame, tempMeterBar;
|
||||
private Sprite tempRangeIndicator;
|
||||
|
||||
private Sprite graphLine;
|
||||
//private GUIFrame graph;
|
||||
|
||||
private Color optimalRangeColor = new Color(74,238,104,255);
|
||||
private Color offRangeColor = Color.Orange;
|
||||
private Color warningColor = Color.Red;
|
||||
private Color coldColor = Color.LightBlue;
|
||||
private Color warmColor = Color.Orange;
|
||||
private Color hotColor = Color.Red;
|
||||
private Color outputColor = Color.Goldenrod;
|
||||
private Color loadColor = Color.LightSteelBlue;
|
||||
|
||||
public GUIScrollBar FissionRateScrollBar { get; private set; }
|
||||
|
||||
public GUIScrollBar TurbineOutputScrollBar { get; private set; }
|
||||
|
||||
private readonly float[] outputGraph = new float[GraphSize];
|
||||
private readonly float[] loadGraph = new float[GraphSize];
|
||||
|
||||
private GUITickBox criticalHeatWarning;
|
||||
private GUITickBox lowTemperatureWarning;
|
||||
private GUITickBox criticalOutputWarning;
|
||||
|
||||
private GUIFrame inventoryContainer;
|
||||
|
||||
private readonly Dictionary<string, GUIButton> warningButtons = new Dictionary<string, GUIButton>();
|
||||
|
||||
private static readonly string[] warningTexts = new string[]
|
||||
{
|
||||
"ReactorWarningLowTemp", "ReactorWarningLowOutput", "ReactorWarningLowFuel", "ReactorWarningMeltdown",
|
||||
"ReactorWarningOverheating", "ReactorWarningHighOutput", "ReactorWarningFuelOut", "ReactorWarningSCRAM"
|
||||
};
|
||||
|
||||
partial void InitProjSpecific(XElement element)
|
||||
{
|
||||
// TODO: need to recreate the gui when the resolution changes
|
||||
|
||||
fissionRateMeter = new Sprite(element.GetChildElement("fissionratemeter")?.GetChildElement("sprite"));
|
||||
turbineOutputMeter = new Sprite(element.GetChildElement("turbineoutputmeter")?.GetChildElement("sprite"));
|
||||
meterPointer = new Sprite(element.GetChildElement("meterpointer")?.GetChildElement("sprite"));
|
||||
sectorSprite = new Sprite(element.GetChildElement("sectorsprite")?.GetChildElement("sprite"));
|
||||
tempMeterFrame = new Sprite(element.GetChildElement("tempmeterframe")?.GetChildElement("sprite"));
|
||||
tempMeterBar = new Sprite(element.GetChildElement("tempmeterbar")?.GetChildElement("sprite"));
|
||||
tempRangeIndicator = new Sprite(element.GetChildElement("temprangeindicator")?.GetChildElement("sprite"));
|
||||
graphLine = new Sprite(element.GetChildElement("graphline")?.GetChildElement("sprite"));
|
||||
|
||||
var paddedFrame = new GUILayoutGroup(new RectTransform(
|
||||
GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center)
|
||||
{ AbsoluteOffset = GUIStyle.ItemFrameOffset },
|
||||
isHorizontal: true)
|
||||
{
|
||||
RelativeSpacing = 0.012f,
|
||||
Stretch = true
|
||||
};
|
||||
|
||||
GUILayoutGroup columnLeft = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), paddedFrame.RectTransform))
|
||||
{
|
||||
RelativeSpacing = 0.012f,
|
||||
Stretch = true
|
||||
};
|
||||
GUILayoutGroup columnRight = new GUILayoutGroup(new RectTransform(new Vector2(0.4f, 1.0f), paddedFrame.RectTransform))
|
||||
{
|
||||
CanBeFocused = true,
|
||||
RelativeSpacing = 0.012f,
|
||||
Stretch = true
|
||||
};
|
||||
|
||||
//----------------------------------------------------------
|
||||
//left column
|
||||
//----------------------------------------------------------
|
||||
|
||||
GUIFrame inventoryWindow = new GUIFrame(new RectTransform(new Vector2(0.1f, 0.75f), GuiFrame.RectTransform, Anchor.TopLeft, Pivot.TopRight)
|
||||
{
|
||||
MinSize = new Point(85, 220),
|
||||
RelativeOffset = new Vector2(-0.02f, 0)
|
||||
}, style: "ItemUI");
|
||||
|
||||
GUILayoutGroup inventoryContent = new GUILayoutGroup(new RectTransform(inventoryWindow.Rect.Size - GUIStyle.ItemFrameMargin, inventoryWindow.RectTransform, Anchor.Center)
|
||||
{ AbsoluteOffset = GUIStyle.ItemFrameOffset },
|
||||
childAnchor: Anchor.TopCenter)
|
||||
{
|
||||
Stretch = true
|
||||
};
|
||||
|
||||
/*new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), inventoryContent.RectTransform), "",
|
||||
textAlignment: Alignment.Center, font: GUI.SubHeadingFont, wrap: true);*/
|
||||
inventoryContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.9f), inventoryContent.RectTransform), style: null);
|
||||
|
||||
//----------------------------------------------------------
|
||||
//mid column
|
||||
//----------------------------------------------------------
|
||||
|
||||
var topLeftArea = new GUILayoutGroup(new RectTransform(new Vector2(1, 0.2f), columnLeft.RectTransform),
|
||||
isHorizontal: true, childAnchor: Anchor.CenterLeft)
|
||||
{
|
||||
Stretch = true
|
||||
};
|
||||
|
||||
|
||||
Point maxIndicatorSize = new Point(int.MaxValue, (int)(40 * GUI.Scale));
|
||||
criticalHeatWarning = new GUITickBox(new RectTransform(new Vector2(0.33f, 1.0f), topLeftArea.RectTransform) { MaxSize = maxIndicatorSize },
|
||||
TextManager.Get("ReactorWarningCriticalTemp"), font: GUI.SubHeadingFont, style: "IndicatorLightRed")
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
lowTemperatureWarning = new GUITickBox(new RectTransform(new Vector2(0.33f, 1.0f), topLeftArea.RectTransform) { MaxSize = maxIndicatorSize },
|
||||
TextManager.Get("ReactorWarningCriticalLowTemp"), font: GUI.SubHeadingFont, style: "IndicatorLightRed")
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
criticalOutputWarning = new GUITickBox(new RectTransform(new Vector2(0.33f, 1.0f), topLeftArea.RectTransform) { MaxSize = maxIndicatorSize },
|
||||
TextManager.Get("ReactorWarningCriticalOutput"), font: GUI.SubHeadingFont, style: "IndicatorLightRed")
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
List<GUITickBox> indicatorLights = new List<GUITickBox>() { criticalHeatWarning, lowTemperatureWarning, criticalOutputWarning };
|
||||
indicatorLights.ForEach(l => l.TextBlock.OverrideTextColor(GUI.Style.TextColor));
|
||||
topLeftArea.Recalculate();
|
||||
|
||||
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), columnLeft.RectTransform), style: "HorizontalLine");
|
||||
|
||||
float relativeYMargin = 0.02f;
|
||||
Vector2 relativeTextSize = new Vector2(0.9f, 0.2f);
|
||||
Vector2 sliderSize = new Vector2(1.0f, 0.125f);
|
||||
Vector2 meterSize = new Vector2(1, 1 - relativeTextSize.Y - relativeYMargin - sliderSize.Y - 0.1f);
|
||||
|
||||
var meterArea = new GUIFrame(new RectTransform(new Vector2(1, 0.6f - relativeYMargin * 2), columnLeft.RectTransform), style: null);
|
||||
var leftArea = new GUIFrame(new RectTransform(new Vector2(0.49f, 1), meterArea.RectTransform), style: null);
|
||||
var rightArea = new GUIFrame(new RectTransform(new Vector2(0.49f, 1), meterArea.RectTransform, Anchor.TopCenter, Pivot.TopLeft), style: null);
|
||||
|
||||
var fissionRateTextBox = new GUITextBlock(new RectTransform(relativeTextSize, leftArea.RectTransform, Anchor.TopCenter),
|
||||
TextManager.Get("ReactorFissionRate"), textColor: GUI.Style.TextColor, textAlignment: Alignment.Center, font: GUI.SubHeadingFont)
|
||||
{
|
||||
AutoScaleHorizontal = true
|
||||
};
|
||||
var fissionMeter = new GUICustomComponent(new RectTransform(meterSize, leftArea.RectTransform, Anchor.TopCenter)
|
||||
{
|
||||
RelativeOffset = new Vector2(0.0f, relativeTextSize.Y + relativeYMargin)
|
||||
},
|
||||
DrawFissionRateMeter, null)
|
||||
{
|
||||
ToolTip = TextManager.Get("ReactorTipFissionRate")
|
||||
};
|
||||
|
||||
var turbineOutputTextBox = new GUITextBlock(new RectTransform(relativeTextSize, rightArea.RectTransform, Anchor.TopCenter),
|
||||
TextManager.Get("ReactorTurbineOutput"), textColor: GUI.Style.TextColor, textAlignment: Alignment.Center, font: GUI.SubHeadingFont)
|
||||
{
|
||||
AutoScaleHorizontal = true
|
||||
};
|
||||
GUITextBlock.AutoScaleAndNormalize(turbineOutputTextBox, fissionRateTextBox);
|
||||
|
||||
var turbineMeter = new GUICustomComponent(new RectTransform(meterSize, rightArea.RectTransform, Anchor.TopCenter)
|
||||
{
|
||||
RelativeOffset = new Vector2(0.0f, relativeTextSize.Y + relativeYMargin)
|
||||
},
|
||||
DrawTurbineOutputMeter, null)
|
||||
{
|
||||
ToolTip = TextManager.Get("ReactorTipTurbineOutput")
|
||||
};
|
||||
|
||||
FissionRateScrollBar = new GUIScrollBar(new RectTransform(sliderSize, leftArea.RectTransform, Anchor.TopCenter)
|
||||
{
|
||||
RelativeOffset = new Vector2(0, fissionMeter.RectTransform.RelativeOffset.Y + meterSize.Y)
|
||||
},
|
||||
style: "DeviceSlider", barSize: 0.1f)
|
||||
{
|
||||
Enabled = false,
|
||||
OnMoved = (GUIScrollBar bar, float scrollAmount) =>
|
||||
{
|
||||
LastUser = Character.Controlled;
|
||||
unsentChanges = true;
|
||||
targetFissionRate = scrollAmount * 100.0f;
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
TurbineOutputScrollBar = new GUIScrollBar(new RectTransform(sliderSize, rightArea.RectTransform, Anchor.TopCenter)
|
||||
{
|
||||
RelativeOffset = new Vector2(0, turbineMeter.RectTransform.RelativeOffset.Y + meterSize.Y)
|
||||
},
|
||||
style: "DeviceSlider", barSize: 0.1f, isHorizontal: true)
|
||||
{
|
||||
Enabled = false,
|
||||
OnMoved = (GUIScrollBar bar, float scrollAmount) =>
|
||||
{
|
||||
LastUser = Character.Controlled;
|
||||
unsentChanges = true;
|
||||
targetTurbineOutput = scrollAmount * 100.0f;
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
var buttonArea = new GUILayoutGroup(new RectTransform(new Vector2(1, 0.2f), columnLeft.RectTransform))
|
||||
{
|
||||
Stretch = true,
|
||||
RelativeSpacing = 0.02f
|
||||
};
|
||||
var upperButtons = new GUILayoutGroup(new RectTransform(new Vector2(1, 0.5f), buttonArea.RectTransform), isHorizontal: true)
|
||||
{
|
||||
Stretch = true,
|
||||
RelativeSpacing = 0.01f
|
||||
};
|
||||
var lowerButtons = new GUILayoutGroup(new RectTransform(new Vector2(1, 0.5f), buttonArea.RectTransform), isHorizontal: true)
|
||||
{
|
||||
Stretch = true,
|
||||
RelativeSpacing = 0.01f
|
||||
};
|
||||
int buttonCount = warningTexts.Length;
|
||||
for (int i = 0; i < buttonCount; i++)
|
||||
{
|
||||
string text = warningTexts[i];
|
||||
var b = new GUIButton(new RectTransform(Vector2.One, (i < 4) ? upperButtons.RectTransform : lowerButtons.RectTransform),
|
||||
TextManager.Get(text), style: "IndicatorButton")
|
||||
{
|
||||
Font = GUI.SubHeadingFont,
|
||||
CanBeFocused = false
|
||||
};
|
||||
warningButtons.Add(text, b);
|
||||
}
|
||||
upperButtons.Recalculate();
|
||||
lowerButtons.Recalculate();
|
||||
//only wrap texts that consist of multiple words and are way too big to fit otherwise
|
||||
warningButtons.Values.ForEach(b => b.TextBlock.Wrap = b.Text.Contains(' ') && b.TextBlock.TextSize.X > b.TextBlock.Rect.Width * 1.5f);
|
||||
GUITextBlock.AutoScaleAndNormalize(warningButtons.Values.Select(b => b.TextBlock));
|
||||
|
||||
//----------------------------------------------------------
|
||||
//right column
|
||||
//----------------------------------------------------------
|
||||
|
||||
// Auto temp
|
||||
var topRightArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), columnRight.RectTransform),
|
||||
isHorizontal: true, childAnchor: Anchor.CenterLeft)
|
||||
{
|
||||
Stretch = true,
|
||||
RelativeSpacing = 0.02f
|
||||
};
|
||||
topRightArea.RectTransform.MinSize = new Point(0, topLeftArea.Rect.Height);
|
||||
topRightArea.RectTransform.MaxSize = new Point(int.MaxValue, topLeftArea.Rect.Height);
|
||||
|
||||
new GUIFrame(new RectTransform(new Vector2(0.01f, 1.0f), topRightArea.RectTransform), style: "VerticalLine");
|
||||
|
||||
AutoTempSwitch = new GUIButton(new RectTransform(new Vector2(0.15f, 0.9f), topRightArea.RectTransform),
|
||||
style: "SwitchVertical")
|
||||
{
|
||||
Enabled = false,
|
||||
Selected = AutoTemp,
|
||||
OnClicked = (button, data) =>
|
||||
{
|
||||
AutoTemp = !AutoTemp;
|
||||
LastUser = Character.Controlled;
|
||||
unsentChanges = true;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
AutoTempSwitch.RectTransform.MaxSize = new Point((int)(AutoTempSwitch.Rect.Height * 0.4f), int.MaxValue);
|
||||
|
||||
autoTempLight = new GUITickBox(new RectTransform(new Vector2(0.4f, 1.0f), topRightArea.RectTransform),
|
||||
TextManager.Get("ReactorAutoTemp"), font: GUI.SubHeadingFont, style: "IndicatorLightYellow")
|
||||
{
|
||||
ToolTip = TextManager.Get("ReactorTipAutoTemp"),
|
||||
CanBeFocused = false,
|
||||
Selected = AutoTemp
|
||||
};
|
||||
autoTempLight.RectTransform.MaxSize = new Point(int.MaxValue, criticalHeatWarning.Rect.Height);
|
||||
autoTempLight.TextBlock.OverrideTextColor(GUI.Style.TextColor);
|
||||
|
||||
new GUIFrame(new RectTransform(new Vector2(0.01f, 1.0f), topRightArea.RectTransform), style: "VerticalLine");
|
||||
|
||||
// Power button
|
||||
var powerArea = new GUIFrame(new RectTransform(new Vector2(0.4f, 1.0f), topRightArea.RectTransform), style: null);
|
||||
var paddedPowerArea = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.9f), powerArea.RectTransform, Anchor.Center, scaleBasis: ScaleBasis.BothHeight), style: "PowerButtonFrame");
|
||||
powerLight = new GUITickBox(new RectTransform(new Vector2(0.87f, 0.3f), paddedPowerArea.RectTransform, Anchor.TopCenter, Pivot.Center),
|
||||
TextManager.Get("PowerLabel"), font: GUI.SubHeadingFont, style: "IndicatorLightPower")
|
||||
{
|
||||
CanBeFocused = false,
|
||||
Selected = _powerOn
|
||||
};
|
||||
powerLight.TextBlock.Padding = new Vector4(5.0f, 0.0f, 0.0f, 0.0f);
|
||||
powerLight.TextBlock.AutoScaleHorizontal = true;
|
||||
powerLight.TextBlock.OverrideTextColor(GUI.Style.TextColor);
|
||||
PowerButton = new GUIButton(new RectTransform(new Vector2(0.8f, 0.75f), paddedPowerArea.RectTransform, Anchor.BottomCenter)
|
||||
{
|
||||
RelativeOffset = new Vector2(0, 0.1f)
|
||||
}, style: "PowerButton")
|
||||
{
|
||||
OnClicked = (button, data) =>
|
||||
{
|
||||
PowerOn = !PowerOn;
|
||||
LastUser = Character.Controlled;
|
||||
unsentChanges = true;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
topRightArea.Recalculate();
|
||||
autoTempLight.TextBlock.Wrap = true;
|
||||
indicatorLights.Add(autoTempLight);
|
||||
GUITextBlock.AutoScaleAndNormalize(indicatorLights.Select(l => l.TextBlock));
|
||||
|
||||
// right bottom (graph area) -----------------------
|
||||
|
||||
new GUIFrame(new RectTransform(new Vector2(0.95f, 0.01f), columnRight.RectTransform), style: "HorizontalLine");
|
||||
|
||||
var bottomRightArea = new GUILayoutGroup(new RectTransform(Vector2.One, columnRight.RectTransform), isHorizontal: true)
|
||||
{
|
||||
Stretch = true,
|
||||
CanBeFocused = true,
|
||||
RelativeSpacing = 0.02f
|
||||
};
|
||||
|
||||
new GUIFrame(new RectTransform(new Vector2(0.01f, 1.0f), bottomRightArea.RectTransform), style: "VerticalLine");
|
||||
|
||||
new GUICustomComponent(new RectTransform(new Vector2(0.1f, 1), bottomRightArea.RectTransform, Anchor.Center), DrawTempMeter, null);
|
||||
|
||||
var graphArea = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 1.0f), bottomRightArea.RectTransform))
|
||||
{
|
||||
Stretch = true,
|
||||
RelativeSpacing = 0.02f
|
||||
};
|
||||
|
||||
relativeTextSize = new Vector2(1.0f, 0.15f);
|
||||
var loadText = new GUITextBlock(new RectTransform(relativeTextSize, graphArea.RectTransform),
|
||||
"Load", textColor: loadColor, font: GUI.SubHeadingFont, textAlignment: Alignment.CenterLeft)
|
||||
{
|
||||
ToolTip = TextManager.Get("ReactorTipLoad")
|
||||
};
|
||||
string loadStr = TextManager.Get("ReactorLoad");
|
||||
string kW = TextManager.Get("kilowatt");
|
||||
loadText.TextGetter += () => $"{loadStr.Replace("[kw]", ((int)load).ToString())} {kW}";
|
||||
|
||||
var graph = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.9f), graphArea.RectTransform), style: "InnerFrameRed");
|
||||
new GUICustomComponent(new RectTransform(new Vector2(0.9f, 0.98f), graph.RectTransform, Anchor.Center), DrawGraph, null);
|
||||
|
||||
var outputText = new GUITextBlock(new RectTransform(relativeTextSize, graphArea.RectTransform),
|
||||
"Output", textColor: outputColor, font: GUI.SubHeadingFont, textAlignment: Alignment.CenterLeft)
|
||||
{
|
||||
ToolTip = TextManager.Get("ReactorTipPower")
|
||||
};
|
||||
string outputStr = TextManager.Get("ReactorOutput");
|
||||
outputText.TextGetter += () => $"{outputStr.Replace("[kw]", ((int)-currPowerConsumption).ToString())} {kW}";
|
||||
}
|
||||
|
||||
public override void OnItemLoaded()
|
||||
{
|
||||
base.OnItemLoaded();
|
||||
TurbineOutputScrollBar.BarScroll = targetTurbineOutput / 100.0f;
|
||||
FissionRateScrollBar.BarScroll = targetFissionRate / 100.0f;
|
||||
var itemContainer = item.GetComponent<ItemContainer>();
|
||||
if (itemContainer != null)
|
||||
{
|
||||
itemContainer.UILabel = "";
|
||||
itemContainer.AllowUIOverlap = true;
|
||||
itemContainer.Inventory.RectTransform = inventoryContainer.RectTransform;
|
||||
/*var inventoryLabel = inventoryContainer.Parent?.GetChild<GUITextBlock>();
|
||||
if (inventoryLabel != null)
|
||||
{
|
||||
inventoryLabel.RectTransform.MinSize = new Point(100, 0);
|
||||
inventoryLabel.Text = itemContainer.GetUILabel();
|
||||
inventoryLabel.CalculateHeightFromText();
|
||||
(inventoryLabel.Parent as GUILayoutGroup).Recalculate();
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawTempMeter(SpriteBatch spriteBatch, GUICustomComponent container)
|
||||
{
|
||||
Vector2 meterPos = new Vector2(container.Rect.X, container.Rect.Y);
|
||||
Vector2 meterScale = new Vector2(container.Rect.Width / (float)tempMeterFrame.SourceRect.Width, container.Rect.Height / (float)tempMeterFrame.SourceRect.Height);
|
||||
tempMeterFrame.Draw(spriteBatch, meterPos, Color.White, tempMeterFrame.Origin, 0.0f, scale: meterScale);
|
||||
|
||||
float tempFill = temperature / 100.0f;
|
||||
float meterBarScale = container.Rect.Width / (float)tempMeterBar.SourceRect.Width;
|
||||
Vector2 meterBarPos = new Vector2(container.Center.X, container.Rect.Bottom - tempMeterBar.size.Y * meterBarScale - (int)(5 * GUI.yScale));
|
||||
while (meterBarPos.Y > container.Rect.Bottom + (int)(5 * GUI.yScale) - container.Rect.Height * tempFill)
|
||||
{
|
||||
float tempRatio = 1.0f - ((meterBarPos.Y - container.Rect.Y) / container.Rect.Height);
|
||||
Color color = ToolBox.GradientLerp(tempRatio, coldColor, optimalRangeColor, warmColor, hotColor);
|
||||
tempMeterBar.Draw(spriteBatch, meterBarPos, color: color, scale: meterBarScale);
|
||||
int spacing = 2;
|
||||
meterBarPos.Y -= tempMeterBar.size.Y * meterBarScale + spacing;
|
||||
}
|
||||
|
||||
if (temperature > optimalTemperature.Y)
|
||||
{
|
||||
GUI.DrawRectangle(spriteBatch,
|
||||
meterPos,
|
||||
new Vector2(container.Rect.Width, (container.Rect.Bottom - container.Rect.Height * optimalTemperature.Y / 100.0f) - container.Rect.Y),
|
||||
warningColor * (float)Math.Sin(Timing.TotalTime * 5.0f) * 0.7f, isFilled: true);
|
||||
}
|
||||
if (temperature < optimalTemperature.X)
|
||||
{
|
||||
GUI.DrawRectangle(spriteBatch,
|
||||
new Vector2(meterPos.X, container.Rect.Bottom - container.Rect.Height * optimalTemperature.X / 100.0f),
|
||||
new Vector2(container.Rect.Width, container.Rect.Bottom - (container.Rect.Bottom - container.Rect.Height * optimalTemperature.X / 100.0f)),
|
||||
warningColor * (float)Math.Sin(Timing.TotalTime * 5.0f) * 0.7f, isFilled: true);
|
||||
}
|
||||
|
||||
float tempRangeIndicatorScale = container.Rect.Width / (float)tempRangeIndicator.SourceRect.Width;
|
||||
tempRangeIndicator.Draw(spriteBatch, new Vector2(container.Center.X, container.Rect.Bottom - container.Rect.Height * optimalTemperature.X / 100.0f), Color.White, tempRangeIndicator.Origin, 0, scale: tempRangeIndicatorScale);
|
||||
tempRangeIndicator.Draw(spriteBatch, new Vector2(container.Center.X, container.Rect.Bottom - container.Rect.Height * optimalTemperature.Y / 100.0f), Color.White, tempRangeIndicator.Origin, 0, scale: tempRangeIndicatorScale);
|
||||
}
|
||||
|
||||
private void DrawGraph(SpriteBatch spriteBatch, GUICustomComponent container)
|
||||
{
|
||||
if (item.Removed) { return; }
|
||||
float maxLoad = loadGraph.Max();
|
||||
float xOffset = graphTimer / updateGraphInterval;
|
||||
Rectangle graphRect = new Rectangle(container.Rect.X, container.Rect.Y, container.Rect.Width, container.Rect.Height - (int)(5 * GUI.yScale));
|
||||
DrawGraph(outputGraph, spriteBatch, graphRect, Math.Max(10000.0f, maxLoad), xOffset, outputColor);
|
||||
DrawGraph(loadGraph, spriteBatch, graphRect, Math.Max(10000.0f, maxLoad), xOffset, loadColor);
|
||||
}
|
||||
|
||||
|
||||
private void UpdateGraph(float deltaTime)
|
||||
{
|
||||
graphTimer += deltaTime * 1000.0f;
|
||||
|
||||
if (graphTimer > updateGraphInterval)
|
||||
{
|
||||
UpdateGraph(outputGraph, -currPowerConsumption);
|
||||
UpdateGraph(loadGraph, load);
|
||||
|
||||
graphTimer = 0.0f;
|
||||
}
|
||||
|
||||
if (autoTemp)
|
||||
{
|
||||
FissionRateScrollBar.BarScroll = FissionRate / 100.0f;
|
||||
TurbineOutputScrollBar.BarScroll = TurbineOutput / 100.0f;
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawFissionRateMeter(SpriteBatch spriteBatch, GUICustomComponent container)
|
||||
{
|
||||
if (item.Removed) { return; }
|
||||
|
||||
Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
|
||||
spriteBatch.End();
|
||||
spriteBatch.GraphicsDevice.ScissorRectangle = container.Rect;
|
||||
spriteBatch.Begin(SpriteSortMode.Deferred, rasterizerState: GameMain.ScissorTestEnable);
|
||||
|
||||
//make the pointer jitter a bit if it's at the upper limit of the fission rate
|
||||
float jitter = 0.0f;
|
||||
if (FissionRate > allowedFissionRate.Y - 5.0f)
|
||||
{
|
||||
float jitterAmount = Math.Min(targetFissionRate - allowedFissionRate.Y, 10.0f);
|
||||
float t = graphTimer / updateGraphInterval;
|
||||
|
||||
jitter = (PerlinNoise.GetPerlin(t * 0.5f, t * 0.1f) - 0.5f) * jitterAmount;
|
||||
}
|
||||
|
||||
DrawMeter(spriteBatch, container.Rect,
|
||||
fissionRateMeter, FissionRate + jitter, new Vector2(0.0f, 100.0f), optimalFissionRate, allowedFissionRate);
|
||||
|
||||
spriteBatch.End();
|
||||
spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect;
|
||||
spriteBatch.Begin(SpriteSortMode.Deferred);
|
||||
}
|
||||
|
||||
private void DrawTurbineOutputMeter(SpriteBatch spriteBatch, GUICustomComponent container)
|
||||
{
|
||||
if (item.Removed) { return; }
|
||||
|
||||
DrawMeter(spriteBatch, container.Rect,
|
||||
turbineOutputMeter, TurbineOutput, new Vector2(0.0f, 100.0f), optimalTurbineOutput, allowedTurbineOutput);
|
||||
}
|
||||
|
||||
public override void UpdateHUD(Character character, float deltaTime, Camera cam)
|
||||
{
|
||||
IsActive = true;
|
||||
|
||||
bool lightOn = Timing.TotalTime % 0.5f < 0.25f && PowerOn;
|
||||
|
||||
criticalHeatWarning.Selected = temperature > allowedTemperature.Y && lightOn;
|
||||
lowTemperatureWarning.Selected = temperature < allowedTemperature.X && lightOn;
|
||||
criticalOutputWarning.Selected = -currPowerConsumption > load * 1.5f && lightOn;
|
||||
|
||||
warningButtons["ReactorWarningOverheating"].Selected = temperature > optimalTemperature.Y && lightOn;
|
||||
warningButtons["ReactorWarningHighOutput"].Selected = -currPowerConsumption > load * 1.1f && lightOn;
|
||||
warningButtons["ReactorWarningLowTemp"].Selected = temperature < optimalTemperature.X && lightOn;
|
||||
warningButtons["ReactorWarningLowOutput"].Selected = -currPowerConsumption < load * 0.9f && lightOn;
|
||||
warningButtons["ReactorWarningFuelOut"].Selected = prevAvailableFuel < fissionRate * 0.01f && lightOn;
|
||||
warningButtons["ReactorWarningLowFuel"].Selected = prevAvailableFuel < fissionRate && lightOn;
|
||||
warningButtons["ReactorWarningMeltdown"].Selected = meltDownTimer > MeltdownDelay * 0.5f || item.Condition == 0.0f && lightOn;
|
||||
warningButtons["ReactorWarningSCRAM"].Selected = temperature > 0.1f && !PowerOn;
|
||||
|
||||
if ((FissionRateScrollBar.Rect.Contains(PlayerInput.MousePosition) || FissionRateScrollBar.Children.Contains(GUIScrollBar.DraggingBar) ||
|
||||
TurbineOutputScrollBar.Rect.Contains(PlayerInput.MousePosition) || TurbineOutputScrollBar.Children.Contains(GUIScrollBar.DraggingBar)) &&
|
||||
!PlayerInput.KeyDown(InputType.Deselect) && !PlayerInput.KeyHit(InputType.Deselect))
|
||||
{
|
||||
Character.DisableControls = true;
|
||||
}
|
||||
|
||||
if (!PowerOn)
|
||||
{
|
||||
FissionRateScrollBar.BarScroll = FissionRate / 100.0f;
|
||||
TurbineOutputScrollBar.BarScroll = TurbineOutput / 100.0f;
|
||||
}
|
||||
else if (!autoTemp && Character.DisableControls && GUI.KeyboardDispatcher.Subscriber == null)
|
||||
{
|
||||
Vector2 input = Vector2.Zero;
|
||||
float rate = 50.0f; //percentage per second
|
||||
|
||||
if (PlayerInput.KeyDown(InputType.Left)) input.X += -1.0f;
|
||||
if (PlayerInput.KeyDown(InputType.Right)) input.X += 1.0f;
|
||||
if (PlayerInput.KeyDown(InputType.Up)) input.Y += 1.0f;
|
||||
if (PlayerInput.KeyDown(InputType.Down)) input.Y += -1.0f;
|
||||
if (PlayerInput.KeyDown(InputType.Run))
|
||||
rate = 200.0f;
|
||||
else if (PlayerInput.KeyDown(InputType.Crouch))
|
||||
rate = 20.0f;
|
||||
|
||||
rate *= deltaTime;
|
||||
input.X *= rate;
|
||||
input.Y *= rate;
|
||||
|
||||
if (input.LengthSquared() > 0)
|
||||
{
|
||||
LastUser = Character.Controlled;
|
||||
unsentChanges = true;
|
||||
if (input.X != 0.0f && GUIScrollBar.DraggingBar != FissionRateScrollBar)
|
||||
{
|
||||
targetFissionRate = MathHelper.Clamp(targetFissionRate + input.X, 0.0f, 100.0f);
|
||||
FissionRateScrollBar.BarScroll += input.X / 100.0f;
|
||||
}
|
||||
if (input.Y != 0.0f && GUIScrollBar.DraggingBar != TurbineOutputScrollBar)
|
||||
{
|
||||
targetTurbineOutput = MathHelper.Clamp(targetTurbineOutput + input.Y, 0.0f, 100.0f);
|
||||
TurbineOutputScrollBar.BarScroll += input.Y / 100.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawMeter(SpriteBatch spriteBatch, Rectangle rect, Sprite meterSprite, float value, Vector2 range, Vector2 optimalRange, Vector2 allowedRange)
|
||||
{
|
||||
float scale = Math.Min(rect.Width / meterSprite.size.X, rect.Height / meterSprite.size.Y);
|
||||
Vector2 pos = new Vector2(rect.Center.X, rect.Y + meterSprite.Origin.Y * scale);
|
||||
|
||||
Vector2 optimalRangeNormalized = new Vector2(
|
||||
MathHelper.Clamp((optimalRange.X - range.X) / (range.Y - range.X), 0.0f, 0.95f),
|
||||
MathHelper.Clamp((optimalRange.Y - range.X) / (range.Y - range.X), 0.0f, 1.0f));
|
||||
|
||||
Vector2 allowedRangeNormalized = new Vector2(
|
||||
MathHelper.Clamp((allowedRange.X - range.X) / (range.Y - range.X), 0.0f, 0.95f),
|
||||
MathHelper.Clamp((allowedRange.Y - range.X) / (range.Y - range.X), 0.0f, 1.0f));
|
||||
|
||||
Vector2 sectorRad = new Vector2(-1.57f, 1.57f);
|
||||
|
||||
Vector2 optimalSectorRad = new Vector2(
|
||||
MathHelper.Lerp(sectorRad.X, sectorRad.Y, optimalRangeNormalized.X),
|
||||
MathHelper.Lerp(sectorRad.X, sectorRad.Y, optimalRangeNormalized.Y));
|
||||
|
||||
Vector2 allowedSectorRad = new Vector2(
|
||||
MathHelper.Lerp(sectorRad.X, sectorRad.Y, allowedRangeNormalized.X),
|
||||
MathHelper.Lerp(sectorRad.X, sectorRad.Y, allowedRangeNormalized.Y));
|
||||
|
||||
if (optimalRangeNormalized.X == optimalRangeNormalized.Y)
|
||||
{
|
||||
sectorSprite.Draw(spriteBatch, pos, GUI.Style.Red, MathHelper.PiOver2, scale);
|
||||
}
|
||||
else
|
||||
{
|
||||
spriteBatch.End();
|
||||
Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
|
||||
spriteBatch.GraphicsDevice.ScissorRectangle = new Rectangle(0, 0, GameMain.GraphicsWidth, (int)(pos.Y + (meterSprite.size.Y - meterSprite.Origin.Y) * scale) - 3);
|
||||
spriteBatch.Begin(SpriteSortMode.Deferred, rasterizerState: GameMain.ScissorTestEnable);
|
||||
|
||||
float scaleMultiplier = 0.95f;
|
||||
sectorSprite.Draw(spriteBatch, pos, optimalRangeColor, MathHelper.PiOver2 + (allowedSectorRad.X + allowedSectorRad.Y) / 2.0f, scale * scaleMultiplier);
|
||||
sectorSprite.Draw(spriteBatch, pos, offRangeColor, optimalSectorRad.X, scale * scaleMultiplier);
|
||||
sectorSprite.Draw(spriteBatch, pos, warningColor, allowedSectorRad.X, scale * scaleMultiplier);
|
||||
sectorSprite.Draw(spriteBatch, pos, offRangeColor, MathHelper.Pi + optimalSectorRad.Y, scale * scaleMultiplier);
|
||||
sectorSprite.Draw(spriteBatch, pos, warningColor, MathHelper.Pi + allowedSectorRad.Y, scale * scaleMultiplier);
|
||||
|
||||
spriteBatch.End();
|
||||
spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect;
|
||||
spriteBatch.Begin(SpriteSortMode.Deferred);
|
||||
}
|
||||
|
||||
meterSprite.Draw(spriteBatch, pos, 0, scale);
|
||||
|
||||
float normalizedValue = (value - range.X) / (range.Y - range.X);
|
||||
float valueRad = MathHelper.Lerp(sectorRad.X, sectorRad.Y, normalizedValue);
|
||||
Vector2 offset = new Vector2(0, 40) * scale;
|
||||
meterPointer.Draw(spriteBatch, pos - offset, valueRad, scale);
|
||||
}
|
||||
|
||||
static void UpdateGraph<T>(IList<T> graph, T newValue)
|
||||
{
|
||||
for (int i = graph.Count - 1; i > 0; i--)
|
||||
{
|
||||
graph[i] = graph[i - 1];
|
||||
}
|
||||
graph[0] = newValue;
|
||||
}
|
||||
|
||||
private void DrawGraph(IList<float> graph, SpriteBatch spriteBatch, Rectangle rect, float maxVal, float xOffset, Color color)
|
||||
{
|
||||
Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
|
||||
spriteBatch.End();
|
||||
spriteBatch.GraphicsDevice.ScissorRectangle = rect;
|
||||
spriteBatch.Begin(SpriteSortMode.Deferred, rasterizerState: GameMain.ScissorTestEnable);
|
||||
|
||||
float lineWidth = (float)rect.Width / (float)(graph.Count - 2);
|
||||
float yScale = (float)rect.Height / maxVal;
|
||||
|
||||
Vector2 prevPoint = new Vector2(rect.Right, rect.Bottom - (graph[1] + (graph[0] - graph[1]) * xOffset) * yScale);
|
||||
|
||||
float currX = rect.Right - ((xOffset - 1.0f) * lineWidth);
|
||||
|
||||
for (int i = 1; i < graph.Count - 1; i++)
|
||||
{
|
||||
currX -= lineWidth;
|
||||
|
||||
Vector2 newPoint = new Vector2(currX, rect.Bottom - graph[i] * yScale);
|
||||
|
||||
if (graphLine?.Texture == null)
|
||||
{
|
||||
GUI.DrawLine(spriteBatch, prevPoint, newPoint - new Vector2(1.0f, 0), color);
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector2 dir = Vector2.Normalize(newPoint - prevPoint);
|
||||
GUI.DrawLine(spriteBatch, graphLine.Texture, prevPoint - dir, newPoint + dir, color, 0, 5);
|
||||
}
|
||||
|
||||
prevPoint = newPoint;
|
||||
}
|
||||
|
||||
Vector2 lastPoint = new Vector2(rect.X,
|
||||
rect.Bottom - (graph[graph.Count - 1] + (graph[graph.Count - 2] - graph[graph.Count - 1]) * xOffset) * yScale);
|
||||
|
||||
if (graphLine?.Texture == null)
|
||||
{
|
||||
GUI.DrawLine(spriteBatch, prevPoint, lastPoint, color);
|
||||
}
|
||||
else
|
||||
{
|
||||
GUI.DrawLine(spriteBatch, graphLine.Texture, prevPoint, lastPoint + (lastPoint - prevPoint), color, 0, 5);
|
||||
}
|
||||
|
||||
spriteBatch.End();
|
||||
spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect;
|
||||
spriteBatch.Begin(SpriteSortMode.Deferred);
|
||||
}
|
||||
|
||||
protected override void RemoveComponentSpecific()
|
||||
{
|
||||
base.RemoveComponentSpecific();
|
||||
graphLine?.Remove();
|
||||
fissionRateMeter?.Remove();
|
||||
turbineOutputMeter?.Remove();
|
||||
meterPointer?.Remove();
|
||||
sectorSprite?.Remove();
|
||||
tempMeterFrame?.Remove();
|
||||
tempMeterBar?.Remove();
|
||||
tempRangeIndicator?.Remove();
|
||||
}
|
||||
|
||||
public void ClientWrite(IWriteMessage msg, object[] extraData = null)
|
||||
{
|
||||
msg.Write(autoTemp);
|
||||
msg.Write(PowerOn);
|
||||
msg.WriteRangedSingle(targetFissionRate, 0.0f, 100.0f, 8);
|
||||
msg.WriteRangedSingle(targetTurbineOutput, 0.0f, 100.0f, 8);
|
||||
|
||||
correctionTimer = CorrectionDelay;
|
||||
}
|
||||
|
||||
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
|
||||
{
|
||||
if (correctionTimer > 0.0f)
|
||||
{
|
||||
StartDelayedCorrection(type, msg.ExtractBits(1 + 1 + 8 + 8 + 8 + 8), sendingTime);
|
||||
return;
|
||||
}
|
||||
|
||||
AutoTemp = msg.ReadBoolean();
|
||||
PowerOn = msg.ReadBoolean();
|
||||
Temperature = msg.ReadRangedSingle(0.0f, 100.0f, 8);
|
||||
targetFissionRate = msg.ReadRangedSingle(0.0f, 100.0f, 8);
|
||||
targetTurbineOutput = msg.ReadRangedSingle(0.0f, 100.0f, 8);
|
||||
degreeOfSuccess = msg.ReadRangedSingle(0.0f, 1.0f, 8);
|
||||
|
||||
FissionRateScrollBar.BarScroll = targetFissionRate / 100.0f;
|
||||
TurbineOutputScrollBar.BarScroll = targetTurbineOutput / 100.0f;
|
||||
|
||||
IsActive = true;
|
||||
}
|
||||
|
||||
private void UpdateUIElementStates()
|
||||
{
|
||||
if (powerLight != null)
|
||||
{
|
||||
powerLight.Selected = _powerOn;
|
||||
}
|
||||
if (AutoTempSwitch != null)
|
||||
{
|
||||
AutoTempSwitch.Selected = autoTemp;
|
||||
AutoTempSwitch.Enabled = _powerOn;
|
||||
}
|
||||
if (autoTempLight != null)
|
||||
{
|
||||
autoTempLight.Selected = autoTemp && _powerOn;
|
||||
}
|
||||
if (FissionRateScrollBar != null)
|
||||
{
|
||||
FissionRateScrollBar.Enabled = _powerOn && !autoTemp;
|
||||
}
|
||||
if (TurbineOutputScrollBar != null)
|
||||
{
|
||||
TurbineOutputScrollBar.Enabled = _powerOn && !autoTemp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,924 @@
|
||||
using Barotrauma.Networking;
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class Steering : Powered, IServerSerializable, IClientSerializable
|
||||
{
|
||||
private GUIButton steeringModeSwitch;
|
||||
private GUITickBox autopilotIndicator, manualPilotIndicator;
|
||||
|
||||
enum Destination
|
||||
{
|
||||
MaintainPos,
|
||||
LevelEnd,
|
||||
LevelStart
|
||||
};
|
||||
private GUITickBox maintainPosTickBox, levelEndTickBox, levelStartTickBox;
|
||||
|
||||
private GUIComponent statusContainer, dockingContainer, controlContainer;
|
||||
|
||||
private bool dockingNetworkMessagePending;
|
||||
|
||||
private GUIButton dockingButton;
|
||||
private string dockText, undockText;
|
||||
|
||||
private GUIComponent steerArea;
|
||||
|
||||
private GUITextBlock pressureWarningText;
|
||||
|
||||
private GUITextBlock tipContainer;
|
||||
|
||||
private string noPowerTip, autoPilotMaintainPosTip, autoPilotLevelStartTip, autoPilotLevelEndTip;
|
||||
|
||||
private Sprite maintainPosIndicator, maintainPosOriginIndicator;
|
||||
private Sprite steeringIndicator;
|
||||
|
||||
private List<DockingPort> connectedPorts = new List<DockingPort>();
|
||||
private float checkConnectedPortsTimer;
|
||||
private const float CheckConnectedPortsInterval = 1.0f;
|
||||
|
||||
private Vector2 keyboardInput = Vector2.Zero;
|
||||
private float inputCumulation;
|
||||
|
||||
private bool? swapDestinationOrder;
|
||||
|
||||
private bool levelStartSelected;
|
||||
public bool LevelStartSelected
|
||||
{
|
||||
get { return levelStartTickBox.Selected; }
|
||||
set { levelStartTickBox.Selected = value; }
|
||||
}
|
||||
|
||||
private bool levelEndSelected;
|
||||
public bool LevelEndSelected
|
||||
{
|
||||
get { return levelEndTickBox.Selected; }
|
||||
set { levelEndTickBox.Selected = value; }
|
||||
}
|
||||
|
||||
private bool maintainPos;
|
||||
public bool MaintainPos
|
||||
{
|
||||
get { return maintainPosTickBox.Selected; }
|
||||
set { maintainPosTickBox.Selected = value; }
|
||||
}
|
||||
|
||||
private float steerRadius;
|
||||
public float? SteerRadius
|
||||
{
|
||||
get
|
||||
{
|
||||
return steerRadius;
|
||||
}
|
||||
set
|
||||
{
|
||||
steerRadius = value ?? (steerArea.Rect.Width / 2);
|
||||
}
|
||||
}
|
||||
|
||||
partial void InitProjSpecific(XElement element)
|
||||
{
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "steeringindicator":
|
||||
steeringIndicator = new Sprite(subElement);
|
||||
break;
|
||||
case "maintainposindicator":
|
||||
maintainPosIndicator = new Sprite(subElement);
|
||||
break;
|
||||
case "maintainposoriginindicator":
|
||||
maintainPosOriginIndicator = new Sprite(subElement);
|
||||
break;
|
||||
}
|
||||
}
|
||||
CreateGUI();
|
||||
GameMain.Instance.OnResolutionChanged += RecreateGUI;
|
||||
}
|
||||
|
||||
private void CreateGUI()
|
||||
{
|
||||
controlContainer = new GUIFrame(new RectTransform(new Vector2(Sonar.controlBoxSize.X, 1 - Sonar.controlBoxSize.Y * 2), GuiFrame.RectTransform, Anchor.CenterLeft), "ItemUI");
|
||||
var paddedControlContainer = new GUIFrame(new RectTransform(controlContainer.Rect.Size - GUIStyle.ItemFrameMargin, controlContainer.RectTransform, Anchor.Center)
|
||||
{
|
||||
AbsoluteOffset = GUIStyle.ItemFrameOffset
|
||||
}, style: null);
|
||||
|
||||
var steeringModeArea = new GUIFrame(new RectTransform(new Vector2(1, 0.4f), paddedControlContainer.RectTransform, Anchor.TopLeft), style: null);
|
||||
steeringModeSwitch = new GUIButton(new RectTransform(new Vector2(0.2f, 1), steeringModeArea.RectTransform), string.Empty, style: "SwitchVertical")
|
||||
{
|
||||
Selected = autoPilot,
|
||||
Enabled = true,
|
||||
OnClicked = (button, data) =>
|
||||
{
|
||||
button.Selected = !button.Selected;
|
||||
AutoPilot = button.Selected;
|
||||
if (GameMain.Client != null)
|
||||
{
|
||||
unsentChanges = true;
|
||||
user = Character.Controlled;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
var steeringModeRightSide = new GUIFrame(new RectTransform(new Vector2(1.0f - steeringModeSwitch.RectTransform.RelativeSize.X, 0.8f), steeringModeArea.RectTransform, Anchor.CenterLeft)
|
||||
{
|
||||
RelativeOffset = new Vector2(steeringModeSwitch.RectTransform.RelativeSize.X, 0)
|
||||
}, style: null);
|
||||
manualPilotIndicator = new GUITickBox(new RectTransform(new Vector2(1, 0.45f), steeringModeRightSide.RectTransform, Anchor.TopLeft),
|
||||
TextManager.Get("SteeringManual"), font: GUI.SubHeadingFont, style: "IndicatorLightRedSmall")
|
||||
{
|
||||
Selected = !autoPilot,
|
||||
Enabled = false
|
||||
};
|
||||
autopilotIndicator = new GUITickBox(new RectTransform(new Vector2(1, 0.45f), steeringModeRightSide.RectTransform, Anchor.BottomLeft),
|
||||
TextManager.Get("SteeringAutoPilot"), font: GUI.SubHeadingFont, style: "IndicatorLightRedSmall")
|
||||
{
|
||||
Selected = autoPilot,
|
||||
Enabled = false
|
||||
};
|
||||
manualPilotIndicator.TextBlock.OverrideTextColor(GUI.Style.TextColor);
|
||||
autopilotIndicator.TextBlock.OverrideTextColor(GUI.Style.TextColor);
|
||||
GUITextBlock.AutoScaleAndNormalize(manualPilotIndicator.TextBlock, autopilotIndicator.TextBlock);
|
||||
|
||||
var autoPilotControls = new GUIFrame(new RectTransform(new Vector2(0.75f, 0.62f), paddedControlContainer.RectTransform, Anchor.BottomCenter), "OutlineFrame");
|
||||
var paddedAutoPilotControls = new GUIFrame(new RectTransform(new Vector2(0.92f, 0.88f), autoPilotControls.RectTransform, Anchor.Center), style: null);
|
||||
|
||||
maintainPosTickBox = new GUITickBox(new RectTransform(new Vector2(1, 0.333f), paddedAutoPilotControls.RectTransform, Anchor.TopCenter),
|
||||
TextManager.Get("SteeringMaintainPos"), font: GUI.SmallFont, style: "GUIRadioButton")
|
||||
{
|
||||
Enabled = autoPilot,
|
||||
Selected = maintainPos,
|
||||
OnSelected = tickBox =>
|
||||
{
|
||||
if (maintainPos != tickBox.Selected)
|
||||
{
|
||||
unsentChanges = true;
|
||||
user = Character.Controlled;
|
||||
maintainPos = tickBox.Selected;
|
||||
if (maintainPos)
|
||||
{
|
||||
if (controlledSub == null)
|
||||
{
|
||||
posToMaintain = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
posToMaintain = controlledSub.WorldPosition;
|
||||
}
|
||||
}
|
||||
else if (!LevelEndSelected && !LevelStartSelected)
|
||||
{
|
||||
AutoPilot = false;
|
||||
}
|
||||
if (!maintainPos)
|
||||
{
|
||||
posToMaintain = null;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
int textLimit = (int)(MathHelper.Clamp(25 * GUI.xScale, 15, 35));
|
||||
levelStartTickBox = new GUITickBox(new RectTransform(new Vector2(1, 0.333f), paddedAutoPilotControls.RectTransform, Anchor.Center),
|
||||
GameMain.GameSession?.StartLocation == null ? "" : ToolBox.LimitString(GameMain.GameSession.StartLocation.Name, textLimit),
|
||||
font: GUI.SmallFont, style: "GUIRadioButton")
|
||||
{
|
||||
Enabled = autoPilot,
|
||||
Selected = levelStartSelected,
|
||||
OnSelected = tickBox =>
|
||||
{
|
||||
if (levelStartSelected != tickBox.Selected)
|
||||
{
|
||||
unsentChanges = true;
|
||||
user = Character.Controlled;
|
||||
levelStartSelected = tickBox.Selected;
|
||||
levelEndSelected = !levelStartSelected;
|
||||
if (levelStartSelected)
|
||||
{
|
||||
UpdatePath();
|
||||
}
|
||||
else if (!MaintainPos && !LevelEndSelected)
|
||||
{
|
||||
AutoPilot = false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
levelEndTickBox = new GUITickBox(new RectTransform(new Vector2(1, 0.333f), paddedAutoPilotControls.RectTransform, Anchor.BottomCenter),
|
||||
GameMain.GameSession?.EndLocation == null ? "" : ToolBox.LimitString(GameMain.GameSession.EndLocation.Name, textLimit),
|
||||
font: GUI.SmallFont, style: "GUIRadioButton")
|
||||
{
|
||||
Enabled = autoPilot,
|
||||
Selected = levelEndSelected,
|
||||
OnSelected = tickBox =>
|
||||
{
|
||||
if (levelEndSelected != tickBox.Selected)
|
||||
{
|
||||
unsentChanges = true;
|
||||
user = Character.Controlled;
|
||||
levelEndSelected = tickBox.Selected;
|
||||
levelStartSelected = !levelEndSelected;
|
||||
if (levelEndSelected)
|
||||
{
|
||||
UpdatePath();
|
||||
}
|
||||
else if (!MaintainPos && !LevelStartSelected)
|
||||
{
|
||||
AutoPilot = false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
maintainPosTickBox.RectTransform.IsFixedSize = levelStartTickBox.RectTransform.IsFixedSize = levelEndTickBox.RectTransform.IsFixedSize = false;
|
||||
maintainPosTickBox.RectTransform.MaxSize = levelStartTickBox.RectTransform.MaxSize = levelEndTickBox.RectTransform.MaxSize =
|
||||
new Point(int.MaxValue, paddedAutoPilotControls.Rect.Height / 3);
|
||||
maintainPosTickBox.RectTransform.MinSize = levelStartTickBox.RectTransform.MinSize = levelEndTickBox.RectTransform.MinSize =
|
||||
Point.Zero;
|
||||
|
||||
GUITextBlock.AutoScaleAndNormalize(scaleHorizontal: false, scaleVertical: true, maintainPosTickBox.TextBlock, levelStartTickBox.TextBlock, levelEndTickBox.TextBlock);
|
||||
|
||||
GUIRadioButtonGroup destinations = new GUIRadioButtonGroup();
|
||||
destinations.AddRadioButton((int)Destination.MaintainPos, maintainPosTickBox);
|
||||
destinations.AddRadioButton((int)Destination.LevelStart, levelStartTickBox);
|
||||
destinations.AddRadioButton((int)Destination.LevelEnd, levelEndTickBox);
|
||||
destinations.Selected = (int)(maintainPos ? Destination.MaintainPos :
|
||||
levelStartSelected ? Destination.LevelStart : Destination.LevelEnd);
|
||||
|
||||
// Status ->
|
||||
statusContainer = new GUIFrame(new RectTransform(Sonar.controlBoxSize, GuiFrame.RectTransform, Anchor.BottomLeft)
|
||||
{
|
||||
RelativeOffset = Sonar.controlBoxOffset
|
||||
}, "ItemUI");
|
||||
var paddedStatusContainer = new GUIFrame(new RectTransform(statusContainer.Rect.Size - GUIStyle.ItemFrameMargin, statusContainer.RectTransform, Anchor.Center, isFixedSize: false)
|
||||
{
|
||||
AbsoluteOffset = GUIStyle.ItemFrameOffset
|
||||
}, style: null);
|
||||
|
||||
var elements = GUI.CreateElements(3, new Vector2(1f, 0.333f), paddedStatusContainer.RectTransform, rt => new GUIFrame(rt, style: null), Anchor.TopCenter, relativeSpacing: 0.01f);
|
||||
List<GUIComponent> leftElements = new List<GUIComponent>(), centerElements = new List<GUIComponent>(), rightElements = new List<GUIComponent>();
|
||||
for (int i = 0; i < elements.Count; i++)
|
||||
{
|
||||
var e = elements[i];
|
||||
var group = new GUILayoutGroup(new RectTransform(Vector2.One, e.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft)
|
||||
{
|
||||
RelativeSpacing = 0.01f,
|
||||
Stretch = true
|
||||
};
|
||||
var left = new GUIFrame(new RectTransform(new Vector2(0.45f, 1), group.RectTransform), style: null);
|
||||
var center = new GUIFrame(new RectTransform(new Vector2(0.15f, 1), group.RectTransform), style: null);
|
||||
var right = new GUIFrame(new RectTransform(new Vector2(0.4f, 0.8f), group.RectTransform), style: null);
|
||||
leftElements.Add(left);
|
||||
centerElements.Add(center);
|
||||
rightElements.Add(right);
|
||||
string leftText = string.Empty, centerText = string.Empty;
|
||||
GUITextBlock.TextGetterHandler rightTextGetter = null;
|
||||
switch (i)
|
||||
{
|
||||
case 0:
|
||||
leftText = TextManager.Get("DescentVelocity");
|
||||
centerText = $"({TextManager.Get("KilometersPerHour")})";
|
||||
rightTextGetter = () =>
|
||||
{
|
||||
Vector2 vel = controlledSub == null ? Vector2.Zero : controlledSub.Velocity;
|
||||
var realWorldVel = ConvertUnits.ToDisplayUnits(vel.Y * Physics.DisplayToRealWorldRatio) * 3.6f;
|
||||
return ((int)(-realWorldVel)).ToString();
|
||||
};
|
||||
break;
|
||||
case 1:
|
||||
leftText = TextManager.Get("Velocity");
|
||||
centerText = $"({TextManager.Get("KilometersPerHour")})";
|
||||
rightTextGetter = () =>
|
||||
{
|
||||
Vector2 vel = controlledSub == null ? Vector2.Zero : controlledSub.Velocity;
|
||||
var realWorldVel = ConvertUnits.ToDisplayUnits(vel.X * Physics.DisplayToRealWorldRatio) * 3.6f;
|
||||
return ((int)realWorldVel).ToString();
|
||||
};
|
||||
break;
|
||||
case 2:
|
||||
leftText = TextManager.Get("Depth");
|
||||
centerText = $"({TextManager.Get("Meter")})";
|
||||
rightTextGetter = () =>
|
||||
{
|
||||
Vector2 pos = controlledSub == null ? Vector2.Zero : controlledSub.Position;
|
||||
float realWorldDepth = Level.Loaded == null ? 0.0f : Math.Abs(pos.Y - Level.Loaded.Size.Y) * Physics.DisplayToRealWorldRatio;
|
||||
return ((int)realWorldDepth).ToString();
|
||||
};
|
||||
break;
|
||||
}
|
||||
new GUITextBlock(new RectTransform(Vector2.One, left.RectTransform), leftText, font: GUI.SubHeadingFont, wrap: true, textAlignment: Alignment.CenterRight);
|
||||
new GUITextBlock(new RectTransform(Vector2.One, center.RectTransform), centerText, font: GUI.Font, textAlignment: Alignment.Center) { Padding = Vector4.Zero };
|
||||
var digitalFrame = new GUIFrame(new RectTransform(Vector2.One, right.RectTransform), style: "DigitalFrameDark");
|
||||
new GUITextBlock(new RectTransform(Vector2.One * 0.85f, digitalFrame.RectTransform, Anchor.Center), "12345", GUI.Style.TextColorDark, GUI.DigitalFont, Alignment.CenterRight)
|
||||
{
|
||||
TextGetter = rightTextGetter
|
||||
};
|
||||
}
|
||||
GUITextBlock.AutoScaleAndNormalize(leftElements.SelectMany(e => e.GetAllChildren<GUITextBlock>()));
|
||||
GUITextBlock.AutoScaleAndNormalize(centerElements.SelectMany(e => e.GetAllChildren<GUITextBlock>()));
|
||||
GUITextBlock.AutoScaleAndNormalize(rightElements.SelectMany(e => e.GetAllChildren<GUITextBlock>()));
|
||||
|
||||
//docking interface ----------------------------------------------------
|
||||
float dockingButtonSize = 1.1f;
|
||||
float elementScale = 0.6f;
|
||||
dockingContainer = new GUIFrame(new RectTransform(Sonar.controlBoxSize, GuiFrame.RectTransform, Anchor.BottomLeft, scaleBasis: ScaleBasis.Smallest)
|
||||
{
|
||||
RelativeOffset = new Vector2(Sonar.controlBoxOffset.X + 0.05f, Sonar.controlBoxOffset.Y)
|
||||
}, style: null);
|
||||
|
||||
dockText = TextManager.Get("label.navterminaldock", fallBackTag: "captain.dock");
|
||||
undockText = TextManager.Get("label.navterminalundock", fallBackTag: "captain.undock");
|
||||
dockingButton = new GUIButton(new RectTransform(new Vector2(elementScale), dockingContainer.RectTransform, Anchor.Center), dockText, style: "PowerButton")
|
||||
{
|
||||
OnClicked = (btn, userdata) =>
|
||||
{
|
||||
if (GameMain.Client == null)
|
||||
{
|
||||
item.SendSignal(0, "1", "toggle_docking", sender: null);
|
||||
}
|
||||
else
|
||||
{
|
||||
dockingNetworkMessagePending = true;
|
||||
item.CreateClientEvent(this);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
dockingButton.Font = GUI.SubHeadingFont;
|
||||
dockingButton.TextBlock.RectTransform.MaxSize = new Point((int)(dockingButton.Rect.Width * 0.7f), int.MaxValue);
|
||||
dockingButton.TextBlock.AutoScaleHorizontal = true;
|
||||
|
||||
var style = GUI.Style.GetComponentStyle("DockingButtonUp");
|
||||
Sprite buttonSprite = style.Sprites.FirstOrDefault().Value.FirstOrDefault()?.Sprite;
|
||||
Point buttonSize = buttonSprite != null ? buttonSprite.size.ToPoint() : new Point(149, 52);
|
||||
Point horizontalButtonSize = buttonSize.Multiply(elementScale * GUI.Scale * dockingButtonSize);
|
||||
Point verticalButtonSize = horizontalButtonSize.Flip();
|
||||
var leftButton = new GUIButton(new RectTransform(verticalButtonSize, dockingContainer.RectTransform, Anchor.CenterLeft), "", style: "DockingButtonLeft")
|
||||
{
|
||||
OnClicked = NudgeButtonClicked,
|
||||
UserData = -Vector2.UnitX
|
||||
};
|
||||
var rightButton = new GUIButton(new RectTransform(verticalButtonSize, dockingContainer.RectTransform, Anchor.CenterRight), "", style: "DockingButtonRight")
|
||||
{
|
||||
OnClicked = NudgeButtonClicked,
|
||||
UserData = Vector2.UnitX
|
||||
};
|
||||
var upButton = new GUIButton(new RectTransform(horizontalButtonSize, dockingContainer.RectTransform, Anchor.TopCenter), "", style: "DockingButtonUp")
|
||||
{
|
||||
OnClicked = NudgeButtonClicked,
|
||||
UserData = Vector2.UnitY
|
||||
};
|
||||
var downButton = new GUIButton(new RectTransform(horizontalButtonSize, dockingContainer.RectTransform, Anchor.BottomCenter), "", style: "DockingButtonDown")
|
||||
{
|
||||
OnClicked = NudgeButtonClicked,
|
||||
UserData = -Vector2.UnitY
|
||||
};
|
||||
|
||||
// Sonar area
|
||||
steerArea = new GUICustomComponent(new RectTransform(Vector2.One * GUI.RelativeHorizontalAspectRatio * Sonar.sonarAreaSize, GuiFrame.RectTransform, Anchor.CenterRight, scaleBasis: ScaleBasis.Smallest),
|
||||
(spriteBatch, guiCustomComponent) => { DrawHUD(spriteBatch, guiCustomComponent.Rect); }, null);
|
||||
steerRadius = steerArea.Rect.Width / 2;
|
||||
|
||||
pressureWarningText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.25f), steerArea.RectTransform, Anchor.Center, Pivot.TopCenter),
|
||||
TextManager.Get("SteeringDepthWarning"), Color.Red, GUI.LargeFont, Alignment.Center)
|
||||
{
|
||||
Visible = false
|
||||
};
|
||||
// Tooltip/helper text
|
||||
tipContainer = new GUITextBlock(new RectTransform(new Vector2(0.5f, 0.1f), steerArea.RectTransform, Anchor.BottomCenter, Pivot.TopCenter)
|
||||
, "", font: GUI.Font, wrap: true, style: "GUIToolTip", textAlignment: Alignment.Center)
|
||||
{
|
||||
AutoScaleHorizontal = true
|
||||
};
|
||||
noPowerTip = TextManager.Get("SteeringNoPowerTip");
|
||||
autoPilotMaintainPosTip = TextManager.Get("SteeringAutoPilotMaintainPosTip");
|
||||
autoPilotLevelStartTip = TextManager.GetWithVariable("SteeringAutoPilotLocationTip", "[locationname]",
|
||||
GameMain.GameSession?.StartLocation == null ? "Start" : GameMain.GameSession.StartLocation.Name);
|
||||
autoPilotLevelEndTip = TextManager.GetWithVariable("SteeringAutoPilotLocationTip", "[locationname]",
|
||||
GameMain.GameSession?.EndLocation == null ? "End" : GameMain.GameSession.EndLocation.Name);
|
||||
}
|
||||
|
||||
private void RecreateGUI()
|
||||
{
|
||||
GuiFrame.ClearChildren();
|
||||
CreateGUI();
|
||||
UpdateGUIElements();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Makes the sonar view CustomComponent render the steering HUD, preventing it from being drawn behing the sonar
|
||||
/// </summary>
|
||||
public void AttachToSonarHUD(GUICustomComponent sonarView)
|
||||
{
|
||||
steerArea.Visible = false;
|
||||
sonarView.OnDraw += (spriteBatch, guiCustomComponent) => { DrawHUD(spriteBatch, guiCustomComponent.Rect); };
|
||||
}
|
||||
|
||||
public void DrawHUD(SpriteBatch spriteBatch, Rectangle rect)
|
||||
{
|
||||
int width = rect.Width, height = rect.Height;
|
||||
int x = rect.X;
|
||||
int y = rect.Y;
|
||||
|
||||
if (Voltage < MinVoltage) { return; }
|
||||
|
||||
Rectangle velRect = new Rectangle(x + 20, y + 20, width - 40, height - 40);
|
||||
Vector2 displaySubPos = (-sonar.DisplayOffset * sonar.Zoom) / sonar.Range * sonar.DisplayRadius * sonar.Zoom;
|
||||
displaySubPos.Y = -displaySubPos.Y;
|
||||
displaySubPos = displaySubPos.ClampLength(velRect.Width / 2);
|
||||
displaySubPos = steerArea.Rect.Center.ToVector2() + displaySubPos;
|
||||
|
||||
if (!AutoPilot)
|
||||
{
|
||||
Vector2 unitSteeringInput = steeringInput / 100.0f;
|
||||
//map input from rectangle to circle
|
||||
Vector2 steeringInputPos = new Vector2(
|
||||
steeringInput.X * (float)Math.Sqrt(1.0f - 0.5f * unitSteeringInput.Y * unitSteeringInput.Y),
|
||||
-steeringInput.Y * (float)Math.Sqrt(1.0f - 0.5f * unitSteeringInput.X * unitSteeringInput.X));
|
||||
steeringInputPos += displaySubPos;
|
||||
|
||||
if (steeringIndicator != null)
|
||||
{
|
||||
Vector2 dir = steeringInputPos - displaySubPos;
|
||||
float angle = (float)Math.Atan2(dir.Y, dir.X);
|
||||
steeringIndicator.Draw(spriteBatch, displaySubPos, Color.White, origin: steeringIndicator.Origin, rotate: angle,
|
||||
scale: new Vector2(dir.Length() / steeringIndicator.size.X, 1.0f));
|
||||
}
|
||||
else
|
||||
{
|
||||
GUI.DrawLine(spriteBatch, displaySubPos, steeringInputPos, Color.LightGray);
|
||||
GUI.DrawRectangle(spriteBatch, new Rectangle((int)steeringInputPos.X - 5, (int)steeringInputPos.Y - 5, 10, 10), Color.White);
|
||||
}
|
||||
|
||||
if (velRect.Contains(PlayerInput.MousePosition))
|
||||
{
|
||||
GUI.DrawRectangle(spriteBatch, new Rectangle((int)steeringInputPos.X - 4, (int)steeringInputPos.Y - 4, 8, 8), GUI.Style.Red, thickness: 2);
|
||||
}
|
||||
}
|
||||
else if (posToMaintain.HasValue && !LevelStartSelected && !LevelEndSelected)
|
||||
{
|
||||
Sonar sonar = item.GetComponent<Sonar>();
|
||||
if (sonar != null && controlledSub != null)
|
||||
{
|
||||
Vector2 displayPosToMaintain = ((posToMaintain.Value - sonar.DisplayOffset * sonar.Zoom - controlledSub.WorldPosition)) / sonar.Range * sonar.DisplayRadius * sonar.Zoom;
|
||||
displayPosToMaintain.Y = -displayPosToMaintain.Y;
|
||||
displayPosToMaintain = displayPosToMaintain.ClampLength(velRect.Width / 2);
|
||||
displayPosToMaintain = steerArea.Rect.Center.ToVector2() + displayPosToMaintain;
|
||||
|
||||
Color crosshairColor = GUI.Style.Orange * (0.5f + ((float)Math.Sin(Timing.TotalTime * 5.0f) + 1.0f) / 4.0f);
|
||||
if (maintainPosIndicator != null)
|
||||
{
|
||||
maintainPosIndicator.Draw(spriteBatch, displayPosToMaintain, crosshairColor, scale: 0.5f * sonar.Zoom);
|
||||
}
|
||||
else
|
||||
{
|
||||
float crossHairSize = 8.0f;
|
||||
GUI.DrawLine(spriteBatch, displayPosToMaintain + Vector2.UnitY * crossHairSize, displayPosToMaintain - Vector2.UnitY * crossHairSize, crosshairColor, width: 3);
|
||||
GUI.DrawLine(spriteBatch, displayPosToMaintain + Vector2.UnitX * crossHairSize, displayPosToMaintain - Vector2.UnitX * crossHairSize, crosshairColor, width: 3);
|
||||
}
|
||||
|
||||
if (maintainPosOriginIndicator != null)
|
||||
{
|
||||
maintainPosOriginIndicator.Draw(spriteBatch, displaySubPos, GUI.Style.Orange, scale: 0.5f * sonar.Zoom);
|
||||
}
|
||||
else
|
||||
{
|
||||
GUI.DrawRectangle(spriteBatch, new Rectangle((int)displaySubPos.X - 5, (int)displaySubPos.Y - 5, 10, 10), GUI.Style.Orange);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//map velocity from rectangle to circle
|
||||
Vector2 unitTargetVel = targetVelocity / 100.0f;
|
||||
Vector2 steeringPos = new Vector2(
|
||||
targetVelocity.X * 0.9f * (float)Math.Sqrt(1.0f - 0.5f * unitTargetVel.Y * unitTargetVel.Y),
|
||||
-targetVelocity.Y * 0.9f * (float)Math.Sqrt(1.0f - 0.5f * unitTargetVel.X * unitTargetVel.X));
|
||||
steeringPos += displaySubPos;
|
||||
|
||||
|
||||
if (steeringIndicator != null)
|
||||
{
|
||||
Vector2 dir = steeringPos - displaySubPos;
|
||||
float angle = (float)Math.Atan2(dir.Y, dir.X);
|
||||
steeringIndicator.Draw(spriteBatch, displaySubPos, Color.Gray, origin: steeringIndicator.Origin, rotate: angle,
|
||||
scale: new Vector2(dir.Length() / steeringIndicator.size.X, 0.7f));
|
||||
}
|
||||
else
|
||||
{
|
||||
GUI.DrawLine(spriteBatch,
|
||||
displaySubPos,
|
||||
steeringPos,
|
||||
Color.CadetBlue, 0, 2);
|
||||
}
|
||||
}
|
||||
|
||||
public void DebugDrawHUD(SpriteBatch spriteBatch, Vector2 transducerCenter, float displayScale, float displayRadius, Vector2 center)
|
||||
{
|
||||
if (SteeringPath == null) return;
|
||||
|
||||
Vector2 prevPos = Vector2.Zero;
|
||||
foreach (WayPoint wp in SteeringPath.Nodes)
|
||||
{
|
||||
Vector2 pos = (wp.Position - transducerCenter) * displayScale;
|
||||
if (pos.Length() > displayRadius) continue;
|
||||
|
||||
pos.Y = -pos.Y;
|
||||
pos += center;
|
||||
|
||||
GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X - 3 / 2, (int)pos.Y - 3, 6, 6), (SteeringPath.CurrentNode == wp) ? Color.LightGreen : GUI.Style.Green, false);
|
||||
|
||||
if (prevPos != Vector2.Zero)
|
||||
{
|
||||
GUI.DrawLine(spriteBatch, pos, prevPos, GUI.Style.Green);
|
||||
}
|
||||
|
||||
prevPos = pos;
|
||||
}
|
||||
|
||||
foreach (ObstacleDebugInfo obstacle in debugDrawObstacles)
|
||||
{
|
||||
Vector2 pos1 = (obstacle.Point1 - transducerCenter) * displayScale;
|
||||
pos1.Y = -pos1.Y;
|
||||
pos1 += center;
|
||||
Vector2 pos2 = (obstacle.Point2 - transducerCenter) * displayScale;
|
||||
pos2.Y = -pos2.Y;
|
||||
pos2 += center;
|
||||
|
||||
GUI.DrawLine(spriteBatch,
|
||||
pos1,
|
||||
pos2,
|
||||
GUI.Style.Red * 0.6f, width: 3);
|
||||
|
||||
if (obstacle.Intersection.HasValue)
|
||||
{
|
||||
Vector2 intersectionPos = (obstacle.Intersection.Value - transducerCenter) *displayScale;
|
||||
intersectionPos.Y = -intersectionPos.Y;
|
||||
intersectionPos += center;
|
||||
GUI.DrawRectangle(spriteBatch, intersectionPos - Vector2.One * 2, Vector2.One * 4, GUI.Style.Red);
|
||||
}
|
||||
|
||||
Vector2 obstacleCenter = (pos1 + pos2) / 2;
|
||||
if (obstacle.AvoidStrength.LengthSquared() > 0.01f)
|
||||
{
|
||||
GUI.DrawLine(spriteBatch,
|
||||
obstacleCenter,
|
||||
obstacleCenter + new Vector2(obstacle.AvoidStrength.X, -obstacle.AvoidStrength.Y) * 100,
|
||||
Color.Lerp(GUI.Style.Green, GUI.Style.Orange, obstacle.Dot), width: 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void UpdateHUD(Character character, float deltaTime, Camera cam)
|
||||
{
|
||||
if (swapDestinationOrder == null)
|
||||
{
|
||||
swapDestinationOrder = item.Submarine != null && item.Submarine.FlippedX;
|
||||
if (swapDestinationOrder.Value)
|
||||
{
|
||||
levelStartTickBox.RectTransform.SetAsLastChild();
|
||||
}
|
||||
}
|
||||
|
||||
if (steerArea.Rect.Contains(PlayerInput.MousePosition))
|
||||
{
|
||||
if (!PlayerInput.KeyDown(InputType.Deselect) && !PlayerInput.KeyHit(InputType.Deselect))
|
||||
{
|
||||
Character.DisableControls = true;
|
||||
}
|
||||
}
|
||||
|
||||
dockingContainer.Visible = DockingModeEnabled;
|
||||
statusContainer.Visible = !DockingModeEnabled;
|
||||
|
||||
if (DockingModeEnabled && ActiveDockingSource != null)
|
||||
{
|
||||
if (Math.Abs(ActiveDockingSource.Item.WorldPosition.X - DockingTarget.Item.WorldPosition.X) < ActiveDockingSource.DistanceTolerance.X &&
|
||||
Math.Abs(ActiveDockingSource.Item.WorldPosition.Y - DockingTarget.Item.WorldPosition.Y) < ActiveDockingSource.DistanceTolerance.Y)
|
||||
{
|
||||
dockingButton.Text = dockText;
|
||||
if (dockingButton.FlashTimer <= 0.0f)
|
||||
{
|
||||
dockingButton.Flash(GUI.Style.Blue, 0.5f, useCircularFlash: true);
|
||||
dockingButton.Pulsate(Vector2.One, Vector2.One * 1.2f, dockingButton.FlashTimer);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (DockingSources.Any(d => d.Docked))
|
||||
{
|
||||
dockingButton.Text = undockText;
|
||||
dockingContainer.Visible = true;
|
||||
statusContainer.Visible = false;
|
||||
if (dockingButton.FlashTimer <= 0.0f)
|
||||
{
|
||||
dockingButton.Flash(GUI.Style.Orange, useCircularFlash: true);
|
||||
dockingButton.Pulsate(Vector2.One, Vector2.One * 1.2f, dockingButton.FlashTimer);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
dockingButton.Text = dockText;
|
||||
}
|
||||
|
||||
if (Voltage < MinVoltage)
|
||||
{
|
||||
tipContainer.Visible = true;
|
||||
tipContainer.Text = noPowerTip;
|
||||
return;
|
||||
}
|
||||
|
||||
tipContainer.Visible = AutoPilot;
|
||||
if (AutoPilot)
|
||||
{
|
||||
if (maintainPos)
|
||||
{
|
||||
tipContainer.Text = autoPilotMaintainPosTip;
|
||||
}
|
||||
else if (LevelStartSelected)
|
||||
{
|
||||
tipContainer.Text = autoPilotLevelStartTip;
|
||||
}
|
||||
else if (LevelEndSelected)
|
||||
{
|
||||
tipContainer.Text = autoPilotLevelEndTip;
|
||||
}
|
||||
|
||||
if (DockingModeEnabled && DockingTarget != null)
|
||||
{
|
||||
posToMaintain += ConvertUnits.ToDisplayUnits(DockingTarget.Item.Submarine.Velocity) * deltaTime;
|
||||
}
|
||||
}
|
||||
|
||||
pressureWarningText.Visible = item.Submarine != null && item.Submarine.AtDamageDepth && Timing.TotalTime % 1.0f < 0.5f;
|
||||
|
||||
if (Vector2.DistanceSquared(PlayerInput.MousePosition, steerArea.Rect.Center.ToVector2()) < steerRadius * steerRadius)
|
||||
{
|
||||
if (PlayerInput.PrimaryMouseButtonHeld() && !CrewManager.IsCommandInterfaceOpen && !GameSession.IsInfoFrameOpen)
|
||||
{
|
||||
Vector2 displaySubPos = (-sonar.DisplayOffset * sonar.Zoom) / sonar.Range * sonar.DisplayRadius * sonar.Zoom;
|
||||
displaySubPos.Y = -displaySubPos.Y;
|
||||
displaySubPos = steerArea.Rect.Center.ToVector2() + displaySubPos;
|
||||
|
||||
Vector2 inputPos = PlayerInput.MousePosition - displaySubPos;
|
||||
inputPos.Y = -inputPos.Y;
|
||||
if (AutoPilot && !LevelStartSelected && !LevelEndSelected)
|
||||
{
|
||||
posToMaintain = controlledSub != null ?
|
||||
controlledSub.WorldPosition + inputPos / sonar.DisplayRadius * sonar.Range / sonar.Zoom :
|
||||
item.Submarine == null ? item.WorldPosition : item.Submarine.WorldPosition;
|
||||
}
|
||||
else
|
||||
{
|
||||
SteeringInput = inputPos;
|
||||
}
|
||||
unsentChanges = true;
|
||||
user = Character.Controlled;
|
||||
}
|
||||
}
|
||||
if (!AutoPilot && Character.DisableControls && GUI.KeyboardDispatcher.Subscriber == null)
|
||||
{
|
||||
steeringAdjustSpeed = character == null ? 0.2f : MathHelper.Lerp(0.2f, 1.0f, character.GetSkillLevel("helm") / 100.0f);
|
||||
Vector2 input = Vector2.Zero;
|
||||
if (PlayerInput.KeyDown(InputType.Left)) { input -= Vector2.UnitX; }
|
||||
if (PlayerInput.KeyDown(InputType.Right)) { input += Vector2.UnitX; }
|
||||
if (PlayerInput.KeyDown(InputType.Up)) { input += Vector2.UnitY; }
|
||||
if (PlayerInput.KeyDown(InputType.Down)) { input -= Vector2.UnitY; }
|
||||
if (PlayerInput.KeyDown(InputType.Run))
|
||||
{
|
||||
SteeringInput += input * deltaTime * 200;
|
||||
inputCumulation = 0;
|
||||
keyboardInput = Vector2.Zero;
|
||||
unsentChanges = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
float step = deltaTime * 5;
|
||||
if (input.Length() > 0)
|
||||
{
|
||||
inputCumulation += step;
|
||||
}
|
||||
else
|
||||
{
|
||||
inputCumulation -= step;
|
||||
}
|
||||
float maxCumulation = 1;
|
||||
inputCumulation = MathHelper.Clamp(inputCumulation, 0, maxCumulation);
|
||||
float length = MathHelper.Lerp(0, 0.2f, MathUtils.InverseLerp(0, maxCumulation, inputCumulation));
|
||||
var normalizedInput = Vector2.Normalize(input);
|
||||
if (MathUtils.IsValid(normalizedInput))
|
||||
{
|
||||
keyboardInput += normalizedInput * length;
|
||||
}
|
||||
if (keyboardInput.LengthSquared() > 0.01f)
|
||||
{
|
||||
SteeringInput += keyboardInput;
|
||||
unsentChanges = true;
|
||||
user = Character.Controlled;
|
||||
keyboardInput *= MathHelper.Clamp(1 - step, 0, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
inputCumulation = 0;
|
||||
keyboardInput = Vector2.Zero;
|
||||
}
|
||||
|
||||
if (!UseAutoDocking) { return; }
|
||||
|
||||
if (checkConnectedPortsTimer <= 0.0f)
|
||||
{
|
||||
Connection dockingConnection = item.Connections?.FirstOrDefault(c => c.Name == "toggle_docking");
|
||||
if (dockingConnection != null)
|
||||
{
|
||||
connectedPorts = item.GetConnectedComponentsRecursive<DockingPort>(dockingConnection);
|
||||
}
|
||||
checkConnectedPortsTimer = CheckConnectedPortsInterval;
|
||||
}
|
||||
|
||||
float closestDist = DockingAssistThreshold * DockingAssistThreshold;
|
||||
DockingModeEnabled = false;
|
||||
|
||||
foreach (DockingPort sourcePort in connectedPorts)
|
||||
{
|
||||
if (sourcePort.Docked || sourcePort.Item.Submarine == null) { continue; }
|
||||
if (sourcePort.Item.Submarine != controlledSub) { continue; }
|
||||
|
||||
int sourceDir = sourcePort.GetDir();
|
||||
|
||||
foreach (DockingPort targetPort in DockingPort.List)
|
||||
{
|
||||
if (targetPort.Docked || targetPort.Item.Submarine == null) { continue; }
|
||||
if (targetPort.Item.Submarine == controlledSub || targetPort.IsHorizontal != sourcePort.IsHorizontal) { continue; }
|
||||
if (Level.Loaded != null && targetPort.Item.Submarine.WorldPosition.Y > Level.Loaded.Size.Y) { continue; }
|
||||
|
||||
int targetDir = targetPort.GetDir();
|
||||
|
||||
if (sourceDir == targetDir) { continue; }
|
||||
|
||||
float dist = Vector2.DistanceSquared(sourcePort.Item.WorldPosition, targetPort.Item.WorldPosition);
|
||||
if (dist < closestDist)
|
||||
{
|
||||
DockingModeEnabled = true;
|
||||
ActiveDockingSource = sourcePort;
|
||||
DockingTarget = targetPort;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool NudgeButtonClicked(GUIButton btn, object userdata)
|
||||
{
|
||||
if (!MaintainPos || !AutoPilot)
|
||||
{
|
||||
AutoPilot = true;
|
||||
posToMaintain = item.Submarine.WorldPosition;
|
||||
}
|
||||
MaintainPos = true;
|
||||
if (userdata is Vector2)
|
||||
{
|
||||
Sonar sonar = item.GetComponent<Sonar>();
|
||||
Vector2 nudgeAmount = (Vector2)userdata;
|
||||
if (sonar != null)
|
||||
{
|
||||
nudgeAmount *= sonar == null ? 500.0f : 500.0f / sonar.Zoom;
|
||||
}
|
||||
PosToMaintain += nudgeAmount;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void RemoveComponentSpecific()
|
||||
{
|
||||
base.RemoveComponentSpecific();
|
||||
maintainPosIndicator?.Remove();
|
||||
maintainPosOriginIndicator?.Remove();
|
||||
steeringIndicator?.Remove();
|
||||
|
||||
GameMain.Instance.OnResolutionChanged -= RecreateGUI;
|
||||
}
|
||||
|
||||
public void ClientWrite(IWriteMessage msg, object[] extraData = null)
|
||||
{
|
||||
msg.Write(AutoPilot);
|
||||
msg.Write(dockingNetworkMessagePending);
|
||||
dockingNetworkMessagePending = false;
|
||||
|
||||
if (!AutoPilot)
|
||||
{
|
||||
//no need to write steering info if autopilot is controlling
|
||||
msg.Write(steeringInput.X);
|
||||
msg.Write(steeringInput.Y);
|
||||
}
|
||||
else
|
||||
{
|
||||
msg.Write(posToMaintain != null);
|
||||
if (posToMaintain != null)
|
||||
{
|
||||
msg.Write(((Vector2)posToMaintain).X);
|
||||
msg.Write(((Vector2)posToMaintain).Y);
|
||||
}
|
||||
else
|
||||
{
|
||||
msg.Write(LevelStartSelected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
|
||||
{
|
||||
int msgStartPos = msg.BitPosition;
|
||||
|
||||
bool autoPilot = msg.ReadBoolean();
|
||||
bool dockingButtonClicked = msg.ReadBoolean();
|
||||
Vector2 newSteeringInput = steeringInput;
|
||||
Vector2 newTargetVelocity = targetVelocity;
|
||||
float newSteeringAdjustSpeed = steeringAdjustSpeed;
|
||||
bool maintainPos = false;
|
||||
Vector2? newPosToMaintain = null;
|
||||
bool headingToStart = false;
|
||||
|
||||
if (dockingButtonClicked)
|
||||
{
|
||||
item.SendSignal(0, "1", "toggle_docking", sender: null);
|
||||
}
|
||||
|
||||
if (autoPilot)
|
||||
{
|
||||
maintainPos = msg.ReadBoolean();
|
||||
if (maintainPos)
|
||||
{
|
||||
newPosToMaintain = new Vector2(
|
||||
msg.ReadSingle(),
|
||||
msg.ReadSingle());
|
||||
}
|
||||
else
|
||||
{
|
||||
headingToStart = msg.ReadBoolean();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
newSteeringInput = new Vector2(msg.ReadSingle(), msg.ReadSingle());
|
||||
newTargetVelocity = new Vector2(msg.ReadSingle(), msg.ReadSingle());
|
||||
newSteeringAdjustSpeed = msg.ReadSingle();
|
||||
}
|
||||
|
||||
if (correctionTimer > 0.0f)
|
||||
{
|
||||
int msgLength = (int)(msg.BitPosition - msgStartPos);
|
||||
msg.BitPosition = msgStartPos;
|
||||
StartDelayedCorrection(type, msg.ExtractBits(msgLength), sendingTime);
|
||||
return;
|
||||
}
|
||||
|
||||
AutoPilot = autoPilot;
|
||||
|
||||
if (!AutoPilot)
|
||||
{
|
||||
SteeringInput = newSteeringInput;
|
||||
TargetVelocity = newTargetVelocity;
|
||||
steeringAdjustSpeed = newSteeringAdjustSpeed;
|
||||
}
|
||||
else
|
||||
{
|
||||
MaintainPos = newPosToMaintain != null;
|
||||
posToMaintain = newPosToMaintain;
|
||||
|
||||
if (posToMaintain == null)
|
||||
{
|
||||
LevelStartSelected = headingToStart;
|
||||
LevelEndSelected = !headingToStart;
|
||||
UpdatePath();
|
||||
}
|
||||
else
|
||||
{
|
||||
LevelStartSelected = false;
|
||||
LevelEndSelected = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateGUIElements()
|
||||
{
|
||||
steeringModeSwitch.Selected = AutoPilot;
|
||||
autopilotIndicator.Selected = AutoPilot;
|
||||
manualPilotIndicator.Selected = !AutoPilot;
|
||||
maintainPosTickBox.Enabled = AutoPilot;
|
||||
levelEndTickBox.Enabled = AutoPilot;
|
||||
levelStartTickBox.Enabled = AutoPilot;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class PowerContainer : Powered, IDrawableComponent, IServerSerializable, IClientSerializable
|
||||
{
|
||||
private GUIProgressBar chargeIndicator;
|
||||
private GUIScrollBar rechargeSpeedSlider;
|
||||
|
||||
public Vector2 DrawSize
|
||||
{
|
||||
//use the extents of the item as the draw size
|
||||
get { return Vector2.Zero; }
|
||||
}
|
||||
|
||||
partial void InitProjSpecific()
|
||||
{
|
||||
if (GuiFrame == null) { return; }
|
||||
|
||||
var paddedFrame = new GUIFrame(new RectTransform(new Vector2(0.75f, 0.75f), GuiFrame.RectTransform, Anchor.Center)
|
||||
{
|
||||
//RelativeOffset = new Vector2(0, 0.05f)
|
||||
}, style: null);
|
||||
|
||||
var upperArea = new GUIFrame(new RectTransform(new Vector2(1, 0.4f), paddedFrame.RectTransform, Anchor.TopCenter), style: null);
|
||||
var lowerArea = new GUIFrame(new RectTransform(new Vector2(1, 0.6f), paddedFrame.RectTransform, Anchor.BottomCenter), style: null);
|
||||
|
||||
|
||||
string rechargeStr = TextManager.Get("PowerContainerRechargeRate");
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), upperArea.RectTransform, Anchor.TopCenter),
|
||||
"RechargeRate", textColor: GUI.Style.TextColor, font: GUI.SubHeadingFont, textAlignment: Alignment.Center)
|
||||
{
|
||||
TextGetter = () =>
|
||||
{
|
||||
return rechargeStr.Replace("[rate]", ((int)((rechargeSpeed / maxRechargeSpeed) * 100.0f)).ToString());
|
||||
}
|
||||
};
|
||||
|
||||
rechargeSpeedSlider = new GUIScrollBar(new RectTransform(new Vector2(0.9f, 0.4f), upperArea.RectTransform, Anchor.BottomCenter),
|
||||
barSize: 0.15f, style: "DeviceSlider")
|
||||
{
|
||||
Step = 0.1f,
|
||||
OnMoved = (GUIScrollBar scrollBar, float barScroll) =>
|
||||
{
|
||||
float newRechargeSpeed = maxRechargeSpeed * barScroll;
|
||||
if (Math.Abs(newRechargeSpeed - rechargeSpeed) < 0.1f) { return false; }
|
||||
|
||||
RechargeSpeed = newRechargeSpeed;
|
||||
if (GameMain.Client != null)
|
||||
{
|
||||
item.CreateClientEvent(this);
|
||||
correctionTimer = CorrectionDelay;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
rechargeSpeedSlider.Bar.RectTransform.MaxSize = new Point(rechargeSpeedSlider.Bar.Rect.Height);
|
||||
|
||||
// lower area --------------------------
|
||||
|
||||
var textArea = new GUIFrame(new RectTransform(new Vector2(1, 0.4f), lowerArea.RectTransform), style: null);
|
||||
var chargeLabel = new GUITextBlock(new RectTransform(new Vector2(0.4f, 0.0f), textArea.RectTransform, Anchor.CenterLeft),
|
||||
TextManager.Get("charge"), textColor: GUI.Style.TextColor, font: GUI.SubHeadingFont, textAlignment: Alignment.CenterLeft)
|
||||
{
|
||||
ToolTip = TextManager.Get("PowerTransferTipPower")
|
||||
};
|
||||
string kWmin = TextManager.Get("kilowattminute");
|
||||
var chargeText = new GUITextBlock(new RectTransform(new Vector2(0.6f, 1), textArea.RectTransform, Anchor.CenterRight),
|
||||
"", textColor: GUI.Style.TextColor, font: GUI.Font, textAlignment: Alignment.CenterRight)
|
||||
{
|
||||
TextGetter = () => $"{(int)charge}/{(int)capacity} {kWmin} ({((int)MathUtils.Percentage(charge, capacity)).ToString()} %)"
|
||||
};
|
||||
if (chargeText.TextSize.X > chargeText.Rect.Width) { chargeText.Font = GUI.SmallFont; }
|
||||
|
||||
chargeIndicator = new GUIProgressBar(new RectTransform(new Vector2(1.1f, 0.5f), lowerArea.RectTransform, Anchor.BottomCenter)
|
||||
{
|
||||
RelativeOffset = new Vector2(0, 0.1f)
|
||||
}, barSize: 0.0f, style: "DeviceProgressBar")
|
||||
{
|
||||
ProgressGetter = () =>
|
||||
{
|
||||
return capacity <= 0.0f ? 1.0f : charge / capacity;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public override void OnItemLoaded()
|
||||
{
|
||||
base.OnItemLoaded();
|
||||
if (rechargeSpeedSlider != null)
|
||||
{
|
||||
rechargeSpeedSlider.BarScroll = rechargeSpeed / MaxRechargeSpeed;
|
||||
}
|
||||
}
|
||||
|
||||
public override void UpdateHUD(Character character, float deltaTime, Camera cam)
|
||||
{
|
||||
float chargeRatio = charge / capacity;
|
||||
chargeIndicator.Color = ToolBox.GradientLerp(chargeRatio, Color.Red, Color.Orange, Color.Green);
|
||||
}
|
||||
|
||||
public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1)
|
||||
{
|
||||
if (indicatorSize.X <= 1.0f || indicatorSize.Y <= 1.0f) { return; }
|
||||
|
||||
GUI.DrawRectangle(spriteBatch,
|
||||
new Vector2(
|
||||
item.DrawPosition.X - item.Sprite.SourceRect.Width / 2 * item.Scale + indicatorPosition.X * item.Scale,
|
||||
-item.DrawPosition.Y - item.Sprite.SourceRect.Height / 2 * item.Scale + indicatorPosition.Y * item.Scale),
|
||||
indicatorSize * item.Scale, Color.Black, depth: item.SpriteDepth - 0.00001f);
|
||||
|
||||
if (charge > 0)
|
||||
{
|
||||
Color indicatorColor = ToolBox.GradientLerp(charge / capacity, Color.Red, Color.Orange, Color.Green);
|
||||
if (!isHorizontal)
|
||||
{
|
||||
GUI.DrawRectangle(spriteBatch,
|
||||
new Vector2(
|
||||
item.DrawPosition.X - item.Sprite.SourceRect.Width / 2 * item.Scale + indicatorPosition.X * item.Scale + 1,
|
||||
-item.DrawPosition.Y - item.Sprite.SourceRect.Height / 2 * item.Scale + indicatorPosition.Y * item.Scale + 1 + ((indicatorSize.Y * item.Scale) * (1.0f - charge / capacity))),
|
||||
new Vector2(indicatorSize.X * item.Scale - 2, (indicatorSize.Y * item.Scale - 2) * (charge / capacity)), indicatorColor, true,
|
||||
depth: item.SpriteDepth - 0.00001f);
|
||||
}
|
||||
else
|
||||
{
|
||||
GUI.DrawRectangle(spriteBatch,
|
||||
new Vector2(
|
||||
item.DrawPosition.X - item.Sprite.SourceRect.Width / 2 * item.Scale + indicatorPosition.X * item.Scale + 1 ,
|
||||
-item.DrawPosition.Y - item.Sprite.SourceRect.Height / 2 * item.Scale + indicatorPosition.Y * item.Scale + 1),
|
||||
new Vector2((indicatorSize.X * item.Scale - 2) * (charge / capacity), indicatorSize.Y * item.Scale - 2), indicatorColor, true,
|
||||
depth: item.SpriteDepth - 0.00001f);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void ClientWrite(IWriteMessage msg, object[] extraData)
|
||||
{
|
||||
msg.WriteRangedInteger((int)(rechargeSpeed / MaxRechargeSpeed * 10), 0, 10);
|
||||
}
|
||||
|
||||
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
|
||||
{
|
||||
if (correctionTimer > 0.0f)
|
||||
{
|
||||
StartDelayedCorrection(type, msg.ExtractBits(4 + 8), sendingTime);
|
||||
return;
|
||||
}
|
||||
|
||||
float rechargeRate = msg.ReadRangedInteger(0, 10) / 10.0f;
|
||||
RechargeSpeed = rechargeRate * MaxRechargeSpeed;
|
||||
#if CLIENT
|
||||
if (rechargeSpeedSlider != null)
|
||||
{
|
||||
rechargeSpeedSlider.BarScroll = rechargeRate;
|
||||
}
|
||||
#endif
|
||||
Charge = msg.ReadRangedSingle(0.0f, 1.0f, 8) * capacity;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class PowerTransfer : Powered
|
||||
{
|
||||
private GUITickBox powerIndicator;
|
||||
private GUITickBox highVoltageIndicator;
|
||||
private GUITickBox lowVoltageIndicator;
|
||||
|
||||
partial void InitProjectSpecific(XElement element)
|
||||
{
|
||||
if (GuiFrame == null) { return; }
|
||||
|
||||
var paddedFrame = new GUIFrame(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center) { AbsoluteOffset = GUIStyle.ItemFrameOffset },
|
||||
style: null)
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
|
||||
var lightsArea = new GUILayoutGroup(new RectTransform(new Vector2(0.4f, 1), paddedFrame.RectTransform, Anchor.CenterLeft))
|
||||
{
|
||||
Stretch = true
|
||||
};
|
||||
powerIndicator = new GUITickBox(new RectTransform(new Vector2(1, 0.33f), lightsArea.RectTransform),
|
||||
TextManager.Get("PowerTransferPowered"), font: GUI.SubHeadingFont, style: "IndicatorLightGreen")
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
highVoltageIndicator = new GUITickBox(new RectTransform(new Vector2(1, 0.33f), lightsArea.RectTransform),
|
||||
TextManager.Get("PowerTransferHighVoltage"), font: GUI.SubHeadingFont, style: "IndicatorLightRed")
|
||||
{
|
||||
ToolTip = TextManager.Get("PowerTransferTipOvervoltage"),
|
||||
Enabled = false
|
||||
};
|
||||
lowVoltageIndicator = new GUITickBox(new RectTransform(new Vector2(1, 0.33f), lightsArea.RectTransform),
|
||||
TextManager.Get("PowerTransferLowVoltage"), font: GUI.SubHeadingFont, style: "IndicatorLightRed")
|
||||
{
|
||||
ToolTip = TextManager.Get("PowerTransferTipLowvoltage"),
|
||||
Enabled = false
|
||||
};
|
||||
powerIndicator.TextBlock.OverrideTextColor(GUI.Style.TextColor);
|
||||
highVoltageIndicator.TextBlock.OverrideTextColor(GUI.Style.TextColor);
|
||||
lowVoltageIndicator.TextBlock.OverrideTextColor(GUI.Style.TextColor);
|
||||
GUITextBlock.AutoScaleAndNormalize(powerIndicator.TextBlock, highVoltageIndicator.TextBlock, lowVoltageIndicator.TextBlock);
|
||||
|
||||
var textContainer = new GUIFrame(new RectTransform(new Vector2(0.58f, 1.0f), paddedFrame.RectTransform, Anchor.CenterRight), style: null);
|
||||
var upperTextArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.5f), textContainer.RectTransform, Anchor.TopLeft), isHorizontal: true, childAnchor: Anchor.CenterLeft)
|
||||
{
|
||||
Stretch = true
|
||||
};
|
||||
var lowerTextArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.5f), textContainer.RectTransform, Anchor.BottomLeft), isHorizontal: true, childAnchor: Anchor.CenterLeft)
|
||||
{
|
||||
Stretch = true
|
||||
};
|
||||
|
||||
var powerLabel = new GUITextBlock(new RectTransform(new Vector2(0.4f, 1), upperTextArea.RectTransform),
|
||||
TextManager.Get("PowerTransferPowerLabel"), textColor: GUI.Style.TextColorBright, font: GUI.LargeFont, textAlignment: Alignment.CenterRight)
|
||||
{
|
||||
ToolTip = TextManager.Get("PowerTransferTipPower")
|
||||
};
|
||||
var loadLabel = new GUITextBlock(new RectTransform(new Vector2(0.4f, 1), lowerTextArea.RectTransform),
|
||||
TextManager.Get("PowerTransferLoadLabel"), textColor: GUI.Style.TextColorBright, font: GUI.LargeFont, textAlignment: Alignment.CenterRight)
|
||||
{
|
||||
ToolTip = TextManager.Get("PowerTransferTipLoad")
|
||||
};
|
||||
|
||||
var digitalBackground = new GUIFrame(new RectTransform(new Vector2(0.55f, 0.8f), upperTextArea.RectTransform), style: "DigitalFrameDark");
|
||||
var powerText = new GUITextBlock(new RectTransform(new Vector2(0.9f, 0.95f), digitalBackground.RectTransform, Anchor.Center),
|
||||
"", font: GUI.DigitalFont, textColor: GUI.Style.TextColorDark)
|
||||
{
|
||||
TextAlignment = Alignment.CenterRight,
|
||||
ToolTip = TextManager.Get("PowerTransferTipPower"),
|
||||
TextGetter = () => ((int)Math.Round(-currPowerConsumption)).ToString()
|
||||
};
|
||||
var kw1 = new GUITextBlock(new RectTransform(new Vector2(0.15f, 0.5f), upperTextArea.RectTransform),
|
||||
TextManager.Get("kilowatt"), textColor: GUI.Style.TextColor, font: GUI.Font)
|
||||
{
|
||||
Padding = Vector4.Zero,
|
||||
TextAlignment = Alignment.BottomCenter
|
||||
};
|
||||
|
||||
digitalBackground = new GUIFrame(new RectTransform(new Vector2(0.55f, 0.8f), lowerTextArea.RectTransform), style: "DigitalFrameDark");
|
||||
var loadText = new GUITextBlock(new RectTransform(new Vector2(0.9f, 0.95f), digitalBackground.RectTransform, Anchor.Center),
|
||||
"", font: GUI.DigitalFont, textColor: GUI.Style.TextColorDark)
|
||||
{
|
||||
TextAlignment = Alignment.CenterRight,
|
||||
ToolTip = TextManager.Get("PowerTransferTipLoad"),
|
||||
TextGetter = () => ((int)Math.Round(this is RelayComponent relay ? relay.DisplayLoad : powerLoad)).ToString()
|
||||
};
|
||||
var kw2 = new GUITextBlock(new RectTransform(new Vector2(0.15f, 0.5f), lowerTextArea.RectTransform),
|
||||
TextManager.Get("kilowatt"), textColor: GUI.Style.TextColor, font: GUI.Font)
|
||||
{
|
||||
Padding = Vector4.Zero,
|
||||
TextAlignment = Alignment.BottomCenter
|
||||
};
|
||||
|
||||
GUITextBlock.AutoScaleAndNormalize(powerLabel, loadLabel);
|
||||
GUITextBlock.AutoScaleAndNormalize(true, true, powerText, loadText);
|
||||
GUITextBlock.AutoScaleAndNormalize(kw1, kw2);
|
||||
}
|
||||
|
||||
public override void UpdateHUD(Character character, float deltaTime, Camera cam)
|
||||
{
|
||||
if (GuiFrame == null) return;
|
||||
|
||||
float voltage = powerLoad <= 0.0f ? 1.0f : -currPowerConsumption / powerLoad;
|
||||
powerIndicator.Selected = IsActive && currPowerConsumption < -0.1f;
|
||||
highVoltageIndicator.Selected = Timing.TotalTime % 0.5f < 0.25f && powerIndicator.Selected && voltage > 1.2f;
|
||||
lowVoltageIndicator.Selected = Timing.TotalTime % 0.5f < 0.25f && powerIndicator.Selected && voltage < 0.8f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using Barotrauma.Sounds;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class Powered : ItemComponent
|
||||
{
|
||||
private RoundSound powerOnSound;
|
||||
private bool powerOnSoundPlayed;
|
||||
|
||||
partial void InitProjectSpecific(XElement element)
|
||||
{
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "poweronsound":
|
||||
powerOnSound = Submarine.LoadRoundSound(subElement, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
using Barotrauma.Particles;
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class RepairTool
|
||||
#if DEBUG
|
||||
: IDrawableComponent
|
||||
#endif
|
||||
{
|
||||
#if DEBUG
|
||||
public Vector2 DrawSize
|
||||
{
|
||||
get { return GameMain.DebugDraw ? Vector2.One * Range : Vector2.Zero; }
|
||||
}
|
||||
#endif
|
||||
|
||||
private List<ParticleEmitter> particleEmitters = new List<ParticleEmitter>();
|
||||
private List<ParticleEmitter> particleEmitterHitStructure = new List<ParticleEmitter>();
|
||||
private List<ParticleEmitter> particleEmitterHitCharacter = new List<ParticleEmitter>();
|
||||
private List<Pair<RelatedItem, ParticleEmitter>> particleEmitterHitItem = new List<Pair<RelatedItem, ParticleEmitter>>();
|
||||
|
||||
private float prevProgressBarState;
|
||||
|
||||
partial void InitProjSpecific(XElement element)
|
||||
{
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "particleemitter":
|
||||
particleEmitters.Add(new ParticleEmitter(subElement));
|
||||
break;
|
||||
case "particleemitterhititem":
|
||||
string[] identifiers = subElement.GetAttributeStringArray("identifiers", new string[0]);
|
||||
if (identifiers.Length == 0) identifiers = subElement.GetAttributeStringArray("identifier", new string[0]);
|
||||
string[] excludedIdentifiers = subElement.GetAttributeStringArray("excludedidentifiers", new string[0]);
|
||||
if (excludedIdentifiers.Length == 0) excludedIdentifiers = subElement.GetAttributeStringArray("excludedidentifier", new string[0]);
|
||||
|
||||
particleEmitterHitItem.Add(
|
||||
new Pair<RelatedItem, ParticleEmitter>(
|
||||
new RelatedItem(identifiers, excludedIdentifiers),
|
||||
new ParticleEmitter(subElement)));
|
||||
break;
|
||||
case "particleemitterhitstructure":
|
||||
particleEmitterHitStructure.Add(new ParticleEmitter(subElement));
|
||||
break;
|
||||
case "particleemitterhitcharacter":
|
||||
particleEmitterHitCharacter.Add(new ParticleEmitter(subElement));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
partial void UseProjSpecific(float deltaTime, Vector2 raystart)
|
||||
{
|
||||
foreach (ParticleEmitter particleEmitter in particleEmitters)
|
||||
{
|
||||
float particleAngle = item.body.Rotation + ((item.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi);
|
||||
particleEmitter.Emit(
|
||||
deltaTime, ConvertUnits.ToDisplayUnits(raystart),
|
||||
item.CurrentHull, particleAngle, particleEmitter.Prefab.CopyEntityAngle ? -particleAngle : 0);
|
||||
}
|
||||
}
|
||||
|
||||
partial void FixStructureProjSpecific(Character user, float deltaTime, Structure targetStructure, int sectionIndex)
|
||||
{
|
||||
Vector2 progressBarPos = targetStructure.SectionPosition(sectionIndex);
|
||||
if (targetStructure.Submarine != null)
|
||||
{
|
||||
progressBarPos += targetStructure.Submarine.DrawPosition;
|
||||
}
|
||||
|
||||
var progressBar = user.UpdateHUDProgressBar(
|
||||
targetStructure.ID * 1000 + sectionIndex, //unique "identifier" for each wall section
|
||||
progressBarPos,
|
||||
1.0f - targetStructure.SectionDamage(sectionIndex) / targetStructure.Health,
|
||||
GUI.Style.Red, GUI.Style.Green);
|
||||
|
||||
if (progressBar != null) progressBar.Size = new Vector2(60.0f, 20.0f);
|
||||
|
||||
Vector2 particlePos = ConvertUnits.ToDisplayUnits(pickedPosition);
|
||||
if (targetStructure.Submarine != null) particlePos += targetStructure.Submarine.DrawPosition;
|
||||
foreach (var emitter in particleEmitterHitStructure)
|
||||
{
|
||||
float particleAngle = item.body.Rotation + ((item.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi);
|
||||
emitter.Emit(deltaTime, particlePos, item.CurrentHull, particleAngle + MathHelper.Pi, -particleAngle + MathHelper.Pi);
|
||||
}
|
||||
}
|
||||
|
||||
partial void FixCharacterProjSpecific(Character user, float deltaTime, Character targetCharacter)
|
||||
{
|
||||
Vector2 particlePos = ConvertUnits.ToDisplayUnits(pickedPosition);
|
||||
if (targetCharacter.Submarine != null) particlePos += targetCharacter.Submarine.DrawPosition;
|
||||
foreach (var emitter in particleEmitterHitCharacter)
|
||||
{
|
||||
float particleAngle = item.body.Rotation + ((item.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi);
|
||||
emitter.Emit(deltaTime, particlePos, item.CurrentHull, particleAngle + MathHelper.Pi, -particleAngle + MathHelper.Pi);
|
||||
}
|
||||
}
|
||||
|
||||
partial void FixItemProjSpecific(Character user, float deltaTime, Item targetItem)
|
||||
{
|
||||
float progressBarState = targetItem.ConditionPercentage / 100.0f;
|
||||
if (!MathUtils.NearlyEqual(progressBarState, prevProgressBarState))
|
||||
{
|
||||
var door = targetItem.GetComponent<Door>();
|
||||
if (door == null || door.Stuck <= 0)
|
||||
{
|
||||
Vector2 progressBarPos = targetItem.DrawPosition;
|
||||
var progressBar = user.UpdateHUDProgressBar(
|
||||
targetItem,
|
||||
progressBarPos,
|
||||
progressBarState,
|
||||
GUI.Style.Red, GUI.Style.Green);
|
||||
if (progressBar != null) { progressBar.Size = new Vector2(60.0f, 20.0f); }
|
||||
}
|
||||
prevProgressBarState = progressBarState;
|
||||
}
|
||||
|
||||
Vector2 particlePos = ConvertUnits.ToDisplayUnits(pickedPosition);
|
||||
if (targetItem.Submarine != null) particlePos += targetItem.Submarine.DrawPosition;
|
||||
foreach (var emitter in particleEmitterHitItem)
|
||||
{
|
||||
if (!emitter.First.MatchesItem(targetItem)) continue;
|
||||
float particleAngle = item.body.Rotation + ((item.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi);
|
||||
emitter.Second.Emit(deltaTime, particlePos, item.CurrentHull, particleAngle + MathHelper.Pi, -particleAngle + MathHelper.Pi);
|
||||
}
|
||||
}
|
||||
#if DEBUG
|
||||
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1)
|
||||
{
|
||||
if (GameMain.DebugDraw && IsActive)
|
||||
{
|
||||
GUI.DrawLine(spriteBatch,
|
||||
new Vector2(debugRayStartPos.X, -debugRayStartPos.Y),
|
||||
new Vector2(debugRayEndPos.X, -debugRayEndPos.Y),
|
||||
Color.Yellow);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.Particles;
|
||||
using Barotrauma.Sounds;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class Repairable : ItemComponent, IDrawableComponent
|
||||
{
|
||||
public GUIButton RepairButton { get; private set; }
|
||||
|
||||
public GUIButton SabotageButton { get; private set; }
|
||||
|
||||
private GUIProgressBar progressBar;
|
||||
|
||||
private List<ParticleEmitter> particleEmitters = new List<ParticleEmitter>();
|
||||
//the corresponding particle emitter is active when the condition is within this range
|
||||
private List<Vector2> particleEmitterConditionRanges = new List<Vector2>();
|
||||
|
||||
private SoundChannel repairSoundChannel;
|
||||
|
||||
private string repairButtonText, repairingText;
|
||||
private string sabotageButtonText, sabotagingText;
|
||||
|
||||
private FixActions requestStartFixAction;
|
||||
|
||||
[Serialize("", false, description: "An optional description of the needed repairs displayed in the repair interface.")]
|
||||
public string Description
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public Vector2 DrawSize
|
||||
{
|
||||
//use the extents of the item as the draw size
|
||||
get { return Vector2.Zero; }
|
||||
}
|
||||
|
||||
public override bool ShouldDrawHUD(Character character)
|
||||
{
|
||||
if (!HasRequiredItems(character, false) || character.SelectedConstruction != item) return false;
|
||||
return !item.IsFullCondition || character.IsTraitor && item.ConditionPercentage > MinSabotageCondition || (CurrentFixer == character && (!item.IsFullCondition || (character.IsTraitor && item.ConditionPercentage > MinSabotageCondition)));
|
||||
}
|
||||
|
||||
partial void InitProjSpecific(XElement element)
|
||||
{
|
||||
var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.75f), GuiFrame.RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter)
|
||||
{
|
||||
Stretch = true,
|
||||
RelativeSpacing = 0.05f,
|
||||
CanBeFocused = true
|
||||
};
|
||||
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), paddedFrame.RectTransform),
|
||||
header, textAlignment: Alignment.TopCenter, font: GUI.LargeFont);
|
||||
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform),
|
||||
Description, font: GUI.SmallFont, wrap: true);
|
||||
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform),
|
||||
TextManager.Get("RequiredRepairSkills"), font: GUI.SubHeadingFont);
|
||||
for (int i = 0; i < requiredSkills.Count; i++)
|
||||
{
|
||||
var skillText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform),
|
||||
" - " + TextManager.AddPunctuation(':', TextManager.Get("SkillName." + requiredSkills[i].Identifier), ((int) requiredSkills[i].Level).ToString()),
|
||||
font: GUI.SmallFont)
|
||||
{
|
||||
UserData = requiredSkills[i]
|
||||
};
|
||||
}
|
||||
|
||||
var progressBarHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.2f), paddedFrame.RectTransform), isHorizontal: true)
|
||||
{
|
||||
Stretch = true,
|
||||
RelativeSpacing = 0.02f
|
||||
};
|
||||
|
||||
progressBar = new GUIProgressBar(new RectTransform(new Vector2(0.6f, 1.0f), progressBarHolder.RectTransform),
|
||||
color: GUI.Style.Green, barSize: 0.0f, style: "DeviceProgressBar");
|
||||
repairButtonText = TextManager.Get("RepairButton");
|
||||
repairingText = TextManager.Get("Repairing");
|
||||
RepairButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), progressBarHolder.RectTransform, Anchor.TopCenter), repairButtonText)
|
||||
{
|
||||
OnClicked = (btn, obj) =>
|
||||
{
|
||||
requestStartFixAction = FixActions.Repair;
|
||||
item.CreateClientEvent(this);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
RepairButton.TextBlock.AutoScaleHorizontal = true;
|
||||
progressBarHolder.RectTransform.MinSize = RepairButton.RectTransform.MinSize;
|
||||
RepairButton.RectTransform.MinSize = new Point((int)(RepairButton.TextBlock.TextSize.X * 1.2f), RepairButton.RectTransform.MinSize.Y);
|
||||
|
||||
sabotageButtonText = TextManager.Get("SabotageButton");
|
||||
sabotagingText = TextManager.Get("Sabotaging");
|
||||
SabotageButton = new GUIButton(new RectTransform(new Vector2(0.8f, 0.15f), paddedFrame.RectTransform, Anchor.BottomCenter), sabotageButtonText, style: "GUIButtonSmall")
|
||||
{
|
||||
IgnoreLayoutGroups = true,
|
||||
Visible = false,
|
||||
OnClicked = (btn, obj) =>
|
||||
{
|
||||
requestStartFixAction = FixActions.Sabotage;
|
||||
item.CreateClientEvent(this);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "emitter":
|
||||
case "particleemitter":
|
||||
particleEmitters.Add(new ParticleEmitter(subElement));
|
||||
particleEmitterConditionRanges.Add(new Vector2(
|
||||
subElement.GetAttributeFloat("mincondition", 0.0f),
|
||||
subElement.GetAttributeFloat("maxcondition", 100.0f)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
partial void UpdateProjSpecific(float deltaTime)
|
||||
{
|
||||
if (!GameMain.IsMultiplayer)
|
||||
{
|
||||
switch (requestStartFixAction)
|
||||
{
|
||||
case FixActions.Repair:
|
||||
case FixActions.Sabotage:
|
||||
StartRepairing(Character.Controlled, requestStartFixAction);
|
||||
requestStartFixAction = FixActions.None;
|
||||
break;
|
||||
default:
|
||||
requestStartFixAction = FixActions.None;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < particleEmitters.Count; i++)
|
||||
{
|
||||
if (item.ConditionPercentage >= particleEmitterConditionRanges[i].X && item.ConditionPercentage <= particleEmitterConditionRanges[i].Y)
|
||||
{
|
||||
particleEmitters[i].Emit(deltaTime, item.WorldPosition, item.CurrentHull);
|
||||
}
|
||||
}
|
||||
|
||||
if (CurrentFixer != null && CurrentFixer.SelectedConstruction == item)
|
||||
{
|
||||
if (repairSoundChannel == null || !repairSoundChannel.IsPlaying)
|
||||
{
|
||||
repairSoundChannel = SoundPlayer.PlaySound("repair", item.WorldPosition, hullGuess: item.CurrentHull);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
repairSoundChannel?.FadeOutAndDispose();
|
||||
repairSoundChannel = null;
|
||||
}
|
||||
}
|
||||
|
||||
public override void DrawHUD(SpriteBatch spriteBatch, Character character)
|
||||
{
|
||||
IsActive = true;
|
||||
|
||||
progressBar.BarSize = item.Condition / item.MaxCondition;
|
||||
progressBar.Color = ToolBox.GradientLerp(progressBar.BarSize, GUI.Style.Red, GUI.Style.Orange, GUI.Style.Green);
|
||||
|
||||
RepairButton.Enabled = (currentFixerAction == FixActions.None || (CurrentFixer == character && currentFixerAction != FixActions.Repair)) && !item.IsFullCondition;
|
||||
RepairButton.Text = (currentFixerAction == FixActions.None || CurrentFixer != character || currentFixerAction != FixActions.Repair) ?
|
||||
repairButtonText :
|
||||
repairingText + new string('.', ((int)(Timing.TotalTime * 2.0f) % 3) + 1);
|
||||
|
||||
SabotageButton.Visible = character.IsTraitor;
|
||||
SabotageButton.IgnoreLayoutGroups = !SabotageButton.Visible;
|
||||
SabotageButton.Enabled = (currentFixerAction == FixActions.None || (CurrentFixer == character && currentFixerAction != FixActions.Sabotage)) && character.IsTraitor && item.ConditionPercentage > MinSabotageCondition;
|
||||
SabotageButton.Text = (currentFixerAction == FixActions.None || CurrentFixer != character || currentFixerAction != FixActions.Sabotage || !character.IsTraitor) ?
|
||||
sabotageButtonText :
|
||||
sabotagingText + new string('.', ((int)(Timing.TotalTime * 2.0f) % 3) + 1);
|
||||
|
||||
System.Diagnostics.Debug.Assert(GuiFrame.GetChild(0) is GUILayoutGroup, "Repair UI hierarchy has changed, could not find skill texts");
|
||||
foreach (GUIComponent c in GuiFrame.GetChild(0).Children)
|
||||
{
|
||||
if (!(c.UserData is Skill skill)) continue;
|
||||
|
||||
GUITextBlock textBlock = (GUITextBlock)c;
|
||||
if (character.GetSkillLevel(skill.Identifier) < skill.Level)
|
||||
{
|
||||
textBlock.TextColor = GUI.Style.Red;
|
||||
}
|
||||
else
|
||||
{
|
||||
textBlock.TextColor = Color.White;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1)
|
||||
{
|
||||
if (GameMain.DebugDraw && Character.Controlled?.FocusedItem == item)
|
||||
{
|
||||
bool paused = !ShouldDeteriorate();
|
||||
if (deteriorationTimer > 0.0f)
|
||||
{
|
||||
GUI.DrawString(spriteBatch,
|
||||
new Vector2(item.WorldPosition.X, -item.WorldPosition.Y), "Deterioration delay " + ((int)deteriorationTimer) + (paused ? " [PAUSED]" : ""),
|
||||
paused ? Color.Cyan : Color.Lime, Color.Black * 0.5f);
|
||||
}
|
||||
else
|
||||
{
|
||||
GUI.DrawString(spriteBatch,
|
||||
new Vector2(item.WorldPosition.X, -item.WorldPosition.Y), "Deteriorating at " + (int)(DeteriorationSpeed * 60.0f) + " units/min" + (paused ? " [PAUSED]" : ""),
|
||||
paused ? Color.Cyan : GUI.Style.Red, Color.Black * 0.5f);
|
||||
}
|
||||
GUI.DrawString(spriteBatch,
|
||||
new Vector2(item.WorldPosition.X, -item.WorldPosition.Y + 20), "Condition: " + (int)item.Condition + "/" + (int)item.MaxCondition,
|
||||
GUI.Style.Orange);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void RemoveComponentSpecific()
|
||||
{
|
||||
repairSoundChannel?.FadeOutAndDispose();
|
||||
repairSoundChannel = null;
|
||||
}
|
||||
|
||||
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
|
||||
{
|
||||
deteriorationTimer = msg.ReadSingle();
|
||||
deteriorateAlwaysResetTimer = msg.ReadSingle();
|
||||
DeteriorateAlways = msg.ReadBoolean();
|
||||
ushort currentFixerID = msg.ReadUInt16();
|
||||
currentFixerAction = (FixActions)msg.ReadRangedInteger(0, 2);
|
||||
CurrentFixer = currentFixerID != 0 ? Entity.FindEntityByID(currentFixerID) as Character : null;
|
||||
}
|
||||
|
||||
public void ClientWrite(IWriteMessage msg, object[] extraData = null)
|
||||
{
|
||||
msg.WriteRangedInteger((int)requestStartFixAction, 0, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,391 @@
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class Connection
|
||||
{
|
||||
//private static Texture2D panelTexture;
|
||||
private static Sprite connector;
|
||||
private static Sprite wireVertical;
|
||||
private static Sprite connectionSprite;
|
||||
private static Sprite connectionSpriteHighlight;
|
||||
private static List<Sprite> screwSprites;
|
||||
|
||||
private Color flashColor;
|
||||
private float flashDuration = 1.5f;
|
||||
|
||||
public float FlashTimer { get; private set; }
|
||||
public static Wire DraggingConnected { get; private set; }
|
||||
|
||||
public static void DrawConnections(SpriteBatch spriteBatch, ConnectionPanel panel, Character character)
|
||||
{
|
||||
Rectangle panelRect = panel.GuiFrame.Rect;
|
||||
int x = panelRect.X, y = panelRect.Y;
|
||||
int width = panelRect.Width, height = panelRect.Height;
|
||||
|
||||
Vector2 scale = new Vector2(GUI.Scale);
|
||||
if (panel.GuiFrame.RectTransform.MaxSize.X < int.MaxValue)
|
||||
{
|
||||
scale.X = panel.GuiFrame.RectTransform.MaxSize.X / panel.GuiFrame.Rect.Width;
|
||||
}
|
||||
if (panel.GuiFrame.RectTransform.MaxSize.Y < int.MaxValue)
|
||||
{
|
||||
scale.Y = panel.GuiFrame.RectTransform.MaxSize.Y / panel.GuiFrame.Rect.Height;
|
||||
}
|
||||
|
||||
bool mouseInRect = panelRect.Contains(PlayerInput.MousePosition);
|
||||
|
||||
int totalWireCount = 0;
|
||||
foreach (Connection c in panel.Connections)
|
||||
{
|
||||
totalWireCount += c.Wires.Count(w => w != null);
|
||||
}
|
||||
|
||||
Wire equippedWire = null;
|
||||
|
||||
bool allowRewiring = GameMain.NetworkMember?.ServerSettings == null || GameMain.NetworkMember.ServerSettings.AllowRewiring;
|
||||
if (allowRewiring && (!panel.Locked || Screen.Selected == GameMain.SubEditorScreen))
|
||||
{
|
||||
//if the Character using the panel has a wire item equipped
|
||||
//and the wire hasn't been connected yet, draw it on the panel
|
||||
for (int i = 0; i < character.SelectedItems.Length; i++)
|
||||
{
|
||||
Item selectedItem = character.SelectedItems[i];
|
||||
|
||||
if (selectedItem == null) { continue; }
|
||||
|
||||
Wire wireComponent = selectedItem.GetComponent<Wire>();
|
||||
if (wireComponent != null)
|
||||
{
|
||||
equippedWire = wireComponent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//two passes: first the connector, then the wires to get the wires to render in front
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
Vector2 rightPos = new Vector2(x + width - 80 * scale.X, y + 60 * scale.Y);
|
||||
Vector2 leftPos = new Vector2(x + 80 * scale.X, y + 60 * scale.Y);
|
||||
|
||||
Vector2 rightWirePos = new Vector2(x + width - 5 * scale.X, y + 30 * scale.Y);
|
||||
Vector2 leftWirePos = new Vector2(x + 5 * scale.X, y + 30 * scale.Y);
|
||||
|
||||
int wireInterval = (height - (int)(20 * scale.Y)) / Math.Max(totalWireCount, 1);
|
||||
int connectorIntervalLeft = (height - (int)(100 * scale.Y)) / Math.Max(panel.Connections.Count(c => c.IsOutput), 1);
|
||||
int connectorIntervalRight = (height - (int)(100 * scale.Y)) / Math.Max(panel.Connections.Count(c => !c.IsOutput), 1);
|
||||
|
||||
foreach (Connection c in panel.Connections)
|
||||
{
|
||||
//if dragging a wire, let the Inventory know so that the wire can be
|
||||
//dropped or dragged from the panel to the players inventory
|
||||
if (DraggingConnected != null && i == 1)
|
||||
{
|
||||
//the wire can only be dragged out if it's not connected to anything at the other end
|
||||
if (Screen.Selected == GameMain.SubEditorScreen ||
|
||||
(DraggingConnected.Connections[0] == null && DraggingConnected.Connections[1] == null) ||
|
||||
(DraggingConnected.Connections.Contains(c) && DraggingConnected.Connections.Contains(null)))
|
||||
{
|
||||
int linkIndex = c.FindWireIndex(DraggingConnected.Item);
|
||||
if (linkIndex > -1 || panel.DisconnectedWires.Contains(DraggingConnected))
|
||||
{
|
||||
Inventory.draggingItem = DraggingConnected.Item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//outputs are drawn at the right side of the panel, inputs at the left
|
||||
if (c.IsOutput)
|
||||
{
|
||||
if (i == 0)
|
||||
{
|
||||
c.DrawConnection(spriteBatch, panel, rightPos,
|
||||
new Vector2(rightPos.X - GUI.SmallFont.MeasureString(c.DisplayName.ToUpper()).X - 25 * panel.Scale, rightPos.Y + 5 * panel.Scale),
|
||||
scale);
|
||||
}
|
||||
else
|
||||
{
|
||||
c.DrawWires(spriteBatch, panel, rightPos, rightWirePos, mouseInRect, equippedWire, wireInterval);
|
||||
}
|
||||
|
||||
rightPos.Y += connectorIntervalLeft;
|
||||
rightWirePos.Y += c.Wires.Count(w => w != null) * wireInterval;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (i == 0)
|
||||
{
|
||||
c.DrawConnection(spriteBatch, panel, leftPos,
|
||||
new Vector2(leftPos.X + 25 * panel.Scale, leftPos.Y - 5 * panel.Scale - GUI.SmallFont.MeasureString(c.DisplayName.ToUpper()).Y),
|
||||
scale);
|
||||
}
|
||||
else
|
||||
{
|
||||
c.DrawWires(spriteBatch, panel, leftPos, leftWirePos, mouseInRect, equippedWire, wireInterval);
|
||||
}
|
||||
|
||||
leftPos.Y += connectorIntervalRight;
|
||||
leftWirePos.Y += c.Wires.Count(w => w != null) * wireInterval;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (DraggingConnected != null)
|
||||
{
|
||||
if (mouseInRect)
|
||||
{
|
||||
DrawWire(spriteBatch, DraggingConnected, PlayerInput.MousePosition, new Vector2(x + width / 2, y + height - 10), null, panel, "");
|
||||
}
|
||||
panel.TriggerRewiringSound();
|
||||
|
||||
if (!PlayerInput.PrimaryMouseButtonHeld())
|
||||
{
|
||||
if (GameMain.NetworkMember != null || panel.CheckCharacterSuccess(character))
|
||||
{
|
||||
if (DraggingConnected.Connections[0]?.ConnectionPanel == panel ||
|
||||
DraggingConnected.Connections[1]?.ConnectionPanel == panel)
|
||||
{
|
||||
DraggingConnected.RemoveConnection(panel.Item);
|
||||
if (DraggingConnected.Item.ParentInventory == null)
|
||||
{
|
||||
panel.DisconnectedWires.Add(DraggingConnected);
|
||||
}
|
||||
else if (DraggingConnected.Connections[0] == null && DraggingConnected.Connections[1] == null)
|
||||
{
|
||||
DraggingConnected.ClearConnections(user: Character.Controlled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (GameMain.Client != null)
|
||||
{
|
||||
panel.Item.CreateClientEvent(panel);
|
||||
}
|
||||
|
||||
DraggingConnected = null;
|
||||
}
|
||||
}
|
||||
|
||||
//if the Character using the panel has a wire item equipped
|
||||
//and the wire hasn't been connected yet, draw it on the panel
|
||||
if (equippedWire != null && (DraggingConnected != equippedWire || !mouseInRect))
|
||||
{
|
||||
if (panel.Connections.Find(c => c.Wires.Contains(equippedWire)) == null)
|
||||
{
|
||||
DrawWire(spriteBatch, equippedWire, new Vector2(x + width / 2, y + height - 150 * GUI.Scale),
|
||||
new Vector2(x + width / 2, y + height),
|
||||
null, panel, "");
|
||||
|
||||
if (DraggingConnected == equippedWire) { Inventory.draggingItem = equippedWire.Item; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
float step = (width * 0.75f) / panel.DisconnectedWires.Count();
|
||||
x = (int)(x + width / 2 - step * (panel.DisconnectedWires.Count() - 1) / 2);
|
||||
foreach (Wire wire in panel.DisconnectedWires)
|
||||
{
|
||||
if (wire == DraggingConnected && mouseInRect) { continue; }
|
||||
|
||||
Connection recipient = wire.OtherConnection(null);
|
||||
string label = recipient == null ? "" : recipient.item.Name + $" ({recipient.DisplayName})";
|
||||
if (wire.Locked) { label += "\n" + TextManager.Get("ConnectionLocked"); }
|
||||
DrawWire(spriteBatch, wire, new Vector2(x, y + height - 100 * GUI.Scale),
|
||||
new Vector2(x, y + height),
|
||||
null, panel, label);
|
||||
x += (int)step;
|
||||
}
|
||||
|
||||
//stop dragging a wire item if the cursor is within any connection panel
|
||||
//(so we don't drop the item when dropping the wire on a connection)
|
||||
if (mouseInRect || (GUI.MouseOn?.UserData is ConnectionPanel && GUI.MouseOn.MouseRect.Contains(PlayerInput.MousePosition)))
|
||||
{
|
||||
Inventory.draggingItem = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawConnection(SpriteBatch spriteBatch, ConnectionPanel panel, Vector2 position, Vector2 labelPos, Vector2 scale)
|
||||
{
|
||||
string text = DisplayName.ToUpper();
|
||||
Vector2 textSize = GUI.SmallFont.MeasureString(text);
|
||||
|
||||
//nasty
|
||||
var labelSprite = GUI.Style.GetComponentStyle("ConnectionPanelLabel")?.Sprites.Values.First().First();
|
||||
if (labelSprite != null)
|
||||
{
|
||||
Rectangle labelArea = new Rectangle(labelPos.ToPoint(), textSize.ToPoint());
|
||||
labelArea.Inflate(10 * scale.X, 3 * scale.Y);
|
||||
labelSprite.Draw(spriteBatch, labelArea, IsPower ? GUI.Style.Red : Color.SteelBlue);
|
||||
}
|
||||
|
||||
GUI.DrawString(spriteBatch, labelPos + Vector2.UnitY, text, Color.Black * 0.8f, font: GUI.SmallFont);
|
||||
GUI.DrawString(spriteBatch, labelPos, text, GUI.Style.TextColorBright, font: GUI.SmallFont);
|
||||
|
||||
float connectorSpriteScale = (35.0f / connectionSprite.SourceRect.Width) * panel.Scale;
|
||||
connectionSprite.Draw(spriteBatch, position, scale: connectorSpriteScale);
|
||||
}
|
||||
|
||||
private void DrawWires(SpriteBatch spriteBatch, ConnectionPanel panel, Vector2 position, Vector2 wirePosition, bool mouseIn, Wire equippedWire, float wireInterval)
|
||||
{
|
||||
float connectorSpriteScale = (35.0f / connectionSprite.SourceRect.Width) * panel.Scale;
|
||||
|
||||
for (int i = 0; i < MaxLinked; i++)
|
||||
{
|
||||
if (wires[i] == null || wires[i].Hidden || (DraggingConnected == wires[i] && (mouseIn || Screen.Selected == GameMain.SubEditorScreen))) { continue; }
|
||||
|
||||
Connection recipient = wires[i].OtherConnection(this);
|
||||
string label = recipient == null ? "" : recipient.item.Name + $" ({recipient.DisplayName})";
|
||||
if (wires[i].Locked) { label += "\n" + TextManager.Get("ConnectionLocked"); }
|
||||
DrawWire(spriteBatch, wires[i], position, wirePosition, equippedWire, panel, label);
|
||||
|
||||
wirePosition.Y += wireInterval;
|
||||
}
|
||||
|
||||
if (DraggingConnected != null && Vector2.Distance(position, PlayerInput.MousePosition) < (20.0f * GUI.Scale))
|
||||
{
|
||||
connectionSpriteHighlight.Draw(spriteBatch, position, scale: connectorSpriteScale);
|
||||
|
||||
if (!PlayerInput.PrimaryMouseButtonHeld())
|
||||
{
|
||||
if (GameMain.NetworkMember != null || panel.CheckCharacterSuccess(Character.Controlled))
|
||||
{
|
||||
//find an empty cell for the new connection
|
||||
int index = FindEmptyIndex();
|
||||
if (index > -1 && !Wires.Contains(DraggingConnected))
|
||||
{
|
||||
bool alreadyConnected = DraggingConnected.IsConnectedTo(panel.Item);
|
||||
DraggingConnected.RemoveConnection(panel.Item);
|
||||
if (DraggingConnected.Connect(this, !alreadyConnected, true))
|
||||
{
|
||||
var otherConnection = DraggingConnected.OtherConnection(this);
|
||||
SetWire(index, DraggingConnected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (GameMain.Client != null)
|
||||
{
|
||||
panel.Item.CreateClientEvent(panel);
|
||||
}
|
||||
DraggingConnected = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (FlashTimer > 0.0f)
|
||||
{
|
||||
//the number of flashes depends on the duration, 1 flash per 1 full second
|
||||
int flashCycleCount = (int)Math.Max(flashDuration, 1);
|
||||
float flashCycleDuration = flashDuration / flashCycleCount;
|
||||
|
||||
//MathHelper.Pi * 0.8f -> the curve goes from 144 deg to 0,
|
||||
//i.e. quickly bumps up from almost full brightness to full and then fades out
|
||||
connectionSpriteHighlight.Draw(spriteBatch, position,
|
||||
flashColor * (float)Math.Sin(FlashTimer % flashCycleDuration / flashCycleDuration * MathHelper.Pi * 0.8f), scale: connectorSpriteScale);
|
||||
}
|
||||
|
||||
if (Wires.Any(w => w != null && w != DraggingConnected))
|
||||
{
|
||||
int screwIndex = (int)Math.Floor(position.Y / 30.0f) % screwSprites.Count;
|
||||
screwSprites[screwIndex].Draw(spriteBatch, position, scale: connectorSpriteScale);
|
||||
}
|
||||
}
|
||||
|
||||
public void Flash(Color? color = null, float flashDuration = 1.5f)
|
||||
{
|
||||
FlashTimer = flashDuration;
|
||||
this.flashDuration = flashDuration;
|
||||
flashColor = (color == null) ? GUI.Style.Red : (Color)color;
|
||||
}
|
||||
|
||||
public void UpdateFlashTimer(float deltaTime)
|
||||
{
|
||||
if (FlashTimer <= 0) return;
|
||||
FlashTimer -= deltaTime;
|
||||
}
|
||||
|
||||
private static void DrawWire(SpriteBatch spriteBatch, Wire wire, Vector2 end, Vector2 start, Wire equippedWire, ConnectionPanel panel, string label)
|
||||
{
|
||||
int textX = (int)start.X;
|
||||
if (start.X < end.X)
|
||||
textX -= 10;
|
||||
else
|
||||
textX += 10;
|
||||
|
||||
bool canDrag = equippedWire == null || equippedWire == wire;
|
||||
|
||||
float alpha = canDrag ? 1.0f : 0.5f;
|
||||
|
||||
bool mouseOn =
|
||||
canDrag &&
|
||||
((PlayerInput.MousePosition.X > Math.Min(start.X, end.X) &&
|
||||
PlayerInput.MousePosition.X < Math.Max(start.X, end.X) &&
|
||||
MathUtils.LineToPointDistance(start, end, PlayerInput.MousePosition) < 6) ||
|
||||
Vector2.Distance(end, PlayerInput.MousePosition) < 20.0f ||
|
||||
new Rectangle((start.X < end.X) ? textX - 100 : textX, (int)start.Y - 5, 100, 14).Contains(PlayerInput.MousePosition));
|
||||
|
||||
if (!string.IsNullOrEmpty(label))
|
||||
{
|
||||
if (start.Y > panel.GuiFrame.Rect.Bottom - 1.0f)
|
||||
{
|
||||
//wire at the bottom of the panel -> draw the text below the panel, tilted 45 degrees
|
||||
GUI.Font.DrawString(spriteBatch, label, start + Vector2.UnitY * 20 * GUI.Scale, Color.White, 45.0f, Vector2.Zero, 1.0f, SpriteEffects.None, 0.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
GUI.DrawString(spriteBatch,
|
||||
new Vector2(start.X < end.X ? textX - GUI.SmallFont.MeasureString(label).X : textX, start.Y - 5.0f),
|
||||
label,
|
||||
wire.Locked ? GUI.Style.TextColorDim : (mouseOn ? Wire.higlightColor : GUI.Style.TextColor), Color.Black * 0.9f,
|
||||
3, GUI.SmallFont);
|
||||
}
|
||||
}
|
||||
|
||||
var wireEnd = end + Vector2.Normalize(start - end) * 30.0f * panel.Scale;
|
||||
|
||||
float dist = Vector2.Distance(start, wireEnd);
|
||||
|
||||
float wireWidth = 12 * panel.Scale;
|
||||
float highlight = 5 * panel.Scale;
|
||||
if (mouseOn)
|
||||
{
|
||||
spriteBatch.Draw(wireVertical.Texture, new Rectangle(wireEnd.ToPoint(), new Point((int)(wireWidth + highlight), (int)dist)), wireVertical.SourceRect,
|
||||
Wire.higlightColor,
|
||||
MathUtils.VectorToAngle(end - start) + MathHelper.PiOver2,
|
||||
new Vector2(wireVertical.size.X / 2, 0), // point in line about which to rotate
|
||||
SpriteEffects.None,
|
||||
0.0f);
|
||||
}
|
||||
spriteBatch.Draw(wireVertical.Texture, new Rectangle(wireEnd.ToPoint(), new Point((int)wireWidth, (int)dist)), wireVertical.SourceRect,
|
||||
wire.Item.Color * alpha,
|
||||
MathUtils.VectorToAngle(end - start) + MathHelper.PiOver2,
|
||||
new Vector2(wireVertical.size.X / 2, 0), // point in line about which to rotate
|
||||
SpriteEffects.None,
|
||||
0.0f);
|
||||
|
||||
float connectorScale = wireWidth / (float)wireVertical.SourceRect.Width;
|
||||
|
||||
connector.Draw(spriteBatch, end, Color.White, connector.Origin, MathUtils.VectorToAngle(end - start) + MathHelper.PiOver2, scale: connectorScale);
|
||||
|
||||
if (DraggingConnected == null && canDrag)
|
||||
{
|
||||
if (mouseOn)
|
||||
{
|
||||
ConnectionPanel.HighlightedWire = wire;
|
||||
|
||||
bool allowRewiring = GameMain.NetworkMember?.ServerSettings == null || GameMain.NetworkMember.ServerSettings.AllowRewiring;
|
||||
if (allowRewiring && !wire.Locked && (!panel.Locked || Screen.Selected == GameMain.SubEditorScreen))
|
||||
{
|
||||
//start dragging the wire
|
||||
if (PlayerInput.PrimaryMouseButtonHeld()) { DraggingConnected = wire; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.Sounds;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class ConnectionPanel : ItemComponent, IServerSerializable, IClientSerializable
|
||||
{
|
||||
//how long the rewiring sound plays after doing changes to the wiring
|
||||
const float RewireSoundDuration = 5.0f;
|
||||
|
||||
public static Wire HighlightedWire;
|
||||
|
||||
private SoundChannel rewireSoundChannel;
|
||||
private float rewireSoundTimer;
|
||||
|
||||
public float Scale
|
||||
{
|
||||
get { return GuiFrame.Rect.Width / 400.0f; }
|
||||
}
|
||||
|
||||
partial void InitProjSpecific(XElement element)
|
||||
{
|
||||
if (GuiFrame == null) { return; }
|
||||
new GUICustomComponent(new RectTransform(Vector2.One, GuiFrame.RectTransform), DrawConnections, null)
|
||||
{
|
||||
UserData = this
|
||||
};
|
||||
}
|
||||
|
||||
public void TriggerRewiringSound()
|
||||
{
|
||||
rewireSoundTimer = RewireSoundDuration;
|
||||
}
|
||||
|
||||
partial void UpdateProjSpecific(float deltaTime)
|
||||
{
|
||||
foreach (Wire wire in DisconnectedWires)
|
||||
{
|
||||
if (Rand.Range(0.0f, 500.0f) < 1.0f)
|
||||
{
|
||||
SoundPlayer.PlaySound("zap", item.WorldPosition, hullGuess: item.CurrentHull);
|
||||
Vector2 baseVel = new Vector2(0.0f, -100.0f);
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
var particle = GameMain.ParticleManager.CreateParticle("spark", item.WorldPosition,
|
||||
baseVel + Rand.Vector(100.0f), 0.0f, item.CurrentHull);
|
||||
if (particle != null) { particle.Size *= Rand.Range(0.5f, 1.0f); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rewireSoundTimer -= deltaTime;
|
||||
if (user != null && user.SelectedConstruction == item && rewireSoundTimer > 0.0f)
|
||||
{
|
||||
if (rewireSoundChannel == null || !rewireSoundChannel.IsPlaying)
|
||||
{
|
||||
rewireSoundChannel = SoundPlayer.PlaySound("rewire", item.WorldPosition, hullGuess: item.CurrentHull);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
rewireSoundChannel?.FadeOutAndDispose();
|
||||
rewireSoundChannel = null;
|
||||
rewireSoundTimer = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Move(Vector2 amount)
|
||||
{
|
||||
if (item.Submarine == null || item.Submarine.Loading || Screen.Selected != GameMain.SubEditorScreen) { return; }
|
||||
MoveConnectedWires(amount);
|
||||
}
|
||||
|
||||
public override bool ShouldDrawHUD(Character character)
|
||||
{
|
||||
return character == Character.Controlled && character == user && character.SelectedConstruction == item;
|
||||
}
|
||||
|
||||
public override void AddToGUIUpdateList()
|
||||
{
|
||||
GuiFrame?.AddToGUIUpdateList();
|
||||
}
|
||||
|
||||
public override void UpdateHUD(Character character, float deltaTime, Camera cam)
|
||||
{
|
||||
if (character != Character.Controlled || character != user || character.SelectedConstruction != item) { return; }
|
||||
|
||||
if (HighlightedWire != null)
|
||||
{
|
||||
HighlightedWire.Item.IsHighlighted = true;
|
||||
if (HighlightedWire.Connections[0] != null && HighlightedWire.Connections[0].Item != null) HighlightedWire.Connections[0].Item.IsHighlighted = true;
|
||||
if (HighlightedWire.Connections[1] != null && HighlightedWire.Connections[1].Item != null) HighlightedWire.Connections[1].Item.IsHighlighted = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawConnections(SpriteBatch spriteBatch, GUICustomComponent container)
|
||||
{
|
||||
if (user != Character.Controlled || user == null) { return; }
|
||||
|
||||
HighlightedWire = null;
|
||||
Connection.DrawConnections(spriteBatch, this, user);
|
||||
|
||||
foreach (UISprite sprite in GUI.Style.GetComponentStyle("ConnectionPanelFront").Sprites[GUIComponent.ComponentState.None])
|
||||
{
|
||||
sprite.Draw(spriteBatch, GuiFrame.Rect, Color.White, SpriteEffects.None);
|
||||
}
|
||||
}
|
||||
|
||||
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
|
||||
{
|
||||
if (GameMain.Client.MidRoundSyncing)
|
||||
{
|
||||
//delay reading the state until midround syncing is done
|
||||
//because some of the wires connected to the panel may not exist yet
|
||||
long msgStartPos = msg.BitPosition;
|
||||
msg.ReadUInt16(); //user ID
|
||||
foreach (Connection connection in Connections)
|
||||
{
|
||||
for (int i = 0; i < Connection.MaxLinked; i++)
|
||||
{
|
||||
msg.ReadUInt16();
|
||||
}
|
||||
}
|
||||
ushort disconnectedWireCount = msg.ReadUInt16();
|
||||
for (int i = 0; i < disconnectedWireCount; i++)
|
||||
{
|
||||
msg.ReadUInt16();
|
||||
}
|
||||
int msgLength = (int)(msg.BitPosition - msgStartPos);
|
||||
msg.BitPosition = (int)msgStartPos;
|
||||
StartDelayedCorrection(type, msg.ExtractBits(msgLength), sendingTime, waitForMidRoundSync: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
//don't trigger rewiring sounds if the rewiring is being done by the local user (in that case we'll trigger it locally)
|
||||
if (Character.Controlled == null || user != Character.Controlled) { TriggerRewiringSound(); }
|
||||
ApplyRemoteState(msg);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyRemoteState(IReadMessage msg)
|
||||
{
|
||||
List<Wire> prevWires = Connections.SelectMany(c => c.Wires.Where(w => w != null)).ToList();
|
||||
List<Wire> newWires = new List<Wire>();
|
||||
|
||||
ushort userID = msg.ReadUInt16();
|
||||
|
||||
if (userID == 0)
|
||||
{
|
||||
user = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
user = Entity.FindEntityByID(userID) as Character;
|
||||
base.IsActive = true;
|
||||
}
|
||||
|
||||
foreach (Connection connection in Connections)
|
||||
{
|
||||
connection.ClearConnections();
|
||||
}
|
||||
|
||||
foreach (Connection connection in Connections)
|
||||
{
|
||||
for (int i = 0; i < Connection.MaxLinked; i++)
|
||||
{
|
||||
ushort wireId = msg.ReadUInt16();
|
||||
|
||||
if (!(Entity.FindEntityByID(wireId) is Item wireItem)) { continue; }
|
||||
Wire wireComponent = wireItem.GetComponent<Wire>();
|
||||
if (wireComponent == null) { continue; }
|
||||
|
||||
newWires.Add(wireComponent);
|
||||
|
||||
connection.SetWire(i, wireComponent);
|
||||
wireComponent.Connect(connection, false);
|
||||
}
|
||||
}
|
||||
|
||||
List<Wire> previousDisconnectedWires = new List<Wire>(DisconnectedWires);
|
||||
DisconnectedWires.Clear();
|
||||
ushort disconnectedWireCount = msg.ReadUInt16();
|
||||
for (int i = 0; i < disconnectedWireCount; i++)
|
||||
{
|
||||
ushort wireId = msg.ReadUInt16();
|
||||
if (!(Entity.FindEntityByID(wireId) is Item wireItem)) { continue; }
|
||||
Wire wireComponent = wireItem.GetComponent<Wire>();
|
||||
if (wireComponent == null) { continue; }
|
||||
DisconnectedWires.Add(wireComponent);
|
||||
base.IsActive = true;
|
||||
}
|
||||
|
||||
foreach (Wire wire in prevWires)
|
||||
{
|
||||
bool connected = wire.Connections[0] != null || wire.Connections[1] != null;
|
||||
if (!connected)
|
||||
{
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
var connectionPanel = item.GetComponent<ConnectionPanel>();
|
||||
if (connectionPanel != null && connectionPanel.DisconnectedWires.Contains(wire))
|
||||
{
|
||||
connected = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (wire.Item.ParentInventory == null && !connected)
|
||||
{
|
||||
wire.Item.Drop(null);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Wire disconnectedWire in previousDisconnectedWires)
|
||||
{
|
||||
if (disconnectedWire.Connections[0] == null &&
|
||||
disconnectedWire.Connections[1] == null &&
|
||||
!DisconnectedWires.Contains(disconnectedWire))
|
||||
{
|
||||
disconnectedWire.Item.Drop(dropper: null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class CustomInterface
|
||||
{
|
||||
private readonly List<GUIComponent> uiElements = new List<GUIComponent>();
|
||||
private GUILayoutGroup uiElementContainer;
|
||||
|
||||
private Point ElementMaxSize => new Point(uiElementContainer.Rect.Width, (int)(65 * GUI.yScale));
|
||||
|
||||
partial void InitProjSpecific(XElement element)
|
||||
{
|
||||
CreateGUI();
|
||||
GameMain.Instance.OnResolutionChanged += RecreateGUI;
|
||||
}
|
||||
|
||||
private void RecreateGUI()
|
||||
{
|
||||
GuiFrame.ClearChildren();
|
||||
CreateGUI();
|
||||
UpdateLabelsProjSpecific();
|
||||
}
|
||||
|
||||
private void CreateGUI()
|
||||
{
|
||||
uiElements.Clear();
|
||||
var visibleElements = customInterfaceElementList.Where(ciElement => !string.IsNullOrEmpty(ciElement.Label));
|
||||
uiElementContainer = new GUILayoutGroup(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center)
|
||||
{
|
||||
AbsoluteOffset = GUIStyle.ItemFrameOffset
|
||||
},
|
||||
childAnchor: customInterfaceElementList.Count > 1 ? Anchor.TopCenter : Anchor.Center)
|
||||
{
|
||||
RelativeSpacing = 0.05f,
|
||||
Stretch = visibleElements.Count() > 2,
|
||||
};
|
||||
|
||||
float elementSize = Math.Min(1.0f / visibleElements.Count(), 1);
|
||||
foreach (CustomInterfaceElement ciElement in visibleElements)
|
||||
{
|
||||
if (ciElement.ContinuousSignal)
|
||||
{
|
||||
var tickBox = new GUITickBox(new RectTransform(new Vector2(1.0f, elementSize), uiElementContainer.RectTransform)
|
||||
{
|
||||
MaxSize = ElementMaxSize
|
||||
},
|
||||
TextManager.Get(ciElement.Label, returnNull: true) ?? ciElement.Label)
|
||||
{
|
||||
UserData = ciElement
|
||||
};
|
||||
tickBox.OnSelected += (tBox) =>
|
||||
{
|
||||
if (GameMain.Client == null)
|
||||
{
|
||||
TickBoxToggled(tBox.UserData as CustomInterfaceElement, tBox.Selected);
|
||||
}
|
||||
else
|
||||
{
|
||||
item.CreateClientEvent(this);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
//reset size restrictions set by the Style to make sure the elements can fit the interface
|
||||
tickBox.RectTransform.MinSize = new Point(0, 0);
|
||||
tickBox.RectTransform.MaxSize = new Point(int.MaxValue, int.MaxValue);
|
||||
uiElements.Add(tickBox);
|
||||
}
|
||||
else
|
||||
{
|
||||
var btn = new GUIButton(new RectTransform(new Vector2(1.0f, elementSize), uiElementContainer.RectTransform),
|
||||
TextManager.Get(ciElement.Label, returnNull: true) ?? ciElement.Label, style: "DeviceButton")
|
||||
{
|
||||
UserData = ciElement
|
||||
};
|
||||
btn.OnClicked += (_, userdata) =>
|
||||
{
|
||||
if (GameMain.Client == null)
|
||||
{
|
||||
ButtonClicked(userdata as CustomInterfaceElement);
|
||||
}
|
||||
else
|
||||
{
|
||||
GameMain.Client.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ComponentState, item.GetComponentIndex(this), userdata as CustomInterfaceElement });
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
//reset size restrictions set by the Style to make sure the elements can fit the interface
|
||||
btn.RectTransform.MinSize = btn.Frame.RectTransform.MinSize = new Point(0, 0);
|
||||
btn.RectTransform.MaxSize = btn.Frame.RectTransform.MaxSize = ElementMaxSize;
|
||||
|
||||
uiElements.Add(btn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void CreateEditingHUD(SerializableEntityEditor editor)
|
||||
{
|
||||
base.CreateEditingHUD(editor);
|
||||
|
||||
if (customInterfaceElementList.Count > 0)
|
||||
{
|
||||
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(customInterfaceElementList[0]);
|
||||
PropertyDescriptor labelProperty = properties.Find("Label", false);
|
||||
PropertyDescriptor signalProperty = properties.Find("Signal", false);
|
||||
for (int i = 0; i < customInterfaceElementList.Count; i++)
|
||||
{
|
||||
editor.CreateStringField(customInterfaceElementList[i],
|
||||
new SerializableProperty(labelProperty),
|
||||
customInterfaceElementList[i].Label, "Label #" + (i + 1), "");
|
||||
editor.CreateStringField(customInterfaceElementList[i],
|
||||
new SerializableProperty(signalProperty),
|
||||
customInterfaceElementList[i].Signal, "Signal #" + (i + 1), "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void HighlightElement(int index, Color color, float duration, float pulsateAmount = 0.0f)
|
||||
{
|
||||
if (index < 0 || index >= uiElements.Count) { return; }
|
||||
uiElements[index].Flash(color, duration);
|
||||
|
||||
if (pulsateAmount > 0.0f)
|
||||
{
|
||||
if (uiElements[index] is GUIButton button)
|
||||
{
|
||||
button.Frame.Pulsate(Vector2.One, Vector2.One * (1.0f + pulsateAmount), duration);
|
||||
button.Frame.RectTransform.SetPosition(Anchor.Center);
|
||||
}
|
||||
else
|
||||
{
|
||||
uiElements[index].Pulsate(Vector2.One, Vector2.One * (1.0f + pulsateAmount), duration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
partial void UpdateProjSpecific()
|
||||
{
|
||||
bool elementVisibilityChanged = false;
|
||||
int visibleElementCount = 0;
|
||||
foreach (var uiElement in uiElements)
|
||||
{
|
||||
if (!(uiElement.UserData is CustomInterfaceElement element)) { continue; }
|
||||
bool visible = Screen.Selected == GameMain.SubEditorScreen || element.StatusEffects.Any() || (element.Connection != null && element.Connection.Wires.Any(w => w != null));
|
||||
if (visible) { visibleElementCount++; }
|
||||
if (uiElement.Visible != visible)
|
||||
{
|
||||
uiElement.Visible = visible;
|
||||
elementVisibilityChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (elementVisibilityChanged)
|
||||
{
|
||||
uiElementContainer.Stretch = visibleElementCount > 2;
|
||||
uiElementContainer.ChildAnchor = visibleElementCount > 1 ? Anchor.TopCenter : Anchor.Center;
|
||||
float elementSize = Math.Min(1.0f / visibleElementCount, 1);
|
||||
foreach (var uiElement in uiElements)
|
||||
{
|
||||
uiElement.RectTransform.RelativeSize = new Vector2(1.0f, elementSize);
|
||||
}
|
||||
GuiFrame.Visible = visibleElementCount > 0;
|
||||
}
|
||||
}
|
||||
|
||||
partial void UpdateLabelsProjSpecific()
|
||||
{
|
||||
for (int i = 0; i < labels.Length && i < uiElements.Count; i++)
|
||||
{
|
||||
if (uiElements[i] is GUIButton button)
|
||||
{
|
||||
button.Text = string.IsNullOrWhiteSpace(customInterfaceElementList[i].Label) ?
|
||||
TextManager.GetWithVariable("connection.signaloutx", "[num]", (i + 1).ToString()) :
|
||||
customInterfaceElementList[i].Label;
|
||||
button.TextBlock.Wrap = button.Text.Contains(' ');
|
||||
}
|
||||
else if (uiElements[i] is GUITickBox tickBox)
|
||||
{
|
||||
tickBox.Text = string.IsNullOrWhiteSpace(customInterfaceElementList[i].Label) ?
|
||||
TextManager.GetWithVariable("connection.signaloutx", "[num]", (i + 1).ToString()) :
|
||||
customInterfaceElementList[i].Label;
|
||||
tickBox.TextBlock.Wrap = tickBox.Text.Contains(' ');
|
||||
}
|
||||
}
|
||||
|
||||
uiElementContainer.Recalculate();
|
||||
var textBlocks = new List<GUITextBlock>();
|
||||
foreach (GUIComponent element in uiElementContainer.Children)
|
||||
{
|
||||
if (element is GUIButton btn)
|
||||
{
|
||||
if (btn.TextBlock.TextSize.Y > btn.Rect.Height - btn.TextBlock.Padding.Y - btn.TextBlock.Padding.W)
|
||||
{
|
||||
btn.RectTransform.RelativeSize = new Vector2(btn.RectTransform.RelativeSize.X, btn.RectTransform.RelativeSize.Y * 1.5f);
|
||||
}
|
||||
textBlocks.Add(btn.TextBlock);
|
||||
}
|
||||
else if (element is GUITickBox tickBox)
|
||||
{
|
||||
textBlocks.Add(tickBox.TextBlock);
|
||||
}
|
||||
}
|
||||
uiElementContainer.Recalculate();
|
||||
GUITextBlock.AutoScaleAndNormalize(textBlocks);
|
||||
}
|
||||
|
||||
public void ClientWrite(IWriteMessage msg, object[] extraData = null)
|
||||
{
|
||||
//extradata contains an array of buttons clicked by the player (or nothing if the player didn't click anything)
|
||||
for (int i = 0; i < customInterfaceElementList.Count; i++)
|
||||
{
|
||||
if (customInterfaceElementList[i].ContinuousSignal)
|
||||
{
|
||||
msg.Write(((GUITickBox)uiElements[i]).Selected);
|
||||
}
|
||||
else
|
||||
{
|
||||
msg.Write(extraData != null && extraData.Any(d => d as CustomInterfaceElement == customInterfaceElementList[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
|
||||
{
|
||||
for (int i = 0; i < customInterfaceElementList.Count; i++)
|
||||
{
|
||||
bool elementState = msg.ReadBoolean();
|
||||
if (customInterfaceElementList[i].ContinuousSignal)
|
||||
{
|
||||
((GUITickBox)uiElements[i]).Selected = elementState;
|
||||
TickBoxToggled(customInterfaceElementList[i], elementState);
|
||||
}
|
||||
else if (elementState)
|
||||
{
|
||||
ButtonClicked(customInterfaceElementList[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void RemoveComponentSpecific()
|
||||
{
|
||||
GameMain.Instance.OnResolutionChanged -= RecreateGUI;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class MotionSensor : IDrawableComponent
|
||||
{
|
||||
public Vector2 DrawSize
|
||||
{
|
||||
get { return new Vector2(rangeX, rangeY) * 2.0f; }
|
||||
}
|
||||
|
||||
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1)
|
||||
{
|
||||
if (!editing || !MapEntity.SelectedList.Contains(item)) return;
|
||||
|
||||
Vector2 pos = item.WorldPosition + detectOffset;
|
||||
pos.Y = -pos.Y;
|
||||
GUI.DrawRectangle(spriteBatch, pos - new Vector2(rangeX, rangeY), new Vector2(rangeX, rangeY) * 2.0f, Color.Cyan * 0.5f, isFilled: false, thickness: 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
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 Terminal : ItemComponent, IClientSerializable, IServerSerializable
|
||||
{
|
||||
private GUIListBox historyBox;
|
||||
private GUITextBlock fillerBlock;
|
||||
private GUITextBox inputBox;
|
||||
private bool shouldSelectInputBox;
|
||||
|
||||
partial void InitProjSpecific(XElement element)
|
||||
{
|
||||
var layoutGroup = new GUILayoutGroup(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center) { AbsoluteOffset = GUIStyle.ItemFrameOffset })
|
||||
{
|
||||
ChildAnchor = Anchor.TopCenter,
|
||||
RelativeSpacing = 0.02f,
|
||||
Stretch = true
|
||||
};
|
||||
|
||||
historyBox = new GUIListBox(new RectTransform(new Vector2(1, .9f), layoutGroup.RectTransform), style: null)
|
||||
{
|
||||
AutoHideScrollBar = false
|
||||
};
|
||||
|
||||
// Create fillerBlock to cover historyBox so new values appear at the bottom of historyBox
|
||||
// This could be removed if GUIListBox supported aligning its children
|
||||
fillerBlock = new GUITextBlock(new RectTransform(new Vector2(1, 1), historyBox.Content.RectTransform, anchor: Anchor.TopCenter), string.Empty)
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
|
||||
new GUIFrame(new RectTransform(new Vector2(0.9f, 0.01f), layoutGroup.RectTransform), style: "HorizontalLine");
|
||||
|
||||
inputBox = new GUITextBox(new RectTransform(new Vector2(1, .1f), layoutGroup.RectTransform), textColor: Color.LimeGreen)
|
||||
{
|
||||
MaxTextLength = MaxMessageLength,
|
||||
OverflowClip = true,
|
||||
OnEnterPressed = (GUITextBox textBox, string text) =>
|
||||
{
|
||||
if (GameMain.NetworkMember == null)
|
||||
{
|
||||
SendOutput(text);
|
||||
}
|
||||
else
|
||||
{
|
||||
item.CreateClientEvent(this, new object[] { text });
|
||||
}
|
||||
textBox.Text = string.Empty;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public override void OnItemLoaded()
|
||||
{
|
||||
base.OnItemLoaded();
|
||||
if (!string.IsNullOrEmpty(DisplayedWelcomeMessage))
|
||||
{
|
||||
ShowOnDisplay(DisplayedWelcomeMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private void SendOutput(string input)
|
||||
{
|
||||
if (input.Length > MaxMessageLength)
|
||||
{
|
||||
input = input.Substring(0, MaxMessageLength);
|
||||
}
|
||||
|
||||
OutputValue = input;
|
||||
item.SendSignal(0, input, "signal_out", null);
|
||||
ShowOnDisplay(input);
|
||||
}
|
||||
|
||||
partial void ShowOnDisplay(string input)
|
||||
{
|
||||
while (historyBox.Content.CountChildren > 60)
|
||||
{
|
||||
historyBox.RemoveChild(historyBox.Content.Children.First());
|
||||
}
|
||||
|
||||
GUITextBlock newBlock = new GUITextBlock(
|
||||
new RectTransform(new Vector2(1, 0), historyBox.Content.RectTransform, anchor: Anchor.TopCenter),
|
||||
"> " + input,
|
||||
textColor: Color.LimeGreen, wrap: true)
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
|
||||
if (fillerBlock != null)
|
||||
{
|
||||
float y = fillerBlock.RectTransform.RelativeSize.Y - newBlock.RectTransform.RelativeSize.Y;
|
||||
if (y > 0)
|
||||
{
|
||||
fillerBlock.RectTransform.RelativeSize = new Vector2(1, y);
|
||||
}
|
||||
else
|
||||
{
|
||||
historyBox.RemoveChild(fillerBlock);
|
||||
fillerBlock = null;
|
||||
}
|
||||
}
|
||||
|
||||
historyBox.RecalculateChildren();
|
||||
historyBox.UpdateScrollBarSize();
|
||||
historyBox.ScrollBar.BarScrollValue = 1;
|
||||
}
|
||||
|
||||
public override bool Select(Character character)
|
||||
{
|
||||
shouldSelectInputBox = true;
|
||||
return base.Select(character);
|
||||
}
|
||||
|
||||
// This method is overrided instead of the UpdateHUD method because this ensures the input box is selected
|
||||
// even when the terminal component is selected for the very first time. Doing the input box selection in the
|
||||
// UpdateHUD method only selects the input box on every terminal selection except for the very first time.
|
||||
public override void AddToGUIUpdateList()
|
||||
{
|
||||
base.AddToGUIUpdateList();
|
||||
if (shouldSelectInputBox)
|
||||
{
|
||||
inputBox.Select();
|
||||
shouldSelectInputBox = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void ClientWrite(IWriteMessage msg, object[] extraData = null)
|
||||
{
|
||||
msg.Write((string)extraData[2]);
|
||||
}
|
||||
|
||||
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
|
||||
{
|
||||
SendOutput(msg.ReadString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class WifiComponent : IDrawableComponent
|
||||
{
|
||||
public Vector2 DrawSize
|
||||
{
|
||||
get { return new Vector2(range * 2); }
|
||||
}
|
||||
|
||||
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1)
|
||||
{
|
||||
if (!editing || !MapEntity.SelectedList.Contains(item)) return;
|
||||
|
||||
Vector2 pos = new Vector2(item.DrawPosition.X, -item.DrawPosition.Y);
|
||||
ShapeExtensions.DrawLine(spriteBatch, pos + Vector2.UnitY * range, pos - Vector2.UnitY * range, Color.Cyan * 0.5f, 2);
|
||||
ShapeExtensions.DrawLine(spriteBatch, pos + Vector2.UnitX * range, pos - Vector2.UnitX * range, Color.Cyan * 0.5f, 2);
|
||||
ShapeExtensions.DrawCircle(spriteBatch, pos, range, 32, Color.Cyan * 0.5f, 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,446 @@
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class Wire : ItemComponent, IDrawableComponent, IServerSerializable, IClientSerializable
|
||||
{
|
||||
public static Color higlightColor = Color.LightGreen;
|
||||
public static Color editorHighlightColor = Color.Yellow;
|
||||
public static Color editorSelectedColor = Color.Red;
|
||||
|
||||
partial class WireSection
|
||||
{
|
||||
public void Draw(SpriteBatch spriteBatch, Wire wire, Color color, Vector2 offset, float depth, float width = 0.3f)
|
||||
{
|
||||
spriteBatch.Draw(wire.wireSprite.Texture,
|
||||
new Vector2(start.X + offset.X, -(start.Y + offset.Y)), null, color,
|
||||
-angle,
|
||||
new Vector2(0.0f, wire.wireSprite.size.Y / 2.0f),
|
||||
new Vector2(length / wire.wireSprite.Texture.Width, width),
|
||||
SpriteEffects.None,
|
||||
depth);
|
||||
}
|
||||
|
||||
public static void Draw(SpriteBatch spriteBatch, Wire wire, Vector2 start, Vector2 end, Color color, float depth, float width = 0.3f)
|
||||
{
|
||||
start.Y = -start.Y;
|
||||
end.Y = -end.Y;
|
||||
|
||||
spriteBatch.Draw(wire.wireSprite.Texture,
|
||||
start, null, color,
|
||||
MathUtils.VectorToAngle(end - start),
|
||||
new Vector2(0.0f, wire.wireSprite.size.Y / 2.0f),
|
||||
new Vector2((Vector2.Distance(start, end)) / wire.wireSprite.Texture.Width, width),
|
||||
SpriteEffects.None,
|
||||
depth);
|
||||
}
|
||||
}
|
||||
private static Sprite defaultWireSprite;
|
||||
private Sprite overrideSprite;
|
||||
private Sprite wireSprite;
|
||||
|
||||
private static Wire draggingWire;
|
||||
private static int? selectedNodeIndex;
|
||||
private static int? highlightedNodeIndex;
|
||||
|
||||
public Vector2 DrawSize
|
||||
{
|
||||
get { return sectionExtents; }
|
||||
}
|
||||
|
||||
public static Wire DraggingWire
|
||||
{
|
||||
get => draggingWire;
|
||||
}
|
||||
|
||||
partial void InitProjSpecific(XElement element)
|
||||
{
|
||||
if (defaultWireSprite == null)
|
||||
{
|
||||
defaultWireSprite = new Sprite("Content/Items/wireHorizontal.png", new Vector2(0.5f, 0.5f))
|
||||
{
|
||||
Depth = 0.85f
|
||||
};
|
||||
}
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
if (subElement.Name.ToString().Equals("wiresprite", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
overrideSprite = new Sprite(subElement);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
wireSprite = overrideSprite ?? defaultWireSprite;
|
||||
}
|
||||
|
||||
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1)
|
||||
{
|
||||
if (sections.Count == 0 && !IsActive || Hidden)
|
||||
{
|
||||
Drawable = false;
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2 drawOffset = Vector2.Zero;
|
||||
Submarine sub = item.Submarine;
|
||||
if (IsActive && sub == null) // currently being rewired, we need to get the sub from the connections in case the wire has been taken outside
|
||||
{
|
||||
if (connections[0] != null && connections[0].Item.Submarine != null) { sub = connections[0].Item.Submarine; }
|
||||
if (connections[1] != null && connections[1].Item.Submarine != null) { sub = connections[1].Item.Submarine; }
|
||||
}
|
||||
|
||||
if (sub != null)
|
||||
{
|
||||
drawOffset = sub.DrawPosition + sub.HiddenSubPosition;
|
||||
}
|
||||
|
||||
float depth = item.IsSelected ? 0.0f : wireSprite.Depth + ((item.ID % 100) * 0.00001f);
|
||||
|
||||
if (item.IsHighlighted)
|
||||
{
|
||||
foreach (WireSection section in sections)
|
||||
{
|
||||
section.Draw(spriteBatch, this, Screen.Selected == GameMain.GameScreen ? higlightColor : editorHighlightColor, drawOffset, depth + 0.00001f, 0.7f);
|
||||
}
|
||||
}
|
||||
else if (item.IsSelected)
|
||||
{
|
||||
foreach (WireSection section in sections)
|
||||
{
|
||||
section.Draw(spriteBatch, this, editorSelectedColor, drawOffset, depth + 0.00001f, 0.7f);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (WireSection section in sections)
|
||||
{
|
||||
section.Draw(spriteBatch, this, item.Color, drawOffset, depth, 0.3f);
|
||||
}
|
||||
|
||||
if (nodes.Count > 0)
|
||||
{
|
||||
if (!IsActive)
|
||||
{
|
||||
if (connections[0] == null) { DrawHangingWire(spriteBatch, nodes[0] + drawOffset, depth); }
|
||||
if (connections[1] == null) { DrawHangingWire(spriteBatch, nodes.Last() + drawOffset, depth); }
|
||||
}
|
||||
if (IsActive && item.ParentInventory?.Owner is Character user && user == Character.Controlled)// && Vector2.Distance(newNodePos, nodes[nodes.Count - 1]) > nodeDistance)
|
||||
{
|
||||
if (user.CanInteract)
|
||||
{
|
||||
Vector2 gridPos = Character.Controlled.Position;
|
||||
Vector2 roundedGridPos = new Vector2(
|
||||
MathUtils.RoundTowardsClosest(Character.Controlled.Position.X, Submarine.GridSize.X),
|
||||
MathUtils.RoundTowardsClosest(Character.Controlled.Position.Y, Submarine.GridSize.Y));
|
||||
//Vector2 attachPos = GetAttachPosition(user);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
gridPos += item.Submarine.Position;
|
||||
roundedGridPos += item.Submarine.Position;
|
||||
}
|
||||
|
||||
Submarine.DrawGrid(spriteBatch, 14, gridPos, roundedGridPos, alpha: 0.7f);
|
||||
|
||||
WireSection.Draw(
|
||||
spriteBatch, this,
|
||||
new Vector2(nodes[nodes.Count - 1].X, nodes[nodes.Count - 1].Y) + drawOffset,
|
||||
new Vector2(newNodePos.X, newNodePos.Y) + drawOffset,
|
||||
item.Color, 0.0f, 0.3f);
|
||||
|
||||
WireSection.Draw(
|
||||
spriteBatch, this,
|
||||
new Vector2(newNodePos.X, newNodePos.Y) + drawOffset,
|
||||
item.DrawPosition,
|
||||
item.Color, itemDepth, 0.3f);
|
||||
|
||||
GUI.DrawRectangle(spriteBatch, new Vector2(newNodePos.X + drawOffset.X, -(newNodePos.Y + drawOffset.Y)) - Vector2.One * 3, Vector2.One * 6, item.Color);
|
||||
}
|
||||
else
|
||||
{
|
||||
WireSection.Draw(
|
||||
spriteBatch, this,
|
||||
new Vector2(nodes[nodes.Count - 1].X, nodes[nodes.Count - 1].Y) + drawOffset,
|
||||
item.DrawPosition,
|
||||
item.Color, 0.0f, 0.3f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!editing || !GameMain.SubEditorScreen.WiringMode) { return; }
|
||||
|
||||
for (int i = 0; i < nodes.Count; i++)
|
||||
{
|
||||
Vector2 drawPos = nodes[i];
|
||||
if (item.Submarine != null) drawPos += item.Submarine.Position + item.Submarine.HiddenSubPosition;
|
||||
drawPos.Y = -drawPos.Y;
|
||||
|
||||
if ((highlightedNodeIndex == i && item.IsHighlighted) || (selectedNodeIndex == i && item.IsSelected))
|
||||
{
|
||||
GUI.DrawRectangle(spriteBatch, drawPos + new Vector2(-10, -10), new Vector2(20, 20), editorHighlightColor, false, 0.0f);
|
||||
}
|
||||
|
||||
if (item.IsSelected)
|
||||
{
|
||||
GUI.DrawRectangle(spriteBatch, drawPos + new Vector2(-5, -5), new Vector2(10, 10), item.Color, true, 0.0f);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
GUI.DrawRectangle(spriteBatch, drawPos + new Vector2(-3, -3), new Vector2(6, 6), item.Color, true, 0.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawHangingWire(SpriteBatch spriteBatch, Vector2 start, float depth)
|
||||
{
|
||||
float angle = (float)Math.Sin(GameMain.GameScreen.GameTime * 2.0f + item.ID) * 0.2f;
|
||||
Vector2 endPos = start + new Vector2((float)Math.Sin(angle), -(float)Math.Cos(angle)) * 50.0f;
|
||||
|
||||
WireSection.Draw(
|
||||
spriteBatch, this,
|
||||
start, endPos,
|
||||
GUI.Style.Orange, depth + 0.00001f, 0.2f);
|
||||
|
||||
WireSection.Draw(
|
||||
spriteBatch, this,
|
||||
start, start + (endPos - start) * 0.7f,
|
||||
item.Color, depth, 0.3f);
|
||||
}
|
||||
|
||||
|
||||
public static void UpdateEditing(List<Wire> wires)
|
||||
{
|
||||
Wire equippedWire =
|
||||
Character.Controlled?.SelectedItems[0]?.GetComponent<Wire>() ??
|
||||
Character.Controlled?.SelectedItems[1]?.GetComponent<Wire>();
|
||||
if (equippedWire != null)
|
||||
{
|
||||
if (PlayerInput.PrimaryMouseButtonClicked() && Character.Controlled.SelectedConstruction == null)
|
||||
{
|
||||
equippedWire.Use(1.0f, Character.Controlled);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//dragging a node of some wire
|
||||
if (draggingWire != null)
|
||||
{
|
||||
if (Character.Controlled != null)
|
||||
{
|
||||
Character.Controlled.FocusedItem = null;
|
||||
Character.Controlled.ResetInteract = true;
|
||||
Character.Controlled.ClearInputs();
|
||||
}
|
||||
//cancel dragging
|
||||
if (!PlayerInput.PrimaryMouseButtonHeld())
|
||||
{
|
||||
draggingWire = null;
|
||||
selectedNodeIndex = null;
|
||||
}
|
||||
//update dragging
|
||||
else
|
||||
{
|
||||
MapEntity.DisableSelect = true;
|
||||
|
||||
Submarine sub = draggingWire.item.Submarine;
|
||||
if (draggingWire.connections[0] != null && draggingWire.connections[0].Item.Submarine != null) sub = draggingWire.connections[0].Item.Submarine;
|
||||
if (draggingWire.connections[1] != null && draggingWire.connections[1].Item.Submarine != null) sub = draggingWire.connections[1].Item.Submarine;
|
||||
|
||||
Vector2 nodeWorldPos = GameMain.SubEditorScreen.Cam.ScreenToWorld(PlayerInput.MousePosition);
|
||||
if (sub != null)
|
||||
{
|
||||
nodeWorldPos = nodeWorldPos - sub.HiddenSubPosition - sub.Position;
|
||||
}
|
||||
|
||||
if (selectedNodeIndex.HasValue)
|
||||
{
|
||||
nodeWorldPos.X = MathUtils.Round(nodeWorldPos.X, Submarine.GridSize.X / 2.0f);
|
||||
nodeWorldPos.Y = MathUtils.Round(nodeWorldPos.Y, Submarine.GridSize.Y / 2.0f);
|
||||
|
||||
draggingWire.nodes[(int)selectedNodeIndex] = nodeWorldPos;
|
||||
draggingWire.UpdateSections();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Vector2.DistanceSquared(nodeWorldPos, draggingWire.nodes[(int)highlightedNodeIndex]) > Submarine.GridSize.X * Submarine.GridSize.X)
|
||||
{
|
||||
selectedNodeIndex = highlightedNodeIndex;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
MapEntity.SelectEntity(draggingWire.item);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//a wire has been selected -> check if we should start dragging one of the nodes
|
||||
float nodeSelectDist = 10, sectionSelectDist = 5;
|
||||
highlightedNodeIndex = null;
|
||||
if (MapEntity.SelectedList.Count == 1 && MapEntity.SelectedList[0] is Item)
|
||||
{
|
||||
Wire selectedWire = ((Item)MapEntity.SelectedList[0]).GetComponent<Wire>();
|
||||
|
||||
if (selectedWire != null)
|
||||
{
|
||||
Vector2 mousePos = GameMain.SubEditorScreen.Cam.ScreenToWorld(PlayerInput.MousePosition);
|
||||
if (selectedWire.item.Submarine != null) mousePos -= (selectedWire.item.Submarine.Position + selectedWire.item.Submarine.HiddenSubPosition);
|
||||
|
||||
//left click while holding ctrl -> check if the cursor is on a wire section,
|
||||
//and add a new node if it is
|
||||
if (PlayerInput.KeyDown(Keys.RightControl) || PlayerInput.KeyDown(Keys.LeftControl))
|
||||
{
|
||||
if (PlayerInput.PrimaryMouseButtonClicked())
|
||||
{
|
||||
if (Character.Controlled != null)
|
||||
{
|
||||
Character.Controlled.ResetInteract = true;
|
||||
Character.Controlled.ClearInputs();
|
||||
}
|
||||
int closestSectionIndex = selectedWire.GetClosestSectionIndex(mousePos, sectionSelectDist, out _);
|
||||
if (closestSectionIndex > -1)
|
||||
{
|
||||
selectedWire.nodes.Insert(closestSectionIndex + 1, mousePos);
|
||||
selectedWire.UpdateSections();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//check if close enough to a node
|
||||
int closestIndex = selectedWire.GetClosestNodeIndex(mousePos, nodeSelectDist, out _);
|
||||
if (closestIndex > -1)
|
||||
{
|
||||
highlightedNodeIndex = closestIndex;
|
||||
//start dragging the node
|
||||
if (PlayerInput.PrimaryMouseButtonHeld())
|
||||
{
|
||||
if (Character.Controlled != null)
|
||||
{
|
||||
Character.Controlled.ResetInteract = true;
|
||||
Character.Controlled.ClearInputs();
|
||||
}
|
||||
draggingWire = selectedWire;
|
||||
//selectedNodeIndex = closestIndex;
|
||||
return;
|
||||
}
|
||||
//remove the node
|
||||
else if (PlayerInput.SecondaryMouseButtonClicked() && closestIndex > 0 && closestIndex < selectedWire.nodes.Count - 1)
|
||||
{
|
||||
selectedWire.nodes.RemoveAt(closestIndex);
|
||||
selectedWire.UpdateSections();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Wire highlighted = null;
|
||||
|
||||
//check which wire is highlighted with the cursor
|
||||
if (GUI.MouseOn == null)
|
||||
{
|
||||
float closestDist = float.PositiveInfinity;
|
||||
foreach (Wire w in wires)
|
||||
{
|
||||
Vector2 mousePos = GameMain.SubEditorScreen.Cam.ScreenToWorld(PlayerInput.MousePosition);
|
||||
if (w.item.Submarine != null) { mousePos -= (w.item.Submarine.Position + w.item.Submarine.HiddenSubPosition); }
|
||||
|
||||
int highlightedNode = w.GetClosestNodeIndex(mousePos, highlighted == null ? nodeSelectDist : closestDist, out float dist);
|
||||
if (highlightedNode > -1)
|
||||
{
|
||||
if (dist < closestDist)
|
||||
{
|
||||
highlightedNodeIndex = highlightedNode;
|
||||
highlighted = w;
|
||||
closestDist = dist;
|
||||
}
|
||||
}
|
||||
|
||||
if (w.GetClosestSectionIndex(mousePos, highlighted == null ? sectionSelectDist : closestDist, out dist) > -1)
|
||||
{
|
||||
//prefer nodes over sections
|
||||
if (dist + nodeSelectDist * 0.5f < closestDist)
|
||||
{
|
||||
highlightedNodeIndex = null;
|
||||
highlighted = w;
|
||||
closestDist = dist + nodeSelectDist * 0.5f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (highlighted != null)
|
||||
{
|
||||
highlighted.item.IsHighlighted = true;
|
||||
if (PlayerInput.PrimaryMouseButtonClicked())
|
||||
{
|
||||
MapEntity.DisableSelect = true;
|
||||
MapEntity.SelectEntity(highlighted.item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
|
||||
{
|
||||
int eventIndex = msg.ReadRangedInteger(0, (int)Math.Ceiling(MaxNodeCount / (float)MaxNodesPerNetworkEvent));
|
||||
int nodeCount = msg.ReadRangedInteger(0, MaxNodesPerNetworkEvent);
|
||||
int nodeStartIndex = eventIndex * MaxNodesPerNetworkEvent;
|
||||
|
||||
Vector2[] nodePositions = new Vector2[nodeStartIndex + nodeCount];
|
||||
for (int i = 0; i < nodes.Count && i < nodePositions.Length; i++)
|
||||
{
|
||||
nodePositions[i] = nodes[i];
|
||||
}
|
||||
|
||||
for (int i = 0; i < nodeCount; i++)
|
||||
{
|
||||
nodePositions[nodeStartIndex + i] = new Vector2(msg.ReadSingle(), msg.ReadSingle());
|
||||
}
|
||||
|
||||
if (nodePositions.Any(n => !MathUtils.IsValid(n)))
|
||||
{
|
||||
nodes.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
nodes = nodePositions.ToList();
|
||||
UpdateSections();
|
||||
Drawable = nodes.Any();
|
||||
}
|
||||
|
||||
public void ClientWrite(IWriteMessage msg, object[] extraData = null)
|
||||
{
|
||||
int nodeCount = (int)extraData[2];
|
||||
msg.Write((byte)nodeCount);
|
||||
if (nodeCount > 0)
|
||||
{
|
||||
msg.Write(nodes.Last().X);
|
||||
msg.Write(nodes.Last().Y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class StatusHUD : ItemComponent
|
||||
{
|
||||
private static readonly string[] BleedingTexts =
|
||||
{
|
||||
TextManager.Get("MinorBleeding"),
|
||||
TextManager.Get("Bleeding"),
|
||||
TextManager.Get("HeavyBleeding"),
|
||||
TextManager.Get("CatastrophicBleeding")
|
||||
};
|
||||
|
||||
private static readonly string[] HealthTexts =
|
||||
{
|
||||
TextManager.Get("NoInjuries"),
|
||||
TextManager.Get("MinorInjuries"),
|
||||
TextManager.Get("Injuries"),
|
||||
TextManager.Get("MajorInjuries"),
|
||||
TextManager.Get("CriticalInjuries")
|
||||
};
|
||||
|
||||
private static readonly string[] OxygenTexts =
|
||||
{
|
||||
TextManager.Get("OxygenNormal"),
|
||||
TextManager.Get("OxygenReduced"),
|
||||
TextManager.Get("OxygenLow"),
|
||||
TextManager.Get("NotBreathing")
|
||||
};
|
||||
|
||||
[Serialize(500.0f, false, description: "How close to a target the user must be to see their health data (in pixels).")]
|
||||
public float Range
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(50.0f, false, description: "The range within which the health info texts fades out.")]
|
||||
public float FadeOutRange
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
private readonly List<Character> visibleCharacters = new List<Character>();
|
||||
|
||||
private const float UpdateInterval = 0.5f;
|
||||
private float updateTimer;
|
||||
|
||||
private Character equipper;
|
||||
|
||||
public IEnumerable<Character> VisibleCharacters
|
||||
{
|
||||
get
|
||||
{
|
||||
if (equipper == null || equipper.Removed) { return Enumerable.Empty<Character>(); }
|
||||
return visibleCharacters;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
base.Update(deltaTime, cam);
|
||||
|
||||
if (equipper == null || equipper.Removed)
|
||||
{
|
||||
IsActive = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (updateTimer > 0.0f)
|
||||
{
|
||||
updateTimer -= deltaTime;
|
||||
return;
|
||||
}
|
||||
|
||||
visibleCharacters.Clear();
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (c == equipper || !c.Enabled || c.Removed) { continue; }
|
||||
|
||||
float dist = Vector2.DistanceSquared(equipper.WorldPosition, c.WorldPosition);
|
||||
if (dist < Range * Range)
|
||||
{
|
||||
Vector2 diff = c.WorldPosition - equipper.WorldPosition;
|
||||
if (Submarine.CheckVisibility(equipper.SimPosition, equipper.SimPosition + ConvertUnits.ToSimUnits(diff)) == null)
|
||||
{
|
||||
visibleCharacters.Add(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateTimer = UpdateInterval;
|
||||
}
|
||||
|
||||
public override void Equip(Character character)
|
||||
{
|
||||
updateTimer = 0.0f;
|
||||
equipper = character;
|
||||
IsActive = true;
|
||||
}
|
||||
|
||||
public override void Unequip(Character character)
|
||||
{
|
||||
equipper = null;
|
||||
IsActive = false;
|
||||
}
|
||||
|
||||
public override void DrawHUD(SpriteBatch spriteBatch, Character character)
|
||||
{
|
||||
if (character == null) { return; }
|
||||
|
||||
GUI.UIGlow.Draw(spriteBatch, new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight),
|
||||
Color.LightGreen * 0.5f);
|
||||
|
||||
Character closestCharacter = null;
|
||||
float closestDist = float.PositiveInfinity;
|
||||
foreach (Character c in visibleCharacters)
|
||||
{
|
||||
if (c == character || !c.Enabled || c.Removed) { continue; }
|
||||
|
||||
float dist = Vector2.DistanceSquared(GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition), c.WorldPosition);
|
||||
if (dist < closestDist)
|
||||
{
|
||||
closestCharacter = c;
|
||||
closestDist = dist;
|
||||
}
|
||||
}
|
||||
|
||||
if (closestCharacter != null)
|
||||
{
|
||||
float dist = Vector2.Distance(GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition), closestCharacter.WorldPosition);
|
||||
DrawCharacterInfo(spriteBatch, closestCharacter, 1.0f - MathHelper.Max((dist - (Range - FadeOutRange)) / FadeOutRange, 0.0f));
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawCharacterInfo(SpriteBatch spriteBatch, Character target, float alpha = 1.0f)
|
||||
{
|
||||
Vector2 hudPos = GameMain.GameScreen.Cam.WorldToScreen(target.DrawPosition);
|
||||
hudPos += Vector2.UnitX * 50.0f;
|
||||
|
||||
List<string> texts = new List<string>();
|
||||
List<Color> textColors = new List<Color>();
|
||||
|
||||
if (target.Info != null)
|
||||
{
|
||||
texts.Add(target.Name);
|
||||
textColors.Add(GUI.Style.TextColor);
|
||||
}
|
||||
|
||||
if (target.IsDead)
|
||||
{
|
||||
texts.Add(TextManager.Get("Deceased"));
|
||||
textColors.Add(GUI.Style.Red);
|
||||
texts.Add(
|
||||
target.CauseOfDeath.Affliction?.CauseOfDeathDescription ??
|
||||
TextManager.AddPunctuation(':', TextManager.Get("CauseOfDeath"), TextManager.Get("CauseOfDeath." + target.CauseOfDeath.Type.ToString())));
|
||||
textColors.Add(GUI.Style.Red);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (target.IsUnconscious)
|
||||
{
|
||||
texts.Add(TextManager.Get("Unconscious"));
|
||||
textColors.Add(GUI.Style.Orange);
|
||||
}
|
||||
if (target.Stun > 0.01f)
|
||||
{
|
||||
texts.Add(TextManager.Get("Stunned"));
|
||||
textColors.Add(GUI.Style.Orange);
|
||||
}
|
||||
|
||||
int oxygenTextIndex = MathHelper.Clamp((int)Math.Floor((1.0f - (target.Oxygen / 100.0f)) * OxygenTexts.Length), 0, OxygenTexts.Length - 1);
|
||||
texts.Add(OxygenTexts[oxygenTextIndex]);
|
||||
textColors.Add(Color.Lerp(GUI.Style.Red, GUI.Style.Green, target.Oxygen / 100.0f));
|
||||
|
||||
if (target.Bleeding > 0.0f)
|
||||
{
|
||||
int bleedingTextIndex = MathHelper.Clamp((int)Math.Floor(target.Bleeding / 100.0f) * BleedingTexts.Length, 0, BleedingTexts.Length - 1);
|
||||
texts.Add(BleedingTexts[bleedingTextIndex]);
|
||||
textColors.Add(Color.Lerp(GUI.Style.Orange, GUI.Style.Red, target.Bleeding / 100.0f));
|
||||
}
|
||||
|
||||
var allAfflictions = target.CharacterHealth.GetAllAfflictions();
|
||||
Dictionary<AfflictionPrefab, float> combinedAfflictionStrengths = new Dictionary<AfflictionPrefab, float>();
|
||||
foreach (Affliction affliction in allAfflictions)
|
||||
{
|
||||
if (affliction.Strength < affliction.Prefab.ShowInHealthScannerThreshold || affliction.Strength <= 0.0f) continue;
|
||||
if (combinedAfflictionStrengths.ContainsKey(affliction.Prefab))
|
||||
{
|
||||
combinedAfflictionStrengths[affliction.Prefab] += affliction.Strength;
|
||||
}
|
||||
else
|
||||
{
|
||||
combinedAfflictionStrengths[affliction.Prefab] = affliction.Strength;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (AfflictionPrefab affliction in combinedAfflictionStrengths.Keys)
|
||||
{
|
||||
texts.Add(TextManager.AddPunctuation(':', affliction.Name, ((int)combinedAfflictionStrengths[affliction]).ToString() + " %"));
|
||||
textColors.Add(Color.Lerp(GUI.Style.Orange, GUI.Style.Red, combinedAfflictionStrengths[affliction] / affliction.MaxStrength));
|
||||
}
|
||||
}
|
||||
|
||||
GUI.DrawString(spriteBatch, hudPos, texts[0], textColors[0] * alpha, Color.Black * 0.7f * alpha, 2, GUI.SubHeadingFont);
|
||||
hudPos.X += 5.0f;
|
||||
hudPos.Y += 24.0f;
|
||||
|
||||
hudPos.X = (int)hudPos.X;
|
||||
hudPos.Y = (int)hudPos.Y;
|
||||
|
||||
for (int i = 1; i < texts.Count; i++)
|
||||
{
|
||||
GUI.DrawString(spriteBatch, hudPos, texts[i], textColors[i] * alpha, Color.Black * 0.7f * alpha, 2, GUI.SmallFont);
|
||||
hudPos.Y += 18.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,504 @@
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.Particles;
|
||||
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
|
||||
{
|
||||
partial class Turret : Powered, IDrawableComponent, IServerSerializable
|
||||
{
|
||||
private Sprite crosshairSprite, crosshairPointerSprite;
|
||||
|
||||
private GUIProgressBar powerIndicator;
|
||||
|
||||
private float recoilTimer;
|
||||
|
||||
private RoundSound startMoveSound, endMoveSound, moveSound;
|
||||
|
||||
private SoundChannel moveSoundChannel;
|
||||
|
||||
private Vector2 crosshairPos, crosshairPointerPos;
|
||||
|
||||
private readonly Dictionary<string, Widget> widgets = new Dictionary<string, Widget>();
|
||||
private float prevAngle;
|
||||
|
||||
private bool flashLowPower;
|
||||
private bool flashNoAmmo;
|
||||
private float flashTimer;
|
||||
private float flashLength = 1;
|
||||
|
||||
private readonly List<ParticleEmitter> particleEmitters = new List<ParticleEmitter>();
|
||||
|
||||
[Editable, Serialize("0,0,0,0", true, description: "Optional screen tint color when the item is being operated (R,G,B,A).")]
|
||||
public Color HudTint
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(false, false, description: "Should the charge of the connected batteries/supercapacitors be shown at the top of the screen when operating the item.")]
|
||||
public bool ShowChargeIndicator
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(false, false, description: "Should the available ammunition be shown at the top of the screen when operating the item.")]
|
||||
public bool ShowProjectileIndicator
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(0.0f, false, description: "How far the barrel \"recoils back\" when the turret is fired (in pixels).")]
|
||||
public float RecoilDistance
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public Vector2 DrawSize
|
||||
{
|
||||
get
|
||||
{
|
||||
float size = Math.Max(transformedBarrelPos.X, transformedBarrelPos.Y);
|
||||
if (barrelSprite != null)
|
||||
{
|
||||
if (railSprite != null)
|
||||
{
|
||||
size += Math.Max(Math.Max(barrelSprite.size.X, barrelSprite.size.Y), Math.Max(railSprite.size.X, railSprite.size.Y)) * item.Scale;
|
||||
}
|
||||
else
|
||||
{
|
||||
size += Math.Max(barrelSprite.size.X, barrelSprite.size.Y) * item.Scale;
|
||||
}
|
||||
}
|
||||
return Vector2.One * size * 2;
|
||||
}
|
||||
}
|
||||
|
||||
partial void InitProjSpecific(XElement element)
|
||||
{
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
string texturePath = subElement.GetAttributeString("texture", "");
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "crosshair":
|
||||
crosshairSprite = new Sprite(subElement, texturePath.Contains("/") ? "" : Path.GetDirectoryName(item.Prefab.FilePath));
|
||||
break;
|
||||
case "crosshairpointer":
|
||||
crosshairPointerSprite = new Sprite(subElement, texturePath.Contains("/") ? "" : Path.GetDirectoryName(item.Prefab.FilePath));
|
||||
break;
|
||||
case "startmovesound":
|
||||
startMoveSound = Submarine.LoadRoundSound(subElement, false);
|
||||
break;
|
||||
case "endmovesound":
|
||||
endMoveSound = Submarine.LoadRoundSound(subElement, false);
|
||||
break;
|
||||
case "movesound":
|
||||
moveSound = Submarine.LoadRoundSound(subElement, false);
|
||||
break;
|
||||
case "particleemitter":
|
||||
particleEmitters.Add(new ParticleEmitter(subElement));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
powerIndicator = new GUIProgressBar(new RectTransform(new Vector2(0.18f, 0.03f), GUI.Canvas, Anchor.TopCenter)
|
||||
{
|
||||
MinSize = new Point(100,20),
|
||||
RelativeOffset = new Vector2(0.0f, 0.01f)
|
||||
},
|
||||
barSize: 0.0f, style: "DeviceProgressBar");
|
||||
}
|
||||
|
||||
public override void Move(Vector2 amount)
|
||||
{
|
||||
widgets.Clear();
|
||||
}
|
||||
|
||||
partial void LaunchProjSpecific()
|
||||
{
|
||||
recoilTimer = Math.Max(Reload, 0.1f);
|
||||
PlaySound(ActionType.OnUse);
|
||||
Vector2 particlePos = new Vector2(item.WorldRect.X + transformedBarrelPos.X, item.WorldRect.Y - transformedBarrelPos.Y);
|
||||
foreach (ParticleEmitter emitter in particleEmitters)
|
||||
{
|
||||
emitter.Emit(1.0f, particlePos, hullGuess: null, angle: -rotation, particleRotation: rotation);
|
||||
}
|
||||
}
|
||||
|
||||
partial void UpdateProjSpecific(float deltaTime)
|
||||
{
|
||||
recoilTimer -= deltaTime;
|
||||
|
||||
if (crosshairSprite != null)
|
||||
{
|
||||
Vector2 itemPos = cam.WorldToScreen(new Vector2(item.WorldRect.X + transformedBarrelPos.X, item.WorldRect.Y - transformedBarrelPos.Y));
|
||||
Vector2 turretDir = new Vector2((float)Math.Cos(rotation), (float)Math.Sin(rotation));
|
||||
|
||||
Vector2 mouseDiff = itemPos - PlayerInput.MousePosition;
|
||||
crosshairPos = new Vector2(
|
||||
MathHelper.Clamp(itemPos.X + turretDir.X * mouseDiff.Length(), 0, GameMain.GraphicsWidth),
|
||||
MathHelper.Clamp(itemPos.Y + turretDir.Y * mouseDiff.Length(), 0, GameMain.GraphicsHeight));
|
||||
}
|
||||
|
||||
crosshairPointerPos = PlayerInput.MousePosition;
|
||||
|
||||
if (Math.Abs(angularVelocity) > 0.1f)
|
||||
{
|
||||
if (moveSoundChannel == null && startMoveSound != null)
|
||||
{
|
||||
moveSoundChannel = SoundPlayer.PlaySound(startMoveSound.Sound, item.WorldPosition, startMoveSound.Volume, startMoveSound.Range);
|
||||
}
|
||||
else if (moveSoundChannel == null || !moveSoundChannel.IsPlaying)
|
||||
{
|
||||
if (moveSound != null)
|
||||
{
|
||||
moveSoundChannel.FadeOutAndDispose();
|
||||
moveSoundChannel = SoundPlayer.PlaySound(moveSound.Sound, item.WorldPosition, moveSound.Volume, moveSound.Range);
|
||||
if (moveSoundChannel != null) moveSoundChannel.Looping = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (Math.Abs(angularVelocity) < 0.05f)
|
||||
{
|
||||
if (moveSoundChannel != null)
|
||||
{
|
||||
if (endMoveSound != null && moveSoundChannel.Sound != endMoveSound.Sound)
|
||||
{
|
||||
moveSoundChannel.FadeOutAndDispose();
|
||||
moveSoundChannel = SoundPlayer.PlaySound(endMoveSound.Sound, item.WorldPosition, endMoveSound.Volume, endMoveSound.Range);
|
||||
if (moveSoundChannel != null) moveSoundChannel.Looping = false;
|
||||
}
|
||||
else if (!moveSoundChannel.IsPlaying)
|
||||
{
|
||||
moveSoundChannel.FadeOutAndDispose();
|
||||
moveSoundChannel = null;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (moveSoundChannel != null && moveSoundChannel.IsPlaying)
|
||||
{
|
||||
moveSoundChannel.Gain = MathHelper.Clamp(Math.Abs(angularVelocity), 0.5f, 1.0f);
|
||||
}
|
||||
|
||||
if (flashLowPower || flashNoAmmo)
|
||||
{
|
||||
flashTimer += deltaTime;
|
||||
if (flashTimer >= flashLength)
|
||||
{
|
||||
flashTimer = 0;
|
||||
flashLowPower = false;
|
||||
flashNoAmmo = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void UpdateHUD(Character character, float deltaTime, Camera cam)
|
||||
{
|
||||
if (crosshairSprite != null)
|
||||
{
|
||||
Vector2 itemPos = cam.WorldToScreen(item.WorldPosition);
|
||||
Vector2 turretDir = new Vector2((float)Math.Cos(rotation), (float)Math.Sin(rotation));
|
||||
|
||||
Vector2 mouseDiff = itemPos - PlayerInput.MousePosition;
|
||||
crosshairPos = new Vector2(
|
||||
MathHelper.Clamp(itemPos.X + turretDir.X * mouseDiff.Length(), 0, GameMain.GraphicsWidth),
|
||||
MathHelper.Clamp(itemPos.Y + turretDir.Y * mouseDiff.Length(), 0, GameMain.GraphicsHeight));
|
||||
}
|
||||
|
||||
crosshairPointerPos = PlayerInput.MousePosition;
|
||||
}
|
||||
|
||||
public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1)
|
||||
{
|
||||
Vector2 drawPos = new Vector2(item.Rect.X + transformedBarrelPos.X, item.Rect.Y - transformedBarrelPos.Y);
|
||||
if (item.Submarine != null) drawPos += item.Submarine.DrawPosition;
|
||||
drawPos.Y = -drawPos.Y;
|
||||
|
||||
float recoilOffset = 0.0f;
|
||||
if (RecoilDistance > 0.0f && recoilTimer > 0.0f)
|
||||
{
|
||||
//move the barrel backwards 0.1 seconds after launching
|
||||
if (recoilTimer >= Math.Max(Reload, 0.1f) - 0.1f)
|
||||
{
|
||||
recoilOffset = RecoilDistance * (1.0f - (recoilTimer - (Math.Max(Reload, 0.1f) - 0.1f)) / 0.1f);
|
||||
}
|
||||
//move back to normal position while reloading
|
||||
else
|
||||
{
|
||||
recoilOffset = RecoilDistance * recoilTimer / (Math.Max(Reload, 0.1f) - 0.1f);
|
||||
}
|
||||
}
|
||||
|
||||
railSprite?.Draw(spriteBatch,
|
||||
drawPos,
|
||||
item.SpriteColor,
|
||||
rotation + MathHelper.PiOver2, item.Scale,
|
||||
SpriteEffects.None, item.SpriteDepth + (railSprite.Depth - item.Sprite.Depth));
|
||||
|
||||
barrelSprite?.Draw(spriteBatch,
|
||||
drawPos - new Vector2((float)Math.Cos(rotation), (float)Math.Sin(rotation)) * recoilOffset * item.Scale,
|
||||
item.SpriteColor,
|
||||
rotation + MathHelper.PiOver2, item.Scale,
|
||||
SpriteEffects.None, item.SpriteDepth + (barrelSprite.Depth - item.Sprite.Depth));
|
||||
|
||||
if (!editing) { return; }
|
||||
|
||||
float widgetRadius = 60.0f;
|
||||
|
||||
GUI.DrawLine(spriteBatch,
|
||||
drawPos,
|
||||
drawPos + new Vector2((float)Math.Cos(minRotation), (float)Math.Sin(minRotation)) * widgetRadius,
|
||||
GUI.Style.Green);
|
||||
|
||||
GUI.DrawLine(spriteBatch,
|
||||
drawPos,
|
||||
drawPos + new Vector2((float)Math.Cos(maxRotation), (float)Math.Sin(maxRotation)) * widgetRadius,
|
||||
GUI.Style.Green);
|
||||
|
||||
GUI.DrawLine(spriteBatch,
|
||||
drawPos,
|
||||
drawPos + new Vector2((float)Math.Cos((maxRotation + minRotation) / 2), (float)Math.Sin((maxRotation + minRotation) / 2)) * widgetRadius,
|
||||
Color.LightGreen);
|
||||
|
||||
if (!item.IsSelected) { return; }
|
||||
|
||||
Widget minRotationWidget = GetWidget("minrotation", spriteBatch, size: 10, initMethod: (widget) =>
|
||||
{
|
||||
widget.MouseDown += () =>
|
||||
{
|
||||
widget.color = GUI.Style.Green;
|
||||
prevAngle = minRotation;
|
||||
};
|
||||
widget.Deselected += () =>
|
||||
{
|
||||
widget.color = Color.Yellow;
|
||||
item.CreateEditingHUD();
|
||||
};
|
||||
widget.MouseHeld += (deltaTime) =>
|
||||
{
|
||||
minRotation = GetRotationAngle(drawPos);
|
||||
if (minRotation > maxRotation)
|
||||
{
|
||||
float temp = minRotation;
|
||||
minRotation = maxRotation;
|
||||
maxRotation = temp;
|
||||
}
|
||||
MapEntity.DisableSelect = true;
|
||||
};
|
||||
widget.PreUpdate += (deltaTime) =>
|
||||
{
|
||||
widget.DrawPos = new Vector2(widget.DrawPos.X, -widget.DrawPos.Y);
|
||||
widget.DrawPos = Screen.Selected.Cam.WorldToScreen(widget.DrawPos);
|
||||
};
|
||||
widget.PostUpdate += (deltaTime) =>
|
||||
{
|
||||
widget.DrawPos = Screen.Selected.Cam.ScreenToWorld(widget.DrawPos);
|
||||
widget.DrawPos = new Vector2(widget.DrawPos.X, -widget.DrawPos.Y);
|
||||
};
|
||||
widget.PreDraw += (sprtBtch, deltaTime) =>
|
||||
{
|
||||
widget.tooltip = "Min: " + (int)MathHelper.ToDegrees(minRotation);
|
||||
widget.DrawPos = drawPos + new Vector2((float)Math.Cos(minRotation), (float)Math.Sin(minRotation)) * widgetRadius;
|
||||
widget.Update(deltaTime);
|
||||
};
|
||||
});
|
||||
|
||||
Widget maxRotationWidget = GetWidget("maxrotation", spriteBatch, size: 10, initMethod: (widget) =>
|
||||
{
|
||||
widget.MouseDown += () =>
|
||||
{
|
||||
widget.color = GUI.Style.Green;
|
||||
prevAngle = minRotation;
|
||||
};
|
||||
widget.Deselected += () =>
|
||||
{
|
||||
widget.color = Color.Yellow;
|
||||
item.CreateEditingHUD();
|
||||
};
|
||||
widget.MouseHeld += (deltaTime) =>
|
||||
{
|
||||
maxRotation = GetRotationAngle(drawPos);
|
||||
if (minRotation > maxRotation)
|
||||
{
|
||||
float temp = minRotation;
|
||||
minRotation = maxRotation;
|
||||
maxRotation = temp;
|
||||
}
|
||||
MapEntity.DisableSelect = true;
|
||||
};
|
||||
widget.PreUpdate += (deltaTime) =>
|
||||
{
|
||||
widget.DrawPos = new Vector2(widget.DrawPos.X, -widget.DrawPos.Y);
|
||||
widget.DrawPos = Screen.Selected.Cam.WorldToScreen(widget.DrawPos);
|
||||
};
|
||||
widget.PostUpdate += (deltaTime) =>
|
||||
{
|
||||
widget.DrawPos = Screen.Selected.Cam.ScreenToWorld(widget.DrawPos);
|
||||
widget.DrawPos = new Vector2(widget.DrawPos.X, -widget.DrawPos.Y);
|
||||
};
|
||||
widget.PreDraw += (sprtBtch, deltaTime) =>
|
||||
{
|
||||
widget.tooltip = "Max: " + (int)MathHelper.ToDegrees(maxRotation);
|
||||
widget.DrawPos = drawPos + new Vector2((float)Math.Cos(maxRotation), (float)Math.Sin(maxRotation)) * widgetRadius;
|
||||
widget.Update(deltaTime);
|
||||
};
|
||||
});
|
||||
minRotationWidget.Draw(spriteBatch, (float)Timing.Step);
|
||||
maxRotationWidget.Draw(spriteBatch, (float)Timing.Step);
|
||||
}
|
||||
|
||||
private Widget GetWidget(string id, SpriteBatch spriteBatch, int size = 5, Action<Widget> initMethod = null)
|
||||
{
|
||||
if (!widgets.TryGetValue(id, out Widget widget))
|
||||
{
|
||||
widget = new Widget(id, size, Widget.Shape.Rectangle)
|
||||
{
|
||||
color = Color.Yellow,
|
||||
tooltipOffset = new Vector2(size / 2 + 5, -10),
|
||||
inputAreaMargin = 20,
|
||||
RequireMouseOn = false
|
||||
};
|
||||
widgets.Add(id, widget);
|
||||
initMethod?.Invoke(widget);
|
||||
}
|
||||
return widget;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns correct angle between -2PI and +2PI
|
||||
/// </summary>
|
||||
/// <param name="drawPosition"></param>
|
||||
/// <returns></returns>
|
||||
private float GetRotationAngle(Vector2 drawPosition)
|
||||
{
|
||||
Vector2 mouseVector = Screen.Selected.Cam.ScreenToWorld(PlayerInput.MousePosition);
|
||||
mouseVector.Y = -mouseVector.Y;
|
||||
Vector2 rotationVector = mouseVector - drawPosition;
|
||||
rotationVector.Normalize();
|
||||
double angle = Math.Atan2(MathHelper.ToRadians(rotationVector.Y), MathHelper.ToRadians(rotationVector.X));
|
||||
if (angle < 0)
|
||||
{// calculates which coterminal angle is closer to previous angle
|
||||
angle = Math.Abs(angle - prevAngle) < Math.Abs((angle + Math.PI * 2) - prevAngle) ? angle : angle + Math.PI * 2;
|
||||
}
|
||||
else if (angle > 0)
|
||||
{
|
||||
angle = Math.Abs(angle - prevAngle) < Math.Abs((angle - Math.PI * 2) - prevAngle) ? angle : angle - Math.PI * 2;
|
||||
}
|
||||
angle = MathHelper.Clamp((float)angle, -((float)Math.PI * 2), (float)Math.PI * 2);
|
||||
prevAngle = (float)angle;
|
||||
return (float)angle;
|
||||
}
|
||||
|
||||
public override void DrawHUD(SpriteBatch spriteBatch, Character character)
|
||||
{
|
||||
if (HudTint.A > 0)
|
||||
{
|
||||
GUI.DrawRectangle(spriteBatch, new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight),
|
||||
new Color(HudTint.R, HudTint.G, HudTint.B) * (HudTint.A / 255.0f), true);
|
||||
}
|
||||
|
||||
GetAvailablePower(out float batteryCharge, out float batteryCapacity);
|
||||
|
||||
List<Item> availableAmmo = new List<Item>();
|
||||
foreach (MapEntity e in item.linkedTo)
|
||||
{
|
||||
var linkedItem = e as Item;
|
||||
if (linkedItem == null) continue;
|
||||
|
||||
var itemContainer = linkedItem.GetComponent<ItemContainer>();
|
||||
if (itemContainer?.Inventory?.Items == null) continue;
|
||||
|
||||
availableAmmo.AddRange(itemContainer.Inventory.Items);
|
||||
}
|
||||
|
||||
float chargeRate =
|
||||
powerConsumption <= 0.0f ?
|
||||
1.0f :
|
||||
batteryCapacity > 0.0f ? batteryCharge / batteryCapacity : 0.0f;
|
||||
bool charged = batteryCharge * 3600.0f > powerConsumption;
|
||||
bool readyToFire = reload <= 0.0f && charged && availableAmmo.Any(p => p != null);
|
||||
if (ShowChargeIndicator && PowerConsumption > 0.0f)
|
||||
{
|
||||
powerIndicator.Color = charged ? GUI.Style.Green : GUI.Style.Red;
|
||||
if (flashLowPower)
|
||||
{
|
||||
powerIndicator.BarSize = 1;
|
||||
powerIndicator.Color *= (float)Math.Sin(flashTimer * 12);
|
||||
powerIndicator.RectTransform.ChangeScale(Vector2.Lerp(Vector2.One, Vector2.One * 1.01f, 2 * (float)Math.Sin(flashTimer * 15)));
|
||||
}
|
||||
else
|
||||
{
|
||||
powerIndicator.BarSize = chargeRate;
|
||||
}
|
||||
powerIndicator.DrawManually(spriteBatch, true);
|
||||
|
||||
Rectangle sliderRect = powerIndicator.GetSliderRect(1.0f);
|
||||
int requiredChargeIndicatorPos = (int)(powerConsumption / (batteryCapacity * 3600.0f) * sliderRect.Width);
|
||||
GUI.DrawRectangle(spriteBatch,
|
||||
new Rectangle(sliderRect.X + requiredChargeIndicatorPos, sliderRect.Y, 2, sliderRect.Height),
|
||||
Color.White * 0.5f, true);
|
||||
}
|
||||
|
||||
if (ShowProjectileIndicator)
|
||||
{
|
||||
Point slotSize = (Inventory.SlotSpriteSmall.size * Inventory.UIScale).ToPoint();
|
||||
int spacing = 5;
|
||||
int slotsPerRow = Math.Min(availableAmmo.Count, 6);
|
||||
int totalWidth = slotSize.X * slotsPerRow + spacing * (slotsPerRow - 1);
|
||||
Point invSlotPos = new Point(GameMain.GraphicsWidth / 2 - totalWidth / 2, (int)(60 * GUI.Scale));
|
||||
for (int i = 0; i < availableAmmo.Count; i++)
|
||||
{
|
||||
// TODO: Optimize? Creates multiple new objects per frame?
|
||||
Inventory.DrawSlot(spriteBatch, null,
|
||||
new InventorySlot(new Rectangle(invSlotPos + new Point((i % slotsPerRow) * (slotSize.X + spacing), (int)Math.Floor(i / (float)slotsPerRow) * (slotSize.Y + spacing)), slotSize)),
|
||||
availableAmmo[i], -1, true);
|
||||
}
|
||||
if (flashNoAmmo)
|
||||
{
|
||||
Rectangle rect = new Rectangle(invSlotPos.X, invSlotPos.Y, totalWidth, slotSize.Y);
|
||||
float inflate = MathHelper.Lerp(3, 8, (float)Math.Abs(1 * Math.Sin(flashTimer * 5)));
|
||||
rect.Inflate(inflate, inflate);
|
||||
Color color = GUI.Style.Red * MathHelper.Max(0.5f, (float)Math.Sin(flashTimer * 12));
|
||||
GUI.DrawRectangle(spriteBatch, rect, color, thickness: 3);
|
||||
}
|
||||
}
|
||||
|
||||
float zoom = cam == null ? 1.0f : (float)Math.Sqrt(cam.Zoom);
|
||||
|
||||
GUI.HideCursor = (crosshairSprite != null || crosshairPointerSprite != null) && GUI.MouseOn == null && !GameMain.Instance.Paused;
|
||||
if (GUI.HideCursor)
|
||||
{
|
||||
crosshairSprite?.Draw(spriteBatch, crosshairPos, readyToFire ? Color.White : Color.White * 0.2f, 0, zoom);
|
||||
crosshairPointerSprite?.Draw(spriteBatch, crosshairPointerPos, 0, zoom);
|
||||
}
|
||||
}
|
||||
|
||||
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
|
||||
{
|
||||
UInt16 projectileID = msg.ReadUInt16();
|
||||
//projectile removed, do nothing
|
||||
if (projectileID == 0) return;
|
||||
|
||||
Item projectile = Entity.FindEntityByID(projectileID) as Item;
|
||||
if (projectile == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to launch a projectile - item with the ID \"" + projectileID + " not found");
|
||||
return;
|
||||
}
|
||||
|
||||
Launch(projectile);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user