Build 0.20.4.0
This commit is contained in:
@@ -1836,8 +1836,7 @@ namespace Barotrauma
|
||||
float GetTargetMaxSpeed() => Character.ApplyTemporarySpeedLimits(Character.AnimController.CurrentSwimParams.MovementSpeed * 0.3f);
|
||||
}
|
||||
}
|
||||
|
||||
if (AttackLimb is Limb attackLimb && attackLimb.attack.Ranged)
|
||||
if (selectedTargetingParams.AttackPattern == AttackPattern.Straight && AttackLimb is Limb attackLimb && attackLimb.attack.Ranged)
|
||||
{
|
||||
bool advance = !canAttack && Character.InWater || distance > attackLimb.attack.Range * 0.9f;
|
||||
bool fallBack = canAttack && distance < Math.Min(250, attackLimb.attack.Range * 0.25f);
|
||||
@@ -1881,19 +1880,11 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
IDamageable damageTarget = wallTarget != null ? wallTarget.Structure : SelectedAiTarget?.Entity as IDamageable;
|
||||
if (AttackLimb?.attack is Attack { Ranged: true} attack)
|
||||
Entity targetEntity = wallTarget?.Structure ?? SelectedAiTarget?.Entity;
|
||||
IDamageable damageTarget = targetEntity as IDamageable;
|
||||
if (AttackLimb?.attack is Attack { Ranged: true} attack && targetEntity != null)
|
||||
{
|
||||
Limb limb = GetLimbToRotate(attack);
|
||||
if (limb != null)
|
||||
{
|
||||
Vector2 toTarget = damageTarget.WorldPosition - limb.WorldPosition;
|
||||
float offset = limb.Params.GetSpriteOrientation() - MathHelper.PiOver2;
|
||||
limb.body.SuppressSmoothRotationCalls = false;
|
||||
float angle = MathUtils.VectorToAngle(toTarget);
|
||||
limb.body.SmoothRotate(angle + offset, attack.AimRotationTorque);
|
||||
limb.body.SuppressSmoothRotationCalls = true;
|
||||
}
|
||||
AimRangedAttack(attack, targetEntity);
|
||||
}
|
||||
if (canAttack)
|
||||
{
|
||||
@@ -1908,6 +1899,22 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public void AimRangedAttack(Attack attack, Entity targetEntity)
|
||||
{
|
||||
if (attack == null || attack.Ranged == false || targetEntity == null) { return; }
|
||||
Character.SetInput(InputType.Aim, false, true);
|
||||
Limb limb = GetLimbToRotate(attack);
|
||||
if (limb != null)
|
||||
{
|
||||
Vector2 toTarget = targetEntity.WorldPosition - limb.WorldPosition;
|
||||
float offset = limb.Params.GetSpriteOrientation() - MathHelper.PiOver2;
|
||||
limb.body.SuppressSmoothRotationCalls = false;
|
||||
float angle = MathUtils.VectorToAngle(toTarget);
|
||||
limb.body.SmoothRotate(angle + offset, attack.AimRotationTorque);
|
||||
limb.body.SuppressSmoothRotationCalls = true;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsValidAttack(Limb attackingLimb, IEnumerable<AttackContext> currentContexts, Entity target)
|
||||
{
|
||||
if (attackingLimb == null) { return false; }
|
||||
@@ -2190,11 +2197,11 @@ namespace Barotrauma
|
||||
if (!ActiveAttack.IsRunning)
|
||||
{
|
||||
#if SERVER
|
||||
GameMain.NetworkMember.CreateEntityEvent(Character, new Character.SetAttackTargetEventData(
|
||||
AttackLimb,
|
||||
damageTarget,
|
||||
targetLimb,
|
||||
SimPosition));
|
||||
GameMain.NetworkMember.CreateEntityEvent(Character, new Character.SetAttackTargetEventData(
|
||||
AttackLimb,
|
||||
damageTarget,
|
||||
targetLimb,
|
||||
SimPosition));
|
||||
#else
|
||||
Character.PlaySound(CharacterSound.SoundType.Attack, maxInterval: 3);
|
||||
#endif
|
||||
@@ -3599,7 +3606,10 @@ namespace Barotrauma
|
||||
{
|
||||
// We only want to check the visibility when the target is in ruins/wreck/similiar place where sneaking should be possible.
|
||||
// When the monsters attack the player sub, they wall hack so that they can be more aggressive.
|
||||
checkVisibility = target.Entity.Submarine != null && target.Entity.Submarine == Character.Submarine && target.Entity.Submarine.TeamID == CharacterTeamType.None;
|
||||
// Pets should always check the visibility, unless the pet and the target are both outside the submarine -> shouldn't target when they can't perceive (= no wall hack)
|
||||
checkVisibility =
|
||||
Character.IsPet && (Character.Submarine == null) != (target.Entity.Submarine == null) ||
|
||||
target.Entity.Submarine != null && target.Entity.Submarine == Character.Submarine && target.Entity.Submarine.TeamID == CharacterTeamType.None;
|
||||
}
|
||||
if (dist > 0)
|
||||
{
|
||||
|
||||
@@ -395,6 +395,10 @@ namespace Barotrauma
|
||||
}
|
||||
objectiveManager.UpdateObjectives(deltaTime);
|
||||
|
||||
if (reportProblemsTimer > 0)
|
||||
{
|
||||
reportProblemsTimer -= deltaTime;
|
||||
}
|
||||
if (reactTimer > 0.0f)
|
||||
{
|
||||
reactTimer -= deltaTime;
|
||||
@@ -407,7 +411,6 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
Character.UpdateTeam();
|
||||
|
||||
if (Character.CurrentHull != null)
|
||||
{
|
||||
if (Character.IsOnPlayerTeam)
|
||||
@@ -425,19 +428,15 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Character.SpeechImpediment < 100.0f)
|
||||
if (reportProblemsTimer <= 0.0f)
|
||||
{
|
||||
reportProblemsTimer -= deltaTime;
|
||||
if (reportProblemsTimer <= 0.0f)
|
||||
if (Character.Submarine != null && (Character.Submarine.TeamID == Character.TeamID || Character.IsEscorted) && !Character.Submarine.Info.IsWreck)
|
||||
{
|
||||
if (Character.Submarine != null && (Character.Submarine.TeamID == Character.TeamID || Character.IsEscorted) && !Character.Submarine.Info.IsWreck)
|
||||
{
|
||||
ReportProblems();
|
||||
}
|
||||
reportProblemsTimer = reportProblemsInterval;
|
||||
ReportProblems();
|
||||
}
|
||||
UpdateSpeaking();
|
||||
reportProblemsTimer = reportProblemsInterval;
|
||||
}
|
||||
UpdateSpeaking();
|
||||
UnequipUnnecessaryItems();
|
||||
reactTimer = GetReactionTime();
|
||||
}
|
||||
@@ -912,7 +911,7 @@ namespace Barotrauma
|
||||
{
|
||||
Order newOrder = null;
|
||||
Hull targetHull = null;
|
||||
bool speak = true;
|
||||
bool speak = Character.SpeechImpediment < 100;
|
||||
if (Character.CurrentHull != null)
|
||||
{
|
||||
bool isFighting = ObjectiveManager.HasActiveObjective<AIObjectiveCombat>();
|
||||
@@ -1063,17 +1062,15 @@ namespace Barotrauma
|
||||
private void UpdateSpeaking()
|
||||
{
|
||||
if (!Character.IsOnPlayerTeam) { return; }
|
||||
|
||||
if (Character.SpeechImpediment >= 100) { return; }
|
||||
if (Character.Oxygen < 20.0f)
|
||||
{
|
||||
Character.Speak(TextManager.Get("DialogLowOxygen").Value, null, Rand.Range(0.5f, 5.0f), "lowoxygen".ToIdentifier(), 30.0f);
|
||||
}
|
||||
|
||||
if (Character.Bleeding > 2.0f)
|
||||
{
|
||||
Character.Speak(TextManager.Get("DialogBleeding").Value, null, Rand.Range(0.5f, 5.0f), "bleeding".ToIdentifier(), 30.0f);
|
||||
}
|
||||
|
||||
if (Character.PressureTimer > 50.0f && Character.CurrentHull?.DisplayName != null)
|
||||
{
|
||||
Character.Speak(TextManager.GetWithVariable("DialogPressure", "[roomname]", Character.CurrentHull.DisplayName, FormatCapitals.Yes).Value, null, Rand.Range(0.5f, 5.0f), "pressure".ToIdentifier(), 30.0f);
|
||||
@@ -1860,6 +1857,7 @@ namespace Barotrauma
|
||||
bool targetAdded = false;
|
||||
DoForEachCrewMember(caller, humanAI =>
|
||||
{
|
||||
if (caller != humanAI.Character && caller.SpeechImpediment >= 100) { return; }
|
||||
var objective = humanAI.ObjectiveManager.GetObjective<T1>();
|
||||
if (objective != null)
|
||||
{
|
||||
|
||||
+3
-1
@@ -502,10 +502,12 @@ namespace Barotrauma
|
||||
|
||||
public static IEnumerable<Affliction> GetTreatableAfflictions(Character character)
|
||||
{
|
||||
foreach (Affliction affliction in character.CharacterHealth.GetAllAfflictions())
|
||||
var allAfflictions = character.CharacterHealth.GetAllAfflictions();
|
||||
foreach (Affliction affliction in allAfflictions)
|
||||
{
|
||||
if (affliction.Prefab.IsBuff || affliction.Strength < affliction.Prefab.TreatmentThreshold) { continue; }
|
||||
if (!affliction.Prefab.TreatmentSuitability.Any(kvp => kvp.Value > 0)) { continue; }
|
||||
if (allAfflictions.Any(otherAffliction => affliction.Prefab.IgnoreTreatmentIfAfflictedBy.Contains(otherAffliction.Identifier))) { continue; }
|
||||
yield return affliction;
|
||||
}
|
||||
}
|
||||
|
||||
-2
@@ -1836,8 +1836,6 @@ namespace Barotrauma
|
||||
{
|
||||
heldItem.FlipX(relativeToSub: false);
|
||||
}
|
||||
// TODO: was this added by a mistake?
|
||||
//heldItem.FlipX(relativeToSub: false);
|
||||
}
|
||||
|
||||
foreach (Limb limb in Limbs)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Items.Components;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -530,7 +531,7 @@ namespace Barotrauma
|
||||
effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
||||
{
|
||||
targets.Clear();
|
||||
targets.AddRange(effect.GetNearbyTargets(worldPosition, targets));
|
||||
effect.AddNearbyTargets(worldPosition, targets);
|
||||
effect.Apply(effectType, deltaTime, targetEntity, targets);
|
||||
}
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.UseTarget))
|
||||
@@ -569,7 +570,15 @@ namespace Barotrauma
|
||||
|
||||
DamageParticles(deltaTime, worldPosition);
|
||||
|
||||
var attackResult = targetLimb.character.ApplyAttack(attacker, worldPosition, this, deltaTime, playSound, targetLimb, penetration: Penetration);
|
||||
float penetration = Penetration;
|
||||
|
||||
float? penetrationValue = SourceItem?.GetComponent<RangedWeapon>()?.Penetration;
|
||||
if (penetrationValue.HasValue)
|
||||
{
|
||||
penetration += penetrationValue.Value;
|
||||
}
|
||||
|
||||
var attackResult = targetLimb.character.ApplyAttack(attacker, worldPosition, this, deltaTime, playSound, targetLimb, penetration);
|
||||
var effectType = attackResult.Damage > 0.0f ? ActionType.OnUse : ActionType.OnFailure;
|
||||
|
||||
foreach (StatusEffect effect in statusEffects)
|
||||
@@ -599,7 +608,7 @@ namespace Barotrauma
|
||||
effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
||||
{
|
||||
targets.Clear();
|
||||
targets.AddRange(effect.GetNearbyTargets(worldPosition, targets));
|
||||
effect.AddNearbyTargets(worldPosition, targets);
|
||||
effect.Apply(effectType, deltaTime, targetLimb.character, targets);
|
||||
}
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.UseTarget))
|
||||
|
||||
@@ -1601,6 +1601,15 @@ namespace Barotrauma
|
||||
if (Info?.Job == null) { return 0.0f; }
|
||||
float skillLevel = Info.Job.GetSkillLevel(skillIdentifier);
|
||||
|
||||
if (overrideStatTypes.TryGetValue(skillIdentifier, out StatTypes statType))
|
||||
{
|
||||
float skillOverride = GetStatValue(statType);
|
||||
if (skillOverride > skillLevel)
|
||||
{
|
||||
skillLevel = skillOverride;
|
||||
}
|
||||
}
|
||||
|
||||
// apply multipliers first so that multipliers only affect base skill value
|
||||
foreach (Affliction affliction in CharacterHealth.GetAllAfflictions())
|
||||
{
|
||||
@@ -1631,15 +1640,6 @@ namespace Barotrauma
|
||||
|
||||
skillLevel += GetStatValue(GetSkillStatType(skillIdentifier));
|
||||
|
||||
if (overrideStatTypes.TryGetValue(skillIdentifier, out StatTypes statType))
|
||||
{
|
||||
float skillOverride = GetStatValue(statType);
|
||||
if (skillOverride > skillLevel)
|
||||
{
|
||||
skillLevel = skillOverride;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return skillLevel;
|
||||
}
|
||||
@@ -1972,6 +1972,15 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && Controlled != this && IsKeyDown(InputType.Aim))
|
||||
{
|
||||
if (currentAttackTarget.AttackLimb?.attack is Attack { Ranged: true } attack && AIController is EnemyAIController enemyAi)
|
||||
{
|
||||
enemyAi.AimRangedAttack(attack, currentAttackTarget.DamageTarget as Entity);
|
||||
}
|
||||
}
|
||||
|
||||
if (attackCoolDown > 0.0f)
|
||||
{
|
||||
attackCoolDown -= deltaTime;
|
||||
@@ -1982,7 +1991,7 @@ namespace Barotrauma
|
||||
{
|
||||
if ((currentAttackTarget.DamageTarget as Entity)?.Removed ?? false)
|
||||
{
|
||||
currentAttackTarget = default(AttackTargetData);
|
||||
currentAttackTarget = default;
|
||||
}
|
||||
currentAttackTarget.AttackLimb?.UpdateAttack(deltaTime, currentAttackTarget.AttackPos, currentAttackTarget.DamageTarget, out _);
|
||||
}
|
||||
@@ -2077,19 +2086,22 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
bool CanUseItemsWhenSelected(Item item) => item == null || !item.Prefab.DisableItemUsageWhenSelected;
|
||||
if (CanUseItemsWhenSelected(SelectedItem) && CanUseItemsWhenSelected(SelectedSecondaryItem))
|
||||
if (Inventory != null)
|
||||
{
|
||||
foreach (Item item in HeldItems)
|
||||
bool CanUseItemsWhenSelected(Item item) => item == null || !item.Prefab.DisableItemUsageWhenSelected;
|
||||
if (CanUseItemsWhenSelected(SelectedItem) && CanUseItemsWhenSelected(SelectedSecondaryItem))
|
||||
{
|
||||
tryUseItem(item, deltaTime);
|
||||
}
|
||||
foreach (Item item in Inventory.AllItems)
|
||||
{
|
||||
if (item.GetComponent<Wearable>() is { AllowUseWhenWorn: true } && HasEquippedItem(item))
|
||||
foreach (Item item in HeldItems)
|
||||
{
|
||||
tryUseItem(item, deltaTime);
|
||||
}
|
||||
foreach (Item item in Inventory.AllItems)
|
||||
{
|
||||
if (item.GetComponent<Wearable>() is { AllowUseWhenWorn: true } && HasEquippedItem(item))
|
||||
{
|
||||
tryUseItem(item, deltaTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2170,6 +2182,8 @@ namespace Barotrauma
|
||||
private AttackTargetData currentAttackTarget;
|
||||
public void SetAttackTarget(Limb attackLimb, IDamageable damageTarget, Vector2 attackPos)
|
||||
{
|
||||
DebugConsole.NewMessage($"SetAttackTarget {this.ToString()}: " + (damageTarget?.ToString() ?? null));
|
||||
|
||||
currentAttackTarget = new AttackTargetData()
|
||||
{
|
||||
AttackLimb = attackLimb,
|
||||
@@ -3824,7 +3838,7 @@ namespace Barotrauma
|
||||
return attackResult;
|
||||
}
|
||||
|
||||
public void TrySeverLimbJoints(Limb targetLimb, float severLimbsProbability, float damage, bool allowBeheading, Character attacker = null)
|
||||
public void TrySeverLimbJoints(Limb targetLimb, float severLimbsProbability, float damage, bool allowBeheading, bool ignoreSeveranceProbabilityModifier = false, Character attacker = null)
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
#if DEBUG
|
||||
@@ -3852,7 +3866,7 @@ namespace Barotrauma
|
||||
var referenceLimb = targetLimb.type == LimbType.Head && targetLimb.Params.ID == 0 ? joint.LimbA : joint.LimbB;
|
||||
if (referenceLimb != targetLimb) { continue; }
|
||||
float probability = severLimbsProbability;
|
||||
if (!IsDead && probability < 1)
|
||||
if (!IsDead && !ignoreSeveranceProbabilityModifier)
|
||||
{
|
||||
probability *= joint.Params.SeveranceProbabilityModifier;
|
||||
}
|
||||
@@ -4111,7 +4125,7 @@ namespace Barotrauma
|
||||
statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
||||
{
|
||||
targets.Clear();
|
||||
targets.AddRange(statusEffect.GetNearbyTargets(WorldPosition, targets));
|
||||
statusEffect.AddNearbyTargets(WorldPosition, targets);
|
||||
statusEffect.Apply(actionType, deltaTime, this, targets);
|
||||
}
|
||||
else if (statusEffect.targetLimbs != null)
|
||||
@@ -4745,6 +4759,8 @@ namespace Barotrauma
|
||||
|
||||
public bool HasJob(string identifier) => Info?.Job?.Prefab.Identifier == identifier;
|
||||
|
||||
public bool HasJob(Identifier identifier) => Info?.Job?.Prefab.Identifier == identifier;
|
||||
|
||||
public bool IsProtectedFromPressure()
|
||||
{
|
||||
return HasAbilityFlag(AbilityFlags.ImmuneToPressure) || PressureProtection >= (Level.Loaded?.GetRealWorldDepth(WorldPosition.Y) ?? 1.0f);
|
||||
@@ -4975,7 +4991,7 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
private readonly Dictionary<StatTypes, float> wearableStatValues = new Dictionary<StatTypes, float>();
|
||||
|
||||
public float GetStatValue(StatTypes statType)
|
||||
public float GetStatValue(StatTypes statType, bool includeSaved = true)
|
||||
{
|
||||
if (!IsHuman) { return 0f; }
|
||||
|
||||
@@ -4988,7 +5004,7 @@ namespace Barotrauma
|
||||
{
|
||||
statValue += CharacterHealth.GetStatValue(statType);
|
||||
}
|
||||
if (Info != null)
|
||||
if (Info != null && includeSaved)
|
||||
{
|
||||
// could be optimized by instead updating the Character.cs statvalues dictionary whenever the CharacterInfo.cs values change
|
||||
statValue += Info.GetSavedStatValue(statType);
|
||||
|
||||
@@ -1233,10 +1233,6 @@ namespace Barotrauma
|
||||
int prevAmount = ExperiencePoints;
|
||||
|
||||
var experienceGainMultiplier = new AbilityExperienceGainMultiplier(1f);
|
||||
if (isMissionExperience)
|
||||
{
|
||||
Character?.CheckTalents(AbilityEffectType.OnGainMissionExperience, experienceGainMultiplier);
|
||||
}
|
||||
experienceGainMultiplier.Value += Character?.GetStatValue(StatTypes.ExperienceGainMultiplier) ?? 0;
|
||||
|
||||
amount = (int)(amount * experienceGainMultiplier.Value);
|
||||
@@ -1808,7 +1804,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (SavedStatValues.TryGetValue(statType, out var statValues))
|
||||
{
|
||||
return statValues.Where(s => s.StatIdentifier == statIdentifier).Sum(v => v.StatValue);
|
||||
return statValues.Where(value => ToolBox.StatIdentifierMatches(value.StatIdentifier, statIdentifier)).Sum(static v => v.StatValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
+1
-1
@@ -429,7 +429,7 @@ namespace Barotrauma
|
||||
statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
||||
{
|
||||
targets.Clear();
|
||||
targets.AddRange(statusEffect.GetNearbyTargets(characterHealth.Character.WorldPosition, targets));
|
||||
statusEffect.AddNearbyTargets(characterHealth.Character.WorldPosition, targets);
|
||||
statusEffect.Apply(type, deltaTime, characterHealth.Character, targets);
|
||||
}
|
||||
}
|
||||
|
||||
+99
-19
@@ -5,6 +5,7 @@ using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -214,7 +215,6 @@ namespace Barotrauma
|
||||
[Serialize("", IsPropertySaveable.No)]
|
||||
public Identifier DialogFlag { get; private set; }
|
||||
|
||||
|
||||
[Serialize("", IsPropertySaveable.No)]
|
||||
public Identifier Tag { get; private set; }
|
||||
|
||||
@@ -276,6 +276,47 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public class Description
|
||||
{
|
||||
public enum TargetType
|
||||
{
|
||||
Any,
|
||||
Self,
|
||||
OtherCharacter
|
||||
}
|
||||
|
||||
public readonly LocalizedString Text;
|
||||
public readonly Identifier TextTag;
|
||||
public readonly float MinStrength, MaxStrength;
|
||||
public readonly TargetType Target;
|
||||
|
||||
public Description(ContentXElement element, AfflictionPrefab affliction)
|
||||
{
|
||||
TextTag = element.GetAttributeIdentifier("textidentifier", Identifier.Empty);
|
||||
if (!TextTag.IsEmpty)
|
||||
{
|
||||
Text = TextManager.Get(TextTag);
|
||||
}
|
||||
string text = element.GetAttributeString("text", string.Empty);
|
||||
if (!text.IsNullOrEmpty())
|
||||
{
|
||||
Text = Text?.Fallback(text) ?? text;
|
||||
}
|
||||
else if (TextTag.IsEmpty)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in affliction \"{affliction.Identifier}\" - no text defined for one of the descriptions.");
|
||||
}
|
||||
|
||||
MinStrength = element.GetAttributeFloat(nameof(MinStrength), 0.0f);
|
||||
MaxStrength = element.GetAttributeFloat(nameof(MaxStrength), 100.0f);
|
||||
if (MinStrength >= MaxStrength)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in affliction \"{affliction.Identifier}\" - max strength is not larger than min.");
|
||||
}
|
||||
Target = element.GetAttributeEnum(nameof(Target), TargetType.Any);
|
||||
}
|
||||
}
|
||||
|
||||
public class PeriodicEffect
|
||||
{
|
||||
public readonly List<StatusEffect> StatusEffects = new List<StatusEffect>();
|
||||
@@ -313,7 +354,6 @@ namespace Barotrauma
|
||||
|
||||
public static readonly PrefabCollection<AfflictionPrefab> Prefabs = new PrefabCollection<AfflictionPrefab>();
|
||||
|
||||
private bool disposed = false;
|
||||
public override void Dispose() { }
|
||||
|
||||
public static IEnumerable<AfflictionPrefab> List => Prefabs;
|
||||
@@ -330,15 +370,21 @@ namespace Barotrauma
|
||||
//(e.g. mental health problems on head, lack of oxygen on torso...)
|
||||
public readonly LimbType IndicatorLimb;
|
||||
|
||||
public readonly LocalizedString Name, Description;
|
||||
public readonly LocalizedString Name;
|
||||
public readonly Identifier TranslationIdentifier;
|
||||
public readonly bool IsBuff;
|
||||
public readonly bool AffectMachines;
|
||||
public readonly bool HealableInMedicalClinic;
|
||||
public readonly float HealCostMultiplier;
|
||||
public readonly int BaseHealCost;
|
||||
|
||||
public readonly LocalizedString CauseOfDeathDescription, SelfCauseOfDeathDescription;
|
||||
|
||||
private readonly LocalizedString defaultDescription;
|
||||
public readonly ImmutableList<Description> Descriptions;
|
||||
|
||||
public readonly bool HideIconAfterDelay;
|
||||
|
||||
//how high the strength has to be for the affliction to take affect
|
||||
public readonly float ActivationThreshold = 0.0f;
|
||||
//how high the strength has to be for the affliction icon to be shown in the UI
|
||||
@@ -355,6 +401,11 @@ namespace Barotrauma
|
||||
//how strong the affliction needs to be before bots attempt to treat it
|
||||
public readonly float TreatmentThreshold = 5.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Bots will not try to treat the affliction if the character has any of these afflictions
|
||||
/// </summary>
|
||||
public ImmutableHashSet<Identifier> IgnoreTreatmentIfAfflictedBy;
|
||||
|
||||
/// <summary>
|
||||
/// The affliction is automatically removed after this time. 0 = unlimited
|
||||
/// </summary>
|
||||
@@ -413,13 +464,14 @@ namespace Barotrauma
|
||||
{
|
||||
Name = Name.Fallback(fallbackName);
|
||||
}
|
||||
Description = TextManager.Get($"AfflictionDescription.{TranslationIdentifier}");
|
||||
defaultDescription = TextManager.Get($"AfflictionDescription.{TranslationIdentifier}");
|
||||
string fallbackDescription = element.GetAttributeString("description", "");
|
||||
if (!string.IsNullOrEmpty(fallbackDescription))
|
||||
{
|
||||
Description = Description.Fallback(fallbackDescription);
|
||||
defaultDescription = defaultDescription.Fallback(fallbackDescription);
|
||||
}
|
||||
IsBuff = element.GetAttributeBool("isbuff", false);
|
||||
IsBuff = element.GetAttributeBool(nameof(IsBuff), false);
|
||||
AffectMachines = element.GetAttributeBool(nameof(AffectMachines), true);
|
||||
|
||||
HealableInMedicalClinic = element.GetAttributeBool("healableinmedicalclinic",
|
||||
!IsBuff &&
|
||||
@@ -428,6 +480,8 @@ namespace Barotrauma
|
||||
HealCostMultiplier = element.GetAttributeFloat(nameof(HealCostMultiplier), 1f);
|
||||
BaseHealCost = element.GetAttributeInt(nameof(BaseHealCost), 0);
|
||||
|
||||
IgnoreTreatmentIfAfflictedBy = element.GetAttributeIdentifierArray(nameof(IgnoreTreatmentIfAfflictedBy), Array.Empty<Identifier>()).ToImmutableHashSet();
|
||||
|
||||
Duration = element.GetAttributeFloat(nameof(Duration), 0.0f);
|
||||
|
||||
if (element.GetAttribute("nameidentifier") != null)
|
||||
@@ -445,30 +499,33 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
ActivationThreshold = element.GetAttributeFloat("activationthreshold", 0.0f);
|
||||
ShowIconThreshold = element.GetAttributeFloat("showiconthreshold", Math.Max(ActivationThreshold, 0.05f));
|
||||
ShowIconToOthersThreshold = element.GetAttributeFloat("showicontoothersthreshold", ShowIconThreshold);
|
||||
MaxStrength = element.GetAttributeFloat("maxstrength", 100.0f);
|
||||
GrainBurst = element.GetAttributeFloat(nameof(GrainBurst).ToLowerInvariant(), 0.0f);
|
||||
HideIconAfterDelay = element.GetAttributeBool(nameof(HideIconAfterDelay), false);
|
||||
|
||||
ShowInHealthScannerThreshold = element.GetAttributeFloat("showinhealthscannerthreshold",
|
||||
ActivationThreshold = element.GetAttributeFloat(nameof(ActivationThreshold), 0.0f);
|
||||
ShowIconThreshold = element.GetAttributeFloat(nameof(ShowIconThreshold), Math.Max(ActivationThreshold, 0.05f));
|
||||
ShowIconToOthersThreshold = element.GetAttributeFloat(nameof(ShowIconToOthersThreshold), ShowIconThreshold);
|
||||
MaxStrength = element.GetAttributeFloat(nameof(MaxStrength), 100.0f);
|
||||
GrainBurst = element.GetAttributeFloat(nameof(GrainBurst), 0.0f);
|
||||
|
||||
ShowInHealthScannerThreshold = element.GetAttributeFloat(nameof(ShowInHealthScannerThreshold),
|
||||
Math.Max(ActivationThreshold, AfflictionType == "talentbuff" ? float.MaxValue : ShowIconToOthersThreshold));
|
||||
TreatmentThreshold = element.GetAttributeFloat("treatmentthreshold", Math.Max(ActivationThreshold, 5.0f));
|
||||
TreatmentThreshold = element.GetAttributeFloat(nameof(TreatmentThreshold), Math.Max(ActivationThreshold, 5.0f));
|
||||
|
||||
DamageOverlayAlpha = element.GetAttributeFloat("damageoverlayalpha", 0.0f);
|
||||
BurnOverlayAlpha = element.GetAttributeFloat("burnoverlayalpha", 0.0f);
|
||||
DamageOverlayAlpha = element.GetAttributeFloat(nameof(DamageOverlayAlpha), 0.0f);
|
||||
BurnOverlayAlpha = element.GetAttributeFloat(nameof(BurnOverlayAlpha), 0.0f);
|
||||
|
||||
KarmaChangeOnApplied = element.GetAttributeFloat("karmachangeonapplied", 0.0f);
|
||||
KarmaChangeOnApplied = element.GetAttributeFloat(nameof(KarmaChangeOnApplied), 0.0f);
|
||||
|
||||
CauseOfDeathDescription = TextManager.Get($"AfflictionCauseOfDeath.{TranslationIdentifier}").Fallback(element.GetAttributeString("causeofdeathdescription", ""));
|
||||
SelfCauseOfDeathDescription = TextManager.Get($"AfflictionCauseOfDeathSelf.{TranslationIdentifier}").Fallback(element.GetAttributeString("selfcauseofdeathdescription", ""));
|
||||
|
||||
IconColors = element.GetAttributeColorArray("iconcolors", null);
|
||||
AfflictionOverlayAlphaIsLinear = element.GetAttributeBool("afflictionoverlayalphaislinear", false);
|
||||
AchievementOnRemoved = element.GetAttributeIdentifier("achievementonremoved", "");
|
||||
IconColors = element.GetAttributeColorArray(nameof(IconColors), null);
|
||||
AfflictionOverlayAlphaIsLinear = element.GetAttributeBool(nameof(AfflictionOverlayAlphaIsLinear), false);
|
||||
AchievementOnRemoved = element.GetAttributeIdentifier(nameof(AchievementOnRemoved), "");
|
||||
|
||||
ResetBetweenRounds = element.GetAttributeBool("resetbetweenrounds", false);
|
||||
|
||||
List<Description> descriptions = new List<Description>();
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
@@ -485,15 +542,38 @@ namespace Barotrauma
|
||||
case "effect":
|
||||
case "periodiceffect":
|
||||
break;
|
||||
case "description":
|
||||
descriptions.Add(new Description(subElement, this));
|
||||
break;
|
||||
default:
|
||||
DebugConsole.AddWarning($"Unrecognized element in affliction \"{Identifier}\" ({subElement.Name})");
|
||||
break;
|
||||
}
|
||||
}
|
||||
Descriptions = descriptions.ToImmutableList();
|
||||
|
||||
constructor = type.GetConstructor(new[] { typeof(AfflictionPrefab), typeof(float) });
|
||||
}
|
||||
|
||||
public LocalizedString GetDescription(float strength, Description.TargetType targetType)
|
||||
{
|
||||
foreach (var description in Descriptions)
|
||||
{
|
||||
if (strength < description.MinStrength || strength > description.MaxStrength) { continue; }
|
||||
switch (targetType)
|
||||
{
|
||||
case Description.TargetType.Self:
|
||||
if (description.Target == Description.TargetType.OtherCharacter) { continue; }
|
||||
break;
|
||||
case Description.TargetType.OtherCharacter:
|
||||
if (description.Target == Description.TargetType.Self) { continue; }
|
||||
break;
|
||||
}
|
||||
return description.Text;
|
||||
}
|
||||
return defaultDescription;
|
||||
}
|
||||
|
||||
public static void LoadAllEffects()
|
||||
{
|
||||
Prefabs.ForEach(p => p.LoadEffects());
|
||||
|
||||
@@ -104,7 +104,7 @@ namespace Barotrauma
|
||||
|
||||
public bool DoesBleed
|
||||
{
|
||||
get => Character.Params.Health.DoesBleed;
|
||||
get => Character.Params.Health.DoesBleed && !Character.Params.IsMachine;
|
||||
private set => Character.Params.Health.DoesBleed = value;
|
||||
}
|
||||
|
||||
@@ -550,7 +550,7 @@ namespace Barotrauma
|
||||
amount -= reduceAmount;
|
||||
if (treatmentAction != null)
|
||||
{
|
||||
if (treatmentAction.Value == ActionType.OnUse)
|
||||
if (treatmentAction.Value == ActionType.OnUse || treatmentAction.Value == ActionType.OnSuccess)
|
||||
{
|
||||
matchingAffliction.AppliedAsSuccessfulTreatmentTime = Timing.TotalTime;
|
||||
}
|
||||
@@ -690,6 +690,7 @@ namespace Barotrauma
|
||||
|
||||
private void AddLimbAffliction(LimbHealth limbHealth, Affliction newAffliction, bool allowStacking = true)
|
||||
{
|
||||
if (Character.Params.IsMachine && !newAffliction.Prefab.AffectMachines) { return; }
|
||||
if (!DoesBleed && newAffliction is AfflictionBleeding) { return; }
|
||||
if (!Character.NeedsOxygen && newAffliction.Prefab == AfflictionPrefab.OxygenLow) { return; }
|
||||
if (Character.Params.Health.StunImmunity && newAffliction.Prefab.AfflictionType == "stun") { return; }
|
||||
@@ -1076,6 +1077,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
if (strength <= affliction.Prefab.TreatmentThreshold) { continue; }
|
||||
if (afflictions.Any(otherAffliction => affliction.Prefab.IgnoreTreatmentIfAfflictedBy.Contains(otherAffliction.Key.Identifier))) { continue; }
|
||||
|
||||
if (ignoreHiddenAfflictions)
|
||||
{
|
||||
|
||||
@@ -79,27 +79,35 @@ namespace Barotrauma
|
||||
|
||||
public ref readonly ImmutableArray<Identifier> ParsedAfflictionTypes => ref parsedAfflictionTypes;
|
||||
|
||||
public DamageModifier(XElement element, string parentDebugName)
|
||||
public DamageModifier(XElement element, string parentDebugName, bool checkErrors = true)
|
||||
{
|
||||
Deserialize(element);
|
||||
if (element.Attribute("afflictionnames") != null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in DamageModifier config (" + parentDebugName + ") - define afflictions using identifiers or types instead of names.");
|
||||
}
|
||||
foreach (var afflictionType in parsedAfflictionTypes)
|
||||
if (checkErrors)
|
||||
{
|
||||
if (!AfflictionPrefab.Prefabs.Any(p => p.AfflictionType == afflictionType))
|
||||
foreach (var afflictionType in parsedAfflictionTypes)
|
||||
{
|
||||
createWarningOrError($"Potentially invalid damage modifier in \"{parentDebugName}\". Could not find any afflictions of the type \"{afflictionType}\". Did you mean to use an affliction identifier instead?");
|
||||
}
|
||||
}
|
||||
foreach (var afflictionIdentifier in parsedAfflictionIdentifiers)
|
||||
{
|
||||
if (!AfflictionPrefab.Prefabs.ContainsKey(afflictionIdentifier))
|
||||
{
|
||||
createWarningOrError($"Potentially invalid damage modifier in \"{parentDebugName}\". Could not find any afflictions with the identifier \"{afflictionIdentifier}\". Did you mean to use an affliction type instead?");
|
||||
if (!AfflictionPrefab.Prefabs.Any(p => p.AfflictionType == afflictionType))
|
||||
{
|
||||
createWarningOrError($"Potentially invalid damage modifier in \"{parentDebugName}\". Could not find any afflictions of the type \"{afflictionType}\". Did you mean to use an affliction identifier instead?");
|
||||
}
|
||||
}
|
||||
foreach (var afflictionIdentifier in parsedAfflictionIdentifiers)
|
||||
{
|
||||
if (!AfflictionPrefab.Prefabs.ContainsKey(afflictionIdentifier))
|
||||
{
|
||||
createWarningOrError($"Potentially invalid damage modifier in \"{parentDebugName}\". Could not find any afflictions with the identifier \"{afflictionIdentifier}\". Did you mean to use an affliction type instead?");
|
||||
}
|
||||
}
|
||||
if (!parsedAfflictionTypes.Any() && !parsedAfflictionIdentifiers.Any())
|
||||
{
|
||||
createWarningOrError($"Potentially invalid damage modifier in \"{parentDebugName}\". Neither affliction types of identifiers defined.");
|
||||
}
|
||||
}
|
||||
|
||||
static void createWarningOrError(string msg)
|
||||
{
|
||||
#if DEBUG
|
||||
|
||||
@@ -1196,7 +1196,7 @@ namespace Barotrauma
|
||||
statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
||||
{
|
||||
targets.Clear();
|
||||
targets.AddRange(statusEffect.GetNearbyTargets(WorldPosition, targets));
|
||||
statusEffect.AddNearbyTargets(WorldPosition, targets);
|
||||
statusEffect.Apply(actionType, deltaTime, character, targets);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -50,6 +50,9 @@ namespace Barotrauma
|
||||
[Serialize(false, IsPropertySaveable.Yes, description: "Can the creature live without water or does it die on dry land?"), Editable]
|
||||
public bool NeedsWater { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes, description: "Is this creature an artificial creature, like robot or machine that shouldn't be affected by afflictions that affect only organic creatures? Overrides DoesBleed."), Editable]
|
||||
public bool IsMachine { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No), Editable]
|
||||
public bool CanSpeak { get; set; }
|
||||
|
||||
|
||||
+2
-1
@@ -27,7 +27,8 @@ namespace Barotrauma.Abilities
|
||||
{
|
||||
if (isPositiveReputation)
|
||||
{
|
||||
if (abilityLocation.Location.Reputation.Faction.Reputation.Value <= 0) { return false; }
|
||||
if (abilityLocation.Location?.Reputation is not { } reputation) { return false; }
|
||||
if (reputation.Value <= 0) { return false; }
|
||||
}
|
||||
|
||||
if (locationIdentifiers.Any())
|
||||
|
||||
+10
-3
@@ -33,13 +33,20 @@ namespace Barotrauma.Abilities
|
||||
{
|
||||
if (abilityObject is IAbilityMission { Mission: { } mission })
|
||||
{
|
||||
if (isAffiliated && GameMain.GameSession?.Campaign?.Factions.MaxBy(static f => f.Reputation.Value) is { } highestFaction)
|
||||
if (isAffiliated)
|
||||
{
|
||||
if (highestFaction.Reputation.Value < 0 || !mission.ReputationRewards.ContainsKey(highestFaction.Reputation.Identifier))
|
||||
if (GameMain.GameSession?.Campaign?.Factions is not { } factions) { return false; }
|
||||
|
||||
// FIXME there's probably a better way to check the faction affiliated with the mission later
|
||||
foreach (Identifier factionIdentifier in mission.ReputationRewards.Keys)
|
||||
{
|
||||
return false;
|
||||
if (factions.Where(faction => factionIdentifier == faction.Prefab.Identifier).Any(static faction => faction.GetPlayerAffiliationStatus() != FactionAffiliation.Affiliated))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return missionType.Contains(mission.Prefab.Type);
|
||||
}
|
||||
|
||||
|
||||
+2
-6
@@ -1,9 +1,5 @@
|
||||
using System;
|
||||
using Barotrauma.Items.Components;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Extensions;
|
||||
using System;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ internal sealed class AbilityConditionNearbyCharacterCount : AbilityConditionDat
|
||||
foreach (Character otherCharacter in Character.CharacterList)
|
||||
{
|
||||
if (character.Submarine != otherCharacter.Submarine) { continue; }
|
||||
if (!IsViableTarget(targetTypes, otherCharacter)) { return false; }
|
||||
if (!IsViableTarget(targetTypes, otherCharacter)) { continue; }
|
||||
|
||||
if (Vector2.DistanceSquared(character.WorldPosition, otherCharacter.WorldPosition) < distance * distance)
|
||||
{
|
||||
|
||||
+1
-1
@@ -50,7 +50,7 @@ namespace Barotrauma.Abilities
|
||||
else if (statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
||||
{
|
||||
targets.Clear();
|
||||
targets.AddRange(statusEffect.GetNearbyTargets(targetCharacter.WorldPosition, targets));
|
||||
statusEffect.AddNearbyTargets(targetCharacter.WorldPosition, targets);
|
||||
if (!nearbyCharactersAppliesToSelf)
|
||||
{
|
||||
targets.RemoveAll(c => c == Character);
|
||||
|
||||
+2
@@ -27,6 +27,8 @@ namespace Barotrauma.Abilities
|
||||
|
||||
protected override void ApplyEffect()
|
||||
{
|
||||
if (Character?.Submarine is null) { return; }
|
||||
|
||||
foreach (Item item in Character.Submarine.GetItems(true))
|
||||
{
|
||||
if (item.HasTag(tags) || tags.Contains(item.Prefab.Identifier))
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
#nullable enable
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
internal sealed class CharacterAbilityGiveTalentPointsToAllies : CharacterAbility
|
||||
{
|
||||
private readonly int amount;
|
||||
|
||||
public CharacterAbilityGiveTalentPointsToAllies(CharacterAbilityGroup characterAbilityGroup, ContentXElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
amount = abilityElement.GetAttributeInt("amount", 0);
|
||||
}
|
||||
|
||||
public override void InitializeAbility(bool addingFirstTime)
|
||||
{
|
||||
if (!addingFirstTime) { return; }
|
||||
|
||||
foreach (Character character in GameSession.GetSessionCrewCharacters(CharacterType.Both))
|
||||
{
|
||||
if (character.Info is null) { return; }
|
||||
character.Info.AdditionalTalentPoints += amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-1
@@ -1,5 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
|
||||
+5
@@ -13,6 +13,11 @@ namespace Barotrauma.Abilities
|
||||
value = abilityElement.GetAttributeInt("value", 0);
|
||||
}
|
||||
|
||||
public override void InitializeAbility(bool addingFirstTime)
|
||||
{
|
||||
ApplyEffect();
|
||||
}
|
||||
|
||||
protected override void ApplyEffect()
|
||||
{
|
||||
if (identifier == Identifier.Empty) { return; }
|
||||
|
||||
+2
@@ -12,6 +12,8 @@ namespace Barotrauma.Abilities
|
||||
|
||||
public override void InitializeAbility(bool addingFirstTime)
|
||||
{
|
||||
if (!addingFirstTime) { return; }
|
||||
|
||||
JobPrefab? apprentice = CharacterAbilityApplyStatusEffectsToApprenticeship.GetApprenticeJob(Character, JobPrefab.Prefabs.ToImmutableHashSet());
|
||||
if (apprentice is null)
|
||||
{
|
||||
|
||||
+12
-11
@@ -27,39 +27,40 @@ namespace Barotrauma.Abilities
|
||||
TimeSinceLastUpdate += deltaTime;
|
||||
if (TimeSinceLastUpdate < interval) { return; }
|
||||
|
||||
bool shouldApplyDelayedEffect;
|
||||
bool conditionsDidntMatch;
|
||||
bool conditionsMatched;
|
||||
|
||||
if (AllConditionsMatched())
|
||||
{
|
||||
effectDelayTimer += TimeSinceLastUpdate;
|
||||
shouldApplyDelayedEffect = effectDelayTimer >= effectDelay;
|
||||
conditionsDidntMatch = false;
|
||||
bool shouldApplyDelayedEffect = effectDelayTimer >= effectDelay;
|
||||
conditionsMatched = shouldApplyDelayedEffect;
|
||||
}
|
||||
else
|
||||
{
|
||||
effectDelayTimer = 0f;
|
||||
shouldApplyDelayedEffect = false;
|
||||
conditionsDidntMatch = true;
|
||||
conditionsMatched = false;
|
||||
}
|
||||
|
||||
bool hasFallbacks = fallbackAbilities.Count > 0;
|
||||
|
||||
List<CharacterAbility> abilitiesToRun =
|
||||
conditionsDidntMatch && hasFallbacks
|
||||
!conditionsMatched && hasFallbacks
|
||||
? fallbackAbilities
|
||||
: characterAbilities;
|
||||
|
||||
if (hasFallbacks)
|
||||
{
|
||||
conditionsMatched = true;
|
||||
}
|
||||
|
||||
foreach (var characterAbility in abilitiesToRun)
|
||||
{
|
||||
if (!characterAbility.IsViable()) { continue; }
|
||||
|
||||
characterAbility.UpdateCharacterAbility(
|
||||
shouldApplyDelayedEffect || conditionsDidntMatch,
|
||||
TimeSinceLastUpdate);
|
||||
characterAbility.UpdateCharacterAbility(conditionsMatched, TimeSinceLastUpdate);
|
||||
}
|
||||
|
||||
if (shouldApplyDelayedEffect || (conditionsDidntMatch && hasFallbacks))
|
||||
if (conditionsMatched)
|
||||
{
|
||||
timesTriggered++;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Barotrauma
|
||||
{
|
||||
internal sealed class TalentTree : Prefab
|
||||
{
|
||||
public enum TalentTreeStageState
|
||||
public enum TalentStages
|
||||
{
|
||||
Invalid,
|
||||
Locked,
|
||||
@@ -72,30 +72,30 @@ namespace Barotrauma
|
||||
|
||||
// i hate this function - markus
|
||||
// me too - joonas
|
||||
public static TalentTreeStageState GetTalentOptionStageState(Character character, Identifier subTreeIdentifier, int index, IReadOnlyCollection<Identifier> selectedTalents)
|
||||
public static TalentStages GetTalentOptionStageState(Character character, Identifier subTreeIdentifier, int index, IReadOnlyCollection<Identifier> selectedTalents)
|
||||
{
|
||||
if (character?.Info?.Job.Prefab is null) { return TalentTreeStageState.Invalid; }
|
||||
if (character?.Info?.Job.Prefab is null) { return TalentStages.Invalid; }
|
||||
|
||||
if (!JobTalentTrees.TryGet(character.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { return TalentTreeStageState.Invalid; }
|
||||
if (!JobTalentTrees.TryGet(character.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { return TalentStages.Invalid; }
|
||||
|
||||
TalentSubTree subTree = talentTree!.TalentSubTrees.FirstOrDefault(tst => tst.Identifier == subTreeIdentifier);
|
||||
if (subTree is null) { return TalentTreeStageState.Invalid; }
|
||||
if (subTree is null) { return TalentStages.Invalid; }
|
||||
|
||||
if (!TalentTreeMeetsRequirements(talentTree, subTree, selectedTalents))
|
||||
{
|
||||
return TalentTreeStageState.Locked;
|
||||
return TalentStages.Locked;
|
||||
}
|
||||
|
||||
TalentOption targetTalentOption = subTree.TalentOptionStages[index];
|
||||
|
||||
if (targetTalentOption.HasEnoughTalents(character.Info))
|
||||
{
|
||||
return TalentTreeStageState.Unlocked;
|
||||
return TalentStages.Unlocked;
|
||||
}
|
||||
|
||||
if (targetTalentOption.HasSelectedTalent(selectedTalents))
|
||||
{
|
||||
return TalentTreeStageState.Highlighted;
|
||||
return TalentStages.Highlighted;
|
||||
}
|
||||
|
||||
bool hasTalentInLastTier = true;
|
||||
@@ -111,17 +111,17 @@ namespace Barotrauma
|
||||
|
||||
if (!hasTalentInLastTier)
|
||||
{
|
||||
return TalentTreeStageState.Locked;
|
||||
return TalentStages.Locked;
|
||||
}
|
||||
|
||||
bool hasPointsForNewTalent = character.Info.GetTotalTalentPoints() - selectedTalents.Count > 0;
|
||||
|
||||
if (hasPointsForNewTalent)
|
||||
{
|
||||
return isLastTalentPurchased ? TalentTreeStageState.Highlighted : TalentTreeStageState.Available;
|
||||
return isLastTalentPurchased ? TalentStages.Highlighted : TalentStages.Available;
|
||||
}
|
||||
|
||||
return TalentTreeStageState.Locked;
|
||||
return TalentStages.Locked;
|
||||
}
|
||||
|
||||
|
||||
@@ -207,7 +207,7 @@ namespace Barotrauma
|
||||
{
|
||||
nameIdentifier = $"talenttree.{Identifier}";
|
||||
}
|
||||
DisplayName = TextManager.Get($"talenttree.{nameIdentifier}").Fallback(Identifier.Value);
|
||||
DisplayName = TextManager.Get(nameIdentifier).Fallback(Identifier.Value);
|
||||
Type = subTreeElement.GetAttributeEnum("type", TalentTreeType.Specialization);
|
||||
RequiredTrees = subTreeElement.GetAttributeIdentifierImmutableHashSet("requires", ImmutableHashSet<Identifier>.Empty);
|
||||
BlockedTrees = subTreeElement.GetAttributeIdentifierImmutableHashSet("blocks", ImmutableHashSet<Identifier>.Empty);
|
||||
@@ -234,7 +234,7 @@ namespace Barotrauma
|
||||
/// When specified the talent option will show talent with this identifier
|
||||
/// and clicking on it will expand the talent option to show the talents
|
||||
/// </summary>
|
||||
public readonly Option<Identifier> ShowcaseTalent;
|
||||
public readonly Dictionary<Identifier, ImmutableHashSet<Identifier>> ShowCaseTalents = new Dictionary<Identifier, ImmutableHashSet<Identifier>>();
|
||||
|
||||
public bool HasEnoughTalents(CharacterInfo character) => CountMatchingTalents(character.UnlockedTalents) >= MaxChosenTalents;
|
||||
public bool HasEnoughTalents(IReadOnlyCollection<Identifier> selectedTalents) => CountMatchingTalents(selectedTalents) >= MaxChosenTalents;
|
||||
@@ -269,19 +269,30 @@ namespace Barotrauma
|
||||
{
|
||||
MaxChosenTalents = talentOptionsElement.GetAttributeInt("maxchosentalents", 1);
|
||||
|
||||
Identifier showcaseTalent = talentOptionsElement.GetAttributeIdentifier("showcasetalent", Identifier.Empty);
|
||||
ShowcaseTalent = !showcaseTalent.IsEmpty
|
||||
? Option<Identifier>.Some(showcaseTalent)
|
||||
: Option<Identifier>.None();
|
||||
HashSet<Identifier> identifiers = new HashSet<Identifier>();
|
||||
|
||||
var talentIdentifiers = new HashSet<Identifier>();
|
||||
foreach (var talentOptionElement in talentOptionsElement.GetChildElements("talentoption"))
|
||||
foreach (ContentXElement talentOptionElement in talentOptionsElement.Elements())
|
||||
{
|
||||
Identifier identifier = talentOptionElement.GetAttributeIdentifier("identifier", Identifier.Empty);
|
||||
talentIdentifiers.Add(identifier);
|
||||
Identifier elementName = talentOptionElement.Name.ToIdentifier();
|
||||
if (elementName == "talentoption")
|
||||
{
|
||||
identifiers.Add(talentOptionElement.GetAttributeIdentifier("identifier", Identifier.Empty));
|
||||
}
|
||||
else if (elementName == "showcasetalent")
|
||||
{
|
||||
Identifier showCaseIdentifier = talentOptionElement.GetAttributeIdentifier("identifier", Identifier.Empty);
|
||||
HashSet<Identifier> showCaseTalentIdentifiers = new HashSet<Identifier>();
|
||||
foreach (ContentXElement subElement in talentOptionElement.Elements())
|
||||
{
|
||||
Identifier identifier = subElement.GetAttributeIdentifier("identifier", Identifier.Empty);
|
||||
showCaseTalentIdentifiers.Add(identifier);
|
||||
identifiers.Add(identifier);
|
||||
}
|
||||
ShowCaseTalents.Add(showCaseIdentifier, showCaseTalentIdentifiers.ToImmutableHashSet());
|
||||
}
|
||||
}
|
||||
|
||||
this.talentIdentifiers = talentIdentifiers.ToImmutableHashSet();
|
||||
talentIdentifiers = identifiers.ToImmutableHashSet();
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -18,7 +18,7 @@ namespace Barotrauma
|
||||
|
||||
public const string LocalModsDir = "LocalMods";
|
||||
public static readonly string WorkshopModsDir = Barotrauma.IO.Path.Combine(
|
||||
SaveUtil.SaveFolder,
|
||||
SaveUtil.DefaultSaveFolder,
|
||||
"WorkshopMods",
|
||||
"Installed");
|
||||
|
||||
|
||||
@@ -125,6 +125,7 @@ namespace Barotrauma
|
||||
internal int IndexOf(char c) => Value.IndexOf(c);
|
||||
|
||||
internal Identifier this[Range range] => Value[range].ToIdentifier();
|
||||
internal Char this[int i] => Value[i];
|
||||
}
|
||||
|
||||
public static class IdentifierExtensions
|
||||
|
||||
@@ -2199,7 +2199,7 @@ namespace Barotrauma
|
||||
//Dont do a thing, random is basically Human points anyways - its in the help description.
|
||||
break;
|
||||
default:
|
||||
var matchingCharacter = FindMatchingCharacter(args.Skip(1).ToArray());
|
||||
var matchingCharacter = FindMatchingCharacter(args.Skip(1).Take(1).ToArray());
|
||||
if (matchingCharacter != null){ spawnInventory = matchingCharacter.Inventory; }
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -154,6 +154,7 @@ namespace Barotrauma
|
||||
BallastFloraDamageMultiplier,
|
||||
HoldBreathMultiplier,
|
||||
Apprenticeship,
|
||||
Affiliation,
|
||||
CPRBoost
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,9 @@ namespace Barotrauma
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool RequireEquipped { get; set; }
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes)]
|
||||
public bool Recursive { get; set; }
|
||||
|
||||
[Serialize(-1, IsPropertySaveable.Yes)]
|
||||
public int ItemContainerIndex { get; set; }
|
||||
|
||||
@@ -97,7 +100,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (inventory == null) { return false; }
|
||||
int count = 0;
|
||||
foreach (Item item in inventory.FindAllItems(it => itemTags.Any(it.HasTag) || itemIdentifierSplit.Contains(it.Prefab.Identifier)))
|
||||
foreach (Item item in inventory.FindAllItems(it => itemTags.Any(it.HasTag) || itemIdentifierSplit.Contains(it.Prefab.Identifier), recursive: Recursive))
|
||||
{
|
||||
if (!ConditionalsMatch(item, character)) { continue; }
|
||||
count++;
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma;
|
||||
|
||||
class CheckMissionAction : BinaryOptionAction
|
||||
{
|
||||
public enum MissionType
|
||||
{
|
||||
Current,
|
||||
Selected,
|
||||
Available
|
||||
}
|
||||
|
||||
[Serialize(MissionType.Current, IsPropertySaveable.Yes)]
|
||||
public MissionType Type { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier MissionIdentifier { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier MissionTag { get; set; }
|
||||
|
||||
[Serialize(1, IsPropertySaveable.Yes)]
|
||||
public int MissionCount { get; set; }
|
||||
|
||||
public CheckMissionAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
|
||||
{
|
||||
MissionCount = Math.Max(MissionCount, 0);
|
||||
}
|
||||
|
||||
protected override bool? DetermineSuccess()
|
||||
{
|
||||
var missions = Type switch
|
||||
{
|
||||
MissionType.Current => GameMain.GameSession?.Missions,
|
||||
MissionType.Selected => GameMain.GameSession?.Campaign?.Missions,
|
||||
MissionType.Available => GameMain.GameSession?.Map?.CurrentLocation?.AvailableMissions,
|
||||
_ => null
|
||||
};
|
||||
if (missions is not null)
|
||||
{
|
||||
if (!MissionIdentifier.IsEmpty)
|
||||
{
|
||||
return missions.Any(m => m.Prefab.Identifier == MissionIdentifier);
|
||||
}
|
||||
else if (!MissionTag.IsEmpty)
|
||||
{
|
||||
return missions.Count(m => m.Prefab.Tags.Contains(MissionTag.Value)) >= MissionCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
return missions.Count() >= MissionCount;
|
||||
}
|
||||
}
|
||||
return MissionIdentifier.IsEmpty && MissionTag.IsEmpty && MissionCount == 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace Barotrauma;
|
||||
|
||||
partial class CheckObjectiveAction : BinaryOptionAction
|
||||
{
|
||||
public CheckObjectiveAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
|
||||
|
||||
protected override bool? DetermineSuccess()
|
||||
{
|
||||
bool success = false;
|
||||
DetermineSuccessProjSpecific(ref success);
|
||||
return success;
|
||||
}
|
||||
|
||||
partial void DetermineSuccessProjSpecific(ref bool success);
|
||||
}
|
||||
@@ -1,7 +1,15 @@
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class CheckOrderAction : BinaryOptionAction
|
||||
{
|
||||
public enum OrderPriority
|
||||
{
|
||||
Top,
|
||||
Any
|
||||
}
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier TargetTag { get; set; }
|
||||
|
||||
@@ -14,35 +22,58 @@ namespace Barotrauma
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier OrderTargetTag { get; set; }
|
||||
|
||||
[Serialize(OrderPriority.Top, IsPropertySaveable.Yes)]
|
||||
public OrderPriority Priority { get; set; }
|
||||
|
||||
public CheckOrderAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
|
||||
|
||||
protected override bool? DetermineSuccess()
|
||||
{
|
||||
Character targetCharacter = null;
|
||||
if (!TargetTag.IsEmpty)
|
||||
var targetCharacters = ParentEvent.GetTargets(TargetTag);
|
||||
if (targetCharacters.None())
|
||||
{
|
||||
foreach (var t in ParentEvent.GetTargets(TargetTag))
|
||||
{
|
||||
if (t is Character c)
|
||||
{
|
||||
targetCharacter = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (targetCharacter == null)
|
||||
{
|
||||
DebugConsole.LogError($"CheckConditionalAction error: {GetEventName()} uses a CheckOrderAction but no valid target character was found for tag \"{TargetTag}\"! This will cause the check to automatically fail.");
|
||||
DebugConsole.LogError($"CheckConditionalAction error: {GetEventName()} uses a CheckOrderAction but no valid target characters were found for tag \"{TargetTag}\"! This will cause the check to automatically fail.");
|
||||
return false;
|
||||
}
|
||||
var currentOrderInfo = targetCharacter.GetCurrentOrderWithTopPriority();
|
||||
if (currentOrderInfo?.Identifier == OrderIdentifier)
|
||||
foreach (var t in targetCharacters)
|
||||
{
|
||||
if (!OrderTargetTag.IsEmpty)
|
||||
if (t is not Character c)
|
||||
{
|
||||
if (currentOrderInfo.TargetEntity is not Item targetItem || !targetItem.HasTag(OrderTargetTag)) { return false; }
|
||||
continue;
|
||||
}
|
||||
if (Priority == OrderPriority.Top)
|
||||
{
|
||||
if (c.GetCurrentOrderWithTopPriority() is Order topPrioOrder && IsMatch(topPrioOrder))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (Priority == OrderPriority.Any)
|
||||
{
|
||||
foreach (var order in c.CurrentOrders)
|
||||
{
|
||||
if (IsMatch(order))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool IsMatch(Order order)
|
||||
{
|
||||
if (order?.Identifier == OrderIdentifier)
|
||||
{
|
||||
if (!OrderTargetTag.IsEmpty && (order.TargetEntity is not Item targetItem || !targetItem.HasTag(OrderTargetTag)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (OrderOption.IsEmpty || order?.Option == OrderOption)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return OrderOption.IsEmpty || currentOrderInfo?.Option == OrderOption;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma;
|
||||
|
||||
class CheckPurchasedItemsAction : BinaryOptionAction
|
||||
{
|
||||
public enum TransactionType
|
||||
{
|
||||
Purchased,
|
||||
Sold
|
||||
}
|
||||
|
||||
[Serialize(TransactionType.Purchased, IsPropertySaveable.Yes)]
|
||||
public TransactionType Type { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier ItemIdentifier { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier ItemTag { get; set; }
|
||||
|
||||
[Serialize(1, IsPropertySaveable.Yes)]
|
||||
public int MinCount { get; set; }
|
||||
|
||||
public CheckPurchasedItemsAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
|
||||
{
|
||||
MinCount = Math.Max(MinCount, 1);
|
||||
}
|
||||
|
||||
protected override bool? DetermineSuccess()
|
||||
{
|
||||
if (ItemIdentifier.IsEmpty && ItemTag.IsEmpty)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (GameMain.GameSession?.Campaign?.CargoManager is not CargoManager cargoManager)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (Type == TransactionType.Purchased)
|
||||
{
|
||||
int totalPurchased = 0;
|
||||
foreach ((Identifier id, var items) in cargoManager.PurchasedItems)
|
||||
{
|
||||
if (!ItemIdentifier.IsEmpty)
|
||||
{
|
||||
totalPurchased += items.Find(i => i.ItemPrefabIdentifier == ItemIdentifier)?.Quantity ?? 0;
|
||||
}
|
||||
else if (!ItemTag.IsEmpty)
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (item.ItemPrefab.Tags.Contains(ItemTag))
|
||||
{
|
||||
totalPurchased += item.Quantity;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (totalPurchased >= MinCount)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int totalSold = 0;
|
||||
foreach ((Identifier id, var items) in cargoManager.SoldItems)
|
||||
{
|
||||
if (!ItemIdentifier.IsEmpty)
|
||||
{
|
||||
totalSold += items.Count(i => i.ItemPrefab.Identifier == ItemIdentifier);
|
||||
}
|
||||
else if (!ItemTag.IsEmpty)
|
||||
{
|
||||
totalSold += items.Count(i => i.ItemPrefab.Tags.Contains(ItemTag));
|
||||
}
|
||||
if (totalSold >= MinCount)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+40
-35
@@ -59,7 +59,10 @@ namespace Barotrauma
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool ContinueConversation { get; set; }
|
||||
|
||||
public Character speaker
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool IgnoreInterruptDistance { get; set; }
|
||||
|
||||
public Character Speaker
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
@@ -124,7 +127,7 @@ namespace Barotrauma
|
||||
#else
|
||||
foreach (Client c in GameMain.Server.ConnectedClients)
|
||||
{
|
||||
if (c.InGame && c.Character != null) { ServerWrite(speaker, c, interrupt); }
|
||||
if (c.InGame && c.Character != null) { ServerWrite(Speaker, c, interrupt); }
|
||||
}
|
||||
#endif
|
||||
ResetSpeaker();
|
||||
@@ -160,7 +163,7 @@ namespace Barotrauma
|
||||
selectedOption = -1;
|
||||
interrupt = false;
|
||||
dialogOpened = false;
|
||||
speaker = null;
|
||||
Speaker = null;
|
||||
}
|
||||
|
||||
public override bool SetGoToTarget(string goTo)
|
||||
@@ -181,15 +184,14 @@ namespace Barotrauma
|
||||
|
||||
private void ResetSpeaker()
|
||||
{
|
||||
if (speaker == null) { return; }
|
||||
speaker.CampaignInteractionType = CampaignMode.InteractionType.None;
|
||||
speaker.ActiveConversation = null;
|
||||
speaker.SetCustomInteract(null, null);
|
||||
if (Speaker == null) { return; }
|
||||
Speaker.CampaignInteractionType = CampaignMode.InteractionType.None;
|
||||
Speaker.ActiveConversation = null;
|
||||
Speaker.SetCustomInteract(null, null);
|
||||
#if SERVER
|
||||
GameMain.NetworkMember.CreateEntityEvent(speaker, new Character.AssignCampaignInteractionEventData());
|
||||
GameMain.NetworkMember.CreateEntityEvent(Speaker, new Character.AssignCampaignInteractionEventData());
|
||||
#endif
|
||||
var humanAI = speaker.AIController as HumanAIController;
|
||||
if (humanAI != null && !speaker.IsDead && !speaker.Removed)
|
||||
if (Speaker.AIController is HumanAIController humanAI && !Speaker.IsDead && !Speaker.Removed)
|
||||
{
|
||||
humanAI.ClearForcedOrder();
|
||||
if (prevIdleObjective != null) { humanAI.ObjectiveManager.AddObjective(prevIdleObjective); }
|
||||
@@ -207,7 +209,6 @@ namespace Barotrauma
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
lastActiveTime = Timing.TotalTime;
|
||||
if (interrupt)
|
||||
{
|
||||
Interrupted?.Update(deltaTime);
|
||||
@@ -216,6 +217,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (dialogOpened)
|
||||
{
|
||||
lastActiveTime = Timing.TotalTime;
|
||||
#if CLIENT
|
||||
if (GUIMessageBox.MessageBoxes.Any(mb => mb.UserData as string == "ConversationAction"))
|
||||
{
|
||||
@@ -226,7 +228,7 @@ namespace Barotrauma
|
||||
Reset();
|
||||
}
|
||||
#endif
|
||||
if (ShouldInterrupt())
|
||||
if (ShouldInterrupt(requireTarget: true))
|
||||
{
|
||||
ResetSpeaker();
|
||||
interrupt = true;
|
||||
@@ -236,34 +238,34 @@ namespace Barotrauma
|
||||
|
||||
if (!SpeakerTag.IsEmpty)
|
||||
{
|
||||
if (speaker != null && !speaker.Removed && speaker.CampaignInteractionType == CampaignMode.InteractionType.Talk && speaker.ActiveConversation?.ParentEvent != this.ParentEvent) { return; }
|
||||
speaker = ParentEvent.GetTargets(SpeakerTag).FirstOrDefault(e => e is Character) as Character;
|
||||
if (speaker == null || speaker.Removed)
|
||||
if (Speaker != null && !Speaker.Removed && Speaker.CampaignInteractionType == CampaignMode.InteractionType.Talk && Speaker.ActiveConversation?.ParentEvent != this.ParentEvent) { return; }
|
||||
Speaker = ParentEvent.GetTargets(SpeakerTag).FirstOrDefault(e => e is Character) as Character;
|
||||
if (Speaker == null || Speaker.Removed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
//some conversation already assigned to the speaker, wait for it to be removed
|
||||
if (speaker.CampaignInteractionType == CampaignMode.InteractionType.Talk && speaker.ActiveConversation?.ParentEvent != this.ParentEvent)
|
||||
if (Speaker.CampaignInteractionType == CampaignMode.InteractionType.Talk && Speaker.ActiveConversation?.ParentEvent != this.ParentEvent)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else if (!WaitForInteraction)
|
||||
{
|
||||
TryStartConversation(speaker);
|
||||
TryStartConversation(Speaker);
|
||||
}
|
||||
else if (speaker.ActiveConversation != this)
|
||||
else if (Speaker.ActiveConversation != this)
|
||||
{
|
||||
speaker.CampaignInteractionType = CampaignMode.InteractionType.Talk;
|
||||
speaker.ActiveConversation = this;
|
||||
Speaker.CampaignInteractionType = CampaignMode.InteractionType.Talk;
|
||||
Speaker.ActiveConversation = this;
|
||||
#if CLIENT
|
||||
speaker.SetCustomInteract(
|
||||
Speaker.SetCustomInteract(
|
||||
TryStartConversation,
|
||||
TextManager.GetWithVariable("CampaignInteraction.Talk", "[key]", GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Use)));
|
||||
#else
|
||||
speaker.SetCustomInteract(
|
||||
Speaker.SetCustomInteract(
|
||||
TryStartConversation,
|
||||
TextManager.Get("CampaignInteraction.Talk"));
|
||||
GameMain.NetworkMember.CreateEntityEvent(speaker, new Character.AssignCampaignInteractionEventData());
|
||||
GameMain.NetworkMember.CreateEntityEvent(Speaker, new Character.AssignCampaignInteractionEventData());
|
||||
#endif
|
||||
}
|
||||
return;
|
||||
@@ -275,7 +277,9 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ShouldInterrupt())
|
||||
//after the conversation has been finished and the target character assigned,
|
||||
//we no longer care if we still have a target
|
||||
if (ShouldInterrupt(requireTarget: false))
|
||||
{
|
||||
ResetSpeaker();
|
||||
interrupt = true;
|
||||
@@ -287,35 +291,36 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShouldInterrupt()
|
||||
private bool ShouldInterrupt(bool requireTarget)
|
||||
{
|
||||
IEnumerable<Entity> targets = Enumerable.Empty<Entity>();
|
||||
if (!TargetTag.IsEmpty)
|
||||
if (!TargetTag.IsEmpty && requireTarget)
|
||||
{
|
||||
targets = ParentEvent.GetTargets(TargetTag).Where(e => IsValidTarget(e));
|
||||
targets = ParentEvent.GetTargets(TargetTag).Where(e => IsValidTarget(e, requireTarget));
|
||||
if (!targets.Any()) { return true; }
|
||||
}
|
||||
|
||||
if (speaker != null)
|
||||
if (Speaker != null)
|
||||
{
|
||||
if (!TargetTag.IsEmpty)
|
||||
if (!TargetTag.IsEmpty && requireTarget && !IgnoreInterruptDistance)
|
||||
{
|
||||
if (targets.All(t => Vector2.DistanceSquared(t.WorldPosition, speaker.WorldPosition) > InterruptDistance * InterruptDistance)) { return true; }
|
||||
if (targets.All(t => Vector2.DistanceSquared(t.WorldPosition, Speaker.WorldPosition) > InterruptDistance * InterruptDistance)) { return true; }
|
||||
}
|
||||
if (speaker.AIController is HumanAIController humanAI && !humanAI.AllowCampaignInteraction())
|
||||
if (Speaker.AIController is HumanAIController humanAI && !humanAI.AllowCampaignInteraction())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return speaker.Removed || speaker.IsDead || speaker.IsIncapacitated;
|
||||
return Speaker.Removed || Speaker.IsDead || Speaker.IsIncapacitated;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool IsValidTarget(Entity e)
|
||||
private bool IsValidTarget(Entity e, bool requirePlayerControlled = true)
|
||||
{
|
||||
bool isValid = e is Character character && !character.Removed && !character.IsDead && !character.IsIncapacitated &&
|
||||
(e == Character.Controlled || character.IsRemotePlayer);
|
||||
bool isValid =
|
||||
e is Character character && !character.Removed && !character.IsDead && !character.IsIncapacitated &&
|
||||
(character == Character.Controlled || character.IsRemotePlayer || !requirePlayerControlled);
|
||||
#if SERVER
|
||||
if (!dialogOpened)
|
||||
{
|
||||
|
||||
@@ -49,6 +49,12 @@ namespace Barotrauma
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier ObjectiveTag { get; set; }
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes)]
|
||||
public bool ObjectiveCanBeCompleted { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier ParentObjectiveId { get; set; }
|
||||
|
||||
private bool isFinished = false;
|
||||
|
||||
public MessageBoxAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
|
||||
|
||||
@@ -67,7 +67,7 @@ namespace Barotrauma
|
||||
|
||||
void ChangeItemTeam(Submarine sub, bool allowStealing)
|
||||
{
|
||||
foreach (Item item in npc.Inventory.AllItems)
|
||||
foreach (Item item in npc.Inventory.FindAllItems(recursive: true))
|
||||
{
|
||||
item.AllowStealing = allowStealing;
|
||||
if (item.GetComponent<Items.Components.WifiComponent>() is { } wifiComponent)
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using System;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -11,6 +10,7 @@ namespace Barotrauma
|
||||
{
|
||||
public enum SpawnLocationType
|
||||
{
|
||||
Any,
|
||||
MainSub,
|
||||
Outpost,
|
||||
MainPath,
|
||||
@@ -40,7 +40,7 @@ namespace Barotrauma
|
||||
[Serialize("", IsPropertySaveable.Yes, description: "Tag of an entity with an inventory to spawn the item into.")]
|
||||
public Identifier TargetInventory { get; set; }
|
||||
|
||||
[Serialize(SpawnLocationType.MainSub, IsPropertySaveable.Yes)]
|
||||
[Serialize(SpawnLocationType.Any, IsPropertySaveable.Yes)]
|
||||
public SpawnLocationType SpawnLocation { get; set; }
|
||||
|
||||
[Serialize(SpawnType.Human, IsPropertySaveable.Yes)]
|
||||
@@ -177,7 +177,7 @@ namespace Barotrauma
|
||||
}
|
||||
else if (!ItemIdentifier.IsEmpty)
|
||||
{
|
||||
if (!(MapEntityPrefab.FindByIdentifier(ItemIdentifier) is ItemPrefab itemPrefab))
|
||||
if (MapEntityPrefab.FindByIdentifier(ItemIdentifier) is not ItemPrefab itemPrefab)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in SpawnAction (item prefab \"" + ItemIdentifier + "\" not found)");
|
||||
}
|
||||
@@ -275,6 +275,7 @@ namespace Barotrauma
|
||||
{
|
||||
return spawnLocation switch
|
||||
{
|
||||
SpawnLocationType.Any => true,
|
||||
SpawnLocationType.MainSub => submarine == Submarine.MainSub,
|
||||
SpawnLocationType.MainPath => submarine == null,
|
||||
SpawnLocationType.Outpost => submarine is { Info: { IsOutpost: true } },
|
||||
|
||||
@@ -32,11 +32,13 @@ namespace Barotrauma
|
||||
("bot", v => TagBots(playerCrewOnly: false)),
|
||||
("crew", v => TagCrew()),
|
||||
("humanprefabidentifier", TagHumansByIdentifier),
|
||||
("jobidentifier", TagHumansByJobIdentifier),
|
||||
("structureidentifier", TagStructuresByIdentifier),
|
||||
("structurespecialtag", TagStructuresBySpecialTag),
|
||||
("itemidentifier", TagItemsByIdentifier),
|
||||
("itemtag", TagItemsByTag),
|
||||
("hullname", TagHullsByName)
|
||||
("hullname", TagHullsByName),
|
||||
("submarine", TagSubmarinesByType),
|
||||
}.Select(t => (t.k.ToIdentifier(), t.v)).ToImmutableDictionary();
|
||||
}
|
||||
|
||||
@@ -93,6 +95,18 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void TagHumansByJobIdentifier(Identifier jobIdentifier)
|
||||
{
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (c.HasJob(jobIdentifier))
|
||||
{
|
||||
ParentEvent.AddTarget(Tag, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void TagStructuresByIdentifier(Identifier identifier)
|
||||
{
|
||||
ParentEvent.AddTargetPredicate(Tag, e => e is Structure s && SubmarineTypeMatches(s.Submarine) && s.Prefab.Identifier == identifier);
|
||||
@@ -118,6 +132,11 @@ namespace Barotrauma
|
||||
ParentEvent.AddTargetPredicate(Tag, e => e is Hull h && SubmarineTypeMatches(h.Submarine) && h.RoomName.Contains(name.Value, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
private void TagSubmarinesByType(Identifier type)
|
||||
{
|
||||
ParentEvent.AddTargetPredicate(Tag, e => e is Submarine s && SubmarineTypeMatches(s) && (type.IsEmpty || type == s.Info?.Type.ToIdentifier()));
|
||||
}
|
||||
|
||||
private bool SubmarineTypeMatches(Submarine sub)
|
||||
{
|
||||
if (SubmarineType == SubType.Any) { return true; }
|
||||
|
||||
@@ -8,6 +8,12 @@ namespace Barotrauma
|
||||
{
|
||||
class TriggerAction : EventAction
|
||||
{
|
||||
public enum TriggerType
|
||||
{
|
||||
Inside,
|
||||
Outside
|
||||
}
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the first entity that will be used for trigger checks.")]
|
||||
public Identifier Target1Tag { get; set; }
|
||||
|
||||
@@ -23,7 +29,10 @@ namespace Barotrauma
|
||||
[Serialize("", IsPropertySaveable.Yes, description: "Tag to apply to the second entity when the trigger check succeeds.")]
|
||||
public Identifier ApplyToTarget2 { get; set; }
|
||||
|
||||
[Serialize(0.0f, IsPropertySaveable.Yes, description: "Range both entities must be within to activate the trigger.")]
|
||||
[Serialize(TriggerType.Inside, IsPropertySaveable.Yes, description: "Determines if the targets must be inside or outside of the radius.")]
|
||||
public TriggerType Type { get; set; }
|
||||
|
||||
[Serialize(0.0f, IsPropertySaveable.Yes, description: "Range to activate the trigger.")]
|
||||
public float Radius { get; set; }
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes, description: "If true, characters who are being targeted by some enemy cannot trigger the action.")]
|
||||
@@ -38,6 +47,9 @@ namespace Barotrauma
|
||||
[Serialize(false, IsPropertySaveable.Yes, description: "If true, the action can be triggered by interacting with any matching target (not just the 1st one).")]
|
||||
public bool AllowMultipleTargets { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes, description: "If true and using multiple targets, all targets must be inside/outside the radius.")]
|
||||
public bool CheckAllTargets { get; set; }
|
||||
|
||||
private float distance;
|
||||
|
||||
public TriggerAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
|
||||
@@ -57,6 +69,8 @@ namespace Barotrauma
|
||||
public bool isRunning = false;
|
||||
|
||||
private readonly List<Either<Character, Item>> npcsOrItems = new List<Either<Character, Item>>();
|
||||
|
||||
private readonly List<(Entity e1, Entity e2)> triggerers = new List<(Entity e1, Entity e2)>();
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
@@ -66,18 +80,44 @@ namespace Barotrauma
|
||||
|
||||
var targets1 = ParentEvent.GetTargets(Target1Tag);
|
||||
if (!targets1.Any()) { return; }
|
||||
|
||||
|
||||
triggerers.Clear();
|
||||
foreach (Entity e1 in targets1)
|
||||
{
|
||||
if (DisableInCombat && IsInCombat(e1)) { continue; }
|
||||
if (DisableIfTargetIncapacitated && e1 is Character character1 && (character1.IsDead || character1.IsIncapacitated)) { continue; }
|
||||
if (DisableInCombat && IsInCombat(e1))
|
||||
{
|
||||
if (CheckAllTargets)
|
||||
{
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (DisableIfTargetIncapacitated && e1 is Character character1 && (character1.IsDead || character1.IsIncapacitated))
|
||||
{
|
||||
if (CheckAllTargets)
|
||||
{
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!TargetModuleType.IsEmpty)
|
||||
{
|
||||
if (IsCloseEnoughToHull(e1, out Hull hull))
|
||||
if (!CheckAllTargets && CheckDistanceToHull(e1, out Hull hull))
|
||||
{
|
||||
Trigger(e1, hull);
|
||||
return;
|
||||
}
|
||||
else if (CheckAllTargets)
|
||||
{
|
||||
if (CheckDistanceToHull(e1, out hull))
|
||||
{
|
||||
triggerers.Add((e1, hull));
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -85,9 +125,26 @@ namespace Barotrauma
|
||||
|
||||
foreach (Entity e2 in targets2)
|
||||
{
|
||||
if (e1 == e2) { continue; }
|
||||
if (DisableInCombat && IsInCombat(e2)) { continue; }
|
||||
if (DisableIfTargetIncapacitated && e2 is Character character2 && (character2.IsDead || character2.IsIncapacitated)) { continue; }
|
||||
if (e1 == e2)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (DisableInCombat && IsInCombat(e2))
|
||||
{
|
||||
if (CheckAllTargets)
|
||||
{
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (DisableIfTargetIncapacitated && e2 is Character character2 && (character2.IsDead || character2.IsIncapacitated))
|
||||
{
|
||||
if (CheckAllTargets)
|
||||
{
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (WaitForInteraction)
|
||||
{
|
||||
@@ -173,16 +230,35 @@ namespace Barotrauma
|
||||
Vector2 pos1 = e1.WorldPosition;
|
||||
Vector2 pos2 = e2.WorldPosition;
|
||||
distance = Vector2.Distance(pos1, pos2);
|
||||
if (((e1 is MapEntity m1) && Submarine.RectContains(m1.WorldRect, pos2)) ||
|
||||
((e2 is MapEntity m2) && Submarine.RectContains(m2.WorldRect, pos1)) ||
|
||||
Vector2.DistanceSquared(pos1, pos2) < Radius * Radius)
|
||||
if ((Type == TriggerType.Inside) == IsWithinRadius())
|
||||
{
|
||||
if (!CheckAllTargets)
|
||||
{
|
||||
Trigger(e1, e2);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
triggerers.Add((e1, e2));
|
||||
}
|
||||
}
|
||||
else if (CheckAllTargets)
|
||||
{
|
||||
Trigger(e1, e2);
|
||||
return;
|
||||
}
|
||||
|
||||
bool IsWithinRadius() =>
|
||||
((e1 is MapEntity m1) && Submarine.RectContains(m1.WorldRect, pos2)) ||
|
||||
((e2 is MapEntity m2) && Submarine.RectContains(m2.WorldRect, pos1)) ||
|
||||
Vector2.DistanceSquared(pos1, pos2) < Radius * Radius;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (e1, e2) in triggerers)
|
||||
{
|
||||
Trigger(e1, e2);
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetTargetIcons()
|
||||
@@ -205,7 +281,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsCloseEnoughToHull(Entity e, out Hull hull)
|
||||
private bool CheckDistanceToHull(Entity e, out Hull hull)
|
||||
{
|
||||
hull = null;
|
||||
if (Radius <= 0)
|
||||
@@ -213,36 +289,35 @@ namespace Barotrauma
|
||||
if (e is Character character && character.CurrentHull != null && character.CurrentHull.OutpostModuleTags.Contains(TargetModuleType))
|
||||
{
|
||||
hull = character.CurrentHull;
|
||||
return true;
|
||||
return Type == TriggerType.Inside;
|
||||
}
|
||||
else if (e is Item item && item.CurrentHull != null && item.CurrentHull.OutpostModuleTags.Contains(TargetModuleType))
|
||||
{
|
||||
hull = item.CurrentHull;
|
||||
return true;
|
||||
return Type == TriggerType.Inside;
|
||||
}
|
||||
return false;
|
||||
return Type == TriggerType.Outside;
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (Hull potentialHull in Hull.HullList)
|
||||
{
|
||||
if (!potentialHull.OutpostModuleTags.Contains(TargetModuleType)) { continue; }
|
||||
|
||||
Rectangle hullRect = potentialHull.WorldRect;
|
||||
hullRect.Inflate(Radius, Radius);
|
||||
if (Submarine.RectContains(hullRect, e.WorldPosition))
|
||||
{
|
||||
hull = potentialHull;
|
||||
return true;
|
||||
return Type == TriggerType.Inside;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return Type == TriggerType.Outside;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsInCombat(Entity entity)
|
||||
private static bool IsInCombat(Entity entity)
|
||||
{
|
||||
if (!(entity is Character character)) { return false; }
|
||||
if (entity is not Character character) { return false; }
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (c.IsDead || c.Removed || c.IsIncapacitated || !c.Enabled) { continue; }
|
||||
|
||||
@@ -13,6 +13,12 @@ namespace Barotrauma
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier ObjectiveTag { get; set; }
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes)]
|
||||
public bool CanBeCompleted { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier ParentObjectiveId { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool AutoPlayVideo { get; set; }
|
||||
|
||||
|
||||
@@ -73,6 +73,7 @@ namespace Barotrauma
|
||||
|
||||
private readonly HashSet<Event> finishedEvents = new HashSet<Event>();
|
||||
private readonly HashSet<EventPrefab> nonRepeatableEvents = new HashSet<EventPrefab>();
|
||||
private readonly HashSet<EventSet> usedUniqueSets = new HashSet<EventSet>();
|
||||
|
||||
|
||||
#if DEBUG && SERVER
|
||||
@@ -155,12 +156,19 @@ namespace Barotrauma
|
||||
}
|
||||
rand = new MTRandom(seed);
|
||||
|
||||
EventSet initialEventSet = SelectRandomEvents(EventSet.Prefabs.ToList(), requireCampaignSet: GameMain.GameSession?.GameMode is CampaignMode, rand);
|
||||
bool playingCampaign = GameMain.GameSession?.GameMode is CampaignMode;
|
||||
EventSet initialEventSet = SelectRandomEvents(
|
||||
EventSet.Prefabs.ToList(),
|
||||
requireCampaignSet: playingCampaign,
|
||||
random: rand);
|
||||
EventSet additiveSet = null;
|
||||
if (initialEventSet != null && initialEventSet.Additive)
|
||||
{
|
||||
additiveSet = initialEventSet;
|
||||
initialEventSet = SelectRandomEvents(EventSet.Prefabs.Where(e => !e.Additive).ToList(), requireCampaignSet: GameMain.GameSession?.GameMode is CampaignMode, rand);
|
||||
initialEventSet = SelectRandomEvents(
|
||||
EventSet.Prefabs.Where(e => !e.Additive).ToList(),
|
||||
requireCampaignSet: playingCampaign,
|
||||
random: rand);
|
||||
}
|
||||
if (initialEventSet != null)
|
||||
{
|
||||
@@ -201,6 +209,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
AddChildEvents(initialEventSet);
|
||||
|
||||
void AddChildEvents(EventSet eventSet)
|
||||
{
|
||||
if (eventSet == null) { return; }
|
||||
@@ -351,6 +360,7 @@ namespace Barotrauma
|
||||
QueuedEvents.Clear();
|
||||
finishedEvents.Clear();
|
||||
nonRepeatableEvents.Clear();
|
||||
usedUniqueSets.Clear();
|
||||
|
||||
preloadedSprites.ForEach(s => s.Remove());
|
||||
preloadedSprites.Clear();
|
||||
@@ -364,15 +374,25 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public void RegisterEventHistory()
|
||||
{
|
||||
level.LevelData.EventsExhausted = true;
|
||||
if (level?.LevelData != null && level.LevelData.Type == LevelData.LevelType.Outpost)
|
||||
if (level?.LevelData != null)
|
||||
{
|
||||
level.LevelData.EventHistory.AddRange(selectedEvents.Values.SelectMany(v => v).Select(e => e.Prefab).Where(e => !level.LevelData.EventHistory.Contains(e)));
|
||||
if (level.LevelData.EventHistory.Count > MaxEventHistory)
|
||||
level.LevelData.EventsExhausted = true;
|
||||
if (level.LevelData.Type == LevelData.LevelType.Outpost)
|
||||
{
|
||||
level.LevelData.EventHistory.RemoveRange(0, level.LevelData.EventHistory.Count - MaxEventHistory);
|
||||
level.LevelData.EventHistory.AddRange(selectedEvents.Values.SelectMany(v => v).Select(e => e.Prefab).Where(e => !level.LevelData.EventHistory.Contains(e)));
|
||||
if (level.LevelData.EventHistory.Count > MaxEventHistory)
|
||||
{
|
||||
level.LevelData.EventHistory.RemoveRange(0, level.LevelData.EventHistory.Count - MaxEventHistory);
|
||||
}
|
||||
level.LevelData.NonRepeatableEvents.AddRange(nonRepeatableEvents.Where(e => !level.LevelData.NonRepeatableEvents.Contains(e)));
|
||||
}
|
||||
foreach (var usedUniqueSet in usedUniqueSets)
|
||||
{
|
||||
if (!level.LevelData.UsedUniqueSets.Contains(usedUniqueSet.Identifier))
|
||||
{
|
||||
level.LevelData.UsedUniqueSets.Add(usedUniqueSet.Identifier);
|
||||
}
|
||||
}
|
||||
level.LevelData.NonRepeatableEvents.AddRange(nonRepeatableEvents.Where(e => !level.LevelData.NonRepeatableEvents.Contains(e)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -398,6 +418,11 @@ namespace Barotrauma
|
||||
|
||||
DebugConsole.NewMessage($"Loading event set {eventSet.Identifier}", Color.LightBlue, debugOnly: true);
|
||||
|
||||
if (eventSet.Unique && !usedUniqueSets.Contains(eventSet))
|
||||
{
|
||||
usedUniqueSets.Add(eventSet);
|
||||
}
|
||||
|
||||
int applyCount = 1;
|
||||
List<Func<Level.InterestingPosition, bool>> spawnPosFilter = new List<Func<Level.InterestingPosition, bool>>();
|
||||
if (eventSet.PerRuin)
|
||||
@@ -496,12 +521,12 @@ namespace Barotrauma
|
||||
selectedEvents[eventSet].Add(newEvent);
|
||||
}
|
||||
|
||||
Location location = (GameMain.GameSession?.GameMode as CampaignMode)?.Map?.CurrentLocation ?? level?.StartLocation;
|
||||
var location = GetEventLocation();
|
||||
foreach (EventSet childEventSet in eventSet.ChildSets)
|
||||
{
|
||||
if (!IsValidForLevel(childEventSet, level)) { continue; }
|
||||
if (location != null && !IsValidForLocation(childEventSet, location)) { continue; }
|
||||
CreateEvents(childEventSet);
|
||||
if (!IsValidForLocation(childEventSet, location)) { continue; }
|
||||
CreateEvents(childEventSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -536,10 +561,32 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
Location location = (GameMain.GameSession?.GameMode as CampaignMode)?.Map?.CurrentLocation ?? level?.StartLocation;
|
||||
if (location != null)
|
||||
var location = GetEventLocation();
|
||||
allowedEventSets = allowedEventSets.Where(set => IsValidForLocation(set, location));
|
||||
|
||||
allowedEventSets = allowedEventSets.Where(set => !set.CampaignTutorialOnly ||
|
||||
(GameMain.IsSingleplayer && GameMain.GameSession?.Campaign?.Settings is { TutorialEnabled: true }));
|
||||
|
||||
int? discoveryIndex = GameMain.GameSession?.Map?.GetDiscoveryIndex(location);
|
||||
int? visitIndex = GameMain.GameSession?.Map?.GetVisitIndex(location);
|
||||
if (discoveryIndex is not null && discoveryIndex >= 0 && allowedEventSets.Any(set => set.ForceAtDiscoveredNr == discoveryIndex))
|
||||
{
|
||||
allowedEventSets = allowedEventSets.Where(set => IsValidForLocation(set, location));
|
||||
allowedEventSets = allowedEventSets.Where(set => set.ForceAtDiscoveredNr == discoveryIndex);
|
||||
}
|
||||
else if (visitIndex is not null && visitIndex >= 0 && allowedEventSets.Any(set => set.ForceAtVisitedNr == visitIndex))
|
||||
{
|
||||
allowedEventSets = allowedEventSets.Where(set => set.ForceAtVisitedNr == visitIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
// When there are no forced sets, only allow sets that aren't forced at any specific location
|
||||
allowedEventSets = allowedEventSets.Where(set => set.ForceAtDiscoveredNr < 0 && set.ForceAtVisitedNr < 0);
|
||||
}
|
||||
|
||||
if (allowedEventSets.Count() == 1)
|
||||
{
|
||||
// When there's only a single set available, just select it directly
|
||||
return allowedEventSets.First();
|
||||
}
|
||||
|
||||
float totalCommonness = allowedEventSets.Sum(e => e.GetCommonness(level));
|
||||
@@ -558,18 +605,27 @@ namespace Barotrauma
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool IsValidForLevel(EventSet eventSet, Level level)
|
||||
private static bool IsValidForLevel(EventSet eventSet, Level level)
|
||||
{
|
||||
return
|
||||
level.Difficulty >= eventSet.MinLevelDifficulty && level.Difficulty <= eventSet.MaxLevelDifficulty &&
|
||||
level.LevelData.Type == eventSet.LevelType &&
|
||||
(eventSet.BiomeIdentifier.IsEmpty || eventSet.BiomeIdentifier == level.LevelData.Biome.Identifier);
|
||||
(eventSet.BiomeIdentifier.IsEmpty || eventSet.BiomeIdentifier == level.LevelData.Biome.Identifier) &&
|
||||
(!eventSet.Unique || !level.LevelData.UsedUniqueSets.Contains(eventSet.Identifier));
|
||||
}
|
||||
|
||||
private bool IsValidForLocation(EventSet eventSet, Location location)
|
||||
{
|
||||
return eventSet.LocationTypeIdentifiers == null ||
|
||||
eventSet.LocationTypeIdentifiers.Any(identifier => identifier == location.GetLocationType().Identifier);
|
||||
if (location is null) { return true; }
|
||||
var locationType = location.GetLocationType();
|
||||
bool includeGenericEvents = level.Type == LevelData.LevelType.LocationConnection || !locationType.IgnoreGenericEvents;
|
||||
if (includeGenericEvents && eventSet.LocationTypeIdentifiers == null) { return true; }
|
||||
return eventSet.LocationTypeIdentifiers != null && eventSet.LocationTypeIdentifiers.Any(identifier => identifier == locationType.Identifier);
|
||||
}
|
||||
|
||||
private Location GetEventLocation()
|
||||
{
|
||||
return GameMain.GameSession?.Campaign?.Map?.CurrentLocation ?? level?.StartLocation;
|
||||
}
|
||||
|
||||
private bool CanStartEventSet(EventSet eventSet)
|
||||
|
||||
@@ -113,8 +113,17 @@ namespace Barotrauma
|
||||
public readonly bool PerRuin, PerCave, PerWreck;
|
||||
public readonly bool DisableInHuntingGrounds;
|
||||
|
||||
/// <summary>
|
||||
/// If true, events from this set shouldn't be selected again as long as they remain in <see cref="LevelData.NonRepeatableEvents"/> which has a limited size.
|
||||
/// Use <see cref="Unique"/> to prevent selecting the whole set again altogether.
|
||||
/// </summary>
|
||||
public readonly bool OncePerOutpost;
|
||||
|
||||
/// <summary>
|
||||
/// If true, the whole set can only be selected once for a level.
|
||||
/// </summary>
|
||||
public readonly bool Unique;
|
||||
|
||||
public readonly bool DelayWhenCrewAway;
|
||||
|
||||
public readonly bool TriggerEventCooldown;
|
||||
@@ -126,6 +135,18 @@ namespace Barotrauma
|
||||
|
||||
public readonly float ResetTime;
|
||||
|
||||
/// <summary>
|
||||
/// Used to force an event set based on how many other locations have been discovered before this. (Used for campaign tutorial event sets.)
|
||||
/// </summary>
|
||||
public readonly int ForceAtDiscoveredNr;
|
||||
|
||||
/// <summary>
|
||||
/// Used to force an event set based on how many other outposts have been visited before this. (Used for campaign tutorial event sets.)
|
||||
/// </summary>
|
||||
public readonly int ForceAtVisitedNr;
|
||||
|
||||
public readonly bool CampaignTutorialOnly;
|
||||
|
||||
public readonly struct SubEventPrefab
|
||||
{
|
||||
public SubEventPrefab(Either<Identifier[], EventPrefab> prefabOrIdentifiers, float? commonness, float? probability)
|
||||
@@ -269,9 +290,18 @@ namespace Barotrauma
|
||||
IgnoreCoolDown = element.GetAttributeBool("ignorecooldown", parentSet?.IgnoreCoolDown ?? (PerRuin || PerCave || PerWreck));
|
||||
DelayWhenCrewAway = element.GetAttributeBool("delaywhencrewaway", !PerRuin && !PerCave && !PerWreck);
|
||||
OncePerOutpost = element.GetAttributeBool("onceperoutpost", false);
|
||||
Unique = element.GetAttributeBool("unique", false);
|
||||
TriggerEventCooldown = element.GetAttributeBool("triggereventcooldown", true);
|
||||
IsCampaignSet = element.GetAttributeBool("campaign", LevelType == LevelData.LevelType.Outpost || (parentSet?.IsCampaignSet ?? false));
|
||||
ResetTime = element.GetAttributeFloat("resettime", 0);
|
||||
CampaignTutorialOnly = element.GetAttributeBool(nameof(CampaignTutorialOnly), false);
|
||||
|
||||
ForceAtDiscoveredNr = element.GetAttributeInt(nameof(ForceAtDiscoveredNr), -1);
|
||||
ForceAtVisitedNr = element.GetAttributeInt(nameof(ForceAtVisitedNr), -1);
|
||||
if (ForceAtDiscoveredNr >= 0 && ForceAtVisitedNr >= 0)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error with event set \"{Identifier}\" - both ForceAtDiscoveredNr and ForceAtVisitedNr are defined, this could lead to unexpected behavior");
|
||||
}
|
||||
|
||||
DefaultCommonness = element.GetAttributeFloat("commonness", 1.0f);
|
||||
foreach (var subElement in element.Elements())
|
||||
@@ -489,6 +519,11 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{base.ToString()} ({Identifier.Value})";
|
||||
}
|
||||
|
||||
public override void Dispose() { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -378,7 +378,7 @@ namespace Barotrauma
|
||||
IEnumerable<Character> crewCharacters = GameSession.GetSessionCrewCharacters(CharacterType.Both);
|
||||
|
||||
// use multipliers here so that we can easily add them together without introducing multiplicative XP stacking
|
||||
var experienceGainMultiplier = new AbilityExperienceGainMultiplier(1f);
|
||||
var experienceGainMultiplier = new AbilityMissionExperienceGainMultiplier(this, 1f);
|
||||
crewCharacters.ForEach(c => c.CheckTalents(AbilityEffectType.OnAllyGainMissionExperience, experienceGainMultiplier));
|
||||
crewCharacters.ForEach(c => experienceGainMultiplier.Value += c.GetStatValue(StatTypes.MissionExperienceGainMultiplier));
|
||||
|
||||
@@ -386,13 +386,20 @@ namespace Barotrauma
|
||||
#if CLIENT
|
||||
foreach (Character character in crewCharacters)
|
||||
{
|
||||
var experienceGainMultiplierIndividual = new AbilityMissionExperienceGainMultiplier(this, 1f);
|
||||
character.CheckTalents(AbilityEffectType.OnGainMissionExperience, experienceGainMultiplierIndividual);
|
||||
character.Info?.GiveExperience(experienceGain, isMissionExperience: true);
|
||||
}
|
||||
#else
|
||||
foreach (Barotrauma.Networking.Client c in GameMain.Server.ConnectedClients)
|
||||
{
|
||||
//give the experience to the stored characterinfo if the client isn't currently controlling a character
|
||||
(c.Character?.Info ?? c.CharacterInfo)?.GiveExperience(experienceGain, isMissionExperience: true);
|
||||
CharacterInfo info = c.Character?.Info ?? c.CharacterInfo;
|
||||
|
||||
var experienceGainMultiplierIndividual = new AbilityMissionExperienceGainMultiplier(this, 1f);
|
||||
info?.Character?.CheckTalents(AbilityEffectType.OnGainMissionExperience, experienceGainMultiplierIndividual);
|
||||
|
||||
info?.GiveExperience((int)(experienceGain * experienceGainMultiplier.Value), isMissionExperience: true);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -619,4 +626,16 @@ namespace Barotrauma
|
||||
public Mission Mission { get; set; }
|
||||
}
|
||||
|
||||
class AbilityMissionExperienceGainMultiplier : AbilityObject, IAbilityValue, IAbilityMission
|
||||
{
|
||||
public AbilityMissionExperienceGainMultiplier(Mission mission, float missionExperienceGainMultiplier)
|
||||
{
|
||||
Value = missionExperienceGainMultiplier;
|
||||
Mission = mission;
|
||||
}
|
||||
|
||||
public float Value { get; set; }
|
||||
public Mission Mission { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -218,6 +218,8 @@ namespace Barotrauma.Extensions
|
||||
return new Dictionary<TKey, TValue>(immutableDictionary);
|
||||
}
|
||||
|
||||
public static NetCollection<T> ToNetCollection<T>(this IEnumerable<T> enumerable) => new NetCollection<T>(enumerable.ToImmutableArray());
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether a given collection has at least a certain amount
|
||||
/// of elements for which the predicate returns true.
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
#nullable enable
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public enum FactionAffiliation
|
||||
{
|
||||
Affiliated,
|
||||
Neutral
|
||||
}
|
||||
|
||||
class Faction
|
||||
{
|
||||
public Reputation Reputation { get; }
|
||||
@@ -16,11 +21,25 @@ namespace Barotrauma
|
||||
Reputation = new Reputation(metadata, this, prefab.MinReputation, prefab.MaxReputation, prefab.InitialReputation);
|
||||
}
|
||||
|
||||
public bool IsAffiliated()
|
||||
/// <summary>
|
||||
/// Get what kind of affiliation this faction has towards the player depending on who they chose to side with via talents
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public FactionAffiliation GetPlayerAffiliationStatus()
|
||||
{
|
||||
if (GameMain.GameSession?.Campaign?.Factions.MaxBy(static f => f.Reputation.Value) is not { } highestFaction) { return false; }
|
||||
float affiliation = 1f;
|
||||
foreach (Character character in GameSession.GetSessionCrewCharacters(CharacterType.Both))
|
||||
{
|
||||
if (character.Info is not { } info) { continue; }
|
||||
|
||||
return highestFaction.Reputation.Value < 0 || Prefab.Identifier == highestFaction.Prefab.Identifier;
|
||||
affiliation *= 1f + info.GetSavedStatValue(StatTypes.Affiliation, Prefab.Identifier);
|
||||
}
|
||||
|
||||
return affiliation switch
|
||||
{
|
||||
>= 1f => FactionAffiliation.Affiliated,
|
||||
_ => FactionAffiliation.Neutral
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -749,6 +749,7 @@ namespace Barotrauma
|
||||
location.LevelData = new LevelData(location, location.Biome.AdjustedMaxDifficulty);
|
||||
location.Reset();
|
||||
}
|
||||
Map.ClearLocationHistory();
|
||||
Map.SetLocation(Map.Locations.IndexOf(Map.StartLocation));
|
||||
Map.SelectLocation(-1);
|
||||
if (Map.Radiation != null)
|
||||
|
||||
@@ -18,6 +18,9 @@ namespace Barotrauma
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public string PresetName { get; set; } = string.Empty;
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes)]
|
||||
public bool TutorialEnabled { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes), NetworkSerialize]
|
||||
public bool RadiationEnabled { get; set; }
|
||||
|
||||
@@ -104,7 +107,9 @@ namespace Barotrauma
|
||||
|
||||
private static int GetAddedMissionCount()
|
||||
{
|
||||
return GameSession.GetSessionCrewCharacters(CharacterType.Both).Max(static character => (int)character.GetStatValue(StatTypes.ExtraMissionCount));
|
||||
var characters = GameSession.GetSessionCrewCharacters(CharacterType.Both);
|
||||
if (!characters.Any()) { return 0; }
|
||||
return characters.Max(static character => (int)character.GetStatValue(StatTypes.ExtraMissionCount));
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -133,13 +133,13 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
partial void InitProjSpecific();
|
||||
|
||||
|
||||
public static string GetCharacterDataSavePath(string savePath)
|
||||
{
|
||||
return Path.Combine(SaveUtil.MultiplayerSaveFolder, Path.GetFileNameWithoutExtension(savePath) + "_CharacterData.xml");
|
||||
return Path.Combine(Path.GetDirectoryName(savePath), Path.GetFileNameWithoutExtension(savePath) + "_CharacterData.xml");
|
||||
}
|
||||
|
||||
public string GetCharacterDataSavePath()
|
||||
public static string GetCharacterDataSavePath()
|
||||
{
|
||||
return GetCharacterDataSavePath(GameMain.GameSession.SavePath);
|
||||
}
|
||||
|
||||
+15
@@ -29,6 +29,14 @@ namespace Barotrauma
|
||||
|
||||
public readonly Sprite Banner;
|
||||
|
||||
public readonly EndMessageInfo EndMessage;
|
||||
|
||||
public enum EndType { None, Continue, Restart }
|
||||
|
||||
public readonly record struct EndMessageInfo(
|
||||
EndType EndType,
|
||||
Identifier NextTutorialIdentifier);
|
||||
|
||||
public TutorialPrefab(ContentFile file, ContentXElement element) : base(file, element.GetAttributeIdentifier("identifier", ""))
|
||||
{
|
||||
Order = element.GetAttributeInt("order", int.MaxValue);
|
||||
@@ -59,6 +67,13 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
EventIdentifier = element.GetChildElement("scriptedevent")?.GetAttributeIdentifier("identifier", "") ?? Identifier.Empty;
|
||||
|
||||
if (element.GetChildElement("endmessage") is ContentXElement endMessageElement)
|
||||
{
|
||||
EndMessage = new EndMessageInfo(
|
||||
EndType: endMessageElement.GetAttributeEnum("type", EndType.None),
|
||||
NextTutorialIdentifier: endMessageElement.GetAttributeIdentifier("nexttutorial", Identifier.Empty));
|
||||
}
|
||||
}
|
||||
|
||||
public CharacterInfo GetTutorialCharacterInfo()
|
||||
|
||||
@@ -582,6 +582,9 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
ObjectiveManager.ResetObjectives();
|
||||
#endif
|
||||
EventManager?.StartRound(Level.Loaded);
|
||||
SteamAchievementManager.OnStartRound();
|
||||
|
||||
@@ -847,6 +850,7 @@ namespace Barotrauma
|
||||
if (GameMain.NetLobbyScreen != null) { GameMain.NetLobbyScreen.OnRoundEnded(); }
|
||||
TabMenu.OnRoundEnded();
|
||||
GUIMessageBox.MessageBoxes.RemoveAll(mb => mb.UserData as string == "ConversationAction" || ReadyCheck.IsReadyCheck(mb));
|
||||
ObjectiveManager.ResetUI();
|
||||
#endif
|
||||
SteamAchievementManager.OnRoundEnded(this);
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
internal partial class MedicalClinic
|
||||
internal sealed partial class MedicalClinic
|
||||
{
|
||||
public enum NetworkHeader
|
||||
{
|
||||
@@ -18,7 +18,8 @@ namespace Barotrauma
|
||||
ADD_PENDING,
|
||||
REMOVE_PENDING,
|
||||
CLEAR_PENDING,
|
||||
HEAL_PENDING
|
||||
HEAL_PENDING,
|
||||
ADD_EVERYTHING_TO_PENDING
|
||||
}
|
||||
|
||||
public enum AfflictionSeverity
|
||||
@@ -43,23 +44,10 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
[NetworkSerialize]
|
||||
public struct NetHealRequest : INetSerializableStruct
|
||||
{
|
||||
public HealRequestResult Result;
|
||||
}
|
||||
public readonly record struct NetHealRequest(HealRequestResult Result) : INetSerializableStruct;
|
||||
|
||||
[NetworkSerialize]
|
||||
public struct NetRemovedAffliction : INetSerializableStruct
|
||||
{
|
||||
public NetCrewMember CrewMember;
|
||||
public NetAffliction Affliction;
|
||||
}
|
||||
|
||||
public struct NetPendingCrew : INetSerializableStruct
|
||||
{
|
||||
[NetworkSerialize(ArrayMaxSize = CrewManager.MaxCrewSize)]
|
||||
public NetCrewMember[] CrewMembers;
|
||||
}
|
||||
public readonly record struct NetRemovedAffliction(NetCrewMember CrewMember, NetAffliction Affliction) : INetSerializableStruct;
|
||||
|
||||
public struct NetAffliction : INetSerializableStruct
|
||||
{
|
||||
@@ -87,7 +75,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
// between 0.1 and 0.5
|
||||
if (normalizedStrength > 0.1f && normalizedStrength < 0.5f)
|
||||
if (normalizedStrength is > 0.1f and < 0.5f)
|
||||
{
|
||||
return AfflictionSeverity.Medium;
|
||||
}
|
||||
@@ -146,17 +134,23 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public struct NetCrewMember : INetSerializableStruct
|
||||
public record struct NetCrewMember : INetSerializableStruct
|
||||
{
|
||||
[NetworkSerialize]
|
||||
public int CharacterInfoID;
|
||||
|
||||
[NetworkSerialize]
|
||||
public NetAffliction[] Afflictions;
|
||||
public ImmutableArray<NetAffliction> Afflictions;
|
||||
|
||||
public CharacterInfo CharacterInfo
|
||||
public NetCrewMember(CharacterInfo info)
|
||||
{
|
||||
set => CharacterInfoID = value.GetIdentifierUsingOriginalName();
|
||||
CharacterInfoID = info.GetIdentifierUsingOriginalName();
|
||||
Afflictions = ImmutableArray<NetAffliction>.Empty;
|
||||
}
|
||||
|
||||
public NetCrewMember(CharacterInfo info, ImmutableArray<NetAffliction> afflictions): this(info)
|
||||
{
|
||||
Afflictions = afflictions;
|
||||
}
|
||||
|
||||
public readonly CharacterInfo? FindCharacterInfo(ImmutableArray<CharacterInfo> crew)
|
||||
@@ -194,11 +188,11 @@ namespace Barotrauma
|
||||
|
||||
private static bool IsOutpostInCombat()
|
||||
{
|
||||
if (!(Level.Loaded is { Type: LevelData.LevelType.Outpost })) { return false; }
|
||||
if (Level.Loaded is not { Type: LevelData.LevelType.Outpost }) { return false; }
|
||||
|
||||
IEnumerable<Character> crew = GetCrewCharacters().Where(c => c.Character != null).Select(c => c.Character).ToImmutableHashSet();
|
||||
IEnumerable<Character> crew = GetCrewCharacters().Where(static c => c.Character != null).Select(static c => c.Character).ToImmutableHashSet();
|
||||
|
||||
foreach (Character npc in Character.CharacterList.Where(c => c.TeamID == CharacterTeamType.FriendlyNPC))
|
||||
foreach (Character npc in Character.CharacterList.Where(static c => c.TeamID == CharacterTeamType.FriendlyNPC))
|
||||
{
|
||||
bool isInCombatWithCrew = !npc.IsInstigator && npc.AIController is HumanAIController { ObjectiveManager: { CurrentObjective: AIObjectiveCombat combatObjective } } && crew.Contains(combatObjective.Enemy);
|
||||
if (isInCombatWithCrew) { return true; }
|
||||
@@ -238,6 +232,20 @@ namespace Barotrauma
|
||||
PendingHeals.Clear();
|
||||
}
|
||||
|
||||
private void AddEverythingToPending()
|
||||
{
|
||||
foreach (CharacterInfo info in GetCrewCharacters())
|
||||
{
|
||||
if (info.Character?.CharacterHealth is not { } health) { continue; }
|
||||
|
||||
var afflictions = GetAllAfflictions(health);
|
||||
|
||||
if (afflictions.Length is 0) { continue; }
|
||||
|
||||
InsertPendingCrewMember(new NetCrewMember(info, afflictions));
|
||||
}
|
||||
}
|
||||
|
||||
private void RemovePendingAffliction(NetCrewMember crewMember, NetAffliction affliction)
|
||||
{
|
||||
foreach (NetCrewMember listMember in PendingHeals.ToList())
|
||||
@@ -255,7 +263,7 @@ namespace Barotrauma
|
||||
newAfflictions.Add(pendingAffliction);
|
||||
}
|
||||
|
||||
pendingMember.Afflictions = newAfflictions.ToArray();
|
||||
pendingMember.Afflictions = newAfflictions.ToImmutableArray();
|
||||
}
|
||||
|
||||
if (!pendingMember.Afflictions.Any()) { continue; }
|
||||
@@ -280,9 +288,9 @@ namespace Barotrauma
|
||||
static float GetShowTreshold(Affliction affliction) => Math.Max(0, Math.Min(affliction.Prefab.ShowIconToOthersThreshold, affliction.Prefab.ShowInHealthScannerThreshold));
|
||||
}
|
||||
|
||||
private NetAffliction[] GetAllAfflictions(CharacterHealth health)
|
||||
private ImmutableArray<NetAffliction> GetAllAfflictions(CharacterHealth health)
|
||||
{
|
||||
IEnumerable<Affliction> rawAfflictions = health.GetAllAfflictions().Where(a => IsHealable(a));
|
||||
IEnumerable<Affliction> rawAfflictions = health.GetAllAfflictions().Where(IsHealable);
|
||||
|
||||
List<NetAffliction> afflictions = new List<NetAffliction>();
|
||||
|
||||
@@ -305,12 +313,12 @@ namespace Barotrauma
|
||||
afflictions.Add(newAffliction);
|
||||
}
|
||||
|
||||
return afflictions.ToArray();
|
||||
return afflictions.ToImmutableArray();
|
||||
|
||||
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);
|
||||
public int GetTotalCost() => PendingHeals.SelectMany(static h => h.Afflictions).Aggregate(0, static (current, affliction) => current + affliction.Price);
|
||||
|
||||
private int GetAdjustedPrice(int price) => campaign?.Map?.CurrentLocation is { Type: { HasOutpost: true } } currentLocation ? currentLocation.GetAdjustedHealCost(price) : int.MaxValue;
|
||||
|
||||
@@ -325,7 +333,7 @@ namespace Barotrauma
|
||||
}
|
||||
#endif
|
||||
|
||||
return Character.CharacterList.Where(c => c.Info != null && c.TeamID == CharacterTeamType.Team1).Select(c => c.Info).ToImmutableArray();
|
||||
return Character.CharacterList.Where(static c => c.Info != null && c.TeamID == CharacterTeamType.Team1).Select(static c => c.Info).ToImmutableArray();
|
||||
}
|
||||
|
||||
#if DEBUG && CLIENT
|
||||
|
||||
@@ -493,6 +493,10 @@ namespace Barotrauma
|
||||
base.PutItem(item, i, user, removeItem, createNetworkEvent);
|
||||
#if CLIENT
|
||||
CreateSlots();
|
||||
if (character == Character.Controlled)
|
||||
{
|
||||
HintManager.OnObtainedItem(character, item);
|
||||
}
|
||||
#endif
|
||||
if (item.CampaignInteractionType == CampaignMode.InteractionType.Cargo)
|
||||
{
|
||||
|
||||
@@ -843,10 +843,18 @@ namespace Barotrauma.Items.Components
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
if (item.body == null || !item.body.Enabled) { return; }
|
||||
|
||||
Character owner = picker ?? item.GetRootInventoryOwner() as Character;
|
||||
|
||||
if (owner != null)
|
||||
{
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime, owner);
|
||||
}
|
||||
|
||||
if (picker == null || !picker.HasEquippedItem(item))
|
||||
{
|
||||
if (Pusher != null) { Pusher.Enabled = false; }
|
||||
if (attachTargetCell == null) { IsActive = false; }
|
||||
if (attachTargetCell == null && owner == null) { IsActive = false; }
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -855,23 +863,7 @@ namespace Barotrauma.Items.Components
|
||||
Drawable = true;
|
||||
}
|
||||
|
||||
Vector2 swing = Vector2.Zero;
|
||||
if (swingAmount != Vector2.Zero && !picker.IsUnconscious && picker.Stun <= 0.0f)
|
||||
{
|
||||
swingState += deltaTime;
|
||||
swingState %= 1.0f;
|
||||
if (SwingWhenHolding ||
|
||||
(SwingWhenAiming && picker.IsKeyDown(InputType.Aim)) ||
|
||||
(SwingWhenUsing && picker.IsKeyDown(InputType.Aim) && picker.IsKeyDown(InputType.Shoot)))
|
||||
{
|
||||
swing = swingAmount * new Vector2(
|
||||
PerlinNoise.GetPerlin(swingState * SwingSpeed * 0.1f, swingState * SwingSpeed * 0.1f) - 0.5f,
|
||||
PerlinNoise.GetPerlin(swingState * SwingSpeed * 0.1f + 0.5f, swingState * SwingSpeed * 0.1f + 0.5f) - 0.5f);
|
||||
}
|
||||
}
|
||||
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime, picker);
|
||||
|
||||
UpdateSwingPos(deltaTime, out Vector2 swingPos);
|
||||
if (item.body.Dir != picker.AnimController.Dir)
|
||||
{
|
||||
item.FlipX(relativeToSub: false);
|
||||
@@ -884,7 +876,7 @@ namespace Barotrauma.Items.Components
|
||||
scaledHandlePos[0] = handlePos[0] * item.Scale;
|
||||
scaledHandlePos[1] = handlePos[1] * item.Scale;
|
||||
bool aim = picker.IsKeyDown(InputType.Aim) && aimPos != Vector2.Zero && picker.CanAim;
|
||||
picker.AnimController.HoldItem(deltaTime, item, scaledHandlePos, holdPos + swing, aimPos + swing, aim, holdAngle);
|
||||
picker.AnimController.HoldItem(deltaTime, item, scaledHandlePos, holdPos + swingPos, aimPos + swingPos, aim, holdAngle);
|
||||
if (!aim)
|
||||
{
|
||||
var rope = GetRope();
|
||||
@@ -921,6 +913,24 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateSwingPos(float deltaTime, out Vector2 swingPos)
|
||||
{
|
||||
swingPos = Vector2.Zero;
|
||||
if (swingAmount != Vector2.Zero && !picker.IsUnconscious && picker.Stun <= 0.0f)
|
||||
{
|
||||
swingState += deltaTime;
|
||||
swingState %= 1.0f;
|
||||
if (SwingWhenHolding ||
|
||||
(SwingWhenAiming && picker.IsKeyDown(InputType.Aim)) ||
|
||||
(SwingWhenUsing && picker.IsKeyDown(InputType.Aim) && picker.IsKeyDown(InputType.Shoot)))
|
||||
{
|
||||
swingPos = swingAmount * new Vector2(
|
||||
PerlinNoise.GetPerlin(swingState * SwingSpeed * 0.1f, swingState * SwingSpeed * 0.1f) - 0.5f,
|
||||
PerlinNoise.GetPerlin(swingState * SwingSpeed * 0.1f + 0.5f, swingState * SwingSpeed * 0.1f + 0.5f) - 0.5f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void ReceiveSignal(Signal signal, Connection connection)
|
||||
{
|
||||
//do nothing
|
||||
|
||||
@@ -214,8 +214,9 @@ namespace Barotrauma.Items.Components
|
||||
bool aim = item.RequireAimToUse && picker.AllowInput && picker.IsKeyDown(InputType.Aim) && reloadTimer <= 0 && picker.CanAim;
|
||||
if (aim)
|
||||
{
|
||||
UpdateSwingPos(deltaTime, out Vector2 swingPos);
|
||||
hitPos = MathUtils.WrapAnglePi(Math.Min(hitPos + deltaTime * 3f, MathHelper.PiOver4));
|
||||
ac.HoldItem(deltaTime, item, handlePos, aimPos, Vector2.Zero, aim: false, hitPos, holdAngle + hitPos, aimMelee: true);
|
||||
ac.HoldItem(deltaTime, item, handlePos, aimPos + swingPos, Vector2.Zero, aim: false, hitPos, holdAngle + hitPos, aimMelee: true);
|
||||
if (ac.InWater)
|
||||
{
|
||||
ac.LockFlippingUntil = (float)Timing.TotalTime + Reload;
|
||||
@@ -392,34 +393,35 @@ namespace Barotrauma.Items.Components
|
||||
float damageMultiplier = 1 + User.GetStatValue(StatTypes.MeleeAttackMultiplier);
|
||||
damageMultiplier *= 1.0f + item.GetQualityModifier(Quality.StatType.StrikingPowerMultiplier);
|
||||
|
||||
Character user = User;
|
||||
Limb targetLimb = target.UserData as Limb;
|
||||
Character targetCharacter = targetLimb?.character ?? target.UserData as Character;
|
||||
if (Attack != null)
|
||||
{
|
||||
Attack.SetUser(User);
|
||||
Attack.SetUser(user);
|
||||
Attack.DamageMultiplier = damageMultiplier;
|
||||
|
||||
if (targetLimb != null)
|
||||
{
|
||||
if (targetLimb.character.Removed) { return; }
|
||||
targetLimb.character.LastDamageSource = item;
|
||||
Attack.DoDamageToLimb(User, targetLimb, item.WorldPosition, 1.0f);
|
||||
Attack.DoDamageToLimb(user, targetLimb, item.WorldPosition, 1.0f);
|
||||
}
|
||||
else if (targetCharacter != null)
|
||||
{
|
||||
if (targetCharacter.Removed) { return; }
|
||||
targetCharacter.LastDamageSource = item;
|
||||
Attack.DoDamage(User, targetCharacter, item.WorldPosition, 1.0f);
|
||||
Attack.DoDamage(user, targetCharacter, item.WorldPosition, 1.0f);
|
||||
}
|
||||
else if ((target.UserData as Structure ?? targetFixture.UserData as Structure) is Structure targetStructure)
|
||||
{
|
||||
if (targetStructure.Removed) { return; }
|
||||
Attack.DoDamage(User, targetStructure, item.WorldPosition, 1.0f);
|
||||
Attack.DoDamage(user, targetStructure, item.WorldPosition, 1.0f);
|
||||
}
|
||||
else if (target.UserData is Item targetItem && targetItem.Prefab.DamagedByMeleeWeapons && targetItem.Condition > 0)
|
||||
{
|
||||
if (targetItem.Removed) { return; }
|
||||
var attackResult = Attack.DoDamage(User, targetItem, item.WorldPosition, 1.0f);
|
||||
var attackResult = Attack.DoDamage(user, targetItem, item.WorldPosition, 1.0f);
|
||||
#if CLIENT
|
||||
if (attackResult.Damage > 0.0f && targetItem.Prefab.ShowHealthBar)
|
||||
{
|
||||
@@ -435,7 +437,7 @@ namespace Barotrauma.Items.Components
|
||||
else if (target.UserData is Holdable holdable && holdable.CanPush)
|
||||
{
|
||||
if (holdable.Item.Removed) { return; }
|
||||
Attack.DoDamage(User, holdable.Item, item.WorldPosition, 1.0f);
|
||||
Attack.DoDamage(user, holdable.Item, item.WorldPosition, 1.0f);
|
||||
RestoreCollision();
|
||||
hitting = false;
|
||||
User = null;
|
||||
@@ -448,29 +450,32 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
|
||||
bool success = Rand.Range(0.0f, 0.5f) < DegreeOfSuccess(User);
|
||||
|
||||
#if SERVER
|
||||
if (GameMain.Server != null && targetCharacter != null) //TODO: Log structure hits
|
||||
ActionType conditionalActionType = ActionType.OnSuccess;
|
||||
if (user != null && Rand.Range(0.0f, 0.5f) > DegreeOfSuccess(user))
|
||||
{
|
||||
GameMain.Server.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(
|
||||
success ? ActionType.OnUse : ActionType.OnFailure,
|
||||
targetItemComponent: null,
|
||||
targetCharacter, targetLimb));
|
||||
|
||||
string logStr = picker?.LogName + " used " + item.Name;
|
||||
if (item.ContainedItems != null && item.ContainedItems.Any())
|
||||
{
|
||||
logStr += " (" + string.Join(", ", item.ContainedItems.Select(i => i?.Name)) + ")";
|
||||
}
|
||||
logStr += " on " + targetCharacter.LogName + ".";
|
||||
Networking.GameServer.Log(logStr, Networking.ServerLog.MessageType.Attack);
|
||||
conditionalActionType = ActionType.OnFailure;
|
||||
}
|
||||
if (GameMain.NetworkMember is { IsServer: true } server && targetCharacter != null)
|
||||
{
|
||||
server.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(conditionalActionType, targetItemComponent: null, targetCharacter, targetLimb));
|
||||
server.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(ActionType.OnUse, targetItemComponent: null, targetCharacter, targetLimb));
|
||||
#if SERVER
|
||||
if (GameMain.Server != null) //TODO: Log structure hits
|
||||
{
|
||||
string logStr = picker?.LogName + " used " + item.Name;
|
||||
if (item.ContainedItems != null && item.ContainedItems.Any())
|
||||
{
|
||||
logStr += " (" + string.Join(", ", item.ContainedItems.Select(i => i?.Name)) + ")";
|
||||
}
|
||||
logStr += " on " + targetCharacter.LogName + ".";
|
||||
Networking.GameServer.Log(logStr, Networking.ServerLog.MessageType.Attack);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
if (targetCharacter != null) //TODO: Allow OnUse to happen on structures too maybe??
|
||||
{
|
||||
ApplyStatusEffects(success ? ActionType.OnUse : ActionType.OnFailure, 1.0f, targetCharacter, targetLimb, user: User, afflictionMultiplier: damageMultiplier);
|
||||
ApplyStatusEffects(conditionalActionType, 1.0f, targetCharacter, targetLimb, user: user, afflictionMultiplier: damageMultiplier);
|
||||
ApplyStatusEffects(ActionType.OnUse, 1.0f, targetCharacter, targetLimb, user: user, afflictionMultiplier: damageMultiplier);
|
||||
}
|
||||
|
||||
if (DeleteOnUse)
|
||||
|
||||
@@ -23,6 +23,8 @@ namespace Barotrauma.Items.Components
|
||||
[Serialize(0.0f, IsPropertySaveable.No, description: "The force to apply to the user's body."), Editable(MinValueFloat = -1000.0f, MaxValueFloat = 1000.0f)]
|
||||
public float Force { get; set; }
|
||||
|
||||
[Serialize(true, IsPropertySaveable.No, description: "If the item is held in RightHand or LeftHand, apply extra force there")]
|
||||
public bool ApplyToHands { get; set; }
|
||||
#if CLIENT
|
||||
private string particles;
|
||||
[Serialize("", IsPropertySaveable.No, description: "The name of the particle prefab the item emits when used.")]
|
||||
@@ -70,13 +72,16 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
character.AnimController.Collider.ApplyForce(propulsion);
|
||||
|
||||
if (character.Inventory.IsInLimbSlot(item, InvSlotType.RightHand))
|
||||
{
|
||||
character.AnimController.GetLimb(LimbType.RightHand)?.body.ApplyForce(propulsion);
|
||||
}
|
||||
if (character.Inventory.IsInLimbSlot(item, InvSlotType.LeftHand))
|
||||
{
|
||||
character.AnimController.GetLimb(LimbType.LeftHand)?.body.ApplyForce(propulsion);
|
||||
if (ApplyToHands)
|
||||
{
|
||||
if (character.Inventory.IsInLimbSlot(item, InvSlotType.RightHand))
|
||||
{
|
||||
character.AnimController.GetLimb(LimbType.RightHand)?.body.ApplyForce(propulsion);
|
||||
}
|
||||
if (character.Inventory.IsInLimbSlot(item, InvSlotType.LeftHand))
|
||||
{
|
||||
character.AnimController.GetLimb(LimbType.LeftHand)?.body.ApplyForce(propulsion);
|
||||
}
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
|
||||
@@ -32,6 +32,20 @@ namespace Barotrauma.Items.Components
|
||||
set { reload = Math.Max(value, 0.0f); }
|
||||
}
|
||||
|
||||
[Serialize(0f, IsPropertySaveable.No, description: "Weapons skill requirement to reload at normal speed.")]
|
||||
public float ReloadSkillRequirement
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(1.0f, IsPropertySaveable.No, description: "Reload time at 0 skill level. Reload time scales with skill level up to the Weapons skill requirement.")]
|
||||
public float ReloadNoSkill
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No, description: "Tells the AI to hold the trigger down when it uses this weapon")]
|
||||
public bool HoldTrigger
|
||||
{
|
||||
@@ -39,7 +53,7 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(1, IsPropertySaveable.No, description: "How projectiles the weapon launches when fired once.")]
|
||||
[Serialize(1, IsPropertySaveable.No, description: "How many projectiles the weapon launches when fired once.")]
|
||||
public int ProjectileCount
|
||||
{
|
||||
get;
|
||||
@@ -60,6 +74,23 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0.0f, IsPropertySaveable.No, description: "The impulse applied to the physics body of the projectile (the higher the impulse, the faster the projectiles are launched). Sum of weapon + projectile.")]
|
||||
public float LaunchImpulse
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0.0f, IsPropertySaveable.Yes, description: "Percentage of damage mitigation ignored when hitting armored body parts (deflecting limbs). Sum of weapon + projectile."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1f)]
|
||||
public float Penetration { get; private set; }
|
||||
|
||||
[Serialize(1f, IsPropertySaveable.Yes, description: "Weapon's damage modifier")]
|
||||
public float WeaponDamageModifier
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(0f, IsPropertySaveable.Yes, description: "The time required for a charge-type turret to charge up before able to fire.")]
|
||||
public float MaxChargeTime
|
||||
{
|
||||
@@ -99,6 +130,12 @@ namespace Barotrauma.Items.Components
|
||||
// TODO: should define this in xml if we have ranged weapons that don't require aim to use
|
||||
item.RequireAimToUse = true;
|
||||
characterUsable = true;
|
||||
|
||||
if (ReloadSkillRequirement > 0 && ReloadNoSkill <= reload)
|
||||
{
|
||||
DebugConsole.AddWarning($"Invalid XML at {item.Name}: ReloadNoSkill is lower or equal than it's reload skill, despite having ReloadSkillRequirement.");
|
||||
}
|
||||
|
||||
InitProjSpecific(element);
|
||||
}
|
||||
|
||||
@@ -167,7 +204,15 @@ namespace Barotrauma.Items.Components
|
||||
if (currentChargeTime < MaxChargeTime) { return false; }
|
||||
|
||||
IsActive = true;
|
||||
ReloadTimer = reload / (1 + character?.GetStatValue(StatTypes.RangedAttackSpeed) ?? 0f);
|
||||
float baseReloadTime = reload;
|
||||
float weaponSkill = character.GetSkillLevel("weapons");
|
||||
if (ReloadSkillRequirement > 0 && ReloadNoSkill > reload && weaponSkill < ReloadSkillRequirement)
|
||||
{
|
||||
//Examples, assuming 40 weapon skill required: 1 - 40/40 = 0 ... 1 - 0/40 = 1 ... 1 - 20 / 40 = 0.5
|
||||
float reloadFailure = MathHelper.Clamp(1 - (weaponSkill / ReloadSkillRequirement), 0, 1);
|
||||
baseReloadTime = MathHelper.Lerp(reload, ReloadNoSkill, reloadFailure);
|
||||
}
|
||||
ReloadTimer = baseReloadTime / (1 + character?.GetStatValue(StatTypes.RangedAttackSpeed) ?? 0f);
|
||||
currentChargeTime = 0f;
|
||||
|
||||
if (character != null)
|
||||
@@ -218,9 +263,9 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
lastProjectile?.Item.GetComponent<Rope>()?.Snap();
|
||||
}
|
||||
float damageMultiplier = 1f + item.GetQualityModifier(Quality.StatType.FirepowerMultiplier);
|
||||
float damageMultiplier = (1f + item.GetQualityModifier(Quality.StatType.FirepowerMultiplier)) * WeaponDamageModifier;
|
||||
projectile.Launcher = item;
|
||||
projectile.Shoot(character, character.AnimController.AimSourceSimPos, barrelPos, rotation + spread, ignoredBodies: ignoredBodies.ToList(), createNetworkEvent: false, damageMultiplier);
|
||||
projectile.Shoot(character, character.AnimController.AimSourceSimPos, barrelPos, rotation + spread, ignoredBodies: ignoredBodies.ToList(), createNetworkEvent: false, damageMultiplier, LaunchImpulse);
|
||||
projectile.Item.GetComponent<Rope>()?.Attach(Item, projectile.Item);
|
||||
if (i == 0)
|
||||
{
|
||||
|
||||
@@ -1,17 +1,26 @@
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
class Throwable : Holdable
|
||||
{
|
||||
private float throwPos;
|
||||
private bool throwing, throwDone;
|
||||
enum ThrowState
|
||||
{
|
||||
None,
|
||||
Initiated,
|
||||
Throwing
|
||||
}
|
||||
|
||||
private const float ThrowAngleStart = -MathHelper.PiOver2, ThrowAngleEnd = MathHelper.PiOver2;
|
||||
private float throwAngle = ThrowAngleStart;
|
||||
|
||||
private bool midAir;
|
||||
|
||||
private ThrowState throwState;
|
||||
|
||||
|
||||
//continuous collision detection is used while the item is moving faster than this
|
||||
const float ContinuousCollisionThreshold = 5.0f;
|
||||
|
||||
@@ -27,7 +36,6 @@ namespace Barotrauma.Items.Components
|
||||
public Throwable(Item item, ContentXElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
//throwForce = ToolBox.GetAttributeFloat(element, "throwforce", 1.0f);
|
||||
if (aimPos == Vector2.Zero)
|
||||
{
|
||||
aimPos = new Vector2(0.6f, 0.1f);
|
||||
@@ -36,22 +44,21 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override bool Use(float deltaTime, Character character = null)
|
||||
{
|
||||
return characterUsable || character == null; //We do the actual throwing in Aim because Use might be used by chems
|
||||
//actual throwing logic is handled in Update
|
||||
return characterUsable || character == null;
|
||||
}
|
||||
|
||||
public override bool SecondaryUse(float deltaTime, Character character = null)
|
||||
{
|
||||
if (!throwDone) return false; //This should only be triggered in update
|
||||
throwDone = false;
|
||||
return true;
|
||||
//actual throwing logic is handled in Update - SecondaryUse only triggers when the item is thrown
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void Drop(Character dropper)
|
||||
{
|
||||
base.Drop(dropper);
|
||||
|
||||
throwing = false;
|
||||
throwPos = 0.0f;
|
||||
throwState = ThrowState.None;
|
||||
throwAngle = ThrowAngleStart;
|
||||
}
|
||||
|
||||
public override void UpdateBroken(float deltaTime, Camera cam)
|
||||
@@ -100,13 +107,22 @@ namespace Barotrauma.Items.Components
|
||||
return;
|
||||
}
|
||||
|
||||
if (picker.IsKeyDown(InputType.Aim) && picker.IsKeyHit(InputType.Shoot)) { throwing = true; }
|
||||
if (!picker.IsKeyDown(InputType.Aim) && !throwing) { throwPos = 0.0f; }
|
||||
bool aim = picker.IsKeyDown(InputType.Aim) && picker.CanAim;
|
||||
if (throwState != ThrowState.Throwing)
|
||||
{
|
||||
if (picker.IsKeyDown(InputType.Aim))
|
||||
{
|
||||
if (picker.IsKeyDown(InputType.Shoot)) { throwState = ThrowState.Initiated; }
|
||||
}
|
||||
else if (throwState != ThrowState.Initiated)
|
||||
{
|
||||
throwAngle = ThrowAngleStart;
|
||||
}
|
||||
}
|
||||
|
||||
bool aim = picker.IsKeyDown(InputType.Aim) && picker.CanAim;
|
||||
if (picker.IsDead || !picker.AllowInput)
|
||||
{
|
||||
throwing = false;
|
||||
throwState = ThrowState.None;
|
||||
aim = false;
|
||||
}
|
||||
|
||||
@@ -124,25 +140,29 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
item.Submarine = picker.Submarine;
|
||||
|
||||
if (!throwing)
|
||||
if (throwState != ThrowState.Throwing)
|
||||
{
|
||||
if (aim)
|
||||
if (aim || throwState == ThrowState.Initiated)
|
||||
{
|
||||
throwPos = MathUtils.WrapAnglePi(System.Math.Min(throwPos + deltaTime * 5.0f, MathHelper.PiOver2));
|
||||
ac.HoldItem(deltaTime, item, handlePos, aimPos, Vector2.Zero, aim: false, throwPos);
|
||||
throwAngle = System.Math.Min(throwAngle + deltaTime * 8.0f, ThrowAngleEnd);
|
||||
ac.HoldItem(deltaTime, item, handlePos, aimPos, Vector2.Zero, aim: false, throwAngle);
|
||||
if (throwAngle >= ThrowAngleEnd && throwState == ThrowState.Initiated)
|
||||
{
|
||||
throwState = ThrowState.Throwing;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throwPos = 0;
|
||||
throwAngle = ThrowAngleStart;
|
||||
ac.HoldItem(deltaTime, item, handlePos, holdPos, Vector2.Zero, aim: false, holdAngle);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throwPos = MathUtils.WrapAnglePi(throwPos - deltaTime * 15.0f);
|
||||
ac.HoldItem(deltaTime, item, handlePos, aimPos, Vector2.Zero, aim: false, throwPos);
|
||||
throwAngle = MathUtils.WrapAnglePi(throwAngle - deltaTime * 15.0f);
|
||||
ac.HoldItem(deltaTime, item, handlePos, aimPos, Vector2.Zero, aim: false, throwAngle);
|
||||
|
||||
if (throwPos < 0)
|
||||
if (throwAngle < 0)
|
||||
{
|
||||
Vector2 throwVector = Vector2.Normalize(picker.CursorWorldPosition - picker.WorldPosition);
|
||||
//throw upwards if cursor is at the position of the character
|
||||
@@ -180,8 +200,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
Limb rightHand = ac.GetLimb(LimbType.RightHand);
|
||||
item.body.AngularVelocity = rightHand.body.AngularVelocity;
|
||||
throwPos = 0;
|
||||
throwDone = true;
|
||||
throwAngle = ThrowAngleStart;
|
||||
IsActive = true;
|
||||
|
||||
if (GameMain.NetworkMember is { IsServer: true })
|
||||
@@ -193,7 +212,7 @@ namespace Barotrauma.Items.Components
|
||||
//Stun grenades, flares, etc. all have their throw-related things handled in "onSecondaryUse"
|
||||
ApplyStatusEffects(ActionType.OnSecondaryUse, deltaTime, CurrentThrower, user: CurrentThrower);
|
||||
}
|
||||
throwing = false;
|
||||
throwState = ThrowState.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,8 +65,16 @@ namespace Barotrauma.Items.Components
|
||||
public int Capacity
|
||||
{
|
||||
get { return capacity; }
|
||||
set { capacity = Math.Max(value, 0); }
|
||||
private set
|
||||
{
|
||||
capacity = Math.Max(value, 0);
|
||||
MainContainerCapacity = value;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The capacity of the main container without taking the sub containers into account. Only differs when there's a sub container defined for the component.
|
||||
/// </summary>
|
||||
public int MainContainerCapacity { get; private set; }
|
||||
|
||||
//how many items can be contained
|
||||
private int maxStackSize;
|
||||
@@ -229,6 +237,9 @@ namespace Barotrauma.Items.Components
|
||||
public ImmutableHashSet<Identifier> ContainableItemIdentifiers => containableItemIdentifiers;
|
||||
|
||||
public List<RelatedItem> ContainableItems { get; }
|
||||
public List<RelatedItem> AllSubContainableItems { get; }
|
||||
|
||||
public readonly bool HasSubContainers;
|
||||
|
||||
public ItemContainer(Item item, ContentXElement element)
|
||||
: base(item, element)
|
||||
@@ -251,6 +262,7 @@ namespace Barotrauma.Items.Components
|
||||
break;
|
||||
case "subcontainer":
|
||||
totalCapacity += subElement.GetAttributeInt("capacity", 1);
|
||||
HasSubContainers = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -270,7 +282,7 @@ namespace Barotrauma.Items.Components
|
||||
int subCapacity = subElement.GetAttributeInt("capacity", 1);
|
||||
int subMaxStackSize = subElement.GetAttributeInt("maxstacksize", maxStackSize);
|
||||
|
||||
List<RelatedItem> subContainableItems = null;
|
||||
var subContainableItems = new List<RelatedItem>();
|
||||
foreach (var subSubElement in subElement.Elements())
|
||||
{
|
||||
if (subSubElement.Name.ToString().ToLowerInvariant() != "containable") { continue; }
|
||||
@@ -281,8 +293,9 @@ namespace Barotrauma.Items.Components
|
||||
DebugConsole.ThrowError("Error in item config \"" + item.ConfigFilePath + "\" - containable with no identifiers.");
|
||||
continue;
|
||||
}
|
||||
subContainableItems ??= new List<RelatedItem>();
|
||||
subContainableItems.Add(containable);
|
||||
AllSubContainableItems ??= new List<RelatedItem>();
|
||||
AllSubContainableItems.Add(containable);
|
||||
}
|
||||
|
||||
for (int i = subContainerIndex; i < subContainerIndex + subCapacity; i++)
|
||||
@@ -357,6 +370,14 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
//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);
|
||||
|
||||
if (IsActive && item.GetRootInventoryOwner() is Character owner &&
|
||||
owner.HasEquippedItem(item, predicate: slot => slot.HasFlag(InvSlotType.LeftHand) || slot.HasFlag(InvSlotType.RightHand)))
|
||||
{
|
||||
// Set the contained items active if there's an item inserted inside the container. Enables e.g. the rifle flashlight when it's attached to the rifle (put inside of it).
|
||||
SetContainedActive(true);
|
||||
}
|
||||
|
||||
OnContainedItemsChanged.Invoke(this);
|
||||
}
|
||||
|
||||
@@ -409,6 +430,20 @@ namespace Barotrauma.Items.Components
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void FlipX(bool relativeToSub)
|
||||
{
|
||||
base.FlipX(relativeToSub);
|
||||
if (HideItems) { return; }
|
||||
if (item.body == null) { return; }
|
||||
foreach (Item containedItem in Inventory.AllItems)
|
||||
{
|
||||
if (containedItem.body != null && containedItem.body.Enabled && containedItem.body.Dir != item.body.Dir)
|
||||
{
|
||||
containedItem.FlipX(relativeToSub);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(SpawnWithId) && !alwaysContainedItemsSpawned)
|
||||
@@ -477,7 +512,7 @@ namespace Barotrauma.Items.Components
|
||||
effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
||||
{
|
||||
targets.Clear();
|
||||
targets.AddRange(effect.GetNearbyTargets(item.WorldPosition, targets));
|
||||
effect.AddNearbyTargets(item.WorldPosition, targets);
|
||||
effect.Apply(ActionType.OnActive, deltaTime, item, targets);
|
||||
}
|
||||
}
|
||||
@@ -582,11 +617,53 @@ namespace Barotrauma.Items.Components
|
||||
public override void Drop(Character dropper)
|
||||
{
|
||||
IsActive = true;
|
||||
SetContainedActive(false);
|
||||
}
|
||||
|
||||
public override void Equip(Character character)
|
||||
{
|
||||
IsActive = true;
|
||||
if (character != null && character.HasEquippedItem(item, predicate: slot => slot.HasFlag(InvSlotType.LeftHand) || slot.HasFlag(InvSlotType.RightHand)))
|
||||
{
|
||||
SetContainedActive(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetContainedActive(bool active)
|
||||
{
|
||||
foreach (Item containedItem in Inventory.AllItems)
|
||||
{
|
||||
RelatedItem containableItem = FindContainableItem(containedItem);
|
||||
if (containableItem != null && containableItem.SetActive)
|
||||
{
|
||||
foreach (var ic in containedItem.Components)
|
||||
{
|
||||
ic.IsActive = active;
|
||||
}
|
||||
if (containedItem.body != null)
|
||||
{
|
||||
containedItem.body.Enabled = active;
|
||||
if (active)
|
||||
{
|
||||
containedItem.body.PhysEnabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (active)
|
||||
{
|
||||
FlipX(false);
|
||||
}
|
||||
}
|
||||
|
||||
private RelatedItem FindContainableItem(Item item)
|
||||
{
|
||||
var relatedItem = ContainableItems?.FirstOrDefault(ci => ci.MatchesItem(item));
|
||||
if (relatedItem == null && AllSubContainableItems != null)
|
||||
{
|
||||
relatedItem = AllSubContainableItems.FirstOrDefault(ci => ci.MatchesItem(item));
|
||||
}
|
||||
return relatedItem;
|
||||
}
|
||||
|
||||
public override void ReceiveSignal(Signal signal, Connection connection)
|
||||
@@ -604,6 +681,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
#warning There's some code duplication here and in DrawContainedItems() method, but it's not straightforward to get rid of it, because of slightly different logic and the usage of draw positions vs. positions etc. Should probably be splitted into smaller methods.
|
||||
public void SetContainedItemPositions()
|
||||
{
|
||||
Vector2 transformedItemPos = ItemPos * item.Scale;
|
||||
@@ -657,29 +735,70 @@ namespace Barotrauma.Items.Components
|
||||
transformedItemIntervalHorizontal = Vector2.Transform(transformedItemIntervalHorizontal, transform);
|
||||
transformedItemPos += item.Position;
|
||||
}
|
||||
}
|
||||
|
||||
float currentRotation = itemRotation;
|
||||
if (item.body != null)
|
||||
{
|
||||
currentRotation *= item.body.Dir;
|
||||
currentRotation += item.body.Rotation;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentRotation += -item.RotationRad;
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
Vector2 currentItemPos = transformedItemPos;
|
||||
foreach (Item contained in Inventory.AllItems)
|
||||
{
|
||||
Vector2 itemPos = currentItemPos;
|
||||
var relatedItem = FindContainableItem(contained);
|
||||
if (relatedItem != null)
|
||||
{
|
||||
if (relatedItem.Hide.HasValue && relatedItem.Hide.Value) { continue; }
|
||||
if (relatedItem.ItemPos.HasValue)
|
||||
{
|
||||
Vector2 pos = relatedItem.ItemPos.Value;
|
||||
if (item.body != null)
|
||||
{
|
||||
Matrix transform = Matrix.CreateRotationZ(item.body.Rotation);
|
||||
pos.X *= item.body.Dir;
|
||||
itemPos = Vector2.Transform(pos, transform) + item.body.Position;
|
||||
}
|
||||
else
|
||||
{
|
||||
itemPos = pos;
|
||||
// This code is aped based on above. Not tested.
|
||||
if (item.FlippedX)
|
||||
{
|
||||
itemPos.X = -itemPos.X;
|
||||
itemPos.X += item.Rect.Width;
|
||||
}
|
||||
if (item.FlippedY)
|
||||
{
|
||||
itemPos.Y = -itemPos.Y;
|
||||
itemPos.Y -= item.Rect.Height;
|
||||
}
|
||||
itemPos += new Vector2(item.Rect.X, item.Rect.Y);
|
||||
if (Math.Abs(item.RotationRad) > 0.01f)
|
||||
{
|
||||
Matrix transform = Matrix.CreateRotationZ(item.RotationRad);
|
||||
itemPos = Vector2.Transform(itemPos - item.Position, transform) + item.Position;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (contained.body != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
Vector2 simPos = ConvertUnits.ToSimUnits(currentItemPos);
|
||||
contained.body.FarseerBody.SetTransformIgnoreContacts(ref simPos, currentRotation);
|
||||
Vector2 simPos = ConvertUnits.ToSimUnits(itemPos);
|
||||
float rotation = itemRotation;
|
||||
if (relatedItem != null && relatedItem.Rotation != 0)
|
||||
{
|
||||
rotation = MathHelper.ToRadians(relatedItem.Rotation);
|
||||
}
|
||||
if (item.body != null)
|
||||
{
|
||||
rotation *= item.body.Dir;
|
||||
rotation += item.body.Rotation;
|
||||
}
|
||||
else
|
||||
{
|
||||
rotation += -item.RotationRad;
|
||||
}
|
||||
contained.body.FarseerBody.SetTransformIgnoreContacts(ref simPos, rotation);
|
||||
contained.body.SetPrevTransform(contained.body.SimPosition, contained.body.Rotation);
|
||||
contained.body.UpdateDrawPosition();
|
||||
}
|
||||
@@ -695,8 +814,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
contained.Rect =
|
||||
new Rectangle(
|
||||
(int)(currentItemPos.X - contained.Rect.Width / 2.0f),
|
||||
(int)(currentItemPos.Y + contained.Rect.Height / 2.0f),
|
||||
(int)(itemPos.X - contained.Rect.Width / 2.0f),
|
||||
(int)(itemPos.Y + contained.Rect.Height / 2.0f),
|
||||
contained.Rect.Width, contained.Rect.Height);
|
||||
|
||||
contained.Submarine = item.Submarine;
|
||||
|
||||
@@ -459,6 +459,12 @@ namespace Barotrauma.Items.Components
|
||||
progressTimer = 0.0f;
|
||||
progressState = 0.0f;
|
||||
}
|
||||
#if CLIENT
|
||||
else
|
||||
{
|
||||
HintManager.OnStartDeconstructing(user, this);
|
||||
}
|
||||
#endif
|
||||
|
||||
inputContainer.Inventory.Locked = IsActive;
|
||||
}
|
||||
|
||||
@@ -419,7 +419,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
var fabricationIngredients = new AbilityFabricationItemIngredients(foundAvailableItems);
|
||||
user.CheckTalents(AbilityEffectType.OnItemFabricatedIngredients, fabricationIngredients);
|
||||
user?.CheckTalents(AbilityEffectType.OnItemFabricatedIngredients, fabricationIngredients);
|
||||
|
||||
foreach (Item availableItem in fabricationIngredients.Items)
|
||||
{
|
||||
@@ -559,7 +559,7 @@ namespace Barotrauma.Items.Components
|
||||
if (fabricatedItem.TargetItem.ConfigElement.GetChildElement("Quality") == null) { return 0; }
|
||||
int quality = 0;
|
||||
float floatQuality = 0.0f;
|
||||
floatQuality += user.GetStatValue(StatTypes.IncreaseFabricationQuality);
|
||||
floatQuality += user.GetStatValue(StatTypes.IncreaseFabricationQuality, includeSaved: false);
|
||||
foreach (var tag in fabricatedItem.TargetItem.Tags)
|
||||
{
|
||||
floatQuality += user.Info.GetSavedStatValue(StatTypes.IncreaseFabricationQuality, tag);
|
||||
@@ -740,10 +740,27 @@ namespace Barotrauma.Items.Components
|
||||
//order by condition (prefer using worst-condition items)
|
||||
int index = 0;
|
||||
while (index < availableIngredients[itemIdentifier].Count &&
|
||||
availableIngredients[itemIdentifier][index].Condition < item.Condition)
|
||||
compare(item, availableIngredients[itemIdentifier][index], inputContainer.Inventory) < 0)
|
||||
{
|
||||
index++;
|
||||
}
|
||||
|
||||
static int compare(Item item1, Item item2, Inventory inputInventory)
|
||||
{
|
||||
bool item1InInputInventory = item1.ParentInventory == inputInventory;
|
||||
bool item2InInputInventory = item2.ParentInventory == inputInventory;
|
||||
//prefer items in the input inventory
|
||||
if (item1InInputInventory != item2InInputInventory)
|
||||
{
|
||||
return item1InInputInventory ? 1 : -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
//prefer items in worse condition
|
||||
return Math.Sign(item2.Condition - item1.Condition);
|
||||
}
|
||||
}
|
||||
|
||||
availableIngredients[itemIdentifier].Insert(index, item);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ namespace Barotrauma.Items.Components
|
||||
public float? ReceivedOxygenAmount,
|
||||
ReceivedWaterAmount;
|
||||
|
||||
public double LastOxygenDataTime, LastWaterDataTime;
|
||||
|
||||
public readonly HashSet<IdCard> Cards = new HashSet<IdCard>();
|
||||
|
||||
public bool Distort;
|
||||
@@ -83,7 +85,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
//periodically reset all hull data
|
||||
//reset data if we haven't received anything in a while
|
||||
//(so that outdated hull info won't be shown if detectors stop sending signals)
|
||||
if (DateTime.Now > resetDataTime)
|
||||
{
|
||||
@@ -91,8 +93,8 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (!hullData.Distort)
|
||||
{
|
||||
hullData.ReceivedOxygenAmount = null;
|
||||
hullData.ReceivedWaterAmount = null;
|
||||
if (Timing.TotalTime > hullData.LastOxygenDataTime + 1.0) { hullData.ReceivedOxygenAmount = null; }
|
||||
if (Timing.TotalTime > hullData.LastWaterDataTime + 1.0) { hullData.ReceivedWaterAmount = null; }
|
||||
}
|
||||
}
|
||||
resetDataTime = DateTime.Now + new TimeSpan(0, 0, 1);
|
||||
@@ -159,6 +161,7 @@ namespace Barotrauma.Items.Components
|
||||
//cheating a bit because water detectors don't actually send the water level
|
||||
bool fromWaterDetector = source.GetComponent<WaterDetector>() != null;
|
||||
hullData.ReceivedWaterAmount = null;
|
||||
hullData.LastWaterDataTime = Timing.TotalTime;
|
||||
if (fromWaterDetector)
|
||||
{
|
||||
hullData.ReceivedWaterAmount = WaterDetector.GetWaterPercentage(sourceHull);
|
||||
@@ -184,9 +187,10 @@ namespace Barotrauma.Items.Components
|
||||
oxy = Rand.Range(0.0f, 100.0f);
|
||||
}
|
||||
hullData.ReceivedOxygenAmount = oxy;
|
||||
hullData.LastOxygenDataTime = Timing.TotalTime;
|
||||
foreach (var linked in sourceHull.linkedTo)
|
||||
{
|
||||
if (!(linked is Hull linkedHull)) { continue; }
|
||||
if (linked is not Hull linkedHull) { continue; }
|
||||
if (!hullDatas.TryGetValue(linkedHull, out HullData linkedHullData))
|
||||
{
|
||||
linkedHullData = new HullData();
|
||||
|
||||
@@ -197,11 +197,6 @@ namespace Barotrauma.Items.Components
|
||||
if (currentPingIndex != -1)
|
||||
{
|
||||
var activePing = activePings[currentPingIndex];
|
||||
if (item.AiTarget != null)
|
||||
{
|
||||
float range = MathUtils.InverseLerp(item.AiTarget.MinSoundRange, item.AiTarget.MaxSoundRange, Range * activePing.State / zoom);
|
||||
item.AiTarget.SoundRange = MathHelper.Lerp(item.AiTarget.MinSoundRange, item.AiTarget.MaxSoundRange, range);
|
||||
}
|
||||
if (activePing.State > 1.0f)
|
||||
{
|
||||
aiPingCheckPending = true;
|
||||
@@ -235,6 +230,11 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
for (var pingIndex = 0; pingIndex < activePingsCount;)
|
||||
{
|
||||
if (item.AiTarget != null)
|
||||
{
|
||||
float range = MathUtils.InverseLerp(item.AiTarget.MinSoundRange, item.AiTarget.MaxSoundRange, Range * activePings[pingIndex].State / zoom);
|
||||
item.AiTarget.SoundRange = Math.Max(item.AiTarget.SoundRange, MathHelper.Lerp(item.AiTarget.MinSoundRange, item.AiTarget.MaxSoundRange, range));
|
||||
}
|
||||
if (activePings[pingIndex].State > 1.0f)
|
||||
{
|
||||
var lastIndex = --activePingsCount;
|
||||
|
||||
@@ -144,6 +144,11 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public float TargetVelocityLengthSquared
|
||||
{
|
||||
get => TargetVelocity.LengthSquared();
|
||||
}
|
||||
|
||||
public Vector2 SteeringInput
|
||||
{
|
||||
get { return steeringInput; }
|
||||
|
||||
@@ -392,6 +392,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
User = character;
|
||||
ApplyStatusEffects(ActionType.OnUse, 1.0f, User, user: User);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -916,23 +917,22 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (character != null) { character.LastDamageSource = item; }
|
||||
|
||||
ActionType actionType = ActionType.OnUse;
|
||||
if (_user != null && Rand.Range(0.0f, 0.5f) > DegreeOfSuccess(_user))
|
||||
ActionType conditionalActionType = ActionType.OnSuccess;
|
||||
if (User != null && Rand.Range(0.0f, 0.5f) > DegreeOfSuccess(User))
|
||||
{
|
||||
actionType = ActionType.OnFailure;
|
||||
conditionalActionType = ActionType.OnFailure;
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
PlaySound(actionType, user: _user);
|
||||
PlaySound(ActionType.OnImpact, user: _user);
|
||||
PlaySound(conditionalActionType, user: User);
|
||||
PlaySound(ActionType.OnImpact, user: User);
|
||||
#endif
|
||||
|
||||
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
if (target.Body.UserData is Limb targetLimb)
|
||||
{
|
||||
ApplyStatusEffects(actionType, 1.0f, character, targetLimb, user: _user);
|
||||
ApplyStatusEffects(ActionType.OnImpact, 1.0f, character, targetLimb, user: _user);
|
||||
ApplyStatusEffects(conditionalActionType, 1.0f, character, targetLimb, user: User);
|
||||
ApplyStatusEffects(ActionType.OnImpact, 1.0f, character, targetLimb, user: User);
|
||||
var attack = targetLimb.attack;
|
||||
if (attack != null)
|
||||
{
|
||||
@@ -941,8 +941,6 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (effect.type == ActionType.OnImpact)
|
||||
{
|
||||
//effect.Apply(effect.type, 1.0f, targetLimb.character, targetLimb.character, targetLimb.WorldPosition);
|
||||
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.This))
|
||||
{
|
||||
effect.Apply(effect.type, 1.0f, targetLimb.character, targetLimb.character, targetLimb.WorldPosition);
|
||||
@@ -951,32 +949,27 @@ namespace Barotrauma.Items.Components
|
||||
effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
||||
{
|
||||
targets.Clear();
|
||||
targets.AddRange(effect.GetNearbyTargets(targetLimb.WorldPosition, targets));
|
||||
effect.AddNearbyTargets(targetLimb.WorldPosition, targets);
|
||||
effect.Apply(ActionType.OnActive, 1.0f, targetLimb.character, targets);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
#if SERVER
|
||||
if (GameMain.NetworkMember.IsServer)
|
||||
if (GameMain.NetworkMember is { IsServer: true } server)
|
||||
{
|
||||
GameMain.Server?.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(actionType, this, targetLimb.character, targetLimb, null, item.WorldPosition));
|
||||
GameMain.Server?.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(ActionType.OnImpact, this, targetLimb.character, targetLimb, null, item.WorldPosition));
|
||||
server.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(conditionalActionType, this, targetLimb.character, targetLimb, null, item.WorldPosition));
|
||||
server.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(ActionType.OnImpact, this, targetLimb.character, targetLimb, null, item.WorldPosition));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
ApplyStatusEffects(actionType, 1.0f, useTarget: target.Body.UserData as Entity, user: _user);
|
||||
ApplyStatusEffects(ActionType.OnImpact, 1.0f, useTarget: target.Body.UserData as Entity, user: _user);
|
||||
#if SERVER
|
||||
if (GameMain.NetworkMember.IsServer)
|
||||
ApplyStatusEffects(conditionalActionType, 1.0f, useTarget: target.Body.UserData as Entity, user: User);
|
||||
ApplyStatusEffects(ActionType.OnImpact, 1.0f, useTarget: target.Body.UserData as Entity, user: User);
|
||||
if (GameMain.NetworkMember is { IsServer: true } server)
|
||||
{
|
||||
GameMain.Server?.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(actionType, this, null, null, target.Body.UserData as Entity, item.WorldPosition));
|
||||
GameMain.Server?.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(ActionType.OnImpact, this, null, null, target.Body.UserData as Entity, item.WorldPosition));
|
||||
server.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(conditionalActionType, this, null, null, target.Body.UserData as Entity, item.WorldPosition));
|
||||
server.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(ActionType.OnImpact, this, null, null, target.Body.UserData as Entity, item.WorldPosition));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -289,7 +289,7 @@ namespace Barotrauma.Items.Components
|
||||
#if CLIENT
|
||||
Light.ParentSub = item.Submarine;
|
||||
#endif
|
||||
if (item.Container != null)
|
||||
if (item.Container != null && !(item.GetRootInventoryOwner() is Character))
|
||||
{
|
||||
SetLightSourceState(false, 0.0f);
|
||||
return;
|
||||
@@ -301,7 +301,7 @@ namespace Barotrauma.Items.Components
|
||||
if (body != null && !body.Enabled)
|
||||
{
|
||||
SetLightSourceState(false, 0.0f);
|
||||
return;
|
||||
return;
|
||||
}
|
||||
|
||||
//currPowerConsumption = powerConsumption;
|
||||
|
||||
@@ -138,7 +138,7 @@ namespace Barotrauma
|
||||
private ConcurrentQueue<float> impactQueue;
|
||||
|
||||
//a dictionary containing lists of the status effects in all the components of the item
|
||||
private readonly bool[] hasStatusEffectsOfType;
|
||||
private readonly bool[] hasStatusEffectsOfType = new bool[Enum.GetValues(typeof(ActionType)).Length];
|
||||
private readonly Dictionary<ActionType, List<StatusEffect>> statusEffectLists;
|
||||
|
||||
public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; protected set; }
|
||||
@@ -630,7 +630,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (!spawnedInCurrentOutpost && value)
|
||||
{
|
||||
OriginalOutpost = GameMain.GameSession?.StartLocation?.BaseName ?? "";
|
||||
OriginalOutpost = GameMain.GameSession?.LevelData?.Seed;
|
||||
}
|
||||
spawnedInCurrentOutpost = value;
|
||||
}
|
||||
@@ -651,7 +651,9 @@ namespace Barotrauma
|
||||
set
|
||||
{
|
||||
originalOutpost = value;
|
||||
if (!string.IsNullOrEmpty(value) && GameMain.GameSession?.LevelData?.Type == LevelData.LevelType.Outpost && GameMain.GameSession?.StartLocation?.BaseName == value)
|
||||
if (!string.IsNullOrEmpty(value) &&
|
||||
GameMain.GameSession?.LevelData?.Type == LevelData.LevelType.Outpost &&
|
||||
GameMain.GameSession?.LevelData?.Seed == value)
|
||||
{
|
||||
spawnedInCurrentOutpost = true;
|
||||
}
|
||||
@@ -1005,7 +1007,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
hasStatusEffectsOfType = new bool[Enum.GetValues(typeof(ActionType)).Length];
|
||||
foreach (ItemComponent ic in components)
|
||||
{
|
||||
if (ic is Pickable pickable)
|
||||
@@ -1654,7 +1655,7 @@ namespace Barotrauma
|
||||
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters) || effect.HasTargetType(StatusEffect.TargetType.NearbyItems))
|
||||
{
|
||||
targets.AddRange(effect.GetNearbyTargets(WorldPosition, targets));
|
||||
effect.AddNearbyTargets(WorldPosition, targets);
|
||||
if (targets.Count > 0)
|
||||
{
|
||||
hasTargets = true;
|
||||
@@ -2764,18 +2765,20 @@ namespace Barotrauma
|
||||
if (!ic.HasRequiredContainedItems(user, addMessage: user == Character.Controlled)) { continue; }
|
||||
|
||||
bool success = Rand.Range(0.0f, 0.5f) < ic.DegreeOfSuccess(user);
|
||||
ActionType actionType = success ? ActionType.OnUse : ActionType.OnFailure;
|
||||
ActionType conditionalActionType = success ? ActionType.OnSuccess : ActionType.OnFailure;
|
||||
|
||||
#if CLIENT
|
||||
ic.PlaySound(actionType, user);
|
||||
ic.PlaySound(conditionalActionType, user);
|
||||
ic.PlaySound(ActionType.OnUse, user);
|
||||
#endif
|
||||
ic.WasUsed = true;
|
||||
ic.ApplyStatusEffects(actionType, 1.0f, character, targetLimb, user: user);
|
||||
|
||||
ic.ApplyStatusEffects(conditionalActionType, 1.0f, character, targetLimb, user: user);
|
||||
ic.ApplyStatusEffects(ActionType.OnUse, 1.0f, character, targetLimb, user: user);
|
||||
|
||||
if (GameMain.NetworkMember is { IsServer: true })
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(this, new ApplyStatusEffectEventData(
|
||||
actionType, ic, character, targetLimb));
|
||||
GameMain.NetworkMember.CreateEntityEvent(this, new ApplyStatusEffectEventData(conditionalActionType, ic, character, targetLimb));
|
||||
}
|
||||
|
||||
if (ic.DeleteOnUse) { remove = true; }
|
||||
|
||||
@@ -814,8 +814,6 @@ namespace Barotrauma
|
||||
? category
|
||||
: MapEntityCategory.Misc;
|
||||
|
||||
var parentType = ConfigElement.Parent?.GetAttributeIdentifier("itemtype", "");
|
||||
|
||||
//nameidentifier can be used to make multiple items use the same names and descriptions
|
||||
Identifier nameIdentifier = ConfigElement.GetAttributeIdentifier("nameidentifier", "");
|
||||
|
||||
@@ -831,7 +829,7 @@ namespace Barotrauma
|
||||
name = name.Fallback(OriginalName);
|
||||
}
|
||||
|
||||
if (parentType == "wrecked")
|
||||
if (category == MapEntityCategory.Wrecked)
|
||||
{
|
||||
name = TextManager.GetWithVariable("wreckeditemformat", "[name]", name);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
@@ -57,6 +58,20 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public int TargetSlot = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the position defined in ItemContainer.
|
||||
/// </summary>
|
||||
public Vector2? ItemPos;
|
||||
|
||||
/// <summary>
|
||||
/// Only affects when ItemContainer.hideItems is false. Doesn't override the value.
|
||||
/// </summary>
|
||||
public bool? Hide;
|
||||
|
||||
public float Rotation;
|
||||
|
||||
public bool SetActive;
|
||||
|
||||
public string JoinedIdentifiers
|
||||
{
|
||||
get { return string.Join(",", Identifiers); }
|
||||
@@ -202,7 +217,18 @@ namespace Barotrauma
|
||||
new XAttribute("requireempty", RequireEmpty),
|
||||
new XAttribute("excludefullcondition", ExcludeFullCondition),
|
||||
new XAttribute("targetslot", TargetSlot),
|
||||
new XAttribute("allowvariants", AllowVariants));
|
||||
new XAttribute("allowvariants", AllowVariants),
|
||||
new XAttribute("rotation", Rotation),
|
||||
new XAttribute("setactive", SetActive));
|
||||
|
||||
if (Hide.HasValue)
|
||||
{
|
||||
element.Add(new XAttribute(nameof(Hide), Hide.Value));
|
||||
}
|
||||
if (ItemPos.HasValue)
|
||||
{
|
||||
element.Add(new XAttribute(nameof(ItemPos), ItemPos.Value));
|
||||
}
|
||||
|
||||
if (excludedIdentifiers.Count > 0)
|
||||
{
|
||||
@@ -267,8 +293,18 @@ namespace Barotrauma
|
||||
ExcludeBroken = element.GetAttributeBool("excludebroken", true),
|
||||
RequireEmpty = element.GetAttributeBool("requireempty", false),
|
||||
ExcludeFullCondition = element.GetAttributeBool("excludefullcondition", false),
|
||||
AllowVariants = element.GetAttributeBool("allowvariants", true)
|
||||
AllowVariants = element.GetAttributeBool("allowvariants", true),
|
||||
Rotation = element.GetAttributeFloat("rotation", 0f),
|
||||
SetActive = element.GetAttributeBool("setactive", false)
|
||||
};
|
||||
if (element.GetAttribute(nameof(Hide)) != null)
|
||||
{
|
||||
ri.Hide = element.GetAttributeBool(nameof(Hide), false);
|
||||
}
|
||||
if (element.GetAttribute(nameof(ItemPos)) != null)
|
||||
{
|
||||
ri.ItemPos = element.GetAttributeVector2(nameof(ItemPos), Vector2.Zero);
|
||||
}
|
||||
string typeStr = element.GetAttributeString("type", "");
|
||||
if (string.IsNullOrEmpty(typeStr))
|
||||
{
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using Barotrauma.Extensions;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace Barotrauma
|
||||
@@ -18,6 +20,14 @@ namespace Barotrauma
|
||||
|
||||
public readonly ImmutableHashSet<int> AllowedZones;
|
||||
|
||||
private readonly SubmarineAvailability? submarineAvailability;
|
||||
private readonly ImmutableHashSet<SubmarineAvailability> submarineAvailabilityOverrides;
|
||||
|
||||
public readonly record struct SubmarineAvailability(
|
||||
Identifier LocationType,
|
||||
SubmarineClass Class = SubmarineClass.Undefined,
|
||||
int MaxTier = 0);
|
||||
|
||||
public Biome(ContentXElement element, LevelGenerationParametersFile file) : base(file, ParseIdentifier(element))
|
||||
{
|
||||
OldIdentifier = element.GetAttributeIdentifier("oldidentifier", Identifier.Empty);
|
||||
@@ -34,6 +44,26 @@ namespace Barotrauma
|
||||
AllowedZones = element.GetAttributeIntArray("AllowedZones", new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }).ToImmutableHashSet();
|
||||
MinDifficulty = element.GetAttributeFloat("MinDifficulty", 0);
|
||||
maxDifficulty = element.GetAttributeFloat("MaxDifficulty", 100);
|
||||
|
||||
var submarineAvailabilityOverrides = new HashSet<SubmarineAvailability>();
|
||||
if (element.GetChildElement("submarines") is ContentXElement availabilityElement)
|
||||
{
|
||||
submarineAvailability = GetAvailability(availabilityElement);
|
||||
foreach (var overrideElement in availabilityElement.GetChildElements("override"))
|
||||
{
|
||||
var availabilityOverride = GetAvailability(overrideElement);
|
||||
submarineAvailabilityOverrides.Add(availabilityOverride);
|
||||
}
|
||||
}
|
||||
this.submarineAvailabilityOverrides = submarineAvailabilityOverrides.ToImmutableHashSet();
|
||||
|
||||
static SubmarineAvailability GetAvailability(ContentXElement element)
|
||||
{
|
||||
return new SubmarineAvailability(
|
||||
LocationType: element.GetAttributeIdentifier("locationtype", Identifier.Empty),
|
||||
Class: element.GetAttributeEnum("class", SubmarineClass.Undefined),
|
||||
MaxTier: element.GetAttributeInt("maxtier", 0));
|
||||
}
|
||||
}
|
||||
|
||||
public static Identifier ParseIdentifier(ContentXElement element)
|
||||
@@ -47,6 +77,31 @@ namespace Barotrauma
|
||||
return identifier;
|
||||
}
|
||||
|
||||
public int HighestSubmarineTierAvailable(SubmarineClass subClass, Identifier locationType)
|
||||
{
|
||||
if (!submarineAvailability.HasValue)
|
||||
{
|
||||
// If the availability is not explicitly defined, make all subs available
|
||||
return SubmarineInfo.HighestTier;
|
||||
}
|
||||
int maxTier = submarineAvailability.Value.MaxTier;
|
||||
if (submarineAvailabilityOverrides.FirstOrNull(a => a.LocationType == locationType && a.Class == subClass) is SubmarineAvailability locationAndClassOverride)
|
||||
{
|
||||
maxTier = locationAndClassOverride.MaxTier;
|
||||
}
|
||||
else if (submarineAvailabilityOverrides.FirstOrNull(a => a.LocationType == locationType && a.Class == SubmarineClass.Undefined) is SubmarineAvailability locationOverride)
|
||||
{
|
||||
maxTier = locationOverride.MaxTier;
|
||||
}
|
||||
else if (submarineAvailabilityOverrides.FirstOrNull(a => a.LocationType == Identifier.Empty && a.Class == subClass) is SubmarineAvailability classOverride)
|
||||
{
|
||||
maxTier = classOverride.MaxTier;
|
||||
}
|
||||
return maxTier;
|
||||
}
|
||||
|
||||
public bool IsSubmarineAvailable(SubmarineInfo info, Identifier locationType) => info.Tier <= HighestSubmarineTierAvailable(info.SubmarineClass, locationType);
|
||||
|
||||
public override void Dispose() { }
|
||||
}
|
||||
}
|
||||
@@ -447,7 +447,7 @@ namespace Barotrauma
|
||||
|
||||
private Level(LevelData levelData) : base(null, 0)
|
||||
{
|
||||
this.LevelData = levelData;
|
||||
LevelData = levelData;
|
||||
borders = new Rectangle(Point.Zero, levelData.Size);
|
||||
}
|
||||
|
||||
@@ -3939,7 +3939,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
SubmarineInfo outpostInfo;
|
||||
Submarine outpost;
|
||||
Submarine outpost = null;
|
||||
if (i == 0 && preSelectedStartOutpost == null || i == 1 && preSelectedEndOutpost == null)
|
||||
{
|
||||
if (OutpostGenerationParams.OutpostParams.Any() || LevelData.ForceOutpostGenerationParams != null)
|
||||
|
||||
@@ -59,6 +59,7 @@ namespace Barotrauma
|
||||
|
||||
public readonly List<EventPrefab> EventHistory = new List<EventPrefab>();
|
||||
public readonly List<EventPrefab> NonRepeatableEvents = new List<EventPrefab>();
|
||||
public readonly HashSet<Identifier> UsedUniqueSets = new HashSet<Identifier>();
|
||||
|
||||
public bool EventsExhausted { get; set; }
|
||||
|
||||
@@ -143,10 +144,11 @@ namespace Barotrauma
|
||||
string[] nonRepeatablePrefabNames = element.GetAttributeStringArray("nonrepeatableevents", new string[] { });
|
||||
NonRepeatableEvents.AddRange(EventPrefab.Prefabs.Where(p => nonRepeatablePrefabNames.Any(n => p.Identifier == n)));
|
||||
|
||||
UsedUniqueSets = element.GetAttributeIdentifierArray(nameof(UsedUniqueSets), Array.Empty<Identifier>()).ToHashSet();
|
||||
|
||||
EventsExhausted = element.GetAttributeBool(nameof(EventsExhausted).ToLower(), false);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates level data using the properties of the connection (seed, size, difficulty)
|
||||
/// </summary>
|
||||
@@ -284,6 +286,12 @@ namespace Barotrauma
|
||||
newElement.Add(new XAttribute("nonrepeatableevents", string.Join(',', NonRepeatableEvents.Select(p => p.Identifier))));
|
||||
}
|
||||
}
|
||||
|
||||
if (UsedUniqueSets.Any())
|
||||
{
|
||||
newElement.Add(new XAttribute(nameof(UsedUniqueSets), string.Join(',', UsedUniqueSets)));
|
||||
}
|
||||
|
||||
parentElement.Add(newElement);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,6 +287,13 @@ namespace Barotrauma
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize("1.0,1.0,1.0,1.0", IsPropertySaveable.Yes), Editable]
|
||||
public Color SpriteColor
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public string Name => Identifier.Value;
|
||||
|
||||
public List<ChildObject> ChildObjects
|
||||
|
||||
@@ -661,7 +661,7 @@ namespace Barotrauma
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.NearbyItems) || effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
||||
{
|
||||
targets.Clear();
|
||||
targets.AddRange(effect.GetNearbyTargets(worldPosition, targets));
|
||||
effect.AddNearbyTargets(worldPosition, targets);
|
||||
effect.Apply(effect.type, deltaTime, triggerer, targets);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ namespace Barotrauma
|
||||
|
||||
private LocationType addInitialMissionsForType;
|
||||
|
||||
public bool Discovered { get; private set; }
|
||||
public bool Discovered => GameMain.GameSession?.Map?.IsDiscovered(this) ?? false;
|
||||
|
||||
public readonly Dictionary<LocationTypeChange.Requirement, int> ProximityTimer = new Dictionary<LocationTypeChange.Requirement, int>();
|
||||
public (LocationTypeChange typeChange, int delay, MissionPrefab parentMission)? PendingLocationTypeChange;
|
||||
@@ -287,12 +287,12 @@ namespace Barotrauma
|
||||
var characters = GameSession.GetSessionCrewCharacters(CharacterType.Both);
|
||||
if (characters.Any())
|
||||
{
|
||||
if (Location.Reputation?.Faction is { } faction && faction.IsAffiliated())
|
||||
if (Location.Reputation?.Faction is { } faction && faction.GetPlayerAffiliationStatus() is FactionAffiliation.Affiliated)
|
||||
{
|
||||
price *= 1f - characters.Max(static c => c.GetStatValue(StatTypes.StoreBuyMultiplierAffiliated));
|
||||
}
|
||||
price *= 1f - characters.Max(static c => c.GetStatValue(StatTypes.StoreBuyMultiplier));
|
||||
price *= 1f + characters.Max(c => item.Tags.Sum(tag => c.Info.GetSavedStatValue(StatTypes.StoreBuyMultiplier, tag)));
|
||||
price *= 1f - characters.Max(static c => c.GetStatValue(StatTypes.StoreBuyMultiplier, includeSaved: false));
|
||||
price *= 1f - characters.Max(c => item.Tags.Sum(tag => c.Info.GetSavedStatValue(StatTypes.StoreBuyMultiplier, tag)));
|
||||
}
|
||||
// Price should never go below 1 mk
|
||||
return Math.Max((int)price, 1);
|
||||
@@ -497,7 +497,6 @@ namespace Barotrauma
|
||||
baseName = element.GetAttributeString("basename", "");
|
||||
Name = element.GetAttributeString("name", "");
|
||||
MapPosition = element.GetAttributeVector2("position", Vector2.Zero);
|
||||
Discovered = element.GetAttributeBool("discovered", false);
|
||||
PriceMultiplier = element.GetAttributeFloat("pricemultiplier", 1.0f);
|
||||
IsGateBetweenBiomes = element.GetAttributeBool("isgatebetweenbiomes", false);
|
||||
MechanicalPriceMultiplier = element.GetAttributeFloat("mechanicalpricemultipler", 1.0f);
|
||||
@@ -1292,14 +1291,17 @@ namespace Barotrauma
|
||||
return characters.Sum(c => (int)c.GetStatValue(StatTypes.ExtraSpecialSalesCount));
|
||||
}
|
||||
|
||||
public void Discover(bool checkTalents = true)
|
||||
public int HighestSubmarineTierAvailable(SubmarineClass submarineClass)
|
||||
{
|
||||
if (Discovered) { return; }
|
||||
Discovered = true;
|
||||
if (checkTalents)
|
||||
{
|
||||
GameSession.GetSessionCrewCharacters(CharacterType.Both).ForEach(c => c.CheckTalents(AbilityEffectType.OnLocationDiscovered, new AbilityLocation(this)));
|
||||
}
|
||||
if (!HasOutpost()) { return 0; }
|
||||
return Biome?.HighestSubmarineTierAvailable(submarineClass, Type.Identifier) ?? SubmarineInfo.HighestTier;
|
||||
}
|
||||
|
||||
public int HighestSubmarineTierAvailable() => HighestSubmarineTierAvailable(SubmarineClass.Undefined);
|
||||
|
||||
public bool IsSubmarineAvailable(SubmarineInfo info)
|
||||
{
|
||||
return Biome?.IsSubmarineAvailable(info, Type.Identifier) ?? true;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
@@ -1313,7 +1315,6 @@ namespace Barotrauma
|
||||
ClearMissions();
|
||||
LevelData?.EventHistory?.Clear();
|
||||
UnlockInitialMissions();
|
||||
Discovered = false;
|
||||
}
|
||||
|
||||
public XElement Save(Map map, XElement parentElement)
|
||||
@@ -1324,7 +1325,6 @@ namespace Barotrauma
|
||||
new XAttribute("basename", BaseName),
|
||||
new XAttribute("name", Name),
|
||||
new XAttribute("biome", Biome?.Identifier.Value ?? string.Empty),
|
||||
new XAttribute("discovered", Discovered),
|
||||
new XAttribute("position", XMLExtensions.Vector2ToString(MapPosition)),
|
||||
new XAttribute("pricemultiplier", PriceMultiplier),
|
||||
new XAttribute("isgatebetweenbiomes", IsGateBetweenBiomes),
|
||||
@@ -1453,7 +1453,7 @@ namespace Barotrauma
|
||||
HireManager?.Remove();
|
||||
}
|
||||
|
||||
class AbilityLocation : AbilityObject, IAbilityLocation
|
||||
public class AbilityLocation : AbilityObject, IAbilityLocation
|
||||
{
|
||||
public AbilityLocation(Location location)
|
||||
{
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace Barotrauma
|
||||
public readonly Dictionary<int, int> MinCountPerZone = new Dictionary<int, int>();
|
||||
|
||||
public readonly LocalizedString Name;
|
||||
public readonly LocalizedString Description;
|
||||
|
||||
public readonly float BeaconStationChance;
|
||||
|
||||
@@ -70,6 +71,13 @@ namespace Barotrauma
|
||||
public Sprite Sprite { get; private set; }
|
||||
public Sprite RadiationSprite { get; }
|
||||
|
||||
private readonly Identifier forceOutpostGenerationParamsIdentifier;
|
||||
|
||||
/// <summary>
|
||||
/// If set to true, only event sets that explicitly define this location type in <see cref="EventSet.LocationTypeIdentifiers"/> can be selected at this location. Defaults to false.
|
||||
/// </summary>
|
||||
public bool IgnoreGenericEvents { get; }
|
||||
|
||||
public Color SpriteColor
|
||||
{
|
||||
get;
|
||||
@@ -96,6 +104,7 @@ namespace Barotrauma
|
||||
public LocationType(ContentXElement element, LocationTypesFile file) : base(file, element.GetAttributeIdentifier("identifier", element.Name.LocalName))
|
||||
{
|
||||
Name = TextManager.Get("LocationName." + Identifier, "unknown");
|
||||
Description = TextManager.Get("LocationDescription." + Identifier, "");
|
||||
|
||||
BeaconStationChance = element.GetAttributeFloat("beaconstationchance", 0.0f);
|
||||
|
||||
@@ -110,6 +119,10 @@ namespace Barotrauma
|
||||
|
||||
ReplaceInRadiation = element.GetAttributeIdentifier(nameof(ReplaceInRadiation), Identifier.Empty);
|
||||
|
||||
forceOutpostGenerationParamsIdentifier = element.GetAttributeIdentifier("forceoutpostgenerationparams", Identifier.Empty);
|
||||
|
||||
IgnoreGenericEvents = element.GetAttributeBool(nameof(IgnoreGenericEvents), false);
|
||||
|
||||
string teamStr = element.GetAttributeString("outpostteam", "FriendlyNPC");
|
||||
Enum.TryParse(teamStr, out OutpostTeam);
|
||||
|
||||
@@ -261,6 +274,15 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public OutpostGenerationParams GetForcedOutpostGenerationParams()
|
||||
{
|
||||
if (OutpostGenerationParams.OutpostParams.TryGet(forceOutpostGenerationParamsIdentifier, out var parameters))
|
||||
{
|
||||
return parameters;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public override void Dispose() { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,10 +68,15 @@ namespace Barotrauma
|
||||
|
||||
public List<Location> Locations { get; private set; }
|
||||
|
||||
private readonly List<Location> locationsDiscovered = new List<Location>();
|
||||
private readonly List<Location> outpostsVisited = new List<Location>();
|
||||
|
||||
public List<LocationConnection> Connections { get; private set; }
|
||||
|
||||
public Radiation Radiation;
|
||||
|
||||
private bool wasLocationDiscoveryOrderTracked = true;
|
||||
|
||||
public Map(CampaignSettings settings)
|
||||
{
|
||||
generationParams = MapGenerationParams.Instance;
|
||||
@@ -282,7 +287,12 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
CurrentLocation.Discover(true);
|
||||
if (campaign.IsSinglePlayer && campaign.Settings.TutorialEnabled && LocationType.Prefabs.TryGet("tutorialoutpost", out var tutorialOutpost))
|
||||
{
|
||||
CurrentLocation.ChangeType(tutorialOutpost);
|
||||
}
|
||||
Discover(CurrentLocation);
|
||||
Visit(CurrentLocation);
|
||||
CurrentLocation.CreateStores();
|
||||
|
||||
foreach (var location in Locations)
|
||||
@@ -820,7 +830,8 @@ namespace Barotrauma
|
||||
SelectedConnection.Passed = true;
|
||||
|
||||
CurrentLocation = SelectedLocation;
|
||||
CurrentLocation.Discover();
|
||||
Discover(CurrentLocation);
|
||||
Visit(CurrentLocation);
|
||||
SelectedLocation = null;
|
||||
|
||||
CurrentLocation.CreateStores();
|
||||
@@ -851,7 +862,7 @@ namespace Barotrauma
|
||||
|
||||
Location prevLocation = CurrentLocation;
|
||||
CurrentLocation = Locations[index];
|
||||
CurrentLocation.Discover();
|
||||
Discover(CurrentLocation);
|
||||
|
||||
CurrentLocation.CreateStores();
|
||||
if (prevLocation != CurrentLocation)
|
||||
@@ -1184,6 +1195,51 @@ namespace Barotrauma
|
||||
|
||||
partial void ClearAnimQueue();
|
||||
|
||||
public void Discover(Location location, bool checkTalents = true)
|
||||
{
|
||||
if (location is null) { return; }
|
||||
if (locationsDiscovered.Contains(location)) { return; }
|
||||
locationsDiscovered.Add(location);
|
||||
if (checkTalents)
|
||||
{
|
||||
GameSession.GetSessionCrewCharacters(CharacterType.Both).ForEach(c => c.CheckTalents(AbilityEffectType.OnLocationDiscovered, new Location.AbilityLocation(location)));
|
||||
}
|
||||
}
|
||||
|
||||
public void Visit(Location location)
|
||||
{
|
||||
if (location is null) { return; }
|
||||
if (!location.HasOutpost()) { return; }
|
||||
if (outpostsVisited.Contains(location)) { return; }
|
||||
outpostsVisited.Add(location);
|
||||
}
|
||||
|
||||
public void ClearLocationHistory()
|
||||
{
|
||||
locationsDiscovered.Clear();
|
||||
outpostsVisited.Clear();
|
||||
}
|
||||
|
||||
public int? GetDiscoveryIndex(Location location)
|
||||
{
|
||||
if (!wasLocationDiscoveryOrderTracked) { return null; }
|
||||
if (location is null) { return -1; }
|
||||
return locationsDiscovered.IndexOf(location);
|
||||
}
|
||||
|
||||
public int? GetVisitIndex(Location location)
|
||||
{
|
||||
if (!wasLocationDiscoveryOrderTracked) { return null; }
|
||||
if (location is null) { return -1; }
|
||||
return outpostsVisited.IndexOf(location);
|
||||
}
|
||||
|
||||
public bool IsDiscovered(Location location)
|
||||
{
|
||||
if (location is null) { return false; }
|
||||
return locationsDiscovered.Contains(location);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load a previously saved map from an xml element
|
||||
/// </summary>
|
||||
@@ -1211,6 +1267,7 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
ClearLocationHistory();
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
@@ -1226,19 +1283,12 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
location.LoadLocationTypeChange(subElement);
|
||||
|
||||
// Backwards compatibility
|
||||
if (subElement.GetAttributeBool("discovered", false))
|
||||
{
|
||||
location.Discover(checkTalents: false);
|
||||
}
|
||||
if (location.Discovered)
|
||||
{
|
||||
#if CLIENT
|
||||
RemoveFogOfWar(location);
|
||||
#endif
|
||||
if (furthestDiscoveredLocation == null || location.MapPosition.X > furthestDiscoveredLocation.MapPosition.X)
|
||||
{
|
||||
furthestDiscoveredLocation = location;
|
||||
}
|
||||
Discover(location);
|
||||
wasLocationDiscoveryOrderTracked = false;
|
||||
}
|
||||
|
||||
Identifier locationType = subElement.GetAttributeIdentifier("type", Identifier.Empty);
|
||||
@@ -1268,6 +1318,36 @@ namespace Barotrauma
|
||||
case "radiation":
|
||||
Radiation = new Radiation(this, generationParams.RadiationParams, subElement);
|
||||
break;
|
||||
case "discovered":
|
||||
foreach (var childElement in subElement.GetChildElements("location"))
|
||||
{
|
||||
int index = childElement.GetAttributeInt("i", -1);
|
||||
if (index < 0) { continue; }
|
||||
if (Locations[index] is not Location l) { continue; }
|
||||
Discover(l);
|
||||
}
|
||||
break;
|
||||
case "visited":
|
||||
foreach (var childElement in subElement.GetChildElements("location"))
|
||||
{
|
||||
int index = childElement.GetAttributeInt("i", -1);
|
||||
if (index < 0) { continue; }
|
||||
if (Locations[index] is not Location l) { continue; }
|
||||
Visit(l);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Discover(Location location)
|
||||
{
|
||||
this.Discover(location, checkTalents: false);
|
||||
#if CLIENT
|
||||
RemoveFogOfWar(location);
|
||||
#endif
|
||||
if (furthestDiscoveredLocation == null || location.MapPosition.X > furthestDiscoveredLocation.MapPosition.X)
|
||||
{
|
||||
furthestDiscoveredLocation = location;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1343,6 +1423,30 @@ namespace Barotrauma
|
||||
mapElement.Add(Radiation.Save());
|
||||
}
|
||||
|
||||
if (locationsDiscovered.Any())
|
||||
{
|
||||
var discoveryElement = new XElement("discovered");
|
||||
foreach (Location location in locationsDiscovered)
|
||||
{
|
||||
int index = Locations.IndexOf(location);
|
||||
var locationElement = new XElement("location", new XAttribute("i", index));
|
||||
discoveryElement.Add(locationElement);
|
||||
}
|
||||
mapElement.Add(discoveryElement);
|
||||
}
|
||||
|
||||
if (outpostsVisited.Any())
|
||||
{
|
||||
var visitElement = new XElement("visited");
|
||||
foreach (Location location in outpostsVisited)
|
||||
{
|
||||
int index = Locations.IndexOf(location);
|
||||
var locationElement = new XElement("location", new XAttribute("i", index));
|
||||
visitElement.Add(locationElement);
|
||||
}
|
||||
mapElement.Add(visitElement);
|
||||
}
|
||||
|
||||
element.Add(mapElement);
|
||||
}
|
||||
|
||||
|
||||
@@ -96,6 +96,8 @@ namespace Barotrauma
|
||||
[Serialize("", IsPropertySaveable.Yes), Editable]
|
||||
public string ReplaceInRadiation { get; set; }
|
||||
|
||||
public ContentPath OutpostFilePath { get; set; }
|
||||
|
||||
public class ModuleCount
|
||||
{
|
||||
public Identifier Identifier;
|
||||
@@ -182,6 +184,7 @@ namespace Barotrauma
|
||||
Name = element.GetAttributeString("name", Identifier.Value);
|
||||
allowedLocationTypes = element.GetAttributeIdentifierArray("allowedlocationtypes", Array.Empty<Identifier>()).ToHashSet();
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
OutpostFilePath = element.GetAttributeContentPath(nameof(OutpostFilePath));
|
||||
|
||||
var humanPrefabCollections = new List<IReadOnlyList<HumanPrefab>>();
|
||||
foreach (var subElement in element.Elements())
|
||||
|
||||
@@ -143,7 +143,7 @@ namespace Barotrauma
|
||||
//select which module types the outpost should consist of
|
||||
List<Identifier> pendingModuleFlags =
|
||||
onlyEntrance ?
|
||||
generationParams.ModuleCounts.First().Identifier.ToEnumerable().ToList() :
|
||||
(generationParams.ModuleCounts.FirstOrDefault()?.Identifier.ToEnumerable() ?? Enumerable.Empty<Identifier>()).ToList() :
|
||||
SelectModules(outpostModules, generationParams);
|
||||
|
||||
foreach (Identifier flag in pendingModuleFlags)
|
||||
@@ -246,6 +246,7 @@ namespace Barotrauma
|
||||
|
||||
var outpostFiles = ContentPackageManager.EnabledPackages.All
|
||||
.SelectMany(p => p.GetFiles<OutpostFile>())
|
||||
.Where(f => !TutorialPrefab.Prefabs.Any(tp => tp.OutpostPath == f.Path))
|
||||
.OrderBy(f => f.UintIdentifier).ToArray();
|
||||
if (!outpostFiles.Any())
|
||||
{
|
||||
@@ -696,6 +697,14 @@ namespace Barotrauma
|
||||
rect.Location += (module.Offset + module.MoveOffset).ToPoint();
|
||||
rect.Y += module.Bounds.Height;
|
||||
|
||||
Vector2? selfGapPos1 = null;
|
||||
Vector2? selfGapPos2 = null;
|
||||
if (module.PreviousModule != null)
|
||||
{
|
||||
selfGapPos1 = module.Offset + module.ThisGap.Position + module.MoveOffset;
|
||||
selfGapPos2 = module.PreviousModule.Offset + module.PreviousGap.Position + module.PreviousModule.MoveOffset;
|
||||
}
|
||||
|
||||
foreach (PlacedModule otherModule in modules)
|
||||
{
|
||||
if (otherModule == module || otherModule.PreviousModule == null || otherModule.PreviousModule == module) { continue; }
|
||||
@@ -710,7 +719,17 @@ namespace Barotrauma
|
||||
|
||||
Vector2 gapPos1 = otherModule.Offset + otherModule.ThisGap.Position + gapEdgeOffset + otherModule.MoveOffset;
|
||||
Vector2 gapPos2 = otherModule.PreviousModule.Offset + otherModule.PreviousGap.Position + gapEdgeOffset + otherModule.PreviousModule.MoveOffset;
|
||||
if (Submarine.RectContains(rect, gapPos1) || Submarine.RectContains(rect, gapPos2) || MathUtils.GetLineRectangleIntersection(gapPos1, gapPos2, rect, out _))
|
||||
if (Submarine.RectContains(rect, gapPos1) ||
|
||||
Submarine.RectContains(rect, gapPos2) ||
|
||||
MathUtils.GetLineRectangleIntersection(gapPos1, gapPos2, rect, out _))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
//check if the connection overlaps with this module's connection
|
||||
if (selfGapPos1.HasValue && selfGapPos2.HasValue &&
|
||||
!gapPos1.NearlyEquals(gapPos2) && !selfGapPos1.Value.NearlyEquals(selfGapPos2.Value) &&
|
||||
MathUtils.LinesIntersect(gapPos1, gapPos2, selfGapPos1.Value, selfGapPos2.Value))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ namespace Barotrauma
|
||||
|
||||
public PriceInfo(int price, bool canBeBought,
|
||||
int minAmount = 0, int maxAmount = 0, bool canBeSpecial = true, int minLevelDifficulty = 0, float buyingPriceMultiplier = 1f,
|
||||
bool displayNonEmpty = false, string storeIdentifier = null)
|
||||
bool displayNonEmpty = false, bool requiresUnlock = false, string storeIdentifier = null)
|
||||
{
|
||||
Price = price;
|
||||
CanBeBought = canBeBought;
|
||||
@@ -67,6 +67,7 @@ namespace Barotrauma
|
||||
CanBeSpecial = canBeSpecial;
|
||||
DisplayNonEmpty = displayNonEmpty;
|
||||
StoreIdentifier = new Identifier(storeIdentifier);
|
||||
RequiresUnlock = requiresUnlock;
|
||||
}
|
||||
|
||||
public static List<PriceInfo> CreatePriceInfos(XElement element, out PriceInfo defaultPrice)
|
||||
@@ -81,6 +82,7 @@ namespace Barotrauma
|
||||
float buyingPriceMultiplier = element.GetAttributeFloat("buyingpricemultiplier", 1f);
|
||||
bool displayNonEmpty = element.GetAttributeBool("displaynonempty", false);
|
||||
bool soldByDefault = element.GetAttributeBool("sold", element.GetAttributeBool("soldbydefault", true));
|
||||
bool requiresUnlock = element.GetAttributeBool("requiresunlock", false);
|
||||
foreach (XElement childElement in element.GetChildElements("price"))
|
||||
{
|
||||
float priceMultiplier = childElement.GetAttributeFloat("multiplier", 1.0f);
|
||||
@@ -94,26 +96,28 @@ namespace Barotrauma
|
||||
}
|
||||
string storeIdentifier = childElement.GetAttributeString("storeidentifier", backwardsCompatibleIdentifier);
|
||||
// TODO: Add some error messages if we have defined the min or max amount while the item is not sold
|
||||
var priceInfo = new PriceInfo((int)(priceMultiplier * basePrice),
|
||||
sold,
|
||||
sold ? GetMinAmount(childElement, minAmount) : 0,
|
||||
sold ? GetMaxAmount(childElement, maxAmount) : 0,
|
||||
canBeSpecial,
|
||||
storeMinLevelDifficulty,
|
||||
storeBuyingMultiplier,
|
||||
displayNonEmpty,
|
||||
storeIdentifier);
|
||||
var priceInfo = new PriceInfo(price: (int)(priceMultiplier * basePrice),
|
||||
canBeBought: sold,
|
||||
minAmount: sold ? GetMinAmount(childElement, minAmount) : 0,
|
||||
maxAmount: sold ? GetMaxAmount(childElement, maxAmount) : 0,
|
||||
canBeSpecial: canBeSpecial,
|
||||
minLevelDifficulty: storeMinLevelDifficulty,
|
||||
buyingPriceMultiplier: storeBuyingMultiplier,
|
||||
displayNonEmpty: displayNonEmpty,
|
||||
requiresUnlock: requiresUnlock,
|
||||
storeIdentifier: storeIdentifier);
|
||||
priceInfos.Add(priceInfo);
|
||||
}
|
||||
bool soldElsewhere = soldByDefault && element.GetAttributeBool("soldelsewhere", element.GetAttributeBool("soldeverywhere", false));
|
||||
defaultPrice = new PriceInfo(basePrice,
|
||||
soldElsewhere,
|
||||
soldElsewhere ? minAmount : 0,
|
||||
soldElsewhere ? maxAmount : 0,
|
||||
canBeSpecial,
|
||||
minLevelDifficulty,
|
||||
buyingPriceMultiplier,
|
||||
displayNonEmpty);
|
||||
defaultPrice = new PriceInfo(price: basePrice,
|
||||
canBeBought: soldElsewhere,
|
||||
minAmount: soldElsewhere ? minAmount : 0,
|
||||
maxAmount: soldElsewhere ? maxAmount : 0,
|
||||
canBeSpecial: canBeSpecial,
|
||||
minLevelDifficulty: minLevelDifficulty,
|
||||
buyingPriceMultiplier: buyingPriceMultiplier,
|
||||
displayNonEmpty: displayNonEmpty,
|
||||
requiresUnlock: requiresUnlock);
|
||||
return priceInfos;
|
||||
}
|
||||
|
||||
|
||||
@@ -1114,7 +1114,8 @@ namespace Barotrauma
|
||||
{
|
||||
if (item.Submarine != this) { continue; }
|
||||
var pump = item.GetComponent<Pump>();
|
||||
if (pump == null || !item.HasTag("ballast") || item.CurrentHull == null) { continue; }
|
||||
if (pump == null || item.CurrentHull == null) { continue; }
|
||||
if (!item.HasTag("ballast") && !item.CurrentHull.RoomName.Contains("ballast", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
pump.FlowPercentage = 0.0f;
|
||||
ballastHulls.Add(item.CurrentHull);
|
||||
}
|
||||
|
||||
@@ -314,6 +314,7 @@ namespace Barotrauma
|
||||
Tier = original.Tier;
|
||||
IsManuallyOutfitted = original.IsManuallyOutfitted;
|
||||
Tags = original.Tags;
|
||||
OutpostGenerationParams = original.OutpostGenerationParams;
|
||||
if (original.OutpostModuleInfo != null)
|
||||
{
|
||||
OutpostModuleInfo = new OutpostModuleInfo(original.OutpostModuleInfo);
|
||||
@@ -747,6 +748,8 @@ namespace Barotrauma
|
||||
return doc;
|
||||
}
|
||||
|
||||
public static int GetDefaultTier(int price) => price > 20000 ? 3 : price > 10000 ? 2 : 1;
|
||||
public static int GetDefaultTier(int price) => price > 20000 ? HighestTier : price > 10000 ? 2 : 1;
|
||||
|
||||
public const int HighestTier = 3;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,8 @@ namespace Barotrauma.Networking
|
||||
SellSubItems = 0x4000,
|
||||
ManageMap = 0x8000,
|
||||
ManageHires = 0x10000,
|
||||
All = 0x1FFFF
|
||||
ManageBotTalents = 0x20000,
|
||||
All = 0x3FFFF
|
||||
}
|
||||
|
||||
class PermissionPreset
|
||||
|
||||
@@ -382,12 +382,17 @@ namespace Barotrauma.Networking
|
||||
|
||||
private bool autoRestart;
|
||||
|
||||
public bool IsPublic;
|
||||
|
||||
private int maxPlayers;
|
||||
|
||||
public List<SavedClientPermission> ClientPermissions { get; private set; } = new List<SavedClientPermission>();
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes)]
|
||||
public bool IsPublic
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
private int tickRate = 20;
|
||||
[Serialize(20, IsPropertySaveable.Yes)]
|
||||
public int TickRate
|
||||
@@ -518,13 +523,20 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(Barotrauma.LosMode.Opaque, IsPropertySaveable.Yes)]
|
||||
[Serialize(LosMode.Opaque, IsPropertySaveable.Yes)]
|
||||
public LosMode LosMode
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(EnemyHealthBarMode.ShowAll, IsPropertySaveable.Yes)]
|
||||
public EnemyHealthBarMode ShowEnemyHealthBars
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(800, IsPropertySaveable.Yes)]
|
||||
public int LinesPerLogFile
|
||||
{
|
||||
|
||||
@@ -35,10 +35,19 @@ namespace Barotrauma
|
||||
Activity
|
||||
}
|
||||
|
||||
public enum EnemyHealthBarMode
|
||||
{
|
||||
ShowAll,
|
||||
BossHealthBarsOnly,
|
||||
HideAll
|
||||
}
|
||||
|
||||
public static class GameSettings
|
||||
{
|
||||
public struct Config
|
||||
{
|
||||
public const float DefaultAimAssist = 0.05f;
|
||||
|
||||
public static Config GetDefault()
|
||||
{
|
||||
Config config = new Config
|
||||
@@ -50,7 +59,8 @@ namespace Barotrauma
|
||||
SubEditorBackground = new Color(13, 37, 69, 255),
|
||||
EnableSplashScreen = true,
|
||||
PauseOnFocusLost = true,
|
||||
AimAssistAmount = 0.5f,
|
||||
AimAssistAmount = DefaultAimAssist,
|
||||
ShowEnemyHealthBars = EnemyHealthBarMode.ShowAll,
|
||||
EnableMouseLook = true,
|
||||
ChatOpen = true,
|
||||
CrewMenuOpen = true,
|
||||
@@ -110,6 +120,7 @@ namespace Barotrauma
|
||||
public LanguageIdentifier Language;
|
||||
public bool VerboseLogging;
|
||||
public bool SaveDebugConsoleLogs;
|
||||
public string SavePath;
|
||||
public int SubEditorUndoBuffer;
|
||||
public int MaxAutoSaves;
|
||||
public int AutoSaveIntervalSeconds;
|
||||
@@ -118,6 +129,7 @@ namespace Barotrauma
|
||||
public bool PauseOnFocusLost;
|
||||
public float AimAssistAmount;
|
||||
public bool EnableMouseLook;
|
||||
public EnemyHealthBarMode ShowEnemyHealthBars;
|
||||
public bool ChatOpen;
|
||||
public bool CrewMenuOpen;
|
||||
public bool EditorDisclaimerShown;
|
||||
|
||||
@@ -24,7 +24,8 @@ namespace Barotrauma
|
||||
HasSpecifierTag,
|
||||
Affliction,
|
||||
EntityType,
|
||||
LimbType
|
||||
LimbType,
|
||||
SkillRequirement
|
||||
}
|
||||
|
||||
public enum Comparison
|
||||
@@ -73,6 +74,7 @@ namespace Barotrauma
|
||||
case "targetcontainer":
|
||||
case "targetgrandparent":
|
||||
case "targetcontaineditem":
|
||||
case "skillrequirement":
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
@@ -110,6 +112,11 @@ namespace Barotrauma
|
||||
{
|
||||
Type = ConditionType.Uncertain;
|
||||
}
|
||||
|
||||
if (attribute.Parent.GetAttributeBool("skillrequirement", false))
|
||||
{
|
||||
Type = ConditionType.SkillRequirement;
|
||||
}
|
||||
|
||||
AttributeValue = valueString;
|
||||
SplitAttributeValue = valueString.Split(',');
|
||||
@@ -305,25 +312,20 @@ namespace Barotrauma
|
||||
if (health == null) { return false; }
|
||||
var affliction = health.GetAffliction(AttributeName.ToIdentifier());
|
||||
float afflictionStrength = affliction == null ? 0.0f : affliction.Strength;
|
||||
if (FloatValue.HasValue)
|
||||
{
|
||||
float value = FloatValue.Value;
|
||||
switch (Operator)
|
||||
{
|
||||
case OperatorType.Equals:
|
||||
return afflictionStrength == value;
|
||||
case OperatorType.GreaterThan:
|
||||
return afflictionStrength > value;
|
||||
case OperatorType.GreaterThanEquals:
|
||||
return afflictionStrength >= value;
|
||||
case OperatorType.LessThan:
|
||||
return afflictionStrength < value;
|
||||
case OperatorType.LessThanEquals:
|
||||
return afflictionStrength <= value;
|
||||
case OperatorType.NotEquals:
|
||||
return afflictionStrength != value;
|
||||
}
|
||||
}
|
||||
|
||||
return ValueMatchesRequirement(afflictionStrength);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
case ConditionType.SkillRequirement:
|
||||
{
|
||||
if (target == null) { return Operator == OperatorType.NotEquals; }
|
||||
|
||||
if (target is Character targetChar)
|
||||
{
|
||||
float skillLevel = targetChar.GetSkillLevel(AttributeName.ToIdentifier());
|
||||
|
||||
return ValueMatchesRequirement(skillLevel);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -332,6 +334,30 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private bool ValueMatchesRequirement(float testedValue)
|
||||
{
|
||||
if (FloatValue.HasValue)
|
||||
{
|
||||
float value = FloatValue.Value;
|
||||
switch (Operator)
|
||||
{
|
||||
case OperatorType.Equals:
|
||||
return testedValue == value;
|
||||
case OperatorType.GreaterThan:
|
||||
return testedValue > value;
|
||||
case OperatorType.GreaterThanEquals:
|
||||
return testedValue >= value;
|
||||
case OperatorType.LessThan:
|
||||
return testedValue < value;
|
||||
case OperatorType.LessThanEquals:
|
||||
return testedValue <= value;
|
||||
case OperatorType.NotEquals:
|
||||
return testedValue != value;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool MatchesTagCondition(ISerializableEntity target)
|
||||
{
|
||||
if (!(target is Item item)) { return Operator == OperatorType.NotEquals; }
|
||||
|
||||
@@ -877,10 +877,9 @@ namespace Barotrauma
|
||||
return true;
|
||||
}
|
||||
|
||||
public IReadOnlyList<ISerializableEntity> GetNearbyTargets(Vector2 worldPosition, List<ISerializableEntity> targets = null)
|
||||
public void AddNearbyTargets(Vector2 worldPosition, List<ISerializableEntity> targets)
|
||||
{
|
||||
targets ??= new List<ISerializableEntity>();
|
||||
if (Range <= 0.0f) { return targets; }
|
||||
if (Range <= 0.0f) { return; }
|
||||
if (HasTargetType(TargetType.NearbyCharacters))
|
||||
{
|
||||
foreach (Character c in Character.CharacterList)
|
||||
@@ -917,7 +916,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
return targets;
|
||||
|
||||
bool CheckDistance(ISpatialEntity e)
|
||||
{
|
||||
@@ -1362,7 +1360,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (breakLimb)
|
||||
{
|
||||
targetLimb.character.TrySeverLimbJoints(targetLimb, severLimbsProbability: 1, damage: -1, allowBeheading: true, attacker: user);
|
||||
targetLimb.character.TrySeverLimbJoints(targetLimb, severLimbsProbability: 1, damage: -1, allowBeheading: true, ignoreSeveranceProbabilityModifier: true, attacker: user);
|
||||
}
|
||||
if (hideLimb)
|
||||
{
|
||||
@@ -2163,7 +2161,7 @@ namespace Barotrauma
|
||||
if (result.Afflictions != null && result.Afflictions.Any(a => a.Prefab == limbAffliction.Prefab) &&
|
||||
(!affliction.Prefab.LimbSpecific || limb.character.CharacterHealth.GetAfflictionLimb(affliction) == limb))
|
||||
{
|
||||
if (type == ActionType.OnUse)
|
||||
if (type == ActionType.OnUse || type == ActionType.OnSuccess)
|
||||
{
|
||||
limbAffliction.AppliedAsSuccessfulTreatmentTime = Timing.TotalTime;
|
||||
}
|
||||
|
||||
@@ -310,7 +310,7 @@ namespace Barotrauma.Steam
|
||||
+ "unexpected deletion of your hard work.\n"
|
||||
+ "Instead, modify a copy of your mod in LocalMods.\n";
|
||||
|
||||
string workshopModDirReadmeLocation = Path.Combine(SaveUtil.SaveFolder, "WorkshopMods", "README.txt");
|
||||
string workshopModDirReadmeLocation = Path.Combine(SaveUtil.DefaultSaveFolder, "WorkshopMods", "README.txt");
|
||||
if (!File.Exists(workshopModDirReadmeLocation))
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(workshopModDirReadmeLocation)!);
|
||||
|
||||
@@ -108,6 +108,8 @@ namespace Barotrauma
|
||||
public readonly bool IsWallUpgrade;
|
||||
public readonly LocalizedString Name;
|
||||
|
||||
private readonly object mutex = new object();
|
||||
|
||||
public readonly IEnumerable<Identifier> ItemTags;
|
||||
|
||||
public UpgradeCategory(ContentXElement element, UpgradeModulesFile file) : base(element, file)
|
||||
@@ -119,7 +121,6 @@ namespace Barotrauma
|
||||
ItemTags = selfItemTags.CollectionConcat(prefabsThatAllowUpgrades);
|
||||
|
||||
Identifier nameIdentifier = element.GetAttributeIdentifier("nameidentifier", Identifier.Empty);
|
||||
|
||||
if (!nameIdentifier.IsEmpty)
|
||||
{
|
||||
Name = TextManager.Get($"{nameIdentifier}");
|
||||
@@ -132,10 +133,13 @@ namespace Barotrauma
|
||||
|
||||
public void DeterminePrefabsThatAllowUpgrades()
|
||||
{
|
||||
prefabsThatAllowUpgrades.Clear();
|
||||
prefabsThatAllowUpgrades.UnionWith(ItemPrefab.Prefabs
|
||||
.Where(it => it.GetAllowedUpgrades().Contains(Identifier))
|
||||
.Select(it => it.Identifier));
|
||||
lock (mutex)
|
||||
{
|
||||
prefabsThatAllowUpgrades.Clear();
|
||||
prefabsThatAllowUpgrades.UnionWith(ItemPrefab.Prefabs
|
||||
.Where(it => it.GetAllowedUpgrades().Contains(Identifier))
|
||||
.Select(it => it.Identifier));
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanBeApplied(MapEntity item, UpgradePrefab? upgradePrefab)
|
||||
@@ -153,26 +157,11 @@ namespace Barotrauma
|
||||
|
||||
if (upgradePrefab != null && upgradePrefab.IsDisallowed(item)) { return false; }
|
||||
|
||||
return item.Prefab.GetAllowedUpgrades().Contains(Identifier) ||
|
||||
ItemTags.Any(tag => item.Prefab.Tags.Contains(tag) || item.Prefab.Identifier == tag);
|
||||
}
|
||||
|
||||
public bool CanBeApplied(XElement element, UpgradePrefab prefab)
|
||||
{
|
||||
if ("Structure" == element.NameAsIdentifier()) { return IsWallUpgrade; }
|
||||
|
||||
Identifier identifier = element.GetAttributeIdentifier("identifier", Identifier.Empty);
|
||||
if (identifier.IsEmpty) { return false; }
|
||||
|
||||
ItemPrefab? item = ItemPrefab.Find(null, identifier);
|
||||
if (item == null) { return false; }
|
||||
|
||||
Identifier[] disallowedUpgrades = element.GetAttributeIdentifierArray("disallowedupgrades", Array.Empty<Identifier>());
|
||||
|
||||
if (disallowedUpgrades.Any(s => s == Identifier || s == prefab.Identifier)) { return false; }
|
||||
|
||||
return item.GetAllowedUpgrades().Contains(Identifier) ||
|
||||
ItemTags.Any(tag => item.Tags.Contains(tag) || item.Identifier == tag);
|
||||
lock (mutex)
|
||||
{
|
||||
return item.Prefab.GetAllowedUpgrades().Contains(Identifier) ||
|
||||
ItemTags.Any(tag => item.Prefab.Tags.Contains(tag) || item.Prefab.Identifier == tag);
|
||||
}
|
||||
}
|
||||
|
||||
public static UpgradeCategory? Find(Identifier identifier)
|
||||
|
||||
@@ -104,6 +104,8 @@ namespace Barotrauma
|
||||
(float)Math.Floor(value / div) * div;
|
||||
}
|
||||
|
||||
public static int RoundToInt(float v) => (int)MathF.Round(v);
|
||||
|
||||
public static float RoundTowardsClosest(float value, float div)
|
||||
{
|
||||
return (float)Math.Round(value / div) * div;
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
[NetworkSerialize]
|
||||
public readonly record struct NetCollection<T>(ImmutableArray<T> Array) : INetSerializableStruct, IEnumerable<T>
|
||||
{
|
||||
public static readonly NetCollection<T> Empty = new(ImmutableArray<T>.Empty);
|
||||
|
||||
public NetCollection(params T[] elements) : this(elements.ToImmutableArray()) { }
|
||||
|
||||
IEnumerator<T> IEnumerable<T>.GetEnumerator() => ((IEnumerable<T>)Array).GetEnumerator();
|
||||
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)Array).GetEnumerator();
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ namespace Barotrauma
|
||||
|
||||
#if OSX
|
||||
//"/*user*/Library/Application Support/Daedalic Entertainment GmbH/" on Mac
|
||||
public static readonly string SaveFolder = Path.Combine(
|
||||
public static readonly string DefaultSaveFolder = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.Personal),
|
||||
"Library",
|
||||
"Application Support",
|
||||
@@ -27,13 +27,13 @@ namespace Barotrauma
|
||||
#else
|
||||
//"C:/Users/*user*/AppData/Local/Daedalic Entertainment GmbH/" on Windows
|
||||
//"/home/*user*/.local/share/Daedalic Entertainment GmbH/" on Linux
|
||||
public static readonly string SaveFolder = Path.Combine(
|
||||
public static readonly string DefaultSaveFolder = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||
"Daedalic Entertainment GmbH",
|
||||
"Barotrauma");
|
||||
#endif
|
||||
|
||||
public static string MultiplayerSaveFolder = Path.Combine(SaveFolder, "Multiplayer");
|
||||
public static string DefaultMultiplayerSaveFolder = Path.Combine(DefaultSaveFolder, "Multiplayer");
|
||||
|
||||
public static readonly string SubmarineDownloadFolder = Path.Combine("Submarines", "Downloaded");
|
||||
public static readonly string CampaignDownloadFolder = Path.Combine("Data", "Saves", "Multiplayer_Downloaded");
|
||||
@@ -43,9 +43,9 @@ namespace Barotrauma
|
||||
public static string TempPath
|
||||
{
|
||||
#if SERVER
|
||||
get { return Path.Combine(SaveFolder, "temp_server"); }
|
||||
get { return Path.Combine(GetSaveFolder(SaveType.Singleplayer), "temp_server"); }
|
||||
#else
|
||||
get { return Path.Combine(SaveFolder, "temp"); }
|
||||
get { return Path.Combine(GetSaveFolder(SaveType.Singleplayer), "temp"); }
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -198,7 +198,10 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
//deleting a multiplayer save file -> also delete character data
|
||||
if (Path.GetFullPath(Path.GetDirectoryName(filePath)).Equals(Path.GetFullPath(MultiplayerSaveFolder)))
|
||||
var fullPath = Path.GetFullPath(Path.GetDirectoryName(filePath));
|
||||
|
||||
if (fullPath.Equals(Path.GetFullPath(DefaultMultiplayerSaveFolder)) ||
|
||||
fullPath == Path.GetFullPath(GetSaveFolder(SaveType.Multiplayer)))
|
||||
{
|
||||
string characterDataSavePath = MultiPlayerCampaign.GetCharacterDataSavePath(filePath);
|
||||
if (File.Exists(characterDataSavePath))
|
||||
@@ -215,35 +218,70 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetSavePath(SaveType saveType, string saveName)
|
||||
public static string GetSaveFolder(SaveType saveType)
|
||||
{
|
||||
string folder = saveType == SaveType.Singleplayer ? SaveFolder : MultiplayerSaveFolder;
|
||||
return Path.Combine(folder, saveName);
|
||||
string folder = string.Empty;
|
||||
|
||||
if (!string.IsNullOrEmpty(GameSettings.CurrentConfig.SavePath))
|
||||
{
|
||||
folder = GameSettings.CurrentConfig.SavePath;
|
||||
if (saveType == SaveType.Multiplayer)
|
||||
{
|
||||
folder = Path.Combine(folder, "Multiplayer");
|
||||
}
|
||||
if (!Directory.Exists(folder))
|
||||
{
|
||||
DebugConsole.AddWarning($"Could not find the custom save folder \"{folder}\", creating the folder...");
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(folder);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError($"Could not find the custom save folder \"{folder}\". Using the default save path instead.", e);
|
||||
folder = string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (string.IsNullOrEmpty(folder))
|
||||
{
|
||||
folder = saveType == SaveType.Singleplayer ? DefaultSaveFolder : DefaultMultiplayerSaveFolder;
|
||||
}
|
||||
return folder;
|
||||
}
|
||||
|
||||
public static IReadOnlyList<CampaignMode.SaveInfo> GetSaveFiles(SaveType saveType, bool includeInCompatible = true)
|
||||
{
|
||||
string folder = saveType == SaveType.Singleplayer ? SaveFolder : MultiplayerSaveFolder;
|
||||
if (!Directory.Exists(folder))
|
||||
string defaultFolder = saveType == SaveType.Singleplayer ? DefaultSaveFolder : DefaultMultiplayerSaveFolder;
|
||||
if (!Directory.Exists(defaultFolder))
|
||||
{
|
||||
DebugConsole.Log("Save folder \"" + folder + " not found! Attempting to create a new folder...");
|
||||
DebugConsole.Log("Save folder \"" + defaultFolder + " not found! Attempting to create a new folder...");
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(folder);
|
||||
Directory.CreateDirectory(defaultFolder);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to create the folder \"" + folder + "\"!", e);
|
||||
DebugConsole.ThrowError("Failed to create the folder \"" + defaultFolder + "\"!", e);
|
||||
}
|
||||
}
|
||||
|
||||
List<string> files = Directory.GetFiles(folder, "*.save", System.IO.SearchOption.TopDirectoryOnly).ToList();
|
||||
List<string> files = Directory.GetFiles(defaultFolder, "*.save", System.IO.SearchOption.TopDirectoryOnly).ToList();
|
||||
|
||||
var folder = GetSaveFolder(saveType);
|
||||
if (!string.IsNullOrEmpty(folder) && Directory.Exists(folder))
|
||||
{
|
||||
files.AddRange(Directory.GetFiles(folder, "*.save", System.IO.SearchOption.TopDirectoryOnly));
|
||||
}
|
||||
|
||||
string legacyFolder = saveType == SaveType.Singleplayer ? LegacySaveFolder : LegacyMultiplayerSaveFolder;
|
||||
if (Directory.Exists(legacyFolder))
|
||||
{
|
||||
files.AddRange(Directory.GetFiles(legacyFolder, "*.save", System.IO.SearchOption.TopDirectoryOnly));
|
||||
}
|
||||
|
||||
files = files.Distinct().ToList();
|
||||
|
||||
List<CampaignMode.SaveInfo> saveInfos = new List<CampaignMode.SaveInfo>();
|
||||
foreach (string file in files)
|
||||
{
|
||||
@@ -305,7 +343,7 @@ namespace Barotrauma
|
||||
{
|
||||
fileName = ToolBox.RemoveInvalidFileNameChars(fileName);
|
||||
|
||||
string folder = saveType == SaveType.Singleplayer ? SaveFolder : MultiplayerSaveFolder;
|
||||
string folder = GetSaveFolder(saveType);
|
||||
if (fileName == "Save_Default")
|
||||
{
|
||||
fileName = TextManager.Get("SaveFile.DefaultName").Value;
|
||||
|
||||
@@ -3,6 +3,7 @@ using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Barotrauma.IO;
|
||||
using System.Linq;
|
||||
@@ -101,7 +102,7 @@ namespace Barotrauma
|
||||
|
||||
string startPath = directory ?? "";
|
||||
|
||||
string saveFolder = SaveUtil.SaveFolder.Replace('\\', '/');
|
||||
string saveFolder = SaveUtil.DefaultSaveFolder.Replace('\\', '/');
|
||||
if (originalFilename.Replace('\\', '/').StartsWith(saveFolder))
|
||||
{
|
||||
//paths that lead to the save folder might have incorrect case,
|
||||
@@ -721,5 +722,119 @@ namespace Barotrauma
|
||||
{
|
||||
return TextManager.GetWithVariable("percentageformat", "[value]", ((int)MathF.Round(v * 100)).ToString()).Value;
|
||||
}
|
||||
|
||||
private static readonly ImmutableHashSet<char> affectedCharacters = ImmutableHashSet.Create('%', '+', '%');
|
||||
|
||||
/// <summary>
|
||||
/// Extends % and + characters to color tags in talent name tooltips to make them look nicer.
|
||||
/// This obviously does not work in languages like French where a non breaking space is used
|
||||
/// so it's just a a bit extra for the languages it works with.
|
||||
/// </summary>
|
||||
/// <param name="original"></param>
|
||||
/// <returns></returns>
|
||||
public static string ExtendColorToPercentageSigns(string original)
|
||||
{
|
||||
const string colorEnd = "‖color:end‖",
|
||||
colorStart = "‖color:";
|
||||
|
||||
const char definitionIndicator = '‖';
|
||||
|
||||
char[] chars = original.ToCharArray();
|
||||
|
||||
for (int i = 0; i < chars.Length; i++)
|
||||
{
|
||||
if (!TryGetAt(i, chars, out char currentChar) || !affectedCharacters.Contains(currentChar)) { continue; }
|
||||
|
||||
// look behind
|
||||
if (TryGetAt(i - 1, chars, out char c) && c is definitionIndicator)
|
||||
{
|
||||
int offset = colorEnd.Length;
|
||||
|
||||
if (MatchesSequence(i - offset, colorEnd, chars))
|
||||
{
|
||||
// push the color end tag forwards until the character is within the tag
|
||||
char prev = currentChar;
|
||||
for (int k = i - offset; k <= i; k++)
|
||||
{
|
||||
if (!TryGetAt(k, chars, out c)) { continue; }
|
||||
|
||||
chars[k] = prev;
|
||||
prev = c;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// look ahead
|
||||
if (TryGetAt(i + 1, chars, out c) && c is definitionIndicator)
|
||||
{
|
||||
if (!MatchesSequence(i + 1, colorStart, chars)) { continue; }
|
||||
|
||||
int offset = FindNextDefinitionOffset(i, colorStart.Length, chars);
|
||||
|
||||
// we probably reached the end of the string
|
||||
if (offset > chars.Length) { continue; }
|
||||
|
||||
// push the color start tag back until the character is within the tag
|
||||
char prev = currentChar;
|
||||
for (int k = i + offset; k >= i; k--)
|
||||
{
|
||||
if (!TryGetAt(k, chars, out c)) { continue; }
|
||||
|
||||
chars[k] = prev;
|
||||
prev = c;
|
||||
}
|
||||
|
||||
// skip needlessly checking this section again since we already know what's ahead
|
||||
i += offset;
|
||||
}
|
||||
}
|
||||
|
||||
static int FindNextDefinitionOffset(int index, int initialOffset, char[] chars)
|
||||
{
|
||||
int offset = initialOffset;
|
||||
while (TryGetAt(index + offset, chars, out char c) && c is not definitionIndicator) { offset++; }
|
||||
return offset;
|
||||
}
|
||||
|
||||
static bool MatchesSequence(int index, string sequence, char[] chars)
|
||||
{
|
||||
for (int i = 0; i < sequence.Length; i++)
|
||||
{
|
||||
if (!TryGetAt(index + i, chars, out char c) || c != sequence[i]) { return false; }
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool TryGetAt(int i, char[] chars, out char c)
|
||||
{
|
||||
if (i >= 0 && i < chars.Length)
|
||||
{
|
||||
c = chars[i];
|
||||
return true;
|
||||
}
|
||||
|
||||
c = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
return new string(chars);
|
||||
}
|
||||
|
||||
public static bool StatIdentifierMatches(Identifier original, Identifier match)
|
||||
{
|
||||
if (original == match) { return true; }
|
||||
|
||||
for (int i = 0; i < match.Value.Length; i++)
|
||||
{
|
||||
if (i >= original.Value.Length) { return match[i] is '~'; }
|
||||
if (!CharEquals(original[i], match[i])) { return false; }
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
static bool CharEquals(char a, char b) => char.ToLowerInvariant(a) == char.ToLowerInvariant(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user