Unstable 0.1500.0.0

This commit is contained in:
Markus Isberg
2021-08-26 21:08:21 +09:00
parent 265a2e7ab3
commit 501e02c026
245 changed files with 9775 additions and 2034 deletions
@@ -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;
@@ -130,7 +129,7 @@ namespace Barotrauma
} }
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 +160,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 +388,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)
@@ -470,9 +463,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 +561,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 +572,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;
@@ -638,7 +620,7 @@ namespace Barotrauma
if (!enabled) { return; } if (!enabled) { return; }
if (!IsDead && !IsIncapacitated) if (!IsIncapacitated)
{ {
if (soundTimer > 0) if (soundTimer > 0)
{ {
@@ -649,7 +631,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 +649,6 @@ namespace Barotrauma
else else
{ {
PlaySound(CharacterSound.SoundType.Idle); PlaySound(CharacterSound.SoundType.Idle);
} }
break; break;
} }
@@ -827,12 +815,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 +844,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 +854,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 +908,40 @@ 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;
}
/// <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 +969,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 +987,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 +1040,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;
GUI.AddMessage(
"+" + TextManager.GetWithVariable("currencyformat", "[credits]", increase.ToString()),
GUI.Style.Yellow,
Position + Vector2.UnitY * 150.0f,
Vector2.UnitY * 10.0f,
playSound: true,
subId: Submarine?.ID ?? -1);;
}
}
} }
} }
@@ -391,7 +391,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 +419,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);
} }
} }
@@ -525,12 +544,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 +558,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))
{ {
@@ -170,15 +170,37 @@ namespace Barotrauma
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( GUI.AddMessage(
string.Format("+{0} {1}", increase, TextManager.Get("SkillName." + skillIdentifier)), string.Format("+{0} {1}", increase, TextManager.Get("SkillName." + skillIdentifier)),
GUI.Style.Green, specialIncrease ? GUI.Style.Orange : GUI.Style.Green,
textPopupPos, textPopupPos,
Vector2.UnitY * 10.0f, Vector2.UnitY * 10.0f,
playSound: false, playSound: specialIncrease,
subId: Character?.Submarine?.ID ?? -1);
}
}
partial void OnExperienceChanged(int prevAmount, int newAmount, Vector2 textPopupPos)
{
if (Character.Controlled != null && Character.Controlled.TeamID != TeamID) { return; }
GameSession.TabMenuInstance?.OnExperienceChanged(Character);
if (newAmount > prevAmount)
{
int increase = newAmount - prevAmount;
GUI.AddMessage(
string.Format("+{0} {1}", increase, TextManager.Get("experienceshort")),
GUI.Style.Blue,
textPopupPos,
Vector2.UnitY * 10.0f,
playSound: true,
subId: Character?.Submarine?.ID ?? -1); subId: Character?.Submarine?.ID ?? -1);
} }
} }
@@ -591,6 +613,17 @@ 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);
}
return ch; return ch;
} }
} }
@@ -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, 12);
switch (eventType) switch (eventType)
{ {
case 0: //NetEntityEvent.Type.InventoryState case 0: //NetEntityEvent.Type.InventoryState
@@ -387,6 +395,7 @@ namespace Barotrauma
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,23 @@ 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++)
{
UInt32 talentIdentifier = msg.ReadUInt32();
GiveTalent(talentIdentifier);
}
break;
case 12: //NetEntityEvent.Type.UpdateMoney:
int moneyAmount = msg.ReadInt32();
SetMoney(moneyAmount);
break;
} }
msg.ReadPadBits(); msg.ReadPadBits();
break; break;
@@ -543,7 +543,7 @@ namespace Barotrauma
else else
{ {
var causeOfDeath = GetCauseOfDeath(); var causeOfDeath = GetCauseOfDeath();
Character.Controlled.Kill(causeOfDeath.First, causeOfDeath.Second); Character.Controlled.Kill(causeOfDeath.type, causeOfDeath.affliction);
Character.Controlled = null; Character.Controlled = null;
} }
} }
@@ -683,19 +683,33 @@ namespace Barotrauma
float particleMaxScale = emitter?.Prefab.Properties.ScaleMax ?? 1; float particleMaxScale = emitter?.Prefab.Properties.ScaleMax ?? 1;
float severity = Math.Min(affliction.Strength / affliction.Prefab.MaxStrength * Character.Params.BleedParticleMultiplier, 1); float severity = Math.Min(affliction.Strength / affliction.Prefab.MaxStrength * Character.Params.BleedParticleMultiplier, 1);
float bloodParticleSize = MathHelper.Lerp(particleMinScale, particleMaxScale, severity); float bloodParticleSize = MathHelper.Lerp(particleMinScale, particleMaxScale, severity);
Vector2 velocity = Rand.Vector(affliction.Strength * 0.1f);
if (!inWater) if (!inWater)
{ {
bloodParticleSize *= 2.0f; bloodParticleSize *= 2.0f;
velocity = targetLimb.LinearVelocity * 100.0f;
} }
// TODO: use the blood emitter? // TODO: use the blood emitter?
var blood = GameMain.ParticleManager.CreateParticle( var blood = GameMain.ParticleManager.CreateParticle(
inWater ? Character.Params.BleedParticleWater : Character.Params.BleedParticleAir, inWater ? Character.Params.BleedParticleWater : Character.Params.BleedParticleAir,
targetLimb.WorldPosition, Rand.Vector(affliction.Strength), 0.0f, Character.AnimController.CurrentHull); targetLimb.WorldPosition, velocity, 0.0f, Character.AnimController.CurrentHull);
if (blood != null) if (blood != null && !inWater)
{ {
blood.Size *= bloodParticleSize; blood.Size *= bloodParticleSize;
if (!string.IsNullOrEmpty(Character.BloodDecalName) && Rand.Range(0.0f, 1.0f) < 0.05f)
{
blood.OnCollision += (Vector2 pos, Hull hull) =>
{
var decal = hull?.AddDecal(Character.BloodDecalName, pos, Rand.Range(1.0f, 2.0f), isNetworkEvent: true);
if (decal != null)
{
decal.FadeTimer = decal.LifeTime - decal.FadeOutTime * 2;
}
};
}
} }
bloodParticleTimer = MathHelper.Lerp(2, 0.5f, severity); bloodParticleTimer = MathHelper.Lerp(2, 0.5f, severity);
} }
@@ -1968,9 +1982,9 @@ namespace Barotrauma
healthBarHolder.Visible = value; healthBarHolder.Visible = value;
} }
private readonly List<Pair<AfflictionPrefab, float>> newAfflictions = new List<Pair<AfflictionPrefab, float>>(); private readonly List<(AfflictionPrefab afflictionPrefab, float strength)> newAfflictions = new List<(AfflictionPrefab afflictionPrefab, float strength)>();
private readonly List<Triplet<LimbHealth, AfflictionPrefab, float>> newLimbAfflictions = new List<Triplet<LimbHealth, AfflictionPrefab, float>>(); private readonly List<(LimbHealth limb, AfflictionPrefab afflictionPrefab, float strength)> newLimbAfflictions = new List<(LimbHealth limb, AfflictionPrefab afflictionPrefab, float strength)>();
private readonly List<Pair<AfflictionPrefab.PeriodicEffect, float>> newPeriodicEffects = new List<Pair<AfflictionPrefab.PeriodicEffect, float>>(); private readonly List<(AfflictionPrefab.PeriodicEffect effect, float timer)> newPeriodicEffects = new List<(AfflictionPrefab.PeriodicEffect effect, float timer)>();
public void ClientRead(IReadMessage inc) public void ClientRead(IReadMessage inc)
{ {
@@ -1997,41 +2011,41 @@ namespace Barotrauma
for (int j = 0; j < periodicAfflictionCount; j++) for (int j = 0; j < periodicAfflictionCount; j++)
{ {
float periodicAfflictionTimer = inc.ReadRangedSingle(afflictionPrefab.PeriodicEffects[j].MinInterval, afflictionPrefab.PeriodicEffects[j].MaxInterval, 8); float periodicAfflictionTimer = inc.ReadRangedSingle(afflictionPrefab.PeriodicEffects[j].MinInterval, afflictionPrefab.PeriodicEffects[j].MaxInterval, 8);
newPeriodicEffects.Add(new Pair<AfflictionPrefab.PeriodicEffect, float>(afflictionPrefab.PeriodicEffects[j], periodicAfflictionTimer)); newPeriodicEffects.Add((afflictionPrefab.PeriodicEffects[j], periodicAfflictionTimer));
} }
newAfflictions.Add(new Pair<AfflictionPrefab, float>(afflictionPrefab, afflictionStrength)); newAfflictions.Add((afflictionPrefab, afflictionStrength));
} }
foreach (Affliction affliction in afflictions) foreach (Affliction affliction in afflictions)
{ {
//deactivate afflictions that weren't included in the network message //deactivate afflictions that weren't included in the network message
if (!newAfflictions.Any(a => a.First == affliction.Prefab)) if (!newAfflictions.Any(a => a.afflictionPrefab == affliction.Prefab))
{ {
affliction.Strength = 0.0f; affliction.Strength = 0.0f;
} }
} }
foreach (Pair<AfflictionPrefab, float> newAffliction in newAfflictions) foreach (var (afflictionPrefab, strength) in newAfflictions)
{ {
Affliction existingAffliction = afflictions.Find(a => a.Prefab == newAffliction.First); Affliction existingAffliction = afflictions.Find(a => a.Prefab == afflictionPrefab);
if (existingAffliction == null) if (existingAffliction == null)
{ {
existingAffliction = newAffliction.First.Instantiate(newAffliction.Second); existingAffliction = afflictionPrefab.Instantiate(strength);
afflictions.Add(existingAffliction); afflictions.Add(existingAffliction);
} }
existingAffliction.SetStrength(newAffliction.Second); existingAffliction.SetStrength(strength);
if (existingAffliction == stunAffliction) if (existingAffliction == stunAffliction)
{ {
Character.SetStun(existingAffliction.Strength, true, true); Character.SetStun(existingAffliction.Strength, true, true);
} }
foreach (var periodicEffect in newPeriodicEffects) foreach (var periodicEffect in newPeriodicEffects)
{ {
if (!existingAffliction.Prefab.PeriodicEffects.Contains(periodicEffect.First)) { continue; } if (!existingAffliction.Prefab.PeriodicEffects.Contains(periodicEffect.effect)) { continue; }
//timer has wrapped around, apply the effect //timer has wrapped around, apply the effect
if (periodicEffect.Second - existingAffliction.PeriodicEffectTimers[periodicEffect.First] > periodicEffect.First.MinInterval / 2) if (periodicEffect.timer - existingAffliction.PeriodicEffectTimers[periodicEffect.effect] > periodicEffect.effect.MinInterval / 2)
{ {
existingAffliction.PeriodicEffectTimers[periodicEffect.First] = periodicEffect.Second; existingAffliction.PeriodicEffectTimers[periodicEffect.effect] = periodicEffect.timer;
foreach (StatusEffect effect in periodicEffect.First.StatusEffects) foreach (StatusEffect effect in periodicEffect.effect.StatusEffects)
{ {
existingAffliction.ApplyStatusEffect(ActionType.OnActive, effect, deltaTime: 1.0f, this, targetLimb: null); existingAffliction.ApplyStatusEffect(ActionType.OnActive, effect, deltaTime: 1.0f, this, targetLimb: null);
} }
@@ -2063,9 +2077,9 @@ namespace Barotrauma
for (int j = 0; j < periodicAfflictionCount; j++) for (int j = 0; j < periodicAfflictionCount; j++)
{ {
float periodicAfflictionTimer = inc.ReadRangedSingle(afflictionPrefab.PeriodicEffects[j].MinInterval, afflictionPrefab.PeriodicEffects[j].MaxInterval, 8); float periodicAfflictionTimer = inc.ReadRangedSingle(afflictionPrefab.PeriodicEffects[j].MinInterval, afflictionPrefab.PeriodicEffects[j].MaxInterval, 8);
newPeriodicEffects.Add(new Pair<AfflictionPrefab.PeriodicEffect, float>(afflictionPrefab.PeriodicEffects[j], periodicAfflictionTimer)); newPeriodicEffects.Add((afflictionPrefab.PeriodicEffects[j], periodicAfflictionTimer));
} }
newLimbAfflictions.Add(new Triplet<LimbHealth, AfflictionPrefab, float>(limbHealths[limbIndex], afflictionPrefab, afflictionStrength)); newLimbAfflictions.Add((limbHealths[limbIndex], afflictionPrefab, afflictionStrength));
} }
foreach (LimbHealth limbHealth in limbHealths) foreach (LimbHealth limbHealth in limbHealths)
@@ -2073,33 +2087,33 @@ namespace Barotrauma
foreach (Affliction affliction in limbHealth.Afflictions) foreach (Affliction affliction in limbHealth.Afflictions)
{ {
//deactivate afflictions that weren't included in the network message //deactivate afflictions that weren't included in the network message
if (!newLimbAfflictions.Any(a => a.First == limbHealth && a.Second == affliction.Prefab)) if (!newLimbAfflictions.Any(a => a.limb == limbHealth && a.afflictionPrefab == affliction.Prefab))
{ {
affliction.Strength = 0.0f; affliction.Strength = 0.0f;
} }
} }
foreach (Triplet<LimbHealth, AfflictionPrefab, float> newAffliction in newLimbAfflictions) foreach (var (limb, afflictionPrefab, strength) in newLimbAfflictions)
{ {
if (newAffliction.First != limbHealth) { continue; } if (limb != limbHealth) { continue; }
Affliction existingAffliction = limbHealth.Afflictions.Find(a => a.Prefab == newAffliction.Second); Affliction existingAffliction = limbHealth.Afflictions.Find(a => a.Prefab == afflictionPrefab);
if (existingAffliction == null) if (existingAffliction == null)
{ {
existingAffliction = newAffliction.Second.Instantiate(newAffliction.Third); existingAffliction = afflictionPrefab.Instantiate(strength);
limbHealth.Afflictions.Add(existingAffliction); limbHealth.Afflictions.Add(existingAffliction);
} }
existingAffliction.SetStrength(newAffliction.Third); existingAffliction.SetStrength(strength);
foreach (var periodicEffect in newPeriodicEffects) foreach (var periodicEffect in newPeriodicEffects)
{ {
if (!existingAffliction.Prefab.PeriodicEffects.Contains(periodicEffect.First)) { continue; } if (!existingAffliction.Prefab.PeriodicEffects.Contains(periodicEffect.effect)) { continue; }
//timer has wrapped around, apply the effect //timer has wrapped around, apply the effect
if (periodicEffect.Second - existingAffliction.PeriodicEffectTimers[periodicEffect.First] > periodicEffect.First.MinInterval / 2) if (periodicEffect.timer - existingAffliction.PeriodicEffectTimers[periodicEffect.effect] > periodicEffect.effect.MinInterval / 2)
{ {
existingAffliction.PeriodicEffectTimers[periodicEffect.First] = periodicEffect.Second; existingAffliction.PeriodicEffectTimers[periodicEffect.effect] = periodicEffect.timer;
foreach (StatusEffect effect in periodicEffect.First.StatusEffects) foreach (StatusEffect effect in periodicEffect.effect.StatusEffects)
{ {
Limb targetLimb = Character.AnimController.Limbs.FirstOrDefault(l => l.HealthIndex == limbHealths.IndexOf(newAffliction.First)); Limb targetLimb = Character.AnimController.Limbs.FirstOrDefault(l => l.HealthIndex == limbHealths.IndexOf(limb));
existingAffliction.ApplyStatusEffect(ActionType.OnActive, effect, deltaTime: 1.0f, this, targetLimb: targetLimb); existingAffliction.ApplyStatusEffect(ActionType.OnActive, effect, deltaTime: 1.0f, this, targetLimb: targetLimb);
} }
} }
@@ -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(); });
} }
})); }));
@@ -608,7 +608,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,6 +689,9 @@ namespace Barotrauma
AssignRelayToServer("setskill", true); AssignRelayToServer("setskill", true);
AssignRelayToServer("readycheck", true); AssignRelayToServer("readycheck", true);
AssignRelayToServer("givetalent", true);
AssignRelayToServer("giveexperience", true);
AssignOnExecute("control", (string[] args) => AssignOnExecute("control", (string[] args) =>
{ {
if (args.Length < 1) return; if (args.Length < 1) return;
@@ -163,6 +163,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 +307,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 +673,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);
@@ -927,13 +934,14 @@ namespace Barotrauma
GUIComponent prevMouseOn = MouseOn; GUIComponent prevMouseOn = MouseOn;
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)
{ {
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)
@@ -1050,7 +1056,7 @@ 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; var hoverParent = c;
while (true) while (true)
{ {
@@ -1059,14 +1065,14 @@ namespace Barotrauma
hoverParent = hoverParent.Parent; hoverParent = hoverParent.Parent;
} }
} }
if (parent != null && parent.CanBeFocused) if (parent != null && parent.CanBeFocused)
{ {
if (!parent.Rect.Equals(monitorRect)) { return parent.HoverCursor; } if (!parent.Rect.Equals(monitorRect)) { return parent.HoverCursor; }
} }
} }
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 +1349,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 +1357,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 +1423,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 +1526,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)
@@ -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))
{ {
@@ -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);
} }
} }
} }
@@ -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,8 +233,21 @@ 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;
@@ -472,7 +485,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 +531,7 @@ namespace Barotrauma
if (currIndex != index) if (currIndex != index)
{ {
draggedElement.RectTransform.RepositionChildInHierarchy(currIndex); draggedElement.RectTransform.RepositionChildInHierarchy(currIndex);
HasDraggedElementIndexChanged = true;
} }
return; return;
@@ -577,7 +591,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;
} }
@@ -750,7 +764,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)
{ {
@@ -773,7 +787,6 @@ namespace Barotrauma
ScrollBar.BarScroll -= (PlayerInput.ScrollWheelSpeed / 500.0f) * BarSize; ScrollBar.BarScroll -= (PlayerInput.ScrollWheelSpeed / 500.0f) * BarSize;
} }
} }
ScrollBar.Enabled = ScrollBarEnabled && BarSize < 1.0f; ScrollBar.Enabled = ScrollBarEnabled && BarSize < 1.0f;
if (AutoHideScrollBar) if (AutoHideScrollBar)
@@ -785,6 +798,13 @@ namespace Barotrauma
UpdateDimensions(); UpdateDimensions();
} }
} }
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)
{ {
@@ -982,7 +1002,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();
} }
@@ -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);
}
}
}
@@ -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,11 +41,16 @@ 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 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
@@ -235,6 +241,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;
@@ -247,6 +256,9 @@ namespace Barotrauma
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 +289,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);
@@ -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 ...";
} }
@@ -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);
} }
/// <summary>
/// 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; }
private static void DrawPolygonEdge(SpriteBatch spriteBatch, Texture2D texture, Vector2 point1, Vector2 point2, if (points.Count == 1)
Color color, float thickness) {
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,6 +591,10 @@ namespace Barotrauma
resolutionWhenCreated = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight); resolutionWhenCreated = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
} }
private string GetMerchantBalanceText() => GetCurrencyFormatted(CurrentLocation?.StoreCurrentBalance ?? 0);
private string GetPlayerBalanceText() => GetCurrencyFormatted(PlayerMoney);
private GUILayoutGroup CreateDealsGroup(GUIListBox parentList) private GUILayoutGroup CreateDealsGroup(GUIListBox parentList)
{ {
var elementHeight = (int)(GUI.yScale * 80); var elementHeight = (int)(GUI.yScale * 80);
@@ -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();
} }
@@ -18,7 +18,7 @@ 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 }; private enum InfoFrameTab { Crew, Mission, Reputation, MyCharacter, Traitor, Submarine, Talents };
private static InfoFrameTab selectedTab; private static InfoFrameTab selectedTab;
private GUIFrame infoFrame, contentFrame; private GUIFrame infoFrame, contentFrame;
@@ -258,6 +258,8 @@ namespace Barotrauma
{ {
var myCharacterButton = createTabButton(InfoFrameTab.MyCharacter, "tabmenu.character"); var myCharacterButton = createTabButton(InfoFrameTab.MyCharacter, "tabmenu.character");
} }
var talentsButton = createTabButton(InfoFrameTab.Talents, "tabmenu.talents");
} }
private bool SelectInfoFrameTab(GUIButton button, object userData) private bool SelectInfoFrameTab(GUIButton button, object userData)
@@ -296,6 +298,9 @@ namespace Barotrauma
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 +1164,319 @@ 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, GUIImage background, GUIImage icon)> talentButtons = new List<(GUIButton button, GUIImage background, GUIImage icon)>();
private List<string> selectedTalents = new List<string>();
private GUITextBlock talentTitleText;
private GUITextBlock talentDescriptionText;
private GUITextBlock talentPointsText;
private GUITextBlock experienceText;
private Color experienceBackgroundColor = new Color(255, 255, 255, 155);
private GUIProgressBar experienceBar;
private void CreateTalentInfo(GUIFrame infoFrame)
{
infoFrame.ClearChildren();
talentButtons.Clear();
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);
Character controlledCharacter = Character.Controlled;
if (controlledCharacter.Info == null)
{
DebugConsole.ThrowError("No character info found for talent UI");
return;
}
selectedTalents = controlledCharacter.Info.UnlockedTalents.ToList();
GUILayoutGroup talentFrameLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), talentFrameContent.RectTransform, anchor: Anchor.Center), childAnchor: Anchor.TopCenter)
{
AbsoluteSpacing = GUI.IntScale(5)
};
GUILayoutGroup talentInfoLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.325f), talentFrameLayoutGroup.RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter);
GUIFrame talentTitleFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.25f), talentInfoLayoutGroup.RectTransform, Anchor.TopCenter), style: null);
talentTitleText = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), talentTitleFrame.RectTransform, Anchor.TopLeft), "", font: GUI.LargeFont);
talentPointsText = new GUITextBlock(new RectTransform(new Vector2(0.25f, 1.0f), talentTitleFrame.RectTransform, Anchor.TopRight), "", font: GUI.Font, textAlignment: Alignment.Center);
GUIFrame talentDescriptionFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.4f), talentInfoLayoutGroup.RectTransform, Anchor.TopCenter), style: null);
talentDescriptionText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), talentDescriptionFrame.RectTransform, Anchor.TopLeft), "", font: GUI.Font, textAlignment: Alignment.TopLeft, wrap: true);
GUIFrame characterInfoFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.3f), talentInfoLayoutGroup.RectTransform, Anchor.TopLeft), style: null);
GUILayoutGroup characterInfoColumn = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), characterInfoFrame.RectTransform, anchor: Anchor.TopLeft), childAnchor: Anchor.TopLeft, isHorizontal: true);
CreateCharacterSheet(characterInfoColumn);
if (!TalentTree.JobTalentTrees.TryGetValue(controlledCharacter.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { return; }
GUIListBox talentTreeListBox = new GUIListBox(new RectTransform(new Vector2(1f, 0.6f), talentFrameLayoutGroup.RectTransform, Anchor.TopCenter), isHorizontal: true, style: null);
int spacing = GUI.IntScale(5);
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: "SubtreeHeader");
new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), subtreeTitleFrame.RectTransform, anchor: Anchor.TopCenter), subTree.Identifier, font: GUI.LargeFont, 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: "TalentOptionFrame");
GUIImage talentBackground = new GUIImage(new RectTransform(Vector2.One, talentOptionFrame.RectTransform, anchor: Anchor.Center), style: "TalentBackground")
{
CanBeFocused = false,
Color = unselectableColor,
};
if (subTree.TalentOptionStages.Count > i)
{
TalentOption talentOption = subTree.TalentOptionStages[i];
GUILayoutGroup talentOptionLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 1f), talentOptionFrame.RectTransform, Anchor.CenterLeft), isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
Stretch = true,
};
foreach (Talent talent in talentOption.Talents)
{
int optionPadding = GUI.IntScale(10);
GUIFrame talentFrame = new GUIFrame(new RectTransform(new Point(talentOptionFrame.Rect.Width, talentOptionFrame.Rect.Height - optionPadding), talentOptionLayoutGroup.RectTransform), style: null)
{
CanBeFocused = false,
};
new GUIImage(new RectTransform(Vector2.One, talentFrame.RectTransform, anchor: Anchor.Center), style: "TalentFrameBackground")
{
CanBeFocused = false,
};
GUIImage iconImage = null;
GUIButton talentButton = new GUIButton(new RectTransform(Vector2.One, talentFrame.RectTransform, anchor: Anchor.Center), style: "TalentFrame")
{
ToolTip = $"{TextManager.Get("talentname." + talent.Identifier, returnNull: true) ?? talent.Identifier} \n\n{TextManager.Get("talentdescription." + talent.Identifier, returnNull: true) ?? string.Empty}",
UserData = talent.Identifier,
PressedColor = pressedColor,
OnClicked = (button, userData) =>
{
talentTitleText.Text = TextManager.Get("talentname." + talent.Identifier, returnNull: true) ?? string.Empty;
talentDescriptionText.Text = TextManager.Get("talentdescription." + talent.Identifier, returnNull: true) ?? string.Empty;
// 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;
},
};
int iconPadding = GUI.IntScale(15);
iconImage = new GUIImage(new RectTransform(new Point(talentFrame.Rect.Width - iconPadding, talentFrame.Rect.Height - iconPadding), talentFrame.RectTransform, anchor: Anchor.Center), sprite: talent.Icon, scaleToFit: true)
{
PressedColor = unselectableColor,
CanBeFocused = false,
};
talentButtons.Add((talentButton, talentBackground, iconImage));
}
}
}
}
GUIFrame talentBottomFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.07f), talentFrameLayoutGroup.RectTransform, Anchor.TopCenter), style: null);
GUIFrame experienceBarFrame = new GUIFrame(new RectTransform(new Vector2(0.775f, 0.75f), talentBottomFrame.RectTransform, Anchor.TopCenter), style: null);
experienceBar = new GUIProgressBar(new RectTransform(new Vector2(1f, 1f), experienceBarFrame.RectTransform, Anchor.CenterLeft),
barSize: controlledCharacter.Info.GetProgressTowardsNextLevel(), color: Color.White, style: "ExperienceBar")
{
IsHorizontal = true
};
GUIImage experienceTextBackground = new GUIImage(new RectTransform(new Vector2(0.2f, 0.475f), experienceBarFrame.RectTransform, anchor: Anchor.Center), style: "ExperienceTextBackground");
experienceText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), experienceTextBackground.RectTransform, anchor: Anchor.Center), "", font: GUI.Font, textColor: Color.White, textAlignment: Alignment.Center);
new GUIButton(new RectTransform(new Vector2(0.1f, 0.3f), talentBottomFrame.RectTransform, anchor: Anchor.TopRight), text: TextManager.Get("applysettingsbutton"))
{
OnClicked = ApplyTalentSelection,
};
new GUIButton(new RectTransform(new Vector2(0.1f, 0.3f), talentBottomFrame.RectTransform, anchor: Anchor.TopLeft), text: TextManager.Get("reset"))
{
OnClicked = ResetTalentSelection,
};
UpdateTalentButtons();
}
private void UpdateTalentButtons()
{
Character controlledCharacter = Character.Controlled;
talentPointsText.Text = $"{TextManager.Get("talentpointsleft")}{controlledCharacter.Info.GetAvailableTalentPoints()}";
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);
foreach (var talentButton in talentButtons)
{
talentButton.background.Color = unselectableColor;
}
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;
if (controlledCharacter.HasTalent(talentIdentifier))
{
newTalentColor = ownedColor;
}
else if (selectedTalents.Contains(talentIdentifier))
{
newTalentColor = selectedColor;
}
talentButton.button.Color = newTalentColor;
talentButton.button.SelectedColor = newTalentColor;
talentButton.button.HoverColor = newTalentColor;
talentButton.button.DisabledColor = newTalentColor;
talentButton.icon.Color = newTalentColor;
// update background color as well, if not defined yet
if (talentButton.background.Color == unselectableColor)
{
talentButton.background.Color = newTalentColor;
}
}
}
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.UnlockedTalents.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.MaximumHealthMultiplier,
StatTypes.MovementSpeed,
StatTypes.SwimmingSpeed,
StatTypes.RepairSpeed,
};
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
{ {
@@ -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;
@@ -239,7 +249,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 +576,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 +631,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 +804,16 @@ namespace Barotrauma
} }
#if DEBUG #if DEBUG
if (TitleScreen.LoadState >= 100.0f && !TitleScreen.PlayingSplashScreen && (Config.AutomaticQuickStartEnabled || Config.AutomaticCampaignLoadEnabled) && FirstLoad && !PlayerInput.KeyDown(Keys.LeftShift)) if (TitleScreen.LoadState >= 100.0f && !TitleScreen.PlayingSplashScreen && (Config.AutomaticQuickStartEnabled || Config.AutomaticCampaignLoadEnabled || Config.TestScreenEnabled) && FirstLoad && !PlayerInput.KeyDown(Keys.LeftShift))
{ {
loadingScreenOpen = false; loadingScreenOpen = false;
FirstLoad = false; FirstLoad = false;
if (Config.AutomaticQuickStartEnabled) if (Config.TestScreenEnabled)
{
TestScreen.Select();
}
else if (Config.AutomaticQuickStartEnabled)
{ {
MainMenuScreen.QuickStart(); MainMenuScreen.QuickStart();
} }
@@ -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
@@ -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();
} }
@@ -24,7 +24,7 @@ namespace Barotrauma
if (GameMain.NetLobbyScreen.HeadSelectionList != null) { GameMain.NetLobbyScreen.HeadSelectionList.Visible = false; } if (GameMain.NetLobbyScreen.HeadSelectionList != null) { GameMain.NetLobbyScreen.HeadSelectionList.Visible = false; }
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,7 +34,6 @@ namespace Barotrauma
tabMenu = null; tabMenu = null;
NetLobbyScreen.JobInfoFrame = null; NetLobbyScreen.JobInfoFrame = null;
} }
return true; return true;
} }
@@ -44,7 +43,7 @@ namespace Barotrauma
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)
@@ -1518,6 +1518,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,
@@ -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;
@@ -359,7 +359,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)) { 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 +372,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)) { 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 +399,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)) { 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 +414,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)) { 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 +427,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)) { 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;
} }
@@ -7,6 +7,7 @@ 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;
namespace Barotrauma.Items.Components namespace Barotrauma.Items.Components
{ {
@@ -18,7 +19,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 +53,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,6 +95,51 @@ 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; }
@@ -92,7 +148,7 @@ namespace Barotrauma.Items.Components
if (character.ViewTarget != null && (character.ViewTarget is Item item) && item.Prefab.FocusOnSelected) { return; } if (character.ViewTarget != null && (character.ViewTarget is Item item) && item.Prefab.FocusOnSelected) { 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;
} }
@@ -126,7 +126,8 @@ 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(); if (!(inputContainer?.Inventory?.visualSlots is { } visualSlots)) { return; }
var lastSlot = visualSlots.Last();
GUI.DrawRectangle(spriteBatch, GUI.DrawRectangle(spriteBatch,
new Rectangle( new Rectangle(
@@ -255,12 +255,15 @@ 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);
@@ -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", returnNull: true) ?? "Requires recipe to fabricate", 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,30 +336,30 @@ 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; }
var itemIcon = requiredItem.ItemPrefabs.First().InventoryIcon ?? requiredItem.ItemPrefabs.First().sprite; var itemIcon = requiredItem.ItemPrefabs.First().InventoryIcon ?? requiredItem.ItemPrefabs.First().sprite;
Rectangle slotRect = inputContainer.Inventory.visualSlots[slotIndex].Rect; Rectangle slotRect = inputContainer.Inventory.visualSlots[slotIndex].Rect;
itemIcon.Draw( itemIcon.Draw(
@@ -350,6 +368,16 @@ 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); GUI.DrawRectangle(spriteBatch, new Rectangle(slotRect.X, slotRect.Bottom - 8, slotRect.Width, 8), Color.Black * 0.8f, true);
@@ -601,7 +629,7 @@ namespace Barotrauma.Items.Components
var itemPrefab = child.UserData as FabricationRecipe; var itemPrefab = child.UserData as FabricationRecipe;
if (itemPrefab == null) continue; if (itemPrefab == null) continue;
bool canBeFabricated = CanBeFabricated(itemPrefab, availableIngredients); 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)
@@ -149,12 +150,13 @@ namespace Barotrauma.Items.Components
if (item.CurrentHull != null && item.CurrentHull.Surface < item.Rect.Location.Y + position.Y) { continue; } if (item.CurrentHull != null && item.CurrentHull.Surface < item.Rect.Location.Y + position.Y) { continue; }
//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)
{ {
@@ -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; }
@@ -15,6 +15,8 @@ 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 List<ParticleEmitter> particleEmitters = new List<ParticleEmitter>();
@@ -25,6 +27,7 @@ namespace Barotrauma.Items.Components
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;
@@ -46,7 +49,7 @@ 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))); return item.ConditionPercentage < RepairThreshold || character.IsTraitor && item.ConditionPercentage > MinSabotageCondition || (CurrentFixer == character && (!item.IsFullCondition || (character.IsTraitor && item.ConditionPercentage > MinSabotageCondition))) || CanTinker(character);
} }
partial void InitProjSpecific(XElement element) partial void InitProjSpecific(XElement element)
@@ -148,6 +151,20 @@ namespace Barotrauma.Items.Components
return true; return true;
} }
}; };
tinkerButtonText = "Tinker";
tinkeringText = "Tinkering";
TinkerButton = new GUIButton(new RectTransform(new Vector2(0.8f, 0.15f), paddedFrame.RectTransform, Anchor.BottomCenter), tinkerButtonText, style: "GUIButtonSmall")
{
IgnoreLayoutGroups = true,
Visible = false,
OnClicked = (btn, obj) =>
{
requestStartFixAction = FixActions.Tinker;
item.CreateClientEvent(this);
return true;
}
};
} }
partial void UpdateProjSpecific(float deltaTime) partial void UpdateProjSpecific(float deltaTime)
@@ -176,6 +193,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;
@@ -226,6 +244,13 @@ 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 = CanTinker(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 && CanTinker(character)) ?
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");
foreach (GUIComponent c in GuiFrame.GetChild(0).Children) foreach (GUIComponent c in GuiFrame.GetChild(0).Children)
{ {
@@ -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
}; };
@@ -534,20 +534,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);
@@ -478,7 +478,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)
@@ -667,6 +667,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)
{ {
@@ -826,11 +830,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 +1128,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;
@@ -1306,7 +1322,9 @@ namespace Barotrauma
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();
@@ -332,7 +332,7 @@ namespace Barotrauma
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,
@@ -368,7 +368,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,
@@ -1623,6 +1623,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>();
@@ -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);
@@ -238,13 +238,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 +258,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();
@@ -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;
@@ -403,6 +404,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 +420,125 @@ 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
const float padding = 0.001f;
size.X += padding;
size.Y += padding;
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 +557,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>();
@@ -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))
{ {
@@ -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);
} }
@@ -487,6 +497,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 +535,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);
@@ -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).")]
@@ -54,9 +54,11 @@ namespace Barotrauma
executableDir = Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location); executableDir = Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location);
Directory.SetCurrentDirectory(executableDir); Directory.SetCurrentDirectory(executableDir);
SteamManager.Initialize(); SteamManager.Initialize();
EnableNvOptimus();
Game = new GameMain(args); Game = new GameMain(args);
Game.Run(); Game.Run();
Game.Dispose(); Game.Dispose();
FreeNvOptimus();
CrossThread.ProcessTasks(); CrossThread.ProcessTasks();
} }
@@ -263,6 +265,27 @@ namespace Barotrauma
" if you'd like to help fix this bug, you may post it on Barotrauma's GitHub issue tracker: https://github.com/Regalis11/Barotrauma/issues/", filePath); " if you'd like to help fix this bug, you may post it on Barotrauma's GitHub issue tracker: https://github.com/Regalis11/Barotrauma/issues/", filePath);
} }
} }
}
private static IntPtr nvApi64Dll = IntPtr.Zero;
private static void EnableNvOptimus()
{
#if WINDOWS && X64
// We force load nvapi64.dll so nvidia gives us the dedicated GPU on optimus laptops.
// This is not a method for getting optimus that is documented by nvidia, but it works, so...
if (NativeLibrary.TryLoad("nvapi64.dll", out nvApi64Dll))
{
DebugConsole.Log("Loaded nvapi64.dll successfully");
}
#endif #endif
} }
private static void FreeNvOptimus()
{
#warning TODO: determine if we can do this safely
//NativeLibrary.Free(nvApi64Dll);
}
}
#endif
}
@@ -466,7 +466,7 @@ namespace Barotrauma
{ {
var sub = child.UserData as SubmarineInfo; var sub = child.UserData as SubmarineInfo;
if (sub == null) { return; } if (sub == null) { return; }
child.Visible = string.IsNullOrEmpty(filter) ? true : sub.DisplayName.ToLower().Contains(filter.ToLower()); child.Visible = string.IsNullOrEmpty(filter) || sub.DisplayName.ToLower().Contains(filter.ToLower());
} }
} }
@@ -628,7 +628,7 @@ namespace Barotrauma
{ {
TextGetter = () => TextGetter = () =>
{ {
return TextManager.AddPunctuation(':', TextManager.Get("Missions"), $"{Campaign.NumberOfMissionsAtLocation(destination)}/{Campaign.Settings.MaxMissionCount}"); return TextManager.AddPunctuation(':', TextManager.Get("Missions"), $"{Campaign.NumberOfMissionsAtLocation(destination)}/{Campaign.Settings.TotalMaxMissionCount}");
} }
}; };
@@ -735,7 +735,7 @@ namespace Barotrauma
private void UpdateMaxMissions(Location location) private void UpdateMaxMissions(Location location)
{ {
hasMaxMissions = Campaign.NumberOfMissionsAtLocation(location) >= Campaign.Settings.MaxMissionCount; hasMaxMissions = Campaign.NumberOfMissionsAtLocation(location) >= Campaign.Settings.TotalMaxMissionCount;
} }
} }
} }
@@ -262,7 +262,7 @@ namespace Barotrauma.CharacterEditor
{ {
FileSelection.OnFileSelected = (file) => FileSelection.OnFileSelected = (file) =>
{ {
string relativePath = UpdaterUtil.GetRelativePath(Path.GetFullPath(file), Environment.CurrentDirectory); string relativePath = Path.GetRelativePath(Environment.CurrentDirectory, Path.GetFullPath(file));
string destinationPath = relativePath; string destinationPath = relativePath;
//copy file to XML path if it's not located relative to the game's files //copy file to XML path if it's not located relative to the game's files
@@ -541,7 +541,7 @@ namespace Barotrauma
private XElement? ExportXML() private XElement? ExportXML()
{ {
XElement mainElement = new XElement("ScriptedEvent", new XAttribute("identifier", projectName.RemoveWhitespace().ToLower())); XElement mainElement = new XElement("ScriptedEvent", new XAttribute("identifier", projectName.RemoveWhitespace().ToLowerInvariant()));
EditorNode? startNode = null; EditorNode? startNode = null;
foreach (EditorNode eventNode in nodeList.Where(node => node is EventNode || node is SpecialNode)) foreach (EditorNode eventNode in nodeList.Where(node => node is EventNode || node is SpecialNode))
{ {
@@ -25,6 +25,7 @@ namespace Barotrauma
public Effect PostProcessEffect { get; private set; } public Effect PostProcessEffect { get; private set; }
public Effect GradientEffect { get; private set; } public Effect GradientEffect { get; private set; }
public Effect GrainEffect { get; private set; } public Effect GrainEffect { get; private set; }
public Effect BlueprintEffect { get; set; }
public GameScreen(GraphicsDevice graphics, ContentManager content) public GameScreen(GraphicsDevice graphics, ContentManager content)
{ {
@@ -43,12 +44,14 @@ namespace Barotrauma
PostProcessEffect = content.Load<Effect>("Effects/postprocess_opengl"); PostProcessEffect = content.Load<Effect>("Effects/postprocess_opengl");
GradientEffect = content.Load<Effect>("Effects/gradientshader_opengl"); GradientEffect = content.Load<Effect>("Effects/gradientshader_opengl");
GrainEffect = content.Load<Effect>("Effects/grainshader_opengl"); GrainEffect = content.Load<Effect>("Effects/grainshader_opengl");
BlueprintEffect = content.Load<Effect>("Effects/blueprintshader_opengl");
#else #else
//var blurEffect = content.Load<Effect>("Effects/blurshader"); //var blurEffect = content.Load<Effect>("Effects/blurshader");
damageEffect = content.Load<Effect>("Effects/damageshader"); damageEffect = content.Load<Effect>("Effects/damageshader");
PostProcessEffect = content.Load<Effect>("Effects/postprocess"); PostProcessEffect = content.Load<Effect>("Effects/postprocess");
GradientEffect = content.Load<Effect>("Effects/gradientshader"); GradientEffect = content.Load<Effect>("Effects/gradientshader");
GrainEffect = content.Load<Effect>("Effects/grainshader"); GrainEffect = content.Load<Effect>("Effects/grainshader");
BlueprintEffect = content.Load<Effect>("Effects/blueprintshader");
#endif #endif
damageStencil = TextureLoader.FromFile("Content/Map/walldamage.png"); damageStencil = TextureLoader.FromFile("Content/Map/walldamage.png");
@@ -2020,7 +2020,8 @@ namespace Barotrauma
var playerFrame = (GUITextBlock)PlayerList.Content.FindChild(client); var playerFrame = (GUITextBlock)PlayerList.Content.FindChild(client);
if (playerFrame == null) { return; } if (playerFrame == null) { return; }
playerFrame.Text = client.Name; playerFrame.Text = client.Name;
playerFrame.ToolTip = "";
Color color = Color.White; Color color = Color.White;
if (SelectedMode == GameModePreset.PvP) if (SelectedMode == GameModePreset.PvP)
{ {
@@ -2028,15 +2029,28 @@ namespace Barotrauma
{ {
case CharacterTeamType.Team1: case CharacterTeamType.Team1:
color = new Color(0, 110, 150, 255); color = new Color(0, 110, 150, 255);
playerFrame.ToolTip = TextManager.GetWithVariable("teampreference", "[team]", TextManager.Get("teampreference.team1"));
break; break;
case CharacterTeamType.Team2: case CharacterTeamType.Team2:
color = new Color(150, 110, 0, 255); color = new Color(150, 110, 0, 255);
playerFrame.ToolTip = TextManager.GetWithVariable("teampreference", "[team]", TextManager.Get("teampreference.team2"));
break;
default:
playerFrame.ToolTip = TextManager.GetWithVariable("teampreference", "[team]", TextManager.Get("none"));
break; break;
} }
} }
else if (JobPrefab.Prefabs.ContainsKey(client.PreferredJob)) else
{ {
color = JobPrefab.Prefabs[client.PreferredJob].UIColor; if (JobPrefab.Prefabs.ContainsKey(client.PreferredJob))
{
color = JobPrefab.Prefabs[client.PreferredJob].UIColor;
playerFrame.ToolTip = TextManager.GetWithVariable("jobpreference", "[job]", JobPrefab.Prefabs[client.PreferredJob].Name);
}
else
{
playerFrame.ToolTip = TextManager.GetWithVariable("jobpreference", "[job]", TextManager.Get("none"));
}
} }
playerFrame.Color = color * 0.4f; playerFrame.Color = color * 0.4f;
playerFrame.HoverColor = color * 0.6f; playerFrame.HoverColor = color * 0.6f;
@@ -24,7 +24,8 @@ namespace Barotrauma
private GUIFrame menu; private GUIFrame menu;
private GUIListBox serverList; private GUIListBox serverList;
private GUIFrame serverPreview; private GUIFrame serverPreviewContainer;
private GUIListBox serverPreview;
private GUIButton joinButton; private GUIButton joinButton;
private ServerInfo selectedServer; private ServerInfo selectedServer;
@@ -340,11 +341,11 @@ namespace Barotrauma
void RecalculateHolder() void RecalculateHolder()
{ {
float listContainerSubtract = filtersHolder.Visible ? sidebarWidth : 0.0f; float listContainerSubtract = filtersHolder.Visible ? sidebarWidth : 0.0f;
listContainerSubtract += serverPreview.Visible ? sidebarWidth : 0.0f; listContainerSubtract += serverPreviewContainer.Visible ? sidebarWidth : 0.0f;
float toggleButtonsSubtract = 1.1f * filterToggle.Rect.Width / serverListHolder.Rect.Width; float toggleButtonsSubtract = 1.1f * filterToggle.Rect.Width / serverListHolder.Rect.Width;
listContainerSubtract += filterToggle.Visible ? toggleButtonsSubtract : 0.0f; listContainerSubtract += filterToggle.Visible ? toggleButtonsSubtract : 0.0f;
listContainerSubtract += serverPreviewToggleButton.Visible ? toggleButtonsSubtract : 0.0f; listContainerSubtract += serverPreviewContainer.Visible ? toggleButtonsSubtract : 0.0f;
serverListContainer.RectTransform.RelativeSize = new Vector2(1.0f - listContainerSubtract, 1.0f); serverListContainer.RectTransform.RelativeSize = new Vector2(1.0f - listContainerSubtract, 1.0f);
serverListHolder.Recalculate(); serverListHolder.Recalculate();
@@ -567,17 +568,17 @@ namespace Barotrauma
{ {
joinButton.Enabled = true; joinButton.Enabled = true;
selectedServer = serverInfo; selectedServer = serverInfo;
if (!serverPreview.Visible) if (!serverPreviewContainer.Visible)
{ {
serverPreview.RectTransform.RelativeSize = new Vector2(sidebarWidth, 1.0f); serverPreviewContainer.RectTransform.RelativeSize = new Vector2(sidebarWidth, 1.0f);
serverPreviewToggleButton.Visible = true; serverPreviewToggleButton.Visible = true;
serverPreviewToggleButton.IgnoreLayoutGroups = false; serverPreviewToggleButton.IgnoreLayoutGroups = false;
serverPreview.Visible = true; serverPreviewContainer.Visible = true;
serverPreview.IgnoreLayoutGroups = false; serverPreviewContainer.IgnoreLayoutGroups = false;
RecalculateHolder(); RecalculateHolder();
} }
serverInfo.CreatePreviewWindow(serverPreview); serverInfo.CreatePreviewWindow(serverPreview.Content);
btn.Children.ForEach(c => c.SpriteEffects = serverPreview.Visible ? SpriteEffects.None : SpriteEffects.FlipHorizontally); btn.Children.ForEach(c => c.SpriteEffects = serverPreviewContainer.Visible ? SpriteEffects.None : SpriteEffects.FlipHorizontally);
} }
return true; return true;
} }
@@ -592,24 +593,28 @@ namespace Barotrauma
Visible = false, Visible = false,
OnClicked = (btn, userdata) => OnClicked = (btn, userdata) =>
{ {
serverPreview.RectTransform.RelativeSize = new Vector2(0.2f, 1.0f); serverPreviewContainer.RectTransform.RelativeSize = new Vector2(0.2f, 1.0f);
serverPreview.Visible = !serverPreview.Visible; serverPreviewContainer.Visible = !serverPreviewContainer.Visible;
serverPreview.IgnoreLayoutGroups = !serverPreview.Visible; serverPreviewContainer.IgnoreLayoutGroups = !serverPreviewContainer.Visible;
RecalculateHolder(); RecalculateHolder();
btn.Children.ForEach(c => c.SpriteEffects = serverPreview.Visible ? SpriteEffects.None : SpriteEffects.FlipHorizontally); btn.Children.ForEach(c => c.SpriteEffects = serverPreviewContainer.Visible ? SpriteEffects.None : SpriteEffects.FlipHorizontally);
return true; return true;
} }
}; };
serverPreview = new GUIFrame(new RectTransform(new Vector2(sidebarWidth, 1.0f), serverListHolder.RectTransform, Anchor.Center), style: null) serverPreviewContainer = new GUIFrame(new RectTransform(new Vector2(sidebarWidth, 1.0f), serverListHolder.RectTransform, Anchor.Center), style: null)
{ {
Color = new Color(12, 14, 15, 255) * 0.5f, Color = new Color(12, 14, 15, 255) * 0.5f,
OutlineColor = Color.Black, OutlineColor = Color.Black,
IgnoreLayoutGroups = true, IgnoreLayoutGroups = true,
Visible = false Visible = false
}; };
serverPreview = new GUIListBox(new RectTransform(Vector2.One, serverPreviewContainer.RectTransform, Anchor.Center))
{
Padding = Vector4.One * 10 * GUI.Scale
};
// Spacing // Spacing
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.02f), bottomRow.RectTransform), style: null); new GUIFrame(new RectTransform(new Vector2(1.0f, 0.02f), bottomRow.RectTransform), style: null);
@@ -1697,7 +1702,7 @@ namespace Barotrauma
UpdateFriendsList(); UpdateFriendsList();
serverList.ClearChildren(); serverList.ClearChildren();
serverPreview.ClearChildren(); serverPreview.Content.ClearChildren();
joinButton.Enabled = false; joinButton.Enabled = false;
selectedServer = null; selectedServer = null;
@@ -1604,7 +1604,7 @@ namespace Barotrauma
if (string.IsNullOrEmpty(file) || !File.Exists(file)) { continue; } if (string.IsNullOrEmpty(file) || !File.Exists(file)) { continue; }
string modFolder = Path.GetDirectoryName(itemContentPackage.Path); string modFolder = Path.GetDirectoryName(itemContentPackage.Path);
string filePathRelativeToModFolder = UpdaterUtil.GetRelativePath(file, Path.Combine(Environment.CurrentDirectory, modFolder)); string filePathRelativeToModFolder = Path.GetRelativePath(Path.Combine(Environment.CurrentDirectory, modFolder), file);
//file is not inside the mod folder, we need to move it //file is not inside the mod folder, we need to move it
if (filePathRelativeToModFolder.StartsWith("..") || if (filePathRelativeToModFolder.StartsWith("..") ||
@@ -0,0 +1,125 @@
#nullable enable
using System;
using System.Linq;
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
/*
* This screen only exists because I'm going mental without access to EnC on Linux.
* This is fucking stupid and horrible.
* Remember to remove this crap eventually.
* - Markus
*/
namespace Barotrauma
{
class TestScreen : EditorScreen
{
public override Camera Cam { get; }
private Item? miniMapItem;
private Submarine? submarine;
private Character? dummyCharacter;
public static Effect BlueprintEffect;
public TestScreen()
{
Cam = new Camera();
BlueprintEffect = GameMain.GameScreen.BlueprintEffect;
new GUIButton(new RectTransform(new Point(256, 256), Frame.RectTransform), "Reload shader")
{
OnClicked = (button, o) =>
{
BlueprintEffect.Dispose();
GameMain.Instance.Content.Unload();
BlueprintEffect = GameMain.Instance.Content.Load<Effect>("Effects/blueprintshader_opengl");
GameMain.GameScreen.BlueprintEffect = BlueprintEffect;
return true;
}
};
}
public override void Select()
{
base.Select();
if (dummyCharacter is { Removed: false })
{
dummyCharacter?.Remove();
}
// ????????
submarine = new Submarine(SubmarineInfo.SavedSubmarines.FirstOrDefault(info => info.Name.Equals("Kastrull", StringComparison.OrdinalIgnoreCase)));
miniMapItem = new Item(ItemPrefab.Find(null, "statusmonitor"), Vector2.Zero, submarine);
MiniMap miniMap = miniMapItem.GetComponent<MiniMap>();
miniMap.PowerConsumption = 0;
dummyCharacter = Character.Create(CharacterPrefab.HumanSpeciesName, Vector2.Zero, "", id: Entity.DummyID, hasAi: false);
dummyCharacter.Info.Name = "Galldren";
dummyCharacter.Inventory.CreateSlots();
Character.Controlled = dummyCharacter;
GameMain.World.ProcessChanges();
}
public override void AddToGUIUpdateList()
{
Frame.AddToGUIUpdateList();
CharacterHUD.AddToGUIUpdateList(dummyCharacter);
dummyCharacter?.SelectedConstruction?.AddToGUIUpdateList();
}
public override void Update(double deltaTime)
{
base.Update(deltaTime);
if (dummyCharacter is { } dummy && miniMapItem is { } item)
{
if (dummy.SelectedConstruction != item)
{
dummy.SelectedConstruction = item;
}
dummy.SelectedConstruction?.UpdateHUD(Cam, dummy, (float)deltaTime);
Vector2 pos = FarseerPhysics.ConvertUnits.ToSimUnits(item.Position);
foreach (Limb limb in dummy.AnimController.Limbs)
{
limb.body.SetTransform(pos, 0.0f);
}
if (dummy.AnimController?.Collider is { } collider)
{
collider.SetTransform(pos, 0);
}
dummy.ControlLocalPlayer((float)deltaTime, Cam, false);
dummy.Control((float)deltaTime, Cam);
dummy.Submarine = submarine;
}
}
public override void Draw(double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch)
{
base.Draw(deltaTime, graphics, spriteBatch);
graphics.Clear(BackgroundColor);
spriteBatch.Begin(SpriteSortMode.BackToFront, transformMatrix: Cam.Transform);
miniMapItem?.Draw(spriteBatch, false);
if (dummyCharacter is { } dummy)
{
dummyCharacter.DrawFront(spriteBatch, Cam);
dummyCharacter.Draw(spriteBatch, Cam);
}
spriteBatch.End();
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState);
GUI.Draw(Cam, spriteBatch);
dummyCharacter?.DrawHUD(spriteBatch, Cam, false);
spriteBatch.End();
}
}
}
@@ -38,6 +38,7 @@ namespace Barotrauma
public readonly string File; public readonly string File;
public readonly string Type; public readonly string Type;
public readonly bool DuckVolume; public readonly bool DuckVolume;
public readonly float Volume;
public readonly Vector2 IntensityRange; public readonly Vector2 IntensityRange;
@@ -52,6 +53,7 @@ namespace Barotrauma
this.Type = element.GetAttributeString("type", "").ToLowerInvariant(); this.Type = element.GetAttributeString("type", "").ToLowerInvariant();
this.IntensityRange = element.GetAttributeVector2("intensityrange", new Vector2(0.0f, 100.0f)); this.IntensityRange = element.GetAttributeVector2("intensityrange", new Vector2(0.0f, 100.0f));
this.DuckVolume = element.GetAttributeBool("duckvolume", false); this.DuckVolume = element.GetAttributeBool("duckvolume", false);
this.Volume = element.GetAttributeFloat("volume", 1.0f);
this.ContinueFromPreviousTime = element.GetAttributeBool("continuefromprevioustime", false); this.ContinueFromPreviousTime = element.GetAttributeBool("continuefromprevioustime", false);
this.Element = element; this.Element = element;
} }
@@ -816,6 +818,8 @@ namespace Barotrauma
} }
} }
int noiseLoopIndex = 1;
updateMusicTimer -= deltaTime; updateMusicTimer -= deltaTime;
if (updateMusicTimer <= 0.0f) if (updateMusicTimer <= 0.0f)
{ {
@@ -851,7 +855,6 @@ namespace Barotrauma
} }
} }
int noiseLoopIndex = 1;
if (Level.Loaded?.Type == LevelData.LevelType.LocationConnection) if (Level.Loaded?.Type == LevelData.LevelType.LocationConnection)
{ {
// Find background noise loop for the current biome // Find background noise loop for the current biome
@@ -917,7 +920,7 @@ namespace Barotrauma
{ {
//mute the channel //mute the channel
musicChannel[i].Gain = MathHelper.Lerp(musicChannel[i].Gain, 0.0f, MusicLerpSpeed * deltaTime); musicChannel[i].Gain = MathHelper.Lerp(musicChannel[i].Gain, 0.0f, MusicLerpSpeed * deltaTime);
if (musicChannel[i].Gain < 0.01f) DisposeMusicChannel(i); if (musicChannel[i].Gain < 0.01f) { DisposeMusicChannel(i); }
} }
} }
//something should be playing, but the targetMusic is invalid //something should be playing, but the targetMusic is invalid
@@ -932,7 +935,7 @@ namespace Barotrauma
if (musicChannel[i] != null && musicChannel[i].IsPlaying) if (musicChannel[i] != null && musicChannel[i].IsPlaying)
{ {
musicChannel[i].Gain = MathHelper.Lerp(musicChannel[i].Gain, 0.0f, MusicLerpSpeed * deltaTime); musicChannel[i].Gain = MathHelper.Lerp(musicChannel[i].Gain, 0.0f, MusicLerpSpeed * deltaTime);
if (musicChannel[i].Gain < 0.01f) DisposeMusicChannel(i); if (musicChannel[i].Gain < 0.01f) { DisposeMusicChannel(i); }
} }
//channel free now, start playing the correct clip //channel free now, start playing the correct clip
if (currentMusic[i] == null || (musicChannel[i] == null || !musicChannel[i].IsPlaying)) if (currentMusic[i] == null || (musicChannel[i] == null || !musicChannel[i].IsPlaying))
@@ -949,7 +952,7 @@ namespace Barotrauma
targetMusic[i] = null; targetMusic[i] = null;
break; break;
} }
musicChannel[i] = currentMusic[i].Play(0.0f, "music"); musicChannel[i] = currentMusic[i].Play(0.0f, i == noiseLoopIndex ? "" : "music");
if (targetMusic[i].ContinueFromPreviousTime) if (targetMusic[i].ContinueFromPreviousTime)
{ {
musicChannel[i].StreamSeekPos = targetMusic[i].PreviousTime; musicChannel[i].StreamSeekPos = targetMusic[i].PreviousTime;
@@ -963,13 +966,13 @@ namespace Barotrauma
if (musicChannel[i] == null || !musicChannel[i].IsPlaying) if (musicChannel[i] == null || !musicChannel[i].IsPlaying)
{ {
musicChannel[i]?.Dispose(); musicChannel[i]?.Dispose();
musicChannel[i] = currentMusic[i].Play(0.0f, "music"); musicChannel[i] = currentMusic[i].Play(0.0f, i == noiseLoopIndex ? "" : "music");
musicChannel[i].Looping = true; musicChannel[i].Looping = true;
} }
float targetGain = 1.0f; float targetGain = targetMusic[i].Volume;
if (targetMusic[i].DuckVolume) if (targetMusic[i].DuckVolume)
{ {
targetGain = (float)Math.Sqrt(1.0f / activeTrackCount); targetGain *= (float)Math.Sqrt(1.0f / activeTrackCount);
} }
musicChannel[i].Gain = MathHelper.Lerp(musicChannel[i].Gain, targetGain, MusicLerpSpeed * deltaTime); musicChannel[i].Gain = MathHelper.Lerp(musicChannel[i].Gain, targetGain, MusicLerpSpeed * deltaTime);
} }
@@ -1,9 +1,10 @@
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using Color = Microsoft.Xna.Framework.Color;
namespace Barotrauma namespace Barotrauma
{ {
@@ -54,6 +55,225 @@ namespace Barotrauma
return isInside; return isInside;
} }
public static Vector2 GetPolygonBoundingBoxSize(List<Vector2> verticess)
{
float minX = verticess[0].X;
float maxX = verticess[0].X;
float minY = verticess[0].Y;
float maxY = verticess[0].Y;
foreach (var (vertX, vertY) in verticess)
{
minX = Math.Min(vertX, minX);
maxX = Math.Max(vertX, maxX);
minY = Math.Min(vertY, minY);
maxY = Math.Max(vertY, maxY);
}
return new Vector2(maxX - minX, maxY - minY);
}
public static List<Vector2> ScalePolygon(List<Vector2> vertices, Vector2 scale)
{
List<Vector2> newVertices = new List<Vector2>();
Vector2 center = GetPolygonCentroid(vertices);
foreach (Vector2 vert in vertices)
{
Vector2 centerVector = vert - center;
Vector2 centerVectorScale = centerVector * scale;
Vector2 scaledVector = centerVectorScale + center;
newVertices.Add(scaledVector);
}
return newVertices;
}
public static Vector2 GetPolygonCentroid(List<Vector2> poly)
{
float accumulatedArea = 0.0f;
float centerX = 0.0f;
float centerY = 0.0f;
for (int i = 0, j = poly.Count - 1; i < poly.Count; j = i++)
{
float temp = poly[i].X * poly[j].Y - poly[j].X * poly[i].Y;
accumulatedArea += temp;
centerX += (poly[i].X + poly[j].X) * temp;
centerY += (poly[i].Y + poly[j].Y) * temp;
}
if (Math.Abs(accumulatedArea) < 1E-7f) { return Vector2.Zero; } // Avoid division by zero
accumulatedArea *= 3f;
return new Vector2(centerX / accumulatedArea, centerY / accumulatedArea);
}
public static List<Vector2> SnapVertices(List<Vector2> points, int treshold = 1)
{
Stack<Vector2> toCheck = new Stack<Vector2>();
List<Vector2> newPoints = new List<Vector2>();
foreach (Vector2 point in points)
{
toCheck.Push(point);
}
while (toCheck.TryPop(out Vector2 point))
{
Vector2 newPoint = new Vector2(point.X, point.Y);
foreach (Vector2 otherPoint in toCheck.Concat(newPoints))
{
float diffX = Math.Abs(newPoint.X - otherPoint.X),
diffY = Math.Abs(newPoint.Y - otherPoint.Y);
if (diffX <= treshold)
{
newPoint.X = Math.Max(newPoint.X, otherPoint.X);
}
if (diffY <= treshold)
{
newPoint.Y = Math.Max(newPoint.Y, otherPoint.Y);
}
}
newPoints.Add(newPoint);
}
return newPoints;
}
public static ImmutableArray<RectangleF> SnapRectangles(IEnumerable<RectangleF> rects, int treshold = 1)
{
List<RectangleF> list = new List<RectangleF>();
List<Vector2> points = new List<Vector2>();
foreach (RectangleF rect in rects)
{
points.Add(new Vector2(rect.Left, rect.Top));
points.Add(new Vector2(rect.Right, rect.Top));
points.Add(new Vector2(rect.Right, rect.Bottom));
points.Add(new Vector2(rect.Left, rect.Bottom));
}
points = SnapVertices(points, treshold);
for (int i = 0; i < points.Count; i += 4)
{
Vector2 topLeft = points[i];
Vector2 bottomRight = points[i + 2];
list.Add(new RectangleF(topLeft, bottomRight - topLeft));
}
return list.ToImmutableArray();
}
public static List<List<Vector2>> CombineRectanglesIntoShape(IEnumerable<RectangleF> rectangles)
{
List<Vector2> points =
(from point in rectangles.SelectMany(RectangleToPoints)
group point by point
into g
where g.Count() % 2 == 1
select g.Key)
.ToList();
List<Vector2> sortedY = points.OrderBy(p => p.Y).ThenByDescending(p => p.X).ToList();
List<Vector2> sortedX = points.OrderBy(p => p.X).ThenByDescending(p => p.Y).ToList();
Dictionary<Vector2, Vector2> edgesH = new Dictionary<Vector2, Vector2>();
Dictionary<Vector2, Vector2> edgesV = new Dictionary<Vector2, Vector2>();
int i = 0;
while (i < points.Count)
{
float currY = sortedY[i].Y;
while (i < points.Count && Math.Abs(sortedY[i].Y - currY) < 0.01f)
{
edgesH[sortedY[i]] = sortedY[i + 1];
edgesH[sortedY[i + 1]] = sortedY[i];
i += 2;
}
}
i = 0;
while (i < points.Count)
{
float currX = sortedX[i].X;
while (i < points.Count && Math.Abs(sortedX[i].X - currX) < 0.01f)
{
edgesV[sortedX[i]] = sortedX[i + 1];
edgesV[sortedX[i + 1]] = sortedX[i];
i += 2;
}
}
List<List<Vector2>> polygons = new List<List<Vector2>>();
while (edgesH.Any())
{
var (key, _) = edgesH.First();
List<(Vector2 Point, int Direction)> polygon = new List<(Vector2 Point, int Direction)> { (key, 0) };
edgesH.Remove(key);
while (true)
{
var (curr, direction) = polygon[^1];
if (direction == 0)
{
Vector2 nextVertex = edgesV[curr];
edgesV.Remove(curr);
polygon.Add((nextVertex, 1));
}
else
{
Vector2 nextVertex = edgesH[curr];
edgesH.Remove(curr);
polygon.Add((nextVertex, 0));
}
if (polygon[^1] == polygon[0])
{
polygon.Remove(polygon[^1]);
break;
}
}
List<Vector2> poly = polygon.Select(t => t.Point).ToList();
foreach (Vector2 vertex in poly)
{
if (edgesH.ContainsKey(vertex))
{
edgesH.Remove(vertex);
}
if (edgesV.ContainsKey(vertex))
{
edgesV.Remove(vertex);
}
}
polygons.Add(poly);
}
return polygons;
static IEnumerable<Vector2> RectangleToPoints(RectangleF rect)
{
(float x1, float y1, float x2, float y2) = (rect.Left, rect.Top, rect.Right, rect.Bottom);
Vector2[] pts = { new Vector2(x1, y1), new Vector2(x2, y1), new Vector2(x2, y2), new Vector2(x1, y2) };
return pts;
}
}
// Convert an RGB value into an HLS value. // Convert an RGB value into an HLS value.
public static Vector3 RgbToHLS(this Color color) public static Vector3 RgbToHLS(this Color color)
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product> <Product>Barotrauma</Product>
<Version>0.14.9.0</Version> <Version>0.1500.0.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName> <AssemblyName>Barotrauma</AssemblyName>
+1 -1
View File
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product> <Product>Barotrauma</Product>
<Version>0.14.9.0</Version> <Version>0.1500.0.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName> <AssemblyName>Barotrauma</AssemblyName>
@@ -67,3 +67,9 @@
/processorParam:DebugMode=Auto /processorParam:DebugMode=Auto
/build:grainshader.fx /build:grainshader.fx
#begin blueprintshader.fx
/importer:EffectImporter
/processor:EffectProcessor
/processorParam:DebugMode=Auto
/build:blueprintshader.fx
@@ -67,3 +67,8 @@
/processorParam:DebugMode=Auto /processorParam:DebugMode=Auto
/build:grainshader_opengl.fx /build:grainshader_opengl.fx
#begin blueprintshader_opengl.fx
/importer:EffectImporter
/processor:EffectProcessor
/processorParam:DebugMode=Auto
/build:blueprintshader_opengl.fx
@@ -0,0 +1,48 @@
// vim:ft=hlsl
sampler TextureSampler : register(s0);
float width;
float height;
float3 sobel(float2 uv)
{
float x = 0;
float y = 0;
float w = 1.0 / width;
float h = 1.0 / height;
x += tex2D(TextureSampler, uv + float2(-w, -h)) * -1.0;
x += tex2D(TextureSampler, uv + float2(-w, 0)) * -2.0;
x += tex2D(TextureSampler, uv + float2(-w, h)) * -1.0;
x += tex2D(TextureSampler, uv + float2( w, -h)) * 1.0;
x += tex2D(TextureSampler, uv + float2( w, 0)) * 2.0;
x += tex2D(TextureSampler, uv + float2( w, h)) * 1.0;
y += tex2D(TextureSampler, uv + float2(-w, -h)) * -1.0;
y += tex2D(TextureSampler, uv + float2( 0, -h)) * -2.0;
y += tex2D(TextureSampler, uv + float2( w, -h)) * -1.0;
y += tex2D(TextureSampler, uv + float2(-w, h)) * 1.0;
y += tex2D(TextureSampler, uv + float2( 0, h)) * 2.0;
y += tex2D(TextureSampler, uv + float2( w, h)) * 1.0;
return sqrt(x * x + y * y);
}
float4 blueprint(float4 position : SV_POSITION, float4 clr : COLOR0, float2 texCoord : TEXCOORD0) : COLOR0
{
float3 s = sobel(texCoord);
float a = tex2D(TextureSampler, texCoord).a;
a *= clr.a;
return float4(clr.r + s.r, clr.g + s.g, clr.b + s.b, a);
}
technique Blueprint
{
pass Pass1
{
PixelShader = compile ps_4_0_level_9_1 blueprint();
}
}
@@ -0,0 +1,48 @@
// vim:ft=hlsl
sampler TextureSampler : register(s0);
float width;
float height;
float3 sobel(float2 uv)
{
float x = 0;
float y = 0;
float w = 1.0 / width;
float h = 1.0 / height;
x += tex2D(TextureSampler, uv + float2(-w, -h)) * -1.0;
x += tex2D(TextureSampler, uv + float2(-w, 0)) * -2.0;
x += tex2D(TextureSampler, uv + float2(-w, h)) * -1.0;
x += tex2D(TextureSampler, uv + float2( w, -h)) * 1.0;
x += tex2D(TextureSampler, uv + float2( w, 0)) * 2.0;
x += tex2D(TextureSampler, uv + float2( w, h)) * 1.0;
y += tex2D(TextureSampler, uv + float2(-w, -h)) * -1.0;
y += tex2D(TextureSampler, uv + float2( 0, -h)) * -2.0;
y += tex2D(TextureSampler, uv + float2( w, -h)) * -1.0;
y += tex2D(TextureSampler, uv + float2(-w, h)) * 1.0;
y += tex2D(TextureSampler, uv + float2( 0, h)) * 2.0;
y += tex2D(TextureSampler, uv + float2( w, h)) * 1.0;
return sqrt(x * x + y * y);
}
float4 blueprint(float4 position : SV_POSITION, float4 clr : COLOR0, float2 texCoord : TEXCOORD0) : COLOR0
{
float3 s = sobel(texCoord);
float a = tex2D(TextureSampler, texCoord).a;
a *= clr.a;
return float4(clr.r + s.r, clr.g + s.g, clr.b + s.b, a);
}
technique Blueprint
{
pass Pass1
{
PixelShader = compile ps_3_0 blueprint();
}
}
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product> <Product>Barotrauma</Product>
<Version>0.14.9.0</Version> <Version>0.1500.0.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName> <AssemblyName>Barotrauma</AssemblyName>
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product> <Product>Barotrauma Dedicated Server</Product>
<Version>0.14.9.0</Version> <Version>0.1500.0.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName> <AssemblyName>DedicatedServer</AssemblyName>
+1 -1
View File
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product> <Product>Barotrauma Dedicated Server</Product>
<Version>0.14.9.0</Version> <Version>0.1500.0.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName> <AssemblyName>DedicatedServer</AssemblyName>
@@ -46,5 +46,10 @@ namespace Barotrauma
} }
} }
} }
partial void OnMoneyChanged(int prevAmount, int newAmount)
{
GameMain.NetworkMember.CreateEntityEvent(this, new object[] { NetEntityEvent.Type.UpdateMoney });
}
} }
} }
@@ -2,6 +2,7 @@
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
namespace Barotrauma namespace Barotrauma
{ {
@@ -22,6 +23,14 @@ namespace Barotrauma
} }
} }
partial void OnExperienceChanged(int prevAmount, int newAmount, Vector2 textPopupPos)
{
if (Math.Abs(prevAmount - newAmount) > 0)
{
GameMain.NetworkMember.CreateEntityEvent(Character, new object[] { NetEntityEvent.Type.UpdateExperience });
}
}
public void ServerWrite(IWriteMessage msg) public void ServerWrite(IWriteMessage msg)
{ {
msg.Write(ID); msg.Write(ID);
@@ -53,6 +62,17 @@ namespace Barotrauma
msg.Write((byte)0); msg.Write((byte)0);
} }
// TODO: animations // TODO: animations
msg.Write((byte)savedStatValues.SelectMany(s => s.Value).Count());
foreach (var savedStatValuePair in savedStatValues)
{
foreach (var savedStatValue in savedStatValuePair.Value)
{
msg.Write((byte)savedStatValuePair.Key);
msg.Write(savedStatValue.StatIdentifier);
msg.Write(savedStatValue.StatValue);
msg.Write(savedStatValue.RemoveOnDeath);
}
}
} }
} }
} }
@@ -238,7 +238,7 @@ namespace Barotrauma
break; break;
case ClientNetObject.ENTITY_STATE: case ClientNetObject.ENTITY_STATE:
int eventType = msg.ReadRangedInteger(0, 3); int eventType = msg.ReadRangedInteger(0, 4);
switch (eventType) switch (eventType)
{ {
case 0: case 0:
@@ -268,8 +268,35 @@ namespace Barotrauma
if (IsIncapacitated) if (IsIncapacitated)
{ {
var causeOfDeath = CharacterHealth.GetCauseOfDeath(); var causeOfDeath = CharacterHealth.GetCauseOfDeath();
Kill(causeOfDeath.First, causeOfDeath.Second); Kill(causeOfDeath.type, causeOfDeath.affliction);
} }
break;
case 3: // NetEntityEvent.Type.UpdateTalents
if (c.Character != this)
{
#if DEBUG
DebugConsole.Log("Received a character update message from a client who's not controlling the character");
#endif
return;
}
// get the full list of talents from the player, only give the ones
// that are not already given (or otherwise not viable)
ushort talentCount = msg.ReadUInt16();
List<string> talentSelection = new List<string>();
for (int i = 0; i < talentCount; i++)
{
UInt32 talentIdentifier = msg.ReadUInt32();
var prefab = TalentPrefab.TalentPrefabs.Find(p => p.UIntIdentifier == talentIdentifier);
if (prefab != null) { talentSelection.Add(prefab.Identifier); }
}
talentSelection = TalentTree.CheckTalentSelection(this, talentSelection);
foreach (string talent in talentSelection)
{
GiveTalent(talent);
}
break; break;
} }
break; break;
@@ -283,7 +310,7 @@ namespace Barotrauma
if (extraData != null) if (extraData != null)
{ {
const int min = 0, max = 9; const int min = 0, max = 12;
switch ((NetEntityEvent.Type)extraData[0]) switch ((NetEntityEvent.Type)extraData[0])
{ {
case NetEntityEvent.Type.InventoryState: case NetEntityEvent.Type.InventoryState:
@@ -394,6 +421,22 @@ namespace Barotrauma
msg.Write(inventoryItemIDs[i]); msg.Write(inventoryItemIDs[i]);
} }
break; break;
case NetEntityEvent.Type.UpdateExperience:
msg.WriteRangedInteger(10, min, max);
msg.Write(Info.ExperiencePoints);
break;
case NetEntityEvent.Type.UpdateTalents:
msg.WriteRangedInteger(11, min, max);
msg.Write((ushort)characterTalents.Count);
foreach (var unlockedTalent in characterTalents)
{
msg.Write(unlockedTalent.Prefab.UIntIdentifier);
}
break;
case NetEntityEvent.Type.UpdateMoney:
msg.WriteRangedInteger(12, min, max);
msg.Write(GameMain.GameSession.Campaign.Money);
break;
default: default:
DebugConsole.ThrowError("Invalid NetworkEvent type for entity " + ToString() + " (" + (NetEntityEvent.Type)extraData[0] + ")"); DebugConsole.ThrowError("Invalid NetworkEvent type for entity " + ToString() + " (" + (NetEntityEvent.Type)extraData[0] + ")");
break; break;
@@ -499,7 +542,7 @@ namespace Barotrauma
if (writeStatus) if (writeStatus)
{ {
WriteStatus(tempBuffer); WriteStatus(tempBuffer);
(AIController as EnemyAIController)?.PetBehavior?.ServerWrite(tempBuffer); AIController?.ServerWrite(tempBuffer);
HealthUpdatePending = false; HealthUpdatePending = false;
} }
@@ -1187,6 +1187,7 @@ namespace Barotrauma
NewMessage("*****************", Color.Lime); NewMessage("*****************", Color.Lime);
GameServer.Log("Console command \"restart\" executed: closing the server...", ServerLog.MessageType.ServerMessage); GameServer.Log("Console command \"restart\" executed: closing the server...", ServerLog.MessageType.ServerMessage);
GameMain.Instance.CloseServer(); GameMain.Instance.CloseServer();
GameMain.Instance.TryStartChildServerRelay();
GameMain.Instance.StartServer(); GameMain.Instance.StartServer();
})); }));
@@ -18,7 +18,17 @@ namespace Barotrauma
{ {
public static readonly Version Version = Assembly.GetEntryAssembly().GetName().Version; public static readonly Version Version = Assembly.GetEntryAssembly().GetName().Version;
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 GameSettings Config; public static GameSettings Config;
public static GameServer Server; public static GameServer Server;
@@ -123,6 +133,8 @@ namespace Barotrauma
ItemAssemblyPrefab.LoadAll(); ItemAssemblyPrefab.LoadAll();
LevelObjectPrefab.LoadAll(); LevelObjectPrefab.LoadAll();
BallastFloraPrefab.LoadAll(GetFilesOfType(ContentType.MapCreature)); BallastFloraPrefab.LoadAll(GetFilesOfType(ContentType.MapCreature));
TalentPrefab.LoadAll(GetFilesOfType(ContentType.Talents));
TalentTree.LoadAll(GetFilesOfType(ContentType.TalentTrees));
GameModePreset.Init(); GameModePreset.Init();
DecalManager = new DecalManager(); DecalManager = new DecalManager();
@@ -179,6 +191,20 @@ namespace Barotrauma
} }
} }
public bool TryStartChildServerRelay()
{
for (int i = 0; i < CommandLineArgs.Length; i++)
{
switch (CommandLineArgs[i].Trim())
{
case "-pipes":
ChildServerRelay.Start(CommandLineArgs[i + 2], CommandLineArgs[i + 1]);
return true;
}
}
return false;
}
public void StartServer() public void StartServer()
{ {
string name = "Server"; string name = "Server";
@@ -264,7 +290,7 @@ namespace Barotrauma
i++; i++;
break; break;
case "-pipes": case "-pipes":
ChildServerRelay.Start(CommandLineArgs[i + 2], CommandLineArgs[i + 1]); //handled in TryStartChildServerRelay
i += 2; i += 2;
break; break;
} }
@@ -323,6 +349,7 @@ namespace Barotrauma
Hyper.ComponentModel.HyperTypeDescriptionProvider.Add(typeof(Items.Components.ItemComponent)); Hyper.ComponentModel.HyperTypeDescriptionProvider.Add(typeof(Items.Components.ItemComponent));
Hyper.ComponentModel.HyperTypeDescriptionProvider.Add(typeof(Hull)); Hyper.ComponentModel.HyperTypeDescriptionProvider.Add(typeof(Hull));
TryStartChildServerRelay();
Init(); Init();
StartServer(); StartServer();
@@ -56,6 +56,6 @@ namespace Barotrauma
public void ApplyOrderData(Character character) public void ApplyOrderData(Character character)
{ {
CharacterInfo.ApplyOrderData(character, OrderData); CharacterInfo.ApplyOrderData(character, OrderData);
} }
} }
} }
@@ -16,6 +16,12 @@ namespace Barotrauma.Items.Components
set { unsentChanges = value; } set { unsentChanges = value; }
} }
protected override void RemoveComponentSpecific()
{
base.RemoveComponentSpecific();
pathFinder = null;
}
public void ServerRead(ClientNetObject type, IReadMessage msg, Barotrauma.Networking.Client c) public void ServerRead(ClientNetObject type, IReadMessage msg, Barotrauma.Networking.Client c)
{ {
@@ -289,6 +289,14 @@ namespace Barotrauma
teamID = (byte)wifiComponent.TeamID; teamID = (byte)wifiComponent.TeamID;
break; break;
} }
if (teamID == 0)
{
foreach (IdCard idCard in GetComponents<IdCard>())
{
teamID = (byte)idCard.TeamID;
break;
}
}
msg.Write(teamID); msg.Write(teamID);
bool tagsChanged = tags.Count != prefab.Tags.Count || !tags.All(t => prefab.Tags.Contains(t)); bool tagsChanged = tags.Count != prefab.Tags.Count || !tags.All(t => prefab.Tags.Contains(t));
@@ -1,12 +1,4 @@
using System; using System.IO.Pipes;
using System.Collections.Generic;
using System.Diagnostics;
using Barotrauma.IO;
using System.IO.Pipes;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Win32.SafeHandles;
namespace Barotrauma.Networking namespace Barotrauma.Networking
{ {
@@ -2354,6 +2354,8 @@ namespace Barotrauma.Networking
characterData.ApplyHealthData(spawnedCharacter); characterData.ApplyHealthData(spawnedCharacter);
characterData.ApplyOrderData(spawnedCharacter); characterData.ApplyOrderData(spawnedCharacter);
spawnedCharacter.GiveIdCardTags(mainSubWaypoints[i]); spawnedCharacter.GiveIdCardTags(mainSubWaypoints[i]);
spawnedCharacter.LoadTalents();
characterData.HasSpawned = true; characterData.HasSpawned = true;
} }
spawnedCharacter.OwnerClientEndPoint = teamClients[i].Connection.EndPointString; spawnedCharacter.OwnerClientEndPoint = teamClients[i].Connection.EndPointString;
@@ -2366,6 +2368,8 @@ namespace Barotrauma.Networking
spawnedCharacter.TeamID = teamID; spawnedCharacter.TeamID = teamID;
spawnedCharacter.GiveJobItems(mainSubWaypoints[i]); spawnedCharacter.GiveJobItems(mainSubWaypoints[i]);
spawnedCharacter.GiveIdCardTags(mainSubWaypoints[i]); spawnedCharacter.GiveIdCardTags(mainSubWaypoints[i]);
// talents are only avilable for players in online sessions, but modders or someone else might want to have them loaded anyway
spawnedCharacter.LoadTalents();
} }
} }
@@ -2431,6 +2435,7 @@ namespace Barotrauma.Networking
roundStartTime = DateTime.Now; roundStartTime = DateTime.Now;
startGameCoroutine = null;
yield return CoroutineStatus.Success; yield return CoroutineStatus.Success;
} }
@@ -2619,8 +2624,8 @@ namespace Barotrauma.Networking
} }
} }
Submarine.Unload();
entityEventManager.Clear(); entityEventManager.Clear();
Submarine.Unload();
GameMain.NetLobbyScreen.Select(); GameMain.NetLobbyScreen.Select();
Log("Round ended.", ServerLog.MessageType.ServerMessage); Log("Round ended.", ServerLog.MessageType.ServerMessage);
@@ -3145,28 +3150,19 @@ namespace Barotrauma.Networking
public void SendOrderChatMessage(OrderChatMessage message) public void SendOrderChatMessage(OrderChatMessage message)
{ {
if (message.Sender == null || message.Sender.SpeechImpediment >= 100.0f) { return; } if (message.Sender == null || message.Sender.SpeechImpediment >= 100.0f) { return; }
//ChatMessageType messageType = ChatMessage.CanUseRadio(message.Sender) ? ChatMessageType.Radio : ChatMessageType.Default;
//check which clients can receive the message and apply distance effects //check which clients can receive the message and apply distance effects
foreach (Client client in ConnectedClients) foreach (Client client in ConnectedClients)
{ {
string modifiedMessage = message.Text; if (message.Sender != null && client.Character != null && !client.Character.IsDead)
if (message.Sender != null &&
client.Character != null && !client.Character.IsDead)
{ {
//too far to hear the msg -> don't send //too far to hear the msg -> don't send
if (!client.Character.CanHearCharacter(message.Sender)) { continue; } if (!client.Character.CanHearCharacter(message.Sender)) { continue; }
} }
SendDirectChatMessage(new OrderChatMessage(message.Order, message.OrderOption, message.OrderPriority, message.TargetEntity, message.TargetCharacter, message.Sender), client); SendDirectChatMessage(new OrderChatMessage(message.Order, message.OrderOption, message.OrderPriority, message.TargetEntity, message.TargetCharacter, message.Sender), client);
} }
if (!string.IsNullOrWhiteSpace(message.Text))
string myReceivedMessage = message.Text;
if (!string.IsNullOrWhiteSpace(myReceivedMessage))
{ {
AddChatMessage(new OrderChatMessage(message.Order, message.OrderOption, message.OrderPriority, myReceivedMessage, message.TargetEntity, message.TargetCharacter, message.Sender)); AddChatMessage(new OrderChatMessage(message.Order, message.OrderOption, message.OrderPriority, message.Text, message.TargetEntity, message.TargetCharacter, message.Sender));
} }
} }
@@ -44,11 +44,11 @@ namespace Barotrauma.Networking
class ServerEntityEventManager : NetEntityEventManager class ServerEntityEventManager : NetEntityEventManager
{ {
private List<ServerEntityEvent> events; private readonly List<ServerEntityEvent> events;
//list of unique events (i.e. !IsDuplicate) created during the round //list of unique events (i.e. !IsDuplicate) created during the round
//used for syncing clients who join mid-round //used for syncing clients who join mid-round
private List<ServerEntityEvent> uniqueEvents; private readonly List<ServerEntityEvent> uniqueEvents;
private UInt16 lastSentToAll; private UInt16 lastSentToAll;
private UInt16 lastSentToAnyone; private UInt16 lastSentToAnyone;
@@ -90,11 +90,11 @@ namespace Barotrauma.Networking
} }
} }
private List<BufferedEvent> bufferedEvents; private readonly List<BufferedEvent> bufferedEvents;
private UInt16 ID; private UInt16 ID;
private GameServer server; private readonly GameServer server;
private double lastEventCountHighWarning; private double lastEventCountHighWarning;
@@ -284,6 +284,8 @@ namespace Barotrauma.Networking
partial void RespawnCharactersProjSpecific(Vector2? shuttlePos) partial void RespawnCharactersProjSpecific(Vector2? shuttlePos)
{ {
respawnedCharacters.Clear();
var respawnSub = RespawnShuttle ?? Submarine.MainSub; var respawnSub = RespawnShuttle ?? Submarine.MainSub;
MultiPlayerCampaign campaign = GameMain.GameSession.GameMode as MultiPlayerCampaign; MultiPlayerCampaign campaign = GameMain.GameSession.GameMode as MultiPlayerCampaign;
@@ -300,7 +302,7 @@ namespace Barotrauma.Networking
if (matchingData != null && !matchingData.HasSpawned) if (matchingData != null && !matchingData.HasSpawned)
{ {
c.CharacterInfo = matchingData.CharacterInfo; c.CharacterInfo = matchingData.CharacterInfo;
} }
//all characters are in Team 1 in game modes/missions with only one team. //all characters are in Team 1 in game modes/missions with only one team.
//if at some point we add a game mode with multiple teams where respawning is possible, this needs to be reworked //if at some point we add a game mode with multiple teams where respawning is possible, this needs to be reworked
@@ -355,8 +357,21 @@ namespace Barotrauma.Networking
characterInfos[i].ClearCurrentOrders(); characterInfos[i].ClearCurrentOrders();
var character = Character.Create(characterInfos[i], shuttleSpawnPoints[i].WorldPosition, characterInfos[i].Name, isRemotePlayer: !bot, hasAi: bot); bool forceSpawnInMainSub = false;
if (!bot && campaign != null)
{
var matchingData = campaign?.GetClientCharacterData(clients[i]);
if (matchingData != null && !matchingData.HasSpawned)
{
forceSpawnInMainSub = true;
}
}
var character = Character.Create(characterInfos[i], (forceSpawnInMainSub ? mainSubSpawnPoints[i] : shuttleSpawnPoints[i]).WorldPosition, characterInfos[i].Name, isRemotePlayer: !bot, hasAi: bot);
character.TeamID = CharacterTeamType.Team1; character.TeamID = CharacterTeamType.Team1;
character.LoadTalents();
respawnedCharacters.Add(character);
if (bot) if (bot)
{ {
@@ -185,7 +185,10 @@ namespace Barotrauma
{ {
existingItems.Add(item); existingItems.Add(item);
} }
Entity.Spawner.AddToSpawnQueue(targetPrefab, targetContainer.OwnInventory); Entity.Spawner.AddToSpawnQueue(targetPrefab, targetContainer.OwnInventory, null, item =>
{
item.AddTag("traitormissionitem");
});
target = null; target = null;
} }
else if (allowExisting) else if (allowExisting)
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product> <Product>Barotrauma Dedicated Server</Product>
<Version>0.14.9.0</Version> <Version>0.1500.0.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName> <AssemblyName>DedicatedServer</AssemblyName>
@@ -63,6 +63,11 @@
<Item file="Content/Items/Gardening/gardeningtools.xml" /> <Item file="Content/Items/Gardening/gardeningtools.xml" />
<Item file="Content/Items/Gardening/plantproducts.xml" /> <Item file="Content/Items/Gardening/plantproducts.xml" />
<Item file="Content/Map/Pirates/PirateItems.xml" /> <Item file="Content/Map/Pirates/PirateItems.xml" />
<Item file="Content/Items/Jobgear/Mechanic/mechanic_talent_items.xml" />
<Item file="Content/Items/Jobgear/Security/securityofficer_talent_items.xml" />
<Item file="Content/Items/Jobgear/Engineer/engineer_talent_items.xml" />
<Item file="Content/Items/Jobgear/Medic/medic_talent_items.xml" />
<Item file="Content/Items/Jobgear/Captain/captain_talent_items.xml" />
<Character file="Content/Characters/Balloon/Balloon.xml" /> <Character file="Content/Characters/Balloon/Balloon.xml" />
<Character file="Content/Characters/Carrier/Carrier.xml" /> <Character file="Content/Characters/Carrier/Carrier.xml" />
<Character file="Content/Characters/Charybdis/Charybdis.xml" /> <Character file="Content/Characters/Charybdis/Charybdis.xml" />
@@ -147,6 +152,12 @@
<Text file="Content/Texts/Korean/KoreanVanilla.xml" /> <Text file="Content/Texts/Korean/KoreanVanilla.xml" />
<UIStyle file="Content/UI/style.xml" /> <UIStyle file="Content/UI/style.xml" />
<Afflictions file="Content/Afflictions.xml" /> <Afflictions file="Content/Afflictions.xml" />
<Afflictions file="Content/Talents/Assistant/AfflictionsAssistant.xml" />
<Afflictions file="Content/Talents/Captain/AfflictionsCaptain.xml" />
<Afflictions file="Content/Talents/Doctor/AfflictionsDoctor.xml" />
<Afflictions file="Content/Talents/Engineer/AfflictionsEngineer.xml" />
<Afflictions file="Content/Talents/Mechanic/AfflictionsMechanic.xml" />
<Afflictions file="Content/Talents/Security/AfflictionsSecurity.xml" />
<Structure file="Content/Map/StructurePrefabs.xml" /> <Structure file="Content/Map/StructurePrefabs.xml" />
<Structure file="Content/Map/Outposts/OutpostStructurePrefabs.xml" /> <Structure file="Content/Map/Outposts/OutpostStructurePrefabs.xml" />
<Structure file="Content/Map/Pirates/PirateStructures.xml" /> <Structure file="Content/Map/Pirates/PirateStructures.xml" />
@@ -261,4 +272,11 @@
<EnemySubmarine file="Content/Map/EnemySubmarines/DugongPirate.sub"/> <EnemySubmarine file="Content/Map/EnemySubmarines/DugongPirate.sub"/>
<EnemySubmarine file="Content/Map/EnemySubmarines/HumpbackPirate.sub"/> <EnemySubmarine file="Content/Map/EnemySubmarines/HumpbackPirate.sub"/>
<EnemySubmarine file="Content/Map/EnemySubmarines/Typhon2Pirate.sub"/> <EnemySubmarine file="Content/Map/EnemySubmarines/Typhon2Pirate.sub"/>
<Talents file="Content/Talents/Assistant/TalentsAssistant.xml"/>
<Talents file="Content/Talents/Security/TalentsSecurity.xml"/>
<Talents file="Content/Talents/Engineer/TalentsEngineer.xml"/>
<Talents file="Content/Talents/Mechanic/TalentsMechanic.xml"/>
<Talents file="Content/Talents/Captain/TalentsCaptain.xml"/>
<Talents file="Content/Talents/Doctor/TalentsDoctor.xml"/>
<TalentTrees file="Content/Talents/TalentTrees.xml"/>
</contentpackage> </contentpackage>
@@ -1,7 +1,8 @@
using Microsoft.Xna.Framework; using Barotrauma.Items.Components;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Barotrauma.Items.Components;
using System.Linq; using System.Linq;
namespace Barotrauma namespace Barotrauma
@@ -328,5 +329,8 @@ namespace Barotrauma
protected virtual void OnStateChanged(AIState from, AIState to) { } protected virtual void OnStateChanged(AIState from, AIState to) { }
protected virtual void OnTargetChanged(AITarget previousTarget, AITarget newTarget) { } protected virtual void OnTargetChanged(AITarget previousTarget, AITarget newTarget) { }
public virtual void ClientRead(IReadMessage msg) { }
public virtual void ServerWrite(IWriteMessage msg) { }
} }
} }
@@ -1,5 +1,6 @@
using Barotrauma.Extensions; using Barotrauma.Extensions;
using Barotrauma.Items.Components; using Barotrauma.Items.Components;
using Barotrauma.Networking;
using FarseerPhysics; using FarseerPhysics;
using FarseerPhysics.Dynamics; using FarseerPhysics.Dynamics;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
@@ -37,6 +38,12 @@ namespace Barotrauma
PreviousState = _state; PreviousState = _state;
OnStateChanged(_state, value); OnStateChanged(_state, value);
_state = value; _state = value;
if (_state == AIState.Attack)
{
#if CLIENT
Character.PlaySound(CharacterSound.SoundType.Attack, maxInterval: 3);
#endif
}
} }
} }
@@ -50,6 +57,8 @@ namespace Barotrauma
private readonly float updateTargetsInterval = 1; private readonly float updateTargetsInterval = 1;
private readonly float updateMemoriesInverval = 1; private readonly float updateMemoriesInverval = 1;
private readonly float attackLimbResetInterval = 2; private readonly float attackLimbResetInterval = 2;
// Min priority for the memorized targets. The actual value fades gradually, unless kept fresh by selecting the target.
private const float minPriority = 10;
private readonly float avoidLookAheadDistance; private readonly float avoidLookAheadDistance;
@@ -394,7 +403,7 @@ namespace Barotrauma
public void SelectTarget(AITarget target, float priority) public void SelectTarget(AITarget target, float priority)
{ {
SelectedAiTarget = target; SelectedAiTarget = target;
selectedTargetMemory = GetTargetMemory(target, true); selectedTargetMemory = GetTargetMemory(target, addIfNotFound: true);
selectedTargetMemory.Priority = priority; selectedTargetMemory.Priority = priority;
ignoredTargets.Remove(target); ignoredTargets.Remove(target);
} }
@@ -641,7 +650,7 @@ namespace Barotrauma
{ {
Character c = a.Character; Character c = a.Character;
if (c.IsDead || c.Removed) { return false; } if (c.IsDead || c.Removed) { return false; }
if (!IsFriendly(Character, c)) { return true; } if (!Character.IsFriendly(c)) { return true; }
// Only apply the threshold to friendly characters // Only apply the threshold to friendly characters
return a.Damage >= selectedTargetingParams.DamageThreshold; return a.Damage >= selectedTargetingParams.DamageThreshold;
} }
@@ -976,7 +985,7 @@ namespace Barotrauma
Character owner = GetOwner(item); Character owner = GetOwner(item);
if (owner != null) if (owner != null)
{ {
if (IsFriendly(Character, owner)) if (Character.IsFriendly(owner))
{ {
ResetAITarget(); ResetAITarget();
State = AIState.Idle; State = AIState.Idle;
@@ -1337,7 +1346,7 @@ namespace Barotrauma
} }
else else
{ {
canAttack = Character.CharacterList.All(c => c == Character || !IsFriendly(Character, c) || IsFarEnough(c)); canAttack = Character.CharacterList.All(c => c == Character || !Character.IsFriendly(c) || IsFarEnough(c));
} }
if (canAttack) if (canAttack)
{ {
@@ -1356,7 +1365,7 @@ namespace Barotrauma
{ {
hitTarget = limb.character; hitTarget = limb.character;
} }
if (hitTarget != null && !hitTarget.IsDead && IsFriendly(Character, hitTarget)) if (hitTarget != null && !hitTarget.IsDead && Character.IsFriendly(hitTarget))
{ {
return true; return true;
} }
@@ -1764,7 +1773,7 @@ namespace Barotrauma
Character.AnimController.ReleaseStuckLimbs(); Character.AnimController.ReleaseStuckLimbs();
LatchOntoAI?.DeattachFromBody(reset: true, cooldown: 1); LatchOntoAI?.DeattachFromBody(reset: true, cooldown: 1);
if (attacker == null || attacker.AiTarget == null || attacker.Removed || attacker.IsDead) { return; } if (attacker == null || attacker.AiTarget == null || attacker.Removed || attacker.IsDead) { return; }
bool isFriendly = IsFriendly(Character, attacker); bool isFriendly = Character.IsFriendly(attacker);
if (wasLatched) if (wasLatched)
{ {
State = AIState.Escape; State = AIState.Escape;
@@ -1840,7 +1849,7 @@ namespace Barotrauma
} }
} }
AITargetMemory targetMemory = GetTargetMemory(attacker.AiTarget, true); AITargetMemory targetMemory = GetTargetMemory(attacker.AiTarget, addIfNotFound: true);
targetMemory.Priority += GetRelativeDamage(attackResult.Damage, Character.Vitality) * AIParams.AggressionHurt; targetMemory.Priority += GetRelativeDamage(attackResult.Damage, Character.Vitality) * AIParams.AggressionHurt;
// Only allow to react once. Otherwise would attack the target with only a fraction of a cooldown // Only allow to react once. Otherwise would attack the target with only a fraction of a cooldown
@@ -1884,16 +1893,15 @@ namespace Barotrauma
private bool UpdateLimbAttack(float deltaTime, Limb attackingLimb, Vector2 attackSimPos, float distance = -1, Limb targetLimb = null) private bool UpdateLimbAttack(float deltaTime, Limb attackingLimb, Vector2 attackSimPos, float distance = -1, Limb targetLimb = null)
{ {
if (SelectedAiTarget?.Entity == null) { return false; } if (SelectedAiTarget?.Entity == null) { return false; }
if (attackingLimb?.attack == null) { return false; }
ActiveAttack = attackingLimb?.attack; ActiveAttack = attackingLimb.attack;
if (wallTarget != null) if (wallTarget != null)
{ {
// If the selected target is not the wall target, make the wall target the selected target. // If the selected target is not the wall target, make the wall target the selected target.
var aiTarget = wallTarget.Structure.AiTarget; var aiTarget = wallTarget.Structure.AiTarget;
if (aiTarget != null && SelectedAiTarget != aiTarget) if (aiTarget != null && SelectedAiTarget != aiTarget)
{ {
SelectTarget(aiTarget, GetTargetMemory(SelectedAiTarget, true).Priority); SelectTarget(aiTarget, GetTargetMemory(SelectedAiTarget, addIfNotFound: true).Priority);
State = AIState.Attack; State = AIState.Attack;
} }
} }
@@ -1902,23 +1910,35 @@ namespace Barotrauma
{ {
//simulate attack input to get the character to attack client-side //simulate attack input to get the character to attack client-side
Character.SetInput(InputType.Attack, true, true); Character.SetInput(InputType.Attack, true, true);
#if SERVER if (!ActiveAttack.IsRunning)
GameMain.NetworkMember.CreateEntityEvent(Character, new object[]
{ {
#if SERVER
GameMain.NetworkMember.CreateEntityEvent(Character, new object[]
{
Networking.NetEntityEvent.Type.SetAttackTarget, Networking.NetEntityEvent.Type.SetAttackTarget,
attackingLimb, attackingLimb,
(damageTarget as Entity)?.ID ?? Entity.NullEntityID, (damageTarget as Entity)?.ID ?? Entity.NullEntityID,
damageTarget is Character character && targetLimb != null ? Array.IndexOf(character.AnimController.Limbs, targetLimb) : 0, damageTarget is Character character && targetLimb != null ? Array.IndexOf(character.AnimController.Limbs, targetLimb) : 0,
SimPosition.X, SimPosition.X,
SimPosition.Y SimPosition.Y
}); });
#else
Character.PlaySound(CharacterSound.SoundType.Attack, maxInterval: 3);
#endif #endif
}
if (attackingLimb.UpdateAttack(deltaTime, attackSimPos, damageTarget, out AttackResult attackResult, distance, targetLimb)) if (attackingLimb.UpdateAttack(deltaTime, attackSimPos, damageTarget, out AttackResult attackResult, distance, targetLimb))
{ {
if (damageTarget.Health > 0 && attackResult.Damage > 0) if (damageTarget.Health > 0 && attackResult.Damage > 0)
{ {
// Managed to hit a living/non-destroyed target. Increase the priority more if the target is low in health -> dies easily/soon // Managed to hit a living/non-destroyed target. Increase the priority more if the target is low in health -> dies easily/soon
selectedTargetMemory.Priority += GetRelativeDamage(attackResult.Damage, damageTarget.Health) * AIParams.AggressionGreed; float greed = AIParams.AggressionGreed;
if (!(damageTarget is Character))
{
// Halve the greed for attacking non-characters.
greed /= 2;
}
selectedTargetMemory.Priority += GetRelativeDamage(attackResult.Damage, damageTarget.Health) * greed;
} }
else else
{ {
@@ -2125,6 +2145,9 @@ namespace Barotrauma
string targetingTag = null; string targetingTag = null;
if (targetCharacter != null) if (targetCharacter != null)
{ {
// ignore if target is tagged to be explicitly ignored (Feign Death)
if (targetCharacter.HasAbilityFlag(AbilityFlags.IgnoredByEnemyAI)) { continue; }
if (targetCharacter.IsDead) if (targetCharacter.IsDead)
{ {
targetingTag = "dead"; targetingTag = "dead";
@@ -2139,7 +2162,7 @@ namespace Barotrauma
} }
else else
{ {
if (IsFriendly(Character, targetCharacter)) if (Character.IsFriendly(targetCharacter))
{ {
continue; continue;
} }
@@ -2449,7 +2472,7 @@ namespace Barotrauma
{ {
if (otherCharacter == character) { continue; } if (otherCharacter == character) { continue; }
if (otherCharacter.AIController?.SelectedAiTarget != aiTarget) { continue; } if (otherCharacter.AIController?.SelectedAiTarget != aiTarget) { continue; }
if (!IsFriendly(character, otherCharacter)) { continue; } if (!character.IsFriendly(otherCharacter)) { continue; }
valueModifier /= 2; valueModifier /= 2;
} }
} }
@@ -2469,7 +2492,7 @@ namespace Barotrauma
// -> just ignore the distance and attack whatever has the highest priority // -> just ignore the distance and attack whatever has the highest priority
dist = Math.Max(dist, 100.0f); dist = Math.Max(dist, 100.0f);
AITargetMemory targetMemory = GetTargetMemory(aiTarget, true); AITargetMemory targetMemory = GetTargetMemory(aiTarget, addIfNotFound: true);
if (Character.CurrentHull != null && Math.Abs(toTarget.Y) > Character.CurrentHull.Size.Y) if (Character.CurrentHull != null && Math.Abs(toTarget.Y) > Character.CurrentHull.Size.Y)
{ {
// Inside the sub, treat objects that are up or down, as they were farther away. // Inside the sub, treat objects that are up or down, as they were farther away.
@@ -2527,7 +2550,7 @@ namespace Barotrauma
// Don't target items that we own. // Don't target items that we own.
// This is a rare case, and almost entirely related to Humanhusks, so let's check it last to reduce unnecessary checks (although the check shouldn't be expensive) // This is a rare case, and almost entirely related to Humanhusks, so let's check it last to reduce unnecessary checks (although the check shouldn't be expensive)
if (owner == character) { continue; } if (owner == character) { continue; }
if (owner != null && (IsFriendly(Character, owner) || owner.AiTarget != null && ignoredTargets.Contains(owner.AiTarget))) if (owner != null && (Character.IsFriendly(owner) || owner.AiTarget != null && ignoredTargets.Contains(owner.AiTarget)))
{ {
continue; continue;
} }
@@ -2599,7 +2622,7 @@ namespace Barotrauma
wall = wallTarget?.Structure; wall = wallTarget?.Structure;
} }
// The target is not a wall or it's not the same as we are attached to -> release // The target is not a wall or it's not the same as we are attached to -> release
bool releaseTarget = wall == null || (!wall.Bodies.Contains(LatchOntoAI.AttachJoints[0].BodyB) && wall.Submarine?.PhysicsBody?.FarseerBody != LatchOntoAI.AttachJoints[0].BodyB); bool releaseTarget = wall?.Bodies == null || (!wall.Bodies.Contains(LatchOntoAI.AttachJoints[0].BodyB) && wall.Submarine?.PhysicsBody?.FarseerBody != LatchOntoAI.AttachJoints[0].BodyB);
if (!releaseTarget) if (!releaseTarget)
{ {
for (int i = 0; i < wall.Sections.Length; i++) for (int i = 0; i < wall.Sections.Length; i++)
@@ -2847,10 +2870,15 @@ namespace Barotrauma
{ {
if (addIfNotFound) if (addIfNotFound)
{ {
memory = new AITargetMemory(target, 10); memory = new AITargetMemory(target, minPriority);
targetMemories.Add(target, memory); targetMemories.Add(target, memory);
} }
} }
if (addIfNotFound)
{
// Keep the memory alive.
memory.Priority = Math.Max(memory.Priority, minPriority);
}
return memory; return memory;
} }
@@ -3014,7 +3042,7 @@ namespace Barotrauma
{ {
if (!onlyExisting && !tempParams.ContainsKey(tag)) if (!onlyExisting && !tempParams.ContainsKey(tag))
{ {
if (AIParams.TryAddNewTarget(tag, state, priority ?? 100, out targetParams)) if (AIParams.TryAddNewTarget(tag, state, priority ?? minPriority, out targetParams))
{ {
tempParams.Add(tag, targetParams); tempParams.Add(tag, targetParams);
} }
@@ -3051,6 +3079,7 @@ namespace Barotrauma
ChangeParams(target.SpeciesName, state, priority); ChangeParams(target.SpeciesName, state, priority);
if (target.IsHuman) if (target.IsHuman)
{ {
priority = GetTargetParams("human")?.Priority;
// Target also items, because if we are blind and the target doesn't move, we can only perceive the target when it uses items // Target also items, because if we are blind and the target doesn't move, we can only perceive the target when it uses items
if (state == AIState.Attack || state == AIState.Escape) if (state == AIState.Attack || state == AIState.Escape)
{ {
@@ -3061,20 +3090,19 @@ namespace Barotrauma
{ {
// If the target is shooting from the submarine, we might not perceive it because it doesn't move. // If the target is shooting from the submarine, we might not perceive it because it doesn't move.
// --> Target the submarine too. // --> Target the submarine too.
if (target.Submarine != null && (canAttackDoors || canAttackWalls)) if (target.Submarine != null && Character.Submarine == null && (canAttackDoors || canAttackWalls))
{ {
ChangeParams("room", state, priority); ChangeParams("room", state, priority * 0.1f);
if (canAttackWalls) if (canAttackWalls)
{ {
ChangeParams("wall", state, priority); ChangeParams("wall", state, priority * 0.1f);
} }
if (canAttackDoors) if (canAttackDoors)
{ {
ChangeParams("door", state, priority); ChangeParams("door", state, priority * 0.1f);
} }
} }
ChangeParams("provocative", state, priority, onlyExisting: true); ChangeParams("provocative", state, priority, onlyExisting: true);
ChangeParams("light", state, priority, onlyExisting: true);
} }
} }
} }
@@ -3306,7 +3334,17 @@ namespace Barotrauma
return null; return null;
} }
public static bool IsFriendly(Character me, Character other) => other.SpeciesName == me.SpeciesName || other.Params.CompareGroup(me.Params.Group); public override void ServerWrite(IWriteMessage msg)
{
msg.Write((byte)State);
PetBehavior?.ServerWrite(msg);
}
public override void ClientRead(IReadMessage msg)
{
State = (AIState)msg.ReadByte();
PetBehavior?.ClientRead(msg);
}
} }
//the "memory" of the Character //the "memory" of the Character
@@ -915,10 +915,10 @@ namespace Barotrauma
return false; return false;
} }
public static void ReportProblem(Character reporter, Order order) public static void ReportProblem(Character reporter, Order order, Hull targetHull = null)
{ {
if (reporter == null || order == null) { return; } if (reporter == null || order == null) { return; }
var visibleHulls = new List<Hull>(reporter.GetVisibleHulls()); var visibleHulls = targetHull is null ? new List<Hull>(reporter.GetVisibleHulls()) : new List<Hull> { targetHull };
foreach (var hull in visibleHulls) foreach (var hull in visibleHulls)
{ {
PropagateHullSafety(reporter, hull); PropagateHullSafety(reporter, hull);
@@ -1415,7 +1415,7 @@ namespace Barotrauma
if (GameMain.GameSession?.Campaign?.Map?.CurrentLocation != null) if (GameMain.GameSession?.Campaign?.Map?.CurrentLocation != null)
{ {
var reputationLoss = damageAmount * Reputation.ReputationLossPerWallDamage; var reputationLoss = damageAmount * Reputation.ReputationLossPerWallDamage;
GameMain.GameSession.Campaign.Map.CurrentLocation.Reputation.Value -= reputationLoss; GameMain.GameSession.Campaign.Map.CurrentLocation.Reputation.AddReputation(-reputationLoss);
} }
if (accumulatedDamage <= WarningThreshold) { return; } if (accumulatedDamage <= WarningThreshold) { return; }
@@ -1510,7 +1510,7 @@ namespace Barotrauma
var reputationLoss = MathHelper.Clamp( var reputationLoss = MathHelper.Clamp(
(item.Prefab.GetMinPrice() ?? 0) * Reputation.ReputationLossPerStolenItemPrice, (item.Prefab.GetMinPrice() ?? 0) * Reputation.ReputationLossPerStolenItemPrice,
Reputation.MinReputationLossPerStolenItem, Reputation.MaxReputationLossPerStolenItem); Reputation.MinReputationLossPerStolenItem, Reputation.MaxReputationLossPerStolenItem);
GameMain.GameSession.Campaign.Map.CurrentLocation.Reputation.Value -= reputationLoss; GameMain.GameSession.Campaign.Map.CurrentLocation.Reputation.AddReputation(-reputationLoss);
} }
item.StolenDuringRound = true; item.StolenDuringRound = true;
otherCharacter.Speak(TextManager.Get("dialogstealwarning"), null, Rand.Range(0.5f, 1.0f), "thief", 10.0f); otherCharacter.Speak(TextManager.Get("dialogstealwarning"), null, Rand.Range(0.5f, 1.0f), "thief", 10.0f);
@@ -1971,13 +1971,13 @@ namespace Barotrauma
if (c.Removed) { continue; } if (c.Removed) { continue; }
if (c.TeamID != Character.TeamID) { continue; } if (c.TeamID != Character.TeamID) { continue; }
if (c.IsIncapacitated) { continue; } if (c.IsIncapacitated) { continue; }
other = c;
if (c.IsPlayer) if (c.IsPlayer)
{ {
if (c.SelectedConstruction == target.Item) if (c.SelectedConstruction == target.Item)
{ {
// If the other character is player, don't try to operate // If the other character is player, don't try to operate
return true; other = c;
break;
} }
} }
else if (c.AIController is HumanAIController operatingAI) else if (c.AIController is HumanAIController operatingAI)
@@ -1991,7 +1991,8 @@ namespace Barotrauma
if (!isOrder && isTargetOrdered) if (!isOrder && isTargetOrdered)
{ {
// If the other bot is ordered to operate the item, let him do it, unless we are ordered too // If the other bot is ordered to operate the item, let him do it, unless we are ordered too
return true; other = c;
break;
} }
else else
{ {
@@ -2012,18 +2013,20 @@ namespace Barotrauma
// Steering is hard-coded -> cannot use the required skills collection defined in the xml // Steering is hard-coded -> cannot use the required skills collection defined in the xml
if (Character.GetSkillLevel("helm") <= c.GetSkillLevel("helm")) if (Character.GetSkillLevel("helm") <= c.GetSkillLevel("helm"))
{ {
return true; other = c;
break;
} }
} }
else if (target.DegreeOfSuccess(Character) <= target.DegreeOfSuccess(c)) else if (target.DegreeOfSuccess(Character) <= target.DegreeOfSuccess(c))
{ {
return true; other = c;
break;
} }
} }
} }
} }
} }
return false; return other != null;
bool IsOrderedToOperateThis(AIController ai) => ai is HumanAIController humanAI && humanAI.ObjectiveManager.CurrentOrder is AIObjectiveOperateItem operateOrder && operateOrder.Component.Item == target.Item; bool IsOrderedToOperateThis(AIController ai) => ai is HumanAIController humanAI && humanAI.ObjectiveManager.CurrentOrder is AIObjectiveOperateItem operateOrder && operateOrder.Component.Item == target.Item;
} }
@@ -161,12 +161,13 @@ namespace Barotrauma
private Vector2 CalculateSteeringSeek(Vector2 target, float weight, Func<PathNode, bool> startNodeFilter = null, Func<PathNode, bool> endNodeFilter = null, Func<PathNode, bool> nodeFilter = null, bool checkVisibility = true) private Vector2 CalculateSteeringSeek(Vector2 target, float weight, Func<PathNode, bool> startNodeFilter = null, Func<PathNode, bool> endNodeFilter = null, Func<PathNode, bool> nodeFilter = null, bool checkVisibility = true)
{ {
Vector2 targetDiff = target - currentTarget; Vector2 targetDiff = target - currentTarget;
if (currentPath != null && currentPath.Nodes.Any()) if (currentPath != null && currentPath.Nodes.Any() && character.Submarine != null)
{ {
//current path calculated relative to a different sub than where the character is now //target in a different sub than where the character is now
//take that into account when calculating if the target has moved //take that into account when calculating if the target has moved
Submarine currentPathSub = currentPath?.Nodes.First().Submarine; Submarine currentPathSub = currentPath?.CurrentNode?.Submarine;
if (currentPathSub != character.Submarine && character.Submarine != null) if (currentPathSub == character.Submarine) { currentPathSub = currentPath?.Nodes.LastOrDefault()?.Submarine; }
if (currentPathSub != character.Submarine && targetDiff.LengthSquared() > 1 && currentPathSub != null)
{ {
Vector2 subDiff = character.Submarine.SimPosition - currentPathSub.SimPosition; Vector2 subDiff = character.Submarine.SimPosition - currentPathSub.SimPosition;
targetDiff += subDiff; targetDiff += subDiff;
@@ -101,11 +101,11 @@ namespace Barotrauma
public enum CombatMode public enum CombatMode
{ {
Defensive, Defensive, // Use weapons against the enemy, but try to retreat to a safe place
Offensive, Offensive, // Engage the enemy and keep attacking it
Arrest, Arrest, // Try to arrest the enemy without using lethal weapons (stunning + handcuffs)
Retreat, Retreat, // Run to a safe place without attacking the target
None None // Don't use
} }
public CombatMode Mode { get; private set; } public CombatMode Mode { get; private set; }
@@ -958,14 +958,15 @@ namespace Barotrauma
} }
if (reloadTimer > 0) { return; } if (reloadTimer > 0) { return; }
if (holdFireCondition != null && holdFireCondition()) { return; } if (holdFireCondition != null && holdFireCondition()) { return; }
float sqrDist = Vector2.DistanceSquared(character.Position, Enemy.Position); sqrDistance = Vector2.DistanceSquared(character.WorldPosition, Enemy.WorldPosition);
distanceTimer = distanceCheckInterval;
if (WeaponComponent is MeleeWeapon meleeWeapon) if (WeaponComponent is MeleeWeapon meleeWeapon)
{ {
bool closeEnough = true; bool closeEnough = true;
float sqrRange = meleeWeapon.Range * meleeWeapon.Range; float sqrRange = meleeWeapon.Range * meleeWeapon.Range;
if (character.AnimController.InWater) if (character.AnimController.InWater)
{ {
if (sqrDist > sqrRange) if (sqrDistance > sqrRange)
{ {
closeEnough = false; closeEnough = false;
} }
@@ -1003,7 +1004,7 @@ namespace Barotrauma
{ {
if (WeaponComponent is RepairTool repairTool) if (WeaponComponent is RepairTool repairTool)
{ {
if (sqrDist > repairTool.Range * repairTool.Range) { return; } if (sqrDistance > repairTool.Range * repairTool.Range) { return; }
} }
float aimFactor = MathHelper.PiOver2 * (1 - AimAccuracy); float aimFactor = MathHelper.PiOver2 * (1 - AimAccuracy);
if (VectorExtensions.Angle(VectorExtensions.Forward(Weapon.body.TransformedRotation), Enemy.Position - Weapon.Position) < MathHelper.PiOver4 + aimFactor) if (VectorExtensions.Angle(VectorExtensions.Forward(Weapon.body.TransformedRotation), Enemy.Position - Weapon.Position) < MathHelper.PiOver4 + aimFactor)
@@ -53,9 +53,13 @@ namespace Barotrauma
distanceFactor = 1; distanceFactor = 1;
} }
float severity = AIObjectiveExtinguishFires.GetFireSeverity(targetHull); float severity = AIObjectiveExtinguishFires.GetFireSeverity(targetHull);
if (severity > 0.5f && !isOrder) if (severity > 0.75f && !isOrder &&
targetHull.RoomName != null &&
!targetHull.RoomName.Contains("reactor", StringComparison.OrdinalIgnoreCase) &&
!targetHull.RoomName.Contains("engine", StringComparison.OrdinalIgnoreCase) &&
!targetHull.RoomName.Contains("command", StringComparison.OrdinalIgnoreCase))
{ {
// Ignore severe fires unless ordered. (Let the fire drain all the oxygen instead). // Ignore severe fires to prevent casualities unless ordered to extinguish.
Priority = 0; Priority = 0;
Abandon = true; Abandon = true;
} }
@@ -25,7 +25,7 @@ namespace Barotrauma
/// <summary> /// <summary>
/// 0-1 based on the horizontal size of all of the fires in the hull. /// 0-1 based on the horizontal size of all of the fires in the hull.
/// </summary> /// </summary>
public static float GetFireSeverity(Hull hull) => MathHelper.Lerp(0, 1, MathUtils.InverseLerp(0, Math.Min(hull.Rect.Width, 1000), hull.FireSources.Sum(fs => fs.Size.X))); public static float GetFireSeverity(Hull hull) => MathHelper.Lerp(0, 1, MathUtils.InverseLerp(0, 500, hull.FireSources.Sum(fs => fs.Size.X)));
protected override IEnumerable<Hull> GetList() => Hull.hullList; protected override IEnumerable<Hull> GetList() => Hull.hullList;
@@ -63,6 +63,7 @@ namespace Barotrauma
if (target.CurrentHull == null) { return false; } if (target.CurrentHull == null) { return false; }
if (HumanAIController.IsFriendly(character, target)) { return false; } if (HumanAIController.IsFriendly(character, target)) { return false; }
if (!character.Submarine.IsConnectedTo(target.Submarine)) { return false; } if (!character.Submarine.IsConnectedTo(target.Submarine)) { return false; }
if (target.HasAbilityFlag(AbilityFlags.IgnoredByEnemyAI)) { return false; }
return true; return true;
} }
} }
@@ -57,7 +57,20 @@ namespace Barotrauma
}; };
}, },
onAbandon: () => Abandon = true, onAbandon: () => Abandon = true,
onCompleted: () => RemoveSubObjective(ref getDivingGear)); onCompleted: () =>
{
RemoveSubObjective(ref getDivingGear);
if (gearTag == HEAVY_DIVING_GEAR && HumanAIController.HasItem(character, LIGHT_DIVING_GEAR, out IEnumerable<Item> masks, requireEquipped: true))
{
foreach (Item mask in masks)
{
if (mask != targetItem)
{
character.Inventory.TryPutItem(mask, character, CharacterInventory.anySlot);
}
}
}
});
} }
else else
{ {
@@ -71,9 +84,13 @@ namespace Barotrauma
{ {
if (character.IsOnPlayerTeam) if (character.IsOnPlayerTeam)
{ {
if (HumanAIController.HasItem(character, "oxygensource", out _, conditionPercentage: min)) if (HumanAIController.HasItem(character, OXYGEN_SOURCE, out _, conditionPercentage: min))
{ {
character.Speak(TextManager.Get("dialogswappingoxygentank"), null, 0, "swappingoxygentank", 30.0f); character.Speak(TextManager.Get("dialogswappingoxygentank"), null, 0, "swappingoxygentank", 30.0f);
if (character.Inventory.FindAllItems(i => i.HasTag(OXYGEN_SOURCE) && i.Condition > min).Count == 1)
{
character.Speak(TextManager.Get("dialoglastoxygentank"), null, 0.0f, "dialoglastoxygentank", 30.0f);
}
} }
else else
{ {
@@ -105,7 +122,7 @@ namespace Barotrauma
onAbandon: () => onAbandon: () =>
{ {
Abandon = true; Abandon = true;
if (remainingTanks > 0 && !HumanAIController.HasItem(character, "oxygensource", out _, conditionPercentage: 0.01f)) if (remainingTanks > 0 && !HumanAIController.HasItem(character, OXYGEN_SOURCE, out _, conditionPercentage: 0.01f))
{ {
character.Speak(TextManager.Get("dialogcantfindtoxygen"), null, 0, "cantfindoxygen", 30.0f); character.Speak(TextManager.Get("dialogcantfindtoxygen"), null, 0, "cantfindoxygen", 30.0f);
} }
@@ -121,7 +138,7 @@ namespace Barotrauma
int ReportOxygenTankCount() int ReportOxygenTankCount()
{ {
if (character.Submarine != Submarine.MainSub) { return 1; } if (character.Submarine != Submarine.MainSub) { return 1; }
int remainingOxygenTanks = Submarine.MainSub.GetItems(false).Count(i => i.HasTag("oxygensource") && i.Condition > 1); int remainingOxygenTanks = Submarine.MainSub.GetItems(false).Count(i => i.HasTag(OXYGEN_SOURCE) && i.Condition > 1);
if (remainingOxygenTanks == 0) if (remainingOxygenTanks == 0)
{ {
character.Speak(TextManager.Get("DialogOutOfOxygenTanks"), null, 0.0f, "outofoxygentanks", 30.0f); character.Speak(TextManager.Get("DialogOutOfOxygenTanks"), null, 0.0f, "outofoxygentanks", 30.0f);
@@ -136,17 +153,6 @@ namespace Barotrauma
} }
} }
/// <summary>
/// Returns false only when no inventory can be found from the item.
/// </summary>
public static bool EjectEmptyTanks(Character actor, Item target, out IEnumerable<Item> containedItems)
{
containedItems = target.OwnInventory?.AllItems;
if (containedItems == null) { return false; }
AIController.UnequipEmptyItems(actor, target);
return true;
}
public override void Reset() public override void Reset()
{ {
base.Reset(); base.Reset();
@@ -242,9 +242,8 @@ namespace Barotrauma
if (!searchingNewHull) if (!searchingNewHull)
{ {
//find all available hulls first //find all available hulls first
FindTargetHulls();
searchingNewHull = true; searchingNewHull = true;
return; FindTargetHulls();
} }
else if (targetHulls.Any()) else if (targetHulls.Any())
{ {
@@ -255,11 +254,10 @@ namespace Barotrauma
var path = PathSteering.PathFinder.FindPath(character.SimPosition, currentTarget.SimPosition, errorMsgStr: null, nodeFilter: node => var path = PathSteering.PathFinder.FindPath(character.SimPosition, currentTarget.SimPosition, errorMsgStr: null, nodeFilter: node =>
{ {
if (node.Waypoint.CurrentHull == null) { return false; } if (node.Waypoint.CurrentHull == null) { return false; }
// Check that there is no unsafe or forbidden hulls on the way to the target // Check that there is no unsafe hulls on the way to the target
if (node.Waypoint.CurrentHull != character.CurrentHull && HumanAIController.UnsafeHulls.Contains(node.Waypoint.CurrentHull)) { return false; } if (node.Waypoint.CurrentHull != character.CurrentHull && HumanAIController.UnsafeHulls.Contains(node.Waypoint.CurrentHull)) { return false; }
if (isCurrentHullAllowed && IsForbidden(node.Waypoint.CurrentHull)) { return false; }
return true; return true;
}); }, endNodeFilter: node => !isCurrentHullAllowed | !IsForbidden(node.Waypoint.CurrentHull));
if (path.Unreachable) if (path.Unreachable)
{ {
//can't go to this room, remove it from the list and try another room //can't go to this room, remove it from the list and try another room
@@ -271,30 +269,19 @@ namespace Barotrauma
SetTargetTimerLow(); SetTargetTimerLow();
return; return;
} }
character.AIController.SelectTarget(currentTarget.AiTarget);
PathSteering.SetPath(path);
SetTargetTimerNormal();
searchingNewHull = false; searchingNewHull = false;
} }
else else
{ {
// Couldn't find a target for some reason -> reset // Couldn't find a valid hull
SetTargetTimerHigh(); SetTargetTimerHigh();
searchingNewHull = false; searchingNewHull = false;
} }
if (currentTarget != null)
{
character.AIController.SelectTarget(currentTarget.AiTarget);
string errorMsg = null;
#if DEBUG
bool isRoomNameFound = currentTarget.DisplayName != null;
errorMsg = "(Character " + character.Name + " idling, target " + (isRoomNameFound ? currentTarget.DisplayName : currentTarget.ToString()) + ")";
#endif
var path = PathSteering.PathFinder.FindPath(character.SimPosition, currentTarget.SimPosition, errorMsgStr: errorMsg, nodeFilter: node => node.Waypoint.CurrentHull != null);
PathSteering.SetPath(path);
}
SetTargetTimerNormal();
} }
newTargetTimer -= deltaTime; newTargetTimer -= deltaTime;
if (!character.IsClimbing && IsSteeringFinished()) if (!character.IsClimbing && IsSteeringFinished())
{ {
Wander(deltaTime); Wander(deltaTime);
@@ -141,6 +141,7 @@ namespace Barotrauma
public Entity TargetEntity; public Entity TargetEntity;
public ItemComponent TargetItemComponent; public ItemComponent TargetItemComponent;
public readonly bool UseController; public readonly bool UseController;
public readonly string[] ControllerTags;
public Controller ConnectedController; public Controller ConnectedController;
public Character OrderGiver; public Character OrderGiver;
@@ -309,6 +310,7 @@ namespace Barotrauma
color = orderElement.GetAttributeColor("color"); color = orderElement.GetAttributeColor("color");
FadeOutTime = orderElement.GetAttributeFloat("fadeouttime", 0.0f); FadeOutTime = orderElement.GetAttributeFloat("fadeouttime", 0.0f);
UseController = orderElement.GetAttributeBool("usecontroller", false); UseController = orderElement.GetAttributeBool("usecontroller", false);
ControllerTags = orderElement.GetAttributeStringArray("controllertags", new string[0]);
TargetAllCharacters = orderElement.GetAttributeBool("targetallcharacters", false); TargetAllCharacters = orderElement.GetAttributeBool("targetallcharacters", false);
AppropriateJobs = orderElement.GetAttributeStringArray("appropriatejobs", new string[0]); AppropriateJobs = orderElement.GetAttributeStringArray("appropriatejobs", new string[0]);
Options = orderElement.GetAttributeStringArray("options", new string[0]); Options = orderElement.GetAttributeStringArray("options", new string[0]);
@@ -380,6 +382,7 @@ namespace Barotrauma
SymbolSprite = prefab.SymbolSprite; SymbolSprite = prefab.SymbolSprite;
Color = prefab.Color; Color = prefab.Color;
UseController = prefab.UseController; UseController = prefab.UseController;
ControllerTags = prefab.ControllerTags;
TargetAllCharacters = prefab.TargetAllCharacters; TargetAllCharacters = prefab.TargetAllCharacters;
AppropriateJobs = prefab.AppropriateJobs; AppropriateJobs = prefab.AppropriateJobs;
FadeOutTime = prefab.FadeOutTime; FadeOutTime = prefab.FadeOutTime;
@@ -399,7 +402,7 @@ namespace Barotrauma
{ {
if (UseController) if (UseController)
{ {
ConnectedController = targetItem.Item?.FindController(); ConnectedController = targetItem.Item?.FindController(tags: ControllerTags);
if (ConnectedController == null) if (ConnectedController == null)
{ {
DebugConsole.AddWarning("AI: Tried to use a controller for operating an item, but couldn't find any."); DebugConsole.AddWarning("AI: Tried to use a controller for operating an item, but couldn't find any.");
@@ -450,19 +453,37 @@ namespace Barotrauma
return false; return false;
} }
public string GetChatMessage(string targetCharacterName, string targetRoomName, bool givingOrderToSelf, string orderOption = "") public string GetChatMessage(string targetCharacterName, string targetRoomName, bool givingOrderToSelf, string orderOption = "", int? priority = null)
{ {
orderOption ??= ""; priority ??= CharacterInfo.HighestManualOrderPriority;
// If the order has a lesser priority, it means we are rearranging character orders
string messageTag = (givingOrderToSelf && !TargetAllCharacters ? "OrderDialogSelf." : "OrderDialog.") + Identifier; if (!TargetAllCharacters && priority != CharacterInfo.HighestManualOrderPriority && Identifier != "dismissed")
if (Identifier != "dismissed" && !string.IsNullOrEmpty(orderOption)) { messageTag += "." + orderOption; } {
return TextManager.GetWithVariable("rearrangedorders", "[name]", targetCharacterName ?? string.Empty, returnNull: true) ?? string.Empty;
if (targetCharacterName == null) { targetCharacterName = ""; } }
if (targetRoomName == null) { targetRoomName = ""; } string messageTag = $"{(givingOrderToSelf && !TargetAllCharacters ? "OrderDialogSelf" : "OrderDialog")}";
string msg = TextManager.GetWithVariables(messageTag, new string[2] { "[name]", "[roomname]" }, new string[2] { targetCharacterName, targetRoomName }, new bool[2] { false, true }, true); messageTag += $".{Identifier}";
if (msg == null) { return ""; } if (!string.IsNullOrEmpty(orderOption))
{
return msg; if (Identifier != "dismissed")
{
messageTag += $".{orderOption}";
}
else
{
string[] splitOption = orderOption.Split('.');
if (splitOption.Length > 0)
{
messageTag += $".{splitOption[0]}";
}
}
}
string msg = TextManager.GetWithVariables(messageTag,
new string[2] { "[name]", "[roomname]" },
new string[2] { targetCharacterName ?? string.Empty, targetRoomName ?? string.Empty },
formatCapitals: new bool[2] { false, true },
returnNull: true);
return msg ?? string.Empty;
} }
/// <summary> /// <summary>
@@ -505,7 +526,7 @@ namespace Barotrauma
if (item.NonInteractable) { continue; } if (item.NonInteractable) { continue; }
if (ItemComponentType != null && item.Components.None(c => c.GetType() == ItemComponentType)) { continue; } if (ItemComponentType != null && item.Components.None(c => c.GetType() == ItemComponentType)) { continue; }
Controller controller = null; Controller controller = null;
if (UseController && !item.TryFindController(out controller)) { continue; } if (UseController && !item.TryFindController(out controller, tags: ControllerTags)) { continue; }
if (interactableFor != null && (!item.IsInteractable(interactableFor) || (UseController && !controller.Item.IsInteractable(interactableFor)))) { continue; } if (interactableFor != null && (!item.IsInteractable(interactableFor) || (UseController && !controller.Item.IsInteractable(interactableFor)))) { continue; }
matchingItems.Add(item); matchingItems.Add(item);
} }

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