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