Unstable 1.1.14.0

This commit is contained in:
Markus Isberg
2023-10-02 16:43:54 +03:00
parent 94f5a93a0c
commit cf8f0de659
606 changed files with 21906 additions and 11456 deletions
@@ -255,7 +255,7 @@ namespace Barotrauma
/// </summary> /// </summary>
public bool Freeze { get; set; } 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; prevPosition = position;
prevZoom = zoom; prevZoom = zoom;
@@ -268,7 +268,7 @@ namespace Barotrauma
Vector2 moveInput = Vector2.Zero; Vector2 moveInput = Vector2.Zero;
if (allowMove && !Freeze) 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.LeftShift)) { moveSpeed *= 2.0f; }
if (PlayerInput.KeyDown(Keys.LeftControl)) { moveSpeed *= 0.5f; } if (PlayerInput.KeyDown(Keys.LeftControl)) { moveSpeed *= 0.5f; }
@@ -50,9 +50,9 @@ namespace Barotrauma
} }
else if (Entity is Item i) 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; return;
} }
color = Color.CadetBlue; color = Color.CadetBlue;
@@ -82,15 +82,20 @@ namespace Barotrauma
{ {
var previousNode = path.Nodes[i - 1]; var previousNode = path.Nodes[i - 1];
var currentNode = path.Nodes[i]; var currentNode = path.Nodes[i];
bool isPathActive = !path.Finished && !path.IsAtEndNode;
Color pathColor = isPathActive ? Color.Blue * 0.5f : Color.Gray;
GUI.DrawLine(spriteBatch, GUI.DrawLine(spriteBatch,
new Vector2(currentNode.DrawPosition.X, -currentNode.DrawPosition.Y), new Vector2(currentNode.DrawPosition.X, -currentNode.DrawPosition.Y),
new Vector2(previousNode.DrawPosition.X, -previousNode.DrawPosition.Y), new Vector2(previousNode.DrawPosition.X, -previousNode.DrawPosition.Y),
Color.Blue * 0.5f, 0, 3); pathColor, 0, 3);
GUIStyle.SmallFont.DrawString(spriteBatch, if (isPathActive)
currentNode.ID.ToString(), {
new Vector2(currentNode.DrawPosition.X - 10, -currentNode.DrawPosition.Y - 30), GUIStyle.SmallFont.DrawString(spriteBatch,
Color.Blue); currentNode.ID.ToString(),
new Vector2(currentNode.DrawPosition.X - 10, -currentNode.DrawPosition.Y - 30),
Color.Blue);
}
} }
if (path.CurrentNode != null) if (path.CurrentNode != null)
{ {
@@ -469,6 +469,7 @@ namespace Barotrauma
float gibParticleAmount = MathHelper.Clamp(limb.Mass / character.AnimController.Mass, 0.1f, 1.0f); float gibParticleAmount = MathHelper.Clamp(limb.Mass / character.AnimController.Mass, 0.1f, 1.0f);
foreach (ParticleEmitter emitter in character.GibEmitters) 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.Air) { continue; }
if (!inWater && emitter.Prefab.ParticlePrefab.DrawTarget == ParticlePrefab.DrawTargetType.Water) { continue; } if (!inWater && emitter.Prefab.ParticlePrefab.DrawTarget == ParticlePrefab.DrawTargetType.Water) { continue; }
@@ -23,8 +23,6 @@ namespace Barotrauma
protected float hudInfoTimer = 1.0f; protected float hudInfoTimer = 1.0f;
protected bool hudInfoVisible = false; protected bool hudInfoVisible = false;
private float pressureParticleTimer;
private float findFocusedTimer; private float findFocusedTimer;
protected float lastRecvPositionUpdateTime; protected float lastRecvPositionUpdateTime;
@@ -299,7 +297,9 @@ namespace Barotrauma
keys[i].SetState(); 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.Use);
ResetInputIfPrimaryMouse(InputType.Shoot); ResetInputIfPrimaryMouse(InputType.Shoot);
@@ -340,13 +340,6 @@ namespace Barotrauma
cam.Zoom = MathHelper.Lerp(cam.Zoom, cam.Zoom = MathHelper.Lerp(cam.Zoom,
cam.DefaultZoom + (Math.Max(pressure, 10) / 150.0f) * Rand.Range(0.9f, 1.1f), cam.DefaultZoom + (Math.Max(pressure, 10) / 150.0f) * Rand.Range(0.9f, 1.1f),
zoomInEffectStrength); 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; break;
default: default:
var petBehavior = enemyAI.PetBehavior; 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); PlaySound(CharacterSound.SoundType.Unhappy);
} }
@@ -859,21 +853,11 @@ namespace Barotrauma
if (Controlled.AnimController.Stairs != null) 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; yPos = Controlled.AnimController.Stairs.SimPosition.Y - Controlled.AnimController.Stairs.RectHeight * 0.5f;
} }
foreach (var ladder in Ladder.List) //don't show the HUD texts if the character is below the floor of the room the controlled character is in
{
if (CanInteractWith(ladder.Item) && Controlled.CanInteractWith(ladder.Item))
{
float xPos = ladder.Item.SimPosition.X;
if (Math.Abs(xPos - SimPosition.X) < 3.0)
{
yPos = ladder.Item.SimPosition.Y - ladder.Item.RectHeight * 0.5f;
}
break;
}
}
if (AnimController.FloorY < yPos) { return; } if (AnimController.FloorY < yPos) { return; }
} }
@@ -99,7 +99,7 @@ namespace Barotrauma
public override bool Interrupted => Character.Removed || !Character.Enabled; public override bool Interrupted => Character.Removed || !Character.Enabled;
public override Color Color => 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; GUIStyle.HealthBarColorPoisoned : GUIStyle.Red;
public override string NumberToDisplay => string.Empty; public override string NumberToDisplay => string.Empty;
@@ -19,7 +19,6 @@ namespace Barotrauma
public bool LastControlled; public bool LastControlled;
public int CrewListIndex { get; set; } = -1; public int CrewListIndex { get; set; } = -1;
#warning TODO: Refactor
private Sprite disguisedPortrait; private Sprite disguisedPortrait;
private List<WearableSprite> disguisedAttachmentSprites; private List<WearableSprite> disguisedAttachmentSprites;
private Vector2? disguisedSheetIndex; private Vector2? disguisedSheetIndex;
@@ -609,7 +608,6 @@ namespace Barotrauma
CharacterInfo = info; CharacterInfo = info;
parentComponent = parent; parentComponent = parent;
HasIcon = hasIcon; HasIcon = hasIcon;
RecreateFrameContents(); RecreateFrameContents();
} }
@@ -848,6 +846,12 @@ namespace Barotrauma
var info = CharacterInfo; 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; float characterHeightWidthRatio = info.HeadSprite.size.Y / info.HeadSprite.size.X;
HeadSelectionList ??= new GUIListBox( HeadSelectionList ??= new GUIListBox(
new RectTransform( new RectTransform(
@@ -885,8 +889,13 @@ namespace Barotrauma
GUILayoutGroup row = null; GUILayoutGroup row = null;
int itemsInRow = 0; 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)); 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"); ContentXElement headSpriteElement = headElement.GetChildElement("sprite");
ContentPath spritePathWithTags = headSpriteElement.GetAttributeContentPath("texture"); ContentPath spritePathWithTags = headSpriteElement.GetAttributeContentPath("texture");
@@ -963,7 +972,7 @@ namespace Barotrauma
private bool SwitchAttachment(GUIScrollBar scrollBar, WearableType type) private bool SwitchAttachment(GUIScrollBar scrollBar, WearableType type)
{ {
var info = CharacterInfo; var info = CharacterInfo;
int index = (int)scrollBar.BarScrollValue; int index = (int)Math.Round(scrollBar.BarScrollValue);
switch (type) switch (type)
{ {
case WearableType.Beard: case WearableType.Beard:
@@ -201,9 +201,9 @@ namespace Barotrauma
keys[(int)InputType.Use].Held = useInput; keys[(int)InputType.Use].Held = useInput;
keys[(int)InputType.Use].SetState(false, useInput); keys[(int)InputType.Use].SetState(false, useInput);
bool crouching = msg.ReadBoolean();
if (AnimController is HumanoidAnimController) if (AnimController is HumanoidAnimController)
{ {
bool crouching = msg.ReadBoolean();
keys[(int)InputType.Crouch].Held = crouching; keys[(int)InputType.Crouch].Held = crouching;
keys[(int)InputType.Crouch].SetState(false, crouching); keys[(int)InputType.Crouch].SetState(false, crouching);
} }
@@ -269,7 +269,34 @@ namespace Barotrauma
if (readStatus) if (readStatus)
{ {
ReadStatus(msg); ReadStatus(msg);
AIController?.ClientRead(msg); bool isEnemyAi = msg.ReadBoolean();
if (isEnemyAi)
{
byte aiState = msg.ReadByte();
if (AIController is EnemyAIController enemyAi)
{
enemyAi.State = (AIState)aiState;
}
else
{
DebugConsole.AddWarning($"Received enemy AI data for a character with no {nameof(EnemyAIController)}. Ignoring...");
}
bool isPet = msg.ReadBoolean();
if (isPet)
{
byte happiness = msg.ReadByte();
byte hunger = msg.ReadByte();
if ((AIController as EnemyAIController)?.PetBehavior is PetBehavior petBehavior)
{
petBehavior.Happiness = (float)happiness / byte.MaxValue * petBehavior.MaxHappiness;
petBehavior.Hunger = (float)hunger / byte.MaxValue * petBehavior.MaxHunger;
}
else
{
DebugConsole.AddWarning($"Received pet AI data for a character with no {nameof(PetBehavior)}. Ignoring...");
}
}
}
} }
msg.ReadPadBits(); msg.ReadPadBits();
@@ -323,7 +350,7 @@ namespace Barotrauma
} }
else else
{ {
Inventory.ClientEventRead(msg, sendingTime); Inventory.ClientEventRead(msg);
} }
break; break;
case EventType.Control: case EventType.Control:
@@ -379,7 +406,7 @@ namespace Barotrauma
float targetY = msg.ReadSingle(); float targetY = msg.ReadSingle();
Vector2 targetSimPos = new Vector2(targetX, targetY); Vector2 targetSimPos = new Vector2(targetX, targetY);
//255 = entity already removed, no need to do anything //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) if (attackLimbIndex >= AnimController.Limbs.Length)
{ {
//it's possible to get these errors when mid-round syncing, as the client may not //it's possible to get these errors when mid-round syncing, as the client may not
@@ -639,7 +666,7 @@ namespace Barotrauma
} }
else 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 else
@@ -257,7 +257,8 @@ namespace Barotrauma
{ {
for (int i = 0; i < character.Inventory.Capacity; i++) 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 //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(); 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; inventoryScale = Inventory.UIScale;
uiScale = GUI.Scale; 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.AbsoluteOffset = HUDLayoutSettings.HealthBarArea.Location;
healthBarHolder.RectTransform.NonScaledSize = HUDLayoutSettings.HealthBarArea.Size; healthBarHolder.RectTransform.NonScaledSize = HUDLayoutSettings.HealthBarArea.Size;
healthBarHolder.RectTransform.RelativeOffset = Vector2.Zero; healthBarHolder.RectTransform.RelativeOffset = Vector2.Zero;
@@ -496,6 +508,8 @@ namespace Barotrauma
} }
healthWindow.RectTransform.RecalculateChildren(false); healthWindow.RectTransform.RecalculateChildren(false);
Character.Inventory?.RefreshSlotPositions();
} }
public void UpdateClientSpecific(float deltaTime) public void UpdateClientSpecific(float deltaTime)
@@ -579,7 +593,7 @@ namespace Barotrauma
bool inWater = Character.AnimController.InWater; bool inWater = Character.AnimController.InWater;
var drawTarget = inWater ? Particles.ParticlePrefab.DrawTargetType.Water : Particles.ParticlePrefab.DrawTargetType.Air; 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 particleMinScale = emitter?.Prefab.Properties.ScaleMin ?? 0.5f;
float particleMaxScale = emitter?.Prefab.Properties.ScaleMax ?? 1; float particleMaxScale = emitter?.Prefab.Properties.ScaleMax ?? 1;
float severity = Math.Min(affliction.Strength / affliction.Prefab.MaxStrength * Character.Params.BleedParticleMultiplier, 1); float severity = Math.Min(affliction.Strength / affliction.Prefab.MaxStrength * Character.Params.BleedParticleMultiplier, 1);
@@ -2129,7 +2143,7 @@ namespace Barotrauma
{ {
var affliction = kvp.Key; var affliction = kvp.Key;
float burnStrength = affliction.Strength / Math.Min(affliction.Prefab.MaxStrength, 100) * affliction.Prefab.BurnOverlayAlpha; 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.BurnOverlayStrength += burnStrength;
limb.DamageOverlayStrength += affliction.Strength / Math.Min(affliction.Prefab.MaxStrength, 100) * affliction.Prefab.DamageOverlayAlpha; limb.DamageOverlayStrength += affliction.Strength / Math.Min(affliction.Prefab.MaxStrength, 100) * affliction.Prefab.DamageOverlayAlpha;
@@ -12,8 +12,6 @@ namespace Barotrauma
private static DateTimeOffset OnCooldownUntil = DateTimeOffset.MinValue; private static DateTimeOffset OnCooldownUntil = DateTimeOffset.MinValue;
private const float CooldownDuration = 0.5f; private const float CooldownDuration = 0.5f;
public static readonly Identifier MedicalItemTag = new Identifier("medical");
public static void PutOnCooldown() public static void PutOnCooldown()
{ {
OnCooldownUntil = DateTimeOffset.UtcNow.AddSeconds(CooldownDuration); OnCooldownUntil = DateTimeOffset.UtcNow.AddSeconds(CooldownDuration);
@@ -432,20 +432,20 @@ namespace Barotrauma
{ {
if (Sprite != null) if (Sprite != null)
{ {
Sprite.Remove();
var source = Sprite.SourceElement; var source = Sprite.SourceElement;
Sprite.Remove();
Sprite = new Sprite(source, file: GetSpritePath(source, Params.normalSpriteParams, ref _texturePath)); Sprite = new Sprite(source, file: GetSpritePath(source, Params.normalSpriteParams, ref _texturePath));
} }
if (_deformSprite != null) if (_deformSprite != null)
{ {
_deformSprite.Remove();
var source = _deformSprite.Sprite.SourceElement; var source = _deformSprite.Sprite.SourceElement;
_deformSprite.Remove();
_deformSprite = new DeformableSprite(source, filePath: GetSpritePath(source, Params.deformSpriteParams, ref _texturePath)); _deformSprite = new DeformableSprite(source, filePath: GetSpritePath(source, Params.deformSpriteParams, ref _texturePath));
} }
if (DamagedSprite != null) if (DamagedSprite != null)
{ {
DamagedSprite.Remove();
var source = DamagedSprite.SourceElement; var source = DamagedSprite.SourceElement;
DamagedSprite.Remove();
DamagedSprite = new Sprite(source, file: GetSpritePath(source, Params.damagedSpriteParams, ref _damagedTexturePath)); DamagedSprite = new Sprite(source, file: GetSpritePath(source, Params.damagedSpriteParams, ref _damagedTexturePath));
} }
for (int i = 0; i < ConditionalSprites.Count; i++) for (int i = 0; i < ConditionalSprites.Count; i++)
@@ -458,8 +458,8 @@ namespace Barotrauma
for (int i = 0; i < DecorativeSprites.Count; i++) for (int i = 0; i < DecorativeSprites.Count; i++)
{ {
var decorativeSprite = DecorativeSprites[i]; var decorativeSprite = DecorativeSprites[i];
decorativeSprite.Remove();
var source = decorativeSprite.Sprite.SourceElement; var source = decorativeSprite.Sprite.SourceElement;
decorativeSprite.Remove();
DecorativeSprites[i] = new DecorativeSprite(source, file: GetSpritePath(source, Params.decorativeSpriteParams[i], ref _texturePath)); DecorativeSprites[i] = new DecorativeSprite(source, file: GetSpritePath(source, Params.decorativeSpriteParams[i], ref _texturePath));
} }
} }
@@ -478,9 +478,18 @@ namespace Barotrauma
{ {
if (spriteParams != null) if (spriteParams != null)
{ {
ContentPath texturePath = //1. check if the variant file redefines the texture
character.Params.VariantFile?.Root?.GetAttributeContentPath("texture", character.Prefab.ContentPackage) ContentPath texturePath = character.Params.VariantFile?.Root?.GetAttributeContentPath("texture", character.Prefab.ContentPackage);
?? ContentPath.FromRaw(spriteParams.Element.ContentPackage ?? character.Prefab.ContentPackage, spriteParams.GetTexturePath()); //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); path = GetSpritePath(texturePath);
} }
else else
@@ -592,6 +601,7 @@ namespace Barotrauma
{ {
foreach (ParticleEmitter emitter in character.DamageEmitters) 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.Air) { continue; }
if (!InWater && emitter.Prefab.ParticlePrefab.DrawTarget == ParticlePrefab.DrawTargetType.Water) { continue; } if (!InWater && emitter.Prefab.ParticlePrefab.DrawTarget == ParticlePrefab.DrawTargetType.Water) { continue; }
ParticlePrefab overrideParticle = null; ParticlePrefab overrideParticle = null;
@@ -614,6 +624,7 @@ namespace Barotrauma
foreach (ParticleEmitter emitter in character.BloodEmitters) 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.Air) { continue; }
if (!InWater && emitter.Prefab.ParticlePrefab.DrawTarget == ParticlePrefab.DrawTargetType.Water) { continue; } if (!InWater && emitter.Prefab.ParticlePrefab.DrawTarget == ParticlePrefab.DrawTargetType.Water) { continue; }
emitter.Emit(1.0f, WorldPosition, character.CurrentHull, sizeMultiplier: bloodParticleSize, amountMultiplier: bloodParticleAmount); 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 || bool hideLimb = Hide ||
OtherWearables.Any(w => w.HideLimb) || OtherWearables.Any(w => w.HideLimb) ||
@@ -890,6 +901,28 @@ namespace Barotrauma
foreach (WearableSprite wearable in WearingItems) foreach (WearableSprite wearable in WearingItems)
{ {
if (onlyDrawable != null && onlyDrawable != wearable && wearable.CanBeHiddenByOtherWearables) { continue; } 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); 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 //if there are multiple sprites on this limb, make the successive ones be drawn in front
depthStep += step; depthStep += step;
@@ -1260,12 +1293,11 @@ namespace Barotrauma
private void DrawWearable(WearableSprite wearable, float depthStep, SpriteBatch spriteBatch, Color color, float alpha, SpriteEffects spriteEffect) private void DrawWearable(WearableSprite wearable, float depthStep, SpriteBatch spriteBatch, Color color, float alpha, SpriteEffects spriteEffect)
{ {
var (finalColor, origin, rotation, scale, depth) if (wearable.Sprite?.Texture == null) { return; }
= CalculateDrawParameters(wearable, depthStep, color, alpha); var (finalColor, origin, rotation, scale, depth) = CalculateDrawParameters(wearable, depthStep, color, alpha);
var prevEffect = spriteBatch.GetCurrentEffect(); var prevEffect = spriteBatch.GetCurrentEffect();
var alphaClipper = WearingItems.Find(w => w.AlphaClipOtherWearables); var alphaClipper = WearingItems.Find(w => w.AlphaClipOtherWearables);
bool shouldApplyAlphaClip = alphaClipper != null && wearable != alphaClipper; bool shouldApplyAlphaClip = alphaClipper?.Sprite?.Texture != null && wearable != alphaClipper;
if (shouldApplyAlphaClip) if (shouldApplyAlphaClip)
{ {
ApplyAlphaClip(spriteBatch, wearable, alphaClipper, spriteEffect); ApplyAlphaClip(spriteBatch, wearable, alphaClipper, spriteEffect);
@@ -1308,13 +1340,13 @@ namespace Barotrauma
OtherWearables.ForEach(w => w.Sprite.Remove()); OtherWearables.ForEach(w => w.Sprite.Remove());
OtherWearables.Clear(); OtherWearables.Clear();
HuskSprite?.Sprite.Remove(); HuskSprite?.Sprite?.Remove();
HuskSprite = null; HuskSprite = null;
HairWithHatSprite?.Sprite.Remove(); HairWithHatSprite?.Sprite?.Remove();
HairWithHatSprite = null; HairWithHatSprite = null;
HerpesSprite?.Sprite.Remove(); HerpesSprite?.Sprite?.Remove();
HerpesSprite = null; HerpesSprite = null;
TintMask?.Remove(); TintMask?.Remove();
@@ -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);
}
}
}
@@ -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);
}
}
}
@@ -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);
}
}
}
@@ -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;
}
}
}
}
@@ -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;
}
}
}
@@ -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();
}
}
}
@@ -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);
}
}
@@ -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);
}
}
}
}
@@ -315,7 +315,7 @@ namespace Barotrauma
else else
{ {
var textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), listBox.Content.RectTransform), 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, CanBeFocused = false,
TextColor = msg.Color TextColor = msg.Color
@@ -403,7 +403,7 @@ namespace Barotrauma
{ {
if (Screen.Selected != GameMain.SubEditorScreen) return; 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", ShowQuestionPrompt("This submarine already has hulls and/or gaps. This command will delete them. Do you want to continue? Y/N",
(option) => (option) =>
@@ -974,7 +974,7 @@ namespace Barotrauma
if (Screen.Selected == GameMain.SubEditorScreen) if (Screen.Selected == GameMain.SubEditorScreen)
{ {
bool entityFound = false; bool entityFound = false;
foreach (MapEntity entity in MapEntity.mapEntityList) foreach (MapEntity entity in MapEntity.MapEntityList)
{ {
if (entity is Item item) if (entity is Item item)
{ {
@@ -1096,19 +1096,19 @@ namespace Barotrauma
commands.Add(new Command("cleansub", "", (string[] args) => 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) if (me.SimPosition.Length() > 2000.0f)
{ {
NewMessage("Removed " + me.Name + " (simposition " + me.SimPosition + ")", Color.Orange); NewMessage("Removed " + me.Name + " (simposition " + me.SimPosition + ")", Color.Orange);
MapEntity.mapEntityList.RemoveAt(i); MapEntity.MapEntityList.RemoveAt(i);
} }
else if (!me.ShouldBeSaved) else if (!me.ShouldBeSaved)
{ {
NewMessage("Removed " + me.Name + " (!ShouldBeSaved)", Color.Orange); NewMessage("Removed " + me.Name + " (!ShouldBeSaved)", Color.Orange);
MapEntity.mapEntityList.RemoveAt(i); MapEntity.MapEntityList.RemoveAt(i);
} }
else if (me is Item) else if (me is Item)
{ {
@@ -1477,14 +1477,19 @@ namespace Barotrauma
string itemNameOrId = args[0].ToLowerInvariant(); string itemNameOrId = args[0].ToLowerInvariant();
ItemPrefab itemPrefab = ItemPrefab itemPrefab =
(MapEntityPrefab.Find(itemNameOrId, identifier: null, showErrorMessages: false) ?? (MapEntityPrefab.FindByName(itemNameOrId) ??
MapEntityPrefab.Find(null, identifier: itemNameOrId.ToIdentifier(), showErrorMessages: false)) as ItemPrefab; MapEntityPrefab.FindByIdentifier(itemNameOrId.ToIdentifier())) as ItemPrefab;
if (itemPrefab == null) if (itemPrefab == null)
{ {
NewMessage("Item not found for analyzing."); NewMessage("Item not found for analyzing.");
return; 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); NewMessage("Analyzing item " + itemPrefab.Name + " with base cost " + itemPrefab.DefaultPrice.Price);
var fabricationRecipe = fabricableItems.Find(f => f.TargetItem == itemPrefab); 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)))) foreach (Type itemComponentType in typeof(ItemComponent).Assembly.GetTypes().Where(type => type.IsSubclassOf(typeof(ItemComponent))))
{ {
checkSerializableEntityType(itemComponentType); checkSerializableEntityType(itemComponentType);
@@ -2346,7 +2357,20 @@ namespace Barotrauma
WaterRenderer.BlurAmount = blurAmount; 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) => 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? //TODO: maybe do this automatically during loading when possible?
@@ -2528,8 +2552,11 @@ namespace Barotrauma
HashSet<XDocument> docs = new HashSet<XDocument>(); HashSet<XDocument> docs = new HashSet<XDocument>();
HashSet<string> textIds = new HashSet<string>(); HashSet<string> textIds = new HashSet<string>();
Dictionary<string, string> existingTexts = new Dictionary<string, string>();
foreach (EventPrefab eventPrefab in EventSet.GetAllEventPrefabs()) foreach (EventPrefab eventPrefab in EventSet.GetAllEventPrefabs())
{ {
if (eventPrefab is not TraitorEventPrefab) { continue; }
if (eventPrefab.Identifier.IsEmpty) if (eventPrefab.Identifier.IsEmpty)
{ {
continue; continue;
@@ -2566,35 +2593,74 @@ namespace Barotrauma
void getTextsFromElement(XElement element, List<string> list, string parentName) void getTextsFromElement(XElement element, List<string> list, string parentName)
{ {
string text = element.GetAttributeString("text", null); string text = element.GetAttributeString("text", null);
string textId = $"EventText.{parentName}"; string textAttribute = "text";
if (!string.IsNullOrEmpty(text) && !text.Contains("EventText.", StringComparison.OrdinalIgnoreCase)) XElement textElement = element;
{ if (text == null)
list.Add($"<{textId}>{text}</{textId}>"); {
element.SetAttributeValue("text", textId); 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()) foreach (var subElement in element.Elements())
{ {
string elementName = parentName;
bool ignore = false;
switch (subElement.Name.ToString().ToLowerInvariant()) switch (subElement.Name.ToString().ToLowerInvariant())
{ {
case "conversationaction": case "conversationaction":
while (textIds.Contains(parentName+".c"+i)) 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; break;
case "option": 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; break;
} }
textIds.Add(parentName); if (ignore) { continue; }
getTextsFromElement(subElement, list, parentName); getTextsFromElement(subElement, list, elementName);
} }
} }
})); }));
@@ -2759,9 +2825,9 @@ namespace Barotrauma
ep.DebugCreateInstance(); 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) if (entity != null)
{ {
List<(object obj, SerializableProperty property)> allProperties = new List<(object obj, SerializableProperty property)>(); List<(object obj, SerializableProperty property)> allProperties = new List<(object obj, SerializableProperty property)>();
@@ -42,15 +42,15 @@ namespace Barotrauma
partial void ShowDialog(Character speaker, Character targetCharacter) 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); 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) ConversationAction actionInstance = null, UInt16? actionId = null, bool fadeToBlack = false, DialogTypes dialogType = DialogTypes.Regular, bool continueConversation = false)
{ {
Debug.Assert(actionInstance == null || actionId == null); Debug.Assert(actionInstance == null || actionId == null);
@@ -126,6 +126,7 @@ namespace Barotrauma
if (actionInstance != null) if (actionInstance != null)
{ {
lastActiveAction = actionInstance; lastActiveAction = actionInstance;
actionInstance.lastActiveTime = Timing.TotalTime;
actionInstance.dialogBox = messageBox; actionInstance.dialogBox = messageBox;
} }
else 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) var content = new GUILayoutGroup(new RectTransform(Vector2.One, parentBox.Content.RectTransform), childAnchor: Anchor.TopLeft, isHorizontal: true)
{ {
@@ -322,9 +323,11 @@ namespace Barotrauma
AlwaysOverrideCursor = true AlwaysOverrideCursor = true
}; };
LocalizedString translatedText = speaker?.DisplayName is not null ? LocalizedString translatedText = text.Replace("\\n", "\n");
TextManager.GetWithVariable(text, "[speakername]", speaker?.DisplayName) : if (speaker?.DisplayName is not null)
TextManager.Get(text); {
translatedText = translatedText.Replace("[speakername]", speaker.DisplayName);
}
translatedText = TextManager.ParseInputTypes(translatedText).Fallback(text); translatedText = TextManager.ParseInputTypes(translatedText).Fallback(text);
if (speaker?.Info != null && drawChathead) if (speaker?.Info != null && drawChathead)
@@ -341,7 +344,7 @@ namespace Barotrauma
AbsoluteSpacing = GUI.IntScale(5) 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, AlwaysOverrideCursor = true,
UserData = "text" UserData = "text"
@@ -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);
}
}
@@ -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);
}
}
@@ -17,7 +17,7 @@ partial class MessageBoxAction : EventAction
var segment = ObjectiveManager.Segment.CreateMessageBoxSegment(id, ObjectiveTag, CreateMessageBox); var segment = ObjectiveManager.Segment.CreateMessageBoxSegment(id, ObjectiveTag, CreateMessageBox);
segment.CanBeCompleted = ObjectiveCanBeCompleted; segment.CanBeCompleted = ObjectiveCanBeCompleted;
segment.ParentId = ParentObjectiveId; segment.ParentId = ParentObjectiveId;
ObjectiveManager.TriggerTutorialSegment(segment, connectObjective: Type == ActionType.ConnectObjective); ObjectiveManager.TriggerSegment(segment, connectObjective: Type == ActionType.ConnectObjective);
} }
} }
else if (Type == ActionType.Close) else if (Type == ActionType.Close)
@@ -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;
}
}
}
@@ -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);
}
}
}
}
@@ -413,23 +413,9 @@ namespace Barotrauma
private Rectangle DrawScriptedEvent(SpriteBatch spriteBatch, ScriptedEvent scriptedEvent, Rectangle? parentRect = null) 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>(); List<DebugLine> positions = new List<DebugLine>();
string text = $"Finished: {scriptedEvent.IsFinished.ColorizeObject()}\n" + string text = scriptedEvent.GetDebugInfo();
$"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";
}
if (scriptedEvent.Targets != null) if (scriptedEvent.Targets != null)
{ {
foreach ((_, List<Entity> entities) in scriptedEvent.Targets) foreach ((_, List<Entity> entities) in scriptedEvent.Targets)
@@ -452,10 +438,7 @@ namespace Barotrauma
{ {
debugPositions.Clear(); debugPositions.Clear();
string text = $"Finished: {artifactEvent.IsFinished.ColorizeObject()}\n" + string text = artifactEvent.GetDebugInfo();
$"Item: {artifactEvent.Item.ColorizeObject()}\n" +
$"Spawn pending: {artifactEvent.SpawnPending.ColorizeObject()}\n" +
$"Spawn position: {artifactEvent.SpawnPos.ColorizeObject()}\n";
if (artifactEvent.Item != null && !artifactEvent.Item.Removed) if (artifactEvent.Item != null && !artifactEvent.Item.Removed)
{ {
@@ -470,10 +453,7 @@ namespace Barotrauma
{ {
debugPositions.Clear(); debugPositions.Clear();
string text = $"Finished: {monsterEvent.IsFinished.ColorizeObject()}\n" + string text = monsterEvent.GetDebugInfo();
$"Amount: {monsterEvent.MinAmount.ColorizeObject()} - {monsterEvent.MaxAmount.ColorizeObject()}\n" +
$"Spawn pending: {monsterEvent.SpawnPending.ColorizeObject()}\n" +
$"Spawn position: {monsterEvent.SpawnPos.ColorizeObject()}\n";
if (monsterEvent.SpawnPos != null && Submarine.MainSub != null) if (monsterEvent.SpawnPos != null && Submarine.MainSub != null)
{ {
@@ -712,7 +692,30 @@ namespace Barotrauma
} }
} }
break; 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);
}
} }
} }
@@ -1,4 +1,5 @@
using Barotrauma.Items.Components; using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using Barotrauma.Networking; using Barotrauma.Networking;
using System.Collections.Generic; using System.Collections.Generic;
@@ -9,22 +10,37 @@ namespace Barotrauma
public override bool DisplayAsCompleted => false; public override bool DisplayAsCompleted => false;
public override bool DisplayAsFailed => 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) public override void ClientReadInitial(IReadMessage msg)
{ {
base.ClientReadInitial(msg); base.ClientReadInitial(msg);
byte caveCount = msg.ReadByte(); byte caveCount = msg.ReadByte();
for (int i = 0; i < caveCount; i++) for (int i = 0; i < caveCount; i++)
{ {
byte selectedCave = msg.ReadByte(); byte selectedCaveIndex = msg.ReadByte();
if (selectedCave < 255 && Level.Loaded != null) 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 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}");
} }
} }
} }
@@ -4,6 +4,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using static Barotrauma.MissionPrefab;
namespace Barotrauma namespace Barotrauma
{ {
@@ -27,7 +28,11 @@ namespace Barotrauma
public Color GetDifficultyColor() 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); float t = MathUtils.InverseLerp(MissionPrefab.MinDifficulty, MissionPrefab.MaxDifficulty, v);
return ToolBox.GradientLerp(t, GUIStyle.Green, GUIStyle.Orange, GUIStyle.Red); return ToolBox.GradientLerp(t, GUIStyle.Green, GUIStyle.Orange, GUIStyle.Red);
} }
@@ -61,36 +66,48 @@ namespace Barotrauma
List<LocalizedString> reputationRewardTexts = new List<LocalizedString>(); List<LocalizedString> reputationRewardTexts = new List<LocalizedString>();
foreach (var reputationReward in ReputationRewards) foreach (var reputationReward in ReputationRewards)
{ {
FactionPrefab targetFactionPrefab; FactionPrefab factionPrefab;
if (reputationReward.Key == "location" ) if (reputationReward.FactionIdentifier == "location" )
{ {
targetFactionPrefab = OriginLocation.Faction?.Prefab; factionPrefab = OriginLocation.Faction?.Prefab;
} }
else else
{ {
FactionPrefab.Prefabs.TryGet(reputationReward.Key, out targetFactionPrefab); FactionPrefab.Prefabs.TryGet(reputationReward.FactionIdentifier, out factionPrefab);
}
if (targetFactionPrefab == null)
{
return string.Empty;
} }
float totalReputationChange = reputationReward.Value; if (factionPrefab != null)
if (GameMain.GameSession?.Campaign?.Factions.Find(f => f.Prefab == targetFactionPrefab) is Faction faction)
{ {
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); float normalizedValue = MathUtils.InverseLerp(-100.0f, 100.0f, totalReputationChange);
string formattedValue = ((int)Math.Round(totalReputationChange)).ToString("+#;-#;0"); //force plus sign for positive numbers string formattedValue = ((int)Math.Round(totalReputationChange)).ToString("+#;-#;0"); //force plus sign for positive numbers
LocalizedString rewardText = TextManager.GetWithVariables( LocalizedString rewardText = TextManager.GetWithVariables(
"reputationformat", "reputationformat",
("[reputationname]", name), ("[reputationname]", name),
("[reputationvalue]", $"‖color:{XMLExtensions.ToStringHex(Reputation.GetReputationColor(normalizedValue))}‖{formattedValue}‖end‖" )); ("[reputationvalue]", $"‖color:{XMLExtensions.ToStringHex(Reputation.GetReputationColor(normalizedValue))}‖{formattedValue}‖end‖"));
reputationRewardTexts.Add(rewardText.Value); reputationRewardTexts.Add(rewardText.Value);
} }
if (reputationRewardTexts.Any()) if (reputationRewardTexts.Any())
{ {
return RichString.Rich(TextManager.AddPunctuation(':', TextManager.Get("reputation"), LocalizedString.Join(", ", reputationRewardTexts))); return RichString.Rich(TextManager.AddPunctuation(':', TextManager.Get("reputation"), LocalizedString.Join(", ", reputationRewardTexts)));
@@ -100,6 +117,20 @@ namespace Barotrauma
return string.Empty; 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) partial void ShowMessageProjSpecific(int missionState)
{ {
@@ -9,6 +9,19 @@ namespace Barotrauma
public override bool DisplayAsCompleted => State > 0 && !requireDelivery; public override bool DisplayAsCompleted => State > 0 && !requireDelivery;
public override bool DisplayAsFailed => false; 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) public override void ClientReadInitial(IReadMessage msg)
{ {
base.ClientReadInitial(msg); base.ClientReadInitial(msg);
@@ -20,8 +33,9 @@ namespace Barotrauma
{ {
if (selectedCaveIndex < Level.Loaded.Caves.Count) if (selectedCaveIndex < Level.Loaded.Caves.Count)
{ {
Level.Loaded.Caves[selectedCaveIndex].DisplayOnSonar = true; selectedCave = Level.Loaded.Caves[selectedCaveIndex];
SpawnNestObjects(Level.Loaded, Level.Loaded.Caves[selectedCaveIndex]); selectedCave.MissionsToDisplayOnSonar.Add(this);
SpawnNestObjects(Level.Loaded, selectedCave);
} }
else else
{ {
@@ -99,10 +99,10 @@ namespace Barotrauma
})) }))
.Aggregate(TextManager.SpeciallyHandledCharCategory.None, (current, category) => current | category); .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( : this(
element.GetAttributeContentPath("file")?.Value, element.GetAttributeContentPath("file")?.Value,
(uint)element.GetAttributeInt("size", 14), (uint)element.GetAttributeInt("size", (int)defaultSize),
gd, gd,
element.GetAttributeBool("dynamicloading", false), element.GetAttributeBool("dynamicloading", false),
ExtractShccFromXElement(element)) ExtractShccFromXElement(element))
@@ -38,6 +38,7 @@ namespace Barotrauma
private float prevUIScale; private float prevUIScale;
private readonly GUIFrame channelSettingsFrame; private readonly GUIFrame channelSettingsFrame;
private readonly GUITextBlock radioJammedWarning;
private readonly GUITextBox channelText; private readonly GUITextBox channelText;
private readonly GUILayoutGroup channelPickerContent; private readonly GUILayoutGroup channelPickerContent;
private readonly GUIButton memButton; private readonly GUIButton memButton;
@@ -107,6 +108,13 @@ namespace Barotrauma
RelativeSpacing = 0.01f 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") var buttonLeft = new GUIButton(new RectTransform(new Vector2(0.1f, 0.8f), channelSettingsContent.RectTransform), style: "DeviceButton")
{ {
PlaySoundOnSelect = false, 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); 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) if (prevRadio != radio)
{ {
@@ -672,10 +680,11 @@ namespace Barotrauma
} }
} }
channelSettingsFrame.Visible = true; channelSettingsFrame.Visible = true;
radioJammedWarning.Visible = radio is { JamTimer: > 0 };
} }
else else
{ {
channelSettingsFrame.Visible = false; radioJammedWarning.Visible = channelSettingsFrame.Visible = false;
channelPickerContent.Children.First().CanBeFocused = true; channelPickerContent.Children.First().CanBeFocused = true;
channelMemPending = false; channelMemPending = false;
memButton.Enabled = true; memButton.Enabled = true;
@@ -756,7 +756,7 @@ namespace Barotrauma
private bool CreateRenamingComponent(GUIButton button, object userData) 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), var outerGlowFrame = new GUIFrame(new RectTransform(new Vector2(1.25f, 1.25f), parentComponent.RectTransform, Anchor.Center),
style: "OuterGlow", color: Color.Black * 0.7f); style: "OuterGlow", color: Color.Black * 0.7f);
var frame = new GUIFrame(new RectTransform(new Vector2(0.33f, 0.4f), outerGlowFrame.RectTransform, anchor: Anchor.Center) 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) 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); campaign.CrewManager.FireCharacter(characterInfo);
SelectCharacter(null, null, null); SelectCharacter(null, null, null);
@@ -161,7 +161,7 @@ namespace Barotrauma
return 1; return 1;
} }
return string.Compare(file1, file2); return string.Compare(file1, file2, StringComparison.OrdinalIgnoreCase);
} }
private static void InitIfNecessary() private static void InitIfNecessary()
@@ -419,6 +419,8 @@ namespace Barotrauma
}; };
} }
fileList.Content.RectTransform.SortChildren(SortFiles);
directoryBox!.Text = currentDirectory; directoryBox!.Text = currentDirectory;
fileBox!.Text = ""; fileBox!.Text = "";
fileList.Deselect(); fileList.Deselect();
@@ -111,7 +111,7 @@ namespace Barotrauma
/// </summary> /// </summary>
public static float AspectRatioAdjustment => HorizontalAspectRatio < 1.4f ? (1.0f - (1.4f - HorizontalAspectRatio)) : 1.0f; public static float AspectRatioAdjustment => HorizontalAspectRatio < 1.4f ? (1.0f - (1.4f - HorizontalAspectRatio)) : 1.0f;
public static bool IsUltrawide => HorizontalAspectRatio > 2.0f; public static bool IsUltrawide => HorizontalAspectRatio > 2.3f;
public static int UIWidth public static int UIWidth
{ {
@@ -1010,16 +1010,13 @@ namespace Barotrauma
// Sub editor drag and highlight // Sub editor drag and highlight
case SubEditorScreen editor: case SubEditorScreen editor:
{ {
foreach (var mapEntity in MapEntity.mapEntityList) if (MapEntity.StartMovingPos != Vector2.Zero || MapEntity.Resizing)
{ {
if (MapEntity.StartMovingPos != Vector2.Zero) return CursorState.Dragging;
{ }
return CursorState.Dragging; if (MapEntity.HighlightedEntities.Any(h => !h.IsSelected))
} {
if (mapEntity.IsHighlighted) return CursorState.Hand;
{
return CursorState.Hand;
}
} }
break; break;
} }
@@ -2438,13 +2435,14 @@ namespace Barotrauma
var pauseMenuInner = new GUIFrame(new RectTransform(new Vector2(0.13f, 0.3f), PauseMenu.RectTransform, Anchor.Center) { MinSize = new Point(250, 300) }); var pauseMenuInner = new GUIFrame(new RectTransform(new Vector2(0.13f, 0.3f), PauseMenu.RectTransform, Anchor.Center) { MinSize = new Point(250, 300) });
var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.7f, 0.6f), pauseMenuInner.RectTransform, Anchor.Center)) float padding = 0.06f;
var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.7f, 0.8f), pauseMenuInner.RectTransform, Anchor.BottomCenter) { RelativeOffset = new Vector2(0.0f, padding) })
{ {
Stretch = true, AbsoluteSpacing = IntScale(15)
RelativeSpacing = 0.05f
}; };
new GUIButton(new RectTransform(new Vector2(0.1f, 0.1f), pauseMenuInner.RectTransform, Anchor.TopRight) { AbsoluteOffset = new Point((int)(15 * GUI.Scale)) }, new GUIButton(new RectTransform(new Vector2(0.1f, 0.07f), pauseMenuInner.RectTransform, Anchor.TopRight) { RelativeOffset = new Vector2(padding) },
"", style: "GUIBugButton") "", style: "GUIBugButton")
{ {
IgnoreLayoutGroups = true, IgnoreLayoutGroups = true,
@@ -2520,6 +2518,13 @@ namespace Barotrauma
} }
GUITextBlock.AutoScaleAndNormalize(buttonContainer.Children.Where(c => c is GUIButton).Select(c => ((GUIButton)c).TextBlock)); GUITextBlock.AutoScaleAndNormalize(buttonContainer.Children.Where(c => c is GUIButton).Select(c => ((GUIButton)c).TextBlock));
//scale to ensure there's enough room for all the buttons
pauseMenuInner.RectTransform.MinSize = new Point(
pauseMenuInner.RectTransform.MinSize.X,
Math.Max(
(int)(buttonContainer.Children.Sum(c => c.Rect.Height + buttonContainer.AbsoluteSpacing) / buttonContainer.RectTransform.RelativeSize.Y),
pauseMenuInner.RectTransform.MinSize.X));
} }
void CreateButton(string textTag, GUIComponent parent, Action action, string verificationTextTag = null) void CreateButton(string textTag, GUIComponent parent, Action action, string verificationTextTag = null)
@@ -781,7 +781,7 @@ namespace Barotrauma
if (toolTipBlock.Rect.Right > GameMain.GraphicsWidth - 10) 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) if (toolTipBlock.Rect.Bottom > GameMain.GraphicsHeight - 10)
{ {
@@ -49,7 +49,7 @@ namespace Barotrauma
get { return lifeTime; } get { return lifeTime; }
} }
public ScalableFont Font public GUIFont Font
{ {
get; get;
private set; 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); coloredText = new ColoredText(text, color, false, false);
this.lifeTime = lifeTime; this.lifeTime = lifeTime;
@@ -81,7 +81,7 @@ namespace Barotrauma
Font = font; 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); coloredText = new ColoredText(text, color, false, false);
WorldSpace = true; WorldSpace = true;
@@ -693,5 +693,52 @@ namespace Barotrauma
rectT.Parent = RectTransform; rectT.Parent = RectTransform;
Buttons.Add(new GUIButton(rectT, text) { OnClicked = onClick }); 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;
}
} }
} }
@@ -132,7 +132,7 @@ namespace Barotrauma
if (subElement.NameAsIdentifier() != "override") { continue; } if (subElement.NameAsIdentifier() != "override") { continue; }
if (ScalableFont.ExtractShccFromXElement(subElement).HasFlag(flag)) if (ScalableFont.ExtractShccFromXElement(subElement).HasFlag(flag))
{ {
return new ScalableFont(subElement, GameMain.Instance.GraphicsDevice); return new ScalableFont(subElement, font.Size, GameMain.Instance.GraphicsDevice);
} }
} }
@@ -4,7 +4,7 @@ using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma namespace Barotrauma
{ {
public class GUIScissorComponent: GUIComponent public sealed class GUIScissorComponent : GUIComponent
{ {
public GUIComponent Content; public GUIComponent Content;
@@ -14,19 +14,14 @@ namespace Barotrauma
{ {
CanBeFocused = false CanBeFocused = false
}; };
rectT.ChildrenChanged += CheckForChildren;
} }
protected override void Update(float deltaTime) private void CheckForChildren(RectTransform rectT)
{ {
base.Update(deltaTime); if (rectT == Content.RectTransform) { return; }
throw new InvalidOperationException($"Children were found in {nameof(GUIScissorComponent)}, Add them to {nameof(GUIScissorComponent)}.{nameof(Content)} instead.");
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);
} }
public override void DrawChildren(SpriteBatch spriteBatch, bool recursive) public override void DrawChildren(SpriteBatch spriteBatch, bool recursive)
@@ -57,7 +52,13 @@ namespace Barotrauma
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: prevRasterizerState); 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; child.ClampMouseRectToParent = true;
@@ -142,11 +142,13 @@ namespace Barotrauma
public readonly static GUIColor HealthBarColorHigh = new GUIColor("HealthBarColorHigh"); public readonly static GUIColor HealthBarColorHigh = new GUIColor("HealthBarColorHigh");
public readonly static GUIColor HealthBarColorPoisoned = new GUIColor("HealthBarColorPoisoned"); public readonly static GUIColor HealthBarColorPoisoned = new GUIColor("HealthBarColorPoisoned");
private readonly static Point defaultItemFrameMargin = new Point(50, 56);
public static Point ItemFrameMargin public static Point ItemFrameMargin
{ {
get get
{ {
Point size = new Point(50, 56).Multiply(GUI.SlicedSpriteScale); Point size = defaultItemFrameMargin.Multiply(GUI.SlicedSpriteScale);
var style = GetComponentStyle("ItemUI"); var style = GetComponentStyle("ItemUI");
var sprite = style?.Sprites[GUIComponent.ComponentState.None].First(); 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 Point ItemFrameOffset => new Point(0, 3).Multiply(GUI.SlicedSpriteScale);
public static GUIComponentStyle GetComponentStyle(string styleName) public static GUIComponentStyle GetComponentStyle(string styleName)
@@ -12,8 +12,6 @@ namespace Barotrauma
public delegate bool OnSelectedHandler(GUITickBox obj); public delegate bool OnSelectedHandler(GUITickBox obj);
public OnSelectedHandler OnSelected; public OnSelectedHandler OnSelected;
public static int size = 20;
private GUIRadioButtonGroup radioButtonGroup; private GUIRadioButtonGroup radioButtonGroup;
public override bool Selected public override bool Selected
@@ -21,21 +19,7 @@ namespace Barotrauma
get { return isSelected; } get { return isSelected; }
set set
{ {
if (value == isSelected) { return; } SetSelected(value, callOnSelected: true);
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);
} }
} }
@@ -186,6 +170,27 @@ namespace Barotrauma
text.SetTextPos(); text.SetTextPos();
ContentWidth = box.Rect.Width + text.Padding.X + text.TextSize.X + text.Padding.Z; 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) protected override void Update(float deltaTime)
{ {
@@ -160,8 +160,9 @@ namespace Barotrauma
int crewAreaY = ButtonAreaTop.Bottom + Padding; int crewAreaY = ButtonAreaTop.Bottom + Padding;
int crewAreaHeight = ObjectiveAnchor.Top - Padding - crewAreaY; int crewAreaHeight = ObjectiveAnchor.Top - Padding - crewAreaY;
float crewAreaWidthMultiplier = GUI.IsUltrawide ? GUI.HorizontalAspectRatio : 1.0f; CrewArea = new Rectangle(Padding, crewAreaY,
CrewArea = new Rectangle(Padding, crewAreaY, (int)(Math.Max(400 * GUI.Scale, 220) * crewAreaWidthMultiplier), crewAreaHeight); (int)MathHelper.Clamp(400 * GUI.Scale, 220, GameMain.GraphicsHeight * 0.4f),
crewAreaHeight);
InventoryAreaLower = new Rectangle(ChatBoxArea.Right + Padding * 7, inventoryTopY, GameMain.GraphicsWidth - Padding * 9 - ChatBoxArea.Width, GameMain.GraphicsHeight - inventoryTopY); InventoryAreaLower = new Rectangle(ChatBoxArea.Right + Padding * 7, inventoryTopY, GameMain.GraphicsWidth - Padding * 9 - ChatBoxArea.Width, GameMain.GraphicsHeight - inventoryTopY);
int healthWindowWidth = (int)(GameMain.GraphicsWidth * 0.5f); int healthWindowWidth = (int)(GameMain.GraphicsWidth * 0.5f);
@@ -4,12 +4,13 @@ using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Input;
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
namespace Barotrauma namespace Barotrauma
{ {
class LoadingScreen sealed class LoadingScreen
{ {
private readonly Sprite defaultBackgroundTexture, overlay; private readonly Sprite defaultBackgroundTexture, overlay;
private readonly SpriteSheet decorativeGraph, decorativeMap; private readonly SpriteSheet decorativeGraph, decorativeMap;
@@ -37,36 +38,16 @@ namespace Barotrauma
} }
} }
private Queue<PendingSplashScreen> pendingSplashScreens = new Queue<PendingSplashScreen>();
/// <summary> /// <summary>
/// Triplet.first = filepath, Triplet.second = resolution, Triplet.third = audio gain /// Triplet.first = filepath, Triplet.second = resolution, Triplet.third = audio gain
/// </summary> /// </summary>
public Queue<PendingSplashScreen> PendingSplashScreens public readonly ConcurrentQueue<PendingSplashScreen> PendingSplashScreens = new ConcurrentQueue<PendingSplashScreen>();
{
get
{
lock (loadMutex)
{
return pendingSplashScreens;
}
}
set
{
lock (loadMutex)
{
pendingSplashScreens = value;
}
}
}
public bool PlayingSplashScreen public bool PlayingSplashScreen
{ {
get 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); selectedTip = RichString.Rich(tip);
} }
private readonly object loadMutex = new object(); public float LoadState;
private float? loadState;
public float? LoadState
{
get
{
lock (loadMutex)
{
return loadState;
}
}
set
{
lock (loadMutex)
{
loadState = value;
DrawLoadingText = true;
}
}
}
public bool DrawLoadingText
{
get;
set;
}
public bool WaitForLanguageSelection public bool WaitForLanguageSelection
{ {
@@ -121,7 +76,7 @@ namespace Barotrauma
overlay = new Sprite("Content/UI/MainMenuVignette.png", Vector2.Zero); overlay = new Sprite("Content/UI/MainMenuVignette.png", Vector2.Zero);
noiseSprite = new Sprite("Content/UI/noise.png", Vector2.Zero); noiseSprite = new Sprite("Content/UI/noise.png", Vector2.Zero);
DrawLoadingText = true;
SetSelectedTip(TextManager.Get("LoadingScreenTip")); SetSelectedTip(TextManager.Get("LoadingScreenTip"));
} }
@@ -170,10 +125,11 @@ namespace Barotrauma
{ {
DrawLanguageSelectionPrompt(spriteBatch, graphics); DrawLanguageSelectionPrompt(spriteBatch, graphics);
} }
else if (DrawLoadingText) else
{ {
LocalizedString loadText; LocalizedString loadText;
if (LoadState == 100.0f) var loadState = LoadState; // avoid multiple reads here to prevent jank
if (loadState >= 100.0f)
{ {
#if DEBUG #if DEBUG
if (GameSettings.CurrentConfig.AutomaticQuickStartEnabled || GameSettings.CurrentConfig.AutomaticCampaignLoadEnabled || (GameSettings.CurrentConfig.TestScreenEnabled && GameMain.FirstLoad)) if (GameSettings.CurrentConfig.AutomaticQuickStartEnabled || GameSettings.CurrentConfig.AutomaticCampaignLoadEnabled || (GameSettings.CurrentConfig.TestScreenEnabled && GameMain.FirstLoad))
@@ -191,17 +147,17 @@ namespace Barotrauma
else else
{ {
loadText = TextManager.Get("Loading"); loadText = TextManager.Get("Loading");
if (LoadState != null) if (loadState >= 0f)
{ {
loadText += " " + (int)LoadState + " %"; loadText += $" {loadState:N0} %";
}
#if DEBUG #if DEBUG
if (GameMain.FirstLoad && GameMain.CancelQuickStart) if (GameMain.FirstLoad && GameMain.CancelQuickStart)
{ {
loadText += " (Quickstart aborted)"; loadText += " (Quickstart aborted)";
}
#endif
} }
#endif
} }
if (GUIStyle.LargeFont.HasValue) if (GUIStyle.LargeFont.HasValue)
@@ -343,11 +299,9 @@ namespace Barotrauma
private void DrawSplashScreen(SpriteBatch spriteBatch, GraphicsDevice graphics) private void DrawSplashScreen(SpriteBatch spriteBatch, GraphicsDevice graphics)
{ {
if (currSplashScreen == null && PendingSplashScreens.Count == 0) { return; }
if (currSplashScreen == null) if (currSplashScreen == null)
{ {
var newSplashScreen = PendingSplashScreens.Dequeue(); if (!PendingSplashScreens.TryDequeue(out var newSplashScreen)) { return; }
string fileName = newSplashScreen.Filename; string fileName = newSplashScreen.Filename;
try try
{ {
@@ -362,10 +316,10 @@ namespace Barotrauma
PendingSplashScreens.Clear(); PendingSplashScreens.Clear();
currSplashScreen = null; currSplashScreen = null;
} }
if (currSplashScreen == null) { return; }
} }
if (currSplashScreen == null) { return; }
if (currSplashScreen.IsPlaying) if (currSplashScreen.IsPlaying)
{ {
graphics.Clear(Color.Black); graphics.Clear(Color.Black);
@@ -425,7 +379,7 @@ namespace Barotrauma
public IEnumerable<CoroutineStatus> DoLoading(IEnumerable<CoroutineStatus> loader) public IEnumerable<CoroutineStatus> DoLoading(IEnumerable<CoroutineStatus> loader)
{ {
drawn = false; drawn = false;
LoadState = null; LoadState = -1f;
SetSelectedTip(TextManager.Get("LoadingScreenTip")); SetSelectedTip(TextManager.Get("LoadingScreenTip"));
currentBackgroundTexture = LocationType.Prefabs.Where(p => p.UsePortraitInRandomLoadingScreens).GetRandomUnsynced()?.GetPortrait(Rand.Int(int.MaxValue)); 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)) if (GameMain.GameSession?.GameMode?.Missions is { } missions && missions.Any(m => m.Prefab.HasPortraits))
@@ -946,7 +946,7 @@ namespace Barotrauma
} }
if (truncated) 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); GUITextBlock priceBlock = new GUITextBlock(new RectTransform(new Vector2(1f, 0.25f), bottomTextLayout.RectTransform), TextManager.FormatCurrency(affliction.Price), font: GUIStyle.SubHeadingFont);
@@ -306,6 +306,8 @@ namespace Barotrauma
{ {
_scaleBasis = value; _scaleBasis = value;
RecalculateAbsoluteSize(); RecalculateAbsoluteSize();
RecalculateAnchorPoint();
RecalculatePivotOffset();
} }
} }
@@ -91,8 +91,8 @@ namespace Barotrauma
var angle = (float)Math.Atan2(point2.Y - point1.Y, point2.X - point1.X); var angle = (float)Math.Atan2(point2.Y - point1.Y, point2.X - point1.X);
var scale = new Vector2(length, thickness); var scale = new Vector2(length, thickness);
Vector2 middle = new Vector2((point1.X + point2.X) / 2f, (point1.Y + point2.Y) / 2f); Vector2 middle = new Vector2((point1.X + point2.X) / 2f, (point1.Y + point2.Y) / 2f);
Texture2D tex = GetTexture(spriteBatch); Texture2D tex = GUI.WhiteTexture;
spriteBatch.Draw(GetTexture(spriteBatch), middle, null, color, angle, new Vector2(tex.Width / 2f, tex.Height / 2f), scale, SpriteEffects.None, 0); 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) 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); 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> /// <summary>
/// Draws a line from point1 to point2 with an offset /// Draws a line from point1 to point2 with an offset
/// </summary> /// </summary>
@@ -124,18 +136,18 @@ namespace Barotrauma
// calculate the angle between the two vectors // calculate the angle between the two vectors
var angle = (float)Math.Atan2(point2.Y - point1.Y, point2.X - point1.X); 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> /// <summary>
/// Draws a line from point1 to point2 with an offset /// Draws a line from point1 to point2 with an offset
/// </summary> /// </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) float thickness = 1f)
{ {
var origin = new Vector2(0f, 0.5f); var origin = new Vector2(0f, tex.Height / 2f);
var scale = new Vector2(length, thickness); var scale = new Vector2(length / tex.Width, thickness / tex.Height);
spriteBatch.Draw(GetTexture(spriteBatch), point, null, color, angle, origin, scale, SpriteEffects.None, 0); spriteBatch.Draw(tex, point, null, color, angle, origin, scale, SpriteEffects.None, 0);
} }
/// <summary> /// <summary>
@@ -19,7 +19,7 @@ namespace Barotrauma
private static UISprite spectateIcon, disconnectedIcon; private static UISprite spectateIcon, disconnectedIcon;
private static Sprite ownerIcon, moderatorIcon; 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; } public static InfoFrameTab SelectedTab { get; private set; }
private GUIFrame infoFrame, contentFrame; private GUIFrame infoFrame, contentFrame;
@@ -299,9 +299,15 @@ namespace Barotrauma
var crewButton = createTabButton(InfoFrameTab.Crew, "crew"); 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) 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)); 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"); 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) public void SelectInfoFrameTab(InfoFrameTab selectedTab)
@@ -387,12 +385,6 @@ namespace Barotrauma
GameMain.GameSession.RoundSummary.CreateReputationInfoPanel(reputationFrame, campaignMode); GameMain.GameSession.RoundSummary.CreateReputationInfoPanel(reputationFrame, campaignMode);
} }
break; 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: case InfoFrameTab.Submarine:
CreateSubmarineInfo(infoFrameHolder, Submarine.MainSub); CreateSubmarineInfo(infoFrameHolder, Submarine.MainSub);
break; break;
@@ -1539,96 +1531,44 @@ namespace Barotrauma
int locationInfoYOffset = locationInfoContainer.Rect.Height + padding * 2; 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) }); 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.ContentBackground.Color = Color.Transparent;
missionList.Spacing = GUI.IntScale(15); missionList.Spacing = GUI.IntScale(15);
if (GameMain.GameSession?.Missions != null) 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) foreach (Mission mission in GameMain.GameSession.Missions)
{ {
if (!mission.Prefab.ShowInMenus) { continue; } 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; textContent.AddRange(mission.ShownMessages);
foreach (LocalizedString missionMessage in 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; UpdateMissionStateIcon();
} mission.OnMissionStateChanged += (mission) => UpdateMissionStateIcon();
RichString rewardText = mission.GetMissionRewardText(Submarine.MainSub);
RichString reputationText = mission.GetReputationRewardText();
Func<string, string> wrapMissionText(GUIFont font) void UpdateMissionStateIcon()
{
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)
{ {
Color = mission.Prefab.IconColor, if (mission.DisplayAsCompleted || mission.DisplayAsFailed)
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)
{ {
CanBeFocused = false, RoundSummary.UpdateMissionStateIcon(mission.DisplayAsCompleted, missionIcon);
Color = difficultyColor }
};
} }
} }
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 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); 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); 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) private static void CreateSubmarineInfo(GUIFrame infoFrame, Submarine sub)
{
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)
{ {
GUIFrame subInfoFrame = new GUIFrame(new RectTransform(Vector2.One, infoFrame.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox"); 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); 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) public static void CreateSkillList(Character character, CharacterInfo info, GUIListBox parent)
{ {
@@ -117,6 +117,9 @@ namespace Barotrauma
return MathHelper.Clamp(Math.Min(Math.Min(scale.X, scale.Y), GUI.SlicedSpriteScale), minBorderScale, maxBorderScale); 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) public void Draw(SpriteBatch spriteBatch, Rectangle rect, Color color, SpriteEffects spriteEffects = SpriteEffects.None, Vector2? uvOffset = null)
{ {
uvOffset ??= Vector2.Zero; uvOffset ??= Vector2.Zero;
@@ -11,7 +11,7 @@ namespace Barotrauma
{ {
if (consentTextAvailable) 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 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)) var content = new GUILayoutGroup(new RectTransform(new Vector2(0.95f), frame.RectTransform, Anchor.Center))
@@ -55,7 +55,8 @@ namespace Barotrauma
yesBtn.OnClicked += (btn, userdata) => yesBtn.OnClicked += (btn, userdata) =>
{ {
GUIMessageBox.MessageBoxes.Remove(background); GUIMessageBox.MessageBoxes.Remove(background);
SetConsentInternal(Consent.Yes); var loadingBox = GUIMessageBox.CreateLoadingBox(TextManager.Get("PleaseWait"));
SetConsentInternal(Consent.Yes, onAnswerSent: loadingBox.Close);
return true; return true;
}; };
yesBtn.Enabled = false; yesBtn.Enabled = false;
@@ -76,7 +77,8 @@ namespace Barotrauma
noBtn.OnClicked += (btn, userdata) => noBtn.OnClicked += (btn, userdata) =>
{ {
GUIMessageBox.MessageBoxes.Remove(background); GUIMessageBox.MessageBoxes.Remove(background);
SetConsent(Consent.No); var loadingBox = GUIMessageBox.CreateLoadingBox(TextManager.Get("PleaseWait"));
SetConsent(Consent.No, onAnswerSent: loadingBox.Close);
return true; return true;
}; };
noBtn.Enabled = false; noBtn.Enabled = false;
@@ -111,7 +111,8 @@ namespace Barotrauma
public static LoadingScreen TitleScreen; public static LoadingScreen TitleScreen;
private bool loadingScreenOpen; private bool loadingScreenOpen;
private CoroutineHandle loadingCoroutine; private Thread initialLoadingThread;
public bool HasLoaded { get; private set; } public bool HasLoaded { get; private set; }
private readonly GameTime fixedTime; private readonly GameTime fixedTime;
@@ -411,24 +412,21 @@ namespace Barotrauma
WaitForLanguageSelection = GameSettings.CurrentConfig.Language == LanguageIdentifier.None WaitForLanguageSelection = GameSettings.CurrentConfig.Language == LanguageIdentifier.None
}; };
bool canLoadInSeparateThread = true; initialLoadingThread = new Thread(Load);
initialLoadingThread.Start();
loadingCoroutine = CoroutineManager.StartCoroutine(Load(canLoadInSeparateThread), "Load", canLoadInSeparateThread);
} }
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);
}
} }
}
log("LOADING COROUTINE");
private IEnumerable<CoroutineStatus> Load(bool isSeparateThread)
{
if (GameSettings.CurrentConfig.VerboseLogging)
{
DebugConsole.NewMessage("LOADING COROUTINE", Color.Lime);
}
ContentPackageManager.LoadVanillaFileList(); ContentPackageManager.LoadVanillaFileList();
@@ -438,7 +436,7 @@ namespace Barotrauma
TitleScreen.AvailableLanguages = TextManager.AvailableLanguages.OrderBy(l => l.Value != "english".ToIdentifier()).ThenBy(l => l.Value).ToArray(); TitleScreen.AvailableLanguages = TextManager.AvailableLanguages.OrderBy(l => l.Value != "english".ToIdentifier()).ThenBy(l => l.Value).ToArray();
while (TitleScreen.WaitForLanguageSelection) while (TitleScreen.WaitForLanguageSelection)
{ {
yield return CoroutineStatus.Running; Thread.Sleep((int)(Timing.Step * 1000));
} }
ContentPackageManager.VanillaCorePackage.UnloadFilesOfType<TextFile>(); ContentPackageManager.VanillaCorePackage.UnloadFilesOfType<TextFile>();
} }
@@ -450,25 +448,13 @@ namespace Barotrauma
{ {
var pendingSplashScreens = TitleScreen.PendingSplashScreens; var pendingSplashScreens = TitleScreen.PendingSplashScreens;
float baseVolume = MathHelper.Clamp(GameSettings.CurrentConfig.Audio.SoundVolume * 2.0f, 0.0f, 1.0f); 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_UTG.webm", baseVolume * 0.5f));
pendingSplashScreens?.Enqueue(new LoadingScreen.PendingSplashScreen("Content/SplashScreens/Splash_FF.webm", baseVolume)); pendingSplashScreens.Enqueue(new LoadingScreen.PendingSplashScreen("Content/SplashScreens/Splash_FF.webm", baseVolume));
pendingSplashScreens?.Enqueue(new LoadingScreen.PendingSplashScreen("Content/SplashScreens/Splash_Daedalic.webm", baseVolume * 0.1f)); 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;
}
} }
GUI.Init(); GUI.Init();
yield return CoroutineStatus.Running;
LegacySteamUgcTransition.Prepare(); LegacySteamUgcTransition.Prepare();
var contentPackageLoadRoutine = ContentPackageManager.Init(); var contentPackageLoadRoutine = ContentPackageManager.Init();
foreach (var progress in contentPackageLoadRoutine foreach (var progress in contentPackageLoadRoutine
@@ -476,7 +462,6 @@ namespace Barotrauma
{ {
const float min = 1f, max = 70f; const float min = 1f, max = 70f;
TitleScreen.LoadState = MathHelper.Lerp(min, max, progress); TitleScreen.LoadState = MathHelper.Lerp(min, max, progress);
yield return CoroutineStatus.Running;
} }
var corePackage = ContentPackageManager.EnabledPackages.Core; var corePackage = ContentPackageManager.EnabledPackages.Core;
@@ -505,7 +490,6 @@ namespace Barotrauma
TaskPool.Add("InitRelayNetworkAccess", SteamManager.InitRelayNetworkAccess(), (t) => { }); TaskPool.Add("InitRelayNetworkAccess", SteamManager.InitRelayNetworkAccess(), (t) => { });
HintManager.Init(); HintManager.Init();
yield return CoroutineStatus.Running;
CoreEntityPrefab.InitCorePrefabs(); CoreEntityPrefab.InitCorePrefabs();
GameModePreset.Init(); GameModePreset.Init();
@@ -513,7 +497,6 @@ namespace Barotrauma
SubmarineInfo.RefreshSavedSubs(); SubmarineInfo.RefreshSavedSubs();
TitleScreen.LoadState = 75.0f; TitleScreen.LoadState = 75.0f;
yield return CoroutineStatus.Running;
GameScreen = new GameScreen(GraphicsDeviceManager.GraphicsDevice); GameScreen = new GameScreen(GraphicsDeviceManager.GraphicsDevice);
@@ -521,13 +504,11 @@ namespace Barotrauma
LightManager = new Lights.LightManager(base.GraphicsDevice); LightManager = new Lights.LightManager(base.GraphicsDevice);
TitleScreen.LoadState = 80.0f; TitleScreen.LoadState = 80.0f;
yield return CoroutineStatus.Running;
MainMenuScreen = new MainMenuScreen(this); MainMenuScreen = new MainMenuScreen(this);
ServerListScreen = new ServerListScreen(); ServerListScreen = new ServerListScreen();
TitleScreen.LoadState = 85.0f; TitleScreen.LoadState = 85.0f;
yield return CoroutineStatus.Running;
#if USE_STEAM #if USE_STEAM
if (SteamManager.IsInitialized) if (SteamManager.IsInitialized)
@@ -553,12 +534,10 @@ namespace Barotrauma
TestScreen = new TestScreen(); TestScreen = new TestScreen();
TitleScreen.LoadState = 90.0f; TitleScreen.LoadState = 90.0f;
yield return CoroutineStatus.Running;
ParticleEditorScreen = new ParticleEditorScreen(); ParticleEditorScreen = new ParticleEditorScreen();
TitleScreen.LoadState = 95.0f; TitleScreen.LoadState = 95.0f;
yield return CoroutineStatus.Running;
LevelEditorScreen = new LevelEditorScreen(); LevelEditorScreen = new LevelEditorScreen();
SpriteEditorScreen = new SpriteEditorScreen(); SpriteEditorScreen = new SpriteEditorScreen();
@@ -566,8 +545,6 @@ namespace Barotrauma
CharacterEditorScreen = new CharacterEditor.CharacterEditorScreen(); CharacterEditorScreen = new CharacterEditor.CharacterEditorScreen();
CampaignEndScreen = new CampaignEndScreen(); CampaignEndScreen = new CampaignEndScreen();
yield return CoroutineStatus.Running;
#if DEBUG #if DEBUG
LevelGenerationParams.CheckValidity(); LevelGenerationParams.CheckValidity();
#endif #endif
@@ -583,12 +560,7 @@ namespace Barotrauma
TitleScreen.LoadState = 100.0f; TitleScreen.LoadState = 100.0f;
HasLoaded = true; HasLoaded = true;
if (GameSettings.CurrentConfig.VerboseLogging) log("LOADING COROUTINE FINISHED");
{
DebugConsole.NewMessage("LOADING COROUTINE FINISHED", Color.Lime);
}
yield return CoroutineStatus.Success;
} }
/// <summary> /// <summary>
@@ -741,11 +713,6 @@ namespace Barotrauma
#endif #endif
Client?.Update((float)Timing.Step); Client?.Update((float)Timing.Step);
if (!HasLoaded && !CoroutineManager.IsCoroutineRunning(loadingCoroutine))
{
throw new LoadingException(loadingCoroutine.Exception);
}
} }
else if (HasLoaded) else if (HasLoaded)
{ {
@@ -1174,7 +1141,7 @@ namespace Barotrauma
{ {
waitForKeyHit = waitKeyHit; waitForKeyHit = waitKeyHit;
loadingScreenOpen = true; loadingScreenOpen = true;
TitleScreen.LoadState = null; TitleScreen.LoadState = 0f;
return CoroutineManager.StartCoroutine(TitleScreen.DoLoading(loader)); return CoroutineManager.StartCoroutine(TitleScreen.DoLoading(loader));
} }
@@ -34,6 +34,8 @@ namespace Barotrauma
private bool _isCrewMenuOpen = true; private bool _isCrewMenuOpen = true;
private Point crewListEntrySize; private Point crewListEntrySize;
private readonly List<GUITickBox> traitorButtons = new List<GUITickBox>();
/// <summary> /// <summary>
/// Present only in single player games. In multiplayer. The chatbox is found from GameSession.Client. /// Present only in single player games. In multiplayer. The chatbox is found from GameSession.Client.
/// </summary> /// </summary>
@@ -95,6 +97,7 @@ namespace Barotrauma
{ {
CanBeFocused = false CanBeFocused = false
}; };
crewArea.RectTransform.NonScaledSize = HUDLayoutSettings.CrewArea.Size;
// AbsoluteOffset is set in UpdateProjectSpecific based on crewListOpenState // AbsoluteOffset is set in UpdateProjectSpecific based on crewListOpenState
crewList = new GUIListBox(new RectTransform(Vector2.One, crewArea.RectTransform), style: null, isScrollBarOnDefaultSide: false) crewList = new GUIListBox(new RectTransform(Vector2.One, crewArea.RectTransform), style: null, isScrollBarOnDefaultSide: false)
@@ -193,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()) 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."); 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.");
@@ -225,7 +228,8 @@ namespace Barotrauma
//report buttons //report buttons
foreach (OrderPrefab orderPrefab in reports) 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) var btn = new GUIButton(new RectTransform(Vector2.One, parent.RectTransform, scaleBasis: isHorizontal ? ScaleBasis.BothHeight : ScaleBasis.BothWidth), style: null)
{ {
OnClicked = (button, userData) => OnClicked = (button, userData) =>
@@ -366,7 +370,7 @@ namespace Barotrauma
} }
} }
int iconsVisible = isJobIconVisible ? 5 : 4; int iconsVisible = isJobIconVisible ? 6 : 5;
var nameRelativeWidth = 1.0f var nameRelativeWidth = 1.0f
// Start padding // Start padding
- paddingRelativeWidth - paddingRelativeWidth
@@ -412,7 +416,6 @@ namespace Barotrauma
{ {
AllowMouseWheelScroll = false, AllowMouseWheelScroll = false,
CurrentDragMode = GUIListBox.DragMode.DragWithinBox, CurrentDragMode = GUIListBox.DragMode.DragWithinBox,
HideChildrenOutsideFrame = false,
KeepSpaceForScrollBar = false, KeepSpaceForScrollBar = false,
OnRearranged = OnOrdersRearranged, OnRearranged = OnOrdersRearranged,
ScrollBarVisible = false, ScrollBarVisible = false,
@@ -438,13 +441,13 @@ namespace Barotrauma
Stretch = false 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, CanBeFocused = false,
UserData = "extraicons" 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, CanBeFocused = false,
UserData = "soundicons", UserData = "soundicons",
@@ -469,16 +472,6 @@ namespace Barotrauma
Visible = false 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") new GUIButton(new RectTransform(new Point((int)commandButtonAbsoluteHeight), background.RectTransform), style: "CrewListCommandButton")
{ {
ToolTip = TextManager.Get("inputtype.command"), ToolTip = TextManager.Get("inputtype.command"),
@@ -489,6 +482,44 @@ namespace Barotrauma
return true; 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; return background;
} }
@@ -498,6 +529,7 @@ namespace Barotrauma
if (crewList?.Content.GetChildByUserData(character) is { } component) if (crewList?.Content.GetChildByUserData(character) is { } component)
{ {
crewList.RemoveChild(component); crewList.RemoveChild(component);
traitorButtons.RemoveAll(t => t.IsChildOf(component, recursive: true));
} }
} }
@@ -1204,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(); guiFrame.AddToGUIUpdateList();
} }
@@ -1371,7 +1403,8 @@ namespace Barotrauma
if (PlayerInput.KeyDown(InputType.Command) && if (PlayerInput.KeyDown(InputType.Command) &&
(GUI.KeyboardDispatcher.Subscriber == null || (GUI.KeyboardDispatcher.Subscriber is GUIComponent component && (component == crewList || component.IsChildOf(crewList)))) && (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()) if (PlayerInput.IsShiftDown())
{ {
@@ -1556,6 +1589,12 @@ namespace Barotrauma
{ {
if (characterComponent.UserData is Character character) if (characterComponent.UserData is Character character)
{ {
if (character.Removed)
{
characterComponent.Visible = false;
continue;
}
characterComponent.Visible = Character.Controlled == null || Character.Controlled.TeamID == character.TeamID; characterComponent.Visible = Character.Controlled == null || Character.Controlled.TeamID == character.TeamID;
if (character.TeamID == CharacterTeamType.FriendlyNPC && Character.Controlled != null && if (character.TeamID == CharacterTeamType.FriendlyNPC && Character.Controlled != null &&
(character.CurrentHull == Character.Controlled.CurrentHull || Vector2.DistanceSquared(Character.Controlled.WorldPosition, character.WorldPosition) < 500.0f * 500.0f)) (character.CurrentHull == Character.Controlled.CurrentHull || Vector2.DistanceSquared(Character.Controlled.WorldPosition, character.WorldPosition) < 500.0f * 500.0f))
@@ -1632,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( crewArea.RectTransform.AbsoluteOffset = Vector2.SmoothStep(
new Vector2(-crewArea.Rect.Width - HUDLayoutSettings.Padding, 0.0f), new Vector2(-crewArea.Rect.Width - HUDLayoutSettings.Padding, 0.0f),
Vector2.Zero, Vector2.Zero,
@@ -2395,7 +2436,7 @@ namespace Barotrauma
if (!(GetTargetSubmarine() is { } sub)) { return; } if (!(GetTargetSubmarine() is { } sub)) { return; }
shortcutNodes.Clear(); shortcutNodes.Clear();
var subItems = sub.GetItems(false); 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; float reactorOutput = -reactor.CurrPowerConsumption;
// If player is not an engineer AND the reactor is not powered up AND nobody is using the reactor // If player is not an engineer AND the reactor is not powered up AND nobody is using the reactor
@@ -2414,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 // 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 // --> Create shortcut node for Steer order
if (CanFitMoreNodes() && ShouldDelegateOrder("steer") && IsNonDuplicateOrderPrefab(OrderPrefab.Prefabs["steer"]) && 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) nav.GetComponent<Steering>() is Steering steering && steering.Voltage > steering.MinVoltage)
{ {
var order = new Order(OrderPrefab.Prefabs["steer"], steering.Item, steering); var order = new Order(OrderPrefab.Prefabs["steer"], steering.Item, steering);
@@ -157,11 +157,15 @@ namespace Barotrauma
SlideshowPlayer?.DrawManually(spriteBatch); SlideshowPlayer?.DrawManually(spriteBatch);
if (GUI.DisableHUD || GUI.DisableUpperHUD || ForceMapUI || CoroutineManager.IsCoroutineRunning("LevelTransition")) if (GUI.DisableHUD || GUI.DisableUpperHUD || ForceMapUI ||
CoroutineManager.IsCoroutineRunning("LevelTransition"))
{ {
endRoundButton.Visible = false; endRoundButton.Visible = false;
if (ReadyCheckButton != null) { ReadyCheckButton.Visible = false; } if (ReadyCheckButton != null)
return; {
ReadyCheckButton.Visible = false;
}
return;
} }
if (Submarine.MainSub == null || Level.Loaded == null) { return; } if (Submarine.MainSub == null || Level.Loaded == null) { return; }
@@ -216,11 +220,15 @@ namespace Barotrauma
} }
break; break;
} }
if (Level.IsLoadedOutpost && !ObjectiveManager.AllActiveObjectivesCompleted()) if (Level.IsLoadedOutpost &&
(!ObjectiveManager.AllActiveObjectivesCompleted() && this is not MultiPlayerCampaign))
{ {
allowEndingRound = false; 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 }; endRoundButton.Visible = allowEndingRound && Character.Controlled is { IsIncapacitated: false };
if (endRoundButton.Visible) if (endRoundButton.Visible)
@@ -384,8 +392,11 @@ namespace Barotrauma
protected void TryEndRoundWithFuelCheck(Action onConfirm, Action onReturnToMapScreen) protected void TryEndRoundWithFuelCheck(Action onConfirm, Action onReturnToMapScreen)
{ {
Submarine.MainSub.CheckFuel(); Submarine.MainSub.CheckFuel();
SubmarineInfo nextSub = PendingSubmarineSwitch ?? Submarine.MainSub.Info; bool lowFuel = Submarine.MainSub.Info.LowFuel;
bool lowFuel = nextSub.Name == Submarine.MainSub.Info.Name ? Submarine.MainSub.Info.LowFuel : nextSub.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")))) if (Level.IsLoadedFriendlyOutpost && lowFuel && CargoManager.PurchasedItems.None(i => i.Value.Any(pi => pi.ItemPrefab.Tags.Contains("reactorfuel"))))
{ {
var extraConfirmationBox = var extraConfirmationBox =
@@ -433,11 +444,21 @@ namespace Barotrauma
{ {
GUIMessageBox.MessageBoxes.RemoveAll(mb => mb.UserData is RoundSummary); 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) if (ShowCampaignUI || ForceMapUI)
{ {
CampaignUI?.Update(deltaTime); CampaignUI?.Update(deltaTime);
} }
} }
} }
} }
@@ -284,7 +284,7 @@ namespace Barotrauma
yield return CoroutineStatus.Success; 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; yield return CoroutineStatus.Success;
} }
@@ -1014,9 +1014,9 @@ namespace Barotrauma
public void LoadState(string filePath) public void LoadState(string filePath)
{ {
DebugConsole.Log($"Loading save file for an existing game session ({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); XDocument doc = XMLExtensions.TryLoadXml(gamesessionDocPath);
if (doc == null) if (doc == null)
{ {
@@ -368,7 +368,7 @@ namespace Barotrauma
yield return CoroutineStatus.Success; 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; NextLevel = newLevel;
bool success = CrewManager.GetCharacters().Any(c => !c.IsDead); 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 // Event history must be registered before ending the round or it will be cleared
GameMain.GameSession.EventManager.RegisterEventHistory(); GameMain.GameSession.EventManager.RegisterEventHistory();
} }
GameMain.GameSession.EndRound("", traitorResults, transitionType); GameMain.GameSession.EndRound("", transitionType);
var continueButton = GameMain.GameSession.RoundSummary?.ContinueButton; var continueButton = GameMain.GameSession.RoundSummary?.ContinueButton;
RoundSummary roundSummary = null; RoundSummary roundSummary = null;
if (GUIMessageBox.VisibleBox?.UserData is RoundSummary) 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) if (ShowCampaignUI || ForceMapUI)
{ {
Character.DisableControls = true; Character.DisableControls = true;
@@ -8,7 +8,7 @@ namespace Barotrauma.Tutorials
{ {
enum AutoPlayVideo { Yes, No }; enum AutoPlayVideo { Yes, No };
enum TutorialSegmentType { MessageBox, InfoBox, Objective }; enum SegmentType { MessageBox, InfoBox, Objective };
sealed class Tutorial sealed class Tutorial
{ {
@@ -108,7 +108,7 @@ namespace Barotrauma.Tutorials
GameMain.GameSession.StartRound(LevelSeed); GameMain.GameSession.StartRound(LevelSeed);
} }
GameMain.GameSession.EventManager.ActiveEvents.Clear(); GameMain.GameSession.EventManager.ClearEvents();
GameMain.GameSession.EventManager.Enabled = true; GameMain.GameSession.EventManager.Enabled = true;
GameMain.GameScreen.Select(); GameMain.GameScreen.Select();
@@ -1,5 +1,4 @@
using Barotrauma.Tutorials; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma namespace Barotrauma
@@ -15,8 +14,6 @@ namespace Barotrauma
public static bool IsTabMenuOpen => GameMain.GameSession?.tabMenu != null; public static bool IsTabMenuOpen => GameMain.GameSession?.tabMenu != null;
public static TabMenu TabMenuInstance => GameMain.GameSession?.tabMenu; public static TabMenu TabMenuInstance => GameMain.GameSession?.tabMenu;
private float prevHudScale;
private TabMenu tabMenu; private TabMenu tabMenu;
public bool ToggleTabMenu() public bool ToggleTabMenu()
@@ -27,7 +24,7 @@ namespace Barotrauma
GameMain.NetLobbyScreen.CharacterAppearanceCustomizationMenu = null; GameMain.NetLobbyScreen.CharacterAppearanceCustomizationMenu = null;
if (GameMain.NetLobbyScreen.JobSelectionFrame != null) { GameMain.NetLobbyScreen.JobSelectionFrame.Visible = false; } 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(); tabMenu = new TabMenu();
HintManager.OnShowTabMenu(); HintManager.OnShowTabMenu();
@@ -49,6 +46,8 @@ namespace Barotrauma
private GUITextBlock respawnInfoText; private GUITextBlock respawnInfoText;
private GUITickBox respawnTickBox; private GUITickBox respawnTickBox;
private GUIImage eventLogNotification;
private void CreateTopLeftButtons() private void CreateTopLeftButtons()
{ {
if (topLeftButtonGroup != null) if (topLeftButtonGroup != null)
@@ -96,7 +95,8 @@ namespace Barotrauma
OnClicked = (button, userData) => ToggleTabMenu() OnClicked = (button, userData) => ToggleTabMenu()
}; };
talentPointNotification = CreateTalentIconNotification(tabMenuButton); talentPointNotification = CreateNotificationIcon(tabMenuButton);
eventLogNotification = CreateNotificationIcon(tabMenuButton);
GameMain.Instance.ResolutionChanged += CreateTopLeftButtons; GameMain.Instance.ResolutionChanged += CreateTopLeftButtons;
@@ -121,7 +121,6 @@ namespace Barotrauma
return true; return true;
} }
}; };
prevHudScale = GameSettings.CurrentConfig.Graphics.HUDScale;
} }
public void AddToGUIUpdateList() 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") 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; 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) public static void UpdateTalentNotificationIndicator(GUIImage indicator)
{ {
if (indicator != null) if (indicator == null) { return; }
{ indicator.Visible =
if (Character.Controlled?.Info == null) Character.Controlled?.Info != null &&
{ Character.Controlled.Info.GetAvailableTalentPoints() > 0 && !Character.Controlled.HasUnlockedAllTalents();
indicator.Visible = false;
}
else
{
indicator.Visible = Character.Controlled.Info.GetAvailableTalentPoints() > 0 && !Character.Controlled.HasUnlockedAllTalents();
}
}
} }
public void HUDScaleChanged() public void HUDScaleChanged()
@@ -1,7 +1,6 @@
using Barotrauma.Extensions; using Barotrauma.Extensions;
using Barotrauma.IO; using Barotrauma.IO;
using Barotrauma.Items.Components; using Barotrauma.Items.Components;
using Barotrauma.Tutorials;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -152,8 +151,8 @@ namespace Barotrauma
} }
// onstartedinteracting.turretperiscope // onstartedinteracting.turretperiscope
if (item.HasTag("periscope") && if (item.HasTag(Tags.Periscope) &&
item.GetConnectedComponents<Turret>().FirstOrDefault(t => t.Item.HasTag("turret")) is Turret) item.GetConnectedComponents<Turret>().FirstOrDefault(t => t.Item.HasTag(Tags.Turret)) is Turret)
{ {
if (DisplayHint($"{hintIdentifierBase}.turretperiscope".ToIdentifier(), if (DisplayHint($"{hintIdentifierBase}.turretperiscope".ToIdentifier(),
variables: new[] 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() private static void CheckIsInteracting()
{ {
if (!CanDisplayHints()) { return; } if (!CanDisplayHints()) { return; }
@@ -182,7 +192,7 @@ namespace Barotrauma
if (Character.Controlled.SelectedItem.GetComponent<Reactor>() is Reactor reactor && reactor.PowerOn && if (Character.Controlled.SelectedItem.GetComponent<Reactor>() is Reactor reactor && reactor.PowerOn &&
Character.Controlled.SelectedItem.OwnInventory?.AllItems is IEnumerable<Item> containedItems && 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; } if (DisplayHint("onisinteracting.reactorwithextrarods".ToIdentifier())) { return; }
} }
@@ -210,7 +220,7 @@ namespace Barotrauma
{ {
if (item.CurrentHull == null) { continue; } if (item.CurrentHull == null) { continue; }
if (item.GetComponent<Pump>() == 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); 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; yield return CoroutineStatus.Success;
} }
} }
public static void OnRoundEnded() public static void OnRoundEnded()
@@ -395,8 +411,8 @@ namespace Barotrauma
if (DisplayHint($"onobtaineditem.{tag}".ToIdentifier())) { return; } if (DisplayHint($"onobtaineditem.{tag}".ToIdentifier())) { return; }
} }
if ((item.HasTag("geneticmaterial") && character.Inventory.FindItemByTag("geneticdevice".ToIdentifier(), recursive: true) != null) || if ((item.HasTag(Tags.GeneticMaterial) && character.Inventory.FindItemByTag(Tags.GeneticMaterial, recursive: true) != null) ||
(item.HasTag("geneticdevice") && character.Inventory.FindItemByTag("geneticmaterial".ToIdentifier(), recursive: true) != null)) (item.HasTag(Tags.GeneticDevice) && character.Inventory.FindItemByTag(Tags.GeneticDevice, recursive: true) != null))
{ {
if (DisplayHint($"geneticmaterial.useinstructions".ToIdentifier())) { return; } 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) public static void OnReactorOutOfFuel(Reactor reactor)
{ {
if (!CanDisplayHints()) { return; } 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) public static void OnAvailableTransition(CampaignMode.TransitionType transitionType)
{ {
if (!CanDisplayHints()) { return; } if (!CanDisplayHints()) { return; }
@@ -556,7 +587,7 @@ namespace Barotrauma
private static void CheckIfDivingGearOutOfOxygen() private static void CheckIfDivingGearOutOfOxygen()
{ {
if (!CanDisplayHints()) { return; } 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?.OwnInventory == null) { return; }
if (divingGear.GetContainedItemConditionPercentage() > 0.0f) { return; } if (divingGear.GetContainedItemConditionPercentage() > 0.0f) { return; }
DisplayHint("ondivinggearoutofoxygen".ToIdentifier(), onUpdate: () => DisplayHint("ondivinggearoutofoxygen".ToIdentifier(), onUpdate: () =>
@@ -599,7 +630,7 @@ namespace Barotrauma
if (adjacentHull.WaterPercentage > 75 && !BallastHulls.Contains(adjacentHull) && DisplayHint("onadjacenthull.highwaterpercentage".ToIdentifier())) { return; } 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); static bool IsOnFriendlySub() => Character.Controlled.Submarine is Submarine sub && (sub.TeamID == Character.Controlled.TeamID || sub.TeamID == CharacterTeamType.FriendlyNPC);
@@ -49,7 +49,7 @@ static class ObjectiveManager
public Identifier ParentId { get; set; } 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) 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) private Segment(Identifier id, Identifier objectiveTextTag, AutoPlayVideo autoPlayVideo, Text textContent = default, Video videoContent = default)
{ {
Id = id; Id = id;
ObjectiveText = TextManager.ParseInputTypes(TextManager.Get(objectiveTextTag)); ObjectiveText = TextManager.ParseInputTypes(TextManager.Get(objectiveTextTag).Fallback(objectiveTextTag.Value));
AutoPlayVideo = autoPlayVideo; AutoPlayVideo = autoPlayVideo;
TextContent = textContent; TextContent = textContent;
VideoContent = videoContent; VideoContent = videoContent;
SegmentType = TutorialSegmentType.InfoBox; SegmentType = SegmentType.InfoBox;
} }
private Segment(Identifier id, Identifier objectiveTextTag, Action onClickObjective) private Segment(Identifier id, Identifier objectiveTextTag, Action onClickObjective)
{ {
Id = id; Id = id;
ObjectiveText = TextManager.ParseInputTypes(TextManager.Get(objectiveTextTag)); ObjectiveText = TextManager.ParseInputTypes(TextManager.Get(objectiveTextTag).Fallback(objectiveTextTag.Value));
OnClickObjective = onClickObjective; OnClickObjective = onClickObjective;
SegmentType = TutorialSegmentType.MessageBox; SegmentType = SegmentType.MessageBox;
} }
private Segment(Identifier id, Identifier objectiveTextTag) private Segment(Identifier id, Identifier objectiveTextTag)
{ {
Id = id; Id = id;
ObjectiveText = TextManager.ParseInputTypes(TextManager.Get(objectiveTextTag)); ObjectiveText = TextManager.ParseInputTypes(TextManager.Get(objectiveTextTag).Fallback(objectiveTextTag.Value));
SegmentType = TutorialSegmentType.Objective; SegmentType = SegmentType.Objective;
} }
public void ConnectMessageBox(Segment messageBoxSegment) public void ConnectMessageBox(Segment messageBoxSegment)
{ {
SegmentType = TutorialSegmentType.MessageBox; SegmentType = SegmentType.MessageBox;
OnClickObjective = messageBoxSegment.OnClickObjective; OnClickObjective = messageBoxSegment.OnClickObjective;
} }
} }
@@ -139,9 +139,9 @@ static class ObjectiveManager
VideoPlayer.AddToGUIUpdateList(order: 100); 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); activeObjectives.Add(segment);
AddToObjectiveList(segment, connectObjective); AddToObjectiveList(segment, connectObjective);
@@ -153,15 +153,15 @@ static class ObjectiveManager
ActiveContentSegment = segment; ActiveContentSegment = segment;
var title = TextManager.Get(segment.Id); var title = TextManager.Get(segment.Id);
LocalizedString tutorialText = TextManager.GetFormatted(segment.TextContent.Tag); LocalizedString text = TextManager.GetFormatted(segment.TextContent.Tag).Fallback(segment.TextContent.Tag.Value);
tutorialText = TextManager.ParseInputTypes(tutorialText); text = TextManager.ParseInputTypes(text);
switch (segment.AutoPlayVideo) switch (segment.AutoPlayVideo)
{ {
case AutoPlayVideo.Yes: case AutoPlayVideo.Yes:
infoBox = CreateInfoFrame( infoBox = CreateInfoFrame(
title, title,
tutorialText, text,
segment.TextContent.Width, segment.TextContent.Width,
segment.TextContent.Height, segment.TextContent.Height,
segment.TextContent.Anchor, segment.TextContent.Anchor,
@@ -171,7 +171,7 @@ static class ObjectiveManager
case AutoPlayVideo.No: case AutoPlayVideo.No:
infoBox = CreateInfoFrame( infoBox = CreateInfoFrame(
title, title,
tutorialText, text,
segment.TextContent.Width, segment.TextContent.Width,
segment.TextContent.Height, segment.TextContent.Height,
segment.TextContent.Anchor, 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) if (GetActiveObjective(segmentId) is not Segment segment || !segment.CanBeCompleted || segment.IsCompleted)
{ {
return; return;
} }
if (!MarkSegmentCompleted(segment)) CompleteSegment(segment, failed: false);
}
public static void FailSegment(Identifier segmentId)
{
if (GetActiveObjective(segmentId) is not Segment segment)
{ {
return; 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) if (GameMain.GameSession?.GameMode is TutorialMode tutorialMode)
{ {
GameAnalyticsManager.AddDesignEvent($"Tutorial:{tutorialMode.Tutorial?.Identifier}:{segmentId}:Completed"); GameAnalyticsManager.AddDesignEvent($"Tutorial:{tutorialMode.Tutorial?.Identifier}:{segment.Id}:{(failed ? "Failed" : "Completed")}");
}
else if (GameMain.GameSession?.GameMode is CampaignMode campaign)
{
GameAnalyticsManager.AddDesignEvent($"Tutorial:CampaignMode:{segmentId}:Completed");
campaign?.CampaignMetadata?.SetValue(segmentId, true);
} }
} }
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; segment.IsCompleted = true;
if (GUIStyle.GetComponentStyle("ObjectiveIndicatorCompleted") is GUIComponentStyle style) if (GUIStyle.GetComponentStyle(iconStyleName) is GUIComponentStyle style)
{ {
if (segment.ObjectiveStateIndicator.Style == style) if (segment.ObjectiveStateIndicator.Style == style)
{ {
@@ -216,21 +239,17 @@ static class ObjectiveManager
} }
if (flash) 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.OnClicked = null;
segment.ObjectiveButton.CanBeFocused = false; segment.ObjectiveButton.CanBeFocused = false;
return true; return true;
} }
public static void RemoveTutorialSegment(Identifier segmentId) public static void RemoveSegment(Identifier segmentId)
{ {
if (GetActiveObjective(segmentId) is not Segment segment) 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; return;
} }
segment.ObjectiveStateIndicator.FadeOut(ObjectiveComponentAnimationTime, false); segment.ObjectiveStateIndicator.FadeOut(ObjectiveComponentAnimationTime, false);
@@ -400,10 +419,10 @@ static class ObjectiveManager
void SetButtonBehavior(Segment segment) 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) => segment.ObjectiveButton.OnClicked = (GUIButton btn, object userdata) =>
{ {
if (segment.SegmentType == TutorialSegmentType.InfoBox) if (segment.SegmentType == SegmentType.InfoBox)
{ {
if (segment.AutoPlayVideo == AutoPlayVideo.Yes) if (segment.AutoPlayVideo == AutoPlayVideo.Yes)
{ {
@@ -414,7 +433,7 @@ static class ObjectiveManager
ShowSegmentText(segment); ShowSegmentText(segment);
} }
} }
else if (segment.SegmentType == TutorialSegmentType.MessageBox) else if (segment.SegmentType == SegmentType.MessageBox)
{ {
segment.OnClickObjective?.Invoke(); segment.OnClickObjective?.Invoke();
} }
@@ -438,8 +457,8 @@ static class ObjectiveManager
ContentRunning = true; ContentRunning = true;
ActiveContentSegment = segment; ActiveContentSegment = segment;
infoBox = CreateInfoFrame( infoBox = CreateInfoFrame(
TextManager.Get(segment.Id), TextManager.Get(segment.Id).Fallback(segment.Id.Value),
TextManager.Get(segment.TextContent.Tag), TextManager.Get(segment.TextContent.Tag).Fallback(segment.TextContent.Tag.Value),
segment.TextContent.Width, segment.TextContent.Width,
segment.TextContent.Height, segment.TextContent.Height,
segment.TextContent.Anchor, segment.TextContent.Anchor,
@@ -10,12 +10,12 @@ namespace Barotrauma
{ {
internal partial class ReadyCheck 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()), ("[ready]", ready.ToString()),
("[total]", total.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"); 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); Vector2 relativeSize = new Vector2(0.2f / GUI.AspectRatioAdjustment, 0.15f);
Point minSize = new Point(300, 200); 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); 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 }; 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 }; 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); 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 }; 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 }; 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; 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) 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 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 resultsBox.Buttons[0].OnClicked = delegate
@@ -214,10 +216,7 @@ namespace Barotrauma
case ReadyCheckState.Update: case ReadyCheckState.Update:
ReadyStatus newState = (ReadyStatus)inc.ReadByte(); ReadyStatus newState = (ReadyStatus)inc.ReadByte();
byte targetId = inc.ReadByte(); byte targetId = inc.ReadByte();
if (crewManager.ActiveReadyCheck != null) crewManager.ActiveReadyCheck?.UpdateState(targetId, newState);
{
crewManager.ActiveReadyCheck?.UpdateState(targetId, newState);
}
break; break;
case ReadyCheckState.End: case ReadyCheckState.End:
ushort count = inc.ReadUInt16(); ushort count = inc.ReadUInt16();
@@ -242,7 +241,7 @@ namespace Barotrauma
int readyCount = Clients.Count(static pair => pair.Value == ReadyStatus.Yes); int readyCount = Clients.Count(static pair => pair.Value == ReadyStatus.Yes);
int totalCount = Clients.Count; 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) 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 == null || resultsBox.Closed || !GUIMessageBox.MessageBoxes.Contains(resultsBox)) { return; }
if (resultsBox.Content.FindChild(UserListData) is not GUIListBox userList) { return; } if (resultsBox.Content.FindChild(UserListData) is not GUIListBox userList) { return; }
// for some reason FindChild doesn't work here? var child = userList.Content.FindChild(id);
foreach (GUIComponent child in userList.Content.Children) 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; } case ReadyStatus.Yes:
style = "MissionCompletedIcon";
if (child.GetChild<GUILayoutGroup>().FindChild(ReadySpriteData) is not GUIImage image) { continue; } break;
case ReadyStatus.No:
string style; style = "MissionFailedIcon";
switch (status) break;
{ default:
case ReadyStatus.Yes: return;
style = "MissionCompletedIcon";
break;
case ReadyStatus.No:
style = "MissionFailedIcon";
break;
default:
return;
}
image.ApplyStyle(GUIStyle.GetComponentStyle(style));
} }
image.ApplyStyle(GUIStyle.GetComponentStyle(style));
} }
private static void SendState(ReadyStatus status) private static void SendState(ReadyStatus status)
@@ -303,7 +299,7 @@ namespace Barotrauma
return; 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.Buttons[0].OnClicked = delegate
{ {
msgBox.Close(); msgBox.Close();
@@ -10,6 +10,9 @@ namespace Barotrauma
{ {
class RoundSummary class RoundSummary
{ {
private float crewListAnimDelay = 0.25f;
private float missionIconAnimDelay;
private const float jobColumnWidthPercentage = 0.11f; private const float jobColumnWidthPercentage = 0.11f;
private const float characterColumnWidthPercentage = 0.44f; private const float characterColumnWidthPercentage = 0.44f;
private const float statusColumnWidthPercentage = 0.45f; 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 singleplayer = GameMain.NetworkMember == null;
bool gameOver = bool gameOver =
@@ -70,7 +73,7 @@ namespace Barotrauma
//crew panel ------------------------------------------------------------------------------- //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"); 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)) 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); TextManager.Get("crew"), textAlignment: Alignment.TopLeft, font: GUIStyle.SubHeadingFont);
crewHeader.RectTransform.MinSize = new Point(0, GUI.IntScale(crewHeader.Rect.Height * 2.0f)); 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 //another crew frame for the 2nd team in combat missions
if (gameSession.Missions.Any(m => m is CombatMission)) 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), var crewHeader2 = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), crewContent2.RectTransform),
CombatMission.GetTeamName(CharacterTeamType.Team2), textAlignment: Alignment.TopLeft, font: GUIStyle.SubHeadingFont); CombatMission.GetTeamName(CharacterTeamType.Team2), textAlignment: Alignment.TopLeft, font: GUIStyle.SubHeadingFont);
crewHeader2.RectTransform.MinSize = new Point(0, GUI.IntScale(crewHeader2.Rect.Height * 2.0f)); 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 ------------------------------------------------------------------------------- //header -------------------------------------------------------------------------------
@@ -111,71 +121,6 @@ namespace Barotrauma
headerText, textAlignment: Alignment.BottomLeft, font: GUIStyle.LargeFont, wrap: true); 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 ------------------------------------------------------------------------------- //reputation panel -------------------------------------------------------------------------------
var campaignMode = gameMode as CampaignMode; var campaignMode = gameMode as CampaignMode;
@@ -185,7 +130,7 @@ namespace Barotrauma
rightPanels.Add(reputationframe); 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"); 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 Stretch = true
}; };
@@ -199,15 +144,15 @@ namespace Barotrauma
//mission panel ------------------------------------------------------------------------------- //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)) GUILayoutGroup missionFrameContent = new GUILayoutGroup(new RectTransform(new Point(missionframe.Rect.Width - padding * 2, missionframe.Rect.Height - padding * 2), missionframe.RectTransform, Anchor.Center))
{ {
Stretch = true, 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"); 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 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)) 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; missionList.ContentBackground.Color = Color.Transparent;
@@ -255,120 +193,76 @@ namespace Barotrauma
{ {
CanBeFocused = false 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) var textContent = new List<LocalizedString>();
{
RelativeSpacing = 0.025f,
Stretch = true
};
LocalizedString missionMessage = if (selectedMissions.Contains(mission))
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)
{ {
Color = displayedMission.Prefab.IconColor, textContent.Add(mission.Completed ? mission.SuccessMessage : mission.FailureMessage);
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);
}
var missionTextContent = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 1.0f), missionContentHorizontal.RectTransform)) var repText = mission.GetReputationRewardText();
{ if (!repText.IsNullOrEmpty()) { textContent.Add(repText); }
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 missionDescription = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), int totalReward = mission.GetFinalReward(Submarine.MainSub);
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);
if (totalReward > 0) if (totalReward > 0)
{ {
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), RichString.Rich(displayedMission.GetMissionRewardText(Submarine.MainSub))); textContent.Add(mission.GetMissionRewardText(Submarine.MainSub));
if (GameMain.IsMultiplayer && Character.Controlled is { } controlled && displayedMission.Completed) 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)); var (share, percentage, _) = Mission.GetRewardShare(controlled.Wallet.RewardDistribution, GameSession.GetSessionCrewCharacters(CharacterType.Player).Where(c => c != controlled), Option<int>.Some(totalReward));
if (share > 0) if (share > 0)
{ {
string shareFormatted = string.Format(CultureInfo.InvariantCulture, "{0:N0}", share); string shareFormatted = string.Format(CultureInfo.InvariantCulture, "{0:N0}", share);
RichString yourShareString = RichString.Rich(TextManager.GetWithVariables("crewwallet.missionreward.get", ("[money]", $"{shareFormatted}"), ("[share]", $"{percentage}"))); RichString yourShareString = TextManager.GetWithVariables("crewwallet.missionreward.get", ("[money]", $"{shareFormatted}"), ("[share]", $"{percentage}"));
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), yourShareString); textContent.Add(yourShareString);
} }
} }
} }
} }
else
if (displayedMission != missionsToDisplay.Last())
{ {
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); var repText = mission.GetReputationRewardText();
new GUIFrame(new RectTransform(new Vector2(0.8f, 1.0f), spacing.RectTransform, Anchor.Center) { RelativeOffset = new Vector2(0.1f, 0.0f) }, "HorizontalLine"); 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()) 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, 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); GUIImage missionIcon = new GUIImage(new RectTransform(new Point(missionContentHorizontal.Rect.Height), missionContentHorizontal.RectTransform), style: "NoMissionIcon", scaleToFit: true);
missionIcon.RectTransform.MinSize = new Point((int)(missionContentHorizontal.Rect.Height * 0.7f));
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionContentHorizontal.RectTransform), new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionContentHorizontal.RectTransform),
TextManager.Get("nomission"), font: GUIStyle.LargeFont); TextManager.Get("nomission"), font: GUIStyle.LargeFont);
} }
/*missionContentHorizontal.Recalculate(); gameSession?.EventManager?.EventLog?.CreateEventLogUI(missionList.Content, traitorResults);
missionContent.Recalculate();
missionIcon.RectTransform.MinSize = new Point(0, missionContentHorizontal.Rect.Height); AddSeparators(missionList.Content);
missionTextContent.RectTransform.MaxSize = new Point(int.MaxValue, missionIcon.Rect.Width);*/
ContinueButton = new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), ButtonArea.RectTransform), TextManager.Get("Close")); 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); ButtonArea.RectTransform.NonScaledSize = new Point(ButtonArea.Rect.Width, ContinueButton.Rect.Height);
@@ -405,10 +299,7 @@ namespace Barotrauma
public void CreateReputationInfoPanel(GUIComponent parent, CampaignMode campaignMode) public void CreateReputationInfoPanel(GUIComponent parent, CampaignMode campaignMode)
{ {
GUIListBox reputationList = new GUIListBox(new RectTransform(Vector2.One, parent.RectTransform)) GUIListBox reputationList = new GUIListBox(new RectTransform(Vector2.One, parent.RectTransform));
{
Padding = new Vector4(4, 10, 0, 0) * GUI.Scale
};
reputationList.ContentBackground.Color = Color.Transparent; reputationList.ContentBackground.Color = Color.Transparent;
foreach (Faction faction in campaignMode.Factions.OrderBy(f => f.Prefab.MenuOrder).ThenBy(f => f.Prefab.Name)) 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) private LocalizedString GetHeaderText(bool gameOver, CampaignMode.TransitionType transitionType)
{ {
string locationName = Submarine.MainSub is { AtEndExit: true } ? endLocation?.Name : startLocation?.Name; 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)); 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) 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); headerFrame.RectTransform.RelativeSize -= new Vector2(crewList.ScrollBar.RectTransform.RelativeSize.X, 0.0f);
float delay = crewListAnimDelay;
foreach (CharacterInfo characterInfo in characterInfos) foreach (CharacterInfo characterInfo in characterInfos)
{ {
if (characterInfo == null) { continue; } if (characterInfo == null) { continue; }
CreateCharacterElement(characterInfo, crewList); CreateCharacterElement(characterInfo, crewList, traitorResults, delay);
delay += crewListAnimDelay;
} }
missionIconAnimDelay = delay;
return crewList; 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") 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), 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); 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 name, Reputation reputation, float initialReputation,
LocalizedString shortDescription, LocalizedString fullDescription, Sprite icon, Sprite backgroundPortrait, Color iconColor) LocalizedString shortDescription, LocalizedString fullDescription, Sprite icon, Sprite backgroundPortrait, Color iconColor)
{ {
@@ -1,5 +1,6 @@
using Barotrauma.Extensions; using Barotrauma.Extensions;
using Barotrauma.Items.Components; using Barotrauma.Items.Components;
using Barotrauma.Networking;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Input;
@@ -42,16 +43,29 @@ namespace Barotrauma
if (limbSlotIcons == null) if (limbSlotIcons == null)
{ {
limbSlotIcons = new Dictionary<InvSlotType, Sprite>(); limbSlotIcons = new Dictionary<InvSlotType, Sprite>();
int margin = 2; foreach (InvSlotType invSlotType in Enum.GetValues(typeof(InvSlotType)))
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))); var sprite = GUIStyle.GetComponentStyle($"InventorySlot.{invSlotType}")?.GetDefaultSprite();
limbSlotIcons.Add(InvSlotType.Card, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(640 + margin, 128 + margin, 128 - margin * 2, 128 - margin * 2))); 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))); int margin = 2;
limbSlotIcons.Add(InvSlotType.LeftHand, new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(634, 0, 128, 128))); AddIfMissing(InvSlotType.Headset, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(384 + margin, 128 + margin, 128 - margin * 2, 128 - margin * 2)));
limbSlotIcons.Add(InvSlotType.RightHand, new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(762, 0, 128, 128))); AddIfMissing(InvSlotType.InnerClothes, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(512 + margin, 128 + margin, 128 - margin * 2, 128 - margin * 2)));
limbSlotIcons.Add(InvSlotType.OuterClothes, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(256 + 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)));
limbSlotIcons.Add(InvSlotType.Bag, new Sprite("Content/UI/CommandUIAtlas.png", new Rectangle(639, 926, 128,80))); 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; return limbSlotIcons;
} }
@@ -179,11 +193,30 @@ namespace Barotrauma
BackgroundFrame = frame; 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 (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) if (layout == Layout.Default)
{ {
@@ -216,6 +249,11 @@ namespace Barotrauma
return false; return false;
} }
public void RefreshSlotPositions()
{
SetSlotPositions(CurrentLayout);
}
private void SetSlotPositions(Layout layout) private void SetSlotPositions(Layout layout)
{ {
int spacing = GUI.IntScale(5); 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)))); ((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 //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); 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.Clear();
tempHighlightedSubInventorySlots.AddRange(highlightedSubInventorySlots); tempHighlightedSubInventorySlots.AddRange(highlightedSubInventorySlots);
foreach (var highlightedSubInventorySlot in tempHighlightedSubInventorySlots) foreach (var highlightedSubInventorySlot in tempHighlightedSubInventorySlots)
@@ -525,6 +566,7 @@ namespace Barotrauma
var itemContainer = item.GetComponent<ItemContainer>(); var itemContainer = item.GetComponent<ItemContainer>();
if (itemContainer != null && if (itemContainer != null &&
itemContainer.KeepOpenWhenEquippedBy(character) && itemContainer.KeepOpenWhenEquippedBy(character) &&
!DraggingItems.Contains(item) &&
character.CanAccessInventory(itemContainer.Inventory) && character.CanAccessInventory(itemContainer.Inventory) &&
!highlightedSubInventorySlots.Any(s => s.Inventory == itemContainer.Inventory)) !highlightedSubInventorySlots.Any(s => s.Inventory == itemContainer.Inventory))
{ {
@@ -538,9 +580,11 @@ namespace Barotrauma
if (doubleClickedItems.Any()) if (doubleClickedItems.Any())
{ {
var quickUseAction = GetQuickUseAction(doubleClickedItems.First(), true, true, true); var quickUseAction = GetQuickUseAction(doubleClickedItems.First(), true, true, true);
int itemCount = 0;
foreach (Item doubleClickedItem in doubleClickedItems) foreach (Item doubleClickedItem in doubleClickedItems)
{ {
QuickUseItem(doubleClickedItem, true, true, true, quickUseAction, playSound: doubleClickedItem == doubleClickedItems.First()); 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 //only use one item if we're equipping or using it as a treatment
if (quickUseAction == QuickUseAction.Equip || quickUseAction == QuickUseAction.UseTreatment) if (quickUseAction == QuickUseAction.Equip || quickUseAction == QuickUseAction.UseTreatment)
{ {
@@ -556,6 +600,12 @@ namespace Barotrauma
{ {
break; 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; return QuickUseAction.TakeFromCharacter;
} }
else if (character.HeldItems.Any(i => else if (character.HeldItems.FirstOrDefault(i =>
i.OwnInventory != null && 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; return QuickUseAction.PutToEquippedItem;
} }
else if (allowEquip) //doubleclicked and no other inventory is selected else if (allowEquip) //doubleclicked and no other inventory is selected
@@ -1171,5 +1241,11 @@ namespace Barotrauma
GUIComponent.DrawToolTip(spriteBatch, highlightedQuickUseSlot.QuickUseButtonToolTip, highlightedQuickUseSlot.EquipButtonRect); GUIComponent.DrawToolTip(spriteBatch, highlightedQuickUseSlot.QuickUseButtonToolTip, highlightedQuickUseSlot.EquipButtonRect);
} }
} }
public void ClientEventWrite(IWriteMessage msg, Character.InventoryStateEventData extraData)
{
SharedWrite(msg, extraData.SlotRange);
syncItemsDelay = 1.0f;
}
} }
} }
@@ -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) if (brokenSprite == null)
{ {
//broken doors turn black if no broken sprite has been configured //broken doors turn black if no broken sprite has been configured
@@ -202,7 +202,7 @@ namespace Barotrauma.Items.Components
weldSpritePos.Y = -weldSpritePos.Y; weldSpritePos.Y = -weldSpritePos.Y;
weldedSprite.Draw(spriteBatch, 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; } if (openState >= 1.0f) { return; }
@@ -8,7 +8,7 @@ namespace Barotrauma.Items.Components
{ {
public Vector2 DrawSize => Vector2.Zero; 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; } if (!editing) { return; }
@@ -13,7 +13,7 @@ namespace Barotrauma.Items.Components
get { return item.Rect.Size.ToVector2(); } 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) 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); 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, spriteBatch,
new Vector2(attachPos.X, -attachPos.Y), new Vector2(attachPos.X, -attachPos.Y),
item.SpriteColor * 0.5f, item.SpriteColor * 0.5f,
@@ -1,11 +1,8 @@
using System; using Barotrauma.IO;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.Linq; using System.Linq;
using System.Xml.Linq;
using Barotrauma.Extensions;
using Barotrauma.IO;
namespace Barotrauma.Items.Components namespace Barotrauma.Items.Components
{ {
@@ -48,33 +45,36 @@ namespace Barotrauma.Items.Components
return; 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; } foreach (ContentXElement limbElement in limbElements)
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)))
{ {
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;
} }
break;
} }
if (characterInfo.Wearables != null) if (characterInfo.Wearables != null)
@@ -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; currentCrossHairScale = currentCrossHairPointerScale = cam == null ? 1.0f : cam.Zoom;
if (crosshairSprite != null) if (crosshairSprite != null)
@@ -118,6 +118,7 @@ namespace Barotrauma.Items.Components
else if (chargeSoundChannel != null) else if (chargeSoundChannel != null)
{ {
chargeSoundChannel.FrequencyMultiplier = MathHelper.Lerp(0.5f, 1.5f, chargeRatio); chargeSoundChannel.FrequencyMultiplier = MathHelper.Lerp(0.5f, 1.5f, chargeRatio);
chargeSoundChannel.Position = new Vector3(item.WorldPosition, 0.0f);
} }
break; break;
default: default:
@@ -51,7 +51,7 @@ namespace Barotrauma.Items.Components
private int spraySetting = 0; private int spraySetting = 0;
private readonly Point[] sprayArray = new Point[8]; 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; if (character == null || !character.IsKeyDown(InputType.Aim)) return;
@@ -130,7 +130,7 @@ namespace Barotrauma.Items.Components
if (body.UserData is Item item) if (body.UserData is Item item)
{ {
var door = item.GetComponent<Door>(); var door = item.GetComponent<Door>();
if (door != null && door.CanBeTraversed) { continue; } if (door != null && (door.IsOpen || door.IsBroken)) { continue; }
} }
targetHull = null; 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 DEBUG
if (GameMain.DebugDraw && Character.Controlled != null && Character.Controlled.IsKeyDown(InputType.Aim)) if (GameMain.DebugDraw && Character.Controlled != null && Character.Controlled.IsKeyDown(InputType.Aim))
@@ -150,7 +150,9 @@ namespace Barotrauma.Items.Components
public GUIFrame GuiFrame { get; set; } public GUIFrame GuiFrame { get; set; }
public bool LockGuiFramePosition; private GUIDragHandle guiFrameDragHandle;
private bool guiFrameUpdatePending;
[Serialize(false, IsPropertySaveable.No)] [Serialize(false, IsPropertySaveable.No)]
public bool AllowUIOverlap public bool AllowUIOverlap
@@ -466,7 +468,21 @@ namespace Barotrauma.Items.Components
GuiFrame?.AddToGUIUpdateList(order: order); 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) { } public virtual void UpdateEditing(float deltaTime) { }
@@ -572,6 +588,7 @@ namespace Barotrauma.Items.Components
} }
string style = GuiFrameSource.Attribute("style") == null ? null : GuiFrameSource.GetAttributeString("style", ""); string style = GuiFrameSource.Attribute("style") == null ? null : GuiFrameSource.GetAttributeString("style", "");
GuiFrame = new GUIFrame(RectTransform.Load(GuiFrameSource, GUI.Canvas, Anchor.Center), style, color); GuiFrame = new GUIFrame(RectTransform.Load(GuiFrameSource, GUI.Canvas, Anchor.Center), style, color);
GuiFrame.RectTransform.ScreenSpaceOffset = GuiFrameOffset;
TryCreateDragHandle(); TryCreateDragHandle();
@@ -583,24 +600,25 @@ namespace Barotrauma.Items.Components
GameMain.Instance.ResolutionChanged += OnResolutionChangedPrivate; GameMain.Instance.ResolutionChanged += OnResolutionChangedPrivate;
} }
protected virtual void TryCreateDragHandle() protected void TryCreateDragHandle()
{ {
if (GuiFrame != null && GuiFrameSource.GetAttributeBool("draggable", true)) if (GuiFrame != null && GuiFrameSource.GetAttributeBool("draggable", true))
{ {
bool hideDragIcons = GuiFrameSource.GetAttributeBool("hidedragicons", false); 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) GuiFrame.RectTransform, style: null)
{ {
Enabled = !LockGuiFramePosition,
DragArea = HUDLayoutSettings.ItemHUDArea DragArea = HUDLayoutSettings.ItemHUDArea
}; };
int iconHeight = GUIStyle.ItemFrameMargin.Y / 4; 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"); style: "GUIDragIndicatorHorizontal");
dragIcon.RectTransform.MinSize = new Point(0, iconHeight); dragIcon.RectTransform.MinSize = new Point(0, iconHeight);
handle.ValidatePosition = (RectTransform rectT) => guiFrameDragHandle.ValidatePosition = (RectTransform rectT) =>
{ {
var activeHuds = Character.Controlled?.SelectedItem?.ActiveHUDs ?? item.ActiveHUDs; var activeHuds = Character.Controlled?.SelectedItem?.ActiveHUDs ?? item.ActiveHUDs;
foreach (ItemComponent ic in 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 //refresh slots to ensure they're rendered at the correct position
(ic as ItemContainer)?.Inventory.CreateSlots(); (ic as ItemContainer)?.Inventory.CreateSlots();
} }
GuiFrameOffset = GuiFrame.RectTransform.ScreenSpaceOffset;
guiFrameUpdatePending = true;
return true; return true;
}; };
int buttonHeight = (int)(GUIStyle.ItemFrameMargin.Y * 0.4f); 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") style: "GUIButtonSettings")
{ {
OnClicked = (btn, userdata) => OnClicked = (btn, userdata) =>
@@ -636,6 +656,14 @@ namespace Barotrauma.Items.Components
GUIContextMenu.CreateContextMenu( GUIContextMenu.CreateContextMenu(
new ContextMenuOption("item.resetuiposition", isEnabled: true, onSelected: () => 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) if (Character.Controlled?.SelectedItem != null && item != Character.Controlled.SelectedItem)
{ {
Character.Controlled.SelectedItem.ForceHUDLayoutUpdate(ignoreLocking: true); 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: () => new ContextMenuOption(TextManager.Get(LockGuiFramePosition ? "item.unlockuiposition" : "item.lockuiposition"), isEnabled: true, onSelected: () =>
{ {
LockGuiFramePosition = !LockGuiFramePosition; 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; return true;
} }
@@ -1,9 +1,9 @@
using Microsoft.Xna.Framework; using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System; using System;
using System.Collections; using System.Collections.Generic;
using System.Linq; using System.Linq;
using static Barotrauma.Inventory;
namespace Barotrauma.Items.Components namespace Barotrauma.Items.Components
{ {
@@ -97,6 +97,8 @@ namespace Barotrauma.Items.Components
partial void InitProjSpecific(ContentXElement element) partial void InitProjSpecific(ContentXElement element)
{ {
slotIcons = new Sprite[capacity]; slotIcons = new Sprite[capacity];
int currCapacity = MainContainerCapacity;
foreach (var subElement in element.Elements()) foreach (var subElement in element.Elements())
{ {
switch (subElement.Name.ToString().ToLowerInvariant()) switch (subElement.Name.ToString().ToLowerInvariant())
@@ -127,6 +129,19 @@ namespace Barotrauma.Items.Components
} }
} }
break; 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(); LocalizedString labelText = GetUILabel();
GUITextBlock label = null; GUITextBlock label = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform, Anchor.TopCenter),
if (!labelText.IsNullOrEmpty()) 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), AbsoluteSpacing = margin.X / 2
labelText, font: GUIStyle.SubHeadingFont, textAlignment: Alignment.Center, wrap: true); };
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; float minInventoryAreaSize = 0.5f;
@@ -215,6 +259,71 @@ namespace Barotrauma.Items.Components
Inventory.RectTransform = guiCustomComponent.RectTransform; 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() public LocalizedString GetUILabel()
{ {
if (UILabel == string.Empty) { return string.Empty; } if (UILabel == string.Empty) { return string.Empty; }
@@ -277,7 +386,7 @@ namespace Barotrauma.Items.Components
int ignoredItemCount = 0; int ignoredItemCount = 0;
var subContainableItems = AllSubContainableItems; var subContainableItems = AllSubContainableItems;
float targetSlotCapacity = GetMaxStackSize(targetSlot); float targetSlotCapacity = Math.Min(containedItem.Prefab.MaxStackSize, GetMaxStackSize(targetSlot));
float capacity = targetSlotCapacity * MainContainerCapacity; float capacity = targetSlotCapacity * MainContainerCapacity;
if (subContainableItems != null) if (subContainableItems != null)
{ {
@@ -310,29 +419,36 @@ namespace Barotrauma.Items.Components
int itemCount = Inventory.AllItems.Count() - ignoredItemCount; int itemCount = Inventory.AllItems.Count() - ignoredItemCount;
return Math.Min(itemCount / Math.Max(capacity, 1), 1); 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 else
{ {
if (containedItem != null && (Inventory.Capacity == 1 || HasSubContainers)) return Inventory.EmptySlotCount / (float)Inventory.Capacity;
{ }
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;
}
} }
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; } 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 transformedItemPos = ItemPos * item.Scale;
Vector2 transformedItemInterval = ItemInterval * item.Scale; Vector2 transformedItemInterval = ItemInterval * item.Scale;
@@ -388,7 +504,7 @@ namespace Barotrauma.Items.Components
bool isWiringMode = SubEditorScreen.TransparentWiringMode && SubEditorScreen.IsWiringMode(); bool isWiringMode = SubEditorScreen.TransparentWiringMode && SubEditorScreen.IsWiringMode();
int i = 0; int i = 0;
foreach (DrawableContainedItem contained in drawableContainedItems) foreach (ContainedItem contained in containedItems)
{ {
Vector2 itemPos = currentItemPos; Vector2 itemPos = currentItemPos;
@@ -466,7 +582,7 @@ namespace Barotrauma.Items.Components
contained.Item.Sprite.Draw( contained.Item.Sprite.Draw(
spriteBatch, spriteBatch,
new Vector2(itemPos.X, -itemPos.Y), 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, origin,
-(contained.Item.body == null ? 0.0f : contained.Item.body.DrawRotation), -(contained.Item.body == null ? 0.0f : contained.Item.body.DrawRotation),
contained.Item.Scale, contained.Item.Scale,
@@ -476,7 +592,7 @@ namespace Barotrauma.Items.Components
foreach (ItemContainer ic in contained.Item.GetComponents<ItemContainer>()) foreach (ItemContainer ic in contained.Item.GetComponents<ItemContainer>())
{ {
if (ic.hideItems) { continue; } if (ic.hideItems) { continue; }
ic.DrawContainedItems(spriteBatch, containedSpriteDepth); ic.DrawContainedItems(spriteBatch, containedSpriteDepth, overrideColor);
} }
i++; 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 (!item.IsInteractable(character)) { return; }
if (Inventory.RectTransform != null) 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 //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 //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; } if (!guiCustomComponent.Visible) { return; }
Inventory.Update(deltaTime, cam); 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);
}*/
} }
} }
@@ -311,7 +311,7 @@ namespace Barotrauma.Items.Components
prevRect = item.Rect; 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 (item.ParentInventory != null) { return; }
if (editing) if (editing)
@@ -19,13 +19,14 @@ namespace Barotrauma.Items.Components
private Sprite backgroundSprite; 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; } if (backgroundSprite == null) { return; }
backgroundSprite.DrawTiled(spriteBatch, 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(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, textureScale: Vector2.One * item.Scale,
depth: BackgroundSpriteDepth); depth: BackgroundSpriteDepth);
} }
@@ -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 (Light?.LightSprite == null) { return; }
if ((item.body == null || item.body.Enabled) && lightBrightness > 0.0f && IsOn && Light.Enabled) if ((item.body == null || item.body.Enabled) && lightBrightness > 0.0f && IsOn && Light.Enabled)
@@ -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; inSufficientPowerWarning.Visible = IsActive && !hasPower;
} }
@@ -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; powerIndicator.Selected = hasPower && IsActive;
autoControlIndicator.Selected = controlLockTimer > 0.0f; 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) if (propellerSprite != null)
{ {
Vector2 drawPos = item.DrawPosition; Vector2 drawPos = item.DrawPosition;
drawPos += PropellerPos; drawPos += PropellerPos;
drawPos.Y = -drawPos.Y; 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) if (editing && !DisablePropellerDamage && propellerDamage != null && !GUI.DisableHUD)
@@ -38,6 +38,11 @@ namespace Barotrauma.Items.Components
} }
private FabricationRecipe selectedItem; 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; public Identifier SelectedItemIdentifier => SelectedItem?.TargetItem.Identifier ?? Identifier.Empty;
private GUIComponent inSufficientPowerWarning; private GUIComponent inSufficientPowerWarning;
@@ -358,9 +363,11 @@ namespace Barotrauma.Items.Components
if (inputInventoryHolder != null) if (inputInventoryHolder != null)
{ {
inputContainer.AllowUIOverlap = true; inputContainer.AllowUIOverlap = true;
inputContainer.Inventory.DrawWhenEquipped = true;
inputContainer.Inventory.RectTransform = inputInventoryHolder.RectTransform; inputContainer.Inventory.RectTransform = inputInventoryHolder.RectTransform;
} }
outputContainer.AllowUIOverlap = true; outputContainer.AllowUIOverlap = true;
outputContainer.Inventory.DrawWhenEquipped = true;
outputContainer.Inventory.RectTransform = outputInventoryHolder.RectTransform; outputContainer.Inventory.RectTransform = outputInventoryHolder.RectTransform;
} }
@@ -453,13 +460,8 @@ namespace Barotrauma.Items.Components
requiresRecipeText.RectTransform.RepositionChildInHierarchy(itemList.Content.RectTransform.GetChildIndex(firstRequiresRecipe.RectTransform)); requiresRecipeText.RectTransform.RepositionChildInHierarchy(itemList.Content.RectTransform.GetChildIndex(firstRequiresRecipe.RectTransform));
} }
FilterEntities(selectedItemCategory, itemFilterBox?.Text ?? string.Empty);
HideEmptyItemListCategories(); 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>(); private readonly Dictionary<FabricationRecipe.RequiredItem, int> missingIngredientCounts = new Dictionary<FabricationRecipe.RequiredItem, int>();
@@ -538,6 +540,8 @@ namespace Barotrauma.Items.Components
int slotIndex = 0; int slotIndex = 0;
foreach (var kvp in missingIngredientCounts) foreach (var kvp in missingIngredientCounts)
{ {
if (inputContainer.Inventory?.visualSlots == null) { break; }
var requiredItem = kvp.Key; var requiredItem = kvp.Key;
int missingCount = kvp.Value; int missingCount = kvp.Value;
@@ -560,11 +564,10 @@ namespace Barotrauma.Items.Components
var requiredItemPrefab = requiredItem.FirstMatchingPrefab; var requiredItemPrefab = requiredItem.FirstMatchingPrefab;
float iconAlpha = 0.0f; float iconAlpha = 0.0f;
ItemPrefab requiredItemToDisplay; ItemPrefab requiredItemToDisplay = requiredItem.DefaultItem.IsEmpty ? null : requiredItem.ItemPrefabs.FirstOrDefault(p => p.Identifier == requiredItem.DefaultItem);
int count = requiredItem.ItemPrefabs.Count(); if (requiredItemToDisplay == null && requiredItem.ItemPrefabs.Multiple())
if (count > 1)
{ {
float iconCycleSpeed = 0.5f / count; float iconCycleSpeed = 0.75f;
float iconCycleT = (float)Timing.TotalTime * iconCycleSpeed; float iconCycleT = (float)Timing.TotalTime * iconCycleSpeed;
int iconIndex = (int)(iconCycleT % requiredItem.ItemPrefabs.Count()); int iconIndex = (int)(iconCycleT % requiredItem.ItemPrefabs.Count());
@@ -573,7 +576,7 @@ namespace Barotrauma.Items.Components
} }
else else
{ {
requiredItemToDisplay = requiredItem.ItemPrefabs.FirstOrDefault(); requiredItemToDisplay ??= requiredItem.ItemPrefabs.FirstOrDefault();
iconAlpha = 1.0f; iconAlpha = 1.0f;
} }
if (iconAlpha > 0.0f) if (iconAlpha > 0.0f)
@@ -616,9 +619,12 @@ namespace Barotrauma.Items.Components
if (slotRect.Contains(PlayerInput.MousePosition)) if (slotRect.Contains(PlayerInput.MousePosition))
{ {
var suitableIngredients = requiredItem.ItemPrefabs.Select(ip => ip.Name).Distinct(); LocalizedString toolTipText = requiredItem.OverrideHeader;
LocalizedString toolTipText = string.Join(", ", suitableIngredients.Count() > 3 ? suitableIngredients.SkipLast(suitableIngredients.Count() - 3) : suitableIngredients); if (requiredItem.OverrideHeader.IsNullOrEmpty())
if (suitableIngredients.Count() > 3) { toolTipText += "..."; } {
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) if (requiredItem.UseCondition && requiredItem.MinCondition < 1.0f)
{ {
toolTipText += " " + (int)Math.Round(requiredItem.MinCondition * 100) + "%"; 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) private void DrawOutputOverLay(SpriteBatch spriteBatch, GUICustomComponent overlayComponent)
{ {
overlayComponent.RectTransform.SetAsLastChild(); overlayComponent.RectTransform.SetAsLastChild();
FabricationRecipe targetItem = fabricatedItem ?? selectedItem; FabricationRecipe targetItem = fabricatedItem ?? selectedItem;
if (targetItem != null) if (targetItem != null && outputContainer.Inventory?.visualSlots != null)
{ {
Rectangle slotRect = outputContainer.Inventory.visualSlots[0].Rect; Rectangle slotRect = outputContainer.Inventory.visualSlots[0].Rect;
if (fabricatedItem != null) if (fabricatedItem != null)
{ {
float clampedProgressState = Math.Clamp(progressState, 0f, 1f); float clampedProgressState = Math.Clamp(progressState, 0f, 1f);
@@ -699,6 +758,16 @@ namespace Barotrauma.Items.Components
{ {
FabricationRecipe recipe = child.UserData as FabricationRecipe; FabricationRecipe recipe = child.UserData as FabricationRecipe;
if (recipe?.DisplayName == null) { continue; } if (recipe?.DisplayName == null) { continue; }
if (recipe.HideForNonTraitors)
{
if (Character.Controlled is not { IsTraitor: true })
{
child.Visible = false;
continue;
}
}
child.Visible = child.Visible =
(string.IsNullOrWhiteSpace(filter) || recipe.DisplayName.Contains(filter, StringComparison.OrdinalIgnoreCase)) && (string.IsNullOrWhiteSpace(filter) || recipe.DisplayName.Contains(filter, StringComparison.OrdinalIgnoreCase)) &&
(!category.HasValue || recipe.TargetItem.Category.HasFlag(category.Value)); (!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) private bool SelectItem(Character user, FabricationRecipe selectedItem, float? overrideRequiredTime = null)
{ {
this.selectedItem = selectedItem; 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) if (amountInput != null)
{ {
@@ -924,7 +994,7 @@ namespace Barotrauma.Items.Components
return true; 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; activateButton.Enabled = false;
inSufficientPowerWarning.Visible = IsActive && !hasPower; inSufficientPowerWarning.Visible = IsActive && !hasPower;
@@ -933,6 +1003,12 @@ namespace Barotrauma.Items.Components
if (!IsActive) 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) //only check ingredients if the fabricator isn't active (if it is, this is done in Update)
if (refreshIngredientsTimer <= 0.0f) if (refreshIngredientsTimer <= 0.0f)
{ {
@@ -998,9 +1074,13 @@ namespace Barotrauma.Items.Components
{ {
fabricationLimits[msg.ReadUInt32()] = 0; fabricationLimits[msg.ReadUInt32()] = 0;
} }
State = newState; 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; this.amountRemaining = amountRemaining;
if (newState == FabricatorState.Stopped || recipeHash == 0) if (newState == FabricatorState.Stopped || recipeHash == 0)
{ {
@@ -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) 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)) foreach (ItemPrefab prefab in ItemPrefab.Prefabs.OrderBy(prefab => prefab.Name))
{ {
if (prefab.HideInMenus) { continue; } if (prefab.HideInMenus) { continue; }
if (shownItemPrefabs.Any(ip => DisplayAsSameItem(ip, prefab)))
{
continue;
}
CreateItemFrame(prefab, listBox.Content.RectTransform); CreateItemFrame(prefab, listBox.Content.RectTransform);
shownItemPrefabs.Add(prefab);
} }
searchBar.OnDeselected += (sender, key) => 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); 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) private bool VisibleOnItemFinder(Item it)
{ {
if (it?.Submarine == null) { return false; } 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.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; return true;
} }
@@ -539,13 +566,13 @@ namespace Barotrauma.Items.Components
displayedSubs.Add(item.Submarine); displayedSubs.Add(item.Submarine);
displayedSubs.AddRange(item.Submarine.DockedTo.Where(s => s.TeamID == item.Submarine.TeamID)); 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); BakeSubmarine(item.Submarine, parentRect);
elementSize = GuiFrame.Rect.Size; 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 //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 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) foreach (GUIComponent component in listBox.Content.Children)
{ {
component.Visible = false; 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()); 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); 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; } if (sprite is null) { return; }
GUIFrame frame = new GUIFrame(new RectTransform(new Vector2(1f, 0.25f), parent), style: "ListBoxElement") 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 (!VisibleOnItemFinder(it)) { continue; }
if (it.Prefab == searchedPrefab) if (DisplayAsSameItem(it.Prefab, searchedPrefab))
{ {
// ignore items on players and hidden inventories // ignore items on players and hidden inventories
if (it.FindParentInventory(inv => inv is CharacterInventory || inv is ItemInventory { Owner: Item { HiddenInGame: true }}) is { }) { continue; } 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) if (ShowHullIntegrity)
{ {
float amount = 1f + hullData.LinkedHulls.Count; 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)); borderColor = Color.Lerp(neutralColor, GUIStyle.Red, Math.Min(gapOpenSum, 1.0f));
} }
@@ -181,7 +181,7 @@ namespace Barotrauma.Items.Components
private float flickerTimer; private float flickerTimer;
private readonly float flickerFrequency = 1; 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; autoControlIndicator.Selected = IsAutoControlled;
PowerButton.Enabled = isActiveLockTimer <= 0.0f; PowerButton.Enabled = isActiveLockTimer <= 0.0f;
@@ -615,7 +615,7 @@ namespace Barotrauma.Items.Components
turbineOutputMeter, TurbineOutput, new Vector2(0.0f, 100.0f), clampedOptimalTurbineOutput, clampedAllowedTurbineOutput); 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; IsActive = true;
@@ -109,7 +109,7 @@ namespace Barotrauma.Items.Components
}, },
{ {
BlipType.Destructible, 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, BlipType.Door,
@@ -358,11 +358,6 @@ namespace Barotrauma.Items.Components
} }
} }
protected override void TryCreateDragHandle()
{
base.TryCreateDragHandle();
}
private void SetPingDirection(Vector2 direction) private void SetPingDirection(Vector2 direction)
{ {
pingDirection = 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; showDirectionalIndicatorTimer -= deltaTime;
if (GameMain.Client != null) 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, if (Level.Loaded.StartLocation?.Type is { ShowSonarMarker: true })
Level.Loaded.StartLocation.Name, {
(Level.Loaded.StartOutpost != null ? "outpost" : "location").ToIdentifier(), DrawMarker(spriteBatch,
"startlocation", Level.Loaded.StartLocation.Name,
Level.Loaded.StartExitPosition, transducerCenter, (Level.Loaded.StartOutpost != null ? "outpost" : "location").ToIdentifier(),
displayScale, center, DisplayRadius); "startlocation",
} Level.Loaded.StartExitPosition, transducerCenter,
displayScale, center, DisplayRadius);
}
if (Level.Loaded is { EndLocation.Type.ShowSonarMarker: true, Type: LevelData.LevelType.LocationConnection }) if (Level.Loaded is { EndLocation.Type.ShowSonarMarker: true, Type: LevelData.LevelType.LocationConnection })
{ {
DrawMarker(spriteBatch, DrawMarker(spriteBatch,
Level.Loaded.EndLocation.Name, Level.Loaded.EndLocation.Name,
(Level.Loaded.EndOutpost != null ? "outpost" : "location").ToIdentifier(), (Level.Loaded.EndOutpost != null ? "outpost" : "location").ToIdentifier(),
"endlocation", "endlocation",
Level.Loaded.EndExitPosition, transducerCenter, Level.Loaded.EndExitPosition, transducerCenter,
displayScale, center, DisplayRadius); displayScale, center, DisplayRadius);
} }
for (int i = 0; i < Level.Loaded.Caves.Count; i++) for (int i = 0; i < Level.Loaded.Caves.Count; i++)
{ {
var cave = Level.Loaded.Caves[i]; var cave = Level.Loaded.Caves[i];
if (!cave.DisplayOnSonar) { continue; } if (cave.MissionsToDisplayOnSonar.None()) { continue; }
DrawMarker(spriteBatch, DrawMarker(spriteBatch,
caveLabel.Value, caveLabel.Value,
"cave".ToIdentifier(), "cave".ToIdentifier(),
"cave" + i, "cave" + i,
cave.StartPos.ToVector2(), transducerCenter, cave.StartPos.ToVector2(), transducerCenter,
displayScale, center, DisplayRadius); displayScale, center, DisplayRadius);
}
} }
int missionIndex = 0; int missionIndex = 0;
@@ -1070,7 +1068,7 @@ namespace Barotrauma.Items.Components
{ {
if (!sub.ShowSonarMarker) { continue; } if (!sub.ShowSonarMarker) { continue; }
if (connectedSubs.Contains(sub)) { 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) if (item.Submarine != null || Character.Controlled != null)
{ {
@@ -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) public void DrawHUD(SpriteBatch spriteBatch, Rectangle rect)
{ {
int width = rect.Width, height = rect.Height; int width = rect.Width, height = rect.Height;
@@ -545,11 +561,9 @@ namespace Barotrauma.Items.Components
if (!AutoPilot) if (!AutoPilot)
{ {
Vector2 unitSteeringInput = steeringInput / 100.0f;
//map input from rectangle to circle //map input from rectangle to circle
Vector2 steeringInputPos = new Vector2( Vector2 steeringInputPos = MapSquareToCircle(steeringInput / 100f) * 100.0f;
steeringInput.X * (float)Math.Sqrt(1.0f - 0.5f * unitSteeringInput.Y * unitSteeringInput.Y), steeringInputPos.Y = -steeringInputPos.Y;
-steeringInput.Y * (float)Math.Sqrt(1.0f - 0.5f * unitSteeringInput.X * unitSteeringInput.X));
steeringInputPos += steeringOrigin; steeringInputPos += steeringOrigin;
if (steeringIndicator != null) if (steeringIndicator != null)
@@ -604,10 +618,8 @@ namespace Barotrauma.Items.Components
} }
//map velocity from rectangle to circle //map velocity from rectangle to circle
Vector2 unitTargetVel = targetVelocity / 100.0f; Vector2 steeringPos = MapSquareToCircle(targetVelocity / 100f) * 90.0f;
Vector2 steeringPos = new Vector2( steeringPos.Y = -steeringPos.Y;
targetVelocity.X * 0.9f * (float)Math.Sqrt(1.0f - 0.5f * unitTargetVel.Y * unitTargetVel.Y),
-targetVelocity.Y * 0.9f * (float)Math.Sqrt(1.0f - 0.5f * unitTargetVel.X * unitTargetVel.X));
steeringPos += steeringOrigin; steeringPos += steeringOrigin;
if (steeringIndicator != null) 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) if (swapDestinationOrder == null)
{ {
@@ -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++) for (var i = 0; i < GrowableSeeds.Length; i++)
{ {
@@ -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) 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; Vector2 scaledIndicatorSize = indicatorSize * item.Scale;
if (scaledIndicatorSize.X <= 2.0f || scaledIndicatorSize.Y <= 2.0f) { return; } if (scaledIndicatorSize.X <= 2.0f || scaledIndicatorSize.Y <= 2.0f) { return; }
@@ -10,6 +10,10 @@ namespace Barotrauma.Items.Components
private GUITickBox highVoltageIndicator; private GUITickBox highVoltageIndicator;
private GUITickBox lowVoltageIndicator; private GUITickBox lowVoltageIndicator;
private GUITextBlock powerLabel, loadLabel;
private LanguageIdentifier prevLanguage;
partial void InitProjectSpecific(XElement element) partial void InitProjectSpecific(XElement element)
{ {
if (GuiFrame == null) { return; } if (GuiFrame == null) { return; }
@@ -56,12 +60,12 @@ namespace Barotrauma.Items.Components
Stretch = true 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) TextManager.Get("PowerTransferPowerLabel"), textColor: GUIStyle.TextColorBright, font: GUIStyle.LargeFont, textAlignment: Alignment.CenterRight)
{ {
ToolTip = TextManager.Get("PowerTransferTipPower") 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) TextManager.Get("PowerTransferLoadLabel"), textColor: GUIStyle.TextColorBright, font: GUIStyle.LargeFont, textAlignment: Alignment.CenterRight)
{ {
ToolTip = TextManager.Get("PowerTransferTipLoad") ToolTip = TextManager.Get("PowerTransferTipLoad")
@@ -75,7 +79,7 @@ namespace Barotrauma.Items.Components
ToolTip = TextManager.Get("PowerTransferTipPower"), ToolTip = TextManager.Get("PowerTransferTipPower"),
TextGetter = () => { TextGetter = () => {
float currPower = powerLoad < 0 ? -powerLoad: 0; 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; currPower = PowerConnections[0].Grid.Power;
} }
@@ -119,9 +123,11 @@ namespace Barotrauma.Items.Components
GUITextBlock.AutoScaleAndNormalize(powerLabel, loadLabel); GUITextBlock.AutoScaleAndNormalize(powerLabel, loadLabel);
GUITextBlock.AutoScaleAndNormalize(true, true, powerText, loadText); GUITextBlock.AutoScaleAndNormalize(true, true, powerText, loadText);
GUITextBlock.AutoScaleAndNormalize(kw1, kw2); 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; if (GuiFrame == null) return;
@@ -129,6 +135,13 @@ namespace Barotrauma.Items.Components
powerIndicator.Selected = IsActive && voltage > 0; powerIndicator.Selected = IsActive && voltage > 0;
highVoltageIndicator.Selected = Timing.TotalTime % 0.5f < 0.25f && powerIndicator.Selected && voltage > 1.2f; 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; 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;
}
} }
} }
} }
@@ -42,59 +42,98 @@ namespace Barotrauma.Items.Components
Vector2 axis = new Vector2( Vector2 axis = new Vector2(
msg.ReadSingle(), msg.ReadSingle(),
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; 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.Submarine = submarine;
item.CurrentHull = hull; item.CurrentHull = hull;
item.body.SetTransform(simPosition, item.body.Rotation); item.body.SetTransform(simPosition, item.body.Rotation);
if (entity is Character character)
switch (targetType)
{ {
byte limbIndex = msg.ReadByte(); case StickTargetType.Structure:
if (limbIndex >= character.AnimController.Limbs.Length) UInt16 structureId = msg.ReadUInt16();
{ byte bodyIndex = msg.ReadByte();
DebugConsole.ThrowError($"Failed to read a projectile update from the server. Limb index out of bounds ({limbIndex}, character: {character.ToString()})"); if (Entity.FindEntityByID(structureId) is Structure structure)
return; {
} if (bodyIndex == 255) { bodyIndex = 0; }
if (character.Removed) { return; } if (bodyIndex >= structure.Bodies.Count)
var limb = character.AnimController.Limbs[limbIndex]; {
StickToTarget(limb.body.FarseerBody, axis); DebugConsole.ThrowError($"Failed to read a projectile update from the server. Structure body index out of bounds ({bodyIndex}, structure: {structure})");
} return;
else if (entity is Structure structure) }
{ var body = structure.Bodies[bodyIndex];
byte bodyIndex = msg.ReadByte(); StickToTarget(body, axis);
if (bodyIndex == 255) { bodyIndex = 0; } }
if (bodyIndex >= structure.Bodies.Count) else
{ {
DebugConsole.ThrowError($"Failed to read a projectile update from the server. Structure body index out of bounds ({bodyIndex}, structure: {structure.ToString()})"); DebugConsole.AddWarning($"\"{item.Prefab.Identifier}\" failed to stick to a structure. Could not find a structure with the ID {structureId}");
return; }
} break;
var body = structure.Bodies[bodyIndex]; case StickTargetType.Limb:
StickToTarget(body, axis); UInt16 characterId = msg.ReadUInt16();
} byte limbIndex = msg.ReadByte();
else if (entity is Item item) if (Entity.FindEntityByID(characterId) is Character character)
{ {
if (item.Removed) { return; } if (limbIndex >= character.AnimController.Limbs.Length)
var door = item.GetComponent<Door>(); {
if (door != null) DebugConsole.ThrowError($"Failed to read a projectile update from the server. Limb index out of bounds ({limbIndex}, character: {character})");
{ return;
StickToTarget(door.Body.FarseerBody, axis); }
} if (character.Removed) { return; }
else if (item.body != null) var limb = character.AnimController.Limbs[limbIndex];
{ StickToTarget(limb.body.FarseerBody, axis);
StickToTarget(item.body.FarseerBody, axis); }
} else
} {
else if (entity is Submarine sub) DebugConsole.AddWarning($"\"{this.item.Prefab.Identifier}\" failed to stick to a limb. Could not find a character with the ID {characterId}");
{ }
StickToTarget(sub.PhysicsBody.FarseerBody, axis); break;
} case StickTargetType.Item:
else UInt16 itemID = msg.ReadUInt16();
{ if (Entity.FindEntityByID(itemID) is Item targetItem)
DebugConsole.ThrowError($"Failed to read a projectile update from the server. Invalid stick target ({entity?.ToString() ?? "null"}, {entityID})"); {
} 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 else
{ {
@@ -9,7 +9,7 @@ namespace Barotrauma.Items.Components
currentTarget?.DrawHUD(spriteBatch, Screen.Selected.Cam, character); 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); currentTarget?.UpdateHUD(cam, character,deltaTime);
} }
@@ -144,7 +144,7 @@ namespace Barotrauma.Items.Components
} }
} }
#if DEBUG #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) if (GameMain.DebugDraw && IsActive)
{ {
@@ -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) if (GameMain.DebugDraw && Character.Controlled?.FocusedItem == item)
{ {
bool paused = !ShouldDeteriorate(); bool paused = !ShouldDeteriorate() && ForceDeteriorationTimer <= 0.0f;
if (deteriorationTimer > 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, GUI.DrawString(spriteBatch,
new Vector2(item.DrawPosition.X, -item.DrawPosition.Y), "Deterioration delay " + ((int)deteriorationTimer) + (paused ? " [PAUSED]" : ""), 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) public void ClientEventRead(IReadMessage msg, float sendingTime)
{ {
deteriorationTimer = msg.ReadSingle(); deteriorationTimer = msg.ReadSingle();
deteriorateAlwaysResetTimer = msg.ReadSingle(); ForceDeteriorationTimer = msg.ReadSingle();
DeteriorateAlways = msg.ReadBoolean();
tinkeringDuration = msg.ReadSingle(); tinkeringDuration = msg.ReadSingle();
tinkeringStrength = msg.ReadSingle(); tinkeringStrength = msg.ReadSingle();
tinkeringPowersDevices = msg.ReadBoolean(); tinkeringPowersDevices = msg.ReadBoolean();
@@ -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) partial void InitProjSpecific(ContentXElement element)
{ {
foreach (var subElement in element.Elements()) 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 == null || target.Removed) { return; }
if (target.ParentInventory != null) { return; } if (target.ParentInventory != null) { return; }
if (source is Limb limb && limb.Removed) { return; } if (source is Limb limb && limb.Removed) { return; }
if (source is Entity e && e.Removed) { return; } if (source is Entity e && e.Removed) { return; }
Vector2 startPos = GetSourcePos(); Vector2 startPos = GetSourcePos(useDrawPosition: true);
startPos.Y = -startPos.Y; startPos.Y = -startPos.Y;
if (source is Item sourceItem && !sourceItem.Removed) if ((source as Item)?.GetComponent<Turret>() is { } turret)
{ {
var turret = sourceItem.GetComponent<Turret>(); if (turret.BarrelSprite != null)
var weapon = sourceItem.GetComponent<RangedWeapon>();
if (turret != null)
{ {
startPos = new Vector2(sourceItem.WorldRect.X + turret.TransformedBarrelPos.X, -(sourceItem.WorldRect.Y - turret.TransformedBarrelPos.Y)); 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;
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 -= turret.GetRecoilOffset();
} }
Vector2 endPos = new Vector2(target.DrawPosition.X, target.DrawPosition.Y); Vector2 endPos = new Vector2(target.DrawPosition.X, target.DrawPosition.Y);
Vector2 flippedPos = target.Sprite.size * target.Scale * (Origin - new Vector2(0.5f)); Vector2 flippedPos = target.Sprite.size * target.Scale * (Origin - new Vector2(0.5f));
@@ -156,28 +130,28 @@ namespace Barotrauma.Items.Components
if (startSprite != null) if (startSprite != null)
{ {
float depth = Math.Min(item.GetDrawDepth() + (startSprite.Depth - item.Sprite.Depth), 0.999f); 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)) if (endSprite != null && (!Snapped || BreakFromMiddle))
{ {
float depth = Math.Min(item.GetDrawDepth() + (endSprite.Depth - item.Sprite.Depth), 0.999f); 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 ? float depth = sprite == null ?
item.Sprite.Depth + 0.001f : item.Sprite.Depth + 0.001f :
Math.Min(item.GetDrawDepth() + (sprite.Depth - item.Sprite.Depth), 0.999f); Math.Min(item.GetDrawDepth() + (sprite.Depth - item.Sprite.Depth), 0.999f);
if (sprite?.Texture == null) if (sprite?.Texture == null)
{ {
GUI.DrawLine(spriteBatch, GUI.DrawLine(spriteBatch,
startPos, startPos,
endPos, endPos,
SpriteColor, depth: depth, width: width); overrideColor ?? SpriteColor, depth: depth, width: width);
return; return;
} }
@@ -191,7 +165,7 @@ namespace Barotrauma.Items.Components
GUI.DrawLine(spriteBatch, sprite, GUI.DrawLine(spriteBatch, sprite,
startPos + dir * (x - 5.0f), startPos + dir * (x - 5.0f),
startPos + dir * (x + sprite.size.X), startPos + dir * (x + sprite.size.X),
SpriteColor, depth: depth, width: width); overrideColor ?? SpriteColor, depth: depth, width: width);
} }
float leftOver = length - x; float leftOver = length - x;
if (leftOver > 0.0f) if (leftOver > 0.0f)
@@ -199,7 +173,7 @@ namespace Barotrauma.Items.Components
GUI.DrawLine(spriteBatch, sprite, GUI.DrawLine(spriteBatch, sprite,
startPos + dir * (x - 5.0f), startPos + dir * (x - 5.0f),
endPos, endPos,
SpriteColor, depth: depth, width: width); overrideColor ?? SpriteColor, depth: depth, width: width);
} }
} }
else else
@@ -207,7 +181,7 @@ namespace Barotrauma.Items.Components
GUI.DrawLine(spriteBatch, sprite, GUI.DrawLine(spriteBatch, sprite,
startPos, startPos,
endPos, endPos,
SpriteColor, depth: depth, width: width); overrideColor ?? SpriteColor, depth: depth, width: width);
} }
} }
@@ -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();
}
}
}
@@ -21,6 +21,8 @@ namespace Barotrauma.Items.Components
public float FlashTimer { get; private set; } public float FlashTimer { get; private set; }
public static Wire DraggingConnected { 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, public static void DrawConnections(SpriteBatch spriteBatch, ConnectionPanel panel, Rectangle dragArea, Character character,
out (Vector2 tooltipPos, LocalizedString text) tooltip) out (Vector2 tooltipPos, LocalizedString text) tooltip)
{ {
@@ -33,8 +35,6 @@ namespace Barotrauma.Items.Components
int x = panelRect.X, y = panelRect.Y; int x = panelRect.X, y = panelRect.Y;
int width = panelRect.Width, height = panelRect.Height; int width = panelRect.Width, height = panelRect.Height;
Vector2 scale = GetScale(panel.GuiFrame.RectTransform.MaxSize, panel.GuiFrame.Rect.Size);
bool mouseInRect = panelRect.Contains(PlayerInput.MousePosition); bool mouseInRect = panelRect.Contains(PlayerInput.MousePosition);
int totalWireCount = 0; 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 //two passes: first the connector, then the wires to get the wires to render in front
for (int i = 0; i < 2; i++) for (int i = 0; i < 2; i++)
{ {
Vector2 rightPos = GetRightPos(x, y, width, scale); Vector2 rightPos = GetRightPos(x, y, width);
Vector2 leftPos = GetLeftPos(x, y, scale); Vector2 leftPos = GetLeftPos(x, y);
Vector2 rightWirePos = new Vector2(x + width - 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 * scale.X, y + 30 * scale.Y); Vector2 leftWirePos = new Vector2(x + 5 * GUI.Scale, y + 30 * GUI.Scale);
int wireInterval = (height - (int)(20 * scale.Y)) / Math.Max(totalWireCount, 1); int wireInterval = (height - (int)(20 * GUI.Scale)) / Math.Max(totalWireCount, 1);
int connectorIntervalLeft = GetConnectorIntervalLeft(height, scale, panel); int connectorIntervalLeft = GetConnectorIntervalLeft(height, panel);
int connectorIntervalRight = GetConnectorIntervalRight(height, scale, panel); int connectorIntervalRight = GetConnectorIntervalRight(height, panel);
foreach (Connection c in panel.Connections) foreach (Connection c in panel.Connections)
{ {
@@ -101,47 +101,26 @@ namespace Barotrauma.Items.Components
} }
Vector2 position = c.IsOutput ? rightPos : leftPos; Vector2 position = c.IsOutput ? rightPos : leftPos;
Color highlightColor = Color.Transparent;
if (ConnectionPanel.ShouldDebugDrawWiring) if (ConnectionPanel.ShouldDebugDrawWiring)
{ {
if (c.IsPower) DrawConnectionDebugInfo(spriteBatch, c, position, GUI.Scale, out var tooltipText);
{
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);
LocalizedString toolTipText = c.GetToolTip(); if (!tooltipText.IsNullOrEmpty())
if (mouseOn) { tooltip = (position, toolTipText); }
if (!toolTipText.IsNullOrEmpty())
{ {
var glowSprite = GUIStyle.UIGlowCircular.Value.Sprite; bool mouseOn = Vector2.DistanceSquared(position, PlayerInput.MousePosition) < MathUtils.Pow2(35 * GUI.Scale);
glowSprite.Draw(spriteBatch, position, highlightColor, glowSprite.size / 2, if (mouseOn)
scale: 45.0f / glowSprite.size.X * panel.Scale); {
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 //outputs are drawn at the right side of the panel, inputs at the left
if (c.IsOutput) if (c.IsOutput)
{ {
if (i == 0) if (i == 0)
{ {
c.DrawConnection(spriteBatch, panel, rightPos, GetOutputLabelPosition(rightPos, panel, c), scale); c.DrawConnection(spriteBatch, panel, rightPos, GetOutputLabelPosition(rightPos, panel, c));
} }
else else
{ {
@@ -154,7 +133,7 @@ namespace Barotrauma.Items.Components
{ {
if (i == 0) if (i == 0)
{ {
c.DrawConnection(spriteBatch, panel, leftPos, GetInputLabelPosition(leftPos, panel, c), scale); c.DrawConnection(spriteBatch, panel, leftPos, GetInputLabelPosition(leftPos, panel, c));
} }
else else
{ {
@@ -224,8 +203,8 @@ namespace Barotrauma.Items.Components
} }
float step = (width * 0.75f) / panel.DisconnectedWires.Count(); float step = (width * 0.75f) / panel.DisconnectedWires.Count;
x = (int)(x + width / 2 - step * (panel.DisconnectedWires.Count() - 1) / 2); x = (int)(x + width / 2 - step * (panel.DisconnectedWires.Count - 1) / 2);
foreach (Wire wire in panel.DisconnectedWires) foreach (Wire wire in panel.DisconnectedWires)
{ {
if (wire == DraggingConnected && mouseInRect) { continue; } 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 //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) //(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))) 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(); string text = DisplayName.Value.ToUpperInvariant();
//nasty //nasty
if (GUIStyle.GetComponentStyle("ConnectionPanelLabel")?.Sprites.Values.First().First() is UISprite labelSprite) 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); 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 + Vector2.UnitY, text, Color.Black * 0.8f, font: GUIStyle.SmallFont);
GUI.DrawString(spriteBatch, labelPos, text, GUIStyle.TextColorBright, 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); 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) 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) foreach (var wire in wires)
{ {
@@ -285,9 +299,14 @@ namespace Barotrauma.Items.Components
wirePosition.Y += wireInterval; 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); connectionSpriteHighlight.Draw(spriteBatch, position, scale: connectorSpriteScale);
}
if (DraggingConnected != null && isMouseOn)
{
if (!PlayerInput.PrimaryMouseButtonHeld()) if (!PlayerInput.PrimaryMouseButtonHeld())
{ {
@@ -418,7 +437,7 @@ namespace Barotrauma.Items.Components
bool mouseOn = bool mouseOn =
canDrag && canDrag &&
!(GUI.MouseOn is GUIDragHandle) && GUI.MouseOn is not GUIDragHandle &&
((PlayerInput.MousePosition.X > Math.Min(start.X, end.X) && ((PlayerInput.MousePosition.X > Math.Min(start.X, end.X) &&
PlayerInput.MousePosition.X < Math.Max(start.X, end.X) && PlayerInput.MousePosition.X < Math.Max(start.X, end.X) &&
MathUtils.LineToPointDistanceSquared(start, end, PlayerInput.MousePosition) < 36) || 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 dist = Vector2.Distance(start, wireEnd);
float wireWidth = 12 * panel.Scale; float wireWidth = 12 * GUI.Scale;
float highlight = 5 * panel.Scale; float highlight = 5 * GUI.Scale;
if (mouseOn) if (mouseOn)
{ {
spriteBatch.Draw(wireVertical.Texture, new Rectangle(wireEnd.ToPoint(), new Point((int)(wireWidth + highlight), (int)dist)), wireVertical.SourceRect, 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; Rectangle panelRect = panel.GuiFrame.Rect;
int x = panelRect.X, y = panelRect.Y; 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);
Vector2 rightPos = GetRightPos(x, y, panelRect.Width, scale); Vector2 leftPos = GetLeftPos(x, y);
Vector2 leftPos = GetLeftPos(x, y, scale); int connectorIntervalLeft = GetConnectorIntervalLeft(panelRect.Height, panel);
int connectorIntervalLeft = GetConnectorIntervalLeft(panelRect.Height, scale, panel); int connectorIntervalRight = GetConnectorIntervalRight(panelRect.Height, panel);
int connectorIntervalRight = GetConnectorIntervalRight(panelRect.Height, scale, panel);
newRectSize = panelRect.Size; 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(); if (c.IsOutput)
foreach (var c in panel.Connections)
{ {
if (c.IsOutput) var labelArea = GetLabelArea(GetOutputLabelPosition(rightPos, panel, c), c.DisplayName.Value.ToUpperInvariant());
{ leftMostOutput = Math.Min(leftMostOutput, labelArea.X);
var labelArea = GetLabelArea(GetOutputLabelPosition(rightPos, panel, c), c.DisplayName.Value.ToUpperInvariant(), scale); rightPos.Y += connectorIntervalLeft;
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;
}
} }
bool foundOverlap = false; else
for (int j = 0; j < labelAreas.Count; j++)
{ {
for (int k = 0; k < labelAreas.Count; k++) var labelArea = GetLabelArea(GetInputLabelPosition(leftPos, panel, c), c.DisplayName.Value.ToUpperInvariant());
{ rightMostInput = Math.Max(rightMostInput, labelArea.Right);
if (k == j) { continue; } leftPos.Y += connectorIntervalRight;
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;
}
} }
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; 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) private static Vector2 GetInputLabelPosition(Vector2 connectorPosition, ConnectionPanel panel, Connection connection)
{ {
return new Vector2( return new Vector2(
connectorPosition.X + 25 * panel.Scale, connectorPosition.X + 25 * GUI.Scale,
connectorPosition.Y - 5 * panel.Scale - GUIStyle.SmallFont.MeasureString(connection.DisplayName.ToUpper()).Y); connectorPosition.Y - GUIStyle.SmallFont.MeasureString(connection.DisplayName.ToUpper()).Y / 2);
} }
private static Vector2 GetOutputLabelPosition(Vector2 connectorPosition, ConnectionPanel panel, Connection connection) private static Vector2 GetOutputLabelPosition(Vector2 connectorPosition, ConnectionPanel panel, Connection connection)
{ {
return new Vector2( return new Vector2(
connectorPosition.X - 25 * panel.Scale - GUIStyle.SmallFont.MeasureString(connection.DisplayName.ToUpper()).X, connectorPosition.X - 25 * GUI.Scale - GUIStyle.SmallFont.MeasureString(connection.DisplayName.ToUpper()).X,
connectorPosition.Y + 5 * panel.Scale); 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); Vector2 textSize = GUIStyle.SmallFont.MeasureString(text);
Rectangle labelArea = new Rectangle(labelPos.ToPoint(), textSize.ToPoint()); 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; 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);
} }
} }
} }
@@ -22,11 +22,6 @@ namespace Barotrauma.Items.Components
private SoundChannel rewireSoundChannel; private SoundChannel rewireSoundChannel;
private float rewireSoundTimer; private float rewireSoundTimer;
public float Scale
{
get { return GuiFrame.Rect.Width / 400.0f; }
}
private Point originalMaxSize; private Point originalMaxSize;
private Vector2 originalRelativeSize; private Vector2 originalRelativeSize;
@@ -104,10 +99,10 @@ namespace Barotrauma.Items.Components
public override bool ShouldDrawHUD(Character character) 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; } 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 //because some of the wires connected to the panel may not exist yet
long msgStartPos = msg.BitPosition; long msgStartPos = msg.BitPosition;
msg.ReadUInt16(); //user ID msg.ReadUInt16(); //user ID
foreach (Connection _ in Connections) byte connectionCount = msg.ReadByte();
for (int i = 0; i < connectionCount; i++)
{ {
uint wireCount = msg.ReadVariableUInt32(); uint wireCount = msg.ReadVariableUInt32();
for (int i = 0; i < wireCount; i++) for (int j = 0; j < wireCount; j++)
{ {
msg.ReadUInt16(); msg.ReadUInt16();
} }
@@ -208,21 +204,25 @@ namespace Barotrauma.Items.Components
connection.ClearConnections(); connection.ClearConnections();
} }
foreach (Connection connection in Connections) byte connectionCount = msg.ReadByte();
for (int i = 0; i < connectionCount; i++)
{ {
HashSet<Wire> newWires = new HashSet<Wire>(); HashSet<Wire> newWires = new HashSet<Wire>();
uint wireCount = msg.ReadVariableUInt32(); uint wireCount = msg.ReadVariableUInt32();
for (int i = 0; i < wireCount; i++) for (int j = 0; j < wireCount; j++)
{ {
ushort wireId = msg.ReadUInt16(); ushort wireId = msg.ReadUInt16();
if (Entity.FindEntityByID(wireId) is not Item wireItem) { continue; }
if (!(Entity.FindEntityByID(wireId) is Item wireItem)) { continue; }
Wire wireComponent = wireItem.GetComponent<Wire>(); Wire wireComponent = wireItem.GetComponent<Wire>();
if (wireComponent == null) { continue; } if (wireComponent == null) { continue; }
newWires.Add(wireComponent); 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(); Wire[] oldWires = connection.Wires.Where(w => !newWires.Contains(w)).ToArray();
foreach (var wire in oldWires) foreach (var wire in oldWires)
{ {
@@ -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; bool elementVisibilityChanged = false;
int visibleElementCount = 0; int visibleElementCount = 0;
@@ -335,17 +335,18 @@ namespace Barotrauma.Items.Components
if (signals == null) { return; } if (signals == null) { return; }
for (int i = 0; i < signals.Length && i < uiElements.Count; i++) for (int i = 0; i < signals.Length && i < uiElements.Count; i++)
{ {
string signal = customInterfaceElementList[i].Signal;
if (uiElements[i] is GUITextBox tb) if (uiElements[i] is GUITextBox tb)
{ {
tb.Text = Screen.Selected is { IsEditor: true } ? tb.Text = Screen.Selected is { IsEditor: true } ?
customInterfaceElementList[i].Signal : signal :
TextManager.Get(customInterfaceElementList[i].Signal).Value; TextManager.Get(signal).Fallback(signal).Value;
} }
else if (uiElements[i] is GUINumberInput ni) else if (uiElements[i] is GUINumberInput ni)
{ {
if (ni.InputType == NumberType.Int) if (ni.InputType == NumberType.Int)
{ {
int.TryParse(customInterfaceElementList[i].Signal, out int value); int.TryParse(signal, out int value);
ni.IntValue = value; ni.IntValue = value;
} }
} }
@@ -10,7 +10,7 @@ namespace Barotrauma.Items.Components
get { return new Vector2(rangeX, rangeY) * 2.0f; } 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; } if (!editing || !MapEntity.SelectedList.Contains(item)) { return; }
@@ -86,15 +86,15 @@ namespace Barotrauma.Items.Components
} }
OutputValue = input; OutputValue = input;
ShowOnDisplay(input, addToHistory: true, TextColor); ShowOnDisplay(input, addToHistory: true, TextColor, isWelcomeMessage: false);
item.SendSignal(input, "signal_out"); 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) if (addToHistory)
{ {
messageHistory.Add(new TerminalMessage(input, color)); messageHistory.Add(new TerminalMessage(input, color, isWelcomeMessage));
while (messageHistory.Count > MaxMessages) while (messageHistory.Count > MaxMessages)
{ {
messageHistory.RemoveAt(0); messageHistory.RemoveAt(0);
@@ -11,9 +11,9 @@ namespace Barotrauma.Items.Components
get { return new Vector2(range * 2); } 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); Vector2 pos = new Vector2(item.DrawPosition.X, -item.DrawPosition.Y);
ShapeExtensions.DrawLine(spriteBatch, pos + Vector2.UnitY * range, pos - Vector2.UnitY * range, Color.Cyan * 0.5f, 2); ShapeExtensions.DrawLine(spriteBatch, pos + Vector2.UnitY * range, pos - Vector2.UnitY * range, Color.Cyan * 0.5f, 2);
@@ -186,12 +186,12 @@ namespace Barotrauma.Items.Components
return Color.LightBlue; 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) if (sections.Count == 0 && !IsActive || Hidden)
{ {
@@ -223,7 +223,7 @@ namespace Barotrauma.Items.Components
foreach (WireSection section in sections) 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) if (nodes.Count > 0)
@@ -271,13 +271,13 @@ namespace Barotrauma.Items.Components
spriteBatch, wireSprite, spriteBatch, wireSprite,
nodes[^1] + drawOffset, nodes[^1] + drawOffset,
new Vector2(newNodePos.X, newNodePos.Y) + drawOffset, new Vector2(newNodePos.X, newNodePos.Y) + drawOffset,
item.Color, 0.0f, Width); overrideColor ?? item.Color, 0.0f, Width);
WireSection.Draw( WireSection.Draw(
spriteBatch, wireSprite, spriteBatch, wireSprite,
new Vector2(newNodePos.X, newNodePos.Y) + drawOffset, new Vector2(newNodePos.X, newNodePos.Y) + drawOffset,
item.DrawPosition, 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); 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, spriteBatch, wireSprite,
nodes[^1] + drawOffset, nodes[^1] + drawOffset,
item.DrawPosition, item.DrawPosition,
item.Color, 0.0f, Width); overrideColor ?? item.Color, 0.0f, Width);
} }
} }
} }
@@ -594,7 +594,7 @@ namespace Barotrauma.Items.Components
selectedWire.shouldClearConnections = false; selectedWire.shouldClearConnections = false;
Character.Controlled.Inventory.TryPutItem(selectedWire.item, Character.Controlled, new List<InvSlotType> { InvSlotType.LeftHand, InvSlotType.RightHand }); 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) if (entity is Item item)
{ {
@@ -117,6 +117,13 @@ namespace Barotrauma.Items.Components
get { return barrelSprite; } get { return barrelSprite; }
} }
[Serialize(false, IsPropertySaveable.No)]
public bool HideBarrelWhenBroken
{
get;
private set;
}
partial void InitProjSpecific(ContentXElement element) partial void InitProjSpecific(ContentXElement element)
{ {
foreach (var subElement in element.Elements()) foreach (var subElement in element.Elements())
@@ -232,7 +239,7 @@ namespace Barotrauma.Items.Components
{ {
moveSoundChannel.FadeOutAndDispose(); moveSoundChannel.FadeOutAndDispose();
moveSoundChannel = SoundPlayer.PlaySound(endMoveSound.Sound, item.WorldPosition, endMoveSound.Volume, endMoveSound.Range, ignoreMuffling: endMoveSound.IgnoreMuffling, freqMult: endMoveSound.GetRandomFrequencyMultiplier()); 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) else if (!moveSoundChannel.IsPlaying)
{ {
@@ -261,12 +268,13 @@ namespace Barotrauma.Items.Components
if (chargeSound != null) if (chargeSound != null)
{ {
chargeSoundChannel = SoundPlayer.PlaySound(chargeSound.Sound, item.WorldPosition, chargeSound.Volume, chargeSound.Range, ignoreMuffling: chargeSound.IgnoreMuffling, freqMult: chargeSound.GetRandomFrequencyMultiplier()); 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) else if (chargeSoundChannel != null)
{ {
chargeSoundChannel.FrequencyMultiplier = MathHelper.Lerp(0.5f, 1.5f, chargeRatio); chargeSoundChannel.FrequencyMultiplier = MathHelper.Lerp(0.5f, 1.5f, chargeRatio);
chargeSoundChannel.Position = new Vector3(item.WorldPosition, 0.0f);
} }
break; break;
default: default:
@@ -318,7 +326,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) if (crosshairSprite != null)
{ {
@@ -359,7 +367,7 @@ namespace Barotrauma.Items.Components
return new Vector2((float)Math.Cos(rotation), (float)Math.Sin(rotation)) * recoilOffset; 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)) if (!MathUtils.NearlyEqual(item.Rotation, prevBaseRotation) || !MathUtils.NearlyEqual(item.Scale, prevScale))
{ {
@@ -367,53 +375,55 @@ namespace Barotrauma.Items.Components
} }
Vector2 drawPos = GetDrawPos(); Vector2 drawPos = GetDrawPos();
if (item.Condition > 0.0f || !HideBarrelWhenBroken)
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)
{ {
chargeSprite?.Draw(spriteBatch, railSprite?.Draw(spriteBatch,
drawPos - MathUtils.RotatePoint(new Vector2(position.X * chargeRatio, position.Y * chargeRatio) * item.Scale, rotation + MathHelper.PiOver2), drawPos,
item.SpriteColor, overrideColor ?? item.SpriteColor,
rotation + MathHelper.PiOver2, item.Scale, 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; barrelSprite?.Draw(spriteBatch,
drawPos - GetRecoilOffset() * item.Scale,
for (int i = 0; i < spinningBarrelCount; i++) overrideColor ?? item.SpriteColor,
{
// 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),
rotation + MathHelper.PiOver2, item.Scale, 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) if (GameMain.DebugDraw)
@@ -593,6 +603,7 @@ namespace Barotrauma.Items.Components
if (!recipient.IsPower || !recipient.IsOutput) { continue; } if (!recipient.IsPower || !recipient.IsOutput) { continue; }
var battery = recipient.Item?.GetComponent<PowerContainer>(); var battery = recipient.Item?.GetComponent<PowerContainer>();
if (battery == null || battery.Item.Condition <= 0.0f) { continue; } if (battery == null || battery.Item.Condition <= 0.0f) { continue; }
if (battery.OutputDisabled) { continue; }
availableCharge += battery.Charge; availableCharge += battery.Charge;
availableCapacity += battery.GetCapacity(); availableCapacity += battery.GetCapacity();
} }
@@ -19,7 +19,7 @@ namespace Barotrauma.Items.Components
get { return Vector2.Zero; } 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; if (dockingState == 0.0f) return;
@@ -39,7 +39,8 @@ namespace Barotrauma.Items.Components
drawPos, drawPos,
new Rectangle( new Rectangle(
rect.Center.X + (int)(rect.Width / 2 * (1.0f - dockingState)), rect.Y, 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 else
@@ -48,7 +49,8 @@ namespace Barotrauma.Items.Components
drawPos - Vector2.UnitX * (rect.Width / 2 * dockingState), drawPos - Vector2.UnitX * (rect.Width / 2 * dockingState),
new Rectangle( new Rectangle(
rect.X, rect.Y, rect.X, rect.Y,
(int)(rect.Width / 2 * dockingState), rect.Height), Color.White); (int)(rect.Width / 2 * dockingState), rect.Height),
overrideColor ?? Color.White);
} }
} }
else else
@@ -61,7 +63,8 @@ namespace Barotrauma.Items.Components
drawPos - Vector2.UnitY * (rect.Height / 2 * dockingState), drawPos - Vector2.UnitY * (rect.Height / 2 * dockingState),
new Rectangle( new Rectangle(
rect.X, rect.Y, rect.X, rect.Y,
rect.Width, (int)(rect.Height / 2 * dockingState)), Color.White); rect.Width, (int)(rect.Height / 2 * dockingState)),
overrideColor ?? Color.White);
} }
else else
{ {
@@ -69,7 +72,8 @@ namespace Barotrauma.Items.Components
drawPos, drawPos,
new Rectangle( new Rectangle(
rect.X, rect.Y + rect.Height / 2 + (int)(rect.Height / 2 * (1.0f - dockingState)), 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);
} }
} }
} }
@@ -182,6 +182,7 @@ namespace Barotrauma
public Rectangle BackgroundFrame { get; protected set; } public Rectangle BackgroundFrame { get; protected set; }
private List<ushort>[] partialReceivedItemIDs;
private List<ushort>[] receivedItemIDs; private List<ushort>[] receivedItemIDs;
private CoroutineHandle syncItemsCoroutine; private CoroutineHandle syncItemsCoroutine;
@@ -257,7 +258,7 @@ namespace Barotrauma
else else
{ {
LocalizedString description = item.Description; 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[] readTags = item.Tags.Split(',');
string idName = null; string idName = null;
@@ -347,6 +348,9 @@ namespace Barotrauma
{ {
toolTip += item.Prefab.GetSkillRequirementHints(character); toolTip += item.Prefab.GetSkillRequirementHints(character);
} }
#if DEBUG
toolTip += $" ({item.Prefab.Identifier})";
#endif
return RichString.Rich(toolTip); return RichString.Rich(toolTip);
} }
} }
@@ -387,6 +391,12 @@ namespace Barotrauma
/// </summary> /// </summary>
public RectTransform RectTransform; 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 public static SlotReference SelectedSlot
{ {
get get
@@ -417,6 +427,7 @@ namespace Barotrauma
padding = new Vector4(spacing.X, spacing.Y, spacing.X, spacing.X); padding = new Vector4(spacing.X, spacing.Y, spacing.X, spacing.X);
Vector2 slotAreaSize = new Vector2( Vector2 slotAreaSize = new Vector2(
columns * rectSize.X + (columns - 1) * spacing.X, columns * rectSize.X + (columns - 1) * spacing.X,
rows * rectSize.Y + (rows - 1) * spacing.Y); rows * rectSize.Y + (rows - 1) * spacing.Y);
@@ -427,6 +438,8 @@ namespace Barotrauma
GameMain.GraphicsWidth / 2 - slotAreaSize.X / 2, GameMain.GraphicsWidth / 2 - slotAreaSize.X / 2,
GameMain.GraphicsHeight / 2 - slotAreaSize.Y / 2); GameMain.GraphicsHeight / 2 - slotAreaSize.Y / 2);
Vector2 center = topLeft + slotAreaSize / 2;
if (RectTransform != null) if (RectTransform != null)
{ {
Vector2 scale = new Vector2( Vector2 scale = new Vector2(
@@ -438,6 +451,8 @@ namespace Barotrauma
padding.X *= scale.X; padding.Z *= scale.X; padding.X *= scale.X; padding.Z *= scale.X;
padding.Y *= scale.Y; padding.W *= scale.Y; padding.Y *= scale.Y; padding.W *= scale.Y;
center = RectTransform.Rect.Center.ToVector2();
topLeft = RectTransform.TopLeft.ToVector2() + new Vector2(padding.X, padding.Y); topLeft = RectTransform.TopLeft.ToVector2() + new Vector2(padding.X, padding.Y);
prevRect = RectTransform.Rect; 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); Rectangle slotRect = new Rectangle((int)topLeft.X, (int)topLeft.Y, (int)rectSize.X, (int)rectSize.Y);
for (int i = 0; i < capacity; i++) for (int i = 0; i < capacity; i++)
{ {
slotRect.X = (int)(topLeft.X + (rectSize.X + spacing.X) * (i % slotsPerRow)); int row = (int)Math.Floor((double)i / slotsPerRow);
slotRect.Y = (int)(topLeft.Y + (rectSize.Y + spacing.Y) * ((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] = new VisualSlot(slotRect);
visualSlots[i].InteractRect = new Rectangle( visualSlots[i].InteractRect = new Rectangle(
(int)(visualSlots[i].Rect.X - spacing.X / 2 - 1), (int)(visualSlots[i].Rect.Y - spacing.Y / 2 - 1), (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); 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()); return visualSlots[i].Disabled || (slots[i].HideIfEmpty && slots[i].Empty());
} }
@@ -673,6 +694,7 @@ namespace Barotrauma
var container = item.GetComponent<ItemContainer>(); var container = item.GetComponent<ItemContainer>();
if (container == null || !container.DrawInventory) { return; } if (container == null || !container.DrawInventory) { return; }
if (container.Inventory.DrawWhenEquipped) { return; }
var subInventory = container.Inventory; var subInventory = container.Inventory;
if (subInventory.visualSlots == null) { subInventory.CreateSlots(); } if (subInventory.visualSlots == null) { subInventory.CreateSlots(); }
@@ -871,44 +893,34 @@ namespace Barotrauma
if (Character.Controlled.Inventory != null && !isSubEditor) if (Character.Controlled.Inventory != null && !isSubEditor)
{ {
var inv = Character.Controlled.Inventory; if (IsOnInventorySlot(Character.Controlled.Inventory)) { return true; }
for (var i = 0; i < inv.visualSlots.Length; i++) }
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)) if (slot.InteractRect.Contains(PlayerInput.MousePosition))
{ {
return true; return true;
} }
// check if the equip button actually exists // check if the equip button actually exists
if (slot.EquipButtonRect.Contains(PlayerInput.MousePosition) && if (slot.EquipButtonRect.Contains(PlayerInput.MousePosition) &&
i >= 0 && inv.slots.Length > i && i >= 0 && inventory.slots.Length > i &&
!inv.slots[i].Empty()) !inventory.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())
{ {
return true; return true;
} }
} }
return false;
} }
if (Character.Controlled.SelectedItem != null) if (Character.Controlled.SelectedItem != null)
@@ -1034,7 +1046,7 @@ namespace Barotrauma
protected static void DrawToolTip(SpriteBatch spriteBatch, RichString toolTip, Rectangle highlightedSlot) 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) public void DrawSubInventory(SpriteBatch spriteBatch, int slotIndex)
@@ -1046,6 +1058,7 @@ namespace Barotrauma
if (container == null || !container.DrawInventory) { return; } if (container == null || !container.DrawInventory) { return; }
if (container.Inventory.visualSlots == null || !container.Inventory.isSubInventory) { return; } if (container.Inventory.visualSlots == null || !container.Inventory.isSubInventory) { return; }
if (container.Inventory.DrawWhenEquipped) { return; }
int itemCapacity = container.Capacity; int itemCapacity = container.Capacity;
@@ -1215,8 +1228,8 @@ namespace Barotrauma
else else
{ {
DraggingItems.ForEachMod(it => it.Drop(Character.Controlled)); DraggingItems.ForEachMod(it => it.Drop(Character.Controlled));
DraggingItems.First().CreateDroppedStack(DraggingItems, allowClientExecute: false);
} }
SoundPlayer.PlayUISound(removed ? GUISoundType.PickItem : GUISoundType.DropItem); SoundPlayer.PlayUISound(removed ? GUISoundType.PickItem : GUISoundType.DropItem);
} }
} }
@@ -1258,11 +1271,19 @@ namespace Barotrauma
{ {
allowCombine = false; allowCombine = false;
} }
int itemCount = 0;
foreach (Item item in DraggingItems) foreach (Item item in DraggingItems)
{ {
bool success = selectedInventory.TryPutItem(item, slotIndex, allowSwapping: !anySuccess, allowCombine, Character.Controlled); bool success = selectedInventory.TryPutItem(item, slotIndex, allowSwapping: !anySuccess, allowCombine, Character.Controlled);
anySuccess |= success; if (success)
if (!success) { break; } {
anySuccess = true;
itemCount++;
}
if (!success || itemCount >= item.Prefab.GetMaxStackSize(selectedInventory))
{
break;
}
} }
if (anySuccess) if (anySuccess)
@@ -1360,10 +1381,12 @@ namespace Barotrauma
protected static Rectangle GetSubInventoryHoverArea(SlotReference subSlot) protected static Rectangle GetSubInventoryHoverArea(SlotReference subSlot)
{ {
Rectangle hoverArea; 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)) || (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 = subSlot.Slot.Rect;
hoverArea.Location += subSlot.Slot.DrawOffset.ToPoint(); hoverArea.Location += subSlot.Slot.DrawOffset.ToPoint();
hoverArea = Rectangle.Union(hoverArea, subSlot.Slot.EquipButtonRect); hoverArea = Rectangle.Union(hoverArea, subSlot.Slot.EquipButtonRect);
@@ -1372,7 +1395,10 @@ namespace Barotrauma
{ {
hoverArea = subSlot.Inventory.BackgroundFrame; hoverArea = subSlot.Inventory.BackgroundFrame;
hoverArea.Location += subSlot.Slot.DrawOffset.ToPoint(); 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) if (subSlot.Inventory?.visualSlots != null)
@@ -1465,18 +1491,28 @@ namespace Barotrauma
color: Character.Controlled.FocusedItem == null && !mouseOnHealthInterface ? GUIStyle.Red : Color.LightGreen, color: Character.Controlled.FocusedItem == null && !mouseOnHealthInterface ? GUIStyle.Red : Color.LightGreen,
font: GUIStyle.SmallFont); font: GUIStyle.SmallFont);
} }
Item draggedItem = DraggingItems.First();
sprite.Draw(spriteBatch, itemPos + Vector2.One * 2, Color.Black, scale: scale); sprite.Draw(spriteBatch, itemPos + Vector2.One * 2, Color.Black, scale: scale);
sprite.Draw(spriteBatch, sprite.Draw(spriteBatch,
itemPos, itemPos,
sprite == DraggingItems.First().Sprite ? DraggingItems.First().GetSpriteColor() : DraggingItems.First().GetInventoryIconColor(), sprite == draggedItem.Sprite ? draggedItem.GetSpriteColor() : draggedItem.GetInventoryIconColor(),
scale: scale); 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; 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 + 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, color: GUIStyle.Red,
scale: iconSize.X / stealIcon.size.X); scale: iconSize.X / stealIcon.size.X);
} }
int maxStackSize = item.Prefab.MaxStackSize; int maxStackSize = item.Prefab.GetMaxStackSize(inventory);
if (inventory is ItemInventory itemInventory) if (inventory is ItemInventory itemInventory)
{ {
maxStackSize = Math.Min(maxStackSize, itemInventory.Container.GetMaxStackSize(slotIndex)); 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; RectangleF cdRect = rect;
// shrink the rect from top to bottom depending on HealingCooldown.NormalizedCooldown // 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(); 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 //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 //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; } if (!receivedItemIDs[i].Any()) { continue; }
foreach (UInt16 id in receivedItemIDs[i]) 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)) if (!TryPutItem(item, i, false, false, null, false))
{ {
ForceToSlot(item, i); ForceToSlot(item, i);
@@ -1883,11 +1924,5 @@ namespace Barotrauma
receivedItemIDs = null; receivedItemIDs = null;
} }
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
{
SharedWrite(msg, extraData);
syncItemsDelay = 1.0f;
}
} }
} }

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