Release 1.11.4.1 (Winter Update)
This commit is contained in:
@@ -1223,6 +1223,7 @@ namespace Barotrauma
|
||||
if (otherCharacter.SelectedCharacter == null ||
|
||||
!otherCharacter.SelectedCharacter.IsDead ||
|
||||
otherCharacter.SelectedCharacter.TeamID != Character.TeamID ||
|
||||
otherCharacter.IsPet ||
|
||||
otherCharacter.IsInstigator)
|
||||
{
|
||||
continue;
|
||||
|
||||
@@ -114,7 +114,7 @@ namespace Barotrauma
|
||||
if (target.Submarine != character.Submarine) { return; }
|
||||
Reset();
|
||||
TargetCharacter = target;
|
||||
targetBody = target.AnimController.Collider.FarseerBody;
|
||||
targetBody = target.AnimController.MainLimb.body.FarseerBody;
|
||||
attachSurfaceNormal = Vector2.Normalize(character.WorldPosition - target.WorldPosition);
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -769,7 +769,7 @@ namespace Barotrauma
|
||||
Attack attack = GetAttackDefinition(weapon);
|
||||
if (attack != null)
|
||||
{
|
||||
lethalDmg = attack.GetTotalDamage();
|
||||
lethalDmg = attack.GetTotalCharacterDamage();
|
||||
float max = lethalDmg + 1;
|
||||
if (weapon.Item.HasTag(Tags.StunnerItem))
|
||||
{
|
||||
@@ -795,7 +795,7 @@ namespace Barotrauma
|
||||
Attack attack = GetAttackDefinition(weapon);
|
||||
if (attack != null)
|
||||
{
|
||||
lethalDmg = attack.GetTotalDamage();
|
||||
lethalDmg = attack.GetTotalCharacterDamage();
|
||||
float stunDmg = ApproximateStunDamage(weapon, attack);
|
||||
float diff = stunDmg - lethalDmg;
|
||||
if (diff < 0)
|
||||
@@ -809,7 +809,7 @@ namespace Barotrauma
|
||||
{
|
||||
// Cannot do stun damage -> use the melee damage to determine the priority.
|
||||
Attack attack = GetAttackDefinition(weapon);
|
||||
priority = attack?.GetTotalDamage() ?? priority / 2;
|
||||
priority = attack?.GetTotalCharacterDamage() ?? priority / 2;
|
||||
}
|
||||
// Reduce the priority of the weapon, if we don't have requires skills to use it.
|
||||
float startPriority = priority;
|
||||
@@ -960,7 +960,7 @@ namespace Barotrauma
|
||||
Attack attack = GetAttackDefinition(weapon);
|
||||
if (attack != null)
|
||||
{
|
||||
lethalDmg = attack.GetTotalDamage();
|
||||
lethalDmg = attack.GetTotalCharacterDamage();
|
||||
}
|
||||
return lethalDmg;
|
||||
}
|
||||
|
||||
+3
-1
@@ -1,4 +1,5 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
@@ -94,6 +95,7 @@ namespace Barotrauma
|
||||
if (potentialDeconstructor?.InputContainer == null) { continue; }
|
||||
if (!potentialDeconstructor.InputContainer.Inventory.CanBePut(Item)) { continue; }
|
||||
if (!potentialDeconstructor.Item.HasAccess(character)) { continue; }
|
||||
if (Item.Prefab.DeconstructItems.None(it => it.IsValidDeconstructor(otherItem))) { continue; }
|
||||
float distFactor = GetDistanceFactor(Item.WorldPosition, potentialDeconstructor.Item.WorldPosition, factorAtMaxDistance: 0.2f);
|
||||
if (distFactor > bestDistFactor)
|
||||
{
|
||||
|
||||
+5
@@ -1,6 +1,7 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -30,6 +31,7 @@ namespace Barotrauma
|
||||
if (character.Submarine == null ||
|
||||
Item.ItemList.None(it =>
|
||||
it.GetComponent<Deconstructor>() != null &&
|
||||
!it.IgnoreByAI(character) &&
|
||||
it.IsInteractable(character) &&
|
||||
character.Submarine.IsEntityFoundOnThisSub(it, includingConnectedSubs: true, allowDifferentTeam: true, allowDifferentType: true)))
|
||||
{
|
||||
@@ -60,6 +62,9 @@ namespace Barotrauma
|
||||
protected override bool IsValidTarget(Item target)
|
||||
{
|
||||
if (target == null || target.Removed) { return false; }
|
||||
//bots can't handle deconstructing items that require another item to deconstruct, let's not try to do that
|
||||
//in the vanilla game, this means unidentified genetic materials, which we don't want to "deconstruct" anyway
|
||||
if (target.Prefab.DeconstructItems.All(d => d.RequiredOtherItem.Length > 0)) { return false; }
|
||||
// If the target was selected as a valid target, we'll have to accept it so that the objective can be completed.
|
||||
// The validity changes when a character picks the item up.
|
||||
if (!IsValidTarget(target, character, checkInventory: true))
|
||||
|
||||
+2
-9
@@ -98,18 +98,11 @@ namespace Barotrauma
|
||||
vitality -= affliction.GetVitalityDecrease(character.CharacterHealth, strength) / character.MaxVitality * 100;
|
||||
if (affliction.Strength > affliction.Prefab.TreatmentThreshold)
|
||||
{
|
||||
if (affliction.Prefab.AfflictionType == AfflictionPrefab.ParalysisType)
|
||||
//vitality loss is not required to treat this affliction -> evaluate the strength of the affliction too
|
||||
if (!affliction.Prefab.VitalityLossRequiredForTreatment)
|
||||
{
|
||||
vitality -= affliction.Strength;
|
||||
}
|
||||
else if (affliction.Prefab.AfflictionType == AfflictionPrefab.PoisonType)
|
||||
{
|
||||
vitality -= affliction.Strength;
|
||||
}
|
||||
else if (affliction.Prefab == AfflictionPrefab.HuskInfection)
|
||||
{
|
||||
vitality -= affliction.Strength;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Math.Clamp(vitality, 0, 100);
|
||||
|
||||
@@ -437,8 +437,25 @@ namespace Barotrauma
|
||||
public bool TargetItemsMatchItem(Item item, Identifier option = default)
|
||||
{
|
||||
if (item == null) { return false; }
|
||||
if (Identifier == Tags.DeconstructThis && item.AllowDeconstruct && !Item.DeconstructItems.Contains(item)) { return true; }
|
||||
if (Identifier == Tags.DontDeconstructThis && Item.DeconstructItems.Contains(item)) { return true; }
|
||||
|
||||
if (Identifier == Tags.DeconstructThis && item.AllowDeconstruct)
|
||||
{
|
||||
if (item.AllowDeconstruct && !Item.DeconstructItems.Contains(item) &&
|
||||
//only allow deconstructing if there are deconstruction recipes that
|
||||
item.Prefab.DeconstructItems.Any(deconstructItem =>
|
||||
//1. don't require any additional items (bots can't handle that)
|
||||
deconstructItem.RequiredOtherItem.None() &&
|
||||
//2. don't require a research station (bots don't know how to use those)
|
||||
(deconstructItem.RequiredDeconstructor.Length == 0 || deconstructItem.RequiredDeconstructor.Any(d => d != Tags.GeneticResearchStation))))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (Identifier == Tags.DontDeconstructThis)
|
||||
{
|
||||
if (Item.DeconstructItems.Contains(item)) { return true; }
|
||||
}
|
||||
|
||||
ImmutableArray<Identifier> targetItems = GetTargetItems(option);
|
||||
return TargetItemsMatchItem(targetItems, item);
|
||||
}
|
||||
|
||||
@@ -216,6 +216,7 @@ namespace Barotrauma
|
||||
{
|
||||
UpdateTemporaryAnimations();
|
||||
UpdateAnim(deltaTime);
|
||||
CheckRopeState();
|
||||
}
|
||||
|
||||
protected abstract void UpdateAnim(float deltaTime);
|
||||
@@ -1134,6 +1135,25 @@ namespace Barotrauma
|
||||
character.TeleportTo(pos);
|
||||
}
|
||||
|
||||
protected void CheckRopeState()
|
||||
{
|
||||
if (!shouldHangWithRope)
|
||||
{
|
||||
StopHangingWithRope();
|
||||
}
|
||||
if (!shouldHoldToRope)
|
||||
{
|
||||
StopHoldingToRope();
|
||||
}
|
||||
if (!shouldBeDraggedWithRope)
|
||||
{
|
||||
StopGettingDraggedWithRope();
|
||||
}
|
||||
shouldHoldToRope = false;
|
||||
shouldHangWithRope = false;
|
||||
shouldBeDraggedWithRope = false;
|
||||
}
|
||||
|
||||
private void StartAnimation(Animation animation)
|
||||
{
|
||||
if (animation == Animation.UsingItem)
|
||||
|
||||
+9
-33
@@ -478,21 +478,6 @@ namespace Barotrauma
|
||||
aiming = false;
|
||||
wasAimingMelee = aimingMelee;
|
||||
aimingMelee = false;
|
||||
if (!shouldHangWithRope)
|
||||
{
|
||||
StopHangingWithRope();
|
||||
}
|
||||
if (!shouldHoldToRope)
|
||||
{
|
||||
StopHoldingToRope();
|
||||
}
|
||||
if (!shouldBeDraggedWithRope)
|
||||
{
|
||||
StopGettingDraggedWithRope();
|
||||
}
|
||||
shouldHoldToRope = false;
|
||||
shouldHangWithRope = false;
|
||||
shouldBeDraggedWithRope = false;
|
||||
}
|
||||
|
||||
void UpdateStanding()
|
||||
@@ -1256,34 +1241,25 @@ namespace Barotrauma
|
||||
float prevVitality = target.Vitality;
|
||||
bool wasCritical = prevVitality < 0.0f;
|
||||
|
||||
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient) //Serverside code
|
||||
float cprBoost = character.GetStatValue(StatTypes.CPRBoost);
|
||||
float skill = character.GetSkillLevel(Tags.MedicalSkill);
|
||||
bool oxygenAvailable = target.OxygenAvailable >= CharacterHealth.InsufficientOxygenThreshold;
|
||||
|
||||
//Serverside code
|
||||
if (oxygenAvailable && GameMain.NetworkMember is not { IsClient: true })
|
||||
{
|
||||
target.Oxygen += deltaTime * 0.5f; //Stabilize them
|
||||
}
|
||||
|
||||
float cprBoost = character.GetStatValue(StatTypes.CPRBoost);
|
||||
|
||||
int skill = (int)character.GetSkillLevel(Tags.MedicalSkill);
|
||||
|
||||
if (GameMain.NetworkMember is not { IsClient: true })
|
||||
{
|
||||
if (cprBoost >= 1f)
|
||||
{
|
||||
//prevent the patient from suffocating no matter how fast their oxygen level is dropping
|
||||
target.Oxygen = Math.Max(target.Oxygen, -10.0f);
|
||||
}
|
||||
}
|
||||
|
||||
//Serverside code
|
||||
if (GameMain.NetworkMember is not { IsClient: true })
|
||||
{
|
||||
if (target.Oxygen < -10.0f)
|
||||
{
|
||||
//stabilize the oxygen level but don't allow it to go positive and revive the character yet
|
||||
float stabilizationAmount = skill * CPRSettings.Active.StabilizationPerSkill;
|
||||
stabilizationAmount = MathHelper.Clamp(stabilizationAmount, CPRSettings.Active.StabilizationMin, CPRSettings.Active.StabilizationMax);
|
||||
character.Oxygen -= 1.0f / stabilizationAmount * deltaTime; //Worse skill = more oxygen required
|
||||
if (character.Oxygen > 0.0f) { target.Oxygen += stabilizationAmount * deltaTime; } //we didn't suffocate yet did we
|
||||
target.Oxygen += stabilizationAmount * deltaTime;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1317,7 +1293,7 @@ namespace Barotrauma
|
||||
}
|
||||
//need to CPR for at least a couple of seconds before the target can be revived
|
||||
//(reviving the target when the CPR has barely started looks strange)
|
||||
if (cprAnimTimer > 2.0f && GameMain.NetworkMember is not { IsClient: true })
|
||||
if (oxygenAvailable && cprAnimTimer > 2.0f && GameMain.NetworkMember is not { IsClient: true })
|
||||
{
|
||||
float reviveChance = skill * CPRSettings.Active.ReviveChancePerSkill;
|
||||
reviveChance = (float)Math.Pow(reviveChance, CPRSettings.Active.ReviveChanceExponent);
|
||||
@@ -1338,7 +1314,7 @@ namespace Barotrauma
|
||||
//got the character back into a non-critical state, increase medical skill
|
||||
//BUT only if it has been more than 10 seconds since the character revived someone
|
||||
//otherwise it's easy to abuse the system by repeatedly reviving in a low-oxygen room
|
||||
if (!target.IsDead)
|
||||
if (!target.IsDead || !oxygenAvailable)
|
||||
{
|
||||
target.CharacterHealth.RecalculateVitality();
|
||||
if (wasCritical && target.Vitality > 0.0f && Timing.TotalTime > lastReviveTime + 10.0f)
|
||||
|
||||
@@ -75,7 +75,7 @@ namespace Barotrauma
|
||||
get { return frozen; }
|
||||
set
|
||||
{
|
||||
if (frozen == value) return;
|
||||
if (frozen == value) { return; }
|
||||
|
||||
frozen = value;
|
||||
|
||||
@@ -1055,7 +1055,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public void FindHull(Vector2? worldPosition = null, bool setSubmarine = true)
|
||||
/// <param name="setInWater">Should the character be immediately considered "in water" if it's outside hulls (normally checked in Update)</param>
|
||||
public void FindHull(Vector2? worldPosition = null, bool setSubmarine = true, bool setInWater = false)
|
||||
{
|
||||
Vector2 findPos = worldPosition == null ? this.WorldPosition : (Vector2)worldPosition;
|
||||
if (!MathUtils.IsValid(findPos))
|
||||
@@ -1068,6 +1069,10 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
Hull newHull = Hull.FindHull(findPos, currentHull);
|
||||
if (setInWater && newHull == null)
|
||||
{
|
||||
inWater = true;
|
||||
}
|
||||
|
||||
if (newHull == currentHull) { return; }
|
||||
|
||||
|
||||
@@ -403,14 +403,19 @@ namespace Barotrauma
|
||||
return (Duration == 0.0f) ? dmg : dmg * deltaTime;
|
||||
}
|
||||
|
||||
public float GetTotalDamage(bool includeStructureDamage = false)
|
||||
/// <summary>
|
||||
/// Returns the total damage (vitality decrease) this attack causes on characters.
|
||||
/// </summary>
|
||||
public float GetTotalCharacterDamage()
|
||||
{
|
||||
float totalDamage = includeStructureDamage ? StructureDamage : 0.0f;
|
||||
float totalDamage = 0.0f;
|
||||
foreach (Affliction affliction in Afflictions.Keys)
|
||||
{
|
||||
totalDamage += affliction.GetVitalityDecrease(null);
|
||||
float afflictionVitalityDecrease = affliction.GetVitalityDecrease(null);
|
||||
if (affliction.AffectedByAttackMultipliers) { afflictionVitalityDecrease *= DamageMultiplier; }
|
||||
totalDamage += afflictionVitalityDecrease;
|
||||
}
|
||||
return totalDamage * DamageMultiplier;
|
||||
return totalDamage;
|
||||
}
|
||||
|
||||
public Attack(float damage, float bleedingDamage, float burnDamage, float structureDamage, float itemDamage, float range = 0.0f)
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace Barotrauma
|
||||
|
||||
partial class Character : Entity, IDamageable, ISerializableEntity, IClientSerializable, IServerPositionSync
|
||||
{
|
||||
public readonly static List<Character> CharacterList = new List<Character>();
|
||||
public static readonly List<Character> CharacterList = new List<Character>();
|
||||
|
||||
public const float MaxHighlightDistance = 150.0f;
|
||||
public const float MaxDragDistance = 200.0f;
|
||||
@@ -39,7 +39,13 @@ namespace Barotrauma
|
||||
|
||||
partial void UpdateLimbLightSource(Limb limb);
|
||||
|
||||
private bool enabled = true;
|
||||
private bool initialized;
|
||||
private bool enabled;
|
||||
//characters start disabled in the multiplayer mode, and are enabled if/when
|
||||
// - controlled by the player
|
||||
// - client receives a position update from the server
|
||||
// - server receives an input message from the client controlling the character
|
||||
// - if an AICharacter, the server enables it when close enough to any of the players
|
||||
public bool Enabled
|
||||
{
|
||||
get
|
||||
@@ -48,7 +54,12 @@ namespace Barotrauma
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value == enabled) { return; }
|
||||
if (initialized && value == enabled)
|
||||
{
|
||||
// Ensure that we'll set the value and run the code below at least once, because otherwise the states might be out of sync.
|
||||
return;
|
||||
}
|
||||
initialized = true;
|
||||
|
||||
if (Removed)
|
||||
{
|
||||
@@ -80,7 +91,6 @@ namespace Barotrauma
|
||||
//we only want to enable the physics body if it's an actual holdable item, not e.g. a wearable item like handcuffs
|
||||
item.body.Enabled = true;
|
||||
}
|
||||
|
||||
}
|
||||
AnimController.Collider.Enabled = value;
|
||||
}
|
||||
@@ -109,6 +119,13 @@ namespace Barotrauma
|
||||
if (!CharacterList.Contains(this)) { CharacterList.Add(this); }
|
||||
if (AiTarget != null && !AITarget.List.Contains(AiTarget)) { AITarget.List.Add(AiTarget); }
|
||||
}
|
||||
if (Inventory != null)
|
||||
{
|
||||
foreach (var item in Inventory.FindAllItems(recursive: true))
|
||||
{
|
||||
item.IsActive = !disabledByEvent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1619,18 +1636,14 @@ namespace Barotrauma
|
||||
PressureProtection = int.MaxValue;
|
||||
}
|
||||
|
||||
AnimController.SetPosition(ConvertUnits.ToSimUnits(position));
|
||||
CharacterHealth.CheckForErrors();
|
||||
|
||||
AnimController.FindHull(null);
|
||||
AnimController.SetPosition(ConvertUnits.ToSimUnits(position));
|
||||
AnimController.FindHull(setInWater: true);
|
||||
if (AnimController.CurrentHull != null) { Submarine = AnimController.CurrentHull.Submarine; }
|
||||
|
||||
CharacterList.Add(this);
|
||||
|
||||
//characters start disabled in the multiplayer mode, and are enabled if/when
|
||||
// - controlled by the player
|
||||
// - client receives a position update from the server
|
||||
// - server receives an input message from the client controlling the character
|
||||
// - if an AICharacter, the server enables it when close enough to any of the players
|
||||
|
||||
Enabled = GameMain.NetworkMember == null;
|
||||
|
||||
if (info != null)
|
||||
@@ -3304,17 +3317,22 @@ namespace Barotrauma
|
||||
|
||||
public static void UpdateAll(float deltaTime, Camera cam)
|
||||
{
|
||||
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
|
||||
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient) // single player or server
|
||||
{
|
||||
foreach (Character c in CharacterList)
|
||||
{
|
||||
if (c is not AICharacter && !c.IsRemotePlayer) { continue; }
|
||||
|
||||
if (c.IsPlayer || (c.IsBot && !c.IsDead))
|
||||
// TODO: The logic below seems to be overly complicated and quite confusing
|
||||
if (c is not AICharacter && !c.IsRemotePlayer) { continue; } // confusing -> what this line is intended for? local player? But that's handled below...
|
||||
if (c.IsRemotePlayer)
|
||||
{
|
||||
// Let the client tell when to enable the character. If we force it enabled here, it may e.g. get killed while still loading a round.
|
||||
continue;
|
||||
}
|
||||
if (c.IsLocalPlayer || (c.IsBot && !c.IsDead))
|
||||
{
|
||||
c.Enabled = true;
|
||||
}
|
||||
else if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
else if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer) // mp server
|
||||
{
|
||||
//disable AI characters that are far away from all clients and the host's character and not controlled by anyone
|
||||
float closestPlayerDist = c.GetDistanceToClosestPlayer();
|
||||
@@ -3331,7 +3349,7 @@ namespace Barotrauma
|
||||
c.Enabled = true;
|
||||
}
|
||||
}
|
||||
else if (Submarine.MainSub != null)
|
||||
else if (Submarine.MainSub != null) // sp only?
|
||||
{
|
||||
//disable AI characters that are far away from the sub and the controlled character
|
||||
float distSqr = Vector2.DistanceSquared(Submarine.MainSub.WorldPosition, c.WorldPosition);
|
||||
@@ -3360,10 +3378,9 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < CharacterList.Count; i++)
|
||||
foreach (Character character in CharacterList)
|
||||
{
|
||||
var character = CharacterList[i];
|
||||
System.Diagnostics.Debug.Assert(character != null && !character.Removed);
|
||||
Debug.Assert(character is { Removed: false });
|
||||
character.Update(deltaTime, cam);
|
||||
}
|
||||
|
||||
@@ -3425,8 +3442,7 @@ namespace Barotrauma
|
||||
foreach (Item item in Inventory.GetAllItems(checkForDuplicates: false))
|
||||
{
|
||||
if (item.body == null || item.body.Enabled) { continue; }
|
||||
item.SetTransform(SimPosition, 0.0f);
|
||||
item.Submarine = Submarine;
|
||||
item.SetTransform(SimPosition, 0.0f, forceSubmarine: Submarine);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4575,7 +4591,10 @@ namespace Barotrauma
|
||||
|
||||
SetStun(stun);
|
||||
|
||||
if (attacker != null && attacker != this && GameMain.NetworkMember != null && !GameMain.NetworkMember.ServerSettings.AllowFriendlyFire)
|
||||
if (attacker != null && attacker != this &&
|
||||
attacker.IsOnPlayerTeam &&
|
||||
GameMain.NetworkMember != null &&
|
||||
!GameMain.NetworkMember.ServerSettings.AllowFriendlyFire)
|
||||
{
|
||||
if (attacker.TeamID == TeamID)
|
||||
{
|
||||
|
||||
@@ -85,6 +85,8 @@ namespace Barotrauma
|
||||
|
||||
public double AppliedAsSuccessfulTreatmentTime, AppliedAsFailedTreatmentTime;
|
||||
|
||||
public bool AffectedByAttackMultipliers => Prefab.AffectedByAttackMultipliers;
|
||||
|
||||
public float Duration;
|
||||
|
||||
/// <summary>
|
||||
|
||||
+9
-6
@@ -164,15 +164,18 @@ namespace Barotrauma
|
||||
}
|
||||
break;
|
||||
case InfectionState.Transition:
|
||||
if (character == Character.Controlled)
|
||||
if (Prefab is AfflictionPrefabHusk { CauseSpeechImpediment: true })
|
||||
{
|
||||
if (character == Character.Controlled)
|
||||
{
|
||||
#if CLIENT
|
||||
GUI.AddMessage(TextManager.Get("HuskCantSpeak"), GUIStyle.Red);
|
||||
GUI.AddMessage(TextManager.Get("HuskCantSpeak"), GUIStyle.Red);
|
||||
#endif
|
||||
}
|
||||
else if (character.IsBot)
|
||||
{
|
||||
character.Speak(TextManager.Get("dialoghuskcantspeak").Value, delay: Rand.Range(0.5f, 5.0f), identifier: "huskcantspeak".ToIdentifier());
|
||||
}
|
||||
else if (character.IsBot)
|
||||
{
|
||||
character.Speak(TextManager.Get("dialoghuskcantspeak").Value, delay: Rand.Range(0.5f, 5.0f), identifier: "huskcantspeak".ToIdentifier());
|
||||
}
|
||||
}
|
||||
break;
|
||||
case InfectionState.Active:
|
||||
|
||||
+26
-5
@@ -699,7 +699,13 @@ namespace Barotrauma
|
||||
/// and the health UI will render the affected limb in green rather than red.
|
||||
/// </summary>
|
||||
public readonly bool IsBuff;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Should the affliction be affected by damage multipliers on an attack (e.g. when the attacker has talents that boost damage).
|
||||
/// By default, afflictions defined as buffs aren't affected.
|
||||
/// </summary>
|
||||
public readonly bool AffectedByAttackMultipliers;
|
||||
|
||||
/// <summary>
|
||||
/// If set to true, this affliction can affect characters that are marked as
|
||||
/// machines, such as the Fractal Guardian.
|
||||
@@ -780,6 +786,14 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public readonly float TreatmentSuggestionThreshold;
|
||||
|
||||
/// <summary>
|
||||
/// Does the affliction need to have caused some amount of vitality loss for bots to consider treating it?
|
||||
/// Normally bots use vitality loss as a way to determine what kind of injuries need treatment, but some afflictions (e.g. poisons, infections)
|
||||
/// might require treatment regardless of the vitality loss. If disabled, the bots will use the strength of the affliction to evaluate the severity instead of the vitality loss.
|
||||
/// Defaults to true for all afflictions that aren't of the type Paralysis, Poison or HuskInfection.
|
||||
/// </summary>
|
||||
public readonly bool VitalityLossRequiredForTreatment;
|
||||
|
||||
/// <summary>
|
||||
/// Bots will not try to treat the affliction if the character has any of these afflictions
|
||||
/// </summary>
|
||||
@@ -838,14 +852,15 @@ namespace Barotrauma
|
||||
public readonly bool DamageParticles;
|
||||
|
||||
/// <summary>
|
||||
/// An arbitrary modifier that affects how much medical skill is increased when you apply the affliction on a target.
|
||||
/// If the affliction causes damage or is of the 'poison' or 'paralysis' type, the skill is increased only when the target is hostile.
|
||||
/// If the affliction is of the 'buff' type, the skill is increased only when the target is friendly.
|
||||
/// A modifier that affects how much medical skill is increased when you apply this affliction on a target.
|
||||
/// If the affliction causes damage or is of the 'poison' or 'paralysis' type, the skill is increased only when the target is hostile, and the modifier is multiplied by the amount of vitality the enemy lost.
|
||||
/// If the affliction is of the 'buff' type, the skill is increased only when the target is friendly, and the modifier is multiplied by the strength of the affliction the target gained.
|
||||
/// </summary>
|
||||
public readonly float MedicalSkillGain;
|
||||
|
||||
/// <summary>
|
||||
/// An arbitrary modifier that affects how much weapons skill is increased when you apply the affliction on a target.
|
||||
/// A modifier that affects how much weapons skill is increased when you apply the affliction on a target.
|
||||
/// Multiplied by the amount of vitality the enemy lost.
|
||||
/// The skill is increased only when the target is hostile.
|
||||
/// </summary>
|
||||
public readonly float WeaponsSkillGain;
|
||||
@@ -925,6 +940,7 @@ namespace Barotrauma
|
||||
ShowDescriptionInTooltip = element.GetAttributeBool(nameof(ShowDescriptionInTooltip), true);
|
||||
|
||||
IsBuff = element.GetAttributeBool(nameof(IsBuff), false);
|
||||
AffectedByAttackMultipliers = element.GetAttributeBool(nameof(AffectedByAttackMultipliers), def: !IsBuff);
|
||||
AffectMachines = element.GetAttributeBool(nameof(AffectMachines), true);
|
||||
|
||||
ShowBarInHealthMenu = element.GetAttributeBool("showbarinhealthmenu", true);
|
||||
@@ -977,6 +993,11 @@ namespace Barotrauma
|
||||
TreatmentThreshold = element.GetAttributeFloat(nameof(TreatmentThreshold), Math.Max(ActivationThreshold, 10.0f));
|
||||
TreatmentSuggestionThreshold = element.GetAttributeFloat(nameof(TreatmentSuggestionThreshold), TreatmentThreshold);
|
||||
|
||||
bool alwaysRequiresTreatment = AfflictionType == ParalysisType || AfflictionType == PoisonType || this is AfflictionPrefabHusk;
|
||||
|
||||
VitalityLossRequiredForTreatment = element.GetAttributeBool(nameof(VitalityLossRequiredForTreatment),
|
||||
def: !alwaysRequiresTreatment);
|
||||
|
||||
DamageOverlayAlpha = element.GetAttributeFloat(nameof(DamageOverlayAlpha), 0.0f);
|
||||
BurnOverlayAlpha = element.GetAttributeFloat(nameof(BurnOverlayAlpha), 0.0f);
|
||||
|
||||
|
||||
@@ -304,6 +304,19 @@ namespace Barotrauma
|
||||
InitProjSpecific(element, character);
|
||||
}
|
||||
|
||||
public void CheckForErrors()
|
||||
{
|
||||
for (int i = 0; i < limbHealths.Count; i++)
|
||||
{
|
||||
if (Character.AnimController.Limbs.None(l => l.HealthIndex == i))
|
||||
{
|
||||
DebugConsole.AddWarning(
|
||||
$"Potential error in character {Character.DisplayName}: none of the limbs have been set to use the LimbHealth #{i}, and it will do nothing. "
|
||||
+ "Did you forget to set the HealthIndex values of the limbs?", contentPackage: Character.ContentPackage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void InitIrremovableAfflictions()
|
||||
{
|
||||
irremovableAfflictions.Add(bloodlossAffliction = new Affliction(AfflictionPrefab.Bloodloss, 0.0f));
|
||||
@@ -1118,20 +1131,38 @@ namespace Barotrauma
|
||||
|
||||
// We need to use another list of the afflictions when we call the status effects triggered by afflictions,
|
||||
// because those status effects may add or remove other afflictions while iterating the collection.
|
||||
private readonly List<Affliction> afflictionsCopy = new List<Affliction>();
|
||||
private readonly List<Affliction> afflictionsCopy = [];
|
||||
|
||||
private bool isApplyingAfflictionStatusEffects;
|
||||
public void ApplyAfflictionStatusEffects(ActionType type)
|
||||
{
|
||||
afflictionsCopy.Clear();
|
||||
afflictionsCopy.AddRange(afflictions.Keys);
|
||||
foreach (Affliction affliction in afflictionsCopy)
|
||||
if (isApplyingAfflictionStatusEffects)
|
||||
{
|
||||
affliction.ApplyStatusEffects(type, 1.0f, this, targetLimb: GetAfflictionLimb(affliction));
|
||||
//pretty hacky: if we're already in the process of applying afflictions' status effects
|
||||
//(i.e. calling this method caused some additional afflictions to appear and trigger status effects)
|
||||
//let's instantiate a new list so we don't end up modifying afflictionsCopy while enumerating it
|
||||
foreach (Affliction affliction in afflictions.Keys.ToList())
|
||||
{
|
||||
affliction.ApplyStatusEffects(type, 1.0f, this, targetLimb: GetAfflictionLimb(affliction));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
isApplyingAfflictionStatusEffects = true;
|
||||
afflictionsCopy.Clear();
|
||||
afflictionsCopy.AddRange(afflictions.Keys);
|
||||
isApplyingAfflictionStatusEffects = true;
|
||||
foreach (Affliction affliction in afflictionsCopy)
|
||||
{
|
||||
affliction.ApplyStatusEffects(type, 1.0f, this, targetLimb: GetAfflictionLimb(affliction));
|
||||
}
|
||||
isApplyingAfflictionStatusEffects = false;
|
||||
}
|
||||
}
|
||||
|
||||
public (CauseOfDeathType type, Affliction affliction) GetCauseOfDeath()
|
||||
{
|
||||
List<Affliction> currentAfflictions = GetAllAfflictions(true);
|
||||
IEnumerable<Affliction> currentAfflictions = GetAllAfflictions(true);
|
||||
|
||||
Affliction strongestAffliction = null;
|
||||
float largestStrength = 0.0f;
|
||||
@@ -1154,7 +1185,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
private readonly List<Affliction> allAfflictions = new List<Affliction>();
|
||||
private List<Affliction> GetAllAfflictions(bool mergeSameAfflictions, Func<Affliction, bool> predicate = null)
|
||||
private IEnumerable<Affliction> GetAllAfflictions(bool mergeSameAfflictions, Func<Affliction, bool> predicate = null)
|
||||
{
|
||||
allAfflictions.Clear();
|
||||
if (!mergeSameAfflictions)
|
||||
|
||||
@@ -843,7 +843,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
if (!foundMatchingModifier && random > affliction.Probability) { continue; }
|
||||
float finalDamageModifier = damageMultiplier;
|
||||
float finalDamageModifier = affliction.AffectedByAttackMultipliers ? damageMultiplier : 1.0f;
|
||||
if (character.EmpVulnerability > 0 && affliction.Prefab.AfflictionType == AfflictionPrefab.EMPType)
|
||||
{
|
||||
finalDamageModifier *= character.EmpVulnerability;
|
||||
|
||||
@@ -14,14 +14,41 @@ namespace Barotrauma
|
||||
public enum SpawnLocationType
|
||||
{
|
||||
Any,
|
||||
/// <summary>
|
||||
/// Spawnpoint inside the main submarine.
|
||||
/// </summary>
|
||||
MainSub,
|
||||
/// <summary>
|
||||
/// Spawnpoint inside an outpost.
|
||||
/// </summary>
|
||||
Outpost,
|
||||
/// <summary>
|
||||
/// Spawnpoint on the main path through the level.
|
||||
/// </summary>
|
||||
MainPath,
|
||||
/// <summary>
|
||||
/// Spawnpoint in a cave. Only valid if there are caves in the level.
|
||||
/// </summary>
|
||||
Cave,
|
||||
/// <summary>
|
||||
/// Spawnpoint in an abyss cave. Only valid if there are abyss caves in the level.
|
||||
/// </summary>
|
||||
AbyssCave,
|
||||
/// <summary>
|
||||
/// Spawnpoint in a ruin. Only valid if there are ruins in the level.
|
||||
/// </summary>
|
||||
Ruin,
|
||||
/// <summary>
|
||||
/// Spawnpoint in a wreck. Only valid if there are wrecks in the level.
|
||||
/// </summary>
|
||||
Wreck,
|
||||
/// <summary>
|
||||
/// Spawnpoint in a beacon station. Only valid if there are beacon stations in the level.
|
||||
/// </summary>
|
||||
BeaconStation,
|
||||
/// <summary>
|
||||
/// A spawnpoint on the main path through the level. The difference to the <see cref="MainPath"/> type is that the closest possible spawnpoint is chosen.
|
||||
/// </summary>
|
||||
NearMainSub
|
||||
}
|
||||
|
||||
@@ -425,7 +452,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
spawnPointsWithCorrectType = potentialSpawnPoints.Where(wp => wp.SpawnType != SpawnType.Path);
|
||||
spawnPointsWithCorrectType = potentialSpawnPoints;
|
||||
}
|
||||
if (spawnPointsWithCorrectType.Any())
|
||||
{
|
||||
@@ -485,7 +512,12 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
//spawnpoints that match the desired criteria found, choose the best one next
|
||||
IEnumerable<WayPoint> validSpawnPoints = potentialSpawnPoints;
|
||||
// preferring non-path spawnpoints if there's any available
|
||||
var nonPathSpawnPoints = potentialSpawnPoints.Where(wp => wp.SpawnType != SpawnType.Path);
|
||||
var validSpawnPoints =
|
||||
nonPathSpawnPoints.Any() && spawnPointType != SpawnType.Path ?
|
||||
nonPathSpawnPoints :
|
||||
potentialSpawnPoints;
|
||||
|
||||
//don't spawn in an airlock module if there are other options
|
||||
var airlockSpawnPoints = potentialSpawnPoints.Where(wp => wp.CurrentHull?.OutpostModuleTags.Contains("airlock".ToIdentifier()) ?? false);
|
||||
|
||||
@@ -8,9 +8,9 @@ using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class MissionPrefab : PrefabWithUintIdentifier
|
||||
internal sealed partial class MissionPrefab : PrefabWithUintIdentifier, IImplementsVariants<MissionPrefab>
|
||||
{
|
||||
public static readonly PrefabCollection<MissionPrefab> Prefabs = new PrefabCollection<MissionPrefab>();
|
||||
public static readonly PrefabCollection<MissionPrefab> Prefabs = [];
|
||||
|
||||
/// <summary>
|
||||
/// The keys here are for backwards compatibility, tying the old mission types to the appropriate class.
|
||||
@@ -42,7 +42,7 @@ namespace Barotrauma
|
||||
{ "Combat".ToIdentifier(), typeof(CombatMission) }
|
||||
};
|
||||
|
||||
public static readonly HashSet<Identifier> HiddenMissionTypes = new HashSet<Identifier>() { "GoTo".ToIdentifier(), "End".ToIdentifier() };
|
||||
public static readonly HashSet<Identifier> HiddenMissionTypes = ["GoTo".ToIdentifier(), "End".ToIdentifier()];
|
||||
|
||||
public class ReputationReward
|
||||
{
|
||||
@@ -58,110 +58,117 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private readonly ConstructorInfo constructor;
|
||||
private ConstructorInfo constructor;
|
||||
|
||||
public readonly Identifier Type;
|
||||
public Identifier Type { get; private set; }
|
||||
|
||||
public readonly Type MissionClass;
|
||||
public Type MissionClass { get; private set; }
|
||||
|
||||
public readonly bool MultiplayerOnly, SingleplayerOnly;
|
||||
public bool MultiplayerOnly { get; private set; }
|
||||
public bool SingleplayerOnly { get; private set; }
|
||||
|
||||
public readonly Identifier TextIdentifier;
|
||||
public Identifier TextIdentifier { get; private set; }
|
||||
|
||||
public readonly ImmutableHashSet<Identifier> Tags;
|
||||
public ImmutableHashSet<Identifier> Tags { get; private set; }
|
||||
|
||||
public readonly LocalizedString Name;
|
||||
public readonly LocalizedString Description;
|
||||
public readonly LocalizedString SuccessMessage;
|
||||
public readonly LocalizedString FailureMessage;
|
||||
public readonly LocalizedString SonarLabel;
|
||||
public readonly Identifier SonarIconIdentifier;
|
||||
public LocalizedString Name { get; private set; }
|
||||
public LocalizedString Description { get; private set; }
|
||||
public LocalizedString SuccessMessage { get; private set; }
|
||||
public LocalizedString FailureMessage { get; private set; }
|
||||
public LocalizedString SonarLabel { get; private set; }
|
||||
public Identifier SonarIconIdentifier { get; private set; }
|
||||
|
||||
public readonly Identifier AchievementIdentifier;
|
||||
public Identifier AchievementIdentifier { get; private set; }
|
||||
|
||||
public readonly ImmutableList<ReputationReward> ReputationRewards;
|
||||
public ImmutableList<ReputationReward> ReputationRewards { get; private set; }
|
||||
|
||||
public readonly List<(Identifier Identifier, object Value, SetDataAction.OperationType OperationType)>
|
||||
DataRewards = new List<(Identifier Identifier, object Value, SetDataAction.OperationType OperationType)>();
|
||||
public readonly List<(Identifier Identifier, object Value, SetDataAction.OperationType OperationType)> DataRewards = [];
|
||||
|
||||
public readonly int Commonness;
|
||||
public int Commonness { get; private set; }
|
||||
/// <summary>
|
||||
/// Displayed difficulty (indicator)
|
||||
/// </summary>
|
||||
public readonly int? Difficulty;
|
||||
public int? Difficulty { get; private set; }
|
||||
public const int MinDifficulty = 1, MaxDifficulty = 4;
|
||||
/// <summary>
|
||||
/// The actual minimum difficulty of the level allowed for this mission to trigger.
|
||||
/// </summary>
|
||||
public readonly int MinLevelDifficulty = 0;
|
||||
public int MinLevelDifficulty { get; private set; } = 0;
|
||||
/// <summary>
|
||||
/// The actual maximum difficulty of the level allowed for this mission to trigger.
|
||||
/// </summary>
|
||||
public readonly int MaxLevelDifficulty = 100;
|
||||
public int MaxLevelDifficulty { get; private set; } = 100;
|
||||
|
||||
public readonly int Reward;
|
||||
public int Reward { get; private set; }
|
||||
|
||||
public readonly float ExperienceMultiplier;
|
||||
public float ExperienceMultiplier { get; private set; }
|
||||
|
||||
// The titles and bodies of the popup messages during the mission, shown when the state of the mission changes. The order matters.
|
||||
public readonly ImmutableArray<LocalizedString> Headers;
|
||||
public readonly ImmutableArray<LocalizedString> Messages;
|
||||
public ImmutableArray<LocalizedString> Headers { get; private set; }
|
||||
public ImmutableArray<LocalizedString> Messages { get; private set; }
|
||||
|
||||
public readonly bool AllowRetry;
|
||||
public bool AllowRetry { get; private set; }
|
||||
|
||||
public readonly bool ShowSonarLabels;
|
||||
public bool ShowSonarLabels { get; private set; }
|
||||
|
||||
public readonly bool ShowInMenus, ShowStartMessage;
|
||||
public bool ShowInMenus { get; private set; }
|
||||
public bool ShowStartMessage { get; private set; }
|
||||
|
||||
public readonly bool IsSideObjective;
|
||||
/// <summary>
|
||||
/// Makes the mission not count for the maximum mission limit, and forces it to always be selected when it's available in a level.
|
||||
/// </summary>
|
||||
public bool IsSideObjective { get; private set; }
|
||||
|
||||
public readonly bool AllowOtherMissionsInLevel;
|
||||
public bool AllowOtherMissionsInLevel { get; private set; }
|
||||
|
||||
public readonly bool RequireWreck, RequireRuin, RequireBeaconStation, RequireThalamusWreck;
|
||||
public readonly bool SpawnBeaconStationInMiddle;
|
||||
public bool RequireWreck { get; private set; }
|
||||
public bool RequireRuin { get; private set; }
|
||||
public bool RequireBeaconStation { get; private set; }
|
||||
public bool RequireThalamusWreck { get; private set; }
|
||||
public bool SpawnBeaconStationInMiddle { get; private set; }
|
||||
|
||||
public readonly bool AllowOutpostNPCs;
|
||||
public bool AllowOutpostNPCs { get; private set; }
|
||||
|
||||
public readonly Identifier ForceOutpostGenerationParameters;
|
||||
public Identifier ForceOutpostGenerationParameters { get; private set; }
|
||||
|
||||
public readonly RespawnMode? ForceRespawnMode;
|
||||
public RespawnMode? ForceRespawnMode { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// If set, the players can choose which outpost is used for the mission (selected from the outposts that have this tag). Only works in multiplayer.
|
||||
/// </summary>
|
||||
public readonly Identifier AllowOutpostSelectionFromTag;
|
||||
public Identifier AllowOutpostSelectionFromTag { get; private set; }
|
||||
|
||||
public readonly bool LoadSubmarines = true;
|
||||
public bool LoadSubmarines { get; private set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// If enabled, locations this mission takes place in cannot change their type
|
||||
/// </summary>
|
||||
public readonly bool BlockLocationTypeChanges;
|
||||
public bool BlockLocationTypeChanges { get; private set; }
|
||||
|
||||
public readonly bool ShowProgressBar;
|
||||
public readonly bool ShowProgressInNumbers;
|
||||
public readonly int MaxProgressState;
|
||||
public readonly LocalizedString ProgressBarLabel;
|
||||
public bool ShowProgressBar { get; private set; }
|
||||
public bool ShowProgressInNumbers { get; private set; }
|
||||
public int MaxProgressState { get; private set; }
|
||||
public LocalizedString ProgressBarLabel { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The mission can only be received when travelling from a location of the first type to a location of the second type
|
||||
/// </summary>
|
||||
public readonly List<(Identifier from, Identifier to)> AllowedConnectionTypes;
|
||||
public List<(Identifier from, Identifier to)> AllowedConnectionTypes { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The mission can only be received in these location types
|
||||
/// </summary>
|
||||
public readonly List<Identifier> AllowedLocationTypes = new List<Identifier>();
|
||||
public readonly List<Identifier> AllowedLocationTypes = [];
|
||||
|
||||
/// <summary>
|
||||
/// The mission can only happen in locations owned by this faction. In the mission mode, the location is forced to be owned by this faction.
|
||||
/// </summary>
|
||||
public readonly Identifier RequiredLocationFaction;
|
||||
public Identifier RequiredLocationFaction { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Show entities belonging to these sub categories when the mission starts
|
||||
/// </summary>
|
||||
public readonly List<string> UnhideEntitySubCategories = new List<string>();
|
||||
public List<string> UnhideEntitySubCategories { get; private set; }
|
||||
|
||||
public class TriggerEvent
|
||||
{
|
||||
@@ -186,22 +193,39 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public readonly List<TriggerEvent> TriggerEvents = new List<TriggerEvent>();
|
||||
public readonly List<TriggerEvent> TriggerEvents = [];
|
||||
|
||||
public LocationTypeChange LocationTypeChangeOnCompleted;
|
||||
|
||||
public readonly ContentXElement ConfigElement;
|
||||
private readonly ContentXElement originalElement;
|
||||
public ContentXElement ConfigElement { get; private set; }
|
||||
|
||||
public Identifier VariantOf { get; }
|
||||
public MissionPrefab ParentPrefab { get; set; }
|
||||
|
||||
public MissionPrefab(ContentXElement element, MissionsFile file) : base(file, element.GetAttributeIdentifier("identifier", ""))
|
||||
{
|
||||
ConfigElement = element;
|
||||
ConfigElement = originalElement = element;
|
||||
|
||||
TextIdentifier = element.GetAttributeIdentifier("textidentifier", Identifier);
|
||||
VariantOf = element.VariantOf();
|
||||
if (!VariantOf.IsEmpty) { return; } // Don't read the XML until the PrefabCollection loads the parent.
|
||||
ParseConfigElement();
|
||||
}
|
||||
|
||||
Tags = element.GetAttributeIdentifierArray("tags", Array.Empty<Identifier>()).ToImmutableHashSet();
|
||||
public void InheritFrom(MissionPrefab parent)
|
||||
{
|
||||
ConfigElement = originalElement.CreateVariantXML(parent.ConfigElement);
|
||||
ParseConfigElement(parent);
|
||||
}
|
||||
|
||||
Name = GetText(element.GetAttributeString("name", ""), "MissionName");
|
||||
Description = GetText(element.GetAttributeString("description", ""), "MissionDescription");
|
||||
private void ParseConfigElement(MissionPrefab variantOf = null)
|
||||
{
|
||||
TextIdentifier = ConfigElement.GetAttributeIdentifier("textidentifier", Identifier);
|
||||
|
||||
Tags = [.. ConfigElement.GetAttributeIdentifierArray("tags", [])];
|
||||
|
||||
Name = GetText(ConfigElement.GetAttributeString("name", ""), "MissionName");
|
||||
Description = GetText(ConfigElement.GetAttributeString("description", ""), "MissionDescription");
|
||||
|
||||
LocalizedString GetText(string textTag, string textTagPrefix)
|
||||
{
|
||||
@@ -211,105 +235,100 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
return
|
||||
//prefer finding a text based on the specific text tag defined in the mission config
|
||||
TextManager.Get(textTag)
|
||||
//2nd option: the "default" format (MissionName.SomeMission)
|
||||
.Fallback(TextManager.Get($"{textTagPrefix}.{TextIdentifier}"))
|
||||
//last option: use the text in the xml as-is with no localization
|
||||
.Fallback(textTag);
|
||||
return TextManager.Get(textTag) // Prefer finding a text based on the specific text tag defined in the mission config.
|
||||
.Fallback(TextManager.Get($"{textTagPrefix}.{TextIdentifier}")) // 2nd option: the "default" format (MissionName.SomeMission).
|
||||
.Fallback(textTag); // Last option: Use the text in the xml as-is with no localization.
|
||||
}
|
||||
}
|
||||
|
||||
Reward = element.GetAttributeInt(nameof(Reward), 1);
|
||||
ExperienceMultiplier = element.GetAttributeFloat(nameof(ExperienceMultiplier), 1.0f);
|
||||
AllowRetry = element.GetAttributeBool(nameof(AllowRetry), false);
|
||||
ShowSonarLabels = element.GetAttributeBool(nameof(ShowSonarLabels), true);
|
||||
ShowInMenus = element.GetAttributeBool(nameof(ShowInMenus), true);
|
||||
ShowStartMessage = element.GetAttributeBool(nameof(ShowStartMessage), true);
|
||||
IsSideObjective = element.GetAttributeBool("sideobjective", false);
|
||||
Reward = ConfigElement.GetAttributeInt(nameof(Reward), 1);
|
||||
ExperienceMultiplier = ConfigElement.GetAttributeFloat(nameof(ExperienceMultiplier), 1f);
|
||||
AllowRetry = ConfigElement.GetAttributeBool(nameof(AllowRetry), false);
|
||||
ShowSonarLabels = ConfigElement.GetAttributeBool(nameof(ShowSonarLabels), true);
|
||||
ShowInMenus = ConfigElement.GetAttributeBool(nameof(ShowInMenus), true);
|
||||
ShowStartMessage = ConfigElement.GetAttributeBool(nameof(ShowStartMessage), true);
|
||||
IsSideObjective = ConfigElement.GetAttributeBool("sideobjective", false);
|
||||
|
||||
RequireWreck = element.GetAttributeBool(nameof(RequireWreck), false);
|
||||
RequireThalamusWreck = element.GetAttributeBool(nameof(RequireThalamusWreck), false);
|
||||
RequireRuin = element.GetAttributeBool(nameof(RequireRuin), false);
|
||||
RequireBeaconStation = element.GetAttributeBool(nameof(RequireBeaconStation), false);
|
||||
SpawnBeaconStationInMiddle = element.GetAttributeBool(nameof(SpawnBeaconStationInMiddle), false);
|
||||
if (RequireThalamusWreck) { RequireWreck = true; }
|
||||
RequireWreck = ConfigElement.GetAttributeBool(nameof(RequireWreck), false);
|
||||
RequireThalamusWreck = ConfigElement.GetAttributeBool(nameof(RequireThalamusWreck), false);
|
||||
RequireRuin = ConfigElement.GetAttributeBool(nameof(RequireRuin), false);
|
||||
RequireBeaconStation = ConfigElement.GetAttributeBool(nameof(RequireBeaconStation), false);
|
||||
SpawnBeaconStationInMiddle = ConfigElement.GetAttributeBool(nameof(SpawnBeaconStationInMiddle), false);
|
||||
RequireWreck |= RequireThalamusWreck;
|
||||
|
||||
LoadSubmarines = element.GetAttributeBool(nameof(LoadSubmarines), true);
|
||||
LoadSubmarines = ConfigElement.GetAttributeBool(nameof(LoadSubmarines), true);
|
||||
|
||||
BlockLocationTypeChanges = element.GetAttributeBool(nameof(BlockLocationTypeChanges), false);
|
||||
RequiredLocationFaction = element.GetAttributeIdentifier(nameof(RequiredLocationFaction), Identifier.Empty);
|
||||
Commonness = element.GetAttributeInt(nameof(Commonness), 1);
|
||||
AllowOtherMissionsInLevel = element.GetAttributeBool(nameof(AllowOtherMissionsInLevel), true);
|
||||
BlockLocationTypeChanges = ConfigElement.GetAttributeBool(nameof(BlockLocationTypeChanges), false);
|
||||
RequiredLocationFaction = ConfigElement.GetAttributeIdentifier(nameof(RequiredLocationFaction), Identifier.Empty);
|
||||
Commonness = ConfigElement.GetAttributeInt(nameof(Commonness), 1);
|
||||
AllowOtherMissionsInLevel = ConfigElement.GetAttributeBool(nameof(AllowOtherMissionsInLevel), true);
|
||||
|
||||
if (element.GetAttribute("difficulty") != null)
|
||||
if (ConfigElement.GetAttribute("difficulty") != null)
|
||||
{
|
||||
int difficulty = element.GetAttributeInt(nameof(Difficulty), MinDifficulty);
|
||||
int difficulty = ConfigElement.GetAttributeInt(nameof(Difficulty), MinDifficulty);
|
||||
Difficulty = Math.Clamp(difficulty, MinDifficulty, MaxDifficulty);
|
||||
}
|
||||
MinLevelDifficulty = element.GetAttributeInt(nameof(MinLevelDifficulty), MinLevelDifficulty);
|
||||
MaxLevelDifficulty = element.GetAttributeInt(nameof(MaxLevelDifficulty), MaxLevelDifficulty);
|
||||
MinLevelDifficulty = ConfigElement.GetAttributeInt(nameof(MinLevelDifficulty), MinLevelDifficulty);
|
||||
MaxLevelDifficulty = ConfigElement.GetAttributeInt(nameof(MaxLevelDifficulty), MaxLevelDifficulty);
|
||||
MinLevelDifficulty = Math.Clamp(MinLevelDifficulty, 0, Math.Min(MaxLevelDifficulty, 100));
|
||||
MaxLevelDifficulty = Math.Clamp(MaxLevelDifficulty, Math.Max(MinLevelDifficulty, 0), 100);
|
||||
|
||||
AllowOutpostNPCs = element.GetAttributeBool(nameof(AllowOutpostNPCs), true);
|
||||
ForceOutpostGenerationParameters = element.GetAttributeIdentifier(nameof(ForceOutpostGenerationParameters), Identifier.Empty);
|
||||
AllowOutpostSelectionFromTag = element.GetAttributeIdentifier(nameof(AllowOutpostSelectionFromTag), Identifier.Empty);
|
||||
AllowOutpostNPCs = ConfigElement.GetAttributeBool(nameof(AllowOutpostNPCs), true);
|
||||
ForceOutpostGenerationParameters = ConfigElement.GetAttributeIdentifier(nameof(ForceOutpostGenerationParameters), Identifier.Empty);
|
||||
AllowOutpostSelectionFromTag = ConfigElement.GetAttributeIdentifier(nameof(AllowOutpostSelectionFromTag), Identifier.Empty);
|
||||
|
||||
if (element.GetAttribute(nameof(ForceRespawnMode)) != null)
|
||||
if (ConfigElement.GetAttribute(nameof(ForceRespawnMode)) != null)
|
||||
{
|
||||
ForceRespawnMode = element.GetAttributeEnum(nameof(ForceRespawnMode), RespawnMode.MidRound);
|
||||
ForceRespawnMode = ConfigElement.GetAttributeEnum(nameof(ForceRespawnMode), RespawnMode.MidRound);
|
||||
}
|
||||
|
||||
ShowProgressBar = element.GetAttributeBool(nameof(ShowProgressBar), false);
|
||||
ShowProgressInNumbers = element.GetAttributeBool(nameof(ShowProgressInNumbers), false);
|
||||
MaxProgressState = element.GetAttributeInt(nameof(MaxProgressState), 1);
|
||||
string progressBarLabel = element.GetAttributeString(nameof(ProgressBarLabel), "");
|
||||
ShowProgressBar = ConfigElement.GetAttributeBool(nameof(ShowProgressBar), false);
|
||||
ShowProgressInNumbers = ConfigElement.GetAttributeBool(nameof(ShowProgressInNumbers), false);
|
||||
MaxProgressState = ConfigElement.GetAttributeInt(nameof(MaxProgressState), 1);
|
||||
string progressBarLabel = ConfigElement.GetAttributeString(nameof(ProgressBarLabel), "");
|
||||
ProgressBarLabel = TextManager.Get(progressBarLabel).Fallback(progressBarLabel);
|
||||
|
||||
string successMessageTag = element.GetAttributeString("successmessage", "");
|
||||
string successMessageTag = ConfigElement.GetAttributeString("successmessage", "");
|
||||
SuccessMessage = TextManager.Get($"MissionSuccess.{TextIdentifier}");
|
||||
if (!string.IsNullOrEmpty(successMessageTag))
|
||||
{
|
||||
SuccessMessage = SuccessMessage
|
||||
.Fallback(TextManager.Get(successMessageTag))
|
||||
.Fallback(successMessageTag);
|
||||
.Fallback(TextManager.Get(successMessageTag))
|
||||
.Fallback(successMessageTag);
|
||||
}
|
||||
SuccessMessage = SuccessMessage.Fallback(TextManager.Get("missioncompleted"));
|
||||
|
||||
string failureMessageTag = element.GetAttributeString("failuremessage", "");
|
||||
string failureMessageTag = ConfigElement.GetAttributeString("failuremessage", "");
|
||||
FailureMessage = TextManager.Get($"MissionFailure.{TextIdentifier}");
|
||||
if (!string.IsNullOrEmpty(failureMessageTag))
|
||||
{
|
||||
FailureMessage = FailureMessage
|
||||
.Fallback(TextManager.Get(failureMessageTag))
|
||||
.Fallback(failureMessageTag);
|
||||
.Fallback(TextManager.Get(failureMessageTag))
|
||||
.Fallback(failureMessageTag);
|
||||
}
|
||||
FailureMessage = FailureMessage.Fallback(TextManager.Get("missionfailed"));
|
||||
|
||||
string sonarLabelTag = element.GetAttributeString("sonarlabel", "");
|
||||
SonarLabel =
|
||||
TextManager.Get($"MissionSonarLabel.{sonarLabelTag}")
|
||||
.Fallback(TextManager.Get(sonarLabelTag))
|
||||
.Fallback(TextManager.Get($"MissionSonarLabel.{TextIdentifier}"));
|
||||
string sonarLabelTag = ConfigElement.GetAttributeString("sonarlabel", "");
|
||||
SonarLabel = TextManager.Get($"MissionSonarLabel.{sonarLabelTag}")
|
||||
.Fallback(TextManager.Get(sonarLabelTag))
|
||||
.Fallback(TextManager.Get($"MissionSonarLabel.{TextIdentifier}"));
|
||||
if (!string.IsNullOrEmpty(sonarLabelTag))
|
||||
{
|
||||
SonarLabel = SonarLabel.Fallback(sonarLabelTag);
|
||||
}
|
||||
|
||||
SonarIconIdentifier = element.GetAttributeIdentifier("sonaricon", "");
|
||||
SonarIconIdentifier = ConfigElement.GetAttributeIdentifier("sonaricon", "");
|
||||
|
||||
MultiplayerOnly = element.GetAttributeBool("multiplayeronly", false);
|
||||
SingleplayerOnly = element.GetAttributeBool("singleplayeronly", false);
|
||||
MultiplayerOnly = ConfigElement.GetAttributeBool("multiplayeronly", false);
|
||||
SingleplayerOnly = ConfigElement.GetAttributeBool("singleplayeronly", false);
|
||||
|
||||
AchievementIdentifier = element.GetAttributeIdentifier("achievementidentifier", "");
|
||||
AchievementIdentifier = ConfigElement.GetAttributeIdentifier("achievementidentifier", "");
|
||||
|
||||
UnhideEntitySubCategories = element.GetAttributeStringArray("unhideentitysubcategories", Array.Empty<string>()).ToList();
|
||||
UnhideEntitySubCategories = [.. ConfigElement.GetAttributeStringArray("unhideentitysubcategories", [])];
|
||||
|
||||
var headers = new List<LocalizedString>();
|
||||
var messages = new List<LocalizedString>();
|
||||
AllowedConnectionTypes = new List<(Identifier from, Identifier to)>();
|
||||
List<LocalizedString> headers = [];
|
||||
List<LocalizedString> messages = [];
|
||||
AllowedConnectionTypes = [];
|
||||
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
@@ -322,26 +341,24 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
List<ReputationReward> reputationRewards = new List<ReputationReward>();
|
||||
List<ReputationReward> reputationRewards = [];
|
||||
int messageIndex = 0;
|
||||
foreach (var subElement in element.Elements())
|
||||
foreach (ContentXElement subElement in ConfigElement.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "message":
|
||||
if (messageIndex > headers.Count - 1)
|
||||
if (messageIndex >= headers.Count)
|
||||
{
|
||||
headers.Add(string.Empty);
|
||||
messages.Add(string.Empty);
|
||||
}
|
||||
headers[messageIndex] =
|
||||
TextManager.Get($"MissionHeader{messageIndex}.{TextIdentifier}")
|
||||
.Fallback(TextManager.Get(subElement.GetAttributeString("header", "")))
|
||||
.Fallback(subElement.GetAttributeString("header", ""));
|
||||
messages[messageIndex] =
|
||||
TextManager.Get($"MissionMessage{messageIndex}.{TextIdentifier}")
|
||||
.Fallback(TextManager.Get(subElement.GetAttributeString("text", "")))
|
||||
.Fallback(subElement.GetAttributeString("text", ""));
|
||||
headers[messageIndex] = TextManager.Get($"MissionHeader{messageIndex}.{TextIdentifier}")
|
||||
.Fallback(TextManager.Get(subElement.GetAttributeString("header", "")))
|
||||
.Fallback(subElement.GetAttributeString("header", ""));
|
||||
messages[messageIndex] = TextManager.Get($"MissionMessage{messageIndex}.{TextIdentifier}")
|
||||
.Fallback(TextManager.Get(subElement.GetAttributeString("text", "")))
|
||||
.Fallback(subElement.GetAttributeString("text", ""));
|
||||
messageIndex++;
|
||||
break;
|
||||
case "locationtype":
|
||||
@@ -352,9 +369,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
AllowedConnectionTypes.Add((
|
||||
subElement.GetAttributeIdentifier("from", ""),
|
||||
subElement.GetAttributeIdentifier("to", "")));
|
||||
AllowedConnectionTypes.Add((subElement.GetAttributeIdentifier("from", ""), subElement.GetAttributeIdentifier("to", "")));
|
||||
}
|
||||
break;
|
||||
case "locationtypechange":
|
||||
@@ -375,7 +390,7 @@ namespace Barotrauma
|
||||
string operatingString = subElement.GetAttributeString("operation", string.Empty);
|
||||
if (!string.IsNullOrWhiteSpace(operatingString))
|
||||
{
|
||||
operation = (SetDataAction.OperationType) Enum.Parse(typeof(SetDataAction.OperationType), operatingString);
|
||||
operation = (SetDataAction.OperationType)Enum.Parse(typeof(SetDataAction.OperationType), operatingString);
|
||||
}
|
||||
|
||||
DataRewards.Add((identifier, value, operation));
|
||||
@@ -386,13 +401,13 @@ namespace Barotrauma
|
||||
break;
|
||||
}
|
||||
}
|
||||
Headers = headers.ToImmutableArray();
|
||||
Messages = messages.ToImmutableArray();
|
||||
ReputationRewards = reputationRewards.ToImmutableList();
|
||||
Headers = [.. headers];
|
||||
Messages = [.. messages];
|
||||
ReputationRewards = [.. reputationRewards];
|
||||
|
||||
MissionClass = FindMissionClass(ConfigElement);
|
||||
Type = ConfigElement.GetAttributeIdentifier(nameof(Type), Identifier.Empty);
|
||||
|
||||
MissionClass = FindMissionClass(element);
|
||||
Type = element.GetAttributeIdentifier(nameof(Type), Identifier.Empty);
|
||||
|
||||
#if DEBUG
|
||||
if (MissionClass == typeof(MonsterMission) && SonarLabel.IsNullOrEmpty())
|
||||
{
|
||||
@@ -403,17 +418,19 @@ namespace Barotrauma
|
||||
if (!LoadSubmarines && MissionClass != typeof(CombatMission))
|
||||
{
|
||||
DebugConsole.AddWarning($"Potential error in mission {Identifier}: Disabling submarines is only intended for combat missions taking place in an outpost, and may lead to issues in other types of missions.",
|
||||
contentPackage: element.ContentPackage);
|
||||
contentPackage: ConfigElement.ContentPackage);
|
||||
}
|
||||
|
||||
constructor = FindMissionConstructor(element, MissionClass);
|
||||
constructor = FindMissionConstructor(ConfigElement, MissionClass);
|
||||
if (constructor == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to find a constructor for the mission type \"{Type}\"!",
|
||||
contentPackage: element.ContentPackage);
|
||||
contentPackage: ConfigElement.ContentPackage);
|
||||
}
|
||||
|
||||
InitProjSpecific(element);
|
||||
#if CLIENT
|
||||
ParseConfigElementClient(ConfigElement, variantOf);
|
||||
#endif
|
||||
}
|
||||
|
||||
private Type FindMissionClass(ContentXElement element)
|
||||
@@ -476,8 +493,6 @@ namespace Barotrauma
|
||||
}
|
||||
return constructor;
|
||||
}
|
||||
|
||||
partial void InitProjSpecific(ContentXElement element);
|
||||
|
||||
public bool IsAllowed(Location from, Location to)
|
||||
{
|
||||
|
||||
@@ -64,6 +64,8 @@ namespace Barotrauma
|
||||
|
||||
foreach (var monsterElement in prefab.ConfigElement.GetChildElements("monster"))
|
||||
{
|
||||
if (GameMain.NetworkMember == null && monsterElement.GetAttributeBool("multiplayeronly", false)) { continue; }
|
||||
|
||||
speciesName = monsterElement.GetAttributeIdentifier("character", Identifier.Empty);
|
||||
int defaultCount = monsterElement.GetAttributeInt("count", -1);
|
||||
if (defaultCount < 0)
|
||||
|
||||
@@ -14,8 +14,8 @@ namespace Barotrauma
|
||||
private readonly List<Item> items = new List<Item>();
|
||||
private readonly Dictionary<Item, StatusEffect> statusEffectOnApproach = new Dictionary<Item, StatusEffect>();
|
||||
|
||||
//string = filename, point = min,max
|
||||
private readonly HashSet<Tuple<CharacterPrefab, Point>> monsterPrefabs = new HashSet<Tuple<CharacterPrefab, Point>>();
|
||||
//key = monster to spawn, point = min,max
|
||||
private readonly List<Tuple<CharacterPrefab, Point>> monsterPrefabs = new List<Tuple<CharacterPrefab, Point>>();
|
||||
|
||||
private float itemSpawnRadius = 800.0f;
|
||||
private readonly float approachItemsRadius = 1000.0f;
|
||||
@@ -70,6 +70,8 @@ namespace Barotrauma
|
||||
|
||||
foreach (var monsterElement in prefab.ConfigElement.GetChildElements("monster"))
|
||||
{
|
||||
if (GameMain.NetworkMember == null && monsterElement.GetAttributeBool("multiplayeronly", false)) { continue; }
|
||||
|
||||
Identifier speciesName = monsterElement.GetAttributeIdentifier("character", Identifier.Empty);
|
||||
int defaultCount = monsterElement.GetAttributeInt("count", -1);
|
||||
if (defaultCount < 0)
|
||||
|
||||
@@ -300,7 +300,9 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
Submarine refSub = GetReferenceSub(acceptRemoteControlledSubs: true);
|
||||
if (Submarine.MainSubs.Length == 2 && Submarine.MainSubs[1] != null)
|
||||
//randomly choose which main sub to spawn the monsters around if there's 2 player subs (e.g. in the PvP mode)
|
||||
if (Submarine.MainSubs.Length == 2 &&
|
||||
Submarine.MainSubs[1] is { Info.Type: SubmarineType.Player })
|
||||
{
|
||||
refSub = Submarine.MainSubs.GetRandom(Rand.RandSync.Unsynced);
|
||||
}
|
||||
|
||||
@@ -89,7 +89,19 @@ namespace Barotrauma
|
||||
|
||||
private static async Task<AuthTicket> GetSteamAuthTicket()
|
||||
{
|
||||
var authTicket = await SteamManager.GetAuthTicketForGameAnalyticsConsent();
|
||||
var authTicketTask = SteamManager.GetAuthTicketForGameAnalyticsConsent();
|
||||
|
||||
// Add a timeout to prevent the game from freezing indefinitely
|
||||
var timeoutTask = Task.Delay(TimeSpan.FromSeconds(10));
|
||||
|
||||
var completedTask = await Task.WhenAny(authTicketTask, timeoutTask);
|
||||
if (completedTask == timeoutTask)
|
||||
{
|
||||
throw new TimeoutException("Timed out while trying to retrieve Steamworks authentication ticket for GameAnalytics.");
|
||||
}
|
||||
|
||||
var authTicket = await authTicketTask;
|
||||
|
||||
return authTicket.TryUnwrap(out var ticketUnwrapped) && ticketUnwrapped.Data is { Length: > 0 }
|
||||
? new AuthTicket(ToolBoxCore.ByteArrayToHexString(ticketUnwrapped.Data), Platform.Steam) //convert byte array to hex
|
||||
: throw new Exception("Could not retrieve Steamworks authentication ticket for GameAnalytics");
|
||||
|
||||
@@ -690,7 +690,7 @@ namespace Barotrauma
|
||||
foreach (Item containedItem in character.Inventory.AllItemsMod)
|
||||
{
|
||||
//only put into containers that draw the inventory (not ones with a hidden inventory like circuit boxes!)
|
||||
if (containedItem.OwnInventory?.Container is { DrawInventory: true } &&
|
||||
if (containedItem.OwnInventory?.Container is { DrawInventory: true } container && container.IsAccessible() &&
|
||||
containedItem.OwnInventory.TryPutItem(item, user: null, item.AllowedSlots))
|
||||
{
|
||||
break;
|
||||
|
||||
@@ -469,7 +469,8 @@ namespace Barotrauma
|
||||
foreach (var mission in currentLocation.AvailableMissions)
|
||||
{
|
||||
//if the mission isn't shown in menus, it cannot be selected by the player -> must be something that is supposed to be automatically selected
|
||||
if (!mission.Prefab.ShowInMenus)
|
||||
//side objectives are also automatically selected
|
||||
if (!mission.Prefab.ShowInMenus || mission.Prefab.IsSideObjective)
|
||||
{
|
||||
currentLocation.SelectMission(mission);
|
||||
}
|
||||
@@ -1429,18 +1430,18 @@ namespace Barotrauma
|
||||
map = null;
|
||||
}
|
||||
|
||||
public int NumberOfMissionsAtLocation(Location location)
|
||||
public int NumberOfSelectableMissionsAtLocation(Location location)
|
||||
{
|
||||
return Map?.CurrentLocation?.SelectedMissions?.Count(m => m.Locations.Contains(location)) ?? 0;
|
||||
return Map?.CurrentLocation?.SelectedMissions?.Count(m => m.Locations.Contains(location) && !m.Prefab.IsSideObjective) ?? 0;
|
||||
}
|
||||
|
||||
public void CheckTooManyMissions(Location currentLocation, Client sender)
|
||||
{
|
||||
foreach (Location location in currentLocation.Connections.Select(c => c.OtherLocation(currentLocation)))
|
||||
{
|
||||
if (NumberOfMissionsAtLocation(location) > Settings.TotalMaxMissionCount)
|
||||
if (NumberOfSelectableMissionsAtLocation(location) > Settings.TotalMaxMissionCount)
|
||||
{
|
||||
DebugConsole.AddWarning($"Client {sender.Name} had too many missions selected for location {location.DisplayName}! Count was {NumberOfMissionsAtLocation(location)}. Deselecting extra missions.");
|
||||
DebugConsole.AddWarning($"Client {sender.Name} had too many missions selected for location {location.DisplayName}! Count was {NumberOfSelectableMissionsAtLocation(location)}. Deselecting extra missions.");
|
||||
foreach (Mission mission in currentLocation.SelectedMissions.Where(m => m.Locations[1] == location).Skip(Settings.TotalMaxMissionCount).ToList())
|
||||
{
|
||||
currentLocation.DeselectMission(mission);
|
||||
|
||||
@@ -30,11 +30,23 @@ namespace Barotrauma
|
||||
: base(preset)
|
||||
{
|
||||
Location[] locations = { GameMain.GameSession.StartLocation, GameMain.GameSession.EndLocation };
|
||||
var mission = Mission.LoadRandom(locations, seed, requireCorrectLocationType: false, missionTypes, difficultyLevel: GameMain.NetworkMember.ServerSettings.SelectedLevelDifficulty);
|
||||
float difficulty = GameMain.NetworkMember.ServerSettings.SelectedLevelDifficulty;
|
||||
var mission = Mission.LoadRandom(locations, seed, requireCorrectLocationType: false, missionTypes, difficultyLevel: difficulty);
|
||||
if (mission == null)
|
||||
{
|
||||
DebugConsole.AddWarning(
|
||||
$"Could not find any missions matching the mission types {string.Join(", ", missionTypes.Select(m => m.Value))} " +
|
||||
$"and the difficulty {difficulty}. Ignoring the difficulty requirement...");
|
||||
mission = Mission.LoadRandom(locations, seed, requireCorrectLocationType: false, missionTypes);
|
||||
}
|
||||
if (mission != null)
|
||||
{
|
||||
missions.Add(mission);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.AddWarning($"Could not find any missions matching the mission types {string.Join(", ", missionTypes.Select(m => m.Value))}.");
|
||||
}
|
||||
}
|
||||
|
||||
protected static IEnumerable<MissionPrefab> ValidateMissionPrefabs(IEnumerable<MissionPrefab> missionPrefabs, Dictionary<Identifier, Type> missionClasses)
|
||||
|
||||
@@ -741,7 +741,7 @@ namespace Barotrauma
|
||||
var missionsToShow = missions.Where(m => m.Prefab.ShowStartMessage);
|
||||
if (missionsToShow.Count() > 1)
|
||||
{
|
||||
string joinedMissionNames = string.Join(", ", missions.Select(m => m.Name));
|
||||
string joinedMissionNames = string.Join(", ", missions.Where(static m => m.Prefab.ShowInMenus).Select(static m => m.Name));
|
||||
GUI.AddMessage(TextManager.AddPunctuation(':', TextManager.Get("Mission"), joinedMissionNames), Color.CadetBlue, playSound: false);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -226,6 +226,14 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsSlotEmpty(InvSlotType limbSlot)
|
||||
{
|
||||
for (int i = 0; i < slots.Length; i++)
|
||||
{
|
||||
if (SlotTypes[i] == limbSlot && slots[i].Empty()) { return true; }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can the item be put in the inventory in a slot of the specified type (i.e. is there a suitable free slot or a stack the item can be put in).
|
||||
@@ -438,7 +446,8 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
int placedInSlot = -1;
|
||||
foreach (InvSlotType allowedSlot in allowedSlots)
|
||||
//order by whether the slot is empty, i.e. try putting in free slots first before trying to unequip items from occupied slots
|
||||
foreach (InvSlotType allowedSlot in allowedSlots.OrderBy(slotType => IsSlotEmpty(slotType) ? 0 : 1))
|
||||
{
|
||||
if (allowedSlot.HasFlag(InvSlotType.RightHand) && character.AnimController.GetLimb(LimbType.RightHand) == null) { continue; }
|
||||
if (allowedSlot.HasFlag(InvSlotType.LeftHand) && character.AnimController.GetLimb(LimbType.LeftHand) == null) { continue; }
|
||||
|
||||
@@ -87,6 +87,13 @@ namespace Barotrauma.Items.Components
|
||||
"Normally there's no need to touch this setting, but if you notice the docking position is incorrect (for example due to some unusual docking port configuration without hulls or doors), you can use this to enforce the direction.")]
|
||||
public DirectionType ForceDockingDirection { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes, description: "Was the docking port docked at the end of the previous round.")]
|
||||
public bool WasDocked
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public DockingPort DockingTarget { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -280,6 +287,9 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
OnDocked?.Invoke();
|
||||
OnDocked = null;
|
||||
|
||||
WasDocked = true;
|
||||
DockingTarget.Docked = true;
|
||||
}
|
||||
|
||||
public void Lock(bool isNetworkMessage, bool applyEffects = true, bool moveSubs = true)
|
||||
@@ -988,6 +998,8 @@ namespace Barotrauma.Items.Components
|
||||
Item.Submarine.EnableObstructedWaypoints(DockingTarget.Item.Submarine);
|
||||
obstructedWayPointsDisabled = false;
|
||||
|
||||
WasDocked = false;
|
||||
DockingTarget.WasDocked = false;
|
||||
DockingTarget.Undock();
|
||||
DockingTarget = null;
|
||||
|
||||
@@ -1052,6 +1064,16 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
//PRETTY HACKY:
|
||||
//the docking port was docked on the previous round, but not any more -
|
||||
//must mean that whatever it was docked to (e.g. some enemy sub or respawn shuttle) no longer exists
|
||||
//let's send an "on_undock" signal so circuits can react to the undocking that never "actually" happened
|
||||
if (!docked && WasDocked)
|
||||
{
|
||||
item.SendSignal("1", "on_undock");
|
||||
WasDocked = false;
|
||||
}
|
||||
|
||||
dockingCooldown -= deltaTime;
|
||||
if (DockingTarget == null)
|
||||
{
|
||||
@@ -1208,19 +1230,21 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
if (!item.linkedTo.Any()) { return; }
|
||||
|
||||
List<MapEntity> linked = new List<MapEntity>(item.linkedTo);
|
||||
foreach (MapEntity entity in linked)
|
||||
{
|
||||
if (!(entity is Item linkedItem)) { continue; }
|
||||
|
||||
var dockingPort = linkedItem.GetComponent<DockingPort>();
|
||||
if (dockingPort != null)
|
||||
if (item.linkedTo.Any())
|
||||
{
|
||||
List<MapEntity> linked = new List<MapEntity>(item.linkedTo);
|
||||
foreach (MapEntity entity in linked)
|
||||
{
|
||||
Dock(dockingPort);
|
||||
}
|
||||
if (entity is not Item linkedItem) { continue; }
|
||||
|
||||
var dockingPort = linkedItem.GetComponent<DockingPort>();
|
||||
if (dockingPort != null)
|
||||
{
|
||||
Dock(dockingPort);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public override void ReceiveSignal(Signal signal, Connection connection)
|
||||
|
||||
@@ -154,6 +154,13 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize("0,0", IsPropertySaveable.Yes)]
|
||||
public Point DisallowAttachingOverSize
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No, description: "Should the item be attached to a wall by default when it's placed in the submarine editor.")]
|
||||
public bool AttachedByDefault
|
||||
{
|
||||
@@ -496,13 +503,19 @@ namespace Barotrauma.Items.Components
|
||||
Vector2 diff = new Vector2(
|
||||
(heldHand.SimPosition.X - arm.SimPosition.X) / 2f,
|
||||
(heldHand.SimPosition.Y - arm.SimPosition.Y) / 2.5f);
|
||||
item.SetTransform(heldHand.SimPosition + diff, 0.0f);
|
||||
|
||||
//we have forced the item to be in the same sub as the dropper above,
|
||||
//and are placing it to the position of the hands in "local" coordinates
|
||||
//which may be outside the sub if the character is e.g. standing half-way through the airlock
|
||||
// -> let's use the forceSubmarine argument ensure the item is still considered to be in the sub's coordinate space,
|
||||
// or it will end up in a weird state and seemingly disappear
|
||||
item.SetTransform(heldHand.SimPosition + diff, 0.0f, forceSubmarine: picker.Submarine);
|
||||
}
|
||||
else
|
||||
{
|
||||
item.SetTransform(picker.SimPosition, 0.0f);
|
||||
}
|
||||
}
|
||||
item.SetTransform(picker.SimPosition, 0.0f, forceSubmarine: picker.Submarine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
picker.Inventory.RemoveItem(item);
|
||||
@@ -621,17 +634,34 @@ namespace Barotrauma.Items.Components
|
||||
if (disallowAttachingOverTags.Any() || !AllowAttachInsideDoors)
|
||||
{
|
||||
var connectedHulls = item.CurrentHull?.GetConnectedHulls(includingThis: true, searchDepth: 5, ignoreClosedGaps: true);
|
||||
Vector2 size = item.Rect.Size.ToVector2() / 2;
|
||||
|
||||
Vector2 size = DisallowAttachingOverSize == Point.Zero ?
|
||||
item.Rect.Size.ToVector2() :
|
||||
DisallowAttachingOverSize.ToVector2() * item.Scale;
|
||||
size /= 2f;
|
||||
|
||||
foreach (Item otherItem in Item.ItemList)
|
||||
{
|
||||
if (otherItem == item || otherItem.body is { BodyType: BodyType.Dynamic, Enabled: true }) { continue; }
|
||||
if (connectedHulls != null && !connectedHulls.Contains(otherItem.CurrentHull)) { continue; }
|
||||
if (disallowAttachingOverTags.None(tag => otherItem.HasTag(tag)) &&
|
||||
if (disallowAttachingOverTags.None(otherItem.HasTag) &&
|
||||
(otherItem.GetComponent<Door>() == null || AllowAttachInsideDoors))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Rectangle worldRect = otherItem.WorldRect;
|
||||
|
||||
if (otherItem.GetComponent<Holdable>() is Holdable otherHoldable)
|
||||
{
|
||||
if (!otherHoldable.attached) { continue; }
|
||||
if (otherHoldable.DisallowAttachingOverSize != Point.Zero)
|
||||
{
|
||||
Vector2 scaledSize = otherHoldable.DisallowAttachingOverSize.ToVector2() * item.Scale;
|
||||
worldRect = new Rectangle(
|
||||
otherItem.WorldPosition.ToPoint() - new Point((int)(scaledSize.X / 2), (int)(-scaledSize.Y / 2)),
|
||||
scaledSize.ToPoint());
|
||||
}
|
||||
}
|
||||
if (attachPos.X + size.X < worldRect.X || attachPos.X - size.X > worldRect.Right) { continue; }
|
||||
if (attachPos.Y - size.Y > worldRect.Y || attachPos.Y + size.Y < worldRect.Y - worldRect.Height) { continue; }
|
||||
tempOverlappingItems.Add(otherItem);
|
||||
|
||||
@@ -538,8 +538,8 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
if (targetEntity != null)
|
||||
{
|
||||
ApplyStatusEffects(conditionalActionType, 1.0f, targetCharacter, targetLimb, useTarget: targetEntity, user: user, afflictionMultiplier: damageMultiplier);
|
||||
ApplyStatusEffects(ActionType.OnUse, 1.0f, targetCharacter, targetLimb, useTarget: targetEntity, user: user, afflictionMultiplier: damageMultiplier);
|
||||
ApplyStatusEffects(conditionalActionType, 1.0f, targetCharacter, targetLimb, useTarget: targetEntity, user: user, attackMultiplier: damageMultiplier);
|
||||
ApplyStatusEffects(ActionType.OnUse, 1.0f, targetCharacter, targetLimb, useTarget: targetEntity, user: user, attackMultiplier: damageMultiplier);
|
||||
}
|
||||
|
||||
if (DeleteOnUse)
|
||||
|
||||
@@ -920,7 +920,8 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public void ApplyStatusEffects(ActionType type, float deltaTime, Character character = null, Limb targetLimb = null, Entity useTarget = null, Character user = null, Vector2? worldPosition = null, float afflictionMultiplier = 1.0f)
|
||||
/// <param name="attackMultiplier">Multiplier used on afflictions caused by the status effects, except ones that <see cref="AfflictionPrefab.AffectedByAttackMultipliers">have been configured to not be affected by attack multipliers.</see></param>
|
||||
public void ApplyStatusEffects(ActionType type, float deltaTime, Character character = null, Limb targetLimb = null, Entity useTarget = null, Character user = null, Vector2? worldPosition = null, float attackMultiplier = 1.0f)
|
||||
{
|
||||
if (statusEffectLists == null) { return; }
|
||||
|
||||
@@ -932,7 +933,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (broken && !effect.AllowWhenBroken && effect.type != ActionType.OnBroken) { continue; }
|
||||
if (user != null) { effect.SetUser(user); }
|
||||
effect.AfflictionMultiplier = afflictionMultiplier;
|
||||
effect.AttackMultiplier = attackMultiplier;
|
||||
var c = character;
|
||||
if (user != null && effect.HasTargetType(StatusEffect.TargetType.Character) && !effect.HasTargetType(StatusEffect.TargetType.UseTarget))
|
||||
{
|
||||
@@ -940,7 +941,7 @@ namespace Barotrauma.Items.Components
|
||||
c = user;
|
||||
}
|
||||
item.ApplyStatusEffect(effect, type, deltaTime, c, targetLimb, useTarget, isNetworkEvent: false, checkCondition: false, worldPosition);
|
||||
effect.AfflictionMultiplier = 1.0f;
|
||||
effect.AttackMultiplier = 1.0f;
|
||||
reducesCondition |= effect.ReducesItemCondition();
|
||||
}
|
||||
//if any of the effects reduce the item's condition, set the user for OnBroken effects as well
|
||||
|
||||
@@ -832,7 +832,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public float FabricationDegreeOfSuccess(Character character, ImmutableArray<Skill> skills)
|
||||
{
|
||||
if (skills.Length == 0) { return 1.0f; }
|
||||
if (skills.Length == 0) { return 0.5f; }
|
||||
if (character == null) { return 0.0f; }
|
||||
|
||||
float minDegreeOfSuccess = 1.0f;
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
using Barotrauma.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Makes the item inherit the condition from a linked wall or multiple - or in other words, makes it essentially treat the health of the wall as its own health.
|
||||
/// The wall section with the most damage determines the condition (i.e. the item will be fully broken if there's at least one fully broken wall section).
|
||||
/// </summary>
|
||||
class InheritConditionFromLinkedWall(Item item, ContentXElement element) : ItemComponent(item, element)
|
||||
{
|
||||
private readonly List<Structure> linkedWalls = [];
|
||||
|
||||
public override void OnMapLoaded()
|
||||
{
|
||||
foreach (var linkedTo in item.linkedTo)
|
||||
{
|
||||
if (linkedTo is Structure structure &&
|
||||
structure.HasBody)
|
||||
{
|
||||
linkedWalls.Add(structure);
|
||||
structure.OnHealthChanged += (_, _) => UpdateCondition();
|
||||
}
|
||||
}
|
||||
if (linkedWalls.None())
|
||||
{
|
||||
DebugConsole.AddWarning($"The item {item.Name} ({item.Prefab.Identifier}) is not linked to any walls with a physics body. The {nameof(InheritConditionFromLinkedWall)} component will do nothing.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void UpdateCondition()
|
||||
{
|
||||
float lowestHealthPercent = 1.0f;
|
||||
foreach (var wall in linkedWalls)
|
||||
{
|
||||
foreach (var section in wall.Sections)
|
||||
{
|
||||
lowestHealthPercent = Math.Min(lowestHealthPercent, 1.0f - section.damage / wall.MaxHealth);
|
||||
}
|
||||
}
|
||||
item.Condition = item.MaxCondition * lowestHealthPercent;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -136,6 +136,7 @@ namespace Barotrauma.Items.Components
|
||||
item.CurrentHull.GetLinkedHulls(linkedHulls, includeHiddenHulls: true);
|
||||
foreach (var linkedHull in linkedHulls)
|
||||
{
|
||||
if (linkedHull == item.CurrentHull) { continue; }
|
||||
hullWaterVolume += linkedHull.WaterVolume;
|
||||
totalHullVolume += linkedHull.Volume;
|
||||
}
|
||||
@@ -148,7 +149,7 @@ namespace Barotrauma.Items.Components
|
||||
if (!IsActive || Disabled) { return; }
|
||||
if (flowPercentage <= 0f && item.CurrentHull.WaterVolume <= 0f) { return; }
|
||||
|
||||
float powerFactor = Math.Min(currPowerConsumption <= 0.0f || MinVoltage <= 0.0f ? 1.0f : Voltage, MaxOverVoltageFactor);
|
||||
float powerFactor = Math.Min(PowerConsumption <= 0.0f || MinVoltage <= 0.0f ? 1.0f : Voltage, MaxOverVoltageFactor);
|
||||
|
||||
currFlow = flowPercentage / 100.0f * MaxFlow * powerFactor;
|
||||
if (item.GetComponent<Repairable>() is { IsTinkering: true } repairable)
|
||||
|
||||
@@ -247,9 +247,13 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
bool fissionRateControlledBySignals = signalControlledTargetFissionRate.HasValue && lastReceivedFissionRateSignalTime > Timing.TotalTime - 1;
|
||||
bool turbineOutputRateControlledBySignals = signalControlledTargetTurbineOutput.HasValue && lastReceivedTurbineOutputSignalTime > Timing.TotalTime - 1;
|
||||
|
||||
//rapidly adjust the reactor in the first few seconds of the round to prevent overvoltages if the load changed between rounds
|
||||
//(unless the reactor is being operated by a player)
|
||||
if (GameMain.GameSession is { RoundDuration: <5 } && lastUser is not { IsPlayer: true })
|
||||
if (GameMain.GameSession is { RoundDuration: < 5 } && lastUser is not { IsPlayer: true } && PowerOn && AutoTemp &&
|
||||
!fissionRateControlledBySignals && !turbineOutputRateControlledBySignals)
|
||||
{
|
||||
UpdateAutoTemp(100.0f, (float)(Timing.Step * 10.0f));
|
||||
}
|
||||
@@ -263,7 +267,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
float maxPowerOut = GetMaxOutput();
|
||||
|
||||
if (signalControlledTargetFissionRate.HasValue && lastReceivedFissionRateSignalTime > Timing.TotalTime - 1)
|
||||
if (fissionRateControlledBySignals)
|
||||
{
|
||||
TargetFissionRate = adjustValueWithoutOverShooting(TargetFissionRate, signalControlledTargetFissionRate.Value, deltaTime * 5.0f);
|
||||
#if CLIENT
|
||||
@@ -274,7 +278,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
signalControlledTargetFissionRate = null;
|
||||
}
|
||||
if (signalControlledTargetTurbineOutput.HasValue && lastReceivedTurbineOutputSignalTime > Timing.TotalTime - 1)
|
||||
if (turbineOutputRateControlledBySignals)
|
||||
{
|
||||
TargetTurbineOutput = adjustValueWithoutOverShooting(TargetTurbineOutput, signalControlledTargetTurbineOutput.Value, deltaTime * 5.0f);
|
||||
#if CLIENT
|
||||
|
||||
@@ -641,8 +641,7 @@ namespace Barotrauma.Items.Components
|
||||
for (int i = 0; i < hits.Count; i++)
|
||||
{
|
||||
var h = hits[i];
|
||||
item.SetTransform(h.Point, rotation);
|
||||
item.Submarine = h.Submarine;
|
||||
item.SetTransform(h.Point, rotation, forceSubmarine: h.Submarine);
|
||||
item.UpdateTransform();
|
||||
if (HandleProjectileCollision(h.Fixture, h.Normal, Vector2.Zero))
|
||||
{
|
||||
|
||||
@@ -103,7 +103,7 @@ namespace Barotrauma.Items.Components
|
||||
var inputBuilder = ImmutableArray.CreateBuilder<CircuitBoxInputConnection>();
|
||||
var outputBuilder = ImmutableArray.CreateBuilder<CircuitBoxOutputConnection>();
|
||||
|
||||
foreach (Connection conn in Item.Connections)
|
||||
foreach (Connection conn in Item.Connections.OrderBy(static c => c.DisplayOrder))
|
||||
{
|
||||
if (conn.IsOutput)
|
||||
{
|
||||
@@ -236,9 +236,7 @@ namespace Barotrauma.Items.Components
|
||||
cloneNode.ReplaceAllConnectionLabelOverrides(origNode.ConnectionLabelOverrides);
|
||||
}
|
||||
|
||||
if (!clonedContainedItems.Any()) { return; }
|
||||
|
||||
foreach (var origComp in original.Components)
|
||||
foreach (CircuitBoxComponent origComp in original.Components)
|
||||
{
|
||||
if (!clonedContainedItems.TryGetValue(origComp.Item.ID, out var clonedItem)) { continue; }
|
||||
var newComponent = new CircuitBoxComponent(origComp.ID, clonedItem, origComp.Position, this, origComp.UsedResource);
|
||||
@@ -661,6 +659,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
wire.From.Connection.CircuitBoxConnections.Remove(wire.To);
|
||||
wire.To.Connection.CircuitBoxConnections.Remove(wire.From);
|
||||
|
||||
if (wire.From is CircuitBoxInputConnection input)
|
||||
{
|
||||
|
||||
@@ -17,6 +17,8 @@ namespace Barotrauma.Items.Components
|
||||
//how many wires can be linked to this connection in total
|
||||
public readonly int MaxWires = 5;
|
||||
|
||||
public readonly int DisplayOrder;
|
||||
|
||||
public readonly string Name;
|
||||
private readonly LocalizedString _displayName;
|
||||
public LocalizedString DisplayName
|
||||
@@ -92,7 +94,7 @@ namespace Barotrauma.Items.Components
|
||||
return "Connection (" + item.Name + ", " + Name + ")";
|
||||
}
|
||||
|
||||
public Connection(ContentXElement element, ConnectionPanel connectionPanel, IdRemap idRemap)
|
||||
public Connection(ContentXElement element, int connectionIndex, ConnectionPanel connectionPanel, IdRemap idRemap, bool isItemSwap)
|
||||
{
|
||||
|
||||
#if CLIENT
|
||||
@@ -117,25 +119,44 @@ namespace Barotrauma.Items.Components
|
||||
IsOutput = element.Name.ToString() == "output";
|
||||
Name = element.GetAttributeString("name", IsOutput ? "output" : "input");
|
||||
|
||||
int displayOrder;
|
||||
if (element.GetAttribute("displayorderoverride") is not { } displayOrderAttr)
|
||||
{
|
||||
var sameElements = connectionPanel.Connections.Where(c => c.IsOutput == IsOutput);
|
||||
displayOrder = !sameElements.Any() ? 0 : sameElements.Max(static c => c.DisplayOrder) + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
displayOrder = displayOrderAttr.GetAttributeInt(0);
|
||||
}
|
||||
|
||||
DisplayOrder = displayOrder;
|
||||
|
||||
string displayNameTag = "", fallbackTag = "";
|
||||
//if displayname is not present, attempt to find it from the prefab
|
||||
if (element.GetAttribute("displayname") == null)
|
||||
{
|
||||
foreach (var subElement in item.Prefab.ConfigElement.Elements())
|
||||
{
|
||||
if (!subElement.Name.ToString().Equals("connectionpanel", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
|
||||
if (!subElement.Name.ToString().Equals("connectionpanel", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
int prefabConnectionIndex = 0;
|
||||
foreach (XElement connectionElement in subElement.Elements())
|
||||
{
|
||||
string prefabConnectionName = connectionElement.GetAttributeString("name", null);
|
||||
if (prefabConnectionName.IsNullOrEmpty()) { continue; }
|
||||
|
||||
string[] aliases = connectionElement.GetAttributeStringArray("aliases", Array.Empty<string>());
|
||||
if (prefabConnectionName == Name || aliases.Contains(Name))
|
||||
if (prefabConnectionName == Name || aliases.Contains(Name) ||
|
||||
//when swapping items, we move wires based on the order of the connections, not the names
|
||||
//= we should find a connection based on the index if the name doesn't match
|
||||
(isItemSwap && connectionIndex == prefabConnectionIndex))
|
||||
{
|
||||
displayNameTag = connectionElement.GetAttributeString("displayname", "");
|
||||
fallbackTag = connectionElement.GetAttributeString("fallbackdisplayname", "");
|
||||
}
|
||||
prefabConnectionIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -78,10 +78,10 @@ namespace Barotrauma.Items.Components
|
||||
switch (subElement.Name.ToString())
|
||||
{
|
||||
case "input":
|
||||
Connections.Add(new Connection(subElement, this, IdRemap.DiscardId));
|
||||
Connections.Add(new Connection(subElement, connectionIndex: Connections.Count, this, IdRemap.DiscardId, isItemSwap: false));
|
||||
break;
|
||||
case "output":
|
||||
Connections.Add(new Connection(subElement, this, IdRemap.DiscardId));
|
||||
Connections.Add(new Connection(subElement, connectionIndex: Connections.Count, this, IdRemap.DiscardId, isItemSwap: false));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -293,10 +293,10 @@ namespace Barotrauma.Items.Components
|
||||
switch (subElement.Name.ToString())
|
||||
{
|
||||
case "input":
|
||||
loadedConnections.Add(new Connection(subElement, this, idRemap));
|
||||
loadedConnections.Add(new Connection(subElement, connectionIndex: loadedConnections.Count, this, idRemap, isItemSwap));
|
||||
break;
|
||||
case "output":
|
||||
loadedConnections.Add(new Connection(subElement, this, idRemap));
|
||||
loadedConnections.Add(new Connection(subElement, connectionIndex: loadedConnections.Count, this, idRemap, isItemSwap));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
+12
-2
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Barotrauma.Networking;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
@@ -7,7 +8,7 @@ namespace Barotrauma.Items.Components;
|
||||
/// <summary>
|
||||
/// Base class for signal components that can select between input/output connections (e.g. multiplexer and demultiplexer components)
|
||||
/// </summary>
|
||||
abstract class ConnectionSelectorComponent : ItemComponent
|
||||
abstract partial class ConnectionSelectorComponent : ItemComponent, IServerSerializable
|
||||
{
|
||||
protected int selectedConnectionIndex;
|
||||
protected string selectedConnectionIndexStr;
|
||||
@@ -22,6 +23,8 @@ abstract class ConnectionSelectorComponent : ItemComponent
|
||||
get { return selectedConnectionIndex; }
|
||||
set
|
||||
{
|
||||
int prevIndex = selectedConnectionIndex; // store original, so we know if the state has changed and can sync it in MP
|
||||
|
||||
selectedConnectionIndex = Math.Max(0, value);
|
||||
//don't clamp until we've determined how many connections the item has
|
||||
//(can't be done until the connection panel component has been loaded too)
|
||||
@@ -31,6 +34,11 @@ abstract class ConnectionSelectorComponent : ItemComponent
|
||||
}
|
||||
selectedConnectionName = GetConnectionName(selectedConnectionIndex);
|
||||
selectedConnectionIndexStr = selectedConnectionIndex.ToString();
|
||||
|
||||
if (prevIndex != selectedConnectionIndex)
|
||||
{
|
||||
OnStateChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +63,8 @@ abstract class ConnectionSelectorComponent : ItemComponent
|
||||
{
|
||||
}
|
||||
|
||||
partial void OnStateChanged();
|
||||
|
||||
protected abstract string GetConnectionName(int connectionIndex);
|
||||
|
||||
/// <summary>
|
||||
|
||||
+14
-10
@@ -63,10 +63,7 @@ namespace Barotrauma.Items.Components
|
||||
/// This can be used to make them additionally work the other way around, periodically getting the current value of the property from the item and refreshing the UI.
|
||||
/// </summary>
|
||||
public float GetValueInterval { get; set; } = -1.0f;
|
||||
|
||||
#if CLIENT
|
||||
public float GetValueTimer;
|
||||
#endif
|
||||
|
||||
public string Name => "CustomInterfaceElement";
|
||||
|
||||
@@ -248,7 +245,7 @@ namespace Barotrauma.Items.Components
|
||||
ciElement.Label = "Signal out " + customInterfaceElementList.Count(e => e.ContinuousSignal == ciElement.ContinuousSignal);
|
||||
}
|
||||
customInterfaceElementList.Add(ciElement);
|
||||
IsActive |= ciElement.ContinuousSignal;
|
||||
IsActive |= ciElement.ContinuousSignal || ciElement.GetValueInterval > 0.0f;
|
||||
}
|
||||
|
||||
InitProjSpecific();
|
||||
@@ -348,13 +345,10 @@ namespace Barotrauma.Items.Components
|
||||
//make sure the clients know about the states of the checkboxes and text fields
|
||||
if (customInterfaceElementList.Any())
|
||||
{
|
||||
if (item.FullyInitialized)
|
||||
CoroutineManager.Invoke(() =>
|
||||
{
|
||||
CoroutineManager.Invoke(() =>
|
||||
{
|
||||
if (!item.Removed) { item.CreateServerEvent(this); }
|
||||
}, delay: 0.1f);
|
||||
}
|
||||
if (item.FullyInitialized && !item.Removed) { item.CreateServerEvent(this); }
|
||||
}, delay: 0.1f);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -418,6 +412,16 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
foreach (CustomInterfaceElement ciElement in customInterfaceElementList)
|
||||
{
|
||||
if (ciElement.GetValueInterval > 0.0f)
|
||||
{
|
||||
ciElement.GetValueTimer -= deltaTime;
|
||||
if (ciElement.GetValueTimer <= 0.0f)
|
||||
{
|
||||
SetSignalToPropertyValue(ciElement);
|
||||
ciElement.GetValueTimer = ciElement.GetValueInterval;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ciElement.ContinuousSignal && ciElement.PropertyName != "Voltage") { continue; }
|
||||
//TODO: allow changing output when a tickbox is not selected
|
||||
if (!string.IsNullOrEmpty(ciElement.Signal) && ciElement.Connection != null)
|
||||
|
||||
@@ -140,7 +140,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f, DecimalCount = 2), Serialize(0.0f, IsPropertySaveable.Yes, description: "How much light pulsates (in Hz). 0 = not at all, 1 = alternates between full brightness and off.")]
|
||||
[Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f, DecimalCount = 2), Serialize(0.0f, IsPropertySaveable.Yes, description: "How much light pulsates. 0 = not at all, 1 = alternates between full brightness and off.")]
|
||||
public float PulseAmount
|
||||
{
|
||||
get { return pulseAmount; }
|
||||
|
||||
@@ -268,7 +268,7 @@ namespace Barotrauma.Items.Components
|
||||
public float RotationSpeedHighSkill { get; private set; }
|
||||
|
||||
[Serialize("0,0,0,0", IsPropertySaveable.Yes, description: "Optional screen tint color when the item is being operated (R,G,B,A)."),
|
||||
Editable]
|
||||
Editable(TransferToSwappedItem = true)]
|
||||
public Color HudTint { get; set; }
|
||||
|
||||
[Header(localizedTextTag: "sp.turret.AutoOperate.propertyheader")]
|
||||
|
||||
@@ -461,10 +461,25 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public float ImpactTolerance => Prefab.ImpactTolerance;
|
||||
private float impactTolerance;
|
||||
[Serialize(0.0f, IsPropertySaveable.No), ConditionallyEditable(ConditionallyEditable.ConditionType.ReceivesSubmarineImpacts, MinValueFloat = 0, MaxValueFloat = 100)]
|
||||
public float ImpactTolerance
|
||||
{
|
||||
get { return impactTolerance; }
|
||||
set { impactTolerance = Math.Max(value, 0.0f); }
|
||||
}
|
||||
|
||||
public float ImpactDamage => Prefab.ImpactDamage;
|
||||
public float ImpactDamageProbability => Prefab.ImpactDamageProbability;
|
||||
[Serialize(0.0f, IsPropertySaveable.No, description: "The amount of damage the item takes from impacts. Acts as a multiplier on the strength of the impact. Note that ImpactTolerance must be set for impacts to register."),
|
||||
ConditionallyEditable(ConditionallyEditable.ConditionType.ReceivesSubmarineImpacts, MinValueFloat = 0, MaxValueFloat = 100)]
|
||||
public float ImpactDamage { get; set; }
|
||||
|
||||
[Serialize(1.0f, IsPropertySaveable.No, description: "Probability for impacts to register. Defaults to 1. Note that ImpactTolerance must also be set for impacts to register."),
|
||||
ConditionallyEditable(ConditionallyEditable.ConditionType.ReceivesSubmarineImpacts, MinValueFloat = 0, MaxValueFloat = 1)]
|
||||
public float ImpactDamageProbability { get; set; }
|
||||
|
||||
public const float SubmarineImpactCooldown = 0.1f;
|
||||
|
||||
public double LastSubmarineImpactTime;
|
||||
|
||||
public float InteractDistance => Prefab.InteractDistance;
|
||||
|
||||
@@ -1556,7 +1571,7 @@ namespace Barotrauma
|
||||
if (!updateableComponents.Contains(component))
|
||||
{
|
||||
updateableComponents.Add(component);
|
||||
this.isActive = true;
|
||||
this.IsActive = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1647,7 +1662,19 @@ namespace Barotrauma
|
||||
contained.Container = null;
|
||||
}
|
||||
|
||||
public void SetTransform(Vector2 simPosition, float rotation, bool findNewHull = true, bool setPrevTransform = true)
|
||||
/// <summary>
|
||||
/// Sets the position and rotation of the item, and its physics body if it has one.
|
||||
/// </summary>
|
||||
/// <param name="simPosition">Position in simulation units.</param>
|
||||
/// <param name="rotation">Rotation in radians</param>
|
||||
/// <param name="findNewHull">Should the hull the item is inside be immediately updated? Generally only useful to set to false
|
||||
/// for performance reasons when finding the hull is unnecessary (e.g. if it's being forced to something after setting the transform).</param>
|
||||
/// <param name="setPrevTransform">Should the previous transform of the item be immediately set to the new one?
|
||||
/// The previous transform is used to interpolate draw positions/rotations, and you should generally only set this to false if
|
||||
/// you're trying to simulate movement instead of simply teleporting the item somewhere.</param>
|
||||
/// <param name="forceSubmarine">If you know the position is in a specific sub's coordinate space and want to ensure the item
|
||||
/// is still considered to be in that sub even if the transform ended up outside hulls.</param>
|
||||
public void SetTransform(Vector2 simPosition, float rotation, bool findNewHull = true, bool setPrevTransform = true, Submarine forceSubmarine = null)
|
||||
{
|
||||
if (!MathUtils.IsValid(simPosition))
|
||||
{
|
||||
@@ -1685,6 +1712,7 @@ namespace Barotrauma
|
||||
rect.Y = (int)MathF.Round(displayPos.Y + rect.Height / 2.0f);
|
||||
|
||||
if (findNewHull) { FindHull(); }
|
||||
if (forceSubmarine != null) { Submarine = forceSubmarine; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1856,7 +1884,7 @@ namespace Barotrauma
|
||||
if (newRootContainer != RootContainer)
|
||||
{
|
||||
RootContainer = newRootContainer;
|
||||
isActive = true;
|
||||
IsActive = true;
|
||||
foreach (Item containedItem in ContainedItems)
|
||||
{
|
||||
containedItem.RefreshRootContainer();
|
||||
@@ -2371,12 +2399,16 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private bool isActive = true;
|
||||
/// <summary>
|
||||
/// Inactive items are not updated. Note that actions such as dropping can reactivate the item, and that the item can go inactive by itself if it no longer needs updating;
|
||||
/// </summary>
|
||||
public bool IsActive = true;
|
||||
|
||||
public bool IsInRemoveQueue;
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
if (!isActive || IsLayerHidden || IsInRemoveQueue) { return; }
|
||||
if (!IsActive || IsLayerHidden || IsInRemoveQueue) { return; }
|
||||
|
||||
if (impactQueue != null)
|
||||
{
|
||||
@@ -2542,7 +2574,7 @@ namespace Barotrauma
|
||||
#if CLIENT
|
||||
positionBuffer.Clear();
|
||||
#endif
|
||||
isActive = false;
|
||||
IsActive = false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2703,7 +2735,7 @@ namespace Barotrauma
|
||||
impactQueue.Enqueue(impact);
|
||||
}
|
||||
|
||||
isActive = true;
|
||||
IsActive = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -3484,7 +3516,7 @@ namespace Barotrauma
|
||||
|
||||
if (body != null)
|
||||
{
|
||||
isActive = true;
|
||||
IsActive = true;
|
||||
body.Enabled = true;
|
||||
body.PhysEnabled = true;
|
||||
body.ResetDynamics();
|
||||
@@ -3624,7 +3656,7 @@ namespace Barotrauma
|
||||
item.body.Enabled = item.body.PhysEnabled = isFirst;
|
||||
if (isFirst)
|
||||
{
|
||||
item.isActive = true;
|
||||
item.IsActive = true;
|
||||
item.body.ResetDynamics();
|
||||
}
|
||||
}
|
||||
@@ -4385,13 +4417,18 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (var connection in thisConnectionPanel.Connections)
|
||||
{
|
||||
var newConnection = newConnectionPanel.Connections.FirstOrDefault(c => c.Name == connection.Name);
|
||||
if (newConnection == null) { continue; }
|
||||
foreach (var wire in connection.Wires)
|
||||
{
|
||||
int connectionIndex = wire.Connections.IndexOf(connection);
|
||||
int wireConnectionIndex = wire.Connections.IndexOf(connection);
|
||||
wire.RemoveConnection(this);
|
||||
wire.Connect(newConnection, connectionIndex, addNode: false);
|
||||
int thisConnectionIndex = connection.ConnectionPanel.Connections.IndexOf(connection);
|
||||
if (thisConnectionIndex < 0 || thisConnectionIndex >= newConnectionPanel.Connections.Count)
|
||||
{
|
||||
DebugConsole.AddWarning($"Failed to move a wire from the connection {connection.Name} when swapping the item {Name} with {newItem.Name}. The new item probably does not have the same number of connections as the previous one.");
|
||||
continue;
|
||||
}
|
||||
Connection newConnection = newConnectionPanel.Connections[thisConnectionIndex];
|
||||
wire.Connect(newConnection, wireConnectionIndex, addNode: false);
|
||||
newConnection.ConnectWire(wire);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -813,20 +813,6 @@ namespace Barotrauma
|
||||
[Serialize(false, IsPropertySaveable.No)]
|
||||
public bool DamagedByMonsters { get; private set; }
|
||||
|
||||
private float impactTolerance;
|
||||
[Serialize(0.0f, IsPropertySaveable.No)]
|
||||
public float ImpactTolerance
|
||||
{
|
||||
get { return impactTolerance; }
|
||||
set { impactTolerance = Math.Max(value, 0.0f); }
|
||||
}
|
||||
|
||||
[Serialize(0.0f, IsPropertySaveable.No, description: "The amount of damage the item takes from impacts. Acts as a multiplier on the strength of the impact. Note that ImpactTolerance must be set for impacts to register.")]
|
||||
public float ImpactDamage { get; set; }
|
||||
|
||||
[Serialize(1.0f, IsPropertySaveable.No, description: "Probability for impacts to register. Defaults to 1. Note that ImpactTolerance must also be set for impacts to register.")]
|
||||
public float ImpactDamageProbability { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No, "If true, submarine impacts will trigger OnImpact effects. Only applies to items with a null or non-dynamic physics body - items with dynamic bodies always react to impacts.")]
|
||||
public bool ReceiveSubmarineImpacts { get; set; }
|
||||
|
||||
|
||||
@@ -404,7 +404,7 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
DamageCharacters(worldPosition, Attack, force, damageSource, attacker);
|
||||
DamageCharacters(worldPosition, Attack, force, damageSource, attacker, displayRange);
|
||||
|
||||
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
@@ -465,12 +465,12 @@ namespace Barotrauma
|
||||
|
||||
partial void ExplodeProjSpecific(Vector2 worldPosition, Hull hull);
|
||||
|
||||
private void DamageCharacters(Vector2 worldPosition, Attack attack, float force, Entity damageSource, Character attacker)
|
||||
private void DamageCharacters(Vector2 worldPosition, Attack attack, float force, Entity damageSource, Character attacker, float range)
|
||||
{
|
||||
if (attack.Range <= 0.0f) { return; }
|
||||
if (range <= 0.0f) { return; }
|
||||
|
||||
//long range for the broad distance check, because large characters may still be in range even if their collider isn't
|
||||
float broadRange = Math.Max(attack.Range * 10.0f, 10000.0f);
|
||||
float broadRange = Math.Max(range * 10.0f, 10000.0f);
|
||||
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
@@ -518,7 +518,7 @@ namespace Barotrauma
|
||||
float limbRadius = limb.body.GetMaxExtent();
|
||||
dist = Math.Max(0.0f, dist - ConvertUnits.ToDisplayUnits(limbRadius));
|
||||
|
||||
if (dist > attack.Range) { continue; }
|
||||
if (dist > range) { continue; }
|
||||
|
||||
float distFactor =
|
||||
DistanceFalloff ?
|
||||
|
||||
@@ -371,7 +371,7 @@ namespace Barotrauma
|
||||
if (!IsInDamageRange(c, DamageRange)) { continue; }
|
||||
|
||||
//GetApproximateDistance returns float.MaxValue if there's no path through open gaps between the hulls (e.g. if there's a door/wall in between)
|
||||
if (hull.GetApproximateDistance(Position, c.Position, c.CurrentHull, 10000.0f) > size.X + DamageRange + FlameHeight)
|
||||
if (hull.GetApproximateDistance(Position, c.Position, c.CurrentHull, maxDistance: 10000.0f, minimumGapOpenness: Structure.LargeGapOpenness) > size.X + DamageRange + FlameHeight)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1133,54 +1133,72 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Approximate distance from this hull to the target hull, moving through open gaps without passing through walls.
|
||||
/// Uses a greedy algo and may not use the most optimal path. Returns float.MaxValue if no path is found.
|
||||
/// Used in <see cref="GetApproximateDistance"/>
|
||||
/// </summary>
|
||||
public float GetApproximateDistance(Vector2 startPos, Vector2 endPos, Hull targetHull, float maxDistance, float distanceMultiplierPerClosedDoor = 0)
|
||||
private static readonly Dictionary<Hull, float> cachedDistances = [];
|
||||
/// <summary>
|
||||
/// Used in <see cref="GetApproximateDistance"/>
|
||||
/// </summary>
|
||||
private static readonly PriorityQueue<(Hull hull, Vector2 pos), float> priorityQueue = new PriorityQueue<(Hull hull, Vector2 pos), float>();
|
||||
|
||||
/// <summary>
|
||||
/// Approximate distance from this hull to the target hull, moving through open gaps without passing through walls.
|
||||
/// Uses a Dijkstra's algorithm to find the shortest path.
|
||||
/// </summary>
|
||||
/// <param name="minimumGapOpenness">The gap's <see cref="Gap.Open">openness</see> must be larger than or equal to this to be considered valid for the path.</param>
|
||||
public float GetApproximateDistance(Vector2 startPos, Vector2 endPos, Hull targetHull, float maxDistance, float distanceMultiplierPerClosedDoor = 0, float minimumGapOpenness = 0.5f)
|
||||
{
|
||||
return GetApproximateHullDistance(startPos, endPos, new HashSet<Hull>(), targetHull, 0.0f, maxDistance, distanceMultiplierPerClosedDoor);
|
||||
}
|
||||
cachedDistances.Clear();
|
||||
priorityQueue.Clear();
|
||||
|
||||
private float GetApproximateHullDistance(Vector2 startPos, Vector2 endPos, HashSet<Hull> connectedHulls, Hull target, float distance, float maxDistance, float distanceMultiplierFromDoors = 0)
|
||||
{
|
||||
if (distance >= maxDistance) { return float.MaxValue; }
|
||||
if (this == target)
|
||||
cachedDistances[this] = 0f;
|
||||
priorityQueue.Enqueue((this, startPos), 0f);
|
||||
|
||||
while (priorityQueue.TryDequeue(out var current, out float currentDist))
|
||||
{
|
||||
return distance + Vector2.Distance(startPos, endPos);
|
||||
}
|
||||
Hull currentHull = current.hull;
|
||||
Vector2 currentPos = current.pos;
|
||||
|
||||
connectedHulls.Add(this);
|
||||
if (currentDist > maxDistance) { return float.MaxValue; }
|
||||
|
||||
foreach (Gap g in ConnectedGaps)
|
||||
{
|
||||
float distanceMultiplier = 1;
|
||||
if (g.ConnectedDoor != null && !g.ConnectedDoor.IsBroken)
|
||||
// If we've reached the target, add the final segment from hull to endPos
|
||||
if (currentHull == targetHull)
|
||||
{
|
||||
//gap blocked if the door is closed, and we haven't made any predictions of it opening client-side
|
||||
if ((g.ConnectedDoor.IsClosed && !g.ConnectedDoor.PredictedState.HasValue) ||
|
||||
//OR we've predicted that the door is closed client-side
|
||||
(g.ConnectedDoor.PredictedState.HasValue && !g.ConnectedDoor.PredictedState.Value))
|
||||
return currentDist + Vector2.Distance(currentPos, endPos);
|
||||
}
|
||||
|
||||
foreach (Gap g in ConnectedGaps)
|
||||
{
|
||||
float distanceMultiplier = 1;
|
||||
if (g.ConnectedDoor != null && !g.ConnectedDoor.IsBroken)
|
||||
{
|
||||
if (g.ConnectedDoor.OpenState < 0.1f)
|
||||
//gap blocked if the door is closed, and we haven't made any predictions of it opening client-side
|
||||
if ((g.ConnectedDoor.IsClosed && !g.ConnectedDoor.PredictedState.HasValue) ||
|
||||
//OR we've predicted that the door is closed client-side
|
||||
(g.ConnectedDoor.PredictedState.HasValue && !g.ConnectedDoor.PredictedState.Value))
|
||||
{
|
||||
if (distanceMultiplierFromDoors <= 0) { continue; }
|
||||
distanceMultiplier *= distanceMultiplierFromDoors;
|
||||
if (g.ConnectedDoor.OpenState < 0.1f)
|
||||
{
|
||||
if (distanceMultiplierPerClosedDoor <= 0) { continue; }
|
||||
distanceMultiplier *= distanceMultiplierPerClosedDoor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (g.Open <= 0.0f)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 2 && i < g.linkedTo.Count; i++)
|
||||
{
|
||||
if (g.linkedTo[i] is Hull hull && !connectedHulls.Contains(hull))
|
||||
else if (g.Open < minimumGapOpenness)
|
||||
{
|
||||
float dist = hull.GetApproximateHullDistance(g.Position, endPos, connectedHulls, target, distance + Vector2.Distance(startPos, g.Position) * distanceMultiplier, maxDistance);
|
||||
if (dist < float.MaxValue)
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 2 && i < g.linkedTo.Count; i++)
|
||||
{
|
||||
if (g.linkedTo[i] is Hull nextHull && nextHull != currentHull)
|
||||
{
|
||||
return dist;
|
||||
float newDist = currentDist + Vector2.Distance(currentPos, g.Position) * distanceMultiplier;
|
||||
if (!cachedDistances.TryGetValue(nextHull, out float oldDist) || newDist < oldDist)
|
||||
{
|
||||
cachedDistances[nextHull] = newDist;
|
||||
priorityQueue.Enqueue((nextHull, g.Position), newDist);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1274,7 +1292,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recursively find all the hulls linked to the specified hull.
|
||||
/// Recursively find all the hulls linked to the specified hull, including the hull itself.
|
||||
/// </summary>
|
||||
public void GetLinkedHulls(List<Hull> linkedHulls, bool includeHiddenHulls = false)
|
||||
{
|
||||
|
||||
@@ -7,7 +7,7 @@ using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class LevelGenerationParams : PrefabWithUintIdentifier, ISerializableEntity
|
||||
internal partial class LevelGenerationParams : PrefabWithUintIdentifier, ISerializableEntity
|
||||
{
|
||||
public readonly static PrefabCollection<LevelGenerationParams> LevelParams = new PrefabCollection<LevelGenerationParams>();
|
||||
|
||||
|
||||
@@ -378,7 +378,14 @@ namespace Barotrauma
|
||||
if (characters.Any())
|
||||
{
|
||||
price *= 1f + characters.Max(static c => c.GetStatValue(StatTypes.StoreSellMultiplier, includeSaved: false));
|
||||
price *= 1f + characters.Max(c => item.Tags.Sum(tag => c.Info.GetSavedStatValueWithAll(StatTypes.StoreSellMultiplier, tag)));
|
||||
price *= 1f + characters.Max(c => GetMultiplierForItem(c, item));
|
||||
|
||||
float GetMultiplierForItem(Character character, ItemPrefab item)
|
||||
{
|
||||
return
|
||||
item.Tags.Sum(tag => character.Info.GetSavedStatValue(StatTypes.StoreSellMultiplier, tag)) +
|
||||
character.Info.GetSavedStatValue(StatTypes.StoreSellMultiplier, Tags.StatIdentifierTargetAll);
|
||||
}
|
||||
}
|
||||
|
||||
// Price should never go below 1 mk
|
||||
@@ -588,7 +595,7 @@ namespace Barotrauma
|
||||
public Location(Vector2 mapPosition, int? zone, Identifier? biomeId, Random rand, bool requireOutpost = false, LocationType forceLocationType = null, IEnumerable<Location> existingLocations = null)
|
||||
{
|
||||
Type = OriginalType = forceLocationType ?? LocationType.Random(rand, zone, biomeId, requireOutpost);
|
||||
CreateRandomName(Type, rand, existingLocations);
|
||||
AssignRandomName(Type, rand, existingLocations);
|
||||
MapPosition = mapPosition;
|
||||
PortraitId = ToolBox.StringToInt(nameIdentifier.Value);
|
||||
Connections = new List<LocationConnection>();
|
||||
@@ -1210,7 +1217,7 @@ namespace Barotrauma
|
||||
HireManager.AvailableCharacters = hireableCharacters.ToList();
|
||||
}
|
||||
|
||||
private void CreateRandomName(LocationType type, Random rand, IEnumerable<Location> existingLocations)
|
||||
public void AssignRandomName(LocationType type, Random rand, IEnumerable<Location> existingLocations)
|
||||
{
|
||||
if (!type.ForceLocationName.IsEmpty)
|
||||
{
|
||||
|
||||
@@ -735,7 +735,7 @@ namespace Barotrauma
|
||||
Location startLocation = Locations.MinBy(l => l.MapPosition.X);
|
||||
if (LocationType.Prefabs.TryGet("outpost", out LocationType startLocationType))
|
||||
{
|
||||
startLocation.ChangeType(campaign, startLocationType, createStores: false);
|
||||
mapLocationTypeGenerator.ChangeLocationTypeAndName(campaign, startLocation, startLocationType);
|
||||
mapLocationTypeGenerator.AddToFilled(startLocation);
|
||||
}
|
||||
|
||||
|
||||
@@ -155,13 +155,17 @@ namespace Barotrauma
|
||||
return filledLocations.Contains(location);
|
||||
}
|
||||
|
||||
public static void ChangeLocationTypeAndName(CampaignMode campaign, Location location, LocationType suitableLocationType)
|
||||
public void ChangeLocationTypeAndName(CampaignMode campaign, Location location, LocationType suitableLocationType)
|
||||
{
|
||||
location.ChangeType(campaign, suitableLocationType, createStores: false, unlockInitialMissions: false);
|
||||
if (!suitableLocationType.ForceLocationName.IsEmpty)
|
||||
{
|
||||
location.ForceName(suitableLocationType.ForceLocationName);
|
||||
}
|
||||
else
|
||||
{
|
||||
location.AssignRandomName(location.Type, Rand.GetRNG(Rand.RandSync.ServerAndClient), existingLocations: map.Locations);
|
||||
}
|
||||
}
|
||||
|
||||
public void AssignForcedBiomeGateTypes(IEnumerable<Location> gateLocations)
|
||||
|
||||
@@ -792,6 +792,7 @@ namespace Barotrauma
|
||||
ItemPrefab itemPrefab = ItemPrefab.Find(name, identifier);
|
||||
if (itemPrefab != null)
|
||||
{
|
||||
DebugConsole.AddWarning($"Could not find a structure with the identifier {identifier}, but there's a matching item with the identifier. Converting to an item.");
|
||||
t = typeof(Item);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,16 @@ namespace Barotrauma
|
||||
const float LeakThreshold = 0.1f;
|
||||
const float BigGapThreshold = 0.7f;
|
||||
|
||||
/// <summary>
|
||||
/// How <see cref="Gap.open">open</see> the gap on a partially broken wall section is at most (when it's below <see cref="BigGapThreshold"/>, after which it lerps up to <see cref="LargeGapOpenness"/>).
|
||||
/// </summary>
|
||||
public const float SmallGapOpenness = 0.35f;
|
||||
|
||||
/// <summary>
|
||||
/// How <see cref="Gap.open">open</see> the gap on a fully broken wall section is.
|
||||
/// </summary>
|
||||
public const float LargeGapOpenness = 0.75f;
|
||||
|
||||
public override ContentPackage ContentPackage => Prefab?.ContentPackage;
|
||||
|
||||
#if CLIENT
|
||||
@@ -64,6 +74,9 @@ namespace Barotrauma
|
||||
|
||||
private static Explosion explosionOnBroken;
|
||||
|
||||
public delegate void OnHealthChangedHandler(Character attacker, float damage);
|
||||
public OnHealthChangedHandler OnHealthChanged;
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes), ConditionallyEditable(ConditionallyEditable.ConditionType.HasBody)]
|
||||
public bool Indestructible
|
||||
{
|
||||
@@ -1332,11 +1345,11 @@ namespace Barotrauma
|
||||
float gapOpen = 0;
|
||||
if (damageRatio > BigGapThreshold)
|
||||
{
|
||||
gapOpen = MathHelper.Lerp(0.35f, 0.75f, MathUtils.InverseLerp(BigGapThreshold, 1.0f, damageRatio));
|
||||
gapOpen = MathHelper.Lerp(SmallGapOpenness, LargeGapOpenness, MathUtils.InverseLerp(BigGapThreshold, 1.0f, damageRatio));
|
||||
}
|
||||
else if (damageRatio > LeakThreshold)
|
||||
{
|
||||
gapOpen = MathHelper.Lerp(0f, 0.35f, MathUtils.InverseLerp(LeakThreshold, BigGapThreshold, damageRatio));
|
||||
gapOpen = MathHelper.Lerp(0f, SmallGapOpenness, MathUtils.InverseLerp(LeakThreshold, BigGapThreshold, damageRatio));
|
||||
}
|
||||
gap.Open = gapOpen;
|
||||
|
||||
@@ -1355,16 +1368,20 @@ namespace Barotrauma
|
||||
Sections[sectionIndex].damage = MathHelper.Clamp(damage, 0.0f, MaxHealth);
|
||||
HasDamage = Sections.Any(s => s.damage > 0.0f);
|
||||
|
||||
if (attacker != null && damageDiff != 0.0f)
|
||||
if (damageDiff != 0.0f)
|
||||
{
|
||||
HumanAIController.StructureDamaged(this, damageDiff, attacker);
|
||||
OnHealthChangedProjSpecific(attacker, damageDiff);
|
||||
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
|
||||
OnHealthChanged?.Invoke(attacker, damageDiff);
|
||||
if (attacker != null)
|
||||
{
|
||||
if (damageDiff < 0.0f)
|
||||
HumanAIController.StructureDamaged(this, damageDiff, attacker);
|
||||
OnHealthChangedProjSpecific(attacker, damageDiff);
|
||||
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
attacker.Info?.ApplySkillGain(Barotrauma.Tags.MechanicalSkill,
|
||||
-damageDiff * SkillSettings.Current.SkillIncreasePerRepairedStructureDamage);
|
||||
if (damageDiff < 0.0f)
|
||||
{
|
||||
attacker.Info?.ApplySkillGain(Barotrauma.Tags.MechanicalSkill,
|
||||
-damageDiff * SkillSettings.Current.SkillIncreasePerRepairedStructureDamage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1775,9 +1792,9 @@ namespace Barotrauma
|
||||
//3. not found, attempt to find a prefab that uses the previous name as an identifier
|
||||
if (prefab == null) { prefab = MapEntityPrefab.Find(null, name) as StructurePrefab; }
|
||||
}
|
||||
else
|
||||
else if (StructurePrefab.Prefabs.TryGet(identifier, out StructurePrefab structurePrefab))
|
||||
{
|
||||
prefab = MapEntityPrefab.Find(null, identifier) as StructurePrefab;
|
||||
prefab = structurePrefab;
|
||||
}
|
||||
return prefab;
|
||||
}
|
||||
|
||||
@@ -1400,6 +1400,8 @@ namespace Barotrauma
|
||||
if (item.Submarine != this) { continue; }
|
||||
var pump = item.GetComponent<Pump>();
|
||||
if (pump == null || item.CurrentHull == null) { continue; }
|
||||
//if the pump has no connection panel, it must be something else than a ballast pump (e.g. a weak point which uses a pump component to pump water in)
|
||||
if (item.GetComponent<ConnectionPanel>() == null) { continue; }
|
||||
if (!item.HasTag(Tags.Ballast) && !item.CurrentHull.RoomName.Contains("ballast", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
pump.FlowPercentage = 0.0f;
|
||||
ballastHulls.Add(item.CurrentHull);
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace Barotrauma
|
||||
const float VerticalDrag = 0.05f;
|
||||
const float MaxDrag = 0.1f;
|
||||
|
||||
private const float ImpactDamageMultiplier = 10.0f;
|
||||
private const float ImpactDamageMultiplier = 3.0f;
|
||||
|
||||
//limbs with a mass smaller than this won't cause an impact when they hit the sub
|
||||
private const float MinImpactLimbMass = 10.0f;
|
||||
@@ -886,6 +886,11 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
float wallImpact = Vector2.Dot(impact.Velocity, -impact.Normal);
|
||||
if (wallImpact < MinCollisionImpact) { return; }
|
||||
|
||||
//magic number to make wall impacts on par with monster impacts (the latter are affected by the mass of the monster)
|
||||
const float WallImpactMultiplier = 3.0f;
|
||||
wallImpact *= WallImpactMultiplier;
|
||||
|
||||
ApplyImpact(wallImpact, -impact.Normal, impact.ImpactPos);
|
||||
foreach (Submarine dockedSub in submarine.DockedTo)
|
||||
@@ -1070,17 +1075,20 @@ namespace Barotrauma
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.Submarine != submarine) { continue; }
|
||||
if (Timing.TotalTimeUnpaused < item.LastSubmarineImpactTime + Item.SubmarineImpactCooldown) { continue; }
|
||||
|
||||
if (item.body is not { BodyType: BodyType.Dynamic })
|
||||
{
|
||||
if (!item.Prefab.ReceiveSubmarineImpacts) { continue; }
|
||||
item.ReceiveImpact(impact, recursive: false);
|
||||
item.LastSubmarineImpactTime = Timing.TotalTimeUnpaused;
|
||||
}
|
||||
|
||||
if (!item.body.Enabled || item.CurrentHull == null || item.body.Mass > impulseMagnitude) { continue; }
|
||||
|
||||
item.body.ApplyLinearImpulse(impulse, 10.0f);
|
||||
item.PositionUpdateInterval = 0.0f;
|
||||
item.LastSubmarineImpactTime = Timing.TotalTimeUnpaused;
|
||||
}
|
||||
|
||||
float dmg = applyDamage ? impact * ImpactDamageMultiplier : 0.0f;
|
||||
|
||||
@@ -113,7 +113,7 @@ namespace Barotrauma.Networking
|
||||
Task<int> readTask = readStream?.ReadAsync(readTempBytes, 0, readTempBytes.Length, readCancellationToken.Token);
|
||||
if (readTask is null) { return Option<int>.None(); }
|
||||
|
||||
int timeOutMilliseconds = 100;
|
||||
int timeOutMilliseconds = 150;
|
||||
for (int i = 0; i < 150; i++)
|
||||
{
|
||||
if (status is StatusEnum.ShutDown)
|
||||
|
||||
@@ -94,6 +94,10 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Inventory.Owner is Character { DisabledByEvent: true })
|
||||
{
|
||||
spawnedItem.IsActive = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -363,7 +367,10 @@ namespace Barotrauma
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
Client client = GameMain.Server.ConnectedClients.Find(c => c.Character == character);
|
||||
if (client != null) GameMain.Server.SetClientCharacter(client, null);
|
||||
if (client != null)
|
||||
{
|
||||
GameMain.Server.SetClientCharacter(client, null);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -39,26 +39,6 @@ namespace Barotrauma.Networking
|
||||
|
||||
public const int MaxEventPacketsPerUpdate = 4;
|
||||
|
||||
/// <summary>
|
||||
/// How long the server waits for the clients to get in sync after the round has started before kicking them
|
||||
/// </summary>
|
||||
public const float RoundStartSyncDuration = 60.0f;
|
||||
|
||||
/// <summary>
|
||||
/// How long the server keeps events that everyone currently synced has received
|
||||
/// </summary>
|
||||
public const float EventRemovalTime = 15.0f;
|
||||
|
||||
/// <summary>
|
||||
/// If a client hasn't received an event that has been succesfully sent to someone within this time, they get kicked
|
||||
/// </summary>
|
||||
public const float OldReceivedEventKickTime = 10.0f;
|
||||
|
||||
/// <summary>
|
||||
/// If a client hasn't received an event after this time, they get kicked
|
||||
/// </summary>
|
||||
public const float OldEventKickTime = 30.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Interpolates the positional error of a physics body towards zero.
|
||||
/// </summary>
|
||||
|
||||
+2
-2
@@ -17,8 +17,8 @@ namespace Barotrauma.Networking
|
||||
|
||||
abstract class NetworkConnection
|
||||
{
|
||||
public const double TimeoutThreshold = 60.0; //full minute for timeout because loading screens can take quite a while
|
||||
public const double TimeoutThresholdInGame = 10.0;
|
||||
public static double TimeoutThresholdNotInGame => GameMain.NetworkMember?.ServerSettings?.TimeoutThresholdNotInGame ?? 60.0; //full minute for timeout because loading screens can take quite a while
|
||||
public static double TimeoutThresholdInGame => GameMain.NetworkMember?.ServerSettings?.TimeoutThresholdInGame ?? 10.0;
|
||||
|
||||
public AccountInfo AccountInfo { get; private set; } = AccountInfo.None;
|
||||
|
||||
|
||||
+1
-1
@@ -25,6 +25,6 @@ abstract class P2PConnection : NetworkConnection<P2PEndpoint>
|
||||
|
||||
public void Heartbeat()
|
||||
{
|
||||
Timeout = TimeoutThreshold;
|
||||
Timeout = TimeoutThresholdNotInGame;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1041,12 +1041,77 @@ namespace Barotrauma.Networking
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(10.0f, IsPropertySaveable.Yes)]
|
||||
//note: the following properties are autoinitialized because it's important for them to have sensible (non-zero) default values,
|
||||
//and non-admin clients don't know the values set by the server
|
||||
|
||||
[Serialize(30.0f, IsPropertySaveable.Yes)]
|
||||
public float MinimumMidRoundSyncTimeout
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
} = 30.0f;
|
||||
|
||||
/// <summary>
|
||||
/// How long the server waits for the clients to get in sync after the round has started before kicking them
|
||||
/// </summary>
|
||||
[Serialize(120.0f, IsPropertySaveable.Yes)]
|
||||
public float RoundStartSyncDuration
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = 120.0f;
|
||||
|
||||
/// <summary>
|
||||
/// How long the server keeps events that everyone currently synced has received
|
||||
/// </summary>
|
||||
[Serialize(15.0f, IsPropertySaveable.Yes)]
|
||||
public float EventRemovalTime
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = 15.0f;
|
||||
|
||||
/// <summary>
|
||||
/// If a client hasn't received an event that has been succesfully sent to someone within this time, they get kicked
|
||||
/// </summary>
|
||||
[Serialize(20.0f, IsPropertySaveable.Yes)]
|
||||
public float OldReceivedEventKickTime
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = 20.0f;
|
||||
|
||||
/// <summary>
|
||||
/// If a client hasn't received an event after this time, they get kicked
|
||||
/// </summary>
|
||||
[Serialize(40.0f, IsPropertySaveable.Yes)]
|
||||
public float OldEventKickTime
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = 40.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Amount of seconds before connections between the server and the clients time out (i.e. if the client fails to receive messages from the server for this amount of time or vice versa, they get disconnected).
|
||||
/// Used when a round is not currently running, i.e. in the lobby or in loading screens.
|
||||
/// </summary>
|
||||
[Serialize(60.0f, IsPropertySaveable.Yes)]
|
||||
public float TimeoutThresholdNotInGame
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = 60.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Amount of seconds before connections between the server and the clients time out (i.e. if the client fails to receive messages from the server for this amount of time or vice versa, they get disconnected).
|
||||
/// Used when a round is running.
|
||||
/// </summary>
|
||||
[Serialize(10.0f, IsPropertySaveable.Yes)]
|
||||
public float TimeoutThresholdInGame
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = 10.0f;
|
||||
|
||||
private bool karmaEnabled;
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
|
||||
@@ -208,19 +208,30 @@ namespace Barotrauma
|
||||
isEnabled = value;
|
||||
try
|
||||
{
|
||||
if (isEnabled) FarseerBody.Enabled = isPhysEnabled; else FarseerBody.Enabled = false;
|
||||
FarseerBody.Enabled = isEnabled && isPhysEnabled;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Exception in PhysicsBody.Enabled = " + value + " (" + isPhysEnabled + ")", e);
|
||||
if (UserData != null) DebugConsole.NewMessage("PhysicsBody UserData: " + UserData.GetType().ToString(), Color.Red);
|
||||
if (GameMain.World.ContactManager == null) DebugConsole.NewMessage("ContactManager is null!", Color.Red);
|
||||
else if (GameMain.World.ContactManager.BroadPhase == null) DebugConsole.NewMessage("Broadphase is null!", Color.Red);
|
||||
if (FarseerBody.FixtureList == null) DebugConsole.NewMessage("FixtureList is null!", Color.Red);
|
||||
|
||||
if (UserData != null)
|
||||
{
|
||||
DebugConsole.NewMessage("PhysicsBody UserData: " + UserData.GetType(), Color.Red);
|
||||
}
|
||||
if (GameMain.World.ContactManager == null)
|
||||
{
|
||||
DebugConsole.NewMessage("ContactManager is null!", Color.Red);
|
||||
}
|
||||
else if (GameMain.World.ContactManager.BroadPhase == null)
|
||||
{
|
||||
DebugConsole.NewMessage("Broadphase is null!", Color.Red);
|
||||
}
|
||||
if (FarseerBody.FixtureList == null)
|
||||
{
|
||||
DebugConsole.NewMessage("FixtureList is null!", Color.Red);
|
||||
}
|
||||
if (UserData is Entity entity)
|
||||
{
|
||||
DebugConsole.NewMessage("Entity \"" + entity.ToString() + "\" removed!", Color.Red);
|
||||
DebugConsole.NewMessage("Entity \"" + entity + "\" removed!", Color.Red);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-1
@@ -37,7 +37,8 @@ sealed class ConditionallyEditable : Editable
|
||||
HasIntegratedButtons,
|
||||
IsToggleableController,
|
||||
HasConnectionPanel,
|
||||
DeteriorateUnderStress
|
||||
DeteriorateUnderStress,
|
||||
ReceivesSubmarineImpacts
|
||||
}
|
||||
|
||||
public bool IsEditable(ISerializableEntity entity)
|
||||
@@ -72,6 +73,8 @@ sealed class ConditionallyEditable : Editable
|
||||
=> GetComponent<ConnectionPanel>(entity) != null,
|
||||
ConditionType.DeteriorateUnderStress
|
||||
=> entity is Item repairableItem && repairableItem.Components.Any(c => c is IDeteriorateUnderStress),
|
||||
ConditionType.ReceivesSubmarineImpacts
|
||||
=> entity is Item { Prefab.ReceiveSubmarineImpacts: true },
|
||||
_
|
||||
=> false
|
||||
};
|
||||
|
||||
@@ -779,7 +779,10 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
private readonly HashSet<(Identifier affliction, float strength)> requiredAfflictions;
|
||||
|
||||
public float AfflictionMultiplier = 1.0f;
|
||||
/// <summary>
|
||||
/// Multiplier used on afflictions caused by the status effect, except ones that <see cref="AfflictionPrefab.AffectedByAttackMultipliers">have been configured to not be affected by attack multipliers.
|
||||
/// </summary>
|
||||
public float AttackMultiplier = 1.0f;
|
||||
|
||||
public List<Affliction> Afflictions
|
||||
{
|
||||
@@ -801,6 +804,12 @@ namespace Barotrauma
|
||||
|
||||
public readonly List<(Identifier AfflictionIdentifier, float ReduceAmount)> ReduceAffliction = new List<(Identifier affliction, float amount)>();
|
||||
|
||||
/// <summary>
|
||||
/// Normally using a StatusEffect to heal someone's afflictions gives an amount of medical skill relative to the amount of health the target regained.
|
||||
/// This can be used to disable that behavior, in case there are items that "heal" someone without being considered medical items or something that should give medical skill.
|
||||
/// </summary>
|
||||
public readonly bool CanGiveMedicalSkill;
|
||||
|
||||
private readonly List<Identifier> talentTriggers;
|
||||
private readonly List<int> giveExperiences;
|
||||
private readonly List<GiveSkill> giveSkills;
|
||||
@@ -904,6 +913,8 @@ namespace Barotrauma
|
||||
if (targetLimbs.Count > 0) { this.targetLimbs = targetLimbs.ToArray(); }
|
||||
}
|
||||
|
||||
CanGiveMedicalSkill = element.GetAttributeBool(nameof(CanGiveMedicalSkill), true);
|
||||
|
||||
SeverLimbsProbability = MathHelper.Clamp(element.GetAttributeFloat(0.0f, "severlimbs", "severlimbsprobability"), 0.0f, 1.0f);
|
||||
randomCondition = element.GetAttributeVector2("randomcondition", Vector2.Zero);
|
||||
|
||||
@@ -1985,7 +1996,7 @@ namespace Barotrauma
|
||||
{
|
||||
float healthChange = targetCharacter.Vitality - prevVitality;
|
||||
targetCharacter.AIController?.OnHealed(healer: user, healthChange);
|
||||
if (user != null)
|
||||
if (user != null && CanGiveMedicalSkill)
|
||||
{
|
||||
targetCharacter.TryAdjustHealerSkill(user, healthChange);
|
||||
#if SERVER
|
||||
@@ -2656,16 +2667,6 @@ namespace Barotrauma
|
||||
{
|
||||
Entity.Spawner.AddItemToSpawnQueue(chosenItemSpawnInfo.ItemPrefab, inventory, spawnIfInventoryFull: chosenItemSpawnInfo.SpawnIfInventoryFull, onSpawned: item =>
|
||||
{
|
||||
if (chosenItemSpawnInfo.Equip && entity is Character character && character.Inventory != null)
|
||||
{
|
||||
//if the item is both pickable and wearable, try to wear it instead of picking it up
|
||||
List<InvSlotType> allowedSlots =
|
||||
item.GetComponents<Pickable>().Count() > 1 ?
|
||||
new List<InvSlotType>(item.GetComponent<Wearable>()?.AllowedSlots ?? item.GetComponent<Pickable>().AllowedSlots) :
|
||||
new List<InvSlotType>(item.AllowedSlots);
|
||||
allowedSlots.Remove(InvSlotType.Any);
|
||||
character.Inventory.TryPutItem(item, null, allowedSlots);
|
||||
}
|
||||
OnItemSpawned(item, chosenItemSpawnInfo);
|
||||
});
|
||||
}
|
||||
@@ -2734,6 +2735,17 @@ namespace Barotrauma
|
||||
}
|
||||
void OnItemSpawned(Item newItem, ItemSpawnInfo itemSpawnInfo)
|
||||
{
|
||||
if (itemSpawnInfo.Equip && newItem.ParentInventory is CharacterInventory characterInventory && characterInventory.Owner is Character character)
|
||||
{
|
||||
//if the item is both pickable and wearable, try to wear it instead of picking it up
|
||||
List<InvSlotType> allowedSlots =
|
||||
newItem.GetComponents<Pickable>().Count() > 1 ?
|
||||
new List<InvSlotType>(newItem.GetComponent<Wearable>()?.AllowedSlots ?? newItem.GetComponent<Pickable>().AllowedSlots) :
|
||||
new List<InvSlotType>(newItem.AllowedSlots);
|
||||
allowedSlots.Remove(InvSlotType.Any);
|
||||
character.Inventory.TryPutItem(newItem, null, allowedSlots);
|
||||
}
|
||||
|
||||
newItem.Condition = newItem.MaxCondition * itemSpawnInfo.Condition;
|
||||
if (itemSpawnInfo.InheritEventTags)
|
||||
{
|
||||
@@ -2889,7 +2901,10 @@ namespace Barotrauma
|
||||
targetCharacter.AIController?.OnHealed(healer: element.User, healthChange);
|
||||
if (element.User != null)
|
||||
{
|
||||
targetCharacter.TryAdjustHealerSkill(element.User, healthChange);
|
||||
if (element.Parent.CanGiveMedicalSkill)
|
||||
{
|
||||
targetCharacter.TryAdjustHealerSkill(element.User, healthChange);
|
||||
}
|
||||
#if SERVER
|
||||
GameMain.Server.KarmaManager.OnCharacterHealthChanged(targetCharacter, element.User, -healthChange, 0.0f);
|
||||
#endif
|
||||
@@ -2933,12 +2948,16 @@ namespace Barotrauma
|
||||
afflictionMultiplier *= 1 + user.GetStatValue(StatTypes.PoisonMultiplier);
|
||||
}
|
||||
}
|
||||
return afflictionMultiplier * AfflictionMultiplier;
|
||||
return afflictionMultiplier;
|
||||
}
|
||||
|
||||
private Affliction GetMultipliedAffliction(Affliction affliction, Entity entity, Character targetCharacter, float deltaTime, bool multiplyByMaxVitality)
|
||||
{
|
||||
float afflictionMultiplier = GetAfflictionMultiplier(entity, targetCharacter, deltaTime);
|
||||
if (affliction.AffectedByAttackMultipliers)
|
||||
{
|
||||
afflictionMultiplier *= AttackMultiplier;
|
||||
}
|
||||
if (multiplyByMaxVitality)
|
||||
{
|
||||
afflictionMultiplier *= targetCharacter.MaxVitality / 100f;
|
||||
@@ -2970,6 +2989,10 @@ namespace Barotrauma
|
||||
return affliction;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register results of the afflictions that the status effect applied on the target (e.g. buffs), giving the user medical skill and indicating the treatment in the UI.
|
||||
/// The effects of reducing afflictions are handled when going through the <see cref="ReduceAffliction">ReduceAffliction list</see>.
|
||||
/// </summary>
|
||||
private void RegisterTreatmentResults(Character user, Item item, Limb limb, Affliction affliction, AttackResult result)
|
||||
{
|
||||
if (item == null) { return; }
|
||||
@@ -2986,12 +3009,13 @@ namespace Barotrauma
|
||||
if (type == ActionType.OnUse || type == ActionType.OnSuccess)
|
||||
{
|
||||
limbAffliction.AppliedAsSuccessfulTreatmentTime = Timing.TotalTime;
|
||||
limb.character.TryAdjustHealerSkill(user, affliction: resultAffliction);
|
||||
if (CanGiveMedicalSkill) { limb.character.TryAdjustHealerSkill(user, affliction: resultAffliction); }
|
||||
|
||||
}
|
||||
else if (type == ActionType.OnFailure)
|
||||
{
|
||||
limbAffliction.AppliedAsFailedTreatmentTime = Timing.TotalTime;
|
||||
limb.character.TryAdjustHealerSkill(user, affliction: resultAffliction);
|
||||
if (CanGiveMedicalSkill) { limb.character.TryAdjustHealerSkill(user, affliction: resultAffliction); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ public static class Tags
|
||||
public static readonly Identifier ChairItem = "chair".ToIdentifier();
|
||||
public static readonly Identifier ArtifactHolder = "artifactholder".ToIdentifier();
|
||||
public static readonly Identifier Thalamus = "thalamus".ToIdentifier();
|
||||
public static readonly Identifier GeneticResearchStation = "geneticresearchstation".ToIdentifier();
|
||||
|
||||
public static readonly Identifier IgnoreThis = "ignorethis".ToIdentifier();
|
||||
public static readonly Identifier UnignoreThis = "unignorethis".ToIdentifier();
|
||||
|
||||
@@ -66,21 +66,28 @@ namespace Barotrauma
|
||||
private static readonly string LegacyMultiplayerSaveFolder = Path.Combine(LegacySaveFolder, "Multiplayer");
|
||||
|
||||
#if OSX
|
||||
//"/*user*/Library/Application Support/Daedalic Entertainment GmbH/" on Mac
|
||||
public static readonly string DefaultSaveFolder = Path.Combine(
|
||||
/// <summary>
|
||||
/// These exist because we used to have a workaround here that set the save folder to
|
||||
/// Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "Library", "Application Support", "Daedalic Entertainment GmbH", "Barotrauma")
|
||||
/// on Mac, because apparently LocalApplicationData returned something different than the expected path. That seems to have changed in .NET8, and now we
|
||||
/// can use the same LocalApplicationData on all platforms. We however still check the old path in case someone has their saves there.
|
||||
/// </summary>
|
||||
public static readonly string LegacyMacSaveFolder = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.Personal),
|
||||
"Library",
|
||||
"Application Support",
|
||||
"Daedalic Entertainment GmbH",
|
||||
"Barotrauma");
|
||||
#else
|
||||
//"C:/Users/*user*/AppData/Local/Daedalic Entertainment GmbH/" on Windows
|
||||
//"/home/*user*/.local/share/Daedalic Entertainment GmbH/" on Linux
|
||||
public static string LegacyMacMultiplayerSaveFolder = Path.Combine(LegacyMacSaveFolder, "Multiplayer");
|
||||
#endif
|
||||
|
||||
//C:/Users/*user*/AppData/Local/Daedalic Entertainment GmbH/ on Windows
|
||||
///home/*user*/.local/share/Daedalic Entertainment GmbH/ on Linux
|
||||
///Users/*user*/Library/Application Support/Daedalic Entertainment GmbH/ on Mac
|
||||
public static readonly string DefaultSaveFolder = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||
"Daedalic Entertainment GmbH",
|
||||
"Barotrauma");
|
||||
#endif
|
||||
|
||||
public static string DefaultMultiplayerSaveFolder = Path.Combine(DefaultSaveFolder, "Multiplayer");
|
||||
|
||||
@@ -410,6 +417,13 @@ namespace Barotrauma
|
||||
files.AddRange(Directory.GetFiles(legacyFolder, "*.save", System.IO.SearchOption.TopDirectoryOnly));
|
||||
}
|
||||
|
||||
#if OSX
|
||||
string legacyMacFolder = saveType == SaveType.Singleplayer ? LegacyMacSaveFolder : LegacyMacMultiplayerSaveFolder;
|
||||
if (Directory.Exists(legacyMacFolder))
|
||||
{
|
||||
files.AddRange(Directory.GetFiles(legacyMacFolder, "*.save", System.IO.SearchOption.TopDirectoryOnly));
|
||||
}
|
||||
#endif
|
||||
files = files.Distinct().ToList();
|
||||
|
||||
List<CampaignMode.SaveInfo> saveInfos = new List<CampaignMode.SaveInfo>();
|
||||
|
||||
Reference in New Issue
Block a user