Unstable 0.16.1.0

This commit is contained in:
Markus Isberg
2022-01-27 00:30:32 +09:00
parent 7d6421a548
commit b259af5911
161 changed files with 1913 additions and 638 deletions
@@ -340,7 +340,7 @@ namespace Barotrauma
{
targetingTag = "dead";
}
else if (AIParams.TryGetTarget(targetCharacter.CharacterHealth.GetActiveAfflictionTags(), out CharacterParams.TargetParams tp) && tp.Threshold > Character.GetDamageDoneByAttacker(targetCharacter))
else if (AIParams.TryGetTarget(targetCharacter.CharacterHealth.GetActiveAfflictionTags(), out CharacterParams.TargetParams tp) && tp.Threshold >= Character.GetDamageDoneByAttacker(targetCharacter))
{
targetingTag = tp.Tag;
}
@@ -678,7 +678,10 @@ namespace Barotrauma
return a.Damage >= selectedTargetingParams.Threshold;
}
Character attacker = targetCharacter.LastAttackers.LastOrDefault(IsValid)?.Character;
if (attacker != null)
//if the attacker has the same targeting tag as the character we're protecting, we can't change the TargetState
//otherwise e.g. a pet that's set to follow humans would start attacking all humans (and other pets, since they're considered part of the same group) when a hostile human attacks it
//TODO: a way for pets to differentiate hostile and friendly humans?
if (attacker?.AiTarget != null && !targetCharacter.SpeciesName.Equals(GetTargetingTag(attacker.AiTarget), StringComparison.OrdinalIgnoreCase))
{
// Attack the character that attacked the target we are protecting
ChangeTargetState(attacker, AIState.Attack, selectedTargetingParams.Priority * 2);
@@ -1598,7 +1601,7 @@ namespace Barotrauma
}
else
{
sweepTimer = Rand.Range(-1000, 1000) * selectedTargetingParams.SweepSpeed;
sweepTimer = Rand.Range(-1000f, 1000f) * selectedTargetingParams.SweepSpeed;
}
}
break;
@@ -2305,7 +2308,7 @@ namespace Barotrauma
if (item.Condition <= 0.0f)
{
if (!wasBroken) { PetBehavior?.OnEat(item.GetTags(), 1.0f); }
if (!wasBroken) { PetBehavior?.OnEat(item); }
Entity.Spawner.AddToRemoveQueue(item);
}
}
@@ -565,7 +565,7 @@ namespace Barotrauma
Character.AnimController.HeadInWater ||
Character.Submarine == null ||
(Character.Submarine.TeamID != Character.TeamID && !Character.IsEscorted) ||
ObjectiveManager.CurrentOrders.Any(o => o.Objective.KeepDivingGearOn) ||
!ObjectiveManager.IsCurrentObjective<AIObjectiveIdle>() && 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;
@@ -1090,7 +1090,6 @@ namespace Barotrauma
private void RespondToAttack(Character attacker, AttackResult attackResult)
{
float minorDamageThreshold = 10;
float healAmount = 0.0f;
if (attacker != null)
{
@@ -1099,7 +1098,7 @@ namespace Barotrauma
// excluding poisons etc
float realDamage = attackResult.Damage - healAmount;
// including poisons etc
float totalDamage = realDamage - healAmount;
float totalDamage = realDamage;
if (attackResult.Afflictions != null)
{
foreach (Affliction affliction in attackResult.Afflictions)
@@ -1140,6 +1139,13 @@ namespace Barotrauma
}
bool isAttackerInfected = false;
bool isAttackerFightingEnemy = false;
float minorDamageThreshold = 1;
float majorDamageThreshold = 20;
if (attacker.TeamID == Character.TeamID)
{
minorDamageThreshold = 10;
majorDamageThreshold = 40;
}
if (IsFriendly(attacker))
{
if (attacker.AnimController.Anim == Barotrauma.AnimController.Animation.CPR && attacker.SelectedCharacter == Character)
@@ -1148,11 +1154,11 @@ namespace Barotrauma
// Should not cancel any existing ai objectives (so that if the character attacked you and then helped, we still would want to retaliate).
return;
}
float cumulativeDamage = Character.GetDamageDoneByAttacker(attacker);
float cumulativeDamage = realDamage + Character.GetDamageDoneByAttacker(attacker);
bool isAccidental = attacker.IsBot && !IsMentallyUnstable && !attacker.AIController.IsMentallyUnstable && Character.CombatAction == null;
if (isAccidental)
{
if (!Character.IsSecurity && cumulativeDamage > 1)
if (!Character.IsSecurity && cumulativeDamage > minorDamageThreshold)
{
AddCombatObjective(AIObjectiveCombat.CombatMode.Retreat, attacker);
}
@@ -1161,7 +1167,7 @@ namespace Barotrauma
{
isAttackerInfected = attacker.CharacterHealth.GetAfflictionStrength("alieninfection") > 0;
// Inform other NPCs
if (isAttackerInfected || cumulativeDamage > 1 || totalDamage >= minorDamageThreshold)
if (isAttackerInfected || cumulativeDamage > minorDamageThreshold || totalDamage > minorDamageThreshold)
{
if (GameMain.IsMultiplayer || !attacker.IsPlayer || Character.TeamID != attacker.TeamID)
{
@@ -1170,27 +1176,36 @@ namespace Barotrauma
}
if (Character.IsBot)
{
if (ObjectiveManager.CurrentObjective is AIObjectiveFightIntruders) { return; }
if (attacker.IsPlayer)
var combatMode = DetermineCombatMode(Character, cumulativeDamage);
if (attacker.IsPlayer && !Character.IsInstigator && !ObjectiveManager.IsCurrentObjective<AIObjectiveCombat>())
{
if (Character.IsSecurity)
switch (combatMode)
{
if (attacker.TeamID != Character.TeamID && cumulativeDamage > 1 || cumulativeDamage > minorDamageThreshold)
{
Character.Speak(TextManager.Get("dialogattackedbyfriendlysecurityarrest"), null, 0.50f, "attackedbyfriendlysecurityarrest", minDurationBetweenSimilar: 30.0f);
}
else
{
Character.Speak(TextManager.Get("dialogattackedbyfriendlysecurityresponse"), null, 0.50f, "attackedbyfriendlysecurityresponse", minDurationBetweenSimilar: 30.0f);
}
}
else if (!Character.IsInstigator && cumulativeDamage > 1)
{
Character.Speak(TextManager.Get("DialogAttackedByFriendly"), null, 0.50f, "attackedbyfriendly", minDurationBetweenSimilar: 30.0f);
case AIObjectiveCombat.CombatMode.Defensive:
case AIObjectiveCombat.CombatMode.Retreat:
if (Character.IsSecurity)
{
Character.Speak(TextManager.Get("dialogattackedbyfriendlysecurityresponse"), null, 0.5f, "attackedbyfriendlysecurityresponse", minDurationBetweenSimilar: 10.0f);
}
else
{
Character.Speak(TextManager.Get("DialogAttackedByFriendly"), null, 0.5f, "attackedbyfriendly", minDurationBetweenSimilar: 10.0f);
}
break;
case AIObjectiveCombat.CombatMode.Offensive:
case AIObjectiveCombat.CombatMode.Arrest:
Character.Speak(TextManager.Get("dialogattackedbyfriendlysecurityarrest"), null, 0.5f, "attackedbyfriendlysecurityarrest", minDurationBetweenSimilar: 10.0f);
break;
case AIObjectiveCombat.CombatMode.None:
if (Character.IsSecurity && realDamage > 1)
{
Character.Speak(TextManager.Get("dialogattackedbyfriendlysecurityresponse"), null, 0.5f, "attackedbyfriendlysecurityresponse", minDurationBetweenSimilar: 10.0f);
}
break;
}
}
// If the attacker is using a low damage and high frequency weapon like a repair tool, we shouldn't use any delay.
AddCombatObjective(DetermineCombatMode(Character, cumulativeDamage), attacker, delay: realDamage > 1 ? GetReactionTime() : 0);
AddCombatObjective(combatMode, attacker, delay: realDamage > 1 ? GetReactionTime() : 0);
}
if (!isAttackerFightingEnemy)
{
@@ -1203,15 +1218,15 @@ namespace Barotrauma
if (Character.Submarine != null && Character.Submarine.GetConnectedSubs().Contains(attacker.Submarine))
{
// Non-friendly
InformOtherNPCs(Character.GetDamageDoneByAttacker(attacker));
InformOtherNPCs();
}
if (Character.IsBot)
{
AddCombatObjective(DetermineCombatMode(Character, cumulativeDamage: realDamage), attacker);
AddCombatObjective(DetermineCombatMode(Character), attacker);
}
}
void InformOtherNPCs(float cumulativeDamage)
void InformOtherNPCs(float cumulativeDamage = 0)
{
foreach (Character otherCharacter in Character.CharacterList)
{
@@ -1238,7 +1253,7 @@ namespace Barotrauma
}
}
AIObjectiveCombat.CombatMode DetermineCombatMode(Character c, float cumulativeDamage, bool isWitnessing = false)
AIObjectiveCombat.CombatMode DetermineCombatMode(Character c, float cumulativeDamage = 0, bool isWitnessing = false)
{
if (!IsFriendly(attacker))
{
@@ -1258,7 +1273,6 @@ namespace Barotrauma
}
else
{
float dmgThreshold = attacker.TeamID == Character.TeamID ? 50 : minorDamageThreshold;
if (isAttackerInfected)
{
cumulativeDamage = 100;
@@ -1266,8 +1280,7 @@ namespace Barotrauma
if (GameMain.IsSingleplayer && attacker.IsPlayer && Character.TeamID == attacker.TeamID)
{
// Bots in the player team never act aggressively in single player when attacked by the player
dmgThreshold = minorDamageThreshold;
return cumulativeDamage > dmgThreshold ? AIObjectiveCombat.CombatMode.Retreat : AIObjectiveCombat.CombatMode.None;
return cumulativeDamage > minorDamageThreshold ? AIObjectiveCombat.CombatMode.Retreat : AIObjectiveCombat.CombatMode.None;
}
if (Character.Submarine == null || !Character.Submarine.GetConnectedSubs().Contains(attacker.Submarine))
{
@@ -1308,21 +1321,25 @@ namespace Barotrauma
// Already targeting the attacker -> treat as a more serious threat.
cumulativeDamage *= 2;
}
if (cumulativeDamage > dmgThreshold)
if (cumulativeDamage > majorDamageThreshold)
{
if (c.IsSecurity)
{
return c.IsSecurity ? AIObjectiveCombat.CombatMode.Offensive : AIObjectiveCombat.CombatMode.Arrest;
return AIObjectiveCombat.CombatMode.Offensive;
}
else
{
return c == Character ? AIObjectiveCombat.CombatMode.Defensive : AIObjectiveCombat.CombatMode.Retreat;
}
}
else
else if (cumulativeDamage > minorDamageThreshold)
{
return c.IsSecurity ? AIObjectiveCombat.CombatMode.Arrest : AIObjectiveCombat.CombatMode.Retreat;
}
else
{
return AIObjectiveCombat.CombatMode.None;
}
}
Character FindInstigator()
@@ -30,7 +30,7 @@ namespace Barotrauma
private float holdFireTimer;
private bool hasAimed;
private bool isLethalWeapon;
private bool AllowCoolDown => !IsOffensiveOrArrest || Mode != initialMode;
private bool AllowCoolDown => !IsOffensiveOrArrest || Mode != initialMode || character.TeamID == Enemy.TeamID;
public Character Enemy { get; private set; }
public bool HoldPosition { get; set; }
@@ -143,7 +143,7 @@ namespace Barotrauma
{
Mode = CombatMode.Retreat;
}
spreadTimer = Rand.Range(-10, 10);
spreadTimer = Rand.Range(-10f, 10f);
HumanAIController.SortTimer = 0;
}
@@ -1177,7 +1177,7 @@ namespace Barotrauma
}
private void SpeakNoWeapons() => Speak("dialogcombatnoweapons", delay: 0, minDuration: 30);
private void AskHelp() => Speak("dialogcombatretreating", delay: Rand.Range(0, 1), minDuration: 20);
private void AskHelp() => Speak("dialogcombatretreating", delay: Rand.Range(0f, 1f), minDuration: 20);
private void Speak(string textIdentifier, float delay, float minDuration)
{
@@ -43,7 +43,6 @@ namespace Barotrauma
private readonly float minDistance = 50;
private readonly float seekGapsInterval = 1;
private float seekGapsTimer;
private bool cannotFollow;
/// <summary>
/// Display units
@@ -52,6 +51,11 @@ namespace Barotrauma
{
get
{
if (IsFollowOrderObjective && Target is Character targetCharacter && (targetCharacter.CurrentHull == null) != (character.CurrentHull == null))
{
// Keep close when the target is going inside/outside
return minDistance;
}
float dist = _closeEnough * CloseEnoughMultiplier;
float extraMultiplier = Math.Clamp(CloseEnoughMultiplier * 0.6f, 1, 3);
if (character.AnimController.InWater)
@@ -288,28 +292,16 @@ namespace Barotrauma
{
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit: false, objectiveManager),
onAbandon: () => Abandon = true,
onCompleted: () =>
{
cannotFollow = false;
RemoveSubObjective(ref findDivingGear);
});
onCompleted: () => RemoveSubObjective(ref findDivingGear));
}
else
{
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit, objectiveManager),
onAbandon: () => Abandon = true,
onCompleted: () =>
{
cannotFollow = false;
RemoveSubObjective(ref findDivingGear);
});
onCompleted: () => RemoveSubObjective(ref findDivingGear));
}
return;
}
else
{
cannotFollow = false;
}
}
if (repeat)
{
@@ -735,7 +727,6 @@ namespace Barotrauma
findDivingGear = null;
seekGapsTimer = 0;
TargetGap = null;
cannotFollow = false;
}
}
}
@@ -408,7 +408,7 @@ namespace Barotrauma
if (orderGiver == null) { return null; }
newObjective = new AIObjectiveGoTo(orderGiver, character, this, repeat: true, priorityModifier: priorityModifier)
{
CloseEnough = Rand.Range(80, 100),
CloseEnough = Rand.Range(80f, 100f),
CloseEnoughMultiplier = Math.Min(1 + HumanAIController.CountCrew(c => c.ObjectiveManager.HasOrder<AIObjectiveGoTo>(o => o.Target == orderGiver), onlyBots: true) * Rand.Range(0.8f, 1f), 4),
ExtraDistanceOutsideSub = 100,
ExtraDistanceWhileSwimming = 100,
@@ -221,6 +221,9 @@ namespace Barotrauma
/// </summary>
public int AssignmentPriority { get; }
public bool ColoredWhenControllingGiver { get; }
public bool DisplayGiverInTooltip { get; }
public static void Init()
{
Prefabs = new Dictionary<string, Order>();
@@ -406,6 +409,8 @@ namespace Barotrauma
DrawIconWhenContained = orderElement.GetAttributeBool("displayiconwhencontained", false);
AutoDismiss = orderElement.GetAttributeBool("autodismiss", Category == OrderCategory.Movement);
AssignmentPriority = Math.Clamp(orderElement.GetAttributeInt("assignmentpriority", 100), 0, 100);
ColoredWhenControllingGiver = orderElement.GetAttributeBool("coloredwhencontrollinggiver", false);
DisplayGiverInTooltip = orderElement.GetAttributeBool("displaygiverintooltip", false);
}
/// <summary>
@@ -441,6 +446,8 @@ namespace Barotrauma
Hidden = prefab.Hidden;
IgnoreAtOutpost = prefab.IgnoreAtOutpost;
AssignmentPriority = prefab.AssignmentPriority;
ColoredWhenControllingGiver = prefab.ColoredWhenControllingGiver;
DisplayGiverInTooltip = prefab.DisplayGiverInTooltip;
OrderGiver = orderGiver;
TargetEntity = targetEntity;
@@ -134,6 +134,7 @@ namespace Barotrauma
aggregate += Items[i].Commonness;
if (aggregate >= r && Items[i].Prefab != null)
{
GameAnalyticsManager.AddDesignEvent("MicroInteraction:" + (GameMain.GameSession?.GameMode?.Preset.Identifier ?? "null") + ":PetProducedItem:" + pet.AiController.Character.SpeciesName + ":" + Items[i].Prefab.Identifier);
Entity.Spawner.AddToSpawnQueue(Items[i].Prefab, pet.AiController.Character.WorldPosition);
break;
}
@@ -200,6 +201,8 @@ namespace Barotrauma
break;
}
}
GameAnalyticsManager.AddDesignEvent("MicroInteraction:" + (GameMain.GameSession?.GameMode?.Preset.Identifier ?? "null") + ":PetSpawned:" + aiController.Character.SpeciesName);
}
public StatusIndicatorType GetCurrentStatusIndicatorType()
@@ -210,23 +213,44 @@ namespace Barotrauma
return StatusIndicatorType.None;
}
public bool OnEat(IEnumerable<string> tags, float amount)
public bool OnEat(Item item)
{
bool success = OnEat(item.GetTags());
if (success)
{
GameAnalyticsManager.AddDesignEvent("MicroInteraction:" + (GameMain.GameSession?.GameMode?.Preset.Identifier ?? "null") + ":PetEat:" + AiController.Character.SpeciesName + ":" + item.prefab.Identifier);
}
return success;
}
public bool OnEat(Character character)
{
if (character == null || !character.IsDead) { return false; }
bool success = OnEat("dead");
if (success)
{
GameAnalyticsManager.AddDesignEvent("MicroInteraction:" + (GameMain.GameSession?.GameMode?.Preset.Identifier ?? "null") + ":PetEat:" + AiController.Character.SpeciesName + ":" + character.SpeciesName);
}
return success;
}
private bool OnEat(IEnumerable<string> tags)
{
foreach (string tag in tags)
{
if (OnEat(tag, amount)) { return true; }
if (OnEat(tag)) { return true; }
}
return false;
}
public bool OnEat(string tag, float amount)
private bool OnEat(string tag)
{
for (int i = 0; i < foods.Count; i++)
{
if (tag.Equals(foods[i].Tag, System.StringComparison.OrdinalIgnoreCase))
{
Hunger += foods[i].Hunger * amount;
Happiness += foods[i].Happiness * amount;
Hunger += foods[i].Hunger;
Happiness += foods[i].Happiness;
#if CLIENT
AiController.Character.PlaySound(CharacterSound.SoundType.Happy, 0.5f);
#endif
@@ -22,7 +22,13 @@ namespace Barotrauma
public override void CalculateImportanceSpecific()
{
if (TargetItemComponent is Turret turret && !turret.HasPowerToShoot()) { return; }
if (TargetItemComponent is Turret turret && !turret.HasPowerToShoot())
{
//operate (= recharge the turrets) with low priority if they're out of power
//if something else (issues with reactor or the electrical grid) is preventing them from being charged, fixing those issues should take priority
Importance = ShipCommandManager.MinimumIssueThreshold * 1.05f;
return;
}
targetingImportances.Clear();
foreach (Character character in shipCommandManager.EnemyCharacters)
@@ -51,7 +51,7 @@ namespace Barotrauma
private const float RamTimerMax = 17.5f;
public readonly List<ShipIssueWorker> ShipIssueWorkers = new List<ShipIssueWorker>();
private const float MinimumIssueThreshold = 10f;
public const float MinimumIssueThreshold = 10f;
private const float IssueDevotionBuffer = 5f;
private float decisionTimer = 6f;
@@ -254,7 +254,7 @@ namespace Barotrauma
private void SpawnInitialCells()
{
int brainRoomCells = Rand.Range(MinCellsPerBrainRoom, MaxCellsPerRoom);
int brainRoomCells = Rand.Range(MinCellsPerBrainRoom, MaxCellsPerRoom + 1);
if (brain.CurrentHull?.WaterPercentage >= MinWaterLevel)
{
for (int i = 0; i < brainRoomCells; i++)
@@ -262,12 +262,12 @@ namespace Barotrauma
if (!TrySpawnCell(out _, brain.CurrentHull)) { break; }
}
}
int cellsInside = Rand.Range(MinCellsInside, MaxCellsInside);
int cellsInside = Rand.Range(MinCellsInside, MaxCellsInside + 1);
for (int i = 0; i < cellsInside; i++)
{
if (!TrySpawnCell(out _)) { break; }
}
int cellsOutside = Rand.Range(MinCellsOutside, MaxCellsOutside);
int cellsOutside = Rand.Range(MinCellsOutside, MaxCellsOutside + 1);
// If we failed to spawn some of the cells in the brainroom/inside, spawn some extra cells outside.
cellsOutside = Math.Clamp(cellsOutside + brainRoomCells + cellsInside - protectiveCells.Count, cellsOutside, MaxCellsOutside);
for (int i = 0; i < cellsOutside; i++)
@@ -420,7 +420,7 @@ namespace Barotrauma
if (Character.AIController is EnemyAIController enemyAi)
{
enemyAi.PetBehavior?.OnEat("dead", 1.0f);
enemyAi.PetBehavior?.OnEat(target);
}
character.SelectedCharacter = null;
@@ -533,6 +533,8 @@ namespace Barotrauma
bool onSlope = Math.Abs(movement.X) > 0.01f && Math.Abs(floorNormal.X) > 0.1f && Math.Sign(floorNormal.X) != Math.Sign(movement.X);
bool movingHorizontally = !MathUtils.NearlyEqual(targetMovement.X, 0.0f);
if (Stairs != null || onSlope)
{
torso.PullJointWorldAnchorB = new Vector2(
@@ -562,10 +564,8 @@ namespace Barotrauma
if (!torso.Disabled)
{
if (TorsoPosition.HasValue)
{
y += TorsoPosition.Value;
}
if (TorsoPosition.HasValue) { y += TorsoPosition.Value; }
if (Crouching && !movingHorizontally) { y -= HumanCrouchParams.MoveDownAmountWhenStationary; }
torso.PullJointWorldAnchorB =
MathUtils.SmoothStep(torso.SimPosition,
new Vector2(footMid + movement.X * TorsoLeanAmount, y), getUpForce);
@@ -574,10 +574,8 @@ namespace Barotrauma
if (!head.Disabled)
{
y = colliderPos.Y + stepLift * CurrentGroundedParams.StepLiftHeadMultiplier;
if (HeadPosition.HasValue)
{
y += HeadPosition.Value;
}
if (HeadPosition.HasValue) { y += HeadPosition.Value; }
if (Crouching && !movingHorizontally) { y -= HumanCrouchParams.MoveDownAmountWhenStationary; }
head.PullJointWorldAnchorB =
MathUtils.SmoothStep(head.SimPosition,
new Vector2(footMid + movement.X * HeadLeanAmount, y), getUpForce * 1.2f);
@@ -593,12 +591,15 @@ namespace Barotrauma
{
float torsoAngle = TorsoAngle.Value;
float herpesStrength = character.CharacterHealth.GetAfflictionStrength("spaceherpes");
if (Crouching && !movingHorizontally) { torsoAngle -= HumanCrouchParams.ExtraTorsoAngleWhenStationary; }
torsoAngle -= herpesStrength / 150.0f;
torso.body.SmoothRotate(torsoAngle * Dir, CurrentGroundedParams.TorsoTorque);
}
if (HeadAngle.HasValue)
{
head.body.SmoothRotate(HeadAngle.Value * Dir, CurrentGroundedParams.HeadTorque);
float headAngle = HeadAngle.Value;
if (Crouching && !movingHorizontally) { headAngle -= HumanCrouchParams.ExtraHeadAngleWhenStationary; }
head.body.SmoothRotate(headAngle * Dir, CurrentGroundedParams.HeadTorque);
}
if (!onGround)
@@ -616,8 +617,7 @@ namespace Barotrauma
Vector2 waistPos = waist != null ? waist.SimPosition : torso.SimPosition;
//moving horizontally
if (TargetMovement.X != 0.0f)
if (movingHorizontally)
{
//progress the walking animation
WalkPos -= MathHelper.ToRadians(CurrentAnimationParams.CycleSpeed) * walkCycleMultiplier * movement.X;
@@ -261,9 +261,16 @@ namespace Barotrauma
public AttackResult LastDamage;
public Dictionary<ItemPrefab, double> ItemSelectedDurations
{
get { return itemSelectedDurations; }
}
private readonly Dictionary<ItemPrefab, double> itemSelectedDurations = new Dictionary<ItemPrefab, double>();
private double itemSelectedTime;
public float InvisibleTimer;
private CharacterPrefab prefab;
private readonly CharacterPrefab prefab;
public readonly CharacterParams Params;
public string SpeciesName => Params?.SpeciesName ?? "null";
@@ -700,7 +707,7 @@ namespace Barotrauma
{
get
{
if (!CanSpeak || IsUnconscious || Stun > 0.0f || IsDead) { return 100.0f; }
if (!CanSpeak || IsUnconscious || IsKnockedDown) { return 100.0f; }
return speechImpediment;
}
set
@@ -737,9 +744,7 @@ namespace Barotrauma
get => _selectedConstruction;
set
{
#if CLIENT
var prevSelectedConstruction = _selectedConstruction;
#endif
_selectedConstruction = value;
#if CLIENT
HintManager.OnSetSelectedConstruction(this, prevSelectedConstruction, _selectedConstruction);
@@ -755,6 +760,19 @@ namespace Barotrauma
}
}
#endif
if (prevSelectedConstruction == null && _selectedConstruction != null)
{
itemSelectedTime = Timing.TotalTime;
}
else if (prevSelectedConstruction != null && _selectedConstruction == null && itemSelectedTime > 0)
{
if (!itemSelectedDurations.ContainsKey(prevSelectedConstruction.Prefab))
{
itemSelectedDurations.Add(prevSelectedConstruction.Prefab, 0);
}
itemSelectedDurations[prevSelectedConstruction.Prefab] += Timing.TotalTime - itemSelectedTime;
itemSelectedTime = 0;
}
}
}
@@ -3950,27 +3968,44 @@ namespace Barotrauma
AnimController.Frozen = false;
if (GameAnalyticsManager.SendUserStatistics)
{
string characterType = "Unknown";
if (this == Controlled)
characterType = "Player";
else if (IsRemotePlayer)
characterType = "RemotePlayer";
else if (AIController is EnemyAIController)
characterType = "Enemy";
else if (AIController is HumanAIController)
characterType = "AICrew";
string causeOfDeathStr = causeOfDeathAffliction == null ?
causeOfDeath.ToString() : causeOfDeathAffliction.Prefab.Name.Replace(" ", "");
GameAnalyticsManager.AddDesignEvent("Kill:" + characterType + ":" + SpeciesName + ":" + causeOfDeathStr);
}
CauseOfDeath = new CauseOfDeath(
causeOfDeath, causeOfDeathAffliction?.Prefab,
causeOfDeathAffliction?.Source ?? LastAttacker, LastDamageSource);
causeOfDeathAffliction?.Source, LastDamageSource);
if (GameAnalyticsManager.SendUserStatistics)
{
string causeOfDeathStr = causeOfDeathAffliction == null ?
causeOfDeath.ToString() : causeOfDeathAffliction.Prefab.Identifier.Replace(" ", "");
string characterType = GetCharacterType(this);
GameAnalyticsManager.AddDesignEvent("Kill:" + characterType + ":" + causeOfDeathStr);
if (CauseOfDeath.Killer != null)
{
GameAnalyticsManager.AddDesignEvent("Kill:" + characterType + ":Killer:" + GetCharacterType(CauseOfDeath.Killer));
}
if (CauseOfDeath.DamageSource != null)
{
string damageSourceStr = CauseOfDeath.DamageSource.ToString();
if (CauseOfDeath.DamageSource is Item damageSourceItem) { damageSourceStr = damageSourceItem.ToString(); }
GameAnalyticsManager.AddDesignEvent("Kill:" + characterType + ":DamageSource:" + damageSourceStr);
}
static string GetCharacterType(Character character)
{
if (character.IsPlayer)
return "Player";
else if (character.AIController is EnemyAIController)
return "Enemy" + character.SpeciesName;
else if (character.AIController is HumanAIController && character.TeamID == CharacterTeamType.Team2)
return "EnemyHuman";
else if (character.Info != null && character.TeamID == CharacterTeamType.Team1)
return "AICrew";
else if (character.Info != null && character.TeamID == CharacterTeamType.FriendlyNPC)
return "FriendlyNPC";
return "Unknown";
}
}
OnDeath?.Invoke(this, CauseOfDeath);
var abilityCharacterKiller = new AbilityCharacterKiller(CauseOfDeath.Killer);
@@ -4097,7 +4132,7 @@ namespace Barotrauma
info?.Remove();
#if CLIENT
GameMain.GameSession?.CrewManager?.KillCharacter(this);
GameMain.GameSession?.CrewManager?.KillCharacter(this, resetCrewListIndex: false);
#endif
CharacterList.Remove(this);
@@ -4112,6 +4147,8 @@ namespace Barotrauma
}
}
itemSelectedDurations.Clear();
DisposeProjSpecific();
aiTarget?.Remove();
@@ -1527,7 +1527,7 @@ namespace Barotrauma
orderTargetElement.Add(new XAttribute("hullid", (uint)ot.Hull.ID));
position -= ot.Hull.WorldPosition;
}
orderTargetElement.Add(new XAttribute("position", $"{position.X},{position.Y}"));
orderTargetElement.Add(new XAttribute("position", XMLExtensions.Vector2ToString(position)));
orderElement.Add(orderTargetElement);
break;
case Order.OrderTargetType.WallSection when targetAvailableInNextLevel && order.TargetEntity is Structure s && order.WallSectionIndex.HasValue:
@@ -106,7 +106,7 @@ namespace Barotrauma
{
if (State != InfectionState.Active && stun)
{
character.SetStun(Rand.Range(2, 4));
character.SetStun(Rand.Range(2f, 3f));
}
State = InfectionState.Active;
ActivateHusk();
@@ -363,7 +363,9 @@ namespace Barotrauma
public readonly string Name, Description;
public readonly string TranslationOverride;
public readonly bool IsBuff;
public readonly bool HealableInMedicalClinic;
public readonly float HealCostMultiplier;
public readonly int BaseHealCost;
public readonly string CauseOfDeathDescription, SelfCauseOfDeathDescription;
@@ -656,7 +658,13 @@ namespace Barotrauma
Name = TextManager.Get("AfflictionName." + translationId, true) ?? element.GetAttributeString("name", "");
Description = TextManager.Get("AfflictionDescription." + translationId, true) ?? element.GetAttributeString("description", "");
IsBuff = element.GetAttributeBool("isbuff", false);
HealableInMedicalClinic = element.GetAttributeBool("healableinmedicalclinic",
!IsBuff &&
!AfflictionType.Equals("geneticmaterialbuff", StringComparison.OrdinalIgnoreCase) &&
!AfflictionType.Equals("geneticmaterialdebuff", StringComparison.OrdinalIgnoreCase));
HealCostMultiplier = element.GetAttributeFloat(nameof(HealCostMultiplier).ToLowerInvariant(), 1f);
BaseHealCost = element.GetAttributeInt(nameof(BaseHealCost).ToLowerInvariant(), 0);
if (element.Attribute("nameidentifier") != null)
{
@@ -764,6 +764,7 @@ namespace Barotrauma
if (applyAffliction)
{
afflictionsCopy.Add(newAffliction);
newAffliction.Source ??= attacker;
}
appliedDamageModifiers.AddRange(tempModifiers);
}
@@ -26,6 +26,15 @@ namespace Barotrauma
class HumanCrouchParams : HumanGroundedParams
{
[Serialize(0.0f, true, description: "How much lower the character's head and torso move when stationary."), Editable(MinValueFloat = 0, MaxValueFloat = 2, DecimalCount = 2)]
public float MoveDownAmountWhenStationary { get; set; }
[Serialize(0.0f, true), Editable(-360f, 360f)]
public float ExtraHeadAngleWhenStationary { get; set; }
[Serialize(0.0f, true), Editable(-360f, 360f)]
public float ExtraTorsoAngleWhenStationary { get; set; }
public static HumanCrouchParams GetDefaultAnimParams(Character character) => GetDefaultAnimParams<HumanCrouchParams>(character, AnimationType.Crouch);
public static HumanCrouchParams GetAnimParams(Character character, string fileName = null)
{
@@ -23,7 +23,9 @@ namespace Barotrauma.Abilities
multiplier = 0 + Character.Info.GetSavedStatValue(StatTypes.None, scalingStatIdentifier);
}
targetCharacter.GiveMoney((int)(multiplier * amount));
int totalAmount = (int)(multiplier * amount);
targetCharacter.GiveMoney(totalAmount);
GameAnalyticsManager.AddMoneyGainedEvent(totalAmount, GameAnalyticsManager.MoneySource.Ability, CharacterTalent.Prefab.Identifier);
}
protected override void ApplyEffect(AbilityObject abilityObject)
@@ -16,7 +16,9 @@ namespace Barotrauma.Abilities
{
if ((abilityObject as IAbilityCharacter)?.Character is Character character)
{
Character.GiveMoney((int)(vitalityPercentage * character.MaxVitality));
int totalAmount = (int)(vitalityPercentage * character.MaxVitality);
Character.GiveMoney(totalAmount);
GameAnalyticsManager.AddMoneyGainedEvent(totalAmount, GameAnalyticsManager.MoneySource.Ability, CharacterTalent.Prefab.Identifier);
}
}
}
@@ -30,6 +30,7 @@ namespace Barotrauma.Abilities
if (!enemyCharacter.LockHands) { continue; }
if (timesGiven > max) { continue; }
Character.GiveMoney(moneyAmount);
GameAnalyticsManager.AddMoneyGainedEvent(moneyAmount, GameAnalyticsManager.MoneySource.Ability, CharacterTalent.Prefab.Identifier);
foreach (Character character in Character.GetFriendlyCrew(Character))
{
character.Info?.GiveExperience(experienceAmount);
@@ -12,8 +12,6 @@ namespace Barotrauma.Abilities
private readonly int moneyPerMission;
private static List<Client> clientsAlreadyUsed = new List<Client>();
public CharacterAbilityInsurancePolicy(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
moneyPerMission = abilityElement.GetAttributeInt("moneypermission", 0);
@@ -23,7 +21,9 @@ namespace Barotrauma.Abilities
{
if (Character?.Info is CharacterInfo info)
{
Character.GiveMoney(moneyPerMission * info.MissionsCompletedSinceDeath);
int totalAmount = moneyPerMission * info.MissionsCompletedSinceDeath;
Character.GiveMoney(totalAmount);
GameAnalyticsManager.AddMoneyGainedEvent(totalAmount, GameAnalyticsManager.MoneySource.Ability, CharacterTalent.Prefab.Identifier);
}
}
}
@@ -1534,6 +1534,7 @@ namespace Barotrauma
if (int.TryParse(args[0], out int money))
{
campaign.Money += money;
GameAnalyticsManager.AddMoneyGainedEvent(money, GameAnalyticsManager.MoneySource.Cheat, "console");
}
else
{
@@ -28,6 +28,7 @@ namespace Barotrauma
if (GameMain.GameSession?.GameMode is CampaignMode campaign)
{
campaign.Money += Amount;
GameAnalyticsManager.AddMoneyGainedEvent(Amount, GameAnalyticsManager.MoneySource.Event, ParentEvent.Prefab.Identifier);
#if SERVER
(campaign as MultiPlayerCampaign).LastUpdateID++;
#endif
@@ -152,7 +152,7 @@ namespace Barotrauma
if (spawnPoint is WayPoint wp && wp.CurrentHull != null && wp.CurrentHull.Rect.Width > 100)
{
spawnPos = new Vector2(
MathHelper.Clamp(wp.WorldPosition.X + Rand.Range(-200, 200), wp.CurrentHull.WorldRect.X + 50, wp.CurrentHull.WorldRect.Right - 50),
MathHelper.Clamp(wp.WorldPosition.X + Rand.Range(-200, 201), wp.CurrentHull.WorldRect.X + 50, wp.CurrentHull.WorldRect.Right - 50),
wp.CurrentHull.WorldRect.Y - wp.CurrentHull.Rect.Height + 16.0f);
}
var item = new Item(itemPrefab, spawnPos, null);
@@ -319,6 +319,19 @@ namespace Barotrauma
}
}
foreach (Character character in characters)
{
if (character.Inventory == null) { continue; }
foreach (Item item in character.Inventory.AllItemsMod)
{
//item didn't spawn with the characters -> drop it
if (!characterItems.Any(c => c.Value.Contains(item)))
{
item.Drop(character);
}
}
}
// characters that survived will take their items with them, in case players tried to be crafty and steal them
// this needs to run here in case players abort the mission by going back home
// TODO: I think this might feel like a bug.
@@ -378,7 +378,10 @@ namespace Barotrauma
crewCharacters.ForEach(c => c.CheckTalents(AbilityEffectType.OnGainMissionMoney, missionMoneyGainMultiplier));
crewCharacters.ForEach(c => missionMoneyGainMultiplier.Value += c.GetStatValue(StatTypes.MissionMoneyGainMultiplier));
campaign.Money += (int)(reward * missionMoneyGainMultiplier.Value);
int totalReward = (int)(reward * missionMoneyGainMultiplier.Value);
campaign.Money += totalReward;
GameAnalyticsManager.AddMoneyGainedEvent(totalReward, GameAnalyticsManager.MoneySource.MissionReward, Prefab.Identifier);
foreach (Character character in crewCharacters)
{
@@ -328,7 +328,7 @@ namespace Barotrauma
}
else
{
dir = new Vector2(1, Rand.Range(-1, 1));
dir = new Vector2(1, Rand.Range(-1f, 1f));
}
Vector2 targetPos = spawnPos.Value + dir * offset;
var targetWaypoint = waypoints.OrderBy(wp => Vector2.DistanceSquared(wp.WorldPosition, targetPos)).FirstOrDefault();
@@ -475,6 +475,7 @@ namespace Barotrauma
{
scatterAmount = 0;
}
for (int i = 0; i < amount; i++)
{
string seed = Level.Loaded.Seed + i.ToString();
@@ -540,6 +541,13 @@ namespace Barotrauma
SwarmBehavior.CreateSwarm(monsters.Cast<AICharacter>());
DebugConsole.NewMessage($"Spawned: {ToString()}. Strength: {StringFormatter.FormatZeroDecimal(monsters.Sum(m => m.Params.AI.CombatStrength))}.", Color.LightBlue, debugOnly: true);
}
if (GameMain.GameSession != null)
{
GameAnalyticsManager.AddDesignEvent(
$"MonsterSpawn:{GameMain.GameSession.GameMode?.Preset?.Identifier ?? "none"}:{Level.Loaded?.LevelData?.Biome?.Identifier ?? "none"}:{SpawnPosType}:{speciesName}",
value: Timing.TotalTime - GameMain.GameSession.RoundStartTime);
}
}, delayBetweenSpawns * i);
}
}
@@ -1,12 +1,11 @@
using System;
using Barotrauma.Steam;
using RestSharp;
using System;
using System.Net;
using System.Threading.Tasks;
namespace Barotrauma
{
public static partial class GameAnalyticsManager
static partial class GameAnalyticsManager
{
public enum Consent
{
@@ -1,5 +1,6 @@
#nullable enable
using Barotrauma.IO;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -9,7 +10,7 @@ using System.Text;
namespace Barotrauma
{
public static partial class GameAnalyticsManager
static partial class GameAnalyticsManager
{
public enum ErrorSeverity
{
@@ -29,6 +30,61 @@ namespace Barotrauma
Fail = 3
}
public enum CustomDimensions01
{
Vanilla,
Modded
}
public enum CustomDimensions02
{
None,
Difficulty0to10,
Difficulty10to20,
Difficulty20to30,
Difficulty30to40,
Difficulty40to50,
Difficulty50to60,
Difficulty60to70,
Difficulty70to80,
Difficulty80to90,
Difficulty90to100,
}
public enum ResourceCurrency
{
Money
}
public enum ResourceFlowType
{
Undefined = 0,
Source = 1,
Sink = 2
}
public enum MoneySource
{
Unknown,
MissionReward,
Store,
Event,
Ability,
Cheat
}
public enum MoneySink
{
Unknown,
Store,
Service,
Crew,
SubmarineUpgrade,
SubmarineWeapon,
SubmarinePurchase,
SubmarineSwitch
}
private readonly static HashSet<string> sentEventIdentifiers = new HashSet<string>();
private class Implementation : IDisposable
@@ -69,13 +125,29 @@ namespace Barotrauma
internal void AddProgressionEvent(ProgressionStatus status, string progression01, string progression02, string progression03)
=> addProgressionEvent03(status, progression01, progression02, progression03);
private readonly Action<ResourceFlowType, string, float, string, string> addResourceEvent;
internal void AddResourceEvent(ResourceFlowType flowType, string currency, float amount, string itemType, string itemId)
=> addResourceEvent(flowType, currency, amount, itemType, itemId);
private readonly Action<string> setCustomDimension01;
internal void SetCustomDimension01(string dimension01)
=> setCustomDimension01(dimension01);
private readonly Action<string[]> configureAvailableCustomDimensions01;
internal void ConfigureAvailableCustomDimensions01(params string[] customDimensions)
=> configureAvailableCustomDimensions01(customDimensions);
internal void ConfigureAvailableCustomDimensions01(params CustomDimensions01[] customDimensions)
=> configureAvailableCustomDimensions01(customDimensions.Select(d => d.ToString()).ToArray());
private readonly Action<string> setCustomDimension02;
internal void SetCustomDimension02(string dimension02)
=> setCustomDimension02(dimension02);
private readonly Action<string[]> configureAvailableCustomDimensions02;
internal void ConfigureAvailableCustomDimensions02(params CustomDimensions02[] customDimensions)
=> configureAvailableCustomDimensions02(customDimensions.Select(d => d.ToString()).ToArray());
private readonly Action<string[]> configureAvailableResourceCurrencies;
internal void ConfigureAvailableResourceCurrencies(params ResourceCurrency[] customDimensions)
=> configureAvailableResourceCurrencies(customDimensions.Select(d => d.ToString()).ToArray());
private readonly Action<bool> setEnabledInfoLog;
internal void SetEnabledInfoLog(bool enabled)
@@ -94,6 +166,7 @@ namespace Barotrauma
private readonly object?[] args2 = new object?[2];
private readonly object?[] args3 = new object?[3];
private readonly object?[] args4 = new object?[4];
private readonly object?[] args5 = new object?[5];
private Action Call(MethodInfo methodInfo)
=> () => methodInfo?.Invoke(null, null);
@@ -131,6 +204,17 @@ namespace Barotrauma
args4[3] = arg4;
methodInfo.Invoke(null, args4);
};
private Action<T1, T2, T3, T4, T5> Call<T1, T2, T3, T4, T5>(MethodInfo methodInfo)
=> (T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) =>
{
args5[0] = arg1;
args5[1] = arg2;
args5[2] = arg3;
args5[3] = arg4;
args5[4] = arg5;
methodInfo.Invoke(null, args5);
};
#endregion
private AssemblyLoadContext? loadContext;
@@ -165,9 +249,15 @@ namespace Barotrauma
var mainClass = getType(MainClass);
var errorSeverityEnumType = getType($"{EnumPrefix}{nameof(ErrorSeverity)}");
var progressionStatusEnumType = getType($"{EnumPrefix}{nameof(ProgressionStatus)}");
var resourceFlowTypeEnumType = getType($"{EnumPrefix}{nameof(ResourceFlowType)}");
MethodInfo getMethod(string name, Type[] types)
{
foreach (var me in mainClass.GetMethods())
{
var aksjdnakjsdnf = me;
}
return mainClass?.GetMethod(name, BindingFlags.Public | BindingFlags.Static, binder: null, types: types, modifiers: null)
?? throw new Exception($"Could not find method \"{name}\" with types {string.Join(',', types.Select(t => t.Name))}");
}
@@ -190,10 +280,20 @@ namespace Barotrauma
new Type[] { progressionStatusEnumType, typeof(string), typeof(string) }));
addProgressionEvent03 = Call<ProgressionStatus, string, string, string>(getMethod(nameof(AddProgressionEvent),
new Type[] { progressionStatusEnumType, typeof(string), typeof(string), typeof(string) }));
setCustomDimension01 = Call<string>(getMethod(nameof(SetCustomDimension01),
new Type[] { typeof(string) }));
configureAvailableCustomDimensions01 = Call<string[]>(getMethod(nameof(ConfigureAvailableCustomDimensions01),
new Type[] { typeof(string[]) }));
setCustomDimension02 = Call<string>(getMethod(nameof(SetCustomDimension02),
new Type[] { typeof(string) }));
configureAvailableCustomDimensions02 = Call<string[]>(getMethod(nameof(ConfigureAvailableCustomDimensions02),
new Type[] { typeof(string[]) }));
configureAvailableResourceCurrencies = Call<string[]>(getMethod(nameof(ConfigureAvailableResourceCurrencies),
new Type[] { typeof(string[]) }));
addResourceEvent = Call<ResourceFlowType, string, float, string, string>(getMethod(nameof(AddResourceEvent),
new Type[] { resourceFlowTypeEnumType, typeof(string), typeof(float), typeof(string), typeof(string) }));
setEnabledInfoLog = Call<bool>(getMethod(nameof(SetEnabledInfoLog),
new Type[] { typeof(bool) }));
@@ -204,8 +304,7 @@ namespace Barotrauma
private void OnQuit()
{
try
{
{
if (assembly != null) { onQuit?.Invoke(); }
}
catch (Exception e)
@@ -298,10 +397,40 @@ namespace Barotrauma
loadedImplementation?.AddProgressionEvent(progressionStatus, progression01, progression02, progression03);
}
public static void SetCustomDimension01(string dimension)
public static void SetCustomDimension01(CustomDimensions01 dimension)
{
if (!SendUserStatistics) { return; }
loadedImplementation?.SetCustomDimension01(dimension);
loadedImplementation?.SetCustomDimension01(dimension.ToString());
}
public static void SetCurrentLevel(LevelData levelData)
{
if (!SendUserStatistics) { return; }
CustomDimensions02 customDimension = CustomDimensions02.None;
if (levelData != null)
{
float levelDifficulty = levelData.Difficulty;
customDimension = (CustomDimensions02)MathHelper.Clamp((int)(levelDifficulty / 10) + 1, 0, Enum.GetValues(typeof(CustomDimensions02)).Length - 1);
}
loadedImplementation?.SetCustomDimension02(customDimension.ToString());
}
public static void AddMoneyGainedEvent(int amount, MoneySource moneySource, string eventId)
{
AddResourceEvent(ResourceFlowType.Source, ResourceCurrency.Money, amount, moneySource.ToString(), eventId);
}
public static void AddMoneySpentEvent(int amount, MoneySink moneySink, string eventId)
{
AddResourceEvent(ResourceFlowType.Sink, ResourceCurrency.Money, amount, moneySink.ToString(), eventId);
}
private static void AddResourceEvent(ResourceFlowType flowType, ResourceCurrency currency, float amount, string eventType, string eventId)
{
if (!SendUserStatistics) { return; }
loadedImplementation?.AddResourceEvent(flowType, currency.ToString(), amount, eventType, eventId);
}
private static void Init()
@@ -359,7 +488,8 @@ namespace Barotrauma
+ exeName + ":"
+ AssemblyInfo.GitRevision + ":"
+ buildConfiguration);
loadedImplementation?.ConfigureAvailableCustomDimensions01("singleplayer", "multiplayer", "editor");
loadedImplementation?.ConfigureAvailableCustomDimensions01(Enum.GetValues(typeof(CustomDimensions01)).Cast<CustomDimensions01>().ToArray());
loadedImplementation?.ConfigureAvailableResourceCurrencies(Enum.GetValues(typeof(ResourceCurrency)).Cast<ResourceCurrency>().ToArray());
InitKeys();
@@ -380,15 +510,16 @@ namespace Barotrauma
var allPackages = GameMain.Config?.AllEnabledPackages.ToList();
if (allPackages?.Count > 0)
{
StringBuilder sb = new StringBuilder("ContentPackage: ");
int i = 0;
List<string> packageNames = new List<string>();
foreach (ContentPackage cp in allPackages)
{
string trimmedName = cp.Name.Replace(":", "").Replace(" ", "");
sb.Append(trimmedName.Substring(0, Math.Min(32, trimmedName.Length)));
if (i < allPackages.Count - 1) { sb.Append(" "); }
string sanitizedName = cp.Name.Replace(":", "").Replace(" ", "");
sanitizedName = sanitizedName.Substring(0, Math.Min(32, sanitizedName.Length));
packageNames.Add(sanitizedName);
loadedImplementation?.AddDesignEvent("ContentPackage:" + sanitizedName);
}
loadedImplementation?.AddDesignEvent(sb.ToString());
packageNames.Sort();
loadedImplementation?.AddDesignEvent("AllContentPackages:" + string.Join(", ", packageNames));
}
}
@@ -132,6 +132,7 @@ namespace Barotrauma
// Exchange money
var itemValue = item.Quantity * buyValues[item.ItemPrefab];
campaign.Money -= itemValue;
GameAnalyticsManager.AddMoneySpentEvent(itemValue, GameAnalyticsManager.MoneySink.Store, item.ItemPrefab.Identifier);
Location.StoreCurrentBalance += itemValue;
if (removeFromCrate)
@@ -291,7 +292,7 @@ namespace Barotrauma
float floorPos = hull.Rect.Y - hull.Rect.Height;
Vector2 position = new Vector2(
hull.Rect.Width > 40 ? Rand.Range(hull.Rect.X + 20, hull.Rect.Right - 20) : hull.Rect.Center.X,
hull.Rect.Width > 40 ? Rand.Range(hull.Rect.X + 20f, hull.Rect.Right - 20f) : hull.Rect.Center.X,
floorPos);
//check where the actual floor structure is in case the bottom of the hull extends below it
@@ -37,7 +37,6 @@ namespace Barotrauma
{
IsSinglePlayer = isSinglePlayer;
conversationTimer = 5.0f;
InitProjectSpecific();
}
@@ -100,10 +99,10 @@ namespace Barotrauma
foreach (XElement characterElement in element.Elements())
{
if (!characterElement.Name.ToString().Equals("character", StringComparison.OrdinalIgnoreCase)) { continue; }
CharacterInfo characterInfo = new CharacterInfo(characterElement);
#if CLIENT
if (characterElement.GetAttributeBool("lastcontrolled", false)) { characterInfo.LastControlled = true; }
characterInfo.CrewListIndex = characterElement.GetAttributeInt("crewlistindex", -1);
#endif
characterInfos.Add(characterInfo);
foreach (XElement subElement in characterElement.Elements())
@@ -133,7 +132,7 @@ namespace Barotrauma
characterInfos.Remove(characterInfo);
}
public void AddCharacter(Character character)
public void AddCharacter(Character character, bool sortCrewList = true)
{
if (character.Removed)
{
@@ -155,7 +154,11 @@ namespace Barotrauma
characterInfos.Add(character.Info);
}
#if CLIENT
AddCharacterToCrewList(character);
var characterComponent = AddCharacterToCrewList(character);
if (sortCrewList)
{
SortCrewList();
}
if (character.CurrentOrders != null)
{
foreach (var order in character.CurrentOrders)
@@ -254,12 +257,16 @@ namespace Barotrauma
}
}
AddCharacter(character);
AddCharacter(character, sortCrewList: false);
#if CLIENT
if (IsSinglePlayer && (Character.Controlled == null || character.Info.LastControlled)) { Character.Controlled = character; }
#endif
}
#if CLIENT
if (IsSinglePlayer) { SortCrewList(); }
#endif
//longer delay in multiplayer to prevent the server from triggering NPC conversations while the players are still loading the round
conversationTimer = IsSinglePlayer ? Rand.Range(5.0f, 10.0f) : Rand.Range(45.0f, 60.0f);
}
@@ -14,7 +14,7 @@ namespace Barotrauma
public Faction(CampaignMetadata metadata, FactionPrefab prefab)
{
Prefab = prefab;
Reputation = new Reputation(metadata, $"faction.{prefab.Identifier}", prefab.MinReputation, prefab.MaxReputation, prefab.InitialReputation);
Reputation = new Reputation(metadata, this, prefab.MinReputation, prefab.MaxReputation, prefab.InitialReputation);
}
}
@@ -35,9 +35,22 @@ namespace Barotrauma
private set
{
if (MathUtils.NearlyEqual(Value, value)) { return; }
float prevValue = Value;
Metadata.SetValue(metaDataIdentifier, Math.Clamp(value, MinReputation, MaxReputation));
OnReputationValueChanged?.Invoke();
OnAnyReputationValueChanged?.Invoke();
#if CLIENT
int increase = (int)Value - (int)prevValue;
if (increase != 0 && Character.Controlled != null)
{
Character.Controlled.AddMessage(
TextManager.GetWithVariable("reputationgainnotification", "[reputationname]", Location?.Name ?? Faction.Prefab.Name),
increase > 0 ? GUI.Style.Green : GUI.Style.Red,
playSound: true, Identifier, increase, lifetime: 5.0f);
}
#endif
}
}
@@ -63,15 +76,32 @@ namespace Barotrauma
public Action OnReputationValueChanged;
public static Action OnAnyReputationValueChanged;
public Reputation(CampaignMetadata metadata, string identifier, int minReputation, int maxReputation, int initialReputation)
public readonly Faction Faction;
public readonly Location Location;
public Reputation(CampaignMetadata metadata, Location location, string identifier, int minReputation, int maxReputation, int initialReputation)
: this(metadata, null, location, identifier, minReputation, maxReputation, initialReputation)
{
}
public Reputation(CampaignMetadata metadata, Faction faction, int minReputation, int maxReputation, int initialReputation)
: this(metadata, faction, null, $"faction.{faction.Prefab.Identifier}", minReputation, maxReputation, initialReputation)
{
}
private Reputation(CampaignMetadata metadata, Faction faction, Location location, string identifier, int minReputation, int maxReputation, int initialReputation)
{
System.Diagnostics.Debug.Assert(metadata != null);
System.Diagnostics.Debug.Assert(faction != null || location != null);
Metadata = metadata;
Identifier = identifier.ToLowerInvariant();
metaDataIdentifier = $"reputation.{Identifier}";
MinReputation = minReputation;
MaxReputation = maxReputation;
InitialReputation = initialReputation;
Faction = faction;
Location = location;
}
public string GetReputationName()
@@ -78,6 +78,9 @@ namespace Barotrauma
//there can be no events before this time has passed during the 1st campaign round
const float FirstRoundEventDelay = 0.0f;
public double TotalPlayTime;
public int TotalPassedLevels;
public enum InteractionType { None, Talk, Examine, Map, Crew, Store, Repair, Upgrade, PurchaseSub, MedicalClinic }
public readonly CargoManager CargoManager;
@@ -92,7 +95,7 @@ namespace Barotrauma
public CampaignSettings Settings;
private List<Mission> extraMissions = new List<Mission>();
private readonly List<Mission> extraMissions = new List<Mission>();
public enum TransitionType
{
@@ -690,11 +693,13 @@ namespace Barotrauma
GameAnalyticsManager.AddProgressionEvent(
GameAnalyticsManager.ProgressionStatus.Complete,
Name ?? "none");
Preset?.Identifier ?? "none");
string eventId = "FinishCampaign:";
GameAnalyticsManager.AddDesignEvent(eventId + "Submarine:" + (Submarine.MainSub?.Info?.Name ?? "none"));
GameAnalyticsManager.AddDesignEvent(eventId + "CrewSize:" + (CrewManager?.CharacterInfos?.Count() ?? 0));
GameAnalyticsManager.AddDesignEvent(eventId + "Money", Money);
GameAnalyticsManager.AddDesignEvent(eventId + "Money", Money);
GameAnalyticsManager.AddDesignEvent(eventId + "Playtime", TotalPlayTime);
GameAnalyticsManager.AddDesignEvent(eventId + "PassedLevels", TotalPassedLevels);
}
protected virtual void EndCampaignProjSpecific() { }
@@ -707,12 +712,14 @@ namespace Barotrauma
location.RemoveHireableCharacter(characterInfo);
CrewManager.AddCharacterInfo(characterInfo);
Money -= characterInfo.Salary;
GameAnalyticsManager.AddMoneySpentEvent(characterInfo.Salary, GameAnalyticsManager.MoneySink.Crew, characterInfo.Job?.Prefab.Identifier ?? "unknown");
return true;
}
private void NPCInteract(Character npc, Character interactor)
{
if (!npc.AllowCustomInteract) { return; }
GameAnalyticsManager.AddDesignEvent("CampaignInteraction:" + Preset.Identifier + ":" + npc.CampaignInteractionType);
NPCInteractProjSpecific(npc, interactor);
string coroutineName = "DoCharacterWait." + (npc?.ID ?? Entity.NullEntityID);
if (!CoroutineManager.IsCoroutineRunning(coroutineName))
@@ -876,6 +883,19 @@ namespace Barotrauma
}
public abstract void Save(XElement element);
protected void LoadStats(XElement element)
{
TotalPlayTime = element.GetAttributeDouble(nameof(TotalPlayTime).ToLowerInvariant(), 0);
TotalPassedLevels = element.GetAttributeInt(nameof(TotalPassedLevels).ToLowerInvariant(), 0);
}
protected XElement SaveStats()
{
return new XElement("stats",
new XAttribute(nameof(TotalPlayTime).ToLowerInvariant(), TotalPlayTime),
new XAttribute(nameof(TotalPassedLevels).ToLowerInvariant(), TotalPassedLevels));
}
public void LogState()
{
@@ -126,6 +126,10 @@ namespace Barotrauma
{
case "campaignsettings":
Settings = new CampaignSettings(subElement);
#if CLIENT
GameMain.NetworkMember.ServerSettings.MaxMissionCount = Settings.MaxMissionCount;
GameMain.NetworkMember.ServerSettings.RadiationEnabled = Settings.RadiationEnabled;
#endif
break;
case "map":
if (map == null)
@@ -159,6 +163,9 @@ namespace Barotrauma
case "pets":
petsElement = subElement;
break;
case "stats":
LoadStats(subElement);
break;
#if SERVER
case "savedexperiencepoints":
foreach (XElement savedExp in subElement.Elements())
@@ -22,6 +22,8 @@ namespace Barotrauma
public double RoundStartTime;
public double TimeSpentCleaning, TimeSpentPainting;
private readonly List<Mission> missions = new List<Mission>();
public IEnumerable<Mission> Missions { get { return missions; } }
@@ -276,6 +278,7 @@ namespace Barotrauma
}
Campaign.Money -= cost;
GameAnalyticsManager.AddMoneySpentEvent(cost, GameAnalyticsManager.MoneySink.SubmarineSwitch, newSubmarine.Name);
((CampaignMode)GameMode).PendingSubmarineSwitch = newSubmarine;
return newSubmarine;
@@ -288,6 +291,7 @@ namespace Barotrauma
if (!OwnedSubmarines.Any(s => s.Name == newSubmarine.Name))
{
Campaign.Money -= newSubmarine.Price;
GameAnalyticsManager.AddMoneySpentEvent(newSubmarine.Price, GameAnalyticsManager.MoneySink.SubmarinePurchase, newSubmarine.Name);
OwnedSubmarines.Add(newSubmarine);
}
}
@@ -409,7 +413,7 @@ namespace Barotrauma
GameAnalyticsManager.AddProgressionEvent(
GameAnalyticsManager.ProgressionStatus.Start,
GameMode?.Name ?? "none");
GameMode?.Preset?.Identifier ?? "none");
string eventId = "StartRound:" + (GameMode?.Preset?.Identifier ?? "none") + ":";
GameAnalyticsManager.AddDesignEvent(eventId + "Submarine:" + (Submarine.MainSub?.Info?.Name ?? "none"));
@@ -419,17 +423,39 @@ namespace Barotrauma
{
GameAnalyticsManager.AddDesignEvent(eventId + "MissionType:" + (mission.Prefab.Type.ToString() ?? "none") + ":" + mission.Prefab.Identifier);
}
GameAnalyticsManager.AddDesignEvent(eventId + "LevelType:" + (Level.Loaded?.Type.ToString() ?? "none"));
if (Level.Loaded != null)
{
string levelId = Level.Loaded.Type == LevelData.LevelType.Outpost ?
Level.Loaded.StartOutpost?.Info?.OutpostGenerationParams?.Identifier :
Level.Loaded.GenerationParams?.Identifier;
GameAnalyticsManager.AddDesignEvent(eventId + "LevelType:" + Level.Loaded.Type.ToString() + ":" + (levelId ?? "null"));
}
GameAnalyticsManager.AddDesignEvent(eventId + "Biome:" + (Level.Loaded?.LevelData?.Biome?.Identifier ?? "none"));
#if CLIENT
if (GameMode is TutorialMode tutorialMode)
{
GameAnalyticsManager.AddDesignEvent(eventId + tutorialMode.Tutorial.Identifier);
if (GameMain.IsFirstLaunch)
{
GameAnalyticsManager.AddDesignEvent("FirstLaunch:" + eventId + tutorialMode.Tutorial.Identifier);
}
}
#endif
if (GameMode is CampaignMode campaignMode)
{
if (campaignMode.Map?.Radiation != null && campaignMode.Map.Radiation.Enabled)
{
GameAnalyticsManager.AddDesignEvent(eventId + "RadiationEnabled");
GameAnalyticsManager.AddDesignEvent(eventId + "Radiation:Enabled");
}
else
{
GameAnalyticsManager.AddDesignEvent(eventId + "RadiationDisabled");
GameAnalyticsManager.AddDesignEvent(eventId + "Radiation:Disabled");
}
bool firstTimeInBiome = Map != null && !Map.Connections.Any(c => c.Passed && c.Biome == LevelData.Biome);
if (firstTimeInBiome)
{
GameAnalyticsManager.AddDesignEvent(eventId + (Level.Loaded?.LevelData?.Biome?.Identifier ?? "none") + "Discovered:Playtime", campaignMode.TotalPlayTime);
GameAnalyticsManager.AddDesignEvent(eventId + (Level.Loaded?.LevelData?.Biome?.Identifier ?? "none") + "Discovered:PassedLevels", campaignMode.TotalPassedLevels);
}
}
@@ -759,38 +785,29 @@ namespace Barotrauma
GameMode?.End(transitionType);
EventManager?.EndRound();
StatusEffect.StopAll();
missions.Clear();
IsRunning = false;
bool success = false;
#if CLIENT
success = CrewManager.GetCharacters().Any(c => !c.IsDead);
bool success = CrewManager.GetCharacters().Any(c => !c.IsDead);
#else
success = GameMain.Server.ConnectedClients.Any(c => c.InGame && c.Character != null && !c.Character.IsDead);
bool success = GameMain.Server.ConnectedClients.Any(c => c.InGame && c.Character != null && !c.Character.IsDead);
#endif
double roundDuration = Timing.TotalTime - RoundStartTime;
GameAnalyticsManager.AddProgressionEvent(
success ? GameAnalyticsManager.ProgressionStatus.Complete : GameAnalyticsManager.ProgressionStatus.Fail,
GameMode?.Name ?? "none",
roundDuration);
string eventId = "EndRound:GameMode:" + (GameMode?.Name ?? "none") + ":";
GameAnalyticsManager.AddDesignEvent(eventId + "Submarine:" + (Submarine.MainSub?.Info?.Name ?? "none"), roundDuration);
GameAnalyticsManager.AddDesignEvent(eventId + "GameMode:" + (GameMode?.Name ?? "none"), roundDuration);
GameAnalyticsManager.AddDesignEvent(eventId + "CrewSize:" + (CrewManager?.CharacterInfos?.Count() ?? 0), roundDuration);
foreach (Mission mission in missions)
{
GameAnalyticsManager.AddDesignEvent(eventId + "MissionType:" + (mission.Prefab.Type.ToString() ?? "none") + ":" + mission.Prefab.Identifier + ":" + (mission.Completed ? "Completed" : "Failed"), roundDuration);
}
GameAnalyticsManager.AddDesignEvent(eventId + "LevelType:" + (Level.Loaded?.Type.ToString() ?? "none"), roundDuration);
GameAnalyticsManager.AddDesignEvent(eventId + "Biome:" + (Level.Loaded?.LevelData?.Biome?.Identifier ?? "none"), roundDuration);
string eventId = "EndRound:" + (GameMode?.Preset?.Identifier ?? "none") + ":";
LogEndRoundStats(eventId);
if (GameMode is CampaignMode campaignMode)
{
GameAnalyticsManager.AddDesignEvent(eventId + "MoneyEarned", campaignMode.Money - prevMoney);
campaignMode.TotalPlayTime += roundDuration;
}
#if CLIENT
HintManager.OnRoundEnded();
#endif
missions.Clear();
}
finally
{
@@ -798,6 +815,82 @@ namespace Barotrauma
}
}
public void LogEndRoundStats(string eventId)
{
double roundDuration = Timing.TotalTime - RoundStartTime;
GameAnalyticsManager.AddDesignEvent(eventId + "Submarine:" + (Submarine.MainSub?.Info?.Name ?? "none"), roundDuration);
GameAnalyticsManager.AddDesignEvent(eventId + "GameMode:" + (GameMode?.Name ?? "none"), roundDuration);
GameAnalyticsManager.AddDesignEvent(eventId + "CrewSize:" + (CrewManager?.CharacterInfos?.Count() ?? 0), roundDuration);
foreach (Mission mission in missions)
{
GameAnalyticsManager.AddDesignEvent(eventId + "MissionType:" + (mission.Prefab.Type.ToString() ?? "none") + ":" + mission.Prefab.Identifier + ":" + (mission.Completed ? "Completed" : "Failed"), roundDuration);
}
if (Level.Loaded != null)
{
string levelId = Level.Loaded.Type == LevelData.LevelType.Outpost ?
Level.Loaded.StartOutpost?.Info?.OutpostGenerationParams?.Identifier :
Level.Loaded.GenerationParams?.Identifier;
GameAnalyticsManager.AddDesignEvent(eventId + "LevelType:" + (Level.Loaded?.Type.ToString() ?? "none" + ":" + (levelId ?? "null")), roundDuration);
GameAnalyticsManager.AddDesignEvent(eventId + "Biome:" + (Level.Loaded?.LevelData?.Biome?.Identifier ?? "none"), roundDuration);
}
if (Submarine.MainSub != null)
{
Dictionary<ItemPrefab, int> submarineInventory = new Dictionary<ItemPrefab, int>();
foreach (Item item in Item.ItemList)
{
var rootContainer = item.GetRootContainer() ?? item;
if (rootContainer.Submarine?.Info == null || rootContainer.Submarine.Info.Type != SubmarineType.Player) { continue; }
if (rootContainer.Submarine != Submarine.MainSub && !Submarine.MainSub.DockedTo.Contains(rootContainer.Submarine)) { continue; }
var holdable = item.GetComponent<Holdable>();
if (holdable == null || holdable.Attached) { continue; }
var wire = item.GetComponent<Wire>();
if (wire != null && wire.Connections.Any(c => c != null)) { continue; }
if (!submarineInventory.ContainsKey(item.Prefab))
{
submarineInventory.Add(item.Prefab, 0);
}
submarineInventory[item.Prefab]++;
}
foreach (var subItem in submarineInventory)
{
GameAnalyticsManager.AddDesignEvent(eventId + "SubmarineInventory:" + subItem.Key.Identifier, subItem.Value);
}
}
foreach (Character c in GetSessionCrewCharacters())
{
foreach (var itemSelectedDuration in c.ItemSelectedDurations)
{
string characterType = "Unknown";
if (c.IsBot)
{
characterType = "Bot";
}
else if (c.IsPlayer)
{
characterType = "Player";
}
GameAnalyticsManager.AddDesignEvent("TimeSpentOnDevices:" + (GameMode?.Preset?.Identifier ?? "none") + ":" + characterType + ":" + (c.Info?.Job?.Prefab.Identifier ?? "NoJob") + ":" + itemSelectedDuration.Key.Identifier, itemSelectedDuration.Value);
}
}
#if CLIENT
if (GameMode is TutorialMode tutorialMode)
{
GameAnalyticsManager.AddDesignEvent(eventId + tutorialMode.Tutorial.Identifier);
if (GameMain.IsFirstLaunch)
{
GameAnalyticsManager.AddDesignEvent("FirstLaunch:" + eventId + tutorialMode.Tutorial.Identifier);
}
}
GameAnalyticsManager.AddDesignEvent(eventId + "TimeSpentCleaning", TimeSpentCleaning);
GameAnalyticsManager.AddDesignEvent(eventId + "TimeSpentPainting", TimeSpentPainting);
TimeSpentCleaning = TimeSpentPainting = 0.0;
#endif
}
public void KillCharacter(Character character)
{
#if CLIENT
@@ -102,7 +102,7 @@ namespace Barotrauma
{
Identifier = value.Identifier;
Strength = (ushort)Math.Ceiling(value.Strength);
Price = (ushort)(Strength * value.Prefab.HealCostMultiplier);
Price = (ushort)(value.Prefab.BaseHealCost + Strength * value.Prefab.HealCostMultiplier);
}
}
@@ -276,9 +276,15 @@ namespace Barotrauma
PendingHeals.Add(crewMember);
}
public static bool IsHealable(Affliction affliction)
{
return affliction.Prefab.HealableInMedicalClinic && affliction.Strength > GetShowTreshold(affliction);
static float GetShowTreshold(Affliction affliction) => Math.Max(0, Math.Min(affliction.Prefab.ShowIconToOthersThreshold, affliction.Prefab.ShowInHealthScannerThreshold));
}
private NetAffliction[] GetAllAfflictions(CharacterHealth health)
{
IEnumerable<Affliction> rawAfflictions = health.GetAllAfflictions().Where(a => !a.Prefab.IsBuff && a.Strength > GetShowTreshold(a));
IEnumerable<Affliction> rawAfflictions = health.GetAllAfflictions().Where(a => IsHealable(a));
List<NetAffliction> afflictions = new List<NetAffliction>();
@@ -289,7 +295,7 @@ namespace Barotrauma
{
afflictions.Remove(foundAffliction);
foundAffliction.Strength += (ushort)affliction.Strength;
foundAffliction.Price += (ushort)GetAdjustedPrice((int)(affliction.Prefab.HealCostMultiplier * affliction.Strength));
foundAffliction.Price += (ushort)GetAdjustedPrice(GetHealPrice(affliction));
newAffliction = foundAffliction;
}
else
@@ -303,7 +309,7 @@ namespace Barotrauma
return afflictions.ToArray();
static float GetShowTreshold(Affliction affliction) => Math.Max(0, Math.Min(affliction.Prefab.ShowIconToOthersThreshold, affliction.Prefab.ShowInHealthScannerThreshold));
static int GetHealPrice(Affliction affliction) => (int)(affliction.Prefab.BaseHealCost + (affliction.Prefab.HealCostMultiplier * affliction.Strength));
}
public int GetTotalCost() => PendingHeals.SelectMany(h => h.Afflictions).Aggregate(0, (current, affliction) => current + affliction.Price);
@@ -225,6 +225,7 @@ namespace Barotrauma
}
Campaign.Money -= price;
GameAnalyticsManager.AddMoneySpentEvent(price, GameAnalyticsManager.MoneySink.SubmarineUpgrade, prefab.Identifier);
PurchasedUpgrade? upgrade = FindMatchingUpgrade(prefab, category);
@@ -323,6 +324,7 @@ namespace Barotrauma
}
Campaign.Money -= price;
GameAnalyticsManager.AddMoneySpentEvent(price, GameAnalyticsManager.MoneySink.SubmarineWeapon, itemToInstall.Identifier);
foreach (Item itemToSwap in linkedItems)
{
@@ -488,7 +488,11 @@ namespace Barotrauma
var sortedSelected = enabledRegularPackages
.OrderBy(p => -ContentPackage.RegularPackages.IndexOf(p))
.ToList();
if (previousEnabledRegularPackages.SequenceEqual(sortedSelected)) { return; }
if (previousEnabledRegularPackages.SequenceEqual(sortedSelected))
{
CheckModded();
return;
}
enabledRegularPackages.Clear(); enabledRegularPackages.AddRange(sortedSelected);
CharacterPrefab.Prefabs.SortAll();
@@ -508,6 +512,20 @@ namespace Barotrauma
{
RefreshContentPackageItems(AllEnabledPackages.SelectMany(p => p.Files));
}
CheckModded();
void CheckModded()
{
if (AllEnabledPackages.Any(p => p != GameMain.VanillaContent && p.HasMultiplayerIncompatibleContent))
{
GameAnalyticsManager.SetCustomDimension01(GameAnalyticsManager.CustomDimensions01.Modded);
}
else
{
GameAnalyticsManager.SetCustomDimension01(GameAnalyticsManager.CustomDimensions01.Vanilla);
}
}
}
public void EnableContentPackageItems(IEnumerable<ContentFile> unorderedFiles)
@@ -74,7 +74,6 @@ namespace Barotrauma.Items.Components
a.Identifier.Equals(Effect, StringComparison.OrdinalIgnoreCase) ||
a.AfflictionType.Equals(Effect, StringComparison.OrdinalIgnoreCase)).GetRandom();
}
Tainted = true;
}
[Serialize(3.0f, false)]
@@ -1,13 +1,13 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Extensions;
using Barotrauma.Networking;
using FarseerPhysics;
using FarseerPhysics.Dynamics;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Vector2 = Microsoft.Xna.Framework.Vector2;
using Vector4 = Microsoft.Xna.Framework.Vector4;
@@ -398,8 +398,6 @@ namespace Barotrauma.Items.Components
private int flowerVariants;
private int leafVariants;
private int[] flowerTiles;
private const int serverHealthUpdateDelay = 10;
private int serverHealthUpdateTimer;
public float Health
{
@@ -553,19 +551,21 @@ namespace Barotrauma.Items.Components
if (spawnProduct && ProducedItems.Any())
{
SpawnItem(ProducedItems.RandomElementByWeight(it => it.Probability), spawnPos);
SpawnItem(Item, ProducedItems.RandomElementByWeight(it => it.Probability), spawnPos);
return;
}
if (spawnSeed)
{
SpawnItem(ProducedSeed, spawnPos);
SpawnItem(Item, ProducedSeed, spawnPos);
}
static void SpawnItem(ProducedItem producedItem, Vector2 pos)
static void SpawnItem(Item thisItem, ProducedItem producedItem, Vector2 pos)
{
if (producedItem.Prefab == null) { return; }
GameAnalyticsManager.AddDesignEvent("MicroInteraction:" + (GameMain.GameSession?.GameMode?.Preset.Identifier ?? "null") + ":GardeningProduce:" + thisItem.prefab.Identifier + ":" + producedItem.Prefab.Identifier);
Entity.Spawner?.AddToSpawnQueue(producedItem.Prefab, pos, onSpawned: it =>
{
foreach (StatusEffect effect in producedItem.StatusEffects)
@@ -586,8 +586,13 @@ namespace Barotrauma.Items.Components
{
if (Decayed) { return true; }
if (0 >= Health)
if (Health <= 0)
{
if (!Decayed)
{
GameAnalyticsManager.AddDesignEvent("MicroInteraction:" + (GameMain.GameSession?.GameMode?.Preset.Identifier ?? "null") + ":GardeningDied:" + item.prefab.Identifier);
}
Decayed = true;
#if CLIENT
foreach (VineTile vine in Vines)
@@ -237,10 +237,13 @@ namespace Barotrauma.Items.Components
}
}
private bool loadedFromXml;
public override void Load(XElement componentElement, bool usePrefabValues, IdRemap idRemap)
{
base.Load(componentElement, usePrefabValues, idRemap);
loadedFromXml = true;
if (usePrefabValues)
{
//this needs to be loaded regardless
@@ -536,7 +539,16 @@ namespace Barotrauma.Items.Components
else
{
attachTargetCell = GetAttachTargetCell(150.0f);
if (attachTargetCell != null) { IsActive = true; }
if (attachTargetCell != null && attachTargetCell.IsDestructible)
{
attachTargetCell.OnDestroyed += () =>
{
if (attachTargetCell != null && attachTargetCell.CellType != Voronoi2.CellType.Solid)
{
Drop(dropConnectedWires: true, dropper: null);
}
};
}
}
}
@@ -562,7 +574,7 @@ namespace Barotrauma.Items.Components
public void DeattachFromWall()
{
if (!attachable) return;
if (!attachable) { return; }
Attached = false;
attachTargetCell = null;
@@ -733,15 +745,6 @@ namespace Barotrauma.Items.Components
public override void Update(float deltaTime, Camera cam)
{
if (attachTargetCell != null)
{
if (attachTargetCell.CellType != Voronoi2.CellType.Solid)
{
Drop(dropConnectedWires: true, dropper: null);
}
return;
}
if (item.body == null || !item.body.Enabled) { return; }
if (picker == null || !picker.HasEquippedItem(item))
{
@@ -838,15 +841,25 @@ namespace Barotrauma.Items.Components
public override void OnItemLoaded()
{
if (item.Submarine != null && item.Submarine.Loading) return;
if (item.Submarine != null && item.Submarine.Loading) { return; }
OnMapLoaded();
item.SetActiveSprite();
}
public override void OnMapLoaded()
{
if (!attachable) return;
if (!attachable) { return; }
//a mod has overridden the item, and the base item didn't have a Holdable component = a mod made the item movable/detachable
if (item.Prefab.IsOverride && !loadedFromXml)
{
if (attachedByDefault)
{
AttachToWall();
return;
}
}
if (Attached)
{
AttachToWall();
@@ -51,7 +51,11 @@ namespace Barotrauma.Items.Components
#else
if (deattachTimer >= DeattachDuration)
{
holdable.DeattachFromWall();
if (holdable.Attached)
{
GameAnalyticsManager.AddDesignEvent("ResourceCollected:" + (GameMain.GameSession?.GameMode?.Name ?? "none") + ":" + item.Prefab.Identifier);
holdable.DeattachFromWall();
}
trigger.Enabled = false;
}
#endif
@@ -855,6 +855,10 @@ namespace Barotrauma.Items.Components
{
currentTargets.Add(structure);
}
if (character != null)
{
currentTargets.Add(character);
}
effect.Apply(actionType, deltaTime, item, currentTargets);
}
else if (effect.HasTargetType(StatusEffect.TargetType.Character))
@@ -570,6 +570,7 @@ namespace Barotrauma.Items.Components
{
GUI.RemoveFromUpdateList(GuiFrame, true);
GuiFrame.RectTransform.Parent = null;
GuiFrame = null;
}
#endif
@@ -304,7 +304,12 @@ namespace Barotrauma.Items.Components
}
}
}
}
}
if (item.GetComponent<Planter>() != null)
{
GameAnalyticsManager.AddDesignEvent("MicroInteraction:" + (GameMain.GameSession?.GameMode?.Preset.Identifier ?? "null") + ":GardeningPlanted:" + containedItem.prefab.Identifier);
}
//no need to Update() if this item has no statuseffects and no physics body
IsActive = activeContainedItems.Count > 0 || Inventory.AllItems.Any(it => it.body != null);
@@ -370,29 +370,18 @@ namespace Barotrauma.Items.Components
public Item GetFocusTarget()
{
Item focusTarget = null;
for (int c = 0; c < 2; c++)
{
//try finding the item to focus on using trigger_out, and if that fails, using position_out
string connectionName = c == 0 ? "trigger_out" : "position_out";
string signal = c == 0 ? "0" : MathHelper.ToDegrees(targetRotation).ToString("G", CultureInfo.InvariantCulture);
if (!item.SendSignal(new Signal(signal, sender: user), connectionName) || focusTarget != null)
{
continue;
}
item.SendSignal(new Signal(MathHelper.ToDegrees(targetRotation).ToString("G", CultureInfo.InvariantCulture), sender: user), "position_out");
for (int i = item.LastSentSignalRecipients.Count - 1; i >= 0; i--)
for (int i = item.LastSentSignalRecipients.Count - 1; i >= 0; i--)
{
if (item.LastSentSignalRecipients[i].Item.Condition <= 0.0f || item.LastSentSignalRecipients[i].IsPower) { continue; }
if (item.LastSentSignalRecipients[i].Item.Prefab.FocusOnSelected)
{
if (item.LastSentSignalRecipients[i].Item.Condition <= 0.0f || item.LastSentSignalRecipients[i].IsPower) { continue; }
if (item.LastSentSignalRecipients[i].Item.Prefab.FocusOnSelected)
{
focusTarget = item.LastSentSignalRecipients[i].Item;
break;
}
return item.LastSentSignalRecipients[i].Item;
}
}
return focusTarget;
return null;
}
public override bool Pick(Character picker)
@@ -300,6 +300,8 @@ namespace Barotrauma.Items.Components
}
}
GameAnalyticsManager.AddDesignEvent("ItemDeconstructed:" + (GameMain.GameSession?.GameMode?.Name ?? "none") + ":" + targetItem.prefab.Identifier);
if (targetItem.AllowDeconstruct && allowRemove)
{
//drop all items that are inside the deconstructed item
@@ -397,6 +397,7 @@ namespace Barotrauma.Items.Components
for (int i = 0; i < (int)fabricationitemAmount.Value; i++)
{
float outCondition = fabricatedItem.OutCondition;
GameAnalyticsManager.AddDesignEvent("ItemFabricated:" + (GameMain.GameSession?.GameMode?.Name ?? "none") + ":" + fabricatedItem.TargetItem.Identifier);
if (i < amountFittingContainer)
{
Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, outputContainer.Inventory, fabricatedItem.TargetItem.Health * outCondition, quality,
@@ -314,26 +314,6 @@ namespace Barotrauma.Items.Components
return TextManager.GetWithVariable("roomname.subdiroclock", "[dir]", clockDir.ToString());
}
private Vector2 GetTransducerPos()
{
if (!UseTransducers || connectedTransducers.Count == 0)
{
//use the position of the sub if the item is static (no body) and inside a sub
return item.Submarine != null && item.body == null ? item.Submarine.WorldPosition : item.WorldPosition;
}
Vector2 transducerPosSum = Vector2.Zero;
foreach (ConnectedTransducer transducer in connectedTransducers)
{
if (transducer.Transducer.Item.Submarine != null && CenterOnTransducers)
{
return transducer.Transducer.Item.Submarine.WorldPosition;
}
transducerPosSum += transducer.Transducer.Item.WorldPosition;
}
return transducerPosSum / connectedTransducers.Count;
}
public override void ReceiveSignal(Signal signal, Connection connection)
{
base.ReceiveSignal(signal, connection);
@@ -113,6 +113,9 @@ namespace Barotrauma.Items.Components
[Serialize(false, true, description: "If true, the recharge speed (and power consumption) of the device goes up exponentially as the recharge rate is increased.")]
public bool ExponentialRechargeSpeed { get; set; }
[Editable(minValue: 0.0f, maxValue: 10.0f, decimals: 2), Serialize(0.5f, true)]
public float RechargeAdjustSpeed { get; set; }
private float efficiency;
[Editable(minValue: 0.0f, maxValue: 1.0f, decimals: 2), Serialize(0.95f, true, description: "The amount of power you can get out of a item relative to the amount of power that's put into it.")]
public float Efficiency
@@ -199,7 +202,14 @@ namespace Barotrauma.Items.Components
{
targetRechargeSpeed *= missingCharge;
}
currPowerConsumption = MathHelper.Lerp(currPowerConsumption, targetRechargeSpeed, 0.05f);
if (currPowerConsumption < targetRechargeSpeed)
{
currPowerConsumption = Math.Min(currPowerConsumption + deltaTime * maxRechargeSpeed * RechargeAdjustSpeed, targetRechargeSpeed);
}
else
{
currPowerConsumption = Math.Max(currPowerConsumption - deltaTime * maxRechargeSpeed * RechargeAdjustSpeed, targetRechargeSpeed);
}
Charge += currPowerConsumption * Math.Min(Voltage, 1.0f) / 3600.0f * efficiency;
}
@@ -244,7 +244,6 @@ namespace Barotrauma.Items.Components
return picker != null;
}
private static readonly HashSet<Connection> tempConnected = new HashSet<Connection>();
protected void RefreshConnections()
{
var connections = item.Connections;
@@ -260,41 +259,51 @@ namespace Barotrauma.Items.Components
}
//find all connections that are connected to this one (directly or via another PowerTransfer)
tempConnected.Clear();
HashSet<Connection> tempConnected;
if (!connectedRecipients.ContainsKey(c))
{
tempConnected = new HashSet<Connection>();
connectedRecipients.Add(c, tempConnected);
}
else
{
tempConnected = connectedRecipients[c];
tempConnected.Clear();
//mark all previous recipients as dirty
foreach (Connection recipient in tempConnected)
{
var pt = recipient.Item.GetComponent<PowerTransfer>();
if (pt != null) { pt.connectionDirty[recipient] = true; }
}
}
tempConnected.Add(c);
if (item.Condition > 0.0f)
{
if (!connectedRecipients.ContainsKey(c))
{
connectedRecipients.Add(c, tempConnected);
}
else
{
//mark all previous recipients as dirty
foreach (Connection recipient in connectedRecipients[c])
{
var pt = recipient.Item.GetComponent<PowerTransfer>();
if (pt != null) pt.connectionDirty[recipient] = true;
}
}
tempConnected.Add(c);
GetConnected(c, tempConnected);
}
connectedRecipients[c] = tempConnected;
//go through all the PowerTransfers that we're connected to and set their connections to match the ones we just calculated
//(no need to go through the recursive GetConnected method again)
foreach (Connection recipient in tempConnected)
{
if (recipient == c) { continue; }
var recipientPowerTransfer = recipient.Item.GetComponent<PowerTransfer>();
if (recipientPowerTransfer == null) { continue; }
if (!connectedRecipients.ContainsKey(recipient))
//go through all the PowerTransfers that we're connected to and set their connections to match the ones we just calculated
//(no need to go through the recursive GetConnected method again)
foreach (Connection recipient in tempConnected)
{
connectedRecipients.Add(recipient, tempConnected);
if (recipient == c) { continue; }
var recipientPowerTransfer = recipient.Item.GetComponent<PowerTransfer>();
if (recipientPowerTransfer == null) { continue; }
if (!recipientPowerTransfer.connectedRecipients.ContainsKey(recipient))
{
recipientPowerTransfer.connectedRecipients.Add(recipient, new HashSet<Connection>());
}
else
{
recipientPowerTransfer.connectedRecipients[recipient].Clear();
}
foreach (var connection in tempConnected)
{
recipientPowerTransfer.connectedRecipients[recipient].Add(connection);
}
recipientPowerTransfer.connectionDirty[recipient] = false;
}
recipientPowerTransfer.connectionDirty[recipient] = false;
}
connectionDirty[c] = false;
}
}
@@ -543,6 +543,7 @@ namespace Barotrauma.Items.Components
{
if (fixture.Body.UserData is VoronoiCell) { return -1; }
if (fixture.Body.UserData is Entity entity && entity.Submarine != submarine) { return -1; }
if (fixture.Body.UserData is Limb limb && limb.character?.Submarine != submarine) { return -1; }
}
//ignore level cells if the item and the point of impact are inside a sub
@@ -87,7 +87,11 @@ namespace Barotrauma.Items.Components
}
string signalOut = sendOutput ? output : falseOutput;
if (string.IsNullOrEmpty(signalOut)) { return; }
if (string.IsNullOrEmpty(signalOut))
{
IsActive = false;
return;
}
item.SendSignal(new Signal(signalOut, sender: signalSender[0] ?? signalSender[1]), "signal_out");
}
@@ -100,11 +104,13 @@ namespace Barotrauma.Items.Components
if (signal.value == "0") { return; }
timeSinceReceived[0] = 0.0f;
signalSender[0] = signal.sender;
IsActive = true;
break;
case "signal_in2":
if (signal.value == "0") { return; }
timeSinceReceived[1] = 0.0f;
signalSender[1] = signal.sender;
IsActive = true;
break;
case "set_output":
output = signal.value;
@@ -23,7 +23,6 @@ namespace Barotrauma.Items.Components
public ButtonTerminal(Item item, XElement element) : base(item, element)
{
IsActive = true;
RequiredSignalCount = element.GetChildElements("TerminalButton").Count(c => c.GetAttribute("style") != null);
if (RequiredSignalCount < 1)
{
@@ -88,13 +87,13 @@ namespace Barotrauma.Items.Components
}
}
var containers = item.GetComponents<ItemContainer>().ToList();
if (containers.Count != 1)
var containers = item.GetComponents<ItemContainer>();
if (containers.Count() != 1)
{
DebugConsole.ThrowError($"Error in item \"{item.Name}\": the ButtonTerminal component requires exactly one ItemContainer component!");
return;
}
Container = containers[0];
Container = containers.FirstOrDefault();
OnItemLoadedProjSpecific();
}
@@ -66,11 +66,16 @@ namespace Barotrauma.Items.Components
public override void Update(float deltaTime, Camera cam)
{
if (signalQueue.Count == 0)
{
IsActive = false;
return;
}
foreach (var val in signalQueue)
{
val.SendTimer -= 1;
}
while (signalQueue.Count > 0 && signalQueue.Peek().SendTimer <= 0)
{
var signalOut = signalQueue.Peek();
@@ -114,6 +119,7 @@ namespace Barotrauma.Items.Components
SendDuration = 1
};
signalQueue.Enqueue(prevQueuedSignal);
IsActive = true;
break;
case "set_delay":
if (float.TryParse(signal.value, NumberStyles.Any, CultureInfo.InvariantCulture, out float newDelay))
@@ -26,6 +26,8 @@ namespace Barotrauma.Items.Components
public PhysicsBody ParentBody;
private bool isOn;
private Turret turret;
[Serialize(100.0f, true, description: "The range of the emitted light. Higher values are more performance-intensive.", alwaysUseInstanceValues: true),
@@ -85,12 +87,13 @@ namespace Barotrauma.Items.Components
[Editable, Serialize(false, true, description: "Is the light currently on.", alwaysUseInstanceValues: true)]
public bool IsOn
{
get { return IsActive; }
get { return isOn; }
set
{
if (IsActive == value) { return; }
if (isOn == value && IsActive == value) { return; }
IsActive = value;
IsActive = isOn = value;
SetLightSourceState(value, value ? lightBrightness : 0.0f);
OnStateChanged();
}
}
@@ -200,9 +203,8 @@ namespace Barotrauma.Items.Components
set
{
if (base.IsActive == value) { return; }
base.IsActive = value;
SetLightSourceState(value, value ? lightBrightness : 0.0f);
base.IsActive = isOn = value;
SetLightSourceState(value, value ? lightBrightness : 0.0f);
}
}
@@ -237,6 +239,23 @@ namespace Barotrauma.Items.Components
turret = item.GetComponent<Turret>();
}
public override void OnMapLoaded()
{
if (item.body == null && powerConsumption <= 0.0f && Parent == null && turret == null &&
(statusEffectLists == null || !statusEffectLists.ContainsKey(ActionType.OnActive)) &&
(IsActiveConditionals == null || IsActiveConditionals.Count == 0))
{
lightBrightness = 1.0f;
SetLightSourceState(true, lightBrightness);
SetLightSourceTransformProjSpecific();
base.IsActive = false;
isOn = true;
#if CLIENT
Light.ParentSub = item.Submarine;
#endif
}
}
public override void Update(float deltaTime, Camera cam)
{
if (item.AiTarget != null)
@@ -20,7 +20,11 @@ namespace Barotrauma.Items.Components
}
string signalOut = sendOutput ? output : falseOutput;
if (string.IsNullOrEmpty(signalOut)) { return; }
if (string.IsNullOrEmpty(signalOut))
{
IsActive = false;
return;
}
item.SendSignal(new Signal(signalOut, sender: signalSender[0] ?? signalSender[1]), "signal_out");
}
@@ -8,12 +8,15 @@ using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
partial class WifiComponent : ItemComponent
partial class WifiComponent : ItemComponent, IServerSerializable
{
private static readonly List<WifiComponent> list = new List<WifiComponent>();
const int ChannelMemorySize = 10;
private const int MinChannel = 0;
private const int MaxChannel = 10000;
private float range;
private int channel;
@@ -49,7 +52,7 @@ namespace Barotrauma.Items.Components
get { return channel; }
set
{
channel = MathHelper.Clamp(value, 0, 10000);
channel = MathHelper.Clamp(value, MinChannel, MaxChannel);
}
}
@@ -295,7 +298,14 @@ namespace Barotrauma.Items.Components
case "set_channel":
if (int.TryParse(signal.value, out int newChannel))
{
int prevChannel = Channel;
Channel = newChannel;
if (prevChannel != Channel)
{
#if SERVER
item.CreateServerEvent(this);
#endif
}
}
break;
case "set_range":
@@ -20,7 +20,11 @@ namespace Barotrauma.Items.Components
}
string signalOut = sendOutput == 1 ? output : falseOutput;
if (string.IsNullOrEmpty(signalOut)) { return; }
if (string.IsNullOrEmpty(signalOut))
{
IsActive = false;
return;
}
item.SendSignal(new Signal(signalOut, sender: signalSender[0] ?? signalSender[1]), "signal_out");
}
@@ -1442,6 +1442,11 @@ namespace Barotrauma.Items.Components
crosshairPointerSprite?.Remove(); crosshairPointerSprite = null;
moveSoundChannel?.Dispose(); moveSoundChannel = null;
WeaponIndicatorSprite?.Remove(); WeaponIndicatorSprite = null;
if (powerIndicator != null)
{
powerIndicator.RectTransform.Parent = null;
powerIndicator = null;
}
#endif
}
@@ -479,7 +479,17 @@ namespace Barotrauma
{
if (i < 0 || i >= slots.Length)
{
string errorMsg = $"Inventory.TryPutItem failed: index was out of range (item: {(item?.Name ?? "null")}, inventory: {(Owner?.ToString() ?? "null")}).\n" + Environment.StackTrace.CleanupStackTrace();
string thisItemStr = item?.prefab.Identifier ?? "null";
string ownerStr = "null";
if (Owner is Item ownerItem)
{
ownerStr = ownerItem.prefab.Identifier;
}
else if (Owner is Character ownerCharacter)
{
ownerStr = ownerCharacter.SpeciesName;
}
string errorMsg = $"Inventory.TryPutItem failed: index was out of range (item: {thisItemStr}, inventory: {ownerStr}).";
GameAnalyticsManager.AddErrorEventOnce("Inventory.TryPutItem:IndexOutOfRange", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
#if DEBUG
DebugConsole.ThrowError(errorMsg);
@@ -1741,7 +1741,7 @@ namespace Barotrauma
}
else
{
if (updateableComponents.Count == 0 && aiTarget == null && !hasStatusEffectsOfType[(int)ActionType.Always] && (body == null || !body.Enabled))
if (updateableComponents.Count == 0 && !hasStatusEffectsOfType[(int)ActionType.Always] && (body == null || !body.Enabled))
{
#if CLIENT
positionBuffer.Clear();
@@ -2088,19 +2088,18 @@ namespace Barotrauma
return controller != null;
}
public bool SendSignal(string signal, string connectionName)
public void SendSignal(string signal, string connectionName)
{
return SendSignal(new Signal(signal), connectionName);
SendSignal(new Signal(signal), connectionName);
}
public bool SendSignal(Signal signal, string connectionName)
public void SendSignal(Signal signal, string connectionName)
{
if (connections == null) { return false; }
if (!connections.TryGetValue(connectionName, out Connection connection)) { return false; }
if (connections == null) { return; }
if (!connections.TryGetValue(connectionName, out Connection connection)) { return; }
signal.source ??= this;
SendSignal(signal, connection);
return true;
}
private readonly HashSet<(Signal Signal, Connection Connection)> delayedSignals = new HashSet<(Signal Signal, Connection Connection)>();
@@ -3248,6 +3247,9 @@ namespace Barotrauma
foreach (ItemComponent ic in components)
{
ic.Remove();
#if CLIENT
ic.GuiFrame = null;
#endif
}
ItemList.Remove(this);
@@ -18,8 +18,8 @@ namespace Barotrauma.MapCreatures.Behavior
public readonly BallastFloraBehavior? ParentBallastFlora;
public int ID = -1;
public ushort ClaimedItem;
public bool HasClaimedItem;
public Item ClaimedItem;
public int ClaimedItemId = -1;
public float MaxHealth = 100f;
public float Health = 100f;
@@ -271,10 +271,29 @@ namespace Barotrauma.MapCreatures.Behavior
{
ClaimTarget(item, Branches.FirstOrDefault(b => b.ID == branchid), true);
}
else
{
string errorMsg = $"Error in BallastFloraBehavior.OnMapLoaded: could not find the item claimed by the ballast flora.";
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("BallastFloraBehavior.OnMapLoaded:ClaimedItemNotFound", GameAnalyticsManager.ErrorSeverity.Warning, errorMsg);
}
}
foreach (BallastFloraBranch branch in Branches)
{
if (branch.ClaimedItemId > -1)
{
if (Entity.FindEntityByID((ushort)branch.ClaimedItemId) is Item item)
{
branch.ClaimedItem = item;
}
else
{
string errorMsg = $"Error in BallastFloraBehavior.OnMapLoaded: could not find the item claimed by a branch.";
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("BallastFloraBehavior.OnMapLoaded:BranchClaimedItemNotFound", GameAnalyticsManager.ErrorSeverity.Warning, errorMsg);
}
}
UpdateConnections(branch);
CreateBody(branch);
}
@@ -335,9 +354,9 @@ namespace Barotrauma.MapCreatures.Behavior
new XAttribute("sides", (int)branch.Sides),
new XAttribute("blockedsides", (int)branch.BlockedSides));
if (branch.HasClaimedItem)
if (branch.ClaimedItem != null)
{
be.Add(new XAttribute("claimed", (int)branch.ClaimedItem));
be.Add(new XAttribute("claimed", (int)(branch.ClaimedItem?.ID ?? -1)));
}
saveElement.Add(be);
@@ -345,6 +364,13 @@ namespace Barotrauma.MapCreatures.Behavior
foreach (Item target in ClaimedTargets)
{
if (target.Infector == null)
{
string errorMsg = $"Error in BallastFloraBehavior.Save: claimed target \"{target.Prefab.Identifier}\" had no infector set.";
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("BallastFloraBehavior.Save:InfectorNull", GameAnalyticsManager.ErrorSeverity.Warning, errorMsg);
continue;
}
XElement te = new XElement("ClaimedTarget", new XAttribute("id", target.ID), new XAttribute("branchId", target.Infector.ID));
saveElement.Add(te);
}
@@ -352,7 +378,7 @@ namespace Barotrauma.MapCreatures.Behavior
element.Add(saveElement);
}
public void LoadSave(XElement element)
public void LoadSave(XElement element, IdRemap idRemap)
{
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
Offset = element.GetAttributeVector2("offset", Vector2.Zero);
@@ -361,21 +387,20 @@ namespace Barotrauma.MapCreatures.Behavior
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "branch":
LoadBranch(subElement);
LoadBranch(subElement, idRemap);
break;
case "claimedtarget":
int id = subElement.GetAttributeInt("id", -1);
int branchId = subElement.GetAttributeInt("branchId", -1);
if (id > 0)
{
tempClaimedTargets.Add(Tuple.Create((UInt16)id, branchId));
tempClaimedTargets.Add(Tuple.Create(idRemap.GetOffsetId(id), branchId));
}
break;
}
}
void LoadBranch(XElement branchElement)
void LoadBranch(XElement branchElement, IdRemap idRemap)
{
Vector2 pos = branchElement.GetAttributeVector2("pos", Vector2.Zero);
bool isRoot = branchElement.GetAttributeBool("isroot", false);
@@ -400,8 +425,7 @@ namespace Barotrauma.MapCreatures.Behavior
if (claimedId > -1)
{
newBranch.HasClaimedItem = true;
newBranch.ClaimedItem = (ushort) claimedId;
newBranch.ClaimedItemId = idRemap.GetOffsetId((ushort)claimedId);
}
Branches.Add(newBranch);
@@ -767,8 +791,7 @@ namespace Barotrauma.MapCreatures.Behavior
if (branch != null)
{
branch.ClaimedItem = target.ID;
branch.HasClaimedItem = true;
branch.ClaimedItem = target;
}
#if SERVER
@@ -977,7 +1000,7 @@ namespace Barotrauma.MapCreatures.Behavior
if (isClient) { return; }
if (branch.HasClaimedItem)
if (branch.ClaimedItem != null)
{
RemoveClaim(branch.ClaimedItem);
}
@@ -995,41 +1018,34 @@ namespace Barotrauma.MapCreatures.Behavior
#endif
}
public void RemoveClaim(ushort id)
public void RemoveClaim(Item item)
{
ClaimedTargets.ForEachMod(item =>
if (!IgnoredTargets.ContainsKey(item))
{
if (item.ID == id)
IgnoredTargets.Add(item, 10);
}
ClaimedTargets.Remove(item);
item.Infector = null;
ClaimedJunctionBoxes.ForEachMod(jb =>
{
if (jb.Item == item)
{
if (!IgnoredTargets.ContainsKey(item))
{
IgnoredTargets.Add(item, 10);
}
ClaimedTargets.Remove(item);
item.Infector = null;
ClaimedJunctionBoxes.ForEachMod(jb =>
{
if (jb.Item == item)
{
ClaimedJunctionBoxes.Remove(jb);
}
});
ClaimedBatteries.ForEachMod(bat =>
{
if (bat.Item == item)
{
ClaimedBatteries.Remove(bat);
}
});
#if SERVER
SendNetworkMessage(this, NetworkHeader.Infect, item.ID, false);
#endif
ClaimedJunctionBoxes.Remove(jb);
}
});
ClaimedBatteries.ForEachMod(bat =>
{
if (bat.Item == item)
{
ClaimedBatteries.Remove(bat);
}
});
#if SERVER
SendNetworkMessage(this, NetworkHeader.Infect, item.ID, false);
#endif
}
public void Kill()
@@ -1540,7 +1540,7 @@ namespace Barotrauma
if (prefab != null)
{
hull.BallastFlora = new BallastFloraBehavior(hull, prefab, Vector2.Zero);
hull.BallastFlora.LoadSave(subElement);
hull.BallastFlora.LoadSave(subElement, idRemap);
}
break;
}
@@ -205,6 +205,8 @@ namespace Barotrauma
foreach (var cell in Cells)
{
cell.CellType = CellType.Removed;
cell.OnDestroyed?.Invoke();
cell.OnDestroyed = null;
}
GameMain.World.Remove(Body);
Dispose();
@@ -25,7 +25,17 @@ namespace Barotrauma
/// </summary>
public const int MaxSubmarineWidth = 16000;
public static Level Loaded { get; private set; }
private static Level loaded;
public static Level Loaded
{
get { return loaded; }
private set
{
if (loaded == value) { return; }
loaded = value;
GameAnalyticsManager.SetCurrentLevel(loaded?.LevelData);
}
}
[Flags]
public enum PositionType
@@ -578,8 +588,8 @@ namespace Barotrauma
{
for (int y = siteInterval.Y / 2; y < borders.Height - siteInterval.Y / 2; y += siteInterval.Y)
{
int siteX = x + Rand.Range(-siteVariance.X, siteVariance.X, Rand.RandSync.Server);
int siteY = y + Rand.Range(-siteVariance.Y, siteVariance.Y, Rand.RandSync.Server);
int siteX = x + Rand.Range(-siteVariance.X, siteVariance.X + 1, Rand.RandSync.Server);
int siteY = y + Rand.Range(-siteVariance.Y, siteVariance.Y + 1, Rand.RandSync.Server);
bool closeToTunnel = false;
bool closeToCave = false;
@@ -1776,12 +1786,12 @@ namespace Barotrauma
new Point(0, BottomPos)
};
int mountainCount = Rand.Range(GenerationParams.MountainCountMin, GenerationParams.MountainCountMax, Rand.RandSync.Server);
int mountainCount = Rand.Range(GenerationParams.MountainCountMin, GenerationParams.MountainCountMax + 1, Rand.RandSync.Server);
for (int i = 0; i < mountainCount; i++)
{
bottomPositions.Add(
new Point(Size.X / (mountainCount + 1) * (i + 1),
BottomPos + Rand.Range(GenerationParams.MountainHeightMin, GenerationParams.MountainHeightMax, Rand.RandSync.Server)));
BottomPos + Rand.Range(GenerationParams.MountainHeightMin, GenerationParams.MountainHeightMax + 1, Rand.RandSync.Server)));
}
bottomPositions.Add(new Point(Size.X, BottomPos));
@@ -1794,7 +1804,7 @@ namespace Barotrauma
bottomPositions.Insert(i + 1,
new Point(
(bottomPositions[i].X + bottomPositions[i + 1].X) / 2,
(bottomPositions[i].Y + bottomPositions[i + 1].Y) / 2 + Rand.Range(0, GenerationParams.SeaFloorVariance, Rand.RandSync.Server)));
(bottomPositions[i].Y + bottomPositions[i + 1].Y) / 2 + Rand.Range(0, GenerationParams.SeaFloorVariance + 1, Rand.RandSync.Server)));
i++;
}
@@ -1881,7 +1891,7 @@ namespace Barotrauma
Tunnels.Add(tunnel);
caveBranches.Add(tunnel);
int branches = Rand.Range(caveParams.MinBranchCount, caveParams.MaxBranchCount, Rand.RandSync.Server);
int branches = Rand.Range(caveParams.MinBranchCount, caveParams.MaxBranchCount + 1, Rand.RandSync.Server);
for (int j = 0; j < branches; j++)
{
Tunnel parentBranch = caveBranches.GetRandom(Rand.RandSync.Server);
@@ -2441,7 +2451,7 @@ namespace Barotrauma
}, randSync: Rand.RandSync.Server);
if (location.Cell == null || location.Edge == null) { break; }
int clusterSize = Rand.Range(GenerationParams.ResourceClusterSizeRange.X, GenerationParams.ResourceClusterSizeRange.Y, Rand.RandSync.Server);
int clusterSize = Rand.Range(GenerationParams.ResourceClusterSizeRange.X, GenerationParams.ResourceClusterSizeRange.Y + 1, Rand.RandSync.Server);
PlaceResources(itemPrefab, clusterSize, location, out var abyssResources);
var abyssClusterLocation = new ClusterLocation(location.Cell, location.Edge, initializeResourceList: true);
abyssClusterLocation.Resources.AddRange(abyssResources);
@@ -3310,13 +3320,6 @@ namespace Barotrauma
return pathCells;
}
public string GetWreckIDTag(string originalTag, Submarine wreck)
{
string shortSeed = ToolBox.StringToInt(LevelData.Seed + wreck?.Info.Name).ToString();
if (shortSeed.Length > 6) { shortSeed = shortSeed.Substring(0, 6); }
return originalTag + "_" + shortSeed;
}
public bool IsCloseToStart(Vector2 position, float minDist) => IsCloseToStart(position.ToPoint(), minDist);
public bool IsCloseToEnd(Vector2 position, float minDist) => IsCloseToEnd(position.ToPoint(), minDist);
@@ -4058,7 +4061,7 @@ namespace Barotrauma
foreach (Submarine wreck in Wrecks)
{
int corpseCount = Rand.Range(Loaded.GenerationParams.MinCorpseCount, Loaded.GenerationParams.MaxCorpseCount);
int corpseCount = Rand.Range(Loaded.GenerationParams.MinCorpseCount, Loaded.GenerationParams.MaxCorpseCount + 1);
var allSpawnPoints = WayPoint.WayPointList.FindAll(wp => wp.Submarine == wreck && wp.CurrentHull != null);
var pathPoints = allSpawnPoints.FindAll(wp => wp.SpawnType == SpawnType.Path);
pathPoints.Shuffle(Rand.RandSync.Unsynced);
@@ -4115,6 +4118,7 @@ namespace Barotrauma
corpse.EnableDespawn = false;
selectedPrefab.GiveItems(corpse, wreck);
corpse.Kill(CauseOfDeathType.Unknown, causeOfDeathAffliction: null, log: false);
corpse.GiveIdCardTags(sp);
spawnCounter++;
static CorpsePrefab GetCorpsePrefab(Func<CorpsePrefab, bool> predicate)
@@ -304,7 +304,7 @@ namespace Barotrauma
foreach (LevelObjectPrefab.ChildObject child in prefab.ChildObjects)
{
int childCount = Rand.Range(child.MinCount, child.MaxCount, Rand.RandSync.Server);
int childCount = Rand.Range(child.MinCount, child.MaxCount + 1, Rand.RandSync.Server);
for (int j = 0; j < childCount; j++)
{
var matchingPrefabs = LevelObjectPrefab.List.Where(p => child.AllowedNames.Contains(p.Name));
@@ -436,7 +436,7 @@ namespace Barotrauma
/// <summary>
/// Are there any active contacts between the physics body and the target entity
/// </summary>
public static bool CheckContactsForEntity(PhysicsBody triggerBody, Entity separatingEntity)
public static bool CheckContactsForEntity(PhysicsBody triggerBody, Entity targetEntity)
{
foreach (Fixture fixture in triggerBody.FarseerBody.FixtureList)
{
@@ -447,10 +447,11 @@ namespace Barotrauma
contactEdge.Contact.Enabled &&
contactEdge.Contact.IsTouching)
{
if (contactEdge.Contact.FixtureA != fixture && contactEdge.Contact.FixtureB != fixture)
{
if (GetEntity(contactEdge.Contact.FixtureB) == separatingEntity || GetEntity(contactEdge.Contact.FixtureA) == separatingEntity) { return true; }
}
if ((contactEdge.Contact.FixtureA.Body == triggerBody.FarseerBody && GetEntity(contactEdge.Contact.FixtureB) == targetEntity) ||
(contactEdge.Contact.FixtureB.Body == triggerBody.FarseerBody && GetEntity(contactEdge.Contact.FixtureA) == targetEntity))
{
return true;
}
}
contactEdge = contactEdge.Next;
}
@@ -560,6 +561,8 @@ namespace Barotrauma
foreach (Entity triggerer in triggerers)
{
if (triggerer.Removed) { continue; }
ApplyStatusEffects(statusEffects, worldPosition, triggerer, deltaTime, targets);
if (triggerer is IDamageable damageable)
@@ -691,6 +694,8 @@ namespace Barotrauma
private void ApplyForce(PhysicsBody body)
{
if (body == null) { return; }
float distFactor = 1.0f;
if (ForceFalloff)
{
@@ -352,6 +352,7 @@ namespace Barotrauma
if (hull.Submarine != sub) { continue; }
hull.WaterVolume = 0.0f;
hull.OxygenPercentage = 100.0f;
hull.BallastFlora?.Kill();
}
}
@@ -782,7 +782,7 @@ namespace Barotrauma
{
if (priceInfo.MaxAvailableAmount > priceInfo.MinAvailableAmount)
{
quantity = Rand.Range(priceInfo.MinAvailableAmount, priceInfo.MaxAvailableAmount);
quantity = Rand.Range(priceInfo.MinAvailableAmount, priceInfo.MaxAvailableAmount + 1);
}
else
{
@@ -1010,7 +1010,7 @@ namespace Barotrauma
private void GenerateRandomPriceModifier()
{
StorePriceModifier = Rand.Range(-StorePriceModifierRange, StorePriceModifierRange);
StorePriceModifier = Rand.Range(-StorePriceModifierRange, StorePriceModifierRange + 1);
}
private void CreateStoreSpecials()
@@ -112,7 +112,7 @@ namespace Barotrauma
System.Diagnostics.Debug.Assert(!Locations.Contains(null));
for (int i = 0; i < Locations.Count; i++)
{
Locations[i].Reputation ??= new Reputation(campaign.CampaignMetadata, $"location.{i}", -100, 100, Rand.Range(-10, 10, Rand.RandSync.Server));
Locations[i].Reputation ??= new Reputation(campaign.CampaignMetadata, Locations[i], $"location.{i}", -100, 100, Rand.Range(-10, 11, Rand.RandSync.Server));
}
List<XElement> connectionElements = new List<XElement>();
@@ -214,7 +214,7 @@ namespace Barotrauma
for (int i = 0; i < Locations.Count; i++)
{
Locations[i].Reputation ??= new Reputation(campaign.CampaignMetadata, $"location.{i}", -100, 100, Rand.Range(-10, 10, Rand.RandSync.Server));
Locations[i].Reputation ??= new Reputation(campaign.CampaignMetadata, Locations[i], $"location.{i}", -100, 100, Rand.Range(-10, 11, Rand.RandSync.Server));
}
foreach (Location location in Locations)
@@ -115,7 +115,7 @@ namespace Barotrauma
private float? maxHealth;
[Serialize(100.0f, true), Editable]
[Serialize(100.0f, true), Editable(MinValueFloat = 0)]
public float MaxHealth
{
get => maxHealth ?? Prefab.Health;
@@ -898,8 +898,8 @@ namespace Barotrauma
{
var worldRect = section.WorldRect;
Vector2 particlePos = new Vector2(
Rand.Range(worldRect.X, worldRect.Right),
Rand.Range(worldRect.Y - worldRect.Height, worldRect.Y));
Rand.Range(worldRect.X, worldRect.Right + 1),
Rand.Range(worldRect.Y - worldRect.Height, worldRect.Y + 1));
var particle = GameMain.ParticleManager.CreateParticle("shrapnel", particlePos, Rand.Vector(Rand.Range(1.0f, 50.0f)), collisionIgnoreTimer: 1f);
if (particle == null) break;
@@ -79,9 +79,19 @@ namespace Barotrauma.Networking
public readonly string SenderName;
private Color? customTextColor;
public Color Color
{
get { return MessageColor[(int)Type]; }
get
{
if (customTextColor != null) { return customTextColor.Value; }
return MessageColor[(int)Type];
}
set
{
customTextColor = value;
}
}
public static string GetTimeStamp()
@@ -105,7 +115,7 @@ namespace Barotrauma.Networking
set;
}
protected ChatMessage(string senderName, string text, ChatMessageType type, Character sender, Client client, PlayerConnectionChangeType changeType = PlayerConnectionChangeType.None)
protected ChatMessage(string senderName, string text, ChatMessageType type, Character sender, Client client, PlayerConnectionChangeType changeType = PlayerConnectionChangeType.None, Color? textColor = null)
{
Text = text;
Type = type;
@@ -115,11 +125,13 @@ namespace Barotrauma.Networking
SenderName = senderName;
ChangeType = changeType;
}
public static ChatMessage Create(string senderName, string text, ChatMessageType type, Character sender, Client client = null, PlayerConnectionChangeType changeType = PlayerConnectionChangeType.None)
customTextColor = textColor;
}
public static ChatMessage Create(string senderName, string text, ChatMessageType type, Character sender, Client client = null, PlayerConnectionChangeType changeType = PlayerConnectionChangeType.None, Color? textColor = null)
{
return new ChatMessage(senderName, text, type, sender, client ?? GameMain.NetworkMember?.ConnectedClients?.Find(c => c.Character != null && c.Character == sender), changeType);
return new ChatMessage(senderName, text, type, sender, client ?? GameMain.NetworkMember?.ConnectedClients?.Find(c => c.Character != null && c.Character == sender), changeType, textColor);
}
public static string GetChatMessageCommand(string message, out string messageWithoutCommand)
@@ -234,24 +234,24 @@ namespace Barotrauma.Networking
var radio = sender.Inventory.AllItems.FirstOrDefault(i => i.GetComponent<WifiComponent>() != null);
if (radio == null || !sender.HasEquippedItem(radio)) { return false; }
var radioComponent = radio.GetComponent<WifiComponent>();
if (radioComponent == null) { return false; }
return radioComponent.HasRequiredContainedItems(sender, addMessage: false);
}
public void AddChatMessage(string message, ChatMessageType type, string senderName = "", Client senderClient = null, Character senderCharacter = null, PlayerConnectionChangeType changeType = PlayerConnectionChangeType.None)
public void AddChatMessage(string message, ChatMessageType type, string senderName = "", Client senderClient = null, Character senderCharacter = null, PlayerConnectionChangeType changeType = PlayerConnectionChangeType.None, Color? textColor = null)
{
AddChatMessage(ChatMessage.Create(senderName, message, type, senderCharacter, senderClient, changeType: changeType));
AddChatMessage(ChatMessage.Create(senderName, message, type, senderCharacter, senderClient, changeType: changeType, textColor: textColor));
}
public virtual void AddChatMessage(ChatMessage message)
{
if (string.IsNullOrEmpty(message.Text)) { return; }
if (message.Sender != null && !message.Sender.IsDead)
{
message.Sender.ShowSpeechBubble(2.0f, ChatMessage.MessageColor[(int)message.Type]);
message.Sender.ShowSpeechBubble(2.0f, message.Color);
}
}
@@ -124,7 +124,6 @@ namespace Barotrauma.Steam
return unlocked;
}
public static bool IncrementStat(string statName, int increment)
{
if (!isInitialized || !Steamworks.SteamClient.IsValid) { return false; }
@@ -161,6 +160,12 @@ namespace Barotrauma.Steam
return success;
}
public static int GetStatInt(string statName)
{
if (!isInitialized || !Steamworks.SteamClient.IsValid) { return 0; }
return Steamworks.SteamUserStats.GetStatInt(statName);
}
public static bool StoreStats()
{
if (!isInitialized || !Steamworks.SteamClient.IsValid) { return false; }
@@ -175,6 +180,17 @@ namespace Barotrauma.Steam
return success;
}
public static bool TryGetUnlockedAchievements(out List<Steamworks.Data.Achievement> achievements)
{
if (!isInitialized || !Steamworks.SteamClient.IsValid)
{
achievements = null;
return false;
}
achievements = Steamworks.SteamUserStats.Achievements.Where(a => a.State).ToList();
return true;
}
public static void Update(float deltaTime)
{
if (!isInitialized) { return; }
@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
@@ -7,20 +6,18 @@ namespace Barotrauma
{
public class PerformanceCounter
{
public long TotalFrames { get; private set; }
public double TotalSeconds { get; private set; }
public double AverageFramesPerSecond { get; private set; }
public double CurrentFramesPerSecond { get; private set; }
public const int MaximumSamples = 10;
private Queue<double> sampleBuffer = new Queue<double>();
private readonly Queue<double> sampleBuffer = new Queue<double>();
private Dictionary<string, Queue<long>> elapsedTicks = new Dictionary<string, Queue<long>>();
private Dictionary<string, long> avgTicksPerFrame = new Dictionary<string, long>();
private readonly Dictionary<string, Queue<long>> elapsedTicks = new Dictionary<string, Queue<long>>();
private readonly Dictionary<string, long> avgTicksPerFrame = new Dictionary<string, long>();
#if CLIENT
internal Graph UpdateTimeGraph = new Graph(500), UpdateIterationsGraph = new Graph(500), DrawTimeGraph = new Graph(500);
internal Graph UpdateTimeGraph = new Graph(500), DrawTimeGraph = new Graph(500);
#endif
public IEnumerable<string> GetSavedIdentifiers
@@ -50,7 +47,7 @@ namespace Barotrauma
{
if (deltaTime == 0.0f) { return false; }
CurrentFramesPerSecond = (1.0 / deltaTime);
CurrentFramesPerSecond = 1.0 / deltaTime;
sampleBuffer.Enqueue(CurrentFramesPerSecond);
@@ -64,12 +61,7 @@ namespace Barotrauma
AverageFramesPerSecond = CurrentFramesPerSecond;
}
if (AverageFramesPerSecond < 0 || AverageFramesPerSecond > 500) { }
TotalFrames++;
TotalSeconds += deltaTime;
return true;
}
}
}
@@ -53,6 +53,7 @@
using Barotrauma;
using FarseerPhysics.Dynamics;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -157,6 +158,11 @@ namespace Voronoi2
public bool IsDestructible;
public bool DoesDamage;
/// <summary>
/// Executed when the cell is destroyed (only applies to destructible level walls)
/// </summary>
public Action OnDestroyed;
public Vector2 Center
{
get { return new Vector2((float)Site.Coord.X, (float)Site.Coord.Y) + Translation; }
@@ -556,7 +556,14 @@ namespace Barotrauma
switch (Name)
{
case nameof(Powered.Voltage):
if (parentObject is Powered powered) { value = powered.Voltage; return true; }
{
if (parentObject is Powered powered) { value = powered.Voltage; return true; }
}
break;
case nameof(Powered.CurrPowerConsumption):
{
if (parentObject is Powered powered) { value = powered.CurrPowerConsumption; return true; }
}
break;
case nameof(PowerContainer.Charge):
{
@@ -568,6 +575,11 @@ namespace Barotrauma
if (parentObject is PowerContainer powerContainer) { value = powerContainer.ChargePercentage; return true; }
}
break;
case nameof(PowerContainer.RechargeRatio):
{
if (parentObject is PowerContainer powerContainer) { value = powerContainer.RechargeRatio; return true; }
}
break;
case nameof(Reactor.AvailableFuel):
{ if (parentObject is Reactor reactor) { value = reactor.AvailableFuel; return true; } }
break;
@@ -262,6 +262,28 @@ namespace Barotrauma
return val;
}
public static double GetAttributeDouble(this XElement element, string name, double defaultValue)
{
if (element?.Attribute(name) == null) { return defaultValue; }
double val = defaultValue;
try
{
string strVal = element.Attribute(name).Value;
if (strVal.LastOrDefault() == 'f')
{
strVal = strVal.Substring(0, strVal.Length - 1);
}
val = double.Parse(strVal, CultureInfo.InvariantCulture);
}
catch (Exception e)
{
DebugConsole.ThrowError("Error in " + element + "!", e);
}
return val;
}
public static float[] GetAttributeFloatArray(this XElement element, string name, float[] defaultValue)
{
if (element?.Attribute(name) == null) { return defaultValue; }
@@ -34,7 +34,7 @@ namespace Barotrauma
}
}
private static List<WeakReference<Sprite>> list = new List<WeakReference<Sprite>>();
private readonly static List<WeakReference<Sprite>> list = new List<WeakReference<Sprite>>();
/// <summary>
/// Reference to the xml element from where the sprite was created. Can be null if the sprite was not defined in xml!
@@ -329,7 +329,7 @@ namespace Barotrauma
private bool MatchesTagCondition(ISerializableEntity target)
{
if (!(target is Item item)) { return false; }
if (!(target is Item item)) { return Operator == OperatorType.NotEquals; }
int matches = 0;
foreach (string tag in SplitAttributeValue)
@@ -12,6 +12,7 @@ namespace Barotrauma
public static double Accumulator;
public const int FixedUpdateRate = 60;
public const double Step = 1.0 / FixedUpdateRate;
public const double AccumulatorMax = 0.25f;
private static int frameLimit;
/// <summary>
@@ -62,6 +62,9 @@ namespace Barotrauma
return (sync == RandSync.Unsynced ? localRandom : (syncedRandom[(int)sync])).NextDouble() * (maximum - minimum) + minimum;
}
/// <summary>
/// Min inclusive, Max exclusive!
/// </summary>
public static int Range(int minimum, int maximum, RandSync sync = RandSync.Unsynced)
{
CheckRandThreadSafety(sync);