Unstable v0.1300.0.1
This commit is contained in:
@@ -62,6 +62,7 @@
|
||||
<Character file="Content/Characters/Balloon/Balloon.xml" />
|
||||
<Character file="Content/Characters/Carrier/Carrier.xml" />
|
||||
<Character file="Content/Characters/Charybdis/Charybdis.xml" />
|
||||
<Character file="Content/Characters/Charybdisold/Charybdisold.xml" />
|
||||
<Character file="Content/Characters/Coelanth/Coelanth.xml" />
|
||||
<Character file="Content/Characters/Crawler/Crawler.xml" />
|
||||
<Character file="Content/Characters/Crawlerhusk/Crawlerhusk.xml" />
|
||||
@@ -248,4 +249,6 @@
|
||||
<OutpostModule file="Content/Map/Outposts/Hall4wayModule_01_Abandoned.sub" />
|
||||
<BeaconStation file="Content/Map/BeaconStations/BeaconStation1.sub" />
|
||||
<OutpostModule file="Content/Map/Outposts/MineModule_04.sub" />
|
||||
<OutpostModule file="Content/Map/Outposts/HallModuleHorizontal_Abandoned.sub" />
|
||||
<OutpostModule file="Content/Map/Outposts/HallModuleVertical_Abandoned.sub" />
|
||||
</contentpackage>
|
||||
@@ -232,8 +232,7 @@ namespace Barotrauma
|
||||
|
||||
public bool IsWithinSector(Vector2 worldPosition)
|
||||
{
|
||||
if (sectorRad >= MathHelper.TwoPi) return true;
|
||||
|
||||
if (sectorRad >= MathHelper.TwoPi) { return true; }
|
||||
Vector2 diff = worldPosition - WorldPosition;
|
||||
return MathUtils.GetShortestAngle(MathUtils.VectorToAngle(diff), MathUtils.VectorToAngle(sectorDir)) <= sectorRad * 0.5f;
|
||||
}
|
||||
|
||||
@@ -298,7 +298,7 @@ namespace Barotrauma
|
||||
private CharacterParams.TargetParams GetTargetParams(AITarget aiTarget) => GetTargetParams(GetTargetingTag(aiTarget));
|
||||
private string GetTargetingTag(AITarget aiTarget)
|
||||
{
|
||||
if (aiTarget.Entity == null) { return null; }
|
||||
if (aiTarget?.Entity == null) { return null; }
|
||||
string targetingTag = null;
|
||||
if (aiTarget.Entity is Character targetCharacter)
|
||||
{
|
||||
@@ -377,6 +377,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (DisableEnemyAI) { return; }
|
||||
base.Update(deltaTime);
|
||||
UpdateTriggers(deltaTime);
|
||||
|
||||
bool ignorePlatforms = Character.AnimController.TargetMovement.Y < -0.5f && (-Character.AnimController.TargetMovement.Y > Math.Abs(Character.AnimController.TargetMovement.X));
|
||||
if (steeringManager == insideSteering)
|
||||
@@ -462,7 +463,7 @@ namespace Barotrauma
|
||||
{
|
||||
updateTargetsTimer -= deltaTime;
|
||||
}
|
||||
else if (avoidTimer <= 0)
|
||||
else if (avoidTimer <= 0 || activeTriggers.Any() && returnTimer <= 0)
|
||||
{
|
||||
CharacterParams.TargetParams targetingParams = null;
|
||||
UpdateTargets(Character, out targetingParams);
|
||||
@@ -1448,8 +1449,12 @@ namespace Barotrauma
|
||||
break;
|
||||
case AttackPattern.Circle:
|
||||
if (IsCoolDownRunning) { break; }
|
||||
if (IsAttackRunning) { break; }
|
||||
if (IsAttackRunning && CirclePhase != CirclePhase.Strike) { break; }
|
||||
if (selectedTargetingParams == null) { break; }
|
||||
var targetSub = SelectedAiTarget.Entity?.Submarine;
|
||||
if (targetSub == null) { break; }
|
||||
float subSize = Math.Max(targetSub.Borders.Width, targetSub.Borders.Height) / 2;
|
||||
float sqrDistToSub = Vector2.DistanceSquared(WorldPosition, targetSub.WorldPosition);
|
||||
switch (CirclePhase)
|
||||
{
|
||||
case CirclePhase.Start:
|
||||
@@ -1471,22 +1476,31 @@ namespace Barotrauma
|
||||
circleOffset = Rand.Vector(MathHelper.Lerp(selectedTargetingParams.CircleMaxRandomOffset, 0, currentAttackIntensity * Rand.Range(0.9f, 1.1f)));
|
||||
canAttack = false;
|
||||
aggressionIntensity = Math.Clamp(aggressionIntensity, AIParams.StartAggression, AIParams.MaxAggression);
|
||||
CirclePhase = Vector2.DistanceSquared(WorldPosition, attackWorldPos) > MathUtils.Pow2(circleFallbackDistance) ? CirclePhase.CloseIn : CirclePhase.FallBack;
|
||||
if (targetSub.Borders.Width < 1000)
|
||||
{
|
||||
breakCircling = true;
|
||||
CirclePhase = CirclePhase.CloseIn;
|
||||
}
|
||||
else if (sqrDistToSub > MathUtils.Pow2(subSize + selectedTargetingParams.CircleStartDistance))
|
||||
{
|
||||
CirclePhase = CirclePhase.CloseIn;
|
||||
}
|
||||
else if (sqrDistToSub < MathUtils.Pow2(subSize + circleFallbackDistance))
|
||||
{
|
||||
CirclePhase = CirclePhase.FallBack;
|
||||
}
|
||||
else
|
||||
{
|
||||
CirclePhase = CirclePhase.Advance;
|
||||
}
|
||||
break;
|
||||
case CirclePhase.CloseIn:
|
||||
var sub = SelectedAiTarget.Entity?.Submarine;
|
||||
if (sub == null)
|
||||
{
|
||||
CirclePhase = CirclePhase.Start;
|
||||
break;
|
||||
}
|
||||
if (AttackingLimb != null && distance > 0 && distance < AttackingLimb.attack.Range * GetStrikeDistanceMultiplier(sub.Velocity))
|
||||
if (AttackingLimb != null && distance > 0 && distance < AttackingLimb.attack.Range * GetStrikeDistanceMultiplier(targetSub.Velocity))
|
||||
{
|
||||
strikeTimer = AttackingLimb.attack.CoolDown;
|
||||
CirclePhase = CirclePhase.Strike;
|
||||
}
|
||||
else if (!breakCircling && Vector2.DistanceSquared(WorldPosition, attackWorldPos) <= MathUtils.Pow2(circleFallbackDistance - 1000) &&
|
||||
sub.Velocity.LengthSquared() <= MathUtils.Pow2(GetTargetMaxSpeed()))
|
||||
else if (!breakCircling && sqrDistToSub <= MathUtils.Pow2(subSize + selectedTargetingParams.CircleStartDistance / 2) && targetSub.Velocity.LengthSquared() <= MathUtils.Pow2(GetTargetMaxSpeed()))
|
||||
{
|
||||
CirclePhase = CirclePhase.Advance;
|
||||
}
|
||||
@@ -1494,23 +1508,17 @@ namespace Barotrauma
|
||||
break;
|
||||
case CirclePhase.FallBack:
|
||||
bool isBlocked = !UpdateFallBack(attackWorldPos, deltaTime, followThrough: false, checkBlocking: true);
|
||||
if (isBlocked || Vector2.DistanceSquared(WorldPosition, attackWorldPos) > MathUtils.Pow2(circleFallbackDistance))
|
||||
if (isBlocked || sqrDistToSub > MathUtils.Pow2(subSize + circleFallbackDistance))
|
||||
{
|
||||
CirclePhase = CirclePhase.Advance;
|
||||
break;
|
||||
}
|
||||
return;
|
||||
case CirclePhase.Advance:
|
||||
var targetSub = SelectedAiTarget.Entity?.Submarine;
|
||||
if (targetSub == null)
|
||||
{
|
||||
CirclePhase = CirclePhase.Start;
|
||||
break;
|
||||
}
|
||||
Vector2 subSpeed = targetSub.Velocity;
|
||||
float requiredDistMultiplier = 1;
|
||||
// If the target sub is moving fast, just steer towards the target until close enough to strike
|
||||
if (breakCircling || subSpeed.LengthSquared() > MathUtils.Pow2(GetTargetMaxSpeed()) || distance > selectedTargetingParams.CircleStartDistance + 1000)
|
||||
if (breakCircling || subSpeed.LengthSquared() > MathUtils.Pow2(GetTargetMaxSpeed()) || sqrDistToSub > MathUtils.Pow2(subSize + selectedTargetingParams.CircleStartDistance * 1.2f))
|
||||
{
|
||||
CirclePhase = CirclePhase.CloseIn;
|
||||
}
|
||||
@@ -1532,7 +1540,7 @@ namespace Barotrauma
|
||||
// When the offset position is outside of the sub it happens that the creature sometimes reaches the target point,
|
||||
// which makes it continue circling around the point (as supposed)
|
||||
// But when there is some offset and the offset is too near, this is not what we want.
|
||||
if (targetSub.Borders.ContainsWorld(attackWorldPos + ConvertUnits.ToDisplayUnits(circleOffset)))
|
||||
if (AttackingLimb != null && sqrDistToSub < MathUtils.Pow2(subSize + circleFallbackDistance))
|
||||
{
|
||||
CirclePhase = CirclePhase.Strike;
|
||||
strikeTimer = AttackingLimb.attack.CoolDown;
|
||||
@@ -1646,15 +1654,15 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (UpdateLimbAttack(deltaTime, AttackingLimb, attackSimPos, distance, attackTargetLimb))
|
||||
{
|
||||
CirclePhase = CirclePhase.Start;
|
||||
}
|
||||
else
|
||||
if (!UpdateLimbAttack(deltaTime, AttackingLimb, attackSimPos, distance, attackTargetLimb))
|
||||
{
|
||||
IgnoreTarget(SelectedAiTarget);
|
||||
}
|
||||
}
|
||||
else if (IsAttackRunning)
|
||||
{
|
||||
AttackingLimb.attack.ResetAttackTimer();
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<Limb> attackLimbs = new List<Limb>();
|
||||
@@ -2060,6 +2068,7 @@ namespace Barotrauma
|
||||
targetValue = 0;
|
||||
selectedTargetMemory = null;
|
||||
targetingParams = null;
|
||||
bool isAnyTargetClose = false;
|
||||
|
||||
foreach (AITarget aiTarget in AITarget.List)
|
||||
{
|
||||
@@ -2163,6 +2172,14 @@ namespace Barotrauma
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (door == null)
|
||||
{
|
||||
// Ignore items inside ruins, unless we are in the same hull. We can't target the ruin walls.
|
||||
if (item.Submarine == null && item.CurrentHull != Character.CurrentHull)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
foreach (var prio in AIParams.Targets)
|
||||
{
|
||||
if (item.HasTag(prio.Tag))
|
||||
@@ -2379,18 +2396,16 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!aiTarget.IsWithinSector(WorldPosition)) { continue; }
|
||||
Vector2 toTarget = aiTarget.WorldPosition - character.WorldPosition;
|
||||
float dist = toTarget.Length();
|
||||
|
||||
float nonModifiedDist = dist;
|
||||
//if the target has been within range earlier, the character will notice it more easily
|
||||
if (targetMemories.ContainsKey(aiTarget))
|
||||
{
|
||||
dist *= 0.9f;
|
||||
}
|
||||
|
||||
if (!CanPerceive(aiTarget, dist)) { continue; }
|
||||
if (!aiTarget.IsWithinSector(WorldPosition)) { continue; }
|
||||
|
||||
//if the target is very close, the distance doesn't make much difference
|
||||
// -> just ignore the distance and attack whatever has the highest priority
|
||||
@@ -2405,14 +2420,26 @@ namespace Barotrauma
|
||||
|
||||
if (targetParams.AttackPattern == AttackPattern.Circle)
|
||||
{
|
||||
if (Character.Submarine == null && aiTarget.Entity?.Submarine != null)
|
||||
if (Character.Submarine == null && aiTarget.Entity?.Submarine != null && !isAnyTargetClose)
|
||||
{
|
||||
if (Submarine.MainSubs.Contains(aiTarget.Entity.Submarine))
|
||||
{
|
||||
// Prioritize targets that are near the horizontal center of the sub
|
||||
// Prioritize targets that are near the horizontal center of the sub, but only when none of the targets is reachable.
|
||||
float horizontalDistanceToSubCenter = Math.Abs(aiTarget.WorldPosition.X - aiTarget.Entity.Submarine.WorldPosition.X);
|
||||
dist *= MathHelper.Lerp(1f, 5f, MathUtils.InverseLerp(0, 10000, horizontalDistanceToSubCenter));
|
||||
}
|
||||
else
|
||||
{
|
||||
dist *= 5;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Don't target characters that are outside of the allowed zone, unless attacking or escaping
|
||||
if (targetParams.State != AIState.Attack && targetParams.State != AIState.Escape && targetParams.State != AIState.Avoid)
|
||||
{
|
||||
if (!IsPositionInsideAllowedZone(aiTarget.WorldPosition, out _))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2486,11 +2513,26 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (targetCharacter.Submarine == null && Character.Submarine == null)
|
||||
{
|
||||
// Ignore the target when it's far enough and blocked by the level geometry, because the steering avoidance probably can't get us to the target.
|
||||
if (dist > Math.Clamp(ConvertUnits.ToDisplayUnits(colliderLength) * 10, 1000, 5000))
|
||||
{
|
||||
if (Submarine.PickBodies(SimPosition, targetCharacter.SimPosition, collisionCategory: Physics.CollisionLevel).Any())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
newTarget = aiTarget;
|
||||
selectedTargetMemory = targetMemory;
|
||||
targetValue = valueModifier;
|
||||
targetingParams = targetParams;
|
||||
if (!isAnyTargetClose)
|
||||
{
|
||||
isAnyTargetClose = ConvertUnits.ToDisplayUnits(colliderLength) > nonModifiedDist;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2619,7 +2661,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!Character.AnimController.CanEnterSubmarine && wallTarget == null)
|
||||
if (!Character.AnimController.CanEnterSubmarine && wallTarget == null && selectedTargetingParams?.AttackPattern == AttackPattern.Straight)
|
||||
{
|
||||
if (closestBody.UserData is Structure w && w.Submarine != null && w.Submarine == SelectedAiTarget.Entity?.Submarine ||
|
||||
closestBody.UserData is Item i && i.Submarine != null && i.Submarine == SelectedAiTarget.Entity?.Submarine)
|
||||
@@ -2753,6 +2795,44 @@ namespace Barotrauma
|
||||
private readonly float stateResetCooldown = 10;
|
||||
private float stateResetTimer;
|
||||
private bool isStateChanged;
|
||||
private readonly Dictionary<AITrigger, CharacterParams.TargetParams> activeTriggers = new Dictionary<AITrigger, CharacterParams.TargetParams>();
|
||||
private readonly HashSet<AITrigger> inactiveTriggers = new HashSet<AITrigger>();
|
||||
|
||||
public void LaunchTrigger(AITrigger trigger)
|
||||
{
|
||||
if (trigger.IsTriggered) { return; }
|
||||
if (activeTriggers.ContainsKey(trigger)) { return; }
|
||||
if (activeTriggers.ContainsValue(selectedTargetingParams))
|
||||
{
|
||||
if (!trigger.AllowToOverride) { return; }
|
||||
var existingTrigger = activeTriggers.FirstOrDefault(kvp => kvp.Value == selectedTargetingParams && kvp.Key.AllowToBeOverridden);
|
||||
if (existingTrigger.Key == null) { return; }
|
||||
activeTriggers.Remove(existingTrigger.Key);
|
||||
}
|
||||
trigger.Launch();
|
||||
activeTriggers.Add(trigger, selectedTargetingParams);
|
||||
ChangeParams(selectedTargetingParams, trigger.State);
|
||||
}
|
||||
|
||||
private void UpdateTriggers(float deltaTime)
|
||||
{
|
||||
foreach (var triggerObject in activeTriggers)
|
||||
{
|
||||
AITrigger trigger = triggerObject.Key;
|
||||
trigger.UpdateTimer(deltaTime);
|
||||
if (!trigger.IsActive)
|
||||
{
|
||||
trigger.Reset();
|
||||
ResetParams(triggerObject.Value);
|
||||
inactiveTriggers.Add(trigger);
|
||||
}
|
||||
}
|
||||
foreach (AITrigger trigger in inactiveTriggers)
|
||||
{
|
||||
activeTriggers.Remove(trigger);
|
||||
}
|
||||
inactiveTriggers.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the target's state to the original value defined in the xml.
|
||||
@@ -2768,11 +2848,7 @@ namespace Barotrauma
|
||||
tempParams.Values.ForEach(t => AIParams.RemoveTarget(t));
|
||||
tempParams.Remove(tag);
|
||||
}
|
||||
targetParams.Reset();
|
||||
ResetAITarget();
|
||||
// Enforce the idle state so that we don't keep following the target if there's one
|
||||
State = AIState.Idle;
|
||||
PreviousState = AIState.Idle;
|
||||
ResetParams(targetParams);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
@@ -2784,6 +2860,27 @@ namespace Barotrauma
|
||||
private readonly Dictionary<string, CharacterParams.TargetParams> modifiedParams = new Dictionary<string, CharacterParams.TargetParams>();
|
||||
private readonly Dictionary<string, CharacterParams.TargetParams> tempParams = new Dictionary<string, CharacterParams.TargetParams>();
|
||||
|
||||
private void ChangeParams(CharacterParams.TargetParams targetParams, AIState state, float? priority = null)
|
||||
{
|
||||
if (targetParams == null) { return; }
|
||||
if (priority.HasValue)
|
||||
{
|
||||
targetParams.Priority = priority.Value;
|
||||
}
|
||||
targetParams.State = state;
|
||||
}
|
||||
|
||||
private void ResetParams(CharacterParams.TargetParams targetParams)
|
||||
{
|
||||
targetParams?.Reset();
|
||||
if (selectedTargetingParams == targetParams || State == AIState.Idle)
|
||||
{
|
||||
ResetAITarget();
|
||||
State = AIState.Idle;
|
||||
PreviousState = AIState.Idle;
|
||||
}
|
||||
}
|
||||
|
||||
private void ChangeParams(string tag, AIState state, float? priority = null, bool onlyExisting = false)
|
||||
{
|
||||
if (!AIParams.TryGetTarget(tag, out CharacterParams.TargetParams targetParams))
|
||||
@@ -2938,45 +3035,56 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsPositionInsideAllowedZone(Vector2 pos, out Vector2 targetDir)
|
||||
{
|
||||
targetDir = Vector2.Zero;
|
||||
if (AIParams.AvoidAbyss)
|
||||
{
|
||||
if (pos.Y < Level.Loaded.AbyssStart)
|
||||
{
|
||||
// Too far down
|
||||
targetDir = Vector2.UnitY;
|
||||
}
|
||||
}
|
||||
if (AIParams.StayInAbyss)
|
||||
{
|
||||
if (pos.Y > Level.Loaded.AbyssStart)
|
||||
{
|
||||
// Too far up
|
||||
targetDir = -Vector2.UnitY;
|
||||
}
|
||||
else if (pos.Y < Level.Loaded.AbyssEnd)
|
||||
{
|
||||
// Too far down
|
||||
targetDir = Vector2.UnitY;
|
||||
}
|
||||
}
|
||||
float margin = 30000;
|
||||
if (pos.X < -margin)
|
||||
{
|
||||
// Too far left
|
||||
targetDir = Vector2.UnitX;
|
||||
}
|
||||
else if (pos.X > Level.Loaded.Size.X + margin)
|
||||
{
|
||||
// Too far right
|
||||
targetDir = -Vector2.UnitX;
|
||||
}
|
||||
return targetDir == Vector2.Zero;
|
||||
}
|
||||
|
||||
private Vector2 returnDir;
|
||||
private float returnTimer;
|
||||
private void SteerInsideLevel(float deltaTime)
|
||||
{
|
||||
if (State == AIState.Attack) { return; }
|
||||
if (SteeringManager is IndoorsSteeringManager) { return; }
|
||||
if (Level.Loaded == null) { return; }
|
||||
Point levelSize = Level.Loaded.Size;
|
||||
float returnTime = 10;
|
||||
if (AIParams.AvoidAbyss)
|
||||
if (State == AIState.Attack && returnTimer <= 0) { return; }
|
||||
float returnTime = 5;
|
||||
if (!IsPositionInsideAllowedZone(WorldPosition, out Vector2 targetDir))
|
||||
{
|
||||
if (WorldPosition.Y < Level.Loaded.AbyssStart)
|
||||
{
|
||||
// Too far down
|
||||
returnTimer = returnTime * Rand.Range(0.75f, 1.25f);
|
||||
returnDir = Vector2.UnitY;
|
||||
}
|
||||
}
|
||||
else if (AIParams.StayInAbyss)
|
||||
{
|
||||
if (WorldPosition.Y > Level.Loaded.AbyssStart)
|
||||
{
|
||||
// Too far up
|
||||
returnTimer = returnTime * Rand.Range(0.75f, 1.25f);
|
||||
returnDir = -Vector2.UnitY;
|
||||
}
|
||||
}
|
||||
float margin = AIParams.AvoidAbyss ? 0 : 30000;
|
||||
if (WorldPosition.X < margin)
|
||||
{
|
||||
// Too far left
|
||||
returnDir = targetDir;
|
||||
returnTimer = returnTime * Rand.Range(0.75f, 1.25f);
|
||||
returnDir = Vector2.UnitX;
|
||||
}
|
||||
if (WorldPosition.X > levelSize.X + margin)
|
||||
{
|
||||
// Too far right
|
||||
returnTimer = returnTime * Rand.Range(0.75f, 1.25f);
|
||||
returnDir = -Vector2.UnitX;
|
||||
}
|
||||
if (returnTimer > 0)
|
||||
{
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Barotrauma
|
||||
|
||||
private readonly AIObjectiveManager objectiveManager;
|
||||
|
||||
private float sortTimer;
|
||||
public float SortTimer { get; set; }
|
||||
private float crouchRaycastTimer;
|
||||
private float reactTimer;
|
||||
private float unreachableClearTimer;
|
||||
@@ -131,7 +131,7 @@ namespace Barotrauma
|
||||
outsideSteering = new SteeringManager(this);
|
||||
objectiveManager = new AIObjectiveManager(c);
|
||||
reactTimer = GetReactionTime();
|
||||
sortTimer = Rand.Range(0f, sortObjectiveInterval);
|
||||
SortTimer = Rand.Range(0f, sortObjectiveInterval);
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
@@ -218,6 +218,7 @@ namespace Barotrauma
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (c.Submarine != Character.Submarine) { continue; }
|
||||
if (c.Removed || c.IsDead || c.IsIncapacitated) { continue; }
|
||||
if (IsFriendly(c)) { continue; }
|
||||
Vector2 toTarget = c.WorldPosition - WorldPosition;
|
||||
float dist = toTarget.LengthSquared();
|
||||
@@ -264,14 +265,14 @@ namespace Barotrauma
|
||||
CheckCrouching(deltaTime);
|
||||
Character.ClearInputs();
|
||||
|
||||
if (sortTimer > 0.0f)
|
||||
if (SortTimer > 0.0f)
|
||||
{
|
||||
sortTimer -= deltaTime;
|
||||
SortTimer -= deltaTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
objectiveManager.SortObjectives();
|
||||
sortTimer = sortObjectiveInterval;
|
||||
SortTimer = sortObjectiveInterval;
|
||||
}
|
||||
objectiveManager.UpdateObjectives(deltaTime);
|
||||
|
||||
@@ -288,14 +289,14 @@ namespace Barotrauma
|
||||
{
|
||||
if (Character.CurrentHull != null)
|
||||
{
|
||||
if (Character.TeamID == CharacterTeamType.FriendlyNPC)
|
||||
if (Character.IsOnPlayerTeam)
|
||||
{
|
||||
// Outpost npcs don't inform each other about threats, like crew members do.
|
||||
VisibleHulls.ForEach(h => RefreshHullSafety(h));
|
||||
VisibleHulls.ForEach(h => PropagateHullSafety(Character, h));
|
||||
}
|
||||
else
|
||||
{
|
||||
VisibleHulls.ForEach(h => PropagateHullSafety(Character, h));
|
||||
// Outpost npcs don't inform each other about threats, like crew members do.
|
||||
VisibleHulls.ForEach(h => RefreshHullSafety(h));
|
||||
}
|
||||
}
|
||||
if (Character.SpeechImpediment < 100.0f)
|
||||
@@ -1065,7 +1066,9 @@ namespace Barotrauma
|
||||
{
|
||||
if (!IsFriendly(attacker))
|
||||
{
|
||||
return c.IsSecurity ? AIObjectiveCombat.CombatMode.Offensive : AIObjectiveCombat.CombatMode.Defensive;
|
||||
return c.AIController is HumanAIController humanAI &&
|
||||
(humanAI.ObjectiveManager.IsCurrentOrder<AIObjectiveFightIntruders>() || humanAI.ObjectiveManager.Objectives.Any(o => o is AIObjectiveFightIntruders))
|
||||
? AIObjectiveCombat.CombatMode.Offensive : AIObjectiveCombat.CombatMode.Defensive;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1192,7 +1195,7 @@ namespace Barotrauma
|
||||
{
|
||||
base.Reset();
|
||||
objectiveManager.SortObjectives();
|
||||
sortTimer = sortObjectiveInterval;
|
||||
SortTimer = sortObjectiveInterval;
|
||||
float waitDuration = characterWaitOnSwitch;
|
||||
if (ObjectiveManager.IsCurrentObjective<AIObjectiveIdle>())
|
||||
{
|
||||
@@ -1418,6 +1421,9 @@ namespace Barotrauma
|
||||
item.StolenDuringRound = true;
|
||||
otherCharacter.Speak(TextManager.Get("dialogstealwarning"), null, Rand.Range(0.5f, 1.0f), "thief", 10.0f);
|
||||
someoneSpoke = true;
|
||||
#if CLIENT
|
||||
HintManager.OnStoleItem(thief, item);
|
||||
#endif
|
||||
}
|
||||
// React if we are security
|
||||
if (!TriggerSecurity(otherHumanAI))
|
||||
@@ -1554,7 +1560,7 @@ namespace Barotrauma
|
||||
targetAdded = true;
|
||||
}
|
||||
}
|
||||
}, (caller.AIController as HumanAIController)?.ReportRange ?? float.PositiveInfinity);
|
||||
}, range: (caller.AIController as HumanAIController)?.ReportRange ?? float.PositiveInfinity);
|
||||
return targetAdded;
|
||||
}
|
||||
|
||||
@@ -1726,11 +1732,9 @@ namespace Barotrauma
|
||||
switch (myTeam)
|
||||
{
|
||||
case CharacterTeamType.None:
|
||||
// Only enemies are in the Team "None"
|
||||
return false;
|
||||
case CharacterTeamType.Team1:
|
||||
case CharacterTeamType.Team2:
|
||||
// Team1 is only friendly to Team1 and friendly NPCs
|
||||
// Only friendly to the same team and friendly NPCs
|
||||
return otherTeam == CharacterTeamType.FriendlyNPC;
|
||||
case CharacterTeamType.FriendlyNPC:
|
||||
// Friendly NPCs are friendly to both teams
|
||||
|
||||
@@ -221,6 +221,19 @@ namespace Barotrauma
|
||||
{
|
||||
currentFlags.Add("CampaignNPC." + speaker.CampaignInteractionType);
|
||||
}
|
||||
|
||||
if (GameMain.GameSession?.GameMode is CampaignMode campaignMode &&
|
||||
(campaignMode.Map?.CurrentLocation?.Type?.Identifier.Equals("abandoned", StringComparison.OrdinalIgnoreCase) ?? false))
|
||||
{
|
||||
if (speaker.TeamID == CharacterTeamType.None)
|
||||
{
|
||||
currentFlags.Add("Bandit");
|
||||
}
|
||||
else if (speaker.TeamID == CharacterTeamType.FriendlyNPC)
|
||||
{
|
||||
currentFlags.Add("Hostage");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return currentFlags;
|
||||
|
||||
+28
-16
@@ -79,6 +79,7 @@ namespace Barotrauma
|
||||
private float coolDownTimer;
|
||||
private IEnumerable<Body> myBodies;
|
||||
private float aimTimer;
|
||||
private float reloadTimer;
|
||||
private float spreadTimer;
|
||||
|
||||
private bool canSeeTarget;
|
||||
@@ -147,6 +148,7 @@ namespace Barotrauma
|
||||
Mode = CombatMode.Retreat;
|
||||
}
|
||||
spreadTimer = Rand.Range(-10, 10);
|
||||
HumanAIController.SortTimer = 0;
|
||||
}
|
||||
|
||||
public override float GetPriority()
|
||||
@@ -170,6 +172,10 @@ namespace Barotrauma
|
||||
base.Update(deltaTime);
|
||||
ignoreWeaponTimer -= deltaTime;
|
||||
checkWeaponsTimer -= deltaTime;
|
||||
if (reloadTimer > 0)
|
||||
{
|
||||
reloadTimer -= deltaTime;
|
||||
}
|
||||
if (ignoreWeaponTimer < 0)
|
||||
{
|
||||
ignoredWeapons.Clear();
|
||||
@@ -219,7 +225,11 @@ namespace Barotrauma
|
||||
{
|
||||
OperateWeapon(deltaTime);
|
||||
}
|
||||
if (!HoldPosition && seekAmmunitionObjective == null && seekWeaponObjective == null)
|
||||
if (HoldPosition)
|
||||
{
|
||||
SteeringManager.Reset();
|
||||
}
|
||||
else if (seekAmmunitionObjective == null && seekWeaponObjective == null)
|
||||
{
|
||||
Move(deltaTime);
|
||||
}
|
||||
@@ -641,7 +651,7 @@ namespace Barotrauma
|
||||
var slots = Weapon.AllowedSlots.Where(s => s == InvSlotType.LeftHand || s == InvSlotType.RightHand || s == (InvSlotType.LeftHand | InvSlotType.RightHand));
|
||||
if (character.Inventory.TryPutItem(Weapon, character, slots))
|
||||
{
|
||||
aimTimer = Rand.Range(1f, 1.5f) / AimSpeed;
|
||||
aimTimer = Rand.Range(0.2f, 0.4f) / AimSpeed;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -912,7 +922,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (!canSeeTarget)
|
||||
{
|
||||
aimTimer = Rand.Range(0.2f, 1f) / AimSpeed;
|
||||
aimTimer = Rand.Range(0.2f, 0.4f) / AimSpeed;
|
||||
return;
|
||||
}
|
||||
if (Weapon.RequireAimToUse)
|
||||
@@ -930,6 +940,7 @@ namespace Barotrauma
|
||||
aimTimer -= deltaTime;
|
||||
return;
|
||||
}
|
||||
if (reloadTimer > 0) { return; }
|
||||
if (Mode == CombatMode.Arrest && isLethalWeapon && Enemy.Stun > 1) { return; }
|
||||
if (holdFireCondition != null && holdFireCondition()) { return; }
|
||||
float sqrDist = Vector2.DistanceSquared(character.Position, Enemy.Position);
|
||||
@@ -1010,18 +1021,25 @@ namespace Barotrauma
|
||||
|
||||
private void UseWeapon(float deltaTime)
|
||||
{
|
||||
character.SetInput(InputType.Shoot, false, true);
|
||||
Weapon.Use(deltaTime, character);
|
||||
float reloadTime = 0;
|
||||
if (WeaponComponent is RangedWeapon rangedWeapon)
|
||||
{
|
||||
reloadTime = rangedWeapon.Reload;
|
||||
// If the weapon is just equipped, we can't shoot just yet.
|
||||
if (rangedWeapon.ReloadTimer <= 0)
|
||||
{
|
||||
reloadTime = rangedWeapon.Reload;
|
||||
}
|
||||
}
|
||||
if (WeaponComponent is MeleeWeapon mw)
|
||||
{
|
||||
reloadTime = mw.Reload;
|
||||
if (!((HumanoidAnimController)character.AnimController).Crouching)
|
||||
{
|
||||
reloadTime = mw.Reload;
|
||||
}
|
||||
}
|
||||
aimTimer = Math.Max(reloadTime, reloadTime * Rand.Range(1f, 1.5f) / AimSpeed);
|
||||
character.SetInput(InputType.Shoot, false, true);
|
||||
Weapon.Use(deltaTime, character);
|
||||
reloadTimer = Math.Max(reloadTime, reloadTime * Rand.Range(1f, 1.25f) / AimSpeed);
|
||||
}
|
||||
|
||||
protected override void OnCompleted()
|
||||
@@ -1031,10 +1049,7 @@ namespace Barotrauma
|
||||
{
|
||||
Unequip();
|
||||
}
|
||||
if (!HoldPosition)
|
||||
{
|
||||
SteeringManager.Reset();
|
||||
}
|
||||
SteeringManager.Reset();
|
||||
}
|
||||
|
||||
protected override void OnAbandon()
|
||||
@@ -1044,10 +1059,7 @@ namespace Barotrauma
|
||||
{
|
||||
Unequip();
|
||||
}
|
||||
if (!HoldPosition)
|
||||
{
|
||||
SteeringManager.Reset();
|
||||
}
|
||||
SteeringManager.Reset();
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
|
||||
+4
-1
@@ -10,6 +10,8 @@ namespace Barotrauma
|
||||
protected override float IgnoreListClearInterval => 30;
|
||||
public override bool IgnoreUnsafeHulls => true;
|
||||
|
||||
protected override float TargetUpdateTimeMultiplier => 0.2f;
|
||||
|
||||
public AIObjectiveFightIntruders(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier) { }
|
||||
|
||||
@@ -48,7 +50,8 @@ namespace Barotrauma
|
||||
|
||||
public static bool IsValidTarget(Character target, Character character)
|
||||
{
|
||||
if (target == null || target.IsDead || target.Removed) { return false; }
|
||||
if (target == null || target.Removed) { return false; }
|
||||
if (target.IsDead || target.IsUnconscious) { return false; }
|
||||
if (target == character) { return false; }
|
||||
if (target.Submarine == null) { return false; }
|
||||
if (character.Submarine == null) { return false; }
|
||||
|
||||
@@ -555,6 +555,13 @@ namespace Barotrauma
|
||||
//otherwise characters can let go of the ladders too soon once they're close enough to the target
|
||||
if (PathSteering.CurrentPath.NextNode != null) { return false; }
|
||||
}
|
||||
if (!character.AnimController.InWater)
|
||||
{
|
||||
float yDiff = Math.Abs(Target.WorldPosition.Y - character.WorldPosition.Y);
|
||||
if (yDiff > CloseEnough) { return false; }
|
||||
float xDiff = Math.Abs(Target.WorldPosition.X - character.WorldPosition.X);
|
||||
return xDiff <= CloseEnough;
|
||||
}
|
||||
return Vector2.DistanceSquared(Target.WorldPosition, character.WorldPosition) < CloseEnough * CloseEnough;
|
||||
}
|
||||
}
|
||||
|
||||
+6
-5
@@ -11,6 +11,7 @@ namespace Barotrauma
|
||||
protected HashSet<T> ignoreList = new HashSet<T>();
|
||||
private float ignoreListTimer;
|
||||
protected float targetUpdateTimer;
|
||||
protected virtual float TargetUpdateTimeMultiplier { get; } = 1;
|
||||
|
||||
private float syncTimer;
|
||||
private readonly float syncTime = 1;
|
||||
@@ -61,7 +62,7 @@ namespace Barotrauma
|
||||
ignoreListTimer += deltaTime;
|
||||
}
|
||||
}
|
||||
if (targetUpdateTimer < 0)
|
||||
if (targetUpdateTimer <= 0)
|
||||
{
|
||||
UpdateTargets();
|
||||
}
|
||||
@@ -69,9 +70,9 @@ namespace Barotrauma
|
||||
{
|
||||
targetUpdateTimer -= deltaTime;
|
||||
}
|
||||
if (syncTimer < 0)
|
||||
if (syncTimer <= 0)
|
||||
{
|
||||
syncTimer = syncTime * Rand.Range(0.9f, 1.1f);
|
||||
syncTimer = Math.Min(syncTime * Rand.Range(0.9f, 1.1f), targetUpdateTimer);
|
||||
// Sync objectives, subobjectives and targets
|
||||
foreach (var objective in Objectives)
|
||||
{
|
||||
@@ -95,7 +96,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
// the timer is set between 1 and 10 seconds, depending on the priority modifier and a random +-25%
|
||||
private float SetTargetUpdateTimer() => targetUpdateTimer = 1 / MathHelper.Clamp(PriorityModifier * Rand.Range(0.75f, 1.25f), 0.1f, 1);
|
||||
private float CalculateTargetUpdateTimer() => targetUpdateTimer = 1 / MathHelper.Clamp(PriorityModifier * Rand.Range(0.75f, 1.25f), 0.1f, 1) * TargetUpdateTimeMultiplier;
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
@@ -156,7 +157,7 @@ namespace Barotrauma
|
||||
|
||||
protected void UpdateTargets()
|
||||
{
|
||||
SetTargetUpdateTimer();
|
||||
CalculateTargetUpdateTimer();
|
||||
Targets.Clear();
|
||||
FindTargets();
|
||||
CreateObjectives();
|
||||
|
||||
+3
-1
@@ -386,7 +386,9 @@ namespace Barotrauma
|
||||
Abandon = true;
|
||||
return false;
|
||||
}
|
||||
bool isCompleted = AIObjectiveRescueAll.GetVitalityFactor(targetCharacter) >= AIObjectiveRescueAll.GetVitalityThreshold(objectiveManager, character, targetCharacter);
|
||||
bool isCompleted =
|
||||
AIObjectiveRescueAll.GetVitalityFactor(targetCharacter) >= AIObjectiveRescueAll.GetVitalityThreshold(objectiveManager, character, targetCharacter) ||
|
||||
targetCharacter.CharacterHealth.GetAllAfflictions().All(a => a.Strength < a.Prefab.TreatmentThreshold);
|
||||
if (isCompleted && targetCharacter != character && character.IsOnPlayerTeam)
|
||||
{
|
||||
character.Speak(TextManager.GetWithVariable("DialogTargetHealed", "[targetname]", targetCharacter.Name),
|
||||
|
||||
+3
-3
@@ -25,8 +25,8 @@ namespace Barotrauma
|
||||
{
|
||||
// When targeting player characters, always treat them when ordered, else use the threshold so that minor/non-severe damage is ignored.
|
||||
// If we ignore any damage when the player orders a bot to do healings, it's observed to cause confusion among the players.
|
||||
// On the other hand, if the bots too eagerly heal characters when it's not nevessary, it's inefficient and can feel frustrating, because it can't be controlled.
|
||||
return character == target || manager.CurrentOrder is AIObjectiveRescueAll ? (target.IsPlayer ? 100 : vitalityThresholdForOrders) : vitalityThreshold;
|
||||
// On the other hand, if the bots too eagerly heal characters when it's not necessary, it's inefficient and can feel frustrating, because it can't be controlled.
|
||||
return character == target || manager.HasOrder<AIObjectiveRescueAll>() ? (target.IsPlayer ? 100 : vitalityThresholdForOrders) : vitalityThreshold;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ namespace Barotrauma
|
||||
if (character.AIController is HumanAIController humanAI)
|
||||
{
|
||||
if (GetVitalityFactor(target) >= GetVitalityThreshold(humanAI.ObjectiveManager, character, target)) { return false; }
|
||||
if (!humanAI.ObjectiveManager.IsCurrentOrder<AIObjectiveRescueAll>())
|
||||
if (!humanAI.ObjectiveManager.HasOrder<AIObjectiveRescueAll>())
|
||||
{
|
||||
if (!character.IsMedic && target != character)
|
||||
{
|
||||
|
||||
@@ -281,7 +281,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public const float MAX_SPEED = 30;
|
||||
public const float MAX_SPEED = 20;
|
||||
|
||||
public Vector2 TargetMovement
|
||||
{
|
||||
@@ -636,9 +636,12 @@ namespace Barotrauma
|
||||
//always collides with bodies other than structures
|
||||
if (!(f2.Body.UserData is Structure structure))
|
||||
{
|
||||
lock (impactQueue)
|
||||
if (!f2.IsSensor)
|
||||
{
|
||||
impactQueue.Enqueue(new Impact(f1, f2, contact, velocity));
|
||||
lock (impactQueue)
|
||||
{
|
||||
impactQueue.Enqueue(new Impact(f1, f2, contact, velocity));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -156,6 +156,8 @@ namespace Barotrauma
|
||||
|
||||
public Entity LastDamageSource;
|
||||
|
||||
public AttackResult LastDamage;
|
||||
|
||||
public float InvisibleTimer;
|
||||
|
||||
private CharacterPrefab prefab;
|
||||
@@ -199,7 +201,12 @@ namespace Barotrauma
|
||||
set => Params.Visibility = value;
|
||||
}
|
||||
|
||||
public bool IsTraitor;
|
||||
public bool IsTraitor
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public string TraitorCurrentObjective = "";
|
||||
public bool IsHuman => SpeciesName.Equals(CharacterPrefab.HumanSpeciesName, StringComparison.OrdinalIgnoreCase);
|
||||
public bool IsMale => Info != null && Info.HasGenders && Info.Gender == Gender.Male;
|
||||
@@ -333,6 +340,7 @@ namespace Barotrauma
|
||||
//text displayed when the character is highlighted if custom interact is set
|
||||
public string customInteractHUDText;
|
||||
private Action<Character, Character> onCustomInteract;
|
||||
public ConversationAction ActiveConversation;
|
||||
|
||||
public bool AllowCustomInteract
|
||||
{
|
||||
@@ -349,6 +357,9 @@ namespace Barotrauma
|
||||
set
|
||||
{
|
||||
lockHandsTimer = MathHelper.Clamp(lockHandsTimer + (value ? 1.0f : -0.5f), 0.0f, 10.0f);
|
||||
#if CLIENT
|
||||
HintManager.OnHandcuffed(this);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -605,6 +616,9 @@ namespace Barotrauma
|
||||
get => _selectedConstruction;
|
||||
set
|
||||
{
|
||||
#if CLIENT
|
||||
HintManager.OnSetSelectedConstruction(this, _selectedConstruction, value);
|
||||
#endif
|
||||
_selectedConstruction = value;
|
||||
#if CLIENT
|
||||
if (Controlled == this)
|
||||
@@ -1661,6 +1675,12 @@ namespace Barotrauma
|
||||
{
|
||||
item.Use(deltaTime, this);
|
||||
}
|
||||
#if CLIENT
|
||||
else if (item.RequireAimToUse && !IsKeyDown(InputType.Aim))
|
||||
{
|
||||
HintManager.OnShootWithoutAiming(this, item);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1853,6 +1873,19 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
|
||||
public Item GetEquippedItem(string tagOrIdentifier)
|
||||
{
|
||||
if (Inventory == null) { return null; }
|
||||
for (int i = 0; i < Inventory.Capacity; i++)
|
||||
{
|
||||
if (Inventory.SlotTypes[i] == InvSlotType.Any) { continue; }
|
||||
var item = Inventory.GetItemAt(i);
|
||||
if (item == null) { continue; }
|
||||
if (item.Prefab.Identifier == tagOrIdentifier || item.HasTag(tagOrIdentifier)) { return item; }
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool CanAccessInventory(Inventory inventory)
|
||||
{
|
||||
if (!CanInteract || inventory.Locked) { return false; }
|
||||
@@ -2857,6 +2890,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (character == this) { continue; }
|
||||
if (character.TeamID != TeamID) { continue; }
|
||||
if (!HumanAIController.IsActive(character)) { continue; }
|
||||
foreach (var currentOrder in character.CurrentOrders)
|
||||
{
|
||||
if (currentOrder.Order == null) { continue; }
|
||||
@@ -3268,12 +3302,13 @@ namespace Barotrauma
|
||||
//#endif
|
||||
// }
|
||||
|
||||
SetStun(stun);
|
||||
|
||||
if (attacker != null && attacker != this && GameMain.NetworkMember != null && !GameMain.NetworkMember.ServerSettings.AllowFriendlyFire)
|
||||
{
|
||||
if (attacker.TeamID == TeamID) { return new AttackResult(); }
|
||||
}
|
||||
|
||||
SetStun(stun);
|
||||
Vector2 dir = hitLimb.WorldPosition - worldPosition;
|
||||
if (Math.Abs(attackImpulse) > 0.0f)
|
||||
{
|
||||
@@ -3308,6 +3343,7 @@ namespace Barotrauma
|
||||
};
|
||||
if (attackResult.Damage > 0)
|
||||
{
|
||||
LastDamage = attackResult;
|
||||
ApplyStatusEffects(ActionType.OnDamaged, 1.0f);
|
||||
hitLimb.ApplyStatusEffects(ActionType.OnDamaged, 1.0f);
|
||||
if (attacker != null)
|
||||
|
||||
@@ -153,6 +153,8 @@ namespace Barotrauma
|
||||
private static ushort idCounter;
|
||||
private const string disguiseName = "???";
|
||||
|
||||
public bool HasNickname => Name != OriginalName;
|
||||
public string OriginalName { get; private set; }
|
||||
public string Name;
|
||||
public string DisplayName
|
||||
{
|
||||
@@ -453,7 +455,7 @@ namespace Barotrauma
|
||||
public bool IsAttachmentsLoaded => HairIndex > -1 && BeardIndex > -1 && MoustacheIndex > -1 && FaceAttachmentIndex > -1;
|
||||
|
||||
// Used for creating the data
|
||||
public CharacterInfo(string speciesName, string name = "", JobPrefab jobPrefab = null, string ragdollFileName = null, int variant = 0, Rand.RandSync randSync = Rand.RandSync.Unsynced)
|
||||
public CharacterInfo(string speciesName, string name = "", string originalName = "", JobPrefab jobPrefab = null, string ragdollFileName = null, int variant = 0, Rand.RandSync randSync = Rand.RandSync.Unsynced)
|
||||
{
|
||||
if (speciesName.EndsWith(".xml", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
@@ -503,6 +505,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
OriginalName = !string.IsNullOrEmpty(originalName) ? originalName : Name;
|
||||
personalityTrait = NPCPersonalityTrait.GetRandom(name + HeadSpriteId);
|
||||
Salary = CalculateSalary();
|
||||
if (ragdollFileName != null)
|
||||
@@ -518,6 +521,7 @@ namespace Barotrauma
|
||||
ID = idCounter;
|
||||
idCounter++;
|
||||
Name = infoElement.GetAttributeString("name", "");
|
||||
OriginalName = infoElement.GetAttributeString("originalname", null);
|
||||
string genderStr = infoElement.GetAttributeString("gender", "male").ToLowerInvariant();
|
||||
Salary = infoElement.GetAttributeInt("salary", 1000);
|
||||
Enum.TryParse(infoElement.GetAttributeString("race", "White"), true, out Race race);
|
||||
@@ -576,6 +580,11 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(OriginalName))
|
||||
{
|
||||
OriginalName = Name;
|
||||
}
|
||||
|
||||
StartItemsGiven = infoElement.GetAttributeBool("startitemsgiven", false);
|
||||
string personalityName = infoElement.GetAttributeString("personality", "");
|
||||
ragdollFileName = infoElement.GetAttributeString("ragdoll", string.Empty);
|
||||
@@ -622,7 +631,17 @@ namespace Barotrauma
|
||||
|
||||
public int GetIdentifier()
|
||||
{
|
||||
int id = ToolBox.StringToInt(Name);
|
||||
return GetIdentifier(Name);
|
||||
}
|
||||
|
||||
public int GetIdentifierUsingOriginalName()
|
||||
{
|
||||
return GetIdentifier(OriginalName);
|
||||
}
|
||||
|
||||
private int GetIdentifier(string name)
|
||||
{
|
||||
int id = ToolBox.StringToInt(name);
|
||||
id ^= HeadSpriteId;
|
||||
id ^= (int)Race << 6;
|
||||
id ^= HairIndex << 12;
|
||||
@@ -939,12 +958,24 @@ namespace Barotrauma
|
||||
|
||||
partial void OnSkillChanged(string skillIdentifier, float prevLevel, float newLevel, Vector2 textPopupPos);
|
||||
|
||||
public void Rename(string newName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(newName)) { return; }
|
||||
Name = newName;
|
||||
}
|
||||
|
||||
public void ResetName()
|
||||
{
|
||||
Name = OriginalName;
|
||||
}
|
||||
|
||||
public XElement Save(XElement parentElement)
|
||||
{
|
||||
XElement charElement = new XElement("Character");
|
||||
|
||||
charElement.Add(
|
||||
new XAttribute("name", Name),
|
||||
new XAttribute("originalname", OriginalName),
|
||||
new XAttribute("speciesname", SpeciesName),
|
||||
new XAttribute("gender", Head.gender == Gender.Male ? "male" : "female"),
|
||||
new XAttribute("race", Head.race.ToString()),
|
||||
@@ -957,7 +988,7 @@ namespace Barotrauma
|
||||
new XAttribute("startitemsgiven", StartItemsGiven),
|
||||
new XAttribute("ragdoll", ragdollFileName),
|
||||
new XAttribute("personality", personalityTrait == null ? "" : personalityTrait.Name));
|
||||
|
||||
|
||||
// TODO: animations?
|
||||
|
||||
if (Character != null)
|
||||
|
||||
+5
-2
@@ -260,10 +260,13 @@ namespace Barotrauma
|
||||
|
||||
/// <summary>
|
||||
/// Use this method to skip clamping and additional logic of the setters.
|
||||
/// Intended only to be used when the value is already clamped! (networking code)
|
||||
/// Ideally we would keep this private, but doing so would require too much refactoring.
|
||||
/// </summary>
|
||||
public void SetStrength(float strength) => _strength = strength;
|
||||
public void SetStrength(float strength)
|
||||
{
|
||||
_nonClampedStrength = strength;
|
||||
_strength = _nonClampedStrength;
|
||||
}
|
||||
|
||||
public bool ShouldShowIcon(Character afflictedCharacter)
|
||||
{
|
||||
|
||||
+28
-3
@@ -290,6 +290,9 @@ namespace Barotrauma
|
||||
//how high the strength has to be for the affliction icon to be shown with a health scanner
|
||||
public readonly float ShowInHealthScannerThreshold = 0.05f;
|
||||
|
||||
//how strong the affliction needs to be before bots attempt to treat it
|
||||
public readonly float TreatmentThreshold = 5.0f;
|
||||
|
||||
//how much karma changes when a player applies this affliction to someone (per strength of the affliction)
|
||||
public float KarmaChangeOnApplied;
|
||||
|
||||
@@ -376,6 +379,9 @@ namespace Barotrauma
|
||||
{
|
||||
DebugConsole.ThrowError("Cannot override all afflictions, because many of them are required by the main game! Please try overriding them one by one.");
|
||||
}
|
||||
|
||||
List<(AfflictionPrefab prefab, XElement element)> loadedAfflictions = new List<(AfflictionPrefab prefab, XElement element)>();
|
||||
|
||||
foreach (XElement element in mainElement.Elements())
|
||||
{
|
||||
bool isOverride = element.IsOverride();
|
||||
@@ -510,10 +516,18 @@ namespace Barotrauma
|
||||
|
||||
if (prefab != null)
|
||||
{
|
||||
loadedAfflictions.Add((prefab, element));
|
||||
Prefabs.Add(prefab, isOverride);
|
||||
prefab.CalculatePrefabUIntIdentifier(Prefabs);
|
||||
}
|
||||
}
|
||||
|
||||
//load the effects after all the afflictions in the file have been instantiated
|
||||
//otherwise afflictions can't inflict other afflictions that are defined at a later point in the file
|
||||
foreach ((AfflictionPrefab prefab, XElement element) in loadedAfflictions)
|
||||
{
|
||||
prefab.LoadEffects(element);
|
||||
}
|
||||
}
|
||||
|
||||
public static void RemoveByFile(string filePath)
|
||||
@@ -565,6 +579,7 @@ namespace Barotrauma
|
||||
MaxStrength = element.GetAttributeFloat("maxstrength", 100.0f);
|
||||
|
||||
ShowInHealthScannerThreshold = element.GetAttributeFloat("showinhealthscannerthreshold", Math.Max(ActivationThreshold, 0.05f));
|
||||
TreatmentThreshold = element.GetAttributeFloat("treatmentthreshold", Math.Max(ActivationThreshold, 5.0f));
|
||||
|
||||
DamageOverlayAlpha = element.GetAttributeFloat("damageoverlayalpha", 0.0f);
|
||||
BurnOverlayAlpha = element.GetAttributeFloat("burnoverlayalpha", 0.0f);
|
||||
@@ -584,9 +599,6 @@ namespace Barotrauma
|
||||
case "icon":
|
||||
Icon = new Sprite(subElement);
|
||||
break;
|
||||
case "effect":
|
||||
effects.Add(new Effect(subElement, Name));
|
||||
break;
|
||||
case "periodiceffect":
|
||||
periodicEffects.Add(new PeriodicEffect(subElement, Name));
|
||||
break;
|
||||
@@ -614,6 +626,19 @@ namespace Barotrauma
|
||||
constructor = type.GetConstructor(new[] { typeof(AfflictionPrefab), typeof(float) });
|
||||
}
|
||||
|
||||
private void LoadEffects(XElement element)
|
||||
{
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "effect":
|
||||
effects.Add(new Effect(subElement, Name));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "AfflictionPrefab (" + Name + ")";
|
||||
|
||||
@@ -36,7 +36,11 @@ namespace Barotrauma
|
||||
|
||||
public LimbHealth(XElement element, CharacterHealth characterHealth)
|
||||
{
|
||||
Name = TextManager.Get("HealthLimbName." + element.GetAttributeString("name", ""));
|
||||
string limbName = element.GetAttributeString("name", null) ?? "generic";
|
||||
if (limbName != "generic")
|
||||
{
|
||||
Name = TextManager.Get("HealthLimbName." + limbName);
|
||||
}
|
||||
this.characterHealth = characterHealth;
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
@@ -664,7 +668,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
partial void UpdateProjSpecific(float deltaTime);
|
||||
|
||||
partial void UpdateLimbAfflictionOverlays();
|
||||
@@ -687,6 +690,10 @@ namespace Barotrauma
|
||||
{
|
||||
var affliction = limbHealths[i].Afflictions[j];
|
||||
Limb targetLimb = Character.AnimController.Limbs.LastOrDefault(l => !l.IsSevered && !l.Hidden && l.HealthIndex == i);
|
||||
if (targetLimb == null)
|
||||
{
|
||||
targetLimb = Character.AnimController.MainLimb;
|
||||
}
|
||||
affliction.Update(this, targetLimb, deltaTime);
|
||||
affliction.DamagePerSecondTimer += deltaTime;
|
||||
if (affliction is AfflictionBleeding bleeding)
|
||||
@@ -877,6 +884,7 @@ namespace Barotrauma
|
||||
float minSuitability = -10, maxSuitability = 10;
|
||||
foreach (Affliction affliction in GetAllAfflictions())
|
||||
{
|
||||
if (affliction.Strength < affliction.Prefab.TreatmentThreshold) { continue; }
|
||||
foreach (KeyValuePair<string, float> treatment in affliction.Prefab.TreatmentSuitability)
|
||||
{
|
||||
if (!treatmentSuitability.ContainsKey(treatment.Key))
|
||||
|
||||
@@ -20,6 +20,9 @@ namespace Barotrauma
|
||||
[Serialize(1f, false)]
|
||||
public float HealthMultiplier { get; protected set; }
|
||||
|
||||
[Serialize(1f, false)]
|
||||
public float HealthMultiplierInMultiplayer { get; protected set; }
|
||||
|
||||
[Serialize(1f, false)]
|
||||
public float AimSpeed { get; protected set; }
|
||||
|
||||
@@ -117,6 +120,10 @@ namespace Barotrauma
|
||||
public void InitializeCharacter(Character npc, ISpatialEntity positionToStayIn = null)
|
||||
{
|
||||
npc.CharacterHealth.MaxVitality *= HealthMultiplier;
|
||||
if (GameMain.NetworkMember != null)
|
||||
{
|
||||
npc.CharacterHealth.MaxVitality *= HealthMultiplierInMultiplayer;
|
||||
}
|
||||
var humanAI = npc.AIController as HumanAIController;
|
||||
if (humanAI != null)
|
||||
{
|
||||
|
||||
@@ -732,6 +732,10 @@ namespace Barotrauma
|
||||
{
|
||||
newAffliction = affliction.CreateMultiplied(finalDamageModifier);
|
||||
}
|
||||
else
|
||||
{
|
||||
newAffliction.SetStrength(affliction.NonClampedStrength);
|
||||
}
|
||||
|
||||
if (applyAffliction)
|
||||
{
|
||||
|
||||
@@ -430,10 +430,10 @@ namespace Barotrauma
|
||||
[Serialize(false, true)]
|
||||
public bool UseHealthWindow { get; set; }
|
||||
|
||||
[Serialize(0f, true, description: "How easily the character heals from the bleeding wounds. Default 0 (no extra healing)."), Editable(MinValueFloat = 0, MaxValueFloat = 10, DecimalCount = 2)]
|
||||
[Serialize(0f, true, description: "How easily the character heals from the bleeding wounds. Default 0 (no extra healing)."), Editable(MinValueFloat = 0, MaxValueFloat = 100, DecimalCount = 2)]
|
||||
public float BleedingReduction { get; private set; }
|
||||
|
||||
[Serialize(0f, true, description: "How easily the character heals from the burn wounds. Default 0 (no extra healing)."), Editable(MinValueFloat = 0, MaxValueFloat = 10, DecimalCount = 2)]
|
||||
[Serialize(0f, true, description: "How easily the character heals from the burn wounds. Default 0 (no extra healing)."), Editable(MinValueFloat = 0, MaxValueFloat = 100, DecimalCount = 2)]
|
||||
public float BurnReduction { get; private set; }
|
||||
|
||||
[Serialize(0f, true), Editable(MinValueFloat = 0, MaxValueFloat = 10, DecimalCount = 2)]
|
||||
|
||||
@@ -707,9 +707,10 @@ namespace Barotrauma
|
||||
|
||||
commands.Add(new Command("freecamera|freecam", "freecam: Detach the camera from the controlled character.", (string[] args) =>
|
||||
{
|
||||
#if CLIENT
|
||||
if (Screen.Selected == GameMain.SubEditorScreen) { return; }
|
||||
Character.Controlled = null;
|
||||
GameMain.GameScreen.Cam.TargetPos = Vector2.Zero;
|
||||
#if CLIENT
|
||||
GameMain.Client?.SendConsoleCommand("freecam");
|
||||
#endif
|
||||
}, isCheat: true));
|
||||
@@ -1695,6 +1696,8 @@ namespace Barotrauma
|
||||
return null;
|
||||
}
|
||||
|
||||
// Use same sorting as DebugConsole.ListCharacterNames() above
|
||||
matchingCharacters = matchingCharacters.OrderBy(c => c.IsDead).ThenByDescending(c => c.IsHuman).ToList();
|
||||
if (characterIndex == -1)
|
||||
{
|
||||
if (matchingCharacters.Count > 1)
|
||||
|
||||
@@ -179,6 +179,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (speaker == null) { return; }
|
||||
speaker.CampaignInteractionType = CampaignMode.InteractionType.None;
|
||||
speaker.ActiveConversation = this;
|
||||
speaker.SetCustomInteract(null, null);
|
||||
#if SERVER
|
||||
GameMain.NetworkMember.CreateEntityEvent(speaker, new object[] { NetEntityEvent.Type.AssignCampaignInteraction });
|
||||
@@ -213,24 +214,24 @@ namespace Barotrauma
|
||||
#if CLIENT
|
||||
Character.DisableControls = true;
|
||||
#endif
|
||||
if (ShouldInterrupt())
|
||||
if (ShouldInterrupt())
|
||||
{
|
||||
ResetSpeaker();
|
||||
interrupt = true;
|
||||
interrupt = true;
|
||||
}
|
||||
return;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(SpeakerTag))
|
||||
{
|
||||
if (speaker != null && !speaker.Removed && speaker.CampaignInteractionType == CampaignMode.InteractionType.Talk) { return; }
|
||||
if (speaker != null && !speaker.Removed && speaker.CampaignInteractionType == CampaignMode.InteractionType.Talk && speaker.ActiveConversation?.ParentEvent != this.ParentEvent) { return; }
|
||||
speaker = ParentEvent.GetTargets(SpeakerTag).FirstOrDefault(e => e is Character) as Character;
|
||||
if (speaker == null || speaker.Removed)
|
||||
{
|
||||
return;
|
||||
{
|
||||
return;
|
||||
}
|
||||
//some conversation already assigned to the speaker, wait for it to be removed
|
||||
if (speaker.CampaignInteractionType == CampaignMode.InteractionType.Talk)
|
||||
if (speaker.CampaignInteractionType == CampaignMode.InteractionType.Talk && speaker.ActiveConversation?.ParentEvent != this.ParentEvent)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -241,6 +242,7 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
speaker.CampaignInteractionType = CampaignMode.InteractionType.Talk;
|
||||
speaker.ActiveConversation = this;
|
||||
#if CLIENT
|
||||
speaker.SetCustomInteract(
|
||||
TryStartConversation,
|
||||
|
||||
@@ -18,14 +18,13 @@ namespace Barotrauma
|
||||
|
||||
public MissionAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element)
|
||||
{
|
||||
//TODO: use event identifier in the error messages
|
||||
if (string.IsNullOrEmpty(MissionIdentifier) && string.IsNullOrEmpty(MissionTag))
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in event \"{"event identifier goes here"}\": neither MissionIdentifier or MissionTag has been configured.");
|
||||
DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\": neither MissionIdentifier or MissionTag has been configured.");
|
||||
}
|
||||
if (!string.IsNullOrEmpty(MissionIdentifier) && !string.IsNullOrEmpty(MissionTag))
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in event \"{"event identifier goes here"}\": both MissionIdentifier or MissionTag have been configured. The tag will be ignored.");
|
||||
DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\": both MissionIdentifier or MissionTag have been configured. The tag will be ignored.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -240,7 +240,7 @@ namespace Barotrauma
|
||||
return GetSpawnPos(SpawnLocation, spawnPointType, targetModuleTags, SpawnPointTag.ToEnumerable());
|
||||
}
|
||||
|
||||
public static WayPoint GetSpawnPos(SpawnLocationType spawnLocation, SpawnType? spawnPointType, IEnumerable<string> moduleFlags = null, IEnumerable<string> spawnpointTags = null)
|
||||
public static WayPoint GetSpawnPos(SpawnLocationType spawnLocation, SpawnType? spawnPointType, IEnumerable<string> moduleFlags = null, IEnumerable<string> spawnpointTags = null, bool asFarAsPossibleFromAirlock = false)
|
||||
{
|
||||
List<WayPoint> potentialSpawnPoints = spawnLocation switch
|
||||
{
|
||||
@@ -253,6 +253,7 @@ namespace Barotrauma
|
||||
};
|
||||
|
||||
potentialSpawnPoints = potentialSpawnPoints.FindAll(wp => wp.ConnectedDoor == null && wp.Ladders == null && !wp.isObstructed);
|
||||
var airlockSpawnPoints = potentialSpawnPoints.Where(wp => wp.CurrentHull?.OutpostModuleTags?.Contains("airlock") ?? false).ToList();
|
||||
|
||||
if (moduleFlags != null && moduleFlags.Any())
|
||||
{
|
||||
@@ -282,7 +283,7 @@ namespace Barotrauma
|
||||
IEnumerable<WayPoint> validSpawnPoints;
|
||||
if (spawnPointType.HasValue)
|
||||
{
|
||||
validSpawnPoints = potentialSpawnPoints.FindAll(wp => wp.SpawnType == spawnPointType.Value);
|
||||
validSpawnPoints = potentialSpawnPoints.FindAll(wp => spawnPointType.Value.HasFlag(wp.SpawnType));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -291,7 +292,6 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
//don't spawn in an airlock module if there are other options
|
||||
var airlockSpawnPoints = validSpawnPoints.Where(wp => wp.CurrentHull?.OutpostModuleTags?.Contains("airlock") ?? false);
|
||||
if (airlockSpawnPoints.Count() < validSpawnPoints.Count())
|
||||
{
|
||||
validSpawnPoints = validSpawnPoints.Except(airlockSpawnPoints);
|
||||
@@ -313,7 +313,25 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
return validSpawnPoints.GetRandom();
|
||||
if (asFarAsPossibleFromAirlock && airlockSpawnPoints.Any())
|
||||
{
|
||||
WayPoint furthestPoint = validSpawnPoints.First();
|
||||
float furthestDist = 0.0f;
|
||||
foreach (WayPoint waypoint in validSpawnPoints)
|
||||
{
|
||||
float dist = Vector2.DistanceSquared(waypoint.WorldPosition, airlockSpawnPoints.First().WorldPosition);
|
||||
if (dist > furthestDist)
|
||||
{
|
||||
furthestDist = dist;
|
||||
furthestPoint = waypoint;
|
||||
}
|
||||
}
|
||||
return furthestPoint;
|
||||
}
|
||||
else
|
||||
{
|
||||
return validSpawnPoints.GetRandom();
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
|
||||
@@ -4,6 +4,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using NLog;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -13,7 +14,8 @@ namespace Barotrauma
|
||||
{
|
||||
CONVERSATION,
|
||||
STATUSEFFECT,
|
||||
MISSION
|
||||
MISSION,
|
||||
UNLOCKPATH
|
||||
}
|
||||
|
||||
const float IntensityUpdateInterval = 5.0f;
|
||||
@@ -111,21 +113,50 @@ namespace Barotrauma
|
||||
|
||||
SelectSettings();
|
||||
|
||||
var initialEventSet = SelectRandomEvents(EventSet.List);
|
||||
if (initialEventSet != null)
|
||||
int seed = 0;
|
||||
if (level != null)
|
||||
{
|
||||
pendingEventSets.Add(initialEventSet);
|
||||
int seed = ToolBox.StringToInt(level.Seed);
|
||||
seed = ToolBox.StringToInt(level.Seed);
|
||||
foreach (var previousEvent in level.LevelData.EventHistory)
|
||||
{
|
||||
seed ^= ToolBox.StringToInt(previousEvent.Identifier);
|
||||
}
|
||||
MTRandom rand = new MTRandom(seed);
|
||||
}
|
||||
MTRandom rand = new MTRandom(seed);
|
||||
|
||||
var initialEventSet = SelectRandomEvents(EventSet.List);
|
||||
if (initialEventSet != null)
|
||||
{
|
||||
pendingEventSets.Add(initialEventSet);
|
||||
CreateEvents(initialEventSet, rand);
|
||||
}
|
||||
|
||||
if (level?.LevelData?.Type == LevelData.LevelType.Outpost)
|
||||
{
|
||||
//if the outpost is connected to a locked connection, create an event to unlock it
|
||||
if (level.StartLocation?.Connections.Any(c => c.Locked) ?? false)
|
||||
{
|
||||
var unlockPathPrefabs = EventSet.PrefabList.FindAll(e => e.UnlockPathEvent);
|
||||
var unlockPathPrefabsForBiome = unlockPathPrefabs.FindAll(e =>
|
||||
string.IsNullOrEmpty(e.BiomeIdentifier) ||
|
||||
e.BiomeIdentifier.Equals(level.LevelData.Biome.Identifier, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
var unlockPathEventPrefab = unlockPathPrefabsForBiome.Any() ?
|
||||
ToolBox.SelectWeightedRandom(unlockPathPrefabsForBiome, unlockPathPrefabsForBiome.Select(b => b.Commonness).ToList(), rand) :
|
||||
ToolBox.SelectWeightedRandom(unlockPathPrefabs, unlockPathPrefabs.Select(b => b.Commonness).ToList(), rand);
|
||||
if (unlockPathEventPrefab != null)
|
||||
{
|
||||
var newEvent = unlockPathEventPrefab.CreateInstance();
|
||||
newEvent.Init(true);
|
||||
ActiveEvents.Add(newEvent);
|
||||
}
|
||||
else
|
||||
{
|
||||
//if no event that unlocks the path can be found, unlock it automatically
|
||||
level.StartLocation.Connections.ForEach(c => c.Locked = false);
|
||||
}
|
||||
}
|
||||
|
||||
level.LevelData.EventHistory.AddRange(selectedEvents.Values.SelectMany(v => v).Select(e => e.Prefab).Where(e => !level.LevelData.EventHistory.Contains(e)));
|
||||
if (level.LevelData.EventHistory.Count > MaxEventHistory)
|
||||
{
|
||||
@@ -362,20 +393,24 @@ namespace Barotrauma
|
||||
}
|
||||
else if (eventSet.PerWreck)
|
||||
{
|
||||
var wrecks = Submarine.Loaded.Where(s => s.Info.IsWreck && (s.WreckAI == null || !s.WreckAI.IsAlive));
|
||||
var wrecks = Submarine.Loaded.Where(s => s.Info.IsWreck && (s.WreckAI == null || !s.WreckAI.IsAlive));
|
||||
applyCount = wrecks.Count();
|
||||
foreach (var wreck in wrecks)
|
||||
{
|
||||
spawnPosFilter.Add((Level.InterestingPosition pos) => { return pos.Submarine == wreck; });
|
||||
}
|
||||
}
|
||||
|
||||
var suitablePrefabs = eventSet.EventPrefabs.FindAll(e =>
|
||||
string.IsNullOrEmpty(e.First.BiomeIdentifier) ||
|
||||
e.First.BiomeIdentifier.Equals(Level.Loaded.LevelData?.Biome?.Identifier, StringComparison.OrdinalIgnoreCase));
|
||||
for (int i = 0; i < applyCount; i++)
|
||||
{
|
||||
if (eventSet.ChooseRandom)
|
||||
{
|
||||
if (eventSet.EventPrefabs.Count > 0)
|
||||
if (suitablePrefabs.Count > 0)
|
||||
{
|
||||
List<Pair<EventPrefab, float>> unusedEvents = new List<Pair<EventPrefab, float>>(eventSet.EventPrefabs);
|
||||
List<Pair<EventPrefab, float>> unusedEvents = new List<Pair<EventPrefab, float>>(suitablePrefabs);
|
||||
for (int j = 0; j < eventSet.EventCount; j++)
|
||||
{
|
||||
var eventPrefab = ToolBox.SelectWeightedRandom(unusedEvents, unusedEvents.Select(e => CalculateCommonness(e)).ToList(), rand);
|
||||
@@ -403,7 +438,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (Pair<EventPrefab, float> eventPrefab in eventSet.EventPrefabs)
|
||||
foreach (Pair<EventPrefab, float> eventPrefab in suitablePrefabs)
|
||||
{
|
||||
var newEvent = eventPrefab.First.CreateInstance();
|
||||
if (newEvent == null) { continue; }
|
||||
@@ -430,12 +465,19 @@ namespace Barotrauma
|
||||
MTRandom rand = new MTRandom(ToolBox.StringToInt(level.Seed));
|
||||
|
||||
var allowedEventSets =
|
||||
eventSets.Where(es => level.Difficulty >= es.MinLevelDifficulty && level.Difficulty <= es.MaxLevelDifficulty && level.LevelData.Type == es.LevelType);
|
||||
eventSets.Where(es =>
|
||||
level.Difficulty >= es.MinLevelDifficulty && level.Difficulty <= es.MaxLevelDifficulty &&
|
||||
level.LevelData.Type == es.LevelType &&
|
||||
(string.IsNullOrEmpty(es.BiomeIdentifier) || es.BiomeIdentifier.Equals(level.LevelData.Biome.Identifier, StringComparison.OrdinalIgnoreCase)));
|
||||
|
||||
LocationType locationType = (GameMain.GameSession?.GameMode as CampaignMode)?.Map?.CurrentLocation?.Type ?? level?.StartLocation?.Type;
|
||||
if (locationType != null)
|
||||
Location location = (GameMain.GameSession?.GameMode as CampaignMode)?.Map?.CurrentLocation ?? level?.StartLocation;
|
||||
LocationType locationType = location?.GetLocationType();
|
||||
|
||||
if (location != null)
|
||||
{
|
||||
allowedEventSets = allowedEventSets.Where(set => set.LocationTypeIdentifiers == null || set.LocationTypeIdentifiers.Any(identifier => string.Equals(identifier, locationType.Identifier, StringComparison.OrdinalIgnoreCase)));
|
||||
allowedEventSets = allowedEventSets.Where(set =>
|
||||
set.LocationTypeIdentifiers == null ||
|
||||
set.LocationTypeIdentifiers.Any(identifier => string.Equals(identifier, locationType.Identifier, StringComparison.OrdinalIgnoreCase)));
|
||||
}
|
||||
|
||||
float totalCommonness = allowedEventSets.Sum(e => e.GetCommonness(level));
|
||||
|
||||
@@ -12,6 +12,8 @@ namespace Barotrauma
|
||||
public readonly bool TriggerEventCooldown;
|
||||
public float Commonness;
|
||||
public string Identifier;
|
||||
public bool UnlockPathEvent;
|
||||
public string BiomeIdentifier;
|
||||
|
||||
public EventPrefab(XElement element)
|
||||
{
|
||||
@@ -34,6 +36,8 @@ namespace Barotrauma
|
||||
Commonness = element.GetAttributeFloat("commonness", 1.0f);
|
||||
SpawnProbability = Math.Clamp(element.GetAttributeFloat("spawnprobability", 1.0f), 0, 1);
|
||||
TriggerEventCooldown = element.GetAttributeBool("triggereventcooldown", true);
|
||||
UnlockPathEvent = element.GetAttributeBool("unlockpathevent", false);
|
||||
BiomeIdentifier = ConfigElement.GetAttributeString("biome", string.Empty);
|
||||
}
|
||||
|
||||
public Event CreateInstance()
|
||||
|
||||
@@ -65,6 +65,8 @@ namespace Barotrauma
|
||||
//0-100
|
||||
public readonly float MinLevelDifficulty, MaxLevelDifficulty;
|
||||
|
||||
public readonly string BiomeIdentifier;
|
||||
|
||||
public readonly LevelData.LevelType LevelType;
|
||||
|
||||
public readonly string[] LocationTypeIdentifiers;
|
||||
@@ -111,6 +113,7 @@ namespace Barotrauma
|
||||
EventPrefabs = new List<Pair<EventPrefab, float>>();
|
||||
ChildSets = new List<EventSet>();
|
||||
|
||||
BiomeIdentifier = element.GetAttributeString("biome", string.Empty);
|
||||
MinLevelDifficulty = element.GetAttributeFloat("minleveldifficulty", 0);
|
||||
MaxLevelDifficulty = Math.Max(element.GetAttributeFloat("maxleveldifficulty", 100), MinLevelDifficulty);
|
||||
|
||||
|
||||
+128
-40
@@ -1,5 +1,7 @@
|
||||
using Barotrauma.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
@@ -8,38 +10,33 @@ namespace Barotrauma
|
||||
{
|
||||
private readonly XElement characterConfig;
|
||||
|
||||
private readonly List<Character> characters = new List<Character>();
|
||||
protected readonly List<Character> characters = new List<Character>();
|
||||
private readonly Dictionary<Character, List<Item>> characterItems = new Dictionary<Character, List<Item>>();
|
||||
protected readonly HashSet<Character> requireKill = new HashSet<Character>();
|
||||
protected readonly HashSet<Character> requireRescue = new HashSet<Character>();
|
||||
|
||||
private readonly string itemTag;
|
||||
public override bool AllowRespawn => false;
|
||||
|
||||
private Item itemToDestroy;
|
||||
protected bool wasDocked;
|
||||
|
||||
public AbandonedOutpostMission(MissionPrefab prefab, Location[] locations) :
|
||||
base(prefab, locations)
|
||||
{
|
||||
characterConfig = prefab.ConfigElement.Element("Characters");
|
||||
|
||||
itemTag = prefab.ConfigElement.GetAttributeString("targetitem", "");
|
||||
if (string.IsNullOrEmpty(itemTag))
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in mission prefab \"{prefab.Identifier}\". Target item not defined.");
|
||||
}
|
||||
}
|
||||
|
||||
protected override void StartMissionSpecific(Level level)
|
||||
{
|
||||
itemToDestroy = null;
|
||||
itemToDestroy = Item.ItemList.Find(it => it.Submarine?.Info.Type != SubmarineType.Player && it.HasTag(itemTag));
|
||||
if (itemToDestroy == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in mission \"{Prefab.Identifier}\". Could not find an item with the tag \"{itemTag}\".");
|
||||
}
|
||||
|
||||
characters.Clear();
|
||||
characterItems.Clear();
|
||||
requireKill.Clear();
|
||||
requireRescue.Clear();
|
||||
if (!IsClient)
|
||||
{
|
||||
InitCharacters();
|
||||
}
|
||||
|
||||
wasDocked = Submarine.MainSub.DockedTo.Contains(Level.Loaded.StartOutpost);
|
||||
}
|
||||
|
||||
private void InitCharacters()
|
||||
@@ -57,48 +54,139 @@ namespace Barotrauma
|
||||
|
||||
foreach (XElement element in characterConfig.Elements())
|
||||
{
|
||||
string characterIdentifier = element.GetAttributeString("identifier", "");
|
||||
string characterFrom = element.GetAttributeString("from", "");
|
||||
HumanPrefab humanPrefab = NPCSet.Get(characterFrom, characterIdentifier);
|
||||
if (humanPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't spawn character for abandoned outpost mission: character prefab \"" + characterIdentifier + "\" not found");
|
||||
return;
|
||||
}
|
||||
if (GameMain.NetworkMember == null && element.GetAttributeBool("multiplayeronly", false)) { continue; }
|
||||
|
||||
string[] moduleFlags = element.GetAttributeStringArray("moduleflags", null);
|
||||
string[] spawnPointTags = element.GetAttributeStringArray("spawnpointtags", null);
|
||||
ISpatialEntity spawnPos = SpawnAction.GetSpawnPos(
|
||||
SpawnAction.SpawnLocationType.Outpost, SpawnType.Human,
|
||||
moduleFlags ?? humanPrefab.GetModuleFlags(),
|
||||
spawnPointTags ?? humanPrefab.GetSpawnPointTags());
|
||||
if (spawnPos == null)
|
||||
int defaultCount = element.GetAttributeInt("count", -1);
|
||||
if (defaultCount < 0)
|
||||
{
|
||||
spawnPos = submarine.GetHulls(alsoFromConnectedSubs: false).GetRandom();
|
||||
defaultCount = element.GetAttributeInt("amount", 1);
|
||||
}
|
||||
int min = Math.Min(element.GetAttributeInt("min", defaultCount), 255);
|
||||
int max = Math.Min(Math.Max(min, element.GetAttributeInt("max", defaultCount)), 255);
|
||||
int count = Rand.Range(min, max + 1);
|
||||
|
||||
var characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: humanPrefab.GetJobPrefab(Rand.RandSync.Server), randSync: Rand.RandSync.Server);
|
||||
Character spawnedCharacter = Character.Create(characterInfo.SpeciesName, spawnPos.WorldPosition, ToolBox.RandomSeed(8), characterInfo, createNetworkEvent: false);
|
||||
if (element.Attribute("identifier") != null && element.Attribute("from") != null)
|
||||
{
|
||||
string characterIdentifier = element.GetAttributeString("identifier", "");
|
||||
string characterFrom = element.GetAttributeString("from", "");
|
||||
HumanPrefab humanPrefab = NPCSet.Get(characterFrom, characterIdentifier);
|
||||
if (humanPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't spawn a character for abandoned outpost mission: character prefab \"" + characterIdentifier + "\" not found");
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
LoadHuman(humanPrefab, element, submarine);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string speciesName = element.GetAttributeString("character", element.GetAttributeString("identifier", ""));
|
||||
var characterPrefab = CharacterPrefab.FindBySpeciesName(speciesName);
|
||||
if (characterPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't spawn a character for abandoned outpost mission: character prefab \"" + speciesName + "\" not found");
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
LoadMonster(characterPrefab, element, submarine);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadHuman(HumanPrefab humanPrefab, XElement element, Submarine submarine)
|
||||
{
|
||||
string[] moduleFlags = element.GetAttributeStringArray("moduleflags", null);
|
||||
string[] spawnPointTags = element.GetAttributeStringArray("spawnpointtags", null);
|
||||
ISpatialEntity spawnPos = SpawnAction.GetSpawnPos(
|
||||
SpawnAction.SpawnLocationType.Outpost, SpawnType.Human,
|
||||
moduleFlags ?? humanPrefab.GetModuleFlags(),
|
||||
spawnPointTags ?? humanPrefab.GetSpawnPointTags(),
|
||||
element.GetAttributeBool("asfaraspossible", false));
|
||||
if (spawnPos == null)
|
||||
{
|
||||
spawnPos = submarine.GetHulls(alsoFromConnectedSubs: false).GetRandom();
|
||||
}
|
||||
|
||||
var characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: humanPrefab.GetJobPrefab(Rand.RandSync.Server), randSync: Rand.RandSync.Server);
|
||||
Character spawnedCharacter = Character.Create(characterInfo.SpeciesName, spawnPos.WorldPosition, ToolBox.RandomSeed(8), characterInfo, createNetworkEvent: false);
|
||||
if (element.GetAttributeBool("requirerescue", false))
|
||||
{
|
||||
requireRescue.Add(spawnedCharacter);
|
||||
spawnedCharacter.TeamID = CharacterTeamType.FriendlyNPC;
|
||||
#if CLIENT
|
||||
GameMain.GameSession.CrewManager.AddCharacterToCrewList(spawnedCharacter);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
spawnedCharacter.TeamID = CharacterTeamType.None;
|
||||
humanPrefab.InitializeCharacter(spawnedCharacter, spawnPos);
|
||||
humanPrefab.GiveItems(spawnedCharacter, Submarine.MainSub, Rand.RandSync.Server, createNetworkEvents: false);
|
||||
}
|
||||
humanPrefab.InitializeCharacter(spawnedCharacter, spawnPos);
|
||||
humanPrefab.GiveItems(spawnedCharacter, Submarine.MainSub, Rand.RandSync.Server, createNetworkEvents: false);
|
||||
if (element.GetAttributeBool("requirekill", false))
|
||||
{
|
||||
requireKill.Add(spawnedCharacter);
|
||||
}
|
||||
characters.Add(spawnedCharacter);
|
||||
characterItems.Add(spawnedCharacter, spawnedCharacter.Inventory.FindAllItems(recursive: true));
|
||||
}
|
||||
|
||||
characters.Add(spawnedCharacter);
|
||||
private void LoadMonster(CharacterPrefab monsterPrefab, XElement element, Submarine submarine)
|
||||
{
|
||||
string[] moduleFlags = element.GetAttributeStringArray("moduleflags", null);
|
||||
string[] spawnPointTags = element.GetAttributeStringArray("spawnpointtags", null);
|
||||
ISpatialEntity spawnPos = SpawnAction.GetSpawnPos(SpawnAction.SpawnLocationType.Outpost, SpawnType.Enemy, moduleFlags, spawnPointTags, element.GetAttributeBool("asfaraspossible", false));
|
||||
if (spawnPos == null)
|
||||
{
|
||||
spawnPos = submarine.GetHulls(alsoFromConnectedSubs: false).GetRandom();
|
||||
}
|
||||
Character spawnedCharacter = Character.Create(monsterPrefab.Identifier, spawnPos.WorldPosition, ToolBox.RandomSeed(8), createNetworkEvent: false);
|
||||
characters.Add(spawnedCharacter);
|
||||
if (element.GetAttributeBool("requirekill", false))
|
||||
{
|
||||
requireKill.Add(spawnedCharacter);
|
||||
}
|
||||
if (spawnedCharacter.Inventory != null)
|
||||
{
|
||||
characterItems.Add(spawnedCharacter, spawnedCharacter.Inventory.FindAllItems(recursive: true));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (State == 0 && itemToDestroy != null && itemToDestroy.Condition <= 0.0f)
|
||||
switch (state)
|
||||
{
|
||||
State = 1;
|
||||
case 0:
|
||||
if (requireKill.All(c => c.Removed || c.IsDead) &&
|
||||
requireRescue.All(c => c.Submarine?.Info.Type == SubmarineType.Player))
|
||||
{
|
||||
State = 1;
|
||||
}
|
||||
break;
|
||||
#if SERVER
|
||||
case 1:
|
||||
if (!(GameMain.GameSession.GameMode is CampaignMode) && GameMain.Server != null)
|
||||
{
|
||||
if (!Submarine.MainSub.AtStartPosition || (wasDocked && !Submarine.MainSub.DockedTo.Contains(Level.Loaded.StartOutpost)))
|
||||
{
|
||||
GameMain.Server.EndGame();
|
||||
State = 2;
|
||||
}
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public override void End()
|
||||
{
|
||||
completed = itemToDestroy == null || itemToDestroy.Condition <= 0.0f;
|
||||
completed = State > 0;
|
||||
if (completed)
|
||||
{
|
||||
if (Prefab.LocationTypeChangeOnCompleted != null)
|
||||
|
||||
@@ -43,21 +43,21 @@ namespace Barotrauma
|
||||
public virtual string SuccessMessage
|
||||
{
|
||||
get { return successMessage; }
|
||||
private set { successMessage = value; }
|
||||
//private set { successMessage = value; }
|
||||
}
|
||||
|
||||
private string failureMessage;
|
||||
public virtual string FailureMessage
|
||||
{
|
||||
get { return failureMessage; }
|
||||
private set { failureMessage = value; }
|
||||
//private set { failureMessage = value; }
|
||||
}
|
||||
|
||||
protected string description;
|
||||
public virtual string Description
|
||||
{
|
||||
get { return description; }
|
||||
private set { description = value; }
|
||||
//private set { description = value; }
|
||||
}
|
||||
|
||||
public int Reward
|
||||
@@ -110,7 +110,7 @@ namespace Barotrauma
|
||||
|
||||
description = prefab.Description;
|
||||
successMessage = prefab.SuccessMessage;
|
||||
FailureMessage = prefab.FailureMessage;
|
||||
failureMessage = prefab.FailureMessage;
|
||||
Headers = new List<string>(prefab.Headers);
|
||||
Messages = new List<string>(prefab.Messages);
|
||||
|
||||
@@ -118,12 +118,13 @@ namespace Barotrauma
|
||||
|
||||
for (int n = 0; n < 2; n++)
|
||||
{
|
||||
if (description != null) description = description.Replace("[location" + (n + 1) + "]", locations[n].Name);
|
||||
if (successMessage != null) successMessage = successMessage.Replace("[location" + (n + 1) + "]", locations[n].Name);
|
||||
if (failureMessage != null) failureMessage = failureMessage.Replace("[location" + (n + 1) + "]", locations[n].Name);
|
||||
string locationName = $"‖color:gui.orange‖{locations[n].Name}‖end‖";
|
||||
if (description != null) description = description.Replace("[location" + (n + 1) + "]", locationName);
|
||||
if (successMessage != null) successMessage = successMessage.Replace("[location" + (n + 1) + "]", locationName);
|
||||
if (failureMessage != null) failureMessage = failureMessage.Replace("[location" + (n + 1) + "]", locationName);
|
||||
for (int m = 0; m < Messages.Count; m++)
|
||||
{
|
||||
Messages[m] = Messages[m].Replace("[location" + (n + 1) + "]", locations[n].Name);
|
||||
Messages[m] = Messages[m].Replace("[location" + (n + 1) + "]", locationName);
|
||||
}
|
||||
}
|
||||
if (description != null) description = description.Replace("[reward]", Reward.ToString("N0"));
|
||||
@@ -180,7 +181,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (string categoryToShow in Prefab.UnhideEntitySubCategories)
|
||||
{
|
||||
foreach (MapEntity entityToShow in MapEntity.mapEntityList.Where(me => me.prefab.HasSubCategory(categoryToShow)))
|
||||
foreach (MapEntity entityToShow in MapEntity.mapEntityList.Where(me => me.prefab?.HasSubCategory(categoryToShow) ?? false))
|
||||
{
|
||||
entityToShow.HiddenInGame = false;
|
||||
}
|
||||
|
||||
@@ -18,9 +18,10 @@ namespace Barotrauma
|
||||
Nest = 0x10,
|
||||
Mineral = 0x20,
|
||||
Combat = 0x40,
|
||||
AbandonedOutpost = 0x80,
|
||||
OutpostDestroy = 0x80,
|
||||
OutpostRescue = 0x100,
|
||||
|
||||
All = Salvage | Monster | Cargo | Beacon | Nest | Mineral | AbandonedOutpost
|
||||
All = Salvage | Monster | Cargo | Beacon | Nest | Mineral | Combat | OutpostDestroy | OutpostRescue
|
||||
}
|
||||
|
||||
partial class MissionPrefab
|
||||
@@ -35,7 +36,8 @@ namespace Barotrauma
|
||||
{ MissionType.Beacon, typeof(BeaconMission) },
|
||||
{ MissionType.Nest, typeof(NestMission) },
|
||||
{ MissionType.Mineral, typeof(MineralMission) },
|
||||
{ MissionType.AbandonedOutpost, typeof(AbandonedOutpostMission) },
|
||||
{ MissionType.OutpostDestroy, typeof(OutpostDestroyMission) },
|
||||
{ MissionType.OutpostRescue, typeof(AbandonedOutpostMission) },
|
||||
};
|
||||
public static readonly Dictionary<MissionType, Type> PvPMissionClasses = new Dictionary<MissionType, Type>()
|
||||
{
|
||||
@@ -166,7 +168,10 @@ namespace Barotrauma
|
||||
FailureMessage = element.GetAttributeString("failuremessage", "");
|
||||
}
|
||||
|
||||
SonarLabel = TextManager.Get("MissionSonarLabel." + TextIdentifier, true) ?? element.GetAttributeString("sonarlabel", "");
|
||||
SonarLabel =
|
||||
TextManager.Get("MissionSonarLabel." + TextIdentifier, true) ??
|
||||
TextManager.Get("MissionSonarLabel." + element.GetAttributeString("sonarlabel", ""), true) ??
|
||||
element.GetAttributeString("sonarlabel", "");
|
||||
SonarIconIdentifier = element.GetAttributeString("sonaricon", "");
|
||||
|
||||
MultiplayerOnly = element.GetAttributeBool("multiplayeronly", false);
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Barotrauma
|
||||
partial class MonsterMission : Mission
|
||||
{
|
||||
//string = filename, point = min,max
|
||||
private readonly HashSet<Tuple<CharacterPrefab, Point>> monsterPrefabs = new HashSet<Tuple<CharacterPrefab, Point>>();
|
||||
private readonly HashSet<(CharacterPrefab character, Point amountRange)> monsterPrefabs = new HashSet<(CharacterPrefab character, Point amountRange)>();
|
||||
private readonly List<Character> monsters = new List<Character>();
|
||||
private readonly List<Vector2> sonarPositions = new List<Vector2>();
|
||||
|
||||
@@ -43,7 +43,7 @@ namespace Barotrauma
|
||||
if (characterPrefab != null)
|
||||
{
|
||||
int monsterCount = Math.Min(prefab.ConfigElement.GetAttributeInt("monstercount", 1), 255);
|
||||
monsterPrefabs.Add(new Tuple<CharacterPrefab, Point>(characterPrefab, new Point(monsterCount)));
|
||||
monsterPrefabs.Add((characterPrefab, new Point(monsterCount)));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -73,7 +73,7 @@ namespace Barotrauma
|
||||
var characterPrefab = CharacterPrefab.FindBySpeciesName(speciesName);
|
||||
if (characterPrefab != null)
|
||||
{
|
||||
monsterPrefabs.Add(new Tuple<CharacterPrefab, Point>(characterPrefab, new Point(min, max)));
|
||||
monsterPrefabs.Add((characterPrefab, new Point(min, max)));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -83,7 +83,7 @@ namespace Barotrauma
|
||||
|
||||
if (monsterPrefabs.Any())
|
||||
{
|
||||
var characterParams = new CharacterParams(monsterPrefabs.First().Item1.FilePath);
|
||||
var characterParams = new CharacterParams(monsterPrefabs.First().character.FilePath);
|
||||
description = description.Replace("[monster]",
|
||||
TextManager.Get("character." + characterParams.SpeciesTranslationOverride, returnNull: true) ??
|
||||
TextManager.Get("character." + characterParams.SpeciesName));
|
||||
@@ -115,12 +115,12 @@ namespace Barotrauma
|
||||
if (!IsClient)
|
||||
{
|
||||
Level.Loaded.TryGetInterestingPosition(true, spawnPosType, Level.Loaded.Size.X * 0.3f, out Vector2 spawnPos);
|
||||
foreach (var monster in monsterPrefabs)
|
||||
foreach (var (character, amountRange) in monsterPrefabs)
|
||||
{
|
||||
int amount = Rand.Range(monster.Item2.X, monster.Item2.Y + 1);
|
||||
int amount = Rand.Range(amountRange.X, amountRange.Y + 1);
|
||||
for (int i = 0; i < amount; i++)
|
||||
{
|
||||
monsters.Add(Character.Create(monster.Item1.Identifier, spawnPos, ToolBox.RandomSeed(8), createNetworkEvent: false));
|
||||
monsters.Add(Character.Create(character.Identifier, spawnPos, ToolBox.RandomSeed(8), createNetworkEvent: false));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,12 +18,14 @@ namespace Barotrauma
|
||||
|
||||
private Vector2? spawnPos;
|
||||
|
||||
private readonly bool disallowed;
|
||||
private bool disallowed;
|
||||
|
||||
private readonly Level.PositionType spawnPosType;
|
||||
|
||||
private bool spawnPending;
|
||||
|
||||
private int maxAmountPerLevel = int.MaxValue;
|
||||
|
||||
public List<Character> Monsters => monsters;
|
||||
public Vector2? SpawnPos => spawnPos;
|
||||
public bool SpawnPending => spawnPending;
|
||||
@@ -70,6 +72,8 @@ namespace Barotrauma
|
||||
minAmount = prefab.ConfigElement.GetAttributeInt("minamount", defaultAmount);
|
||||
maxAmount = Math.Max(prefab.ConfigElement.GetAttributeInt("maxamount", 1), minAmount);
|
||||
|
||||
maxAmountPerLevel = prefab.ConfigElement.GetAttributeInt("maxamountperlevel", int.MaxValue);
|
||||
|
||||
var spawnPosTypeStr = prefab.ConfigElement.GetAttributeString("spawntype", "");
|
||||
if (string.IsNullOrWhiteSpace(spawnPosTypeStr) ||
|
||||
!Enum.TryParse(spawnPosTypeStr, true, out spawnPosType))
|
||||
@@ -345,6 +349,15 @@ namespace Barotrauma
|
||||
|
||||
if (spawnPos == null)
|
||||
{
|
||||
if (maxAmountPerLevel < int.MaxValue)
|
||||
{
|
||||
if (Character.CharacterList.Count(c => c.SpeciesName == speciesName) >= maxAmountPerLevel)
|
||||
{
|
||||
disallowed = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
FindSpawnPosition(affectSubImmediately: true);
|
||||
//the event gets marked as finished if a spawn point is not found
|
||||
if (isFinished) { return; }
|
||||
@@ -400,7 +413,7 @@ namespace Barotrauma
|
||||
foreach (Submarine submarine in Submarine.Loaded)
|
||||
{
|
||||
if (submarine.Info.Type != SubmarineType.Player) { continue; }
|
||||
if (submarine.WorldPosition.Y > Level.Loaded.AbyssStart)
|
||||
if (submarine.WorldPosition.Y > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -242,6 +242,21 @@ namespace Barotrauma
|
||||
conversationTimer = IsSinglePlayer ? Rand.Range(5.0f, 10.0f) : Rand.Range(45.0f, 60.0f);
|
||||
}
|
||||
|
||||
public void RenameCharacter(CharacterInfo characterInfo, string newName)
|
||||
{
|
||||
int identifier = characterInfo.GetIdentifierUsingOriginalName();
|
||||
var match = characterInfos.FirstOrDefault(ci => ci.GetIdentifierUsingOriginalName() == identifier);
|
||||
if (match == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Tried to rename an invalid crew member ({identifier})");
|
||||
return;
|
||||
}
|
||||
match.Rename(newName);
|
||||
RenameCharacterProjSpecific(match);
|
||||
}
|
||||
|
||||
partial void RenameCharacterProjSpecific(CharacterInfo characterInfo);
|
||||
|
||||
public void FireCharacter(CharacterInfo characterInfo)
|
||||
{
|
||||
RemoveCharacterInfo(characterInfo);
|
||||
@@ -277,6 +292,7 @@ namespace Barotrauma
|
||||
|
||||
private void UpdateConversations(float deltaTime)
|
||||
{
|
||||
if (GameMain.GameSession?.GameMode?.Preset == GameModePreset.TestMode) { return; }
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.ServerSettings.DisableBotConversations) { return; }
|
||||
|
||||
conversationTimer -= deltaTime;
|
||||
@@ -305,19 +321,33 @@ namespace Barotrauma
|
||||
{
|
||||
List<Character> availableSpeakers = new List<Character>() { npc, player };
|
||||
List<string> dialogFlags = new List<string>() { "OutpostNPC", "EnterOutpost" };
|
||||
if (GameMain.GameSession?.GameMode is CampaignMode campaignMode && campaignMode.Map?.CurrentLocation?.Reputation != null)
|
||||
if (GameMain.GameSession?.GameMode is CampaignMode campaignMode)
|
||||
{
|
||||
float normalizedReputation = MathUtils.InverseLerp(
|
||||
campaignMode.Map.CurrentLocation.Reputation.MinReputation,
|
||||
campaignMode.Map.CurrentLocation.Reputation.MaxReputation,
|
||||
campaignMode.Map.CurrentLocation.Reputation.Value);
|
||||
if (normalizedReputation < 0.2f)
|
||||
if (campaignMode.Map?.CurrentLocation?.Type?.Identifier.Equals("abandoned", StringComparison.OrdinalIgnoreCase) ?? false)
|
||||
{
|
||||
dialogFlags.Add("LowReputation");
|
||||
if (npc.TeamID == CharacterTeamType.None)
|
||||
{
|
||||
dialogFlags.Add("Bandit");
|
||||
}
|
||||
else if (npc.TeamID == CharacterTeamType.FriendlyNPC)
|
||||
{
|
||||
dialogFlags.Add("Hostage");
|
||||
}
|
||||
}
|
||||
else if (normalizedReputation > 0.8f)
|
||||
else if (campaignMode.Map?.CurrentLocation?.Reputation != null)
|
||||
{
|
||||
dialogFlags.Add("HighReputation");
|
||||
float normalizedReputation = MathUtils.InverseLerp(
|
||||
campaignMode.Map.CurrentLocation.Reputation.MinReputation,
|
||||
campaignMode.Map.CurrentLocation.Reputation.MaxReputation,
|
||||
campaignMode.Map.CurrentLocation.Reputation.Value);
|
||||
if (normalizedReputation < 0.2f)
|
||||
{
|
||||
dialogFlags.Add("LowReputation");
|
||||
}
|
||||
else if (normalizedReputation > 0.8f)
|
||||
{
|
||||
dialogFlags.Add("HighReputation");
|
||||
}
|
||||
}
|
||||
}
|
||||
pendingConversationLines.AddRange(NPCConversation.CreateRandom(availableSpeakers, dialogFlags));
|
||||
|
||||
@@ -5,9 +5,39 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
internal struct CampaignSettings
|
||||
{
|
||||
public static CampaignSettings Empty = new CampaignSettings();
|
||||
|
||||
// Anything that uses this field I wasn't sure if actually needed the proper campaign settings to be passed down
|
||||
public static CampaignSettings Unsure = Empty;
|
||||
public bool RadiationEnabled { get; set; }
|
||||
|
||||
public CampaignSettings(IReadMessage inc)
|
||||
{
|
||||
RadiationEnabled = inc.ReadBoolean();
|
||||
}
|
||||
|
||||
public CampaignSettings(XElement element)
|
||||
{
|
||||
RadiationEnabled = element.GetAttributeBool(nameof(RadiationEnabled).ToLower(), true);
|
||||
}
|
||||
|
||||
public void Serialize(IWriteMessage msg)
|
||||
{
|
||||
msg.Write(RadiationEnabled);
|
||||
}
|
||||
|
||||
public XElement Save()
|
||||
{
|
||||
return new XElement(nameof(CampaignSettings), new XAttribute(nameof(RadiationEnabled).ToLower(), RadiationEnabled));
|
||||
}
|
||||
}
|
||||
|
||||
abstract partial class CampaignMode : GameMode
|
||||
{
|
||||
const int MaxMoney = int.MaxValue / 2; //about 1 billion
|
||||
@@ -31,6 +61,8 @@ namespace Barotrauma
|
||||
|
||||
protected XElement petsElement;
|
||||
|
||||
public CampaignSettings Settings;
|
||||
|
||||
private List<Mission> extraMissions = new List<Mission>();
|
||||
|
||||
public enum TransitionType
|
||||
@@ -224,7 +256,7 @@ namespace Barotrauma
|
||||
|
||||
if (levelData.HasBeaconStation && !levelData.IsBeaconActive)
|
||||
{
|
||||
var beaconMissionPrefab = MissionPrefab.List.Find(m => m.Identifier.Equals("beaconnoreward", StringComparison.OrdinalIgnoreCase));
|
||||
var beaconMissionPrefab = MissionPrefab.List.Find(m => m.Tags.Any(t => t.Equals("beaconnoreward", StringComparison.OrdinalIgnoreCase)));
|
||||
if (beaconMissionPrefab != null && !Missions.Any(m => m.Prefab.Type == beaconMissionPrefab.Type))
|
||||
{
|
||||
extraMissions.Add(beaconMissionPrefab.Instantiate(Map.SelectedConnection.Locations));
|
||||
@@ -232,8 +264,12 @@ namespace Barotrauma
|
||||
}
|
||||
if (levelData.HasHuntingGrounds)
|
||||
{
|
||||
var huntingGroundsMissionPrefab = MissionPrefab.List.Find(m => m.Identifier.Equals("huntinggroundsnoreward", StringComparison.OrdinalIgnoreCase));
|
||||
if (huntingGroundsMissionPrefab != null && !Missions.Any(m => m.Prefab.Type == huntingGroundsMissionPrefab.Type))
|
||||
var huntingGroundsMissionPrefab = MissionPrefab.List.Find(m => m.Tags.Any(t => t.Equals("huntinggroundsnoreward", StringComparison.OrdinalIgnoreCase)));
|
||||
if (huntingGroundsMissionPrefab == null)
|
||||
{
|
||||
DebugConsole.AddWarning("Could not find a hunting grounds mission for the level. No mission with the tag \"huntinggroundsnoreward\" found.");
|
||||
}
|
||||
else if (!Missions.Any(m => m.Prefab.Type == huntingGroundsMissionPrefab.Type))
|
||||
{
|
||||
extraMissions.Add(huntingGroundsMissionPrefab.Instantiate(Map.SelectedConnection.Locations));
|
||||
}
|
||||
@@ -342,7 +378,7 @@ namespace Barotrauma
|
||||
nextLevel = map.StartLocation.LevelData;
|
||||
return TransitionType.End;
|
||||
}
|
||||
if (Level.Loaded.EndLocation != null && Level.Loaded.EndLocation.HasOutpost() && Level.Loaded.EndOutpost != null)
|
||||
if (Level.Loaded.EndLocation != null && Level.Loaded.EndLocation.Type.HasOutpost && Level.Loaded.EndOutpost != null)
|
||||
{
|
||||
nextLevel = Level.Loaded.EndLocation.LevelData;
|
||||
return TransitionType.ProgressToNextLocation;
|
||||
@@ -361,12 +397,12 @@ namespace Barotrauma
|
||||
}
|
||||
else if (leavingSub.AtStartPosition)
|
||||
{
|
||||
if (map.CurrentLocation.HasOutpost() && Level.Loaded.StartOutpost != null)
|
||||
if (map.CurrentLocation.Type.HasOutpost && Level.Loaded.StartOutpost != null)
|
||||
{
|
||||
nextLevel = map.CurrentLocation.LevelData;
|
||||
return TransitionType.ReturnToPreviousLocation;
|
||||
}
|
||||
else if (map.SelectedLocation != null && map.SelectedLocation != map.CurrentLocation && !map.CurrentLocation.HasOutpost() &&
|
||||
else if (map.SelectedLocation != null && map.SelectedLocation != map.CurrentLocation && !map.CurrentLocation.Type.HasOutpost &&
|
||||
map.SelectedConnection != null && Level.Loaded.LevelData != map.SelectedConnection.LevelData)
|
||||
{
|
||||
nextLevel = map.SelectedConnection.LevelData;
|
||||
@@ -584,14 +620,12 @@ namespace Barotrauma
|
||||
|
||||
public bool TryHireCharacter(Location location, CharacterInfo characterInfo)
|
||||
{
|
||||
if (characterInfo == null) { return false; }
|
||||
if (Money < characterInfo.Salary) { return false; }
|
||||
|
||||
characterInfo.IsNewHire = true;
|
||||
|
||||
location.RemoveHireableCharacter(characterInfo);
|
||||
CrewManager.AddCharacterInfo(characterInfo);
|
||||
Money -= characterInfo.Salary;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
+7
-3
@@ -59,13 +59,14 @@ namespace Barotrauma
|
||||
InitCampaignData();
|
||||
}
|
||||
|
||||
public static MultiPlayerCampaign StartNew(string mapSeed, SubmarineInfo selectedSub)
|
||||
public static MultiPlayerCampaign StartNew(string mapSeed, SubmarineInfo selectedSub, CampaignSettings settings)
|
||||
{
|
||||
MultiPlayerCampaign campaign = new MultiPlayerCampaign();
|
||||
//only the server generates the map, the clients load it from a save file
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
campaign.map = new Map(campaign, mapSeed);
|
||||
campaign.map = new Map(campaign, mapSeed, settings);
|
||||
campaign.Settings = settings;
|
||||
}
|
||||
campaign.InitProjSpecific();
|
||||
return campaign;
|
||||
@@ -128,11 +129,14 @@ namespace Barotrauma
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "campaignsettings":
|
||||
Settings = new CampaignSettings(subElement);
|
||||
break;
|
||||
case "map":
|
||||
if (map == null)
|
||||
{
|
||||
//map not created yet, loading this campaign for the first time
|
||||
map = Map.Load(this, subElement);
|
||||
map = Map.Load(this, subElement, Settings);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -103,12 +103,12 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Start a new GameSession. Will be saved to the specified save path (if playing a game mode that can be saved).
|
||||
/// </summary>
|
||||
public GameSession(SubmarineInfo submarineInfo, string savePath, GameModePreset gameModePreset, string seed = null, MissionType missionType = MissionType.None)
|
||||
public GameSession(SubmarineInfo submarineInfo, string savePath, GameModePreset gameModePreset, CampaignSettings settings, string seed = null, MissionType missionType = MissionType.None)
|
||||
: this(submarineInfo)
|
||||
{
|
||||
this.SavePath = savePath;
|
||||
CrewManager = new CrewManager(gameModePreset != null && gameModePreset.IsSinglePlayer);
|
||||
GameMode = InstantiateGameMode(gameModePreset, seed, submarineInfo, missionType: missionType);
|
||||
GameMode = InstantiateGameMode(gameModePreset, seed, submarineInfo, settings, missionType: missionType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -118,14 +118,13 @@ namespace Barotrauma
|
||||
: this(submarineInfo)
|
||||
{
|
||||
CrewManager = new CrewManager(gameModePreset != null && gameModePreset.IsSinglePlayer);
|
||||
GameMode = InstantiateGameMode(gameModePreset, seed, submarineInfo, missionPrefabs: missionPrefabs);
|
||||
GameMode = InstantiateGameMode(gameModePreset, seed, submarineInfo, CampaignSettings.Empty, missionPrefabs: missionPrefabs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load a game session from the specified XML document. The session will be saved to the specified path.
|
||||
/// </summary>
|
||||
public GameSession(SubmarineInfo submarineInfo, List<SubmarineInfo> ownedSubmarines, XDocument doc, string saveFile)
|
||||
: this(submarineInfo, ownedSubmarines)
|
||||
public GameSession(SubmarineInfo submarineInfo, List<SubmarineInfo> ownedSubmarines, XDocument doc, string saveFile) : this(submarineInfo, ownedSubmarines)
|
||||
{
|
||||
this.SavePath = saveFile;
|
||||
GameMain.GameSession = this;
|
||||
@@ -159,7 +158,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private GameMode InstantiateGameMode(GameModePreset gameModePreset, string seed, SubmarineInfo selectedSub, IEnumerable<MissionPrefab> missionPrefabs = null, MissionType missionType = MissionType.None)
|
||||
private GameMode InstantiateGameMode(GameModePreset gameModePreset, string seed, SubmarineInfo selectedSub, CampaignSettings settings, IEnumerable<MissionPrefab> missionPrefabs = null, MissionType missionType = MissionType.None)
|
||||
{
|
||||
if (gameModePreset.GameModeType == typeof(CoOpMode))
|
||||
{
|
||||
@@ -175,7 +174,7 @@ namespace Barotrauma
|
||||
}
|
||||
else if (gameModePreset.GameModeType == typeof(MultiPlayerCampaign))
|
||||
{
|
||||
var campaign = MultiPlayerCampaign.StartNew(seed ?? ToolBox.RandomSeed(8), selectedSub);
|
||||
var campaign = MultiPlayerCampaign.StartNew(seed ?? ToolBox.RandomSeed(8), selectedSub, settings);
|
||||
if (campaign != null && selectedSub != null)
|
||||
{
|
||||
campaign.Money = Math.Max(MultiPlayerCampaign.MinimumInitialMoney, campaign.Money - selectedSub.Price);
|
||||
@@ -185,7 +184,7 @@ namespace Barotrauma
|
||||
#if CLIENT
|
||||
else if (gameModePreset.GameModeType == typeof(SinglePlayerCampaign))
|
||||
{
|
||||
var campaign = SinglePlayerCampaign.StartNew(seed ?? ToolBox.RandomSeed(8), selectedSub);
|
||||
var campaign = SinglePlayerCampaign.StartNew(seed ?? ToolBox.RandomSeed(8), selectedSub, settings);
|
||||
if (campaign != null && selectedSub != null)
|
||||
{
|
||||
campaign.Money = Math.Max(SinglePlayerCampaign.MinimumInitialMoney, campaign.Money - selectedSub.Price);
|
||||
@@ -400,6 +399,8 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
GUI.PreventPauseMenuToggle = false;
|
||||
|
||||
HintManager.OnRoundStarted();
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -467,7 +468,7 @@ namespace Barotrauma
|
||||
{
|
||||
mpCampaign.CargoManager.CreatePurchasedItems();
|
||||
#if SERVER
|
||||
mpCampaign.SendCrewState(false, null);
|
||||
mpCampaign.SendCrewState(null, default, null);
|
||||
#endif
|
||||
}
|
||||
mpCampaign.UpgradeManager.ApplyUpgrades();
|
||||
@@ -544,7 +545,7 @@ namespace Barotrauma
|
||||
{
|
||||
Submarine.SetPosition(spawnPos);
|
||||
myPort.Dock(outPostPort);
|
||||
myPort.Lock(true, forcePosition: true, applyEffects: false);
|
||||
myPort.Lock(isNetworkMessage: true, applyEffects: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -583,9 +584,10 @@ namespace Barotrauma
|
||||
{
|
||||
EventManager?.Update(deltaTime);
|
||||
GameMode?.Update(deltaTime);
|
||||
foreach (Mission mission in missions)
|
||||
//backwards for loop because the missions may get completed and removed from the list in Update()
|
||||
for (int i = missions.Count - 1; i >= 0; i--)
|
||||
{
|
||||
mission.Update(deltaTime);
|
||||
missions[i].Update(deltaTime);
|
||||
}
|
||||
UpdateProjSpecific(deltaTime);
|
||||
}
|
||||
@@ -636,6 +638,10 @@ namespace Barotrauma
|
||||
StatusEffect.StopAll();
|
||||
missions.Clear();
|
||||
IsRunning = false;
|
||||
|
||||
#if CLIENT
|
||||
HintManager.OnRoundEnded();
|
||||
#endif
|
||||
}
|
||||
|
||||
public void KillCharacter(Character character)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -39,5 +39,12 @@ namespace Barotrauma
|
||||
AvailableCharacters.ForEach(c => c.Remove());
|
||||
AvailableCharacters.Clear();
|
||||
}
|
||||
|
||||
public void RenameCharacter(CharacterInfo characterInfo, string newName)
|
||||
{
|
||||
if (characterInfo == null || string.IsNullOrEmpty(newName)) { return; }
|
||||
AvailableCharacters.FirstOrDefault(ci => ci == characterInfo)?.Rename(newName);
|
||||
PendingHires.FirstOrDefault(ci => ci == characterInfo)?.Rename(newName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,7 +171,7 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// How many corpses there can be in a sub before they start to get despawned
|
||||
/// </summary>
|
||||
public int CorpsesPerSubDespawnThreshold { get; set; } = 5;
|
||||
public int CorpsesPerSubDespawnThreshold { get; set; } = 10;
|
||||
|
||||
private string overrideSaveFolder, overrideMultiplayerSaveFolder;
|
||||
|
||||
@@ -301,10 +301,14 @@ namespace Barotrauma
|
||||
|
||||
public volatile bool WaitingForAutoUpdate;
|
||||
|
||||
public bool DisableInGameHints { get; set; }
|
||||
|
||||
#if DEBUG
|
||||
public bool AutomaticQuickStartEnabled { get; set; }
|
||||
public bool AutomaticCampaignLoadEnabled { get; set; }
|
||||
public bool TextManagerDebugModeEnabled { get; set; }
|
||||
|
||||
public bool ModBreakerMode { get; set; }
|
||||
#endif
|
||||
|
||||
private System.IO.FileSystemWatcher modsFolderWatcher;
|
||||
@@ -735,6 +739,10 @@ namespace Barotrauma
|
||||
private bool textScaleDirty;
|
||||
|
||||
public List<string> CompletedTutorialNames { get; private set; }
|
||||
/// <summary>
|
||||
/// Identifiers of hints the player has chosen not to see again
|
||||
/// </summary>
|
||||
public HashSet<string> IgnoredHints { get; private set; } = new HashSet<string>();
|
||||
public HashSet<string> EncounteredCreatures { get; private set; } = new HashSet<string>();
|
||||
public HashSet<string> KilledCreatures { get; private set; } = new HashSet<string>();
|
||||
|
||||
@@ -1151,6 +1159,12 @@ namespace Barotrauma
|
||||
CompletedTutorialNames.Add(element.GetAttributeString("name", ""));
|
||||
}
|
||||
}
|
||||
|
||||
if (doc.Root.Element("ignoredhints") is XElement ignoredHintsElement)
|
||||
{
|
||||
IgnoredHints = new HashSet<string>(ignoredHintsElement.GetAttributeStringArray("identifiers", new string[0], convertToLowerInvariant: true));
|
||||
}
|
||||
|
||||
XElement encounters = doc.Root.Element("encountered");
|
||||
if (encounters != null)
|
||||
{
|
||||
@@ -1172,7 +1186,7 @@ namespace Barotrauma
|
||||
#endregion
|
||||
|
||||
#region Save PlayerConfig
|
||||
public void SaveNewPlayerConfig()
|
||||
public bool SaveNewPlayerConfig()
|
||||
{
|
||||
XDocument doc = new XDocument();
|
||||
UnsavedSettings = false;
|
||||
@@ -1211,11 +1225,13 @@ namespace Barotrauma
|
||||
new XAttribute("tutorialskipwarning", ShowTutorialSkipWarning),
|
||||
new XAttribute("corpsedespawndelay", CorpseDespawnDelay),
|
||||
new XAttribute("corpsespersubdespawnthreshold", CorpsesPerSubDespawnThreshold),
|
||||
new XAttribute("usedualmodesockets", UseDualModeSockets)
|
||||
new XAttribute("usedualmodesockets", UseDualModeSockets),
|
||||
new XAttribute("disableingamehints", DisableInGameHints)
|
||||
#if DEBUG
|
||||
, new XAttribute("automaticquickstartenabled", AutomaticQuickStartEnabled)
|
||||
, new XAttribute("automaticcampaignloadenabled", AutomaticCampaignLoadEnabled)
|
||||
, new XAttribute("textmanagerdebugmodeenabled", TextManagerDebugModeEnabled)
|
||||
, new XAttribute("modbreakermode", ModBreakerMode)
|
||||
#endif
|
||||
);
|
||||
|
||||
@@ -1403,6 +1419,8 @@ namespace Barotrauma
|
||||
}
|
||||
doc.Root.Add(tutorialElement);
|
||||
|
||||
doc.Root.Add(new XElement("ignoredhints", new XAttribute("identifiers", string.Join(",", IgnoredHints).Trim().ToLowerInvariant())));
|
||||
|
||||
doc.Root.Add(new XElement("encountered", new XAttribute("creatures", string.Join(",", EncounteredCreatures).Trim().ToLowerInvariant())));
|
||||
doc.Root.Add(new XElement("killed", new XAttribute("creatures", string.Join(",", KilledCreatures).Trim().ToLowerInvariant())));
|
||||
|
||||
@@ -1426,7 +1444,10 @@ namespace Barotrauma
|
||||
DebugConsole.ThrowError("Saving game settings failed.", e);
|
||||
GameAnalyticsManager.AddErrorEventOnce("GameSettings.Save:SaveFailed", GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
|
||||
"Saving game settings failed.\n" + e.Message + "\n" + e.StackTrace.CleanupStackTrace());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -1460,10 +1481,12 @@ namespace Barotrauma
|
||||
EditorDisclaimerShown = doc.Root.GetAttributeBool("editordisclaimershown", EditorDisclaimerShown);
|
||||
ShowTutorialSkipWarning = doc.Root.GetAttributeBool("tutorialskipwarning", true);
|
||||
UseDualModeSockets = doc.Root.GetAttributeBool("usedualmodesockets", true);
|
||||
DisableInGameHints = doc.Root.GetAttributeBool("disableingamehints", DisableInGameHints);
|
||||
#if DEBUG
|
||||
AutomaticQuickStartEnabled = doc.Root.GetAttributeBool("automaticquickstartenabled", AutomaticQuickStartEnabled);
|
||||
AutomaticCampaignLoadEnabled = doc.Root.GetAttributeBool("automaticcampaignloadenabled", AutomaticCampaignLoadEnabled);
|
||||
TextManagerDebugModeEnabled = doc.Root.GetAttributeBool("textmanagerdebugmodeenabled", TextManagerDebugModeEnabled);
|
||||
ModBreakerMode = doc.Root.GetAttributeBool("modbreakermode", ModBreakerMode);
|
||||
#endif
|
||||
XElement gameplayElement = doc.Root.Element("gameplay");
|
||||
jobPreferences = new List<Pair<string, int>>();
|
||||
@@ -1579,6 +1602,32 @@ namespace Barotrauma
|
||||
CurrentCorePackage = null;
|
||||
enabledRegularPackages.Clear();
|
||||
|
||||
#if DEBUG && CLIENT
|
||||
if (ModBreakerMode)
|
||||
{
|
||||
CurrentCorePackage = ContentPackage.CorePackages.GetRandom();
|
||||
foreach (var regularPackage in ContentPackage.RegularPackages)
|
||||
{
|
||||
if (Rand.Range(0.0, 1.0) <= 0.5)
|
||||
{
|
||||
enabledRegularPackages.Add(regularPackage);
|
||||
}
|
||||
}
|
||||
ContentPackage.SortContentPackages(p =>
|
||||
{
|
||||
return Rand.Int(int.MaxValue);
|
||||
}, config: this);
|
||||
|
||||
if (CurrentCorePackage == null)
|
||||
{
|
||||
CurrentCorePackage = ContentPackage.CorePackages.First();
|
||||
}
|
||||
|
||||
TextManager.LoadTextPacks(AllEnabledPackages);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
var contentPackagesElement = doc.Root.Element("contentpackages");
|
||||
if (contentPackagesElement != null)
|
||||
{
|
||||
@@ -1725,6 +1774,7 @@ namespace Barotrauma
|
||||
AutoUpdateWorkshopItems = true;
|
||||
TextScale = 1;
|
||||
textScaleDirty = false;
|
||||
DisableInGameHints = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@ namespace Barotrauma.Items.Components
|
||||
var prevDockingTarget = DockingTarget;
|
||||
Undock(applyEffects: false);
|
||||
Dock(prevDockingTarget);
|
||||
Lock(true, applyEffects: false);
|
||||
Lock(isNetworkMessage: true, applyEffects: false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,7 +240,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
|
||||
public void Lock(bool isNetworkMessage, bool forcePosition = false, bool applyEffects = true)
|
||||
public void Lock(bool isNetworkMessage, bool applyEffects = true)
|
||||
{
|
||||
#if CLIENT
|
||||
if (GameMain.Client != null && !isNetworkMessage) { return; }
|
||||
@@ -262,20 +262,17 @@ namespace Barotrauma.Items.Components
|
||||
ApplyStatusEffects(ActionType.OnUse, 1.0f);
|
||||
}
|
||||
|
||||
if (forcePosition)
|
||||
Vector2 jointDiff = joint.WorldAnchorB - joint.WorldAnchorA;
|
||||
if (item.Submarine.PhysicsBody.Mass < DockingTarget.item.Submarine.PhysicsBody.Mass ||
|
||||
DockingTarget.item.Submarine.Info.IsOutpost)
|
||||
{
|
||||
Vector2 jointDiff = joint.WorldAnchorB - joint.WorldAnchorA;
|
||||
if (item.Submarine.PhysicsBody.Mass < DockingTarget.item.Submarine.PhysicsBody.Mass ||
|
||||
DockingTarget.item.Submarine.Info.IsOutpost)
|
||||
{
|
||||
item.Submarine.SubBody.SetPosition(item.Submarine.SubBody.Position + ConvertUnits.ToDisplayUnits(jointDiff));
|
||||
}
|
||||
else if (DockingTarget.item.Submarine.PhysicsBody.Mass < item.Submarine.PhysicsBody.Mass ||
|
||||
item.Submarine.Info.IsOutpost)
|
||||
{
|
||||
DockingTarget.item.Submarine.SubBody.SetPosition(DockingTarget.item.Submarine.SubBody.Position - ConvertUnits.ToDisplayUnits(jointDiff));
|
||||
}
|
||||
item.Submarine.SubBody.SetPosition(item.Submarine.SubBody.Position + ConvertUnits.ToDisplayUnits(jointDiff));
|
||||
}
|
||||
else if (DockingTarget.item.Submarine.PhysicsBody.Mass < item.Submarine.PhysicsBody.Mass ||
|
||||
item.Submarine.Info.IsOutpost)
|
||||
{
|
||||
DockingTarget.item.Submarine.SubBody.SetPosition(DockingTarget.item.Submarine.SubBody.Position - ConvertUnits.ToDisplayUnits(jointDiff));
|
||||
}
|
||||
|
||||
ConnectWireBetweenPorts();
|
||||
CreateJoint(true);
|
||||
@@ -988,7 +985,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else
|
||||
{
|
||||
Lock(isNetworkMessage: false, forcePosition: true);
|
||||
Lock(isNetworkMessage: false);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -305,9 +305,21 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private void ToggleState(ActionType actionType, Character user)
|
||||
{
|
||||
if (toggleCooldownTimer > 0.0f && user != lastUser) { OnFailedToOpen(); return; }
|
||||
if (toggleCooldownTimer > 0.0f && user != lastUser)
|
||||
{
|
||||
OnFailedToOpen();
|
||||
return;
|
||||
}
|
||||
toggleCooldownTimer = ToggleCoolDown;
|
||||
if (IsStuck || IsJammed) { toggleCooldownTimer = 1.0f; OnFailedToOpen(); return; }
|
||||
if (IsStuck || IsJammed)
|
||||
{
|
||||
#if CLIENT
|
||||
if (IsStuck) { HintManager.OnTryOpenStuckDoor(user); }
|
||||
#endif
|
||||
toggleCooldownTimer = 1.0f;
|
||||
OnFailedToOpen();
|
||||
return;
|
||||
}
|
||||
lastUser = user;
|
||||
SetState(PredictedState == null ? !isOpen : !PredictedState.Value, false, true, forcedOpen: actionType == ActionType.OnPicked);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,8 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class RangedWeapon : ItemComponent
|
||||
{
|
||||
private float reload, reloadTimer;
|
||||
private float reload;
|
||||
public float ReloadTimer { get; private set; }
|
||||
|
||||
private Vector2 barrelPos;
|
||||
|
||||
@@ -75,17 +76,17 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void Equip(Character character)
|
||||
{
|
||||
reloadTimer = Math.Min(reload, 1.0f);
|
||||
ReloadTimer = Math.Min(reload, 1.0f);
|
||||
IsActive = true;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
reloadTimer -= deltaTime;
|
||||
ReloadTimer -= deltaTime;
|
||||
|
||||
if (reloadTimer < 0.0f)
|
||||
if (ReloadTimer < 0.0f)
|
||||
{
|
||||
reloadTimer = 0.0f;
|
||||
ReloadTimer = 0.0f;
|
||||
IsActive = false;
|
||||
}
|
||||
}
|
||||
@@ -101,10 +102,10 @@ namespace Barotrauma.Items.Components
|
||||
public override bool Use(float deltaTime, Character character = null)
|
||||
{
|
||||
if (character == null || character.Removed) { return false; }
|
||||
if ((item.RequireAimToUse && !character.IsKeyDown(InputType.Aim)) || reloadTimer > 0.0f) { return false; }
|
||||
if ((item.RequireAimToUse && !character.IsKeyDown(InputType.Aim)) || ReloadTimer > 0.0f) { return false; }
|
||||
|
||||
IsActive = true;
|
||||
reloadTimer = reload;
|
||||
ReloadTimer = reload;
|
||||
|
||||
if (item.AiTarget != null)
|
||||
{
|
||||
|
||||
@@ -854,9 +854,11 @@ namespace Barotrauma.Items.Components
|
||||
object value = property.GetValue(target);
|
||||
if (door.Stuck > 0)
|
||||
{
|
||||
bool isCutting = effect.propertyEffects[i].GetType() == typeof(float) && (float)effect.propertyEffects[i] < 0;
|
||||
var progressBar = user.UpdateHUDProgressBar(door, door.Item.WorldPosition, door.Stuck / 100, Color.DarkGray * 0.5f, Color.White,
|
||||
effect.propertyEffects[i].GetType() == typeof(float) && (float)effect.propertyEffects[i] < 0 ? "progressbar.cutting" : "progressbar.welding");
|
||||
textTag: isCutting ? "progressbar.cutting" : "progressbar.welding");
|
||||
if (progressBar != null) { progressBar.Size = new Vector2(60.0f, 20.0f); }
|
||||
if (!isCutting) { HintManager.OnWeldingDoor(user); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -771,6 +771,10 @@ namespace Barotrauma.Items.Components
|
||||
brokenEffects.ForEach(e => e.SetUser(user));
|
||||
}
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
HintManager.OnStatusEffectApplied(this, type, character);
|
||||
#endif
|
||||
}
|
||||
|
||||
public virtual void Load(XElement componentElement, bool usePrefabValues, IdRemap idRemap)
|
||||
|
||||
@@ -212,6 +212,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
progressState = 0.0f;
|
||||
timeUntilReady = 0.0f;
|
||||
UpdateRequiredTimeProjSpecific();
|
||||
inputContainer.Inventory.Locked = false;
|
||||
outputContainer.Inventory.Locked = false;
|
||||
|
||||
@@ -279,6 +280,7 @@ namespace Barotrauma.Items.Components
|
||||
if (powerConsumption <= 0) { Voltage = 1.0f; }
|
||||
|
||||
timeUntilReady -= deltaTime * Math.Min(Voltage, 1.0f);
|
||||
UpdateRequiredTimeProjSpecific();
|
||||
|
||||
if (timeUntilReady > 0.0f) { return; }
|
||||
|
||||
@@ -360,6 +362,8 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
partial void UpdateRequiredTimeProjSpecific();
|
||||
|
||||
private bool CanBeFabricated(FabricationRecipe fabricableItem)
|
||||
{
|
||||
if (fabricableItem == null) { return false; }
|
||||
|
||||
@@ -221,6 +221,13 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
if(PowerOn && AvailableFuel < 1)
|
||||
{
|
||||
HintManager.OnReactorOutOfFuel(this);
|
||||
}
|
||||
#endif
|
||||
|
||||
prevAvailableFuel = AvailableFuel;
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
|
||||
|
||||
|
||||
@@ -150,7 +150,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
currPowerConsumption = powerConsumption;
|
||||
currPowerConsumption = (currentMode == Mode.Active) ? powerConsumption : powerConsumption * 0.1f;
|
||||
|
||||
UpdateOnActiveEffects(deltaTime);
|
||||
|
||||
|
||||
@@ -22,6 +22,11 @@ namespace Barotrauma.Items.Components
|
||||
private const float AutoPilotMaxSpeed = 0.5f;
|
||||
private const float AIPilotMaxSpeed = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// How fast the steering vector adjusts when the nav terminal is operated by something else than a character (= signals)
|
||||
/// </summary>
|
||||
const float DefaultSteeringAdjustSpeed = 0.2f;
|
||||
|
||||
private Vector2 targetVelocity;
|
||||
|
||||
private Vector2 steeringInput;
|
||||
@@ -543,6 +548,10 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
TargetVelocity *= 100.0f / velMagnitude;
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
HintManager.OnAutoPilotPathUpdated(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
private float? GetNodePenalty(PathNode node, PathNode nextNode)
|
||||
@@ -700,7 +709,10 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (connection.Name == "velocity_in")
|
||||
{
|
||||
TargetVelocity = XMLExtensions.ParseVector2(signal, errorMessages: false);
|
||||
steeringAdjustSpeed = DefaultSteeringAdjustSpeed;
|
||||
steeringInput = XMLExtensions.ParseVector2(signal, errorMessages: false);
|
||||
steeringInput.X = MathHelper.Clamp(steeringInput.X, -100.0f, 100.0f);
|
||||
steeringInput.Y = MathHelper.Clamp(steeringInput.Y, -100.0f, 100.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -4,7 +4,7 @@ using System.Xml.Linq;
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
class AndComponent : ItemComponent
|
||||
{
|
||||
{
|
||||
protected string output, falseOutput;
|
||||
|
||||
//an array to keep track of how long ago a non-zero signal was received on both inputs
|
||||
@@ -27,14 +27,41 @@ namespace Barotrauma.Items.Components
|
||||
public string Output
|
||||
{
|
||||
get { return output; }
|
||||
set { output = value; }
|
||||
set
|
||||
{
|
||||
if (value == null) { return; }
|
||||
output = value;
|
||||
if (output.Length > MaxOutputLength)
|
||||
{
|
||||
output = output.Substring(0, MaxOutputLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[InGameEditable, Serialize("", true, description: "The signal sent when the condition is met (if empty, no signal is sent).", alwaysUseInstanceValues: true)]
|
||||
public string FalseOutput
|
||||
{
|
||||
get { return falseOutput; }
|
||||
set { falseOutput = value; }
|
||||
set
|
||||
{
|
||||
if (value == null) { return; }
|
||||
falseOutput = value;
|
||||
if (falseOutput.Length > MaxOutputLength)
|
||||
{
|
||||
falseOutput = falseOutput.Substring(0, MaxOutputLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int maxOutputLength;
|
||||
[Editable, Serialize(200, false, description: "The maximum length of the output strings. Warning: Large values can lead to large memory usage or networking issues.")]
|
||||
public int MaxOutputLength
|
||||
{
|
||||
get { return maxOutputLength; }
|
||||
set
|
||||
{
|
||||
maxOutputLength = Math.Max(value, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public AndComponent(Item item, XElement element)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
@@ -10,6 +11,9 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private string output = "0,0,0,0";
|
||||
|
||||
[InGameEditable, Serialize(false, true, description: "When enabled makes the component translate the signal from HSV into RGB where red is the hue between 0 and 360, green is the saturation between 0 and 1 and blue is the value between 0 and 1.", alwaysUseInstanceValues: true)]
|
||||
public bool UseHSV { get; set; }
|
||||
|
||||
public ColorComponent(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
@@ -24,10 +28,23 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private void UpdateOutput()
|
||||
{
|
||||
output = receivedSignal[0].ToString("G", CultureInfo.InvariantCulture);
|
||||
output += "," + receivedSignal[1].ToString("G", CultureInfo.InvariantCulture);
|
||||
output += "," + receivedSignal[2].ToString("G", CultureInfo.InvariantCulture);
|
||||
output += "," + receivedSignal[3].ToString("G", CultureInfo.InvariantCulture);
|
||||
float signalR = receivedSignal[0],
|
||||
signalG = receivedSignal[1],
|
||||
signalB = receivedSignal[2],
|
||||
signalA = receivedSignal[3];
|
||||
|
||||
if (UseHSV)
|
||||
{
|
||||
Color hsvColor = ToolBox.HSVToRGB(signalR, signalG, signalB);
|
||||
signalR = hsvColor.R / (float) byte.MaxValue;
|
||||
signalG = hsvColor.G / (float) byte.MaxValue;
|
||||
signalB = hsvColor.B / (float) byte.MaxValue;
|
||||
}
|
||||
|
||||
output = signalR.ToString("G", CultureInfo.InvariantCulture);
|
||||
output += "," + signalG.ToString("G", CultureInfo.InvariantCulture);
|
||||
output += "," + signalB.ToString("G", CultureInfo.InvariantCulture);
|
||||
output += "," + signalA.ToString("G", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
|
||||
|
||||
+31
-4
@@ -15,18 +15,45 @@ namespace Barotrauma.Items.Components
|
||||
//the output is sent if both inputs have received a signal within the timeframe
|
||||
protected float timeFrame;
|
||||
|
||||
[InGameEditable, Serialize("1", true, description: "The signal this item outputs when the condition is met.", alwaysUseInstanceValues: true)]
|
||||
[InGameEditable, Serialize("1", true, description: "The signal sent when the condition is met.", alwaysUseInstanceValues: true)]
|
||||
public string Output
|
||||
{
|
||||
get { return output; }
|
||||
set { output = value; }
|
||||
set
|
||||
{
|
||||
if (value == null) { return; }
|
||||
output = value;
|
||||
if (output.Length > MaxOutputLength)
|
||||
{
|
||||
output = output.Substring(0, MaxOutputLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[InGameEditable, Serialize("", true, description: "The signal this item outputs when the condition is not met.", alwaysUseInstanceValues: true)]
|
||||
[InGameEditable, Serialize("", true, description: "The signal sent when the condition is met (if empty, no signal is sent).", alwaysUseInstanceValues: true)]
|
||||
public string FalseOutput
|
||||
{
|
||||
get { return falseOutput; }
|
||||
set { falseOutput = value; }
|
||||
set
|
||||
{
|
||||
if (value == null) { return; }
|
||||
falseOutput = value;
|
||||
if (falseOutput.Length > MaxOutputLength)
|
||||
{
|
||||
falseOutput = falseOutput.Substring(0, MaxOutputLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int maxOutputLength;
|
||||
[Editable, Serialize(200, false, description: "The maximum length of the output strings. Warning: Large values can lead to large memory usage or networking issues.")]
|
||||
public int MaxOutputLength
|
||||
{
|
||||
get { return maxOutputLength; }
|
||||
set
|
||||
{
|
||||
maxOutputLength = Math.Max(value, 0);
|
||||
}
|
||||
}
|
||||
|
||||
[InGameEditable(DecimalCount = 2), Serialize(0.0f, true, description: "The maximum amount of time between the received signals. If set to 0, the signals must be received at the same time.", alwaysUseInstanceValues: true)]
|
||||
|
||||
@@ -74,11 +74,48 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
private string output;
|
||||
[InGameEditable, Serialize("1", true, description: "The signal the item outputs when it has detected movement.", alwaysUseInstanceValues: true)]
|
||||
public string Output { get; set; }
|
||||
public string Output
|
||||
{
|
||||
get { return output; }
|
||||
set
|
||||
{
|
||||
if (value == null) { return; }
|
||||
output = value;
|
||||
if (output.Length > MaxOutputLength)
|
||||
{
|
||||
output = output.Substring(0, MaxOutputLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string falseOutput;
|
||||
[InGameEditable, Serialize("", true, description: "The signal the item outputs when it has not detected movement.", alwaysUseInstanceValues: true)]
|
||||
public string FalseOutput { get; set; }
|
||||
public string FalseOutput
|
||||
{
|
||||
get { return falseOutput; }
|
||||
set
|
||||
{
|
||||
if (value == null) { return; }
|
||||
falseOutput = value;
|
||||
if (falseOutput.Length > MaxOutputLength)
|
||||
{
|
||||
falseOutput = falseOutput.Substring(0, MaxOutputLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int maxOutputLength;
|
||||
[Editable, Serialize(200, false, description: "The maximum length of the output strings. Warning: Large values can lead to large memory usage or networking issues.")]
|
||||
public int MaxOutputLength
|
||||
{
|
||||
get { return maxOutputLength; }
|
||||
set
|
||||
{
|
||||
maxOutputLength = Math.Max(value, 0);
|
||||
}
|
||||
}
|
||||
|
||||
[Editable(DecimalCount = 3), Serialize(0.01f, true, description: "How fast the objects within the detector's range have to be moving (in m/s).", alwaysUseInstanceValues: true)]
|
||||
public float MinimumVelocity
|
||||
|
||||
+28
-2
@@ -1,4 +1,5 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
@@ -17,8 +18,22 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private bool nonContinuousOutputSent;
|
||||
|
||||
private string output;
|
||||
|
||||
[InGameEditable, Serialize("1", true, description: "The signal this item outputs when the received signal matches the regular expression.", alwaysUseInstanceValues: true)]
|
||||
public string Output { get; set; }
|
||||
public string Output
|
||||
{
|
||||
get { return output; }
|
||||
set
|
||||
{
|
||||
if (value == null) { return; }
|
||||
output = value;
|
||||
if (output.Length > MaxOutputLength)
|
||||
{
|
||||
output = output.Substring(0, MaxOutputLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[InGameEditable, Serialize(false, true, description: "Should the component output a value of a capture group instead of a constant signal.", alwaysUseInstanceValues: true)]
|
||||
public bool UseCaptureGroup { get; set; }
|
||||
@@ -52,6 +67,17 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
private int maxOutputLength;
|
||||
[Editable, Serialize(200, false, description: "The maximum length of the output string. Warning: Large values can lead to large memory usage or networking issues.")]
|
||||
public int MaxOutputLength
|
||||
{
|
||||
get { return maxOutputLength; }
|
||||
set
|
||||
{
|
||||
maxOutputLength = Math.Max(value, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public RegExFindComponent(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
|
||||
+42
-3
@@ -1,17 +1,56 @@
|
||||
using System.Xml.Linq;
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
class SignalCheckComponent : ItemComponent
|
||||
{
|
||||
private string output;
|
||||
[InGameEditable, Serialize("1", true, description: "The signal this item outputs when the received signal matches the target signal.", alwaysUseInstanceValues: true)]
|
||||
public string Output { get; set; }
|
||||
public string Output
|
||||
{
|
||||
get { return output; }
|
||||
set
|
||||
{
|
||||
if (value == null) { return; }
|
||||
output = value;
|
||||
if (output.Length > MaxOutputLength)
|
||||
{
|
||||
output = output.Substring(0, MaxOutputLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string falseOutput;
|
||||
[InGameEditable, Serialize("0", true, description: "The signal this item outputs when the received signal does not match the target signal.", alwaysUseInstanceValues: true)]
|
||||
public string FalseOutput { get; set; }
|
||||
public string FalseOutput
|
||||
{
|
||||
get { return falseOutput; }
|
||||
set
|
||||
{
|
||||
if (value == null) { return; }
|
||||
falseOutput = value;
|
||||
if (falseOutput.Length > MaxOutputLength)
|
||||
{
|
||||
falseOutput = falseOutput.Substring(0, MaxOutputLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[InGameEditable, Serialize("", true, description: "The value to compare the received signals against.", alwaysUseInstanceValues: true)]
|
||||
public string TargetSignal { get; set; }
|
||||
|
||||
private int maxOutputLength;
|
||||
[Editable, Serialize(200, false, description: "The maximum length of the output strings. Warning: Large values can lead to large memory usage or networking issues.")]
|
||||
public int MaxOutputLength
|
||||
{
|
||||
get { return maxOutputLength; }
|
||||
set
|
||||
{
|
||||
maxOutputLength = Math.Max(value, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public SignalCheckComponent(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Xml.Linq;
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
@@ -9,11 +10,48 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private bool fireInRange;
|
||||
|
||||
[InGameEditable, Serialize("1", true, description: "The signal the item outputs when it has detected movement.", alwaysUseInstanceValues: true)]
|
||||
public string Output { get; set; }
|
||||
private string output;
|
||||
[InGameEditable, Serialize("1", true, description: "The signal the item outputs when it has detected a fire.", alwaysUseInstanceValues: true)]
|
||||
public string Output
|
||||
{
|
||||
get { return output; }
|
||||
set
|
||||
{
|
||||
if (value == null) { return; }
|
||||
output = value;
|
||||
if (output.Length > MaxOutputLength)
|
||||
{
|
||||
output = output.Substring(0, MaxOutputLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[InGameEditable, Serialize("0", true, description: "The signal the item outputs when it has not detected movement.", alwaysUseInstanceValues: true)]
|
||||
public string FalseOutput { get; set; }
|
||||
private string falseOutput;
|
||||
[InGameEditable, Serialize("0", true, description: "The signal the item outputs when it has not detected a fire.", alwaysUseInstanceValues: true)]
|
||||
public string FalseOutput
|
||||
{
|
||||
get { return falseOutput; }
|
||||
set
|
||||
{
|
||||
if (value == null) { return; }
|
||||
falseOutput = value;
|
||||
if (falseOutput.Length > MaxOutputLength)
|
||||
{
|
||||
falseOutput = falseOutput.Substring(0, MaxOutputLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int maxOutputLength;
|
||||
[Editable, Serialize(200, false, description: "The maximum length of the output strings. Warning: Large values can lead to large memory usage or networking issues.")]
|
||||
public int MaxOutputLength
|
||||
{
|
||||
get { return maxOutputLength; }
|
||||
set
|
||||
{
|
||||
maxOutputLength = Math.Max(value, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public SmokeDetector(Item item, XElement element)
|
||||
: base(item, element)
|
||||
|
||||
@@ -12,11 +12,48 @@ namespace Barotrauma.Items.Components
|
||||
private bool isInWater;
|
||||
private float stateSwitchDelay;
|
||||
|
||||
private string output;
|
||||
[InGameEditable, Serialize("1", true, description: "The signal the item sends out when it's underwater.", alwaysUseInstanceValues: true)]
|
||||
public string Output { get; set; }
|
||||
public string Output
|
||||
{
|
||||
get { return output; }
|
||||
set
|
||||
{
|
||||
if (value == null) { return; }
|
||||
output = value;
|
||||
if (output.Length > MaxOutputLength)
|
||||
{
|
||||
output = output.Substring(0, MaxOutputLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string falseOutput;
|
||||
[InGameEditable, Serialize("0", true, description: "The signal the item sends out when it's not underwater.", alwaysUseInstanceValues: true)]
|
||||
public string FalseOutput { get; set; }
|
||||
public string FalseOutput
|
||||
{
|
||||
get { return falseOutput; }
|
||||
set
|
||||
{
|
||||
if (value == null) { return; }
|
||||
falseOutput = value;
|
||||
if (falseOutput.Length > MaxOutputLength)
|
||||
{
|
||||
falseOutput = falseOutput.Substring(0, MaxOutputLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int maxOutputLength;
|
||||
[Editable, Serialize(200, false, description: "The maximum length of the output strings. Warning: Large values can lead to large memory usage or networking issues.")]
|
||||
public int MaxOutputLength
|
||||
{
|
||||
get { return maxOutputLength; }
|
||||
set
|
||||
{
|
||||
maxOutputLength = Math.Max(value, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public WaterDetector(Item item, XElement element)
|
||||
: base(item, element)
|
||||
|
||||
@@ -767,7 +767,6 @@ namespace Barotrauma.Items.Components
|
||||
TryLaunch(deltaTime, ignorePower: true);
|
||||
}
|
||||
|
||||
private bool outOfAmmo;
|
||||
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
|
||||
{
|
||||
if (character.AIController.SelectedAiTarget?.Entity is Character previousTarget &&
|
||||
@@ -834,7 +833,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
if (container == null || container.ContainableItems.Count == 0)
|
||||
{
|
||||
if (!outOfAmmo && character.IsOnPlayerTeam)
|
||||
if (character.IsOnPlayerTeam)
|
||||
{
|
||||
character.Speak(TextManager.GetWithVariable("DialogCannotLoadTurret", "[itemname]", item.Name, true), null, 0.0f, "cannotloadturret", 30.0f);
|
||||
}
|
||||
@@ -850,7 +849,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
loadItemsObjective.Abandoned += CheckRemainingAmmo;
|
||||
loadItemsObjective.Completed += CheckRemainingAmmo;
|
||||
return outOfAmmo;
|
||||
return false;
|
||||
|
||||
void CheckRemainingAmmo()
|
||||
{
|
||||
@@ -1203,8 +1202,7 @@ namespace Barotrauma.Items.Components
|
||||
resetUserTimer = 10.0f;
|
||||
break;
|
||||
case "trigger_in":
|
||||
if (signal == "0") { return; }
|
||||
lightComponent.IsOn = !lightComponent.IsOn;
|
||||
if (signal == "0") { return; }
|
||||
item.Use((float)Timing.Step, sender);
|
||||
user = sender;
|
||||
resetUserTimer = 10.0f;
|
||||
|
||||
@@ -826,7 +826,8 @@ namespace Barotrauma
|
||||
{
|
||||
slots[index].Add(item);
|
||||
item.ParentInventory = this;
|
||||
if (item.body != null)
|
||||
bool equipped = (this as CharacterInventory)?.Owner is Character character && character.HasEquippedItem(item);
|
||||
if (item.body != null && !equipped)
|
||||
{
|
||||
item.body.Enabled = false;
|
||||
item.body.BodyType = FarseerPhysics.BodyType.Dynamic;
|
||||
|
||||
@@ -485,12 +485,14 @@ namespace Barotrauma
|
||||
public int ClusterQuantity { get; }
|
||||
public int ClusterSize { get; }
|
||||
public bool IsIslandSpecifc { get; }
|
||||
public bool AllowAtStart { get; }
|
||||
|
||||
public FixedQuantityResourceInfo(int clusterQuantity, int clusterSize, bool isIslandSpecific)
|
||||
public FixedQuantityResourceInfo(int clusterQuantity, int clusterSize, bool isIslandSpecific, bool allowAtStart)
|
||||
{
|
||||
ClusterQuantity = clusterQuantity;
|
||||
ClusterSize = clusterSize;
|
||||
IsIslandSpecifc = isIslandSpecific;
|
||||
AllowAtStart = allowAtStart;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -965,7 +967,8 @@ namespace Barotrauma
|
||||
LevelQuantity.Add(levelName, new FixedQuantityResourceInfo(
|
||||
levelCommonnessElement.GetAttributeInt("clusterquantity", 0),
|
||||
levelCommonnessElement.GetAttributeInt("clustersize", 0),
|
||||
levelCommonnessElement.GetAttributeBool("isislandspecific", false)));
|
||||
levelCommonnessElement.GetAttributeBool("isislandspecific", false),
|
||||
levelCommonnessElement.GetAttributeBool("allowatstart", true)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,6 +159,11 @@ namespace Barotrauma
|
||||
get { return AbyssArea.Y + AbyssArea.Height; }
|
||||
}
|
||||
|
||||
public int AbyssEnd
|
||||
{
|
||||
get { return AbyssArea.Y; }
|
||||
}
|
||||
|
||||
public class AbyssIsland
|
||||
{
|
||||
public readonly Rectangle Area;
|
||||
@@ -1370,15 +1375,15 @@ namespace Barotrauma
|
||||
float seaFloorPos = GetBottomPosition(xPos).Y;
|
||||
|
||||
//above the bottom of the level = can't place a point here
|
||||
if (seaFloorPos > AbyssArea.Bottom) { continue; }
|
||||
if (seaFloorPos > AbyssStart) { continue; }
|
||||
|
||||
float yPos = Rand.Range(Math.Max(seaFloorPos, AbyssArea.Y), AbyssArea.Bottom);
|
||||
float yPos = MathHelper.Lerp(AbyssStart, Math.Max(seaFloorPos, AbyssArea.Y), Rand.Range(0.2f, 1.0f, Rand.RandSync.Server));
|
||||
|
||||
foreach (var abyssIsland in AbyssIslands)
|
||||
{
|
||||
if (abyssIsland.Area.Contains(new Point((int)xPos, (int)yPos)))
|
||||
{
|
||||
xPos = abyssIsland.Area.Center.X + (int)(Rand.Int(1) == 0 ? abyssIsland.Area.Width * -0.6f : 0.6f);
|
||||
xPos = abyssIsland.Area.Center.X + (int)(Rand.Int(1, Rand.RandSync.Server) == 0 ? abyssIsland.Area.Width * -0.6f : 0.6f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2035,6 +2040,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (l.Cell == null || l.Edge == null) { return false; }
|
||||
if (resourceInfo.IsIslandSpecifc && !l.Cell.Island) { return false; }
|
||||
if (!resourceInfo.AllowAtStart && l.EdgeCenter.Y > StartPosition.Y && l.EdgeCenter.X < Size.X * 0.25f) { return false; }
|
||||
if (l.EdgeCenter.Y < AbyssArea.Bottom) { return false; }
|
||||
return resourceInfo.ClusterSize <= GetMaxResourcesOnEdge(itemPrefab, l, out _);
|
||||
|
||||
@@ -3240,13 +3246,13 @@ namespace Barotrauma
|
||||
}
|
||||
#endif
|
||||
}
|
||||
if (StartLocation != null && !StartLocation.HasOutpost()) { continue; }
|
||||
if (StartLocation != null && !StartLocation.Type.HasOutpost) { continue; }
|
||||
}
|
||||
else
|
||||
{
|
||||
//don't create an end outpost for locations
|
||||
if (LevelData.Type == LevelData.LevelType.Outpost) { continue; }
|
||||
if (EndLocation != null && !EndLocation.HasOutpost()) { continue; }
|
||||
if (EndLocation != null && !EndLocation.Type.HasOutpost) { continue; }
|
||||
}
|
||||
|
||||
SubmarineInfo outpostInfo;
|
||||
@@ -3266,7 +3272,7 @@ namespace Barotrauma
|
||||
{
|
||||
var suitableParams = OutpostGenerationParams.Params
|
||||
.Where(p => location == null || p.AllowedLocationTypes.Contains(location.Type.Identifier));
|
||||
if (suitableParams.Count() == 0)
|
||||
if (!suitableParams.Any())
|
||||
{
|
||||
suitableParams = OutpostGenerationParams.Params
|
||||
.Where(p => location == null || !p.AllowedLocationTypes.Any());
|
||||
@@ -3305,7 +3311,7 @@ namespace Barotrauma
|
||||
|
||||
foreach (string categoryToHide in locationType.HideEntitySubcategories)
|
||||
{
|
||||
foreach (MapEntity entityToHide in MapEntity.mapEntityList.Where(me => me.Submarine == outpost && me.prefab.HasSubCategory(categoryToHide)))
|
||||
foreach (MapEntity entityToHide in MapEntity.mapEntityList.Where(me => me.Submarine == outpost && (me.prefab?.HasSubCategory(categoryToHide) ?? false)))
|
||||
{
|
||||
entityToHide.HiddenInGame = true;
|
||||
}
|
||||
@@ -3444,7 +3450,12 @@ namespace Barotrauma
|
||||
Item reactorItem = beaconItems.Find(it => it.GetComponent<Reactor>() != null);
|
||||
Reactor reactorComponent = reactorItem.GetComponent<Reactor>();
|
||||
ItemContainer reactorContainer = reactorItem.GetComponent<ItemContainer>();
|
||||
|
||||
Repairable repairable = reactorItem.GetComponent<Repairable>();
|
||||
reactorComponent.FuelConsumptionRate = 0.0f;
|
||||
if (repairable != null)
|
||||
{
|
||||
repairable.DeteriorationSpeed = 0.0f;
|
||||
}
|
||||
if (LevelData.IsBeaconActive)
|
||||
{
|
||||
if (reactorContainer.Inventory.IsEmpty())
|
||||
|
||||
@@ -236,6 +236,11 @@ namespace Barotrauma
|
||||
DebugConsole.ThrowError("Failed to load a linked submarine (empty XML element). The save file may be corrupted.");
|
||||
return;
|
||||
}
|
||||
if (!info.SubmarineElement.Elements().Any(e => e.Name.ToString().Equals("hull", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to load a linked submarine (the submarine contains no hulls).");
|
||||
return;
|
||||
}
|
||||
|
||||
IdRemap parentRemap = new IdRemap(Submarine.Info.SubmarineElement, Submarine.IdOffset);
|
||||
sub = Submarine.Load(info, false, parentRemap);
|
||||
@@ -322,7 +327,7 @@ namespace Barotrauma
|
||||
sub.SetPosition((linkedPort.Item.WorldPosition - portDiff) - offset);
|
||||
|
||||
myPort.Dock(linkedPort);
|
||||
myPort.Lock(true, applyEffects: false);
|
||||
myPort.Lock(isNetworkMessage: true, applyEffects: false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -579,6 +579,16 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
|
||||
public LocationType GetLocationType()
|
||||
{
|
||||
if (IsCriticallyRadiated() && LocationType.List.FirstOrDefault(lt => lt.Identifier.Equals(Type.ReplaceInRadiation, StringComparison.OrdinalIgnoreCase)) is { } newLocationType)
|
||||
{
|
||||
return newLocationType;
|
||||
}
|
||||
|
||||
return Type;
|
||||
}
|
||||
|
||||
public IEnumerable<Mission> GetMissionsInConnection(LocationConnection connection)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(Connections.Contains(connection));
|
||||
|
||||
@@ -14,6 +14,8 @@ namespace Barotrauma
|
||||
|
||||
public bool Passed;
|
||||
|
||||
public bool Locked;
|
||||
|
||||
public LevelData LevelData { get; set; }
|
||||
|
||||
public Vector2 CenterPos
|
||||
|
||||
@@ -54,8 +54,11 @@ namespace Barotrauma
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public string ReplaceInRadiation { get; }
|
||||
|
||||
public Sprite Sprite { get; private set; }
|
||||
public Sprite RadiationSprite { get; }
|
||||
|
||||
public Color SpriteColor
|
||||
{
|
||||
@@ -85,6 +88,8 @@ namespace Barotrauma
|
||||
|
||||
HideEntitySubcategories = element.GetAttributeStringArray("hideentitysubcategories", new string[0]).ToList();
|
||||
|
||||
ReplaceInRadiation = element.GetAttributeString(nameof(ReplaceInRadiation).ToLower(), "");
|
||||
|
||||
string nameFile = element.GetAttributeString("namefile", "Content/Map/locationNames.txt");
|
||||
try
|
||||
{
|
||||
@@ -140,6 +145,9 @@ namespace Barotrauma
|
||||
Sprite = new Sprite(subElement, lazyLoad: true);
|
||||
SpriteColor = subElement.GetAttributeColor("color", Color.White);
|
||||
break;
|
||||
case "radiationsymbol":
|
||||
RadiationSprite = new Sprite(subElement, lazyLoad: true);
|
||||
break;
|
||||
case "changeto":
|
||||
CanChangeTo.Add(new LocationTypeChange(Identifier, subElement, requireChangeMessages: true));
|
||||
break;
|
||||
|
||||
@@ -59,18 +59,21 @@ namespace Barotrauma
|
||||
|
||||
public Radiation Radiation;
|
||||
|
||||
public Map()
|
||||
public Map(CampaignSettings settings)
|
||||
{
|
||||
generationParams = MapGenerationParams.Instance;
|
||||
Locations = new List<Location>();
|
||||
Connections = new List<LocationConnection>();
|
||||
Radiation = new Radiation(this, generationParams.RadiationParams);
|
||||
Radiation = new Radiation(this, generationParams.RadiationParams)
|
||||
{
|
||||
Enabled = settings.RadiationEnabled
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load a previously saved campaign map from XML
|
||||
/// </summary>
|
||||
private Map(CampaignMode campaign, XElement element) : this()
|
||||
private Map(CampaignMode campaign, XElement element, CampaignSettings settings) : this(settings)
|
||||
{
|
||||
Seed = element.GetAttributeString("seed", "a");
|
||||
Rand.SetSyncedSeed(ToolBox.StringToInt(Seed));
|
||||
@@ -87,7 +90,10 @@ namespace Barotrauma
|
||||
Locations[i] = new Location(subElement);
|
||||
break;
|
||||
case "radiation":
|
||||
Radiation = new Radiation(this, generationParams.RadiationParams, subElement);
|
||||
Radiation = new Radiation(this, generationParams.RadiationParams, subElement)
|
||||
{
|
||||
Enabled = settings.RadiationEnabled
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -107,6 +113,7 @@ namespace Barotrauma
|
||||
var connection = new LocationConnection(Locations[locationIndices.X], Locations[locationIndices.Y])
|
||||
{
|
||||
Passed = subElement.GetAttributeBool("passed", false),
|
||||
Locked = subElement.GetAttributeBool("locked", false),
|
||||
Difficulty = subElement.GetAttributeFloat("difficulty", 0.0f)
|
||||
};
|
||||
Locations[locationIndices.X].Connections.Add(connection);
|
||||
@@ -162,7 +169,7 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Generate a new campaign map from the seed
|
||||
/// </summary>
|
||||
public Map(CampaignMode campaign, string seed) : this()
|
||||
public Map(CampaignMode campaign, string seed, CampaignSettings settings) : this(settings)
|
||||
{
|
||||
Seed = seed;
|
||||
Rand.SetSyncedSeed(ToolBox.StringToInt(Seed));
|
||||
@@ -404,6 +411,7 @@ namespace Barotrauma
|
||||
leftMostLocation.ChangeType(LocationType.List.First(lt => lt.HasOutpost));
|
||||
}
|
||||
leftMostLocation.IsGateBetweenBiomes = true;
|
||||
Connections[i].Locked = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -688,6 +696,10 @@ namespace Barotrauma
|
||||
SelectedConnection =
|
||||
Connections.Find(c => c.Locations.Contains(GameMain.GameSession?.Campaign?.CurrentDisplayLocation) && c.Locations.Contains(SelectedLocation)) ??
|
||||
Connections.Find(c => c.Locations.Contains(CurrentLocation) && c.Locations.Contains(SelectedLocation));
|
||||
if (SelectedConnection?.Locked ?? false)
|
||||
{
|
||||
DebugConsole.ThrowError("A locked connection was selected - this should not be possible.\n" + Environment.StackTrace.CleanupStackTrace());
|
||||
}
|
||||
OnLocationSelected?.Invoke(SelectedLocation, SelectedConnection);
|
||||
}
|
||||
|
||||
@@ -703,6 +715,10 @@ namespace Barotrauma
|
||||
|
||||
SelectedLocation = location;
|
||||
SelectedConnection = Connections.Find(c => c.Locations.Contains(CurrentLocation) && c.Locations.Contains(SelectedLocation));
|
||||
if (SelectedConnection?.Locked ?? false)
|
||||
{
|
||||
DebugConsole.ThrowError("A locked connection was selected - this should not be possible.\n" + Environment.StackTrace.CleanupStackTrace());
|
||||
}
|
||||
OnLocationSelected?.Invoke(SelectedLocation, SelectedConnection);
|
||||
}
|
||||
|
||||
@@ -736,7 +752,7 @@ namespace Barotrauma
|
||||
|
||||
public void SelectRandomLocation(bool preferUndiscovered)
|
||||
{
|
||||
List<Location> nextLocations = CurrentLocation.Connections.Select(c => c.OtherLocation(CurrentLocation)).ToList();
|
||||
List<Location> nextLocations = CurrentLocation.Connections.Where(c => !c.Locked).Select(c => c.OtherLocation(CurrentLocation)).ToList();
|
||||
List<Location> undiscoveredLocations = nextLocations.FindAll(l => !l.Discovered);
|
||||
|
||||
if (undiscoveredLocations.Count > 0 && preferUndiscovered)
|
||||
@@ -943,9 +959,9 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Load a previously saved map from an xml element
|
||||
/// </summary>
|
||||
public static Map Load(CampaignMode campaign, XElement element)
|
||||
public static Map Load(CampaignMode campaign, XElement element, CampaignSettings settings)
|
||||
{
|
||||
Map map = new Map(campaign, element);
|
||||
Map map = new Map(campaign, element, settings);
|
||||
map.LoadState(element, false);
|
||||
#if CLIENT
|
||||
map.DrawOffset = -map.CurrentLocation.MapPosition;
|
||||
@@ -1079,6 +1095,7 @@ namespace Barotrauma
|
||||
|
||||
var connectionElement = new XElement("connection",
|
||||
new XAttribute("passed", connection.Passed),
|
||||
new XAttribute("locked", connection.Locked),
|
||||
new XAttribute("difficulty", connection.Difficulty),
|
||||
new XAttribute("biome", connection.Biome.Identifier),
|
||||
new XAttribute("locations", Locations.IndexOf(connection.Locations[0]) + "," + Locations.IndexOf(connection.Locations[1])));
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
@@ -13,6 +14,9 @@ namespace Barotrauma
|
||||
[Serialize(defaultValue: 0f, isSaveable: true)]
|
||||
public float Amount { get; set; }
|
||||
|
||||
[Serialize(defaultValue: true, isSaveable: true)]
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public Dictionary<string, SerializableProperty> SerializableProperties { get; }
|
||||
|
||||
public readonly Map Map;
|
||||
@@ -23,8 +27,6 @@ namespace Barotrauma
|
||||
private float increasedAmount;
|
||||
private float lastIncrease;
|
||||
|
||||
public bool Enabled = true;
|
||||
|
||||
public Radiation(Map map, RadiationParams radiationParams, XElement? element = null)
|
||||
{
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
@@ -52,6 +54,12 @@ namespace Barotrauma
|
||||
|
||||
foreach (Location location in Map.Locations.Where(Contains))
|
||||
{
|
||||
if (location.IsGateBetweenBiomes)
|
||||
{
|
||||
location.Connections.ForEach(c => c.Locked = false);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (amountOfOutposts <= Params.MinimumOutpostAmount) { break; }
|
||||
|
||||
if (Map.CurrentLocation is { } currLocation)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -29,6 +30,15 @@ namespace Barotrauma
|
||||
[Serialize(defaultValue: 1f, isSaveable: false, "How much is the radiation affliction increased by while in a radiated zone.")]
|
||||
public float RadiationDamageAmount { get; set; }
|
||||
|
||||
[Serialize(defaultValue: "139,0,0,85", isSaveable: false, "The color of the radiated area.")]
|
||||
public Color RadiationAreaColor { get; set; }
|
||||
|
||||
[Serialize(defaultValue: "255,0,0,255", isSaveable: false, "The tint of the radiation border sprites.")]
|
||||
public Color RadiationBorderTint { get; set; }
|
||||
|
||||
[Serialize(defaultValue: 16.66f, isSaveable: false, "Speed of the border spritesheet animation.")]
|
||||
public float BorderAnimationSpeed { get; set; }
|
||||
|
||||
public RadiationParams(XElement element)
|
||||
{
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
|
||||
@@ -67,6 +67,9 @@ namespace Barotrauma
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("", isSaveable: true), Editable]
|
||||
public string ReplaceInRadiation { get; set; }
|
||||
|
||||
private readonly Dictionary<string, int> moduleCounts = new Dictionary<string, int>();
|
||||
|
||||
public IEnumerable<KeyValuePair<string, int>> ModuleCounts
|
||||
|
||||
@@ -68,6 +68,15 @@ namespace Barotrauma
|
||||
private static Submarine Generate(OutpostGenerationParams generationParams, LocationType locationType, Location location, bool onlyEntrance = false)
|
||||
{
|
||||
var outpostModuleFiles = ContentPackage.GetFilesOfType(GameMain.Config.AllEnabledPackages, ContentType.OutpostModule);
|
||||
if (location != null)
|
||||
{
|
||||
if (location.IsCriticallyRadiated() && OutpostGenerationParams.Params.FirstOrDefault(p => p.Identifier.Equals(generationParams.ReplaceInRadiation, StringComparison.OrdinalIgnoreCase)) is { } newParams)
|
||||
{
|
||||
generationParams = newParams;
|
||||
}
|
||||
|
||||
locationType = location.GetLocationType();
|
||||
}
|
||||
|
||||
//load the infos of the outpost module files
|
||||
List<SubmarineInfo> outpostModules = new List<SubmarineInfo>();
|
||||
@@ -974,7 +983,7 @@ namespace Barotrauma
|
||||
var moduleEntities = MapEntity.LoadAll(sub, hallwayInfo.SubmarineElement, hallwayInfo.FilePath, -1);
|
||||
|
||||
//remove items that don't fit in the hallway
|
||||
moduleEntities.Where(e => e is Item item && item.GetComponent<Door>() == null && e.Rect.Width > hallwayLength).ForEach(e => e.Remove());
|
||||
moduleEntities.Where(e => e is Item item && item.GetComponent<Door>() == null && (isHorizontal ? e.Rect.Width : e.Rect.Height) > hallwayLength).ForEach(e => e.Remove());
|
||||
|
||||
//find the largest hull to use it as the center point of the hallway
|
||||
//and the bounds of all the hulls, used when resizing the hallway to fit between the modules
|
||||
@@ -1047,11 +1056,11 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (me is Structure structure)
|
||||
else if (me is Structure || (me is Item item && item.GetComponent<Door>() == null))
|
||||
{
|
||||
if (isHorizontal)
|
||||
{
|
||||
if (!structure.ResizeHorizontal)
|
||||
if (!me.ResizeHorizontal)
|
||||
{
|
||||
int xPos = (int)(leftHull.WorldRect.Right + (me.WorldPosition.X - hullBounds.X) * scaleFactor);
|
||||
me.Rect = new Rectangle(xPos - me.RectWidth / 2, me.Rect.Y, me.Rect.Width, me.Rect.Height);
|
||||
@@ -1065,9 +1074,9 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!structure.ResizeVertical)
|
||||
if (!me.ResizeVertical)
|
||||
{
|
||||
int yPos = (int)(topHull.WorldRect.Y - topHull.RectHeight + (me.WorldPosition.X - hullBounds.Bottom) * scaleFactor);
|
||||
int yPos = (int)(topHull.WorldRect.Y - topHull.RectHeight + (me.WorldPosition.Y - hullBounds.Bottom) * scaleFactor);
|
||||
me.Rect = new Rectangle(me.Rect.X, yPos + me.RectHeight / 2, me.Rect.Width, me.Rect.Height);
|
||||
}
|
||||
else
|
||||
@@ -1394,7 +1403,7 @@ namespace Barotrauma
|
||||
ISpatialEntity gotoTarget = SpawnAction.GetSpawnPos(SpawnAction.SpawnLocationType.Outpost, SpawnType.Human, humanPrefab.GetModuleFlags(), humanPrefab.GetSpawnPointTags());
|
||||
if (gotoTarget == null)
|
||||
{
|
||||
gotoTarget = outpost.GetHulls(true).GetRandom();
|
||||
gotoTarget = outpost.GetHulls(true).GetRandom(Rand.RandSync.Server);
|
||||
}
|
||||
characterInfo.TeamID = CharacterTeamType.FriendlyNPC;
|
||||
var npc = Character.Create(CharacterPrefab.HumanConfigFile, SpawnAction.OffsetSpawnPos(gotoTarget.WorldPosition, 100.0f), ToolBox.RandomSeed(8), characterInfo, hasAi: true, createNetworkEvent: true);
|
||||
|
||||
@@ -590,10 +590,11 @@ namespace Barotrauma
|
||||
if (item.GetComponent<Turret>() != null) { return false; }
|
||||
if (item.body != null && !item.body.Enabled) { return true; }
|
||||
}
|
||||
if (e.HiddenInGame) { return true; }
|
||||
return false;
|
||||
});
|
||||
|
||||
if (entities.Count == 0) return Rectangle.Empty;
|
||||
if (entities.Count == 0) { return Rectangle.Empty; }
|
||||
|
||||
float minX = entities[0].Rect.X, minY = entities[0].Rect.Y - entities[0].Rect.Height;
|
||||
float maxX = entities[0].Rect.Right, maxY = entities[0].Rect.Y;
|
||||
@@ -1356,7 +1357,7 @@ namespace Barotrauma
|
||||
if (me.Submarine != this) { continue; }
|
||||
if (me is Item item)
|
||||
{
|
||||
item.SpawnedInOutpost = !info.OutpostGenerationParams.AllowStealing;
|
||||
item.SpawnedInOutpost = info.OutpostGenerationParams != null && !info.OutpostGenerationParams.AllowStealing;
|
||||
if (item.GetComponent<Repairable>() != null && indestructible)
|
||||
{
|
||||
item.Indestructible = true;
|
||||
@@ -1366,7 +1367,7 @@ namespace Barotrauma
|
||||
if (ic is ConnectionPanel connectionPanel)
|
||||
{
|
||||
//prevent rewiring
|
||||
if (!info.OutpostGenerationParams.AlwaysRewireable)
|
||||
if (info.OutpostGenerationParams != null && !info.OutpostGenerationParams.AlwaysRewireable)
|
||||
{
|
||||
connectionPanel.Locked = true;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,9 @@ using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public enum SpawnType { Path = 0, Human = 1, Enemy = 2, Cargo = 3, Corpse = 4 };
|
||||
[Flags]
|
||||
public enum SpawnType { Path = 0, Human = 1, Enemy = 2, Cargo = 4, Corpse = 8 };
|
||||
|
||||
partial class WayPoint : MapEntity
|
||||
{
|
||||
public static List<WayPoint> WayPointList = new List<WayPoint>();
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace Barotrauma.Networking
|
||||
public string Name; public UInt16 NameID;
|
||||
public byte ID;
|
||||
public UInt64 SteamID;
|
||||
public UInt64 OwnerSteamID;
|
||||
|
||||
public string Language;
|
||||
|
||||
|
||||
+16
@@ -21,6 +21,12 @@ namespace Barotrauma.Networking
|
||||
protected set;
|
||||
}
|
||||
|
||||
public UInt64 OwnerSteamID
|
||||
{
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
|
||||
public string EndPointString
|
||||
{
|
||||
get;
|
||||
@@ -44,5 +50,15 @@ namespace Barotrauma.Networking
|
||||
//is received by the server.
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool SetOwnerSteamIDIfUnknown(UInt64 id)
|
||||
{
|
||||
//we know that for both Lidgren and SteamP2P, the
|
||||
//owner id isn't known until the auth ticket is
|
||||
//processed, so this method is the same for both
|
||||
if (OwnerSteamID != 0) { return false; }
|
||||
OwnerSteamID = id;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -10,6 +10,7 @@ namespace Barotrauma.Networking
|
||||
public SteamP2PConnection(string name, UInt64 steamId)
|
||||
{
|
||||
SteamID = steamId;
|
||||
OwnerSteamID = 0;
|
||||
EndPointString = SteamManager.SteamIDUInt64ToString(SteamID);
|
||||
Name = name;
|
||||
Heartbeat();
|
||||
|
||||
@@ -872,6 +872,13 @@ namespace Barotrauma.Networking
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(true, true)]
|
||||
public bool RadiationEnabled
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public void SetPassword(string password)
|
||||
{
|
||||
if (string.IsNullOrEmpty(password))
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace Barotrauma
|
||||
var collision = prefabs.Find(p => p != prefab && p.UIntIdentifier == prefab.UIntIdentifier);
|
||||
if (collision != null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Hashing collision when generating uint identifiers for {nameof(T)}: {prefab.Identifier} has the same identifier as {collision.Identifier} ({prefab.UIntIdentifier})");
|
||||
DebugConsole.ThrowError($"Hashing collision when generating uint identifiers for {typeof(T).Name}: {prefab.Identifier} has the same identifier as {collision.Identifier} ({prefab.UIntIdentifier})");
|
||||
collision.UIntIdentifier++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ namespace Barotrauma
|
||||
//Handle bad overrides and duplicates
|
||||
if (basePrefabExists && !isOverride)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error registering \"{prefab.OriginalName}\", \"{prefab.Identifier}\" ({typeof(T).ToString()}): base already exists; try overriding");
|
||||
DebugConsole.ThrowError($"Error registering \"{prefab.OriginalName}\", \"{prefab.Identifier}\" ({typeof(T).ToString()}): base already exists; try overriding\n{Environment.StackTrace}");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,22 @@ namespace Barotrauma
|
||||
levelDifficultyScrollBar.OnMoved(levelDifficultyScrollBar, levelDifficultyScrollBar.BarScroll);
|
||||
#endif
|
||||
}
|
||||
|
||||
public void SetRadiationEnabled(bool enabled)
|
||||
{
|
||||
#if CLIENT
|
||||
radiationEnabledTickBox.Selected = enabled;
|
||||
#endif
|
||||
}
|
||||
|
||||
public bool IsRadiationEnabled()
|
||||
{
|
||||
#if CLIENT
|
||||
return radiationEnabledTickBox.Selected;
|
||||
#elif SERVER
|
||||
return GameMain.Server.ServerSettings.RadiationEnabled;
|
||||
#endif
|
||||
}
|
||||
|
||||
public void ToggleTraitorsEnabled(int dir)
|
||||
{
|
||||
|
||||
@@ -570,6 +570,25 @@ namespace Barotrauma
|
||||
|
||||
public static Color ParseColor(string stringColor, bool errorMessages = true)
|
||||
{
|
||||
if (stringColor.StartsWith("gui.", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
#if CLIENT
|
||||
if (GUI.Style != null)
|
||||
{
|
||||
string colorName = stringColor.Substring(4);
|
||||
var property = GUI.Style.GetType().GetProperties().FirstOrDefault(
|
||||
p => p.PropertyType == typeof(Color) &&
|
||||
p.Name.Equals(colorName, StringComparison.OrdinalIgnoreCase));
|
||||
if (property != null)
|
||||
{
|
||||
return (Color)property?.GetValue(GUI.Style);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return Color.White;
|
||||
}
|
||||
|
||||
|
||||
string[] strComponents = stringColor.Split(',');
|
||||
|
||||
Color color = Color.White;
|
||||
|
||||
@@ -39,6 +39,66 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
class AITrigger : ISerializableEntity
|
||||
{
|
||||
public string Name => "ai trigger";
|
||||
|
||||
public Dictionary<string, SerializableProperty> SerializableProperties { get; set; }
|
||||
|
||||
[Serialize(AIState.Idle, false)]
|
||||
public AIState State { get; private set; }
|
||||
|
||||
[Serialize(0f, false)]
|
||||
public float Duration { get; private set; }
|
||||
|
||||
[Serialize(1f, false)]
|
||||
public float Probability { get; private set; }
|
||||
|
||||
[Serialize(0f, false)]
|
||||
public float MinDamage { get; private set; }
|
||||
|
||||
[Serialize(true, false)]
|
||||
public bool AllowToOverride { get; private set; }
|
||||
|
||||
[Serialize(true, false)]
|
||||
public bool AllowToBeOverridden { get; private set; }
|
||||
|
||||
public bool IsTriggered { get; private set; }
|
||||
|
||||
public float Timer { get; private set; } = -1;
|
||||
|
||||
public bool IsActive { get; private set; }
|
||||
|
||||
public void Launch()
|
||||
{
|
||||
IsTriggered = true;
|
||||
IsActive = true;
|
||||
Timer = Duration;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
IsTriggered = false;
|
||||
IsActive = false;
|
||||
Timer = 0;
|
||||
}
|
||||
|
||||
public void UpdateTimer(float deltaTime)
|
||||
{
|
||||
Timer -= deltaTime;
|
||||
if (Timer < 0)
|
||||
{
|
||||
Timer = 0;
|
||||
IsActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
public AITrigger(XElement element)
|
||||
{
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
}
|
||||
}
|
||||
|
||||
partial class StatusEffect
|
||||
{
|
||||
[Flags]
|
||||
@@ -203,6 +263,7 @@ namespace Barotrauma
|
||||
|
||||
private readonly List<ItemSpawnInfo> spawnItems;
|
||||
private readonly List<CharacterSpawnInfo> spawnCharacters;
|
||||
private readonly List<AITrigger> aiTriggers;
|
||||
|
||||
private readonly List<EventPrefab> triggeredEvents;
|
||||
private readonly string triggeredEventTargetTag = "statuseffecttarget",
|
||||
@@ -282,6 +343,7 @@ namespace Barotrauma
|
||||
requiredItems = new List<RelatedItem>();
|
||||
spawnItems = new List<ItemSpawnInfo>();
|
||||
spawnCharacters = new List<CharacterSpawnInfo>();
|
||||
aiTriggers = new List<AITrigger>();
|
||||
Afflictions = new List<Affliction>();
|
||||
Explosions = new List<Explosion>();
|
||||
triggeredEvents = new List<EventPrefab>();
|
||||
@@ -532,7 +594,6 @@ namespace Barotrauma
|
||||
triggeredEvents.Add(prefab);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (XElement eventElement in subElement.Elements())
|
||||
{
|
||||
if (!eventElement.Name.ToString().Equals("ScriptedEvent", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
@@ -543,6 +604,9 @@ namespace Barotrauma
|
||||
var newSpawnCharacter = new CharacterSpawnInfo(subElement, parentDebugName);
|
||||
if (!string.IsNullOrWhiteSpace(newSpawnCharacter.SpeciesName)) { spawnCharacters.Add(newSpawnCharacter); }
|
||||
break;
|
||||
case "aitrigger":
|
||||
aiTriggers.Add(new AITrigger(subElement));
|
||||
break;
|
||||
}
|
||||
}
|
||||
InitProjSpecific(element, parentDebugName);
|
||||
@@ -1032,7 +1096,7 @@ namespace Barotrauma
|
||||
{
|
||||
targetCharacter = character;
|
||||
}
|
||||
else if (target is Limb limb)
|
||||
else if (target is Limb limb && !limb.Removed)
|
||||
{
|
||||
targetLimb = limb;
|
||||
targetCharacter = limb.character;
|
||||
@@ -1053,6 +1117,32 @@ namespace Barotrauma
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
if (aiTriggers.Any())
|
||||
{
|
||||
Character targetCharacter = target as Character;
|
||||
if (targetCharacter == null)
|
||||
{
|
||||
if (target is Limb targetLimb && !targetLimb.Removed)
|
||||
{
|
||||
targetCharacter = targetLimb.character;
|
||||
}
|
||||
}
|
||||
if (targetCharacter != null && !targetCharacter.Removed && !targetCharacter.IsPlayer)
|
||||
{
|
||||
if (targetCharacter.AIController is EnemyAIController enemyAI)
|
||||
{
|
||||
foreach (AITrigger trigger in aiTriggers)
|
||||
{
|
||||
if (Rand.Value(Rand.RandSync.Unsynced) > trigger.Probability) { continue; }
|
||||
if (target is Limb targetLimb && targetCharacter.LastDamage.HitLimb != targetLimb) { continue; }
|
||||
if (targetCharacter.LastDamage.Damage < trigger.MinDamage) { continue; }
|
||||
enemyAI.LaunchTrigger(trigger);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (FireSize > 0.0f && entity != null)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Barotrauma.Extensions;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -1066,6 +1067,16 @@ namespace Barotrauma
|
||||
if (diff == 0) { return v >= max ? 1f : 0f; }
|
||||
return MathHelper.Clamp((v - min) / diff, 0f, 1f);
|
||||
}
|
||||
|
||||
public static float Min(params float[] vals)
|
||||
{
|
||||
return vals.Min();
|
||||
}
|
||||
|
||||
public static float Max(params float[] vals)
|
||||
{
|
||||
return vals.Max();
|
||||
}
|
||||
}
|
||||
|
||||
class CompareCCW : IComparer<Vector2>
|
||||
|
||||
@@ -7,7 +7,10 @@ namespace Barotrauma.IO
|
||||
{
|
||||
static readonly string[] unwritableDirs = new string[] { "Content", "Data/ContentPackages" };
|
||||
|
||||
public static bool DevException;
|
||||
/// <summary>
|
||||
/// When set to true, the game is allowed to modify the vanilla content in debug builds. Has no effect in non-debug builds.
|
||||
/// </summary>
|
||||
public static bool SkipValidationInDebugBuilds;
|
||||
|
||||
public static bool CanWrite(string path)
|
||||
{
|
||||
@@ -20,7 +23,7 @@ namespace Barotrauma.IO
|
||||
if (path.StartsWith(dir, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
#if DEBUG
|
||||
return DevException;
|
||||
return SkipValidationInDebugBuilds;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
@@ -37,7 +40,7 @@ namespace Barotrauma.IO
|
||||
{
|
||||
if (!Validation.CanWrite(path))
|
||||
{
|
||||
DebugConsole.ThrowError($"Cannot save XML document to \"{path}\": failed validation");
|
||||
DebugConsole.ThrowError($"Cannot save XML document to \"{path}\": modifying the files in the folder is not allowed.");
|
||||
return;
|
||||
}
|
||||
doc.Save(path);
|
||||
@@ -47,7 +50,7 @@ namespace Barotrauma.IO
|
||||
{
|
||||
if (!Validation.CanWrite(path))
|
||||
{
|
||||
DebugConsole.ThrowError($"Cannot save XML element to \"{path}\": failed validation");
|
||||
DebugConsole.ThrowError($"Cannot save XML element to \"{path}\": modifying the files in the folder is not allowed.");
|
||||
return;
|
||||
}
|
||||
element.Save(path);
|
||||
@@ -72,7 +75,7 @@ namespace Barotrauma.IO
|
||||
{
|
||||
if (!Validation.CanWrite(path))
|
||||
{
|
||||
DebugConsole.ThrowError($"Cannot write XML document to \"{path}\": failed validation");
|
||||
DebugConsole.ThrowError($"Cannot write XML document to \"{path}\": modifying the files in the folder is not allowed.");
|
||||
Writer = null;
|
||||
return;
|
||||
}
|
||||
@@ -222,7 +225,7 @@ namespace Barotrauma.IO
|
||||
{
|
||||
if (!Validation.CanWrite(path))
|
||||
{
|
||||
DebugConsole.ThrowError($"Cannot create directory \"{path}\": failed validation");
|
||||
DebugConsole.ThrowError($"Cannot create directory \"{path}\": modifying the contents of the folder is not allowed.");
|
||||
return null;
|
||||
}
|
||||
return System.IO.Directory.CreateDirectory(path);
|
||||
@@ -232,7 +235,7 @@ namespace Barotrauma.IO
|
||||
{
|
||||
if (!Validation.CanWrite(path))
|
||||
{
|
||||
DebugConsole.ThrowError($"Cannot delete directory \"{path}\": failed validation");
|
||||
DebugConsole.ThrowError($"Cannot delete directory \"{path}\": modifying the contents of the folder is not allowed.");
|
||||
return;
|
||||
}
|
||||
//TODO: validate recursion?
|
||||
@@ -251,7 +254,7 @@ namespace Barotrauma.IO
|
||||
{
|
||||
if (!Validation.CanWrite(dest))
|
||||
{
|
||||
DebugConsole.ThrowError($"Cannot copy \"{src}\" to \"{dest}\": failed validation");
|
||||
DebugConsole.ThrowError($"Cannot copy \"{src}\" to \"{dest}\": modifying the contents of the folder is not allowed.");
|
||||
return;
|
||||
}
|
||||
System.IO.File.Copy(src, dest, overwrite);
|
||||
@@ -261,12 +264,12 @@ namespace Barotrauma.IO
|
||||
{
|
||||
if (!Validation.CanWrite(src))
|
||||
{
|
||||
DebugConsole.ThrowError($"Cannot move \"{src}\" to \"{dest}\": src failed validation");
|
||||
DebugConsole.ThrowError($"Cannot move \"{src}\" to \"{dest}\": modifying the contents of the source folder is not allowed.");
|
||||
return;
|
||||
}
|
||||
if (!Validation.CanWrite(dest))
|
||||
{
|
||||
DebugConsole.ThrowError($"Cannot move \"{src}\" to \"{dest}\": dest failed validation");
|
||||
DebugConsole.ThrowError($"Cannot move \"{src}\" to \"{dest}\": modifying the contents of the destination folder is not allowed");
|
||||
return;
|
||||
}
|
||||
System.IO.File.Move(src, dest);
|
||||
@@ -276,7 +279,7 @@ namespace Barotrauma.IO
|
||||
{
|
||||
if (!Validation.CanWrite(path))
|
||||
{
|
||||
DebugConsole.ThrowError($"Cannot delete file \"{path}\": failed validation");
|
||||
DebugConsole.ThrowError($"Cannot delete file \"{path}\": modifying the contents of the folder is not allowed.");
|
||||
return;
|
||||
}
|
||||
System.IO.File.Delete(path);
|
||||
@@ -298,7 +301,7 @@ namespace Barotrauma.IO
|
||||
case System.IO.FileMode.Truncate:
|
||||
if (!Validation.CanWrite(path))
|
||||
{
|
||||
DebugConsole.ThrowError($"Cannot open \"{path}\" in {mode} mode: failed validation");
|
||||
DebugConsole.ThrowError($"Cannot open \"{path}\" in {mode} mode: modifying the contents of the folder is not allowed.");
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
@@ -328,7 +331,7 @@ namespace Barotrauma.IO
|
||||
{
|
||||
if (!Validation.CanWrite(path))
|
||||
{
|
||||
DebugConsole.ThrowError($"Cannot write all bytes to \"{path}\": failed validation");
|
||||
DebugConsole.ThrowError($"Cannot write all bytes to \"{path}\": modifying the files in the folder is not allowed.");
|
||||
return;
|
||||
}
|
||||
System.IO.File.WriteAllBytes(path, contents);
|
||||
@@ -338,7 +341,7 @@ namespace Barotrauma.IO
|
||||
{
|
||||
if (!Validation.CanWrite(path))
|
||||
{
|
||||
DebugConsole.ThrowError($"Cannot write all text to \"{path}\": failed validation");
|
||||
DebugConsole.ThrowError($"Cannot write all text to \"{path}\": modifying the files in the folder is not allowed.");
|
||||
return;
|
||||
}
|
||||
System.IO.File.WriteAllText(path, contents, encoding ?? System.Text.Encoding.UTF8);
|
||||
@@ -348,7 +351,7 @@ namespace Barotrauma.IO
|
||||
{
|
||||
if (!Validation.CanWrite(path))
|
||||
{
|
||||
DebugConsole.ThrowError($"Cannot write all lines to \"{path}\": failed validation");
|
||||
DebugConsole.ThrowError($"Cannot write all lines to \"{path}\": modifying the files in the folder is not allowed.");
|
||||
return;
|
||||
}
|
||||
System.IO.File.WriteAllLines(path, contents, encoding ?? System.Text.Encoding.UTF8);
|
||||
@@ -420,7 +423,7 @@ namespace Barotrauma.IO
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Cannot write to file \"{fileName}\": failed validation");
|
||||
DebugConsole.ThrowError($"Cannot write to file \"{fileName}\": modifying the files in the folder is not allowed.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -487,7 +490,7 @@ namespace Barotrauma.IO
|
||||
{
|
||||
if (!Validation.CanWrite(innerInfo.FullName))
|
||||
{
|
||||
DebugConsole.ThrowError($"Cannot delete directory \"{Name}\": failed validation");
|
||||
DebugConsole.ThrowError($"Cannot delete directory \"{Name}\": modifying the contents of the folder is not allowed.");
|
||||
return;
|
||||
}
|
||||
innerInfo.Delete();
|
||||
@@ -523,7 +526,7 @@ namespace Barotrauma.IO
|
||||
{
|
||||
if (!Validation.CanWrite(innerInfo.FullName))
|
||||
{
|
||||
DebugConsole.ThrowError($"Cannot set read-only to {value} for \"{Name}\": failed validation");
|
||||
DebugConsole.ThrowError($"Cannot set read-only to {value} for \"{Name}\": modifying the files in the folder is not allowed.");
|
||||
return;
|
||||
}
|
||||
innerInfo.IsReadOnly = value;
|
||||
@@ -534,7 +537,7 @@ namespace Barotrauma.IO
|
||||
{
|
||||
if (!Validation.CanWrite(dest))
|
||||
{
|
||||
DebugConsole.ThrowError($"Cannot copy \"{Name}\" to \"{dest}\": failed validation");
|
||||
DebugConsole.ThrowError($"Cannot copy \"{Name}\" to \"{dest}\": modifying the contents of the destination folder is not allowed.");
|
||||
return;
|
||||
}
|
||||
innerInfo.CopyTo(dest, overwriteExisting);
|
||||
@@ -544,7 +547,7 @@ namespace Barotrauma.IO
|
||||
{
|
||||
if (!Validation.CanWrite(innerInfo.FullName))
|
||||
{
|
||||
DebugConsole.ThrowError($"Cannot delete file \"{Name}\": failed validation");
|
||||
DebugConsole.ThrowError($"Cannot delete file \"{Name}\": modifying the files in the folder is not allowed.");
|
||||
return;
|
||||
}
|
||||
innerInfo.Delete();
|
||||
|
||||
@@ -224,6 +224,38 @@ namespace Barotrauma
|
||||
return inputType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a HSV value into a RGB value.
|
||||
/// </summary>
|
||||
/// <param name="hue">Value between 0 and 360</param>
|
||||
/// <param name="saturation">Value between 0 and 1</param>
|
||||
/// <param name="value">Value between 0 and 1</param>
|
||||
/// <see href="https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_RGB">Reference</see>
|
||||
/// <returns></returns>
|
||||
public static Color HSVToRGB(float hue, float saturation, float value)
|
||||
{
|
||||
float c = value * saturation;
|
||||
|
||||
float h = Math.Clamp(hue, 0, 360) / 60f;
|
||||
|
||||
float x = c * (1 - Math.Abs(h % 2 - 1));
|
||||
|
||||
float r = 0,
|
||||
g = 0,
|
||||
b = 0;
|
||||
|
||||
if (0 <= h && h <= 1) { r = c; g = x; b = 0; }
|
||||
else if (1 < h && h <= 2) { r = x; g = c; b = 0; }
|
||||
else if (2 < h && h <= 3) { r = 0; g = c; b = x; }
|
||||
else if (3 < h && h <= 4) { r = 0; g = x; b = c; }
|
||||
else if (4 < h && h <= 5) { r = x; g = 0; b = c; }
|
||||
else if (5 < h && h <= 6) { r = c; g = 0; b = x; }
|
||||
|
||||
float m = value - c;
|
||||
|
||||
return new Color(r + m, g + m, b + m);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns either a green [x] or a red [o]
|
||||
/// </summary>
|
||||
@@ -491,6 +523,30 @@ namespace Barotrauma
|
||||
return destination;
|
||||
}
|
||||
|
||||
public static void SiftElement<T>(this List<T> list, int from, int to)
|
||||
{
|
||||
if (from < 0 || from >= list.Count) { throw new ArgumentException($"from parameter out of range (from={from}, range=[0..{list.Count - 1}])"); }
|
||||
if (to < 0 || to >= list.Count) { throw new ArgumentException($"to parameter out of range (to={to}, range=[0..{list.Count - 1}])"); }
|
||||
|
||||
T elem = list[from];
|
||||
if (from > to)
|
||||
{
|
||||
for (int i = from; i > to; i--)
|
||||
{
|
||||
list[i] = list[i - 1];
|
||||
}
|
||||
list[to] = elem;
|
||||
}
|
||||
else if (from < to)
|
||||
{
|
||||
for (int i = from; i < to; i++)
|
||||
{
|
||||
list[i] = list[i + 1];
|
||||
}
|
||||
list[to] = elem;
|
||||
}
|
||||
}
|
||||
|
||||
public static string ByteArrayToString(byte[] ba)
|
||||
{
|
||||
StringBuilder hex = new StringBuilder(ba.Length * 2);
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,5 +1,86 @@
|
||||
---------------------------------------------------------------------------------------------------------
|
||||
v0.12.X.X
|
||||
v0.1300.0.1 (unstable)
|
||||
---------------------------------------------------------------------------------------------------------
|
||||
|
||||
Changes:
|
||||
- 3 new outpost mission types: clearing a nest, hostage rescue and assassination.
|
||||
- The enemies in abandoned outposts are tougher and there's more of them in multiplayer.
|
||||
- Improvements to abandoned outpost modules.
|
||||
- Respawning is disabled in abandoned outposts.
|
||||
- Gating progress between biomes: you need a certain amount of money or reputation before you can enter the next biome.
|
||||
- Reworked Charybdis as another abyss monster (in addition to Endworm).
|
||||
- Revisited husk animations, movement speeds, and general balance. Husks regenerate a lot and are a bit tougher than they use to be. Human husks can and will easily rise again, unless properly killed.
|
||||
- Adjustments and fixes on the thresher attacks.
|
||||
- Revisit mudraptors' attacks. Fixes unarmored mudraptors and mudraptor hatchlings acting weird when attacking characters in water.
|
||||
- Lower the overall damage of all the hatchlings.
|
||||
- Bots can now be renamed through the outpost crew management interface.
|
||||
- Fabricators now display remaining time when fabricating.
|
||||
- Endworm now groans when the tail is cut.
|
||||
- Beacon stations' reactors don't deteriorate or consuming fuel to prevent the station from going back down when the player is travelling out from the level.
|
||||
- Irradiated outposts become abandoned instead of simply disappearing.
|
||||
- Fabricator displays the time until ready when fabrication is in progress.
|
||||
- The output length of all signal components is now restricted to 200 characters by default to prevent performance and networking issues if the output is set to an excessively long value (such as the entire dialog of the Bee Movie). The limit can be increased in the sub editor.
|
||||
- Stunning still works on friendly characters even if friendly fire has been disabled on a server.
|
||||
- Made radiation sickness's icon appear before it starts causing burns (instead of working the other way around).
|
||||
- Made radiation sickness cause nausea.
|
||||
- Added a checkbox to color component that toggles between RGBA and HSV input.
|
||||
- Disabled NPC conversations in sub editor test mode.
|
||||
- Added a tooltip when hovering over radiated area.
|
||||
- Added a checkbox for the radiation feature to both singleplayer and multiplayer.
|
||||
- Radiated outposts now turn into abandoned outposts instead of vanishing in thin air.
|
||||
- Use ternary states for some of the server filters (thanks someone972!)
|
||||
- Banning a player who's using Steam Family Sharing will now also ban the owner's account, solving ban evasion using this method.
|
||||
- Added interactive submarine previews to the lobby and "New Game" screen.
|
||||
- Implemented rudimentary text highlighting in mission descriptions.
|
||||
- Added in-game hints, designed to help with new player onboarding. Can be disabled in the settings.
|
||||
|
||||
Fixes:
|
||||
- Attempt to fix crashes with the error message "failed to generate OpenAL/stream buffer" on Mac.
|
||||
- Waypoint fixes in the abandoned outposts.
|
||||
- Fixed bot reaction times and readjusted aiming delays.
|
||||
- Fixed bots that follow you not always holding position in the combat mode.
|
||||
- Fixed bots not being able to hit targets that lie on the ground.
|
||||
- Fixed monsters sometimes targeting ruin rooms.
|
||||
- Monsters now stop targeting characters that are far enough behind level geometry. Fixes them getting stuck while trying to reach targets that are not easily reachable.
|
||||
- Fixes to the Endworm's behavior and ragdoll.
|
||||
- Minor boost to the Endworm's attack on walls, major boost to the attack on characters.
|
||||
- Fixed a crash when no valid limb was found for an affliction. Probably only happened with characters that already had some limbs severed.
|
||||
- Fixed non-multiplied damage to limbs effectively always being clamped to 100, which means that no attack could do more than 100 damage per hit unless there's some damage modifier defined on the target limb.
|
||||
- Fixed husk's mouth tentacles rendering on top of the left hand. Also fixed a minor texture bleeding on the waist.
|
||||
- Fixed possible desync when multiple players use the crew management interface simultaneously.
|
||||
- Fixed being able to drag order icons when spectating.
|
||||
- Fixed non-interactable items being counted as owned in the store interface.
|
||||
- Fixed abyss monsters not keeping in the depths and non-abyss monsters not avoiding the depths when they have attack targets. Now they should only attack the targets that come to their zone and continue pursuing the target until they either lose it or if the state changes from attack to something else.
|
||||
- Fixed bots treating radiation sickness too eagerly (leading to wasted antirad).
|
||||
- Added missing platforms to abandoned outpost modules.
|
||||
- Fixed vanilla wrecks/modules/outposts being shown in the Workshop menu's publish tab.
|
||||
- Fixed bioluminescent cave's hallucination effect never going fully away and made it treatable with haloperidol.
|
||||
- Fixed characters getting impact damage from collisions with sensors. Caused characters to get stunned when they're thrown around by a monster while touching a level trigger (for instance, the branches in forest caves).
|
||||
- Fixed held items not appearing in the characters hand after entering a new level in the campaign.
|
||||
- Fixed hidden items being shown when searching for them in sub editor.
|
||||
- Fixed crashing when trying to load a linked sub that contains no hulls.
|
||||
- Fixed "hide incompatible" checkbox in the server list.
|
||||
- Fixed one of the engineer variants having too many items in the toolbelt.
|
||||
- Monster events don't spawn an additional endworm in the abyss if one has already been spawned by a hunting grounds mission.
|
||||
- Fixed nav terminal ignoring velocity_in signals when the terminal hasn't been operated by anyone during the round.
|
||||
- Fixed launching depth charges crashing the game.
|
||||
- Fixed coilgun lights flickering when firing.
|
||||
- Fixed ability to walk through the heavy doors in mining outposts.
|
||||
- Fixed inability to move stacks of items to containers by double-clicking.
|
||||
- Fixed crashing when trying to enter an abandoned outpost with a sub that has shuttles.
|
||||
- Fixed monsters targeting entities outside of their allowed zone. They dropped the target when they reached the non-allowed zone, which caused indetermined behavior (fluctuating) at the border.
|
||||
- Fixed monsters with the circling behavior sometimes not being able to reach the target because they were not targeting the closest target.
|
||||
- Fixed Workshop mod download prompt looping and freezing when there's a hash mismatch after a mod has already been installed.
|
||||
|
||||
Modding:
|
||||
- Added AITrigger that can be used to trigger an ai state using the status effects (See Charybdis).
|
||||
- Turned IsTraitor into a property so it can be accessed by status effects.
|
||||
|
||||
Bots:
|
||||
- Fixed non-security bots fleeing the enemy (in defensive combat state) even when they are ordered to fight intruders.
|
||||
|
||||
---------------------------------------------------------------------------------------------------------
|
||||
v0.1300.0.0 (unstable)
|
||||
---------------------------------------------------------------------------------------------------------
|
||||
|
||||
Campaign changes:
|
||||
@@ -12,17 +93,21 @@ Campaign changes:
|
||||
Abyss:
|
||||
- Reintroduced Endworms.
|
||||
- Added floating islands that contain caves and rare minerals to the Abyss.
|
||||
- Multiple current orders: you can assign up to three simultaneous orders for characters and drag the icons to change their priority.
|
||||
|
||||
Changes:
|
||||
- Added abandoned outposts and a new abandoned outpost mission type.
|
||||
- Added entity subcategories to the submarine editor (note that most of the vanilla items/structures aren't categorized yet in this build).
|
||||
- Reworked the impacts to the sub when it's hit by monsters. Increased the screen shake and stunning. There's now a 30 second cooldown for getting knocked down. Fixed the impact not triggering if the colliding limb is not big enough.
|
||||
- Reworked attacks and effects for the following creatures: Moloch, Black Moloch, Hammerhead, Hammerhead Matriarch, and Golden Hammerhead. They now have bigger impact on the sub when they hit it. Black Moloch's emp damage is halved.
|
||||
- Moloch's shell now always breaks when shot with a railgun.
|
||||
- Recreated waypoints for the vanilla subs.
|
||||
- Added EMP effect to nuclear shells.
|
||||
- Added EMP effect to nuclear shells. Increased the damage a bit.
|
||||
- Added a right click context menu option to copy debug console errors to clipboard.
|
||||
- Railgun shells now explode instead of piercing armor. Physicorium shells still go through armor.
|
||||
- Railgun shells now explode instead of piercing armor. Physicorium shells still go through armor. Make all railgun ammunition slightly more likely to break limb joints.
|
||||
- Added multiple current orders: you can assign up to three simultaneous orders for characters and drag the icons to change their priority.
|
||||
- NPCs (non-bots) speak report related lines less than they used to.
|
||||
- The bleeding particles are now emitted from the last matching limb instead of the first. Readjusted particle emit frequency and scale.
|
||||
- Reduced Moloch's bleeding reductions.
|
||||
|
||||
Fixes:
|
||||
- Major improvements to the voice chat: higher audio quality, less intrusive radio effect, fixed clicks/distortion.
|
||||
@@ -43,6 +128,10 @@ Fixes:
|
||||
- Fixed status monitor in a docked shuttle sometimes not displaying the main sub.
|
||||
- Fixed campaign stores sometimes displaying prices for a previous location.
|
||||
- Fixed various scaling issues on higher resolutions.
|
||||
- Fixed monsters fluttering while pursuing other creatures. Only happened while swimming and was not notable on all monsters).
|
||||
- Fixed mouse cursor being switched to hand even when the spritesheet is not shown in the character editor.
|
||||
- Fixed monsters with low head/torso torques moving backwards when they try to turn around 180 degrees.
|
||||
- Fixed regular Moloch not bleeding correctly.
|
||||
|
||||
Bots:
|
||||
- Bots now warn when you are running low / out of oxygen or welding fuel tanks, turret ammunition, or reactor fuel.
|
||||
@@ -58,11 +147,33 @@ Bots:
|
||||
- Fixed bots trying to return the diving suit when it's not a reasonable thing to do, but when they actually don't need it.
|
||||
- Fixed bots failing to swap an oxygen tank from a diving mask to a diving suit when both items are equipped.
|
||||
- Fixed bots getting confused when they have a mask without a valid oxygen tank inside it and when there's not enough oxygen in the room to be without the mask.
|
||||
- Bots now react faster in general to all enemies.
|
||||
- Bots now automatically attack enemies outside of the main sub, if they have a weapon. While docked to an enemy outpost or submarine, the "fight intruders" order now acts as an offensive order to attack the enemies on the connected submarine/outpost.
|
||||
- Fixed bots following the player when the player is controlling a monster.
|
||||
- You can now escape from NPCs by going far enough from them while they are pursuing you.
|
||||
- Fixed bots reacting to attackers that are outside of the submarine.
|
||||
- Fixed the "reportrange" parameter not working when the character is attacked. In practice only has effect on NPCs.
|
||||
- Fixed report icons being shown also for other teams instead of just the own team.
|
||||
- Bots now target the closest limb instead of the main collider when they aim with the turret. With small creatures, this should now make any difference. With long creatures, it allows the bots to target the extremities of the body instead of always shooting at the main body.
|
||||
|
||||
Modding:
|
||||
- The hit impact of a monster's attack can now be adjusted with the new "submarineimpactmultiplier" defined in the attack block. Note that this is a multipler to the actual impact, hence also the force applied on the attacking monster affects the final impact.
|
||||
- Explosions now have three new parameters: ignorecover, onlyinside, and onlyoutside.
|
||||
- Fixed crashing when attempting to play a music clip that isn't a valid ogg file or if the file is not found.
|
||||
- Fixed crashing when trying to spawn a character variant with custom inventory contents.
|
||||
- Renamed the ai parameter "Threshold" as "DamageThreshold".
|
||||
- Replaced "spawndeep" with "abyss" spawn type (defined in random events).
|
||||
- New parameters StayInAbyss and StayInsideLevel that define the area where the creature tries to keep inside while not attacking.
|
||||
- The creature disable distance, which totally disables the creature, is now exposed in the character parameters. The distance for triggering simple physics, which disables all the limbs and keeps only the main collider updated, is half of this distance. Increased the default from 22 000 to 25 000 (pixels).
|
||||
- New attack pattern: Circle (around the target). Used by the abyss creatures.
|
||||
- The aim speed and accuracy of NPC characters can now be adjusted in the npc (spawn) definition. The Aim speed also affects melee attack speed.
|
||||
- Fixed OnSevered status effects launching also on the limb that the severed limb was attached to.
|
||||
- Added "bleedingnonstop" affliction, which is just the same as normal bleeding but it never wears off.
|
||||
- Added and option to target the last matching limb insted of the first (StatusEffect.TargetType.Limb). Implemented targeting other limbs even when the status effect is triggered from the limb, which was previously only implemented for status effects that targeted character. See Endworm for an example.
|
||||
- Fixed hidden limbs not being ignored in many cases where they should, which potentially could cause issues with some custom monsters.
|
||||
- Added "bleedparticlemultiplier" parameter in character definition, which can be used to increase/decrease the general amount of bleeding for the character in question.
|
||||
- Added an option to always ignore an ai target if it's not inside the same sub as the character.
|
||||
- Attacks can now "blink" limbs when they attack (Endworm). Blinking is a generic way to rotate limbs so that they "animate" (see Watcher's eye).
|
||||
|
||||
---------------------------------------------------------------------------------------------------------
|
||||
v0.12.0.3
|
||||
|
||||
Reference in New Issue
Block a user