Merge branch 'master' of https://github.com/Regalis11/Barotrauma.git
This commit is contained in:
@@ -31,6 +31,17 @@ namespace Barotrauma
|
||||
if (_previousAiTarget != null)
|
||||
{
|
||||
_lastAiTarget = _previousAiTarget;
|
||||
if (_selectedAiTarget != null)
|
||||
{
|
||||
if (_selectedAiTarget.Entity is Item i && _previousAiTarget.Entity is Character c)
|
||||
{
|
||||
if (i.IsOwnedBy(c)) { return; }
|
||||
}
|
||||
else if (_previousAiTarget.Entity is Item it && _selectedAiTarget.Entity is Character ch)
|
||||
{
|
||||
if (it.IsOwnedBy(ch)) { return; }
|
||||
}
|
||||
}
|
||||
}
|
||||
OnTargetChanged(_previousAiTarget, _selectedAiTarget);
|
||||
}
|
||||
|
||||
@@ -34,14 +34,18 @@ namespace Barotrauma
|
||||
public float SoundRange
|
||||
{
|
||||
get { return soundRange; }
|
||||
set
|
||||
set
|
||||
{
|
||||
if (float.IsNaN(value))
|
||||
{
|
||||
DebugConsole.ThrowError("Attempted to set the SoundRange of an AITarget to NaN.\n" + Environment.StackTrace.CleanupStackTrace());
|
||||
return;
|
||||
}
|
||||
soundRange = MathHelper.Clamp(value, MinSoundRange, MaxSoundRange);
|
||||
soundRange = MathHelper.Clamp(value, MinSoundRange, MaxSoundRange);
|
||||
if (soundRange > 0.0f && !Static && FadeOutTime > 0.0f)
|
||||
{
|
||||
NeedsUpdate = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +59,11 @@ namespace Barotrauma
|
||||
DebugConsole.ThrowError("Attempted to set the SightRange of an AITarget to NaN.\n" + Environment.StackTrace.CleanupStackTrace());
|
||||
return;
|
||||
}
|
||||
sightRange = MathHelper.Clamp(value, MinSightRange, MaxSightRange);
|
||||
sightRange = MathHelper.Clamp(value, MinSightRange, MaxSightRange);
|
||||
if (sightRange > 0 && !Static && FadeOutTime > 0.0f)
|
||||
{
|
||||
NeedsUpdate = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,13 +107,33 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public bool InDetectable
|
||||
{
|
||||
get => inDetectable || (SoundRange <= 0 && SightRange <= 0);
|
||||
set => inDetectable = value;
|
||||
get
|
||||
{
|
||||
return inDetectable || (SoundRange <= 0 && SightRange <= 0);
|
||||
}
|
||||
set
|
||||
{
|
||||
inDetectable = value;
|
||||
if (inDetectable)
|
||||
{
|
||||
NeedsUpdate = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public float MinSoundRange, MinSightRange;
|
||||
public float MaxSoundRange = 100000, MaxSightRange = 100000;
|
||||
|
||||
/// <summary>
|
||||
/// Does the AI target do something that requires Update() to be called (e.g. static targets don't need to be updated)
|
||||
/// </summary>
|
||||
public bool NeedsUpdate
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = true;
|
||||
|
||||
public TargetType Type { get; private set; }
|
||||
|
||||
public enum TargetType
|
||||
@@ -190,14 +218,22 @@ namespace Barotrauma
|
||||
if (!Static && FadeOutTime > 0)
|
||||
{
|
||||
// The aitarget goes silent/invisible if the components don't keep it active
|
||||
if (!StaticSight && SightRange > 0)
|
||||
if (!StaticSight && sightRange > 0)
|
||||
{
|
||||
DecreaseSightRange(deltaTime);
|
||||
}
|
||||
if (!StaticSound && SoundRange > 0)
|
||||
if (!StaticSound && soundRange > 0)
|
||||
{
|
||||
DecreaseSoundRange(deltaTime);
|
||||
}
|
||||
if (sightRange <= 0 && soundRange <= 0)
|
||||
{
|
||||
NeedsUpdate = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
NeedsUpdate = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -275,7 +275,11 @@ namespace Barotrauma
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "chooserandom":
|
||||
LoadSubElement(subElement.Elements().ToArray().GetRandom(random));
|
||||
var subElements = subElement.Elements();
|
||||
if (subElements.Any())
|
||||
{
|
||||
LoadSubElement(subElements.ToArray().GetRandom(random));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
LoadSubElement(subElement);
|
||||
@@ -1055,6 +1059,9 @@ namespace Barotrauma
|
||||
|
||||
private Vector2 attackWorldPos;
|
||||
private Vector2 attackSimPos;
|
||||
private float reachTimer;
|
||||
// How long the monster tries to reach out for the target when it's close to it before ignoring it.
|
||||
private const float reachTimeOut = 10;
|
||||
|
||||
private void UpdateAttack(float deltaTime)
|
||||
{
|
||||
@@ -1427,6 +1434,41 @@ namespace Barotrauma
|
||||
// Check that we can reach the target
|
||||
distance = toTarget.Length();
|
||||
canAttack = distance < AttackLimb.attack.Range;
|
||||
if (canAttack)
|
||||
{
|
||||
reachTimer = 0;
|
||||
}
|
||||
else if (selectedTargetingParams.AttackPattern == AttackPattern.Straight && distance < AttackLimb.attack.Range * 5)
|
||||
{
|
||||
Vector2 targetVelocity = Vector2.Zero;
|
||||
Submarine targetSub = SelectedAiTarget.Entity.Submarine;
|
||||
if (targetSub != null)
|
||||
{
|
||||
targetVelocity = targetSub.Velocity;
|
||||
}
|
||||
else if (targetCharacter != null)
|
||||
{
|
||||
targetVelocity = targetCharacter.AnimController.Collider.LinearVelocity;
|
||||
}
|
||||
else if (SelectedAiTarget.Entity is Item i && i.body != null)
|
||||
{
|
||||
targetVelocity = i.body.LinearVelocity;
|
||||
}
|
||||
float mySpeed = Character.AnimController.Collider.LinearVelocity.LengthSquared();
|
||||
float targetSpeed = targetVelocity.LengthSquared();
|
||||
if (mySpeed < 0.1f || mySpeed > targetSpeed)
|
||||
{
|
||||
reachTimer += deltaTime;
|
||||
if (reachTimer > reachTimeOut)
|
||||
{
|
||||
reachTimer = 0;
|
||||
IgnoreTarget(SelectedAiTarget);
|
||||
State = AIState.Idle;
|
||||
ResetAITarget();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Crouch if the target is down (only humanoids), so that we can reach it.
|
||||
if (Character.AnimController is HumanoidAnimController humanoidAnimController && distance < AttackLimb.attack.Range * 2)
|
||||
@@ -1958,9 +2000,8 @@ namespace Barotrauma
|
||||
}
|
||||
if (!isFriendly && attackResult.Damage > 0.0f)
|
||||
{
|
||||
ignoredTargets.Remove(attacker.AiTarget);
|
||||
bool canAttack = attacker.Submarine == Character.Submarine && canAttackCharacters || attacker.Submarine != null && canAttackWalls;
|
||||
if (AIParams.AttackWhenProvoked && canAttack)
|
||||
if (AIParams.AttackWhenProvoked && canAttack && !ignoredTargets.Contains(attacker.AiTarget))
|
||||
{
|
||||
if (attacker.IsHusk)
|
||||
{
|
||||
@@ -3476,6 +3517,7 @@ namespace Barotrauma
|
||||
{
|
||||
observeTimer = targetParams.Timer * Rand.Range(0.75f, 1.25f);
|
||||
}
|
||||
reachTimer = 0;
|
||||
}
|
||||
|
||||
protected override void OnStateChanged(AIState from, AIState to)
|
||||
@@ -3496,6 +3538,7 @@ namespace Barotrauma
|
||||
SetStateResetTimer();
|
||||
}
|
||||
blockCheckTimer = 0;
|
||||
reachTimer = 0;
|
||||
}
|
||||
|
||||
private void SetStateResetTimer() => stateResetTimer = stateResetCooldown * Rand.Range(0.75f, 1.25f);
|
||||
|
||||
@@ -59,7 +59,11 @@ namespace Barotrauma
|
||||
private readonly float enemyCheckInterval = 0.2f;
|
||||
private readonly float enemySpotDistanceOutside = 800;
|
||||
private readonly float enemySpotDistanceInside = 1000;
|
||||
private float enemycheckTimer;
|
||||
private float enemyCheckTimer;
|
||||
|
||||
private readonly float reportProblemsInterval = 1.0f;
|
||||
private float reportProblemsTimer;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// How far other characters can hear reports done by this character (e.g. reports for fires, intruders). Defaults to infinity.
|
||||
@@ -166,6 +170,7 @@ namespace Barotrauma
|
||||
objectiveManager = new AIObjectiveManager(c);
|
||||
reactTimer = GetReactionTime();
|
||||
SortTimer = Rand.Range(0f, sortObjectiveInterval);
|
||||
reportProblemsTimer = Rand.Range(0f, reportProblemsInterval);
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
@@ -309,10 +314,10 @@ namespace Barotrauma
|
||||
{
|
||||
// Spot enemies while staying outside or inside an enemy ship.
|
||||
// does not apply for escorted characters, such as prisoners or terrorists who have their own behavior
|
||||
enemycheckTimer -= deltaTime;
|
||||
if (enemycheckTimer < 0)
|
||||
enemyCheckTimer -= deltaTime;
|
||||
if (enemyCheckTimer < 0)
|
||||
{
|
||||
enemycheckTimer = enemyCheckInterval * Rand.Range(0.75f, 1.25f);
|
||||
enemyCheckTimer = enemyCheckInterval * Rand.Range(0.75f, 1.25f);
|
||||
if (!objectiveManager.IsCurrentObjective<AIObjectiveCombat>())
|
||||
{
|
||||
float closestDistance = 0;
|
||||
@@ -407,19 +412,29 @@ namespace Barotrauma
|
||||
{
|
||||
if (Character.IsOnPlayerTeam)
|
||||
{
|
||||
VisibleHulls.ForEach(h => PropagateHullSafety(Character, h));
|
||||
foreach (Hull h in VisibleHulls)
|
||||
{
|
||||
PropagateHullSafety(Character, h);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Outpost npcs don't inform each other about threats, like crew members do.
|
||||
VisibleHulls.ForEach(h => RefreshHullSafety(h));
|
||||
foreach (Hull h in VisibleHulls)
|
||||
{
|
||||
RefreshHullSafety(h);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Character.SpeechImpediment < 100.0f)
|
||||
{
|
||||
if (Character.Submarine != null && (Character.Submarine.TeamID == Character.TeamID || Character.IsEscorted) && !Character.Submarine.Info.IsWreck)
|
||||
reportProblemsTimer -= deltaTime;
|
||||
if (reportProblemsTimer <= 0.0f)
|
||||
{
|
||||
ReportProblems();
|
||||
if (Character.Submarine != null && (Character.Submarine.TeamID == Character.TeamID || Character.IsEscorted) && !Character.Submarine.Info.IsWreck)
|
||||
{
|
||||
ReportProblems();
|
||||
}
|
||||
reportProblemsTimer = reportProblemsInterval;
|
||||
}
|
||||
UpdateSpeaking();
|
||||
}
|
||||
@@ -785,9 +800,10 @@ namespace Barotrauma
|
||||
if (item == null || item.Removed) { return; }
|
||||
if (!itemsToRelocate.Contains(item)) { return; }
|
||||
var mainSub = Submarine.MainSub;
|
||||
if (item.ParentInventory != null)
|
||||
Entity owner = item.GetRootInventoryOwner();
|
||||
if (owner != null)
|
||||
{
|
||||
if (item.ParentInventory.Owner is Character c)
|
||||
if (owner is Character c)
|
||||
{
|
||||
if (c.TeamID == CharacterTeamType.Team1 || c.TeamID == CharacterTeamType.Team2)
|
||||
{
|
||||
@@ -795,24 +811,37 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (item.ParentInventory.Owner.Submarine == mainSub)
|
||||
else if (owner.Submarine == mainSub)
|
||||
{
|
||||
// Placed inside an inventory that's already in the main sub.
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Laying on ground inside the main sub.
|
||||
// Laying on the ground inside the main sub.
|
||||
if (item.Submarine == mainSub)
|
||||
{
|
||||
return;
|
||||
}
|
||||
WayPoint wp = WayPoint.GetRandom(SpawnType.Cargo, null, mainSub);
|
||||
if (wp != null)
|
||||
if (owner != null && owner != item)
|
||||
{
|
||||
item.Submarine = mainSub;
|
||||
item.SetTransform(wp.SimPosition, 0.0f);
|
||||
item.Drop(null);
|
||||
}
|
||||
item.Submarine = mainSub;
|
||||
Item newContainer = mainSub.FindContainerFor(item, onlyPrimary: false);
|
||||
if (newContainer == null || !newContainer.OwnInventory.TryPutItem(item, user: null))
|
||||
{
|
||||
WayPoint wp = WayPoint.GetRandom(SpawnType.Cargo, null, mainSub) ?? WayPoint.GetRandom(SpawnType.Path, null, mainSub);
|
||||
if (wp != null)
|
||||
{
|
||||
item.SetTransform(wp.SimPosition, 0.0f, findNewHull: false, setPrevTransform: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.AddWarning($"Failed to relocate item {item.Prefab.Identifier} ({item.ID}), because no cargo spawn point could be found!");
|
||||
}
|
||||
}
|
||||
itemsToRelocate.Remove(item);
|
||||
DebugConsole.Log($"Relocated item {item.Prefab.Identifier} ({item.ID}) back to the main sub.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -837,8 +866,8 @@ namespace Barotrauma
|
||||
var container = i.GetComponent<ItemContainer>();
|
||||
if (container == null) { return 0; }
|
||||
if (!container.Inventory.CanBePut(containableItem)) { return 0; }
|
||||
var rootContainer = container.Item.GetRootContainer();
|
||||
if (rootContainer?.GetComponent<Fabricator>() != null || rootContainer?.GetComponent<Deconstructor>() != null) { return 0; }
|
||||
var rootContainer = container.Item.GetRootContainer() ?? container.Item;
|
||||
if (rootContainer.GetComponent<Fabricator>() != null || rootContainer.GetComponent<Deconstructor>() != null) { return 0; }
|
||||
if (container.ShouldBeContained(containableItem, out bool isRestrictionsDefined))
|
||||
{
|
||||
if (isRestrictionsDefined)
|
||||
@@ -853,7 +882,12 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
return isPreferencesDefined ? 0 : 1;
|
||||
if (isPreferencesDefined)
|
||||
{
|
||||
// Use any valid locker as a fall back container.
|
||||
return container.Item.HasTag("locker") ? 0.5f : 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1149,7 +1183,7 @@ namespace Barotrauma
|
||||
bool isAttackerFightingEnemy = false;
|
||||
float minorDamageThreshold = 1;
|
||||
float majorDamageThreshold = 20;
|
||||
if (attacker.TeamID == Character.TeamID)
|
||||
if (attacker.TeamID == Character.TeamID && !attacker.IsInstigator)
|
||||
{
|
||||
minorDamageThreshold = 10;
|
||||
majorDamageThreshold = 40;
|
||||
@@ -1356,6 +1390,10 @@ namespace Barotrauma
|
||||
|
||||
Character FindInstigator()
|
||||
{
|
||||
if (Character.IsInstigator)
|
||||
{
|
||||
return Character;
|
||||
}
|
||||
if (attacker.IsInstigator)
|
||||
{
|
||||
return attacker;
|
||||
@@ -1545,7 +1583,7 @@ namespace Barotrauma
|
||||
(!requireEquipped || character.HasEquippedItem(i)) &&
|
||||
(predicate == null || predicate(i)), recursive, matchingItems);
|
||||
items = matchingItems;
|
||||
return matchingItems.Any(i => i != null && (containedTag.IsEmpty || i.ContainedItems.Any(it => it.HasTag(containedTag) && it.ConditionPercentage > conditionPercentage)));
|
||||
return matchingItems.Any(i => i != null && (containedTag.IsEmpty || i.OwnInventory == null || i.ContainedItems.Any(it => it.HasTag(containedTag) && it.ConditionPercentage > conditionPercentage)));
|
||||
}
|
||||
|
||||
public static void StructureDamaged(Structure structure, float damageAmount, Character character)
|
||||
@@ -1889,7 +1927,7 @@ namespace Barotrauma
|
||||
float fireFactor = 1;
|
||||
if (!ignoreFire)
|
||||
{
|
||||
float calculateFire(Hull h) => h.FireSources.Count * 0.5f + h.FireSources.Sum(fs => fs.DamageRange) / h.Size.X;
|
||||
static float calculateFire(Hull h) => h.FireSources.Count * 0.5f + h.FireSources.Sum(fs => fs.DamageRange) / h.Size.X;
|
||||
// Even the smallest fire reduces the safety by 50%
|
||||
float fire = visibleHulls == null ? calculateFire(hull) : visibleHulls.Sum(h => calculateFire(h));
|
||||
fireFactor = MathHelper.Lerp(1, 0, MathHelper.Clamp(fire, 0, 1));
|
||||
@@ -1897,20 +1935,32 @@ namespace Barotrauma
|
||||
float enemyFactor = 1;
|
||||
if (!ignoreEnemies)
|
||||
{
|
||||
bool isValidTarget(Character e) => IsActive(e) && !IsFriendly(character, e) && !e.IsArrested;
|
||||
int enemyCount = visibleHulls == null ?
|
||||
Character.CharacterList.Count(e => isValidTarget(e) && e.CurrentHull == hull) :
|
||||
Character.CharacterList.Count(e => isValidTarget(e) && visibleHulls.Contains(e.CurrentHull));
|
||||
int enemyCount = 0;
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (visibleHulls == null)
|
||||
{
|
||||
if (c.CurrentHull != hull) { continue; }
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!visibleHulls.Contains(c.CurrentHull)) { continue; }
|
||||
}
|
||||
if (IsActive(c) && !IsFriendly(character, c) && !c.IsArrested)
|
||||
{
|
||||
enemyCount++;
|
||||
}
|
||||
}
|
||||
// The hull safety decreases 90% per enemy up to 100% (TODO: test smaller percentages)
|
||||
enemyFactor = MathHelper.Lerp(1, 0, MathHelper.Clamp(enemyCount * 0.9f, 0, 1));
|
||||
}
|
||||
float dangerousItemsFactor = 1f;
|
||||
foreach (Item item in Item.ItemList)
|
||||
foreach (Item item in Item.DangerousItems)
|
||||
{
|
||||
if (item.CurrentHull != hull) { continue; }
|
||||
if (item.Prefab != null && item.Prefab.IsDangerous)
|
||||
{
|
||||
if (item.CurrentHull == hull)
|
||||
{
|
||||
dangerousItemsFactor = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
float safety = oxygenFactor * waterFactor * fireFactor * enemyFactor * dangerousItemsFactor;
|
||||
|
||||
@@ -245,7 +245,7 @@ namespace Barotrauma
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IgnoreAtOutpost && Level.IsLoadedOutpost && character.TeamID != CharacterTeamType.FriendlyNPC)
|
||||
if (IgnoreAtOutpost && Level.IsLoadedFriendlyOutpost && character.TeamID != CharacterTeamType.FriendlyNPC)
|
||||
{
|
||||
if (Submarine.MainSub != null && Submarine.MainSub.DockedTo.None(s => s.TeamID != CharacterTeamType.FriendlyNPC && s.TeamID != character.TeamID))
|
||||
{
|
||||
|
||||
+5
-10
@@ -2,7 +2,6 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -48,6 +47,9 @@ namespace Barotrauma
|
||||
|
||||
protected override bool Filter(Item target)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(target.GetComponent<Pickable>() is { } pickable && !pickable.IsAttached, "Invalid target in AIObjectiveCleanUpItems - the the objective should only be checking pickable, non-attached items.");
|
||||
System.Diagnostics.Debug.Assert(target.Prefab.PreferredContainers.Any(), "Invalid target in AIObjectiveCleanUpItems - the the objective should only be checking items that have preferred containers defined.");
|
||||
|
||||
// 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)) { return Objectives.ContainsKey(target) && IsItemInsideValidSubmarine(target, character); }
|
||||
@@ -57,7 +59,7 @@ namespace Barotrauma
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override IEnumerable<Item> GetList() => Item.ItemList;
|
||||
protected override IEnumerable<Item> GetList() => Item.CleanableItems;
|
||||
|
||||
protected override AIObjective ObjectiveConstructor(Item item)
|
||||
=> new AIObjectiveCleanupItem(item, character, objectiveManager, priorityModifier: PriorityModifier)
|
||||
@@ -102,9 +104,6 @@ namespace Barotrauma
|
||||
}
|
||||
if (character != null && !IsItemInsideValidSubmarine(item, character)) { return false; }
|
||||
if (item.HasBallastFloraInHull) { return false; }
|
||||
var pickable = item.GetComponent<Pickable>();
|
||||
if (pickable == null) { return false; }
|
||||
if (pickable is Holdable h && h.Attachable && h.Attached) { return false; }
|
||||
var wire = item.GetComponent<Wire>();
|
||||
if (wire != null)
|
||||
{
|
||||
@@ -113,15 +112,11 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
var connectionPanel = item.GetComponent<ConnectionPanel>();
|
||||
if (connectionPanel != null && connectionPanel.Connections.Any(c => c.Wires.Any(w => w != null)))
|
||||
if (connectionPanel != null && connectionPanel.Connections.Any(c => c.Wires.Count > 0))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (item.Prefab.PreferredContainers.None())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!checkInventory)
|
||||
{
|
||||
return true;
|
||||
|
||||
+22
-15
@@ -5,6 +5,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using static Barotrauma.AIObjectiveFindSafety;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -775,7 +776,13 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
retreatTarget = findSafety.FindBestHull(HumanAIController.VisibleHulls, allowChangingTheSubmarine: character.TeamID != CharacterTeamType.FriendlyNPC);
|
||||
HullSearchStatus hullSearchStatus = findSafety.FindBestHull(out Hull potentialSafeHull, HumanAIController.VisibleHulls, allowChangingSubmarine: character.TeamID != CharacterTeamType.FriendlyNPC);
|
||||
if (hullSearchStatus != HullSearchStatus.Finished)
|
||||
{
|
||||
findSafety.UpdateSimpleEscape(deltaTime);
|
||||
return;
|
||||
}
|
||||
retreatTarget = potentialSafeHull;
|
||||
findHullTimer = findHullInterval * Rand.Range(0.9f, 1.1f);
|
||||
}
|
||||
}
|
||||
@@ -785,21 +792,21 @@ namespace Barotrauma
|
||||
{
|
||||
UsePathingOutside = false
|
||||
},
|
||||
onAbandon: () =>
|
||||
onAbandon: () =>
|
||||
{
|
||||
if (Enemy != null && HumanAIController.VisibleHulls.Contains(Enemy.CurrentHull))
|
||||
{
|
||||
if (Enemy != null && HumanAIController.VisibleHulls.Contains(Enemy.CurrentHull))
|
||||
{
|
||||
// If in the same room with an enemy -> don't try to escape because we'd want to fight it
|
||||
SteeringManager.Reset();
|
||||
RemoveSubObjective(ref retreatObjective);
|
||||
}
|
||||
else
|
||||
{
|
||||
// else abandon and fall back to find safety mode
|
||||
Abandon = true;
|
||||
}
|
||||
},
|
||||
onCompleted: () => RemoveSubObjective(ref retreatObjective));
|
||||
// If in the same room with an enemy -> don't try to escape because we'd want to fight it
|
||||
SteeringManager.Reset();
|
||||
RemoveSubObjective(ref retreatObjective);
|
||||
}
|
||||
else
|
||||
{
|
||||
// else abandon and fall back to find safety mode
|
||||
Abandon = true;
|
||||
}
|
||||
},
|
||||
onCompleted: () => RemoveSubObjective(ref retreatObjective));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+195
-117
@@ -1,4 +1,5 @@
|
||||
using FarseerPhysics;
|
||||
using Barotrauma.Extensions;
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -192,9 +193,17 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
HullSearchStatus hullSearchStatus = FindBestHull(out Hull potentialSafeHull, allowChangingSubmarine: character.TeamID != CharacterTeamType.FriendlyNPC);
|
||||
if (hullSearchStatus != HullSearchStatus.Finished)
|
||||
{
|
||||
UpdateSimpleEscape(deltaTime);
|
||||
return;
|
||||
}
|
||||
|
||||
searchHullTimer = SearchHullInterval * Rand.Range(0.9f, 1.1f);
|
||||
previousSafeHull = currentSafeHull;
|
||||
currentSafeHull = FindBestHull(allowChangingTheSubmarine: character.TeamID != CharacterTeamType.FriendlyNPC);
|
||||
currentSafeHull = potentialSafeHull;
|
||||
|
||||
cannotFindSafeHull = currentSafeHull == null || HumanAIController.NeedsDivingGear(currentSafeHull, out _);
|
||||
if (currentSafeHull == null)
|
||||
{
|
||||
@@ -250,58 +259,122 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
if (subObjectives.Any(so => so.CanBeCompleted)) { return; }
|
||||
if (currentHull != null)
|
||||
UpdateSimpleEscape(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateSimpleEscape(float deltaTime)
|
||||
{
|
||||
Vector2 escapeVel = Vector2.Zero;
|
||||
if (character.CurrentHull != null)
|
||||
{
|
||||
foreach (Hull hull in HumanAIController.VisibleHulls)
|
||||
{
|
||||
//goto objective doesn't exist (a safe hull not found, or a path to a safe hull not found)
|
||||
// -> attempt to manually steer away from hazards
|
||||
Vector2 escapeVel = Vector2.Zero;
|
||||
foreach (Hull hull in HumanAIController.VisibleHulls)
|
||||
foreach (FireSource fireSource in hull.FireSources)
|
||||
{
|
||||
foreach (FireSource fireSource in hull.FireSources)
|
||||
{
|
||||
Vector2 dir = character.Position - fireSource.Position;
|
||||
float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(fireSource.Position, character.Position), 0.1f, 10.0f);
|
||||
escapeVel += new Vector2(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
|
||||
}
|
||||
}
|
||||
foreach (Character enemy in Character.CharacterList)
|
||||
{
|
||||
if (!HumanAIController.IsActive(enemy) || HumanAIController.IsFriendly(enemy) || enemy.IsArrested) { continue; }
|
||||
if (HumanAIController.VisibleHulls.Contains(enemy.CurrentHull))
|
||||
{
|
||||
Vector2 dir = character.Position - enemy.Position;
|
||||
float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(enemy.Position, character.Position), 0.1f, 10.0f);
|
||||
escapeVel += new Vector2(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
|
||||
}
|
||||
}
|
||||
if (escapeVel != Vector2.Zero)
|
||||
{
|
||||
float left = currentHull.Rect.X + 50;
|
||||
float right = currentHull.Rect.Right - 50;
|
||||
//only move if we haven't reached the edge of the room
|
||||
if (escapeVel.X < 0 && character.Position.X > left || escapeVel.X > 0 && character.Position.X < right)
|
||||
{
|
||||
character.AIController.SteeringManager.SteeringManual(deltaTime, escapeVel);
|
||||
}
|
||||
else
|
||||
{
|
||||
character.AnimController.TargetDir = escapeVel.X < 0.0f ? Direction.Right : Direction.Left;
|
||||
character.AIController.SteeringManager.Reset();
|
||||
}
|
||||
return;
|
||||
Vector2 dir = character.Position - fireSource.Position;
|
||||
float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(fireSource.Position, character.Position), 0.1f, 10.0f);
|
||||
escapeVel += new Vector2(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
|
||||
}
|
||||
}
|
||||
foreach (Character enemy in Character.CharacterList)
|
||||
{
|
||||
if (!HumanAIController.IsActive(enemy) || HumanAIController.IsFriendly(enemy) || enemy.IsArrested) { continue; }
|
||||
if (HumanAIController.VisibleHulls.Contains(enemy.CurrentHull))
|
||||
{
|
||||
Vector2 dir = character.Position - enemy.Position;
|
||||
float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(enemy.Position, character.Position), 0.1f, 10.0f);
|
||||
escapeVel += new Vector2(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (escapeVel != Vector2.Zero)
|
||||
{
|
||||
float left = character.CurrentHull.Rect.X + 50;
|
||||
float right = character.CurrentHull.Rect.Right - 50;
|
||||
//only move if we haven't reached the edge of the room
|
||||
if (escapeVel.X < 0 && character.Position.X > left || escapeVel.X > 0 && character.Position.X < right)
|
||||
{
|
||||
character.AIController.SteeringManager.SteeringManual(deltaTime, escapeVel);
|
||||
}
|
||||
else
|
||||
{
|
||||
character.AnimController.TargetDir = escapeVel.X < 0.0f ? Direction.Right : Direction.Left;
|
||||
character.AIController.SteeringManager.Reset();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
objectiveManager.GetObjective<AIObjectiveIdle>().Wander(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
public Hull FindBestHull(IEnumerable<Hull> ignoredHulls = null, bool allowChangingTheSubmarine = true)
|
||||
public enum HullSearchStatus
|
||||
{
|
||||
//sort the hulls based on distance and which sub they're in
|
||||
//tends to make the method much faster, because we find a potential hull earlier and can discard further-away hulls more easily
|
||||
//(for instance, an NPC in an outpost might otherwise go through all the hulls in the main sub first and do tons of expensive
|
||||
//path calculations, only to discard all of them when going through the hulls in the outpost)
|
||||
float EstimateHullSuitability(Hull hull)
|
||||
Running,
|
||||
Finished
|
||||
}
|
||||
|
||||
private readonly List<Hull> hulls = new List<Hull>();
|
||||
private int hullSearchIndex = -1;
|
||||
float bestHullValue = 0;
|
||||
bool bestHullIsAirlock = false;
|
||||
Hull potentialBestHull;
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find the best (safe, nearby) hull the character can find a path to.
|
||||
/// Checks one hull at a time, and returns HullSearchStatus.Finished when all potential hulls have been checked.
|
||||
/// </summary>
|
||||
public HullSearchStatus FindBestHull(out Hull bestHull, IEnumerable<Hull> ignoredHulls = null, bool allowChangingSubmarine = true)
|
||||
{
|
||||
if (hullSearchIndex == -1)
|
||||
{
|
||||
bestHullValue = 0;
|
||||
potentialBestHull = null;
|
||||
bestHullIsAirlock = false;
|
||||
hulls.Clear();
|
||||
var connectedSubs = character.Submarine?.GetConnectedSubs();
|
||||
foreach (Hull hull in Hull.HullList)
|
||||
{
|
||||
if (hull.Submarine == null) { continue; }
|
||||
// Ruins are mazes filled with water. There's no safe hulls and we don't want to use the resources on it.
|
||||
if (hull.Submarine.Info.IsRuin) { continue; }
|
||||
if (!allowChangingSubmarine && hull.Submarine != character.Submarine) { continue; }
|
||||
if (hull.Rect.Height < ConvertUnits.ToDisplayUnits(character.AnimController.ColliderHeightFromFloor) * 2) { continue; }
|
||||
if (ignoredHulls != null && ignoredHulls.Contains(hull)) { continue; }
|
||||
if (HumanAIController.UnreachableHulls.Contains(hull)) { continue; }
|
||||
if (connectedSubs != null && !connectedSubs.Contains(hull.Submarine)) { continue; }
|
||||
|
||||
//sort the hulls based on distance and which sub they're in
|
||||
//tends to make the method much faster, because we find a potential hull earlier and can discard further-away hulls more easily
|
||||
//(for instance, an NPC in an outpost might otherwise go through all the hulls in the main sub first and do tons of expensive
|
||||
//path calculations, only to discard all of them when going through the hulls in the outpost)
|
||||
float hullSuitability = EstimateHullSuitability(character, hull);
|
||||
if (!hulls.Any())
|
||||
{
|
||||
hulls.Add(hull);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < hulls.Count; i++)
|
||||
{
|
||||
if (hullSuitability > EstimateHullSuitability(character, hulls[i]))
|
||||
{
|
||||
hulls.Insert(i, hull);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hulls.None())
|
||||
{
|
||||
bestHull = null;
|
||||
return HullSearchStatus.Finished;
|
||||
}
|
||||
hullSearchIndex = 0;
|
||||
}
|
||||
|
||||
static float EstimateHullSuitability(Character character, Hull hull)
|
||||
{
|
||||
float dist =
|
||||
Math.Abs(hull.WorldPosition.X - character.WorldPosition.X) +
|
||||
@@ -314,86 +387,91 @@ namespace Barotrauma
|
||||
return suitability;
|
||||
}
|
||||
|
||||
Hull bestHull = null;
|
||||
float bestValue = 0;
|
||||
bool bestIsAirlock = false;
|
||||
foreach (Hull hull in Hull.HullList.OrderByDescending(h => EstimateHullSuitability(h)))
|
||||
Hull potentialHull = hulls[hullSearchIndex];
|
||||
|
||||
float hullSafety = 0;
|
||||
bool hullIsAirlock = false;
|
||||
bool isCharacterInside = character.CurrentHull != null && character.Submarine != null;
|
||||
if (isCharacterInside)
|
||||
{
|
||||
if (hull.Submarine == null) { continue; }
|
||||
// Ruins are mazes filled with water. There's no safe hulls and we don't want to use the resources on it.
|
||||
if (hull.Submarine.Info.IsRuin) { continue; }
|
||||
if (!allowChangingTheSubmarine && hull.Submarine != character.Submarine) { continue; }
|
||||
if (hull.Rect.Height < ConvertUnits.ToDisplayUnits(character.AnimController.ColliderHeightFromFloor) * 2) { continue; }
|
||||
if (ignoredHulls != null && ignoredHulls.Contains(hull)) { continue; }
|
||||
if (HumanAIController.UnreachableHulls.Contains(hull)) { continue; }
|
||||
float hullSafety = 0;
|
||||
bool hullIsAirlock = false;
|
||||
bool isCharacterInside = character.CurrentHull != null && character.Submarine != null;
|
||||
if (isCharacterInside)
|
||||
{
|
||||
if (!character.Submarine.IsConnectedTo(hull.Submarine)) { continue; }
|
||||
hullSafety = HumanAIController.GetHullSafety(hull, hull.GetConnectedHulls(true, 1), character);
|
||||
float yDist = Math.Abs(character.WorldPosition.Y - hull.WorldPosition.Y);
|
||||
yDist = yDist > 100 ? yDist * 3 : 0;
|
||||
float dist = Math.Abs(character.WorldPosition.X - hull.WorldPosition.X) + yDist;
|
||||
float distanceFactor = MathHelper.Lerp(1, 0.9f, MathUtils.InverseLerp(0, 10000, dist));
|
||||
hullSafety *= distanceFactor;
|
||||
//skip the hull if the safety is already less than the best hull
|
||||
//(no need to do the expensive pathfinding if we already know we're not going to choose this hull)
|
||||
if (hullSafety < bestValue) { continue; }
|
||||
hullSafety = HumanAIController.GetHullSafety(potentialHull, potentialHull.GetConnectedHulls(true, 1), character);
|
||||
float yDist = Math.Abs(character.WorldPosition.Y - potentialHull.WorldPosition.Y);
|
||||
yDist = yDist > 100 ? yDist * 3 : 0;
|
||||
float dist = Math.Abs(character.WorldPosition.X - potentialHull.WorldPosition.X) + yDist;
|
||||
float distanceFactor = MathHelper.Lerp(1, 0.9f, MathUtils.InverseLerp(0, 10000, dist));
|
||||
hullSafety *= distanceFactor;
|
||||
//skip the hull if the safety is already less than the best hull
|
||||
//(no need to do the expensive pathfinding if we already know we're not going to choose this hull)
|
||||
if (hullSafety > bestHullValue)
|
||||
{
|
||||
//avoid airlock modules if not allowed to change the sub
|
||||
if (!allowChangingTheSubmarine && hull.OutpostModuleTags.Any(t => t == "airlock"))
|
||||
if (allowChangingSubmarine || !potentialHull.OutpostModuleTags.Any(t => t == "airlock"))
|
||||
{
|
||||
continue;
|
||||
// Don't allow to go outside if not already outside.
|
||||
var path = PathSteering.PathFinder.FindPath(character.SimPosition, potentialHull.SimPosition, character.Submarine, nodeFilter: node => node.Waypoint.CurrentHull != null);
|
||||
if (path.Unreachable)
|
||||
{
|
||||
hullSafety = 0;
|
||||
HumanAIController.UnreachableHulls.Add(potentialHull);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Each unsafe node reduces the hull safety value.
|
||||
// Ignore the current hull, because otherwise we couldn't find a path out.
|
||||
int unsafeNodes = path.Nodes.Count(n => n.CurrentHull != character.CurrentHull && HumanAIController.UnsafeHulls.Contains(n.CurrentHull));
|
||||
hullSafety /= 1 + unsafeNodes;
|
||||
// If the target is not inside a friendly submarine, considerably reduce the hull safety.
|
||||
if (!character.Submarine.IsEntityFoundOnThisSub(potentialHull, true))
|
||||
{
|
||||
hullSafety /= 10;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Don't allow to go outside if not already outside.
|
||||
var path = PathSteering.PathFinder.FindPath(character.SimPosition, hull.SimPosition, character.Submarine, nodeFilter: node => node.Waypoint.CurrentHull != null);
|
||||
if (path.Unreachable)
|
||||
else
|
||||
{
|
||||
HumanAIController.UnreachableHulls.Add(hull);
|
||||
continue;
|
||||
hullSafety = 0;
|
||||
}
|
||||
// Each unsafe node reduces the hull safety value.
|
||||
// Ignore the current hull, because otherwise we couldn't find a path out.
|
||||
int unsafeNodes = path.Nodes.Count(n => n.CurrentHull != character.CurrentHull && HumanAIController.UnsafeHulls.Contains(n.CurrentHull));
|
||||
hullSafety /= 1 + unsafeNodes;
|
||||
// If the target is not inside a friendly submarine, considerably reduce the hull safety.
|
||||
if (!character.Submarine.IsEntityFoundOnThisSub(hull, true))
|
||||
{
|
||||
hullSafety /= 10;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: could also target gaps that get us inside?
|
||||
if (hull.IsTaggedAirlock())
|
||||
{
|
||||
hullSafety = 100;
|
||||
hullIsAirlock = true;
|
||||
}
|
||||
else if(!bestIsAirlock && hull.LeadsOutside(character))
|
||||
{
|
||||
hullSafety = 100;
|
||||
}
|
||||
// Huge preference for closer targets
|
||||
float distance = Vector2.DistanceSquared(character.WorldPosition, hull.WorldPosition);
|
||||
float distanceFactor = MathHelper.Lerp(1, 0.2f, MathUtils.InverseLerp(0, MathUtils.Pow(100000, 2), distance));
|
||||
hullSafety *= distanceFactor;
|
||||
// If the target is not inside a friendly submarine, considerably reduce the hull safety.
|
||||
// Intentionally exclude wrecks from this check
|
||||
if (hull.Submarine.TeamID != character.TeamID && hull.Submarine.TeamID != CharacterTeamType.FriendlyNPC)
|
||||
{
|
||||
hullSafety /= 10;
|
||||
}
|
||||
}
|
||||
if (hullSafety > bestValue || (!isCharacterInside && hullIsAirlock && !bestIsAirlock))
|
||||
{
|
||||
bestHull = hull;
|
||||
bestValue = hullSafety;
|
||||
bestIsAirlock = hullIsAirlock;
|
||||
}
|
||||
}
|
||||
return bestHull;
|
||||
else
|
||||
{
|
||||
// TODO: could also target gaps that get us inside?
|
||||
if (potentialHull.IsTaggedAirlock())
|
||||
{
|
||||
hullSafety = 100;
|
||||
hullIsAirlock = true;
|
||||
}
|
||||
else if(!bestHullIsAirlock && potentialHull.LeadsOutside(character))
|
||||
{
|
||||
hullSafety = 100;
|
||||
}
|
||||
// Huge preference for closer targets
|
||||
float distance = Vector2.DistanceSquared(character.WorldPosition, potentialHull.WorldPosition);
|
||||
float distanceFactor = MathHelper.Lerp(1, 0.2f, MathUtils.InverseLerp(0, MathUtils.Pow(100000, 2), distance));
|
||||
hullSafety *= distanceFactor;
|
||||
// If the target is not inside a friendly submarine, considerably reduce the hull safety.
|
||||
// Intentionally exclude wrecks from this check
|
||||
if (potentialHull.Submarine.TeamID != character.TeamID && potentialHull.Submarine.TeamID != CharacterTeamType.FriendlyNPC)
|
||||
{
|
||||
hullSafety /= 10;
|
||||
}
|
||||
}
|
||||
if (hullSafety > bestHullValue || (!isCharacterInside && hullIsAirlock && !bestHullIsAirlock))
|
||||
{
|
||||
potentialBestHull = potentialHull;
|
||||
bestHullValue = hullSafety;
|
||||
bestHullIsAirlock = hullIsAirlock;
|
||||
}
|
||||
|
||||
bestHull = potentialBestHull;
|
||||
hullSearchIndex++;
|
||||
|
||||
if (hullSearchIndex >= hulls.Count)
|
||||
{
|
||||
hullSearchIndex = -1;
|
||||
return HullSearchStatus.Finished;
|
||||
}
|
||||
return HullSearchStatus.Running;
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
|
||||
+24
-10
@@ -48,16 +48,29 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
float xDist = Math.Abs(character.WorldPosition.X - Leak.WorldPosition.X);
|
||||
float yDist = Math.Abs(character.WorldPosition.Y - Leak.WorldPosition.Y);
|
||||
// Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally).
|
||||
// If the target is close, ignore the distance factor alltogether so that we keep fixing the leaks that are nearby.
|
||||
float distanceFactor = isPriority || xDist < 200 && yDist < 100 ? 1 : MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 3000, xDist + yDist * 3.0f));
|
||||
float severity = isPriority ? 1 : AIObjectiveFixLeaks.GetLeakSeverity(Leak) / 100;
|
||||
float reduction = isPriority ? 1 : 2;
|
||||
float max = AIObjectiveManager.LowestOrderPriority - reduction;
|
||||
float devotion = CumulatedDevotion / 100;
|
||||
Priority = MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + (severity * distanceFactor * PriorityModifier), 0, 1));
|
||||
float maxPriority = AIObjectiveManager.LowestOrderPriority - reduction;
|
||||
if (operateObjective != null && objectiveManager.GetActiveObjective<AIObjectiveFixLeaks>() is AIObjectiveFixLeaks fixLeaks && fixLeaks.CurrentSubObjective == this)
|
||||
{
|
||||
// Prioritize leaks that we are already fixing
|
||||
Priority = maxPriority;
|
||||
}
|
||||
else
|
||||
{
|
||||
float xDist = Math.Abs(character.WorldPosition.X - Leak.WorldPosition.X);
|
||||
float yDist = Math.Abs(character.WorldPosition.Y - Leak.WorldPosition.Y);
|
||||
// Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally).
|
||||
// If the target is close, ignore the distance factor alltogether so that we keep fixing the leaks that are nearby.
|
||||
float distanceFactor = isPriority || xDist < 200 && yDist < 100 ? 1 : MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 3000, xDist + yDist * 3.0f));
|
||||
if (Leak.linkedTo.Any(e => e is Hull h && h == character.CurrentHull))
|
||||
{
|
||||
// Double the distance when the leak can be accessed from the current hull.
|
||||
distanceFactor *= 2;
|
||||
}
|
||||
float severity = isPriority ? 1 : AIObjectiveFixLeaks.GetLeakSeverity(Leak) / 100;
|
||||
float devotion = CumulatedDevotion / 100;
|
||||
Priority = MathHelper.Lerp(0, maxPriority, MathHelper.Clamp(devotion + (severity * distanceFactor * PriorityModifier), 0, 1));
|
||||
}
|
||||
}
|
||||
return Priority;
|
||||
}
|
||||
@@ -165,6 +178,7 @@ namespace Barotrauma
|
||||
requiredCondition = () =>
|
||||
Leak.Submarine == character.Submarine &&
|
||||
Leak.linkedTo.Any(e => e is Hull h && character.CurrentHull == h),
|
||||
endNodeFilter = n => n.Waypoint.CurrentHull != null && Leak.linkedTo.Any(e => e is Hull h && h == n.Waypoint.CurrentHull),
|
||||
// The Go To objective can be abandoned if the leak is fixed (in which case we don't want to use the dialogue)
|
||||
SpeakCannotReachCondition = () => !CheckObjectiveSpecific()
|
||||
},
|
||||
@@ -201,7 +215,7 @@ namespace Barotrauma
|
||||
// This is an approximation, because we don't know the exact reach until the pose is taken.
|
||||
// And even then the actual range depends on the direction we are aiming to.
|
||||
// Found out that without any multiplier the value (209) is often too short.
|
||||
return repairTool.Range + armLength * 1.3f;
|
||||
return repairTool.Range + armLength * 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+29
-1
@@ -471,7 +471,8 @@ namespace Barotrauma
|
||||
{
|
||||
if (spawnItemIfNotFound)
|
||||
{
|
||||
if (!(MapEntityPrefab.List.FirstOrDefault(me => me is ItemPrefab ip && IdentifiersOrTags.Any(id => id == ip.Identifier || ip.Tags.Contains(id))) is ItemPrefab prefab))
|
||||
ItemPrefab prefab = FindItemToSpawn();
|
||||
if (prefab == null)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage($"{character.Name}: Cannot find an item with the following identifier(s) or tag(s): {string.Join(", ", IdentifiersOrTags)}, tried to spawn the item but no matching item prefabs were found.", Color.Yellow);
|
||||
@@ -501,6 +502,33 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the "best" item to spawn when using <see cref="spawnItemIfNotFound"/> and there's multiple suitable items.
|
||||
/// Best in this context is the one that's sold at the lowest price in stores (usually the most "basic" item)
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private ItemPrefab FindItemToSpawn()
|
||||
{
|
||||
ItemPrefab bestItem = null;
|
||||
float lowestCost = float.MaxValue;
|
||||
foreach (MapEntityPrefab prefab in MapEntityPrefab.List)
|
||||
{
|
||||
if (!(prefab is ItemPrefab itemPrefab)) { continue; }
|
||||
if (IdentifiersOrTags.Any(id => id == prefab.Identifier || prefab.Tags.Contains(id)))
|
||||
{
|
||||
float cost = itemPrefab.DefaultPrice != null && itemPrefab.CanBeBought ?
|
||||
itemPrefab.DefaultPrice.Price :
|
||||
float.MaxValue;
|
||||
if (cost < lowestCost || bestItem == null)
|
||||
{
|
||||
bestItem = itemPrefab;
|
||||
lowestCost = cost;
|
||||
}
|
||||
}
|
||||
}
|
||||
return bestItem;
|
||||
}
|
||||
|
||||
protected override bool CheckObjectiveSpecific()
|
||||
{
|
||||
if (IsCompleted) { return true; }
|
||||
|
||||
+20
-2
@@ -185,6 +185,11 @@ namespace Barotrauma
|
||||
{
|
||||
PathSteering.SteeringSeek(character.GetRelativeSimPosition(currentTarget), weight: 1, nodeFilter: node => node.Waypoint.CurrentHull != null);
|
||||
}
|
||||
else
|
||||
{
|
||||
PathSteering.ResetPath();
|
||||
PathSteering.Reset();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -290,12 +295,25 @@ namespace Barotrauma
|
||||
{
|
||||
PathSteering.SteeringSeek(character.GetRelativeSimPosition(currentTarget), weight: 1, nodeFilter: node => node.Waypoint.CurrentHull != null);
|
||||
}
|
||||
else
|
||||
{
|
||||
PathSteering.ResetPath();
|
||||
PathSteering.Reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Wander(float deltaTime)
|
||||
{
|
||||
if (character.IsClimbing) { return; }
|
||||
if (character.IsClimbing)
|
||||
{
|
||||
if (character.AnimController.GetHeightFromFloor() < 0.1f)
|
||||
{
|
||||
character.AnimController.Anim = AnimController.Animation.None;
|
||||
character.SelectedConstruction = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
var currentHull = character.CurrentHull;
|
||||
if (!character.AnimController.InWater && currentHull != null)
|
||||
{
|
||||
@@ -470,7 +488,7 @@ namespace Barotrauma
|
||||
if (hull != null)
|
||||
{
|
||||
itemsToClean.Clear();
|
||||
foreach (Item item in Item.ItemList)
|
||||
foreach (Item item in Item.CleanableItems)
|
||||
{
|
||||
if (item.CurrentHull != hull) { continue; }
|
||||
if (AIObjectiveCleanupItems.IsValidTarget(item, character, checkInventory: true, allowUnloading: false) && !ignoredItems.Contains(item))
|
||||
|
||||
+12
-4
@@ -39,12 +39,21 @@ namespace Barotrauma
|
||||
{
|
||||
TargetContainers.Add(targetContainer);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (!OrderPrefab.TargetItemsMatchItem(TargetContainerTags, item)) { continue; }
|
||||
TargetContainers.Add(item);
|
||||
}
|
||||
}
|
||||
TargetCondition = option == "turretammo" ? ItemCondition.Empty : ItemCondition.Full;
|
||||
}
|
||||
|
||||
protected override bool Filter(Item target)
|
||||
{
|
||||
if (!IsValidTarget(target, character, TargetContainerTags, TargetCondition)) { return false; }
|
||||
//don't pass TargetContainerTags to the method (no need to filter by tags anymore, it's already done when populating TargetContainers)
|
||||
if (!IsValidTarget(target, character, null, TargetCondition)) { return false; }
|
||||
if (target.CurrentHull == null || target.CurrentHull.FireSources.Count > 0) { return false; }
|
||||
if (Character.CharacterList.Any(c => c.CurrentHull == target.CurrentHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c))) { return false; }
|
||||
return true;
|
||||
@@ -52,8 +61,7 @@ namespace Barotrauma
|
||||
|
||||
public static bool IsValidTarget(Item item, Character character, ImmutableArray<Identifier>? targetContainerTags = null, ItemCondition? targetCondition = null)
|
||||
{
|
||||
if (item == null) { return false; }
|
||||
if (item.Removed) { return false; }
|
||||
if (item == null || item.Removed) { return false; }
|
||||
if (targetContainerTags.HasValue && !OrderPrefab.TargetItemsMatchItem(targetContainerTags.Value, item)) { return false; }
|
||||
if (!(item.GetComponent<ItemContainer>() is ItemContainer container)) { return false; }
|
||||
if (container.Inventory == null) { return false; }
|
||||
@@ -88,7 +96,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
protected override IEnumerable<Item> GetList() => TargetContainers.Any() ? TargetContainers : Item.ItemList;
|
||||
protected override IEnumerable<Item> GetList() => TargetContainers;
|
||||
|
||||
protected override AIObjective ObjectiveConstructor(Item target)
|
||||
=> new AIObjectiveLoadItem(target, TargetContainerTags, TargetCondition, Option, character, objectiveManager, PriorityModifier);
|
||||
|
||||
+1
-1
@@ -151,7 +151,7 @@ namespace Barotrauma
|
||||
}
|
||||
var order = new Order(orderPrefab, autonomousObjective.Option, item ?? character.CurrentHull as Entity, orderPrefab.GetTargetItemComponent(item), orderGiver: character);
|
||||
if (order == null) { continue; }
|
||||
if ((order.IgnoreAtOutpost || autonomousObjective.IgnoreAtOutpost) && Level.IsLoadedOutpost && character.TeamID != CharacterTeamType.FriendlyNPC)
|
||||
if ((order.IgnoreAtOutpost || autonomousObjective.IgnoreAtOutpost) && Level.IsLoadedFriendlyOutpost && character.TeamID != CharacterTeamType.FriendlyNPC)
|
||||
{
|
||||
if (Submarine.MainSub != null && Submarine.MainSub.DockedTo.None(s => s.TeamID != CharacterTeamType.FriendlyNPC && s.TeamID != character.TeamID))
|
||||
{
|
||||
|
||||
+12
-7
@@ -13,7 +13,7 @@ namespace Barotrauma
|
||||
public override bool KeepDivingGearOn => true;
|
||||
public override bool AllowAutomaticItemUnequipping => true;
|
||||
|
||||
private IEnumerable<Pump> pumpList;
|
||||
private List<Pump> pumpList;
|
||||
|
||||
public AIObjectivePumpWater(Character character, AIObjectiveManager objectiveManager, Identifier option, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier, option) { }
|
||||
@@ -26,13 +26,9 @@ namespace Barotrauma
|
||||
|
||||
protected override bool Filter(Pump pump)
|
||||
{
|
||||
if (pump == null) { return false; }
|
||||
if (pump?.Item == null || pump.Item.Removed) { return false; }
|
||||
if (pump.Item.IgnoreByAI(character)) { return false; }
|
||||
if (!pump.Item.IsInteractable(character)) { return false; }
|
||||
if (pump.Item.HasTag("ballast")) { return false; }
|
||||
if (pump.Item.Submarine == null) { return false; }
|
||||
if (pump.Item.CurrentHull == null) { return false; }
|
||||
if (pump.Item.Submarine.TeamID != character.TeamID) { return false; }
|
||||
if (pump.IsAutoControlled) { return false; }
|
||||
if (pump.Item.ConditionPercentage <= 0) { return false; }
|
||||
if (pump.Item.CurrentHull.FireSources.Count > 0) { return false; }
|
||||
@@ -50,7 +46,16 @@ namespace Barotrauma
|
||||
if (pumpList == null)
|
||||
{
|
||||
if (character == null || character.Submarine == null) { return Array.Empty<Pump>(); }
|
||||
pumpList = character.Submarine.GetItems(true).Select(i => i.GetComponent<Pump>()).Where(p => p != null);
|
||||
|
||||
pumpList = new List<Pump>();
|
||||
foreach (Item item in character.Submarine.GetItems(true))
|
||||
{
|
||||
var pump = item.GetComponent<Pump>();
|
||||
if (pump == null || pump.Item.Submarine == null || pump.Item.CurrentHull == null) { continue; }
|
||||
if (pump.Item.Submarine.TeamID != character.TeamID) { continue; }
|
||||
if (pump.Item.HasTag("ballast")) { continue; }
|
||||
pumpList.Add(pump);
|
||||
}
|
||||
}
|
||||
return pumpList;
|
||||
}
|
||||
|
||||
+1
-1
@@ -238,7 +238,7 @@ namespace Barotrauma
|
||||
};
|
||||
if (repairTool != null)
|
||||
{
|
||||
objective.CloseEnough = repairTool.Range * 0.75f;
|
||||
objective.CloseEnough = AIObjectiveFixLeak.CalculateReach(repairTool, character);
|
||||
}
|
||||
return objective;
|
||||
},
|
||||
|
||||
+4
-1
@@ -136,7 +136,7 @@ namespace Barotrauma
|
||||
return MathHelper.Lerp(0, 100, MathHelper.Clamp(damagePriority * successFactor, 0, 1));
|
||||
}
|
||||
|
||||
protected override IEnumerable<Item> GetList() => Item.ItemList;
|
||||
protected override IEnumerable<Item> GetList() => Item.RepairableItems;
|
||||
|
||||
protected override AIObjective ObjectiveConstructor(Item item)
|
||||
=> new AIObjectiveRepairItem(character, item, objectiveManager, priorityModifier: PriorityModifier, isPriority: item == PrioritizedItem);
|
||||
@@ -156,6 +156,9 @@ namespace Barotrauma
|
||||
if (character.IsOnPlayerTeam && item.Submarine.Info.IsOutpost) { return false; }
|
||||
if (!character.Submarine.IsEntityFoundOnThisSub(item, includingConnectedSubs: true)) { return false; }
|
||||
if (item.Repairables.None()) { return false; }
|
||||
|
||||
System.Diagnostics.Debug.Assert(item.Repairables.Any(), "Invalid target in AIObjectiveRepairItems - the objective should only be checking items that have a Repairable component (Item.RepairableItems)");
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
+4
-1
@@ -4,6 +4,7 @@ using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using static Barotrauma.AIObjectiveFindSafety;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -186,7 +187,9 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
safeHull = objectiveManager.GetObjective<AIObjectiveFindSafety>().FindBestHull(HumanAIController.VisibleHulls);
|
||||
HullSearchStatus hullSearchStatus = objectiveManager.GetObjective<AIObjectiveFindSafety>().FindBestHull(out Hull potentialSafeHull, HumanAIController.VisibleHulls);
|
||||
if (hullSearchStatus != HullSearchStatus.Finished) { return; }
|
||||
safeHull = potentialSafeHull;
|
||||
findHullTimer = findHullInterval * Rand.Range(0.9f, 1.1f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using FarseerPhysics.Dynamics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using FarseerPhysics;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -90,6 +88,10 @@ namespace Barotrauma
|
||||
{
|
||||
steering = Vector2.Normalize(steering) * Math.Abs(speed);
|
||||
}
|
||||
if (host is AIController aiController && aiController?.Character.CharacterHealth.GetAfflictionOfType("invertcontrols".ToIdentifier()) != null)
|
||||
{
|
||||
steering = -steering;
|
||||
}
|
||||
host.Steering = steering;
|
||||
}
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ namespace Barotrauma
|
||||
|
||||
public readonly Identifier[] ForbiddenAmmunition;
|
||||
|
||||
public static WreckAIConfig GetRandom() => Prefabs.GetRandom(Rand.RandSync.ServerAndClient);
|
||||
public static WreckAIConfig GetRandom() => Prefabs.OrderBy(p => p.UintIdentifier).GetRandom(Rand.RandSync.ServerAndClient);
|
||||
|
||||
protected override Identifier DetermineIdentifier(XElement element)
|
||||
{
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Barotrauma
|
||||
public bool IsAiming => wasAiming;
|
||||
public bool IsAimingMelee => wasAimingMelee;
|
||||
|
||||
protected bool Aiming => aiming || aimingMelee;
|
||||
protected bool Aiming => aiming || aimingMelee || LockFlippingUntil > Timing.TotalTime && character.IsKeyDown(InputType.Aim);
|
||||
|
||||
public float ArmLength => upperArmLength + forearmLength;
|
||||
|
||||
@@ -275,6 +275,8 @@ namespace Barotrauma
|
||||
// We need some margin, because if a hatch has closed, it's possible that the height from floor is slightly negative.
|
||||
public bool IsAboveFloor => GetHeightFromFloor() > -0.1f;
|
||||
|
||||
public float LockFlippingUntil;
|
||||
|
||||
public void UpdateUseItem(bool allowMovement, Vector2 handWorldPos)
|
||||
{
|
||||
useItemTimer = 0.5f;
|
||||
@@ -380,18 +382,10 @@ namespace Barotrauma
|
||||
{
|
||||
//if holding two items that should control the characters' pose, let the item in the right hand do it
|
||||
bool anotherItemControlsPose = equippedInLefthand && rightHandItem != item && (rightHandItem?.GetComponent<Holdable>()?.ControlPose ?? false);
|
||||
if (!anotherItemControlsPose)
|
||||
if (!anotherItemControlsPose && TargetMovement == Vector2.Zero && inWater)
|
||||
{
|
||||
var head = GetLimb(LimbType.Head);
|
||||
if (head != null)
|
||||
{
|
||||
head.body.SmoothRotate(itemAngle, force: 30 * head.Mass);
|
||||
}
|
||||
if (TargetMovement == Vector2.Zero && inWater)
|
||||
{
|
||||
torso.body.AngularVelocity -= torso.body.AngularVelocity * 0.1f;
|
||||
torso.body.ApplyForce(torso.body.LinearVelocity * -0.5f);
|
||||
}
|
||||
torso.body.AngularVelocity -= torso.body.AngularVelocity * 0.1f;
|
||||
torso.body.ApplyForce(torso.body.LinearVelocity * -0.5f);
|
||||
}
|
||||
aiming = true;
|
||||
}
|
||||
|
||||
@@ -22,8 +22,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (_ragdollParams == null)
|
||||
{
|
||||
#warning TODO: this is kinda janky, this should probably be done better
|
||||
_ragdollParams = FishRagdollParams.GetDefaultRagdollParams(character.VariantOf.IfEmpty(character.SpeciesName));
|
||||
_ragdollParams = FishRagdollParams.GetDefaultRagdollParams(character.SpeciesName);
|
||||
if (!character.VariantOf.IsEmpty)
|
||||
{
|
||||
_ragdollParams.ApplyVariantScale(character.Params.VariantFile);
|
||||
|
||||
+1
-3
@@ -164,8 +164,6 @@ namespace Barotrauma
|
||||
public float LegBendTorque => CurrentGroundedParams.LegBendTorque * RagdollParams.JointScale;
|
||||
public Vector2 HandMoveOffset => CurrentGroundedParams.HandMoveOffset * RagdollParams.JointScale;
|
||||
|
||||
public float LockFlippingUntil;
|
||||
|
||||
public override Vector2 AimSourceSimPos
|
||||
{
|
||||
get
|
||||
@@ -841,7 +839,7 @@ namespace Barotrauma
|
||||
rotation += 360;
|
||||
}
|
||||
float targetSpeed = TargetMovement.Length();
|
||||
if (targetSpeed > 0.1f && !character.IsRemotelyControlled && !character.IsKeyDown(InputType.Aim))
|
||||
if (targetSpeed > 0.1f && !character.IsRemotelyControlled && !Aiming)
|
||||
{
|
||||
if (Anim != Animation.UsingConstruction && !(character.SelectedConstruction?.GetComponent<Controller>()?.ControlCharacterPose ?? false))
|
||||
{
|
||||
|
||||
@@ -75,7 +75,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasMultipleLimbsOfSameType => limbs == null ? false : Limbs.Length > limbDictionary.Count;
|
||||
public bool HasMultipleLimbsOfSameType => limbs != null && limbs.Length > limbDictionary.Count;
|
||||
|
||||
private bool frozen;
|
||||
public bool Frozen
|
||||
@@ -228,9 +228,13 @@ namespace Barotrauma
|
||||
{
|
||||
mainLimb = Limbs.FirstOrDefault(l => IsValid(l));
|
||||
}
|
||||
if (mainLimb == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't find a valid main limb. The limb can't be hidden nor be set to ignore collisions!");
|
||||
mainLimb = Limbs.FirstOrDefault();
|
||||
}
|
||||
}
|
||||
|
||||
bool IsValid(Limb limb) => limb != null && !limb.IsSevered && !limb.IgnoreCollisions && !limb.Hidden;
|
||||
static bool IsValid(Limb limb) => limb != null && !limb.IsSevered && !limb.IgnoreCollisions && !limb.Hidden;
|
||||
return mainLimb;
|
||||
}
|
||||
}
|
||||
@@ -1858,36 +1862,30 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Note that if there are multiple limbs of the same type, only the first of them is found in the dictionary.
|
||||
/// Note that if there are multiple limbs of the same type, only the first (valid) limb is returned.
|
||||
/// </summary>
|
||||
public Limb GetLimb(LimbType limbType, bool excludeSevered = true)
|
||||
{
|
||||
Limb limb = null;
|
||||
if (HasMultipleLimbsOfSameType)
|
||||
if (limbDictionary.TryGetValue(limbType, out Limb limb))
|
||||
{
|
||||
for (int i = 0; i < 10; i++)
|
||||
if (excludeSevered && limb.IsSevered)
|
||||
{
|
||||
limbDictionary.TryGetValue(limbType, out limb);
|
||||
if (limb == null)
|
||||
limb = null;
|
||||
}
|
||||
}
|
||||
if (limb == null && HasMultipleLimbsOfSameType)
|
||||
{
|
||||
// Didn't find a (valid) limb of the matching type. If there's multiple limbs of the same type, check the other limbs.
|
||||
foreach (var l in limbs)
|
||||
{
|
||||
if (l.type != limbType) { continue; }
|
||||
if (!excludeSevered || !l.IsSevered)
|
||||
{
|
||||
// No limbs found
|
||||
break;
|
||||
}
|
||||
if (!excludeSevered || !limb.IsSevered)
|
||||
{
|
||||
// Found a valid limb
|
||||
limb = l;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
limbDictionary.TryGetValue(limbType, out limb);
|
||||
}
|
||||
if (excludeSevered && limb != null && limb.IsSevered)
|
||||
{
|
||||
limb = null;
|
||||
}
|
||||
return limb;
|
||||
}
|
||||
|
||||
|
||||
@@ -120,7 +120,29 @@ namespace Barotrauma
|
||||
|
||||
protected Key[] keys;
|
||||
|
||||
public HumanPrefab HumanPrefab;
|
||||
private HumanPrefab humanPrefab;
|
||||
public HumanPrefab HumanPrefab
|
||||
{
|
||||
get { return humanPrefab; }
|
||||
set
|
||||
{
|
||||
if (humanPrefab == value) { return; }
|
||||
humanPrefab = value;
|
||||
|
||||
if (humanPrefab != null)
|
||||
{
|
||||
HumanPrefabHealthMultiplier = humanPrefab.HealthMultiplier;
|
||||
if (GameMain.NetworkMember != null)
|
||||
{
|
||||
HumanPrefabHealthMultiplier *= humanPrefab.HealthMultiplierInMultiplayer;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
HumanPrefabHealthMultiplier = 1.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private CharacterTeamType teamID;
|
||||
public CharacterTeamType TeamID
|
||||
@@ -156,12 +178,12 @@ namespace Barotrauma
|
||||
protected ActiveTeamChange currentTeamChange;
|
||||
const string OriginalTeamIdentifier = "original";
|
||||
|
||||
public static void ThrowIfAccessingWalletsInSingleplayer()
|
||||
private void ThrowIfAccessingWalletsInSingleplayer()
|
||||
{
|
||||
#if CLIENT && DEBUG
|
||||
if (Screen.Selected is TestScreen) { return; }
|
||||
#endif
|
||||
if (GameMain.NetworkMember is null || GameMain.IsSingleplayer)
|
||||
if ((GameMain.NetworkMember is null || GameMain.IsSingleplayer) && IsPlayer)
|
||||
{
|
||||
throw new InvalidOperationException($"Tried to access crew wallets in singleplayer. Use {nameof(CampaignMode)}.{nameof(CampaignMode.Bank)} or {nameof(CampaignMode)}.{nameof(CampaignMode.GetWallet)} instead.");
|
||||
}
|
||||
@@ -563,18 +585,35 @@ namespace Barotrauma
|
||||
|
||||
#if CLIENT
|
||||
CharacterHealth.SetHealthBarVisibility(value == null);
|
||||
#elif SERVER
|
||||
if (value is { IsDead: true, Wallet: { Balance: var balance } grabbedWallet } && balance > 0)
|
||||
#endif
|
||||
bool isServerOrSingleplayer = GameMain.IsSingleplayer || GameMain.NetworkMember is { IsServer: true };
|
||||
if (IsPlayer && isServerOrSingleplayer && value is { IsDead: true, Wallet: { Balance: var balance } grabbedWallet } && balance > 0)
|
||||
{
|
||||
if (GameMain.GameSession.Campaign is MultiPlayerCampaign mpCampaign)
|
||||
#if SERVER
|
||||
if (GameMain.GameSession.Campaign is MultiPlayerCampaign mpCampaign && GameMain.Server is { ServerSettings: { } settings })
|
||||
{
|
||||
mpCampaign.Bank.Give(balance);
|
||||
switch (settings.LootedMoneyDestination)
|
||||
{
|
||||
case LootedMoneyDestination.Wallet when IsPlayer:
|
||||
Wallet.Give(balance);
|
||||
break;
|
||||
default:
|
||||
mpCampaign.Bank.Give(balance);
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
grabbedWallet.Deduct(balance);
|
||||
GameServer.Log($"{GameServer.CharacterLogName(this)} grabbed {value.Name}'s body and received {grabbedWallet.Balance} mk.", ServerLog.MessageType.Money);
|
||||
}
|
||||
#elif CLIENT
|
||||
if (GameMain.GameSession.Campaign is SinglePlayerCampaign spCampaign)
|
||||
{
|
||||
spCampaign.Bank.Give(balance);
|
||||
}
|
||||
#endif
|
||||
|
||||
grabbedWallet.Deduct(balance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1181,7 +1220,7 @@ namespace Barotrauma
|
||||
CharacterHealth = new CharacterHealth(selectedHealthElement, this, limbHealthElement);
|
||||
}
|
||||
|
||||
if (Params.Husk && speciesName != "husk")
|
||||
if (Params.Husk && speciesName != "husk" && Prefab.VariantOf != "husk")
|
||||
{
|
||||
// Get the non husked name and find the ragdoll with it
|
||||
var matchingAffliction = AfflictionPrefab.List
|
||||
@@ -1381,7 +1420,7 @@ namespace Barotrauma
|
||||
if (inputType == InputType.Up || inputType == InputType.Down ||
|
||||
inputType == InputType.Left || inputType == InputType.Right)
|
||||
{
|
||||
var invertControls = CharacterHealth.GetAffliction("invertcontrols");
|
||||
var invertControls = CharacterHealth.GetAfflictionOfType("invertcontrols".ToIdentifier());
|
||||
if (invertControls != null)
|
||||
{
|
||||
switch (inputType)
|
||||
@@ -1451,7 +1490,7 @@ namespace Barotrauma
|
||||
|
||||
foreach (Item item in Inventory.AllItems)
|
||||
{
|
||||
if (item?.Prefab.Identifier != "idcard") { continue; }
|
||||
if (item?.GetComponent<IdCard>() == null) { continue; }
|
||||
foreach (string s in spawnPoint.IdCardTags)
|
||||
{
|
||||
item.AddTag(s);
|
||||
@@ -1643,14 +1682,9 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can be used to modify a character's health for runtime session. Change with AddHealthMultiplier
|
||||
/// Health multiplier of the human prefab this character is an instance of (if any)
|
||||
/// </summary>
|
||||
public float StaticHealthMultiplier { get; private set; } = 1;
|
||||
|
||||
public void AddStaticHealthMultiplier(float newMultiplier)
|
||||
{
|
||||
StaticHealthMultiplier *= newMultiplier;
|
||||
}
|
||||
public float HumanPrefabHealthMultiplier { get; private set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Speed reduction from the current limb specific damage. Min 0, max 1.
|
||||
@@ -3976,7 +4010,10 @@ namespace Barotrauma
|
||||
if (actionType != ActionType.OnDamaged && actionType != ActionType.OnSevered)
|
||||
{
|
||||
// OnDamaged is called only for the limb that is hit.
|
||||
AnimController.Limbs.ForEach(l => l.ApplyStatusEffects(actionType, deltaTime));
|
||||
foreach (Limb limb in AnimController.Limbs)
|
||||
{
|
||||
limb.ApplyStatusEffects(actionType, deltaTime);
|
||||
}
|
||||
}
|
||||
//OnActive effects are handled by the afflictions themselves
|
||||
if (actionType != ActionType.OnActive)
|
||||
@@ -4826,21 +4863,21 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<AbilityFlags> abilityFlags = new List<AbilityFlags>();
|
||||
private AbilityFlags abilityFlags;
|
||||
|
||||
public void AddAbilityFlag(AbilityFlags abilityFlag)
|
||||
{
|
||||
abilityFlags.Add(abilityFlag);
|
||||
abilityFlags |= abilityFlag;
|
||||
}
|
||||
|
||||
public void RemoveAbilityFlag(AbilityFlags abilityFlag)
|
||||
{
|
||||
abilityFlags.Remove(abilityFlag);
|
||||
abilityFlags &= ~abilityFlag;
|
||||
}
|
||||
|
||||
public bool HasAbilityFlag(AbilityFlags abilityFlag)
|
||||
{
|
||||
return abilityFlags.Contains(abilityFlag) || CharacterHealth.HasFlag(abilityFlag);
|
||||
return abilityFlags.HasFlag(abilityFlag) || CharacterHealth.HasFlag(abilityFlag);
|
||||
}
|
||||
|
||||
private readonly Dictionary<Identifier, float> abilityResistances = new Dictionary<Identifier, float>();
|
||||
|
||||
@@ -94,11 +94,67 @@ namespace Barotrauma
|
||||
|
||||
public Vector2 SheetIndex => Preset.SheetIndex;
|
||||
|
||||
public ContentXElement HairElement => CharacterInfo.Hairs?.ElementAtOrDefault(HairIndex);
|
||||
public ContentXElement HairWithHatElement => CharacterInfo.Hairs?.ElementAtOrDefault(HairWithHatIndex);
|
||||
public ContentXElement BeardElement => CharacterInfo.Beards?.ElementAtOrDefault(BeardIndex);
|
||||
public ContentXElement MoustacheElement => CharacterInfo.Moustaches?.ElementAtOrDefault(MoustacheIndex);
|
||||
public ContentXElement FaceAttachment => CharacterInfo.FaceAttachments?.ElementAtOrDefault(FaceAttachmentIndex);
|
||||
public ContentXElement HairElement
|
||||
{
|
||||
get
|
||||
{
|
||||
if (CharacterInfo.Hairs == null) { return null; }
|
||||
if (hairIndex >= CharacterInfo.Hairs.Count)
|
||||
{
|
||||
DebugConsole.AddWarning($"Hair index out of range (character: {CharacterInfo?.Name ?? "null"}, index: {hairIndex})");
|
||||
}
|
||||
return CharacterInfo.Hairs.ElementAtOrDefault(hairIndex);
|
||||
}
|
||||
}
|
||||
public ContentXElement HairWithHatElement
|
||||
{
|
||||
get
|
||||
{
|
||||
if (CharacterInfo.Hairs == null) { return null; }
|
||||
if (HairWithHatIndex >= CharacterInfo.Hairs.Count)
|
||||
{
|
||||
DebugConsole.AddWarning($"Hair with hat index out of range (character: {CharacterInfo?.Name ?? "null"}, index: {HairWithHatIndex})");
|
||||
}
|
||||
return CharacterInfo.Hairs.ElementAtOrDefault(HairWithHatIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public ContentXElement BeardElement
|
||||
{
|
||||
get
|
||||
{
|
||||
if (CharacterInfo.Beards == null) { return null; }
|
||||
if (BeardIndex >= CharacterInfo.Beards.Count)
|
||||
{
|
||||
DebugConsole.AddWarning($"Beard index out of range (character: {CharacterInfo?.Name ?? "null"}, index: {BeardIndex})");
|
||||
}
|
||||
return CharacterInfo.Beards.ElementAtOrDefault(BeardIndex);
|
||||
}
|
||||
}
|
||||
public ContentXElement MoustacheElement
|
||||
{
|
||||
get
|
||||
{
|
||||
if (CharacterInfo.Moustaches == null) { return null; }
|
||||
if (MoustacheIndex >= CharacterInfo.Moustaches.Count)
|
||||
{
|
||||
DebugConsole.AddWarning($"Moustache index out of range (character: {CharacterInfo?.Name ?? "null"}, index: {MoustacheIndex})");
|
||||
}
|
||||
return CharacterInfo.Moustaches.ElementAtOrDefault(MoustacheIndex);
|
||||
}
|
||||
}
|
||||
public ContentXElement FaceAttachment
|
||||
{
|
||||
get
|
||||
{
|
||||
if (CharacterInfo.FaceAttachments == null) { return null; }
|
||||
if (FaceAttachmentIndex >= CharacterInfo.FaceAttachments.Count)
|
||||
{
|
||||
DebugConsole.AddWarning($"Face attachment index out of range (character: {CharacterInfo?.Name ?? "null"}, index: {FaceAttachmentIndex})");
|
||||
}
|
||||
return CharacterInfo.FaceAttachments.ElementAtOrDefault(FaceAttachmentIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public HeadInfo(CharacterInfo characterInfo, HeadPreset headPreset, int hairIndex = 0, int beardIndex = 0, int moustacheIndex = 0, int faceAttachmentIndex = 0)
|
||||
{
|
||||
@@ -130,6 +186,10 @@ namespace Barotrauma
|
||||
head = value;
|
||||
HeadSprite = null;
|
||||
AttachmentSprites = null;
|
||||
hairs = null;
|
||||
beards = null;
|
||||
moustaches = null;
|
||||
faceAttachments = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -252,12 +312,11 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Endocrine boosters can unlock talents outside the user's talent tree. This method is used to specifically get them
|
||||
/// Returns unlocked talents that aren't part of the character's talent tree (which can be unlocked e.g. with an endocrine booster)
|
||||
/// </summary>
|
||||
public IEnumerable<Identifier> GetEndocrineTalents()
|
||||
public IEnumerable<Identifier> GetUnlockedTalentsOutsideTree()
|
||||
{
|
||||
if (!TalentTree.JobTalentTrees.TryGet(Job.Prefab.Identifier, out TalentTree talentTree)) { return Enumerable.Empty<Identifier>(); }
|
||||
|
||||
return UnlockedTalents.Where(t => !talentTree.TalentIsInTree(t));
|
||||
}
|
||||
|
||||
@@ -297,7 +356,10 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public bool OmitJobInPortraitClothing;
|
||||
/// <summary>
|
||||
/// Can be used to disable displaying the job in any info panels
|
||||
/// </summary>
|
||||
public bool OmitJobInMenus;
|
||||
|
||||
private Sprite portrait;
|
||||
public Sprite Portrait
|
||||
@@ -375,7 +437,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (attachmentSprites == null)
|
||||
{
|
||||
LoadAttachmentSprites(OmitJobInPortraitClothing);
|
||||
LoadAttachmentSprites();
|
||||
}
|
||||
return attachmentSprites;
|
||||
}
|
||||
@@ -844,7 +906,14 @@ namespace Barotrauma
|
||||
public void RecreateHead(ImmutableHashSet<Identifier> tags, int hairIndex, int beardIndex, int moustacheIndex, int faceAttachmentIndex)
|
||||
{
|
||||
HeadPreset headPreset = Prefab.Heads.FirstOrDefault(h => h.TagSet.SetEquals(tags));
|
||||
if (headPreset == null) { headPreset = Prefab.Heads.GetRandomUnsynced(); }
|
||||
if (headPreset == null)
|
||||
{
|
||||
if (tags.Count == 1)
|
||||
{
|
||||
headPreset = Prefab.Heads.FirstOrDefault(h => h.TagSet.Contains(tags.First()));
|
||||
}
|
||||
headPreset ??= Prefab.Heads.GetRandomUnsynced();
|
||||
}
|
||||
head = new HeadInfo(this, headPreset, hairIndex, beardIndex, moustacheIndex, faceAttachmentIndex);
|
||||
ReloadHeadAttachments();
|
||||
}
|
||||
@@ -1026,7 +1095,7 @@ namespace Barotrauma
|
||||
|
||||
private static IEnumerable<float> GetWeights(IEnumerable<ContentXElement> elements) => elements.Select(h => h.GetAttributeFloat("commonness", 1f));
|
||||
|
||||
partial void LoadAttachmentSprites(bool omitJob);
|
||||
partial void LoadAttachmentSprites();
|
||||
|
||||
private int CalculateSalary()
|
||||
{
|
||||
@@ -1182,7 +1251,7 @@ namespace Barotrauma
|
||||
// Replace the name tag of any existing id cards or duffel bags
|
||||
foreach (var item in Item.ItemList)
|
||||
{
|
||||
if (item.Prefab.Identifier != "idcard" && !item.Tags.Contains("despawncontainer")) { continue; }
|
||||
if (!item.HasTag("identitycard") && !item.HasTag("despawncontainer")) { continue; }
|
||||
foreach (var tag in item.Tags.Split(','))
|
||||
{
|
||||
var splitTag = tag.Split(":");
|
||||
|
||||
+6
-2
@@ -35,6 +35,7 @@ namespace Barotrauma
|
||||
if (newValue > _strength)
|
||||
{
|
||||
PendingAdditionStrength = Prefab.GrainBurst;
|
||||
Duration = Prefab.Duration;
|
||||
}
|
||||
_strength = newValue;
|
||||
}
|
||||
@@ -60,6 +61,8 @@ namespace Barotrauma
|
||||
|
||||
public double AppliedAsSuccessfulTreatmentTime, AppliedAsFailedTreatmentTime;
|
||||
|
||||
public float Duration;
|
||||
|
||||
/// <summary>
|
||||
/// Which character gave this affliction
|
||||
/// </summary>
|
||||
@@ -75,6 +78,8 @@ namespace Barotrauma
|
||||
_strength = strength;
|
||||
Identifier = prefab.Identifier;
|
||||
|
||||
Duration = prefab.Duration;
|
||||
|
||||
foreach (var periodicEffect in prefab.PeriodicEffects)
|
||||
{
|
||||
PeriodicEffectTimers[periodicEffect] = Rand.Range(periodicEffect.MinInterval, periodicEffect.MaxInterval);
|
||||
@@ -315,8 +320,7 @@ namespace Barotrauma
|
||||
public bool HasFlag(AbilityFlags flagType)
|
||||
{
|
||||
if (!(GetViableEffect() is AfflictionPrefab.Effect currentEffect)) { return false; }
|
||||
|
||||
return currentEffect.AfflictionAbilityFlags.Contains(flagType);
|
||||
return currentEffect.AfflictionAbilityFlags.HasFlag(flagType);
|
||||
}
|
||||
|
||||
private AfflictionPrefab.Effect GetViableEffect()
|
||||
|
||||
+12
-4
@@ -235,7 +235,7 @@ namespace Barotrauma
|
||||
public Identifier[] BlockTransformation { get; private set; }
|
||||
|
||||
public readonly Dictionary<StatTypes, (float minValue, float maxValue)> AfflictionStatValues = new Dictionary<StatTypes, (float minValue, float maxValue)>();
|
||||
public readonly HashSet<AbilityFlags> AfflictionAbilityFlags = new HashSet<AbilityFlags>();
|
||||
public AbilityFlags AfflictionAbilityFlags;
|
||||
|
||||
//statuseffects applied on the character when the affliction is active
|
||||
public readonly List<StatusEffect> StatusEffects = new List<StatusEffect>();
|
||||
@@ -265,7 +265,7 @@ namespace Barotrauma
|
||||
break;
|
||||
case "abilityflag":
|
||||
var flagType = CharacterAbilityGroup.ParseFlagType(subElement.GetAttributeString("flagtype", ""), parentDebugName);
|
||||
AfflictionAbilityFlags.Add(flagType);
|
||||
AfflictionAbilityFlags |= flagType;
|
||||
break;
|
||||
case "affliction":
|
||||
DebugConsole.AddWarning($"Error in affliction \"{parentDebugName}\" - additional afflictions caused by the affliction should be configured inside status effects.");
|
||||
@@ -300,6 +300,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
public static AfflictionPrefab InternalDamage => Prefabs["internaldamage"];
|
||||
public static AfflictionPrefab BiteWounds => Prefabs["bitewounds"];
|
||||
public static AfflictionPrefab ImpactDamage => Prefabs["blunttrauma"];
|
||||
public static AfflictionPrefab Bleeding => Prefabs["bleeding"];
|
||||
public static AfflictionPrefab Burn => Prefabs["burn"];
|
||||
@@ -353,6 +354,11 @@ namespace Barotrauma
|
||||
//how strong the affliction needs to be before bots attempt to treat it
|
||||
public readonly float TreatmentThreshold = 5.0f;
|
||||
|
||||
/// <summary>
|
||||
/// The affliction is automatically removed after this time. 0 = unlimited
|
||||
/// </summary>
|
||||
public readonly float Duration;
|
||||
|
||||
//how much karma changes when a player applies this affliction to someone (per strength of the affliction)
|
||||
public float KarmaChangeOnApplied;
|
||||
|
||||
@@ -406,8 +412,10 @@ namespace Barotrauma
|
||||
!IsBuff &&
|
||||
AfflictionType != "geneticmaterialbuff" &&
|
||||
AfflictionType != "geneticmaterialdebuff");
|
||||
HealCostMultiplier = element.GetAttributeFloat(nameof(HealCostMultiplier).ToLowerInvariant(), 1f);
|
||||
BaseHealCost = element.GetAttributeInt(nameof(BaseHealCost).ToLowerInvariant(), 0);
|
||||
HealCostMultiplier = element.GetAttributeFloat(nameof(HealCostMultiplier), 1f);
|
||||
BaseHealCost = element.GetAttributeInt(nameof(BaseHealCost), 0);
|
||||
|
||||
Duration = element.GetAttributeFloat(nameof(Duration), 0.0f);
|
||||
|
||||
if (element.GetAttribute("nameidentifier") != null)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Networking;
|
||||
@@ -125,7 +128,18 @@ namespace Barotrauma
|
||||
|
||||
public float PressureKillDelay { get; private set; } = 5.0f;
|
||||
|
||||
public float Vitality { get; private set; }
|
||||
private float vitality;
|
||||
public float Vitality
|
||||
{
|
||||
get
|
||||
{
|
||||
return Character.IsDead ? minVitality : vitality;
|
||||
}
|
||||
private set
|
||||
{
|
||||
vitality = value;
|
||||
}
|
||||
}
|
||||
|
||||
public float HealthPercentage => MathUtils.Percentage(Vitality, MaxVitality);
|
||||
|
||||
@@ -138,7 +152,7 @@ namespace Barotrauma
|
||||
{
|
||||
max += Character.Info.Job.Prefab.VitalityModifier;
|
||||
}
|
||||
max *= Character.StaticHealthMultiplier;
|
||||
max *= Character.HumanPrefabHealthMultiplier;
|
||||
max *= 1f + Character.GetStatValue(StatTypes.MaximumHealthMultiplier);
|
||||
return max * Character.HealthMultiplier;
|
||||
}
|
||||
@@ -701,6 +715,7 @@ namespace Barotrauma
|
||||
newStrength = Math.Min(existingAffliction.Prefab.MaxStrength, newStrength);
|
||||
if (existingAffliction == stunAffliction) { Character.SetStun(newStrength, true, true); }
|
||||
existingAffliction.Strength = newStrength;
|
||||
existingAffliction.Duration = existingAffliction.Prefab.Duration;
|
||||
if (newAffliction.Source != null) { existingAffliction.Source = newAffliction.Source; }
|
||||
CalculateVitality();
|
||||
if (Vitality <= MinVitality)
|
||||
@@ -737,6 +752,8 @@ namespace Barotrauma
|
||||
AddLimbAffliction(limbHealth: null, newAffliction, allowStacking);
|
||||
}
|
||||
|
||||
partial void UpdateSkinTint();
|
||||
|
||||
partial void UpdateLimbAfflictionOverlays();
|
||||
|
||||
public void Update(float deltaTime)
|
||||
@@ -758,6 +775,15 @@ namespace Barotrauma
|
||||
if (!irremovableAfflictions.Contains(affliction)) { afflictionsToRemove.Add(affliction); }
|
||||
continue;
|
||||
}
|
||||
if (affliction.Prefab.Duration > 0.0f)
|
||||
{
|
||||
affliction.Duration -= deltaTime;
|
||||
if (affliction.Duration <= 0.0f)
|
||||
{
|
||||
afflictionsToRemove.Add(affliction);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
afflictionsToUpdate.Add(kvp);
|
||||
}
|
||||
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictionsToUpdate)
|
||||
@@ -800,7 +826,7 @@ namespace Barotrauma
|
||||
if (!Character.GodMode)
|
||||
{
|
||||
UpdateLimbAfflictionOverlays();
|
||||
UpdateSkinTint();
|
||||
UpdateSkinTint();
|
||||
CalculateVitality();
|
||||
|
||||
if (Vitality <= MinVitality)
|
||||
@@ -810,23 +836,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateSkinTint()
|
||||
{
|
||||
FaceTint = DefaultFaceTint;
|
||||
BodyTint = Color.TransparentBlack;
|
||||
|
||||
if (!(Character?.Params?.Health.ApplyAfflictionColors ?? false)) { return; }
|
||||
|
||||
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
|
||||
{
|
||||
var affliction = kvp.Key;
|
||||
Color faceTint = affliction.GetFaceTint();
|
||||
if (faceTint.A > FaceTint.A) { FaceTint = faceTint; }
|
||||
Color bodyTint = affliction.GetBodyTint();
|
||||
if (bodyTint.A > BodyTint.A) { BodyTint = bodyTint; }
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateDamageReductions(float deltaTime)
|
||||
{
|
||||
float healthRegen = Character.Params.Health.ConstantHealthRegeneration;
|
||||
@@ -917,6 +926,7 @@ namespace Barotrauma
|
||||
if (Unkillable || Character.GodMode) { return; }
|
||||
|
||||
var (type, affliction) = GetCauseOfDeath();
|
||||
UpdateLimbAfflictionOverlays();
|
||||
UpdateSkinTint();
|
||||
Character.Kill(type, affliction);
|
||||
#if CLIENT
|
||||
|
||||
@@ -105,19 +105,13 @@ namespace Barotrauma
|
||||
return spawnPointTags;
|
||||
}
|
||||
|
||||
public JobPrefab GetJobPrefab(Rand.RandSync randSync = Rand.RandSync.Unsynced)
|
||||
public JobPrefab GetJobPrefab(Rand.RandSync randSync = Rand.RandSync.Unsynced, Func<JobPrefab, bool> predicate = null)
|
||||
{
|
||||
return Job != null && Job != "any" ? JobPrefab.Get(Job) : JobPrefab.Random(randSync);
|
||||
return Job != null && Job != "any" ? JobPrefab.Get(Job) : JobPrefab.Random(randSync, predicate);
|
||||
}
|
||||
|
||||
public void InitializeCharacter(Character npc, ISpatialEntity positionToStayIn = null)
|
||||
{
|
||||
npc.AddStaticHealthMultiplier(HealthMultiplier);
|
||||
if (GameMain.NetworkMember != null)
|
||||
{
|
||||
npc.AddStaticHealthMultiplier(HealthMultiplierInMultiplayer);
|
||||
}
|
||||
|
||||
var humanAI = npc.AIController as HumanAIController;
|
||||
if (humanAI != null)
|
||||
{
|
||||
|
||||
@@ -209,11 +209,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (item.Prefab.Identifier == "idcard")
|
||||
{
|
||||
IdCard idCardComponent = item.GetComponent<IdCard>();
|
||||
idCardComponent?.Initialize(spawnPoint, character);
|
||||
}
|
||||
IdCard idCardComponent = item.GetComponent<IdCard>();
|
||||
idCardComponent?.Initialize(spawnPoint, character);
|
||||
|
||||
foreach (WifiComponent wifiComponent in item.GetComponents<WifiComponent>())
|
||||
{
|
||||
|
||||
@@ -79,7 +79,6 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public static IReadOnlyDictionary<Identifier, float> ItemRepairPriorities => _itemRepairPriorities;
|
||||
|
||||
public static ContentXElement NoJobElement;
|
||||
public static JobPrefab Get(string identifier)
|
||||
{
|
||||
if (Prefabs.ContainsKey(identifier))
|
||||
@@ -213,7 +212,7 @@ namespace Barotrauma
|
||||
public SkillPrefab PrimarySkill => Skills?.FirstOrDefault(s => s.IsPrimarySkill);
|
||||
|
||||
public ContentXElement Element { get; private set; }
|
||||
public ContentXElement ClothingElement { get; private set; }
|
||||
|
||||
public int Variants { get; private set; }
|
||||
|
||||
public JobPrefab(ContentXElement element, JobsFile file) : base(file, element.GetAttributeIdentifier("identifier", ""))
|
||||
@@ -288,11 +287,8 @@ namespace Barotrauma
|
||||
Variants = variant;
|
||||
|
||||
Skills.Sort((x,y) => y.LevelRange.Start.CompareTo(x.LevelRange.Start));
|
||||
|
||||
// Disabled on purpose, TODO: remove all references?
|
||||
//ClothingElement = element.GetChildElement("PortraitClothing");
|
||||
}
|
||||
|
||||
public static JobPrefab Random(Rand.RandSync sync) => Prefabs.GetRandom(p => !p.HiddenJob, sync);
|
||||
public static JobPrefab Random(Rand.RandSync sync, Func<JobPrefab, bool> predicate = null) => Prefabs.GetRandom(p => !p.HiddenJob && (predicate == null || predicate(p)), sync);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,7 +331,7 @@ namespace Barotrauma
|
||||
#if CLIENT
|
||||
if (isSevered)
|
||||
{
|
||||
damageOverlayStrength = 100.0f;
|
||||
damageOverlayStrength = 1.0f;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -352,7 +352,7 @@ namespace Barotrauma
|
||||
|
||||
public Vector2 Position
|
||||
{
|
||||
get { return ConvertUnits.ToDisplayUnits(body.SimPosition); }
|
||||
get { return ConvertUnits.ToDisplayUnits(body?.SimPosition ?? Vector2.Zero); }
|
||||
}
|
||||
|
||||
public Vector2 SimPosition
|
||||
@@ -597,18 +597,7 @@ namespace Barotrauma
|
||||
dir = Direction.Right;
|
||||
body = new PhysicsBody(limbParams);
|
||||
type = limbParams.Type;
|
||||
if (limbParams.IgnoreCollisions)
|
||||
{
|
||||
body.CollisionCategories = Category.None;
|
||||
body.CollidesWith = Category.None;
|
||||
IgnoreCollisions = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
//limbs don't collide with each other
|
||||
body.CollisionCategories = Physics.CollisionCharacter;
|
||||
body.CollidesWith = Physics.CollisionAll & ~Physics.CollisionCharacter & ~Physics.CollisionItem & ~Physics.CollisionItemBlocking;
|
||||
}
|
||||
IgnoreCollisions = limbParams.IgnoreCollisions;
|
||||
body.UserData = this;
|
||||
pullJoint = new FixedMouseJoint(body.FarseerBody, ConvertUnits.ToSimUnits(limbParams.PullPos * Scale))
|
||||
{
|
||||
@@ -646,10 +635,9 @@ namespace Barotrauma
|
||||
}
|
||||
attack.DamageRange = ConvertUnits.ToDisplayUnits(attack.DamageRange);
|
||||
}
|
||||
if (!character.VariantOf.IsEmpty)
|
||||
if (character is { VariantOf: { IsEmpty: false } })
|
||||
{
|
||||
var attackElement = CharacterPrefab.Prefabs.TryGet(character.VariantOf, out var basePrefab)
|
||||
? basePrefab.ConfigElement.GetChildElement("attack") : null;
|
||||
var attackElement = character.Params.VariantFile.Root.GetChildElement("attack");
|
||||
if (attackElement != null)
|
||||
{
|
||||
attack.DamageMultiplier = attackElement.GetAttributeFloat("damagemultiplier", 1f);
|
||||
|
||||
@@ -574,7 +574,7 @@ namespace Barotrauma
|
||||
public float AggressionGreed { get; private set; }
|
||||
|
||||
[Serialize(0f, IsPropertySaveable.Yes, description: "If the health drops below this threshold, the character flees. In percentages."), Editable(minValue: 0f, maxValue: 100f)]
|
||||
public float FleeHealthThreshold { get; private set; }
|
||||
public float FleeHealthThreshold { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes, description: "Does the character attack when provoked? When enabled, overrides the predefined targeting state with Attack and increases the priority of it."), Editable()]
|
||||
public bool AttackWhenProvoked { get; private set; }
|
||||
|
||||
+34
-42
@@ -121,7 +121,7 @@ namespace Barotrauma
|
||||
return folder.CleanUpPathCrossPlatform(correctFilenameCase: true);
|
||||
}
|
||||
|
||||
public static T GetDefaultRagdollParams<T>(Identifier speciesName) where T : RagdollParams, new() => GetRagdollParams<T>(speciesName, GetDefaultFileName(speciesName));
|
||||
public static T GetDefaultRagdollParams<T>(Identifier speciesName) where T : RagdollParams, new() => GetRagdollParams<T>(speciesName);
|
||||
|
||||
/// <summary>
|
||||
/// If the file name is left null, default file is selected. If fails, will select the default file. Note: Use the filename without the extensions, don't use the full path!
|
||||
@@ -138,61 +138,53 @@ namespace Barotrauma
|
||||
ragdolls = new Dictionary<string, RagdollParams>();
|
||||
allRagdolls.Add(speciesName, ragdolls);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(fileName) && ragdolls.TryGetValue(fileName, out RagdollParams ragdoll))
|
||||
{
|
||||
return (T)ragdoll;
|
||||
}
|
||||
|
||||
string selectedFile = null;
|
||||
|
||||
void tryFolderForSpecies(Identifier species, out string err)
|
||||
Identifier ragdollSpecies = speciesName;
|
||||
if (CharacterPrefab.Prefabs.TryGet(speciesName, out var prefab))
|
||||
{
|
||||
err = null;
|
||||
string folder = GetFolder(species);
|
||||
if (!prefab.VariantOf.IsEmpty)
|
||||
{
|
||||
ragdollSpecies = prefab.VariantOf;
|
||||
}
|
||||
string error = null;
|
||||
string folder = GetFolder(ragdollSpecies);
|
||||
if (!Directory.Exists(folder))
|
||||
{
|
||||
err = $"[RagdollParams] Invalid directory: {folder}. Using the default ragdoll.";
|
||||
selectedFile = GetDefaultFile(species);
|
||||
return;
|
||||
}
|
||||
|
||||
string[] files = Directory.GetFiles(folder);
|
||||
if (files.None())
|
||||
{
|
||||
err = $"[RagdollParams] Could not find any ragdoll files from the folder: {folder}. Using the default ragdoll.";
|
||||
selectedFile = GetDefaultFile(species);
|
||||
}
|
||||
else if (string.IsNullOrEmpty(fileName))
|
||||
{
|
||||
// Files found, but none specified
|
||||
selectedFile = GetDefaultFile(species);
|
||||
error = $"[RagdollParams] Invalid directory: {folder}. Using the default ragdoll.";
|
||||
selectedFile = GetDefaultFile(ragdollSpecies);
|
||||
}
|
||||
else
|
||||
{
|
||||
selectedFile = files.FirstOrDefault(f => IO.Path.GetFileNameWithoutExtension(f).Equals(fileName, StringComparison.OrdinalIgnoreCase));
|
||||
if (selectedFile == null)
|
||||
string[] files = Directory.GetFiles(folder);
|
||||
if (files.None())
|
||||
{
|
||||
err = $"[RagdollParams] Could not find a ragdoll file that matches the name {fileName}. Using the default ragdoll.";
|
||||
selectedFile = GetDefaultFile(species);
|
||||
error = $"[RagdollParams] Could not find any ragdoll files from the folder: {folder}. Using the default ragdoll.";
|
||||
selectedFile = GetDefaultFile(ragdollSpecies);
|
||||
}
|
||||
else if (string.IsNullOrEmpty(fileName))
|
||||
{
|
||||
// Files found, but none specified
|
||||
selectedFile = GetDefaultFile(ragdollSpecies);
|
||||
}
|
||||
else
|
||||
{
|
||||
selectedFile = files.FirstOrDefault(f => IO.Path.GetFileNameWithoutExtension(f).Equals(fileName, StringComparison.OrdinalIgnoreCase));
|
||||
if (selectedFile == null)
|
||||
{
|
||||
error = $"[RagdollParams] Could not find a ragdoll file that matches the name {fileName}. Using the default ragdoll.";
|
||||
selectedFile = GetDefaultFile(ragdollSpecies);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (error != null)
|
||||
{
|
||||
DebugConsole.ThrowError(error);
|
||||
}
|
||||
}
|
||||
|
||||
tryFolderForSpecies(speciesName, out var error);
|
||||
Identifier parentSpeciesName = CharacterPrefab.Prefabs.TryGet(speciesName, out var prefab)
|
||||
? prefab.VariantOf
|
||||
: Identifier.Empty;
|
||||
if (!error.IsNullOrEmpty() && !parentSpeciesName.IsEmpty)
|
||||
{
|
||||
tryFolderForSpecies(parentSpeciesName, out error);
|
||||
}
|
||||
|
||||
if (!error.IsNullOrEmpty())
|
||||
{
|
||||
DebugConsole.ThrowError(error);
|
||||
}
|
||||
|
||||
if (selectedFile == null)
|
||||
{
|
||||
throw new Exception("[RagdollParams] Selected file null!");
|
||||
@@ -200,7 +192,7 @@ namespace Barotrauma
|
||||
DebugConsole.Log($"[RagdollParams] Loading ragdoll from {selectedFile}.");
|
||||
var characterPrefab = CharacterPrefab.Prefabs[speciesName];
|
||||
T r = new T();
|
||||
if (r.Load(ContentPath.FromRaw(characterPrefab.ContentPackage, selectedFile), speciesName))
|
||||
if (r.Load(ContentPath.FromRaw(characterPrefab.ContentPackage, selectedFile), ragdollSpecies))
|
||||
{
|
||||
if (!ragdolls.ContainsKey(r.Name))
|
||||
{
|
||||
|
||||
-1
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
|
||||
+1
-3
@@ -1,6 +1,4 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
abstract class AbilityConditionDataless : AbilityCondition
|
||||
{
|
||||
|
||||
-3
@@ -1,8 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
|
||||
-1
@@ -1,5 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
|
||||
+2
-6
@@ -1,7 +1,4 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
@@ -19,10 +16,9 @@ namespace Barotrauma.Abilities
|
||||
|
||||
protected override void ApplyEffect()
|
||||
{
|
||||
IEnumerable<Character> chosenCharacters = Character.GetFriendlyCrew(Character).Where(c => allowSelf || c != Character);
|
||||
|
||||
foreach (Character character in chosenCharacters)
|
||||
foreach (Character character in Character.GetFriendlyCrew(Character))
|
||||
{
|
||||
if (!allowSelf && character == Character) { continue; }
|
||||
if (maxDistance < float.MaxValue)
|
||||
{
|
||||
if (Vector2.DistanceSquared(character.WorldPosition, Character.WorldPosition) > maxDistance * maxDistance) { continue; }
|
||||
|
||||
+1
-4
@@ -1,7 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityApplyStatusEffectsToAttacker : CharacterAbilityApplyStatusEffects
|
||||
{
|
||||
|
||||
+1
-3
@@ -1,6 +1,4 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityApplyStatusEffectsToLastOrderedCharacter : CharacterAbilityApplyStatusEffects
|
||||
{
|
||||
|
||||
-1
@@ -1,6 +1,5 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
|
||||
-1
@@ -2,7 +2,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
|
||||
+9
-8
@@ -1,10 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityGroupEffect : CharacterAbilityGroup
|
||||
{
|
||||
@@ -30,7 +24,14 @@ namespace Barotrauma.Abilities
|
||||
private bool IsApplicable(AbilityObject abilityObject)
|
||||
{
|
||||
if (timesTriggered >= maxTriggerCount) { return false; }
|
||||
return abilityConditions.All(c => c.MatchesCondition(abilityObject));
|
||||
foreach (var abilityCondition in abilityConditions)
|
||||
{
|
||||
if (!abilityCondition.MatchesCondition(abilityObject))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+9
-8
@@ -1,10 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityGroupInterval : CharacterAbilityGroup
|
||||
{
|
||||
@@ -49,7 +43,14 @@ namespace Barotrauma.Abilities
|
||||
private bool IsApplicable()
|
||||
{
|
||||
if (timesTriggered >= maxTriggerCount) { return false; }
|
||||
return abilityConditions.All(c => c.MatchesCondition());
|
||||
foreach (var abilityCondition in abilityConditions)
|
||||
{
|
||||
if (!abilityCondition.MatchesCondition())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using System;
|
||||
using Barotrauma.Abilities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Abilities;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ namespace Barotrauma
|
||||
|
||||
protected override bool MatchesSingular(Identifier identifier) => identifier == "ballastflorabehavior";
|
||||
protected override bool MatchesPlural(Identifier identifier) => identifier == "ballastflorabehaviors";
|
||||
protected override PrefabCollection<BallastFloraPrefab> prefabs => BallastFloraPrefab.Prefabs;
|
||||
protected override PrefabCollection<BallastFloraPrefab> Prefabs => BallastFloraPrefab.Prefabs;
|
||||
|
||||
protected override BallastFloraPrefab CreatePrefab(ContentXElement element)
|
||||
{
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ namespace Barotrauma
|
||||
|
||||
protected override bool MatchesSingular(Identifier identifier) => identifier == "cave";
|
||||
protected override bool MatchesPlural(Identifier identifier) => identifier == "cavegenerationparameters";
|
||||
protected override PrefabCollection<CaveGenerationParams> prefabs => CaveGenerationParams.CaveParams;
|
||||
protected override PrefabCollection<CaveGenerationParams> Prefabs => CaveGenerationParams.CaveParams;
|
||||
protected override CaveGenerationParams CreatePrefab(ContentXElement element)
|
||||
{
|
||||
return new CaveGenerationParams(element, this);
|
||||
|
||||
+1
-1
@@ -77,7 +77,7 @@ namespace Barotrauma
|
||||
{
|
||||
HashSet<string> texturePaths = new HashSet<string>
|
||||
{
|
||||
ragdollParams.Texture
|
||||
ContentPath.FromRaw(CharacterPrefab.Prefabs[speciesName].ContentPackage, ragdollParams.Texture).Value
|
||||
};
|
||||
foreach (RagdollParams.LimbParams limb in ragdollParams.Limbs)
|
||||
{
|
||||
|
||||
+24
-8
@@ -2,12 +2,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using Barotrauma.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -69,10 +67,10 @@ namespace Barotrauma
|
||||
.ToImmutableHashSet();
|
||||
}
|
||||
|
||||
public static Result<ContentFile, string> CreateFromXElement(ContentPackage contentPackage, XElement element)
|
||||
public static Result<ContentFile, LoadError> CreateFromXElement(ContentPackage contentPackage, XElement element)
|
||||
{
|
||||
static Result<ContentFile, string> fail(string error, string? stackTrace = null)
|
||||
=> Result<ContentFile, string>.Failure(error, stackTrace);
|
||||
static Result<ContentFile, LoadError> fail(string error, Exception? exception = null)
|
||||
=> Result<ContentFile, LoadError>.Failure(new LoadError(error, exception));
|
||||
|
||||
Identifier elemName = element.NameAsIdentifier();
|
||||
var type = Types.FirstOrDefault(t => t.Names.Contains(elemName));
|
||||
@@ -95,11 +93,11 @@ namespace Barotrauma
|
||||
var file = type.CreateInstance(contentPackage, filePath);
|
||||
return file is null
|
||||
? throw new Exception($"Content type is not implemented correctly")
|
||||
: Result<ContentFile, string>.Success(file);
|
||||
: Result<ContentFile, LoadError>.Success(file);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return fail($"Failed to load file \"{filePath}\" of type \"{elemName}\": {e.Message}", e.StackTrace.CleanupStackTrace());
|
||||
return fail($"Failed to load file \"{filePath}\" of type \"{elemName}\": {e.Message}", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,5 +123,23 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
public bool NotSyncedInMultiplayer => Types.Any(t => t.Type == GetType() && t.NotSyncedInMultiplayer);
|
||||
|
||||
public readonly struct LoadError
|
||||
{
|
||||
public readonly string Message;
|
||||
public readonly Exception? Exception;
|
||||
|
||||
public LoadError(string message, Exception? exception)
|
||||
{
|
||||
Message = message;
|
||||
Exception = exception;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> Message
|
||||
+ (Exception is { StackTrace: var stackTrace }
|
||||
? '\n' + stackTrace.CleanupStackTrace()
|
||||
: string.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -9,7 +9,7 @@ namespace Barotrauma
|
||||
|
||||
protected override bool MatchesSingular(Identifier identifier) => identifier == "corpse";
|
||||
protected override bool MatchesPlural(Identifier identifier) => identifier == "corpses";
|
||||
protected override PrefabCollection<CorpsePrefab> prefabs => CorpsePrefab.Prefabs;
|
||||
protected override PrefabCollection<CorpsePrefab> Prefabs => CorpsePrefab.Prefabs;
|
||||
protected override CorpsePrefab CreatePrefab(ContentXElement element)
|
||||
{
|
||||
return new CorpsePrefab(element, this);
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ namespace Barotrauma
|
||||
|
||||
protected override bool MatchesSingular(Identifier identifier) => !MatchesPlural(identifier);
|
||||
protected override bool MatchesPlural(Identifier identifier) => identifier == "EventManagerSettings";
|
||||
protected override PrefabCollection<EventManagerSettings> prefabs => EventManagerSettings.Prefabs;
|
||||
protected override PrefabCollection<EventManagerSettings> Prefabs => EventManagerSettings.Prefabs;
|
||||
protected override EventManagerSettings CreatePrefab(ContentXElement element)
|
||||
{
|
||||
return new EventManagerSettings(element, this);
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ namespace Barotrauma
|
||||
|
||||
protected override bool MatchesSingular(Identifier identifier) => identifier == "faction";
|
||||
protected override bool MatchesPlural(Identifier identifier) => identifier == "factions";
|
||||
protected override PrefabCollection<FactionPrefab> prefabs => FactionPrefab.Prefabs;
|
||||
protected override PrefabCollection<FactionPrefab> Prefabs => FactionPrefab.Prefabs;
|
||||
protected override FactionPrefab CreatePrefab(ContentXElement element)
|
||||
{
|
||||
return new FactionPrefab(element, this);
|
||||
|
||||
+6
-6
@@ -9,7 +9,7 @@ namespace Barotrauma
|
||||
|
||||
protected abstract bool MatchesSingular(Identifier identifier);
|
||||
protected abstract bool MatchesPlural(Identifier identifier);
|
||||
protected abstract PrefabCollection<T> prefabs { get; }
|
||||
protected abstract PrefabCollection<T> Prefabs { get; }
|
||||
protected abstract T CreatePrefab(ContentXElement element);
|
||||
|
||||
private void LoadFromXElement(ContentXElement parentElement, bool overriding)
|
||||
@@ -29,14 +29,14 @@ namespace Barotrauma
|
||||
}
|
||||
else if (elemName == "clear")
|
||||
{
|
||||
prefabs.AddOverrideFile(this);
|
||||
Prefabs.AddOverrideFile(this);
|
||||
}
|
||||
else if (MatchesSingular(elemName))
|
||||
{
|
||||
T prefab = CreatePrefab(parentElement);
|
||||
try
|
||||
{
|
||||
prefabs.Add(prefab, overriding);
|
||||
Prefabs.Add(prefab, overriding);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -53,7 +53,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Invalid {GetType().Name} element: {parentElement.Name} in {Path}");
|
||||
DebugConsole.ThrowError($"GenericPrefabFile: Invalid {GetType().Name} element: {parentElement.Name} in {Path}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,12 +68,12 @@ namespace Barotrauma
|
||||
|
||||
public override sealed void UnloadFile()
|
||||
{
|
||||
prefabs.RemoveByFile(this);
|
||||
Prefabs.RemoveByFile(this);
|
||||
}
|
||||
|
||||
public sealed override void Sort()
|
||||
{
|
||||
prefabs.SortAll();
|
||||
Prefabs.SortAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -9,7 +9,7 @@ namespace Barotrauma
|
||||
|
||||
protected override bool MatchesSingular(Identifier identifier) => identifier == "itemassembly";
|
||||
protected override bool MatchesPlural(Identifier identifier) => identifier == "itemassemblies";
|
||||
protected override PrefabCollection<ItemAssemblyPrefab> prefabs => ItemAssemblyPrefab.Prefabs;
|
||||
protected override PrefabCollection<ItemAssemblyPrefab> Prefabs => ItemAssemblyPrefab.Prefabs;
|
||||
protected override ItemAssemblyPrefab CreatePrefab(ContentXElement element)
|
||||
{
|
||||
return new ItemAssemblyPrefab(element, this);
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Barotrauma
|
||||
|
||||
protected override bool MatchesSingular(Identifier identifier) => !MatchesPlural(identifier);
|
||||
protected override bool MatchesPlural(Identifier identifier) => identifier == "items";
|
||||
protected override PrefabCollection<ItemPrefab> prefabs => ItemPrefab.Prefabs;
|
||||
protected override PrefabCollection<ItemPrefab> Prefabs => ItemPrefab.Prefabs;
|
||||
protected override ItemPrefab CreatePrefab(ContentXElement element)
|
||||
{
|
||||
return new ItemPrefab(element, this);
|
||||
|
||||
@@ -22,11 +22,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (var element in mainElement.Elements())
|
||||
{
|
||||
if (element.NameAsIdentifier() == "nojob")
|
||||
{
|
||||
JobPrefab.NoJobElement ??= element;
|
||||
}
|
||||
else if (element.NameAsIdentifier() == "ItemRepairPriorities")
|
||||
if (element.NameAsIdentifier() == "ItemRepairPriorities")
|
||||
{
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ namespace Barotrauma
|
||||
|
||||
protected override bool MatchesSingular(Identifier identifier) => !MatchesPlural(identifier);
|
||||
protected override bool MatchesPlural(Identifier identifier) => identifier == "levelobjects";
|
||||
protected override PrefabCollection<LevelObjectPrefab> prefabs => LevelObjectPrefab.Prefabs;
|
||||
protected override PrefabCollection<LevelObjectPrefab> Prefabs => LevelObjectPrefab.Prefabs;
|
||||
protected override LevelObjectPrefab CreatePrefab(ContentXElement element)
|
||||
{
|
||||
return new LevelObjectPrefab(element, this);
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ namespace Barotrauma
|
||||
|
||||
protected override bool MatchesSingular(Identifier identifier) => !MatchesPlural(identifier);
|
||||
protected override bool MatchesPlural(Identifier identifier) => identifier == "locationtypes";
|
||||
protected override PrefabCollection<LocationType> prefabs => LocationType.Prefabs;
|
||||
protected override PrefabCollection<LocationType> Prefabs => LocationType.Prefabs;
|
||||
protected override LocationType CreatePrefab(ContentXElement element)
|
||||
{
|
||||
return new LocationType(element, this);
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ namespace Barotrauma
|
||||
/*missionTypes.Any(t => identifier == t.Name)
|
||||
|| identifier == "OutpostDestroyMission" || identifier == "OutpostRescueMission";*/
|
||||
protected override bool MatchesPlural(Identifier identifier) => identifier == "missions";
|
||||
protected override PrefabCollection<MissionPrefab> prefabs => MissionPrefab.Prefabs;
|
||||
protected override PrefabCollection<MissionPrefab> Prefabs => MissionPrefab.Prefabs;
|
||||
protected override MissionPrefab CreatePrefab(ContentXElement element)
|
||||
{
|
||||
return new MissionPrefab(element, this);
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ namespace Barotrauma
|
||||
|
||||
protected override bool MatchesSingular(Identifier identifier) => identifier == "npcset";
|
||||
protected override bool MatchesPlural(Identifier identifier) => identifier == "npcsets";
|
||||
protected override PrefabCollection<NPCSet> prefabs => NPCSet.Sets;
|
||||
protected override PrefabCollection<NPCSet> Prefabs => NPCSet.Sets;
|
||||
protected override NPCSet CreatePrefab(ContentXElement element)
|
||||
{
|
||||
return new NPCSet(element, this);
|
||||
|
||||
+1
-1
@@ -42,7 +42,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Invalid {GetType().Name} element: {parentElement.Name} in {Path}");
|
||||
DebugConsole.ThrowError($"OrdersFile: Invalid {GetType().Name} element: {parentElement.Name} in {Path}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ namespace Barotrauma
|
||||
|
||||
protected override bool MatchesSingular(Identifier identifier) => identifier == "OutpostConfig";
|
||||
protected override bool MatchesPlural(Identifier identifier) => identifier == "OutpostGenerationParameters";
|
||||
protected override PrefabCollection<OutpostGenerationParams> prefabs => OutpostGenerationParams.OutpostParams;
|
||||
protected override PrefabCollection<OutpostGenerationParams> Prefabs => OutpostGenerationParams.OutpostParams;
|
||||
protected override OutpostGenerationParams CreatePrefab(ContentXElement element)
|
||||
{
|
||||
return new OutpostGenerationParams(element, this);
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ namespace Barotrauma
|
||||
|
||||
protected override bool MatchesSingular(Identifier identifier) => !MatchesPlural(identifier);
|
||||
protected override bool MatchesPlural(Identifier identifier) => identifier == "prefabs" || identifier == "particles";
|
||||
protected override PrefabCollection<ParticlePrefab> prefabs => ParticlePrefab.Prefabs;
|
||||
protected override PrefabCollection<ParticlePrefab> Prefabs => ParticlePrefab.Prefabs;
|
||||
protected override ParticlePrefab CreatePrefab(ContentXElement element)
|
||||
{
|
||||
return new ParticlePrefab(element, this);
|
||||
|
||||
+1
-1
@@ -57,7 +57,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Invalid {GetType().Name} element: {parentElement.Name} in {Path}");
|
||||
DebugConsole.ThrowError($"RandomEventsFile: Invalid {GetType().Name} element: {parentElement.Name} in {Path}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ namespace Barotrauma
|
||||
|
||||
protected override bool MatchesSingular(Identifier identifier) => identifier == "RuinConfig";
|
||||
protected override bool MatchesPlural(Identifier identifier) => identifier == "RuinGenerationParameters";
|
||||
protected override PrefabCollection<RuinGenerationParams> prefabs => RuinGenerationParams.RuinParams;
|
||||
protected override PrefabCollection<RuinGenerationParams> Prefabs => RuinGenerationParams.RuinParams;
|
||||
protected override RuinGenerationParams CreatePrefab(ContentXElement element)
|
||||
{
|
||||
return new RuinGenerationParams(element, this);
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ namespace Barotrauma
|
||||
{
|
||||
public SoundsFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
|
||||
|
||||
protected override PrefabCollection<SoundPrefab> prefabs => SoundPrefab.Prefabs;
|
||||
protected override PrefabCollection<SoundPrefab> Prefabs => SoundPrefab.Prefabs;
|
||||
|
||||
protected override SoundPrefab CreatePrefab(ContentXElement element)
|
||||
{
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
namespace Barotrauma
|
||||
{
|
||||
sealed class StartItemsFile : GenericPrefabFile<StartItemSet>
|
||||
{
|
||||
public StartItemsFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { }
|
||||
|
||||
protected override bool MatchesSingular(Identifier identifier) => identifier == "itemset";
|
||||
protected override bool MatchesPlural(Identifier identifier) => identifier == "startitems";
|
||||
protected override PrefabCollection<StartItemSet> Prefabs => StartItemSet.Sets;
|
||||
protected override StartItemSet CreatePrefab(ContentXElement element) => new StartItemSet(element, this);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -9,7 +9,7 @@ namespace Barotrauma
|
||||
|
||||
protected override bool MatchesSingular(Identifier identifier) => !MatchesPlural(identifier);
|
||||
protected override bool MatchesPlural(Identifier identifier) => identifier == "prefabs" || identifier == "structures";
|
||||
protected override PrefabCollection<StructurePrefab> prefabs => StructurePrefab.Prefabs;
|
||||
protected override PrefabCollection<StructurePrefab> Prefabs => StructurePrefab.Prefabs;
|
||||
protected override StructurePrefab CreatePrefab(ContentXElement element)
|
||||
{
|
||||
return new StructurePrefab(element, this);
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ namespace Barotrauma
|
||||
|
||||
protected override bool MatchesSingular(Identifier identifier) => identifier == "talenttree";
|
||||
protected override bool MatchesPlural(Identifier identifier) => identifier == "talenttrees";
|
||||
protected override PrefabCollection<TalentTree> prefabs => TalentTree.JobTalentTrees;
|
||||
protected override PrefabCollection<TalentTree> Prefabs => TalentTree.JobTalentTrees;
|
||||
protected override TalentTree CreatePrefab(ContentXElement element)
|
||||
{
|
||||
return new TalentTree(element, this);
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ namespace Barotrauma
|
||||
|
||||
protected override bool MatchesSingular(Identifier identifier) => identifier == "talent";
|
||||
protected override bool MatchesPlural(Identifier identifier) => identifier == "talents";
|
||||
protected override PrefabCollection<TalentPrefab> prefabs => TalentPrefab.TalentPrefabs;
|
||||
protected override PrefabCollection<TalentPrefab> Prefabs => TalentPrefab.TalentPrefabs;
|
||||
protected override TalentPrefab CreatePrefab(ContentXElement element)
|
||||
{
|
||||
return new TalentPrefab(element, this);
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ namespace Barotrauma
|
||||
|
||||
protected override bool MatchesSingular(Identifier identifier) => identifier == "TraitorMission";
|
||||
protected override bool MatchesPlural(Identifier identifier) => identifier == "TraitorMissions";
|
||||
protected override PrefabCollection<PrefabType> prefabs => PrefabType.Prefabs;
|
||||
protected override PrefabCollection<PrefabType> Prefabs => PrefabType.Prefabs;
|
||||
protected override PrefabType CreatePrefab(ContentXElement element)
|
||||
{
|
||||
return new PrefabType(element, this);
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ namespace Barotrauma
|
||||
protected override bool MatchesPlural(Identifier identifier) =>
|
||||
identifier == "upgrademodules";
|
||||
|
||||
protected override PrefabCollection<UpgradeContentPrefab> prefabs => UpgradeContentPrefab.PrefabsAndCategories;
|
||||
protected override PrefabCollection<UpgradeContentPrefab> Prefabs => UpgradeContentPrefab.PrefabsAndCategories;
|
||||
protected override UpgradeContentPrefab CreatePrefab(ContentXElement element)
|
||||
{
|
||||
Identifier elemName = element.NameAsIdentifier();
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ namespace Barotrauma
|
||||
|
||||
protected override bool MatchesSingular(Identifier identifier) => identifier == "wreckaiconfig";
|
||||
protected override bool MatchesPlural(Identifier identifier) => identifier == "wreckaiconfigs";
|
||||
protected override PrefabCollection<WreckAIConfig> prefabs => WreckAIConfig.Prefabs;
|
||||
protected override PrefabCollection<WreckAIConfig> Prefabs => WreckAIConfig.Prefabs;
|
||||
protected override WreckAIConfig CreatePrefab(ContentXElement element)
|
||||
{
|
||||
return new WreckAIConfig(element, this);
|
||||
|
||||
+55
-35
@@ -14,8 +14,7 @@ namespace Barotrauma
|
||||
{
|
||||
public abstract class ContentPackage
|
||||
{
|
||||
#warning TODO: make this independent of the current version
|
||||
public static readonly Version MinimumHashCompatibleVersion = GameMain.Version;
|
||||
public static readonly Version MinimumHashCompatibleVersion = new Version(0, 18, 3, 0);
|
||||
|
||||
public const string LocalModsDir = "LocalMods";
|
||||
public static readonly string WorkshopModsDir = Barotrauma.IO.Path.Combine(
|
||||
@@ -34,11 +33,11 @@ namespace Barotrauma
|
||||
|
||||
public readonly Version GameVersion;
|
||||
public readonly string ModVersion;
|
||||
public readonly Md5Hash Hash;
|
||||
public Md5Hash Hash { get; private set; }
|
||||
public readonly DateTime? InstallTime;
|
||||
|
||||
public readonly ImmutableArray<ContentFile> Files;
|
||||
public readonly ImmutableArray<(string error, string? stackTrace)> Errors;
|
||||
public ImmutableArray<ContentFile> Files { get; private set; }
|
||||
public ImmutableArray<ContentFile.LoadError> Errors { get; private set; }
|
||||
|
||||
public async Task<bool> IsUpToDate()
|
||||
{
|
||||
@@ -56,7 +55,7 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Does the content package include some content that needs to match between all players in multiplayer.
|
||||
/// </summary>
|
||||
public readonly bool HasMultiplayerSyncedContent;
|
||||
public bool HasMultiplayerSyncedContent { get; private set; }
|
||||
|
||||
protected ContentPackage(XDocument doc, string path)
|
||||
{
|
||||
@@ -85,13 +84,13 @@ namespace Barotrauma
|
||||
.ToArray();
|
||||
|
||||
Files = fileResults
|
||||
.OfType<Success<ContentFile, string>>()
|
||||
.OfType<Success<ContentFile, ContentFile.LoadError>>()
|
||||
.Select(f => f.Value)
|
||||
.ToImmutableArray();
|
||||
|
||||
Errors = fileResults
|
||||
.OfType<Failure<ContentFile, string>>()
|
||||
.Select(f => (f.Error, f.StackTrace))
|
||||
.OfType<Failure<ContentFile, ContentFile.LoadError>>()
|
||||
.Select(f => f.Error)
|
||||
.ToImmutableArray();
|
||||
|
||||
HasMultiplayerSyncedContent = Files.Any(f => !f.NotSyncedInMultiplayer);
|
||||
@@ -128,18 +127,13 @@ namespace Barotrauma
|
||||
|
||||
try
|
||||
{
|
||||
if (doc.Root.GetAttributeBool("corepackage", false))
|
||||
{
|
||||
return new CorePackage(doc, path);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new RegularPackage(doc, path);
|
||||
}
|
||||
return doc.Root.GetAttributeBool("corepackage", false)
|
||||
? (ContentPackage)new CorePackage(doc, path)
|
||||
: new RegularPackage(doc, path);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
while (e.InnerException != null) { e = e.InnerException; }
|
||||
e = e.GetInnermost();
|
||||
DebugConsole.ThrowError($"{e.Message}: {e.StackTrace}");
|
||||
return null;
|
||||
}
|
||||
@@ -279,12 +273,42 @@ namespace Barotrauma
|
||||
Files.ForEach(f => f.UnloadFile());
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
public void ReloadSubsAndItemAssemblies()
|
||||
{
|
||||
byte[] shortHash = Encoding.ASCII.GetBytes(Hash.StringRepresentation.Substring(0, 4));
|
||||
return (shortHash[0] << 24) | (shortHash[1] << 16) | (shortHash[2] << 8) | shortHash[3];
|
||||
XDocument doc = XMLExtensions.TryLoadXml(Path);
|
||||
List<ContentFile> newFileList = new List<ContentFile>();
|
||||
XElement rootElement = doc.Root ?? throw new NullReferenceException("XML document is invalid: root element is null.");
|
||||
|
||||
var fileResults = rootElement.Elements()
|
||||
.Select(e => ContentFile.CreateFromXElement(this, e))
|
||||
.ToArray();
|
||||
|
||||
foreach (var result in fileResults)
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case Success<ContentFile, ContentFile.LoadError> { Value: var file }:
|
||||
if (file is BaseSubFile || file is ItemAssemblyFile)
|
||||
{
|
||||
newFileList.Add(file);
|
||||
}
|
||||
else
|
||||
{
|
||||
var existingFile = Files.FirstOrDefault(f => f.Path == file.Path);
|
||||
newFileList.Add(existingFile ?? file);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
UnloadFilesOfType<BaseSubFile>();
|
||||
UnloadFilesOfType<ItemAssemblyFile>();
|
||||
Files = newFileList.ToImmutableArray();
|
||||
Hash = CalculateHash();
|
||||
LoadFilesOfType<BaseSubFile>();
|
||||
LoadFilesOfType<ItemAssemblyFile>();
|
||||
}
|
||||
|
||||
|
||||
public static bool PathAllowedAsLocalModFile(string path)
|
||||
{
|
||||
#if DEBUG
|
||||
@@ -306,21 +330,17 @@ namespace Barotrauma
|
||||
|
||||
public void LogErrors()
|
||||
{
|
||||
if (Errors.Any())
|
||||
if (!Errors.Any())
|
||||
{
|
||||
DebugConsole.AddWarning(
|
||||
$"The following errors occurred while loading the content package\"{Name}\". The package might not work correctly.\n" +
|
||||
string.Join('\n', Errors.Select(e => errorToStr(e.error, e.stackTrace))));
|
||||
static string errorToStr(string error, string? stackTrace)
|
||||
{
|
||||
string str = error;
|
||||
if (stackTrace != null)
|
||||
{
|
||||
str += '\n' + stackTrace;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
DebugConsole.AddWarning(
|
||||
$"The following errors occurred while loading the content package \"{Name}\". The package might not work correctly.\n" +
|
||||
string.Join('\n', Errors.Select(errorToStr)));
|
||||
|
||||
static string errorToStr(ContentFile.LoadError error)
|
||||
=> error.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -430,9 +430,9 @@ namespace Barotrauma
|
||||
public static void LoadVanillaFileList()
|
||||
{
|
||||
VanillaCorePackage = new CorePackage(XDocument.Load(VanillaFileList), VanillaFileList);
|
||||
foreach ((string error, string? stackTrace) in VanillaCorePackage.Errors)
|
||||
foreach (ContentFile.LoadError error in VanillaCorePackage.Errors)
|
||||
{
|
||||
DebugConsole.ThrowError(error + (stackTrace == null ? string.Empty : '\n' + stackTrace));
|
||||
DebugConsole.ThrowError(error.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,10 @@ namespace Barotrauma
|
||||
.Replace(string.Format(OtherModDirFmt, ContentPackage.SteamWorkshopId.ToString(CultureInfo.InvariantCulture)), modPath, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
var allPackages = ContentPackageManager.EnabledPackages.All;
|
||||
var allPackages = ContentPackageManager.AllPackages;
|
||||
#if CLIENT
|
||||
if (GameMain.ModDownloadScreen?.DownloadedPackages != null) { allPackages = allPackages.Concat(GameMain.ModDownloadScreen.DownloadedPackages); }
|
||||
#endif
|
||||
foreach (Identifier otherModName in otherMods)
|
||||
{
|
||||
if (!UInt64.TryParse(otherModName.Value, out UInt64 workshopId)) { workshopId = 0; }
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace Barotrauma
|
||||
|
||||
public string BaseUri => Element.BaseUri;
|
||||
|
||||
public XDocument Document => Element.Document ?? throw new NullReferenceException("XML element is invalid: document is null.");
|
||||
public XDocument? Document => Element.Document;
|
||||
|
||||
public ContentXElement? FirstElement() => Elements().FirstOrDefault();
|
||||
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ namespace Barotrauma
|
||||
{
|
||||
Message = $"\"{whoAsked?.Name ?? "[NULL]"}\" depends on a package " +
|
||||
$"with name or ID \"{missingPackage ?? "[NULL]"}\" " +
|
||||
$"that is not currently enabled.";
|
||||
$"that is not currently installed.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -781,7 +781,7 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
GameMain.GameSession.EventManager.ActiveEvents.Add(newEvent);
|
||||
newEvent.Init(true);
|
||||
newEvent.Init();
|
||||
NewMessage($"Initialized event {eventPrefab.Identifier}", Color.Aqua);
|
||||
return;
|
||||
}
|
||||
@@ -1123,7 +1123,7 @@ namespace Barotrauma
|
||||
{
|
||||
var gamesession = new GameSession(
|
||||
SubmarineInfo.SavedSubmarines.GetRandomUnsynced(s => s.Type == SubmarineType.Player && !s.HasTag(SubmarineTag.HideInMenus)),
|
||||
GameModePreset.DevSandbox);
|
||||
GameModePreset.DevSandbox ?? GameModePreset.Sandbox);
|
||||
string seed = ToolBox.RandomSeed(16);
|
||||
gamesession.StartRound(seed);
|
||||
|
||||
@@ -1830,6 +1830,17 @@ namespace Barotrauma
|
||||
}));
|
||||
#endif
|
||||
|
||||
commands.Add(new Command("startitems|startitemset", "start item set identifier", (string[] args) =>
|
||||
{
|
||||
if (args.Length == 0)
|
||||
{
|
||||
ThrowError($"No start item set identifier defined!");
|
||||
return;
|
||||
}
|
||||
AutoItemPlacer.DefaultStartItemSet = args[0].ToIdentifier();
|
||||
NewMessage($"Start item set changed to \"{AutoItemPlacer.DefaultStartItemSet}\"");
|
||||
}, isCheat: false));
|
||||
|
||||
//"dummy commands" that only exist so that the server can give clients permissions to use them
|
||||
//TODO: alphabetical order?
|
||||
commands.Add(new Command("control", "control [character name]: Start controlling the specified character (client-only).", null, () =>
|
||||
@@ -2516,5 +2527,15 @@ namespace Barotrauma
|
||||
ThrowError("Saving debug console log to " + filePath + " failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void DeactivateCheats()
|
||||
{
|
||||
#if CLIENT
|
||||
GameMain.DebugDraw = false;
|
||||
GameMain.LightManager.LightingEnabled = true;
|
||||
#endif
|
||||
Hull.EditWater = false;
|
||||
Hull.EditFire = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,13 +33,13 @@ namespace Barotrauma
|
||||
public enum AbilityEffectType
|
||||
{
|
||||
Undefined,
|
||||
None,
|
||||
None,
|
||||
OnAttack,
|
||||
OnAttackResult,
|
||||
OnAttacked,
|
||||
OnAttackedResult,
|
||||
OnGainSkillPoint,
|
||||
OnAllyGainSkillPoint,
|
||||
OnGainSkillPoint,
|
||||
OnAllyGainSkillPoint,
|
||||
OnRepairComplete,
|
||||
OnItemFabricationSkillGain,
|
||||
OnItemFabricatedAmount,
|
||||
@@ -131,21 +131,22 @@ namespace Barotrauma
|
||||
MaxAttachableCount,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum AbilityFlags
|
||||
{
|
||||
None,
|
||||
MustWalk,
|
||||
ImmuneToPressure,
|
||||
IgnoredByEnemyAI,
|
||||
MoveNormallyWhileDragging,
|
||||
CanTinker,
|
||||
CanTinkerFabricatorsAndDeconstructors,
|
||||
TinkeringPowersDevices,
|
||||
GainSkillPastMaximum,
|
||||
RetainExperienceForNewCharacter,
|
||||
AllowSecondOrderedTarget,
|
||||
PowerfulCPR,
|
||||
AlwaysStayConscious,
|
||||
None = 0,
|
||||
MustWalk = 0x1,
|
||||
ImmuneToPressure = 0x2,
|
||||
IgnoredByEnemyAI = 0x4,
|
||||
MoveNormallyWhileDragging = 0x8,
|
||||
CanTinker = 0x10,
|
||||
CanTinkerFabricatorsAndDeconstructors = 0x20,
|
||||
TinkeringPowersDevices = 0x40,
|
||||
GainSkillPastMaximum = 0x80,
|
||||
RetainExperienceForNewCharacter = 0x100,
|
||||
AllowSecondOrderedTarget = 0x200,
|
||||
PowerfulCPR = 0x400,
|
||||
AlwaysStayConscious = 0x800,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
@@ -155,4 +156,32 @@ namespace Barotrauma
|
||||
Player = 0b10,
|
||||
Both = Bot | Player
|
||||
}
|
||||
|
||||
public enum StartingBalanceAmount
|
||||
{
|
||||
Low,
|
||||
Medium,
|
||||
High,
|
||||
}
|
||||
|
||||
public enum GameDifficulty
|
||||
{
|
||||
Easy,
|
||||
Medium,
|
||||
Hard,
|
||||
Hellish
|
||||
}
|
||||
|
||||
public enum NumberType
|
||||
{
|
||||
Int,
|
||||
Float
|
||||
}
|
||||
|
||||
public enum ChatMode
|
||||
{
|
||||
None,
|
||||
Local,
|
||||
Radio
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,8 +53,9 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public override void Init(bool affectSubImmediately)
|
||||
public override void Init(EventSet parentSet)
|
||||
{
|
||||
base.Init(parentSet);
|
||||
spawnPos = Level.Loaded.GetRandomItemPos(
|
||||
(Rand.Value(Rand.RandSync.ServerAndClient) < 0.5f) ?
|
||||
Level.PositionType.MainPath | Level.PositionType.SidePath :
|
||||
@@ -111,7 +112,7 @@ namespace Barotrauma
|
||||
case 1:
|
||||
if (!Submarine.MainSub.AtEndExit && !Submarine.MainSub.AtStartExit) return;
|
||||
|
||||
Finished();
|
||||
Finish();
|
||||
state = 2;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -5,13 +5,16 @@ using System.Collections.Generic;
|
||||
namespace Barotrauma
|
||||
{
|
||||
class Event
|
||||
{
|
||||
{
|
||||
public event Action Finished;
|
||||
protected bool isFinished;
|
||||
|
||||
protected readonly EventPrefab prefab;
|
||||
|
||||
public EventPrefab Prefab => prefab;
|
||||
|
||||
public EventSet ParentSet { get; private set; }
|
||||
|
||||
public Func<Level.InterestingPosition, bool> SpawnPosFilter;
|
||||
|
||||
public bool IsFinished
|
||||
@@ -42,23 +45,20 @@ namespace Barotrauma
|
||||
yield break;
|
||||
}
|
||||
|
||||
public virtual void Init(bool affectSubImmediately)
|
||||
public virtual void Init(EventSet parentSet = null)
|
||||
{
|
||||
ParentSet = parentSet;
|
||||
}
|
||||
|
||||
public virtual void Update(float deltaTime)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void Finished()
|
||||
public virtual void Finish()
|
||||
{
|
||||
isFinished = true;
|
||||
}
|
||||
|
||||
public virtual bool CanAffectSubImmediately(Level level)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
Finished?.Invoke();
|
||||
}
|
||||
|
||||
public virtual bool LevelMeetsRequirements()
|
||||
{
|
||||
|
||||
@@ -313,10 +313,14 @@ namespace Barotrauma
|
||||
bool isValid = e is Character character && !character.Removed && !character.IsDead && !character.IsIncapacitated &&
|
||||
(e == Character.Controlled || character.IsRemotePlayer);
|
||||
#if SERVER
|
||||
UpdateIgnoredClients();
|
||||
isValid &= !ignoredClients.Keys.Any(c => c.Character == e);
|
||||
if (!dialogOpened)
|
||||
{
|
||||
UpdateIgnoredClients();
|
||||
isValid &= !ignoredClients.Keys.Any(c => c.Character == e);
|
||||
}
|
||||
#elif CLIENT
|
||||
isValid &= (e != Character.Controlled || !GUI.InputBlockingMenuOpen);
|
||||
bool block = GUI.InputBlockingMenuOpen && !dialogOpened;
|
||||
isValid &= (e != Character.Controlled || !block);
|
||||
#endif
|
||||
return isValid;
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (campaign is MultiPlayerCampaign mpCampaign)
|
||||
{
|
||||
mpCampaign.LastUpdateID++;
|
||||
mpCampaign.IncrementLastUpdateIdForFlag(MultiPlayerCampaign.NetFlags.MapAndMissions);
|
||||
}
|
||||
if (prefab != null)
|
||||
{
|
||||
|
||||
@@ -64,8 +64,6 @@ namespace Barotrauma
|
||||
campaign.GetWallet(client).Give(Amount);
|
||||
}
|
||||
}
|
||||
|
||||
((MultiPlayerCampaign)campaign).LastUpdateID++;
|
||||
#else
|
||||
campaign.Wallet.Give(Amount);
|
||||
#endif
|
||||
|
||||
@@ -49,6 +49,12 @@ namespace Barotrauma
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier SpawnPointTag { get; set; }
|
||||
|
||||
[Serialize(CharacterTeamType.FriendlyNPC, IsPropertySaveable.Yes)]
|
||||
public CharacterTeamType Team { get; protected set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes, description: "Should we spawn the entity even when no spawn points with matching tags were found?")]
|
||||
public bool RequireSpawnPointTag { get; set; }
|
||||
|
||||
private readonly HashSet<Identifier> targetModuleTags = new HashSet<Identifier>();
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes, "What outpost module tags does the entity prefer to spawn in.")]
|
||||
@@ -79,7 +85,7 @@ namespace Barotrauma
|
||||
|
||||
public SpawnAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
|
||||
{
|
||||
ignoreSpawnPointType = !element.Attributes().Any(a => a.Name.ToString().Equals("spawnpointtype", StringComparison.OrdinalIgnoreCase));
|
||||
ignoreSpawnPointType = element.GetAttribute("spawnpointtype") == null;
|
||||
}
|
||||
|
||||
public override bool IsFinished(ref string goTo)
|
||||
@@ -110,22 +116,40 @@ namespace Barotrauma
|
||||
if (humanPrefab != null)
|
||||
{
|
||||
ISpatialEntity spawnPos = GetSpawnPos();
|
||||
Entity.Spawner.AddCharacterToSpawnQueue(CharacterPrefab.HumanSpeciesName, OffsetSpawnPos(spawnPos?.WorldPosition ?? Vector2.Zero, 100.0f), humanPrefab.GetCharacterInfo(), onSpawn: newCharacter =>
|
||||
if (spawnPos != null)
|
||||
{
|
||||
if (newCharacter == null) { return; }
|
||||
newCharacter.HumanPrefab = humanPrefab;
|
||||
newCharacter.TeamID = CharacterTeamType.FriendlyNPC;
|
||||
newCharacter.EnableDespawn = false;
|
||||
humanPrefab.GiveItems(newCharacter, newCharacter.Submarine);
|
||||
if (LootingIsStealing)
|
||||
Entity.Spawner.AddCharacterToSpawnQueue(CharacterPrefab.HumanSpeciesName, OffsetSpawnPos(spawnPos.WorldPosition, 100.0f), humanPrefab.GetCharacterInfo(), onSpawn: newCharacter =>
|
||||
{
|
||||
foreach (Item item in newCharacter.Inventory.AllItems)
|
||||
if (newCharacter == null) { return; }
|
||||
newCharacter.HumanPrefab = humanPrefab;
|
||||
newCharacter.TeamID = Team;
|
||||
newCharacter.EnableDespawn = false;
|
||||
humanPrefab.GiveItems(newCharacter, newCharacter.Submarine);
|
||||
if (LootingIsStealing)
|
||||
{
|
||||
item.SpawnedInCurrentOutpost = true;
|
||||
item.AllowStealing = false;
|
||||
foreach (Item item in newCharacter.Inventory.AllItems)
|
||||
{
|
||||
item.SpawnedInCurrentOutpost = true;
|
||||
item.AllowStealing = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
humanPrefab.InitializeCharacter(newCharacter, spawnPos);
|
||||
humanPrefab.InitializeCharacter(newCharacter, spawnPos);
|
||||
if (!TargetTag.IsEmpty && newCharacter != null)
|
||||
{
|
||||
ParentEvent.AddTarget(TargetTag, newCharacter);
|
||||
}
|
||||
spawnedEntity = newCharacter;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!SpeciesName.IsEmpty)
|
||||
{
|
||||
ISpatialEntity spawnPos = GetSpawnPos();
|
||||
if (spawnPos != null)
|
||||
{
|
||||
Entity.Spawner.AddCharacterToSpawnQueue(SpeciesName, OffsetSpawnPos(spawnPos.WorldPosition, 100.0f), onSpawn: newCharacter =>
|
||||
{
|
||||
if (!TargetTag.IsEmpty && newCharacter != null)
|
||||
{
|
||||
ParentEvent.AddTarget(TargetTag, newCharacter);
|
||||
@@ -134,20 +158,9 @@ namespace Barotrauma
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (!SpeciesName.IsEmpty)
|
||||
{
|
||||
Entity.Spawner.AddCharacterToSpawnQueue(SpeciesName, OffsetSpawnPos(GetSpawnPos()?.WorldPosition ?? Vector2.Zero, 100.0f), onSpawn: newCharacter =>
|
||||
{
|
||||
if (!TargetTag.IsEmpty && newCharacter != null)
|
||||
{
|
||||
ParentEvent.AddTarget(TargetTag, newCharacter);
|
||||
}
|
||||
spawnedEntity = newCharacter;
|
||||
});
|
||||
}
|
||||
else if (!ItemIdentifier.IsEmpty)
|
||||
{
|
||||
if (!(MapEntityPrefab.Find(null, identifier: ItemIdentifier) is ItemPrefab itemPrefab))
|
||||
if (!(MapEntityPrefab.FindByIdentifier(ItemIdentifier) is ItemPrefab itemPrefab))
|
||||
{
|
||||
DebugConsole.ThrowError("Error in SpawnAction (item prefab \"" + ItemIdentifier + "\" not found)");
|
||||
}
|
||||
@@ -178,7 +191,11 @@ namespace Barotrauma
|
||||
|
||||
if (spawnInventory == null)
|
||||
{
|
||||
Entity.Spawner.AddItemToSpawnQueue(itemPrefab, OffsetSpawnPos(GetSpawnPos()?.WorldPosition ?? Vector2.Zero, 100.0f), onSpawned: onSpawned);
|
||||
ISpatialEntity spawnPos = GetSpawnPos();
|
||||
if (spawnPos != null)
|
||||
{
|
||||
Entity.Spawner.AddItemToSpawnQueue(itemPrefab, OffsetSpawnPos(spawnPos.WorldPosition, 100.0f), onSpawned: onSpawned);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -244,10 +261,10 @@ namespace Barotrauma
|
||||
SpawnType? spawnPointType = null;
|
||||
if (!ignoreSpawnPointType) { spawnPointType = SpawnPointType; }
|
||||
|
||||
return GetSpawnPos(SpawnLocation, spawnPointType, targetModuleTags, SpawnPointTag.ToEnumerable());
|
||||
return GetSpawnPos(SpawnLocation, spawnPointType, targetModuleTags, SpawnPointTag.ToEnumerable(), requireTaggedSpawnPoint: RequireSpawnPointTag);
|
||||
}
|
||||
|
||||
public static WayPoint GetSpawnPos(SpawnLocationType spawnLocation, SpawnType? spawnPointType, IEnumerable<Identifier> moduleFlags = null, IEnumerable<Identifier> spawnpointTags = null, bool asFarAsPossibleFromAirlock = false)
|
||||
public static WayPoint GetSpawnPos(SpawnLocationType spawnLocation, SpawnType? spawnPointType, IEnumerable<Identifier> moduleFlags = null, IEnumerable<Identifier> spawnpointTags = null, bool asFarAsPossibleFromAirlock = false, bool requireTaggedSpawnPoint = false)
|
||||
{
|
||||
List<WayPoint> potentialSpawnPoints = spawnLocation switch
|
||||
{
|
||||
@@ -274,18 +291,24 @@ namespace Barotrauma
|
||||
if (spawnpointTags != null && spawnpointTags.Any())
|
||||
{
|
||||
var spawnPoints = potentialSpawnPoints
|
||||
.Where(wp => spawnpointTags.Any(tag => wp.Tags.Contains(tag)))
|
||||
.Where(wp => wp.ConnectedDoor == null && !wp.isObstructed);
|
||||
.Where(wp => spawnpointTags.Any(tag => wp.Tags.Contains(tag) && wp.ConnectedDoor == null && !wp.isObstructed));
|
||||
|
||||
if (spawnPoints.Any())
|
||||
if (requireTaggedSpawnPoint || spawnPoints.Any())
|
||||
{
|
||||
potentialSpawnPoints = spawnPoints.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
if (potentialSpawnPoints.Count == 0)
|
||||
if (potentialSpawnPoints.None())
|
||||
{
|
||||
DebugConsole.ThrowError($"Could not find a spawn point for a SpawnAction (spawn location: {spawnLocation})");
|
||||
if (requireTaggedSpawnPoint && spawnpointTags != null && spawnpointTags.Any())
|
||||
{
|
||||
DebugConsole.NewMessage($"Could not find a spawn point for a SpawnAction (spawn location: {spawnLocation} (tag: {string.Join(",", spawnpointTags)}), skipping.", color: Color.White);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Could not find a spawn point for a SpawnAction (spawn location: {spawnLocation})");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -307,7 +330,7 @@ namespace Barotrauma
|
||||
validSpawnPoints = validSpawnPoints.Except(airlockSpawnPoints);
|
||||
}
|
||||
|
||||
if (!validSpawnPoints.Any())
|
||||
if (validSpawnPoints.None())
|
||||
{
|
||||
DebugConsole.ThrowError($"Could not find a spawn point of the correct type for a SpawnAction (spawn location: {spawnLocation}, type: {spawnPointType}, module flags: {((moduleFlags == null || !moduleFlags.Any()) ? "none" : string.Join(", ", moduleFlags))})");
|
||||
return potentialSpawnPoints.GetRandomUnsynced();
|
||||
@@ -320,7 +343,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
//if not trying to spawn at a tagged spawnpoint, favor spawnpoints without tags
|
||||
if (spawnpointTags == null || !spawnpointTags.Any())
|
||||
if (spawnpointTags == null || spawnpointTags.None())
|
||||
{
|
||||
var spawnPoints = validSpawnPoints.Where(wp => !wp.Tags.Any());
|
||||
if (spawnPoints.Any())
|
||||
|
||||
@@ -117,6 +117,8 @@ namespace Barotrauma
|
||||
|
||||
public bool Enabled = true;
|
||||
|
||||
private MTRandom rand;
|
||||
|
||||
public void StartRound(Level level)
|
||||
{
|
||||
this.level = level;
|
||||
@@ -147,7 +149,7 @@ namespace Barotrauma
|
||||
seed ^= ToolBox.IdentifierToInt(previousEvent.Identifier);
|
||||
}
|
||||
}
|
||||
MTRandom rand = new MTRandom(seed);
|
||||
rand = new MTRandom(seed);
|
||||
|
||||
EventSet initialEventSet = SelectRandomEvents(EventSet.Prefabs.ToList(), requireCampaignSet: GameMain.GameSession?.GameMode is CampaignMode, rand);
|
||||
EventSet additiveSet = null;
|
||||
@@ -159,12 +161,12 @@ namespace Barotrauma
|
||||
if (initialEventSet != null)
|
||||
{
|
||||
pendingEventSets.Add(initialEventSet);
|
||||
CreateEvents(initialEventSet, rand);
|
||||
CreateEvents(initialEventSet);
|
||||
}
|
||||
if (additiveSet != null)
|
||||
{
|
||||
pendingEventSets.Add(additiveSet);
|
||||
CreateEvents(additiveSet, rand);
|
||||
CreateEvents(additiveSet);
|
||||
}
|
||||
|
||||
if (level?.LevelData?.Type == LevelData.LevelType.Outpost)
|
||||
@@ -183,7 +185,7 @@ namespace Barotrauma
|
||||
if (unlockPathEventPrefab != null)
|
||||
{
|
||||
var newEvent = unlockPathEventPrefab.CreateInstance();
|
||||
newEvent.Init(true);
|
||||
newEvent.Init();
|
||||
ActiveEvents.Add(newEvent);
|
||||
}
|
||||
else
|
||||
@@ -258,9 +260,15 @@ namespace Barotrauma
|
||||
throw new InvalidOperationException("Could not select EventManager settings (level not set).");
|
||||
}
|
||||
|
||||
float extraDifficulty = 0;
|
||||
if (GameMain.GameSession.Campaign?.Settings != null)
|
||||
{
|
||||
extraDifficulty = GameMain.GameSession.Campaign.Settings.ExtraEventManagerDifficulty;
|
||||
}
|
||||
float modifiedDifficulty = Math.Clamp(level.Difficulty + extraDifficulty, 0, 100);
|
||||
var suitableSettings = EventManagerSettings.OrderedByDifficulty.Where(s =>
|
||||
level.Difficulty >= s.MinLevelDifficulty &&
|
||||
level.Difficulty <= s.MaxLevelDifficulty).ToArray();
|
||||
modifiedDifficulty >= s.MinLevelDifficulty &&
|
||||
modifiedDifficulty <= s.MaxLevelDifficulty).ToArray();
|
||||
|
||||
if (suitableSettings.Length == 0)
|
||||
{
|
||||
@@ -362,8 +370,9 @@ namespace Barotrauma
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private void CreateEvents(EventSet eventSet, Random rand)
|
||||
private void CreateEvents(EventSet eventSet)
|
||||
{
|
||||
selectedEvents.Remove(eventSet);
|
||||
if (level == null) { return; }
|
||||
if (level.LevelData.HasHuntingGrounds && eventSet.DisableInHuntingGrounds) { return; }
|
||||
DebugConsole.NewMessage($"Loading event set {eventSet.Identifier}", Color.LightBlue, debugOnly: true);
|
||||
@@ -399,6 +408,14 @@ namespace Barotrauma
|
||||
bool isPrefabSuitable(EventPrefab e)
|
||||
=> e.BiomeIdentifier.IsEmpty ||
|
||||
e.BiomeIdentifier == level.LevelData?.Biome?.Identifier;
|
||||
|
||||
foreach (var subEventPrefab in eventSet.EventPrefabs)
|
||||
{
|
||||
foreach (Identifier missingId in subEventPrefab.GetMissingIdentifiers())
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in event set \"{eventSet.Identifier}\" ({eventSet.ContentFile?.ContentPackage?.Name ?? "null"}) - could not find an event prefab with the identifier \"{missingId}\".");
|
||||
}
|
||||
}
|
||||
|
||||
var suitablePrefabSubsets = eventSet.EventPrefabs.Where(
|
||||
e => e.EventPrefabs.Any(isPrefabSuitable)).ToArray();
|
||||
@@ -421,7 +438,7 @@ namespace Barotrauma
|
||||
|
||||
var newEvent = eventPrefab.CreateInstance();
|
||||
if (newEvent == null) { continue; }
|
||||
newEvent.Init(true);
|
||||
newEvent.Init(eventSet);
|
||||
if (i < spawnPosFilter.Count) { newEvent.SpawnPosFilter = spawnPosFilter[i]; }
|
||||
DebugConsole.NewMessage($"Initialized event {newEvent}", debugOnly: true);
|
||||
if (!selectedEvents.ContainsKey(eventSet))
|
||||
@@ -438,7 +455,7 @@ namespace Barotrauma
|
||||
var newEventSet = SelectRandomEvents(eventSet.ChildSets, random: rand);
|
||||
if (newEventSet != null)
|
||||
{
|
||||
CreateEvents(newEventSet, rand);
|
||||
CreateEvents(newEventSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -451,7 +468,7 @@ namespace Barotrauma
|
||||
var eventPrefab = ToolBox.SelectWeightedRandom(eventPrefabs.Where(isPrefabSuitable), e => e.Commonness, rand);
|
||||
var newEvent = eventPrefab.CreateInstance();
|
||||
if (newEvent == null) { continue; }
|
||||
newEvent.Init(true);
|
||||
newEvent.Init(eventSet);
|
||||
DebugConsole.NewMessage($"Initialized event {newEvent}", debugOnly: true);
|
||||
if (!selectedEvents.ContainsKey(eventSet))
|
||||
{
|
||||
@@ -465,7 +482,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (!IsValidForLevel(childEventSet, level)) { continue; }
|
||||
if (location != null && !IsValidForLocation(childEventSet, location)) { continue; }
|
||||
CreateEvents(childEventSet, rand);
|
||||
CreateEvents(childEventSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -666,6 +683,14 @@ namespace Barotrauma
|
||||
{
|
||||
eventCoolDown = settings.EventCooldown;
|
||||
}
|
||||
if (eventSet.ResetTime > 0)
|
||||
{
|
||||
ev.Finished += () =>
|
||||
{
|
||||
pendingEventSets.Add(eventSet);
|
||||
CreateEvents(eventSet);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ namespace Barotrauma
|
||||
}
|
||||
#endif
|
||||
|
||||
public static List<EventPrefab> GetAllEventPrefabs()
|
||||
public static List<EventPrefab> GetAllEventPrefabs()
|
||||
{
|
||||
List<EventPrefab> eventPrefabs = EventPrefab.Prefabs.ToList();
|
||||
foreach (var eventSet in Prefabs)
|
||||
@@ -118,6 +118,8 @@ namespace Barotrauma
|
||||
public readonly float DefaultCommonness;
|
||||
public readonly ImmutableDictionary<Identifier, float> OverrideCommonness;
|
||||
|
||||
public readonly float ResetTime;
|
||||
|
||||
public readonly struct SubEventPrefab
|
||||
{
|
||||
public SubEventPrefab(Either<Identifier[], EventPrefab> prefabOrIdentifiers, float? commonness, float? probability)
|
||||
@@ -140,11 +142,15 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (var id in (Identifier[])PrefabOrIdentifier)
|
||||
{
|
||||
yield return EventPrefab.Prefabs[id];
|
||||
if (EventPrefab.Prefabs.TryGet(id, out EventPrefab prefab))
|
||||
{
|
||||
yield return prefab;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public readonly float? SelfCommonness;
|
||||
public float Commonness => SelfCommonness ?? EventPrefabs.MaxOrNull(p => p.Commonness) ?? 0.0f;
|
||||
|
||||
@@ -157,6 +163,20 @@ namespace Barotrauma
|
||||
commonness = Commonness;
|
||||
probability = Probability;
|
||||
}
|
||||
|
||||
public IEnumerable<Identifier> GetMissingIdentifiers()
|
||||
{
|
||||
if (PrefabOrIdentifier.TryCast<Identifier[]>(out var ids))
|
||||
{
|
||||
foreach (var id in ids)
|
||||
{
|
||||
if (!EventPrefab.Prefabs.ContainsKey(id))
|
||||
{
|
||||
yield return id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public readonly ImmutableArray<SubEventPrefab> EventPrefabs;
|
||||
|
||||
@@ -244,6 +264,7 @@ namespace Barotrauma
|
||||
OncePerOutpost = element.GetAttributeBool("onceperoutpost", false);
|
||||
TriggerEventCooldown = element.GetAttributeBool("triggereventcooldown", true);
|
||||
IsCampaignSet = element.GetAttributeBool("campaign", LevelType == LevelData.LevelType.Outpost || (parentSet?.IsCampaignSet ?? false));
|
||||
ResetTime = element.GetAttributeFloat("resettime", 0);
|
||||
|
||||
DefaultCommonness = 1.0f;
|
||||
foreach (var subElement in element.Elements())
|
||||
@@ -454,7 +475,6 @@ namespace Barotrauma
|
||||
{
|
||||
childSet.Dispose();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,13 +39,9 @@ namespace Barotrauma
|
||||
targetItemIdentifiers = prefab.ConfigElement.GetAttributeIdentifierArray("itemidentifiers", Array.Empty<Identifier>());
|
||||
}
|
||||
|
||||
public override bool CanAffectSubImmediately(Level level)
|
||||
{
|
||||
return Item.ItemList.Count(i => i.Condition > 0.0f && targetItemIdentifiers.Contains(i.Prefab.Identifier)) >= maxItemAmount;
|
||||
}
|
||||
|
||||
public override void Init(bool affectSubImmediately)
|
||||
public override void Init(EventSet parentSet)
|
||||
{
|
||||
base.Init(parentSet);
|
||||
var matchingItems = Item.ItemList.FindAll(i => i.Condition > 0.0f && targetItemIdentifiers.Contains(i.Prefab.Identifier));
|
||||
int itemAmount = Rand.Range(minItemAmount, maxItemAmount, Rand.RandSync.ServerAndClient);
|
||||
for (int i = 0; i < itemAmount; i++)
|
||||
@@ -60,7 +56,7 @@ namespace Barotrauma
|
||||
if (isFinished) return;
|
||||
if (targetItems.Count == 0 || timer >= duration)
|
||||
{
|
||||
Finished();
|
||||
Finish();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -125,10 +125,6 @@ namespace Barotrauma
|
||||
if (!AllTargetsEliminated()) { return; }
|
||||
State = 1;
|
||||
break;
|
||||
case 1:
|
||||
if (!Submarine.MainSub.AtEndExit && !Submarine.MainSub.AtStartExit) { return; }
|
||||
State = 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,11 +162,16 @@ namespace Barotrauma
|
||||
|
||||
public override void End()
|
||||
{
|
||||
if (State == 2)
|
||||
bool exitingLevel = GameMain.GameSession?.GameMode is CampaignMode campaign ?
|
||||
campaign.GetAvailableTransition() != CampaignMode.TransitionType.None :
|
||||
Submarine.MainSub is { } sub && (sub.AtEndExit || sub.AtStartExit);
|
||||
|
||||
if (State > 0 && exitingLevel)
|
||||
{
|
||||
GiveReward();
|
||||
completed = true;
|
||||
}
|
||||
|
||||
failed = !completed && State > 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -23,15 +22,16 @@ namespace Barotrauma
|
||||
private int calculatedReward;
|
||||
private int maxItemCount;
|
||||
|
||||
private Submarine sub;
|
||||
|
||||
private Submarine currentSub;
|
||||
private SubmarineInfo nextRoundSubInfo;
|
||||
|
||||
private readonly List<CargoMission> previouslySelectedMissions = new List<CargoMission>();
|
||||
|
||||
public override LocalizedString Description
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Submarine.MainSub != sub)
|
||||
if ((GameMain.GameSession?.Campaign?.PendingSubmarineSwitch ?? Submarine.MainSub?.Info) != nextRoundSubInfo)
|
||||
{
|
||||
string rewardText = $"‖color:gui.orange‖{string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0:N0}", GetReward(Submarine.MainSub))}‖end‖";
|
||||
if (descriptionWithoutReward != null) { description = descriptionWithoutReward.Replace("[reward]", rewardText); }
|
||||
@@ -43,7 +43,8 @@ namespace Barotrauma
|
||||
public CargoMission(MissionPrefab prefab, Location[] locations, Submarine sub)
|
||||
: base(prefab, locations, sub)
|
||||
{
|
||||
this.sub = sub;
|
||||
this.currentSub = sub;
|
||||
this.nextRoundSubInfo = sub?.Info;
|
||||
itemConfig = prefab.ConfigElement.GetChildElement("Items");
|
||||
requiredDeliveryAmount = Math.Min(prefab.ConfigElement.GetAttributeFloat("requireddeliveryamount", 0.98f), 1.0f);
|
||||
//this can get called between rounds when the client receives a campaign save
|
||||
@@ -57,39 +58,13 @@ namespace Barotrauma
|
||||
|
||||
private void DetermineCargo()
|
||||
{
|
||||
if (this.sub == null || itemConfig == null)
|
||||
if (this.currentSub == null || itemConfig == null)
|
||||
{
|
||||
calculatedReward = Prefab.Reward;
|
||||
return;
|
||||
}
|
||||
|
||||
itemsToSpawn.Clear();
|
||||
List<(ItemContainer container, int freeSlots)> containers = sub.GetCargoContainers();
|
||||
containers.Sort((c1, c2) => { return c2.container.Capacity.CompareTo(c1.container.Capacity); });
|
||||
|
||||
previouslySelectedMissions.Clear();
|
||||
if (GameMain.GameSession?.StartLocation?.SelectedMissions != null)
|
||||
{
|
||||
bool isPriorMission = true;
|
||||
foreach (Mission mission in GameMain.GameSession.StartLocation.SelectedMissions)
|
||||
{
|
||||
if (!(mission is CargoMission otherMission)) { continue; }
|
||||
if (mission == this) { isPriorMission = false; }
|
||||
previouslySelectedMissions.Add(otherMission);
|
||||
if (!isPriorMission) { continue; }
|
||||
foreach (var (element, container) in otherMission.itemsToSpawn)
|
||||
{
|
||||
for (int i = 0; i < containers.Count; i++)
|
||||
{
|
||||
if (containers[i].container == container)
|
||||
{
|
||||
containers[i] = (containers[i].container, containers[i].freeSlots - 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
maxItemCount = 0;
|
||||
foreach (var subElement in itemConfig.Elements())
|
||||
@@ -98,18 +73,85 @@ namespace Barotrauma
|
||||
maxItemCount += maxCount;
|
||||
}
|
||||
|
||||
for (int i = 0; i < containers.Count; i++)
|
||||
var pendingSubInfo = GameMain.GameSession?.Campaign?.PendingSubmarineSwitch;
|
||||
if (pendingSubInfo != null && pendingSubInfo != currentSub.Info)
|
||||
{
|
||||
foreach (var subElement in itemConfig.Elements())
|
||||
//if we've got a submarine switch pending, calculate the amount of cargo based on it's cargo capacity
|
||||
//TODO: this isn't guaranteed to be accurate, because we don't take existing items in the new sub's cargo containers
|
||||
//or items that might get transferred in them into account
|
||||
maxItemCount = Math.Min(maxItemCount, pendingSubInfo.CargoCapacity);
|
||||
previouslySelectedMissions.Clear();
|
||||
if (GameMain.GameSession?.StartLocation?.SelectedMissions != null)
|
||||
{
|
||||
int maxCount = subElement.GetAttributeInt("maxcount", 10);
|
||||
if (itemsToSpawn.Count(it => it.element == subElement) >= maxCount) { continue; }
|
||||
ItemPrefab itemPrefab = FindItemPrefab(subElement);
|
||||
while (containers[i].freeSlots > 0 && containers[i].container.Inventory.CanBePut(itemPrefab))
|
||||
bool isPriorMission = true;
|
||||
foreach (Mission mission in GameMain.GameSession.StartLocation.SelectedMissions)
|
||||
{
|
||||
containers[i] = (containers[i].container, containers[i].freeSlots - 1);
|
||||
itemsToSpawn.Add((subElement, containers[i].container));
|
||||
if (itemsToSpawn.Count(it => it.element == subElement) >= maxCount) { break; }
|
||||
if (!(mission is CargoMission otherMission)) { continue; }
|
||||
if (mission == this) { isPriorMission = false; }
|
||||
previouslySelectedMissions.Add(otherMission);
|
||||
if (!isPriorMission) { continue; }
|
||||
maxItemCount -= otherMission.itemsToSpawn.Count;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < maxItemCount; i++)
|
||||
{
|
||||
foreach (var subElement in itemConfig.Elements())
|
||||
{
|
||||
int maxCount = subElement.GetAttributeInt("maxcount", 10);
|
||||
if (itemsToSpawn.Count(it => it.element == subElement) >= maxCount) { continue; }
|
||||
ItemPrefab itemPrefab = FindItemPrefab(subElement);
|
||||
while (itemsToSpawn.Count < maxItemCount)
|
||||
{
|
||||
itemsToSpawn.Add((subElement, null));
|
||||
if (itemsToSpawn.Count(it => it.element == subElement) >= maxCount) { break; }
|
||||
}
|
||||
}
|
||||
}
|
||||
maxItemCount = Math.Max(0, maxItemCount);
|
||||
nextRoundSubInfo = pendingSubInfo;
|
||||
}
|
||||
else
|
||||
{
|
||||
List<(ItemContainer container, int freeSlots)> containers = currentSub.GetCargoContainers();
|
||||
containers.Sort((c1, c2) => { return c2.container.Capacity.CompareTo(c1.container.Capacity); });
|
||||
|
||||
previouslySelectedMissions.Clear();
|
||||
if (GameMain.GameSession?.StartLocation?.SelectedMissions != null)
|
||||
{
|
||||
bool isPriorMission = true;
|
||||
foreach (Mission mission in GameMain.GameSession.StartLocation.SelectedMissions)
|
||||
{
|
||||
if (!(mission is CargoMission otherMission)) { continue; }
|
||||
if (mission == this) { isPriorMission = false; }
|
||||
previouslySelectedMissions.Add(otherMission);
|
||||
if (!isPriorMission) { continue; }
|
||||
foreach (var (element, container) in otherMission.itemsToSpawn)
|
||||
{
|
||||
for (int i = 0; i < containers.Count; i++)
|
||||
{
|
||||
if (containers[i].container == container)
|
||||
{
|
||||
containers[i] = (containers[i].container, containers[i].freeSlots - 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < containers.Count; i++)
|
||||
{
|
||||
foreach (var subElement in itemConfig.Elements())
|
||||
{
|
||||
int maxCount = subElement.GetAttributeInt("maxcount", 10);
|
||||
if (itemsToSpawn.Count(it => it.element == subElement) >= maxCount) { continue; }
|
||||
ItemPrefab itemPrefab = FindItemPrefab(subElement);
|
||||
while (containers[i].freeSlots > 0 && containers[i].container.Inventory.CanBePut(itemPrefab))
|
||||
{
|
||||
containers[i] = (containers[i].container, containers[i].freeSlots - 1);
|
||||
itemsToSpawn.Add((subElement, containers[i].container));
|
||||
if (itemsToSpawn.Count(it => it.element == subElement) >= maxCount) { break; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -135,7 +177,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (rewardPerCrate.HasValue && rewardPerCrate < 0) { rewardPerCrate = null; }
|
||||
|
||||
string rewardText = $"‖color:gui.orange‖{string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0:N0}", GetReward(sub))}‖end‖";
|
||||
string rewardText = $"‖color:gui.orange‖{string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0:N0}", GetReward(currentSub))}‖end‖";
|
||||
if (descriptionWithoutReward != null) { description = descriptionWithoutReward.Replace("[reward]", rewardText); }
|
||||
}
|
||||
|
||||
@@ -167,18 +209,26 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sub != this.sub || missionsChanged)
|
||||
|
||||
var pendingSubInfo = GameMain.GameSession?.Campaign?.PendingSubmarineSwitch;
|
||||
if (pendingSubInfo != null && nextRoundSubInfo != pendingSubInfo)
|
||||
{
|
||||
this.sub = sub;
|
||||
this.nextRoundSubInfo = pendingSubInfo;
|
||||
DetermineCargo();
|
||||
}
|
||||
else if (sub != this.currentSub || missionsChanged)
|
||||
{
|
||||
this.currentSub = sub;
|
||||
this.nextRoundSubInfo = sub.Info;
|
||||
DetermineCargo();
|
||||
}
|
||||
|
||||
return calculatedReward;
|
||||
}
|
||||
|
||||
private void InitItems()
|
||||
{
|
||||
this.sub = Submarine.MainSub;
|
||||
this.currentSub = Submarine.MainSub;
|
||||
DetermineCargo();
|
||||
|
||||
items.Clear();
|
||||
|
||||
@@ -323,7 +323,7 @@ namespace Barotrauma
|
||||
{
|
||||
var newEvent = eventPrefab.CreateInstance();
|
||||
GameMain.GameSession.EventManager.ActiveEvents.Add(newEvent);
|
||||
newEvent.Init(true);
|
||||
newEvent.Init();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,7 +382,8 @@ namespace Barotrauma
|
||||
#if SERVER
|
||||
totalReward = DistributeRewardsToCrew(GameSession.GetSessionCrewCharacters(CharacterType.Player), totalReward);
|
||||
#endif
|
||||
if (totalReward > 0)
|
||||
bool isSingleplayerOrServer = GameMain.IsSingleplayer || GameMain.NetworkMember is { IsServer: true };
|
||||
if (isSingleplayerOrServer && totalReward > 0)
|
||||
{
|
||||
campaign.Bank.Give(totalReward);
|
||||
}
|
||||
|
||||
@@ -146,8 +146,15 @@ namespace Barotrauma
|
||||
|
||||
tags = element.GetAttributeStringArray("tags", Array.Empty<string>(), convertToLowerInvariant: true);
|
||||
|
||||
Name = TextManager.Get($"MissionName.{TextIdentifier}").Fallback(element.GetAttributeString("name", ""));
|
||||
Description = TextManager.Get($"MissionDescription.{TextIdentifier}").Fallback(element.GetAttributeString("description", ""));
|
||||
Name =
|
||||
TextManager.Get($"MissionName.{TextIdentifier}")
|
||||
.Fallback(TextManager.Get(element.GetAttributeString("name", "")))
|
||||
.Fallback(element.GetAttributeString("name", ""));
|
||||
Description =
|
||||
TextManager.Get($"MissionDescription.{TextIdentifier}")
|
||||
.Fallback(TextManager.Get(element.GetAttributeString("description", "")))
|
||||
.Fallback(element.GetAttributeString("description", ""));
|
||||
|
||||
Reward = element.GetAttributeInt("reward", 1);
|
||||
AllowRetry = element.GetAttributeBool("allowretry", false);
|
||||
IsSideObjective = element.GetAttributeBool("sideobjective", false);
|
||||
@@ -160,10 +167,15 @@ namespace Barotrauma
|
||||
Difficulty = Math.Clamp(difficulty, MinDifficulty, MaxDifficulty);
|
||||
}
|
||||
|
||||
SuccessMessage = TextManager.Get($"MissionSuccess.{TextIdentifier}").Fallback(element.GetAttributeString("successmessage", "Mission completed successfully"));
|
||||
FailureMessage = TextManager.Get($"MissionFailure.{TextIdentifier}").Fallback(
|
||||
TextManager.Get("missionfailed")).Fallback(
|
||||
GameSettings.CurrentConfig.Language == TextManager.DefaultLanguage ? element.GetAttributeString("failuremessage", "") : "");
|
||||
SuccessMessage =
|
||||
TextManager.Get($"MissionSuccess.{TextIdentifier}")
|
||||
.Fallback(TextManager.Get(element.GetAttributeString("successmessage", "")))
|
||||
.Fallback(element.GetAttributeString("successmessage", "Mission completed successfully"));
|
||||
FailureMessage =
|
||||
TextManager.Get($"MissionFailure.{TextIdentifier}")
|
||||
.Fallback(TextManager.Get(element.GetAttributeString("missionfailed", "")))
|
||||
.Fallback(TextManager.Get("missionfailed"))
|
||||
.Fallback(GameSettings.CurrentConfig.Language == TextManager.DefaultLanguage ? element.GetAttributeString("failuremessage", "") : "");
|
||||
|
||||
string sonarLabelTag = element.GetAttributeString("sonarlabel", "");
|
||||
|
||||
@@ -208,8 +220,14 @@ namespace Barotrauma
|
||||
headers.Add(string.Empty);
|
||||
messages.Add(string.Empty);
|
||||
}
|
||||
headers[messageIndex] = TextManager.Get($"MissionHeader{messageIndex}.{TextIdentifier}").Fallback(subElement.GetAttributeString("header", ""));
|
||||
messages[messageIndex] = TextManager.Get($"MissionMessage{messageIndex}.{TextIdentifier}").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":
|
||||
|
||||
@@ -135,8 +135,10 @@ namespace Barotrauma
|
||||
monster.Enabled = false;
|
||||
if (monster.Params.AI != null && monster.Params.AI.EnforceAggressiveBehaviorForMissions)
|
||||
{
|
||||
monster.Params.AI.FleeHealthThreshold = 0;
|
||||
foreach (var targetParam in monster.Params.AI.Targets)
|
||||
{
|
||||
if (targetParam.Tag.Equals("engine", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
switch (targetParam.State)
|
||||
{
|
||||
case AIState.Avoid:
|
||||
|
||||
@@ -270,7 +270,7 @@ namespace Barotrauma
|
||||
|
||||
foreach (Item item in spawnedCharacter.Inventory.AllItems)
|
||||
{
|
||||
if (item?.Prefab.Identifier == "idcard")
|
||||
if (item?.GetComponent<IdCard>() != null)
|
||||
{
|
||||
item.AddTag("id_pirate");
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ namespace Barotrauma
|
||||
private readonly float scatter;
|
||||
private readonly float offset;
|
||||
private readonly float delayBetweenSpawns;
|
||||
private float resetTime;
|
||||
private float resetTimer;
|
||||
|
||||
private Vector2? spawnPos;
|
||||
|
||||
@@ -24,7 +26,7 @@ namespace Barotrauma
|
||||
public readonly Level.PositionType SpawnPosType;
|
||||
private readonly string spawnPointTag;
|
||||
|
||||
private bool spawnPending;
|
||||
private bool spawnPending, spawnReady;
|
||||
|
||||
public readonly int MaxAmountPerLevel = int.MaxValue;
|
||||
|
||||
@@ -96,6 +98,7 @@ namespace Barotrauma
|
||||
offset = prefab.ConfigElement.GetAttributeFloat("offset", 0);
|
||||
scatter = Math.Clamp(prefab.ConfigElement.GetAttributeFloat("scatter", 500), 0, 3000);
|
||||
delayBetweenSpawns = prefab.ConfigElement.GetAttributeFloat("delaybetweenspawns", 0.1f);
|
||||
resetTime = prefab.ConfigElement.GetAttributeFloat("resettime", 0);
|
||||
|
||||
if (GameMain.NetworkMember != null)
|
||||
{
|
||||
@@ -131,14 +134,14 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public override bool CanAffectSubImmediately(Level level)
|
||||
{
|
||||
float maxRange = Sonar.DefaultSonarRange * 0.8f;
|
||||
return GetAvailableSpawnPositions().Any(p => Vector2.DistanceSquared(p.Position.ToVector2(), GetReferenceSub().WorldPosition) < maxRange * maxRange);
|
||||
}
|
||||
|
||||
public override void Init(bool affectSubImmediately)
|
||||
public override void Init(EventSet parentSet)
|
||||
{
|
||||
base.Init(parentSet);
|
||||
if (parentSet != null && resetTime == 0)
|
||||
{
|
||||
// Use the parent reset time only if there's no reset time defined for the event.
|
||||
resetTime = parentSet.ResetTime;
|
||||
}
|
||||
if (GameSettings.CurrentConfig.VerboseLogging)
|
||||
{
|
||||
DebugConsole.NewMessage("Initialized MonsterEvent (" + SpeciesName + ")", Color.White);
|
||||
@@ -199,7 +202,7 @@ namespace Barotrauma
|
||||
{
|
||||
//no suitable position found, disable the event
|
||||
spawnPos = null;
|
||||
Finished();
|
||||
Finish();
|
||||
return;
|
||||
}
|
||||
Submarine refSub = GetReferenceSub();
|
||||
@@ -267,22 +270,17 @@ namespace Barotrauma
|
||||
if (!isRuinOrWreck)
|
||||
{
|
||||
float minDistance = 20000;
|
||||
var refSub = GetReferenceSub();
|
||||
availablePositions.RemoveAll(p => Vector2.DistanceSquared(refSub.WorldPosition, p.Position.ToVector2()) < minDistance * minDistance);
|
||||
if (Submarine.MainSubs.Length > 1)
|
||||
for (int i = 0; i < Submarine.MainSubs.Length; i++)
|
||||
{
|
||||
for (int i = 1; i < Submarine.MainSubs.Length; i++)
|
||||
{
|
||||
if (Submarine.MainSubs[i] == null) { continue; }
|
||||
availablePositions.RemoveAll(p => Vector2.DistanceSquared(Submarine.MainSubs[i].WorldPosition, p.Position.ToVector2()) < minDistance * minDistance);
|
||||
}
|
||||
if (Submarine.MainSubs[i] == null) { continue; }
|
||||
availablePositions.RemoveAll(p => Vector2.DistanceSquared(Submarine.MainSubs[i].WorldPosition, p.Position.ToVector2()) < minDistance * minDistance);
|
||||
}
|
||||
}
|
||||
if (availablePositions.None())
|
||||
{
|
||||
//no suitable position found, disable the event
|
||||
spawnPos = null;
|
||||
Finished();
|
||||
Finish();
|
||||
return;
|
||||
}
|
||||
chosenPosition = availablePositions.GetRandomUnsynced();
|
||||
@@ -306,7 +304,7 @@ namespace Barotrauma
|
||||
{
|
||||
//no suitable position found, disable the event
|
||||
spawnPos = null;
|
||||
Finished();
|
||||
Finish();
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -344,7 +342,7 @@ namespace Barotrauma
|
||||
{
|
||||
//no suitable position found, disable the event
|
||||
spawnPos = null;
|
||||
Finished();
|
||||
Finish();
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -352,20 +350,42 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private float GetMinDistanceToSub(Submarine submarine)
|
||||
private float GetMinDistanceToSub(Submarine submarine)
|
||||
{
|
||||
return Math.Max(Math.Max(submarine.Borders.Width, submarine.Borders.Height), Sonar.DefaultSonarRange * 0.9f);
|
||||
float minDist = Math.Max(Math.Max(submarine.Borders.Width, submarine.Borders.Height), Sonar.DefaultSonarRange * 0.9f);
|
||||
if (SpawnPosType.HasFlag(Level.PositionType.Abyss))
|
||||
{
|
||||
minDist *= 2;
|
||||
}
|
||||
return minDist;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (disallowed)
|
||||
{
|
||||
Finished();
|
||||
Finish();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isFinished) { return; }
|
||||
if (resetTimer > 0)
|
||||
{
|
||||
resetTimer -= deltaTime;
|
||||
if (resetTimer <= 0)
|
||||
{
|
||||
if (ParentSet?.ResetTime > 0)
|
||||
{
|
||||
// If parent has reset time defined, the set is recreated. Otherwise we'll just reset this event.
|
||||
Finish();
|
||||
}
|
||||
else
|
||||
{
|
||||
spawnReady = false;
|
||||
spawnPos = null;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (spawnPos == null)
|
||||
{
|
||||
@@ -373,7 +393,11 @@ namespace Barotrauma
|
||||
{
|
||||
if (Character.CharacterList.Count(c => c.SpeciesName == SpeciesName) >= MaxAmountPerLevel)
|
||||
{
|
||||
disallowed = true;
|
||||
// If the event is set to reset, let's just wait until the old corpse is removed (after being disabled).
|
||||
if (resetTime == 0)
|
||||
{
|
||||
disallowed = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -384,9 +408,14 @@ namespace Barotrauma
|
||||
spawnPending = true;
|
||||
}
|
||||
|
||||
bool spawnReady = false;
|
||||
if (spawnPending)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(spawnPos.HasValue);
|
||||
if (spawnPos == null)
|
||||
{
|
||||
Finish();
|
||||
return;
|
||||
}
|
||||
//wait until there are no submarines at the spawnpos
|
||||
if (SpawnPosType.HasFlag(Level.PositionType.MainPath) || SpawnPosType.HasFlag(Level.PositionType.SidePath) || SpawnPosType.HasFlag(Level.PositionType.Abyss))
|
||||
{
|
||||
@@ -408,7 +437,7 @@ namespace Barotrauma
|
||||
{
|
||||
minDistance = 5000;
|
||||
}
|
||||
else if (SpawnPosType.HasFlag(Level.PositionType.Wreck))
|
||||
else if (SpawnPosType.HasFlag(Level.PositionType.Wreck) || SpawnPosType.HasFlag(Level.PositionType.BeaconStation))
|
||||
{
|
||||
minDistance = 3000;
|
||||
}
|
||||
@@ -554,28 +583,24 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (!spawnReady) { return; }
|
||||
|
||||
Entity targetEntity = Submarine.FindClosest(GameMain.GameScreen.Cam.WorldViewCenter);
|
||||
#if CLIENT
|
||||
if (Character.Controlled != null) { targetEntity = Character.Controlled; }
|
||||
#endif
|
||||
|
||||
bool monstersDead = true;
|
||||
foreach (Character monster in monsters)
|
||||
if (spawnReady)
|
||||
{
|
||||
if (!monster.IsDead)
|
||||
if (monsters.None())
|
||||
{
|
||||
monstersDead = false;
|
||||
|
||||
if (targetEntity != null && Vector2.DistanceSquared(monster.WorldPosition, targetEntity.WorldPosition) < 5000.0f * 5000.0f)
|
||||
Finish();
|
||||
}
|
||||
else if (monsters.All(m => m.IsDead))
|
||||
{
|
||||
if (resetTime > 0)
|
||||
{
|
||||
break;
|
||||
resetTimer = resetTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
Finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (monstersDead) { Finished(); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -12,6 +11,7 @@ namespace Barotrauma
|
||||
private readonly Dictionary<Identifier, List<Entity>> cachedTargets = new Dictionary<Identifier, List<Entity>>();
|
||||
private int prevEntityCount;
|
||||
private int prevPlayerCount, prevBotCount;
|
||||
private Character prevControlled;
|
||||
|
||||
private readonly string[] requiredDestinationTypes;
|
||||
public readonly bool RequireBeaconStation;
|
||||
@@ -163,24 +163,25 @@ namespace Barotrauma
|
||||
botCount++;
|
||||
}
|
||||
}
|
||||
if (Entity.EntityCount != prevEntityCount || botCount != prevBotCount || playerCount != prevPlayerCount)
|
||||
if (Entity.EntityCount != prevEntityCount || botCount != prevBotCount || playerCount != prevPlayerCount || prevControlled != Character.Controlled)
|
||||
{
|
||||
cachedTargets.Clear();
|
||||
prevEntityCount = Entity.EntityCount;
|
||||
prevBotCount = botCount;
|
||||
prevPlayerCount = playerCount;
|
||||
prevControlled = Character.Controlled;
|
||||
}
|
||||
|
||||
if (!Actions.Any())
|
||||
{
|
||||
Finished();
|
||||
Finish();
|
||||
return;
|
||||
}
|
||||
|
||||
var currentAction = Actions[CurrentActionIndex];
|
||||
if (!currentAction.CanBeFinished())
|
||||
{
|
||||
Finished();
|
||||
Finish();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -207,7 +208,7 @@ namespace Barotrauma
|
||||
|
||||
if (CurrentActionIndex >= Actions.Count || CurrentActionIndex < 0)
|
||||
{
|
||||
Finished();
|
||||
Finish();
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -232,9 +233,9 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void Finished()
|
||||
public override void Finish()
|
||||
{
|
||||
base.Finished();
|
||||
base.Finish();
|
||||
GameAnalyticsManager.AddDesignEvent($"ScriptedEvent:{prefab.Identifier}:Finished:{CurrentActionIndex}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,11 +339,6 @@ namespace Barotrauma
|
||||
loadContext = null;
|
||||
assembly = null;
|
||||
}
|
||||
|
||||
~Implementation()
|
||||
{
|
||||
OnQuit();
|
||||
}
|
||||
}
|
||||
private static Implementation? loadedImplementation;
|
||||
|
||||
|
||||
@@ -1,74 +1,141 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
#warning TODO: This class needs some changes:
|
||||
// - We shouldn't be iterating over MapEntityPrefab.List. It has no guarantee of any sort of order and becomes entirely unpredictable once you start adding mods.
|
||||
// - Note: iterating over ItemPrefab.Prefabs would also be incorrect. Sorting by UintIdentifier is necessary for determinism.
|
||||
// - SpawnItems and SpawnItem are named incorrectly.
|
||||
static class AutoItemPlacer
|
||||
{
|
||||
public static bool OutputDebugInfo = false;
|
||||
|
||||
/// <summary>
|
||||
/// If we are spawning in an area where difficulty should not be a factor, assume difficulty is at the exact "middle"
|
||||
/// </summary>
|
||||
public const float DefaultDifficultyModifier = 0f;
|
||||
|
||||
public static void PlaceIfNeeded()
|
||||
public static void SpawnItems(Identifier? startItemSet = null)
|
||||
{
|
||||
if (GameMain.NetworkMember != null && !GameMain.NetworkMember.IsServer) { return; }
|
||||
|
||||
for (int i = 0; i < Submarine.MainSubs.Length; i++)
|
||||
//player has more than one sub = we must have given the start items already
|
||||
bool startItemsGiven = GameMain.GameSession?.OwnedSubmarines != null && GameMain.GameSession.OwnedSubmarines.Count > 1;
|
||||
if (!startItemsGiven)
|
||||
{
|
||||
if (Submarine.MainSubs[i] == null || Submarine.MainSubs[i].Info.InitialSuppliesSpawned) { continue; }
|
||||
List<Submarine> subs = new List<Submarine>() { Submarine.MainSubs[i] };
|
||||
subs.AddRange(Submarine.MainSubs[i].DockedTo.Where(d => !d.Info.IsOutpost));
|
||||
Place(subs);
|
||||
subs.ForEach(s => s.Info.InitialSuppliesSpawned = true);
|
||||
for (int i = 0; i < Submarine.MainSubs.Length; i++)
|
||||
{
|
||||
var sub = Submarine.MainSubs[i];
|
||||
if (sub == null || sub.Info.InitialSuppliesSpawned || !sub.Info.IsPlayer) { continue; }
|
||||
//1st pass: items defined in the start item set, only spawned in the main sub (not drones/shuttles or other linked subs)
|
||||
SpawnStartItems(sub, startItemSet);
|
||||
//2nd pass: items defined using preferred containers, spawned in the main sub and all the linked subs (drones, shuttles etc)
|
||||
var subs = sub.GetConnectedSubs().Where(s => s.TeamID == sub.TeamID);
|
||||
CreateAndPlace(subs);
|
||||
subs.ForEach(s => s.Info.InitialSuppliesSpawned = true);
|
||||
}
|
||||
}
|
||||
|
||||
float difficultyModifier = GetLevelDifficultyModifier();
|
||||
//spawn items in wrecks, beacon stations and pirate subs
|
||||
foreach (var sub in Submarine.Loaded)
|
||||
{
|
||||
if (sub.Info.Type == SubmarineType.Player ||
|
||||
sub.Info.Type == SubmarineType.Outpost ||
|
||||
sub.Info.Type == SubmarineType.OutpostModule ||
|
||||
sub.Info.Type == SubmarineType.EnemySubmarine)
|
||||
sub.Info.Type == SubmarineType.OutpostModule)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Place(sub.ToEnumerable(), difficultyModifier: difficultyModifier);
|
||||
if (sub.Info.InitialSuppliesSpawned) { continue; }
|
||||
CreateAndPlace(sub.ToEnumerable());
|
||||
sub.Info.InitialSuppliesSpawned = true;
|
||||
}
|
||||
|
||||
if (Level.Loaded?.StartOutpost != null && Level.Loaded.Type == LevelData.LevelType.Outpost)
|
||||
{
|
||||
Rand.SetSyncedSeed(ToolBox.StringToInt(Level.Loaded.StartOutpost.Info.Name));
|
||||
Place(Level.Loaded.StartOutpost.ToEnumerable());
|
||||
var sub = Level.Loaded.StartOutpost;
|
||||
if (!sub.Info.InitialSuppliesSpawned)
|
||||
{
|
||||
Rand.SetSyncedSeed(ToolBox.StringToInt(sub.Info.Name));
|
||||
CreateAndPlace(sub.ToEnumerable());
|
||||
sub.Info.InitialSuppliesSpawned = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const float MaxDifficultyModifier = 0.2f;
|
||||
|
||||
/// <summary>
|
||||
/// Spawn probability of loot is modified by difficulty, -20% less loot at 0% difficulty and +20% loot at 100% difficulty.
|
||||
/// </summary>
|
||||
private static float GetLevelDifficultyModifier()
|
||||
{
|
||||
return Math.Clamp(Level.Loaded?.Difficulty is float difficulty ? (difficulty / 100f) * (MaxDifficultyModifier * 2) - MaxDifficultyModifier : DefaultDifficultyModifier, -MaxDifficultyModifier, MaxDifficultyModifier);
|
||||
}
|
||||
|
||||
public static void RegenerateLoot(Submarine sub, ItemContainer regeneratedContainer)
|
||||
{
|
||||
// Level difficulty currently doesn't affect regenerated loot for the sake of simplicity
|
||||
Place(sub.ToEnumerable(), regeneratedContainer: regeneratedContainer);
|
||||
CreateAndPlace(sub.ToEnumerable(), regeneratedContainer: regeneratedContainer);
|
||||
}
|
||||
|
||||
private static void Place(IEnumerable<Submarine> subs, ItemContainer regeneratedContainer = null, float difficultyModifier = DefaultDifficultyModifier)
|
||||
public static Identifier DefaultStartItemSet = new Identifier("normal");
|
||||
|
||||
/// <summary>
|
||||
/// Spawns the items defined in the start item set in the specified sub.
|
||||
/// </summary>
|
||||
private static void SpawnStartItems(Submarine sub, Identifier? startItemSet)
|
||||
{
|
||||
Identifier setIdentifier = startItemSet ?? DefaultStartItemSet;
|
||||
if (!StartItemSet.Sets.TryGet(setIdentifier, out StartItemSet itemSet))
|
||||
{
|
||||
DebugConsole.AddWarning($"Couldn't find a start item set matching the identifier \"{setIdentifier}\"!");
|
||||
if (!StartItemSet.Sets.TryGet(DefaultStartItemSet, out StartItemSet defaultSet))
|
||||
{
|
||||
DebugConsole.ThrowError($"Couldn't find the default start item set \"{DefaultStartItemSet}\"!");
|
||||
return;
|
||||
}
|
||||
itemSet = defaultSet;
|
||||
}
|
||||
WayPoint wp = WayPoint.GetRandom(SpawnType.Cargo, null, sub);
|
||||
ISpatialEntity initialSpawnPos;
|
||||
if (wp?.CurrentHull == null)
|
||||
{
|
||||
var spawnHull = Hull.HullList.Where(h => h.Submarine == sub && !h.IsWetRoom).GetRandomUnsynced();
|
||||
if (spawnHull == null)
|
||||
{
|
||||
DebugConsole.AddWarning($"Failed to spawn start items in the sub. No cargo waypoint or dry hulls found to spawn the items in.");
|
||||
return;
|
||||
}
|
||||
initialSpawnPos = spawnHull;
|
||||
}
|
||||
else
|
||||
{
|
||||
initialSpawnPos = wp;
|
||||
}
|
||||
var newItems = new List<Item>();
|
||||
foreach (var startItem in itemSet.Items)
|
||||
{
|
||||
if (!ItemPrefab.Prefabs.TryGet(startItem.Item, out ItemPrefab itemPrefab))
|
||||
{
|
||||
DebugConsole.AddWarning($"Cannot find a start item with with the identifier \"{startItem.Item}\"");
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < startItem.Amount; i++)
|
||||
{
|
||||
var item = new Item(itemPrefab, initialSpawnPos.Position, sub, callOnItemLoaded: false);
|
||||
// Is this necessary?
|
||||
foreach (WifiComponent wifiComponent in item.GetComponents<WifiComponent>())
|
||||
{
|
||||
wifiComponent.TeamID = sub.TeamID;
|
||||
}
|
||||
newItems.Add(item);
|
||||
}
|
||||
}
|
||||
var cargoContainers = new List<ItemContainer>();
|
||||
foreach (var item in newItems)
|
||||
{
|
||||
#if SERVER
|
||||
Entity.Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(item));
|
||||
#endif
|
||||
foreach (ItemComponent ic in item.Components)
|
||||
{
|
||||
ic.OnItemLoaded();
|
||||
}
|
||||
var container = sub.FindContainerFor(item, onlyPrimary: true);
|
||||
if (container == null)
|
||||
{
|
||||
var cargoContainer = CargoManager.GetOrCreateCargoContainerFor(item.Prefab, initialSpawnPos, ref cargoContainers);
|
||||
container = cargoContainer?.Item;
|
||||
}
|
||||
container?.OwnInventory.TryPutItem(item, user: null);
|
||||
}
|
||||
}
|
||||
|
||||
private static void CreateAndPlace(IEnumerable<Submarine> subs, ItemContainer regeneratedContainer = null)
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
@@ -76,7 +143,7 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
List<Item> spawnedItems = new List<Item>(100);
|
||||
List<Item> itemsToSpawn = new List<Item>(100);
|
||||
|
||||
int itemCountApprox = MapEntityPrefab.List.Count() / 3;
|
||||
var containers = new List<ItemContainer>(70 + 30 * subs.Count());
|
||||
@@ -100,11 +167,11 @@ namespace Barotrauma
|
||||
containers.Shuffle(Rand.RandSync.ServerAndClient);
|
||||
}
|
||||
|
||||
foreach (ItemPrefab ip in ItemPrefab.Prefabs)
|
||||
var itemPrefabs = ItemPrefab.Prefabs.OrderBy(p => p.UintIdentifier);
|
||||
foreach (ItemPrefab ip in itemPrefabs)
|
||||
{
|
||||
if (!ip.PreferredContainers.Any()) { continue; }
|
||||
if (ip.ConfigElement.Elements().Any(e => string.Equals(e.Name.ToString(), typeof(ItemContainer).Name.ToString(), StringComparison.OrdinalIgnoreCase)) &&
|
||||
ItemPrefab.Prefabs.Any(ip2 => CanSpawnIn(ip2, ip)))
|
||||
if (ip.PreferredContainers.None()) { continue; }
|
||||
if (ip.ConfigElement.Elements().Any(e => string.Equals(e.Name.ToString(), typeof(ItemContainer).Name.ToString(), StringComparison.OrdinalIgnoreCase)) && itemPrefabs.Any(ip2 => CanSpawnIn(ip2, ip)))
|
||||
{
|
||||
prefabsItemsCanSpawnIn.Add(ip);
|
||||
}
|
||||
@@ -141,9 +208,9 @@ namespace Barotrauma
|
||||
{
|
||||
var subNames = subs.Select(s => s.Info.Name).ToList();
|
||||
DebugConsole.NewMessage($"Automatically placed items in { string.Join(", ", subNames) }:");
|
||||
foreach (string itemName in spawnedItems.Select(it => it.Name).Distinct())
|
||||
foreach (string itemName in itemsToSpawn.Select(it => it.Name).Distinct())
|
||||
{
|
||||
DebugConsole.NewMessage(" - " + itemName + " x" + spawnedItems.Count(it => it.Name == itemName));
|
||||
DebugConsole.NewMessage(" - " + itemName + " x" + itemsToSpawn.Count(it => it.Name == itemName));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,24 +220,28 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (Location.TakenItem takenItem in GameMain.GameSession.StartLocation.TakenItems)
|
||||
{
|
||||
var matchingItem = spawnedItems.Find(it => takenItem.Matches(it));
|
||||
var matchingItem = itemsToSpawn.Find(it => takenItem.Matches(it));
|
||||
if (matchingItem == null) { continue; }
|
||||
var containedItems = spawnedItems.FindAll(it => it.ParentInventory?.Owner == matchingItem);
|
||||
if (OutputDebugInfo)
|
||||
{
|
||||
DebugConsole.NewMessage($"Removing the stolen item: {matchingItem.Prefab.Identifier} ({matchingItem.ID})");
|
||||
}
|
||||
var containedItems = itemsToSpawn.FindAll(it => it.ParentInventory?.Owner == matchingItem);
|
||||
matchingItem.Remove();
|
||||
spawnedItems.Remove(matchingItem);
|
||||
itemsToSpawn.Remove(matchingItem);
|
||||
foreach (Item containedItem in containedItems)
|
||||
{
|
||||
containedItem.Remove();
|
||||
spawnedItems.Remove(containedItem);
|
||||
itemsToSpawn.Remove(containedItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (Item spawnedItem in spawnedItems)
|
||||
foreach (Item item in itemsToSpawn)
|
||||
{
|
||||
#if SERVER
|
||||
Entity.Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(spawnedItem));
|
||||
Entity.Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(item));
|
||||
#endif
|
||||
foreach (ItemComponent ic in spawnedItem.Components)
|
||||
foreach (ItemComponent ic in item.Components)
|
||||
{
|
||||
ic.OnItemLoaded();
|
||||
}
|
||||
@@ -186,9 +257,12 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
bool success = false;
|
||||
bool isCampaign = GameMain.GameSession?.GameMode is CampaignMode;
|
||||
foreach (PreferredContainer preferredContainer in itemPrefab.PreferredContainers)
|
||||
{
|
||||
if (preferredContainer.SpawnProbability <= 0.0f || preferredContainer.MaxAmount <= 0) { continue; }
|
||||
if (preferredContainer.CampaignOnly && !isCampaign) { continue; }
|
||||
if (preferredContainer.NotCampaign && isCampaign) { continue; }
|
||||
if (preferredContainer.SpawnProbability <= 0.0f || preferredContainer.MaxAmount <= 0 && preferredContainer.Amount <= 0) { continue; }
|
||||
validContainers = GetValidContainers(preferredContainer, containers, validContainers, primary: true);
|
||||
if (validContainers.None())
|
||||
{
|
||||
@@ -196,10 +270,10 @@ namespace Barotrauma
|
||||
}
|
||||
foreach (var validContainer in validContainers)
|
||||
{
|
||||
var newItems = SpawnItem(itemPrefab, containers, validContainer, difficultyModifier);
|
||||
var newItems = CreateItems(itemPrefab, containers, validContainer);
|
||||
if (newItems.Any())
|
||||
{
|
||||
spawnedItems.AddRange(newItems);
|
||||
itemsToSpawn.AddRange(newItems);
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
@@ -238,16 +312,20 @@ namespace Barotrauma
|
||||
(3, 0.0f),
|
||||
};
|
||||
|
||||
private static List<Item> SpawnItem(ItemPrefab itemPrefab, List<ItemContainer> containers, KeyValuePair<ItemContainer, PreferredContainer> validContainer, float difficultyModifier)
|
||||
private static List<Item> CreateItems(ItemPrefab itemPrefab, List<ItemContainer> containers, KeyValuePair<ItemContainer, PreferredContainer> validContainer)
|
||||
{
|
||||
List<Item> spawnedItems = new List<Item>();
|
||||
if (Rand.Value(Rand.RandSync.ServerAndClient) > validContainer.Value.SpawnProbability * (1f + difficultyModifier)) { return spawnedItems; }
|
||||
List<Item> newItems = new List<Item>();
|
||||
if (Rand.Value(Rand.RandSync.ServerAndClient) > validContainer.Value.SpawnProbability) { return newItems; }
|
||||
// Don't add dangerously reactive materials in thalamus wrecks
|
||||
if (validContainer.Key.Item.Submarine.WreckAI != null && itemPrefab.Tags.Contains("explodesinwater"))
|
||||
{
|
||||
return spawnedItems;
|
||||
return newItems;
|
||||
}
|
||||
int amount = validContainer.Value.Amount;
|
||||
if (amount == 0)
|
||||
{
|
||||
amount = Rand.Range(validContainer.Value.MinAmount, validContainer.Value.MaxAmount + 1, Rand.RandSync.ServerAndClient);
|
||||
}
|
||||
int amount = Rand.Range(validContainer.Value.MinAmount, validContainer.Value.MaxAmount + 1, Rand.RandSync.ServerAndClient);
|
||||
for (int i = 0; i < amount; i++)
|
||||
{
|
||||
if (validContainer.Key.Inventory.IsFull(takeStacksIntoAccount: true))
|
||||
@@ -255,14 +333,12 @@ namespace Barotrauma
|
||||
containers.Remove(validContainer.Key);
|
||||
break;
|
||||
}
|
||||
|
||||
var existingItem = validContainer.Key.Inventory.AllItems.FirstOrDefault(it => it.Prefab == itemPrefab);
|
||||
int quality =
|
||||
existingItem?.Quality ??
|
||||
ToolBox.SelectWeightedRandom(
|
||||
qualityCommonnesses.Select(q => q.quality).ToList(),
|
||||
qualityCommonnesses.Select(q => q.commonness).ToList(),
|
||||
Rand.RandSync.ServerAndClient);
|
||||
qualityCommonnesses.Select(q => q.commonness).ToList(), Rand.RandSync.ServerAndClient);
|
||||
if (!validContainer.Key.Inventory.CanBePut(itemPrefab, quality: quality)) { break; }
|
||||
var item = new Item(itemPrefab, validContainer.Key.Item.Position, validContainer.Key.Item.Submarine, callOnItemLoaded: false)
|
||||
{
|
||||
@@ -277,11 +353,11 @@ namespace Barotrauma
|
||||
{
|
||||
wifiComponent.TeamID = validContainer.Key.Item.Submarine.TeamID;
|
||||
}
|
||||
spawnedItems.Add(item);
|
||||
newItems.Add(item);
|
||||
validContainer.Key.Inventory.TryPutItem(item, null, createNetworkEvent: false);
|
||||
containers.AddRange(item.GetComponents<ItemContainer>());
|
||||
}
|
||||
return spawnedItems;
|
||||
return newItems;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user