Merge branch 'dev'

# Conflicts:
#	Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs
#	Barotrauma/BarotraumaClient/LinuxClient.csproj
#	Barotrauma/BarotraumaClient/MacClient.csproj
#	Barotrauma/BarotraumaClient/WindowsClient.csproj
#	Barotrauma/BarotraumaServer/LinuxServer.csproj
#	Barotrauma/BarotraumaServer/MacServer.csproj
#	Barotrauma/BarotraumaServer/WindowsServer.csproj
This commit is contained in:
Regalis11
2023-10-19 17:19:58 +03:00
612 changed files with 22093 additions and 11452 deletions

View File

@@ -255,7 +255,7 @@ namespace Barotrauma
/// </summary>
public bool Freeze { get; set; }
public void MoveCamera(float deltaTime, bool allowMove = true, bool allowZoom = true, bool? followSub = null)
public void MoveCamera(float deltaTime, bool allowMove = true, bool allowZoom = true, bool allowInput = true, bool? followSub = null)
{
prevPosition = position;
prevZoom = zoom;
@@ -268,7 +268,7 @@ namespace Barotrauma
Vector2 moveInput = Vector2.Zero;
if (allowMove && !Freeze)
{
if (GUI.KeyboardDispatcher.Subscriber == null)
if (GUI.KeyboardDispatcher.Subscriber == null && allowInput)
{
if (PlayerInput.KeyDown(Keys.LeftShift)) { moveSpeed *= 2.0f; }
if (PlayerInput.KeyDown(Keys.LeftControl)) { moveSpeed *= 0.5f; }

View File

@@ -50,9 +50,9 @@ namespace Barotrauma
}
else if (Entity is Item i)
{
if (i.Submarine != null && i.GetComponent<Items.Components.Door>() == null)
if (i.Submarine != null && i.Container != null)
{
// Don't show items that are inside the submarine, because monsters shouldn't target them when they are inside and the monsters are outside.
// Don't show contained items that are inside the submarine, because they shouldn't attract monsters.
return;
}
color = Color.CadetBlue;

View File

@@ -82,15 +82,20 @@ namespace Barotrauma
{
var previousNode = path.Nodes[i - 1];
var currentNode = path.Nodes[i];
bool isPathActive = !path.Finished && !path.IsAtEndNode;
Color pathColor = isPathActive ? Color.Blue * 0.5f : Color.Gray;
GUI.DrawLine(spriteBatch,
new Vector2(currentNode.DrawPosition.X, -currentNode.DrawPosition.Y),
new Vector2(previousNode.DrawPosition.X, -previousNode.DrawPosition.Y),
Color.Blue * 0.5f, 0, 3);
pathColor, 0, 3);
GUIStyle.SmallFont.DrawString(spriteBatch,
currentNode.ID.ToString(),
new Vector2(currentNode.DrawPosition.X - 10, -currentNode.DrawPosition.Y - 30),
Color.Blue);
if (isPathActive)
{
GUIStyle.SmallFont.DrawString(spriteBatch,
currentNode.ID.ToString(),
new Vector2(currentNode.DrawPosition.X - 10, -currentNode.DrawPosition.Y - 30),
Color.Blue);
}
}
if (path.CurrentNode != null)
{

View File

@@ -469,6 +469,7 @@ namespace Barotrauma
float gibParticleAmount = MathHelper.Clamp(limb.Mass / character.AnimController.Mass, 0.1f, 1.0f);
foreach (ParticleEmitter emitter in character.GibEmitters)
{
if (emitter?.Prefab == null) { continue; }
if (inWater && emitter.Prefab.ParticlePrefab.DrawTarget == ParticlePrefab.DrawTargetType.Air) { continue; }
if (!inWater && emitter.Prefab.ParticlePrefab.DrawTarget == ParticlePrefab.DrawTargetType.Water) { continue; }

View File

@@ -23,8 +23,6 @@ namespace Barotrauma
protected float hudInfoTimer = 1.0f;
protected bool hudInfoVisible = false;
private float pressureParticleTimer;
private float findFocusedTimer;
protected float lastRecvPositionUpdateTime;
@@ -299,7 +297,9 @@ namespace Barotrauma
keys[i].SetState();
}
if (CharacterInventory.IsMouseOnInventory && CharacterHUD.ShouldDrawInventory(this))
if (CharacterInventory.IsMouseOnInventory &&
!keys[(int)InputType.Aim].Held &&
CharacterHUD.ShouldDrawInventory(this))
{
ResetInputIfPrimaryMouse(InputType.Use);
ResetInputIfPrimaryMouse(InputType.Shoot);
@@ -340,13 +340,6 @@ namespace Barotrauma
cam.Zoom = MathHelper.Lerp(cam.Zoom,
cam.DefaultZoom + (Math.Max(pressure, 10) / 150.0f) * Rand.Range(0.9f, 1.1f),
zoomInEffectStrength);
pressureParticleTimer += pressure * deltaTime;
if (pressureParticleTimer > 10.0f)
{
GameMain.ParticleManager.CreateParticle(Params.BleedParticleWater, WorldPosition + Rand.Vector(5.0f), Rand.Vector(10.0f));
pressureParticleTimer = 0.0f;
}
}
}
@@ -722,7 +715,8 @@ namespace Barotrauma
break;
default:
var petBehavior = enemyAI.PetBehavior;
if (petBehavior != null && petBehavior.Happiness < petBehavior.MaxHappiness * 0.25f)
if (petBehavior != null &&
(petBehavior.Happiness < petBehavior.UnhappyThreshold || petBehavior.Hunger > petBehavior.HungryThreshold))
{
PlaySound(CharacterSound.SoundType.Unhappy);
}
@@ -859,21 +853,11 @@ namespace Barotrauma
if (Controlled.AnimController.Stairs != null)
{
//consider the bottom of the stairs the "floor of the room the controlled character is in"
yPos = Controlled.AnimController.Stairs.SimPosition.Y - Controlled.AnimController.Stairs.RectHeight * 0.5f;
}
foreach (var ladder in Ladder.List)
{
if (CanInteractWith(ladder.Item) && Controlled.CanInteractWith(ladder.Item))
{
float xPos = ladder.Item.SimPosition.X;
if (Math.Abs(xPos - SimPosition.X) < 3.0)
{
yPos = ladder.Item.SimPosition.Y - ladder.Item.RectHeight * 0.5f;
}
break;
}
}
//don't show the HUD texts if the character is below the floor of the room the controlled character is in
if (AnimController.FloorY < yPos) { return; }
}

View File

@@ -99,7 +99,7 @@ namespace Barotrauma
public override bool Interrupted => Character.Removed || !Character.Enabled;
public override Color Color =>
Character.CharacterHealth.GetAfflictionStrength(AfflictionPrefab.PoisonType) > 0 || Character.CharacterHealth.GetAfflictionStrength(AfflictionPrefab.ParalysisType) > 0 ?
Character.CharacterHealth.GetAfflictionStrengthByType(AfflictionPrefab.PoisonType) > 0 || Character.CharacterHealth.GetAfflictionStrengthByType(AfflictionPrefab.ParalysisType) > 0 ?
GUIStyle.HealthBarColorPoisoned : GUIStyle.Red;
public override string NumberToDisplay => string.Empty;
@@ -737,7 +737,8 @@ namespace Barotrauma
if (!character.DisableHealthWindow &&
character.IsFriendly(character.FocusedCharacter) &&
character.FocusedCharacter.CharacterHealth.UseHealthWindow &&
character.CanInteractWith(character.FocusedCharacter, 160f, false))
character.CanInteractWith(character.FocusedCharacter, 160f, false) &&
!character.IsClimbing)
{
GUI.DrawString(spriteBatch, textPos, GetCachedHudText("HealHint", InputType.Health),
GUIStyle.Green, Color.Black, 2, GUIStyle.SmallFont);

View File

@@ -19,7 +19,6 @@ namespace Barotrauma
public bool LastControlled;
public int CrewListIndex { get; set; } = -1;
#warning TODO: Refactor
private Sprite disguisedPortrait;
private List<WearableSprite> disguisedAttachmentSprites;
private Vector2? disguisedSheetIndex;
@@ -609,7 +608,6 @@ namespace Barotrauma
CharacterInfo = info;
parentComponent = parent;
HasIcon = hasIcon;
RecreateFrameContents();
}
@@ -848,6 +846,12 @@ namespace Barotrauma
var info = CharacterInfo;
if (info.HeadSprite == null)
{
DebugConsole.ThrowError($"Head Selection: the head sprite is null! Failed to open the head selection.");
return false;
}
float characterHeightWidthRatio = info.HeadSprite.size.Y / info.HeadSprite.size.X;
HeadSelectionList ??= new GUIListBox(
new RectTransform(
@@ -885,8 +889,13 @@ namespace Barotrauma
GUILayoutGroup row = null;
int itemsInRow = 0;
ContentXElement headElement = info.Ragdoll.MainElement.Elements().FirstOrDefault(e =>
ContentXElement headElement = info.Ragdoll.MainElement?.Elements().FirstOrDefault(e =>
e.GetAttributeString("type", "").Equals("head", StringComparison.OrdinalIgnoreCase));
if (headElement == null)
{
DebugConsole.ThrowError($"Head Selection: the head element is null in {info.ragdoll.FileName}! Failed to open the head selection.");
return false;
}
ContentXElement headSpriteElement = headElement.GetChildElement("sprite");
ContentPath spritePathWithTags = headSpriteElement.GetAttributeContentPath("texture");
@@ -963,7 +972,7 @@ namespace Barotrauma
private bool SwitchAttachment(GUIScrollBar scrollBar, WearableType type)
{
var info = CharacterInfo;
int index = (int)scrollBar.BarScrollValue;
int index = (int)Math.Round(scrollBar.BarScrollValue);
switch (type)
{
case WearableType.Beard:

View File

@@ -350,7 +350,7 @@ namespace Barotrauma
}
else
{
Inventory.ClientEventRead(msg, sendingTime);
Inventory.ClientEventRead(msg);
}
break;
case EventType.Control:
@@ -406,7 +406,7 @@ namespace Barotrauma
float targetY = msg.ReadSingle();
Vector2 targetSimPos = new Vector2(targetX, targetY);
//255 = entity already removed, no need to do anything
if (attackLimbIndex == 255 || Removed) { break; }
if (attackLimbIndex == 255 || targetEntityID == NullEntityID || Removed) { break; }
if (attackLimbIndex >= AnimController.Limbs.Length)
{
//it's possible to get these errors when mid-round syncing, as the client may not
@@ -666,7 +666,7 @@ namespace Barotrauma
}
else
{
DebugConsole.ThrowError("Could not set order \"" + orderPrefab.Identifier + "\" for character \"" + character.Name + "\" because required target entity was not found.");
DebugConsole.AddSafeError("Could not set order \"" + orderPrefab.Identifier + "\" for character \"" + character.Name + "\" because required target entity was not found.");
}
}
else

View File

@@ -257,7 +257,8 @@ namespace Barotrauma
{
for (int i = 0; i < character.Inventory.Capacity; i++)
{
if (character.Inventory.SlotTypes[i] != InvSlotType.HealthInterface || Character.Controlled != Character) { continue; }
if (character.Inventory.SlotTypes[i] != InvSlotType.HealthInterface) { continue; }
if (character.Inventory.HideSlot(i)) { continue; }
//don't draw the item if it's being dragged out of the slot
bool drawItem = !Inventory.DraggingItems.Any() || !Character.Inventory.GetItemsAt(i).All(it => Inventory.DraggingItems.Contains(it)) || character.Inventory.visualSlots[i].MouseOn();
@@ -479,6 +480,17 @@ namespace Barotrauma
inventoryScale = Inventory.UIScale;
uiScale = GUI.Scale;
showHiddenAfflictionsButton.RectTransform.NonScaledSize = new Point(afflictionIconContainer.Rect.Height);
//remove affliction icons so we recreate and resize them
for (int i = afflictionIconContainer.CountChildren - 1; i >= 0; i--)
{
var child = afflictionIconContainer.GetChild(i);
if (child.UserData is AfflictionPrefab)
{
afflictionIconContainer.RemoveChild(child);
}
}
healthBarHolder.RectTransform.AbsoluteOffset = HUDLayoutSettings.HealthBarArea.Location;
healthBarHolder.RectTransform.NonScaledSize = HUDLayoutSettings.HealthBarArea.Size;
healthBarHolder.RectTransform.RelativeOffset = Vector2.Zero;
@@ -496,6 +508,8 @@ namespace Barotrauma
}
healthWindow.RectTransform.RecalculateChildren(false);
Character.Inventory?.RefreshSlotPositions();
}
public void UpdateClientSpecific(float deltaTime)
@@ -579,7 +593,7 @@ namespace Barotrauma
bool inWater = Character.AnimController.InWater;
var drawTarget = inWater ? Particles.ParticlePrefab.DrawTargetType.Water : Particles.ParticlePrefab.DrawTargetType.Air;
var emitter = Character.BloodEmitters.FirstOrDefault(e => e.Prefab.ParticlePrefab.DrawTarget == drawTarget || e.Prefab.ParticlePrefab.DrawTarget == Particles.ParticlePrefab.DrawTargetType.Both);
var emitter = Character.BloodEmitters.FirstOrDefault(e => e.Prefab.ParticlePrefab?.DrawTarget == drawTarget || e.Prefab.ParticlePrefab?.DrawTarget == Particles.ParticlePrefab.DrawTargetType.Both);
float particleMinScale = emitter?.Prefab.Properties.ScaleMin ?? 0.5f;
float particleMaxScale = emitter?.Prefab.Properties.ScaleMax ?? 1;
float severity = Math.Min(affliction.Strength / affliction.Prefab.MaxStrength * Character.Params.BleedParticleMultiplier, 1);
@@ -2129,7 +2143,7 @@ namespace Barotrauma
{
var affliction = kvp.Key;
float burnStrength = affliction.Strength / Math.Min(affliction.Prefab.MaxStrength, 100) * affliction.Prefab.BurnOverlayAlpha;
if (kvp.Value == limbHealths[limb.HealthIndex])
if (kvp.Value == limbHealths[limb.HealthIndex] || !affliction.Prefab.LimbSpecific)
{
limb.BurnOverlayStrength += burnStrength;
limb.DamageOverlayStrength += affliction.Strength / Math.Min(affliction.Prefab.MaxStrength, 100) * affliction.Prefab.DamageOverlayAlpha;

View File

@@ -12,8 +12,6 @@ namespace Barotrauma
private static DateTimeOffset OnCooldownUntil = DateTimeOffset.MinValue;
private const float CooldownDuration = 0.5f;
public static readonly Identifier MedicalItemTag = new Identifier("medical");
public static void PutOnCooldown()
{
OnCooldownUntil = DateTimeOffset.UtcNow.AddSeconds(CooldownDuration);

View File

@@ -432,20 +432,20 @@ namespace Barotrauma
{
if (Sprite != null)
{
Sprite.Remove();
var source = Sprite.SourceElement;
Sprite.Remove();
Sprite = new Sprite(source, file: GetSpritePath(source, Params.normalSpriteParams, ref _texturePath));
}
if (_deformSprite != null)
{
_deformSprite.Remove();
var source = _deformSprite.Sprite.SourceElement;
_deformSprite.Remove();
_deformSprite = new DeformableSprite(source, filePath: GetSpritePath(source, Params.deformSpriteParams, ref _texturePath));
}
if (DamagedSprite != null)
{
DamagedSprite.Remove();
var source = DamagedSprite.SourceElement;
DamagedSprite.Remove();
DamagedSprite = new Sprite(source, file: GetSpritePath(source, Params.damagedSpriteParams, ref _damagedTexturePath));
}
for (int i = 0; i < ConditionalSprites.Count; i++)
@@ -458,8 +458,8 @@ namespace Barotrauma
for (int i = 0; i < DecorativeSprites.Count; i++)
{
var decorativeSprite = DecorativeSprites[i];
decorativeSprite.Remove();
var source = decorativeSprite.Sprite.SourceElement;
decorativeSprite.Remove();
DecorativeSprites[i] = new DecorativeSprite(source, file: GetSpritePath(source, Params.decorativeSpriteParams[i], ref _texturePath));
}
}
@@ -478,9 +478,18 @@ namespace Barotrauma
{
if (spriteParams != null)
{
ContentPath texturePath =
character.Params.VariantFile?.Root?.GetAttributeContentPath("texture", character.Prefab.ContentPackage)
?? ContentPath.FromRaw(spriteParams.Element.ContentPackage ?? character.Prefab.ContentPackage, spriteParams.GetTexturePath());
//1. check if the variant file redefines the texture
ContentPath texturePath = character.Params.VariantFile?.Root?.GetAttributeContentPath("texture", character.Prefab.ContentPackage);
//2. check if the base prefab defines the texture
if (texturePath.IsNullOrEmpty() && !character.Prefab.VariantOf.IsEmpty)
{
RagdollParams parentRagdollParams = character.IsHumanoid ?
RagdollParams.GetRagdollParams<HumanRagdollParams>(character.Prefab.VariantOf) :
RagdollParams.GetRagdollParams<FishRagdollParams>(character.Prefab.VariantOf);
texturePath = parentRagdollParams.OriginalElement?.GetAttributeContentPath("texture");
}
//3. "default case", get the texture from this character's XML
texturePath ??= ContentPath.FromRaw(spriteParams.Element.ContentPackage ?? character.Prefab.ContentPackage, spriteParams.GetTexturePath());
path = GetSpritePath(texturePath);
}
else
@@ -592,6 +601,7 @@ namespace Barotrauma
{
foreach (ParticleEmitter emitter in character.DamageEmitters)
{
if (emitter?.Prefab == null) { continue; }
if (InWater && emitter.Prefab.ParticlePrefab.DrawTarget == ParticlePrefab.DrawTargetType.Air) { continue; }
if (!InWater && emitter.Prefab.ParticlePrefab.DrawTarget == ParticlePrefab.DrawTargetType.Water) { continue; }
ParticlePrefab overrideParticle = null;
@@ -614,6 +624,7 @@ namespace Barotrauma
foreach (ParticleEmitter emitter in character.BloodEmitters)
{
if (emitter?.Prefab == null) { continue; }
if (InWater && emitter.Prefab.ParticlePrefab.DrawTarget == ParticlePrefab.DrawTargetType.Air) { continue; }
if (!InWater && emitter.Prefab.ParticlePrefab.DrawTarget == ParticlePrefab.DrawTargetType.Water) { continue; }
emitter.Emit(1.0f, WorldPosition, character.CurrentHull, sizeMultiplier: bloodParticleSize, amountMultiplier: bloodParticleAmount);
@@ -727,7 +738,7 @@ namespace Barotrauma
}
}
float herpesStrength = character.CharacterHealth.GetAfflictionStrength(AfflictionPrefab.SpaceHerpesType);
float herpesStrength = character.CharacterHealth.GetAfflictionStrengthByType(AfflictionPrefab.SpaceHerpesType);
bool hideLimb = Hide ||
OtherWearables.Any(w => w.HideLimb) ||
@@ -890,6 +901,28 @@ namespace Barotrauma
foreach (WearableSprite wearable in WearingItems)
{
if (onlyDrawable != null && onlyDrawable != wearable && wearable.CanBeHiddenByOtherWearables) { continue; }
if (wearable.CanBeHiddenByItem.Any())
{
bool hiddenByOtherItem = false;
foreach (var otherWearable in WearingItems)
{
if (otherWearable == wearable) { continue; }
if (wearable.CanBeHiddenByItem.Contains(otherWearable.WearableComponent.Item.Prefab.Identifier))
{
hiddenByOtherItem = true;
break;
}
foreach (Identifier tag in wearable.CanBeHiddenByItem)
{
if (otherWearable.WearableComponent.Item.HasTag(tag))
{
hiddenByOtherItem = true;
break;
}
}
}
if (hiddenByOtherItem) { continue; }
}
DrawWearable(wearable, depthStep, spriteBatch, blankColor, alpha: color.A / 255f, spriteEffect);
//if there are multiple sprites on this limb, make the successive ones be drawn in front
depthStep += step;
@@ -1245,6 +1278,12 @@ namespace Barotrauma
};
paramsToPass.Params["wearableUvToClipperUv"] = wearableUvToClipperUv;
paramsToPass.Params["stencilUVmin"] = new Vector2(
(float)alphaClipper.Sprite.SourceRect.X / alphaClipper.Sprite.Texture.Width,
(float)alphaClipper.Sprite.SourceRect.Y / alphaClipper.Sprite.Texture.Height);
paramsToPass.Params["stencilUVmax"] = new Vector2(
(float)alphaClipper.Sprite.SourceRect.Right / alphaClipper.Sprite.Texture.Width,
(float)alphaClipper.Sprite.SourceRect.Bottom / alphaClipper.Sprite.Texture.Height);
paramsToPass.Params["clipperTexelSize"] = 2f / alphaClipper.Sprite.Texture.Width;
paramsToPass.Params["aCutoff"] = 2f / 255f;
paramsToPass.Params["xTexture"] = wearable.Sprite.Texture;
@@ -1254,12 +1293,11 @@ namespace Barotrauma
private void DrawWearable(WearableSprite wearable, float depthStep, SpriteBatch spriteBatch, Color color, float alpha, SpriteEffects spriteEffect)
{
var (finalColor, origin, rotation, scale, depth)
= CalculateDrawParameters(wearable, depthStep, color, alpha);
if (wearable.Sprite?.Texture == null) { return; }
var (finalColor, origin, rotation, scale, depth) = CalculateDrawParameters(wearable, depthStep, color, alpha);
var prevEffect = spriteBatch.GetCurrentEffect();
var alphaClipper = WearingItems.Find(w => w.AlphaClipOtherWearables);
bool shouldApplyAlphaClip = alphaClipper != null && wearable != alphaClipper;
bool shouldApplyAlphaClip = alphaClipper?.Sprite?.Texture != null && wearable != alphaClipper;
if (shouldApplyAlphaClip)
{
ApplyAlphaClip(spriteBatch, wearable, alphaClipper, spriteEffect);
@@ -1302,13 +1340,13 @@ namespace Barotrauma
OtherWearables.ForEach(w => w.Sprite.Remove());
OtherWearables.Clear();
HuskSprite?.Sprite.Remove();
HuskSprite?.Sprite?.Remove();
HuskSprite = null;
HairWithHatSprite?.Sprite.Remove();
HairWithHatSprite?.Sprite?.Remove();
HairWithHatSprite = null;
HerpesSprite?.Sprite.Remove();
HerpesSprite?.Sprite?.Remove();
HerpesSprite = null;
TintMask?.Remove();

View File

@@ -0,0 +1,114 @@
#nullable enable
using System;
using System.Linq;
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma
{
internal partial class CircuitBoxComponent
{
public static Option<GUIComponent> EditingHUD = Option.None;
private Sprite Sprite => Item.Prefab.InventoryIcon ?? Item.Prefab.Sprite;
private CircuitBoxLabel? label;
private CircuitBoxLabel Label
{
get
{
if (label is { } l)
{
return l;
}
var name = TextManager.Get($"circuitboxnode.{Item.Prefab.Identifier}").Fallback($"[FALLBACK] {Item.Name}");
label = new CircuitBoxLabel(name, GUIStyle.LargeFont);
return label.Value;
}
}
public void UpdateEditing(RectTransform parent)
{
if (EditingHUD.TryUnwrap(out var editor))
{
if (editor.UserData == this) { return; }
RemoveEditingHUD();
}
EditingHUD = Option.Some(CreateEditingHUD(parent));
}
public static void RemoveEditingHUD()
{
if (!EditingHUD.TryUnwrap(out var editor)) { return; }
editor.RectTransform.Parent = null;
EditingHUD = Option.None;
}
public GUIComponent CreateEditingHUD(RectTransform parent)
{
GUIFrame frame = new(new RectTransform(new Vector2(0.4f, 0.3f), parent, Anchor.TopRight))
{
UserData = this
};
GUIListBox listBox = new(new RectTransform(ToolBox.PaddingSizeParentRelative(frame.RectTransform, 0.8f), frame.RectTransform, Anchor.Center))
{
KeepSpaceForScrollBar = true,
AutoHideScrollBar = false,
CanTakeKeyBoardFocus = false
};
bool isEditor = Screen.Selected is { IsEditor: true };
GUILayoutGroup titleHolder = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.3f), listBox.Content.RectTransform));
new GUITextBlock(new RectTransform(Vector2.One, titleHolder.RectTransform), Item.Name, font: GUIStyle.LargeFont)
{
TextColor = Color.White,
Color = Color.Black
};
int fieldCount = 0;
foreach (ItemComponent ic in Item.Components)
{
if (ic is Holdable) { continue; }
if (!ic.AllowInGameEditing) { continue; }
if (SerializableProperty.GetProperties<InGameEditable>(ic).Count == 0 &&
!SerializableProperty.GetProperties<ConditionallyEditable>(ic).Any(p => p.GetAttribute<ConditionallyEditable>().IsEditable(ic)))
{
continue;
}
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.02f), listBox.Content.RectTransform), style: "HorizontalLine");
var componentEditor = new SerializableEntityEditor(listBox.Content.RectTransform, ic, inGame: !isEditor, showName: false, titleFont: GUIStyle.SubHeadingFont);
fieldCount += componentEditor.Fields.Count;
ic.CreateEditingHUD(componentEditor);
componentEditor.Recalculate();
}
if (fieldCount == 0)
{
frame.Visible = false;
}
return frame;
}
public override void DrawHeader(SpriteBatch spriteBatch, RectangleF drawRect, Color color)
{
// scale to topRect height
Vector2 scale = new(drawRect.Height / MathF.Min(Sprite.size.X, Sprite.size.Y)),
spritePosition = new(drawRect.Left, drawRect.Top);
float spriteWidth = Sprite.size.X * scale.X;
Sprite.Draw(spriteBatch, spritePosition, Color.White, Vector2.Zero, 0f, scale);
GUI.DrawString(spriteBatch, new Vector2(spritePosition.X + spriteWidth + CircuitBoxSizes.NodeHeaderTextPadding, drawRect.Center.Y - Label.Size.Y / 2f), Label.Value, GUIStyle.TextColorNormal, font: GUIStyle.LargeFont);
}
}
}

View File

@@ -0,0 +1,127 @@
#nullable enable
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma
{
internal abstract partial class CircuitBoxConnection
{
public string Name => Label.Value.Value;
public CircuitBoxLabel Label { get; private set; }
private Sprite? knobSprite,
screwSprite,
connectorSprite;
private static int padding => GUI.IntScale(8);
private Option<LocalizedString> tooltip = Option.None;
private partial void InitProjSpecific(CircuitBox circuitBox)
{
Label = new CircuitBoxLabel(Connection.Name, GUIStyle.SubHeadingFont);
knobSprite = circuitBox.ConnectionSprite;
screwSprite = circuitBox.ConnectionScrewSprite;
connectorSprite = circuitBox.WireConnectorSprite;
Length = Rect.Width + padding + Label.Size.X;
}
public void Draw(SpriteBatch spriteBatch, Vector2 drawPos, Vector2 parentPos, Color color)
{
if (CircuitBox.UI is not { } circuitBoxUi) { return; }
var drawRect = CircuitBoxNode.OverrideRectLocation(Rect, drawPos, parentPos);
Vector2 cursorPos = circuitBoxUi.GetCursorPosition();
cursorPos.Y = -cursorPos.Y;
bool isMouseOver = drawRect.Contains(cursorPos);
float xPos;
if (IsOutput)
{
xPos = drawRect.Left - padding - Label.Size.X;
}
else
{
xPos = drawRect.Right + padding;
}
Vector2 stringPos = new Vector2(xPos, drawRect.Center.Y - Label.Size.Y / 2f);
GUI.DrawString(spriteBatch, stringPos, Label.Value, GUIStyle.TextColorNormal, font: Label.Font);
if (knobSprite is null)
{
CircuitBoxUI.DrawRectangleWithBorder(spriteBatch, drawRect, GUIStyle.Blue * 0.3f, GUIStyle.Blue);
}
else
{
float scale = drawRect.Height / knobSprite.size.Y;
knobSprite?.Draw(spriteBatch, drawRect.Center, color, 0f, scale);
}
bool isScrewed = this switch
{
CircuitBoxOutputConnection output => output.ExternallyConnectedFrom.Count > 0,
CircuitBoxInputConnection input => input.ExternallyConnectedTo.Count > 0,
_ => Connection.Wires.Count > 0 ||
Connection.CircuitBoxConnections.Count > 0 ||
ExternallyConnectedFrom.Count > 0
};
if (isMouseOver)
{
var glowSprite = GUIStyle.UIGlowCircular.Value.Sprite;
float glowScale = 40f / glowSprite.size.X;
if (isScrewed)
{
glowScale *= 1.2f;
}
glowSprite.Draw(spriteBatch, position, GUIStyle.Yellow, glowSprite.size / 2, scale: glowScale);
}
tooltip = Option.None;
if (ConnectionPanel.ShouldDebugDrawWiring)
{
Connection.DrawConnectionDebugInfo(spriteBatch, Connection, drawRect.Center, isScrewed ? 1.1f : 0.9f, out var tooltipText);
if (isMouseOver && !tooltipText.IsNullOrEmpty())
{
tooltip = Option.Some(tooltipText);
}
}
if (!isScrewed) { return; }
if (screwSprite is not null)
{
float screwScale = drawRect.Height / screwSprite.size.Y;
screwSprite.Draw(spriteBatch, drawRect.Center, color, 0f, screwScale);
}
if (connectorSprite is not null)
{
float screwScale = drawRect.Height / connectorSprite.size.Y * 2f;
Vector2 pos = drawRect.Center;
connectorSprite.Draw(spriteBatch,
pos: pos,
color: Color.White,
origin: connectorSprite.Origin,
rotate: MathHelper.Pi / (IsOutput ? -2f : 2f),
scale: screwScale,
spriteEffect: SpriteEffects.None);
}
}
public void DrawHUD(SpriteBatch spriteBatch, Camera camera)
{
if (!tooltip.TryUnwrap(out var text)) { return; }
var drawPos = camera.WorldToScreen(new Vector2(Rect.Right, -Rect.Bottom));
GUIComponent.DrawToolTip(spriteBatch, text, drawPos);
}
}
}

View File

@@ -0,0 +1,22 @@
#nullable enable
using Microsoft.Xna.Framework;
namespace Barotrauma
{
internal readonly struct CircuitBoxLabel
{
public LocalizedString Value { get; }
public Vector2 Size { get; }
public GUIFont Font { get; }
public CircuitBoxLabel(LocalizedString value, GUIFont font)
{
Value = value;
Font = font;
Size = font.MeasureString(font.ForceUpperCase ? value.Value.ToUpperInvariant() : value.Value);
}
}
}

View File

@@ -0,0 +1,233 @@
#nullable enable
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.Xna.Framework;
namespace Barotrauma
{
/// <summary>
/// This class handles a couple things:
/// - Figuring out which components should be moved when dragging a certain part of the UI.
/// - Finding components, connectors and wires under cursor.
/// - Determines whether the user is dragging something.
/// </summary>
internal sealed class CircuitBoxMouseDragSnapshotHandler
{
public IEnumerable<CircuitBoxNode> Nodes => circuitBoxUi.CircuitBox.Components.Union<CircuitBoxNode>(circuitBoxUi.CircuitBox.InputOutputNodes);
private IReadOnlyList<CircuitBoxWire> Wires => circuitBoxUi.CircuitBox.Wires;
// List of all connections in the circuit box
private ImmutableArray<CircuitBoxConnection> connections = ImmutableArray<CircuitBoxConnection>.Empty;
// Nodes that were under cursor when dragging started
private ImmutableHashSet<CircuitBoxNode> lastNodesUnderCursor = ImmutableHashSet<CircuitBoxNode>.Empty,
// Nodes that were selected when dragging started
lastSelectedComponents = ImmutableHashSet<CircuitBoxNode>.Empty,
// Nodes that should be moved when dragging
moveAffectedComponents = ImmutableHashSet<CircuitBoxNode>.Empty;
public ImmutableHashSet<CircuitBoxNode> GetLastComponentsUnderCursor() => lastNodesUnderCursor;
public ImmutableHashSet<CircuitBoxNode> GetMoveAffectedComponents() => moveAffectedComponents;
public Option<CircuitBoxConnection> LastConnectorUnderCursor = Option.None;
public Option<CircuitBoxWire> LastWireUnderCursor = Option.None;
/// <summary>
/// If the user is currently dragging a node
/// </summary>
public bool IsDragging { get; private set; }
/// <summary>
/// If the user is currently dragging a wire
/// </summary>
public bool IsWiring { get; private set; }
private Vector2 startClick = Vector2.Zero;
private readonly CircuitBoxUI circuitBoxUi;
/// <summary>
/// How far the user has to drag the mouse while holding down the button before dragging starts
/// </summary>
private const float dragTreshold = 16f;
public CircuitBoxMouseDragSnapshotHandler(CircuitBoxUI ui)
{
circuitBoxUi = ui;
}
/// <summary>
/// Called when the user holds down the mouse button
/// </summary>
public void StartDragging()
{
Vector2 cursorPos = circuitBoxUi.GetCursorPosition();
SnapshotNodesUnderCursor(cursorPos);
SnapshotSelectedNodes();
SnapshotMoveAffectedNodes();
startClick = cursorPos;
}
/// <summary>
/// Finds all connections and gathers them into a single list for easier iteration.
/// </summary>
public void UpdateConnections()
{
var builder = ImmutableArray.CreateBuilder<CircuitBoxConnection>();
builder.AddRange(circuitBoxUi.CircuitBox.Inputs);
builder.AddRange(circuitBoxUi.CircuitBox.Outputs);
foreach (var node in Nodes)
{
builder.AddRange(node.Connectors);
}
connections = builder.ToImmutable();
}
/// <summary>
/// Finds a possible connector under the cursor.
/// </summary>
public Option<CircuitBoxConnection> FindConnectorUnderCursor(Vector2 cursorPos)
{
foreach (var connection in connections)
{
if (connection.Contains(cursorPos))
{
return Option.Some(connection);
}
}
return Option.None;
}
/// <summary>
/// Finds a possible wire under the cursor.
/// </summary>
public Option<CircuitBoxWire> FindWireUnderCursor(Vector2 cursorPos)
{
foreach (CircuitBoxWire wire in Wires)
{
if (wire is { IsSelected: true, IsSelectedByMe: false }) { continue; }
if (wire.Renderer.Contains(cursorPos))
{
return Option.Some(wire);
}
}
return Option.None;
}
/// <summary>
/// Find all nodes that are currently under the cursor that are not selected by someone else.
/// </summary>
public ImmutableHashSet<CircuitBoxNode> FindNodesUnderCursor(Vector2 cursorPos)
{
var builder = ImmutableHashSet.CreateBuilder<CircuitBoxNode>();
foreach (var node in Nodes)
{
if (node is { IsSelected: true, IsSelectedByMe: false }) { continue; }
if (node.Rect.Contains(cursorPos))
{
builder.Add(node);
}
}
return builder.ToImmutable();
}
/// <summary>
/// Finds and stores all nodes, connectors and wires that are under the cursor when dragging starts.
/// </summary>
private void SnapshotNodesUnderCursor(Vector2 cursorPos)
{
lastNodesUnderCursor = FindNodesUnderCursor(cursorPos);
LastConnectorUnderCursor = FindConnectorUnderCursor(cursorPos);
LastWireUnderCursor = FindWireUnderCursor(cursorPos);
}
/// <summary>
/// Stores all nodes that are currently selected when dragging starts.
/// There's no real way to change your selection while dragging so this is kinda pointless
/// but we snapshot it anyway just in case.
/// </summary>
private void SnapshotSelectedNodes()
{
lastSelectedComponents = Nodes.Where(static n => n is { IsSelected: true, IsSelectedByMe: true }).ToImmutableHashSet();
}
/// <summary>
/// Stores all nodes that should be moved when dragging starts.
/// </summary>
private void SnapshotMoveAffectedNodes()
{
bool moveSelection = lastNodesUnderCursor.Any(node => lastSelectedComponents.Contains(node));
/*
* If the user is dragging a selection, we should move all selected nodes (true).
*
* But for convenience, if the user is dragging a single node that is not part of the selection,
* we should move that node only instead and leave the selection alone. (false)
*/
moveAffectedComponents = moveSelection switch
{
true => lastSelectedComponents,
false => circuitBoxUi.GetTopmostNode(lastNodesUnderCursor) switch
{
null => ImmutableHashSet<CircuitBoxNode>.Empty,
var node => ImmutableHashSet.Create(node)
}
};
}
public Vector2 GetDragAmount(Vector2 mousePos) => mousePos - startClick;
/// <summary>
/// Called when the user releases the mouse button
/// </summary>
public void EndDragging()
{
startClick = Vector2.Zero;
IsDragging = false;
IsWiring = false;
lastNodesUnderCursor = ImmutableHashSet<CircuitBoxNode>.Empty;
}
public void UpdateDrag(Vector2 cursorPos)
{
// if there are no connectors under cursor, we can't be wiring anything
if (LastConnectorUnderCursor.IsNone())
{
IsWiring = false;
}
// if there are no nodes under cursor, we can't be dragging anything
if (lastNodesUnderCursor.IsEmpty)
{
IsDragging = false;
}
// startClick is set to zero when the user releases the mouse button, so we should be neither dragging nor wiring in this state
if (startClick == Vector2.Zero)
{
IsDragging = false;
IsWiring = false;
return;
}
bool isDragTresholdExceeded = Vector2.DistanceSquared(startClick, cursorPos) > dragTreshold * dragTreshold;
if (LastConnectorUnderCursor.IsNone())
{
IsDragging |= isDragTresholdExceeded;
}
else
{
IsWiring |= isDragTresholdExceeded;
}
}
}
}

View File

@@ -0,0 +1,88 @@
#nullable enable
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma
{
internal partial class CircuitBoxNode
{
private RectangleF DrawRect,
TopDrawRect;
private void UpdateDrawRects()
{
var drawRect = new RectangleF(Position - Size / 2f, Size);
drawRect.Y = -drawRect.Y;
drawRect.Y -= drawRect.Height;
DrawRect = drawRect;
TopDrawRect = new RectangleF(drawRect.X, drawRect.Y - (CircuitBoxSizes.NodeHeaderHeight - 1), drawRect.Width, CircuitBoxSizes.NodeHeaderHeight);
}
public void OnUICreated()
{
Size = CalculateSize(Connectors);
UpdatePositions();
}
public void DrawBackground(SpriteBatch spriteBatch, RectangleF drawRect, RectangleF topDrawRect, Color color)
{
CircuitBox.NodeFrameSprite?.Draw(spriteBatch, drawRect, color);
CircuitBox.NodeTopSprite?.Draw(spriteBatch, topDrawRect, color);
}
public void Draw(SpriteBatch spriteBatch, Vector2 drawPos, Color color)
{
RectangleF drawRect = OverrideRectLocation(DrawRect, drawPos, Position),
topDrawRect = OverrideRectLocation(TopDrawRect, drawPos, Position);
DrawBackground(spriteBatch, drawRect, topDrawRect, color);
DrawHeader(spriteBatch, topDrawRect, color);
DrawConnectors(spriteBatch, drawPos);
}
public void DrawHUD(SpriteBatch spriteBatch, Camera camera)
{
foreach (var c in Connectors)
{
c.DrawHUD(spriteBatch, camera);
}
}
public virtual void DrawHeader(SpriteBatch spriteBatch, RectangleF rect, Color color) { }
public void DrawConnectors(SpriteBatch spriteBatch, Vector2 drawPos)
{
var color = Color.White * Opacity;
foreach (var c in Connectors)
{
c.Draw(spriteBatch, drawPos, Position, color);
}
}
public void DrawSelection(SpriteBatch spriteBatch, Color color)
{
int pad = GUI.IntScale(8);
var rect = Rect;
rect.Y = -rect.Y;
rect.Y -= rect.Height;
rect.Inflate(pad, pad);
GUI.DrawFilledRectangle(spriteBatch, rect, color * Opacity);
}
/// <summary>
/// Sets the location of the rectangle to a specific position, keeping origin intact.
/// </summary>
public static RectangleF OverrideRectLocation(RectangleF rect, Vector2 overridePos, Vector2 originalPos)
{
rect.Location -= new Vector2(originalPos.X, -originalPos.Y);
rect.Location += new Vector2(overridePos.X, -overridePos.Y);
return rect;
}
}
}

View File

@@ -0,0 +1,722 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
namespace Barotrauma
{
internal sealed class CircuitBoxUI
{
private readonly Camera camera;
private static readonly Vector2 gridSize = new Vector2(128f);
public readonly CircuitBox CircuitBox;
private bool componentMenuOpen;
private float componentMenuOpenState;
private GUICustomComponent? circuitComponent;
private GUIFrame? componentMenu;
private GUIButton? toggleMenuButton;
private GUIFrame? selectedWireFrame;
private GUIListBox? componentList;
private GUITextBlock? inventoryIndicatorText;
private readonly Sprite cursorSprite = GUIStyle.CursorSprite[CursorState.Default];
private Option<RectangleF> selection = Option.None;
private string searchTerm = string.Empty;
public static Option<CircuitBoxWireRenderer> DraggedWire = Option.None;
public readonly CircuitBoxMouseDragSnapshotHandler MouseSnapshotHandler;
public List<CircuitBoxWireRenderer> VirtualWires = new();
public CircuitBoxUI(CircuitBox box)
{
camera = new Camera
{
MinZoom = 0.25f,
MaxZoom = 2f
};
CircuitBox = box;
MouseSnapshotHandler = new CircuitBoxMouseDragSnapshotHandler(this);
}
#region UI
public void CreateGUI(GUIFrame parent)
{
GUIFrame paddedFrame = new GUIFrame(new RectTransform(new Vector2(0.97f, 0.95f), parent.RectTransform, Anchor.Center), style: null);
circuitComponent = new GUICustomComponent(new RectTransform(Vector2.One, paddedFrame.RectTransform), onDraw: (spriteBatch, component) =>
{
Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
spriteBatch.End();
spriteBatch.GraphicsDevice.ScissorRectangle = component.Rect;
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable, transformMatrix: camera.Transform);
DrawCircuits(spriteBatch);
spriteBatch.End();
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
DrawHUD(spriteBatch);
spriteBatch.End();
spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect;
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
});
GUIScissorComponent menuContainer = new GUIScissorComponent(new RectTransform(Vector2.One, paddedFrame.RectTransform, anchor: Anchor.Center))
{
CanBeFocused = false
};
componentMenuOpen = true;
componentMenu = new GUIFrame(new RectTransform(new Vector2(1f, 0.4f), menuContainer.Content.RectTransform, Anchor.BottomRight));
toggleMenuButton = new GUIButton(new RectTransform(new Point(300, 30), GUI.Canvas) { MinSize = new Point(0, 15) }, style: "UIToggleButtonVertical")
{
OnClicked = (btn, userdata) =>
{
componentMenuOpen = !componentMenuOpen;
foreach (GUIComponent child in btn.Children)
{
child.SpriteEffects = componentMenuOpen ? SpriteEffects.None : SpriteEffects.FlipVertically;
}
return true;
}
};
GUILayoutGroup menuLayout = new GUILayoutGroup(new RectTransform(Vector2.One, componentMenu.RectTransform), childAnchor: Anchor.TopCenter) { RelativeSpacing = 0.02f };
GUILayoutGroup headerLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.2f), menuLayout.RectTransform), isHorizontal: true);
GUILayoutGroup labelLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.33f, 1f), headerLayout.RectTransform), isHorizontal: true);
GUILayoutGroup searchBarLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.33f, 1f), headerLayout.RectTransform), childAnchor: Anchor.CenterLeft, isHorizontal: true);
GUITextBlock searchBarLabel = new GUITextBlock(new RectTransform(new Vector2(0.15f, 1f), searchBarLayout.RectTransform), "Filter");
GUITextBox searchbar = new GUITextBox(new RectTransform(new Vector2(0.85f, 1f), searchBarLayout.RectTransform), string.Empty, createClearButton: true);
new GUIFrame(new RectTransform(new Vector2(0.5f, 0.01f), menuLayout.RectTransform), style: "HorizontalLine");
componentList = new GUIListBox(new RectTransform(new Vector2(0.95f, 0.65f), menuLayout.RectTransform))
{
PlaySoundOnSelect = true,
UseGridLayout = true,
OnSelected = (_, o) =>
{
if (o is not ItemPrefab prefab) { return false; }
CircuitBox.HeldComponent = Option.Some(prefab);
return true;
}
};
GUILayoutGroup inventoryLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.33f, 1f), headerLayout.RectTransform), isHorizontal: true, childAnchor: Anchor.Center);
GUILayoutGroup indicatorLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.2f, 1f), inventoryLayout.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft);
GUIImage indicatorIcon = new GUIImage(new RectTransform(new Vector2(0.5f, 0.8f), indicatorLayout.RectTransform, scaleBasis: ScaleBasis.BothHeight), style: "CircuitIndicatorIcon");
inventoryIndicatorText = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), indicatorLayout.RectTransform), GetInventoryText(), font: GUIStyle.SubHeadingFont);
int gapSize = GUI.IntScale(8);
selectedWireFrame = SubEditorScreen.CreateWiringPanel(Point.Zero, SelectWire);
selectedWireFrame.RectTransform.AbsoluteOffset = new Point(parent.Rect.X - (selectedWireFrame.Rect.Width + gapSize), parent.Rect.Y);
foreach (ItemPrefab prefab in ItemPrefab.Prefabs.OrderBy(static p => p.Name))
{
if (!prefab.Tags.Contains("circuitboxcomponent")) { continue; }
CreateComponentElement(prefab, componentList.Content.RectTransform);
}
searchbar.OnTextChanged += (tb, s) =>
{
searchTerm = s;
UpdateComponentList();
return true;
};
int buttonHeight = (int)(GUIStyle.ItemFrameMargin.Y * 0.4f);
var settingsIcon = new GUIButton(new RectTransform(new Point(buttonHeight), parent.RectTransform, Anchor.TopLeft) { AbsoluteOffset = new Point(buttonHeight / 4), MinSize = new Point(buttonHeight) },
style: "GUIButtonSettings")
{
OnClicked = (btn, userdata) =>
{
GUIContextMenu.CreateContextMenu(
new ContextMenuOption("circuitboxsetting.resetview", isEnabled: true, onSelected: ResetCamera)
{
Tooltip = TextManager.Get("circuitboxsettingdescription.resetview")
},
new ContextMenuOption("circuitboxsetting.find", isEnabled: true,
new ContextMenuOption("circuitboxsetting.focusinput", isEnabled: true, onSelected: () => FindInputOuput(CircuitBoxInputOutputNode.Type.Input))
{
Tooltip = TextManager.Get("circuitboxsettingdescription.focusinput")
},
new ContextMenuOption("circuitboxsetting.focusoutput", isEnabled: true, onSelected: () => FindInputOuput(CircuitBoxInputOutputNode.Type.Output))
{
Tooltip = TextManager.Get("circuitboxsettingdescription.focusoutput")
},
new ContextMenuOption("circuitboxsetting.focuscircuits", isEnabled: CircuitBox.Components.Any(), onSelected: FindCircuit)
{
Tooltip = TextManager.Get("circuitboxsettingdescription.focuscircuits")
}));
void ResetCamera()
{
// Vector2.One because Vector2.Zero means no value
camera.TargetPos = Vector2.One;
}
void FindInputOuput(CircuitBoxInputOutputNode.Type type)
{
var input = CircuitBox.InputOutputNodes.FirstOrDefault(n => n.NodeType == type);
if (input is null) { return; }
camera.TargetPos = input.Position;
}
void FindCircuit()
{
var closestComponent = CircuitBox.Components.MinBy(c => Vector2.DistanceSquared(c.Position, camera.Position));
if (closestComponent is null) { return; }
camera.TargetPos = closestComponent.Position;
}
return true;
}
};
MouseSnapshotHandler.UpdateConnections();
// update scales of everything
foreach (var node in CircuitBox.Components) { node.OnUICreated(); }
foreach (var node in CircuitBox.InputOutputNodes) { node.OnUICreated(); }
foreach (var wire in CircuitBox.Wires) { wire.Update(); }
}
private string GetInventoryText()
=> CircuitBox.ComponentContainer is { } container
? $"{container.Inventory.AllItems.Count()}/{container.Capacity}"
: "0/0";
public void UpdateComponentList()
{
if (inventoryIndicatorText is { } text)
{
text.Text = GetInventoryText();
}
if (componentList is null) { return; }
var playerInventory = CircuitBox.GetSortedCircuitBoxSortedItemsFromPlayer(Character.Controlled);
foreach (GUIComponent child in componentList.Content.Children)
{
if (child.UserData is not ItemPrefab prefab) { continue; }
child.Enabled = !CircuitBox.IsFull && (!CircuitBox.IsInGame() || CircuitBox.GetApplicableResourcePlayerHas(prefab, playerInventory).IsSome());
if (child.GetChild<GUILayoutGroup>()?.GetChild<GUIImage>() is { } image)
{
image.Enabled = child.Enabled;
}
child.ToolTip = child.Enabled
? prefab.Description
: RichString.Rich(TextManager.GetWithVariable(new Identifier("CircuitBoxUIComponentNotAvailable"), new Identifier("[item]"), prefab.Name));
if (string.IsNullOrWhiteSpace(searchTerm))
{
child.Visible = true;
continue;
}
child.Visible = prefab.Name.Contains(searchTerm, StringComparison.OrdinalIgnoreCase);
}
}
private static bool SelectWire(GUIComponent component, object obj)
{
if (obj is not ItemPrefab prefab) { return false; }
CircuitBoxWire.SelectedWirePrefab = prefab;
return true;
}
private static void CreateComponentElement(ItemPrefab prefab, RectTransform parent)
{
GUIFrame itemFrame = new GUIFrame(new RectTransform(new Vector2(0.1f, 0.9f), parent) { MinSize = new Point(0, 50) }, style: "GUITextBox")
{
UserData = prefab
};
itemFrame.RectTransform.MinSize = new Point(0, itemFrame.Rect.Width);
itemFrame.RectTransform.MaxSize = new Point(int.MaxValue, itemFrame.Rect.Width);
itemFrame.ToolTip = prefab.Name;
GUILayoutGroup paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.8f), itemFrame.RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter)
{
Stretch = true,
RelativeSpacing = 0.03f,
CanBeFocused = false
};
Sprite icon;
Color iconColor;
if (prefab.InventoryIcon != null)
{
icon = prefab.InventoryIcon;
iconColor = prefab.InventoryIconColor;
}
else
{
icon = prefab.Sprite;
iconColor = prefab.SpriteColor;
}
GUIImage? img = null;
if (icon != null)
{
img = new GUIImage(new RectTransform(new Vector2(1.0f, 0.8f), paddedFrame.RectTransform, Anchor.TopCenter), icon)
{
CanBeFocused = false,
LoadAsynchronously = true,
DisabledColor = Color.DarkGray * 0.8f,
Color = iconColor
};
}
GUITextBlock textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform, Anchor.BottomCenter),
text: prefab.Name, textAlignment: Alignment.Center, font: GUIStyle.SmallFont)
{
CanBeFocused = false
};
textBlock.Text = ToolBox.LimitString(textBlock.Text, textBlock.Font, textBlock.Rect.Width);
paddedFrame.Recalculate();
if (img != null)
{
img.Scale = Math.Min(Math.Min(img.Rect.Width / img.Sprite.size.X, img.Rect.Height / img.Sprite.size.Y), 1.5f);
img.RectTransform.NonScaledSize = new Point((int)(img.Sprite.size.X * img.Scale), img.Rect.Height);
}
}
#endregion
private void DrawHUD(SpriteBatch spriteBatch)
{
float scale = GUI.Scale / 1.5f;
Vector2 offset = new Vector2(20, 40) * scale;
foreach (var (character, cursor) in CircuitBox.ActiveCursors)
{
if (!cursor.IsActive) { continue; }
Vector2 cursorWorldPos = camera.WorldToScreen(cursor.DrawPosition);
if (cursor.Info.DragStart.TryUnwrap(out Vector2 dragStart))
{
DrawSelection(spriteBatch, dragStart, cursor.DrawPosition, cursor.Color);
}
if (cursor.HeldPrefab.TryUnwrap(out ItemPrefab? otherHeldPrefab))
{
otherHeldPrefab.Sprite.Draw(spriteBatch, cursorWorldPos);
}
cursorSprite?.Draw(spriteBatch, cursorWorldPos, cursor.Color, 0f, scale);
GUI.DrawString(spriteBatch, cursorWorldPos + offset, character.Name, cursor.Color, Color.Black, GUI.IntScale(4), GUIStyle.SmallFont);
}
if (selection.TryUnwrap(out RectangleF rect))
{
Vector2 pos1 = rect.Location;
Vector2 pos2 = new Vector2(rect.Location.X + rect.Size.X, rect.Location.Y + rect.Size.Y);
DrawSelection(spriteBatch, pos1, pos2, GUIStyle.Blue);
}
if (CircuitBox.HeldComponent.TryUnwrap(out ItemPrefab? component))
{
component.Sprite.Draw(spriteBatch, PlayerInput.MousePosition);
}
foreach (var c in CircuitBox.Components)
{
c.DrawHUD(spriteBatch, camera);
}
foreach (var n in CircuitBox.InputOutputNodes)
{
n.DrawHUD(spriteBatch, camera);
}
}
private void DrawSelection(SpriteBatch spriteBatch, Vector2 pos1, Vector2 pos2, Color color)
{
Vector2 location = camera.WorldToScreen(pos1);
location.Y = -location.Y;
Vector2 location2 = camera.WorldToScreen(pos2);
location2.Y = -location2.Y;
MapEntity.DrawSelectionRect(spriteBatch, location, new Vector2(-(location.X - location2.X), location.Y - location2.Y), color);
}
private const float lineBaseWidth = 1f;
private static float lineWidth;
public static void DrawRectangleWithBorder(SpriteBatch spriteBatch, RectangleF rect, Color fillColor, Color borderColor)
{
Vector2 topRight = new Vector2(rect.Right, rect.Top),
topLeft = new Vector2(rect.Left, rect.Top),
bottomRight = new Vector2(rect.Right, rect.Bottom),
bottomLeft = new Vector2(rect.Left, rect.Bottom);
Vector2 offset = new Vector2(0f, lineWidth / 2f);
GUI.DrawFilledRectangle(spriteBatch, rect, fillColor);
spriteBatch.DrawLine(topRight, topLeft, borderColor, thickness: lineWidth);
spriteBatch.DrawLine(topLeft - offset, bottomLeft + offset, borderColor, thickness: lineWidth);
spriteBatch.DrawLine(bottomLeft, bottomRight, borderColor, thickness: lineWidth);
spriteBatch.DrawLine(bottomRight + offset, topRight - offset, borderColor, thickness: lineWidth);
}
private void DrawCircuits(SpriteBatch spriteBatch)
{
camera.UpdateTransform(interpolate: true, updateListener: false);
SubEditorScreen.DrawOutOfBoundsArea(spriteBatch, camera, CircuitBoxSizes.PlayableAreaSize, GUIStyle.Red * 0.33f);
SubEditorScreen.DrawGrid(spriteBatch, camera, gridSize.X, gridSize.Y, zoomTreshold: false);
lineWidth = lineBaseWidth / camera.Zoom;
Vector2 mousePos = GetCursorPosition();
mousePos.Y = -mousePos.Y;
foreach (CircuitBoxWire wire in CircuitBox.Wires)
{
wire.Renderer.Draw(spriteBatch, GetSelectionColor(wire));
}
foreach (var node in CircuitBox.Components)
{
if (node.IsSelected)
{
node.DrawSelection(spriteBatch, GetSelectionColor(node));
}
node.Draw(spriteBatch, node.Position, node.Item.Prefab.SignalComponentColor * CircuitBoxNode.Opacity);
}
foreach (var ioNode in CircuitBox.InputOutputNodes)
{
if (ioNode.IsSelected)
{
ioNode.DrawSelection(spriteBatch, GetSelectionColor(ioNode));
}
Color color = ioNode.NodeType is CircuitBoxInputOutputNode.Type.Input ? GUIStyle.Green : GUIStyle.Red;
ioNode.Draw(spriteBatch, ioNode.Position, color * CircuitBoxNode.Opacity);
}
if (MouseSnapshotHandler.IsDragging)
{
var draggedNodes = MouseSnapshotHandler.GetMoveAffectedComponents();
Vector2 dragOffset = MouseSnapshotHandler.GetDragAmount(GetCursorPosition());
foreach (CircuitBoxNode moveable in draggedNodes)
{
Color color = moveable switch
{
CircuitBoxComponent node => node.Item.Prefab.SignalComponentColor,
CircuitBoxInputOutputNode ioNode => ioNode.NodeType is CircuitBoxInputOutputNode.Type.Input ? GUIStyle.Green : GUIStyle.Red,
_ => Color.White
};
moveable.Draw(spriteBatch, moveable.Position + dragOffset, color * 0.5f);
}
}
if (DraggedWire.TryUnwrap(out CircuitBoxWireRenderer? draggedWire))
{
draggedWire.Draw(spriteBatch, GUIStyle.Yellow);
}
}
private Color GetSelectionColor(CircuitBoxNode node)
=> GetSelectionColor(node.SelectedBy, node.IsSelectedByMe);
private Color GetSelectionColor(CircuitBoxWire wire)
=> GetSelectionColor(wire.SelectedBy, wire.IsSelectedByMe);
private Color GetSelectionColor(ushort selectedBy, bool isSelectedByMe)
{
#if !DEBUG
if (isSelectedByMe)
{
return GUIStyle.Yellow;
}
#endif
foreach (var (_, cursor) in CircuitBox.ActiveCursors)
{
if (cursor.Info.CharacterID == selectedBy)
{
return cursor.Color;
}
}
return GUIStyle.Yellow;
}
private Vector2 cursorPos;
public Vector2 GetCursorPosition() => cursorPos;
public Option<Vector2> GetDragStart() => selection.Select(static f => f.Location);
public void Update(float deltaTime)
{
cursorPos = camera.ScreenToWorld(PlayerInput.MousePosition);
foreach (CircuitBoxWire wire in CircuitBox.Wires)
{
wire.Update();
}
bool foundSelected = false;
foreach (var node in CircuitBox.Components)
{
if (!node.IsSelectedByMe) { continue; }
foundSelected = true;
if (circuitComponent is not null)
{
node.UpdateEditing(circuitComponent.RectTransform);
}
break;
}
if (!foundSelected)
{
CircuitBoxComponent.RemoveEditingHUD();
}
bool isMouseOn = GUI.MouseOn == circuitComponent;
if (isMouseOn)
{
Character.DisableControls = true;
}
camera.MoveCamera(deltaTime, allowMove: true, allowZoom: isMouseOn, allowInput: isMouseOn, followSub: false);
if (camera.TargetPos != Vector2.Zero && MathUtils.NearlyEqual(camera.Position, camera.TargetPos, 0.01f))
{
camera.TargetPos = Vector2.Zero;
}
if (isMouseOn)
{
if (CircuitBox.HeldComponent.IsNone() && PlayerInput.PrimaryMouseButtonDown())
{
MouseSnapshotHandler.StartDragging();
}
if (PlayerInput.MidButtonHeld() || (PlayerInput.IsAltDown() && PlayerInput.PrimaryMouseButtonHeld()))
{
Vector2 moveSpeed = PlayerInput.MouseSpeed / camera.Zoom;
moveSpeed.X = -moveSpeed.X;
camera.Position += moveSpeed;
}
if (PlayerInput.PrimaryMouseButtonHeld())
{
MouseSnapshotHandler.UpdateDrag(GetCursorPosition());
}
if (MouseSnapshotHandler.IsWiring && MouseSnapshotHandler.LastConnectorUnderCursor.TryUnwrap(out var c))
{
Vector2 start = c.Rect.Center,
end = GetCursorPosition();
end.Y = -end.Y;
if (!c.IsOutput)
{
(start, end) = (end, start);
}
if (DraggedWire.TryUnwrap(out var wire))
{
wire.Recompute(start, end, CircuitBoxWire.SelectedWirePrefab.SpriteColor);
}
else
{
DraggedWire = Option.Some(new CircuitBoxWireRenderer(Option.None,start, end, GUIStyle.Red, CircuitBox.WireSprite));
}
}
else
{
DraggedWire = Option.None;
}
if (PlayerInput.SecondaryMouseButtonClicked())
{
OpenContextMenu();
}
if (PlayerInput.PrimaryMouseButtonClicked())
{
if (CircuitBox.HeldComponent.TryUnwrap(out ItemPrefab? prefab))
{
CircuitBox.AddComponent(prefab, cursorPos);
}
else
{
if (MouseSnapshotHandler.IsDragging && PlayerInput.PrimaryMouseButtonReleased())
{
CircuitBox.MoveComponent(MouseSnapshotHandler.GetDragAmount(cursorPos), MouseSnapshotHandler.GetMoveAffectedComponents());
}
else if (!MouseSnapshotHandler.IsWiring)
{
TrySelectComponentsUnderCursor();
}
}
if (MouseSnapshotHandler.IsWiring && MouseSnapshotHandler.LastConnectorUnderCursor.TryUnwrap(out var one))
{
if (MouseSnapshotHandler.FindConnectorUnderCursor(cursorPos).TryUnwrap(out var two))
{
CircuitBox.AddWire(one, two);
}
}
CircuitBox.SelectWires(MouseSnapshotHandler.LastWireUnderCursor.TryUnwrap(out var wire) ? ImmutableArray.Create(wire) : ImmutableArray<CircuitBoxWire>.Empty, !PlayerInput.IsShiftDown());
CircuitBox.HeldComponent = Option.None;
MouseSnapshotHandler.EndDragging();
}
if (MouseSnapshotHandler.GetLastComponentsUnderCursor().IsEmpty && MouseSnapshotHandler.LastConnectorUnderCursor.IsNone())
{
UpdateSelection();
}
// Allow using both Delete key and Ctrl+D for those who don't have a Delete key
bool hitDeleteCombo = PlayerInput.KeyHit(Keys.Delete) || (PlayerInput.IsCtrlDown() && PlayerInput.KeyHit(Keys.D));
if (GUI.KeyboardDispatcher.Subscriber is null && hitDeleteCombo)
{
CircuitBox.RemoveComponents(CircuitBox.Components.Where(static node => node.IsSelectedByMe).ToArray());
CircuitBox.RemoveWires(CircuitBox.Wires.Where(static wire => wire.IsSelectedByMe).ToImmutableArray());
}
}
if (componentMenu is { } menu && toggleMenuButton is { } button)
{
componentMenuOpenState = componentMenuOpen ? Math.Min(componentMenuOpenState + deltaTime * 5.0f, 1.0f) : Math.Max(componentMenuOpenState - deltaTime * 5.0f, 0.0f);
menu.RectTransform.ScreenSpaceOffset = Vector2.Lerp(new Vector2(0.0f, menu.Rect.Height - 10), Vector2.Zero, componentMenuOpenState).ToPoint();
button.RectTransform.AbsoluteOffset = new Point(menu.Rect.X + ((menu.Rect.Width / 2) - (button.Rect.Width / 2)), menu.Rect.Y - button.Rect.Height);
}
camera.Position = Vector2.Clamp(camera.Position,
new Vector2(-CircuitBoxSizes.PlayableAreaSize / 2f),
new Vector2(CircuitBoxSizes.PlayableAreaSize / 2f));
}
private void UpdateSelection()
{
if (!PlayerInput.IsAltDown() && PlayerInput.PrimaryMouseButtonDown())
{
selection = Option.Some(new RectangleF(GetCursorPosition(), Vector2.Zero));
}
if (!selection.TryUnwrap(out RectangleF rect)) { return; }
if (!PlayerInput.PrimaryMouseButtonHeld())
{
selection = Option.None;
RectangleF selectionRect = Submarine.AbsRectF(rect.Location, rect.Size);
float treshold = 12f / camera.Zoom;
if (selectionRect.Size.X < treshold || selectionRect.Size.Y < treshold) { return; }
CircuitBox.SelectComponents(MouseSnapshotHandler.Nodes.Where(n => selectionRect.Intersects(n.Rect)).ToImmutableHashSet(), !PlayerInput.IsShiftDown());
}
else
{
RectangleF oldRect = rect;
rect.Size = camera.ScreenToWorld(PlayerInput.MousePosition) - rect.Location;
if (rect.Equals(oldRect)) { return; }
selection = Option.Some(rect);
}
}
private void TrySelectComponentsUnderCursor()
{
CircuitBoxNode? foundNode = GetTopmostNode(MouseSnapshotHandler.GetLastComponentsUnderCursor());
CircuitBox.SelectComponents(foundNode is null ? ImmutableArray<CircuitBoxNode>.Empty : ImmutableArray.Create(foundNode), !PlayerInput.IsShiftDown());
}
private void OpenContextMenu()
{
var wireOption = MouseSnapshotHandler.FindWireUnderCursor(cursorPos);
var wireSelection = CircuitBox.Wires.Where(static w => w.IsSelectedByMe).ToImmutableArray();
var nodeOption = GetTopmostNode(MouseSnapshotHandler.FindNodesUnderCursor(cursorPos));
var nodeSelection = CircuitBox.Components.Where(static n => n.IsSelectedByMe).ToImmutableArray();
var option = new ContextMenuOption(TextManager.Get("delete"), isEnabled: wireOption.IsSome() || nodeOption is CircuitBoxComponent, () =>
{
if (wireOption.TryUnwrap(out var wire))
{
CircuitBox.RemoveWires(wire.IsSelected ? wireSelection : ImmutableArray.Create(wire));
}
if (nodeOption is CircuitBoxComponent node)
{
CircuitBox.RemoveComponents(node.IsSelected ? nodeSelection : ImmutableArray.Create(node));
}
});
// show component name in the header to better indicate what is about to be deleted
if (nodeOption is CircuitBoxComponent comp)
{
GUIContextMenu.CreateContextMenu(PlayerInput.MousePosition, comp.Item.Name, comp.Item.Prefab.SignalComponentColor, option);
return;
}
// also check if a wire is being deleted
if (wireOption.TryUnwrap(out var foundWire))
{
GUIContextMenu.CreateContextMenu(PlayerInput.MousePosition, foundWire.UsedItemPrefab.Name, foundWire.Color, option);
return;
}
GUIContextMenu.CreateContextMenu(option);
}
public CircuitBoxNode? GetTopmostNode(ImmutableHashSet<CircuitBoxNode> nodes)
{
CircuitBoxNode? foundNode = null;
var allNodes = MouseSnapshotHandler.Nodes.ToImmutableArray();
for (int i = allNodes.Length - 1; i >= 0; i--)
{
CircuitBoxNode node = allNodes[i];
if (nodes.Contains(node))
{
foundNode = node;
break;
}
}
return foundNode;
}
public void AddToGUIUpdateList()
{
toggleMenuButton?.AddToGUIUpdateList();
selectedWireFrame?.AddToGUIUpdateList();
}
}
}

View File

@@ -0,0 +1,11 @@
#nullable enable
namespace Barotrauma
{
internal partial class CircuitBoxWire
{
public CircuitBoxWireRenderer Renderer;
public void Update() => Renderer.Recompute(From.AnchorPoint, To.AnchorPoint, Color);
}
}

View File

@@ -0,0 +1,271 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma
{
internal class CircuitBoxWireRenderer
{
private const int VertsPerQuad = 4, // how many points per quad
QuadsPerLine = 10, // how many quads per line
VertsPerLine = QuadsPerLine * VertsPerQuad, // how many points we need to draw all the quads for a single line
TotalVertsPerWire = VertsPerLine * 2; // we are drawing 2 lines
private readonly Texture2D texture;
private VertexPositionColorTexture[] verts = new VertexPositionColorTexture[TotalVertsPerWire];
private readonly Vector2[][] colliders = new Vector2[2][];
private SquareLine skeleton;
private Vector2 lastStart, lastEnd;
private Color lastColor;
private readonly Option<CircuitBoxWire> wire;
public CircuitBoxWireRenderer(Option<CircuitBoxWire> wire, Vector2 start, Vector2 end, Color color, Sprite? wireSprite)
{
this.wire = wire;
texture = wireSprite?.Texture ?? GUI.WhiteTexture;
Recompute(start, end, color);
}
private void UpdateColor(Color color)
{
for (int i = 0; i < TotalVertsPerWire; i++)
{
verts[i].Color = color;
}
lastColor = color;
}
public void Recompute(Vector2 start, Vector2 end, Color color)
{
if (MathUtils.NearlyEqual(lastStart, start) && MathUtils.NearlyEqual(lastEnd, end))
{
if (lastColor == color) { return; }
UpdateColor(color);
return;
}
lastStart = start;
lastEnd = end;
lastColor = color;
skeleton = ToolBox.GetSquareLineBetweenPoints(start, end, CircuitBoxSizes.WireKnobLength);
var points = skeleton.Points;
Vector2 centerOfLine = (points[2] + points[3]) / 2f;
ImmutableArray<Vector2> points1 = GetLinePoints(points[1], points[2], centerOfLine),
points2 = GetLinePoints(centerOfLine, points[3], points[4]);
colliders[0] = ConstructQuads(ref verts, 0, points1, color);
colliders[1] = ConstructQuads(ref verts, VertsPerLine, points2, color);
static ImmutableArray<Vector2> GetLinePoints(Vector2 start, Vector2 control, Vector2 end)
{
var points = ImmutableArray.CreateBuilder<Vector2>(QuadsPerLine);
for (int i = 0; i < QuadsPerLine; i++)
{
float t = (float)i / (QuadsPerLine - 1);
Vector2 pos = MathUtils.Bezier(start, control, end, t);
points.Add(pos);
}
return points.ToImmutable();
}
static Vector2[] ConstructQuads(ref VertexPositionColorTexture[] verts, int startOffset, IReadOnlyList<Vector2> points, Color color)
{
// ok I don't know why this needs to be one quad less, maybe we are drawing with only 9 quads lol
var collider = new Vector2[VertsPerLine - VertsPerQuad];
int leftIndex = collider.Length - 1,
rightIndex = 0;
// we need to calculate half of the width since the way we expand the quads from origin, otherwise the line will be twice as wide
const float halfWidth = CircuitBoxSizes.WireWidth / 2f;
// draw the line using quads
for (int i = 0; i < points.Count - 1; i++)
{
bool isFirst = i == 0 && startOffset == 0,
isLast = i == points.Count - 2 && startOffset > 0;
Vector2 start = points[i],
end = points[i + 1];
Vector2 dir = Vector2.Normalize(end - start);
Vector2 length = new Vector2(dir.Y, -dir.X) * halfWidth;
int vertIndex = startOffset + i * 4;
Vector2 topRight = end + length;
Vector2 topLeft = end - length;
Vector2 bottomRight;
Vector2 bottomLeft;
// get previous points if any
int prevIndex = vertIndex - 4;
if ((prevIndex - startOffset) >= 0)
{
// connect the previous "upper" corners into the current "lower" corners to stitch the line together
Vector3 prevTopRight = verts[TopRight(prevIndex)].Position,
prevTopLeft = verts[TopLeft(prevIndex)].Position;
bottomRight = ToVector2(prevTopRight);
bottomLeft = ToVector2(prevTopLeft);
}
else
{
bottomRight = start + length;
bottomLeft = start - length;
}
if (isFirst)
{
if (MathF.Abs(dir.Y) > MathF.Abs(dir.X))
{
float offset = dir.Y < 0 ? halfWidth : -halfWidth;
// if the line is more vertical than horizontal, we want to move the bottom corners to the left
bottomRight.Y = start.Y - offset;
bottomLeft.Y = start.Y - offset;
}
else
{
// otherwise we want to move the bottom corners to the top
bottomRight.X = start.X;
bottomLeft.X = start.X;
}
}
else if (isLast)
{
if (MathF.Abs(dir.Y) > MathF.Abs(dir.X))
{
float offset = dir.Y < 0 ? halfWidth : -halfWidth;
// if the line is more vertical than horizontal, we want to move the bottom corners to the left
topRight.Y = end.Y + offset;
topLeft.Y = end.Y + offset;
}
else
{
// otherwise we want to move the bottom corners to the top
topRight.X = end.X;
topLeft.X = end.X;
}
}
collider[rightIndex++] = bottomLeft;
collider[rightIndex++] = topLeft;
collider[leftIndex--] = bottomRight;
collider[leftIndex--] = topRight;
// adjust this if we want sprites to support sourceRects
Vector2 uvTopRight = new Vector2(0, 1),
uvTopLeft = new Vector2(0, 0),
uvBottomRight = new Vector2(1, 1),
uvBottomLeft = new Vector2(1, 0);
SetPos(ref verts, TopRight(vertIndex), topRight, color, uvTopRight);
SetPos(ref verts, TopLeft(vertIndex), topLeft, color, uvTopLeft);
SetPos(ref verts, BottomRight(vertIndex), bottomRight, color, uvBottomRight);
SetPos(ref verts, BottomLeft(vertIndex), bottomLeft, color, uvBottomLeft);
static void SetPos(ref VertexPositionColorTexture[] verts, int index, Vector2 pos, Color color, Vector2 uv)
{
verts[index].Position = ToVector3(pos);
verts[index].Color = color;
verts[index].TextureCoordinate = uv;
static Vector3 ToVector3(Vector2 v) => new Vector3(v.X, v.Y, 0f);
}
static int TopRight(int vertIndex) => vertIndex;
static int TopLeft(int vertIndex) => vertIndex + 1;
static int BottomRight(int vertIndex) => vertIndex + 2;
static int BottomLeft(int vertIndex) => vertIndex + 3;
static Vector2 ToVector2(Vector3 v) => new Vector2(v.X, v.Y);
}
return collider;
}
}
public bool Contains(Vector2 pos)
{
pos.Y = -pos.Y;
foreach (Vector2[] collider in colliders)
{
if (ToolBox.PointIntersectsWithPolygon(pos, collider, checkBoundingBox: false)) { return true; }
}
return false;
}
public void Draw(SpriteBatch spriteBatch, Color selectionColor)
{
if (GameMain.DebugDraw)
{
for (int i = 0; i < skeleton.Points.Length; i++)
{
Vector2 point = skeleton.Points[i];
spriteBatch.DrawPoint(point, Color.White, 25f);
GUI.DrawString(spriteBatch, point - new Vector2(5f, 17f), i.ToString(), Color.Black, font: GUIStyle.LargeFont);
}
spriteBatch.DrawLine(skeleton.Points[0], skeleton.Points[1], GUIStyle.Green, thickness: 2f);
spriteBatch.DrawLine(skeleton.Points[1], skeleton.Points[2], GUIStyle.Green, thickness: 2f);
spriteBatch.DrawLine(skeleton.Points[2], skeleton.Points[3], GUIStyle.Green, thickness: 2f);
spriteBatch.DrawLine(skeleton.Points[3], skeleton.Points[4], GUIStyle.Green, thickness: 2f);
spriteBatch.DrawLine(skeleton.Points[4], skeleton.Points[5], GUIStyle.Green, thickness: 2f);
}
bool isSelected = wire.TryUnwrap(out var w) && w.IsSelected;
if (isSelected)
{
foreach (var colliderPolys in colliders)
{
spriteBatch.DrawPolygon(Vector2.Zero, colliderPolys, selectionColor, 5f);
}
}
spriteBatch.Draw(texture, verts, 0f);
if (skeleton.Type is SquareLine.LineType.SixPointBackwardsLine)
{
// we need to expand the start and end points to make the line look like it's connected to the "smooth" part of the line
Vector2 expandedEnd = skeleton.Points[1],
expandedStart = skeleton.Points[4];
expandedEnd.X += CircuitBoxSizes.WireWidth / 2f;
expandedStart.X -= CircuitBoxSizes.WireWidth / 2f;
spriteBatch.DrawLineWithTexture(texture, skeleton.Points[0], expandedEnd, lastColor, thickness: CircuitBoxSizes.WireWidth);
spriteBatch.DrawLineWithTexture(texture, expandedStart, skeleton.Points[5], lastColor, thickness: CircuitBoxSizes.WireWidth);
const float rectSize = CircuitBoxSizes.WireWidth * 1.5f;
RectangleF startKnob = new RectangleF(skeleton.Points[1] - new Vector2(rectSize / 2f), new Vector2(rectSize)),
endKnob = new RectangleF(skeleton.Points[4] - new Vector2(rectSize / 2f), new Vector2(rectSize));
GUI.DrawFilledRectangle(spriteBatch, startKnob, lastColor);
GUI.DrawFilledRectangle(spriteBatch, endKnob, lastColor);
}
if (!GameMain.DebugDraw) { return; }
foreach (var colliderPolys in colliders)
{
spriteBatch.DrawPolygonInner(Vector2.Zero, colliderPolys, Color.Lime, 1f);
}
}
}
}

View File

@@ -315,7 +315,7 @@ namespace Barotrauma
else
{
var textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), listBox.Content.RectTransform),
msg.Text, font: GUIStyle.SmallFont, wrap: true)
RichString.Rich(msg.Text), font: GUIStyle.SmallFont, wrap: true)
{
CanBeFocused = false,
TextColor = msg.Color
@@ -403,7 +403,7 @@ namespace Barotrauma
{
if (Screen.Selected != GameMain.SubEditorScreen) return;
if (MapEntity.mapEntityList.Any(e => e is Hull || e is Gap))
if (MapEntity.MapEntityList.Any(e => e is Hull || e is Gap))
{
ShowQuestionPrompt("This submarine already has hulls and/or gaps. This command will delete them. Do you want to continue? Y/N",
(option) =>
@@ -974,7 +974,7 @@ namespace Barotrauma
if (Screen.Selected == GameMain.SubEditorScreen)
{
bool entityFound = false;
foreach (MapEntity entity in MapEntity.mapEntityList)
foreach (MapEntity entity in MapEntity.MapEntityList)
{
if (entity is Item item)
{
@@ -1096,19 +1096,19 @@ namespace Barotrauma
commands.Add(new Command("cleansub", "", (string[] args) =>
{
for (int i = MapEntity.mapEntityList.Count - 1; i >= 0; i--)
for (int i = MapEntity.MapEntityList.Count - 1; i >= 0; i--)
{
MapEntity me = MapEntity.mapEntityList[i];
MapEntity me = MapEntity.MapEntityList[i];
if (me.SimPosition.Length() > 2000.0f)
{
NewMessage("Removed " + me.Name + " (simposition " + me.SimPosition + ")", Color.Orange);
MapEntity.mapEntityList.RemoveAt(i);
MapEntity.MapEntityList.RemoveAt(i);
}
else if (!me.ShouldBeSaved)
{
NewMessage("Removed " + me.Name + " (!ShouldBeSaved)", Color.Orange);
MapEntity.mapEntityList.RemoveAt(i);
MapEntity.MapEntityList.RemoveAt(i);
}
else if (me is Item)
{
@@ -1477,14 +1477,19 @@ namespace Barotrauma
string itemNameOrId = args[0].ToLowerInvariant();
ItemPrefab itemPrefab =
(MapEntityPrefab.Find(itemNameOrId, identifier: null, showErrorMessages: false) ??
MapEntityPrefab.Find(null, identifier: itemNameOrId.ToIdentifier(), showErrorMessages: false)) as ItemPrefab;
(MapEntityPrefab.FindByName(itemNameOrId) ??
MapEntityPrefab.FindByIdentifier(itemNameOrId.ToIdentifier())) as ItemPrefab;
if (itemPrefab == null)
{
NewMessage("Item not found for analyzing.");
return;
}
if (itemPrefab.DefaultPrice == null)
{
NewMessage($"Item \"{itemPrefab.Name}\" is not sellable/purchaseable.");
return;
}
NewMessage("Analyzing item " + itemPrefab.Name + " with base cost " + itemPrefab.DefaultPrice.Price);
var fabricationRecipe = fabricableItems.Find(f => f.TargetItem == itemPrefab);
@@ -1847,6 +1852,12 @@ namespace Barotrauma
}
}
foreach (var eventPrefab in EventPrefab.Prefabs)
{
if (eventPrefab is not TraitorEventPrefab traitorEventPrefab) { continue; }
addIfMissing($"eventname.{traitorEventPrefab.Identifier}".ToIdentifier(), language);
}
foreach (Type itemComponentType in typeof(ItemComponent).Assembly.GetTypes().Where(type => type.IsSubclassOf(typeof(ItemComponent))))
{
checkSerializableEntityType(itemComponentType);
@@ -2346,7 +2357,20 @@ namespace Barotrauma
WaterRenderer.BlurAmount = blurAmount;
}));
commands.Add(new Command("generatelevels", "generatelevels [amount]: generate a bunch of levels with the currently selected parameters in the level editor.", (string[] args) =>
{
if (GameMain.GameSession == null)
{
int amount = 1;
if (args.Length > 0) { int.TryParse(args[0], out amount); }
GameMain.LevelEditorScreen.TestLevelGenerationForErrors(amount);
}
else
{
NewMessage("Can't use the command while round is running.");
}
}));
commands.Add(new Command("refreshrect", "Updates the dimensions of the selected items to match the ones defined in the prefab. Applied only in the subeditor.", (string[] args) =>
{
//TODO: maybe do this automatically during loading when possible?
@@ -2528,8 +2552,11 @@ namespace Barotrauma
HashSet<XDocument> docs = new HashSet<XDocument>();
HashSet<string> textIds = new HashSet<string>();
Dictionary<string, string> existingTexts = new Dictionary<string, string>();
foreach (EventPrefab eventPrefab in EventSet.GetAllEventPrefabs())
{
if (eventPrefab is not TraitorEventPrefab) { continue; }
if (eventPrefab.Identifier.IsEmpty)
{
continue;
@@ -2566,35 +2593,74 @@ namespace Barotrauma
void getTextsFromElement(XElement element, List<string> list, string parentName)
{
string text = element.GetAttributeString("text", null);
string textId = $"EventText.{parentName}";
if (!string.IsNullOrEmpty(text) && !text.Contains("EventText.", StringComparison.OrdinalIgnoreCase))
{
list.Add($"<{textId}>{text}</{textId}>");
element.SetAttributeValue("text", textId);
string textAttribute = "text";
XElement textElement = element;
if (text == null)
{
var subTextElement = element?.Element("Text");
if (subTextElement != null)
{
textAttribute = "tag";
text = subTextElement?.GetAttributeString(textAttribute, null);
textElement = subTextElement;
}
if (text == null)
{
AddWarning("Failed to find text from the element " + element.ToString());
}
}
int i = 1;
string textId = $"EventText.{parentName}";
if (!string.IsNullOrEmpty(text) && !text.Contains("EventText.", StringComparison.OrdinalIgnoreCase))
{
if (existingTexts.TryGetValue(text, out string existingTextId))
{
textElement.SetAttributeValue(textAttribute, existingTextId);
}
else
{
textIds.Add(parentName);
list.Add($"<{textId}>{text}</{textId}>");
existingTexts.Add(text, textId);
textElement.SetAttributeValue(textAttribute, textId);
}
}
int conversationIndex = 1;
int objectiveIndex = 1;
foreach (var subElement in element.Elements())
{
string elementName = parentName;
bool ignore = false;
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "conversationaction":
while (textIds.Contains(parentName+".c"+i))
case "conversationaction":
while (textIds.Contains(elementName + ".c" + conversationIndex))
{
i++;
conversationIndex++;
}
parentName += ".c" + i;
elementName += ".c" + conversationIndex;
break;
case "eventlogaction":
while (textIds.Contains(elementName + ".objective" + objectiveIndex))
{
objectiveIndex++;
}
elementName += ".objective" + objectiveIndex;
break;
case "option":
while (textIds.Contains(parentName.Substring(0, parentName.Length - 3) + ".o" + i))
while (textIds.Contains(elementName.Substring(0, elementName.Length - 3) + ".o" + conversationIndex))
{
i++;
conversationIndex++;
}
parentName = parentName.Substring(0, parentName.Length - 3) + ".o" + i;
elementName = elementName.Substring(0, elementName.Length - 3) + ".o" + conversationIndex;
break;
case "text":
ignore = true;
break;
}
textIds.Add(parentName);
getTextsFromElement(subElement, list, parentName);
if (ignore) { continue; }
getTextsFromElement(subElement, list, elementName);
}
}
}));
@@ -2759,9 +2825,9 @@ namespace Barotrauma
ep.DebugCreateInstance();
}
for (int i = 0; i < MapEntity.mapEntityList.Count; i++)
for (int i = 0; i < MapEntity.MapEntityList.Count; i++)
{
var entity = MapEntity.mapEntityList[i] as ISerializableEntity;
var entity = MapEntity.MapEntityList[i] as ISerializableEntity;
if (entity != null)
{
List<(object obj, SerializableProperty property)> allProperties = new List<(object obj, SerializableProperty property)>();

View File

@@ -42,15 +42,15 @@ namespace Barotrauma
partial void ShowDialog(Character speaker, Character targetCharacter)
{
CreateDialog(Text, speaker, Options.Select(opt => opt.Text), GetEndingOptions(), actionInstance: this, spriteIdentifier: EventSprite, fadeToBlack: FadeToBlack, dialogType: DialogType, continueConversation: ContinueConversation);
CreateDialog(GetDisplayText(), speaker, Options.Select(opt => opt.Text), GetEndingOptions(), actionInstance: this, spriteIdentifier: EventSprite, fadeToBlack: FadeToBlack, dialogType: DialogType, continueConversation: ContinueConversation);
}
public static void CreateDialog(string text, Character speaker, IEnumerable<string> options, int[] closingOptions, string eventSprite, UInt16 actionId, bool fadeToBlack, DialogTypes dialogType, bool continueConversation = false)
public static void CreateDialog(LocalizedString text, Character speaker, IEnumerable<string> options, int[] closingOptions, string eventSprite, UInt16 actionId, bool fadeToBlack, DialogTypes dialogType, bool continueConversation = false)
{
CreateDialog(text, speaker, options, closingOptions, actionInstance: null, actionId: actionId, spriteIdentifier: eventSprite, fadeToBlack: fadeToBlack, dialogType: dialogType, continueConversation: continueConversation);
}
private static void CreateDialog(string text, Character speaker, IEnumerable<string> options, int[] closingOptions, string spriteIdentifier = null,
private static void CreateDialog(LocalizedString text, Character speaker, IEnumerable<string> options, int[] closingOptions, string spriteIdentifier = null,
ConversationAction actionInstance = null, UInt16? actionId = null, bool fadeToBlack = false, DialogTypes dialogType = DialogTypes.Regular, bool continueConversation = false)
{
Debug.Assert(actionInstance == null || actionId == null);
@@ -126,6 +126,7 @@ namespace Barotrauma
if (actionInstance != null)
{
lastActiveAction = actionInstance;
actionInstance.lastActiveTime = Timing.TotalTime;
actionInstance.dialogBox = messageBox;
}
else
@@ -313,7 +314,7 @@ namespace Barotrauma
};
}
private static List<GUIButton> CreateConversation(GUIListBox parentBox, string text, Character speaker, IEnumerable<string> options, bool drawChathead = true)
private static List<GUIButton> CreateConversation(GUIListBox parentBox, LocalizedString text, Character speaker, IEnumerable<string> options, bool drawChathead = true)
{
var content = new GUILayoutGroup(new RectTransform(Vector2.One, parentBox.Content.RectTransform), childAnchor: Anchor.TopLeft, isHorizontal: true)
{
@@ -322,9 +323,11 @@ namespace Barotrauma
AlwaysOverrideCursor = true
};
LocalizedString translatedText = speaker?.DisplayName is not null ?
TextManager.GetWithVariable(text, "[speakername]", speaker?.DisplayName) :
TextManager.Get(text);
LocalizedString translatedText = text.Replace("\\n", "\n");
if (speaker?.DisplayName is not null)
{
translatedText = translatedText.Replace("[speakername]", speaker.DisplayName);
}
translatedText = TextManager.ParseInputTypes(translatedText).Fallback(text);
if (speaker?.Info != null && drawChathead)
@@ -341,7 +344,7 @@ namespace Barotrauma
AbsoluteSpacing = GUI.IntScale(5)
};
var textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), textContent.RectTransform), translatedText, wrap: true)
var textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), textContent.RectTransform), RichString.Rich(translatedText), wrap: true)
{
AlwaysOverrideCursor = true,
UserData = "text"

View File

@@ -0,0 +1,11 @@
#nullable enable
namespace Barotrauma;
partial class EventLogAction : EventAction
{
partial void AddEntryProjSpecific(EventLog? eventLog, string displayText)
{
eventLog?.AddEntry(ParentEvent.Prefab.Identifier, Id, displayText);
}
}

View File

@@ -0,0 +1,66 @@
#nullable enable
namespace Barotrauma;
partial class EventObjectiveAction : EventAction
{
public static void Trigger(
SegmentActionType Type,
Identifier Identifier,
Identifier ObjectiveTag,
Identifier ParentObjectiveId,
Identifier TextTag,
bool CanBeCompleted,
bool autoPlayVideo = false,
string videoFile = "",
int width = 450,
int height = 80)
{
ObjectiveManager.Segment? segment = null;
// Only need to create the segment when it's being triggered (otherwise the tutorial already has the segment instance)
if (Type == SegmentActionType.Trigger)
{
segment = ObjectiveManager.Segment.CreateInfoBoxSegment(Identifier, ObjectiveTag, autoPlayVideo ? Tutorials.AutoPlayVideo.Yes : Tutorials.AutoPlayVideo.No,
new ObjectiveManager.Segment.Text(TextTag, width, height, Anchor.Center),
new ObjectiveManager.Segment.Video(videoFile, TextTag, width, height));
}
else if (Type == SegmentActionType.Add)
{
segment = ObjectiveManager.Segment.CreateObjectiveSegment(Identifier, !ObjectiveTag.IsEmpty ? ObjectiveTag : Identifier);
}
if (segment is not null)
{
segment.CanBeCompleted = CanBeCompleted;
segment.ParentId = ParentObjectiveId;
}
switch (Type)
{
case SegmentActionType.Trigger:
case SegmentActionType.Add:
ObjectiveManager.TriggerSegment(segment);
break;
case SegmentActionType.Complete:
ObjectiveManager.CompleteSegment(Identifier);
break;
case SegmentActionType.Remove:
ObjectiveManager.RemoveSegment(Identifier);
break;
case SegmentActionType.CompleteAndRemove:
ObjectiveManager.CompleteSegment(Identifier);
ObjectiveManager.RemoveSegment(Identifier);
break;
case SegmentActionType.Fail:
ObjectiveManager.FailSegment(Identifier);
break;
case SegmentActionType.FailAndRemove:
ObjectiveManager.FailSegment(Identifier);
ObjectiveManager.RemoveSegment(Identifier);
break;
}
}
partial void UpdateProjSpecific()
{
Trigger(Type, Identifier, ObjectiveTag, ParentObjectiveId, TextTag, CanBeCompleted, AutoPlayVideo, VideoFile, Width, Height);
}
}

View File

@@ -17,7 +17,7 @@ partial class MessageBoxAction : EventAction
var segment = ObjectiveManager.Segment.CreateMessageBoxSegment(id, ObjectiveTag, CreateMessageBox);
segment.CanBeCompleted = ObjectiveCanBeCompleted;
segment.ParentId = ParentObjectiveId;
ObjectiveManager.TriggerTutorialSegment(segment, connectObjective: Type == ActionType.ConnectObjective);
ObjectiveManager.TriggerSegment(segment, connectObjective: Type == ActionType.ConnectObjective);
}
}
else if (Type == ActionType.Close)

View File

@@ -1,43 +0,0 @@
namespace Barotrauma;
partial class TutorialSegmentAction : EventAction
{
private ObjectiveManager.Segment segment;
partial void UpdateProjSpecific()
{
// Only need to create the segment when it's being triggered (otherwise the tutorial already has the segment instance)
if (Type == SegmentActionType.Trigger)
{
segment = ObjectiveManager.Segment.CreateInfoBoxSegment(Identifier, ObjectiveTag, AutoPlayVideo ? Tutorials.AutoPlayVideo.Yes : Tutorials.AutoPlayVideo.No,
new ObjectiveManager.Segment.Text(TextTag, Width, Height, Anchor.Center),
new ObjectiveManager.Segment.Video(VideoFile, TextTag, Width, Height));
}
else if (Type == SegmentActionType.Add)
{
segment = ObjectiveManager.Segment.CreateObjectiveSegment(Identifier, !ObjectiveTag.IsEmpty ? ObjectiveTag : Identifier);
}
if (segment is not null)
{
segment.CanBeCompleted = CanBeCompleted;
segment.ParentId = ParentObjectiveId;
}
switch (Type)
{
case SegmentActionType.Trigger:
case SegmentActionType.Add:
ObjectiveManager.TriggerTutorialSegment(segment);
break;
case SegmentActionType.Complete:
ObjectiveManager.CompleteTutorialSegment(Identifier);
break;
case SegmentActionType.Remove:
ObjectiveManager.RemoveTutorialSegment(Identifier);
break;
case SegmentActionType.CompleteAndRemove:
ObjectiveManager.CompleteTutorialSegment(Identifier);
ObjectiveManager.RemoveTutorialSegment(Identifier);
break;
}
}
}

View File

@@ -0,0 +1,61 @@
#nullable enable
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma;
partial class EventLog
{
public bool UnreadEntries { get; private set; }
public void AddEntry(Identifier eventPrefabId, Identifier entryId, string text)
{
TryAddEntryInternal(eventPrefabId, entryId, text);
GameMain.GameSession?.EnableEventLogNotificationIcon(enabled: true);
UnreadEntries = true;
}
public void CreateEventLogUI(GUIComponent parent, TraitorManager.TraitorResults? traitorResults = null)
{
UnreadEntries = false;
int spacing = GUI.IntScale(5);
foreach (var ev in events.Values)
{
LocalizedString nameString = string.Empty;
int difficultyIconCount = 0;
EventPrefab.Prefabs.TryGet(ev.EventIdentifier, out EventPrefab? eventPrefab);
if (eventPrefab is not null)
{
nameString = RichString.Rich(eventPrefab.Name);
if (eventPrefab is TraitorEventPrefab traitorEventPrefab)
{
difficultyIconCount = traitorEventPrefab.DangerLevel;
}
}
var textContent = new List<LocalizedString>();
textContent.AddRange(ev.Entries.Select(e => (LocalizedString)e.Text));
var icon = GUIStyle.GetComponentStyle("TraitorMissionIcon")?.GetDefaultSprite();
RoundSummary.CreateMissionEntry(
parent,
nameString,
textContent,
difficultyIconCount,
icon, GUIStyle.Red,
out GUIImage missionIcon);
if (traitorResults != null &&
traitorResults.Value.TraitorEventIdentifier == ev.EventIdentifier)
{
RoundSummary.UpdateMissionStateIcon(traitorResults.Value.ObjectiveSuccessful, missionIcon);
}
}
}
}

View File

@@ -413,23 +413,9 @@ namespace Barotrauma
private Rectangle DrawScriptedEvent(SpriteBatch spriteBatch, ScriptedEvent scriptedEvent, Rectangle? parentRect = null)
{
EventAction? currentEvent = !scriptedEvent.IsFinished ? scriptedEvent.Actions[scriptedEvent.CurrentActionIndex] : null;
List<DebugLine> positions = new List<DebugLine>();
string text = $"Finished: {scriptedEvent.IsFinished.ColorizeObject()}\n" +
$"Action index: {scriptedEvent.CurrentActionIndex.ColorizeObject()}\n" +
$"Current action: {currentEvent?.ToDebugString() ?? ToolBox.ColorizeObject(null)}\n";
text += "All actions:\n";
text += FindActions(scriptedEvent).Aggregate(string.Empty, (current, action) => current + $"{new string(' ', action.Item1 * 6)}{action.Item2.ToDebugString()}\n");
text += "Targets:\n";
foreach (var (key, value) in scriptedEvent.Targets)
{
text += $" {key.ColorizeObject()}: {value.Aggregate(string.Empty, (current, entity) => current + $"{entity.ColorizeObject()} ")}\n";
}
string text = scriptedEvent.GetDebugInfo();
if (scriptedEvent.Targets != null)
{
foreach ((_, List<Entity> entities) in scriptedEvent.Targets)
@@ -452,10 +438,7 @@ namespace Barotrauma
{
debugPositions.Clear();
string text = $"Finished: {artifactEvent.IsFinished.ColorizeObject()}\n" +
$"Item: {artifactEvent.Item.ColorizeObject()}\n" +
$"Spawn pending: {artifactEvent.SpawnPending.ColorizeObject()}\n" +
$"Spawn position: {artifactEvent.SpawnPos.ColorizeObject()}\n";
string text = artifactEvent.GetDebugInfo();
if (artifactEvent.Item != null && !artifactEvent.Item.Removed)
{
@@ -470,10 +453,7 @@ namespace Barotrauma
{
debugPositions.Clear();
string text = $"Finished: {monsterEvent.IsFinished.ColorizeObject()}\n" +
$"Amount: {monsterEvent.MinAmount.ColorizeObject()} - {monsterEvent.MaxAmount.ColorizeObject()}\n" +
$"Spawn pending: {monsterEvent.SpawnPending.ColorizeObject()}\n" +
$"Spawn position: {monsterEvent.SpawnPos.ColorizeObject()}\n";
string text = monsterEvent.GetDebugInfo();
if (monsterEvent.SpawnPos != null && Submarine.MainSub != null)
{
@@ -712,7 +692,30 @@ namespace Barotrauma
}
}
break;
case NetworkEventType.EVENTLOG:
ClientReadEventLog(GameMain.Client, msg);
break;
case NetworkEventType.EVENTOBJECTIVE:
ClientReadEventObjective(GameMain.Client, msg);
break;
}
}
private void ClientReadEventLog(GameClient client, IReadMessage msg)
{
NetEventLogEntry entry = INetSerializableStruct.Read<NetEventLogEntry>(msg);
EventLog.AddEntry(entry.EventPrefabId, entry.LogEntryId, entry.Text.Replace("\\n", "\n"));
}
private static void ClientReadEventObjective(GameClient client, IReadMessage msg)
{
NetEventObjective entry = INetSerializableStruct.Read<NetEventObjective>(msg);
EventObjectiveAction.Trigger(
entry.Type,
entry.Identifier,
entry.ObjectiveTag,
entry.ParentObjectiveId,
entry.TextTag,
entry.CanBeCompleted);
}
}
}

View File

@@ -1,4 +1,5 @@
using Barotrauma.Items.Components;
using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using Barotrauma.Networking;
using System.Collections.Generic;
@@ -9,22 +10,37 @@ namespace Barotrauma
public override bool DisplayAsCompleted => false;
public override bool DisplayAsFailed => false;
public override int State
{
get => base.State;
set
{
base.State = value;
if (base.State > 0)
{
caves.ForEach(c => c.MissionsToDisplayOnSonar.Remove(this));
}
}
}
public override void ClientReadInitial(IReadMessage msg)
{
base.ClientReadInitial(msg);
byte caveCount = msg.ReadByte();
for (int i = 0; i < caveCount; i++)
{
byte selectedCave = msg.ReadByte();
if (selectedCave < 255 && Level.Loaded != null)
byte selectedCaveIndex = msg.ReadByte();
if (selectedCaveIndex < 255 && Level.Loaded != null)
{
if (selectedCave < Level.Loaded.Caves.Count)
if (selectedCaveIndex < Level.Loaded.Caves.Count)
{
Level.Loaded.Caves[selectedCave].DisplayOnSonar = true;
var selectedCave = Level.Loaded.Caves[selectedCaveIndex];
selectedCave.MissionsToDisplayOnSonar.Add(this);
caves.Add(selectedCave);
}
else
{
DebugConsole.ThrowError($"Cave index out of bounds when reading nest mission data. Index: {selectedCave}, number of caves: {Level.Loaded.Caves.Count}");
DebugConsole.ThrowError($"Cave index out of bounds when reading nest mission data. Index: {selectedCaveIndex}, number of caves: {Level.Loaded.Caves.Count}");
}
}
}

View File

@@ -4,6 +4,7 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using static Barotrauma.MissionPrefab;
namespace Barotrauma
{
@@ -27,7 +28,11 @@ namespace Barotrauma
public Color GetDifficultyColor()
{
int v = Difficulty ?? MissionPrefab.MinDifficulty;
return GetDifficultyColor(Difficulty ?? MissionPrefab.MinDifficulty);
}
public static Color GetDifficultyColor(int difficulty)
{
int v = difficulty;
float t = MathUtils.InverseLerp(MissionPrefab.MinDifficulty, MissionPrefab.MaxDifficulty, v);
return ToolBox.GradientLerp(t, GUIStyle.Green, GUIStyle.Orange, GUIStyle.Red);
}
@@ -61,36 +66,48 @@ namespace Barotrauma
List<LocalizedString> reputationRewardTexts = new List<LocalizedString>();
foreach (var reputationReward in ReputationRewards)
{
FactionPrefab targetFactionPrefab;
if (reputationReward.Key == "location" )
FactionPrefab factionPrefab;
if (reputationReward.FactionIdentifier == "location" )
{
targetFactionPrefab = OriginLocation.Faction?.Prefab;
factionPrefab = OriginLocation.Faction?.Prefab;
}
else
{
FactionPrefab.Prefabs.TryGet(reputationReward.Key, out targetFactionPrefab);
}
if (targetFactionPrefab == null)
{
return string.Empty;
FactionPrefab.Prefabs.TryGet(reputationReward.FactionIdentifier, out factionPrefab);
}
float totalReputationChange = reputationReward.Value;
if (GameMain.GameSession?.Campaign?.Factions.Find(f => f.Prefab == targetFactionPrefab) is Faction faction)
if (factionPrefab != null)
{
totalReputationChange = reputationReward.Value * faction.Reputation.GetReputationChangeMultiplier(reputationReward.Value);
AddReputationText(factionPrefab, reputationReward.Amount);
if (!MathUtils.NearlyEqual(reputationReward.AmountForOpposingFaction, 0.0f) &&
FactionPrefab.Prefabs.TryGet(factionPrefab.OpposingFaction, out var opposingFactionPrefab))
{
AddReputationText(opposingFactionPrefab, reputationReward.AmountForOpposingFaction);
}
}
}
void AddReputationText(FactionPrefab factionPrefab, float amount)
{
if (factionPrefab == null) { return; }
float totalReputationChange = amount;
if (GameMain.GameSession?.Campaign?.Factions.Find(f => f.Prefab == factionPrefab) is Faction faction)
{
totalReputationChange = amount * faction.Reputation.GetReputationChangeMultiplier(amount);
}
LocalizedString name = $"‖color:{XMLExtensions.ToStringHex(targetFactionPrefab.IconColor)}‖{targetFactionPrefab.Name}‖end‖";
LocalizedString name = $"‖color:{XMLExtensions.ToStringHex(factionPrefab.IconColor)}‖{factionPrefab.Name}‖end‖";
float normalizedValue = MathUtils.InverseLerp(-100.0f, 100.0f, totalReputationChange);
string formattedValue = ((int)Math.Round(totalReputationChange)).ToString("+#;-#;0"); //force plus sign for positive numbers
LocalizedString rewardText = TextManager.GetWithVariables(
"reputationformat",
("[reputationname]", name),
("[reputationvalue]", $"‖color:{XMLExtensions.ToStringHex(Reputation.GetReputationColor(normalizedValue))}‖{formattedValue}‖end‖" ));
("[reputationvalue]", $"‖color:{XMLExtensions.ToStringHex(Reputation.GetReputationColor(normalizedValue))}‖{formattedValue}‖end‖"));
reputationRewardTexts.Add(rewardText.Value);
}
if (reputationRewardTexts.Any())
{
return RichString.Rich(TextManager.AddPunctuation(':', TextManager.Get("reputation"), LocalizedString.Join(", ", reputationRewardTexts)));
@@ -100,6 +117,20 @@ namespace Barotrauma
return string.Empty;
}
}
partial void DistributeExperienceToCrew(IEnumerable<Character> crew, int experienceGain)
{
foreach (Character character in crew)
{
GiveMissionExperience(character.Info);
}
void GiveMissionExperience(CharacterInfo info)
{
if (info == null) { return; }
var experienceGainMultiplierIndividual = new AbilityMissionExperienceGainMultiplier(this, 1f);
info.Character?.CheckTalents(AbilityEffectType.OnGainMissionExperience, experienceGainMultiplierIndividual);
info.GiveExperience((int)(experienceGain * experienceGainMultiplierIndividual.Value));
}
}
partial void ShowMessageProjSpecific(int missionState)
{

View File

@@ -9,6 +9,19 @@ namespace Barotrauma
public override bool DisplayAsCompleted => State > 0 && !requireDelivery;
public override bool DisplayAsFailed => false;
public override int State
{
get => base.State;
set
{
base.State = value;
if (base.State > 0 && selectedCave != null)
{
selectedCave.MissionsToDisplayOnSonar.Remove(this);
}
}
}
public override void ClientReadInitial(IReadMessage msg)
{
base.ClientReadInitial(msg);
@@ -20,8 +33,9 @@ namespace Barotrauma
{
if (selectedCaveIndex < Level.Loaded.Caves.Count)
{
Level.Loaded.Caves[selectedCaveIndex].DisplayOnSonar = true;
SpawnNestObjects(Level.Loaded, Level.Loaded.Caves[selectedCaveIndex]);
selectedCave = Level.Loaded.Caves[selectedCaveIndex];
selectedCave.MissionsToDisplayOnSonar.Add(this);
SpawnNestObjects(Level.Loaded, selectedCave);
}
else
{

View File

@@ -99,10 +99,10 @@ namespace Barotrauma
}))
.Aggregate(TextManager.SpeciallyHandledCharCategory.None, (current, category) => current | category);
public ScalableFont(ContentXElement element, GraphicsDevice gd = null)
public ScalableFont(ContentXElement element, uint defaultSize = 14, GraphicsDevice gd = null)
: this(
element.GetAttributeContentPath("file")?.Value,
(uint)element.GetAttributeInt("size", 14),
(uint)element.GetAttributeInt("size", (int)defaultSize),
gd,
element.GetAttributeBool("dynamicloading", false),
ExtractShccFromXElement(element))

View File

@@ -38,6 +38,7 @@ namespace Barotrauma
private float prevUIScale;
private readonly GUIFrame channelSettingsFrame;
private readonly GUITextBlock radioJammedWarning;
private readonly GUITextBox channelText;
private readonly GUILayoutGroup channelPickerContent;
private readonly GUIButton memButton;
@@ -107,6 +108,13 @@ namespace Barotrauma
RelativeSpacing = 0.01f
};
radioJammedWarning = new GUITextBlock(new RectTransform(Vector2.One, channelSettingsFrame.RectTransform), TextManager.Get("radiojammedwarning"),
textColor: GUIStyle.Orange, color: Color.Black,
textAlignment: Alignment.Center, style: "OuterGlow")
{
ToolTip = TextManager.Get("hint.radiojammed")
};
var buttonLeft = new GUIButton(new RectTransform(new Vector2(0.1f, 0.8f), channelSettingsContent.RectTransform), style: "DeviceButton")
{
PlaySoundOnSelect = false,
@@ -643,7 +651,7 @@ namespace Barotrauma
ToggleButton.RectTransform.AbsoluteOffset = new Point(GUIFrame.Rect.Right, GUIFrame.Rect.Y + HUDLayoutSettings.ChatBoxArea.Height - ToggleButton.Rect.Height);
}
if (Character.Controlled != null && ChatMessage.CanUseRadio(Character.Controlled, out WifiComponent radio))
if (Character.Controlled != null && ChatMessage.CanUseRadio(Character.Controlled, out WifiComponent radio, ignoreJamming: true))
{
if (prevRadio != radio)
{
@@ -672,10 +680,11 @@ namespace Barotrauma
}
}
channelSettingsFrame.Visible = true;
radioJammedWarning.Visible = radio is { JamTimer: > 0 };
}
else
{
channelSettingsFrame.Visible = false;
radioJammedWarning.Visible = channelSettingsFrame.Visible = false;
channelPickerContent.Children.First().CanBeFocused = true;
channelMemPending = false;
memButton.Enabled = true;

View File

@@ -756,7 +756,7 @@ namespace Barotrauma
private bool CreateRenamingComponent(GUIButton button, object userData)
{
if (!HasPermission || !(userData is CharacterInfo characterInfo)) { return false; }
if (!HasPermission || userData is not CharacterInfo characterInfo) { return false; }
var outerGlowFrame = new GUIFrame(new RectTransform(new Vector2(1.25f, 1.25f), parentComponent.RectTransform, Anchor.Center),
style: "OuterGlow", color: Color.Black * 0.7f);
var frame = new GUIFrame(new RectTransform(new Vector2(0.33f, 0.4f), outerGlowFrame.RectTransform, anchor: Anchor.Center)
@@ -843,7 +843,7 @@ namespace Barotrauma
private bool FireCharacter(GUIButton button, object selection)
{
if (!(selection is CharacterInfo characterInfo)) { return false; }
if (selection is not CharacterInfo characterInfo) { return false; }
campaign.CrewManager.FireCharacter(characterInfo);
SelectCharacter(null, null, null);

View File

@@ -161,7 +161,7 @@ namespace Barotrauma
return 1;
}
return string.Compare(file1, file2);
return string.Compare(file1, file2, StringComparison.OrdinalIgnoreCase);
}
private static void InitIfNecessary()
@@ -419,6 +419,8 @@ namespace Barotrauma
};
}
fileList.Content.RectTransform.SortChildren(SortFiles);
directoryBox!.Text = currentDirectory;
fileBox!.Text = "";
fileList.Deselect();

View File

@@ -1010,16 +1010,13 @@ namespace Barotrauma
// Sub editor drag and highlight
case SubEditorScreen editor:
{
foreach (var mapEntity in MapEntity.mapEntityList)
if (MapEntity.StartMovingPos != Vector2.Zero || MapEntity.Resizing)
{
if (MapEntity.StartMovingPos != Vector2.Zero)
{
return CursorState.Dragging;
}
if (mapEntity.IsHighlighted)
{
return CursorState.Hand;
}
return CursorState.Dragging;
}
if (MapEntity.HighlightedEntities.Any(h => !h.IsSelected))
{
return CursorState.Hand;
}
break;
}
@@ -2442,8 +2439,7 @@ namespace Barotrauma
var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.7f, 0.8f), pauseMenuInner.RectTransform, Anchor.BottomCenter) { RelativeOffset = new Vector2(0.0f, padding) })
{
Stretch = true,
RelativeSpacing = 0.05f
AbsoluteSpacing = IntScale(15)
};
new GUIButton(new RectTransform(new Vector2(0.1f, 0.07f), pauseMenuInner.RectTransform, Anchor.TopRight) { RelativeOffset = new Vector2(padding) },
@@ -2526,7 +2522,7 @@ namespace Barotrauma
pauseMenuInner.RectTransform.MinSize = new Point(
pauseMenuInner.RectTransform.MinSize.X,
Math.Max(
(int)(buttonContainer.Children.Sum(c => c.Rect.Height + buttonContainer.Rect.Height * buttonContainer.RelativeSpacing)),
(int)(buttonContainer.Children.Sum(c => c.Rect.Height + buttonContainer.AbsoluteSpacing) / buttonContainer.RectTransform.RelativeSize.Y),
pauseMenuInner.RectTransform.MinSize.X));
}

View File

@@ -781,7 +781,7 @@ namespace Barotrauma
if (toolTipBlock.Rect.Right > GameMain.GraphicsWidth - 10)
{
toolTipBlock.RectTransform.AbsoluteOffset -= new Point(toolTipBlock.Rect.Width, 0);
toolTipBlock.RectTransform.AbsoluteOffset -= new Point(toolTipBlock.Rect.Width + targetElement.Width, 0);
}
if (toolTipBlock.Rect.Bottom > GameMain.GraphicsHeight - 10)
{

View File

@@ -49,7 +49,7 @@ namespace Barotrauma
get { return lifeTime; }
}
public ScalableFont Font
public GUIFont Font
{
get;
private set;
@@ -69,7 +69,7 @@ namespace Barotrauma
}
}
public GUIMessage(string text, Color color, float lifeTime, ScalableFont font = null)
public GUIMessage(string text, Color color, float lifeTime, GUIFont font = null)
{
coloredText = new ColoredText(text, color, false, false);
this.lifeTime = lifeTime;
@@ -81,7 +81,7 @@ namespace Barotrauma
Font = font;
}
public GUIMessage(string text, Color color, Vector2 position, Vector2 velocity, float lifeTime, Alignment textAlignment = Alignment.Center, ScalableFont font = null, Submarine sub = null)
public GUIMessage(string text, Color color, Vector2 position, Vector2 velocity, float lifeTime, Alignment textAlignment = Alignment.Center, GUIFont font = null, Submarine sub = null)
{
coloredText = new ColoredText(text, color, false, false);
WorldSpace = true;

View File

@@ -693,5 +693,52 @@ namespace Barotrauma
rectT.Parent = RectTransform;
Buttons.Add(new GUIButton(rectT, text) { OnClicked = onClick });
}
public static GUIMessageBox CreateLoadingBox(LocalizedString text, (LocalizedString Label, Action<GUIMessageBox> Action)[] buttons = null, Vector2? relativeSize = null)
{
buttons ??= Array.Empty<(LocalizedString Label, Action<GUIMessageBox> Action)>();
var relativeSizeFallback = relativeSize ?? (0.7f, 0.5f);
var newMessageBox = new GUIMessageBox(
headerText: "",
text: "",
relativeSize: relativeSizeFallback,
buttons: buttons.Select(b => b.Label).ToArray());
newMessageBox.InnerFrame.RectTransform.ScaleBasis = ScaleBasis.BothHeight;
for (int i = 0; i < buttons.Length; i++)
{
var capturedIndex = i;
newMessageBox.Buttons[i].OnClicked = (_, _) =>
{
buttons[capturedIndex].Action(newMessageBox);
return false;
};
}
const float throbberSize = 0.25f;
new GUITextBlock(
new RectTransform((0.9f, 0f), newMessageBox.InnerFrame.RectTransform, Anchor.Center, Pivot.BottomCenter) { RelativeOffset = (0f, -throbberSize * 0.5f) },
text: text, textAlignment: Alignment.Center, wrap: true);
// Throbber
new GUICustomComponent(
new RectTransform(Vector2.One * throbberSize, newMessageBox.InnerFrame.RectTransform, Anchor.Center, scaleBasis: ScaleBasis.BothHeight),
onDraw: static (sb, component) =>
{
GUIStyle.GenericThrobber.Draw(
sb,
spriteIndex: (int)(Timing.TotalTime * 20f) % GUIStyle.GenericThrobber.FrameCount,
pos: component.Rect.Center.ToVector2(),
color: Color.White,
origin: GUIStyle.GenericThrobber.FrameSize.ToVector2() * 0.5f,
rotate: 0f,
scale: component.Rect.Size.ToVector2() / GUIStyle.GenericThrobber.FrameSize.ToVector2());
});
MessageBoxes.Remove(newMessageBox);
MessageBoxes.Insert(0, newMessageBox);
return newMessageBox;
}
}
}

View File

@@ -132,7 +132,7 @@ namespace Barotrauma
if (subElement.NameAsIdentifier() != "override") { continue; }
if (ScalableFont.ExtractShccFromXElement(subElement).HasFlag(flag))
{
return new ScalableFont(subElement, GameMain.Instance.GraphicsDevice);
return new ScalableFont(subElement, font.Size, GameMain.Instance.GraphicsDevice);
}
}

View File

@@ -4,7 +4,7 @@ using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma
{
public class GUIScissorComponent: GUIComponent
public sealed class GUIScissorComponent : GUIComponent
{
public GUIComponent Content;
@@ -14,19 +14,14 @@ namespace Barotrauma
{
CanBeFocused = false
};
rectT.ChildrenChanged += CheckForChildren;
}
protected override void Update(float deltaTime)
private void CheckForChildren(RectTransform rectT)
{
base.Update(deltaTime);
foreach (GUIComponent child in Children)
{
if (child == Content) { continue; }
throw new InvalidOperationException($"Children were found in {nameof(GUIScissorComponent)}, Add them to {nameof(GUIScissorComponent)}.{nameof(Content)} instead.");
}
ClampChildMouseRects(Content);
if (rectT == Content.RectTransform) { return; }
throw new InvalidOperationException($"Children were found in {nameof(GUIScissorComponent)}, Add them to {nameof(GUIScissorComponent)}.{nameof(Content)} instead.");
}
public override void DrawChildren(SpriteBatch spriteBatch, bool recursive)
@@ -57,7 +52,13 @@ namespace Barotrauma
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: prevRasterizerState);
}
private void ClampChildMouseRects(GUIComponent child)
protected override void Update(float deltaTime)
{
base.Update(deltaTime);
ClampChildMouseRects(Content);
}
private static void ClampChildMouseRects(GUIComponent child)
{
child.ClampMouseRectToParent = true;

View File

@@ -142,11 +142,13 @@ namespace Barotrauma
public readonly static GUIColor HealthBarColorHigh = new GUIColor("HealthBarColorHigh");
public readonly static GUIColor HealthBarColorPoisoned = new GUIColor("HealthBarColorPoisoned");
private readonly static Point defaultItemFrameMargin = new Point(50, 56);
public static Point ItemFrameMargin
{
get
{
Point size = new Point(50, 56).Multiply(GUI.SlicedSpriteScale);
Point size = defaultItemFrameMargin.Multiply(GUI.SlicedSpriteScale);
var style = GetComponentStyle("ItemUI");
var sprite = style?.Sprites[GUIComponent.ComponentState.None].First();
@@ -159,6 +161,16 @@ namespace Barotrauma
}
}
public static int ItemFrameTopBarHeight
{
get
{
var style = GetComponentStyle("ItemUI");
var sprite = style?.Sprites[GUIComponent.ComponentState.None].First();
return (int)Math.Min(sprite?.Slices[0].Height ?? 0, defaultItemFrameMargin.Y / 2 * GUI.SlicedSpriteScale);
}
}
public static Point ItemFrameOffset => new Point(0, 3).Multiply(GUI.SlicedSpriteScale);
public static GUIComponentStyle GetComponentStyle(string styleName)

View File

@@ -12,8 +12,6 @@ namespace Barotrauma
public delegate bool OnSelectedHandler(GUITickBox obj);
public OnSelectedHandler OnSelected;
public static int size = 20;
private GUIRadioButtonGroup radioButtonGroup;
public override bool Selected
@@ -21,21 +19,7 @@ namespace Barotrauma
get { return isSelected; }
set
{
if (value == isSelected) { return; }
if (radioButtonGroup != null && radioButtonGroup.SelectedRadioButton == this)
{
isSelected = true;
return;
}
isSelected = value;
State = isSelected ? ComponentState.Selected : ComponentState.None;
if (value && radioButtonGroup != null)
{
radioButtonGroup.SelectRadioButton(this);
}
OnSelected?.Invoke(this);
SetSelected(value, callOnSelected: true);
}
}
@@ -186,6 +170,27 @@ namespace Barotrauma
text.SetTextPos();
ContentWidth = box.Rect.Width + text.Padding.X + text.TextSize.X + text.Padding.Z;
}
public void SetSelected(bool selected, bool callOnSelected = true)
{
if (selected == isSelected) { return; }
if (radioButtonGroup != null && radioButtonGroup.SelectedRadioButton == this)
{
isSelected = true;
return;
}
isSelected = selected;
State = isSelected ? ComponentState.Selected : ComponentState.None;
if (selected && radioButtonGroup != null)
{
radioButtonGroup.SelectRadioButton(this);
}
if (callOnSelected)
{
OnSelected?.Invoke(this);
}
}
protected override void Update(float deltaTime)
{

View File

@@ -4,12 +4,13 @@ using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
class LoadingScreen
sealed class LoadingScreen
{
private readonly Sprite defaultBackgroundTexture, overlay;
private readonly SpriteSheet decorativeGraph, decorativeMap;
@@ -37,36 +38,16 @@ namespace Barotrauma
}
}
private Queue<PendingSplashScreen> pendingSplashScreens = new Queue<PendingSplashScreen>();
/// <summary>
/// Triplet.first = filepath, Triplet.second = resolution, Triplet.third = audio gain
/// </summary>
public Queue<PendingSplashScreen> PendingSplashScreens
{
get
{
lock (loadMutex)
{
return pendingSplashScreens;
}
}
set
{
lock (loadMutex)
{
pendingSplashScreens = value;
}
}
}
public readonly ConcurrentQueue<PendingSplashScreen> PendingSplashScreens = new ConcurrentQueue<PendingSplashScreen>();
public bool PlayingSplashScreen
{
get
{
lock (loadMutex)
{
return currSplashScreen != null || pendingSplashScreens.Count > 0;
}
return currSplashScreen != null || PendingSplashScreens.Count > 0;
}
}
@@ -76,33 +57,7 @@ namespace Barotrauma
selectedTip = RichString.Rich(tip);
}
private readonly object loadMutex = new object();
private float? loadState;
public float? LoadState
{
get
{
lock (loadMutex)
{
return loadState;
}
}
set
{
lock (loadMutex)
{
loadState = value;
DrawLoadingText = true;
}
}
}
public bool DrawLoadingText
{
get;
set;
}
public float LoadState;
public bool WaitForLanguageSelection
{
@@ -121,7 +76,7 @@ namespace Barotrauma
overlay = new Sprite("Content/UI/MainMenuVignette.png", Vector2.Zero);
noiseSprite = new Sprite("Content/UI/noise.png", Vector2.Zero);
DrawLoadingText = true;
SetSelectedTip(TextManager.Get("LoadingScreenTip"));
}
@@ -170,10 +125,11 @@ namespace Barotrauma
{
DrawLanguageSelectionPrompt(spriteBatch, graphics);
}
else if (DrawLoadingText)
else
{
LocalizedString loadText;
if (LoadState == 100.0f)
var loadState = LoadState; // avoid multiple reads here to prevent jank
if (loadState >= 100.0f)
{
#if DEBUG
if (GameSettings.CurrentConfig.AutomaticQuickStartEnabled || GameSettings.CurrentConfig.AutomaticCampaignLoadEnabled || (GameSettings.CurrentConfig.TestScreenEnabled && GameMain.FirstLoad))
@@ -191,17 +147,17 @@ namespace Barotrauma
else
{
loadText = TextManager.Get("Loading");
if (LoadState != null)
if (loadState >= 0f)
{
loadText += " " + (int)LoadState + " %";
loadText += $" {loadState:N0} %";
}
#if DEBUG
if (GameMain.FirstLoad && GameMain.CancelQuickStart)
{
loadText += " (Quickstart aborted)";
}
#endif
if (GameMain.FirstLoad && GameMain.CancelQuickStart)
{
loadText += " (Quickstart aborted)";
}
#endif
}
if (GUIStyle.LargeFont.HasValue)
@@ -343,11 +299,9 @@ namespace Barotrauma
private void DrawSplashScreen(SpriteBatch spriteBatch, GraphicsDevice graphics)
{
if (currSplashScreen == null && PendingSplashScreens.Count == 0) { return; }
if (currSplashScreen == null)
{
var newSplashScreen = PendingSplashScreens.Dequeue();
if (!PendingSplashScreens.TryDequeue(out var newSplashScreen)) { return; }
string fileName = newSplashScreen.Filename;
try
{
@@ -362,10 +316,10 @@ namespace Barotrauma
PendingSplashScreens.Clear();
currSplashScreen = null;
}
if (currSplashScreen == null) { return; }
}
if (currSplashScreen == null) { return; }
if (currSplashScreen.IsPlaying)
{
graphics.Clear(Color.Black);
@@ -425,7 +379,7 @@ namespace Barotrauma
public IEnumerable<CoroutineStatus> DoLoading(IEnumerable<CoroutineStatus> loader)
{
drawn = false;
LoadState = null;
LoadState = -1f;
SetSelectedTip(TextManager.Get("LoadingScreenTip"));
currentBackgroundTexture = LocationType.Prefabs.Where(p => p.UsePortraitInRandomLoadingScreens).GetRandomUnsynced()?.GetPortrait(Rand.Int(int.MaxValue));
if (GameMain.GameSession?.GameMode?.Missions is { } missions && missions.Any(m => m.Prefab.HasPortraits))

View File

@@ -946,7 +946,7 @@ namespace Barotrauma
}
if (truncated)
{
descriptionBlock.Text += "...";
descriptionBlock.Text += TextManager.Get("ellipsis");
}
GUITextBlock priceBlock = new GUITextBlock(new RectTransform(new Vector2(1f, 0.25f), bottomTextLayout.RectTransform), TextManager.FormatCurrency(affliction.Price), font: GUIStyle.SubHeadingFont);

View File

@@ -306,6 +306,8 @@ namespace Barotrauma
{
_scaleBasis = value;
RecalculateAbsoluteSize();
RecalculateAnchorPoint();
RecalculatePivotOffset();
}
}

View File

@@ -91,8 +91,8 @@ namespace Barotrauma
var angle = (float)Math.Atan2(point2.Y - point1.Y, point2.X - point1.X);
var scale = new Vector2(length, thickness);
Vector2 middle = new Vector2((point1.X + point2.X) / 2f, (point1.Y + point2.Y) / 2f);
Texture2D tex = GetTexture(spriteBatch);
spriteBatch.Draw(GetTexture(spriteBatch), middle, null, color, angle, new Vector2(tex.Width / 2f, tex.Height / 2f), scale, SpriteEffects.None, 0);
Texture2D tex = GUI.WhiteTexture;
spriteBatch.Draw(tex, middle, null, color, angle, new Vector2(tex.Width / 2f, tex.Height / 2f), scale, SpriteEffects.None, 0);
}
private static void DrawPolygonEdge(SpriteBatch spriteBatch, Vector2 point1, Vector2 point2, Color color, float thickness)
@@ -112,6 +112,18 @@ namespace Barotrauma
DrawLine(spriteBatch, new Vector2(x1, y1), new Vector2(x2, y2), color, thickness);
}
public static void DrawLineWithTexture(this SpriteBatch spriteBatch, Texture2D tex, Vector2 point1, Vector2 point2,
Color color, float thickness = 1f)
{
// calculate the distance between the two vectors
var distance = Vector2.Distance(point1, point2);
// calculate the angle between the two vectors
var angle = (float)Math.Atan2(point2.Y - point1.Y, point2.X - point1.X);
DrawLine(spriteBatch, tex, point1, distance, angle, color, thickness);
}
/// <summary>
/// Draws a line from point1 to point2 with an offset
/// </summary>
@@ -124,18 +136,18 @@ namespace Barotrauma
// calculate the angle between the two vectors
var angle = (float)Math.Atan2(point2.Y - point1.Y, point2.X - point1.X);
DrawLine(spriteBatch, point1, distance, angle, color, thickness);
DrawLine(spriteBatch, GetTexture(spriteBatch), point1, distance, angle, color, thickness);
}
/// <summary>
/// Draws a line from point1 to point2 with an offset
/// </summary>
public static void DrawLine(this SpriteBatch spriteBatch, Vector2 point, float length, float angle, Color color,
public static void DrawLine(this SpriteBatch spriteBatch, Texture2D tex, Vector2 point, float length, float angle, Color color,
float thickness = 1f)
{
var origin = new Vector2(0f, 0.5f);
var scale = new Vector2(length, thickness);
spriteBatch.Draw(GetTexture(spriteBatch), point, null, color, angle, origin, scale, SpriteEffects.None, 0);
var origin = new Vector2(0f, tex.Height / 2f);
var scale = new Vector2(length / tex.Width, thickness / tex.Height);
spriteBatch.Draw(tex, point, null, color, angle, origin, scale, SpriteEffects.None, 0);
}
/// <summary>

View File

@@ -19,7 +19,7 @@ namespace Barotrauma
private static UISprite spectateIcon, disconnectedIcon;
private static Sprite ownerIcon, moderatorIcon;
public enum InfoFrameTab { Crew, Mission, Reputation, Traitor, Submarine, Talents };
public enum InfoFrameTab { Crew, Mission, Reputation, Submarine, Talents };
public static InfoFrameTab SelectedTab { get; private set; }
private GUIFrame infoFrame, contentFrame;
@@ -299,9 +299,15 @@ namespace Barotrauma
var crewButton = createTabButton(InfoFrameTab.Crew, "crew");
if (!(GameMain.GameSession?.GameMode is TestGameMode))
if (GameMain.GameSession?.GameMode is not TestGameMode)
{
createTabButton(InfoFrameTab.Mission, "mission");
var missionBtn = createTabButton(InfoFrameTab.Mission, "mission");
eventLogNotification = GameSession.CreateNotificationIcon(missionBtn);
eventLogNotification.Visible = GameMain.GameSession.EventManager?.EventLog?.UnreadEntries ?? false;
if (eventLogNotification.Visible)
{
eventLogNotification.Pulsate(Vector2.One, Vector2.One * 2, 1.0f);
}
}
if (GameMain.GameSession?.GameMode is CampaignMode campaignMode)
@@ -340,14 +346,6 @@ namespace Barotrauma
text.Text = TextManager.GetWithVariable("bankbalanceformat", "[money]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", balance));
}
}
else
{
bool isTraitor = GameMain.Client?.Character?.IsTraitor ?? false;
if (isTraitor && GameMain.Client.TraitorMission != null)
{
var traitorButton = createTabButton(InfoFrameTab.Traitor, "tabmenu.traitor");
}
}
var submarineButton = createTabButton(InfoFrameTab.Submarine, "submarine");
@@ -361,7 +359,7 @@ namespace Barotrauma
}
};
talentPointNotification = GameSession.CreateTalentIconNotification(talentsButton);
talentPointNotification = GameSession.CreateNotificationIcon(talentsButton);
}
public void SelectInfoFrameTab(InfoFrameTab selectedTab)
@@ -387,12 +385,6 @@ namespace Barotrauma
GameMain.GameSession.RoundSummary.CreateReputationInfoPanel(reputationFrame, campaignMode);
}
break;
case InfoFrameTab.Traitor:
TraitorMissionPrefab traitorMission = GameMain.Client?.TraitorMission;
Character traitor = GameMain.Client?.Character;
if (traitor == null || traitorMission == null) { return; }
CreateTraitorInfo(infoFrameHolder, traitorMission, traitor);
break;
case InfoFrameTab.Submarine:
CreateSubmarineInfo(infoFrameHolder, Submarine.MainSub);
break;
@@ -1539,96 +1531,44 @@ namespace Barotrauma
int locationInfoYOffset = locationInfoContainer.Rect.Height + padding * 2;
GUIListBox missionList = new GUIListBox(new RectTransform(new Point(contentWidth, missionFrameContent.Rect.Height - locationInfoYOffset), missionFrameContent.RectTransform, Anchor.TopCenter) { AbsoluteOffset = new Point(0, locationInfoYOffset) });
missionList.ContentBackground.Color = Color.Transparent;
missionList.Spacing = GUI.IntScale(15);
if (GameMain.GameSession?.Missions != null)
{
int spacing = GUI.IntScale(5);
int iconSize = (int)(GUIStyle.LargeFont.MeasureChar('T').Y + GUIStyle.Font.MeasureChar('T').Y * 4 + spacing * 4);
foreach (Mission mission in GameMain.GameSession.Missions)
{
if (!mission.Prefab.ShowInMenus) { continue; }
GUIFrame missionDescriptionHolder = new GUIFrame(new RectTransform(Vector2.One, missionList.Content.RectTransform), style: null);
GUILayoutGroup missionTextGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.744f, 0f), missionDescriptionHolder.RectTransform, Anchor.CenterLeft) { AbsoluteOffset = new Point(iconSize + spacing, 0) }, false, childAnchor: Anchor.TopLeft)
var textContent = new List<LocalizedString>()
{
AbsoluteSpacing = spacing
mission.GetMissionRewardText(Submarine.MainSub),
mission.GetReputationRewardText(),
mission.Description
};
LocalizedString descriptionText = mission.Description;
foreach (LocalizedString missionMessage in mission.ShownMessages)
textContent.AddRange(mission.ShownMessages);
RoundSummary.CreateMissionEntry(
missionList.Content,
mission.Name,
textContent,
mission.Difficulty ?? 0,
mission.Prefab.Icon, mission.Prefab.IconColor,
out GUIImage missionIcon);
if (missionIcon != null)
{
descriptionText += "\n\n" + missionMessage;
}
RichString rewardText = mission.GetMissionRewardText(Submarine.MainSub);
RichString reputationText = mission.GetReputationRewardText();
UpdateMissionStateIcon();
mission.OnMissionStateChanged += (mission) => UpdateMissionStateIcon();
Func<string, string> wrapMissionText(GUIFont font)
{
return (str) => ToolBox.WrapText(str, missionTextGroup.Rect.Width, font.Value);
}
RichString missionNameString = RichString.Rich(mission.Name, wrapMissionText(GUIStyle.LargeFont));
RichString missionRewardString = RichString.Rich(rewardText, wrapMissionText(GUIStyle.Font));
RichString missionReputationString = RichString.Rich(reputationText, wrapMissionText(GUIStyle.Font));
RichString missionDescriptionString = RichString.Rich(descriptionText, wrapMissionText(GUIStyle.Font));
Vector2 missionNameSize = GUIStyle.LargeFont.MeasureString(missionNameString.SanitizedValue);
Vector2 missionDescriptionSize = GUIStyle.Font.MeasureString(missionDescriptionString.SanitizedValue);
Vector2 missionRewardSize = GUIStyle.Font.MeasureString(missionRewardString.SanitizedValue);
Vector2 missionReputationSize = GUIStyle.Font.MeasureString(missionReputationString.SanitizedValue);
float ySize = missionNameSize.Y + missionDescriptionSize.Y + missionRewardSize.Y + missionReputationSize.Y + missionTextGroup.AbsoluteSpacing * 4;
bool displayDifficulty = mission.Difficulty.HasValue;
if (displayDifficulty) { ySize += missionRewardSize.Y; }
missionDescriptionHolder.RectTransform.NonScaledSize = new Point(missionDescriptionHolder.RectTransform.NonScaledSize.X, (int)ySize);
missionTextGroup.RectTransform.NonScaledSize = new Point(missionTextGroup.RectTransform.NonScaledSize.X, missionDescriptionHolder.RectTransform.NonScaledSize.Y);
if (mission.Prefab.Icon != null)
{
/*float iconAspectRatio = mission.Prefab.Icon.SourceRect.Width / mission.Prefab.Icon.SourceRect.Height;
int iconWidth = (int)(0.225f * missionDescriptionHolder.RectTransform.NonScaledSize.X);
int iconHeight = Math.Max(missionTextGroup.RectTransform.NonScaledSize.Y, (int)(iconWidth * iconAspectRatio));
Point iconSize = new Point(iconWidth, iconHeight);*/
var icon = new GUIImage(new RectTransform(new Point(iconSize), missionDescriptionHolder.RectTransform), mission.Prefab.Icon, null, true)
void UpdateMissionStateIcon()
{
Color = mission.Prefab.IconColor,
HoverColor = mission.Prefab.IconColor,
SelectedColor = mission.Prefab.IconColor,
CanBeFocused = false
};
UpdateMissionStateIcon(mission, icon);
mission.OnMissionStateChanged += (mission) => UpdateMissionStateIcon(mission, icon);
}
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextGroup.RectTransform), missionNameString, font: GUIStyle.LargeFont);
GUILayoutGroup difficultyIndicatorGroup = null;
if (displayDifficulty)
{
difficultyIndicatorGroup = new GUILayoutGroup(new RectTransform(new Point(missionTextGroup.Rect.Width, (int)missionRewardSize.Y), parent: missionTextGroup.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
AbsoluteSpacing = 1
};
var difficultyColor = mission.GetDifficultyColor();
for (int i = 0; i < mission.Difficulty.Value; i++)
{
new GUIImage(new RectTransform(Vector2.One, difficultyIndicatorGroup.RectTransform, scaleBasis: ScaleBasis.Smallest), "DifficultyIndicator", scaleToFit: true)
if (mission.DisplayAsCompleted || mission.DisplayAsFailed)
{
CanBeFocused = false,
Color = difficultyColor
};
RoundSummary.UpdateMissionStateIcon(mission.DisplayAsCompleted, missionIcon);
}
}
}
var rewardTextBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextGroup.RectTransform), missionRewardString);
if (difficultyIndicatorGroup != null)
{
difficultyIndicatorGroup.RectTransform.Resize(new Point((int)(difficultyIndicatorGroup.Rect.Width - rewardTextBlock.Padding.X - rewardTextBlock.Padding.Z), difficultyIndicatorGroup.Rect.Height));
difficultyIndicatorGroup.RectTransform.AbsoluteOffset = new Point((int)rewardTextBlock.Padding.X, 0);
}
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextGroup.RectTransform), missionReputationString);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextGroup.RectTransform), missionDescriptionString);
}
}
else
@@ -1636,66 +1576,14 @@ namespace Barotrauma
GUILayoutGroup missionTextGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0f), missionList.RectTransform, Anchor.CenterLeft), false, childAnchor: Anchor.TopLeft);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextGroup.RectTransform), TextManager.Get("NoMission"), font: GUIStyle.LargeFont);
}
GameMain.GameSession?.EventManager?.EventLog?.CreateEventLogUI(missionList.Content);
GameMain.GameSession.EnableEventLogNotificationIcon(enabled: false);
RoundSummary.AddSeparators(missionList.Content);
}
private void UpdateMissionStateIcon(Mission mission, GUIImage missionIcon)
{
if (mission == null || missionIcon == null) { return; }
string style = string.Empty;
if (mission.DisplayAsFailed)
{
style = "MissionFailedIcon";
}
else if (mission.DisplayAsCompleted)
{
style = "MissionCompletedIcon";
}
GUIImage stateIcon = missionIcon.GetChild<GUIImage>();
if (string.IsNullOrEmpty(style))
{
if (stateIcon != null)
{
stateIcon.Visible = false;
}
}
else
{
stateIcon ??= new GUIImage(new RectTransform(Vector2.One, missionIcon.RectTransform), style, scaleToFit: true);
stateIcon.Visible = true;
}
}
private void CreateTraitorInfo(GUIFrame infoFrame, TraitorMissionPrefab traitorMission, Character traitor)
{
GUIFrame missionFrame = new GUIFrame(new RectTransform(Vector2.One, infoFrame.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox");
int padding = (int)(0.0245f * missionFrame.Rect.Height);
GUIFrame missionDescriptionHolder = new GUIFrame(new RectTransform(new Point(missionFrame.Rect.Width - padding * 2, 0), missionFrame.RectTransform, Anchor.TopCenter) { AbsoluteOffset = new Point(0, padding) }, style: null);
GUILayoutGroup missionTextGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.65f, 0f), missionDescriptionHolder.RectTransform, Anchor.CenterLeft) { RelativeOffset = new Vector2(0.319f, 0f) }, false, childAnchor: Anchor.TopLeft);
LocalizedString missionNameString = ToolBox.WrapText(TextManager.Get("tabmenu.traitor"), missionTextGroup.Rect.Width, GUIStyle.LargeFont);
LocalizedString missionDescriptionString = ToolBox.WrapText(traitor.TraitorCurrentObjective, missionTextGroup.Rect.Width, GUIStyle.Font);
Vector2 missionNameSize = GUIStyle.LargeFont.MeasureString(missionNameString);
Vector2 missionDescriptionSize = GUIStyle.Font.MeasureString(missionDescriptionString);
missionDescriptionHolder.RectTransform.NonScaledSize = new Point(missionDescriptionHolder.RectTransform.NonScaledSize.X, (int)(missionNameSize.Y + missionDescriptionSize.Y));
missionTextGroup.RectTransform.NonScaledSize = new Point(missionTextGroup.RectTransform.NonScaledSize.X, missionDescriptionHolder.RectTransform.NonScaledSize.Y);
float aspectRatio = traitorMission.Icon.SourceRect.Width / traitorMission.Icon.SourceRect.Height;
int iconWidth = (int)(0.319f * missionDescriptionHolder.RectTransform.NonScaledSize.X);
int iconHeight = Math.Max(missionTextGroup.RectTransform.NonScaledSize.Y, (int)(iconWidth * aspectRatio));
Point iconSize = new Point(iconWidth, iconHeight);
new GUIImage(new RectTransform(iconSize, missionDescriptionHolder.RectTransform), traitorMission.Icon, null, true) { Color = traitorMission.IconColor };
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextGroup.RectTransform), missionNameString, font: GUIStyle.LargeFont);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextGroup.RectTransform), missionDescriptionString);
}
private void CreateSubmarineInfo(GUIFrame infoFrame, Submarine sub)
private static void CreateSubmarineInfo(GUIFrame infoFrame, Submarine sub)
{
GUIFrame subInfoFrame = new GUIFrame(new RectTransform(Vector2.One, infoFrame.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox");
GUIFrame paddedFrame = new GUIFrame(new RectTransform(Vector2.One * 0.97f, subInfoFrame.RectTransform, Anchor.Center), style: null);
@@ -1793,7 +1681,7 @@ namespace Barotrauma
}
}
private GUIImage talentPointNotification;
private GUIImage talentPointNotification, eventLogNotification;
public static void CreateSkillList(Character character, CharacterInfo info, GUIListBox parent)
{

View File

@@ -117,6 +117,9 @@ namespace Barotrauma
return MathHelper.Clamp(Math.Min(Math.Min(scale.X, scale.Y), GUI.SlicedSpriteScale), minBorderScale, maxBorderScale);
}
public void Draw(SpriteBatch spriteBatch, RectangleF rect, Color color, SpriteEffects spriteEffects = SpriteEffects.None, Vector2? uvOffset = null)
=> Draw(spriteBatch, new Rectangle(rect.Location.ToPoint(), rect.Size.ToPoint()), color, spriteEffects, uvOffset);
public void Draw(SpriteBatch spriteBatch, Rectangle rect, Color color, SpriteEffects spriteEffects = SpriteEffects.None, Vector2? uvOffset = null)
{
uvOffset ??= Vector2.Zero;

View File

@@ -11,7 +11,7 @@ namespace Barotrauma
{
if (consentTextAvailable)
{
var background = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas), style: "GUIBackgroundBlocker");
var background = new GUIFrame(new RectTransform(GUI.Canvas.RelativeSize, GUI.Canvas), style: "GUIBackgroundBlocker");
var frame = new GUIFrame(new RectTransform(new Vector2(0.5f, 0.7f), background.RectTransform, Anchor.Center) { MinSize = new Point(800, 0), MaxSize = new Point(1500, int.MaxValue) });
var content = new GUILayoutGroup(new RectTransform(new Vector2(0.95f), frame.RectTransform, Anchor.Center))
@@ -55,7 +55,8 @@ namespace Barotrauma
yesBtn.OnClicked += (btn, userdata) =>
{
GUIMessageBox.MessageBoxes.Remove(background);
SetConsentInternal(Consent.Yes);
var loadingBox = GUIMessageBox.CreateLoadingBox(TextManager.Get("PleaseWait"));
SetConsentInternal(Consent.Yes, onAnswerSent: loadingBox.Close);
return true;
};
yesBtn.Enabled = false;
@@ -76,7 +77,8 @@ namespace Barotrauma
noBtn.OnClicked += (btn, userdata) =>
{
GUIMessageBox.MessageBoxes.Remove(background);
SetConsent(Consent.No);
var loadingBox = GUIMessageBox.CreateLoadingBox(TextManager.Get("PleaseWait"));
SetConsent(Consent.No, onAnswerSent: loadingBox.Close);
return true;
};
noBtn.Enabled = false;

View File

@@ -111,7 +111,8 @@ namespace Barotrauma
public static LoadingScreen TitleScreen;
private bool loadingScreenOpen;
private CoroutineHandle loadingCoroutine;
private Thread initialLoadingThread;
public bool HasLoaded { get; private set; }
private readonly GameTime fixedTime;
@@ -411,24 +412,21 @@ namespace Barotrauma
WaitForLanguageSelection = GameSettings.CurrentConfig.Language == LanguageIdentifier.None
};
bool canLoadInSeparateThread = true;
loadingCoroutine = CoroutineManager.StartCoroutine(Load(canLoadInSeparateThread), "Load", canLoadInSeparateThread);
initialLoadingThread = new Thread(Load);
initialLoadingThread.Start();
}
public class LoadingException : Exception
private void Load()
{
public LoadingException(Exception e) : base("Loading was interrupted due to an error.", innerException: e)
static void log(string str)
{
if (GameSettings.CurrentConfig.VerboseLogging)
{
DebugConsole.NewMessage(str, Color.Lime);
}
}
}
private IEnumerable<CoroutineStatus> Load(bool isSeparateThread)
{
if (GameSettings.CurrentConfig.VerboseLogging)
{
DebugConsole.NewMessage("LOADING COROUTINE", Color.Lime);
}
log("LOADING COROUTINE");
ContentPackageManager.LoadVanillaFileList();
@@ -438,7 +436,7 @@ namespace Barotrauma
TitleScreen.AvailableLanguages = TextManager.AvailableLanguages.OrderBy(l => l.Value != "english".ToIdentifier()).ThenBy(l => l.Value).ToArray();
while (TitleScreen.WaitForLanguageSelection)
{
yield return CoroutineStatus.Running;
Thread.Sleep((int)(Timing.Step * 1000));
}
ContentPackageManager.VanillaCorePackage.UnloadFilesOfType<TextFile>();
}
@@ -450,25 +448,13 @@ namespace Barotrauma
{
var pendingSplashScreens = TitleScreen.PendingSplashScreens;
float baseVolume = MathHelper.Clamp(GameSettings.CurrentConfig.Audio.SoundVolume * 2.0f, 0.0f, 1.0f);
pendingSplashScreens?.Enqueue(new LoadingScreen.PendingSplashScreen("Content/SplashScreens/Splash_UTG.webm", baseVolume * 0.5f));
pendingSplashScreens?.Enqueue(new LoadingScreen.PendingSplashScreen("Content/SplashScreens/Splash_FF.webm", baseVolume));
pendingSplashScreens?.Enqueue(new LoadingScreen.PendingSplashScreen("Content/SplashScreens/Splash_Daedalic.webm", baseVolume * 0.1f));
}
//if not loading in a separate thread, wait for the splash screens to finish before continuing the loading
//otherwise the videos will look extremely choppy
if (!isSeparateThread)
{
while (TitleScreen.PlayingSplashScreen || TitleScreen.PendingSplashScreens.Count > 0)
{
yield return CoroutineStatus.Running;
}
pendingSplashScreens.Enqueue(new LoadingScreen.PendingSplashScreen("Content/SplashScreens/Splash_UTG.webm", baseVolume * 0.5f));
pendingSplashScreens.Enqueue(new LoadingScreen.PendingSplashScreen("Content/SplashScreens/Splash_FF.webm", baseVolume));
pendingSplashScreens.Enqueue(new LoadingScreen.PendingSplashScreen("Content/SplashScreens/Splash_Daedalic.webm", baseVolume * 0.1f));
}
GUI.Init();
yield return CoroutineStatus.Running;
LegacySteamUgcTransition.Prepare();
var contentPackageLoadRoutine = ContentPackageManager.Init();
foreach (var progress in contentPackageLoadRoutine
@@ -476,7 +462,6 @@ namespace Barotrauma
{
const float min = 1f, max = 70f;
TitleScreen.LoadState = MathHelper.Lerp(min, max, progress);
yield return CoroutineStatus.Running;
}
var corePackage = ContentPackageManager.EnabledPackages.Core;
@@ -505,7 +490,6 @@ namespace Barotrauma
TaskPool.Add("InitRelayNetworkAccess", SteamManager.InitRelayNetworkAccess(), (t) => { });
HintManager.Init();
yield return CoroutineStatus.Running;
CoreEntityPrefab.InitCorePrefabs();
GameModePreset.Init();
@@ -513,7 +497,6 @@ namespace Barotrauma
SubmarineInfo.RefreshSavedSubs();
TitleScreen.LoadState = 75.0f;
yield return CoroutineStatus.Running;
GameScreen = new GameScreen(GraphicsDeviceManager.GraphicsDevice);
@@ -521,13 +504,11 @@ namespace Barotrauma
LightManager = new Lights.LightManager(base.GraphicsDevice);
TitleScreen.LoadState = 80.0f;
yield return CoroutineStatus.Running;
MainMenuScreen = new MainMenuScreen(this);
ServerListScreen = new ServerListScreen();
TitleScreen.LoadState = 85.0f;
yield return CoroutineStatus.Running;
#if USE_STEAM
if (SteamManager.IsInitialized)
@@ -553,12 +534,10 @@ namespace Barotrauma
TestScreen = new TestScreen();
TitleScreen.LoadState = 90.0f;
yield return CoroutineStatus.Running;
ParticleEditorScreen = new ParticleEditorScreen();
TitleScreen.LoadState = 95.0f;
yield return CoroutineStatus.Running;
LevelEditorScreen = new LevelEditorScreen();
SpriteEditorScreen = new SpriteEditorScreen();
@@ -566,8 +545,6 @@ namespace Barotrauma
CharacterEditorScreen = new CharacterEditor.CharacterEditorScreen();
CampaignEndScreen = new CampaignEndScreen();
yield return CoroutineStatus.Running;
#if DEBUG
LevelGenerationParams.CheckValidity();
#endif
@@ -583,12 +560,7 @@ namespace Barotrauma
TitleScreen.LoadState = 100.0f;
HasLoaded = true;
if (GameSettings.CurrentConfig.VerboseLogging)
{
DebugConsole.NewMessage("LOADING COROUTINE FINISHED", Color.Lime);
}
yield return CoroutineStatus.Success;
log("LOADING COROUTINE FINISHED");
}
/// <summary>
@@ -741,11 +713,6 @@ namespace Barotrauma
#endif
Client?.Update((float)Timing.Step);
if (!HasLoaded && !CoroutineManager.IsCoroutineRunning(loadingCoroutine))
{
throw new LoadingException(loadingCoroutine.Exception);
}
}
else if (HasLoaded)
{
@@ -1174,7 +1141,7 @@ namespace Barotrauma
{
waitForKeyHit = waitKeyHit;
loadingScreenOpen = true;
TitleScreen.LoadState = null;
TitleScreen.LoadState = 0f;
return CoroutineManager.StartCoroutine(TitleScreen.DoLoading(loader));
}

View File

@@ -34,6 +34,8 @@ namespace Barotrauma
private bool _isCrewMenuOpen = true;
private Point crewListEntrySize;
private readonly List<GUITickBox> traitorButtons = new List<GUITickBox>();
/// <summary>
/// Present only in single player games. In multiplayer. The chatbox is found from GameSession.Client.
/// </summary>
@@ -194,7 +196,7 @@ namespace Barotrauma
};
}
var reports = OrderPrefab.Prefabs.Where(o => o.IsReport && o.SymbolSprite != null && !o.Hidden).OrderBy(o => o.Identifier).ToArray();
var reports = OrderPrefab.Prefabs.Where(o => o.IsVisibleAsReportButton).OrderBy(o => o.Identifier).ToArray();
if (reports.None())
{
DebugConsole.ThrowError("No valid orders for report buttons found! Cannot create report buttons. The orders for the report buttons must have 'targetallcharacters' attribute enabled and a valid 'symbolsprite' defined.");
@@ -226,7 +228,8 @@ namespace Barotrauma
//report buttons
foreach (OrderPrefab orderPrefab in reports)
{
if (!orderPrefab.IsReport || orderPrefab.SymbolSprite == null || orderPrefab.Hidden) { continue; }
if (!orderPrefab.IsVisibleAsReportButton) { continue; }
var btn = new GUIButton(new RectTransform(Vector2.One, parent.RectTransform, scaleBasis: isHorizontal ? ScaleBasis.BothHeight : ScaleBasis.BothWidth), style: null)
{
OnClicked = (button, userData) =>
@@ -367,7 +370,7 @@ namespace Barotrauma
}
}
int iconsVisible = isJobIconVisible ? 5 : 4;
int iconsVisible = isJobIconVisible ? 6 : 5;
var nameRelativeWidth = 1.0f
// Start padding
- paddingRelativeWidth
@@ -413,7 +416,6 @@ namespace Barotrauma
{
AllowMouseWheelScroll = false,
CurrentDragMode = GUIListBox.DragMode.DragWithinBox,
HideChildrenOutsideFrame = false,
KeepSpaceForScrollBar = false,
OnRearranged = OnOrdersRearranged,
ScrollBarVisible = false,
@@ -439,13 +441,13 @@ namespace Barotrauma
Stretch = false
};
var extraIconFrame = new GUIFrame(new RectTransform(new Vector2(0.8f * iconRelativeWidth, 0.8f), layoutGroup.RectTransform), style: null)
var extraIconFrame = new GUIFrame(new RectTransform(new Vector2(0.8f * iconRelativeWidth * 2, 0.8f), layoutGroup.RectTransform), style: null)
{
CanBeFocused = false,
UserData = "extraicons"
};
var soundIconParent = new GUIFrame(new RectTransform(Vector2.One, extraIconFrame.RectTransform), style: null)
var soundIconParent = new GUIFrame(new RectTransform(new Vector2(0.8f), extraIconFrame.RectTransform, Anchor.CenterLeft, scaleBasis: ScaleBasis.Smallest), style: null)
{
CanBeFocused = false,
UserData = "soundicons",
@@ -470,16 +472,6 @@ namespace Barotrauma
Visible = false
};
if (character.IsBot)
{
new GUIFrame(new RectTransform(Vector2.One, extraIconFrame.RectTransform), style: null)
{
CanBeFocused = false,
UserData = "objectiveicon",
Visible = false
};
}
new GUIButton(new RectTransform(new Point((int)commandButtonAbsoluteHeight), background.RectTransform), style: "CrewListCommandButton")
{
ToolTip = TextManager.Get("inputtype.command"),
@@ -490,6 +482,44 @@ namespace Barotrauma
return true;
}
};
if (character.IsBot)
{
new GUIFrame(new RectTransform(Vector2.One, extraIconFrame.RectTransform, scaleBasis: ScaleBasis.Smallest), style: null)
{
CanBeFocused = false,
UserData = "objectiveicon",
Visible = false
};
}
else if (GameMain.GameSession is { TraitorsEnabled: true } && GameMain.Client != null && character != Character.Controlled)
{
Client targetClient = GameMain.Client.ConnectedClients.FirstOrDefault(c => c.Character == character);
if (targetClient != null)
{
if (OrderPrefab.Prefabs.TryGet("reporttraitor", out OrderPrefab order))
{
var voteTraitorBtn = new GUITickBox(new RectTransform(Vector2.One, extraIconFrame.RectTransform, Anchor.CenterRight, scaleBasis: ScaleBasis.Smallest), label: string.Empty, style: "TraitorVoteButton")
{
UserData = character,
ToolTip =
RichString.Rich(
$"‖color:{XMLExtensions.ToStringHex(GUIStyle.TextColorBright)}‖{TextManager.Get("traitor.blamebutton")}‖color:end‖\n"
+ TextManager.Get("traitor.blamebutton.tooltip")),
OnSelected = (GUITickBox obj) =>
{
foreach (var traitorBtn in traitorButtons)
{
//deselect other traitor buttons
if (traitorBtn != obj) { traitorBtn.SetSelected(false, callOnSelected: false); }
}
GameMain.Client?.Vote(VoteType.Traitor, obj.Selected ? targetClient : null);
return true;
}
};
traitorButtons.Add(voteTraitorBtn);
}
}
}
return background;
}
@@ -499,6 +529,7 @@ namespace Barotrauma
if (crewList?.Content.GetChildByUserData(character) is { } component)
{
crewList.RemoveChild(component);
traitorButtons.RemoveAll(t => t.IsChildOf(component, recursive: true));
}
}
@@ -1205,7 +1236,7 @@ namespace Barotrauma
}
}
crewArea.Visible = !(GameMain.GameSession?.GameMode is CampaignMode campaign) || (!campaign.ForceMapUI && !campaign.ShowCampaignUI);
crewArea.Visible = GameMain.GameSession?.GameMode is not CampaignMode campaign || (!campaign.ForceMapUI && !campaign.ShowCampaignUI);
guiFrame.AddToGUIUpdateList();
}
@@ -1372,7 +1403,8 @@ namespace Barotrauma
if (PlayerInput.KeyDown(InputType.Command) &&
(GUI.KeyboardDispatcher.Subscriber == null || (GUI.KeyboardDispatcher.Subscriber is GUIComponent component && (component == crewList || component.IsChildOf(crewList)))) &&
commandFrame == null && !clicklessSelectionActive && CanIssueOrders && !(GameMain.GameSession?.Campaign?.ShowCampaignUI ?? false))
commandFrame == null && !clicklessSelectionActive && CanIssueOrders && !(GameMain.GameSession?.Campaign?.ShowCampaignUI ?? false) &&
Character.Controlled?.SelectedItem?.Prefab is not { DisableCommandMenuWhenSelected: true })
{
if (PlayerInput.IsShiftDown())
{
@@ -1557,6 +1589,12 @@ namespace Barotrauma
{
if (characterComponent.UserData is Character character)
{
if (character.Removed)
{
characterComponent.Visible = false;
continue;
}
characterComponent.Visible = Character.Controlled == null || Character.Controlled.TeamID == character.TeamID;
if (character.TeamID == CharacterTeamType.FriendlyNPC && Character.Controlled != null &&
(character.CurrentHull == Character.Controlled.CurrentHull || Vector2.DistanceSquared(Character.Controlled.WorldPosition, character.WorldPosition) < 500.0f * 500.0f))
@@ -1633,6 +1671,8 @@ namespace Barotrauma
}
}
traitorButtons.ForEach(btn => btn.Visible = Character.Controlled is { IsDead: false } && btn.UserData as Character != Character.Controlled);
crewArea.RectTransform.AbsoluteOffset = Vector2.SmoothStep(
new Vector2(-crewArea.Rect.Width - HUDLayoutSettings.Padding, 0.0f),
Vector2.Zero,
@@ -2396,7 +2436,7 @@ namespace Barotrauma
if (!(GetTargetSubmarine() is { } sub)) { return; }
shortcutNodes.Clear();
var subItems = sub.GetItems(false);
if (CanFitMoreNodes() && subItems.Find(i => i.HasTag("reactor") && i.IsPlayerTeamInteractable)?.GetComponent<Reactor>() is Reactor reactor)
if (CanFitMoreNodes() && subItems.Find(i => i.HasTag(Tags.Reactor) && i.IsPlayerTeamInteractable)?.GetComponent<Reactor>() is Reactor reactor)
{
float reactorOutput = -reactor.CurrPowerConsumption;
// If player is not an engineer AND the reactor is not powered up AND nobody is using the reactor
@@ -2415,7 +2455,7 @@ namespace Barotrauma
// If player is not a captain AND nobody is using the nav terminal AND the nav terminal is powered up
// --> Create shortcut node for Steer order
if (CanFitMoreNodes() && ShouldDelegateOrder("steer") && IsNonDuplicateOrderPrefab(OrderPrefab.Prefabs["steer"]) &&
subItems.Find(i => i.HasTag("navterminal") && i.IsPlayerTeamInteractable) is Item nav && characters.None(c => c.SelectedItem == nav) &&
subItems.Find(i => i.HasTag(Tags.NavTerminal) && i.IsPlayerTeamInteractable) is Item nav && characters.None(c => c.SelectedItem == nav) &&
nav.GetComponent<Steering>() is Steering steering && steering.Voltage > steering.MinVoltage)
{
var order = new Order(OrderPrefab.Prefabs["steer"], steering.Item, steering);

View File

@@ -157,11 +157,15 @@ namespace Barotrauma
SlideshowPlayer?.DrawManually(spriteBatch);
if (GUI.DisableHUD || GUI.DisableUpperHUD || ForceMapUI || CoroutineManager.IsCoroutineRunning("LevelTransition"))
if (GUI.DisableHUD || GUI.DisableUpperHUD || ForceMapUI ||
CoroutineManager.IsCoroutineRunning("LevelTransition"))
{
endRoundButton.Visible = false;
if (ReadyCheckButton != null) { ReadyCheckButton.Visible = false; }
return;
if (ReadyCheckButton != null)
{
ReadyCheckButton.Visible = false;
}
return;
}
if (Submarine.MainSub == null || Level.Loaded == null) { return; }
@@ -216,11 +220,15 @@ namespace Barotrauma
}
break;
}
if (Level.IsLoadedOutpost && !ObjectiveManager.AllActiveObjectivesCompleted())
if (Level.IsLoadedOutpost &&
(!ObjectiveManager.AllActiveObjectivesCompleted() && this is not MultiPlayerCampaign))
{
allowEndingRound = false;
}
if (ReadyCheckButton != null) { ReadyCheckButton.Visible = allowEndingRound; }
if (ReadyCheckButton != null)
{
ReadyCheckButton.Visible = allowEndingRound && GameMain.GameSession != null && GameMain.GameSession.RoundDuration > 10.0f;
}
endRoundButton.Visible = allowEndingRound && Character.Controlled is { IsIncapacitated: false };
if (endRoundButton.Visible)
@@ -384,8 +392,11 @@ namespace Barotrauma
protected void TryEndRoundWithFuelCheck(Action onConfirm, Action onReturnToMapScreen)
{
Submarine.MainSub.CheckFuel();
SubmarineInfo nextSub = PendingSubmarineSwitch ?? Submarine.MainSub.Info;
bool lowFuel = nextSub.Name == Submarine.MainSub.Info.Name ? Submarine.MainSub.Info.LowFuel : nextSub.LowFuel;
bool lowFuel = Submarine.MainSub.Info.LowFuel;
if (PendingSubmarineSwitch != null)
{
lowFuel = TransferItemsOnSubSwitch ? (lowFuel && PendingSubmarineSwitch.LowFuel) : PendingSubmarineSwitch.LowFuel;
}
if (Level.IsLoadedFriendlyOutpost && lowFuel && CargoManager.PurchasedItems.None(i => i.Value.Any(pi => pi.ItemPrefab.Tags.Contains("reactorfuel"))))
{
var extraConfirmationBox =
@@ -433,11 +444,21 @@ namespace Barotrauma
{
GUIMessageBox.MessageBoxes.RemoveAll(mb => mb.UserData is RoundSummary);
}
#if DEBUG
if (GUI.KeyboardDispatcher.Subscriber == null && PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.M))
{
if (GUIMessageBox.MessageBoxes.Any()) { GUIMessageBox.MessageBoxes.Remove(GUIMessageBox.MessageBoxes.Last()); }
GUIFrame summaryFrame = GameMain.GameSession.RoundSummary.CreateSummaryFrame(GameMain.GameSession, "");
GUIMessageBox.MessageBoxes.Add(summaryFrame);
GameMain.GameSession.RoundSummary.ContinueButton.OnClicked = (_, __) => { GUIMessageBox.MessageBoxes.Remove(summaryFrame); return true; };
}
#endif
if (ShowCampaignUI || ForceMapUI)
{
CampaignUI?.Update(deltaTime);
}
}
}
}

View File

@@ -284,7 +284,7 @@ namespace Barotrauma
yield return CoroutineStatus.Success;
}
protected override IEnumerable<CoroutineStatus> DoLevelTransition(TransitionType transitionType, LevelData newLevel, Submarine leavingSub, bool mirror, List<TraitorMissionResult> traitorResults = null)
protected override IEnumerable<CoroutineStatus> DoLevelTransition(TransitionType transitionType, LevelData newLevel, Submarine leavingSub, bool mirror)
{
yield return CoroutineStatus.Success;
}
@@ -1014,9 +1014,9 @@ namespace Barotrauma
public void LoadState(string filePath)
{
DebugConsole.Log($"Loading save file for an existing game session ({filePath})");
SaveUtil.DecompressToDirectory(filePath, SaveUtil.TempPath, null);
SaveUtil.DecompressToDirectory(filePath, SaveUtil.TempPath);
string gamesessionDocPath = Path.Combine(SaveUtil.TempPath, "gamesession.xml");
string gamesessionDocPath = Path.Combine(SaveUtil.TempPath, SaveUtil.GameSessionFileName);
XDocument doc = XMLExtensions.TryLoadXml(gamesessionDocPath);
if (doc == null)
{

View File

@@ -368,7 +368,7 @@ namespace Barotrauma
yield return CoroutineStatus.Success;
}
protected override IEnumerable<CoroutineStatus> DoLevelTransition(TransitionType transitionType, LevelData newLevel, Submarine leavingSub, bool mirror, List<TraitorMissionResult> traitorResults = null)
protected override IEnumerable<CoroutineStatus> DoLevelTransition(TransitionType transitionType, LevelData newLevel, Submarine leavingSub, bool mirror)
{
NextLevel = newLevel;
bool success = CrewManager.GetCharacters().Any(c => !c.IsDead);
@@ -382,7 +382,7 @@ namespace Barotrauma
// Event history must be registered before ending the round or it will be cleared
GameMain.GameSession.EventManager.RegisterEventHistory();
}
GameMain.GameSession.EndRound("", traitorResults, transitionType);
GameMain.GameSession.EndRound("", transitionType);
var continueButton = GameMain.GameSession.RoundSummary?.ContinueButton;
RoundSummary roundSummary = null;
if (GUIMessageBox.VisibleBox?.UserData is RoundSummary)
@@ -513,17 +513,6 @@ namespace Barotrauma
}
}
#if DEBUG
if (GUI.KeyboardDispatcher.Subscriber == null && PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.M))
{
if (GUIMessageBox.MessageBoxes.Any()) { GUIMessageBox.MessageBoxes.Remove(GUIMessageBox.MessageBoxes.Last()); }
GUIFrame summaryFrame = GameMain.GameSession.RoundSummary.CreateSummaryFrame(GameMain.GameSession, "", null);
GUIMessageBox.MessageBoxes.Add(summaryFrame);
GameMain.GameSession.RoundSummary.ContinueButton.OnClicked = (_, __) => { GUIMessageBox.MessageBoxes.Remove(summaryFrame); return true; };
}
#endif
if (ShowCampaignUI || ForceMapUI)
{
Character.DisableControls = true;

View File

@@ -8,7 +8,7 @@ namespace Barotrauma.Tutorials
{
enum AutoPlayVideo { Yes, No };
enum TutorialSegmentType { MessageBox, InfoBox, Objective };
enum SegmentType { MessageBox, InfoBox, Objective };
sealed class Tutorial
{
@@ -108,7 +108,7 @@ namespace Barotrauma.Tutorials
GameMain.GameSession.StartRound(LevelSeed);
}
GameMain.GameSession.EventManager.ActiveEvents.Clear();
GameMain.GameSession.EventManager.ClearEvents();
GameMain.GameSession.EventManager.Enabled = true;
GameMain.GameScreen.Select();

View File

@@ -1,5 +1,4 @@
using Barotrauma.Tutorials;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma
@@ -15,8 +14,6 @@ namespace Barotrauma
public static bool IsTabMenuOpen => GameMain.GameSession?.tabMenu != null;
public static TabMenu TabMenuInstance => GameMain.GameSession?.tabMenu;
private float prevHudScale;
private TabMenu tabMenu;
public bool ToggleTabMenu()
@@ -27,7 +24,7 @@ namespace Barotrauma
GameMain.NetLobbyScreen.CharacterAppearanceCustomizationMenu = null;
if (GameMain.NetLobbyScreen.JobSelectionFrame != null) { GameMain.NetLobbyScreen.JobSelectionFrame.Visible = false; }
}
if (tabMenu == null && !(GameMode is TutorialMode) && !ConversationAction.IsDialogOpen)
if (tabMenu == null && GameMode is not TutorialMode && !ConversationAction.IsDialogOpen)
{
tabMenu = new TabMenu();
HintManager.OnShowTabMenu();
@@ -49,6 +46,8 @@ namespace Barotrauma
private GUITextBlock respawnInfoText;
private GUITickBox respawnTickBox;
private GUIImage eventLogNotification;
private void CreateTopLeftButtons()
{
if (topLeftButtonGroup != null)
@@ -96,7 +95,8 @@ namespace Barotrauma
OnClicked = (button, userData) => ToggleTabMenu()
};
talentPointNotification = CreateTalentIconNotification(tabMenuButton);
talentPointNotification = CreateNotificationIcon(tabMenuButton);
eventLogNotification = CreateNotificationIcon(tabMenuButton);
GameMain.Instance.ResolutionChanged += CreateTopLeftButtons;
@@ -121,7 +121,6 @@ namespace Barotrauma
return true;
}
};
prevHudScale = GameSettings.CurrentConfig.Graphics.HUDScale;
}
public void AddToGUIUpdateList()
@@ -152,7 +151,7 @@ namespace Barotrauma
}
}
public static GUIImage CreateTalentIconNotification(GUIComponent parent, bool offset = true)
public static GUIImage CreateNotificationIcon(GUIComponent parent, bool offset = true)
{
GUIImage indicator = new GUIImage(new RectTransform(new Vector2(0.45f), parent.RectTransform, anchor: Anchor.TopRight, scaleBasis: ScaleBasis.BothWidth), style: "TalentPointNotification")
{
@@ -167,19 +166,22 @@ namespace Barotrauma
return indicator;
}
public void EnableEventLogNotificationIcon(bool enabled)
{
if (eventLogNotification == null) { return; }
if (!eventLogNotification.Visible && enabled)
{
eventLogNotification.Pulsate(Vector2.One, Vector2.One * 2, 1.0f);
}
eventLogNotification.Visible = enabled;
}
public static void UpdateTalentNotificationIndicator(GUIImage indicator)
{
if (indicator != null)
{
if (Character.Controlled?.Info == null)
{
indicator.Visible = false;
}
else
{
indicator.Visible = Character.Controlled.Info.GetAvailableTalentPoints() > 0 && !Character.Controlled.HasUnlockedAllTalents();
}
}
if (indicator == null) { return; }
indicator.Visible =
Character.Controlled?.Info != null &&
Character.Controlled.Info.GetAvailableTalentPoints() > 0 && !Character.Controlled.HasUnlockedAllTalents();
}
public void HUDScaleChanged()

View File

@@ -1,7 +1,6 @@
using Barotrauma.Extensions;
using Barotrauma.IO;
using Barotrauma.Items.Components;
using Barotrauma.Tutorials;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
@@ -152,8 +151,8 @@ namespace Barotrauma
}
// onstartedinteracting.turretperiscope
if (item.HasTag("periscope") &&
item.GetConnectedComponents<Turret>().FirstOrDefault(t => t.Item.HasTag("turret")) is Turret)
if (item.HasTag(Tags.Periscope) &&
item.GetConnectedComponents<Turret>().FirstOrDefault(t => t.Item.HasTag(Tags.Turret)) is Turret)
{
if (DisplayHint($"{hintIdentifierBase}.turretperiscope".ToIdentifier(),
variables: new[]
@@ -175,6 +174,17 @@ namespace Barotrauma
}
}
public static void OnStartRepairing(Character character, Repairable repairable)
{
if (repairable.ForceDeteriorationTimer > 0.0f && !character.IsTraitor)
{
CoroutineManager.Invoke(() =>
{
DisplayHint($"repairingsabotageditem".ToIdentifier());
}, delay: 5.0f);
}
}
private static void CheckIsInteracting()
{
if (!CanDisplayHints()) { return; }
@@ -182,7 +192,7 @@ namespace Barotrauma
if (Character.Controlled.SelectedItem.GetComponent<Reactor>() is Reactor reactor && reactor.PowerOn &&
Character.Controlled.SelectedItem.OwnInventory?.AllItems is IEnumerable<Item> containedItems &&
containedItems.Count(i => i.HasTag("reactorfuel")) > 1)
containedItems.Count(i => i.HasTag(Tags.Fuel)) > 1)
{
if (DisplayHint("onisinteracting.reactorwithextrarods".ToIdentifier())) { return; }
}
@@ -210,7 +220,7 @@ namespace Barotrauma
{
if (item.CurrentHull == null) { continue; }
if (item.GetComponent<Pump>() == null) { continue; }
if (!item.HasTag("ballast") && !item.CurrentHull.RoomName.Contains("ballast", StringComparison.OrdinalIgnoreCase)) { continue; }
if (!item.HasTag(Tags.Ballast) && !item.CurrentHull.RoomName.Contains("ballast", StringComparison.OrdinalIgnoreCase)) { continue; }
BallastHulls.Add(item.CurrentHull);
}
}
@@ -246,8 +256,14 @@ namespace Barotrauma
});
}
if (GameMain.GameSession is { TraitorsEnabled: true })
{
DisplayHint("traitorsonboard".ToIdentifier());
DisplayHint("traitorsonboard2".ToIdentifier());
}
yield return CoroutineStatus.Success;
}
}
public static void OnRoundEnded()
@@ -395,8 +411,8 @@ namespace Barotrauma
if (DisplayHint($"onobtaineditem.{tag}".ToIdentifier())) { return; }
}
if ((item.HasTag("geneticmaterial") && character.Inventory.FindItemByTag("geneticdevice".ToIdentifier(), recursive: true) != null) ||
(item.HasTag("geneticdevice") && character.Inventory.FindItemByTag("geneticmaterial".ToIdentifier(), recursive: true) != null))
if ((item.HasTag(Tags.GeneticMaterial) && character.Inventory.FindItemByTag(Tags.GeneticMaterial, recursive: true) != null) ||
(item.HasTag(Tags.GeneticDevice) && character.Inventory.FindItemByTag(Tags.GeneticDevice, recursive: true) != null))
{
if (DisplayHint($"geneticmaterial.useinstructions".ToIdentifier())) { return; }
}
@@ -437,6 +453,14 @@ namespace Barotrauma
});
}
public static void OnRadioJammed(Item radioItem)
{
if (!CanDisplayHints()) { return; }
if (radioItem?.ParentInventory is not CharacterInventory characterInventory) { return; }
if (characterInventory.Owner != Character.Controlled) { return; }
DisplayHint("radiojammed".ToIdentifier());
}
public static void OnReactorOutOfFuel(Reactor reactor)
{
if (!CanDisplayHints()) { return; }
@@ -450,6 +474,13 @@ namespace Barotrauma
});
}
public static void OnAssignedAsTraitor()
{
if (!CanDisplayHints()) { return; }
DisplayHint("assignedastraitor".ToIdentifier());
DisplayHint("assignedastraitor2".ToIdentifier());
}
public static void OnAvailableTransition(CampaignMode.TransitionType transitionType)
{
if (!CanDisplayHints()) { return; }
@@ -556,7 +587,7 @@ namespace Barotrauma
private static void CheckIfDivingGearOutOfOxygen()
{
if (!CanDisplayHints()) { return; }
var divingGear = Character.Controlled.GetEquippedItem("diving", InvSlotType.OuterClothes);
var divingGear = Character.Controlled.GetEquippedItem(Tags.DivingGear, InvSlotType.OuterClothes);
if (divingGear?.OwnInventory == null) { return; }
if (divingGear.GetContainedItemConditionPercentage() > 0.0f) { return; }
DisplayHint("ondivinggearoutofoxygen".ToIdentifier(), onUpdate: () =>
@@ -599,7 +630,7 @@ namespace Barotrauma
if (adjacentHull.WaterPercentage > 75 && !BallastHulls.Contains(adjacentHull) && DisplayHint("onadjacenthull.highwaterpercentage".ToIdentifier())) { return; }
}
static bool IsWearingDivingSuit() => Character.Controlled.GetEquippedItem("deepdiving", InvSlotType.OuterClothes) is Item;
static bool IsWearingDivingSuit() => Character.Controlled.GetEquippedItem(Tags.HeavyDivingGear, InvSlotType.OuterClothes) is Item;
}
static bool IsOnFriendlySub() => Character.Controlled.Submarine is Submarine sub && (sub.TeamID == Character.Controlled.TeamID || sub.TeamID == CharacterTeamType.FriendlyNPC);

View File

@@ -49,7 +49,7 @@ static class ObjectiveManager
public Identifier ParentId { get; set; }
public TutorialSegmentType SegmentType { get; private set; }
public SegmentType SegmentType { get; private set; }
public static Segment CreateInfoBoxSegment(Identifier id, Identifier objectiveTextTag, AutoPlayVideo autoPlayVideo, Text textContent = default, Video videoContent = default)
{
@@ -69,31 +69,31 @@ static class ObjectiveManager
private Segment(Identifier id, Identifier objectiveTextTag, AutoPlayVideo autoPlayVideo, Text textContent = default, Video videoContent = default)
{
Id = id;
ObjectiveText = TextManager.ParseInputTypes(TextManager.Get(objectiveTextTag));
ObjectiveText = TextManager.ParseInputTypes(TextManager.Get(objectiveTextTag).Fallback(objectiveTextTag.Value));
AutoPlayVideo = autoPlayVideo;
TextContent = textContent;
VideoContent = videoContent;
SegmentType = TutorialSegmentType.InfoBox;
SegmentType = SegmentType.InfoBox;
}
private Segment(Identifier id, Identifier objectiveTextTag, Action onClickObjective)
{
Id = id;
ObjectiveText = TextManager.ParseInputTypes(TextManager.Get(objectiveTextTag));
ObjectiveText = TextManager.ParseInputTypes(TextManager.Get(objectiveTextTag).Fallback(objectiveTextTag.Value));
OnClickObjective = onClickObjective;
SegmentType = TutorialSegmentType.MessageBox;
SegmentType = SegmentType.MessageBox;
}
private Segment(Identifier id, Identifier objectiveTextTag)
{
Id = id;
ObjectiveText = TextManager.ParseInputTypes(TextManager.Get(objectiveTextTag));
SegmentType = TutorialSegmentType.Objective;
ObjectiveText = TextManager.ParseInputTypes(TextManager.Get(objectiveTextTag).Fallback(objectiveTextTag.Value));
SegmentType = SegmentType.Objective;
}
public void ConnectMessageBox(Segment messageBoxSegment)
{
SegmentType = TutorialSegmentType.MessageBox;
SegmentType = SegmentType.MessageBox;
OnClickObjective = messageBoxSegment.OnClickObjective;
}
}
@@ -139,9 +139,9 @@ static class ObjectiveManager
VideoPlayer.AddToGUIUpdateList(order: 100);
}
public static void TriggerTutorialSegment(Segment segment, bool connectObjective = false)
public static void TriggerSegment(Segment segment, bool connectObjective = false)
{
if (segment.SegmentType != TutorialSegmentType.InfoBox)
if (segment.SegmentType != SegmentType.InfoBox)
{
activeObjectives.Add(segment);
AddToObjectiveList(segment, connectObjective);
@@ -153,15 +153,15 @@ static class ObjectiveManager
ActiveContentSegment = segment;
var title = TextManager.Get(segment.Id);
LocalizedString tutorialText = TextManager.GetFormatted(segment.TextContent.Tag);
tutorialText = TextManager.ParseInputTypes(tutorialText);
LocalizedString text = TextManager.GetFormatted(segment.TextContent.Tag).Fallback(segment.TextContent.Tag.Value);
text = TextManager.ParseInputTypes(text);
switch (segment.AutoPlayVideo)
{
case AutoPlayVideo.Yes:
infoBox = CreateInfoFrame(
title,
tutorialText,
text,
segment.TextContent.Width,
segment.TextContent.Height,
segment.TextContent.Anchor,
@@ -171,7 +171,7 @@ static class ObjectiveManager
case AutoPlayVideo.No:
infoBox = CreateInfoFrame(
title,
tutorialText,
text,
segment.TextContent.Width,
segment.TextContent.Height,
segment.TextContent.Anchor,
@@ -182,31 +182,54 @@ static class ObjectiveManager
}
}
public static void CompleteTutorialSegment(Identifier segmentId)
public static void CompleteSegment(Identifier segmentId)
{
if (GetActiveObjective(segmentId) is not Segment segment || !segment.CanBeCompleted || segment.IsCompleted)
{
return;
}
if (!MarkSegmentCompleted(segment))
CompleteSegment(segment, failed: false);
}
public static void FailSegment(Identifier segmentId)
{
if (GetActiveObjective(segmentId) is not Segment segment)
{
return;
}
CompleteSegment(segment, failed: true);
}
private static void CompleteSegment(Segment segment, bool failed = false)
{
if (failed)
{
if (!MarkSegmentFailed(segment)) { return; }
}
else
{
if (!MarkSegmentCompleted(segment)) { return; }
}
if (GameMain.GameSession?.GameMode is TutorialMode tutorialMode)
{
GameAnalyticsManager.AddDesignEvent($"Tutorial:{tutorialMode.Tutorial?.Identifier}:{segmentId}:Completed");
}
else if (GameMain.GameSession?.GameMode is CampaignMode campaign)
{
GameAnalyticsManager.AddDesignEvent($"Tutorial:CampaignMode:{segmentId}:Completed");
campaign?.CampaignMetadata?.SetValue(segmentId, true);
GameAnalyticsManager.AddDesignEvent($"Tutorial:{tutorialMode.Tutorial?.Identifier}:{segment.Id}:{(failed ? "Failed" : "Completed")}");
}
}
public static bool MarkSegmentCompleted(Segment segment, bool flash = true)
private static bool MarkSegmentCompleted(Segment segment, bool flash = true)
{
return MarkSegment(segment, "ObjectiveIndicatorCompleted", flash, flashColor: GUIStyle.Green);
}
private static bool MarkSegmentFailed(Segment segment, bool flash = true)
{
return MarkSegment(segment, "MissionFailedIcon", flash, flashColor: GUIStyle.Red);
}
private static bool MarkSegment(Segment segment, string iconStyleName, bool flash, Color flashColor)
{
segment.IsCompleted = true;
if (GUIStyle.GetComponentStyle("ObjectiveIndicatorCompleted") is GUIComponentStyle style)
if (GUIStyle.GetComponentStyle(iconStyleName) is GUIComponentStyle style)
{
if (segment.ObjectiveStateIndicator.Style == style)
{
@@ -216,21 +239,17 @@ static class ObjectiveManager
}
if (flash)
{
segment.ObjectiveStateIndicator.Parent.Flash(color: GUIStyle.Green, flashDuration: 0.35f, useRectangleFlash: true);
}
segment.ObjectiveStateIndicator.Parent.Flash(color: flashColor, flashDuration: 0.35f, useRectangleFlash: true);
}
segment.ObjectiveButton.OnClicked = null;
segment.ObjectiveButton.CanBeFocused = false;
return true;
}
public static void RemoveTutorialSegment(Identifier segmentId)
public static void RemoveSegment(Identifier segmentId)
{
if (GetActiveObjective(segmentId) is not Segment segment)
{
if (GameMain.GameSession?.GameMode is TutorialMode tutorialMode)
{
DebugConsole.AddWarning($"Warning: tried to remove the tutorial segment \"{segmentId}\" in tutorial \"{tutorialMode.Tutorial?.Identifier}\" but it isn't active!");
}
return;
}
segment.ObjectiveStateIndicator.FadeOut(ObjectiveComponentAnimationTime, false);
@@ -400,10 +419,10 @@ static class ObjectiveManager
void SetButtonBehavior(Segment segment)
{
segment.ObjectiveButton.CanBeFocused = segment.SegmentType != TutorialSegmentType.Objective;
segment.ObjectiveButton.CanBeFocused = segment.SegmentType != SegmentType.Objective;
segment.ObjectiveButton.OnClicked = (GUIButton btn, object userdata) =>
{
if (segment.SegmentType == TutorialSegmentType.InfoBox)
if (segment.SegmentType == SegmentType.InfoBox)
{
if (segment.AutoPlayVideo == AutoPlayVideo.Yes)
{
@@ -414,7 +433,7 @@ static class ObjectiveManager
ShowSegmentText(segment);
}
}
else if (segment.SegmentType == TutorialSegmentType.MessageBox)
else if (segment.SegmentType == SegmentType.MessageBox)
{
segment.OnClickObjective?.Invoke();
}
@@ -438,8 +457,8 @@ static class ObjectiveManager
ContentRunning = true;
ActiveContentSegment = segment;
infoBox = CreateInfoFrame(
TextManager.Get(segment.Id),
TextManager.Get(segment.TextContent.Tag),
TextManager.Get(segment.Id).Fallback(segment.Id.Value),
TextManager.Get(segment.TextContent.Tag).Fallback(segment.TextContent.Tag.Value),
segment.TextContent.Width,
segment.TextContent.Height,
segment.TextContent.Anchor,

View File

@@ -10,12 +10,12 @@ namespace Barotrauma
{
internal partial class ReadyCheck
{
private static LocalizedString readyCheckBody(string name) => string.IsNullOrWhiteSpace(name) ? TextManager.Get("readycheck.serverbody") : TextManager.GetWithVariable("readycheck.body", "[player]", name);
private static LocalizedString ReadyCheckBody(string name) => string.IsNullOrWhiteSpace(name) ? TextManager.Get("readycheck.serverbody") : TextManager.GetWithVariable("readycheck.body", "[player]", name);
private static LocalizedString readyCheckStatus(int ready, int total) => TextManager.GetWithVariables("readycheck.readycount",
private static LocalizedString ReadyCheckStatus(int ready, int total) => TextManager.GetWithVariables("readycheck.readycount",
("[ready]", ready.ToString()),
("[total]", total.ToString()));
private static LocalizedString readyCheckPleaseWait(int seconds) => TextManager.GetWithVariable("readycheck.pleasewait", "[seconds]", seconds.ToString());
private static LocalizedString ReadyCheckPleaseWait(int seconds) => TextManager.GetWithVariable("readycheck.pleasewait", "[seconds]", seconds.ToString());
private static readonly LocalizedString readyCheckHeader = TextManager.Get("ReadyCheck.Title");
@@ -42,7 +42,7 @@ namespace Barotrauma
{
Vector2 relativeSize = new Vector2(0.2f / GUI.AspectRatioAdjustment, 0.15f);
Point minSize = new Point(300, 200);
msgBox = new GUIMessageBox(readyCheckHeader, readyCheckBody(author), new[] { yesButton, noButton }, relativeSize, minSize, type: GUIMessageBox.Type.Vote) { UserData = PromptData, Draggable = true };
msgBox = new GUIMessageBox(readyCheckHeader, ReadyCheckBody(author), new[] { yesButton, noButton }, relativeSize, minSize, type: GUIMessageBox.Type.Vote) { UserData = PromptData, Draggable = true };
GUILayoutGroup contentLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.125f), msgBox.Content.RectTransform), childAnchor: Anchor.Center);
new GUIProgressBar(new RectTransform(new Vector2(0.8f, 1f), contentLayout.RectTransform), 0.0f, GUIStyle.Orange) { UserData = TimerData };
@@ -82,9 +82,15 @@ namespace Barotrauma
GUIListBox listBox = new GUIListBox(new RectTransform(new Vector2(1f, 0.8f), resultsBox.Content.RectTransform)) { UserData = UserListData };
foreach (var (id, _) in Clients)
foreach (var (id, status) in Clients)
{
Client? client = GameMain.Client.ConnectedClients.FirstOrDefault(c => c.SessionId == id);
if (client == null)
{
string list = GameMain.Client.ConnectedClients.Aggregate("Available clients:\n", (current, c) => current + $"{c.SessionId}: {c.Name}\n");
DebugConsole.AddWarning($"Client ID {id} was reported in ready check but was not found.\n" + list.TrimEnd('\n'));
continue;
}
GUIFrame container = new GUIFrame(new RectTransform(new Vector2(1f, 0.15f), listBox.Content.RectTransform), style: "ListBoxElement") { UserData = id };
GUILayoutGroup frame = new GUILayoutGroup(new RectTransform(Vector2.One, container.RectTransform), isHorizontal: true) { Stretch = true };
@@ -92,11 +98,6 @@ namespace Barotrauma
JobPrefab? jobPrefab = client?.Character?.Info?.Job?.Prefab;
if (client == null)
{
string list = GameMain.Client.ConnectedClients.Aggregate("Available clients:\n", (current, c) => current + $"{c.SessionId}: {c.Name}\n");
DebugConsole.ThrowError($"Client ID {id} was reported in ready check but was not found.\n" + list.TrimEnd('\n'));
}
if (jobPrefab?.Icon != null)
{
@@ -105,7 +106,8 @@ namespace Barotrauma
}
new GUITextBlock(new RectTransform(new Vector2(0.75f, 1), frame.RectTransform), client?.Name ?? $"Unknown ID {id}", jobPrefab?.UIColor ?? Color.White, textAlignment: Alignment.Center) { AutoScaleHorizontal = true };
new GUIImage(new RectTransform(new Point(height, height), frame.RectTransform), null, scaleToFit: true) { UserData = ReadySpriteData };
var statusIcon = new GUIImage(new RectTransform(new Point(height, height), frame.RectTransform), null, scaleToFit: true) { UserData = ReadySpriteData };
UpdateStatusIcon(statusIcon, status);
}
resultsBox.Buttons[0].OnClicked = delegate
@@ -214,10 +216,7 @@ namespace Barotrauma
case ReadyCheckState.Update:
ReadyStatus newState = (ReadyStatus)inc.ReadByte();
byte targetId = inc.ReadByte();
if (crewManager.ActiveReadyCheck != null)
{
crewManager.ActiveReadyCheck?.UpdateState(targetId, newState);
}
crewManager.ActiveReadyCheck?.UpdateState(targetId, newState);
break;
case ReadyCheckState.End:
ushort count = inc.ReadUInt16();
@@ -242,7 +241,7 @@ namespace Barotrauma
int readyCount = Clients.Count(static pair => pair.Value == ReadyStatus.Yes);
int totalCount = Clients.Count;
GameMain.Client.AddChatMessage(ChatMessage.Create(string.Empty, readyCheckStatus(readyCount, totalCount).Value, ChatMessageType.Server, null));
GameMain.Client.AddChatMessage(ChatMessage.Create(string.Empty, ReadyCheckStatus(readyCount, totalCount).Value, ChatMessageType.Server, null));
}
private void UpdateState(byte id, ReadyStatus status)
@@ -253,31 +252,28 @@ namespace Barotrauma
}
if (resultsBox == null || resultsBox.Closed || !GUIMessageBox.MessageBoxes.Contains(resultsBox)) { return; }
if (resultsBox.Content.FindChild(UserListData) is not GUIListBox userList) { return; }
// for some reason FindChild doesn't work here?
foreach (GUIComponent child in userList.Content.Children)
var child = userList.Content.FindChild(id);
if (child?.GetChild<GUILayoutGroup>().FindChild(ReadySpriteData) is not GUIImage image) { return; }
UpdateStatusIcon(image, status);
}
private static void UpdateStatusIcon(GUIImage image, ReadyStatus status)
{
string style;
switch (status)
{
if (child.UserData is not byte b || b != id) { continue; }
if (child.GetChild<GUILayoutGroup>().FindChild(ReadySpriteData) is not GUIImage image) { continue; }
string style;
switch (status)
{
case ReadyStatus.Yes:
style = "MissionCompletedIcon";
break;
case ReadyStatus.No:
style = "MissionFailedIcon";
break;
default:
return;
}
image.ApplyStyle(GUIStyle.GetComponentStyle(style));
case ReadyStatus.Yes:
style = "MissionCompletedIcon";
break;
case ReadyStatus.No:
style = "MissionFailedIcon";
break;
default:
return;
}
image.ApplyStyle(GUIStyle.GetComponentStyle(style));
}
private static void SendState(ReadyStatus status)
@@ -303,7 +299,7 @@ namespace Barotrauma
return;
}
GUIMessageBox msgBox = new GUIMessageBox(readyCheckHeader, readyCheckPleaseWait((ReadyCheckCooldown - DateTime.Now).Seconds), new[] { closeButton });
GUIMessageBox msgBox = new GUIMessageBox(readyCheckHeader, ReadyCheckPleaseWait((ReadyCheckCooldown - DateTime.Now).Seconds), new[] { closeButton });
msgBox.Buttons[0].OnClicked = delegate
{
msgBox.Close();

View File

@@ -10,6 +10,9 @@ namespace Barotrauma
{
class RoundSummary
{
private float crewListAnimDelay = 0.25f;
private float missionIconAnimDelay;
private const float jobColumnWidthPercentage = 0.11f;
private const float characterColumnWidthPercentage = 0.44f;
private const float statusColumnWidthPercentage = 0.45f;
@@ -44,7 +47,7 @@ namespace Barotrauma
}
}
public GUIFrame CreateSummaryFrame(GameSession gameSession, string endMessage, List<TraitorMissionResult> traitorResults, CampaignMode.TransitionType transitionType = CampaignMode.TransitionType.None)
public GUIFrame CreateSummaryFrame(GameSession gameSession, string endMessage, CampaignMode.TransitionType transitionType = CampaignMode.TransitionType.None, TraitorManager.TraitorResults? traitorResults = null)
{
bool singleplayer = GameMain.NetworkMember == null;
bool gameOver =
@@ -70,7 +73,7 @@ namespace Barotrauma
//crew panel -------------------------------------------------------------------------------
GUIFrame crewFrame = new GUIFrame(new RectTransform(new Vector2(0.35f, 0.45f), background.RectTransform, Anchor.TopCenter, minSize: new Point(minWidth, minHeight)));
GUIFrame crewFrame = new GUIFrame(new RectTransform(new Vector2(0.35f, 0.4f), background.RectTransform, Anchor.TopCenter, minSize: new Point(minWidth, minHeight)));
GUIFrame crewFrameInner = new GUIFrame(new RectTransform(new Point(crewFrame.Rect.Width - padding * 2, crewFrame.Rect.Height - padding * 2), crewFrame.RectTransform, Anchor.Center), style: "InnerFrame");
var crewContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), crewFrameInner.RectTransform, Anchor.Center))
@@ -82,7 +85,14 @@ namespace Barotrauma
TextManager.Get("crew"), textAlignment: Alignment.TopLeft, font: GUIStyle.SubHeadingFont);
crewHeader.RectTransform.MinSize = new Point(0, GUI.IntScale(crewHeader.Rect.Height * 2.0f));
CreateCrewList(crewContent, gameSession.CrewManager.GetCharacterInfos().Where(c => c.TeamID != CharacterTeamType.Team2));
var crewList = CreateCrewList(crewContent, gameSession.CrewManager.GetCharacterInfos().Where(c => c.TeamID != CharacterTeamType.Team2), traitorResults);
if (traitorResults != null && traitorResults.Value.VotedAsTraitorClientSessionId > 0)
{
var traitorInfoPanel = CreateTraitorInfoPanel(crewList.Content, traitorResults.Value, crewListAnimDelay);
traitorInfoPanel.RectTransform.SetAsFirstChild();
var spacing = new GUIFrame(new RectTransform(new Point(0, GUI.IntScale(20)), crewList.Content.RectTransform), style: null);
spacing.RectTransform.RepositionChildInHierarchy(1);
}
//another crew frame for the 2nd team in combat missions
if (gameSession.Missions.Any(m => m is CombatMission))
@@ -98,7 +108,7 @@ namespace Barotrauma
var crewHeader2 = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), crewContent2.RectTransform),
CombatMission.GetTeamName(CharacterTeamType.Team2), textAlignment: Alignment.TopLeft, font: GUIStyle.SubHeadingFont);
crewHeader2.RectTransform.MinSize = new Point(0, GUI.IntScale(crewHeader2.Rect.Height * 2.0f));
CreateCrewList(crewContent2, gameSession.CrewManager.GetCharacterInfos().Where(c => c.TeamID == CharacterTeamType.Team2));
CreateCrewList(crewContent2, gameSession.CrewManager.GetCharacterInfos().Where(c => c.TeamID == CharacterTeamType.Team2), traitorResults);
}
//header -------------------------------------------------------------------------------
@@ -111,71 +121,6 @@ namespace Barotrauma
headerText, textAlignment: Alignment.BottomLeft, font: GUIStyle.LargeFont, wrap: true);
}
//traitor panel -------------------------------------------------------------------------------
if (traitorResults != null && traitorResults.Any())
{
GUIFrame traitorframe = new GUIFrame(new RectTransform(crewFrame.RectTransform.RelativeSize, background.RectTransform, Anchor.TopCenter, minSize: crewFrame.RectTransform.MinSize));
rightPanels.Add(traitorframe);
GUIFrame traitorframeInner = new GUIFrame(new RectTransform(new Point(traitorframe.Rect.Width - padding * 2, traitorframe.Rect.Height - padding * 2), traitorframe.RectTransform, Anchor.Center), style: "InnerFrame");
var traitorContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), traitorframeInner.RectTransform, Anchor.Center))
{
Stretch = true
};
var traitorHeader = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), traitorContent.RectTransform),
TextManager.Get("traitors"), font: GUIStyle.SubHeadingFont);
traitorHeader.RectTransform.MinSize = new Point(0, GUI.IntScale(traitorHeader.Rect.Height * 2.0f));
GUIListBox listBox = CreateCrewList(traitorContent, traitorResults.SelectMany(tr => tr.Characters.Select(c => c.Info)));
foreach (var traitorResult in traitorResults)
{
var traitorMission = TraitorMissionPrefab.Prefabs.Find(t => t.Identifier == traitorResult.MissionIdentifier);
if (traitorMission == null) { continue; }
//spacing
new GUIFrame(new RectTransform(new Point(listBox.Content.Rect.Width, GUI.IntScale(25)), listBox.Content.RectTransform), style: null);
var traitorResultHorizontal = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.3f), listBox.Content.RectTransform), childAnchor: Anchor.CenterLeft, isHorizontal: true)
{
RelativeSpacing = 0.05f,
Stretch = true
};
new GUIImage(new RectTransform(new Point(traitorResultHorizontal.Rect.Height), traitorResultHorizontal.RectTransform), traitorMission.Icon, scaleToFit: true)
{
Color = traitorMission.IconColor
};
LocalizedString traitorMessage = TextManager.GetServerMessage(traitorResult.EndMessage);
if (!traitorMessage.IsNullOrEmpty())
{
var textContent = new GUILayoutGroup(new RectTransform(Vector2.One, traitorResultHorizontal.RectTransform))
{
RelativeSpacing = 0.025f
};
var traitorStatusText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), textContent.RectTransform),
TextManager.Get(traitorResult.Success ? "missioncompleted" : "missionfailed"),
textColor: traitorResult.Success ? GUIStyle.Green : GUIStyle.Red, font: GUIStyle.SubHeadingFont);
var traitorMissionInfo = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), textContent.RectTransform),
traitorMessage, font: GUIStyle.SmallFont, wrap: true);
traitorResultHorizontal.Recalculate();
traitorStatusText.CalculateHeightFromText();
traitorMissionInfo.CalculateHeightFromText();
traitorStatusText.RectTransform.MinSize = new Point(0, traitorStatusText.Rect.Height);
traitorMissionInfo.RectTransform.MinSize = new Point(0, traitorMissionInfo.Rect.Height);
textContent.RectTransform.MaxSize = new Point(int.MaxValue, (int)((traitorStatusText.Rect.Height + traitorMissionInfo.Rect.Height) * 1.2f));
traitorResultHorizontal.RectTransform.MinSize = new Point(0, traitorStatusText.RectTransform.MinSize.Y + traitorMissionInfo.RectTransform.MinSize.Y);
}
}
}
//reputation panel -------------------------------------------------------------------------------
var campaignMode = gameMode as CampaignMode;
@@ -185,7 +130,7 @@ namespace Barotrauma
rightPanels.Add(reputationframe);
GUIFrame reputationframeInner = new GUIFrame(new RectTransform(new Point(reputationframe.Rect.Width - padding * 2, reputationframe.Rect.Height - padding * 2), reputationframe.RectTransform, Anchor.Center), style: "InnerFrame");
var reputationContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), reputationframeInner.RectTransform, Anchor.Center))
var reputationContent = new GUILayoutGroup(new RectTransform(new Vector2(0.98f, 0.95f), reputationframeInner.RectTransform, Anchor.Center))
{
Stretch = true
};
@@ -199,15 +144,15 @@ namespace Barotrauma
//mission panel -------------------------------------------------------------------------------
GUIFrame missionframe = new GUIFrame(new RectTransform(new Vector2(0.39f, 0.3f), background.RectTransform, Anchor.TopCenter, minSize: new Point(minWidth, minHeight / 4)));
GUIFrame missionframe = new GUIFrame(new RectTransform(new Vector2(0.5f, 0.4f), background.RectTransform, Anchor.TopCenter, minSize: new Point(minWidth, minHeight / 4)));
GUILayoutGroup missionFrameContent = new GUILayoutGroup(new RectTransform(new Point(missionframe.Rect.Width - padding * 2, missionframe.Rect.Height - padding * 2), missionframe.RectTransform, Anchor.Center))
{
Stretch = true,
RelativeSpacing = 0.05f
RelativeSpacing = 0.03f
};
GUIFrame missionframeInner = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.9f), missionFrameContent.RectTransform, Anchor.Center), style: "InnerFrame");
var missionContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.93f), missionframeInner.RectTransform, Anchor.Center))
var missionContent = new GUILayoutGroup(new RectTransform(new Vector2(0.98f, 0.93f), missionframeInner.RectTransform, Anchor.Center))
{
Stretch = true
};
@@ -227,16 +172,9 @@ namespace Barotrauma
}
}
if (missionsToDisplay.Any())
{
var missionHeader = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionContent.RectTransform),
TextManager.Get(missionsToDisplay.Count > 1 ? "Missions" : "Mission"), textAlignment: Alignment.TopLeft, font: GUIStyle.SubHeadingFont);
missionHeader.RectTransform.MinSize = new Point(0, (int)(missionHeader.Rect.Height * 1.2f));
}
GUIListBox missionList = new GUIListBox(new RectTransform(Vector2.One, missionContent.RectTransform, Anchor.Center))
{
Padding = new Vector4(4, 10, 0, 0) * GUI.Scale
Spacing = GUI.IntScale(15)
};
missionList.ContentBackground.Color = Color.Transparent;
@@ -255,120 +193,76 @@ namespace Barotrauma
{
CanBeFocused = false
};
endText.RectTransform.MinSize = new Point(0, endText.Rect.Height);
var line = new GUIFrame(new RectTransform(new Vector2(0.5f, 0.1f), missionList.Content.RectTransform), style: "HorizontalLine");
line.RectTransform.NonScaledSize = new Point(line.Rect.Width, GUI.IntScale(5.0f));
}
foreach (Mission displayedMission in missionsToDisplay)
float animDelay = missionIconAnimDelay;
foreach (Mission mission in missionsToDisplay)
{
var missionContentHorizontal = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.8f), missionList.Content.RectTransform), childAnchor: Anchor.CenterLeft, isHorizontal: true)
{
RelativeSpacing = 0.025f,
Stretch = true
};
var textContent = new List<LocalizedString>();
LocalizedString missionMessage =
selectedMissions.Contains(displayedMission) ?
displayedMission.Completed ? displayedMission.SuccessMessage : displayedMission.FailureMessage :
displayedMission.Description;
GUIImage missionIcon = new GUIImage(new RectTransform(new Point((int)(missionContentHorizontal.Rect.Height)), missionContentHorizontal.RectTransform), displayedMission.Prefab.Icon, scaleToFit: true)
if (selectedMissions.Contains(mission))
{
Color = displayedMission.Prefab.IconColor,
HoverColor = displayedMission.Prefab.IconColor,
SelectedColor = displayedMission.Prefab.IconColor
};
missionIcon.RectTransform.MinSize = new Point((int)(missionContentHorizontal.Rect.Height * 0.9f));
if (selectedMissions.Contains(displayedMission))
{
new GUIImage(new RectTransform(Vector2.One, missionIcon.RectTransform), displayedMission.Completed ? "MissionCompletedIcon" : "MissionFailedIcon", scaleToFit: true);
}
textContent.Add(mission.Completed ? mission.SuccessMessage : mission.FailureMessage);
var missionTextContent = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 1.0f), missionContentHorizontal.RectTransform))
{
AbsoluteSpacing = GUI.IntScale(5)
};
missionContentHorizontal.Recalculate();
var missionNameTextBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform),
displayedMission.Name, font: GUIStyle.SubHeadingFont);
if (displayedMission.Difficulty.HasValue)
{
var groupSize = missionNameTextBlock.Rect.Size;
groupSize.X -= (int)(missionNameTextBlock.Padding.X + missionNameTextBlock.Padding.Z);
var indicatorGroup = new GUILayoutGroup(new RectTransform(groupSize, missionTextContent.RectTransform) { AbsoluteOffset = new Point((int)missionNameTextBlock.Padding.X, 0) },
isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
AbsoluteSpacing = 1
};
var difficultyColor = displayedMission.GetDifficultyColor();
for (int i = 0; i < displayedMission.Difficulty; i++)
{
new GUIImage(new RectTransform(Vector2.One, indicatorGroup.RectTransform, scaleBasis: ScaleBasis.Smallest) { IsFixedSize = true }, "DifficultyIndicator", scaleToFit: true)
{
CanBeFocused = false,
Color = difficultyColor
};
}
}
var repText = mission.GetReputationRewardText();
if (!repText.IsNullOrEmpty()) { textContent.Add(repText); }
var missionDescription = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform),
RichString.Rich(missionMessage), wrap: true);
if (selectedMissions.Contains(displayedMission))
{
RichString reputationText = displayedMission.GetReputationRewardText();
if (!reputationText.IsNullOrEmpty())
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), reputationText, wrap: true);
}
int totalReward = displayedMission.GetFinalReward(Submarine.MainSub);
int totalReward = mission.GetFinalReward(Submarine.MainSub);
if (totalReward > 0)
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), RichString.Rich(displayedMission.GetMissionRewardText(Submarine.MainSub)));
if (GameMain.IsMultiplayer && Character.Controlled is { } controlled && displayedMission.Completed)
textContent.Add(mission.GetMissionRewardText(Submarine.MainSub));
if (GameMain.IsMultiplayer && Character.Controlled is { } controlled && mission.Completed)
{
var (share, percentage, _) = Mission.GetRewardShare(controlled.Wallet.RewardDistribution, GameSession.GetSessionCrewCharacters(CharacterType.Player).Where(c => c != controlled), Option<int>.Some(totalReward));
if (share > 0)
{
string shareFormatted = string.Format(CultureInfo.InvariantCulture, "{0:N0}", share);
RichString yourShareString = RichString.Rich(TextManager.GetWithVariables("crewwallet.missionreward.get", ("[money]", $"{shareFormatted}"), ("[share]", $"{percentage}")));
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), yourShareString);
RichString yourShareString = TextManager.GetWithVariables("crewwallet.missionreward.get", ("[money]", $"{shareFormatted}"), ("[share]", $"{percentage}"));
textContent.Add(yourShareString);
}
}
}
}
if (displayedMission != missionsToDisplay.Last())
else
{
var spacing = new GUIFrame(new RectTransform(new Vector2(1.0f, 1.0f), missionList.Content.RectTransform) { MaxSize = new Point(int.MaxValue, GUI.IntScale(15)) }, style: null);
new GUIFrame(new RectTransform(new Vector2(0.8f, 1.0f), spacing.RectTransform, Anchor.Center) { RelativeOffset = new Vector2(0.1f, 0.0f) }, "HorizontalLine");
var repText = mission.GetReputationRewardText();
if (!repText.IsNullOrEmpty()) { textContent.Add(repText); }
textContent.Add(mission.GetMissionRewardText(Submarine.MainSub));
textContent.Add(mission.Description);
textContent.AddRange(mission.ShownMessages);
}
foreach (GUIComponent child in missionTextContent.Children)
CreateMissionEntry(
missionList.Content,
mission.Name,
textContent,
mission.Difficulty ?? 0,
mission.Prefab.Icon, mission.Prefab.IconColor,
out GUIImage missionIcon);
if (selectedMissions.Contains(mission))
{
child.RectTransform.IsFixedSize = true;
UpdateMissionStateIcon(mission.Completed, missionIcon, animDelay);
animDelay += 0.25f;
}
missionTextContent.RectTransform.MinSize = new Point(0, missionTextContent.Children.Sum(c => c.Rect.Height + missionTextContent.AbsoluteSpacing));
missionContentHorizontal.RectTransform.MinSize = new Point(0, (int)(missionTextContent.Rect.Height / missionTextContent.RectTransform.RelativeSize.Y));
}
if (!missionsToDisplay.Any())
{
var missionContentHorizontal = new GUILayoutGroup(new RectTransform(Vector2.One, missionList.Content.RectTransform), childAnchor: Anchor.TopLeft, isHorizontal: true)
var missionContentHorizontal = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.4f), missionList.Content.RectTransform), childAnchor: Anchor.TopLeft, isHorizontal: true)
{
RelativeSpacing = 0.025f,
Stretch = true
Stretch = true,
CanBeFocused = true
};
GUIImage missionIcon = new GUIImage(new RectTransform(new Point((int)(missionContentHorizontal.Rect.Height * 0.7f)), missionContentHorizontal.RectTransform), style: "NoMissionIcon", scaleToFit: true);
missionIcon.RectTransform.MinSize = new Point((int)(missionContentHorizontal.Rect.Height * 0.7f));
GUIImage missionIcon = new GUIImage(new RectTransform(new Point(missionContentHorizontal.Rect.Height), missionContentHorizontal.RectTransform), style: "NoMissionIcon", scaleToFit: true);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionContentHorizontal.RectTransform),
TextManager.Get("nomission"), font: GUIStyle.LargeFont);
}
/*missionContentHorizontal.Recalculate();
missionContent.Recalculate();
missionIcon.RectTransform.MinSize = new Point(0, missionContentHorizontal.Rect.Height);
missionTextContent.RectTransform.MaxSize = new Point(int.MaxValue, missionIcon.Rect.Width);*/
gameSession?.EventManager?.EventLog?.CreateEventLogUI(missionList.Content, traitorResults);
AddSeparators(missionList.Content);
ContinueButton = new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), ButtonArea.RectTransform), TextManager.Get("Close"));
ButtonArea.RectTransform.NonScaledSize = new Point(ButtonArea.Rect.Width, ContinueButton.Rect.Height);
@@ -405,10 +299,7 @@ namespace Barotrauma
public void CreateReputationInfoPanel(GUIComponent parent, CampaignMode campaignMode)
{
GUIListBox reputationList = new GUIListBox(new RectTransform(Vector2.One, parent.RectTransform))
{
Padding = new Vector4(4, 10, 0, 0) * GUI.Scale
};
GUIListBox reputationList = new GUIListBox(new RectTransform(Vector2.One, parent.RectTransform));
reputationList.ContentBackground.Color = Color.Transparent;
foreach (Faction faction in campaignMode.Factions.OrderBy(f => f.Prefab.MenuOrder).ThenBy(f => f.Prefab.Name))
@@ -506,6 +397,171 @@ namespace Barotrauma
}
}
private static GUIComponent CreateTraitorInfoPanel(GUIComponent parent, TraitorManager.TraitorResults traitorResults, float iconAnimDelay)
{
var traitorCharacter = traitorResults.GetTraitorClient()?.Character;
string resultTag =
traitorResults.VotedCorrectTraitor ?
traitorResults.ObjectiveSuccessful ? "traitor.blameresult.correct.objectivesuccessful" : "traitor.blameresult.correct.objectivefailed" :
"traitor.blameresult.failure";
var textContent = new List<LocalizedString>()
{
TextManager.GetWithVariable("traitor.blameresult", "[name]", traitorCharacter?.Name ?? "unknown"),
TextManager.Get(resultTag)
};
if (traitorResults.MoneyPenalty > 0)
{
textContent.Add(
TextManager.GetWithVariable(
"traitor.blameresult.failure.penalty",
"[money]",
TextManager.FormatCurrency(traitorResults.MoneyPenalty, includeCurrencySymbol: false)));
}
var icon = GUIStyle.GetComponentStyle("TraitorMissionIcon")?.GetDefaultSprite();
var content = CreateMissionEntry(
parent,
string.Empty,
textContent,
difficultyIconCount: 0,
icon, GUIStyle.Red,
out GUIImage missionIcon);
UpdateMissionStateIcon(traitorResults.VotedCorrectTraitor, missionIcon, iconAnimDelay);
return content;
}
public static GUIComponent CreateMissionEntry(GUIComponent parent, LocalizedString header, List<LocalizedString> textContent, int difficultyIconCount,
Sprite icon, Color iconColor, out GUIImage missionIcon)
{
int spacing = GUI.IntScale(5);
int defaultLineHeight = (int)GUIStyle.Font.MeasureChar('T').Y;
//make the icon big enough for header + some lines of text
int iconSize = (int)(GUIStyle.SubHeadingFont.MeasureChar('T').Y + defaultLineHeight * 6);
GUILayoutGroup content = new GUILayoutGroup(new RectTransform(new Point(parent.Rect.Width, iconSize), parent.RectTransform), isHorizontal: true)
{
Stretch = true,
AbsoluteSpacing = spacing,
CanBeFocused = true
};
if (icon != null)
{
missionIcon = new GUIImage(new RectTransform(new Point(iconSize), content.RectTransform), icon, null, true)
{
Color = iconColor,
HoverColor = iconColor,
SelectedColor = iconColor,
CanBeFocused = false
};
missionIcon.RectTransform.IsFixedSize = true;
}
else
{
missionIcon = null;
}
GUILayoutGroup missionTextGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.744f, 0f), content.RectTransform, Anchor.CenterLeft), childAnchor: Anchor.TopLeft)
{
AbsoluteSpacing = spacing
};
content.Recalculate();
RichString missionNameString = RichString.Rich(header);
List<RichString> contentStrings = new List<RichString>(textContent.Select(t => RichString.Rich(t)));
if (!header.IsNullOrEmpty())
{
var nameText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextGroup.RectTransform),
missionNameString, font: GUIStyle.SubHeadingFont, wrap: true);
nameText.RectTransform.MinSize = new Point(0, (int)nameText.TextSize.Y);
}
GUILayoutGroup difficultyIndicatorGroup = null;
if (difficultyIconCount > 0)
{
difficultyIndicatorGroup = new GUILayoutGroup(new RectTransform(new Point(missionTextGroup.Rect.Width, defaultLineHeight), parent: missionTextGroup.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
AbsoluteSpacing = 1
};
difficultyIndicatorGroup.RectTransform.MinSize = new Point(0, defaultLineHeight);
var difficultyColor = Mission.GetDifficultyColor(difficultyIconCount);
for (int i = 0; i < difficultyIconCount; i++)
{
new GUIImage(new RectTransform(Vector2.One, difficultyIndicatorGroup.RectTransform, scaleBasis: ScaleBasis.Smallest), "DifficultyIndicator", scaleToFit: true)
{
CanBeFocused = false,
Color = difficultyColor
};
}
}
GUITextBlock firstContentText = null;
foreach (var contentString in contentStrings)
{
var text = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextGroup.RectTransform), contentString, wrap: true);
text.RectTransform.MinSize = new Point(0, (int)text.TextSize.Y);
firstContentText ??= text;
}
if (difficultyIndicatorGroup != null && firstContentText != null)
{
//make the icons align with the text content
difficultyIndicatorGroup.RectTransform.AbsoluteOffset = new Point((int)firstContentText.Padding.X, 0);
}
missionTextGroup.RectTransform.MinSize =
new Point(0, missionTextGroup.Children.Sum(c => c.Rect.Height + missionTextGroup.AbsoluteSpacing) - missionTextGroup.AbsoluteSpacing);
missionTextGroup.Recalculate();
content.RectTransform.MinSize = new Point(0, Math.Max(missionTextGroup.Rect.Height, iconSize));
return content;
}
public static void AddSeparators(GUIComponent container)
{
var children = container.Children.ToList();
if (children.Count < 2) { return; }
var lastChild = children.Last();
foreach (var child in children)
{
if (child != lastChild)
{
var separator = new GUIFrame(new RectTransform(new Vector2(0.5f, 0.1f), container.RectTransform), style: "HorizontalLine");
separator.RectTransform.RepositionChildInHierarchy(container.GetChildIndex(child) + 1);
}
}
}
public static void UpdateMissionStateIcon(bool success, GUIImage missionIcon, float delay = 0.5f)
{
if (missionIcon == null) { return; }
string style = success ? "MissionCompletedIcon" : "MissionFailedIcon";
GUIImage stateIcon = missionIcon.GetChild<GUIImage>();
if (string.IsNullOrEmpty(style))
{
if (stateIcon != null)
{
stateIcon.Visible = false;
}
}
else
{
bool wasVisible = stateIcon is { Visible: true };
stateIcon ??= new GUIImage(new RectTransform(Vector2.One, missionIcon.RectTransform, Anchor.Center), style, scaleToFit: true);
stateIcon.Visible = true;
if (!wasVisible)
{
stateIcon.FadeIn(delay, 0.15f);
stateIcon.Pulsate(Vector2.One, Vector2.One * 1.5f, 1.0f + delay);
}
}
}
private LocalizedString GetHeaderText(bool gameOver, CampaignMode.TransitionType transitionType)
{
string locationName = Submarine.MainSub is { AtEndExit: true } ? endLocation?.Name : startLocation?.Name;
@@ -568,7 +624,7 @@ namespace Barotrauma
return TextManager.GetWithVariables(textTag, ("[sub]", subName), ("[location]", locationName));
}
private GUIListBox CreateCrewList(GUIComponent parent, IEnumerable<CharacterInfo> characterInfos)
private GUIListBox CreateCrewList(GUIComponent parent, IEnumerable<CharacterInfo> characterInfos, TraitorManager.TraitorResults? traitorResults)
{
var headerFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.0f), parent.RectTransform, Anchor.TopCenter, minSize: new Point(0, (int)(30 * GUI.Scale))) { }, isHorizontal: true)
{
@@ -602,16 +658,19 @@ namespace Barotrauma
headerFrame.RectTransform.RelativeSize -= new Vector2(crewList.ScrollBar.RectTransform.RelativeSize.X, 0.0f);
float delay = crewListAnimDelay;
foreach (CharacterInfo characterInfo in characterInfos)
{
if (characterInfo == null) { continue; }
CreateCharacterElement(characterInfo, crewList);
CreateCharacterElement(characterInfo, crewList, traitorResults, delay);
delay += crewListAnimDelay;
}
missionIconAnimDelay = delay;
return crewList;
}
private void CreateCharacterElement(CharacterInfo characterInfo, GUIListBox listBox)
private void CreateCharacterElement(CharacterInfo characterInfo, GUIListBox listBox, TraitorManager.TraitorResults? traitorResults, float animDelay)
{
GUIFrame frame = new GUIFrame(new RectTransform(new Point(listBox.Content.Rect.Width, GUI.IntScale(45)), listBox.Content.RectTransform), style: "ListBoxElement")
{
@@ -684,9 +743,31 @@ namespace Barotrauma
GUITextBlock statusBlock = new GUITextBlock(new RectTransform(new Point(statusColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform),
ToolBox.LimitString(statusText.Value, GUIStyle.Font, characterColumnWidth), textAlignment: Alignment.Center, textColor: statusColor);
frame.FadeIn(animDelay, 0.15f);
foreach (var child in frame.GetAllChildren())
{
child.FadeIn(animDelay, 0.15f);
}
if (traitorResults.HasValue && GameMain.NetworkMember != null)
{
var clientVotedAsTraitor = traitorResults.Value.GetTraitorClient();
bool isTraitor = clientVotedAsTraitor != null && clientVotedAsTraitor.Character == character;
if (isTraitor)
{
var img = new GUIImage(new RectTransform(new Point(paddedFrame.Rect.Height), paddedFrame.RectTransform, Anchor.CenterRight), style: "TraitorVoteButton")
{
IgnoreLayoutGroups = true,
ToolTip = TextManager.GetWithVariable("traitor.blameresult", "[name]", characterInfo.Name)
};
img.FadeIn(1.0f + animDelay, 0.15f);
img.Pulsate(Vector2.One, Vector2.One * 1.5f, 1.5f + animDelay);
}
}
}
private GUIFrame CreateReputationElement(GUIComponent parent,
private static GUIFrame CreateReputationElement(GUIComponent parent,
LocalizedString name, Reputation reputation, float initialReputation,
LocalizedString shortDescription, LocalizedString fullDescription, Sprite icon, Sprite backgroundPortrait, Color iconColor)
{

View File

@@ -1,5 +1,6 @@
using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
@@ -42,16 +43,29 @@ namespace Barotrauma
if (limbSlotIcons == null)
{
limbSlotIcons = new Dictionary<InvSlotType, Sprite>();
int margin = 2;
limbSlotIcons.Add(InvSlotType.Headset, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(384 + margin, 128 + margin, 128 - margin * 2, 128 - margin * 2)));
limbSlotIcons.Add(InvSlotType.InnerClothes, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(512 + margin, 128 + margin, 128 - margin * 2, 128 - margin * 2)));
limbSlotIcons.Add(InvSlotType.Card, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(640 + margin, 128 + margin, 128 - margin * 2, 128 - margin * 2)));
foreach (InvSlotType invSlotType in Enum.GetValues(typeof(InvSlotType)))
{
var sprite = GUIStyle.GetComponentStyle($"InventorySlot.{invSlotType}")?.GetDefaultSprite();
if (sprite != null)
{
limbSlotIcons.Add(invSlotType, sprite);
}
}
limbSlotIcons.Add(InvSlotType.Head, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(896 + margin, 128 + margin, 128 - margin * 2, 128 - margin * 2)));
limbSlotIcons.Add(InvSlotType.LeftHand, new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(634, 0, 128, 128)));
limbSlotIcons.Add(InvSlotType.RightHand, new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(762, 0, 128, 128)));
limbSlotIcons.Add(InvSlotType.OuterClothes, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(256 + margin, 128 + margin, 128 - margin * 2, 128 - margin * 2)));
limbSlotIcons.Add(InvSlotType.Bag, new Sprite("Content/UI/CommandUIAtlas.png", new Rectangle(639, 926, 128,80)));
int margin = 2;
AddIfMissing(InvSlotType.Headset, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(384 + margin, 128 + margin, 128 - margin * 2, 128 - margin * 2)));
AddIfMissing(InvSlotType.InnerClothes, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(512 + margin, 128 + margin, 128 - margin * 2, 128 - margin * 2)));
AddIfMissing(InvSlotType.Card, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(640 + margin, 128 + margin, 128 - margin * 2, 128 - margin * 2)));
AddIfMissing(InvSlotType.Head, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(896 + margin, 128 + margin, 128 - margin * 2, 128 - margin * 2)));
AddIfMissing(InvSlotType.LeftHand, new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(634, 0, 128, 128)));
AddIfMissing(InvSlotType.RightHand, new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(762, 0, 128, 128)));
AddIfMissing(InvSlotType.OuterClothes, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(256 + margin, 128 + margin, 128 - margin * 2, 128 - margin * 2)));
AddIfMissing(InvSlotType.Bag, new Sprite("Content/UI/CommandUIAtlas.png", new Rectangle(639, 926, 128,80)));
static void AddIfMissing(InvSlotType slotType, Sprite sprite)
{
limbSlotIcons.TryAdd(slotType, sprite);
}
}
return limbSlotIcons;
}
@@ -179,11 +193,30 @@ namespace Barotrauma
BackgroundFrame = frame;
}
protected override bool HideSlot(int i)
public override bool HideSlot(int i)
{
if (visualSlots[i].Disabled || (slots[i].HideIfEmpty && slots[i].Empty())) { return true; }
if (CharacterHealth.OpenHealthWindow != Character.Controlled?.CharacterHealth && SlotTypes[i] == InvSlotType.HealthInterface) { return true; }
if (SlotTypes[i] == InvSlotType.HealthInterface)
{
//hide health interface slot unless this character's health window is open
if (CharacterHealth.OpenHealthWindow == null || Character.Controlled == null)
{
return true;
}
if (character == Character.Controlled)
{
bool ownHealthWindowOpen = CharacterHealth.OpenHealthWindow == Character.Controlled.CharacterHealth;
if (!ownHealthWindowOpen) { return true; }
}
else if (character == Character.Controlled.SelectedCharacter)
{
bool otherCharacterHealthWindowOpen = CharacterHealth.OpenHealthWindow == Character.Controlled?.SelectedCharacter?.CharacterHealth;
if (!otherCharacterHealthWindowOpen) { return true; }
//can't access the health interface slot of a non-incapacitated player (bots are fine though)
if (character.IsPlayer && !character.IsIncapacitated) { return true; }
}
}
if (layout == Layout.Default)
{
@@ -216,6 +249,11 @@ namespace Barotrauma
return false;
}
public void RefreshSlotPositions()
{
SetSlotPositions(CurrentLayout);
}
private void SetSlotPositions(Layout layout)
{
int spacing = GUI.IntScale(5);
@@ -450,6 +488,9 @@ namespace Barotrauma
((s.SlotIndex < 0 || s.SlotIndex >= slots.Length || slots[s.SlotIndex] == null) || (Character.Controlled != null && !Character.Controlled.CanAccessInventory(s.Inventory))));
//remove highlighted subinventory slots that refer to items no longer in this inventory
highlightedSubInventorySlots.RemoveWhere(s => s.Item != null && s.ParentInventory == this && s.Item.ParentInventory != this);
//remove highlighted subinventory slots if we're dragging that item out of the inventory
highlightedSubInventorySlots.RemoveWhere(s => s.Item != null && s.ParentInventory == this && DraggingItems.Contains(s.Item));
tempHighlightedSubInventorySlots.Clear();
tempHighlightedSubInventorySlots.AddRange(highlightedSubInventorySlots);
foreach (var highlightedSubInventorySlot in tempHighlightedSubInventorySlots)
@@ -525,6 +566,7 @@ namespace Barotrauma
var itemContainer = item.GetComponent<ItemContainer>();
if (itemContainer != null &&
itemContainer.KeepOpenWhenEquippedBy(character) &&
!DraggingItems.Contains(item) &&
character.CanAccessInventory(itemContainer.Inventory) &&
!highlightedSubInventorySlots.Any(s => s.Inventory == itemContainer.Inventory))
{
@@ -538,9 +580,11 @@ namespace Barotrauma
if (doubleClickedItems.Any())
{
var quickUseAction = GetQuickUseAction(doubleClickedItems.First(), true, true, true);
int itemCount = 0;
foreach (Item doubleClickedItem in doubleClickedItems)
{
QuickUseItem(doubleClickedItem, true, true, true, quickUseAction, playSound: doubleClickedItem == doubleClickedItems.First());
itemCount++;
//only use one item if we're equipping or using it as a treatment
if (quickUseAction == QuickUseAction.Equip || quickUseAction == QuickUseAction.UseTreatment)
{
@@ -556,6 +600,12 @@ namespace Barotrauma
{
break;
}
if ((quickUseAction == QuickUseAction.TakeFromContainer || quickUseAction == QuickUseAction.PutToEquippedItem) &&
doubleClickedItem.ParentInventory != null &&
itemCount >= doubleClickedItem.Prefab.GetMaxStackSize(doubleClickedItem.ParentInventory))
{
break;
}
}
}
@@ -789,10 +839,30 @@ namespace Barotrauma
{
return QuickUseAction.TakeFromCharacter;
}
else if (character.HeldItems.Any(i =>
else if (character.HeldItems.FirstOrDefault(i =>
i.OwnInventory != null &&
(i.OwnInventory.CanBePut(item) || ((i.OwnInventory.Capacity == 1 || i.OwnInventory.Container.HasSubContainers) && i.OwnInventory.AllowSwappingContainedItems && i.OwnInventory.Container.CanBeContained(item)))))
(i.OwnInventory.CanBePut(item) || ((i.OwnInventory.Capacity == 1 || i.OwnInventory.Container.HasSubContainers) && i.OwnInventory.AllowSwappingContainedItems && i.OwnInventory.Container.CanBeContained(item)))) is { } equippedContainer)
{
if (allowEquip)
{
if (!character.HasEquippedItem(item))
{
if (equippedContainer.GetComponent<ItemContainer>() is { QuickUseMovesItemsInside: false})
{
//put the item in a hand slot if that hand is free
if ((item.AllowedSlots.Contains(InvSlotType.RightHand) && character.Inventory.GetItemInLimbSlot(InvSlotType.RightHand) == null) ||
(item.AllowedSlots.Contains(InvSlotType.LeftHand) && character.Inventory.GetItemInLimbSlot(InvSlotType.LeftHand) == null))
{
return QuickUseAction.Equip;
}
}
}
//equipped -> attempt to unequip
else if (item.AllowedSlots.Contains(InvSlotType.Any))
{
return QuickUseAction.Unequip;
}
}
return QuickUseAction.PutToEquippedItem;
}
else if (allowEquip) //doubleclicked and no other inventory is selected
@@ -1171,5 +1241,11 @@ namespace Barotrauma
GUIComponent.DrawToolTip(spriteBatch, highlightedQuickUseSlot.QuickUseButtonToolTip, highlightedQuickUseSlot.EquipButtonRect);
}
}
public void ClientEventWrite(IWriteMessage msg, Character.InventoryStateEventData extraData)
{
SharedWrite(msg, extraData.SlotRange);
syncItemsDelay = 1.0f;
}
}
}

View File

@@ -185,9 +185,9 @@ namespace Barotrauma.Items.Components
}
}
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1)
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1, Color? overrideColor = null)
{
Color color = item.GetSpriteColor(withHighlight: true);
Color color = overrideColor ?? item.GetSpriteColor(withHighlight: true);
if (brokenSprite == null)
{
//broken doors turn black if no broken sprite has been configured
@@ -202,7 +202,7 @@ namespace Barotrauma.Items.Components
weldSpritePos.Y = -weldSpritePos.Y;
weldedSprite.Draw(spriteBatch,
weldSpritePos, item.SpriteColor * (stuck / 100.0f), scale: item.Scale);
weldSpritePos, overrideColor ?? (item.SpriteColor * (stuck / 100.0f)), scale: item.Scale);
}
if (openState >= 1.0f) { return; }

View File

@@ -8,7 +8,7 @@ namespace Barotrauma.Items.Components
{
public Vector2 DrawSize => Vector2.Zero;
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1)
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1, Color? overrideColor = null)
{
if (!editing) { return; }

View File

@@ -13,7 +13,7 @@ namespace Barotrauma.Items.Components
get { return item.Rect.Size.ToVector2(); }
}
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1)
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1, Color? overrideColor = null)
{
if (!IsActive || picker == null || !CanBeAttached(picker) || !picker.IsKeyDown(InputType.Aim) || picker != Character.Controlled)
{
@@ -50,7 +50,17 @@ namespace Barotrauma.Items.Components
Submarine.DrawGrid(spriteBatch, 14, gridPos, roundedGridPos, alpha: 0.4f);
item.Sprite.Draw(
Sprite sprite = item.Sprite;
foreach (ContainedItemSprite containedSprite in item.Prefab.ContainedSprites)
{
if (containedSprite.UseWhenAttached)
{
sprite = containedSprite.Sprite;
break;
}
}
sprite.Draw(
spriteBatch,
new Vector2(attachPos.X, -attachPos.Y),
item.SpriteColor * 0.5f,

View File

@@ -1,11 +1,8 @@
using System;
using Barotrauma.IO;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Extensions;
using Barotrauma.IO;
namespace Barotrauma.Items.Components
{
@@ -48,33 +45,36 @@ namespace Barotrauma.Items.Components
return;
}
foreach (ContentXElement limbElement in characterInfo.Ragdoll.MainElement.Elements())
if (characterInfo.Ragdoll.MainElement?.Elements() is { } limbElements)
{
if (!limbElement.GetAttributeString("type", "").Equals("head", StringComparison.OrdinalIgnoreCase)) { continue; }
ContentXElement spriteElement = limbElement.GetChildElement("sprite");
if (spriteElement == null) { continue; }
ContentPath contentPath = spriteElement.GetAttributeContentPath("texture");
string spritePath = characterInfo.ReplaceVars(contentPath.Value);
string fileName = Path.GetFileNameWithoutExtension(spritePath);
//go through the files in the directory to find a matching sprite
foreach (string file in Directory.GetFiles(Path.GetDirectoryName(spritePath)))
foreach (ContentXElement limbElement in limbElements)
{
if (!file.EndsWith(".png", StringComparison.OrdinalIgnoreCase))
if (!limbElement.GetAttributeString("type", "").Equals("head", StringComparison.OrdinalIgnoreCase)) { continue; }
ContentXElement spriteElement = limbElement.GetChildElement("sprite");
if (spriteElement == null) { continue; }
ContentPath contentPath = spriteElement.GetAttributeContentPath("texture");
string spritePath = characterInfo.ReplaceVars(contentPath.Value);
string fileName = Path.GetFileNameWithoutExtension(spritePath);
//go through the files in the directory to find a matching sprite
foreach (string file in Directory.GetFiles(Path.GetDirectoryName(spritePath)))
{
continue;
if (!file.EndsWith(".png", StringComparison.OrdinalIgnoreCase))
{
continue;
}
string fileWithoutTags = Path.GetFileNameWithoutExtension(file);
fileWithoutTags = fileWithoutTags.Split('[', ']').First();
if (fileWithoutTags != fileName) { continue; }
Portrait = new Sprite(spriteElement, "", file) { RelativeOrigin = Vector2.Zero };
break;
}
string fileWithoutTags = Path.GetFileNameWithoutExtension(file);
fileWithoutTags = fileWithoutTags.Split('[', ']').First();
if (fileWithoutTags != fileName) { continue; }
Portrait = new Sprite(spriteElement, "", file) { RelativeOrigin = Vector2.Zero };
break;
}
break;
}
if (characterInfo.Wearables != null)

View File

@@ -60,7 +60,7 @@ namespace Barotrauma.Items.Components
}
}
public override void UpdateHUD(Character character, float deltaTime, Camera cam)
public override void UpdateHUDComponentSpecific(Character character, float deltaTime, Camera cam)
{
currentCrossHairScale = currentCrossHairPointerScale = cam == null ? 1.0f : cam.Zoom;
if (crosshairSprite != null)
@@ -118,6 +118,7 @@ namespace Barotrauma.Items.Components
else if (chargeSoundChannel != null)
{
chargeSoundChannel.FrequencyMultiplier = MathHelper.Lerp(0.5f, 1.5f, chargeRatio);
chargeSoundChannel.Position = new Vector3(item.WorldPosition, 0.0f);
}
break;
default:

View File

@@ -51,7 +51,7 @@ namespace Barotrauma.Items.Components
private int spraySetting = 0;
private readonly Point[] sprayArray = new Point[8];
public override void UpdateHUD(Character character, float deltaTime, Camera cam)
public override void UpdateHUDComponentSpecific(Character character, float deltaTime, Camera cam)
{
if (character == null || !character.IsKeyDown(InputType.Aim)) return;
@@ -130,7 +130,7 @@ namespace Barotrauma.Items.Components
if (body.UserData is Item item)
{
var door = item.GetComponent<Door>();
if (door != null && door.CanBeTraversed) { continue; }
if (door != null && (door.IsOpen || door.IsBroken)) { continue; }
}
targetHull = null;
@@ -288,7 +288,7 @@ namespace Barotrauma.Items.Components
}
}
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1)
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1, Color? overrideColor = null)
{
#if DEBUG
if (GameMain.DebugDraw && Character.Controlled != null && Character.Controlled.IsKeyDown(InputType.Aim))

View File

@@ -150,7 +150,9 @@ namespace Barotrauma.Items.Components
public GUIFrame GuiFrame { get; set; }
public bool LockGuiFramePosition;
private GUIDragHandle guiFrameDragHandle;
private bool guiFrameUpdatePending;
[Serialize(false, IsPropertySaveable.No)]
public bool AllowUIOverlap
@@ -466,7 +468,21 @@ namespace Barotrauma.Items.Components
GuiFrame?.AddToGUIUpdateList(order: order);
}
public virtual void UpdateHUD(Character character, float deltaTime, Camera cam) { }
public void UpdateHUD(Character character, float deltaTime, Camera cam)
{
UpdateHUDComponentSpecific(character, deltaTime, cam);
if (guiFrameUpdatePending && !PlayerInput.PrimaryMouseButtonHeld())
{
//send a guiframe position update once the player stops dragging the frame
guiFrameUpdatePending = false;
if (SerializableProperties.TryGetValue(nameof(GuiFrameOffset).ToIdentifier(), out var property))
{
GameMain.Client?.CreateEntityEvent(Item, new Item.ChangePropertyEventData(property, this));
}
}
}
public virtual void UpdateHUDComponentSpecific(Character character, float deltaTime, Camera cam) { }
public virtual void UpdateEditing(float deltaTime) { }
@@ -572,6 +588,7 @@ namespace Barotrauma.Items.Components
}
string style = GuiFrameSource.Attribute("style") == null ? null : GuiFrameSource.GetAttributeString("style", "");
GuiFrame = new GUIFrame(RectTransform.Load(GuiFrameSource, GUI.Canvas, Anchor.Center), style, color);
GuiFrame.RectTransform.ScreenSpaceOffset = GuiFrameOffset;
TryCreateDragHandle();
@@ -583,24 +600,25 @@ namespace Barotrauma.Items.Components
GameMain.Instance.ResolutionChanged += OnResolutionChangedPrivate;
}
protected virtual void TryCreateDragHandle()
protected void TryCreateDragHandle()
{
if (GuiFrame != null && GuiFrameSource.GetAttributeBool("draggable", true))
{
bool hideDragIcons = GuiFrameSource.GetAttributeBool("hidedragicons", false);
var handle = new GUIDragHandle(new RectTransform(Vector2.One, GuiFrame.RectTransform, Anchor.Center),
guiFrameDragHandle = new GUIDragHandle(new RectTransform(Vector2.One, GuiFrame.RectTransform, Anchor.Center),
GuiFrame.RectTransform, style: null)
{
Enabled = !LockGuiFramePosition,
DragArea = HUDLayoutSettings.ItemHUDArea
};
int iconHeight = GUIStyle.ItemFrameMargin.Y / 4;
var dragIcon = new GUIImage(new RectTransform(new Point(GuiFrame.Rect.Width, iconHeight), handle.RectTransform, Anchor.TopCenter) { AbsoluteOffset = new Point(0, iconHeight / 2) },
var dragIcon = new GUIImage(new RectTransform(new Point(GuiFrame.Rect.Width, iconHeight), guiFrameDragHandle.RectTransform, Anchor.TopCenter) { AbsoluteOffset = new Point(0, iconHeight / 2) },
style: "GUIDragIndicatorHorizontal");
dragIcon.RectTransform.MinSize = new Point(0, iconHeight);
handle.ValidatePosition = (RectTransform rectT) =>
guiFrameDragHandle.ValidatePosition = (RectTransform rectT) =>
{
var activeHuds = Character.Controlled?.SelectedItem?.ActiveHUDs ?? item.ActiveHUDs;
foreach (ItemComponent ic in activeHuds)
@@ -624,11 +642,13 @@ namespace Barotrauma.Items.Components
//refresh slots to ensure they're rendered at the correct position
(ic as ItemContainer)?.Inventory.CreateSlots();
}
GuiFrameOffset = GuiFrame.RectTransform.ScreenSpaceOffset;
guiFrameUpdatePending = true;
return true;
};
int buttonHeight = (int)(GUIStyle.ItemFrameMargin.Y * 0.4f);
var settingsIcon = new GUIButton(new RectTransform(new Point(buttonHeight), handle.RectTransform, Anchor.TopLeft) { AbsoluteOffset = new Point(buttonHeight / 4), MinSize = new Point(buttonHeight) },
var settingsIcon = new GUIButton(new RectTransform(new Point(buttonHeight), guiFrameDragHandle.RectTransform, Anchor.TopLeft) { AbsoluteOffset = new Point(buttonHeight / 4), MinSize = new Point(buttonHeight) },
style: "GUIButtonSettings")
{
OnClicked = (btn, userdata) =>
@@ -636,6 +656,14 @@ namespace Barotrauma.Items.Components
GUIContextMenu.CreateContextMenu(
new ContextMenuOption("item.resetuiposition", isEnabled: true, onSelected: () =>
{
foreach (var ic in item.Components)
{
if (ic.GuiFrame != null && ic.GuiFrameOffset != Point.Zero)
{
ic.GuiFrameOffset = Point.Zero;
ic.guiFrameUpdatePending = true;
}
}
if (Character.Controlled?.SelectedItem != null && item != Character.Controlled.SelectedItem)
{
Character.Controlled.SelectedItem.ForceHUDLayoutUpdate(ignoreLocking: true);
@@ -648,7 +676,11 @@ namespace Barotrauma.Items.Components
new ContextMenuOption(TextManager.Get(LockGuiFramePosition ? "item.unlockuiposition" : "item.lockuiposition"), isEnabled: true, onSelected: () =>
{
LockGuiFramePosition = !LockGuiFramePosition;
handle.Enabled = !LockGuiFramePosition;
guiFrameDragHandle.Enabled = !LockGuiFramePosition;
if (SerializableProperties.TryGetValue(nameof(LockGuiFramePosition).ToIdentifier(), out var property))
{
GameMain.Client?.CreateEntityEvent(Item, new Item.ChangePropertyEventData(property, this));
}
}));
return true;
}

View File

@@ -1,9 +1,9 @@
using Microsoft.Xna.Framework;
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using static Barotrauma.Inventory;
namespace Barotrauma.Items.Components
{
@@ -97,6 +97,8 @@ namespace Barotrauma.Items.Components
partial void InitProjSpecific(ContentXElement element)
{
slotIcons = new Sprite[capacity];
int currCapacity = MainContainerCapacity;
foreach (var subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
@@ -127,6 +129,19 @@ namespace Barotrauma.Items.Components
}
}
break;
case "subcontainer":
int subContainerCapacity = subElement.GetAttributeInt("capacity", 1);
var slotIconElement = subElement.GetChildElement("sloticon");
if (slotIconElement != null)
{
var slotIcon = new Sprite(slotIconElement);
for (int i = currCapacity; i < currCapacity + subContainerCapacity; i++)
{
slotIcons[i] = slotIcon;
}
}
currCapacity += subContainerCapacity;
break;
}
}
@@ -180,11 +195,40 @@ namespace Barotrauma.Items.Components
};
LocalizedString labelText = GetUILabel();
GUITextBlock label = null;
if (!labelText.IsNullOrEmpty())
GUITextBlock label = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform, Anchor.TopCenter),
labelText, font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterLeft, wrap: true)
{
IgnoreLayoutGroups = true
};
int buttonSize = GUIStyle.ItemFrameTopBarHeight;
Point margin = new Point(buttonSize / 4, buttonSize / 6);
GUILayoutGroup buttonArea = new GUILayoutGroup(new RectTransform(new Point(GuiFrame.Rect.Width - margin.X * 2, buttonSize - margin.Y * 2), GuiFrame.RectTransform, Anchor.TopCenter) { AbsoluteOffset = new Point(0, margin.Y) },
isHorizontal: true, childAnchor: Anchor.TopRight)
{
label = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform, Anchor.TopCenter),
labelText, font: GUIStyle.SubHeadingFont, textAlignment: Alignment.Center, wrap: true);
AbsoluteSpacing = margin.X / 2
};
if (Inventory.Capacity > 1)
{
new GUIButton(new RectTransform(Vector2.One, buttonArea.RectTransform, scaleBasis: ScaleBasis.Smallest), style: "SortItemsButton")
{
ToolTip = TextManager.Get("SortItemsAlphabetically"),
OnClicked = (btn, userdata) =>
{
SortItems();
return true;
}
};
new GUIButton(new RectTransform(Vector2.One, buttonArea.RectTransform, scaleBasis: ScaleBasis.Smallest), style: "MergeStacksButton")
{
ToolTip = TextManager.Get("MergeItemStacks"),
OnClicked = (btn, userdata) =>
{
MergeStacks();
return true;
}
};
}
float minInventoryAreaSize = 0.5f;
@@ -215,6 +259,71 @@ namespace Barotrauma.Items.Components
Inventory.RectTransform = guiCustomComponent.RectTransform;
}
private void SortItems()
{
List<List<Item>> itemsPerSlot = new List<List<Item>>();
for (int i = 0; i < Inventory.Capacity; i++)
{
var items = Inventory.GetItemsAt(i).ToList();
if (items.Any())
{
itemsPerSlot.Add(items);
items.ForEach(it => it.Drop(dropper: null, createNetworkEvent: false, setTransform: false));
}
}
itemsPerSlot.Sort((i1, i2) => i1.First().Name.CompareTo(i2.First().Name));
foreach (var items in itemsPerSlot)
{
int firstFreeSlot = -1;
for (int i = 0; i < Inventory.Capacity; i++)
{
if (Inventory.GetItemAt(i) == null && Inventory.CanBePut(items.First()))
{
firstFreeSlot = i;
break;
}
}
if (firstFreeSlot == -1)
{
items.ForEach(it => it.Drop(dropper: null));
continue;
}
foreach (var item in items)
{
if (!Inventory.TryPutItem(item, firstFreeSlot, allowSwapping: false, allowCombine: false, user: null, createNetworkEvent: false))
{
//if putting in the specific slot fails (prevented by containable restrictions?), just put in the first free slot
if (!Inventory.TryPutItem(item, user: null, createNetworkEvent: false))
{
item.Drop(dropper: null);
}
}
}
}
Inventory.CreateNetworkEvent();
}
private void MergeStacks()
{
for (int i = Inventory.Capacity - 1; i >= 0; i--)
{
var items = Inventory.GetItemsAt(i).ToList();
if (items.None()) { continue; }
//find the first stack we can put the item in
for (int j = 0; j < i; j++)
{
if (Inventory.GetItemsAt(j).Any() && Inventory.CanBePutInSlot(items.First(), j))
{
items.ForEach(it => Inventory.TryPutItem(it, j, allowSwapping: false, allowCombine: false, user: null, createNetworkEvent: false));
break;
}
}
}
Inventory.CreateNetworkEvent();
}
public LocalizedString GetUILabel()
{
if (UILabel == string.Empty) { return string.Empty; }
@@ -277,7 +386,7 @@ namespace Barotrauma.Items.Components
int ignoredItemCount = 0;
var subContainableItems = AllSubContainableItems;
float targetSlotCapacity = GetMaxStackSize(targetSlot);
float targetSlotCapacity = Math.Min(containedItem.Prefab.MaxStackSize, GetMaxStackSize(targetSlot));
float capacity = targetSlotCapacity * MainContainerCapacity;
if (subContainableItems != null)
{
@@ -310,29 +419,36 @@ namespace Barotrauma.Items.Components
int itemCount = Inventory.AllItems.Count() - ignoredItemCount;
return Math.Min(itemCount / Math.Max(capacity, 1), 1);
}
//display the state of an item in a specific slot
if (Inventory.Capacity == 1 || ContainedStateIndicatorSlot > -1)
{
if (containedItem == null) { return 0.0f; }
//if the contained item has some contained state indicator, show that
if (containedItem.GetComponent<ItemContainer>() is { ShowContainedStateIndicator: true } containedItemContainer)
{
return containedItemContainer.GetContainedIndicatorState();
}
int maxStackSize = Math.Min(containedItem.Prefab.GetMaxStackSize(Inventory), GetMaxStackSize(targetSlot));
if (maxStackSize == 1)
{
return containedItem.Condition / containedItem.MaxCondition;
}
return containedItems.Count() / (float)maxStackSize;
}
else
{
if (containedItem != null && (Inventory.Capacity == 1 || HasSubContainers))
{
int maxStackSize = Math.Min(containedItem.Prefab.MaxStackSize, GetMaxStackSize(targetSlot));
if (maxStackSize > 1 || containedItem.Prefab.HideConditionBar)
{
return containedItems.Count() / (float)maxStackSize;
}
}
return Inventory.Capacity == 1 || ContainedStateIndicatorSlot > -1 ?
(containedItem == null ? 0.0f : containedItem.Condition / containedItem.MaxCondition) :
Inventory.EmptySlotCount / (float)Inventory.Capacity;
}
return Inventory.EmptySlotCount / (float)Inventory.Capacity;
}
}
public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1)
public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1, Color? overrideColor = null)
{
if (hideItems || (item.body != null && !item.body.Enabled)) { return; }
DrawContainedItems(spriteBatch, itemDepth);
DrawContainedItems(spriteBatch, itemDepth, overrideColor);
}
public void DrawContainedItems(SpriteBatch spriteBatch, float itemDepth)
public void DrawContainedItems(SpriteBatch spriteBatch, float itemDepth, Color? overrideColor = null)
{
Vector2 transformedItemPos = ItemPos * item.Scale;
Vector2 transformedItemInterval = ItemInterval * item.Scale;
@@ -388,7 +504,7 @@ namespace Barotrauma.Items.Components
bool isWiringMode = SubEditorScreen.TransparentWiringMode && SubEditorScreen.IsWiringMode();
int i = 0;
foreach (DrawableContainedItem contained in drawableContainedItems)
foreach (ContainedItem contained in containedItems)
{
Vector2 itemPos = currentItemPos;
@@ -466,7 +582,7 @@ namespace Barotrauma.Items.Components
contained.Item.Sprite.Draw(
spriteBatch,
new Vector2(itemPos.X, -itemPos.Y),
isWiringMode ? contained.Item.GetSpriteColor(withHighlight: true) * 0.15f : contained.Item.GetSpriteColor(withHighlight: true),
overrideColor ?? (isWiringMode ? contained.Item.GetSpriteColor(withHighlight: true) * 0.15f : contained.Item.GetSpriteColor(withHighlight: true)),
origin,
-(contained.Item.body == null ? 0.0f : contained.Item.body.DrawRotation),
contained.Item.Scale,
@@ -476,7 +592,7 @@ namespace Barotrauma.Items.Components
foreach (ItemContainer ic in contained.Item.GetComponents<ItemContainer>())
{
if (ic.hideItems) { continue; }
ic.DrawContainedItems(spriteBatch, containedSpriteDepth);
ic.DrawContainedItems(spriteBatch, containedSpriteDepth, overrideColor);
}
i++;
@@ -497,7 +613,7 @@ namespace Barotrauma.Items.Components
}
}
public override void UpdateHUD(Character character, float deltaTime, Camera cam)
public override void UpdateHUDComponentSpecific(Character character, float deltaTime, Camera cam)
{
if (!item.IsInteractable(character)) { return; }
if (Inventory.RectTransform != null)
@@ -512,19 +628,10 @@ namespace Barotrauma.Items.Components
//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 = DrawInventory && item.ParentInventory?.Owner != character;
guiCustomComponent.Visible = DrawInventory && (item.ParentInventory?.Owner != character || Inventory.DrawWhenEquipped);
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);
}*/
}
}

View File

@@ -311,7 +311,7 @@ namespace Barotrauma.Items.Components
prevRect = item.Rect;
}
public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1)
public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1, Color? overrideColor = null)
{
if (item.ParentInventory != null) { return; }
if (editing)

View File

@@ -19,13 +19,14 @@ namespace Barotrauma.Items.Components
private Sprite backgroundSprite;
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1)
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1, Color? overrideColor = null)
{
if (backgroundSprite == null) { return; }
backgroundSprite.DrawTiled(spriteBatch,
new Vector2(item.DrawPosition.X - item.Rect.Width / 2 * item.Scale, -(item.DrawPosition.Y + item.Rect.Height / 2)) - backgroundSprite.Origin * item.Scale,
new Vector2(backgroundSprite.size.X * item.Scale, item.Rect.Height), color: item.Color,
new Vector2(backgroundSprite.size.X * item.Scale, item.Rect.Height),
color: overrideColor ?? item.Color,
textureScale: Vector2.One * item.Scale,
depth: BackgroundSpriteDepth);
}

View File

@@ -79,7 +79,7 @@ namespace Barotrauma.Items.Components
}
}
public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1)
public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1, Color? overrideColor = null)
{
if (Light?.LightSprite == null) { return; }
if ((item.body == null || item.body.Enabled) && lightBrightness > 0.0f && IsOn && Light.Enabled)

View File

@@ -428,7 +428,7 @@ namespace Barotrauma.Items.Components
}
}
public override void UpdateHUD(Character character, float deltaTime, Camera cam)
public override void UpdateHUDComponentSpecific(Character character, float deltaTime, Camera cam)
{
inSufficientPowerWarning.Visible = IsActive && !hasPower;
}

View File

@@ -108,7 +108,7 @@ namespace Barotrauma.Items.Components
}
}
public override void UpdateHUD(Character character, float deltaTime, Camera cam)
public override void UpdateHUDComponentSpecific(Character character, float deltaTime, Camera cam)
{
powerIndicator.Selected = hasPower && IsActive;
autoControlIndicator.Selected = controlLockTimer > 0.0f;
@@ -138,14 +138,14 @@ namespace Barotrauma.Items.Components
}
}
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1)
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1, Color? overrideColor = null)
{
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);
propellerSprite.Draw(spriteBatch, (int)Math.Floor(spriteIndex), drawPos, overrideColor ?? Color.White, propellerSprite.Origin, 0.0f, Vector2.One);
}
if (editing && !DisablePropellerDamage && propellerDamage != null && !GUI.DisableHUD)

View File

@@ -38,6 +38,11 @@ namespace Barotrauma.Items.Components
}
private FabricationRecipe selectedItem;
/// <summary>
/// Which character's skills the current view is displayed based on
/// </summary>
private Character displayingForCharacter;
public Identifier SelectedItemIdentifier => SelectedItem?.TargetItem.Identifier ?? Identifier.Empty;
private GUIComponent inSufficientPowerWarning;
@@ -358,9 +363,11 @@ namespace Barotrauma.Items.Components
if (inputInventoryHolder != null)
{
inputContainer.AllowUIOverlap = true;
inputContainer.Inventory.DrawWhenEquipped = true;
inputContainer.Inventory.RectTransform = inputInventoryHolder.RectTransform;
}
outputContainer.AllowUIOverlap = true;
outputContainer.Inventory.DrawWhenEquipped = true;
outputContainer.Inventory.RectTransform = outputInventoryHolder.RectTransform;
}
@@ -453,13 +460,8 @@ namespace Barotrauma.Items.Components
requiresRecipeText.RectTransform.RepositionChildInHierarchy(itemList.Content.RectTransform.GetChildIndex(firstRequiresRecipe.RectTransform));
}
FilterEntities(selectedItemCategory, itemFilterBox?.Text ?? string.Empty);
HideEmptyItemListCategories();
if (selectedItem != null)
{
//reselect to recreate the info based on the new user's skills
SelectItem(character, selectedItem);
}
}
private readonly Dictionary<FabricationRecipe.RequiredItem, int> missingIngredientCounts = new Dictionary<FabricationRecipe.RequiredItem, int>();
@@ -538,6 +540,8 @@ namespace Barotrauma.Items.Components
int slotIndex = 0;
foreach (var kvp in missingIngredientCounts)
{
if (inputContainer.Inventory?.visualSlots == null) { break; }
var requiredItem = kvp.Key;
int missingCount = kvp.Value;
@@ -560,11 +564,10 @@ namespace Barotrauma.Items.Components
var requiredItemPrefab = requiredItem.FirstMatchingPrefab;
float iconAlpha = 0.0f;
ItemPrefab requiredItemToDisplay;
int count = requiredItem.ItemPrefabs.Count();
if (count > 1)
ItemPrefab requiredItemToDisplay = requiredItem.DefaultItem.IsEmpty ? null : requiredItem.ItemPrefabs.FirstOrDefault(p => p.Identifier == requiredItem.DefaultItem);
if (requiredItemToDisplay == null && requiredItem.ItemPrefabs.Multiple())
{
float iconCycleSpeed = 0.5f / count;
float iconCycleSpeed = 0.75f;
float iconCycleT = (float)Timing.TotalTime * iconCycleSpeed;
int iconIndex = (int)(iconCycleT % requiredItem.ItemPrefabs.Count());
@@ -573,7 +576,7 @@ namespace Barotrauma.Items.Components
}
else
{
requiredItemToDisplay = requiredItem.ItemPrefabs.FirstOrDefault();
requiredItemToDisplay ??= requiredItem.ItemPrefabs.FirstOrDefault();
iconAlpha = 1.0f;
}
if (iconAlpha > 0.0f)
@@ -616,9 +619,12 @@ namespace Barotrauma.Items.Components
if (slotRect.Contains(PlayerInput.MousePosition))
{
var suitableIngredients = requiredItem.ItemPrefabs.Select(ip => ip.Name).Distinct();
LocalizedString toolTipText = string.Join(", ", suitableIngredients.Count() > 3 ? suitableIngredients.SkipLast(suitableIngredients.Count() - 3) : suitableIngredients);
if (suitableIngredients.Count() > 3) { toolTipText += "..."; }
LocalizedString toolTipText = requiredItem.OverrideHeader;
if (requiredItem.OverrideHeader.IsNullOrEmpty())
{
var suitableIngredients = requiredItem.ItemPrefabs.Where(ip => !ip.HideInMenus).OrderBy(ip => ip.DefaultPrice?.Price ?? 0).Select(ip => ip.Name).Distinct();
toolTipText = GetSuitableIngredientText(suitableIngredients);
}
if (requiredItem.UseCondition && requiredItem.MinCondition < 1.0f)
{
toolTipText += " " + (int)Math.Round(requiredItem.MinCondition * 100) + "%";
@@ -656,15 +662,68 @@ namespace Barotrauma.Items.Components
}
}
private LocalizedString GetSuitableIngredientText(IEnumerable<LocalizedString> itemNameList)
{
int count = itemNameList.Count();
if (count == 0)
{
return string.Empty;
}
else if (count == 1)
{
return itemNameList.First();
}
else if (count == 2)
{
//[item1] or [item2]
return TextManager.GetWithVariables(
"DialogRequiredTreatmentOptionsLast",
("[treatment1]", itemNameList.ElementAt(0)),
("[treatment2]", itemNameList.ElementAt(1)));
}
else
{
// [item1], [item2], [item3] ... or [lastitem]
LocalizedString itemListStr = TextManager.GetWithVariables(
"DialogRequiredTreatmentOptionsFirst",
("[treatment1]", itemNameList.ElementAt(0)),
("[treatment2]", itemNameList.ElementAt(1)));
int i;
bool isTruncated = false;
for (i = 2; i < count - 1; i++)
{
if (itemListStr.Length > 50)
{
isTruncated = true;
break;
}
itemListStr = TextManager.GetWithVariables(
"DialogRequiredTreatmentOptionsFirst",
("[treatment1]", itemListStr),
("[treatment2]", itemNameList.ElementAt(i)));
}
itemListStr = TextManager.GetWithVariables(
"DialogRequiredTreatmentOptionsLast",
("[treatment1]", itemListStr),
("[treatment2]", itemNameList.ElementAt(i)));
if (isTruncated)
{
itemListStr += TextManager.Get("ellipsis");
}
return itemListStr;
}
}
private void DrawOutputOverLay(SpriteBatch spriteBatch, GUICustomComponent overlayComponent)
{
overlayComponent.RectTransform.SetAsLastChild();
FabricationRecipe targetItem = fabricatedItem ?? selectedItem;
if (targetItem != null)
if (targetItem != null && outputContainer.Inventory?.visualSlots != null)
{
Rectangle slotRect = outputContainer.Inventory.visualSlots[0].Rect;
if (fabricatedItem != null)
{
float clampedProgressState = Math.Clamp(progressState, 0f, 1f);
@@ -699,6 +758,16 @@ namespace Barotrauma.Items.Components
{
FabricationRecipe recipe = child.UserData as FabricationRecipe;
if (recipe?.DisplayName == null) { continue; }
if (recipe.HideForNonTraitors)
{
if (Character.Controlled is not { IsTraitor: true })
{
child.Visible = false;
continue;
}
}
child.Visible =
(string.IsNullOrWhiteSpace(filter) || recipe.DisplayName.Contains(filter, StringComparison.OrdinalIgnoreCase)) &&
(!category.HasValue || recipe.TargetItem.Category.HasFlag(category.Value));
@@ -749,8 +818,9 @@ namespace Barotrauma.Items.Components
private bool SelectItem(Character user, FabricationRecipe selectedItem, float? overrideRequiredTime = null)
{
this.selectedItem = selectedItem;
displayingForCharacter = user;
int max = Math.Max(selectedItem.TargetItem.MaxStackSize / selectedItem.Amount, 1);
int max = Math.Max(selectedItem.TargetItem.GetMaxStackSize(outputContainer.Inventory) / selectedItem.Amount, 1);
if (amountInput != null)
{
@@ -924,7 +994,7 @@ namespace Barotrauma.Items.Components
return true;
}
public override void UpdateHUD(Character character, float deltaTime, Camera cam)
public override void UpdateHUDComponentSpecific(Character character, float deltaTime, Camera cam)
{
activateButton.Enabled = false;
inSufficientPowerWarning.Visible = IsActive && !hasPower;
@@ -933,6 +1003,12 @@ namespace Barotrauma.Items.Components
if (!IsActive)
{
if (selectedItem != null && displayingForCharacter != character)
{
//reselect to recreate the info based on the new user's skills
SelectItem(character, selectedItem);
}
//only check ingredients if the fabricator isn't active (if it is, this is done in Update)
if (refreshIngredientsTimer <= 0.0f)
{
@@ -998,9 +1074,13 @@ namespace Barotrauma.Items.Components
{
fabricationLimits[msg.ReadUInt32()] = 0;
}
State = newState;
this.amountToFabricate = amountToFabricate;
//don't touch the amount unless another character changed it or the fabricator is running
//otherwise we may end up reverting the changes the client just did to the amount
if ((user != null && user != Character.Controlled) || State != FabricatorState.Stopped)
{
this.amountToFabricate = amountToFabricate;
}
this.amountRemaining = amountRemaining;
if (newState == FabricatorState.Stopped || recipeHash == 0)
{

View File

@@ -295,7 +295,7 @@ namespace Barotrauma.Items.Components
}
}
OrderPrefab[] reports = OrderPrefab.Prefabs.Where(o => o.IsReport && o.SymbolSprite != null && !o.Hidden).OrderBy(o => o.Identifier).ToArray();
OrderPrefab[] reports = OrderPrefab.Prefabs.Where(o => o.IsVisibleAsReportButton).OrderBy(o => o.Identifier).ToArray();
GUIFrame bottomFrame = new GUIFrame(new RectTransform(new Vector2(0.5f, 0.15f), paddedContainer.RectTransform, Anchor.BottomCenter) { MaxSize = new Point(int.MaxValue, GUI.IntScale(40)) }, style: null)
{
@@ -350,10 +350,16 @@ namespace Barotrauma.Items.Components
}
};
List<ItemPrefab> shownItemPrefabs = new List<ItemPrefab>();
foreach (ItemPrefab prefab in ItemPrefab.Prefabs.OrderBy(prefab => prefab.Name))
{
if (prefab.HideInMenus) { continue; }
if (shownItemPrefabs.Any(ip => DisplayAsSameItem(ip, prefab)))
{
continue;
}
CreateItemFrame(prefab, listBox.Content.RectTransform);
shownItemPrefabs.Add(prefab);
}
searchBar.OnDeselected += (sender, key) =>
@@ -398,6 +404,27 @@ namespace Barotrauma.Items.Components
new Point(int.MaxValue, paddedContainer.Rect.Height - bottomFrame.Rect.Height - buttonLayout.Rect.Height);
}
private static Sprite GetPreviewSprite(ItemPrefab prefab)
{
return prefab.InventoryIcon ?? prefab.Sprite;
}
/// <summary>
/// If the items have an identical name and icon (e.g. a variant with an alternative fabrication/deconstruction recipe),
/// they're displayed as if they were the same item, not as two separate entries.
/// </summary>
private static bool DisplayAsSameItem(ItemPrefab prefab1, ItemPrefab prefab2)
{
if (prefab1 == prefab2) { return true; }
if (prefab1.Name == prefab2.Name)
{
var sprite1 = GetPreviewSprite(prefab1);
var sprite2 = GetPreviewSprite(prefab2);
return sprite1?.FullPath == sprite2?.FullPath && sprite1?.SourceRect == sprite2?.SourceRect;
}
return false;
}
private bool VisibleOnItemFinder(Item it)
{
if (it?.Submarine == null) { return false; }
@@ -413,7 +440,7 @@ namespace Barotrauma.Items.Components
if (it.Container?.GetComponent<ItemContainer>() is { DrawInventory: false } or { AllowAccess: false }) { return false; }
if (it.HasTag("traitormissionitem")) { return false; }
if (it.HasTag(Tags.TraitorMissionItem)) { return false; }
return true;
}
@@ -539,13 +566,13 @@ namespace Barotrauma.Items.Components
displayedSubs.Add(item.Submarine);
displayedSubs.AddRange(item.Submarine.DockedTo.Where(s => s.TeamID == item.Submarine.TeamID));
subEntities = MapEntity.mapEntityList.Where(me => (item.Submarine is { } sub && sub.IsEntityFoundOnThisSub(me, includingConnectedSubs: true, allowDifferentType: false)) && !me.HiddenInGame).OrderByDescending(w => w.SpriteDepth).ToList();
subEntities = MapEntity.MapEntityList.Where(me => (item.Submarine is { } sub && sub.IsEntityFoundOnThisSub(me, includingConnectedSubs: true, allowDifferentType: false)) && !me.HiddenInGame).OrderByDescending(w => w.SpriteDepth).ToList();
BakeSubmarine(item.Submarine, parentRect);
elementSize = GuiFrame.Rect.Size;
}
public override void UpdateHUD(Character character, float deltaTime, Camera cam)
public override void UpdateHUDComponentSpecific(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
@@ -824,7 +851,8 @@ namespace Barotrauma.Items.Components
foreach (GUIComponent component in listBox.Content.Children)
{
component.Visible = false;
if (component.UserData is ItemPrefab { Name: { } prefabName} prefab && itemsFoundOnSub.Contains(prefab))
if (component.UserData is ItemPrefab { Name: { } prefabName} prefab &&
(itemsFoundOnSub.Contains(prefab) || itemsFoundOnSub.Any(ip => DisplayAsSameItem(ip, prefab))))
{
component.Visible = prefabName.ToLower().Contains(text.ToLower());
@@ -851,9 +879,9 @@ namespace Barotrauma.Items.Components
tooltip.RectTransform.ScreenSpaceOffset = new Point(box.Rect.X, box.Rect.Y - height);
}
private void CreateItemFrame(ItemPrefab prefab, RectTransform parent)
private static void CreateItemFrame(ItemPrefab prefab, RectTransform parent)
{
Sprite sprite = prefab.InventoryIcon ?? prefab.Sprite;
Sprite sprite = GetPreviewSprite(prefab);
if (sprite is null) { return; }
GUIFrame frame = new GUIFrame(new RectTransform(new Vector2(1f, 0.25f), parent), style: "ListBoxElement")
{
@@ -899,7 +927,7 @@ namespace Barotrauma.Items.Components
{
if (!VisibleOnItemFinder(it)) { continue; }
if (it.Prefab == searchedPrefab)
if (DisplayAsSameItem(it.Prefab, searchedPrefab))
{
// ignore items on players and hidden inventories
if (it.FindParentInventory(inv => inv is CharacterInventory || inv is ItemInventory { Owner: Item { HiddenInGame: true }}) is { }) { continue; }
@@ -1079,7 +1107,7 @@ namespace Barotrauma.Items.Components
if (ShowHullIntegrity)
{
float amount = 1f + hullData.LinkedHulls.Count;
gapOpenSum = hull.ConnectedGaps.Concat(hullData.LinkedHulls.SelectMany(h => h.ConnectedGaps)).Where(g => !g.IsRoomToRoom && !g.HiddenInGame).Sum(g => g.Open) / amount;
gapOpenSum = hull.ConnectedGaps.Concat(hullData.LinkedHulls.SelectMany(h => h.ConnectedGaps)).Where(g => g.linkedTo.Count == 1 && !g.HiddenInGame).Sum(g => g.Open) / amount;
borderColor = Color.Lerp(neutralColor, GUIStyle.Red, Math.Min(gapOpenSum, 1.0f));
}

View File

@@ -181,7 +181,7 @@ namespace Barotrauma.Items.Components
private float flickerTimer;
private readonly float flickerFrequency = 1;
public override void UpdateHUD(Character character, float deltaTime, Camera cam)
public override void UpdateHUDComponentSpecific(Character character, float deltaTime, Camera cam)
{
autoControlIndicator.Selected = IsAutoControlled;
PowerButton.Enabled = isActiveLockTimer <= 0.0f;

View File

@@ -615,7 +615,7 @@ namespace Barotrauma.Items.Components
turbineOutputMeter, TurbineOutput, new Vector2(0.0f, 100.0f), clampedOptimalTurbineOutput, clampedAllowedTurbineOutput);
}
public override void UpdateHUD(Character character, float deltaTime, Camera cam)
public override void UpdateHUDComponentSpecific(Character character, float deltaTime, Camera cam)
{
IsActive = true;

View File

@@ -109,7 +109,7 @@ namespace Barotrauma.Items.Components
},
{
BlipType.Destructible,
new Color[] { Color.TransparentBlack, new Color(74, 113, 75) * 0.8f, new Color(151, 236, 172) * 0.8f, new Color(153, 217, 234) * 0.8f }
new Color[] { Color.TransparentBlack, new Color(94, 114, 73) * 0.8f, new Color(255, 236, 151) * 0.8f, new Color(242, 243, 194) * 0.8f }
},
{
BlipType.Door,
@@ -358,11 +358,6 @@ namespace Barotrauma.Items.Components
}
}
protected override void TryCreateDragHandle()
{
base.TryCreateDragHandle();
}
private void SetPingDirection(Vector2 direction)
{
pingDirection = direction;
@@ -471,7 +466,7 @@ namespace Barotrauma.Items.Components
}
}
public override void UpdateHUD(Character character, float deltaTime, Camera cam)
public override void UpdateHUDComponentSpecific(Character character, float deltaTime, Camera cam)
{
showDirectionalIndicatorTimer -= deltaTime;
if (GameMain.Client != null)
@@ -981,38 +976,41 @@ namespace Barotrauma.Items.Components
}
}
if (GameMain.GameSession == null || Level.Loaded == null) { return; }
if (GameMain.GameSession == null) { return; }
if (Level.Loaded.StartLocation?.Type is { ShowSonarMarker: true })
if (Level.Loaded != null)
{
DrawMarker(spriteBatch,
Level.Loaded.StartLocation.Name,
(Level.Loaded.StartOutpost != null ? "outpost" : "location").ToIdentifier(),
"startlocation",
Level.Loaded.StartExitPosition, transducerCenter,
displayScale, center, DisplayRadius);
}
if (Level.Loaded.StartLocation?.Type is { ShowSonarMarker: true })
{
DrawMarker(spriteBatch,
Level.Loaded.StartLocation.Name,
(Level.Loaded.StartOutpost != null ? "outpost" : "location").ToIdentifier(),
"startlocation",
Level.Loaded.StartExitPosition, transducerCenter,
displayScale, center, DisplayRadius);
}
if (Level.Loaded is { EndLocation.Type.ShowSonarMarker: true, Type: LevelData.LevelType.LocationConnection })
{
DrawMarker(spriteBatch,
Level.Loaded.EndLocation.Name,
(Level.Loaded.EndOutpost != null ? "outpost" : "location").ToIdentifier(),
"endlocation",
Level.Loaded.EndExitPosition, transducerCenter,
displayScale, center, DisplayRadius);
}
if (Level.Loaded is { EndLocation.Type.ShowSonarMarker: true, Type: LevelData.LevelType.LocationConnection })
{
DrawMarker(spriteBatch,
Level.Loaded.EndLocation.Name,
(Level.Loaded.EndOutpost != null ? "outpost" : "location").ToIdentifier(),
"endlocation",
Level.Loaded.EndExitPosition, transducerCenter,
displayScale, center, DisplayRadius);
}
for (int i = 0; i < Level.Loaded.Caves.Count; i++)
{
var cave = Level.Loaded.Caves[i];
if (!cave.DisplayOnSonar) { continue; }
DrawMarker(spriteBatch,
caveLabel.Value,
"cave".ToIdentifier(),
"cave" + i,
cave.StartPos.ToVector2(), transducerCenter,
displayScale, center, DisplayRadius);
for (int i = 0; i < Level.Loaded.Caves.Count; i++)
{
var cave = Level.Loaded.Caves[i];
if (cave.MissionsToDisplayOnSonar.None()) { continue; }
DrawMarker(spriteBatch,
caveLabel.Value,
"cave".ToIdentifier(),
"cave" + i,
cave.StartPos.ToVector2(), transducerCenter,
displayScale, center, DisplayRadius);
}
}
int missionIndex = 0;
@@ -1070,7 +1068,7 @@ namespace Barotrauma.Items.Components
{
if (!sub.ShowSonarMarker) { continue; }
if (connectedSubs.Contains(sub)) { continue; }
if (sub.WorldPosition.Y > Level.Loaded.Size.Y) { continue; }
if (Level.Loaded != null && sub.WorldPosition.Y > Level.Loaded.Size.Y) { continue; }
if (item.Submarine != null || Character.Controlled != null)
{

View File

@@ -532,6 +532,22 @@ namespace Barotrauma.Items.Components
};
}
/// <summary>
/// Map the rectangular steering vector to a circular area using FG-Squircular Mapping which preserves the angle of the vector.
/// </summary>
private static Vector2 MapSquareToCircle(Vector2 steeringVector)
{
float xSqr = steeringVector.X * steeringVector.X;
float ySqr = steeringVector.Y * steeringVector.Y;
float length = MathF.Sqrt(ySqr + xSqr);
if (MathUtils.NearlyEqual(length, 0.0f)) { return Vector2.Zero; }
//FG-Squircular mapping formula from https://arxiv.org/ftp/arxiv/papers/1509/1509.06344.pdf
float x = steeringVector.X * MathF.Sqrt(xSqr + ySqr - xSqr * ySqr) / length;
float y = steeringVector.Y * MathF.Sqrt(xSqr + ySqr - xSqr * ySqr) / length;
return new Vector2(x, y);
}
public void DrawHUD(SpriteBatch spriteBatch, Rectangle rect)
{
int width = rect.Width, height = rect.Height;
@@ -545,11 +561,9 @@ namespace Barotrauma.Items.Components
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));
Vector2 steeringInputPos = MapSquareToCircle(steeringInput / 100f) * 100.0f;
steeringInputPos.Y = -steeringInputPos.Y;
steeringInputPos += steeringOrigin;
if (steeringIndicator != null)
@@ -604,10 +618,8 @@ namespace Barotrauma.Items.Components
}
//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));
Vector2 steeringPos = MapSquareToCircle(targetVelocity / 100f) * 90.0f;
steeringPos.Y = -steeringPos.Y;
steeringPos += steeringOrigin;
if (steeringIndicator != null)
@@ -682,7 +694,7 @@ namespace Barotrauma.Items.Components
}
}
public override void UpdateHUD(Character character, float deltaTime, Camera cam)
public override void UpdateHUDComponentSpecific(Character character, float deltaTime, Camera cam)
{
if (swapDestinationOrder == null)
{

View File

@@ -40,7 +40,7 @@ namespace Barotrauma.Items.Components
}
}
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1)
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1, Color? overrideColor = null)
{
for (var i = 0; i < GrowableSeeds.Length; i++)
{

View File

@@ -122,7 +122,7 @@ namespace Barotrauma.Items.Components
}
}
public override void UpdateHUD(Character character, float deltaTime, Camera cam)
public override void UpdateHUDComponentSpecific(Character character, float deltaTime, Camera cam)
{
if (chargeIndicator != null)
{
@@ -131,7 +131,7 @@ namespace Barotrauma.Items.Components
}
}
public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1)
public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1, Color? overrideColor = null)
{
Vector2 scaledIndicatorSize = indicatorSize * item.Scale;
if (scaledIndicatorSize.X <= 2.0f || scaledIndicatorSize.Y <= 2.0f) { return; }

View File

@@ -10,6 +10,10 @@ namespace Barotrauma.Items.Components
private GUITickBox highVoltageIndicator;
private GUITickBox lowVoltageIndicator;
private GUITextBlock powerLabel, loadLabel;
private LanguageIdentifier prevLanguage;
partial void InitProjectSpecific(XElement element)
{
if (GuiFrame == null) { return; }
@@ -56,12 +60,12 @@ namespace Barotrauma.Items.Components
Stretch = true
};
var powerLabel = new GUITextBlock(new RectTransform(new Vector2(0.4f, 1), upperTextArea.RectTransform),
powerLabel = new GUITextBlock(new RectTransform(new Vector2(0.4f, 1), upperTextArea.RectTransform),
TextManager.Get("PowerTransferPowerLabel"), textColor: GUIStyle.TextColorBright, font: GUIStyle.LargeFont, textAlignment: Alignment.CenterRight)
{
ToolTip = TextManager.Get("PowerTransferTipPower")
};
var loadLabel = new GUITextBlock(new RectTransform(new Vector2(0.4f, 1), lowerTextArea.RectTransform),
loadLabel = new GUITextBlock(new RectTransform(new Vector2(0.4f, 1), lowerTextArea.RectTransform),
TextManager.Get("PowerTransferLoadLabel"), textColor: GUIStyle.TextColorBright, font: GUIStyle.LargeFont, textAlignment: Alignment.CenterRight)
{
ToolTip = TextManager.Get("PowerTransferTipLoad")
@@ -75,7 +79,7 @@ namespace Barotrauma.Items.Components
ToolTip = TextManager.Get("PowerTransferTipPower"),
TextGetter = () => {
float currPower = powerLoad < 0 ? -powerLoad: 0;
if (!(this is RelayComponent) && PowerConnections != null && PowerConnections.Count > 0 && PowerConnections[0].Grid != null)
if (this is not RelayComponent && PowerConnections != null && PowerConnections.Count > 0 && PowerConnections[0].Grid != null)
{
currPower = PowerConnections[0].Grid.Power;
}
@@ -119,9 +123,11 @@ namespace Barotrauma.Items.Components
GUITextBlock.AutoScaleAndNormalize(powerLabel, loadLabel);
GUITextBlock.AutoScaleAndNormalize(true, true, powerText, loadText);
GUITextBlock.AutoScaleAndNormalize(kw1, kw2);
prevLanguage = GameSettings.CurrentConfig.Language;
}
public override void UpdateHUD(Character character, float deltaTime, Camera cam)
public override void UpdateHUDComponentSpecific(Character character, float deltaTime, Camera cam)
{
if (GuiFrame == null) return;
@@ -129,6 +135,13 @@ namespace Barotrauma.Items.Components
powerIndicator.Selected = IsActive && voltage > 0;
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;
if (prevLanguage != GameSettings.CurrentConfig.Language)
{
GUITextBlock.AutoScaleAndNormalize(powerIndicator.TextBlock, highVoltageIndicator.TextBlock, lowVoltageIndicator.TextBlock);
GUITextBlock.AutoScaleAndNormalize(powerLabel, loadLabel);
prevLanguage = GameSettings.CurrentConfig.Language;
}
}
}
}

View File

@@ -21,9 +21,11 @@ namespace Barotrauma.Items.Components
Vector2 simPosition = new Vector2(msg.ReadSingle(), msg.ReadSingle());
float rotation = msg.ReadSingle();
spreadIndex = msg.ReadByte();
ushort submarineID = msg.ReadUInt16();
if (User != null)
{
Shoot(User, simPosition, simPosition, rotation, ignoredBodies: User.AnimController.Limbs.Where(l => !l.IsSevered).Select(l => l.body.FarseerBody).ToList(), createNetworkEvent: false);
item.Submarine = Entity.FindEntityByID(submarineID) as Submarine;
}
else
{
@@ -42,59 +44,98 @@ namespace Barotrauma.Items.Components
Vector2 axis = new Vector2(
msg.ReadSingle(),
msg.ReadSingle());
UInt16 entityID = msg.ReadUInt16();
StickTargetType targetType = (StickTargetType)msg.ReadByte();
Entity entity = Entity.FindEntityByID(entityID);
Submarine submarine = Entity.FindEntityByID(submarineID) as Submarine;
Hull hull = Entity.FindEntityByID(hullID) as Hull;
Hull hull = Entity.FindEntityByID(hullID) as Hull;
item.Submarine = submarine;
item.CurrentHull = hull;
item.body.SetTransform(simPosition, item.body.Rotation);
if (entity is Character character)
switch (targetType)
{
byte limbIndex = msg.ReadByte();
if (limbIndex >= character.AnimController.Limbs.Length)
{
DebugConsole.ThrowError($"Failed to read a projectile update from the server. Limb index out of bounds ({limbIndex}, character: {character.ToString()})");
return;
}
if (character.Removed) { return; }
var limb = character.AnimController.Limbs[limbIndex];
StickToTarget(limb.body.FarseerBody, axis);
}
else if (entity is Structure structure)
{
byte bodyIndex = msg.ReadByte();
if (bodyIndex == 255) { bodyIndex = 0; }
if (bodyIndex >= structure.Bodies.Count)
{
DebugConsole.ThrowError($"Failed to read a projectile update from the server. Structure body index out of bounds ({bodyIndex}, structure: {structure.ToString()})");
return;
}
var body = structure.Bodies[bodyIndex];
StickToTarget(body, axis);
}
else if (entity is Item item)
{
if (item.Removed) { return; }
var door = item.GetComponent<Door>();
if (door != null)
{
StickToTarget(door.Body.FarseerBody, axis);
}
else if (item.body != null)
{
StickToTarget(item.body.FarseerBody, axis);
}
}
else if (entity is Submarine sub)
{
StickToTarget(sub.PhysicsBody.FarseerBody, axis);
}
else
{
DebugConsole.ThrowError($"Failed to read a projectile update from the server. Invalid stick target ({entity?.ToString() ?? "null"}, {entityID})");
}
case StickTargetType.Structure:
UInt16 structureId = msg.ReadUInt16();
byte bodyIndex = msg.ReadByte();
if (Entity.FindEntityByID(structureId) is Structure structure)
{
if (bodyIndex == 255) { bodyIndex = 0; }
if (bodyIndex >= structure.Bodies.Count)
{
DebugConsole.ThrowError($"Failed to read a projectile update from the server. Structure body index out of bounds ({bodyIndex}, structure: {structure})");
return;
}
var body = structure.Bodies[bodyIndex];
StickToTarget(body, axis);
}
else
{
DebugConsole.AddWarning($"\"{item.Prefab.Identifier}\" failed to stick to a structure. Could not find a structure with the ID {structureId}");
}
break;
case StickTargetType.Limb:
UInt16 characterId = msg.ReadUInt16();
byte limbIndex = msg.ReadByte();
if (Entity.FindEntityByID(characterId) is Character character)
{
if (limbIndex >= character.AnimController.Limbs.Length)
{
DebugConsole.ThrowError($"Failed to read a projectile update from the server. Limb index out of bounds ({limbIndex}, character: {character})");
return;
}
if (character.Removed) { return; }
var limb = character.AnimController.Limbs[limbIndex];
StickToTarget(limb.body.FarseerBody, axis);
}
else
{
DebugConsole.AddWarning($"\"{this.item.Prefab.Identifier}\" failed to stick to a limb. Could not find a character with the ID {characterId}");
}
break;
case StickTargetType.Item:
UInt16 itemID = msg.ReadUInt16();
if (Entity.FindEntityByID(itemID) is Item targetItem)
{
if (targetItem.Removed) { return; }
var door = targetItem.GetComponent<Door>();
if (door != null)
{
StickToTarget(door.Body.FarseerBody, axis);
}
else if (targetItem.body != null)
{
StickToTarget(targetItem.body.FarseerBody, axis);
}
}
else
{
DebugConsole.AddWarning($"\"{this.item.Prefab.Identifier}\" failed to stick to an item. Could not find n item with the ID {itemID}");
}
break;
case StickTargetType.Submarine:
UInt16 targetSubmarineId = msg.ReadUInt16();
if (Entity.FindEntityByID(targetSubmarineId) is Submarine targetSub)
{
StickToTarget(targetSub.PhysicsBody.FarseerBody, axis);
}
else
{
DebugConsole.AddWarning($"\"{item.Prefab.Identifier}\" failed to stick to a submarine. Could not find a structure with the ID {targetSubmarineId}");
}
break;
case StickTargetType.LevelWall:
int levelWallIndex = msg.ReadInt32();
var allCells = Level.Loaded.GetAllCells();
if (levelWallIndex >= 0 && levelWallIndex < allCells.Count)
{
StickToTarget(allCells[levelWallIndex].Body, axis);
}
else
{
DebugConsole.ThrowError($"Failed to read a projectile update from the server. Level wall index out of bounds ({levelWallIndex}, wall count: {allCells.Count})");
}
break;
}
}
else
{

View File

@@ -9,7 +9,7 @@ namespace Barotrauma.Items.Components
currentTarget?.DrawHUD(spriteBatch, Screen.Selected.Cam, character);
}
public override void UpdateHUD(Character character, float deltaTime, Camera cam)
public override void UpdateHUDComponentSpecific(Character character, float deltaTime, Camera cam)
{
currentTarget?.UpdateHUD(cam, character,deltaTime);
}

View File

@@ -144,7 +144,7 @@ namespace Barotrauma.Items.Components
}
}
#if DEBUG
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1)
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1, Color? overrideColor = null)
{
if (GameMain.DebugDraw && IsActive)
{

View File

@@ -373,12 +373,19 @@ namespace Barotrauma.Items.Components
}
}
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1)
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1, Color? overrideColor = null)
{
if (GameMain.DebugDraw && Character.Controlled?.FocusedItem == item)
{
bool paused = !ShouldDeteriorate();
if (deteriorationTimer > 0.0f)
bool paused = !ShouldDeteriorate() && ForceDeteriorationTimer <= 0.0f;
if (ForceDeteriorationTimer > 0.0f)
{
GUI.DrawString(spriteBatch,
new Vector2(item.DrawPosition.X, -item.DrawPosition.Y), "Forced deterioration for " + ((int)ForceDeteriorationTimer) + " s",
Color.Red, Color.Black * 0.5f);
}
else if (deteriorationTimer > 0.0f)
{
GUI.DrawString(spriteBatch,
new Vector2(item.DrawPosition.X, -item.DrawPosition.Y), "Deterioration delay " + ((int)deteriorationTimer) + (paused ? " [PAUSED]" : ""),
@@ -430,8 +437,7 @@ namespace Barotrauma.Items.Components
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
deteriorationTimer = msg.ReadSingle();
deteriorateAlwaysResetTimer = msg.ReadSingle();
DeteriorateAlways = msg.ReadBoolean();
ForceDeteriorationTimer = msg.ReadSingle();
tinkeringDuration = msg.ReadSingle();
tinkeringStrength = msg.ReadSingle();
tinkeringPowersDevices = msg.ReadBoolean();

View File

@@ -55,20 +55,6 @@ namespace Barotrauma.Items.Components
}
}
private Vector2 GetSourcePos()
{
Vector2 sourcePos = source.WorldPosition;
if (source is Item sourceItem)
{
sourcePos = sourceItem.DrawPosition;
}
else if (source is Limb sourceLimb && sourceLimb.body != null)
{
sourcePos = sourceLimb.body.DrawPosition;
}
return sourcePos;
}
partial void InitProjSpecific(ContentXElement element)
{
foreach (var subElement in element.Elements())
@@ -88,34 +74,22 @@ namespace Barotrauma.Items.Components
}
}
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1)
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1, Color? overrideColor = null)
{
if (target == null || target.Removed) { return; }
if (target.ParentInventory != null) { return; }
if (source is Limb limb && limb.Removed) { return; }
if (source is Entity e && e.Removed) { return; }
Vector2 startPos = GetSourcePos();
Vector2 startPos = GetSourcePos(useDrawPosition: true);
startPos.Y = -startPos.Y;
if (source is Item sourceItem && !sourceItem.Removed)
if ((source as Item)?.GetComponent<Turret>() is { } turret)
{
var turret = sourceItem.GetComponent<Turret>();
var weapon = sourceItem.GetComponent<RangedWeapon>();
if (turret != null)
if (turret.BarrelSprite != null)
{
startPos = new Vector2(sourceItem.WorldRect.X + turret.TransformedBarrelPos.X, -(sourceItem.WorldRect.Y - turret.TransformedBarrelPos.Y));
if (turret.BarrelSprite != null)
{
startPos += new Vector2((float)Math.Cos(turret.Rotation), (float)Math.Sin(turret.Rotation)) * turret.BarrelSprite.size.Y * turret.BarrelSprite.RelativeOrigin.Y * item.Scale * 0.9f;
}
startPos -= turret.GetRecoilOffset();
}
else if (weapon != null)
{
Vector2 barrelPos = FarseerPhysics.ConvertUnits.ToDisplayUnits(weapon.TransformedBarrelPos);
barrelPos.Y = -barrelPos.Y;
startPos += barrelPos;
startPos += new Vector2((float)Math.Cos(turret.Rotation), (float)Math.Sin(turret.Rotation)) * turret.BarrelSprite.size.Y * turret.BarrelSprite.RelativeOrigin.Y * item.Scale * 0.9f;
}
startPos -= turret.GetRecoilOffset();
}
Vector2 endPos = new Vector2(target.DrawPosition.X, target.DrawPosition.Y);
Vector2 flippedPos = target.Sprite.size * target.Scale * (Origin - new Vector2(0.5f));
@@ -156,28 +130,28 @@ namespace Barotrauma.Items.Components
if (startSprite != null)
{
float depth = Math.Min(item.GetDrawDepth() + (startSprite.Depth - item.Sprite.Depth), 0.999f);
startSprite?.Draw(spriteBatch, startPos, SpriteColor, angle, depth: depth);
startSprite?.Draw(spriteBatch, startPos, overrideColor ?? SpriteColor, angle, depth: depth);
}
if (endSprite != null && (!Snapped || BreakFromMiddle))
{
float depth = Math.Min(item.GetDrawDepth() + (endSprite.Depth - item.Sprite.Depth), 0.999f);
endSprite?.Draw(spriteBatch, endPos, SpriteColor, angle, depth: depth);
endSprite?.Draw(spriteBatch, endPos, overrideColor ?? SpriteColor, angle, depth: depth);
}
}
}
private void DrawRope(SpriteBatch spriteBatch, Vector2 startPos, Vector2 endPos, int width)
private void DrawRope(SpriteBatch spriteBatch, Vector2 startPos, Vector2 endPos, int width, Color? overrideColor = null)
{
float depth = sprite == null ?
item.Sprite.Depth + 0.001f :
Math.Min(item.GetDrawDepth() + (sprite.Depth - item.Sprite.Depth), 0.999f);
if (sprite?.Texture == null)
{
GUI.DrawLine(spriteBatch,
startPos,
endPos,
SpriteColor, depth: depth, width: width);
overrideColor ?? SpriteColor, depth: depth, width: width);
return;
}
@@ -191,7 +165,7 @@ namespace Barotrauma.Items.Components
GUI.DrawLine(spriteBatch, sprite,
startPos + dir * (x - 5.0f),
startPos + dir * (x + sprite.size.X),
SpriteColor, depth: depth, width: width);
overrideColor ?? SpriteColor, depth: depth, width: width);
}
float leftOver = length - x;
if (leftOver > 0.0f)
@@ -199,7 +173,7 @@ namespace Barotrauma.Items.Components
GUI.DrawLine(spriteBatch, sprite,
startPos + dir * (x - 5.0f),
endPos,
SpriteColor, depth: depth, width: width);
overrideColor ?? SpriteColor, depth: depth, width: width);
}
}
else
@@ -207,7 +181,7 @@ namespace Barotrauma.Items.Components
GUI.DrawLine(spriteBatch, sprite,
startPos,
endPos,
SpriteColor, depth: depth, width: width);
overrideColor ?? SpriteColor, depth: depth, width: width);
}
}

View File

@@ -0,0 +1,499 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
namespace Barotrauma.Items.Components
{
internal sealed partial class CircuitBox
{
public CircuitBoxUI? UI;
public readonly Dictionary<Character, CircuitBoxCursor> ActiveCursors = new Dictionary<Character, CircuitBoxCursor>();
public Option<ItemPrefab> HeldComponent = Option.None;
private const float CursorUpdateInterval = 1f;
private float cursorUpdateTimer;
private readonly Vector2[] recordedCursorPositions = new Vector2[10];
private Option<Vector2> recordedDragStart = Option.None;
private Option<ItemPrefab> recordedHeldPrefab = Option.None;
/// <summary>
/// If the circuit box was initialized by the server instead of from the save file.
/// Used to ensure the wires the server sends are properly connected up when we load in.
/// </summary>
private bool wasInitializedByServer;
public Sprite? WireSprite { get; private set; }
public Sprite? ConnectionSprite { get; private set; }
public Sprite? WireConnectorSprite { get; private set; }
public Sprite? ConnectionScrewSprite { get; private set; }
public UISprite? NodeFrameSprite { get; private set; }
public UISprite? NodeTopSprite { get; private set; }
protected override void CreateGUI()
{
base.CreateGUI();
GuiFrame.ClearChildren();
UI?.CreateGUI(GuiFrame);
}
partial void InitProjSpecific(ContentXElement element)
{
UI = new CircuitBoxUI(this);
IsActive = true;
CreateGUI();
foreach (var subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "wiresprite":
WireSprite = new Sprite(subElement);
break;
case "connectionsprite":
ConnectionSprite = new Sprite(subElement);
break;
case "wireconnectorsprite":
WireConnectorSprite = new Sprite(subElement);
break;
case "connectionscrewsprite":
ConnectionScrewSprite = new Sprite(subElement);
break;
}
}
if (GUIStyle.GetComponentStyle("CircuitBoxTop") is { } topStyle)
{
NodeTopSprite = topStyle.Sprites[GUIComponent.ComponentState.None][0];
}
if (GUIStyle.GetComponentStyle("CircuitBoxFrame") is { } compStyle)
{
NodeFrameSprite = compStyle.Sprites[GUIComponent.ComponentState.None][0];
}
}
public override bool ShouldDrawHUD(Character character)
=> character == Character.Controlled && (character.SelectedItem == item || character.SelectedSecondaryItem == item);
public override void UpdateHUDComponentSpecific(Character character, float deltaTime, Camera cam)
{
if (UI is null) { return; }
UI.Update(deltaTime);
if (GameMain.NetworkMember is null) { return; }
foreach (var (cursorChar, cursor) in ActiveCursors)
{
if (!cursor.IsActive) { continue; }
ActiveCursors[cursorChar].Update(deltaTime);
}
Vector2 cursorPos = UI.GetCursorPosition();
int lastCursorPosIndex = recordedCursorPositions.Length - 1;
if (cursorUpdateTimer < CursorUpdateInterval)
{
cursorUpdateTimer += deltaTime;
int cursorIndex = (int)MathF.Floor(cursorUpdateTimer * lastCursorPosIndex);
RecordCursorPosition(cursorIndex);
}
else
{
RecordCursorPosition(lastCursorPosIndex);
SendCursorState(recordedCursorPositions, recordedDragStart, recordedHeldPrefab.Select(static c => c.Identifier));
recordedDragStart = Option.None;
recordedHeldPrefab = Option.None;
cursorUpdateTimer = 0f;
}
void RecordCursorPosition(int index)
{
var dragStart = UI.GetDragStart();
if (dragStart.IsSome()) { recordedDragStart = dragStart; }
var heldComponent = HeldComponent;
if (heldComponent.IsSome()) { recordedHeldPrefab = heldComponent; }
if (index >= 0 && index < recordedCursorPositions.Length) { recordedCursorPositions[index] = cursorPos; }
}
}
public void RemoveComponents(IReadOnlyCollection<CircuitBoxComponent> node)
{
var ids = node.Select(static n => n.ID).ToImmutableArray();
if (GameMain.NetworkMember is null)
{
CreateRefundItemsForUsedResources(ids, Character.Controlled);
RemoveComponentInternal(ids);
return;
}
if (!node.Any()) { return; }
CreateClientEvent(new CircuitBoxRemoveComponentEvent(ids));
}
public void AddWire(CircuitBoxConnection one, CircuitBoxConnection two)
{
if (GameMain.NetworkMember is null)
{
Connect(one, two, static delegate { }, CircuitBoxWire.SelectedWirePrefab);
return;
}
if (!VerifyConnection(one, two)) { return; }
CreateClientEvent(new CircuitBoxClientAddWireEvent(Color.White, CircuitBoxConnectorIdentifier.FromConnection(one), CircuitBoxConnectorIdentifier.FromConnection(two), CircuitBoxWire.SelectedWirePrefab.UintIdentifier));
}
public void RemoveWires(IReadOnlyCollection<CircuitBoxWire> wires)
{
var ids = wires.Select(static w => w.ID).ToImmutableArray();
if (GameMain.NetworkMember is null)
{
RemoveWireInternal(ids);
return;
}
if (!ids.Any()) { return; }
CreateClientEvent(new CircuitBoxRemoveWireEvent(ids));
}
public void SelectComponents(IReadOnlyCollection<CircuitBoxNode> moveables, bool overwrite)
{
if (Character.Controlled is not { ID: var controlledId }) { return; }
var ids = ImmutableArray.CreateBuilder<ushort>();
var ios = ImmutableArray.CreateBuilder<CircuitBoxInputOutputNode.Type>();
foreach (var moveable in moveables)
{
if (moveable is { IsSelected: true, IsSelectedByMe: false }) { continue; }
switch (moveable)
{
case CircuitBoxComponent node:
ids.Add(node.ID);
break;
case CircuitBoxInputOutputNode io:
ios.Add(io.NodeType);
break;
}
}
if (GameMain.NetworkMember is null)
{
SelectComponentsInternal(ids, controlledId, overwrite);
SelectInputOutputInternal(ios, controlledId, overwrite);
return;
}
if ((!ids.Any() && !ios.Any()) && !overwrite) { return; }
CreateClientEvent(new CircuitBoxSelectNodesEvent(ids.ToImmutable(), ios.ToImmutable(), overwrite, controlledId));
}
public void SelectWires(IReadOnlyCollection<CircuitBoxWire> wires, bool overwrite)
{
if (Character.Controlled is not { ID: var controlledId }) { return; }
var ids = (from wire in wires where !wire.IsSelected || wire.IsSelectedByMe select wire.ID).ToImmutableArray();
if (GameMain.NetworkMember is null)
{
SelectWiresInternal(ids, controlledId, overwrite);
return;
}
if (!ids.Any() && !overwrite) { return; }
CreateClientEvent(new CircuitBoxSelectWiresEvent(ids, overwrite, Character.Controlled.ID));
}
public void MoveComponent(Vector2 moveAmount, IReadOnlyCollection<CircuitBoxNode> moveables)
{
var ids = ImmutableArray.CreateBuilder<ushort>();
var ios = ImmutableArray.CreateBuilder<CircuitBoxInputOutputNode.Type>();
foreach (CircuitBoxNode move in moveables)
{
switch (move)
{
case CircuitBoxComponent node:
ids.Add(node.ID);
break;
case CircuitBoxInputOutputNode io:
ios.Add(io.NodeType);
break;
}
}
if (GameMain.NetworkMember is null)
{
MoveNodesInternal(ids, ios, moveAmount);
return;
}
if (!ids.Any() && !ios.Any()) { return; }
CreateClientEvent(new CircuitBoxMoveComponentEvent(ids.ToImmutable(), ios.ToImmutable(), moveAmount));
}
public void AddComponent(ItemPrefab prefab, Vector2 pos)
{
if (GameMain.NetworkMember is null)
{
ItemPrefab resource;
if (IsFull) { return; }
if (IsInGame())
{
if (!GetApplicableResourcePlayerHas(prefab, Character.Controlled).TryUnwrap(out var r)) { return; }
resource = r.Prefab;
RemoveItem(r);
}
else
{
resource = ItemPrefab.Prefabs[Tags.FPGACircuit];
}
AddComponentInternal(ICircuitBoxIdentifiable.FindFreeID(Components), prefab, resource, pos, static delegate { });
return;
}
CreateClientEvent(new CircuitBoxAddComponentEvent(prefab.UintIdentifier, pos));
}
public partial void OnViewUpdateProjSpecific()
{
UI?.MouseSnapshotHandler.UpdateConnections();
UI?.UpdateComponentList();
}
protected override void OnResolutionChanged()
{
base.OnResolutionChanged();
CreateGUI();
}
// Remove selection when the circuit box is deselected
public partial void OnDeselected(Character c)
{
cursorUpdateTimer = 0f;
// Server will broadcast the deselection, we don't need to do it ourselves
if (GameMain.NetworkMember is not null) { return; }
ClearAllSelectionsInternal(c.ID);
}
public void ClientRead(INetSerializableStruct data)
{
switch (data)
{
case NetCircuitBoxCursorInfo cursorInfo:
{
ClientReadCursor(cursorInfo);
break;
}
case CircuitBoxErrorEvent errorData:
{
DebugConsole.ThrowError($"The server responded with an error: {errorData.Message}");
break;
}
default:
throw new ArgumentOutOfRangeException(nameof(data), data, "This data cannot be handled using direct network messages.");
}
}
public void SendMessage(CircuitBoxOpcode opcode, INetSerializableStruct data)
{
IWriteMessage msg = new WriteOnlyMessage().WithHeader(ClientPacketHeader.CIRCUITBOX);
msg.WriteNetSerializableStruct(new NetCircuitBoxHeader(
Opcode: opcode,
ItemID: item.ID,
ComponentIndex: (byte)item.GetComponentIndex(this)));
msg.WriteNetSerializableStruct(data);
DeliveryMethod deliveryMethod =
UnrealiableOpcodes.Contains(opcode)
? DeliveryMethod.Unreliable
: DeliveryMethod.Reliable;
GameMain.Client?.ClientPeer?.Send(msg, deliveryMethod);
}
private void SendCursorState(Vector2[] cursorPositions, Option<Vector2> dragStart, Option<Identifier> heldComponent)
{
if (!IsRoundRunning()) { return; }
var msg = new NetCircuitBoxCursorInfo(
RecordedPositions: cursorPositions,
DragStart: dragStart,
HeldItem: heldComponent);
SendMessage(CircuitBoxOpcode.Cursor, msg);
}
public void ClientReadCursor(NetCircuitBoxCursorInfo info)
{
if (Entity.FindEntityByID(info.CharacterID) is not Character character) { return; }
if (!ActiveCursors.ContainsKey(character))
{
var newCursor = new CircuitBoxCursor(info);
ActiveCursors.Add(character, newCursor);
return;
}
var activeCursor = ActiveCursors[character];
activeCursor.UpdateInfo(info);
activeCursor.ResetTimers();
}
public void CreateClientEvent(INetSerializableStruct data)
=> item.CreateClientEvent(this, new CircuitBoxEventData(data));
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData? extraData = null)
{
if (extraData is null) { return; }
var eventData = ExtractEventData<CircuitBoxEventData>(extraData);
msg.WriteByte((byte)eventData.Opcode);
msg.WriteNetSerializableStruct(eventData.Data);
}
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
var header = (CircuitBoxOpcode)msg.ReadByte();
switch (header)
{
case CircuitBoxOpcode.AddComponent:
{
var data = INetSerializableStruct.Read<CircuitBoxServerCreateComponentEvent>(msg);
AddComponentFromData(data);
break;
}
case CircuitBoxOpcode.DeleteComponent:
{
var data = INetSerializableStruct.Read<CircuitBoxRemoveComponentEvent>(msg);
RemoveComponentInternal(data.TargetIDs);
break;
}
case CircuitBoxOpcode.MoveComponent:
{
var data = INetSerializableStruct.Read<CircuitBoxMoveComponentEvent>(msg);
MoveNodesInternal(data.TargetIDs, data.IOs, data.MoveAmount);
break;
}
case CircuitBoxOpcode.UpdateSelection:
{
var data = INetSerializableStruct.Read<CircuitBoxServerUpdateSelection>(msg);
var nodeDict = data.ComponentIds.ToImmutableDictionary(static s => s.ID, static s => s.SelectedBy);
var wireDict = data.WireIds.ToImmutableDictionary(static s => s.ID, static s => s.SelectedBy);
var ioDict = data.InputOutputs.ToImmutableDictionary(static s => s.Type, static s => s.SelectedBy);
UpdateSelections(nodeDict, wireDict, ioDict);
break;
}
case CircuitBoxOpcode.AddWire:
{
var data = INetSerializableStruct.Read<CircuitBoxServerCreateWireEvent>(msg);
AddWireFromData(data);
break;
}
case CircuitBoxOpcode.RemoveWire:
{
var data = INetSerializableStruct.Read<CircuitBoxRemoveWireEvent>(msg);
RemoveWireInternal(data.TargetIDs);
break;
}
case CircuitBoxOpcode.ServerInitialize:
{
Components.Clear();
Wires.Clear();
var data = INetSerializableStruct.Read<CircuitBoxInitializeStateFromServerEvent>(msg);
foreach (var compData in data.Components) { AddComponentFromData(compData); }
foreach (var wireData in data.Wires) { AddWireFromData(wireData); }
foreach (var node in InputOutputNodes)
{
node.Position = node.NodeType switch
{
CircuitBoxInputOutputNode.Type.Input => data.InputPos,
CircuitBoxInputOutputNode.Type.Output => data.OutputPos,
_ => node.Position
};
}
wasInitializedByServer = true;
break;
}
default:
throw new ArgumentOutOfRangeException(nameof(header), header, "This opcode cannot be handled using entity events");
}
}
public void AddComponentFromData(CircuitBoxServerCreateComponentEvent data)
{
if (ItemPrefab.Prefabs.Find(p => p.UintIdentifier == data.UsedResource) is not { } prefab)
{
throw new Exception($"No item prefab found for \"{data.UsedResource}\"");
}
AddComponentInternalUnsafe(data.ComponentId, FindItemByID(data.BackingItemId), prefab, data.Position);
}
public void AddWireFromData(CircuitBoxServerCreateWireEvent data)
{
var (req, wireId, possibleItemId) = data;
var prefab = ItemPrefab.Prefabs.Find(p => p.UintIdentifier == req.SelectedWirePrefabIdentifier);
if (prefab is null)
{
throw new Exception($"No prefab found for \"{req.SelectedWirePrefabIdentifier}\"");
}
if (!req.Start.FindConnection(this).TryUnwrap(out var start))
{
throw new Exception($"No connection found for ({req.Start})");
}
if (!req.End.FindConnection(this).TryUnwrap(out var end))
{
throw new Exception($"No connection found for ({req.Start})");
}
if (possibleItemId.TryUnwrap(out var backingItem))
{
CreateWireWithItem(start, end, wireId, FindItemByID(backingItem));
}
else
{
CreateWireWithoutItem(start, end, wireId, prefab);
}
}
public static Item FindItemByID(ushort id)
=> Entity.FindEntityByID(id) as Item ?? throw new Exception($"No item with ID {id} exists.");
public override void AddToGUIUpdateList(int order = 0)
{
base.AddToGUIUpdateList(order);
UI?.AddToGUIUpdateList();
}
}
}

View File

@@ -21,6 +21,8 @@ namespace Barotrauma.Items.Components
public float FlashTimer { get; private set; }
public static Wire DraggingConnected { get; private set; }
private static float ConnectionSpriteSize => 35.0f * GUI.Scale;
public static void DrawConnections(SpriteBatch spriteBatch, ConnectionPanel panel, Rectangle dragArea, Character character,
out (Vector2 tooltipPos, LocalizedString text) tooltip)
{
@@ -33,8 +35,6 @@ namespace Barotrauma.Items.Components
int x = panelRect.X, y = panelRect.Y;
int width = panelRect.Width, height = panelRect.Height;
Vector2 scale = GetScale(panel.GuiFrame.RectTransform.MaxSize, panel.GuiFrame.Rect.Size);
bool mouseInRect = panelRect.Contains(PlayerInput.MousePosition);
int totalWireCount = 0;
@@ -70,15 +70,15 @@ namespace Barotrauma.Items.Components
//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 = GetRightPos(x, y, width, scale);
Vector2 leftPos = GetLeftPos(x, y, scale);
Vector2 rightPos = GetRightPos(x, y, width);
Vector2 leftPos = GetLeftPos(x, 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);
Vector2 rightWirePos = new Vector2(x + width - 5 * GUI.Scale, y + 30 * GUI.Scale);
Vector2 leftWirePos = new Vector2(x + 5 * GUI.Scale, y + 30 * GUI.Scale);
int wireInterval = (height - (int)(20 * scale.Y)) / Math.Max(totalWireCount, 1);
int connectorIntervalLeft = GetConnectorIntervalLeft(height, scale, panel);
int connectorIntervalRight = GetConnectorIntervalRight(height, scale, panel);
int wireInterval = (height - (int)(20 * GUI.Scale)) / Math.Max(totalWireCount, 1);
int connectorIntervalLeft = GetConnectorIntervalLeft(height, panel);
int connectorIntervalRight = GetConnectorIntervalRight(height, panel);
foreach (Connection c in panel.Connections)
{
@@ -101,47 +101,26 @@ namespace Barotrauma.Items.Components
}
Vector2 position = c.IsOutput ? rightPos : leftPos;
Color highlightColor = Color.Transparent;
if (ConnectionPanel.ShouldDebugDrawWiring)
{
if (c.IsPower)
{
highlightColor = VisualizeSignal(0.0f, highlightColor, Color.Red);
}
else
{
highlightColor = VisualizeSignal(c.LastReceivedSignal.TimeSinceCreated, highlightColor, Color.LightGreen);
highlightColor = VisualizeSignal(c.LastSentSignal.TimeSinceCreated, highlightColor, Color.Orange);
}
bool mouseOn = Vector2.DistanceSquared(position, PlayerInput.MousePosition) < MathUtils.Pow2(35 * GUI.Scale);
DrawConnectionDebugInfo(spriteBatch, c, position, GUI.Scale, out var tooltipText);
LocalizedString toolTipText = c.GetToolTip();
if (mouseOn) { tooltip = (position, toolTipText); }
if (!toolTipText.IsNullOrEmpty())
if (!tooltipText.IsNullOrEmpty())
{
var glowSprite = GUIStyle.UIGlowCircular.Value.Sprite;
glowSprite.Draw(spriteBatch, position, highlightColor, glowSprite.size / 2,
scale: 45.0f / glowSprite.size.X * panel.Scale);
bool mouseOn = Vector2.DistanceSquared(position, PlayerInput.MousePosition) < MathUtils.Pow2(35 * GUI.Scale);
if (mouseOn)
{
tooltip = (position, tooltipText);
}
}
}
static Color VisualizeSignal(double timeSinceCreated, Color defaultColor, Color color)
{
if (timeSinceCreated < 1.0f)
{
float pulseAmount = (MathF.Sin((float)Timing.TotalTimeUnpaused * 10.0f) + 3.0f) / 4.0f;
Color targetColor = Color.Lerp(defaultColor, color, pulseAmount);
return Color.Lerp(targetColor, defaultColor, (float)timeSinceCreated);
}
return defaultColor;
}
//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, GetOutputLabelPosition(rightPos, panel, c), scale);
c.DrawConnection(spriteBatch, panel, rightPos, GetOutputLabelPosition(rightPos, panel, c));
}
else
{
@@ -154,7 +133,7 @@ namespace Barotrauma.Items.Components
{
if (i == 0)
{
c.DrawConnection(spriteBatch, panel, leftPos, GetInputLabelPosition(leftPos, panel, c), scale);
c.DrawConnection(spriteBatch, panel, leftPos, GetInputLabelPosition(leftPos, panel, c));
}
else
{
@@ -224,8 +203,8 @@ namespace Barotrauma.Items.Components
}
float step = (width * 0.75f) / panel.DisconnectedWires.Count();
x = (int)(x + width / 2 - step * (panel.DisconnectedWires.Count() - 1) / 2);
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; }
@@ -243,26 +222,61 @@ namespace Barotrauma.Items.Components
//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.DraggingItems.Clear();
}
{
Inventory.DraggingItems.Clear();
}
}
private void DrawConnection(SpriteBatch spriteBatch, ConnectionPanel panel, Vector2 position, Vector2 labelPos, Vector2 scale)
public static void DrawConnectionDebugInfo(SpriteBatch spriteBatch, Connection c, Vector2 position, float scale, out LocalizedString tooltip)
{
Color highlightColor = Color.Transparent;
if (c.IsPower)
{
highlightColor = VisualizeSignal(0.0f, highlightColor, Color.Red);
}
else
{
highlightColor = VisualizeSignal(c.LastReceivedSignal.TimeSinceCreated, highlightColor, Color.LightGreen);
highlightColor = VisualizeSignal(c.LastSentSignal.TimeSinceCreated, highlightColor, Color.Orange);
}
LocalizedString toolTipText = c.GetToolTip();
if (!toolTipText.IsNullOrEmpty())
{
var glowSprite = GUIStyle.UIGlowCircular.Value.Sprite;
glowSprite.Draw(spriteBatch, position, highlightColor, glowSprite.size / 2,
scale: 45.0f / glowSprite.size.X * scale);
}
tooltip = toolTipText;
static Color VisualizeSignal(double timeSinceCreated, Color defaultColor, Color color)
{
if (timeSinceCreated < 1.0f)
{
float pulseAmount = (MathF.Sin((float)Timing.TotalTimeUnpaused * 10.0f) + 3.0f) / 4.0f;
Color targetColor = Color.Lerp(defaultColor, color, pulseAmount);
return Color.Lerp(targetColor, defaultColor, (float)timeSinceCreated);
}
return defaultColor;
}
}
private void DrawConnection(SpriteBatch spriteBatch, ConnectionPanel panel, Vector2 position, Vector2 labelPos)
{
string text = DisplayName.Value.ToUpperInvariant();
//nasty
if (GUIStyle.GetComponentStyle("ConnectionPanelLabel")?.Sprites.Values.First().First() is UISprite labelSprite)
{
Rectangle labelArea = GetLabelArea(labelPos, text, scale);
Rectangle labelArea = GetLabelArea(labelPos, text);
labelSprite.Draw(spriteBatch, labelArea, IsPower ? GUIStyle.Red : Color.SteelBlue);
}
GUI.DrawString(spriteBatch, labelPos + Vector2.UnitY, text, Color.Black * 0.8f, font: GUIStyle.SmallFont);
GUI.DrawString(spriteBatch, labelPos, text, GUIStyle.TextColorBright, font: GUIStyle.SmallFont);
float connectorSpriteScale = (35.0f / connectionSprite.SourceRect.Width) * panel.Scale;
float connectorSpriteScale = ConnectionSpriteSize / connectionSprite.SourceRect.Width;
connectionSprite.Draw(spriteBatch, position, scale: connectorSpriteScale);
@@ -270,7 +284,7 @@ namespace Barotrauma.Items.Components
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;
float connectorSpriteScale = ConnectionSpriteSize / connectionSprite.SourceRect.Width;
foreach (var wire in wires)
{
@@ -285,9 +299,14 @@ namespace Barotrauma.Items.Components
wirePosition.Y += wireInterval;
}
if (DraggingConnected != null && Vector2.Distance(position, PlayerInput.MousePosition) < (20.0f * GUI.Scale))
bool isMouseOn = Vector2.Distance(position, PlayerInput.MousePosition) < (20.0f * GUI.Scale);
if (isMouseOn)
{
connectionSpriteHighlight.Draw(spriteBatch, position, scale: connectorSpriteScale);
}
if (DraggingConnected != null && isMouseOn)
{
if (!PlayerInput.PrimaryMouseButtonHeld())
{
@@ -418,7 +437,7 @@ namespace Barotrauma.Items.Components
bool mouseOn =
canDrag &&
!(GUI.MouseOn is GUIDragHandle) &&
GUI.MouseOn is not GUIDragHandle &&
((PlayerInput.MousePosition.X > Math.Min(start.X, end.X) &&
PlayerInput.MousePosition.X < Math.Max(start.X, end.X) &&
MathUtils.LineToPointDistanceSquared(start, end, PlayerInput.MousePosition) < 36) ||
@@ -442,12 +461,12 @@ namespace Barotrauma.Items.Components
}
}
var wireEnd = end + Vector2.Normalize(start - end) * 30.0f * panel.Scale;
var wireEnd = end + Vector2.Normalize(start - end) * 30.0f * GUI.Scale;
float dist = Vector2.Distance(start, wireEnd);
float wireWidth = 12 * panel.Scale;
float highlight = 5 * panel.Scale;
float wireWidth = 12 * GUI.Scale;
float highlight = 5 * GUI.Scale;
if (mouseOn)
{
spriteBatch.Draw(wireVertical.Texture, new Rectangle(wireEnd.ToPoint(), new Point((int)(wireWidth + highlight), (int)dist)), wireVertical.SourceRect,
@@ -488,110 +507,84 @@ namespace Barotrauma.Items.Components
{
Rectangle panelRect = panel.GuiFrame.Rect;
int x = panelRect.X, y = panelRect.Y;
Vector2 scale = GetScale(panel.GuiFrame.RectTransform.MaxSize, panel.GuiFrame.Rect.Size);
Vector2 rightPos = GetRightPos(x, y, panelRect.Width, scale);
Vector2 leftPos = GetLeftPos(x, y, scale);
int connectorIntervalLeft = GetConnectorIntervalLeft(panelRect.Height, scale, panel);
int connectorIntervalRight = GetConnectorIntervalRight(panelRect.Height, scale, panel);
Vector2 rightPos = GetRightPos(x, y, panelRect.Width);
Vector2 leftPos = GetLeftPos(x, y);
int connectorIntervalLeft = GetConnectorIntervalLeft(panelRect.Height, panel);
int connectorIntervalRight = GetConnectorIntervalRight(panelRect.Height, panel);
newRectSize = panelRect.Size;
var labelAreas = new List<Rectangle>();
for (int i = 0; i < 100; i++)
//make sure the connection labels don't overlap horizontally
float rightMostInput = panelRect.Center.X;
float leftMostOutput = panelRect.Center.X;
foreach (var c in panel.Connections)
{
labelAreas.Clear();
foreach (var c in panel.Connections)
if (c.IsOutput)
{
if (c.IsOutput)
{
var labelArea = GetLabelArea(GetOutputLabelPosition(rightPos, panel, c), c.DisplayName.Value.ToUpperInvariant(), scale);
labelAreas.Add(labelArea);
rightPos.Y += connectorIntervalLeft;
}
else
{
var labelArea = GetLabelArea(GetInputLabelPosition(leftPos, panel, c), c.DisplayName.Value.ToUpperInvariant(), scale);
labelAreas.Add(labelArea);
leftPos.Y += connectorIntervalRight;
}
var labelArea = GetLabelArea(GetOutputLabelPosition(rightPos, panel, c), c.DisplayName.Value.ToUpperInvariant());
leftMostOutput = Math.Min(leftMostOutput, labelArea.X);
rightPos.Y += connectorIntervalLeft;
}
bool foundOverlap = false;
for (int j = 0; j < labelAreas.Count; j++)
else
{
for (int k = 0; k < labelAreas.Count; k++)
{
if (k == j) { continue; }
if (!labelAreas[j].Intersects(labelAreas[k])) { continue; }
newRectSize += new Point(10);
Point maxSize = new Point(
Math.Max(panel.GuiFrame.RectTransform.MaxSize.X, newRectSize.X),
Math.Max(panel.GuiFrame.RectTransform.MaxSize.Y, newRectSize.Y));
scale = GetScale(maxSize, newRectSize);
rightPos = GetRightPos(x, y, newRectSize.X, scale);
leftPos = GetLeftPos(x, y, scale);
connectorIntervalLeft = GetConnectorIntervalLeft(newRectSize.Y, scale, panel);
connectorIntervalRight = GetConnectorIntervalRight(newRectSize.Y, scale, panel);
foundOverlap = true;
break;
}
var labelArea = GetLabelArea(GetInputLabelPosition(leftPos, panel, c), c.DisplayName.Value.ToUpperInvariant());
rightMostInput = Math.Max(rightMostInput, labelArea.Right);
leftPos.Y += connectorIntervalRight;
}
if (!foundOverlap) { break; }
}
if (leftMostOutput < rightMostInput)
{
newRectSize += new Point((int)(rightMostInput - leftMostOutput) + GUI.IntScale(15), 0);
}
//make sure connection sprites don't overlap vertically
while (GetConnectorIntervalLeft(newRectSize.Y, panel) < ConnectionSpriteSize ||
GetConnectorIntervalRight(newRectSize.Y, panel) < ConnectionSpriteSize)
{
newRectSize.Y += 10;
}
return newRectSize.X != panel.GuiFrame.Rect.Width || newRectSize.Y > panel.GuiFrame.Rect.Height;
}
private static Vector2 GetScale(Point maxSize, Point size)
{
Vector2 scale = new Vector2(GUI.Scale);
if (maxSize.X < int.MaxValue)
{
scale.X = maxSize.X / size.X;
}
if (maxSize.Y < int.MaxValue)
{
scale.Y = maxSize.Y / size.Y;
}
return scale;
}
private static Vector2 GetInputLabelPosition(Vector2 connectorPosition, ConnectionPanel panel, Connection connection)
{
return new Vector2(
connectorPosition.X + 25 * panel.Scale,
connectorPosition.Y - 5 * panel.Scale - GUIStyle.SmallFont.MeasureString(connection.DisplayName.ToUpper()).Y);
connectorPosition.X + 25 * GUI.Scale,
connectorPosition.Y - GUIStyle.SmallFont.MeasureString(connection.DisplayName.ToUpper()).Y / 2);
}
private static Vector2 GetOutputLabelPosition(Vector2 connectorPosition, ConnectionPanel panel, Connection connection)
{
return new Vector2(
connectorPosition.X - 25 * panel.Scale - GUIStyle.SmallFont.MeasureString(connection.DisplayName.ToUpper()).X,
connectorPosition.Y + 5 * panel.Scale);
connectorPosition.X - 25 * GUI.Scale - GUIStyle.SmallFont.MeasureString(connection.DisplayName.ToUpper()).X,
connectorPosition.Y - GUIStyle.SmallFont.MeasureString(connection.DisplayName.ToUpper()).Y / 2);
}
private static Rectangle GetLabelArea(Vector2 labelPos, string text, Vector2 scale)
private static Rectangle GetLabelArea(Vector2 labelPos, string text)
{
Vector2 textSize = GUIStyle.SmallFont.MeasureString(text);
Rectangle labelArea = new Rectangle(labelPos.ToPoint(), textSize.ToPoint());
labelArea.Inflate(10 * scale.X, 3 * scale.Y);
labelArea.Inflate(GUI.IntScale(10), GUI.IntScale(3));
return labelArea;
}
private static Vector2 GetLeftPos(int x, int y, Vector2 scale)
private static Vector2 GetLeftPos(int x, int y)
{
return new Vector2(x + 80 * scale.X, y + 60 * scale.Y);
return new Vector2(x + 80 * GUI.Scale, y + 60 * GUI.Scale);
}
private static Vector2 GetRightPos(int x, int y, int width, Vector2 scale)
private static Vector2 GetRightPos(int x, int y, int width)
{
return new Vector2(x + width - 80 * scale.X, y + 60 * scale.Y);
return new Vector2(x + width - 80 * GUI.Scale, y + 60 * GUI.Scale);
}
private static int GetConnectorIntervalLeft(int height, Vector2 scale, ConnectionPanel panel)
private static int GetConnectorIntervalLeft(int height, ConnectionPanel panel)
{
return (height - (int)(100 * scale.Y)) / Math.Max(panel.Connections.Count(c => c.IsOutput), 1);
return (height - GUI.IntScale(60)) / Math.Max(panel.Connections.Count(c => c.IsOutput), 1);
}
private static int GetConnectorIntervalRight(int height, Vector2 scale, ConnectionPanel panel)
private static int GetConnectorIntervalRight(int height, ConnectionPanel panel)
{
return (height - (int)(100 * scale.Y)) / Math.Max(panel.Connections.Count(c => !c.IsOutput), 1);
return (height - GUI.IntScale(60)) / Math.Max(panel.Connections.Count(c => !c.IsOutput), 1);
}
}
}

View File

@@ -22,11 +22,6 @@ namespace Barotrauma.Items.Components
private SoundChannel rewireSoundChannel;
private float rewireSoundTimer;
public float Scale
{
get { return GuiFrame.Rect.Width / 400.0f; }
}
private Point originalMaxSize;
private Vector2 originalRelativeSize;
@@ -104,10 +99,10 @@ namespace Barotrauma.Items.Components
public override bool ShouldDrawHUD(Character character)
{
return character == Character.Controlled && character == user && character.SelectedItem == item;
return character == Character.Controlled && character == user && (character.SelectedItem == item || character.SelectedSecondaryItem == item);
}
public override void UpdateHUD(Character character, float deltaTime, Camera cam)
public override void UpdateHUDComponentSpecific(Character character, float deltaTime, Camera cam)
{
if (character != Character.Controlled || character != user || character.SelectedItem != item) { return; }
@@ -162,10 +157,11 @@ namespace Barotrauma.Items.Components
//because some of the wires connected to the panel may not exist yet
long msgStartPos = msg.BitPosition;
msg.ReadUInt16(); //user ID
foreach (Connection _ in Connections)
byte connectionCount = msg.ReadByte();
for (int i = 0; i < connectionCount; i++)
{
uint wireCount = msg.ReadVariableUInt32();
for (int i = 0; i < wireCount; i++)
for (int j = 0; j < wireCount; j++)
{
msg.ReadUInt16();
}
@@ -208,21 +204,25 @@ namespace Barotrauma.Items.Components
connection.ClearConnections();
}
foreach (Connection connection in Connections)
byte connectionCount = msg.ReadByte();
for (int i = 0; i < connectionCount; i++)
{
HashSet<Wire> newWires = new HashSet<Wire>();
uint wireCount = msg.ReadVariableUInt32();
for (int i = 0; i < wireCount; i++)
for (int j = 0; j < wireCount; j++)
{
ushort wireId = msg.ReadUInt16();
if (!(Entity.FindEntityByID(wireId) is Item wireItem)) { continue; }
if (Entity.FindEntityByID(wireId) is not Item wireItem) { continue; }
Wire wireComponent = wireItem.GetComponent<Wire>();
if (wireComponent == null) { continue; }
newWires.Add(wireComponent);
}
//this may happen if the item has been deleted server-side at the point the server is writing this event to the client
if (i >= Connections.Count) { continue; }
var connection = Connections[i];
Wire[] oldWires = connection.Wires.Where(w => !newWires.Contains(w)).ToArray();
foreach (var wire in oldWires)
{

View File

@@ -244,7 +244,7 @@ namespace Barotrauma.Items.Components
}
}
public override void UpdateHUD(Character character, float deltaTime, Camera cam)
public override void UpdateHUDComponentSpecific(Character character, float deltaTime, Camera cam)
{
bool elementVisibilityChanged = false;
int visibleElementCount = 0;
@@ -335,17 +335,18 @@ namespace Barotrauma.Items.Components
if (signals == null) { return; }
for (int i = 0; i < signals.Length && i < uiElements.Count; i++)
{
string signal = customInterfaceElementList[i].Signal;
if (uiElements[i] is GUITextBox tb)
{
tb.Text = Screen.Selected is { IsEditor: true } ?
customInterfaceElementList[i].Signal :
TextManager.Get(customInterfaceElementList[i].Signal).Value;
signal :
TextManager.Get(signal).Fallback(signal).Value;
}
else if (uiElements[i] is GUINumberInput ni)
{
if (ni.InputType == NumberType.Int)
{
int.TryParse(customInterfaceElementList[i].Signal, out int value);
int.TryParse(signal, out int value);
ni.IntValue = value;
}
}

View File

@@ -10,7 +10,7 @@ namespace Barotrauma.Items.Components
get { return new Vector2(rangeX, rangeY) * 2.0f; }
}
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1)
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1, Color? overrideColor = null)
{
if (!editing || !MapEntity.SelectedList.Contains(item)) { return; }

View File

@@ -86,15 +86,15 @@ namespace Barotrauma.Items.Components
}
OutputValue = input;
ShowOnDisplay(input, addToHistory: true, TextColor);
ShowOnDisplay(input, addToHistory: true, TextColor, isWelcomeMessage: false);
item.SendSignal(input, "signal_out");
}
partial void ShowOnDisplay(string input, bool addToHistory, Color color)
partial void ShowOnDisplay(string input, bool addToHistory, Color color, bool isWelcomeMessage)
{
if (addToHistory)
{
messageHistory.Add(new TerminalMessage(input, color));
messageHistory.Add(new TerminalMessage(input, color, isWelcomeMessage));
while (messageHistory.Count > MaxMessages)
{
messageHistory.RemoveAt(0);

View File

@@ -11,9 +11,9 @@ namespace Barotrauma.Items.Components
get { return new Vector2(range * 2); }
}
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1)
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1, Color? overrideColor = null)
{
if (!editing || !MapEntity.SelectedList.Contains(item)) return;
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);

View File

@@ -186,12 +186,12 @@ namespace Barotrauma.Items.Components
return Color.LightBlue;
}
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1)
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1, Color? overrideColor = null)
{
Draw(spriteBatch, editing, Vector2.Zero, itemDepth);
Draw(spriteBatch, editing, Vector2.Zero, itemDepth, overrideColor);
}
public void Draw(SpriteBatch spriteBatch, bool editing, Vector2 offset, float itemDepth = -1)
public void Draw(SpriteBatch spriteBatch, bool editing, Vector2 offset, float itemDepth = -1, Color? overrideColor = null)
{
if (sections.Count == 0 && !IsActive || Hidden)
{
@@ -223,7 +223,7 @@ namespace Barotrauma.Items.Components
foreach (WireSection section in sections)
{
section.Draw(spriteBatch, wireSprite, item.Color, drawOffset, depth, Width);
section.Draw(spriteBatch, wireSprite, overrideColor ?? item.Color, drawOffset, depth, Width);
}
if (nodes.Count > 0)
@@ -271,13 +271,13 @@ namespace Barotrauma.Items.Components
spriteBatch, wireSprite,
nodes[^1] + drawOffset,
new Vector2(newNodePos.X, newNodePos.Y) + drawOffset,
item.Color, 0.0f, Width);
overrideColor ?? item.Color, 0.0f, Width);
WireSection.Draw(
spriteBatch, wireSprite,
new Vector2(newNodePos.X, newNodePos.Y) + drawOffset,
item.DrawPosition,
item.Color, itemDepth, Width);
overrideColor ?? item.Color, itemDepth, Width);
GUI.DrawRectangle(spriteBatch, new Vector2(newNodePos.X + drawOffset.X, -(newNodePos.Y + drawOffset.Y)) - Vector2.One * 3, Vector2.One * 6, item.Color);
}
@@ -287,7 +287,7 @@ namespace Barotrauma.Items.Components
spriteBatch, wireSprite,
nodes[^1] + drawOffset,
item.DrawPosition,
item.Color, 0.0f, Width);
overrideColor ?? item.Color, 0.0f, Width);
}
}
}
@@ -594,7 +594,7 @@ namespace Barotrauma.Items.Components
selectedWire.shouldClearConnections = false;
Character.Controlled.Inventory.TryPutItem(selectedWire.item, Character.Controlled, new List<InvSlotType> { InvSlotType.LeftHand, InvSlotType.RightHand });
foreach (var entity in MapEntity.mapEntityList)
foreach (var entity in MapEntity.MapEntityList)
{
if (entity is Item item)
{

View File

@@ -57,13 +57,6 @@ namespace Barotrauma.Items.Components
private readonly List<ParticleEmitter> particleEmitters = new List<ParticleEmitter>();
private readonly List<ParticleEmitter> particleEmitterCharges = new List<ParticleEmitter>();
[Editable, Serialize("0,0,0,0", IsPropertySaveable.Yes, description: "Optional screen tint color when the item is being operated (R,G,B,A).")]
public Color HudTint
{
get;
private set;
}
[Serialize(false, IsPropertySaveable.No, description: "Should the charge of the connected batteries/supercapacitors be shown at the top of the screen when operating the item.")]
public bool ShowChargeIndicator
{
@@ -117,6 +110,13 @@ namespace Barotrauma.Items.Components
get { return barrelSprite; }
}
[Serialize(false, IsPropertySaveable.No)]
public bool HideBarrelWhenBroken
{
get;
private set;
}
partial void InitProjSpecific(ContentXElement element)
{
foreach (var subElement in element.Elements())
@@ -232,7 +232,7 @@ namespace Barotrauma.Items.Components
{
moveSoundChannel.FadeOutAndDispose();
moveSoundChannel = SoundPlayer.PlaySound(endMoveSound.Sound, item.WorldPosition, endMoveSound.Volume, endMoveSound.Range, ignoreMuffling: endMoveSound.IgnoreMuffling, freqMult: endMoveSound.GetRandomFrequencyMultiplier());
if (moveSoundChannel != null) moveSoundChannel.Looping = false;
if (moveSoundChannel != null) { moveSoundChannel.Looping = false; }
}
else if (!moveSoundChannel.IsPlaying)
{
@@ -261,12 +261,13 @@ namespace Barotrauma.Items.Components
if (chargeSound != null)
{
chargeSoundChannel = SoundPlayer.PlaySound(chargeSound.Sound, item.WorldPosition, chargeSound.Volume, chargeSound.Range, ignoreMuffling: chargeSound.IgnoreMuffling, freqMult: chargeSound.GetRandomFrequencyMultiplier());
if (chargeSoundChannel != null) chargeSoundChannel.Looping = true;
if (chargeSoundChannel != null) { chargeSoundChannel.Looping = true; }
}
}
else if (chargeSoundChannel != null)
{
chargeSoundChannel.FrequencyMultiplier = MathHelper.Lerp(0.5f, 1.5f, chargeRatio);
chargeSoundChannel.Position = new Vector3(item.WorldPosition, 0.0f);
}
break;
default:
@@ -318,7 +319,7 @@ namespace Barotrauma.Items.Components
}
}
public override void UpdateHUD(Character character, float deltaTime, Camera cam)
public override void UpdateHUDComponentSpecific(Character character, float deltaTime, Camera cam)
{
if (crosshairSprite != null)
{
@@ -359,7 +360,7 @@ namespace Barotrauma.Items.Components
return new Vector2((float)Math.Cos(rotation), (float)Math.Sin(rotation)) * recoilOffset;
}
public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1)
public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1, Color? overrideColor = null)
{
if (!MathUtils.NearlyEqual(item.Rotation, prevBaseRotation) || !MathUtils.NearlyEqual(item.Scale, prevScale))
{
@@ -367,53 +368,55 @@ namespace Barotrauma.Items.Components
}
Vector2 drawPos = GetDrawPos();
railSprite?.Draw(spriteBatch,
drawPos,
item.SpriteColor,
rotation + MathHelper.PiOver2, item.Scale,
SpriteEffects.None, item.SpriteDepth + (railSprite.Depth - item.Sprite.Depth));
barrelSprite?.Draw(spriteBatch,
drawPos - GetRecoilOffset() * item.Scale,
item.SpriteColor,
rotation + MathHelper.PiOver2, item.Scale,
SpriteEffects.None, item.SpriteDepth + (barrelSprite.Depth - item.Sprite.Depth));
float chargeRatio = currentChargeTime / MaxChargeTime;
foreach ((Sprite chargeSprite, Vector2 position) in chargeSprites)
if (item.Condition > 0.0f || !HideBarrelWhenBroken)
{
chargeSprite?.Draw(spriteBatch,
drawPos - MathUtils.RotatePoint(new Vector2(position.X * chargeRatio, position.Y * chargeRatio) * item.Scale, rotation + MathHelper.PiOver2),
item.SpriteColor,
railSprite?.Draw(spriteBatch,
drawPos,
overrideColor ?? item.SpriteColor,
rotation + MathHelper.PiOver2, item.Scale,
SpriteEffects.None, item.SpriteDepth + (chargeSprite.Depth - item.Sprite.Depth));
}
SpriteEffects.None, item.SpriteDepth + (railSprite.Depth - item.Sprite.Depth));
int spinningBarrelCount = spinningBarrelSprites.Count;
for (int i = 0; i < spinningBarrelCount; i++)
{
// this block is messy since I was debugging it with a bunch of values, should be cleaned up / optimized if prototype is accepted
Sprite spinningBarrel = spinningBarrelSprites[i];
float barrelCirclePosition = (MaxCircle * i / spinningBarrelCount + currentBarrelSpin) % MaxCircle;
float newDepth = item.SpriteDepth + (spinningBarrel.Depth - item.Sprite.Depth) + (barrelCirclePosition > HalfCircle ? 0.0f : 0.001f);
float barrelColorPosition = (barrelCirclePosition + QuarterCircle) % MaxCircle;
float colorOffset = Math.Abs(barrelColorPosition - HalfCircle) / HalfCircle;
Color newColorModifier = Color.Lerp(Color.Black, Color.Gray, colorOffset);
float barrelHalfCirclePosition = Math.Abs(barrelCirclePosition - HalfCircle);
float barrelPositionModifier = MathUtils.SmoothStep(barrelHalfCirclePosition / HalfCircle);
float newPositionOffset = barrelPositionModifier * SpinningBarrelDistance;
spinningBarrel.Draw(spriteBatch,
drawPos - MathUtils.RotatePoint(new Vector2(newPositionOffset, 0f) * item.Scale, rotation + MathHelper.PiOver2),
Color.Lerp(item.SpriteColor, newColorModifier, 0.8f),
barrelSprite?.Draw(spriteBatch,
drawPos - GetRecoilOffset() * item.Scale,
overrideColor ?? item.SpriteColor,
rotation + MathHelper.PiOver2, item.Scale,
SpriteEffects.None, newDepth);
SpriteEffects.None, item.SpriteDepth + (barrelSprite.Depth - item.Sprite.Depth));
float chargeRatio = currentChargeTime / MaxChargeTime;
foreach ((Sprite chargeSprite, Vector2 position) in chargeSprites)
{
chargeSprite?.Draw(spriteBatch,
drawPos - MathUtils.RotatePoint(new Vector2(position.X * chargeRatio, position.Y * chargeRatio) * item.Scale, rotation + MathHelper.PiOver2),
item.SpriteColor,
rotation + MathHelper.PiOver2, item.Scale,
SpriteEffects.None, item.SpriteDepth + (chargeSprite.Depth - item.Sprite.Depth));
}
int spinningBarrelCount = spinningBarrelSprites.Count;
for (int i = 0; i < spinningBarrelCount; i++)
{
// this block is messy since I was debugging it with a bunch of values, should be cleaned up / optimized if prototype is accepted
Sprite spinningBarrel = spinningBarrelSprites[i];
float barrelCirclePosition = (MaxCircle * i / spinningBarrelCount + currentBarrelSpin) % MaxCircle;
float newDepth = item.SpriteDepth + (spinningBarrel.Depth - item.Sprite.Depth) + (barrelCirclePosition > HalfCircle ? 0.0f : 0.001f);
float barrelColorPosition = (barrelCirclePosition + QuarterCircle) % MaxCircle;
float colorOffset = Math.Abs(barrelColorPosition - HalfCircle) / HalfCircle;
Color newColorModifier = Color.Lerp(Color.Black, Color.Gray, colorOffset);
float barrelHalfCirclePosition = Math.Abs(barrelCirclePosition - HalfCircle);
float barrelPositionModifier = MathUtils.SmoothStep(barrelHalfCirclePosition / HalfCircle);
float newPositionOffset = barrelPositionModifier * SpinningBarrelDistance;
spinningBarrel.Draw(spriteBatch,
drawPos - MathUtils.RotatePoint(new Vector2(newPositionOffset, 0f) * item.Scale, rotation + MathHelper.PiOver2),
Color.Lerp(overrideColor ?? item.SpriteColor, newColorModifier, 0.8f),
rotation + MathHelper.PiOver2, item.Scale,
SpriteEffects.None, newDepth);
}
}
if (GameMain.DebugDraw)
@@ -593,6 +596,7 @@ namespace Barotrauma.Items.Components
if (!recipient.IsPower || !recipient.IsOutput) { continue; }
var battery = recipient.Item?.GetComponent<PowerContainer>();
if (battery == null || battery.Item.Condition <= 0.0f) { continue; }
if (battery.OutputDisabled) { continue; }
availableCharge += battery.Charge;
availableCapacity += battery.GetCapacity();
}

View File

@@ -19,7 +19,7 @@ namespace Barotrauma.Items.Components
get { return Vector2.Zero; }
}
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1)
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1, Color? overrideColor = null)
{
if (dockingState == 0.0f) return;
@@ -39,7 +39,8 @@ namespace Barotrauma.Items.Components
drawPos,
new Rectangle(
rect.Center.X + (int)(rect.Width / 2 * (1.0f - dockingState)), rect.Y,
(int)(rect.Width / 2 * dockingState), rect.Height), Color.White);
(int)(rect.Width / 2 * dockingState), rect.Height),
overrideColor ?? Color.White);
}
else
@@ -48,7 +49,8 @@ namespace Barotrauma.Items.Components
drawPos - Vector2.UnitX * (rect.Width / 2 * dockingState),
new Rectangle(
rect.X, rect.Y,
(int)(rect.Width / 2 * dockingState), rect.Height), Color.White);
(int)(rect.Width / 2 * dockingState), rect.Height),
overrideColor ?? Color.White);
}
}
else
@@ -61,7 +63,8 @@ namespace Barotrauma.Items.Components
drawPos - Vector2.UnitY * (rect.Height / 2 * dockingState),
new Rectangle(
rect.X, rect.Y,
rect.Width, (int)(rect.Height / 2 * dockingState)), Color.White);
rect.Width, (int)(rect.Height / 2 * dockingState)),
overrideColor ?? Color.White);
}
else
{
@@ -69,7 +72,8 @@ namespace Barotrauma.Items.Components
drawPos,
new Rectangle(
rect.X, rect.Y + rect.Height / 2 + (int)(rect.Height / 2 * (1.0f - dockingState)),
rect.Width, (int)(rect.Height / 2 * dockingState)), Color.White);
rect.Width, (int)(rect.Height / 2 * dockingState)),
overrideColor ?? Color.White);
}
}
}

View File

@@ -182,6 +182,7 @@ namespace Barotrauma
public Rectangle BackgroundFrame { get; protected set; }
private List<ushort>[] partialReceivedItemIDs;
private List<ushort>[] receivedItemIDs;
private CoroutineHandle syncItemsCoroutine;
@@ -257,7 +258,7 @@ namespace Barotrauma
else
{
LocalizedString description = item.Description;
if (item.HasTag("identitycard") || item.HasTag("despawncontainer"))
if (item.HasTag(Tags.IdCard) || item.HasTag(Tags.DespawnContainer))
{
string[] readTags = item.Tags.Split(',');
string idName = null;
@@ -347,6 +348,9 @@ namespace Barotrauma
{
toolTip += item.Prefab.GetSkillRequirementHints(character);
}
#if DEBUG
toolTip += $" ({item.Prefab.Identifier})";
#endif
return RichString.Rich(toolTip);
}
}
@@ -387,6 +391,12 @@ namespace Barotrauma
/// </summary>
public RectTransform RectTransform;
/// <summary>
/// Normally false - we don't draw the UI because it's drawn when the player hovers the cursor over the item in their inventory.
/// Enabled in special cases like equippable fabricators where the inventory is a part of the fabricator UI.
/// </summary>
public bool DrawWhenEquipped;
public static SlotReference SelectedSlot
{
get
@@ -417,6 +427,7 @@ namespace Barotrauma
padding = new Vector4(spacing.X, spacing.Y, spacing.X, spacing.X);
Vector2 slotAreaSize = new Vector2(
columns * rectSize.X + (columns - 1) * spacing.X,
rows * rectSize.Y + (rows - 1) * spacing.Y);
@@ -427,6 +438,8 @@ namespace Barotrauma
GameMain.GraphicsWidth / 2 - slotAreaSize.X / 2,
GameMain.GraphicsHeight / 2 - slotAreaSize.Y / 2);
Vector2 center = topLeft + slotAreaSize / 2;
if (RectTransform != null)
{
Vector2 scale = new Vector2(
@@ -438,6 +451,8 @@ namespace Barotrauma
padding.X *= scale.X; padding.Z *= scale.X;
padding.Y *= scale.Y; padding.W *= scale.Y;
center = RectTransform.Rect.Center.ToVector2();
topLeft = RectTransform.TopLeft.ToVector2() + new Vector2(padding.X, padding.Y);
prevRect = RectTransform.Rect;
}
@@ -445,8 +460,14 @@ namespace Barotrauma
Rectangle slotRect = new Rectangle((int)topLeft.X, (int)topLeft.Y, (int)rectSize.X, (int)rectSize.Y);
for (int i = 0; i < capacity; i++)
{
slotRect.X = (int)(topLeft.X + (rectSize.X + spacing.X) * (i % slotsPerRow));
slotRect.Y = (int)(topLeft.Y + (rectSize.Y + spacing.Y) * ((int)Math.Floor((double)i / slotsPerRow)));
int row = (int)Math.Floor((double)i / slotsPerRow);
int slotsPerThisRow = Math.Min(slotsPerRow, capacity - row * slotsPerRow);
int rowWidth = (int)(rectSize.X * slotsPerThisRow + spacing.X * (slotsPerThisRow - 1));
slotRect.X = (int)(center.X) - rowWidth / 2;
slotRect.X += (int)((rectSize.X + spacing.X) * (i % slotsPerThisRow));
slotRect.Y = (int)(topLeft.Y + (rectSize.Y + spacing.Y) * row);
visualSlots[i] = new VisualSlot(slotRect);
visualSlots[i].InteractRect = new Rectangle(
(int)(visualSlots[i].Rect.X - spacing.X / 2 - 1), (int)(visualSlots[i].Rect.Y - spacing.Y / 2 - 1),
@@ -489,7 +510,7 @@ namespace Barotrauma
return owner.SelectedCharacter != null|| (!(owner is Character character)) || !container.KeepOpenWhenEquippedBy(character) || !owner.HasEquippedItem(container.Item);
}
protected virtual bool HideSlot(int i)
public virtual bool HideSlot(int i)
{
return visualSlots[i].Disabled || (slots[i].HideIfEmpty && slots[i].Empty());
}
@@ -673,6 +694,7 @@ namespace Barotrauma
var container = item.GetComponent<ItemContainer>();
if (container == null || !container.DrawInventory) { return; }
if (container.Inventory.DrawWhenEquipped) { return; }
var subInventory = container.Inventory;
if (subInventory.visualSlots == null) { subInventory.CreateSlots(); }
@@ -871,44 +893,34 @@ namespace Barotrauma
if (Character.Controlled.Inventory != null && !isSubEditor)
{
var inv = Character.Controlled.Inventory;
for (var i = 0; i < inv.visualSlots.Length; i++)
if (IsOnInventorySlot(Character.Controlled.Inventory)) { return true; }
}
if (Character.Controlled.SelectedCharacter?.Inventory != null && !isSubEditor)
{
if (IsOnInventorySlot(Character.Controlled.SelectedCharacter.Inventory)) { return true; }
}
bool IsOnInventorySlot(Inventory inventory)
{
for (var i = 0; i < inventory.visualSlots.Length; i++)
{
var slot = inv.visualSlots[i];
if (inventory.HideSlot(i)) { continue; }
var slot = inventory.visualSlots[i];
if (slot.InteractRect.Contains(PlayerInput.MousePosition))
{
return true;
}
// check if the equip button actually exists
if (slot.EquipButtonRect.Contains(PlayerInput.MousePosition) &&
i >= 0 && inv.slots.Length > i &&
!inv.slots[i].Empty())
{
return true;
}
}
}
if (Character.Controlled.SelectedCharacter?.Inventory != null && !isSubEditor)
{
var inv = Character.Controlled.SelectedCharacter.Inventory;
for (var i = 0; i < inv.visualSlots.Length; i++)
{
var slot = inv.visualSlots[i];
if (slot.InteractRect.Contains(PlayerInput.MousePosition))
{
return true;
}
// check if the equip button actually exists
if (slot.EquipButtonRect.Contains(PlayerInput.MousePosition) &&
i >= 0 && inv.slots.Length > i &&
!inv.slots[i].Empty())
if (slot.EquipButtonRect.Contains(PlayerInput.MousePosition) &&
i >= 0 && inventory.slots.Length > i &&
!inventory.slots[i].Empty())
{
return true;
}
}
return false;
}
if (Character.Controlled.SelectedItem != null)
@@ -1034,7 +1046,7 @@ namespace Barotrauma
protected static void DrawToolTip(SpriteBatch spriteBatch, RichString toolTip, Rectangle highlightedSlot)
{
GUIComponent.DrawToolTip(spriteBatch, toolTip, highlightedSlot);
GUIComponent.DrawToolTip(spriteBatch, toolTip, highlightedSlot, Anchor.BottomRight);
}
public void DrawSubInventory(SpriteBatch spriteBatch, int slotIndex)
@@ -1046,6 +1058,7 @@ namespace Barotrauma
if (container == null || !container.DrawInventory) { return; }
if (container.Inventory.visualSlots == null || !container.Inventory.isSubInventory) { return; }
if (container.Inventory.DrawWhenEquipped) { return; }
int itemCapacity = container.Capacity;
@@ -1215,8 +1228,8 @@ namespace Barotrauma
else
{
DraggingItems.ForEachMod(it => it.Drop(Character.Controlled));
DraggingItems.First().CreateDroppedStack(DraggingItems, allowClientExecute: false);
}
SoundPlayer.PlayUISound(removed ? GUISoundType.PickItem : GUISoundType.DropItem);
}
}
@@ -1258,11 +1271,19 @@ namespace Barotrauma
{
allowCombine = false;
}
int itemCount = 0;
foreach (Item item in DraggingItems)
{
bool success = selectedInventory.TryPutItem(item, slotIndex, allowSwapping: !anySuccess, allowCombine, Character.Controlled);
anySuccess |= success;
if (!success) { break; }
if (success)
{
anySuccess = true;
itemCount++;
}
if (!success || itemCount >= item.Prefab.GetMaxStackSize(selectedInventory))
{
break;
}
}
if (anySuccess)
@@ -1360,10 +1381,12 @@ namespace Barotrauma
protected static Rectangle GetSubInventoryHoverArea(SlotReference subSlot)
{
Rectangle hoverArea;
if (!subSlot.Inventory.Movable() ||
if ((Screen.Selected != GameMain.SubEditorScreen || GameMain.SubEditorScreen.DrawCharacterInventory) &&
(!subSlot.Inventory.Movable() ||
(Character.Controlled?.Inventory == subSlot.ParentInventory && !Character.Controlled.HasEquippedItem(subSlot.Item)) ||
(subSlot.ParentInventory is CharacterInventory characterInventory && characterInventory.CurrentLayout != CharacterInventory.Layout.Default))
(subSlot.ParentInventory is CharacterInventory characterInventory && characterInventory.CurrentLayout != CharacterInventory.Layout.Default)))
{
//slot not visible as a separate, movable panel -> just use the area of the slot directly
hoverArea = subSlot.Slot.Rect;
hoverArea.Location += subSlot.Slot.DrawOffset.ToPoint();
hoverArea = Rectangle.Union(hoverArea, subSlot.Slot.EquipButtonRect);
@@ -1372,7 +1395,10 @@ namespace Barotrauma
{
hoverArea = subSlot.Inventory.BackgroundFrame;
hoverArea.Location += subSlot.Slot.DrawOffset.ToPoint();
hoverArea = Rectangle.Union(hoverArea, subSlot.Inventory.movableFrameRect);
if (subSlot.Inventory.movableFrameRect != Rectangle.Empty)
{
hoverArea = Rectangle.Union(hoverArea, subSlot.Inventory.movableFrameRect);
}
}
if (subSlot.Inventory?.visualSlots != null)
@@ -1465,18 +1491,28 @@ namespace Barotrauma
color: Character.Controlled.FocusedItem == null && !mouseOnHealthInterface ? GUIStyle.Red : Color.LightGreen,
font: GUIStyle.SmallFont);
}
Item draggedItem = DraggingItems.First();
sprite.Draw(spriteBatch, itemPos + Vector2.One * 2, Color.Black, scale: scale);
sprite.Draw(spriteBatch,
itemPos,
sprite == DraggingItems.First().Sprite ? DraggingItems.First().GetSpriteColor() : DraggingItems.First().GetInventoryIconColor(),
sprite == draggedItem.Sprite ? draggedItem.GetSpriteColor() : draggedItem.GetInventoryIconColor(),
scale: scale);
if (DraggingItems.First().Prefab.MaxStackSize > 1)
if (draggedItem.Prefab.GetMaxStackSize(null) > 1)
{
int stackAmount = DraggingItems.Count;
if (selectedSlot?.ParentInventory != null)
{
stackAmount = Math.Min(
stackAmount,
selectedSlot.ParentInventory.HowManyCanBePut(draggedItem.Prefab, selectedSlot.SlotIndex, draggedItem.Condition));
}
Vector2 stackCountPos = itemPos + Vector2.One * iconSize * 0.25f;
string stackCountText = "x" + DraggingItems.Count;
string stackCountText = "x" + stackAmount;
GUIStyle.SmallFont.DrawString(spriteBatch, stackCountText, stackCountPos + Vector2.One, Color.Black);
GUIStyle.SmallFont.DrawString(spriteBatch, stackCountText, stackCountPos, Color.White);
GUIStyle.SmallFont.DrawString(spriteBatch, stackCountText, stackCountPos, GUIStyle.TextColorBright);
}
}
}
@@ -1673,7 +1709,7 @@ namespace Barotrauma
color: GUIStyle.Red,
scale: iconSize.X / stealIcon.size.X);
}
int maxStackSize = item.Prefab.MaxStackSize;
int maxStackSize = item.Prefab.GetMaxStackSize(inventory);
if (inventory is ItemInventory itemInventory)
{
maxStackSize = Math.Min(maxStackSize, itemInventory.Container.GetMaxStackSize(slotIndex));
@@ -1691,7 +1727,7 @@ namespace Barotrauma
}
}
if (HealingCooldown.IsOnCooldown && item.HasTag(HealingCooldown.MedicalItemTag))
if (HealingCooldown.IsOnCooldown && item.HasTag(Tags.MedicalItem))
{
RectangleF cdRect = rect;
// shrink the rect from top to bottom depending on HealingCooldown.NormalizedCooldown
@@ -1794,10 +1830,15 @@ namespace Barotrauma
}
}
public void ClientEventRead(IReadMessage msg, float sendingTime)
public void ClientEventRead(IReadMessage msg)
{
UInt16 lastEventID = msg.ReadUInt16();
SharedRead(msg, out receivedItemIDs);
partialReceivedItemIDs ??= new List<ushort>[capacity];
SharedRead(msg, partialReceivedItemIDs, out bool readyToApply);
if (!readyToApply) { return; }
receivedItemIDs = partialReceivedItemIDs.ToArray();
partialReceivedItemIDs = null;
//delay applying the new state if less than 1 second has passed since this client last sent a state to the server
//prevents the inventory from briefly reverting to an old state if items are moved around in quick succession
@@ -1866,7 +1907,7 @@ namespace Barotrauma
if (!receivedItemIDs[i].Any()) { continue; }
foreach (UInt16 id in receivedItemIDs[i])
{
if (!(Entity.FindEntityByID(id) is Item item) || slots[i].Contains(item)) { continue; }
if (Entity.FindEntityByID(id) is not Item item || slots[i].Contains(item)) { continue; }
if (!TryPutItem(item, i, false, false, null, false))
{
ForceToSlot(item, i);
@@ -1883,11 +1924,5 @@ namespace Barotrauma
receivedItemIDs = null;
}
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
{
SharedWrite(msg, extraData);
syncItemsDelay = 1.0f;
}
}
}

View File

@@ -38,7 +38,7 @@ namespace Barotrauma
}
}
partial void AssignCampaignInteractionTypeProjSpecific(CampaignMode.InteractionType interactionType)
partial void AssignCampaignInteractionTypeProjSpecific(CampaignMode.InteractionType interactionType, IEnumerable<Client> targetClients)
{
if (interactionType == CampaignMode.InteractionType.None)
{
@@ -316,6 +316,11 @@ namespace Barotrauma
}
public override void Draw(SpriteBatch spriteBatch, bool editing, bool back = true)
{
Draw(spriteBatch, editing, back, overrideColor: null);
}
public void Draw(SpriteBatch spriteBatch, bool editing, bool back = true, Color? overrideColor = null)
{
if (!Visible || (!editing && HiddenInGame) || !SubEditorScreen.IsLayerVisible(this)) { return; }
@@ -328,7 +333,9 @@ namespace Barotrauma
else if (!ShowItems) { return; }
}
Color color = IsIncludedInSelection && editing ? GUIStyle.Blue : GetSpriteColor(withHighlight: true);
Color color =
overrideColor ??
(IsIncludedInSelection && editing ? GUIStyle.Blue : GetSpriteColor(withHighlight: true));
bool isWiringMode = editing && SubEditorScreen.TransparentWiringMode && SubEditorScreen.IsWiringMode() && !isWire && parentInventory == null;
bool renderTransparent = isWiringMode && GetComponent<ConnectionPanel>() == null;
@@ -412,15 +419,7 @@ namespace Barotrauma
}
else
{
Vector2 origin = activeSprite.Origin;
if ((activeSprite.effects & SpriteEffects.FlipHorizontally) == SpriteEffects.FlipHorizontally)
{
origin.X = activeSprite.SourceRect.Width - origin.X;
}
if ((activeSprite.effects & SpriteEffects.FlipVertically) == SpriteEffects.FlipVertically)
{
origin.Y = activeSprite.SourceRect.Height - origin.Y;
}
Vector2 origin = GetSpriteOrigin(activeSprite);
if (color.A > 0)
{
activeSprite.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y) + drawOffset, color, origin, RotationRad, Scale, activeSprite.effects, depth);
@@ -484,7 +483,8 @@ namespace Barotrauma
}
}
}
body.Draw(spriteBatch, activeSprite, color, depth, Scale);
Vector2 origin = GetSpriteOrigin(activeSprite);
body.Draw(spriteBatch, activeSprite, color, depth, Scale, origin: origin);
if (fadeInBrokenSprite != null)
{
float d = Math.Min(depth + (fadeInBrokenSprite.Sprite.Depth - activeSprite.Depth - 0.000001f), 0.999f);
@@ -497,12 +497,12 @@ namespace Barotrauma
Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier, -RotationRad) * Scale;
if (flippedX && Prefab.CanSpriteFlipX) { offset.X = -offset.X; }
if (flippedY && Prefab.CanSpriteFlipY) { offset.Y = -offset.Y; }
var ca = (float)Math.Cos(-body.Rotation);
var sa = (float)Math.Sin(-body.Rotation);
var ca = MathF.Cos(-body.DrawRotation);
var sa = MathF.Sin(-body.DrawRotation);
Vector2 transformedOffset = new Vector2(ca * offset.X + sa * offset.Y, -sa * offset.X + ca * offset.Y);
decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X + transformedOffset.X, -(DrawPosition.Y + transformedOffset.Y)), color,
-body.Rotation + rotation, decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale, activeSprite.effects,
decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(body.DrawPosition.X + transformedOffset.X, -(body.DrawPosition.Y + transformedOffset.Y)), color,
-body.DrawRotation + rotation, decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale, activeSprite.effects,
depth: depth + (decorativeSprite.Sprite.Depth - activeSprite.Depth));
}
}
@@ -536,7 +536,7 @@ namespace Barotrauma
//causing them to be removed from the list
for (int i = drawableComponents.Count - 1; i >= 0; i--)
{
drawableComponents[i].Draw(spriteBatch, editing, depth);
drawableComponents[i].Draw(spriteBatch, editing, depth, overrideColor);
}
if (GameMain.DebugDraw)
@@ -604,6 +604,20 @@ namespace Barotrauma
GUI.DrawLine(spriteBatch, from, to, lineColor, width: 1);
//GUI.DrawString(spriteBatch, from, $"Linked to {e.Name}", lineColor, Color.Black * 0.5f);
}
Vector2 GetSpriteOrigin(Sprite sprite)
{
Vector2 origin = sprite.Origin;
if ((sprite.effects & SpriteEffects.FlipHorizontally) == SpriteEffects.FlipHorizontally)
{
origin.X = sprite.SourceRect.Width - origin.X;
}
if ((sprite.effects & SpriteEffects.FlipVertically) == SpriteEffects.FlipVertically)
{
origin.Y = sprite.SourceRect.Height - origin.Y;
}
return origin;
}
}
partial void OnCollisionProjSpecific(float impact)
@@ -1076,7 +1090,7 @@ namespace Barotrauma
if (!ignoreLocking && ic.LockGuiFramePosition) { continue; }
//if the frame covers nearly all of the screen, don't trying to prevent overlaps because it'd fail anyway
if (ic.GuiFrame.Rect.Width >= GameMain.GraphicsWidth * 0.9f && ic.GuiFrame.Rect.Height >= GameMain.GraphicsHeight * 0.9f) { continue; }
ic.GuiFrame.RectTransform.ScreenSpaceOffset = Point.Zero;
ic.GuiFrame.RectTransform.ScreenSpaceOffset = ic.GuiFrameOffset;
elementsToMove.Add(ic.GuiFrame);
debugInitialHudPositions.Add(ic.GuiFrame.Rect);
}
@@ -1281,6 +1295,11 @@ namespace Barotrauma
nameText += $" ({idName})";
}
}
if (DroppedStack.Any())
{
nameText += $" x{DroppedStack.Count()}";
}
texts.Add(new ColoredText(nameText, GUIStyle.TextColorNormal, false, false));
if (CampaignMode.BlocksInteraction(CampaignInteractionType))
@@ -1350,10 +1369,16 @@ namespace Barotrauma
}
}
if (Character.Controlled != null && Character.Controlled.SelectedItem != this && GetComponent<RemoteController>() == null)
var character = Character.Controlled;
var selectedItem = Character.Controlled?.SelectedItem;
if (character != null && selectedItem != this && GetComponent<RemoteController>() == null)
{
if (Character.Controlled.SelectedItem?.GetComponent<RemoteController>()?.TargetItem != this &&
!Character.Controlled.HeldItems.Any(it => it.GetComponent<RemoteController>()?.TargetItem == this))
bool insideCircuitBox =
selectedItem?.GetComponent<CircuitBox>() != null &&
selectedItem.ContainedItems.Contains(this);
if (!insideCircuitBox &&
selectedItem?.GetComponent<RemoteController>()?.TargetItem != this &&
!character.HeldItems.Any(it => it.GetComponent<RemoteController>()?.TargetItem == this))
{
return;
}
@@ -1407,7 +1432,7 @@ namespace Barotrauma
int containerIndex = msg.ReadRangedInteger(0, components.Count - 1);
if (components[containerIndex] is ItemContainer container)
{
container.Inventory.ClientEventRead(msg, sendingTime);
container.Inventory.ClientEventRead(msg);
}
else
{
@@ -1421,7 +1446,12 @@ namespace Barotrauma
SetCondition(newCondition, isNetworkEvent: true, executeEffects: !loadingRound);
break;
case EventType.AssignCampaignInteraction:
CampaignInteractionType = (CampaignMode.InteractionType)msg.ReadByte();
bool isVisible = msg.ReadBoolean();
if (isVisible)
{
var interactionType = (CampaignMode.InteractionType)msg.ReadByte();
AssignCampaignInteractionType(interactionType);
}
break;
case EventType.ApplyStatusEffect:
{
@@ -1486,6 +1516,30 @@ namespace Barotrauma
AddUpgrade(upgrade, false);
}
break;
case EventType.DroppedStack:
int itemCount = msg.ReadRangedInteger(0, Inventory.MaxPossibleStackSize);
if (itemCount > 0)
{
List<Item> droppedStack = new List<Item>();
for (int i = 0; i < itemCount; i++)
{
var id = msg.ReadUInt16();
if (FindEntityByID(id) is not Item droppedItem)
{
DebugConsole.ThrowError($"Error while reading {EventType.DroppedStack} message: could not find an item with the ID {id}.");
}
else
{
droppedStack.Add(droppedItem);
}
}
CreateDroppedStack(droppedStack, allowClientExecute: true);
}
else
{
RemoveFromDroppedStack(allowClientExecute: true);
}
break;
default:
throw new Exception($"Malformed incoming item event: unsupported event type {eventType}");
}
@@ -1511,7 +1565,7 @@ namespace Barotrauma
{
var component = componentStateEventData.Component;
if (component is null) { throw error("component was null"); }
if (!(component is IClientSerializable clientSerializable)) { throw error($"component was not {nameof(IClientSerializable)}"); }
if (component is not IClientSerializable clientSerializable) { throw error($"component was not {nameof(IClientSerializable)}"); }
int componentIndex = components.IndexOf(component);
if (componentIndex < 0) { throw error("component did not belong to item"); }
msg.WriteRangedInteger(componentIndex, 0, components.Count - 1);
@@ -1525,7 +1579,7 @@ namespace Barotrauma
int containerIndex = components.IndexOf(container);
if (containerIndex < 0) { throw error("container did not belong to item"); }
msg.WriteRangedInteger(containerIndex, 0, components.Count - 1);
container.Inventory.ClientEventWrite(msg, extraData);
container.Inventory.ClientEventWrite(msg, inventoryStateEventData);
}
break;
case TreatmentEventData treatmentEventData:
@@ -1551,7 +1605,7 @@ namespace Barotrauma
{
if (GameMain.Client == null) { return; }
if (parentInventory != null || body == null || !body.Enabled || Removed || (GetComponent<Projectile>()?.IsStuckToTarget ?? false))
if (parentInventory != null || body == null || !body.Enabled || Removed || (GetComponent<Projectile>() is { IsStuckToTarget: true }))
{
positionBuffer.Clear();
return;
@@ -1567,12 +1621,20 @@ namespace Barotrauma
body.CorrectPosition(positionBuffer, out Vector2 newPosition, out Vector2 newVelocity, out float newRotation, out float newAngularVelocity);
body.LinearVelocity = newVelocity;
body.AngularVelocity = newAngularVelocity;
if (Vector2.DistanceSquared(newPosition, body.SimPosition) > 0.0001f ||
float distSqr = Vector2.DistanceSquared(newPosition, body.SimPosition);
if (distSqr > 0.0001f ||
Math.Abs(newRotation - body.Rotation) > 0.01f)
{
body.TargetPosition = newPosition;
body.TargetRotation = newRotation;
body.MoveToTargetPosition(lerp: true);
if (distSqr > 10.0f * 10.0f)
{
//very large change in position, we need to recheck which submarine the item is in
Submarine = null;
UpdateTransform();
}
}
Vector2 displayPos = ConvertUnits.ToDisplayUnits(body.SimPosition);
@@ -1594,8 +1656,12 @@ namespace Barotrauma
return;
}
var posInfo = body.ClientRead(msg, sendingTime, parentDebugName: Name);
msg.ReadPadBits();
if (GetComponent<Projectile>() is { IsStuckToTarget: true }) { return; }
if (posInfo != null)
{
int index = 0;

Some files were not shown because too many files have changed in this diff Show More