Release v0.15.12.0

This commit is contained in:
Joonas Rikkonen
2021-10-27 18:50:57 +03:00
parent bf95e82d80
commit 234fb6bc06
450 changed files with 26042 additions and 10457 deletions
@@ -48,8 +48,13 @@ namespace Barotrauma
{ {
color = Color.CornflowerBlue; color = Color.CornflowerBlue;
} }
else if (Entity is Item) else if (Entity is Item i)
{ {
if (i.Submarine != null && i.GetComponent<Items.Components.Door>() == 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.
return;
}
color = Color.CadetBlue; color = Color.CadetBlue;
} }
else else
@@ -81,10 +81,10 @@ namespace Barotrauma
ConvertUnits.ToDisplayUnits(new Vector2(attachJoint.WorldAnchorB.X, -attachJoint.WorldAnchorB.Y)), GUI.Style.Green, 0, 4); ConvertUnits.ToDisplayUnits(new Vector2(attachJoint.WorldAnchorB.X, -attachJoint.WorldAnchorB.Y)), GUI.Style.Green, 0, 4);
} }
if (LatchOntoAI.WallAttachPos.HasValue) if (LatchOntoAI.AttachPos.HasValue)
{ {
//GUI.DrawLine(spriteBatch, pos, GUI.DrawLine(spriteBatch, pos,
// ConvertUnits.ToDisplayUnits(new Vector2(LatchOntoAI.WallAttachPos.Value.X, -LatchOntoAI.WallAttachPos.Value.Y)), GUI.Style.Green, 0, 3); ConvertUnits.ToDisplayUnits(new Vector2(LatchOntoAI.AttachPos.Value.X, -LatchOntoAI.AttachPos.Value.Y)), GUI.Style.Green, 0, 3);
} }
} }
@@ -284,7 +284,7 @@ namespace Barotrauma
limb.LastImpactSoundTime = (float)Timing.TotalTime; limb.LastImpactSoundTime = (float)Timing.TotalTime;
if (!string.IsNullOrWhiteSpace(limb.HitSoundTag)) if (!string.IsNullOrWhiteSpace(limb.HitSoundTag))
{ {
bool inWater = limb.inWater; bool inWater = limb.InWater;
if (character.CurrentHull != null && if (character.CurrentHull != null &&
character.CurrentHull.Surface > character.CurrentHull.Rect.Y - character.CurrentHull.Rect.Height + 5.0f && character.CurrentHull.Surface > character.CurrentHull.Rect.Y - character.CurrentHull.Rect.Height + 5.0f &&
limb.SimPosition.Y < ConvertUnits.ToSimUnits(character.CurrentHull.Rect.Y - character.CurrentHull.Rect.Height) + limb.body.GetMaxExtent()) limb.SimPosition.Y < ConvertUnits.ToSimUnits(character.CurrentHull.Rect.Y - character.CurrentHull.Rect.Height) + limb.body.GetMaxExtent())
@@ -342,22 +342,41 @@ namespace Barotrauma
partial void SetupDrawOrder() partial void SetupDrawOrder()
{ {
//make sure every character gets drawn at a distinct "layer" //make sure every character gets drawn at a distinct "layer"
//(instead of having some of the limbs appear behind and some in front of other characters) //(instead of having some of the limbs appear behind and some in front of other characters)
float startDepth = 0.1f; float startDepth = 0.1f;
float increment = 0.001f; float increment = 0.001f;
foreach (Character otherCharacter in Character.CharacterList) foreach (Character otherCharacter in Character.CharacterList)
{ {
if (otherCharacter == character) continue; if (otherCharacter == character) { continue; }
startDepth += increment; startDepth += increment;
} }
//make sure each limb has a distinct depth value //make sure each limb has a distinct depth value
List<Limb> depthSortedLimbs = Limbs.OrderBy(l => l.ActiveSprite == null ? 0.0f : l.ActiveSprite.Depth).ToList(); List<Limb> depthSortedLimbs = Limbs.OrderBy(l => l.DefaultSpriteDepth).ToList();
foreach (Limb limb in Limbs) foreach (Limb limb in Limbs)
{ {
if (limb.ActiveSprite != null) var sprite = limb.GetActiveSprite();
limb.ActiveSprite.Depth = startDepth + depthSortedLimbs.IndexOf(limb) * 0.00001f; if (sprite == null) { continue; }
sprite.Depth = startDepth + depthSortedLimbs.IndexOf(limb) * 0.00001f;
foreach (var conditionalSprite in limb.ConditionalSprites)
{
if (conditionalSprite.Exclusive)
{
conditionalSprite.ActiveSprite.Depth = sprite.Depth;
}
}
} }
foreach (Limb limb in Limbs)
{
if (limb.ActiveSprite == null) { continue; }
if (limb.Params.InheritLimbDepth == LimbType.None) { continue; }
var matchingLimb = GetLimb(limb.Params.InheritLimbDepth);
if (matchingLimb != null)
{
limb.ActiveSprite.Depth = matchingLimb.ActiveSprite.Depth - 0.0000001f;
}
}
depthSortedLimbs.Reverse(); depthSortedLimbs.Reverse();
inversedLimbDrawOrder = depthSortedLimbs.ToArray(); inversedLimbDrawOrder = depthSortedLimbs.ToArray();
} }
@@ -562,9 +581,16 @@ namespace Barotrauma
if (this is HumanoidAnimController humanoid) if (this is HumanoidAnimController humanoid)
{ {
Vector2 pos = ConvertUnits.ToDisplayUnits(humanoid.RightHandIKPos); Vector2 pos = ConvertUnits.ToDisplayUnits(humanoid.RightHandIKPos);
if (humanoid.character.Submarine != null) { pos += humanoid.character.Submarine.Position; }
GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)-pos.Y, 4, 4), GUI.Style.Green, true); GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)-pos.Y, 4, 4), GUI.Style.Green, true);
pos = ConvertUnits.ToDisplayUnits(humanoid.LeftHandIKPos); pos = ConvertUnits.ToDisplayUnits(humanoid.LeftHandIKPos);
if (humanoid.character.Submarine != null) { pos += humanoid.character.Submarine.Position; }
GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)-pos.Y, 4, 4), GUI.Style.Green, true); GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)-pos.Y, 4, 4), GUI.Style.Green, true);
Vector2 aimPos = humanoid.AimSourceWorldPos;
aimPos.Y = -aimPos.Y;
GUI.DrawLine(spriteBatch, aimPos - Vector2.UnitY * 3, aimPos + Vector2.UnitY * 3, Color.Red);
GUI.DrawLine(spriteBatch, aimPos - Vector2.UnitX * 3, aimPos + Vector2.UnitX * 3, Color.Red);
} }
if (character.MemState.Count > 1) if (character.MemState.Count > 1)
@@ -1,5 +1,4 @@
using Barotrauma.Sounds; using Barotrauma.Particles;
using Barotrauma.Particles;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System.Xml.Linq; using System.Xml.Linq;
@@ -21,7 +21,6 @@ namespace Barotrauma
public static bool DebugDrawInteract; public static bool DebugDrawInteract;
protected float soundTimer; protected float soundTimer;
protected float soundInterval;
protected float hudInfoTimer = 1.0f; protected float hudInfoTimer = 1.0f;
protected bool hudInfoVisible = false; protected bool hudInfoVisible = false;
@@ -129,8 +128,52 @@ namespace Barotrauma
get { return gibEmitters; } get { return gibEmitters; }
} }
private class GUIMessage
{
public string RawText;
public string Identifier;
public string Text;
private int _value;
public int Value
{
get { return _value; }
set
{
_value = value;
Text = RawText.Replace("[value]", _value.ToString());
Size = GUI.Font.MeasureString(Text);
}
}
public Color Color;
public float Lifetime;
public float Timer;
public Vector2 Size;
public bool PlaySound;
public GUIMessage(string rawText, Color color, float delay, string identifier = null, int? value = null)
{
RawText = Text = rawText;
if (value.HasValue)
{
Text = rawText.Replace("[value]", value.Value.ToString());
Value = value.Value;
}
Timer = -delay;
Size = GUI.Font.MeasureString(Text);
Color = color;
Identifier = identifier;
Lifetime = 3.0f;
}
}
private List<GUIMessage> guiMessages = new List<GUIMessage>();
public static bool IsMouseOnUI => GUI.MouseOn != null || public static bool IsMouseOnUI => GUI.MouseOn != null ||
(CharacterInventory.IsMouseOnInventory() && !CharacterInventory.DraggingItemToWorld); (CharacterInventory.IsMouseOnInventory && !CharacterInventory.DraggingItemToWorld);
public class ObjectiveEntity public class ObjectiveEntity
{ {
@@ -161,8 +204,7 @@ namespace Barotrauma
partial void InitProjSpecific(XElement mainElement) partial void InitProjSpecific(XElement mainElement)
{ {
soundInterval = mainElement.GetAttributeFloat("soundinterval", 10.0f); soundTimer = Rand.Range(0.0f, Params.SoundInterval);
soundTimer = Rand.Range(0.0f, soundInterval);
sounds = new List<CharacterSound>(); sounds = new List<CharacterSound>();
Params.Sounds.ForEach(s => sounds.Add(new CharacterSound(s))); Params.Sounds.ForEach(s => sounds.Add(new CharacterSound(s)));
@@ -390,12 +432,7 @@ namespace Barotrauma
{ {
if (attackResult.Damage <= 1.0f) { return; } if (attackResult.Damage <= 1.0f) { return; }
} }
PlaySound(CharacterSound.SoundType.Damage, maxInterval: 2);
if (soundTimer < soundInterval * 0.5f)
{
PlaySound(CharacterSound.SoundType.Damage);
soundTimer = soundInterval;
}
} }
partial void KillProjSpecific(CauseOfDeathType causeOfDeath, Affliction causeOfDeathAffliction, bool log) partial void KillProjSpecific(CauseOfDeathType causeOfDeath, Affliction causeOfDeathAffliction, bool log)
@@ -412,7 +449,7 @@ namespace Barotrauma
if (GameMain.NetworkMember.RespawnManager?.UseRespawnPrompt ?? false) if (GameMain.NetworkMember.RespawnManager?.UseRespawnPrompt ?? false)
{ {
CoroutineManager.InvokeAfter(() => CoroutineManager.Invoke(() =>
{ {
if (controlled != null || (!(GameMain.GameSession?.IsRunning ?? false))) { return; } if (controlled != null || (!(GameMain.GameSession?.IsRunning ?? false))) { return; }
var respawnPrompt = new GUIMessageBox( var respawnPrompt = new GUIMessageBox(
@@ -470,9 +507,9 @@ namespace Barotrauma
} }
private List<Item> debugInteractablesInRange = new List<Item>(); private readonly List<Item> debugInteractablesInRange = new List<Item>();
private List<Item> debugInteractablesAtCursor = new List<Item>(); private readonly List<Item> debugInteractablesAtCursor = new List<Item>();
private List<Pair<Item, float>> debugInteractablesNearCursor = new List<Pair<Item, float>>(); private readonly List<(Item item, float dist)> debugInteractablesNearCursor = new List<(Item item, float dist)>();
/// <summary> /// <summary>
/// Finds the front (lowest depth) interactable item at a position. "Interactable" in this case means that the character can "reach" the item. /// Finds the front (lowest depth) interactable item at a position. "Interactable" in this case means that the character can "reach" the item.
@@ -568,7 +605,7 @@ namespace Barotrauma
if (distanceToItem > closestItemDistance) { continue; } if (distanceToItem > closestItemDistance) { continue; }
if (!CanInteractWith(item)) { continue; } if (!CanInteractWith(item)) { continue; }
debugInteractablesNearCursor.Add(new Pair<Item, float>(item, 1.0f - distanceToItem / (100.0f * aimAssistModifier))); debugInteractablesNearCursor.Add((item, 1.0f - distanceToItem / (100.0f * aimAssistModifier)));
closestItem = item; closestItem = item;
closestItemDistance = distanceToItem; closestItemDistance = distanceToItem;
} }
@@ -579,31 +616,20 @@ namespace Barotrauma
private Character FindCharacterAtPosition(Vector2 mouseSimPos, float maxDist = 150.0f) private Character FindCharacterAtPosition(Vector2 mouseSimPos, float maxDist = 150.0f)
{ {
Character closestCharacter = null; Character closestCharacter = null;
float closestDist = 0.0f;
maxDist = ConvertUnits.ToSimUnits(maxDist); maxDist = ConvertUnits.ToSimUnits(maxDist);
float closestDist = maxDist * maxDist;
foreach (Character c in CharacterList) foreach (Character c in CharacterList)
{ {
if (!CanInteractWith(c, checkVisibility: false) || (c.AnimController?.SimplePhysicsEnabled ?? true)) { continue; } if (!CanInteractWith(c, checkVisibility: false) || (c.AnimController?.SimplePhysicsEnabled ?? true)) { continue; }
float dist = Vector2.DistanceSquared(mouseSimPos, c.SimPosition); float dist = Vector2.DistanceSquared(mouseSimPos, c.SimPosition);
if (dist < maxDist * maxDist && (closestCharacter == null || dist < closestDist)) if (dist < closestDist ||
(c.CampaignInteractionType != CampaignMode.InteractionType.None && closestCharacter?.CampaignInteractionType == CampaignMode.InteractionType.None && dist * 0.9f < closestDist))
{ {
closestCharacter = c; closestCharacter = c;
closestDist = dist; closestDist = dist;
} }
/*FarseerPhysics.Common.Transform transform;
c.AnimController.Collider.FarseerBody.GetTransform(out transform);
for (int i = 0; i < c.AnimController.Collider.FarseerBody.FixtureList.Count; i++)
{
if (c.AnimController.Collider.FarseerBody.FixtureList[i].Shape.TestPoint(ref transform, ref mouseSimPos))
{
Console.WriteLine("Hit: " + i);
closestCharacter = c;
}
}*/
} }
return closestCharacter; return closestCharacter;
@@ -636,9 +662,20 @@ namespace Barotrauma
} }
} }
foreach (GUIMessage message in guiMessages)
{
bool wasPending = message.Timer < 0.0f;
message.Timer += deltaTime;
if (wasPending && message.Timer >= 0.0f && message.PlaySound)
{
SoundPlayer.PlayUISound(GUISoundType.UIMessage);
}
}
guiMessages.RemoveAll(m => m.Timer >= m.Lifetime);
if (!enabled) { return; } if (!enabled) { return; }
if (!IsDead && !IsIncapacitated) if (!IsIncapacitated)
{ {
if (soundTimer > 0) if (soundTimer > 0)
{ {
@@ -649,7 +686,14 @@ namespace Barotrauma
switch (enemyAI.State) switch (enemyAI.State)
{ {
case AIState.Attack: case AIState.Attack:
PlaySound(CharacterSound.SoundType.Attack); if (Rand.Value() > 0.5f)
{
PlaySound(CharacterSound.SoundType.Attack);
}
else
{
PlaySound(CharacterSound.SoundType.Idle);
}
break; break;
default: default:
var petBehavior = enemyAI.PetBehavior; var petBehavior = enemyAI.PetBehavior;
@@ -660,7 +704,6 @@ namespace Barotrauma
else else
{ {
PlaySound(CharacterSound.SoundType.Idle); PlaySound(CharacterSound.SoundType.Idle);
} }
break; break;
} }
@@ -749,6 +792,27 @@ namespace Barotrauma
if (drawHealth && !CharacterHUD.IsCampaignInterfaceOpen) { CharacterHealth.DrawHUD(spriteBatch); } if (drawHealth && !CharacterHUD.IsCampaignInterfaceOpen) { CharacterHealth.DrawHUD(spriteBatch); }
} }
public void DrawGUIMessages(SpriteBatch spriteBatch, Camera cam)
{
if (info == null || !Enabled || InvisibleTimer > 0.0f)
{
return;
}
Vector2 messagePos = DrawPosition;
messagePos.Y += hudInfoHeight;
messagePos = cam.WorldToScreen(messagePos) - Vector2.UnitY * GUI.IntScale(60);
foreach (GUIMessage message in guiMessages)
{
if (message.Timer < 0) { continue; }
Vector2 drawPos = messagePos + Vector2.UnitX * (GUI.IntScale(60) - message.Size.X);
drawPos = new Vector2((int)drawPos.X, (int)drawPos.Y);
float alpha = MathHelper.SmoothStep(1.0f, 0.0f, message.Timer / message.Lifetime);
GUI.DrawString(spriteBatch, drawPos, message.Text, message.Color * alpha);
messagePos -= Vector2.UnitY * message.Size.Y * 1.2f;
}
}
public virtual void DrawFront(SpriteBatch spriteBatch, Camera cam) public virtual void DrawFront(SpriteBatch spriteBatch, Camera cam)
{ {
if (!Enabled || InvisibleTimer > 0.0f || (AnimController?.SimplePhysicsEnabled ?? true)) { return; } if (!Enabled || InvisibleTimer > 0.0f || (AnimController?.SimplePhysicsEnabled ?? true)) { return; }
@@ -827,12 +891,12 @@ namespace Barotrauma
GUI.DrawLine(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y), GUI.DrawLine(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y),
new Vector2(item.DrawPosition.X, -item.DrawPosition.Y), Color.White * 0.1f, width: 4); new Vector2(item.DrawPosition.X, -item.DrawPosition.Y), Color.White * 0.1f, width: 4);
} }
foreach (Pair<Item, float> item in debugInteractablesNearCursor) foreach ((Item item, float dist) in debugInteractablesNearCursor)
{ {
GUI.DrawLine(spriteBatch, GUI.DrawLine(spriteBatch,
cursorPos, cursorPos,
new Vector2(item.First.DrawPosition.X, -item.First.DrawPosition.Y), new Vector2(item.DrawPosition.X, -item.DrawPosition.Y),
ToolBox.GradientLerp(item.Second, GUI.Style.Red, GUI.Style.Orange, GUI.Style.Green), width: 2); ToolBox.GradientLerp(dist, GUI.Style.Red, GUI.Style.Orange, GUI.Style.Green), width: 2);
} }
} }
return; return;
@@ -856,6 +920,7 @@ namespace Barotrauma
Vector2 nameSize = GUI.Font.MeasureString(name); Vector2 nameSize = GUI.Font.MeasureString(name);
Vector2 namePos = new Vector2(pos.X, pos.Y - 10.0f - (5.0f / cam.Zoom)) - nameSize * 0.5f / cam.Zoom; Vector2 namePos = new Vector2(pos.X, pos.Y - 10.0f - (5.0f / cam.Zoom)) - nameSize * 0.5f / cam.Zoom;
Color nameColor = GetNameColor();
Vector2 screenSize = new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight); Vector2 screenSize = new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
Vector2 viewportSize = new Vector2(cam.WorldView.Width, cam.WorldView.Height); Vector2 viewportSize = new Vector2(cam.WorldView.Width, cam.WorldView.Height);
@@ -865,18 +930,6 @@ namespace Barotrauma
namePos *= viewportSize / screenSize; namePos *= viewportSize / screenSize;
namePos.X += cam.WorldView.X; namePos.Y -= cam.WorldView.Y; namePos.X += cam.WorldView.X; namePos.Y -= cam.WorldView.Y;
Color nameColor = Color.White;
if (Controlled != null && TeamID != Controlled.TeamID)
{
if (TeamID == CharacterTeamType.FriendlyNPC)
{
nameColor = UniqueNameColor ?? Color.SkyBlue;
}
else
{
nameColor = GUI.Style.Red;
}
}
if (CampaignInteractionType != CampaignMode.InteractionType.None && AllowCustomInteract) if (CampaignInteractionType != CampaignMode.InteractionType.None && AllowCustomInteract)
{ {
var iconStyle = GUI.Style.GetComponentStyle("CampaignInteractionBubble." + CampaignInteractionType); var iconStyle = GUI.Style.GetComponentStyle("CampaignInteractionBubble." + CampaignInteractionType);
@@ -931,6 +984,89 @@ namespace Barotrauma
} }
} }
public Color GetNameColor()
{
CharacterTeamType team = teamID;
if (Info?.IsDisguisedAsAnother != null)
{
var idCard = Inventory.GetItemInLimbSlot(InvSlotType.Card)?.GetComponent<IdCard>();
if (idCard != null)
{
if (team == CharacterTeamType.Team2 && idCard.TeamID != CharacterTeamType.Team2)
{
team = CharacterTeamType.Team1;
}
else if (team == CharacterTeamType.Team1 && idCard.TeamID == CharacterTeamType.Team2)
{
team = CharacterTeamType.Team2;
}
}
}
Color nameColor = GUI.Style.TextColor;
if (Controlled != null && team != Controlled.TeamID)
{
if (TeamID == CharacterTeamType.FriendlyNPC)
{
nameColor = UniqueNameColor ?? Color.SkyBlue;
}
else
{
nameColor = GUI.Style.Red;
}
}
return nameColor;
}
public void AddMessage(string rawText, Color color, bool playSound, string identifier = null, int? value = null)
{
GUIMessage existingMessage = null;
float delay = 0.0f;
if (guiMessages.Any())
{
delay = guiMessages.Min(m => m.Timer) - 0.5f;
if (delay < 0)
{
delay = -delay;
if (guiMessages.Count > 5)
{
//reduce delays if there's lots of messages
guiMessages.Where(m => m.Timer < 0.0f).ForEach(m => m.Timer *= 0.9f);
}
}
else
{
delay = 0;
}
}
if (identifier != null)
{
existingMessage = guiMessages.Find(m => m.Identifier == identifier && m.Timer < m.Lifetime * 0.5f);
}
if (existingMessage == null || !value.HasValue)
{
var newMessage = new GUIMessage(rawText, color, delay, identifier, value);
guiMessages.Insert(0, newMessage);
if (playSound)
{
if (delay > 0.0f)
{
newMessage.PlaySound = true;
}
else
{
SoundPlayer.PlayUISound(GUISoundType.UIMessage);
}
}
}
else
{
existingMessage.Value += value.Value;
}
}
/// <summary> /// <summary>
/// Creates a progress bar that's "linked" to the specified object (or updates an existing one if there's one already linked to the object) /// Creates a progress bar that's "linked" to the specified object (or updates an existing one if there's one already linked to the object)
/// The progress bar will automatically fade out after 1 sec if the method hasn't been called during that time /// The progress bar will automatically fade out after 1 sec if the method hasn't been called during that time
@@ -958,12 +1094,13 @@ namespace Barotrauma
private readonly List<CharacterSound> matchingSounds = new List<CharacterSound>(); private readonly List<CharacterSound> matchingSounds = new List<CharacterSound>();
private SoundChannel soundChannel; private SoundChannel soundChannel;
public void PlaySound(CharacterSound.SoundType soundType, float soundIntervalFactor = 1.0f) public void PlaySound(CharacterSound.SoundType soundType, float soundIntervalFactor = 1.0f, float maxInterval = 0)
{ {
if (sounds == null || sounds.Count == 0) { return; } if (sounds == null || sounds.Count == 0) { return; }
if (soundChannel != null && soundChannel.IsPlaying) { return; } if (soundChannel != null && soundChannel.IsPlaying) { return; }
if (GameMain.SoundManager?.Disabled ?? true) { return; } if (GameMain.SoundManager?.Disabled ?? true) { return; }
if (soundTimer > soundInterval * soundIntervalFactor) { return; } if (soundTimer > Params.SoundInterval * soundIntervalFactor) { return; }
if (Params.SoundInterval - soundTimer < maxInterval) { return; }
matchingSounds.Clear(); matchingSounds.Clear();
foreach (var s in sounds) foreach (var s in sounds)
{ {
@@ -975,7 +1112,7 @@ namespace Barotrauma
var selectedSound = matchingSounds.GetRandom(); var selectedSound = matchingSounds.GetRandom();
if (selectedSound?.Sound == null) { return; } if (selectedSound?.Sound == null) { return; }
soundChannel = SoundPlayer.PlaySound(selectedSound.Sound, AnimController.WorldPosition, selectedSound.Volume, selectedSound.Range, hullGuess: CurrentHull, ignoreMuffling: selectedSound.IgnoreMuffling); soundChannel = SoundPlayer.PlaySound(selectedSound.Sound, AnimController.WorldPosition, selectedSound.Volume, selectedSound.Range, hullGuess: CurrentHull, ignoreMuffling: selectedSound.IgnoreMuffling);
soundTimer = soundInterval; soundTimer = Params.SoundInterval;
} }
public void AddActiveObjectiveEntity(Entity entity, Sprite sprite, Color? color = null) public void AddActiveObjectiveEntity(Entity entity, Sprite sprite, Color? color = null)
@@ -1028,5 +1165,20 @@ namespace Barotrauma
Rand.Range(50.0f, 500.0f), null); Rand.Range(50.0f, 500.0f), null);
} }
} }
partial void OnMoneyChanged(int prevAmount, int newAmount)
{
if (newAmount > prevAmount)
{
int increase = newAmount - prevAmount;
AddMessage("+" + TextManager.GetWithVariable("currencyformat", "[credits]", "[value]"),
GUI.Style.Yellow, playSound: this == Controlled, "money", increase);
}
}
partial void OnTalentGiven(string talentIdentifier)
{
AddMessage(TextManager.Get("talentname." + talentIdentifier.ToString()), GUI.Style.Yellow, playSound: this == Controlled);
}
} }
} }
@@ -100,7 +100,7 @@ namespace Barotrauma
} }
} }
private static bool shouldRecreateHudTexts = true; public static bool ShouldRecreateHudTexts { get; set; } = true;
private static bool heldDownShiftWhenGotHudTexts; private static bool heldDownShiftWhenGotHudTexts;
public static bool IsCampaignInterfaceOpen => public static bool IsCampaignInterfaceOpen =>
@@ -150,7 +150,7 @@ namespace Barotrauma
} }
} }
if (character.IsHumanoid && character.SelectedCharacter != null) if (character.Params.CanInteract && character.SelectedCharacter != null)
{ {
character.SelectedCharacter.CharacterHealth.AddToGUIUpdateList(); character.SelectedCharacter.CharacterHealth.AddToGUIUpdateList();
} }
@@ -195,7 +195,7 @@ namespace Barotrauma
} }
} }
if (character.IsHumanoid && character.SelectedCharacter != null && character.SelectedCharacter.Inventory != null) if (character.Params.CanInteract && character.SelectedCharacter != null && character.SelectedCharacter.Inventory != null)
{ {
if (character.SelectedCharacter.CanInventoryBeAccessed) if (character.SelectedCharacter.CanInventoryBeAccessed)
{ {
@@ -219,7 +219,7 @@ namespace Barotrauma
if (focusedItemOverlayTimer <= 0.0f) if (focusedItemOverlayTimer <= 0.0f)
{ {
focusedItem = null; focusedItem = null;
shouldRecreateHudTexts = true; ShouldRecreateHudTexts = true;
} }
} }
} }
@@ -285,6 +285,21 @@ namespace Barotrauma
i.GetRootInventoryOwner() == i); i.GetRootInventoryOwner() == i);
} }
if (GameMain.GameSession != null)
{
foreach (var mission in GameMain.GameSession.Missions)
{
if (!mission.DisplayTargetHudIcons) { continue; }
foreach (var target in mission.HudIconTargets)
{
if (target.Submarine != character.Submarine) { continue; }
float alpha = GetDistanceBasedIconAlpha(target, maxDistance: mission.Prefab.HudIconMaxDistance);
if (alpha <= 0.0f) { continue; }
GUI.DrawIndicator(spriteBatch, target.DrawPosition, cam, 100.0f, mission.Prefab.HudIcon, mission.Prefab.HudIconColor * alpha);
}
}
}
foreach (Character.ObjectiveEntity objectiveEntity in character.ActiveObjectiveEntities) foreach (Character.ObjectiveEntity objectiveEntity in character.ActiveObjectiveEntities)
{ {
DrawObjectiveIndicator(spriteBatch, cam, character, objectiveEntity, 1.0f); DrawObjectiveIndicator(spriteBatch, cam, character, objectiveEntity, 1.0f);
@@ -317,7 +332,7 @@ namespace Barotrauma
if (focusedItem != character.FocusedItem) if (focusedItem != character.FocusedItem)
{ {
focusedItemOverlayTimer = Math.Min(1.0f, focusedItemOverlayTimer); focusedItemOverlayTimer = Math.Min(1.0f, focusedItemOverlayTimer);
shouldRecreateHudTexts = true; ShouldRecreateHudTexts = true;
} }
focusedItem = character.FocusedItem; focusedItem = character.FocusedItem;
} }
@@ -342,13 +357,13 @@ namespace Barotrauma
if (!GUI.DisableItemHighlights && !Inventory.DraggingItemToWorld) if (!GUI.DisableItemHighlights && !Inventory.DraggingItemToWorld)
{ {
bool shiftDown = PlayerInput.IsShiftDown(); bool shiftDown = PlayerInput.IsShiftDown();
if (shouldRecreateHudTexts || heldDownShiftWhenGotHudTexts != shiftDown) if (ShouldRecreateHudTexts || heldDownShiftWhenGotHudTexts != shiftDown)
{ {
shouldRecreateHudTexts = true; ShouldRecreateHudTexts = true;
heldDownShiftWhenGotHudTexts = shiftDown; heldDownShiftWhenGotHudTexts = shiftDown;
} }
var hudTexts = focusedItem.GetHUDTexts(character, shouldRecreateHudTexts); var hudTexts = focusedItem.GetHUDTexts(character, ShouldRecreateHudTexts);
shouldRecreateHudTexts = false; ShouldRecreateHudTexts = false;
int dir = Math.Sign(focusedItem.WorldPosition.X - character.WorldPosition.X); int dir = Math.Sign(focusedItem.WorldPosition.X - character.WorldPosition.X);
@@ -391,7 +406,26 @@ namespace Barotrauma
if (npc.CampaignInteractionType == CampaignMode.InteractionType.None || npc.Submarine != character.Submarine || npc.IsDead || npc.IsIncapacitated) { continue; } if (npc.CampaignInteractionType == CampaignMode.InteractionType.None || npc.Submarine != character.Submarine || npc.IsDead || npc.IsIncapacitated) { continue; }
var iconStyle = GUI.Style.GetComponentStyle("CampaignInteractionIcon." + npc.CampaignInteractionType); var iconStyle = GUI.Style.GetComponentStyle("CampaignInteractionIcon." + npc.CampaignInteractionType);
GUI.DrawIndicator(spriteBatch, npc.WorldPosition, cam, npc.CurrentHull == character.CurrentHull ? 500.0f : 100.0f, iconStyle.GetDefaultSprite(), iconStyle.Color); Range<float> visibleRange = new Range<float>(npc.CurrentHull == Character.Controlled.CurrentHull ? 500.0f : 100.0f, float.PositiveInfinity);
if (npc.CampaignInteractionType == CampaignMode.InteractionType.Examine)
{
//TODO: we could probably do better than just hardcoding
//a check for InteractionType.Examine here.
if (Vector2.DistanceSquared(character.Position, npc.Position) > 500f * 500f) { continue; }
var body = Submarine.CheckVisibility(character.SimPosition, npc.SimPosition, ignoreLevel: true);
if (body != null && body.UserData as Character != npc) { continue; }
visibleRange = new Range<float>(-100f, 500f);
}
GUI.DrawIndicator(
spriteBatch,
npc.WorldPosition,
cam,
visibleRange,
iconStyle.GetDefaultSprite(),
iconStyle.Color);
} }
foreach (Item item in Item.ItemList) foreach (Item item in Item.ItemList)
@@ -400,7 +434,7 @@ namespace Barotrauma
if (Vector2.DistanceSquared(character.Position, item.Position) > 500f*500f) { continue; } if (Vector2.DistanceSquared(character.Position, item.Position) > 500f*500f) { continue; }
var body = Submarine.CheckVisibility(character.SimPosition, item.SimPosition, ignoreLevel: true); var body = Submarine.CheckVisibility(character.SimPosition, item.SimPosition, ignoreLevel: true);
if (body != null && body.UserData as Item != item) { continue; } if (body != null && body.UserData as Item != item) { continue; }
GUI.DrawIndicator(spriteBatch, item.WorldPosition + new Vector2(0f, item.RectHeight * 0.65f), cam, new Vector2(-100f, 500.0f), item.IconStyle.GetDefaultSprite(), item.IconStyle.Color, createOffset: false); GUI.DrawIndicator(spriteBatch, item.WorldPosition + new Vector2(0f, item.RectHeight * 0.65f), cam, new Range<float>(-100f, 500.0f), item.IconStyle.GetDefaultSprite(), item.IconStyle.Color, createOffset: false);
} }
} }
@@ -471,7 +505,7 @@ namespace Barotrauma
if (!character.IsIncapacitated && character.Stun <= 0.0f) if (!character.IsIncapacitated && character.Stun <= 0.0f)
{ {
if (character.IsHumanoid && character.SelectedCharacter != null && character.SelectedCharacter.Inventory != null) if (character.Params.CanInteract && character.SelectedCharacter != null && character.SelectedCharacter.Inventory != null)
{ {
if (character.SelectedCharacter.CanInventoryBeAccessed) if (character.SelectedCharacter.CanInventoryBeAccessed)
{ {
@@ -525,12 +559,7 @@ namespace Barotrauma
textPos -= new Vector2(textSize.X / 2, textSize.Y); textPos -= new Vector2(textSize.X / 2, textSize.Y);
Color nameColor = GUI.Style.TextColor; Color nameColor = character.FocusedCharacter.GetNameColor();
if (character.TeamID != character.FocusedCharacter.TeamID)
{
nameColor = character.FocusedCharacter.TeamID == CharacterTeamType.FriendlyNPC ? Color.SkyBlue : GUI.Style.Red;
}
GUI.DrawString(spriteBatch, textPos, focusName, nameColor, Color.Black * 0.7f, 2, GUI.SubHeadingFont); GUI.DrawString(spriteBatch, textPos, focusName, nameColor, Color.Black * 0.7f, 2, GUI.SubHeadingFont);
textPos.X += 10.0f * GUI.Scale; textPos.X += 10.0f * GUI.Scale;
textPos.Y += GUI.SubHeadingFont.MeasureString(focusName).Y; textPos.Y += GUI.SubHeadingFont.MeasureString(focusName).Y;
@@ -544,11 +573,14 @@ namespace Barotrauma
if (character.FocusedCharacter.CanBeDragged) if (character.FocusedCharacter.CanBeDragged)
{ {
GUI.DrawString(spriteBatch, textPos, GetCachedHudText("GrabHint", GameMain.Config.KeyBindText(InputType.Grab)), string text = character.CanEat ? "EatHint" : "GrabHint";
GUI.DrawString(spriteBatch, textPos, GetCachedHudText(text, GameMain.Config.KeyBindText(InputType.Grab)),
GUI.Style.Green, Color.Black, 2, GUI.SmallFont); GUI.Style.Green, Color.Black, 2, GUI.SmallFont);
textPos.Y += largeTextSize.Y; textPos.Y += largeTextSize.Y;
} }
if (!character.DisableHealthWindow && if (!character.DisableHealthWindow &&
character.IsFriendly(character.FocusedCharacter) &&
character.FocusedCharacter.CharacterHealth.UseHealthWindow && character.FocusedCharacter.CharacterHealth.UseHealthWindow &&
character.CanInteractWith(character.FocusedCharacter, 160f, false)) character.CanInteractWith(character.FocusedCharacter, 160f, false))
{ {
@@ -17,11 +17,19 @@ namespace Barotrauma
public bool LastControlled; public bool LastControlled;
#warning TODO: Refactor
private Sprite disguisedPortrait; private Sprite disguisedPortrait;
private List<WearableSprite> disguisedAttachmentSprites; private List<WearableSprite> disguisedAttachmentSprites;
private Vector2? disguisedSheetIndex; private Vector2? disguisedSheetIndex;
private Sprite disguisedJobIcon; private Sprite disguisedJobIcon;
private Color disguisedJobColor; private Color disguisedJobColor;
private Color disguisedHairColor;
private Color disguisedFacialHairColor;
private Color disguisedSkinColor;
private Sprite tintMask;
private float tintHighlightThreshold;
private float tintHighlightMultiplier;
public static void Init() public static void Init()
{ {
@@ -29,6 +37,20 @@ namespace Barotrauma
new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(833, 298, 142, 98), null, 0); new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(833, 298, 142, 98), null, 0);
} }
partial void LoadHeadSpriteProjectSpecific(XElement limbElement)
{
XElement maskElement = limbElement.Element("tintmask");
if (maskElement != null)
{
string tintMaskPath = maskElement.GetAttributeString("texture", "");
if (!string.IsNullOrWhiteSpace(tintMaskPath))
{
tintMask = new Sprite(maskElement, file: Limb.GetSpritePath(tintMaskPath, this));
tintHighlightThreshold = maskElement.GetAttributeFloat("highlightthreshold", 0.6f);
tintHighlightMultiplier = maskElement.GetAttributeFloat("highlightmultiplier", 0.8f);
}
}
}
public GUIComponent CreateInfoFrame(GUIFrame frame, bool returnParent, Sprite permissionIcon = null) public GUIComponent CreateInfoFrame(GUIFrame frame, bool returnParent, Sprite permissionIcon = null)
{ {
@@ -143,10 +165,10 @@ namespace Barotrauma
private void DrawInfoFrameCharacterIcon(SpriteBatch sb, Rectangle componentRect) private void DrawInfoFrameCharacterIcon(SpriteBatch sb, Rectangle componentRect)
{ {
if (headSprite == null) { return; } if (_headSprite == null) { return; }
Vector2 targetAreaSize = componentRect.Size.ToVector2(); Vector2 targetAreaSize = componentRect.Size.ToVector2();
float scale = Math.Min(targetAreaSize.X / headSprite.size.X, targetAreaSize.Y / headSprite.size.Y); float scale = Math.Min(targetAreaSize.X / _headSprite.size.X, targetAreaSize.Y / _headSprite.size.Y);
DrawIcon(sb, componentRect.Location.ToVector2() + headSprite.size / 2 * scale, targetAreaSize); DrawIcon(sb, componentRect.Location.ToVector2() + _headSprite.size / 2 * scale, targetAreaSize);
} }
public GUIFrame CreateCharacterFrame(GUIComponent parent, string text, object userData) public GUIFrame CreateCharacterFrame(GUIComponent parent, string text, object userData)
@@ -165,21 +187,36 @@ namespace Barotrauma
return frame; return frame;
} }
partial void OnSkillChanged(string skillIdentifier, float prevLevel, float newLevel, Vector2 textPopupPos) partial void OnSkillChanged(string skillIdentifier, float prevLevel, float newLevel)
{ {
if (TeamID == CharacterTeamType.FriendlyNPC) { return; } if (TeamID == CharacterTeamType.FriendlyNPC) { return; }
if (Character.Controlled != null && Character.Controlled.TeamID != TeamID) { return; } if (Character.Controlled != null && Character.Controlled.TeamID != TeamID) { return; }
// if we increased by more than 1 in one increase, then display special color (for talents)
bool specialIncrease = Math.Abs(newLevel - prevLevel) >= 1.0f;
if ((int)newLevel > (int)prevLevel) if ((int)newLevel > (int)prevLevel)
{ {
int increase = Math.Max((int)newLevel - (int)prevLevel, 1); int increase = Math.Max((int)newLevel - (int)prevLevel, 1);
GUI.AddMessage( Character?.AddMessage(
string.Format("+{0} {1}", increase, TextManager.Get("SkillName." + skillIdentifier)), "+[value] "+ TextManager.Get("SkillName." + skillIdentifier),
GUI.Style.Green, specialIncrease ? GUI.Style.Orange : GUI.Style.Green,
textPopupPos, playSound: Character == Character.Controlled, skillIdentifier, increase);
Vector2.UnitY * 10.0f, }
playSound: false, }
subId: Character?.Submarine?.ID ?? -1);
partial void OnExperienceChanged(int prevAmount, int newAmount)
{
if (Character.Controlled != null && Character.Controlled.TeamID != TeamID) { return; }
GameSession.TabMenuInstance?.OnExperienceChanged(Character);
if (newAmount > prevAmount)
{
int increase = newAmount - prevAmount;
Character?.AddMessage(
"+[value] " + TextManager.Get("experienceshort"),
GUI.Style.Blue, playSound: Character == Character.Controlled, "exp", increase);
} }
} }
@@ -187,193 +224,36 @@ namespace Barotrauma
{ {
if (idCard.Item.Tags == string.Empty) return; if (idCard.Item.Tags == string.Empty) return;
if (idCard.StoredJobPrefab == null || idCard.StoredPortrait == null) if (idCard.StoredOwnerAppearance.JobPrefab == null || idCard.StoredOwnerAppearance.Portrait == null)
{ {
string[] readTags = idCard.Item.Tags.Split(','); string[] readTags = idCard.Item.Tags.Split(',');
if (readTags.Length == 0) return; if (readTags.Length == 0) { return; }
if (idCard.StoredJobPrefab == null) if (idCard.StoredOwnerAppearance.JobPrefab == null)
{ {
string jobIdTag = readTags.FirstOrDefault(s => s.StartsWith("jobid:")); idCard.StoredOwnerAppearance.ExtractJobPrefab(readTags);
if (jobIdTag != null && jobIdTag.Length > 6)
{
string jobId = jobIdTag.Substring(6);
if (jobId != string.Empty)
{
idCard.StoredJobPrefab = JobPrefab.Get(jobId);
}
}
} }
if (idCard.StoredPortrait == null) if (idCard.StoredOwnerAppearance.Portrait == null)
{ {
string disguisedGender = string.Empty; idCard.StoredOwnerAppearance.ExtractAppearance(this, readTags);
string disguisedRace = string.Empty;
string disguisedHeadSpriteId = string.Empty;
int disguisedHairIndex = -1;
int disguisedBeardIndex = -1;
int disguisedMoustacheIndex = -1;
int disguisedFaceAttachmentIndex = -1;
foreach (string tag in readTags)
{
string[] s = tag.Split(':');
switch (s[0])
{
case "gender":
disguisedGender = s[1];
break;
case "race":
disguisedRace = s[1];
break;
case "headspriteid":
disguisedHeadSpriteId = s[1];
break;
case "hairindex":
disguisedHairIndex = int.Parse(s[1]);
break;
case "beardindex":
disguisedBeardIndex = int.Parse(s[1]);
break;
case "moustacheindex":
disguisedMoustacheIndex = int.Parse(s[1]);
break;
case "faceattachmentindex":
disguisedFaceAttachmentIndex = int.Parse(s[1]);
break;
case "sheetindex":
string[] vectorValues = s[1].Split(";");
idCard.StoredSheetIndex = new Vector2(float.Parse(vectorValues[0]), float.Parse(vectorValues[1]));
break;
}
}
if (disguisedGender == string.Empty || disguisedRace == string.Empty || disguisedHeadSpriteId == string.Empty)
{
idCard.StoredPortrait = null;
idCard.StoredAttachments = null;
return;
}
foreach (XElement limbElement in Ragdoll.MainElement.Elements())
{
if (!limbElement.GetAttributeString("type", "").Equals("head", StringComparison.OrdinalIgnoreCase)) { continue; }
XElement spriteElement = limbElement.Element("sprite");
if (spriteElement == null) { continue; }
string spritePath = spriteElement.Attribute("texture").Value;
spritePath = spritePath.Replace("[GENDER]", disguisedGender);
spritePath = spritePath.Replace("[RACE]", disguisedRace.ToLowerInvariant());
spritePath = spritePath.Replace("[HEADID]", disguisedHeadSpriteId);
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))
{
continue;
}
string fileWithoutTags = Path.GetFileNameWithoutExtension(file);
fileWithoutTags = fileWithoutTags.Split('[', ']').First();
if (fileWithoutTags != fileName) { continue; }
idCard.StoredPortrait = new Sprite(spriteElement, "", file) { RelativeOrigin = Vector2.Zero };
break;
}
break;
}
if (Wearables != null)
{
XElement disguisedHairElement, disguisedBeardElement, disguisedMoustacheElement, disguisedFaceAttachmentElement;
List<XElement> disguisedHairs, disguisedBeards, disguisedMoustaches, disguisedFaceAttachments;
Gender disguisedGenderEnum = disguisedGender == "female" ? Gender.Female : Gender.Male;
Race disguisedRaceEnum = (Race)Enum.Parse(typeof(Race), disguisedRace);
int headSpriteId = int.Parse(disguisedHeadSpriteId);
float commonness = disguisedGenderEnum == Gender.Female ? 0.05f : 0.2f;
disguisedHairs = AddEmpty(FilterByTypeAndHeadID(FilterElementsByGenderAndRace(wearables, disguisedGenderEnum, disguisedRaceEnum), WearableType.Hair, headSpriteId), WearableType.Hair, commonness);
disguisedBeards = AddEmpty(FilterByTypeAndHeadID(FilterElementsByGenderAndRace(wearables, disguisedGenderEnum, disguisedRaceEnum), WearableType.Beard, headSpriteId), WearableType.Beard);
disguisedMoustaches = AddEmpty(FilterByTypeAndHeadID(FilterElementsByGenderAndRace(wearables, disguisedGenderEnum, disguisedRaceEnum), WearableType.Moustache, headSpriteId), WearableType.Moustache);
disguisedFaceAttachments = AddEmpty(FilterByTypeAndHeadID(FilterElementsByGenderAndRace(wearables, disguisedGenderEnum, disguisedRaceEnum), WearableType.FaceAttachment, headSpriteId), WearableType.FaceAttachment);
if (IsValidIndex(disguisedHairIndex, disguisedHairs))
{
disguisedHairElement = disguisedHairs[disguisedHairIndex];
}
else
{
disguisedHairElement = GetRandomElement(disguisedHairs);
}
if (IsValidIndex(disguisedBeardIndex, disguisedBeards))
{
disguisedBeardElement = disguisedBeards[disguisedBeardIndex];
}
else
{
disguisedBeardElement = GetRandomElement(disguisedBeards);
}
if (IsValidIndex(disguisedMoustacheIndex, disguisedMoustaches))
{
disguisedMoustacheElement = disguisedMoustaches[disguisedMoustacheIndex];
}
else
{
disguisedMoustacheElement = GetRandomElement(disguisedMoustaches);
}
if (IsValidIndex(disguisedFaceAttachmentIndex, disguisedFaceAttachments))
{
disguisedFaceAttachmentElement = disguisedFaceAttachments[disguisedFaceAttachmentIndex];
}
else
{
disguisedFaceAttachmentElement = GetRandomElement(disguisedFaceAttachments);
}
idCard.StoredAttachments = new List<WearableSprite>();
disguisedFaceAttachmentElement?.Elements("sprite").ForEach(s => idCard.StoredAttachments.Add(new WearableSprite(s, WearableType.FaceAttachment)));
disguisedBeardElement?.Elements("sprite").ForEach(s => idCard.StoredAttachments.Add(new WearableSprite(s, WearableType.Beard)));
disguisedMoustacheElement?.Elements("sprite").ForEach(s => idCard.StoredAttachments.Add(new WearableSprite(s, WearableType.Moustache)));
disguisedHairElement?.Elements("sprite").ForEach(s => idCard.StoredAttachments.Add(new WearableSprite(s, WearableType.Hair)));
if (OmitJobInPortraitClothing)
{
JobPrefab.NoJobElement?.Element("PortraitClothing")?.Elements("sprite").ForEach(s => idCard.StoredAttachments.Add(new WearableSprite(s, WearableType.JobIndicator)));
}
else
{
idCard.StoredJobPrefab?.ClothingElement?.Elements("sprite").ForEach(s => idCard.StoredAttachments.Add(new WearableSprite(s, WearableType.JobIndicator)));
}
}
} }
} }
if (idCard.StoredJobPrefab != null) if (idCard.StoredOwnerAppearance.JobPrefab != null)
{ {
disguisedJobIcon = idCard.StoredJobPrefab.Icon; disguisedJobIcon = idCard.StoredOwnerAppearance.JobPrefab.Icon;
disguisedJobColor = idCard.StoredJobPrefab.UIColor; disguisedJobColor = idCard.StoredOwnerAppearance.JobPrefab.UIColor;
} }
disguisedPortrait = idCard.StoredPortrait; disguisedPortrait = idCard.StoredOwnerAppearance.Portrait;
disguisedSheetIndex = idCard.StoredSheetIndex; disguisedSheetIndex = idCard.StoredOwnerAppearance.SheetIndex;
disguisedAttachmentSprites = idCard.StoredAttachments; disguisedAttachmentSprites = idCard.StoredOwnerAppearance.Attachments;
disguisedHairColor = idCard.StoredOwnerAppearance.HairColor;
disguisedFacialHairColor = idCard.StoredOwnerAppearance.FacialHairColor;
disguisedSkinColor = idCard.StoredOwnerAppearance.SkinColor;
} }
partial void LoadAttachmentSprites(bool omitJob) partial void LoadAttachmentSprites(bool omitJob)
@@ -422,43 +302,107 @@ namespace Barotrauma
public void DrawPortrait(SpriteBatch spriteBatch, Vector2 screenPos, Vector2 offset, float targetWidth, bool flip = false, bool evaluateDisguise = false) public void DrawPortrait(SpriteBatch spriteBatch, Vector2 screenPos, Vector2 offset, float targetWidth, bool flip = false, bool evaluateDisguise = false)
{ {
if (evaluateDisguise && IsDisguised) return; if (evaluateDisguise && IsDisguised) { return; }
Vector2? sheetIndex; Vector2? sheetIndex;
Sprite portraitToDraw; Sprite portraitToDraw;
List<WearableSprite> attachmentsToDraw; List<WearableSprite> attachmentsToDraw;
Color hairColor;
Color facialHairColor;
Color skinColor;
if (!IsDisguisedAsAnother || !evaluateDisguise) if (!IsDisguisedAsAnother || !evaluateDisguise)
{ {
sheetIndex = Head.SheetIndex; sheetIndex = Head.SheetIndex;
portraitToDraw = Portrait; portraitToDraw = Portrait;
attachmentsToDraw = AttachmentSprites; attachmentsToDraw = AttachmentSprites;
hairColor = Head.HairColor;
facialHairColor = Head.FacialHairColor;
skinColor = Head.SkinColor;
} }
else else
{ {
sheetIndex = disguisedSheetIndex; sheetIndex = disguisedSheetIndex;
portraitToDraw = disguisedPortrait; portraitToDraw = disguisedPortrait;
attachmentsToDraw = disguisedAttachmentSprites; attachmentsToDraw = disguisedAttachmentSprites;
hairColor = disguisedHairColor;
facialHairColor = disguisedFacialHairColor;
skinColor = disguisedSkinColor;
} }
if (portraitToDraw != null) if (portraitToDraw != null)
{ {
var currEffect = spriteBatch.GetCurrentEffect();
// Scale down the head sprite 10% // Scale down the head sprite 10%
float scale = targetWidth * 0.9f / Portrait.size.X; float scale = targetWidth * 0.9f / Portrait.size.X;
if (sheetIndex.HasValue) if (sheetIndex.HasValue)
{ {
SetHeadEffect(spriteBatch);
portraitToDraw.SourceRect = new Rectangle(CalculateOffset(portraitToDraw, sheetIndex.Value.ToPoint()), portraitToDraw.SourceRect.Size); portraitToDraw.SourceRect = new Rectangle(CalculateOffset(portraitToDraw, sheetIndex.Value.ToPoint()), portraitToDraw.SourceRect.Size);
} }
portraitToDraw.Draw(spriteBatch, screenPos + offset, Color.White, portraitToDraw.Origin, scale: scale, spriteEffect: flip ? SpriteEffects.FlipHorizontally : SpriteEffects.None); portraitToDraw.Draw(spriteBatch, screenPos + offset, skinColor, portraitToDraw.Origin, scale: scale, spriteEffect: flip ? SpriteEffects.FlipHorizontally : SpriteEffects.None);
if (attachmentsToDraw != null) if (attachmentsToDraw != null)
{ {
float depthStep = 0.000001f; float depthStep = 0.000001f;
foreach (var attachment in attachmentsToDraw) foreach (var attachment in attachmentsToDraw)
{ {
DrawAttachmentSprite(spriteBatch, attachment, portraitToDraw, sheetIndex, screenPos + offset, scale, depthStep, flip ? SpriteEffects.FlipHorizontally : SpriteEffects.None); SetAttachmentEffect(spriteBatch, attachment);
DrawAttachmentSprite(spriteBatch, attachment, portraitToDraw, sheetIndex, screenPos + offset, scale, depthStep, GetAttachmentColor(attachment, hairColor, facialHairColor), flip ? SpriteEffects.FlipHorizontally : SpriteEffects.None);
depthStep += depthStep; depthStep += depthStep;
} }
} }
spriteBatch.SwapEffect(currEffect);
}
}
//TODO: I hate this so much :(
private SpriteBatch.EffectWithParams headEffectParameters;
private Dictionary<WearableType, SpriteBatch.EffectWithParams> attachmentEffectParameters
= new Dictionary<WearableType, SpriteBatch.EffectWithParams>();
private void SetHeadEffect(SpriteBatch spriteBatch)
{
headEffectParameters.Effect ??= GameMain.GameScreen.ThresholdTintEffect;
headEffectParameters.Params ??= new Dictionary<string, object>();
headEffectParameters.Params["xBaseTexture"] = HeadSprite.Texture;
headEffectParameters.Params["xTintMaskTexture"] = tintMask?.Texture ?? GUI.WhiteTexture;
headEffectParameters.Params["xCutoffTexture"] = GUI.WhiteTexture;
headEffectParameters.Params["baseToCutoffSizeRatio"] = 1.0f;
headEffectParameters.Params["highlightThreshold"] = tintHighlightThreshold;
headEffectParameters.Params["highlightMultiplier"] = tintHighlightMultiplier;
spriteBatch.SwapEffect(headEffectParameters);
}
private void SetAttachmentEffect(SpriteBatch spriteBatch, WearableSprite attachment)
{
if (!attachmentEffectParameters.ContainsKey(attachment.Type))
{
attachmentEffectParameters.Add(attachment.Type, new SpriteBatch.EffectWithParams(GameMain.GameScreen.ThresholdTintEffect, new Dictionary<string, object>()));
}
var parameters = attachmentEffectParameters[attachment.Type].Params;
parameters["xBaseTexture"] = attachment.Sprite.Texture;
parameters["xTintMaskTexture"] = GUI.WhiteTexture;
parameters["xCutoffTexture"] = GUI.WhiteTexture;
parameters["baseToCutoffSizeRatio"] = 1.0f;
parameters["highlightThreshold"] = tintHighlightThreshold;
parameters["highlightMultiplier"] = tintHighlightMultiplier;
spriteBatch.SwapEffect(attachmentEffectParameters[attachment.Type]);
}
private Color GetAttachmentColor(WearableSprite attachment, Color hairColor, Color facialHairColor)
{
switch (attachment.Type)
{
case WearableType.Hair:
return hairColor;
case WearableType.Beard:
case WearableType.Moustache:
return facialHairColor;
default:
return Color.White;
} }
} }
@@ -467,34 +411,28 @@ namespace Barotrauma
var headSprite = HeadSprite; var headSprite = HeadSprite;
if (headSprite != null) if (headSprite != null)
{ {
var currEffect = spriteBatch.GetCurrentEffect();
float scale = Math.Min(targetAreaSize.X / headSprite.size.X, targetAreaSize.Y / headSprite.size.Y); float scale = Math.Min(targetAreaSize.X / headSprite.size.X, targetAreaSize.Y / headSprite.size.Y);
if (Head.SheetIndex.HasValue) if (Head.SheetIndex.HasValue)
{ {
headSprite.SourceRect = new Rectangle(CalculateOffset(headSprite, Head.SheetIndex.Value.ToPoint()), headSprite.SourceRect.Size); headSprite.SourceRect = new Rectangle(CalculateOffset(headSprite, Head.SheetIndex.Value.ToPoint()), headSprite.SourceRect.Size);
} }
headSprite.Draw(spriteBatch, screenPos, scale: scale); SetHeadEffect(spriteBatch);
headSprite.Draw(spriteBatch, screenPos, scale: scale, color: SkinColor);
if (AttachmentSprites != null) if (AttachmentSprites != null)
{ {
float depthStep = 0.000001f; float depthStep = 0.000001f;
foreach (var attachment in AttachmentSprites) foreach (var attachment in AttachmentSprites)
{ {
DrawAttachmentSprite(spriteBatch, attachment, headSprite, Head.SheetIndex, screenPos, scale, depthStep); SetAttachmentEffect(spriteBatch, attachment);
DrawAttachmentSprite(spriteBatch, attachment, headSprite, Head.SheetIndex, screenPos, scale, depthStep, GetAttachmentColor(attachment, HairColor, FacialHairColor));
depthStep += depthStep; depthStep += depthStep;
} }
} }
spriteBatch.SwapEffect(currEffect);
} }
} }
public void DrawJobIcon(SpriteBatch spriteBatch, Vector2 pos, float scale = 1.0f, bool evaluateDisguise = false)
{
if (evaluateDisguise && IsDisguised) return;
var icon = !IsDisguisedAsAnother || !evaluateDisguise ? Job?.Prefab?.Icon : disguisedJobIcon;
if (icon == null) { return; }
Color iconColor = !IsDisguisedAsAnother || !evaluateDisguise ? Job.Prefab.UIColor : disguisedJobColor;
icon.Draw(spriteBatch, pos, iconColor, scale: scale);
}
public void DrawJobIcon(SpriteBatch spriteBatch, Rectangle area, bool evaluateDisguise = false) public void DrawJobIcon(SpriteBatch spriteBatch, Rectangle area, bool evaluateDisguise = false)
{ {
if (evaluateDisguise && IsDisguised) return; if (evaluateDisguise && IsDisguised) return;
@@ -505,7 +443,7 @@ namespace Barotrauma
icon.Draw(spriteBatch, area.Center.ToVector2(), iconColor, scale: Math.Min(area.Width / (float)icon.SourceRect.Width, area.Height / (float)icon.SourceRect.Height)); icon.Draw(spriteBatch, area.Center.ToVector2(), iconColor, scale: Math.Min(area.Width / (float)icon.SourceRect.Width, area.Height / (float)icon.SourceRect.Height));
} }
private void DrawAttachmentSprite(SpriteBatch spriteBatch, WearableSprite attachment, Sprite head, Vector2? sheetIndex, Vector2 drawPos, float scale, float depthStep, SpriteEffects spriteEffects = SpriteEffects.None) private void DrawAttachmentSprite(SpriteBatch spriteBatch, WearableSprite attachment, Sprite head, Vector2? sheetIndex, Vector2 drawPos, float scale, float depthStep, Color? color = null, SpriteEffects spriteEffects = SpriteEffects.None)
{ {
if (attachment.InheritSourceRect) if (attachment.InheritSourceRect)
{ {
@@ -522,7 +460,7 @@ namespace Barotrauma
attachment.Sprite.SourceRect = head.SourceRect; attachment.Sprite.SourceRect = head.SourceRect;
} }
} }
Vector2 origin = attachment.Sprite.Origin; Vector2 origin;
if (attachment.InheritOrigin) if (attachment.InheritOrigin)
{ {
origin = head.Origin; origin = head.Origin;
@@ -537,7 +475,7 @@ namespace Barotrauma
{ {
depth = head.Depth - depthStep; depth = head.Depth - depthStep;
} }
attachment.Sprite.Draw(spriteBatch, drawPos, Color.White, origin, rotate: 0, scale: scale, depth: depth, spriteEffect: spriteEffects); attachment.Sprite.Draw(spriteBatch, drawPos, color ?? Color.White, origin, rotate: 0, scale: scale, depth: depth, spriteEffect: spriteEffects);
} }
public static CharacterInfo ClientRead(string speciesName, IReadMessage inc) public static CharacterInfo ClientRead(string speciesName, IReadMessage inc)
@@ -552,6 +490,9 @@ namespace Barotrauma
int beardIndex = inc.ReadByte(); int beardIndex = inc.ReadByte();
int moustacheIndex = inc.ReadByte(); int moustacheIndex = inc.ReadByte();
int faceAttachmentIndex = inc.ReadByte(); int faceAttachmentIndex = inc.ReadByte();
Color skinColor = inc.ReadColorR8G8B8();
Color hairColor = inc.ReadColorR8G8B8();
Color facialHairColor = inc.ReadColorR8G8B8();
string ragdollFile = inc.ReadString(); string ragdollFile = inc.ReadString();
string jobIdentifier = inc.ReadString(); string jobIdentifier = inc.ReadString();
@@ -577,6 +518,9 @@ namespace Barotrauma
ID = infoID, ID = infoID,
}; };
ch.RecreateHead(headSpriteID,(Race)race, (Gender)gender, hairIndex, beardIndex, moustacheIndex, faceAttachmentIndex); ch.RecreateHead(headSpriteID,(Race)race, (Gender)gender, hairIndex, beardIndex, moustacheIndex, faceAttachmentIndex);
ch.SkinColor = skinColor;
ch.HairColor = hairColor;
ch.FacialHairColor = facialHairColor;
if (ch.Job != null) if (ch.Job != null)
{ {
foreach (KeyValuePair<string, float> skill in skillLevels) foreach (KeyValuePair<string, float> skill in skillLevels)
@@ -591,7 +535,449 @@ namespace Barotrauma
} }
ch.Job.Skills.RemoveAll(s => !skillLevels.ContainsKey(s.Identifier)); ch.Job.Skills.RemoveAll(s => !skillLevels.ContainsKey(s.Identifier));
} }
byte savedStatValueCount = inc.ReadByte();
for (int i = 0; i < savedStatValueCount; i++)
{
int statType = inc.ReadByte();
string statIdentifier = inc.ReadString();
float statValue = inc.ReadSingle();
bool removeOnDeath = inc.ReadBoolean();
ch.ChangeSavedStatValue((StatTypes)statType, statValue, statIdentifier, removeOnDeath);
}
ch.ExperiencePoints = inc.ReadUInt16();
ch.AdditionalTalentPoints = inc.ReadUInt16();
return ch; return ch;
} }
public void CreateIcon(RectTransform rectT)
{
LoadHeadAttachments();
new GUICustomComponent(rectT,
onDraw: (sb, component) => DrawIcon(sb, component.Rect.Center.ToVector2(), targetAreaSize: component.Rect.Size.ToVector2()));
}
public class AppearanceCustomizationMenu : IDisposable
{
public readonly CharacterInfo CharacterInfo;
public GUIListBox HeadSelectionList = null;
public bool HasIcon = true;
public GUIScrollBar.OnMovedHandler OnSliderMoved = null;
public GUIScrollBar.OnMovedHandler OnSliderReleased = null;
public Action<AppearanceCustomizationMenu> OnHeadSwitch = null;
private readonly GUIComponent parentComponent;
private readonly List<Sprite> characterSprites = new List<Sprite>();
public GUIButton RandomizeButton;
public AppearanceCustomizationMenu(CharacterInfo info, GUIComponent parent, bool hasIcon = true)
{
CharacterInfo = info;
parentComponent = parent;
HasIcon = hasIcon;
RecreateFrameContents();
}
public void RecreateFrameContents()
{
var info = CharacterInfo;
HeadSelectionList = null;
parentComponent.ClearChildren();
ClearSprites();
float contentWidth = HasIcon ? 0.75f : 1.0f;
var listBox = new GUIListBox(
new RectTransform(new Vector2(contentWidth, 1.0f), parentComponent.RectTransform,
Anchor.CenterLeft))
{ CanBeFocused = false, CanTakeKeyBoardFocus = false };
var content = listBox.Content;
info.LoadHeadAttachments();
if (HasIcon)
{
info.CreateIcon(
new RectTransform(new Vector2(0.25f, 1.0f), parentComponent.RectTransform, Anchor.CenterRight)
{ RelativeOffset = new Vector2(-0.01f, 0.0f) });
}
RectTransform createItemRectTransform(string labelTag, float width = 0.6f)
{
var layoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.166f), content.RectTransform));
var label = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), layoutGroup.RectTransform),
TextManager.Get(labelTag), font: GUI.SubHeadingFont);
var bottomItem = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.5f), layoutGroup.RectTransform),
style: null);
return new RectTransform(new Vector2(width, 1.0f), bottomItem.RectTransform, Anchor.Center);
}
RectTransform genderItemRT = createItemRectTransform("Gender", 1.0f);
GUILayoutGroup genderContainer =
new GUILayoutGroup(genderItemRT, isHorizontal: true)
{
Stretch = true,
RelativeSpacing = 0.05f
};
void createGenderButton(Gender gender)
{
new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), genderContainer.RectTransform),
TextManager.Get(gender.ToString()), style: "ListBoxElement")
{
UserData = gender,
OnClicked = OpenHeadSelection,
Selected = info.Gender == gender
};
}
createGenderButton(Gender.Male);
createGenderButton(Gender.Female);
int countAttachmentsOfType(WearableType wearableType)
=> info.FilterByTypeAndHeadID(
info.FilterElementsByGenderAndRace(info.Wearables, info.Head.gender, info.Head.race),
wearableType, info.HeadSpriteId).Count();
List<GUIScrollBar> attachmentSliders = new List<GUIScrollBar>();
void createAttachmentSlider(int initialValue, WearableType wearableType)
{
int attachmentCount = countAttachmentsOfType(wearableType);
if (attachmentCount > 0)
{
var labelTag = wearableType == WearableType.FaceAttachment
? "FaceAttachment.Accessories"
: $"FaceAttachment.{wearableType}";
var sliderItemRT = createItemRectTransform(labelTag);
var slider =
new GUIScrollBar(sliderItemRT, style: "GUISlider")
{
Range = new Vector2(0, attachmentCount),
StepValue = 1,
OnMoved = (bar, scroll) => SwitchAttachment(bar, wearableType),
OnReleased = OnSliderReleased,
BarSize = 1.0f / (float)(attachmentCount + 1)
};
slider.BarScrollValue = initialValue;
attachmentSliders.Add(slider);
}
}
createAttachmentSlider(info.HairIndex, WearableType.Hair);
createAttachmentSlider(info.BeardIndex, WearableType.Beard);
createAttachmentSlider(info.MoustacheIndex, WearableType.Moustache);
createAttachmentSlider(info.FaceAttachmentIndex, WearableType.FaceAttachment);
void createColorSelector(string labelTag, IEnumerable<(Color Color, float Commonness)> options, Func<Color> getter,
Action<Color> setter)
{
var selectorItemRT = createItemRectTransform(labelTag, 0.4f);
var dropdown =
new GUIDropDown(selectorItemRT)
{ AllowNonText = true };
var listBoxSize = dropdown.ListBox.RectTransform.RelativeSize;
dropdown.ListBox.RectTransform.RelativeSize = new Vector2(listBoxSize.X * 1.75f, listBoxSize.Y);
var dropdownButton = dropdown.GetChild<GUIButton>();
var buttonFrame =
new GUIFrame(
new RectTransform(Vector2.One * 0.7f, dropdownButton.RectTransform, Anchor.CenterLeft)
{ RelativeOffset = new Vector2(0.05f, 0.0f) }, style: null);
Color? previewingColor = null;
dropdown.OnSelected = (component, color) =>
{
previewingColor = null;
setter((Color)color);
buttonFrame.Color = getter();
buttonFrame.HoverColor = getter();
return true;
};
buttonFrame.Color = getter();
buttonFrame.HoverColor = getter();
dropdown.ListBox.UseGridLayout = true;
foreach (var option in options)
{
var optionElement =
new GUIFrame(
new RectTransform(new Vector2(0.25f, 1.0f / 3.0f),
dropdown.ListBox.Content.RectTransform),
style: "ListBoxElement")
{
UserData = option.Color,
CanBeFocused = true
};
var colorElement =
new GUIFrame(
new RectTransform(Vector2.One * 0.75f, optionElement.RectTransform, Anchor.Center,
scaleBasis: ScaleBasis.Smallest),
style: null)
{
Color = option.Color,
HoverColor = option.Color,
OutlineColor = Color.Lerp(Color.Black, option.Color, 0.5f),
CanBeFocused = false
};
}
var childToSelect = dropdown.ListBox.Content.FindChild(c => (Color)c.UserData == getter());
dropdown.Select(dropdown.ListBox.Content.GetChildIndex(childToSelect));
//The following exists to track mouseover to preview colors before selecting them
new GUICustomComponent(new RectTransform(Vector2.One, buttonFrame.RectTransform),
onUpdate: (deltaTime, component) =>
{
if (GUI.MouseOn is GUIFrame { Parent: { } p } hoveredFrame && dropdown.ListBox.Content.IsParentOf(hoveredFrame))
{
previewingColor ??= getter();
Color color = (Color)(dropdown.ListBox.Content.FindChild(c =>
c == hoveredFrame || c.IsParentOf(hoveredFrame))?.UserData ?? dropdown.SelectedData ?? getter());
setter(color);
buttonFrame.Color = getter();
buttonFrame.HoverColor = getter();
}
else if (previewingColor.HasValue)
{
setter(previewingColor.Value);
buttonFrame.Color = getter();
buttonFrame.HoverColor = getter();
previewingColor = null;
}
}, onDraw: null)
{
CanBeFocused = false,
Visible = true
};
}
if (countAttachmentsOfType(WearableType.Hair) > 0)
{
createColorSelector($"Customization.{nameof(info.HairColor)}", info.HairColors,
() => info.HairColor, (color) => info.HairColor = color);
}
if (countAttachmentsOfType(WearableType.Moustache) > 0 ||
countAttachmentsOfType(WearableType.Beard) > 0)
{
createColorSelector($"Customization.{nameof(info.FacialHairColor)}", info.FacialHairColors,
() => info.FacialHairColor, (color) => info.FacialHairColor = color);
}
createColorSelector($"Customization.{nameof(info.SkinColor)}", info.SkinColors, () => info.SkinColor,
(color) => info.SkinColor = color);
RandomizeButton = new GUIButton(new RectTransform(Vector2.One * 0.12f,
parentComponent.RectTransform,
anchor: Anchor.BottomRight, scaleBasis: ScaleBasis.Smallest)
{ RelativeOffset = new Vector2(0.01f, 0.005f) }, style: "RandomizeButton")
{
OnClicked = (button, o) =>
{
info.Head = new HeadInfo();
info.SetGenderAndRace(Rand.RandSync.Unsynced);
info.SetColors();
RecreateFrameContents();
info.RefreshHead();
OnHeadSwitch?.Invoke(this);
attachmentSliders.ForEach(s => OnSliderMoved?.Invoke(s, s.BarScroll));
return false;
}
};
//force update twice because the listbox is insanely janky
//TODO: fix all of the UI :)
listBox.ForceUpdate();
listBox.ForceUpdate();
foreach (var childLayoutGroup in listBox.Content.GetAllChildren<GUILayoutGroup>())
{
childLayoutGroup.Recalculate();
}
}
private bool OpenHeadSelection(GUIButton button, object userData)
{
Gender selectedGender = (Gender)userData;
var info = CharacterInfo;
float characterHeightWidthRatio = info.HeadSprite.size.Y / info.HeadSprite.size.X;
HeadSelectionList ??= new GUIListBox(
new RectTransform(
new Point(parentComponent.Rect.Width,
(int)(parentComponent.Rect.Width * characterHeightWidthRatio * 0.6f)), GUI.Canvas)
{
AbsoluteOffset = new Point(parentComponent.Rect.Right - parentComponent.Rect.Width,
button.Rect.Bottom)
});
HeadSelectionList.Visible = true;
HeadSelectionList.Content.ClearChildren();
ClearSprites();
parentComponent.RectTransform.SizeChanged += () =>
{
if (parentComponent == null || HeadSelectionList?.RectTransform == null || button == null)
{
return;
}
HeadSelectionList.RectTransform.Resize(new Point(parentComponent.Rect.Width,
(int)(parentComponent.Rect.Width * characterHeightWidthRatio * 0.6f)));
HeadSelectionList.RectTransform.AbsoluteOffset =
new Point(parentComponent.Rect.Right - parentComponent.Rect.Width, button.Rect.Bottom);
};
new GUIFrame(
new RectTransform(new Vector2(1.25f, 1.25f), HeadSelectionList.RectTransform, Anchor.Center),
style: "OuterGlow", color: Color.Black)
{
UserData = "outerglow",
CanBeFocused = false
};
GUILayoutGroup row = null;
int itemsInRow = 0;
XElement headElement = info.Ragdoll.MainElement.Elements().FirstOrDefault(e =>
e.GetAttributeString("type", "").Equals("head", StringComparison.OrdinalIgnoreCase));
XElement headSpriteElement = headElement.Element("sprite");
string spritePathWithTags = headSpriteElement.Attribute("texture").Value;
var characterConfigElement = info.CharacterConfigElement;
var heads = info.Heads;
if (heads != null)
{
row = null;
itemsInRow = 0;
foreach (var kvp in heads.Where(kv => kv.Key.Gender == selectedGender))
{
var headPreset = kvp.Key;
Race race = headPreset.Race;
int headIndex = headPreset.ID;
string spritePath = spritePathWithTags
.Replace("[GENDER]", selectedGender.ToString().ToLowerInvariant())
.Replace("[RACE]", race.ToString().ToLowerInvariant());
if (!File.Exists(spritePath))
{
continue;
}
Sprite headSprite = new Sprite(headSpriteElement, "", spritePath);
headSprite.SourceRect =
new Rectangle(CalculateOffset(headSprite, kvp.Value.ToPoint()),
headSprite.SourceRect.Size);
characterSprites.Add(headSprite);
if (itemsInRow >= 4 || row == null)
{
row = new GUILayoutGroup(
new RectTransform(new Vector2(1.0f, 0.333f), HeadSelectionList.Content.RectTransform),
true)
{
UserData = selectedGender,
Visible = true
};
itemsInRow = 0;
}
var btn = new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), row.RectTransform),
style: "ListBoxElementSquare")
{
OutlineColor = Color.White * 0.5f,
PressedColor = Color.White * 0.5f,
UserData = new Tuple<Gender, Race, int>(selectedGender, race, headIndex),
OnClicked = SwitchHead,
Selected = selectedGender == info.Gender && race == info.Race && headIndex == info.HeadSpriteId,
Visible = true
};
new GUIImage(new RectTransform(Vector2.One, btn.RectTransform), headSprite, scaleToFit: true);
itemsInRow++;
}
}
return false;
}
private bool SwitchHead(GUIButton button, object obj)
{
var info = CharacterInfo;
Gender gender = ((Tuple<Gender, Race, int>)obj).Item1;
Race race = ((Tuple<Gender, Race, int>)obj).Item2;
int id = ((Tuple<Gender, Race, int>)obj).Item3;
info.Gender = gender;
info.Race = race;
info.Head.HeadSpriteId = id;
RecreateFrameContents();
OnHeadSwitch?.Invoke(this);
return true;
}
private bool SwitchAttachment(GUIScrollBar scrollBar, WearableType type)
{
var info = CharacterInfo;
int index = (int)scrollBar.BarScrollValue;
switch (type)
{
case WearableType.Beard:
info.BeardIndex = index;
break;
case WearableType.FaceAttachment:
info.FaceAttachmentIndex = index;
break;
case WearableType.Hair:
info.HairIndex = index;
break;
case WearableType.Moustache:
info.MoustacheIndex = index;
break;
default:
DebugConsole.ThrowError($"Wearable type not implemented: {type}");
return false;
}
info.RefreshHead();
OnSliderMoved?.Invoke(scrollBar, scrollBar.BarScroll);
return true;
}
public void Update()
{
if (HeadSelectionList != null && PlayerInput.PrimaryMouseButtonDown() &&
!GUI.IsMouseOn(HeadSelectionList))
{
HeadSelectionList.Visible = false;
}
}
public void AddToGUIUpdateList()
{
HeadSelectionList?.AddToGUIUpdateList();
}
private void ClearSprites()
{
foreach (Sprite sprite in characterSprites) { sprite.Remove(); }
characterSprites.Clear();
}
public void Dispose()
{
ClearSprites();
}
~AppearanceCustomizationMenu()
{
Dispose();
}
}
} }
} }
@@ -119,15 +119,23 @@ namespace Barotrauma
switch ((NetEntityEvent.Type)extraData[0]) switch ((NetEntityEvent.Type)extraData[0])
{ {
case NetEntityEvent.Type.InventoryState: case NetEntityEvent.Type.InventoryState:
msg.WriteRangedInteger(0, 0, 3); msg.WriteRangedInteger(0, 0, 4);
Inventory.ClientWrite(msg, extraData); Inventory.ClientWrite(msg, extraData);
break; break;
case NetEntityEvent.Type.Treatment: case NetEntityEvent.Type.Treatment:
msg.WriteRangedInteger(1, 0, 3); msg.WriteRangedInteger(1, 0, 4);
msg.Write(AnimController.Anim == AnimController.Animation.CPR); msg.Write(AnimController.Anim == AnimController.Animation.CPR);
break; break;
case NetEntityEvent.Type.Status: case NetEntityEvent.Type.Status:
msg.WriteRangedInteger(2, 0, 3); msg.WriteRangedInteger(2, 0, 4);
break;
case NetEntityEvent.Type.UpdateTalents:
msg.WriteRangedInteger(3, 0, 4);
msg.Write((ushort)characterTalents.Count);
foreach (var unlockedTalent in characterTalents)
{
msg.Write(unlockedTalent.Prefab.UIntIdentifier);
}
break; break;
} }
} }
@@ -258,7 +266,7 @@ namespace Barotrauma
if (readStatus) if (readStatus)
{ {
ReadStatus(msg); ReadStatus(msg);
(AIController as EnemyAIController)?.PetBehavior?.ClientRead(msg); AIController?.ClientRead(msg);
} }
msg.ReadPadBits(); msg.ReadPadBits();
@@ -291,7 +299,7 @@ namespace Barotrauma
break; break;
case ServerNetObject.ENTITY_EVENT: case ServerNetObject.ENTITY_EVENT:
int eventType = msg.ReadRangedInteger(0, 9); int eventType = msg.ReadRangedInteger(0, 13);
switch (eventType) switch (eventType)
{ {
case 0: //NetEntityEvent.Type.InventoryState case 0: //NetEntityEvent.Type.InventoryState
@@ -350,7 +358,7 @@ namespace Barotrauma
{ {
string skillIdentifier = msg.ReadString(); string skillIdentifier = msg.ReadString();
float skillLevel = msg.ReadSingle(); float skillLevel = msg.ReadSingle();
info?.SetSkillLevel(skillIdentifier, skillLevel, Position + Vector2.UnitY * 150.0f); info?.SetSkillLevel(skillIdentifier, skillLevel);
} }
break; break;
case 4: // NetEntityEvent.Type.SetAttackTarget case 4: // NetEntityEvent.Type.SetAttackTarget
@@ -382,11 +390,12 @@ namespace Barotrauma
} }
targetLimb = targetCharacter.AnimController.Limbs[targetLimbIndex]; targetLimb = targetCharacter.AnimController.Limbs[targetLimbIndex];
} }
if (attackLimb?.attack != null) if (attackLimb?.attack != null && Controlled != this)
{ {
if (eventType == 4) if (eventType == 4)
{ {
SetAttackTarget(attackLimb, targetEntity, targetSimPos); SetAttackTarget(attackLimb, targetEntity, targetSimPos);
PlaySound(CharacterSound.SoundType.Attack, maxInterval: 3);
} }
else else
{ {
@@ -450,6 +459,36 @@ namespace Barotrauma
} }
} }
break; break;
case 10: //NetEntityEvent.Type.UpdateExperience
int experienceAmount = msg.ReadInt32();
info?.SetExperience(experienceAmount);
break;
case 11: //NetEntityEvent.Type.UpdateTalents:
ushort talentCount = msg.ReadUInt16();
for (int i = 0; i < talentCount; i++)
{
bool addedThisRound = msg.ReadBoolean();
UInt32 talentIdentifier = msg.ReadUInt32();
GiveTalent(talentIdentifier, addedThisRound);
}
break;
case 12: //NetEntityEvent.Type.UpdateMoney:
int moneyAmount = msg.ReadInt32();
SetMoney(moneyAmount);
break;
case 13: //NetEntityEvent.Type.UpdatePermanentStats:
byte savedStatValueCount = msg.ReadByte();
StatTypes statType = (StatTypes)msg.ReadByte();
info?.ClearSavedStatValues(statType);
for (int i = 0; i < savedStatValueCount; i++)
{
string statIdentifier = msg.ReadString();
float statValue = msg.ReadSingle();
bool removeOnDeath = msg.ReadBoolean();
info?.ChangeSavedStatValue(statType, statValue, statIdentifier, removeOnDeath, setValue: true);
}
break;
} }
msg.ReadPadBits(); msg.ReadPadBits();
break; break;
@@ -1,10 +1,4 @@
using Microsoft.Xna.Framework; namespace Barotrauma
using System.Collections.Generic;
using Barotrauma.IO;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{ {
partial class AfflictionHusk : Affliction partial class AfflictionHusk : Affliction
{ {
File diff suppressed because it is too large Load Diff
@@ -109,6 +109,7 @@ namespace Barotrauma
private float wetTimer; private float wetTimer;
private float dripParticleTimer; private float dripParticleTimer;
private float deadTimer; private float deadTimer;
private Color? randomColor;
/// <summary> /// <summary>
/// Note that different limbs can share the same deformations. /// Note that different limbs can share the same deformations.
@@ -120,6 +121,15 @@ namespace Barotrauma
public List<SpriteDeformation> ActiveDeformations { get; set; } = new List<SpriteDeformation>(); public List<SpriteDeformation> ActiveDeformations { get; set; } = new List<SpriteDeformation>();
public Sprite Sprite { get; protected set; } public Sprite Sprite { get; protected set; }
public Sprite TintMask { get; protected set; }
public Sprite HuskMask { get; protected set; }
public float TintHighlightThreshold { get; protected set; }
public float TintHighlightMultiplier { get; protected set; }
private SpriteBatch.EffectWithParams tintEffectParams;
private SpriteBatch.EffectWithParams huskSpriteParams;
protected DeformableSprite _deformSprite; protected DeformableSprite _deformSprite;
@@ -157,6 +167,12 @@ namespace Barotrauma
} }
} }
public Sprite GetActiveSprite(bool excludeConditionalSprites = true)
=> excludeConditionalSprites ? (_deformSprite != null ? _deformSprite.Sprite : Sprite)
: ActiveSprite;
public float DefaultSpriteDepth { get; private set; }
public WearableSprite HuskSprite { get; private set; } public WearableSprite HuskSprite { get; private set; }
public WearableSprite HerpesSprite { get; private set; } public WearableSprite HerpesSprite { get; private set; }
@@ -273,6 +289,7 @@ namespace Barotrauma
DecorativeSpriteGroups[groupID].Add(decorativeSprite); DecorativeSpriteGroups[groupID].Add(decorativeSprite);
spriteAnimState.Add(decorativeSprite, new SpriteState()); spriteAnimState.Add(decorativeSprite, new SpriteState());
} }
TintMask = null;
foreach (XElement subElement in element.Elements()) foreach (XElement subElement in element.Elements())
{ {
switch (subElement.Name.ToString().ToLowerInvariant()) switch (subElement.Name.ToString().ToLowerInvariant())
@@ -299,15 +316,42 @@ namespace Barotrauma
Deformations.AddRange(deformations); Deformations.AddRange(deformations);
NonConditionalDeformations.AddRange(deformations); NonConditionalDeformations.AddRange(deformations);
break; break;
case "randomcolor":
randomColor = subElement.GetAttributeColorArray("colors", null)?.GetRandom();
if (randomColor.HasValue)
{
Params.GetSprite().Color = randomColor.Value;
}
break;
case "lightsource": case "lightsource":
LightSource = new LightSource(subElement, GetConditionalTarget()) LightSource = new LightSource(subElement, GetConditionalTarget())
{ {
ParentBody = body, ParentBody = body,
SpriteScale = Vector2.One * Scale * TextureScale SpriteScale = Vector2.One * Scale * TextureScale
}; };
if (randomColor.HasValue)
{
LightSource.Color = new Color(randomColor.Value.R, randomColor.Value.G, randomColor.Value.B, LightSource.Color.A);
}
InitialLightSourceColor = LightSource.Color; InitialLightSourceColor = LightSource.Color;
InitialLightSpriteAlpha = LightSource.OverrideLightSpriteAlpha; InitialLightSpriteAlpha = LightSource.OverrideLightSpriteAlpha;
break; break;
case "tintmask":
string tintMaskPath = subElement.GetAttributeString("texture", "");
if (!string.IsNullOrWhiteSpace(tintMaskPath))
{
TintMask = new Sprite(subElement, file: GetSpritePath(tintMaskPath));
TintHighlightThreshold = subElement.GetAttributeFloat("highlightthreshold", 0.6f);
TintHighlightMultiplier = subElement.GetAttributeFloat("highlightmultiplier", 0.8f);
}
break;
case "huskmask":
string huskMaskPath = subElement.GetAttributeString("texture", "");
if (!string.IsNullOrWhiteSpace(huskMaskPath))
{
HuskMask = new Sprite(subElement, file: GetSpritePath(huskMaskPath));
}
break;
} }
ISerializableEntity GetConditionalTarget() ISerializableEntity GetConditionalTarget()
@@ -357,6 +401,7 @@ namespace Barotrauma
return deformations; return deformations;
} }
} }
DefaultSpriteDepth = GetActiveSprite()?.Depth ?? 0.0f;
LightSource?.CheckConditionals(); LightSource?.CheckConditionals();
} }
@@ -449,20 +494,20 @@ namespace Barotrauma
/// <summary> /// <summary>
/// Get the full path of a limb sprite, taking into account tags, gender and head id /// Get the full path of a limb sprite, taking into account tags, gender and head id
/// </summary> /// </summary>
private string GetSpritePath(string texturePath) public static string GetSpritePath(string texturePath, CharacterInfo characterInfo)
{ {
string spritePath = texturePath; string spritePath = texturePath;
string spritePathWithTags = spritePath; string spritePathWithTags = spritePath;
if (character.Info != null && character.IsHumanoid) if (characterInfo != null)
{ {
spritePath = spritePath.Replace("[GENDER]", (character.Info.Gender == Gender.Female) ? "female" : "male"); spritePath = spritePath.Replace("[GENDER]", (characterInfo.Gender == Gender.Female) ? "female" : "male");
spritePath = spritePath.Replace("[RACE]", character.Info.Race.ToString().ToLowerInvariant()); spritePath = spritePath.Replace("[RACE]", characterInfo.Race.ToString().ToLowerInvariant());
spritePath = spritePath.Replace("[HEADID]", character.Info.HeadSpriteId.ToString()); spritePath = spritePath.Replace("[HEADID]", characterInfo.HeadSpriteId.ToString());
if (character.Info.HeadSprite != null && character.Info.SpriteTags.Any()) if (characterInfo.HeadSprite != null && characterInfo.SpriteTags.Any())
{ {
string tags = ""; string tags = "";
character.Info.SpriteTags.ForEach(tag => tags += "[" + tag + "]"); characterInfo.SpriteTags.ForEach(tag => tags += "[" + tag + "]");
spritePathWithTags = Path.Combine( spritePathWithTags = Path.Combine(
Path.GetDirectoryName(spritePath), Path.GetDirectoryName(spritePath),
@@ -472,6 +517,13 @@ namespace Barotrauma
return File.Exists(spritePathWithTags) ? spritePathWithTags : spritePath; return File.Exists(spritePathWithTags) ? spritePathWithTags : spritePath;
} }
private string GetSpritePath(string texturePath)
{
if (!character.IsHumanoid) { return texturePath; }
return GetSpritePath(texturePath, character?.Info);
}
partial void LoadParamsProjSpecific() partial void LoadParamsProjSpecific()
{ {
bool isFlipped = dir == Direction.Left; bool isFlipped = dir == Direction.Left;
@@ -538,8 +590,8 @@ namespace Barotrauma
{ {
foreach (ParticleEmitter emitter in character.DamageEmitters) foreach (ParticleEmitter emitter in character.DamageEmitters)
{ {
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;
foreach (DamageModifier damageModifier in result.AppliedDamageModifiers) foreach (DamageModifier damageModifier in result.AppliedDamageModifiers)
{ {
@@ -560,8 +612,8 @@ namespace Barotrauma
foreach (ParticleEmitter emitter in character.BloodEmitters) foreach (ParticleEmitter emitter in character.BloodEmitters)
{ {
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);
} }
} }
@@ -585,7 +637,7 @@ namespace Barotrauma
} }
} }
if (inWater) if (InWater)
{ {
wetTimer = 1.0f; wetTimer = 1.0f;
} }
@@ -632,19 +684,38 @@ namespace Barotrauma
RefreshDeformations(); RefreshDeformations();
} }
public void Draw(SpriteBatch spriteBatch, Camera cam, Color? overrideColor = null) public void Draw(SpriteBatch spriteBatch, Camera cam, Color? overrideColor = null, bool disableDeformations = false)
{ {
float brightness = 1.0f - (burnOverLayStrength / 100.0f) * 0.5f; float brightness = 1.0f - (burnOverLayStrength / 100.0f) * 0.5f;
var spriteParams = Params.GetSprite(); var spriteParams = Params.GetSprite();
if (spriteParams == null) { return; } if (spriteParams == null) { return; }
Color color = new Color(spriteParams.Color.R / 255f * brightness, spriteParams.Color.G / 255f * brightness, spriteParams.Color.B / 255f * brightness, spriteParams.Color.A / 255f); Color clr = spriteParams.Color;
if (!spriteParams.IgnoreTint)
{
clr = clr.Multiply(ragdoll.RagdollParams.Color);
if (character.Info != null)
{
clr = clr.Multiply(character.Info.SkinColor);
}
if (character.CharacterHealth.FaceTint.A > 0 && type == LimbType.Head)
{
clr = Color.Lerp(clr, character.CharacterHealth.FaceTint.Opaque(), character.CharacterHealth.FaceTint.A / 255.0f);
}
if (character.CharacterHealth.BodyTint.A > 0)
{
clr = Color.Lerp(clr, character.CharacterHealth.BodyTint.Opaque(), character.CharacterHealth.BodyTint.A / 255.0f);
}
}
Color color = new Color((byte)(clr.R * brightness), (byte)(clr.G * brightness), (byte)(clr.B * brightness), clr.A);
Color blankColor = new Color(brightness, brightness, brightness, 1);
if (deadTimer > 0) if (deadTimer > 0)
{ {
color = Color.Lerp(color, spriteParams.DeadColor, MathUtils.InverseLerp(0, spriteParams.DeadColorTime, deadTimer)); color = Color.Lerp(color, spriteParams.DeadColor, MathUtils.InverseLerp(0, spriteParams.DeadColorTime, deadTimer));
} }
color = overrideColor ?? color; color = overrideColor ?? color;
blankColor = overrideColor ?? blankColor;
if (isSevered) if (isSevered)
{ {
@@ -667,6 +738,8 @@ namespace Barotrauma
OtherWearables.Any(w => w.HideLimb) || OtherWearables.Any(w => w.HideLimb) ||
wearingItems.Any(w => w != null && w.HideLimb); wearingItems.Any(w => w != null && w.HideLimb);
bool drawHuskSprite = HuskSprite != null && !wearableTypesToHide.Contains(WearableType.Husk);
var activeSprite = ActiveSprite; var activeSprite = ActiveSprite;
if (type == LimbType.Head) if (type == LimbType.Head)
{ {
@@ -674,11 +747,12 @@ namespace Barotrauma
} }
body.UpdateDrawPosition(); body.UpdateDrawPosition();
float depthStep = 0.000001f;
if (!hideLimb) if (!hideLimb)
{ {
var deformSprite = DeformSprite; var deformSprite = DeformSprite;
if (deformSprite != null) if (deformSprite != null && !disableDeformations)
{ {
if (ActiveDeformations.Any()) if (ActiveDeformations.Any())
{ {
@@ -698,7 +772,33 @@ namespace Barotrauma
} }
else else
{ {
bool useTintMask = TintMask != null && spriteBatch.GetCurrentEffect() is null;
if (useTintMask)
{
tintEffectParams.Effect ??= GameMain.GameScreen.ThresholdTintEffect;
tintEffectParams.Params ??= new Dictionary<string, object>();
var parameters = tintEffectParams.Params;
parameters["xBaseTexture"] = Sprite.Texture;
parameters["xTintMaskTexture"] = TintMask.Texture;
if (drawHuskSprite && HuskMask != null)
{
parameters["xCutoffTexture"] = HuskMask.Texture;
parameters["baseToCutoffSizeRatio"] = (float)Sprite.Texture.Width / (float)HuskMask.Texture.Width;
}
else
{
parameters["xCutoffTexture"] = GUI.WhiteTexture;
parameters["baseToCutoffSizeRatio"] = 1.0f;
}
parameters["highlightThreshold"] = TintHighlightThreshold;
parameters["highlightMultiplier"] = TintHighlightMultiplier;
spriteBatch.SwapEffect(tintEffectParams);
}
body.Draw(spriteBatch, activeSprite, color, null, Scale * TextureScale, Params.MirrorHorizontally, Params.MirrorVertically); body.Draw(spriteBatch, activeSprite, color, null, Scale * TextureScale, Params.MirrorHorizontally, Params.MirrorVertically);
if (useTintMask)
{
spriteBatch.SwapEffect(null);
}
} }
// Handle non-exlusive, i.e. additional conditional sprites // Handle non-exlusive, i.e. additional conditional sprites
foreach (var conditionalSprite in ConditionalSprites) foreach (var conditionalSprite in ConditionalSprites)
@@ -722,7 +822,7 @@ namespace Barotrauma
} }
else else
{ {
body.Draw(spriteBatch, conditionalSprite.Sprite, color, null, Scale * TextureScale, Params.MirrorHorizontally, Params.MirrorVertically); body.Draw(spriteBatch, conditionalSprite.Sprite, color, depth: activeSprite.Depth - (depthStep * 50), Scale * TextureScale, Params.MirrorHorizontally, Params.MirrorVertically);
} }
} }
} }
@@ -737,7 +837,7 @@ namespace Barotrauma
new Vector2(body.DrawPosition.X, -body.DrawPosition.Y), new Vector2(body.DrawPosition.X, -body.DrawPosition.Y),
color * Math.Min(damageOverlayStrength, 1.0f), activeSprite.Origin, color * Math.Min(damageOverlayStrength, 1.0f), activeSprite.Origin,
-body.DrawRotation, -body.DrawRotation,
Scale, spriteEffect, activeSprite.Depth - 0.0000015f); Scale, spriteEffect, activeSprite.Depth - (depthStep * 90));
} }
foreach (var decorativeSprite in DecorativeSprites) foreach (var decorativeSprite in DecorativeSprites)
{ {
@@ -755,9 +855,8 @@ namespace Barotrauma
Vector2 transformedOffset = new Vector2(ca * offset.X + sa * offset.Y, -sa * offset.X + ca * offset.Y); Vector2 transformedOffset = new Vector2(ca * offset.X + sa * offset.Y, -sa * offset.X + ca * offset.Y);
decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(body.DrawPosition.X + transformedOffset.X, -(body.DrawPosition.Y + transformedOffset.Y)), c, decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(body.DrawPosition.X + transformedOffset.X, -(body.DrawPosition.Y + transformedOffset.Y)), c,
-body.Rotation + rotation, decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale, spriteEffect, -body.Rotation + rotation, decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale, spriteEffect,
depth: decorativeSprite.Sprite.Depth); depth: activeSprite.Depth - (depthStep * 100));
} }
float depthStep = 0.000001f;
float step = depthStep; float step = depthStep;
WearableSprite onlyDrawable = wearingItems.Find(w => w.HideOtherWearables); WearableSprite onlyDrawable = wearingItems.Find(w => w.HideOtherWearables);
if (Params.MirrorHorizontally) if (Params.MirrorHorizontally)
@@ -770,23 +869,44 @@ namespace Barotrauma
} }
if (onlyDrawable == null) if (onlyDrawable == null)
{ {
if (HerpesSprite != null && !wearableTypesToHide.Contains(WearableType.Herpes)) if (HerpesSprite != null && !wearableTypesToHide.Contains(WearableType.Herpes) && herpesStrength > 0)
{ {
DrawWearable(HerpesSprite, depthStep, spriteBatch, color * Math.Min(herpesStrength / 10.0f, 1.0f), spriteEffect); float alpha = Math.Min(herpesStrength * 2 / 100.0f, 1.0f);
DrawWearable(HerpesSprite, depthStep, spriteBatch, blankColor, alpha: alpha, spriteEffect);
depthStep += step;
}
if (drawHuskSprite)
{
bool useTintEffect = HuskMask != null && spriteBatch.GetCurrentEffect() is null;
if (useTintEffect)
{
huskSpriteParams.Effect ??= GameMain.GameScreen.ThresholdTintEffect;
huskSpriteParams.Params ??= new Dictionary<string, object>();
var parameters = huskSpriteParams.Params;
parameters["xCutoffTexture"] = GUI.WhiteTexture;
parameters["baseToCutoffSizeRatio"] = 1.0f;
spriteBatch.SwapEffect(huskSpriteParams);
}
DrawWearable(HuskSprite, depthStep, spriteBatch, color, alpha: color.A / 255f, spriteEffect);
if (useTintEffect)
{
spriteBatch.SwapEffect(null);
}
depthStep += step; depthStep += step;
} }
foreach (WearableSprite wearable in OtherWearables) foreach (WearableSprite wearable in OtherWearables)
{ {
if (wearable.Type == WearableType.Husk) { continue; }
if (wearableTypesToHide.Contains(wearable.Type)) { continue; } if (wearableTypesToHide.Contains(wearable.Type)) { continue; }
DrawWearable(wearable, depthStep, spriteBatch, color, 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;
} }
} }
foreach (WearableSprite wearable in WearingItems) foreach (WearableSprite wearable in WearingItems)
{ {
if (onlyDrawable != null && onlyDrawable != wearable) continue; if (onlyDrawable != null && onlyDrawable != wearable && wearable.CanBeHiddenByOtherWearables) { continue; }
DrawWearable(wearable, depthStep, spriteBatch, color, 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;
} }
@@ -936,7 +1056,7 @@ namespace Barotrauma
} }
} }
private void DrawWearable(WearableSprite wearable, float depthStep, SpriteBatch spriteBatch, Color color, SpriteEffects spriteEffect) private void DrawWearable(WearableSprite wearable, float depthStep, SpriteBatch spriteBatch, Color color, float alpha, SpriteEffects spriteEffect)
{ {
var sprite = ActiveSprite; var sprite = ActiveSprite;
if (wearable.InheritSourceRect) if (wearable.InheritSourceRect)
@@ -955,7 +1075,7 @@ namespace Barotrauma
} }
} }
Vector2 origin = wearable.Sprite.Origin; Vector2 origin;
if (wearable.InheritOrigin) if (wearable.InheritOrigin)
{ {
origin = sprite.Origin; origin = sprite.Origin;
@@ -986,24 +1106,49 @@ namespace Barotrauma
Color wearableColor = Color.White; Color wearableColor = Color.White;
if (wearableItemComponent != null) if (wearableItemComponent != null)
{ {
// Draw outer cloths on top of inner cloths. // Draw outer clothes on top of inner clothes.
if (wearableItemComponent.AllowedSlots.Contains(InvSlotType.OuterClothes)) if (wearableItemComponent.AllowedSlots.Contains(InvSlotType.OuterClothes))
{ {
depth -= depthStep; depth -= depthStep;
} }
if (wearableItemComponent.AllowedSlots.Contains(InvSlotType.Bag)) if (wearableItemComponent.AllowedSlots.Contains(InvSlotType.Bag))
{ {
depth -= depthStep * 2; depth -= depthStep * 4;
} }
wearableColor = wearableItemComponent.Item.GetSpriteColor(); wearableColor = wearableItemComponent.Item.GetSpriteColor();
} }
float textureScale = wearable.InheritTextureScale ? TextureScale : wearable.Scale; else if (character.Info != null)
{
wearable.Sprite.Draw(spriteBatch, if (wearable.Type == WearableType.Hair)
new Vector2(body.DrawPosition.X, -body.DrawPosition.Y), {
new Color((color.R * wearableColor.R) / (255.0f * 255.0f), (color.G * wearableColor.G) / (255.0f * 255.0f), (color.B * wearableColor.B) / (255.0f * 255.0f)) * ((color.A * wearableColor.A) / (255.0f * 255.0f)), wearableColor = character.Info.HairColor;
origin, -body.DrawRotation, }
Scale * textureScale, spriteEffect, depth); else if (wearable.Type == WearableType.Beard || wearable.Type == WearableType.Moustache)
{
wearableColor = character.Info.FacialHairColor;
}
}
float scale = wearable.Scale;
if (wearable.InheritScale)
{
if (!wearable.IgnoreTextureScale)
{
scale *= TextureScale;
}
if (!wearable.IgnoreLimbScale)
{
scale *= Params.Scale;
}
if (!wearable.IgnoreRagdollScale)
{
scale *= ragdoll.RagdollParams.LimbScale;
}
}
float rotation = -body.DrawRotation - wearable.Rotation * Dir;
float finalAlpha = alpha * wearableColor.A;
Color finalColor = color.Multiply(wearableColor);
finalColor = new Color(finalColor.R, finalColor.G, finalColor.B, (byte)finalAlpha);
wearable.Sprite.Draw(spriteBatch, new Vector2(body.DrawPosition.X, -body.DrawPosition.Y), finalColor, origin, rotation, scale, spriteEffect, depth);
} }
private WearableSprite GetWearableSprite(WearableType type, bool random = false) private WearableSprite GetWearableSprite(WearableType type, bool random = false)
@@ -1054,6 +1199,9 @@ namespace Barotrauma
HerpesSprite?.Sprite.Remove(); HerpesSprite?.Sprite.Remove();
HerpesSprite = null; HerpesSprite = null;
TintMask?.Remove();
TintMask = null;
} }
} }
} }
@@ -418,14 +418,14 @@ namespace Barotrauma
ShowQuestionPrompt("The automatic hull generation may not work correctly if your submarine uses curved walls. Do you want to continue? Y/N", ShowQuestionPrompt("The automatic hull generation may not work correctly if your submarine uses curved walls. Do you want to continue? Y/N",
(option2) => (option2) =>
{ {
if (option2.ToLower() == "y") { GameMain.SubEditorScreen.AutoHull(); } if (option2.ToLowerInvariant() == "y") { GameMain.SubEditorScreen.AutoHull(); }
}); });
}); });
} }
else else
{ {
ShowQuestionPrompt("The automatic hull generation may not work correctly if your submarine uses curved walls. Do you want to continue? Y/N", ShowQuestionPrompt("The automatic hull generation may not work correctly if your submarine uses curved walls. Do you want to continue? Y/N",
(option) => { if (option.ToLower() == "y") GameMain.SubEditorScreen.AutoHull(); }); (option) => { if (option.ToLowerInvariant() == "y") GameMain.SubEditorScreen.AutoHull(); });
} }
})); }));
@@ -476,6 +476,7 @@ namespace Barotrauma
if (Screen.Selected == GameMain.SubEditorScreen) if (Screen.Selected == GameMain.SubEditorScreen)
{ {
NewMessage("WARNING: Switching directly from the submarine editor to the game view may cause bugs and crashes. Use with caution.", Color.Orange); NewMessage("WARNING: Switching directly from the submarine editor to the game view may cause bugs and crashes. Use with caution.", Color.Orange);
Entity.Spawner ??= new EntitySpawner();
} }
GameMain.GameScreen.Select(); GameMain.GameScreen.Select();
})); }));
@@ -488,6 +489,8 @@ namespace Barotrauma
Submarine.MainSub = Submarine.Load(subInfo, true); Submarine.MainSub = Submarine.Load(subInfo, true);
} }
GameMain.SubEditorScreen.Select(enableAutoSave: Screen.Selected != GameMain.GameScreen); GameMain.SubEditorScreen.Select(enableAutoSave: Screen.Selected != GameMain.GameScreen);
Entity.Spawner?.Remove();
Entity.Spawner = null;
}, isCheat: true)); }, isCheat: true));
commands.Add(new Command("editparticles|particleeditor", "editparticles/particleeditor: Switch to the Particle Editor to edit particle effects.", (string[] args) => commands.Add(new Command("editparticles|particleeditor", "editparticles/particleeditor: Switch to the Particle Editor to edit particle effects.", (string[] args) =>
@@ -608,7 +611,7 @@ namespace Barotrauma
ShowQuestionPrompt($"Some keybinds may render the game unusable, are you sure you want to make these keybinds persistent? ({Keybinds.Count} keybind(s) assigned) Y/N", ShowQuestionPrompt($"Some keybinds may render the game unusable, are you sure you want to make these keybinds persistent? ({Keybinds.Count} keybind(s) assigned) Y/N",
(option2) => (option2) =>
{ {
if (option2.ToLower() != "y") if (option2.ToLowerInvariant() != "y")
{ {
NewMessage("Aborted.", GUI.Style.Red); NewMessage("Aborted.", GUI.Style.Red);
return; return;
@@ -689,9 +692,18 @@ namespace Barotrauma
AssignRelayToServer("setskill", true); AssignRelayToServer("setskill", true);
AssignRelayToServer("readycheck", true); AssignRelayToServer("readycheck", true);
AssignRelayToServer("givetalent", true);
AssignRelayToServer("unlocktalents", true);
AssignRelayToServer("giveexperience", true);
AssignOnExecute("control", (string[] args) => AssignOnExecute("control", (string[] args) =>
{ {
if (args.Length < 1) return; if (args.Length < 1) { return; }
if (GameMain.NetworkMember != null)
{
GameMain.Client?.SendConsoleCommand("control " + string.Join(' ', args[0]));
return;
}
var character = FindMatchingCharacter(args, true); var character = FindMatchingCharacter(args, true);
if (character != null) if (character != null)
{ {
@@ -1096,9 +1108,35 @@ namespace Barotrauma
commands.Add(new Command("load|loadsub", "load [submarine name]: Load a submarine.", (string[] args) => commands.Add(new Command("load|loadsub", "load [submarine name]: Load a submarine.", (string[] args) =>
{ {
if (args.Length == 0) return; if (args.Length == 0) { return; }
SubmarineInfo subInfo = new SubmarineInfo(string.Join(" ", args));
if (GameMain.GameSession != null)
{
ThrowError("The loadsub command cannot be used when a round is running. You should probably be using spawnsub instead.");
return;
}
string name = string.Join(" ", args);
SubmarineInfo subInfo = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => name.Equals(s.Name, StringComparison.OrdinalIgnoreCase));
if (subInfo == null)
{
string path = Path.Combine(SubmarineInfo.SavePath, name);
if (!File.Exists(path))
{
ThrowError($"Could not find a submarine with the name \"{name}\" or in the path {path}.");
return;
}
subInfo = new SubmarineInfo(path);
}
Submarine.Load(subInfo, true); Submarine.Load(subInfo, true);
},
() =>
{
return new string[][]
{
SubmarineInfo.SavedSubmarines.Select(s => s.Name).ToArray()
};
})); }));
commands.Add(new Command("cleansub", "", (string[] args) => commands.Add(new Command("cleansub", "", (string[] args) =>
@@ -1317,11 +1355,13 @@ namespace Barotrauma
continue; continue;
} }
float avgOutCondition = (deconstructItem.OutConditionMin + deconstructItem.OutConditionMax) / 2;
int? deconstructProductPrice = targetItem.GetMinPrice(); int? deconstructProductPrice = targetItem.GetMinPrice();
if (deconstructProductPrice.HasValue) if (deconstructProductPrice.HasValue)
{ {
if (!deconstructProductCost.HasValue) { deconstructProductCost = 0; } if (!deconstructProductCost.HasValue) { deconstructProductCost = 0; }
deconstructProductCost += (int)(deconstructProductPrice * deconstructItem.OutCondition); deconstructProductCost += (int)(deconstructProductPrice * avgOutCondition);
} }
if (fabricationRecipe != null) if (fabricationRecipe != null)
@@ -1331,9 +1371,9 @@ namespace Barotrauma
{ {
NewMessage("Deconstructing \"" + itemPrefab.Name + "\" produces \"" + deconstructItem.ItemIdentifier + "\", which isn't required in the fabrication recipe of the item.", Color.Red); NewMessage("Deconstructing \"" + itemPrefab.Name + "\" produces \"" + deconstructItem.ItemIdentifier + "\", which isn't required in the fabrication recipe of the item.", Color.Red);
} }
else if (ingredient.UseCondition && ingredient.MinCondition < deconstructItem.OutCondition) else if (ingredient.UseCondition && ingredient.MinCondition < avgOutCondition)
{ {
NewMessage($"Deconstructing \"{itemPrefab.Name}\" produces more \"{deconstructItem.ItemIdentifier}\", than what's required to fabricate the item (required: {targetItem.Name} {(int)(ingredient.MinCondition * 100)}%, output: {deconstructItem.ItemIdentifier} {(int)(deconstructItem.OutCondition * 100)}%)", Color.Red); NewMessage($"Deconstructing \"{itemPrefab.Name}\" produces more \"{deconstructItem.ItemIdentifier}\", than what's required to fabricate the item (required: {targetItem.Name} {(int)(ingredient.MinCondition * 100)}%, output: {deconstructItem.ItemIdentifier} {(int)(avgOutCondition * 100)}%)", Color.Red);
} }
} }
} }
@@ -1692,13 +1732,14 @@ namespace Barotrauma
//check missing mission texts //check missing mission texts
foreach (var missionPrefab in MissionPrefab.List) foreach (var missionPrefab in MissionPrefab.List)
{ {
string nameIdentifier = "missionname." + missionPrefab.Identifier; string missionId = (missionPrefab.ConfigElement.Attribute("textidentifier") == null ? missionPrefab.Identifier : missionPrefab.ConfigElement.GetAttributeString("textidentifier", string.Empty));
string nameIdentifier = "missionname." + missionId;
if (!tags[language].Contains(nameIdentifier)) if (!tags[language].Contains(nameIdentifier))
{ {
if (!missingTags.ContainsKey(nameIdentifier)) { missingTags[nameIdentifier] = new HashSet<string>(); } if (!missingTags.ContainsKey(nameIdentifier)) { missingTags[nameIdentifier] = new HashSet<string>(); }
missingTags[nameIdentifier].Add(language); missingTags[nameIdentifier].Add(language);
} }
string descriptionIdentifier = "missiondescription." + missionPrefab.Identifier; string descriptionIdentifier = "missiondescription." + missionId;
if (!tags[language].Contains(descriptionIdentifier)) if (!tags[language].Contains(descriptionIdentifier))
{ {
if (!missingTags.ContainsKey(descriptionIdentifier)) { missingTags[descriptionIdentifier] = new HashSet<string>(); } if (!missingTags.ContainsKey(descriptionIdentifier)) { missingTags[descriptionIdentifier] = new HashSet<string>(); }
@@ -1708,6 +1749,7 @@ namespace Barotrauma
foreach (SubmarineInfo sub in SubmarineInfo.SavedSubmarines) foreach (SubmarineInfo sub in SubmarineInfo.SavedSubmarines)
{ {
if (sub.Type != SubmarineType.Player) { continue; }
string nameIdentifier = "submarine.name." + sub.Name.ToLowerInvariant(); string nameIdentifier = "submarine.name." + sub.Name.ToLowerInvariant();
if (!tags[language].Contains(nameIdentifier)) if (!tags[language].Contains(nameIdentifier))
{ {
@@ -1724,14 +1766,23 @@ namespace Barotrauma
foreach (AfflictionPrefab affliction in AfflictionPrefab.List) foreach (AfflictionPrefab affliction in AfflictionPrefab.List)
{ {
string nameIdentifier = "afflictionname." + affliction.Identifier; if (affliction.ShowIconThreshold > affliction.MaxStrength &&
affliction.ShowIconToOthersThreshold > affliction.MaxStrength &&
affliction.ShowInHealthScannerThreshold > affliction.MaxStrength)
{
//hidden affliction, no need for localization
continue;
}
string afflictionId = affliction.TranslationOverride ?? affliction.Identifier;
string nameIdentifier = "afflictionname." + afflictionId;
if (!tags[language].Contains(nameIdentifier)) if (!tags[language].Contains(nameIdentifier))
{ {
if (!missingTags.ContainsKey(nameIdentifier)) { missingTags[nameIdentifier] = new HashSet<string>(); } if (!missingTags.ContainsKey(nameIdentifier)) { missingTags[nameIdentifier] = new HashSet<string>(); }
missingTags[nameIdentifier].Add(language); missingTags[nameIdentifier].Add(language);
} }
string descriptionIdentifier = "afflictiondescription." + affliction.Identifier; string descriptionIdentifier = "afflictiondescription." + afflictionId;
if (!tags[language].Contains(descriptionIdentifier)) if (!tags[language].Contains(descriptionIdentifier))
{ {
if (!missingTags.ContainsKey(descriptionIdentifier)) { missingTags[descriptionIdentifier] = new HashSet<string>(); } if (!missingTags.ContainsKey(descriptionIdentifier)) { missingTags[descriptionIdentifier] = new HashSet<string>(); }
@@ -1739,6 +1790,29 @@ namespace Barotrauma
} }
} }
foreach (var talentTree in TalentTree.JobTalentTrees)
{
foreach (var talentSubTree in talentTree.Value.TalentSubTrees)
{
string nameIdentifier = "talenttree." + talentSubTree.Identifier;
if (!tags[language].Contains(nameIdentifier))
{
if (!missingTags.ContainsKey(nameIdentifier)) { missingTags[nameIdentifier] = new HashSet<string>(); }
missingTags[nameIdentifier].Add(language);
}
}
}
foreach (var talent in TalentPrefab.TalentPrefabs)
{
string nameIdentifier = "talentname." + talent.Identifier;
if (!tags[language].Contains(nameIdentifier))
{
if (!missingTags.ContainsKey(nameIdentifier)) { missingTags[nameIdentifier] = new HashSet<string>(); }
missingTags[nameIdentifier].Add(language);
}
}
//check missing entity names //check missing entity names
foreach (MapEntityPrefab me in MapEntityPrefab.List) foreach (MapEntityPrefab me in MapEntityPrefab.List)
{ {
@@ -22,6 +22,7 @@ namespace Barotrauma
public override void ClientReadInitial(IReadMessage msg) public override void ClientReadInitial(IReadMessage msg)
{ {
base.ClientReadInitial(msg);
ushort targetItemCount = msg.ReadUInt16(); ushort targetItemCount = msg.ReadUInt16();
for (int i = 0; i < targetItemCount; i++) for (int i = 0; i < targetItemCount; i++)
{ {
@@ -0,0 +1,32 @@
using Barotrauma.Networking;
namespace Barotrauma
{
partial class AlienRuinMission : Mission
{
public override void ClientReadInitial(IReadMessage msg)
{
base.ClientReadInitial(msg);
existingTargets.Clear();
spawnedTargets.Clear();
allTargets.Clear();
ushort existingTargetsCount = msg.ReadUInt16();
for (int i = 0; i < existingTargetsCount; i++)
{
ushort targetId = msg.ReadUInt16();
if (targetId == Entity.NullEntityID) { continue; }
Entity target = Entity.FindEntityByID(targetId);
if (target == null) { continue; }
existingTargets.Add(target);
allTargets.Add(target);
}
ushort spawnedTargetsCount = msg.ReadUInt16();
for (int i = 0; i < spawnedTargetsCount; i++)
{
var enemy = Character.ReadSpawnData(msg);
existingTargets.Add(enemy);
allTargets.Add(enemy);
}
}
}
}
@@ -1,12 +0,0 @@
using Barotrauma.Networking;
namespace Barotrauma
{
partial class BeaconMission : Mission
{
public override void ClientReadInitial(IReadMessage msg)
{
return;
}
}
}
@@ -25,6 +25,7 @@ namespace Barotrauma
} }
public override void ClientReadInitial(IReadMessage msg) public override void ClientReadInitial(IReadMessage msg)
{ {
base.ClientReadInitial(msg);
items.Clear(); items.Clear();
ushort itemCount = msg.ReadUInt16(); ushort itemCount = msg.ReadUInt16();
for (int i = 0; i < itemCount; i++) for (int i = 0; i < itemCount; i++)
@@ -20,10 +20,5 @@ namespace Barotrauma
return descriptions[GameMain.Client.Character.TeamID == CharacterTeamType.Team1 ? 1 : 2]; return descriptions[GameMain.Client.Character.TeamID == CharacterTeamType.Team1 ? 1 : 2];
} }
} }
public override void ClientReadInitial(IReadMessage msg)
{
//do nothing
}
} }
} }
@@ -6,6 +6,8 @@ namespace Barotrauma
{ {
public override void ClientReadInitial(IReadMessage msg) public override void ClientReadInitial(IReadMessage msg)
{ {
base.ClientReadInitial(msg);
byte characterCount = msg.ReadByte(); byte characterCount = msg.ReadByte();
for (int i = 0; i < characterCount; i++) for (int i = 0; i < characterCount; i++)
@@ -12,6 +12,7 @@ namespace Barotrauma
{ {
public override void ClientReadInitial(IReadMessage msg) public override void ClientReadInitial(IReadMessage 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++)
{ {
@@ -29,7 +30,7 @@ namespace Barotrauma
} }
} }
for (int i = 0; i < ResourceClusters.Count; i++) for (int i = 0; i < resourceClusters.Count; i++)
{ {
var amount = msg.ReadByte(); var amount = msg.ReadByte();
var rotation = msg.ReadSingle(); var rotation = msg.ReadSingle();
@@ -41,20 +42,20 @@ namespace Barotrauma
h.AttachToWall(); h.AttachToWall();
item.Rotation = rotation; item.Rotation = rotation;
} }
if (SpawnedResources.TryGetValue(item.Prefab.Identifier, out var resources)) if (spawnedResources.TryGetValue(item.Prefab.Identifier, out var resources))
{ {
resources.Add(item); resources.Add(item);
} }
else else
{ {
SpawnedResources.Add(item.Prefab.Identifier, new List<Item>() { item }); spawnedResources.Add(item.Prefab.Identifier, new List<Item>() { item });
} }
} }
} }
CalculateMissionClusterPositions(); CalculateMissionClusterPositions();
for(int i = 0; i < ResourceClusters.Count; i++) for(int i = 0; i < resourceClusters.Count; i++)
{ {
var identifier = msg.ReadString(); var identifier = msg.ReadString();
var count = msg.ReadByte(); var count = msg.ReadByte();
@@ -66,7 +67,7 @@ namespace Barotrauma
if (!(entity is Item item)) { continue; } if (!(entity is Item item)) { continue; }
resources[j] = item; resources[j] = item;
} }
RelevantLevelResources.Add(identifier, resources); relevantLevelResources.Add(identifier, resources);
} }
} }
} }
@@ -3,6 +3,7 @@ using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq;
namespace Barotrauma namespace Barotrauma
{ {
@@ -14,6 +15,10 @@ namespace Barotrauma
get { return shownMessages; } get { return shownMessages; }
} }
public bool DisplayTargetHudIcons => Prefab.DisplayTargetHudIcons;
public virtual IEnumerable<Entity> HudIconTargets => Enumerable.Empty<Entity>();
public Color GetDifficultyColor() public Color GetDifficultyColor()
{ {
int v = Difficulty ?? MissionPrefab.MinDifficulty; int v = Difficulty ?? MissionPrefab.MinDifficulty;
@@ -92,11 +97,14 @@ namespace Barotrauma
}; };
} }
public void ClientRead(IReadMessage msg) public virtual void ClientRead(IReadMessage msg)
{ {
State = msg.ReadInt16(); State = msg.ReadInt16();
} }
public abstract void ClientReadInitial(IReadMessage msg); public virtual void ClientReadInitial(IReadMessage msg)
{
state = msg.ReadInt16();
}
} }
} }
@@ -18,13 +18,54 @@ namespace Barotrauma
private set; private set;
} }
public bool DisplayTargetHudIcons
{
get;
private set;
}
public float HudIconMaxDistance
{
get;
private set;
}
public Sprite HudIcon
{
get
{
return hudIcon ?? Icon;
}
}
public Color HudIconColor
{
get
{
return hudIconColor ?? IconColor;
}
}
private Sprite hudIcon;
private Color? hudIconColor;
partial void InitProjSpecific(XElement element) partial void InitProjSpecific(XElement element)
{ {
DisplayTargetHudIcons = element.GetAttributeBool("displaytargethudicons", false);
HudIconMaxDistance = element.GetAttributeFloat("hudiconmaxdistance", 1000.0f);
foreach (XElement subElement in element.Elements()) foreach (XElement subElement in element.Elements())
{ {
if (!subElement.Name.ToString().Equals("icon", StringComparison.OrdinalIgnoreCase)) { continue; } string name = subElement.Name.ToString();
Icon = new Sprite(subElement); if (name.Equals("icon", StringComparison.OrdinalIgnoreCase))
IconColor = subElement.GetAttributeColor("color", Color.White); {
Icon = new Sprite(subElement);
IconColor = subElement.GetAttributeColor("color", Color.White);
}
else if (name.Equals("hudicon", StringComparison.OrdinalIgnoreCase))
{
hudIcon = new Sprite(subElement);
hudIconColor = subElement.GetAttributeColor("color");
}
} }
} }
} }
@@ -6,6 +6,7 @@ namespace Barotrauma
{ {
public override void ClientReadInitial(IReadMessage msg) public override void ClientReadInitial(IReadMessage msg)
{ {
base.ClientReadInitial(msg);
byte monsterCount = msg.ReadByte(); byte monsterCount = msg.ReadByte();
for (int i = 0; i < monsterCount; i++) for (int i = 0; i < monsterCount; i++)
{ {
@@ -8,6 +8,7 @@ namespace Barotrauma
{ {
public override void ClientReadInitial(IReadMessage msg) public override void ClientReadInitial(IReadMessage msg)
{ {
base.ClientReadInitial(msg);
byte selectedCaveIndex = msg.ReadByte(); byte selectedCaveIndex = msg.ReadByte();
nestPosition = new Vector2( nestPosition = new Vector2(
msg.ReadSingle(), msg.ReadSingle(),
@@ -6,6 +6,7 @@ namespace Barotrauma
{ {
public override void ClientReadInitial(IReadMessage msg) public override void ClientReadInitial(IReadMessage msg)
{ {
base.ClientReadInitial(msg);
// duplicate code from escortmission, should possibly be combined, though additional loot items might be added so maybe not // duplicate code from escortmission, should possibly be combined, though additional loot items might be added so maybe not
byte characterCount = msg.ReadByte(); byte characterCount = msg.ReadByte();
@@ -7,6 +7,7 @@ namespace Barotrauma
{ {
public override void ClientReadInitial(IReadMessage msg) public override void ClientReadInitial(IReadMessage msg)
{ {
base.ClientReadInitial(msg);
bool usedExistingItem = msg.ReadBoolean(); bool usedExistingItem = msg.ReadBoolean();
if (usedExistingItem) if (usedExistingItem)
{ {
@@ -0,0 +1,67 @@
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
partial class ScanMission : Mission
{
public override IEnumerable<Entity> HudIconTargets
{
get
{
if (State == 0)
{
return scanTargets.Where(kvp => !kvp.Value).Select(kvp => kvp.Key);
}
else
{
return Enumerable.Empty<Entity>();
}
}
}
public override void ClientReadInitial(IReadMessage msg)
{
base.ClientReadInitial(msg);
startingItems.Clear();
ushort itemCount = msg.ReadUInt16();
for (int i = 0; i < itemCount; i++)
{
startingItems.Add(Item.ReadSpawnData(msg));
}
if (startingItems.Contains(null))
{
throw new Exception($"Error in ScanMission.ClientReadInitial: item list contains null (mission: {Prefab.Identifier})");
}
if (startingItems.Count != itemCount)
{
throw new Exception($"Error in ScanMission.ClientReadInitial: item count does not match the server count ({itemCount} != {startingItems.Count}, mission: {Prefab.Identifier})");
}
scanners.Clear();
GetScanners();
ClientReadScanTargetStatus(msg);
}
public override void ClientRead(IReadMessage msg)
{
base.ClientRead(msg);
ClientReadScanTargetStatus(msg);
}
private void ClientReadScanTargetStatus(IReadMessage msg)
{
scanTargets.Clear();
byte targetsToScan = msg.ReadByte();
for (int i = 0; i < targetsToScan; i++)
{
ushort id = msg.ReadUInt16();
bool scanned = msg.ReadBoolean();
Entity entity = Entity.FindEntityByID(id);
scanTargets.Add(entity as WayPoint, scanned);
}
}
}
}
@@ -130,6 +130,7 @@ namespace Barotrauma
public static GUIStyle Style; public static GUIStyle Style;
private static Texture2D t; private static Texture2D t;
public static Texture2D WhiteTexture => t;
private static Sprite[] MouseCursorSprites => Style.CursorSprite; private static Sprite[] MouseCursorSprites => Style.CursorSprite;
private static bool debugDrawSounds, debugDrawEvents, debugDrawMetadata; private static bool debugDrawSounds, debugDrawEvents, debugDrawMetadata;
@@ -163,6 +164,7 @@ namespace Barotrauma
public static ScalableFont SubHeadingFont => Style?.SubHeadingFont; public static ScalableFont SubHeadingFont => Style?.SubHeadingFont;
public static ScalableFont DigitalFont => Style?.DigitalFont; public static ScalableFont DigitalFont => Style?.DigitalFont;
public static ScalableFont HotkeyFont => Style?.HotkeyFont; public static ScalableFont HotkeyFont => Style?.HotkeyFont;
public static ScalableFont MonospacedFont => Style?.MonospacedFont;
public static ScalableFont CJKFont { get; private set; } public static ScalableFont CJKFont { get; private set; }
@@ -306,7 +308,7 @@ namespace Barotrauma
}); });
SubmarineIcon = new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(452, 385, 182, 81), new Vector2(0.5f, 0.5f)); SubmarineIcon = new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(452, 385, 182, 81), new Vector2(0.5f, 0.5f));
arrow = new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(392, 393, 49, 45), new Vector2(0.5f, 0.5f)); arrow = new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(393, 393, 49, 45), new Vector2(0.5f, 0.5f));
SpeechBubbleIcon = new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(385, 449, 66, 60), new Vector2(0.5f, 0.5f)); SpeechBubbleIcon = new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(385, 449, 66, 60), new Vector2(0.5f, 0.5f));
BrokenIcon = new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(898, 386, 123, 123), new Vector2(0.5f, 0.5f)); BrokenIcon = new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(898, 386, 123, 123), new Vector2(0.5f, 0.5f));
} }
@@ -672,6 +674,12 @@ namespace Barotrauma
spriteBatch.End(); spriteBatch.End();
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: SamplerStateClamp, rasterizerState: GameMain.ScissorTestEnable); spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: SamplerStateClamp, rasterizerState: GameMain.ScissorTestEnable);
if (GameMain.GameSession?.CrewManager is { DraggedOrder: { SymbolSprite: { } orderSprite, Color: var color }, DragOrder: true })
{
float spriteSize = Math.Max(orderSprite.size.X, orderSprite.size.Y);
orderSprite.Draw(spriteBatch, PlayerInput.LatestMousePosition, color, orderSprite.size / 2f, scale: 32f / spriteSize * Scale);
}
var sprite = MouseCursorSprites[(int)MouseCursor] ?? MouseCursorSprites[(int)CursorState.Default]; var sprite = MouseCursorSprites[(int)MouseCursor] ?? MouseCursorSprites[(int)CursorState.Default];
sprite.Draw(spriteBatch, PlayerInput.LatestMousePosition, Color.White, sprite.Origin, 0f, Scale / 1.5f); sprite.Draw(spriteBatch, PlayerInput.LatestMousePosition, Color.White, sprite.Origin, 0f, Scale / 1.5f);
@@ -855,11 +863,10 @@ namespace Barotrauma
int index = 0; int index = 0;
if (updateList.Count > 0) if (updateList.Count > 0)
{ {
index = updateList.Count - 1; index = updateList.Count;
while (updateList[index].UpdateOrder > item.UpdateOrder) while (index > 0 && updateList[index-1].UpdateOrder > item.UpdateOrder)
{ {
index--; index--;
if (index == 0) { break; }
} }
} }
if (!updateListSet.Contains(item)) if (!updateListSet.Contains(item))
@@ -928,12 +935,13 @@ namespace Barotrauma
MouseOn = null; MouseOn = null;
int inventoryIndex = -1; int inventoryIndex = -1;
if (Inventory.IsMouseOnInventory()) Inventory.RefreshMouseOnInventory();
if (Inventory.IsMouseOnInventory)
{ {
inventoryIndex = updateList.IndexOf(CharacterHUD.HUDFrame); inventoryIndex = updateList.IndexOf(CharacterHUD.HUDFrame);
} }
if (!PlayerInput.PrimaryMouseButtonHeld() && !PlayerInput.PrimaryMouseButtonClicked()) if ((!PlayerInput.PrimaryMouseButtonHeld() && !PlayerInput.PrimaryMouseButtonClicked()) || (prevMouseOn == null && !PlayerInput.SecondaryMouseButtonHeld()))
{ {
for (var i = updateList.Count - 1; i > inventoryIndex; i--) for (var i = updateList.Count - 1; i > inventoryIndex; i--)
{ {
@@ -941,10 +949,9 @@ namespace Barotrauma
if (!c.CanBeFocused) { continue; } if (!c.CanBeFocused) { continue; }
if (c.MouseRect.Contains(PlayerInput.MousePosition)) if (c.MouseRect.Contains(PlayerInput.MousePosition))
{ {
if ((!PlayerInput.PrimaryMouseButtonHeld() && !PlayerInput.PrimaryMouseButtonClicked()) || c == prevMouseOn) if ((!PlayerInput.PrimaryMouseButtonHeld() && !PlayerInput.PrimaryMouseButtonClicked()) || c == prevMouseOn || prevMouseOn == null)
{ {
MouseOn = c; MouseOn = c;
var sakdjfnsjkd = c.MouseRect;
} }
break; break;
} }
@@ -958,7 +965,6 @@ namespace Barotrauma
MouseCursor = UpdateMouseCursorState(MouseOn); MouseCursor = UpdateMouseCursorState(MouseOn);
return MouseOn; return MouseOn;
} }
} }
private static CursorState UpdateMouseCursorState(GUIComponent c) private static CursorState UpdateMouseCursorState(GUIComponent c)
@@ -1051,12 +1057,15 @@ namespace Barotrauma
if (listBox.DraggedElement != null) { return CursorState.Dragging; } if (listBox.DraggedElement != null) { return CursorState.Dragging; }
if (listBox.CanDragElements) { return CursorState.Move; } if (listBox.CanDragElements) { return CursorState.Move; }
var hoverParent = c; if (listBox.HoverCursor != CursorState.Default)
while (true)
{ {
if (hoverParent == parent || hoverParent == null) { break; } var hoverParent = c;
if (hoverParent.State == GUIComponent.ComponentState.Hover) { return CursorState.Hand; } while (true)
hoverParent = hoverParent.Parent; {
if (hoverParent == parent || hoverParent == null) { break; }
if (hoverParent.State == GUIComponent.ComponentState.Hover) { return CursorState.Hand; }
hoverParent = hoverParent.Parent;
}
} }
} }
@@ -1066,7 +1075,7 @@ namespace Barotrauma
} }
} }
if (Inventory.IsMouseOnInventory()) { return Inventory.GetInventoryMouseCursor(); } if (Inventory.IsMouseOnInventory) { return Inventory.GetInventoryMouseCursor(); }
var character = Character.Controlled; var character = Character.Controlled;
// ReSharper disable once InvertIf // ReSharper disable once InvertIf
@@ -1343,7 +1352,7 @@ namespace Barotrauma
/// <param name="createOffset">Should the indicator move based on the camera position?</param> /// <param name="createOffset">Should the indicator move based on the camera position?</param>
/// <param name="overrideAlpha">Override the distance-based alpha value with the specified alpha value</param> /// <param name="overrideAlpha">Override the distance-based alpha value with the specified alpha value</param>
public static void DrawIndicator(SpriteBatch spriteBatch, in Vector2 worldPosition, Camera cam, in Vector2 visibleRange, Sprite sprite, in Color color, public static void DrawIndicator(SpriteBatch spriteBatch, in Vector2 worldPosition, Camera cam, in Range<float> visibleRange, Sprite sprite, in Color color,
bool createOffset = true, float scaleMultiplier = 1.0f, float? overrideAlpha = null) bool createOffset = true, float scaleMultiplier = 1.0f, float? overrideAlpha = null)
{ {
Vector2 diff = worldPosition - cam.WorldViewCenter; Vector2 diff = worldPosition - cam.WorldViewCenter;
@@ -1351,9 +1360,9 @@ namespace Barotrauma
float symbolScale = Math.Min(64.0f / sprite.size.X, 1.0f) * scaleMultiplier * Scale; float symbolScale = Math.Min(64.0f / sprite.size.X, 1.0f) * scaleMultiplier * Scale;
if (overrideAlpha.HasValue || (dist > visibleRange.X && dist < visibleRange.Y)) if (overrideAlpha.HasValue || (dist > visibleRange.Start && dist < visibleRange.End))
{ {
float alpha = overrideAlpha ?? MathUtils.Min((dist - visibleRange.X) / 100.0f, 1.0f - ((dist - visibleRange.Y + 100f) / 100.0f), 1.0f); float alpha = overrideAlpha ?? MathUtils.Min((dist - visibleRange.Start) / 100.0f, 1.0f - ((dist - visibleRange.End + 100f) / 100.0f), 1.0f);
Vector2 targetScreenPos = cam.WorldToScreen(worldPosition); Vector2 targetScreenPos = cam.WorldToScreen(worldPosition);
if (!createOffset) if (!createOffset)
@@ -1417,7 +1426,7 @@ namespace Barotrauma
public static void DrawIndicator(SpriteBatch spriteBatch, Vector2 worldPosition, Camera cam, float hideDist, Sprite sprite, Color color, public static void DrawIndicator(SpriteBatch spriteBatch, Vector2 worldPosition, Camera cam, float hideDist, Sprite sprite, Color color,
bool createOffset = true, float scaleMultiplier = 1.0f, float? overrideAlpha = null) bool createOffset = true, float scaleMultiplier = 1.0f, float? overrideAlpha = null)
{ {
DrawIndicator(spriteBatch, worldPosition, cam, new Vector2(hideDist, float.PositiveInfinity), sprite, color, createOffset, scaleMultiplier, overrideAlpha); DrawIndicator(spriteBatch, worldPosition, cam, new Range<float>(hideDist, float.PositiveInfinity), sprite, color, createOffset, scaleMultiplier, overrideAlpha);
} }
public static void DrawLine(SpriteBatch sb, Vector2 start, Vector2 end, Color clr, float depth = 0.0f, float width = 1) public static void DrawLine(SpriteBatch sb, Vector2 start, Vector2 end, Color clr, float depth = 0.0f, float width = 1)
@@ -1520,6 +1529,11 @@ namespace Barotrauma
} }
} }
public static void DrawFilledRectangle(SpriteBatch sb, RectangleF rect, Color clr, float depth = 0.0f)
{
DrawFilledRectangle(sb, rect.Location, rect.Size, clr, depth);
}
public static void DrawFilledRectangle(SpriteBatch sb, Vector2 start, Vector2 size, Color clr, float depth = 0.0f) public static void DrawFilledRectangle(SpriteBatch sb, Vector2 start, Vector2 size, Color clr, float depth = 0.0f)
{ {
if (size.X < 0) if (size.X < 0)
@@ -2433,14 +2447,43 @@ namespace Barotrauma
{ {
if (messages.Any(msg => msg.Text == message)) { return; } if (messages.Any(msg => msg.Text == message)) { return; }
messages.Add(new GUIMessage(message, color, lifeTime ?? MathHelper.Clamp(message.Length / 5.0f, 3.0f, 10.0f), font ?? LargeFont)); messages.Add(new GUIMessage(message, color, lifeTime ?? MathHelper.Clamp(message.Length / 5.0f, 3.0f, 10.0f), font ?? LargeFont));
if (playSound) SoundPlayer.PlayUISound(GUISoundType.UIMessage); if (playSound) { SoundPlayer.PlayUISound(GUISoundType.UIMessage); }
} }
public static void AddMessage(string message, Color color, Vector2 pos, Vector2 velocity, float lifeTime = 3.0f, bool playSound = true, GUISoundType soundType = GUISoundType.UIMessage, int subId = -1) public static void AddMessage(string message, Color color, Vector2 pos, Vector2 velocity, float lifeTime = 3.0f, bool playSound = true, GUISoundType soundType = GUISoundType.UIMessage, int subId = -1)
{ {
Submarine sub = Submarine.Loaded.FirstOrDefault(s => s.ID == subId); Submarine sub = Submarine.Loaded.FirstOrDefault(s => s.ID == subId);
messages.Add(new GUIMessage(message, color, pos, velocity, lifeTime, Alignment.Center, LargeFont, sub: sub));
if (playSound) SoundPlayer.PlayUISound(soundType); var newMessage = new GUIMessage(message, color, pos, velocity, lifeTime, Alignment.Center, Font, sub: sub);
if (playSound) { SoundPlayer.PlayUISound(soundType); }
bool overlapFound = true;
int tries = 0;
while (overlapFound)
{
overlapFound = false;
foreach (var otherMessage in messages)
{
float xDiff = otherMessage.Pos.X - newMessage.Pos.X;
if (Math.Abs(xDiff) > (newMessage.Size.X + otherMessage.Size.X) / 2) { continue; }
float yDiff = otherMessage.Pos.Y - newMessage.Pos.Y;
if (Math.Abs(yDiff) > (newMessage.Size.Y + otherMessage.Size.Y) / 2) { continue; }
Vector2 moveDir = -(new Vector2(xDiff, yDiff) + Rand.Vector(1.0f));
if (moveDir.LengthSquared() > 0.0001f)
{
moveDir = Vector2.Normalize(moveDir);
}
else
{
moveDir = Rand.Vector(1.0f);
}
moveDir.Y = -Math.Abs(moveDir.Y);
newMessage.Pos -= Vector2.UnitY * 10;
}
tries++;
if (tries > 20) { break; }
}
messages.Add(newMessage);
} }
public static void ClearMessages() public static void ClearMessages()
@@ -52,13 +52,13 @@ namespace Barotrauma
public GUIComponent GetChild(int index) public GUIComponent GetChild(int index)
{ {
if (index < 0 || index >= CountChildren) return null; if (index < 0 || index >= CountChildren) { return null; }
return RectTransform.GetChild(index).GUIComponent; return RectTransform.GetChild(index).GUIComponent;
} }
public int GetChildIndex(GUIComponent child) public int GetChildIndex(GUIComponent child)
{ {
if (child == null) return -1; if (child == null) { return -1; }
return RectTransform.GetChildIndex(child.RectTransform); return RectTransform.GetChildIndex(child.RectTransform);
} }
@@ -66,7 +66,7 @@ namespace Barotrauma
{ {
foreach (GUIComponent child in Children) foreach (GUIComponent child in Children)
{ {
if (child.UserData == obj || (child.userData != null && child.userData.Equals(obj))) return child; if (child.UserData == obj || (child.userData != null && child.userData.Equals(obj))) { return child; }
} }
return null; return null;
} }
@@ -175,6 +175,8 @@ namespace Barotrauma
public bool GlowOnSelect { get; set; } public bool GlowOnSelect { get; set; }
public Vector2 UVOffset { get; set; }
private CoroutineHandle pulsateCoroutine; private CoroutineHandle pulsateCoroutine;
protected Color flashColor; protected Color flashColor;
@@ -256,9 +258,9 @@ namespace Barotrauma
protected Rectangle ClampRect(Rectangle r) protected Rectangle ClampRect(Rectangle r)
{ {
if (Parent == null || !ClampMouseRectToParent) return r; if (Parent == null || !ClampMouseRectToParent) { return r; }
Rectangle parentRect = Parent.ClampRect(Parent.Rect); Rectangle parentRect = Parent.ClampRect(Parent.Rect);
if (parentRect.Width <= 0 || parentRect.Height <= 0) return Rectangle.Empty; if (parentRect.Width <= 0 || parentRect.Height <= 0) { return Rectangle.Empty; }
if (parentRect.X > r.X) if (parentRect.X > r.X)
{ {
int diff = parentRect.X - r.X; int diff = parentRect.X - r.X;
@@ -281,7 +283,7 @@ namespace Barotrauma
int diff = (r.Y + r.Height) - (parentRect.Y + parentRect.Height); int diff = (r.Y + r.Height) - (parentRect.Y + parentRect.Height);
r.Height -= diff; r.Height -= diff;
} }
if (r.Width <= 0 || r.Height <= 0) return Rectangle.Empty; if (r.Width <= 0 || r.Height <= 0) { return Rectangle.Empty; }
return r; return r;
} }
@@ -295,7 +297,7 @@ namespace Barotrauma
{ {
get get
{ {
if (!CanBeFocused) return Rectangle.Empty; if (!CanBeFocused) { return Rectangle.Empty; }
return ClampMouseRectToParent ? ClampRect(Rect) : Rect; return ClampMouseRectToParent ? ClampRect(Rect) : Rect;
} }
} }
@@ -431,7 +433,7 @@ namespace Barotrauma
#region Updating #region Updating
public virtual void AddToGUIUpdateList(bool ignoreChildren = false, int order = 0) public virtual void AddToGUIUpdateList(bool ignoreChildren = false, int order = 0)
{ {
if (!Visible) return; if (!Visible) { return; }
UpdateOrder = order; UpdateOrder = order;
GUI.AddToUpdateList(this); GUI.AddToUpdateList(this);
@@ -463,7 +465,7 @@ namespace Barotrauma
/// </summary> /// </summary>
public void UpdateManually(float deltaTime, bool alsoChildren = false, bool recursive = true) public void UpdateManually(float deltaTime, bool alsoChildren = false, bool recursive = true)
{ {
if (!Visible) return; if (!Visible) { return; }
AutoUpdate = false; AutoUpdate = false;
Update(deltaTime); Update(deltaTime);
@@ -475,7 +477,7 @@ namespace Barotrauma
protected virtual void Update(float deltaTime) protected virtual void Update(float deltaTime)
{ {
if (!Visible) return; if (!Visible) { return; }
if (CanBeFocused && OnSecondaryClicked != null) if (CanBeFocused && OnSecondaryClicked != null)
{ {
@@ -555,7 +557,7 @@ namespace Barotrauma
/// </summary> /// </summary>
public virtual void DrawManually(SpriteBatch spriteBatch, bool alsoChildren = false, bool recursive = true) public virtual void DrawManually(SpriteBatch spriteBatch, bool alsoChildren = false, bool recursive = true)
{ {
if (!Visible) return; if (!Visible) { return; }
AutoDraw = false; AutoDraw = false;
Draw(spriteBatch); Draw(spriteBatch);
@@ -598,7 +600,7 @@ namespace Barotrauma
protected virtual void Draw(SpriteBatch spriteBatch) protected virtual void Draw(SpriteBatch spriteBatch)
{ {
if (!Visible) return; if (!Visible) { return; }
var rect = Rect; var rect = Rect;
GetBlendedColor(GetColor(State), ref _currentColor); GetBlendedColor(GetColor(State), ref _currentColor);
@@ -653,7 +655,7 @@ namespace Barotrauma
? MathUtils.InverseLerp(0, SpriteCrossFadeTime, ToolBox.GetEasing(uiSprite.TransitionMode, spriteFadeTimer)) : 0; ? MathUtils.InverseLerp(0, SpriteCrossFadeTime, ToolBox.GetEasing(uiSprite.TransitionMode, spriteFadeTimer)) : 0;
if (alphaMultiplier > 0) if (alphaMultiplier > 0)
{ {
uiSprite.Draw(spriteBatch, rect, previousColor * alphaMultiplier, SpriteEffects); uiSprite.Draw(spriteBatch, rect, previousColor * alphaMultiplier, SpriteEffects, uvOffset: UVOffset);
} }
} }
} }
@@ -667,7 +669,11 @@ namespace Barotrauma
? MathUtils.InverseLerp(SpriteCrossFadeTime, 0, ToolBox.GetEasing(uiSprite.TransitionMode, spriteFadeTimer)) : (_currentColor.A / 255.0f); ? MathUtils.InverseLerp(SpriteCrossFadeTime, 0, ToolBox.GetEasing(uiSprite.TransitionMode, spriteFadeTimer)) : (_currentColor.A / 255.0f);
if (alphaMultiplier > 0) if (alphaMultiplier > 0)
{ {
uiSprite.Draw(spriteBatch, rect, _currentColor * alphaMultiplier, SpriteEffects); // * (rect.Location.Y - PlayerInput.MousePosition.Y) / rect.Height
Vector2 offset = new Vector2(
MathUtils.PositiveModulo((int)-UVOffset.X, uiSprite.Sprite.SourceRect.Width),
MathUtils.PositiveModulo((int)-UVOffset.Y, uiSprite.Sprite.SourceRect.Height));
uiSprite.Draw(spriteBatch, rect, _currentColor * alphaMultiplier, SpriteEffects, uvOffset: offset);
} }
} }
} }
@@ -708,7 +714,7 @@ namespace Barotrauma
/// </summary> /// </summary>
public void DrawToolTip(SpriteBatch spriteBatch) public void DrawToolTip(SpriteBatch spriteBatch)
{ {
if (!Visible) return; if (!Visible) { return; }
DrawToolTip(spriteBatch, ToolTip, GUI.MouseOn.Rect, TooltipRichTextData); DrawToolTip(spriteBatch, ToolTip, GUI.MouseOn.Rect, TooltipRichTextData);
} }
@@ -1048,7 +1054,7 @@ namespace Barotrauma
{ {
case "language": case "language":
string[] languages = element.GetAttributeStringArray(attribute.Name.ToString(), new string[0]); string[] languages = element.GetAttributeStringArray(attribute.Name.ToString(), new string[0]);
if (!languages.Any(l => GameMain.Config.Language.ToLower() == l.ToLower())) { return false; } if (!languages.Any(l => GameMain.Config.Language.Equals(l, StringComparison.OrdinalIgnoreCase))) { return false; }
break; break;
case "gameversion": case "gameversion":
var version = new Version(attribute.Value); var version = new Version(attribute.Value);
@@ -1213,8 +1219,7 @@ namespace Barotrauma
private static GUIImage LoadGUIImage(XElement element, RectTransform parent) private static GUIImage LoadGUIImage(XElement element, RectTransform parent)
{ {
Sprite sprite = null; Sprite sprite;
string url = element.GetAttributeString("url", ""); string url = element.GetAttributeString("url", "");
if (!string.IsNullOrEmpty(url)) if (!string.IsNullOrEmpty(url))
{ {
@@ -24,6 +24,8 @@ namespace Barotrauma
public bool Dropped { get; set; } public bool Dropped { get; set; }
public bool AllowNonText { get; set; }
public object SelectedItemData public object SelectedItemData
{ {
get get
@@ -318,9 +320,9 @@ namespace Barotrauma
if (textBlock == null) if (textBlock == null)
{ {
textBlock = component.GetChild<GUITextBlock>(); textBlock = component.GetChild<GUITextBlock>();
if (textBlock == null) return false; if (textBlock is null && !AllowNonText) { return false; }
} }
button.Text = textBlock.Text; button.Text = textBlock?.Text ?? "";
} }
Dropped = false; Dropped = false;
// TODO: OnSelected can be called multiple times and when it shouldn't be called -> turn into an event so that nobody else can call it. // TODO: OnSelected can be called multiple times and when it shouldn't be called -> turn into an event so that nobody else can call it.
@@ -6,7 +6,7 @@ namespace Barotrauma
{ {
public class GUIFrame : GUIComponent public class GUIFrame : GUIComponent
{ {
public int OutlineThickness { get; set; } public float OutlineThickness { get; set; }
public GUIFrame(RectTransform rectT, string style = "", Color? color = null) : base(style, rectT) public GUIFrame(RectTransform rectT, string style = "", Color? color = null) : base(style, rectT)
{ {
@@ -28,7 +28,7 @@ namespace Barotrauma
if (OutlineColor != Color.Transparent) if (OutlineColor != Color.Transparent)
{ {
GUI.DrawRectangle(spriteBatch, Rect, OutlineColor * (OutlineColor.A/255.0f), false, thickness: OutlineThickness); GUI.DrawRectangle(spriteBatch, Rect, OutlineColor, false, thickness: OutlineThickness);
} }
} }
} }
@@ -174,7 +174,7 @@ namespace Barotrauma
if (BlendState != null) if (BlendState != null)
{ {
spriteBatch.End(); spriteBatch.End();
spriteBatch.Begin(blendState: BlendState, samplerState: GUI.SamplerState); spriteBatch.Begin(blendState: BlendState, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
} }
if (style != null) if (style != null)
@@ -33,7 +33,7 @@ namespace Barotrauma
public GUIFrame Content { get; private set; } public GUIFrame Content { get; private set; }
public GUIScrollBar ScrollBar { get; private set; } public GUIScrollBar ScrollBar { get; private set; }
private Dictionary<GUIComponent, bool> childVisible = new Dictionary<GUIComponent, bool>(); private readonly Dictionary<GUIComponent, bool> childVisible = new Dictionary<GUIComponent, bool>();
private int totalSize; private int totalSize;
private bool childrenNeedsRecalculation; private bool childrenNeedsRecalculation;
@@ -224,7 +224,7 @@ namespace Barotrauma
{ {
if (value == false && canDragElements && draggedElement != null) if (value == false && canDragElements && draggedElement != null)
{ {
draggedElement = null; DraggedElement = null;
} }
canDragElements = value; canDragElements = value;
} }
@@ -233,11 +233,29 @@ namespace Barotrauma
private GUIComponent draggedElement; private GUIComponent draggedElement;
private Rectangle draggedReferenceRectangle; private Rectangle draggedReferenceRectangle;
private Point draggedReferenceOffset; private Point draggedReferenceOffset;
public bool HasDraggedElementIndexChanged { get; private set; }
public GUIComponent DraggedElement => draggedElement; public GUIComponent DraggedElement
{
get
{
return draggedElement;
}
set
{
if (value == draggedElement) { return; }
draggedElement = value;
HasDraggedElementIndexChanged = false;
}
}
private readonly bool isHorizontal; private readonly bool isHorizontal;
/// <summary>
/// Setting this to true and CanBeFocused to false allows the list background to be unfocusable while the elements can still be interacted with.
/// </summary>
public bool CanInteractWhenUnfocusable { get; set; } = false;
/// <param name="isScrollBarOnDefaultSide">For horizontal listbox, default side is on the bottom. For vertical, it's on the right.</param> /// <param name="isScrollBarOnDefaultSide">For horizontal listbox, default side is on the bottom. For vertical, it's on the right.</param>
public GUIListBox(RectTransform rectT, bool isHorizontal = false, Color? color = null, string style = "", bool isScrollBarOnDefaultSide = true, bool useMouseDownToSelect = false) : base(style, rectT) public GUIListBox(RectTransform rectT, bool isHorizontal = false, Color? color = null, string style = "", bool isScrollBarOnDefaultSide = true, bool useMouseDownToSelect = false) : base(style, rectT)
{ {
@@ -472,7 +490,7 @@ namespace Barotrauma
if (!PlayerInput.PrimaryMouseButtonHeld()) if (!PlayerInput.PrimaryMouseButtonHeld())
{ {
OnRearranged?.Invoke(this, draggedElement.UserData); OnRearranged?.Invoke(this, draggedElement.UserData);
draggedElement = null; DraggedElement = null;
RepositionChildren(); RepositionChildren();
} }
else else
@@ -518,6 +536,7 @@ namespace Barotrauma
if (currIndex != index) if (currIndex != index)
{ {
draggedElement.RectTransform.RepositionChildInHierarchy(currIndex); draggedElement.RectTransform.RepositionChildInHierarchy(currIndex);
HasDraggedElementIndexChanged = true;
} }
return; return;
@@ -556,7 +575,7 @@ namespace Barotrauma
if (child == null || !child.Visible) { continue; } if (child == null || !child.Visible) { continue; }
// selecting // selecting
if (Enabled && CanBeFocused && child.CanBeFocused && child.Rect.Contains(PlayerInput.MousePosition) && GUI.IsMouseOn(child)) if (Enabled && (CanBeFocused || CanInteractWhenUnfocusable) && child.CanBeFocused && child.Rect.Contains(PlayerInput.MousePosition) && GUI.IsMouseOn(child))
{ {
child.State = ComponentState.Hover; child.State = ComponentState.Hover;
@@ -577,7 +596,7 @@ namespace Barotrauma
if (CanDragElements && PlayerInput.PrimaryMouseButtonDown() && GUI.MouseOn == child) if (CanDragElements && PlayerInput.PrimaryMouseButtonDown() && GUI.MouseOn == child)
{ {
draggedElement = child; DraggedElement = child;
draggedReferenceRectangle = child.Rect; draggedReferenceRectangle = child.Rect;
draggedReferenceOffset = child.RectTransform.AbsoluteOffset; draggedReferenceOffset = child.RectTransform.AbsoluteOffset;
} }
@@ -681,6 +700,8 @@ namespace Barotrauma
} }
} }
public void ForceUpdate() => Update((float)Timing.Step);
protected override void Update(float deltaTime) protected override void Update(float deltaTime)
{ {
if (!Visible) { return; } if (!Visible) { return; }
@@ -750,7 +771,7 @@ namespace Barotrauma
} }
} }
if ((GUI.IsMouseOn(this) || GUI.IsMouseOn(ScrollBar)) && AllowMouseWheelScroll && PlayerInput.ScrollWheelSpeed != 0) if (PlayerInput.ScrollWheelSpeed != 0 && AllowMouseWheelScroll && (FindScrollableParentListBox(GUI.MouseOn) == this || GUI.IsMouseOn(ScrollBar)))
{ {
if (SmoothScroll) if (SmoothScroll)
{ {
@@ -774,7 +795,6 @@ namespace Barotrauma
} }
} }
ScrollBar.Enabled = ScrollBarEnabled && BarSize < 1.0f; ScrollBar.Enabled = ScrollBarEnabled && BarSize < 1.0f;
if (AutoHideScrollBar) if (AutoHideScrollBar)
{ {
@@ -786,6 +806,13 @@ namespace Barotrauma
} }
} }
private static GUIListBox FindScrollableParentListBox(GUIComponent target)
{
if (target is GUIListBox listBox && listBox.ScrollBarEnabled && listBox.BarSize < 1.0f) { return listBox; }
if (target?.Parent == null) { return null; }
return FindScrollableParentListBox(target.Parent);
}
public void SelectNext(bool force = false, bool autoScroll = true, bool takeKeyBoardFocus = false) public void SelectNext(bool force = false, bool autoScroll = true, bool takeKeyBoardFocus = false)
{ {
int index = SelectedIndex + 1; int index = SelectedIndex + 1;
@@ -982,7 +1009,7 @@ namespace Barotrauma
if (child == null) { return; } if (child == null) { return; }
child.RectTransform.Parent = null; child.RectTransform.Parent = null;
if (selected.Contains(child)) { selected.Remove(child); } if (selected.Contains(child)) { selected.Remove(child); }
if (draggedElement == child) { draggedElement = null; } if (draggedElement == child) { DraggedElement = null; }
UpdateScrollBarSize(); UpdateScrollBarSize();
} }
@@ -999,7 +1026,6 @@ namespace Barotrauma
ContentBackground.DrawManually(spriteBatch, alsoChildren: false); ContentBackground.DrawManually(spriteBatch, alsoChildren: false);
Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle; Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
RasterizerState prevRasterizerState = spriteBatch.GraphicsDevice.RasterizerState;
if (HideChildrenOutsideFrame) if (HideChildrenOutsideFrame)
{ {
spriteBatch.End(); spriteBatch.End();
@@ -1027,7 +1053,7 @@ namespace Barotrauma
{ {
spriteBatch.End(); spriteBatch.End();
spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect; spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect;
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: prevRasterizerState); spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
} }
if (ScrollBarVisible) if (ScrollBarVisible)
@@ -79,14 +79,14 @@ namespace Barotrauma
new Rectangle( new Rectangle(
sliderArea.X + (int)sliceBorderSizes.X, sliderArea.X + (int)sliceBorderSizes.X,
sliderArea.Y, sliderArea.Y,
(int)((sliderArea.Width - sliceBorderSizes.X - sliceBorderSizes.Z) * fillAmount), (int)Math.Round((sliderArea.Width - sliceBorderSizes.X - sliceBorderSizes.Z) * fillAmount),
sliderArea.Height) sliderArea.Height)
: :
new Rectangle( new Rectangle(
sliderArea.X, sliderArea.X,
(int)(sliderArea.Bottom - (sliderArea.Height - sliceBorderSizes.Y - sliceBorderSizes.W) * fillAmount - sliceBorderSizes.W), (int)Math.Round(sliderArea.Bottom - (sliderArea.Height - sliceBorderSizes.Y - sliceBorderSizes.W) * fillAmount - sliceBorderSizes.W),
sliderArea.Width, sliderArea.Width,
(int)((sliderArea.Height - sliceBorderSizes.Y - sliceBorderSizes.W) * fillAmount)); (int)Math.Round((sliderArea.Height - sliceBorderSizes.Y - sliceBorderSizes.W) * fillAmount));
sliderRect.Width = Math.Max(sliderRect.Width, 1); sliderRect.Width = Math.Max(sliderRect.Width, 1);
sliderRect.Height = Math.Max(sliderRect.Height, 1); sliderRect.Height = Math.Max(sliderRect.Height, 1);
@@ -0,0 +1,87 @@
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma
{
public class GUIScissorComponent: GUIComponent
{
public GUIComponent Content;
public GUIScissorComponent(RectTransform rectT) : base(null, rectT)
{
Content = new GUIFrame(new RectTransform(Vector2.One, rectT), style: null)
{
CanBeFocused = false
};
}
protected override void Update(float deltaTime)
{
base.Update(deltaTime);
foreach (GUIComponent child in Children)
{
if (child == Content) { continue; }
throw new InvalidOperationException($"Children were found in {nameof(GUIScissorComponent)}, Add them to {nameof(GUIScissorComponent)}.{nameof(Content)} instead.");
}
ClampChildMouseRects(Content);
}
protected override void Draw(SpriteBatch spriteBatch)
{
if (!Visible) { return; }
Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
RasterizerState prevRasterizerState = spriteBatch.GraphicsDevice.RasterizerState;
spriteBatch.End();
spriteBatch.GraphicsDevice.ScissorRectangle = Rectangle.Intersect(prevScissorRect, Rect);
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
foreach (GUIComponent child in Content.Children)
{
if (!child.Visible) { continue; }
child.DrawManually(spriteBatch, alsoChildren: true, recursive: true);
}
spriteBatch.End();
spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect;
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: prevRasterizerState);
}
private void ClampChildMouseRects(GUIComponent child)
{
child.ClampMouseRectToParent = true;
if (child is GUIListBox) { return; }
foreach (GUIComponent grandChild in child.Children)
{
ClampChildMouseRects(grandChild);
}
}
public override void AddToGUIUpdateList(bool ignoreChildren = false, int order = 0)
{
if (!Visible) { return; }
UpdateOrder = order;
GUI.AddToUpdateList(this);
if (ignoreChildren)
{
OnAddedToGUIUpdateList?.Invoke(this);
return;
}
foreach (GUIComponent child in Content.Children)
{
if (!child.Visible) { continue; }
child.AddToGUIUpdateList(false, order);
}
OnAddedToGUIUpdateList?.Invoke(this);
}
}
}
@@ -11,7 +11,7 @@ namespace Barotrauma
{ {
private Dictionary<string, GUIComponentStyle> componentStyles; private Dictionary<string, GUIComponentStyle> componentStyles;
private XElement configElement; private readonly XElement configElement;
private GraphicsDevice graphicsDevice; private GraphicsDevice graphicsDevice;
@@ -25,6 +25,7 @@ namespace Barotrauma
public ScalableFont SubHeadingFont { get; private set; } public ScalableFont SubHeadingFont { get; private set; }
public ScalableFont DigitalFont { get; private set; } public ScalableFont DigitalFont { get; private set; }
public ScalableFont HotkeyFont { get; private set; } public ScalableFont HotkeyFont { get; private set; }
public ScalableFont MonospacedFont { get; private set; }
public Dictionary<ScalableFont, bool> ForceFontUpperCase public Dictionary<ScalableFont, bool> ForceFontUpperCase
{ {
@@ -40,12 +41,20 @@ namespace Barotrauma
public SpriteSheet SavingIndicator { get; private set; } public SpriteSheet SavingIndicator { get; private set; }
public UISprite UIGlow { get; private set; } public UISprite UIGlow { get; private set; }
public UISprite PingCircle { get; private set; }
public UISprite UIGlowCircular { get; private set; } public UISprite UIGlowCircular { get; private set; }
public UISprite UIGlowSolidCircular { get; private set; }
public UISprite UIThermalGlow { get; private set; }
public UISprite ButtonPulse { get; private set; } public UISprite ButtonPulse { get; private set; }
public SpriteSheet FocusIndicator { get; private set; } public SpriteSheet FocusIndicator { get; private set; }
public UISprite IconOverflowIndicator { get; private set; }
/// <summary> /// <summary>
/// General green color used for elements whose colors are set from code /// General green color used for elements whose colors are set from code
/// </summary> /// </summary>
@@ -82,6 +91,12 @@ namespace Barotrauma
public Color TextColorDark { get; private set; } = Color.Black * 0.9f; public Color TextColorDark { get; private set; } = Color.Black * 0.9f;
public Color TextColorDim { get; private set; } = Color.White * 0.6f; public Color TextColorDim { get; private set; } = Color.White * 0.6f;
public Color ItemQualityColorPoor { get; private set; } = Color.DarkRed;
public Color ItemQualityColorNormal { get; private set; } = Color.Gray;
public Color ItemQualityColorGood { get; private set; } = Color.LightGreen;
public Color ItemQualityColorExcellent { get; private set; } = Color.LightBlue;
public Color ItemQualityColorMasterwork { get; private set; } = Color.MediumPurple;
public Color ColorReputationVeryLow { get; private set; } = Color.Red; public Color ColorReputationVeryLow { get; private set; } = Color.Red;
public Color ColorReputationLow { get; private set; } = Color.Orange; public Color ColorReputationLow { get; private set; } = Color.Orange;
public Color ColorReputationNeutral { get; private set; } = Color.White * 0.8f; public Color ColorReputationNeutral { get; private set; } = Color.White * 0.8f;
@@ -235,6 +250,9 @@ namespace Barotrauma
case "uiglow": case "uiglow":
UIGlow = new UISprite(subElement); UIGlow = new UISprite(subElement);
break; break;
case "pingcircle":
PingCircle = new UISprite(subElement);
break;
case "radiation": case "radiation":
RadiationSprite = new UISprite(subElement); RadiationSprite = new UISprite(subElement);
break; break;
@@ -244,9 +262,18 @@ namespace Barotrauma
case "uiglowcircular": case "uiglowcircular":
UIGlowCircular = new UISprite(subElement); UIGlowCircular = new UISprite(subElement);
break; break;
case "uiglowsolidcircular":
UIGlowSolidCircular = new UISprite(subElement);
break;
case "uithermalglow":
UIThermalGlow = new UISprite(subElement);
break;
case "endroundbuttonpulse": case "endroundbuttonpulse":
ButtonPulse = new UISprite(subElement); ButtonPulse = new UISprite(subElement);
break; break;
case "iconoverflowindicator":
IconOverflowIndicator = new UISprite(subElement);
break;
case "focusindicator": case "focusindicator":
FocusIndicator = new SpriteSheet(subElement); FocusIndicator = new SpriteSheet(subElement);
break; break;
@@ -277,6 +304,10 @@ namespace Barotrauma
DigitalFont = LoadFont(subElement, graphicsDevice); DigitalFont = LoadFont(subElement, graphicsDevice);
ForceFontUpperCase[DigitalFont] = subElement.GetAttributeBool("forceuppercase", false); ForceFontUpperCase[DigitalFont] = subElement.GetAttributeBool("forceuppercase", false);
break; break;
case "monospacedfont":
MonospacedFont = LoadFont(subElement, graphicsDevice);
ForceFontUpperCase[MonospacedFont] = subElement.GetAttributeBool("forceuppercase", false);
break;
case "hotkeyfont": case "hotkeyfont":
HotkeyFont = LoadFont(subElement, graphicsDevice); HotkeyFont = LoadFont(subElement, graphicsDevice);
ForceFontUpperCase[HotkeyFont] = subElement.GetAttributeBool("forceuppercase", false); ForceFontUpperCase[HotkeyFont] = subElement.GetAttributeBool("forceuppercase", false);
@@ -480,5 +511,22 @@ namespace Barotrauma
targetComponent.ApplyStyle(componentStyle); targetComponent.ApplyStyle(componentStyle);
} }
public Color GetQualityColor(int quality)
{
switch (quality)
{
case 1:
return ItemQualityColorGood;
case 2:
return ItemQualityColorExcellent;
case 3:
return ItemQualityColorMasterwork;
case -1:
return ItemQualityColorPoor;
default:
return ItemQualityColorNormal;
}
}
} }
} }
@@ -185,7 +185,7 @@ namespace Barotrauma
if (GUI.MouseOn != null) { return false; } if (GUI.MouseOn != null) { return false; }
//don't close when hovering over an inventory element //don't close when hovering over an inventory element
if (Inventory.IsMouseOnInventory()) { return false; } if (Inventory.IsMouseOnInventory) { return false; }
bool input = PlayerInput.PrimaryMouseButtonDown() || PlayerInput.SecondaryMouseButtonClicked(); bool input = PlayerInput.PrimaryMouseButtonDown() || PlayerInput.SecondaryMouseButtonClicked();
return input && !rect.Contains(PlayerInput.MousePosition); return input && !rect.Contains(PlayerInput.MousePosition);
@@ -193,7 +193,7 @@ namespace Barotrauma
if (LoadState == 100.0f) if (LoadState == 100.0f)
{ {
#if DEBUG #if DEBUG
if (GameMain.Config.AutomaticQuickStartEnabled || GameMain.Config.AutomaticCampaignLoadEnabled && GameMain.FirstLoad) if (GameMain.Config.AutomaticQuickStartEnabled || GameMain.Config.AutomaticCampaignLoadEnabled || GameMain.Config.TestScreenEnabled && GameMain.FirstLoad)
{ {
loadText = "QUICKSTARTING ..."; loadText = "QUICKSTARTING ...";
} }
@@ -211,6 +211,13 @@ namespace Barotrauma
if (LoadState != null) if (LoadState != null)
{ {
loadText += " " + (int)LoadState + " %"; loadText += " " + (int)LoadState + " %";
#if DEBUG
if (GameMain.FirstLoad && GameMain.CancelQuickStart)
{
loadText += " (Quickstart aborted)";
}
#endif
} }
} }
if (GUI.LargeFont != null) if (GUI.LargeFont != null)
@@ -55,14 +55,47 @@ namespace Barotrauma
var texture = GetTexture(spriteBatch); var texture = GetTexture(spriteBatch);
for (var i = 0; i < points.Count - 1; i++) for (var i = 0; i < points.Count - 1; i++)
DrawPolygonEdge(spriteBatch, texture, points[i] + offset, points[i + 1] + offset, color, thickness); DrawPolygonEdge(spriteBatch, points[i] + offset, points[i + 1] + offset, color, thickness);
DrawPolygonEdge(spriteBatch, texture, points[points.Count - 1] + offset, points[0] + offset, color, DrawPolygonEdge(spriteBatch, points[points.Count - 1] + offset, points[0] + offset, color,
thickness); thickness);
} }
private static void DrawPolygonEdge(SpriteBatch spriteBatch, Texture2D texture, Vector2 point1, Vector2 point2, /// <summary>
Color color, float thickness) /// Draws a closed polygon from an array of points
/// </summary>
public static void DrawPolygonInner(this SpriteBatch spriteBatch, Vector2 offset, IReadOnlyList<Vector2> points, Color color, float thickness = 1f)
{
if (points.Count == 0) { return; }
if (points.Count == 1)
{
DrawPoint(spriteBatch, points[0], color, (int)thickness);
return;
}
for (var i = 0; i < points.Count - 1; i++)
{
Vector2 point1 = points[i] + offset,
point2 = points[i + 1] + offset;
DrawPolygonEdgeInner(spriteBatch, point1, point2, color, thickness);
}
DrawPolygonEdgeInner(spriteBatch, points[^1] + offset, points[0] + offset, color, thickness);
}
private static void DrawPolygonEdgeInner(SpriteBatch spriteBatch, Vector2 point1, Vector2 point2, Color color, float thickness)
{
var length = Vector2.Distance(point1, point2) + thickness;
var angle = (float)Math.Atan2(point2.Y - point1.Y, point2.X - point1.X);
var scale = new Vector2(length, thickness);
Vector2 middle = new Vector2((point1.X + point2.X) / 2f, (point1.Y + point2.Y) / 2f);
Texture2D tex = GetTexture(spriteBatch);
spriteBatch.Draw(GetTexture(spriteBatch), middle, null, color, angle, new Vector2(tex.Width / 2f, tex.Height / 2f), scale, SpriteEffects.None, 0);
}
private static void DrawPolygonEdge(SpriteBatch spriteBatch, Vector2 point1, Vector2 point2, Color color, float thickness)
{ {
var length = Vector2.Distance(point1, point2); var length = Vector2.Distance(point1, point2);
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);
@@ -37,7 +37,7 @@ namespace Barotrauma
private Color storeSpecialColor; private Color storeSpecialColor;
private GUIListBox shoppingCrateBuyList, shoppingCrateSellList, shoppingCrateSellFromSubList; private GUIListBox shoppingCrateBuyList, shoppingCrateSellList, shoppingCrateSellFromSubList;
private GUITextBlock shoppingCrateTotal; private GUITextBlock relevantBalanceName, shoppingCrateTotal;
private GUIButton clearAllButton, confirmButton; private GUIButton clearAllButton, confirmButton;
private bool needsRefresh, needsBuyingRefresh, needsSellingRefresh, needsItemsToSellRefresh, needsSellingFromSubRefresh, needsItemsToSellFromSubRefresh; private bool needsRefresh, needsBuyingRefresh, needsSellingRefresh, needsItemsToSellRefresh, needsSellingFromSubRefresh, needsItemsToSellFromSubRefresh;
@@ -58,7 +58,6 @@ namespace Barotrauma
StoreTab.SellFromSub => false, StoreTab.SellFromSub => false,
_ => throw new NotImplementedException() _ => throw new NotImplementedException()
}; };
private bool IsSelling => !IsBuying;
private GUIListBox ActiveShoppingCrateList => activeTab switch private GUIListBox ActiveShoppingCrateList => activeTab switch
{ {
StoreTab.Buy => shoppingCrateBuyList, StoreTab.Buy => shoppingCrateBuyList,
@@ -222,16 +221,8 @@ namespace Barotrauma
TextScale = 1.1f, TextScale = 1.1f,
TextGetter = () => TextGetter = () =>
{ {
if (CurrentLocation != null) merchantBalanceBlock.TextColor = CurrentLocation?.BalanceColor ?? Color.Red;
{ return GetMerchantBalanceText();
merchantBalanceBlock.TextColor = CurrentLocation.BalanceColor;
return GetCurrencyFormatted(CurrentLocation.StoreCurrentBalance);
}
else
{
merchantBalanceBlock.TextColor = Color.Red;
return GetCurrencyFormatted(0);
}
} }
}; };
@@ -375,28 +366,37 @@ namespace Barotrauma
//don't show categories with no buyable items //don't show categories with no buyable items
itemCategories.RemoveAll(c => !ItemPrefab.Prefabs.Any(ep => ep.Category.HasFlag(c) && ep.CanBeBought)); itemCategories.RemoveAll(c => !ItemPrefab.Prefabs.Any(ep => ep.Category.HasFlag(c) && ep.CanBeBought));
itemCategoryButtons.Clear(); itemCategoryButtons.Clear();
var categoryButton = new GUIButton(new RectTransform(new Point(categoryButtonContainer.Rect.Width, categoryButtonContainer.Rect.Width), categoryButtonContainer.RectTransform), style: "CategoryButton.All")
{
ToolTip = TextManager.Get("MapEntityCategory.All"),
OnClicked = OnClickedCategoryButton
};
itemCategoryButtons.Add(categoryButton);
foreach (MapEntityCategory category in itemCategories) foreach (MapEntityCategory category in itemCategories)
{ {
var categoryButton = new GUIButton(new RectTransform(new Point(categoryButtonContainer.Rect.Width, categoryButtonContainer.Rect.Width), categoryButtonContainer.RectTransform), categoryButton = new GUIButton(new RectTransform(new Point(categoryButtonContainer.Rect.Width, categoryButtonContainer.Rect.Width), categoryButtonContainer.RectTransform),
style: "CategoryButton." + category) style: "CategoryButton." + category)
{ {
ToolTip = TextManager.Get("MapEntityCategory." + category), ToolTip = TextManager.Get("MapEntityCategory." + category),
UserData = category, UserData = category,
OnClicked = (btn, userdata) => OnClicked = OnClickedCategoryButton
{
MapEntityCategory? newCategory = !btn.Selected ? (MapEntityCategory?)userdata : null;
if (newCategory.HasValue) { searchBox.Text = ""; }
if (newCategory != selectedItemCategory) { tabLists[activeTab].ScrollBar.BarScroll = 0f; }
FilterStoreItems(newCategory, searchBox.Text);
return true;
}
}; };
itemCategoryButtons.Add(categoryButton); itemCategoryButtons.Add(categoryButton);
categoryButton.RectTransform.SizeChanged += () => }
bool OnClickedCategoryButton(GUIButton button, object userData)
{
MapEntityCategory? newCategory = !button.Selected ? (MapEntityCategory?)userData : null;
if (newCategory.HasValue) { searchBox.Text = ""; }
if (newCategory != selectedItemCategory) { tabLists[activeTab].ScrollBar.BarScroll = 0f; }
FilterStoreItems(newCategory, searchBox.Text);
return true;
}
foreach (var btn in itemCategoryButtons)
{
btn.RectTransform.SizeChanged += () =>
{ {
var sprite = categoryButton.Frame.sprites[GUIComponent.ComponentState.None].First(); var sprite = btn.Frame.sprites[GUIComponent.ComponentState.None].First();
categoryButton.RectTransform.NonScaledSize = btn.RectTransform.NonScaledSize = new Point(btn.Rect.Width, (int)(btn.Rect.Width * ((float)sprite.Sprite.SourceRect.Height / sprite.Sprite.SourceRect.Width)));
new Point(categoryButton.Rect.Width, (int)(categoryButton.Rect.Width * ((float)sprite.Sprite.SourceRect.Height / sprite.Sprite.SourceRect.Width)));
}; };
} }
@@ -503,11 +503,11 @@ namespace Barotrauma
ForceUpperCase = true ForceUpperCase = true
}; };
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), playerBalanceContainer.RectTransform), new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), playerBalanceContainer.RectTransform),
"", font: GUI.SubHeadingFont, textAlignment: Alignment.TopRight) "", textColor: Color.White, font: GUI.SubHeadingFont, textAlignment: Alignment.TopRight)
{ {
AutoScaleVertical = true, AutoScaleVertical = true,
TextScale = 1.1f, TextScale = 1.1f,
TextGetter = () => GetCurrencyFormatted(PlayerMoney) TextGetter = GetPlayerBalanceText
}; };
// Divider ------------------------------------------------ // Divider ------------------------------------------------
@@ -523,7 +523,7 @@ namespace Barotrauma
RelativeSpacing = 0.015f, RelativeSpacing = 0.015f,
Stretch = true Stretch = true
}; };
var shoppingCrateListContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.85f), shoppingCrateInventoryContainer.RectTransform), style: null); var shoppingCrateListContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.8f), shoppingCrateInventoryContainer.RectTransform), style: null);
shoppingCrateBuyList = new GUIListBox(new RectTransform(Vector2.One, shoppingCrateListContainer.RectTransform)) { Visible = false }; shoppingCrateBuyList = new GUIListBox(new RectTransform(Vector2.One, shoppingCrateListContainer.RectTransform)) { Visible = false };
shoppingCrateSellList = new GUIListBox(new RectTransform(Vector2.One, shoppingCrateListContainer.RectTransform)) { Visible = false }; shoppingCrateSellList = new GUIListBox(new RectTransform(Vector2.One, shoppingCrateListContainer.RectTransform)) { Visible = false };
if (GameMain.IsSingleplayer) if (GameMain.IsSingleplayer)
@@ -531,6 +531,21 @@ namespace Barotrauma
shoppingCrateSellFromSubList = new GUIListBox(new RectTransform(Vector2.One, shoppingCrateListContainer.RectTransform)) { Visible = false }; shoppingCrateSellFromSubList = new GUIListBox(new RectTransform(Vector2.One, shoppingCrateListContainer.RectTransform)) { Visible = false };
} }
var relevantBalanceContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), shoppingCrateInventoryContainer.RectTransform), isHorizontal: true)
{
Stretch = true
};
relevantBalanceName = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), relevantBalanceContainer.RectTransform), "", font: GUI.Font)
{
CanBeFocused = false
};
new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), relevantBalanceContainer.RectTransform), "", textColor: Color.White, font: GUI.SubHeadingFont, textAlignment: Alignment.Right)
{
CanBeFocused = false,
TextScale = 1.1f,
TextGetter = () => IsBuying ? GetPlayerBalanceText() : GetMerchantBalanceText()
};
var totalContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), shoppingCrateInventoryContainer.RectTransform), isHorizontal: true) var totalContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), shoppingCrateInventoryContainer.RectTransform), isHorizontal: true)
{ {
Stretch = true Stretch = true
@@ -576,10 +591,14 @@ namespace Barotrauma
resolutionWhenCreated = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight); resolutionWhenCreated = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
} }
private GUILayoutGroup CreateDealsGroup(GUIListBox parentList) private string GetMerchantBalanceText() => GetCurrencyFormatted(CurrentLocation?.StoreCurrentBalance ?? 0);
private string GetPlayerBalanceText() => GetCurrencyFormatted(PlayerMoney);
private GUILayoutGroup CreateDealsGroup(GUIListBox parentList, int elementCount = 4)
{ {
var elementHeight = (int)(GUI.yScale * 80); var elementHeight = (int)(GUI.yScale * 80);
var frame = new GUIFrame(new RectTransform(new Point(parentList.Content.Rect.Width, 4 * elementHeight + 3), parent: parentList.Content.RectTransform), style: null); var frame = new GUIFrame(new RectTransform(new Point(parentList.Content.Rect.Width, elementCount * elementHeight + 3), parent: parentList.Content.RectTransform), style: null);
frame.UserData = "deals"; frame.UserData = "deals";
var dealsGroup = new GUILayoutGroup(new RectTransform(Vector2.One, frame.RectTransform, anchor: Anchor.Center), childAnchor: Anchor.TopCenter); var dealsGroup = new GUILayoutGroup(new RectTransform(Vector2.One, frame.RectTransform, anchor: Anchor.Center), childAnchor: Anchor.TopCenter);
var dealsHeader = new GUILayoutGroup(new RectTransform(new Point((int)(0.95f * parentList.Content.Rect.Width), elementHeight), parent: dealsGroup.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft); var dealsHeader = new GUILayoutGroup(new RectTransform(new Point((int)(0.95f * parentList.Content.Rect.Width), elementHeight), parent: dealsGroup.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft);
@@ -604,17 +623,14 @@ namespace Barotrauma
{ {
prevLocation.Reputation.OnReputationValueChanged = null; prevLocation.Reputation.OnReputationValueChanged = null;
} }
if (ItemPrefab.Prefabs.Any(p => p.CanBeBoughtAtLocation(CurrentLocation, out PriceInfo _)))
foreach (ItemPrefab itemPrefab in ItemPrefab.Prefabs)
{ {
if (itemPrefab.CanBeBoughtAtLocation(CurrentLocation, out PriceInfo _)) selectedItemCategory = null;
searchBox.Text = "";
ChangeStoreTab(StoreTab.Buy);
if (newLocation?.Reputation != null)
{ {
ChangeStoreTab(StoreTab.Buy); newLocation.Reputation.OnReputationValueChanged += () => { needsRefresh = true; };
if (newLocation?.Reputation != null)
{
newLocation.Reputation.OnReputationValueChanged += () => { needsRefresh = true; };
}
return;
} }
} }
} }
@@ -628,6 +644,7 @@ namespace Barotrauma
tabButton.Selected = (StoreTab)tabButton.UserData == activeTab; tabButton.Selected = (StoreTab)tabButton.UserData == activeTab;
} }
sortingDropDown.SelectItem(tabSortingMethods[tab]); sortingDropDown.SelectItem(tabSortingMethods[tab]);
relevantBalanceName.Text = IsBuying ? TextManager.Get("campaignstore.balance") : TextManager.Get("campaignstore.storebalance");
SetShoppingCrateTotalText(); SetShoppingCrateTotalText();
SetClearAllButtonStatus(); SetClearAllButtonStatus();
SetConfirmButtonBehavior(); SetConfirmButtonBehavior();
@@ -697,7 +714,7 @@ namespace Barotrauma
} }
foreach (GUIButton btn in itemCategoryButtons) foreach (GUIButton btn in itemCategoryButtons)
{ {
btn.Selected = category.HasValue && (MapEntityCategory)btn.UserData == selectedItemCategory; btn.Selected = (MapEntityCategory?)btn.UserData == selectedItemCategory;
} }
list.UpdateScrollBarSize(); list.UpdateScrollBarSize();
} }
@@ -709,6 +726,8 @@ namespace Barotrauma
FilterStoreItems(category, searchBox.Text); FilterStoreItems(category, searchBox.Text);
} }
int prevDailySpecialCount;
private void RefreshStoreBuyList() private void RefreshStoreBuyList()
{ {
float prevBuyListScroll = storeBuyList.BarScroll; float prevBuyListScroll = storeBuyList.BarScroll;
@@ -717,11 +736,14 @@ namespace Barotrauma
bool hasPermissions = HasPermissions; bool hasPermissions = HasPermissions;
HashSet<GUIComponent> existingItemFrames = new HashSet<GUIComponent>(); HashSet<GUIComponent> existingItemFrames = new HashSet<GUIComponent>();
if ((storeDailySpecialsGroup != null) != CurrentLocation.DailySpecials.Any()) int dailySpecialCount = CurrentLocation?.DailySpecials.Count() ?? 3;
if ((storeDailySpecialsGroup != null) != CurrentLocation.DailySpecials.Any() || dailySpecialCount != prevDailySpecialCount)
{ {
if (storeDailySpecialsGroup == null) if (storeDailySpecialsGroup == null || dailySpecialCount != prevDailySpecialCount)
{ {
storeDailySpecialsGroup = CreateDealsGroup(storeBuyList); storeBuyList.RemoveChild(storeDailySpecialsGroup?.Parent);
storeDailySpecialsGroup = CreateDealsGroup(storeBuyList, 1 + dailySpecialCount);
storeDailySpecialsGroup.Parent.SetAsFirstChild(); storeDailySpecialsGroup.Parent.SetAsFirstChild();
} }
else else
@@ -730,6 +752,7 @@ namespace Barotrauma
storeDailySpecialsGroup = null; storeDailySpecialsGroup = null;
} }
storeBuyList.RecalculateChildren(); storeBuyList.RecalculateChildren();
prevDailySpecialCount = dailySpecialCount;
} }
foreach (PurchasedItem item in CurrentLocation.StoreStock) foreach (PurchasedItem item in CurrentLocation.StoreStock)
@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System.Linq; using System.Linq;
@@ -18,8 +19,8 @@ namespace Barotrauma
private static UISprite spectateIcon, disconnectedIcon; private static UISprite spectateIcon, disconnectedIcon;
private static Sprite ownerIcon, moderatorIcon; private static Sprite ownerIcon, moderatorIcon;
private enum InfoFrameTab { Crew, Mission, Reputation, MyCharacter, Traitor, Submarine }; public enum InfoFrameTab { Crew, Mission, Reputation, Traitor, Submarine, Talents };
private static InfoFrameTab selectedTab; public static InfoFrameTab selectedTab;
private GUIFrame infoFrame, contentFrame; private GUIFrame infoFrame, contentFrame;
private readonly List<GUIButton> tabButtons = new List<GUIButton>(); private readonly List<GUIButton> tabButtons = new List<GUIButton>();
@@ -33,6 +34,8 @@ namespace Barotrauma
private List<CharacterTeamType> teamIDs; private List<CharacterTeamType> teamIDs;
private const string inLobbyString = "\u2022 \u2022 \u2022"; private const string inLobbyString = "\u2022 \u2022 \u2022";
private GUIFrame pendingChangesFrame = null;
public static Color OwnCharacterBGColor = Color.Gold * 0.7f; public static Color OwnCharacterBGColor = Color.Gold * 0.7f;
private class LinkedGUI private class LinkedGUI
@@ -125,7 +128,7 @@ namespace Barotrauma
public TabMenu() public TabMenu()
{ {
if (!initialized) Initialize(); if (!initialized) { Initialize(); }
CreateInfoFrame(selectedTab); CreateInfoFrame(selectedTab);
SelectInfoFrameTab(null, selectedTab); SelectInfoFrameTab(null, selectedTab);
@@ -133,6 +136,17 @@ namespace Barotrauma
public void Update() public void Update()
{ {
GameSession.UpdateTalentNotificationIndicator(talentPointNotification);
if (Character.Controlled is { } controlled && talentResetButton != null && talentApplyButton != null)
{
int talentCount = selectedTalents.Count - controlled.Info.GetUnlockedTalentsInTree().Count();
talentResetButton.Enabled = talentApplyButton.Enabled = talentCount > 0;
if (talentApplyButton.Enabled && talentApplyButton.FlashTimer <= 0.0f)
{
talentApplyButton.Flash(GUI.Style.Orange);
}
}
if (selectedTab != InfoFrameTab.Crew) return; if (selectedTab != InfoFrameTab.Crew) return;
if (linkedGUIList == null) return; if (linkedGUIList == null) return;
@@ -182,16 +196,8 @@ namespace Barotrauma
new GUIFrame(new RectTransform(GUI.Canvas.RelativeSize, infoFrame.RectTransform, Anchor.Center), style: "GUIBackgroundBlocker"); new GUIFrame(new RectTransform(GUI.Canvas.RelativeSize, infoFrame.RectTransform, Anchor.Center), style: "GUIBackgroundBlocker");
//this used to be a switch expression but i changed it because it killed enc :( //this used to be a switch expression but i changed it because it killed enc :(
Vector2 contentFrameSize; //now it's not even a switch statement anymore :(
switch (selectedTab) Vector2 contentFrameSize = new Vector2(0.45f, 0.667f);
{
case InfoFrameTab.MyCharacter:
contentFrameSize = new Vector2(0.45f, 0.5f);
break;
default:
contentFrameSize = new Vector2(0.45f, 0.667f);
break;
}
contentFrame = new GUIFrame(new RectTransform(contentFrameSize, infoFrame.RectTransform, Anchor.TopCenter, Pivot.TopCenter) { RelativeOffset = new Vector2(0.0f, 0.12f) }); contentFrame = new GUIFrame(new RectTransform(contentFrameSize, infoFrame.RectTransform, Anchor.TopCenter, Pivot.TopCenter) { RelativeOffset = new Vector2(0.0f, 0.12f) });
var horizontalLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.958f, 0.943f), contentFrame.RectTransform, Anchor.TopCenter, Pivot.TopCenter) { AbsoluteOffset = new Point(0, GUI.IntScale(25f)) }, isHorizontal: true) var horizontalLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.958f, 0.943f), contentFrame.RectTransform, Anchor.TopCenter, Pivot.TopCenter) { AbsoluteOffset = new Point(0, GUI.IntScale(25f)) }, isHorizontal: true)
@@ -242,6 +248,17 @@ namespace Barotrauma
{ {
TextGetter = () => TextManager.GetWithVariable("campaignmoney", "[money]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", campaignMode.Money)) TextGetter = () => TextManager.GetWithVariable("campaignmoney", "[money]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", campaignMode.Money))
}; };
GUIFrame bottomDisclaimerFrame = new GUIFrame(new RectTransform(new Vector2(contentFrameSize.X, 0.1f), infoFrame.RectTransform)
{
AbsoluteOffset = new Point(contentFrame.Rect.X, contentFrame.Rect.Bottom + GUI.IntScale(8))
}, style: null);
pendingChangesFrame = new GUIFrame(new RectTransform(Vector2.One, bottomDisclaimerFrame.RectTransform, Anchor.Center), style: null);
if (GameMain.NetLobbyScreen?.CampaignCharacterDiscarded ?? false)
{
NetLobbyScreen.CreateChangesPendingFrame(pendingChangesFrame);
}
} }
else else
{ {
@@ -254,10 +271,17 @@ namespace Barotrauma
var submarineButton = createTabButton(InfoFrameTab.Submarine, "submarine"); var submarineButton = createTabButton(InfoFrameTab.Submarine, "submarine");
if (GameMain.NetworkMember != null) var talentsButton = createTabButton(InfoFrameTab.Talents, "tabmenu.character");
talentsButton.OnAddedToGUIUpdateList += (component) =>
{ {
var myCharacterButton = createTabButton(InfoFrameTab.MyCharacter, "tabmenu.character"); talentsButton.Enabled = Character.Controlled?.Info != null;
} if (!talentsButton.Enabled && selectedTab == InfoFrameTab.Talents)
{
SelectInfoFrameTab(null, InfoFrameTab.Crew);
}
};
talentPointNotification = GameSession.CreateTalentIconNotification(talentsButton);
} }
private bool SelectInfoFrameTab(GUIButton button, object userData) private bool SelectInfoFrameTab(GUIButton button, object userData)
@@ -289,13 +313,12 @@ namespace Barotrauma
if (traitor == null || traitorMission == null) return false; if (traitor == null || traitorMission == null) return false;
CreateTraitorInfo(infoFrameHolder, traitorMission, traitor); CreateTraitorInfo(infoFrameHolder, traitorMission, traitor);
break; break;
case InfoFrameTab.MyCharacter:
if (GameMain.NetworkMember == null) { return false; }
GameMain.NetLobbyScreen.CreatePlayerFrame(infoFrameHolder);
break;
case InfoFrameTab.Submarine: case InfoFrameTab.Submarine:
CreateSubmarineInfo(infoFrameHolder, Submarine.MainSub); CreateSubmarineInfo(infoFrameHolder, Submarine.MainSub);
break; break;
case InfoFrameTab.Talents:
CreateTalentInfo(infoFrameHolder);
break;
} }
return true; return true;
@@ -1159,5 +1182,506 @@ namespace Barotrauma
sub.Info.CreateSpecsWindow(specsListBox, GUI.Font, includeTitle: false, includeClass: false, includeDescription: true); sub.Info.CreateSpecsWindow(specsListBox, GUI.Font, includeTitle: false, includeClass: false, includeDescription: true);
} }
} }
private Color unselectedColor = new Color(240, 255, 255, 225);
private Color selectedColor = new Color(220, 255, 220, 225);
private Color ownedColor = new Color(140, 180, 140, 225);
private Color unselectableColor = new Color(100, 100, 100, 225);
private Color pressedColor = new Color(60, 60, 60, 225);
private readonly List<(GUIButton button, GUIComponent icon)> talentButtons = new List<(GUIButton button, GUIComponent icon)>();
private readonly List<(string talentTree, int index, GUIImage icon, GUIFrame background, GUIFrame backgroundGlow)> talentCornerIcons = new List<(string talentTree, int index, GUIImage icon, GUIFrame background, GUIFrame backgroundGlow)>();
private List<string> selectedTalents = new List<string>();
private GUITextBlock experienceText;
private GUIProgressBar experienceBar;
private GUITextBlock talentPointText;
private GUIListBox skillListBox;
private GUIButton talentApplyButton,
talentResetButton;
private GUIImage talentPointNotification;
private readonly ImmutableDictionary<TalentTree.TalentTreeStageState, GUIComponentStyle> talentStageStyles = new Dictionary<TalentTree.TalentTreeStageState, GUIComponentStyle>
{
{ TalentTree.TalentTreeStageState.Invalid, GUI.Style.GetComponentStyle("TalentTreeLocked") },
{ TalentTree.TalentTreeStageState.Locked, GUI.Style.GetComponentStyle("TalentTreeLocked") },
{ TalentTree.TalentTreeStageState.Unlocked, GUI.Style.GetComponentStyle("TalentTreePurchased") },
{ TalentTree.TalentTreeStageState.Available, GUI.Style.GetComponentStyle("TalentTreeUnlocked") },
{ TalentTree.TalentTreeStageState.Highlighted, GUI.Style.GetComponentStyle("TalentTreeAvailable") },
}.ToImmutableDictionary();
private readonly ImmutableDictionary<TalentTree.TalentTreeStageState, Color> talentStageBackgroundColors = new Dictionary<TalentTree.TalentTreeStageState, Color>
{
{ TalentTree.TalentTreeStageState.Invalid, new Color(48,48,48,255) },
{ TalentTree.TalentTreeStageState.Locked, new Color(48,48,48,255) },
{ TalentTree.TalentTreeStageState.Unlocked, new Color(24,37,31,255) },
{ TalentTree.TalentTreeStageState.Available, new Color(50,47,33,255) },
{ TalentTree.TalentTreeStageState.Highlighted, new Color(50,47,33,255) },
}.ToImmutableDictionary();
private void CreateTalentInfo(GUIFrame infoFrame)
{
infoFrame.ClearChildren();
talentButtons.Clear();
talentCornerIcons.Clear();
Character controlledCharacter = Character.Controlled;
if (controlledCharacter == null) { return; }
GUIFrame talentFrameBackground = new GUIFrame(new RectTransform(Vector2.One, infoFrame.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox");
int padding = GUI.IntScale(15);
GUIFrame talentFrameContent = new GUIFrame(new RectTransform(new Point(talentFrameBackground.Rect.Width - padding, talentFrameBackground.Rect.Height - padding), infoFrame.RectTransform, Anchor.Center), style: null);
GUIFrame paddedTalentFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.9f), talentFrameContent.RectTransform, Anchor.Center), style: null);
GUIFrame talentFrameMain = new GUIFrame(new RectTransform(Vector2.One, paddedTalentFrame.RectTransform), style: null);
GUIFrame characterSettingsFrame = null;
GUILayoutGroup characterLayout = null;
if (!(GameMain.NetworkMember is null))
{
characterSettingsFrame = new GUIFrame(new RectTransform(Vector2.One, talentFrameContent.RectTransform), style: null) { Visible = false };
characterLayout = new GUILayoutGroup(new RectTransform(Vector2.One, characterSettingsFrame.RectTransform));
GUIFrame containerFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.9f), characterLayout.RectTransform), style: null);
GUIFrame playerFrame = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.7f), containerFrame.RectTransform, Anchor.Center), style: null);
GameMain.NetLobbyScreen.CreatePlayerFrame(playerFrame, alwaysAllowEditing: true, createPendingText: false);
}
if (controlledCharacter.Info is null)
{
DebugConsole.ThrowError("No character info found for talent UI");
return;
}
selectedTalents = controlledCharacter.Info.GetUnlockedTalentsInTree().ToList();
GUILayoutGroup talentFrameLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), talentFrameMain.RectTransform, anchor: Anchor.Center), childAnchor: Anchor.TopCenter)
{
AbsoluteSpacing = GUI.IntScale(5)
};
GUILayoutGroup talentInfoLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), talentFrameLayoutGroup.RectTransform, Anchor.Center), isHorizontal: true);
CharacterInfo info = controlledCharacter.Info;
Job job = info.Job;
new GUICustomComponent(new RectTransform(new Vector2(0.25f, 1f), talentInfoLayoutGroup.RectTransform), onDraw: (batch, component) =>
{
float posY = component.Rect.Center.Y - component.Rect.Width / 2;
info.DrawPortrait(batch, new Vector2(component.Rect.X, posY), Vector2.Zero, component.Rect.Width, false, false);
});
GUILayoutGroup nameLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.3f, 1f), talentInfoLayoutGroup.RectTransform)) { RelativeSpacing = 0.05f };
Vector2 nameSize = GUI.SubHeadingFont.MeasureString(info.Name);
GUITextBlock nameBlock = new GUITextBlock(new RectTransform(Vector2.One, nameLayout.RectTransform), info.Name, font: GUI.SubHeadingFont) { TextColor = job.Prefab.UIColor };
nameBlock.RectTransform.NonScaledSize = nameSize.Pad(nameBlock.Padding).ToPoint();
Vector2 jobSize = GUI.SmallFont.MeasureString(job.Name);
GUITextBlock jobBlock = new GUITextBlock(new RectTransform(Vector2.One, nameLayout.RectTransform), job.Name, font: GUI.SmallFont) { TextColor = job.Prefab.UIColor };
jobBlock.RectTransform.NonScaledSize = jobSize.Pad(jobBlock.Padding).ToPoint();
string traitString = TextManager.AddPunctuation(':', TextManager.Get("PersonalityTrait"), TextManager.Get("personalitytrait." + info.PersonalityTrait.Name.Replace(" ", "")));
Vector2 traitSize = GUI.SmallFont.MeasureString(traitString);
GUITextBlock traitBlock = new GUITextBlock(new RectTransform(Vector2.One, nameLayout.RectTransform), traitString, font: GUI.SmallFont);
traitBlock.RectTransform.NonScaledSize = traitSize.Pad(traitBlock.Padding).ToPoint();
GUIFrame endocrineFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.35f), nameLayout.RectTransform, Anchor.BottomCenter), style: null);
if (!(GameMain.NetworkMember is null))
{
GUIButton newCharacterBox = new GUIButton(new RectTransform(new Vector2(0.675f, 1f), endocrineFrame.RectTransform, Anchor.TopLeft), text: GameMain.NetLobbyScreen.CampaignCharacterDiscarded ? TextManager.Get("settings") : TextManager.Get("createnew"))
{
IgnoreLayoutGroups = true
};
newCharacterBox.OnClicked = (button, o) =>
{
if (!GameMain.NetLobbyScreen.CampaignCharacterDiscarded)
{
GameMain.NetLobbyScreen.TryDiscardCampaignCharacter(() =>
{
newCharacterBox.Text = TextManager.Get("settings");
if (pendingChangesFrame != null)
{
NetLobbyScreen.CreateChangesPendingFrame(pendingChangesFrame);
}
OpenMenu();
});
return true;
}
OpenMenu();
return true;
void OpenMenu()
{
characterSettingsFrame!.Visible = true;
talentFrameMain.Visible = false;
}
};
if (!(characterLayout is null))
{
GUILayoutGroup characterCloseButtonLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.1f), characterLayout.RectTransform), childAnchor: Anchor.BottomRight);
new GUIButton(new RectTransform(new Vector2(0.4f, 1f), characterCloseButtonLayout.RectTransform), TextManager.Get("ApplySettingsButton")) //TODO: Is this text appropriate for this circumstance for all languages?
{
OnClicked = (button, o) =>
{
characterSettingsFrame!.Visible = false;
talentFrameMain.Visible = true;
return true;
}
};
}
}
IEnumerable<TalentPrefab> endocrineTalents = info.GetEndocrineTalents().Select(e => TalentPrefab.TalentPrefabs.Find(c => c.Identifier.Equals(e, StringComparison.OrdinalIgnoreCase)));
if (endocrineTalents.Count() > 0)
{
GUIImage endocrineIcon = new GUIImage(new RectTransform(new Vector2(0.275f, 1f), endocrineFrame.RectTransform, anchor: Anchor.TopRight, scaleBasis: ScaleBasis.Normal), style: "EndocrineReminderIcon")
{
ToolTip = $"{TextManager.Get("afflictionname.endocrineboost")}\n\n{string.Join(", ", endocrineTalents.Select(e => e.DisplayName))}"
};
}
GUILayoutGroup skillLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 1f), talentInfoLayoutGroup.RectTransform)) { Stretch = true };
string skillString = TextManager.Get("skills");
Vector2 skillSize = GUI.SubHeadingFont.MeasureString(skillString);
GUITextBlock skillBlock = new GUITextBlock(new RectTransform(Vector2.One, skillLayout.RectTransform), skillString, font: GUI.SubHeadingFont);
skillBlock.RectTransform.NonScaledSize = skillSize.Pad(skillBlock.Padding).ToPoint();
skillListBox = new GUIListBox(new RectTransform(new Vector2(1f, 1f - skillBlock.RectTransform.RelativeSize.Y), skillLayout.RectTransform), style: null);
CreateTalentSkillList(controlledCharacter, skillListBox);
if (!TalentTree.JobTalentTrees.TryGetValue(controlledCharacter.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { return; }
new GUIFrame(new RectTransform(new Vector2(1f, 1f), talentFrameLayoutGroup.RectTransform), style: "HorizontalLine");
GUIListBox talentTreeListBox = new GUIListBox(new RectTransform(new Vector2(1f, 0.7f), talentFrameLayoutGroup.RectTransform, Anchor.TopCenter), isHorizontal: true, style: null);
List<GUITextBlock> subTreeNames = new List<GUITextBlock>();
foreach (var subTree in talentTree.TalentSubTrees)
{
GUIFrame subTreeFrame = new GUIFrame(new RectTransform(new Vector2(0.333f, 1f), talentTreeListBox.Content.RectTransform, anchor: Anchor.TopLeft), style: null);
GUILayoutGroup subTreeLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 1f), subTreeFrame.RectTransform, Anchor.Center), false, childAnchor: Anchor.TopCenter);
GUIFrame subtreeTitleFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.111f), subTreeLayoutGroup.RectTransform, anchor: Anchor.TopCenter), style: null);
int elementPadding = GUI.IntScale(8);
Point headerSize = subtreeTitleFrame.RectTransform.NonScaledSize;
GUIFrame subTreeTitleBackground = new GUIFrame(new RectTransform(new Point(headerSize.X - elementPadding, headerSize.Y), subtreeTitleFrame.RectTransform, anchor: Anchor.Center), style: "SubtreeHeader");
subTreeNames.Add(new GUITextBlock(new RectTransform(Vector2.One, subTreeTitleBackground.RectTransform, anchor: Anchor.TopCenter), subTree.DisplayName, font: GUI.SubHeadingFont, textAlignment: Alignment.Center));
for (int i = 0; i < 4; i++)
{
GUIFrame talentOptionFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.222f), subTreeLayoutGroup.RectTransform, anchor: Anchor.TopCenter), style: null);
Point talentFrameSize = talentOptionFrame.RectTransform.NonScaledSize;
GUIFrame talentBackground = new GUIFrame(new RectTransform(new Point(talentFrameSize.X - elementPadding, talentFrameSize.Y - elementPadding), talentOptionFrame.RectTransform, anchor: Anchor.Center), style: "TalentBackground");
GUIFrame talentBackgroundHighlight = new GUIFrame(new RectTransform(Vector2.One, talentBackground.RectTransform, anchor: Anchor.Center), style: "TalentBackgroundGlow") { Visible = false };
GUIImage cornerIcon = new GUIImage(new RectTransform(new Vector2(0.2f), talentOptionFrame.RectTransform, anchor: Anchor.BottomRight, scaleBasis: ScaleBasis.BothHeight) { MaxSize = new Point(16) }, style: null)
{
CanBeFocused = false
};
Point iconSize = cornerIcon.RectTransform.NonScaledSize;
cornerIcon.RectTransform.AbsoluteOffset = new Point(iconSize.X / 2, iconSize.Y / 2);
if (subTree.TalentOptionStages.Count > i)
{
TalentOption talentOption = subTree.TalentOptionStages[i];
GUILayoutGroup talentOptionCenterGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.75f, 0.7f), talentOptionFrame.RectTransform, Anchor.Center), childAnchor: Anchor.CenterLeft);
GUILayoutGroup talentOptionLayoutGroup = new GUILayoutGroup(new RectTransform(Vector2.One, talentOptionCenterGroup.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { Stretch = true };
foreach (TalentPrefab talent in talentOption.Talents)
{
GUIFrame talentFrame = new GUIFrame(new RectTransform(Vector2.One, talentOptionLayoutGroup.RectTransform), style: null)
{
CanBeFocused = false
};
GUIFrame croppedTalentFrame = new GUIFrame(new RectTransform(Vector2.One, talentFrame.RectTransform, anchor: Anchor.Center, scaleBasis: ScaleBasis.BothHeight), style: null);
GUIButton talentButton = new GUIButton(new RectTransform(Vector2.One, croppedTalentFrame.RectTransform, anchor: Anchor.Center), style: null)
{
ToolTip = $"{talent.DisplayName}\n\n{talent.Description}",
UserData = talent.Identifier,
PressedColor = pressedColor,
OnClicked = (button, userData) =>
{
// deselect other buttons in tier by removing their selected talents from pool
foreach (GUIButton guiButton in talentOptionLayoutGroup.GetAllChildren<GUIButton>())
{
if (guiButton.UserData is string otherTalentIdentifier && guiButton != button)
{
if (!controlledCharacter.HasTalent(otherTalentIdentifier))
{
selectedTalents.Remove(otherTalentIdentifier);
}
}
}
string talentIdentifier = userData as string;
if (TalentTree.IsViableTalentForCharacter(controlledCharacter, talentIdentifier, selectedTalents))
{
if (!selectedTalents.Contains(talentIdentifier))
{
selectedTalents.Add(talentIdentifier);
}
}
else if (!controlledCharacter.HasTalent(talentIdentifier))
{
selectedTalents.Remove(talentIdentifier);
}
UpdateTalentButtons();
return true;
},
};
talentButton.Color = talentButton.HoverColor = talentButton.PressedColor = talentButton.SelectedColor = Color.Transparent;
GUIComponent iconImage;
if (talent.Icon is null)
{
iconImage = new GUITextBlock(new RectTransform(Vector2.One, talentButton.RectTransform, anchor: Anchor.Center), text: "???", font: GUI.LargeFont, textAlignment: Alignment.Center, style: null)
{
OutlineColor = GUI.Style.Red,
TextColor = GUI.Style.Red,
PressedColor = unselectableColor,
CanBeFocused = false,
};
}
else
{
iconImage = new GUIImage(new RectTransform(Vector2.One, talentButton.RectTransform, anchor: Anchor.Center), sprite: talent.Icon, scaleToFit: true)
{
PressedColor = unselectableColor,
CanBeFocused = false,
};
}
talentButtons.Add((talentButton, iconImage));
}
talentCornerIcons.Add((subTree.Identifier, i, cornerIcon, talentBackground, talentBackgroundHighlight));
}
}
}
GUITextBlock.AutoScaleAndNormalize(subTreeNames);
GUILayoutGroup talentBottomFrame = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.07f), talentFrameLayoutGroup.RectTransform, Anchor.TopCenter), isHorizontal: true) { RelativeSpacing = 0.01f };
GUILayoutGroup experienceLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.59f, 1f), talentBottomFrame.RectTransform));
GUIFrame experienceBarFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.5f), experienceLayout.RectTransform), style: null);
experienceBar = new GUIProgressBar(new RectTransform(new Vector2(1f, 1f), experienceBarFrame.RectTransform, Anchor.CenterLeft),
barSize: controlledCharacter.Info.GetProgressTowardsNextLevel(), color: GUI.Style.Green)
{
IsHorizontal = true
};
experienceText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), experienceBarFrame.RectTransform, anchor: Anchor.Center), "", font: GUI.Font, textAlignment: Alignment.CenterRight)
{
Shadow = true
};
talentPointText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), experienceLayout.RectTransform, anchor: Anchor.Center), "", font: GUI.SubHeadingFont, parseRichText: true, textAlignment: Alignment.CenterRight) { AutoScaleVertical = true };
talentResetButton = new GUIButton(new RectTransform(new Vector2(0.19f, 1f), talentBottomFrame.RectTransform), text: TextManager.Get("reset"), style: "GUIButtonFreeScale")
{
OnClicked = ResetTalentSelection
};
talentApplyButton = new GUIButton(new RectTransform(new Vector2(0.19f, 1f), talentBottomFrame.RectTransform), text: TextManager.Get("applysettingsbutton"), style: "GUIButtonFreeScale")
{
OnClicked = ApplyTalentSelection,
};
GUITextBlock.AutoScaleAndNormalize(talentResetButton.TextBlock, talentApplyButton.TextBlock);
UpdateTalentButtons();
}
private void CreateTalentSkillList(Character character, GUIListBox parent)
{
parent.Content.ClearChildren();
List<GUITextBlock> skillNames = new List<GUITextBlock>();
foreach (Skill skill in character.Info.Job.Skills)
{
GUILayoutGroup skillContainer = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.2f), parent.Content.RectTransform), isHorizontal: true) { CanBeFocused = false };
skillNames.Add(new GUITextBlock(new RectTransform(new Vector2(0.7f, 1f), skillContainer.RectTransform), TextManager.Get($"skillname.{skill.Identifier}", returnNull: true) ?? skill.Identifier));
new GUITextBlock(new RectTransform(new Vector2(0.15f, 1.0f), skillContainer.RectTransform), Math.Floor(skill.Level).ToString("F0"), textAlignment: Alignment.CenterRight) { Padding = new Vector4(0, 0, 4, 0) };
float modifiedSkillLevel = character.GetSkillLevel(skill.Identifier);
if (!MathUtils.NearlyEqual(MathF.Floor(modifiedSkillLevel), MathF.Floor(skill.Level)))
{
int skillChange = (int)MathF.Floor(modifiedSkillLevel - skill.Level);
string stringColor = true switch
{
true when skillChange > 0 => XMLExtensions.ColorToString(GUI.Style.Green),
true when skillChange < 0 => XMLExtensions.ColorToString(GUI.Style.Red),
_ => XMLExtensions.ColorToString(GUI.Style.TextColor)
};
string changeText = $"(‖color:{stringColor}‖{(skillChange > 0 ? "+" : string.Empty) + skillChange}‖color:end‖)";
new GUITextBlock(new RectTransform(new Vector2(0.15f, 1.0f), skillContainer.RectTransform), changeText, parseRichText: true) { Padding = Vector4.Zero };
}
skillContainer.Recalculate();
}
parent.RecalculateChildren();
GUITextBlock.AutoScaleAndNormalize(skillNames);
}
private void UpdateTalentButtons()
{
Character controlledCharacter = Character.Controlled;
experienceText.Text = $"{controlledCharacter.Info.ExperiencePoints - controlledCharacter.Info.GetExperienceRequiredForCurrentLevel()} / {controlledCharacter.Info.GetExperienceRequiredToLevelUp() - controlledCharacter.Info.GetExperienceRequiredForCurrentLevel()}";
experienceBar.BarSize = controlledCharacter.Info.GetProgressTowardsNextLevel();
//experienceBar.ToolTip = $"{controlledCharacter.Info.ExperiencePoints - controlledCharacter.Info.GetExperienceRequiredForCurrentLevel()} / {controlledCharacter.Info.GetExperienceRequiredToLevelUp() - controlledCharacter.Info.GetExperienceRequiredForCurrentLevel()}";
selectedTalents = TalentTree.CheckTalentSelection(controlledCharacter, selectedTalents);
string pointsLeft = controlledCharacter.Info.GetAvailableTalentPoints().ToString();
int talentCount = selectedTalents.Count - controlledCharacter.Info.GetUnlockedTalentsInTree().Count();
if (talentCount > 0)
{
string pointsUsed = $"‖color:{XMLExtensions.ColorToString(GUI.Style.Red)}‖{-talentCount}‖color:end‖";
string localizedString = TextManager.GetWithVariables("talentmenu.points.spending", new []{ "[amount]", "[used]" }, new []{ pointsLeft, pointsUsed});
talentPointText.SetRichText(localizedString);
}
else
{
talentPointText.SetRichText(TextManager.GetWithVariable("talentmenu.points", "[amount]", pointsLeft));
}
foreach (var (talentTree, index, icon, frame, glow) in talentCornerIcons)
{
TalentTree.TalentTreeStageState state = TalentTree.GetTalentOptionStageState(controlledCharacter, talentTree, index, selectedTalents);
GUIComponentStyle newStyle = talentStageStyles[state];
icon.ApplyStyle(newStyle);
icon.Color = newStyle.Color;
frame.Color = talentStageBackgroundColors[state];
glow.Visible = state == TalentTree.TalentTreeStageState.Highlighted;
}
foreach (var talentButton in talentButtons)
{
string talentIdentifier = talentButton.button.UserData as string;
bool unselectable = !TalentTree.IsViableTalentForCharacter(controlledCharacter, talentIdentifier, selectedTalents) || controlledCharacter.HasTalent(talentIdentifier);
Color newTalentColor = unselectable ? unselectableColor : unselectedColor;
Color hoverColor = Color.White;
if (controlledCharacter.HasTalent(talentIdentifier))
{
newTalentColor = GUI.Style.Green;
}
else if (selectedTalents.Contains(talentIdentifier))
{
newTalentColor = GUI.Style.Orange;
hoverColor = Color.Lerp(GUI.Style.Orange, Color.White, 0.7f);
}
talentButton.icon.Color = newTalentColor;
talentButton.icon.HoverColor = hoverColor;
}
CreateTalentSkillList(controlledCharacter, skillListBox);
}
private void ApplyTalents(Character controlledCharacter)
{
selectedTalents = TalentTree.CheckTalentSelection(controlledCharacter, selectedTalents);
foreach (string talent in selectedTalents)
{
controlledCharacter.GiveTalent(talent);
if (GameMain.Client != null)
{
GameMain.Client.CreateEntityEvent(controlledCharacter, new object[] { NetEntityEvent.Type.UpdateTalents });
}
}
UpdateTalentButtons();
}
private bool ApplyTalentSelection(GUIButton guiButton, object userData)
{
Character controlledCharacter = Character.Controlled;
ApplyTalents(controlledCharacter);
return true;
}
private bool ResetTalentSelection(GUIButton guiButton, object userData)
{
Character controlledCharacter = Character.Controlled;
selectedTalents = controlledCharacter.Info.GetUnlockedTalentsInTree().ToList();
UpdateTalentButtons();
return true;
}
public void OnExperienceChanged(Character character)
{
if (character != Character.Controlled) { return; }
UpdateTalentButtons();
}
private readonly StatTypes[] basicStats = new StatTypes[]
{
StatTypes.MaximumHealthMultiplier,
StatTypes.MovementSpeed,
StatTypes.SwimmingSpeed,
StatTypes.RepairSpeed,
};
private readonly StatTypes[] combatStats = new StatTypes[]
{
StatTypes.MeleeAttackMultiplier,
StatTypes.MeleeAttackSpeed,
StatTypes.RangedAttackSpeed,
StatTypes.TurretAttackSpeed,
};
private readonly StatTypes[] miscStats = new StatTypes[]
{
StatTypes.ReputationGainMultiplier,
StatTypes.MissionMoneyGainMultiplier,
StatTypes.ExperienceGainMultiplier,
StatTypes.MissionExperienceGainMultiplier,
};
private void CreateCharacterSheet(GUILayoutGroup characterInfoColumn)
{
Character controlledCharacter = Character.Controlled;
CreateRow(basicStats);
CreateRow(combatStats);
CreateRow(miscStats);
void CreateRow(StatTypes[] statTypes)
{
GUILayoutGroup characterInfoRow = new GUILayoutGroup(new RectTransform(new Vector2(0.33f, 1.0f), characterInfoColumn.RectTransform, anchor: Anchor.TopLeft), childAnchor: Anchor.TopCenter);
foreach (StatTypes statType in statTypes)
{
ShowStat(statType, characterInfoRow);
}
}
void ShowStat(StatTypes statType, GUILayoutGroup characterInfoRow)
{
GUIFrame textInfoFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.33f), characterInfoRow.RectTransform, Anchor.TopCenter), style: null);
new GUITextBlock(new RectTransform(new Vector2(1f, 1f), textInfoFrame.RectTransform, Anchor.TopLeft), statType.ToString(), font: GUI.SmallFont, textAlignment: Alignment.TopLeft);
new GUITextBlock(new RectTransform(new Vector2(1f, 1f), textInfoFrame.RectTransform, Anchor.TopLeft), (int)(100f * (1 + controlledCharacter.GetStatValue(statType))) + "%", font: GUI.Font, textAlignment: Alignment.TopRight);
}
}
} }
} }
@@ -115,8 +115,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, Rectangle rect, Color color, SpriteEffects spriteEffects = SpriteEffects.None) public void Draw(SpriteBatch spriteBatch, Rectangle rect, Color color, SpriteEffects spriteEffects = SpriteEffects.None, Vector2? uvOffset = null)
{ {
uvOffset ??= Vector2.Zero;
if (Sprite.Texture == null) if (Sprite.Texture == null)
{ {
GUI.DrawRectangle(spriteBatch, rect, Color.Magenta); GUI.DrawRectangle(spriteBatch, rect, Color.Magenta);
@@ -157,7 +158,7 @@ namespace Barotrauma
else if (Tile) else if (Tile)
{ {
Vector2 startPos = new Vector2(rect.X, rect.Y); Vector2 startPos = new Vector2(rect.X, rect.Y);
Sprite.DrawTiled(spriteBatch, startPos, new Vector2(rect.Width, rect.Height), color); Sprite.DrawTiled(spriteBatch, startPos, new Vector2(rect.Width, rect.Height), color, startOffset: uvOffset);
} }
else else
{ {
@@ -1045,10 +1045,10 @@ namespace Barotrauma
public static GUIFrame CreateUpgradeFrame(UpgradePrefab prefab, UpgradeCategory category, CampaignMode campaign, RectTransform rectTransform, bool addBuyButton = true) public static GUIFrame CreateUpgradeFrame(UpgradePrefab prefab, UpgradeCategory category, CampaignMode campaign, RectTransform rectTransform, bool addBuyButton = true)
{ {
int price = prefab.Price.GetBuyprice(campaign.UpgradeManager.GetUpgradeLevel(prefab, category), campaign.Map?.CurrentLocation); int price = prefab.Price.GetBuyprice(campaign.UpgradeManager.GetUpgradeLevel(prefab, category), campaign.Map?.CurrentLocation);
return CreateUpgradeEntry(rectTransform, prefab.Sprite, prefab.Name, prefab.Description, price, new CategoryData(category, prefab), addBuyButton); return CreateUpgradeEntry(rectTransform, prefab.Sprite, prefab.Name, prefab.Description, price, new CategoryData(category, prefab), addBuyButton, upgradePrefab: prefab, currentLevel: campaign.UpgradeManager.GetUpgradeLevel(prefab, category));
} }
public static GUIFrame CreateUpgradeEntry(RectTransform parent, Sprite sprite, string title, string body, int price, object? userData, bool addBuyButton = true, bool addProgressBar = true, string buttonStyle = "UpgradeBuyButton") public static GUIFrame CreateUpgradeEntry(RectTransform parent, Sprite sprite, string title, string body, int price, object? userData, bool addBuyButton = true, bool addProgressBar = true, string buttonStyle = "UpgradeBuyButton", UpgradePrefab upgradePrefab = null, int currentLevel = 0)
{ {
float progressBarHeight = 0.25f; float progressBarHeight = 0.25f;
@@ -1089,7 +1089,7 @@ namespace Barotrauma
//negative price = refund //negative price = refund
if (price < 0) { formattedPrice = "+" + formattedPrice; } if (price < 0) { formattedPrice = "+" + formattedPrice; }
buyButtonLayout = new GUILayoutGroup(rectT(0.2f, 1, prefabLayout), childAnchor: Anchor.TopCenter) { UserData = "buybutton" }; buyButtonLayout = new GUILayoutGroup(rectT(0.2f, 1, prefabLayout), childAnchor: Anchor.TopCenter) { UserData = "buybutton" };
var priceText = new GUITextBlock(rectT(1, 0.4f, buyButtonLayout), formattedPrice, textAlignment: Alignment.Center); var priceText = new GUITextBlock(rectT(1, 0.2f, buyButtonLayout), formattedPrice, textAlignment: Alignment.Center);
if (price < 0) if (price < 0)
{ {
priceText.TextColor = GUI.Style.Green; priceText.TextColor = GUI.Style.Green;
@@ -1099,6 +1099,11 @@ namespace Barotrauma
priceText.Text = string.Empty; priceText.Text = string.Empty;
} }
new GUIButton(rectT(0.7f, 0.5f, buyButtonLayout), string.Empty, style: buttonStyle) { Enabled = false }; new GUIButton(rectT(0.7f, 0.5f, buyButtonLayout), string.Empty, style: buttonStyle) { Enabled = false };
if (upgradePrefab != null)
{
var increaseText = new GUITextBlock(rectT(1, 0.2f, buyButtonLayout), "", textAlignment: Alignment.Center);
UpdateUpgradePercentageText(increaseText, upgradePrefab, currentLevel);
}
} }
description.CalculateHeightFromText(); description.CalculateHeightFromText();
@@ -1127,6 +1132,19 @@ namespace Barotrauma
return prefabFrame; return prefabFrame;
} }
private static void UpdateUpgradePercentageText(GUITextBlock text, UpgradePrefab upgradePrefab, int currentLevel)
{
float nextIncrease = upgradePrefab.IncreaseOnTooltip * (Math.Min(currentLevel + 1, upgradePrefab.MaxLevel));
if (nextIncrease != 0f)
{
text.Text = $"{Math.Round(nextIncrease, 1)} %";
if (currentLevel == upgradePrefab.MaxLevel)
{
text.TextColor = Color.Gray;
}
}
}
private void CreateUpgradeEntry(UpgradePrefab prefab, UpgradeCategory category, GUIComponent parent, List<Item>? itemsOnSubmarine) private void CreateUpgradeEntry(UpgradePrefab prefab, UpgradeCategory category, GUIComponent parent, List<Item>? itemsOnSubmarine)
{ {
if (Campaign is null) { return; } if (Campaign is null) { return; }
@@ -1541,7 +1559,9 @@ namespace Barotrauma
if (prefabFrame.FindChild("buybutton", true) is { } buttonParent) if (prefabFrame.FindChild("buybutton", true) is { } buttonParent)
{ {
GUITextBlock priceLabel = buttonParent.GetChild<GUITextBlock>(); List<GUITextBlock> textBlocks = buttonParent.GetAllChildren<GUITextBlock>().ToList();
GUITextBlock priceLabel = textBlocks[0];
int price = prefab.Price.GetBuyprice(campaign.UpgradeManager.GetUpgradeLevel(prefab, category), campaign.Map?.CurrentLocation); int price = prefab.Price.GetBuyprice(campaign.UpgradeManager.GetUpgradeLevel(prefab, category), campaign.Map?.CurrentLocation);
if (priceLabel != null && !WaitForServerUpdate) if (priceLabel != null && !WaitForServerUpdate)
@@ -1562,6 +1582,11 @@ namespace Barotrauma
button.Enabled = false; button.Enabled = false;
} }
} }
GUITextBlock increaseLabel = textBlocks[1];
if (increaseLabel != null && !WaitForServerUpdate)
{
UpdateUpgradePercentageText(increaseLabel, prefab, currentLevel);
}
} }
} }
@@ -43,6 +43,7 @@ namespace Barotrauma
public static SteamWorkshopScreen SteamWorkshopScreen; public static SteamWorkshopScreen SteamWorkshopScreen;
public static SubEditorScreen SubEditorScreen; public static SubEditorScreen SubEditorScreen;
public static TestScreen TestScreen;
public static ParticleEditorScreen ParticleEditorScreen; public static ParticleEditorScreen ParticleEditorScreen;
public static LevelEditorScreen LevelEditorScreen; public static LevelEditorScreen LevelEditorScreen;
public static SpriteEditorScreen SpriteEditorScreen; public static SpriteEditorScreen SpriteEditorScreen;
@@ -89,7 +90,16 @@ namespace Barotrauma
public static ParticleManager ParticleManager; public static ParticleManager ParticleManager;
public static DecalManager DecalManager; public static DecalManager DecalManager;
public static World World; private static World world;
public static World World
{
get
{
if (world == null) { world = new World(new Vector2(0, -9.82f)); }
return world;
}
set { world = value; }
}
public static LoadingScreen TitleScreen; public static LoadingScreen TitleScreen;
private bool loadingScreenOpen; private bool loadingScreenOpen;
@@ -184,6 +194,8 @@ namespace Barotrauma
#if DEBUG #if DEBUG
public static bool FirstLoad = true; public static bool FirstLoad = true;
public static bool CancelQuickStart;
#endif #endif
public GameMain(string[] args) public GameMain(string[] args)
@@ -239,7 +251,6 @@ namespace Barotrauma
GameMain.ResetFrameTime(); GameMain.ResetFrameTime();
fixedTime = new GameTime(); fixedTime = new GameTime();
World = new World(new Vector2(0, -9.82f));
FarseerPhysics.Settings.AllowSleep = true; FarseerPhysics.Settings.AllowSleep = true;
FarseerPhysics.Settings.ContinuousPhysics = false; FarseerPhysics.Settings.ContinuousPhysics = false;
FarseerPhysics.Settings.VelocityIterations = 1; FarseerPhysics.Settings.VelocityIterations = 1;
@@ -567,6 +578,8 @@ namespace Barotrauma
ItemPrefab.LoadAll(GetFilesOfType(ContentType.Item)); ItemPrefab.LoadAll(GetFilesOfType(ContentType.Item));
AfflictionPrefab.LoadAll(GetFilesOfType(ContentType.Afflictions)); AfflictionPrefab.LoadAll(GetFilesOfType(ContentType.Afflictions));
SkillSettings.Load(GetFilesOfType(ContentType.SkillSettings)); SkillSettings.Load(GetFilesOfType(ContentType.SkillSettings));
TalentPrefab.LoadAll(GetFilesOfType(ContentType.Talents));
TalentTree.LoadAll(GetFilesOfType(ContentType.TalentTrees));
Order.Init(); Order.Init();
EventManagerSettings.Init(); EventManagerSettings.Init();
BallastFloraPrefab.LoadAll(GetFilesOfType(ContentType.MapCreature)); BallastFloraPrefab.LoadAll(GetFilesOfType(ContentType.MapCreature));
@@ -620,6 +633,7 @@ namespace Barotrauma
#endif #endif
SubEditorScreen = new SubEditorScreen(); SubEditorScreen = new SubEditorScreen();
TestScreen = new TestScreen();
TitleScreen.LoadState = 75.0f; TitleScreen.LoadState = 75.0f;
yield return CoroutineStatus.Running; yield return CoroutineStatus.Running;
@@ -792,12 +806,18 @@ namespace Barotrauma
} }
#if DEBUG #if DEBUG
if (TitleScreen.LoadState >= 100.0f && !TitleScreen.PlayingSplashScreen && (Config.AutomaticQuickStartEnabled || Config.AutomaticCampaignLoadEnabled) && FirstLoad && !PlayerInput.KeyDown(Keys.LeftShift)) CancelQuickStart |= PlayerInput.KeyDown(Keys.LeftShift);
if (TitleScreen.LoadState >= 100.0f && !TitleScreen.PlayingSplashScreen && (Config.AutomaticQuickStartEnabled || Config.AutomaticCampaignLoadEnabled || Config.TestScreenEnabled) && FirstLoad && !CancelQuickStart)
{ {
loadingScreenOpen = false; loadingScreenOpen = false;
FirstLoad = false; FirstLoad = false;
if (Config.AutomaticQuickStartEnabled) if (Config.TestScreenEnabled)
{
TestScreen.Select();
}
else if (Config.AutomaticQuickStartEnabled)
{ {
MainMenuScreen.QuickStart(); MainMenuScreen.QuickStart();
} }
@@ -902,8 +922,7 @@ namespace Barotrauma
} }
//open the pause menu if not controlling a character OR if the character has no UIs active that can be closed with ESC //open the pause menu if not controlling a character OR if the character has no UIs active that can be closed with ESC
else if ((Character.Controlled == null || !itemHudActive()) else if ((Character.Controlled == null || !itemHudActive())
//TODO: do we need to check Inventory.SelectedSlot? && CharacterHealth.OpenHealthWindow == null
&& Inventory.SelectedSlot == null && CharacterHealth.OpenHealthWindow == null
&& !CrewManager.IsCommandInterfaceOpen && !CrewManager.IsCommandInterfaceOpen
&& !(Screen.Selected is SubEditorScreen editor && !editor.WiringMode && Character.Controlled?.SelectedConstruction != null)) && !(Screen.Selected is SubEditorScreen editor && !editor.WiringMode && Character.Controlled?.SelectedConstruction != null))
{ {
@@ -1048,7 +1067,6 @@ namespace Barotrauma
spriteBatch.End(); spriteBatch.End();
} }
sw.Stop(); sw.Stop();
PerformanceCounter.AddElapsedTicks("Draw total", sw.ElapsedTicks); PerformanceCounter.AddElapsedTicks("Draw total", sw.ElapsedTicks);
PerformanceCounter.DrawTimeGraph.Update(sw.ElapsedTicks * 1000.0f / (float)Stopwatch.Frequency); PerformanceCounter.DrawTimeGraph.Update(sw.ElapsedTicks * 1000.0f / (float)Stopwatch.Frequency);
@@ -48,11 +48,7 @@ namespace Barotrauma
var equipmentSlots = new List<InvSlotType>() { InvSlotType.Head, InvSlotType.InnerClothes, InvSlotType.OuterClothes, InvSlotType.Headset, InvSlotType.Card }; var equipmentSlots = new List<InvSlotType>() { InvSlotType.Head, InvSlotType.InnerClothes, InvSlotType.OuterClothes, InvSlotType.Headset, InvSlotType.Card };
return character.Inventory.FindAllItems(item => return character.Inventory.FindAllItems(item =>
{ {
if (item.SpawnedInOutpost) { return false; } if (!IsItemSellable(item, confirmedSoldEntities)) { return false; }
if (!item.Prefab.AllowSellingWhenBroken && item.ConditionPercentage < 90.0f) { return false; }
if (confirmedSoldEntities.Any(it => it.Item == item)) { return false; }
// There must be no contained items or the contained items must be confirmed as sold
if (!item.ContainedItems.All(it => confirmedSoldEntities.Any(se => se.Item == it))) { return false; }
// Item must be in a non-equipment slot if possible // Item must be in a non-equipment slot if possible
if (!item.AllowedSlots.All(s => equipmentSlots.Contains(s)) && IsInEquipmentSlot(item)) { return false; } if (!item.AllowedSlots.All(s => equipmentSlots.Contains(s)) && IsInEquipmentSlot(item)) { return false; }
// Item must not be contained inside an item in an equipment slot // Item must not be contained inside an item in an equipment slot
@@ -76,15 +72,10 @@ namespace Barotrauma
var confirmedSoldEntities = GetConfirmedSoldEntities(); var confirmedSoldEntities = GetConfirmedSoldEntities();
return Submarine.MainSub.GetItems(true).FindAll(item => return Submarine.MainSub.GetItems(true).FindAll(item =>
{ {
if (!item.Prefab.CanBeSold) { return false; } if (!IsItemSellable(item, confirmedSoldEntities)) { return false; }
if (item.SpawnedInOutpost) { return false; }
if (!item.Prefab.AllowSellingWhenBroken && item.ConditionPercentage < 90.0f) { return false; }
if (!item.Components.All(c => !(c is Holdable h) || !h.Attachable || !h.Attached)) { return false; } if (!item.Components.All(c => !(c is Holdable h) || !h.Attachable || !h.Attached)) { return false; }
if (!item.Components.All(c => !(c is Wire w) || w.Connections.All(c => c == null))) { return false; } if (!item.Components.All(c => !(c is Wire w) || w.Connections.All(c => c == null))) { return false; }
if (!ItemAndAllContainersInteractable(item)) { return false; } if (!ItemAndAllContainersInteractable(item)) { return false; }
if (confirmedSoldEntities.Any(it => it.Item == item)) { return false; }
// There must be no contained items or the contained items must be confirmed as sold
if (!item.ContainedItems.All(it => confirmedSoldEntities.Any(se => se.Item == it))) { return false; }
return true; return true;
}).Distinct(); }).Distinct();
@@ -107,6 +98,24 @@ namespace Barotrauma
return SoldEntities.Where(se => se.Status != SoldEntity.SellStatus.Unconfirmed); return SoldEntities.Where(se => se.Status != SoldEntity.SellStatus.Unconfirmed);
} }
private bool IsItemSellable(Item item, IEnumerable<SoldEntity> confirmedSoldEntities)
{
if (!item.Prefab.CanBeSold) { return false; }
if (item.SpawnedInOutpost) { return false; }
if (!item.Prefab.AllowSellingWhenBroken && item.ConditionPercentage < 90.0f) { return false; }
if (confirmedSoldEntities.Any(it => it.Item == item)) { return false; }
if (item.OwnInventory?.Container is ItemContainer itemContainer)
{
var containedItems = item.ContainedItems;
if (containedItems.None()) { return true; }
// Allow selling the item if contained items are unsellable and set to be removed on deconstruct
if (itemContainer.RemoveContainedItemsOnDeconstruct && containedItems.All(it => !it.Prefab.CanBeSold)) { return true; }
// Otherwise there must be no contained items or the contained items must be confirmed as sold
if (!containedItems.All(it => confirmedSoldEntities.Any(se => se.Item == it))) { return false; }
}
return true;
}
public void SetItemsInBuyCrate(List<PurchasedItem> items) public void SetItemsInBuyCrate(List<PurchasedItem> items)
{ {
ItemsInBuyCrate.Clear(); ItemsInBuyCrate.Clear();
File diff suppressed because it is too large Load Diff
@@ -60,7 +60,7 @@ namespace Barotrauma
var newCampaignContainer = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.95f), campaignContainer.RectTransform, Anchor.Center), style: null); var newCampaignContainer = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.95f), campaignContainer.RectTransform, Anchor.Center), style: null);
var loadCampaignContainer = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.95f), campaignContainer.RectTransform, Anchor.Center), style: null); var loadCampaignContainer = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.95f), campaignContainer.RectTransform, Anchor.Center), style: null);
GameMain.NetLobbyScreen.CampaignSetupUI = new CampaignSetupUI(true, newCampaignContainer, loadCampaignContainer, null, saveFiles); GameMain.NetLobbyScreen.CampaignSetupUI = new MultiPlayerCampaignSetupUI(newCampaignContainer, loadCampaignContainer, null, saveFiles);
var newCampaignButton = new GUIButton(new RectTransform(new Vector2(0.5f, 1.0f), buttonContainer.RectTransform), var newCampaignButton = new GUIButton(new RectTransform(new Vector2(0.5f, 1.0f), buttonContainer.RectTransform),
TextManager.Get("NewCampaign"), style: "GUITabButton") TextManager.Get("NewCampaign"), style: "GUITabButton")
@@ -761,7 +761,7 @@ namespace Barotrauma
Faction faction = campaign.Factions.FirstOrDefault(f => f.Prefab.Identifier.Equals(identifier, StringComparison.OrdinalIgnoreCase)); Faction faction = campaign.Factions.FirstOrDefault(f => f.Prefab.Identifier.Equals(identifier, StringComparison.OrdinalIgnoreCase));
if (faction?.Reputation != null) if (faction?.Reputation != null)
{ {
faction.Reputation.Value = rep; faction.Reputation.SetReputation(rep);
} }
else else
{ {
@@ -771,7 +771,7 @@ namespace Barotrauma
if (reputation.HasValue) if (reputation.HasValue)
{ {
campaign.Map.CurrentLocation.Reputation.Value = reputation.Value; campaign.Map.CurrentLocation.Reputation.SetReputation(reputation.Value);
campaign?.CampaignUI?.UpgradeStore?.RefreshAll(); campaign?.CampaignUI?.UpgradeStore?.RefreshAll();
} }
@@ -21,10 +21,11 @@ namespace Barotrauma
{ {
if (GameMain.NetworkMember != null && GameMain.NetLobbyScreen != null) if (GameMain.NetworkMember != null && GameMain.NetLobbyScreen != null)
{ {
if (GameMain.NetLobbyScreen.HeadSelectionList != null) { GameMain.NetLobbyScreen.HeadSelectionList.Visible = false; } GameMain.NetLobbyScreen.CharacterAppearanceCustomizationMenu?.Dispose();
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 == false) if (tabMenu == null && !(GameMode is TutorialMode) && !ConversationAction.IsDialogOpen)
{ {
tabMenu = new TabMenu(); tabMenu = new TabMenu();
HintManager.OnShowTabMenu(); HintManager.OnShowTabMenu();
@@ -34,17 +35,17 @@ namespace Barotrauma
tabMenu = null; tabMenu = null;
NetLobbyScreen.JobInfoFrame = null; NetLobbyScreen.JobInfoFrame = null;
} }
return true; return true;
} }
private GUILayoutGroup topLeftButtonGroup; private GUILayoutGroup topLeftButtonGroup;
private GUIButton crewListButton, commandButton, tabMenuButton; private GUIButton crewListButton, commandButton, tabMenuButton;
private GUIImage talentPointNotification;
private GUIComponent respawnInfoFrame, respawnButtonContainer; private GUIComponent respawnInfoFrame, respawnButtonContainer;
private GUITextBlock respawnInfoText; private GUITextBlock respawnInfoText;
private GUITickBox respawnTickBox; private GUITickBox respawnTickBox;
private GUILayoutGroup TopLeftButtonGroup;
private void CreateTopLeftButtons() private void CreateTopLeftButtons()
{ {
if (topLeftButtonGroup != null) if (topLeftButtonGroup != null)
@@ -89,11 +90,11 @@ namespace Barotrauma
tabMenuButton = new GUIButton(new RectTransform(buttonSize, parent: topLeftButtonGroup.RectTransform), style: "TabMenuButton") tabMenuButton = new GUIButton(new RectTransform(buttonSize, parent: topLeftButtonGroup.RectTransform), style: "TabMenuButton")
{ {
ToolTip = TextManager.GetWithVariable("hudbutton.tabmenu", "[key]", GameMain.Config.KeyBindText(InputType.InfoTab)), ToolTip = TextManager.GetWithVariable("hudbutton.tabmenu", "[key]", GameMain.Config.KeyBindText(InputType.InfoTab)),
OnClicked = (button, userData) => OnClicked = (button, userData) => ToggleTabMenu()
{
return ToggleTabMenu();
}
}; };
talentPointNotification = CreateTalentIconNotification(tabMenuButton);
GameMain.Instance.ResolutionChanged += CreateTopLeftButtons; GameMain.Instance.ResolutionChanged += CreateTopLeftButtons;
respawnInfoFrame = new GUIFrame(new RectTransform(new Vector2(0.5f, 1.0f), parent: topLeftButtonGroup.RectTransform) respawnInfoFrame = new GUIFrame(new RectTransform(new Vector2(0.5f, 1.0f), parent: topLeftButtonGroup.RectTransform)
@@ -140,11 +141,41 @@ namespace Barotrauma
if (GameMain.NetworkMember != null) if (GameMain.NetworkMember != null)
{ {
GameMain.NetLobbyScreen?.HeadSelectionList?.AddToGUIUpdateList(); GameMain.NetLobbyScreen.CharacterAppearanceCustomizationMenu?.AddToGUIUpdateList();
GameMain.NetLobbyScreen?.JobSelectionFrame?.AddToGUIUpdateList(); GameMain.NetLobbyScreen?.JobSelectionFrame?.AddToGUIUpdateList();
} }
} }
public static GUIImage CreateTalentIconNotification(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")
{
Visible = false,
CanBeFocused = false
};
Point notificationSize = indicator.RectTransform.NonScaledSize;
if (offset)
{
indicator.RectTransform.AbsoluteOffset = new Point(-(notificationSize.X / 2), -(notificationSize.Y / 2));
}
return indicator;
}
public static void UpdateTalentNotificationIndicator(GUIImage indicator)
{
if (indicator != null)
{
if (Character.Controlled?.Info == null)
{
indicator.Visible = false;
}
else
{
indicator.Visible = Character.Controlled.Info.GetAvailableTalentPoints() > 0;
}
}
}
partial void UpdateProjSpecific(float deltaTime) partial void UpdateProjSpecific(float deltaTime)
{ {
if (GUI.DisableHUD) { return; } if (GUI.DisableHUD) { return; }
@@ -166,21 +197,17 @@ namespace Barotrauma
} }
} }
UpdateTalentNotificationIndicator(talentPointNotification);
if (GameMain.NetworkMember != null) if (GameMain.NetworkMember != null)
{ {
if (GameMain.NetLobbyScreen?.HeadSelectionList != null) GameMain.NetLobbyScreen?.CharacterAppearanceCustomizationMenu?.Update();
{
if (PlayerInput.PrimaryMouseButtonDown() && !GUI.IsMouseOn(GameMain.NetLobbyScreen.HeadSelectionList))
{
if (GameMain.NetLobbyScreen.HeadSelectionList != null) { GameMain.NetLobbyScreen.HeadSelectionList.Visible = false; }
}
}
if (GameMain.NetLobbyScreen?.JobSelectionFrame != null) if (GameMain.NetLobbyScreen?.JobSelectionFrame != null)
{ {
if (PlayerInput.PrimaryMouseButtonDown() && !GUI.IsMouseOn(GameMain.NetLobbyScreen.JobSelectionFrame)) if (GameMain.NetLobbyScreen.JobSelectionFrame != null && PlayerInput.PrimaryMouseButtonDown() && !GUI.IsMouseOn(GameMain.NetLobbyScreen.JobSelectionFrame))
{ {
GameMain.NetLobbyScreen.JobList.Deselect(); GameMain.NetLobbyScreen.JobList.Deselect();
if (GameMain.NetLobbyScreen.JobSelectionFrame != null) { GameMain.NetLobbyScreen.JobSelectionFrame.Visible = false; } GameMain.NetLobbyScreen.JobSelectionFrame.Visible = false;
} }
} }
} }
@@ -544,7 +544,7 @@ namespace Barotrauma
if (Character.Controlled.CurrentHull == null) { return; } if (Character.Controlled.CurrentHull == null) { return; }
if (HumanAIController.IsBallastFloraNoticeable(Character.Controlled, Character.Controlled.CurrentHull)) if (HumanAIController.IsBallastFloraNoticeable(Character.Controlled, Character.Controlled.CurrentHull))
{ {
if (DisplayHint("onballastflorainfected")) { return; } if (IsOnFriendlySub() && DisplayHint("onballastflorainfected")) { return; }
} }
foreach (var gap in Character.Controlled.CurrentHull.ConnectedGaps) foreach (var gap in Character.Controlled.CurrentHull.ConnectedGaps)
{ {
@@ -552,7 +552,7 @@ namespace Barotrauma
if (Vector2.DistanceSquared(Character.Controlled.WorldPosition, gap.ConnectedDoor.Item.WorldPosition) > 400 * 400) { continue; } if (Vector2.DistanceSquared(Character.Controlled.WorldPosition, gap.ConnectedDoor.Item.WorldPosition) > 400 * 400) { continue; }
if (!gap.IsRoomToRoom) if (!gap.IsRoomToRoom)
{ {
if (!(Character.Controlled.GetEquippedItem("deepdiving", InvSlotType.OuterClothes) is Item)) { continue; } if (!IsWearingDivingSuit()) { continue; }
if (Character.Controlled.IsProtectedFromPressure()) { continue; } if (Character.Controlled.IsProtectedFromPressure()) { continue; }
if (DisplayHint("divingsuitwarning", extendTextTag: false)) { return; } if (DisplayHint("divingsuitwarning", extendTextTag: false)) { return; }
continue; continue;
@@ -561,10 +561,16 @@ namespace Barotrauma
{ {
if (me == Character.Controlled.CurrentHull) { continue; } if (me == Character.Controlled.CurrentHull) { continue; }
if (!(me is Hull adjacentHull)) { continue; } if (!(me is Hull adjacentHull)) { continue; }
if (!IsOnFriendlySub()) { continue; }
if (IsWearingDivingSuit()) { continue; }
if (adjacentHull.LethalPressure > 5.0f && DisplayHint("onadjacenthull.highpressure")) { return; } if (adjacentHull.LethalPressure > 5.0f && DisplayHint("onadjacenthull.highpressure")) { return; }
if (adjacentHull.WaterPercentage > 75 && !BallastHulls.Contains(adjacentHull) && DisplayHint("onadjacenthull.highwaterpercentage")) { return; } if (adjacentHull.WaterPercentage > 75 && !BallastHulls.Contains(adjacentHull) && DisplayHint("onadjacenthull.highwaterpercentage")) { return; }
} }
static bool IsWearingDivingSuit() => Character.Controlled.GetEquippedItem("deepdiving", InvSlotType.OuterClothes) is Item;
} }
static bool IsOnFriendlySub() => Character.Controlled.Submarine is Submarine sub && (sub.TeamID == Character.Controlled.TeamID || sub.TeamID == CharacterTeamType.FriendlyNPC);
} }
private static void CheckReminders() private static void CheckReminders()
@@ -51,7 +51,7 @@ namespace Barotrauma
{ {
keyMapping = new KeyOrMouse[Enum.GetNames(typeof(InputType)).Length]; keyMapping = new KeyOrMouse[Enum.GetNames(typeof(InputType)).Length];
keyMapping[(int)InputType.Run] = new KeyOrMouse(Keys.LeftShift); keyMapping[(int)InputType.Run] = new KeyOrMouse(Keys.LeftShift);
keyMapping[(int)InputType.Attack] = new KeyOrMouse(Keys.R); keyMapping[(int)InputType.Attack] = new KeyOrMouse(Keys.F);
keyMapping[(int)InputType.Crouch] = new KeyOrMouse(Keys.LeftControl); keyMapping[(int)InputType.Crouch] = new KeyOrMouse(Keys.LeftControl);
keyMapping[(int)InputType.Grab] = new KeyOrMouse(Keys.G); keyMapping[(int)InputType.Grab] = new KeyOrMouse(Keys.G);
keyMapping[(int)InputType.Health] = new KeyOrMouse(Keys.H); keyMapping[(int)InputType.Health] = new KeyOrMouse(Keys.H);
@@ -173,7 +173,7 @@ namespace Barotrauma
} }
} }
private void LoadKeyBinds(XElement element) private void LoadKeyBinds(XElement element, Version gameVersion)
{ {
foreach (XAttribute attribute in element.Attributes()) foreach (XAttribute attribute in element.Attributes())
{ {
@@ -183,7 +183,6 @@ namespace Barotrauma
keyMapping[(int)InputType.TakeHalfFromInventorySlot] = new KeyOrMouse(Keys.LeftShift); keyMapping[(int)InputType.TakeHalfFromInventorySlot] = new KeyOrMouse(Keys.LeftShift);
keyMapping[(int)InputType.TakeOneFromInventorySlot] = new KeyOrMouse(Keys.LeftControl); keyMapping[(int)InputType.TakeOneFromInventorySlot] = new KeyOrMouse(Keys.LeftControl);
} }
if (!Enum.TryParse(attribute.Name.ToString(), true, out InputType inputType)) { continue; } if (!Enum.TryParse(attribute.Name.ToString(), true, out InputType inputType)) { continue; }
if (int.TryParse(attribute.Value.ToString(), out int mouseButtonInt)) if (int.TryParse(attribute.Value.ToString(), out int mouseButtonInt))
@@ -199,6 +198,13 @@ namespace Barotrauma
keyMapping[(int)inputType] = new KeyOrMouse(key); keyMapping[(int)inputType] = new KeyOrMouse(key);
} }
} }
//v0.15 added creature attacks that can be used with a character capable of speaking (with mudraptor or spineling genes),
//which causes the previous attack keybind R to conflict with the radio keybind
// -> automatically change it to F
if (gameVersion < new Version(0, 15, 0, 0))
{
keyMapping[(int)InputType.Attack] = new KeyOrMouse(Keys.F);
}
} }
private void LoadInventoryKeybinds(XElement element) private void LoadInventoryKeybinds(XElement element)
@@ -223,10 +229,12 @@ namespace Barotrauma
private void LoadControls(XDocument doc) private void LoadControls(XDocument doc)
{ {
var gameVersion = new Version(doc.Root.GetAttributeString("gameversion", "0.0.0.0"));
XElement keyMapping = doc.Root.Element("keymapping"); XElement keyMapping = doc.Root.Element("keymapping");
if (keyMapping != null) if (keyMapping != null)
{ {
LoadKeyBinds(keyMapping); LoadKeyBinds(keyMapping, gameVersion);
} }
XElement inventoryKeyMapping = doc.Root.Element("inventorykeymapping"); XElement inventoryKeyMapping = doc.Root.Element("inventorykeymapping");
@@ -1518,6 +1526,12 @@ namespace Barotrauma
"Automatic quickstart enabled", "Automatic quickstart enabled",
"Will the game automatically move on to Quickstart when the game is launched"); "Will the game automatically move on to Quickstart when the game is launched");
addDebugTickBox(
TestScreenEnabled,
(b) => TestScreenEnabled = b,
"Test screen enabled",
"Will the game automatically move on to a test screen when the game is launched");
addDebugTickBox( addDebugTickBox(
AutomaticCampaignLoadEnabled, AutomaticCampaignLoadEnabled,
(b) => AutomaticCampaignLoadEnabled = b, (b) => AutomaticCampaignLoadEnabled = b,
@@ -1831,6 +1845,7 @@ namespace Barotrauma
ic.ParseMsg(); ic.ParseMsg();
} }
} }
CharacterHUD.ShouldRecreateHudTexts = true;
} }
private void ApplySettings() private void ApplySettings()
@@ -161,7 +161,7 @@ namespace Barotrauma
public override void CreateSlots() public override void CreateSlots()
{ {
if (visualSlots == null) { visualSlots = new VisualSlot[capacity]; } visualSlots ??= new VisualSlot[capacity];
float multiplier = !GUI.IsFourByThree() ? UIScale : UIScale * 0.925f; float multiplier = !GUI.IsFourByThree() ? UIScale : UIScale * 0.925f;
@@ -175,6 +175,12 @@ namespace Barotrauma
(int)SlotPositions[i].Y, (int)SlotPositions[i].Y,
(int)(slotSprite.size.X * multiplier), (int)(slotSprite.size.Y * multiplier)); (int)(slotSprite.size.X * multiplier), (int)(slotSprite.size.Y * multiplier));
if (SlotTypes[i] == InvSlotType.HealthInterface &&
character.CharacterHealth?.InventorySlotContainer != null)
{
slotRect.Width = slotRect.Height = (int)(character.CharacterHealth.InventorySlotContainer.Rect.Width * 1.2f);
}
ItemContainer itemContainer = slots[i].FirstOrDefault()?.GetComponent<ItemContainer>(); ItemContainer itemContainer = slots[i].FirstOrDefault()?.GetComponent<ItemContainer>();
if (itemContainer != null) if (itemContainer != null)
{ {
@@ -238,6 +244,8 @@ namespace Barotrauma
{ {
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 (layout == Layout.Default) if (layout == Layout.Default)
{ {
if (PersonalSlots.HasFlag(SlotTypes[i]) && !personalSlotArea.Contains(visualSlots[i].Rect.Center + visualSlots[i].DrawOffset.ToPoint())) { return true; } if (PersonalSlots.HasFlag(SlotTypes[i]) && !personalSlotArea.Contains(visualSlots[i].Rect.Center + visualSlots[i].DrawOffset.ToPoint())) { return true; }
@@ -315,7 +323,7 @@ namespace Barotrauma
case Layout.Default: case Layout.Default:
{ {
int personalSlotCount = SlotTypes.Count(s => PersonalSlots.HasFlag(s)); int personalSlotCount = SlotTypes.Count(s => PersonalSlots.HasFlag(s));
int normalSlotCount = SlotTypes.Count(s => !PersonalSlots.HasFlag(s)); int normalSlotCount = SlotTypes.Count(s => !PersonalSlots.HasFlag(s) && s != InvSlotType.HealthInterface);
int x = GameMain.GraphicsWidth / 2 - normalSlotCount * (SlotSize.X + Spacing) / 2; int x = GameMain.GraphicsWidth / 2 - normalSlotCount * (SlotSize.X + Spacing) / 2;
int upperX = HUDLayoutSettings.BottomRightInfoArea.X - SlotSize.X - Spacing * 4 - HideButtonWidth; int upperX = HUDLayoutSettings.BottomRightInfoArea.X - SlotSize.X - Spacing * 4 - HideButtonWidth;
@@ -359,7 +367,8 @@ namespace Barotrauma
int personalSlotX = HUDLayoutSettings.InventoryAreaLower.Right - SlotSize.X - Spacing; int personalSlotX = HUDLayoutSettings.InventoryAreaLower.Right - SlotSize.X - Spacing;
for (int i = 0; i < visualSlots.Length; i++) for (int i = 0; i < visualSlots.Length; i++)
{ {
if (HideSlot(i)) continue; if (HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; }
if (SlotTypes[i] == InvSlotType.RightHand || SlotTypes[i] == InvSlotType.LeftHand) { continue; }
if (PersonalSlots.HasFlag(SlotTypes[i])) if (PersonalSlots.HasFlag(SlotTypes[i]))
{ {
//upperX -= slotSize.X + spacing; //upperX -= slotSize.X + spacing;
@@ -371,10 +380,18 @@ namespace Barotrauma
} }
int lowerX = x; int lowerX = x;
int handSlotX = x;
int personalSlotY = GameMain.GraphicsHeight - bottomOffset * 2 - Spacing * 2 - (int)(!GUI.IsFourByThree() ? UnequippedIndicator.size.Y * UIScale * IndicatorScaleAdjustment : UnequippedIndicator.size.Y * UIScale * IndicatorScaleAdjustment * 2f); int personalSlotY = GameMain.GraphicsHeight - bottomOffset * 2 - Spacing * 2 - (int)(!GUI.IsFourByThree() ? UnequippedIndicator.size.Y * UIScale * IndicatorScaleAdjustment : UnequippedIndicator.size.Y * UIScale * IndicatorScaleAdjustment * 2f);
for (int i = 0; i < SlotPositions.Length; i++) for (int i = 0; i < SlotPositions.Length; i++)
{ {
if (HideSlot(i)) continue; if (SlotTypes[i] == InvSlotType.RightHand || SlotTypes[i] == InvSlotType.LeftHand)
{
SlotPositions[i] = new Vector2(handSlotX, personalSlotY);
handSlotX += visualSlots[i].Rect.Width + Spacing;
continue;
}
if (HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; }
if (PersonalSlots.HasFlag(SlotTypes[i])) if (PersonalSlots.HasFlag(SlotTypes[i]))
{ {
SlotPositions[i] = new Vector2(personalSlotX, personalSlotY); SlotPositions[i] = new Vector2(personalSlotX, personalSlotY);
@@ -390,7 +407,8 @@ namespace Barotrauma
x = lowerX; x = lowerX;
for (int i = 0; i < SlotPositions.Length; i++) for (int i = 0; i < SlotPositions.Length; i++)
{ {
if (!HideSlot(i)) continue; if (!HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; }
if (SlotTypes[i] == InvSlotType.RightHand || SlotTypes[i] == InvSlotType.LeftHand) { continue; }
x -= visualSlots[i].Rect.Width + Spacing; x -= visualSlots[i].Rect.Width + Spacing;
SlotPositions[i] = new Vector2(x, GameMain.GraphicsHeight - bottomOffset); SlotPositions[i] = new Vector2(x, GameMain.GraphicsHeight - bottomOffset);
} }
@@ -404,7 +422,8 @@ namespace Barotrauma
for (int i = 0; i < SlotPositions.Length; i++) for (int i = 0; i < SlotPositions.Length; i++)
{ {
if (HideSlot(i)) continue; if (HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; }
if (SlotTypes[i] == InvSlotType.RightHand || SlotTypes[i] == InvSlotType.LeftHand) { continue; }
if (PersonalSlots.HasFlag(SlotTypes[i])) if (PersonalSlots.HasFlag(SlotTypes[i]))
{ {
SlotPositions[i] = new Vector2(personalSlotX, personalSlotY); SlotPositions[i] = new Vector2(personalSlotX, personalSlotY);
@@ -416,9 +435,16 @@ namespace Barotrauma
x += visualSlots[i].Rect.Width + Spacing; x += visualSlots[i].Rect.Width + Spacing;
} }
} }
int handSlotX = x - visualSlots[0].Rect.Width - Spacing;
for (int i = 0; i < SlotPositions.Length; i++) for (int i = 0; i < SlotPositions.Length; i++)
{ {
if (!HideSlot(i)) continue; if (SlotTypes[i] == InvSlotType.RightHand || SlotTypes[i] == InvSlotType.LeftHand)
{
bool rightSlot = SlotTypes[i] == InvSlotType.RightHand;
SlotPositions[i] = new Vector2(rightSlot ? handSlotX : handSlotX - visualSlots[0].Rect.Width - Spacing, personalSlotY);
continue;
}
if (!HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; }
SlotPositions[i] = new Vector2(x, GameMain.GraphicsHeight - bottomOffset); SlotPositions[i] = new Vector2(x, GameMain.GraphicsHeight - bottomOffset);
x += visualSlots[i].Rect.Width + Spacing; x += visualSlots[i].Rect.Width + Spacing;
} }
@@ -432,7 +458,7 @@ namespace Barotrauma
int x = startX, y = startY; int x = startX, y = startY;
for (int i = 0; i < SlotPositions.Length; i++) for (int i = 0; i < SlotPositions.Length; i++)
{ {
if (HideSlot(i)) continue; if (HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; }
if (SlotTypes[i] == InvSlotType.Card || SlotTypes[i] == InvSlotType.Headset || SlotTypes[i] == InvSlotType.InnerClothes) if (SlotTypes[i] == InvSlotType.Card || SlotTypes[i] == InvSlotType.Headset || SlotTypes[i] == InvSlotType.InnerClothes)
{ {
SlotPositions[i] = new Vector2(x, y); SlotPositions[i] = new Vector2(x, y);
@@ -444,7 +470,7 @@ namespace Barotrauma
int n = 0; int n = 0;
for (int i = 0; i < SlotPositions.Length; i++) for (int i = 0; i < SlotPositions.Length; i++)
{ {
if (HideSlot(i)) continue; if (HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; }
if (SlotTypes[i] != InvSlotType.Card && SlotTypes[i] != InvSlotType.Headset && SlotTypes[i] != InvSlotType.InnerClothes) if (SlotTypes[i] != InvSlotType.Card && SlotTypes[i] != InvSlotType.Headset && SlotTypes[i] != InvSlotType.InnerClothes)
{ {
SlotPositions[i] = new Vector2(x, y); SlotPositions[i] = new Vector2(x, y);
@@ -462,12 +488,22 @@ namespace Barotrauma
break; break;
} }
if (character.CharacterHealth?.UseHealthWindow ?? false)
{
Vector2 pos = character.CharacterHealth.InventorySlotContainer.Rect.Location.ToVector2();
for (int i = 0; i < capacity; i++)
{
if (SlotTypes[i] != InvSlotType.HealthInterface) { continue; }
SlotPositions[i] = pos;
pos.Y += visualSlots[i].Rect.Height + Spacing;
}
}
CreateSlots(); CreateSlots();
if (layout == Layout.Default) if (layout == Layout.Default)
{ {
HUDLayoutSettings.InventoryTopY = visualSlots[0].EquipButtonRect.Y - (int)(15 * GUI.Scale); HUDLayoutSettings.InventoryTopY = visualSlots[0].EquipButtonRect.Y - (int)(15 * GUI.Scale);
} }
} }
protected override void ControlInput(Camera cam) protected override void ControlInput(Camera cam)
@@ -652,6 +688,11 @@ namespace Barotrauma
{ {
break; break;
} }
//if putting an item to a container with a max stack size of 1, only put one item from the stack
if (quickUseAction == QuickUseAction.PutToContainer && (character.SelectedConstruction?.GetComponent<ItemContainer>()?.MaxStackSize ?? 0) <= 1)
{
break;
}
} }
} }
@@ -661,7 +702,7 @@ namespace Barotrauma
if (item != null) if (item != null)
{ {
var slot = visualSlots[i]; var slot = visualSlots[i];
if (item.AllowedSlots.Any(a => a != InvSlotType.Any)) if (item.AllowedSlots.Any(a => a != InvSlotType.Any && a != InvSlotType.HealthInterface))
{ {
HandleButtonEquipStates(item, slot, deltaTime); HandleButtonEquipStates(item, slot, deltaTime);
} }
@@ -840,7 +881,9 @@ namespace Barotrauma
private QuickUseAction GetQuickUseAction(Item item, bool allowEquip, bool allowInventorySwap, bool allowApplyTreatment) private QuickUseAction GetQuickUseAction(Item item, bool allowEquip, bool allowInventorySwap, bool allowApplyTreatment)
{ {
if (allowApplyTreatment && CharacterHealth.OpenHealthWindow != null) if (allowApplyTreatment && CharacterHealth.OpenHealthWindow != null &&
//if the item can be equipped in the health interface slot, don't use it as a treatment but try to equip it
!item.AllowedSlots.Contains(InvSlotType.HealthInterface))
{ {
return QuickUseAction.UseTreatment; return QuickUseAction.UseTreatment;
} }
@@ -1135,7 +1178,7 @@ namespace Barotrauma
for (int i = 0; i < capacity; i++) for (int i = 0; i < capacity; i++)
{ {
if (HideSlot(i)) { continue; } if (HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { 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 = !DraggingItems.Any() || !slots[i].Items.All(it => DraggingItems.Contains(it)) || visualSlots[i].MouseOn(); bool drawItem = !DraggingItems.Any() || !slots[i].Items.All(it => DraggingItems.Contains(it)) || visualSlots[i].MouseOn();
@@ -1189,7 +1232,7 @@ namespace Barotrauma
highlightedQuickUseSlot = visualSlots[i]; highlightedQuickUseSlot = visualSlots[i];
} }
if (!slots[i].First().AllowedSlots.Any(a => a == InvSlotType.Any)) if (slots[i].First().AllowedSlots.Count() == 1 || SlotTypes[i] == InvSlotType.HealthInterface)
{ {
continue; continue;
} }
@@ -0,0 +1,61 @@
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma.Items.Components
{
internal partial class EntitySpawnerComponent
{
public Vector2 DrawSize => Vector2.Zero;
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1)
{
if (!editing) { return; }
switch (SpawnAreaShape)
{
case AreaShape.Rectangle:
{
RectangleF rect = GetAreaRectangle(SpawnAreaBounds, SpawnAreaOffset, draw: true);
GUI.DrawRectangle(spriteBatch, rect.Location, rect.Size, GUI.Style.Red, isFilled: false, 0f, 4f);
if (MaximumAmountRangePadding > 0f)
{
rect.Inflate(MaximumAmountRangePadding, MaximumAmountRangePadding);
GUI.DrawRectangle(spriteBatch, rect.Location, rect.Size, GUI.Style.Red, isFilled: false, 0f, 2f);
}
break;
}
case AreaShape.Circle:
Vector2 center = item.WorldPosition;
center.Y = -center.Y;
center += SpawnAreaOffset;
spriteBatch.DrawCircle(center, SpawnAreaRadius, 32, GUI.Style.Red, thickness: 4f);
if (MaximumAmountRangePadding > 0f)
{
spriteBatch.DrawCircle(center, SpawnAreaRadius + MaximumAmountRangePadding, 32, GUI.Style.Red, thickness: 2f);
}
break;
}
if (!OnlySpawnWhenCrewInRange) { return; }
switch (CrewAreaShape)
{
case AreaShape.Rectangle:
{
RectangleF rect = GetAreaRectangle(CrewAreaBounds, CrewAreaOffset, draw: true);
GUI.DrawRectangle(spriteBatch, rect.Location, rect.Size, GUI.Style.Green, isFilled: false, 0f, 4f);
break;
}
case AreaShape.Circle:
Vector2 center = item.WorldPosition;
center.Y = -center.Y;
center += CrewAreaOffset;
spriteBatch.DrawCircle(center, CrewAreaRadius, 32, GUI.Style.Green);
break;
}
}
}
}
@@ -0,0 +1,86 @@
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System.Linq;
namespace Barotrauma.Items.Components
{
partial class GeneticMaterial : ItemComponent
{
[Serialize(0.0f, false)]
public float TooltipValueMin { get; set; }
[Serialize(0.0f, false)]
public float TooltipValueMax { get; set; }
public override void AddTooltipInfo(ref string name, ref string description)
{
if (!string.IsNullOrEmpty(materialName) && item.ContainedItems.Count() > 0)
{
string mergedMaterialName = materialName;
foreach (Item containedItem in item.ContainedItems)
{
var containedMaterial = containedItem.GetComponent<GeneticMaterial>();
if (containedMaterial == null) { continue; }
mergedMaterialName += ", " + containedMaterial.materialName;
}
name = name.Replace(materialName, mergedMaterialName);
}
if (Tainted)
{
name = TextManager.GetWithVariable("entityname.taintedgeneticmaterial", "[geneticmaterialname]", name);
}
if (TextManager.ContainsTag("entitydescription." + Item.prefab.Identifier))
{
int value = (int)MathHelper.Lerp(TooltipValueMin, TooltipValueMax, item.ConditionPercentage / 100.0f);
description = TextManager.GetWithVariable("entitydescription." + Item.prefab.Identifier, "[value]", value.ToString());
}
foreach (Item containedItem in item.ContainedItems)
{
var containedGeneticMaterial = containedItem.GetComponent<GeneticMaterial>();
if (containedGeneticMaterial == null) { continue; }
string _ = string.Empty;
string containedDescription = containedItem.Description;
containedGeneticMaterial.AddTooltipInfo(ref _, ref containedDescription);
if (!string.IsNullOrEmpty(containedDescription))
{
description += '\n' + containedDescription;
}
}
}
public void ModifyDeconstructInfo(Deconstructor deconstructor, ref string buttonText, ref string infoText)
{
if (deconstructor.InputContainer.Inventory.AllItems.Count() == 2)
{
if (!deconstructor.InputContainer.Inventory.AllItems.All(it => it.prefab == item.prefab))
{
buttonText = TextManager.Get("researchstation.combine");
infoText = TextManager.Get("researchstation.combine.infotext");
}
else
{
buttonText = TextManager.Get("researchstation.refine");
int taintedProbability = (int)(GetTaintedProbabilityOnRefine(Character.Controlled) * 100);
infoText = TextManager.GetWithVariable("researchstation.refine.infotext", "[taintedprobability]", taintedProbability.ToString());
}
}
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{
Tainted = msg.ReadBoolean();
if (Tainted)
{
uint selectedTaintedEffectId = msg.ReadUInt32();
selectedTaintedEffect = AfflictionPrefab.Prefabs.Find(a => a.UIntIdentifier == selectedTaintedEffectId);
}
else
{
uint selectedEffectId = msg.ReadUInt32();
selectedEffect = AfflictionPrefab.Prefabs.Find(a => a.UIntIdentifier == selectedEffectId);
}
}
}
}
@@ -1,13 +1,201 @@
using Microsoft.Xna.Framework; using System;
using Microsoft.Xna.Framework;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Extensions;
using Barotrauma.IO;
namespace Barotrauma.Items.Components namespace Barotrauma.Items.Components
{ {
partial class IdCard partial class IdCard
{ {
public Sprite StoredPortrait; public struct OwnerAppearance
public Vector2 StoredSheetIndex; {
public JobPrefab StoredJobPrefab; public Sprite Portrait;
public List<WearableSprite> StoredAttachments; public Vector2 SheetIndex;
public JobPrefab JobPrefab;
public List<WearableSprite> Attachments;
public Color HairColor;
public Color FacialHairColor;
public Color SkinColor;
public void ExtractJobPrefab(string[] tags)
{
string jobIdTag = tags.FirstOrDefault(s => s.StartsWith("jobid:"));
if (jobIdTag != null && jobIdTag.Length > 6)
{
string jobId = jobIdTag.Substring(6);
if (jobId != string.Empty)
{
JobPrefab = JobPrefab.Get(jobId);
}
}
}
public void ExtractAppearance(CharacterInfo characterInfo, string[] tags)
{
Gender disguisedGender = Gender.None;
Race disguisedRace = Race.None;
int disguisedHeadSpriteId = -1;
int disguisedHairIndex = -1;
int disguisedBeardIndex = -1;
int disguisedMoustacheIndex = -1;
int disguisedFaceAttachmentIndex = -1;
Color hairColor = Color.Black;
Color facialHairColor = Color.Black;
Color skinColor = Color.Black;
foreach (string tag in tags)
{
string[] s = tag.Split(':');
switch (s[0].ToLowerInvariant())
{
case "haircolor":
hairColor = XMLExtensions.ParseColor(s[1]);
break;
case "facialhaircolor":
facialHairColor = XMLExtensions.ParseColor(s[1]);
break;
case "skincolor":
skinColor = XMLExtensions.ParseColor(s[1]);
break;
case "gender":
Enum.TryParse(s[1], ignoreCase: true, out disguisedGender);
break;
case "race":
Enum.TryParse(s[1], ignoreCase: true, out disguisedRace);
break;
case "headspriteid":
int.TryParse(s[1], NumberStyles.Any, CultureInfo.InvariantCulture, out disguisedHeadSpriteId);
break;
case "hairindex":
disguisedHairIndex = int.Parse(s[1]);
break;
case "beardindex":
disguisedBeardIndex = int.Parse(s[1]);
break;
case "moustacheindex":
disguisedMoustacheIndex = int.Parse(s[1]);
break;
case "faceattachmentindex":
disguisedFaceAttachmentIndex = int.Parse(s[1]);
break;
case "sheetindex":
string[] vectorValues = s[1].Split(";");
SheetIndex = new Vector2(float.Parse(vectorValues[0]), float.Parse(vectorValues[1]));
break;
}
}
if ((characterInfo.HasGenders && disguisedGender == Gender.None)
|| (characterInfo.HasRaces && disguisedRace == Race.None)
|| disguisedHeadSpriteId <= 0)
{
Portrait = null;
Attachments = null;
return;
}
foreach (XElement limbElement in characterInfo.Ragdoll.MainElement.Elements())
{
if (!limbElement.GetAttributeString("type", "").Equals("head", StringComparison.OrdinalIgnoreCase)) { continue; }
XElement spriteElement = limbElement.Element("sprite");
if (spriteElement == null) { continue; }
string spritePath = spriteElement.Attribute("texture").Value;
spritePath = spritePath.Replace("[GENDER]", disguisedGender.ToString().ToLowerInvariant());
spritePath = spritePath.Replace("[RACE]", disguisedRace.ToString().ToLowerInvariant());
spritePath = spritePath.Replace("[HEADID]", disguisedHeadSpriteId.ToString());
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))
{
continue;
}
string fileWithoutTags = Path.GetFileNameWithoutExtension(file);
fileWithoutTags = fileWithoutTags.Split('[', ']').First();
if (fileWithoutTags != fileName) { continue; }
Portrait = new Sprite(spriteElement, "", file) { RelativeOrigin = Vector2.Zero };
break;
}
break;
}
if (characterInfo.Wearables != null)
{
float baldnessChance = disguisedGender == Gender.Female ? 0.05f : 0.2f;
List<XElement> createElementList(WearableType wearableType, float emptyCommonness = 1.0f)
=> CharacterInfo.AddEmpty(
characterInfo.FilterByTypeAndHeadID(
characterInfo.FilterElementsByGenderAndRace(characterInfo.Wearables, disguisedGender, disguisedRace),
wearableType, disguisedHeadSpriteId),
wearableType, emptyCommonness);
var disguisedHairs = createElementList(WearableType.Hair, baldnessChance);
var disguisedBeards = createElementList(WearableType.Beard);
var disguisedMoustaches = createElementList(WearableType.Moustache);
var disguisedFaceAttachments = createElementList(WearableType.FaceAttachment);
XElement getElementFromList(List<XElement> list, int index)
=> CharacterInfo.IsValidIndex(index, list)
? list[index]
: characterInfo.GetRandomElement(list);
var disguisedHairElement = getElementFromList(disguisedHairs, disguisedHairIndex);
var disguisedBeardElement = getElementFromList(disguisedBeards, disguisedBeardIndex);
var disguisedMoustacheElement = getElementFromList(disguisedMoustaches, disguisedMoustacheIndex);
var disguisedFaceAttachmentElement = getElementFromList(disguisedFaceAttachments, disguisedFaceAttachmentIndex);
Attachments = new List<WearableSprite>();
void loadAttachments(List<WearableSprite> attachments, XElement element, WearableType wearableType)
{
foreach (var s in element?.Elements("sprite") ?? Enumerable.Empty<XElement>())
{
attachments.Add(new WearableSprite(s, wearableType));
}
}
loadAttachments(Attachments, disguisedFaceAttachmentElement, WearableType.FaceAttachment);
loadAttachments(Attachments, disguisedBeardElement, WearableType.Beard);
loadAttachments(Attachments, disguisedMoustacheElement, WearableType.Moustache);
loadAttachments(Attachments, disguisedHairElement, WearableType.Hair);
loadAttachments(Attachments,
characterInfo.OmitJobInPortraitClothing
? JobPrefab.NoJobElement?.Element("PortraitClothing")
: JobPrefab?.ClothingElement,
WearableType.JobIndicator);
}
HairColor = hairColor;
FacialHairColor = facialHairColor;
SkinColor = skinColor;
}
}
public OwnerAppearance StoredOwnerAppearance = default;
} }
} }
@@ -7,6 +7,8 @@ using System.Collections.Generic;
using Barotrauma.IO; using Barotrauma.IO;
using System.Text; using System.Text;
using System.Xml.Linq; using System.Xml.Linq;
using Barotrauma.Sounds;
using System.Linq;
namespace Barotrauma.Items.Components namespace Barotrauma.Items.Components
{ {
@@ -18,7 +20,11 @@ namespace Barotrauma.Items.Components
protected float currentCrossHairScale, currentCrossHairPointerScale; protected float currentCrossHairScale, currentCrossHairPointerScale;
private RoundSound chargeSound;
private SoundChannel chargeSoundChannel;
private readonly List<ParticleEmitter> particleEmitters = new List<ParticleEmitter>(); private readonly List<ParticleEmitter> particleEmitters = new List<ParticleEmitter>();
private readonly List<ParticleEmitter> particleEmitterCharges = new List<ParticleEmitter>();
[Serialize(1.0f, false, description: "The scale of the crosshair sprite (if there is one).")] [Serialize(1.0f, false, description: "The scale of the crosshair sprite (if there is one).")]
public float CrossHairScale public float CrossHairScale
@@ -48,6 +54,12 @@ namespace Barotrauma.Items.Components
case "particleemitter": case "particleemitter":
particleEmitters.Add(new ParticleEmitter(subElement)); particleEmitters.Add(new ParticleEmitter(subElement));
break; break;
case "particleemittercharge":
particleEmitterCharges.Add(new ParticleEmitter(subElement));
break;
case "chargesound":
chargeSound = Submarine.LoadRoundSound(subElement, false);
break;
} }
} }
} }
@@ -84,15 +96,62 @@ namespace Barotrauma.Items.Components
crosshairPointerPos = PlayerInput.MousePosition; crosshairPointerPos = PlayerInput.MousePosition;
} }
partial void UpdateProjSpecific(float deltaTime)
{
float chargeRatio = currentChargeTime / MaxChargeTime;
switch (currentChargingState)
{
case ChargingState.WindingUp:
case ChargingState.WindingDown:
Vector2 particlePos = item.WorldPosition + ConvertUnits.ToDisplayUnits(TransformedBarrelPos);
float sizeMultiplier = Math.Clamp(chargeRatio, 0.1f, 1f);
foreach (ParticleEmitter emitter in particleEmitterCharges)
{
emitter.Emit(deltaTime, particlePos, hullGuess: null, sizeMultiplier: sizeMultiplier, colorMultiplier: emitter.Prefab.Properties.ColorMultiplier);
}
if (chargeSoundChannel == null || !chargeSoundChannel.IsPlaying)
{
if (chargeSound != null)
{
chargeSoundChannel = SoundPlayer.PlaySound(chargeSound.Sound, item.WorldPosition, chargeSound.Volume, chargeSound.Range, ignoreMuffling: chargeSound.IgnoreMuffling);
if (chargeSoundChannel != null) chargeSoundChannel.Looping = true;
}
}
else if (chargeSoundChannel != null)
{
chargeSoundChannel.FrequencyMultiplier = MathHelper.Lerp(0.5f, 1.5f, chargeRatio);
}
break;
default:
if (chargeSoundChannel != null)
{
if (chargeSoundChannel.IsPlaying)
{
chargeSoundChannel.FadeOutAndDispose();
chargeSoundChannel.Looping = false;
}
else
{
chargeSoundChannel = null;
}
}
break;
}
}
public override void DrawHUD(SpriteBatch spriteBatch, Character character) public override void DrawHUD(SpriteBatch spriteBatch, Character character)
{ {
if (character == null || !character.IsKeyDown(InputType.Aim)) { return; } if (character == null || !character.IsKeyDown(InputType.Aim)) { return; }
//camera focused on some other item/device, don't draw the crosshair //camera focused on some other item/device, don't draw the crosshair
if (character.ViewTarget != null && (character.ViewTarget is Item item) && item.Prefab.FocusOnSelected) { return; } if (character.ViewTarget != null && (character.ViewTarget is Item viewTargetItem) && viewTargetItem.Prefab.FocusOnSelected) { return; }
//don't draw the crosshair if the item is in some other type of equip slot than hands (e.g. assault rifle in the bag slot)
if (!character.HeldItems.Contains(item)) { return; }
GUI.HideCursor = (crosshairSprite != null || crosshairPointerSprite != null) && GUI.HideCursor = (crosshairSprite != null || crosshairPointerSprite != null) &&
GUI.MouseOn == null && !Inventory.IsMouseOnInventory() && !GameMain.Instance.Paused; GUI.MouseOn == null && !Inventory.IsMouseOnInventory && !GameMain.Instance.Paused;
if (GUI.HideCursor) if (GUI.HideCursor)
{ {
crosshairSprite?.Draw(spriteBatch, crosshairPos, Color.White, 0, currentCrossHairScale); crosshairSprite?.Draw(spriteBatch, crosshairPos, Color.White, 0, currentCrossHairScale);
@@ -246,6 +246,7 @@ namespace Barotrauma.Items.Components
public void PlaySound(ActionType type, Character user = null) public void PlaySound(ActionType type, Character user = null)
{ {
if (!hasSoundsOfType[(int)type]) { return; } if (!hasSoundsOfType[(int)type]) { return; }
if (GameMain.Client?.MidRoundSyncing ?? false) { return; }
if (loopingSound != null) if (loopingSound != null)
{ {
@@ -429,7 +430,7 @@ namespace Barotrauma.Items.Components
} }
foreach (ItemComponent component in item.Components) foreach (ItemComponent component in item.Components)
{ {
if (component.name.ToLower() == LinkUIToComponent.ToLower()) if (component.name.Equals(LinkUIToComponent, StringComparison.OrdinalIgnoreCase))
{ {
linkToUIComponent = component; linkToUIComponent = component;
} }
@@ -443,9 +444,9 @@ namespace Barotrauma.Items.Components
public virtual void DrawHUD(SpriteBatch spriteBatch, Character character) { } public virtual void DrawHUD(SpriteBatch spriteBatch, Character character) { }
public virtual void AddToGUIUpdateList() public virtual void AddToGUIUpdateList(int order = 0)
{ {
GuiFrame?.AddToGUIUpdateList(); GuiFrame?.AddToGUIUpdateList(order: order);
} }
public virtual void UpdateHUD(Character character, float deltaTime, Camera cam) { } public virtual void UpdateHUD(Character character, float deltaTime, Camera cam) { }
@@ -620,6 +621,6 @@ namespace Barotrauma.Items.Components
} }
OnResolutionChanged(); OnResolutionChanged();
} }
public virtual void AddTooltipInfo(ref string description) { } public virtual void AddTooltipInfo(ref string name, ref string description) { }
} }
} }
@@ -19,6 +19,8 @@ namespace Barotrauma.Items.Components
/// </summary> /// </summary>
private float[] containedSpriteDepths; private float[] containedSpriteDepths;
private Sprite[] slotIcons;
public Sprite InventoryTopSprite public Sprite InventoryTopSprite
{ {
get { return inventoryTopSprite; } get { return inventoryTopSprite; }
@@ -58,6 +60,9 @@ namespace Barotrauma.Items.Components
[Serialize(null, false)] [Serialize(null, false)]
public string ContainedStateIndicatorStyle { get; set; } public string ContainedStateIndicatorStyle { get; set; }
[Serialize(-1, false, description: "Can be used to make the contained state indicator display the condition of the item in a specific slot even when the container's capacity is more than 1.")]
public int ContainedStateIndicatorSlot { get; set; }
[Serialize(true, false, description: "Should an indicator displaying the state of the contained items be displayed on this item's inventory slot. "+ [Serialize(true, false, description: "Should an indicator displaying the state of the contained items be displayed on this item's inventory slot. "+
"If this item can only contain one item, the indicator will display the condition of the contained item, otherwise it will indicate how full the item is.")] "If this item can only contain one item, the indicator will display the condition of the contained item, otherwise it will indicate how full the item is.")]
public bool ShowContainedStateIndicator { get; set; } public bool ShowContainedStateIndicator { get; set; }
@@ -85,6 +90,7 @@ namespace Barotrauma.Items.Components
partial void InitProjSpecific(XElement element) partial void InitProjSpecific(XElement element)
{ {
slotIcons = new Sprite[capacity];
foreach (XElement subElement in element.Elements()) foreach (XElement subElement in element.Elements())
{ {
switch (subElement.Name.ToString().ToLowerInvariant()) switch (subElement.Name.ToString().ToLowerInvariant())
@@ -104,6 +110,17 @@ namespace Barotrauma.Items.Components
case "containedstateindicatorempty": case "containedstateindicatorempty":
ContainedStateIndicatorEmpty = new Sprite(subElement); ContainedStateIndicatorEmpty = new Sprite(subElement);
break; break;
case "sloticon":
int index = subElement.GetAttributeInt("slotindex", -1);
Sprite icon = new Sprite(subElement);
for (int i = 0; i < capacity; i++)
{
if (i == index || index == -1)
{
slotIcons[i] = icon;
}
}
break;
} }
} }
@@ -197,7 +214,7 @@ namespace Barotrauma.Items.Components
if (UILabel == string.Empty) { return string.Empty; } if (UILabel == string.Empty) { return string.Empty; }
if (UILabel != null) if (UILabel != null)
{ {
return TextManager.Get("UILabel." + UILabel); return TextManager.Get("UILabel." + UILabel, returnNull: true) ?? TextManager.Get(UILabel);
} }
else else
{ {
@@ -205,6 +222,12 @@ namespace Barotrauma.Items.Components
} }
} }
public Sprite GetSlotIcon(int slotIndex)
{
if (slotIndex < 0 || slotIndex >= slotIcons.Length) { return null; }
return slotIcons[slotIndex];
}
public bool KeepOpenWhenEquippedBy(Character character) public bool KeepOpenWhenEquippedBy(Character character)
{ {
if (!character.CanAccessInventory(Inventory) || if (!character.CanAccessInventory(Inventory) ||
@@ -266,7 +289,7 @@ namespace Barotrauma.Items.Components
} }
else else
{ {
Matrix transform = Matrix.CreateRotationZ(item.body.Rotation); Matrix transform = Matrix.CreateRotationZ(item.body.DrawRotation);
if (item.body.Dir == -1.0f) if (item.body.Dir == -1.0f)
{ {
transformedItemPos.X = -transformedItemPos.X; transformedItemPos.X = -transformedItemPos.X;
@@ -277,7 +300,7 @@ namespace Barotrauma.Items.Components
transformedItemPos = Vector2.Transform(transformedItemPos, transform); transformedItemPos = Vector2.Transform(transformedItemPos, transform);
transformedItemInterval = Vector2.Transform(transformedItemInterval, transform); transformedItemInterval = Vector2.Transform(transformedItemInterval, transform);
transformedItemIntervalHorizontal = Vector2.Transform(transformedItemIntervalHorizontal, transform); transformedItemIntervalHorizontal = Vector2.Transform(transformedItemIntervalHorizontal, transform);
transformedItemPos += item.DrawPosition; transformedItemPos += item.body.DrawPosition;
} }
Vector2 currentItemPos = transformedItemPos; Vector2 currentItemPos = transformedItemPos;
@@ -319,7 +342,7 @@ namespace Barotrauma.Items.Components
new Vector2(currentItemPos.X, -currentItemPos.Y), new Vector2(currentItemPos.X, -currentItemPos.Y),
isWiringMode ? containedItem.GetSpriteColor() * 0.15f : containedItem.GetSpriteColor(), isWiringMode ? containedItem.GetSpriteColor() * 0.15f : containedItem.GetSpriteColor(),
origin, origin,
-(containedItem.body == null ? 0.0f : containedItem.body.DrawRotation + MathHelper.ToRadians(-item.Rotation)), -(containedItem.body == null ? 0.0f : containedItem.body.DrawRotation ),
containedItem.Scale, containedItem.Scale,
spriteEffects, spriteEffects,
depth: containedSpriteDepth); depth: containedSpriteDepth);
@@ -39,7 +39,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)
{ {
if (Light.LightSprite != null && (item.body == null || item.body.Enabled) && lightBrightness > 0.0f && IsOn) if (Light.LightSprite != null && (item.body == null || item.body.Enabled) && lightBrightness > 0.0f && IsOn && Light.Enabled)
{ {
Vector2 origin = Light.LightSprite.Origin; Vector2 origin = Light.LightSprite.Origin;
if ((Light.LightSpriteEffect & SpriteEffects.FlipHorizontally) == SpriteEffects.FlipHorizontally) { origin.X = Light.LightSprite.SourceRect.Width - origin.X; } if ((Light.LightSpriteEffect & SpriteEffects.FlipHorizontally) == SpriteEffects.FlipHorizontally) { origin.X = Light.LightSprite.SourceRect.Width - origin.X; }
@@ -14,12 +14,22 @@ namespace Barotrauma.Items.Components
} }
private GUIButton activateButton; private GUIButton activateButton;
private GUIComponent inputInventoryHolder, outputInventoryHolder; private GUIComponent inputInventoryHolder, outputInventoryHolder;
private GUICustomComponent inputInventoryOverlay;
private GUIComponent inSufficientPowerWarning; private GUIComponent inSufficientPowerWarning;
private bool pendingState; private bool pendingState;
private GUITextBlock infoArea;
[Serialize("DeconstructorDeconstruct", true)]
public string ActivateButtonText { get; set; }
[Serialize("", true)]
public string InfoText { get; set; }
[Serialize(0.0f, true)]
public float InfoAreaWidth { get; set; }
partial void InitProjSpecific(XElement element) partial void InitProjSpecific(XElement element)
{ {
CreateGUI(); CreateGUI();
@@ -39,6 +49,12 @@ namespace Barotrauma.Items.Components
RelativeSpacing = 0.08f RelativeSpacing = 0.08f
}; };
new GUITextBlock(new RectTransform(new Vector2(1f, 0.07f), paddedFrame.RectTransform), item.Name, font: GUI.SubHeadingFont)
{
TextAlignment = Alignment.Center,
AutoScaleHorizontal = true
};
var topFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.5f), paddedFrame.RectTransform), style: null); var topFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.5f), paddedFrame.RectTransform), style: null);
// === INPUT LABEL === // // === INPUT LABEL === //
@@ -55,22 +71,23 @@ namespace Barotrauma.Items.Components
// === INPUT SLOTS === // // === INPUT SLOTS === //
inputInventoryHolder = new GUIFrame(new RectTransform(new Vector2(0.7f, 1f), inputArea.RectTransform), style: null); inputInventoryHolder = new GUIFrame(new RectTransform(new Vector2(0.7f, 1f), inputArea.RectTransform), style: null);
inputInventoryOverlay = new GUICustomComponent(new RectTransform(Vector2.One, inputInventoryHolder.RectTransform), DrawOverLay, null) { CanBeFocused = false }; new GUICustomComponent(new RectTransform(Vector2.One, inputInventoryHolder.RectTransform), DrawOverLay, null) { CanBeFocused = false };
// === ACTIVATE BUTTON === // // === ACTIVATE BUTTON === //
var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.4f, 0.7f), inputArea.RectTransform), childAnchor: Anchor.CenterLeft); var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.4f, 0.8f), inputArea.RectTransform), childAnchor: Anchor.CenterLeft);
activateButton = new GUIButton(new RectTransform(new Vector2(0.95f, 0.8f), buttonContainer.RectTransform), TextManager.Get("DeconstructorDeconstruct"), style: "DeviceButton") activateButton = new GUIButton(new RectTransform(new Vector2(0.95f, 0.8f), buttonContainer.RectTransform), TextManager.Get("DeconstructorDeconstruct"), style: "DeviceButton")
{ {
TextBlock = { AutoScaleHorizontal = true }, TextBlock = { AutoScaleHorizontal = true },
OnClicked = ToggleActive OnClicked = ToggleActive
}; };
inSufficientPowerWarning = new GUITextBlock(new RectTransform(Vector2.One, activateButton.RectTransform), inSufficientPowerWarning = new GUITextBlock(new RectTransform(Vector2.One, activateButton.RectTransform),
TextManager.Get("DeconstructorNoPower"), textColor: GUI.Style.Orange, textAlignment: Alignment.Center, color: Color.Black, style: "OuterGlow") TextManager.Get("DeconstructorNoPower"), textColor: GUI.Style.Orange, textAlignment: Alignment.Center, color: Color.Black, style: "OuterGlow", wrap: true)
{ {
HoverColor = Color.Black, HoverColor = Color.Black,
IgnoreLayoutGroups = true, IgnoreLayoutGroups = true,
Visible = false, Visible = false,
CanBeFocused = false CanBeFocused = false,
AutoScaleHorizontal = true
}; };
// === OUTPUT AREA === // // === OUTPUT AREA === //
@@ -86,8 +103,70 @@ namespace Barotrauma.Items.Components
outputLabel.RectTransform.Resize(new Point((int) outputLabel.Font.MeasureString(outputLabel.Text).X, outputLabel.RectTransform.Rect.Height)); outputLabel.RectTransform.Resize(new Point((int) outputLabel.Font.MeasureString(outputLabel.Text).X, outputLabel.RectTransform.Rect.Height));
new GUIFrame(new RectTransform(Vector2.One, outputLabelArea.RectTransform), style: "HorizontalLine"); new GUIFrame(new RectTransform(Vector2.One, outputLabelArea.RectTransform), style: "HorizontalLine");
// === OUTPUT SLOTS === // var outputArea = new GUILayoutGroup(new RectTransform(new Vector2(1f, 1f), bottomFrame.RectTransform, Anchor.CenterLeft), childAnchor: Anchor.BottomLeft, isHorizontal: true) { Stretch = true, RelativeSpacing = 0.05f };
outputInventoryHolder = new GUIFrame(new RectTransform(new Vector2(1f, 1f), bottomFrame.RectTransform, Anchor.CenterLeft), style: null);
// === OUTPUT SLOTS === //
outputInventoryHolder = new GUIFrame(new RectTransform(new Vector2(1f - InfoAreaWidth, 1f), outputArea.RectTransform, Anchor.CenterLeft), style: null);
if (InfoAreaWidth >= 0.0f)
{
var infoAreaContainer = new GUILayoutGroup(new RectTransform(new Vector2(InfoAreaWidth, 0.8f), outputArea.RectTransform), childAnchor: Anchor.CenterLeft);
infoArea = new GUITextBlock(new RectTransform(new Vector2(0.95f, 0.95f), infoAreaContainer.RectTransform), string.Empty, wrap: true);
}
ActivateButton.OnAddedToGUIUpdateList += (GUIComponent component) =>
{
activateButton.Enabled = true;
if (string.IsNullOrEmpty(InfoText))
{
infoArea.Text = string.Empty;
}
else
{
infoArea.Text = TextManager.Get(InfoText, returnNull: true) ?? InfoText;
}
if (IsActive)
{
activateButton.Text = TextManager.Get("DeconstructorCancel");
infoArea.Text = string.Empty;
return;
}
bool outputsFound = false;
foreach (var (inputItem, deconstructItem) in GetAvailableOutputs(checkRequiredOtherItems: true))
{
outputsFound = true;
if (!string.IsNullOrEmpty(deconstructItem.ActivateButtonText))
{
string buttonText = TextManager.Get(deconstructItem.ActivateButtonText, returnNull: true) ?? deconstructItem.ActivateButtonText;
string infoText = string.Empty;
if (!string.IsNullOrEmpty(deconstructItem.InfoText))
{
infoText = TextManager.Get(deconstructItem.InfoText, returnNull: true) ?? deconstructItem.InfoText;
}
inputItem.GetComponent<GeneticMaterial>()?.ModifyDeconstructInfo(this, ref buttonText, ref infoText);
activateButton.Text = buttonText;
if (infoArea != null)
{
infoArea.Text = infoText;
}
return;
}
}
//no valid outputs found: check if we're missing some required items from the input slots and display a message about it if possible
if (!outputsFound && infoArea != null)
{
foreach (var (inputItem, deconstructItem) in GetAvailableOutputs(checkRequiredOtherItems: false))
{
if (deconstructItem.RequiredOtherItem.Any() && !string.IsNullOrEmpty(deconstructItem.InfoTextOnOtherItemMissing))
{
string missingItemName = TextManager.Get("entityname." + deconstructItem.RequiredOtherItem.First(), returnNull: true);
infoArea.Text = TextManager.GetWithVariable(deconstructItem.InfoTextOnOtherItemMissing, "[itemname]", missingItemName);
}
}
}
activateButton.Enabled = outputsFound;
activateButton.Text = TextManager.Get(ActivateButtonText);
};
} }
public override bool Select(Character character) public override bool Select(Character character)
@@ -126,13 +205,30 @@ namespace Barotrauma.Items.Components
private void DrawOverLay(SpriteBatch spriteBatch, GUICustomComponent overlayComponent) private void DrawOverLay(SpriteBatch spriteBatch, GUICustomComponent overlayComponent)
{ {
overlayComponent.RectTransform.SetAsLastChild(); overlayComponent.RectTransform.SetAsLastChild();
var lastSlot = inputContainer.Inventory.visualSlots.Last();
GUI.DrawRectangle(spriteBatch, if (!(inputContainer?.Inventory?.visualSlots is { } visualSlots)) { return; }
new Rectangle(
lastSlot.Rect.X, lastSlot.Rect.Y + (int)(lastSlot.Rect.Height * (1.0f - progressState)), if (DeconstructItemsSimultaneously)
lastSlot.Rect.Width, (int)(lastSlot.Rect.Height * progressState)), {
GUI.Style.Green * 0.5f, isFilled: true); for (int i = 0; i < InputContainer.Inventory.Capacity; i++)
{
if (InputContainer.Inventory.GetItemAt(i) == null) { continue; }
DrawProgressBar(InputContainer.Inventory.visualSlots[i]);
}
}
else
{
DrawProgressBar(inputContainer.Inventory.visualSlots.Last());
}
void DrawProgressBar(VisualSlot slot)
{
GUI.DrawRectangle(spriteBatch,
new Rectangle(
slot.Rect.X, slot.Rect.Y + (int)(slot.Rect.Height * (1.0f - progressState)),
slot.Rect.Width, (int)(slot.Rect.Height * progressState)),
GUI.Style.Green * 0.5f, isFilled: true);
}
} }
public override void UpdateHUD(Character character, float deltaTime, Camera cam) public override void UpdateHUD(Character character, float deltaTime, Camera cam)
@@ -37,7 +37,7 @@ namespace Barotrauma.Items.Components
private FabricationRecipe pendingFabricatedItem; private FabricationRecipe pendingFabricatedItem;
private Pair<Rectangle, string> tooltip; private (Rectangle area, string text)? tooltip;
private GUITextBlock requiredTimeBlock; private GUITextBlock requiredTimeBlock;
@@ -255,19 +255,22 @@ namespace Barotrauma.Items.Components
var item1 = c1.GUIComponent.UserData as FabricationRecipe; var item1 = c1.GUIComponent.UserData as FabricationRecipe;
var item2 = c2.GUIComponent.UserData as FabricationRecipe; var item2 = c2.GUIComponent.UserData as FabricationRecipe;
bool hasSkills1 = FabricationDegreeOfSuccess(character, item1.RequiredSkills) >= 0.5f; int itemPlacement1 = FabricationDegreeOfSuccess(character, item1.RequiredSkills) >= 0.5f ? 0 : -1;
bool hasSkills2 = FabricationDegreeOfSuccess(character, item2.RequiredSkills) >= 0.5f; int itemPlacement2 = FabricationDegreeOfSuccess(character, item2.RequiredSkills) >= 0.5f ? 0 : -1;
if (hasSkills1 != hasSkills2) itemPlacement1 += item1.RequiresRecipe && !character.HasRecipeForItem(item1.TargetItem.Identifier) ? -2 : 0;
itemPlacement2 += item2.RequiresRecipe && !character.HasRecipeForItem(item2.TargetItem.Identifier) ? -2 : 0;
if (itemPlacement1 != itemPlacement2)
{ {
return hasSkills1 ? -1 : 1; return itemPlacement1 > itemPlacement2 ? -1 : 1;
} }
return string.Compare(item1.DisplayName, item2.DisplayName); return string.Compare(item1.DisplayName, item2.DisplayName);
}); });
var sufficientSkillsText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), itemList.Content.RectTransform), var sufficientSkillsText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), itemList.Content.RectTransform),
TextManager.Get("fabricatorsufficientskills", returnNull: true) ?? "Sufficient skills to fabricate", textColor: GUI.Style.Green, font: GUI.SubHeadingFont) TextManager.Get("fabricatorsufficientskills"), textColor: GUI.Style.Green, font: GUI.SubHeadingFont)
{ {
AutoScaleHorizontal = true, AutoScaleHorizontal = true,
CanBeFocused = false CanBeFocused = false
@@ -275,7 +278,7 @@ namespace Barotrauma.Items.Components
sufficientSkillsText.RectTransform.SetAsFirstChild(); sufficientSkillsText.RectTransform.SetAsFirstChild();
var insufficientSkillsText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), itemList.Content.RectTransform), var insufficientSkillsText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), itemList.Content.RectTransform),
TextManager.Get("fabricatorinsufficientskills", returnNull: true) ?? "Insufficient skills to fabricate", textColor: Color.Orange, font: GUI.SubHeadingFont) TextManager.Get("fabricatorinsufficientskills"), textColor: Color.Orange, font: GUI.SubHeadingFont)
{ {
AutoScaleHorizontal = true, AutoScaleHorizontal = true,
CanBeFocused = false CanBeFocused = false
@@ -285,6 +288,18 @@ namespace Barotrauma.Items.Components
{ {
insufficientSkillsText.RectTransform.RepositionChildInHierarchy(itemList.Content.RectTransform.GetChildIndex(firstinSufficient.RectTransform)); insufficientSkillsText.RectTransform.RepositionChildInHierarchy(itemList.Content.RectTransform.GetChildIndex(firstinSufficient.RectTransform));
} }
var requiresRecipeText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), itemList.Content.RectTransform),
TextManager.Get("fabricatorrequiresrecipe"), textColor: Color.Red, font: GUI.SubHeadingFont)
{
AutoScaleHorizontal = true,
CanBeFocused = false
};
var firstRequiresRecipe = itemList.Content.Children.FirstOrDefault(c => c.UserData is FabricationRecipe fabricableItem && (fabricableItem.RequiresRecipe && !character.HasRecipeForItem(fabricableItem.TargetItem.Identifier)));
if (firstRequiresRecipe != null)
{
requiresRecipeText.RectTransform.RepositionChildInHierarchy(itemList.Content.RectTransform.GetChildIndex(firstRequiresRecipe.RectTransform));
}
} }
private void DrawInputOverLay(SpriteBatch spriteBatch, GUICustomComponent overlayComponent) private void DrawInputOverLay(SpriteBatch spriteBatch, GUICustomComponent overlayComponent)
@@ -297,6 +312,7 @@ namespace Barotrauma.Items.Components
int slotIndex = 0; int slotIndex = 0;
var missingItems = new List<FabricationRecipe.RequiredItem>(); var missingItems = new List<FabricationRecipe.RequiredItem>();
foreach (FabricationRecipe.RequiredItem requiredItem in targetItem.RequiredItems) foreach (FabricationRecipe.RequiredItem requiredItem in targetItem.RequiredItems)
{ {
for (int i = 0; i < requiredItem.Amount; i++) for (int i = 0; i < requiredItem.Amount; i++)
@@ -308,6 +324,8 @@ namespace Barotrauma.Items.Components
{ {
missingItems.Remove(missingItems.FirstOrDefault(mi => mi.ItemPrefabs.Contains(item.prefab))); missingItems.Remove(missingItems.FirstOrDefault(mi => mi.ItemPrefabs.Contains(item.prefab)));
} }
var missingCounts = missingItems.GroupBy(missingItem => missingItem).ToDictionary(x => x.Key, x => x.Count());
missingItems = missingItems.Distinct().ToList();
var availableIngredients = GetAvailableIngredients(); var availableIngredients = GetAvailableIngredients();
@@ -318,27 +336,27 @@ namespace Barotrauma.Items.Components
slotIndex++; slotIndex++;
} }
//highlight suitable ingredients in linked inventories requiredItem.ItemPrefabs
foreach (Item item in availableIngredients) .Where(requiredPrefab => availableIngredients.ContainsKey(requiredPrefab.Identifier))
{ .ForEach(requiredPrefab => {
if (item.ParentInventory != inputContainer.Inventory && IsItemValidIngredient(item, requiredItem)) var availablePrefabs = availableIngredients[requiredPrefab.Identifier];
{
int availableSlotIndex = item.ParentInventory.FindIndex(item); availablePrefabs
//slots are null if the inventory has never been displayed .Where(availablePrefab => availablePrefab.ParentInventory != inputContainer.Inventory)
//(linked item, but the UI is not set to be displayed at the same time) .Where(availablePrefab => availablePrefab.ParentInventory.visualSlots != null) //slots are null if the inventory has never been displayed
if (item.ParentInventory.visualSlots != null) .ForEach(availablePrefab => { //(linked item, but the UI is not set to be displayed at the same time)
{ int availableSlotIndex = availablePrefab.ParentInventory.FindIndex(availablePrefab);
if (item.ParentInventory.visualSlots[availableSlotIndex].HighlightTimer <= 0.0f)
{ if (availablePrefab.ParentInventory.visualSlots[availableSlotIndex].HighlightTimer <= 0.0f)
item.ParentInventory.visualSlots[availableSlotIndex].ShowBorderHighlight(GUI.Style.Green, 0.5f, 0.5f, 0.2f);
if (slotIndex < inputContainer.Capacity)
{ {
inputContainer.Inventory.visualSlots[slotIndex].ShowBorderHighlight(GUI.Style.Green, 0.5f, 0.5f, 0.2f); availablePrefab.ParentInventory.visualSlots[availableSlotIndex].ShowBorderHighlight(GUI.Style.Green, 0.5f, 0.5f, 0.2f);
if (slotIndex < inputContainer.Capacity)
{
inputContainer.Inventory.visualSlots[slotIndex].ShowBorderHighlight(GUI.Style.Green, 0.5f, 0.5f, 0.2f);
}
} }
} });
} });
}
}
if (slotIndex >= inputContainer.Capacity) { break; } if (slotIndex >= inputContainer.Capacity) { break; }
@@ -350,11 +368,32 @@ namespace Barotrauma.Items.Components
color: requiredItem.ItemPrefabs.First().InventoryIconColor * 0.3f, color: requiredItem.ItemPrefabs.First().InventoryIconColor * 0.3f,
scale: Math.Min(slotRect.Width / itemIcon.size.X, slotRect.Height / itemIcon.size.Y)); scale: Math.Min(slotRect.Width / itemIcon.size.X, slotRect.Height / itemIcon.size.Y));
if (missingCounts[requiredItem] > 1)
{
Vector2 stackCountPos = new Vector2(slotRect.Right, slotRect.Bottom);
string stackCountText = "x" + missingCounts[requiredItem];
stackCountPos -= GUI.SmallFont.MeasureString(stackCountText) + new Vector2(4, 2);
GUI.SmallFont.DrawString(spriteBatch, stackCountText, stackCountPos + Vector2.One, Color.Black);
GUI.SmallFont.DrawString(spriteBatch, stackCountText, stackCountPos, Color.White);
}
if (requiredItem.UseCondition && requiredItem.MinCondition < 1.0f) if (requiredItem.UseCondition && requiredItem.MinCondition < 1.0f)
{ {
GUI.DrawRectangle(spriteBatch, new Rectangle(slotRect.X, slotRect.Bottom - 8, slotRect.Width, 8), Color.Black * 0.8f, true); DrawConditionBar(spriteBatch, requiredItem.MinCondition);
}
else if (requiredItem.MaxCondition < 1.0f)
{
DrawConditionBar(spriteBatch, requiredItem.MaxCondition);
}
void DrawConditionBar(SpriteBatch sb, float condition)
{
int spacing = GUI.IntScale(4);
int height = GUI.IntScale(10);
GUI.DrawRectangle(spriteBatch, new Rectangle(slotRect.X + spacing, slotRect.Bottom - spacing - height, slotRect.Width - spacing * 2, height), Color.Black * 0.8f, true);
GUI.DrawRectangle(spriteBatch, GUI.DrawRectangle(spriteBatch,
new Rectangle(slotRect.X, slotRect.Bottom - 8, (int)(slotRect.Width * requiredItem.MinCondition), 8), new Rectangle(slotRect.X + spacing, slotRect.Bottom - spacing - height, (int)((slotRect.Width - spacing * 2) * condition), height),
GUI.Style.Green * 0.8f, true); GUI.Style.Green * 0.8f, true);
} }
@@ -367,6 +406,10 @@ namespace Barotrauma.Items.Components
{ {
toolTipText += " " + (int)Math.Round(requiredItem.MinCondition * 100) + "%"; toolTipText += " " + (int)Math.Round(requiredItem.MinCondition * 100) + "%";
} }
else if(requiredItem.MaxCondition < 1.0f)
{
toolTipText += " 0-" + (int)Math.Round(requiredItem.MaxCondition * 100) + "%";
}
else if (requiredItem.MaxCondition <= 0.0f) else if (requiredItem.MaxCondition <= 0.0f)
{ {
toolTipText = TextManager.GetWithVariable("displayname.emptyitem", "[itemname]", toolTipText); toolTipText = TextManager.GetWithVariable("displayname.emptyitem", "[itemname]", toolTipText);
@@ -375,7 +418,7 @@ namespace Barotrauma.Items.Components
{ {
toolTipText += '\n' + requiredItem.ItemPrefabs.First().Description; toolTipText += '\n' + requiredItem.ItemPrefabs.First().Description;
} }
tooltip = new Pair<Rectangle, string>(slotRect, toolTipText); tooltip = (slotRect, toolTipText);
} }
slotIndex++; slotIndex++;
@@ -415,7 +458,7 @@ namespace Barotrauma.Items.Components
if (tooltip != null) if (tooltip != null)
{ {
GUIComponent.DrawToolTip(spriteBatch, tooltip.Second, tooltip.First); GUIComponent.DrawToolTip(spriteBatch, tooltip.Value.text, tooltip.Value.area);
tooltip = null; tooltip = null;
} }
} }
@@ -435,6 +478,22 @@ namespace Barotrauma.Items.Components
if (recipe?.DisplayName == null) { continue; } if (recipe?.DisplayName == null) { continue; }
child.Visible = recipe.DisplayName.ToLower().Contains(filter); child.Visible = recipe.DisplayName.ToLower().Contains(filter);
} }
//go through the elements backwards, and disable the labels ("insufficient skills to fabricate", "recipe required...") if there's no items below them
bool recipeVisible = false;
foreach (GUIComponent child in itemList.Content.Children.Reverse())
{
if (!(child.UserData is FabricationRecipe recipe))
{
child.Visible = recipeVisible;
recipeVisible = false;
}
else
{
recipeVisible = child.Visible;
}
}
itemList.UpdateScrollBarSize(); itemList.UpdateScrollBarSize();
itemList.BarScroll = 0.0f; itemList.BarScroll = 0.0f;
@@ -470,14 +529,30 @@ namespace Barotrauma.Items.Components
}; };
}*/ }*/
string itemName = GetRecipeNameAndAmount(selectedItem);
string name = itemName;
float quality = GetFabricatedItemQuality(selectedItem, user);
if (quality > 0)
{
name = TextManager.GetWithVariable("itemname.quality" + (int)quality, "[itemname]", itemName + '\n', fallBackTag: "itemname.quality3");
}
var nameBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform), var nameBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform),
GetRecipeNameAndAmount(selectedItem), textAlignment: Alignment.CenterLeft, textColor: Color.Aqua, font: GUI.SubHeadingFont) name, textAlignment: Alignment.TopLeft, textColor: Color.Aqua, font: GUI.SubHeadingFont, parseRichText: true)
{ {
AutoScaleHorizontal = true AutoScaleHorizontal = true
}; };
nameBlock.Padding = new Vector4(0, nameBlock.Padding.Y, GUI.IntScale(5), nameBlock.Padding.W);
nameBlock.Padding = new Vector4(0, nameBlock.Padding.Y, nameBlock.Padding.Z, nameBlock.Padding.W); if (nameBlock.TextScale < 0.7f)
{
nameBlock.SetRichText(TextManager.GetWithVariable("itemname.quality" + (int)quality, "[itemname]", itemName, fallBackTag: "itemname.quality3"));
nameBlock.AutoScaleHorizontal = false;
nameBlock.TextScale = 0.7f;
nameBlock.Wrap = true;
nameBlock.SetTextPos();
nameBlock.RectTransform.MinSize = new Point(0, (int)(nameBlock.TextSize.Y * nameBlock.TextScale));
}
if (!string.IsNullOrWhiteSpace(selectedItem.TargetItem.Description)) if (!string.IsNullOrWhiteSpace(selectedItem.TargetItem.Description))
{ {
@@ -489,6 +564,7 @@ namespace Barotrauma.Items.Components
while (description.Rect.Height + nameBlock.Rect.Height > paddedFrame.Rect.Height) while (description.Rect.Height + nameBlock.Rect.Height > paddedFrame.Rect.Height)
{ {
var lines = description.WrappedText.Split('\n'); var lines = description.WrappedText.Split('\n');
if (lines.Length <= 1) { break; }
var newString = string.Join('\n', lines.Take(lines.Length - 1)); var newString = string.Join('\n', lines.Take(lines.Length - 1));
description.Text = newString.Substring(0, newString.Length - 4) + "..."; description.Text = newString.Substring(0, newString.Length - 4) + "...";
description.CalculateHeightFromText(); description.CalculateHeightFromText();
@@ -598,10 +674,15 @@ namespace Barotrauma.Items.Components
{ {
foreach (GUIComponent child in itemList.Content.Children) foreach (GUIComponent child in itemList.Content.Children)
{ {
var itemPrefab = child.UserData as FabricationRecipe; if (!(child.UserData is FabricationRecipe itemPrefab)) { continue; }
if (itemPrefab == null) continue;
bool canBeFabricated = CanBeFabricated(itemPrefab, availableIngredients); if (itemPrefab != selectedItem &&
(child.Rect.Y > itemList.Rect.Bottom || child.Rect.Bottom < itemList.Rect.Y))
{
continue;
}
bool canBeFabricated = CanBeFabricated(itemPrefab, availableIngredients, character);
if (itemPrefab == selectedItem) if (itemPrefab == selectedItem)
{ {
activateButton.Enabled = canBeFabricated; activateButton.Enabled = canBeFabricated;
File diff suppressed because it is too large Load Diff
@@ -142,6 +142,7 @@ namespace Barotrauma.Items.Components
partial void UpdateProjSpecific(float deltaTime) partial void UpdateProjSpecific(float deltaTime)
{ {
float rotationRad = MathHelper.ToRadians(item.Rotation);
if (FlowPercentage < 0.0f) if (FlowPercentage < 0.0f)
{ {
foreach (var (position, emitter) in pumpOutEmitters) foreach (var (position, emitter) in pumpOutEmitters)
@@ -150,11 +151,12 @@ namespace Barotrauma.Items.Components
//only emit "pump out" particles when underwater //only emit "pump out" particles when underwater
Vector2 relativeParticlePos = (item.WorldRect.Location.ToVector2() + position * item.Scale) - item.WorldPosition; Vector2 relativeParticlePos = (item.WorldRect.Location.ToVector2() + position * item.Scale) - item.WorldPosition;
float angle = 0.0f; relativeParticlePos = MathUtils.RotatePoint(relativeParticlePos, item.FlippedX ? rotationRad : -rotationRad);
float angle = -rotationRad;
if (item.FlippedX) if (item.FlippedX)
{ {
relativeParticlePos.X = -relativeParticlePos.X; relativeParticlePos.X = -relativeParticlePos.X;
angle = MathHelper.Pi; angle += MathHelper.Pi;
} }
if (item.FlippedY) if (item.FlippedY)
{ {
@@ -170,11 +172,12 @@ namespace Barotrauma.Items.Components
foreach (var (position, emitter) in pumpInEmitters) foreach (var (position, emitter) in pumpInEmitters)
{ {
Vector2 relativeParticlePos = (item.WorldRect.Location.ToVector2() + position * item.Scale) - item.WorldPosition; Vector2 relativeParticlePos = (item.WorldRect.Location.ToVector2() + position * item.Scale) - item.WorldPosition;
float angle = 0.0f; relativeParticlePos = MathUtils.RotatePoint(relativeParticlePos, item.FlippedX ? rotationRad : -rotationRad);
float angle = -rotationRad;
if (item.FlippedX) if (item.FlippedX)
{ {
relativeParticlePos.X = -relativeParticlePos.X; relativeParticlePos.X = -relativeParticlePos.X;
angle = MathHelper.Pi; angle += MathHelper.Pi;
} }
if (item.FlippedY) if (item.FlippedY)
{ {
@@ -134,6 +134,14 @@ namespace Barotrauma.Items.Components
private static string caveLabel; private static string caveLabel;
[Serialize(false, false)]
public bool RightLayout
{
get;
set;
}
private bool AllowUsingMineralScanner => private bool AllowUsingMineralScanner =>
HasMineralScanner && !isConnectedToSteering; HasMineralScanner && !isConnectedToSteering;
@@ -316,7 +324,7 @@ namespace Barotrauma.Items.Components
"", warningColor, GUI.LargeFont, Alignment.Center); "", warningColor, GUI.LargeFont, Alignment.Center);
// Setup layout for nav terminal // Setup layout for nav terminal
if (isConnectedToSteering) if (isConnectedToSteering || RightLayout)
{ {
controlContainer.RectTransform.RelativeOffset = controlBoxOffset; controlContainer.RectTransform.RelativeOffset = controlBoxOffset;
controlContainer.RectTransform.SetPosition(Anchor.TopRight); controlContainer.RectTransform.SetPosition(Anchor.TopRight);
@@ -446,11 +454,6 @@ namespace Barotrauma.Items.Components
zoomSlider.BarScroll += PlayerInput.ScrollWheelSpeed / 1000.0f; zoomSlider.BarScroll += PlayerInput.ScrollWheelSpeed / 1000.0f;
zoomSlider.OnMoved(zoomSlider, zoomSlider.BarScroll); zoomSlider.OnMoved(zoomSlider, zoomSlider.BarScroll);
} }
if (PlayerInput.KeyHit(InputType.Run))
{
SonarModeSwitch.OnClicked(SonarModeSwitch, null);
}
} }
float distort = 1.0f - item.Condition / item.MaxCondition; float distort = 1.0f - item.Condition / item.MaxCondition;
@@ -885,7 +888,7 @@ namespace Barotrauma.Items.Components
foreach (AITarget aiTarget in AITarget.List) foreach (AITarget aiTarget in AITarget.List)
{ {
if (!aiTarget.Enabled) { continue; } if (aiTarget.InDetectable) { continue; }
if (string.IsNullOrEmpty(aiTarget.SonarLabel) || aiTarget.SoundRange <= 0.0f) { continue; } if (string.IsNullOrEmpty(aiTarget.SonarLabel) || aiTarget.SoundRange <= 0.0f) { continue; }
if (Vector2.DistanceSquared(aiTarget.WorldPosition, transducerCenter) < aiTarget.SoundRange * aiTarget.SoundRange) if (Vector2.DistanceSquared(aiTarget.WorldPosition, transducerCenter) < aiTarget.SoundRange * aiTarget.SoundRange)
@@ -1239,7 +1242,7 @@ namespace Barotrauma.Items.Components
foreach (AITarget aiTarget in AITarget.List) foreach (AITarget aiTarget in AITarget.List)
{ {
float disruption = aiTarget.Entity is Character c ? c.Params.SonarDisruption : aiTarget.SonarDisruption; float disruption = aiTarget.Entity is Character c ? c.Params.SonarDisruption : aiTarget.SonarDisruption;
if (disruption <= 0.0f || !aiTarget.Enabled) { continue; } if (disruption <= 0.0f || aiTarget.InDetectable) { continue; }
float distSqr = Vector2.DistanceSquared(aiTarget.WorldPosition, pingSource); float distSqr = Vector2.DistanceSquared(aiTarget.WorldPosition, pingSource);
if (distSqr > worldPingRadiusSqr) { continue; } if (distSqr > worldPingRadiusSqr) { continue; }
float disruptionDist = (float)Math.Sqrt(distSqr); float disruptionDist = (float)Math.Sqrt(distSqr);
@@ -1364,28 +1367,6 @@ namespace Barotrauma.Items.Components
blipType : cell.IsDestructible ? BlipType.Destructible : BlipType.Default); blipType : cell.IsDestructible ? BlipType.Destructible : BlipType.Default);
} }
} }
foreach (RuinGeneration.Ruin ruin in Level.Loaded.Ruins)
{
if (!MathUtils.CircleIntersectsRectangle(pingSource, range, ruin.Area)) continue;
foreach (var ruinShape in ruin.RuinShapes)
{
foreach (RuinGeneration.Line wall in ruinShape.Walls)
{
float cellDot = Vector2.Dot(
Vector2.Normalize(ruinShape.Center - pingSource),
Vector2.Normalize((wall.A + wall.B) / 2.0f - ruinShape.Center));
if (cellDot > 0) continue;
CreateBlipsForLine(
wall.A, wall.B,
pingSource, transducerPos,
pingRadius, prevPingRadius,
100.0f, 1000.0f, range, pingStrength, passive);
}
}
}
} }
foreach (Item item in Item.ItemList) foreach (Item item in Item.ItemList)
@@ -1634,7 +1615,7 @@ namespace Barotrauma.Items.Components
void CalculateDistance() void CalculateDistance()
{ {
pathFinder ??= new PathFinder(WayPoint.WayPointList, indoorsSteering: false); pathFinder ??= new PathFinder(WayPoint.WayPointList, false);
var path = pathFinder.FindPath(ConvertUnits.ToSimUnits(transducerPosition), ConvertUnits.ToSimUnits(worldPosition)); var path = pathFinder.FindPath(ConvertUnits.ToSimUnits(transducerPosition), ConvertUnits.ToSimUnits(worldPosition));
if (!path.Unreachable) if (!path.Unreachable)
{ {
@@ -888,6 +888,7 @@ namespace Barotrauma.Items.Components
maintainPosOriginIndicator?.Remove(); maintainPosOriginIndicator?.Remove();
steeringIndicator?.Remove(); steeringIndicator?.Remove();
enterOutpostPrompt?.Close(); enterOutpostPrompt?.Close();
pathFinder = null;
} }
public void ClientWrite(IWriteMessage msg, object[] extraData = null) public void ClientWrite(IWriteMessage msg, object[] extraData = null)
@@ -71,7 +71,7 @@ namespace Barotrauma.Items.Components
var chargeText = new GUITextBlock(new RectTransform(new Vector2(0.6f, 1), textArea.RectTransform, Anchor.CenterRight), var chargeText = new GUITextBlock(new RectTransform(new Vector2(0.6f, 1), textArea.RectTransform, Anchor.CenterRight),
"", textColor: GUI.Style.TextColor, font: GUI.Font, textAlignment: Alignment.CenterRight) "", textColor: GUI.Style.TextColor, font: GUI.Font, textAlignment: Alignment.CenterRight)
{ {
TextGetter = () => $"{(int)charge}/{(int)capacity} {kWmin} ({((int)MathUtils.Percentage(charge, capacity)).ToString()} %)" TextGetter = () => $"{(int)Math.Round(charge)}/{(int)capacity} {kWmin} ({(int)Math.Round(MathUtils.Percentage(charge, capacity))} %)"
}; };
if (chargeText.TextSize.X > chargeText.Rect.Width) { chargeText.Font = GUI.SmallFont; } if (chargeText.TextSize.X > chargeText.Rect.Width) { chargeText.Font = GUI.SmallFont; }
@@ -0,0 +1,21 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
partial class Quality : ItemComponent
{
public override void AddTooltipInfo(ref string name, ref string description)
{
foreach (var statValue in statValues)
{
int roundedValue = (int)Math.Round(statValue.Value * qualityLevel * 100);
if (roundedValue == 0) { return; }
string colorStr = XMLExtensions.ColorToString(GUI.Style.Green);
description += $"\n ‖color:{colorStr}‖{roundedValue.ToString("+0;-#")}%‖color:end‖ {TextManager.Get("qualitystattypenames." + statValue.Key.ToString(), true) ?? statValue.Key.ToString()}";
}
}
}
}
@@ -0,0 +1,22 @@
using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma.Items.Components
{
partial class RemoteController : ItemComponent
{
public override void DrawHUD(SpriteBatch spriteBatch, Character character)
{
currentTarget?.DrawHUD(spriteBatch, Screen.Selected.Cam, character);
}
public override void UpdateHUD(Character character, float deltaTime, Camera cam)
{
currentTarget?.UpdateHUD(cam, character,deltaTime);
}
public override void AddToGUIUpdateList(int order = 0)
{
currentTarget?.AddToGUIUpdateList(order: -1);
}
}
}
@@ -15,16 +15,23 @@ namespace Barotrauma.Items.Components
public GUIButton SabotageButton { get; private set; } public GUIButton SabotageButton { get; private set; }
public GUIButton TinkerButton { get; private set; }
private GUIProgressBar progressBar; private GUIProgressBar progressBar;
private List<ParticleEmitter> particleEmitters = new List<ParticleEmitter>(); private GUITextBlock progressBarOverlayText;
private GUILayoutGroup extraButtonContainer;
private readonly List<ParticleEmitter> particleEmitters = new List<ParticleEmitter>();
//the corresponding particle emitter is active when the condition is within this range //the corresponding particle emitter is active when the condition is within this range
private List<Vector2> particleEmitterConditionRanges = new List<Vector2>(); private readonly List<Vector2> particleEmitterConditionRanges = new List<Vector2>();
private SoundChannel repairSoundChannel; private SoundChannel repairSoundChannel;
private string repairButtonText, repairingText; private string repairButtonText, repairingText;
private string sabotageButtonText, sabotagingText; private string sabotageButtonText, sabotagingText;
private string tinkerButtonText, tinkeringText;
private FixActions requestStartFixAction; private FixActions requestStartFixAction;
@@ -45,8 +52,24 @@ namespace Barotrauma.Items.Components
public override bool ShouldDrawHUD(Character character) public override bool ShouldDrawHUD(Character character)
{ {
if (!HasRequiredItems(character, false) || character.SelectedConstruction != item) return false; if (!HasRequiredItems(character, false) || character.SelectedConstruction != item) { return false; }
return item.ConditionPercentage < RepairThreshold || character.IsTraitor && item.ConditionPercentage > MinSabotageCondition || (CurrentFixer == character && (!item.IsFullCondition || (character.IsTraitor && item.ConditionPercentage > MinSabotageCondition))); if (character.IsTraitor && item.ConditionPercentage > MinSabotageCondition) { return true; }
float maxRepairConditionMultiplier = GetMaxRepairConditionMultiplier(character);
if (item.Condition / maxRepairConditionMultiplier < RepairThreshold) { return true; }
if (CurrentFixer == character)
{
float condition = item.Condition / item.MaxRepairConditionMultiplier;
float maxCondition = item.MaxCondition / item.MaxRepairConditionMultiplier;
if (condition < maxCondition * maxRepairConditionMultiplier)
{
return true;
}
}
if (IsTinkerable(character)) { return true; }
return false;
} }
partial void InitProjSpecific(XElement element) partial void InitProjSpecific(XElement element)
@@ -85,7 +108,7 @@ namespace Barotrauma.Items.Components
} }
} }
private void CreateGUI() protected override void CreateGUI()
{ {
var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.75f), GuiFrame.RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter) var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.75f), GuiFrame.RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter)
{ {
@@ -120,6 +143,11 @@ namespace Barotrauma.Items.Components
progressBar = new GUIProgressBar(new RectTransform(new Vector2(0.6f, 1.0f), progressBarHolder.RectTransform), progressBar = new GUIProgressBar(new RectTransform(new Vector2(0.6f, 1.0f), progressBarHolder.RectTransform),
color: GUI.Style.Green, barSize: 0.0f, style: "DeviceProgressBar"); color: GUI.Style.Green, barSize: 0.0f, style: "DeviceProgressBar");
progressBarOverlayText = new GUITextBlock(new RectTransform(Vector2.One, progressBar.RectTransform), string.Empty, font: GUI.SubHeadingFont, textAlignment: Alignment.Center)
{
IgnoreLayoutGroups = true
};
repairButtonText = TextManager.Get("RepairButton"); repairButtonText = TextManager.Get("RepairButton");
repairingText = TextManager.Get("Repairing"); repairingText = TextManager.Get("Repairing");
RepairButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), progressBarHolder.RectTransform, Anchor.TopCenter), repairButtonText) RepairButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), progressBarHolder.RectTransform, Anchor.TopCenter), repairButtonText)
@@ -135,9 +163,16 @@ namespace Barotrauma.Items.Components
progressBarHolder.RectTransform.MinSize = RepairButton.RectTransform.MinSize; progressBarHolder.RectTransform.MinSize = RepairButton.RectTransform.MinSize;
RepairButton.RectTransform.MinSize = new Point((int)(RepairButton.TextBlock.TextSize.X * 1.2f), RepairButton.RectTransform.MinSize.Y); RepairButton.RectTransform.MinSize = new Point((int)(RepairButton.TextBlock.TextSize.X * 1.2f), RepairButton.RectTransform.MinSize.Y);
extraButtonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.15f), paddedFrame.RectTransform), isHorizontal: true)
{
IgnoreLayoutGroups = true,
Stretch = true,
AbsoluteSpacing = GUI.IntScale(5)
};
sabotageButtonText = TextManager.Get("SabotageButton"); sabotageButtonText = TextManager.Get("SabotageButton");
sabotagingText = TextManager.Get("Sabotaging"); sabotagingText = TextManager.Get("Sabotaging");
SabotageButton = new GUIButton(new RectTransform(new Vector2(0.8f, 0.15f), paddedFrame.RectTransform, Anchor.BottomCenter), sabotageButtonText, style: "GUIButtonSmall") SabotageButton = new GUIButton(new RectTransform(Vector2.One, extraButtonContainer.RectTransform), sabotageButtonText, style: "GUIButtonSmall")
{ {
IgnoreLayoutGroups = true, IgnoreLayoutGroups = true,
Visible = false, Visible = false,
@@ -148,6 +183,22 @@ namespace Barotrauma.Items.Components
return true; return true;
} }
}; };
tinkerButtonText = TextManager.Get("TinkerButton", returnNull: true) ?? "Tinker";
tinkeringText = TextManager.Get("Tinkering", returnNull: true) ?? "Tinkering";
TinkerButton = new GUIButton(new RectTransform(Vector2.One, extraButtonContainer.RectTransform), tinkerButtonText, style: "GUIButtonSmall")
{
IgnoreLayoutGroups = true,
Visible = false,
OnClicked = (btn, obj) =>
{
requestStartFixAction = FixActions.Tinker;
item.CreateClientEvent(this);
return true;
}
};
extraButtonContainer.RectTransform.MinSize = new Point(0, SabotageButton.RectTransform.MinSize.Y);
} }
partial void UpdateProjSpecific(float deltaTime) partial void UpdateProjSpecific(float deltaTime)
@@ -176,6 +227,7 @@ namespace Barotrauma.Items.Components
{ {
case FixActions.Repair: case FixActions.Repair:
case FixActions.Sabotage: case FixActions.Sabotage:
case FixActions.Tinker:
StartRepairing(Character.Controlled, requestStartFixAction); StartRepairing(Character.Controlled, requestStartFixAction);
requestStartFixAction = FixActions.None; requestStartFixAction = FixActions.None;
break; break;
@@ -211,10 +263,24 @@ namespace Barotrauma.Items.Components
{ {
IsActive = true; IsActive = true;
progressBar.BarSize = item.Condition / item.MaxCondition; float defaultMaxCondition = (item.MaxCondition / item.MaxRepairConditionMultiplier);
progressBar.BarSize = item.Condition / defaultMaxCondition;
progressBar.Color = ToolBox.GradientLerp(progressBar.BarSize, GUI.Style.Red, GUI.Style.Orange, GUI.Style.Green); progressBar.Color = ToolBox.GradientLerp(progressBar.BarSize, GUI.Style.Red, GUI.Style.Orange, GUI.Style.Green);
RepairButton.Enabled = (currentFixerAction == FixActions.None || (CurrentFixer == character && currentFixerAction != FixActions.Repair)) && !item.IsFullCondition; if (item.Condition > defaultMaxCondition)
{
float extraCondition = item.MaxCondition * (item.MaxRepairConditionMultiplier - 1.0f);
progressBar.Color = ToolBox.GradientLerp((item.Condition - defaultMaxCondition) / extraCondition, GUI.Style.ColorReputationHigh, GUI.Style.ColorReputationVeryHigh);
progressBarOverlayText.Visible = true;
progressBarOverlayText.Text = $"{(int)Math.Round((item.Condition / defaultMaxCondition) * 100)}%";
}
else
{
progressBarOverlayText.Visible = false;
}
RepairButton.Enabled = (currentFixerAction == FixActions.None || (CurrentFixer == character && currentFixerAction != FixActions.Repair)) && !item.IsFullCondition && item.ConditionPercentage < RepairThreshold;
RepairButton.Text = (currentFixerAction == FixActions.None || CurrentFixer != character || currentFixerAction != FixActions.Repair) ? RepairButton.Text = (currentFixerAction == FixActions.None || CurrentFixer != character || currentFixerAction != FixActions.Repair) ?
repairButtonText : repairButtonText :
repairingText + new string('.', ((int)(Timing.TotalTime * 2.0f) % 3) + 1); repairingText + new string('.', ((int)(Timing.TotalTime * 2.0f) % 3) + 1);
@@ -226,7 +292,18 @@ namespace Barotrauma.Items.Components
sabotageButtonText : sabotageButtonText :
sabotagingText + new string('.', ((int)(Timing.TotalTime * 2.0f) % 3) + 1); sabotagingText + new string('.', ((int)(Timing.TotalTime * 2.0f) % 3) + 1);
TinkerButton.Visible = IsTinkerable(character);
TinkerButton.IgnoreLayoutGroups = !TinkerButton.Visible;
TinkerButton.Enabled = (currentFixerAction == FixActions.None || (CurrentFixer == character && currentFixerAction != FixActions.Tinker)) && CanTinker(character);
TinkerButton.Text = (currentFixerAction == FixActions.None || CurrentFixer != character || currentFixerAction != FixActions.Tinker) ?
tinkerButtonText :
tinkeringText + new string('.', ((int)(Timing.TotalTime * 2.0f) % 3) + 1);
System.Diagnostics.Debug.Assert(GuiFrame.GetChild(0) is GUILayoutGroup, "Repair UI hierarchy has changed, could not find skill texts"); System.Diagnostics.Debug.Assert(GuiFrame.GetChild(0) is GUILayoutGroup, "Repair UI hierarchy has changed, could not find skill texts");
extraButtonContainer.Visible = SabotageButton.Visible || TinkerButton.Visible;
extraButtonContainer.IgnoreLayoutGroups = !extraButtonContainer.Visible;
foreach (GUIComponent c in GuiFrame.GetChild(0).Children) foreach (GUIComponent c in GuiFrame.GetChild(0).Children)
{ {
if (!(c.UserData is Skill skill)) continue; if (!(c.UserData is Skill skill)) continue;
@@ -278,9 +355,12 @@ namespace Barotrauma.Items.Components
deteriorationTimer = msg.ReadSingle(); deteriorationTimer = msg.ReadSingle();
deteriorateAlwaysResetTimer = msg.ReadSingle(); deteriorateAlwaysResetTimer = msg.ReadSingle();
DeteriorateAlways = msg.ReadBoolean(); DeteriorateAlways = msg.ReadBoolean();
tinkeringDuration = msg.ReadSingle();
tinkeringStrength = msg.ReadSingle();
ushort currentFixerID = msg.ReadUInt16(); ushort currentFixerID = msg.ReadUInt16();
currentFixerAction = (FixActions)msg.ReadRangedInteger(0, 2); currentFixerAction = (FixActions)msg.ReadRangedInteger(0, 2);
CurrentFixer = currentFixerID != 0 ? Entity.FindEntityByID(currentFixerID) as Character : null; CurrentFixer = currentFixerID != 0 ? Entity.FindEntityByID(currentFixerID) as Character : null;
item.MaxRepairConditionMultiplier = GetMaxRepairConditionMultiplier(CurrentFixer);
} }
public void ClientWrite(IWriteMessage msg, object[] extraData = null) public void ClientWrite(IWriteMessage msg, object[] extraData = null)
@@ -31,6 +31,9 @@ namespace Barotrauma.Items.Components
set; set;
} }
[Serialize("0.5,0.5)", false)]
public Vector2 Origin { get; set; } = new Vector2(0.5f, 0.5f);
public Vector2 DrawSize public Vector2 DrawSize
{ {
get get
@@ -57,7 +60,6 @@ namespace Barotrauma.Items.Components
sourcePos = sourceLimb.body.DrawPosition; sourcePos = sourceLimb.body.DrawPosition;
} }
return sourcePos; return sourcePos;
} }
partial void InitProjSpecific(XElement element) partial void InitProjSpecific(XElement element)
@@ -81,13 +83,15 @@ namespace Barotrauma.Items.Components
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1) public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1)
{ {
if (target == null) { return; } if (target == null || target.Removed) { return; }
if (target.ParentInventory != null) { return; }
Vector2 startPos = GetSourcePos(); Vector2 startPos = GetSourcePos();
startPos.Y = -startPos.Y; startPos.Y = -startPos.Y;
if (source is Item sourceItem) if (source is Item sourceItem)
{ {
var turret = sourceItem?.GetComponent<Turret>(); var turret = sourceItem.GetComponent<Turret>();
var weapon = sourceItem.GetComponent<RangedWeapon>();
if (turret != null) if (turret != null)
{ {
startPos = new Vector2(sourceItem.WorldRect.X + turret.TransformedBarrelPos.X, -(sourceItem.WorldRect.Y - turret.TransformedBarrelPos.Y)); startPos = new Vector2(sourceItem.WorldRect.X + turret.TransformedBarrelPos.X, -(sourceItem.WorldRect.Y - turret.TransformedBarrelPos.Y));
@@ -96,8 +100,21 @@ namespace Barotrauma.Items.Components
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 += new Vector2((float)Math.Cos(turret.Rotation), (float)Math.Sin(turret.Rotation)) * turret.BarrelSprite.size.Y * turret.BarrelSprite.RelativeOrigin.Y * item.Scale * 0.9f;
} }
} }
else if (weapon != null)
{
Vector2 barrelPos = FarseerPhysics.ConvertUnits.ToDisplayUnits(weapon.TransformedBarrelPos);
barrelPos.Y = -barrelPos.Y;
startPos += barrelPos;
}
} }
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));
if (target.body.Dir < 0.0f)
{
flippedPos.X = -flippedPos.X;
}
endPos += Vector2.Transform(flippedPos, Matrix.CreateRotationZ(target.body.Rotation));
endPos.Y = -endPos.Y;
if (Snapped) if (Snapped)
{ {
@@ -0,0 +1,29 @@
using Barotrauma.Networking;
namespace Barotrauma.Items.Components
{
partial class Scanner : ItemComponent, IServerSerializable
{
partial void UpdateProjSpecific()
{
if (Holdable != null && Holdable.Attached && (AlwaysDisplayProgressBar || DisplayProgressBar) && !IsScanCompleted)
{
Character.Controlled?.UpdateHUDProgressBar(this,
item.WorldPosition,
ScanTimer / ScanDuration,
GUI.Style.Red, GUI.Style.Green,
textTag: "progressbar.scanning");
}
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{
bool wasScanCompletedPreviously = IsScanCompleted;
scanTimer = msg.ReadSingle();
if (!wasScanCompletedPreviously && IsScanCompleted)
{
OnScanCompleted?.Invoke(this);
}
}
}
}
@@ -0,0 +1,115 @@
using Barotrauma.Extensions;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
partial class ButtonTerminal : ItemComponent, IClientSerializable, IServerSerializable
{
private string[] terminalButtonStyles;
private GUIFrame containerHolder;
private GUIImage containerIndicator;
private GUIComponentStyle indicatorStyleRed, indicatorStyleGreen;
partial void InitProjSpecific(XElement element)
{
terminalButtonStyles = new string[RequiredSignalCount];
int i = 0;
foreach (var childElement in element.GetChildElements("TerminalButton"))
{
string style = childElement.GetAttributeString("style", null);
if (style == null) { continue; }
terminalButtonStyles[i++] = style;
}
indicatorStyleRed = GUI.Style.GetComponentStyle("IndicatorLightRed");
indicatorStyleGreen = GUI.Style.GetComponentStyle("IndicatorLightGreen");
CreateGUI();
}
protected override void CreateGUI()
{
var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.8f), GuiFrame.RectTransform, Anchor.Center), isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
Stretch = true,
RelativeSpacing = 0.08f
};
paddedFrame.OnAddedToGUIUpdateList += (component) =>
{
bool buttonsEnabled = AllowUsingButtons;
foreach (var child in component.Children)
{
if (!(child is GUIButton)) { continue; }
if (!(child.UserData is int)) { continue; }
child.Enabled = buttonsEnabled;
child.Children.ForEach(c => c.Enabled = buttonsEnabled);
}
bool itemsContained = Container.Inventory.AllItems.Any();
if (itemsContained)
{
var indicatorStyle = buttonsEnabled ? indicatorStyleGreen : indicatorStyleRed;
if (containerIndicator.Style != indicatorStyle)
{
containerIndicator.ApplyStyle(indicatorStyle);
}
}
containerIndicator.OverrideState = itemsContained ? GUIComponent.ComponentState.Selected : GUIComponent.ComponentState.None;
};
float x = 1.0f / (1 + RequiredSignalCount);
float y = Math.Min((x * paddedFrame.Rect.Width) / paddedFrame.Rect.Height, 0.5f);
Vector2 relativeSize = new Vector2(x, y);
var containerSection = new GUIFrame(new RectTransform(new Vector2(x, 1.0f), paddedFrame.RectTransform), style: null);
var containerSlot = new GUIFrame(new RectTransform(new Vector2(1.0f, y), containerSection.RectTransform, anchor: Anchor.Center), style: null);
containerHolder = new GUIFrame(new RectTransform(new Vector2(1f, 1.2f), containerSlot.RectTransform, Anchor.BottomCenter), style: null);
containerIndicator = new GUIImage(new RectTransform(new Vector2(0.5f, 0.5f * (1.0f - y)), containerSection.RectTransform, anchor: Anchor.BottomCenter),
style: "IndicatorLightRed", scaleToFit: true);
for (int i = 0; i < RequiredSignalCount; i++)
{
var button = new GUIButton(new RectTransform(relativeSize, paddedFrame.RectTransform), style: null)
{
UserData = i,
OnClicked = (button, userData) =>
{
if (GameMain.IsSingleplayer)
{
SendSignal((int)userData);
}
else
{
item.CreateClientEvent(this, new object[] { userData });
}
return true;
}
};
var image = new GUIImage(new RectTransform(Vector2.One, button.RectTransform), terminalButtonStyles[i], scaleToFit: true);
}
}
protected override void OnResolutionChanged()
{
base.OnResolutionChanged();
OnItemLoadedProjSpecific();
}
partial void OnItemLoadedProjSpecific()
{
Container.AllowUIOverlap = true;
Container.Inventory.RectTransform = containerHolder.RectTransform;
}
public void ClientWrite(IWriteMessage msg, object[] extraData = null)
{
Write(msg, extraData);
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{
SendSignal(msg.ReadRangedInteger(0, Signals.Length - 1), isServerMessage: true);
}
}
}
@@ -54,6 +54,11 @@ namespace Barotrauma.Items.Components
if (wireComponent != null) if (wireComponent != null)
{ {
equippedWire = wireComponent; equippedWire = wireComponent;
var connectedEnd = equippedWire.OtherConnection(null);
if (connectedEnd?.Item.Submarine != null && panel.Item.Submarine != connectedEnd.Item.Submarine)
{
equippedWire = null;
}
} }
} }
} }
@@ -88,11 +88,6 @@ namespace Barotrauma.Items.Components
return character == Character.Controlled && character == user && character.SelectedConstruction == item; return character == Character.Controlled && character == user && character.SelectedConstruction == item;
} }
public override void AddToGUIUpdateList()
{
GuiFrame?.AddToGUIUpdateList();
}
public override void UpdateHUD(Character character, float deltaTime, Camera cam) public override void UpdateHUD(Character character, float deltaTime, Camera cam)
{ {
if (character != Character.Controlled || character != user || character.SelectedConstruction != item) { return; } if (character != Character.Controlled || character != user || character.SelectedConstruction != item) { return; }
@@ -63,11 +63,11 @@ namespace Barotrauma.Items.Components
} }
OutputValue = input; OutputValue = input;
ShowOnDisplay(input); ShowOnDisplay(input, addToHistory: true);
item.SendSignal(input, "signal_out"); item.SendSignal(input, "signal_out");
} }
partial void ShowOnDisplay(string input, bool addToHistory = true) partial void ShowOnDisplay(string input, bool addToHistory)
{ {
if (addToHistory) if (addToHistory)
{ {
@@ -85,7 +85,7 @@ namespace Barotrauma.Items.Components
GUITextBlock newBlock = new GUITextBlock( GUITextBlock newBlock = new GUITextBlock(
new RectTransform(new Vector2(1, 0), historyBox.Content.RectTransform, anchor: Anchor.TopCenter), new RectTransform(new Vector2(1, 0), historyBox.Content.RectTransform, anchor: Anchor.TopCenter),
"> " + input, "> " + input,
textColor: Color.LimeGreen, wrap: true) textColor: Color.LimeGreen, wrap: true, font: UseMonospaceFont ? GUI.MonospacedFont : GUI.GlobalFont)
{ {
CanBeFocused = false CanBeFocused = false
}; };
@@ -118,9 +118,9 @@ namespace Barotrauma.Items.Components
// This method is overrided instead of the UpdateHUD method because this ensures the input box is selected // This method is overrided instead of the UpdateHUD method because this ensures the input box is selected
// even when the terminal component is selected for the very first time. Doing the input box selection in the // even when the terminal component is selected for the very first time. Doing the input box selection in the
// UpdateHUD method only selects the input box on every terminal selection except for the very first time. // UpdateHUD method only selects the input box on every terminal selection except for the very first time.
public override void AddToGUIUpdateList() public override void AddToGUIUpdateList(int order = 0)
{ {
base.AddToGUIUpdateList(); base.AddToGUIUpdateList(order: order);
if (shouldSelectInputBox) if (shouldSelectInputBox)
{ {
inputBox.Select(); inputBox.Select();
@@ -39,6 +39,34 @@ namespace Barotrauma.Items.Components
private set; private set;
} }
[Serialize(false, false)]
public bool ThermalGoggles
{
get;
private set;
}
[Serialize(true, false)]
public bool ShowDeadCharacters
{
get;
private set;
}
[Serialize(true, false)]
public bool ShowTexts
{
get;
private set;
}
[Serialize("72,119,72,120", false)]
public Color OverlayColor
{
get;
private set;
}
private readonly List<Character> visibleCharacters = new List<Character>(); private readonly List<Character> visibleCharacters = new List<Character>();
private const float UpdateInterval = 0.5f; private const float UpdateInterval = 0.5f;
@@ -48,6 +76,8 @@ namespace Barotrauma.Items.Components
private bool isEquippable; private bool isEquippable;
private float thermalEffectState;
public IEnumerable<Character> VisibleCharacters public IEnumerable<Character> VisibleCharacters
{ {
get get
@@ -81,6 +111,9 @@ namespace Barotrauma.Items.Components
refEntity = item; refEntity = item;
} }
thermalEffectState += deltaTime;
thermalEffectState %= 10000.0f;
if (updateTimer > 0.0f) if (updateTimer > 0.0f)
{ {
updateTimer -= deltaTime; updateTimer -= deltaTime;
@@ -91,6 +124,7 @@ namespace Barotrauma.Items.Components
foreach (Character c in Character.CharacterList) foreach (Character c in Character.CharacterList)
{ {
if (c == equipper || !c.Enabled || c.Removed) { continue; } if (c == equipper || !c.Enabled || c.Removed) { continue; }
if (!ShowDeadCharacters && c.IsDead) { continue; }
float dist = Vector2.DistanceSquared(refEntity.WorldPosition, c.WorldPosition); float dist = Vector2.DistanceSquared(refEntity.WorldPosition, c.WorldPosition);
if (dist < Range * Range) if (dist < Range * Range)
@@ -123,27 +157,71 @@ namespace Barotrauma.Items.Components
{ {
if (character == null) { return; } if (character == null) { return; }
GUI.UIGlow.Draw(spriteBatch, new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight), if (OverlayColor.A > 0)
Color.LightGreen * 0.5f);
Character closestCharacter = null;
float closestDist = float.PositiveInfinity;
foreach (Character c in visibleCharacters)
{ {
if (c == character || !c.Enabled || c.Removed) { continue; } GUI.UIGlow.Draw(spriteBatch, new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight), OverlayColor);
}
float dist = Vector2.DistanceSquared(GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition), c.WorldPosition); if (ShowTexts)
if (dist < closestDist) {
Character closestCharacter = null;
float closestDist = float.PositiveInfinity;
foreach (Character c in visibleCharacters)
{ {
closestCharacter = c; if (c == character || !c.Enabled || c.Removed) { continue; }
closestDist = dist;
float dist = Vector2.DistanceSquared(GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition), c.WorldPosition);
if (dist < closestDist)
{
closestCharacter = c;
closestDist = dist;
}
}
if (closestCharacter != null)
{
float dist = Vector2.Distance(GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition), closestCharacter.WorldPosition);
DrawCharacterInfo(spriteBatch, closestCharacter, 1.0f - MathHelper.Max((dist - (Range - FadeOutRange)) / FadeOutRange, 0.0f));
} }
} }
if (closestCharacter != null) if (ThermalGoggles)
{ {
float dist = Vector2.Distance(GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition), closestCharacter.WorldPosition); spriteBatch.End();
DrawCharacterInfo(spriteBatch, closestCharacter, 1.0f - MathHelper.Max((dist - (Range - FadeOutRange)) / FadeOutRange, 0.0f)); GameMain.LightManager.SolidColorEffect.Parameters["color"].SetValue(Color.Red.ToVector4() * (0.3f + MathF.Sin(thermalEffectState) * 0.05f));
GameMain.LightManager.SolidColorEffect.CurrentTechnique = GameMain.LightManager.SolidColorEffect.Techniques["SolidColorBlur"];
GameMain.LightManager.SolidColorEffect.Parameters["blurDistance"].SetValue(0.01f + MathF.Sin(thermalEffectState) * 0.005f);
GameMain.LightManager.SolidColorEffect.CurrentTechnique.Passes[0].Apply();
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, transformMatrix: Screen.Selected.Cam.Transform, effect: GameMain.LightManager.SolidColorEffect);
Entity refEntity = equipper;
if (!isEquippable || refEntity == null)
{
refEntity = item;
}
foreach (Character c in Character.CharacterList)
{
if (c == character || !c.Enabled || c.Removed || c.Params.HideInThermalGoggles) { continue; }
if (!ShowDeadCharacters && c.IsDead) { continue; }
float dist = Vector2.DistanceSquared(refEntity.WorldPosition, c.WorldPosition);
if (dist > Range * Range) { continue; }
Sprite pingCircle = GUI.Style.UIThermalGlow.Sprite;
foreach (Limb limb in c.AnimController.Limbs)
{
if (limb.Mass < 1.0f) { continue; }
float noise1 = PerlinNoise.GetPerlin((thermalEffectState + limb.Params.ID + c.ID) * 0.01f, (thermalEffectState + limb.Params.ID + c.ID) * 0.02f);
float noise2 = PerlinNoise.GetPerlin((thermalEffectState + limb.Params.ID + c.ID) * 0.01f, (thermalEffectState + limb.Params.ID + c.ID) * 0.008f);
Vector2 spriteScale = ConvertUnits.ToDisplayUnits(limb.body.GetSize()) / pingCircle.size * (noise1 * 0.5f + 2f);
Vector2 drawPos = new Vector2(limb.body.DrawPosition.X + (noise1 - 0.5f) * 100, -limb.body.DrawPosition.Y + (noise2 - 0.5f) * 100);
pingCircle.Draw(spriteBatch, drawPos, 0.0f, scale: Math.Max(spriteScale.X, spriteScale.Y));
}
}
spriteBatch.End();
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied);
} }
} }
@@ -177,6 +177,10 @@ namespace Barotrauma.Items.Components
partial void LaunchProjSpecific() partial void LaunchProjSpecific()
{ {
recoilTimer = RetractionTime; recoilTimer = RetractionTime;
if (user != null)
{
recoilTimer /= 1 + user.GetStatValue(StatTypes.TurretAttackSpeed);
}
PlaySound(ActionType.OnUse); PlaySound(ActionType.OnUse);
Vector2 particlePos = GetRelativeFiringPosition(UseFiringOffsetForMuzzleFlash); Vector2 particlePos = GetRelativeFiringPosition(UseFiringOffsetForMuzzleFlash);
foreach (ParticleEmitter emitter in particleEmitters) foreach (ParticleEmitter emitter in particleEmitters)
@@ -534,20 +538,20 @@ namespace Barotrauma.Items.Components
minRotationWidget.Draw(spriteBatch, (float)Timing.Step); minRotationWidget.Draw(spriteBatch, (float)Timing.Step);
maxRotationWidget.Draw(spriteBatch, (float)Timing.Step); maxRotationWidget.Draw(spriteBatch, (float)Timing.Step);
Vector2 GetDrawPos()
{
Vector2 drawPos = new Vector2(item.Rect.X + transformedBarrelPos.X, item.Rect.Y - transformedBarrelPos.Y);
if (item.Submarine != null) { drawPos += item.Submarine.DrawPosition; }
drawPos.Y = -drawPos.Y;
return drawPos;
}
void UpdateBarrel() void UpdateBarrel()
{ {
rotation = (minRotation + maxRotation) / 2; rotation = (minRotation + maxRotation) / 2;
} }
} }
public Vector2 GetDrawPos()
{
Vector2 drawPos = new Vector2(item.Rect.X + transformedBarrelPos.X, item.Rect.Y - transformedBarrelPos.Y);
if (item.Submarine != null) { drawPos += item.Submarine.DrawPosition; }
drawPos.Y = -drawPos.Y;
return drawPos;
}
private Widget GetWidget(string id, SpriteBatch spriteBatch, int size = 5, float thickness = 1f, Action<Widget> initMethod = null) private Widget GetWidget(string id, SpriteBatch spriteBatch, int size = 5, float thickness = 1f, Action<Widget> initMethod = null)
{ {
Vector2 offset = new Vector2(size / 2 + 5, -10); Vector2 offset = new Vector2(size / 2 + 5, -10);
@@ -5,17 +5,17 @@ namespace Barotrauma.Items.Components
{ {
partial class Wearable partial class Wearable
{ {
private void GetDamageModifierText(ref string description, float damageMultiplier, string afflictionIdentifier) private void GetDamageModifierText(ref string description, DamageModifier damageModifier, string afflictionIdentifier)
{ {
int roundedValue = (int)Math.Round((1 - damageMultiplier) * 100); int roundedValue = (int)Math.Round((1 - damageModifier.DamageMultiplier * damageModifier.ProbabilityMultiplier) * 100);
if (roundedValue == 0) { return; } if (roundedValue == 0) { return; }
string colorStr = XMLExtensions.ColorToString(GUI.Style.Green); string colorStr = XMLExtensions.ColorToString(GUI.Style.Green);
description += $"\n ‖color:{colorStr}‖{roundedValue.ToString("-0;+#")}%‖color:end‖ {AfflictionPrefab.List.FirstOrDefault(ap => ap.Identifier.Equals(afflictionIdentifier, StringComparison.OrdinalIgnoreCase))?.Name ?? afflictionIdentifier}"; description += $"\n ‖color:{colorStr}‖{roundedValue.ToString("-0;+#")}%‖color:end‖ {AfflictionPrefab.List.FirstOrDefault(ap => ap.Identifier.Equals(afflictionIdentifier, StringComparison.OrdinalIgnoreCase))?.Name ?? afflictionIdentifier}";
} }
public override void AddTooltipInfo(ref string description) public override void AddTooltipInfo(ref string name, ref string description)
{ {
if (damageModifiers.Any(d => !MathUtils.NearlyEqual(d.DamageMultiplier, 1f)) || SkillModifiers.Any()) if (damageModifiers.Any(d => !MathUtils.NearlyEqual(d.DamageMultiplier, 1f) || !MathUtils.NearlyEqual(d.ProbabilityMultiplier, 1f)) || SkillModifiers.Any())
{ {
description += "\n"; description += "\n";
} }
@@ -31,11 +31,11 @@ namespace Barotrauma.Items.Components
foreach (string afflictionIdentifier in damageModifier.ParsedAfflictionIdentifiers) foreach (string afflictionIdentifier in damageModifier.ParsedAfflictionIdentifiers)
{ {
GetDamageModifierText(ref description, damageModifier.DamageMultiplier, afflictionIdentifier); GetDamageModifierText(ref description, damageModifier, afflictionIdentifier);
} }
foreach (string afflictionIdentifier in damageModifier.ParsedAfflictionTypes) foreach (string afflictionIdentifier in damageModifier.ParsedAfflictionTypes)
{ {
GetDamageModifierText(ref description, damageModifier.DamageMultiplier, afflictionIdentifier); GetDamageModifierText(ref description, damageModifier, afflictionIdentifier);
} }
} }
} }
@@ -297,9 +297,10 @@ namespace Barotrauma
} }
} }
string name = item.Name;
foreach (ItemComponent component in item.Components) foreach (ItemComponent component in item.Components)
{ {
component.AddTooltipInfo(ref description); component.AddTooltipInfo(ref name, ref description);
} }
if (item.Prefab.ShowContentsInTooltip && item.OwnInventory != null) if (item.Prefab.ShowContentsInTooltip && item.OwnInventory != null)
@@ -315,12 +316,18 @@ namespace Barotrauma
string colorStr = XMLExtensions.ColorToString(!item.AllowStealing ? GUI.Style.Red : Color.White); string colorStr = XMLExtensions.ColorToString(!item.AllowStealing ? GUI.Style.Red : Color.White);
toolTip = $"‖color:{colorStr}‖{item.Name}‖color:end‖"; toolTip = $"‖color:{colorStr}‖{name}‖color:end‖";
if (item.GetComponent<Quality>() != null)
{
// substring by to get rid of the empty space at start, text file should be adjusted
toolTip += $"\n{TextManager.GetWithVariable("itemname.quality" + item.Quality, "[itemname]", "", fallBackTag: "itemname.quality3")?.Substring(1)}";
}
if (itemsInSlot.All(it => it.NonInteractable || it.NonPlayerTeamInteractable)) if (itemsInSlot.All(it => it.NonInteractable || it.NonPlayerTeamInteractable))
{ {
toolTip += " " + TextManager.Get("connectionlocked"); toolTip += " " + TextManager.Get("connectionlocked");
} }
if (!item.IsFullCondition && !item.Prefab.HideConditionBar) if (!item.IsFullCondition && !item.Prefab.HideConditionInTooltip)
{ {
string conditionColorStr = XMLExtensions.ColorToString(ToolBox.GradientLerp(item.Condition / item.MaxCondition, GUI.Style.ColorInventoryEmpty, GUI.Style.ColorInventoryHalf, GUI.Style.ColorInventoryFull)); string conditionColorStr = XMLExtensions.ColorToString(ToolBox.GradientLerp(item.Condition / item.MaxCondition, GUI.Style.ColorInventoryEmpty, GUI.Style.ColorInventoryHalf, GUI.Style.ColorInventoryFull));
toolTip += $"‖color:{conditionColorStr}‖ ({(int)item.ConditionPercentage} %)‖color:end‖"; toolTip += $"‖color:{conditionColorStr}‖ ({(int)item.ConditionPercentage} %)‖color:end‖";
@@ -478,7 +485,7 @@ namespace Barotrauma
} }
if (container == null) { return false; } if (container == null) { return false; }
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) protected virtual bool HideSlot(int i)
@@ -594,7 +601,10 @@ namespace Barotrauma
{ {
var slotRef = new SlotReference(this, slot, slotIndex, isSubSlot, slots[slotIndex].FirstOrDefault()?.GetComponent<ItemContainer>()?.Inventory); var slotRef = new SlotReference(this, slot, slotIndex, isSubSlot, slots[slotIndex].FirstOrDefault()?.GetComponent<ItemContainer>()?.Inventory);
if (Screen.Selected is SubEditorScreen editor && !editor.WiringMode && slotRef.ParentInventory is CharacterInventory) { return; } if (Screen.Selected is SubEditorScreen editor && !editor.WiringMode && slotRef.ParentInventory is CharacterInventory) { return; }
selectedSlot = slotRef; if (CanSelectSlot(slotRef))
{
selectedSlot = slotRef;
}
} }
if (!DraggingItems.Any()) if (!DraggingItems.Any())
@@ -667,6 +677,10 @@ namespace Barotrauma
if (subInventory.visualSlots == null) { subInventory.CreateSlots(); } if (subInventory.visualSlots == null) { subInventory.CreateSlots(); }
canMove = container.MovableFrame && !subInventory.IsInventoryHoverAvailable(Owner as Character, container) && subInventory.originalPos != Point.Zero; canMove = container.MovableFrame && !subInventory.IsInventoryHoverAvailable(Owner as Character, container) && subInventory.originalPos != Point.Zero;
if (this is CharacterInventory characterInventory && characterInventory.CurrentLayout != CharacterInventory.Layout.Default)
{
canMove = false;
}
if (canMove) if (canMove)
{ {
@@ -715,7 +729,7 @@ namespace Barotrauma
spacing = new Vector2(10 * UIScale, (10 + UnequippedIndicator.size.Y) * UIScale); spacing = new Vector2(10 * UIScale, (10 + UnequippedIndicator.size.Y) * UIScale);
} }
int columns = (int)Math.Max(Math.Floor(Math.Sqrt(itemCapacity)), 1); int columns = MathHelper.Clamp((int)Math.Floor(Math.Sqrt(itemCapacity)), 1, container.SlotsPerRow);
while (itemCapacity / columns * (subRect.Height + spacing.Y) > GameMain.GraphicsHeight * 0.5f) while (itemCapacity / columns * (subRect.Height + spacing.Y) > GameMain.GraphicsHeight * 0.5f)
{ {
columns++; columns++;
@@ -826,11 +840,23 @@ namespace Barotrauma
return rect.Contains(PlayerInput.MousePosition); return rect.Contains(PlayerInput.MousePosition);
} }
public static bool IsMouseOnInventory
{
get; private set;
}
/// <summary>
/// Refresh the value of IsMouseOnInventory
/// </summary>
public static void RefreshMouseOnInventory()
{
IsMouseOnInventory = DetermineMouseOnInventory();
}
/// <summary> /// <summary>
/// Is the mouse on any inventory element (slot, equip button, subinventory...) /// Is the mouse on any inventory element (slot, equip button, subinventory...)
/// </summary> /// </summary>
/// <returns></returns> private static bool DetermineMouseOnInventory(bool ignoreDraggedItem = false)
public static bool IsMouseOnInventory(bool ignoreDraggedItem = false)
{ {
if (GameMain.GameSession?.Campaign != null && if (GameMain.GameSession?.Campaign != null &&
(GameMain.GameSession.Campaign.ShowCampaignUI || GameMain.GameSession.Campaign.ForceMapUI)) (GameMain.GameSession.Campaign.ShowCampaignUI || GameMain.GameSession.Campaign.ForceMapUI))
@@ -1112,7 +1138,7 @@ namespace Barotrauma
{ {
Character.Controlled.ClearInputs(); Character.Controlled.ClearInputs();
if (!IsMouseOnInventory(ignoreDraggedItem: true) && if (!DetermineMouseOnInventory(ignoreDraggedItem: true) &&
CharacterHealth.OpenHealthWindow != null) CharacterHealth.OpenHealthWindow != null)
{ {
bool dropSuccessful = false; bool dropSuccessful = false;
@@ -1279,34 +1305,52 @@ namespace Barotrauma
DraggingItems.Clear(); DraggingItems.Clear();
} }
if (selectedSlot != null) if (selectedSlot != null && !CanSelectSlot(selectedSlot))
{ {
if (!selectedSlot.Slot.MouseOn()) selectedSlot = null;
{
selectedSlot = null;
}
else
{
var rootOwner = (selectedSlot.ParentInventory?.Owner as Item)?.GetRootInventoryOwner();
if (selectedSlot.ParentInventory?.Owner != Character.Controlled &&
selectedSlot.ParentInventory?.Owner != Character.Controlled.SelectedCharacter &&
selectedSlot.ParentInventory?.Owner != Character.Controlled.SelectedConstruction &&
!(Character.Controlled.SelectedConstruction?.linkedTo.Contains(selectedSlot.ParentInventory?.Owner) ?? false) &&
rootOwner != Character.Controlled &&
rootOwner != Character.Controlled.SelectedCharacter &&
rootOwner != Character.Controlled.SelectedConstruction &&
!(Character.Controlled.SelectedConstruction?.linkedTo.Contains(rootOwner) ?? false))
{
selectedSlot = null;
}
}
} }
} }
private static bool CanSelectSlot(SlotReference selectedSlot)
{
if (!selectedSlot.Slot.MouseOn())
{
return false;
}
else
{
var rootOwner = (selectedSlot.ParentInventory?.Owner as Item)?.GetRootInventoryOwner();
if (selectedSlot.ParentInventory?.Owner != Character.Controlled &&
selectedSlot.ParentInventory?.Owner != Character.Controlled.SelectedCharacter &&
selectedSlot.ParentInventory?.Owner != Character.Controlled.SelectedConstruction &&
!(Character.Controlled.SelectedConstruction?.linkedTo.Contains(selectedSlot.ParentInventory?.Owner) ?? false) &&
rootOwner != Character.Controlled &&
rootOwner != Character.Controlled.SelectedCharacter &&
rootOwner != Character.Controlled.SelectedConstruction &&
!(Character.Controlled.SelectedConstruction?.linkedTo.Contains(rootOwner) ?? false))
{
return false;
}
var parentItem = (selectedSlot?.ParentInventory?.Owner as Item) ?? selectedSlot?.Item;
if ((parentItem?.GetRootInventoryOwner() is Character ownerCharacter) &&
ownerCharacter == Character.Controlled &&
CharacterHealth.OpenHealthWindow?.Character != ownerCharacter &&
ownerCharacter.Inventory.IsInLimbSlot(parentItem, InvSlotType.HealthInterface))
{
highlightedSubInventorySlots.RemoveWhere(s => s.Item == parentItem);
return false;
}
}
return true;
}
protected static Rectangle GetSubInventoryHoverArea(SlotReference subSlot) protected static Rectangle GetSubInventoryHoverArea(SlotReference subSlot)
{ {
Rectangle hoverArea; Rectangle hoverArea;
if (!subSlot.Inventory.Movable() || Character.Controlled?.Inventory == subSlot.ParentInventory && !Character.Controlled.HasEquippedItem(subSlot.Item)) if (!subSlot.Inventory.Movable() ||
(Character.Controlled?.Inventory == subSlot.ParentInventory && !Character.Controlled.HasEquippedItem(subSlot.Item)) ||
(subSlot.ParentInventory is CharacterInventory characterInventory && characterInventory.CurrentLayout != CharacterInventory.Layout.Default))
{ {
hoverArea = subSlot.Slot.Rect; hoverArea = subSlot.Slot.Rect;
hoverArea.Location += subSlot.Slot.DrawOffset.ToPoint(); hoverArea.Location += subSlot.Slot.DrawOffset.ToPoint();
@@ -1375,7 +1419,7 @@ namespace Barotrauma
float scale = Math.Min(Math.Min(iconSize / sprite.size.X, iconSize / sprite.size.Y), 1.5f); float scale = Math.Min(Math.Min(iconSize / sprite.size.X, iconSize / sprite.size.Y), 1.5f);
Vector2 itemPos = PlayerInput.MousePosition; Vector2 itemPos = PlayerInput.MousePosition;
bool mouseOnHealthInterface = CharacterHealth.OpenHealthWindow != null && CharacterHealth.OpenHealthWindow.MouseOnElement; bool mouseOnHealthInterface = CharacterHealth.OpenHealthWindow != null && CharacterHealth.OpenHealthWindow.MouseOnElement && DraggingItems.Any(it => it.UseInHealthInterface);
if ((GUI.MouseOn == null || mouseOnHealthInterface) && selectedSlot == null) if ((GUI.MouseOn == null || mouseOnHealthInterface) && selectedSlot == null)
{ {
@@ -1434,7 +1478,8 @@ namespace Barotrauma
} }
Color slotColor = Color.White; Color slotColor = Color.White;
if (inventory?.Owner is Item i && !i.IsPlayerTeamInteractable) { slotColor = Color.Gray; } Item parentItem = inventory?.Owner as Item;
if (parentItem != null && !parentItem.IsPlayerTeamInteractable) { slotColor = Color.Gray; }
var itemContainer = item?.GetComponent<ItemContainer>(); var itemContainer = item?.GetComponent<ItemContainer>();
if (itemContainer != null && (itemContainer.InventoryTopSprite != null || itemContainer.InventoryBottomSprite != null)) if (itemContainer != null && (itemContainer.InventoryTopSprite != null || itemContainer.InventoryBottomSprite != null))
{ {
@@ -1513,7 +1558,7 @@ namespace Barotrauma
var indicatorStyle = GUI.Style.GetComponentStyle("ContainedStateIndicator.Default"); var indicatorStyle = GUI.Style.GetComponentStyle("ContainedStateIndicator.Default");
Sprite indicatorSprite = indicatorStyle?.GetDefaultSprite(); Sprite indicatorSprite = indicatorStyle?.GetDefaultSprite();
Sprite emptyIndicatorSprite = indicatorStyle?.GetSprite(GUIComponent.ComponentState.Hover); Sprite emptyIndicatorSprite = indicatorStyle?.GetSprite(GUIComponent.ComponentState.Hover);
DrawItemStateIndicator(spriteBatch, inventory, indicatorSprite, emptyIndicatorSprite, conditionIndicatorArea, item.Condition / item.MaxCondition); DrawItemStateIndicator(spriteBatch, inventory, indicatorSprite, emptyIndicatorSprite, conditionIndicatorArea, item.Condition / item.MaxCondition);
} }
if (itemContainer != null && itemContainer.ShowContainedStateIndicator) if (itemContainer != null && itemContainer.ShowContainedStateIndicator)
@@ -1525,14 +1570,14 @@ namespace Barotrauma
} }
else else
{ {
var containedItem = itemContainer.Inventory.slots[0].FirstOrDefault(); var containedItem = itemContainer.Inventory.slots[Math.Max(itemContainer.ContainedStateIndicatorSlot, 0)].FirstOrDefault();
containedState = itemContainer.Inventory.Capacity == 1 ? containedState = itemContainer.Inventory.Capacity == 1 || itemContainer.ContainedStateIndicatorSlot > -1 ?
(containedItem == null ? 0.0f : containedItem.Condition / containedItem.MaxCondition) : (containedItem == null ? 0.0f : containedItem.Condition / containedItem.MaxCondition) :
itemContainer.Inventory.slots.Count(i => !i.Empty()) / (float)itemContainer.Inventory.capacity; itemContainer.Inventory.slots.Count(i => !i.Empty()) / (float)itemContainer.Inventory.capacity;
if (containedItem != null && itemContainer.Inventory.Capacity == 1) if (containedItem != null && itemContainer.Inventory.Capacity == 1)
{ {
int maxStackSize = Math.Min(containedItem.Prefab.MaxStackSize, itemContainer.MaxStackSize); int maxStackSize = Math.Min(containedItem.Prefab.MaxStackSize, itemContainer.GetMaxStackSize(0));
if (maxStackSize > 1) if (maxStackSize > 1 || containedItem.Prefab.HideConditionBar)
{ {
containedState = itemContainer.Inventory.slots[0].ItemCount / (float)maxStackSize; containedState = itemContainer.Inventory.slots[0].ItemCount / (float)maxStackSize;
} }
@@ -1556,6 +1601,27 @@ namespace Barotrauma
DrawItemStateIndicator(spriteBatch, inventory, indicatorSprite, emptyIndicatorSprite, containedIndicatorArea, containedState, DrawItemStateIndicator(spriteBatch, inventory, indicatorSprite, emptyIndicatorSprite, containedIndicatorArea, containedState,
pulsate: !usingDefaultSprite && containedState >= 0.0f && containedState < 0.25f && inventory == Character.Controlled?.Inventory && Character.Controlled.HasEquippedItem(item)); pulsate: !usingDefaultSprite && containedState >= 0.0f && containedState < 0.25f && inventory == Character.Controlled?.Inventory && Character.Controlled.HasEquippedItem(item));
} }
if (item.Quality != 0)
{
var style = GUI.Style.GetComponentStyle("InnerGlowSmall");
if (style == null)
{
GUI.DrawRectangle(spriteBatch, rect, GUI.Style.GetQualityColor(item.Quality) * 0.7f);
}
else
{
style.Sprites[GUIComponent.ComponentState.None].FirstOrDefault()?.Draw(spriteBatch, rect, GUI.Style.GetQualityColor(item.Quality) * 0.5f);
}
}
}
else
{
var slotIcon = parentItem?.GetComponent<ItemContainer>()?.GetSlotIcon(slotIndex);
if (slotIcon != null)
{
slotIcon.Draw(spriteBatch, rect.Center.ToVector2(), GUI.Style.EquipmentSlotIconColor, scale: Math.Min(rect.Width / slotIcon.size.X, rect.Height / slotIcon.size.Y) * 0.8f);
}
} }
} }
@@ -1590,7 +1656,7 @@ namespace Barotrauma
Color spriteColor = sprite == item.Sprite ? item.GetSpriteColor() : item.GetInventoryIconColor(); Color spriteColor = sprite == item.Sprite ? item.GetSpriteColor() : item.GetInventoryIconColor();
if (inventory != null && (inventory.Locked || inventory.slots[slotIndex].Items.All(it => it.NonInteractable || it.NonPlayerTeamInteractable))) { spriteColor *= 0.5f; } if (inventory != null && (inventory.Locked || inventory.slots[slotIndex].Items.All(it => it.NonInteractable || it.NonPlayerTeamInteractable))) { spriteColor *= 0.5f; }
if (CharacterHealth.OpenHealthWindow != null && !item.UseInHealthInterface) if (CharacterHealth.OpenHealthWindow != null && !item.UseInHealthInterface && !item.AllowedSlots.Contains(InvSlotType.HealthInterface) && item.GetComponent<GeneticMaterial>() == null)
{ {
spriteColor = Color.Lerp(spriteColor, Color.TransparentBlack, 0.5f); spriteColor = Color.Lerp(spriteColor, Color.TransparentBlack, 0.5f);
} }
@@ -1611,9 +1677,9 @@ namespace Barotrauma
scale: iconSize.X / stealIcon.size.X); scale: iconSize.X / stealIcon.size.X);
} }
int maxStackSize = item.Prefab.MaxStackSize; int maxStackSize = item.Prefab.MaxStackSize;
if (item.Container != null) if (inventory is ItemInventory itemInventory)
{ {
maxStackSize = Math.Min(maxStackSize, item.Container.GetComponent<ItemContainer>()?.MaxStackSize ?? maxStackSize); maxStackSize = Math.Min(maxStackSize, itemInventory.Container.GetMaxStackSize(slotIndex));
} }
if (maxStackSize > 1 && inventory != null) if (maxStackSize > 1 && inventory != null)
{ {
@@ -276,7 +276,7 @@ namespace Barotrauma
BrokenItemSprite fadeInBrokenSprite = null; BrokenItemSprite fadeInBrokenSprite = null;
float fadeInBrokenSpriteAlpha = 0.0f; float fadeInBrokenSpriteAlpha = 0.0f;
float displayCondition = FakeBroken ? 0.0f : condition; float displayCondition = FakeBroken ? 0.0f : ConditionPercentage;
Vector2 drawOffset = Vector2.Zero; Vector2 drawOffset = Vector2.Zero;
if (displayCondition < MaxCondition) if (displayCondition < MaxCondition)
{ {
@@ -326,13 +326,18 @@ namespace Barotrauma
size, color: color, size, color: color,
textureScale: Vector2.One * Scale, textureScale: Vector2.One * Scale,
depth: depth); depth: depth);
fadeInBrokenSprite?.Sprite.DrawTiled(spriteBatch, new Vector2(DrawPosition.X - rect.Width / 2, -(DrawPosition.Y + rect.Height / 2)) + fadeInBrokenSprite.Offset.ToVector2() * Scale, size, color: color * fadeInBrokenSpriteAlpha,
textureScale: Vector2.One * Scale, if (fadeInBrokenSprite != null)
depth: depth - 0.000001f); {
float d = Math.Min(depth + (fadeInBrokenSprite.Sprite.Depth - activeSprite.Depth - 0.000001f), 0.999f);
fadeInBrokenSprite.Sprite.DrawTiled(spriteBatch, new Vector2(DrawPosition.X - rect.Width / 2, -(DrawPosition.Y + rect.Height / 2)) + fadeInBrokenSprite.Offset.ToVector2() * Scale, size, color: color * fadeInBrokenSpriteAlpha,
textureScale: Vector2.One * Scale,
depth: d);
}
foreach (var decorativeSprite in Prefab.DecorativeSprites) foreach (var decorativeSprite in Prefab.DecorativeSprites)
{ {
if (!spriteAnimState[decorativeSprite].IsActive) { continue; } if (!spriteAnimState[decorativeSprite].IsActive) { continue; }
Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier, -rotationRad) * Scale; Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier, flippedX && Prefab.CanSpriteFlipX ? rotationRad : -rotationRad) * Scale;
if (flippedX && Prefab.CanSpriteFlipX) { offset.X = -offset.X; } if (flippedX && Prefab.CanSpriteFlipX) { offset.X = -offset.X; }
if (flippedY && Prefab.CanSpriteFlipY) { offset.Y = -offset.Y; } if (flippedY && Prefab.CanSpriteFlipY) { offset.Y = -offset.Y; }
decorativeSprite.Sprite.DrawTiled(spriteBatch, decorativeSprite.Sprite.DrawTiled(spriteBatch,
@@ -357,7 +362,11 @@ namespace Barotrauma
if (color.A > 0) if (color.A > 0)
{ {
activeSprite.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y) + drawOffset, color, origin, rotationRad, Scale, activeSprite.effects, depth); activeSprite.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y) + drawOffset, color, origin, rotationRad, Scale, activeSprite.effects, depth);
fadeInBrokenSprite?.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y) + fadeInBrokenSprite.Offset.ToVector2() * Scale, color * fadeInBrokenSpriteAlpha, origin, rotationRad, Scale, activeSprite.effects, depth - 0.000001f); if (fadeInBrokenSprite != null)
{
float d = Math.Min(depth + (fadeInBrokenSprite.Sprite.Depth - activeSprite.Depth - 0.000001f), 0.999f);
fadeInBrokenSprite.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y) + fadeInBrokenSprite.Offset.ToVector2() * Scale, color * fadeInBrokenSpriteAlpha, origin, rotationRad, Scale, activeSprite.effects, d);
}
} }
if (Infector != null && (Infector.ParentBallastFlora.HasBrokenThrough || BallastFloraBehavior.AlwaysShowBallastFloraSprite)) if (Infector != null && (Infector.ParentBallastFlora.HasBrokenThrough || BallastFloraBehavior.AlwaysShowBallastFloraSprite))
{ {
@@ -368,7 +377,7 @@ namespace Barotrauma
{ {
if (!spriteAnimState[decorativeSprite].IsActive) { continue; } if (!spriteAnimState[decorativeSprite].IsActive) { continue; }
float rot = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState, spriteAnimState[decorativeSprite].RandomRotationFactor); float rot = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState, spriteAnimState[decorativeSprite].RandomRotationFactor);
Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier, -rotationRad) * Scale; Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier, flippedX && Prefab.CanSpriteFlipX ? rotationRad : -rotationRad) * Scale;
if (flippedX && Prefab.CanSpriteFlipX) { offset.X = -offset.X; } if (flippedX && Prefab.CanSpriteFlipX) { offset.X = -offset.X; }
if (flippedY && Prefab.CanSpriteFlipY) { offset.Y = -offset.Y; } if (flippedY && Prefab.CanSpriteFlipY) { offset.Y = -offset.Y; }
decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X + offset.X, -(DrawPosition.Y + offset.Y)), color, decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X + offset.X, -(DrawPosition.Y + offset.Y)), color,
@@ -410,8 +419,11 @@ namespace Barotrauma
} }
} }
body.Draw(spriteBatch, activeSprite, color, depth, Scale); body.Draw(spriteBatch, activeSprite, color, depth, Scale);
if (fadeInBrokenSprite != null) { body.Draw(spriteBatch, fadeInBrokenSprite.Sprite, color * fadeInBrokenSpriteAlpha, depth - 0.000001f, Scale); } if (fadeInBrokenSprite != null)
{
float d = Math.Min(depth + (fadeInBrokenSprite.Sprite.Depth - activeSprite.Depth - 0.000001f), 0.999f);
body.Draw(spriteBatch, fadeInBrokenSprite.Sprite, color * fadeInBrokenSpriteAlpha, d, Scale);
}
foreach (var decorativeSprite in Prefab.DecorativeSprites) foreach (var decorativeSprite in Prefab.DecorativeSprites)
{ {
if (!spriteAnimState[decorativeSprite].IsActive) { continue; } if (!spriteAnimState[decorativeSprite].IsActive) { continue; }
@@ -464,6 +476,11 @@ namespace Barotrauma
if (GameMain.DebugDraw) if (GameMain.DebugDraw)
{ {
body?.DebugDraw(spriteBatch, Color.White); body?.DebugDraw(spriteBatch, Color.White);
if (GetComponent<TriggerComponent>()?.PhysicsBody is PhysicsBody triggerBody)
{
triggerBody.UpdateDrawPosition();
triggerBody.DebugDraw(spriteBatch, Color.White);
}
} }
if (editing && IsSelected && PlayerInput.KeyDown(Keys.Space)) if (editing && IsSelected && PlayerInput.KeyDown(Keys.Space))
@@ -704,6 +721,7 @@ namespace Barotrauma
new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityX"), style: "GUIButtonSmall") new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityX"), style: "GUIButtonSmall")
{ {
ToolTip = TextManager.Get("MirrorEntityXToolTip"), ToolTip = TextManager.Get("MirrorEntityXToolTip"),
Enabled = Prefab.CanFlipX,
OnClicked = (button, data) => OnClicked = (button, data) =>
{ {
foreach (MapEntity me in SelectedList) foreach (MapEntity me in SelectedList)
@@ -717,6 +735,7 @@ namespace Barotrauma
new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityY"), style: "GUIButtonSmall") new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityY"), style: "GUIButtonSmall")
{ {
ToolTip = TextManager.Get("MirrorEntityYToolTip"), ToolTip = TextManager.Get("MirrorEntityYToolTip"),
Enabled = Prefab.CanFlipY,
OnClicked = (button, data) => OnClicked = (button, data) =>
{ {
foreach (MapEntity me in SelectedList) foreach (MapEntity me in SelectedList)
@@ -1068,7 +1087,7 @@ namespace Barotrauma
foreach (Character otherCharacter in Character.CharacterList) foreach (Character otherCharacter in Character.CharacterList)
{ {
if (otherCharacter != character && if (otherCharacter != character &&
otherCharacter.SelectedConstruction == character.SelectedConstruction) otherCharacter.SelectedConstruction == this)
{ {
ItemInUseWarning.Visible = true; ItemInUseWarning.Visible = true;
if (mergedHUDRect.Width > GameMain.GraphicsWidth / 2) { mergedHUDRect.Inflate(-GameMain.GraphicsWidth / 4, 0); } if (mergedHUDRect.Width > GameMain.GraphicsWidth / 2) { mergedHUDRect.Inflate(-GameMain.GraphicsWidth / 4, 0); }
@@ -1180,7 +1199,7 @@ namespace Barotrauma
return texts; return texts;
} }
public override void AddToGUIUpdateList() public override void AddToGUIUpdateList(int order = 0)
{ {
if (Screen.Selected is SubEditorScreen) if (Screen.Selected is SubEditorScreen)
{ {
@@ -1194,7 +1213,14 @@ namespace Barotrauma
} }
} }
if (Character.Controlled != null && Character.Controlled.SelectedConstruction != this) { return; } if (Character.Controlled != null && Character.Controlled.SelectedConstruction != this && GetComponent<RemoteController>() == null)
{
if (Character.Controlled.SelectedConstruction?.GetComponent<RemoteController>()?.TargetItem != this &&
!Character.Controlled.HeldItems.Any(it => it.GetComponent<RemoteController>()?.TargetItem == this))
{
return;
}
}
bool needsLayoutUpdate = false; bool needsLayoutUpdate = false;
foreach (ItemComponent ic in activeHUDs) foreach (ItemComponent ic in activeHUDs)
@@ -1205,7 +1231,7 @@ namespace Barotrauma
bool wasUsingAlternativeLayout = ic.UseAlternativeLayout; bool wasUsingAlternativeLayout = ic.UseAlternativeLayout;
ic.UseAlternativeLayout = useAlternativeLayout; ic.UseAlternativeLayout = useAlternativeLayout;
needsLayoutUpdate |= ic.UseAlternativeLayout != wasUsingAlternativeLayout; needsLayoutUpdate |= ic.UseAlternativeLayout != wasUsingAlternativeLayout;
ic.AddToGUIUpdateList(); ic.AddToGUIUpdateList(order);
} }
if (itemInUseWarning != null && itemInUseWarning.Visible) if (itemInUseWarning != null && itemInUseWarning.Visible)
@@ -1530,6 +1556,7 @@ namespace Barotrauma
byte bodyType = msg.ReadByte(); byte bodyType = msg.ReadByte();
bool spawnedInOutpost = msg.ReadBoolean(); bool spawnedInOutpost = msg.ReadBoolean();
bool allowStealing = msg.ReadBoolean(); bool allowStealing = msg.ReadBoolean();
int quality = msg.ReadRangedInteger(0, Items.Components.Quality.MaxQuality);
byte teamID = msg.ReadByte(); byte teamID = msg.ReadByte();
bool tagsChanged = msg.ReadBoolean(); bool tagsChanged = msg.ReadBoolean();
string tags = ""; string tags = "";
@@ -1605,7 +1632,8 @@ namespace Barotrauma
item = new Item(itemPrefab, pos, sub, id: itemId) item = new Item(itemPrefab, pos, sub, id: itemId)
{ {
SpawnedInOutpost = spawnedInOutpost, SpawnedInOutpost = spawnedInOutpost,
AllowStealing = allowStealing AllowStealing = allowStealing,
Quality = quality
}; };
} }
catch (Exception e) catch (Exception e)
@@ -1623,6 +1651,10 @@ namespace Barotrauma
{ {
wifiComponent.TeamID = (CharacterTeamType)teamID; wifiComponent.TeamID = (CharacterTeamType)teamID;
} }
foreach (IdCard idCard in item.GetComponents<IdCard>())
{
idCard.TeamID = (CharacterTeamType)teamID;
}
if (descriptionChanged) { item.Description = itemDesc; } if (descriptionChanged) { item.Description = itemDesc; }
if (tagsChanged) { item.Tags = tags; } if (tagsChanged) { item.Tags = tags; }
var nameTag = item.GetComponent<NameTag>(); var nameTag = item.GetComponent<NameTag>();
@@ -77,6 +77,13 @@ namespace Barotrauma
protected set; protected set;
} }
[Serialize(true, false)]
public bool ShowInStatusMonitor
{
get;
private set;
}
[Serialize("", false)] [Serialize("", false)]
public string ImpactSoundTag { get; private set; } public string ImpactSoundTag { get; private set; }
@@ -8,6 +8,8 @@ namespace Barotrauma
{ {
partial void ExplodeProjSpecific(Vector2 worldPosition, Hull hull) partial void ExplodeProjSpecific(Vector2 worldPosition, Hull hull)
{ {
if (GameMain.Client?.MidRoundSyncing ?? false) { return; }
if (shockwave) if (shockwave)
{ {
GameMain.ParticleManager.CreateParticle("shockwave", worldPosition, GameMain.ParticleManager.CreateParticle("shockwave", worldPosition,
@@ -676,7 +676,7 @@ namespace Barotrauma
} }
else else
{ {
remoteBackgroundSections.Add(new BackgroundSection(new Rectangle(0, 0, 1, 1), i, colorStrength, color, 0)); remoteBackgroundSections.Add(new BackgroundSection(new Rectangle(0, 0, 1, 1), (ushort)i, colorStrength, color, 0));
} }
} }
paintAmount = BackgroundSections.Sum(s => s.ColorStrength); paintAmount = BackgroundSections.Sum(s => s.ColorStrength);
@@ -7,14 +7,9 @@ namespace Barotrauma.RuinGeneration
{ {
public void DebugDraw(SpriteBatch spriteBatch) public void DebugDraw(SpriteBatch spriteBatch)
{ {
foreach (RuinShape shape in allShapes) Rectangle drawRect = Area;
{ drawRect.Y = -drawRect.Y - Area.Height;
GUI.DrawString(spriteBatch, new Vector2(shape.Center.X, -shape.Center.Y - 50), shape.DistanceFromEntrance.ToString(), Color.White, Color.Black * 0.5f, font: GUI.LargeFont); GUI.DrawRectangle(spriteBatch, drawRect, Color.Cyan, false, 0, 6);
}
foreach (Line line in walls)
{
GUI.DrawLine(spriteBatch, new Vector2(line.A.X, -line.A.Y), new Vector2(line.B.X, -line.B.Y), GUI.Style.Red, 0.0f, 10);
}
} }
} }
} }
@@ -846,16 +846,15 @@ namespace Barotrauma.Lights
if (chList.Submarine == null) if (chList.Submarine == null)
{ {
list.AddRange(chList.List.FindAll(ch => MathUtils.CircleIntersectsRectangle(lightPos, range, ch.BoundingBox))); list.AddRange(chList.List.FindAll(ch => MathUtils.CircleIntersectsRectangle(lightPos, range, ch.BoundingBox)));
} }
//light is outside, convexhull inside a sub //light is outside, convexhull inside a sub
else else
{ {
Rectangle subBorders = chList.Submarine.Borders; Rectangle subBorders = chList.Submarine.Borders;
subBorders.Y -= chList.Submarine.Borders.Height; subBorders.Y -= chList.Submarine.Borders.Height;
if (!MathUtils.CircleIntersectsRectangle(lightPos - chList.Submarine.WorldPosition, range, subBorders)) continue; if (!MathUtils.CircleIntersectsRectangle(lightPos - chList.Submarine.WorldPosition, range, subBorders)) { continue; }
lightPos -= (chList.Submarine.WorldPosition - chList.Submarine.HiddenSubPosition); lightPos -= chList.Submarine.WorldPosition - chList.Submarine.HiddenSubPosition;
list.AddRange(chList.List.FindAll(ch => MathUtils.CircleIntersectsRectangle(lightPos, range, ch.BoundingBox))); list.AddRange(chList.List.FindAll(ch => MathUtils.CircleIntersectsRectangle(lightPos, range, ch.BoundingBox)));
} }
@@ -865,14 +864,6 @@ namespace Barotrauma.Lights
//light is inside, convexhull outside //light is inside, convexhull outside
if (chList.Submarine == null) if (chList.Submarine == null)
{ {
lightPos += (ParentSub.WorldPosition - ParentSub.HiddenSubPosition);
HashSet<RuinGeneration.Ruin> visibleRuins = new HashSet<RuinGeneration.Ruin>();
foreach (RuinGeneration.Ruin ruin in Level.Loaded.Ruins)
{
if (!MathUtils.CircleIntersectsRectangle(lightPos, range, ruin.Area)) { continue; }
visibleRuins.Add(ruin);
}
list.AddRange(chList.List.FindAll(ch => ch.ParentEntity?.ParentRuin != null && visibleRuins.Contains(ch.ParentEntity.ParentRuin)));
continue; continue;
} }
//light and convexhull are both inside the same sub //light and convexhull are both inside the same sub
@@ -197,7 +197,9 @@ namespace Barotrauma.Lights
float spriteRange = Math.Max( float spriteRange = Math.Max(
light.LightSprite.size.X * light.SpriteScale.X * (0.5f + Math.Abs(light.LightSprite.RelativeOrigin.X - 0.5f)), light.LightSprite.size.X * light.SpriteScale.X * (0.5f + Math.Abs(light.LightSprite.RelativeOrigin.X - 0.5f)),
light.LightSprite.size.Y * light.SpriteScale.Y * (0.5f + Math.Abs(light.LightSprite.RelativeOrigin.Y - 0.5f))); light.LightSprite.size.Y * light.SpriteScale.Y * (0.5f + Math.Abs(light.LightSprite.RelativeOrigin.Y - 0.5f)));
range = Math.Max(spriteRange, range);
float targetSize = Math.Max(light.LightTextureTargetSize.X, light.LightTextureTargetSize.Y);
range = Math.Max(Math.Max(spriteRange, targetSize), range);
} }
if (!MathUtils.CircleIntersectsRectangle(light.WorldPosition, range, viewRect)) { continue; } if (!MathUtils.CircleIntersectsRectangle(light.WorldPosition, range, viewRect)) { continue; }
activeLights.Add(light); activeLights.Add(light);
@@ -238,13 +240,13 @@ namespace Barotrauma.Lights
//draw a black rectangle on hulls to hide background lights behind subs //draw a black rectangle on hulls to hide background lights behind subs
//--------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------
if (backgroundObstructor != null) /*if (backgroundObstructor != null)
{ {
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied); spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied);
spriteBatch.Draw(backgroundObstructor, new Rectangle(0, 0, spriteBatch.Draw(backgroundObstructor, new Rectangle(0, 0,
(int)(GameMain.GraphicsWidth * currLightMapScale), (int)(GameMain.GraphicsHeight * currLightMapScale)), Color.Black); (int)(GameMain.GraphicsWidth * currLightMapScale), (int)(GameMain.GraphicsHeight * currLightMapScale)), Color.Black);
spriteBatch.End(); spriteBatch.End();
} }*/
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Opaque, transformMatrix: spriteBatchTransform); spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Opaque, transformMatrix: spriteBatchTransform);
Dictionary<Hull, Rectangle> visibleHulls = GetVisibleHulls(cam); Dictionary<Hull, Rectangle> visibleHulls = GetVisibleHulls(cam);
@@ -258,7 +260,7 @@ namespace Barotrauma.Lights
spriteBatch.End(); spriteBatch.End();
SolidColorEffect.CurrentTechnique = SolidColorEffect.Techniques["SolidColor"]; SolidColorEffect.CurrentTechnique = SolidColorEffect.Techniques["SolidColor"];
SolidColorEffect.Parameters["color"].SetValue(AmbientLight.ToVector4()); SolidColorEffect.Parameters["color"].SetValue(AmbientLight.Opaque().ToVector4());
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, transformMatrix: spriteBatchTransform, effect: SolidColorEffect); spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, transformMatrix: spriteBatchTransform, effect: SolidColorEffect);
Submarine.DrawDamageable(spriteBatch, null); Submarine.DrawDamageable(spriteBatch, null);
spriteBatch.End(); spriteBatch.End();
@@ -376,6 +376,22 @@ namespace Barotrauma.Lights
} }
} }
private Vector2 lightTextureTargetSize;
public Vector2 LightTextureTargetSize
{
get => lightTextureTargetSize;
set
{
NeedsRecalculation = true;
NeedsHullCheck = true;
lightTextureTargetSize = value;
}
}
public Vector2 LightTextureOffset { get; set; }
public Vector2 LightTextureScale { get; set; } = Vector2.One;
public float TextureRange public float TextureRange
{ {
get get
@@ -1228,10 +1244,19 @@ namespace Barotrauma.Lights
} }
drawPos.Y = -drawPos.Y; drawPos.Y = -drawPos.Y;
LightSprite.Draw( Color color = new Color(Color, (lightSourceParams.OverrideLightSpriteAlpha ?? Color.A / 255.0f) * CurrentBrightness);
spriteBatch, drawPos,
new Color(Color, (lightSourceParams.OverrideLightSpriteAlpha ?? Color.A / 255.0f) * CurrentBrightness), if (LightTextureTargetSize != Vector2.Zero)
origin, -Rotation + MathHelper.ToRadians(LightSourceParams.Rotation), SpriteScale, LightSpriteEffect); {
LightSprite.DrawTiled(spriteBatch, drawPos, LightTextureTargetSize, color, startOffset: LightTextureOffset, textureScale: LightTextureScale);
}
else
{
LightSprite.Draw(
spriteBatch, drawPos,
color,
origin, -Rotation + MathHelper.ToRadians(LightSourceParams.Rotation), SpriteScale, LightSpriteEffect);
}
} }
if (GameMain.DebugDraw && Screen.Selected.Cam.Zoom > 0.1f) if (GameMain.DebugDraw && Screen.Selected.Cam.Zoom > 0.1f)
@@ -1291,7 +1316,7 @@ namespace Barotrauma.Lights
if (ParentSub != null) { drawPos += ParentSub.DrawPosition; } if (ParentSub != null) { drawPos += ParentSub.DrawPosition; }
drawPos.Y = -drawPos.Y; drawPos.Y = -drawPos.Y;
spriteBatch.Draw(currentTexture, drawPos, null, Color.Multiply(CurrentBrightness), -rotation, center, scale, SpriteEffects.None, 1); spriteBatch.Draw(currentTexture, drawPos, null, Color.Multiply(CurrentBrightness), -rotation + MathHelper.ToRadians(LightSourceParams.Rotation), center, scale, SpriteEffects.None, 1);
return; return;
} }
@@ -1318,7 +1343,7 @@ namespace Barotrauma.Lights
Vector2 offset = ParentSub == null ? Vector2.Zero : ParentSub.DrawPosition; Vector2 offset = ParentSub == null ? Vector2.Zero : ParentSub.DrawPosition;
lightEffect.World = lightEffect.World =
Matrix.CreateTranslation(-new Vector3(position, 0.0f)) * Matrix.CreateTranslation(-new Vector3(position, 0.0f)) *
Matrix.CreateRotationZ(rotateVertices) * Matrix.CreateRotationZ(rotateVertices - MathHelper.ToRadians(LightSourceParams.Rotation)) *
Matrix.CreateTranslation(new Vector3(position + offset + translateVertices, 0.0f)) * Matrix.CreateTranslation(new Vector3(position + offset + translateVertices, 0.0f)) *
transform; transform;
@@ -265,7 +265,7 @@ namespace Barotrauma
if (!currentDisplayLocation.Discovered) if (!currentDisplayLocation.Discovered)
{ {
RemoveFogOfWar(currentDisplayLocation); RemoveFogOfWar(currentDisplayLocation);
currentDisplayLocation.Discovered = true; currentDisplayLocation.Discover();
if (currentDisplayLocation.MapPosition.X > furthestDiscoveredLocation.MapPosition.X) if (currentDisplayLocation.MapPosition.X > furthestDiscoveredLocation.MapPosition.X)
{ {
furthestDiscoveredLocation = currentDisplayLocation; furthestDiscoveredLocation = currentDisplayLocation;
@@ -426,7 +426,7 @@ namespace Barotrauma
Level.Loaded.DebugSetStartLocation(CurrentLocation); Level.Loaded.DebugSetStartLocation(CurrentLocation);
Level.Loaded.DebugSetEndLocation(null); Level.Loaded.DebugSetEndLocation(null);
CurrentLocation.Discovered = true; CurrentLocation.Discover();
OnLocationChanged?.Invoke(prevLocation, CurrentLocation); OnLocationChanged?.Invoke(prevLocation, CurrentLocation);
SelectLocation(-1); SelectLocation(-1);
if (GameMain.Client == null) if (GameMain.Client == null)
@@ -6,6 +6,7 @@ using Microsoft.Xna.Framework.Input;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Barotrauma.Lights;
namespace Barotrauma namespace Barotrauma
{ {
@@ -423,20 +424,24 @@ namespace Barotrauma
//select wire if both items it's connected to are selected //select wire if both items it's connected to are selected
var selectedItems = SelectedList.Where(e => e is Item).Cast<Item>().ToList(); var selectedItems = SelectedList.Where(e => e is Item).Cast<Item>().ToList();
foreach (Item item in selectedItems) foreach (Item item in Item.ItemList)
{ {
if (item.Connections == null) continue; var wire = item.GetComponent<Wire>();
foreach (Connection c in item.Connections) if (wire == null) { continue; }
{ Item item0 = wire.Connections[0]?.Item;
foreach (Wire w in c.Wires) Item item1 = wire.Connections[1]?.Item;
{
if (w == null || SelectedList.Contains(w.Item)) continue;
if (w.OtherConnection(c) != null && SelectedList.Contains(w.OtherConnection(c).Item)) if (item0 == null && item1 != null)
{ {
SelectedList.Add(w.Item); item0 = Item.ItemList.Find(it => it.GetComponent<ConnectionPanel>()?.DisconnectedWires.Contains(wire) ?? false);
} }
} else if (item0 != null && item1 == null)
{
item1 = Item.ItemList.Find(it => it.GetComponent<ConnectionPanel>()?.DisconnectedWires.Contains(wire) ?? false);
}
if (item0 != null && item1 != null && SelectedList.Contains(item0) && SelectedList.Contains(item1))
{
SelectedList.Add(item);
} }
} }
@@ -989,6 +994,10 @@ namespace Barotrauma
clone.Move(moveAmount); clone.Move(moveAmount);
clone.Submarine = Submarine.MainSub; clone.Submarine = Submarine.MainSub;
} }
foreach (MapEntity clone in SelectedList)
{
(clone as Item)?.GetComponent<ItemContainer>()?.SetContainedItemPositions();
}
SubEditorScreen.StoreCommand(new AddOrDeleteCommand(clones, false, handleInventoryBehavior: false)); SubEditorScreen.StoreCommand(new AddOrDeleteCommand(clones, false, handleInventoryBehavior: false));
} }
@@ -1012,9 +1021,9 @@ namespace Barotrauma
return newEntities; return newEntities;
} }
public virtual void AddToGUIUpdateList() public virtual void AddToGUIUpdateList(int order = 0)
{ {
if (editingHUD != null && editingHUD.UserData == this) editingHUD.AddToGUIUpdateList(); if (editingHUD != null && editingHUD.UserData == this) { editingHUD.AddToGUIUpdateList(order: order); }
} }
public virtual void UpdateEditing(Camera cam, float deltaTime) { } public virtual void UpdateEditing(Camera cam, float deltaTime) { }
@@ -1140,6 +1149,15 @@ namespace Barotrauma
var oldData = new List<Rectangle> { prevRect.Value }; var oldData = new List<Rectangle> { prevRect.Value };
SubEditorScreen.StoreCommand(new TransformCommand(new List<MapEntity> { this }, newData, oldData, true)); SubEditorScreen.StoreCommand(new TransformCommand(new List<MapEntity> { this }, newData, oldData, true));
} }
if (this is Structure structure)
{
foreach (LightSource light in structure.Lights)
{
light.LightTextureTargetSize = Rect.Size.ToVector2();
light.Position = rect.Location.ToVector2();
}
}
prevRect = null; prevRect = null;
} }
} }
@@ -20,6 +20,8 @@ namespace Barotrauma
private readonly Dictionary<DecorativeSprite, DecorativeSprite.State> spriteAnimState = new Dictionary<DecorativeSprite, DecorativeSprite.State>(); private readonly Dictionary<DecorativeSprite, DecorativeSprite.State> spriteAnimState = new Dictionary<DecorativeSprite, DecorativeSprite.State>();
public readonly List<LightSource> Lights = new List<LightSource>();
public override bool SelectableInEditor public override bool SelectableInEditor
{ {
get get
@@ -91,6 +93,22 @@ namespace Barotrauma
} }
} }
private void SetLightTextureOffset()
{
Vector2 textOffset = textureOffset;
if (FlippedX) { textOffset.X = -textOffset.X; }
if (FlippedY) { textOffset.Y = -textOffset.Y; }
foreach (LightSource light in Lights)
{
Vector2 bgOffset = new Vector2(
MathUtils.PositiveModulo((int)-textOffset.X, light.texture.Width),
MathUtils.PositiveModulo((int)-textOffset.Y, light.texture.Height));
light.LightTextureOffset = bgOffset;
}
}
public GUIComponent CreateEditingHUD(bool inGame = false) public GUIComponent CreateEditingHUD(bool inGame = false)
{ {
int heightScaled = (int)(20 * GUI.Scale); int heightScaled = (int)(20 * GUI.Scale);
@@ -267,6 +285,8 @@ namespace Barotrauma
//color = Color.Lerp(color, Color.Gold, 0.5f); //color = Color.Lerp(color, Color.Gold, 0.5f);
color = spriteColor; color = spriteColor;
Vector2 rectSize = rect.Size.ToVector2(); Vector2 rectSize = rect.Size.ToVector2();
if (BodyWidth > 0.0f) { rectSize.X = BodyWidth; } if (BodyWidth > 0.0f) { rectSize.X = BodyWidth; }
if (BodyHeight > 0.0f) { rectSize.Y = BodyHeight; } if (BodyHeight > 0.0f) { rectSize.Y = BodyHeight; }
@@ -7,6 +7,7 @@ using Microsoft.Xna.Framework.Graphics;
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using Barotrauma.IO; using Barotrauma.IO;
using System.Linq; using System.Linq;
using System.Xml.Linq; using System.Xml.Linq;
@@ -177,7 +178,6 @@ namespace Barotrauma
//drawing ---------------------------------------------------- //drawing ----------------------------------------------------
private static readonly HashSet<Submarine> visibleSubs = new HashSet<Submarine>(); private static readonly HashSet<Submarine> visibleSubs = new HashSet<Submarine>();
private static readonly HashSet<Ruin> visibleRuins = new HashSet<Ruin>();
public static void CullEntities(Camera cam) public static void CullEntities(Camera cam)
{ {
visibleSubs.Clear(); visibleSubs.Clear();
@@ -197,24 +197,6 @@ namespace Barotrauma
} }
} }
visibleRuins.Clear();
if (Level.Loaded != null)
{
foreach (Ruin ruin in Level.Loaded.Ruins)
{
Rectangle worldBorders = new Rectangle(
ruin.Area.X - 500,
ruin.Area.Y + ruin.Area.Height + 500,
ruin.Area.Width + 1000,
ruin.Area.Height + 1000);
if (RectsOverlap(worldBorders, cam.WorldView))
{
visibleRuins.Add(ruin);
}
}
}
if (visibleEntities == null) if (visibleEntities == null)
{ {
visibleEntities = new List<MapEntity>(MapEntity.mapEntityList.Count); visibleEntities = new List<MapEntity>(MapEntity.mapEntityList.Count);
@@ -231,10 +213,6 @@ namespace Barotrauma
{ {
if (!visibleSubs.Contains(entity.Submarine)) { continue; } if (!visibleSubs.Contains(entity.Submarine)) { continue; }
} }
else if (entity.ParentRuin != null)
{
if (!visibleRuins.Contains(entity.ParentRuin)) { continue; }
}
if (entity.IsVisible(worldView)) { visibleEntities.Add(entity); } if (entity.IsVisible(worldView)) { visibleEntities.Add(entity); }
} }
@@ -403,6 +381,8 @@ namespace Barotrauma
} }
} }
// TODO remove
[Obsolete("Use MiniMap.CreateMiniMap()")]
public void CreateMiniMap(GUIComponent parent, IEnumerable<Entity> pointsOfInterest = null, bool ignoreOutpost = false) public void CreateMiniMap(GUIComponent parent, IEnumerable<Entity> pointsOfInterest = null, bool ignoreOutpost = false)
{ {
Rectangle worldBorders = GetDockedBorders(); Rectangle worldBorders = GetDockedBorders();
@@ -417,24 +397,122 @@ namespace Barotrauma
GUIFrame hullContainer = new GUIFrame(new RectTransform( GUIFrame hullContainer = new GUIFrame(new RectTransform(
(parentAspectRatio > aspectRatio ? new Vector2(aspectRatio / parentAspectRatio, 1.0f) : new Vector2(1.0f, parentAspectRatio / aspectRatio)) * scale, (parentAspectRatio > aspectRatio ? new Vector2(aspectRatio / parentAspectRatio, 1.0f) : new Vector2(1.0f, parentAspectRatio / aspectRatio)) * scale,
parent.RectTransform, Anchor.Center), parent.RectTransform, Anchor.Center),
style: null); style: null)
{
UserData = "hullcontainer"
};
var connectedSubs = GetConnectedSubs(); var connectedSubs = GetConnectedSubs();
foreach (Hull hull in Hull.hullList)
{
if (hull.Submarine != this && !connectedSubs.Contains(hull.Submarine)) { continue; }
if (ignoreOutpost && !IsEntityFoundOnThisSub(hull, true)) { continue; }
HashSet<Hull> hullList = Hull.hullList.Where(hull => hull.Submarine == this || connectedSubs.Contains(hull.Submarine)).Where(hull => !ignoreOutpost || IsEntityFoundOnThisSub(hull, true)).ToHashSet();
Dictionary<Hull, HashSet<Hull>> combinedHulls = new Dictionary<Hull, HashSet<Hull>>();
foreach (Hull hull in hullList)
{
if (combinedHulls.ContainsKey(hull) || combinedHulls.Values.Any(hh => hh.Contains(hull))) { continue; }
List<Hull> linkedHulls = new List<Hull>();
MiniMap.GetLinkedHulls(hull, linkedHulls);
linkedHulls.Remove(hull);
foreach (Hull linkedHull in linkedHulls)
{
if (!combinedHulls.ContainsKey(hull))
{
combinedHulls.Add(hull, new HashSet<Hull>());
}
combinedHulls[hull].Add(linkedHull);
}
}
foreach (Hull hull in hullList)
{
Vector2 relativeHullPos = new Vector2( Vector2 relativeHullPos = new Vector2(
(hull.WorldRect.X - worldBorders.X) / (float)worldBorders.Width, (hull.WorldRect.X - worldBorders.X) / (float)worldBorders.Width,
(worldBorders.Y - hull.WorldRect.Y) / (float)worldBorders.Height); (worldBorders.Y - hull.WorldRect.Y) / (float)worldBorders.Height);
Vector2 relativeHullSize = new Vector2(hull.Rect.Width / (float)worldBorders.Width, hull.Rect.Height / (float)worldBorders.Height); Vector2 relativeHullSize = new Vector2(hull.Rect.Width / (float)worldBorders.Width, hull.Rect.Height / (float)worldBorders.Height);
var hullFrame = new GUIFrame(new RectTransform(relativeHullSize, hullContainer.RectTransform) { RelativeOffset = relativeHullPos }, style: "MiniMapRoom", color: Color.DarkCyan * 0.8f) bool hideHull = combinedHulls.ContainsKey(hull) || combinedHulls.Values.Any(hh => hh.Contains(hull));
if (hideHull) { continue; }
Color color = Color.DarkCyan * 0.8f;
var hullFrame = new GUIFrame(new RectTransform(relativeHullSize, hullContainer.RectTransform) { RelativeOffset = relativeHullPos }, style: "MiniMapRoom", color: color)
{ {
UserData = hull UserData = hull
}; };
new GUIFrame(new RectTransform(Vector2.One, hullFrame.RectTransform), style: "ScanLines", color: Color.DarkCyan * 0.8f);
new GUIFrame(new RectTransform(Vector2.One, hullFrame.RectTransform), style: "ScanLines", color: color);
}
foreach (var (mainHull, linkedHulls) in combinedHulls)
{
MiniMapHullData data = ConstructLinkedHulls(mainHull, linkedHulls, hullContainer, worldBorders);
Vector2 relativeHullPos = new Vector2(
(data.Bounds.X - worldBorders.X) / worldBorders.Width,
(worldBorders.Y - data.Bounds.Y) / worldBorders.Height);
Vector2 relativeHullSize = new Vector2(data.Bounds.Width / worldBorders.Width, data.Bounds.Height / worldBorders.Height);
Color color = Color.DarkCyan * 0.8f;
float highestY = 0f,
highestX = 0f;
foreach (var (r, _) in data.RectDatas)
{
float y = r.Y - -r.Height,
x = r.X;
if (y > highestY) { highestY = y; }
if (x > highestX) { highestX = x; }
}
HashSet<GUIFrame> frames = new HashSet<GUIFrame>();
foreach (var (snappredRect, hull) in data.RectDatas)
{
RectangleF rect = snappredRect;
rect.Height = -rect.Height;
rect.Y -= rect.Height;
var (parentW, parentH) = hullContainer.Rect.Size.ToVector2();
Vector2 size = new Vector2(rect.Width / parentW, rect.Height / parentH);
// TODO this won't be required if we some day switch RectTransform to use RectangleF
Vector2 pos = new Vector2(rect.X / parentW, rect.Y / parentH);
GUIFrame hullFrame = new GUIFrame(new RectTransform(size, hullContainer.RectTransform) { RelativeOffset = pos }, style: "ScanLinesSeamless", color: color)
{
UserData = hull,
UVOffset = new Vector2(highestX - rect.X, highestY - rect.Y)
};
frames.Add(hullFrame);
}
new GUICustomComponent(new RectTransform(relativeHullSize, hullContainer.RectTransform) { RelativeOffset = relativeHullPos }, (spriteBatch, component) =>
{
foreach (List<Vector2> list in data.Polygon)
{
spriteBatch.DrawPolygonInner(hullContainer.Rect.Location.ToVector2(), list, component.Color, 2f);
}
}, (deltaTime, component) =>
{
if (component.Parent.Rect.Size != data.ParentSize)
{
data = ConstructLinkedHulls(mainHull, linkedHulls, hullContainer, worldBorders);
}
})
{
UserData = frames,
Color = color,
CanBeFocused = false
};
} }
if (pointsOfInterest != null) if (pointsOfInterest != null)
@@ -453,6 +531,64 @@ namespace Barotrauma
} }
} }
public static MiniMapHullData ConstructLinkedHulls(Hull mainHull, HashSet<Hull> linkedHulls, GUIComponent parent, Rectangle worldBorders)
{
Rectangle parentRect = parent.Rect;
Dictionary<Hull, Rectangle> rects = new Dictionary<Hull, Rectangle>();
Rectangle worldRect = mainHull.WorldRect;
worldRect.Y = -worldRect.Y;
rects.Add(mainHull, worldRect);
foreach (Hull hull in linkedHulls)
{
Rectangle rect = hull.WorldRect;
rect.Y = -rect.Y;
worldRect = Rectangle.Union(worldRect, rect);
rects.Add(hull, rect);
}
worldRect.Y = -worldRect.Y;
List<RectangleF> normalizedRects = new List<RectangleF>();
List<Hull> hullRefs = new List<Hull>();
foreach (var (hull, rect) in rects)
{
Rectangle wRect = rect;
wRect.Y = -wRect.Y;
var (posX, posY) = new Vector2(
(wRect.X - worldBorders.X) / (float)worldBorders.Width,
(worldBorders.Y - wRect.Y) / (float)worldBorders.Height);
var (scaleX, scaleY) = new Vector2(wRect.Width / (float)worldBorders.Width, wRect.Height / (float)worldBorders.Height);
RectangleF newRect = new RectangleF(posX * parentRect.Width, posY * parentRect.Height, scaleX * parentRect.Width, scaleY * parentRect.Height);
normalizedRects.Add(newRect);
hullRefs.Add(hull);
}
ImmutableArray<RectangleF> snappedRectangles = ToolBox.SnapRectangles(normalizedRects, treshold: 1);
List<List<Vector2>> polygon = ToolBox.CombineRectanglesIntoShape(snappedRectangles);
List<List<Vector2>> scaledPolygon = new List<List<Vector2>>();
foreach (List<Vector2> list in polygon)
{
var (polySizeX, polySizeY) = ToolBox.GetPolygonBoundingBoxSize(list);
float sizeX = polySizeX - 1f,
sizeY = polySizeY - 1f;
scaledPolygon.Add(ToolBox.ScalePolygon(list, new Vector2(sizeX / polySizeX, sizeY / polySizeY)));
}
return new MiniMapHullData(scaledPolygon, worldRect, parentRect.Size, snappedRectangles, hullRefs.ToImmutableArray());
}
public void CheckForErrors() public void CheckForErrors()
{ {
List<string> errorMsgs = new List<string>(); List<string> errorMsgs = new List<string>();
@@ -600,6 +600,8 @@ namespace Barotrauma
var prevScissorRect = GameMain.Instance.GraphicsDevice.ScissorRectangle; var prevScissorRect = GameMain.Instance.GraphicsDevice.ScissorRectangle;
GameMain.Instance.GraphicsDevice.ScissorRectangle = scissorRectangle; GameMain.Instance.GraphicsDevice.ScissorRectangle = scissorRectangle;
var prevRasterizerState = GameMain.Instance.GraphicsDevice.RasterizerState;
GameMain.Instance.GraphicsDevice.RasterizerState = GameMain.ScissorTestEnable;
spriteRecorder.Render(camera); spriteRecorder.Render(camera);
@@ -643,6 +645,7 @@ namespace Barotrauma
spriteBatch.End(); spriteBatch.End();
GameMain.Instance.GraphicsDevice.ScissorRectangle = prevScissorRect; GameMain.Instance.GraphicsDevice.ScissorRectangle = prevScissorRect;
GameMain.Instance.GraphicsDevice.RasterizerState = prevRasterizerState;
spriteBatch.Begin(SpriteSortMode.Deferred); spriteBatch.Begin(SpriteSortMode.Deferred);
} }
@@ -127,6 +127,13 @@ namespace Barotrauma
ID.ToString(), ID.ToString(),
new Vector2(DrawPosition.X - 10, -DrawPosition.Y - 30), new Vector2(DrawPosition.X - 10, -DrawPosition.Y - 30),
color); color);
if (Tunnel?.Type != null)
{
GUI.SmallFont.DrawString(spriteBatch,
Tunnel.Type.ToString(),
new Vector2(DrawPosition.X - 10, -DrawPosition.Y - 45),
color);
}
} }
public override bool IsMouseOn(Vector2 position) public override bool IsMouseOn(Vector2 position)
@@ -164,10 +171,7 @@ namespace Barotrauma
{ {
foreach (MapEntity e in mapEntityList) foreach (MapEntity e in mapEntityList)
{ {
if (e.GetType() != typeof(WayPoint)) continue; if (!(e is WayPoint) || e == this || !e.IsHighlighted) { continue; }
if (e == this) continue;
if (!Submarine.RectContains(e.Rect, position)) continue;
if (linkedTo.Contains(e)) if (linkedTo.Contains(e))
{ {
@@ -64,7 +64,10 @@ namespace Barotrauma.Networking
string orderOption = orderMessageInfo.OrderOption; string orderOption = orderMessageInfo.OrderOption;
orderOption ??= orderMessageInfo.OrderOptionIndex.HasValue && orderMessageInfo.OrderOptionIndex >= 0 && orderMessageInfo.OrderOptionIndex < orderPrefab.Options.Length ? orderOption ??= orderMessageInfo.OrderOptionIndex.HasValue && orderMessageInfo.OrderOptionIndex >= 0 && orderMessageInfo.OrderOptionIndex < orderPrefab.Options.Length ?
orderPrefab.Options[orderMessageInfo.OrderOptionIndex.Value] : ""; orderPrefab.Options[orderMessageInfo.OrderOptionIndex.Value] : "";
txt = orderPrefab.GetChatMessage(orderMessageInfo.TargetCharacter?.Name, senderCharacter?.CurrentHull?.DisplayName, givingOrderToSelf: orderMessageInfo.TargetCharacter == senderCharacter, orderOption: orderOption); txt = orderPrefab.GetChatMessage(orderMessageInfo.TargetCharacter?.Name, senderCharacter?.CurrentHull?.DisplayName,
givingOrderToSelf: orderMessageInfo.TargetCharacter == senderCharacter,
orderOption: orderOption,
priority: orderMessageInfo.Priority);
if (GameMain.Client.GameStarted && Screen.Selected == GameMain.GameScreen) if (GameMain.Client.GameStarted && Screen.Selected == GameMain.GameScreen)
{ {
@@ -550,6 +550,13 @@ namespace Barotrauma.Networking
okButton.OnClicked += msgBox.Close; okButton.OnClicked += msgBox.Close;
var cancelButton = msgBox.Buttons[1]; var cancelButton = msgBox.Buttons[1];
cancelButton.OnClicked += msgBox.Close; cancelButton.OnClicked += msgBox.Close;
passwordBox.OnEnterPressed += (GUITextBox textBox, string text) =>
{
msgBox.Close();
clientPeer?.SendPassword(passwordBox.Text);
requiresPw = false;
return true;
};
okButton.OnClicked += (GUIButton button, object obj) => okButton.OnClicked += (GUIButton button, object obj) =>
{ {
@@ -565,6 +572,8 @@ namespace Barotrauma.Networking
GameMain.ServerListScreen.Select(); GameMain.ServerListScreen.Select();
return true; return true;
}; };
yield return CoroutineStatus.Running;
passwordBox.Select();
while (GUIMessageBox.MessageBoxes.Contains(msgBox)) while (GUIMessageBox.MessageBoxes.Contains(msgBox))
{ {
@@ -1456,6 +1465,7 @@ namespace Barotrauma.Networking
bool respawnAllowed = inc.ReadBoolean(); bool respawnAllowed = inc.ReadBoolean();
serverSettings.AllowDisguises = inc.ReadBoolean(); serverSettings.AllowDisguises = inc.ReadBoolean();
serverSettings.AllowRewiring = inc.ReadBoolean(); serverSettings.AllowRewiring = inc.ReadBoolean();
serverSettings.AllowFriendlyFire = inc.ReadBoolean();
serverSettings.LockAllDefaultWires = inc.ReadBoolean(); serverSettings.LockAllDefaultWires = inc.ReadBoolean();
serverSettings.AllowRagdollButton = inc.ReadBoolean(); serverSettings.AllowRagdollButton = inc.ReadBoolean();
GameMain.NetLobbyScreen.UsingShuttle = inc.ReadBoolean(); GameMain.NetLobbyScreen.UsingShuttle = inc.ReadBoolean();
@@ -2746,6 +2756,9 @@ namespace Barotrauma.Networking
msg.Write((byte)characterInfo.BeardIndex); msg.Write((byte)characterInfo.BeardIndex);
msg.Write((byte)characterInfo.MoustacheIndex); msg.Write((byte)characterInfo.MoustacheIndex);
msg.Write((byte)characterInfo.FaceAttachmentIndex); msg.Write((byte)characterInfo.FaceAttachmentIndex);
msg.WriteColorR8G8B8(characterInfo.SkinColor);
msg.WriteColorR8G8B8(characterInfo.HairColor);
msg.WriteColorR8G8B8(characterInfo.FacialHairColor);
var jobPreferences = GameMain.NetLobbyScreen.JobPreferences; var jobPreferences = GameMain.NetLobbyScreen.JobPreferences;
int count = Math.Min(jobPreferences.Count, 3); int count = Math.Min(jobPreferences.Count, 3);
@@ -288,7 +288,7 @@ namespace Barotrauma.Networking
heartbeatTimer = 5.0; heartbeatTimer = 5.0;
#if DEBUG #if DEBUG
CoroutineManager.InvokeAfter(() => CoroutineManager.Invoke(() =>
{ {
if (GameMain.Client == null) { return; } if (GameMain.Client == null) { return; }
if (Rand.Range(0.0f, 1.0f) < GameMain.Client.SimulatedLoss && sendType != Steamworks.P2PSend.Reliable) { return; } if (Rand.Range(0.0f, 1.0f) < GameMain.Client.SimulatedLoss && sendType != Steamworks.P2PSend.Reliable) { return; }
@@ -110,12 +110,7 @@ namespace Barotrauma.Networking
if (frame == null) { return; } if (frame == null) { return; }
var previewContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.98f), frame.RectTransform, Anchor.Center)) var title = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), frame.RectTransform), ServerName, font: GUI.LargeFont)
{
Stretch = true
};
var title = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), previewContainer.RectTransform, Anchor.CenterLeft), ServerName, font: GUI.LargeFont)
{ {
ToolTip = ServerName ToolTip = ServerName
}; };
@@ -141,41 +136,30 @@ namespace Barotrauma.Networking
} }
}; };
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), previewContainer.RectTransform), new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), frame.RectTransform),
TextManager.AddPunctuation(':', TextManager.Get("ServerListVersion"), string.IsNullOrEmpty(GameVersion) ? TextManager.Get("Unknown") : GameVersion)); TextManager.AddPunctuation(':', TextManager.Get("ServerListVersion"), string.IsNullOrEmpty(GameVersion) ? TextManager.Get("Unknown") : GameVersion));
bool hidePlaystyleBanner = previewContainer.Rect.Height < 380 || !PlayStyle.HasValue; bool hidePlaystyleBanner = !PlayStyle.HasValue;
if (!hidePlaystyleBanner) if (!hidePlaystyleBanner)
{ {
PlayStyle playStyle = PlayStyle ?? Networking.PlayStyle.Serious; PlayStyle playStyle = PlayStyle ?? Networking.PlayStyle.Serious;
Sprite playStyleBannerSprite = ServerListScreen.PlayStyleBanners[(int)playStyle]; Sprite playStyleBannerSprite = ServerListScreen.PlayStyleBanners[(int)playStyle];
float playStyleBannerAspectRatio = playStyleBannerSprite.SourceRect.Width / playStyleBannerSprite.SourceRect.Height; float playStyleBannerAspectRatio = playStyleBannerSprite.SourceRect.Width / playStyleBannerSprite.SourceRect.Height;
var playStyleBanner = new GUIImage(new RectTransform(new Point(previewContainer.Rect.Width, (int)(previewContainer.Rect.Width / playStyleBannerAspectRatio)), previewContainer.RectTransform), var playStyleBanner = new GUIImage(new RectTransform(new Point(frame.Rect.Width, (int)(frame.Rect.Width / playStyleBannerAspectRatio)), frame.RectTransform),
playStyleBannerSprite, null, true); playStyleBannerSprite, null, true);
var playStyleName = new GUITextBlock(new RectTransform(new Vector2(0.15f, 0.0f), playStyleBanner.RectTransform) { RelativeOffset = new Vector2(0.01f, 0.06f) }, var playStyleName = new GUITextBlock(new RectTransform(new Vector2(0.15f, 0.0f), playStyleBanner.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.06f) },
TextManager.AddPunctuation(':', TextManager.Get("serverplaystyle"), TextManager.Get("servertag."+ playStyle)), textColor: Color.White, TextManager.AddPunctuation(':', TextManager.Get("serverplaystyle"), TextManager.Get("servertag."+ playStyle)), textColor: Color.White,
font: GUI.SmallFont, textAlignment: Alignment.Center, font: GUI.SmallFont, textAlignment: Alignment.Center,
color: ServerListScreen.PlayStyleColors[(int)playStyle], style: "GUISlopedHeader"); color: ServerListScreen.PlayStyleColors[(int)playStyle], style: "GUISlopedHeader");
playStyleName.RectTransform.NonScaledSize = (playStyleName.Font.MeasureString(playStyleName.Text) + new Vector2(20, 5) * GUI.Scale).ToPoint(); playStyleName.RectTransform.NonScaledSize = (playStyleName.Font.MeasureString(playStyleName.Text) + new Vector2(20, 5) * GUI.Scale).ToPoint();
playStyleName.RectTransform.IsFixedSize = true; playStyleName.RectTransform.IsFixedSize = true;
var serverTypeContainer = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.2f), playStyleBanner.RectTransform, Anchor.BottomLeft, Pivot.BottomLeft),
"MainMenuNotifBackground", Color.Black)
{
CanBeFocused = false,
};
var serverType = new GUITextBlock(new RectTransform(Vector2.One, serverTypeContainer.RectTransform, Anchor.CenterLeft),
TextManager.Get((OwnerID != 0 || LobbyID != 0) ? "SteamP2PServer" : "DedicatedServer"), textAlignment: Alignment.CenterLeft);
}
else
{
var serverType = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), previewContainer.RectTransform, Anchor.CenterLeft),
TextManager.Get((OwnerID != 0 || LobbyID != 0) ? "SteamP2PServer" : "DedicatedServer"), textAlignment: Alignment.CenterLeft);
} }
var serverType = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), frame.RectTransform),
TextManager.Get((OwnerID != 0 || LobbyID != 0) ? "SteamP2PServer" : "DedicatedServer"), textAlignment: Alignment.TopLeft);
serverType.RectTransform.MinSize = new Point(0, (int)(serverType.Rect.Height * 1.5f));
var content = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.6f), previewContainer.RectTransform)) var content = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.6f), frame.RectTransform))
{ {
Stretch = true Stretch = true
}; };
@@ -285,7 +269,6 @@ namespace Barotrauma.Networking
else else
usingWhiteList.Selected = UsingWhiteList.Value; usingWhiteList.Selected = UsingWhiteList.Value;
content.RectTransform.SizeChanged += () => content.RectTransform.SizeChanged += () =>
{ {
GUITextBlock.AutoScaleAndNormalize(allowSpectating.TextBlock, allowRespawn.TextBlock, usingWhiteList.TextBlock); GUITextBlock.AutoScaleAndNormalize(allowSpectating.TextBlock, allowRespawn.TextBlock, usingWhiteList.TextBlock);
@@ -294,7 +277,7 @@ namespace Barotrauma.Networking
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform),
TextManager.Get("ServerListContentPackages"), textAlignment: Alignment.Center, font: GUI.SubHeadingFont); TextManager.Get("ServerListContentPackages"), textAlignment: Alignment.Center, font: GUI.SubHeadingFont);
var contentPackageList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.2f), content.RectTransform)) { ScrollBarVisible = true }; var contentPackageList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.3f), frame.RectTransform)) { ScrollBarVisible = true };
if (ContentPackageNames.Count == 0) if (ContentPackageNames.Count == 0)
{ {
new GUITextBlock(new RectTransform(Vector2.One, contentPackageList.Content.RectTransform), TextManager.Get("Unknown"), textAlignment: Alignment.Center) new GUITextBlock(new RectTransform(Vector2.One, contentPackageList.Content.RectTransform), TextManager.Get("Unknown"), textAlignment: Alignment.Center)
@@ -309,7 +292,7 @@ namespace Barotrauma.Networking
var packageText = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.15f), contentPackageList.Content.RectTransform) { MinSize = new Point(0, 15) }, var packageText = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.15f), contentPackageList.Content.RectTransform) { MinSize = new Point(0, 15) },
ContentPackageNames[i]) ContentPackageNames[i])
{ {
Enabled = false CanBeFocused = false
}; };
if (i < ContentPackageHashes.Count) if (i < ContentPackageHashes.Count)
{ {
@@ -322,7 +305,7 @@ namespace Barotrauma.Networking
//workshop download link found //workshop download link found
if (i < ContentPackageWorkshopIds.Count && ContentPackageWorkshopIds[i] != 0) if (i < ContentPackageWorkshopIds.Count && ContentPackageWorkshopIds[i] != 0)
{ {
packageText.TextColor = Color.Yellow; packageText.TextColor = GUI.Style.Yellow;
packageText.ToolTip = TextManager.GetWithVariable("ServerListIncompatibleContentPackageWorkshopAvailable", "[contentpackage]", ContentPackageNames[i]); packageText.ToolTip = TextManager.GetWithVariable("ServerListIncompatibleContentPackageWorkshopAvailable", "[contentpackage]", ContentPackageNames[i]);
} }
else //no package or workshop download link found, tough luck else //no package or workshop download link found, tough luck
@@ -1207,7 +1207,7 @@ namespace Barotrauma.Steam
foreach (string file in allPackageFiles) foreach (string file in allPackageFiles)
{ {
if (file == metaDataFilePath) { continue; } if (file == metaDataFilePath) { continue; }
string relativePath = UpdaterUtil.GetRelativePath(file, item.Directory); string relativePath = Path.GetRelativePath(item.Directory, file);
string fullPath = Path.GetFullPath(relativePath); string fullPath = Path.GetFullPath(relativePath);
if (contentPackage.Files.Any(f => { string fp = Path.GetFullPath(f.Path); return fp == fullPath; })) { continue; } if (contentPackage.Files.Any(f => { string fp = Path.GetFullPath(f.Path); return fp == fullPath; })) { continue; }
nonContentFiles.Add(relativePath); nonContentFiles.Add(relativePath);
@@ -3,7 +3,6 @@ using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reflection.Metadata;
namespace Barotrauma.Particles namespace Barotrauma.Particles
{ {
@@ -16,6 +15,8 @@ namespace Barotrauma.Particles
public delegate void OnChangeHullHandler(Vector2 position, Hull currentHull); public delegate void OnChangeHullHandler(Vector2 position, Hull currentHull);
public OnChangeHullHandler OnChangeHull; public OnChangeHullHandler OnChangeHull;
public OnChangeHullHandler OnCollision;
private Vector2 position; private Vector2 position;
private Vector2 prevPosition; private Vector2 prevPosition;
@@ -166,6 +167,7 @@ namespace Barotrauma.Particles
HighQualityCollisionDetection = false; HighQualityCollisionDetection = false;
OnChangeHull = null; OnChangeHull = null;
OnCollision = null;
subEmitters.Clear(); subEmitters.Clear();
hasSubEmitters = false; hasSubEmitters = false;
@@ -340,12 +342,20 @@ namespace Barotrauma.Particles
Vector2 collisionNormal = Vector2.Zero; Vector2 collisionNormal = Vector2.Zero;
if (velocity.Y < 0.0f && position.Y - prefab.CollisionRadius * size.Y < hullRect.Y - hullRect.Height) if (velocity.Y < 0.0f && position.Y - prefab.CollisionRadius * size.Y < hullRect.Y - hullRect.Height)
{ {
if (prefab.DeleteOnCollision) { return UpdateResult.Delete; } if (prefab.DeleteOnCollision)
{
OnCollision?.Invoke(position, currentHull);
return UpdateResult.Delete;
}
collisionNormal = new Vector2(0.0f, 1.0f); collisionNormal = new Vector2(0.0f, 1.0f);
} }
else if (velocity.Y > 0.0f && position.Y + prefab.CollisionRadius * size.Y > hullRect.Y) else if (velocity.Y > 0.0f && position.Y + prefab.CollisionRadius * size.Y > hullRect.Y)
{ {
if (prefab.DeleteOnCollision) { return UpdateResult.Delete; } if (prefab.DeleteOnCollision)
{
OnCollision?.Invoke(position, currentHull);
return UpdateResult.Delete;
}
collisionNormal = new Vector2(0.0f, -1.0f); collisionNormal = new Vector2(0.0f, -1.0f);
} }
@@ -368,6 +378,7 @@ namespace Barotrauma.Particles
handleCollision(gapFound, collisionNormal); handleCollision(gapFound, collisionNormal);
} }
collisionNormal = Vector2.Zero;
if (velocity.X < 0.0f && position.X - prefab.CollisionRadius * size.X < hullRect.X) if (velocity.X < 0.0f && position.X - prefab.CollisionRadius * size.X < hullRect.X)
{ {
if (prefab.DeleteOnCollision) { return UpdateResult.Delete; } if (prefab.DeleteOnCollision) { return UpdateResult.Delete; }
@@ -487,6 +498,8 @@ namespace Barotrauma.Particles
velocity.Y = Math.Sign(collisionNormal.Y) * Math.Abs(velocity.Y) * prefab.Restitution; velocity.Y = Math.Sign(collisionNormal.Y) * Math.Abs(velocity.Y) * prefab.Restitution;
} }
OnCollision?.Invoke(position, currentHull);
velocity += subVel; velocity += subVel;
} }
@@ -523,6 +536,8 @@ namespace Barotrauma.Particles
velocity.Y *= (1.0f - prefab.Friction); velocity.Y *= (1.0f - prefab.Friction);
} }
OnCollision?.Invoke(position, currentHull);
velocity *= prefab.Restitution; velocity *= prefab.Restitution;
} }
@@ -138,6 +138,8 @@ namespace Barotrauma.Particles
public void Emit(float deltaTime, Vector2 position, Hull hullGuess = null, float angle = 0.0f, float particleRotation = 0.0f, float velocityMultiplier = 1.0f, float sizeMultiplier = 1.0f, float amountMultiplier = 1.0f, Color? colorMultiplier = null, ParticlePrefab overrideParticle = null, Tuple<Vector2, Vector2> tracerPoints = null) public void Emit(float deltaTime, Vector2 position, Hull hullGuess = null, float angle = 0.0f, float particleRotation = 0.0f, float velocityMultiplier = 1.0f, float sizeMultiplier = 1.0f, float amountMultiplier = 1.0f, Color? colorMultiplier = null, ParticlePrefab overrideParticle = null, Tuple<Vector2, Vector2> tracerPoints = null)
{ {
if (GameMain.Client?.MidRoundSyncing ?? false) { return; }
if (initialDelay < Prefab.Properties.InitialDelay) if (initialDelay < Prefab.Properties.InitialDelay)
{ {
initialDelay += deltaTime; initialDelay += deltaTime;
@@ -133,22 +133,41 @@ namespace Barotrauma.Particles
public Particle CreateParticle(ParticlePrefab prefab, Vector2 position, Vector2 velocity, float rotation = 0.0f, Hull hullGuess = null, bool drawOnTop = false, float collisionIgnoreTimer = 0f, Tuple<Vector2, Vector2> tracerPoints = null) public Particle CreateParticle(ParticlePrefab prefab, Vector2 position, Vector2 velocity, float rotation = 0.0f, Hull hullGuess = null, bool drawOnTop = false, float collisionIgnoreTimer = 0f, Tuple<Vector2, Vector2> tracerPoints = null)
{ {
if (particleCount >= MaxParticles || prefab == null || prefab.Sprites.Count == 0) { return null; } if (prefab == null || prefab.Sprites.Count == 0) { return null; }
// this should be optimized for tracers after prototyping if (particleCount >= MaxParticles)
if (tracerPoints == null)
{ {
Vector2 particleEndPos = prefab.CalculateEndPosition(position, velocity); for (int i = 0; i < particleCount; i++)
{
Vector2 minPos = new Vector2(Math.Min(position.X, particleEndPos.X), Math.Min(position.Y, particleEndPos.Y)); if (particles[i].Prefab.Priority < prefab.Priority)
Vector2 maxPos = new Vector2(Math.Max(position.X, particleEndPos.X), Math.Max(position.Y, particleEndPos.Y)); {
RemoveParticle(i);
Rectangle expandedViewRect = MathUtils.ExpandRect(cam.WorldView, MaxOutOfViewDist); break;
}
if (minPos.X > expandedViewRect.Right || maxPos.X < expandedViewRect.X) { return null; } }
if (minPos.Y > expandedViewRect.Y || maxPos.Y < expandedViewRect.Y - expandedViewRect.Height) { return null; } if (particleCount >= MaxParticles) { return null; }
} }
Vector2 particleEndPos = prefab.CalculateEndPosition(position, velocity);
Vector2 minPos = new Vector2(Math.Min(position.X, particleEndPos.X), Math.Min(position.Y, particleEndPos.Y));
Vector2 maxPos = new Vector2(Math.Max(position.X, particleEndPos.X), Math.Max(position.Y, particleEndPos.Y));
if (tracerPoints != null)
{
minPos = new Vector2(
Math.Min(Math.Min(minPos.X, tracerPoints.Item1.X), tracerPoints.Item2.X),
Math.Min(Math.Min(minPos.Y, tracerPoints.Item1.Y), tracerPoints.Item2.Y));
maxPos = new Vector2(
Math.Max(Math.Max(maxPos.X, tracerPoints.Item1.X), tracerPoints.Item2.X),
Math.Max(Math.Max(maxPos.Y, tracerPoints.Item1.Y), tracerPoints.Item2.Y));
}
Rectangle expandedViewRect = MathUtils.ExpandRect(cam.WorldView, MaxOutOfViewDist);
if (minPos.X > expandedViewRect.Right || maxPos.X < expandedViewRect.X) { return null; }
if (minPos.Y > expandedViewRect.Y || maxPos.Y < expandedViewRect.Y - expandedViewRect.Height) { return null; }
if (particles[particleCount] == null) particles[particleCount] = new Particle(); if (particles[particleCount] == null) particles[particleCount] = new Particle();
particles[particleCount].Init(prefab, position, velocity, rotation, hullGuess, drawOnTop, collisionIgnoreTimer, tracerPoints: tracerPoints); particles[particleCount].Init(prefab, position, velocity, rotation, hullGuess, drawOnTop, collisionIgnoreTimer, tracerPoints: tracerPoints);
@@ -165,7 +184,7 @@ namespace Barotrauma.Particles
public ParticlePrefab FindPrefab(string prefabName) public ParticlePrefab FindPrefab(string prefabName)
{ {
return Prefabs.Find(p => p.Identifier == prefabName); return Prefabs.Find(p => p.Identifier.Equals(prefabName, StringComparison.OrdinalIgnoreCase));
} }
private void RemoveParticle(int index) private void RemoveParticle(int index)
@@ -180,16 +180,13 @@ namespace Barotrauma.Particles
[Editable, Serialize("1.0,1.0", false, description: "The maximum initial size of the particle.")] [Editable, Serialize("1.0,1.0", false, description: "The maximum initial size of the particle.")]
public Vector2 StartSizeMax { get; private set; } public Vector2 StartSizeMax { get; private set; }
[Editable] [Editable, Serialize("0.0,0.0", false, description: "How much the size of the particle changes per second. The rate of growth for each particle is randomize between SizeChangeMin and SizeChangeMax.")]
[Serialize("0.0,0.0", false, description: "How much the size of the particle changes per second. The rate of growth for each particle is randomize between SizeChangeMin and SizeChangeMax.")]
public Vector2 SizeChangeMin { get; private set; } public Vector2 SizeChangeMin { get; private set; }
[Editable] [Editable, Serialize("0.0,0.0", false, description: "How much the size of the particle changes per second. The rate of growth for each particle is randomize between SizeChangeMin and SizeChangeMax.")]
[Serialize("0.0,0.0", false, description: "How much the size of the particle changes per second. The rate of growth for each particle is randomize between SizeChangeMin and SizeChangeMax.")]
public Vector2 SizeChangeMax { get; private set; } public Vector2 SizeChangeMax { get; private set; }
[Editable] [Editable, Serialize(0.0f, false, description: "How many seconds it takes for the particle to grow to it's initial size.")]
[Serialize(0.0f, false, description: "How many seconds it takes for the particle to grow to it's initial size.")]
public float GrowTime { get; private set; } public float GrowTime { get; private set; }
//rendering ----------------------------------------- //rendering -----------------------------------------
@@ -215,6 +212,9 @@ namespace Barotrauma.Particles
[Editable, Serialize(ParticleBlendState.AlphaBlend, false, description: "The type of blending to use when rendering the particle.")] [Editable, Serialize(ParticleBlendState.AlphaBlend, false, description: "The type of blending to use when rendering the particle.")]
public ParticleBlendState BlendState { get; private set; } public ParticleBlendState BlendState { get; private set; }
[Editable, Serialize(0, false, description: "Particles with a higher priority can replace lower-priority ones if the maximum number of active particles has been reached.")]
public int Priority { get; private set; }
//animation ----------------------------------------- //animation -----------------------------------------
[Editable(0.0f, float.MaxValue), Serialize(1.0f, false, description: "The duration of the particle's animation cycle (if it's animated).")] [Editable(0.0f, float.MaxValue), Serialize(1.0f, false, description: "The duration of the particle's animation cycle (if it's animated).")]

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