Build 0.18.0.0
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);
|
||||
}
|
||||
|
||||
@@ -1055,6 +1055,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 +1430,22 @@ 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)
|
||||
{
|
||||
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 +1977,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 +3494,7 @@ namespace Barotrauma
|
||||
{
|
||||
observeTimer = targetParams.Timer * Rand.Range(0.75f, 1.25f);
|
||||
}
|
||||
reachTimer = 0;
|
||||
}
|
||||
|
||||
protected override void OnStateChanged(AIState from, AIState to)
|
||||
@@ -3496,6 +3515,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.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1149,7 +1178,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 +1385,10 @@ namespace Barotrauma
|
||||
|
||||
Character FindInstigator()
|
||||
{
|
||||
if (Character.IsInstigator)
|
||||
{
|
||||
return Character;
|
||||
}
|
||||
if (attacker.IsInstigator)
|
||||
{
|
||||
return attacker;
|
||||
@@ -1545,7 +1578,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 +1922,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,10 +1930,22 @@ 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));
|
||||
}
|
||||
@@ -1911,6 +1956,7 @@ namespace Barotrauma
|
||||
if (item.Prefab != null && item.Prefab.IsDangerous)
|
||||
{
|
||||
dangerousItemsFactor = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
float safety = oxygenFactor * waterFactor * fireFactor * enemyFactor * dangerousItemsFactor;
|
||||
|
||||
+1
-1
@@ -113,7 +113,7 @@ 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;
|
||||
}
|
||||
|
||||
+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()
|
||||
|
||||
+1
@@ -165,6 +165,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()
|
||||
},
|
||||
|
||||
+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; }
|
||||
|
||||
+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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
+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))
|
||||
{
|
||||
|
||||
@@ -153,12 +153,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.");
|
||||
}
|
||||
@@ -560,18 +560,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1443,7 +1460,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);
|
||||
@@ -3954,7 +3971,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)
|
||||
|
||||
@@ -252,12 +252,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));
|
||||
}
|
||||
|
||||
@@ -1182,7 +1181,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(":");
|
||||
|
||||
+1
@@ -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"];
|
||||
|
||||
@@ -124,7 +124,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);
|
||||
|
||||
@@ -725,6 +736,8 @@ namespace Barotrauma
|
||||
AddLimbAffliction(limbHealth: null, newAffliction, allowStacking);
|
||||
}
|
||||
|
||||
partial void UpdateSkinTint();
|
||||
|
||||
partial void UpdateLimbAfflictionOverlays();
|
||||
|
||||
public void Update(float deltaTime)
|
||||
@@ -788,7 +801,7 @@ namespace Barotrauma
|
||||
if (!Character.GodMode)
|
||||
{
|
||||
UpdateLimbAfflictionOverlays();
|
||||
UpdateSkinTint();
|
||||
UpdateSkinTint();
|
||||
CalculateVitality();
|
||||
|
||||
if (Vitality <= MinVitality)
|
||||
@@ -798,23 +811,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;
|
||||
@@ -905,6 +901,7 @@ namespace Barotrauma
|
||||
if (Unkillable || Character.GodMode) { return; }
|
||||
|
||||
var (type, affliction) = GetCauseOfDeath();
|
||||
UpdateLimbAfflictionOverlays();
|
||||
UpdateSkinTint();
|
||||
Character.Kill(type, affliction);
|
||||
#if CLIENT
|
||||
|
||||
@@ -105,9 +105,9 @@ 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)
|
||||
|
||||
@@ -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>())
|
||||
{
|
||||
|
||||
@@ -293,6 +293,6 @@ namespace Barotrauma
|
||||
//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
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
+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)
|
||||
{
|
||||
|
||||
+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);
|
||||
|
||||
+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);
|
||||
|
||||
+1
-2
@@ -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, 17, 16, 0);
|
||||
|
||||
public const string LocalModsDir = "LocalMods";
|
||||
public static readonly string WorkshopModsDir = Barotrauma.IO.Path.Combine(
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -51,7 +51,7 @@ namespace Barotrauma
|
||||
=> Element.Descendants().Select(e => new ContentXElement(ContentPackage, e));
|
||||
|
||||
public IEnumerable<ContentXElement> GetChildElements(string name)
|
||||
=> Elements().Where(e => string.Equals(name, e.Name.LocalName, StringComparison.CurrentCultureIgnoreCase));
|
||||
=> Elements().Where(e => string.Equals(name, e.Name.LocalName, StringComparison.InvariantCultureIgnoreCase));
|
||||
|
||||
public XAttribute? GetAttribute(string name) => Element.GetAttribute(name);
|
||||
|
||||
|
||||
+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.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -780,7 +780,7 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
GameMain.GameSession.EventManager.ActiveEvents.Add(newEvent);
|
||||
newEvent.Init(true);
|
||||
newEvent.Init();
|
||||
NewMessage($"Initialized event {eventPrefab.Identifier}", Color.Aqua);
|
||||
return;
|
||||
}
|
||||
@@ -1829,6 +1829,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.StartItemSet = args[0].ToIdentifier();
|
||||
NewMessage($"Start item set changed to \"{AutoItemPlacer.StartItemSet}\"");
|
||||
}, 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, () =>
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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
|
||||
@@ -362,8 +364,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);
|
||||
@@ -421,7 +424,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 +441,7 @@ namespace Barotrauma
|
||||
var newEventSet = SelectRandomEvents(eventSet.ChildSets, random: rand);
|
||||
if (newEventSet != null)
|
||||
{
|
||||
CreateEvents(newEventSet, rand);
|
||||
CreateEvents(newEventSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -451,7 +454,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 +468,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (!IsValidForLevel(childEventSet, level)) { continue; }
|
||||
if (location != null && !IsValidForLocation(childEventSet, location)) { continue; }
|
||||
CreateEvents(childEventSet, rand);
|
||||
CreateEvents(childEventSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -666,6 +669,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)
|
||||
@@ -244,6 +246,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 +457,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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":
|
||||
|
||||
@@ -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))
|
||||
{
|
||||
@@ -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(); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,14 +173,14 @@ namespace Barotrauma
|
||||
|
||||
if (!Actions.Any())
|
||||
{
|
||||
Finished();
|
||||
Finish();
|
||||
return;
|
||||
}
|
||||
|
||||
var currentAction = Actions[CurrentActionIndex];
|
||||
if (!currentAction.CanBeFinished())
|
||||
{
|
||||
Finished();
|
||||
Finish();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -207,7 +207,7 @@ namespace Barotrauma
|
||||
|
||||
if (CurrentActionIndex >= Actions.Count || CurrentActionIndex < 0)
|
||||
{
|
||||
Finished();
|
||||
Finish();
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -232,9 +232,9 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void Finished()
|
||||
public override void Finish()
|
||||
{
|
||||
base.Finished();
|
||||
base.Finish();
|
||||
GameAnalyticsManager.AddDesignEvent($"ScriptedEvent:{prefab.Identifier}:Finished:{CurrentActionIndex}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,36 +3,32 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
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()
|
||||
{
|
||||
if (GameMain.NetworkMember != null && !GameMain.NetworkMember.IsServer) { return; }
|
||||
|
||||
for (int i = 0; i < Submarine.MainSubs.Length; i++)
|
||||
bool skipMainSubs = GameMain.GameSession.GameMode is CampaignMode { IsFirstRound: false };
|
||||
if (!skipMainSubs)
|
||||
{
|
||||
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) { continue; }
|
||||
SpawnStartItems(sub);
|
||||
var subs = sub.GetConnectedSubs().Where(s => s.TeamID == sub.TeamID);
|
||||
CreateAndPlace(subs);
|
||||
subs.ForEach(s => s.Info.InitialSuppliesSpawned = true);
|
||||
}
|
||||
}
|
||||
|
||||
float difficultyModifier = GetLevelDifficultyModifier();
|
||||
foreach (var sub in Submarine.Loaded)
|
||||
{
|
||||
if (sub.Info.Type == SubmarineType.Player ||
|
||||
@@ -42,33 +38,93 @@ namespace Barotrauma
|
||||
{
|
||||
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 StartItemSet = new Identifier("normal");
|
||||
private static void SpawnStartItems(Submarine sub)
|
||||
{
|
||||
if (!Barotrauma.StartItemSet.Sets.TryGet(StartItemSet, out StartItemSet itemSet))
|
||||
{
|
||||
DebugConsole.AddWarning($"Couldn't find a start item set matching the identifier \"{StartItemSet}\"!");
|
||||
return;
|
||||
}
|
||||
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 +132,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 +156,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.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 +197,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 +209,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 +246,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 +259,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 +301,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 +322,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 +342,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,14 +22,14 @@ namespace Barotrauma
|
||||
public int Quantity { get; set; }
|
||||
public bool? IsStoreComponentEnabled { get; set; }
|
||||
|
||||
public readonly int BuyerCharacterInfoId;
|
||||
public readonly int BuyerCharacterInfoIdentifier;
|
||||
|
||||
public PurchasedItem(ItemPrefab itemPrefab, int quantity, int buyerCharacterInfoId)
|
||||
{
|
||||
ItemPrefabIdentifier = itemPrefab.Identifier;
|
||||
Quantity = quantity;
|
||||
IsStoreComponentEnabled = null;
|
||||
BuyerCharacterInfoId = buyerCharacterInfoId;
|
||||
BuyerCharacterInfoIdentifier = buyerCharacterInfoId;
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
@@ -44,7 +44,7 @@ namespace Barotrauma
|
||||
ItemPrefabIdentifier = itemPrefabId;
|
||||
Quantity = quantity;
|
||||
IsStoreComponentEnabled = null;
|
||||
BuyerCharacterInfoId = buyer?.Character?.Info?.ID ?? Character.Controlled?.Info?.ID ?? 0;
|
||||
BuyerCharacterInfoIdentifier = buyer?.Character?.Info?.GetIdentifier() ?? Character.Controlled?.Info?.GetIdentifier() ?? 0;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
@@ -284,11 +284,10 @@ namespace Barotrauma
|
||||
foreach (PurchasedItem item in newItems)
|
||||
{
|
||||
int itemValue = item.Quantity * buyValues[item.ItemPrefab];
|
||||
GameAnalyticsManager.AddMoneySpentEvent(itemValue, GameAnalyticsManager.MoneySink.Store, item.ItemPrefab.Identifier.Value);
|
||||
sb.Append($"\n - {item.ItemPrefab.Name} x{item.Quantity}");
|
||||
price += itemValue;
|
||||
|
||||
}
|
||||
|
||||
GameServer.Log($"{NetworkMember.ClientLogName(client, client?.Name ?? "Unknown")} purchased {newItems.Count} item(s) for {TextManager.FormatCurrency(price)}{sb.ToString()}", ServerLog.MessageType.Money);
|
||||
}
|
||||
#endif
|
||||
@@ -317,7 +316,10 @@ namespace Barotrauma
|
||||
// Exchange money
|
||||
int itemValue = item.Quantity * buyValues[item.ItemPrefab];
|
||||
campaign.TryPurchase(client, itemValue);
|
||||
GameAnalyticsManager.AddMoneySpentEvent(itemValue, GameAnalyticsManager.MoneySink.Store, item.ItemPrefab.Identifier.Value);
|
||||
if (GameMain.IsSingleplayer)
|
||||
{
|
||||
GameAnalyticsManager.AddMoneySpentEvent(itemValue, GameAnalyticsManager.MoneySink.Store, item.ItemPrefab.Identifier.Value);
|
||||
}
|
||||
store.Balance += itemValue;
|
||||
if (removeFromCrate)
|
||||
{
|
||||
@@ -368,12 +370,13 @@ namespace Barotrauma
|
||||
|
||||
public void CreatePurchasedItems()
|
||||
{
|
||||
purchasedIDCards.Clear();
|
||||
var items = new List<PurchasedItem>();
|
||||
foreach (var storeSpecificItems in PurchasedItems)
|
||||
{
|
||||
items.AddRange(storeSpecificItems.Value);
|
||||
}
|
||||
CreateItems(items, Submarine.MainSub);
|
||||
CreateItems(items, Submarine.MainSub, this);
|
||||
PurchasedItems.Clear();
|
||||
OnPurchasedItemsChanged?.Invoke();
|
||||
}
|
||||
@@ -407,7 +410,7 @@ namespace Barotrauma
|
||||
if (!item.Components.All(c => !(c is Holdable h) || !h.Attachable || !h.Attached)) { return false; }
|
||||
if (!item.Components.All(c => !(c is Wire w) || w.Connections.All(c => c == null))) { return false; }
|
||||
if (!ItemAndAllContainersInteractable(item)) { return false; }
|
||||
if (item.GetRootContainer() is Item rootContainer && rootContainer.HasTag("donttakeitems")) { return false; }
|
||||
if (item.GetRootContainer() is Item rootContainer && rootContainer.HasTag("dontsellitems")) { return false; }
|
||||
return true;
|
||||
}).Distinct();
|
||||
|
||||
@@ -428,7 +431,7 @@ namespace Barotrauma
|
||||
if (!item.Prefab.CanBeSold) { return false; }
|
||||
if (item.SpawnedInCurrentOutpost) { return false; }
|
||||
if (!item.Prefab.AllowSellingWhenBroken && item.ConditionPercentage < 90.0f) { return false; }
|
||||
if (confirmedItems.Any(ci => ci.Item == item)) { return false; }
|
||||
if (confirmedItems != null && confirmedItems.Any(ci => ci.Item == item)) { return false; }
|
||||
if (UndeterminedSoldEntities.TryGetValue(item.Prefab, out int count))
|
||||
{
|
||||
int newCount = count - 1;
|
||||
@@ -448,13 +451,58 @@ namespace Barotrauma
|
||||
if (containedItems.None()) { return true; }
|
||||
// Allow selling the item if contained items are unsellable and set to be removed on deconstruct
|
||||
if (itemContainer.RemoveContainedItemsOnDeconstruct && containedItems.All(it => !it.Prefab.CanBeSold)) { return true; }
|
||||
// Otherwise there must be no contained items or the contained items must be confirmed as sold
|
||||
if (!containedItems.All(it => confirmedItems.Any(ci => ci.Item == it))) { return false; }
|
||||
if (confirmedItems != null)
|
||||
{
|
||||
// Otherwise there must be no contained items or the contained items must be confirmed as sold
|
||||
if (!containedItems.All(it => confirmedItems.Any(ci => ci.Item == it))) { return false; }
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void CreateItems(List<PurchasedItem> itemsToSpawn, Submarine sub)
|
||||
public static ItemContainer GetOrCreateCargoContainerFor(ItemPrefab item, ISpatialEntity cargoRoomOrSpawnPoint, ref List<ItemContainer> availableContainers)
|
||||
{
|
||||
ItemContainer itemContainer = null;
|
||||
if (!string.IsNullOrEmpty(item.CargoContainerIdentifier))
|
||||
{
|
||||
itemContainer = availableContainers.Find(ac =>
|
||||
ac.Inventory.CanBePut(item) &&
|
||||
(ac.Item.Prefab.Identifier == item.CargoContainerIdentifier ||
|
||||
ac.Item.Prefab.Tags.Contains(item.CargoContainerIdentifier)));
|
||||
|
||||
if (itemContainer == null)
|
||||
{
|
||||
ItemPrefab containerPrefab = ItemPrefab.Prefabs.Find(ep =>
|
||||
ep.Identifier == item.CargoContainerIdentifier ||
|
||||
(ep.Tags != null && ep.Tags.Contains(item.CargoContainerIdentifier)));
|
||||
|
||||
if (containerPrefab == null)
|
||||
{
|
||||
DebugConsole.AddWarning($"CargoManager: could not find the item prefab for container {item.CargoContainerIdentifier}!");
|
||||
return null;
|
||||
}
|
||||
|
||||
Vector2 containerPosition = cargoRoomOrSpawnPoint is Hull cargoRoom ? GetCargoPos(cargoRoom, containerPrefab) : cargoRoomOrSpawnPoint.Position;
|
||||
Item containerItem = new Item(containerPrefab, containerPosition, cargoRoomOrSpawnPoint.Submarine);
|
||||
itemContainer = containerItem.GetComponent<ItemContainer>();
|
||||
if (itemContainer == null)
|
||||
{
|
||||
DebugConsole.AddWarning($"CargoManager: No ItemContainer component found in {containerItem.Prefab.Identifier}!");
|
||||
return null;
|
||||
}
|
||||
availableContainers.Add(itemContainer);
|
||||
#if SERVER
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
Entity.Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(itemContainer.Item));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
return itemContainer;
|
||||
}
|
||||
|
||||
public static void CreateItems(List<PurchasedItem> itemsToSpawn, Submarine sub, CargoManager cargoManager)
|
||||
{
|
||||
if (itemsToSpawn.Count == 0) { return; }
|
||||
|
||||
@@ -496,60 +544,26 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
List<ItemContainer> availableContainers = new List<ItemContainer>();
|
||||
ItemPrefab containerPrefab = null;
|
||||
foreach (PurchasedItem pi in itemsToSpawn)
|
||||
{
|
||||
Vector2 position = GetCargoPos(cargoRoom, pi.ItemPrefab);
|
||||
|
||||
for (int i = 0; i < pi.Quantity; i++)
|
||||
{
|
||||
ItemContainer itemContainer = null;
|
||||
if (!string.IsNullOrEmpty(pi.ItemPrefab.CargoContainerIdentifier))
|
||||
{
|
||||
itemContainer = availableContainers.Find(ac =>
|
||||
ac.Inventory.CanBePut(pi.ItemPrefab) &&
|
||||
(ac.Item.Prefab.Identifier == pi.ItemPrefab.CargoContainerIdentifier ||
|
||||
ac.Item.Prefab.Tags.Contains(pi.ItemPrefab.CargoContainerIdentifier.ToLowerInvariant())));
|
||||
|
||||
if (itemContainer == null)
|
||||
{
|
||||
containerPrefab = ItemPrefab.Prefabs.Find(ep =>
|
||||
ep.Identifier == pi.ItemPrefab.CargoContainerIdentifier ||
|
||||
(ep.Tags != null && ep.Tags.Contains(pi.ItemPrefab.CargoContainerIdentifier.ToLowerInvariant())));
|
||||
|
||||
if (containerPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Cargo spawning failed - could not find the item prefab for container \"" + pi.ItemPrefab.CargoContainerIdentifier + "\"!");
|
||||
continue;
|
||||
}
|
||||
|
||||
Vector2 containerPosition = GetCargoPos(cargoRoom, containerPrefab);
|
||||
Item containerItem = new Item(containerPrefab, containerPosition, wp.Submarine);
|
||||
itemContainer = containerItem.GetComponent<ItemContainer>();
|
||||
if (itemContainer == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Cargo spawning failed - container \"" + containerItem.Name + "\" does not have an ItemContainer component!");
|
||||
continue;
|
||||
}
|
||||
availableContainers.Add(itemContainer);
|
||||
#if SERVER
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
Entity.Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(itemContainer.Item));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
var item = new Item(pi.ItemPrefab, position, wp.Submarine);
|
||||
itemContainer?.Inventory.TryPutItem(item, null);
|
||||
|
||||
itemSpawned(item);
|
||||
var itemContainer = GetOrCreateCargoContainerFor(pi.ItemPrefab, cargoRoom, ref availableContainers);
|
||||
itemContainer?.Inventory.TryPutItem(item, null);
|
||||
var idCard = item.GetComponent<IdCard>();
|
||||
if (cargoManager != null && idCard != null && pi.BuyerCharacterInfoIdentifier != 0)
|
||||
{
|
||||
cargoManager.purchasedIDCards.Add((pi, idCard));
|
||||
}
|
||||
itemSpawned(pi, item);
|
||||
#if SERVER
|
||||
Entity.Spawner?.CreateNetworkEvent(new EntitySpawner.SpawnEntity(item));
|
||||
#endif
|
||||
(itemContainer?.Item ?? item).CampaignInteractionType = CampaignMode.InteractionType.Cargo;
|
||||
static void itemSpawned(Item item)
|
||||
static void itemSpawned(PurchasedItem purchased, Item item)
|
||||
{
|
||||
Submarine sub = item.Submarine ?? item.GetRootContainer()?.Submarine;
|
||||
if (sub != null)
|
||||
@@ -565,6 +579,23 @@ namespace Barotrauma
|
||||
itemsToSpawn.Clear();
|
||||
}
|
||||
|
||||
private readonly List<(PurchasedItem purchaseInfo, IdCard idCard)> purchasedIDCards = new List<(PurchasedItem purchaseInfo, IdCard idCard)>();
|
||||
public void InitPurchasedIDCards()
|
||||
{
|
||||
foreach ((PurchasedItem purchased, IdCard idCard) in purchasedIDCards)
|
||||
{
|
||||
if (idCard != null && purchased.BuyerCharacterInfoIdentifier != 0)
|
||||
{
|
||||
var owner = Character.CharacterList.Find(c => c.Info?.GetIdentifier() == purchased.BuyerCharacterInfoIdentifier);
|
||||
if (owner?.Info != null)
|
||||
{
|
||||
var mainSubSpawnPoints = WayPoint.SelectCrewSpawnPoints(new List<CharacterInfo>() { owner.Info }, Submarine.MainSub);
|
||||
idCard.Initialize(mainSubSpawnPoints.FirstOrDefault(), owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Vector2 GetCargoPos(Hull hull, ItemPrefab itemPrefab)
|
||||
{
|
||||
float floorPos = hull.Rect.Y - hull.Rect.Height;
|
||||
@@ -603,7 +634,7 @@ namespace Barotrauma
|
||||
new XAttribute("id", item.ItemPrefab.Identifier),
|
||||
new XAttribute("qty", item.Quantity),
|
||||
new XAttribute("storeid", storeSpecificItems.Key),
|
||||
new XAttribute("buyer", item.BuyerCharacterInfoId)));
|
||||
new XAttribute("buyer", item.BuyerCharacterInfoIdentifier)));
|
||||
}
|
||||
}
|
||||
parentElement.Add(itemsElement);
|
||||
|
||||
@@ -51,8 +51,6 @@ namespace Barotrauma
|
||||
|
||||
public ReadyCheck ActiveReadyCheck;
|
||||
|
||||
public XElement ActiveOrdersElement { get; set; }
|
||||
|
||||
public CrewManager(bool isSinglePlayer)
|
||||
{
|
||||
IsSinglePlayer = isSinglePlayer;
|
||||
@@ -493,9 +491,8 @@ namespace Barotrauma
|
||||
|
||||
partial void UpdateProjectSpecific(float deltaTime);
|
||||
|
||||
private void SaveActiveOrders(XElement parentElement)
|
||||
public void SaveActiveOrders(XElement element)
|
||||
{
|
||||
ActiveOrdersElement = new XElement("activeorders");
|
||||
// Only save orders with no fade out time (e.g. ignore orders)
|
||||
var ordersToSave = new List<Order>();
|
||||
foreach (var activeOrder in ActiveOrders)
|
||||
@@ -504,14 +501,13 @@ namespace Barotrauma
|
||||
if (order == null || activeOrder.FadeOutTime.HasValue) { continue; }
|
||||
ordersToSave.Add(order.WithManualPriority(CharacterInfo.HighestManualOrderPriority));
|
||||
}
|
||||
CharacterInfo.SaveOrders(ActiveOrdersElement, ordersToSave.ToArray());
|
||||
parentElement?.Add(ActiveOrdersElement);
|
||||
CharacterInfo.SaveOrders(element, ordersToSave.ToArray());
|
||||
}
|
||||
|
||||
public void LoadActiveOrders()
|
||||
public void LoadActiveOrders(XElement element)
|
||||
{
|
||||
if (ActiveOrdersElement == null) { return; }
|
||||
foreach (var orderInfo in CharacterInfo.LoadOrders(ActiveOrdersElement))
|
||||
if (element == null) { return; }
|
||||
foreach (var orderInfo in CharacterInfo.LoadOrders(element))
|
||||
{
|
||||
IIgnorable ignoreTarget = null;
|
||||
if (orderInfo.IsIgnoreOrder)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Networking;
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
@@ -107,6 +108,8 @@ namespace Barotrauma
|
||||
|
||||
protected XElement petsElement;
|
||||
|
||||
protected XElement ActiveOrdersElement { get; set; }
|
||||
|
||||
public CampaignSettings Settings;
|
||||
|
||||
private readonly List<Mission> extraMissions = new List<Mission>();
|
||||
@@ -739,8 +742,10 @@ namespace Barotrauma
|
||||
foreach (LocationConnection connection in Map.Connections)
|
||||
{
|
||||
connection.Difficulty = MathHelper.Lerp(connection.Difficulty, 100.0f, 0.25f);
|
||||
connection.LevelData.Difficulty = connection.Difficulty;
|
||||
connection.LevelData.IsBeaconActive = false;
|
||||
connection.LevelData = new LevelData(connection)
|
||||
{
|
||||
IsBeaconActive = false
|
||||
};
|
||||
connection.LevelData.HasHuntingGrounds = connection.LevelData.OriginallyHadHuntingGrounds;
|
||||
}
|
||||
foreach (Location location in Map.Locations)
|
||||
@@ -1032,5 +1037,184 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
protected void LeaveUnconnectedSubs(Submarine leavingSub)
|
||||
{
|
||||
if (leavingSub != Submarine.MainSub && !leavingSub.DockedTo.Contains(Submarine.MainSub))
|
||||
{
|
||||
Submarine.MainSub = leavingSub;
|
||||
GameMain.GameSession.Submarine = leavingSub;
|
||||
GameMain.GameSession.SubmarineInfo = leavingSub.Info;
|
||||
leavingSub.Info.FilePath = System.IO.Path.Combine(SaveUtil.TempPath, leavingSub.Info.Name + ".sub");
|
||||
var subsToLeaveBehind = GetSubsToLeaveBehind(leavingSub);
|
||||
GameMain.GameSession.OwnedSubmarines.Add(leavingSub.Info);
|
||||
foreach (Submarine sub in subsToLeaveBehind)
|
||||
{
|
||||
GameMain.GameSession.OwnedSubmarines.RemoveAll(s => s != leavingSub.Info && s.Name == sub.Info.Name);
|
||||
MapEntity.mapEntityList.RemoveAll(e => e.Submarine == sub && e is LinkedSubmarine);
|
||||
LinkedSubmarine.CreateDummy(leavingSub, sub);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public SubmarineInfo SwitchSubs()
|
||||
{
|
||||
TransferItemsBetweenSubs();
|
||||
RefreshOwnedSubmarines();
|
||||
PendingSubmarineSwitch = null;
|
||||
return GameMain.GameSession.SubmarineInfo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Also serializes the current sub.
|
||||
/// </summary>
|
||||
protected void TransferItemsBetweenSubs()
|
||||
{
|
||||
Submarine currentSub = GameMain.GameSession.Submarine;
|
||||
if (currentSub == null || currentSub.Removed)
|
||||
{
|
||||
DebugConsole.ThrowError("Cannot transfer items between subs, because the current sub is null or removed!");
|
||||
return;
|
||||
}
|
||||
var itemsToTransfer = new List<(Item item, Item container)>();
|
||||
if (PendingSubmarineSwitch != null)
|
||||
{
|
||||
// Remove items from the old sub
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.Removed) { continue; }
|
||||
if (item.NonInteractable) { continue; }
|
||||
if (item.HiddenInGame) { continue; }
|
||||
if (item.Submarine != currentSub) { continue; }
|
||||
if (item.Prefab.DontTransferBetweenSubs) { continue; }
|
||||
if (item.GetRootInventoryOwner() is Character) { continue; }
|
||||
if (item.GetComponent<Holdable>() == null && item.GetComponent<Wearable>() == null && item.GetComponent<Projectile>() == null) { continue; }
|
||||
if (item.Components.Any(c => c is Holdable h && h.Attached)) { continue; }
|
||||
if (item.Components.Any(c => c is Wire w && w.Connections.Any(c => c != null))) { continue; }
|
||||
itemsToTransfer.Add((item, item.Container));
|
||||
item.Submarine = null;
|
||||
}
|
||||
foreach (var (item, container) in itemsToTransfer)
|
||||
{
|
||||
if (container?.Submarine != null)
|
||||
{
|
||||
// Drop the item if it's not inside another item set to be transferred.
|
||||
item.Drop(null, createNetworkEvent: false, setTransform: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Serialize the current sub
|
||||
GameMain.GameSession.SubmarineInfo = new SubmarineInfo(currentSub);
|
||||
if (PendingSubmarineSwitch != null && itemsToTransfer.Any())
|
||||
{
|
||||
// Load the new sub
|
||||
var newSub = new Submarine(PendingSubmarineSwitch);
|
||||
// Move the transferred items
|
||||
List<ItemContainer> availableContainers = Item.ItemList
|
||||
.Where(it => it.Submarine == newSub && it.HasTag("crate") && !it.NonInteractable && !it.HiddenInGame && !it.Removed)
|
||||
.Select(it => it.GetComponent<ItemContainer>())
|
||||
.Where(c => c != null)
|
||||
.ToList();
|
||||
foreach (var (item, oldContainer) in itemsToTransfer)
|
||||
{
|
||||
Item newContainer = null;
|
||||
item.Submarine = newSub;
|
||||
if (item.Container == null)
|
||||
{
|
||||
newContainer = newSub.FindContainerFor(item, onlyPrimary: true, checkTransferConditions: true);
|
||||
}
|
||||
if (item.Container == null && (newContainer == null || !newContainer.OwnInventory.TryPutItem(item, user: null, createNetworkEvent: false)))
|
||||
{
|
||||
WayPoint wp = WayPoint.GetRandom(SpawnType.Cargo, null, newSub);
|
||||
Hull spawnHull = wp?.CurrentHull ?? Hull.HullList.Where(h => h.Submarine == newSub && !h.IsWetRoom).GetRandomUnsynced();
|
||||
if (spawnHull == null)
|
||||
{
|
||||
DebugConsole.AddWarning($"Failed to transfer items between subs. No cargo waypoint or dry hulls found in the new sub.");
|
||||
return;
|
||||
}
|
||||
if (spawnHull != null)
|
||||
{
|
||||
var cargoContainer = CargoManager.GetOrCreateCargoContainerFor(item.Prefab, spawnHull, ref availableContainers);
|
||||
if (cargoContainer == null || !cargoContainer.Inventory.TryPutItem(item, user: null, createNetworkEvent: false))
|
||||
{
|
||||
item.SetTransform(wp.SimPosition, 0.0f, findNewHull: false, setPrevTransform: false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.AddWarning($"Failed to transfer item {item.Prefab.Identifier} ({item.ID}), because no cargo spawn point could be found!");
|
||||
}
|
||||
}
|
||||
string newContainerName = newContainer == null ? "(null)" : $"{newContainer.Prefab.Identifier} ({newContainer.Tags})";
|
||||
string msg = "Item transfer log error.";
|
||||
if (oldContainer != null)
|
||||
{
|
||||
if (newContainer == null && oldContainer == item.Container)
|
||||
{
|
||||
msg = $"Transferred {item.Prefab.Identifier} ({item.ID}) contained inside {oldContainer.Prefab.Identifier} ({oldContainer.ID})";
|
||||
}
|
||||
else
|
||||
{
|
||||
msg = $"Transferred {item.Prefab.Identifier} ({item.ID}) from {oldContainer.Prefab.Identifier} ({oldContainer.Tags}) to {newContainerName}";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
msg = $"Transferred {item.Prefab.Identifier} ({item.ID}) to {newContainerName}";
|
||||
}
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage(msg);
|
||||
#else
|
||||
DebugConsole.Log(msg);
|
||||
#endif
|
||||
}
|
||||
// Serialize the new sub
|
||||
PendingSubmarineSwitch = new SubmarineInfo(newSub);
|
||||
}
|
||||
}
|
||||
|
||||
protected void RefreshOwnedSubmarines()
|
||||
{
|
||||
if (PendingSubmarineSwitch != null)
|
||||
{
|
||||
SubmarineInfo previousSub = GameMain.GameSession.SubmarineInfo;
|
||||
GameMain.GameSession.SubmarineInfo = PendingSubmarineSwitch;
|
||||
|
||||
for (int i = 0; i < GameMain.GameSession.OwnedSubmarines.Count; i++)
|
||||
{
|
||||
if (GameMain.GameSession.OwnedSubmarines[i].Name == previousSub.Name)
|
||||
{
|
||||
GameMain.GameSession.OwnedSubmarines[i] = previousSub;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SavePets(XElement parentElement = null)
|
||||
{
|
||||
petsElement = new XElement("pets");
|
||||
PetBehavior.SavePets(petsElement);
|
||||
parentElement?.Add(petsElement);
|
||||
}
|
||||
|
||||
public void LoadPets()
|
||||
{
|
||||
if (petsElement != null)
|
||||
{
|
||||
PetBehavior.LoadPets(petsElement);
|
||||
}
|
||||
}
|
||||
|
||||
public void SaveActiveOrders(XElement parentElement = null)
|
||||
{
|
||||
ActiveOrdersElement = new XElement("activeorders");
|
||||
CrewManager?.SaveActiveOrders(ActiveOrdersElement);
|
||||
parentElement?.Add(ActiveOrdersElement);
|
||||
}
|
||||
|
||||
public void LoadActiveOrders()
|
||||
{
|
||||
CrewManager?.LoadActiveOrders(ActiveOrdersElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -155,7 +155,7 @@ namespace Barotrauma
|
||||
case "bots" when GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer:
|
||||
CrewManager.HasBots = subElement.GetAttributeBool("hasbots", false);
|
||||
CrewManager.AddCharacterElements(subElement);
|
||||
CrewManager.ActiveOrdersElement = subElement.GetChildElement("activeorders");
|
||||
ActiveOrdersElement = subElement.GetChildElement("activeorders");
|
||||
break;
|
||||
case "cargo":
|
||||
CargoManager?.LoadPurchasedItems(subElement);
|
||||
|
||||
@@ -275,7 +275,7 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Switch to another submarine. The sub is loaded when the next round starts.
|
||||
/// </summary>
|
||||
public SubmarineInfo SwitchSubmarine(SubmarineInfo newSubmarine, int cost, Client? client = null)
|
||||
public void SwitchSubmarine(SubmarineInfo newSubmarine, int cost, Client? client = null)
|
||||
{
|
||||
if (!OwnedSubmarines.Any(s => s.Name == newSubmarine.Name))
|
||||
{
|
||||
@@ -293,15 +293,12 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((GameMain.NetworkMember is null || GameMain.NetworkMember is { IsServer: true }) && cost > 0)
|
||||
{
|
||||
Campaign!.TryPurchase(client, cost);
|
||||
}
|
||||
GameAnalyticsManager.AddMoneySpentEvent(cost, GameAnalyticsManager.MoneySink.SubmarineSwitch, newSubmarine.Name);
|
||||
Campaign!.PendingSubmarineSwitch = newSubmarine;
|
||||
|
||||
return newSubmarine;
|
||||
}
|
||||
|
||||
public void PurchaseSubmarine(SubmarineInfo newSubmarine, Client? client = null)
|
||||
@@ -600,10 +597,13 @@ namespace Barotrauma
|
||||
{
|
||||
//only place items and corpses here in single player
|
||||
//the server does this after loading the respawn shuttle
|
||||
Level?.SpawnNPCs();
|
||||
Level?.SpawnCorpses();
|
||||
Level?.PrepareBeaconStation();
|
||||
AutoItemPlacer.PlaceIfNeeded();
|
||||
if (Level != null)
|
||||
{
|
||||
Level.SpawnNPCs();
|
||||
Level.SpawnCorpses();
|
||||
Level.PrepareBeaconStation();
|
||||
}
|
||||
AutoItemPlacer.SpawnItems();
|
||||
}
|
||||
if (GameMode is MultiPlayerCampaign mpCampaign)
|
||||
{
|
||||
@@ -836,6 +836,11 @@ namespace Barotrauma
|
||||
{
|
||||
GUI.TogglePauseMenu();
|
||||
}
|
||||
if (IsTabMenuOpen)
|
||||
{
|
||||
ToggleTabMenu();
|
||||
}
|
||||
|
||||
GUI.PreventPauseMenuToggle = true;
|
||||
|
||||
if (!(GameMode is TestGameMode) && Screen.Selected == GameMain.GameScreen && RoundSummary != null)
|
||||
@@ -1072,8 +1077,21 @@ namespace Barotrauma
|
||||
|
||||
rootElement.Add(new XAttribute("savetime", ToolBox.Epoch.NowLocal));
|
||||
rootElement.Add(new XAttribute("version", GameMain.Version));
|
||||
var submarineInfo = Campaign?.PendingSubmarineSwitch ?? SubmarineInfo;
|
||||
rootElement.Add(new XAttribute("submarine", submarineInfo == null ? "" : submarineInfo.Name));
|
||||
if (Submarine?.Info != null && !Submarine.Removed && Campaign != null)
|
||||
{
|
||||
bool hasNewPendingSub = Campaign.PendingSubmarineSwitch != null &&
|
||||
Campaign.PendingSubmarineSwitch.MD5Hash.StringRepresentation != Submarine.Info.MD5Hash.StringRepresentation;
|
||||
|
||||
if (hasNewPendingSub)
|
||||
{
|
||||
Campaign.SwitchSubs();
|
||||
}
|
||||
else
|
||||
{
|
||||
SubmarineInfo = new SubmarineInfo(Submarine);
|
||||
}
|
||||
}
|
||||
rootElement.Add(new XAttribute("submarine", SubmarineInfo == null ? "" : SubmarineInfo.Name));
|
||||
if (OwnedSubmarines != null)
|
||||
{
|
||||
List<string> ownedSubmarineNames = new List<string>();
|
||||
|
||||
@@ -17,7 +17,6 @@ namespace Barotrauma
|
||||
Deselect,
|
||||
Shoot,
|
||||
Command,
|
||||
ToggleInventory,
|
||||
TakeOneFromInventorySlot,
|
||||
TakeHalfFromInventorySlot,
|
||||
NextFireMode,
|
||||
|
||||
@@ -99,8 +99,8 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (!docked && value)
|
||||
{
|
||||
if (DockingTarget == null) AttemptDock();
|
||||
if (DockingTarget == null) return;
|
||||
if (DockingTarget == null) { AttemptDock(); }
|
||||
if (DockingTarget == null) { return; }
|
||||
|
||||
docked = true;
|
||||
}
|
||||
@@ -126,6 +126,14 @@ namespace Barotrauma.Items.Components
|
||||
/// </summary>
|
||||
public event Action OnUnDocked;
|
||||
|
||||
private bool outpostAutoDockingPromptShown;
|
||||
|
||||
enum AllowOutpostAutoDocking
|
||||
{
|
||||
Ask, Yes, No
|
||||
}
|
||||
private AllowOutpostAutoDocking allowOutpostAutoDocking = AllowOutpostAutoDocking.Ask;
|
||||
|
||||
public DockingPort(Item item, ContentXElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
@@ -622,7 +630,8 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
bodies[i + j * 2] = GameMain.World.CreateEdge(
|
||||
ConvertUnits.ToSimUnits(new Vector2(hullRects[i].X, hullRects[i].Y - hullRects[i].Height * j)),
|
||||
ConvertUnits.ToSimUnits(new Vector2(hullRects[i].Right, hullRects[i].Y - hullRects[i].Height * j)));
|
||||
ConvertUnits.ToSimUnits(new Vector2(hullRects[i].Right, hullRects[i].Y - hullRects[i].Height * j)),
|
||||
BodyType.Static);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -632,7 +641,9 @@ namespace Barotrauma.Items.Components
|
||||
ConvertUnits.ToSimUnits(hullRects[0].Width + hullRects[1].Width),
|
||||
ConvertUnits.ToSimUnits(hullRects[0].Height),
|
||||
density: 0.0f,
|
||||
offset: ConvertUnits.ToSimUnits(new Vector2(hullRects[0].Right, hullRects[0].Y - hullRects[0].Height / 2) - hulls[0].Submarine.HiddenSubPosition));
|
||||
offset: ConvertUnits.ToSimUnits(new Vector2(hullRects[0].Right, hullRects[0].Y - hullRects[0].Height / 2) - hulls[0].Submarine.HiddenSubPosition),
|
||||
Physics.CollisionWall,
|
||||
Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionCharacter | Physics.CollisionItemBlocking | Physics.CollisionProjectile);
|
||||
outsideBlocker.UserData = this;
|
||||
}
|
||||
|
||||
@@ -742,7 +753,8 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
bodies[i + j * 2] = GameMain.World.CreateEdge(
|
||||
ConvertUnits.ToSimUnits(new Vector2(hullRects[i].X + hullRects[i].Width * j, hullRects[i].Y)),
|
||||
ConvertUnits.ToSimUnits(new Vector2(hullRects[i].X + hullRects[i].Width * j, hullRects[i].Y - hullRects[i].Height)));
|
||||
ConvertUnits.ToSimUnits(new Vector2(hullRects[i].X + hullRects[i].Width * j, hullRects[i].Y - hullRects[i].Height)),
|
||||
BodyType.Static);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -752,7 +764,9 @@ namespace Barotrauma.Items.Components
|
||||
ConvertUnits.ToSimUnits(hullRects[0].Width),
|
||||
ConvertUnits.ToSimUnits(hullRects[0].Height + hullRects[1].Height),
|
||||
density: 0.0f,
|
||||
offset: ConvertUnits.ToSimUnits(new Vector2(hullRects[0].Center.X, hullRects[0].Y) - hulls[0].Submarine.HiddenSubPosition));
|
||||
offset: ConvertUnits.ToSimUnits(new Vector2(hullRects[0].Center.X, hullRects[0].Y) - hulls[0].Submarine.HiddenSubPosition),
|
||||
Physics.CollisionWall,
|
||||
Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionCharacter | Physics.CollisionItemBlocking | Physics.CollisionProjectile);
|
||||
outsideBlocker.UserData = this;
|
||||
}
|
||||
|
||||
@@ -778,8 +792,6 @@ namespace Barotrauma.Items.Components
|
||||
if (body == null) { continue; }
|
||||
body.BodyType = BodyType.Static;
|
||||
body.Friction = 0.5f;
|
||||
|
||||
body.CollisionCategories = Physics.CollisionWall;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -947,7 +959,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
foreach (Body body in bodies)
|
||||
{
|
||||
if (body == null) continue;
|
||||
if (body == null) { continue; }
|
||||
GameMain.World.Remove(body);
|
||||
}
|
||||
bodies = null;
|
||||
@@ -961,6 +973,9 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
#elif CLIENT
|
||||
autodockingVerification?.Close();
|
||||
autodockingVerification = null;
|
||||
#endif
|
||||
OnUnDocked?.Invoke();
|
||||
OnUnDocked = null;
|
||||
@@ -1140,27 +1155,86 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void ReceiveSignal(Signal signal, Connection connection)
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
#if CLIENT
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient &&
|
||||
!(GameMain.GameSession?.Campaign?.AllowedToManageCampaign(ClientPermissions.ManageMap) ?? false))
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (dockingCooldown > 0.0f) { return; }
|
||||
|
||||
bool wasDocked = docked;
|
||||
DockingPort prevDockingTarget = DockingTarget;
|
||||
|
||||
bool newDockedState = wasDocked;
|
||||
switch (connection.Name)
|
||||
{
|
||||
case "toggle":
|
||||
if (signal.value != "0")
|
||||
{
|
||||
Docked = !docked;
|
||||
newDockedState = !docked;
|
||||
}
|
||||
break;
|
||||
case "set_active":
|
||||
case "set_state":
|
||||
Docked = signal.value != "0";
|
||||
newDockedState = signal.value != "0";
|
||||
break;
|
||||
}
|
||||
|
||||
if (newDockedState != wasDocked)
|
||||
{
|
||||
bool tryingToToggleOutpostDocking = docked ?
|
||||
DockingTarget?.Item?.Submarine?.Info?.IsOutpost ?? false :
|
||||
FindAdjacentPort()?.Item?.Submarine?.Info?.IsOutpost ?? false;
|
||||
//trying to dock/undock from an outpost and the signal was sent by some automated system instead of a character
|
||||
// -> ask if the player really wants to dock/undock to prevent a softlock if someone's wired the docking port
|
||||
// in a way that makes always makes it dock/undock immediately at the start of the roun
|
||||
if (tryingToToggleOutpostDocking && signal.sender == null)
|
||||
{
|
||||
if (allowOutpostAutoDocking == AllowOutpostAutoDocking.Ask)
|
||||
{
|
||||
#if CLIENT
|
||||
if (!outpostAutoDockingPromptShown)
|
||||
{
|
||||
autodockingVerification = new GUIMessageBox(string.Empty,
|
||||
TextManager.Get(newDockedState ? "autodockverification" : "autoundockverification"),
|
||||
new LocalizedString[] { TextManager.Get("Yes"), TextManager.Get("No") });
|
||||
autodockingVerification.Buttons[0].OnClicked += (btn, userdata) =>
|
||||
{
|
||||
autodockingVerification?.Close();
|
||||
autodockingVerification = null;
|
||||
if (item.Removed || GameMain.Client == null) { return false; }
|
||||
allowOutpostAutoDocking = AllowOutpostAutoDocking.Yes;
|
||||
item.CreateClientEvent(this);
|
||||
return true;
|
||||
};
|
||||
autodockingVerification.Buttons[1].OnClicked += (btn, userdata) =>
|
||||
{
|
||||
autodockingVerification?.Close();
|
||||
autodockingVerification = null;
|
||||
if (item.Removed || GameMain.Client == null) { return false; }
|
||||
allowOutpostAutoDocking = AllowOutpostAutoDocking.No;
|
||||
item.CreateClientEvent(this);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
#endif
|
||||
outpostAutoDockingPromptShown = true;
|
||||
return;
|
||||
}
|
||||
else if (allowOutpostAutoDocking == AllowOutpostAutoDocking.No)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
|
||||
Docked = newDockedState;
|
||||
}
|
||||
|
||||
#if SERVER
|
||||
if (signal.sender != null && docked != wasDocked)
|
||||
{
|
||||
|
||||
@@ -241,12 +241,14 @@ namespace Barotrauma.Items.Components
|
||||
Body = new PhysicsBody(
|
||||
ConvertUnits.ToSimUnits(Math.Max(doorRect.Width, 1)),
|
||||
ConvertUnits.ToSimUnits(Math.Max(doorRect.Height, 1)),
|
||||
0.0f,
|
||||
1.5f)
|
||||
radius: 0.0f,
|
||||
density: 1.5f,
|
||||
BodyType.Static,
|
||||
Physics.CollisionWall,
|
||||
Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionCharacter | Physics.CollisionItemBlocking | Physics.CollisionProjectile,
|
||||
findNewContacts: false)
|
||||
{
|
||||
UserData = item,
|
||||
CollisionCategories = Physics.CollisionWall,
|
||||
BodyType = BodyType.Static,
|
||||
Friction = 0.5f
|
||||
};
|
||||
Body.SetTransformIgnoreContacts(
|
||||
@@ -258,11 +260,16 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public override void Move(Vector2 amount)
|
||||
public override void Move(Vector2 amount, bool ignoreContacts = false)
|
||||
{
|
||||
base.Move(amount);
|
||||
|
||||
Body?.SetTransform(Body.SimPosition + ConvertUnits.ToSimUnits(amount), 0.0f);
|
||||
if (ignoreContacts)
|
||||
{
|
||||
Body?.SetTransformIgnoreContacts(Body.SimPosition + ConvertUnits.ToSimUnits(amount), 0.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
Body?.SetTransform(Body.SimPosition + ConvertUnits.ToSimUnits(amount), 0.0f);
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
UpdateConvexHulls();
|
||||
|
||||
@@ -164,6 +164,8 @@ namespace Barotrauma.Items.Components
|
||||
public bool SwingWhenAiming { get; set; }
|
||||
[Editable, Serialize(false, IsPropertySaveable.No, description: "Should the item swing around when it's being used (for example, when firing a weapon or a welding tool).")]
|
||||
public bool SwingWhenUsing { get; set; }
|
||||
[Editable, Serialize(false, IsPropertySaveable.No)]
|
||||
public bool DisableHeadRotation { get; set; }
|
||||
|
||||
[ConditionallyEditable(ConditionallyEditable.ConditionType.Attachable, MinValueFloat = 0.0f, MaxValueFloat = 0.999f, DecimalCount = 3), Serialize(0.55f, IsPropertySaveable.No, description: "Sprite depth that's used when the item is NOT attached to a wall.")]
|
||||
public float SpriteDepthWhenDropped
|
||||
@@ -180,11 +182,12 @@ namespace Barotrauma.Items.Components
|
||||
Pusher = null;
|
||||
if (element.GetAttributeBool("blocksplayers", false))
|
||||
{
|
||||
Pusher = new PhysicsBody(item.body.width, item.body.height, item.body.radius, item.body.Density)
|
||||
Pusher = new PhysicsBody(item.body.width, item.body.height, item.body.radius,
|
||||
item.body.Density,
|
||||
BodyType.Dynamic,
|
||||
Physics.CollisionItemBlocking,
|
||||
Physics.CollisionCharacter | Physics.CollisionProjectile)
|
||||
{
|
||||
BodyType = BodyType.Dynamic,
|
||||
CollidesWith = Physics.CollisionCharacter | Physics.CollisionProjectile,
|
||||
CollisionCategories = Physics.CollisionItemBlocking,
|
||||
Enabled = false,
|
||||
UserData = this
|
||||
};
|
||||
|
||||
+16
-7
@@ -79,11 +79,18 @@ namespace Barotrauma.Items.Components
|
||||
IsActive = true;
|
||||
}
|
||||
|
||||
public override void Move(Vector2 amount)
|
||||
public override void Move(Vector2 amount, bool ignoreContacts = false)
|
||||
{
|
||||
if (trigger != null && amount.LengthSquared() > 0.00001f)
|
||||
{
|
||||
trigger.SetTransform(item.SimPosition, 0.0f);
|
||||
if (ignoreContacts)
|
||||
{
|
||||
trigger.SetTransformIgnoreContacts(item.SimPosition, 0.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
trigger.SetTransform(item.SimPosition, 0.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,17 +126,19 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
var body = item.body ?? holdable.Body;
|
||||
|
||||
|
||||
if (body != null)
|
||||
{
|
||||
trigger = new PhysicsBody(body.width, body.height, body.radius, body.Density)
|
||||
trigger = new PhysicsBody(body.width, body.height, body.radius,
|
||||
body.Density,
|
||||
BodyType.Static,
|
||||
Physics.CollisionWall,
|
||||
Physics.CollisionNone,
|
||||
findNewContacts: false)
|
||||
{
|
||||
UserData = item
|
||||
};
|
||||
trigger.FarseerBody.SetIsSensor(true);
|
||||
trigger.FarseerBody.BodyType = BodyType.Static;
|
||||
trigger.FarseerBody.CollisionCategories = Physics.CollisionWall;
|
||||
trigger.FarseerBody.CollidesWith = Physics.CollisionNone;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -111,8 +111,9 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
ActivateNearbySleepingCharacters();
|
||||
reloadTimer = reload;
|
||||
reloadTimer /= (1f + character.GetStatValue(StatTypes.MeleeAttackSpeed));
|
||||
reloadTimer /= (1f + item.GetQualityModifier(Quality.StatType.StrikingSpeedMultiplier));
|
||||
reloadTimer /= 1f + character.GetStatValue(StatTypes.MeleeAttackSpeed);
|
||||
reloadTimer /= 1f + item.GetQualityModifier(Quality.StatType.StrikingSpeedMultiplier);
|
||||
character.AnimController.LockFlippingUntil = (float)Timing.TotalTime + reloadTimer;
|
||||
|
||||
item.body.FarseerBody.CollisionCategories = Physics.CollisionProjectile;
|
||||
item.body.FarseerBody.CollidesWith = Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionItemBlocking;
|
||||
@@ -216,6 +217,10 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
hitPos = MathUtils.WrapAnglePi(Math.Min(hitPos + deltaTime * 3f, MathHelper.PiOver4));
|
||||
ac.HoldItem(deltaTime, item, handlePos, aimPos, Vector2.Zero, aim: false, hitPos, holdAngle + hitPos, aimMelee: true);
|
||||
if (ac.InWater)
|
||||
{
|
||||
ac.LockFlippingUntil = (float)Timing.TotalTime + Reload;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -71,6 +71,7 @@ namespace Barotrauma.Items.Components
|
||||
//return if someone is already trying to pick the item
|
||||
if (pickTimer > 0.0f) { return false; }
|
||||
if (picker == null || picker.Inventory == null) { return false; }
|
||||
if (!picker.Inventory.AccessibleWhenAlive && !picker.Inventory.AccessibleByOwner) { return false; }
|
||||
|
||||
if (PickingTime > 0.0f)
|
||||
{
|
||||
@@ -226,7 +227,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
foreach (Connection c in connectionPanel.Connections)
|
||||
{
|
||||
foreach (Wire w in c.Wires)
|
||||
foreach (Wire w in c.Wires.ToArray())
|
||||
{
|
||||
if (w == null) continue;
|
||||
w.Item.Drop(character);
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override bool Use(float deltaTime, Character character = null)
|
||||
{
|
||||
if (character == null || character.Removed) return false;
|
||||
if (character == null || character.Removed) { return false; }
|
||||
if (!character.IsKeyDown(InputType.Aim) || character.Stun > 0.0f) { return false; }
|
||||
|
||||
IsActive = true;
|
||||
@@ -55,12 +55,11 @@ namespace Barotrauma.Items.Components
|
||||
if (UsableIn == UseEnvironment.Water) { return true; }
|
||||
}
|
||||
|
||||
Vector2 dir = Vector2.Normalize(character.CursorPosition - character.Position);
|
||||
//move upwards if the cursor is at the position of the character
|
||||
if (!MathUtils.IsValid(dir)) dir = Vector2.UnitY;
|
||||
|
||||
Vector2 dir = character.CursorPosition - character.Position;
|
||||
if (!MathUtils.IsValid(dir)) { return true; }
|
||||
float length = 200;
|
||||
dir = dir.ClampLength(length) / length;
|
||||
Vector2 propulsion = dir * Force * character.PropulsionSpeedMultiplier;
|
||||
|
||||
if (character.AnimController.InWater && Force > 0.0f) { character.AnimController.TargetMovement = dir; }
|
||||
|
||||
foreach (Limb limb in character.AnimController.Limbs)
|
||||
|
||||
@@ -416,7 +416,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Move(Vector2 amount) { }
|
||||
public virtual void Move(Vector2 amount, bool ignoreContacts = false) { }
|
||||
|
||||
/// <summary>a Character has picked the item</summary>
|
||||
public virtual bool Pick(Character picker)
|
||||
|
||||
@@ -315,7 +315,7 @@ namespace Barotrauma.Items.Components
|
||||
IsActive = activeContainedItems.Count > 0 || Inventory.AllItems.Any(it => it.body != null);
|
||||
}
|
||||
|
||||
public override void Move(Vector2 amount)
|
||||
public override void Move(Vector2 amount, bool ignoreContacts = false)
|
||||
{
|
||||
SetContainedItemPositions();
|
||||
}
|
||||
@@ -751,7 +751,11 @@ namespace Barotrauma.Items.Components
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
Inventory.AllItemsMod.ForEach(it => it.Drop(null));
|
||||
//if we're unloading the whole sub, no need to drop anything (everything's going to be removed anyway)
|
||||
if (!Submarine.Unloading)
|
||||
{
|
||||
Inventory.AllItemsMod.ForEach(it => it.Drop(null));
|
||||
}
|
||||
}
|
||||
|
||||
public override void Load(ContentXElement componentElement, bool usePrefabValues, IdRemap idRemap)
|
||||
|
||||
@@ -462,19 +462,8 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
dir = dir == Direction.Left ? Direction.Right : Direction.Left;
|
||||
}
|
||||
|
||||
userPos.X = -UserPos.X;
|
||||
|
||||
for (int i = 0; i < limbPositions.Count; i++)
|
||||
{
|
||||
float diff = (item.Rect.X + limbPositions[i].Position.X * item.Scale) - item.Rect.Center.X;
|
||||
|
||||
Vector2 flippedPos =
|
||||
new Vector2(
|
||||
(item.Rect.Center.X - diff - item.Rect.X) / item.Scale,
|
||||
limbPositions[i].Position.Y);
|
||||
limbPositions[i] = new LimbPos(limbPositions[i].LimbType, flippedPos, limbPositions[i].AllowUsingLimb);
|
||||
}
|
||||
userPos.X = -UserPos.X;
|
||||
FlipLimbPositions();
|
||||
}
|
||||
|
||||
public override void FlipY(bool relativeToSub)
|
||||
@@ -519,6 +508,11 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (Screen.Selected == GameMain.SubEditorScreen)
|
||||
{
|
||||
if (item.FlippedX)
|
||||
{
|
||||
FlipLimbPositions();
|
||||
}
|
||||
// Don't save flipped positions.
|
||||
foreach (var limbPos in limbPositions)
|
||||
{
|
||||
element.Add(new XElement("limbposition",
|
||||
@@ -526,6 +520,10 @@ namespace Barotrauma.Items.Components
|
||||
new XAttribute("position", XMLExtensions.Vector2ToString(limbPos.Position)),
|
||||
new XAttribute("allowusinglimb", limbPos.AllowUsingLimb)));
|
||||
}
|
||||
if (item.FlippedX)
|
||||
{
|
||||
FlipLimbPositions();
|
||||
}
|
||||
}
|
||||
return element;
|
||||
}
|
||||
@@ -558,5 +556,29 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void FlipLimbPositions()
|
||||
{
|
||||
for (int i = 0; i < limbPositions.Count; i++)
|
||||
{
|
||||
float diff = (item.Rect.X + limbPositions[i].Position.X * item.Scale) - item.Rect.Center.X;
|
||||
|
||||
Vector2 flippedPos =
|
||||
new Vector2(
|
||||
(item.Rect.Center.X - diff - item.Rect.X) / item.Scale,
|
||||
limbPositions[i].Position.Y);
|
||||
limbPositions[i] = new LimbPos(limbPositions[i].LimbType, flippedPos, limbPositions[i].AllowUsingLimb);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
LoadLimbPositions(originalElement);
|
||||
if (item.FlippedX)
|
||||
{
|
||||
FlipLimbPositions();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (powerOut?.Grid != null) { return powerOut.Grid.Voltage; }
|
||||
}
|
||||
return voltage;
|
||||
return currPowerConsumption <= 0.0f ? 1.0f : voltage;
|
||||
}
|
||||
set
|
||||
{
|
||||
|
||||
@@ -231,6 +231,13 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No, description:"Enable only if you want to make the projectile ignore collisions with other projectiles when it's shot. Doesn't have any effect, if the item is not set to be damaged by projectiles.")]
|
||||
public bool IgnoreProjectilesWhileActive
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public Body StickTarget
|
||||
{
|
||||
get;
|
||||
@@ -405,6 +412,10 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
item.body.CollisionCategories = Physics.CollisionProjectile;
|
||||
item.body.CollidesWith = Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionItemBlocking;
|
||||
if (item.Prefab.DamagedByProjectiles && !IgnoreProjectilesWhileActive)
|
||||
{
|
||||
item.body.CollidesWith |= Physics.CollisionProjectile;
|
||||
}
|
||||
|
||||
IsActive = true;
|
||||
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
sealed class AndComponent : BooleanOperatorComponent
|
||||
{
|
||||
public AndComponent(Item item, ContentXElement element)
|
||||
: base(item, element) { }
|
||||
|
||||
protected override bool GetOutput(int numTrueInputs) => numTrueInputs >= 2;
|
||||
}
|
||||
}
|
||||
+8
-5
@@ -3,7 +3,7 @@ using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
class AndComponent : ItemComponent
|
||||
abstract class BooleanOperatorComponent : ItemComponent
|
||||
{
|
||||
protected string output, falseOutput;
|
||||
|
||||
@@ -70,22 +70,25 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public AndComponent(Item item, ContentXElement element)
|
||||
public BooleanOperatorComponent(Item item, ContentXElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
timeSinceReceived = new float[] { Math.Max(timeFrame * 2.0f, 0.1f), Math.Max(timeFrame * 2.0f, 0.1f) };
|
||||
IsActive = true;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
protected abstract bool GetOutput(int numTrueInputs);
|
||||
|
||||
public sealed override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
bool state = true;
|
||||
int receivedInputs = 0;
|
||||
for (int i = 0; i < timeSinceReceived.Length; i++)
|
||||
{
|
||||
if (timeSinceReceived[i] > timeFrame) { state = false; }
|
||||
if (timeSinceReceived[i] <= timeFrame) { receivedInputs += 1; }
|
||||
timeSinceReceived[i] += deltaTime;
|
||||
}
|
||||
|
||||
bool state = GetOutput(receivedInputs);
|
||||
string signalOut = state ? output : falseOutput;
|
||||
if (string.IsNullOrEmpty(signalOut))
|
||||
{
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
sealed class OrComponent : BooleanOperatorComponent
|
||||
{
|
||||
public OrComponent(Item item, ContentXElement element)
|
||||
: base(item, element) { }
|
||||
|
||||
protected override bool GetOutput(int numTrueInputs) => numTrueInputs > 0;
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
sealed class XorComponent : BooleanOperatorComponent
|
||||
{
|
||||
public XorComponent(Item item, ContentXElement element)
|
||||
: base(item, element) { }
|
||||
|
||||
protected override bool GetOutput(int numTrueInputs) => numTrueInputs == 1;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
@@ -19,11 +20,8 @@ namespace Barotrauma.Items.Components
|
||||
public readonly string Name;
|
||||
public readonly LocalizedString DisplayName;
|
||||
|
||||
private readonly Wire[] wires;
|
||||
public IEnumerable<Wire> Wires
|
||||
{
|
||||
get { return wires; }
|
||||
}
|
||||
private readonly HashSet<Wire> wires;
|
||||
public IReadOnlyCollection<Wire> Wires => wires;
|
||||
|
||||
private readonly Item item;
|
||||
|
||||
@@ -31,7 +29,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public readonly List<StatusEffect> Effects;
|
||||
|
||||
public readonly ushort[] wireId;
|
||||
public readonly List<ushort> LoadedWireIds;
|
||||
|
||||
//The grid the connection is a part of
|
||||
public GridInfo Grid;
|
||||
@@ -92,7 +90,7 @@ namespace Barotrauma.Items.Components
|
||||
MaxWires = Math.Max(element.Elements().Count(e => e.Name.ToString().Equals("link", StringComparison.OrdinalIgnoreCase)), MaxWires);
|
||||
|
||||
MaxPlayerConnectableWires = element.GetAttributeInt("maxplayerconnectablewires", MaxWires);
|
||||
wires = new Wire[MaxWires];
|
||||
wires = new HashSet<Wire>();
|
||||
|
||||
IsOutput = element.Name.ToString() == "output";
|
||||
Name = element.GetAttributeString("name", IsOutput ? "output" : "input");
|
||||
@@ -150,23 +148,15 @@ namespace Barotrauma.Items.Components
|
||||
IsPower = Name == "power_in" || Name == "power" || Name == "power_out";
|
||||
|
||||
|
||||
wireId = new ushort[MaxWires];
|
||||
|
||||
LoadedWireIds = new List<ushort>();
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "link":
|
||||
int index = -1;
|
||||
for (int i = 0; i < MaxWires; i++)
|
||||
{
|
||||
if (wireId[i] < 1) { index = i; }
|
||||
}
|
||||
if (index == -1) { break; }
|
||||
|
||||
int id = subElement.GetAttributeInt("w", 0);
|
||||
if (id < 0) { id = 0; }
|
||||
wireId[index] = idRemap.GetOffsetId(id);
|
||||
if (LoadedWireIds.Count < MaxWires) { LoadedWireIds.Add(idRemap.GetOffsetId(id)); }
|
||||
|
||||
break;
|
||||
case "statuseffect":
|
||||
@@ -185,138 +175,111 @@ namespace Barotrauma.Items.Components
|
||||
private void RefreshRecipients()
|
||||
{
|
||||
recipients.Clear();
|
||||
for (int i = 0; i < MaxWires; i++)
|
||||
foreach (var wire in wires)
|
||||
{
|
||||
if (wires[i] == null) continue;
|
||||
Connection recipient = wires[i].OtherConnection(this);
|
||||
if (recipient != null) recipients.Add(recipient);
|
||||
Connection recipient = wire.OtherConnection(this);
|
||||
if (recipient != null) { recipients.Add(recipient); }
|
||||
}
|
||||
recipientsDirty = false;
|
||||
}
|
||||
|
||||
public int FindEmptyIndex()
|
||||
{
|
||||
for (int i = 0; i < MaxWires; i++)
|
||||
{
|
||||
if (wires[i] == null) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public int FindWireIndex(Wire wire)
|
||||
{
|
||||
for (int i = 0; i < MaxWires; i++)
|
||||
{
|
||||
if (wires[i] == wire) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public int FindWireIndex(Item wireItem)
|
||||
{
|
||||
for (int i = 0; i < MaxWires; i++)
|
||||
{
|
||||
if (wires[i] == null && wireItem == null) return i;
|
||||
if (wires[i] != null && wires[i].Item == wireItem) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
public Wire FindWireByItem(Item it)
|
||||
=> Wires.FirstOrDefault(w => w.Item == it);
|
||||
|
||||
public bool WireSlotsAvailable()
|
||||
=> wires.Count < MaxWires;
|
||||
|
||||
public bool TryAddLink(Wire wire)
|
||||
{
|
||||
for (int i = 0; i < MaxWires; i++)
|
||||
if (wire is null
|
||||
|| wires.Contains(wire)
|
||||
|| !WireSlotsAvailable())
|
||||
{
|
||||
if (wires[i] == null)
|
||||
{
|
||||
SetWire(i, wire);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
wires.Add(wire);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void SetWire(int index, Wire wire)
|
||||
public void DisconnectWire(Wire wire)
|
||||
{
|
||||
Wire previousWire = wires[index];
|
||||
if (wire != previousWire && previousWire != null)
|
||||
{
|
||||
var otherConnection = previousWire.OtherConnection(this);
|
||||
if (otherConnection != null)
|
||||
{
|
||||
//Change the connection grids or flag them for updating
|
||||
if (IsPower && otherConnection.IsPower && Grid != null)
|
||||
{
|
||||
//Check if both connections belong to a larger grid
|
||||
if (otherConnection.recipients.Count > 1 && recipients.Count > 1)
|
||||
{
|
||||
Powered.ChangedConnections.Add(otherConnection);
|
||||
Powered.ChangedConnections.Add(this);
|
||||
}
|
||||
else if (recipients.Count > 1)
|
||||
{
|
||||
//This wire was the only one at the other grid
|
||||
otherConnection.Grid?.RemoveConnection(otherConnection);
|
||||
otherConnection.Grid = null;
|
||||
}
|
||||
else if (otherConnection.recipients.Count > 1)
|
||||
{
|
||||
Grid?.RemoveConnection(this);
|
||||
Grid = null;
|
||||
}
|
||||
else if (Grid.Connections.Count == 2)
|
||||
{
|
||||
//Delete the grid as these were the only 2 devices
|
||||
Powered.Grids.Remove(Grid.ID);
|
||||
Grid = null;
|
||||
otherConnection.Grid = null;
|
||||
}
|
||||
}
|
||||
otherConnection.recipientsDirty = true;
|
||||
}
|
||||
}
|
||||
if (wire == null || !wires.Contains(wire)) { return; }
|
||||
|
||||
wires[index] = wire;
|
||||
var prevOtherConnection = wire.OtherConnection(this);
|
||||
if (prevOtherConnection != null)
|
||||
{
|
||||
//Change the connection grids or flag them for updating
|
||||
if (IsPower && prevOtherConnection.IsPower && Grid != null)
|
||||
{
|
||||
//Check if both connections belong to a larger grid
|
||||
if (prevOtherConnection.recipients.Count > 1 && recipients.Count > 1)
|
||||
{
|
||||
Powered.ChangedConnections.Add(prevOtherConnection);
|
||||
Powered.ChangedConnections.Add(this);
|
||||
}
|
||||
else if (recipients.Count > 1)
|
||||
{
|
||||
//This wire was the only one at the other grid
|
||||
prevOtherConnection.Grid?.RemoveConnection(prevOtherConnection);
|
||||
prevOtherConnection.Grid = null;
|
||||
}
|
||||
else if (prevOtherConnection.recipients.Count > 1)
|
||||
{
|
||||
Grid?.RemoveConnection(this);
|
||||
Grid = null;
|
||||
}
|
||||
else if (Grid.Connections.Count == 2)
|
||||
{
|
||||
//Delete the grid as these were the only 2 devices
|
||||
Powered.Grids.Remove(Grid.ID);
|
||||
Grid = null;
|
||||
prevOtherConnection.Grid = null;
|
||||
}
|
||||
}
|
||||
prevOtherConnection.recipientsDirty = true;
|
||||
}
|
||||
wires.Remove(wire);
|
||||
recipientsDirty = true;
|
||||
if (wire != null)
|
||||
}
|
||||
|
||||
public void ConnectWire(Wire wire)
|
||||
{
|
||||
if (wire == null || !TryAddLink(wire)) { return; }
|
||||
ConnectionPanel.DisconnectedWires.Remove(wire);
|
||||
var otherConnection = wire.OtherConnection(this);
|
||||
if (otherConnection != null)
|
||||
{
|
||||
|
||||
ConnectionPanel.DisconnectedWires.Remove(wire);
|
||||
var otherConnection = wire.OtherConnection(this);
|
||||
if (otherConnection != null)
|
||||
//Set the other connection grid if a grid exists already
|
||||
if (Powered.ValidPowerConnection(this, otherConnection))
|
||||
{
|
||||
//Set the other connection grid if a grid exists already
|
||||
if (Powered.ValidPowerConnection(this, otherConnection))
|
||||
if (Grid == null && otherConnection.Grid != null)
|
||||
{
|
||||
if (Grid == null && otherConnection.Grid != null)
|
||||
{
|
||||
otherConnection.Grid.AddConnection(this);
|
||||
Grid = otherConnection.Grid;
|
||||
}
|
||||
else if (Grid != null && otherConnection.Grid == null)
|
||||
{
|
||||
Grid.AddConnection(otherConnection);
|
||||
otherConnection.Grid = Grid;
|
||||
}
|
||||
else
|
||||
{
|
||||
//Flag change so that proper grids can be formed
|
||||
Powered.ChangedConnections.Add(this);
|
||||
Powered.ChangedConnections.Add(otherConnection);
|
||||
}
|
||||
otherConnection.Grid.AddConnection(this);
|
||||
Grid = otherConnection.Grid;
|
||||
}
|
||||
else if (Grid != null && otherConnection.Grid == null)
|
||||
{
|
||||
Grid.AddConnection(otherConnection);
|
||||
otherConnection.Grid = Grid;
|
||||
}
|
||||
else
|
||||
{
|
||||
//Flag change so that proper grids can be formed
|
||||
Powered.ChangedConnections.Add(this);
|
||||
Powered.ChangedConnections.Add(otherConnection);
|
||||
}
|
||||
|
||||
otherConnection.recipientsDirty = true;
|
||||
}
|
||||
|
||||
otherConnection.recipientsDirty = true;
|
||||
}
|
||||
recipientsDirty = true;
|
||||
}
|
||||
|
||||
public void SendSignal(Signal signal)
|
||||
{
|
||||
for (int i = 0; i < MaxWires; i++)
|
||||
foreach (var wire in wires)
|
||||
{
|
||||
if (wires[i] == null) { continue; }
|
||||
|
||||
Connection recipient = wires[i].OtherConnection(this);
|
||||
Connection recipient = wire.OtherConnection(this);
|
||||
if (recipient == null) { continue; }
|
||||
if (recipient.item == this.item || signal.source?.LastSentSignalRecipients.LastOrDefault() == recipient) { continue; }
|
||||
|
||||
@@ -350,35 +313,32 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < MaxWires; i++)
|
||||
foreach (var wire in wires)
|
||||
{
|
||||
if (wires[i] == null) continue;
|
||||
|
||||
wires[i].RemoveConnection(this);
|
||||
wires[i] = null;
|
||||
wire.RemoveConnection(this);
|
||||
recipientsDirty = true;
|
||||
}
|
||||
wires.Clear();
|
||||
}
|
||||
|
||||
public void ConnectLinked()
|
||||
public void InitializeFromLoaded()
|
||||
{
|
||||
if (wireId == null) return;
|
||||
if (LoadedWireIds.Count == 0) { return; }
|
||||
|
||||
for (int i = 0; i < MaxWires; i++)
|
||||
for (int i = 0; i < LoadedWireIds.Count; i++)
|
||||
{
|
||||
if (wireId[i] == 0) { continue; }
|
||||
if (!(Entity.FindEntityByID(LoadedWireIds[i]) is Item wireItem)) { continue; }
|
||||
|
||||
if (!(Entity.FindEntityByID(wireId[i]) is Item wireItem)) { continue; }
|
||||
wires[i] = wireItem.GetComponent<Wire>();
|
||||
recipientsDirty = true;
|
||||
|
||||
if (wires[i] != null)
|
||||
var wire = wireItem.GetComponent<Wire>();
|
||||
if (wire != null && TryAddLink(wire))
|
||||
{
|
||||
if (wires[i].Item.body != null) wires[i].Item.body.Enabled = false;
|
||||
wires[i].Connect(this, false, false);
|
||||
wires[i].FixNodeEnds();
|
||||
if (wire.Item.body != null) wire.Item.body.Enabled = false;
|
||||
wire.Connect(this, false, false);
|
||||
wire.FixNodeEnds();
|
||||
recipientsDirty = true;
|
||||
}
|
||||
}
|
||||
LoadedWireIds.Clear();
|
||||
}
|
||||
|
||||
|
||||
@@ -386,19 +346,10 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
XElement newElement = new XElement(IsOutput ? "output" : "input", new XAttribute("name", Name));
|
||||
|
||||
Array.Sort(wires, delegate (Wire wire1, Wire wire2)
|
||||
foreach (var wire in wires.OrderBy(w => w.Item.ID))
|
||||
{
|
||||
if (wire1 == null) return 1;
|
||||
if (wire2 == null) return -1;
|
||||
return wire1.Item.ID.CompareTo(wire2.Item.ID);
|
||||
});
|
||||
|
||||
for (int i = 0; i < MaxWires; i++)
|
||||
{
|
||||
if (wires[i] == null) continue;
|
||||
|
||||
newElement.Add(new XElement("link",
|
||||
new XAttribute("w", wires[i].Item.ID.ToString())));
|
||||
new XAttribute("w", wire.Item.ID.ToString())));
|
||||
}
|
||||
|
||||
parentElement.Add(newElement);
|
||||
|
||||
+7
-25
@@ -49,7 +49,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public bool TemporarilyLocked
|
||||
{
|
||||
get { return Level.IsLoadedOutpost && item.GetComponent<DockingPort>() != null; }
|
||||
get { return Level.IsLoadedOutpost && (item.GetComponent<DockingPort>()?.Docked ?? false); }
|
||||
}
|
||||
|
||||
//connection panels can't be deactivated externally (by signals or status effects)
|
||||
@@ -99,7 +99,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
foreach (Connection c in Connections)
|
||||
{
|
||||
c.ConnectLinked();
|
||||
c.InitializeFromLoaded();
|
||||
}
|
||||
|
||||
if (disconnectedWireIds != null)
|
||||
@@ -286,25 +286,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
for (int i = 0; i < loadedConnections.Count && i < Connections.Count; i++)
|
||||
{
|
||||
if (loadedConnections[i].wireId.Length == Connections[i].wireId.Length)
|
||||
{
|
||||
loadedConnections[i].wireId.CopyTo(Connections[i].wireId, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
//backwards compatibility when maximum number of wires has changed
|
||||
foreach (ushort id in loadedConnections[i].wireId)
|
||||
{
|
||||
for (int j = 0; j < Connections[i].wireId.Length; j++)
|
||||
{
|
||||
if (Connections[i].wireId[j] == 0)
|
||||
{
|
||||
Connections[i].wireId[j] = id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Connections[i].LoadedWireIds.Clear();
|
||||
Connections[i].LoadedWireIds.AddRange(loadedConnections[i].LoadedWireIds);
|
||||
}
|
||||
|
||||
disconnectedWireIds = element.GetAttributeUshortArray("disconnectedwires", Array.Empty<ushort>()).ToList();
|
||||
@@ -361,10 +344,8 @@ namespace Barotrauma.Items.Components
|
||||
DisconnectedWires.Clear();
|
||||
foreach (Connection c in Connections)
|
||||
{
|
||||
foreach (Wire wire in c.Wires)
|
||||
foreach (Wire wire in c.Wires.ToArray())
|
||||
{
|
||||
if (wire == null) { continue; }
|
||||
|
||||
if (wire.OtherConnection(c) == null) //wire not connected to anything else
|
||||
{
|
||||
#if CLIENT
|
||||
@@ -408,13 +389,14 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
foreach (Connection connection in Connections)
|
||||
{
|
||||
msg.WriteVariableUInt32((uint)connection.Wires.Count);
|
||||
foreach (Wire wire in connection.Wires)
|
||||
{
|
||||
msg.Write(wire?.Item == null ? (ushort)0 : wire.Item.ID);
|
||||
}
|
||||
}
|
||||
|
||||
msg.Write((ushort)DisconnectedWires.Count());
|
||||
msg.Write((ushort)DisconnectedWires.Count);
|
||||
foreach (Wire disconnectedWire in DisconnectedWires)
|
||||
{
|
||||
msg.Write(disconnectedWire.Item.ID);
|
||||
|
||||
@@ -187,7 +187,7 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
public override void Move(Vector2 amount)
|
||||
public override void Move(Vector2 amount, bool ignoreContacts = false)
|
||||
{
|
||||
#if CLIENT
|
||||
Light.Position += amount;
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
class OrComponent : AndComponent
|
||||
{
|
||||
public OrComponent(Item item, ContentXElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
IsActive = true;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
bool state = false;
|
||||
for (int i = 0; i < timeSinceReceived.Length; i++)
|
||||
{
|
||||
if (timeSinceReceived[i] <= timeFrame) { state = true; }
|
||||
timeSinceReceived[i] += deltaTime;
|
||||
}
|
||||
|
||||
string signalOut = state ? output : falseOutput;
|
||||
if (string.IsNullOrEmpty(signalOut))
|
||||
{
|
||||
//deactivate the component if state is false and there's no false output (will be woken up by non-zero signals in ReceiveSignal)
|
||||
if (!state) { IsActive = false; }
|
||||
return;
|
||||
}
|
||||
|
||||
item.SendSignal(new Signal(signalOut, sender: signalSender[0] ?? signalSender[1]), "signal_out");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -135,7 +135,7 @@ namespace Barotrauma.Items.Components
|
||||
// = no point in receiving
|
||||
if (!LinkToChat)
|
||||
{
|
||||
if (signalOutConnection == null || !signalOutConnection.Wires.Any(w => w != null))
|
||||
if (signalOutConnection == null || signalOutConnection.Wires.Count <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -143,12 +143,11 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (connections[i] == null || connections[i].Item != item) { continue; }
|
||||
|
||||
foreach (Wire wire in connections[i].Wires)
|
||||
if (connections[i].Wires.Contains(this))
|
||||
{
|
||||
if (wire != this) continue;
|
||||
SetConnectedDirty();
|
||||
|
||||
connections[i].SetWire(connections[i].FindWireIndex(wire), null);
|
||||
connections[i].DisconnectWire(this);
|
||||
}
|
||||
|
||||
connections[i] = null;
|
||||
@@ -597,15 +596,16 @@ namespace Barotrauma.Items.Components
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
if (connections[i] == null) { continue; }
|
||||
int wireIndex = connections[i].FindWireIndex(item);
|
||||
if (wireIndex == -1) { continue; }
|
||||
|
||||
var wire = connections[i].FindWireByItem(item);
|
||||
if (wire is null) { continue; }
|
||||
#if SERVER
|
||||
if (!connections[i].Item.Removed && (!connections[i].Item.Submarine?.Loading ?? true) && (!Level.Loaded?.Generating ?? true))
|
||||
{
|
||||
connections[i].Item.CreateServerEvent(connections[i].Item.GetComponent<ConnectionPanel>());
|
||||
}
|
||||
#endif
|
||||
connections[i].SetWire(wireIndex, null);
|
||||
connections[i].DisconnectWire(wire);
|
||||
connections[i] = null;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
class XorComponent : AndComponent
|
||||
{
|
||||
public XorComponent(Item item, ContentXElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
IsActive = true;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
int receivedInputs = 0;
|
||||
for (int i = 0; i < timeSinceReceived.Length; i++)
|
||||
{
|
||||
if (timeSinceReceived[i] <= timeFrame) { receivedInputs += 1; }
|
||||
timeSinceReceived[i] += deltaTime;
|
||||
}
|
||||
|
||||
bool state = receivedInputs == 1;
|
||||
string signalOut = state ? output : falseOutput;
|
||||
if (string.IsNullOrEmpty(signalOut))
|
||||
{
|
||||
//deactivate the component if state is false and there's no false output (will be woken up by non-zero signals in ReceiveSignal)
|
||||
if (!state) { IsActive = false; }
|
||||
return;
|
||||
}
|
||||
|
||||
item.SendSignal(new Signal(signalOut, sender: signalSender[0] ?? signalSender[1]), "signal_out");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,8 @@ using FarseerPhysics.Dynamics;
|
||||
using FarseerPhysics.Dynamics.Contacts;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
@@ -93,13 +92,11 @@ namespace Barotrauma.Items.Components
|
||||
base.OnItemLoaded();
|
||||
float radiusAttribute = originalElement.GetAttributeFloat("radius", 10.0f);
|
||||
Radius = ConvertUnits.ToSimUnits(radiusAttribute * item.Scale);
|
||||
PhysicsBody = new PhysicsBody(0.0f, 0.0f, Radius, 1.5f)
|
||||
PhysicsBody = new PhysicsBody(0.0f, 0.0f, Radius, 1.5f, BodyType.Static, Physics.CollisionWall, LevelTrigger.GetCollisionCategories(triggeredBy))
|
||||
{
|
||||
BodyType = BodyType.Static,
|
||||
CollidesWith = LevelTrigger.GetCollisionCategories(triggeredBy),
|
||||
CollisionCategories = Physics.CollisionWall,
|
||||
UserData = item
|
||||
};
|
||||
PhysicsBody.SetTransformIgnoreContacts(item.SimPosition, 0.0f);
|
||||
PhysicsBody.FarseerBody.SetIsSensor(true);
|
||||
PhysicsBody.FarseerBody.OnCollision += OnCollision;
|
||||
PhysicsBody.FarseerBody.OnSeparation += OnSeparation;
|
||||
@@ -215,12 +212,18 @@ namespace Barotrauma.Items.Components
|
||||
body.ApplyForce(force);
|
||||
}
|
||||
|
||||
public override void Move(Vector2 amount)
|
||||
public override void Move(Vector2 amount, bool ignoreContacts = false)
|
||||
{
|
||||
base.Move(amount);
|
||||
if (PhysicsBody != null)
|
||||
{
|
||||
PhysicsBody.SetTransform(PhysicsBody.SimPosition + ConvertUnits.ToSimUnits(amount), 0.0f);
|
||||
if (ignoreContacts)
|
||||
{
|
||||
PhysicsBody.SetTransformIgnoreContacts(PhysicsBody.SimPosition + ConvertUnits.ToSimUnits(amount), 0.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
PhysicsBody.SetTransform(PhysicsBody.SimPosition + ConvertUnits.ToSimUnits(amount), 0.0f);
|
||||
}
|
||||
PhysicsBody.Submarine = item.Submarine;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -661,6 +661,7 @@ namespace Barotrauma.Items.Components
|
||||
while (neededPower > 0.0001f && batteries.Count > 0)
|
||||
{
|
||||
batteries.RemoveAll(b => b.Charge <= 0.0001f || b.MaxOutPut <= 0.0001f);
|
||||
if (!batteries.Any()) { break; }
|
||||
float takePower = neededPower / batteries.Count;
|
||||
takePower = Math.Min(takePower, batteries.Min(b => Math.Min(b.Charge * 3600.0f, b.MaxOutPut)));
|
||||
foreach (PowerContainer battery in batteries)
|
||||
@@ -1151,8 +1152,12 @@ namespace Barotrauma.Items.Components
|
||||
foreach (Character enemy in Character.CharacterList)
|
||||
{
|
||||
// Ignore dead, friendly, and those that are inside the same sub
|
||||
if (enemy.IsDead || !enemy.Enabled || enemy.Submarine == character.Submarine) { continue; }
|
||||
if (enemy.Submarine != null && enemy.Submarine.TeamID == character.Submarine.TeamID) { continue; }
|
||||
if (enemy.IsDead || !enemy.Enabled) { continue; }
|
||||
if (character.Submarine != null)
|
||||
{
|
||||
if (enemy.Submarine == character.Submarine) { continue; }
|
||||
if (enemy.Submarine != null && enemy.Submarine.TeamID == character.Submarine.TeamID) { continue; }
|
||||
}
|
||||
// Don't aim monsters that are inside any submarine.
|
||||
if (!enemy.IsHuman && enemy.CurrentHull != null) { continue; }
|
||||
if (HumanAIController.IsFriendly(character, enemy)) { continue; }
|
||||
|
||||
@@ -115,7 +115,7 @@ namespace Barotrauma
|
||||
|
||||
private readonly Quality qualityComponent;
|
||||
|
||||
private readonly ConcurrentQueue<float> impactQueue = new ConcurrentQueue<float>();
|
||||
private ConcurrentQueue<float> impactQueue;
|
||||
|
||||
//a dictionary containing lists of the status effects in all the components of the item
|
||||
private readonly bool[] hasStatusEffectsOfType;
|
||||
@@ -835,33 +835,35 @@ namespace Barotrauma
|
||||
var rand = new Random(ID);
|
||||
density = MathHelper.Lerp(minDensity, maxDensity, (float)rand.NextDouble());
|
||||
}
|
||||
body = new PhysicsBody(subElement, ConvertUnits.ToSimUnits(Position), Scale, density);
|
||||
|
||||
string collisionCategory = subElement.GetAttributeString("collisioncategory", null);
|
||||
string collisionCategoryStr = subElement.GetAttributeString("collisioncategory", null);
|
||||
|
||||
Category collisionCategory = Physics.CollisionItem;
|
||||
Category collidesWith = Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionPlatform;
|
||||
if ((Prefab.DamagedByProjectiles || Prefab.DamagedByMeleeWeapons) && Condition > 0)
|
||||
{
|
||||
//force collision category to Character to allow projectiles and weapons to hit
|
||||
//(we could also do this by making the projectiles and weapons hit CollisionItem
|
||||
//and check if the collision should be ignored in the OnCollision callback, but
|
||||
//that'd make the hit detection more expensive because every item would be included)
|
||||
body.CollisionCategories = Physics.CollisionCharacter;
|
||||
body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionPlatform | Physics.CollisionProjectile;
|
||||
collisionCategory = Physics.CollisionCharacter;
|
||||
}
|
||||
if (collisionCategory != null)
|
||||
if (collisionCategoryStr != null)
|
||||
{
|
||||
if (!Physics.TryParseCollisionCategory(collisionCategory, out Category cat))
|
||||
if (!Physics.TryParseCollisionCategory(collisionCategoryStr, out Category cat))
|
||||
{
|
||||
DebugConsole.ThrowError("Invalid collision category in item \"" + Name+"\" (" + collisionCategory + ")");
|
||||
DebugConsole.ThrowError("Invalid collision category in item \"" + Name+"\" (" + collisionCategoryStr + ")");
|
||||
}
|
||||
else
|
||||
{
|
||||
body.CollisionCategories = cat;
|
||||
collisionCategory = cat;
|
||||
if (cat.HasFlag(Physics.CollisionCharacter))
|
||||
{
|
||||
body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionPlatform | Physics.CollisionProjectile;
|
||||
collisionCategory |= Physics.CollisionProjectile;
|
||||
}
|
||||
}
|
||||
}
|
||||
body = new PhysicsBody(subElement, ConvertUnits.ToSimUnits(Position), Scale, density, collisionCategory, collidesWith, findNewContacts: false);
|
||||
body.FarseerBody.AngularDamping = subElement.GetAttributeFloat("angulardamping", 0.2f);
|
||||
body.FarseerBody.LinearDamping = subElement.GetAttributeFloat("lineardamping", 0.1f);
|
||||
body.UserData = this;
|
||||
@@ -1261,12 +1263,7 @@ namespace Barotrauma
|
||||
|
||||
partial void SetActiveSpriteProjSpecific();
|
||||
|
||||
public override void Move(Vector2 amount)
|
||||
{
|
||||
Move(amount, ignoreContacts: false);
|
||||
}
|
||||
|
||||
public void Move(Vector2 amount, bool ignoreContacts)
|
||||
public override void Move(Vector2 amount, bool ignoreContacts = false)
|
||||
{
|
||||
if (!MathUtils.IsValid(amount))
|
||||
{
|
||||
@@ -1289,7 +1286,7 @@ namespace Barotrauma
|
||||
}
|
||||
foreach (ItemComponent ic in components)
|
||||
{
|
||||
ic.Move(amount);
|
||||
ic.Move(amount, ignoreContacts);
|
||||
}
|
||||
|
||||
if (body != null && (Submarine == null || !Submarine.Loading)) { FindHull(); }
|
||||
@@ -1703,9 +1700,12 @@ namespace Barotrauma
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
while (impactQueue.TryDequeue(out float impact))
|
||||
if (impactQueue != null)
|
||||
{
|
||||
HandleCollision(impact);
|
||||
while (impactQueue.TryDequeue(out float impact))
|
||||
{
|
||||
HandleCollision(impact);
|
||||
}
|
||||
}
|
||||
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer && (!Submarine?.Loading ?? true))
|
||||
@@ -1959,6 +1959,7 @@ namespace Barotrauma
|
||||
if (contact.FixtureA.Body == f1.Body) { normal = -normal; }
|
||||
float impact = Vector2.Dot(f1.Body.LinearVelocity, -normal);
|
||||
|
||||
impactQueue ??= new ConcurrentQueue<float>();
|
||||
impactQueue.Enqueue(impact);
|
||||
|
||||
return true;
|
||||
@@ -2680,21 +2681,21 @@ namespace Barotrauma
|
||||
foreach (ItemComponent ic in components) { ic.Unequip(character); }
|
||||
}
|
||||
|
||||
public List<Pair<object, SerializableProperty>> GetProperties<T>()
|
||||
public List<(object obj, SerializableProperty property)> GetProperties<T>()
|
||||
{
|
||||
List<Pair<object, SerializableProperty>> allProperties = new List<Pair<object, SerializableProperty>>();
|
||||
List<(object obj, SerializableProperty property)> allProperties = new List<(object obj, SerializableProperty property)>();
|
||||
|
||||
List<SerializableProperty> itemProperties = SerializableProperty.GetProperties<T>(this);
|
||||
foreach (var itemProperty in itemProperties)
|
||||
{
|
||||
allProperties.Add(new Pair<object, SerializableProperty>(this, itemProperty));
|
||||
allProperties.Add((this, itemProperty));
|
||||
}
|
||||
foreach (ItemComponent ic in components)
|
||||
{
|
||||
List<SerializableProperty> componentProperties = SerializableProperty.GetProperties<T>(ic);
|
||||
foreach (var componentProperty in componentProperties)
|
||||
{
|
||||
allProperties.Add(new Pair<object, SerializableProperty>(ic, componentProperty));
|
||||
allProperties.Add((ic, componentProperty));
|
||||
}
|
||||
}
|
||||
return allProperties;
|
||||
@@ -2708,13 +2709,13 @@ namespace Barotrauma
|
||||
SerializableProperty property = extraData.SerializableProperty;
|
||||
if (property != null)
|
||||
{
|
||||
var propertyOwner = allProperties.Find(p => p.Second == property);
|
||||
var propertyOwner = allProperties.Find(p => p.property == property);
|
||||
if (allProperties.Count > 1)
|
||||
{
|
||||
msg.Write((byte)allProperties.FindIndex(p => p.Second == property));
|
||||
msg.Write((byte)allProperties.FindIndex(p => p.property == property));
|
||||
}
|
||||
|
||||
object value = property.GetValue(propertyOwner.First);
|
||||
object value = property.GetValue(propertyOwner.obj);
|
||||
if (value is string stringVal)
|
||||
{
|
||||
msg.Write(stringVal);
|
||||
@@ -2795,7 +2796,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private List<Pair<object, SerializableProperty>> GetInGameEditableProperties(bool ignoreConditions = false)
|
||||
private List<(object obj, SerializableProperty property)> GetInGameEditableProperties(bool ignoreConditions = false)
|
||||
{
|
||||
if (ignoreConditions)
|
||||
{
|
||||
@@ -2804,7 +2805,7 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
return GetProperties<ConditionallyEditable>()
|
||||
.Where(ce => ce.Second.GetAttribute<ConditionallyEditable>().IsEditable(this))
|
||||
.Where(ce => ce.property.GetAttribute<ConditionallyEditable>().IsEditable(this))
|
||||
.Union(GetProperties<InGameEditable>()).ToList();
|
||||
}
|
||||
}
|
||||
@@ -2823,8 +2824,8 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
bool allowEditing = true;
|
||||
object parentObject = allProperties[propertyIndex].First;
|
||||
SerializableProperty property = allProperties[propertyIndex].Second;
|
||||
object parentObject = allProperties[propertyIndex].obj;
|
||||
SerializableProperty property = allProperties[propertyIndex].property;
|
||||
if (inGameEditableOnly && parentObject is ItemComponent ic)
|
||||
{
|
||||
if (!ic.AllowInGameEditing) { allowEditing = false; }
|
||||
|
||||
@@ -253,6 +253,12 @@ namespace Barotrauma
|
||||
public readonly float MinCondition;
|
||||
public readonly int MinAmount;
|
||||
public readonly int MaxAmount;
|
||||
// Overrides min and max, if defined.
|
||||
public readonly int Amount;
|
||||
public readonly bool CampaignOnly;
|
||||
public readonly bool NotCampaign;
|
||||
public readonly bool TransferOnlyOnePerContainer;
|
||||
public readonly bool AllowTransfersHere = true;
|
||||
|
||||
public PreferredContainer(XElement element)
|
||||
{
|
||||
@@ -261,21 +267,26 @@ namespace Barotrauma
|
||||
SpawnProbability = element.GetAttributeFloat("spawnprobability", 0.0f);
|
||||
MinAmount = element.GetAttributeInt("minamount", 0);
|
||||
MaxAmount = Math.Max(MinAmount, element.GetAttributeInt("maxamount", 0));
|
||||
Amount = element.GetAttributeInt("amount", 0);
|
||||
MaxCondition = element.GetAttributeFloat("maxcondition", 100f);
|
||||
MinCondition = element.GetAttributeFloat("mincondition", 0f);
|
||||
CampaignOnly = element.GetAttributeBool("campaignonly", CampaignOnly);
|
||||
NotCampaign = element.GetAttributeBool("notcampaign", NotCampaign);
|
||||
TransferOnlyOnePerContainer = element.GetAttributeBool("TransferOnlyOnePerContainer", TransferOnlyOnePerContainer);
|
||||
AllowTransfersHere = element.GetAttributeBool("AllowTransfersHere", AllowTransfersHere);
|
||||
|
||||
if (element.Attribute("spawnprobability") == null)
|
||||
if (element.GetAttribute("spawnprobability") == null)
|
||||
{
|
||||
//if spawn probability is not defined but amount is, assume the probability is 1
|
||||
if (MaxAmount > 0)
|
||||
if (MaxAmount > 0 || Amount > 0)
|
||||
{
|
||||
SpawnProbability = 1.0f;
|
||||
}
|
||||
}
|
||||
else if (element.Attribute("minamount") == null && element.Attribute("maxamount") == null)
|
||||
else if (element.GetAttribute("minamount") == null && element.GetAttribute("maxamount") == null && element.GetAttribute("amount") == null)
|
||||
{
|
||||
//spawn probability defined but amount isn't, assume amount is 1
|
||||
MinAmount = MaxAmount = 1;
|
||||
MinAmount = MaxAmount = Amount = 1;
|
||||
SpawnProbability = element.GetAttributeFloat("spawnprobability", 0.0f);
|
||||
}
|
||||
}
|
||||
@@ -600,6 +611,9 @@ namespace Barotrauma
|
||||
|
||||
public ImmutableHashSet<Identifier> AllowDroppingOnSwapWith { get; private set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No)]
|
||||
public bool DontTransferBetweenSubs { get; private set; }
|
||||
|
||||
protected override Identifier DetermineIdentifier(XElement element)
|
||||
{
|
||||
Identifier identifier = base.DetermineIdentifier(element);
|
||||
@@ -1084,7 +1098,7 @@ namespace Barotrauma
|
||||
//legacy support
|
||||
identifier = GenerateLegacyIdentifier(name);
|
||||
}
|
||||
prefab = Find(p => p is ItemPrefab && p.Identifier == identifier) as ItemPrefab;
|
||||
Prefabs.TryGet(identifier, out prefab);
|
||||
|
||||
//not found, see if we can find a prefab with a matching alias
|
||||
if (prefab == null && !string.IsNullOrEmpty(name))
|
||||
@@ -1104,12 +1118,13 @@ namespace Barotrauma
|
||||
return prefab;
|
||||
}
|
||||
|
||||
public bool IsContainerPreferred(Item item, ItemContainer targetContainer, out bool isPreferencesDefined, out bool isSecondary, bool requireConditionRequirement = false)
|
||||
public bool IsContainerPreferred(Item item, ItemContainer targetContainer, out bool isPreferencesDefined, out bool isSecondary, bool requireConditionRequirement = false, bool checkTransferConditions = false)
|
||||
{
|
||||
isPreferencesDefined = PreferredContainers.Any();
|
||||
isSecondary = false;
|
||||
if (!isPreferencesDefined) { return true; }
|
||||
if (PreferredContainers.Any(pc => (!requireConditionRequirement || HasConditionRequirement(pc)) && IsItemConditionAcceptable(item, pc) && IsContainerPreferred(pc.Primary, targetContainer)))
|
||||
if (PreferredContainers.Any(pc => (!requireConditionRequirement || HasConditionRequirement(pc)) && IsItemConditionAcceptable(item, pc) &&
|
||||
IsContainerPreferred(pc.Primary, targetContainer) && (!checkTransferConditions || CanBeTransferred(item.Prefab.Identifier, pc, targetContainer))))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -1132,6 +1147,8 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
private bool IsItemConditionAcceptable(Item item, PreferredContainer pc) => item.ConditionPercentage >= pc.MinCondition && item.ConditionPercentage <= pc.MaxCondition;
|
||||
private bool CanBeTransferred(Identifier item, PreferredContainer pc, ItemContainer targetContainer) =>
|
||||
pc.AllowTransfersHere && (!pc.TransferOnlyOnePerContainer || targetContainer.Inventory.AllItems.None(i => i.Prefab.Identifier == item));
|
||||
|
||||
public static bool IsContainerPreferred(IEnumerable<Identifier> preferences, ItemContainer c) => preferences.Any(id => c.Item.Prefab.Identifier == id || c.Item.HasTag(id));
|
||||
public static bool IsContainerPreferred(IEnumerable<Identifier> preferences, IEnumerable<Identifier> ids) => ids.Any(id => preferences.Contains(id));
|
||||
|
||||
@@ -214,7 +214,7 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
[Serialize(400, IsPropertySaveable.Yes, "How much health the root has.")]
|
||||
public int RootHealth { get; set; }
|
||||
|
||||
[Serialize(0.0005f, IsPropertySaveable.Yes, "How fast the root's health regenerates per each grown branch.")]
|
||||
[Serialize(0.00025f, IsPropertySaveable.Yes, "How fast the root's health regenerates per each grown branch.")]
|
||||
public float HealthRegenPerBranch { get; set; }
|
||||
|
||||
[Serialize(30, IsPropertySaveable.Yes, "How far away from the root branches can regenerate health (in number of branches). The amount of regen decreases lineary further from the root.")]
|
||||
@@ -1148,7 +1148,7 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
return;
|
||||
}
|
||||
#if SERVER
|
||||
if (!wasRemoved)
|
||||
if (!wasRemoved && Parent != null && !Parent.Removed)
|
||||
{
|
||||
CreateNetworkMessage(new BranchRemoveEventData(branch));
|
||||
}
|
||||
@@ -1199,7 +1199,10 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
|
||||
StateMachine?.State?.Exit();
|
||||
#if SERVER
|
||||
CreateNetworkMessage(new KillEventData());
|
||||
if (Parent != null && !Parent.Removed)
|
||||
{
|
||||
CreateNetworkMessage(new KillEventData());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1220,8 +1223,11 @@ namespace Barotrauma.MapCreatures.Behavior
|
||||
}
|
||||
|
||||
_entityList.Remove(this);
|
||||
#if SERVER
|
||||
CreateNetworkMessage(new RemoveEventData());
|
||||
#if SERVER
|
||||
if (Parent != null && !Parent.Removed)
|
||||
{
|
||||
CreateNetworkMessage(new RemoveEventData());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -148,11 +148,12 @@ namespace Barotrauma
|
||||
InsertToList();
|
||||
|
||||
float blockerSize = ConvertUnits.ToSimUnits(Math.Max(rect.Width, rect.Height)) / 2;
|
||||
outsideCollisionBlocker = GameMain.World.CreateEdge(-Vector2.UnitX * blockerSize, Vector2.UnitX * blockerSize);
|
||||
outsideCollisionBlocker = GameMain.World.CreateEdge(-Vector2.UnitX * blockerSize, Vector2.UnitX * blockerSize,
|
||||
BodyType.Static,
|
||||
Physics.CollisionWall,
|
||||
Physics.CollisionCharacter,
|
||||
findNewContacts: false);
|
||||
outsideCollisionBlocker.UserData = $"CollisionBlocker (Gap {ID})";
|
||||
outsideCollisionBlocker.BodyType = BodyType.Static;
|
||||
outsideCollisionBlocker.CollisionCategories = Physics.CollisionWall;
|
||||
outsideCollisionBlocker.CollidesWith = Physics.CollisionCharacter;
|
||||
outsideCollisionBlocker.Enabled = false;
|
||||
#if CLIENT
|
||||
Resized += newRect => IsHorizontal = newRect.Width < newRect.Height;
|
||||
@@ -165,7 +166,7 @@ namespace Barotrauma
|
||||
return new Gap(rect, IsHorizontal, Submarine);
|
||||
}
|
||||
|
||||
public override void Move(Vector2 amount)
|
||||
public override void Move(Vector2 amount, bool ignoreContacts = false)
|
||||
{
|
||||
if (!MathUtils.IsValid(amount))
|
||||
{
|
||||
@@ -326,14 +327,6 @@ namespace Barotrauma
|
||||
{
|
||||
lerpedFlowForce = Vector2.Lerp(lerpedFlowForce, flowForce, deltaTime * 5.0f);
|
||||
}
|
||||
if (FlowTargetHull != null && IsRoomToRoom)
|
||||
{
|
||||
var otherRoom = linkedTo[1] == FlowTargetHull ? linkedTo[0] : linkedTo[1];
|
||||
if ((otherRoom as Hull).Volume < FlowTargetHull.Volume)
|
||||
{
|
||||
lerpedFlowForce = Vector2.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
openedTimer -= deltaTime;
|
||||
|
||||
|
||||
@@ -590,7 +590,7 @@ namespace Barotrauma
|
||||
return index;
|
||||
}
|
||||
|
||||
public override void Move(Vector2 amount)
|
||||
public override void Move(Vector2 amount, bool ignoreContacts = false)
|
||||
{
|
||||
if (!MathUtils.IsValid(amount))
|
||||
{
|
||||
|
||||
@@ -157,9 +157,6 @@ namespace Barotrauma
|
||||
|
||||
public static List<VoronoiCell> GeneratePath(List<VoronoiCell> targetCells, List<VoronoiCell> cells)
|
||||
{
|
||||
Stopwatch sw2 = new Stopwatch();
|
||||
sw2.Start();
|
||||
|
||||
List<VoronoiCell> pathCells = new List<VoronoiCell>();
|
||||
|
||||
if (targetCells.Count == 0) { return pathCells; }
|
||||
@@ -213,10 +210,6 @@ namespace Barotrauma
|
||||
|
||||
} while (currentCell != targetCells[targetCells.Count - 1] && iterationsLeft > 0);
|
||||
|
||||
|
||||
Debug.WriteLine("gettooclose: " + sw2.ElapsedMilliseconds + " ms");
|
||||
sw2.Restart();
|
||||
|
||||
return pathCells;
|
||||
}
|
||||
|
||||
@@ -351,7 +344,7 @@ namespace Barotrauma
|
||||
BodyType = BodyType.Static,
|
||||
CollisionCategories = Physics.CollisionLevel
|
||||
};
|
||||
GameMain.World.Add(cellBody);
|
||||
GameMain.World.Add(cellBody, findNewContacts: false);
|
||||
|
||||
for (int n = cells.Count - 1; n >= 0; n-- )
|
||||
{
|
||||
@@ -429,7 +422,9 @@ namespace Barotrauma
|
||||
|
||||
Vertices bodyVertices = new Vertices(triangles[i]);
|
||||
PolygonShape polygon = new PolygonShape(bodyVertices, 5.0f);
|
||||
Fixture fixture = new Fixture(polygon)
|
||||
Fixture fixture = new Fixture(polygon,
|
||||
Physics.CollisionLevel,
|
||||
Physics.CollisionAll)
|
||||
{
|
||||
UserData = cell
|
||||
};
|
||||
@@ -446,8 +441,6 @@ namespace Barotrauma
|
||||
}
|
||||
cell.Body = cellBody;
|
||||
}
|
||||
|
||||
cellBody.CollisionCategories = Physics.CollisionLevel;
|
||||
cellBody.ResetMassData();
|
||||
|
||||
return cellBody;
|
||||
|
||||
@@ -299,11 +299,41 @@ namespace Barotrauma
|
||||
/// Random integers generated during the level generation. If these values differ between clients/server,
|
||||
/// it means the levels aren't identical for some reason and there will most likely be major ID mismatches.
|
||||
/// </summary>
|
||||
public List<int> EqualityCheckValues
|
||||
public enum LevelGenStage
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = new List<int>();
|
||||
GenStart,
|
||||
TunnelGen,
|
||||
VoronoiGen,
|
||||
VoronoiGen2,
|
||||
VoronoiGen3,
|
||||
Ruins,
|
||||
FloatingIce,
|
||||
LevelBodies,
|
||||
IceSpires,
|
||||
TopAndBottom,
|
||||
PlaceLevelObjects,
|
||||
GenerateItems,
|
||||
Finish
|
||||
}
|
||||
|
||||
private readonly Dictionary<LevelGenStage, int> equalityCheckValues = Enum.GetValues(typeof(LevelGenStage))
|
||||
.Cast<LevelGenStage>()
|
||||
.Select(k => (k, 0))
|
||||
.ToDictionary();
|
||||
public IReadOnlyDictionary<LevelGenStage, int> EqualityCheckValues => equalityCheckValues;
|
||||
|
||||
private void GenerateEqualityCheckValue(LevelGenStage stage)
|
||||
{
|
||||
equalityCheckValues[stage] = Rand.Int(int.MaxValue, Rand.RandSync.ServerAndClient);
|
||||
}
|
||||
|
||||
private void ClearEqualityCheckValues()
|
||||
{
|
||||
foreach (LevelGenStage stage in Enum.GetValues(typeof(LevelGenStage)))
|
||||
{
|
||||
equalityCheckValues[stage] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public List<Entity> EntitiesBeforeGenerate { get; private set; } = new List<Entity>();
|
||||
public int EntityCountBeforeGenerate { get; private set; }
|
||||
@@ -404,7 +434,7 @@ namespace Barotrauma
|
||||
Loaded = this;
|
||||
Generating = true;
|
||||
|
||||
EqualityCheckValues.Clear();
|
||||
ClearEqualityCheckValues();
|
||||
EntitiesBeforeGenerate = GetEntities().ToList();
|
||||
EntityCountBeforeGenerate = EntitiesBeforeGenerate.Count();
|
||||
|
||||
@@ -414,7 +444,7 @@ namespace Barotrauma
|
||||
EndLocation = GameMain.GameSession?.EndLocation;
|
||||
}
|
||||
|
||||
EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.ServerAndClient));
|
||||
GenerateEqualityCheckValue(LevelGenStage.GenStart);
|
||||
|
||||
LevelObjectManager = new LevelObjectManager();
|
||||
|
||||
@@ -477,7 +507,7 @@ namespace Barotrauma
|
||||
(int)MathHelper.Lerp(borders.Bottom - Math.Max(minMainPathWidth, ExitDistance * 1.5f), borders.Y + minMainPathWidth, GenerationParams.EndPosition.Y));
|
||||
endExitPosition = new Point(endPosition.X, borders.Bottom);
|
||||
|
||||
EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.ServerAndClient));
|
||||
GenerateEqualityCheckValue(LevelGenStage.TunnelGen);
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
//generate the initial nodes for the main path and smaller tunnels
|
||||
@@ -573,7 +603,7 @@ namespace Barotrauma
|
||||
GenerateAbyssArea();
|
||||
GenerateCaves(mainPath);
|
||||
|
||||
EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.ServerAndClient));
|
||||
GenerateEqualityCheckValue(LevelGenStage.VoronoiGen);
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
//generate voronoi sites
|
||||
@@ -678,7 +708,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.ServerAndClient));
|
||||
GenerateEqualityCheckValue(LevelGenStage.VoronoiGen2);
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// construct the voronoi graph and cells
|
||||
@@ -796,7 +826,7 @@ namespace Barotrauma
|
||||
startPosition.X = (int)pathCells[0].Site.Coord.X;
|
||||
startExitPosition.X = startPosition.X;
|
||||
|
||||
EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.ServerAndClient));
|
||||
GenerateEqualityCheckValue(LevelGenStage.VoronoiGen3);
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// remove unnecessary cells and create some holes at the bottom of the level
|
||||
@@ -1025,7 +1055,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.ServerAndClient));
|
||||
GenerateEqualityCheckValue(LevelGenStage.Ruins);
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// create some ruins
|
||||
@@ -1038,7 +1068,7 @@ namespace Barotrauma
|
||||
GenerateRuin(ruinPositions[i], mirror);
|
||||
}
|
||||
|
||||
EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.ServerAndClient));
|
||||
GenerateEqualityCheckValue(LevelGenStage.FloatingIce);
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// create floating ice chunks
|
||||
@@ -1070,7 +1100,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.ServerAndClient));
|
||||
GenerateEqualityCheckValue(LevelGenStage.LevelBodies);
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// generate the bodies and rendered triangles of the cells
|
||||
@@ -1175,7 +1205,7 @@ namespace Barotrauma
|
||||
}
|
||||
#endif
|
||||
|
||||
EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.ServerAndClient));
|
||||
GenerateEqualityCheckValue(LevelGenStage.IceSpires);
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// create ice spires
|
||||
@@ -1210,7 +1240,7 @@ namespace Barotrauma
|
||||
|
||||
CreateOutposts();
|
||||
|
||||
EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.ServerAndClient));
|
||||
GenerateEqualityCheckValue(LevelGenStage.TopAndBottom);
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// top barrier & sea floor
|
||||
@@ -1252,15 +1282,15 @@ namespace Barotrauma
|
||||
CreateWrecks();
|
||||
CreateBeaconStation();
|
||||
|
||||
EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.ServerAndClient));
|
||||
GenerateEqualityCheckValue(LevelGenStage.PlaceLevelObjects);
|
||||
|
||||
LevelObjectManager.PlaceObjects(this, GenerationParams.LevelObjectAmount);
|
||||
|
||||
EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.ServerAndClient));
|
||||
GenerateEqualityCheckValue(LevelGenStage.GenerateItems);
|
||||
|
||||
GenerateItems();
|
||||
|
||||
EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.ServerAndClient));
|
||||
GenerateEqualityCheckValue(LevelGenStage.Finish);
|
||||
|
||||
#if CLIENT
|
||||
backgroundCreatureManager.SpawnCreatures(this, GenerationParams.BackgroundCreatureAmount);
|
||||
@@ -2606,7 +2636,7 @@ namespace Barotrauma
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage("Level resources spawned: " + itemCount + "\n" +
|
||||
" Spawn points containing resources: " + PathPoints.Where(p => p.ClusterLocations.Any()).Count() + "/" + PathPoints.Count + "\n" +
|
||||
" Total value: "+ PathPoints.Sum(p => p.ClusterLocations.Sum(c => c.Resources.Sum(r => r.Prefab.DefaultPrice?.Price ?? 0)))+" mk");
|
||||
" Total value: " + PathPoints.Sum(p => p.ClusterLocations.Sum(c => c.Resources.Sum(r => r.Prefab.DefaultPrice?.Price ?? 0))) + " mk");
|
||||
if (AbyssResources.Count > 0)
|
||||
{
|
||||
|
||||
@@ -3688,6 +3718,7 @@ namespace Barotrauma
|
||||
if (wreckFiles.None())
|
||||
{
|
||||
DebugConsole.ThrowError("No wreck files found in the selected content packages!");
|
||||
Wrecks = new List<Submarine>();
|
||||
return;
|
||||
}
|
||||
wreckFiles.Shuffle(Rand.RandSync.ServerAndClient);
|
||||
@@ -4112,12 +4143,12 @@ namespace Barotrauma
|
||||
int corpseCount = Rand.Range(Loaded.GenerationParams.MinCorpseCount, Loaded.GenerationParams.MaxCorpseCount + 1);
|
||||
var allSpawnPoints = WayPoint.WayPointList.FindAll(wp => wp.Submarine == wreck && wp.CurrentHull != null);
|
||||
var pathPoints = allSpawnPoints.FindAll(wp => wp.SpawnType == SpawnType.Path);
|
||||
pathPoints.Shuffle(Rand.RandSync.Unsynced);
|
||||
var corpsePoints = allSpawnPoints.FindAll(wp => wp.SpawnType == SpawnType.Corpse);
|
||||
corpsePoints.Shuffle(Rand.RandSync.Unsynced);
|
||||
|
||||
if (!corpsePoints.Any() && !pathPoints.Any()) { continue; }
|
||||
|
||||
pathPoints.Shuffle(Rand.RandSync.Unsynced);
|
||||
// Sort by job so that we first spawn those with a predefined job (might have special id cards)
|
||||
corpsePoints = corpsePoints.OrderBy(p => p.AssignedJob == null).ThenBy(p => Rand.Value()).ToList();
|
||||
var usedJobs = new HashSet<JobPrefab>();
|
||||
int spawnCounter = 0;
|
||||
for (int j = 0; j < corpseCount; j++)
|
||||
{
|
||||
@@ -4126,18 +4157,18 @@ namespace Barotrauma
|
||||
CorpsePrefab selectedPrefab;
|
||||
if (job == null)
|
||||
{
|
||||
selectedPrefab = GetCorpsePrefab(p => p.SpawnPosition == PositionType.Wreck);
|
||||
selectedPrefab = GetCorpsePrefab(usedJobs);
|
||||
}
|
||||
else
|
||||
{
|
||||
selectedPrefab = GetCorpsePrefab(p => p.SpawnPosition == PositionType.Wreck && (p.Job == "any" || p.Job == job.Identifier));
|
||||
selectedPrefab = GetCorpsePrefab(usedJobs, p => p.Job == "any" || p.Job == job.Identifier);
|
||||
if (selectedPrefab == null)
|
||||
{
|
||||
corpsePoints.Remove(sp);
|
||||
pathPoints.Remove(sp);
|
||||
sp = corpsePoints.FirstOrDefault(sp => sp.AssignedJob == null) ?? pathPoints.FirstOrDefault(sp => sp.AssignedJob == null);
|
||||
// Deduce the job from the selected prefab
|
||||
selectedPrefab = GetCorpsePrefab(p => p.SpawnPosition == PositionType.Wreck);
|
||||
selectedPrefab = GetCorpsePrefab(usedJobs);
|
||||
}
|
||||
}
|
||||
if (selectedPrefab == null) { continue; }
|
||||
@@ -4156,28 +4187,65 @@ namespace Barotrauma
|
||||
pathPoints.Remove(sp);
|
||||
}
|
||||
|
||||
job ??= selectedPrefab.GetJobPrefab();
|
||||
job ??= selectedPrefab.GetJobPrefab(predicate: p => !usedJobs.Contains(p));
|
||||
if (job == null) { continue; }
|
||||
|
||||
if (job.Identifier == "captain" || job.Identifier == "engineer" || job.Identifier == "medicaldoctor" || job.Identifier == "securityofficer")
|
||||
{
|
||||
// Only spawn one of these jobs per wreck
|
||||
usedJobs.Add(job);
|
||||
}
|
||||
var characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: job, randSync: Rand.RandSync.ServerAndClient);
|
||||
var corpse = Character.Create(CharacterPrefab.HumanSpeciesName, worldPos, ToolBox.RandomSeed(8), characterInfo, hasAi: true, createNetworkEvent: true);
|
||||
corpse.AnimController.FindHull(worldPos, setSubmarine: true);
|
||||
corpse.TeamID = CharacterTeamType.None;
|
||||
corpse.EnableDespawn = false;
|
||||
selectedPrefab.GiveItems(corpse, wreck);
|
||||
corpse.CharacterHealth.ApplyAffliction(corpse.AnimController.MainLimb, AfflictionPrefab.OxygenLow.Instantiate(200));
|
||||
bool applyBurns = Rand.Value() < 0.1f;
|
||||
bool applyDamage = Rand.Value() < 0.3f;
|
||||
foreach (var limb in corpse.AnimController.Limbs)
|
||||
{
|
||||
if (applyDamage && (limb.type == LimbType.Head || Rand.Value() < 0.5f))
|
||||
{
|
||||
var prefab = AfflictionPrefab.BiteWounds;
|
||||
float max = prefab.MaxStrength / prefab.DamageOverlayAlpha;
|
||||
corpse.CharacterHealth.ApplyAffliction(limb, prefab.Instantiate(GetStrength(limb, max)));
|
||||
}
|
||||
if (applyBurns)
|
||||
{
|
||||
var prefab = AfflictionPrefab.Burn;
|
||||
float max = prefab.MaxStrength / prefab.BurnOverlayAlpha;
|
||||
corpse.CharacterHealth.ApplyAffliction(limb, prefab.Instantiate(GetStrength(limb, max)));
|
||||
}
|
||||
|
||||
static float GetStrength(Limb limb, float max)
|
||||
{
|
||||
float strength = Rand.Range(0, max);
|
||||
if (limb.type != LimbType.Head)
|
||||
{
|
||||
strength = Math.Min(strength, Rand.Range(0, max));
|
||||
}
|
||||
return strength;
|
||||
}
|
||||
}
|
||||
corpse.Kill(CauseOfDeathType.Unknown, causeOfDeathAffliction: null, log: false);
|
||||
corpse.GiveIdCardTags(sp);
|
||||
#if SERVER
|
||||
if (selectedPrefab.MinMoney >= 0 && selectedPrefab.MaxMoney > 0)
|
||||
|
||||
bool isServerOrSingleplayer = GameMain.IsSingleplayer || GameMain.NetworkMember is { IsServer: true };
|
||||
if (isServerOrSingleplayer && selectedPrefab.MinMoney >= 0 && selectedPrefab.MaxMoney > 0)
|
||||
{
|
||||
corpse.Wallet.Give(Rand.Range(selectedPrefab.MinMoney, selectedPrefab.MaxMoney, Rand.RandSync.Unsynced));
|
||||
}
|
||||
#endif
|
||||
|
||||
spawnCounter++;
|
||||
|
||||
static CorpsePrefab GetCorpsePrefab(Func<CorpsePrefab, bool> predicate)
|
||||
static CorpsePrefab GetCorpsePrefab(HashSet<JobPrefab> usedJobs, Func<CorpsePrefab, bool> predicate = null)
|
||||
{
|
||||
IEnumerable<CorpsePrefab> filteredPrefabs = CorpsePrefab.Prefabs.Where(predicate);
|
||||
IEnumerable<CorpsePrefab> filteredPrefabs = CorpsePrefab.Prefabs.Where(p =>
|
||||
usedJobs.None(j => j.Identifier == p.Job.ToIdentifier()) &&
|
||||
p.SpawnPosition == PositionType.Wreck &&
|
||||
(predicate == null || predicate(p)));
|
||||
|
||||
return ToolBox.SelectWeightedRandom(filteredPrefabs.ToList(), filteredPrefabs.Select(p => p.Commonness).ToList(), Rand.RandSync.Unsynced);
|
||||
}
|
||||
}
|
||||
@@ -4270,7 +4338,7 @@ namespace Barotrauma
|
||||
blockedRects?.Clear();
|
||||
|
||||
EntitiesBeforeGenerate?.Clear();
|
||||
EqualityCheckValues?.Clear();
|
||||
ClearEqualityCheckValues();
|
||||
|
||||
if (Ruins != null)
|
||||
{
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Barotrauma
|
||||
|
||||
public readonly string Seed;
|
||||
|
||||
public float Difficulty;
|
||||
public readonly float Difficulty;
|
||||
|
||||
public readonly Biome Biome;
|
||||
|
||||
@@ -141,8 +141,8 @@ namespace Barotrauma
|
||||
Seed = locationConnection.Locations[0].BaseName + locationConnection.Locations[1].BaseName;
|
||||
Biome = locationConnection.Biome;
|
||||
Type = LevelType.LocationConnection;
|
||||
GenerationParams = LevelGenerationParams.GetRandom(Seed, LevelType.LocationConnection, Biome.Identifier);
|
||||
Difficulty = locationConnection.Difficulty;
|
||||
GenerationParams = LevelGenerationParams.GetRandom(Seed, LevelType.LocationConnection, Difficulty, Biome.Identifier);
|
||||
|
||||
float sizeFactor = MathUtils.InverseLerp(
|
||||
MapGenerationParams.Instance.SmallLevelConnectionLength,
|
||||
@@ -171,13 +171,13 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Instantiates level data using the properties of the location
|
||||
/// </summary>
|
||||
public LevelData(Location location)
|
||||
public LevelData(Location location, float difficulty)
|
||||
{
|
||||
Seed = location.BaseName;
|
||||
Biome = location.Biome;
|
||||
Type = LevelType.Outpost;
|
||||
GenerationParams = LevelGenerationParams.GetRandom(Seed, LevelType.Outpost, Biome.Identifier);
|
||||
Difficulty = 0.0f;
|
||||
Difficulty = difficulty;
|
||||
GenerationParams = LevelGenerationParams.GetRandom(Seed, LevelType.Outpost, Difficulty, Biome.Identifier);
|
||||
|
||||
var rand = new MTRandom(ToolBox.StringToInt(Seed));
|
||||
int width = (int)MathHelper.Lerp(GenerationParams.MinWidth, GenerationParams.MaxWidth, (float)rand.NextDouble());
|
||||
@@ -200,14 +200,16 @@ namespace Barotrauma
|
||||
(requireOutpost ? LevelType.Outpost : LevelType.LocationConnection) :
|
||||
generationParams.Type;
|
||||
|
||||
if (generationParams == null) { generationParams = LevelGenerationParams.GetRandom(seed, type); }
|
||||
float selectedDifficulty = difficulty ?? Rand.Range(30.0f, 80.0f, Rand.RandSync.ServerAndClient);
|
||||
|
||||
if (generationParams == null) { generationParams = LevelGenerationParams.GetRandom(seed, type, selectedDifficulty); }
|
||||
var biome =
|
||||
Biome.Prefabs.FirstOrDefault(b => generationParams?.AllowedBiomeIdentifiers.Contains(b.Identifier) ?? false) ??
|
||||
Biome.Prefabs.GetRandom(Rand.RandSync.ServerAndClient);
|
||||
|
||||
var levelData = new LevelData(
|
||||
seed,
|
||||
difficulty ?? Rand.Range(30.0f, 80.0f, Rand.RandSync.ServerAndClient),
|
||||
selectedDifficulty,
|
||||
Rand.Range(0.0f, 1.0f, Rand.RandSync.ServerAndClient),
|
||||
generationParams,
|
||||
biome);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user