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

View File

@@ -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);
}
}
}

View File

@@ -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)
{

View File

@@ -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);
}
}

View File

@@ -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
{

View File

@@ -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);
}
}
}

View File

@@ -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),

View File

@@ -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;
}
}
}
}

View File

@@ -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;

View File

@@ -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));

View File

@@ -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();

View File

@@ -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();
}
}
}

View File

@@ -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‖");
}

View File

@@ -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 + ")");
}
}
}
}

View File

@@ -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"),

View File

@@ -140,6 +140,7 @@ namespace Barotrauma
{
if (value == intValue) { return; }
intValue = value;
ClampIntValue();
UpdateText();
}
}

View File

@@ -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})");
}
}
}

View File

@@ -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);

View File

@@ -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)

View File

@@ -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()

View File

@@ -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);
}
}
}
}

View File

@@ -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);

View File

@@ -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));

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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: () =>

View File

@@ -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())

View File

@@ -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;
}

View File

@@ -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(

View File

@@ -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)

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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;
}
}
}
}
}

View File

@@ -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);

View File

@@ -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)
{

View File

@@ -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);

View File

@@ -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);

View File

@@ -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;

View File

@@ -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

View File

@@ -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;

View File

@@ -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)
{

View File

@@ -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),

View File

@@ -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)

View File

@@ -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;

View File

@@ -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)

View File

@@ -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();

View File

@@ -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);

View File

@@ -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;

View File

@@ -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)
{

View File

@@ -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++;

View File

@@ -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)

View File

@@ -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) =>
{

View File

@@ -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);

View File

@@ -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; }

View File

@@ -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;
};
}
}
}

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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) &&

View File

@@ -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);

View File

@@ -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;

View File

@@ -363,7 +363,9 @@ namespace Barotrauma
"VineSprite",
"LeafSprite",
"FlowerSprite",
"DecorativeSprite"
"DecorativeSprite",
"BarrelSprite",
"RailSprite"
};
foreach (string spriteElementName in spriteElementNames)

View File

@@ -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) =>

View File

@@ -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);

View File

@@ -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;
}
};

View File

@@ -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;

View File

@@ -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);
}
}
}

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product>
<Version>0.13.3.11</Version>
<Version>0.1400.0.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName>

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product>
<Version>0.13.3.11</Version>
<Version>0.1400.0.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName>

View File

@@ -1,6 +1,7 @@
// vim:ft=hlsl
//float4 baseColor;
float seed;
float intensity;
float4 grainColor;
float nrand(float2 uv)
{
@@ -9,16 +10,15 @@ float nrand(float2 uv)
float4 grain(float4 position : SV_POSITION, float4 clr : COLOR0, float2 texCoord : TEXCOORD0) : COLOR0
{
float4 baseColor = { 1, 1, 1, 0.25 };
float4 baseColor = grainColor;
float4 color = baseColor * nrand(texCoord);
float2 center = { 0.5, 0.5 };
float2 diff = texCoord - center;
float alpha = diff.x * diff.x + diff.y * diff.y;
color.a = alpha;
return clr * color;
color.a = alpha * intensity;
return color;
}
technique Grain
{
pass Pass1

View File

@@ -1,6 +1,7 @@
// vim:ft=hlsl
//float4 baseColor;
float seed;
float intensity;
float4 grainColor;
float nrand(float2 uv)
{
@@ -9,16 +10,15 @@ float nrand(float2 uv)
float4 grain(float4 position : SV_POSITION, float4 clr : COLOR0, float2 texCoord : TEXCOORD0) : COLOR0
{
float4 baseColor = { 1, 1, 1, 0.25 };
float4 baseColor = grainColor;
float4 color = baseColor * nrand(texCoord);
float2 center = { 0.5, 0.5 };
float2 diff = texCoord - center;
float alpha = diff.x * diff.x + diff.y * diff.y;
color.a = alpha;
return clr * color;
color.a = alpha * intensity;
return color;
}
technique Grain
{
pass Pass1

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product>
<Version>0.13.3.11</Version>
<Version>0.1400.0.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName>

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product>
<Version>0.13.3.11</Version>
<Version>0.1400.0.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName>

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product>
<Version>0.13.3.11</Version>
<Version>0.1400.0.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName>

View File

@@ -283,24 +283,25 @@ namespace Barotrauma
if (extraData != null)
{
int min = 0, max = 9;
switch ((NetEntityEvent.Type)extraData[0])
{
case NetEntityEvent.Type.InventoryState:
msg.WriteRangedInteger(0, 0, 6);
msg.WriteRangedInteger(0, min, max);
msg.Write(GameMain.Server.EntityEventManager.Events.Last()?.ID ?? (ushort)0);
Inventory.ServerWrite(msg, c);
break;
case NetEntityEvent.Type.Control:
msg.WriteRangedInteger(1, 0, 6);
msg.WriteRangedInteger(1, min, max);
Client owner = (Client)extraData[1];
msg.Write(owner != null && owner.Character == this && GameMain.Server.ConnectedClients.Contains(owner) ? owner.ID : (byte)0);
break;
case NetEntityEvent.Type.Status:
msg.WriteRangedInteger(2, 0, 6);
msg.WriteRangedInteger(2, min, max);
WriteStatus(msg);
break;
case NetEntityEvent.Type.UpdateSkills:
msg.WriteRangedInteger(3, 0, 6);
msg.WriteRangedInteger(3, min, max);
if (Info?.Job == null)
{
msg.Write((byte)0);
@@ -315,39 +316,71 @@ namespace Barotrauma
}
}
break;
case NetEntityEvent.Type.SetAttackTarget:
case NetEntityEvent.Type.ExecuteAttack:
Limb attackLimb = extraData[1] as Limb;
UInt16 targetEntityID = (UInt16)extraData[2];
int targetLimbIndex = extraData.Length > 3 ? (int)extraData[3] : 0;
msg.WriteRangedInteger(4, 0, 6);
msg.WriteRangedInteger(extraData[0] is NetEntityEvent.Type.SetAttackTarget ? 4 : 5, min, max);
msg.Write((byte)(Removed ? 255 : Array.IndexOf(AnimController.Limbs, attackLimb)));
msg.Write(targetEntityID);
msg.Write((byte)targetLimbIndex);
msg.Write(extraData.Length > 4 ? (float)extraData[4] : 0);
msg.Write(extraData.Length > 5 ? (float)extraData[5] : 0);
break;
case NetEntityEvent.Type.AssignCampaignInteraction:
msg.WriteRangedInteger(5, 0, 6);
msg.WriteRangedInteger(6, min, max);
msg.Write((byte)CampaignInteractionType);
break;
case NetEntityEvent.Type.ObjectiveManagerOrderState:
msg.WriteRangedInteger(6, 0, 6);
case NetEntityEvent.Type.ObjectiveManagerState:
msg.WriteRangedInteger(7, min, max);
int type = (extraData[1] as string) switch
{
"order" => 1,
"objective" => 2,
_ => 0
};
msg.WriteRangedInteger(type, 0, 2);
if (!(AIController is HumanAIController controller))
{
msg.Write(false);
break;
}
var currentOrderInfo = controller.ObjectiveManager.GetCurrentOrderInfo();
if (!currentOrderInfo.HasValue)
if (type == 1)
{
msg.Write(false);
break;
var currentOrderInfo = controller.ObjectiveManager.GetCurrentOrderInfo();
bool validOrder = currentOrderInfo.HasValue;
msg.Write(validOrder);
if (!validOrder) { break; }
var orderPrefab = currentOrderInfo.Value.Order.Prefab;
int orderIndex = Order.PrefabList.IndexOf(orderPrefab);
msg.WriteRangedInteger(orderIndex, 0, Order.PrefabList.Count);
if (!orderPrefab.HasOptions) { break; }
int optionIndex = orderPrefab.Options.IndexOf(currentOrderInfo.Value.OrderOption);
msg.WriteRangedInteger(optionIndex, 0, orderPrefab.Options.Length);
}
msg.Write(true);
var orderPrefab = currentOrderInfo.Value.Order.Prefab;
int orderIndex = Order.PrefabList.IndexOf(orderPrefab);
msg.WriteRangedInteger(orderIndex, 0, Order.PrefabList.Count);
if (!orderPrefab.HasOptions) { break; }
int optionIndex = orderPrefab.Options.IndexOf(currentOrderInfo.Value.OrderOption);
msg.WriteRangedInteger(optionIndex, 0, orderPrefab.Options.Length);
else if (type == 2)
{
var objective = controller.ObjectiveManager.CurrentObjective;
bool validObjective = !string.IsNullOrEmpty(objective?.Identifier);
msg.Write(validObjective);
if (!validObjective) { break; }
msg.Write(objective.Identifier);
msg.Write(objective.Option ?? "");
UInt16 targetEntityId = 0;
if (objective is AIObjectiveOperateItem operateObjective && operateObjective.OperateTarget != null)
{
targetEntityId = operateObjective.OperateTarget.ID;
}
msg.Write(targetEntityId);
}
break;
case NetEntityEvent.Type.TeamChange:
msg.WriteRangedInteger(8, min, max);
msg.Write((byte)TeamID);
break;
case NetEntityEvent.Type.AddToCrew:
msg.WriteRangedInteger(9, min, max);
break;
default:
DebugConsole.ThrowError("Invalid NetworkEvent type for entity " + ToString() + " (" + (NetEntityEvent.Type)extraData[0] + ")");

View File

@@ -1688,13 +1688,18 @@ namespace Barotrauma
"heal",
(Client client, Vector2 cursorWorldPos, string[] args) =>
{
Character healedCharacter = (args.Length == 0) ? client.Character : FindMatchingCharacter(args);
bool healAll = args.Length > 1 && args[1].Equals("all", StringComparison.OrdinalIgnoreCase);
Character healedCharacter = (args.Length == 0) ? Character.Controlled : FindMatchingCharacter(healAll ? args.Take(args.Length - 1).ToArray() : args);
if (healedCharacter != null)
{
healedCharacter.SetAllDamage(0.0f, 0.0f, 0.0f);
healedCharacter.Oxygen = 100.0f;
healedCharacter.Bloodloss = 0.0f;
healedCharacter.SetStun(0.0f, true);
if (healAll)
{
healedCharacter.CharacterHealth.RemoveAllAfflictions();
}
}
}
);

View File

@@ -22,7 +22,7 @@ namespace Barotrauma
msg.Write((ushort)characterItems[character].Count());
foreach (Item item in characterItems[character])
{
item.WriteSpawnData(msg, item.ID, item.ParentInventory.Owner?.ID ?? Entity.NullEntityID, 0);
item.WriteSpawnData(msg, item.ID, item.ParentInventory?.Owner?.ID ?? Entity.NullEntityID, 0);
}
}
}

View File

@@ -0,0 +1,31 @@
using Barotrauma.Networking;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
partial class EscortMission : Mission
{
public override void ServerWriteInitial(IWriteMessage msg, Client c)
{
if (characters.Count == 0)
{
throw new InvalidOperationException("Server attempted to write escort mission data when no characters had been spawned.");
}
msg.Write((byte)characters.Count);
foreach (Character character in characters)
{
character.WriteSpawnData(msg, character.ID, restrictMessageSize: false);
List<Item> characterItems = characterDictionary[character];
// items must be written in a specific sequence so that child items aren't written before their parents
msg.Write((ushort)characterItems.Count());
foreach (Item item in characterItems)
{
item.WriteSpawnData(msg, item.ID, item.ParentInventory.Owner?.ID ?? Entity.NullEntityID, 0);
}
}
}
}
}

View File

@@ -0,0 +1,32 @@
using Barotrauma.Networking;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
partial class PirateMission : Mission
{
public override void ServerWriteInitial(IWriteMessage msg, Client c)
{
// duplicate code from escortmission, should possibly be combined, though additional loot items might be added so maybe not
if (characters.Count == 0)
{
throw new InvalidOperationException("Server attempted to write escort mission data when no characters had been spawned.");
}
msg.Write((byte)characters.Count);
foreach (Character character in characters)
{
character.WriteSpawnData(msg, character.ID, restrictMessageSize: false);
List<Item> characterItems = characterDictionary[character];
// items must be written in a specific sequence so that child items aren't written before their parents
msg.Write((ushort)characterItems.Count());
foreach (Item item in characterItems)
{
item.WriteSpawnData(msg, item.ID, item.ParentInventory.Owner?.ID ?? Entity.NullEntityID, 0);
}
}
}
}
}

View File

@@ -1,8 +1,7 @@
using System;
using Barotrauma.Networking;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Networking;
namespace Barotrauma
{
@@ -26,7 +25,6 @@ namespace Barotrauma
/// <summary>
/// Saves bots in multiplayer
/// </summary>
/// <param name="root"></param>
public void SaveMultiplayer(XElement root)
{
XElement saveElement = new XElement("bots", new XAttribute("hasbots", HasBots));
@@ -40,8 +38,30 @@ namespace Barotrauma
XElement characterElement = info.Save(saveElement);
if (info.InventoryData != null) { characterElement.Add(info.InventoryData); }
if (info.HealthData != null) { characterElement.Add(info.HealthData); }
if (info.OrderData != null) { characterElement.Add(info.OrderData); }
}
SaveActiveOrders(saveElement);
root.Add(saveElement);
}
public void ServerWriteActiveOrders(IWriteMessage msg)
{
ushort count = (ushort)ActiveOrders.Count(o => o.First != null && !o.Second.HasValue);
msg.Write(count);
if (count > 0)
{
foreach (var activeOrder in ActiveOrders)
{
if (!(activeOrder?.First is Order order) || activeOrder.Second.HasValue) { continue; }
OrderChatMessage.WriteOrder(msg, order, null, order.TargetSpatialEntity, null, 0, order.WallSectionIndex);
bool hasOrderGiver = order.OrderGiver != null;
msg.Write(hasOrderGiver);
if (hasOrderGiver)
{
msg.Write(order.OrderGiver.ID);
}
}
}
}
}
}

View File

@@ -35,9 +35,14 @@ namespace Barotrauma
character.SpawnInventoryItems(inventory, itemData);
}
public void ApplyHealthData(CharacterInfo characterInfo, Character character)
public void ApplyHealthData(Character character)
{
characterInfo.ApplyHealthData(character, healthData);
CharacterInfo.ApplyHealthData(character, healthData);
}
public void ApplyOrderData(Character character)
{
CharacterInfo.ApplyOrderData(character, OrderData);
}
}
}

View File

@@ -49,7 +49,15 @@ namespace Barotrauma
{
GameMain.NetLobbyScreen.ToggleCampaignMode(true);
SaveUtil.LoadGame(selectedSave);
((MultiPlayerCampaign)GameMain.GameSession.GameMode).LastSaveID++;
if (GameMain.GameSession.GameMode is MultiPlayerCampaign mpCampaign)
{
mpCampaign.LastSaveID++;
}
else
{
DebugConsole.ThrowError("Unexpected game mode: " + GameMain.GameSession.GameMode);
return;
}
DebugConsole.NewMessage("Campaign loaded!", Color.Cyan);
DebugConsole.NewMessage(
GameMain.GameSession.Map.SelectedLocation == null ?
@@ -155,6 +163,63 @@ namespace Barotrauma
}
}
public void SaveInventories()
{
List<CharacterCampaignData> prevCharacterData = new List<CharacterCampaignData>(characterData);
//client character has spawned this round -> remove old data (and replace with an up-to-date one if the client still has a character)
characterData.RemoveAll(cd => cd.HasSpawned);
//refresh the character data of clients who are still in the server
foreach (Client c in GameMain.Server.ConnectedClients)
{
if (c.Character?.Info == null) { continue; }
if (c.Character.IsDead && c.Character.CauseOfDeath?.Type != CauseOfDeathType.Disconnected) { continue; }
c.CharacterInfo = c.Character.Info;
characterData.RemoveAll(cd => cd.MatchesClient(c));
characterData.Add(new CharacterCampaignData(c));
}
//refresh the character data of clients who aren't in the server anymore
foreach (CharacterCampaignData data in prevCharacterData)
{
if (data.HasSpawned && !characterData.Any(cd => cd.IsDuplicate(data)))
{
var character = Character.CharacterList.Find(c => c.Info == data.CharacterInfo && !c.IsHusk);
if (character != null && (!character.IsDead || character.CauseOfDeath?.Type == CauseOfDeathType.Disconnected))
{
data.Refresh(character);
characterData.Add(data);
}
}
}
characterData.ForEach(cd => cd.HasSpawned = false);
petsElement = new XElement("pets");
PetBehavior.SavePets(petsElement);
//remove all items that are in someone's inventory
foreach (Character c in Character.CharacterList)
{
if (c.Inventory == null) { continue; }
if (Level.Loaded.Type == LevelData.LevelType.Outpost && c.Submarine != Level.Loaded.StartOutpost)
{
Map.CurrentLocation.RegisterTakenItems(c.Inventory.AllItems.Where(it => it.SpawnedInOutpost && it.OriginalModuleIndex > 0));
}
if (c.Info != null && c.IsBot)
{
if (c.IsDead && c.CauseOfDeath?.Type != CauseOfDeathType.Disconnected) { CrewManager.RemoveCharacterInfo(c.Info); }
c.Info.HealthData = new XElement("health");
c.CharacterHealth.Save(c.Info.HealthData);
c.Info.InventoryData = new XElement("inventory");
c.SaveInventory();
}
c.Inventory.DeleteAllItems();
}
}
protected override IEnumerable<object> DoLevelTransition(TransitionType transitionType, LevelData newLevel, Submarine leavingSub, bool mirror, List<TraitorMissionResult> traitorResults)
{
lastUpdateID++;
@@ -186,59 +251,7 @@ namespace Barotrauma
if (success)
{
List<CharacterCampaignData> prevCharacterData = new List<CharacterCampaignData>(characterData);
//client character has spawned this round -> remove old data (and replace with an up-to-date one if the client still has a character)
characterData.RemoveAll(cd => cd.HasSpawned);
//refresh the character data of clients who are still in the server
foreach (Client c in GameMain.Server.ConnectedClients)
{
if (c.Character?.Info == null) { continue; }
if (c.Character.IsDead && c.Character.CauseOfDeath?.Type != CauseOfDeathType.Disconnected) { continue; }
c.CharacterInfo = c.Character.Info;
characterData.RemoveAll(cd => cd.MatchesClient(c));
characterData.Add(new CharacterCampaignData(c));
}
//refresh the character data of clients who aren't in the server anymore
foreach (CharacterCampaignData data in prevCharacterData)
{
if (data.HasSpawned && !characterData.Any(cd => cd.IsDuplicate(data)))
{
var character = Character.CharacterList.Find(c => c.Info == data.CharacterInfo && !c.IsHusk);
if (character != null && (!character.IsDead || character.CauseOfDeath?.Type == CauseOfDeathType.Disconnected))
{
data.Refresh(character);
characterData.Add(data);
}
}
}
characterData.ForEach(cd => cd.HasSpawned = false);
petsElement = new XElement("pets");
PetBehavior.SavePets(petsElement);
//remove all items that are in someone's inventory
foreach (Character c in Character.CharacterList)
{
if (c.Inventory == null) { continue; }
if (Level.Loaded.Type == LevelData.LevelType.Outpost && c.Submarine != Level.Loaded.StartOutpost)
{
Map.CurrentLocation.RegisterTakenItems(c.Inventory.AllItems.Where(it => it.SpawnedInOutpost && it.OriginalModuleIndex > 0));
}
if (c.Info != null && c.IsBot)
{
if (c.IsDead && c.CauseOfDeath?.Type != CauseOfDeathType.Disconnected) { CrewManager.RemoveCharacterInfo(c.Info); }
c.Info.HealthData = new XElement("health");
c.CharacterHealth.Save(c.Info.HealthData);
c.Info.InventoryData = new XElement("inventory");
c.SaveInventory(c.Inventory, c.Info.InventoryData);
}
c.Inventory.DeleteAllItems();
}
SaveInventories();
yield return CoroutineStatus.Running;
@@ -284,6 +297,8 @@ namespace Barotrauma
yield return CoroutineStatus.Success;
}
CrewManager?.ClearCurrentOrders();
//--------------------------------------
GameMain.Server.EndGame(transitionType);
@@ -497,6 +512,13 @@ namespace Barotrauma
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);
}
var characterData = GetClientCharacterData(c);
if (characterData?.CharacterInfo == null)
{
@@ -563,6 +585,21 @@ namespace Barotrauma
purchasedUpgrades.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));
}
if (!AllowedToManageCampaign(sender))
{
DebugConsole.ThrowError("Client \"" + sender.Name + "\" does not have a permission to manage the campaign");
@@ -649,6 +686,26 @@ namespace Barotrauma
int level = UpgradeManager.GetUpgradeLevel(prefab, category);
GameServer.Log($"SERVER: Purchased level {level} {category.Identifier}.{prefab.Identifier} for {price}", ServerLog.MessageType.ServerMessage);
}
foreach (var purchasedItemSwap in purchasedItemSwaps)
{
if (purchasedItemSwap.ItemToInstall == null)
{
UpgradeManager.CancelItemSwap(purchasedItemSwap.ItemToRemove);
}
else
{
UpgradeManager.PurchaseItemSwap(purchasedItemSwap.ItemToRemove, purchasedItemSwap.ItemToInstall);
}
}
foreach (Item item in Item.ItemList)
{
if (item.PendingItemSwap != null && !purchasedItemSwaps.Any(it => it.ItemToRemove == item))
{
UpgradeManager.CancelItemSwap(item);
item.PendingItemSwap = null;
}
}
}
public void ServerReadCrew(IReadMessage msg, Client sender)
@@ -863,7 +920,7 @@ namespace Barotrauma
CampaignMetadata?.Save(modeElement);
Map.Save(modeElement);
CargoManager?.SavePurchasedItems(modeElement);
UpgradeManager?.SavePendingUpgrades(modeElement, UpgradeManager?.PendingUpgrades);
UpgradeManager?.Save(modeElement);
if (petsElement != null)
{

View File

@@ -8,11 +8,18 @@ namespace Barotrauma
{
partial class Item : MapEntity, IDamageable, ISerializableEntity, IServerSerializable, IClientSerializable
{
private CoroutineHandle logPropertyChangeCoroutine;
public override Sprite Sprite
{
get { return prefab?.sprite; }
}
partial void AssignCampaignInteractionTypeProjSpecific(CampaignMode.InteractionType interactionType)
{
GameMain.NetworkMember.CreateEntityEvent(this, new object[] { NetEntityEvent.Type.AssignCampaignInteraction });
}
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
{
string errorMsg = "";
@@ -86,6 +93,9 @@ namespace Barotrauma
case NetEntityEvent.Type.Status:
msg.Write(condition);
break;
case NetEntityEvent.Type.AssignCampaignInteraction:
msg.Write((byte)CampaignInteractionType);
break;
case NetEntityEvent.Type.Treatment:
{
ItemComponent targetComponent = (ItemComponent)extraData[1];

View File

@@ -22,80 +22,23 @@ namespace Barotrauma.Networking
int? wallSectionIndex = null;
if (type == ChatMessageType.Order)
{
int orderIndex = msg.ReadByte();
orderTargetCharacter = Entity.FindEntityByID(msg.ReadUInt16()) as Character;
orderTargetEntity = Entity.FindEntityByID(msg.ReadUInt16()) as Entity;
Order orderPrefab = null;
int? orderOptionIndex = 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)
var orderMessageInfo = OrderChatMessage.ReadOrder(msg);
if (orderMessageInfo.OrderIndex < 0 || orderMessageInfo.OrderIndex >= Order.PrefabList.Count)
{
orderPrefab = Order.PrefabList[orderIndex];
if (orderPrefab.Identifier != "dismissed")
{
orderOptionIndex = 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
{
orderOptionIndex = msg.ReadByte();
}
int orderPriority = msg.ReadByte();
orderTargetType = (Order.OrderTargetType)msg.ReadByte();
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, true);
}
else if (orderTargetType == Order.OrderTargetType.WallSection)
{
wallSectionIndex = msg.ReadByte();
}
if (orderIndex < 0 || orderIndex >= Order.PrefabList.Count)
{
DebugConsole.ThrowError($"Invalid order message from client \"{c.Name}\" - order index out of bounds ({orderIndex}).");
DebugConsole.ThrowError($"Invalid order message from client \"{c.Name}\" - order index out of bounds ({orderMessageInfo.OrderIndex}).");
if (NetIdUtils.IdMoreRecent(ID, c.LastSentChatMsgID)) { c.LastSentChatMsgID = ID; }
return;
}
orderPrefab ??= Order.PrefabList[orderIndex];
orderOption ??= orderOptionIndex == null || orderOptionIndex < 0 || orderOptionIndex >= orderPrefab.Options.Length ? "" : orderPrefab.Options[orderOptionIndex.Value];
orderMsg = new OrderChatMessage(orderPrefab, orderOption, orderPriority, orderTargetPosition ?? orderTargetEntity as ISpatialEntity, orderTargetCharacter, c.Character)
orderTargetCharacter = orderMessageInfo.TargetCharacter;
orderTargetEntity = orderMessageInfo.TargetEntity;
orderTargetPosition = orderMessageInfo.TargetPosition;
orderTargetType = orderMessageInfo.TargetType;
wallSectionIndex = orderMessageInfo.WallSectionIndex;
var orderPrefab = orderMessageInfo.OrderPrefab ?? Order.PrefabList[orderMessageInfo.OrderIndex];
string orderOption = orderMessageInfo.OrderOption ??
(orderMessageInfo.OrderOptionIndex == null || orderMessageInfo.OrderOptionIndex < 0 || orderMessageInfo.OrderOptionIndex >= orderPrefab.Options.Length ?
"" : orderPrefab.Options[orderMessageInfo.OrderOptionIndex.Value]);
orderMsg = new OrderChatMessage(orderPrefab, orderOption, orderMessageInfo.Priority, orderTargetPosition ?? orderTargetEntity as ISpatialEntity, orderTargetCharacter, c.Character)
{
WallSectionIndex = wallSectionIndex
};
@@ -176,46 +119,49 @@ namespace Barotrauma.Networking
if (type == ChatMessageType.Order)
{
if (c.Character == null || c.Character.SpeechImpediment >= 100.0f || c.Character.IsDead) { return; }
Order order = null;
if (orderMsg.Order.IsReport)
{
HumanAIController.ReportProblem(orderMsg.Sender, orderMsg.Order);
}
else if (orderTargetCharacter != null && !orderMsg.Order.TargetAllCharacters)
Order order = orderTargetType switch
{
switch (orderTargetType)
Order.OrderTargetType.Entity =>
new Order(orderMsg.Order, orderTargetEntity, orderMsg.Order?.GetTargetItemComponent(orderTargetEntity as Item), orderGiver: orderMsg.Sender),
Order.OrderTargetType.Position =>
new Order(orderMsg.Order, orderTargetPosition, orderGiver: orderMsg.Sender),
Order.OrderTargetType.WallSection when orderTargetEntity is Structure s && wallSectionIndex.HasValue =>
new Order(orderMsg.Order, s, wallSectionIndex, orderGiver: orderMsg.Sender),
_ => throw new NotImplementedException()
};
if (order != null)
{
if (order.TargetAllCharacters)
{
case Order.OrderTargetType.Entity:
order = new Order(orderMsg.Order.Prefab, orderTargetEntity, orderMsg.Order.Prefab?.GetTargetItemComponent(orderTargetEntity as Item), orderGiver: orderMsg.Sender);
break;
case Order.OrderTargetType.Position:
order = new Order(orderMsg.Order.Prefab, orderTargetPosition, orderGiver: orderMsg.Sender);
break;
if (order.IsIgnoreOrder)
{
switch (orderTargetType)
{
case Order.OrderTargetType.Entity:
if (!(orderTargetEntity is IIgnorable ignorableEntity)) { break; }
ignorableEntity.OrderedToBeIgnored = order.Identifier == "ignorethis";
break;
case Order.OrderTargetType.Position:
throw new NotImplementedException();
case Order.OrderTargetType.WallSection:
if (!wallSectionIndex.HasValue) { break; }
if (!(orderTargetEntity is Structure s)) { break; }
if (!(s.GetSection(wallSectionIndex.Value) is IIgnorable ignorableWall)) { break; }
ignorableWall.OrderedToBeIgnored = order.Identifier == "ignorethis";
break;
}
}
GameMain.GameSession?.CrewManager?.AddOrder(order, order.IsIgnoreOrder ? (float?)null : order.FadeOutTime);
}
if (order != null)
else if (orderTargetCharacter != null)
{
orderTargetCharacter.SetOrder(order, orderMsg.OrderOption, orderMsg.OrderPriority, orderMsg.Sender);
}
}
else if (orderMsg.Order.IsIgnoreOrder)
{
switch (orderTargetType)
{
case Order.OrderTargetType.Entity:
if (orderTargetEntity is IIgnorable ignorableEntity)
{
ignorableEntity.OrderedToBeIgnored = orderMsg.Order.Identifier == "ignorethis";
}
break;
case Order.OrderTargetType.WallSection:
if (!wallSectionIndex.HasValue) { break; }
if (orderTargetEntity is Structure s && s.GetSection(wallSectionIndex.Value) is IIgnorable ignorableWall)
{
ignorableWall.OrderedToBeIgnored = orderMsg.Order.Identifier == "ignorethis";
}
break;
}
}
GameMain.Server.SendOrderChatMessage(orderMsg);
}
else

View File

@@ -537,7 +537,8 @@ namespace Barotrauma.Networking
initiatedStartGame = false;
}
}
else if (Screen.Selected == GameMain.NetLobbyScreen && !gameStarted && !initiatedStartGame)
else if (Screen.Selected == GameMain.NetLobbyScreen && !gameStarted && !initiatedStartGame &&
(GameMain.NetLobbyScreen.SelectedMode != GameModePreset.MultiPlayerCampaign || GameMain.GameSession?.GameMode is MultiPlayerCampaign))
{
if (serverSettings.AutoRestart)
{
@@ -1207,6 +1208,7 @@ namespace Barotrauma.Networking
mpCampaign.ServerReadCrew(inc, sender);
}
}
private void ReadReadyToSpawnMessage(IReadMessage inc, Client sender)
{
sender.SpectateOnly = inc.ReadBoolean() && (serverSettings.AllowSpectating || sender.Connection == OwnerConnection);
@@ -1315,6 +1317,12 @@ namespace Barotrauma.Networking
if (gameStarted)
{
Log("Client \"" + GameServer.ClientLogName(sender) + "\" ended the round.", ServerLog.MessageType.ServerMessage);
if (mpCampaign != null && Level.IsLoadedOutpost)
{
mpCampaign.SaveInventories();
GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine);
SaveUtil.SaveGame(GameMain.GameSession.SavePath);
}
EndGame();
}
}
@@ -1492,7 +1500,6 @@ namespace Barotrauma.Networking
inc.ReadPadBits();
}
private void ClientWrite(Client c)
{
if (gameStarted && c.InGame)
@@ -2251,10 +2258,6 @@ namespace Barotrauma.Networking
{
client.CharacterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, client.Name);
}
else
{
client.CharacterInfo.ClearCurrentOrders();
}
characterInfos.Add(client.CharacterInfo);
if (client.CharacterInfo.Job == null || client.CharacterInfo.Job.Prefab != client.AssignedJob.First)
{
@@ -2340,7 +2343,8 @@ namespace Barotrauma.Networking
else
{
characterData.SpawnInventoryItems(spawnedCharacter, spawnedCharacter.Inventory);
characterData.ApplyHealthData(spawnedCharacter.Info, spawnedCharacter);
characterData.ApplyHealthData(spawnedCharacter);
characterData.ApplyOrderData(spawnedCharacter);
spawnedCharacter.GiveIdCardTags(mainSubWaypoints[i]);
characterData.HasSpawned = true;
}
@@ -2362,7 +2366,7 @@ namespace Barotrauma.Networking
if (hadBots)
{
//loaded existing bots -> init them
crewManager?.InitRound();
crewManager.InitRound();
}
else
{
@@ -2372,6 +2376,7 @@ namespace Barotrauma.Networking
}
campaign?.LoadPets();
crewManager?.LoadActiveOrders();
foreach (Submarine sub in Submarine.MainSubs)
{
@@ -2516,6 +2521,8 @@ namespace Barotrauma.Networking
{
mission.ServerWriteInitial(msg, client);
}
msg.Write(GameMain.GameSession.CrewManager != null);
GameMain.GameSession.CrewManager?.ServerWriteActiveOrders(msg);
}
public void EndGame(CampaignMode.TransitionType transitionType = CampaignMode.TransitionType.None)
@@ -3314,7 +3321,6 @@ namespace Barotrauma.Networking
serverSettings.SaveClientPermissions();
}
private IEnumerable<object> SendClientPermissionsAfterClientListSynced(Client recipient, Client client)
{
DateTime timeOut = DateTime.Now + new TimeSpan(0, 0, 10);
@@ -3331,7 +3337,6 @@ namespace Barotrauma.Networking
yield return CoroutineStatus.Success;
}
private void SendClientPermissions(Client recipient, Client client)
{
if (recipient?.Connection == null) { return; }

View File

@@ -10,6 +10,11 @@ namespace Barotrauma.Networking
{
private DateTime despawnTime;
private float shuttleEmptyTimer;
private int pendingRespawnCount, requiredRespawnCount;
private int prevPendingRespawnCount, prevRequiredRespawnCount;
private IEnumerable<Client> GetClientsToRespawn()
{
MultiPlayerCampaign campaign = GameMain.GameSession.GameMode as MultiPlayerCampaign;
@@ -95,10 +100,19 @@ namespace Barotrauma.Networking
RespawnShuttle.Velocity = Vector2.Zero;
}
int clientsToRespawn = GetClientsToRespawn().Count();
pendingRespawnCount = GetClientsToRespawn().Count();
requiredRespawnCount = (int)Math.Max((float)GameMain.Server.ConnectedClients.Count * GameMain.Server.ServerSettings.MinRespawnRatio, 1.0f);
if (pendingRespawnCount != prevPendingRespawnCount ||
requiredRespawnCount != prevRequiredRespawnCount)
{
prevPendingRespawnCount = pendingRespawnCount;
prevRequiredRespawnCount = requiredRespawnCount;
GameMain.Server.CreateEntityEvent(this);
}
if (RespawnCountdownStarted)
{
if (clientsToRespawn == 0)
if (pendingRespawnCount == 0)
{
RespawnCountdownStarted = false;
GameMain.Server.CreateEntityEvent(this);
@@ -106,7 +120,7 @@ namespace Barotrauma.Networking
}
else
{
bool shouldStartCountdown = ShouldStartRespawnCountdown(clientsToRespawn);
bool shouldStartCountdown = ShouldStartRespawnCountdown(pendingRespawnCount);
if (shouldStartCountdown)
{
RespawnCountdownStarted = true;
@@ -167,8 +181,14 @@ namespace Barotrauma.Networking
}
}
partial void UpdateReturningProjSpecific()
partial void UpdateReturningProjSpecific(float deltaTime)
{
//speed up despawning if there's no-one inside the shuttle
if (despawnTime > DateTime.Now + new TimeSpan(0, 0, seconds: 30) && CheckShuttleEmpty(deltaTime))
{
despawnTime = DateTime.Now + new TimeSpan(0, 0, seconds: 30);
}
foreach (Door door in shuttleDoors)
{
if (door.IsOpen) door.TrySetState(false, false, true);
@@ -214,7 +234,7 @@ namespace Barotrauma.Networking
if (!ReturnCountdownStarted)
{
//if there are no living chracters inside, transporting can be stopped immediately
if (!Character.CharacterList.Any(c => c.Submarine == RespawnShuttle && !c.IsDead))
if (CheckShuttleEmpty(deltaTime))
{
ReturnTime = DateTime.Now;
ReturnCountdownStarted = true;
@@ -232,6 +252,10 @@ namespace Barotrauma.Networking
GameMain.Server.CreateEntityEvent(this);
}
}
else if (CheckShuttleEmpty(deltaTime))
{
ReturnTime = DateTime.Now;
}
if (DateTime.Now > ReturnTime)
{
@@ -245,6 +269,19 @@ namespace Barotrauma.Networking
}
}
private bool CheckShuttleEmpty(float deltaTime)
{
if (!Character.CharacterList.Any(c => c.Submarine == RespawnShuttle && !c.IsDead))
{
shuttleEmptyTimer += deltaTime;
}
else
{
shuttleEmptyTimer = 0.0f;
}
return shuttleEmptyTimer > 1.0f;
}
partial void RespawnCharactersProjSpecific(Vector2? shuttlePos)
{
var respawnSub = RespawnShuttle ?? Submarine.MainSub;
@@ -394,7 +431,7 @@ namespace Barotrauma.Networking
else
{
characterData.SpawnInventoryItems(character, character.Inventory);
characterData.ApplyHealthData(character.Info, character);
characterData.ApplyHealthData(character);
character.GiveIdCardTags(mainSubSpawnPoints[i]);
characterData.HasSpawned = true;
}
@@ -427,6 +464,8 @@ namespace Barotrauma.Networking
msg.Write((float)(ReturnTime - DateTime.Now).TotalSeconds);
break;
case State.Waiting:
msg.Write((ushort)pendingRespawnCount);
msg.Write((ushort)requiredRespawnCount);
msg.Write(RespawnCountdownStarted);
msg.Write((float)(RespawnTime - DateTime.Now).TotalSeconds);
break;

View File

@@ -37,6 +37,11 @@ namespace Barotrauma
targetItems.Add(item);
}
}
//only target items in the main sub if there are any
if (targetItems.Count > 1 && targetItems.Any(it => it.Submarine == Submarine.MainSub))
{
targetItems.RemoveAll(it => it.Submarine != Submarine.MainSub);
}
if (targetItems.Count > 0)
{
var textId = targetItems[0].Prefab.GetItemNameTextId();

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product>
<Version>0.13.3.11</Version>
<Version>0.1400.0.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName>

View File

@@ -38,10 +38,13 @@
<Item file="Content/Items/Reactor/reactor.xml" />
<Item file="Content/Items/Tools/tools.xml" />
<Item file="Content/Items/Weapons/coilgun.xml" />
<Item file="Content/Items/Weapons/chaingun.xml" />
<Item file="Content/Items/Weapons/depthcharge.xml" />
<Item file="Content/Items/Weapons/dischargecoil.xml" />
<Item file="Content/Items/Weapons/explosives.xml" />
<Item file="Content/Items/Weapons/pulselaser.xml" />
<Item file="Content/Items/Weapons/railgun.xml" />
<Item file="Content/Items/Weapons/turrethardpoint.xml" />
<Item file="Content/Items/Weapons/weapons.xml" />
<Item file="Content/Items/Legacy/legacycommand.xml" />
<Item file="Content/Items/Legacy/legacycontainers.xml" />
@@ -59,6 +62,7 @@
<Item file="Content/Items/Gardening/growableplants.xml" />
<Item file="Content/Items/Gardening/gardeningtools.xml" />
<Item file="Content/Items/Gardening/plantproducts.xml" />
<Item file="Content/Map/Pirates/PirateItems.xml" />
<Character file="Content/Characters/Balloon/Balloon.xml" />
<Character file="Content/Characters/Carrier/Carrier.xml" />
<Character file="Content/Characters/Charybdis/Charybdis.xml" />
@@ -144,6 +148,7 @@
<Afflictions file="Content/Afflictions.xml" />
<Structure file="Content/Map/StructurePrefabs.xml" />
<Structure file="Content/Map/Outposts/OutpostStructurePrefabs.xml" />
<Structure file="Content/Map/Pirates/PirateAssets.xml" />
<Item file="Content/Map/Outposts/OutpostItems.xml" />
<Structure file="Content/Items/Shipwrecks/StructurePrefabsWrecked.xml" />
<Structure file="Content/Map/Thalamus/thalamusstructures.xml" />
@@ -252,4 +257,5 @@
<OutpostModule file="Content/Map/Outposts/MineModule_04.sub" />
<OutpostModule file="Content/Map/Outposts/HallModuleHorizontal_Abandoned.sub" />
<OutpostModule file="Content/Map/Outposts/HallModuleVertical_Abandoned.sub" />
<EnemySubmarine file="Content/Map/EnemySubmarines/HumpbackPirate.sub"/>
</contentpackage>

View File

@@ -0,0 +1,28 @@
using Microsoft.Xna.Framework;
namespace Barotrauma
{
public class CachedDistance
{
public readonly Vector2 StartWorldPos;
public readonly Vector2 EndWorldPos;
public readonly float Distance;
public double RecalculationTime;
public CachedDistance(Vector2 startWorldPos, Vector2 endWorldPos, float dist, double recalculationTime)
{
StartWorldPos = startWorldPos;
EndWorldPos = endWorldPos;
Distance = dist;
RecalculationTime = recalculationTime;
}
public bool ShouldUpdateDistance(Vector2 currentStartWorldPos, Vector2 currentEndWorldPos, float minDistanceToUpdate = 500.0f)
{
if (Timing.TotalTime < RecalculationTime) { return false; }
float minDistSquared = minDistanceToUpdate * minDistanceToUpdate;
return Vector2.DistanceSquared(StartWorldPos, currentStartWorldPos) > minDistSquared ||
Vector2.DistanceSquared(EndWorldPos, currentEndWorldPos) > minDistSquared;
}
}
}

View File

@@ -73,6 +73,8 @@ namespace Barotrauma
get { return true; }
}
public virtual bool IsMentallyUnstable => false;
private IEnumerable<Hull> visibleHulls;
private float hullVisibilityTimer;
const float hullVisibilityInterval = 0.5f;
@@ -215,10 +217,26 @@ namespace Barotrauma
}
private readonly HashSet<Item> unequippedItems = new HashSet<Item>();
public bool TakeItem(Item item, Inventory targetInventory, bool equip, bool dropOtherIfCannotMove = true, bool allowSwapping = false, bool storeUnequipped = false)
public bool TakeItem(Item item, CharacterInventory targetInventory, bool equip, bool wear = false, bool dropOtherIfCannotMove = true, bool allowSwapping = false, bool storeUnequipped = false)
{
var pickable = item.GetComponent<Pickable>();
if (pickable == null) { return false; }
if (wear)
{
var wearable = item.GetComponent<Wearable>();
if (wearable != null)
{
pickable = wearable;
}
}
else
{
var holdable = item.GetComponent<Holdable>();
if (holdable != null)
{
pickable = holdable;
}
}
if (item.ParentInventory is ItemInventory itemInventory)
{
if (!itemInventory.Container.HasRequiredItems(Character, addMessage: false)) { return false; }
@@ -302,7 +320,7 @@ namespace Barotrauma
{
if (item != null && !item.Removed && Character.HasItem(item))
{
TakeItem(item, Character.Inventory, equip: true, dropOtherIfCannotMove: true, allowSwapping: true, storeUnequipped: false);
TakeItem(item, Character.Inventory, equip: true, wear: true, dropOtherIfCannotMove: true, allowSwapping: true, storeUnequipped: false);
}
}
unequippedItems.Clear();

View File

@@ -144,21 +144,6 @@ namespace Barotrauma
}
}
public void Reset()
{
if (Static)
{
SightRange = MaxSightRange;
SoundRange = MaxSoundRange;
}
else
{
// Non-static ai targets must be kept alive by a custom logic (e.g. item components)
SightRange = StaticSight ? MaxSightRange : MinSightRange;
SoundRange = StaticSound ? MaxSoundRange : MinSoundRange;
}
}
public AITarget(Entity e, XElement element) : this(e)
{
SightRange = element.GetAttributeFloat("sightrange", 0.0f);
@@ -242,5 +227,20 @@ namespace Barotrauma
List.Remove(this);
entity = null;
}
public void Reset()
{
if (Static)
{
SightRange = MaxSightRange;
SoundRange = MaxSoundRange;
}
else
{
// Non-static ai targets must be kept alive by a custom logic (e.g. item components)
SightRange = StaticSight ? MaxSightRange : MinSightRange;
SoundRange = StaticSound ? MaxSoundRange : MinSoundRange;
}
}
}
}

View File

@@ -381,6 +381,7 @@ namespace Barotrauma
SelectedAiTarget = target;
selectedTargetMemory = GetTargetMemory(target, true);
selectedTargetMemory.Priority = priority;
ignoredTargets.Remove(target);
}
private float movementMargin;
@@ -496,7 +497,7 @@ namespace Barotrauma
}
}
if (AIParams.Infiltrate)
if (AIParams.CanOpenDoors)
{
bool IsCloseEnoughToTargetSub(float threshold) => SelectedAiTarget?.Entity?.Submarine is Submarine sub && sub != null && Vector2.DistanceSquared(Character.WorldPosition, sub.WorldPosition) < MathUtils.Pow(Math.Max(sub.Borders.Size.X, sub.Borders.Size.Y) / 2 + threshold, 2);
@@ -787,7 +788,7 @@ namespace Barotrauma
if (pathSteering != null && !Character.AnimController.InWater)
{
// Wander around inside
pathSteering.Wander(deltaTime, ConvertUnits.ToDisplayUnits(colliderLength), stayStillInTightSpace: false);
pathSteering.Wander(deltaTime, Math.Max(ConvertUnits.ToDisplayUnits(colliderLength), 100.0f), stayStillInTightSpace: false);
}
else
{
@@ -1203,7 +1204,7 @@ namespace Barotrauma
}
canAttack = AttackingLimb != null && AttackingLimb.attack.CoolDownTimer <= 0;
}
if (!AIParams.Infiltrate)
if (!AIParams.CanOpenDoors)
{
if (!Character.AnimController.SimplePhysicsEnabled && SelectedAiTarget.Entity.Submarine != null && Character.Submarine == null && (!canAttackDoors || !canAttackWalls || !AIParams.TargetOuterWalls))
{
@@ -1777,6 +1778,7 @@ namespace Barotrauma
}
if (!isFriendly && attackResult.Damage > 0.0f)
{
ignoredTargets.Remove(attacker.AiTarget);
bool canAttack = attacker.Submarine == Character.Submarine && canAttackCharacters || attacker.Submarine != null && canAttackWalls;
if (AIParams.AttackWhenProvoked && canAttack)
{
@@ -1893,16 +1895,28 @@ namespace Barotrauma
{
//simulate attack input to get the character to attack client-side
Character.SetInput(InputType.Attack, true, true);
#if SERVER
GameMain.NetworkMember.CreateEntityEvent(Character, new object[]
{
Networking.NetEntityEvent.Type.SetAttackTarget,
attackingLimb,
(damageTarget as Entity)?.ID ?? Entity.NullEntityID,
damageTarget is Character character && targetLimb != null ? Array.IndexOf(character.AnimController.Limbs, targetLimb) : 0,
SimPosition.X,
SimPosition.Y
});
#endif
if (attackingLimb.UpdateAttack(deltaTime, attackSimPos, damageTarget, out AttackResult attackResult, distance, targetLimb))
{
if (damageTarget.Health > 0)
if (damageTarget.Health > 0 && attackResult.Damage > 0)
{
// Managed to hit a living/non-destroyed target. Increase the priority more if the target is low in health -> dies easily/soon
selectedTargetMemory.Priority += GetRelativeDamage(attackResult.Damage, damageTarget.Health) * AIParams.AggressionGreed;
}
else
{
selectedTargetMemory.Priority = 0;
selectedTargetMemory.Priority -= Math.Max(selectedTargetMemory.Priority / 2, 1);
return selectedTargetMemory.Priority > 1;
}
}
return true;
@@ -2184,7 +2198,7 @@ namespace Barotrauma
bool targetingFromOutsideToInside = item.CurrentHull != null && character.CurrentHull == null;
if (targetingFromOutsideToInside)
{
if (door != null && (!canAttackDoors && !AIParams.Infiltrate) || !canAttackWalls)
if (door != null && (!canAttackDoors && !AIParams.CanOpenDoors) || !canAttackWalls)
{
// Can't reach
continue;
@@ -2610,7 +2624,7 @@ namespace Barotrauma
{
wallTarget = null;
if (State == AIState.Flee || State == AIState.Escape) { return; }
if (AIParams.Infiltrate && HasValidPath(requireNonDirty: true)) { return; }
if (AIParams.CanOpenDoors && HasValidPath(requireNonDirty: true)) { return; }
if (SelectedAiTarget == null) { return; }
if (SelectedAiTarget.Entity == null) { return; }
Vector2 rayStart = SimPosition;

View File

@@ -29,6 +29,9 @@ namespace Barotrauma
private float flipTimer;
private const float FlipInterval = 0.5f;
private float teamChangeTimer;
private const float TeamChangeInterval = 0.5f;
public const float HULL_SAFETY_THRESHOLD = 40;
public const float HULL_LOW_OXYGEN_PERCENTAGE = 30;
@@ -121,6 +124,32 @@ namespace Barotrauma
}
}
public MentalStateManager MentalStateManager { get; private set; }
public void InitMentalStateManager()
{
if (MentalStateManager == null)
{
MentalStateManager = new MentalStateManager(Character, this);
}
MentalStateManager.Active = true;
}
public override bool IsMentallyUnstable =>
MentalStateManager?.CurrentMentalType != MentalStateManager.MentalType.Normal &&
MentalStateManager?.CurrentMentalType != MentalStateManager.MentalType.Confused;
public ShipCommandManager ShipCommandManager { get; private set; }
public void InitShipCommandManager()
{
if (ShipCommandManager == null)
{
ShipCommandManager = new ShipCommandManager(Character);
}
ShipCommandManager.Active = true;
}
public HumanAIController(Character c) : base(c)
{
if (!c.IsHuman)
@@ -204,9 +233,11 @@ namespace Barotrauma
}
}
}
if (Character.Submarine == null || !IsOnFriendlyTeam(Character.TeamID, Character.Submarine.TeamID))
if (Character.Submarine == null || !IsOnFriendlyTeam(Character.TeamID, Character.Submarine.TeamID) && !Character.IsEscorted)
{
// Spot enemies while staying outside or inside an enemy ship.
// does not apply for escorted characters, such as prisoners or terrorists who have their own behavior
enemycheckTimer -= deltaTime;
if (enemycheckTimer < 0)
{
@@ -287,6 +318,8 @@ namespace Barotrauma
}
else
{
Character.UpdateTeam();
if (Character.CurrentHull != null)
{
if (Character.IsOnPlayerTeam)
@@ -301,7 +334,7 @@ namespace Barotrauma
}
if (Character.SpeechImpediment < 100.0f)
{
if (Character.Submarine != null && Character.Submarine.TeamID == Character.TeamID && !Character.Submarine.Info.IsWreck)
if (Character.Submarine != null && (Character.Submarine.TeamID == Character.TeamID || Character.IsEscorted) && !Character.Submarine.Info.IsWreck)
{
ReportProblems();
}
@@ -314,7 +347,7 @@ namespace Barotrauma
if (objectiveManager.CurrentObjective == null) { return; }
objectiveManager.DoCurrentObjective(deltaTime);
bool run = objectiveManager.CurrentObjective.ForceRun || objectiveManager.GetCurrentPriority() > AIObjectiveManager.RunPriority;
bool run = objectiveManager.CurrentObjective.ForceRun || !objectiveManager.CurrentObjective.ForceWalk && objectiveManager.GetCurrentPriority() > AIObjectiveManager.RunPriority;
if (ObjectiveManager.CurrentObjective is AIObjectiveGoTo goTo && goTo.Target != null)
{
if (Character.CurrentHull == null)
@@ -395,6 +428,9 @@ namespace Barotrauma
flipTimer = FlipInterval;
}
}
MentalStateManager?.Update(deltaTime);
ShipCommandManager?.Update(deltaTime);
}
private void UnequipUnnecessaryItems()
@@ -442,9 +478,8 @@ namespace Barotrauma
Character.AnimController.InWater ||
Character.AnimController.HeadInWater ||
Character.CurrentHull == null ||
Character.Submarine?.TeamID != Character.TeamID ||
(Character.Submarine.TeamID != Character.TeamID && !Character.IsEscorted) || // these instances should maybe be combined to a method
ObjectiveManager.IsCurrentObjective<AIObjectiveFindSafety>() ||
ObjectiveManager.CurrentOrder is AIObjectiveGoTo goTo && goTo.Target == Character || // wait order
ObjectiveManager.CurrentObjective.GetSubObjectivesRecursive(true).Any(o => o.KeepDivingGearOn);
if (oxygenLow && Character.CurrentHull.Oxygen > 0)
{
@@ -454,7 +489,7 @@ namespace Barotrauma
{
shouldKeepTheGearOn = true;
}
bool removeDivingSuit = !shouldKeepTheGearOn;
bool removeDivingSuit = !shouldKeepTheGearOn && Character.Submarine?.TeamID == Character.TeamID && (!(ObjectiveManager.CurrentOrder is AIObjectiveGoTo goTo) || goTo.Target != Character);
bool takeMaskOff = !shouldKeepTheGearOn;
if (!shouldKeepTheGearOn && !oxygenLow)
{
@@ -505,7 +540,7 @@ namespace Barotrauma
var divingSuit = Character.Inventory.FindItemByTag(AIObjectiveFindDivingGear.HEAVY_DIVING_GEAR);
if (divingSuit != null)
{
if (oxygenLow || ObjectiveManager.GetCurrentPriority() >= AIObjectiveManager.RunPriority)
if (oxygenLow || Character.Submarine?.TeamID != Character.TeamID || ObjectiveManager.GetCurrentPriority() >= AIObjectiveManager.RunPriority)
{
divingSuit.Drop(Character);
HandleRelocation(divingSuit);
@@ -550,7 +585,7 @@ namespace Barotrauma
{
if (!mask.AllowedSlots.Contains(InvSlotType.Any) || !Character.Inventory.TryPutItem(mask, Character, new List<InvSlotType>() { InvSlotType.Any }))
{
if (ObjectiveManager.GetCurrentPriority() >= AIObjectiveManager.RunPriority)
if (Character.Submarine?.TeamID != Character.TeamID || ObjectiveManager.GetCurrentPriority() >= AIObjectiveManager.RunPriority)
{
mask.Drop(Character);
HandleRelocation(mask);
@@ -603,7 +638,7 @@ namespace Barotrauma
Item item = Character.Inventory.GetItemInLimbSlot(hand);
if (item == null) { continue; }
if (!item.AllowedSlots.Contains(InvSlotType.Any) || !Character.Inventory.TryPutItem(item, Character, new List<InvSlotType>() { InvSlotType.Any }))
if (!item.AllowedSlots.Contains(InvSlotType.Any) || !Character.Inventory.TryPutItem(item, Character, new List<InvSlotType>() { InvSlotType.Any }) && Character.Submarine?.TeamID == Character.TeamID )
{
findItemState = FindItemState.OtherItem;
if (FindSuitableContainer(item, out Item targetContainer))
@@ -743,6 +778,7 @@ namespace Barotrauma
{
Order newOrder = null;
Hull targetHull = null;
bool speak = true;
if (Character.CurrentHull != null)
{
bool isFighting = ObjectiveManager.HasActiveObjective<AIObjectiveCombat>();
@@ -759,6 +795,21 @@ namespace Barotrauma
var orderPrefab = Order.GetPrefab("reportintruders");
newOrder = new Order(orderPrefab, hull, null, orderGiver: Character);
targetHull = hull;
if (target.IsEscorted)
{
if (!Character.IsPrisoner && target.IsPrisoner)
{
string msg = TextManager.GetWithVariables("orderdialog.prisonerescaped", new string[] { "[roomname]" }, new string[] { targetHull.DisplayName }, new bool[] { false, true }, true);
Character.Speak(msg, ChatMessageType.Order);
speak = false;
}
else if (!IsMentallyUnstable && target.AIController.IsMentallyUnstable)
{
string msg = TextManager.GetWithVariables("orderdialog.mentalcase", new string[] { "[roomname]" }, new string[] { targetHull.DisplayName }, new bool[] { false, true }, true);
Character.Speak(msg, ChatMessageType.Order);
speak = false;
}
}
}
}
}
@@ -771,7 +822,7 @@ namespace Barotrauma
targetHull = hull;
}
}
if (IsBallastFloraNoticeable(Character, hull))
if (IsBallastFloraNoticeable(Character, hull) && newOrder == null)
{
var orderPrefab = Order.GetPrefab("reportballastflora");
newOrder = new Order(orderPrefab, hull, null, orderGiver: Character);
@@ -824,20 +875,24 @@ namespace Barotrauma
}
}
}
if (newOrder != null)
if (newOrder != null && speak)
{
if (Character.TeamID == CharacterTeamType.FriendlyNPC)
// for now, escorted characters use the report system to get targets but do not speak. escort-character specific dialogue could be implemented
if (!Character.IsEscorted)
{
Character.Speak(newOrder.GetChatMessage("", targetHull?.DisplayName, givingOrderToSelf: false), ChatMessageType.Default,
identifier: newOrder.Prefab.Identifier + (targetHull?.DisplayName ?? "null"),
minDurationBetweenSimilar: 60.0f);
}
else if (Character.IsOnPlayerTeam && GameMain.GameSession?.CrewManager != null && GameMain.GameSession.CrewManager.AddOrder(newOrder, newOrder.FadeOutTime))
{
Character.Speak(newOrder.GetChatMessage("", targetHull?.DisplayName, givingOrderToSelf: false), ChatMessageType.Order);
if (Character.TeamID == CharacterTeamType.FriendlyNPC)
{
Character.Speak(newOrder.GetChatMessage("", targetHull?.DisplayName, givingOrderToSelf: false), ChatMessageType.Default,
identifier: newOrder.Prefab.Identifier + (targetHull?.DisplayName ?? "null"),
minDurationBetweenSimilar: 60.0f);
}
else if (Character.IsOnPlayerTeam && GameMain.GameSession?.CrewManager != null && GameMain.GameSession.CrewManager.AddOrder(newOrder, newOrder.FadeOutTime))
{
Character.Speak(newOrder.GetChatMessage("", targetHull?.DisplayName, givingOrderToSelf: false), ChatMessageType.Order);
#if SERVER
GameMain.Server.SendOrderChatMessage(new OrderChatMessage(newOrder, "", CharacterInfo.HighestManualOrderPriority, targetHull, null, Character));
GameMain.Server.SendOrderChatMessage(new OrderChatMessage(newOrder, "", CharacterInfo.HighestManualOrderPriority, targetHull, null, Character));
#endif
}
}
}
}
@@ -977,11 +1032,11 @@ namespace Barotrauma
return;
}
float cumulativeDamage = GetDamageDoneByAttacker(attacker);
if (!Character.IsSecurity && attacker.IsBot && Character.CombatAction == null)
if (!Character.IsSecurity && attacker.IsBot && !IsMentallyUnstable && !attacker.AIController.IsMentallyUnstable && Character.CombatAction == null)
{
if (cumulativeDamage > 1)
{
// Don't retaliate on damage done by friendly NPC, because we know it's accidental
// Don't retaliate on damage done by friendly NPC, because we know it's accidental, unless if it's a berserking AI
AddCombatObjective(AIObjectiveCombat.CombatMode.Retreat, attacker);
}
}
@@ -1039,8 +1094,11 @@ namespace Barotrauma
}
else
{
// Non-friendly
InformOtherNPCs(GetDamageDoneByAttacker(attacker));
if (Character.Submarine != null && Character.Submarine.GetConnectedSubs().Contains(attacker.Submarine))
{
// Non-friendly
InformOtherNPCs(GetDamageDoneByAttacker(attacker));
}
if (Character.IsBot)
{
AddCombatObjective(DetermineCombatMode(Character, cumulativeDamage: realDamage), attacker);
@@ -1070,12 +1128,27 @@ namespace Barotrauma
{
if (!IsFriendly(attacker))
{
return c.AIController is HumanAIController humanAI &&
if (Character.Submarine == null)
{
// Outside -> don't react.
return AIObjectiveCombat.CombatMode.None;
}
if (!Character.Submarine.GetConnectedSubs().Contains(attacker.Submarine))
{
// Attacked from an unconnected submarine.
return Character.SelectedConstruction?.GetComponent<Turret>() != null ? AIObjectiveCombat.CombatMode.None : AIObjectiveCombat.CombatMode.Retreat;
}
return c.AIController is HumanAIController humanAI &&
(humanAI.ObjectiveManager.IsCurrentOrder<AIObjectiveFightIntruders>() || humanAI.ObjectiveManager.Objectives.Any(o => o is AIObjectiveFightIntruders))
? AIObjectiveCombat.CombatMode.Offensive : AIObjectiveCombat.CombatMode.Defensive;
}
else
{
if (Character.Submarine == null || !Character.Submarine.GetConnectedSubs().Contains(attacker.Submarine))
{
// Outside or attacked from an unconnected submarine -> don't react.
return AIObjectiveCombat.CombatMode.None;
}
// If there are any enemies around, just ignore the friendly fire
if (Character.CharacterList.Any(ch => ch.Submarine == Character.Submarine && !ch.Removed && !ch.IsDead && !ch.IsIncapacitated && !IsFriendly(ch) && VisibleHulls.Contains(ch.CurrentHull)))
{
@@ -1090,7 +1163,7 @@ namespace Barotrauma
// The guards don't react when the player attacks instigators.
return c.IsSecurity ? AIObjectiveCombat.CombatMode.None : (Character.CombatAction != null ? Character.CombatAction.WitnessReaction : AIObjectiveCombat.CombatMode.Retreat);
}
else if (attacker.TeamID == CharacterTeamType.FriendlyNPC)
else if (attacker.TeamID == CharacterTeamType.FriendlyNPC && !(attacker.AIController.IsMentallyUnstable || attacker.AIController.IsMentallyUnstable))
{
if (c.IsSecurity)
{
@@ -1132,7 +1205,7 @@ namespace Barotrauma
}
}
private void AddCombatObjective(AIObjectiveCombat.CombatMode mode, Character target, float delay = 0, Func<bool> abortCondition = null, Action onAbort = null, Action onCompleted = null, bool allowHoldFire = false)
public void AddCombatObjective(AIObjectiveCombat.CombatMode mode, Character target, float delay = 0, Func<AIObjective, bool> abortCondition = null, Action onAbort = null, Action onCompleted = null, bool allowHoldFire = false)
{
if (mode == AIObjectiveCombat.CombatMode.None) { return; }
if (Character.IsDead || Character.IsIncapacitated || Character.Removed) { return; }
@@ -1168,7 +1241,7 @@ namespace Barotrauma
Character.Info?.Job?.Prefab.Identifier == "watchman" ||
Character.CurrentHull == null ||
Character.IsOnPlayerTeam && !target.IsPlayer && ObjectiveManager.GetActiveObjective<AIObjectiveGoTo>()?.Target is Character followTarget && followTarget.IsPlayer,
abortCondition = abortCondition,
AbortCondition = abortCondition,
allowHoldFire = allowHoldFire,
};
if (onAbort != null)
@@ -1190,7 +1263,7 @@ namespace Barotrauma
public void SetForcedOrder(Order order, string option, Character orderGiver)
{
var objective = ObjectiveManager.CreateObjective(order, option, orderGiver, false);
var objective = ObjectiveManager.CreateObjective(order, option, orderGiver);
ObjectiveManager.SetForcedOrder(objective);
}
@@ -1273,7 +1346,8 @@ namespace Barotrauma
/// <summary>
/// Check whether the character has a diving suit in usable condition plus some oxygen.
/// </summary>
public static bool HasDivingSuit(Character character, float conditionPercentage = 0) => HasItem(character, AIObjectiveFindDivingGear.HEAVY_DIVING_GEAR, out _, AIObjectiveFindDivingGear.OXYGEN_SOURCE, conditionPercentage, requireEquipped: true);
public static bool HasDivingSuit(Character character, float conditionPercentage = 0) => HasItem(character, AIObjectiveFindDivingGear.HEAVY_DIVING_GEAR, out _, AIObjectiveFindDivingGear.OXYGEN_SOURCE, conditionPercentage, requireEquipped: true,
predicate: (Item item) => { return character.HasEquippedItem(item, InvSlotType.OuterClothes); });
/// <summary>
/// Check whether the character has a diving mask in usable condition plus some oxygen.
@@ -1464,7 +1538,7 @@ namespace Barotrauma
if (!humanAI.Character.IsSecurity) { return false; }
if (humanAI.ObjectiveManager.IsCurrentObjective<AIObjectiveCombat>()) { return false; }
humanAI.AddCombatObjective(AIObjectiveCombat.CombatMode.Arrest, thief, delay: GetReactionTime(),
abortCondition: () => thief.Inventory.FindItem(it => it != null && it.StolenDuringRound, true) == null,
abortCondition: obj => thief.Inventory.FindItem(it => it != null && it.StolenDuringRound, true) == null,
onAbort: () =>
{
if (item != null && !item.Removed && humanAI != null && !humanAI.ObjectiveManager.IsCurrentObjective<AIObjectiveGetItem>())

View File

@@ -301,34 +301,33 @@ namespace Barotrauma
}
Ladder nextLadder = GetNextLadder();
var ladders = currentLadder ?? nextLadder;
if (canClimb && !isDiving && ladders != null && character.SelectedConstruction != ladders.Item)
bool useLadders = canClimb && ladders != null && (!isDiving || Math.Abs(steering.X) < 0.1f && Math.Abs(steering.Y) > 1);
if (useLadders && character.SelectedConstruction != ladders.Item)
{
if (IsNextNodeLadder || currentPath.Finished)
{
if (character.CanInteractWith(ladders.Item))
{
ladders.Item.TryInteract(character, false, true);
}
else
{
// Cannot interact with the current (or next) ladder,
// Try to select the previous ladder, unless it's already selected, unless the previous ladder is not adjacent to the current ladder.
// The intention of this code is to prevent the bots from dropping from the "double ladders".
var previousLadders = currentPath.PrevNode?.Ladders;
if (previousLadders != null && previousLadders != ladders && character.SelectedConstruction != previousLadders.Item &&
character.CanInteractWith(previousLadders.Item) && Math.Abs(previousLadders.Item.WorldPosition.X - ladders.Item.WorldPosition.X) < 5)
{
previousLadders.Item.TryInteract(character, false, true);
}
}
}
else if (!IsNextLadderSameAsCurrent && character.SelectedConstruction?.GetComponent<Ladder>() != null && character.CanInteractWith(ladders.Item))
if (character.CanInteractWith(ladders.Item))
{
ladders.Item.TryInteract(character, false, true);
}
else
{
// Cannot interact with the current (or next) ladder,
// Try to select the previous ladder, unless it's already selected, unless the previous ladder is not adjacent to the current ladder.
// The intention of this code is to prevent the bots from dropping from the "double ladders".
var previousLadders = currentPath.PrevNode?.Ladders;
if (previousLadders != null && previousLadders != ladders && character.SelectedConstruction != previousLadders.Item &&
character.CanInteractWith(previousLadders.Item) && Math.Abs(previousLadders.Item.WorldPosition.X - ladders.Item.WorldPosition.X) < 5)
{
previousLadders.Item.TryInteract(character, false, true);
}
}
}
var collider = character.AnimController.Collider;
if (character.IsClimbing && !isDiving)
if (character.IsClimbing && !useLadders)
{
character.AnimController.Anim = AnimController.Animation.None;
character.SelectedConstruction = null;
}
if (character.IsClimbing && useLadders)
{
Vector2 diff = currentPath.CurrentNode.SimPosition - pos;
bool nextLadderSameAsCurrent = IsNextLadderSameAsCurrent;
@@ -380,17 +379,12 @@ namespace Barotrauma
}
else if (character.AnimController.InWater)
{
// If the character is underwater, we don't need the ladders anymore
if (character.IsClimbing && isDiving)
{
character.AnimController.Anim = AnimController.Animation.None;
character.SelectedConstruction = null;
}
var door = currentPath.CurrentNode.ConnectedDoor;
if (door == null || door.CanBeTraversed)
{
float multiplier = MathHelper.Lerp(1, 10, MathHelper.Clamp(collider.LinearVelocity.Length() / 10, 0, 1));
float targetDistance = collider.GetSize().X * multiplier;
float margin = MathHelper.Lerp(1, 5, MathHelper.Clamp(collider.LinearVelocity.Length() / 10, 0, 1));
Vector2 colliderSize = collider.GetSize();
float targetDistance = Math.Max(Math.Max(colliderSize.X, colliderSize.Y) / 2 * margin, 0.5f);
float horizontalDistance = Math.Abs(character.WorldPosition.X - currentPath.CurrentNode.WorldPosition.X);
float verticalDistance = Math.Abs(character.WorldPosition.Y - currentPath.CurrentNode.WorldPosition.Y);
if (character.CurrentHull != currentPath.CurrentNode.CurrentHull)
@@ -404,24 +398,25 @@ namespace Barotrauma
}
}
}
else if (!canClimb || !IsNextLadderSameAsCurrent)
else
{
// Walking horizontally
Vector2 colliderBottom = character.AnimController.GetColliderBottom();
Vector2 colliderSize = collider.GetSize();
Vector2 velocity = collider.LinearVelocity;
// If the character is smaller than this, it would fail to use the waypoint nodes because they are always too high.
float minHeight = 1;
// If the character is very thin, without a min value, it would often fail to reach the waypoints, because the horizontal distance is too small.
float minWidth = 0.17f;
// If the character is very short, it would fail to use the waypoint nodes because they are always too high.
// If the character is very thin, it would often fail to reach the waypoints, because the horizontal distance is too small.
// Both values are based on the human size. So basically anything smaller than humans are considered as equal in size.
float minHeight = 1.6125001f;
float minWidth = 0.3225f;
// Cannot use the head position, because not all characters have head or it can be below the total height of the character
float characterHeight = Math.Max(colliderSize.Y + character.AnimController.ColliderHeightFromFloor, minHeight);
float horizontalDistance = Math.Abs(collider.SimPosition.X - currentPath.CurrentNode.SimPosition.X);
bool isAboveFeet = currentPath.CurrentNode.SimPosition.Y > colliderBottom.Y;
bool isNotTooHigh = currentPath.CurrentNode.SimPosition.Y < colliderBottom.Y + characterHeight;
var door = currentPath.CurrentNode.ConnectedDoor;
float margin = MathHelper.Lerp(1, 10, MathHelper.Clamp(Math.Abs(velocity.X) / 10, 0, 1));
float targetDistance = Math.Max(collider.radius * margin, minWidth);
float margin = MathHelper.Lerp(1, 10, MathHelper.Clamp(Math.Abs(velocity.X) / 5, 0, 1));
float targetDistance = Math.Max(colliderSize.X / 2 * margin, minWidth / 2);
if (horizontalDistance < targetDistance && isAboveFeet && isNotTooHigh && (door == null || door.CanBeTraversed))
{
currentPath.SkipToNextNode();

View File

@@ -0,0 +1,176 @@
using Barotrauma.Extensions;
using System;
using System.Linq;
namespace Barotrauma
{
partial class MentalStateManager
{
private float mentalStateTimer;
private const float MentalStateInterval = 7.5f;
private float mentalBehaviorTimer;
private const float MentalBehaviorInterval = 7.5f;
private readonly Character character;
private readonly HumanAIController humanAIController;
public bool Active { get; set; }
public MentalType CurrentMentalType { get; private set; }
public enum MentalType
{
Normal,
Confused, // No effects other than special dialogue
Afraid, // Will retreat from whoever is nearby
Desperate, // Will defensively attack/arrest whoever is nearby
Berserk // turns fully hostile using team change logic
}
private const string MentalTeamChange = "mental";
public MentalStateManager(Character character, HumanAIController humanAIController)
{
this.character = character;
this.humanAIController = humanAIController;
}
public void Update(float deltaTime)
{
if (!Active) { return; }
mentalStateTimer -= deltaTime;
if (mentalStateTimer <= 0.0f)
{
UpdateMentalState();
mentalStateTimer = MentalStateInterval * Rand.Range(0.75f, 1.25f);
}
mentalBehaviorTimer = Math.Max(0f, mentalBehaviorTimer - deltaTime);
}
private void UpdateMentalState()
{
MentalType newMentalType = GetMentalType(character.CharacterHealth.GetAffliction("psychosis"));
bool createdCombat = false;
switch (newMentalType)
{
case MentalType.Normal:
case MentalType.Confused:
// remove combat if we became normal again
mentalBehaviorTimer = 0f;
break;
case MentalType.Afraid:
case MentalType.Desperate:
case MentalType.Berserk:
// berserk is not removed unless we drop to normal behavior again
if (CurrentMentalType == MentalType.Berserk)
{
newMentalType = MentalType.Berserk;
}
// give players a full interval to react to mental changes
if (newMentalType == CurrentMentalType)
{
createdCombat = CreateCombatBehavior(CurrentMentalType);
}
break;
}
if (!createdCombat)
{
CreateDialogueBehavior(newMentalType);
}
if (newMentalType != MentalType.Berserk)
{
character.TryRemoveTeamChange(MentalTeamChange);
}
CurrentMentalType = newMentalType;
}
private int mentalTypeCount;
private int MentalTypeCount
{
get
{
if (mentalTypeCount == 0)
{
mentalTypeCount = Enum.GetNames(typeof(MentalType)).Length;
}
return mentalTypeCount;
}
}
private MentalType GetMentalType(Affliction affliction)
{
if (affliction == null)
{
return MentalType.Normal;
}
// test this later
int psychosisIndex = (int)(affliction.Strength / (affliction.Prefab.MaxStrength / MentalTypeCount) * Rand.Range(1f, 1.2f));
psychosisIndex = Math.Clamp(psychosisIndex, 0, 4);
MentalType mentalType = psychosisIndex switch
{
0 => MentalType.Normal,
1 => MentalType.Confused,
2 => MentalType.Afraid,
3 => MentalType.Desperate,
4 => MentalType.Berserk,
_ => throw new ArgumentOutOfRangeException(psychosisIndex.ToString()),
};
return mentalType;
}
public bool CreateCombatBehavior(MentalType mentalType)
{
Character mentalAttackTarget = Character.CharacterList.Where(
possibleTarget => HumanAIController.IsActive(possibleTarget) &&
(possibleTarget.TeamID != character.TeamID || mentalType == MentalType.Berserk) &&
humanAIController.VisibleHulls.Contains(possibleTarget.CurrentHull) &&
possibleTarget != character).GetRandom();
if (mentalAttackTarget == null)
{
return false;
}
var combatMode = AIObjectiveCombat.CombatMode.None;
bool holdFire = mentalType == MentalType.Afraid && character.IsSecurity;
switch (mentalType)
{
case MentalType.Afraid:
combatMode = character.IsSecurity ? AIObjectiveCombat.CombatMode.Arrest : AIObjectiveCombat.CombatMode.Retreat;
break;
case MentalType.Desperate:
// might be unnecessary to explicitly declare as arrest against non-humans
combatMode = character.IsSecurity && mentalAttackTarget.IsHuman ? AIObjectiveCombat.CombatMode.Arrest : AIObjectiveCombat.CombatMode.Defensive;
break;
case MentalType.Berserk:
combatMode = AIObjectiveCombat.CombatMode.Offensive;
break;
}
// using this as an explicit time-out for the behavior. it's possible it will never run out because of the manager being disabled, but combat objective has failsafes for that
mentalBehaviorTimer = MentalBehaviorInterval;
humanAIController.AddCombatObjective(combatMode, mentalAttackTarget, allowHoldFire: holdFire, abortCondition: obj => mentalBehaviorTimer <= 0f);
string textIdentifier = $"dialogmentalstatereaction{combatMode.ToString().ToLowerInvariant()}";
character.Speak(TextManager.Get(textIdentifier), delay: Rand.Range(0.5f, 1.0f), identifier: textIdentifier, minDurationBetweenSimilar: 25f);
if (mentalType == MentalType.Berserk && !character.HasTeamChange(MentalTeamChange))
{
// TODO: could this be handled in the switch block above?
character.TryAddNewTeamChange(MentalTeamChange, new ActiveTeamChange(CharacterTeamType.None, ActiveTeamChange.TeamChangePriorities.Absolute, aggressiveBehavior: true));
}
return true;
}
public void CreateDialogueBehavior(MentalType mentalType)
{
if (mentalType == MentalType.Normal) { return; }
string textIdentifier = $"dialogmentalstate{mentalType.ToString().ToLowerInvariant()}";
character.Speak(TextManager.Get(textIdentifier), delay: Rand.Range(0.5f, 1.0f), identifier: textIdentifier, minDurationBetweenSimilar: 35f);
}
}
}

View File

@@ -64,6 +64,10 @@ namespace Barotrauma
public readonly List<NPCConversation> Responses;
private readonly int speakerIndex;
private readonly List<string> allowedSpeakerTags;
private readonly bool requireNextLine;
// used primarily for team1 characters interacting with escorted personnel (TODO: not used anywhere)
private readonly bool requireSight;
public static void LoadAll(IEnumerable<ContentFile> files)
{
foreach (var file in files)
@@ -161,6 +165,8 @@ namespace Barotrauma
{
Responses.Add(new NPCConversation(subElement, filePath));
}
requireNextLine = element.GetAttributeBool("requirenextline", false);
requireSight = element.GetAttributeBool("requiresight", false);
}
private static List<string> GetCurrentFlags(Character speaker)
@@ -211,7 +217,7 @@ namespace Barotrauma
var afflictions = speaker.CharacterHealth.GetAllAfflictions();
foreach (Affliction affliction in afflictions)
{
var currentEffect = affliction.Prefab.GetActiveEffect(affliction.Strength);
var currentEffect = affliction.GetActiveEffect();
if (currentEffect != null && !string.IsNullOrEmpty(currentEffect.DialogFlag) && !currentFlags.Contains(currentEffect.DialogFlag))
{
currentFlags.Add(currentEffect.DialogFlag);
@@ -226,7 +232,6 @@ namespace Barotrauma
{
currentFlags.Add("CampaignNPC." + speaker.CampaignInteractionType);
}
if (GameMain.GameSession?.GameMode is CampaignMode campaignMode &&
(campaignMode.Map?.CurrentLocation?.Type?.Identifier.Equals("abandoned", StringComparison.OrdinalIgnoreCase) ?? false))
{
@@ -239,6 +244,10 @@ namespace Barotrauma
currentFlags.Add("Hostage");
}
}
if (speaker.IsEscorted)
{
currentFlags.Add("escort");
}
}
return currentFlags;
@@ -325,43 +334,15 @@ namespace Barotrauma
foreach (Character potentialSpeaker in availableSpeakers)
{
//check if the character has an appropriate job to say the line
if ((potentialSpeaker.Info?.Job != null && potentialSpeaker.Info.Job.Prefab.OnlyJobSpecificDialog) ||
selectedConversation.AllowedJobs.Count > 0)
if (CheckSpeakerViability(potentialSpeaker, selectedConversation, assignedSpeakers.Values.ToList(), ignoreFlags))
{
if (!selectedConversation.AllowedJobs.Contains(potentialSpeaker.Info?.Job.Prefab)) { continue; }
allowedSpeakers.Add(potentialSpeaker);
}
//check if the character has all required flags to say the line
if (!ignoreFlags)
{
var characterFlags = GetCurrentFlags(potentialSpeaker);
if (!selectedConversation.Flags.All(flag => characterFlags.Contains(flag))) { continue; }
}
//check if the character is close enough to hear the rest of the speakers
if (assignedSpeakers.Values.Any(s => !potentialSpeaker.CanHearCharacter(s))) { continue; }
//check if the character has an appropriate personality
if (selectedConversation.allowedSpeakerTags.Count > 0)
{
if (potentialSpeaker.Info?.PersonalityTrait == null) { continue; }
if (!selectedConversation.allowedSpeakerTags.Any(t => potentialSpeaker.Info.PersonalityTrait.AllowedDialogTags.Any(t2 => t2 == t))) { continue; }
}
else
{
if (potentialSpeaker.Info?.PersonalityTrait != null &&
!potentialSpeaker.Info.PersonalityTrait.AllowedDialogTags.Contains("none"))
{
continue;
}
}
allowedSpeakers.Add(potentialSpeaker);
}
if (allowedSpeakers.Count == 0)
if (allowedSpeakers.Count == 0 || NextLineFailure(selectedConversation, availableSpeakers, allowedSpeakers, ignoreFlags))
{
allowedSpeakers.Clear();
potentialLines.Remove(selectedConversation);
}
else
@@ -385,6 +366,62 @@ namespace Barotrauma
CreateConversation(availableSpeakers, assignedSpeakers, selectedConversation, lineList, availableConversations);
}
static bool NextLineFailure(NPCConversation selectedConversation, List<Character> availableSpeakers, List<Character> allowedSpeakers, bool ignoreFlags)
{
if (selectedConversation.requireNextLine)
{
foreach (NPCConversation nextConversation in selectedConversation.Responses)
{
foreach (Character potentialNextSpeaker in availableSpeakers)
{
if (CheckSpeakerViability(potentialNextSpeaker, nextConversation, allowedSpeakers, ignoreFlags))
{
return false;
}
}
}
return true;
}
return false;
}
static bool CheckSpeakerViability(Character potentialSpeaker, NPCConversation selectedConversation, List<Character> checkedSpeakers, bool ignoreFlags)
{
//check if the character has an appropriate job to say the line
if ((potentialSpeaker.Info?.Job != null && potentialSpeaker.Info.Job.Prefab.OnlyJobSpecificDialog) || selectedConversation.AllowedJobs.Count > 0)
{
if (!selectedConversation.AllowedJobs.Contains(potentialSpeaker.Info?.Job.Prefab)) { return false; }
}
//check if the character has all required flags to say the line
if (!ignoreFlags)
{
var characterFlags = GetCurrentFlags(potentialSpeaker);
if (!selectedConversation.Flags.All(flag => characterFlags.Contains(flag))) { return false; }
}
//check if the character is close enough to hear the rest of the speakers
if (checkedSpeakers.Any(s => !potentialSpeaker.CanHearCharacter(s))) { return false; }
//check if the character is close enough to see the rest of the speakers (this should be replaced with a more performant method)
if (checkedSpeakers.Any(s => !potentialSpeaker.CanSeeCharacter(s))) { return false; }
//check if the character has an appropriate personality
if (selectedConversation.allowedSpeakerTags.Count > 0)
{
if (potentialSpeaker.Info?.PersonalityTrait == null) { return false; }
if (!selectedConversation.allowedSpeakerTags.Any(t => potentialSpeaker.Info.PersonalityTrait.AllowedDialogTags.Any(t2 => t2 == t))) { return false; }
}
else
{
if (potentialSpeaker.Info?.PersonalityTrait != null &&
!potentialSpeaker.Info.PersonalityTrait.AllowedDialogTags.Contains("none"))
{
return false;
}
}
return true;
}
private static NPCConversation GetRandomConversation(List<NPCConversation> conversations, bool avoidPreviouslyUsed)
{
if (!avoidPreviouslyUsed)

View File

@@ -6,16 +6,18 @@ using Barotrauma.Extensions;
namespace Barotrauma
{
abstract class AIObjective
abstract partial class AIObjective
{
public virtual float Devotion => AIObjectiveManager.baseDevotion;
public abstract string DebugTag { get; }
public abstract string Identifier { get; set; }
public virtual string DebugTag => Identifier;
public virtual bool ForceRun => false;
public virtual bool IgnoreUnsafeHulls => false;
public virtual bool AbandonWhenCannotCompleteSubjectives => true;
public virtual bool AllowSubObjectiveSorting => false;
public virtual bool ForceOrderPriority => true;
public virtual bool PrioritizeIfSubObjectivesActive => false;
/// <summary>
/// Can there be multiple objective instaces of the same type?
@@ -52,8 +54,17 @@ namespace Barotrauma
/// </summary>
public float Priority { get; set; }
public float BasePriority { get; set; }
public float PriorityModifier { get; private set; } = 1;
// For forcing the highest priority temporarily. Will reset after each priority calculation, so it will need to be kept alive by something.
public bool ForceHighestPriority { get; set; }
// For temporarily forcing walking. Will reset after each priority calculation, so it will need to be kept alive by something.
// The intention of this boolean to allow walking even when the priority is higher than AIObjectiveManager.RunPriority.
public bool ForceWalk { get; set; }
public bool IgnoreAtOutpost { get; set; }
public readonly Character character;
public readonly AIObjectiveManager objectiveManager;
public string Option { get; private set; }
@@ -102,6 +113,13 @@ namespace Barotrauma
return all;
}
#pragma warning disable CS0649
/// <summary>
/// Aborts the objective when this condition is true.
/// </summary>
public Func<AIObjective, bool> AbortCondition;
#pragma warning restore CS0649
/// <summary>
/// A single shot event. Automatically cleared after launching. Use OnCompleted method for implementing (internal) persistent behavior.
/// </summary>
@@ -217,18 +235,22 @@ namespace Barotrauma
protected bool IsAllowed
{
get
{
{
if (IgnoreAtOutpost && Level.IsLoadedOutpost && character.TeamID != CharacterTeamType.FriendlyNPC)
{
if (Submarine.MainSub != null && Submarine.MainSub.DockedTo.None(s => s.TeamID != CharacterTeamType.FriendlyNPC && s.TeamID != character.TeamID))
{
return false;
}
}
if (!AllowOutsideSubmarine && character.Submarine == null) { return false; }
if (AllowInAnySub) { return true; }
if (AllowInFriendlySubs && character.Submarine.TeamID == CharacterTeamType.FriendlyNPC) { return true; }
if ((AllowInFriendlySubs && character.Submarine.TeamID == CharacterTeamType.FriendlyNPC) || character.IsEscorted) { return true; }
return character.Submarine.TeamID == character.TeamID || character.Submarine.DockedTo.Any(sub => sub.TeamID == character.TeamID);
}
}
/// <summary>
/// Call this only when the priority needs to be recalculated. Use the cached Priority property when you don't need to recalculate.
/// </summary>
public virtual float GetPriority()
protected virtual float GetPriority()
{
bool isOrder = objectiveManager.IsOrder(this);
if (!IsAllowed)
@@ -248,6 +270,17 @@ namespace Barotrauma
return Priority;
}
/// <summary>
/// Call this only when the priority needs to be recalculated. Use the cached Priority property when you don't need to recalculate.
/// </summary>
public float CalculatePriority()
{
Priority = GetPriority();
ForceHighestPriority = false;
ForceWalk = false;
return Priority;
}
private void UpdateDevotion(float deltaTime)
{
var currentObjective = objectiveManager.CurrentObjective;
@@ -393,7 +426,17 @@ namespace Barotrauma
}
}
protected abstract bool Check();
protected virtual bool Check()
{
if (AbortCondition != null && AbortCondition(this))
{
Abandon = true;
return false;
}
return CheckObjectiveSpecific();
}
protected abstract bool CheckObjectiveSpecific();
private bool CheckState()
{

View File

@@ -9,7 +9,7 @@ namespace Barotrauma
{
class AIObjectiveChargeBatteries : AIObjectiveLoop<PowerContainer>
{
public override string DebugTag => "charge batteries";
public override string Identifier { get; set; } = "charge batteries";
public override bool AllowAutomaticItemUnequipping => true;
private IEnumerable<PowerContainer> batteryList;

View File

@@ -9,7 +9,7 @@ namespace Barotrauma
{
class AIObjectiveCleanupItem : AIObjective
{
public override string DebugTag => "cleanup item";
public override string Identifier { get; set; } = "cleanup item";
public override bool KeepDivingGearOn => true;
public override bool AllowAutomaticItemUnequipping => false;
@@ -26,7 +26,7 @@ namespace Barotrauma
this.item = item;
}
public override float GetPriority()
protected override float GetPriority()
{
if (!IsAllowed)
{
@@ -68,7 +68,7 @@ namespace Barotrauma
}
if (item.ParentInventory != null)
{
if (item.Container != null && !AIObjectiveCleanupItems.IsValidContainer(item.Container, character, allowUnloading: objectiveManager.HasOrders()))
if (item.Container != null && !AIObjectiveCleanupItems.IsValidContainer(item.Container, character, allowUnloading: objectiveManager.HasOrder<AIObjectiveCleanupItems>()))
{
// Target was picked up or moved by someone.
Abandon = true;
@@ -82,14 +82,14 @@ namespace Barotrauma
itemIndex = 0;
if (suitableContainer != null)
{
bool equip = item.HasTag(AIObjectiveFindDivingGear.HEAVY_DIVING_GEAR) || (
item.GetComponent<Wearable>() == null &&
bool equip = item.GetComponent<Holdable>() != null ||
item.AllowedSlots.None(s =>
s == InvSlotType.Card ||
s == InvSlotType.Head ||
s == InvSlotType.Headset ||
s == InvSlotType.InnerClothes ||
s == InvSlotType.OuterClothes));
s == InvSlotType.Card ||
s == InvSlotType.Head ||
s == InvSlotType.Headset ||
s == InvSlotType.InnerClothes ||
s == InvSlotType.OuterClothes);
TryAddSubObjective(ref decontainObjective, () => new AIObjectiveDecontainItem(character, item, objectiveManager, targetContainer: suitableContainer.GetComponent<ItemContainer>())
{
Equip = equip,
@@ -131,7 +131,7 @@ namespace Barotrauma
}
}
protected override bool Check() => IsCompleted;
protected override bool CheckObjectiveSpecific() => IsCompleted;
public override void Reset()
{

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