Unstable 0.16.1.0
This commit is contained in:
@@ -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()
|
||||
|
||||
+3
-3
@@ -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)
|
||||
{
|
||||
|
||||
+7
-16
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+7
-1
@@ -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;
|
||||
|
||||
+11
-11
@@ -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:
|
||||
|
||||
+1
-1
@@ -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();
|
||||
|
||||
+8
@@ -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);
|
||||
}
|
||||
|
||||
+9
@@ -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)
|
||||
{
|
||||
|
||||
+3
-1
@@ -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)
|
||||
|
||||
+3
-1
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -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);
|
||||
|
||||
+3
-3
@@ -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)
|
||||
|
||||
+1
-1
@@ -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; }
|
||||
|
||||
+6
-14
@@ -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);
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,3 +1,71 @@
|
||||
---------------------------------------------------------------------------------------------------------
|
||||
v0.16.1.0
|
||||
---------------------------------------------------------------------------------------------------------
|
||||
|
||||
Changes and additions:
|
||||
- Added 2 new subs: Herja and Winterhalter.
|
||||
- Improvements and fixes to Barsuk or Orca 2.
|
||||
- Improvements to clothing and headgear sprites.
|
||||
- Improvements to the human sprites.
|
||||
- Item update optimizations.
|
||||
- The order of the crew list is saved between rounds in single player.
|
||||
- Pulse laser ammo can be bought from outposts and cities.
|
||||
- Indicate when a bot is following someone else than you on the crew list's order icons.
|
||||
- Bots that follow a character who's going inside/outside stick closer to the character they're following. Helps the bots to get back inside Barsuk with you.
|
||||
- Wired Herja and Barsuk diving suit lockers (unstable only).
|
||||
- Chaingun tweaks: doubled turning speed when firing, reduced charging up time, reduced ammo consumption and made the ammo boxes more expensive.
|
||||
- Simplified Remora drone docking system.
|
||||
- Show notifications about reputation changes mid-round.
|
||||
- Escorted NPCs drop the items they took from the sub (like suits) at the end of the round.
|
||||
- Added some extra logging to diagnose the "did not receive STARTGAMEFINALIZE message from the server" errors.
|
||||
- Added warnings when the game fails to run the physics at the desired 60 updates per second (which would cause rubberbanding in multiplayer). The warnings are shown below the FPS when the client is running slowly, and in the debug console of the host/moderators/admins when the server is.
|
||||
- Additions to supercapacitors to make the new high recharge speed work better: warning indicators on the capacitor UI, high recharge speed makes the capacitors emit a red glow and smoke, show the current recharge speed (in kW, not just percentage) in the UI, made the recharge speed adjust more slowly (to make it less easy to wreck the electrical grid with rapid capacitor adjustments).
|
||||
- Adjusted supercapacitors: reduced the max power consumption, increased efficiency (less load, the time it takes to recharge is still the same). Unstable only.
|
||||
- Made turret icons on the minimap gray instead of red when not manned (easy to think there's something wrong with the turret when it's red).
|
||||
- Abandoned outpost's oxygen generators now consume power.
|
||||
- Adjusted affliction heal costs in the medical clinic (unstable only).
|
||||
- Made characters crouch a little lower (enough to make it possible to shoot while standing behind a crouching character).
|
||||
- Added a "sendchatmessage" console command with an option to configure the color of the message.
|
||||
- Added button to align selected items and wire nodes to grid to the sub editor.
|
||||
|
||||
Fixes:
|
||||
- Misc localization fixes and improvements.
|
||||
- Fixed tab menu's character tab not refreshing when switching to another character.
|
||||
- Fixed ballast flora still being present when you replace an infested lost shuttle in an outpost.
|
||||
- Fixed occasional crashes and entity ID errors when entering a new level with a ballast flora infested sub.
|
||||
- Fixed inability to swap SMG magazines (or other items that go inside the held item) by double-clicking.
|
||||
- Fixed "atmos machine" not spawning psychosis artifacts.
|
||||
- Removed "periscopes determine which turret to focus on using the trigger_out connection instead of position_out" from the changelog (decided not to change this after all, because it causes problems and the position_out behavior can be worked around using relays). (unstable only)
|
||||
- Fixed "center on sonar transducer" setting working backwards (unstable only).
|
||||
- Fixed ability to remove tainted genetic materials' negative effects in the clinic (unstable only).
|
||||
- Fixed genetic materials being tainted by default (unstable only).
|
||||
- Fixed pets becoming hostile towards the crew and other pets if a human attacks the character they're protecting.
|
||||
- Fixed bots reacting (fleeing/attacking) to any amount of damage their crewmates do to them in multiplayer (unstable only).
|
||||
- Fixed security bots pointing their guns at you indefinitely when you inflict a small amount of damage on them (unstable only).
|
||||
- Fixed "failed to parse the string to Vector2" when loading bot orders that have been saved on a system that uses comma as a decimal separator.
|
||||
- Fixed sub editor's group list being empty when re-entering the editor.
|
||||
- Fixed rounding error in RespawnManager that caused it to require 1 extra dead player to trigger a respawn (e.g. 9 players and a minimum of 30% players to respawn required 3 players, but the client-side texts showed 2).
|
||||
- Fixed crashing when opening the head dropdown lists in the single player campaign's character customization menu (unstable only).
|
||||
- Fixed wikiimage_sub outputting an empty image (unstable only).
|
||||
- Fixed "stairs left" appearing mirrored in the status monitor's sub blueprint and in the sub editor's entity selection menu.
|
||||
- Fixed pirates not operating turrets when they have no power.
|
||||
- Fixed Wifi Component's "set_channel" input not working when sending signals to it via chat in multiplayer.
|
||||
- Fixed autoshotgun not taking stacks into account in the ammo indicator below the inventory slot (= displaying it as being full when there's one shell in each slot, even though more could be stacked on the slots).
|
||||
- Fixed hitscan turrets sometimes hitting targets inside your own sub when there's linked subs present.
|
||||
- Fixed bots not unequipping diving suits when they have an order but not actively following it (i.e. they are on idle).
|
||||
- Fixed broken Outpost Wall 3 sprite (unstable only).
|
||||
- Fixed junction boxes' signal components not working (unstable only).
|
||||
- Fixed artifact's effects not working when the artifact is held by a character (unstable only).
|
||||
- Fixed faraday and nasonov artifacts' periodic explosions stopping if the round is ended during their 0.5s "reset" period.
|
||||
- Fixed oxygenite shards not exploding in depth charge shells.
|
||||
- Fixed endworm not having a burn damage modifier in the right tooth.
|
||||
- Fixed killer sometimes being determined incorrectly when a character gets killed by something else than another character: e.g. if a character got crushed by pressure, the character who last did damage to them was considered to be the killer, which could for example lead to achievements being unlocked in inappropriate situations.
|
||||
|
||||
Modding:
|
||||
- If a mod makes a vanilla item movable/detachable and sets it as being attached by default, attach it to a wall when loading a sub that already had those items placed. I.e. making static devices movable doesn't cause them to deattach in existing subs.
|
||||
- Fixed monster AI's targeting priorities doing nothing if the threshold is 0 and the target hasn't done any damage.
|
||||
- Fixed custom ID card tags not working in wrecks.
|
||||
|
||||
---------------------------------------------------------------------------------------------------------
|
||||
v0.16.0.0
|
||||
---------------------------------------------------------------------------------------------------------
|
||||
@@ -5,7 +73,7 @@ v0.16.0.0
|
||||
Changes and additions:
|
||||
- Added a medical clinic to outposts that allows you to heal your crew for a price.
|
||||
- Added Barsuk, a small beginner-level sub.
|
||||
- Added photoshop-like layers to submarine editor.
|
||||
- Entities can be grouped together and the groups selectively hidden in the submarine editor.
|
||||
- Added some new decorative items and structures.
|
||||
- Adjusted medical items' effects on bleeding and burns. Bandages, plastiseal and antibiotic glue are now much more effective at treating them, and morphine & fentanyl only heal them by a negligible amount.
|
||||
- Heavily increased supercapacitor's power consumption and made the recharge speed increase exponentially when the recharge rate is increased.
|
||||
@@ -19,7 +87,6 @@ Changes and additions:
|
||||
- Made ballast flora toxins more visible and made them emit a sound.
|
||||
- Health scanner doesn't show buffs from talents.
|
||||
- Made impacts toss items around less effectively, especially when the item is heavy.
|
||||
- Periscopes determine which turret to focus on using the trigger_out connection instead of position_out (making it easier to build circuits that switch which turret a periscope controls).
|
||||
- Gave spinelings and threshers inventories (+ added alien blood as loot) to make it possible for "gene harvester" and "deep sea slayer" to spawn loot.
|
||||
- Monsters' burns don't heal by themselves.
|
||||
- "Allow rewiring" server setting doesn't affect wrecks, pirate subs or ruins.
|
||||
|
||||
Reference in New Issue
Block a user