Unstable 0.15.15.0 (and the one before it I forgor)
This commit is contained in:
@@ -64,7 +64,7 @@ namespace Barotrauma
|
||||
#endif
|
||||
}
|
||||
|
||||
private IEnumerable<object> Update(ISpatialEntity targetEntity, Camera cam)
|
||||
private IEnumerable<CoroutineStatus> Update(ISpatialEntity targetEntity, Camera cam)
|
||||
{
|
||||
if (targetEntity == null || (targetEntity is Entity e && e.Removed)) { yield return CoroutineStatus.Success; }
|
||||
|
||||
|
||||
@@ -229,7 +229,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (sectorRad >= MathHelper.TwoPi) { return true; }
|
||||
Vector2 diff = worldPosition - WorldPosition;
|
||||
return MathUtils.GetShortestAngle(MathUtils.VectorToAngle(diff), MathUtils.VectorToAngle(sectorDir)) <= sectorRad * 0.5f;
|
||||
return Math.Abs(MathUtils.GetShortestAngle(MathUtils.VectorToAngle(diff), MathUtils.VectorToAngle(sectorDir))) <= sectorRad * 0.5f;
|
||||
}
|
||||
|
||||
public void Remove()
|
||||
|
||||
@@ -524,11 +524,12 @@ namespace Barotrauma
|
||||
if (Character.LockHands) { return; }
|
||||
if (ObjectiveManager.CurrentObjective == null) { return; }
|
||||
if (Character.CurrentHull == null) { return; }
|
||||
bool oxygenLow = !Character.AnimController.HeadInWater && Character.OxygenAvailable < CharacterHealth.LowOxygenThreshold;
|
||||
bool oxygenLow = !Character.AnimController.HeadInWater && Character.OxygenAvailable < CharacterHealth.LowOxygenThreshold && Character.NeedsOxygen;
|
||||
bool isCarrying = ObjectiveManager.HasActiveObjective<AIObjectiveContainItem>() || ObjectiveManager.HasActiveObjective<AIObjectiveDecontainItem>();
|
||||
|
||||
bool NeedsDivingGearOnPath(AIObjectiveGoTo gotoObjective)
|
||||
{
|
||||
if (!Character.NeedsAir) { return false; }
|
||||
bool insideSteering = SteeringManager == PathSteering && PathSteering.CurrentPath != null && !PathSteering.IsPathDirty;
|
||||
Hull targetHull = gotoObjective.GetTargetHull();
|
||||
return gotoObjective.Target != null && targetHull == null ||
|
||||
@@ -567,6 +568,7 @@ namespace Barotrauma
|
||||
Character.AnimController.HeadInWater ||
|
||||
Character.Submarine == null ||
|
||||
(Character.Submarine.TeamID != Character.TeamID && !Character.IsEscorted) ||
|
||||
ObjectiveManager.CurrentOrders.Any(o => o.Objective.KeepDivingGearOn) ||
|
||||
ObjectiveManager.CurrentObjective.GetSubObjectivesRecursive(true).Any(o => o.KeepDivingGearOn) ||
|
||||
Character.CurrentHull.OxygenPercentage < HULL_LOW_OXYGEN_PERCENTAGE + 10;
|
||||
bool IsOrderedToWait() => Character.IsOnPlayerTeam && ObjectiveManager.CurrentOrder is AIObjectiveGoTo goTo && goTo.Target == Character;
|
||||
@@ -625,7 +627,7 @@ namespace Barotrauma
|
||||
if (removeDivingSuit)
|
||||
{
|
||||
var divingSuit = Character.Inventory.FindItemByTag(AIObjectiveFindDivingGear.HEAVY_DIVING_GEAR);
|
||||
if (divingSuit != null)
|
||||
if (divingSuit != null && !divingSuit.HasTag(AIObjectiveFindDivingGear.DIVING_GEAR_WEARABLE_INDOORS))
|
||||
{
|
||||
if (oxygenLow || Character.Submarine?.TeamID != Character.TeamID || ObjectiveManager.GetCurrentPriority() >= AIObjectiveManager.RunPriority)
|
||||
{
|
||||
@@ -950,7 +952,7 @@ namespace Barotrauma
|
||||
if (item.CurrentHull != hull) { continue; }
|
||||
if (AIObjectiveRepairItems.IsValidTarget(item, Character))
|
||||
{
|
||||
if (item.Repairables.All(r => item.ConditionPercentage > r.RepairIconThreshold)) { continue; }
|
||||
if (!item.Repairables.Any(r => r.IsBelowRepairIconThreshold)) { continue; }
|
||||
if (AddTargets<AIObjectiveRepairItems, Item>(Character, item) && newOrder == null && !ObjectiveManager.HasActiveObjective<AIObjectiveRepairItem>())
|
||||
{
|
||||
var orderPrefab = Order.GetPrefab("reportbrokendevices");
|
||||
@@ -1117,6 +1119,7 @@ namespace Barotrauma
|
||||
// Don't react to attackers that are outside of the sub (e.g. AoE attacks)
|
||||
return;
|
||||
}
|
||||
bool isAttackerFightingEnemy = false;
|
||||
if (IsFriendly(attacker))
|
||||
{
|
||||
if (attacker.AnimController.Anim == Barotrauma.AnimController.Animation.CPR && attacker.SelectedCharacter == Character)
|
||||
@@ -1136,7 +1139,6 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
(GameMain.GameSession?.GameMode as CampaignMode)?.OutpostNPCAttacked(Character, attacker, attackResult);
|
||||
// Inform other NPCs
|
||||
if (cumulativeDamage > 1 || totalDamage >= 10)
|
||||
{
|
||||
@@ -1184,6 +1186,10 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isAttackerFightingEnemy)
|
||||
{
|
||||
(GameMain.GameSession?.GameMode as CampaignMode)?.OutpostNPCAttacked(Character, attacker, attackResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -1211,7 +1217,15 @@ namespace Barotrauma
|
||||
if (!(otherCharacter.AIController is HumanAIController otherHumanAI)) { continue; }
|
||||
if (!otherHumanAI.IsFriendly(Character)) { continue; }
|
||||
bool isWitnessing = otherHumanAI.VisibleHulls.Contains(Character.CurrentHull) || otherHumanAI.VisibleHulls.Contains(attacker.CurrentHull);
|
||||
if (!isWitnessing && !CheckReportRange(Character, otherCharacter, ReportRange)) { continue; }
|
||||
if (!isWitnessing)
|
||||
{
|
||||
//if the other character did not witness the attack, and the character is not within report range (or capable of reporting)
|
||||
//don't react to the attack
|
||||
if (Character.IsDead || Character.IsUnconscious || !CheckReportRange(Character, otherCharacter, ReportRange))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
var combatMode = DetermineCombatMode(otherCharacter, cumulativeDamage, isWitnessing, dmgThreshold: attacker.TeamID == Character.TeamID ? 50 : 10);
|
||||
float delay = isWitnessing ? GetReactionTime() : Rand.Range(2.0f, 5.0f, Rand.RandSync.Unsynced);
|
||||
otherHumanAI.AddCombatObjective(combatMode, attacker, delay);
|
||||
@@ -1244,18 +1258,20 @@ namespace Barotrauma
|
||||
return AIObjectiveCombat.CombatMode.None;
|
||||
}
|
||||
// If there are any enemies around, just ignore the friendly fire
|
||||
if (Character.CharacterList.Any(ch => ch.Submarine == Character.Submarine && !ch.Removed && !ch.IsDead && !ch.IsIncapacitated && !IsFriendly(ch) && VisibleHulls.Contains(ch.CurrentHull)))
|
||||
if (Character.CharacterList.Any(ch => ch.Submarine == Character.Submarine && !ch.Removed && !ch.IsIncapacitated && !IsFriendly(ch) && VisibleHulls.Contains(ch.CurrentHull)))
|
||||
{
|
||||
isAttackerFightingEnemy = true;
|
||||
return AIObjectiveCombat.CombatMode.None;
|
||||
}
|
||||
else if (isWitnessing && Character.CombatAction != null && !c.IsSecurity)
|
||||
{
|
||||
return Character.CombatAction.WitnessReaction;
|
||||
}
|
||||
else if (Character.IsInstigator && attacker.IsPlayer)
|
||||
else if (attacker.IsPlayer && FindInstigator() is Character instigator)
|
||||
{
|
||||
// The guards don't react when the player attacks instigators.
|
||||
return c.IsSecurity ? AIObjectiveCombat.CombatMode.None : (Character.CombatAction != null ? Character.CombatAction.WitnessReaction : AIObjectiveCombat.CombatMode.Retreat);
|
||||
// The guards don't react when the player there's an instigator around
|
||||
isAttackerFightingEnemy = true;
|
||||
return c.IsSecurity ? AIObjectiveCombat.CombatMode.None : (instigator.CombatAction != null ? instigator.CombatAction.WitnessReaction : AIObjectiveCombat.CombatMode.Retreat);
|
||||
}
|
||||
else if (attacker.TeamID == CharacterTeamType.FriendlyNPC && !(attacker.AIController.IsMentallyUnstable || attacker.AIController.IsMentallyUnstable))
|
||||
{
|
||||
@@ -1295,6 +1311,22 @@ namespace Barotrauma
|
||||
return c.IsSecurity ? AIObjectiveCombat.CombatMode.Arrest : AIObjectiveCombat.CombatMode.Retreat;
|
||||
}
|
||||
}
|
||||
|
||||
Character FindInstigator()
|
||||
{
|
||||
if (Character.IsInstigator)
|
||||
{
|
||||
return Character;
|
||||
}
|
||||
else if (c.AIController is HumanAIController humanAi)
|
||||
{
|
||||
return Character.CharacterList.FirstOrDefault(ch => ch.Submarine == c.Submarine && !ch.Removed && !ch.IsIncapacitated && ch.IsInstigator && humanAi.VisibleHulls.Contains(ch.CurrentHull));
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1416,15 +1448,20 @@ namespace Barotrauma
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool NeedsDivingGear(Hull hull, out bool needsSuit)
|
||||
public bool NeedsDivingGear(Hull hull, out bool needsSuit)
|
||||
{
|
||||
if (!Character.NeedsAir)
|
||||
{
|
||||
needsSuit = false;
|
||||
return false;
|
||||
}
|
||||
needsSuit = false;
|
||||
if (hull == null ||
|
||||
hull.WaterPercentage > 90 ||
|
||||
hull.LethalPressure > 0 ||
|
||||
hull.ConnectedGaps.Any(gap => !gap.IsRoomToRoom && gap.Open > 0.5f))
|
||||
{
|
||||
needsSuit = true;
|
||||
needsSuit = !Character.HasAbilityFlag(AbilityFlags.ImmuneToPressure);
|
||||
return true;
|
||||
}
|
||||
if (hull.WaterPercentage > 60 || hull.OxygenPercentage < HULL_LOW_OXYGEN_PERCENTAGE + 1)
|
||||
@@ -1570,9 +1607,9 @@ namespace Barotrauma
|
||||
Character thief = character;
|
||||
bool someoneSpoke = false;
|
||||
|
||||
bool stolenItemsInside = item.OwnInventory?.FindAllItems(it => it.SpawnedInOutpost && !it.AllowStealing, recursive: true).Any() ?? false;
|
||||
bool stolenItemsInside = item.OwnInventory?.FindAllItems(it => it.SpawnedInCurrentOutpost && !it.AllowStealing, recursive: true).Any() ?? false;
|
||||
|
||||
if ((item.SpawnedInOutpost && !item.AllowStealing || stolenItemsInside) && thief.TeamID != CharacterTeamType.FriendlyNPC && !item.HasTag("handlocker"))
|
||||
if ((item.SpawnedInCurrentOutpost && !item.AllowStealing || stolenItemsInside) && thief.TeamID != CharacterTeamType.FriendlyNPC && !item.HasTag("handlocker"))
|
||||
{
|
||||
foreach (Character otherCharacter in Character.CharacterList)
|
||||
{
|
||||
@@ -1624,7 +1661,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (item.OwnInventory?.FindItem(it => it.SpawnedInOutpost && !item.AllowStealing, true) is { } foundItem)
|
||||
else if (item.OwnInventory?.FindItem(it => it.SpawnedInCurrentOutpost && !item.AllowStealing, true) is { } foundItem)
|
||||
{
|
||||
ItemTaken(foundItem, character);
|
||||
}
|
||||
@@ -1698,7 +1735,7 @@ namespace Barotrauma
|
||||
if (item.CurrentHull != hull) { continue; }
|
||||
if (AIObjectiveRepairItems.IsValidTarget(item, character))
|
||||
{
|
||||
if (item.Repairables.All(r => item.ConditionPercentage >= r.RepairThreshold)) { continue; }
|
||||
if (item.Repairables.All(r => r.IsBelowRepairThreshold)) { continue; }
|
||||
AddTargets<AIObjectiveRepairItems, Item>(character, item);
|
||||
}
|
||||
}
|
||||
@@ -1786,7 +1823,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (isCurrentHull)
|
||||
{
|
||||
CurrentHullSafety = 0;
|
||||
CurrentHullSafety = character.NeedsAir ? 0 : 100;
|
||||
}
|
||||
return CurrentHullSafety;
|
||||
}
|
||||
@@ -1809,8 +1846,8 @@ namespace Barotrauma
|
||||
|
||||
private static float CalculateHullSafety(Hull hull, IEnumerable<Hull> visibleHulls, Character character, bool ignoreWater = false, bool ignoreOxygen = false, bool ignoreFire = false, bool ignoreEnemies = false)
|
||||
{
|
||||
if (hull == null) { return 0; }
|
||||
if (hull.LethalPressure > 0 && character.PressureProtection <= 0) { return 0; }
|
||||
if (hull == null) { return character.NeedsAir ? 0 : 100; }
|
||||
if (hull.LethalPressure > 0 && character.PressureProtection <= 0 && !character.HasAbilityFlag(AbilityFlags.ImmuneToPressure)) { return 0; }
|
||||
// Oxygen factor should be 1 with 70% oxygen or more and 0.1 when the oxygen level is 30% or lower.
|
||||
// With insufficient oxygen, the safety of the hull should be 39, all the other factors aside. So, just below the HULL_SAFETY_THRESHOLD.
|
||||
float oxygenFactor = ignoreOxygen ? 1 : MathHelper.Lerp((HULL_SAFETY_THRESHOLD - 1) / 100, 1, MathUtils.InverseLerp(HULL_LOW_OXYGEN_PERCENTAGE, 100 - HULL_LOW_OXYGEN_PERCENTAGE, hull.OxygenPercentage));
|
||||
|
||||
@@ -204,11 +204,7 @@ namespace Barotrauma
|
||||
pathFinder.ApplyPenaltyToOutsideNodes = character.Submarine != null && character.PressureProtection <= 0;
|
||||
var newPath = pathFinder.FindPath(currentPos, target, character.Submarine, "(Character: " + character.Name + ")", minGapSize, startNodeFilter, endNodeFilter, nodeFilter, checkVisibility: checkVisibility);
|
||||
bool useNewPath = needsNewPath || currentPath == null || currentPath.CurrentNode == null || character.Submarine != null && findPathTimer < -1 && Math.Abs(character.AnimController.TargetMovement.Combine()) <= 0;
|
||||
if (newPath.Unreachable || newPath.Nodes.None())
|
||||
{
|
||||
useNewPath = false;
|
||||
}
|
||||
else if (!useNewPath && currentPath != null && currentPath.CurrentNode != null)
|
||||
if (!useNewPath && currentPath != null && currentPath.CurrentNode != null && newPath.Nodes.Any() && !newPath.Unreachable)
|
||||
{
|
||||
// Check if the new path is the same as the old, in which case we just ignore it and continue using the old path (or the progress would reset).
|
||||
if (IsIdenticalPath())
|
||||
|
||||
@@ -12,20 +12,19 @@ namespace Barotrauma
|
||||
class LatchOntoAI
|
||||
{
|
||||
const float RaycastInterval = 5.0f;
|
||||
|
||||
private float raycastTimer;
|
||||
|
||||
private Structure targetWall;
|
||||
private Body targetBody;
|
||||
private Vector2 attachSurfaceNormal;
|
||||
private Submarine targetSubmarine;
|
||||
private Character targetCharacter;
|
||||
private readonly Character character;
|
||||
|
||||
public bool AttachToSub { get; private set; }
|
||||
public bool AttachToWalls { get; private set; }
|
||||
public bool AttachToCharacters { get; private set; }
|
||||
|
||||
public Submarine TargetSubmarine { get; private set; }
|
||||
public Structure TargetWall { get; private set; }
|
||||
public Character TargetCharacter { get; private set; }
|
||||
|
||||
private readonly float minDeattachSpeed, maxDeattachSpeed, maxAttachDuration, coolDown;
|
||||
private readonly float damageOnDetach, detachStun;
|
||||
private readonly bool weld;
|
||||
@@ -51,7 +50,7 @@ namespace Barotrauma
|
||||
|
||||
public bool IsAttached => AttachJoints.Count > 0;
|
||||
|
||||
public bool IsAttachedToSub => IsAttached && targetSubmarine != null && targetCharacter == null;
|
||||
public bool IsAttachedToSub => IsAttached && TargetSubmarine != null && TargetCharacter == null;
|
||||
|
||||
public LatchOntoAI(XElement element, EnemyAIController enemyAI)
|
||||
{
|
||||
@@ -93,9 +92,9 @@ namespace Barotrauma
|
||||
var sub = wall.Submarine;
|
||||
if (sub == null) { return; }
|
||||
Reset();
|
||||
targetWall = wall;
|
||||
targetSubmarine = sub;
|
||||
targetBody = targetSubmarine.PhysicsBody.FarseerBody;
|
||||
TargetWall = wall;
|
||||
TargetSubmarine = sub;
|
||||
targetBody = TargetSubmarine.PhysicsBody.FarseerBody;
|
||||
this.attachSurfaceNormal = attachSurfaceNormal;
|
||||
_attachPos = attachPos;
|
||||
}
|
||||
@@ -103,23 +102,20 @@ namespace Barotrauma
|
||||
public void SetAttachTarget(Character target)
|
||||
{
|
||||
if (!AttachToCharacters) { return; }
|
||||
if (target.Submarine != character.Submarine) { return; }
|
||||
Reset();
|
||||
targetCharacter = target;
|
||||
targetSubmarine = target.Submarine;
|
||||
TargetCharacter = target;
|
||||
targetBody = target.AnimController.Collider.FarseerBody;
|
||||
attachSurfaceNormal = Vector2.Normalize(character.WorldPosition - target.WorldPosition);
|
||||
}
|
||||
|
||||
public void Update(EnemyAIController enemyAI, float deltaTime)
|
||||
{
|
||||
if (character.Submarine != null)
|
||||
if (TargetCharacter != null && character.Submarine != TargetCharacter.Submarine ||
|
||||
character.Submarine != null && TargetSubmarine != null && TargetCharacter == null)
|
||||
{
|
||||
if (targetCharacter != null && targetCharacter.Submarine != targetSubmarine ||
|
||||
character.Submarine != null && targetSubmarine != null && targetCharacter == null)
|
||||
{
|
||||
DeattachFromBody(reset: true);
|
||||
return;
|
||||
}
|
||||
DeattachFromBody(reset: true);
|
||||
return;
|
||||
}
|
||||
if (IsAttached)
|
||||
{
|
||||
@@ -150,7 +146,7 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (targetCharacter != null)
|
||||
if (TargetCharacter != null)
|
||||
{
|
||||
if (enemyAI.AttackingLimb?.attack == null)
|
||||
{
|
||||
@@ -159,10 +155,14 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
float range = enemyAI.AttackingLimb.attack.DamageRange * 2f;
|
||||
if (Vector2.DistanceSquared(targetCharacter.WorldPosition, enemyAI.AttackingLimb.WorldPosition) > range * range)
|
||||
if (Vector2.DistanceSquared(TargetCharacter.WorldPosition, enemyAI.AttackingLimb.WorldPosition) > range * range)
|
||||
{
|
||||
DeattachFromBody(reset: true, cooldown: 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
TargetCharacter.Latchers.Add(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -176,15 +176,15 @@ namespace Barotrauma
|
||||
deattachCheckTimer -= deltaTime;
|
||||
}
|
||||
|
||||
if (targetCharacter != null)
|
||||
if (TargetCharacter != null)
|
||||
{
|
||||
// Own sim pos -> target where we are
|
||||
_attachPos = character.SimPosition;
|
||||
}
|
||||
Vector2 transformedAttachPos = _attachPos;
|
||||
if (character.Submarine == null && targetSubmarine != null)
|
||||
if (character.Submarine == null && TargetSubmarine != null)
|
||||
{
|
||||
transformedAttachPos += ConvertUnits.ToSimUnits(targetSubmarine.Position);
|
||||
transformedAttachPos += ConvertUnits.ToSimUnits(TargetSubmarine.Position);
|
||||
}
|
||||
if (transformedAttachPos != Vector2.Zero)
|
||||
{
|
||||
@@ -267,7 +267,7 @@ namespace Barotrauma
|
||||
if (enemyAI.AttackingLimb == null) { break; }
|
||||
if (targetBody == null) { break; }
|
||||
if (IsAttached && AttachJoints[0].BodyB == targetBody) { break; }
|
||||
Vector2 referencePos = targetCharacter != null ? targetCharacter.WorldPosition : ConvertUnits.ToDisplayUnits(transformedAttachPos);
|
||||
Vector2 referencePos = TargetCharacter != null ? TargetCharacter.WorldPosition : ConvertUnits.ToDisplayUnits(transformedAttachPos);
|
||||
if (Vector2.DistanceSquared(referencePos, enemyAI.AttackingLimb.WorldPosition) < enemyAI.AttackingLimb.attack.DamageRange * enemyAI.AttackingLimb.attack.DamageRange)
|
||||
{
|
||||
AttachToBody(transformedAttachPos);
|
||||
@@ -286,11 +286,11 @@ namespace Barotrauma
|
||||
deattach = true;
|
||||
attachCooldown = coolDown;
|
||||
}
|
||||
if (!deattach && targetWall != null && targetSubmarine != null)
|
||||
if (!deattach && TargetWall != null && TargetSubmarine != null)
|
||||
{
|
||||
// Deattach if the wall is broken enough where we are attached to
|
||||
int targetSection = targetWall.FindSectionIndex(attachLimb.WorldPosition, world: true, clamp: true);
|
||||
if (enemyAI.CanPassThroughHole(targetWall, targetSection))
|
||||
int targetSection = TargetWall.FindSectionIndex(attachLimb.WorldPosition, world: true, clamp: true);
|
||||
if (enemyAI.CanPassThroughHole(TargetWall, targetSection))
|
||||
{
|
||||
deattach = true;
|
||||
attachCooldown = coolDown;
|
||||
@@ -298,7 +298,7 @@ namespace Barotrauma
|
||||
if (!deattach)
|
||||
{
|
||||
// Deattach if the velocity is high
|
||||
float velocity = targetSubmarine.Velocity == Vector2.Zero ? 0.0f : targetSubmarine.Velocity.Length();
|
||||
float velocity = TargetSubmarine.Velocity == Vector2.Zero ? 0.0f : TargetSubmarine.Velocity.Length();
|
||||
deattach = velocity > maxDeattachSpeed;
|
||||
if (!deattach)
|
||||
{
|
||||
@@ -385,11 +385,8 @@ namespace Barotrauma
|
||||
} as Joint;
|
||||
|
||||
GameMain.World.Add(colliderJoint);
|
||||
AttachJoints.Add(colliderJoint);
|
||||
if (targetCharacter != null)
|
||||
{
|
||||
targetCharacter.Latchers.Add(this);
|
||||
}
|
||||
AttachJoints.Add(colliderJoint);
|
||||
TargetCharacter?.Latchers.Add(this);
|
||||
if (maxAttachDuration > 0)
|
||||
{
|
||||
deattachCheckTimer = maxAttachDuration;
|
||||
@@ -407,25 +404,19 @@ namespace Barotrauma
|
||||
{
|
||||
attachCooldown = cooldown;
|
||||
}
|
||||
TargetCharacter?.Latchers.Remove(this);
|
||||
if (reset)
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
if (targetCharacter != null)
|
||||
{
|
||||
targetCharacter.Latchers.Remove(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
if (targetCharacter != null)
|
||||
{
|
||||
targetCharacter.Latchers.Remove(this);
|
||||
}
|
||||
targetCharacter = null;
|
||||
targetWall = null;
|
||||
targetSubmarine = null;
|
||||
TargetCharacter?.Latchers.Remove(this);
|
||||
TargetCharacter = null;
|
||||
TargetWall = null;
|
||||
TargetSubmarine = null;
|
||||
targetBody = null;
|
||||
AttachPos = null;
|
||||
}
|
||||
|
||||
@@ -93,13 +93,6 @@ namespace Barotrauma
|
||||
_abandon = value;
|
||||
if (_abandon)
|
||||
{
|
||||
#if DEBUG
|
||||
if (HumanAIController.debugai && objectiveManager.IsOrder(this) && !objectiveManager.IsCurrentOrder<AIObjectiveGoTo>() && !objectiveManager.IsCurrentOrder<AIObjectiveReturn>())
|
||||
{
|
||||
// TODO: dismiss
|
||||
throw new Exception("Order abandoned!");
|
||||
}
|
||||
#endif
|
||||
OnAbandon();
|
||||
}
|
||||
}
|
||||
@@ -247,7 +240,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
protected bool IsAllowed
|
||||
public bool IsAllowed
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -271,7 +264,7 @@ namespace Barotrauma
|
||||
if (!IsAllowed)
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = !isOrder;
|
||||
Abandon = true;
|
||||
return Priority;
|
||||
}
|
||||
if (isOrder)
|
||||
|
||||
+1
-1
@@ -94,7 +94,7 @@ namespace Barotrauma
|
||||
if (item == null) { return false; }
|
||||
if (item.IgnoreByAI(character)) { return false; }
|
||||
if (!item.IsInteractable(character)) { return false; }
|
||||
if (item.SpawnedInOutpost) { return false; }
|
||||
if ((item.SpawnedInCurrentOutpost && !item.AllowStealing) == character.IsOnPlayerTeam) { return false; }
|
||||
if (item.ParentInventory != null)
|
||||
{
|
||||
if (item.Container == null)
|
||||
|
||||
+30
-34
@@ -294,10 +294,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsLoaded(ItemComponent weapon, bool checkContainedItems = true) =>
|
||||
weapon.HasRequiredContainedItems(character, addMessage: false) &&
|
||||
(!checkContainedItems || weapon.Item.OwnInventory == null || weapon.Item.OwnInventory.AllItems.Any(i => i.Condition > 0));
|
||||
|
||||
private bool TryArm()
|
||||
{
|
||||
if (character.LockHands || Enemy == null)
|
||||
@@ -325,7 +321,7 @@ namespace Barotrauma
|
||||
Weapon = null;
|
||||
continue;
|
||||
}
|
||||
if (IsLoaded(WeaponComponent, checkContainedItems: true))
|
||||
if (WeaponComponent.IsLoaded(character))
|
||||
{
|
||||
// All good, the weapon is loaded
|
||||
break;
|
||||
@@ -380,6 +376,7 @@ namespace Barotrauma
|
||||
constructor: () => new AIObjectiveGetItem(character, "weapon", objectiveManager, equip: true, checkInventory: false)
|
||||
{
|
||||
AllowStealing = HumanAIController.IsMentallyUnstable,
|
||||
EvaluateCombatPriority = false, // Use a custom formula instead
|
||||
GetItemPriority = i =>
|
||||
{
|
||||
if (Weapon != null && (i == Weapon || i.Prefab.Identifier == Weapon.Prefab.Identifier)) { return 0; }
|
||||
@@ -433,7 +430,7 @@ namespace Barotrauma
|
||||
// Not in the inventory anymore or cannot find the weapon component
|
||||
return false;
|
||||
}
|
||||
if (!IsLoaded(WeaponComponent))
|
||||
if (!WeaponComponent.IsLoaded(character))
|
||||
{
|
||||
// Try reloading (and seek ammo)
|
||||
if (!Reload(seekAmmo))
|
||||
@@ -475,7 +472,7 @@ namespace Barotrauma
|
||||
foreach (var weapon in weaponList)
|
||||
{
|
||||
float priority = weapon.CombatPriority;
|
||||
if (!IsLoaded(weapon))
|
||||
if (!weapon.IsLoaded(character))
|
||||
{
|
||||
if (weapon is RangedWeapon && enemyIsClose)
|
||||
{
|
||||
@@ -564,31 +561,6 @@ namespace Barotrauma
|
||||
}
|
||||
return weaponComponent.Item;
|
||||
|
||||
static Attack GetAttackDefinition(ItemComponent weapon)
|
||||
{
|
||||
Attack attack = null;
|
||||
if (weapon is MeleeWeapon meleeWeapon)
|
||||
{
|
||||
attack = meleeWeapon.Attack;
|
||||
}
|
||||
else if (weapon is RangedWeapon rangedWeapon)
|
||||
{
|
||||
attack = rangedWeapon.FindProjectile(triggerOnUseOnContainers: false)?.Attack;
|
||||
}
|
||||
return attack;
|
||||
}
|
||||
|
||||
static float GetLethalDamage(ItemComponent weapon)
|
||||
{
|
||||
float lethalDmg = 0;
|
||||
Attack attack = GetAttackDefinition(weapon);
|
||||
if (attack != null)
|
||||
{
|
||||
lethalDmg = attack.GetTotalDamage();
|
||||
}
|
||||
return lethalDmg;
|
||||
}
|
||||
|
||||
float ApproximateStunDamage(ItemComponent weapon, Attack attack)
|
||||
{
|
||||
// Try to reduce the priority using the actual damage values and status effects.
|
||||
@@ -628,6 +600,31 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public static float GetLethalDamage(ItemComponent weapon)
|
||||
{
|
||||
float lethalDmg = 0;
|
||||
Attack attack = GetAttackDefinition(weapon);
|
||||
if (attack != null)
|
||||
{
|
||||
lethalDmg = attack.GetTotalDamage();
|
||||
}
|
||||
return lethalDmg;
|
||||
}
|
||||
|
||||
private static Attack GetAttackDefinition(ItemComponent weapon)
|
||||
{
|
||||
Attack attack = null;
|
||||
if (weapon is MeleeWeapon meleeWeapon)
|
||||
{
|
||||
attack = meleeWeapon.Attack;
|
||||
}
|
||||
else if (weapon is RangedWeapon rangedWeapon)
|
||||
{
|
||||
attack = rangedWeapon.FindProjectile(triggerOnUseOnContainers: false)?.Attack;
|
||||
}
|
||||
return attack;
|
||||
}
|
||||
|
||||
private HashSet<ItemComponent> FindWeaponsFromInventory()
|
||||
{
|
||||
weapons.Clear();
|
||||
@@ -788,7 +785,6 @@ namespace Barotrauma
|
||||
{
|
||||
UsePathingOutside = false,
|
||||
IgnoreIfTargetDead = true,
|
||||
DialogueIdentifier = "dialogcannotreachtarget",
|
||||
TargetName = Enemy.DisplayName,
|
||||
AlwaysUseEuclideanDistance = false
|
||||
},
|
||||
@@ -812,7 +808,7 @@ namespace Barotrauma
|
||||
ItemPrefab prefab = ItemPrefab.Find(null, "handcuffs");
|
||||
if (prefab != null)
|
||||
{
|
||||
Entity.Spawner.AddToSpawnQueue(prefab, character.Inventory, onSpawned: (Item i) => i.SpawnedInOutpost = true);
|
||||
Entity.Spawner.AddToSpawnQueue(prefab, character.Inventory, onSpawned: (Item i) => i.SpawnedInCurrentOutpost = true);
|
||||
}
|
||||
}
|
||||
RemoveFollowTarget();
|
||||
|
||||
-1
@@ -144,7 +144,6 @@ namespace Barotrauma
|
||||
{
|
||||
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(container.Item, character, objectiveManager, getDivingGearIfNeeded: AllowToFindDivingGear)
|
||||
{
|
||||
DialogueIdentifier = "dialogcannotreachtarget",
|
||||
TargetName = container.Item.Name,
|
||||
AbortCondition = obj =>
|
||||
container?.Item == null || container.Item.Removed || container.Item.IsThisOrAnyContainerIgnoredByAI(character) ||
|
||||
|
||||
+9
-4
@@ -19,10 +19,15 @@ namespace Barotrauma
|
||||
private AIObjectiveContainItem getOxygen;
|
||||
private Item targetItem;
|
||||
|
||||
public static float MIN_OXYGEN = 10;
|
||||
public static string HEAVY_DIVING_GEAR = "deepdiving";
|
||||
public static string LIGHT_DIVING_GEAR = "lightdiving";
|
||||
public static string OXYGEN_SOURCE = "oxygensource";
|
||||
public const float MIN_OXYGEN = 10;
|
||||
|
||||
public const string HEAVY_DIVING_GEAR = "deepdiving";
|
||||
public const string LIGHT_DIVING_GEAR = "lightdiving";
|
||||
/// <summary>
|
||||
/// Diving gear that's suitable for wearing indoors (-> the bots don't try to unequip it when they don't need diving gear)
|
||||
/// </summary>
|
||||
public const string DIVING_GEAR_WEARABLE_INDOORS = "divinggear_wearableindoors";
|
||||
public const string OXYGEN_SOURCE = "oxygensource";
|
||||
|
||||
protected override bool CheckObjectiveSpecific() => targetItem != null && character.HasEquippedItem(targetItem, slotType: InvSlotType.OuterClothes | InvSlotType.Head);
|
||||
|
||||
|
||||
+11
-4
@@ -46,10 +46,17 @@ namespace Barotrauma
|
||||
}
|
||||
if (character.CurrentHull == null)
|
||||
{
|
||||
Priority = (objectiveManager.IsCurrentOrder<AIObjectiveGoTo>() ||
|
||||
objectiveManager.IsCurrentOrder<AIObjectiveReturn>() ||
|
||||
objectiveManager.Objectives.Any(o => o.Priority > 0 && o is AIObjectiveCombat))
|
||||
&& HumanAIController.HasDivingSuit(character) ? 0 : 100;
|
||||
if (!character.NeedsAir)
|
||||
{
|
||||
Priority = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
Priority = (objectiveManager.IsCurrentOrder<AIObjectiveGoTo>() ||
|
||||
objectiveManager.IsCurrentOrder<AIObjectiveReturn>() ||
|
||||
objectiveManager.Objectives.Any(o => o.Priority > 0 && o is AIObjectiveCombat))
|
||||
&& HumanAIController.HasDivingSuit(character) ? 0 : 100;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
+125
-59
@@ -1,6 +1,7 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
@@ -11,6 +12,7 @@ namespace Barotrauma
|
||||
public override string Identifier { get; set; } = "get item";
|
||||
|
||||
public override bool AbandonWhenCannotCompleteSubjectives => false;
|
||||
public override bool AllowMultipleInstances => true;
|
||||
|
||||
public HashSet<Item> ignoredItems = new HashSet<Item>();
|
||||
|
||||
@@ -19,7 +21,7 @@ namespace Barotrauma
|
||||
public float TargetCondition { get; set; } = 1;
|
||||
public bool AllowDangerousPressure { get; set; }
|
||||
|
||||
private readonly string[] identifiersOrTags;
|
||||
private readonly ImmutableArray<string> identifiersOrTags;
|
||||
|
||||
//if the item can't be found, spawn it in the character's inventory (used by outpost NPCs)
|
||||
private bool spawnItemIfNotFound = false;
|
||||
@@ -31,6 +33,7 @@ namespace Barotrauma
|
||||
public Item TargetItem => targetItem;
|
||||
private int currSearchIndex;
|
||||
public string[] ignoredContainerIdentifiers;
|
||||
public string[] ignoredIdentifiersOrTags;
|
||||
private AIObjectiveGoTo goToObjective;
|
||||
private float currItemPriority;
|
||||
private readonly bool checkInventory;
|
||||
@@ -51,6 +54,10 @@ namespace Barotrauma
|
||||
public bool AllowVariants { get; set; }
|
||||
public bool Equip { get; set; }
|
||||
public bool Wear { get; set; }
|
||||
public bool RequireLoaded { get; set; }
|
||||
public bool EvaluateCombatPriority { get; set; }
|
||||
public bool CheckPathForEachItem { get; set; }
|
||||
public bool SpeakIfFails { get; set; }
|
||||
|
||||
public InvSlotType? EquipSlotType { get; set; }
|
||||
|
||||
@@ -67,18 +74,41 @@ namespace Barotrauma
|
||||
public AIObjectiveGetItem(Character character, string identifierOrTag, AIObjectiveManager objectiveManager, bool equip = true, bool checkInventory = true, float priorityModifier = 1, bool spawnItemIfNotFound = false)
|
||||
: this(character, new string[] { identifierOrTag }, objectiveManager, equip, checkInventory, priorityModifier, spawnItemIfNotFound) { }
|
||||
|
||||
public AIObjectiveGetItem(Character character, string[] identifiersOrTags, AIObjectiveManager objectiveManager, bool equip = true, bool checkInventory = true, float priorityModifier = 1, bool spawnItemIfNotFound = false)
|
||||
public AIObjectiveGetItem(Character character, IEnumerable<string> identifiersOrTags, AIObjectiveManager objectiveManager, bool equip = true, bool checkInventory = true, float priorityModifier = 1, bool spawnItemIfNotFound = false)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
{
|
||||
currSearchIndex = -1;
|
||||
Equip = equip;
|
||||
this.identifiersOrTags = identifiersOrTags;
|
||||
this.spawnItemIfNotFound = spawnItemIfNotFound;
|
||||
for (int i = 0; i < identifiersOrTags.Length; i++)
|
||||
{
|
||||
identifiersOrTags[i] = identifiersOrTags[i].ToLowerInvariant();
|
||||
}
|
||||
this.checkInventory = checkInventory;
|
||||
this.identifiersOrTags = ParseGearTags(identifiersOrTags).ToImmutableArray();
|
||||
ignoredIdentifiersOrTags = ParseIgnoredTags(identifiersOrTags).ToArray();
|
||||
}
|
||||
|
||||
public static IEnumerable<string> ParseGearTags(IEnumerable<string> identifiersOrTags)
|
||||
{
|
||||
var tags = new List<string>();
|
||||
foreach (string tag in identifiersOrTags)
|
||||
{
|
||||
if (!tag.Contains('!'))
|
||||
{
|
||||
tags.Add(tag.ToLowerInvariant());
|
||||
}
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
|
||||
public static IEnumerable<string> ParseIgnoredTags(IEnumerable<string> identifiersOrTags)
|
||||
{
|
||||
var ignoredTags = new List<string>();
|
||||
foreach (string tag in identifiersOrTags)
|
||||
{
|
||||
if (tag.Contains('!'))
|
||||
{
|
||||
ignoredTags.Add(tag.Remove("!").ToLowerInvariant());
|
||||
}
|
||||
}
|
||||
return ignoredTags;
|
||||
}
|
||||
|
||||
private bool CheckInventory()
|
||||
@@ -219,6 +249,13 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!Equip)
|
||||
{
|
||||
// Try equipping and wearing the item
|
||||
Wear = true;
|
||||
Equip = true;
|
||||
return;
|
||||
}
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage($"{character.Name}: Failed to equip/move the item '{targetItem.Name}' into the character inventory. Aborting.", Color.Red);
|
||||
#endif
|
||||
@@ -243,6 +280,10 @@ namespace Barotrauma
|
||||
{
|
||||
// Try again
|
||||
ignoredItems.Add(targetItem);
|
||||
if (targetItem != moveToTarget && moveToTarget is Item item)
|
||||
{
|
||||
ignoredItems.Add(item);
|
||||
}
|
||||
ResetInternal();
|
||||
}
|
||||
else
|
||||
@@ -269,7 +310,15 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
float priority = Math.Clamp(objectiveManager.GetCurrentPriority(), 10, 100);
|
||||
bool checkPath = priority >= AIObjectiveManager.LowestOrderPriority && (objectiveManager.IsCurrentOrder<AIObjectiveFixLeaks>() || objectiveManager.CurrentOrder is AIObjectiveGoTo gotoOrder && gotoOrder.isFollowOrderObjective);
|
||||
if (!CheckPathForEachItem)
|
||||
{
|
||||
// While following the player, let's ensure that there's a valid path to the target before accepting it.
|
||||
// Otherwise it will take some time for us to find a valid item when there are multiple items that we can't reach and some that we can.
|
||||
// This is relatively expensive, so let's do this only when it significantly improves the behavior.
|
||||
// Only allow one path find call per frame.
|
||||
CheckPathForEachItem = priority >= AIObjectiveManager.LowestOrderPriority && (objectiveManager.IsCurrentOrder<AIObjectiveFixLeaks>() || objectiveManager.CurrentOrder is AIObjectiveGoTo gotoOrder && gotoOrder.isFollowOrderObjective);
|
||||
}
|
||||
bool checkPath = CheckPathForEachItem;
|
||||
bool hasCalledPathFinder = false;
|
||||
int itemsPerFrame = (int)priority;
|
||||
for (int i = 0; i < itemsPerFrame && currSearchIndex < Item.ItemList.Count - 1; i++)
|
||||
@@ -280,14 +329,20 @@ namespace Barotrauma
|
||||
if (itemSub == null) { continue; }
|
||||
Submarine mySub = character.Submarine;
|
||||
if (mySub == null) { continue; }
|
||||
if (!checkInventory)
|
||||
{
|
||||
// Ignore items in the inventory when defined not to check it.
|
||||
if (item.IsOwnedBy(character)) { continue; }
|
||||
}
|
||||
if (!AllowStealing)
|
||||
{
|
||||
if (character.TeamID == CharacterTeamType.FriendlyNPC != item.SpawnedInOutpost) { continue; }
|
||||
if (character.TeamID == CharacterTeamType.FriendlyNPC != item.SpawnedInCurrentOutpost) { continue; }
|
||||
}
|
||||
if (!CheckItem(item)) { continue; }
|
||||
if (item.Container != null)
|
||||
{
|
||||
if (item.Container.HasTag("donttakeitems")) { continue; }
|
||||
if (ignoredItems.Contains(item.Container)) { continue; }
|
||||
if (ignoredContainerIdentifiers != null)
|
||||
{
|
||||
if (ignoredContainerIdentifiers.Contains(item.ContainerIdentifier)) { continue; }
|
||||
@@ -315,17 +370,51 @@ namespace Barotrauma
|
||||
float yDist = Math.Abs(character.WorldPosition.Y - itemPos.Y);
|
||||
yDist = yDist > 100 ? yDist * 5 : 0;
|
||||
float dist = Math.Abs(character.WorldPosition.X - itemPos.X) + yDist;
|
||||
float distanceFactor = MathHelper.Lerp(1, 0, MathUtils.InverseLerp(0, 10000, dist));
|
||||
float minDistFactor = EvaluateCombatPriority ? 0.1f : 0;
|
||||
float distanceFactor = MathHelper.Lerp(1, minDistFactor, MathUtils.InverseLerp(100, 10000, dist));
|
||||
itemPriority *= distanceFactor;
|
||||
itemPriority *= item.Condition / item.MaxCondition;
|
||||
if (EvaluateCombatPriority)
|
||||
{
|
||||
var mw = item.GetComponent<MeleeWeapon>();
|
||||
var rw = item.GetComponent<RangedWeapon>();
|
||||
float combatFactor = 0;
|
||||
if (mw != null)
|
||||
{
|
||||
if (mw.CombatPriority > 0)
|
||||
{
|
||||
combatFactor = mw.CombatPriority / 100;
|
||||
}
|
||||
else
|
||||
{
|
||||
// The combat factor of items with zero combat priority is not allowed to be greater than 0.1f
|
||||
combatFactor = Math.Min(AIObjectiveCombat.GetLethalDamage(mw) / 1000, 0.1f);
|
||||
}
|
||||
}
|
||||
else if (rw != null)
|
||||
{
|
||||
if (rw.CombatPriority > 0)
|
||||
{
|
||||
combatFactor = rw.CombatPriority / 100;
|
||||
}
|
||||
else
|
||||
{
|
||||
combatFactor = Math.Min(AIObjectiveCombat.GetLethalDamage(rw) / 1000, 0.1f);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
combatFactor = Math.Min(item.Components.Sum(ic => AIObjectiveCombat.GetLethalDamage(ic)) / 1000, 0.1f);
|
||||
}
|
||||
itemPriority *= combatFactor;
|
||||
}
|
||||
else
|
||||
{
|
||||
itemPriority *= item.Condition / item.MaxCondition;
|
||||
}
|
||||
// Ignore if the item has a lower priority than the currently selected one
|
||||
if (itemPriority < currItemPriority) { continue; }
|
||||
if (!hasCalledPathFinder && PathSteering != null && checkPath)
|
||||
{
|
||||
// While following the player, let's ensure that there's a valid path to the target before accepting it.
|
||||
// Otherwise it will take some time for us to find a valid item when there are multiple items that we can't reach and some that we can.
|
||||
// This is relatively expensive, so let's do this only when it significantly improves the behavior.
|
||||
// Only allow one path find call per frame.
|
||||
hasCalledPathFinder = true;
|
||||
var path = PathSteering.PathFinder.FindPath(character.SimPosition, item.SimPosition, character.Submarine, errorMsgStr: $"AIObjectiveGetItem {character.DisplayName}", nodeFilter: node => node.Waypoint.CurrentHull != null);
|
||||
if (path.Unreachable) { continue; }
|
||||
@@ -355,7 +444,7 @@ namespace Barotrauma
|
||||
targetItem = spawnedItem;
|
||||
if (character.TeamID == CharacterTeamType.FriendlyNPC && (character.Submarine?.Info.IsOutpost ?? false))
|
||||
{
|
||||
spawnedItem.SpawnedInOutpost = true;
|
||||
spawnedItem.SpawnedInCurrentOutpost = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -365,7 +454,6 @@ namespace Barotrauma
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage($"{character.Name}: Cannot find an item with the following identifier(s) or tag(s): {string.Join(", ", identifiersOrTags)}", Color.Yellow);
|
||||
#endif
|
||||
SpeakCannotFind();
|
||||
Abandon = true;
|
||||
}
|
||||
}
|
||||
@@ -375,34 +463,19 @@ namespace Barotrauma
|
||||
protected override bool CheckObjectiveSpecific()
|
||||
{
|
||||
if (IsCompleted) { return true; }
|
||||
if (targetItem != null)
|
||||
if (targetItem == null)
|
||||
{
|
||||
if (Equip && EquipSlotType.HasValue)
|
||||
{
|
||||
return character.HasEquippedItem(targetItem, EquipSlotType.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
return character.HasItem(targetItem, Equip);
|
||||
}
|
||||
}
|
||||
else if (identifiersOrTags != null)
|
||||
{
|
||||
var matchingItem = character.Inventory.FindItem(i => CheckItem(i), recursive: true);
|
||||
if (matchingItem != null)
|
||||
{
|
||||
if (Equip && EquipSlotType.HasValue)
|
||||
{
|
||||
return character.HasEquippedItem(matchingItem, EquipSlotType.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
return !Equip || character.HasEquippedItem(matchingItem);
|
||||
}
|
||||
}
|
||||
// Not yet ready
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
if (Equip && EquipSlotType.HasValue)
|
||||
{
|
||||
return character.HasEquippedItem(targetItem, EquipSlotType.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
return character.HasItem(targetItem, Equip);
|
||||
}
|
||||
}
|
||||
|
||||
private bool CheckItem(Item item)
|
||||
@@ -410,8 +483,10 @@ namespace Barotrauma
|
||||
if (!item.IsInteractable(character)) { return false; }
|
||||
if (item.IsThisOrAnyContainerIgnoredByAI(character)) { return false; }
|
||||
if (ignoredItems.Contains(item)) { return false; };
|
||||
if (ignoredIdentifiersOrTags != null && ignoredIdentifiersOrTags.Any(id => item.prefab.Identifier == id || item.HasTag(id))) { return false; }
|
||||
if (item.Condition < TargetCondition) { return false; }
|
||||
if (ItemFilter != null && !ItemFilter(item)) { return false; }
|
||||
if (RequireLoaded && item.Components.Any(i => !i.IsLoaded(character))) { return false; }
|
||||
return identifiersOrTags.Any(id => id == item.Prefab.Identifier || item.HasTag(id) || (AllowVariants && item.Prefab.VariantOf?.Identifier == id));
|
||||
}
|
||||
|
||||
@@ -437,15 +512,20 @@ namespace Barotrauma
|
||||
protected override void OnAbandon()
|
||||
{
|
||||
base.OnAbandon();
|
||||
if (moveToTarget == null) { return; }
|
||||
if (moveToTarget != null)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage($"{character.Name}: Get item failed to reach {moveToTarget}", Color.Yellow);
|
||||
DebugConsole.NewMessage($"{character.Name}: Get item failed to reach {moveToTarget}", Color.Yellow);
|
||||
#endif
|
||||
}
|
||||
if (SpeakIfFails)
|
||||
{
|
||||
SpeakCannotFind();
|
||||
}
|
||||
}
|
||||
|
||||
private void SpeakCannotFind()
|
||||
{
|
||||
// TODO: Use the item name as the variable here.
|
||||
if (character.IsOnPlayerTeam && objectiveManager.CurrentOrder == objectiveManager.CurrentObjective)
|
||||
{
|
||||
string msg = TextManager.Get("dialogcannotfinditem", true);
|
||||
@@ -455,19 +535,5 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: remove?
|
||||
private void SpeakCannotReach()
|
||||
{
|
||||
if (character.IsOnPlayerTeam && objectiveManager.CurrentOrder == objectiveManager.CurrentObjective)
|
||||
{
|
||||
string TargetName = (moveToTarget as MapEntity)?.Name ?? (moveToTarget as Character)?.Name ?? moveToTarget.ToString();
|
||||
string msg = TargetName == null ? TextManager.Get("dialogcannotreachtarget", true) : TextManager.GetWithVariable("dialogcannotreachtarget", "[name]", TargetName, formatCapitals: !(moveToTarget is Character));
|
||||
if (msg != null)
|
||||
{
|
||||
character.Speak(msg, identifier: "dialogcannotreachtarget", minDurationBetweenSimilar: 20.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
#nullable enable
|
||||
using Barotrauma.Extensions;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class AIObjectiveGetItems : AIObjective
|
||||
{
|
||||
public override string Identifier { get; set; } = "get items";
|
||||
public override string DebugTag => $"{Identifier}";
|
||||
public override bool KeepDivingGearOn => true;
|
||||
|
||||
public bool AllowStealing { get; set; }
|
||||
public bool TakeWholeStack { get; set; }
|
||||
public bool AllowVariants { get; set; }
|
||||
public bool Equip { get; set; }
|
||||
public bool Wear { get; set; }
|
||||
public bool CheckInventory { get; set; }
|
||||
public bool EvaluateCombatPriority { get; set; }
|
||||
public bool CheckPathForEachItem { get; set; }
|
||||
public bool RequireLoaded { get; set; }
|
||||
|
||||
private readonly ImmutableArray<string> gearTags;
|
||||
private readonly string[] ignoredTags;
|
||||
private bool subObjectivesCreated;
|
||||
|
||||
public readonly HashSet<Item> achievedItems = new HashSet<Item>();
|
||||
|
||||
public AIObjectiveGetItems(Character character, AIObjectiveManager objectiveManager, IEnumerable<string> identifiersOrTags, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier)
|
||||
{
|
||||
gearTags = AIObjectiveGetItem.ParseGearTags(identifiersOrTags).ToImmutableArray();
|
||||
ignoredTags = AIObjectiveGetItem.ParseIgnoredTags(identifiersOrTags).ToArray();
|
||||
}
|
||||
|
||||
protected override bool CheckObjectiveSpecific() => subObjectivesCreated && subObjectives.None();
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
if (character.LockHands)
|
||||
{
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
if (!subObjectivesCreated)
|
||||
{
|
||||
foreach (string tag in gearTags)
|
||||
{
|
||||
AIObjectiveGetItem? getItem = null;
|
||||
TryAddSubObjective(ref getItem, () =>
|
||||
new AIObjectiveGetItem(character, tag, objectiveManager, Equip, CheckInventory)
|
||||
{
|
||||
AllowVariants = AllowVariants,
|
||||
Wear = Wear,
|
||||
TakeWholeStack = TakeWholeStack,
|
||||
AllowStealing = AllowStealing,
|
||||
ignoredIdentifiersOrTags = ignoredTags,
|
||||
CheckPathForEachItem = CheckPathForEachItem,
|
||||
RequireLoaded = RequireLoaded
|
||||
},
|
||||
onCompleted: () =>
|
||||
{
|
||||
var item = getItem?.TargetItem;
|
||||
if (item?.IsOwnedBy(character) != null)
|
||||
{
|
||||
achievedItems.Add(item);
|
||||
}
|
||||
},
|
||||
onAbandon: () =>
|
||||
{
|
||||
var item = getItem?.TargetItem;
|
||||
if (item != null)
|
||||
{
|
||||
achievedItems.Remove(item);
|
||||
}
|
||||
RemoveSubObjective(ref getItem);
|
||||
});
|
||||
}
|
||||
subObjectivesCreated = true;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
subObjectivesCreated = false;
|
||||
achievedItems.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
+34
-25
@@ -27,6 +27,7 @@ namespace Barotrauma
|
||||
public bool isFollowOrderObjective;
|
||||
public bool mimic;
|
||||
public bool SpeakIfFails { get; set; } = true;
|
||||
public bool DebugLogWhenFails { get; set; } = true;
|
||||
public bool UsePathingOutside { get; set; } = true;
|
||||
|
||||
public float extraDistanceWhileSwimming;
|
||||
@@ -61,6 +62,9 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Currently we never check the visibility (to the end node), which is actually unintentional.
|
||||
// I don't think it has caused any issues so far, so let's keep defaulting to false for now, because the less we do raycasts the better.
|
||||
// However, if there are cases where the bots attempt to go through walls (select the end node that is behind an obstacle), we should set this true.
|
||||
public bool CheckVisibility { get; set; }
|
||||
public bool IgnoreIfTargetDead { get; set; }
|
||||
public bool AllowGoingOutside { get; set; }
|
||||
@@ -77,7 +81,7 @@ namespace Barotrauma
|
||||
public override bool AllowOutsideSubmarine => AllowGoingOutside;
|
||||
public override bool AllowInAnySub => true;
|
||||
|
||||
public string DialogueIdentifier { get; set; }
|
||||
public string DialogueIdentifier { get; set; } = "dialogcannotreachtarget";
|
||||
public string TargetName { get; set; }
|
||||
|
||||
public ISpatialEntity Target { get; private set; }
|
||||
@@ -149,7 +153,10 @@ namespace Barotrauma
|
||||
private void SpeakCannotReach()
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage($"{character.Name}: Cannot reach the target: {Target}", Color.Yellow);
|
||||
if (DebugLogWhenFails)
|
||||
{
|
||||
DebugConsole.NewMessage($"{character.Name}: Cannot reach the target: {Target}", Color.Yellow);
|
||||
}
|
||||
#endif
|
||||
if (character.IsOnPlayerTeam && objectiveManager.CurrentOrder == objectiveManager.CurrentObjective && DialogueIdentifier != null && SpeakIfFails)
|
||||
{
|
||||
@@ -170,17 +177,12 @@ namespace Barotrauma
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
if (Target == character || character.SelectedBy != null && HumanAIController.IsFriendly(character.SelectedBy))
|
||||
if (cannotFollow || Target == character || character.SelectedBy != null && HumanAIController.IsFriendly(character.SelectedBy))
|
||||
{
|
||||
// Wait
|
||||
character.AIController.SteeringManager.Reset();
|
||||
return;
|
||||
}
|
||||
if (cannotFollow)
|
||||
{
|
||||
// Wait
|
||||
character.AIController.SteeringManager.Reset();
|
||||
}
|
||||
if (!character.IsClimbing)
|
||||
{
|
||||
character.SelectedConstruction = null;
|
||||
@@ -211,26 +213,33 @@ namespace Barotrauma
|
||||
}
|
||||
bool insideSteering = SteeringManager == PathSteering && PathSteering.CurrentPath != null && !PathSteering.IsPathDirty;
|
||||
bool isInside = character.CurrentHull != null;
|
||||
bool targetIsOutside = (Target != null && targetHull == null) || (insideSteering && PathSteering.CurrentPath.HasOutdoorsNodes);
|
||||
if (isInside && targetIsOutside && !AllowGoingOutside)
|
||||
bool hasOutdoorNodes = insideSteering && PathSteering.CurrentPath.HasOutdoorsNodes;
|
||||
if (isInside && hasOutdoorNodes && !AllowGoingOutside)
|
||||
{
|
||||
Abandon = true;
|
||||
}
|
||||
else if (HumanAIController.IsCurrentPathNullOrUnreachable)
|
||||
else if (HumanAIController.SteeringManager == PathSteering)
|
||||
{
|
||||
waitUntilPathUnreachable -= deltaTime;
|
||||
SteeringManager.Reset();
|
||||
if (waitUntilPathUnreachable < 0)
|
||||
if (HumanAIController.IsCurrentPathNullOrUnreachable)
|
||||
{
|
||||
SteeringManager.Reset();
|
||||
if (waitUntilPathUnreachable < 0)
|
||||
{
|
||||
waitUntilPathUnreachable = pathWaitingTime;
|
||||
if (repeat)
|
||||
{
|
||||
SpeakCannotReach();
|
||||
}
|
||||
else
|
||||
{
|
||||
Abandon = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (HumanAIController.HasValidPath(requireNonDirty: true, requireUnfinished: false))
|
||||
{
|
||||
waitUntilPathUnreachable = pathWaitingTime;
|
||||
if (repeat)
|
||||
{
|
||||
SpeakCannotReach();
|
||||
}
|
||||
else
|
||||
{
|
||||
Abandon = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!Abandon)
|
||||
@@ -238,16 +247,16 @@ namespace Barotrauma
|
||||
if (getDivingGearIfNeeded && !character.LockHands)
|
||||
{
|
||||
Character followTarget = Target as Character;
|
||||
bool needsDivingSuit = !isInside || targetIsOutside;
|
||||
bool needsDivingGear = needsDivingSuit || HumanAIController.NeedsDivingGear(targetHull, out needsDivingSuit);
|
||||
bool needsDivingSuit = (!isInside || hasOutdoorNodes) && character.NeedsAir && !character.HasAbilityFlag(AbilityFlags.ImmuneToPressure);
|
||||
bool needsDivingGear = (needsDivingSuit || HumanAIController.NeedsDivingGear(targetHull, out needsDivingSuit)) && character.NeedsAir;
|
||||
if (mimic)
|
||||
{
|
||||
if (HumanAIController.HasDivingSuit(followTarget))
|
||||
if (HumanAIController.HasDivingSuit(followTarget) && character.NeedsAir)
|
||||
{
|
||||
needsDivingGear = true;
|
||||
needsDivingSuit = true;
|
||||
}
|
||||
else if (HumanAIController.HasDivingMask(followTarget))
|
||||
else if (HumanAIController.HasDivingMask(followTarget) && character.NeedsAir)
|
||||
{
|
||||
needsDivingGear = true;
|
||||
}
|
||||
|
||||
+26
-50
@@ -323,7 +323,6 @@ namespace Barotrauma
|
||||
SortObjectives();
|
||||
}
|
||||
|
||||
private CoroutineHandle speakRoutine;
|
||||
public void SetOrder(Order order, string option, int priority, Character orderGiver, bool speak)
|
||||
{
|
||||
if (character.IsDead)
|
||||
@@ -379,6 +378,7 @@ namespace Barotrauma
|
||||
var newCurrentOrder = CreateObjective(order, option, orderGiver);
|
||||
if (newCurrentOrder != null)
|
||||
{
|
||||
newCurrentOrder.Abandoned += () => DismissSelf(order, option);
|
||||
CurrentOrders.Add(new OrderInfo(order, option, priority, newCurrentOrder));
|
||||
}
|
||||
if (!HasOrders())
|
||||
@@ -386,53 +386,12 @@ namespace Barotrauma
|
||||
// Recreate objectives, because some of them may be removed, if impossible to complete (e.g. due to path finding)
|
||||
CreateAutonomousObjectives();
|
||||
}
|
||||
else
|
||||
else if (newCurrentOrder != null)
|
||||
{
|
||||
// This should be redundant, because all the objectives are reset when they are selected as active.
|
||||
newCurrentOrder?.Reset();
|
||||
|
||||
if (speak && character.IsOnPlayerTeam)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogAffirmative"), null, 1.0f);
|
||||
//if (speakRoutine != null)
|
||||
//{
|
||||
// CoroutineManager.StopCoroutines(speakRoutine);
|
||||
//}
|
||||
//speakRoutine = CoroutineManager.InvokeAfter(() =>
|
||||
//{
|
||||
// if (GameMain.GameSession == null || Level.Loaded == null) { return; }
|
||||
// if (newCurrentOrder != null && character.SpeechImpediment < 100.0f)
|
||||
// {
|
||||
// if (newCurrentOrder is AIObjectiveRepairItems repairItems && repairItems.Targets.None())
|
||||
// {
|
||||
// character.Speak(TextManager.Get("DialogNoRepairTargets"), null, 3.0f, "norepairtargets");
|
||||
// }
|
||||
// else if (newCurrentOrder is AIObjectiveChargeBatteries chargeBatteries && chargeBatteries.Targets.None())
|
||||
// {
|
||||
// character.Speak(TextManager.Get("DialogNoBatteries"), null, 3.0f, "nobatteries");
|
||||
// }
|
||||
// else if (newCurrentOrder is AIObjectiveExtinguishFires extinguishFires && extinguishFires.Targets.None())
|
||||
// {
|
||||
// character.Speak(TextManager.Get("DialogNoFire"), null, 3.0f, "nofire");
|
||||
// }
|
||||
// else if (newCurrentOrder is AIObjectiveFixLeaks fixLeaks && fixLeaks.Targets.None())
|
||||
// {
|
||||
// character.Speak(TextManager.Get("DialogNoLeaks"), null, 3.0f, "noleaks");
|
||||
// }
|
||||
// else if (newCurrentOrder is AIObjectiveFightIntruders fightIntruders && fightIntruders.Targets.None())
|
||||
// {
|
||||
// character.Speak(TextManager.Get("DialogNoEnemies"), null, 3.0f, "noenemies");
|
||||
// }
|
||||
// else if (newCurrentOrder is AIObjectiveRescueAll rescueAll && rescueAll.Targets.None())
|
||||
// {
|
||||
// character.Speak(TextManager.Get("DialogNoRescueTargets"), null, 3.0f, "norescuetargets");
|
||||
// }
|
||||
// else if (newCurrentOrder is AIObjectivePumpWater pumpWater && pumpWater.Targets.None())
|
||||
// {
|
||||
// character.Speak(TextManager.Get("DialogNoPumps"), null, 3.0f, "nopumps");
|
||||
// }
|
||||
// }
|
||||
//}, 3);
|
||||
string msg = newCurrentOrder.IsAllowed ? TextManager.Get("DialogAffirmative") : TextManager.Get("DialogNegative");
|
||||
character.Speak(msg, delay: 1.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -465,7 +424,6 @@ namespace Barotrauma
|
||||
break;
|
||||
case "return":
|
||||
newObjective = new AIObjectiveReturn(character, orderGiver, this, priorityModifier: priorityModifier);
|
||||
newObjective.Abandoned += () => DismissSelf(order, option);
|
||||
newObjective.Completed += () => DismissSelf(order, option);
|
||||
break;
|
||||
case "fixleaks":
|
||||
@@ -491,12 +449,10 @@ namespace Barotrauma
|
||||
if (!order.TargetItemComponent.Item.IsInteractable(character)) { return null; }
|
||||
newObjective = new AIObjectiveOperateItem(targetPump, character, this, option, false, priorityModifier: priorityModifier)
|
||||
{
|
||||
IsLoop = true,
|
||||
IsLoop = false,
|
||||
Override = orderGiver != null && orderGiver.IsCommanding
|
||||
};
|
||||
// ItemComponent.AIOperate() returns false by default -> We'd have to set IsLoop = false and implement a custom override of AIOperate for the Pump.cs,
|
||||
// if we want that the bot just switches the pump on/off and continues doing something else.
|
||||
// If we want that the bot does the objective and then forgets about it, I think we could do the same plus dismiss when the bot is done.
|
||||
newObjective.Completed += () => DismissSelf(order, option);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -566,6 +522,26 @@ namespace Barotrauma
|
||||
case "escapehandcuffs":
|
||||
newObjective = new AIObjectiveEscapeHandcuffs(character, this, priorityModifier: priorityModifier);
|
||||
break;
|
||||
case "prepareforexpedition":
|
||||
newObjective = new AIObjectivePrepare(character, this, order.TargetItems)
|
||||
{
|
||||
KeepActiveWhenReady = true,
|
||||
CheckInventory = true,
|
||||
Equip = false,
|
||||
FindAllItems = true
|
||||
};
|
||||
break;
|
||||
case "findweapon":
|
||||
newObjective = new AIObjectivePrepare(character, this, order.TargetItems)
|
||||
{
|
||||
KeepActiveWhenReady = false,
|
||||
CheckInventory = false,
|
||||
Equip = true,
|
||||
EvaluateCombatPriority = true,
|
||||
FindAllItems = false
|
||||
};
|
||||
newObjective.Completed += () => DismissSelf(order, option);
|
||||
break;
|
||||
default:
|
||||
if (order.TargetItemComponent == null) { return null; }
|
||||
if (!order.TargetItemComponent.Item.IsInteractable(character)) { return null; }
|
||||
|
||||
-1
@@ -213,7 +213,6 @@ namespace Barotrauma
|
||||
{
|
||||
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(target.Item, character, objectiveManager, closeEnough: 50)
|
||||
{
|
||||
DialogueIdentifier = "dialogcannotreachtarget",
|
||||
TargetName = target.Item.Name,
|
||||
endNodeFilter = node => node.Waypoint.Ladders == null
|
||||
},
|
||||
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
#nullable enable
|
||||
using Barotrauma.Extensions;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class AIObjectivePrepare : AIObjective
|
||||
{
|
||||
public override string Identifier { get; set; } = "prepare";
|
||||
public override string DebugTag => $"{Identifier}";
|
||||
public override bool KeepDivingGearOn => true;
|
||||
|
||||
private AIObjectiveGetItem? getSingleItemObjective;
|
||||
private AIObjectiveGetItems? getMultipleItemsObjective;
|
||||
private bool subObjectivesCreated;
|
||||
private readonly ImmutableArray<string> gearTags;
|
||||
private readonly HashSet<Item> items = new HashSet<Item>();
|
||||
public bool KeepActiveWhenReady { get; set; }
|
||||
public bool CheckInventory { get; set; }
|
||||
public bool FindAllItems { get; set; }
|
||||
public bool Equip { get; set; }
|
||||
public bool EvaluateCombatPriority { get; set; }
|
||||
|
||||
private AIObjective? GetSubObjective() => getSingleItemObjective ?? getMultipleItemsObjective as AIObjective;
|
||||
|
||||
public AIObjectivePrepare(Character character, AIObjectiveManager objectiveManager, IEnumerable<string> items, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
{
|
||||
gearTags = items.ToImmutableArray();
|
||||
}
|
||||
|
||||
protected override bool CheckObjectiveSpecific() => IsCompleted;
|
||||
|
||||
protected override float GetPriority()
|
||||
{
|
||||
if (!IsAllowed)
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = true;
|
||||
return Priority;
|
||||
}
|
||||
Priority = objectiveManager.GetOrderPriority(this);
|
||||
var subObjective = GetSubObjective();
|
||||
if (subObjective != null && subObjective.IsCompleted)
|
||||
{
|
||||
Priority = 0;
|
||||
items.RemoveWhere(i => i == null || i.Removed || !i.IsOwnedBy(character));
|
||||
if (items.None())
|
||||
{
|
||||
Abandon = true;
|
||||
|
||||
}
|
||||
else if (items.Any(i => i.Components.Any(i => !i.IsLoaded(character))))
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
}
|
||||
return Priority;
|
||||
}
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
if (character.LockHands)
|
||||
{
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
if (!subObjectivesCreated)
|
||||
{
|
||||
if (FindAllItems)
|
||||
{
|
||||
if (!TryAddSubObjective(ref getMultipleItemsObjective, () => new AIObjectiveGetItems(character, objectiveManager, gearTags)
|
||||
{
|
||||
CheckInventory = CheckInventory,
|
||||
Equip = Equip,
|
||||
EvaluateCombatPriority = EvaluateCombatPriority,
|
||||
RequireLoaded = true
|
||||
},
|
||||
onCompleted: () =>
|
||||
{
|
||||
if (KeepActiveWhenReady)
|
||||
{
|
||||
if (getMultipleItemsObjective != null)
|
||||
{
|
||||
foreach (var item in getMultipleItemsObjective.achievedItems)
|
||||
{
|
||||
if (item?.IsOwnedBy(character) != null)
|
||||
{
|
||||
items.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
IsCompleted = true;
|
||||
}
|
||||
},
|
||||
onAbandon: () => Abandon = true))
|
||||
{
|
||||
Abandon = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!TryAddSubObjective(ref getSingleItemObjective, () => new AIObjectiveGetItem(character, gearTags, objectiveManager, equip: Equip, checkInventory: CheckInventory)
|
||||
{
|
||||
EvaluateCombatPriority = EvaluateCombatPriority,
|
||||
SpeakIfFails = true,
|
||||
RequireLoaded = true
|
||||
},
|
||||
onCompleted: () =>
|
||||
{
|
||||
if (KeepActiveWhenReady)
|
||||
{
|
||||
if (getSingleItemObjective != null)
|
||||
{
|
||||
var item = getSingleItemObjective?.TargetItem;
|
||||
if (item?.IsOwnedBy(character) != null)
|
||||
{
|
||||
items.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
IsCompleted = true;
|
||||
}
|
||||
},
|
||||
onAbandon: () => Abandon = true))
|
||||
{
|
||||
Abandon = true;
|
||||
}
|
||||
}
|
||||
subObjectivesCreated = true;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
items.Clear();
|
||||
subObjectivesCreated = false;
|
||||
RemoveSubObjective(ref getMultipleItemsObjective);
|
||||
RemoveSubObjective(ref getSingleItemObjective);
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-3
@@ -107,7 +107,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (RelatedItem requiredItem in kvp.Value)
|
||||
{
|
||||
var getItemObjective = new AIObjectiveGetItem(character, requiredItem.Identifiers, objectiveManager, true)
|
||||
var getItemObjective = new AIObjectiveGetItem(character, requiredItem.Identifiers, objectiveManager, equip: true)
|
||||
{
|
||||
AllowVariants = requiredItem.AllowVariants
|
||||
};
|
||||
@@ -219,8 +219,7 @@ namespace Barotrauma
|
||||
{
|
||||
// Don't stop in ladders, because we can't interact with other items while holding the ladders.
|
||||
endNodeFilter = node => node.Waypoint.Ladders == null,
|
||||
// Allow repairing hatches and airlock doors.
|
||||
AllowGoingOutside = HumanAIController.ObjectiveManager.IsCurrentOrder<AIObjectiveRepairItems>() && Item.GetComponent<Door>() != null
|
||||
TargetName = Item.Name
|
||||
};
|
||||
if (repairTool != null)
|
||||
{
|
||||
|
||||
+1
-2
@@ -91,8 +91,7 @@ namespace Barotrauma
|
||||
|
||||
public static bool NearlyFullCondition(Item item)
|
||||
{
|
||||
float condition = item.ConditionPercentage;
|
||||
return item.Repairables.All(r => condition >= r.RepairThreshold);
|
||||
return item.Repairables.All(r => !r.IsBelowRepairThreshold);
|
||||
}
|
||||
|
||||
protected override float TargetEvaluation()
|
||||
|
||||
+24
-1
@@ -319,9 +319,32 @@ namespace Barotrauma
|
||||
{
|
||||
itemListStr = itemNameList[0];
|
||||
}
|
||||
else if (itemNameList.Count == 2)
|
||||
{
|
||||
//[treatment1] or [treatment2]
|
||||
itemListStr = TextManager.GetWithVariables(
|
||||
"DialogRequiredTreatmentOptionsLast",
|
||||
new string[] { "[treatment1]", "[treatment2]" },
|
||||
new string[] { itemNameList[0], itemNameList[1] });
|
||||
}
|
||||
else
|
||||
{
|
||||
itemListStr = string.Join(" or ", string.Join(", ", itemNameList.Take(itemNameList.Count - 1)), itemNameList.Last());
|
||||
//[treatment1], [treatment2], [treatment3] ... or [treatmentx]
|
||||
itemListStr = TextManager.GetWithVariables(
|
||||
"DialogRequiredTreatmentOptionsFirst",
|
||||
new string[] { "[treatment1]", "[treatment2]" },
|
||||
new string[] { itemNameList[0], itemNameList[1] });
|
||||
for (int i = 2; i < itemNameList.Count - 1; i++)
|
||||
{
|
||||
itemListStr = TextManager.GetWithVariables(
|
||||
"DialogRequiredTreatmentOptionsFirst",
|
||||
new string[] { "[treatment1]", "[treatment2]" },
|
||||
new string[] { itemListStr, itemNameList[i] });
|
||||
}
|
||||
itemListStr = TextManager.GetWithVariables(
|
||||
"DialogRequiredTreatmentOptionsLast",
|
||||
new string[] { "[treatment1]", "[treatment2]" },
|
||||
new string[] { itemListStr, itemNameList.Last() });
|
||||
}
|
||||
if (targetCharacter != character && character.IsOnPlayerTeam)
|
||||
{
|
||||
|
||||
+1
@@ -79,6 +79,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (target == null || target.IsDead || target.Removed) { return false; }
|
||||
if (target.IsInstigator) { return false; }
|
||||
if (target.IsPet) { return false; }
|
||||
if (!HumanAIController.IsFriendly(character, target, onlySameTeam: true)) { return false; }
|
||||
if (character.AIController is HumanAIController humanAI)
|
||||
{
|
||||
|
||||
@@ -170,6 +170,7 @@ namespace Barotrauma
|
||||
public bool HasOptions => (IsPrefab ? Options : Prefab.Options).Length > 1;
|
||||
public bool IsPrefab { get; private set; }
|
||||
public readonly bool MustManuallyAssign;
|
||||
public readonly bool AutoDismiss;
|
||||
|
||||
public readonly OrderTarget TargetPosition;
|
||||
|
||||
@@ -363,6 +364,7 @@ namespace Barotrauma
|
||||
MustManuallyAssign = orderElement.GetAttributeBool("mustmanuallyassign", false);
|
||||
IsIgnoreOrder = Identifier == "ignorethis" || Identifier == "unignorethis";
|
||||
DrawIconWhenContained = orderElement.GetAttributeBool("displayiconwhencontained", false);
|
||||
AutoDismiss = orderElement.GetAttributeBool("autodismiss", Category == OrderCategory.Movement);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
+46
-14
@@ -1048,7 +1048,8 @@ namespace Barotrauma
|
||||
|
||||
void UpdateClimbing()
|
||||
{
|
||||
if (character.SelectedConstruction == null || character.SelectedConstruction.GetComponent<Ladder>() == null || character.IsIncapacitated)
|
||||
var ladder = character.SelectedConstruction?.GetComponent<Ladder>();
|
||||
if (ladder == null || character.IsIncapacitated)
|
||||
{
|
||||
Anim = Animation.None;
|
||||
return;
|
||||
@@ -1075,22 +1076,29 @@ namespace Barotrauma
|
||||
if (leftHand == null || rightHand == null || head == null || torso == null) { return; }
|
||||
|
||||
Vector2 ladderSimPos = ConvertUnits.ToSimUnits(
|
||||
character.SelectedConstruction.Rect.X + character.SelectedConstruction.Rect.Width / 2.0f,
|
||||
character.SelectedConstruction.Rect.Y);
|
||||
ladder.Item.Rect.X + ladder.Item.Rect.Width / 2.0f,
|
||||
ladder.Item.Rect.Y);
|
||||
|
||||
Vector2 ladderSimSize = ConvertUnits.ToSimUnits(character.SelectedConstruction.Rect.Size.ToVector2());
|
||||
Vector2 ladderSimSize = ConvertUnits.ToSimUnits(ladder.Item.Rect.Size.ToVector2());
|
||||
|
||||
float lowestLadderSimPos = ladderSimPos.Y - ladderSimPos.Y;
|
||||
var lowestNearbyLadder = GetLowestNearbyLadder(ladder);
|
||||
if (lowestNearbyLadder != null && lowestNearbyLadder != ladder)
|
||||
{
|
||||
ladderSimSize.Y = ConvertUnits.ToSimUnits(ladder.Item.WorldRect.Y - (lowestNearbyLadder.Item.WorldRect.Y - lowestNearbyLadder.Item.Rect.Size.Y));
|
||||
}
|
||||
|
||||
float stepHeight = ConvertUnits.ToSimUnits(30.0f);
|
||||
|
||||
if (currentHull == null && character.SelectedConstruction.Submarine != null)
|
||||
if (currentHull == null && ladder.Item.Submarine != null)
|
||||
{
|
||||
ladderSimPos += character.SelectedConstruction.Submarine.SimPosition;
|
||||
ladderSimPos += ladder.Item.Submarine.SimPosition;
|
||||
}
|
||||
else if (currentHull?.Submarine != null && currentHull.Submarine != character.SelectedConstruction.Submarine && character.SelectedConstruction.Submarine != null)
|
||||
else if (currentHull?.Submarine != null && currentHull.Submarine != ladder.Item.Submarine && ladder.Item.Submarine != null)
|
||||
{
|
||||
ladderSimPos += character.SelectedConstruction.Submarine.SimPosition - currentHull.Submarine.SimPosition;
|
||||
ladderSimPos += ladder.Item.Submarine.SimPosition - currentHull.Submarine.SimPosition;
|
||||
}
|
||||
else if (currentHull?.Submarine != null && character.SelectedConstruction.Submarine == null)
|
||||
else if (currentHull?.Submarine != null && ladder.Item.Submarine == null)
|
||||
{
|
||||
ladderSimPos -= currentHull.Submarine.SimPosition;
|
||||
}
|
||||
@@ -1174,25 +1182,35 @@ namespace Barotrauma
|
||||
float movementFactor = (handPos.Y / stepHeight) * (float)Math.PI;
|
||||
movementFactor = 0.8f + (float)Math.Abs(Math.Sin(movementFactor));
|
||||
|
||||
Vector2 subSpeed = currentHull != null || character.SelectedConstruction.Submarine == null
|
||||
? Vector2.Zero : character.SelectedConstruction.Submarine.Velocity;
|
||||
Vector2 subSpeed = currentHull != null || ladder.Item.Submarine == null
|
||||
? Vector2.Zero : ladder.Item.Submarine.Velocity;
|
||||
|
||||
Vector2 climbForce = new Vector2(0.0f, movement.Y + 0.3f) * movementFactor;
|
||||
//reached the top of the ladders -> can't go further up
|
||||
if (character.SimPosition.Y > ladderSimPos.Y) { climbForce.Y = Math.Min(0.0f, climbForce.Y); }
|
||||
//reached the bottom -> can't go further down
|
||||
float minHeightFromFloor = ColliderHeightFromFloor / 2 + Collider.height;
|
||||
if (floorFixture != null &&
|
||||
!floorFixture.CollisionCategories.HasFlag(Physics.CollisionStairs) &&
|
||||
!floorFixture.CollisionCategories.HasFlag(Physics.CollisionPlatform) &&
|
||||
character.SimPosition.Y < standOnFloorY + minHeightFromFloor)
|
||||
{
|
||||
climbForce.Y = MathHelper.Clamp((standOnFloorY + minHeightFromFloor - character.SimPosition.Y) * 5.0f, climbForce.Y, 1.0f);
|
||||
}
|
||||
|
||||
//apply forces to the collider to move the Character up/down
|
||||
Collider.ApplyForce((climbForce * 20.0f + subSpeed * 50.0f) * Collider.Mass);
|
||||
float movementMultiplier = targetMovement.Y < 0 ? 0 : 1;
|
||||
head.body.SmoothRotate(MathHelper.PiOver4 * movementMultiplier * Dir, WalkParams.HeadTorque);
|
||||
|
||||
if (!character.SelectedConstruction.Prefab.Triggers.Any())
|
||||
if (!ladder.Item.Prefab.Triggers.Any())
|
||||
{
|
||||
character.SelectedConstruction = null;
|
||||
return;
|
||||
}
|
||||
|
||||
Rectangle trigger = character.SelectedConstruction.Prefab.Triggers.FirstOrDefault();
|
||||
trigger = character.SelectedConstruction.TransformTrigger(trigger);
|
||||
Rectangle trigger = ladder.Item.Prefab.Triggers.FirstOrDefault();
|
||||
trigger = ladder.Item.TransformTrigger(trigger);
|
||||
|
||||
bool isRemote = false;
|
||||
bool isClimbing = true;
|
||||
@@ -1221,6 +1239,19 @@ namespace Barotrauma
|
||||
character.SelectedConstruction = null;
|
||||
IgnorePlatforms = false;
|
||||
}
|
||||
|
||||
Ladder GetLowestNearbyLadder(Ladder currentLadder, float threshold = 16.0f)
|
||||
{
|
||||
foreach (Ladder ladder in Ladder.List)
|
||||
{
|
||||
if (ladder == currentLadder || !ladder.Item.IsInteractable(character)) { continue; }
|
||||
if (Math.Abs(ladder.Item.WorldPosition.X - currentLadder.Item.WorldPosition.X) > threshold) { continue; }
|
||||
if (ladder.Item.WorldPosition.Y > currentLadder.Item.WorldPosition.Y) { continue; }
|
||||
if ((currentLadder.Item.WorldRect.Y - currentLadder.Item.Rect.Height) - ladder.Item.WorldRect.Y > threshold) { continue; }
|
||||
return ladder;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateDying(float deltaTime)
|
||||
@@ -1576,6 +1607,7 @@ namespace Barotrauma
|
||||
|
||||
Vector2 shoulderPos = rightShoulder.WorldAnchorA;
|
||||
Vector2 dragDir = inWater ? Vector2.Normalize(targetLimb.SimPosition - shoulderPos) : Vector2.UnitY;
|
||||
if (!MathUtils.IsValid(dragDir)) { dragDir = Vector2.UnitY; }
|
||||
|
||||
targetAnchor = shoulderPos - dragDir * ConvertUnits.ToSimUnits(upperArmLength + forearmLength);
|
||||
targetForce = 200.0f;
|
||||
|
||||
@@ -121,6 +121,7 @@ namespace Barotrauma
|
||||
protected Vector2 overrideTargetMovement;
|
||||
|
||||
protected float floorY, standOnFloorY;
|
||||
protected Fixture floorFixture;
|
||||
protected Vector2 floorNormal = Vector2.UnitY;
|
||||
protected float surfaceY;
|
||||
|
||||
@@ -488,6 +489,7 @@ namespace Barotrauma
|
||||
limbDictionary = new Dictionary<LimbType, Limb>();
|
||||
limbs = new Limb[RagdollParams.Limbs.Count];
|
||||
RagdollParams.Limbs.ForEach(l => AddLimb(l));
|
||||
if (limbs.Contains(null)) { return; }
|
||||
SetupDrawOrder();
|
||||
}
|
||||
|
||||
@@ -548,19 +550,23 @@ namespace Barotrauma
|
||||
byte ID = Convert.ToByte(limbParams.ID);
|
||||
Limb limb = new Limb(this, character, limbParams);
|
||||
limb.body.FarseerBody.OnCollision += OnLimbCollision;
|
||||
if (ID >= Limbs.Length)
|
||||
{
|
||||
throw new Exception($"Failed to add a limb to the character \"{Character?.ConfigPath ?? "null"}\" (limb index {ID} out of bounds). The ragdoll file may be configured incorrectly.");
|
||||
}
|
||||
Limbs[ID] = limb;
|
||||
Mass += limb.Mass;
|
||||
if (!limbDictionary.ContainsKey(limb.type)) limbDictionary.Add(limb.type, limb);
|
||||
if (!limbDictionary.ContainsKey(limb.type)) { limbDictionary.Add(limb.type, limb); }
|
||||
}
|
||||
|
||||
public void AddLimb(Limb limb)
|
||||
{
|
||||
if (Limbs.Contains(limb)) return;
|
||||
if (Limbs.Contains(limb)) { return; }
|
||||
limb.body.FarseerBody.OnCollision += OnLimbCollision;
|
||||
Array.Resize(ref limbs, Limbs.Length + 1);
|
||||
Limbs[Limbs.Length - 1] = limb;
|
||||
Mass += limb.Mass;
|
||||
if (!limbDictionary.ContainsKey(limb.type)) limbDictionary.Add(limb.type, limb);
|
||||
if (!limbDictionary.ContainsKey(limb.type)) { limbDictionary.Add(limb.type, limb); }
|
||||
SetupDrawOrder();
|
||||
}
|
||||
|
||||
@@ -923,8 +929,8 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
Hull newHull = Hull.FindHull(findPos, currentHull);
|
||||
|
||||
if (newHull == currentHull) return;
|
||||
|
||||
if (newHull == currentHull) { return; }
|
||||
|
||||
if (!CanEnterSubmarine || (character.AIController != null && !character.AIController.CanEnterSubmarine))
|
||||
{
|
||||
@@ -957,16 +963,16 @@ namespace Barotrauma
|
||||
if (newHull?.Submarine == null && currentHull?.Submarine != null)
|
||||
{
|
||||
//don't teleport out yet if the character is going through a gap
|
||||
if (Gap.FindAdjacent(currentHull.ConnectedGaps, findPos, 150.0f) != null) { return; }
|
||||
if (Gap.FindAdjacent(Gap.GapList.Where(g => g.Submarine == currentHull.Submarine), findPos, 150.0f) != null) { return; }
|
||||
if (Limbs.Any(l => Gap.FindAdjacent(currentHull.ConnectedGaps, l.WorldPosition, ConvertUnits.ToDisplayUnits(l.body.GetSize().Combine())) != null)) { return; }
|
||||
character.MemLocalState?.Clear();
|
||||
Teleport(ConvertUnits.ToSimUnits(currentHull.Submarine.Position), currentHull.Submarine.Velocity);
|
||||
Teleport(ConvertUnits.ToSimUnits(currentHull.Submarine.Position), currentHull.Submarine.Velocity, detachProjectiles: false);
|
||||
}
|
||||
//out -> in
|
||||
else if (currentHull == null && newHull.Submarine != null)
|
||||
{
|
||||
character.MemLocalState?.Clear();
|
||||
Teleport(-ConvertUnits.ToSimUnits(newHull.Submarine.Position), -newHull.Submarine.Velocity);
|
||||
Teleport(-ConvertUnits.ToSimUnits(newHull.Submarine.Position), -newHull.Submarine.Velocity, detachProjectiles: false);
|
||||
}
|
||||
//from one sub to another
|
||||
else if (newHull != null && currentHull != null && newHull.Submarine != currentHull.Submarine)
|
||||
@@ -974,13 +980,13 @@ namespace Barotrauma
|
||||
character.MemLocalState?.Clear();
|
||||
Vector2 newSubPos = newHull.Submarine == null ? Vector2.Zero : newHull.Submarine.Position;
|
||||
Vector2 prevSubPos = currentHull.Submarine == null ? Vector2.Zero : currentHull.Submarine.Position;
|
||||
|
||||
Teleport(ConvertUnits.ToSimUnits(prevSubPos - newSubPos), Vector2.Zero);
|
||||
Teleport(ConvertUnits.ToSimUnits(prevSubPos - newSubPos), Vector2.Zero, detachProjectiles: false);
|
||||
}
|
||||
}
|
||||
|
||||
CurrentHull = newHull;
|
||||
character.Submarine = currentHull?.Submarine;
|
||||
character.AttachedProjectiles.ForEach(p => p?.Item?.UpdateTransform());
|
||||
}
|
||||
|
||||
private void PreventOutsideCollision()
|
||||
@@ -1013,7 +1019,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public void Teleport(Vector2 moveAmount, Vector2 velocityChange)
|
||||
public void Teleport(Vector2 moveAmount, Vector2 velocityChange, bool detachProjectiles = true)
|
||||
{
|
||||
foreach (Limb limb in Limbs)
|
||||
{
|
||||
@@ -1036,7 +1042,7 @@ namespace Barotrauma
|
||||
|
||||
character.DisableImpactDamageTimer = 0.25f;
|
||||
|
||||
SetPosition(Collider.SimPosition + moveAmount);
|
||||
SetPosition(Collider.SimPosition + moveAmount, detachProjectiles: detachProjectiles);
|
||||
character.CursorPosition += moveAmount;
|
||||
|
||||
Collider?.UpdateDrawPosition();
|
||||
@@ -1500,6 +1506,7 @@ namespace Barotrauma
|
||||
{
|
||||
onGround = false;
|
||||
Stairs = null;
|
||||
floorFixture = null;
|
||||
Vector2 rayStart = simPosition;
|
||||
float height = ColliderHeightFromFloor;
|
||||
if (HeadPosition.HasValue && MathUtils.IsValid(HeadPosition.Value)) { height = Math.Max(height, HeadPosition.Value); }
|
||||
@@ -1572,6 +1579,7 @@ namespace Barotrauma
|
||||
|
||||
if (standOnFloorFixture != null && !IsHanging)
|
||||
{
|
||||
floorFixture = standOnFloorFixture;
|
||||
standOnFloorY = rayStart.Y + (rayEnd.Y - rayStart.Y) * standOnFloorFraction;
|
||||
if (rayStart.Y - standOnFloorY < Collider.height * 0.5f + Collider.radius + ColliderHeightFromFloor * 1.2f)
|
||||
{
|
||||
@@ -1612,7 +1620,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public void SetPosition(Vector2 simPosition, bool lerp = false, bool ignorePlatforms = true, bool forceMainLimbToCollider = false)
|
||||
public void SetPosition(Vector2 simPosition, bool lerp = false, bool ignorePlatforms = true, bool forceMainLimbToCollider = false, bool detachProjectiles = true)
|
||||
{
|
||||
if (!MathUtils.IsValid(simPosition))
|
||||
{
|
||||
@@ -1625,13 +1633,28 @@ namespace Barotrauma
|
||||
}
|
||||
if (MainLimb == null) { return; }
|
||||
|
||||
// A Work-around for an issue with teleporting the characters:
|
||||
// Detach every latcher when either one of the latchers or the target is teleported,
|
||||
// because otherwise all the characters are teleported to invalid positions.
|
||||
if (Character.AIController is EnemyAIController enemyAI && enemyAI.LatchOntoAI != null && enemyAI.LatchOntoAI.IsAttached)
|
||||
{
|
||||
var target = enemyAI.LatchOntoAI.TargetCharacter;
|
||||
if (target != null)
|
||||
{
|
||||
target.Latchers.ForEachMod(l => l?.DeattachFromBody(reset: true));
|
||||
target.Latchers.Clear();
|
||||
}
|
||||
enemyAI.LatchOntoAI.DeattachFromBody(reset: true);
|
||||
}
|
||||
Character.Latchers.ForEachMod(l => l.DeattachFromBody(reset: true));
|
||||
Character.Latchers.ForEachMod(l => l?.DeattachFromBody(reset: true));
|
||||
Character.Latchers.Clear();
|
||||
|
||||
if (detachProjectiles)
|
||||
{
|
||||
character.AttachedProjectiles.ForEachMod(p => p?.Unstick());
|
||||
character.AttachedProjectiles.Clear();
|
||||
}
|
||||
|
||||
Vector2 limbMoveAmount = forceMainLimbToCollider ? simPosition - MainLimb.SimPosition : simPosition - Collider.SimPosition;
|
||||
if (lerp)
|
||||
{
|
||||
@@ -1712,7 +1735,7 @@ namespace Barotrauma
|
||||
if (distSqrd > resetDist * resetDist)
|
||||
{
|
||||
//ragdoll way too far, reset position
|
||||
SetPosition(Collider.SimPosition, true, forceMainLimbToCollider: true);
|
||||
SetPosition(Collider.SimPosition, lerp: true, forceMainLimbToCollider: true);
|
||||
}
|
||||
if (distSqrd > allowedDist * allowedDist)
|
||||
{
|
||||
@@ -1732,7 +1755,7 @@ namespace Barotrauma
|
||||
else if (collisionsDisabled)
|
||||
{
|
||||
//set the position of the ragdoll to make sure limbs don't get stuck inside walls when re-enabling collisions
|
||||
SetPosition(Collider.SimPosition, true);
|
||||
SetPosition(Collider.SimPosition, lerp: true);
|
||||
collisionsDisabled = false;
|
||||
//force collision categories to be updated
|
||||
prevCollisionCategory = Category.None;
|
||||
|
||||
@@ -130,6 +130,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
public readonly HashSet<LatchOntoAI> Latchers = new HashSet<LatchOntoAI>();
|
||||
public readonly HashSet<Projectile> AttachedProjectiles = new HashSet<Projectile>();
|
||||
|
||||
protected readonly Dictionary<string, ActiveTeamChange> activeTeamChanges = new Dictionary<string, ActiveTeamChange>();
|
||||
protected ActiveTeamChange currentTeamChange;
|
||||
@@ -265,7 +266,7 @@ namespace Barotrauma
|
||||
private CharacterPrefab prefab;
|
||||
|
||||
public readonly CharacterParams Params;
|
||||
public string SpeciesName => Params.SpeciesName;
|
||||
public string SpeciesName => Params?.SpeciesName ?? "null";
|
||||
public string Group => Params.Group;
|
||||
public bool IsHumanoid => Params.Humanoid;
|
||||
public bool IsHusk => Params.Husk;
|
||||
@@ -2640,7 +2641,7 @@ namespace Barotrauma
|
||||
ApplyStatusEffects(ActionType.Always, deltaTime);
|
||||
|
||||
PreviousHull = CurrentHull;
|
||||
CurrentHull = Hull.FindHull(WorldPosition, CurrentHull, true);
|
||||
CurrentHull = Hull.FindHull(WorldPosition, CurrentHull, useWorldCoordinates: true);
|
||||
|
||||
speechBubbleTimer = Math.Max(0.0f, speechBubbleTimer - deltaTime);
|
||||
|
||||
@@ -2710,7 +2711,7 @@ namespace Barotrauma
|
||||
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
Implode();
|
||||
if (IsDead) { return; }
|
||||
if (IsDead) { return; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2719,7 +2720,9 @@ namespace Barotrauma
|
||||
PressureTimer = 0.0f;
|
||||
}
|
||||
}
|
||||
else if ((GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient) && WorldPosition.Y < CharacterHealth.CrushDepth)
|
||||
else if ((GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient) &&
|
||||
PressureProtection < (Level.Loaded?.GetRealWorldDepth(WorldPosition.Y) ?? 1.0f) &&
|
||||
WorldPosition.Y < CharacterHealth.CrushDepth)
|
||||
{
|
||||
//implode if below crush depth, and either outside or in a high-pressure hull
|
||||
if (AnimController.CurrentHull == null || AnimController.CurrentHull.LethalPressure >= 80.0f)
|
||||
@@ -3124,47 +3127,53 @@ namespace Barotrauma
|
||||
//set the character order only if the character is close enough to hear the message
|
||||
if (!force && orderGiver != null && !CanHearCharacter(orderGiver)) { return; }
|
||||
|
||||
if (order != null && order.OrderGiver != orderGiver)
|
||||
if (order != null)
|
||||
{
|
||||
order.OrderGiver = orderGiver;
|
||||
}
|
||||
|
||||
switch (order?.Category)
|
||||
{
|
||||
case OrderCategory.Operate when order?.TargetEntity != null:
|
||||
// If there's another character operating the same device, make them dismiss themself
|
||||
foreach (var character in CharacterList)
|
||||
if (order.OrderGiver != orderGiver)
|
||||
{
|
||||
order.OrderGiver = orderGiver;
|
||||
}
|
||||
if (order.AutoDismiss)
|
||||
{
|
||||
switch (order.Category)
|
||||
{
|
||||
if (character == this) { continue; }
|
||||
if (character.TeamID != TeamID) { continue; }
|
||||
if (!(character.AIController is HumanAIController)) { continue; }
|
||||
if (!HumanAIController.IsActive(character)) { continue; }
|
||||
foreach (var currentOrder in character.CurrentOrders)
|
||||
{
|
||||
if (currentOrder.Order == null) { continue; }
|
||||
if (currentOrder.Order.Category != OrderCategory.Operate) { continue; }
|
||||
if (currentOrder.Order.Identifier != order.Identifier) { continue; }
|
||||
if (currentOrder.Order.TargetEntity != order.TargetEntity) { continue; }
|
||||
character.SetOrder(Order.GetPrefab("dismissed"), Order.GetDismissOrderOption(currentOrder), currentOrder.ManualPriority, character, speak: speak, force: force);
|
||||
case OrderCategory.Operate when order.TargetEntity != null:
|
||||
// If there's another character operating the same device, make them dismiss themself
|
||||
foreach (var character in CharacterList)
|
||||
{
|
||||
if (character == this) { continue; }
|
||||
if (character.TeamID != TeamID) { continue; }
|
||||
if (!(character.AIController is HumanAIController)) { continue; }
|
||||
if (!HumanAIController.IsActive(character)) { continue; }
|
||||
foreach (var currentOrder in character.CurrentOrders)
|
||||
{
|
||||
if (currentOrder.Order == null) { continue; }
|
||||
if (currentOrder.Order.Category != OrderCategory.Operate) { continue; }
|
||||
if (currentOrder.Order.Identifier != order.Identifier) { continue; }
|
||||
if (currentOrder.Order.TargetEntity != order.TargetEntity) { continue; }
|
||||
if (!currentOrder.Order.AutoDismiss) { continue; }
|
||||
character.SetOrder(Order.GetPrefab("dismissed"), Order.GetDismissOrderOption(currentOrder), currentOrder.ManualPriority, character, speak: speak, force: force);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case OrderCategory.Movement:
|
||||
// If there character has another movement order, dismiss that order
|
||||
OrderInfo? orderToReplace = null;
|
||||
foreach (var currentOrder in CurrentOrders)
|
||||
{
|
||||
if (currentOrder.Order == null) { continue; }
|
||||
if (currentOrder.Order.Category != OrderCategory.Movement) { continue; }
|
||||
orderToReplace = currentOrder;
|
||||
break;
|
||||
}
|
||||
if (orderToReplace.HasValue && orderToReplace.Value.Order.AutoDismiss)
|
||||
{
|
||||
SetOrder(Order.GetPrefab("dismissed"), Order.GetDismissOrderOption(orderToReplace.Value), orderToReplace.Value.ManualPriority, this, speak: speak, force: force);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case OrderCategory.Movement:
|
||||
// If there character has another movement order, dismiss that order
|
||||
OrderInfo? orderToReplace = null;
|
||||
foreach (var currentOrder in CurrentOrders)
|
||||
{
|
||||
if (currentOrder.Order == null) { continue; }
|
||||
if (currentOrder.Order.Category != OrderCategory.Movement) { continue; }
|
||||
orderToReplace = currentOrder;
|
||||
break;
|
||||
}
|
||||
if (orderToReplace.HasValue)
|
||||
{
|
||||
SetOrder(Order.GetPrefab("dismissed"), Order.GetDismissOrderOption(orderToReplace.Value), orderToReplace.Value.ManualPriority, this, speak: speak, force: force);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent adding duplicate orders
|
||||
@@ -3624,7 +3633,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (attacker.TeamID == TeamID)
|
||||
{
|
||||
afflictions = afflictions.Where(a => !a.Prefab.IsBuff);
|
||||
afflictions = afflictions.Where(a => a.Prefab.IsBuff);
|
||||
if (!afflictions.Any()) { return new AttackResult(); }
|
||||
}
|
||||
}
|
||||
@@ -3936,6 +3945,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
SelectedConstruction = null;
|
||||
SelectedCharacter = null;
|
||||
|
||||
AnimController.ResetPullJoints();
|
||||
|
||||
@@ -4063,10 +4073,11 @@ namespace Barotrauma
|
||||
|
||||
public void TeleportTo(Vector2 worldPos)
|
||||
{
|
||||
CurrentHull = null;
|
||||
AnimController.CurrentHull = null;
|
||||
Submarine = null;
|
||||
AnimController.SetPosition(ConvertUnits.ToSimUnits(worldPos), false);
|
||||
AnimController.FindHull(worldPos, true);
|
||||
AnimController.SetPosition(ConvertUnits.ToSimUnits(worldPos), lerp: false);
|
||||
AnimController.FindHull(worldPos, setSubmarine: true);
|
||||
}
|
||||
|
||||
public static void SaveInventory(Inventory inventory, XElement parentElement)
|
||||
|
||||
@@ -1176,7 +1176,7 @@ namespace Barotrauma
|
||||
int salary = 0;
|
||||
foreach (Skill skill in Job.Skills)
|
||||
{
|
||||
salary += (int)(skill.Level * skill.Prefab.PriceMultiplier);
|
||||
salary += (int)(skill.Level * skill.PriceMultiplier);
|
||||
}
|
||||
|
||||
return (int)(salary * Job.Prefab.PriceMultiplier);
|
||||
@@ -1274,19 +1274,18 @@ namespace Barotrauma
|
||||
return Math.Max(GetTotalTalentPoints() - GetUnlockedTalentsInTree().Count(), 0);
|
||||
}
|
||||
|
||||
public float GetProgressTowardsNextLevel()
|
||||
public int GetProgressTowardsNextLevel()
|
||||
{
|
||||
float progress = (ExperiencePoints - GetExperienceRequiredForCurrentLevel()) / (GetExperienceRequiredToLevelUp() - GetExperienceRequiredForCurrentLevel());
|
||||
return progress;
|
||||
return (ExperiencePoints - GetExperienceRequiredForCurrentLevel()) / (GetExperienceRequiredToLevelUp() - GetExperienceRequiredForCurrentLevel());
|
||||
}
|
||||
|
||||
public float GetExperienceRequiredForCurrentLevel()
|
||||
public int GetExperienceRequiredForCurrentLevel()
|
||||
{
|
||||
GetCurrentLevel(out int experienceRequired);
|
||||
return experienceRequired;
|
||||
}
|
||||
|
||||
public float GetExperienceRequiredToLevelUp()
|
||||
public int GetExperienceRequiredToLevelUp()
|
||||
{
|
||||
int level = GetCurrentLevel(out int experienceRequired);
|
||||
return experienceRequired + ExperienceRequiredPerLevel(level);
|
||||
|
||||
+3
-2
@@ -174,7 +174,8 @@ namespace Barotrauma
|
||||
private void CharacterDead(Character character, CauseOfDeath causeOfDeath)
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
if (Strength < TransformThresholdOnDeath || character.Removed)
|
||||
if (Strength < TransformThresholdOnDeath || character.Removed ||
|
||||
character.CharacterHealth.GetAllAfflictions().Any(a => a.GetActiveEffect()?.BlockTransformation.Contains(Prefab.Identifier) ?? false))
|
||||
{
|
||||
UnsubscribeFromDeathEvent();
|
||||
return;
|
||||
@@ -193,7 +194,7 @@ namespace Barotrauma
|
||||
CoroutineManager.StartCoroutine(CreateAIHusk());
|
||||
}
|
||||
|
||||
private IEnumerable<object> CreateAIHusk()
|
||||
private IEnumerable<CoroutineStatus> CreateAIHusk()
|
||||
{
|
||||
//character already in remove queue (being removed by something else, for example a modded affliction that uses AfflictionHusk as the base)
|
||||
// -> don't spawn the AI husk
|
||||
|
||||
+6
@@ -234,6 +234,11 @@ namespace Barotrauma
|
||||
[Serialize("0,0,0,0", false)]
|
||||
public Color MaxBodyTint { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Prevents AfflictionHusks with the specified identifier(s) from transforming the character into an AI-controlled character
|
||||
/// </summary>
|
||||
public string[] BlockTransformation { get; private set; }
|
||||
|
||||
public readonly Dictionary<StatTypes, (float minValue, float maxValue)> AfflictionStatValues = new Dictionary<StatTypes, (float minValue, float maxValue)>();
|
||||
public readonly HashSet<AbilityFlags> AfflictionAbilityFlags = new HashSet<AbilityFlags>();
|
||||
|
||||
@@ -245,6 +250,7 @@ namespace Barotrauma
|
||||
SerializableProperty.DeserializeProperties(this, element);
|
||||
|
||||
resistanceFor = element.GetAttributeStringArray("resistancefor", new string[0], convertToLowerInvariant: true);
|
||||
BlockTransformation = element.GetAttributeStringArray("blocktransformation", new string[0], convertToLowerInvariant: true);
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
|
||||
@@ -153,7 +153,10 @@ namespace Barotrauma
|
||||
(GameMain.GameSession.GameMode as CampaignMode)?.AssignNPCMenuInteraction(npc, CampaignInteractionType);
|
||||
if (positionToStayIn != null && humanAI != null)
|
||||
{
|
||||
humanAI.ObjectiveManager.SetForcedOrder(new AIObjectiveGoTo(positionToStayIn, npc, humanAI.ObjectiveManager, repeat: true, getDivingGearIfNeeded: false, closeEnough: 200));
|
||||
humanAI.ObjectiveManager.SetForcedOrder(new AIObjectiveGoTo(positionToStayIn, npc, humanAI.ObjectiveManager, repeat: true, getDivingGearIfNeeded: false, closeEnough: 200)
|
||||
{
|
||||
DebugLogWhenFails = false
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Barotrauma
|
||||
{
|
||||
private readonly JobPrefab prefab;
|
||||
|
||||
private Dictionary<string, Skill> skills;
|
||||
private readonly Dictionary<string, Skill> skills;
|
||||
|
||||
public string Name
|
||||
{
|
||||
|
||||
@@ -282,7 +282,7 @@ namespace Barotrauma
|
||||
|
||||
Variants = variant;
|
||||
|
||||
Skills.Sort((x,y) => y.LevelRange.X.CompareTo(x.LevelRange.X));
|
||||
Skills.Sort((x,y) => y.LevelRange.Start.CompareTo(x.LevelRange.Start));
|
||||
|
||||
// Disabled on purpose, TODO: remove all references?
|
||||
//ClothingElement = element.GetChildElement("PortraitClothing");
|
||||
|
||||
@@ -34,14 +34,14 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
internal SkillPrefab Prefab { get; private set; }
|
||||
public readonly float PriceMultiplier = 1.0f;
|
||||
|
||||
public Skill(SkillPrefab prefab)
|
||||
{
|
||||
this.Prefab = prefab;
|
||||
Identifier = prefab.Identifier;
|
||||
level = Rand.Range(prefab.LevelRange.X, prefab.LevelRange.Y, Rand.RandSync.Server);
|
||||
level = Rand.Range(prefab.LevelRange.Start, prefab.LevelRange.End, Rand.RandSync.Server);
|
||||
icon = GetIcon();
|
||||
PriceMultiplier = prefab.PriceMultiplier;
|
||||
}
|
||||
|
||||
public Skill(string identifier, float level)
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Barotrauma
|
||||
{
|
||||
public readonly string Identifier;
|
||||
|
||||
public Vector2 LevelRange { get; private set; }
|
||||
public Range<float> LevelRange { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// How much this skill affects characters' hiring cost
|
||||
@@ -23,12 +23,13 @@ namespace Barotrauma
|
||||
var levelString = element.GetAttributeString("level", "");
|
||||
if (levelString.Contains(","))
|
||||
{
|
||||
LevelRange = XMLExtensions.ParseVector2(levelString, false);
|
||||
var rangeVector2 = XMLExtensions.ParseVector2(levelString, false);
|
||||
LevelRange = new Range<float>(rangeVector2.X, rangeVector2.Y);
|
||||
}
|
||||
else
|
||||
{
|
||||
float skillLevel = float.Parse(levelString, System.Globalization.CultureInfo.InvariantCulture);
|
||||
LevelRange = new Vector2(skillLevel, skillLevel);
|
||||
LevelRange = new Range<float>(skillLevel, skillLevel);
|
||||
}
|
||||
|
||||
IsPrimarySkill = element.GetAttributeBool("primary", false);
|
||||
|
||||
@@ -430,8 +430,15 @@ namespace Barotrauma
|
||||
|
||||
public float Dir
|
||||
{
|
||||
get { return ((dir == Direction.Left) ? -1.0f : 1.0f); }
|
||||
set { dir = (value == -1.0f) ? Direction.Left : Direction.Right; }
|
||||
get { return (dir == Direction.Left) ? -1.0f : 1.0f; }
|
||||
set
|
||||
{
|
||||
dir = (value == -1.0f) ? Direction.Left : Direction.Right;
|
||||
if (body != null)
|
||||
{
|
||||
body.Dir = Dir;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int RefJointIndex => Params.RefJoint;
|
||||
|
||||
+14
-2
@@ -16,10 +16,12 @@ namespace Barotrauma.Abilities
|
||||
private readonly string itemIdentifier;
|
||||
private readonly string[] tags;
|
||||
private readonly WeaponType weapontype;
|
||||
private bool ignoreNonHarmfulAttacks;
|
||||
public AbilityConditionAttackData(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
|
||||
{
|
||||
itemIdentifier = conditionElement.GetAttributeString("itemidentifier", "");
|
||||
tags = conditionElement.GetAttributeStringArray("tags", new string[0], convertToLowerInvariant: true);
|
||||
ignoreNonHarmfulAttacks = conditionElement.GetAttributeBool("ignorenonharmfulattacks", false);
|
||||
switch (conditionElement.GetAttributeString("weapontype", ""))
|
||||
{
|
||||
case "melee":
|
||||
@@ -35,8 +37,15 @@ namespace Barotrauma.Abilities
|
||||
{
|
||||
if (abilityObject is AbilityAttackData attackData)
|
||||
{
|
||||
Item item = attackData?.SourceAttack?.SourceItem;
|
||||
if (ignoreNonHarmfulAttacks && attackData.SourceAttack != null)
|
||||
{
|
||||
if (attackData.SourceAttack.Stun <= 0.0f && (attackData.SourceAttack.Afflictions?.All(a => a.Key.Prefab.IsBuff) ?? true))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Item item = attackData?.SourceAttack?.SourceItem;
|
||||
if (item == null)
|
||||
{
|
||||
DebugConsole.AddWarning($"Source Item was not found in {this} for talent {characterTalent.DebugIdentifier}!");
|
||||
@@ -61,10 +70,13 @@ namespace Barotrauma.Abilities
|
||||
|
||||
switch (weapontype)
|
||||
{
|
||||
// it is possible that an item that has both a melee and a projectile component will return true
|
||||
// even when not used as a melee/ranged weapon respectively
|
||||
// attackdata should contain data regarding whether the attack is melee or not
|
||||
case WeaponType.Melee:
|
||||
return item.GetComponent<MeleeWeapon>() != null;
|
||||
case WeaponType.Ranged:
|
||||
return item.GetComponent<RangedWeapon>() != null;
|
||||
return item.GetComponent<Projectile>() != null;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
+2
-2
@@ -18,13 +18,13 @@ namespace Barotrauma.Abilities
|
||||
|
||||
protected void LogAbilityConditionError(AbilityObject abilityObject, Type expectedData)
|
||||
{
|
||||
DebugConsole.ThrowError($"Used data-reliant ability condition when data is incompatible! Expected {expectedData}, but received {abilityObject}");
|
||||
DebugConsole.ThrowError($"Used data-reliant ability condition when data is incompatible! Expected {expectedData}, but received {abilityObject} in talent {characterTalent.DebugIdentifier}");
|
||||
}
|
||||
|
||||
protected abstract bool MatchesConditionSpecific(AbilityObject abilityObject);
|
||||
public override bool MatchesCondition()
|
||||
{
|
||||
DebugConsole.ThrowError("Used data-reliant ability condition in a state-based ability! This is not allowed.");
|
||||
DebugConsole.ThrowError($"Used data-reliant ability condition in a state-based ability in talent {characterTalent.DebugIdentifier}! This is not allowed.");
|
||||
return false;
|
||||
}
|
||||
public override bool MatchesCondition(AbilityObject abilityObject)
|
||||
|
||||
+3
-3
@@ -79,17 +79,17 @@ namespace Barotrauma.Abilities
|
||||
|
||||
protected virtual void ApplyEffect()
|
||||
{
|
||||
DebugConsole.AddWarning($"Ability {this} used improperly! This ability does not have a definition for ApplyEffect");
|
||||
DebugConsole.AddWarning($"Ability {this} used improperly! This ability does not have a definition for ApplyEffect in talent {CharacterTalent.DebugIdentifier}");
|
||||
}
|
||||
|
||||
protected virtual void ApplyEffect(AbilityObject abilityObject)
|
||||
{
|
||||
DebugConsole.AddWarning($"Ability {this} used improperly! This ability does not take a parameter for ApplyEffect");
|
||||
DebugConsole.AddWarning($"Ability {this} used improperly! This ability does not take a parameter for ApplyEffect in talent {CharacterTalent.DebugIdentifier}");
|
||||
}
|
||||
|
||||
protected void LogabilityObjectMismatch()
|
||||
{
|
||||
DebugConsole.ThrowError($"Incompatible ability! Ability {this} is incompatitible with this type of ability effect type.");
|
||||
DebugConsole.ThrowError($"Incompatible ability! Ability {this} is incompatitible with this type of ability effect type in talent {CharacterTalent.DebugIdentifier}");
|
||||
}
|
||||
|
||||
// XML
|
||||
|
||||
+9
-1
@@ -10,8 +10,10 @@ namespace Barotrauma.Abilities
|
||||
|
||||
protected readonly List<StatusEffect> statusEffects;
|
||||
|
||||
private readonly bool applyToSelf;
|
||||
private readonly bool nearbyCharactersAppliesToSelf;
|
||||
private readonly bool nearbyCharactersAppliesToAllies;
|
||||
private readonly bool nearbyCharactersAppliesToEnemies;
|
||||
private readonly bool applyToSelected;
|
||||
|
||||
readonly List<ISerializableEntity> targets = new List<ISerializableEntity>();
|
||||
@@ -19,9 +21,11 @@ namespace Barotrauma.Abilities
|
||||
public CharacterAbilityApplyStatusEffects(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
statusEffects = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffects"));
|
||||
applyToSelf = abilityElement.GetAttributeBool("applytoself", false);
|
||||
applyToSelected = abilityElement.GetAttributeBool("applytoselected", false);
|
||||
nearbyCharactersAppliesToSelf = abilityElement.GetAttributeBool("nearbycharactersappliestoself", true);
|
||||
nearbyCharactersAppliesToAllies = abilityElement.GetAttributeBool("nearbycharactersappliestoallies", true);
|
||||
nearbyCharactersAppliesToEnemies = abilityElement.GetAttributeBool("nearbycharactersappliestoenemies", true);
|
||||
}
|
||||
|
||||
protected void ApplyEffectSpecific(Character targetCharacter)
|
||||
@@ -46,6 +50,10 @@ namespace Barotrauma.Abilities
|
||||
{
|
||||
targets.RemoveAll(c => c is Character otherCharacter && HumanAIController.IsFriendly(otherCharacter, Character));
|
||||
}
|
||||
if (!nearbyCharactersAppliesToEnemies)
|
||||
{
|
||||
targets.RemoveAll(c => c is Character otherCharacter && !HumanAIController.IsFriendly(otherCharacter, Character));
|
||||
}
|
||||
statusEffect.SetUser(Character);
|
||||
statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, targetCharacter, targets);
|
||||
}
|
||||
@@ -75,7 +83,7 @@ namespace Barotrauma.Abilities
|
||||
|
||||
protected override void ApplyEffect(AbilityObject abilityObject)
|
||||
{
|
||||
if ((abilityObject as IAbilityCharacter)?.Character is Character targetCharacter)
|
||||
if ((abilityObject as IAbilityCharacter)?.Character is Character targetCharacter && !applyToSelf)
|
||||
{
|
||||
ApplyEffectSpecific(targetCharacter);
|
||||
}
|
||||
|
||||
+5
@@ -31,5 +31,10 @@ namespace Barotrauma.Abilities
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ApplyEffect(AbilityObject abilityObject)
|
||||
{
|
||||
ApplyEffect();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityModifyAttackData : CharacterAbility
|
||||
{
|
||||
private readonly List<Affliction> afflictions;
|
||||
private readonly List<Affliction> afflictions = new List<Affliction>();
|
||||
|
||||
private readonly float addedDamageMultiplier;
|
||||
private readonly float addedPenetration;
|
||||
|
||||
+8
-2
@@ -1,4 +1,5 @@
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
@@ -15,14 +16,19 @@ namespace Barotrauma.Abilities
|
||||
if (!TalentTree.JobTalentTrees.TryGetValue(Character.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { return; }
|
||||
|
||||
var subTree = talentTree.TalentSubTrees.Find(t => t.TalentOptionStages.Any(ts => ts.Talents.Contains(CharacterTalent.Prefab)));
|
||||
|
||||
if (subTree != null)
|
||||
{
|
||||
subTree.ForceUnlock = true;
|
||||
foreach (var talentOption in subTree.TalentOptionStages)
|
||||
{
|
||||
foreach (var talent in talentOption.Talents)
|
||||
{
|
||||
if (talent == CharacterTalent.Prefab) { continue; }
|
||||
Character.GiveTalent(talent);
|
||||
if (Character.GiveTalent(talent))
|
||||
{
|
||||
Character.Info.AdditionalTalentPoints++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,37 +101,40 @@ namespace Barotrauma
|
||||
XDocument doc = XMLExtensions.TryLoadXml(file.Path);
|
||||
if (doc == null) { return; }
|
||||
|
||||
var rootElement = doc.Root;
|
||||
switch (rootElement.Name.ToString().ToLowerInvariant())
|
||||
void loadSinglePrefab(XElement element, bool isOverride)
|
||||
{
|
||||
case "talent":
|
||||
TalentPrefabs.Add(new TalentPrefab(rootElement, file.Path), false);
|
||||
break;
|
||||
case "talents":
|
||||
foreach (var element in rootElement.Elements())
|
||||
{
|
||||
if (element.IsOverride())
|
||||
{
|
||||
var itemElement = element.GetChildElement("talent");
|
||||
if (itemElement != null)
|
||||
{
|
||||
TalentPrefabs.Add(new TalentPrefab(rootElement, file.Path), true);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Cannot find a talent element from the children of the override element defined in {file.Path}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
TalentPrefabs.Add(new TalentPrefab(element, file.Path), false);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
DebugConsole.ThrowError($"Invalid XML root element: '{rootElement.Name.ToString()}' in {file.Path}");
|
||||
break;
|
||||
TalentPrefabs.Add(new TalentPrefab(element, file.Path) { ContentPackage = file.ContentPackage }, isOverride);
|
||||
}
|
||||
|
||||
void loadMultiplePrefabs(XElement element, bool isOverride)
|
||||
{
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
interpretElement(subElement, isOverride);
|
||||
}
|
||||
}
|
||||
|
||||
void interpretElement(XElement subElement, bool isOverride)
|
||||
{
|
||||
if (subElement.IsOverride())
|
||||
{
|
||||
loadMultiplePrefabs(subElement, true);
|
||||
}
|
||||
else if (subElement.Name.LocalName.Equals("talents", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
loadMultiplePrefabs(subElement, isOverride);
|
||||
}
|
||||
else if (subElement.Name.LocalName.Equals("talent", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
loadSinglePrefab(subElement, isOverride);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Invalid XML element for the {nameof(TalentPrefab)} prefab type: '{subElement.Name}' in {file.Path}");
|
||||
}
|
||||
}
|
||||
|
||||
interpretElement(doc.Root, false);
|
||||
}
|
||||
|
||||
public static void LoadAll(IEnumerable<ContentFile> files)
|
||||
|
||||
@@ -5,7 +5,7 @@ using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class TalentTree
|
||||
class TalentTree : IPrefab, IDisposable
|
||||
{
|
||||
public enum TalentTreeStageState
|
||||
{
|
||||
@@ -16,7 +16,7 @@ namespace Barotrauma
|
||||
Highlighted
|
||||
}
|
||||
|
||||
public static readonly Dictionary<string, TalentTree> JobTalentTrees = new Dictionary<string, TalentTree>();
|
||||
public static readonly PrefabCollection<TalentTree> JobTalentTrees = new PrefabCollection<TalentTree>();
|
||||
|
||||
public readonly List<TalentSubTree> TalentSubTrees = new List<TalentSubTree>();
|
||||
|
||||
@@ -26,13 +26,21 @@ namespace Barotrauma
|
||||
private set;
|
||||
}
|
||||
|
||||
public string OriginalName => Identifier;
|
||||
|
||||
public string Identifier { get; }
|
||||
|
||||
public string FilePath { get; }
|
||||
|
||||
public ContentPackage ContentPackage { get; set; }
|
||||
|
||||
public TalentTree(XElement element, string filePath)
|
||||
{
|
||||
ConfigElement = element;
|
||||
FilePath = filePath;
|
||||
Identifier = element.GetAttributeString("jobidentifier", "").ToLowerInvariant();
|
||||
|
||||
string jobIdentifier = element.GetAttributeString("jobidentifier", "").ToLowerInvariant();
|
||||
|
||||
if (string.IsNullOrEmpty(jobIdentifier))
|
||||
if (string.IsNullOrEmpty(Identifier))
|
||||
{
|
||||
DebugConsole.ThrowError($"No job defined for talent tree in \"{filePath}\"!");
|
||||
return;
|
||||
@@ -50,20 +58,15 @@ namespace Barotrauma
|
||||
TalentPrefab talentPrefab = TalentPrefab.TalentPrefabs.Find(c => c.Identifier.Equals(talent, StringComparison.OrdinalIgnoreCase));
|
||||
if (talentPrefab == null)
|
||||
{
|
||||
DebugConsole.AddWarning($"Talent tree for job {jobIdentifier} contains non-existent talent {talent}! Talent tree not added.");
|
||||
DebugConsole.AddWarning($"Talent tree for job {Identifier} contains non-existent talent {talent}! Talent tree not added.");
|
||||
return;
|
||||
}
|
||||
if (!duplicateSet.Add(talent))
|
||||
{
|
||||
DebugConsole.ThrowError($"Talent tree for job {jobIdentifier} contains duplicate talent {talent}! Talent tree not added.");
|
||||
DebugConsole.ThrowError($"Talent tree for job {Identifier} contains duplicate talent {talent}! Talent tree not added.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!JobTalentTrees.TryAdd(jobIdentifier, this))
|
||||
{
|
||||
DebugConsole.ThrowError($"Could not add talent tree for job {jobIdentifier}! A talent tree for this job is already likely defined");
|
||||
}
|
||||
}
|
||||
|
||||
public bool TalentIsInTree(string talentIdentifier)
|
||||
@@ -78,37 +81,40 @@ namespace Barotrauma
|
||||
XDocument doc = XMLExtensions.TryLoadXml(file.Path);
|
||||
if (doc == null) { return; }
|
||||
|
||||
var rootElement = doc.Root;
|
||||
switch (rootElement.Name.ToString().ToLowerInvariant())
|
||||
void loadSinglePrefab(XElement element, bool isOverride)
|
||||
{
|
||||
case "talenttree":
|
||||
new TalentTree(rootElement, file.Path);
|
||||
break;
|
||||
case "talenttrees":
|
||||
foreach (var element in rootElement.Elements())
|
||||
{
|
||||
if (element.IsOverride())
|
||||
{
|
||||
var treeElement = element.GetChildElement("talenttree");
|
||||
if (treeElement != null)
|
||||
{
|
||||
new TalentTree(rootElement, file.Path);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Cannot find a talent tree element from the children of the override element defined in {file.Path}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
new TalentTree(element, file.Path);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
DebugConsole.ThrowError($"Invalid XML root element: '{rootElement.Name}' in {file.Path}");
|
||||
break;
|
||||
JobTalentTrees.Add(new TalentTree(element, file.Path) { ContentPackage = file.ContentPackage }, isOverride);
|
||||
}
|
||||
|
||||
void loadMultiplePrefabs(XElement element, bool isOverride)
|
||||
{
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
interpretElement(subElement, isOverride);
|
||||
}
|
||||
}
|
||||
|
||||
void interpretElement(XElement subElement, bool isOverride)
|
||||
{
|
||||
if (subElement.IsOverride())
|
||||
{
|
||||
loadMultiplePrefabs(subElement, true);
|
||||
}
|
||||
else if (subElement.Name.LocalName.Equals("talenttrees", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
loadMultiplePrefabs(subElement, isOverride);
|
||||
}
|
||||
else if (subElement.Name.LocalName.Equals("talenttree", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
loadSinglePrefab(subElement, isOverride);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Invalid XML element for the {nameof(TalentTree)} prefab type: '{subElement.Name}' in {file.Path}");
|
||||
}
|
||||
}
|
||||
|
||||
interpretElement(doc.Root, false);
|
||||
}
|
||||
|
||||
public static void LoadAll(IEnumerable<ContentFile> files)
|
||||
@@ -190,6 +196,8 @@ namespace Barotrauma
|
||||
|
||||
foreach (var subTree in talentTree.TalentSubTrees)
|
||||
{
|
||||
if (subTree.ForceUnlock && subTree.TalentOptionStages.Any(option => option.Talents.Any(t => t.Identifier == talentIdentifier))) { return true; }
|
||||
|
||||
foreach (var talentOptionStage in subTree.TalentOptionStages)
|
||||
{
|
||||
bool hasTalentInThisTier = talentOptionStage.Talents.Any(t => selectedTalents.Contains(t.Identifier));
|
||||
@@ -220,7 +228,7 @@ namespace Barotrauma
|
||||
canStillUnlock = false;
|
||||
foreach (string talent in selectedTalents)
|
||||
{
|
||||
if (IsViableTalentForCharacter(controlledCharacter, talent, viableTalents))
|
||||
if (!viableTalents.Contains(talent) && IsViableTalentForCharacter(controlledCharacter, talent, viableTalents))
|
||||
{
|
||||
viableTalents.Add(talent);
|
||||
canStillUnlock = true;
|
||||
@@ -229,6 +237,14 @@ namespace Barotrauma
|
||||
}
|
||||
return viableTalents;
|
||||
}
|
||||
|
||||
private bool disposed = false;
|
||||
public void Dispose()
|
||||
{
|
||||
if (disposed) { return; }
|
||||
disposed = true;
|
||||
JobTalentTrees.Remove(this);
|
||||
}
|
||||
}
|
||||
|
||||
class TalentSubTree
|
||||
@@ -237,6 +253,8 @@ namespace Barotrauma
|
||||
|
||||
public string DisplayName { get; }
|
||||
|
||||
public bool ForceUnlock;
|
||||
|
||||
public readonly List<TalentOption> TalentOptionStages = new List<TalentOption>();
|
||||
|
||||
public TalentSubTree(XElement subTreeElement)
|
||||
|
||||
@@ -5,14 +5,64 @@ using System.Threading;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
enum CoroutineStatus
|
||||
abstract class CoroutineStatus
|
||||
{
|
||||
Running, Success, Failure
|
||||
public static CoroutineStatus Running => EnumCoroutineStatus.Running;
|
||||
public static CoroutineStatus Success => EnumCoroutineStatus.Success;
|
||||
public static CoroutineStatus Failure => EnumCoroutineStatus.Failure;
|
||||
|
||||
public abstract bool CheckFinished(float deltaTime);
|
||||
public abstract bool EndsCoroutine(CoroutineHandle handle);
|
||||
}
|
||||
|
||||
class EnumCoroutineStatus : CoroutineStatus
|
||||
{
|
||||
private enum StatusValue
|
||||
{
|
||||
Running, Success, Failure
|
||||
}
|
||||
|
||||
private readonly StatusValue Value;
|
||||
|
||||
private EnumCoroutineStatus(StatusValue value) { Value = value; }
|
||||
|
||||
public new readonly static EnumCoroutineStatus Running = new EnumCoroutineStatus(StatusValue.Running);
|
||||
public new readonly static EnumCoroutineStatus Success = new EnumCoroutineStatus(StatusValue.Success);
|
||||
public new readonly static EnumCoroutineStatus Failure = new EnumCoroutineStatus(StatusValue.Failure);
|
||||
|
||||
public override bool CheckFinished(float deltaTime)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool EndsCoroutine(CoroutineHandle handle)
|
||||
{
|
||||
if (Value == StatusValue.Failure)
|
||||
{
|
||||
DebugConsole.ThrowError("Coroutine \"" + handle.Name + "\" has failed");
|
||||
}
|
||||
return Value != StatusValue.Running;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is EnumCoroutineStatus other && Value == other.Value;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Value.GetHashCode();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Value.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
class CoroutineHandle
|
||||
{
|
||||
public readonly IEnumerator<object> Coroutine;
|
||||
public readonly IEnumerator<CoroutineStatus> Coroutine;
|
||||
public readonly string Name;
|
||||
|
||||
public Exception Exception;
|
||||
@@ -20,7 +70,7 @@ namespace Barotrauma
|
||||
|
||||
public Thread Thread;
|
||||
|
||||
public CoroutineHandle(IEnumerator<object> coroutine, string name = "", bool useSeparateThread = false)
|
||||
public CoroutineHandle(IEnumerator<CoroutineStatus> coroutine, string name = "", bool useSeparateThread = false)
|
||||
{
|
||||
Coroutine = coroutine;
|
||||
Name = string.IsNullOrWhiteSpace(name) ? coroutine.ToString() : name;
|
||||
@@ -36,7 +86,7 @@ namespace Barotrauma
|
||||
|
||||
public static float UnscaledDeltaTime, DeltaTime;
|
||||
|
||||
public static CoroutineHandle StartCoroutine(IEnumerable<object> func, string name = "", bool useSeparateThread = false)
|
||||
public static CoroutineHandle StartCoroutine(IEnumerable<CoroutineStatus> func, string name = "", bool useSeparateThread = false)
|
||||
{
|
||||
var handle = new CoroutineHandle(func.GetEnumerator(), name);
|
||||
lock (Coroutines)
|
||||
@@ -63,7 +113,7 @@ namespace Barotrauma
|
||||
return StartCoroutine(DoInvokeAfter(action, delay));
|
||||
}
|
||||
|
||||
private static IEnumerable<object> DoInvokeAfter(Action action, float delay)
|
||||
private static IEnumerable<CoroutineStatus> DoInvokeAfter(Action action, float delay)
|
||||
{
|
||||
if (action == null)
|
||||
{
|
||||
@@ -127,9 +177,7 @@ namespace Barotrauma
|
||||
bool joined = false;
|
||||
while (!joined)
|
||||
{
|
||||
#if CLIENT
|
||||
CrossThread.ProcessTasks();
|
||||
#endif
|
||||
joined = coroutine.Thread.Join(TimeSpan.FromMilliseconds(500));
|
||||
}
|
||||
}
|
||||
@@ -137,35 +185,26 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private static bool PerformCoroutineStep(CoroutineHandle handle)
|
||||
{
|
||||
var current = handle.Coroutine.Current;
|
||||
if (current != null)
|
||||
{
|
||||
if (current.EndsCoroutine(handle) || handle.AbortRequested) { return true; }
|
||||
if (!current.CheckFinished(UnscaledDeltaTime)) { return false; }
|
||||
}
|
||||
if (!handle.Coroutine.MoveNext()) { return true; }
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void ExecuteCoroutineThread(CoroutineHandle handle)
|
||||
{
|
||||
try
|
||||
{
|
||||
while (!handle.AbortRequested)
|
||||
{
|
||||
if (handle.Coroutine.Current != null)
|
||||
{
|
||||
WaitForSeconds wfs = handle.Coroutine.Current as WaitForSeconds;
|
||||
if (wfs != null)
|
||||
{
|
||||
Thread.Sleep((int)(wfs.TotalTime * 1000));
|
||||
}
|
||||
else
|
||||
{
|
||||
switch ((CoroutineStatus)handle.Coroutine.Current)
|
||||
{
|
||||
case CoroutineStatus.Success:
|
||||
return;
|
||||
|
||||
case CoroutineStatus.Failure:
|
||||
DebugConsole.ThrowError("Coroutine \"" + handle.Name + "\" has failed");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Thread.Yield();
|
||||
if (!handle.Coroutine.MoveNext()) return;
|
||||
if (PerformCoroutineStep(handle)) { return; }
|
||||
Thread.Sleep((int)(UnscaledDeltaTime * 1000));
|
||||
}
|
||||
}
|
||||
catch (ThreadAbortException)
|
||||
@@ -187,36 +226,13 @@ namespace Barotrauma
|
||||
#endif
|
||||
if (handle.Thread == null)
|
||||
{
|
||||
if (handle.AbortRequested) { return true; }
|
||||
if (handle.Coroutine.Current != null)
|
||||
{
|
||||
WaitForSeconds wfs = handle.Coroutine.Current as WaitForSeconds;
|
||||
if (wfs != null)
|
||||
{
|
||||
if (!wfs.CheckFinished(UnscaledDeltaTime)) return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
switch ((CoroutineStatus)handle.Coroutine.Current)
|
||||
{
|
||||
case CoroutineStatus.Success:
|
||||
return true;
|
||||
|
||||
case CoroutineStatus.Failure:
|
||||
DebugConsole.ThrowError("Coroutine \"" + handle.Name + "\" has failed");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handle.Coroutine.MoveNext();
|
||||
return false;
|
||||
return PerformCoroutineStep(handle);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (handle.Thread.ThreadState.HasFlag(ThreadState.Stopped))
|
||||
{
|
||||
if (handle.Exception!=null || (CoroutineStatus)handle.Coroutine.Current == CoroutineStatus.Failure)
|
||||
if (handle.Exception!=null || handle.Coroutine.Current == CoroutineStatus.Failure)
|
||||
{
|
||||
DebugConsole.ThrowError("Coroutine \"" + handle.Name + "\" has failed");
|
||||
}
|
||||
@@ -262,7 +278,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
class WaitForSeconds
|
||||
class WaitForSeconds : CoroutineStatus
|
||||
{
|
||||
public readonly float TotalTime;
|
||||
|
||||
@@ -276,7 +292,7 @@ namespace Barotrauma
|
||||
this.ignorePause = ignorePause;
|
||||
}
|
||||
|
||||
public bool CheckFinished(float deltaTime)
|
||||
public override bool CheckFinished(float deltaTime)
|
||||
{
|
||||
#if !SERVER
|
||||
if (ignorePause || !GUI.PauseMenuOpen)
|
||||
@@ -288,5 +304,10 @@ namespace Barotrauma
|
||||
#endif
|
||||
return timer <= 0.0f;
|
||||
}
|
||||
|
||||
public override bool EndsCoroutine(CoroutineHandle handle)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ namespace Barotrauma
|
||||
public static bool CheatsEnabled;
|
||||
|
||||
private static readonly List<ColoredText> unsavedMessages = new List<ColoredText>();
|
||||
private static readonly int messagesPerFile = 5000;
|
||||
private static readonly int messagesPerFile = 800;
|
||||
public const string SavePath = "ConsoleLogs";
|
||||
|
||||
private static void AssignOnExecute(string names, Action<string[]> onExecute)
|
||||
@@ -878,7 +878,7 @@ namespace Barotrauma
|
||||
List<TalentTree> talentTrees = new List<TalentTree>();
|
||||
if (args.Length == 0 || args[0].Equals("all", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
talentTrees.AddRange(TalentTree.JobTalentTrees.Values);
|
||||
talentTrees.AddRange(TalentTree.JobTalentTrees);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1062,7 +1062,7 @@ namespace Barotrauma
|
||||
},
|
||||
null));
|
||||
|
||||
IEnumerable<object> TestLevels()
|
||||
IEnumerable<CoroutineStatus> TestLevels()
|
||||
{
|
||||
SubmarineInfo selectedSub = null;
|
||||
string subName = GameMain.Config.QuickStartSubmarineName;
|
||||
@@ -1410,6 +1410,29 @@ namespace Barotrauma
|
||||
}
|
||||
}, null, isCheat: true));
|
||||
|
||||
commands.Add(new Command("despawnnow", "despawnnow [character]: Immediately despawns the specified dead character. If the character argument is omitted, all dead characters are despawned.", (string[] args) =>
|
||||
{
|
||||
if (args.Length == 0)
|
||||
{
|
||||
foreach (Character c in Character.CharacterList.Where(c => c.IsDead).ToList())
|
||||
{
|
||||
c.DespawnNow();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Character character = FindMatchingCharacter(args);
|
||||
character?.DespawnNow();
|
||||
}
|
||||
},
|
||||
() =>
|
||||
{
|
||||
return new string[][]
|
||||
{
|
||||
Character.CharacterList.Where(c => c.IsDead).Select(c => c.Name).Distinct().ToArray()
|
||||
};
|
||||
}, isCheat: true));
|
||||
|
||||
commands.Add(new Command("setclientcharacter", "setclientcharacter [client name] [character name]: Gives the client control of the specified character.", null,
|
||||
() =>
|
||||
{
|
||||
@@ -1832,7 +1855,7 @@ namespace Barotrauma
|
||||
#if CLIENT
|
||||
activeQuestionText = null;
|
||||
#endif
|
||||
NewMessage(command, Color.White, true);
|
||||
NewCommand(command);
|
||||
//reset the variable before invoking the delegate because the method may need to activate another question
|
||||
var temp = activeQuestionCallback;
|
||||
activeQuestionCallback = null;
|
||||
@@ -1857,7 +1880,7 @@ namespace Barotrauma
|
||||
|
||||
if (!firstCommand.Equals("admin", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
NewMessage(command, Color.White, true);
|
||||
NewCommand(command);
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
@@ -2173,15 +2196,37 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public static void NewMessage(string msg, bool isCommand = false)
|
||||
public static void ShowError(string msg, Color? color = null)
|
||||
{
|
||||
color ??= Color.Red;
|
||||
NewMessage(msg, color.Value, isCommand: false, isError: true);
|
||||
}
|
||||
|
||||
public static void NewCommand(string command, Color? color = null)
|
||||
{
|
||||
color ??= Color.White;
|
||||
NewMessage(command, color.Value, isCommand: true, isError: false);
|
||||
}
|
||||
|
||||
public static void NewMessage(string msg, Color? color = null, bool debugOnly = false)
|
||||
{
|
||||
color ??= Color.White;
|
||||
if (debugOnly)
|
||||
{
|
||||
#if DEBUG
|
||||
NewMessage(msg, color.Value, isCommand: false, isError: false);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
NewMessage(msg, color.Value, isCommand: false, isError: false);
|
||||
}
|
||||
#if DEBUG
|
||||
Console.WriteLine(msg);
|
||||
#endif
|
||||
NewMessage(msg, Color.White, isCommand);
|
||||
}
|
||||
|
||||
public static void NewMessage(string msg, Color color, bool isCommand = false, bool isError = false)
|
||||
private static void NewMessage(string msg, Color color, bool isCommand, bool isError)
|
||||
{
|
||||
if (string.IsNullOrEmpty(msg)) { return; }
|
||||
|
||||
@@ -2271,7 +2316,10 @@ namespace Barotrauma
|
||||
|
||||
public static void Log(string message)
|
||||
{
|
||||
if (GameSettings.VerboseLogging) NewMessage(message, Color.Gray);
|
||||
if (GameSettings.VerboseLogging)
|
||||
{
|
||||
NewMessage(message, Color.Gray);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ThrowError(string error, Exception e = null, bool createMessageBox = false, bool appendStackTrace = false)
|
||||
@@ -2309,7 +2357,7 @@ namespace Barotrauma
|
||||
}
|
||||
#endif
|
||||
|
||||
NewMessage(error, Color.Red, isError: true);
|
||||
ShowError(error);
|
||||
}
|
||||
|
||||
public static void AddWarning(string warning)
|
||||
@@ -2319,7 +2367,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
private static IEnumerable<object> CreateMessageBox(string errorMsg)
|
||||
private static IEnumerable<CoroutineStatus> CreateMessageBox(string errorMsg)
|
||||
{
|
||||
while (GUI.Style == null)
|
||||
{
|
||||
|
||||
@@ -121,7 +121,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (Item item in newCharacter.Inventory.AllItems)
|
||||
{
|
||||
item.SpawnedInOutpost = true;
|
||||
item.SpawnedInCurrentOutpost = true;
|
||||
item.AllowStealing = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@ namespace Barotrauma
|
||||
private float currentIntensity;
|
||||
//The exact intensity of the current situation, current intensity is lerped towards this value
|
||||
private float targetIntensity;
|
||||
//follows targetIntensity a bit faster than currentIntensity to prevent e.g. combat musing staying on very long after the monsters are dead
|
||||
private float musicIntensity;
|
||||
|
||||
//How low the intensity has to be for an event to be triggered.
|
||||
//Gradually increases with time, so additional problems can still appear eventually even if
|
||||
@@ -50,7 +52,11 @@ namespace Barotrauma
|
||||
private float calculateDistanceTraveledTimer;
|
||||
private float distanceTraveled;
|
||||
|
||||
private float avgCrewHealth, avgHullIntegrity, floodingAmount, fireAmount, enemyDanger, monsterTotalStrength;
|
||||
private float avgCrewHealth, avgHullIntegrity, floodingAmount, fireAmount, enemyDanger, monsterStrength;
|
||||
public float CumulativeMonsterStrengthMain;
|
||||
public float CumulativeMonsterStrengthRuins;
|
||||
public float CumulativeMonsterStrengthWrecks;
|
||||
public float CumulativeMonsterStrengthCaves;
|
||||
|
||||
private float roundDuration;
|
||||
|
||||
@@ -78,6 +84,10 @@ namespace Barotrauma
|
||||
{
|
||||
get { return currentIntensity; }
|
||||
}
|
||||
public float MusicIntensity
|
||||
{
|
||||
get { return musicIntensity; }
|
||||
}
|
||||
|
||||
public List<Event> ActiveEvents
|
||||
{
|
||||
@@ -85,7 +95,22 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
public readonly Queue<Event> QueuedEvents = new Queue<Event>();
|
||||
|
||||
|
||||
private struct TimeStamp
|
||||
{
|
||||
public readonly double Time;
|
||||
public readonly Event Event;
|
||||
|
||||
public TimeStamp(Event e)
|
||||
{
|
||||
Event = e;
|
||||
Time = Timing.TotalTime;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<TimeStamp> timeStamps = new List<TimeStamp>();
|
||||
public void AddTimeStamp(Event e) => timeStamps.Add(new TimeStamp(e));
|
||||
|
||||
public EventManager()
|
||||
{
|
||||
isClient = GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient;
|
||||
@@ -99,6 +124,7 @@ namespace Barotrauma
|
||||
|
||||
if (isClient) { return; }
|
||||
|
||||
timeStamps.Clear();
|
||||
pendingEventSets.Clear();
|
||||
selectedEvents.Clear();
|
||||
activeEvents.Clear();
|
||||
@@ -202,8 +228,12 @@ namespace Barotrauma
|
||||
crewAwayResetTimer = 0.0f;
|
||||
intensityUpdateTimer = 0.0f;
|
||||
CalculateCurrentIntensity(0.0f);
|
||||
currentIntensity = targetIntensity;
|
||||
currentIntensity = musicIntensity = targetIntensity;
|
||||
eventCoolDown = 0.0f;
|
||||
CumulativeMonsterStrengthMain = 0;
|
||||
CumulativeMonsterStrengthRuins = 0;
|
||||
CumulativeMonsterStrengthWrecks = 0;
|
||||
CumulativeMonsterStrengthCaves = 0;
|
||||
}
|
||||
|
||||
private void SelectSettings()
|
||||
@@ -401,11 +431,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (level == null) { return; }
|
||||
if (level.LevelData.HasHuntingGrounds && eventSet.DisableInHuntingGrounds) { return; }
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage($"Loading event set {eventSet.DebugIdentifier}", Color.LightBlue);
|
||||
#else
|
||||
DebugConsole.Log($"Loading event set {eventSet.DebugIdentifier}");
|
||||
#endif
|
||||
DebugConsole.NewMessage($"Loading event set {eventSet.DebugIdentifier}", Color.LightBlue, debugOnly: true);
|
||||
int applyCount = 1;
|
||||
List<Func<Level.InterestingPosition, bool>> spawnPosFilter = new List<Func<Level.InterestingPosition, bool>>();
|
||||
if (eventSet.PerRuin)
|
||||
@@ -413,7 +439,7 @@ namespace Barotrauma
|
||||
applyCount = level.Ruins.Count();
|
||||
foreach (var ruin in level.Ruins)
|
||||
{
|
||||
spawnPosFilter.Add((Level.InterestingPosition pos) => { return pos.Ruin == ruin; });
|
||||
spawnPosFilter.Add(pos => pos.Ruin == ruin);
|
||||
}
|
||||
}
|
||||
else if (eventSet.PerCave)
|
||||
@@ -421,7 +447,7 @@ namespace Barotrauma
|
||||
applyCount = level.Caves.Count();
|
||||
foreach (var cave in level.Caves)
|
||||
{
|
||||
spawnPosFilter.Add((Level.InterestingPosition pos) => { return pos.Cave == cave; });
|
||||
spawnPosFilter.Add(pos => pos.Cave == cave);
|
||||
}
|
||||
}
|
||||
else if (eventSet.PerWreck)
|
||||
@@ -430,7 +456,7 @@ namespace Barotrauma
|
||||
applyCount = wrecks.Count();
|
||||
foreach (var wreck in wrecks)
|
||||
{
|
||||
spawnPosFilter.Add((Level.InterestingPosition pos) => { return pos.Submarine == wreck; });
|
||||
spawnPosFilter.Add(pos => pos.Submarine == wreck);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -463,11 +489,7 @@ namespace Barotrauma
|
||||
if (newEvent == null) { continue; }
|
||||
newEvent.Init(true);
|
||||
if (i < spawnPosFilter.Count) { newEvent.SpawnPosFilter = spawnPosFilter[i]; }
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage($"Initialized event {newEvent}");
|
||||
#else
|
||||
DebugConsole.Log($"Initialized event {newEvent}");
|
||||
#endif
|
||||
DebugConsole.NewMessage($"Initialized event {newEvent}", debugOnly: true);
|
||||
if (!selectedEvents.ContainsKey(eventSet))
|
||||
{
|
||||
selectedEvents.Add(eventSet, new List<Event>());
|
||||
@@ -498,11 +520,7 @@ namespace Barotrauma
|
||||
var newEvent = eventPrefab.CreateInstance();
|
||||
if (newEvent == null) { continue; }
|
||||
newEvent.Init(true);
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage($"Initialized event {newEvent}");
|
||||
#else
|
||||
DebugConsole.Log($"Initialized event {newEvent}");
|
||||
#endif
|
||||
DebugConsole.NewMessage($"Initialized event {newEvent}", debugOnly: true);
|
||||
if (!selectedEvents.ContainsKey(eventSet))
|
||||
{
|
||||
selectedEvents.Add(eventSet, new List<Event>());
|
||||
@@ -525,6 +543,7 @@ namespace Barotrauma
|
||||
|
||||
var allowedEventSets =
|
||||
eventSets.Where(es =>
|
||||
es.IsCampaignSet == GameMain.GameSession?.GameMode is CampaignMode &&
|
||||
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)));
|
||||
@@ -647,6 +666,7 @@ namespace Barotrauma
|
||||
isCrewAway = false;
|
||||
crewAwayDuration = 0.0f;
|
||||
eventThreshold += settings.EventThresholdIncrease * deltaTime;
|
||||
eventThreshold = Math.Min(eventThreshold, 1.0f);
|
||||
eventCoolDown -= deltaTime;
|
||||
}
|
||||
|
||||
@@ -739,7 +759,7 @@ namespace Barotrauma
|
||||
// enemy amount --------------------------------------------------------
|
||||
|
||||
enemyDanger = 0.0f;
|
||||
monsterTotalStrength = 0;
|
||||
monsterStrength = 0;
|
||||
foreach (Character character in Character.CharacterList)
|
||||
{
|
||||
if (character.IsIncapacitated || !character.Enabled || character.IsPet || character.Params.CompareGroup("human")) { continue; }
|
||||
@@ -749,28 +769,9 @@ namespace Barotrauma
|
||||
if (!enemyAI.AIParams.StayInAbyss)
|
||||
{
|
||||
// Ignore abyss monsters because they can stay active for quite great distances. They'll be taken into account when they target the sub.
|
||||
monsterTotalStrength += enemyAI.CombatStrength;
|
||||
monsterStrength += enemyAI.CombatStrength;
|
||||
}
|
||||
|
||||
// Example combat strengths:
|
||||
// Hammerheadspawn 1
|
||||
// Moloch Pupa 1
|
||||
// Terminal cell 20
|
||||
// Leucocyte 40
|
||||
// Husk 90
|
||||
// Crawler 100
|
||||
// Unarmored Mudraptor 140
|
||||
// Spineling 150
|
||||
// Tigerthresher 200
|
||||
// Armored Mudraptor 210
|
||||
// Watcher 400
|
||||
// Golden Hammerhead 400
|
||||
// Hammerhead 500
|
||||
// Hammerhead Matriarch 550
|
||||
// Bonethresher 600
|
||||
// Moloch 1250
|
||||
// Black Moloch 1500
|
||||
// Endworm 10000
|
||||
if (character.CurrentHull?.Submarine != null &&
|
||||
(character.CurrentHull.Submarine == Submarine.MainSub || Submarine.MainSub.DockedTo.Contains(character.CurrentHull.Submarine)))
|
||||
{
|
||||
@@ -792,7 +793,7 @@ namespace Barotrauma
|
||||
// 5 Mudraptors -> +0.21 (0.42 in total, before they get inside).
|
||||
// 3 Hammerheads -> +0.3 (0.6 in total, if they all target the sub).
|
||||
// 2 Molochs -> +0.5 (1.0 in total, if both target the sub).
|
||||
enemyDanger += monsterTotalStrength / 5000f;
|
||||
enemyDanger += monsterStrength / 5000f;
|
||||
enemyDanger = MathHelper.Clamp(enemyDanger, 0.0f, 1.0f);
|
||||
|
||||
// The definitions above aim for that we never spawn more monsters that the player (and the performance) can handle.
|
||||
@@ -868,11 +869,15 @@ namespace Barotrauma
|
||||
{
|
||||
//25 seconds for intensity to go from 0.0 to 1.0
|
||||
currentIntensity = Math.Min(currentIntensity + 0.04f * IntensityUpdateInterval, targetIntensity);
|
||||
//20 seconds for intensity to go from 0.0 to 1.0
|
||||
musicIntensity = Math.Min(musicIntensity + 0.05f * IntensityUpdateInterval, targetIntensity);
|
||||
}
|
||||
else
|
||||
{
|
||||
//400 seconds for intensity to go from 1.0 to 0.0
|
||||
currentIntensity = Math.Max(currentIntensity - 0.0025f * IntensityUpdateInterval, targetIntensity);
|
||||
//20 seconds for intensity to go from 1.0 to 0.0
|
||||
musicIntensity = Math.Max(musicIntensity - 0.05f * IntensityUpdateInterval, targetIntensity);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace Barotrauma
|
||||
public float Commonness;
|
||||
public string Identifier;
|
||||
public string BiomeIdentifier;
|
||||
public float SpawnDistance;
|
||||
|
||||
public bool UnlockPathEvent;
|
||||
public string UnlockPathTooltip;
|
||||
@@ -46,25 +47,30 @@ namespace Barotrauma
|
||||
UnlockPathTooltip = element.GetAttributeString("unlockpathtooltip", "lockedpathtooltip");
|
||||
UnlockPathReputation = element.GetAttributeInt("unlockpathreputation", 0);
|
||||
UnlockPathFaction = element.GetAttributeString("unlockpathfaction", "");
|
||||
|
||||
SpawnDistance = element.GetAttributeFloat("spawndistance", 0);
|
||||
}
|
||||
|
||||
public bool TryCreateInstance<T>(out T instance) where T : Event
|
||||
{
|
||||
instance = CreateInstance() as T;
|
||||
return instance is T;
|
||||
}
|
||||
|
||||
public Event CreateInstance()
|
||||
{
|
||||
ConstructorInfo constructor = EventType.GetConstructor(new[] { typeof(EventPrefab) });
|
||||
object instance = null;
|
||||
Event instance = null;
|
||||
try
|
||||
{
|
||||
instance = constructor.Invoke(new object[] { this });
|
||||
instance = constructor.Invoke(new object[] { this }) as Event;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DebugConsole.ThrowError(ex.InnerException != null ? ex.InnerException.ToString() : ex.ToString());
|
||||
}
|
||||
|
||||
Event ev = (Event)instance;
|
||||
if (!ev.LevelMeetsRequirements()) { return null; }
|
||||
|
||||
return (Event)instance;
|
||||
if (instance != null && !instance.LevelMeetsRequirements()) { return null; }
|
||||
return instance;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Barotrauma
|
||||
{
|
||||
public readonly EventSet RootSet;
|
||||
public readonly Dictionary<string, int> MonsterCounts = new Dictionary<string, int>();
|
||||
public float MonsterStrength;
|
||||
|
||||
public EventDebugStats(EventSet rootSet)
|
||||
{
|
||||
@@ -63,6 +64,8 @@ namespace Barotrauma
|
||||
return GetAllEventPrefabs().Find(prefab => string.Equals(prefab.Identifier, identifer, StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
public readonly bool IsCampaignSet;
|
||||
|
||||
//0-100
|
||||
public readonly float MinLevelDifficulty, MaxLevelDifficulty;
|
||||
|
||||
@@ -193,6 +196,7 @@ namespace Barotrauma
|
||||
DelayWhenCrewAway = element.GetAttributeBool("delaywhencrewaway", !PerRuin && !PerCave && !PerWreck);
|
||||
OncePerOutpost = element.GetAttributeBool("onceperoutpost", false);
|
||||
TriggerEventCooldown = element.GetAttributeBool("triggereventcooldown", true);
|
||||
IsCampaignSet = element.GetAttributeBool("campaign", LevelType == LevelData.LevelType.Outpost);
|
||||
|
||||
Commonness[""] = element.GetAttributeFloat("commonness", 1.0f);
|
||||
foreach (XElement subElement in element.Elements())
|
||||
@@ -205,7 +209,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (overrideElement.Name.ToString().Equals("override", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
string levelType = overrideElement.GetAttributeString("leveltype", "");
|
||||
string levelType = overrideElement.GetAttributeString("leveltype", "").ToLowerInvariant();
|
||||
if (!Commonness.ContainsKey(levelType))
|
||||
{
|
||||
Commonness.Add(levelType, overrideElement.GetAttributeFloat("commonness", 0.0f));
|
||||
@@ -227,8 +231,8 @@ namespace Barotrauma
|
||||
EventPrefabs.Add(new SubEventPrefab(
|
||||
debugIdentifier,
|
||||
identifiers,
|
||||
commonness>=0f ? commonness : (float?)null,
|
||||
probability>=0f ? probability : (float?)null));
|
||||
commonness >= 0f ? commonness : (float?)null,
|
||||
probability >= 0f ? probability : (float?)null));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -347,7 +351,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public static List<string> GetDebugStatistics(int simulatedRoundCount = 100)
|
||||
public static List<string> GetDebugStatistics(int simulatedRoundCount = 100, Func<MonsterEvent, bool> filter = null)
|
||||
{
|
||||
List<string> debugLines = new List<string>();
|
||||
|
||||
@@ -357,82 +361,75 @@ namespace Barotrauma
|
||||
for (int i = 0; i < simulatedRoundCount; i++)
|
||||
{
|
||||
var newStats = new EventDebugStats(eventSet);
|
||||
CheckEventSet(newStats, eventSet);
|
||||
CheckEventSet(newStats, eventSet, filter);
|
||||
stats.Add(newStats);
|
||||
}
|
||||
debugLines.Add($"Event stats ({eventSet.DebugIdentifier}): ");
|
||||
LogEventStats(stats, debugLines);
|
||||
}
|
||||
|
||||
for (int difficulty = 0; difficulty <= 100; difficulty += 10)
|
||||
{
|
||||
debugLines.Add($"Event stats on difficulty level {difficulty}: ");
|
||||
List<EventDebugStats> stats = new List<EventDebugStats>();
|
||||
for (int i = 0; i < simulatedRoundCount; i++)
|
||||
{
|
||||
EventSet selectedSet = List.Where(s => difficulty >= s.MinLevelDifficulty && difficulty <= s.MaxLevelDifficulty).GetRandom();
|
||||
if (selectedSet == null) { continue; }
|
||||
var newStats = new EventDebugStats(selectedSet);
|
||||
CheckEventSet(newStats, selectedSet);
|
||||
stats.Add(newStats);
|
||||
}
|
||||
LogEventStats(stats, debugLines);
|
||||
}
|
||||
|
||||
return debugLines;
|
||||
|
||||
static void CheckEventSet(EventDebugStats stats, EventSet thisSet)
|
||||
static void CheckEventSet(EventDebugStats stats, EventSet thisSet, Func<MonsterEvent, bool> filter = null)
|
||||
{
|
||||
if (thisSet.ChooseRandom)
|
||||
{
|
||||
var unusedEvents = thisSet.EventPrefabs.ToList();
|
||||
for (int i = 0; i < thisSet.EventCount; i++)
|
||||
if (unusedEvents.Any())
|
||||
{
|
||||
var eventPrefab = ToolBox.SelectWeightedRandom(unusedEvents, unusedEvents.Select(e => e.Commonness).ToList(), Rand.RandSync.Unsynced);
|
||||
if (eventPrefab.Prefabs.Any(p => p != null))
|
||||
for (int i = 0; i < thisSet.EventCount; i++)
|
||||
{
|
||||
AddEvents(stats, eventPrefab.Prefabs);
|
||||
unusedEvents.Remove(eventPrefab);
|
||||
var eventPrefab = ToolBox.SelectWeightedRandom(unusedEvents, unusedEvents.Select(e => e.Commonness).ToList());
|
||||
if (eventPrefab.Prefabs.Any(p => p != null))
|
||||
{
|
||||
AddEvents(stats, eventPrefab.Prefabs, filter);
|
||||
unusedEvents.Remove(eventPrefab);
|
||||
}
|
||||
}
|
||||
}
|
||||
List<float> values = thisSet.ChildSets.SelectMany(s => s.Commonness.Values).ToList();
|
||||
EventSet childSet = ToolBox.SelectWeightedRandom(thisSet.ChildSets, values);
|
||||
if (childSet != null)
|
||||
{
|
||||
CheckEventSet(stats, childSet, filter);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var eventPrefab in thisSet.EventPrefabs)
|
||||
{
|
||||
AddEvents(stats, eventPrefab.Prefabs);
|
||||
AddEvents(stats, eventPrefab.Prefabs, filter);
|
||||
}
|
||||
foreach (var childSet in thisSet.ChildSets)
|
||||
{
|
||||
CheckEventSet(stats, childSet, filter);
|
||||
}
|
||||
}
|
||||
foreach (var childSet in thisSet.ChildSets)
|
||||
{
|
||||
CheckEventSet(stats, childSet);
|
||||
}
|
||||
}
|
||||
|
||||
static void AddEvents(EventDebugStats stats, IEnumerable<EventPrefab> eventPrefabs)
|
||||
=> eventPrefabs.ForEach(p => AddEvent(stats, p));
|
||||
static void AddEvents(EventDebugStats stats, IEnumerable<EventPrefab> eventPrefabs, Func<MonsterEvent, bool> filter = null)
|
||||
=> eventPrefabs.ForEach(p => AddEvent(stats, p, filter));
|
||||
|
||||
static void AddEvent(EventDebugStats stats, EventPrefab eventPrefab)
|
||||
static void AddEvent(EventDebugStats stats, EventPrefab eventPrefab, Func<MonsterEvent, bool> filter = null)
|
||||
{
|
||||
if (eventPrefab.EventType == typeof(MonsterEvent))
|
||||
if (eventPrefab.EventType == typeof(MonsterEvent) && eventPrefab.TryCreateInstance(out MonsterEvent monsterEvent))
|
||||
{
|
||||
float spawnProbability = eventPrefab.ConfigElement.GetAttributeFloat("spawnprobability", 1.0f);
|
||||
if (Rand.Value(Rand.RandSync.Server) > spawnProbability)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (filter != null && !filter(monsterEvent)) { return; }
|
||||
|
||||
string character = eventPrefab.ConfigElement.GetAttributeString("characterfile", "");
|
||||
System.Diagnostics.Debug.Assert(!string.IsNullOrEmpty(character));
|
||||
int amount = eventPrefab.ConfigElement.GetAttributeInt("amount", 0);
|
||||
int minAmount = eventPrefab.ConfigElement.GetAttributeInt("minamount", amount);
|
||||
int maxAmount = eventPrefab.ConfigElement.GetAttributeInt("maxamount", amount);
|
||||
float spawnProbability = monsterEvent.Prefab.Probability;
|
||||
if (Rand.Value() > spawnProbability) { return; }
|
||||
|
||||
int count = Rand.Range(minAmount, maxAmount + 1);
|
||||
string character = monsterEvent.speciesName;
|
||||
int count = Rand.Range(monsterEvent.MinAmount, monsterEvent.MaxAmount + 1);
|
||||
if (count <= 0) { return; }
|
||||
|
||||
if (!stats.MonsterCounts.ContainsKey(character)) { stats.MonsterCounts[character] = 0; }
|
||||
stats.MonsterCounts[character] += count;
|
||||
|
||||
var aiElement = CharacterPrefab.FindBySpeciesName(character)?.XDocument?.Root?.GetChildElement("ai");
|
||||
if (aiElement != null)
|
||||
{
|
||||
stats.MonsterStrength += aiElement.GetAttributeFloat("combatstrength", 0) * count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -445,16 +442,21 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
stats.Sort((s1, s2) => { return s1.MonsterCounts.Values.Sum().CompareTo(s2.MonsterCounts.Values.Sum()); });
|
||||
|
||||
EventDebugStats minStats = stats.First();
|
||||
EventDebugStats maxStats = stats.First();
|
||||
debugLines.Add($" Minimum monster spawns: {stats.First().MonsterCounts.Values.Sum()}");
|
||||
stats.Sort((s1, s2) => s1.MonsterCounts.Values.Sum().CompareTo(s2.MonsterCounts.Values.Sum()));
|
||||
debugLines.Add($" Minimum monster count: {stats.First().MonsterCounts.Values.Sum()}");
|
||||
debugLines.Add($" {LogMonsterCounts(stats.First())}");
|
||||
debugLines.Add($" Median monster spawns: {stats[stats.Count / 2].MonsterCounts.Values.Sum()}");
|
||||
debugLines.Add($" Median monster count: {stats[stats.Count / 2].MonsterCounts.Values.Sum()}");
|
||||
debugLines.Add($" {LogMonsterCounts(stats[stats.Count / 2])}");
|
||||
debugLines.Add($" Maximum monster spawns: {stats.Last().MonsterCounts.Values.Sum()}");
|
||||
debugLines.Add($" Maximum monster count: {stats.Last().MonsterCounts.Values.Sum()}");
|
||||
debugLines.Add($" {LogMonsterCounts(stats.Last())}");
|
||||
debugLines.Add($" Average monster count: {StringFormatter.FormatZeroDecimal((float)stats.Average(s => s.MonsterCounts.Values.Sum()))}");
|
||||
debugLines.Add($" ");
|
||||
|
||||
stats.Sort((s1, s2) => s1.MonsterStrength.CompareTo(s2.MonsterStrength));
|
||||
debugLines.Add($" Minimum monster strength: {StringFormatter.FormatZeroDecimal(stats.First().MonsterStrength)}");
|
||||
debugLines.Add($" Median monster strength: {StringFormatter.FormatZeroDecimal(stats[stats.Count / 2].MonsterStrength)}");
|
||||
debugLines.Add($" Maximum monster strength: {StringFormatter.FormatZeroDecimal(stats.Last().MonsterStrength)}");
|
||||
debugLines.Add($" Average monster strength: {StringFormatter.FormatZeroDecimal(stats.Average(s => s.MonsterStrength))}");
|
||||
debugLines.Add($" ");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -161,6 +162,24 @@ namespace Barotrauma
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
//if any of the target items is a reactor, prevent exploding it from damaging the player's sub
|
||||
foreach (Item item in items)
|
||||
{
|
||||
if (item.GetComponent<Reactor>() is Reactor reactor && (reactor.statusEffectLists?.ContainsKey(ActionType.OnBroken) ?? false))
|
||||
{
|
||||
foreach (var statusEffect in reactor.statusEffectLists[ActionType.OnBroken])
|
||||
{
|
||||
foreach (Explosion explosion in statusEffect.Explosions)
|
||||
{
|
||||
foreach (Submarine sub in Submarine.Loaded)
|
||||
{
|
||||
if (sub.TeamID == CharacterTeamType.Team1) { explosion.IgnoredSubmarines.Add(sub); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void InitCharacters(Submarine submarine)
|
||||
|
||||
@@ -207,7 +207,7 @@ namespace Barotrauma
|
||||
|
||||
var item = new Item(itemPrefab, position.Value, cargoRoomSub)
|
||||
{
|
||||
SpawnedInOutpost = true,
|
||||
SpawnedInCurrentOutpost = true,
|
||||
AllowStealing = false
|
||||
};
|
||||
item.FindHull();
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using Barotrauma.Extensions;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
@@ -6,8 +5,6 @@ namespace Barotrauma
|
||||
partial class CombatMission : Mission
|
||||
{
|
||||
private Submarine[] subs;
|
||||
// TODO: not used
|
||||
private List<Character>[] crews;
|
||||
|
||||
private readonly string[] descriptions;
|
||||
private static string[] teamNames = { "Team A", "Team B" };
|
||||
@@ -103,15 +100,16 @@ namespace Barotrauma
|
||||
|
||||
subs[0].NeutralizeBallast();
|
||||
subs[0].TeamID = CharacterTeamType.Team1;
|
||||
subs[0].DockedTo.ForEach(s => s.TeamID = CharacterTeamType.Team1);
|
||||
subs[0].GetConnectedSubs().ForEach(s => s.TeamID = CharacterTeamType.Team1);
|
||||
|
||||
subs[1].NeutralizeBallast();
|
||||
subs[1].TeamID = CharacterTeamType.Team2;
|
||||
subs[1].DockedTo.ForEach(s => s.TeamID = CharacterTeamType.Team2);
|
||||
subs[1].GetConnectedSubs().ForEach(s => s.TeamID = CharacterTeamType.Team2);
|
||||
subs[1].SetPosition(subs[1].FindSpawnPos(Level.Loaded.EndPosition));
|
||||
subs[1].FlipX();
|
||||
|
||||
#if SERVER
|
||||
crews = new List<Character>[] { new List<Character>(), new List<Character>() };
|
||||
#endif
|
||||
}
|
||||
|
||||
public override void End()
|
||||
|
||||
@@ -203,6 +203,14 @@ namespace Barotrauma
|
||||
enemySub.TeamID = CharacterTeamType.None;
|
||||
//make the enemy sub withstand atleast the same depth as the player sub
|
||||
enemySub.RealWorldCrushDepth = Math.Max(enemySub.RealWorldCrushDepth, Submarine.MainSub.RealWorldCrushDepth);
|
||||
if (Level.Loaded != null)
|
||||
{
|
||||
//...and the depth of the patrol positions + 1000 m
|
||||
foreach (var patrolPos in patrolPositions)
|
||||
{
|
||||
enemySub.RealWorldCrushDepth = Math.Max(enemySub.RealWorldCrushDepth, Level.Loaded.GetRealWorldDepth(patrolPos.Y) + 1000);
|
||||
}
|
||||
}
|
||||
enemySub.ImmuneToBallastFlora = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@ namespace Barotrauma
|
||||
{
|
||||
class MonsterEvent : Event
|
||||
{
|
||||
private readonly string speciesName;
|
||||
private readonly int minAmount, maxAmount;
|
||||
public readonly string speciesName;
|
||||
public readonly int minAmount, maxAmount;
|
||||
private List<Character> monsters;
|
||||
|
||||
private readonly float scatter;
|
||||
@@ -20,7 +20,7 @@ namespace Barotrauma
|
||||
|
||||
private bool disallowed;
|
||||
|
||||
private readonly Level.PositionType spawnPosType;
|
||||
public readonly Level.PositionType SpawnPosType;
|
||||
private readonly string spawnPointTag;
|
||||
|
||||
private bool spawnPending;
|
||||
@@ -77,15 +77,15 @@ namespace Barotrauma
|
||||
|
||||
var spawnPosTypeStr = prefab.ConfigElement.GetAttributeString("spawntype", "");
|
||||
if (string.IsNullOrWhiteSpace(spawnPosTypeStr) ||
|
||||
!Enum.TryParse(spawnPosTypeStr, true, out spawnPosType))
|
||||
!Enum.TryParse(spawnPosTypeStr, true, out SpawnPosType))
|
||||
{
|
||||
spawnPosType = Level.PositionType.MainPath;
|
||||
SpawnPosType = Level.PositionType.MainPath;
|
||||
}
|
||||
|
||||
//backwards compatibility
|
||||
if (prefab.ConfigElement.GetAttributeBool("spawndeep", false))
|
||||
{
|
||||
spawnPosType = Level.PositionType.Abyss;
|
||||
SpawnPosType = Level.PositionType.Abyss;
|
||||
}
|
||||
|
||||
spawnPointTag = prefab.ConfigElement.GetAttributeString("spawnpointtag", string.Empty);
|
||||
@@ -143,7 +143,7 @@ namespace Barotrauma
|
||||
|
||||
private List<Level.InterestingPosition> GetAvailableSpawnPositions()
|
||||
{
|
||||
var availablePositions = Level.Loaded.PositionsOfInterest.FindAll(p => spawnPosType.HasFlag(p.PositionType));
|
||||
var availablePositions = Level.Loaded.PositionsOfInterest.FindAll(p => SpawnPosType.HasFlag(p.PositionType));
|
||||
var removals = new List<Level.InterestingPosition>();
|
||||
foreach (var position in availablePositions)
|
||||
{
|
||||
@@ -188,8 +188,8 @@ namespace Barotrauma
|
||||
spawnPos = Vector2.Zero;
|
||||
var availablePositions = GetAvailableSpawnPositions();
|
||||
var chosenPosition = new Level.InterestingPosition(Point.Zero, Level.PositionType.MainPath, isValid: false);
|
||||
bool isRuinOrWreck = spawnPosType.HasFlag(Level.PositionType.Ruin) || spawnPosType.HasFlag(Level.PositionType.Wreck);
|
||||
if (affectSubImmediately && !isRuinOrWreck && !spawnPosType.HasFlag(Level.PositionType.Abyss))
|
||||
bool isRuinOrWreck = SpawnPosType.HasFlag(Level.PositionType.Ruin) || SpawnPosType.HasFlag(Level.PositionType.Wreck);
|
||||
if (affectSubImmediately && !isRuinOrWreck && !SpawnPosType.HasFlag(Level.PositionType.Abyss))
|
||||
{
|
||||
if (availablePositions.None())
|
||||
{
|
||||
@@ -288,11 +288,14 @@ namespace Barotrauma
|
||||
spawnPos = chosenPosition.Position.ToVector2();
|
||||
if (chosenPosition.Submarine != null || chosenPosition.Ruin != null)
|
||||
{
|
||||
var spawnPoint =
|
||||
WayPoint.GetRandom(SpawnType.Enemy, sub: chosenPosition.Submarine ?? chosenPosition.Ruin?.Submarine, useSyncedRand: false, spawnPointTag: spawnPointTag);
|
||||
bool ignoreSubmarine = chosenPosition.Ruin != null;
|
||||
var spawnPoint = WayPoint.GetRandom(SpawnType.Enemy, sub: chosenPosition.Submarine, useSyncedRand: false, spawnPointTag: spawnPointTag, ignoreSubmarine: ignoreSubmarine);
|
||||
if (spawnPoint != null)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(spawnPoint.Submarine == (chosenPosition.Submarine ?? chosenPosition.Ruin?.Submarine));
|
||||
if (!ignoreSubmarine)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(spawnPoint.Submarine == chosenPosition.Submarine);
|
||||
}
|
||||
spawnPos = spawnPoint.WorldPosition;
|
||||
}
|
||||
else
|
||||
@@ -303,32 +306,42 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if ((chosenPosition.PositionType == Level.PositionType.MainPath || chosenPosition.PositionType == Level.PositionType.SidePath)
|
||||
&& offset > 0)
|
||||
else if (chosenPosition.PositionType == Level.PositionType.MainPath || chosenPosition.PositionType == Level.PositionType.SidePath)
|
||||
{
|
||||
Vector2 dir;
|
||||
var waypoints = WayPoint.WayPointList.FindAll(wp => wp.Submarine == null);
|
||||
var nearestWaypoint = waypoints.OrderBy(wp => Vector2.DistanceSquared(wp.WorldPosition, spawnPos.Value)).FirstOrDefault();
|
||||
if (nearestWaypoint != null)
|
||||
if (offset > 0)
|
||||
{
|
||||
int currentIndex = waypoints.IndexOf(nearestWaypoint);
|
||||
var nextWaypoint = waypoints[Math.Min(currentIndex + 20, waypoints.Count - 1)];
|
||||
dir = Vector2.Normalize(nextWaypoint.WorldPosition - nearestWaypoint.WorldPosition);
|
||||
// Ensure that the spawn position is not offset to the left.
|
||||
if (dir.X < 0)
|
||||
Vector2 dir;
|
||||
var waypoints = WayPoint.WayPointList.FindAll(wp => wp.Submarine == null && wp.Ruin == null);
|
||||
var nearestWaypoint = waypoints.OrderBy(wp => Vector2.DistanceSquared(wp.WorldPosition, spawnPos.Value)).FirstOrDefault();
|
||||
if (nearestWaypoint != null)
|
||||
{
|
||||
dir.X = 0;
|
||||
int currentIndex = waypoints.IndexOf(nearestWaypoint);
|
||||
var nextWaypoint = waypoints[Math.Min(currentIndex + 20, waypoints.Count - 1)];
|
||||
dir = Vector2.Normalize(nextWaypoint.WorldPosition - nearestWaypoint.WorldPosition);
|
||||
// Ensure that the spawn position is not offset to the left.
|
||||
if (dir.X < 0)
|
||||
{
|
||||
dir.X = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
dir = new Vector2(1, Rand.Range(-1, 1));
|
||||
}
|
||||
Vector2 targetPos = spawnPos.Value + dir * offset;
|
||||
var targetWaypoint = waypoints.OrderBy(wp => Vector2.DistanceSquared(wp.WorldPosition, targetPos)).FirstOrDefault();
|
||||
if (targetWaypoint != null)
|
||||
{
|
||||
spawnPos = targetWaypoint.WorldPosition;
|
||||
}
|
||||
}
|
||||
else
|
||||
// Ensure that the position is not inside a submarine (in practice wrecks).
|
||||
if (Submarine.Loaded.Any(s => ToolBox.GetWorldBounds(s.Borders.Center, s.Borders.Size).ContainsWorld(spawnPos.Value)))
|
||||
{
|
||||
dir = new Vector2(1, Rand.Range(-1, 1));
|
||||
}
|
||||
Vector2 targetPos = spawnPos.Value + dir * offset;
|
||||
var targetWaypoint = waypoints.OrderBy(wp => Vector2.DistanceSquared(wp.WorldPosition, targetPos)).FirstOrDefault();
|
||||
if (targetWaypoint != null)
|
||||
{
|
||||
spawnPos = targetWaypoint.WorldPosition;
|
||||
//no suitable position found, disable the event
|
||||
spawnPos = null;
|
||||
Finished();
|
||||
return;
|
||||
}
|
||||
}
|
||||
spawnPending = true;
|
||||
@@ -371,7 +384,7 @@ namespace Barotrauma
|
||||
if (spawnPending)
|
||||
{
|
||||
//wait until there are no submarines at the spawnpos
|
||||
if (spawnPosType.HasFlag(Level.PositionType.MainPath) || spawnPosType.HasFlag(Level.PositionType.SidePath) || spawnPosType.HasFlag(Level.PositionType.Abyss))
|
||||
if (SpawnPosType.HasFlag(Level.PositionType.MainPath) || SpawnPosType.HasFlag(Level.PositionType.SidePath) || SpawnPosType.HasFlag(Level.PositionType.Abyss))
|
||||
{
|
||||
foreach (Submarine submarine in Submarine.Loaded)
|
||||
{
|
||||
@@ -380,17 +393,29 @@ namespace Barotrauma
|
||||
if (Vector2.DistanceSquared(submarine.WorldPosition, spawnPos.Value) < minDist * minDist) { return; }
|
||||
}
|
||||
}
|
||||
|
||||
//if spawning in a ruin/cave, wait for someone to be close to it to spawning
|
||||
//unnecessary monsters in places the players might never visit during the round
|
||||
if (spawnPosType.HasFlag(Level.PositionType.Ruin) || spawnPosType.HasFlag(Level.PositionType.Cave) || spawnPosType.HasFlag(Level.PositionType.Wreck))
|
||||
float minDistance = Prefab.SpawnDistance;
|
||||
if (minDistance <= 0)
|
||||
{
|
||||
if (SpawnPosType.HasFlag(Level.PositionType.Cave))
|
||||
{
|
||||
minDistance = 8000;
|
||||
}
|
||||
else if (SpawnPosType.HasFlag(Level.PositionType.Ruin))
|
||||
{
|
||||
minDistance = 5000;
|
||||
}
|
||||
else if (SpawnPosType.HasFlag(Level.PositionType.Wreck))
|
||||
{
|
||||
minDistance = 3000;
|
||||
}
|
||||
}
|
||||
if (minDistance > 0)
|
||||
{
|
||||
bool someoneNearby = false;
|
||||
float minDist = Sonar.DefaultSonarRange * 0.8f;
|
||||
foreach (Submarine submarine in Submarine.Loaded)
|
||||
{
|
||||
if (submarine.Info.Type != SubmarineType.Player) { continue; }
|
||||
if (Vector2.DistanceSquared(submarine.WorldPosition, spawnPos.Value) < minDist * minDist)
|
||||
if (Vector2.DistanceSquared(submarine.WorldPosition, spawnPos.Value) < MathUtils.Pow2(minDistance))
|
||||
{
|
||||
someoneNearby = true;
|
||||
break;
|
||||
@@ -400,7 +425,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (c == Character.Controlled || c.IsRemotePlayer)
|
||||
{
|
||||
if (Vector2.DistanceSquared(c.WorldPosition, spawnPos.Value) < minDist * minDist)
|
||||
if (Vector2.DistanceSquared(c.WorldPosition, spawnPos.Value) < MathUtils.Pow2(minDistance))
|
||||
{
|
||||
someoneNearby = true;
|
||||
break;
|
||||
@@ -411,7 +436,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
|
||||
if (spawnPosType.HasFlag(Level.PositionType.Abyss) || spawnPosType.HasFlag(Level.PositionType.AbyssCave))
|
||||
if (SpawnPosType.HasFlag(Level.PositionType.Abyss) || SpawnPosType.HasFlag(Level.PositionType.AbyssCave))
|
||||
{
|
||||
bool anyInAbyss = false;
|
||||
foreach (Submarine submarine in Submarine.Loaded)
|
||||
@@ -432,7 +457,7 @@ namespace Barotrauma
|
||||
int amount = Rand.Range(minAmount, maxAmount + 1);
|
||||
monsters = new List<Character>();
|
||||
float scatterAmount = scatter;
|
||||
if (spawnPosType.HasFlag(Level.PositionType.SidePath))
|
||||
if (SpawnPosType.HasFlag(Level.PositionType.SidePath))
|
||||
{
|
||||
var sidePaths = Level.Loaded.Tunnels.Where(t => t.Type == Level.TunnelType.SidePath);
|
||||
if (sidePaths.Any())
|
||||
@@ -444,7 +469,7 @@ namespace Barotrauma
|
||||
scatterAmount = scatter;
|
||||
}
|
||||
}
|
||||
else if (!spawnPosType.HasFlag(Level.PositionType.MainPath))
|
||||
else if (!SpawnPosType.HasFlag(Level.PositionType.MainPath))
|
||||
{
|
||||
scatterAmount = 0;
|
||||
}
|
||||
@@ -474,6 +499,27 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
Character createdCharacter = Character.Create(speciesName, pos, seed, characterInfo: null, isRemotePlayer: false, hasAi: true, createNetworkEvent: true);
|
||||
var eventManager = GameMain.GameSession.EventManager;
|
||||
if (eventManager != null)
|
||||
{
|
||||
if (SpawnPosType.HasFlag(Level.PositionType.MainPath) || SpawnPosType.HasFlag(Level.PositionType.SidePath))
|
||||
{
|
||||
eventManager.CumulativeMonsterStrengthMain += createdCharacter.Params.AI.CombatStrength;
|
||||
eventManager.AddTimeStamp(this);
|
||||
}
|
||||
else if (SpawnPosType.HasFlag(Level.PositionType.Ruin))
|
||||
{
|
||||
eventManager.CumulativeMonsterStrengthRuins += createdCharacter.Params.AI.CombatStrength;
|
||||
}
|
||||
else if (SpawnPosType.HasFlag(Level.PositionType.Wreck))
|
||||
{
|
||||
eventManager.CumulativeMonsterStrengthWrecks += createdCharacter.Params.AI.CombatStrength;
|
||||
}
|
||||
else if (SpawnPosType.HasFlag(Level.PositionType.Cave))
|
||||
{
|
||||
eventManager.CumulativeMonsterStrengthCaves += createdCharacter.Params.AI.CombatStrength;
|
||||
}
|
||||
}
|
||||
if (GameMain.GameSession.IsCurrentLocationRadiated())
|
||||
{
|
||||
AfflictionPrefab radiationPrefab = AfflictionPrefab.RadiationSickness;
|
||||
@@ -490,6 +536,7 @@ namespace Barotrauma
|
||||
//this will do nothing if the monsters have no swarm behavior defined,
|
||||
//otherwise it'll make the spawned characters act as a swarm
|
||||
SwarmBehavior.CreateSwarm(monsters.Cast<AICharacter>());
|
||||
DebugConsole.NewMessage($"Spawned: {ToString()}. Strength: {StringFormatter.FormatZeroDecimal(monsters.Sum(m => m.Params.AI.CombatStrength))}.", Color.LightBlue, debugOnly: true);
|
||||
}
|
||||
}, Rand.Range(0f, amount / 2f));
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ namespace Barotrauma
|
||||
|
||||
public static string Format(this float value, int decimalCount)
|
||||
{
|
||||
return value.ToString($"F{decimalCount.ToString()}", CultureInfo.InvariantCulture);
|
||||
return value.ToString($"F{decimalCount}", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
public static string FormatSingleDecimal(this Vector2 value)
|
||||
|
||||
@@ -254,7 +254,7 @@ namespace Barotrauma
|
||||
if (!validContainer.Key.Inventory.CanBePut(itemPrefab, quality: quality)) { break; }
|
||||
var item = new Item(itemPrefab, validContainer.Key.Item.Position, validContainer.Key.Item.Submarine)
|
||||
{
|
||||
SpawnedInOutpost = validContainer.Key.Item.SpawnedInOutpost,
|
||||
SpawnedInCurrentOutpost = validContainer.Key.Item.SpawnedInCurrentOutpost,
|
||||
AllowStealing = validContainer.Key.Item.AllowStealing,
|
||||
Quality = quality,
|
||||
OriginalModuleIndex = validContainer.Key.Item.OriginalModuleIndex,
|
||||
|
||||
@@ -393,7 +393,7 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
protected abstract void LoadInitialLevel();
|
||||
|
||||
protected abstract IEnumerable<object> DoLevelTransition(TransitionType transitionType, LevelData newLevel, Submarine leavingSub, bool mirror, List<TraitorMissionResult> traitorResults = null);
|
||||
protected abstract IEnumerable<CoroutineStatus> DoLevelTransition(TransitionType transitionType, LevelData newLevel, Submarine leavingSub, bool mirror, List<TraitorMissionResult> traitorResults = null);
|
||||
|
||||
/// <summary>
|
||||
/// Which type of transition between levels is currently possible (if any)
|
||||
@@ -572,7 +572,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (!item.SpawnedInOutpost || item.OriginalModuleIndex < 0) { continue; }
|
||||
if (!item.SpawnedInCurrentOutpost || item.OriginalModuleIndex < 0) { continue; }
|
||||
var owner = item.GetRootInventoryOwner();
|
||||
if ((!(owner?.Submarine?.Info?.IsOutpost ?? false)) || (owner is Character character && character.TeamID == CharacterTeamType.Team1) || item.Submarine == null || !item.Submarine.Info.IsOutpost)
|
||||
{
|
||||
@@ -712,7 +712,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<object> DoCharacterWait(Character npc, Character interactor)
|
||||
private IEnumerable<CoroutineStatus> DoCharacterWait(Character npc, Character interactor)
|
||||
{
|
||||
if (npc == null || interactor == null) { yield return CoroutineStatus.Failure; }
|
||||
|
||||
@@ -907,7 +907,7 @@ namespace Barotrauma
|
||||
|
||||
public int NumberOfMissionsAtLocation(Location location)
|
||||
{
|
||||
return Map.CurrentLocation.SelectedMissions.Count(m => m.Locations.Contains(location));
|
||||
return Map?.CurrentLocation?.SelectedMissions?.Count(m => m.Locations.Contains(location)) ?? 0;
|
||||
}
|
||||
|
||||
public void CheckTooManyMissions(Location currentLocation, Client sender)
|
||||
|
||||
@@ -312,7 +312,7 @@ namespace Barotrauma
|
||||
return isRadiated;
|
||||
}
|
||||
|
||||
public void StartRound(string levelSeed, float? difficulty = null)
|
||||
public void StartRound(string levelSeed, float? difficulty = null, LevelGenerationParams levelGenerationParams = null)
|
||||
{
|
||||
LevelData randomLevel = null;
|
||||
foreach (Mission mission in Missions.Union(GameMode.Missions))
|
||||
@@ -324,11 +324,11 @@ namespace Barotrauma
|
||||
{
|
||||
LocationType locationType = LocationType.List.FirstOrDefault(lt => missionPrefab.AllowedLocationTypes.Any(m => m.Equals(lt.Identifier, StringComparison.OrdinalIgnoreCase)));
|
||||
CreateDummyLocations(locationType);
|
||||
randomLevel = LevelData.CreateRandom(levelSeed, difficulty, requireOutpost: true);
|
||||
randomLevel = LevelData.CreateRandom(levelSeed, difficulty, levelGenerationParams, requireOutpost: true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
randomLevel ??= LevelData.CreateRandom(levelSeed, difficulty);
|
||||
randomLevel ??= LevelData.CreateRandom(levelSeed, difficulty, levelGenerationParams);
|
||||
StartRound(randomLevel);
|
||||
}
|
||||
|
||||
@@ -351,6 +351,8 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
Submarine.LockX = Submarine.LockY = false;
|
||||
|
||||
LevelData = levelData;
|
||||
|
||||
Submarine.Unload();
|
||||
@@ -511,10 +513,6 @@ namespace Barotrauma
|
||||
mpCampaign.UpgradeManager.ApplyUpgrades();
|
||||
mpCampaign.UpgradeManager.SanityCheckUpgrades(Submarine);
|
||||
}
|
||||
if (GameMode is CampaignMode)
|
||||
{
|
||||
Submarine.WarmStartPower();
|
||||
}
|
||||
}
|
||||
|
||||
GameMain.Config.RecentlyEncounteredCreatures.Clear();
|
||||
|
||||
@@ -158,7 +158,7 @@ namespace Barotrauma.Items.Components
|
||||
if (!CanBeCombinedWith(otherGeneticMaterial)) { return false; }
|
||||
|
||||
float conditionIncrease = Rand.Range(ConditionIncreaseOnCombineMin, ConditionIncreaseOnCombineMax);
|
||||
conditionIncrease += user.GetStatValue(StatTypes.GeneticMaterialRefineBonus);
|
||||
conditionIncrease += user?.GetStatValue(StatTypes.GeneticMaterialRefineBonus) ?? 0.0f;
|
||||
if (item.Prefab == otherGeneticMaterial.item.Prefab)
|
||||
{
|
||||
item.Condition = Math.Max(item.Condition, otherGeneticMaterial.item.Condition) + conditionIncrease;
|
||||
|
||||
@@ -110,7 +110,9 @@ namespace Barotrauma.Items.Components
|
||||
if (Item.RequireAimToUse && hitPos < MathHelper.PiOver4) { return false; }
|
||||
|
||||
ActivateNearbySleepingCharacters();
|
||||
reloadTimer = reload / (1 + character.GetStatValue(StatTypes.MeleeAttackSpeed));
|
||||
reloadTimer = reload;
|
||||
reloadTimer /= (1f + character.GetStatValue(StatTypes.MeleeAttackSpeed));
|
||||
reloadTimer /= (1f + item.GetQualityModifier(Quality.StatType.StrikingSpeedMultiplier));
|
||||
|
||||
item.body.FarseerBody.CollisionCategories = Physics.CollisionProjectile;
|
||||
item.body.FarseerBody.CollidesWith = Physics.CollisionCharacter | Physics.CollisionWall;
|
||||
@@ -385,7 +387,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
Attack.SetUser(User);
|
||||
Attack.DamageMultiplier = 1 + User.GetStatValue(StatTypes.MeleeAttackMultiplier);
|
||||
Attack.DamageMultiplier *= 1.0f + item.GetQualityModifier(Quality.StatType.AttackMultiplier);
|
||||
Attack.DamageMultiplier *= 1.0f + item.GetQualityModifier(Quality.StatType.StrikingPowerMultiplier);
|
||||
|
||||
if (targetLimb != null)
|
||||
{
|
||||
|
||||
@@ -142,7 +142,7 @@ namespace Barotrauma.Items.Components
|
||||
return false;
|
||||
}
|
||||
|
||||
private IEnumerable<object> WaitForPick(Character picker, float requiredTime)
|
||||
private IEnumerable<CoroutineStatus> WaitForPick(Character picker, float requiredTime)
|
||||
{
|
||||
activePicker = picker;
|
||||
picker.PickingItem = item;
|
||||
|
||||
@@ -149,7 +149,8 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
float degreeOfFailure = 1.0f - DegreeOfSuccess(user);
|
||||
degreeOfFailure *= degreeOfFailure;
|
||||
return MathHelper.ToRadians(MathHelper.Lerp(Spread, UnskilledSpread, degreeOfFailure));
|
||||
float spread = MathHelper.Lerp(Spread, UnskilledSpread, degreeOfFailure) / (1f + user.GetStatValue(StatTypes.RangedSpreadReduction));
|
||||
return MathHelper.ToRadians(spread);
|
||||
}
|
||||
|
||||
private readonly List<Body> limbBodies = new List<Body>();
|
||||
@@ -203,7 +204,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
lastProjectile?.Item.GetComponent<Rope>()?.Snap();
|
||||
}
|
||||
float damageMultiplier = 1f + item.GetQualityModifier(Quality.StatType.AttackMultiplier);
|
||||
float damageMultiplier = 1f + item.GetQualityModifier(Quality.StatType.StoppingPowerMultiplier);
|
||||
projectile.Shoot(character, character.AnimController.AimSourceSimPos, barrelPos, rotation + spread, ignoredBodies: limbBodies.ToList(), createNetworkEvent: false, damageMultiplier);
|
||||
projectile.Item.GetComponent<Rope>()?.Attach(Item, projectile.Item);
|
||||
if (i == 0)
|
||||
|
||||
@@ -632,6 +632,10 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public virtual void FlipY(bool relativeToSub) { }
|
||||
|
||||
public bool IsLoaded(Character user, bool checkContainedItems = true) =>
|
||||
HasRequiredContainedItems(user, addMessage: false) &&
|
||||
(!checkContainedItems || Item.OwnInventory == null || Item.OwnInventory.AllItems.Any(i => i.Condition > 0));
|
||||
|
||||
public bool HasRequiredContainedItems(Character user, bool addMessage, string msg = null)
|
||||
{
|
||||
if (!requiredItems.ContainsKey(RelatedItem.RelationType.Contained)) { return true; }
|
||||
|
||||
@@ -470,7 +470,9 @@ namespace Barotrauma.Items.Components
|
||||
if (!AllowDragAndDrop && user != null) { return false; }
|
||||
if (!slotRestrictions.Any(s => s.MatchesItem(item))) { return false; }
|
||||
if (user != null && !user.CanAccessInventory(Inventory)) { return false; }
|
||||
|
||||
//genetic materials use special logic for combining, don't allow doing it by placing them inside each other here
|
||||
if (item.GetComponent<GeneticMaterial>() != null) { return false; }
|
||||
|
||||
if (Inventory.TryPutItem(item, user))
|
||||
{
|
||||
IsActive = true;
|
||||
|
||||
@@ -13,6 +13,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
partial void OnStateChanged();
|
||||
|
||||
private string prevColorSignal;
|
||||
|
||||
public override void ReceiveSignal(Signal signal, Connection connection)
|
||||
{
|
||||
switch (connection.Name)
|
||||
@@ -22,6 +24,13 @@ namespace Barotrauma.Items.Components
|
||||
Text = signal.value;
|
||||
OnStateChanged();
|
||||
break;
|
||||
case "set_text_color":
|
||||
if (signal.value != prevColorSignal)
|
||||
{
|
||||
TextColor = XMLExtensions.ParseColor(signal.value, false);
|
||||
prevColorSignal = signal.value;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+10
-2
@@ -221,7 +221,7 @@ namespace Barotrauma.Items.Components
|
||||
if (targetItem == otherItem) { continue; }
|
||||
if (deconstructProduct.RequiredOtherItem.Any(r => otherItem.HasTag(r) || r.Equals(otherItem.Prefab.Identifier, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
user.CheckTalents(AbilityEffectType.OnGeneticMaterialCombinedOrRefined);
|
||||
user?.CheckTalents(AbilityEffectType.OnGeneticMaterialCombinedOrRefined);
|
||||
foreach (Character character in Character.GetFriendlyCrew(user))
|
||||
{
|
||||
character.CheckTalents(AbilityEffectType.OnCrewGeneticMaterialCombinedOrRefined);
|
||||
@@ -264,6 +264,8 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
Entity.Spawner.AddToSpawnQueue(itemPrefab, outputContainer.Inventory, condition, onSpawned: (Item spawnedItem) =>
|
||||
{
|
||||
spawnedItem.StolenDuringRound = targetItem.StolenDuringRound;
|
||||
spawnedItem.AllowStealing = targetItem.AllowStealing;
|
||||
for (int i = 0; i < outputContainer.Capacity; i++)
|
||||
{
|
||||
var containedItem = outputContainer.Inventory.GetItemAt(i);
|
||||
@@ -283,7 +285,13 @@ namespace Barotrauma.Items.Components
|
||||
foreach (ItemContainer ic in targetItem.GetComponents<ItemContainer>())
|
||||
{
|
||||
if (ic?.Inventory == null || ic.RemoveContainedItemsOnDeconstruct) { continue; }
|
||||
ic.Inventory.AllItemsMod.ForEach(containedItem => outputContainer.Inventory.TryPutItem(containedItem, user: null));
|
||||
foreach (Item containedItem in ic.Inventory.AllItemsMod)
|
||||
{
|
||||
if (!outputContainer.Inventory.TryPutItem(containedItem, user: null))
|
||||
{
|
||||
containedItem.Drop(dropper: null);
|
||||
}
|
||||
}
|
||||
}
|
||||
inputContainer.Inventory.RemoveItem(targetItem);
|
||||
Entity.Spawner.AddToRemoveQueue(targetItem);
|
||||
|
||||
@@ -20,6 +20,11 @@ namespace Barotrauma.Items.Components
|
||||
private string savedFabricatedItem;
|
||||
private float savedTimeUntilReady, savedRequiredTime;
|
||||
|
||||
private readonly Dictionary<string, List<Item>> availableIngredients = new Dictionary<string, List<Item>>();
|
||||
|
||||
const float RefreshIngredientsInterval = 1.0f;
|
||||
private float refreshIngredientsTimer;
|
||||
|
||||
private bool hasPower;
|
||||
|
||||
private Character user;
|
||||
@@ -174,6 +179,8 @@ namespace Barotrauma.Items.Components
|
||||
if (selectedItem == null) { return; }
|
||||
if (!outputContainer.Inventory.CanBePut(selectedItem.TargetItem, selectedItem.OutCondition * selectedItem.TargetItem.Health)) { return; }
|
||||
|
||||
RefreshAvailableIngredients();
|
||||
|
||||
#if CLIENT
|
||||
itemList.Enabled = false;
|
||||
activateButton.Text = TextManager.Get("FabricatorCancel");
|
||||
@@ -242,7 +249,13 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
var availableIngredients = GetAvailableIngredients();
|
||||
if (refreshIngredientsTimer <= 0.0f)
|
||||
{
|
||||
RefreshAvailableIngredients();
|
||||
refreshIngredientsTimer = RefreshIngredientsInterval;
|
||||
}
|
||||
refreshIngredientsTimer -= deltaTime;
|
||||
|
||||
if (fabricatedItem == null || !CanBeFabricated(fabricatedItem, availableIngredients, user))
|
||||
{
|
||||
CancelFabricating();
|
||||
@@ -271,56 +284,87 @@ namespace Barotrauma.Items.Components
|
||||
State = FabricatorState.Active;
|
||||
}
|
||||
|
||||
float tinkeringStrength = 0f;
|
||||
var repairable = item.GetComponent<Repairable>();
|
||||
if (repairable != null)
|
||||
{
|
||||
repairable.LastActiveTime = (float)Timing.TotalTime + 10.0f;
|
||||
if (repairable.IsTinkering)
|
||||
{
|
||||
tinkeringStrength = repairable.TinkeringStrength;
|
||||
}
|
||||
}
|
||||
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
|
||||
|
||||
if (powerConsumption <= 0) { Voltage = 1.0f; }
|
||||
|
||||
float tinkeringStrength = 0f;
|
||||
if (repairable.IsTinkering)
|
||||
{
|
||||
tinkeringStrength = repairable.TinkeringStrength;
|
||||
}
|
||||
float fabricationSpeedIncrease = 1f + tinkeringStrength * TinkeringSpeedIncrease;
|
||||
|
||||
timeUntilReady -= deltaTime * fabricationSpeedIncrease * Math.Min(Voltage, 1.0f);
|
||||
|
||||
UpdateRequiredTimeProjSpecific();
|
||||
|
||||
if (timeUntilReady > 0.0f) { return; }
|
||||
if (timeUntilReady <= 0.0f)
|
||||
{
|
||||
Fabricate();
|
||||
}
|
||||
}
|
||||
|
||||
private void Fabricate()
|
||||
{
|
||||
RefreshAvailableIngredients();
|
||||
if (fabricatedItem == null || !CanBeFabricated(fabricatedItem, availableIngredients, user))
|
||||
{
|
||||
CancelFabricating();
|
||||
return;
|
||||
}
|
||||
|
||||
bool ingredientsStolen = false;
|
||||
bool ingredientsAllowStealing = true;
|
||||
|
||||
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
fabricatedItem.RequiredItems.ForEach(requiredItem => {
|
||||
fabricatedItem.RequiredItems.ForEach(requiredItem =>
|
||||
{
|
||||
for (int usedPrefabsAmount = 0; usedPrefabsAmount < requiredItem.Amount; usedPrefabsAmount++)
|
||||
{
|
||||
foreach (ItemPrefab requiredPrefab in requiredItem.ItemPrefabs)
|
||||
{
|
||||
if (!availableIngredients.ContainsKey(requiredPrefab.Identifier)) { continue; }
|
||||
|
||||
var availablePrefabs = availableIngredients[requiredPrefab.Identifier];
|
||||
var availablePrefab = availablePrefabs.FirstOrDefault(potentialPrefab =>
|
||||
var availableItems = availableIngredients[requiredPrefab.Identifier];
|
||||
var availableItem = availableItems.FirstOrDefault(potentialPrefab =>
|
||||
{
|
||||
return potentialPrefab.ConditionPercentage >= requiredItem.MinCondition * 100.0f &&
|
||||
potentialPrefab.ConditionPercentage <= requiredItem.MaxCondition * 100.0f;
|
||||
});
|
||||
|
||||
if (availablePrefab == null) { continue; }
|
||||
if (availableItem == null) { continue; }
|
||||
|
||||
if (requiredItem.UseCondition && availablePrefab.ConditionPercentage - requiredItem.MinCondition * 100 > 0.0f) //Leave it behind with reduced condition if it has enough to stay above 0
|
||||
ingredientsStolen |= availableItem.StolenDuringRound;
|
||||
if (!availableItem.AllowStealing)
|
||||
{
|
||||
availablePrefab.Condition -= availablePrefab.Prefab.Health * requiredItem.MinCondition;
|
||||
continue;
|
||||
ingredientsAllowStealing = false;
|
||||
}
|
||||
|
||||
availablePrefabs.Remove(availablePrefab);
|
||||
Entity.Spawner.AddToRemoveQueue(availablePrefab);
|
||||
inputContainer.Inventory.RemoveItem(availablePrefab);
|
||||
//Leave it behind with reduced condition if it has enough to stay above 0
|
||||
if (requiredItem.UseCondition && availableItem.ConditionPercentage - requiredItem.MinCondition * 100 > 0.0f)
|
||||
{
|
||||
availableItem.Condition -= availableItem.Prefab.Health * requiredItem.MinCondition;
|
||||
continue;
|
||||
}
|
||||
if (availableItem.OwnInventory != null)
|
||||
{
|
||||
foreach (Item containedItem in availableItem.OwnInventory.AllItemsMod)
|
||||
{
|
||||
containedItem.Drop(dropper: null);
|
||||
}
|
||||
}
|
||||
|
||||
availableItems.Remove(availableItem);
|
||||
Entity.Spawner.AddToRemoveQueue(availableItem);
|
||||
inputContainer.Inventory.RemoveItem(availableItem);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -351,6 +395,9 @@ namespace Barotrauma.Items.Components
|
||||
onSpawned: (Item spawnedItem) =>
|
||||
{
|
||||
onItemSpawned(spawnedItem, tempUser);
|
||||
spawnedItem.Quality = quality;
|
||||
spawnedItem.StolenDuringRound = ingredientsStolen;
|
||||
spawnedItem.AllowStealing = ingredientsAllowStealing;
|
||||
//reset the condition in case the max condition is higher than the prefab's due to e.g. quality modifiers
|
||||
spawnedItem.Condition = spawnedItem.MaxCondition * outCondition;
|
||||
});
|
||||
@@ -361,6 +408,9 @@ namespace Barotrauma.Items.Components
|
||||
onSpawned: (Item spawnedItem) =>
|
||||
{
|
||||
onItemSpawned(spawnedItem, tempUser);
|
||||
spawnedItem.Quality = quality;
|
||||
spawnedItem.StolenDuringRound = ingredientsStolen;
|
||||
spawnedItem.AllowStealing = ingredientsAllowStealing;
|
||||
//reset the condition in case the max condition is higher than the prefab's due to e.g. quality modifiers
|
||||
spawnedItem.Condition = spawnedItem.MaxCondition * outCondition;
|
||||
});
|
||||
@@ -408,6 +458,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
CancelFabricating();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private int GetFabricatedItemQuality(FabricationRecipe fabricatedItem, Character user)
|
||||
@@ -446,8 +497,8 @@ namespace Barotrauma.Items.Components
|
||||
var availablePrefabs = availableIngredients[requiredPrefab.Identifier];
|
||||
foreach (Item availablePrefab in availablePrefabs)
|
||||
{
|
||||
if (availablePrefab.Condition / availablePrefab.Prefab.Health >= requiredItem.MinCondition &&
|
||||
availablePrefab.Condition / availablePrefab.Prefab.Health <= requiredItem.MaxCondition)
|
||||
if (availablePrefab.ConditionPercentage / 100.0f >= requiredItem.MinCondition &&
|
||||
availablePrefab.ConditionPercentage / 100.0f <= requiredItem.MaxCondition)
|
||||
{
|
||||
availablePrefabsAmount++;
|
||||
}
|
||||
@@ -490,14 +541,10 @@ namespace Barotrauma.Items.Components
|
||||
return SkillRequirementMultiplier;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a list of all items available in the input container and linked containers
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private Dictionary<string, List<Item>> GetAvailableIngredients()
|
||||
private void RefreshAvailableIngredients()
|
||||
{
|
||||
List<Item> availableIngredients = new List<Item>();
|
||||
availableIngredients.AddRange(inputContainer.Inventory.AllItems);
|
||||
List<Item> itemList = new List<Item>();
|
||||
itemList.AddRange(inputContainer.Inventory.AllItems);
|
||||
foreach (MapEntity linkedTo in item.linkedTo)
|
||||
{
|
||||
if (linkedTo is Item linkedItem)
|
||||
@@ -511,34 +558,38 @@ namespace Barotrauma.Items.Components
|
||||
itemContainer = deconstructor.OutputContainer;
|
||||
}
|
||||
|
||||
availableIngredients.AddRange(itemContainer.Inventory.AllItems);
|
||||
itemList.AddRange(itemContainer.Inventory.AllItems);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < itemList.Count; i++)
|
||||
{
|
||||
var container = itemList[i].GetComponent<ItemContainer>();
|
||||
if (container != null)
|
||||
{
|
||||
itemList.AddRange(container.Inventory.AllItems);
|
||||
}
|
||||
}
|
||||
#if CLIENT
|
||||
if (Character.Controlled?.Inventory != null)
|
||||
{
|
||||
availableIngredients.AddRange(Character.Controlled.Inventory.AllItems);
|
||||
itemList.AddRange(Character.Controlled.Inventory.AllItems);
|
||||
}
|
||||
#else
|
||||
if (user?.Inventory != null)
|
||||
{
|
||||
availableIngredients.AddRange(user.Inventory.AllItems);
|
||||
itemList.AddRange(user.Inventory.AllItems);
|
||||
}
|
||||
#endif
|
||||
|
||||
Dictionary<string, List<Item>> ingredientsDictionary = new Dictionary<string, List<Item>>();
|
||||
for (int i = 0; i < availableIngredients.Count; i++)
|
||||
availableIngredients.Clear();
|
||||
foreach (Item item in itemList)
|
||||
{
|
||||
var itemIdentifier = availableIngredients[i].prefab.Identifier;
|
||||
if (!ingredientsDictionary.ContainsKey(itemIdentifier))
|
||||
var itemIdentifier = item.prefab.Identifier;
|
||||
if (!availableIngredients.ContainsKey(itemIdentifier))
|
||||
{
|
||||
ingredientsDictionary[itemIdentifier] = new List<Item>(availableIngredients.Count);
|
||||
availableIngredients[itemIdentifier] = new List<Item>(itemList.Count);
|
||||
}
|
||||
|
||||
ingredientsDictionary[itemIdentifier].Add(availableIngredients[i]);
|
||||
availableIngredients[itemIdentifier].Add(item);
|
||||
}
|
||||
|
||||
return ingredientsDictionary;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -552,7 +603,6 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
bool isClient = GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient;
|
||||
|
||||
var availableIngredients = GetAvailableIngredients();
|
||||
targetItem.RequiredItems.ForEach(requiredItem => {
|
||||
for (int i = 0; i < requiredItem.Amount; i++)
|
||||
{
|
||||
@@ -588,6 +638,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
});
|
||||
RefreshAvailableIngredients();
|
||||
}
|
||||
|
||||
public override XElement Save(XElement parentElement)
|
||||
|
||||
@@ -190,28 +190,38 @@ namespace Barotrauma.Items.Components
|
||||
#if CLIENT
|
||||
if (GameMain.Client != null) { return false; }
|
||||
#endif
|
||||
|
||||
if (objective.Option.Equals("stoppumping", StringComparison.OrdinalIgnoreCase))
|
||||
switch (objective.Option.ToLowerInvariant())
|
||||
{
|
||||
case "pumpout":
|
||||
#if SERVER
|
||||
if (objective.Override || FlowPercentage > 0.0f)
|
||||
{
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
if (objective.Override || !IsActive || FlowPercentage > -100.0f)
|
||||
{
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
#endif
|
||||
IsActive = false;
|
||||
FlowPercentage = 0.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
IsActive = true;
|
||||
FlowPercentage = -100.0f;
|
||||
break;
|
||||
case "pumpin":
|
||||
#if SERVER
|
||||
if (objective.Override || !IsActive || FlowPercentage > -100.0f)
|
||||
{
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
if (objective.Override || !IsActive || FlowPercentage < 100.0f)
|
||||
{
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
#endif
|
||||
IsActive = true;
|
||||
FlowPercentage = -100.0f;
|
||||
IsActive = true;
|
||||
FlowPercentage = 100.0f;
|
||||
break;
|
||||
case "stoppumping":
|
||||
#if SERVER
|
||||
if (objective.Override || FlowPercentage > 0.0f)
|
||||
{
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
#endif
|
||||
IsActive = false;
|
||||
FlowPercentage = 0.0f;
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ namespace Barotrauma.Items.Components
|
||||
partial class Reactor : Powered, IServerSerializable, IClientSerializable
|
||||
{
|
||||
const float NetworkUpdateIntervalHigh = 0.5f;
|
||||
const float NetworkUpdateIntervalLow = 10.0f;
|
||||
|
||||
//the rate at which the reactor is being run on (higher rate -> higher temperature)
|
||||
private float fissionRate;
|
||||
@@ -38,9 +37,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private float maxPowerOutput;
|
||||
|
||||
private Queue<float> loadQueue = new Queue<float>();
|
||||
private float load;
|
||||
|
||||
private readonly Queue<float> loadQueue = new Queue<float>();
|
||||
|
||||
private bool unsentChanges;
|
||||
private float sendUpdateTimer;
|
||||
|
||||
@@ -158,11 +156,6 @@ namespace Barotrauma.Items.Components
|
||||
set { /*do nothing*/ }
|
||||
}
|
||||
|
||||
private float correctTurbineOutput;
|
||||
|
||||
private float targetFissionRate;
|
||||
private float targetTurbineOutput;
|
||||
|
||||
[Serialize(false, true, description: "Is the automatic temperature control currently on. Indended to be used by StatusEffect conditionals (setting the value from XML is not recommended).")]
|
||||
public bool AutoTemp
|
||||
{
|
||||
@@ -181,6 +174,18 @@ namespace Barotrauma.Items.Components
|
||||
[Serialize(0.0f, true)]
|
||||
public float AvailableFuel { get; set; }
|
||||
|
||||
[Serialize(0.0f, true)]
|
||||
public new float Load { get; private set; }
|
||||
|
||||
[Serialize(0.0f, true)]
|
||||
public float TargetFissionRate { get; set; }
|
||||
|
||||
[Serialize(0.0f, true)]
|
||||
public float TargetTurbineOutput { get; set; }
|
||||
|
||||
[Serialize(0.0f, true)]
|
||||
public float CorrectTurbineOutput { get; set; }
|
||||
|
||||
public Reactor(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
@@ -199,8 +204,8 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
GameServer.Log(GameServer.CharacterLogName(lastUser) + " adjusted reactor settings: " +
|
||||
"Temperature: " + (int)(temperature * 100.0f) +
|
||||
", Fission rate: " + (int)targetFissionRate +
|
||||
", Turbine output: " + (int)targetTurbineOutput +
|
||||
", Fission rate: " + (int)TargetFissionRate +
|
||||
", Turbine output: " + (int)TargetTurbineOutput +
|
||||
(autoTemp ? ", Autotemp ON" : ", Autotemp OFF"),
|
||||
ServerLog.MessageType.ItemInteraction);
|
||||
|
||||
@@ -223,7 +228,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
if(PowerOn && AvailableFuel < 1)
|
||||
if (PowerOn && AvailableFuel < 1)
|
||||
{
|
||||
HintManager.OnReactorOutOfFuel(this);
|
||||
}
|
||||
@@ -236,15 +241,15 @@ namespace Barotrauma.Items.Components
|
||||
//so the player doesn't have to keep adjusting the rate impossibly fast when the load fluctuates heavily
|
||||
if (!MathUtils.NearlyEqual(MaxPowerOutput, 0.0f))
|
||||
{
|
||||
correctTurbineOutput += MathHelper.Clamp((load / MaxPowerOutput * 100.0f) - correctTurbineOutput, -10.0f, 10.0f) * deltaTime;
|
||||
CorrectTurbineOutput += MathHelper.Clamp((Load / MaxPowerOutput * 100.0f) - CorrectTurbineOutput, -10.0f, 10.0f) * deltaTime;
|
||||
}
|
||||
|
||||
//calculate tolerances of the meters based on the skills of the user
|
||||
//more skilled characters have larger "sweet spots", making it easier to keep the power output at a suitable level
|
||||
float tolerance = MathHelper.Lerp(2.5f, 10.0f, degreeOfSuccess);
|
||||
optimalTurbineOutput = new Vector2(correctTurbineOutput - tolerance, correctTurbineOutput + tolerance);
|
||||
optimalTurbineOutput = new Vector2(CorrectTurbineOutput - tolerance, CorrectTurbineOutput + tolerance);
|
||||
tolerance = MathHelper.Lerp(5.0f, 20.0f, degreeOfSuccess);
|
||||
allowedTurbineOutput = new Vector2(correctTurbineOutput - tolerance, correctTurbineOutput + tolerance);
|
||||
allowedTurbineOutput = new Vector2(CorrectTurbineOutput - tolerance, CorrectTurbineOutput + tolerance);
|
||||
|
||||
optimalTemperature = Vector2.Lerp(new Vector2(40.0f, 60.0f), new Vector2(30.0f, 70.0f), degreeOfSuccess);
|
||||
allowedTemperature = Vector2.Lerp(new Vector2(30.0f, 70.0f), new Vector2(10.0f, 90.0f), degreeOfSuccess);
|
||||
@@ -260,9 +265,9 @@ namespace Barotrauma.Items.Components
|
||||
Temperature += MathHelper.Clamp(Math.Sign(temperatureDiff) * 10.0f * deltaTime, -Math.Abs(temperatureDiff), Math.Abs(temperatureDiff));
|
||||
//if (item.InWater && AvailableFuel < 100.0f) Temperature -= 12.0f * deltaTime;
|
||||
|
||||
FissionRate = MathHelper.Lerp(fissionRate, Math.Min(targetFissionRate, AvailableFuel), deltaTime);
|
||||
FissionRate = MathHelper.Lerp(fissionRate, Math.Min(TargetFissionRate, AvailableFuel), deltaTime);
|
||||
|
||||
TurbineOutput = MathHelper.Lerp(turbineOutput, targetTurbineOutput, deltaTime);
|
||||
TurbineOutput = MathHelper.Lerp(turbineOutput, TargetTurbineOutput, deltaTime);
|
||||
|
||||
float temperatureFactor = Math.Min(temperature / 50.0f, 1.0f);
|
||||
currPowerConsumption = -MaxPowerOutput * Math.Min(turbineOutput / 100.0f, temperatureFactor);
|
||||
@@ -276,7 +281,7 @@ namespace Barotrauma.Items.Components
|
||||
float maxAutoAdjust = maxPowerOutput * 0.1f;
|
||||
autoAdjustAmount = MathHelper.Lerp(
|
||||
autoAdjustAmount,
|
||||
MathHelper.Clamp(-load - currPowerConsumption, -maxAutoAdjust, maxAutoAdjust),
|
||||
MathHelper.Clamp(-Load - currPowerConsumption, -maxAutoAdjust, maxAutoAdjust),
|
||||
deltaTime * 10.0f);
|
||||
}
|
||||
else
|
||||
@@ -287,8 +292,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (!PowerOn)
|
||||
{
|
||||
targetFissionRate = 0.0f;
|
||||
targetTurbineOutput = 0.0f;
|
||||
TargetFissionRate = 0.0f;
|
||||
TargetTurbineOutput = 0.0f;
|
||||
}
|
||||
else if (autoTemp)
|
||||
{
|
||||
@@ -317,56 +322,30 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
if (!loadQueue.Any() && PowerOn)
|
||||
{
|
||||
//loadQueue is empty, round must've just started
|
||||
//reset the fission rate, turbine output and
|
||||
//temperature to optimal levels to prevent fires
|
||||
//at the start of the round
|
||||
correctTurbineOutput = MathUtils.NearlyEqual(MaxPowerOutput, 0.0f) ? 0.0f : currentLoad / MaxPowerOutput * 100.0f;
|
||||
tolerance = MathHelper.Lerp(2.5f, 10.0f, degreeOfSuccess);
|
||||
optimalTurbineOutput = new Vector2(correctTurbineOutput - tolerance, correctTurbineOutput + tolerance);
|
||||
tolerance = MathHelper.Lerp(5.0f, 20.0f, degreeOfSuccess);
|
||||
allowedTurbineOutput = new Vector2(correctTurbineOutput - tolerance, correctTurbineOutput + tolerance);
|
||||
|
||||
DebugConsole.Log($"Degree of success: {degreeOfSuccess}");
|
||||
DebugConsole.Log($"Current load: {currentLoad}");
|
||||
DebugConsole.Log($"Max power output: {MaxPowerOutput}");
|
||||
DebugConsole.Log($"Available fuel: {AvailableFuel}");
|
||||
|
||||
float desiredTurbineOutput = MathHelper.Clamp(correctTurbineOutput, 0.0f, 100.0f);
|
||||
DebugConsole.Log($"Turbine output reset: {targetTurbineOutput}, {turbineOutput} -> {desiredTurbineOutput}");
|
||||
targetTurbineOutput = desiredTurbineOutput;
|
||||
turbineOutput = desiredTurbineOutput;
|
||||
|
||||
float desiredTemperature = (optimalTemperature.X + optimalTemperature.Y) / 2.0f;
|
||||
DebugConsole.Log($"Temperature reset: {temperature} -> {desiredTemperature}");
|
||||
temperature = desiredTemperature;
|
||||
|
||||
float desiredFissionRate = GetFissionRateForTargetTemperatureAndTurbineOutput(desiredTemperature, desiredTurbineOutput);
|
||||
DebugConsole.Log($"Fission rate reset: {targetFissionRate}, {fissionRate} -> {desiredFissionRate}");
|
||||
targetFissionRate = desiredFissionRate;
|
||||
fissionRate = desiredFissionRate;
|
||||
}
|
||||
|
||||
loadQueue.Enqueue(currentLoad);
|
||||
while (loadQueue.Count() > 60.0f)
|
||||
{
|
||||
load = loadQueue.Average();
|
||||
Load = loadQueue.Average();
|
||||
loadQueue.Dequeue();
|
||||
}
|
||||
|
||||
float fuelLeft = 0.0f;
|
||||
var containedItems = item.OwnInventory?.AllItems;
|
||||
if (containedItems != null)
|
||||
{
|
||||
foreach (Item item in containedItems)
|
||||
{
|
||||
if (!item.HasTag("reactorfuel")) { continue; }
|
||||
if (fissionRate > 0.0f)
|
||||
{
|
||||
item.Condition -= fissionRate / 100.0f * fuelConsumptionRate * deltaTime;
|
||||
}
|
||||
fuelLeft += item.ConditionPercentage;
|
||||
}
|
||||
}
|
||||
|
||||
if (fissionRate > 0.0f)
|
||||
{
|
||||
var containedItems = item.OwnInventory?.AllItems;
|
||||
if (containedItems != null)
|
||||
{
|
||||
foreach (Item item in containedItems)
|
||||
{
|
||||
if (!item.HasTag("reactorfuel")) { continue; }
|
||||
item.Condition -= fissionRate / 100.0f * fuelConsumptionRate * deltaTime;
|
||||
}
|
||||
}
|
||||
if (item.AiTarget != null && MaxPowerOutput > 0)
|
||||
{
|
||||
var aiTarget = item.AiTarget;
|
||||
@@ -385,8 +364,9 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
item.SendSignal(((int)(temperature * 100.0f)).ToString(), "temperature_out");
|
||||
item.SendSignal(((int)-CurrPowerConsumption).ToString(), "power_value_out");
|
||||
item.SendSignal(((int)load).ToString(), "load_value_out");
|
||||
item.SendSignal(((int)Load).ToString(), "load_value_out");
|
||||
item.SendSignal(((int)AvailableFuel).ToString(), "fuel_out");
|
||||
item.SendSignal(((int)fuelLeft).ToString(), "fuel_percentage_left");
|
||||
|
||||
UpdateFailures(deltaTime);
|
||||
#if CLIENT
|
||||
@@ -407,8 +387,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
#endif
|
||||
#if CLIENT
|
||||
#elif CLIENT
|
||||
if (GameMain.Client != null)
|
||||
{
|
||||
item.CreateClientEvent(this);
|
||||
@@ -424,12 +403,6 @@ namespace Barotrauma.Items.Components
|
||||
return fissionRate * (prevAvailableFuel / 100.0f) * 2.0f;
|
||||
}
|
||||
|
||||
private float GetFissionRateForTargetTemperatureAndTurbineOutput(float temperature, float turbineOutput)
|
||||
{
|
||||
if (MathUtils.NearlyEqual(AvailableFuel, 0f)) { return 0f; }
|
||||
return (temperature + turbineOutput) / (AvailableFuel / 100f) / 2f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Do we need more fuel to generate enough power to match the current load.
|
||||
/// </summary>
|
||||
@@ -438,7 +411,7 @@ namespace Barotrauma.Items.Components
|
||||
private bool NeedMoreFuel(float minimumOutputRatio, float minCondition = 0)
|
||||
{
|
||||
float remainingFuel = item.ContainedItems.Sum(i => i.Condition);
|
||||
if (remainingFuel <= minCondition && load > 0.0f)
|
||||
if (remainingFuel <= minCondition && Load > 0.0f)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -455,7 +428,7 @@ namespace Barotrauma.Items.Components
|
||||
float theoreticalMaxOutput = Math.Min(maxTurbineOutput / 100.0f, temperatureFactor) * MaxPowerOutput;
|
||||
|
||||
//maximum output not enough, we need more fuel
|
||||
return theoreticalMaxOutput < load * minimumOutputRatio;
|
||||
return theoreticalMaxOutput < Load * minimumOutputRatio;
|
||||
}
|
||||
|
||||
private bool TooMuchFuel()
|
||||
@@ -467,7 +440,7 @@ namespace Barotrauma.Items.Components
|
||||
float minimumHeat = GetGeneratedHeat(optimalFissionRate.X);
|
||||
|
||||
//if we need a very high turbine output to keep the engine from overheating, there's too much fuel
|
||||
return minimumHeat > Math.Min(correctTurbineOutput * 1.5f, 90);
|
||||
return minimumHeat > Math.Min(CorrectTurbineOutput * 1.5f, 90);
|
||||
}
|
||||
|
||||
private void UpdateFailures(float deltaTime)
|
||||
@@ -514,26 +487,26 @@ namespace Barotrauma.Items.Components
|
||||
public void UpdateAutoTemp(float speed, float deltaTime)
|
||||
{
|
||||
float desiredTurbineOutput = (optimalTurbineOutput.X + optimalTurbineOutput.Y) / 2.0f;
|
||||
targetTurbineOutput += MathHelper.Clamp(desiredTurbineOutput - targetTurbineOutput, -speed, speed) * deltaTime;
|
||||
targetTurbineOutput = MathHelper.Clamp(targetTurbineOutput, 0.0f, 100.0f);
|
||||
TargetTurbineOutput += MathHelper.Clamp(desiredTurbineOutput - TargetTurbineOutput, -speed, speed) * deltaTime;
|
||||
TargetTurbineOutput = MathHelper.Clamp(TargetTurbineOutput, 0.0f, 100.0f);
|
||||
|
||||
float desiredFissionRate = (optimalFissionRate.X + optimalFissionRate.Y) / 2.0f;
|
||||
targetFissionRate += MathHelper.Clamp(desiredFissionRate - targetFissionRate, -speed, speed) * deltaTime;
|
||||
TargetFissionRate += MathHelper.Clamp(desiredFissionRate - TargetFissionRate, -speed, speed) * deltaTime;
|
||||
|
||||
if (temperature > (optimalTemperature.X + optimalTemperature.Y) / 2.0f)
|
||||
{
|
||||
targetFissionRate = Math.Min(targetFissionRate - speed * 2 * deltaTime, allowedFissionRate.Y);
|
||||
TargetFissionRate = Math.Min(TargetFissionRate - speed * 2 * deltaTime, allowedFissionRate.Y);
|
||||
}
|
||||
else if (-currPowerConsumption < load)
|
||||
else if (-currPowerConsumption < Load)
|
||||
{
|
||||
targetFissionRate = Math.Min(targetFissionRate + speed * 2 * deltaTime, 100.0f);
|
||||
TargetFissionRate = Math.Min(TargetFissionRate + speed * 2 * deltaTime, 100.0f);
|
||||
}
|
||||
targetFissionRate = MathHelper.Clamp(targetFissionRate, 0.0f, 100.0f);
|
||||
TargetFissionRate = MathHelper.Clamp(TargetFissionRate, 0.0f, 100.0f);
|
||||
|
||||
//don't push the target too far from the current fission rate
|
||||
//otherwise we may "overshoot", cranking the target fission rate all the way up because it takes a while
|
||||
//for the actual fission rate and temperature to follow
|
||||
targetFissionRate = MathHelper.Clamp(targetFissionRate, FissionRate - 5, FissionRate + 5);
|
||||
TargetFissionRate = MathHelper.Clamp(TargetFissionRate, FissionRate - 5, FissionRate + 5);
|
||||
}
|
||||
|
||||
public void PowerUpImmediately()
|
||||
@@ -557,8 +530,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
currPowerConsumption = 0.0f;
|
||||
Temperature -= deltaTime * 1000.0f;
|
||||
targetFissionRate = Math.Max(targetFissionRate - deltaTime * 10.0f, 0.0f);
|
||||
targetTurbineOutput = Math.Max(targetTurbineOutput - deltaTime * 10.0f, 0.0f);
|
||||
TargetFissionRate = Math.Max(TargetFissionRate - deltaTime * 10.0f, 0.0f);
|
||||
TargetTurbineOutput = Math.Max(TargetTurbineOutput - deltaTime * 10.0f, 0.0f);
|
||||
#if CLIENT
|
||||
FissionRateScrollBar.BarScroll = 1.0f - FissionRate / 100.0f;
|
||||
TurbineOutputScrollBar.BarScroll = 1.0f - TurbineOutput / 100.0f;
|
||||
@@ -583,7 +556,6 @@ namespace Barotrauma.Items.Components
|
||||
containedItem.Condition = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
#if SERVER
|
||||
GameServer.Log("Reactor meltdown!", ServerLog.MessageType.ItemInteraction);
|
||||
if (GameMain.Server != null)
|
||||
@@ -696,15 +668,15 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
bool prevAutoTemp = autoTemp;
|
||||
bool prevPowerOn = _powerOn;
|
||||
float prevFissionRate = targetFissionRate;
|
||||
float prevTurbineOutput = targetTurbineOutput;
|
||||
float prevFissionRate = TargetFissionRate;
|
||||
float prevTurbineOutput = TargetTurbineOutput;
|
||||
|
||||
if (shutDown)
|
||||
{
|
||||
PowerOn = false;
|
||||
AutoTemp = false;
|
||||
targetFissionRate = 0.0f;
|
||||
targetTurbineOutput = 0.0f;
|
||||
TargetFissionRate = 0.0f;
|
||||
TargetTurbineOutput = 0.0f;
|
||||
unsentChanges = true;
|
||||
return true;
|
||||
}
|
||||
@@ -730,8 +702,8 @@ namespace Barotrauma.Items.Components
|
||||
#endif
|
||||
if (autoTemp != prevAutoTemp ||
|
||||
prevPowerOn != _powerOn ||
|
||||
Math.Abs(prevFissionRate - targetFissionRate) > 1.0f ||
|
||||
Math.Abs(prevTurbineOutput - targetTurbineOutput) > 1.0f)
|
||||
Math.Abs(prevFissionRate - TargetFissionRate) > 1.0f ||
|
||||
Math.Abs(prevTurbineOutput - TargetTurbineOutput) > 1.0f)
|
||||
{
|
||||
unsentChanges = true;
|
||||
}
|
||||
@@ -767,32 +739,32 @@ namespace Barotrauma.Items.Components
|
||||
switch (connection.Name)
|
||||
{
|
||||
case "shutdown":
|
||||
if (targetFissionRate > 0.0f || targetTurbineOutput > 0.0f)
|
||||
if (TargetFissionRate > 0.0f || TargetTurbineOutput > 0.0f)
|
||||
{
|
||||
PowerOn = false;
|
||||
AutoTemp = false;
|
||||
targetFissionRate = 0.0f;
|
||||
targetTurbineOutput = 0.0f;
|
||||
TargetFissionRate = 0.0f;
|
||||
TargetTurbineOutput = 0.0f;
|
||||
if (GameMain.NetworkMember?.IsServer ?? false) { unsentChanges = true; }
|
||||
}
|
||||
break;
|
||||
case "set_fissionrate":
|
||||
if (PowerOn && float.TryParse(signal.value, NumberStyles.Float, CultureInfo.InvariantCulture, out float newFissionRate))
|
||||
{
|
||||
targetFissionRate = MathHelper.Clamp(newFissionRate, 0.0f, 100.0f);
|
||||
TargetFissionRate = MathHelper.Clamp(newFissionRate, 0.0f, 100.0f);
|
||||
if (GameMain.NetworkMember?.IsServer ?? false) { unsentChanges = true; }
|
||||
#if CLIENT
|
||||
FissionRateScrollBar.BarScroll = targetFissionRate / 100.0f;
|
||||
FissionRateScrollBar.BarScroll = TargetFissionRate / 100.0f;
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
case "set_turbineoutput":
|
||||
if (PowerOn && float.TryParse(signal.value, NumberStyles.Float, CultureInfo.InvariantCulture, out float newTurbineOutput))
|
||||
{
|
||||
targetTurbineOutput = MathHelper.Clamp(newTurbineOutput, 0.0f, 100.0f);
|
||||
TargetTurbineOutput = MathHelper.Clamp(newTurbineOutput, 0.0f, 100.0f);
|
||||
if (GameMain.NetworkMember?.IsServer ?? false) { unsentChanges = true; }
|
||||
#if CLIENT
|
||||
TurbineOutputScrollBar.BarScroll = targetTurbineOutput / 100.0f;
|
||||
TurbineOutputScrollBar.BarScroll = TargetTurbineOutput / 100.0f;
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -17,6 +17,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public const float DefaultSonarRange = 10000.0f;
|
||||
|
||||
public const float PassivePowerConsumption = 0.1f;
|
||||
|
||||
class ConnectedTransducer
|
||||
{
|
||||
public readonly SonarTransducer Transducer;
|
||||
@@ -150,7 +152,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
currPowerConsumption = (currentMode == Mode.Active) ? powerConsumption : powerConsumption * 0.1f;
|
||||
currPowerConsumption = (currentMode == Mode.Active) ? powerConsumption : powerConsumption * PassivePowerConsumption;
|
||||
|
||||
UpdateOnActiveEffects(deltaTime);
|
||||
|
||||
@@ -332,7 +334,9 @@ namespace Barotrauma.Items.Components
|
||||
if (connection.Name == "transducer_in")
|
||||
{
|
||||
var transducer = signal.source.GetComponent<SonarTransducer>();
|
||||
if (transducer == null) return;
|
||||
if (transducer == null) { return; }
|
||||
|
||||
transducer.ConnectedSonar = this;
|
||||
|
||||
var connectedTransducer = connectedTransducers.Find(t => t.Transducer == transducer);
|
||||
if (connectedTransducer == null)
|
||||
|
||||
+3
-1
@@ -8,6 +8,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private float sendSignalTimer;
|
||||
|
||||
public Sonar ConnectedSonar;
|
||||
|
||||
public SonarTransducer(Item item, XElement element) : base(item, element)
|
||||
{
|
||||
IsActive = true;
|
||||
@@ -17,7 +19,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
UpdateOnActiveEffects(deltaTime);
|
||||
|
||||
CurrPowerConsumption = powerConsumption;
|
||||
CurrPowerConsumption = powerConsumption * (ConnectedSonar?.CurrentMode == Sonar.Mode.Active ? 1.0f : Sonar.PassivePowerConsumption);
|
||||
|
||||
if (Voltage >= MinVoltage)
|
||||
{
|
||||
|
||||
@@ -61,6 +61,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public List<Body> IgnoredBodies;
|
||||
|
||||
private Character stickTargetCharacter;
|
||||
|
||||
private Character _user;
|
||||
public Character User
|
||||
{
|
||||
@@ -614,8 +616,8 @@ namespace Barotrauma.Items.Components
|
||||
return;
|
||||
}
|
||||
|
||||
//target very far from the item -> update the item's transform to make sure it's inside the same sub as the target (or outside)
|
||||
if (Math.Abs(stickJoint.JointTranslation) > 100.0f)
|
||||
// Update the item's transform to make sure it's inside the same sub as the target (or outside)
|
||||
if (StickTarget?.UserData is Limb target && target.Submarine != item.Submarine || Math.Abs(stickJoint.JointTranslation) > 100.0f)
|
||||
{
|
||||
item.UpdateTransform();
|
||||
}
|
||||
@@ -866,7 +868,7 @@ namespace Barotrauma.Items.Components
|
||||
(DoesStick ||
|
||||
(StickToCharacters && (target.Body.UserData is Limb || target.Body.UserData is Character)) ||
|
||||
(StickToStructures && target.Body.UserData is Structure) ||
|
||||
(StickToItems && target.Body.UserData is Item)))
|
||||
(StickToItems && target.Body.UserData is Item)))
|
||||
{
|
||||
Vector2 dir = new Vector2(
|
||||
(float)Math.Cos(item.body.Rotation),
|
||||
@@ -965,9 +967,14 @@ namespace Barotrauma.Items.Components
|
||||
GameMain.World.Add(stickJoint);
|
||||
|
||||
IsActive = true;
|
||||
if (targetBody.UserData is Limb limb)
|
||||
{
|
||||
stickTargetCharacter = limb.character;
|
||||
stickTargetCharacter.AttachedProjectiles.Add(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void Unstick()
|
||||
public void Unstick()
|
||||
{
|
||||
StickTarget = null;
|
||||
if (stickJoint != null)
|
||||
@@ -979,25 +986,21 @@ namespace Barotrauma.Items.Components
|
||||
stickJoint = null;
|
||||
}
|
||||
if (!item.body.FarseerBody.IsBullet) { IsActive = false; }
|
||||
item.GetComponent<Rope>()?.Snap();
|
||||
if (stickTargetCharacter != null)
|
||||
{
|
||||
stickTargetCharacter.AttachedProjectiles.Remove(this);
|
||||
stickTargetCharacter = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void RemoveComponentSpecific()
|
||||
{
|
||||
base.RemoveComponentSpecific();
|
||||
if (stickJoint != null)
|
||||
if (IsStuckToTarget || stickJoint != null || stickTargetCharacter != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
GameMain.World.Remove(stickJoint);
|
||||
}
|
||||
catch
|
||||
{
|
||||
//the body that the projectile was stuck to has been removed
|
||||
}
|
||||
|
||||
stickJoint = null;
|
||||
Unstick();
|
||||
}
|
||||
|
||||
}
|
||||
partial void LaunchProjSpecific(Vector2 startLocation, Vector2 endLocation);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,10 @@ namespace Barotrauma.Items.Components
|
||||
RepairToolStructureRepairMultiplier,
|
||||
RepairToolStructureDamageMultiplier,
|
||||
RepairToolDeattachTimeMultiplier,
|
||||
StoppingPowerMultiplier,
|
||||
StrikingPowerMultiplier,
|
||||
StrikingSpeedMultiplier,
|
||||
FiringRateMultiplier,
|
||||
// unused as of now
|
||||
AttackMultiplier,
|
||||
AttackSpeedMultiplier,
|
||||
@@ -33,7 +37,6 @@ namespace Barotrauma.Items.Components
|
||||
RangedSpreadReduction,
|
||||
ChargeSpeedMultiplier,
|
||||
MovementSpeedMultiplier,
|
||||
// generic stats to be used for various needs, declared just in case (localization)
|
||||
EffectivenessMultiplier,
|
||||
PowerOutputMultiplier,
|
||||
ConsumptionReductionMultiplier,
|
||||
|
||||
@@ -105,11 +105,6 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public bool IsTinkering { get; private set; } = false;
|
||||
|
||||
public float RepairIconThreshold
|
||||
{
|
||||
get { return RepairThreshold / 2; }
|
||||
}
|
||||
|
||||
public Character CurrentFixer { get; private set; }
|
||||
private Item currentRepairItem;
|
||||
|
||||
@@ -118,6 +113,9 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public float TinkeringStrength => tinkeringStrength;
|
||||
|
||||
public bool IsBelowRepairThreshold => item.ConditionPercentage <= RepairThreshold;
|
||||
public bool IsBelowRepairIconThreshold => item.ConditionPercentage <= RepairThreshold / 2;
|
||||
|
||||
public enum FixActions : int
|
||||
{
|
||||
None = 0,
|
||||
@@ -179,8 +177,17 @@ namespace Barotrauma.Items.Components
|
||||
if (bestRepairItem != null && bestRepairItem.Prefab.CannotRepairFail) { return true; }
|
||||
|
||||
// unpowered (electrical) items can be repaired without a risk of electrical shock
|
||||
if (requiredSkills.Any(s => s != null && s.Identifier.Equals("electrical", StringComparison.OrdinalIgnoreCase)) &&
|
||||
item.GetComponent<Powered>() is Powered powered && powered.Voltage < 0.1f) { return true; }
|
||||
if (requiredSkills.Any(s => s != null && s.Identifier.Equals("electrical", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
if (item.GetComponent<Reactor>() is Reactor reactor)
|
||||
{
|
||||
if (MathUtils.NearlyEqual(reactor.CurrPowerConsumption, 0.0f, 0.1f)) { return true; }
|
||||
}
|
||||
else if (item.GetComponent<Powered>() is Powered powered && powered.Voltage < 0.1f)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (Rand.Range(0.0f, 0.5f) < RepairDegreeOfSuccess(character, requiredSkills)) { return true; }
|
||||
|
||||
@@ -393,7 +400,7 @@ namespace Barotrauma.Items.Components
|
||||
float successFactor = requiredSkills.Count == 0 ? 1.0f : RepairDegreeOfSuccess(CurrentFixer, requiredSkills);
|
||||
|
||||
//item must have been below the repair threshold for the player to get an achievement or XP for repairing it
|
||||
if (item.ConditionPercentage < RepairThreshold)
|
||||
if (IsBelowRepairThreshold)
|
||||
{
|
||||
wasBroken = true;
|
||||
}
|
||||
@@ -524,7 +531,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public void AdjustPowerConsumption(ref float powerConsumption)
|
||||
{
|
||||
if (item.ConditionPercentage < RepairThreshold)
|
||||
if (IsBelowRepairThreshold)
|
||||
{
|
||||
powerConsumption *= MathHelper.Lerp(1.5f, 1.0f, item.Condition / item.MaxCondition);
|
||||
}
|
||||
|
||||
@@ -306,9 +306,13 @@ namespace Barotrauma.Items.Components
|
||||
forceDir.X = Math.Clamp(forceDir.X, -0.1f, 0.1f);
|
||||
}
|
||||
}
|
||||
float force = LerpForces ? MathHelper.Lerp(0, TargetPullForce, MathUtils.InverseLerp(0, MaxLength / 3, distance)) : TargetPullForce;
|
||||
float force = LerpForces ? MathHelper.Lerp(0, TargetPullForce, MathUtils.InverseLerp(0, MaxLength / 3, distance - 50)) : TargetPullForce;
|
||||
targetBody?.ApplyForce(-forceDir * force);
|
||||
targetCharacter?.AnimController.Collider.ApplyForce(-forceDir * force * 3);
|
||||
var targetRagdoll = targetCharacter?.AnimController;
|
||||
if (targetRagdoll != null && (targetRagdoll.InWater || targetRagdoll.OnGround))
|
||||
{
|
||||
targetRagdoll.Collider.ApplyForce(-forceDir * force * 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,10 @@ namespace Barotrauma.Items.Components
|
||||
get { return timeFrame; }
|
||||
set
|
||||
{
|
||||
if (value > timeFrame)
|
||||
{
|
||||
timeSinceReceived[0] = timeSinceReceived[1] = Math.Max(value * 2.0f, 0.1f);
|
||||
}
|
||||
timeFrame = Math.Max(0.0f, value);
|
||||
}
|
||||
}
|
||||
|
||||
+4
@@ -39,6 +39,10 @@ namespace Barotrauma.Items.Components
|
||||
get { return timeFrame; }
|
||||
set
|
||||
{
|
||||
if (value > timeFrame)
|
||||
{
|
||||
timeSinceReceived[0] = timeSinceReceived[1] = Math.Max(value * 2.0f, 0.1f);
|
||||
}
|
||||
timeFrame = Math.Max(0.0f, value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
using Microsoft.Xna.Framework;
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
class DelayComponent : ItemComponent
|
||||
@@ -114,6 +114,18 @@ namespace Barotrauma.Items.Components
|
||||
};
|
||||
signalQueue.Enqueue(prevQueuedSignal);
|
||||
break;
|
||||
case "set_delay":
|
||||
if (float.TryParse(signal.value, out float newDelay))
|
||||
{
|
||||
newDelay = MathHelper.Clamp(newDelay, 0, 60);
|
||||
if (signalQueue.Count > 0 && newDelay != Delay)
|
||||
{
|
||||
prevQueuedSignal = null;
|
||||
signalQueue.Clear();
|
||||
}
|
||||
Delay = newDelay;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +62,10 @@ namespace Barotrauma.Items.Components
|
||||
get { return timeFrame; }
|
||||
set
|
||||
{
|
||||
if (value > timeFrame)
|
||||
{
|
||||
timeSinceReceived[0] = timeSinceReceived[1] = Math.Max(value * 2.0f, 0.1f);
|
||||
}
|
||||
timeFrame = Math.Max(0.0f, value);
|
||||
}
|
||||
}
|
||||
|
||||
+22
-32
@@ -43,7 +43,16 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public float Rotation;
|
||||
private float rotation;
|
||||
public float Rotation
|
||||
{
|
||||
get { return rotation; }
|
||||
set
|
||||
{
|
||||
rotation = value;
|
||||
SetLightSourceTransform();
|
||||
}
|
||||
}
|
||||
|
||||
[Editable, Serialize(true, true, description: "Should structures cast shadows when light from this light source hits them. " +
|
||||
"Disabling shadows increases the performance of the game, and is recommended for lights with a short range.", alwaysUseInstanceValues: true)]
|
||||
@@ -246,39 +255,14 @@ namespace Barotrauma.Items.Components
|
||||
SetLightSourceState(false, 0.0f);
|
||||
return;
|
||||
}
|
||||
#if CLIENT
|
||||
if (ParentBody != null)
|
||||
{
|
||||
Light.Position = ParentBody.Position;
|
||||
}
|
||||
else if (turret != null)
|
||||
{
|
||||
Light.Position = new Vector2(item.Rect.X + turret.TransformedBarrelPos.X, item.Rect.Y - turret.TransformedBarrelPos.Y);
|
||||
}
|
||||
else
|
||||
{
|
||||
Light.Position = item.Position;
|
||||
}
|
||||
#endif
|
||||
|
||||
SetLightSourceTransform();
|
||||
|
||||
PhysicsBody body = ParentBody ?? item.body;
|
||||
if (body != null)
|
||||
if (body != null && !body.Enabled)
|
||||
{
|
||||
#if CLIENT
|
||||
Light.Rotation = body.Dir > 0.0f ? body.DrawRotation : body.DrawRotation - MathHelper.Pi;
|
||||
Light.LightSpriteEffect = (body.Dir > 0.0f) ? SpriteEffects.None : SpriteEffects.FlipVertically;
|
||||
#endif
|
||||
if (!body.Enabled)
|
||||
{
|
||||
SetLightSourceState(false, 0.0f);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
#if CLIENT
|
||||
Light.Rotation = -Rotation - MathHelper.ToRadians(item.Rotation);
|
||||
Light.LightSpriteEffect = item.SpriteEffects;
|
||||
#endif
|
||||
SetLightSourceState(false, 0.0f);
|
||||
return;
|
||||
}
|
||||
|
||||
currPowerConsumption = powerConsumption;
|
||||
@@ -333,6 +317,9 @@ namespace Barotrauma.Items.Components
|
||||
if (signal.value != prevColorSignal)
|
||||
{
|
||||
LightColor = XMLExtensions.ParseColor(signal.value, false);
|
||||
#if CLIENT
|
||||
SetLightSourceState(Light.Enabled, currentBrightness);
|
||||
#endif
|
||||
prevColorSignal = signal.value;
|
||||
}
|
||||
break;
|
||||
@@ -350,5 +337,8 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
partial void SetLightSourceState(bool enabled, float brightness);
|
||||
|
||||
partial void SetLightSourceTransform();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,12 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
protected bool writeable = true;
|
||||
[Editable, Serialize(true, true, description: "Can the value stored in the memory component be changed via signals.", alwaysUseInstanceValues: true)]
|
||||
public bool Writeable
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public MemoryComponent(Item item, XElement element)
|
||||
: base(item, element)
|
||||
@@ -54,7 +59,7 @@ namespace Barotrauma.Items.Components
|
||||
switch (connection.Name)
|
||||
{
|
||||
case "signal_in":
|
||||
if (writeable)
|
||||
if (Writeable)
|
||||
{
|
||||
string prevValue = Value;
|
||||
Value = signal.value;
|
||||
@@ -66,7 +71,7 @@ namespace Barotrauma.Items.Components
|
||||
break;
|
||||
case "signal_store":
|
||||
case "lock_state":
|
||||
writeable = signal.value == "1";
|
||||
Writeable = signal.value == "1";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,6 +131,13 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(true, true, description: "Should the sensor trigger when the item itself moves.")]
|
||||
public bool DetectOwnMotion
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public MotionSensor(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
@@ -168,7 +175,7 @@ namespace Barotrauma.Items.Components
|
||||
MotionDetected = false;
|
||||
updateTimer = UpdateInterval;
|
||||
|
||||
if (item.body != null && item.body.Enabled)
|
||||
if (item.body != null && item.body.Enabled && DetectOwnMotion)
|
||||
{
|
||||
if (Math.Abs(item.body.LinearVelocity.X) > MinimumVelocity || Math.Abs(item.body.LinearVelocity.Y) > MinimumVelocity)
|
||||
{
|
||||
|
||||
+12
-4
@@ -6,6 +6,8 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
class RegExFindComponent : ItemComponent
|
||||
{
|
||||
private static readonly TimeSpan timeout = TimeSpan.FromSeconds(Timing.Step);
|
||||
|
||||
private string expression;
|
||||
|
||||
private string receivedSignal;
|
||||
@@ -67,7 +69,10 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
try
|
||||
{
|
||||
regex = new Regex(@expression);
|
||||
regex = new Regex(
|
||||
@expression,
|
||||
options: RegexOptions.None,
|
||||
matchTimeout: timeout);
|
||||
}
|
||||
|
||||
catch
|
||||
@@ -97,11 +102,14 @@ namespace Barotrauma.Items.Components
|
||||
previousResult = match.Success;
|
||||
previousGroups = UseCaptureGroup && previousResult ? match.Groups : null;
|
||||
previousReceivedSignal = receivedSignal;
|
||||
|
||||
}
|
||||
catch
|
||||
catch (Exception e)
|
||||
{
|
||||
item.SendSignal("ERROR", "signal_out");
|
||||
item.SendSignal(
|
||||
e is RegexMatchTimeoutException
|
||||
? "TIMEOUT"
|
||||
: "ERROR",
|
||||
"signal_out");
|
||||
previousResult = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,10 @@ namespace Barotrauma.Items.Components
|
||||
get { return timeFrame; }
|
||||
set
|
||||
{
|
||||
if (value > timeFrame)
|
||||
{
|
||||
timeSinceReceived[0] = timeSinceReceived[1] = Math.Max(value * 2.0f, 0.1f);
|
||||
}
|
||||
timeFrame = Math.Max(0.0f, value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,16 +2,35 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
readonly struct TerminalMessage
|
||||
{
|
||||
public readonly string Text;
|
||||
public readonly Color Color;
|
||||
|
||||
public TerminalMessage(string text, Color color)
|
||||
{
|
||||
Text = text;
|
||||
Color = color;
|
||||
}
|
||||
|
||||
public void Deconstruct(out string text, out Color color)
|
||||
{
|
||||
text = Text;
|
||||
color = Color;
|
||||
}
|
||||
}
|
||||
|
||||
partial class Terminal : ItemComponent
|
||||
{
|
||||
private const int MaxMessageLength = ChatMessage.MaxLength;
|
||||
|
||||
private const int MaxMessages = 60;
|
||||
|
||||
private List<string> messageHistory = new List<string>(MaxMessages);
|
||||
private List<TerminalMessage> messageHistory = new List<TerminalMessage>(MaxMessages);
|
||||
|
||||
public string DisplayedWelcomeMessage
|
||||
{
|
||||
@@ -37,19 +56,39 @@ namespace Barotrauma.Items.Components
|
||||
/// </summary>
|
||||
public string ShowMessage
|
||||
{
|
||||
get { return messageHistory.Count == 0 ? string.Empty : messageHistory.Last(); }
|
||||
get { return messageHistory.Count == 0 ? string.Empty : messageHistory.Last().Text; }
|
||||
set
|
||||
{
|
||||
if (string.IsNullOrEmpty(value)) { return; }
|
||||
ShowOnDisplay(value, addToHistory: true);
|
||||
ShowOnDisplay(value, addToHistory: true, TextColor);
|
||||
}
|
||||
}
|
||||
|
||||
[Editable, Serialize(false, true, description: "The terminal will use a monospace font if this box is ticked.", alwaysUseInstanceValues: true)]
|
||||
public bool UseMonospaceFont { get; set; }
|
||||
|
||||
private Color textColor = Color.LimeGreen;
|
||||
|
||||
[Editable, Serialize("50,205,50,255", true, description: "Color of the terminal text.", alwaysUseInstanceValues: true)]
|
||||
public Color TextColor
|
||||
{
|
||||
get => textColor;
|
||||
set
|
||||
{
|
||||
textColor = value;
|
||||
#if CLIENT
|
||||
if (inputBox is { } input)
|
||||
{
|
||||
input.TextColor = value;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private string OutputValue { get; set; }
|
||||
|
||||
private string prevColorSignal;
|
||||
|
||||
public Terminal(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
@@ -59,18 +98,39 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
partial void InitProjSpecific(XElement element);
|
||||
|
||||
partial void ShowOnDisplay(string input, bool addToHistory);
|
||||
partial void ShowOnDisplay(string input, bool addToHistory, Color color);
|
||||
|
||||
public override void ReceiveSignal(Signal signal, Connection connection)
|
||||
{
|
||||
if (connection.Name != "signal_in") { return; }
|
||||
if (signal.value.Length > MaxMessageLength)
|
||||
switch (connection.Name)
|
||||
{
|
||||
signal.value = signal.value.Substring(0, MaxMessageLength);
|
||||
}
|
||||
case "set_text":
|
||||
|
||||
string inputSignal = signal.value.Replace("\\n", "\n");
|
||||
ShowOnDisplay(inputSignal, addToHistory: true);
|
||||
if (signal.value.Length > MaxMessageLength)
|
||||
{
|
||||
signal.value = signal.value.Substring(0, MaxMessageLength);
|
||||
}
|
||||
|
||||
string inputSignal = signal.value.Replace("\\n", "\n");
|
||||
ShowOnDisplay(inputSignal, addToHistory: true, TextColor);
|
||||
break;
|
||||
case "set_text_color":
|
||||
if (signal.value != prevColorSignal)
|
||||
{
|
||||
TextColor = XMLExtensions.ParseColor(signal.value, false);
|
||||
prevColorSignal = signal.value;
|
||||
}
|
||||
break;
|
||||
case "clear_text" when signal.value != "0":
|
||||
messageHistory.Clear();
|
||||
#if CLIENT
|
||||
if (historyBox?.Content is { } history)
|
||||
{
|
||||
history.ClearChildren();
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnItemLoaded()
|
||||
@@ -83,7 +143,7 @@ namespace Barotrauma.Items.Components
|
||||
base.OnItemLoaded();
|
||||
if (!string.IsNullOrEmpty(DisplayedWelcomeMessage))
|
||||
{
|
||||
ShowOnDisplay(DisplayedWelcomeMessage, addToHistory: !isSubEditor);
|
||||
ShowOnDisplay(DisplayedWelcomeMessage, addToHistory: !isSubEditor, TextColor);
|
||||
DisplayedWelcomeMessage = "";
|
||||
//remove welcome message if a game session is running so it doesn't reappear on successive rounds
|
||||
if (GameMain.GameSession != null && !isSubEditor)
|
||||
@@ -98,7 +158,8 @@ namespace Barotrauma.Items.Components
|
||||
var componentElement = base.Save(parentElement);
|
||||
for (int i = 0; i < messageHistory.Count; i++)
|
||||
{
|
||||
componentElement.Add(new XAttribute("msg" + i, messageHistory[i]));
|
||||
componentElement.Add(new XAttribute("msg" + i, messageHistory[i].Text));
|
||||
componentElement.Add(new XAttribute("color" + i, messageHistory[i].Color.ToStringHex()));
|
||||
}
|
||||
return componentElement;
|
||||
}
|
||||
@@ -109,8 +170,9 @@ namespace Barotrauma.Items.Components
|
||||
for (int i = 0; i < MaxMessages; i++)
|
||||
{
|
||||
string msg = componentElement.GetAttributeString("msg" + i, null);
|
||||
if (msg == null) { break; }
|
||||
ShowOnDisplay(msg, addToHistory: true);
|
||||
if (msg is null) { break; }
|
||||
Color color = componentElement.GetAttributeColor("color" + i, TextColor);
|
||||
ShowOnDisplay(msg, addToHistory: true, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -17,8 +17,8 @@ namespace Barotrauma.Items.Components
|
||||
Atan,
|
||||
}
|
||||
|
||||
private float[] receivedSignal = new float[2];
|
||||
private float[] timeSinceReceived = new float[2];
|
||||
private readonly float[] receivedSignal = new float[2];
|
||||
private readonly float[] timeSinceReceived = new float[2];
|
||||
|
||||
[Serialize(FunctionType.Sin, false, description: "Which kind of function to run the input through.", alwaysUseInstanceValues: true)]
|
||||
public FunctionType Function
|
||||
|
||||
@@ -125,7 +125,17 @@ namespace Barotrauma.Items.Components
|
||||
if (sender.TeamID != TeamID && !AllowCrossTeamCommunication)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//if the component is not linked to chat and has nothing connected to the output, sending a signal to it does nothing
|
||||
// = no point in receiving
|
||||
if (!LinkToChat)
|
||||
{
|
||||
if (signalOutConnection == null || !signalOutConnection.Wires.Any(w => w != null))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (Vector2.DistanceSquared(item.WorldPosition, sender.item.WorldPosition) > sender.range * sender.range) { return false; }
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
class TriggerComponent : ItemComponent
|
||||
partial class TriggerComponent : ItemComponent
|
||||
{
|
||||
[Editable, Serialize(0.0f, true, description: "The maximum amount of force applied to the triggering entitites.", alwaysUseInstanceValues: true)]
|
||||
public float Force { get; set; }
|
||||
@@ -18,12 +18,32 @@ namespace Barotrauma.Items.Components
|
||||
private float Radius { get; set; }
|
||||
private float RadiusInDisplayUnits { get; set; }
|
||||
private bool TriggeredOnce { get; set; }
|
||||
|
||||
private float CurrentForceFluctuation { get; set; } = 1.0f;
|
||||
public bool TriggerActive { get; private set; }
|
||||
private float ForceFluctuationTimer { get; set; }
|
||||
private static float TimeInLevel
|
||||
{
|
||||
get
|
||||
{
|
||||
if (GameMain.GameSession != null)
|
||||
{
|
||||
return (float)(Timing.TotalTime - GameMain.GameSession.RoundStartTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly LevelTrigger.TriggererType triggeredBy;
|
||||
private readonly HashSet<Entity> triggerers = new HashSet<Entity>();
|
||||
private readonly bool triggerOnce;
|
||||
private readonly bool distanceBasedForce;
|
||||
private readonly bool forceFluctuation;
|
||||
private readonly float forceFluctuationStrength;
|
||||
private readonly float forceFluctuationFrequency;
|
||||
private readonly float forceFluctuationInterval;
|
||||
private readonly List<ISerializableEntity> statusEffectTargets = new List<ISerializableEntity>();
|
||||
/// <summary>
|
||||
/// Effects applied to entities inside the trigger
|
||||
@@ -42,6 +62,15 @@ namespace Barotrauma.Items.Components
|
||||
DebugConsole.ThrowError($"Error in ForceComponent config: \"{triggeredByAttribute}\" is not a valid triggerer type.");
|
||||
}
|
||||
triggerOnce = element.GetAttributeBool("triggeronce", false);
|
||||
distanceBasedForce = element.GetAttributeBool("distancebasedforce", false);
|
||||
forceFluctuation = element.GetAttributeBool("forcefluctuation", false);
|
||||
forceFluctuationStrength = element.GetAttributeFloat("forcefluctuationstrength", 1.0f);
|
||||
forceFluctuationStrength = Math.Clamp(forceFluctuationStrength, 0.0f, 1.0f);
|
||||
forceFluctuationFrequency = element.GetAttributeFloat("fluctuationfrequency", 1.0f);
|
||||
forceFluctuationFrequency = Math.Max(forceFluctuationFrequency, 0.01f);
|
||||
forceFluctuationInterval = element.GetAttributeFloat("fluctuationinterval", 0.01f);
|
||||
forceFluctuationInterval = Math.Max(forceFluctuationInterval, 0.01f);
|
||||
|
||||
string parentDebugName = $"TriggerComponent in {item.Name}";
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
@@ -128,6 +157,19 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
TriggerActive = triggerers.Any();
|
||||
|
||||
if (forceFluctuation && TriggerActive && (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer))
|
||||
{
|
||||
ForceFluctuationTimer += deltaTime;
|
||||
if (ForceFluctuationTimer >= forceFluctuationInterval)
|
||||
{
|
||||
float v = MathF.Sin(2 * MathF.PI * forceFluctuationFrequency * TimeInLevel);
|
||||
float amount = MathUtils.InverseLerp(-1.0f, 1.0f, v);
|
||||
CurrentForceFluctuation = MathHelper.Lerp(1.0f - forceFluctuationStrength, 1.0f, amount);
|
||||
ForceFluctuationTimer = 0.0f;
|
||||
GameMain.NetworkMember?.CreateEntityEvent(this);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Entity triggerer in triggerers)
|
||||
{
|
||||
LevelTrigger.ApplyStatusEffects(statusEffects, item.WorldPosition, triggerer, deltaTime, statusEffectTargets);
|
||||
@@ -167,9 +209,9 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
Vector2 diff = ConvertUnits.ToDisplayUnits(PhysicsBody.SimPosition - body.SimPosition);
|
||||
if (diff.LengthSquared() < 0.0001f) { return; }
|
||||
float distanceFactor = LevelTrigger.GetDistanceFactor(body, PhysicsBody, RadiusInDisplayUnits);
|
||||
float distanceFactor = distanceBasedForce ? LevelTrigger.GetDistanceFactor(body, PhysicsBody, RadiusInDisplayUnits) : 1.0f;
|
||||
if (distanceFactor <= 0.0f) { return; }
|
||||
Vector2 force = distanceFactor * Force * Vector2.Normalize(diff);
|
||||
Vector2 force = distanceFactor * (CurrentForceFluctuation * Force) * Vector2.Normalize(diff);
|
||||
if (force.LengthSquared() < 0.01f) { return; }
|
||||
body.ApplyForce(force);
|
||||
}
|
||||
|
||||
@@ -68,6 +68,9 @@ namespace Barotrauma.Items.Components
|
||||
private const float TinkeringDamageIncrease = 0.2f;
|
||||
private const float TinkeringReloadDecrease = 0.2f;
|
||||
|
||||
public Character ActiveUser;
|
||||
private float resetActiveUserTimer;
|
||||
|
||||
public float Rotation
|
||||
{
|
||||
get { return rotation; }
|
||||
@@ -362,7 +365,7 @@ namespace Barotrauma.Items.Components
|
||||
UpdateTransformedBarrelPos();
|
||||
}
|
||||
|
||||
if (user != null && user.Removed)
|
||||
if (user is { Removed: true })
|
||||
{
|
||||
user = null;
|
||||
}
|
||||
@@ -371,6 +374,19 @@ namespace Barotrauma.Items.Components
|
||||
resetUserTimer -= deltaTime;
|
||||
if (resetUserTimer <= 0.0f) { user = null; }
|
||||
}
|
||||
|
||||
if (ActiveUser is { Removed: true })
|
||||
{
|
||||
ActiveUser = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
resetActiveUserTimer -= deltaTime;
|
||||
if (resetActiveUserTimer <= 0.0f)
|
||||
{
|
||||
ActiveUser = null;
|
||||
}
|
||||
}
|
||||
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
|
||||
|
||||
@@ -744,8 +760,10 @@ namespace Barotrauma.Items.Components
|
||||
if (projectileComponent != null)
|
||||
{
|
||||
projectileComponent.Attacker = projectileComponent.User = user;
|
||||
projectileComponent.Attack.DamageMultiplier = 1f + (TinkeringDamageIncrease * tinkeringStrength);
|
||||
|
||||
if (projectileComponent.Attack != null)
|
||||
{
|
||||
projectileComponent.Attack.DamageMultiplier = 1f + (TinkeringDamageIncrease * tinkeringStrength);
|
||||
}
|
||||
projectileComponent.Use();
|
||||
projectile.GetComponent<Rope>()?.Attach(item, projectile);
|
||||
projectileComponent.User = user;
|
||||
@@ -956,10 +974,11 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
|
||||
{
|
||||
if (character.AIController.SelectedAiTarget?.Entity is Character previousTarget &&
|
||||
previousTarget.IsDead)
|
||||
if (character.AIController.SelectedAiTarget?.Entity is Character previousTarget && previousTarget.IsDead)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogTurretTargetDead"), identifier: "killedtarget" + previousTarget.ID, minDurationBetweenSimilar: 10.0f);
|
||||
character.Speak(TextManager.Get("DialogTurretTargetDead"),
|
||||
identifier: "killedtarget" + previousTarget.ID,
|
||||
minDurationBetweenSimilar: 10.0f);
|
||||
character.AIController.SelectTarget(null);
|
||||
}
|
||||
|
||||
@@ -986,7 +1005,9 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogSupercapacitorIsBroken"), identifier: "supercapacitorisbroken", minDurationBetweenSimilar: 30.0f);
|
||||
character.Speak(TextManager.Get("DialogSupercapacitorIsBroken"),
|
||||
identifier: "supercapacitorisbroken",
|
||||
minDurationBetweenSimilar: 30.0f);
|
||||
canShoot = false;
|
||||
}
|
||||
}
|
||||
@@ -999,7 +1020,9 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
if (lowestCharge <= 0 && batteryToLoad.Item.ConditionPercentage > 0)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogTurretHasNoPower"), identifier: "turrethasnopower", minDurationBetweenSimilar: 30.0f);
|
||||
character.Speak(TextManager.Get("DialogTurretHasNoPower"),
|
||||
identifier: "turrethasnopower",
|
||||
minDurationBetweenSimilar: 30.0f);
|
||||
canShoot = false;
|
||||
}
|
||||
}
|
||||
@@ -1039,7 +1062,9 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (character.IsOnPlayerTeam)
|
||||
{
|
||||
character.Speak(TextManager.GetWithVariable("DialogCannotLoadTurret", "[itemname]", item.Name, formatCapitals: true), identifier: "cannotloadturret", minDurationBetweenSimilar: 30.0f);
|
||||
character.Speak(TextManager.GetWithVariable("DialogCannotLoadTurret", "[itemname]", item.Name, formatCapitals: true),
|
||||
identifier: "cannotloadturret",
|
||||
minDurationBetweenSimilar: 30.0f);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -1049,7 +1074,9 @@ namespace Barotrauma.Items.Components
|
||||
loadItemsObjective.ignoredContainerIdentifiers = new string[] { containerItem.prefab.Identifier };
|
||||
if (character.IsOnPlayerTeam)
|
||||
{
|
||||
character.Speak(TextManager.GetWithVariable("DialogLoadTurret", "[itemname]", item.Name, formatCapitals: true), identifier: "loadturret", minDurationBetweenSimilar: 30.0f);
|
||||
character.Speak(TextManager.GetWithVariable("DialogLoadTurret", "[itemname]", item.Name, formatCapitals: true),
|
||||
identifier: "loadturret",
|
||||
minDurationBetweenSimilar: 30.0f);
|
||||
}
|
||||
loadItemsObjective.Abandoned += CheckRemainingAmmo;
|
||||
loadItemsObjective.Completed += CheckRemainingAmmo;
|
||||
@@ -1063,11 +1090,15 @@ namespace Barotrauma.Items.Components
|
||||
int remainingAmmo = Submarine.MainSub.GetItems(false).Count(i => i.HasTag(ammoType) && i.Condition > 1);
|
||||
if (remainingAmmo == 0)
|
||||
{
|
||||
character.Speak(TextManager.Get($"DialogOutOf{ammoType}", fallBackTag: "DialogOutOfTurretAmmo"), identifier: "outofammo", minDurationBetweenSimilar: 30.0f);
|
||||
character.Speak(TextManager.Get($"DialogOutOf{ammoType}", fallBackTag: "DialogOutOfTurretAmmo"),
|
||||
identifier: "outofammo",
|
||||
minDurationBetweenSimilar: 30.0f);
|
||||
}
|
||||
else if (remainingAmmo < 3)
|
||||
{
|
||||
character.Speak(TextManager.Get($"DialogLowOn{ammoType}"), identifier: "outofammo", minDurationBetweenSimilar: 30.0f);
|
||||
character.Speak(TextManager.Get($"DialogLowOn{ammoType}"),
|
||||
identifier: "outofammo",
|
||||
minDurationBetweenSimilar: 30.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1090,7 +1121,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
float closestDistance = maxDistance * maxDistance;
|
||||
|
||||
if (currentTarget != null)
|
||||
bool hadCurrentTarget = currentTarget != null;
|
||||
if (hadCurrentTarget)
|
||||
{
|
||||
if (currentTarget.Removed || currentTarget.IsDead)
|
||||
{
|
||||
@@ -1222,24 +1254,32 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (character.IsOnPlayerTeam)
|
||||
{
|
||||
if (character.AIController.SelectedAiTarget == null)
|
||||
if (character.AIController.SelectedAiTarget == null && !hadCurrentTarget)
|
||||
{
|
||||
if (GameMain.Config.RecentlyEncounteredCreatures.Contains(closestEnemy.SpeciesName))
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogNewTargetSpotted"), null, 0.0f, "newtargetspotted", 30.0f);
|
||||
character.Speak(TextManager.Get("DialogNewTargetSpotted"),
|
||||
identifier: "newtargetspotted",
|
||||
minDurationBetweenSimilar: 30.0f);
|
||||
}
|
||||
else if (GameMain.Config.EncounteredCreatures.Any(name => name.Equals(closestEnemy.SpeciesName, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
character.Speak(TextManager.GetWithVariable("DialogIdentifiedTargetSpotted", "[speciesname]", closestEnemy.DisplayName), null, 0.0f, "identifiedtargetspotted", 30.0f);
|
||||
character.Speak(TextManager.GetWithVariable("DialogIdentifiedTargetSpotted", "[speciesname]", closestEnemy.DisplayName),
|
||||
identifier: "identifiedtargetspotted",
|
||||
minDurationBetweenSimilar: 30.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogUnidentifiedTargetSpotted"), null, 0.0f, "unidentifiedtargetspotted", 5.0f);
|
||||
character.Speak(TextManager.Get("DialogUnidentifiedTargetSpotted"),
|
||||
identifier: "unidentifiedtargetspotted",
|
||||
minDurationBetweenSimilar: 5.0f);
|
||||
}
|
||||
}
|
||||
else if (GameMain.Config.EncounteredCreatures.None(name => name.Equals(closestEnemy.SpeciesName, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogUnidentifiedTargetSpotted"), null, 0.0f, "unidentifiedtargetspotted", 5.0f);
|
||||
character.Speak(TextManager.Get("DialogUnidentifiedTargetSpotted"),
|
||||
identifier: "unidentifiedtargetspotted",
|
||||
minDurationBetweenSimilar: 5.0f);
|
||||
}
|
||||
character.AddEncounter(closestEnemy);
|
||||
}
|
||||
@@ -1247,7 +1287,9 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else if (closestEnemy == null && character.IsOnPlayerTeam)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogIceSpireSpotted"), null, 0.0f, "icespirespotted", 60.0f);
|
||||
character.Speak(TextManager.Get("DialogIceSpireSpotted"),
|
||||
identifier: "icespirespotted",
|
||||
minDurationBetweenSimilar: 60.0f);
|
||||
}
|
||||
|
||||
character.CursorPosition = targetPos.Value;
|
||||
@@ -1289,7 +1331,9 @@ namespace Barotrauma.Items.Components
|
||||
if (!shoot) { return false; }
|
||||
if (character.IsOnPlayerTeam)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogFireTurret"), null, 0.0f, "fireturret", 10.0f);
|
||||
character.Speak(TextManager.Get("DialogFireTurret"),
|
||||
identifier: "fireturret",
|
||||
minDurationBetweenSimilar: 30.0f);
|
||||
}
|
||||
character.SetInput(InputType.Shoot, true, true);
|
||||
}
|
||||
@@ -1389,6 +1433,7 @@ namespace Barotrauma.Items.Components
|
||||
crosshairSprite?.Remove(); crosshairSprite = null;
|
||||
crosshairPointerSprite?.Remove(); crosshairPointerSprite = null;
|
||||
moveSoundChannel?.Dispose(); moveSoundChannel = null;
|
||||
WeaponIndicatorSprite?.Remove(); WeaponIndicatorSprite = null;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1503,12 +1548,16 @@ namespace Barotrauma.Items.Components
|
||||
IsActive = true;
|
||||
}
|
||||
user = sender;
|
||||
ActiveUser = sender;
|
||||
resetActiveUserTimer = 1f;
|
||||
resetUserTimer = 10.0f;
|
||||
break;
|
||||
case "trigger_in":
|
||||
if (signal.value == "0") { return; }
|
||||
item.Use((float)Timing.Step, sender);
|
||||
user = sender;
|
||||
ActiveUser = sender;
|
||||
resetActiveUserTimer = 1f;
|
||||
resetUserTimer = 10.0f;
|
||||
//triggering the Use method through item.Use will fail if the item is not characterusable and the signal was sent by a character
|
||||
//so lets do it manually
|
||||
@@ -1521,12 +1570,18 @@ namespace Barotrauma.Items.Components
|
||||
if (lightComponent != null && signal.value != "0")
|
||||
{
|
||||
lightComponent.IsOn = !lightComponent.IsOn;
|
||||
UpdateLightComponent();
|
||||
}
|
||||
break;
|
||||
case "set_light":
|
||||
if (lightComponent != null)
|
||||
{
|
||||
lightComponent.IsOn = signal.value != "0";
|
||||
bool shouldBeOn = signal.value != "0";
|
||||
if (shouldBeOn != lightComponent.IsOn)
|
||||
{
|
||||
lightComponent.IsOn = shouldBeOn;
|
||||
UpdateLightComponent();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -802,13 +802,13 @@ namespace Barotrauma
|
||||
|
||||
if (otherIsEquipped)
|
||||
{
|
||||
existingItems.ForEach(existingItem => TryPutItem(existingItem, index, false, false, user, createNetworkEvent));
|
||||
stackedItems.ForEach(stackedItem => otherInventory.TryPutItem(stackedItem, otherIndex, false, false, user, createNetworkEvent));
|
||||
existingItems.ForEach(existingItem => TryPutItem(existingItem, index, false, false, user, createNetworkEvent, ignoreCondition: true));
|
||||
stackedItems.ForEach(stackedItem => otherInventory.TryPutItem(stackedItem, otherIndex, false, false, user, createNetworkEvent, ignoreCondition: true));
|
||||
}
|
||||
else
|
||||
{
|
||||
stackedItems.ForEach(stackedItem => otherInventory.TryPutItem(stackedItem, otherIndex, false, false, user, createNetworkEvent));
|
||||
existingItems.ForEach(existingItem => TryPutItem(existingItem, index, false, false, user, createNetworkEvent));
|
||||
stackedItems.ForEach(stackedItem => otherInventory.TryPutItem(stackedItem, otherIndex, false, false, user, createNetworkEvent, ignoreCondition: true));
|
||||
existingItems.ForEach(existingItem => TryPutItem(existingItem, index, false, false, user, createNetworkEvent, ignoreCondition: true));
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
|
||||
@@ -473,7 +473,12 @@ namespace Barotrauma
|
||||
public float HealthMultiplier
|
||||
{
|
||||
get => healthMultiplier;
|
||||
set { healthMultiplier = MathHelper.Clamp(value, 0.0f, float.PositiveInfinity); }
|
||||
set
|
||||
{
|
||||
float prevConditionPercentage = ConditionPercentage;
|
||||
healthMultiplier = MathHelper.Clamp(value, 0.0f, float.PositiveInfinity);
|
||||
Condition = MaxCondition * prevConditionPercentage / 100.0f;
|
||||
}
|
||||
}
|
||||
|
||||
private float maxRepairConditionMultiplier = 1.0f;
|
||||
@@ -564,21 +569,26 @@ namespace Barotrauma
|
||||
|
||||
public bool StolenDuringRound;
|
||||
|
||||
private bool spawnedInOutpost;
|
||||
public bool SpawnedInOutpost
|
||||
private bool spawnedInCurrentOutpost;
|
||||
public bool SpawnedInCurrentOutpost
|
||||
{
|
||||
get { return spawnedInOutpost; }
|
||||
get { return spawnedInCurrentOutpost; }
|
||||
set
|
||||
{
|
||||
if (!spawnedInOutpost && value)
|
||||
if (!spawnedInCurrentOutpost && value)
|
||||
{
|
||||
OriginalOutpost = GameMain.GameSession?.StartLocation?.BaseName ?? "";
|
||||
}
|
||||
spawnedInOutpost = value;
|
||||
spawnedInCurrentOutpost = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool AllowStealing = true;
|
||||
[Serialize(true, true, alwaysUseInstanceValues: true)]
|
||||
public bool AllowStealing
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
private string originalOutpost;
|
||||
[Serialize("", true, alwaysUseInstanceValues: true)]
|
||||
@@ -588,9 +598,9 @@ namespace Barotrauma
|
||||
set
|
||||
{
|
||||
originalOutpost = value;
|
||||
if (!string.IsNullOrEmpty(value) && GameMain.GameSession?.StartLocation?.BaseName == value)
|
||||
if (!string.IsNullOrEmpty(value) && GameMain.GameSession?.LevelData?.Type == LevelData.LevelType.Outpost && GameMain.GameSession?.StartLocation?.BaseName == value)
|
||||
{
|
||||
spawnedInOutpost = true;
|
||||
spawnedInCurrentOutpost = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1736,10 +1746,10 @@ namespace Barotrauma
|
||||
Submarine prevSub = Submarine;
|
||||
|
||||
var projectile = GetComponent<Projectile>();
|
||||
if (projectile?.StickTarget?.UserData is Limb limb)
|
||||
if (projectile?.StickTarget?.UserData is Limb limb && limb.character != null)
|
||||
{
|
||||
Submarine = body.Submarine = limb.character?.Submarine;
|
||||
currentHull = limb.character?.CurrentHull;
|
||||
Submarine = body.Submarine = limb.character.Submarine;
|
||||
currentHull = limb.character.CurrentHull;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -2125,7 +2135,7 @@ namespace Barotrauma
|
||||
|
||||
}
|
||||
|
||||
private IEnumerable<object> DelaySignal(Signal signal, Connection connection)
|
||||
private IEnumerable<CoroutineStatus> DelaySignal(Signal signal, Connection connection)
|
||||
{
|
||||
do
|
||||
{
|
||||
|
||||
@@ -103,6 +103,9 @@ namespace Barotrauma
|
||||
|
||||
container.IsActive = true;
|
||||
container.OnItemContained(item);
|
||||
#if SERVER
|
||||
GameMain.Server?.KarmaManager?.OnItemContained(item, container.Item, user);
|
||||
#endif
|
||||
}
|
||||
|
||||
return wasPut;
|
||||
@@ -122,6 +125,9 @@ namespace Barotrauma
|
||||
|
||||
container.IsActive = true;
|
||||
container.OnItemContained(item);
|
||||
#if SERVER
|
||||
GameMain.Server?.KarmaManager?.OnItemContained(item, container.Item, user);
|
||||
#endif
|
||||
}
|
||||
|
||||
return wasPut;
|
||||
|
||||
@@ -45,7 +45,8 @@ namespace Barotrauma
|
||||
{
|
||||
DebugConsole.AddWarning($"Invalid deconstruction output in \"{parentDebugName}\": the output item \"{ItemIdentifier}\" has the out condition set, but is also set to copy the condition of the deconstructed item. Ignoring the out condition.");
|
||||
}
|
||||
RequiredDeconstructor = element.GetAttributeStringArray("requireddeconstructor", new string[0]);
|
||||
RequiredDeconstructor = element.GetAttributeStringArray("requireddeconstructor",
|
||||
element.Parent?.GetAttributeStringArray("requireddeconstructor", new string[0]) ?? new string[0]);
|
||||
RequiredOtherItem = element.GetAttributeStringArray("requiredotheritem", new string[0]);
|
||||
ActivateButtonText = element.GetAttributeString("activatebuttontext", string.Empty);
|
||||
InfoText = element.GetAttributeString("infotext", string.Empty);
|
||||
@@ -915,7 +916,7 @@ namespace Barotrauma
|
||||
string spriteFolder = "";
|
||||
if (!subElement.GetAttributeString("texture", "").Contains("/"))
|
||||
{
|
||||
spriteFolder = Path.GetDirectoryName(filePath);
|
||||
spriteFolder = Path.GetDirectoryName(VariantOf?.FilePath ?? filePath);
|
||||
}
|
||||
|
||||
CanSpriteFlipX = subElement.GetAttributeBool("canflipx", true);
|
||||
@@ -972,7 +973,7 @@ namespace Barotrauma
|
||||
string iconFolder = "";
|
||||
if (!subElement.GetAttributeString("texture", "").Contains("/"))
|
||||
{
|
||||
iconFolder = Path.GetDirectoryName(filePath);
|
||||
iconFolder = Path.GetDirectoryName(VariantOf?.FilePath ?? filePath);
|
||||
}
|
||||
UpgradePreviewSprite = new Sprite(subElement, iconFolder, lazyLoad: true);
|
||||
UpgradePreviewScale = subElement.GetAttributeFloat("scale", 1.0f);
|
||||
@@ -983,7 +984,7 @@ namespace Barotrauma
|
||||
string iconFolder = "";
|
||||
if (!subElement.GetAttributeString("texture", "").Contains("/"))
|
||||
{
|
||||
iconFolder = Path.GetDirectoryName(filePath);
|
||||
iconFolder = Path.GetDirectoryName(VariantOf?.FilePath ?? filePath);
|
||||
}
|
||||
InventoryIcon = new Sprite(subElement, iconFolder, lazyLoad: true);
|
||||
}
|
||||
@@ -993,7 +994,7 @@ namespace Barotrauma
|
||||
string iconFolder = "";
|
||||
if (!subElement.GetAttributeString("texture", "").Contains("/"))
|
||||
{
|
||||
iconFolder = Path.GetDirectoryName(filePath);
|
||||
iconFolder = Path.GetDirectoryName(VariantOf?.FilePath ?? filePath);
|
||||
}
|
||||
MinimapIcon = new Sprite(subElement, iconFolder, lazyLoad: true);
|
||||
}
|
||||
@@ -1003,7 +1004,7 @@ namespace Barotrauma
|
||||
string iconFolder = "";
|
||||
if (!subElement.GetAttributeString("texture", "").Contains("/"))
|
||||
{
|
||||
iconFolder = Path.GetDirectoryName(filePath);
|
||||
iconFolder = Path.GetDirectoryName(VariantOf?.FilePath ?? filePath);
|
||||
}
|
||||
|
||||
InfectedSprite = new Sprite(subElement, iconFolder, lazyLoad: true);
|
||||
@@ -1014,7 +1015,7 @@ namespace Barotrauma
|
||||
string iconFolder = "";
|
||||
if (!subElement.GetAttributeString("texture", "").Contains("/"))
|
||||
{
|
||||
iconFolder = Path.GetDirectoryName(filePath);
|
||||
iconFolder = Path.GetDirectoryName(VariantOf?.FilePath ?? filePath);
|
||||
}
|
||||
|
||||
DamagedInfectedSprite = new Sprite(subElement, iconFolder, lazyLoad: true);
|
||||
@@ -1024,7 +1025,7 @@ namespace Barotrauma
|
||||
string brokenSpriteFolder = "";
|
||||
if (!subElement.GetAttributeString("texture", "").Contains("/"))
|
||||
{
|
||||
brokenSpriteFolder = Path.GetDirectoryName(filePath);
|
||||
brokenSpriteFolder = Path.GetDirectoryName(VariantOf?.FilePath ?? filePath);
|
||||
}
|
||||
|
||||
var brokenSprite = new BrokenItemSprite(
|
||||
@@ -1034,7 +1035,7 @@ namespace Barotrauma
|
||||
subElement.GetAttributePoint("offset", Point.Zero));
|
||||
|
||||
int spriteIndex = 0;
|
||||
for (int i = 0; i < BrokenSprites.Count && BrokenSprites[i].MaxCondition < brokenSprite.MaxCondition; i++)
|
||||
for (int i = 0; i < BrokenSprites.Count && BrokenSprites[i].MaxConditionPercentage < brokenSprite.MaxConditionPercentage; i++)
|
||||
{
|
||||
spriteIndex = i;
|
||||
}
|
||||
@@ -1044,7 +1045,7 @@ namespace Barotrauma
|
||||
string decorativeSpriteFolder = "";
|
||||
if (!subElement.GetAttributeString("texture", "").Contains("/"))
|
||||
{
|
||||
decorativeSpriteFolder = Path.GetDirectoryName(filePath);
|
||||
decorativeSpriteFolder = Path.GetDirectoryName(VariantOf?.FilePath ?? filePath);
|
||||
}
|
||||
|
||||
int groupID = 0;
|
||||
@@ -1070,7 +1071,7 @@ namespace Barotrauma
|
||||
string containedSpriteFolder = "";
|
||||
if (!subElement.GetAttributeString("texture", "").Contains("/"))
|
||||
{
|
||||
containedSpriteFolder = Path.GetDirectoryName(filePath);
|
||||
containedSpriteFolder = Path.GetDirectoryName(VariantOf?.FilePath ?? filePath);
|
||||
}
|
||||
var containedSprite = new ContainedItemSprite(subElement, containedSpriteFolder, lazyLoad: true);
|
||||
if (containedSprite.Sprite != null)
|
||||
|
||||
@@ -7,7 +7,7 @@ using System.Text;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class Entity : ISpatialEntity
|
||||
abstract class Entity : ISpatialEntity
|
||||
{
|
||||
public const ushort NullEntityID = 0;
|
||||
public const ushort EntitySpawnerID = ushort.MaxValue;
|
||||
@@ -16,8 +16,10 @@ namespace Barotrauma
|
||||
|
||||
public const ushort ReservedIDStart = ushort.MaxValue - 3;
|
||||
|
||||
public const ushort MaxEntityCount = ushort.MaxValue - 2; //ushort.MaxValue - 2 because 0 and ushort.MaxValue are reserved values
|
||||
|
||||
private static Dictionary<ushort, Entity> dictionary = new Dictionary<ushort, Entity>();
|
||||
public static IEnumerable<Entity> GetEntities()
|
||||
public static IReadOnlyCollection<Entity> GetEntities()
|
||||
{
|
||||
return dictionary.Values;
|
||||
}
|
||||
@@ -28,11 +30,9 @@ namespace Barotrauma
|
||||
|
||||
protected AITarget aiTarget;
|
||||
|
||||
private bool idFreed;
|
||||
public bool Removed { get; private set; }
|
||||
|
||||
public virtual bool Removed { get; private set; }
|
||||
|
||||
public bool IdFreed => idFreed;
|
||||
public bool IdFreed { get; private set; }
|
||||
|
||||
public readonly ushort ID;
|
||||
|
||||
@@ -75,43 +75,65 @@ namespace Barotrauma
|
||||
this.Submarine = submarine;
|
||||
spawnTime = Timing.TotalTime;
|
||||
|
||||
if (id != NullEntityID && dictionary.ContainsKey(id))
|
||||
{
|
||||
throw new Exception($"ID {id} is taken by {dictionary[id]}");
|
||||
}
|
||||
|
||||
//give a unique ID
|
||||
ID = DetermineID(id, submarine);
|
||||
|
||||
|
||||
if (dictionary.ContainsKey(ID))
|
||||
{
|
||||
throw new Exception($"ID {ID} is taken by {dictionary[ID]}");
|
||||
}
|
||||
|
||||
dictionary.Add(ID, this);
|
||||
}
|
||||
|
||||
protected virtual ushort DetermineID(ushort id, Submarine submarine)
|
||||
{
|
||||
return id != NullEntityID ?
|
||||
id :
|
||||
FindFreeID(submarine == null ? (ushort)1 : submarine.IdOffset);
|
||||
return id != NullEntityID
|
||||
? id
|
||||
: FindFreeId(submarine == null ? (ushort)1 : submarine.IdOffset);
|
||||
}
|
||||
|
||||
public static ushort FindFreeID(ushort idOffset = 0)
|
||||
private static ushort FindFreeId(ushort idOffset)
|
||||
{
|
||||
//ushort.MaxValue - 2 because 0 and ushort.MaxValue are reserved values
|
||||
if (dictionary.Count >= ushort.MaxValue - 2)
|
||||
if (dictionary.Count >= MaxEntityCount)
|
||||
{
|
||||
throw new Exception("Maximum amount of entities (" + (ushort.MaxValue - 1) + ") reached!");
|
||||
throw new Exception($"Maximum amount of entities ({MaxEntityCount}) reached!");
|
||||
}
|
||||
|
||||
idOffset = Math.Max(idOffset, (ushort)1);
|
||||
bool IDfound;
|
||||
ushort id = idOffset;
|
||||
do
|
||||
while (id < ReservedIDStart)
|
||||
{
|
||||
id += 1;
|
||||
IDfound = dictionary.ContainsKey(id);
|
||||
} while (IDfound || id == NullEntityID || id > ReservedIDStart);
|
||||
if (!dictionary.ContainsKey(id)) { break; }
|
||||
id++;
|
||||
};
|
||||
return id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds a contiguous block of free IDs of at least the given size
|
||||
/// </summary>
|
||||
/// <returns>The first ID in the found block, or zero if none are found</returns>
|
||||
public static int FindFreeIdBlock(int minBlockSize)
|
||||
{
|
||||
int currentBlockSize = 0;
|
||||
for (int i = 1; i < ReservedIDStart; i++)
|
||||
{
|
||||
if (dictionary.ContainsKey((ushort)i))
|
||||
{
|
||||
currentBlockSize = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentBlockSize++;
|
||||
if (currentBlockSize >= minBlockSize)
|
||||
{
|
||||
return i - (currentBlockSize-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find an entity based on the ID
|
||||
/// </summary>
|
||||
@@ -134,11 +156,11 @@ namespace Barotrauma
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
DebugConsole.ThrowError("Error while removing entity \"" + e.ToString() + "\"", exception);
|
||||
DebugConsole.ThrowError($"Error while removing entity \"{e}\"", exception);
|
||||
GameAnalyticsManager.AddErrorEventOnce(
|
||||
"Entity.RemoveAll:Exception" + e.ToString(),
|
||||
$"Entity.RemoveAll:Exception{e}",
|
||||
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
|
||||
"Error while removing entity \"" + e.ToString() + " (" + exception.Message + ")\n" + exception.StackTrace.CleanupStackTrace());
|
||||
$"Error while removing entity \"{e} ({exception.Message})\n{exception.StackTrace.CleanupStackTrace()}");
|
||||
}
|
||||
}
|
||||
StringBuilder errorMsg = new StringBuilder();
|
||||
@@ -167,7 +189,7 @@ namespace Barotrauma
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
DebugConsole.ThrowError("Error while removing item \"" + item.ToString() + "\"", exception);
|
||||
DebugConsole.ThrowError($"Error while removing item \"{item}\"", exception);
|
||||
}
|
||||
}
|
||||
Item.ItemList.Clear();
|
||||
@@ -189,7 +211,7 @@ namespace Barotrauma
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
DebugConsole.ThrowError("Error while removing character \"" + character.ToString() + "\"", exception);
|
||||
DebugConsole.ThrowError($"Error while removing character \"{character}\"", exception);
|
||||
}
|
||||
}
|
||||
Character.CharacterList.Clear();
|
||||
@@ -214,35 +236,33 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public void FreeID()
|
||||
{
|
||||
DebugConsole.Log("Removing entity " + ToString() + " (" + ID + ") from entity dictionary.");
|
||||
if (IdFreed) { return; }
|
||||
DebugConsole.Log($"Removing entity {ToString()} ({ID}) from entity dictionary.");
|
||||
if (!dictionary.TryGetValue(ID, out Entity existingEntity))
|
||||
{
|
||||
DebugConsole.Log("Entity " + ToString() + " (" + ID + ") not present in entity dictionary.");
|
||||
DebugConsole.ThrowError($"Entity {ToString()} ({ID}) not present in entity dictionary.");
|
||||
GameAnalyticsManager.AddErrorEventOnce(
|
||||
"Entity.FreeID:EntityNotFound" + ID,
|
||||
$"Entity.FreeID:EntityNotFound{ID}",
|
||||
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
|
||||
"Entity " + ToString() + " (" + ID + ") not present in entity dictionary.\n" + Environment.StackTrace.CleanupStackTrace());
|
||||
$"Entity {ToString()} ({ID}) not present in entity dictionary.\n{Environment.StackTrace.CleanupStackTrace()}");
|
||||
}
|
||||
else if (existingEntity != this)
|
||||
{
|
||||
DebugConsole.Log("Entity ID mismatch in entity dictionary. Entity " + existingEntity + " had the ID " + ID + " (expecting " + ToString() + ")");
|
||||
DebugConsole.ThrowError($"Entity ID mismatch in entity dictionary. Entity {existingEntity} had the ID {ID} (expecting {ToString()})");
|
||||
GameAnalyticsManager.AddErrorEventOnce("Entity.FreeID:EntityMismatch" + ID,
|
||||
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
|
||||
"Entity ID mismatch in entity dictionary. Entity " + existingEntity + " had the ID " + ID + " (expecting " + ToString() + ")");
|
||||
|
||||
foreach (var keyValuePair in dictionary.Where(kvp => kvp.Value == this).ToList())
|
||||
{
|
||||
dictionary.Remove(keyValuePair.Key);
|
||||
}
|
||||
$"Entity ID mismatch in entity dictionary. Entity {existingEntity} had the ID {ID} (expecting {ToString()})");
|
||||
}
|
||||
|
||||
dictionary.Remove(ID);
|
||||
idFreed = true;
|
||||
else
|
||||
{
|
||||
dictionary.Remove(ID);
|
||||
}
|
||||
IdFreed = true;
|
||||
}
|
||||
|
||||
public virtual void Remove()
|
||||
{
|
||||
if (!idFreed) FreeID();
|
||||
FreeID();
|
||||
Removed = true;
|
||||
}
|
||||
|
||||
@@ -255,8 +275,8 @@ namespace Barotrauma
|
||||
List<string> lines = new List<string>();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
lines.Add(entities[i].ID + ": " + entities[i].ToString());
|
||||
DebugConsole.ThrowError(entities[i].ID + ": " + entities[i].ToString());
|
||||
lines.Add($"{entities[i].ID}: {entities[i]}");
|
||||
DebugConsole.ThrowError($"{entities[i].ID}: {entities[i]}");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(filename))
|
||||
|
||||
@@ -39,6 +39,8 @@ namespace Barotrauma
|
||||
|
||||
private readonly float itemRepairStrength;
|
||||
|
||||
public readonly HashSet<Submarine> IgnoredSubmarines = new HashSet<Submarine>();
|
||||
|
||||
public float EmpStrength { get; set; }
|
||||
|
||||
public float BallastFloraDamage { get; set; }
|
||||
@@ -149,7 +151,7 @@ namespace Barotrauma
|
||||
|
||||
if (!MathUtils.NearlyEqual(Attack.GetStructureDamage(1.0f), 0.0f) || !MathUtils.NearlyEqual(Attack.GetLevelWallDamage(1.0f), 0.0f))
|
||||
{
|
||||
RangedStructureDamage(worldPosition, displayRange, Attack.GetStructureDamage(1.0f), Attack.GetLevelWallDamage(1.0f), attacker);
|
||||
RangedStructureDamage(worldPosition, displayRange, Attack.GetStructureDamage(1.0f), Attack.GetLevelWallDamage(1.0f), attacker, IgnoredSubmarines);
|
||||
}
|
||||
|
||||
if (BallastFloraDamage > 0.0f)
|
||||
@@ -397,13 +399,14 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Returns a dictionary where the keys are the structures that took damage and the values are the amount of damage taken
|
||||
/// </summary>
|
||||
public static Dictionary<Structure, float> RangedStructureDamage(Vector2 worldPosition, float worldRange, float damage, float levelWallDamage, Character attacker = null)
|
||||
public static Dictionary<Structure, float> RangedStructureDamage(Vector2 worldPosition, float worldRange, float damage, float levelWallDamage, Character attacker = null, IEnumerable<Submarine> ignoredSubmarines = null)
|
||||
{
|
||||
List<Structure> structureList = new List<Structure>();
|
||||
float dist = 600.0f;
|
||||
foreach (MapEntity entity in MapEntity.mapEntityList)
|
||||
{
|
||||
if (!(entity is Structure structure)) { continue; }
|
||||
if (ignoredSubmarines != null && entity.Submarine != null && ignoredSubmarines.Contains(entity.Submarine)) { continue; }
|
||||
|
||||
if (structure.HasBody &&
|
||||
!structure.IsPlatform &&
|
||||
|
||||
@@ -26,25 +26,25 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
//a value between 0.0f-1.0f (0.0 = closed, 1.0f = open)
|
||||
private float open;
|
||||
private float open;
|
||||
|
||||
//the force of the water flow which is exerted on physics bodies
|
||||
private Vector2 flowForce;
|
||||
private Hull flowTargetHull;
|
||||
|
||||
private float openedTimer = 1.0f;
|
||||
|
||||
|
||||
private float higherSurface;
|
||||
private float lowerSurface;
|
||||
|
||||
|
||||
private Vector2 lerpedFlowForce;
|
||||
|
||||
|
||||
//if set to true, hull connections of this gap won't be updated when changes are being done to hulls
|
||||
public bool DisableHullRechecks;
|
||||
|
||||
|
||||
//can ambient light get through the gap even if it's not open
|
||||
public bool PassAmbientLight;
|
||||
|
||||
|
||||
|
||||
//a collider outside the gap (for example an ice wall next to the sub)
|
||||
//used by ragdolls to prevent them from ending up inside colliders when teleporting out of the sub
|
||||
@@ -54,11 +54,11 @@ namespace Barotrauma
|
||||
public float Open
|
||||
{
|
||||
get { return open; }
|
||||
set
|
||||
set
|
||||
{
|
||||
if (float.IsNaN(value)) { return; }
|
||||
if (value > open) { openedTimer = 1.0f; }
|
||||
open = MathHelper.Clamp(value, 0.0f, 1.0f);
|
||||
open = MathHelper.Clamp(value, 0.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,7 +319,7 @@ namespace Barotrauma
|
||||
if (openedTimer > 0.0f && flowForce.Length() > lerpedFlowForce.Length())
|
||||
{
|
||||
//if the gap has just been opened/created, allow it to exert a large force instantly without any smoothing
|
||||
lerpedFlowForce = flowForce;
|
||||
lerpedFlowForce = flowForce;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -375,7 +375,7 @@ namespace Barotrauma
|
||||
|
||||
//make sure not to move more than what the room contains
|
||||
delta = Math.Min(((hull2.Pressure + subOffset.Y) - hull1.Pressure) * 5.0f * sizeModifier, Math.Min(hull2.WaterVolume, hull2.Volume));
|
||||
|
||||
|
||||
//make sure not to place more water to the target room than it can hold
|
||||
delta = Math.Min(delta, hull1.Volume * Hull.MaxCompress - (hull1.WaterVolume));
|
||||
hull1.WaterVolume += delta;
|
||||
@@ -405,7 +405,7 @@ namespace Barotrauma
|
||||
{
|
||||
hull2.Pressure = Math.Max(hull2.Pressure, ((hull1.Pressure-subOffset.Y) + hull2.Pressure) / 2);
|
||||
}
|
||||
|
||||
|
||||
flowForce = new Vector2(delta, 0.0f);
|
||||
}
|
||||
|
||||
@@ -448,7 +448,7 @@ namespace Barotrauma
|
||||
hull2.WaterVolume -= delta;
|
||||
|
||||
flowForce = new Vector2(
|
||||
0.0f,
|
||||
0.0f,
|
||||
Math.Min(Math.Min((hull2.Pressure + subOffset.Y) - hull1.Pressure, 200.0f), delta));
|
||||
|
||||
flowTargetHull = hull1;
|
||||
@@ -456,7 +456,7 @@ namespace Barotrauma
|
||||
if (hull1.WaterVolume > hull1.Volume)
|
||||
{
|
||||
hull1.Pressure = Math.Max(hull1.Pressure, (hull1.Pressure + (hull2.Pressure + subOffset.Y)) / 2);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
//there's water in the upper room, drop to lower
|
||||
@@ -494,7 +494,7 @@ namespace Barotrauma
|
||||
hull1.LethalPressure = avgLethality;
|
||||
hull2.LethalPressure = avgLethality;
|
||||
}
|
||||
else
|
||||
else
|
||||
{
|
||||
hull1.LethalPressure = 0.0f;
|
||||
hull2.LethalPressure = 0.0f;
|
||||
@@ -511,7 +511,7 @@ namespace Barotrauma
|
||||
float sizeModifier = size * open * open;
|
||||
|
||||
float delta = 500.0f * sizeModifier * deltaTime;
|
||||
|
||||
|
||||
//make sure not to place more water to the target room than it can hold
|
||||
delta = Math.Min(delta, hull1.Volume * Hull.MaxCompress - hull1.WaterVolume);
|
||||
hull1.WaterVolume += delta;
|
||||
@@ -526,7 +526,7 @@ namespace Barotrauma
|
||||
if (rect.X > hull1.Rect.X + hull1.Rect.Width / 2.0f)
|
||||
{
|
||||
flowForce = new Vector2(-delta, 0.0f);
|
||||
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -554,7 +554,7 @@ namespace Barotrauma
|
||||
|
||||
hull1.WaveVel[0] += vel;
|
||||
hull1.WaveVel[1] += vel;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -651,12 +651,12 @@ namespace Barotrauma
|
||||
|
||||
float totalOxygen = hull1.Oxygen + hull2.Oxygen;
|
||||
float totalVolume = (hull1.Volume + hull2.Volume);
|
||||
|
||||
|
||||
float deltaOxygen = (totalOxygen * hull1.Volume / totalVolume) - hull1.Oxygen;
|
||||
deltaOxygen = MathHelper.Clamp(deltaOxygen, -Hull.OxygenDistributionSpeed, Hull.OxygenDistributionSpeed);
|
||||
|
||||
hull1.Oxygen += deltaOxygen;
|
||||
hull2.Oxygen -= deltaOxygen;
|
||||
hull2.Oxygen -= deltaOxygen;
|
||||
}
|
||||
|
||||
public static Gap FindAdjacent(IEnumerable<Gap> gaps, Vector2 worldPos, float allowedOrthogonalDist)
|
||||
@@ -724,7 +724,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (!DisableHullRechecks) FindHulls();
|
||||
}
|
||||
|
||||
|
||||
public static Gap Load(XElement element, Submarine submarine, IdRemap idRemap)
|
||||
{
|
||||
Rectangle rect = Rectangle.Empty;
|
||||
@@ -755,6 +755,8 @@ namespace Barotrauma
|
||||
{
|
||||
linkedToID = new List<ushort>(),
|
||||
};
|
||||
|
||||
g.HiddenInGame = element.GetAttributeBool(nameof(HiddenInGame).ToLower(), g.HiddenInGame);
|
||||
return g;
|
||||
}
|
||||
|
||||
@@ -764,7 +766,8 @@ namespace Barotrauma
|
||||
|
||||
element.Add(
|
||||
new XAttribute("ID", ID),
|
||||
new XAttribute("horizontal", IsHorizontal ? "true" : "false"));
|
||||
new XAttribute("horizontal", IsHorizontal ? "true" : "false"),
|
||||
new XAttribute(nameof(HiddenInGame).ToLower(), HiddenInGame));
|
||||
|
||||
element.Add(new XAttribute("rect",
|
||||
(int)(rect.X - Submarine.HiddenSubPosition.X) + "," +
|
||||
|
||||
@@ -1058,11 +1058,17 @@ namespace Barotrauma
|
||||
/// <param name="inclusive">Does being exactly at the edge of the hull count as being inside?</param>
|
||||
public static Hull FindHull(Vector2 position, Hull guess = null, bool useWorldCoordinates = true, bool inclusive = true)
|
||||
{
|
||||
if (EntityGrids == null) return null;
|
||||
if (EntityGrids == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (guess != null)
|
||||
{
|
||||
if (Submarine.RectContains(useWorldCoordinates ? guess.WorldRect : guess.rect, position, inclusive)) return guess;
|
||||
if (Submarine.RectContains(useWorldCoordinates ? guess.WorldRect : guess.rect, position, inclusive))
|
||||
{
|
||||
return guess;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (EntityGrid entityGrid in EntityGrids)
|
||||
@@ -1088,15 +1094,19 @@ namespace Barotrauma
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
Vector2 transformedPosition = position;
|
||||
if (useWorldCoordinates && entityGrid.Submarine != null) transformedPosition -= entityGrid.Submarine.Position;
|
||||
|
||||
if (useWorldCoordinates && entityGrid.Submarine != null)
|
||||
{
|
||||
transformedPosition -= entityGrid.Submarine.Position;
|
||||
}
|
||||
var entities = entityGrid.GetEntities(transformedPosition);
|
||||
if (entities == null) continue;
|
||||
if (entities == null) { continue; }
|
||||
foreach (Hull hull in entities)
|
||||
{
|
||||
if (Submarine.RectContains(hull.rect, transformedPosition, inclusive)) return hull;
|
||||
if (Submarine.RectContains(hull.rect, transformedPosition, inclusive))
|
||||
{
|
||||
return hull;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -145,8 +145,7 @@ namespace Barotrauma
|
||||
|
||||
public static List<MapEntity> PasteEntities(Vector2 position, Submarine sub, XElement configElement, string filePath = null, bool selectInstance = false)
|
||||
{
|
||||
int idOffset = Entity.FindFreeID(1);
|
||||
if (MapEntity.mapEntityList.Any()) { idOffset = MapEntity.mapEntityList.Max(e => e.ID); }
|
||||
int idOffset = Entity.FindFreeIdBlock(configElement.Elements().Count());
|
||||
List<MapEntity> entities = MapEntity.LoadAll(sub, configElement, filePath, idOffset);
|
||||
if (entities.Count == 0) { return entities; }
|
||||
|
||||
|
||||
@@ -202,10 +202,10 @@ namespace Barotrauma
|
||||
get { return startPosition.ToVector2(); }
|
||||
}
|
||||
|
||||
private Vector2 startExitPosition;
|
||||
private Point startExitPosition;
|
||||
public Vector2 StartExitPosition
|
||||
{
|
||||
get { return startExitPosition; }
|
||||
get { return startExitPosition.ToVector2(); }
|
||||
}
|
||||
|
||||
public Point Size
|
||||
@@ -218,10 +218,10 @@ namespace Barotrauma
|
||||
get { return endPosition.ToVector2(); }
|
||||
}
|
||||
|
||||
private Vector2 endExitPosition;
|
||||
private Point endExitPosition;
|
||||
public Vector2 EndExitPosition
|
||||
{
|
||||
get { return endExitPosition; }
|
||||
get { return endExitPosition.ToVector2(); }
|
||||
}
|
||||
|
||||
public int BottomPos
|
||||
@@ -366,9 +366,6 @@ namespace Barotrauma
|
||||
{
|
||||
this.LevelData = levelData;
|
||||
borders = new Rectangle(Point.Zero, levelData.Size);
|
||||
|
||||
//remove from entity dictionary
|
||||
//base.Remove();
|
||||
}
|
||||
|
||||
public static Level Generate(LevelData levelData, bool mirror, SubmarineInfo startOutpost = null, SubmarineInfo endOutpost = null)
|
||||
@@ -462,12 +459,12 @@ namespace Barotrauma
|
||||
startPosition = new Point(
|
||||
(int)MathHelper.Lerp(minMainPathWidth, borders.Width - minMainPathWidth, GenerationParams.StartPosition.X),
|
||||
(int)MathHelper.Lerp(borders.Bottom - Math.Max(minMainPathWidth, ExitDistance * 1.5f), borders.Y + minMainPathWidth, GenerationParams.StartPosition.Y));
|
||||
startExitPosition = new Vector2(startPosition.X, borders.Bottom);
|
||||
startExitPosition = new Point(startPosition.X, borders.Bottom);
|
||||
|
||||
endPosition = new Point(
|
||||
(int)MathHelper.Lerp(minMainPathWidth, borders.Width - minMainPathWidth, GenerationParams.EndPosition.X),
|
||||
(int)MathHelper.Lerp(borders.Bottom - Math.Max(minMainPathWidth, ExitDistance * 1.5f), borders.Y + minMainPathWidth, GenerationParams.EndPosition.Y));
|
||||
endExitPosition = new Vector2(endPosition.X, borders.Bottom);
|
||||
endExitPosition = new Point(endPosition.X, borders.Bottom);
|
||||
|
||||
EqualityCheckValues.Add(Rand.Int(int.MaxValue, Rand.RandSync.Server));
|
||||
|
||||
@@ -486,25 +483,25 @@ namespace Barotrauma
|
||||
{
|
||||
startPath = new Tunnel(
|
||||
TunnelType.SidePath,
|
||||
new List<Point>() { startExitPosition.ToPoint(), startPosition },
|
||||
new List<Point>() { startExitPosition, startPosition },
|
||||
minWidth / 2, parentTunnel: mainPath);
|
||||
Tunnels.Add(startPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
startExitPosition = StartPosition;
|
||||
startExitPosition = startPosition;
|
||||
}
|
||||
if (GenerationParams.EndPosition.Y < 0.5f && (Mirrored ? !HasStartOutpost() : !HasEndOutpost()))
|
||||
{
|
||||
endPath = new Tunnel(
|
||||
TunnelType.SidePath,
|
||||
new List<Point>() { endPosition, endExitPosition.ToPoint() },
|
||||
new List<Point>() { endPosition, endExitPosition },
|
||||
minWidth / 2, parentTunnel: mainPath);
|
||||
Tunnels.Add(endPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
endExitPosition = EndPosition;
|
||||
endExitPosition = endPosition;
|
||||
}
|
||||
|
||||
if (GenerationParams.CreateHoleNextToEnd)
|
||||
@@ -513,14 +510,14 @@ namespace Barotrauma
|
||||
{
|
||||
endHole = new Tunnel(
|
||||
TunnelType.SidePath,
|
||||
new List<Point>() { startPosition, startExitPosition.ToPoint(), new Point(0, Size.Y) },
|
||||
new List<Point>() { startPosition, startExitPosition, new Point(0, Size.Y) },
|
||||
minWidth / 2, parentTunnel: mainPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
endHole = new Tunnel(
|
||||
TunnelType.SidePath,
|
||||
new List<Point>() { endPosition, endExitPosition.ToPoint(), Size },
|
||||
new List<Point>() { endPosition, endExitPosition, Size },
|
||||
minWidth / 2, parentTunnel: mainPath);
|
||||
}
|
||||
Tunnels.Add(endHole);
|
||||
@@ -799,8 +796,12 @@ namespace Barotrauma
|
||||
for (int i = 0; i < GenerationParams.RuinCount; i++)
|
||||
{
|
||||
Point ruinSize = new Point(5000);
|
||||
ruinPositions.Add(FindPosAwayFromMainPath((Math.Max(ruinSize.X, ruinSize.Y) + mainPath.MinWidth) * 1.2f, asCloseAsPossible: true,
|
||||
limits: new Rectangle(new Point(ruinSize.X / 2, ruinSize.Y / 2), Size - ruinSize)));
|
||||
int limitLeft = Math.Max(startPosition.X, ruinSize.X / 2);
|
||||
int limitRight = Math.Min(endPosition.X, Size.X - ruinSize.X / 2);
|
||||
Rectangle limits = new Rectangle(limitLeft, ruinSize.Y, limitRight - limitLeft, Size.Y - ruinSize.Y);
|
||||
Debug.Assert(limits.Width > 0);
|
||||
Debug.Assert(limits.Height > 0);
|
||||
ruinPositions.Add(FindPosAwayFromMainPath((Math.Max(ruinSize.X, ruinSize.Y) + mainPath.MinWidth) * 1.2f, asCloseAsPossible: true, limits: limits));
|
||||
CalculateTunnelDistanceField(ruinPositions);
|
||||
}
|
||||
|
||||
@@ -1004,7 +1005,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (pos.PositionType != PositionType.MainPath && pos.PositionType != PositionType.SidePath) { continue; }
|
||||
if (pos.Position.X < 5000 || pos.Position.X > Size.X - 5000) { continue; }
|
||||
if (Math.Abs(pos.Position.X - StartPosition.X) < minMainPathWidth * 2 || Math.Abs(pos.Position.X - EndPosition.X) < minMainPathWidth * 2) { continue; }
|
||||
if (Math.Abs(pos.Position.X - startPosition.X) < minMainPathWidth * 2 || Math.Abs(pos.Position.X - endPosition.X) < minMainPathWidth * 2) { continue; }
|
||||
if (GetTooCloseCells(pos.Position.ToVector2(), minMainPathWidth * 0.7f).Count > 0) { continue; }
|
||||
iceChunkPositions.Add(pos.Position);
|
||||
}
|
||||
@@ -1187,19 +1188,19 @@ namespace Barotrauma
|
||||
startPosition = endPosition;
|
||||
endPosition = tempP;
|
||||
|
||||
Vector2 tempV = startExitPosition;
|
||||
tempP = startExitPosition;
|
||||
startExitPosition = endExitPosition;
|
||||
endExitPosition = tempV;
|
||||
endExitPosition = tempP;
|
||||
}
|
||||
if (StartOutpost != null)
|
||||
{
|
||||
startExitPosition = StartOutpost.WorldPosition;
|
||||
startPosition = startExitPosition.ToPoint();
|
||||
startExitPosition = StartOutpost.WorldPosition.ToPoint();
|
||||
startPosition = startExitPosition;
|
||||
}
|
||||
if (EndOutpost != null)
|
||||
{
|
||||
endExitPosition = EndOutpost.WorldPosition;
|
||||
endPosition = endExitPosition.ToPoint();
|
||||
endExitPosition = EndOutpost.WorldPosition.ToPoint();
|
||||
endPosition = endExitPosition;
|
||||
}
|
||||
|
||||
CreateWrecks();
|
||||
@@ -2321,7 +2322,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 (!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 _);
|
||||
|
||||
@@ -2738,10 +2739,26 @@ namespace Barotrauma
|
||||
allValidLocations.Sort((x, y) => Vector2.DistanceSquared(poiPos, x.EdgeCenter)
|
||||
.CompareTo(Vector2.DistanceSquared(poiPos, y.EdgeCenter)));
|
||||
var maxResourceOverlap = 0.4f;
|
||||
// TODO: Find multiple locations if there's too many resources to fit on a sigle edge
|
||||
var selectedLocation = allValidLocations.FirstOrDefault(l =>
|
||||
Vector2.Distance(l.Edge.Point1, l.Edge.Point2) is float edgeLength &&
|
||||
requiredAmount <= (int)Math.Floor(edgeLength / ((1.0f - maxResourceOverlap) * prefab.Size.X)));
|
||||
if (selectedLocation.Edge == null)
|
||||
{
|
||||
//couldn't find a long enough edge, find the largest one
|
||||
float longestEdge = 0.0f;
|
||||
foreach (var validLocation in allValidLocations)
|
||||
{
|
||||
if (Vector2.Distance(validLocation.Edge.Point1, validLocation.Edge.Point2) is float edgeLength && edgeLength > longestEdge)
|
||||
{
|
||||
selectedLocation = validLocation;
|
||||
longestEdge = edgeLength;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (selectedLocation.Edge == null)
|
||||
{
|
||||
throw new Exception("Failed to find a suitable level wall edge to place level resources on.");
|
||||
}
|
||||
PlaceResources(prefab, requiredAmount, selectedLocation, out placedResources);
|
||||
var edgeNormal = selectedLocation.Edge.GetNormal(selectedLocation.Cell);
|
||||
rotation = MathHelper.ToDegrees(-MathUtils.VectorToAngle(edgeNormal) + MathHelper.PiOver2);
|
||||
@@ -3197,12 +3214,12 @@ namespace Barotrauma
|
||||
|
||||
public bool IsCloseToStart(Point position, float minDist)
|
||||
{
|
||||
return MathUtils.LineSegmentToPointDistanceSquared(StartPosition.ToPoint(), StartExitPosition.ToPoint(), position) < minDist * minDist;
|
||||
return MathUtils.LineSegmentToPointDistanceSquared(startPosition, startExitPosition, position) < minDist * minDist;
|
||||
}
|
||||
|
||||
public bool IsCloseToEnd(Point position, float minDist)
|
||||
{
|
||||
return MathUtils.LineSegmentToPointDistanceSquared(EndPosition.ToPoint(), EndExitPosition.ToPoint(), position) < minDist * minDist;
|
||||
return MathUtils.LineSegmentToPointDistanceSquared(endPosition, endExitPosition, position) < minDist * minDist;
|
||||
}
|
||||
|
||||
private Submarine SpawnSubOnPath(string subName, ContentFile contentFile, SubmarineType type)
|
||||
@@ -3214,6 +3231,7 @@ namespace Barotrauma
|
||||
var waypoints = WayPoint.WayPointList.Where(wp =>
|
||||
wp.Submarine == null &&
|
||||
wp.SpawnType == SpawnType.Path &&
|
||||
wp.WorldPosition.X < EndExitPosition.X &&
|
||||
!IsCloseToStart(wp.WorldPosition, minDistance) &&
|
||||
!IsCloseToEnd(wp.WorldPosition, minDistance)).ToList();
|
||||
|
||||
@@ -3971,7 +3989,7 @@ namespace Barotrauma
|
||||
|
||||
var characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: job, randSync: Rand.RandSync.Server);
|
||||
var corpse = Character.Create(CharacterPrefab.HumanConfigFile, worldPos, ToolBox.RandomSeed(8), characterInfo, hasAi: true, createNetworkEvent: true);
|
||||
corpse.AnimController.FindHull(worldPos, true);
|
||||
corpse.AnimController.FindHull(worldPos, setSubmarine: true);
|
||||
corpse.TeamID = CharacterTeamType.None;
|
||||
corpse.EnableDespawn = false;
|
||||
selectedPrefab.GiveItems(corpse, wreck);
|
||||
|
||||
@@ -143,14 +143,20 @@ namespace Barotrauma
|
||||
|
||||
var rand = new MTRandom(ToolBox.StringToInt(Seed));
|
||||
InitialDepth = (int)MathHelper.Lerp(GenerationParams.InitialDepthMin, GenerationParams.InitialDepthMax, (float)rand.NextDouble());
|
||||
|
||||
//minimum difficulty of the level before hunting grounds can appear
|
||||
float huntingGroundsDifficultyThreshold = 25;
|
||||
//probability of hunting grounds appearing in 100% difficulty levels
|
||||
float maxHuntingGroundsProbability = 0.3f;
|
||||
HasHuntingGrounds = OriginallyHadHuntingGrounds = rand.NextDouble() < MathUtils.InverseLerp(huntingGroundsDifficultyThreshold, 100.0f, Difficulty) * maxHuntingGroundsProbability;
|
||||
|
||||
HasBeaconStation = !HasHuntingGrounds && rand.NextDouble() < locationConnection.Locations.Select(l => l.Type.BeaconStationChance).Max();
|
||||
if (Biome.IsEndBiome)
|
||||
{
|
||||
HasHuntingGrounds = false;
|
||||
HasBeaconStation = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
//minimum difficulty of the level before hunting grounds can appear
|
||||
float huntingGroundsDifficultyThreshold = 25;
|
||||
//probability of hunting grounds appearing in 100% difficulty levels
|
||||
float maxHuntingGroundsProbability = 0.3f;
|
||||
HasHuntingGrounds = OriginallyHadHuntingGrounds = rand.NextDouble() < MathUtils.InverseLerp(huntingGroundsDifficultyThreshold, 100.0f, Difficulty) * maxHuntingGroundsProbability;
|
||||
HasBeaconStation = !HasHuntingGrounds && rand.NextDouble() < locationConnection.Locations.Select(l => l.Type.BeaconStationChance).Max();
|
||||
}
|
||||
IsBeaconActive = false;
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user