Build 0.18.2.0

This commit is contained in:
Markus Isberg
2022-05-19 23:43:21 +09:00
parent d4f6f4cf88
commit 077917fa5d
115 changed files with 1080 additions and 1763 deletions
@@ -275,7 +275,11 @@ namespace Barotrauma
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "chooserandom":
LoadSubElement(subElement.Elements().ToArray().GetRandom(random));
var subElements = subElement.Elements();
if (subElements.Any())
{
LoadSubElement(subElements.ToArray().GetRandom(random));
}
break;
default:
LoadSubElement(subElement);
@@ -85,7 +85,7 @@ namespace Barotrauma
public readonly Identifier[] ForbiddenAmmunition;
public static WreckAIConfig GetRandom() => Prefabs.GetRandom(Rand.RandSync.ServerAndClient);
public static WreckAIConfig GetRandom() => Prefabs.OrderBy(p => p.UintIdentifier).GetRandom(Rand.RandSync.ServerAndClient);
protected override Identifier DetermineIdentifier(XElement element)
{
@@ -94,11 +94,67 @@ namespace Barotrauma
public Vector2 SheetIndex => Preset.SheetIndex;
public ContentXElement HairElement => CharacterInfo.Hairs?.ElementAtOrDefault(HairIndex);
public ContentXElement HairWithHatElement => CharacterInfo.Hairs?.ElementAtOrDefault(HairWithHatIndex);
public ContentXElement BeardElement => CharacterInfo.Beards?.ElementAtOrDefault(BeardIndex);
public ContentXElement MoustacheElement => CharacterInfo.Moustaches?.ElementAtOrDefault(MoustacheIndex);
public ContentXElement FaceAttachment => CharacterInfo.FaceAttachments?.ElementAtOrDefault(FaceAttachmentIndex);
public ContentXElement HairElement
{
get
{
if (CharacterInfo.Hairs == null) { return null; }
if (hairIndex >= CharacterInfo.Hairs.Count)
{
DebugConsole.AddWarning($"Hair index out of range (character: {CharacterInfo?.Name ?? "null"}, index: {hairIndex})");
}
return CharacterInfo.Hairs.ElementAtOrDefault(hairIndex);
}
}
public ContentXElement HairWithHatElement
{
get
{
if (CharacterInfo.Hairs == null) { return null; }
if (HairWithHatIndex >= CharacterInfo.Hairs.Count)
{
DebugConsole.AddWarning($"Hair with hat index out of range (character: {CharacterInfo?.Name ?? "null"}, index: {HairWithHatIndex})");
}
return CharacterInfo.Hairs.ElementAtOrDefault(HairWithHatIndex);
}
}
public ContentXElement BeardElement
{
get
{
if (CharacterInfo.Beards == null) { return null; }
if (BeardIndex >= CharacterInfo.Beards.Count)
{
DebugConsole.AddWarning($"Beard index out of range (character: {CharacterInfo?.Name ?? "null"}, index: {BeardIndex})");
}
return CharacterInfo.Beards.ElementAtOrDefault(BeardIndex);
}
}
public ContentXElement MoustacheElement
{
get
{
if (CharacterInfo.Moustaches == null) { return null; }
if (MoustacheIndex >= CharacterInfo.Moustaches.Count)
{
DebugConsole.AddWarning($"Moustache index out of range (character: {CharacterInfo?.Name ?? "null"}, index: {MoustacheIndex})");
}
return CharacterInfo.Moustaches.ElementAtOrDefault(MoustacheIndex);
}
}
public ContentXElement FaceAttachment
{
get
{
if (CharacterInfo.FaceAttachments == null) { return null; }
if (FaceAttachmentIndex >= CharacterInfo.FaceAttachments.Count)
{
DebugConsole.AddWarning($"Face attachment index out of range (character: {CharacterInfo?.Name ?? "null"}, index: {FaceAttachmentIndex})");
}
return CharacterInfo.FaceAttachments.ElementAtOrDefault(FaceAttachmentIndex);
}
}
public HeadInfo(CharacterInfo characterInfo, HeadPreset headPreset, int hairIndex = 0, int beardIndex = 0, int moustacheIndex = 0, int faceAttachmentIndex = 0)
{
@@ -130,6 +186,10 @@ namespace Barotrauma
head = value;
HeadSprite = null;
AttachmentSprites = null;
hairs = null;
beards = null;
moustaches = null;
faceAttachments = null;
}
}
}
@@ -843,7 +903,14 @@ namespace Barotrauma
public void RecreateHead(ImmutableHashSet<Identifier> tags, int hairIndex, int beardIndex, int moustacheIndex, int faceAttachmentIndex)
{
HeadPreset headPreset = Prefab.Heads.FirstOrDefault(h => h.TagSet.SetEquals(tags));
if (headPreset == null) { headPreset = Prefab.Heads.GetRandomUnsynced(); }
if (headPreset == null)
{
if (tags.Count == 1)
{
headPreset = Prefab.Heads.FirstOrDefault(h => h.TagSet.Contains(tags.First()));
}
headPreset ??= Prefab.Heads.GetRandomUnsynced();
}
head = new HeadInfo(this, headPreset, hairIndex, beardIndex, moustacheIndex, faceAttachmentIndex);
ReloadHeadAttachments();
}
@@ -352,7 +352,7 @@ namespace Barotrauma
public Vector2 Position
{
get { return ConvertUnits.ToDisplayUnits(body.SimPosition); }
get { return ConvertUnits.ToDisplayUnits(body?.SimPosition ?? Vector2.Zero); }
}
public Vector2 SimPosition
@@ -574,7 +574,7 @@ namespace Barotrauma
public float AggressionGreed { get; private set; }
[Serialize(0f, IsPropertySaveable.Yes, description: "If the health drops below this threshold, the character flees. In percentages."), Editable(minValue: 0f, maxValue: 100f)]
public float FleeHealthThreshold { get; private set; }
public float FleeHealthThreshold { get; set; }
[Serialize(false, IsPropertySaveable.Yes, description: "Does the character attack when provoked? When enabled, overrides the predefined targeting state with Attack and increases the priority of it."), Editable()]
public bool AttackWhenProvoked { get; private set; }
@@ -1122,7 +1122,7 @@ namespace Barotrauma
{
var gamesession = new GameSession(
SubmarineInfo.SavedSubmarines.GetRandomUnsynced(s => s.Type == SubmarineType.Player && !s.HasTag(SubmarineTag.HideInMenus)),
GameModePreset.DevSandbox);
GameModePreset.DevSandbox ?? GameModePreset.Sandbox);
string seed = ToolBox.RandomSeed(16);
gamesession.StartRound(seed);
@@ -33,13 +33,13 @@ namespace Barotrauma
public enum AbilityEffectType
{
Undefined,
None,
None,
OnAttack,
OnAttackResult,
OnAttacked,
OnAttackedResult,
OnGainSkillPoint,
OnAllyGainSkillPoint,
OnGainSkillPoint,
OnAllyGainSkillPoint,
OnRepairComplete,
OnItemFabricationSkillGain,
OnItemFabricatedAmount,
@@ -155,4 +155,10 @@ namespace Barotrauma
Player = 0b10,
Both = Bot | Player
}
}
public enum NumberType
{
Int,
Float
}
}
@@ -313,10 +313,14 @@ namespace Barotrauma
bool isValid = e is Character character && !character.Removed && !character.IsDead && !character.IsIncapacitated &&
(e == Character.Controlled || character.IsRemotePlayer);
#if SERVER
UpdateIgnoredClients();
isValid &= !ignoredClients.Keys.Any(c => c.Character == e);
if (!dialogOpened)
{
UpdateIgnoredClients();
isValid &= !ignoredClients.Keys.Any(c => c.Character == e);
}
#elif CLIENT
isValid &= (e != Character.Controlled || !GUI.InputBlockingMenuOpen);
bool block = GUI.InputBlockingMenuOpen && !dialogOpened;
isValid &= (e != Character.Controlled || !block);
#endif
return isValid;
}
@@ -135,8 +135,10 @@ namespace Barotrauma
monster.Enabled = false;
if (monster.Params.AI != null && monster.Params.AI.EnforceAggressiveBehaviorForMissions)
{
monster.Params.AI.FleeHealthThreshold = 0;
foreach (var targetParam in monster.Params.AI.Targets)
{
if (targetParam.Tag.Equals("engine", StringComparison.OrdinalIgnoreCase)) { continue; }
switch (targetParam.State)
{
case AIState.Avoid:
@@ -12,6 +12,7 @@ namespace Barotrauma
private readonly Dictionary<Identifier, List<Entity>> cachedTargets = new Dictionary<Identifier, List<Entity>>();
private int prevEntityCount;
private int prevPlayerCount, prevBotCount;
private Character prevControlled;
private readonly string[] requiredDestinationTypes;
public readonly bool RequireBeaconStation;
@@ -163,12 +164,13 @@ namespace Barotrauma
botCount++;
}
}
if (Entity.EntityCount != prevEntityCount || botCount != prevBotCount || playerCount != prevPlayerCount)
if (Entity.EntityCount != prevEntityCount || botCount != prevBotCount || playerCount != prevPlayerCount || prevControlled != Character.Controlled)
{
cachedTargets.Clear();
prevEntityCount = Entity.EntityCount;
prevBotCount = botCount;
prevPlayerCount = playerCount;
prevControlled = Character.Controlled;
}
if (!Actions.Any())
@@ -339,11 +339,6 @@ namespace Barotrauma
loadContext = null;
assembly = null;
}
~Implementation()
{
OnQuit();
}
}
private static Implementation? loadedImplementation;
@@ -1,9 +1,8 @@
using Barotrauma.Items.Components;
using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using System;
using System.Collections.Generic;
using System.Linq;
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
namespace Barotrauma
{
@@ -15,29 +14,29 @@ namespace Barotrauma
{
if (GameMain.NetworkMember != null && !GameMain.NetworkMember.IsServer) { return; }
bool skipMainSubs = GameMain.GameSession.GameMode is CampaignMode { IsFirstRound: false };
if (!skipMainSubs)
//player has more than one sub = we must have given the start items already
bool startItemsGiven = GameMain.GameSession?.OwnedSubmarines != null && GameMain.GameSession.OwnedSubmarines.Count > 1;
if (!startItemsGiven)
{
if (Submarine.MainSub is Submarine mainSub && mainSub.Info.IsPlayer)
{
SpawnStartItems(mainSub);
}
for (int i = 0; i < Submarine.MainSubs.Length; i++)
{
var sub = Submarine.MainSubs[i];
if (sub == null || sub.Info.InitialSuppliesSpawned) { continue; }
if (sub == null || sub.Info.InitialSuppliesSpawned || !sub.Info.IsPlayer) { continue; }
//1st pass: items defined in the start item set, only spawned in the main sub (not drones/shuttles or other linked subs)
SpawnStartItems(sub);
//2nd pass: items defined using preferred containers, spawned in the main sub and all the linked subs (drones, shuttles etc)
var subs = sub.GetConnectedSubs().Where(s => s.TeamID == sub.TeamID);
CreateAndPlace(subs);
subs.ForEach(s => s.Info.InitialSuppliesSpawned = true);
}
}
//spawn items in wrecks, beacon stations and pirate subs
foreach (var sub in Submarine.Loaded)
{
if (sub.Info.Type == SubmarineType.Player ||
sub.Info.Type == SubmarineType.Outpost ||
sub.Info.Type == SubmarineType.OutpostModule ||
sub.Info.Type == SubmarineType.EnemySubmarine)
sub.Info.Type == SubmarineType.OutpostModule)
{
continue;
}
@@ -64,6 +63,10 @@ namespace Barotrauma
}
public static Identifier StartItemSet = new Identifier("normal");
/// <summary>
/// Spawns the items defined in the start item set in the specified sub.
/// </summary>
private static void SpawnStartItems(Submarine sub)
{
if (!Barotrauma.StartItemSet.Sets.TryGet(StartItemSet, out StartItemSet itemSet))
@@ -1081,15 +1081,10 @@ namespace Barotrauma
{
bool hasNewPendingSub = Campaign.PendingSubmarineSwitch != null &&
Campaign.PendingSubmarineSwitch.MD5Hash.StringRepresentation != Submarine.Info.MD5Hash.StringRepresentation;
if (hasNewPendingSub)
{
Campaign.SwitchSubs();
}
else
{
SubmarineInfo = new SubmarineInfo(Submarine);
}
}
rootElement.Add(new XAttribute("submarine", SubmarineInfo == null ? "" : SubmarineInfo.Name));
if (OwnedSubmarines != null)
@@ -621,7 +621,7 @@ namespace Barotrauma.Items.Components
hullRects[i].X -= expand;
hullRects[i].Width += expand * 2;
hullRects[i].Location -= MathUtils.ToPoint(subs[i].WorldPosition - subs[i].HiddenSubPosition);
hulls[i] = new Hull(MapEntityPrefab.Find(null, "hull"), hullRects[i], subs[i]);
hulls[i] = new Hull(hullRects[i], subs[i]);
hulls[i].RoomName = IsHorizontal ? "entityname.dockingport" : "entityname.dockinghatch";
hulls[i].AddToGrid(subs[i]);
hulls[i].FreeID();
@@ -744,7 +744,7 @@ namespace Barotrauma.Items.Components
hullRects[i].Y += expand;
hullRects[i].Height += expand * 2;
hullRects[i].Location -= MathUtils.ToPoint(subs[i].WorldPosition - subs[i].HiddenSubPosition);
hulls[i] = new Hull(MapEntityPrefab.Find(null, "hull"), hullRects[i], subs[i]);
hulls[i] = new Hull(hullRects[i], subs[i]);
hulls[i].RoomName = IsHorizontal ? "entityname.dockingport" : "entityname.dockinghatch";
hulls[i].AddToGrid(subs[i]);
hulls[i].FreeID();
@@ -3,7 +3,6 @@ using FarseerPhysics;
using Microsoft.Xna.Framework;
using System;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
@@ -79,7 +79,7 @@ namespace Barotrauma.Items.Components
{
get
{
Matrix bodyTransform = Matrix.CreateRotationZ(item.body == null ? MathHelper.ToRadians(item.Rotation) : item.body.Rotation);
Matrix bodyTransform = Matrix.CreateRotationZ(item.body == null ? item.RotationRad : item.body.Rotation);
Vector2 flippedPos = barrelPos;
if (item.body != null && item.body.Dir < 0.0f) { flippedPos.X = -flippedPos.X; }
return Vector2.Transform(flippedPos, bodyTransform) * item.Scale;
@@ -17,11 +17,13 @@ namespace Barotrauma.Items.Components
public readonly Item Item;
public readonly StatusEffect StatusEffect;
public readonly bool ExcludeBroken;
public ActiveContainedItem(Item item, StatusEffect statusEffect, bool excludeBroken)
public readonly bool ExcludeFullCondition;
public ActiveContainedItem(Item item, StatusEffect statusEffect, bool excludeBroken, bool excludeFullCondition)
{
Item = item;
StatusEffect = statusEffect;
ExcludeBroken = excludeBroken;
ExcludeFullCondition = excludeFullCondition;
}
}
@@ -300,7 +302,7 @@ namespace Barotrauma.Items.Components
if (!containableItem.MatchesItem(containedItem)) { continue; }
foreach (StatusEffect effect in containableItem.statusEffects)
{
activeContainedItems.Add(new ActiveContainedItem(containedItem, effect, containableItem.ExcludeBroken));
activeContainedItems.Add(new ActiveContainedItem(containedItem, effect, containableItem.ExcludeBroken, containableItem.ExcludeFullCondition));
}
}
}
@@ -408,6 +410,7 @@ namespace Barotrauma.Items.Components
Item contained = activeContainedItem.Item;
if (activeContainedItem.ExcludeBroken && contained.Condition <= 0.0f) { continue; }
if (activeContainedItem.ExcludeFullCondition && contained.IsFullCondition) { continue; }
StatusEffect effect = activeContainedItem.StatusEffect;
if (effect.HasTargetType(StatusEffect.TargetType.This))
@@ -569,7 +572,7 @@ namespace Barotrauma.Items.Components
transformedItemPos += new Vector2(item.Rect.X, item.Rect.Y);
if (Math.Abs(item.Rotation) > 0.01f)
{
Matrix transform = Matrix.CreateRotationZ(MathHelper.ToRadians(-item.Rotation));
Matrix transform = Matrix.CreateRotationZ(-item.RotationRad);
transformedItemPos = Vector2.Transform(transformedItemPos - item.Position, transform) + item.Position;
transformedItemInterval = Vector2.Transform(transformedItemInterval, transform);
transformedItemIntervalHorizontal = Vector2.Transform(transformedItemIntervalHorizontal, transform);
@@ -600,7 +603,7 @@ namespace Barotrauma.Items.Components
}
else
{
currentRotation += MathHelper.ToRadians(-item.Rotation);
currentRotation += -item.RotationRad;
}
int i = 0;
@@ -161,7 +161,7 @@ namespace Barotrauma.Items.Components
hullData.ReceivedWaterAmount = null;
if (fromWaterDetector)
{
hullData.ReceivedWaterAmount = Math.Min(sourceHull.WaterVolume / sourceHull.Volume, 1.0f);
hullData.ReceivedWaterAmount = WaterDetector.GetWaterPercentage(sourceHull);
}
foreach (var linked in sourceHull.linkedTo)
{
@@ -174,7 +174,7 @@ namespace Barotrauma.Items.Components
linkedHullData.ReceivedWaterAmount = null;
if (fromWaterDetector)
{
linkedHullData.ReceivedWaterAmount = Math.Min(linkedHull.WaterVolume / linkedHull.Volume, 1.0f);
linkedHullData.ReceivedWaterAmount = WaterDetector.GetWaterPercentage(linkedHull);
}
}
break;
@@ -68,6 +68,8 @@ namespace Barotrauma.Items.Components
private const float ConnectedSubUpdateInterval = 1.0f;
float connectedSubUpdateTimer;
private double lastReceivedSteeringSignalTime;
public bool AutoPilot
{
get { return autoPilot; }
@@ -312,16 +314,20 @@ namespace Barotrauma.Items.Components
}
else if (AutoPilot)
{
UpdateAutoPilot(deltaTime);
float throttle = 1.0f;
if (controlledSub != null)
//signals override autopilot for a duration of one second
if (lastReceivedSteeringSignalTime < Timing.TotalTime - 1)
{
//if the sub is heading in the correct direction, throttle the speed according to the user's skill
//if it's e.g. sinking due to extra water, don't throttle, but allow emptying up the ballast completely
throttle = MathHelper.Clamp(Vector2.Dot(controlledSub.Velocity, TargetVelocity) / 100.0f, 0.0f, 1.0f);
UpdateAutoPilot(deltaTime);
float throttle = 1.0f;
if (controlledSub != null)
{
//if the sub is heading in the correct direction, throttle the speed according to the user's skill
//if it's e.g. sinking due to extra water, don't throttle, but allow emptying up the ballast completely
throttle = MathHelper.Clamp(Vector2.Dot(controlledSub.Velocity, TargetVelocity) / 100.0f, 0.0f, 1.0f);
}
float maxSpeed = MathHelper.Lerp(AutoPilotMaxSpeed, AIPilotMaxSpeed, userSkill) * 100.0f;
TargetVelocity = TargetVelocity.ClampLength(MathHelper.Lerp(100.0f, maxSpeed, throttle));
}
float maxSpeed = MathHelper.Lerp(AutoPilotMaxSpeed, AIPilotMaxSpeed, userSkill) * 100.0f;
TargetVelocity = TargetVelocity.ClampLength(MathHelper.Lerp(100.0f, maxSpeed, throttle));
}
else
{
@@ -821,6 +827,7 @@ namespace Barotrauma.Items.Components
steeringInput.X = MathHelper.Clamp(steeringInput.X, -100.0f, 100.0f);
steeringInput.Y = MathHelper.Clamp(-steeringInput.Y, -100.0f, 100.0f);
TargetVelocity = steeringInput;
lastReceivedSteeringSignalTime = Timing.TotalTime;
}
else
{
@@ -150,7 +150,7 @@ namespace Barotrauma.Items.Components
{
if (powerOut?.Grid != null) { return powerOut.Grid.Voltage; }
}
return currPowerConsumption <= 0.0f ? 1.0f : voltage;
return PowerConsumption <= 0.0f ? 1.0f : voltage;
}
set
{
@@ -158,21 +158,7 @@ namespace Barotrauma.Items.Components
}
}
public bool PoweredByTinkering
{
get
{
if (this is PowerContainer) { return false; }
foreach (Repairable repairable in Item.Repairables)
{
if (repairable.IsTinkering && repairable.TinkeringPowersDevices)
{
return true;
}
}
return false;
}
}
public bool PoweredByTinkering { get; set; }
[Editable, Serialize(true, IsPropertySaveable.Yes, description: "Can the item be damaged by electomagnetic pulses.")]
public bool VulnerableToEMP
@@ -965,7 +965,8 @@ namespace Barotrauma.Items.Components
{
item.body.LinearVelocity *= deflectedSpeedMultiplier;
}
else if ( stickJoint == null && StickTarget == null &&
else if ( remainingHits <= 0 &&
stickJoint == null && StickTarget == null &&
StickToStructures && target.Body.UserData is Structure ||
((StickToLightTargets || target.Body.Mass > item.body.Mass * 0.5f) &&
(DoesStick ||
@@ -56,7 +56,8 @@ namespace Barotrauma.Items.Components
if (value == qualityLevel) { return; }
bool wasInFullCondition = item.IsFullCondition;
qualityLevel = MathHelper.Clamp(value, 0, MaxQuality);
qualityLevel = MathHelper.Clamp(value, 0, MaxQuality);
item.RecalculateConditionValues();
//set the condition to the new max condition
if (wasInFullCondition && statValues.ContainsKey(StatType.Condition))
{
@@ -106,7 +106,25 @@ namespace Barotrauma.Items.Components
}
}
public bool IsTinkering { get; private set; } = false;
private bool isTinkering;
public bool IsTinkering
{
get { return isTinkering; }
private set
{
if (isTinkering == value) { return; }
isTinkering = value;
if (tinkeringPowersDevices)
{
foreach (Powered powered in item.GetComponents<Powered>())
{
if (powered is PowerContainer) { continue; }
powered.PoweredByTinkering = isTinkering;
}
}
}
}
public Character CurrentFixer { get; private set; }
private Item currentRepairItem;
@@ -160,7 +160,8 @@ namespace Barotrauma.Items.Components
public override void Update(float deltaTime, Camera cam)
{
if (source == null || target == null || target.Removed ||
(source is Entity sourceEntity && sourceEntity.Removed))
(source is Entity sourceEntity && sourceEntity.Removed) ||
(source is Limb limb && limb.Removed))
{
ResetSource();
target = null;
@@ -3,6 +3,7 @@ using Barotrauma.Networking;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Globalization;
namespace Barotrauma.Items.Components
{
@@ -34,13 +35,17 @@ namespace Barotrauma.Items.Components
public Identifier PropertyName { get; }
public bool TargetOnlyParentProperty { get; }
public int NumberInputMin { get; }
public int NumberInputMax { get; }
public string NumberInputMin { get; }
public string NumberInputMax { get; }
public string NumberInputStep { get; }
public int NumberInputDecimalPlaces { get; }
public int MaxTextLength { get; }
public const int DefaultNumberInputMin = 0, DefaultNumberInputMax = 99;
public bool IsIntegerInput { get; }
public const string DefaultNumberInputMin = "0", DefaultNumberInputMax = "99", DefaultNumberInputStep = "1";
public const int DefaultNumberInputDecimalPlaces = 0;
public bool IsNumberInput { get; }
public NumberType? NumberType { get; }
public bool HasPropertyName { get; }
public bool ShouldSetProperty { get; set; }
@@ -60,11 +65,34 @@ namespace Barotrauma.Items.Components
ConnectionName = element.GetAttributeString("connection", "");
PropertyName = element.GetAttributeIdentifier("propertyname", "");
TargetOnlyParentProperty = element.GetAttributeBool("targetonlyparentproperty", false);
NumberInputMin = element.GetAttributeInt("min", DefaultNumberInputMin);
NumberInputMax = element.GetAttributeInt("max", DefaultNumberInputMax);
NumberInputMin = element.GetAttributeString("min", DefaultNumberInputMin);
NumberInputMax = element.GetAttributeString("max", DefaultNumberInputMax);
NumberInputStep = element.GetAttributeString("step", DefaultNumberInputStep);
NumberInputDecimalPlaces = element.GetAttributeInt("decimalplaces", DefaultNumberInputDecimalPlaces);
MaxTextLength = element.GetAttributeInt("maxtextlength", int.MaxValue);
HasPropertyName = !PropertyName.IsEmpty;
IsIntegerInput = HasPropertyName && element.Name.ToString().ToLowerInvariant() == "integerinput";
if (HasPropertyName)
{
string elementName = element.Name.ToString().ToLowerInvariant();
IsNumberInput = elementName == "numberinput" || elementName == "integerinput"; // backwards compatibility
if (IsNumberInput)
{
string numberType = element.GetAttributeString("numbertype", string.Empty);
switch (numberType)
{
case "f":
case "float":
NumberType = Barotrauma.NumberType.Float;
break;
case "int":
case "integer":
default: // backwards compatibility
NumberType = Barotrauma.NumberType.Int;
break;
}
}
}
if (element.GetAttribute("signal") is XAttribute attribute)
{
@@ -152,7 +180,8 @@ namespace Barotrauma.Items.Components
{
case "button":
case "textbox":
case "integerinput":
case "integerinput": // backwards compatibility
case "numberinput":
var button = new CustomInterfaceElement(item, subElement, this)
{
ContinuousSignal = false
@@ -317,6 +346,24 @@ namespace Barotrauma.Items.Components
}
}
private void ValueChanged(CustomInterfaceElement numberInputElement, float value)
{
if (numberInputElement == null) { return; }
numberInputElement.Signal = value.ToString();
if (!numberInputElement.TargetOnlyParentProperty)
{
foreach (ISerializableEntity e in item.AllPropertyObjects)
{
if (!e.SerializableProperties.ContainsKey(numberInputElement.PropertyName)) { continue; }
e.SerializableProperties[numberInputElement.PropertyName].TrySetValue(e, value);
}
}
else if (SerializableProperties.ContainsKey(numberInputElement.PropertyName))
{
SerializableProperties[numberInputElement.PropertyName].TrySetValue(this, value);
}
}
public override void Update(float deltaTime, Camera cam)
{
foreach (CustomInterfaceElement ciElement in customInterfaceElementList)
@@ -341,5 +388,10 @@ namespace Barotrauma.Items.Components
signals = customInterfaceElementList.Select(ci => ci.Signal).ToArray();
return base.Save(parentElement);
}
private static bool TryParseFloatInvariantCulture(string s, out float f)
{
return float.TryParse(s, NumberStyles.Any, CultureInfo.InvariantCulture, out f);
}
}
}
@@ -64,6 +64,11 @@ namespace Barotrauma.Items.Components
IsActive = true;
}
public static int GetWaterPercentage(Hull hull)
{
return hull.WaterVolume > 1.0f ? MathHelper.Clamp((int)Math.Ceiling(hull.WaterPercentage), 0, 100) : 0;
}
public override void Update(float deltaTime, Camera cam)
{
if (stateSwitchDelay > 0.0f)
@@ -103,12 +108,7 @@ namespace Barotrauma.Items.Components
if (item.CurrentHull != null)
{
int waterPercentage = 0;
//ignore minuscule amounts of water
if (item.CurrentHull.WaterVolume > 1.0f)
{
waterPercentage = MathHelper.Clamp((int)Math.Ceiling(item.CurrentHull.WaterPercentage), 0, 100);
}
int waterPercentage = GetWaterPercentage(item.CurrentHull);
if (prevSentWaterPercentageValue != waterPercentage || waterPercentageSignal == null)
{
prevSentWaterPercentageValue = waterPercentage;
@@ -350,7 +350,7 @@ namespace Barotrauma.Items.Components
if (lightComponent != null)
{
lightComponent.Parent = null;
lightComponent.Rotation = Rotation - MathHelper.ToRadians(item.Rotation);
lightComponent.Rotation = Rotation - item.RotationRad;
lightComponent.Light.Rotation = -rotation;
}
#endif
@@ -516,7 +516,7 @@ namespace Barotrauma.Items.Components
{
if (lightComponent != null)
{
lightComponent.Rotation = Rotation - MathHelper.ToRadians(item.Rotation);
lightComponent.Rotation = Rotation - item.RotationRad;
}
}
@@ -262,19 +262,19 @@ namespace Barotrauma
}
}
private float rotationRad;
public float RotationRad { get; private set; }
[ConditionallyEditable(ConditionallyEditable.ConditionType.AllowRotating, MinValueFloat = 0.0f, MaxValueFloat = 360.0f, DecimalCount = 1, ValueStep = 1f), Serialize(0.0f, IsPropertySaveable.Yes)]
public float Rotation
{
get
{
return MathHelper.ToDegrees(rotationRad);
return MathHelper.ToDegrees(RotationRad);
}
set
{
if (!Prefab.AllowRotatingInEditor) { return; }
rotationRad = MathHelper.ToRadians(value);
RotationRad = MathHelper.ToRadians(value);
#if CLIENT
if (Screen.Selected == GameMain.SubEditorScreen)
{
@@ -472,9 +472,9 @@ namespace Barotrauma
get { return spriteColor; }
}
public bool IsFullCondition => MathUtils.NearlyEqual(Condition, MaxCondition);
public float MaxCondition => Prefab.Health * healthMultiplier * maxRepairConditionMultiplier * (1.0f + GetQualityModifier(Items.Components.Quality.StatType.Condition));
public float ConditionPercentage => MathUtils.Percentage(Condition, MaxCondition);
public bool IsFullCondition { get; private set; }
public float MaxCondition { get; private set; }
public float ConditionPercentage { get; private set; }
private float offsetOnSelectedMultiplier = 1.0f;
@@ -495,7 +495,8 @@ namespace Barotrauma
{
float prevConditionPercentage = ConditionPercentage;
healthMultiplier = MathHelper.Clamp(value, 0.0f, float.PositiveInfinity);
Condition = MaxCondition * prevConditionPercentage / 100.0f;
condition = MaxCondition * prevConditionPercentage / 100.0f;
RecalculateConditionValues();
}
}
@@ -505,7 +506,11 @@ namespace Barotrauma
public float MaxRepairConditionMultiplier
{
get => maxRepairConditionMultiplier;
set { maxRepairConditionMultiplier = MathHelper.Clamp(value, 0.0f, float.PositiveInfinity); }
set
{
maxRepairConditionMultiplier = MathHelper.Clamp(value, 0.0f, float.PositiveInfinity);
RecalculateConditionValues();
}
}
//the default value should be Prefab.Health, but because we can't use it in the attribute,
@@ -806,7 +811,9 @@ namespace Barotrauma
defaultRect = newRect;
rect = newRect;
condition = MaxCondition;
condition = MaxCondition = Prefab.Health;
ConditionPercentage = 100.0f;
lastSentCondition = condition;
AllowDeconstruct = itemPrefab.AllowDeconstruct;
@@ -1002,6 +1009,7 @@ namespace Barotrauma
ApplyStatusEffects(ActionType.OnSpawn, 1.0f);
Components.ForEach(c => c.ApplyStatusEffects(ActionType.OnSpawn, 1.0f));
RecalculateConditionValues();
}
partial void InitProjSpecific();
@@ -1184,7 +1192,6 @@ namespace Barotrauma
public void RemoveContained(Item contained)
{
ownInventory?.RemoveItem(contained);
contained.Container = null;
}
@@ -1611,6 +1618,10 @@ namespace Barotrauma
bool wasInFullCondition = IsFullCondition;
condition = MathHelper.Clamp(value, 0.0f, MaxCondition);
if (MathUtils.NearlyEqual(prev, condition, epsilon: 0.000001f)) { return; }
RecalculateConditionValues();
if (condition == 0.0f && prev > 0.0f)
{
//Flag connections to be updated as device is broken
@@ -1672,6 +1683,17 @@ namespace Barotrauma
}
}
/// <summary>
/// Recalculates the item's maximum condition, condition percentage and whether it's in full condition.
/// You generally never need to call this manually - done automatically when any of the factors that affect the values change.
/// </summary>
public void RecalculateConditionValues()
{
MaxCondition = Prefab.Health * healthMultiplier * maxRepairConditionMultiplier * (1.0f + GetQualityModifier(Items.Components.Quality.StatType.Condition));
IsFullCondition = MathUtils.NearlyEqual(Condition, MaxCondition);
ConditionPercentage = MathUtils.Percentage(Condition, MaxCondition);
}
private bool IsInWater()
{
if (CurrentHull == null) { return true; }
@@ -1999,7 +2021,7 @@ namespace Barotrauma
if (Prefab.AllowRotatingInEditor)
{
rotationRad = MathUtils.WrapAngleTwoPi(-rotationRad);
RotationRad = MathUtils.WrapAngleTwoPi(-RotationRad);
}
#if CLIENT
if (Prefab.CanSpriteFlipX)
@@ -3153,12 +3175,12 @@ namespace Barotrauma
{
Vector2 oldRelativeOrigin = (oldPrefab.SwappableItem.SwapOrigin - oldPrefab.Size / 2) * element.GetAttributeFloat(item.scale, "scale", "Scale");
oldRelativeOrigin.Y = -oldRelativeOrigin.Y;
oldRelativeOrigin = MathUtils.RotatePoint(oldRelativeOrigin, -item.rotationRad);
oldRelativeOrigin = MathUtils.RotatePoint(oldRelativeOrigin, -item.RotationRad);
Vector2 oldOrigin = centerPos + oldRelativeOrigin;
Vector2 relativeOrigin = (prefab.SwappableItem.SwapOrigin - prefab.Size / 2) * item.Scale;
relativeOrigin.Y = -relativeOrigin.Y;
relativeOrigin = MathUtils.RotatePoint(relativeOrigin, -item.rotationRad);
relativeOrigin = MathUtils.RotatePoint(relativeOrigin, -item.RotationRad);
Vector2 origin = new Vector2(rect.X + rect.Width / 2, rect.Y - rect.Height / 2) + relativeOrigin;
item.rect.Location -= (origin - oldOrigin).ToPoint();
@@ -3194,6 +3216,7 @@ namespace Barotrauma
item.condition = MathHelper.Clamp(condition, 0, item.MaxCondition);
item.lastSentCondition = item.condition;
item.RecalculateConditionValues();
item.SetActiveSprite();
if (submarine?.Info.GameVersion != null)
@@ -36,6 +36,11 @@ namespace Barotrauma
/// </summary>
public bool ExcludeBroken { get; private set; }
/// <summary>
/// Should full condition (100%) items be excluded
/// </summary>
public bool ExcludeFullCondition { get; private set; }
public bool AllowVariants { get; private set; } = true;
public RelationType Type
@@ -102,14 +107,14 @@ namespace Barotrauma
return CheckContained(parentItem);
case RelationType.Container:
if (parentItem == null || parentItem.Container == null) { return MatchOnEmpty; }
return (!ExcludeBroken || parentItem.Container.Condition > 0.0f) && MatchesItem(parentItem.Container);
return (!ExcludeBroken || parentItem.Container.Condition > 0.0f) && (!ExcludeFullCondition || !parentItem.Container.IsFullCondition) && MatchesItem(parentItem.Container);
case RelationType.Equipped:
if (character == null) { return false; }
if (MatchOnEmpty && !character.HeldItems.Any()) { return true; }
foreach (Item equippedItem in character.HeldItems)
{
if (equippedItem == null) { continue; }
if ((!ExcludeBroken || equippedItem.Condition > 0.0f) && MatchesItem(equippedItem)) { return true; }
if ((!ExcludeBroken || equippedItem.Condition > 0.0f) && (!ExcludeFullCondition || !equippedItem.IsFullCondition) && MatchesItem(equippedItem)) { return true; }
}
break;
case RelationType.Picked:
@@ -138,8 +143,7 @@ namespace Barotrauma
foreach (Item contained in parentItem.ContainedItems)
{
if (TargetSlot > -1 && parentItem.OwnInventory.FindIndex(contained) != TargetSlot) { continue; }
if ((!ExcludeBroken || contained.Condition > 0.0f) && MatchesItem(contained)) { return true; }
if ((!ExcludeBroken || contained.Condition > 0.0f) && (!ExcludeFullCondition || !contained.IsFullCondition) && MatchesItem(contained)) { return true; }
if (CheckContained(contained)) { return true; }
}
return false;
@@ -153,6 +157,7 @@ namespace Barotrauma
new XAttribute("optional", IsOptional),
new XAttribute("ignoreineditor", IgnoreInEditor),
new XAttribute("excludebroken", ExcludeBroken),
new XAttribute("excludefullcondition", ExcludeFullCondition),
new XAttribute("targetslot", TargetSlot),
new XAttribute("allowvariants", AllowVariants));
@@ -212,12 +217,12 @@ namespace Barotrauma
}
}
if (identifiers.Length == 0 && excludedIdentifiers.Length == 0 && !returnEmpty) { return null; }
RelatedItem ri = new RelatedItem(identifiers, excludedIdentifiers)
{
ExcludeBroken = element.GetAttributeBool("excludebroken", true),
ExcludeFullCondition = element.GetAttributeBool("excludefullcondition", false),
AllowVariants = element.GetAttributeBool("allowvariants", true)
};
string typeStr = element.GetAttributeString("type", "");
@@ -0,0 +1,36 @@
#nullable enable
using System.Collections.Immutable;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
internal class StartItem
{
public Identifier Item;
public int Amount;
public StartItem(XElement element)
{
Item = element.GetAttributeIdentifier("identifier", Identifier.Empty);
Amount = element.GetAttributeInt("amount", 1);
}
}
/// <summary>
/// Additive sets of items spawned only at the start of the game.
/// </summary>
internal class StartItemSet : PrefabWithUintIdentifier
{
public readonly static PrefabCollection<StartItemSet> Sets = new PrefabCollection<StartItemSet>();
public readonly ImmutableArray<StartItem> Items;
public StartItemSet(ContentXElement element, StartItemsFile file) : base(file, element.GetAttributeIdentifier("identifier", Identifier.Empty))
{
Items = element.Elements().Select(e => new StartItem(e!)).ToImmutableArray();
}
public override void Dispose() { }
}
}
@@ -25,6 +25,7 @@ namespace Barotrauma
IEnumerable<string> aliases = null)
: base(identifier)
{
System.Diagnostics.Debug.Assert(constructor != null);
this.constructor = constructor;
this.Name = TextManager.Get($"EntityName.{identifier}");
this.Description = TextManager.Get($"EntityDescription.{identifier}");
@@ -35,40 +36,52 @@ namespace Barotrauma
this.Aliases = (aliases ?? Enumerable.Empty<string>()).Concat(identifier.Value.ToEnumerable()).ToImmutableHashSet();
}
public static CoreEntityPrefab HullPrefab { get; private set; }
public static CoreEntityPrefab GapPrefab { get; private set; }
public static CoreEntityPrefab WayPointPrefab { get; private set; }
public static CoreEntityPrefab SpawnPointPrefab { get; private set; }
public static void InitCorePrefabs()
{
CoreEntityPrefab ep = new CoreEntityPrefab(
HullPrefab = new CoreEntityPrefab(
"hull".ToIdentifier(),
typeof(Hull).GetConstructor(new Type[] { typeof(MapEntityPrefab), typeof(Rectangle) }),
typeof(Hull).GetConstructor(new Type[] { typeof(Rectangle) }),
resizeHorizontal: true,
resizeVertical: true,
linkable: true,
allowedLinks: new Identifier[] { "hull".ToIdentifier() });
Prefabs.Add(ep, false);
Prefabs.Add(HullPrefab, false);
ep = new CoreEntityPrefab(
GapPrefab = new CoreEntityPrefab(
"gap".ToIdentifier(),
typeof(Gap).GetConstructor(new Type[] { typeof(MapEntityPrefab), typeof(Rectangle) }),
typeof(Gap).GetConstructor(new Type[] { typeof(Rectangle) }),
resizeHorizontal: true,
resizeVertical: true);
Prefabs.Add(ep, false);
Prefabs.Add(GapPrefab, false);
ep = new CoreEntityPrefab(
WayPointPrefab = new CoreEntityPrefab(
"waypoint".ToIdentifier(),
typeof(WayPoint).GetConstructor(new Type[] { typeof(MapEntityPrefab), typeof(Rectangle) }));
Prefabs.Add(ep, false);
Prefabs.Add(WayPointPrefab, false);
ep = new CoreEntityPrefab(
SpawnPointPrefab = new CoreEntityPrefab(
"spawnpoint".ToIdentifier(),
typeof(WayPoint).GetConstructor(new Type[] { typeof(MapEntityPrefab), typeof(Rectangle) }));
Prefabs.Add(ep, false);
Prefabs.Add(SpawnPointPrefab, false);
}
protected override void CreateInstance(Rectangle rect)
{
if (constructor == null) return;
object[] lobject = new object[] { this, rect };
constructor.Invoke(lobject);
if (this == WayPointPrefab || this == SpawnPointPrefab)
{
object[] lobject = new object[] { this, rect };
constructor.Invoke(lobject);
}
else
{
object[] lobject = new object[] { rect };
constructor.Invoke(lobject);
}
}
private bool disposed = false;
@@ -120,7 +120,7 @@ namespace Barotrauma
}
}
public Gap(MapEntityPrefab prefab, Rectangle rectangle)
public Gap(Rectangle rectangle)
: this(rectangle, Submarine.MainSub)
{
#if CLIENT
@@ -136,7 +136,7 @@ namespace Barotrauma
{ }
public Gap(Rectangle rect, bool isHorizontal, Submarine submarine, ushort id = Entity.NullEntityID)
: base(MapEntityPrefab.FindByIdentifier("gap".ToIdentifier()), submarine, id)
: base(CoreEntityPrefab.GapPrefab, submarine, id)
{
this.rect = rect;
flowForce = Vector2.Zero;
@@ -410,8 +410,8 @@ namespace Barotrauma
public BallastFloraBehavior BallastFlora { get; set; }
public Hull(MapEntityPrefab prefab, Rectangle rectangle)
: this (prefab, rectangle, Submarine.MainSub)
public Hull(Rectangle rectangle)
: this (rectangle, Submarine.MainSub)
{
#if CLIENT
if (SubEditorScreen.IsSubEditor())
@@ -421,8 +421,8 @@ namespace Barotrauma
#endif
}
public Hull(MapEntityPrefab prefab, Rectangle rectangle, Submarine submarine, ushort id = Entity.NullEntityID)
: base (prefab, submarine, id)
public Hull(Rectangle rectangle, Submarine submarine, ushort id = Entity.NullEntityID)
: base (CoreEntityPrefab.HullPrefab, submarine, id)
{
rect = rectangle;
@@ -500,7 +500,7 @@ namespace Barotrauma
public override MapEntity Clone()
{
var clone = new Hull(MapEntityPrefab.FindByIdentifier("hull".ToIdentifier()), rect, Submarine);
var clone = new Hull(rect, Submarine);
foreach (KeyValuePair<Identifier, SerializableProperty> property in SerializableProperties)
{
if (!property.Value.Attributes.OfType<Editable>().Any()) { continue; }
@@ -1543,7 +1543,7 @@ namespace Barotrauma
int.Parse(element.GetAttribute("height").Value));
}
var hull = new Hull(MapEntityPrefab.Find(null, "hull"), rect, submarine, idRemap.GetOffsetId(element))
var hull = new Hull(rect, submarine, idRemap.GetOffsetId(element))
{
WaterVolume = element.GetAttributeFloat("pressure", 0.0f)
};
@@ -1,7 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Xml.Linq;
namespace Barotrauma
{
@@ -14,6 +11,8 @@ namespace Barotrauma
public readonly LocalizedString Description;
public readonly bool IsEndBiome;
public readonly float MinDifficulty;
public readonly float MaxDifficulty;
public readonly ImmutableHashSet<int> AllowedZones;
@@ -30,8 +29,9 @@ namespace Barotrauma
element.GetAttributeString("description", ""));
IsEndBiome = element.GetAttributeBool("endbiome", false);
AllowedZones = element.GetAttributeIntArray("AllowedZones", new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }).ToImmutableHashSet();
MinDifficulty = element.GetAttributeFloat("MinDifficulty", 0);
MaxDifficulty = element.GetAttributeFloat("MaxDifficulty", 100);
}
public static Identifier ParseIdentifier(ContentXElement element)
@@ -96,24 +96,31 @@ namespace Barotrauma
public readonly Sprite WallSprite;
public readonly Sprite WallEdgeSprite;
public static CaveGenerationParams GetRandom(LevelGenerationParams generationParams, bool abyss, Rand.RandSync rand)
public static CaveGenerationParams GetRandom(Level level, bool abyss, Rand.RandSync rand)
{
var caveParams = CaveParams.OrderBy(p => p.UintIdentifier).ToList();
if (caveParams.All(p => p.GetCommonness(generationParams, abyss) <= 0.0f))
if (caveParams.All(p => p.GetCommonness(level.LevelData, abyss) <= 0.0f))
{
return caveParams.First();
}
return ToolBox.SelectWeightedRandom(caveParams.ToList(), caveParams.Select(p => p.GetCommonness(generationParams, abyss)).ToList(), rand);
return ToolBox.SelectWeightedRandom(caveParams.ToList(), caveParams.Select(p => p.GetCommonness(level.LevelData, abyss)).ToList(), rand);
}
public float GetCommonness(LevelGenerationParams generationParams, bool abyss)
public float GetCommonness(LevelData levelData, bool abyss)
{
if (generationParams != null &&
generationParams.Identifier != Identifier.Empty &&
OverrideCommonness.TryGetValue(abyss ? "abyss".ToIdentifier() : generationParams.Identifier, out float commonness))
if (levelData.GenerationParams != null && levelData.GenerationParams.Identifier != Identifier.Empty &&
OverrideCommonness.TryGetValue(abyss ? "abyss".ToIdentifier() : levelData.GenerationParams.Identifier, out float commonness))
{
return commonness;
}
if (levelData?.Biome != null)
{
if (OverrideCommonness.TryGetValue(levelData.Biome.Identifier, out float biomeCommonness))
{
return biomeCommonness;
}
}
return Commonness;
}
@@ -442,6 +442,11 @@ namespace Barotrauma
Loaded?.Remove();
Loaded = this;
Generating = true;
#if CLIENT
Debug.Assert(GenerationParams.Identifier != "coldcavernstutorial" || GameMain.GameSession?.GameMode == null || GameMain.GameSession.GameMode is TutorialMode);
#endif
Debug.Assert(GenerationParams.AnyBiomeAllowed || GenerationParams.AllowedBiomeIdentifiers.Contains(LevelData.Biome.Identifier));
DebugConsole.NewMessage("Level identifier: " + GenerationParams.Identifier);
ClearEqualityCheckValues();
EntitiesBeforeGenerate = GetEntities().ToList();
@@ -1711,7 +1716,8 @@ namespace Barotrauma
else
{
//if the bottom of the abyss area is below crush depth, try to move it up to keep (most) of the abyss content above crush depth
if (abyssEndY + CrushDepth < 0)
//but only if start of the abyss is above crush depth (no point in doing this if all of it is below crush depth)
if (abyssEndY + CrushDepth < 0 && abyssStartY > -CrushDepth)
{
abyssEndY += Math.Min(-(abyssEndY + (int)CrushDepth), abyssHeight / 2);
}
@@ -1820,7 +1826,7 @@ namespace Barotrauma
}
}
var caveParams = CaveGenerationParams.GetRandom(GenerationParams, abyss: true, rand: Rand.RandSync.ServerAndClient);
var caveParams = CaveGenerationParams.GetRandom(this, abyss: true, rand: Rand.RandSync.ServerAndClient);
float caveScaleRelativeToIsland = 0.7f;
GenerateCave(
@@ -1889,7 +1895,7 @@ namespace Barotrauma
{
for (int i = 0; i < GenerationParams.CaveCount; i++)
{
var caveParams = CaveGenerationParams.GetRandom(GenerationParams, abyss: false, rand: Rand.RandSync.ServerAndClient);
var caveParams = CaveGenerationParams.GetRandom(this, abyss: false, rand: Rand.RandSync.ServerAndClient);
Point caveSize = new Point(
Rand.Range(caveParams.MinWidth, caveParams.MaxWidth, Rand.RandSync.ServerAndClient),
Rand.Range(caveParams.MinHeight, caveParams.MaxHeight, Rand.RandSync.ServerAndClient));
@@ -2479,6 +2485,7 @@ namespace Barotrauma
foreach (ItemPrefab itemPrefab in ItemPrefab.Prefabs.OrderBy(p => p.UintIdentifier))
{
if (itemPrefab.LevelCommonness.TryGetValue(levelName, out float commonness) ||
itemPrefab.LevelCommonness.TryGetValue(LevelData.Biome.Identifier, out commonness) ||
itemPrefab.LevelCommonness.TryGetValue(Identifier.Empty, out commonness))
{
if (commonness <= 0.0f) { continue; }
@@ -3237,7 +3244,8 @@ namespace Barotrauma
if (index < 0 || index >= bottomPositions.Count - 1) { return new Vector2(xPosition, BottomPos); }
float t = (xPosition - bottomPositions[index].X) / (bottomPositions[index + 1].X - bottomPositions[index].X);
Debug.Assert(t <= 1.0f);
//t can go slightly outside the 0-1 due to rounding, safe to ignore
Debug.Assert(t <= 1.001f && t >= -0.001f);
t = MathHelper.Clamp(t, 0.0f, 1.0f);
float yPos = MathHelper.Lerp(bottomPositions[index].Y, bottomPositions[index + 1].Y, t);
@@ -20,7 +20,7 @@ namespace Barotrauma
public readonly string Seed;
public readonly float Difficulty;
public float Difficulty;
public readonly Biome Biome;
@@ -90,10 +90,10 @@ namespace Barotrauma
(int)MathUtils.Round(generationParams.Height, Level.GridCellSize));
}
public LevelData(XElement element)
public LevelData(XElement element, float? forceDifficulty = null)
{
Seed = element.GetAttributeString("seed", "");
Difficulty = element.GetAttributeFloat("difficulty", 0.0f);
Difficulty = forceDifficulty ?? element.GetAttributeFloat("difficulty", 0.0f);
Size = element.GetAttributePoint("size", new Point(1000));
Enum.TryParse(element.GetAttributeString("type", "LocationConnection"), out Type);
@@ -414,7 +414,7 @@ namespace Barotrauma
set;
}
[Serialize(50, IsPropertySaveable.Yes, description: "Maximum number of resource clusters in the abyss (the actual number is picked between min and max according to the level difficulty)"), Editable(MinValueInt = 0, MaxValueInt = 1000)]
[Serialize(40, IsPropertySaveable.Yes, description: "Maximum number of resource clusters in the abyss (the actual number is picked between min and max according to the level difficulty)"), Editable(MinValueInt = 0, MaxValueInt = 1000)]
public int AbyssResourceClustersMax
{
get;
@@ -170,7 +170,7 @@ namespace Barotrauma
for (int i = 0; i < amount; i++)
{
//get a random prefab and find a place to spawn it
LevelObjectPrefab prefab = GetRandomPrefab(level.GenerationParams, availablePrefabs);
LevelObjectPrefab prefab = GetRandomPrefab(level, availablePrefabs);
if (prefab == null) { continue; }
if (!suitableSpawnPositions.ContainsKey(prefab))
{
@@ -595,12 +595,12 @@ namespace Barotrauma
}
}
private LevelObjectPrefab GetRandomPrefab(LevelGenerationParams generationParams, IList<LevelObjectPrefab> availablePrefabs)
private LevelObjectPrefab GetRandomPrefab(Level level, IList<LevelObjectPrefab> availablePrefabs)
{
if (availablePrefabs.Sum(p => p.GetCommonness(generationParams)) <= 0.0f) { return null; }
if (availablePrefabs.Sum(p => p.GetCommonness(level.LevelData)) <= 0.0f) { return null; }
return ToolBox.SelectWeightedRandom(
availablePrefabs,
availablePrefabs.Select(p => p.GetCommonness(generationParams)).ToList(), Rand.RandSync.ServerAndClient);
availablePrefabs.Select(p => p.GetCommonness(level.LevelData)).ToList(), Rand.RandSync.ServerAndClient);
}
private LevelObjectPrefab GetRandomPrefab(CaveGenerationParams caveParams, IList<LevelObjectPrefab> availablePrefabs, bool requireCaveSpecificOverride)
@@ -426,15 +426,21 @@ namespace Barotrauma
return requireCaveSpecificOverride ? 0.0f : Commonness;
}
public float GetCommonness(LevelGenerationParams generationParams)
{
if (generationParams != null &&
generationParams.Identifier != Identifier.Empty &&
(OverrideCommonness.TryGetValue(generationParams.Identifier, out float commonness) ||
(!generationParams.OldIdentifier.IsEmpty && OverrideCommonness.TryGetValue(generationParams.OldIdentifier, out commonness))))
public float GetCommonness(LevelData levelData)
{
if (levelData.GenerationParams != null && levelData.GenerationParams.Identifier != Identifier.Empty &&
OverrideCommonness.TryGetValue(levelData.GenerationParams.Identifier, out float commonness) ||
(!levelData.GenerationParams.OldIdentifier.IsEmpty && OverrideCommonness.TryGetValue(levelData.GenerationParams.OldIdentifier, out commonness)))
{
return commonness;
}
if (levelData?.Biome != null)
{
if (OverrideCommonness.TryGetValue(levelData.Biome.Identifier, out float biomeCommonness))
{
return biomeCommonness;
}
}
return Commonness;
}
@@ -152,12 +152,6 @@ namespace Barotrauma
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
#if CLIENT
VertexBuffer?.Dispose();
VertexBuffer = null;
@@ -485,6 +485,19 @@ namespace Barotrauma
TurnsInRadiation = element.GetAttributeInt(nameof(TurnsInRadiation).ToLower(), 0);
StepsSinceSpecialsUpdated = element.GetAttributeInt("stepssincespecialsupdated", 0);
Identifier biomeId = element.GetAttributeIdentifier("biome", Identifier.Empty);
if (biomeId != Identifier.Empty)
{
if (Biome.Prefabs.TryGet(biomeId, out Biome biome))
{
Biome = biome;
}
else
{
DebugConsole.ThrowError($"Error while loading the campaign map: could not find a biome with the identifier \"{biomeId}\".");
}
}
if (!typeNotFound)
{
for (int i = 0; i < Type.CanChangeTo.Count; i++)
@@ -773,22 +786,41 @@ namespace Barotrauma
static float GetConnectionWeight(Location location, LocationConnection c)
{
float weight = c.Passed ? 1.0f : 5.0f;
Location destination = c.OtherLocation(location);
if (destination != null)
if (destination == null) { return 0; }
float minWeight = 0.0001f;
float lowWeight = 0.2f;
float normalWeight = 1.0f;
float maxWeight = 2.0f;
float weight = c.Passed ? lowWeight : normalWeight;
if (location.Biome.AllowedZones.Contains(1))
{
if (destination.MapPosition.X > location.MapPosition.X) { weight *= 2.0f; }
int missionCount = location.availableMissions.Count(m => m.Locations.Contains(destination));
if (missionCount > 0)
{
weight /= missionCount * 2;
}
if (destination.IsRadiated())
// In the first biome, give a stronger preference for locations that are farther to the right)
float diff = destination.MapPosition.X - location.MapPosition.X;
if (diff < 0)
{
weight *= 0.001f;
weight *= 0.1f;
}
else
{
float maxRelevantDiff = 300;
weight = MathHelper.Lerp(weight, maxWeight, MathUtils.InverseLerp(0, maxRelevantDiff, diff));
}
}
return weight;
else if (destination.MapPosition.X > location.MapPosition.X)
{
weight *= 2.0f;
}
int missionCount = location.availableMissions.Count(m => m.Locations.Contains(destination));
if (missionCount > 0)
{
weight /= missionCount * 2;
}
if (destination.IsRadiated())
{
weight *= 0.001f;
}
return MathHelper.Clamp(weight, minWeight, maxWeight);
}
return InstantiateMission(prefab, connection);
@@ -1255,6 +1287,7 @@ namespace Barotrauma
new XAttribute("originaltype", (Type ?? OriginalType).Identifier),
new XAttribute("basename", BaseName),
new XAttribute("name", Name),
new XAttribute("biome", Biome?.Identifier.Value ?? string.Empty),
new XAttribute("discovered", Discovered),
new XAttribute("position", XMLExtensions.Vector2ToString(MapPosition)),
new XAttribute("pricemultiplier", PriceMultiplier),
@@ -131,18 +131,27 @@ namespace Barotrauma
};
Locations[locationIndices.X].Connections.Add(connection);
Locations[locationIndices.Y].Connections.Add(connection);
connection.LevelData = new LevelData(subElement.Element("Level"));
string biomeId = subElement.GetAttributeString("biome", "");
connection.Biome =
Biome.Prefabs.FirstOrDefault(b => b.Identifier == biomeId) ??
Biome.Prefabs.FirstOrDefault(b => !b.OldIdentifier.IsEmpty && b.OldIdentifier == biomeId) ??
Biome.Prefabs.First();
connection.Difficulty = MathHelper.Clamp(connection.Difficulty, connection.Biome.MinDifficulty, connection.Biome.MaxDifficulty);
connection.LevelData = new LevelData(subElement.Element("Level"), connection.Difficulty);
Connections.Add(connection);
connectionElements.Add(subElement);
break;
}
}
//backwards compatibility: location biomes weren't saved (or used for anything) previously,
//assign them if they haven't been assigned
Random rand = new MTRandom(ToolBox.StringToInt(Seed));
if (Locations.First().Biome == null)
{
AssignBiomes(rand);
}
int startLocationindex = element.GetAttributeInt("startlocation", -1);
if (startLocationindex > 0 && startLocationindex < Locations.Count)
{
@@ -237,6 +246,10 @@ namespace Barotrauma
}
}
System.Diagnostics.Debug.Assert(StartLocation != null, "Start location not assigned after level generation.");
if (StartLocation?.LevelData != null)
{
StartLocation.LevelData.Difficulty = 0;
}
//ensure all paths from the starting location have 0 difficulty to make the 1st campaign round very easy
foreach (var locationConnection in StartLocation.Connections)
@@ -251,6 +264,11 @@ namespace Barotrauma
CurrentLocation.Discover(true);
CurrentLocation.CreateStores();
foreach (var location in Locations)
{
location.UnlockInitialMissions();
}
InitProjectSpecific();
}
@@ -505,22 +523,31 @@ namespace Barotrauma
//remove orphans
Locations.RemoveAll(l => !Connections.Any(c => c.Locations.Contains(l)));
AssignBiomes(new MTRandom(ToolBox.StringToInt(Seed)));
foreach (LocationConnection connection in Connections)
{
//float difficulty = GetLevelDifficulty(connection.CenterPos.X / Width);
//connection.Difficulty = MathHelper.Clamp(difficulty + Rand.Range(-10.0f, 0.0f, Rand.RandSync.ServerAndClient), 1.2f, 100.0f);
float difficulty = connection.CenterPos.X / Width * 100;
float random = difficulty > 10 ? 5 : 0;
connection.Difficulty = MathHelper.Clamp(difficulty + Rand.Range(-random, random, Rand.RandSync.ServerAndClient), 1.0f, 100.0f);
float minDifficulty = 0;
float maxDifficulty = 100;
var biome = connection.Biome;
if (biome != null)
{
minDifficulty = connection.Biome.MinDifficulty;
maxDifficulty = connection.Biome.MaxDifficulty;
if (connection.Locked)
{
connection.Difficulty = maxDifficulty;
}
}
connection.Difficulty = MathHelper.Clamp(difficulty, minDifficulty, maxDifficulty);
}
AssignBiomes();
CreateEndLocation();
foreach (Location location in Locations)
{
location.LevelData = new LevelData(location, MathHelper.Clamp(location.MapPosition.X / Width * 100, 0.0f, 100.0f));
location.UnlockInitialMissions();
}
foreach (LocationConnection connection in Connections)
{
@@ -549,7 +576,7 @@ namespace Barotrauma
return Biome.Prefabs.FirstOrDefault(b => b.AllowedZones.Contains(zoneIndex));
}
private void AssignBiomes()
private void AssignBiomes(Random rand)
{
var biomes = Biome.Prefabs;
float zoneWidth = Width / generationParams.DifficultyZones;
@@ -565,7 +592,7 @@ namespace Barotrauma
{
if (location.MapPosition.X < zoneX)
{
location.Biome = allowedBiomes[Rand.Range(0, allowedBiomes.Count, Rand.RandSync.ServerAndClient)];
location.Biome = allowedBiomes[rand.Next() % allowedBiomes.Count];
}
}
}
@@ -34,23 +34,6 @@ namespace Barotrauma
return prefab;
}
private void Dispose(bool disposing)
{
if (!Disposed)
{
if (disposing)
{
Humans.Clear();
}
}
Disposed = true;
}
public override void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public override void Dispose() { }
}
}
@@ -109,14 +109,21 @@ namespace Barotrauma
#endif
}
public enum Type
{
WayPoint,
SpawnPoint
}
public WayPoint(Rectangle newRect, Submarine submarine)
: this (MapEntityPrefab.FindByIdentifier("waypoint".ToIdentifier()), newRect, submarine)
: this (Type.WayPoint, newRect, submarine)
{
}
public WayPoint(MapEntityPrefab prefab, Rectangle newRect, Submarine submarine, ushort id = Entity.NullEntityID)
: base (prefab, submarine, id)
public WayPoint(Type type, Rectangle newRect, Submarine submarine, ushort id = Entity.NullEntityID)
: base (type is Type.WayPoint
? CoreEntityPrefab.WayPointPrefab
: CoreEntityPrefab.SpawnPointPrefab, submarine, id)
{
rect = newRect;
idCardTags = Array.Empty<string>();
@@ -1010,7 +1017,7 @@ namespace Barotrauma
Enum.TryParse(element.GetAttributeString("spawn", "Path"), out SpawnType spawnType);
WayPoint w = new WayPoint(MapEntityPrefab.FindByIdentifier((spawnType == SpawnType.Path ? "waypoint" : "spawnpoint").ToIdentifier()), rect, submarine, idRemap.GetOffsetId(element))
WayPoint w = new WayPoint(spawnType == SpawnType.Path ? Type.WayPoint : Type.SpawnPoint, rect, submarine, idRemap.GetOffsetId(element))
{
spawnType = spawnType
};
@@ -107,7 +107,7 @@ namespace Barotrauma.Networking
return -1;
}
//FIXME workaround for crash when closing the server under .NET 6.0, not sure if this is the proper way to fix it but it prevents it from crashing the client. - Markus
// BUG workaround for crash when closing the server under .NET 6.0, not sure if this is the proper way to fix it but it prevents it from crashing the client. - Markus
#if NET6_0
try
{
@@ -45,6 +45,12 @@ namespace Barotrauma
bool matchingElementFound = false;
foreach (var subElement in element.Elements())
{
if (replacementSubElement.Name.ToString().Equals("clear", StringComparison.OrdinalIgnoreCase))
{
matchingElementFound = true;
elementsToRemove.AddRange(element.Elements());
break;
}
if (!subElement.Name.ToString().Equals(replacementSubElement.Name.ToString(), StringComparison.OrdinalIgnoreCase)) { continue; }
if (i == index)
{
@@ -161,6 +161,7 @@ namespace Barotrauma
RadialDistortion = true,
InventoryScale = 1.0f,
LightMapScale = 1.0f,
VisibleLightLimit = 50,
TextScale = 1.0f,
HUDScale = 1.0f,
Specularity = true,
@@ -200,6 +201,7 @@ namespace Barotrauma
public float HUDScale;
public float InventoryScale;
public float LightMapScale;
public int VisibleLightLimit;
public float TextScale;
public bool RadialDistortion;
}
@@ -406,23 +406,14 @@ namespace Barotrauma
}
}
private void Dispose(bool disposing)
public void Dispose()
{
if (!Disposed)
{
if (disposing)
{
TargetComponents.Clear();
}
TargetComponents.Clear();
}
Disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}
@@ -396,29 +396,20 @@ namespace Barotrauma
return 1;
}
private void Dispose(bool disposing)
public override void Dispose()
{
if (!disposed)
{
if (disposing)
{
Prefabs.Remove(this);
Prefabs.Remove(this);
#if CLIENT
Sprite?.Remove();
Sprite = null;
DecorativeSprites.ForEach(sprite => sprite.Remove());
targetProperties.Clear();
Sprite?.Remove();
Sprite = null;
DecorativeSprites.ForEach(sprite => sprite.Remove());
targetProperties.Clear();
#endif
}
}
disposed = true;
}
public override void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}
@@ -7,11 +7,6 @@ namespace Barotrauma
{
private readonly Dictionary<Identifier, Action<T>> events = new Dictionary<Identifier, Action<T>>();
~NamedEvent()
{
ReleaseUnmanagedResources();
}
public void Register(Identifier identifier, Action<T> action)
{
if (HasEvent(identifier))
@@ -53,15 +48,9 @@ namespace Barotrauma
}
}
private void ReleaseUnmanagedResources()
public void Dispose()
{
events.Clear();
}
public void Dispose()
{
ReleaseUnmanagedResources();
GC.SuppressFinalize(this);
}
}
}
@@ -429,8 +429,8 @@ namespace Barotrauma.IO
public class FileStream : System.IO.Stream
{
private System.IO.FileStream innerStream;
private string fileName;
private readonly System.IO.FileStream innerStream;
private readonly string fileName;
public FileStream(string fn, System.IO.FileStream stream)
{
@@ -496,9 +496,9 @@ namespace Barotrauma.IO
innerStream.Flush();
}
protected override void Dispose(bool disposing)
protected override void Dispose(bool notCalledByFinalizer)
{
innerStream.Dispose();
if (notCalledByFinalizer) { innerStream.Dispose(); }
}
}
@@ -479,7 +479,7 @@ namespace Barotrauma
{
int read = 0;
// FIXME workaround for .NET6 causing save decompression to fail
// BUG workaround for .NET6 causing save decompression to fail
#if NET6_0
for (int i = 0; i < amount; i++)
{