0.15.21.0

This commit is contained in:
Markus Isberg
2021-12-16 01:05:43 +09:00
parent 617d9ede88
commit 7d43cb1e91
74 changed files with 487 additions and 541 deletions
@@ -726,6 +726,7 @@ namespace Barotrauma
if (!item.AllowedSlots.Contains(InvSlotType.Any) || !Character.Inventory.TryPutItem(item, Character, new List<InvSlotType>() { InvSlotType.Any }) && Character.Submarine?.TeamID == Character.TeamID )
{
if (item.AllowedSlots.Contains(InvSlotType.Bag) && Character.Inventory.TryPutItem(item, Character, new List<InvSlotType>() { InvSlotType.Bag })) { continue; }
findItemState = FindItemState.OtherItem;
if (FindSuitableContainer(item, out Item targetContainer))
{
@@ -877,8 +878,7 @@ namespace Barotrauma
if (target.CurrentHull != hull || !target.Enabled) { continue; }
if (AIObjectiveFightIntruders.IsValidTarget(target, Character))
{
bool arrested = AIObjectiveFightIntruders.ShouldArrest(target, Character) && target.HasEquippedItem("handlocker");
if (!arrested && AddTargets<AIObjectiveFightIntruders, Character>(Character, target) && newOrder == null)
if (!target.IsArrested && AddTargets<AIObjectiveFightIntruders, Character>(Character, target) && newOrder == null)
{
var orderPrefab = Order.GetPrefab("reportintruders");
newOrder = new Order(orderPrefab, hull, null, orderGiver: Character);
@@ -1871,7 +1871,7 @@ namespace Barotrauma
float enemyFactor = 1;
if (!ignoreEnemies)
{
bool isValidTarget(Character e) => IsActive(e) && !IsFriendly(character, e);
bool isValidTarget(Character e) => IsActive(e) && !IsFriendly(character, e) && !e.IsArrested;
int enemyCount = visibleHulls == null ?
Character.CharacterList.Count(e => isValidTarget(e) && e.CurrentHull == hull) :
Character.CharacterList.Count(e => isValidTarget(e) && visibleHulls.Contains(e.CurrentHull));
@@ -26,8 +26,9 @@ namespace Barotrauma
private float findPathTimer;
private const float buttonPressCooldown = 3;
private float checkDoorsTimer;
private float buttonPressCooldown;
private float buttonPressTimer;
public SteeringPath CurrentPath
{
@@ -98,7 +99,7 @@ namespace Barotrauma
base.Update(speed);
float step = 1.0f / 60.0f;
checkDoorsTimer -= step;
buttonPressCooldown -= step;
buttonPressTimer -= step;
findPathTimer -= step;
}
@@ -120,10 +121,18 @@ namespace Barotrauma
{
steering += base.DoSteeringSeek(targetSimPos, weight);
}
public void SteeringSeek(Vector2 target, float weight, float minGapWidth = 0, Func<PathNode, bool> startNodeFilter = null, Func<PathNode, bool> endNodeFilter = null, Func<PathNode, bool> nodeFilter = null, bool checkVisiblity = true)
{
steering += CalculateSteeringSeek(target, weight, minGapWidth, startNodeFilter, endNodeFilter, nodeFilter, checkVisiblity);
if (buttonPressTimer > 0 && lastDoor.door != null && lastDoor.state && !lastDoor.door.IsOpen)
{
// We have pressed the button and are waiting for the door to open -> Hold still until we can press the button again.
Reset();
}
else
{
steering += CalculateSteeringSeek(target, weight, minGapWidth, startNodeFilter, endNodeFilter, nodeFilter, checkVisiblity);
}
}
/// <summary>
@@ -612,18 +621,17 @@ namespace Barotrauma
float closestDist = 0;
bool canAccess = CanAccessDoor(door, button =>
{
if (currentWaypoint == null) { return true; }
// Check that the button is on the right side of the door. If the door is open, doesn't matter
if (!door.IsOpen)
// Check that the button is on the right side of the door.
if (nextWaypoint != null)
{
if (door.LinkedGap.IsHorizontal)
{
int dir = Math.Sign((nextWaypoint ?? currentWaypoint).WorldPosition.X - door.Item.WorldPosition.X);
int dir = Math.Sign((nextWaypoint).WorldPosition.X - door.Item.WorldPosition.X);
if (button.Item.WorldPosition.X * dir > door.Item.WorldPosition.X * dir) { return false; }
}
else
{
int dir = Math.Sign((nextWaypoint ?? currentWaypoint).WorldPosition.Y - door.Item.WorldPosition.Y);
int dir = Math.Sign((nextWaypoint).WorldPosition.Y - door.Item.WorldPosition.Y);
if (button.Item.WorldPosition.Y * dir > door.Item.WorldPosition.Y * dir) { return false; }
}
}
@@ -637,7 +645,7 @@ namespace Barotrauma
});
if (canAccess)
{
bool pressButton = buttonPressCooldown <= 0 || lastDoor.door != door || lastDoor.state != shouldBeOpen;
bool pressButton = buttonPressTimer <= 0 || lastDoor.door != door || lastDoor.state != shouldBeOpen;
if (door.HasIntegratedButtons)
{
if (pressButton && character.CanSeeTarget(door.Item))
@@ -645,11 +653,11 @@ namespace Barotrauma
if (door.Item.TryInteract(character, forceSelectKey: true))
{
lastDoor = (door, shouldBeOpen);
buttonPressCooldown = 3;
buttonPressTimer = buttonPressCooldown;
}
else
{
buttonPressCooldown = 0;
buttonPressTimer = 0;
}
}
break;
@@ -663,11 +671,11 @@ namespace Barotrauma
if (closestButton.Item.TryInteract(character, forceSelectKey: true))
{
lastDoor = (door, shouldBeOpen);
buttonPressCooldown = 3;
buttonPressTimer = buttonPressCooldown;
}
else
{
buttonPressCooldown = 0;
buttonPressTimer = 0;
}
}
break;
@@ -111,7 +111,7 @@ namespace Barotrauma
public CombatMode Mode { get; private set; }
private bool IsOffensiveOrArrest => initialMode == CombatMode.Offensive || initialMode == CombatMode.Arrest;
private bool TargetEliminated => IsEnemyDisabled || Enemy.IsUnconscious && Enemy.Params.Health.ConstantHealthRegeneration <= 0.0f || Enemy.IsHuman && Enemy.HasEquippedItem("handlocker") && !character.IsInstigator;
private bool TargetEliminated => IsEnemyDisabled || Enemy.IsUnconscious && Enemy.Params.Health.ConstantHealthRegeneration <= 0.0f || Enemy.IsArrested && !character.IsInstigator;
private bool IsEnemyDisabled => Enemy == null || Enemy.Removed || Enemy.IsDead;
private float AimSpeed => HumanAIController.AimSpeed;
@@ -668,6 +668,13 @@ namespace Barotrauma
{
if (!Weapon.AllowedSlots.Contains(InvSlotType.Any) || !character.Inventory.TryPutItem(Weapon, character, new List<InvSlotType>() { InvSlotType.Any }))
{
if (Weapon.AllowedSlots.Contains(InvSlotType.Bag))
{
if (character.Inventory.TryPutItem(Weapon, character, new List<InvSlotType>() { InvSlotType.Bag }))
{
return;
}
}
Weapon.Drop(character);
}
}
@@ -680,10 +687,10 @@ namespace Barotrauma
{
return false;
}
if (!character.HasEquippedItem(Weapon))
if (!character.HasEquippedItem(Weapon, predicate: IsHandSlotType))
{
Weapon.TryInteract(character, forceSelectKey: true);
var slots = Weapon.AllowedSlots.Where(s => s == InvSlotType.LeftHand || s == InvSlotType.RightHand || s == (InvSlotType.LeftHand | InvSlotType.RightHand));
var slots = Weapon.AllowedSlots.Where(s => IsHandSlotType(s));
if (character.Inventory.TryPutItem(Weapon, character, slots))
{
aimTimer = Rand.Range(0.2f, 0.4f) / AimSpeed;
@@ -697,6 +704,8 @@ namespace Barotrauma
}
}
return true;
bool IsHandSlotType(InvSlotType s) => s == InvSlotType.LeftHand || s == InvSlotType.RightHand || s == (InvSlotType.LeftHand | InvSlotType.RightHand);
}
private float findHullTimer;
@@ -903,8 +912,9 @@ namespace Barotrauma
TryAddSubObjective(ref seekAmmunitionObjective,
constructor: () => new AIObjectiveContainItem(character, ammunitionIdentifiers, Weapon.GetComponent<ItemContainer>(), objectiveManager)
{
ItemCount = Weapon.GetComponent<ItemContainer>().Capacity,
checkInventory = false
ItemCount = Weapon.GetComponent<ItemContainer>().Capacity * Weapon.GetComponent<ItemContainer>().MaxStackSize,
checkInventory = false,
MoveWholeStack = true
},
onCompleted: () => RemoveSubObjective(ref seekAmmunitionObjective),
onAbandon: () =>
@@ -186,7 +186,9 @@ namespace Barotrauma
AllowToFindDivingGear = AllowToFindDivingGear,
AllowDangerousPressure = AllowDangerousPressure,
TargetCondition = ConditionLevel,
ItemFilter = (Item potentialItem) => RemoveEmpty ? container.CanBeContained(potentialItem) : container.Inventory.CanBePut(potentialItem)
ItemFilter = (Item potentialItem) => RemoveEmpty ? container.CanBeContained(potentialItem) : container.Inventory.CanBePut(potentialItem),
ItemCount = ItemCount,
TakeWholeStack = MoveWholeStack
}, onAbandon: () =>
{
Abandon = true;
@@ -65,7 +65,7 @@ namespace Barotrauma
if (HumanAIController.IsFriendly(character, target)) { return false; }
if (!character.Submarine.IsConnectedTo(target.Submarine)) { return false; }
if (target.HasAbilityFlag(AbilityFlags.IgnoredByEnemyAI)) { return false; }
if (ShouldArrest(target, character) && target.HasEquippedItem("handlocker")) { return false; }
if (target.IsArrested) { return false; }
return true;
}
@@ -262,7 +262,7 @@ namespace Barotrauma
}
foreach (Character enemy in Character.CharacterList)
{
if (!HumanAIController.IsActive(enemy) || HumanAIController.IsFriendly(enemy)) { continue; }
if (!HumanAIController.IsActive(enemy) || HumanAIController.IsFriendly(enemy) || enemy.IsArrested) { continue; }
if (HumanAIController.VisibleHulls.Contains(enemy.CurrentHull))
{
Vector2 dir = character.Position - enemy.Position;
@@ -182,8 +182,12 @@ namespace Barotrauma
{
if (character.SelectedConstruction != Item)
{
if (!Item.TryInteract(character, ignoreRequiredItems: true, forceSelectKey: true) &&
!Item.TryInteract(character, ignoreRequiredItems: true, forceUseKey: true))
if (Item.TryInteract(character, ignoreRequiredItems: true, forceSelectKey: true) ||
Item.TryInteract(character, ignoreRequiredItems: true, forceUseKey: true))
{
character.SelectedConstruction = Item;
}
else
{
Abandon = true;
}
@@ -199,7 +199,7 @@ namespace Barotrauma
foreach (Character potentialCharacter in Character.CharacterList)
{
if (!HumanAIController.IsActive(character)) { continue; }
if (!HumanAIController.IsActive(potentialCharacter)) { continue; }
if (HumanAIController.IsFriendly(character, potentialCharacter, true) && potentialCharacter.AIController is HumanAIController)
{
@@ -621,6 +621,11 @@ namespace Barotrauma
get { return CharacterHealth.IsUnconscious; }
}
public bool IsArrested
{
get { return IsHuman && HasEquippedItem("handlocker"); }
}
public bool IsPet
{
get { return AIController is EnemyAIController enemyController && enemyController.PetBehavior != null; }
@@ -2043,16 +2048,21 @@ namespace Barotrauma
public bool HasItem(Item item, bool requireEquipped = false, InvSlotType? slotType = null) => requireEquipped ? HasEquippedItem(item, slotType) : item.IsOwnedBy(this);
public bool HasEquippedItem(Item item, InvSlotType? slotType = null)
public bool HasEquippedItem(Item item, InvSlotType? slotType = null, Func<InvSlotType, bool> predicate = null)
{
if (Inventory == null) { return false; }
for (int i = 0; i < Inventory.Capacity; i++)
{
InvSlotType slot = Inventory.SlotTypes[i];
if (predicate != null)
{
if (!predicate(slot)) { continue; }
}
if (slotType.HasValue)
{
if (!slotType.Value.HasFlag(Inventory.SlotTypes[i])) { continue; }
if (!slotType.Value.HasFlag(slot)) { continue; }
}
else if (Inventory.SlotTypes[i] == InvSlotType.Any)
else if (slot == InvSlotType.Any)
{
continue;
}
@@ -1206,7 +1206,7 @@ namespace Barotrauma
return (int)(salary * Job.Prefab.PriceMultiplier);
}
public void IncreaseSkillLevel(string skillIdentifier, float increase, bool gainedFromApprenticeship = false)
public void IncreaseSkillLevel(string skillIdentifier, float increase, bool gainedFromAbility = false)
{
if (Job == null || (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) || Character == null) { return; }
@@ -1226,7 +1226,7 @@ namespace Barotrauma
{
// assume we are getting at least 1 point in skill, since this logic only runs in such cases
float increaseSinceLastSkillPoint = MathHelper.Max(increase, 1f);
var abilitySkillGain = new AbilitySkillGain(increaseSinceLastSkillPoint, skillIdentifier, Character, gainedFromApprenticeship);
var abilitySkillGain = new AbilitySkillGain(increaseSinceLastSkillPoint, skillIdentifier, Character, gainedFromAbility);
Character.CheckTalents(AbilityEffectType.OnGainSkillPoint, abilitySkillGain);
foreach (Character character in Character.GetFriendlyCrew(Character))
{
@@ -1298,9 +1298,9 @@ namespace Barotrauma
return Math.Max(GetTotalTalentPoints() - GetUnlockedTalentsInTree().Count(), 0);
}
public int GetProgressTowardsNextLevel()
public float GetProgressTowardsNextLevel()
{
return (ExperiencePoints - GetExperienceRequiredForCurrentLevel()) / (GetExperienceRequiredToLevelUp() - GetExperienceRequiredForCurrentLevel());
return (ExperiencePoints - GetExperienceRequiredForCurrentLevel()) / (float)(GetExperienceRequiredToLevelUp() - GetExperienceRequiredForCurrentLevel());
}
public int GetExperienceRequiredForCurrentLevel()
@@ -1860,16 +1860,16 @@ namespace Barotrauma
class AbilitySkillGain : AbilityObject, IAbilityValue, IAbilityString, IAbilityCharacter
{
public AbilitySkillGain(float value, string abilityString, Character character, bool gainedFromApprenticeship)
public AbilitySkillGain(float value, string abilityString, Character character, bool gainedFromAbility)
{
Value = value;
String = abilityString;
Character = character;
GainedFromApprenticeship = gainedFromApprenticeship;
GainedFromAbility = gainedFromAbility;
}
public Character Character { get; set; }
public float Value { get; set; }
public string String { get; set; }
public bool GainedFromApprenticeship { get; set; }
public bool GainedFromAbility { get; }
}
}
@@ -959,14 +959,11 @@ namespace Barotrauma
{
var affliction = kvp.Key;
var limbHealth = kvp.Value;
if (limb != null)
if (limb != null && affliction.Prefab.IndicatorLimb != limb.type)
{
if (limbHealth == null) { continue; }
int healthIndex = limbHealths.IndexOf(limbHealth);
Limb targetLimb =
Character.AnimController.Limbs.LastOrDefault(l => !l.IsSevered && !l.Hidden && l.HealthIndex == healthIndex) ??
Character.AnimController.MainLimb;
if (limb != targetLimb) { continue; }
if (limb.HealthIndex != healthIndex) { continue; }
}
float strength = affliction.Strength;
@@ -9,7 +9,7 @@ namespace Barotrauma
public string Identifier { get; }
public const float MaximumSkill = 100.0f;
public float Level
{
get { return level; }
@@ -18,7 +18,7 @@ namespace Barotrauma
public void IncreaseSkill(float value, bool increasePastMax)
{
level = MathHelper.Clamp(level + value, 0.0f, increasePastMax ? float.MaxValue : MaximumSkill);
level = MathHelper.Clamp(level + value, 0.0f, increasePastMax ? SkillSettings.Current.MaximumOlympianSkill : MaximumSkill);
}
private Sprite icon;
@@ -96,6 +96,13 @@ namespace Barotrauma
set;
}
[Serialize(500.0f, true)]
public float MaximumOlympianSkill
{
get;
set;
}
private SkillSettings(XElement element)
{
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
@@ -22,7 +22,7 @@ namespace Barotrauma.Abilities
private readonly bool ignoreNonHarmfulAttacks;
public AbilityConditionAttackData(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
itemIdentifier = conditionElement.GetAttributeString("itemidentifier", "");
itemIdentifier = conditionElement.GetAttributeString("itemidentifier", string.Empty);
tags = conditionElement.GetAttributeStringArray("tags", new string[0], convertToLowerInvariant: true);
ignoreNonHarmfulAttacks = conditionElement.GetAttributeBool("ignorenonharmfulattacks", false);
@@ -46,15 +46,10 @@ namespace Barotrauma.Abilities
}
Item item = attackData?.SourceAttack?.SourceItem;
if (item == null)
{
DebugConsole.AddWarning($"Source Item was not found in {this} for talent {characterTalent.DebugIdentifier}!");
return false;
}
if (!string.IsNullOrEmpty(itemIdentifier))
{
if (item.prefab.Identifier != itemIdentifier)
if (item?.prefab.Identifier != itemIdentifier)
{
return false;
}
@@ -62,31 +57,34 @@ namespace Barotrauma.Abilities
if (tags.Any())
{
if (!tags.All(t => item.HasTag(t)))
if (!tags.All(t => item?.HasTag(t) ?? false))
{
return false;
}
}
switch (weapontype)
if (weapontype != WeaponType.Any)
{
// it is possible that an item that has both a melee and a projectile component will return true
// even when not used as a melee/ranged weapon respectively
// attackdata should contain data regarding whether the attack is melee or not
case WeaponType.Melee:
return item.GetComponent<MeleeWeapon>() != null;
case WeaponType.Ranged:
return item.GetComponent<Projectile>() != null;
case WeaponType.HandheldRanged:
{
var projectile = item.GetComponent<Projectile>();
return projectile?.Launcher?.GetComponent<Holdable>() != null;
}
case WeaponType.Turret:
{
var projectile = item.GetComponent<Projectile>();
return projectile?.Launcher?.GetComponent<Turret>() != null;
}
switch (weapontype)
{
// it is possible that an item that has both a melee and a projectile component will return true
// even when not used as a melee/ranged weapon respectively
// attackdata should contain data regarding whether the attack is melee or not
case WeaponType.Melee:
return item?.GetComponent<MeleeWeapon>() != null;
case WeaponType.Ranged:
return item?.GetComponent<Projectile>() != null;
case WeaponType.HandheldRanged:
{
var projectile = item?.GetComponent<Projectile>();
return projectile?.Launcher?.GetComponent<Holdable>() != null;
}
case WeaponType.Turret:
{
var projectile = item?.GetComponent<Projectile>();
return projectile?.Launcher?.GetComponent<Turret>() != null;
}
}
}
return true;
@@ -5,18 +5,21 @@ namespace Barotrauma.Abilities
{
class CharacterAbilityGainSimultaneousSkill : CharacterAbility
{
private string skillIdentifier;
private readonly string skillIdentifier;
private readonly bool ignoreAbilitySkillGain;
public CharacterAbilityGainSimultaneousSkill(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
skillIdentifier = abilityElement.GetAttributeString("skillidentifier", "").ToLowerInvariant();
ignoreAbilitySkillGain = abilityElement.GetAttributeBool("ignoreabilityskillgain", true);
}
protected override void ApplyEffect(AbilityObject abilityObject)
{
if ((abilityObject as IAbilityValue)?.Value is float skillIncrease)
if (abilityObject is AbilitySkillGain abilitySkillGain)
{
Character.Info?.IncreaseSkillLevel(skillIdentifier, skillIncrease);
if (ignoreAbilitySkillGain && !abilitySkillGain.GainedFromAbility) { return; }
Character.Info?.IncreaseSkillLevel(skillIdentifier, abilitySkillGain.Value, gainedFromAbility: true);
}
else
{
@@ -49,11 +49,11 @@ namespace Barotrauma.Abilities
{
var skill = character.Info?.Job?.Skills?.GetRandom();
if (skill == null) { return; }
character.Info?.IncreaseSkillLevel(skill.Identifier, skillIncrease);
character.Info?.IncreaseSkillLevel(skill.Identifier, skillIncrease, gainedFromAbility: true);
}
else
{
character.Info?.IncreaseSkillLevel(skillIdentifier, skillIncrease);
character.Info?.IncreaseSkillLevel(skillIdentifier, skillIncrease, gainedFromAbility: true);
}
}
}
@@ -1,28 +0,0 @@
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityModifyReduceAffliction : CharacterAbility
{
float addedAmountMultiplier;
public override bool AllowClientSimulation => true;
public CharacterAbilityModifyReduceAffliction(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
addedAmountMultiplier = abilityElement.GetAttributeFloat("addedamountmultiplier", 0f);
}
protected override void ApplyEffect(AbilityObject abilityObject)
{
if (abilityObject is AbilityValueAffliction afflictionReduceAmount)
{
afflictionReduceAmount.Affliction.Strength -= addedAmountMultiplier * afflictionReduceAmount.Value;
}
else
{
LogabilityObjectMismatch();
}
}
}
}
@@ -36,7 +36,7 @@ namespace Barotrauma.Abilities
if (GameMain.GameSession?.RoundEnding ?? true)
{
Item item = new Item(itemPrefab, Character.WorldPosition, Character.Submarine);
if (!Character.Inventory.TryPutItem(item, Character))
if (!Character.Inventory.TryPutItem(item, Character, item.AllowedSlots))
{
foreach (Item containedItem in Character.Inventory.AllItemsMod)
{
@@ -6,15 +6,19 @@ namespace Barotrauma.Abilities
{
class CharacterAbilityApprenticeship : CharacterAbility
{
private readonly bool ignoreAbilitySkillGain;
public CharacterAbilityApprenticeship(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
ignoreAbilitySkillGain = abilityElement.GetAttributeBool("ignoreabilityskillgain", true);
}
protected override void ApplyEffect(AbilityObject abilityObject)
{
if (abilityObject is AbilitySkillGain abilitySkillGain && !abilitySkillGain.GainedFromApprenticeship && abilitySkillGain.Character != Character)
if (abilityObject is AbilitySkillGain abilitySkillGain && abilitySkillGain.Character != Character)
{
Character.Info?.IncreaseSkillLevel(abilitySkillGain.String, 1.0f, gainedFromApprenticeship: true);
if (ignoreAbilitySkillGain && !abilitySkillGain.GainedFromAbility) { return; }
Character.Info?.IncreaseSkillLevel(abilitySkillGain.String, 1.0f, gainedFromAbility: true);
}
}
}
@@ -18,7 +18,7 @@ namespace Barotrauma.Abilities
if (skillIdentifier != lastSkillIdentifier)
{
lastSkillIdentifier = skillIdentifier;
Character.Info?.IncreaseSkillLevel(skillIdentifier, 1.0f);
Character.Info?.IncreaseSkillLevel(skillIdentifier, 1.0f, gainedFromAbility: true);
}
}
}
@@ -1531,6 +1531,11 @@ namespace Barotrauma
}
}, isCheat: true));
commands.Add(new Command("skipeventcooldown", "skipeventcooldown: Skips the currently active event cooldown and triggers pending monster spawns immediately.", args =>
{
GameMain.GameSession?.EventManager?.SkipEventCooldown();
}, isCheat: true));
commands.Add(new Command("ballastflora", "infectballast [options]: Infect ballasts and control its growth.", args =>
{
if (args.Length == 0)
@@ -149,12 +149,12 @@ namespace Barotrauma
}
MTRandom rand = new MTRandom(seed);
EventSet initialEventSet = SelectRandomEvents(EventSet.List, rand);
EventSet initialEventSet = SelectRandomEvents(EventSet.List, requireCampaignSet: GameMain.GameSession?.GameMode is CampaignMode, rand);
EventSet additiveSet = null;
if (initialEventSet != null && initialEventSet.Additive)
{
additiveSet = initialEventSet;
initialEventSet = SelectRandomEvents(EventSet.List.FindAll(e => !e.Additive), rand);
initialEventSet = SelectRandomEvents(EventSet.List.FindAll(e => !e.Additive), requireCampaignSet: GameMain.GameSession?.GameMode is CampaignMode, rand);
}
if (initialEventSet != null)
{
@@ -418,6 +418,11 @@ namespace Barotrauma
pathFinder = null;
}
public void SkipEventCooldown()
{
eventCoolDown = 0.0f;
}
private float CalculateCommonness(EventPrefab eventPrefab, float baseCommonness)
{
if (level.LevelData.NonRepeatableEvents.Contains(eventPrefab)) { return 0.0f; }
@@ -500,7 +505,7 @@ namespace Barotrauma
}
if (eventSet.ChildSets.Count > 0)
{
var newEventSet = SelectRandomEvents(eventSet.ChildSets, rand);
var newEventSet = SelectRandomEvents(eventSet.ChildSets, random: rand);
if (newEventSet != null)
{
CreateEvents(newEventSet, rand);
@@ -535,18 +540,38 @@ namespace Barotrauma
}
}
private EventSet SelectRandomEvents(List<EventSet> eventSets, Random random = null)
private EventSet SelectRandomEvents(List<EventSet> eventSets, bool? requireCampaignSet = null, Random random = null)
{
if (level == null) { return null; }
Random rand = random ?? new MTRandom(ToolBox.StringToInt(level.Seed));
var allowedEventSets =
eventSets.Where(es =>
es.IsCampaignSet == GameMain.GameSession?.GameMode is CampaignMode &&
level.Difficulty >= es.MinLevelDifficulty && level.Difficulty <= es.MaxLevelDifficulty &&
level.LevelData.Type == es.LevelType &&
(string.IsNullOrEmpty(es.BiomeIdentifier) || es.BiomeIdentifier.Equals(level.LevelData.Biome.Identifier, StringComparison.OrdinalIgnoreCase)));
if (requireCampaignSet.HasValue)
{
if (requireCampaignSet.Value)
{
if (allowedEventSets.Any(es => es.IsCampaignSet))
{
allowedEventSets =
allowedEventSets.Where(es => es.IsCampaignSet);
}
else
{
DebugConsole.AddWarning("No campaign event sets available. Using a non-campaign-specific set instead.");
}
}
else
{
allowedEventSets =
allowedEventSets.Where(es => !es.IsCampaignSet);
}
}
Location location = (GameMain.GameSession?.GameMode as CampaignMode)?.Map?.CurrentLocation ?? level?.StartLocation;
LocationType locationType = location?.GetLocationType();
@@ -196,7 +196,7 @@ namespace Barotrauma
DelayWhenCrewAway = element.GetAttributeBool("delaywhencrewaway", !PerRuin && !PerCave && !PerWreck);
OncePerOutpost = element.GetAttributeBool("onceperoutpost", false);
TriggerEventCooldown = element.GetAttributeBool("triggereventcooldown", true);
IsCampaignSet = element.GetAttributeBool("campaign", LevelType == LevelData.LevelType.Outpost);
IsCampaignSet = element.GetAttributeBool("campaign", LevelType == LevelData.LevelType.Outpost || (parentSet?.IsCampaignSet ?? false));
Commonness[""] = element.GetAttributeFloat("commonness", 1.0f);
foreach (XElement subElement in element.Elements())
@@ -42,15 +42,15 @@ namespace Barotrauma
{
if (maxAmount <= 1)
{
return "MonsterEvent (" + speciesName + ")";
return $"MonsterEvent ({speciesName}, {SpawnPosType})";
}
else if (minAmount < maxAmount)
{
return "MonsterEvent (" + speciesName + " x" + minAmount + "-" + maxAmount + ")";
return $"MonsterEvent ({speciesName} x{minAmount}-{maxAmount}, {SpawnPosType})";
}
else
{
return "MonsterEvent (" + speciesName + " x" + maxAmount + ")";
return $"MonsterEvent ({speciesName} x{maxAmount}, {SpawnPosType})";
}
}
@@ -317,9 +317,11 @@ namespace Barotrauma
#endif
string exePath = Assembly.GetEntryAssembly()!.Location;
string? exeName = null;
string? exeName = string.Empty;
#if SERVER
exeName = "s";
#endif
Md5Hash? exeHash = null;
exeName = Path.GetFileNameWithoutExtension(exePath).Replace(":", "");
try
{
using (var stream = File.OpenRead(exePath))
@@ -333,16 +335,27 @@ namespace Barotrauma
}
try
{
string buildConfiguration = "Release";
#if DEBUG
buildConfiguration = "Debug";
#elif UNSTABLE
buildConfiguration = "Unstable";
#endif
loadedImplementation?.ConfigureBuild(GameMain.Version.ToString()
+ (string.IsNullOrEmpty(exeName) ? "Unknown" : exeName) + ":"
+ ((exeHash?.ShortHash == null) ? "Unknown" : exeHash.ShortHash));
+ exeName + ":"
+ AssemblyInfo.GitRevision + ":"
+ buildConfiguration);
loadedImplementation?.ConfigureAvailableCustomDimensions01("singleplayer", "multiplayer", "editor");
InitKeys();
loadedImplementation?.AddDesignEvent("Executable:"
+ (string.IsNullOrEmpty(exeName) ? "Unknown" : exeName) + ":"
+ ((exeHash?.ShortHash == null) ? "Unknown" : exeHash.ShortHash));
+ GameMain.Version.ToString()
+ exeName + ":"
+ ((exeHash?.ShortHash == null) ? "Unknown" : exeHash.ShortHash) + ":"
+ AssemblyInfo.GitBranch + ":"
+ AssemblyInfo.GitRevision + ":"
+ buildConfiguration);
}
catch (Exception e)
{
@@ -1,132 +0,0 @@
using GameAnalyticsSDK.Net;
using System;
using System.Text;
using System.Collections.Generic;
using Barotrauma.IO;
using System.Linq;
using System.Reflection;
using System.Security.Cryptography;
namespace Barotrauma
{
public static class GameAnalyticsManager
{
private static HashSet<string> sentEventIdentifiers = new HashSet<string>();
public static void Init()
{
#if DEBUG
try
{
GameAnalytics.SetEnabledInfoLog(true);
}
catch (Exception e)
{
DebugConsole.ThrowError("Initializing GameAnalytics failed. Disabling user statistics...", e);
GameSettings.SendUserStatistics = false;
return;
}
#endif
string exePath = Assembly.GetEntryAssembly().Location;
string exeName = null;
Md5Hash exeHash = null;
exeName = Path.GetFileNameWithoutExtension(exePath).Replace(":", "");
var md5 = MD5.Create();
try
{
using (var stream = File.OpenRead(exePath))
{
exeHash = new Md5Hash(stream);
}
}
catch (Exception e)
{
DebugConsole.ThrowError("Error while calculating MD5 hash for the executable \"" + exePath + "\"", e);
}
try
{
GameAnalytics.ConfigureBuild(GameMain.Version.ToString()
+ (string.IsNullOrEmpty(exeName) ? "Unknown" : exeName) + ":"
+ ((exeHash?.ShortHash == null) ? "Unknown" : exeHash.ShortHash));
GameAnalytics.ConfigureAvailableCustomDimensions01("singleplayer", "multiplayer", "editor");
GameAnalytics.Initialize("a3a073c20982de7c15d21e840e149122", "9010ad9a671233b8d9610d76cec8c897d9ff3ba7");
GameAnalytics.AddDesignEvent("Executable:"
+ (string.IsNullOrEmpty(exeName) ? "Unknown" : exeName) + ":"
+ ((exeHash?.ShortHash == null) ? "Unknown" : exeHash.ShortHash));
}
catch (Exception e)
{
DebugConsole.ThrowError("Initializing GameAnalytics failed. Disabling user statistics...", e);
GameSettings.SendUserStatistics = false;
return;
}
var allPackages = GameMain.Config?.AllEnabledPackages.ToList();
if (allPackages?.Count > 0)
{
StringBuilder sb = new StringBuilder("ContentPackage: ");
int i = 0;
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(" "); }
}
GameAnalytics.AddDesignEvent(sb.ToString());
}
}
/// <summary>
/// Adds an error event to GameAnalytics if an event with the same identifier has not been added yet.
/// </summary>
public static void AddErrorEventOnce(string identifier, EGAErrorSeverity errorSeverity, string message)
{
if (!GameSettings.SendUserStatistics) { return; }
if (sentEventIdentifiers.Contains(identifier)) { return; }
if (GameMain.Config.AllEnabledPackages != null)
{
if (GameMain.VanillaContent == null || GameMain.Config.AllEnabledPackages.Any(p => p.HasMultiplayerIncompatibleContent && p != GameMain.VanillaContent))
{
message = "[MODDED] " + message;
}
}
GameAnalytics.AddErrorEvent(errorSeverity, message);
sentEventIdentifiers.Add(identifier);
}
public static void AddDesignEvent(string eventID)
{
if (!GameSettings.SendUserStatistics) return;
GameAnalytics.AddDesignEvent(eventID);
}
public static void AddDesignEvent(string eventID, double value)
{
if (!GameSettings.SendUserStatistics) return;
GameAnalytics.AddDesignEvent(eventID, value);
}
public static void AddProgressionEvent(EGAProgressionStatus progressionStatus, string progression01)
{
if (!GameSettings.SendUserStatistics) return;
GameAnalytics.AddProgressionEvent(progressionStatus, progression01);
}
public static void AddProgressionEvent(EGAProgressionStatus progressionStatus, string progression01, string progression02)
{
if (!GameSettings.SendUserStatistics) return;
GameAnalytics.AddProgressionEvent(progressionStatus, progression01, progression02);
}
public static void SetCustomDimension01(string dimension)
{
if (!GameSettings.SendUserStatistics) return;
GameAnalytics.SetCustomDimension01(dimension);
}
}
}
@@ -283,7 +283,8 @@ namespace Barotrauma
public void PurchaseSubmarine(SubmarineInfo newSubmarine)
{
if (Campaign == null) return;
if (Campaign is null) { return; }
if (Campaign.Money < newSubmarine.Price) { return; }
if (!OwnedSubmarines.Any(s => s.Name == newSubmarine.Name))
{
Campaign.Money -= newSubmarine.Price;
@@ -207,8 +207,14 @@ namespace Barotrauma.Items.Components
if (!item.linkedTo.Contains(target.item)) { item.linkedTo.Add(target.item); }
if (!target.item.linkedTo.Contains(item)) { target.item.linkedTo.Add(item); }
if (!target.item.Submarine.DockedTo.Contains(item.Submarine)) target.item.Submarine.ConnectedDockingPorts.Add(item.Submarine, target);
if (!item.Submarine.DockedTo.Contains(target.item.Submarine)) item.Submarine.ConnectedDockingPorts.Add(target.item.Submarine, this);
if (!target.item.Submarine.DockedTo.Contains(item.Submarine))
{
target.item.Submarine.ConnectedDockingPorts.Add(item.Submarine, target);
}
if (!item.Submarine.DockedTo.Contains(target.item.Submarine))
{
item.Submarine.ConnectedDockingPorts.Add(target.item.Submarine, this);
}
DockingTarget = target;
DockingTarget.DockingTarget = this;
@@ -869,11 +875,17 @@ namespace Barotrauma.Items.Components
if (myWayPoint != null && targetWayPoint != null)
{
myWayPoint.FindHull();
myWayPoint.linkedTo.Remove(targetWayPoint);
myWayPoint.OnLinksChanged?.Invoke(myWayPoint);
if (myWayPoint.linkedTo.Contains(targetWayPoint))
{
myWayPoint.linkedTo.Remove(targetWayPoint);
myWayPoint.OnLinksChanged?.Invoke(myWayPoint);
}
targetWayPoint.FindHull();
targetWayPoint.linkedTo.Remove(myWayPoint);
targetWayPoint.OnLinksChanged?.Invoke(targetWayPoint);
if (targetWayPoint.linkedTo.Contains(myWayPoint))
{
targetWayPoint.linkedTo.Remove(myWayPoint);
targetWayPoint.OnLinksChanged?.Invoke(targetWayPoint);
}
}
}
@@ -19,7 +19,7 @@ namespace Barotrauma.Items.Components
private float userDeconstructorSpeedMultiplier = 1.0f;
private const float TinkeringSpeedIncrease = 1.5f;
private const float TinkeringSpeedIncrease = 2.5f;
private ItemContainer inputContainer, outputContainer;
@@ -37,7 +37,7 @@ namespace Barotrauma.Items.Components
[Serialize(1.0f, true)]
public float SkillRequirementMultiplier { get; set; }
private const float TinkeringSpeedIncrease = 1.5f;
private const float TinkeringSpeedIncrease = 2.5f;
private enum FabricatorState
{
@@ -70,7 +70,7 @@ namespace Barotrauma.Items.Components
public bool HasPower => IsActive && Voltage >= MinVoltage;
public bool IsAutoControlled => pumpSpeedLockTimer > 0.0f || isActiveLockTimer > 0.0f;
private const float TinkeringSpeedIncrease = 1.5f;
private const float TinkeringSpeedIncrease = 4.0f;
public Pump(Item item, XElement element)
: base(item, element)
@@ -16,8 +16,6 @@ namespace Barotrauma.Items.Components
private float deteriorationTimer;
private float deteriorateAlwaysResetTimer;
private float repairBoost;
bool wasBroken;
bool wasGoodCondition;
@@ -208,7 +206,7 @@ namespace Barotrauma.Items.Components
public float RepairDegreeOfSuccess(Character character, List<Skill> skills)
{
if (skills.Count == 0) return 1.0f;
if (skills.Count == 0) { return 1.0f; }
float skillSum = (from t in skills let characterLevel = character.GetSkillLevel(t.Identifier) select (characterLevel - (t.Level * SkillRequirementMultiplier))).Sum();
float average = skillSum / skills.Count;
@@ -220,11 +218,14 @@ namespace Barotrauma.Items.Components
{
if (qteSuccess)
{
repairBoost = RepairDegreeOfSuccess(CurrentFixer, requiredSkills) * 3 * (currentFixerAction == FixActions.Repair ? 1.0f : -1.0f);
item.Condition += RepairDegreeOfSuccess(CurrentFixer, requiredSkills) * 3 * (currentFixerAction == FixActions.Repair ? 1.0f : -1.0f);
}
else
else if (Rand.Range(0.0f, 2.0f) > RepairDegreeOfSuccess(CurrentFixer, requiredSkills))
{
repairBoost = (1 - RepairDegreeOfSuccess(CurrentFixer, requiredSkills)) * 10 * (currentFixerAction == FixActions.Repair ? -1.0f : 1.0f);
ApplyStatusEffects(ActionType.OnFailure, 1.0f, CurrentFixer);
#if SERVER
GameMain.Server?.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnFailure, this, CurrentFixer.ID });
#endif
}
}
@@ -312,6 +313,8 @@ namespace Barotrauma.Items.Components
currentRepairItem = null;
currentFixerAction = FixActions.None;
#if CLIENT
qteTimer = QteDuration;
qteCooldown = 0.0f;
repairSoundChannel?.FadeOutAndDispose();
repairSoundChannel = null;
#endif
@@ -423,12 +426,6 @@ namespace Barotrauma.Items.Components
wasGoodCondition = true;
}
if (!MathUtils.NearlyEqual(repairBoost, 0.0f))
{
item.Condition += repairBoost;
repairBoost = 0.0f;
}
float fixDuration = MathHelper.Lerp(FixDurationLowSkill, FixDurationHighSkill, successFactor);
fixDuration /= 1 + CurrentFixer.GetStatValue(StatTypes.RepairSpeed) + currentRepairItem?.Prefab.AddedRepairSpeedMultiplier ?? 0f;
fixDuration /= 1 + item.GetQualityModifier(Quality.StatType.RepairSpeed);
@@ -77,7 +77,7 @@ namespace Barotrauma.Items.Components
//item in water -> we definitely want to send the True output
isInWater = true;
}
else if (item.CurrentHull != null && item.CurrentHull.WaterPercentage > 0.0f)
else if (item.CurrentHull != null && item.CurrentHull.WaterPercentage > 0.0f && item.CurrentHull.WaterVolume > 1.0f)
{
//(center of the) item in not water -> check if the water surface is below the bottom of the item's rect
if (item.CurrentHull.Surface > item.Rect.Y - item.Rect.Height)
@@ -102,7 +102,7 @@ namespace Barotrauma.Items.Components
{
int waterPercentage = 0;
//ignore minuscule amounts of water
if (item.CurrentHull.WaterVolume < 1.0f)
if (item.CurrentHull.WaterVolume > 1.0f)
{
waterPercentage = MathHelper.Clamp((int)Math.Ceiling(item.CurrentHull.WaterPercentage), 0, 100);
}
@@ -2573,7 +2573,9 @@ namespace Barotrauma
private void WritePropertyChange(IWriteMessage msg, object[] extraData, bool inGameEditableOnly)
{
var allProperties = inGameEditableOnly ? GetInGameEditableProperties() : GetProperties<Editable>();
//ignoreConditions: true = include all ConditionallyEditable properties at this point,
//to ensure client/server doesn't get any properties mixed up if there's some conditions that can vary between the server and the clients
var allProperties = inGameEditableOnly ? GetInGameEditableProperties(ignoreConditions: true) : GetProperties<Editable>();
SerializableProperty property = extraData[1] as SerializableProperty;
if (property != null)
{
@@ -2660,16 +2662,25 @@ namespace Barotrauma
}
}
private List<Pair<object, SerializableProperty>> GetInGameEditableProperties()
private List<Pair<object, SerializableProperty>> GetInGameEditableProperties(bool ignoreConditions = false)
{
return GetProperties<ConditionallyEditable>()
.Where(ce => ce.Second.GetAttribute<ConditionallyEditable>().IsEditable(this))
.Union(GetProperties<InGameEditable>()).ToList();
if (ignoreConditions)
{
return GetProperties<ConditionallyEditable>().Union(GetProperties<InGameEditable>()).ToList();
}
else
{
return GetProperties<ConditionallyEditable>()
.Where(ce => ce.Second.GetAttribute<ConditionallyEditable>().IsEditable(this))
.Union(GetProperties<InGameEditable>()).ToList();
}
}
private void ReadPropertyChange(IReadMessage msg, bool inGameEditableOnly, Client sender = null)
{
var allProperties = inGameEditableOnly ? GetInGameEditableProperties() : GetProperties<Editable>();
//ignoreConditions: true = include all ConditionallyEditable properties at this point,
//to ensure client/server doesn't get any properties mixed up if there's some conditions that can vary between the server and the clients
var allProperties = inGameEditableOnly ? GetInGameEditableProperties(ignoreConditions: true) : GetProperties<Editable>();
if (allProperties.Count == 0) { return; }
int propertyIndex = 0;
@@ -2686,9 +2697,12 @@ namespace Barotrauma
if (!ic.AllowInGameEditing) { allowEditing = false; }
}
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer && !CanClientAccess(sender))
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
{
allowEditing = false;
if (!CanClientAccess(sender) || !(property.GetAttribute<ConditionallyEditable>()?.IsEditable(this) ?? true))
{
allowEditing = false;
}
}
Type type = property.PropertyType;
@@ -861,13 +861,13 @@ namespace Barotrauma
System.Diagnostics.Debug.Assert(this != wayPoint2);
if (!linkedTo.Contains(wayPoint2))
{
linkedTo.Add(wayPoint2);
OnLinksChanged?.Invoke(this);
linkedTo.Add(wayPoint2);
}
if (!wayPoint2.linkedTo.Contains(this))
{
wayPoint2.linkedTo.Add(this);
wayPoint2.OnLinksChanged?.Invoke(wayPoint2);
wayPoint2.linkedTo.Add(this);
}
}
@@ -1105,7 +1105,6 @@ namespace Barotrauma
Ladders = null;
OnLinksChanged = null;
WayPointList.Remove(this);
}
}
}
}
@@ -9,7 +9,7 @@ namespace Barotrauma.Networking
static class NetIdUtils
{
/// <summary>
/// Is newID more recent than oldID
/// Is newID more recent than oldID, i.e. newId > oldId accounting for ushort rollover
/// </summary>
public static bool IdMoreRecent(ushort newID, ushort oldID)
{
@@ -22,6 +22,12 @@ namespace Barotrauma.Networking
(id2 > id1) && (id2 - id1 > ushort.MaxValue / 2);
}
/// <summary>
/// newId >= oldId accounting for ushort rollover (newer or equals)
/// </summary>
public static bool IdMoreRecentOrMatches(ushort newId, ushort oldId)
=> !IdMoreRecent(oldId, newId);
public static ushort Difference(ushort id1, ushort id2)
{
int diff = id2 > id1 ? id2 - id1 : id1 - id2;
@@ -1067,6 +1067,7 @@ namespace Barotrauma.Networking
}
#if SERVER
MultiPlayerCampaign.UpdateCampaignSubs();
SelectNonHiddenSubmarine();
#endif
}