Unstable 0.1400.0.0

This commit is contained in:
Markus Isberg
2021-05-11 15:47:47 +03:00
parent 3f324b14e8
commit 92f0264af2
247 changed files with 8238 additions and 1911 deletions
@@ -0,0 +1,32 @@
namespace Barotrauma
{
abstract partial class AIObjective
{
public static Sprite GetSprite(string identifier, string option, Entity targetEntity)
{
if (string.IsNullOrEmpty(identifier))
{
return null;
}
identifier = identifier.RemoveWhitespace();
if (Order.Prefabs.TryGetValue(identifier, out Order orderPrefab))
{
if (!string.IsNullOrEmpty(option) && orderPrefab.OptionSprites.TryGetValue(option, out var optionSprite))
{
return optionSprite;
}
if (targetEntity is Item targetItem && targetItem.Prefab.MinimapIcon != null)
{
return targetItem.Prefab.MinimapIcon;
}
return orderPrefab.SymbolSprite;
}
return GUI.Style.GetComponentStyle($"{identifier}objectiveicon")?.GetDefaultSprite();
}
public Sprite GetSprite()
{
return GetSprite(Identifier, Option, (this as AIObjectiveOperateItem)?.OperateTarget);
}
}
}
@@ -102,11 +102,13 @@ namespace Barotrauma
set { chromaticAberrationStrength = MathHelper.Clamp(value, 0.0f, 100.0f); }
}
public Color GrainColor { get; set; }
private float grainStrength;
public float GrainStrength
{
get => grainStrength;
set => grainStrength = MathHelper.Clamp(value, 0.0f, 1.0f);
set => grainStrength = Math.Max(0, value);
}
private readonly List<ParticleEmitter> bloodEmitters = new List<ParticleEmitter>();
@@ -859,7 +861,14 @@ namespace Barotrauma
Color nameColor = Color.White;
if (Controlled != null && TeamID != Controlled.TeamID)
{
nameColor = TeamID == CharacterTeamType.FriendlyNPC ? Color.SkyBlue : GUI.Style.Red;
if (TeamID == CharacterTeamType.FriendlyNPC)
{
nameColor = UniqueNameColor ?? Color.SkyBlue;
}
else
{
nameColor = GUI.Style.Red;
}
}
if (CampaignInteractionType != CampaignMode.InteractionType.None && AllowCustomInteract)
{
@@ -296,7 +296,7 @@ namespace Barotrauma
float alpha = GetDistanceBasedIconAlpha(brokenItem);
if (alpha <= 0.0f) continue;
GUI.DrawIndicator(spriteBatch, brokenItem.DrawPosition, cam, 100.0f, GUI.BrokenIcon,
Color.Lerp(GUI.Style.Red, GUI.Style.Orange * 0.5f, brokenItem.Condition / brokenItem.MaxCondition) * alpha);
Color.Lerp(GUI.Style.Red, GUI.Style.Orange * 0.5f, brokenItem.Condition / brokenItem.MaxCondition) * alpha);
}
float GetDistanceBasedIconAlpha(ISpatialEntity target, float maxDistance = 1000.0f)
@@ -341,7 +341,7 @@ namespace Barotrauma
if (!GUI.DisableItemHighlights && !Inventory.DraggingItemToWorld)
{
bool shiftDown = PlayerInput.KeyDown(Keys.LeftShift) || PlayerInput.KeyDown(Keys.RightShift);
bool shiftDown = PlayerInput.IsShiftDown();
if (shouldRecreateHudTexts || heldDownShiftWhenGotHudTexts != shiftDown)
{
shouldRecreateHudTexts = true;
@@ -391,7 +391,16 @@ namespace Barotrauma
if (npc.CampaignInteractionType == CampaignMode.InteractionType.None || npc.Submarine != character.Submarine || npc.IsDead || npc.IsIncapacitated) { continue; }
var iconStyle = GUI.Style.GetComponentStyle("CampaignInteractionIcon." + npc.CampaignInteractionType);
GUI.DrawIndicator(spriteBatch, npc.WorldPosition, cam, 500.0f, iconStyle.GetDefaultSprite(), iconStyle.Color);
GUI.DrawIndicator(spriteBatch, npc.WorldPosition, cam, npc.CurrentHull == Character.Controlled.CurrentHull ? 500.0f : 100.0f, iconStyle.GetDefaultSprite(), iconStyle.Color);
}
foreach (Item item in Item.ItemList)
{
if (item.IconStyle is null || item.Submarine != character.Submarine) { continue; }
if (Vector2.DistanceSquared(character.Position, item.Position) > 500f*500f) { continue; }
var body = Submarine.CheckVisibility(character.SimPosition, item.SimPosition, ignoreLevel: true);
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);
}
}
@@ -291,8 +291,7 @@ namespace Barotrauma
break;
case ServerNetObject.ENTITY_EVENT:
int eventType = msg.ReadRangedInteger(0, 6);
int eventType = msg.ReadRangedInteger(0, 9);
switch (eventType)
{
case 0: //NetEntityEvent.Type.InventoryState
@@ -354,56 +353,88 @@ namespace Barotrauma
info?.SetSkillLevel(skillIdentifier, skillLevel, Position + Vector2.UnitY * 150.0f);
}
break;
case 4: //NetEntityEvent.Type.ExecuteAttack
case 4: // NetEntityEvent.Type.SetAttackTarget
case 5: //NetEntityEvent.Type.ExecuteAttack
int attackLimbIndex = msg.ReadByte();
UInt16 targetEntityID = msg.ReadUInt16();
int targetLimbIndex = msg.ReadByte();
//255 = entity already removed, no need to do anything
if (attackLimbIndex == 255 || Removed) { break; }
Vector2 targetSimPos = new Vector2(msg.ReadSingle(), msg.ReadSingle());
if (attackLimbIndex >= AnimController.Limbs.Length)
{
DebugConsole.ThrowError($"Received invalid ExecuteAttack message. Limb index out of bounds (character: {Name}, limb index: {attackLimbIndex}, limb count: {AnimController.Limbs.Length})");
DebugConsole.ThrowError($"Received invalid SetAttack/ExecuteAttack message. Limb index out of bounds (character: {Name}, limb index: {attackLimbIndex}, limb count: {AnimController.Limbs.Length})");
break;
}
Limb attackLimb = AnimController.Limbs[attackLimbIndex];
Limb targetLimb = null;
if (!(FindEntityByID(targetEntityID) is IDamageable targetEntity))
{
DebugConsole.ThrowError($"Received invalid ExecuteAttack message. Target entity not found (ID {targetEntityID})");
DebugConsole.ThrowError($"Received invalid SetAttack/ExecuteAttack message. Target entity not found (ID {targetEntityID})");
break;
}
if (targetEntity is Character targetCharacter)
{
if (targetLimbIndex >= targetCharacter.AnimController.Limbs.Length)
{
DebugConsole.ThrowError($"Received invalid ExecuteAttack message. Target limb index out of bounds (target character: {targetCharacter.Name}, limb index: {targetLimbIndex}, limb count: {targetCharacter.AnimController.Limbs.Length})");
DebugConsole.ThrowError($"Received invalid SetAttack/ExecuteAttack message. Target limb index out of bounds (target character: {targetCharacter.Name}, limb index: {targetLimbIndex}, limb count: {targetCharacter.AnimController.Limbs.Length})");
break;
}
targetLimb = targetCharacter.AnimController.Limbs[targetLimbIndex];
}
if (attackLimb?.attack != null)
{
attackLimb.ExecuteAttack(targetEntity, targetLimb, out _);
if (eventType == 4)
{
SetAttackTarget(attackLimb, targetEntity, targetSimPos);
}
else
{
attackLimb.ExecuteAttack(targetEntity, targetLimb, out _);
}
}
break;
case 5: //NetEntityEvent.Type.AssignCampaignInteraction
case 6: //NetEntityEvent.Type.AssignCampaignInteraction
byte campaignInteractionType = msg.ReadByte();
(GameMain.GameSession?.GameMode as CampaignMode)?.AssignNPCMenuInteraction(this, (CampaignMode.InteractionType)campaignInteractionType);
break;
case 6: //NetEntityEvent.Type.ObjectiveManagerOrderState
bool properData = msg.ReadBoolean();
if (!properData) { break; }
int orderIndex = msg.ReadRangedInteger(0, Order.PrefabList.Count);
var orderPrefab = Order.PrefabList[orderIndex];
string option = null;
if (orderPrefab.HasOptions)
case 7: //NetEntityEvent.Type.ObjectiveManagerState
// 1 = order, 2 = objective
int msgType = msg.ReadRangedInteger(0, 2);
if (msgType == 0) { break; }
bool validData = msg.ReadBoolean();
if (!validData) { break; }
if (msgType == 1)
{
int optionIndex = msg.ReadRangedInteger(0, orderPrefab.Options.Length);
option = orderPrefab.Options[optionIndex];
int orderIndex = msg.ReadRangedInteger(0, Order.PrefabList.Count);
var orderPrefab = Order.PrefabList[orderIndex];
string option = null;
if (orderPrefab.HasOptions)
{
int optionIndex = msg.ReadRangedInteger(0, orderPrefab.Options.Length);
option = orderPrefab.Options[optionIndex];
}
GameMain.GameSession?.CrewManager?.SetOrderHighlight(this, orderPrefab.Identifier, option);
}
else if (msgType == 2)
{
string identifier = msg.ReadString();
string option = msg.ReadString();
ushort objectiveTargetEntityId = msg.ReadUInt16();
var objectiveTargetEntity = FindEntityByID(objectiveTargetEntityId);
GameMain.GameSession?.CrewManager?.CreateObjectiveIcon(this, identifier, option, objectiveTargetEntity);
}
break;
case 8: //NetEntityEvent.Type.TeamChange
byte newTeamId = msg.ReadByte();
ChangeTeam((CharacterTeamType)newTeamId);
break;
case 9: //NetEntityEvent.Type.AddToCrew
GameMain.GameSession.CrewManager.AddCharacter(this);
foreach (Item item in Inventory.AllItems)
{
item.AllowStealing = true;
}
GameMain.GameSession.CrewManager.SetHighlightedOrderIcon(this, orderPrefab.Identifier, option);
break;
}
msg.ReadPadBits();
@@ -471,7 +502,7 @@ namespace Barotrauma
var x = inc.ReadSingle();
var y = inc.ReadSingle();
var hull = FindEntityByID(inc.ReadUInt16()) as Hull;
targetPosition = new OrderTarget(new Vector2(x, y), hull, true);
targetPosition = new OrderTarget(new Vector2(x, y), hull, creatingFromExistingData: true);
}
if (orderPrefabIndex >= 0 && orderPrefabIndex < Order.PrefabList.Count)
@@ -485,7 +516,7 @@ namespace Barotrauma
new Order(orderPrefab, targetPosition, orderGiver: orderGiver);
character.SetOrder(order,
orderOptionIndex >= 0 && orderOptionIndex < orderPrefab.Options.Length ? orderPrefab.Options[orderOptionIndex] : null,
orderPriority, orderGiver, speak: false);
orderPriority, orderGiver, speak: false, force: true);
}
else
{
@@ -63,6 +63,8 @@ namespace Barotrauma
private GUIListBox afflictionTooltip;
private static readonly Color oxygenLowGrainColor = new Color(0.1f, 0.1f, 0.1f, 1f);
private struct HeartratePosition
{
public float Time;
@@ -671,17 +673,19 @@ namespace Barotrauma
bloodParticleTimer -= deltaTime * (affliction.Strength / 10.0f);
if (bloodParticleTimer <= 0.0f)
{
var emitter = Character.BloodEmitters.FirstOrDefault();
bool inWater = Character.AnimController.InWater;
var drawTarget = inWater ? Particles.ParticlePrefab.DrawTargetType.Water : Particles.ParticlePrefab.DrawTargetType.Air;
var emitter = Character.BloodEmitters.FirstOrDefault(e => e.Prefab.ParticlePrefab.DrawTarget == drawTarget || e.Prefab.ParticlePrefab.DrawTarget == Particles.ParticlePrefab.DrawTargetType.Both);
float particleMinScale = emitter != null ? emitter.Prefab.ScaleMin : 0.5f;
float particleMaxScale = emitter != null ? emitter.Prefab.ScaleMax : 1;
float severity = Math.Min(affliction.Strength / affliction.Prefab.MaxStrength * Character.Params.BleedParticleMultiplier, 1);
float bloodParticleSize = MathHelper.Lerp(particleMinScale, particleMaxScale, severity);
bool inWater = Character.AnimController.InWater;
if (!inWater)
{
bloodParticleSize *= 2.0f;
}
// TODO: use the blood emitter?
var blood = GameMain.ParticleManager.CreateParticle(
inWater ? Character.Params.BleedParticleWater : Character.Params.BleedParticleAir,
targetLimb.WorldPosition, Rand.Vector(affliction.Strength), 0.0f, Character.AnimController.CurrentHull);
@@ -742,6 +746,7 @@ namespace Barotrauma
float radialDistortStrength = 0.0f;
float chromaticAberrationStrength = 0.0f;
float grainStrength = 0.0f;
Color grainColor = Color.White;
if (Character.IsUnconscious)
{
@@ -750,10 +755,15 @@ namespace Barotrauma
}
else if (OxygenAmount < 100.0f)
{
// TODO disable some of these?
blurStrength = MathHelper.Lerp(0.5f, 1.0f, 1.0f - Vitality / MaxVitality);
distortStrength = blurStrength;
distortSpeed = (blurStrength + 1.0f);
distortSpeed *= distortSpeed * distortSpeed * distortSpeed;
grainStrength = MathHelper.Lerp(0.5f, 10.0f, 1.0f - (OxygenAmount - LowOxygenThreshold) / LowOxygenThreshold);
grainColor = oxygenLowGrainColor;
}
foreach (Affliction affliction in afflictions)
@@ -778,6 +788,7 @@ namespace Barotrauma
Character.RadialDistortStrength = radialDistortStrength;
Character.ChromaticAberrationStrength = chromaticAberrationStrength;
Character.GrainStrength = grainStrength;
Character.GrainColor = grainColor;
if (blurStrength > 0.0f)
{
distortTimer = (distortTimer + deltaTime * distortSpeed) % MathHelper.TwoPi;
@@ -2004,7 +2015,7 @@ namespace Barotrauma
existingAffliction.PeriodicEffectTimers[periodicEffect.First] = periodicEffect.Second;
foreach (StatusEffect effect in periodicEffect.First.StatusEffects)
{
existingAffliction.ApplyStatusEffect(effect, deltaTime: 1.0f, this, targetLimb: null);
existingAffliction.ApplyStatusEffect(ActionType.OnActive, effect, deltaTime: 1.0f, this, targetLimb: null);
}
}
}
@@ -2071,7 +2082,7 @@ namespace Barotrauma
foreach (StatusEffect effect in periodicEffect.First.StatusEffects)
{
Limb targetLimb = Character.AnimController.Limbs.FirstOrDefault(l => l.HealthIndex == limbHealths.IndexOf(newAffliction.First));
existingAffliction.ApplyStatusEffect(effect, deltaTime: 1.0f, this, targetLimb: targetLimb);
existingAffliction.ApplyStatusEffect(ActionType.OnActive, effect, deltaTime: 1.0f, this, targetLimb: targetLimb);
}
}
}
@@ -997,7 +997,7 @@ namespace Barotrauma
}
wearableColor = wearableItemComponent.Item.GetSpriteColor();
}
float textureScale = wearable.InheritTextureScale ? TextureScale : 1;
float textureScale = wearable.InheritTextureScale ? TextureScale : wearable.Scale;
wearable.Sprite.Draw(spriteBatch,
new Vector2(body.DrawPosition.X, -body.DrawPosition.Y),
@@ -15,6 +15,7 @@ using Barotrauma.Extensions;
using Barotrauma.Steam;
using System.Threading.Tasks;
using Barotrauma.MapCreatures.Behavior;
using static Barotrauma.FabricationRecipe;
namespace Barotrauma
{
@@ -1367,6 +1368,237 @@ namespace Barotrauma
}
}, isCheat: false));
commands.Add(new Command("analyzeitem", "analyzeitem: Analyzes one item for exploits.", (string[] args) =>
{
if (args.Length < 1) return;
List<FabricationRecipe> fabricableItems = new List<FabricationRecipe>();
foreach (ItemPrefab iPrefab in ItemPrefab.Prefabs)
{
fabricableItems.AddRange(iPrefab.FabricationRecipes);
}
string itemNameOrId = args[0].ToLowerInvariant();
ItemPrefab itemPrefab =
(MapEntityPrefab.Find(itemNameOrId, identifier: null, showErrorMessages: false) ??
MapEntityPrefab.Find(null, identifier: itemNameOrId, showErrorMessages: false)) as ItemPrefab;
if (itemPrefab == null)
{
NewMessage("Item not found for analyzing.");
return;
}
NewMessage("Analyzing item " + itemPrefab.Name + " with base cost " + itemPrefab.DefaultPrice.Price);
var fabricationRecipe = fabricableItems.Find(f => f.TargetItem == itemPrefab);
// omega nesting incoming
if (fabricationRecipe != null)
{
foreach (Tuple<string, PriceInfo> itemLocationPrice in itemPrefab.GetSellPricesOver(0))
{
NewMessage(" If bought at " + itemLocationPrice.Item1 + " it costs " + itemLocationPrice.Item2.Price);
int totalPrice = 0;
int? totalBestPrice = 0;
foreach (var ingredient in fabricationRecipe.RequiredItems)
{
foreach (ItemPrefab ingredientItemPrefab in ingredient.ItemPrefabs)
{
NewMessage(" Its ingredient " + ingredientItemPrefab.Name + " has base cost " + ingredientItemPrefab.DefaultPrice.Price);
totalPrice += ingredientItemPrefab.DefaultPrice.Price;
totalBestPrice += ingredientItemPrefab.GetMinPrice();
int basePrice = ingredientItemPrefab.DefaultPrice.Price;
foreach (Tuple<string, PriceInfo> ingredientItemLocationPrice in ingredientItemPrefab.GetBuyPricesUnder())
{
if (basePrice > ingredientItemLocationPrice.Item2.Price)
{
NewMessage(" Location " + ingredientItemLocationPrice.Item1 + " sells ingredient " + ingredientItemPrefab.Name + " for cheaper, " + ingredientItemLocationPrice.Item2.Price, Color.Yellow);
}
else
{
NewMessage(" Location " + ingredientItemLocationPrice.Item1 + " sells ingredient " + ingredientItemPrefab.Name + " for more, " + ingredientItemLocationPrice.Item2.Price, Color.Teal);
}
}
}
}
int costDifference = itemPrefab.DefaultPrice.Price - totalPrice;
NewMessage(" Constructing the item from store-bought items provides " + costDifference + " profit with default values.");
if (totalBestPrice.HasValue)
{
int? bestDifference = itemLocationPrice.Item2.Price - totalBestPrice;
NewMessage(" Constructing the item from store-bought items provides " + bestDifference + " profit with best-case scenario values.");
}
}
}
}, isCheat: false));
commands.Add(new Command("checkcraftingexploits", "checkcraftingexploits: Finds outright item exploits created by buying store-bought ingredients and constructing them into sellable items.", (string[] args) =>
{
List<FabricationRecipe> fabricableItems = new List<FabricationRecipe>();
foreach (ItemPrefab itemPrefab in ItemPrefab.Prefabs)
{
fabricableItems.AddRange(itemPrefab.FabricationRecipes);
}
List<Tuple<string, int>> costDifferences = new List<Tuple<string, int>>();
int maximumAllowedCost = 5;
if (args.Length > 0)
{
Int32.TryParse(args[0], out maximumAllowedCost);
}
foreach (ItemPrefab itemPrefab in ItemPrefab.Prefabs)
{
int? defaultCost = itemPrefab.DefaultPrice?.Price;
int? fabricationCostStore = null;
var fabricationRecipe = fabricableItems.Find(f => f.TargetItem == itemPrefab);
if (fabricationRecipe == null)
{
continue;
}
bool canBeBought = true;
foreach (var ingredient in fabricationRecipe.RequiredItems)
{
int? ingredientPrice = ingredient.ItemPrefabs.Where(p => p.CanBeBought).Min(ip => ip.DefaultPrice?.Price);
if (ingredientPrice.HasValue)
{
if (!fabricationCostStore.HasValue) { fabricationCostStore = 0; }
float useAmount = ingredient.UseCondition ? ingredient.MinCondition : 1.0f;
fabricationCostStore += (int)(ingredientPrice.Value * ingredient.Amount * useAmount);
}
else
{
canBeBought = false;
}
}
if (fabricationCostStore.HasValue && defaultCost.HasValue && canBeBought)
{
int costDifference = defaultCost.Value - fabricationCostStore.Value;
if (costDifference > maximumAllowedCost || costDifference < 0f)
{
float ratio = (float)fabricationCostStore.Value / defaultCost.Value;
string message = "Fabricating \"" + itemPrefab.Name + "\" costs " + (int)(ratio * 100) + "% of the price of the item, or " + costDifference + " more. Item price: " + defaultCost.Value + ", ingredient prices: " + fabricationCostStore.Value;
costDifferences.Add(new Tuple<string, int>(message, costDifference));
}
}
}
costDifferences.Sort((x, y) => x.Item2.CompareTo(y.Item2));
foreach (Tuple<string, int> costDifference in costDifferences)
{
Color color = Color.Yellow;
NewMessage(costDifference.Item1, color);
}
}, isCheat: false));
commands.Add(new Command("adjustprice", "adjustprice: Recursively prints out expected price adjustments for items derived from this item.", (string[] args) =>
{
List<FabricationRecipe> fabricableItems = new List<FabricationRecipe>();
foreach (ItemPrefab iP in ItemPrefab.Prefabs)
{
fabricableItems.AddRange(iP.FabricationRecipes);
}
if (args.Length < 2)
{
NewMessage("Item or value not defined.");
return;
}
string itemNameOrId = args[0].ToLowerInvariant();
ItemPrefab materialPrefab =
(MapEntityPrefab.Find(itemNameOrId, identifier: null, showErrorMessages: false) ??
MapEntityPrefab.Find(null, identifier: itemNameOrId, showErrorMessages: false)) as ItemPrefab;
if (materialPrefab == null)
{
NewMessage("Item not found for price adjustment.");
return;
}
AdjustItemTypes adjustItemType = AdjustItemTypes.NoAdjustment;
if (args.Length > 2)
{
switch (args[2].ToLowerInvariant())
{
case "add":
adjustItemType = AdjustItemTypes.Additive;
break;
case "mult":
adjustItemType = AdjustItemTypes.Multiplicative;
break;
}
}
if (Int32.TryParse(args[1].ToLowerInvariant(), out int newPrice))
{
Dictionary<ItemPrefab, int> newPrices = new Dictionary<ItemPrefab, int>();
PrintItemCosts(newPrices, materialPrefab, fabricableItems, newPrice, true, adjustItemType: adjustItemType);
PrintItemCosts(newPrices, materialPrefab, fabricableItems, newPrice, false, adjustItemType: adjustItemType);
}
}, isCheat: false));
commands.Add(new Command("deconstructvalue", "deconstructvalue: Views and compares deconstructed component prices for this item.", (string[] args) =>
{
List<FabricationRecipe> fabricableItems = new List<FabricationRecipe>();
foreach (ItemPrefab iP in ItemPrefab.Prefabs)
{
fabricableItems.AddRange(iP.FabricationRecipes);
}
if (args.Length < 1)
{
NewMessage("Item not defined.");
return;
}
string itemNameOrId = args[0].ToLowerInvariant();
ItemPrefab parentItem =
(MapEntityPrefab.Find(itemNameOrId, identifier: null, showErrorMessages: false) ??
MapEntityPrefab.Find(null, identifier: itemNameOrId, showErrorMessages: false)) as ItemPrefab;
if (parentItem == null)
{
NewMessage("Item not found for price adjustment.");
return;
}
var fabricationRecipe = fabricableItems.Find(f => f.TargetItem == parentItem);
int totalValue = 0;
NewMessage(parentItem.Name + " has the price " + parentItem.DefaultPrice.Price);
if (fabricationRecipe != null)
{
NewMessage(" It constructs from:");
foreach (RequiredItem requiredItem in fabricationRecipe.RequiredItems)
{
foreach (ItemPrefab itemPrefab in requiredItem.ItemPrefabs)
{
NewMessage(" " + itemPrefab.Name + " has the price " + itemPrefab.DefaultPrice.Price);
totalValue += itemPrefab.DefaultPrice.Price;
}
}
NewMessage("Its total value was: " + totalValue);
totalValue = 0;
}
NewMessage(" The item deconstructs into:");
foreach (DeconstructItem deconstructItem in parentItem.DeconstructItems)
{
ItemPrefab itemPrefab =
(MapEntityPrefab.Find(deconstructItem.ItemIdentifier, identifier: null, showErrorMessages: false) ??
MapEntityPrefab.Find(null, identifier: itemNameOrId, showErrorMessages: false)) as ItemPrefab;
NewMessage(" " + itemPrefab.Name + " has the price " + itemPrefab.DefaultPrice.Price);
totalValue += itemPrefab.DefaultPrice.Price;
}
NewMessage("Its deconstruct value was: " + totalValue);
}, isCheat: false));
commands.Add(new Command("setentityproperties", "setentityproperties [property name] [value]: Sets the value of some property on all selected items/structures in the sub editor.", (string[] args) =>
{
if (args.Length != 2 || Screen.Selected != GameMain.SubEditorScreen) { return; }
@@ -2935,5 +3167,155 @@ namespace Barotrauma
return false;
}
}
private enum AdjustItemTypes
{
NoAdjustment,
Additive,
Multiplicative
}
private static void PrintItemCosts(Dictionary<ItemPrefab, int> newPrices, ItemPrefab materialPrefab, List<FabricationRecipe> fabricableItems, int newPrice, bool adjustDown, string depth = "", AdjustItemTypes adjustItemType = AdjustItemTypes.NoAdjustment)
{
if (newPrice < 1)
{
NewMessage(depth + materialPrefab.Name + " cannot be adjusted to this price, because it would become less than 1.");
return;
}
depth += " ";
if (newPrice > 0)
{
newPrices.TryAdd(materialPrefab, newPrice);
}
int componentCost = 0;
int newComponentCost = 0;
var fabricationRecipe = fabricableItems.Find(f => f.TargetItem == materialPrefab);
if (fabricationRecipe != null)
{
foreach (RequiredItem requiredItem in fabricationRecipe.RequiredItems)
{
foreach (ItemPrefab itemPrefab in requiredItem.ItemPrefabs)
{
GetAdjustedPrice(itemPrefab, ref componentCost, ref newComponentCost, newPrices);
}
}
}
string componentCostMultiplier = "";
if (componentCost > 0)
{
componentCostMultiplier = $" (Relative difference to component cost {GetComponentCostDifference(materialPrefab.DefaultPrice.Price, componentCost)} => {GetComponentCostDifference(newPrice, newComponentCost)}, or flat profit {(int)(materialPrefab.DefaultPrice.Price - (int)componentCost)} => {newPrice - newComponentCost})";
}
string priceAdjustment = "";
if (newPrice != materialPrefab.DefaultPrice.Price)
{
priceAdjustment = ", Suggested price adjustment is " + materialPrefab.DefaultPrice.Price + " => " + newPrice;
}
NewMessage(depth + materialPrefab.Name + "(" + materialPrefab.DefaultPrice.Price + ") " + priceAdjustment + componentCostMultiplier);
if (adjustDown)
{
if (componentCost > 0)
{
double newPriceMult = (double)newPrice / (double)(materialPrefab.DefaultPrice.Price);
int newPriceDiff = componentCost + newPrice - materialPrefab.DefaultPrice.Price;
switch (adjustItemType)
{
case AdjustItemTypes.Additive:
NewMessage(depth + materialPrefab.Name + "'s components should be adjusted " + componentCost + " => " + newPriceDiff);
break;
case AdjustItemTypes.Multiplicative:
NewMessage(depth + materialPrefab.Name + "'s components should be adjusted " + componentCost + " => " + Math.Round(newPriceMult * componentCost));
break;
}
if (fabricationRecipe != null)
{
foreach (RequiredItem requiredItem in fabricationRecipe.RequiredItems)
{
foreach (ItemPrefab itemPrefab in requiredItem.ItemPrefabs)
{
if (itemPrefab.DefaultPrice != null)
{
switch (adjustItemType)
{
case AdjustItemTypes.NoAdjustment:
PrintItemCosts(newPrices, itemPrefab, fabricableItems, itemPrefab.DefaultPrice.Price, adjustDown, depth, adjustItemType);
break;
case AdjustItemTypes.Additive:
PrintItemCosts(newPrices, itemPrefab, fabricableItems, itemPrefab.DefaultPrice.Price + (int)((newPrice - materialPrefab.DefaultPrice.Price) / (double)fabricationRecipe.RequiredItems.Count), adjustDown, depth, adjustItemType);
break;
case AdjustItemTypes.Multiplicative:
PrintItemCosts(newPrices, itemPrefab, fabricableItems, (int)(itemPrefab.DefaultPrice.Price * newPriceMult), adjustDown, depth, adjustItemType);
break;
}
}
}
}
}
}
}
else
{
var fabricationRecipes = fabricableItems.Where(f => f.RequiredItems.Any(x => x.ItemPrefabs.Contains(materialPrefab)));
foreach (FabricationRecipe fabricationRecipeParent in fabricationRecipes)
{
if (fabricationRecipeParent.TargetItem.DefaultPrice != null)
{
int targetComponentCost = 0;
int newTargetComponentCost = 0;
foreach (RequiredItem requiredItem in fabricationRecipeParent.RequiredItems)
{
foreach (ItemPrefab itemPrefab in requiredItem.ItemPrefabs)
{
GetAdjustedPrice(itemPrefab, ref targetComponentCost, ref newTargetComponentCost, newPrices);
}
}
switch (adjustItemType)
{
case AdjustItemTypes.NoAdjustment:
PrintItemCosts(newPrices, fabricationRecipeParent.TargetItem, fabricableItems, fabricationRecipeParent.TargetItem.DefaultPrice.Price, adjustDown, depth, adjustItemType);
break;
case AdjustItemTypes.Additive:
PrintItemCosts(newPrices, fabricationRecipeParent.TargetItem, fabricableItems, fabricationRecipeParent.TargetItem.DefaultPrice.Price + newPrice - materialPrefab.DefaultPrice.Price, adjustDown, depth, adjustItemType);
break;
case AdjustItemTypes.Multiplicative:
double maintainedMultiplier = GetComponentCostDifference(fabricationRecipeParent.TargetItem.DefaultPrice.Price, targetComponentCost);
PrintItemCosts(newPrices, fabricationRecipeParent.TargetItem, fabricableItems, (int)(newTargetComponentCost * maintainedMultiplier), adjustDown, depth, adjustItemType);
break;
}
}
}
}
}
private static double GetComponentCostDifference(int itemCost, int componentCost)
{
return Math.Round((double)(itemCost / (double)componentCost), 2);
}
private static void GetAdjustedPrice(ItemPrefab itemPrefab, ref int componentCost, ref int newComponentCost, Dictionary<ItemPrefab, int> newPrices)
{
if (newPrices.TryGetValue(itemPrefab, out int newPrice))
{
newComponentCost += newPrice;
}
else if (itemPrefab.DefaultPrice != null)
{
newComponentCost += itemPrefab.DefaultPrice.Price;
}
if (itemPrefab.DefaultPrice != null)
{
componentCost += itemPrefab.DefaultPrice.Price;
}
}
}
}
@@ -10,6 +10,8 @@ namespace Barotrauma
public void Draw(SpriteBatch spriteBatch, Hull hull, float depth)
{
if (Sprite.Texture == null) { return; }
Vector2 drawPos = position + hull.Rect.Location.ToVector2();
if (hull.Submarine != null) { drawPos += hull.Submarine.DrawPosition; }
drawPos.Y = -drawPos.Y;
@@ -52,6 +52,7 @@ namespace Barotrauma
GUI.DrawString(spriteBatch, new Vector2(15, y + 95), "FloodingAmount: " + (int)Math.Round(floodingAmount * 100), Color.Lerp(GUI.Style.Green, GUI.Style.Red, floodingAmount), Color.Black * 0.6f, 0, GUI.SmallFont);
GUI.DrawString(spriteBatch, new Vector2(15, y + 110), "FireAmount: " + (int)Math.Round(fireAmount * 100), Color.Lerp(GUI.Style.Green, GUI.Style.Red, fireAmount), Color.Black * 0.6f, 0, GUI.SmallFont);
GUI.DrawString(spriteBatch, new Vector2(15, y + 125), "EnemyDanger: " + (int)Math.Round(enemyDanger * 100), Color.Lerp(GUI.Style.Green, GUI.Style.Red, enemyDanger), Color.Black * 0.6f, 0, GUI.SmallFont);
GUI.DrawString(spriteBatch, new Vector2(15, y + 140), "MonsterTotalStrength: " + (int)Math.Round(monsterTotalStrength), Color.Lerp(GUI.Style.Green, GUI.Style.Red, monsterTotalStrength / 5000f), Color.Black * 0.6f, 0, GUI.SmallFont);
#if DEBUG
if (PlayerInput.KeyDown(Microsoft.Xna.Framework.Input.Keys.LeftAlt) &&
@@ -75,7 +76,7 @@ namespace Barotrauma
lastIntensityUpdate = (float) Timing.TotalTime;
}
Rectangle graphRect = new Rectangle(15, y + 150, 150, 50);
Rectangle graphRect = new Rectangle(15, y + 165, 150, 50);
GUI.DrawRectangle(spriteBatch, graphRect, Color.Black * 0.5f, true);
intensityGraph.Draw(spriteBatch, graphRect, 1.0f, 0.0f, Color.Lerp(Color.White, GUI.Style.Red, currentIntensity));
@@ -1,9 +1,28 @@
using Barotrauma.Networking;
using System.Globalization;
namespace Barotrauma
{
partial class CargoMission : Mission
{
public override string GetMissionRewardText(Submarine sub)
{
string rewardText = TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", GetReward(sub)));
if (rewardPerCrate.HasValue)
{
string rewardPerCrateText = TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", rewardPerCrate.Value));
return TextManager.GetWithVariables("missionrewardcargopercrate",
new string[] { "[rewardpercrate]", "[itemcount]", "[maxitemcount]", "[totalreward]" },
new string[] { rewardPerCrateText, itemsToSpawn.Count.ToString(), maxItemCount.ToString(), $"‖color:gui.orange‖{rewardText}‖end‖" });
}
else
{
return TextManager.GetWithVariables("missionrewardcargo",
new string[] { "[totalreward]", "[itemcount]", "[maxitemcount]" },
new string[] { $"‖color:gui.orange‖{rewardText}‖end‖", itemsToSpawn.Count.ToString(), maxItemCount.ToString() });
}
}
public override void ClientReadInitial(IReadMessage msg)
{
items.Clear();
@@ -0,0 +1,32 @@
using Barotrauma.Networking;
namespace Barotrauma
{
partial class EscortMission : Mission
{
public override void ClientReadInitial(IReadMessage msg)
{
byte characterCount = msg.ReadByte();
for (int i = 0; i < characterCount; i++)
{
characters.Add(Character.ReadSpawnData(msg));
ushort itemCount = msg.ReadUInt16();
for (int j = 0; j < itemCount; j++)
{
Item.ReadSpawnData(msg);
}
}
if (characters.Contains(null))
{
throw new System.Exception("Error in EscortMission.ClientReadInitial: character list contains null (mission: " + Prefab.Identifier + ")");
}
if (characters.Count != characterCount)
{
throw new System.Exception("Error in EscortMission.ClientReadInitial: character count does not match the server count (" + characterCount + " != " + characters.Count + "mission: " + Prefab.Identifier + ")");
}
InitCharacters();
}
}
}
@@ -21,9 +21,9 @@ namespace Barotrauma
return ToolBox.GradientLerp(t, GUI.Style.Green, GUI.Style.Orange, GUI.Style.Red);
}
public string GetMissionRewardText()
public virtual string GetMissionRewardText(Submarine sub)
{
string rewardText = TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", Reward));
string rewardText = TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", GetReward(sub)));
return TextManager.GetWithVariable("missionreward", "[reward]", $"‖color:gui.orange‖{rewardText}‖end‖");
}
@@ -0,0 +1,33 @@
using Barotrauma.Networking;
namespace Barotrauma
{
partial class PirateMission : Mission
{
public override void ClientReadInitial(IReadMessage msg)
{
// duplicate code from escortmission, should possibly be combined, though additional loot items might be added so maybe not
byte characterCount = msg.ReadByte();
for (int i = 0; i < characterCount; i++)
{
characters.Add(Character.ReadSpawnData(msg));
ushort itemCount = msg.ReadUInt16();
for (int j = 0; j < itemCount; j++)
{
Item.ReadSpawnData(msg);
}
}
if (characters.Contains(null))
{
throw new System.Exception("Error in PirateMission.ClientReadInitial: character list contains null (mission: " + Prefab.Identifier + ")");
}
if (characters.Count != characterCount)
{
throw new System.Exception("Error in PirateMission.ClientReadInitial: character count does not match the server count (" + characterCount + " != " + characters.Count + "mission: " + Prefab.Identifier + ")");
}
}
}
}
@@ -1331,7 +1331,7 @@ namespace Barotrauma
/// <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>
public static void DrawIndicator(SpriteBatch spriteBatch, Vector2 worldPosition, Camera cam, float hideDist, Sprite sprite, Color color,
public static void DrawIndicator(SpriteBatch spriteBatch, in Vector2 worldPosition, Camera cam, in Vector2 visibleRange, Sprite sprite, in Color color,
bool createOffset = true, float scaleMultiplier = 1.0f, float? overrideAlpha = null)
{
Vector2 diff = worldPosition - cam.WorldViewCenter;
@@ -1339,9 +1339,9 @@ namespace Barotrauma
float symbolScale = Math.Min(64.0f / sprite.size.X, 1.0f) * scaleMultiplier * Scale;
if (overrideAlpha.HasValue || dist > hideDist)
if (overrideAlpha.HasValue || (dist > visibleRange.X && dist < visibleRange.Y))
{
float alpha = overrideAlpha ?? Math.Min((dist - hideDist) / 100.0f, 1.0f);
float alpha = overrideAlpha ?? MathUtils.Min((dist - visibleRange.X) / 100.0f, 1.0f - ((dist - visibleRange.Y + 100f) / 100.0f), 1.0f);
Vector2 targetScreenPos = cam.WorldToScreen(worldPosition);
if (!createOffset)
@@ -1395,6 +1395,12 @@ namespace Barotrauma
}
}
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)
{
DrawIndicator(spriteBatch, worldPosition, cam, new Vector2(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)
{
DrawLine(sb, t, start, end, clr, depth, (int)width);
@@ -1495,6 +1501,22 @@ namespace Barotrauma
}
}
public static void DrawFilledRectangle(SpriteBatch sb, Vector2 start, Vector2 size, Color clr, float depth = 0.0f)
{
if (size.X < 0)
{
start.X += size.X;
size.X = -size.X;
}
if (size.Y < 0)
{
start.Y += size.Y;
size.Y = -size.Y;
}
sb.Draw(t, start, null, clr, 0f, Vector2.Zero, size, SpriteEffects.None, depth);
}
public static void DrawRectangle(SpriteBatch sb, Vector2 center, float width, float height, float rotation, Color clr, float depth = 0.0f, float thickness = 1)
{
Matrix rotate = Matrix.CreateRotationZ(rotation);
@@ -1971,6 +1993,30 @@ namespace Barotrauma
}
return frame;
}
public static GUIMessageBox AskForConfirmation(string header, string body, Action onConfirm, Action onDeny = null)
{
string[] buttons = { TextManager.Get("Ok"), TextManager.Get("Cancel") };
GUIMessageBox msgBox = new GUIMessageBox(header, body, buttons, new Vector2(0.2f, 0.175f), minSize: new Point(300, 175));
// Cancel button
msgBox.Buttons[1].OnClicked = delegate
{
onDeny?.Invoke();
msgBox.Close();
return true;
};
// Ok button
msgBox.Buttons[0].OnClicked = delegate
{
onConfirm.Invoke();
msgBox.Close();
return true;
};
return msgBox;
}
#endregion
#region Element positioning
@@ -2221,6 +2267,7 @@ namespace Barotrauma
}
};
bool IsOutpostLevel() => GameMain.GameSession != null && Level.IsLoadedOutpost;
if (Screen.Selected == GameMain.GameScreen && GameMain.GameSession != null)
{
if (GameMain.GameSession.GameMode is SinglePlayerCampaign spMode)
@@ -2253,45 +2300,22 @@ namespace Barotrauma
};
return true;
};
var saveAndQuitButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonContainer.RectTransform), TextManager.Get("PauseMenuSaveQuit"))
if (IsOutpostLevel())
{
UserData = "save"
};
saveAndQuitButton.OnClicked += (btn, userdata) =>
{
//Only allow saving mid-round in outpost levels. Quitting in the middle of a mission reset progress to the start of the round.
if (GameMain.GameSession == null)
var saveAndQuitButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonContainer.RectTransform), TextManager.Get("PauseMenuSaveQuit"))
{
pauseMenuOpen = false;
}
else if (GameMain.GameSession?.Campaign == null || Level.IsLoadedOutpost)
{
pauseMenuOpen = false;
GameMain.QuitToMainMenu(save: true);
}
else
{
var msgBox = new GUIMessageBox("", TextManager.Get("PauseMenuSaveAndQuitVerification", fallBackTag: "pausemenuquitverification"), new string[] { TextManager.Get("Yes"), TextManager.Get("Cancel") })
{
UserData = "verificationprompt"
};
msgBox.Buttons[0].OnClicked = (_, userdata) =>
UserData = "save",
OnClicked = (btn, userData) =>
{
pauseMenuOpen = false;
GameMain.QuitToMainMenu(save: false);
if (IsOutpostLevel())
{
GameMain.QuitToMainMenu(save: true);
}
return true;
};
msgBox.Buttons[0].OnClicked += msgBox.Close;
msgBox.Buttons[1].OnClicked = (_, userdata) =>
{
pauseMenuOpen = false;
msgBox.Close();
return true;
};
}
return true;
};
}
};
}
}
else if (GameMain.GameSession.GameMode is TestGameMode)
{
@@ -2313,7 +2337,7 @@ namespace Barotrauma
OnClicked = (btn, userdata) =>
{
if (!GameMain.Client.HasPermission(ClientPermissions.ManageRound)) { return false; }
if (GameMain.GameSession.GameMode is CampaignMode || (!Submarine.MainSub.AtStartExit && !Submarine.MainSub.AtEndExit))
if (GameMain.GameSession.GameMode is CampaignMode && !IsOutpostLevel() || (!Submarine.MainSub.AtStartExit && !Submarine.MainSub.AtEndExit))
{
var msgBox = new GUIMessageBox("",
TextManager.Get(GameMain.GameSession.GameMode is CampaignMode ? "PauseMenuReturnToServerLobbyVerification" : "EndRoundSubNotAtLevelEnd"),
@@ -140,6 +140,7 @@ namespace Barotrauma
{
if (value == intValue) { return; }
intValue = value;
ClampIntValue();
UpdateText();
}
}
@@ -49,8 +49,11 @@ namespace Barotrauma
get { return _caretIndex; }
set
{
_caretIndex = value;
caretPosDirty = true;
if (value >= 0)
{
_caretIndex = value;
caretPosDirty = true;
}
}
}
private bool caretPosDirty;
@@ -454,7 +457,7 @@ namespace Barotrauma
}
if (!isSelecting)
{
isSelecting = PlayerInput.KeyDown(Keys.LeftShift) || PlayerInput.KeyDown(Keys.RightShift);
isSelecting = PlayerInput.IsShiftDown();
}
if (mouseHeldInside && !PlayerInput.PrimaryMouseButtonHeld())
@@ -879,15 +882,22 @@ namespace Barotrauma
selectionEndIndex = Math.Min(CaretIndex, textDrawn.Length);
selectionEndPos = caretPos;
selectedCharacters = Math.Abs(selectionStartIndex - selectionEndIndex);
if (IsLeftToRight)
try
{
selectedText = Text.Substring(selectionStartIndex, selectedCharacters);
selectionRectSize = Font.MeasureString(textDrawn.Substring(selectionStartIndex, selectedCharacters)) * TextBlock.TextScale;
if (IsLeftToRight)
{
selectedText = Text.Substring(selectionStartIndex, Math.Min(selectedCharacters, Text.Length));
selectionRectSize = Font.MeasureString(textDrawn.Substring(selectionStartIndex, Math.Min(selectedCharacters, textDrawn.Length))) * TextBlock.TextScale;
}
else
{
selectedText = Text.Substring(selectionEndIndex, Math.Min(selectedCharacters, Text.Length));
selectionRectSize = Font.MeasureString(textDrawn.Substring(selectionEndIndex, Math.Min(selectedCharacters, textDrawn.Length))) * TextBlock.TextScale;
}
}
else
catch (ArgumentOutOfRangeException exception)
{
selectedText = Text.Substring(selectionEndIndex, Math.Min(selectedCharacters, textDrawn.Length - selectionEndIndex));
selectionRectSize = Font.MeasureString(textDrawn.Substring(selectionEndIndex, selectedCharacters)) * TextBlock.TextScale;
DebugConsole.ThrowError($"GUITextBox: Invalid selection: ({exception})");
}
}
}
@@ -947,7 +947,7 @@ namespace Barotrauma
{
descriptionText += "\n\n" + missionMessage;
}
string rewardText = mission.GetMissionRewardText();
string rewardText = mission.GetMissionRewardText(Submarine.MainSub);
string reputationText = mission.GetReputationRewardText(mission.Locations[0]);
var missionNameRichTextData = RichTextData.GetRichTextData(mission.Name, out string missionNameString);
@@ -1,4 +1,6 @@
using System;
#nullable enable
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
@@ -7,8 +9,10 @@ using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using FarseerPhysics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using SpriteBatch = Microsoft.Xna.Framework.Graphics.SpriteBatch;
// ReSharper disable UnusedVariable
namespace Barotrauma
{
@@ -18,8 +22,8 @@ namespace Barotrauma
public readonly struct CategoryData
{
public readonly UpgradeCategory Category;
public readonly List<UpgradePrefab> Prefabs;
public readonly UpgradePrefab SinglePrefab;
public readonly List<UpgradePrefab>? Prefabs;
public readonly UpgradePrefab? SinglePrefab;
public CategoryData(UpgradeCategory category, List<UpgradePrefab> prefabs)
{
@@ -37,30 +41,34 @@ namespace Barotrauma
}
private readonly CampaignUI campaignUI;
private CampaignMode Campaign => campaignUI?.Campaign;
private CampaignMode? Campaign => campaignUI.Campaign;
private int AvailableMoney => Campaign?.Money ?? 0;
private UpgradeTab selectedUpgradTab = UpgradeTab.Upgrade;
private GUIMessageBox currectConfirmation;
private GUIMessageBox? currectConfirmation;
public readonly GUIFrame ItemInfoFrame;
private GUIComponent selectedUpgradeCategoryLayout;
private GUILayoutGroup topHeaderLayout;
private GUILayoutGroup mainStoreLayout;
private GUILayoutGroup storeLayout;
private GUILayoutGroup categoryButtonLayout;
private GUILayoutGroup submarineInfoFrame;
private GUIListBox currentStoreLayout;
private GUICustomComponent submarinePreviewComponent;
private GUIFrame subPreviewFrame;
private Submarine drawnSubmarine;
private GUIComponent? selectedUpgradeCategoryLayout;
private GUILayoutGroup? topHeaderLayout;
private GUILayoutGroup? mainStoreLayout;
private GUILayoutGroup? storeLayout;
private GUILayoutGroup? categoryButtonLayout;
private GUILayoutGroup? submarineInfoFrame;
private GUIListBox? currentStoreLayout;
private GUICustomComponent? submarinePreviewComponent;
private GUIFrame? subPreviewFrame;
private Submarine? drawnSubmarine;
private readonly List<UpgradeCategory> applicableCategories = new List<UpgradeCategory>();
private Vector2[][] subHullVerticies = new Vector2[0][];
private List<Structure> submarineWalls = new List<Structure>();
public MapEntity HoveredItem;
public MapEntity? HoveredItem;
private bool highlightWalls;
private readonly Dictionary<Item, GUIFrame> itemPreviews = new Dictionary<Item, GUIFrame>();
private UpgradeCategory? currentUpgradeCategory;
private GUIButton? activeItemSwapSlideDown;
private readonly Dictionary<Item, GUIComponent> itemPreviews = new Dictionary<Item, GUIComponent>();
private static readonly Color previewWhite = Color.White * 0.5f;
@@ -94,6 +102,7 @@ namespace Barotrauma
CreateUI(upgradeFrame);
if (Campaign == null) { return; }
Campaign.UpgradeManager.OnUpgradesChanged += RefreshAll;
Campaign.CargoManager.OnPurchasedItemsChanged += RefreshAll;
Campaign.CargoManager.OnSoldItemsChanged += RefreshAll;
@@ -103,29 +112,41 @@ namespace Barotrauma
{
switch (selectedUpgradTab)
{
case UpgradeTab.Repairs:
{
case UpgradeTab.Repairs:
SelectTab(UpgradeTab.Repairs);
break;
}
case UpgradeTab.Upgrade:
{
break;
case UpgradeTab.Upgrade:
RefreshUpgradeList();
break;
}
foreach (var itemPreview in itemPreviews)
{
if (itemPreview.Key?.PendingItemSwap?.UpgradePreviewSprite == null) { continue; }
if (!(itemPreview.Value is GUIImage image)) { continue; }
image.Sprite = itemPreview.Key.PendingItemSwap.UpgradePreviewSprite;
}
break;
}
}
private void RefreshUpgradeList()
{
if (Campaign == null) { return; }
// Updates the progress bar / text and disables the buy button if we reached max level
if (selectedUpgradeCategoryLayout?.Parent != null && selectedUpgradeCategoryLayout.FindChild("prefablist", true) is GUIListBox listBox)
{
foreach (var component in listBox.Content.Children)
{
if (component.UserData is CategoryData data)
if (component.UserData is CategoryData { SinglePrefab: { } prefab} data)
{
UpdateUpgradeEntry(component, data.SinglePrefab, data.Category, Campaign);
UpdateUpgradeEntry(component, prefab, data.Category, Campaign);
}
}
if (customizeTabOpen && selectedUpgradeCategoryLayout != null && Submarine.MainSub != null && currentUpgradeCategory != null)
{
CreateSwappableItemList(listBox, currentUpgradeCategory, Submarine.MainSub);
if (activeItemSwapSlideDown?.UserData is Item prevOpenedItem)
{
var currentButton = listBox.FindChild(c => c.UserData as Item == prevOpenedItem, recursive: true) as GUIButton;
currentButton?.OnClicked(currentButton, prevOpenedItem);
}
}
}
@@ -135,16 +156,18 @@ namespace Barotrauma
{
UpdateCategoryList(currentStoreLayout, Campaign, drawnSubmarine, applicableCategories);
}
}
//TODO: move this somewhere else
public static void UpdateCategoryList(GUIListBox categoryList, CampaignMode campaign, Submarine drawnSubmarine, IEnumerable<UpgradeCategory> applicableCategories)
public static void UpdateCategoryList(GUIListBox categoryList, CampaignMode campaign, Submarine? drawnSubmarine, IEnumerable<UpgradeCategory> applicableCategories)
{
foreach (GUIComponent component in categoryList.Content.Children)
{
if (!(component.UserData is CategoryData data)) { continue; }
if (component.FindChild("indicators", true) is { } indicators)
if (component.FindChild("indicators", true) is { } indicators && data.Prefabs != null)
{
// ReSharper disable once PossibleMultipleEnumeration
UpdateCategoryIndicators(indicators, component, data.Prefabs, data.Category, campaign, drawnSubmarine, applicableCategories);
}
}
@@ -252,7 +275,7 @@ namespace Barotrauma
GUILayoutGroup rightLayout = new GUILayoutGroup(rectT(0.5f, 1, topHeaderLayout), childAnchor: Anchor.TopRight);
GUILayoutGroup priceLayout = new GUILayoutGroup(rectT(1, 0.8f, rightLayout), childAnchor: Anchor.Center) { RelativeSpacing = 0.08f };
new GUITextBlock(rectT(1f, 0f, priceLayout), TextManager.Get("CampaignStore.Balance"), font: GUI.SubHeadingFont, textAlignment: Alignment.Right);
new GUITextBlock(rectT(1f, 0f, priceLayout), FormatCurrency(Campaign.Money, format: true), font: GUI.SubHeadingFont, textAlignment: Alignment.Right) { TextGetter = () => FormatCurrency(Campaign.Money, format: true) };
new GUITextBlock(rectT(1f, 0f, priceLayout), FormatCurrency(AvailableMoney, format: true), font: GUI.SubHeadingFont, textAlignment: Alignment.Right) { TextGetter = () => FormatCurrency(AvailableMoney, format: true) };
new GUIFrame(rectT(0.5f, 0.1f, rightLayout, Anchor.BottomRight), style: "HorizontalLine") { IgnoreLayoutGroups = true };
repairButton.OnClicked = upgradeButton.OnClicked = (button, o) =>
@@ -304,12 +327,12 @@ namespace Barotrauma
{
if (currentStoreLayout != null)
{
storeLayout.RemoveChild(currentStoreLayout);
storeLayout?.RemoveChild(currentStoreLayout);
}
if (selectedUpgradeCategoryLayout != null)
{
mainStoreLayout.RemoveChild(selectedUpgradeCategoryLayout);
mainStoreLayout?.RemoveChild(selectedUpgradeCategoryLayout);
}
switch (tab)
@@ -329,8 +352,10 @@ namespace Barotrauma
private void CreateRepairsTab()
{
if (Campaign == null || storeLayout == null) { return; }
highlightWalls = false;
foreach (GUIFrame itemFrame in itemPreviews.Values)
foreach (GUIComponent itemFrame in itemPreviews.Values)
{
itemFrame.OutlineColor = previewWhite;
}
@@ -355,12 +380,12 @@ namespace Barotrauma
return false;
}
if (Campaign.Money >= hullRepairCost)
if (AvailableMoney >= hullRepairCost)
{
string body = TextManager.GetWithVariable("WallRepairs.PurchasePromptBody", "[amount]", hullRepairCost.ToString());
currectConfirmation = EventEditorScreen.AskForConfirmation(TextManager.Get("Upgrades.PurchasePromptTitle"), body, () =>
{
if (Campaign.Money >= hullRepairCost)
if (AvailableMoney >= hullRepairCost)
{
Campaign.Money -= hullRepairCost;
Campaign.PurchasedHullRepairs = true;
@@ -389,12 +414,12 @@ namespace Barotrauma
CreateRepairEntry(currentStoreLayout.Content, TextManager.Get("repairallitems"), "RepairItemsButton", itemRepairCost, (button, o) =>
{
if (Campaign.Money >= itemRepairCost && !Campaign.PurchasedItemRepairs)
if (AvailableMoney >= itemRepairCost && !Campaign.PurchasedItemRepairs)
{
string body = TextManager.GetWithVariable("ItemRepairs.PurchasePromptBody", "[amount]", itemRepairCost.ToString());
currectConfirmation = EventEditorScreen.AskForConfirmation(TextManager.Get("Upgrades.PurchasePromptTitle"), body, () =>
{
if (Campaign.Money >= itemRepairCost && !Campaign.PurchasedItemRepairs)
if (AvailableMoney >= itemRepairCost && !Campaign.PurchasedItemRepairs)
{
Campaign.Money -= itemRepairCost;
Campaign.PurchasedItemRepairs = true;
@@ -434,12 +459,12 @@ namespace Barotrauma
return false;
}
if (Campaign.Money >= shuttleRetrieveCost && !Campaign.PurchasedLostShuttles)
if (AvailableMoney >= shuttleRetrieveCost && !Campaign.PurchasedLostShuttles)
{
string body = TextManager.GetWithVariable("ReplaceLostShuttles.PurchasePromptBody", "[amount]", shuttleRetrieveCost.ToString());
currectConfirmation = EventEditorScreen.AskForConfirmation(TextManager.Get("Upgrades.PurchasePromptTitle"), body, () =>
{
if (Campaign.Money >= shuttleRetrieveCost && !Campaign.PurchasedLostShuttles)
if (AvailableMoney >= shuttleRetrieveCost && !Campaign.PurchasedLostShuttles)
{
Campaign.Money -= shuttleRetrieveCost;
Campaign.PurchasedLostShuttles = true;
@@ -460,12 +485,13 @@ namespace Barotrauma
}, Campaign.PurchasedLostShuttles || !HasPermission || GameMain.GameSession?.SubmarineInfo == null || !GameMain.GameSession.SubmarineInfo.SubsLeftBehind, isHovered =>
{
if (!isHovered) { return false; }
if (!(GameMain.GameSession?.SubmarineInfo is { } subInfo)) { return false; }
foreach (var (item, itemFrame) in itemPreviews)
{
if (GameMain.GameSession.SubmarineInfo.LeftBehindDockingPortIDs.Contains(item.ID))
if (subInfo.LeftBehindDockingPortIDs.Contains(item.ID))
{
itemFrame.OutlineColor = itemFrame.Color = GameMain.GameSession.SubmarineInfo.BlockedDockingPortIDs.Contains(item.ID) ? GUI.Style.Red : GUI.Style.Green;
itemFrame.OutlineColor = itemFrame.Color = subInfo.BlockedDockingPortIDs.Contains(item.ID) ? GUI.Style.Red : GUI.Style.Green;
}
else
{
@@ -476,7 +502,7 @@ namespace Barotrauma
}, disableElement: true);
}
private void CreateRepairEntry(GUIComponent parent, string title, string imageStyle, int price, GUIButton.OnClickedHandler onPressed, bool isDisabled, Func<bool, bool> onHover = null, bool disableElement = false)
private void CreateRepairEntry(GUIComponent parent, string title, string imageStyle, int price, GUIButton.OnClickedHandler onPressed, bool isDisabled, Func<bool, bool>? onHover = null, bool disableElement = false)
{
GUIFrame frameChild = new GUIFrame(rectT(new Point(parent.Rect.Width, (int) (96 * GUI.Scale)), parent), style: "UpgradeUIFrame");
frameChild.SelectedColor = frameChild.Color;
@@ -497,13 +523,13 @@ namespace Barotrauma
new GUITextBlock(rectT(1, 0, textLayout), title, font: GUI.SubHeadingFont) { CanBeFocused = false, AutoScaleHorizontal = true };
new GUITextBlock(rectT(1, 0, textLayout), FormatCurrency(price));
GUILayoutGroup buyButtonLayout = new GUILayoutGroup(rectT(0.2f, 1, contentLayout), childAnchor: Anchor.Center) { UserData = "buybutton" };
new GUIButton(rectT(0.7f, 0.5f, buyButtonLayout), string.Empty, style: "RepairBuyButton") { ClickSound = GUISoundType.HireRepairClick, Enabled = Campaign.Money >= price && !isDisabled, OnClicked = onPressed };
new GUIButton(rectT(0.7f, 0.5f, buyButtonLayout), string.Empty, style: "RepairBuyButton") { ClickSound = GUISoundType.HireRepairClick, Enabled = AvailableMoney >= price && !isDisabled, OnClicked = onPressed };
contentLayout.Recalculate();
buyButtonLayout.Recalculate();
if (disableElement)
{
frameChild.Enabled = Campaign.Money >= price && !isDisabled;
frameChild.Enabled = AvailableMoney >= price && !isDisabled;
}
if (!HasPermission)
@@ -594,6 +620,7 @@ namespace Barotrauma
private void CreateUpgradeTab()
{
if (storeLayout == null || mainStoreLayout == null) { return; }
currentStoreLayout = CreateUpgradeCategoryList(rectT(1.0f, 1.5f, storeLayout));
selectedUpgradeCategoryLayout = new GUIFrame(rectT(GUI.IsFourByThree() ? 0.3f : 0.25f, 1, mainStoreLayout), style: null) { CanBeFocused = false };
@@ -605,16 +632,16 @@ namespace Barotrauma
if (!component.Enabled)
{
selectedUpgradeCategoryLayout?.ClearChildren();
foreach (GUIFrame itemFrame in itemPreviews.Values)
foreach (GUIComponent itemFrame in itemPreviews.Values)
{
itemFrame.OutlineColor = itemFrame.Color = previewWhite;
}
return true;
}
if (userData is CategoryData categoryData && Submarine.MainSub != null)
if (userData is CategoryData categoryData && Submarine.MainSub is { } sub && categoryData.Prefabs is { } prefabs)
{
TrySelectCategory(categoryData.Prefabs, categoryData.Category, Submarine.MainSub);
TrySelectCategory(prefabs, categoryData.Category, sub);
}
return true;
@@ -624,35 +651,296 @@ namespace Barotrauma
// This was supposed to have some logic for fancy animations to slide the previous tab out but maybe another time
private void TrySelectCategory(List<UpgradePrefab> prefabs, UpgradeCategory category, Submarine submarine) => SelectUpgradeCategory(prefabs, category, submarine);
private bool customizeTabOpen;
private void SelectUpgradeCategory(List<UpgradePrefab> prefabs, UpgradeCategory category, Submarine submarine)
{
if (selectedUpgradeCategoryLayout == null || submarine == null) { return; }
if (selectedUpgradeCategoryLayout == null) { return; }
GUIFrame[] categoryFrames = GetFrames(category);
foreach (GUIFrame itemFrame in itemPreviews.Values)
customizeTabOpen = false;
GUIComponent[] categoryFrames = GetFrames(category);
foreach (GUIComponent itemFrame in itemPreviews.Values)
{
itemFrame.OutlineColor = itemFrame.Color = categoryFrames.Contains(itemFrame) ? GUI.Style.Orange : previewWhite;
}
highlightWalls = category.IsWallUpgrade;
selectedUpgradeCategoryLayout?.ClearChildren();
GUIFrame frame = new GUIFrame(rectT(1, 0.4f, selectedUpgradeCategoryLayout));
GUIListBox prefabList = new GUIListBox(rectT(0.93f, 0.9f, frame, Anchor.Center)) { UserData = "prefablist" };
selectedUpgradeCategoryLayout.ClearChildren();
GUIFrame frame = new GUIFrame(rectT(1.0f, 0.4f, selectedUpgradeCategoryLayout));
GUIFrame paddedFrame = new GUIFrame(rectT(0.93f, 0.9f, frame, Anchor.Center), style: null);
List<Item> entitiesOnSub = null;
bool hasSwappableItems = Submarine.MainSub.GetItems(true).Any(i =>
i.Prefab.SwappableItem != null && (i.Prefab.SwappableItem.CanBeBought || ItemPrefab.Prefabs.Any(ip => ip.SwappableItem?.ReplacementOnUninstall == i.Prefab.Identifier)) &&
Submarine.MainSub.IsEntityFoundOnThisSub(i, true) && category.ItemTags.Any(t => i.HasTag(t)));
float listHeight = hasSwappableItems ? 0.9f : 1.0f;
GUIListBox prefabList = new GUIListBox(rectT(1.0f, listHeight, paddedFrame, Anchor.BottomLeft))
{
UserData = "prefablist",
AutoHideScrollBar = false,
ScrollBarVisible = true
};
if (hasSwappableItems)
{
GUILayoutGroup buttonLayout = new GUILayoutGroup(rectT(1.0f, 0.1f, paddedFrame, anchor: Anchor.TopLeft), isHorizontal: true);
GUIButton upgradeButton = new GUIButton(rectT(0.5f, 1f, buttonLayout), text: TextManager.Get("uicategory.upgrades"), style: "GUITabButton")
{
Selected = true
};
GUIButton customizeButton = new GUIButton(rectT(0.5f, 1f, buttonLayout), text: TextManager.Get("uicategory.customize"), style: "GUITabButton");
upgradeButton.OnClicked = delegate
{
customizeTabOpen = false;
customizeButton.Selected = false;
upgradeButton.Selected = true;
CreateUpgradePrefabList(prefabList, category, prefabs, submarine);
GUIComponent[] categoryFrames = GetFrames(category);
foreach (GUIComponent itemFrame in itemPreviews.Values)
{
itemFrame.OutlineColor = itemFrame.Color = categoryFrames.Contains(itemFrame) ? GUI.Style.Orange : previewWhite;
}
return true;
};
customizeButton.OnClicked = delegate
{
customizeTabOpen = true;
customizeButton.Selected = true;
upgradeButton.Selected = false;
CreateSwappableItemList(prefabList, category, submarine);
return true;
};
}
CreateUpgradePrefabList(prefabList, category, prefabs, submarine);
}
private void CreateUpgradePrefabList(GUIListBox parent, UpgradeCategory category, List<UpgradePrefab> prefabs, Submarine submarine)
{
parent.Content.ClearChildren();
List<Item>? entitiesOnSub = null;
if (!category.IsWallUpgrade)
{
entitiesOnSub = submarine.GetItems(true).Where(i => submarine.IsEntityFoundOnThisSub(i, true)).ToList();
}
foreach (UpgradePrefab prefab in prefabs)
{
CreateUpgradeEntry(prefab, category, prefabList.Content, entitiesOnSub);
CreateUpgradeEntry(prefab, category, parent.Content, entitiesOnSub);
}
}
private void CreateSwappableItemList(GUIListBox parent, UpgradeCategory category, Submarine submarine)
{
parent.Content.ClearChildren();
currentUpgradeCategory = category;
IEnumerable<ItemPrefab> availableReplacements = MapEntityPrefab.List.Where(p =>
p is ItemPrefab itemPrefab &&
category.ItemTags.Any(t => itemPrefab.Tags.Contains(t)) &&
(itemPrefab.SwappableItem?.CanBeBought ?? false)).Cast<ItemPrefab>();
var entitiesOnSub = submarine.GetItems(true).Where(i => submarine.IsEntityFoundOnThisSub(i, true) && category.ItemTags.Any(t => i.HasTag(t))).ToList();
int slotIndex = 0;
foreach (Item item in entitiesOnSub)
{
slotIndex++;
CreateSwappableItemSlideDown(parent, slotIndex, item, availableReplacements);
}
}
private void CreateSwappableItemSlideDown(GUIListBox parent, int slotIndex, Item item, IEnumerable<ItemPrefab> availableReplacements)
{
if (Campaign == null) { return; }
var currentOrPending = item.PendingItemSwap ?? item.Prefab;
bool isOpen = false;
GUIButton toggleButton = new GUIButton(rectT(1f, 0.1f, parent.Content), text: string.Empty, style: "SlideDown")
{
UserData = item
};
GUILayoutGroup buttonLayout = new GUILayoutGroup(rectT(1f, 1f, toggleButton.Frame), isHorizontal: true);
new GUITextBlock(rectT(0.3f, 1f, buttonLayout), text: TextManager.GetWithVariable("weaponslot", "[number]", slotIndex.ToString()), font: GUI.SubHeadingFont);
GUILayoutGroup group = new GUILayoutGroup(rectT(0.7f, 1f, buttonLayout), isHorizontal: true) { Stretch = true };
string title = item.PendingItemSwap != null ? TextManager.GetWithVariable("upgrades.pendingitem", "[itemname]", currentOrPending.Name) : item.Name;
GUITextBlock text = new GUITextBlock(rectT(0.7f, 1f, group), text: title, font: GUI.SubHeadingFont, textAlignment: Alignment.Right, parseRichText: true)
{
TextColor = GUI.Style.Orange
};
GUIImage arrowImage = new GUIImage(rectT(0.5f, 1f, group, scaleBasis: ScaleBasis.BothHeight), style: "SlideDownArrow", scaleToFit: true);
group.Recalculate();
if (text.TextSize.X > text.Rect.Width)
{
text.ToolTip = text.Text;
text.Text = ToolBox.LimitString(text.Text, text.Font, text.Rect.Width);
}
List<GUIFrame> frames = new List<GUIFrame>();
if (currentOrPending != null)
{
bool canUninstall = item.PendingItemSwap != null || !string.IsNullOrEmpty(currentOrPending.SwappableItem?.ReplacementOnUninstall);
bool isUninstallPending = item.PendingItemSwap?.Identifier == item.Prefab.SwappableItem.ReplacementOnUninstall;
if (isUninstallPending) { canUninstall = false; }
frames.Add(CreateUpgradeEntry(rectT(1f, 0.25f, parent.Content), currentOrPending.UpgradePreviewSprite,
TextManager.GetWithVariable(item.PendingItemSwap != null ? "upgrades.pendingitem" : "upgrades.installeditem", "[itemname]", currentOrPending.Name),
currentOrPending.Description,
0, null, addBuyButton: canUninstall, addProgressBar: false, buttonStyle: "StoreRemoveFromCrateButton"));
if (canUninstall && frames.Last().FindChild(c => c is GUIButton, recursive: true) is GUIButton refundButton)
{
refundButton.Enabled = true;
refundButton.OnClicked += (button, o) =>
{
string textTag = item.PendingItemSwap != null ? "upgrades.cancelitemswappromptbody" : "upgrades.itemuninstallpromptbody";
if (isUninstallPending) { textTag = "upgrades.cancelitemuninstallpromptbody"; }
string promptBody = TextManager.GetWithVariables(textTag,
new[] { "[itemtouninstall]" },
new[] { isUninstallPending ? item.Name : currentOrPending.Name });
currectConfirmation = EventEditorScreen.AskForConfirmation(TextManager.Get("upgrades.refundprompttitle"), promptBody, () =>
{
if (GameMain.NetworkMember != null)
{
WaitForServerUpdate = true;
}
Campaign?.UpgradeManager.CancelItemSwap(item);
GameMain.Client?.SendCampaignState();
return true;
});
return true;
};
}
var dividerContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.1f), parent.Content.RectTransform), style: null);
new GUIFrame(new RectTransform(new Vector2(0.8f, 0.5f), dividerContainer.RectTransform, Anchor.Center), style: "HorizontalLine");
frames.Add(dividerContainer);
}
foreach (ItemPrefab replacement in availableReplacements)
{
if (replacement == currentOrPending) { continue; }
bool isPurchased = item.AvailableSwaps.Contains(replacement);
int price = isPurchased || replacement == item.Prefab ? 0 : replacement.SwappableItem.GetPrice(Campaign.Map?.CurrentLocation);
frames.Add(CreateUpgradeEntry(rectT(1f, 0.25f, parent.Content), replacement.UpgradePreviewSprite, replacement.Name, replacement.Description,
price, null,
addBuyButton: true,
addProgressBar: false,
buttonStyle: isPurchased ? "UpgradeBuyButton" : "StoreAddToCrateButton"));
if (!(frames.Last().FindChild(c => c is GUIButton, recursive: true) is GUIButton buyButton)) { continue; }
if (Campaign.Money >= price)
{
buyButton.Enabled = true;
buyButton.OnClicked += (button, o) =>
{
string promptBody = TextManager.GetWithVariables(isPurchased ? "upgrades.itemswappromptbody" : "upgrades.purchaseitemswappromptbody",
new[] { "[itemtoinstall]", "[amount]" },
new[] { replacement.Name, replacement.SwappableItem.GetPrice(Campaign?.Map?.CurrentLocation).ToString() });
currectConfirmation = EventEditorScreen.AskForConfirmation(TextManager.Get("Upgrades.PurchasePromptTitle"), promptBody, () =>
{
if (GameMain.NetworkMember != null)
{
WaitForServerUpdate = true;
}
if (item.Prefab == replacement && item.PendingItemSwap != null)
{
Campaign?.UpgradeManager.CancelItemSwap(item);
}
else
{
Campaign?.UpgradeManager.PurchaseItemSwap(item, replacement);
}
GameMain.Client?.SendCampaignState();
return true;
});
return true;
};
}
else
{
buyButton.Enabled = false;
}
}
foreach (GUIFrame frame in frames)
{
frame.Visible = false;
}
toggleButton.OnClicked = delegate
{
isOpen = !isOpen;
toggleButton.Selected = !toggleButton.Selected;
foreach (GUIFrame frame in frames)
{
frame.Visible = toggleButton.Selected;
}
if (toggleButton.Selected)
{
foreach (var itemPreview in itemPreviews)
{
itemPreview.Value.OutlineColor = itemPreview.Value.Color = itemPreview.Key == item ? GUI.Style.Orange : previewWhite;
}
foreach (GUIComponent otherComponent in toggleButton.Parent.Children)
{
if (otherComponent == toggleButton || frames.Contains(otherComponent)) { continue; }
if (otherComponent is GUIButton otherButton)
{
var otherArrowImage = otherComponent.FindChild(c => c is GUIImage, recursive: true);
otherArrowImage.SpriteEffects = SpriteEffects.None;
otherButton.Selected = false;
}
else
{
otherComponent.Visible = false;
}
}
}
else
{
foreach (var itemPreview in itemPreviews)
{
if (currentStoreLayout?.SelectedData is CategoryData categoryData && !categoryData.Category.ItemTags.Any(t => itemPreview.Key.HasTag(t))) { continue; }
itemPreview.Value.OutlineColor = itemPreview.Value.Color = GUI.Style.Orange;
}
}
activeItemSwapSlideDown = toggleButton.Selected ? toggleButton : null;
arrowImage.SpriteEffects = toggleButton.Selected ? SpriteEffects.FlipVertically : SpriteEffects.None;
parent.RecalculateChildren();
parent.UpdateScrollBarSize();
return true;
};
}
public static GUIFrame CreateUpgradeFrame(UpgradePrefab prefab, UpgradeCategory category, CampaignMode campaign, RectTransform rectTransform, bool addBuyButton = true)
{
int price = prefab.Price.GetBuyprice(campaign.UpgradeManager.GetUpgradeLevel(prefab, category), campaign.Map?.CurrentLocation);
return CreateUpgradeEntry(rectTransform, prefab.Sprite, prefab.Name, prefab.Description, price, new CategoryData(category, prefab), addBuyButton);
}
public static GUIFrame CreateUpgradeEntry(RectTransform parent, Sprite sprite, string title, string body, int price, object? userData, bool addBuyButton = true, bool addProgressBar = true, string buttonStyle = "UpgradeBuyButton")
{
float progressBarHeight = 0.25f;
if (!addProgressBar)
{
progressBarHeight = 0f;
}
/* UPGRADE PREFAB ENTRY
* |------------------------------------------------------------------|
* | | title | price |
@@ -662,23 +950,40 @@ namespace Barotrauma
* | | progress bar | x / y | |
* |------------------------------------------------------------------|
*/
GUIFrame prefabFrame = new GUIFrame(rectTransform, style: "ListBoxElement") { SelectedColor = Color.Transparent, UserData = new CategoryData(category, prefab) };
GUIFrame prefabFrame = new GUIFrame(parent, style: "ListBoxElement") { SelectedColor = Color.Transparent, UserData = userData };
GUILayoutGroup prefabLayout = new GUILayoutGroup(rectT(0.98f, 0.95f, prefabFrame, Anchor.Center), isHorizontal: true) { Stretch = true };
GUILayoutGroup imageLayout = new GUILayoutGroup(rectT(new Point(prefabLayout.Rect.Height, prefabLayout.Rect.Height), prefabLayout), childAnchor: Anchor.Center);
var icon = new GUIImage(rectT(0.9f, 0.9f, imageLayout), prefab.Sprite, scaleToFit: true) { CanBeFocused = false };
var icon = new GUIImage(rectT(0.9f, 0.9f, imageLayout, scaleBasis: ScaleBasis.BothHeight), sprite, scaleToFit: true) { CanBeFocused = false };
GUILayoutGroup textLayout = new GUILayoutGroup(rectT(0.8f - imageLayout.RectTransform.RelativeSize.X, 1, prefabLayout));
var name = new GUITextBlock(rectT(1, 0.25f, textLayout), prefab.Name, font: GUI.SubHeadingFont) { AutoScaleHorizontal = true, AutoScaleVertical = true, Padding = Vector4.Zero };
GUILayoutGroup descriptionLayout = new GUILayoutGroup(rectT(1, 0.50f, textLayout));
var description = new GUITextBlock(rectT(1, 1, descriptionLayout), prefab.Description, font: GUI.SmallFont, wrap: true, textAlignment: Alignment.TopLeft) { Padding = Vector4.Zero };
GUILayoutGroup progressLayout = new GUILayoutGroup(rectT(1, 0.25f, textLayout), isHorizontal: true, childAnchor: Anchor.CenterLeft) { UserData = "progressbar" };
new GUIProgressBar(rectT(0.8f, 0.75f, progressLayout), 0.0f, GUI.Style.Orange);
new GUITextBlock(rectT(0.2f, 1, progressLayout), string.Empty, font: GUI.SmallFont, textAlignment: Alignment.Center) { Padding = Vector4.Zero };
GUILayoutGroup buyButtonLayout = null;
var name = new GUITextBlock(rectT(1, 0.25f, textLayout), title, font: GUI.SubHeadingFont, parseRichText: true) { AutoScaleHorizontal = true, AutoScaleVertical = true, Padding = Vector4.Zero };
GUILayoutGroup descriptionLayout = new GUILayoutGroup(rectT(1, 0.75f - progressBarHeight, textLayout));
var description = new GUITextBlock(rectT(1, 1, descriptionLayout), body, font: GUI.SmallFont, wrap: true, textAlignment: Alignment.TopLeft) { Padding = Vector4.Zero };
GUILayoutGroup? progressLayout = null;
GUILayoutGroup? buyButtonLayout = null;
if (addProgressBar)
{
progressLayout = new GUILayoutGroup(rectT(1, 0.25f, textLayout), isHorizontal: true, childAnchor: Anchor.CenterLeft) { UserData = "progressbar" };
new GUIProgressBar(rectT(0.8f, 0.75f, progressLayout), 0.0f, GUI.Style.Orange);
new GUITextBlock(rectT(0.2f, 1, progressLayout), string.Empty, font: GUI.SmallFont, textAlignment: Alignment.Center) { Padding = Vector4.Zero };
}
if (addBuyButton)
{
buyButtonLayout = new GUILayoutGroup(rectT(0.2f, 1, prefabLayout), childAnchor: Anchor.TopCenter) { UserData = "buybutton" };
new GUITextBlock(rectT(1, 0.4f, buyButtonLayout), FormatCurrency(prefab.Price.GetBuyprice(campaign.UpgradeManager.GetUpgradeLevel(prefab, category), campaign.Map?.CurrentLocation)), textAlignment: Alignment.Center) { Padding = Vector4.Zero };
var buyButton = new GUIButton(rectT(0.7f, 0.5f, buyButtonLayout), string.Empty, style: "UpgradeBuyButton") { Enabled = false };
string formattedPrice = FormatCurrency(Math.Abs(price));
//negative price = refund
if (price < 0) { formattedPrice = "+" + formattedPrice; }
buyButtonLayout = new GUILayoutGroup(rectT(0.2f, 1, prefabLayout), childAnchor: Anchor.TopCenter) { UserData = "buybutton" };
var priceText = new GUITextBlock(rectT(1, 0.4f, buyButtonLayout), formattedPrice, textAlignment: Alignment.Center);
if (price < 0)
{
priceText.TextColor = GUI.Style.Green;
}
else if (price == 0)
{
priceText.Text = string.Empty;
}
new GUIButton(rectT(0.7f, 0.5f, buyButtonLayout), string.Empty, style: buttonStyle) { Enabled = false };
}
description.CalculateHeightFromText();
@@ -691,24 +996,26 @@ namespace Barotrauma
description.Text = newString.Substring(0, newString.Length - 4) + "...";
description.CalculateHeightFromText();
description.ToolTip = prefab.Description;
description.ToolTip = body;
}
// Recalculate everything to prevent jumping
if (rectTransform.Parent.GUIComponent is GUILayoutGroup group) { group.Recalculate(); }
if (parent.Parent.GUIComponent is GUILayoutGroup group) { group.Recalculate(); }
descriptionLayout.Recalculate();
prefabLayout.Recalculate();
imageLayout.Recalculate();
textLayout.Recalculate();
progressLayout.Recalculate();
progressLayout?.Recalculate();
buyButtonLayout?.Recalculate();
return prefabFrame;
}
private void CreateUpgradeEntry(UpgradePrefab prefab, UpgradeCategory category, GUIComponent parent, List<Item> itemsOnSubmarine)
private void CreateUpgradeEntry(UpgradePrefab prefab, UpgradeCategory category, GUIComponent parent, List<Item>? itemsOnSubmarine)
{
if (Campaign is null) { return; }
GUIFrame prefabFrame = CreateUpgradeFrame(prefab, category, Campaign, rectT(1f, 0.25f, parent));
var prefabLayout = prefabFrame.GetChild<GUILayoutGroup>();
GUILayoutGroup[] childLayouts = prefabLayout.GetAllChildren<GUILayoutGroup>().ToArray();
@@ -723,7 +1030,7 @@ namespace Barotrauma
var buyButtonLayout = childLayouts[2];
var buyButton = buyButtonLayout.GetChild<GUIButton>();
if (!HasPermission || itemsOnSubmarine != null && !itemsOnSubmarine.Any(it => category.CanBeApplied(it, prefab)))
if (!HasPermission || (itemsOnSubmarine != null && !itemsOnSubmarine.Any(it => category.CanBeApplied(it, prefab))))
{
prefabFrame.Enabled = false;
description.Enabled = false;
@@ -755,9 +1062,16 @@ namespace Barotrauma
private void CreateItemTooltip(MapEntity entity)
{
GUITextBlock itemName = ItemInfoFrame.FindChild("itemname", true) as GUITextBlock;
GUIListBox upgradeList = ItemInfoFrame.FindChild("upgradelist", true) as GUIListBox;
GUITextBlock moreIndicator = ItemInfoFrame.FindChild("moreindicator", true) as GUITextBlock;
int slotIndex = -1;
if (currentStoreLayout?.SelectedData is CategoryData categoryData)
{
var entitiesOnSub = Submarine.MainSub.GetItems(true).Where(i => i.Prefab.SwappableItem != null && Submarine.MainSub.IsEntityFoundOnThisSub(i, true) && categoryData.Category.ItemTags.Any(t => i.HasTag(t))).ToList();
slotIndex = entitiesOnSub.IndexOf(entity) + 1;
}
GUITextBlock? itemName = ItemInfoFrame.FindChild("itemname", true) as GUITextBlock;
GUIListBox? upgradeList = ItemInfoFrame.FindChild("upgradelist", true) as GUIListBox;
GUITextBlock? moreIndicator = ItemInfoFrame.FindChild("moreindicator", true) as GUITextBlock;
GUILayoutGroup layout = ItemInfoFrame.GetChild<GUILayoutGroup>();
Debug.Assert(itemName != null && upgradeList != null && moreIndicator != null && layout != null, "One ore more tooltip elements not found");
@@ -766,6 +1080,10 @@ namespace Barotrauma
const int maxUpgrades = 4;
itemName.Text = entity is Item ? entity.Name : TextManager.Get("upgradecategory.walls");
if (slotIndex > -1)
{
itemName.Text = TextManager.GetWithVariables("weaponslotwithname", new string[] { "[number]", "[weaponname]" }, new string[] { slotIndex.ToString(), itemName.Text });
}
upgradeList.Content.ClearChildren();
for (var i = 0; i < upgrades.Count && i < maxUpgrades; i++)
{
@@ -773,8 +1091,10 @@ namespace Barotrauma
new GUITextBlock(rectT(1, 0.25f, upgradeList.Content), CreateListEntry(upgrade.Prefab.Name, upgrade.Level)) { AutoScaleHorizontal = true, UserData = Tuple.Create(upgrade.Level, upgrade.Prefab) };
}
if (!(Campaign?.UpgradeManager is { } upgradeManager)) { return; }
// include pending upgrades into the tooltip
foreach (var (prefab, category, level) in Campaign.UpgradeManager.PendingUpgrades)
foreach (var (prefab, category, level) in upgradeManager.PendingUpgrades)
{
if (entity is Item item && category.CanBeApplied(item, prefab) || entity is Structure && category.IsWallUpgrade)
{
@@ -856,16 +1176,14 @@ namespace Barotrauma
if (GUIMessageBox.MessageBoxes[i] is GUIMessageBox msgBox && msgBox == currectConfirmation)
{
// first button is the ok button
GUIButton firstButton = msgBox.Buttons.FirstOrDefault();
if (firstButton == null) { continue; }
GUIButton? firstButton = msgBox.Buttons.FirstOrDefault();
if (firstButton is null) { continue; }
firstButton.OnClicked.Invoke(firstButton, firstButton.UserData);
}
}
}
if (itemPreviews == null) { return; }
bool found = false;
foreach (var (item, frame) in itemPreviews)
{
@@ -875,7 +1193,20 @@ namespace Barotrauma
HoveredItem = item;
if (PlayerInput.PrimaryMouseButtonClicked() && selectedUpgradTab == UpgradeTab.Upgrade && currentStoreLayout != null)
{
ScrollToCategory(data => data.Category.CanBeApplied(item, null));
if (customizeTabOpen)
{
if (selectedUpgradeCategoryLayout != null)
{
if (selectedUpgradeCategoryLayout.FindChild(c => c.UserData as Item == HoveredItem, recursive: true) is GUIButton itemElement && !itemElement.Selected)
{
itemElement.OnClicked(itemElement, itemElement.UserData);
}
}
}
else
{
ScrollToCategory(data => data.Category.CanBeApplied(item, null));
}
}
found = true;
break;
@@ -888,11 +1219,11 @@ namespace Barotrauma
if (GUI.MouseOn == submarinePreviewComponent || GUI.MouseOn == subPreviewFrame)
{
// Every wall should have the same upgrades so we can just display the first one in the tooltip
Structure firstStructure = submarineWalls.FirstOrDefault();
Structure? firstStructure = submarineWalls.FirstOrDefault();
// use pnpoly algorithm to detect if our mouse is within any of the hull polygons
if (subHullVerticies.Any(hullVertex => ToolBox.PointIntersectsWithPolygon(PlayerInput.MousePosition, hullVertex)))
{
if (HoveredItem != firstStructure) { CreateItemTooltip(firstStructure); }
if (HoveredItem != firstStructure && !(firstStructure is null)) { CreateItemTooltip(firstStructure); }
HoveredItem = firstStructure;
isMouseOnStructure = true;
GUI.MouseCursor = CursorState.Hand;
@@ -917,6 +1248,8 @@ namespace Barotrauma
private void CreateSubmarinePreview(Submarine submarine, GUIComponent parent)
{
if (mainStoreLayout == null) { return; }
if (submarineInfoFrame != null && mainStoreLayout == submarineInfoFrame.Parent)
{
mainStoreLayout.RemoveChild(submarineInfoFrame);
@@ -961,15 +1294,32 @@ namespace Barotrauma
GUIComponent component = parent.FindChild(entity, true);
if (component != null && entity is Item item)
{
Point size = new Point((int) (item.Rect.Width * item.Scale / dockedBorders.Width * hullContainer.Rect.Width), (int) (item.Rect.Height * item.Scale / dockedBorders.Height * hullContainer.Rect.Height));
GUIFrame itemFrame = new GUIFrame(rectT(size, component, Anchor.Center), style: "ScanLines")
GUIComponent itemFrame;
if (item.Prefab.UpgradePreviewSprite is { } icon)
{
SelectedColor = GUI.Style.Orange,
OutlineColor = previewWhite,
Color = previewWhite,
OutlineThickness = 2,
HoverCursor = CursorState.Hand
};
float spriteSize = 128f * item.Prefab.UpgradePreviewScale;
Point size = new Point((int) (spriteSize * item.Scale / dockedBorders.Width * hullContainer.Rect.Width));
itemFrame = new GUIImage(rectT(size, component, Anchor.Center), icon, scaleToFit: true)
{
SelectedColor = GUI.Style.Orange,
Color = previewWhite,
HoverCursor = CursorState.Hand,
SpriteEffects = item.Rotation > 90.0f && item.Rotation < 270.0f ? SpriteEffects.FlipVertically : SpriteEffects.None
};
}
else
{
Point size = new Point((int) (item.Rect.Width * item.Scale / dockedBorders.Width * hullContainer.Rect.Width), (int) (item.Rect.Height * item.Scale / dockedBorders.Height * hullContainer.Rect.Height));
itemFrame = new GUIFrame(rectT(size, component, Anchor.Center), style: "ScanLines")
{
SelectedColor = GUI.Style.Orange,
OutlineColor = previewWhite,
Color = previewWhite,
OutlineThickness = 2,
HoverCursor = CursorState.Hand
};
}
if (!itemPreviews.ContainsKey(item))
{
itemPreviews.Add(item, itemFrame);
@@ -1093,7 +1443,7 @@ namespace Barotrauma
List<UpgradePrefab> prefabs,
UpgradeCategory category,
CampaignMode campaign,
Submarine drawnSubmarine,
Submarine? drawnSubmarine,
IEnumerable<UpgradeCategory> applicableCategories)
{
// Disables the parent and only re-enables if the submarine contains valid items
@@ -1148,6 +1498,8 @@ namespace Barotrauma
private void ScrollToCategory(Predicate<CategoryData> predicate)
{
if (currentStoreLayout == null) { return; }
foreach (GUIComponent child in currentStoreLayout.Content.Children)
{
if (child.UserData is CategoryData data && predicate(data))
@@ -1163,9 +1515,9 @@ namespace Barotrauma
/// </summary>
/// <param name="category"></param>
/// <returns></returns>
private GUIFrame[] GetFrames(UpgradeCategory category)
private GUIComponent[] GetFrames(UpgradeCategory category)
{
List<GUIFrame> frames = new List<GUIFrame>();
List<GUIComponent> frames = new List<GUIComponent>();
foreach (var (item, guiFrame) in itemPreviews)
{
if (category.CanBeApplied(item, null))
@@ -1185,9 +1537,9 @@ namespace Barotrauma
}
// just a shortcut to create new RectTransforms since all the new RectTransform and new Vector2 confuses my IDE (and me)
private static RectTransform rectT(float x, float y, GUIComponent parentComponent, Anchor anchor = Anchor.TopLeft)
private static RectTransform rectT(float x, float y, GUIComponent parentComponent, Anchor anchor = Anchor.TopLeft, ScaleBasis scaleBasis = ScaleBasis.Normal)
{
return new RectTransform(new Vector2(x, y), parentComponent.RectTransform, anchor);
return new RectTransform(new Vector2(x, y), parentComponent.RectTransform, anchor, scaleBasis: scaleBasis);
}
private static RectTransform rectT(Point point, GUIComponent parentComponent, Anchor anchor = Anchor.TopLeft)
@@ -26,6 +26,7 @@ namespace Barotrauma
public static bool ShowFPS = false;
public static bool ShowPerf = false;
public static bool DebugDraw;
public static bool IsSingleplayer => NetworkMember == null;
public static bool IsMultiplayer => NetworkMember != null;
public static PerformanceCounter PerformanceCounter;
@@ -245,6 +246,23 @@ namespace Barotrauma
FarseerPhysics.Settings.PositionIterations = 1;
MainThread = Thread.CurrentThread;
Window.FileDropped += OnFileDropped;
}
public static void OnFileDropped(object sender, FileDropEventArgs args)
{
if (!(Screen.Selected is { } screen)) { return; }
string filePath = args.FilePath;
if (string.IsNullOrWhiteSpace(filePath)) { return; }
string extension = Path.GetExtension(filePath).ToLower();
System.IO.FileInfo info = new System.IO.FileInfo(args.FilePath);
if (!info.Exists) { return; }
screen.OnFileDropped(filePath, extension);
}
public void ApplyGraphicsSettings()
@@ -71,6 +71,7 @@ namespace Barotrauma
: this(isSinglePlayer)
{
AddCharacterElements(element);
ActiveOrdersElement = element.GetChildElement("activeorders");
}
partial void InitProjectSpecific()
@@ -192,7 +193,8 @@ namespace Barotrauma
{
AbsoluteSpacing = (int)(5 * GUI.Scale),
UserData = "reportbuttons",
CanBeFocused = false
CanBeFocused = false,
Visible = false
};
ReportButtonFrame.RectTransform.AbsoluteOffset = new Point(0, -chatBox.ToggleButton.Rect.Height);
@@ -433,13 +435,20 @@ namespace Barotrauma
Stretch = false
};
var soundIcons = new GUIFrame(new RectTransform(new Vector2(0.8f * iconRelativeWidth, 0.8f), layoutGroup.RectTransform), style: null)
var extraIconFrame = new GUIFrame(new RectTransform(new Vector2(0.8f * iconRelativeWidth, 0.8f), layoutGroup.RectTransform), style: null)
{
CanBeFocused = false,
UserData = "soundicons"
UserData = "extraicons"
};
var soundIconParent = new GUIFrame(new RectTransform(Vector2.One, extraIconFrame.RectTransform), style: null)
{
CanBeFocused = false,
UserData = "soundicons",
Visible = character.IsPlayer
};
new GUIImage(
new RectTransform(Vector2.One, soundIcons.RectTransform),
new RectTransform(Vector2.One, soundIconParent.RectTransform),
GUI.Style.GetComponentStyle("GUISoundIcon").GetDefaultSprite(),
scaleToFit: true)
{
@@ -448,7 +457,7 @@ namespace Barotrauma
Visible = true
};
new GUIImage(
new RectTransform(Vector2.One, soundIcons.RectTransform),
new RectTransform(Vector2.One, soundIconParent.RectTransform),
"GUISoundIconDisabled",
scaleToFit: true)
{
@@ -457,6 +466,16 @@ namespace Barotrauma
Visible = false
};
if (character.IsBot)
{
new GUIFrame(new RectTransform(Vector2.One, extraIconFrame.RectTransform), style: null)
{
CanBeFocused = false,
UserData = "objectiveicon",
Visible = false
};
}
new GUIButton(new RectTransform(new Point((int)commandButtonAbsoluteHeight), background.RectTransform), style: "CrewListCommandButton")
{
ToolTip = TextManager.Get("inputtype.command"),
@@ -624,9 +643,7 @@ namespace Barotrauma
{
if (client?.Character == null) { return; }
if (crewList.Content.GetChildByUserData(client.Character)?
.FindChild(c => c is GUILayoutGroup)?
.GetChildByUserData("soundicons") is GUIComponent soundIcons)
if (GetSoundIconParent(client.Character) is GUIComponent soundIcons)
{
var soundIcon = soundIcons.FindChild(c => c.UserData is Pair<string, float> pair && pair.First == "soundicon");
var soundIconDisabled = soundIcons.FindChild("soundicondisabled");
@@ -638,15 +655,17 @@ namespace Barotrauma
public void SetClientSpeaking(Client client)
{
if (client?.Character != null) { SetCharacterSpeaking(client.Character); }
if (client?.Character != null)
{
SetCharacterSpeaking(client.Character);
}
}
public void SetCharacterSpeaking(Character character)
{
if (crewList.Content.GetChildByUserData(character)?
.FindChild(c => c is GUILayoutGroup)?
.GetChildByUserData("soundicons")?
.FindChild(c => c.UserData is Pair<string, float> pair && pair.First == "soundicon") is GUIComponent soundIcon)
if (character == null || character.IsBot) { return; }
if (GetSoundIconParent(character)?.FindChild(c => c.UserData is Pair<string, float> pair && pair.First == "soundicon") is GUIComponent soundIcon)
{
soundIcon.Color = Color.White;
Pair<string, float> userdata = soundIcon.UserData as Pair<string, float>;
@@ -654,6 +673,19 @@ namespace Barotrauma
}
}
private GUIComponent GetSoundIconParent(GUIComponent characterComponent)
{
return characterComponent?
.FindChild(c => c is GUILayoutGroup)?
.GetChildByUserData("extraicons")?
.GetChildByUserData("soundicons");
}
private GUIComponent GetSoundIconParent(Character character)
{
return GetSoundIconParent(crewList?.Content.GetChildByUserData(character));
}
#endregion
#region Crew List Order Displayment
@@ -770,6 +802,7 @@ namespace Barotrauma
if (icon is GUIImage image)
{
image.Sprite = GetOrderIconSprite(order, option);
image.ToolTip = CreateOrderTooltip(order, option);
}
updatedExistingIcon = true;
}
@@ -835,7 +868,7 @@ namespace Barotrauma
Visible = false
};
int hierarchyIndex = GetOrderIconHierarchyIndex(priority);
int hierarchyIndex = Math.Clamp(CharacterInfo.HighestManualOrderPriority - priority, 0, Math.Max(currentOrderIconList.Content.CountChildren - 1, 0));
if (hierarchyIndex != currentOrderIconList.Content.GetChildIndex(nodeIcon))
{
nodeIcon.RectTransform.RepositionChildInHierarchy(hierarchyIndex);
@@ -878,11 +911,6 @@ namespace Barotrauma
}
}
}
static int GetOrderIconHierarchyIndex(int priority)
{
return CharacterInfo.HighestManualOrderPriority - priority;
}
}
public void AddCurrentOrderIcon(Character character, OrderInfo? orderInfo)
@@ -1020,29 +1048,36 @@ namespace Barotrauma
SetCharacterOrder(character, orderInfo.Order, orderInfo.OrderOption, priority, Character.Controlled);
}
private string CreateOrderTooltip(Order order, string option)
private string CreateOrderTooltip(Order orderPrefab, string option, Entity targetEntity)
{
if (order == null) { return ""; }
if (orderPrefab == null) { return ""; }
if (!string.IsNullOrEmpty(option))
{
return TextManager.GetWithVariables("crewlistordericontooltip",
new string[2] { "[ordername]", "[orderoption]" },
new string[2] { order.Name, order.GetOptionName(option) });
new string[2] { orderPrefab.Name, orderPrefab.GetOptionName(option) });
}
else if (order.TargetEntity is Item targetItem && order.MinimapIcons.ContainsKey(targetItem.Prefab.Identifier))
else if (targetEntity is Item targetItem && targetItem.Prefab.MinimapIcon != null)
{
return TextManager.GetWithVariables("crewlistordericontooltip",
new string[2] { "[ordername]", "[orderoption]" },
new string[2] { order.Name, targetItem.Name });
new string[2] { orderPrefab.Name, targetItem.Name });
}
else
{
return order.Name;
return orderPrefab.Name;
}
}
private string CreateOrderTooltip(OrderInfo orderInfo) =>
CreateOrderTooltip(orderInfo.Order, orderInfo.OrderOption);
private string CreateOrderTooltip(Order order, string option)
{
return CreateOrderTooltip(order?.Prefab ?? order, option, order?.TargetEntity);
}
private string CreateOrderTooltip(OrderInfo orderInfo)
{
return CreateOrderTooltip(orderInfo.Order?.Prefab ?? orderInfo.Order, orderInfo.OrderOption, orderInfo.Order?.TargetEntity);
}
private Sprite GetOrderIconSprite(Order order, string option)
{
@@ -1052,9 +1087,9 @@ namespace Barotrauma
{
order.Prefab.OptionSprites.TryGetValue(option, out sprite);
}
if (sprite == null && order.TargetEntity is Item targetItem && order.MinimapIcons.Any())
if (sprite == null && order.TargetEntity is Item targetItem && targetItem.Prefab.MinimapIcon != null)
{
order.MinimapIcons.TryGetValue(targetItem.Prefab.Identifier, out sprite);
sprite = targetItem.Prefab.MinimapIcon;
}
return sprite ?? order.SymbolSprite;
}
@@ -1272,7 +1307,7 @@ namespace Barotrauma
partial void UpdateProjectSpecific(float deltaTime)
{
// Quick selection
if (!GameMain.IsMultiplayer && GUI.KeyboardDispatcher.Subscriber == null)
if (GameMain.IsSingleplayer && GUI.KeyboardDispatcher.Subscriber == null)
{
if (PlayerInput.KeyHit(InputType.SelectNextCharacter))
{
@@ -1294,7 +1329,7 @@ namespace Barotrauma
(GUI.KeyboardDispatcher.Subscriber == null || (GUI.KeyboardDispatcher.Subscriber is GUIComponent component && (component == crewList || component.IsChildOf(crewList)))) &&
commandFrame == null && !clicklessSelectionActive && CanIssueOrders && !(GameMain.GameSession?.Campaign?.ShowCampaignUI ?? false))
{
if (PlayerInput.KeyDown(Keys.LeftShift) || PlayerInput.KeyDown(Keys.RightShift))
if (PlayerInput.IsShiftDown())
{
CreateCommandUI(FindEntityContext(), true);
}
@@ -1521,19 +1556,44 @@ namespace Barotrauma
{
crewList.Select(character, force: true);
}
if (character.AIController is HumanAIController controller)
if (GameMain.IsSingleplayer && character.IsBot && character.AIController is HumanAIController controller &&
controller.ObjectiveManager is AIObjectiveManager objectiveManager)
{
OrderInfo? currentOrderInfo = controller.ObjectiveManager?.GetCurrentOrderInfo();
if (currentOrderInfo.HasValue)
// In multiplayer, these are set through character networking (the server lets the clients now when these are updated)
if (objectiveManager.CurrentObjective is AIObjective currentObjective)
{
SetHighlightedOrderIcon(characterComponent, currentOrderInfo.Value.Order?.Identifier, currentOrderInfo.Value.OrderOption);
if (objectiveManager.IsOrder(currentObjective))
{
var orderInfo = objectiveManager.CurrentOrders.FirstOrDefault(o => o.Objective == currentObjective);
SetOrderHighlight(characterComponent, orderInfo.Order?.Identifier, orderInfo.OrderOption);
}
else
{
CreateObjectiveIcon(characterComponent, currentObjective);
}
}
}
if (characterComponent.GetChild<GUILayoutGroup>().GetChildByUserData("soundicons") is GUIComponent soundIconParent)
// Order highlighting and objective icons are intended to communicate bot behavior so they should be disabled for player characters
if (character.IsPlayer)
{
DisableOrderHighlight(characterComponent);
RemoveObjectiveIcon(characterComponent);
}
if (GetSoundIconParent(characterComponent) is GUIComponent soundIconParent)
{
if (soundIconParent.FindChild(c => c.UserData is Pair<string, float> pair && pair.First == "soundicon") is GUIImage soundIcon)
{
VoipClient.UpdateVoiceIndicator(soundIcon, 0.0f, deltaTime);
if (character.IsPlayer)
{
soundIconParent.Visible = true;
VoipClient.UpdateVoiceIndicator(soundIcon, 0.0f, deltaTime);
}
else if(soundIcon.Visible)
{
var userdata = soundIcon.UserData as Pair<string, float>;
userdata.Second = 0.0f;
soundIconParent.Visible = soundIcon.Visible = false;
}
}
}
}
@@ -1559,31 +1619,127 @@ namespace Barotrauma
UpdateReports();
}
private void SetHighlightedOrderIcon(GUIComponent characterComponent, string orderIdentifier, string orderOption)
private void SetOrderHighlight(GUIComponent characterComponent, string orderIdentifier, string orderOption)
{
var currentOrderIconList = GetCurrentOrderIconList(characterComponent);
if (currentOrderIconList == null) { return; }
bool foundMatch = false;
foreach (var orderIcon in currentOrderIconList.Content.Children)
if (characterComponent == null) { return; }
RemoveObjectiveIcon(characterComponent);
if (GetCurrentOrderIconList(characterComponent) is GUIListBox currentOrderIconList)
{
var glowComponent = orderIcon.GetChildByUserData("glow");
if (glowComponent == null) { continue; }
if (foundMatch)
bool foundMatch = false;
foreach (var orderIcon in currentOrderIconList.Content.Children)
{
glowComponent.Visible = false;
continue;
var glowComponent = orderIcon.GetChildByUserData("glow");
if (glowComponent == null) { continue; }
if (foundMatch)
{
glowComponent.Visible = false;
continue;
}
var orderInfo = (OrderInfo)orderIcon.UserData;
foundMatch = orderInfo.MatchesOrder(orderIdentifier, orderOption);
glowComponent.Visible = foundMatch;
}
var orderInfo = (OrderInfo)orderIcon.UserData;
foundMatch = orderInfo.MatchesOrder(orderIdentifier, orderOption);
glowComponent.Visible = foundMatch;
}
}
public void SetHighlightedOrderIcon(Character character, string orderIdentifier, string orderOption)
public void SetOrderHighlight(Character character, string orderIdentifier, string orderOption)
{
if (crewList == null) { return; }
var characterComponent = crewList.Content.GetChildByUserData(character);
SetHighlightedOrderIcon(characterComponent, orderIdentifier, orderOption);
SetOrderHighlight(characterComponent, orderIdentifier, orderOption);
}
private void DisableOrderHighlight(GUIComponent characterComponent)
{
if (GetCurrentOrderIconList(characterComponent) is GUIListBox currentOrderIconList)
{
foreach (var orderIcon in currentOrderIconList.Content.Children)
{
var glowComponent = orderIcon.GetChildByUserData("glow");
if (glowComponent == null) { continue; }
glowComponent.Visible = false;
}
}
}
private void CreateObjectiveIcon(GUIComponent characterComponent, Sprite sprite, string tooltip)
{
if (characterComponent == null || !(characterComponent.UserData is Character character) || character.IsPlayer) { return; }
DisableOrderHighlight(characterComponent);
if (GetObjectiveIconParent(characterComponent) is GUIFrame objectiveIconFrame)
{
var existingObjectiveIcon = objectiveIconFrame.GetChild<GUIImage>();
if (existingObjectiveIcon == null || existingObjectiveIcon.Sprite != sprite || existingObjectiveIcon.ToolTip != tooltip)
{
objectiveIconFrame.ClearChildren();
if (sprite != null)
{
var objectiveIcon = CreateNodeIcon(Vector2.One, objectiveIconFrame.RectTransform, sprite, Color.LightGray, tooltip: tooltip);
new GUIFrame(new RectTransform(new Vector2(1.5f), objectiveIcon.RectTransform, anchor: Anchor.Center), style: "OuterGlowCircular")
{
CanBeFocused = false,
Color = Color.LightGray
};
objectiveIconFrame.Visible = true;
}
else
{
objectiveIconFrame.Visible = false;
}
}
}
}
public void CreateObjectiveIcon(Character character, string identifier, string option, Entity targetEntity)
{
CreateObjectiveIcon(crewList?.Content.GetChildByUserData(character),
AIObjective.GetSprite(identifier, option, targetEntity),
GetObjectiveIconTooltip(identifier, option, targetEntity));
}
private void CreateObjectiveIcon(GUIComponent characterComponent, AIObjective objective)
{
CreateObjectiveIcon(characterComponent,
objective?.GetSprite(),
GetObjectiveIconTooltip(objective));
}
private string GetObjectiveIconTooltip(string identifier, string option, Entity targetEntity)
{
string variableValue;
identifier = identifier.RemoveWhitespace();
if (Order.Prefabs.TryGetValue(identifier, out Order orderPrefab))
{
variableValue = CreateOrderTooltip(orderPrefab, option, targetEntity);
}
else
{
variableValue = TextManager.Get($"objective.{identifier}", returnNull: true) ?? "";
}
return string.IsNullOrEmpty(variableValue) ? variableValue : TextManager.GetWithVariable("crewlistobjectivetooltip", "[objective]", variableValue);
}
private string GetObjectiveIconTooltip(AIObjective objective)
{
return objective == null ? "" :
GetObjectiveIconTooltip(objective.Identifier, objective.Option, (objective as AIObjectiveOperateItem)?.OperateTarget);
}
private GUIComponent GetObjectiveIconParent(GUIComponent characterComponent)
{
return characterComponent?
.GetChild<GUILayoutGroup>()?
.GetChildByUserData("extraicons")?
.GetChildByUserData("objectiveicon");
}
private void RemoveObjectiveIcon(GUIComponent characterComponent)
{
if (GetObjectiveIconParent(characterComponent) is GUIFrame objectiveIconFrame)
{
objectiveIconFrame.ClearChildren();
objectiveIconFrame.Visible = false;
}
}
#endregion
@@ -2126,6 +2282,8 @@ namespace Barotrauma
private void CreateShortcutNodes()
{
bool HasAppropriateJob(Character c, string jobId) => c.Info?.Job != null && c.Info.Job.Prefab.AppropriateOrders.Contains(jobId);
Submarine sub = GetTargetSubmarine();
if (sub == null) { return; }
@@ -2138,7 +2296,7 @@ namespace Barotrauma
var reactorOutput = -reactor.CurrPowerConsumption;
// If player is not an engineer AND the reactor is not powered up AND nobody is using the reactor
// ---> Create shortcut node for "Operate Reactor" order's "Power Up" option
if ((Character.Controlled == null || Character.Controlled.Info?.Job?.Prefab != JobPrefab.Get("engineer")) &&
if ((Character.Controlled == null || !HasAppropriateJob(Character.Controlled, "operatereactor")) &&
reactorOutput < float.Epsilon && characters.None(c => c.SelectedConstruction == reactor.Item))
{
var order = new Order(Order.GetPrefab("operatereactor"), reactor.Item, reactor, Character.Controlled);
@@ -2151,7 +2309,7 @@ namespace Barotrauma
// TODO: Reconsider the conditions as bot captain can have the nav term selected without operating it
// If player is not a captain AND nobody is using the nav terminal AND the nav terminal is powered up
// --> Create shortcut node for Steer order
if (shortcutNodes.Count < maxShortCutNodeCount && (Character.Controlled == null || Character.Controlled.Info?.Job?.Prefab != JobPrefab.Get("captain")) &&
if (shortcutNodes.Count < maxShortCutNodeCount && (Character.Controlled == null || !HasAppropriateJob(Character.Controlled, "steer")) &&
sub.GetItems(false).Find(i => i.HasTag("navterminal") && i.IsPlayerTeamInteractable) is Item nav && characters.None(c => c.SelectedConstruction == nav) &&
nav.GetComponent<Steering>() is Steering steering && steering.Voltage > steering.MinVoltage)
{
@@ -2161,8 +2319,8 @@ namespace Barotrauma
// If player is not a security officer AND invaders are reported
// --> Create shorcut node for Fight Intruders order
if (shortcutNodes.Count < maxShortCutNodeCount && (Character.Controlled == null || Character.Controlled.Info?.Job?.Prefab != JobPrefab.Get("securityofficer")) &&
(Order.GetPrefab("reportintruders") is Order reportIntruders && ActiveOrders.Any(o => o.First.Prefab == reportIntruders)))
if (shortcutNodes.Count < maxShortCutNodeCount && (Character.Controlled == null || !HasAppropriateJob(Character.Controlled, "fightintruders")) &&
Order.GetPrefab("reportintruders") is Order reportIntruders && ActiveOrders.Any(o => o.First.Prefab == reportIntruders))
{
shortcutNodes.Add(
CreateOrderNode(shortcutNodeSize, null, Point.Zero, Order.GetPrefab("fightintruders"), -1));
@@ -2170,25 +2328,43 @@ namespace Barotrauma
// If player is not a mechanic AND a breach has been reported
// --> Create shorcut node for Fix Leaks order
if (shortcutNodes.Count < maxShortCutNodeCount && (Character.Controlled == null || Character.Controlled.Info?.Job?.Prefab != JobPrefab.Get("mechanic")) &&
(Order.GetPrefab("reportbreach") is Order reportBreach && ActiveOrders.Any(o => o.First.Prefab == reportBreach)))
if (shortcutNodes.Count < maxShortCutNodeCount && (Character.Controlled == null || !HasAppropriateJob(Character.Controlled, "fixleaks")) &&
Order.GetPrefab("reportbreach") is Order reportBreach && ActiveOrders.Any(o => o.First.Prefab == reportBreach))
{
shortcutNodes.Add(
CreateOrderNode(shortcutNodeSize, null, Point.Zero, Order.GetPrefab("fixleaks"), -1));
}
// If player is not an engineer AND broken devices have been reported
// --> Create shortcut node for Repair Damaged Systems order
if (shortcutNodes.Count < maxShortCutNodeCount && (Character.Controlled == null || Character.Controlled.Info?.Job?.Prefab != JobPrefab.Get("engineer")) &&
(Order.GetPrefab("reportbrokendevices") is Order reportBrokenDevices && ActiveOrders.Any(o => o.First.Prefab == reportBrokenDevices)))
// --> Create shortcut nodes for the Repair orders
if (shortcutNodes.Count < maxShortCutNodeCount && Order.GetPrefab("reportbrokendevices") is Order reportBrokenDevices && ActiveOrders.Any(o => o.First.Prefab == reportBrokenDevices))
{
shortcutNodes.Add(
CreateOrderNode(shortcutNodeSize, null, Point.Zero, Order.GetPrefab("repairsystems"), -1));
// TODO: Doesn't work for player issued reports, because they don't have a target.
int repairNodes = 0;
string tag = "repairelectrical";
if (shortcutNodes.Count < maxShortCutNodeCount && (Character.Controlled == null || !HasAppropriateJob(Character.Controlled, tag)) && ActiveOrders.Any(o => o.First.Prefab == reportBrokenDevices && o.First.TargetItemComponent is Repairable r && r.requiredSkills.Any(s => s.Identifier == "electrical")))
{
shortcutNodes.Add(CreateOrderNode(shortcutNodeSize, null, Point.Zero, Order.GetPrefab(tag), -1));
repairNodes++;
}
tag = "repairmechanical";
if (shortcutNodes.Count < maxShortCutNodeCount && (Character.Controlled == null || !HasAppropriateJob(Character.Controlled, tag)) && ActiveOrders.Any(o => o.First.Prefab == reportBrokenDevices && o.First.TargetItemComponent is Repairable r && r.requiredSkills.Any(s => s.Identifier == "mechanical")))
{
shortcutNodes.Add(CreateOrderNode(shortcutNodeSize, null, Point.Zero, Order.GetPrefab(tag), -1));
repairNodes++;
}
if (repairNodes == 0 && shortcutNodes.Count < maxShortCutNodeCount)
{
tag = "repairsystems";
if (Character.Controlled == null || !HasAppropriateJob(Character.Controlled, tag))
{
shortcutNodes.Add(CreateOrderNode(shortcutNodeSize, null, Point.Zero, Order.GetPrefab(tag), -1));
}
}
}
// If fire is reported
// --> Create shortcut node for Extinguish Fires order
if (shortcutNodes.Count < maxShortCutNodeCount && ActiveOrders.Any(o=> o.First.Prefab == Order.GetPrefab("reportfire")))
if (shortcutNodes.Count < maxShortCutNodeCount && ActiveOrders.Any(o => o.First.Prefab == Order.GetPrefab("reportfire")))
{
shortcutNodes.Add(
CreateOrderNode(shortcutNodeSize, null, Point.Zero, Order.GetPrefab("extinguishfires"), -1));
@@ -2645,16 +2821,10 @@ namespace Barotrauma
{
optionElement.OnSecondaryClicked = (button, _) => CreateAssignmentNodes(button);
}
Sprite icon = null;
order.MinimapIcons?.TryGetValue(item.Prefab.Identifier, out icon);
if (item.Prefab.MinimapIcon != null)
{
icon = item.Prefab.MinimapIcon;
}
var colorMultiplier = characters.Any(c => c.CurrentOrders.Any(o => o.Order != null &&
o.Order.Identifier == userData.Item1.Identifier &&
o.Order.TargetEntity == userData.Item1.TargetEntity)) ? 0.5f : 1f;
CreateNodeIcon(Vector2.One, optionElement.RectTransform, icon ?? order.SymbolSprite, order.Color * colorMultiplier);
CreateNodeIcon(Vector2.One, optionElement.RectTransform, item.Prefab.MinimapIcon ?? order.SymbolSprite, order.Color * colorMultiplier);
optionNodes.Add(new Tuple<GUIComponent, Keys>(optionElement, Keys.None));
}
optionElements.Add(optionElement);
@@ -3182,16 +3352,7 @@ namespace Barotrauma
private Character GetCharacterForQuickAssignment(Order order)
{
var controllingCharacter = Character.Controlled != null;
#if !DEBUG
if (!controllingCharacter) { return null; }
#endif
if (order.Category == OrderCategory.Operate && HumanAIController.IsItemOperatedByAnother(null, order.TargetItemComponent, out Character operatingCharacter) &&
(!controllingCharacter || operatingCharacter.CanHearCharacter(Character.Controlled)))
{
return operatingCharacter;
}
return GetCharactersSortedForOrder(order, false).FirstOrDefault(c => !controllingCharacter || c.CanHearCharacter(Character.Controlled)) ?? Character.Controlled;
return GetCharacterForQuickAssignment(order, Character.Controlled, characters);
}
private List<Character> GetCharactersForManualAssignment(Order order)
@@ -3203,34 +3364,15 @@ namespace Barotrauma
{
return characters.Union(GetOrderableFriendlyNPCs()).Where(c => !c.IsDismissed).OrderBy(c => c.Info.DisplayName).ToList();
}
return GetCharactersSortedForOrder(order, order.Identifier != "follow").ToList();
}
private IEnumerable<Character> GetCharactersSortedForOrder(Order order, bool includeSelf)
{
return characters.Where(c => Character.Controlled == null || ((includeSelf || c != Character.Controlled) && c.TeamID == Character.Controlled.TeamID)).Union(GetOrderableFriendlyNPCs())
// 1. Prioritize those who are on the same submarine than the controlled character
.OrderByDescending(c => Character.Controlled == null || c.Submarine == Character.Controlled.Submarine)
// 2. Prioritize those who have been given the same maintenance or operate order as now issued
.ThenByDescending(c => c.CurrentOrders.Any(o =>
o.Order != null && o.Order.Identifier == order.Identifier &&
(order.Category == OrderCategory.Maintenance || order.Category == OrderCategory.Operate)))
// 3. Prioritize those with the appropriate job for the order
.ThenByDescending(c => order.HasAppropriateJob(c))
// 4. Prioritize bots over player controlled characters
.ThenByDescending(c => c.IsBot)
// 5. Use the priority value of the current objective
.ThenBy(c => c.AIController is HumanAIController humanAI ? humanAI.ObjectiveManager.CurrentObjective?.Priority : 0)
// 6. Prioritize those with the best skill for the order
.ThenByDescending(c => c.GetSkillLevel(order.AppropriateSkill));
return GetCharactersSortedForOrder(order, characters, Character.Controlled, order.Identifier != "follow", extraCharacters: GetOrderableFriendlyNPCs()).ToList();
}
private IEnumerable<Character> GetOrderableFriendlyNPCs()
{
// TODO: change this so that we can get the data without having to rely on ui elements.
return crewList.Content.Children.Where(c => c.UserData is Character character && character.TeamID == CharacterTeamType.FriendlyNPC).Select(c => (Character)c.UserData);
}
#endregion
#endregion
@@ -3325,15 +3467,76 @@ namespace Barotrauma
public void Save(XElement parentElement)
{
XElement element = new XElement("crew");
foreach (CharacterInfo ci in characterInfos)
{
var infoElement = ci.Save(element);
if (ci.InventoryData != null) { infoElement.Add(ci.InventoryData); }
if (ci.HealthData != null) { infoElement.Add(ci.HealthData); }
if (ci.OrderData != null) { infoElement.Add(ci.OrderData); }
if (ci.LastControlled) { infoElement.Add(new XAttribute("lastcontrolled", true)); }
}
SaveActiveOrders(element);
parentElement.Add(element);
}
public static void ClientReadActiveOrders(IReadMessage inc)
{
ushort count = inc.ReadUInt16();
if (count < 1) { return; }
var activeOrders = new List<(Order, float?)>();
for (ushort i = 0; i < count; i++)
{
var orderMessageInfo = OrderChatMessage.ReadOrder(inc);
Character orderGiver = null;
if (inc.ReadBoolean())
{
ushort orderGiverId = inc.ReadUInt16();
orderGiver = orderGiverId != Entity.NullEntityID ? Entity.FindEntityByID(orderGiverId) as Character : null;
}
if (orderMessageInfo.OrderIndex < 0 || orderMessageInfo.OrderIndex >= Order.PrefabList.Count)
{
DebugConsole.ThrowError("Invalid active order - order index out of bounds.");
continue;
}
Order orderPrefab = orderMessageInfo.OrderPrefab ?? Order.PrefabList[orderMessageInfo.OrderIndex];
Order order = orderMessageInfo.TargetType switch
{
Order.OrderTargetType.Entity =>
new Order(orderPrefab, orderMessageInfo.TargetEntity, orderPrefab.GetTargetItemComponent(orderMessageInfo.TargetEntity as Item), orderGiver: orderGiver),
Order.OrderTargetType.Position =>
new Order(orderPrefab, orderMessageInfo.TargetPosition, orderGiver: orderGiver),
Order.OrderTargetType.WallSection =>
new Order(orderPrefab, orderMessageInfo.TargetEntity as Structure, orderMessageInfo.WallSectionIndex, orderGiver: orderGiver),
_ => throw new NotImplementedException()
};
if (order != null && order.TargetAllCharacters)
{
var fadeOutTime = !orderPrefab.IsIgnoreOrder ? (float?)orderPrefab.FadeOutTime : null;
activeOrders.Add((order, fadeOutTime));
}
}
foreach (var (order, fadeOutTime) in activeOrders)
{
if (order.IsIgnoreOrder)
{
switch (order.TargetType)
{
case Order.OrderTargetType.Entity:
if (!(order.TargetEntity is IIgnorable ignorableEntity)) { break; }
ignorableEntity.OrderedToBeIgnored = order.Identifier == "ignorethis";
break;
case Order.OrderTargetType.Position:
throw new NotImplementedException();
case Order.OrderTargetType.WallSection:
if (!order.WallSectionIndex.HasValue) { break; }
if (!(order.TargetEntity is Structure s)) { break; }
if (!(s.GetSection(order.WallSectionIndex.Value) is IIgnorable ignorableWall)) { break; }
ignorableWall.OrderedToBeIgnored = order.Identifier == "ignorethis";
break;
}
}
GameMain.GameSession?.CrewManager?.AddOrder(order, fadeOutTime);
}
}
}
}
@@ -189,7 +189,7 @@ namespace Barotrauma
case TransitionType.None:
default:
if (Level.Loaded.Type == LevelData.LevelType.Outpost &&
(Character.Controlled?.Submarine?.Info.Type == SubmarineType.Player || (Character.Controlled?.CurrentHull?.OutpostModuleTags?.Contains("airlock") ?? false)))
(Character.Controlled?.Submarine?.Info.Type == SubmarineType.Player || (Character.Controlled?.CurrentHull?.OutpostModuleTags.Contains("airlock") ?? false)))
{
buttonText = TextManager.GetWithVariable("LeaveLocation", "[locationname]", Level.Loaded.StartLocation?.Name ?? "[ERROR]");
endRoundButton.Visible = !ForceMapUI && !ShowCampaignUI;
@@ -287,6 +287,7 @@ namespace Barotrauma
{
case InteractionType.None:
case InteractionType.Talk:
case InteractionType.Examine:
return;
case InteractionType.Upgrade when !UpgradeManager.CanUpgradeSub():
UpgradeManager.CreateUpgradeErrorMessage(TextManager.Get("Dialog.CantUpgrade"), IsSinglePlayer, npc);
@@ -326,7 +326,6 @@ namespace Barotrauma
Level prevLevel = Level.Loaded;
bool success = CrewManager.GetCharacters().Any(c => !c.IsDead);
GUI.SetSavingIndicatorState(success);
crewDead = false;
var continueButton = GameMain.GameSession.RoundSummary?.ContinueButton;
@@ -484,6 +483,8 @@ namespace Barotrauma
{
IsFirstRound = false;
CoroutineManager.StartCoroutine(DoLevelTransition(), "LevelTransition");
bool success = CrewManager.GetCharacters().Any(c => !c.IsDead);
GUI.SetSavingIndicatorState(success && (Level.IsLoadedOutpost || transitionType != TransitionType.None));
}
}
@@ -569,6 +570,13 @@ namespace Barotrauma
msg.Write(category.Identifier);
msg.Write((byte)level);
}
msg.Write((ushort)UpgradeManager.PurchasedItemSwaps.Count);
foreach (var itemSwap in UpgradeManager.PurchasedItemSwaps)
{
msg.Write(itemSwap.ItemToRemove.ID);
msg.Write(itemSwap.ItemToInstall?.Identifier ?? string.Empty);
}
}
//static because we may need to instantiate the campaign if it hasn't been done yet
@@ -657,6 +665,21 @@ namespace Barotrauma
pendingUpgrades.Add(new PurchasedUpgrade(prefab, category, upgradeLevel));
}
ushort purchasedItemSwapCount = msg.ReadUInt16();
List<PurchasedItemSwap> purchasedItemSwaps = new List<PurchasedItemSwap>();
for (int i = 0; i < purchasedItemSwapCount; i++)
{
UInt16 itemToRemoveID = msg.ReadUInt16();
Item itemToRemove = Entity.FindEntityByID(itemToRemoveID) as Item;
string itemToInstallIdentifier = msg.ReadString();
ItemPrefab itemToInstall = string.IsNullOrEmpty(itemToInstallIdentifier) ? null : ItemPrefab.Find(string.Empty, itemToInstallIdentifier);
if (itemToRemove == null) { continue; }
purchasedItemSwaps.Add(new PurchasedItemSwap(itemToRemove, itemToInstall));
}
bool hasCharacterData = msg.ReadBoolean();
CharacterInfo myCharacterInfo = null;
if (hasCharacterData)
@@ -703,6 +726,26 @@ namespace Barotrauma
campaign.UpgradeManager.SetPendingUpgrades(pendingUpgrades);
campaign.UpgradeManager.PurchasedUpgrades.Clear();
campaign.UpgradeManager.PurchasedUpgrades.Clear();
foreach (var purchasedItemSwap in purchasedItemSwaps)
{
if (purchasedItemSwap.ItemToInstall == null)
{
campaign.UpgradeManager.CancelItemSwap(purchasedItemSwap.ItemToRemove, force: true);
}
else
{
campaign.UpgradeManager.PurchaseItemSwap(purchasedItemSwap.ItemToRemove, purchasedItemSwap.ItemToInstall, force: true);
}
}
foreach (Item item in Item.ItemList)
{
if (item.PendingItemSwap != null && !purchasedItemSwaps.Any(it => it.ItemToRemove == item))
{
item.PendingItemSwap = null;
}
}
foreach (var (identifier, rep) in factionReps)
{
Faction faction = campaign.Factions.FirstOrDefault(f => f.Prefab.Identifier.Equals(identifier, StringComparison.OrdinalIgnoreCase));
@@ -101,7 +101,8 @@ namespace Barotrauma
case "cargo":
CargoManager.LoadPurchasedItems(subElement);
break;
case "pendingupgrades":
case "pendingupgrades": //backwards compatibility
case "upgrademanager":
UpgradeManager = new UpgradeManager(this, subElement, isSingleplayer: true);
break;
case "pets":
@@ -229,6 +230,7 @@ namespace Barotrauma
{
PetBehavior.LoadPets(petsElement);
}
CrewManager.LoadActiveOrders();
GUI.DisableSavingIndicatorDelayed();
}
@@ -480,6 +482,8 @@ namespace Barotrauma
EnableRoundSummaryGameOverState();
}
CrewManager?.ClearCurrentOrders();
//--------------------------------------
SelectSummaryScreen(roundSummary, newLevel, mirror, () =>
@@ -735,9 +739,10 @@ namespace Barotrauma
if (c.Inventory != null)
{
c.Info.InventoryData = new XElement("inventory");
c.SaveInventory(c.Inventory, c.Info.InventoryData);
c.SaveInventory();
c.Inventory?.DeleteAllItems();
}
c.Info.SaveOrderData();
}
petsElement = new XElement("pets");
@@ -748,7 +753,7 @@ namespace Barotrauma
CampaignMetadata.Save(modeElement);
Map.Save(modeElement);
CargoManager?.SavePurchasedItems(modeElement);
UpgradeManager?.SavePendingUpgrades(modeElement, UpgradeManager?.PendingUpgrades);
UpgradeManager?.Save(modeElement);
element.Add(modeElement);
}
}
@@ -531,7 +531,7 @@ namespace Barotrauma.Tutorials
}
}
yield return null;
} while (!mechanic.HasEquippedItem("divingsuit"));
} while (!mechanic.HasEquippedItem("divingsuit", slotType: InvSlotType.OuterClothes));
SetHighlight(mechanic_divingSuitContainer.Item, false);
RemoveCompletedObjective(segments[8]);
SetDoorAccess(tutorial_mechanicFinalDoor, tutorial_mechanicFinalDoorLight, true);
@@ -524,7 +524,7 @@ namespace Barotrauma
private static void CheckIfDivingGearOutOfOxygen()
{
if (!CanDisplayHints()) { return; }
var divingGear = Character.Controlled.GetEquippedItem("diving");
var divingGear = Character.Controlled.GetEquippedItem("diving", InvSlotType.OuterClothes);
if (divingGear?.OwnInventory == null) { return; }
if (divingGear.GetContainedItemConditionPercentage() > 0.0f) { return; }
DisplayHint("ondivinggearoutofoxygen", onUpdate: () =>
@@ -311,10 +311,11 @@ namespace Barotrauma
}
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform),
missionMessage, wrap: true, parseRichText: true);
if (selectedMissions.Contains(displayedMission) && displayedMission.Completed && displayedMission.Reward > 0)
int reward = displayedMission.GetReward(Submarine.MainSub);
if (selectedMissions.Contains(displayedMission) && displayedMission.Completed && reward > 0)
{
string rewardText = TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", displayedMission.Reward));
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), displayedMission.GetMissionRewardText(), parseRichText: true);
string rewardText = TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", reward));
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), displayedMission.GetMissionRewardText(Submarine.MainSub), parseRichText: true);
}
if (displayedMission != missionsToDisplay.Last())
@@ -910,7 +910,7 @@ namespace Barotrauma
else if (allowEquip) //doubleclicked and no other inventory is selected
{
//not equipped -> attempt to equip
if (!character.HasEquippedItem(item))
if (!character.HasEquippedItem(item) || item.GetComponents<Pickable>().Count() > 1)
{
return QuickUseAction.Equip;
}
@@ -14,6 +14,11 @@ namespace Barotrauma.Items.Components
private GUICustomComponent guiCustomComponent;
/// <summary>
/// Can be used to set the sprite depth individually for each contained item
/// </summary>
private float[] containedSpriteDepths;
public Sprite InventoryTopSprite
{
get { return inventoryTopSprite; }
@@ -153,6 +158,8 @@ namespace Barotrauma.Items.Components
//if a GUIFrame has been defined, draw the inventory inside it
CreateGUI();
}
containedSpriteDepths = element.GetAttributeFloatArray("containedspritedepths", new float[0]);
}
protected override void CreateGUI()
@@ -316,6 +323,10 @@ namespace Barotrauma.Items.Components
if (item.FlippedY) { origin.Y = containedItem.Sprite.SourceRect.Height - origin.Y; }
float containedSpriteDepth = ContainedSpriteDepth < 0.0f ? containedItem.Sprite.Depth : ContainedSpriteDepth;
if (i < containedSpriteDepths.Length)
{
containedSpriteDepth = containedSpriteDepths[i];
}
containedSpriteDepth = itemDepth + (containedSpriteDepth - (item.Sprite?.Depth ?? item.SpriteDepth)) / 10000.0f;
containedItem.Sprite.Draw(
@@ -199,13 +199,9 @@ namespace Barotrauma.Items.Components
}
Color neutralColor = Color.DarkCyan;
if (hull.RoomName != null)
if (hull.IsWetRoom)
{
if (hull.RoomName.Contains("ballast") || hull.RoomName.Contains("Ballast") ||
hull.RoomName.Contains("airlock") || hull.RoomName.Contains("Airlock"))
{
neutralColor = new Color(9, 80, 159);
}
neutralColor = new Color(9, 80, 159);
}
if (hullData.Distort)
@@ -53,6 +53,8 @@ namespace Barotrauma.Items.Components
private GUIFrame inventoryContainer;
private GUILayoutGroup paddedFrame;
private readonly Dictionary<string, GUIButton> warningButtons = new Dictionary<string, GUIButton>();
private static readonly string[] warningTexts = new string[]
@@ -74,7 +76,7 @@ namespace Barotrauma.Items.Components
tempRangeIndicator = new Sprite(element.GetChildElement("temprangeindicator")?.GetChildElement("sprite"));
graphLine = new Sprite(element.GetChildElement("graphline")?.GetChildElement("sprite"));
var paddedFrame = new GUILayoutGroup(new RectTransform(
paddedFrame = new GUILayoutGroup(new RectTransform(
GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center)
{ AbsoluteOffset = GUIStyle.ItemFrameOffset },
isHorizontal: true)
@@ -128,27 +130,27 @@ namespace Barotrauma.Items.Components
Point maxIndicatorSize = new Point(int.MaxValue, (int)(40 * GUI.Scale));
criticalHeatWarning = new GUITickBox(new RectTransform(new Vector2(0.33f, 1.0f), topLeftArea.RectTransform) { MaxSize = maxIndicatorSize },
criticalHeatWarning = new GUITickBox(new RectTransform(new Vector2(0.3f, 1.0f), topLeftArea.RectTransform) { MaxSize = maxIndicatorSize },
TextManager.Get("ReactorWarningCriticalTemp"), font: GUI.SubHeadingFont, style: "IndicatorLightRed")
{
Selected = false,
Enabled = false,
ToolTip = TextManager.Get("ReactorHeatTip")
};
lowTemperatureWarning = new GUITickBox(new RectTransform(new Vector2(0.33f, 1.0f), topLeftArea.RectTransform) { MaxSize = maxIndicatorSize },
TextManager.Get("ReactorWarningCriticalLowTemp"), font: GUI.SubHeadingFont, style: "IndicatorLightRed")
{
Selected = false,
Enabled = false,
ToolTip = TextManager.Get("ReactorTempTip")
};
criticalOutputWarning = new GUITickBox(new RectTransform(new Vector2(0.33f, 1.0f), topLeftArea.RectTransform) { MaxSize = maxIndicatorSize },
criticalOutputWarning = new GUITickBox(new RectTransform(new Vector2(0.3f, 1.0f), topLeftArea.RectTransform) { MaxSize = maxIndicatorSize },
TextManager.Get("ReactorWarningCriticalOutput"), font: GUI.SubHeadingFont, style: "IndicatorLightRed")
{
Selected = false,
Enabled = false,
ToolTip = TextManager.Get("ReactorOutputTip")
};
lowTemperatureWarning = new GUITickBox(new RectTransform(new Vector2(0.4f, 1.0f), topLeftArea.RectTransform) { MaxSize = maxIndicatorSize },
TextManager.Get("ReactorWarningCriticalLowTemp"), font: GUI.SubHeadingFont, style: "IndicatorLightRed")
{
Selected = false,
Enabled = false,
ToolTip = TextManager.Get("ReactorTempTip")
};
List<GUITickBox> indicatorLights = new List<GUITickBox>() { criticalHeatWarning, lowTemperatureWarning, criticalOutputWarning };
indicatorLights.ForEach(l => l.TextBlock.OverrideTextColor(GUI.Style.TextColor));
topLeftArea.Recalculate();
@@ -334,8 +336,9 @@ namespace Barotrauma.Items.Components
};
topRightArea.Recalculate();
autoTempLight.TextBlock.Wrap = true;
indicatorLights.Add(autoTempLight);
autoTempLight.TextBlock.Padding = new Vector4(autoTempLight.TextBlock.Padding.X, 0.0f, 0.0f, 0.0f);
autoTempLight.TextBlock.Text = autoTempLight.TextBlock.Text.Replace(' ', '\n');
autoTempLight.TextBlock.AutoScaleHorizontal = true;
GUITextBlock.AutoScaleAndNormalize(indicatorLights.Select(l => l.TextBlock));
// right bottom (graph area) -----------------------
@@ -533,8 +536,7 @@ namespace Barotrauma.Items.Components
warningButtons["ReactorWarningMeltdown"].Selected = meltDownTimer > MeltdownDelay * 0.5f || item.Condition == 0.0f && lightOn;
warningButtons["ReactorWarningSCRAM"].Selected = temperature > 0.1f && !PowerOn;
if ((FissionRateScrollBar.Rect.Contains(PlayerInput.MousePosition) || FissionRateScrollBar.Children.Contains(GUIScrollBar.DraggingBar) ||
TurbineOutputScrollBar.Rect.Contains(PlayerInput.MousePosition) || TurbineOutputScrollBar.Children.Contains(GUIScrollBar.DraggingBar)) &&
if (paddedFrame.Rect.Contains(PlayerInput.MousePosition) &&
!PlayerInput.KeyDown(InputType.Deselect) && !PlayerInput.KeyHit(InputType.Deselect))
{
Character.DisableControls = true;
@@ -87,21 +87,6 @@ namespace Barotrauma.Items.Components
//float = strength of the disruption, between 0-1
private readonly List<Pair<Vector2, float>> disruptedDirections = new List<Pair<Vector2, float>>();
class CachedDistance
{
public readonly Vector2 TransducerWorldPos;
public readonly Vector2 WorldPos;
public readonly float Distance;
public double RecalculationTime;
public CachedDistance(Vector2 transducerWorldPos, Vector2 worldPos, float dist)
{
TransducerWorldPos = transducerWorldPos;
WorldPos = worldPos;
Distance = dist;
}
}
private readonly Dictionary<object, CachedDistance> markerDistances = new Dictionary<object, CachedDistance>();
private readonly Color positiveColor = Color.Green;
@@ -459,8 +444,13 @@ namespace Barotrauma.Items.Components
zoomSlider.BarScroll += PlayerInput.ScrollWheelSpeed / 1000.0f;
zoomSlider.OnMoved(zoomSlider, zoomSlider.BarScroll);
}
if (PlayerInput.KeyHit(InputType.Run))
{
SonarModeSwitch.OnClicked(SonarModeSwitch, null);
}
}
float distort = 1.0f - item.Condition / item.MaxCondition;
for (int i = sonarBlips.Count - 1; i >= 0; i--)
{
@@ -1101,19 +1091,14 @@ namespace Barotrauma.Items.Components
if (Level.Loaded != null && dockingPort.Item.Submarine.WorldPosition.Y > Level.Loaded.Size.Y) { continue; }
if (dockingPort.Item.Submarine == null) { continue; }
if (dockingPort.Item.Submarine.Info.IsWreck) { continue; }
if (!dockingPort.Item.Submarine.ShowSonarMarker && !dockingPort.Item.Submarine.Info.IsOutpost) { continue; }
// docking ports should be shown even if defined as not, if the submarine is the same as the sonar's
if (!dockingPort.Item.Submarine.ShowSonarMarker && dockingPort.Item.Submarine != item.Submarine && !dockingPort.Item.Submarine.Info.IsOutpost) { continue; }
//don't show the docking ports of the opposing team on the sonar
if (item.Submarine != null)
{
if (dockingPort.Item.Submarine.TeamID == CharacterTeamType.Team1 && (item.Submarine.TeamID == CharacterTeamType.Team2 || Character.Controlled?.TeamID == CharacterTeamType.Team2))
{
continue;
}
else if (dockingPort.Item.Submarine.TeamID == CharacterTeamType.Team2 && (item.Submarine.TeamID == CharacterTeamType.Team1 || Character.Controlled?.TeamID == CharacterTeamType.Team1))
{
continue;
}
// specifically checking for friendlyNPC seems more logical here
if (dockingPort.Item.Submarine.TeamID != item.Submarine.TeamID && dockingPort.Item.Submarine.TeamID != CharacterTeamType.FriendlyNPC) { continue; }
}
Vector2 offset = (dockingPort.Item.WorldPosition - transducerCenter) * scale;
@@ -1629,9 +1614,7 @@ namespace Barotrauma.Items.Components
{
if (markerDistances.TryGetValue(targetIdentifier, out CachedDistance cachedDistance))
{
if (Timing.TotalTime > cachedDistance.RecalculationTime &&
(Vector2.DistanceSquared(cachedDistance.TransducerWorldPos, transducerPosition) > 500 * 500 ||
Vector2.DistanceSquared(cachedDistance.WorldPos, worldPosition) > 500 * 500))
if (cachedDistance.ShouldUpdateDistance(transducerPosition, worldPosition))
{
markerDistances.Remove(targetIdentifier);
CalculateDistance();
@@ -1653,10 +1636,7 @@ namespace Barotrauma.Items.Components
var path = pathFinder.FindPath(ConvertUnits.ToSimUnits(transducerPosition), ConvertUnits.ToSimUnits(worldPosition));
if (!path.Unreachable)
{
var cachedDistance = new CachedDistance(transducerPosition, worldPosition, path.TotalLength)
{
RecalculationTime = Timing.TotalTime + Rand.Range(1.0f, 5.0f)
};
var cachedDistance = new CachedDistance(transducerPosition, worldPosition, path.TotalLength, Timing.TotalTime + Rand.Range(1.0f, 5.0f));
markerDistances.Add(targetIdentifier, cachedDistance);
dist = path.TotalLength;
}
@@ -1,12 +1,18 @@
using Barotrauma.Networking;
using Barotrauma.Particles;
using FarseerPhysics;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
partial class Projectile : ItemComponent
{
private readonly List<ParticleEmitter> particleEmitters = new List<ParticleEmitter>();
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{
bool launch = msg.ReadBoolean();
@@ -96,5 +102,30 @@ namespace Barotrauma.Items.Components
Unstick();
}
}
partial void LaunchProjSpecific(Vector2 startLocation, Vector2 endLocation)
{
Vector2 particlePos = item.WorldPosition;
float rotation = -item.body.Rotation;
if (item.body.Dir < 0.0f) { rotation += MathHelper.Pi; }
Tuple<Vector2, Vector2> tracerPoints = new Tuple<Vector2, Vector2>(startLocation, endLocation);
foreach (ParticleEmitter emitter in particleEmitters)
{
emitter.Emit(1.0f, particlePos, hullGuess: null, angle: rotation, particleRotation: rotation, colorMultiplier: emitter.Prefab.ColorMultiplier, tracerPoints: tracerPoints);
}
}
partial void InitProjSpecific(XElement element)
{
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "particleemitter":
particleEmitters.Add(new ParticleEmitter(subElement));
break;
}
}
}
}
}
@@ -36,12 +36,30 @@ namespace Barotrauma.Items.Components
get
{
if (target == null || source == null) { return Vector2.Zero; }
Vector2 sourcePos = GetSourcePos();
return new Vector2(
Math.Abs(target.DrawPosition.X - source.DrawPosition.X),
Math.Abs(target.DrawPosition.Y - source.DrawPosition.Y)) * 1.5f;
Math.Abs(target.DrawPosition.X - sourcePos.X),
Math.Abs(target.DrawPosition.Y - sourcePos.Y)) * 1.5f;
}
}
private Vector2 GetSourcePos()
{
Vector2 sourcePos = source.WorldPosition;
if (source is Item sourceItem)
{
sourcePos = sourceItem.DrawPosition;
}
else if (source is Limb sourceLimb && sourceLimb.body != null)
{
sourcePos = sourceLimb.body.DrawPosition;
}
return sourcePos;
}
partial void InitProjSpecific(XElement element)
{
foreach (XElement subElement in element.Elements())
@@ -65,14 +83,18 @@ namespace Barotrauma.Items.Components
{
if (target == null) { return; }
Vector2 startPos = new Vector2(source.DrawPosition.X, -source.DrawPosition.Y);
var turret = source?.GetComponent<Turret>();
if (turret != null)
Vector2 startPos = GetSourcePos();
startPos.Y = -startPos.Y;
if (source is Item sourceItem)
{
startPos = new Vector2(source.WorldRect.X + turret.TransformedBarrelPos.X, -(source.WorldRect.Y - turret.TransformedBarrelPos.Y));
if (turret.BarrelSprite != null)
var turret = sourceItem?.GetComponent<Turret>();
if (turret != null)
{
startPos += new Vector2((float)Math.Cos(turret.Rotation), (float)Math.Sin(turret.Rotation)) * turret.BarrelSprite.size.Y * turret.BarrelSprite.RelativeOrigin.Y * item.Scale * 0.9f;
startPos = new Vector2(sourceItem.WorldRect.X + turret.TransformedBarrelPos.X, -(sourceItem.WorldRect.Y - turret.TransformedBarrelPos.Y));
if (turret.BarrelSprite != null)
{
startPos += new Vector2((float)Math.Cos(turret.Rotation), (float)Math.Sin(turret.Rotation)) * turret.BarrelSprite.size.Y * turret.BarrelSprite.RelativeOrigin.Y * item.Scale * 0.9f;
}
}
}
Vector2 endPos = new Vector2(target.DrawPosition.X, -target.DrawPosition.Y);
@@ -80,7 +102,7 @@ namespace Barotrauma.Items.Components
if (Snapped)
{
float snapState = 1.0f - snapTimer / SnapAnimDuration;
Vector2 diff = target.DrawPosition - source.DrawPosition;
Vector2 diff = target.DrawPosition - new Vector2(startPos.X, -startPos.Y);
diff.Y = -diff.Y;
int width = (int)(SpriteWidth * snapState);
@@ -17,15 +17,6 @@ namespace Barotrauma.Items.Components
TextManager.Get("CatastrophicBleeding")
};
private static readonly string[] HealthTexts =
{
TextManager.Get("NoInjuries"),
TextManager.Get("MinorInjuries"),
TextManager.Get("Injuries"),
TextManager.Get("MajorInjuries"),
TextManager.Get("CriticalInjuries")
};
private static readonly string[] OxygenTexts =
{
TextManager.Get("OxygenNormal"),
@@ -147,9 +138,13 @@ namespace Barotrauma.Items.Components
List<string> texts = new List<string>();
List<Color> textColors = new List<Color>();
texts.Add(target.Info == null ? target.DisplayName : target.Info.DisplayName);
textColors.Add(GUI.Style.TextColor);
Color nameColor = GUI.Style.TextColor;
if (Character.Controlled != null && target.TeamID != Character.Controlled.TeamID)
{
nameColor = target.TeamID == CharacterTeamType.FriendlyNPC ? Color.SkyBlue : GUI.Style.Red;
}
textColors.Add(nameColor);
if (target.IsDead)
{
@@ -34,7 +34,9 @@ namespace Barotrauma.Items.Components
private RoundSound startMoveSound, endMoveSound, moveSound;
private SoundChannel moveSoundChannel;
private RoundSound chargeSound;
private SoundChannel moveSoundChannel, chargeSoundChannel;
private Vector2 oldRotation = Vector2.Zero;
private Vector2 crosshairPos, crosshairPointerPos;
@@ -43,11 +45,16 @@ namespace Barotrauma.Items.Components
private float prevAngle;
private bool flashLowPower;
private bool flashNoAmmo;
private bool flashNoAmmo, flashLoaderBroken;
private float flashTimer;
private float flashLength = 1;
private readonly float flashLength = 1;
private const float MaxCircle = 360f;
private const float HalfCircle = 180f;
private const float QuarterCircle = 90f;
private readonly List<ParticleEmitter> particleEmitters = new List<ParticleEmitter>();
private readonly List<ParticleEmitter> particleEmitterCharges = new List<ParticleEmitter>();
[Editable, Serialize("0,0,0,0", true, description: "Optional screen tint color when the item is being operated (R,G,B,A).")]
public Color HudTint
@@ -77,6 +84,21 @@ namespace Barotrauma.Items.Components
private set;
}
[Serialize(0.0f, false, description: "The distance in which the spinning barrels rotate. Only used if spinning barrels are created.")]
public float SpinningBarrelDistance
{
get;
private set;
}
[Serialize(false, false, description: "Use firing offset for muzzleflash? This field shouldn't be needed but I'm using it for prototyping")]
public bool UseFiringOffsetForMuzzleFlash
{
get;
private set;
}
public Vector2 DrawSize
{
get
@@ -124,9 +146,15 @@ namespace Barotrauma.Items.Components
case "movesound":
moveSound = Submarine.LoadRoundSound(subElement, false);
break;
case "chargesound":
chargeSound = Submarine.LoadRoundSound(subElement, false);
break;
case "particleemitter":
particleEmitters.Add(new ParticleEmitter(subElement));
break;
case "particleemittercharge":
particleEmitterCharges.Add(new ParticleEmitter(subElement));
break;
}
}
@@ -150,7 +178,7 @@ namespace Barotrauma.Items.Components
{
recoilTimer = RetractionTime;
PlaySound(ActionType.OnUse);
Vector2 particlePos = new Vector2(item.WorldRect.X + transformedBarrelPos.X, item.WorldRect.Y - transformedBarrelPos.Y);
Vector2 particlePos = GetRelativeFiringPosition(UseFiringOffsetForMuzzleFlash);
foreach (ParticleEmitter emitter in particleEmitters)
{
emitter.Emit(1.0f, particlePos, hullGuess: null, angle: -rotation, particleRotation: rotation);
@@ -215,12 +243,55 @@ namespace Barotrauma.Items.Components
}
}
float chargeRatio = currentChargeTime / MaxChargeTime;
currentBarrelSpin = (currentBarrelSpin + MaxCircle * chargeRatio * deltaTime * 3f) % MaxCircle;
switch (currentChargingState)
{
case ChargingState.WindingUp:
Vector2 particlePos = GetRelativeFiringPosition();
float sizeMultiplier = Math.Clamp(chargeRatio, 0.1f, 1f);
foreach (ParticleEmitter emitter in particleEmitterCharges)
{
// color is currently not connected to ammo type, should be updated when ammo is changed
emitter.Emit(deltaTime, particlePos, hullGuess: null, angle: -rotation, particleRotation: rotation, sizeMultiplier: sizeMultiplier, colorMultiplier: emitter.Prefab.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(1f, 2f, chargeRatio);
}
break;
default:
if (chargeSoundChannel != null)
{
if (chargeSoundChannel.IsPlaying)
{
chargeSoundChannel.FadeOutAndDispose();
chargeSoundChannel.Looping = false;
}
else
{
chargeSoundChannel = null;
}
}
break;
}
if (moveSoundChannel != null && moveSoundChannel.IsPlaying)
{
moveSoundChannel.Gain = MathHelper.Clamp(Math.Abs(angularVelocity), 0.5f, 1.0f);
}
if (flashLowPower || flashNoAmmo)
if (flashLowPower || flashNoAmmo || flashLoaderBroken)
{
flashTimer += deltaTime;
if (flashTimer >= flashLength)
@@ -228,6 +299,7 @@ namespace Barotrauma.Items.Components
flashTimer = 0;
flashLowPower = false;
flashNoAmmo = false;
flashLoaderBroken = false;
}
}
}
@@ -289,6 +361,42 @@ namespace Barotrauma.Items.Components
rotation + MathHelper.PiOver2, item.Scale,
SpriteEffects.None, item.SpriteDepth + (barrelSprite.Depth - item.Sprite.Depth));
float chargeRatio = currentChargeTime / MaxChargeTime;
foreach (Tuple<Sprite, Vector2> chargeSprite in chargeSprites)
{
chargeSprite.Item1?.Draw(spriteBatch,
drawPos - MathUtils.RotatePoint(new Vector2(chargeSprite.Item2.X * chargeRatio, chargeSprite.Item2.Y * chargeRatio) * item.Scale, rotation + MathHelper.PiOver2),
item.SpriteColor,
rotation + MathHelper.PiOver2, item.Scale,
SpriteEffects.None, item.SpriteDepth + (chargeSprite.Item1.Depth - item.Sprite.Depth));
}
int spinningBarrelCount = spinningBarrelSprites.Count;
for (int i = 0; i < spinningBarrelCount; i++)
{
// this block is messy since I was debugging it with a bunch of values, should be cleaned up / optimized if prototype is accepted
Sprite spinningBarrel = spinningBarrelSprites[i];
float barrelCirclePosition = (MaxCircle * i / spinningBarrelCount + currentBarrelSpin) % MaxCircle;
float newDepth = spinningBarrel.Depth + (barrelCirclePosition > HalfCircle ? -0.001f : 0.001f);
float barrelColorPosition = (barrelCirclePosition + QuarterCircle) % MaxCircle;
float colorOffset = Math.Abs(barrelColorPosition - HalfCircle) / HalfCircle;
Color newColorModifier = Color.Lerp(Color.Black, Color.Gray, colorOffset);
float barrelHalfCirclePosition = Math.Abs(barrelCirclePosition - HalfCircle);
float barrelPositionModifier = MathUtils.SmoothStep(barrelHalfCirclePosition / HalfCircle);
float newPositionOffset = barrelPositionModifier * SpinningBarrelDistance;
spinningBarrel.Draw(spriteBatch,
drawPos - MathUtils.RotatePoint(new Vector2(newPositionOffset, 0f) * item.Scale, rotation + MathHelper.PiOver2),
Color.Lerp(item.SpriteColor, newColorModifier, 0.8f),
rotation + MathHelper.PiOver2, item.Scale,
SpriteEffects.None, newDepth);
}
if (!editing || GUI.DisableHUD || !item.IsSelected) { return; }
const float widgetRadius = 60.0f;
@@ -552,14 +660,20 @@ namespace Barotrauma.Items.Components
new VisualSlot(new Rectangle(invSlotPos + new Point((i % slotsPerRow) * (slotSize.X + spacing.X), (int)Math.Floor(i / (float)slotsPerRow) * (slotSize.Y + spacing.Y)), slotSize)),
availableAmmo[i], -1, true);
}
Rectangle rect = new Rectangle(invSlotPos.X, invSlotPos.Y, totalWidth, slotSize.Y);
float inflate = MathHelper.Lerp(3, 8, (float)Math.Abs(Math.Sin(flashTimer * 5)));
rect.Inflate(inflate, inflate);
Color color = GUI.Style.Red * Math.Max(0.5f, (float)Math.Sin(flashTimer * 12));
if (flashNoAmmo)
{
Rectangle rect = new Rectangle(invSlotPos.X, invSlotPos.Y, totalWidth, slotSize.Y);
float inflate = MathHelper.Lerp(3, 8, (float)Math.Abs(1 * Math.Sin(flashTimer * 5)));
rect.Inflate(inflate, inflate);
Color color = GUI.Style.Red * MathHelper.Max(0.5f, (float)Math.Sin(flashTimer * 12));
GUI.DrawRectangle(spriteBatch, rect, color, thickness: 3);
}
else if (flashLoaderBroken)
{
GUI.DrawRectangle(spriteBatch, rect, color, thickness: 3);
GUI.BrokenIcon.Draw(spriteBatch, rect.Center.ToVector2(), color, scale: rect.Height / GUI.BrokenIcon.size.Y);
GUIComponent.DrawToolTip(spriteBatch, TextManager.Get("turretloaderbroken"), new Rectangle(invSlotPos.X + totalWidth + GUI.IntScale(10), invSlotPos.Y + slotSize.Y / 2 - GUI.IntScale(9), 0, 0));
}
}
float zoom = cam == null ? 1.0f : (float)Math.Sqrt(cam.Zoom);
@@ -320,6 +320,11 @@ namespace Barotrauma
toolTip += $"‖color:{conditionColorStr}‖ ({(int)item.ConditionPercentage} %)‖color:end‖";
}
if (!string.IsNullOrEmpty(description)) { toolTip += '\n' + description; }
if (item.prefab.ContentPackage != GameMain.VanillaContent && item.prefab.ContentPackage != null)
{
colorStr = XMLExtensions.ColorToString(Color.MediumPurple);
toolTip += $"\n‖color:{colorStr}‖{item.prefab.ContentPackage.Name}‖color:end‖";
}
}
if (itemsInSlot.Count() > 1)
{
@@ -1575,7 +1580,7 @@ namespace Barotrauma
}
sprite.Draw(spriteBatch, itemPos, spriteColor, rotation, scale);
if (!item.AllowStealing && CharacterInventory.LimbSlotIcons.ContainsKey(InvSlotType.LeftHand))
if ((!item.AllowStealing || (inventory != null && inventory.slots[slotIndex].Items.Any(it => !it.AllowStealing))) && CharacterInventory.LimbSlotIcons.ContainsKey(InvSlotType.LeftHand))
{
var stealIcon = CharacterInventory.LimbSlotIcons[InvSlotType.LeftHand];
Vector2 iconSize = new Vector2(25 * GUI.Scale);
@@ -22,6 +22,19 @@ namespace Barotrauma
private readonly List<ItemComponent> activeHUDs = new List<ItemComponent>();
public GUIComponentStyle IconStyle { get; private set; } = null;
partial void AssignCampaignInteractionTypeProjSpecific(CampaignMode.InteractionType interactionType)
{
if (interactionType == CampaignMode.InteractionType.None)
{
IconStyle = null;
}
else
{
IconStyle = GUI.Style.GetComponentStyle($"CampaignInteractionIcon.{interactionType}");
}
}
public IEnumerable<ItemComponent> ActiveHUDs => activeHUDs;
public float LastImpactSoundTime;
@@ -252,7 +265,8 @@ namespace Barotrauma
else if (!ShowItems) { return; }
}
Color color = IsHighlighted && !GUI.DisableItemHighlights && Screen.Selected != GameMain.GameScreen ? GUI.Style.Orange : GetSpriteColor();
Color color = IsIncludedInSelection && editing ? GUI.Style.Blue : IsHighlighted && !GUI.DisableItemHighlights && Screen.Selected != GameMain.GameScreen ? GUI.Style.Orange * Math.Max(GetSpriteColor().A / (float) byte.MaxValue, 0.1f) : GetSpriteColor();
//if (IsSelected && editing) color = Color.Lerp(color, Color.Gold, 0.5f);
bool isWiringMode = editing && SubEditorScreen.TransparentWiringMode && SubEditorScreen.IsWiringMode() && !isWire && parentInventory == null;
@@ -305,24 +319,27 @@ namespace Barotrauma
if (prefab.ResizeHorizontal || prefab.ResizeVertical)
{
Vector2 size = new Vector2(rect.Width, rect.Height);
activeSprite.DrawTiled(spriteBatch, new Vector2(DrawPosition.X - rect.Width / 2, -(DrawPosition.Y + rect.Height / 2)) + drawOffset,
size, color: color,
textureScale: Vector2.One * Scale,
depth: depth);
fadeInBrokenSprite?.Sprite.DrawTiled(spriteBatch, new Vector2(DrawPosition.X - rect.Width / 2, -(DrawPosition.Y + rect.Height / 2)) + fadeInBrokenSprite.Offset.ToVector2() * Scale, size, color: color * fadeInBrokenSpriteAlpha,
textureScale: Vector2.One * Scale,
depth: depth - 0.000001f);
foreach (var decorativeSprite in Prefab.DecorativeSprites)
if (color.A > 0)
{
if (!spriteAnimState[decorativeSprite].IsActive) { continue; }
Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier, -rotationRad) * Scale;
if (flippedX && Prefab.CanSpriteFlipX) { offset.X = -offset.X; }
if (flippedY && Prefab.CanSpriteFlipY) { offset.Y = -offset.Y; }
decorativeSprite.Sprite.DrawTiled(spriteBatch,
new Vector2(DrawPosition.X + offset.X - rect.Width / 2, -(DrawPosition.Y + offset.Y + rect.Height / 2)),
activeSprite.DrawTiled(spriteBatch, new Vector2(DrawPosition.X - rect.Width / 2, -(DrawPosition.Y + rect.Height / 2)) + drawOffset,
size, color: color,
textureScale: Vector2.One * Scale,
depth: Math.Min(depth + (decorativeSprite.Sprite.Depth - activeSprite.Depth), 0.999f));
depth: depth);
fadeInBrokenSprite?.Sprite.DrawTiled(spriteBatch, new Vector2(DrawPosition.X - rect.Width / 2, -(DrawPosition.Y + rect.Height / 2)) + fadeInBrokenSprite.Offset.ToVector2() * Scale, size, color: color * fadeInBrokenSpriteAlpha,
textureScale: Vector2.One * Scale,
depth: depth - 0.000001f);
foreach (var decorativeSprite in Prefab.DecorativeSprites)
{
if (!spriteAnimState[decorativeSprite].IsActive) { continue; }
Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier, -rotationRad) * Scale;
if (flippedX && Prefab.CanSpriteFlipX) { offset.X = -offset.X; }
if (flippedY && Prefab.CanSpriteFlipY) { offset.Y = -offset.Y; }
decorativeSprite.Sprite.DrawTiled(spriteBatch,
new Vector2(DrawPosition.X + offset.X - rect.Width / 2, -(DrawPosition.Y + offset.Y + rect.Height / 2)),
size, color: color,
textureScale: Vector2.One * Scale,
depth: Math.Min(depth + (decorativeSprite.Sprite.Depth - activeSprite.Depth), 0.999f));
}
}
}
else
@@ -336,8 +353,11 @@ namespace Barotrauma
{
origin.Y = activeSprite.SourceRect.Height - origin.Y;
}
activeSprite.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y) + drawOffset, color, origin, rotationRad, Scale, activeSprite.effects, depth);
fadeInBrokenSprite?.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y) + fadeInBrokenSprite.Offset.ToVector2() * Scale, color * fadeInBrokenSpriteAlpha, origin, rotationRad, Scale, activeSprite.effects, depth - 0.000001f);
if (color.A > 0)
{
activeSprite.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y) + drawOffset, color, origin, rotationRad, Scale, activeSprite.effects, depth);
fadeInBrokenSprite?.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y) + fadeInBrokenSprite.Offset.ToVector2() * Scale, color * fadeInBrokenSpriteAlpha, origin, rotationRad, Scale, activeSprite.effects, depth - 0.000001f);
}
if (Infector != null && (Infector.ParentBallastFlora.HasBrokenThrough || BallastFloraBehavior.AlwaysShowBallastFloraSprite))
{
Prefab.InfectedSprite?.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y) + drawOffset, color, Prefab.InfectedSprite.Origin, rotationRad, Scale, activeSprite.effects, depth - 0.001f);
@@ -365,7 +385,7 @@ namespace Barotrauma
float depthStep = 0.000001f;
if (holdable.Picker.Inventory?.GetItemInLimbSlot(InvSlotType.RightHand) == this)
{
Limb holdLimb = holdable.Picker.AnimController.GetLimb(LimbType.RightHand);
Limb holdLimb = holdable.Picker.AnimController.GetLimb(LimbType.RightArm);
if (holdLimb?.ActiveSprite != null)
{
depth = holdLimb.ActiveSprite.Depth + holdable.Picker.AnimController.GetDepthOffset() + depthStep * 2;
@@ -377,7 +397,7 @@ namespace Barotrauma
}
else if (holdable.Picker.Inventory?.GetItemInLimbSlot(InvSlotType.LeftHand) == this)
{
Limb holdLimb = holdable.Picker.AnimController.GetLimb(LimbType.LeftHand);
Limb holdLimb = holdable.Picker.AnimController.GetLimb(LimbType.LeftArm);
if (holdLimb?.ActiveSprite != null)
{
depth = holdLimb.ActiveSprite.Depth + holdable.Picker.AnimController.GetDepthOffset() - depthStep * 2;
@@ -675,7 +695,11 @@ namespace Barotrauma
ToolTip = TextManager.Get("MirrorEntityXToolTip"),
OnClicked = (button, data) =>
{
FlipX(relativeToSub: false);
foreach (MapEntity me in SelectedList)
{
me.FlipX(relativeToSub: false);
}
if (!SelectedList.Contains(this)) { FlipX(relativeToSub: false); }
return true;
}
};
@@ -684,7 +708,11 @@ namespace Barotrauma
ToolTip = TextManager.Get("MirrorEntityYToolTip"),
OnClicked = (button, data) =>
{
FlipY(relativeToSub: false);
foreach (MapEntity me in SelectedList)
{
me.FlipY(relativeToSub: false);
}
if (!SelectedList.Contains(this)) { FlipY(relativeToSub: false); }
return true;
}
};
@@ -702,7 +730,12 @@ namespace Barotrauma
{
OnClicked = (button, data) =>
{
Reset();
foreach (MapEntity me in SelectedList)
{
(me as Item)?.Reset();
(me as Structure)?.Reset();
}
if (!SelectedList.Contains(this)) { Reset(); }
CreateEditingHUD();
return true;
}
@@ -734,7 +767,11 @@ namespace Barotrauma
if (inGame)
{
if (!ic.AllowInGameEditing) { continue; }
if (SerializableProperty.GetProperties<InGameEditable>(ic).Count == 0) { continue; }
if (SerializableProperty.GetProperties<InGameEditable>(ic).Count == 0 &&
!SerializableProperty.GetProperties<ConditionallyEditable>(ic).Any(p => p.GetAttribute<ConditionallyEditable>().IsEditable()))
{
continue;
}
}
else
{
@@ -1093,8 +1130,9 @@ namespace Barotrauma
nameText += $" ({idName})";
}
}
texts.Add(new ColoredText(nameText, GUI.Style.TextColor, false, false));
texts.Add(new ColoredText(nameText, GUI.Style.TextColor, false, false));
bool noComponentText = true;
foreach (ItemComponent ic in components)
{
if (string.IsNullOrEmpty(ic.DisplayMsg)) { continue; }
@@ -1114,8 +1152,13 @@ namespace Barotrauma
}
}
texts.Add(new ColoredText(ic.DisplayMsg, color, false, false));
noComponentText = false;
}
if ((PlayerInput.KeyDown(Keys.LeftShift) || PlayerInput.KeyDown(Keys.RightShift)) && CrewManager.DoesItemHaveContextualOrders(this))
if (noComponentText && CampaignInteractionType != CampaignMode.InteractionType.None)
{
texts.Add(new ColoredText(TextManager.GetWithVariable($"CampaignInteraction.{CampaignInteractionType}", "[key]", GameMain.Config.KeyBindText(InputType.Use)), Color.Cyan, false, false));
}
if (PlayerInput.IsShiftDown() && CrewManager.DoesItemHaveContextualOrders(this))
{
texts.Add(new ColoredText(TextManager.ParseInputTypes(TextManager.Get("itemmsgcontextualorders")), Color.Cyan, false, false));
}
@@ -1213,6 +1256,9 @@ namespace Barotrauma
}
SetActiveSprite();
break;
case NetEntityEvent.Type.AssignCampaignInteraction:
CampaignInteractionType = (CampaignMode.InteractionType)msg.ReadByte();
break;
case NetEntityEvent.Type.ApplyStatusEffect:
{
ActionType actionType = (ActionType)msg.ReadRangedInteger(0, Enum.GetValues(typeof(ActionType)).Length - 1);
@@ -1327,6 +1373,11 @@ namespace Barotrauma
isActive = true;
if (positionBuffer.Count > 0)
{
transformDirty = true;
}
body.CorrectPosition(positionBuffer, out Vector2 newPosition, out Vector2 newVelocity, out float newRotation, out float newAngularVelocity);
body.LinearVelocity = newVelocity;
body.AngularVelocity = newAngularVelocity;
@@ -63,9 +63,12 @@ namespace Barotrauma
public Dictionary<int, List<DecorativeSprite>> DecorativeSpriteGroups = new Dictionary<int, List<DecorativeSprite>>();
public Sprite InventoryIcon;
public Sprite MinimapIcon;
public Sprite UpgradePreviewSprite;
public Sprite InfectedSprite;
public Sprite DamagedInfectedSprite;
public float UpgradePreviewScale = 1.0f;
//only used to display correct color in the sub editor, item instances have their own property that can be edited on a per-item basis
[Serialize("1.0,1.0,1.0,1.0", false)]
public Color InventoryIconColor
@@ -97,6 +97,8 @@ namespace Barotrauma
{
get { return selectedList.Contains(this); }
}
public bool IsIncludedInSelection { get; set; }
public virtual bool IsVisible(Rectangle worldView)
{
@@ -360,6 +362,11 @@ namespace Barotrauma
selectionSize.X = position.X - selectionPos.X;
selectionSize.Y = selectionPos.Y - position.Y;
foreach (MapEntity entity in mapEntityList)
{
entity.IsIncludedInSelection = false;
}
List<MapEntity> newSelection = new List<MapEntity>();// FindSelectedEntities(selectionPos, selectionSize);
if (Math.Abs(selectionSize.X) > Submarine.GridSize.X || Math.Abs(selectionSize.Y) > Submarine.GridSize.Y)
{
@@ -372,10 +379,15 @@ namespace Barotrauma
if (SelectionGroups.TryGetValue(highLightedEntity, out List<MapEntity> group))
{
newSelection.AddRange(group);
foreach (MapEntity entity in group)
{
entity.IsIncludedInSelection = true;
}
}
else
{
newSelection.Add(highLightedEntity);
highLightedEntity.IsIncludedInSelection = true;
}
}
}
@@ -443,6 +455,10 @@ namespace Barotrauma
selectionPos = Vector2.Zero;
selectionSize = Vector2.Zero;
foreach (MapEntity entity in mapEntityList)
{
entity.IsIncludedInSelection = false;
}
}
}
//default, not doing anything specific yet
@@ -800,7 +816,32 @@ namespace Barotrauma
}
if (selectionPos != null && selectionPos != Vector2.Zero)
{
GUI.DrawRectangle(spriteBatch, new Vector2(selectionPos.X, -selectionPos.Y), selectionSize, Color.DarkRed, false, 0, 2f / GameScreen.Selected.Cam.Zoom);
var (sizeX, sizeY) = selectionSize;
var (posX, posY) = selectionPos;
posY = -posY;
Vector2[] corners =
{
new Vector2(posX, posY),
new Vector2(posX + sizeX, posY),
new Vector2(posX + sizeX, posY + sizeY),
new Vector2(posX, posY + sizeY)
};
Color selectionColor = GUI.Style.Blue;
float thickness = Math.Max(2f, 2f / Screen.Selected.Cam.Zoom);
GUI.DrawFilledRectangle(spriteBatch, corners[0], selectionSize, selectionColor * 0.1f);
Vector2 offset = new Vector2(0f, thickness / 2f);
if (sizeY < 0) { offset.Y = -offset.Y; }
spriteBatch.DrawLine(corners[0], corners[1], selectionColor, thickness);
spriteBatch.DrawLine(corners[1] - offset, corners[2] + offset, selectionColor, thickness);
spriteBatch.DrawLine(corners[2], corners[3], selectionColor, thickness);
spriteBatch.DrawLine(corners[3] + offset, corners[0] - offset, selectionColor, thickness);
}
}
@@ -1141,7 +1182,11 @@ namespace Barotrauma
{
if (!e.SelectableInEditor) continue;
if (Submarine.RectsOverlap(selectionRect, e.rect)) foundEntities.Add(e);
if (Submarine.RectsOverlap(selectionRect, e.rect))
{
foundEntities.Add(e);
e.IsIncludedInSelection = true;
}
}
return foundEntities;
@@ -127,7 +127,11 @@ namespace Barotrauma
ToolTip = TextManager.Get("MirrorEntityXToolTip"),
OnClicked = (button, data) =>
{
FlipX(relativeToSub: false);
foreach (MapEntity me in SelectedList)
{
me.FlipX(relativeToSub: false);
}
if (!SelectedList.Contains(this)) { FlipX(relativeToSub: false); }
return true;
}
};
@@ -136,7 +140,11 @@ namespace Barotrauma
ToolTip = TextManager.Get("MirrorEntityYToolTip"),
OnClicked = (button, data) =>
{
FlipY(relativeToSub: false);
foreach (MapEntity me in SelectedList)
{
me.FlipY(relativeToSub: false);
}
if (!SelectedList.Contains(this)) { FlipY(relativeToSub: false); }
return true;
}
};
@@ -153,7 +161,12 @@ namespace Barotrauma
{
OnClicked = (button, data) =>
{
Reset();
foreach (MapEntity me in SelectedList)
{
(me as Item)?.Reset();
(me as Structure)?.Reset();
}
if (!SelectedList.Contains(this)) { Reset(); }
CreateEditingHUD();
return true;
}
@@ -247,7 +260,7 @@ namespace Barotrauma
}
else if (HiddenInGame) { return; }
Color color = IsHighlighted ? GUI.Style.Orange : spriteColor;
Color color = IsIncludedInSelection && editing ? GUI.Style.Blue : IsHighlighted ? GUI.Style.Orange * Math.Max(spriteColor.A / (float) byte.MaxValue, 0.1f) : spriteColor;
if (IsSelected && editing)
{
@@ -114,7 +114,6 @@ namespace Barotrauma
if (realWorldDimensions != Vector2.Zero)
{
string dimensionsStr = TextManager.GetWithVariables("DimensionsFormat", new string[2] { "[width]", "[height]" }, new string[2] { ((int)realWorldDimensions.X).ToString(), ((int)realWorldDimensions.Y).ToString() });
var dimensionsText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), parent.Content.RectTransform),
TextManager.Get("Dimensions"), textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false };
@@ -124,6 +123,15 @@ namespace Barotrauma
dimensionsText.RectTransform.MinSize = new Point(0, dimensionsText.Children.First().Rect.Height);
}
string cargoCapacityStr = CargoCapacity < 0 ? TextManager.Get("unknown") : TextManager.GetWithVariables("cargocapacityformat", new string[1] { "[cratecount]" }, new string[1] {CargoCapacity.ToString() });
var cargoCapacityText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), parent.Content.RectTransform),
TextManager.Get("cargocapacity"), textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false };
new GUITextBlock(new RectTransform(new Vector2(rightPanelWidth, 0.0f), cargoCapacityText.RectTransform, Anchor.TopRight, Pivot.TopLeft),
cargoCapacityStr, textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false };
cargoCapacityText.RectTransform.MinSize = new Point(0, cargoCapacityText.Children.First().Rect.Height);
if (RecommendedCrewSizeMax > 0)
{
var crewSizeText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), parent.Content.RectTransform),
@@ -46,7 +46,10 @@ namespace Barotrauma
if (IsHighlighted || IsHighlighted) { clr = Color.Lerp(clr, Color.White, 0.8f); }
int iconSize = spawnType == SpawnType.Path ? WaypointSize : SpawnPointSize;
if (ConnectedDoor != null || Ladders != null || Stairs != null || SpawnType != SpawnType.Path) { iconSize = (int)(iconSize * 1.5f); }
if (ConnectedDoor != null || Ladders != null || Stairs != null || SpawnType != SpawnType.Path)
{
iconSize = (int)(iconSize * 1.5f);
}
if (IsSelected || IsHighlighted)
{
@@ -98,10 +101,32 @@ namespace Barotrauma
GUI.Style.Green * 0.5f, width: 1);
}
var color = Color.WhiteSmoke;
if (spawnType == SpawnType.Path)
{
if (linkedTo.Count < 2)
{
if (linkedTo.Count == 0)
{
color = Color.Red;
}
else
{
if (CurrentHull == null)
{
color = Ladders == null ? Color.Red : Color.Yellow;
}
else
{
color = Color.Yellow;
}
}
}
}
GUI.SmallFont.DrawString(spriteBatch,
ID.ToString(),
new Vector2(DrawPosition.X - 10, -DrawPosition.Y - 30),
Color.WhiteSmoke);
color);
}
public override bool IsMouseOn(Vector2 position)
@@ -53,100 +53,32 @@ namespace Barotrauma.Networking
case ChatMessageType.Default:
break;
case ChatMessageType.Order:
int orderIndex = msg.ReadByte();
UInt16 targetCharacterID = msg.ReadUInt16();
Character targetCharacter = Entity.FindEntityByID(targetCharacterID) as Character;
Entity targetEntity = Entity.FindEntityByID(msg.ReadUInt16());
Order orderPrefab = null;
int? optionIndex = null;
string orderOption = null;
// The option of a Dismiss order is written differently so we know what order we target
// now that the game supports multiple current orders simultaneously
if (orderIndex >= 0 && orderIndex < Order.PrefabList.Count)
{
orderPrefab = Order.PrefabList[orderIndex];
if (orderPrefab.Identifier != "dismissed")
{
optionIndex = msg.ReadByte();
}
// Does the dismiss order have a specified target?
else if (msg.ReadBoolean())
{
int identifierCount = msg.ReadByte();
if (identifierCount > 0)
{
int dismissedOrderIndex = msg.ReadByte();
Order dismissedOrderPrefab = null;
if (dismissedOrderIndex >= 0 && dismissedOrderIndex < Order.PrefabList.Count)
{
dismissedOrderPrefab = Order.PrefabList[dismissedOrderIndex];
orderOption = dismissedOrderPrefab.Identifier;
}
if (identifierCount > 1)
{
int dismissedOrderOptionIndex = msg.ReadByte();
if (dismissedOrderPrefab != null)
{
var options = dismissedOrderPrefab.Options;
if (options != null && dismissedOrderOptionIndex >= 0 && dismissedOrderOptionIndex < options.Length)
{
orderOption += $".{options[dismissedOrderOptionIndex]}";
}
}
}
}
}
}
else
{
optionIndex = msg.ReadByte();
}
int orderPriority = msg.ReadByte();
OrderTarget orderTargetPosition = null;
Order.OrderTargetType orderTargetType = (Order.OrderTargetType)msg.ReadByte();
int wallSectionIndex = 0;
if (msg.ReadBoolean())
{
var x = msg.ReadSingle();
var y = msg.ReadSingle();
var hull = Entity.FindEntityByID(msg.ReadUInt16()) as Hull;
orderTargetPosition = new OrderTarget(new Vector2(x, y), hull, creatingFromExistingData: true);
}
else if(orderTargetType == Order.OrderTargetType.WallSection)
{
wallSectionIndex = msg.ReadByte();
}
if (orderIndex < 0 || orderIndex >= Order.PrefabList.Count)
var orderMessageInfo = OrderChatMessage.ReadOrder(msg);
if (orderMessageInfo.OrderIndex < 0 || orderMessageInfo.OrderIndex >= Order.PrefabList.Count)
{
DebugConsole.ThrowError("Invalid order message - order index out of bounds.");
if (NetIdUtils.IdMoreRecent(id, LastID)) { LastID = id; }
return;
}
else
{
orderPrefab ??= Order.PrefabList[orderIndex];
}
orderOption ??= optionIndex.HasValue && optionIndex >= 0 && optionIndex < orderPrefab.Options.Length ? orderPrefab.Options[optionIndex.Value] : "";
txt = orderPrefab.GetChatMessage(targetCharacter?.Name, senderCharacter?.CurrentHull?.DisplayName, givingOrderToSelf: targetCharacter == senderCharacter, orderOption: orderOption);
var orderPrefab = orderMessageInfo.OrderPrefab ?? Order.PrefabList[orderMessageInfo.OrderIndex];
string orderOption = orderMessageInfo.OrderOption;
orderOption ??= orderMessageInfo.OrderOptionIndex.HasValue && orderMessageInfo.OrderOptionIndex >= 0 && orderMessageInfo.OrderOptionIndex < orderPrefab.Options.Length ?
orderPrefab.Options[orderMessageInfo.OrderOptionIndex.Value] : "";
txt = orderPrefab.GetChatMessage(orderMessageInfo.TargetCharacter?.Name, senderCharacter?.CurrentHull?.DisplayName, givingOrderToSelf: orderMessageInfo.TargetCharacter == senderCharacter, orderOption: orderOption);
if (GameMain.Client.GameStarted && Screen.Selected == GameMain.GameScreen)
{
Order order = null;
switch (orderTargetType)
switch (orderMessageInfo.TargetType)
{
case Order.OrderTargetType.Entity:
order = new Order(orderPrefab, targetEntity, orderPrefab.GetTargetItemComponent(targetEntity as Item), orderGiver: senderCharacter);
order = new Order(orderPrefab, orderMessageInfo.TargetEntity, orderPrefab.GetTargetItemComponent(orderMessageInfo.TargetEntity as Item), orderGiver: senderCharacter);
break;
case Order.OrderTargetType.Position:
order = new Order(orderPrefab, orderTargetPosition, orderGiver: senderCharacter);
order = new Order(orderPrefab, orderMessageInfo.TargetPosition, orderGiver: senderCharacter);
break;
case Order.OrderTargetType.WallSection:
order = new Order(orderPrefab, targetEntity as Structure, wallSectionIndex, orderGiver: senderCharacter);
order = new Order(orderPrefab, orderMessageInfo.TargetEntity as Structure, orderMessageInfo.WallSectionIndex, orderGiver: senderCharacter);
break;
}
@@ -157,9 +89,9 @@ namespace Barotrauma.Networking
var fadeOutTime = !orderPrefab.IsIgnoreOrder ? (float?)orderPrefab.FadeOutTime : null;
GameMain.GameSession?.CrewManager?.AddOrder(order, fadeOutTime);
}
else if (targetCharacter != null)
else if (orderMessageInfo.TargetCharacter != null)
{
targetCharacter.SetOrder(order, orderOption, orderPriority, senderCharacter);
orderMessageInfo.TargetCharacter.SetOrder(order, orderOption, orderMessageInfo.Priority, senderCharacter);
}
}
}
@@ -167,7 +99,7 @@ namespace Barotrauma.Networking
if (NetIdUtils.IdMoreRecent(id, LastID))
{
GameMain.Client.AddChatMessage(
new OrderChatMessage(orderPrefab, orderOption, orderPriority, txt, orderTargetPosition ?? targetEntity as ISpatialEntity, targetCharacter, senderCharacter));
new OrderChatMessage(orderPrefab, orderOption, orderMessageInfo.Priority, txt, orderMessageInfo.TargetPosition ?? orderMessageInfo.TargetEntity as ISpatialEntity, orderMessageInfo.TargetCharacter, senderCharacter));
LastID = id;
}
return;
@@ -1046,6 +1046,11 @@ namespace Barotrauma.Networking
mission.ClientReadInitial(inc);
}
if (inc.ReadBoolean())
{
CrewManager.ClientReadActiveOrders(inc);
}
roundInitStatus = RoundInitStatus.Started;
}
@@ -1609,6 +1614,9 @@ namespace Barotrauma.Networking
DateTime? timeOut = null;
DateTime requestFinalizeTime = DateTime.Now;
TimeSpan requestFinalizeInterval = new TimeSpan(0, 0, 2);
IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ClientPacketHeader.REQUEST_STARTGAMEFINALIZE);
clientPeer.Send(msg, DeliveryMethod.Unreliable);
while (true)
{
@@ -1618,7 +1626,7 @@ namespace Barotrauma.Networking
{
if (DateTime.Now > requestFinalizeTime)
{
IWriteMessage msg = new WriteOnlyMessage();
msg = new WriteOnlyMessage();
msg.Write((byte)ClientPacketHeader.REQUEST_STARTGAMEFINALIZE);
clientPeer.Send(msg, DeliveryMethod.Unreliable);
requestFinalizeTime = DateTime.Now + requestFinalizeInterval;
@@ -1648,12 +1656,11 @@ namespace Barotrauma.Networking
break;
}
if (roundInitStatus != RoundInitStatus.WaitingForStartGameFinalize)
{
break;
}
if (roundInitStatus != RoundInitStatus.WaitingForStartGameFinalize) { break; }
clientPeer.Update((float)Timing.Step);
if (roundInitStatus != RoundInitStatus.WaitingForStartGameFinalize) { break; }
}
catch (Exception e)
{
@@ -3289,16 +3296,24 @@ namespace Barotrauma.Networking
{
string respawnText = string.Empty;
Color textColor = Color.White;
bool canChooseRespawn =
GameMain.GameSession.GameMode is CampaignMode &&
Character.Controlled == null &&
bool canChooseRespawn =
GameMain.GameSession.GameMode is CampaignMode &&
Character.Controlled == null &&
Level.Loaded?.Type != LevelData.LevelType.Outpost &&
(characterInfo == null || HasSpawned);
if (respawnManager.CurrentState == RespawnManager.State.Waiting &&
respawnManager.RespawnCountdownStarted)
if (respawnManager.CurrentState == RespawnManager.State.Waiting)
{
float timeLeft = (float)(respawnManager.RespawnTime - DateTime.Now).TotalSeconds;
respawnText = TextManager.GetWithVariable(respawnManager.UsingShuttle ? "RespawnShuttleDispatching" : "RespawningIn", "[time]", ToolBox.SecondsToReadableTime(timeLeft));
if (respawnManager.RespawnCountdownStarted)
{
float timeLeft = (float)(respawnManager.RespawnTime - DateTime.Now).TotalSeconds;
respawnText = TextManager.GetWithVariable(respawnManager.UsingShuttle ? "RespawnShuttleDispatching" : "RespawningIn", "[time]", ToolBox.SecondsToReadableTime(timeLeft));
}
else if (respawnManager.PendingRespawnCount > 0)
{
respawnText = TextManager.GetWithVariables("RespawnWaitingForMoreDeadPlayers",
new string[] { "[deadplayers]", "[requireddeadplayers]" },
new string[] { respawnManager.PendingRespawnCount.ToString(), respawnManager.RequiredRespawnCount.ToString() });
}
}
else if (respawnManager.CurrentState == RespawnManager.State.Transporting &&
respawnManager.ReturnCountdownStarted)
@@ -1,6 +1,4 @@
using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System;
namespace Barotrauma.Networking
{
@@ -8,6 +6,17 @@ namespace Barotrauma.Networking
{
private DateTime lastShuttleLeavingWarningTime;
public int PendingRespawnCount
{
get; private set;
}
public int RequiredRespawnCount
{
get; private set;
}
partial void UpdateTransportingProjSpecific(float deltaTime)
{
if (GameMain.Client?.Character == null || GameMain.Client.Character.Submarine != RespawnShuttle) { return; }
@@ -41,6 +50,8 @@ namespace Barotrauma.Networking
}
break;
case State.Waiting:
PendingRespawnCount = msg.ReadUInt16();
RequiredRespawnCount = msg.ReadUInt16();
RespawnCountdownStarted = msg.ReadBoolean();
ResetShuttle();
float newRespawnTime = msg.ReadSingle();
@@ -745,6 +745,10 @@ namespace Barotrauma.Networking
TextManager.Get("ServerSettingsAllowRewiring"));
GetPropertyData("AllowRewiring").AssignGUIComponent(allowRewiring);
var allowWifiChatter = new GUITickBox(new RectTransform(new Vector2(0.48f, 0.05f), tickBoxContainer.Content.RectTransform),
TextManager.Get("ServerSettingsAllowWifiChat"));
GetPropertyData("AllowLinkingWifiToChat").AssignGUIComponent(allowWifiChatter);
var allowDisguises = new GUITickBox(new RectTransform(new Vector2(0.48f, 0.05f), tickBoxContainer.Content.RectTransform),
TextManager.Get("ServerSettingsAllowDisguises"));
GetPropertyData("AllowDisguises").AssignGUIComponent(allowDisguises);
@@ -66,7 +66,7 @@ namespace Barotrauma.Particles
public Vector4 ColorMultiplier;
public bool DrawOnTop { get; private set; }
public ParticlePrefab.DrawTargetType DrawTarget
{
get { return prefab.DrawTarget; }
@@ -103,8 +103,7 @@ namespace Barotrauma.Particles
{
return debugName;
}
public void Init(ParticlePrefab prefab, Vector2 position, Vector2 speed, float rotation, Hull hullGuess = null, bool drawOnTop = false, float collisionIgnoreTimer = 0f)
public void Init(ParticlePrefab prefab, Vector2 position, Vector2 speed, float rotation, Hull hullGuess = null, bool drawOnTop = false, float collisionIgnoreTimer = 0f, Tuple<Vector2, Vector2> tracerPoints = null)
{
this.prefab = prefab;
debugName = $"Particle ({prefab.Name})";
@@ -118,6 +117,16 @@ namespace Barotrauma.Particles
currentHull = Hull.FindHull(position, hullGuess);
size = prefab.StartSizeMin + (prefab.StartSizeMax - prefab.StartSizeMin) * Rand.Range(0.0f, 1.0f);
if (tracerPoints != null)
{
size = new Vector2(Vector2.Distance(tracerPoints.Item1, tracerPoints.Item2), size.Y);
position = (tracerPoints.Item1 + tracerPoints.Item2) / 2;
}
sizeChange = prefab.SizeChangeMin + (prefab.SizeChangeMax - prefab.SizeChangeMin) * Rand.Range(0.0f, 1.0f);
this.position = position;
prevPosition = position;
@@ -138,10 +147,6 @@ namespace Barotrauma.Particles
totalLifeTime = prefab.LifeTime;
lifeTime = prefab.LifeTime;
startDelay = Rand.Range(prefab.StartDelayMin, prefab.StartDelayMax);
size = prefab.StartSizeMin + (prefab.StartSizeMax - prefab.StartSizeMin) * Rand.Range(0.0f, 1.0f);
sizeChange = prefab.SizeChangeMin + (prefab.SizeChangeMax - prefab.SizeChangeMin) * Rand.Range(0.0f, 1.0f);
color = prefab.StartColor;
changeColor = prefab.StartColor != prefab.EndColor;
@@ -1,10 +1,21 @@
using Microsoft.Xna.Framework;
using System;
using System.Linq;
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma.Particles
{
class ParticleEmitterProperties : ISerializableEntity
{
public string Name => nameof(ParticleEmitterProperties);
public Dictionary<string, SerializableProperty> SerializableProperties { get; }
public ParticleEmitterProperties(XElement element)
{
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
}
}
class ParticleEmitter
{
private float emitTimer;
@@ -23,7 +34,7 @@ namespace Barotrauma.Particles
Prefab = prefab;
}
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)
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)
{
emitTimer += deltaTime * amountMultiplier;
burstEmitTimer -= deltaTime;
@@ -33,7 +44,7 @@ namespace Barotrauma.Particles
float emitInterval = 1.0f / Prefab.ParticlesPerSecond;
while (emitTimer > emitInterval)
{
Emit(position, hullGuess, angle, particleRotation, velocityMultiplier, sizeMultiplier, colorMultiplier, overrideParticle);
Emit(position, hullGuess, angle, particleRotation, velocityMultiplier, sizeMultiplier, colorMultiplier, overrideParticle, tracerPoints: tracerPoints);
emitTimer -= emitInterval;
}
}
@@ -43,11 +54,11 @@ namespace Barotrauma.Particles
burstEmitTimer = Prefab.EmitInterval;
for (int i = 0; i < Prefab.ParticleAmount * amountMultiplier; i++)
{
Emit(position, hullGuess, angle, particleRotation, velocityMultiplier, sizeMultiplier, colorMultiplier, overrideParticle);
Emit(position, hullGuess, angle, particleRotation, velocityMultiplier, sizeMultiplier, colorMultiplier, overrideParticle, tracerPoints: tracerPoints);
}
}
private void Emit(Vector2 position, Hull hullGuess, float angle, float particleRotation, float velocityMultiplier, float sizeMultiplier, Color? colorMultiplier = null, ParticlePrefab overrideParticle = null)
private void Emit(Vector2 position, Hull hullGuess, float angle, float particleRotation, float velocityMultiplier, float sizeMultiplier, Color? colorMultiplier = null, ParticlePrefab overrideParticle = null, Tuple<Vector2, Vector2> tracerPoints = null)
{
angle += Rand.Range(Prefab.AngleMin, Prefab.AngleMax);
@@ -55,11 +66,12 @@ namespace Barotrauma.Particles
Vector2 velocity = dir * Rand.Range(Prefab.VelocityMin, Prefab.VelocityMax) * velocityMultiplier;
position += dir * Rand.Range(Prefab.DistanceMin, Prefab.DistanceMax);
var particle = GameMain.ParticleManager.CreateParticle(overrideParticle ?? Prefab.ParticlePrefab, position, velocity, particleRotation, hullGuess, Prefab.DrawOnTop);
var particle = GameMain.ParticleManager.CreateParticle(overrideParticle ?? Prefab.ParticlePrefab, position, velocity, particleRotation, hullGuess, Prefab.DrawOnTop, tracerPoints: tracerPoints);
if (particle != null)
{
particle.Size *= Rand.Range(Prefab.ScaleMin, Prefab.ScaleMax) * sizeMultiplier;
particle.Size *= Prefab.ScaleMultiplier;
particle.HighQualityCollisionDetection = Prefab.HighQualityCollisionDetection;
if (colorMultiplier.HasValue)
{
@@ -135,6 +147,7 @@ namespace Barotrauma.Particles
public readonly float VelocityMin, VelocityMax;
public readonly float ScaleMin, ScaleMax;
public readonly Vector2 ScaleMultiplier;
public readonly float EmitInterval;
public readonly int ParticleAmount;
@@ -179,6 +192,7 @@ namespace Barotrauma.Particles
ScaleMin = element.GetAttributeFloat("scalemin", 1.0f);
ScaleMax = Math.Max(ScaleMin, element.GetAttributeFloat("scalemax", 1.0f));
}
ScaleMultiplier = element.GetAttributeVector2("scalemultiplier", Vector2.One);
if (element.Attribute("distance") == null)
{
@@ -114,13 +114,12 @@ namespace Barotrauma.Particles
{
Prefabs.RemoveByFile(configFile);
}
public Particle CreateParticle(string prefabName, Vector2 position, float angle, float speed, Hull hullGuess = null, float collisionIgnoreTimer = 0f)
public Particle CreateParticle(string prefabName, Vector2 position, float angle, float speed, Hull hullGuess = null, float collisionIgnoreTimer = 0f, Tuple<Vector2, Vector2> tracerPoints = null)
{
return CreateParticle(prefabName, position, new Vector2((float)Math.Cos(angle), (float)-Math.Sin(angle)) * speed, angle, hullGuess, collisionIgnoreTimer);
return CreateParticle(prefabName, position, new Vector2((float)Math.Cos(angle), (float)-Math.Sin(angle)) * speed, angle, hullGuess, collisionIgnoreTimer, tracerPoints: tracerPoints);
}
public Particle CreateParticle(string prefabName, Vector2 position, Vector2 velocity, float rotation = 0.0f, Hull hullGuess = null, float collisionIgnoreTimer = 0f)
public Particle CreateParticle(string prefabName, Vector2 position, Vector2 velocity, float rotation = 0.0f, Hull hullGuess = null, float collisionIgnoreTimer = 0f, Tuple<Vector2, Vector2> tracerPoints = null)
{
ParticlePrefab prefab = FindPrefab(prefabName);
@@ -129,27 +128,30 @@ namespace Barotrauma.Particles
DebugConsole.ThrowError("Particle prefab \"" + prefabName + "\" not found!");
return null;
}
return CreateParticle(prefab, position, velocity, rotation, hullGuess, collisionIgnoreTimer: collisionIgnoreTimer);
return CreateParticle(prefab, position, velocity, rotation, hullGuess, collisionIgnoreTimer: collisionIgnoreTimer, tracerPoints:tracerPoints);
}
public Particle CreateParticle(ParticlePrefab prefab, Vector2 position, Vector2 velocity, float rotation = 0.0f, Hull hullGuess = null, bool drawOnTop = false, float collisionIgnoreTimer = 0f)
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; }
// this should be optimized for tracers after prototyping
if (tracerPoints == null)
{
Vector2 particleEndPos = prefab.CalculateEndPosition(position, velocity);
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));
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));
Rectangle expandedViewRect = MathUtils.ExpandRect(cam.WorldView, MaxOutOfViewDist);
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 (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();
particles[particleCount].Init(prefab, position, velocity, rotation, hullGuess, drawOnTop, collisionIgnoreTimer);
particles[particleCount].Init(prefab, position, velocity, rotation, hullGuess, drawOnTop, collisionIgnoreTimer, tracerPoints: tracerPoints);
particleCount++;
@@ -443,7 +443,7 @@ namespace Barotrauma
public static bool KeyHit(Keys button)
{
return (AllowInput && oldKeyboardState.IsKeyDown(button) && keyboardState.IsKeyUp(button));
return AllowInput && oldKeyboardState.IsKeyUp(button) && keyboardState.IsKeyDown(button);
}
public static bool InventoryKeyHit(int index)
@@ -454,7 +454,7 @@ namespace Barotrauma
public static bool KeyDown(Keys button)
{
return (AllowInput && keyboardState.IsKeyDown(button));
return AllowInput && keyboardState.IsKeyDown(button);
}
public static bool KeyUp(Keys button)
@@ -505,7 +505,7 @@ namespace Barotrauma
missionName.CalculateHeightFromText();
}
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), mission.GetMissionRewardText(), wrap: true, parseRichText: true);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), mission.GetMissionRewardText(Submarine.MainSub), wrap: true, parseRichText: true);
string reputationText = mission.GetReputationRewardText(mission.Locations[0]);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), reputationText, wrap: true, parseRichText: true);
@@ -517,8 +517,9 @@ namespace Barotrauma
{
var textBlock = child as GUITextBlock;
textBlock.Color = textBlock.SelectedColor = textBlock.HoverColor = Color.Transparent;
textBlock.HoverTextColor = textBlock.TextColor;
textBlock.SelectedTextColor = textBlock.TextColor;
textBlock.TextColor *= 0.5f;
textBlock.HoverTextColor = textBlock.TextColor;
}
missionPanel.OnAddedToGUIUpdateList = (c) =>
{
@@ -4731,7 +4731,7 @@ namespace Barotrauma.CharacterEditor
rotation: 0,
origin: orig,
sourceRectangle: wearable.InheritSourceRect ? limb.ActiveSprite.SourceRect : wearable.Sprite.SourceRect,
scale: (wearable.InheritTextureScale ? 1 : 1 / RagdollParams.TextureScale) * spriteSheetZoom,
scale: (wearable.InheritTextureScale ? 1 : wearable.Scale / RagdollParams.TextureScale) * spriteSheetZoom,
effects: SpriteEffects.None,
color: Color.White,
layerDepth: 0);
@@ -305,7 +305,7 @@ namespace Barotrauma.CharacterEditor
new GUITextBlock(new RectTransform(new Vector2(0.3f, 1), mainElement.RectTransform, Anchor.CenterLeft), TextManager.Get("ContentPackage"));
var rightContainer = new GUIFrame(new RectTransform(new Vector2(0.7f, 1), mainElement.RectTransform, Anchor.CenterRight), style: null);
contentPackageDropDown = new GUIDropDown(new RectTransform(new Vector2(1.0f, 0.5f), rightContainer.RectTransform, Anchor.TopRight));
foreach (ContentPackage cp in ContentPackage.AllPackages)
foreach (ContentPackage cp in GameMain.Config.AllEnabledPackages)
{
#if !DEBUG
if (cp == GameMain.VanillaContent) { continue; }
@@ -0,0 +1,57 @@
using Microsoft.Xna.Framework;
namespace Barotrauma
{
class EditorScreen : Screen
{
public static Color BackgroundColor = GameSettings.SubEditorBackgroundColor;
public void CreateBackgroundColorPicker()
{
var msgBox = new GUIMessageBox(TextManager.Get("CharacterEditor.EditBackgroundColor"), "", new[] { TextManager.Get("Reset"), TextManager.Get("OK")}, new Vector2(0.2f, 0.175f), minSize: new Point(300, 175));
var rgbLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.25f), msgBox.Content.RectTransform), isHorizontal: true);
// Generate R,G,B labels and parent elements
var layoutParents = new GUILayoutGroup[3];
for (int i = 0; i < 3; i++)
{
var colorContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.33f, 1), rgbLayout.RectTransform), isHorizontal: true) { Stretch = true };
new GUITextBlock(new RectTransform(new Vector2(0.2f, 1), colorContainer.RectTransform, Anchor.CenterLeft) { MinSize = new Point(15, 0) }, GUI.colorComponentLabels[i], font: GUI.SmallFont, textAlignment: Alignment.Center);
layoutParents[i] = colorContainer;
}
// attach number inputs to our generated parent elements
var rInput = new GUINumberInput(new RectTransform(new Vector2(0.7f, 1f), layoutParents[0].RectTransform), GUINumberInput.NumberType.Int) { IntValue = BackgroundColor.R };
var gInput = new GUINumberInput(new RectTransform(new Vector2(0.7f, 1f), layoutParents[1].RectTransform), GUINumberInput.NumberType.Int) { IntValue = BackgroundColor.G };
var bInput = new GUINumberInput(new RectTransform(new Vector2(0.7f, 1f), layoutParents[2].RectTransform), GUINumberInput.NumberType.Int) { IntValue = BackgroundColor.B };
rInput.MinValueInt = gInput.MinValueInt = bInput.MinValueInt = 0;
rInput.MaxValueInt = gInput.MaxValueInt = bInput.MaxValueInt = 255;
rInput.OnValueChanged = gInput.OnValueChanged = bInput.OnValueChanged = delegate
{
var color = new Color(rInput.IntValue, gInput.IntValue, bInput.IntValue);
BackgroundColor = color;
GameSettings.SubEditorBackgroundColor = color;
};
// Reset button
msgBox.Buttons[0].OnClicked = (button, o) =>
{
rInput.IntValue = 13;
gInput.IntValue = 37;
bInput.IntValue = 69;
return true;
};
// Ok button
msgBox.Buttons[1].OnClicked = (button, o) =>
{
msgBox.Close();
GameMain.Config.SaveNewPlayerConfig();
return true;
};
}
}
}
@@ -227,7 +227,7 @@ namespace Barotrauma
public static GUIMessageBox AskForConfirmation(string header, string body, Func<bool> onConfirm)
{
string[] buttons = { TextManager.Get("Ok"), TextManager.Get("Cancel") };
GUIMessageBox msgBox = new GUIMessageBox(header, body, buttons, new Vector2(0.2f, 0.175f), minSize: new Point(300, 175));
GUIMessageBox msgBox = new GUIMessageBox(header, body, buttons);
// Cancel button
msgBox.Buttons[1].OnClicked = delegate
@@ -364,13 +364,15 @@ namespace Barotrauma
Quad.Render();
}
float grainStrength = Character.Controlled?.GrainStrength ?? 0;
if (grainStrength > 0)
if (Character.Controlled is { } character)
{
float grainStrength = character.GrainStrength;
Rectangle screenRect = new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight);
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, effect: GrainEffect);
GUI.DrawRectangle(spriteBatch, screenRect, Color.White * grainStrength, isFilled: true);
GUI.DrawRectangle(spriteBatch, screenRect, Color.White, isFilled: true);
GrainEffect.Parameters["seed"].SetValue(Rand.Range(0f, 1f, Rand.RandSync.Unsynced));
GrainEffect.Parameters["intensity"].SetValue(grainStrength);
GrainEffect.Parameters["grainColor"].SetValue(character.GrainColor.ToVector4());
spriteBatch.End();
}
@@ -929,7 +929,7 @@ namespace Barotrauma
}
};
QuitCampaignButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.3f), campaignContent.RectTransform),
TextManager.Get("pausemenusavequit"), textAlignment: Alignment.Center)
TextManager.Get("quitbutton"), textAlignment: Alignment.Center)
{
OnClicked = (_, __) =>
{
@@ -1322,7 +1322,7 @@ namespace Barotrauma
roundControlsHolder.Children.ForEach(c => c.IgnoreLayoutGroups = !c.Visible);
roundControlsHolder.Recalculate();
ReadyToStartBox.Parent.Visible = !GameMain.Client.GameStarted && SelectedMode != GameModePreset.MultiPlayerCampaign;
ReadyToStartBox.Parent.Visible = !GameMain.Client.GameStarted;
RefreshGameModeContent();
}
@@ -3269,7 +3269,7 @@ namespace Barotrauma
CampaignFrame.Visible = CampaignSetupFrame.Visible = false;
}
ReadyToStartBox.Parent.Visible = !GameMain.Client.GameStarted && SelectedMode != GameModePreset.MultiPlayerCampaign;
ReadyToStartBox.Parent.Visible = !GameMain.Client.GameStarted;
StartButton.Visible =
GameMain.Client.HasPermission(ClientPermissions.ManageRound) &&
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Xml.Linq;
using System.Text;
using Barotrauma.Extensions;
using FarseerPhysics;
#if DEBUG
using System.IO;
using System.Xml;
@@ -15,7 +16,7 @@ using Barotrauma.IO;
namespace Barotrauma
{
class ParticleEditorScreen : Screen
class ParticleEditorScreen : EditorScreen
{
class Emitter : ISerializableEntity
{
@@ -23,22 +24,22 @@ namespace Barotrauma
public float BurstTimer;
[Editable(), Serialize("0.0,360.0", false)]
[Editable, Serialize("0.0,360.0", false)]
public Vector2 AngleRange { get; private set; }
[Editable(), Serialize("0.0,0.0", false)]
[Editable, Serialize("0.0,0.0", false)]
public Vector2 VelocityRange { get; private set; }
[Editable(), Serialize("1.0,1.0", false)]
[Editable, Serialize("1.0,1.0", false)]
public Vector2 ScaleRange { get; private set; }
[Editable(), Serialize(0, false)]
[Editable, Serialize(0, false)]
public int ParticleBurstAmount { get; private set; }
[Editable(), Serialize(1.0f, false)]
[Editable, Serialize(1.0f, false)]
public float ParticleBurstInterval { get; private set; }
[Editable(), Serialize(1.0f, false)]
[Editable, Serialize(1.0f, false)]
public float ParticlesPerSecond { get; private set; }
public string Name
@@ -77,19 +78,24 @@ namespace Barotrauma
private readonly Camera cam;
public override Camera Cam
{
get
{
return cam;
}
}
public override Camera Cam => cam;
private const string sizeRefFilePath = "Content/size_reference.png";
private readonly Texture2D sizeReference;
private Vector2 sizeRefPosition = Vector2.Zero;
private readonly Vector2 sizeRefOrigin;
private bool sizeRefEnabled;
public ParticleEditorScreen()
{
cam = new Camera();
GameMain.Instance.ResolutionChanged += CreateUI;
CreateUI();
if (File.Exists(sizeRefFilePath))
{
sizeReference = TextureLoader.FromFile(sizeRefFilePath, compress: false);
sizeRefOrigin = new Vector2(sizeReference.Width / 2f, sizeReference.Height / 2f);
}
}
private void CreateUI()
@@ -314,7 +320,17 @@ namespace Barotrauma
public override void Update(double deltaTime)
{
cam.MoveCamera((float)deltaTime, allowMove: true, allowZoom: GUI.MouseOn == null);
if (GUI.MouseOn is null && PlayerInput.PrimaryMouseButtonHeld())
{
sizeRefPosition = cam.ScreenToWorld(PlayerInput.MousePosition);
}
if (PlayerInput.SecondaryMouseButtonClicked())
{
CreateContextMenu();
}
if (selectedPrefab != null)
{
emitter.EmitTimer += (float)deltaTime;
@@ -345,6 +361,15 @@ namespace Barotrauma
GameMain.ParticleManager.Update((float)deltaTime);
}
private void CreateContextMenu()
{
GUIContextMenu.CreateContextMenu
(
new ContextMenuOption("subeditor.editbackgroundcolor", true, CreateBackgroundColorPicker),
new ContextMenuOption("editor.togglereferencecharacter", true, delegate { sizeRefEnabled = !sizeRefEnabled; })
);
}
public override void Draw(double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch)
{
cam.UpdateTransform();
@@ -357,7 +382,7 @@ namespace Barotrauma
null, null, null, null,
cam.Transform);
graphics.Clear(new Color(0.051f, 0.149f, 0.271f, 1.0f));
graphics.Clear(BackgroundColor);
GameMain.ParticleManager.Draw(spriteBatch, false, false, ParticleBlendState.AlphaBlend);
GameMain.ParticleManager.Draw(spriteBatch, true, false, ParticleBlendState.AlphaBlend);
@@ -374,6 +399,20 @@ namespace Barotrauma
spriteBatch.End();
if (sizeRefEnabled && !(sizeReference is null))
{
spriteBatch.Begin(SpriteSortMode.Deferred,
BlendState.NonPremultiplied,
null, null, null, null,
cam.Transform);
Vector2 pos = sizeRefPosition;
pos.Y = -pos.Y;
spriteBatch.Draw(sizeReference, pos, null, Color.White, 0f, sizeRefOrigin, new Vector2(0.4f), SpriteEffects.None, 0f);
spriteBatch.End();
}
//-------------------------------------------------------
spriteBatch.Begin(SpriteSortMode.Deferred, null, GUI.SamplerState, null, GameMain.ScissorTestEnable);
@@ -66,6 +66,8 @@ namespace Barotrauma
yield return CoroutineStatus.Success;
}
public virtual void OnFileDropped(string filePath, string extension) { }
public virtual void Release()
{
frame.RectTransform.Parent = null;
@@ -363,7 +363,9 @@ namespace Barotrauma
"VineSprite",
"LeafSprite",
"FlowerSprite",
"DecorativeSprite"
"DecorativeSprite",
"BarrelSprite",
"RailSprite"
};
foreach (string spriteElementName in spriteElementNames)
@@ -26,6 +26,8 @@ namespace Barotrauma
//listbox that shows the files included in the item being created
private GUIListBox createItemFileList;
private GUIImage previewIcon;
private System.IO.FileSystemWatcher createItemWatcher;
private readonly List<GUIButton> tabButtons = new List<GUIButton>();
@@ -277,6 +279,24 @@ namespace Barotrauma
SelectTab(Tab.Mods);
}
public override void OnFileDropped(string filePath, string extension)
{
switch (extension)
{
case ".png": // workshop preview
case ".jpg":
case ".jpeg":
if (previewIcon == null || itemContentPackage == null) { break; }
OnPreviewImageSelected(previewIcon, filePath);
break;
default:
DebugConsole.ThrowError($"Could not drag and drop the file. \"{extension}\" is not a valid file extension! (expected .png, .jpg or .jpeg)");
break;
}
}
private void OnItemInstalled(ulong itemId)
{
RefreshSubscribedItems();
@@ -1263,7 +1283,7 @@ namespace Barotrauma
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), topLeftColumn.RectTransform), TextManager.Get("WorkshopItemPreviewImage"), font: GUI.SubHeadingFont);
var previewIcon = new GUIImage(new RectTransform(new Vector2(1.0f, 0.7f), topLeftColumn.RectTransform), SteamManager.DefaultPreviewImage, scaleToFit: true);
previewIcon = new GUIImage(new RectTransform(new Vector2(1.0f, 0.7f), topLeftColumn.RectTransform), SteamManager.DefaultPreviewImage, scaleToFit: true);
new GUIButton(new RectTransform(new Vector2(1.0f, 0.2f), topLeftColumn.RectTransform), TextManager.Get("WorkshopItemBrowse"), style: "GUIButtonSmall")
{
OnClicked = (btn, userdata) =>
@@ -19,7 +19,7 @@ using Barotrauma.IO;
namespace Barotrauma
{
class SubEditorScreen : Screen
class SubEditorScreen : EditorScreen
{
private static readonly string[] crewExperienceLevels =
{
@@ -177,8 +177,6 @@ namespace Barotrauma
private Mode mode;
private Color backgroundColor = GameSettings.SubEditorBackgroundColor;
private Vector2 MeasurePositionStart = Vector2.Zero;
// Prevent the mode from changing
@@ -1007,6 +1005,12 @@ namespace Barotrauma
string name = legacy ? TextManager.GetWithVariable("legacyitemformat", "[name]", ep.Name) : ep.Name;
frame.ToolTip = string.IsNullOrEmpty(ep.Description) ? name : name + '\n' + ep.Description;
if (ep.ContentPackage != GameMain.VanillaContent && ep.ContentPackage != null)
{
frame.Color = Color.Magenta;
string colorStr = XMLExtensions.ColorToString(Color.MediumPurple);
frame.ToolTip += $"\n‖color:{colorStr}‖{ep.ContentPackage?.Name}‖color:end‖";
}
if (ep.HideInMenus)
{
frame.Color = Color.Red;
@@ -1225,6 +1229,50 @@ namespace Barotrauma
}
}
public override void OnFileDropped(string filePath, string extension)
{
switch (extension)
{
case ".sub": // Submarine
SubmarineInfo info = new SubmarineInfo(filePath);
if (info.IsFileCorrupted)
{
DebugConsole.ThrowError($"Could not drag and drop the file. File \"{filePath}\" is corrupted!");
info.Dispose();
return;
}
string body = TextManager.GetWithVariable("SubEditor.LoadConfirmBody", "[submarine]", info.Name);
GUI.AskForConfirmation(TextManager.Get("Load"), body, onConfirm: () => LoadSub(info), onDeny: () => info.Dispose());
break;
case ".xml": // Item Assembly
string text = File.ReadAllText(filePath);
// PlayerInput.MousePosition doesn't update while the window is not active so we need to use this method
Vector2 mousePos = Mouse.GetState().Position.ToVector2();
PasteAssembly(text, cam.ScreenToWorld(mousePos));
break;
case ".png": // submarine preview
case ".jpg":
case ".jpeg":
if (saveFrame == null) { break; }
Texture2D texture = Sprite.LoadTexture(filePath);
previewImage.Sprite = new Sprite(texture, null, null);
if (Submarine.MainSub != null)
{
Submarine.MainSub.Info.PreviewImage = previewImage.Sprite;
}
break;
default:
DebugConsole.ThrowError($"Could not drag and drop the file. \"{extension}\" is not a valid file extension! (expected .xml, .sub, .png or .jpg)");
break;
}
}
/// <summary>
/// Coroutine that waits 5 minutes and then runs itself recursively again to save the submarine into a temporary file
/// </summary>
@@ -2020,30 +2068,27 @@ namespace Barotrauma
Submarine.MainSub.Info.Price = Math.Max(Submarine.MainSub.Info.Price, basePrice);
}
if (!Submarine.MainSub.Info.HasTag(SubmarineTag.Shuttle))
var classGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), subSettingsContainer.RectTransform), isHorizontal: true)
{
var classGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), subSettingsContainer.RectTransform), isHorizontal: true)
{
Stretch = true
};
new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), classGroup.RectTransform),
TextManager.Get("submarineclass"), textAlignment: Alignment.CenterLeft, wrap: true);
GUIDropDown classDropDown = new GUIDropDown(new RectTransform(new Vector2(0.4f, 1.0f), classGroup.RectTransform));
classDropDown.RectTransform.MinSize = new Point(0, subTypeContainer.RectTransform.Children.Max(c => c.MinSize.Y));
classDropDown.AddItem(TextManager.Get("submarineclass.undefined"), SubmarineClass.Undefined);
classDropDown.AddItem(TextManager.Get("submarineclass.scout"), SubmarineClass.Scout);
classDropDown.AddItem(TextManager.Get("submarineclass.attack"), SubmarineClass.Attack);
classDropDown.AddItem(TextManager.Get("submarineclass.transport"), SubmarineClass.Transport);
classDropDown.AddItem(TextManager.Get("submarineclass.deepdiver"), SubmarineClass.DeepDiver);
classDropDown.OnSelected += (selected, userdata) =>
{
SubmarineClass submarineClass = (SubmarineClass)userdata;
Submarine.MainSub.Info.SubmarineClass = submarineClass;
return true;
};
classDropDown.SelectItem(Submarine.MainSub.Info.SubmarineClass);
}
Stretch = true
};
var classText = new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), classGroup.RectTransform),
TextManager.Get("submarineclass"), textAlignment: Alignment.CenterLeft, wrap: true);
GUIDropDown classDropDown = new GUIDropDown(new RectTransform(new Vector2(0.4f, 1.0f), classGroup.RectTransform));
classDropDown.RectTransform.MinSize = new Point(0, subTypeContainer.RectTransform.Children.Max(c => c.MinSize.Y));
classDropDown.AddItem(TextManager.Get("submarineclass.undefined"), SubmarineClass.Undefined);
classDropDown.AddItem(TextManager.Get("submarineclass.scout"), SubmarineClass.Scout);
classDropDown.AddItem(TextManager.Get("submarineclass.attack"), SubmarineClass.Attack);
classDropDown.AddItem(TextManager.Get("submarineclass.transport"), SubmarineClass.Transport);
classDropDown.AddItem(TextManager.Get("submarineclass.deepdiver"), SubmarineClass.DeepDiver);
classDropDown.OnSelected += (selected, userdata) =>
{
SubmarineClass submarineClass = (SubmarineClass)userdata;
Submarine.MainSub.Info.SubmarineClass = submarineClass;
return true;
};
classDropDown.SelectItem(Submarine.MainSub.Info.SubmarineClass);
classText.Enabled = classDropDown.ButtonEnabled = !Submarine.MainSub.Info.HasTag(SubmarineTag.Shuttle);
var crewSizeArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), subSettingsContainer.RectTransform), isHorizontal: true)
{
@@ -2216,17 +2261,29 @@ namespace Barotrauma
{
Selected = Submarine.MainSub != null && Submarine.MainSub.Info.HasTag(tag),
UserData = tag,
OnSelected = (GUITickBox tickBox) =>
{
if (Submarine.MainSub == null) return false;
SubmarineTag tag = (SubmarineTag)tickBox.UserData;
if (tag == SubmarineTag.Shuttle)
{
if (tickBox.Selected)
{
classDropDown.SelectItem(SubmarineClass.Undefined);
}
else
{
classDropDown.SelectItem(Submarine.MainSub.Info.SubmarineClass);
}
classText.Enabled = classDropDown.ButtonEnabled = !tickBox.Selected;
}
if (tickBox.Selected)
{
Submarine.MainSub.Info.AddTag((SubmarineTag)tickBox.UserData);
Submarine.MainSub.Info.AddTag(tag);
}
else
{
Submarine.MainSub.Info.RemoveTag((SubmarineTag)tickBox.UserData);
Submarine.MainSub.Info.RemoveTag(tag);
}
return true;
}
@@ -2305,7 +2362,6 @@ namespace Barotrauma
if (quickSave) { SaveSub(saveButton, saveButton.UserData); }
}
private void CreateSaveAssemblyScreen()
{
SetMode(Mode.Default);
@@ -2712,8 +2768,15 @@ namespace Barotrauma
if (subList.SelectedComponent == null) { return false; }
if (!(subList.SelectedComponent.UserData is SubmarineInfo selectedSubInfo)) { return false; }
LoadSub(selectedSubInfo);
return true;
}
public void LoadSub(SubmarineInfo info)
{
Submarine.Unload();
var selectedSub = new Submarine(selectedSubInfo);
var selectedSub = new Submarine(info);
Submarine.MainSub = selectedSub;
Submarine.MainSub.UpdateTransform(interpolate: false);
ClearUndoBuffer();
@@ -2744,8 +2807,6 @@ namespace Barotrauma
};
adjustLightsPrompt.Buttons[1].OnClicked += adjustLightsPrompt.Close;
}
return true;
}
private void TryDeleteSub(SubmarineInfo sub)
@@ -2822,7 +2883,7 @@ namespace Barotrauma
}
}
if (!string.IsNullOrEmpty(entityFilterBox.Text) || dummyCharacter?.SelectedConstruction?.OwnInventory != null)
if (!string.IsNullOrEmpty(entityFilterBox.Text))
{
FilterEntities(entityFilterBox.Text);
}
@@ -2835,7 +2896,7 @@ namespace Barotrauma
private void FilterEntities(string filter)
{
if (string.IsNullOrWhiteSpace(filter) && dummyCharacter?.SelectedConstruction?.OwnInventory == null)
if (string.IsNullOrWhiteSpace(filter))
{
allEntityList.Visible = false;
categorizedEntityList.Visible = true;
@@ -2862,11 +2923,7 @@ namespace Barotrauma
{
child.Visible =
(!selectedCategory.HasValue || ((MapEntityPrefab)child.UserData).Category.HasFlag(selectedCategory)) &&
((MapEntityPrefab)child.UserData).Name.ToLower().Contains(filter); ;
if (child.Visible && dummyCharacter?.SelectedConstruction?.OwnInventory != null)
{
child.Visible = child.UserData is MapEntityPrefab item && IsItemPrefab(item);
}
((MapEntityPrefab)child.UserData).Name.ToLower().Contains(filter);
}
allEntityList.UpdateScrollBarSize();
allEntityList.BarScroll = 0.0f;
@@ -2942,7 +2999,7 @@ namespace Barotrauma
new ContextMenuOption("SubEditor.EditBackgroundColor", isEnabled: true, onSelected: CreateBackgroundColorPicker),
new ContextMenuOption("SubEditor.ToggleTransparency", isEnabled: true, onSelected: () => TransparentWiringMode = !TransparentWiringMode),
new ContextMenuOption("SubEditor.ToggleGrid", isEnabled: true, onSelected: () => ShouldDrawGrid = !ShouldDrawGrid),
new ContextMenuOption("SubEditor.PasteAssembly", isEnabled: true, PasteAssembly),
new ContextMenuOption("SubEditor.PasteAssembly", isEnabled: true, () => PasteAssembly()),
new ContextMenuOption("Editor.SelectSame", isEnabled: targets.Count > 0, onSelected: delegate
{
IEnumerable<MapEntity> matching = MapEntity.mapEntityList.Where(e => e.prefab != null && targets.Any(t => t.prefab?.Identifier == e.prefab.Identifier) && !MapEntity.SelectedList.Contains(e));
@@ -2973,10 +3030,11 @@ namespace Barotrauma
}
}
private void PasteAssembly()
private void PasteAssembly(string text = null, Vector2? pos = null)
{
string clipboard = Clipboard.GetText();
if (string.IsNullOrWhiteSpace(clipboard))
pos ??= cam.ScreenToWorld(PlayerInput.MousePosition);
text ??= Clipboard.GetText();
if (string.IsNullOrWhiteSpace(text))
{
DebugConsole.ThrowError("Unable to paste assembly: Clipboard content is empty.");
return;
@@ -2986,7 +3044,7 @@ namespace Barotrauma
try
{
element = XDocument.Parse(clipboard).Root;
element = XDocument.Parse(text).Root;
}
catch (Exception) { /* ignored */ }
@@ -2996,12 +3054,11 @@ namespace Barotrauma
return;
}
Vector2 pos = cam.ScreenToWorld(PlayerInput.MousePosition);
Submarine sub = Submarine.MainSub;
List<MapEntity> entities;
try
{
entities = ItemAssemblyPrefab.PasteEntities(pos, sub, element, selectInstance: true);
entities = ItemAssemblyPrefab.PasteEntities(pos.Value, sub, element, selectInstance: true);
}
catch (Exception e)
{
@@ -3274,57 +3331,6 @@ namespace Barotrauma
static string ColorToHex(Color color) => $"#{(color.R << 16 | color.G << 8 | color.B):X6}";
}
/// <summary>
/// Creates a color picker that can be used to change the submarine editor's background color
/// </summary>
private void CreateBackgroundColorPicker()
{
var msgBox = new GUIMessageBox(TextManager.Get("CharacterEditor.EditBackgroundColor"), "", new[] { TextManager.Get("Reset"), TextManager.Get("OK")}, new Vector2(0.2f, 0.175f), minSize: new Point(300, 175));
var rgbLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.25f), msgBox.Content.RectTransform), isHorizontal: true);
// Generate R,G,B labels and parent elements
var layoutParents = new GUILayoutGroup[3];
for (int i = 0; i < 3; i++)
{
var colorContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.33f, 1), rgbLayout.RectTransform), isHorizontal: true) { Stretch = true };
new GUITextBlock(new RectTransform(new Vector2(0.2f, 1), colorContainer.RectTransform, Anchor.CenterLeft) { MinSize = new Point(15, 0) }, GUI.colorComponentLabels[i], font: GUI.SmallFont, textAlignment: Alignment.Center);
layoutParents[i] = colorContainer;
}
// attach number inputs to our generated parent elements
var rInput = new GUINumberInput(new RectTransform(new Vector2(0.7f, 1f), layoutParents[0].RectTransform), GUINumberInput.NumberType.Int) { IntValue = backgroundColor.R };
var gInput = new GUINumberInput(new RectTransform(new Vector2(0.7f, 1f), layoutParents[1].RectTransform), GUINumberInput.NumberType.Int) { IntValue = backgroundColor.G };
var bInput = new GUINumberInput(new RectTransform(new Vector2(0.7f, 1f), layoutParents[2].RectTransform), GUINumberInput.NumberType.Int) { IntValue = backgroundColor.B };
rInput.MinValueInt = gInput.MinValueInt = bInput.MinValueInt = 0;
rInput.MaxValueInt = gInput.MaxValueInt = bInput.MaxValueInt = 255;
rInput.OnValueChanged = gInput.OnValueChanged = bInput.OnValueChanged = delegate
{
var color = new Color(rInput.IntValue, gInput.IntValue, bInput.IntValue);
backgroundColor = color;
GameSettings.SubEditorBackgroundColor = color;
};
// Reset button
msgBox.Buttons[0].OnClicked = (button, o) =>
{
rInput.IntValue = 13;
gInput.IntValue = 37;
bInput.IntValue = 69;
return true;
};
// Ok button
msgBox.Buttons[1].OnClicked = (button, o) =>
{
msgBox.Close();
GameMain.Config.SaveNewPlayerConfig();
return true;
};
}
private GUIFrame CreateWiringPanel()
{
@@ -3495,27 +3501,7 @@ namespace Barotrauma
submarineDescriptionCharacterCount.Text = text.Length + " / " + submarineDescriptionLimit;
}
/// <summary>
/// Checks if the prefab is an item or if it only consists of items
/// </summary>
/// <param name="mapPrefab">The prefab to check</param>
/// <returns>True if the the prefab is an item or it contains only items</returns>
private bool IsItemPrefab(MapEntityPrefab mapPrefab)
{
if (dummyCharacter?.SelectedConstruction == null)
{
return false;
}
return mapPrefab switch
{
ItemPrefab iPrefab => true,
ItemAssemblyPrefab aPrefab => aPrefab.DisplayEntities.All(pair => pair.First is ItemPrefab),
_ => false
};
}
private bool SelectPrefab(GUIComponent component, object obj)
{
allEntityList.Deselect();
@@ -4753,7 +4739,7 @@ namespace Barotrauma
sub.UpdateTransform();
}
graphics.Clear(backgroundColor);
graphics.Clear(BackgroundColor);
ImageManager.Draw(spriteBatch, cam);
@@ -272,7 +272,9 @@ namespace Barotrauma
}
public SerializableEntityEditor(RectTransform parent, ISerializableEntity entity, bool inGame, bool showName, string style = "", int elementHeight = 24, ScalableFont titleFont = null)
: this(parent, entity, inGame ? SerializableProperty.GetProperties<InGameEditable>(entity) : SerializableProperty.GetProperties<Editable>(entity), showName, style, elementHeight, titleFont)
: this(parent, entity, inGame ?
SerializableProperty.GetProperties<InGameEditable>(entity).Union(SerializableProperty.GetProperties<ConditionallyEditable>(entity).Where(p => p.GetAttribute<ConditionallyEditable>()?.IsEditable() ?? false))
: SerializableProperty.GetProperties<Editable>(entity), showName, style, elementHeight, titleFont)
{
}
@@ -447,6 +449,13 @@ namespace Barotrauma
{
TrySendNetworkUpdate(entity, property);
}
// Ensure that the values stay in sync (could be that we force the value in the property accessor).
bool propertyValue = (bool)property.GetValue(entity);
if (tickBox.Selected != propertyValue)
{
tickBox.Selected = propertyValue;
tickBox.Flash(Color.Red);
}
return true;
}
};
@@ -72,6 +72,8 @@ namespace Barotrauma
private readonly static BackgroundMusic[] targetMusic = new BackgroundMusic[MaxMusicChannels];
private static List<BackgroundMusic> musicClips;
private static BackgroundMusic previousDefaultMusic;
private static float updateMusicTimer;
//ambience
@@ -829,9 +831,23 @@ namespace Barotrauma
targetMusic[0] = null;
}
//switch the music if nothing playing atm or the currently playing clip is not suitable anymore
else if (targetMusic[0] == null || currentMusic[0] == null || !suitableMusic.Any(m => m.File == currentMusic[0].Filename))
else if (targetMusic[0] == null || currentMusic[0] == null || !currentMusic[0].IsPlaying() || !suitableMusic.Any(m => m.File == currentMusic[0].Filename))
{
targetMusic[0] = suitableMusic.GetRandom();
if (currentMusicType == "default")
{
if (previousDefaultMusic == null)
{
targetMusic[0] = previousDefaultMusic = suitableMusic.GetRandom();
}
else
{
targetMusic[0] = previousDefaultMusic;
}
}
else
{
targetMusic[0] = suitableMusic.GetRandom();
}
}
//get the appropriate intensity layers for current situation
@@ -851,7 +867,7 @@ namespace Barotrauma
foreach (BackgroundMusic intensityMusic in suitableIntensityMusic)
{
//already playing, do nothing
if (targetMusic.Any(m => m != null && m.File == intensityMusic.File)) continue;
if (targetMusic.Any(m => m != null && m.File == intensityMusic.File)) { continue; }
for (int i = 1; i < MaxMusicChannels; i++)
{
@@ -973,7 +989,11 @@ namespace Barotrauma
return "editor";
}
if (Screen.Selected != GameMain.GameScreen) { return firstTimeInMainMenu ? "menu" : "default"; }
if (Screen.Selected != GameMain.GameScreen)
{
previousDefaultMusic = null;
return firstTimeInMainMenu ? "menu" : "default";
}
firstTimeInMainMenu = false;
@@ -112,7 +112,7 @@ namespace Barotrauma
GameMain.Instance.GraphicsDevice.Viewport = prevViewport;
using (FileStream fs = File.Open("wikiimage.png", System.IO.FileMode.Create))
{
rt.SaveAsPng(fs, boundingBox.Width, boundingBox.Height);
rt.SaveAsPng(fs, texWidth, texHeight);
}
}
}