Unstable 0.1500.1.0 (BaroDev edition)

This commit is contained in:
Markus Isberg
2021-09-03 21:56:31 +09:00
parent 501e02c026
commit e7b7c1a748
143 changed files with 2928 additions and 1356 deletions
@@ -1915,12 +1915,12 @@ namespace Barotrauma
#if SERVER
GameMain.NetworkMember.CreateEntityEvent(Character, new object[]
{
Networking.NetEntityEvent.Type.SetAttackTarget,
attackingLimb,
(damageTarget as Entity)?.ID ?? Entity.NullEntityID,
damageTarget is Character character && targetLimb != null ? Array.IndexOf(character.AnimController.Limbs, targetLimb) : 0,
SimPosition.X,
SimPosition.Y
Networking.NetEntityEvent.Type.SetAttackTarget,
attackingLimb,
(damageTarget as Entity)?.ID ?? Entity.NullEntityID,
damageTarget is Character character && targetLimb != null ? Array.IndexOf(character.AnimController.Limbs, targetLimb) : 0,
SimPosition.X,
SimPosition.Y
});
#else
Character.PlaySound(CharacterSound.SoundType.Attack, maxInterval: 3);
@@ -588,8 +588,9 @@ namespace Barotrauma
// assume that it's required for the stun effect
// as we can't check the status effect conditions here.
var mobileBatteryTag = "mobilebattery";
var containers = weapon.Item.Components.Where(ic => ic is ItemContainer container &&
container.ContainableItems.Any(containable => containable.Identifiers.Any(id => id.Equals(mobileBatteryTag))));
var containers = weapon.Item.Components.Where(ic =>
ic is ItemContainer container &&
container.ContainableItemIdentifiers.Contains(mobileBatteryTag));
// If there's no such container, assume that the melee weapon can stun without a battery.
return containers.None() || containers.Any(container =>
(container as ItemContainer)?.Inventory.AllItems.Any(i => i != null && i.HasTag(mobileBatteryTag) && i.Condition > 0.0f) ?? false);
@@ -148,7 +148,7 @@ namespace Barotrauma
public virtual void UpdateAnim(float deltaTime) { }
public virtual void HoldItem(float deltaTime, Item item, Vector2[] handlePos, Vector2 holdPos, Vector2 aimPos, bool aim, float holdAngle, float itemAngleRelativeToHoldAngle = 0.0f) { }
public virtual void HoldItem(float deltaTime, Item item, Vector2[] handlePos, Vector2 holdPos, Vector2 aimPos, bool aim, float holdAngle, float itemAngleRelativeToHoldAngle = 0.0f, bool aimingMelee = false) { }
public virtual void DragCharacter(Character target, float deltaTime) { }
@@ -390,11 +390,17 @@ namespace Barotrauma
}
if (eatTimer % 1.0f < 0.5f && (eatTimer - deltaTime * eatSpeed) % 1.0f > 0.5f)
{
bool CanBeSevered(LimbJoint j) => !j.IsSevered && j.CanBeSevered && j.LimbA != null && !j.LimbA.IsSevered && j.LimbB != null && !j.LimbB.IsSevered;
static bool CanBeSevered(LimbJoint j) => !j.IsSevered && j.CanBeSevered && j.LimbA != null && !j.LimbA.IsSevered && j.LimbB != null && !j.LimbB.IsSevered;
//keep severing joints until there is only one limb left
var nonSeveredJoints = target.AnimController.LimbJoints.Where(CanBeSevered);
if (nonSeveredJoints.None())
{
//small monsters don't eat the contents of the character's inventory
if (Mass < target.AnimController.Mass)
{
target.Inventory?.AllItemsMod.ForEach(it => it?.Drop(dropper: null));
}
//only one limb left, the character is now full eaten
Entity.Spawner?.AddToRemoveQueue(target);
@@ -150,6 +150,13 @@ namespace Barotrauma
private float upperLegLength = 0.0f, lowerLegLength = 0.0f;
private bool aiming;
private bool wasAiming;
private bool aimingMelee;
private bool wasAimingMelee;
public bool IsAiming => wasAiming;
public bool IsAimingMelee => wasAimingMelee;
private readonly float movementLerp;
@@ -532,7 +539,10 @@ namespace Barotrauma
limb.Disabled = false;
}
wasAiming = aiming;
aiming = false;
wasAimingMelee = aimingMelee;
aimingMelee = false;
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer) return;
}
@@ -1718,7 +1728,7 @@ namespace Barotrauma
}
//TODO: refactor this method, it's way too convoluted
public override void HoldItem(float deltaTime, Item item, Vector2[] handlePos, Vector2 holdPos, Vector2 aimPos, bool aim, float holdAngle, float itemAngleRelativeToHoldAngle = 0.0f)
public override void HoldItem(float deltaTime, Item item, Vector2[] handlePos, Vector2 holdPos, Vector2 aimPos, bool aim, float holdAngle, float itemAngleRelativeToHoldAngle = 0.0f, bool aimingMelee = false)
{
if (character.Stun > 0.0f || character.IsIncapacitated)
{
@@ -1748,6 +1758,8 @@ namespace Barotrauma
Holdable holdable = item.GetComponent<Holdable>();
this.aimingMelee = aimingMelee;
if (!isClimbing && !usingController && character.Stun <= 0.0f && aim && itemPos != Vector2.Zero && !character.IsIncapacitated)
{
Vector2 mousePos = ConvertUnits.ToSimUnits(character.SmoothedCursorPosition);
@@ -1771,7 +1783,6 @@ namespace Barotrauma
aiming = true;
}
}
else
{
@@ -395,12 +395,18 @@ namespace Barotrauma
if (character.IsHusk && character.Params.UseHuskAppendage)
{
bool inEditor = false;
#if CLIENT
inEditor = Screen.Selected == GameMain.CharacterEditorScreen;
#endif
var characterPrefab = CharacterPrefab.FindByFilePath(character.ConfigPath);
if (characterPrefab?.XDocument != null)
{
var mainElement = characterPrefab.XDocument.Root.IsOverride() ? characterPrefab.XDocument.Root.FirstElement() : characterPrefab.XDocument.Root;
foreach (var huskAppendage in mainElement.GetChildElements("huskappendage"))
{
if (!inEditor && huskAppendage.GetAttributeBool("onlyfromafflictions", false)) { continue; }
AfflictionHusk.AttachHuskAppendage(character, huskAppendage.GetAttributeString("affliction", string.Empty), huskAppendage, ragdoll: this);
}
}
@@ -8,7 +8,8 @@ namespace Barotrauma
public enum HitDetection
{
Distance,
Contact
Contact,
None
}
public enum AttackContext
@@ -74,20 +75,6 @@ namespace Barotrauma
}
}
class AttackData
{
public float DamageMultiplier { get; set; } = 1f;
public float AddedPenetration { get; set; } = 0f;
public List<Affliction> Afflictions { get; set; }
public Attack SourceAttack { get; }
public AttackData(Attack sourceAttack)
{
SourceAttack = sourceAttack;
}
}
partial class Attack : ISerializableEntity
{
[Serialize(AttackContext.Any, true, description: "The attack will be used only in this context."), Editable]
@@ -466,7 +453,7 @@ namespace Barotrauma
DamageParticles(deltaTime, worldPosition);
var attackResult = target.AddDamage(attacker, worldPosition, this, deltaTime, playSound);
var attackResult = target?.AddDamage(attacker, worldPosition, this, deltaTime, playSound) ?? new AttackResult();
var effectType = attackResult.Damage > 0.0f ? ActionType.OnUse : ActionType.OnFailure;
if (targetCharacter != null && targetCharacter.IsDead)
{
@@ -9,6 +9,7 @@ using System.Xml.Linq;
using Barotrauma.Items.Components;
using FarseerPhysics.Dynamics;
using Barotrauma.Extensions;
using Barotrauma.Abilities;
#if SERVER
using System.Text;
#endif
@@ -1366,7 +1367,6 @@ namespace Barotrauma
}
}
}
private List<Item> wearableItems = new List<Item>();
public float GetSkillLevel(string skillIdentifier)
{
@@ -2075,11 +2075,10 @@ namespace Barotrauma
return SelectedCharacter == owner && owner.CanInventoryBeAccessed;
}
if (inventory.Owner is Item)
if (inventory.Owner is Item item)
{
var owner = (Item)inventory.Owner;
if (!CanInteractWith(owner) && !owner.linkedTo.Any(lt => lt is Item item && item.DisplaySideBySideWhenLinked && CanInteractWith(item))) { return false; }
ItemContainer container = owner.GetComponents<ItemContainer>().FirstOrDefault(ic => ic.Inventory == inventory);
if (!CanInteractWith(item) && !item.linkedTo.Any(lt => lt is Item item && item.DisplaySideBySideWhenLinked && CanInteractWith(item))) { return false; }
ItemContainer container = item.GetComponents<ItemContainer>().FirstOrDefault(ic => ic.Inventory == inventory);
if (container != null && !container.HasRequiredItems(this, addMessage: false)) { return false; }
}
return true;
@@ -2218,6 +2217,12 @@ namespace Barotrauma
}
}
if (SelectedConstruction?.GetComponent<RemoteController>()?.TargetItem == item ||
HeldItems.Any(it => it.GetComponent<RemoteController>()?.TargetItem == item))
{
return true;
}
if (item.InteractDistance == 0.0f && !item.Prefab.Triggers.Any()) { return false; }
Pickable pickableComponent = item.GetComponent<Pickable>();
@@ -3355,10 +3360,18 @@ namespace Barotrauma
float attackImpulse = attack.TargetImpulse + attack.TargetForce * deltaTime;
AttackData attackData = new AttackData(attack);
attacker.CheckTalents(AbilityEffectType.OnAttack, attackData);
CheckTalents(AbilityEffectType.OnAttacked, attackData);
attackData.DamageMultiplier *= (1 + attacker.GetStatValue(StatTypes.AttackMultiplier));
AbilityAttackData attackData = new AbilityAttackData(attack, this);
if (attacker != null)
{
attackData.Attacker = attacker;
attacker.CheckTalents(AbilityEffectType.OnAttack, attackData);
CheckTalents(AbilityEffectType.OnAttacked, attackData);
attackData.DamageMultiplier *= 1 + attacker.GetStatValue(StatTypes.AttackMultiplier);
if (attacker.TeamID == TeamID)
{
attackData.DamageMultiplier *= 1 + attacker.GetStatValue(StatTypes.TeamAttackMultiplier);
}
}
IEnumerable<Affliction> attackAfflictions;
@@ -3495,6 +3508,7 @@ namespace Barotrauma
{
attackerCrewmember.CheckTalents(AbilityEffectType.OnCrewKillCharacter, target);
}
CheckTalents(AbilityEffectType.OnKillCharacter, target);
if (!IsOnPlayerTeam) { return; }
if (GameMain.Config.KilledCreatures.Any(name => name.Equals(target.SpeciesName, StringComparison.OrdinalIgnoreCase))) { return; }
@@ -3653,10 +3667,7 @@ namespace Barotrauma
if (statusEffect.type != actionType) { continue; }
if (statusEffect.type == ActionType.OnDamaged)
{
if (statusEffect.AllowedAfflictions != null && (LastDamage.Afflictions == null || LastDamage.Afflictions.None(a => statusEffect.AllowedAfflictions.Contains(a.Prefab.AfflictionType) || statusEffect.AllowedAfflictions.Contains(a.Prefab.Identifier))))
{
continue;
}
if (!statusEffect.HasRequiredAfflictions(LastDamage)) { continue; }
if (statusEffect.OnlyPlayerTriggered)
{
if (LastAttacker == null || !LastAttacker.IsPlayer)
@@ -3719,7 +3730,7 @@ namespace Barotrauma
}
}
private void Implode(bool isNetworkMessage = false)
public void Implode(bool isNetworkMessage = false)
{
if (CharacterHealth.Unkillable || GodMode || IsDead) { return; }
@@ -3829,7 +3840,7 @@ namespace Barotrauma
if (info != null)
{
info.CauseOfDeath = CauseOfDeath;
info.ResetSavedStatValues();
info.MissionsCompletedSinceDeath = 0;
}
AnimController.movement = Vector2.Zero;
AnimController.TargetMovement = Vector2.Zero;
@@ -4434,7 +4445,7 @@ namespace Barotrauma
}
}
private StatTypes GetSkillStatType(string skillIdentifier)
public static StatTypes GetSkillStatType(string skillIdentifier)
{
// Using this method to translate between skill identifiers and stat types. Feel free to replace it if there's a better way
switch (skillIdentifier)
@@ -462,6 +462,9 @@ namespace Barotrauma
public bool IsAttachmentsLoaded => HairIndex > -1 && BeardIndex > -1 && MoustacheIndex > -1 && FaceAttachmentIndex > -1;
// talent-relevant values
public int MissionsCompletedSinceDeath = 0;
// Used for creating the data
public CharacterInfo(string speciesName, string name = "", string originalName = "", JobPrefab jobPrefab = null, string ragdollFileName = null, int variant = 0, Rand.RandSync randSync = Rand.RandSync.Unsynced, string npcIdentifier = "")
{
@@ -605,7 +608,10 @@ namespace Barotrauma
if (!string.IsNullOrEmpty(personalityName))
{
personalityTrait = NPCPersonalityTrait.List.Find(p => p.Name == personalityName);
}
}
MissionsCompletedSinceDeath = infoElement.GetAttributeInt("missionscompletedsincedeath", 0);
foreach (XElement subElement in infoElement.Elements())
{
bool jobCreated = false;
@@ -973,16 +979,17 @@ namespace Barotrauma
}
float prevLevel = Job.GetSkillLevel(skillIdentifier);
Job.IncreaseSkillLevel(skillIdentifier, increase);
Job.IncreaseSkillLevel(skillIdentifier, increase, Character.HasAbilityFlag(AbilityFlags.GainSkillPastMaximum));
float newLevel = Job.GetSkillLevel(skillIdentifier);
if ((int)newLevel > (int)prevLevel)
{
Character.CheckTalents(AbilityEffectType.OnGainSkillPoint, skillIdentifier);
foreach (Character character in Character.GetFriendlyCrew(Character))
{
character.CheckTalents(AbilityEffectType.OnAllyGainSkillPoint, (skillIdentifier, Character));
var abilityStringCharacter = new AbilityStringCharacter(skillIdentifier, Character);
character.CheckTalents(AbilityEffectType.OnAllyGainSkillPoint, abilityStringCharacter);
}
}
@@ -1139,9 +1146,10 @@ namespace Barotrauma
new XAttribute("startitemsgiven", StartItemsGiven),
new XAttribute("ragdoll", ragdollFileName),
new XAttribute("personality", personalityTrait == null ? "" : personalityTrait.Name));
// TODO: animations?
charElement.Add(new XAttribute("missionscompletedsincedeath", MissionsCompletedSinceDeath));
if (Character != null)
{
if (Character.AnimController.CurrentHull != null)
@@ -1158,6 +1166,7 @@ namespace Barotrauma
foreach (var savedStat in statValuePair.Value)
{
if (savedStat.StatValue == 0f) { continue; }
if (savedStat.RemoveAfterRound) { continue; }
savedStatElement.Add(new XElement("savedstatvalue",
new XAttribute("stattype", statValuePair.Key.ToString()),
@@ -1168,6 +1177,8 @@ namespace Barotrauma
}
}
charElement.Add(savedStatElement);
parentElement.Add(charElement);
@@ -1496,7 +1507,6 @@ namespace Barotrauma
}
}
}
public void ResetSavedStatValue(string statIdentifier)
{
savedStatValues.SelectMany(s => s.Value).Where(s => s.StatIdentifier == statIdentifier).ForEach(v => v.StatValue = 0f);
@@ -1514,7 +1524,7 @@ namespace Barotrauma
}
}
public void ChangeSavedStatValue(StatTypes statType, float value, string statIdentifier, bool removeOnDeath)
public void ChangeSavedStatValue(StatTypes statType, float value, string statIdentifier, bool removeOnDeath, bool removeAfterRound = false, float maxValue = float.MaxValue)
{
if (!savedStatValues.ContainsKey(statType))
{
@@ -1523,12 +1533,11 @@ namespace Barotrauma
if (savedStatValues[statType].FirstOrDefault(s => s.StatIdentifier == statIdentifier) is SavedStatValue savedStat)
{
savedStat.StatValue += value;
savedStat.RemoveOnDeath = removeOnDeath;
savedStat.StatValue = MathHelper.Min(savedStat.StatValue + value, maxValue);
}
else
{
savedStatValues[statType].Add(new SavedStatValue(statIdentifier, value, removeOnDeath));
savedStatValues[statType].Add(new SavedStatValue(statIdentifier, MathHelper.Min(value, maxValue), removeOnDeath, removeAfterRound));
}
}
}
@@ -1538,12 +1547,14 @@ namespace Barotrauma
public string StatIdentifier { get; set; }
public float StatValue { get; set; }
public bool RemoveOnDeath { get; set; }
public bool RemoveAfterRound { get; set; }
public SavedStatValue(string statIdentifier, float value, bool removeOnDeath)
public SavedStatValue(string statIdentifier, float value, bool removeOnDeath, bool retainAfterRound)
{
StatValue = value;
RemoveOnDeath = removeOnDeath;
StatIdentifier = statIdentifier;
RemoveAfterRound = retainAfterRound;
}
}
}
@@ -17,6 +17,8 @@ namespace Barotrauma
public float PendingAdditionStrength { get; set; }
public float AdditionStrength { get; set; }
private float fluctuationTimer;
protected float _strength;
[Serialize(0f, true), Editable]
@@ -56,6 +58,8 @@ namespace Barotrauma
public readonly Dictionary<AfflictionPrefab.PeriodicEffect, float> PeriodicEffectTimers = new Dictionary<AfflictionPrefab.PeriodicEffect, float>();
public double AppliedAsSuccessfulTreatmentTime, AppliedAsFailedTreatmentTime;
/// <summary>
/// Which character gave this affliction
/// </summary>
@@ -123,7 +127,7 @@ namespace Barotrauma
float amount = MathHelper.Lerp(
currentEffect.MinGrainStrength,
currentEffect.MaxGrainStrength,
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength));
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength)) * GetScreenEffectFluctuation(currentEffect);
if (Prefab.GrainBurst > 0 && AdditionStrength > amount)
{
@@ -138,12 +142,12 @@ namespace Barotrauma
if (Strength < Prefab.ActivationThreshold) { return 0.0f; }
AfflictionPrefab.Effect currentEffect = GetActiveEffect();
if (currentEffect == null) { return 0.0f; }
if (currentEffect.MaxScreenDistortStrength - currentEffect.MinScreenDistortStrength < 0.0f) { return 0.0f; }
if (currentEffect.MaxScreenDistort - currentEffect.MinScreenDistort < 0.0f) { return 0.0f; }
return MathHelper.Lerp(
currentEffect.MinScreenDistortStrength,
currentEffect.MaxScreenDistortStrength,
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength));
currentEffect.MinScreenDistort,
currentEffect.MaxScreenDistort,
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength)) * GetScreenEffectFluctuation(currentEffect);
}
public float GetRadialDistortStrength()
@@ -151,12 +155,12 @@ namespace Barotrauma
if (Strength < Prefab.ActivationThreshold) { return 0.0f; }
AfflictionPrefab.Effect currentEffect = GetActiveEffect();
if (currentEffect == null) { return 0.0f; }
if (currentEffect.MaxRadialDistortStrength - currentEffect.MinRadialDistortStrength < 0.0f) { return 0.0f; }
if (currentEffect.MaxRadialDistort - currentEffect.MinRadialDistort < 0.0f) { return 0.0f; }
return MathHelper.Lerp(
currentEffect.MinRadialDistortStrength,
currentEffect.MaxRadialDistortStrength,
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength));
currentEffect.MinRadialDistort,
currentEffect.MaxRadialDistort,
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength)) * GetScreenEffectFluctuation(currentEffect);
}
public float GetChromaticAberrationStrength()
@@ -164,12 +168,12 @@ namespace Barotrauma
if (Strength < Prefab.ActivationThreshold) { return 0.0f; }
AfflictionPrefab.Effect currentEffect = GetActiveEffect();
if (currentEffect == null) { return 0.0f; }
if (currentEffect.MaxChromaticAberrationStrength - currentEffect.MinChromaticAberrationStrength < 0.0f) { return 0.0f; }
if (currentEffect.MaxChromaticAberration - currentEffect.MinChromaticAberration < 0.0f) { return 0.0f; }
return MathHelper.Lerp(
currentEffect.MinChromaticAberrationStrength,
currentEffect.MaxChromaticAberrationStrength,
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength));
currentEffect.MinChromaticAberration,
currentEffect.MaxChromaticAberration,
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength)) * GetScreenEffectFluctuation(currentEffect);
}
public float GetScreenBlurStrength()
@@ -177,12 +181,18 @@ namespace Barotrauma
if (Strength < Prefab.ActivationThreshold) { return 0.0f; }
AfflictionPrefab.Effect currentEffect = GetActiveEffect();
if (currentEffect == null) { return 0.0f; }
if (currentEffect.MaxScreenBlurStrength - currentEffect.MinScreenBlurStrength < 0.0f) { return 0.0f; }
if (currentEffect.MaxScreenBlur - currentEffect.MinScreenBlur < 0.0f) { return 0.0f; }
return MathHelper.Lerp(
currentEffect.MinScreenBlurStrength,
currentEffect.MaxScreenBlurStrength,
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength));
currentEffect.MinScreenBlur,
currentEffect.MaxScreenBlur,
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength)) * GetScreenEffectFluctuation(currentEffect);
}
private float GetScreenEffectFluctuation(AfflictionPrefab.Effect currentEffect)
{
if (currentEffect == null || currentEffect.ScreenEffectFluctuationFrequency <= 0.0f) { return 1.0f; }
return ((float)Math.Sin(fluctuationTimer * MathHelper.TwoPi) + 1.0f) * 0.5f;
}
public float GetSkillMultiplier()
@@ -210,14 +220,17 @@ namespace Barotrauma
}
}
public float GetResistance(string afflictionId)
public float GetResistance(AfflictionPrefab affliction)
{
if (Strength < Prefab.ActivationThreshold) { return 0.0f; }
AfflictionPrefab.Effect currentEffect = GetActiveEffect();
if (currentEffect == null) { return 0.0f; }
if (currentEffect.MaxResistance - currentEffect.MinResistance <= 0.0f) { return 0.0f; }
if (afflictionId != null && afflictionId != currentEffect.ResistanceFor) { return 0.0f; }
if (!currentEffect.ResistanceFor.Any(r =>
r.Equals(affliction.Identifier, StringComparison.OrdinalIgnoreCase) ||
r.Equals(affliction.AfflictionType, StringComparison.OrdinalIgnoreCase)))
{
return 0.0f;
}
return MathHelper.Lerp(
currentEffect.MinResistance,
currentEffect.MaxResistance,
@@ -229,8 +242,6 @@ namespace Barotrauma
if (Strength < Prefab.ActivationThreshold) { return 1.0f; }
AfflictionPrefab.Effect currentEffect = GetActiveEffect();
if (currentEffect == null) { return 1.0f; }
if (currentEffect.MaxSpeedMultiplier - currentEffect.MinSpeedMultiplier <= 0.0f) { return 1.0f; }
return MathHelper.Lerp(
currentEffect.MinSpeedMultiplier,
currentEffect.MaxSpeedMultiplier,
@@ -282,6 +293,9 @@ namespace Barotrauma
AfflictionPrefab.Effect currentEffect = GetActiveEffect();
if (currentEffect == null) { return; }
fluctuationTimer += deltaTime * currentEffect.ScreenEffectFluctuationFrequency;
fluctuationTimer %= 1.0f;
if (currentEffect.StrengthChange < 0) // Reduce diminishing of buffs if boosted
{
float durationMultiplier = 1 / (1 + (Prefab.IsBuff ? characterHealth.Character.GetStatValue(StatTypes.BuffDurationMultiplier)
@@ -290,9 +304,9 @@ namespace Barotrauma
_strength += currentEffect.StrengthChange * deltaTime * StrengthDiminishMultiplier * durationMultiplier;
}
else // Reduce strengthening of afflictions if resistant
else if (currentEffect.StrengthChange > 0) // Reduce strengthening of afflictions if resistant
{
_strength += currentEffect.StrengthChange * deltaTime * (1f - characterHealth.GetResistance(Prefab.Identifier));
_strength += currentEffect.StrengthChange * deltaTime * (1f - characterHealth.GetResistance(Prefab));
}
// Don't use the property, because it's virtual and some afflictions like husk overload it for external use.
_strength = MathHelper.Clamp(_strength, 0.0f, Prefab.MaxStrength);
@@ -34,6 +34,10 @@ namespace Barotrauma
float threshold = _strength > ActiveThreshold ? ActiveThreshold + 1 : DormantThreshold - 1;
float max = Math.Max(threshold, previousValue);
_strength = Math.Clamp(value, 0, max);
if (previousValue > 0.0f && value <= 0.0f)
{
DeactivateHusk();
}
}
}
@@ -51,8 +55,10 @@ namespace Barotrauma
}
}
private float DormantThreshold => Prefab.MaxStrength * 0.5f;
private float ActiveThreshold => Prefab.MaxStrength * 0.75f;
private float DormantThreshold => (Prefab as AfflictionPrefabHusk)?.DormantThreshold ?? Prefab.MaxStrength * 0.5f;
private float ActiveThreshold => (Prefab as AfflictionPrefabHusk)?.ActiveThreshold ?? Prefab.MaxStrength * 0.75f;
private float TransitionThreshold => (Prefab as AfflictionPrefabHusk)?.TransitionThreshold ?? Prefab.MaxStrength * 0.75f;
public AfflictionHusk(AfflictionPrefab prefab, float strength) : base(prefab, strength) { }
@@ -83,7 +89,7 @@ namespace Barotrauma
}
State = InfectionState.Transition;
}
else if (Strength < Prefab.MaxStrength)
else if (Strength < TransitionThreshold)
{
if (State != InfectionState.Active)
{
@@ -97,6 +97,10 @@ namespace Barotrauma
CauseSpeechImpediment = element.GetAttributeBool("causespeechimpediment", true);
NeedsAir = element.GetAttributeBool("needsair", false);
ControlHusk = element.GetAttributeBool("controlhusk", false);
DormantThreshold = element.GetAttributeFloat("dormantthreshold", MaxStrength * 0.5f);
ActiveThreshold = element.GetAttributeFloat("activethreshold", MaxStrength * 0.75f);
TransitionThreshold = element.GetAttributeFloat("transitionthreshold", MaxStrength);
}
// Use any of these to define which limb the appendage is attached to.
@@ -105,6 +109,8 @@ namespace Barotrauma
public readonly string AttachLimbName;
public readonly LimbType AttachLimbType;
public float ActiveThreshold, DormantThreshold, TransitionThreshold;
public readonly string HuskedSpeciesName;
public readonly string[] TargetSpecies;
public const string Tag = "[speciesname]";
@@ -141,28 +147,31 @@ namespace Barotrauma
public bool MultiplyByMaxVitality { get; private set; }
[Serialize(0.0f, false)]
public float MinScreenBlurStrength { get; private set; }
public float MinScreenBlur { get; private set; }
[Serialize(0.0f, false)]
public float MaxScreenBlurStrength { get; private set; }
public float MaxScreenBlur { get; private set; }
[Serialize(0.0f, false)]
public float MinScreenDistortStrength { get; private set; }
public float MinScreenDistort { get; private set; }
[Serialize(0.0f, false)]
public float MaxScreenDistortStrength { get; private set; }
public float MaxScreenDistort { get; private set; }
[Serialize(0.0f, false)]
public float MinRadialDistortStrength { get; private set; }
public float MinRadialDistort { get; private set; }
[Serialize(0.0f, false)]
public float MaxRadialDistortStrength { get; private set; }
public float MaxRadialDistort { get; private set; }
[Serialize(0.0f, false)]
public float MinChromaticAberrationStrength { get; private set; }
public float MinChromaticAberration { get; private set; }
[Serialize(0.0f, false)]
public float MaxChromaticAberrationStrength { get; private set; }
public float MaxChromaticAberration { get; private set; }
[Serialize("255,255,255,255", false)]
public Color GrainColor { get; private set; }
[Serialize(0.0f, false)]
public float MinGrainStrength { get; private set; }
@@ -170,6 +179,9 @@ namespace Barotrauma
[Serialize(0.0f, false)]
public float MaxGrainStrength { get; private set; }
[Serialize(0.0f, false)]
public float ScreenEffectFluctuationFrequency { get; private set; }
[Serialize(1.0f, false)]
public float MinBuffMultiplier { get; private set; }
@@ -188,8 +200,11 @@ namespace Barotrauma
[Serialize(1.0f, false)]
public float MaxSkillMultiplier { get; private set; }
[Serialize("", false)]
public string ResistanceFor { get; private set; }
private readonly string[] resistanceFor;
public IEnumerable<string> ResistanceFor
{
get { return resistanceFor; }
}
[Serialize(0.0f, false)]
public float MinResistance { get; private set; }
@@ -209,6 +224,8 @@ namespace Barotrauma
{
SerializableProperty.DeserializeProperties(this, element);
resistanceFor = element.GetAttributeStringArray("resistancefor", new string[0], convertToLowerInvariant: true);
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
@@ -266,6 +266,12 @@ namespace Barotrauma
private LimbHealth GetMatchingLimbHealth(Limb limb) => limb == null ? null : limbHealths[limb.HealthIndex];
private LimbHealth GetMatchingLimbHealth(Affliction affliction) => GetMatchingLimbHealth(Character.AnimController.GetLimb(affliction.Prefab.IndicatorLimb, excludeSevered: false));
/// <summary>
/// Returns the limb afflictions and non-limbspecific afflictions that are set to be displayed on this limb.
/// </summary>
private IEnumerable<Affliction> GetMatchingAfflictions(LimbHealth limb)
=> limb.Afflictions.Union(afflictions.Where(a => GetMatchingLimbHealth(a) == limb));
/// <summary>
/// Returns the limb afflictions and non-limbspecific afflictions that are set to be displayed on this limb.
/// </summary>
@@ -426,18 +432,14 @@ namespace Barotrauma
}
}
public float GetResistance(string resistanceId)
public float GetResistance(AfflictionPrefab affliction)
{
float resistance = 0.0f;
for (int i = 0; i < afflictions.Count; i++)
{
if (!afflictions[i].Prefab.IsBuff) continue;
float temp = afflictions[i].GetResistance(resistanceId);
if (temp > resistance) resistance = temp;
resistance += afflictions[i].GetResistance(affliction);
}
resistance = 1 - ((1 - resistance) * Character.GetAbilityResistance(resistanceId));
return resistance;
return 1 - ((1 - resistance) * Character.GetAbilityResistance(affliction.Identifier));
}
public float GetStatValue(StatTypes statType)
@@ -451,7 +453,7 @@ namespace Barotrauma
}
private readonly List<Affliction> matchingAfflictions = new List<Affliction>();
public void ReduceAffliction(Limb targetLimb, string affliction, float amount)
public void ReduceAffliction(Limb targetLimb, string affliction, float amount, ActionType? treatmentAction = null)
{
matchingAfflictions.Clear();
matchingAfflictions.AddRange(afflictions);
@@ -499,6 +501,17 @@ namespace Barotrauma
{
matchingAffliction.Strength -= reduceAmount;
amount -= reduceAmount;
if (treatmentAction != null)
{
if (treatmentAction.Value == ActionType.OnUse)
{
matchingAffliction.AppliedAsSuccessfulTreatmentTime = Timing.TotalTime;
}
else if (treatmentAction.Value == ActionType.OnFailure)
{
matchingAffliction.AppliedAsFailedTreatmentTime = Timing.TotalTime;
}
}
}
}
CalculateVitality();
@@ -610,7 +623,7 @@ namespace Barotrauma
{
if (newAffliction.Prefab == affliction.Prefab)
{
float newStrength = newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(affliction.Prefab.Identifier));
float newStrength = newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(affliction.Prefab));
if (allowStacking)
{
// Add the existing strength
@@ -632,7 +645,7 @@ namespace Barotrauma
//create a new instance of the affliction to make sure we don't use the same instance for multiple characters
//or modify the affliction instance of an Attack or a StatusEffect
var copyAffliction = newAffliction.Prefab.Instantiate(
Math.Min(newAffliction.Prefab.MaxStrength, newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(newAffliction.Prefab.Identifier))),
Math.Min(newAffliction.Prefab.MaxStrength, newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(newAffliction.Prefab))),
newAffliction.Source);
limbHealth.Afflictions.Add(copyAffliction);
@@ -666,7 +679,7 @@ namespace Barotrauma
{
if (newAffliction.Prefab == affliction.Prefab)
{
float newStrength = newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(affliction.Prefab.Identifier));
float newStrength = newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(affliction.Prefab));
if (allowStacking)
{
// Add the existing strength
@@ -688,7 +701,7 @@ namespace Barotrauma
//create a new instance of the affliction to make sure we don't use the same instance for multiple characters
//or modify the affliction instance of an Attack or a StatusEffect
afflictions.Add(newAffliction.Prefab.Instantiate(
Math.Min(newAffliction.Prefab.MaxStrength, newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(newAffliction.Prefab.Identifier))),
Math.Min(newAffliction.Prefab.MaxStrength, newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(newAffliction.Prefab))),
source: newAffliction.Source));
Character.HealthUpdateInterval = 0.0f;
@@ -700,8 +713,6 @@ namespace Barotrauma
}
}
partial void UpdateProjSpecific(float deltaTime);
partial void UpdateLimbAfflictionOverlays();
public void Update(float deltaTime)
@@ -741,7 +752,7 @@ namespace Barotrauma
for (int i = afflictions.Count - 1; i >= 0; i--)
{
var affliction = afflictions[i];
if (irremovableAfflictions.Contains(affliction)) continue;
if (irremovableAfflictions.Contains(affliction)) { continue; }
if (affliction.Strength <= 0.0f)
{
SteamAchievementManager.OnAfflictionRemoved(affliction, Character);
@@ -763,6 +774,10 @@ namespace Barotrauma
{
Character.StackSpeedMultiplier(1f + Character.GetStatValue(StatTypes.SwimmingSpeed));
}
else
{
Character.StackSpeedMultiplier(1f + Character.GetStatValue(StatTypes.WalkingSpeed));
}
UpdateLimbAfflictionOverlays();
@@ -786,7 +801,12 @@ namespace Barotrauma
}
else
{
OxygenAmount = MathHelper.Clamp(OxygenAmount + deltaTime * (Character.OxygenAvailable < InsufficientOxygenThreshold ? -5.0f : 10.0f), -100.0f, 100.0f);
float decreaseSpeed = -5.0f;
float increaseSpeed = 10.0f;
float oxygenlowResistance = GetResistance(oxygenLowAffliction.Prefab);
decreaseSpeed *= (1f - oxygenlowResistance);
increaseSpeed *= (1f + oxygenlowResistance);
OxygenAmount = MathHelper.Clamp(OxygenAmount + deltaTime * (Character.OxygenAvailable < InsufficientOxygenThreshold ? decreaseSpeed : increaseSpeed), -100.0f, 100.0f);
}
UpdateOxygenProjSpecific(prevOxygen, deltaTime);
@@ -807,8 +827,6 @@ namespace Barotrauma
Vitality = MaxVitality;
if (Unkillable || Character.GodMode) { return; }
float damageResistanceMultiplier = 1f - GetResistance("damage");
foreach (LimbHealth limbHealth in limbHealths)
{
foreach (Affliction affliction in limbHealth.Afflictions)
@@ -824,7 +842,6 @@ namespace Barotrauma
{
vitalityDecrease *= limbHealth.VitalityTypeMultipliers[type];
}
vitalityDecrease *= damageResistanceMultiplier;
Vitality -= vitalityDecrease;
affliction.CalculateDamagePerSecond(vitalityDecrease);
}
@@ -833,7 +850,6 @@ namespace Barotrauma
foreach (Affliction affliction in afflictions)
{
float vitalityDecrease = affliction.GetVitalityDecrease(this);
vitalityDecrease *= damageResistanceMultiplier;
Vitality -= vitalityDecrease;
affliction.CalculateDamagePerSecond(vitalityDecrease);
}
@@ -951,13 +967,13 @@ namespace Barotrauma
/// <param name="treatmentSuitability">A dictionary where the key is the identifier of the item and the value the suitability</param>
/// <param name="normalize">If true, the suitability values are normalized between 0 and 1. If not, they're arbitrary values defined in the medical item XML, where negative values are unsuitable, and positive ones suitable.</param>
/// <param name="randomization">Amount of randomization to apply to the values (0 = the values are accurate, 1 = the values are completely random)</param>
public void GetSuitableTreatments(Dictionary<string, float> treatmentSuitability, bool normalize, float randomization = 0.0f)
public void GetSuitableTreatments(Dictionary<string, float> treatmentSuitability, bool normalize, Limb limb = null, float randomization = 0.0f)
{
//key = item identifier
//float = suitability
treatmentSuitability.Clear();
float minSuitability = -10, maxSuitability = 10;
foreach (Affliction affliction in GetAllAfflictions())
foreach (Affliction affliction in getAfflictions(limb))
{
if (affliction.Strength < affliction.Prefab.TreatmentThreshold) { continue; }
foreach (KeyValuePair<string, float> treatment in affliction.Prefab.TreatmentSuitability)
@@ -990,6 +1006,18 @@ namespace Barotrauma
treatmentSuitability[treatment] += Rand.Range(-100.0f, 100.0f) * randomization;
}
}
IEnumerable<Affliction> getAfflictions(Limb limb)
{
if (limb == null)
{
return GetAllAfflictions();
}
else
{
return GetMatchingAfflictions(GetMatchingLimbHealth(limb));
}
}
}
private readonly List<Affliction> activeAfflictions = new List<Affliction>();
@@ -89,11 +89,11 @@ namespace Barotrauma
return (skill == null) ? 0.0f : skill.Level;
}
public void IncreaseSkillLevel(string skillIdentifier, float increase)
public void IncreaseSkillLevel(string skillIdentifier, float increase, bool increasePastMax)
{
if (skills.TryGetValue(skillIdentifier, out Skill skill))
{
skill.Level += increase;
skill.IncreaseSkill(increase, increasePastMax);
}
else
{
@@ -7,11 +7,18 @@ namespace Barotrauma
private float level;
public string Identifier { get; }
public const float MaximumSkill = 100.0f;
public float Level
{
get { return level; }
set { level = MathHelper.Clamp(value, 0.0f, 100.0f); }
set { level = value; }
}
public void IncreaseSkill(float value, bool increasePastMax)
{
level = MathHelper.Clamp(level + value, 0.0f, increasePastMax ? float.MaxValue : MaximumSkill);
}
private Sprite icon;
@@ -868,7 +868,7 @@ namespace Barotrauma
/// </summary>
public bool UpdateAttack(float deltaTime, Vector2 attackSimPos, IDamageable damageTarget, out AttackResult attackResult, float distance = -1, Limb targetLimb = null)
{
attackResult = default(AttackResult);
attackResult = default;
Vector2 simPos = ragdoll.SimplePhysicsEnabled ? character.SimPosition : SimPosition;
float dist = distance > -1 ? distance : ConvertUnits.ToDisplayUnits(Vector2.Distance(simPos, attackSimPos));
bool wasRunning = attack.IsRunning;
@@ -971,7 +971,7 @@ namespace Barotrauma
wasHit = damageTarget != null;
}
if (wasHit)
if (wasHit || attack.HitDetectionType == HitDetection.None)
{
if (character == Character.Controlled || GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
{
@@ -1132,10 +1132,7 @@ namespace Barotrauma
if (statusEffect.type != actionType) { continue; }
if (statusEffect.type == ActionType.OnDamaged)
{
if (statusEffect.AllowedAfflictions != null && (character.LastDamage.Afflictions == null || character.LastDamage.Afflictions.None(a => statusEffect.AllowedAfflictions.Contains(a.Prefab.AfflictionType) || statusEffect.AllowedAfflictions.Contains(a.Prefab.Identifier))))
{
continue;
}
if (!statusEffect.HasRequiredAfflictions(character.LastDamage)) { continue; }
if (statusEffect.OnlyPlayerTriggered)
{
if (character.LastAttacker == null || !character.LastAttacker.IsPlayer)
@@ -15,7 +15,7 @@ namespace Barotrauma.Abilities
private readonly string itemIdentifier;
private readonly string[] tags;
private WeaponType weapontype;
private readonly WeaponType weapontype;
public AbilityConditionAttackData(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
itemIdentifier = conditionElement.GetAttributeString("itemidentifier", "");
@@ -33,7 +33,7 @@ namespace Barotrauma.Abilities
protected override bool MatchesConditionSpecific(object abilityData)
{
if (abilityData is AttackData attackData)
if (abilityData is AbilityAttackData attackData)
{
Item item = attackData?.SourceAttack?.SourceItem;
@@ -71,7 +71,7 @@ namespace Barotrauma.Abilities
}
else
{
LogAbilityConditionError(abilityData, typeof(AttackData));
LogAbilityConditionError(abilityData, typeof(AbilityAttackData));
return false;
}
}
@@ -30,7 +30,7 @@ namespace Barotrauma.Abilities
}
else
{
LogAbilityConditionError(abilityData, typeof(AttackData));
LogAbilityConditionError(abilityData, typeof(AbilityAttackData));
return false;
}
}
@@ -1,7 +1,4 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System;
using System.Xml.Linq;
namespace Barotrauma.Abilities
@@ -1,27 +0,0 @@
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionHandsomeStranger : AbilityConditionData
{
string skillIdentifier;
public AbilityConditionHandsomeStranger(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
skillIdentifier = conditionElement.GetAttributeString("skillidentifier", "").ToLowerInvariant();
}
protected override bool MatchesConditionSpecific(object abilityData)
{
if (abilityData is string skillIdentifier)
{
return this.skillIdentifier == skillIdentifier;
}
else
{
LogAbilityConditionError(abilityData, typeof(string));
return false;
}
}
}
}
@@ -0,0 +1,54 @@
using Barotrauma.Items.Components;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionIsAiming : AbilityConditionDataless
{
private enum WeaponType
{
Any = 0,
Melee = 1,
Ranged = 2
};
private WeaponType weapontype;
public AbilityConditionIsAiming(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
switch (conditionElement.GetAttributeString("weapontype", ""))
{
case "melee":
weapontype = WeaponType.Melee;
break;
case "ranged":
weapontype = WeaponType.Ranged;
break;
}
}
protected override bool MatchesConditionSpecific()
{
bool aimingCorrectItem = false;
if (character.AnimController is HumanoidAnimController animController)
{
foreach (Item item in character.HeldItems)
{
switch (weapontype)
{
case WeaponType.Melee:
aimingCorrectItem |= item.GetComponent<MeleeWeapon>() != null && animController.IsAimingMelee;
break;
case WeaponType.Ranged:
aimingCorrectItem |= item.GetComponent<RangedWeapon>() != null && animController.IsAiming;
break;
default:
aimingCorrectItem |= animController.IsAiming || animController.IsAimingMelee;
break;
}
}
}
return aimingCorrectItem;
}
}
}
@@ -22,10 +22,9 @@ namespace Barotrauma.Abilities
{
item = tempItem.Prefab;
}
// this and other instances of this type of casting will be refactored
else if (abilityData is (ItemPrefab itemPrefab, object _))
else if (abilityData is IAbilityItemPrefab abilityItemPrefab)
{
item = itemPrefab;
item = abilityItemPrefab.ItemPrefab;
}
if (item != null)
@@ -15,17 +15,17 @@ namespace Barotrauma.Abilities
protected override bool MatchesConditionSpecific(object abilityData)
{
if (abilityData is (Affliction affliction, float reduceAmount))
if (abilityData is IAbilityAffliction abilityAffliction)
{
if (allowedTypes.Find(c => c == affliction.Prefab.AfflictionType) == null) { return false; }
if (allowedTypes.Find(c => c == abilityAffliction.Affliction.Prefab.AfflictionType) == null) { return false; }
if (!string.IsNullOrEmpty(identifier) && affliction.Prefab.Identifier != identifier) { return false; }
if (!string.IsNullOrEmpty(identifier) && abilityAffliction.Affliction.Prefab.Identifier != identifier) { return false; }
return true;
}
else
{
LogAbilityConditionError(abilityData, typeof((Affliction, float)));
LogAbilityConditionError(abilityData, typeof(IAbilityAffliction));
return false;
}
}
@@ -0,0 +1,32 @@
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionSkill : AbilityConditionData
{
private readonly string skillIdentifier;
public AbilityConditionSkill(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
skillIdentifier = conditionElement.GetAttributeString("skillidentifier", "").ToLowerInvariant();
}
private bool MatchesConditionSpecific(string skillIdentifier)
{
return this.skillIdentifier == skillIdentifier;
}
protected override bool MatchesConditionSpecific(object abilityData)
{
if ((abilityData as string ?? (abilityData as IAbilityString)?.String) is string skillIdentifier)
{
return MatchesConditionSpecific(skillIdentifier);
}
else
{
LogAbilityConditionError(abilityData, typeof(string));
return false;
}
}
}
}
@@ -1,11 +1,10 @@
using System.Linq;
using System.Xml.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionAboveVitality : AbilityConditionDataless
{
float vitalityPercentage;
private readonly float vitalityPercentage;
public AbilityConditionAboveVitality(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
@@ -0,0 +1,26 @@
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionCoauthor : AbilityConditionDataless
{
private readonly string jobIdentifier;
public AbilityConditionCoauthor(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
jobIdentifier = conditionElement.GetAttributeString("jobidentifier", string.Empty);
}
protected override bool MatchesConditionSpecific()
{
if (character.SelectedCharacter is Character otherCharacter)
{
if (!otherCharacter.HasJob(jobIdentifier)) { return false; }
if (!(character.SelectedBy == otherCharacter)) { return false; }
return true;
}
return false;
}
}
}
@@ -1,8 +1,4 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
@@ -0,0 +1,23 @@
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionHasPermanentStat : AbilityConditionDataless
{
private readonly StatTypes statType;
private readonly float min;
public AbilityConditionHasPermanentStat(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
statType = CharacterAbilityGroup.ParseStatType(conditionElement.GetAttributeString("stattype", ""), characterTalent.DebugIdentifier);
min = conditionElement.GetAttributeFloat("min", 0f);
}
protected override bool MatchesConditionSpecific()
{
// should consider decoupling this from stat values entirely
return character.Info.GetSavedStatValue(statType) >= min;
}
}
}
@@ -0,0 +1,31 @@
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionHasStatusTag : AbilityConditionDataless
{
private readonly string tag;
public AbilityConditionHasStatusTag(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
tag = conditionElement.GetAttributeString("tag", "");
if (string.IsNullOrEmpty(tag))
{
DebugConsole.AddWarning($"Error in talent \"{characterTalent.Prefab.OriginalName}\" - tag not defined in AbilityConditionHasStatusTag.");
}
}
protected override bool MatchesConditionSpecific()
{
if (!string.IsNullOrEmpty(tag))
{
return
StatusEffect.DurationList.Any(d => d.Targets.Contains(character) && d.Parent.HasTag(tag)) ||
DelayedEffect.DelayList.Any(d => d.Targets.Contains(character) && d.Parent.HasTag(tag));
}
return false;
}
}
}
@@ -0,0 +1,15 @@
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionInFriendlySubmarine : AbilityConditionDataless
{
public AbilityConditionInFriendlySubmarine(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { }
protected override bool MatchesConditionSpecific()
{
return character.Submarine?.TeamID == character.TeamID;
}
}
}
@@ -0,0 +1,15 @@
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionInHull : AbilityConditionDataless
{
public AbilityConditionInHull(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement) { }
protected override bool MatchesConditionSpecific()
{
return character.CurrentHull != null;
}
}
}
@@ -0,0 +1,20 @@
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionLevelsBehindHighest : AbilityConditionDataless
{
private readonly int levelsBehind;
public AbilityConditionLevelsBehindHighest(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
{
levelsBehind = conditionElement.GetAttributeInt("levelsbehind", 0);
}
protected override bool MatchesConditionSpecific()
{
return Character.GetFriendlyCrew(character).Where(c => c.Info != null && (c.Info.GetCurrentLevel() - character.Info.GetCurrentLevel() >= levelsBehind)).Any();
}
}
}
@@ -24,13 +24,13 @@ namespace Barotrauma.Abilities
protected override bool MatchesConditionSpecific(object abilityData)
{
if (abilityData is (Mission mission, AbilityValue missionAbilityValue))
if (abilityData is IAbilityMission abilityMission)
{
return mission.Prefab.Type == missionType;
return abilityMission.Mission.Prefab.Type == missionType;
}
else
{
LogAbilityConditionError(abilityData, typeof((Mission, AbilityValue)));
LogAbilityConditionError(abilityData, typeof(IAbilityMission));
return false;
}
}
@@ -1,14 +1,10 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class AbilityConditionServerRandom : AbilityConditionDataless
{
private float randomChance = 0f;
private readonly float randomChance = 0f;
public override bool AllowClientSimulation => false;
public AbilityConditionServerRandom(CharacterTalent characterTalent, XElement conditionElement) : base(characterTalent, conditionElement)
@@ -0,0 +1,32 @@
namespace Barotrauma.Abilities
{
interface IAbilityItemPrefab
{
public ItemPrefab ItemPrefab { get; set; }
}
interface IAbilityValue
{
public float Value { get; set; }
}
interface IAbilityMission
{
public Mission Mission { get; set; }
}
interface IAbilityCharacter
{
public Character Character { get; set; }
}
interface IAbilityString
{
public string String { get; set; }
}
interface IAbilityAffliction
{
public Affliction Affliction { get; set; }
}
}
@@ -0,0 +1,85 @@
using System.Collections.Generic;
namespace Barotrauma.Abilities
{
class AbilityValue : IAbilityValue
{
public AbilityValue(float value)
{
Value = value;
}
public float Value { get; set; }
}
class AbilityValueItem : IAbilityValue, IAbilityItemPrefab
{
public AbilityValueItem(float value, ItemPrefab itemPrefab)
{
Value = value;
ItemPrefab = itemPrefab;
}
public float Value { get; set; }
public ItemPrefab ItemPrefab { get; set; }
}
class AbilityValueString : IAbilityValue, IAbilityString
{
public AbilityValueString(float value, string abilityString)
{
Value = value;
String = abilityString;
}
public float Value { get; set; }
public string String { get; set; }
}
class AbilityStringCharacter : IAbilityCharacter, IAbilityString
{
public AbilityStringCharacter(string abilityString, Character character)
{
String = abilityString;
Character = character;
}
public Character Character { get; set; }
public string String { get; set; }
}
class AbilityValueAffliction : IAbilityValue, IAbilityAffliction
{
public AbilityValueAffliction(float value, Affliction affliction)
{
Value = value;
Affliction = affliction;
}
public float Value { get; set; }
public Affliction Affliction { get; set; }
}
class AbilityValueMission : IAbilityValue, IAbilityMission
{
public AbilityValueMission(float value, Mission mission)
{
Value = value;
Mission = mission;
}
public float Value { get; set; }
public Mission Mission { get; set; }
}
class AbilityAttackData : IAbilityCharacter
{
public float DamageMultiplier { get; set; } = 1f;
public float AddedPenetration { get; set; } = 0f;
public List<Affliction> Afflictions { get; set; }
public Attack SourceAttack { get; }
public Character Character { get; set; }
public Character Attacker { get; set; }
public AbilityAttackData(Attack sourceAttack, Character character)
{
SourceAttack = sourceAttack;
Character = character;
}
}
}
@@ -12,12 +12,16 @@ namespace Barotrauma.Abilities
public CharacterTalent CharacterTalent { get; }
public Character Character { get; }
public virtual bool RequiresAlive => true;
public bool RequiresAlive { get; }
public virtual bool AllowClientSimulation => false;
public virtual bool AppliesEffectOnIntervalUpdate => false;
private const float DefaultEffectTime = 1.0f;
// currently resets if the character dies. would need to be stored in a dictionary of sorts to maintain through death
/// <summary>
/// Used primarily for StatusEffects. Default to constant outside interval abilities.
/// </summary>
@@ -28,6 +32,7 @@ namespace Barotrauma.Abilities
CharacterAbilityGroup = characterAbilityGroup;
CharacterTalent = characterAbilityGroup.CharacterTalent;
Character = CharacterTalent.Character;
RequiresAlive = abilityElement.GetAttributeBool("requiresalive", true);
}
public bool IsViable()
@@ -132,11 +137,5 @@ namespace Barotrauma.Abilities
}
return flagType;
}
public static float DistanceToSquaredDistance(float distance)
{
return distance * distance;
}
}
}
@@ -10,16 +10,41 @@ namespace Barotrauma.Abilities
protected readonly List<StatusEffect> statusEffects;
private readonly bool applyToSelected;
readonly List<ISerializableEntity> targets = new List<ISerializableEntity>();
public CharacterAbilityApplyStatusEffects(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
statusEffects = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffects"));
applyToSelected = abilityElement.GetAttributeBool("applytoselected", false);
}
protected void ApplyEffectSpecific(Character targetCharacter)
{
foreach (var statusEffect in statusEffects)
{
statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, Character, targetCharacter);
if (statusEffect.HasTargetType(StatusEffect.TargetType.UseTarget))
{
// currently used this to spawn items on the targeted character
statusEffect.SetUser(targetCharacter);
statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, targetCharacter, targetCharacter);
}
else if (statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
{
targets.Clear();
targets.AddRange(statusEffect.GetNearbyTargets(targetCharacter.WorldPosition, targets));
statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, targetCharacter, targets);
}
else if (statusEffect.HasTargetType(StatusEffect.TargetType.This))
{
statusEffect.SetUser(Character);
statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, Character, Character);
}
else
{
statusEffect.Apply(ActionType.OnAbility, EffectDeltaTime, Character, targetCharacter);
}
}
}
@@ -30,13 +55,17 @@ namespace Barotrauma.Abilities
protected override void ApplyEffect(object abilityData)
{
if (abilityData is Character targetCharacter)
if (applyToSelected && Character.SelectedCharacter is Character selectedCharacter)
{
ApplyEffectSpecific(selectedCharacter);
}
else if ((abilityData as Character ?? (abilityData as IAbilityCharacter)?.Character) is Character targetCharacter)
{
ApplyEffectSpecific(targetCharacter);
}
else
else
{
ApplyEffect();
ApplyEffect();
}
}
}
@@ -0,0 +1,30 @@
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityApplyStatusEffectsToAllies : CharacterAbilityApplyStatusEffects
{
private readonly bool allowSelf;
public CharacterAbilityApplyStatusEffectsToAllies(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
allowSelf = abilityElement.GetAttributeBool("allowself", true);
}
protected override void ApplyEffect()
{
IEnumerable<Character> chosenCharacters = Character.GetFriendlyCrew(Character).Where(c => allowSelf || c != Character);
foreach (Character character in chosenCharacters)
{
ApplyEffectSpecific(character);
}
}
}
}
@@ -0,0 +1,20 @@
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityApplyStatusEffectsToAttacker : CharacterAbilityApplyStatusEffects
{
public CharacterAbilityApplyStatusEffectsToAttacker(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
}
protected override void ApplyEffect(object abilityData)
{
if ((abilityData as AbilityAttackData)?.Attacker is Character attacker)
{
ApplyEffectSpecific(attacker);
}
}
}
}
@@ -1,6 +1,5 @@
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
using System;
using System.Xml.Linq;
namespace Barotrauma.Abilities
@@ -10,7 +9,7 @@ namespace Barotrauma.Abilities
protected float squaredMaxDistance;
public CharacterAbilityApplyStatusEffectsToNearestAlly(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
squaredMaxDistance = DistanceToSquaredDistance(abilityElement.GetAttributeFloat("maxdistance", float.MaxValue));
squaredMaxDistance = MathF.Pow(abilityElement.GetAttributeFloat("maxdistance", float.MaxValue), 2);
}
protected override void ApplyEffect()
@@ -20,7 +19,7 @@ namespace Barotrauma.Abilities
foreach (Character crewCharacter in Character.GetFriendlyCrew(Character))
{
if (crewCharacter != Character && Vector2.DistanceSquared(Character.SimPosition, Character.GetRelativeSimPosition(crewCharacter)) is float tempDistance && tempDistance < closestDistance)
if (crewCharacter != Character && Vector2.DistanceSquared(Character.WorldPosition, crewCharacter.WorldPosition) is float tempDistance && tempDistance < closestDistance)
{
closestCharacter = crewCharacter;
closestDistance = tempDistance;
@@ -1,5 +1,6 @@
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
@@ -16,7 +17,7 @@ namespace Barotrauma.Abilities
public CharacterAbilityApplyStatusEffectsToRandomAlly(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
squaredMaxDistance = DistanceToSquaredDistance(abilityElement.GetAttributeFloat("maxdistance", float.MaxValue));
squaredMaxDistance = MathF.Pow(abilityElement.GetAttributeFloat("maxdistance", float.MaxValue), 2);
allowDifferentSub = abilityElement.GetAttributeBool("mustbeonsamesub", true);
allowSelf = abilityElement.GetAttributeBool("allowself", true);
}
@@ -26,9 +27,9 @@ namespace Barotrauma.Abilities
Character chosenCharacter = null;
chosenCharacter = Character.GetFriendlyCrew(Character).Where(c =>
(allowSelf ||c != Character) &&
(allowSelf || c != Character) &&
(allowDifferentSub || c.Submarine == Character.Submarine) &&
Vector2.DistanceSquared(Character.SimPosition, Character.GetRelativeSimPosition(c)) is float tempDistance &&
Vector2.DistanceSquared(Character.WorldPosition, c.WorldPosition) is float tempDistance &&
tempDistance < squaredMaxDistance).GetRandom();
if (chosenCharacter == null) { return; }
@@ -7,16 +7,41 @@ namespace Barotrauma.Abilities
{
public override bool AppliesEffectOnIntervalUpdate => true;
private int amount;
private readonly int amount;
private StatTypes scalingStatType;
public CharacterAbilityGiveMoney(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
amount = abilityElement.GetAttributeInt("amount", 0);
scalingStatType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("scalingstattype", "None"), CharacterTalent.DebugIdentifier);
}
private void ApplyEffectSpecific(Character targetCharacter)
{
float multiplier = 1f;
if (scalingStatType != StatTypes.None)
{
multiplier = 0 + Character.Info.GetSavedStatValue(scalingStatType);
}
targetCharacter.GiveMoney((int)(multiplier * amount));
}
protected override void ApplyEffect(object abilityData)
{
if ((abilityData as Character ?? (abilityData as IAbilityCharacter)?.Character) is Character targetCharacter)
{
ApplyEffectSpecific(targetCharacter);
}
else
{
ApplyEffectSpecific(Character);
}
}
protected override void ApplyEffect()
{
Character.GiveMoney(amount);
ApplyEffectSpecific(Character);
}
}
}
@@ -8,8 +8,10 @@ namespace Barotrauma.Abilities
private readonly string statIdentifier;
private readonly StatTypes statType;
private readonly float value;
private readonly float maxValue;
private readonly bool targetAllies;
private readonly bool removeOnDeath;
private readonly bool removeAfterRound;
//private readonly float maximumValue;
public override bool AppliesEffectOnIntervalUpdate => true;
@@ -19,8 +21,10 @@ namespace Barotrauma.Abilities
statIdentifier = abilityElement.GetAttributeString("statidentifier", "").ToLowerInvariant();
statType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("stattype", ""), CharacterTalent.DebugIdentifier);
value = abilityElement.GetAttributeFloat("value", 0f);
maxValue = abilityElement.GetAttributeFloat("maxvalue", float.MaxValue);
targetAllies = abilityElement.GetAttributeBool("targetallies", false);
removeOnDeath = abilityElement.GetAttributeBool("removeondeath", true);
removeAfterRound = abilityElement.GetAttributeBool("removeafterround", false);
//maximumValue = abilityElement.GetAttributeFloat("maximumvalue", float.MaxValue);
}
@@ -38,11 +42,11 @@ namespace Barotrauma.Abilities
{
if (targetAllies)
{
Character.GetFriendlyCrew(Character).ForEach(c => c?.Info.ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath));
Character.GetFriendlyCrew(Character).ForEach(c => c?.Info.ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath, removeAfterRound, maxValue));
}
else
{
Character?.Info.ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath);
Character?.Info.ChangeSavedStatValue(statType, value, statIdentifier, removeOnDeath, removeAfterRound, maxValue);
}
}
}
@@ -4,18 +4,23 @@ namespace Barotrauma.Abilities
{
class CharacterAbilityGiveResistance : CharacterAbility
{
private string resistanceId;
private float resistance;
private readonly string resistanceId;
private readonly float multiplier;
public CharacterAbilityGiveResistance(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
resistanceId = abilityElement.GetAttributeString("resistanceid", "");
resistance = abilityElement.GetAttributeFloat("resistance", 1f);
multiplier = abilityElement.GetAttributeFloat("multiplier", 1f);
if (string.IsNullOrEmpty(resistanceId))
{
DebugConsole.ThrowError("Error in CharacterAbilityGiveResistance - resistance identifier not set.");
}
}
public override void InitializeAbility(bool addingFirstTime)
{
Character.ChangeAbilityResistance(resistanceId, resistance);
Character.ChangeAbilityResistance(resistanceId, multiplier);
}
}
}
@@ -4,10 +4,9 @@ namespace Barotrauma.Abilities
{
class CharacterAbilityGiveStat : CharacterAbility
{
private StatTypes statType;
private float value;
private readonly StatTypes statType;
private readonly float value;
// this and resistance giving should probably be moved directly to charactertalent attributes, as they don't need to interact with either ability group types
public CharacterAbilityGiveStat(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
statType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("stattype", ""), CharacterTalent.DebugIdentifier);
@@ -7,8 +7,9 @@ namespace Barotrauma.Abilities
{
private readonly List<Affliction> afflictions;
float addedDamageMultiplier;
float addedPenetration;
private readonly float addedDamageMultiplier;
private readonly float addedPenetration;
private readonly bool implode;
public CharacterAbilityModifyAttackData(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
@@ -18,11 +19,12 @@ namespace Barotrauma.Abilities
}
addedDamageMultiplier = abilityElement.GetAttributeFloat("addeddamagemultiplier", 0f);
addedPenetration = abilityElement.GetAttributeFloat("addedpenetration", 0f);
implode = abilityElement.GetAttributeBool("implode", false);
}
protected override void ApplyEffect(object abilityData)
{
if (abilityData is AttackData attackData)
if (abilityData is AbilityAttackData attackData)
{
if (attackData.Afflictions == null)
{
@@ -34,6 +36,13 @@ namespace Barotrauma.Abilities
}
attackData.DamageMultiplier += addedDamageMultiplier;
attackData.AddedPenetration += addedPenetration;
if (implode)
{
// might have issues, as the method used to be private and only used for pressure death
attackData.Character?.Implode();
}
}
else
{
@@ -4,8 +4,8 @@ namespace Barotrauma.Abilities
{
class CharacterAbilityModifyResistance : CharacterAbility
{
private string resistanceId;
private float resistance;
private readonly string resistanceId;
private readonly float resistance;
bool lastState;
// should probably be split to different classes
@@ -13,6 +13,11 @@ namespace Barotrauma.Abilities
{
resistanceId = abilityElement.GetAttributeString("resistanceid", "");
resistance = abilityElement.GetAttributeFloat("resistance", 1f);
if (string.IsNullOrEmpty(resistanceId))
{
DebugConsole.ThrowError("Error in CharacterAbilityModifyResistance - resistance identifier not set.");
}
}
public override void UpdateCharacterAbility(bool conditionsMatched, float timeSinceLastUpdate)
@@ -0,0 +1,52 @@
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityModifyStatToSkill : CharacterAbility
{
private readonly StatTypes statType;
private readonly float maxValue;
private readonly string skillIdentifier;
private readonly bool useAll;
private float lastValue = 0f;
public CharacterAbilityModifyStatToSkill(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
statType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("stattype", ""), CharacterTalent.DebugIdentifier);
maxValue = abilityElement.GetAttributeFloat("maxvalue", 0f);
skillIdentifier = abilityElement.GetAttributeString("skillidentifier", string.Empty);
useAll = skillIdentifier == "all";
}
protected override void VerifyState(bool conditionsMatched, float timeSinceLastUpdate)
{
Character.ChangeStat(statType, -lastValue);
if (conditionsMatched)
{
float skillTotal = 0f;
if (useAll && Character.Info?.Job != null)
{
foreach (Skill skill in Character.Info.Job.Skills)
{
skillTotal += Character.GetSkillLevel(skill.Identifier);
}
skillTotal /= Character.Info.Job.Skills.Count;
}
else
{
skillTotal = Character.GetSkillLevel(skillIdentifier);
}
lastValue = skillTotal / 100f * maxValue;
Character.ChangeStat(statType, lastValue);
}
else
{
lastValue = 0f;
}
}
}
}
@@ -5,42 +5,21 @@ namespace Barotrauma.Abilities
class CharacterAbilityModifyValue : CharacterAbility
{
private float addedValue;
private float multiplierValue;
private float multiplyValue;
public CharacterAbilityModifyValue(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
addedValue = abilityElement.GetAttributeFloat("addedvalue", 0f);
multiplierValue = abilityElement.GetAttributeFloat("multipliervalue", 1f);
multiplyValue = abilityElement.GetAttributeFloat("multiplyvalue", 1f);
}
protected override void ApplyEffect(object abilityData)
{
if (abilityData is AbilityValue abilityValue)
if (abilityData is IAbilityValue abilityValue)
{
ApplyEffectSpecific(abilityValue);
abilityValue.Value += addedValue;
abilityValue.Value *= multiplyValue;
}
else if (abilityData is (object _, AbilityValue tupleAbilityValue))
{
ApplyEffectSpecific(tupleAbilityValue);
}
}
private void ApplyEffectSpecific(AbilityValue abilityValue)
{
abilityValue.Value += addedValue;
abilityValue.Value *= multiplierValue;
}
}
// this seems like a real silly way to have to pass values by reference into these same interfaces
// if more of these are required, maybe there should be an additional set of interfaces to easily pass values by reference instead
class AbilityValue
{
public float Value { get; set; }
public AbilityValue(float value)
{
Value = value;
}
}
}
@@ -5,7 +5,7 @@ namespace Barotrauma.Abilities
class CharacterAbilityResetPermanentStat : CharacterAbility
{
private readonly string statIdentifier;
public override bool RequiresAlive => false;
public override bool AppliesEffectOnIntervalUpdate => true;
public CharacterAbilityResetPermanentStat(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
@@ -0,0 +1,29 @@
using Microsoft.Xna.Framework;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityRevive : CharacterAbility
{
public override bool AppliesEffectOnIntervalUpdate => true;
public CharacterAbilityRevive(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
}
private void ApplyEffectSpecific()
{
Character.Revive();
}
protected override void ApplyEffect()
{
ApplyEffectSpecific();
}
protected override void ApplyEffect(object abilityData)
{
ApplyEffectSpecific();
}
}
}
@@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityAlienHoarder : CharacterAbility
{
private readonly float addedDamageMultiplierPerItem;
private readonly int maxAmount;
private readonly string[] tags;
public CharacterAbilityAlienHoarder(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
addedDamageMultiplierPerItem = abilityElement.GetAttributeFloat("addeddamagemultiplierperitem", 0f);
maxAmount = abilityElement.GetAttributeInt("maxamount", 0);
tags = abilityElement.GetAttributeStringArray("tags", Array.Empty<string>(), convertToLowerInvariant: true);
}
protected override void ApplyEffect(object abilityData)
{
if (abilityData is AbilityAttackData attackData)
{
float totalAddedDamageMultiplier = 0f;
foreach (Item item in Character.Inventory.AllItems)
{
if (tags.Any(t => item.Prefab.Tags.Any(p => t == p)))
{
totalAddedDamageMultiplier += addedDamageMultiplierPerItem;
}
}
attackData.DamageMultiplier += addedDamageMultiplierPerItem;
}
else
{
LogAbilityDataMismatch();
}
}
}
}
@@ -12,9 +12,9 @@ namespace Barotrauma.Abilities
protected override void ApplyEffect(object abilityData)
{
if (abilityData is (string skillIdentifier, Character character) && character != Character)
if (abilityData is AbilityStringCharacter abilityStringCharacter && abilityStringCharacter.Character != Character)
{
character.Info?.IncreaseSkillLevel(skillIdentifier, 1.0f, character.Position + Vector2.UnitY * 175.0f);
Character.Info?.IncreaseSkillLevel(abilityStringCharacter.String, 1.0f, abilityStringCharacter.Character.Position + Vector2.UnitY * 175.0f);
}
}
}
@@ -0,0 +1,36 @@
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Abilities
{
class CharacterAbilityByTheBook : CharacterAbility
{
private int moneyAmount;
private int max;
public CharacterAbilityByTheBook(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
moneyAmount = abilityElement.GetAttributeInt("moneyamount", 0);
max = abilityElement.GetAttributeInt("max", 0);
}
protected override void ApplyEffect()
{
IEnumerable<Character> enemyCharacters = Character.CharacterList.Where(c => c.TeamID == CharacterTeamType.None);
int timesGiven = 0;
foreach (Character enemyCharacter in enemyCharacters)
{
if (!enemyCharacter.IsHuman) { continue; }
if (enemyCharacter.Submarine == null || enemyCharacter.Submarine != Submarine.MainSub) { continue; }
if (enemyCharacter.IsDead) { continue; }
if (!enemyCharacter.LockHands) { continue; }
if (timesGiven > max) { continue; }
Character.GiveMoney(moneyAmount);
timesGiven++;
}
}
}
}
@@ -9,45 +9,22 @@ namespace Barotrauma.Abilities
class CharacterAbilityInsurancePolicy : CharacterAbility
{
public override bool AppliesEffectOnIntervalUpdate => true;
public override bool RequiresAlive => false;
private readonly int moneyPerLevel;
private bool hasOccurred = false;
private readonly int moneyPerMission;
private static List<Client> clientsAlreadyUsed = new List<Client>();
public CharacterAbilityInsurancePolicy(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
moneyPerLevel = abilityElement.GetAttributeInt("moneyperlevel", 0);
moneyPerMission = abilityElement.GetAttributeInt("moneypermission", 0);
}
protected override void ApplyEffect()
{
if (Character?.Info is CharacterInfo info && !hasOccurred)
if (Character?.Info is CharacterInfo info)
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
{
foreach (Client client in GameMain.NetworkMember.ConnectedClients)
{
if (client.Character == Character && clientsAlreadyUsed.Contains(client)) { return; }
}
}
Character.GiveMoney(moneyPerLevel * info.GetCurrentLevel());
hasOccurred = true;
// this is an ugly way to do this, but this effect should not occur more than once per round for a client
// this seemed like the simplest way to do it since characters are instantiated from scratch each time
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
{
foreach (Client client in GameMain.NetworkMember.ConnectedClients)
{
if (client.Character == Character)
{
clientsAlreadyUsed.Add(client);
}
}
}
Character.GiveMoney(moneyPerMission * info.MissionsCompletedSinceDeath);
}
}
}
@@ -5,14 +5,14 @@ namespace Barotrauma.Abilities
class CharacterAbilityPsychoClown : CharacterAbility
{
private StatTypes statType;
private float value;
private float maxValue;
private string afflictionIdentifier;
private float lastValue = 0f;
public CharacterAbilityPsychoClown(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
statType = CharacterAbilityGroup.ParseStatType(abilityElement.GetAttributeString("stattype", ""), CharacterTalent.DebugIdentifier);
value = abilityElement.GetAttributeFloat("value", 0f);
maxValue = abilityElement.GetAttributeFloat("maxvalue", 0f);
afflictionIdentifier = abilityElement.GetAttributeString("afflictionidentifier", "");
}
@@ -32,7 +32,7 @@ namespace Barotrauma.Abilities
afflictionStrength = affliction.Strength / affliction.Prefab.MaxStrength;
}
lastValue = afflictionStrength * value;
lastValue = afflictionStrength * maxValue;
Character.ChangeStat(statType, lastValue);
}
else
@@ -8,6 +8,8 @@ namespace Barotrauma.Abilities
{
class CharacterAbilityRegenerateLoot : CharacterAbility
{
// not maintained through death, so it's possible for players to respawn and re-loot chests
// seems like a minor issue for now
List<Item> openedContainers = new List<Item>();
public CharacterAbilityRegenerateLoot(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
@@ -10,20 +10,20 @@ namespace Barotrauma.Abilities
{
private readonly List<StatusEffect> statusEffects;
private readonly List<StatusEffect> statusEffectsReset;
private int maxEnemyCount;
private float squaredDistance;
private readonly int maxEnemyCount;
private readonly float squaredDistance;
public CharacterAbilityStonewall(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
statusEffects = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffects"));
statusEffectsReset = CharacterAbilityGroup.ParseStatusEffects(CharacterTalent, abilityElement.GetChildElement("statuseffectsreset"));
maxEnemyCount = abilityElement.GetAttributeInt("maxenemycount", 0);
squaredDistance = DistanceToSquaredDistance(abilityElement.GetAttributeFloat("distance", 0));
squaredDistance = MathF.Pow(abilityElement.GetAttributeFloat("distance", 0), 2);
}
protected override void VerifyState(bool conditionsMatched, float timeSinceLastUpdate)
{
int numberOfEnemiesInRange = Character.CharacterList.Where(c => !HumanAIController.IsFriendly(Character, c) && !c.IsDead && Vector2.DistanceSquared(Character.SimPosition, Character.GetRelativeSimPosition(c)) < squaredDistance).Count();
int numberOfEnemiesInRange = Character.CharacterList.Count(c => !HumanAIController.IsFriendly(Character, c) && !c.IsDead && Vector2.DistanceSquared(Character.WorldPosition, c.WorldPosition) < squaredDistance);
foreach (var statusEffect in statusEffectsReset)
{
@@ -8,6 +8,7 @@ namespace Barotrauma.Abilities
{
class CharacterAbilityTandemFire : CharacterAbilityApplyStatusEffectsToNearestAlly
{
// this should just be its own class, misleading to inherit here
private string tag;
public CharacterAbilityTandemFire(CharacterAbilityGroup characterAbilityGroup, XElement abilityElement) : base(characterAbilityGroup, abilityElement)
{
@@ -14,6 +14,10 @@ namespace Barotrauma.Abilities
// currently only used to turn off simulation if random conditions are in use
public bool IsActive { get; private set; } = true;
protected int maxTriggerCount { get; }
protected int timesTriggered = 0;
// add support for OR conditions?
protected readonly List<AbilityCondition> abilityConditions = new List<AbilityCondition>();
@@ -24,7 +28,7 @@ namespace Barotrauma.Abilities
{
CharacterTalent = characterTalent;
Character = CharacterTalent.Character;
maxTriggerCount = abilityElementGroup.GetAttributeInt("maxtriggercount", int.MaxValue);
foreach (XElement subElement in abilityElementGroup.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
@@ -27,6 +27,7 @@ namespace Barotrauma.Abilities
private bool IsApplicable(object abilityData)
{
if (timesTriggered >= maxTriggerCount) { return false; }
return abilityConditions.All(c => c.MatchesCondition(abilityData));
}
}
@@ -14,6 +14,7 @@ namespace Barotrauma.Abilities
private float effectDelay;
private float effectDelayTimer;
public CharacterAbilityGroupInterval(CharacterTalent characterTalent, XElement abilityElementGroup) : base(characterTalent, abilityElementGroup)
{
// too many overlapping intervals could cause hitching? maybe randomize a little
@@ -42,6 +43,7 @@ namespace Barotrauma.Abilities
}
private bool IsApplicable()
{
if (timesTriggered >= maxTriggerCount) { return false; }
return abilityConditions.All(c => c.MatchesCondition());
}
}
@@ -111,8 +111,7 @@ namespace Barotrauma
public static AbilityEffectType ParseAbilityEffectType(CharacterTalent characterTalent, string abilityEffectTypeString)
{
AbilityEffectType abilityEffectType = AbilityEffectType.Undefined;
if (!Enum.TryParse(abilityEffectTypeString, true, out abilityEffectType))
if (!Enum.TryParse(abilityEffectTypeString, true, out AbilityEffectType abilityEffectType))
{
DebugConsole.ThrowError("Invalid ability effect type \"" + abilityEffectTypeString + "\" in CharacterTalent (" + characterTalent.DebugIdentifier + ")");
}
@@ -1,7 +1,5 @@
using Microsoft.Xna.Framework;
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
@@ -13,6 +11,12 @@ namespace Barotrauma
public ContentPackage ContentPackage { get; private set; }
public string FilePath { get; private set; }
public string DisplayName { get; private set; }
public string Description { get; private set; }
public readonly Sprite Icon;
public static readonly PrefabCollection<TalentPrefab> TalentPrefabs = new PrefabCollection<TalentPrefab>();
public XElement ConfigElement
@@ -26,7 +30,51 @@ namespace Barotrauma
FilePath = filePath;
ConfigElement = element;
Identifier = element.GetAttributeString("identifier", "noidentifier");
DisplayName = TextManager.Get("talentname." + Identifier, returnNull: true) ?? Identifier;
this.CalculatePrefabUIntIdentifier(TalentPrefabs);
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "icon":
Icon = new Sprite(subElement);
break;
case "description":
string tempDescription = Description;
TextManager.ConstructDescription(ref tempDescription, subElement);
Description = tempDescription;
break;
}
}
if (string.IsNullOrEmpty(Description))
{
if (element.Attribute("description") != null)
{
string description = element.GetAttributeString("description", string.Empty);
Description = TextManager.Get(description, returnNull: true) ?? description;
}
else
{
Description = TextManager.Get("talentdescription." + Identifier, returnNull: true) ?? string.Empty;
}
}
#if DEBUG
if (!TextManager.ContainsTag("talentname." + Identifier))
{
DebugConsole.AddWarning($"Name for the talent \"{Identifier}\" not found in the text files.");
}
if (string.IsNullOrEmpty(Description))
{
DebugConsole.AddWarning($"Description for the talent \"{Identifier}\" not configured");
}
if (Description.Contains('['))
{
DebugConsole.ThrowError($"Description for the talent \"{Identifier}\" contains brackets - was some variable not replaced correctly? ({Description})");
}
#endif
}
private bool disposed = false;
@@ -94,9 +142,6 @@ namespace Barotrauma
{
LoadFromFile(file);
}
}
}
}
@@ -14,6 +14,7 @@ namespace Barotrauma
private static HashSet<string> subtreeTalents = new HashSet<string>();
private const string PlaceholderTalent = "placeholder";
public XElement ConfigElement
{
get;
@@ -41,6 +42,7 @@ namespace Barotrauma
HashSet<string> duplicateSet = new HashSet<string>();
foreach (string talent in TalentSubTrees.SelectMany(s => s.TalentOptionStages.SelectMany(o => o.Talents.Select(t => t.Identifier))))
{
if (talent == PlaceholderTalent) { continue; }
TalentPrefab talentPrefab = TalentPrefab.TalentPrefabs.Find(c => c.Identifier.Equals(talent, StringComparison.OrdinalIgnoreCase));
if (talentPrefab == null)
{
@@ -118,6 +120,7 @@ namespace Barotrauma
public static bool IsViableTalentForCharacter(Character character, string talentIdentifier, IEnumerable<string> selectedTalents)
{
if (talentIdentifier == PlaceholderTalent) { return false; }
if (character?.Info?.Job.Prefab == null) { return false; }
if (character.Info.GetTotalTalentPoints() - selectedTalents.Count() <= 0) { return false; }
@@ -178,7 +181,7 @@ namespace Barotrauma
foreach (XElement talentOptionsElement in subTreeElement.GetChildElements("talentoptions"))
{
TalentOptionStages.Add(new TalentOption(talentOptionsElement));
TalentOptionStages.Add(new TalentOption(talentOptionsElement, Identifier));
}
}
@@ -186,32 +189,19 @@ namespace Barotrauma
class TalentOption
{
public readonly List<Talent> Talents = new List<Talent>();
public readonly List<TalentPrefab> Talents = new List<TalentPrefab>();
public TalentOption(XElement talentOptionsElement)
public TalentOption(XElement talentOptionsElement, string debugIdentifier)
{
foreach (XElement talentOptionElement in talentOptionsElement.GetChildElements("talentoption"))
{
Talents.Add(new Talent(talentOptionElement));
}
}
}
class Talent
{
public readonly string Identifier;
public readonly Sprite Icon;
public Talent(XElement talentOptionElement)
{
Identifier = talentOptionElement.GetAttributeString("identifier", "");
foreach (XElement subElement in talentOptionElement.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
string identifier = talentOptionElement.GetAttributeString("identifier", string.Empty);
if (!TalentPrefab.TalentPrefabs.ContainsKey(identifier))
{
case "icon":
Icon = new Sprite(subElement);
break;
DebugConsole.ThrowError($"Error in talent tree \"{debugIdentifier}\" - could not find a talent with the identifier \"{identifier}\".");
return;
}
Talents.Add(TalentPrefab.TalentPrefabs[identifier]);
}
}
}
@@ -212,7 +212,7 @@ namespace Barotrauma
};
}, isCheat: true));
commands.Add(new Command("spawnitem", "spawnitem [itemname/itemidentifier] [cursor/inventory/cargo/random/[name]]: Spawn an item at the position of the cursor, in the inventory of the controlled character, in the inventory of the client with the given name, or at a random spawnpoint if the last parameter is omitted or \"random\".",
commands.Add(new Command("spawnitem", "spawnitem [itemname/itemidentifier] [cursor/inventory/cargo/random/[name]] [amount]: Spawn an item at the position of the cursor, in the inventory of the controlled character, in the inventory of the client with the given name, or at a random spawnpoint if the last parameter is omitted or \"random\".",
(string[] args) =>
{
try
@@ -841,8 +841,8 @@ namespace Barotrauma
commands.Add(new Command("givetalent", "give [player] testing [talent]", (string[] args) =>
{
if (args.Length < 2) return;
var character = FindMatchingCharacter(args.Skip(1).ToArray()) ?? Character.Controlled;
if (args.Length == 0) { return; }
var character = args.Length >= 2 ? FindMatchingCharacter(args.Skip(1).ToArray()) : Character.Controlled;
if (character != null)
{
character.GiveTalent(args[0]);
@@ -858,8 +858,8 @@ namespace Barotrauma
return new string[][]
{
talentNames.ToArray(),
Character.CharacterList.Select(c => c.Name).Distinct().ToArray()
talentNames.ToArray(),
Character.CharacterList.Select(c => c.Name).Distinct().ToArray()
};
}, isCheat: true));
@@ -2033,9 +2033,17 @@ namespace Barotrauma
return;
}
int amount = 1;
if (args.Length > 1)
{
switch (args.Last())
string spawnLocation = args.Last();
if (args.Length > 2)
{
spawnLocation = args[^2];
if (!int.TryParse(args[^1], NumberStyles.Any, CultureInfo.InvariantCulture, out amount)) { amount = 1; }
}
switch (spawnLocation)
{
case "cursor":
spawnPos = cursorPos;
@@ -2063,37 +2071,40 @@ namespace Barotrauma
spawnPos = wp == null ? Vector2.Zero : wp.WorldPosition;
}
if (spawnPos != null)
for (int i = 0; i < amount; i++)
{
if (Entity.Spawner == null)
if (spawnPos != null)
{
new Item(itemPrefab, spawnPos.Value, null);
}
else
{
Entity.Spawner?.AddToSpawnQueue(itemPrefab, spawnPos.Value);
}
}
else if (spawnInventory != null)
{
if (Entity.Spawner == null)
{
var spawnedItem = new Item(itemPrefab, Vector2.Zero, null);
spawnInventory.TryPutItem(spawnedItem, null, spawnedItem.AllowedSlots);
onItemSpawned(spawnedItem);
}
else
{
Entity.Spawner?.AddToSpawnQueue(itemPrefab, spawnInventory, onSpawned: onItemSpawned);
}
static void onItemSpawned(Item item)
{
if (item.ParentInventory?.Owner is Character character)
if (Entity.Spawner == null)
{
foreach (WifiComponent wifiComponent in item.GetComponents<WifiComponent>())
new Item(itemPrefab, spawnPos.Value, null);
}
else
{
Entity.Spawner?.AddToSpawnQueue(itemPrefab, spawnPos.Value);
}
}
else if (spawnInventory != null)
{
if (Entity.Spawner == null)
{
var spawnedItem = new Item(itemPrefab, Vector2.Zero, null);
spawnInventory.TryPutItem(spawnedItem, null, spawnedItem.AllowedSlots);
onItemSpawned(spawnedItem);
}
else
{
Entity.Spawner?.AddToSpawnQueue(itemPrefab, spawnInventory, onSpawned: onItemSpawned);
}
static void onItemSpawned(Item item)
{
if (item.ParentInventory?.Owner is Character character)
{
wifiComponent.TeamID = character.TeamID;
foreach (WifiComponent wifiComponent in item.GetComponents<WifiComponent>())
{
wifiComponent.TeamID = character.TeamID;
}
}
}
}
@@ -45,14 +45,18 @@
OnReduceAffliction,
OnAddDamageAffliction,
OnSelfRagdoll,
OnRoundEnd,
OnAnyMissionCompleted,
OnAllMissionsCompleted,
OnGiveOrder,
OnCrewKillCharacter,
OnKillCharacter,
OnDieToCharacter,
OnAllyGainMissionExperience,
OnGainMissionExperience,
OnGainMissionMoney,
OnItemDeconstructed,
OnItemDeconstructedMaterial,
AfterSubmarineAttacked,
}
@@ -68,23 +72,32 @@
// Character attributes
MaximumHealthMultiplier,
MovementSpeed,
WalkingSpeed,
SwimmingSpeed,
BuffDurationMultiplier,
DebuffDurationMultiplier,
MedicalItemEffectivenessMultiplier,
// Combat
AttackMultiplier,
TeamAttackMultiplier,
RangedAttackSpeed,
TurretAttackSpeed,
TurretPowerCostReduction,
MeleeAttackSpeed,
SpreadMultiplier,
MeleeAttackMultiplier,
RangedSpreadReduction,
// Utility
RepairSpeed,
DeconstructorSpeedMultiplier,
// Misc
ReputationGainMultiplier,
MissionMoneyGainMultiplier,
ExperienceGainMultiplier,
MissionExperienceGainMultiplier,
// these should be deprecated and moved to their own implementation, no sense making them share space with stat values
Coathor,
WarriorPoetMissionRuns,
WarriorPoetEnemiesKilled,
}
public enum AbilityFlags
@@ -95,6 +108,8 @@
IgnoredByEnemyAI,
MoveNormallyWhileDragging,
CanTinker,
GainSkillPastMaximum,
RetainExperienceForNewCharacter
}
}
@@ -362,11 +362,16 @@ namespace Barotrauma
}
// apply money gains afterwards to prevent them from affecting XP gains
var moneyGainMultiplier = new AbilityValue(1f);
crewCharacters.ForEach(c => c.CheckTalents(AbilityEffectType.OnGainMissionMoney, (this, moneyGainMultiplier)));
crewCharacters.ForEach(c => moneyGainMultiplier.Value += c.GetStatValue(StatTypes.MissionMoneyGainMultiplier));
var moneyGainMission = new AbilityValueMission(1f, this);
crewCharacters.ForEach(c => c.CheckTalents(AbilityEffectType.OnGainMissionMoney, moneyGainMission));
crewCharacters.ForEach(c => moneyGainMission.Value += c.GetStatValue(StatTypes.MissionMoneyGainMultiplier));
campaign.Money += (int)(reward * moneyGainMultiplier.Value);
campaign.Money += (int)(reward * moneyGainMission.Value);
foreach (Character character in crewCharacters)
{
character.Info.MissionsCompletedSinceDeath++;
}
foreach (KeyValuePair<string, float> reputationReward in ReputationRewards)
{
@@ -531,6 +531,7 @@ namespace Barotrauma
if (Level.Loaded.EndOutpost == null)
{
Submarine closestSub = Submarine.FindClosest(Level.Loaded.EndExitPosition, ignoreOutposts: true, ignoreRespawnShuttle: true, teamType: leavingPlayers.FirstOrDefault()?.TeamID);
if (closestSub == null) { return null; }
return closestSub.DockedTo.Contains(Submarine.MainSub) ? Submarine.MainSub : closestSub;
}
else
@@ -173,6 +173,12 @@ namespace Barotrauma
if (matchingSub != null) { availableSubs.Add(matchingSub); }
}
break;
case "savedexperiencepoints":
foreach (XElement savedExp in subElement.Elements())
{
savedExperiencePoints.Add(new SavedExperiencePoints(savedExp));
}
break;
#endif
}
}
@@ -661,7 +661,7 @@ namespace Barotrauma
#if SERVER
return GameMain.Server.ConnectedClients.Select(c => c.Character).Where(c => c.Info != null);
#else
return GameMain.GameSession.CrewManager.CharacterInfos.Select(i => i.Character).Where(c => c != null);
return GameMain.GameSession.CrewManager.GetCharacters().Where(c => c.Info != null);
#endif
}
@@ -671,28 +671,33 @@ namespace Barotrauma
try
{
IEnumerable<Character> crewCharacters = GameSession.GetSessionCrewCharacters();
IEnumerable<Character> crewCharacters = GetSessionCrewCharacters();
foreach (Mission mission in missions)
{
mission.End();
}
foreach (Character character in crewCharacters)
{
character.CheckTalents(AbilityEffectType.OnRoundEnd);
}
if (missions.Any())
{
if (missions.Any(m => m.Completed))
{
foreach (CharacterInfo characterInfo in GameMain.GameSession.CrewManager.CharacterInfos)
foreach (Character character in crewCharacters)
{
characterInfo.Character?.CheckTalents(AbilityEffectType.OnAnyMissionCompleted);
character.CheckTalents(AbilityEffectType.OnAnyMissionCompleted);
}
}
if (missions.All(m => m.Completed))
{
foreach (CharacterInfo characterInfo in GameMain.GameSession.CrewManager.CharacterInfos)
foreach (Character character in crewCharacters)
{
characterInfo.Character?.CheckTalents(AbilityEffectType.OnAllMissionsCompleted);
character.CheckTalents(AbilityEffectType.OnAllMissionsCompleted);
}
}
}
@@ -9,7 +9,7 @@ namespace Barotrauma
[Flags]
public enum InvSlotType
{
None = 0, Any = 1, RightHand = 2, LeftHand = 4, Head = 8, InnerClothes = 16, OuterClothes = 32, Headset = 64, Card = 128, Bag = 256
None = 0, Any = 1, RightHand = 2, LeftHand = 4, Head = 8, InnerClothes = 16, OuterClothes = 32, Headset = 64, Card = 128, Bag = 256, HealthInterface = 512
};
partial class CharacterInventory : Inventory
@@ -0,0 +1,179 @@
using System;
using System.Collections.Generic;
using System.Xml.Linq;
using System.Linq;
using Barotrauma.Extensions;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
namespace Barotrauma.Items.Components
{
partial class GeneticMaterial : ItemComponent, IServerSerializable
{
private readonly string materialName;
private Character targetCharacter;
private AfflictionPrefab selectedEffect, selectedTaintedEffect;
[Serialize("", false)]
public string Effect
{
get;
set;
}
[Serialize("geneticmaterialdebuff", false)]
public string TaintedEffect
{
get;
set;
}
private bool tainted;
[Serialize(false, false)]
public bool Tainted
{
get { return tainted; }
private set
{
if (!value) { return; }
tainted = true;
item.AllowDeconstruct = false;
if (!string.IsNullOrEmpty(TaintedEffect))
{
selectedTaintedEffect = AfflictionPrefab.Prefabs.Where(a =>
a.Identifier.Equals(TaintedEffect, StringComparison.OrdinalIgnoreCase) ||
a.AfflictionType.Equals(TaintedEffect, StringComparison.OrdinalIgnoreCase)).GetRandom();
}
}
}
//only for saving the selected tainted effect
[Serialize("", false)]
public string SelectedTaintedEffect
{
get { return selectedTaintedEffect?.Identifier ?? string.Empty; }
private set
{
if (string.IsNullOrEmpty(value)) { return; }
selectedTaintedEffect = AfflictionPrefab.Prefabs.Find(a => a.Identifier == value);
}
}
public GeneticMaterial(Item item, XElement element)
: base(item, element)
{
string nameId = element.GetAttributeString("nameidentifier", "");
if (!string.IsNullOrEmpty(nameId))
{
materialName = TextManager.Get(nameId);
}
if (!string.IsNullOrEmpty(Effect))
{
selectedEffect = AfflictionPrefab.Prefabs.Where(a =>
a.Identifier.Equals(Effect, StringComparison.OrdinalIgnoreCase) ||
a.AfflictionType.Equals(Effect, StringComparison.OrdinalIgnoreCase)).GetRandom();
}
}
[Serialize(3.0f, false)]
public float ConditionIncreaseOnCombineMin { get; set; }
[Serialize(8.0f, false)]
public float ConditionIncreaseOnCombineMax { get; set; }
public bool CanBeCombinedWith(GeneticMaterial otherGeneticMaterial)
{
return !tainted && otherGeneticMaterial != null && !otherGeneticMaterial.tainted;
}
public override void Equip(Character character)
{
if (character == null) { return; }
IsActive = true;
if (targetCharacter != null) { return; }
if (tainted)
{
if (selectedTaintedEffect != null)
{
float selectedTaintedEffectStrength = item.ConditionPercentage / 100.0f * selectedTaintedEffect.MaxStrength;
character.CharacterHealth.ApplyAffliction(null, selectedTaintedEffect.Instantiate(selectedTaintedEffectStrength));
targetCharacter = character;
#if SERVER
item.CreateServerEvent(this);
#endif
}
}
if (selectedEffect != null)
{
ApplyStatusEffects(ActionType.OnWearing, 1.0f);
float selectedEffectStrength = item.ConditionPercentage / 100.0f * selectedEffect.MaxStrength;
character.CharacterHealth.ApplyAffliction(null, selectedEffect.Instantiate(selectedEffectStrength));
targetCharacter = character;
#if SERVER
item.CreateServerEvent(this);
#endif
}
}
public override void Update(float deltaTime, Camera cam)
{
base.Update(deltaTime, cam);
if (targetCharacter != null)
{
if (!targetCharacter.HasEquippedItem(item) &&
(item.Container == null || !targetCharacter.HasEquippedItem(item.Container) || !(item.Container.GetComponent<ItemContainer>()?.AutoInject ?? false)))
{
item.ApplyStatusEffects(ActionType.OnSevered, 1.0f, targetCharacter);
var currentEffect = tainted ? selectedTaintedEffect : selectedEffect;
targetCharacter.CharacterHealth.ReduceAffliction(null, currentEffect.Identifier, currentEffect.MaxStrength);
targetCharacter = null;
IsActive = false;
}
}
}
public bool Combine(GeneticMaterial otherGeneticMaterial, Character user)
{
if (!CanBeCombinedWith(otherGeneticMaterial)) { return false; }
if (item.Prefab == otherGeneticMaterial.item.Prefab)
{
item.Condition = Math.Max(item.Condition, otherGeneticMaterial.item.Condition) + Rand.Range(ConditionIncreaseOnCombineMin, ConditionIncreaseOnCombineMax);
float taintedProbability = GetTaintedProbabilityOnRefine(user);
if (taintedProbability >= Rand.Range(0.0f, 1.0f))
{
MakeTainted();
}
return true;
}
else
{
item.Condition = otherGeneticMaterial.Item.Condition =
(item.Condition + otherGeneticMaterial.Item.Condition) / 2.0f + Rand.Range(ConditionIncreaseOnCombineMin, ConditionIncreaseOnCombineMax);
item.OwnInventory?.TryPutItem(otherGeneticMaterial.Item, user: null);
MakeTainted();
return false;
}
}
private float GetTaintedProbabilityOnRefine(Character user)
{
if (user == null) { return 1.0f; }
float probability = MathHelper.Lerp(0.0f, 0.99f, item.Condition / 100.0f);
probability *= MathHelper.Lerp(1.0f, 0.25f, DegreeOfSuccess(user));
return probability;
}
private void MakeTainted()
{
if (GameMain.NetworkMember?.IsClient ?? false) { return; }
Tainted = true;
#if SERVER
item.CreateServerEvent(this);
#endif
}
}
}
@@ -181,7 +181,7 @@ namespace Barotrauma.Items.Components
if (aim)
{
hitPos = MathUtils.WrapAnglePi(Math.Min(hitPos + deltaTime * 5f, MathHelper.PiOver4));
ac.HoldItem(deltaTime, item, handlePos, aimPos, Vector2.Zero, false, hitPos, holdAngle + hitPos);
ac.HoldItem(deltaTime, item, handlePos, aimPos, Vector2.Zero, false, hitPos, holdAngle + hitPos, aimingMelee: true);
}
else
{
@@ -356,6 +356,7 @@ namespace Barotrauma.Items.Components
if (Attack != null)
{
Attack.SetUser(User);
Attack.DamageMultiplier = 1 + User.GetStatValue(StatTypes.MeleeAttackMultiplier);
if (targetLimb != null)
{
@@ -107,7 +107,7 @@ namespace Barotrauma.Items.Components
if (ReloadTimer < 0.0f)
{
ReloadTimer = 0.0f;
// was this an optimization or related to something else? currently disabled for charge-type weapons
// was this an optimization or related to something else? it cannot occur for charge-type weapons
//IsActive = false;
if (MaxChargeTime == 0.0f)
{
@@ -118,7 +118,7 @@ namespace Barotrauma.Items.Components
float previousChargeTime = currentChargeTime;
float chargeDeltaTime = tryingToCharge ? deltaTime : -deltaTime;
float chargeDeltaTime = tryingToCharge && ReloadTimer <= 0f ? deltaTime : -deltaTime;
currentChargeTime = Math.Clamp(currentChargeTime + chargeDeltaTime, 0f, MaxChargeTime);
tryingToCharge = false;
@@ -982,7 +982,7 @@ namespace Barotrauma.Items.Components
AIObjectiveContainItem containObjective = null;
if (character.AIController is HumanAIController aiController)
{
containObjective = new AIObjectiveContainItem(character, container.GetContainableItemIdentifiers.ToArray(), container, currentObjective.objectiveManager, spawnItemIfNotFound: spawnItemIfNotFound)
containObjective = new AIObjectiveContainItem(character, container.ContainableItemIdentifiers.ToArray(), container, currentObjective.objectiveManager, spawnItemIfNotFound: spawnItemIfNotFound)
{
targetItemCount = itemCount,
Equip = equip,
@@ -5,6 +5,7 @@ using System.Linq;
using System.Xml.Linq;
using Barotrauma.Extensions;
using FarseerPhysics;
using System.Collections.Immutable;
namespace Barotrauma.Items.Components
{
@@ -23,6 +24,28 @@ namespace Barotrauma.Items.Components
}
}
class SlotRestrictions
{
public readonly int MaxStackSize;
public readonly List<RelatedItem> ContainableItems;
public SlotRestrictions(int maxStackSize, List<RelatedItem> containableItems)
{
MaxStackSize = maxStackSize;
ContainableItems = containableItems;
}
public bool MatchesItem(Item item)
{
return ContainableItems == null || ContainableItems.Count == 0 || ContainableItems.Any(c => c.MatchesItem(item));
}
public bool MatchesItem(ItemPrefab itemPrefab)
{
return ContainableItems == null || ContainableItems.Count == 0 || ContainableItems.Any(c => c.MatchesItem(itemPrefab));
}
}
private bool alwaysContainedItemsSpawned;
public ItemInventory Inventory;
@@ -73,6 +96,7 @@ namespace Barotrauma.Items.Components
#endif
[Serialize("0.0,0.0", false, description: "The interval at which the contained items are spaced apart from each other (in pixels).")]
public Vector2 ItemInterval { get; set; }
[Serialize(100, false, description: "How many items are placed in a row before starting a new row.")]
public int ItemsPerRow { get; set; }
@@ -90,7 +114,6 @@ namespace Barotrauma.Items.Components
set;
}
[Serialize(false, false, description: "If set to true, interacting with this item will make the character interact with the contained item(s), automatically picking them up if they can be picked up.")]
public bool AutoInteractWithContained
{
@@ -98,6 +121,9 @@ namespace Barotrauma.Items.Components
set;
}
[Serialize(true, false)]
public bool AllowAccess { get; set; }
[Serialize(false, false)]
public bool AccessOnlyWhenBroken { get; set; }
@@ -147,7 +173,7 @@ namespace Barotrauma.Items.Components
set;
}
[Serialize(0.5f, false, description: "The rotation in which the contained sprites are drawn (in degrees).")]
[Serialize(0.5f, false, description: "The health threshold that the user must reach in order to activate the autoinjection.")]
public float AutoInjectThreshold
{
get;
@@ -157,10 +183,12 @@ namespace Barotrauma.Items.Components
[Serialize(false, false)]
public bool RemoveContainedItemsOnDeconstruct { get; set; }
private SlotRestrictions[] slotRestrictions;
public bool ShouldBeContained(string[] identifiersOrTags, out bool isRestrictionsDefined)
{
isRestrictionsDefined = containableRestrictions.Any();
if (ContainableItems.None(ri => ri.MatchesItem(item))) { return false; }
if (slotRestrictions.None(s => s.MatchesItem(item))) { return false; }
if (!isRestrictionsDefined) { return true; }
return identifiersOrTags.Any(id => containableRestrictions.Any(r => r == id));
}
@@ -168,22 +196,22 @@ namespace Barotrauma.Items.Components
public bool ShouldBeContained(Item item, out bool isRestrictionsDefined)
{
isRestrictionsDefined = containableRestrictions.Any();
if (ContainableItems.None(ri => ri.MatchesItem(item))) { return false; }
if (slotRestrictions.None(s => s.MatchesItem(item))) { return false; }
if (!isRestrictionsDefined) { return true; }
return containableRestrictions.Any(id => item.Prefab.Identifier == id || item.HasTag(id));
}
public List<RelatedItem> ContainableItems { get; private set; } = new List<RelatedItem>();
public IEnumerable<string> GetContainableItemIdentifiers => ContainableItems.SelectMany(ri => ri.Identifiers);
private ImmutableHashSet<string> containableItemIdentifiers;
public IEnumerable<string> ContainableItemIdentifiers => containableItemIdentifiers;
public override bool RecreateGUIOnResolutionChange => true;
public ItemContainer(Item item, XElement element)
: base (item, element)
: base(item, element)
{
Inventory = new ItemInventory(item, this, capacity, SlotsPerRow);
int totalCapacity = capacity;
List<RelatedItem> containableItems = null;
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
@@ -195,34 +223,92 @@ namespace Barotrauma.Items.Components
DebugConsole.ThrowError("Error in item config \"" + item.ConfigFile + "\" - containable with no identifiers.");
continue;
}
ContainableItems.Add(containable);
containableItems ??= new List<RelatedItem>();
containableItems.Add(containable);
break;
case "subcontainer":
totalCapacity += subElement.GetAttributeInt("capacity", 1);
break;
}
}
Inventory = new ItemInventory(item, this, totalCapacity, SlotsPerRow);
slotRestrictions = new SlotRestrictions[totalCapacity];
for (int i = 0; i < capacity; i++)
{
slotRestrictions[i] = new SlotRestrictions(maxStackSize, containableItems);
}
int subContainerIndex = capacity;
foreach (XElement subElement in element.Elements())
{
if (subElement.Name.ToString().ToLowerInvariant() != "subcontainer") { continue; }
int subCapacity = subElement.GetAttributeInt("capacity", 1);
int subMaxStackSize = subElement.GetAttributeInt("maxstacksize", maxStackSize);
List<RelatedItem> subContainableItems = null;
foreach (XElement subSubElement in subElement.Elements())
{
if (subSubElement.Name.ToString().ToLowerInvariant() != "containable") { continue; }
RelatedItem containable = RelatedItem.Load(subSubElement, returnEmpty: false, parentDebugName: item.Name);
if (containable == null)
{
DebugConsole.ThrowError("Error in item config \"" + item.ConfigFile + "\" - containable with no identifiers.");
continue;
}
subContainableItems ??= new List<RelatedItem>();
subContainableItems.Add(containable);
}
for (int i = subContainerIndex; i < subContainerIndex + subCapacity; i++)
{
slotRestrictions[i] = new SlotRestrictions(subMaxStackSize, subContainableItems);
}
subContainerIndex += subCapacity;
}
capacity = totalCapacity;
InitProjSpecific(element);
}
public int GetMaxStackSize(int slotIndex)
{
if (slotIndex < 0 || slotIndex >= capacity)
{
return 0;
}
return slotRestrictions[slotIndex].MaxStackSize;
}
partial void InitProjSpecific(XElement element);
public void OnItemContained(Item containedItem)
{
item.SetContainedItemPositions();
RelatedItem ri = ContainableItems.Find(x => x.MatchesItem(containedItem));
if (ri != null)
int index = Inventory.FindIndex(containedItem);
if (index >= 0 && index < slotRestrictions.Length)
{
activeContainedItems.RemoveAll(i => i.Item == containedItem);
foreach (StatusEffect effect in ri.statusEffects)
RelatedItem ri = slotRestrictions[index].ContainableItems?.Find(ci => ci.MatchesItem(containedItem));
if (ri != null)
{
activeContainedItems.Add(new ActiveContainedItem(containedItem, effect, ri.ExcludeBroken));
activeContainedItems.RemoveAll(i => i.Item == containedItem);
foreach (StatusEffect effect in ri.statusEffects)
{
activeContainedItems.Add(new ActiveContainedItem(containedItem, effect, ri.ExcludeBroken));
}
}
}
}
//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);
}
public override void Move(Vector2 amount)
{
SetContainedItemPositions();
}
public void OnItemRemoved(Item containedItem)
{
activeContainedItems.RemoveAll(i => i.Item == containedItem);
@@ -233,13 +319,11 @@ namespace Barotrauma.Items.Components
public bool CanBeContained(Item item)
{
if (ContainableItems.Count == 0) { return true; }
return ContainableItems.Find(c => c.MatchesItem(item)) != null;
return slotRestrictions.Any(s => s.MatchesItem(item));
}
public bool CanBeContained(ItemPrefab itemPrefab)
{
if (ContainableItems.Count == 0) { return true; }
return ContainableItems.Find(c => c.MatchesItem(itemPrefab)) != null;
return slotRestrictions.Any(s => s.MatchesItem(itemPrefab));
}
readonly List<ISerializableEntity> targets = new List<ISerializableEntity>();
@@ -264,6 +348,7 @@ namespace Barotrauma.Items.Components
foreach (Item item in Inventory.AllItemsMod)
{
item.ApplyStatusEffects(ActionType.OnUse, 1.0f, ownerCharacter);
item.GetComponent<GeneticMaterial>()?.Equip(ownerCharacter);
}
}
}
@@ -304,11 +389,12 @@ namespace Barotrauma.Items.Components
public override bool HasRequiredItems(Character character, bool addMessage, string msg = null)
{
return (!AccessOnlyWhenBroken || Item.Condition <= 0) && base.HasRequiredItems(character, addMessage, msg);
return AllowAccess && (!AccessOnlyWhenBroken || Item.Condition <= 0) && base.HasRequiredItems(character, addMessage, msg);
}
public override bool Select(Character character)
{
if (!AllowAccess) { return false; }
if (item.Container != null) { return false; }
if (AccessOnlyWhenBroken)
{
@@ -335,6 +421,7 @@ namespace Barotrauma.Items.Components
public override bool Pick(Character picker)
{
if (!AllowAccess) { return false; }
if (AccessOnlyWhenBroken)
{
if (item.Condition > 0)
@@ -362,7 +449,7 @@ namespace Barotrauma.Items.Components
public override bool Combine(Item item, Character user)
{
if (!AllowDragAndDrop && user != null) { return false; }
if (!ContainableItems.Any(it => it.MatchesItem(item))) { return false; }
if (!slotRestrictions.Any(s => s.MatchesItem(item))) { return false; }
if (user != null && !user.CanAccessInventory(Inventory)) { return false; }
if (Inventory.TryPutItem(item, user))
@@ -392,50 +479,59 @@ namespace Barotrauma.Items.Components
Vector2 transformedItemInterval = ItemInterval * item.Scale;
Vector2 transformedItemIntervalHorizontal = new Vector2(transformedItemInterval.X, 0.0f);
Vector2 transformedItemIntervalVertical = new Vector2(0.0f, transformedItemInterval.Y);
if (item.body == null)
if (ItemPos == Vector2.Zero && ItemInterval == Vector2.Zero)
{
if (item.FlippedX)
{
transformedItemPos.X = -transformedItemPos.X;
transformedItemPos.X += item.Rect.Width;
transformedItemInterval.X = -transformedItemInterval.X;
transformedItemIntervalHorizontal.X = -transformedItemIntervalHorizontal.X;
}
if (item.FlippedY)
{
transformedItemPos.Y = -transformedItemPos.Y;
transformedItemPos.Y -= item.Rect.Height;
transformedItemInterval.Y = -transformedItemInterval.Y;
transformedItemIntervalVertical.Y = -transformedItemIntervalVertical.Y;
}
transformedItemPos += new Vector2(item.Rect.X, item.Rect.Y);
if (Math.Abs(item.Rotation) > 0.01f)
{
Matrix transform = Matrix.CreateRotationZ(MathHelper.ToRadians(-item.Rotation));
transformedItemPos = Vector2.Transform(transformedItemPos, transform);
transformedItemInterval = Vector2.Transform(transformedItemInterval, transform);
transformedItemIntervalHorizontal = Vector2.Transform(transformedItemIntervalHorizontal, transform);
transformedItemIntervalVertical = Vector2.Transform(transformedItemIntervalVertical, transform);
}
transformedItemPos = item.Position;
}
else
{
Matrix transform = Matrix.CreateRotationZ(item.body.Rotation);
if (item.body.Dir == -1.0f)
if (item.body == null)
{
transformedItemPos.X = -transformedItemPos.X;
transformedItemInterval.X = -transformedItemInterval.X;
transformedItemIntervalHorizontal.X = -transformedItemIntervalHorizontal.X;
if (item.FlippedX)
{
transformedItemPos.X = -transformedItemPos.X;
transformedItemPos.X += item.Rect.Width;
transformedItemInterval.X = -transformedItemInterval.X;
transformedItemIntervalHorizontal.X = -transformedItemIntervalHorizontal.X;
}
if (item.FlippedY)
{
transformedItemPos.Y = -transformedItemPos.Y;
transformedItemPos.Y -= item.Rect.Height;
transformedItemInterval.Y = -transformedItemInterval.Y;
transformedItemIntervalVertical.Y = -transformedItemIntervalVertical.Y;
}
transformedItemPos += new Vector2(item.Rect.X, item.Rect.Y);
if (Math.Abs(item.Rotation) > 0.01f)
{
Matrix transform = Matrix.CreateRotationZ(MathHelper.ToRadians(-item.Rotation));
transformedItemPos = Vector2.Transform(transformedItemPos, transform);
transformedItemInterval = Vector2.Transform(transformedItemInterval, transform);
transformedItemIntervalHorizontal = Vector2.Transform(transformedItemIntervalHorizontal, transform);
transformedItemIntervalVertical = Vector2.Transform(transformedItemIntervalVertical, transform);
}
}
transformedItemPos = Vector2.Transform(transformedItemPos, transform);
transformedItemInterval = Vector2.Transform(transformedItemInterval, transform);
transformedItemIntervalHorizontal = Vector2.Transform(transformedItemIntervalHorizontal, transform);
transformedItemPos += item.Position;
}
else
{
Matrix transform = Matrix.CreateRotationZ(item.body.Rotation);
if (item.body.Dir == -1.0f)
{
transformedItemPos.X = -transformedItemPos.X;
transformedItemInterval.X = -transformedItemInterval.X;
transformedItemIntervalHorizontal.X = -transformedItemIntervalHorizontal.X;
}
transformedItemPos = Vector2.Transform(transformedItemPos, transform);
transformedItemInterval = Vector2.Transform(transformedItemInterval, transform);
transformedItemIntervalHorizontal = Vector2.Transform(transformedItemIntervalHorizontal, transform);
transformedItemPos += item.Position;
}
}
float currentRotation = itemRotation;
if (item.body != null)
{
currentRotation *= item.body.Dir;
currentRotation += item.body.Rotation;
}
@@ -492,6 +588,7 @@ namespace Barotrauma.Items.Components
public override void OnItemLoaded()
{
containableItemIdentifiers = slotRestrictions.SelectMany(s => s.ContainableItems?.SelectMany(ri => ri.Identifiers) ?? Enumerable.Empty<string>()).ToImmutableHashSet();
if (item.Submarine == null || !item.Submarine.Loading)
{
SpawnAlwaysContainedItems();
@@ -1,4 +1,5 @@
using Barotrauma.Extensions;
using Barotrauma.Abilities;
using Barotrauma.Extensions;
using Barotrauma.Networking;
using System;
using System.Collections.Generic;
@@ -14,6 +15,10 @@ namespace Barotrauma.Items.Components
private bool hasPower;
private Character user;
private float userDeconstructorSpeedMultiplier = 1.0f;
private ItemContainer inputContainer, outputContainer;
public ItemContainer InputContainer
@@ -25,7 +30,10 @@ namespace Barotrauma.Items.Components
{
get { return outputContainer; }
}
[Serialize(false, true)]
public bool DeconstructItemsSimultaneously { get; set; }
[Editable, Serialize(1.0f, true)]
public float DeconstructionSpeed { get; set; }
@@ -81,65 +89,149 @@ namespace Barotrauma.Items.Components
if (powerConsumption <= 0.0f) { Voltage = 1.0f; }
progressTimer += deltaTime * Math.Min(Voltage, 1.0f);
var targetItem = inputContainer.Inventory.LastOrDefault();
if (targetItem == null) { return; }
float deconstructTime = targetItem.Prefab.DeconstructItems.Any() ? targetItem.Prefab.DeconstructTime / DeconstructionSpeed : 1.0f;
progressState = Math.Min(progressTimer / deconstructTime, 1.0f);
if (progressTimer > deconstructTime)
if (DeconstructItemsSimultaneously)
{
// In multiplayer, the server handles the deconstruction into new items
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
if (targetItem.Prefab.RandomDeconstructionOutput)
float deconstructTime = 0.0f;
foreach (Item targetItem in inputContainer.Inventory.AllItems)
{
int amount = targetItem.Prefab.RandomDeconstructionOutputAmount;
List<int> deconstructItemIndexes = new List<int>();
for (int i = 0; i < targetItem.Prefab.DeconstructItems.Count; i++)
{
deconstructItemIndexes.Add(i);
}
List<float> commonness = targetItem.Prefab.DeconstructItems.Select(i => i.Commonness).ToList();
List<DeconstructItem> products = new List<DeconstructItem>();
for (int i = 0; i < amount; i++)
{
if (deconstructItemIndexes.Count < 1) { break; }
var itemIndex = ToolBox.SelectWeightedRandom(deconstructItemIndexes, commonness, Rand.RandSync.Unsynced);
products.Add(targetItem.Prefab.DeconstructItems[itemIndex]);
var removeIndex = deconstructItemIndexes.IndexOf(itemIndex);
deconstructItemIndexes.RemoveAt(removeIndex);
commonness.RemoveAt(removeIndex);
}
foreach (DeconstructItem deconstructProduct in products)
{
CreateDeconstructProduct(deconstructProduct);
}
}
else
{
foreach (DeconstructItem deconstructProduct in targetItem.Prefab.DeconstructItems)
{
CreateDeconstructProduct(deconstructProduct);
}
deconstructTime += targetItem.Prefab.DeconstructTime / (DeconstructionSpeed * userDeconstructorSpeedMultiplier);
}
void CreateDeconstructProduct(DeconstructItem deconstructProduct)
progressState = Math.Min(progressTimer / deconstructTime, 1.0f);
if (progressTimer > deconstructTime)
{
float percentageHealth = targetItem.Condition / targetItem.Prefab.Health;
if (percentageHealth <= deconstructProduct.MinCondition || percentageHealth > deconstructProduct.MaxCondition) { return; }
if (!(MapEntityPrefab.Find(null, deconstructProduct.ItemIdentifier) is ItemPrefab itemPrefab))
List<Item> items = inputContainer.Inventory.AllItems.ToList();
foreach (Item targetItem in items)
{
DebugConsole.ThrowError("Tried to deconstruct item \"" + targetItem.Name + "\" but couldn't find item prefab \"" + deconstructProduct.ItemIdentifier + "\"!");
return;
if ((Entity.Spawner?.IsInRemoveQueue(targetItem) ?? false) || !inputContainer.Inventory.AllItems.Contains(targetItem)) { continue; }
var validDeconstructItems = targetItem.Prefab.DeconstructItems.FindAll(it =>
(it.RequiredDeconstructor.Length == 0 || it.RequiredDeconstructor.Any(r => item.HasTag(r) || item.Prefab.Identifier.Equals(r, StringComparison.OrdinalIgnoreCase))) &&
(it.RequiredOtherItem.Length == 0 || it.RequiredOtherItem.Any(r => items.Any(it => it.HasTag(r) || it.Prefab.Identifier.Equals(r, StringComparison.OrdinalIgnoreCase)))));
ProcessItem(targetItem, items, validDeconstructItems, allowRemove: validDeconstructItems.Any() || !targetItem.Prefab.DeconstructItems.Any());
}
#if SERVER
item.CreateServerEvent(this);
#endif
progressTimer = 0.0f;
progressState = 0.0f;
float condition = deconstructProduct.CopyCondition ?
percentageHealth * itemPrefab.Health :
itemPrefab.Health * deconstructProduct.OutCondition;
}
}
else
{
var targetItem = inputContainer.Inventory.LastOrDefault();
if (targetItem == null) { return; }
var validDeconstructItems = targetItem.Prefab.DeconstructItems.FindAll(it =>
it.RequiredDeconstructor.Length == 0 || it.RequiredDeconstructor.Any(r => item.HasTag(r) || item.Prefab.Identifier.Equals(r, StringComparison.OrdinalIgnoreCase)));
float deconstructTime = validDeconstructItems.Any() ? targetItem.Prefab.DeconstructTime / DeconstructionSpeed : 1.0f;
progressState = Math.Min(progressTimer / deconstructTime, 1.0f);
if (progressTimer > deconstructTime)
{
ProcessItem(targetItem, inputContainer.Inventory.AllItemsMod, validDeconstructItems, allowRemove: validDeconstructItems.Any() || !targetItem.Prefab.DeconstructItems.Any());
#if SERVER
item.CreateServerEvent(this);
#endif
progressTimer = 0.0f;
progressState = 0.0f;
}
}
}
private void ProcessItem(Item targetItem, IEnumerable<Item> inputItems, List<DeconstructItem> validDeconstructItems, bool allowRemove = true)
{
// In multiplayer, the server handles the deconstruction into new items
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
if (targetItem.Prefab.RandomDeconstructionOutput)
{
int amount = targetItem.Prefab.RandomDeconstructionOutputAmount;
List<int> deconstructItemIndexes = new List<int>();
for (int i = 0; i < validDeconstructItems.Count; i++)
{
deconstructItemIndexes.Add(i);
}
List<float> commonness = validDeconstructItems.Select(i => i.Commonness).ToList();
List<DeconstructItem> products = new List<DeconstructItem>();
for (int i = 0; i < amount; i++)
{
if (deconstructItemIndexes.Count < 1) { break; }
var itemIndex = ToolBox.SelectWeightedRandom(deconstructItemIndexes, commonness, Rand.RandSync.Unsynced);
products.Add(validDeconstructItems[itemIndex]);
var removeIndex = deconstructItemIndexes.IndexOf(itemIndex);
deconstructItemIndexes.RemoveAt(removeIndex);
commonness.RemoveAt(removeIndex);
}
user.CheckTalents(AbilityEffectType.OnItemDeconstructed, targetItem);
foreach (DeconstructItem deconstructProduct in products)
{
CreateDeconstructProduct(deconstructProduct, inputItems);
}
}
else
{
foreach (DeconstructItem deconstructProduct in validDeconstructItems)
{
CreateDeconstructProduct(deconstructProduct, inputItems);
}
}
void CreateDeconstructProduct(DeconstructItem deconstructProduct, IEnumerable<Item> inputItems)
{
float percentageHealth = targetItem.Condition / targetItem.Prefab.Health;
if (percentageHealth <= deconstructProduct.MinCondition || percentageHealth > deconstructProduct.MaxCondition) { return; }
if (!(MapEntityPrefab.Find(null, deconstructProduct.ItemIdentifier) is ItemPrefab itemPrefab))
{
DebugConsole.ThrowError("Tried to deconstruct item \"" + targetItem.Name + "\" but couldn't find item prefab \"" + deconstructProduct.ItemIdentifier + "\"!");
return;
}
float condition = deconstructProduct.CopyCondition ?
percentageHealth * itemPrefab.Health :
itemPrefab.Health * Rand.Range(deconstructProduct.OutConditionMin, deconstructProduct.OutConditionMax);
if (DeconstructItemsSimultaneously && deconstructProduct.RequiredOtherItem.Length > 0)
{
foreach (Item otherItem in inputItems)
{
if (targetItem == otherItem) { continue; }
if (deconstructProduct.RequiredOtherItem.Any(r => otherItem.HasTag(r) || r.Equals(otherItem.Prefab.Identifier, StringComparison.OrdinalIgnoreCase)))
{
var geneticMaterial1 = targetItem.GetComponent<GeneticMaterial>();
var geneticMaterial2 = otherItem.GetComponent<GeneticMaterial>();
if (geneticMaterial1 != null && geneticMaterial2 != null)
{
if (geneticMaterial1.Combine(geneticMaterial2, user))
{
inputContainer.Inventory.RemoveItem(otherItem);
OutputContainer.Inventory.RemoveItem(otherItem);
Entity.Spawner.AddToRemoveQueue(otherItem);
}
allowRemove = false;
return;
}
inputContainer.Inventory.RemoveItem(otherItem);
OutputContainer.Inventory.RemoveItem(otherItem);
Entity.Spawner.AddToRemoveQueue(otherItem);
}
}
}
var itemsCreated = new AbilityValue(1f);
user.CheckTalents(AbilityEffectType.OnItemDeconstructedMaterial, (targetItem.Prefab, itemsCreated));
int amount = (int)itemsCreated.Value;
for (int i = 0; i < amount; i++)
{
Entity.Spawner.AddToSpawnQueue(itemPrefab, outputContainer.Inventory, condition, onSpawned: (Item spawnedItem) =>
{
for (int i = 0; i < outputContainer.Capacity; i++)
@@ -153,36 +245,31 @@ namespace Barotrauma.Items.Components
PutItemsToLinkedContainer();
});
}
}
if (targetItem.Prefab.AllowDeconstruct)
if (targetItem.AllowDeconstruct && allowRemove)
{
//drop all items that are inside the deconstructed item
foreach (ItemContainer ic in targetItem.GetComponents<ItemContainer>())
{
//drop all items that are inside the deconstructed item
foreach (ItemContainer ic in targetItem.GetComponents<ItemContainer>())
{
if (ic?.Inventory == null || ic.RemoveContainedItemsOnDeconstruct) { continue; }
ic.Inventory.AllItemsMod.ForEach(containedItem => outputContainer.Inventory.TryPutItem(containedItem, user: null));
}
inputContainer.Inventory.RemoveItem(targetItem);
Entity.Spawner.AddToRemoveQueue(targetItem);
MoveInputQueue();
PutItemsToLinkedContainer();
if (ic?.Inventory == null || ic.RemoveContainedItemsOnDeconstruct) { continue; }
ic.Inventory.AllItemsMod.ForEach(containedItem => outputContainer.Inventory.TryPutItem(containedItem, user: null));
}
inputContainer.Inventory.RemoveItem(targetItem);
Entity.Spawner.AddToRemoveQueue(targetItem);
MoveInputQueue();
PutItemsToLinkedContainer();
}
else
{
if (!outputContainer.Inventory.CanBePut(targetItem) || (Entity.Spawner?.IsInRemoveQueue(targetItem) ?? false))
{
targetItem.Drop(dropper: null);
}
else
{
if (!outputContainer.Inventory.CanBePut(targetItem))
{
targetItem.Drop(dropper: null);
}
else
{
outputContainer.Inventory.TryPutItem(targetItem, user: null, createNetworkEvent: true);
}
outputContainer.Inventory.TryPutItem(targetItem, user: null, createNetworkEvent: true);
}
#if SERVER
item.CreateServerEvent(this);
#endif
progressTimer = 0.0f;
progressState = 0.0f;
}
}
@@ -190,7 +277,7 @@ namespace Barotrauma.Items.Components
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
if (outputContainer.Inventory.IsEmpty()) { return; }
foreach (MapEntity linkedTo in item.linkedTo)
{
if (linkedTo is Item linkedItem)
@@ -201,7 +288,7 @@ namespace Barotrauma.Items.Components
if (itemContainer == null) { continue; }
outputContainer.Inventory.AllItemsMod.ForEach(containedItem => itemContainer.Inventory.TryPutItem(containedItem, user: null, createNetworkEvent: true));
}
}
}
}
/// <summary>
@@ -221,14 +308,54 @@ namespace Barotrauma.Items.Components
}
}
private IEnumerable<(Item item, DeconstructItem output)> GetAvailableOutputs(bool checkRequiredOtherItems = true)
{
var items = inputContainer.Inventory.AllItems;
foreach (Item inputItem in items)
{
if (!inputItem.AllowDeconstruct) { continue; }
foreach (var deconstructItem in inputItem.Prefab.DeconstructItems)
{
if (deconstructItem.RequiredDeconstructor.Length > 0)
{
if (!deconstructItem.RequiredDeconstructor.Any(r => item.HasTag(r) || item.Prefab.Identifier.Equals(r, StringComparison.OrdinalIgnoreCase))) { continue; }
}
if (deconstructItem.RequiredOtherItem.Length > 0 && checkRequiredOtherItems)
{
if (!deconstructItem.RequiredOtherItem.Any(r => items.Any(it => it.HasTag(r) || it.Prefab.Identifier.Equals(r, StringComparison.OrdinalIgnoreCase)))) { continue; }
bool validOtherItemFound = false;
foreach (Item otherInputItem in items)
{
if (otherInputItem == inputItem) { continue; }
if (!deconstructItem.RequiredOtherItem.Any(r => otherInputItem.HasTag(r) || otherInputItem.Prefab.Identifier.Equals(r, StringComparison.OrdinalIgnoreCase))) { continue; }
var geneticMaterial1 = inputItem.GetComponent<GeneticMaterial>();
var geneticMaterial2 = otherInputItem.GetComponent<GeneticMaterial>();
if (geneticMaterial1 != null && geneticMaterial2 != null)
{
if (!geneticMaterial1.CanBeCombinedWith(geneticMaterial2)) { continue; }
}
validOtherItemFound = true;
}
if (!validOtherItemFound) { continue; }
}
yield return (inputItem, deconstructItem);
}
}
}
private void SetActive(bool active, Character user = null)
{
PutItemsToLinkedContainer();
this.user = user;
if (inputContainer.Inventory.IsEmpty()) { active = false; }
IsActive = active;
currPowerConsumption = IsActive ? powerConsumption : 0.0f;
userDeconstructorSpeedMultiplier = user != null ? 1f + user.GetStatValue(StatTypes.DeconstructorSpeedMultiplier) : 1f;
#if SERVER
if (user != null)
{
@@ -241,10 +368,6 @@ namespace Barotrauma.Items.Components
progressState = 0.0f;
}
#if CLIENT
activateButton.Text = TextManager.Get(IsActive ? "DeconstructorCancel" : "DeconstructorDeconstruct");
#endif
inputContainer.Inventory.Locked = IsActive;
}
}
@@ -316,7 +316,7 @@ namespace Barotrauma.Items.Components
availablePrefab.Condition -= availablePrefab.Prefab.Health * requiredItem.MinCondition;
continue;
}
availablePrefabs.Remove(availablePrefab);
Entity.Spawner.AddToRemoveQueue(availablePrefab);
inputContainer.Inventory.RemoveItem(availablePrefab);
@@ -324,18 +324,20 @@ namespace Barotrauma.Items.Components
}
});
Character tempUser = user;
int amountFittingContainer = outputContainer.Inventory.HowManyCanBePut(fabricatedItem.TargetItem, fabricatedItem.OutCondition * fabricatedItem.TargetItem.Health);
var itemsCreated = new AbilityValue(fabricatedItem.Amount);
foreach (Character character in Character.CharacterList.Where(c => c.TeamID == user.TeamID))
var fabricationValueItem = new AbilityValueItem(fabricatedItem.Amount, fabricatedItem.TargetItem);
if (user != null)
{
character.CheckTalents(AbilityEffectType.OnAllyItemFabricatedAmount, (fabricatedItem.TargetItem, itemsCreated));
foreach (Character character in Character.CharacterList.Where(c => c.TeamID == user.TeamID))
{
character.CheckTalents(AbilityEffectType.OnAllyItemFabricatedAmount, fabricationValueItem);
}
user.CheckTalents(AbilityEffectType.OnItemFabricatedAmount, fabricationValueItem);
}
tempUser.CheckTalents(AbilityEffectType.OnItemFabricatedAmount, (fabricatedItem.TargetItem, itemsCreated));
for (int i = 0; i < (int)itemsCreated.Value; i++)
var tempUser = user;
for (int i = 0; i < (int)fabricationValueItem.Value; i++)
{
if (i < amountFittingContainer)
{
@@ -359,14 +361,13 @@ namespace Barotrauma.Items.Components
}
}
}
if (user?.Info != null && !user.Removed)
{
foreach (Skill skill in fabricatedItem.RequiredSkills)
{
float userSkill = user.GetSkillLevel(skill.Identifier);
float addedSkill = skill.Level * SkillSettings.Current.SkillIncreasePerFabricatorRequiredSkill / Math.Max(userSkill, 1.0f);
var addedSkillValue = new AbilityValue(0f);
var addedSkillValue = new AbilityValueString(0f, skill.Identifier);
user.CheckTalents(AbilityEffectType.OnItemFabricationSkillGain, addedSkillValue);
user.Info.IncreaseSkillLevel(
@@ -365,16 +365,13 @@ namespace Barotrauma.Items.Components
item.SendSignal(new Signal(velY.ToString(CultureInfo.InvariantCulture), sender: user), "velocity_y_out");
// converts the controlled sub's velocity to km/h and sends it.
// TODO: add current_velocity_x and current_velocity_y pins on the navigation terminals and shuttle terminals
// TODO: increase the size of the connection panels of both navigation terminals
if (controlledSub is { } sub)
{
item.SendSignal(new Signal((ConvertUnits.ToDisplayUnits(sub.Velocity.X * Physics.DisplayToRealWorldRatio) * 3.6f).ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_velocity_x");
item.SendSignal(new Signal((ConvertUnits.ToDisplayUnits(sub.Velocity.Y * Physics.DisplayToRealWorldRatio) * -3.6f).ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_velocity_y");
item.SendSignal(new Signal(sub.WorldPosition.X.ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_position_x");
item.SendSignal(new Signal(sub.RealWorldDepth.ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_depth");
item.SendSignal(new Signal(sub.RealWorldDepth.ToString("0.0000", CultureInfo.InvariantCulture), sender: user), "current_position_y");
}
// if our tactical AI pilot has left, revert back to maintaining position
@@ -15,6 +15,9 @@ namespace Barotrauma.Items.Components
//a list of connections a given connection is connected to, either directly or via other power transfer components
private readonly Dictionary<Connection, HashSet<Connection>> connectedRecipients = new Dictionary<Connection, HashSet<Connection>>();
private float overloadCooldownTimer;
private const float OverloadCooldown = 5.0f;
protected float powerLoad;
protected bool isBroken;
@@ -173,12 +176,19 @@ namespace Barotrauma.Items.Components
Overload = -currPowerConsumption > Math.Max(powerLoad, 200.0f) * maxOverVoltage;
if (Overload && (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer))
{
if (overloadCooldownTimer > 0.0f)
{
overloadCooldownTimer -= deltaTime;
return;
}
//damage the item if voltage is too high (except if running as a client)
float prevCondition = item.Condition;
item.Condition -= deltaTime * 10.0f;
if (item.Condition <= 0.0f && prevCondition > 0.0f)
{
overloadCooldownTimer = OverloadCooldown;
#if CLIENT
SoundPlayer.PlaySound("zap", item.WorldPosition, hullGuess: item.CurrentHull);
Vector2 baseVel = Rand.Vector(300.0f);
@@ -0,0 +1,96 @@
using Microsoft.Xna.Framework;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
partial class RemoteController : ItemComponent
{
[Serialize("", false, description: "Tag or identifier of the item that should be controlled.")]
public string Target
{
get;
private set;
}
[Serialize(false, false)]
public bool OnlyInOwnSub
{
get;
private set;
}
[Serialize(10000.0f, false)]
public float Range
{
get;
private set;
}
public Item TargetItem { get => currentTarget; }
private Item currentTarget;
private Character currentUser;
private Submarine currentSub;
public RemoteController(Item item, XElement element)
: base(item, element)
{
}
public override bool Select(Character character)
{
if (base.Select(character))
{
FindTarget(character);
return true;
}
return false;
}
public override void Equip(Character character)
{
FindTarget(character);
}
public override void Update(float deltaTime, Camera cam)
{
base.Update(deltaTime, cam);
if (currentTarget.Removed ||
item.Submarine != currentSub ||
Vector2.DistanceSquared(currentTarget.WorldPosition, item.WorldPosition) > Range * Range)
{
FindTarget(currentUser);
}
}
private void FindTarget(Character user)
{
currentTarget = null;
if (user == null || (item.Submarine == null && OnlyInOwnSub))
{
IsActive = false;
return;
}
float closestDist = float.PositiveInfinity;
foreach (Item targetItem in Item.ItemList)
{
if (OnlyInOwnSub)
{
if (targetItem.Submarine != item.Submarine) { continue; }
if (targetItem.Submarine.TeamID != user.TeamID) { continue; }
}
if (!targetItem.HasTag(Target) && targetItem.prefab.Identifier != Target) { continue; }
float distSqr = Vector2.DistanceSquared(item.WorldPosition, targetItem.WorldPosition);
if (distSqr > Range * Range || distSqr > closestDist) { continue; }
currentTarget = targetItem;
currentSub = item.Submarine;
closestDist = distSqr;
currentUser = user;
}
IsActive = currentTarget != null;
}
}
}
@@ -339,7 +339,7 @@ namespace Barotrauma.Items.Components
if (currentFixerAction == FixActions.Tinker)
{
// this is a bit code rotty to interject it here, should be less reliant on returning
// not great to interject it here, should be less reliant on returning
if (!CanTinker(CurrentFixer))
{
StopRepairing(CurrentFixer);
@@ -134,20 +134,30 @@ namespace Barotrauma.Items.Components
foreach (Wire wire in c.Wires)
{
if (wire == null) { continue; }
#if CLIENT
if (wire.Item.IsSelected) { continue; }
#endif
var wireNodes = wire.GetNodes();
if (wireNodes.Count == 0) { continue; }
TryMoveWire(wire);
}
}
if (Submarine.RectContains(item.Rect, wireNodes[0] + wireNodeOffset))
{
wire.MoveNode(0, amount);
}
else if (Submarine.RectContains(item.Rect, wireNodes[wireNodes.Count - 1] + wireNodeOffset))
{
wire.MoveNode(wireNodes.Count - 1, amount);
}
foreach (var wire in DisconnectedWires)
{
TryMoveWire(wire);
}
void TryMoveWire(Wire wire)
{
#if CLIENT
if (wire.Item.IsSelected) { return; }
#endif
var wireNodes = wire.GetNodes();
if (wireNodes.Count == 0) { return; }
if (Submarine.RectContains(item.Rect, wireNodes[0] + wireNodeOffset))
{
wire.MoveNode(0, amount);
}
else if (Submarine.RectContains(item.Rect, wireNodes[wireNodes.Count - 1] + wireNodeOffset))
{
wire.MoveNode(wireNodes.Count - 1, amount);
}
}
}
@@ -41,7 +41,7 @@ namespace Barotrauma.Items.Components
set
{
if (string.IsNullOrEmpty(value)) { return; }
ShowOnDisplay(value);
ShowOnDisplay(value, addToHistory: true);
}
}
@@ -59,7 +59,7 @@ namespace Barotrauma.Items.Components
partial void InitProjSpecific(XElement element);
partial void ShowOnDisplay(string input, bool addToHistory = true);
partial void ShowOnDisplay(string input, bool addToHistory);
public override void ReceiveSignal(Signal signal, Connection connection)
{
@@ -70,14 +70,14 @@ namespace Barotrauma.Items.Components
}
string inputSignal = signal.value.Replace("\\n", "\n");
ShowOnDisplay(inputSignal);
ShowOnDisplay(inputSignal, addToHistory: true);
}
public override void OnItemLoaded()
{
bool isSubEditor = false;
#if CLIENT
isSubEditor = Screen.Selected != GameMain.SubEditorScreen || GameMain.GameSession?.GameMode is TestGameMode;
isSubEditor = Screen.Selected == GameMain.SubEditorScreen || GameMain.GameSession?.GameMode is TestGameMode;
#endif
base.OnItemLoaded();
@@ -110,7 +110,7 @@ namespace Barotrauma.Items.Components
{
string msg = componentElement.GetAttributeString("msg" + i, null);
if (msg == null) { break; }
ShowOnDisplay(msg);
ShowOnDisplay(msg, addToHistory: true);
}
}
}
@@ -133,15 +133,15 @@ namespace Barotrauma.Items.Components
public bool IsConnectedTo(Item item)
{
if (connections[0] != null && connections[0].Item == item) return true;
return (connections[1] != null && connections[1].Item == item);
if (connections[0] != null && connections[0].Item == item) { return true; }
return connections[1] != null && connections[1].Item == item;
}
public void RemoveConnection(Item item)
{
for (int i = 0; i < 2; i++)
{
if (connections[i] == null || connections[i].Item != item) continue;
if (connections[i] == null || connections[i].Item != item) { continue; }
foreach (Wire wire in connections[i].Wires)
{
@@ -64,6 +64,8 @@ namespace Barotrauma.Items.Components
private Character currentTarget;
const float aiFindTargetInterval = 5.0f;
private const float TinkeringPowerCostReduction = 1.25f;
public float Rotation
{
get { return rotation; }
@@ -504,9 +506,19 @@ namespace Barotrauma.Items.Components
return TryLaunch(deltaTime, character);
}
public float GetPowerRequiredToShoot()
{
float powerCost = powerConsumption;
if (user != null)
{
powerCost /= (1 + user.GetStatValue(StatTypes.TurretPowerCostReduction));
}
return powerCost;
}
public bool HasPowerToShoot()
{
return GetAvailableBatteryPower() >= powerConsumption;
return GetAvailableBatteryPower() >= GetPowerRequiredToShoot();
}
private bool TryLaunch(float deltaTime, Character character = null, bool ignorePower = false)
@@ -617,10 +629,12 @@ namespace Barotrauma.Items.Components
if (!ignorePower)
{
var batteries = item.GetConnectedComponents<PowerContainer>();
float neededPower = powerConsumption;
float neededPower = GetPowerRequiredToShoot();
// tinkering is currently not factored into the common method as it is checked only when shooting
// but this is a minor issue that causes mostly cosmetic woes. might still be worth refactoring later
if (isTinkering)
{
neededPower /= 1.25f;
neededPower /= TinkeringPowerCostReduction;
}
while (neededPower > 0.0001f && batteries.Count > 0)
{
@@ -1022,7 +1036,7 @@ namespace Barotrauma.Items.Components
container = containerItem.GetComponent<ItemContainer>();
if (container != null) { break; }
}
if (container == null || container.ContainableItems.Count == 0)
if (container == null || !container.ContainableItemIdentifiers.Any())
{
if (character.IsOnPlayerTeam)
{
@@ -1046,7 +1060,7 @@ namespace Barotrauma.Items.Components
{
if (!character.IsOnPlayerTeam) { return; }
if (character.Submarine != Submarine.MainSub) { return; }
string ammoType = container.ContainableItems.First().Identifiers.FirstOrDefault() ?? "ammobox";
string ammoType = container.ContainableItemIdentifiers.FirstOrDefault() ?? "ammobox";
int remainingAmmo = Submarine.MainSub.GetItems(false).Count(i => i.HasTag(ammoType) && i.Condition > 1);
if (remainingAmmo == 0)
{
@@ -55,6 +55,8 @@ namespace Barotrauma
public float Scale { get; private set; }
public float Rotation { get; private set; }
public LimbType DepthLimb { get; private set; }
private Wearable _wearableComponent;
public Wearable WearableComponent
@@ -177,6 +179,7 @@ namespace Barotrauma
DepthLimb = (LimbType)Enum.Parse(typeof(LimbType), SourceElement.GetAttributeString("depthlimb", "None"), true);
Sound = SourceElement.GetAttributeString("sound", "");
Scale = SourceElement.GetAttributeFloat("scale", 1.0f);
Rotation = MathHelper.ToRadians(SourceElement.GetAttributeFloat("rotation", 0.0f));
var index = SourceElement.GetAttributePoint("sheetindex", new Point(-1, -1));
if (index.X > -1 && index.Y > -1)
{
@@ -496,7 +496,7 @@ namespace Barotrauma
var itemInSlot = slots[i].First();
if (itemInSlot.OwnInventory != null &&
!itemInSlot.OwnInventory.Contains(item) &&
(itemInSlot.GetComponent<ItemContainer>()?.MaxStackSize ?? 0) == 1 &&
(itemInSlot.GetComponent<ItemContainer>()?.GetMaxStackSize(0) ?? 0) == 1 &&
itemInSlot.OwnInventory.TrySwapping(0, item, user, createNetworkEvent, swapWholeStack: false))
{
return true;
@@ -540,6 +540,12 @@ namespace Barotrauma
set => indestructible = value;
}
public bool AllowDeconstruct
{
get;
set;
}
[Editable, Serialize(false, isSaveable: true, "When enabled will prevent the item from taking damage from all sources")]
public bool InvulnerableToDamage { get; set; }
@@ -767,6 +773,8 @@ namespace Barotrauma
condition = MaxCondition;
lastSentCondition = condition;
AllowDeconstruct = itemPrefab.AllowDeconstruct;
allPropertyObjects.Add(this);
XElement element = itemPrefab.ConfigElement;
@@ -1431,7 +1439,7 @@ namespace Barotrauma
bool hasTargets = effect.TargetIdentifiers == null;
targets.Clear();
if (effect.HasTargetType(StatusEffect.TargetType.Contained))
{
foreach (Item containedItem in ContainedItems)
@@ -1443,6 +1451,11 @@ namespace Barotrauma
continue;
}
if (effect.TargetSlot > -1)
{
if (OwnInventory.FindIndex(containedItem) != effect.TargetSlot) { continue; }
}
hasTargets = true;
targets.Add(containedItem);
}
@@ -1500,8 +1513,8 @@ namespace Barotrauma
{
targets.Add(limb);
}
if (Container != null && effect.HasTargetType(StatusEffect.TargetType.Parent)) targets.Add(Container);
if (Container != null && effect.HasTargetType(StatusEffect.TargetType.Parent)) { targets.Add(Container); }
effect.Apply(type, deltaTime, this, targets, worldPosition);
}
@@ -2298,8 +2311,8 @@ namespace Barotrauma
public void ApplyTreatment(Character user, Character character, Limb targetLimb)
{
//can't apply treatment to dead characters
if (character.IsDead) return;
if (!UseInHealthInterface) return;
if (character.IsDead) { return; }
if (!UseInHealthInterface) { return; }
#if CLIENT
if (GameMain.Client != null)
@@ -2312,7 +2325,7 @@ namespace Barotrauma
bool remove = false;
foreach (ItemComponent ic in components)
{
if (!ic.HasRequiredContainedItems(user, addMessage: user == Character.Controlled)) continue;
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;
@@ -2331,7 +2344,7 @@ namespace Barotrauma
});
}
if (ic.DeleteOnUse) remove = true;
if (ic.DeleteOnUse) { remove = true; }
}
if (remove) { Spawner?.AddToRemoveQueue(this); }
@@ -9,7 +9,7 @@ namespace Barotrauma
{
partial class ItemInventory : Inventory
{
private ItemContainer container;
private readonly ItemContainer container;
public ItemContainer Container
{
get { return container; }
@@ -48,14 +48,14 @@ namespace Barotrauma
if (ItemOwnsSelf(item)) { return false; }
if (i < 0 || i >= slots.Length) { return false; }
if (!container.CanBeContained(item)) { return false; }
return item != null && slots[i].CanBePut(item, ignoreCondition) && slots[i].ItemCount < container.MaxStackSize;
return item != null && slots[i].CanBePut(item, ignoreCondition) && slots[i].ItemCount < container.GetMaxStackSize(i);
}
public override bool CanBePutInSlot(ItemPrefab itemPrefab, int i, float? condition)
{
if (i < 0 || i >= slots.Length) { return false; }
if (!container.CanBeContained(itemPrefab)) { return false; }
return itemPrefab != null && slots[i].CanBePut(itemPrefab, condition) && slots[i].ItemCount < container.MaxStackSize;
return itemPrefab != null && slots[i].CanBePut(itemPrefab, condition) && slots[i].ItemCount < container.GetMaxStackSize(i);
}
public override int HowManyCanBePut(ItemPrefab itemPrefab, int i, float? condition)
@@ -63,7 +63,7 @@ namespace Barotrauma
if (itemPrefab == null) { return 0; }
if (i < 0 || i >= slots.Length) { return 0; }
if (!container.CanBeContained(itemPrefab)) { return 0; }
return slots[i].HowManyCanBePut(itemPrefab, maxStackSize: Math.Min(itemPrefab.MaxStackSize, container.MaxStackSize), condition);
return slots[i].HowManyCanBePut(itemPrefab, maxStackSize: Math.Min(itemPrefab.MaxStackSize, container.GetMaxStackSize(i)), condition);
}
public override bool IsFull(bool takeStacksIntoAccount = false)
@@ -74,7 +74,7 @@ namespace Barotrauma
{
if (!slots[i].Any()) { return false; }
var item = slots[i].FirstOrDefault();
if (slots[i].ItemCount < Math.Min(item.Prefab.MaxStackSize, container.MaxStackSize)) { return false; }
if (slots[i].ItemCount < Math.Min(item.Prefab.MaxStackSize, container.GetMaxStackSize(i))) { return false; }
}
}
else
@@ -18,9 +18,18 @@ namespace Barotrauma
//maxCondition does > check, meaning that above this max the deconstruct item will be skipped.
public readonly float MaxCondition;
//Condition of item on creation
public readonly float OutCondition;
public readonly float OutConditionMin, OutConditionMax;
//should the condition of the deconstructed item be copied to the output items
public readonly bool CopyCondition;
//tag/identifier of the deconstructor(s) that can be used to deconstruct the item into this
public readonly string[] RequiredDeconstructor;
//tag/identifier of other item(s) that that need to be present in the deconstructor to deconstruct the item into this
public readonly string[] RequiredOtherItem;
//text to display on the deconstructor's activate button when this output is available
public readonly string ActivateButtonText;
public readonly string InfoText;
public readonly string InfoTextOnOtherItemMissing;
public float Commonness { get; }
public DeconstructItem(XElement element, string parentDebugName)
@@ -28,14 +37,20 @@ namespace Barotrauma
ItemIdentifier = element.GetAttributeString("identifier", "notfound");
MinCondition = element.GetAttributeFloat("mincondition", -0.1f);
MaxCondition = element.GetAttributeFloat("maxcondition", 1.0f);
OutCondition = element.GetAttributeFloat("outcondition", 1.0f);
OutConditionMin = element.GetAttributeFloat("outconditionmin", element.GetAttributeFloat("outcondition", 1.0f));
OutConditionMax = element.GetAttributeFloat("outconditionmax", element.GetAttributeFloat("outcondition", 1.0f));
CopyCondition = element.GetAttributeBool("copycondition", false);
Commonness = element.GetAttributeFloat("commonness", 1.0f);
if (element.Attribute("copycondition") != null && element.Attribute("outcondition") != null)
{
DebugConsole.AddWarning($"Invalid deconstruction output in \"{parentDebugName}\": the output item \"{ItemIdentifier}\" has the out condition set, but is also set to copy the condition of the deconstructed item. Ignoring the out condition.");
}
RequiredDeconstructor = element.GetAttributeStringArray("requireddeconstructor", new string[0]);
RequiredOtherItem = element.GetAttributeStringArray("requiredotheritem", new string[0]);
ActivateButtonText = element.GetAttributeString("activatebuttontext", string.Empty);
InfoText = element.GetAttributeString("infotext", string.Empty);
InfoTextOnOtherItemMissing = element.GetAttributeString("infotextonotheritemmissing", string.Empty);
}
}
@@ -33,6 +33,9 @@ namespace Barotrauma
private readonly float? flashRange;
private readonly string decal;
private readonly float decalSize;
// used to apply friendly afflictions in an area without effects displaying
private readonly bool abilityExplosion;
private readonly bool applyToSelf;
private readonly float itemRepairStrength;
@@ -63,8 +66,10 @@ namespace Barotrauma
force = element.GetAttributeFloat("force", 0.0f);
bool showEffects = element.GetAttributeBool("showeffects", true);
abilityExplosion = element.GetAttributeBool("abilityexplosion", false);
applyToSelf = element.GetAttributeBool("applytoself", true);
bool showEffects = !abilityExplosion;
sparks = element.GetAttributeBool("sparks", showEffects);
shockwave = element.GetAttributeBool("shockwave", showEffects);
flames = element.GetAttributeBool("flames", showEffects);
@@ -191,12 +196,12 @@ namespace Barotrauma
}
}
if (MathUtils.NearlyEqual(force, 0.0f) && MathUtils.NearlyEqual(Attack.Stun, 0.0f) && MathUtils.NearlyEqual(Attack.GetTotalDamage(false), 0.0f))
if (MathUtils.NearlyEqual(force, 0.0f) && MathUtils.NearlyEqual(Attack.Stun, 0.0f) && MathUtils.NearlyEqual(Attack.GetTotalDamage(false), 0.0f) && !abilityExplosion)
{
return;
}
DamageCharacters(worldPosition, Attack, force, damageSource, attacker);
DamageCharacters(worldPosition, Attack, force, damageSource, attacker, applyToSelf);
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
{
@@ -250,7 +255,7 @@ namespace Barotrauma
partial void ExplodeProjSpecific(Vector2 worldPosition, Hull hull);
private void DamageCharacters(Vector2 worldPosition, Attack attack, float force, Entity damageSource, Character attacker)
private void DamageCharacters(Vector2 worldPosition, Attack attack, float force, Entity damageSource, Character attacker, bool applyToSelf)
{
if (attack.Range <= 0.0f) { return; }
@@ -265,6 +270,8 @@ namespace Barotrauma
{
continue;
}
if (c == attacker && !applyToSelf) { continue; }
if (onlyInside && c.Submarine == null) { continue; }
else if (onlyOutside && c.Submarine != null) { continue; }
@@ -3641,9 +3641,10 @@ namespace Barotrauma
}
if (LevelData.IsBeaconActive)
{
if (reactorContainer != null && reactorContainer.Inventory.IsEmpty())
if (reactorContainer != null && reactorContainer.Inventory.IsEmpty() &&
reactorContainer.ContainableItemIdentifiers.Any() && ItemPrefab.Prefabs.ContainsKey(reactorContainer.ContainableItemIdentifiers.FirstOrDefault()))
{
ItemPrefab fuelPrefab = ItemPrefab.Prefabs[reactorContainer.ContainableItems[0].Identifiers[0]];
ItemPrefab fuelPrefab = ItemPrefab.Prefabs[reactorContainer.ContainableItemIdentifiers.FirstOrDefault()];
Spawner.AddToSpawnQueue(
fuelPrefab, reactorContainer.Inventory,
onSpawned: (it) => reactorComponent.PowerUpImmediately());
@@ -203,8 +203,8 @@ namespace Barotrauma
if (!ResizeHorizontal || !ResizeVertical)
{
int newWidth = ResizeHorizontal ? rect.Width : (int)(defaultRect.Width * relativeScale);
int newHeight = ResizeVertical ? rect.Height : (int)(defaultRect.Height * relativeScale);
int newWidth = Math.Max(ResizeHorizontal ? rect.Width : (int)(defaultRect.Width * relativeScale), 1);
int newHeight = Math.Max(ResizeVertical ? rect.Height : (int)(defaultRect.Height * relativeScale), 1);
Rect = new Rectangle(rect.X, rect.Y, newWidth, newHeight);
if (StairDirection != Direction.None)
{
@@ -223,6 +223,11 @@ namespace Barotrauma
private readonly TargetType targetTypes;
protected HashSet<string> targetIdentifiers;
/// <summary>
/// Index of the slot the target must be in when targeting a Contained item
/// </summary>
public int TargetSlot = -1;
private readonly List<RelatedItem> requiredItems;
public readonly string[] propertyNames;
@@ -262,7 +267,9 @@ namespace Barotrauma
public readonly List<Explosion> Explosions;
private readonly List<ItemSpawnInfo> spawnItems;
private readonly bool spawnItemRandomly;
private readonly List<CharacterSpawnInfo> spawnCharacters;
private readonly List<AITrigger> aiTriggers;
private readonly List<EventPrefab> triggeredEvents;
@@ -294,7 +301,10 @@ namespace Barotrauma
get { return targetIdentifiers; }
}
public HashSet<string> AllowedAfflictions { get; private set; }
/// <summary>
/// Which type of afflictions the target must receive for the StatusEffect to be applied. Only valid when the type of the effect is OnDamaged.
/// </summary>
private readonly HashSet<(string affliction, float strength)> requiredAfflictions;
public List<Affliction> Afflictions
{
@@ -307,7 +317,7 @@ namespace Barotrauma
get { return spawnCharacters; }
}
public readonly List<Pair<string, float>> ReduceAffliction;
public readonly List<(string affliction, float amount)> ReduceAffliction;
private readonly List<int> giveExperiences;
private readonly List<(string identifier, float amount)> giveSkills;
@@ -354,12 +364,13 @@ namespace Barotrauma
{
requiredItems = new List<RelatedItem>();
spawnItems = new List<ItemSpawnInfo>();
spawnItemRandomly = element.GetAttributeBool("spawnitemrandomly", false);
spawnCharacters = new List<CharacterSpawnInfo>();
aiTriggers = new List<AITrigger>();
Afflictions = new List<Affliction>();
Explosions = new List<Explosion>();
triggeredEvents = new List<EventPrefab>();
ReduceAffliction = new List<Pair<string, float>>();
ReduceAffliction = new List<(string affliction, float amount)>();
giveExperiences = new List<int>();
giveSkills = new List<(string, float)>();
@@ -369,6 +380,8 @@ namespace Barotrauma
OnlyPlayerTriggered = element.GetAttributeBool("onlyplayertriggered", false);
AllowWhenBroken = element.GetAttributeBool("allowwhenbroken", false);
TargetSlot = element.GetAttributeInt("targetslot", -1);
Range = element.GetAttributeFloat("range", 0.0f);
Offset = element.GetAttributeVector2("offset", Vector2.Zero);
string[] targetLimbNames = element.GetAttributeStringArray("targetlimb", null) ?? element.GetAttributeStringArray("targetlimbs", null);
@@ -436,11 +449,12 @@ namespace Barotrauma
}
break;
case "allowedafflictions":
case "requiredafflictions":
string[] types = attribute.Value.Split(',');
AllowedAfflictions = new HashSet<string>();
requiredAfflictions ??= new HashSet<(string, float)>();
for (int i = 0; i < types.Length; i++)
{
AllowedAfflictions.Add(types[i].Trim().ToLowerInvariant());
requiredAfflictions.Add((types[i].Trim().ToLowerInvariant(), 0.0f));
}
break;
case "duration":
@@ -551,6 +565,13 @@ namespace Barotrauma
}
requiredItems.Add(newRequiredItem);
break;
case "requiredaffliction":
requiredAfflictions ??= new HashSet<(string, float)>();
requiredAfflictions.Add((
subElement.GetAttributeString("identifier", string.Empty),
subElement.GetAttributeFloat("minstrength", 0.0f)));
break;
case "conditional":
foreach (XAttribute attribute in subElement.Attributes())
{
@@ -593,7 +614,7 @@ namespace Barotrauma
if (subElement.Attribute("name") != null)
{
DebugConsole.ThrowError("Error in StatusEffect (" + parentDebugName + ") - define afflictions using identifiers or types instead of names.");
ReduceAffliction.Add(new Pair<string, float>(
ReduceAffliction.Add((
subElement.GetAttributeString("name", "").ToLowerInvariant(),
subElement.GetAttributeFloat(1.0f, "amount", "strength", "reduceamount")));
}
@@ -604,9 +625,7 @@ namespace Barotrauma
if (AfflictionPrefab.List.Any(ap => ap.Identifier == name || ap.AfflictionType == name))
{
ReduceAffliction.Add(new Pair<string, float>(
name,
subElement.GetAttributeFloat(1.0f, "amount", "strength", "reduceamount")));
ReduceAffliction.Add((name, subElement.GetAttributeFloat(1.0f, "amount", "strength", "reduceamount")));
}
else
{
@@ -672,6 +691,17 @@ namespace Barotrauma
return false;
}
public bool HasRequiredAfflictions(AttackResult attackResult)
{
if (requiredAfflictions == null) { return true; }
if (attackResult.Afflictions == null) { return false; }
if (attackResult.Afflictions.None(a => requiredAfflictions.Any(a2 => a.Strength >= a2.strength && a.Identifier == a2.affliction || a.Prefab.AfflictionType == a2.affliction)))
{
return false;
}
return true;
}
public virtual bool HasRequiredItems(Entity entity)
{
if (entity == null) { return true; }
@@ -1118,13 +1148,10 @@ namespace Barotrauma
{
if (Rand.Value(Rand.RandSync.Unsynced) > affliction.Probability) { continue; }
Affliction newAffliction = affliction;
if (!disableDeltaTime && !setValue)
{
newAffliction = affliction.CreateMultiplied(deltaTime);
}
if (target is Character character)
{
if (character.Removed) { continue; }
newAffliction = GetMultipliedAffliction(affliction, entity, character, deltaTime);
character.LastDamageSource = entity;
foreach (Limb limb in character.AnimController.Limbs)
{
@@ -1133,6 +1160,7 @@ namespace Barotrauma
if (targetLimbs != null && !targetLimbs.Contains(limb.type)) { continue; }
AttackResult result = limb.character.DamageLimb(position, limb, newAffliction.ToEnumerable(), stun: 0.0f, playSound: false, attackImpulse: 0.0f, attacker: affliction.Source, allowStacking: !setValue);
limb.character.TrySeverLimbJoints(limb, SeverLimbsProbability, disableDeltaTime ? result.Damage : result.Damage / deltaTime, allowBeheading: true);
RegisterTreatmentResults(entity, limb, affliction, result);
//only apply non-limb-specific afflictions to the first limb
if (!affliction.Prefab.LimbSpecific) { break; }
}
@@ -1141,14 +1169,15 @@ namespace Barotrauma
{
if (limb.IsSevered) { continue; }
if (limb.character.Removed || limb.Removed) { continue; }
newAffliction = GetMultipliedAffliction(affliction, entity, limb.character, deltaTime);
AttackResult result = limb.character.DamageLimb(position, limb, newAffliction.ToEnumerable(), stun: 0.0f, playSound: false, attackImpulse: 0.0f, attacker: affliction.Source, allowStacking: !setValue);
limb.character.TrySeverLimbJoints(limb, SeverLimbsProbability, disableDeltaTime ? result.Damage : result.Damage / deltaTime, allowBeheading: true);
RegisterTreatmentResults(entity, limb, affliction, result);
}
}
foreach (Pair<string, float> reduceAffliction in ReduceAffliction)
foreach (var (affliction, amount) in ReduceAffliction)
{
float reduceAmount = disableDeltaTime || setValue ? reduceAffliction.Second : reduceAffliction.Second * deltaTime;
Limb targetLimb = null;
Character targetCharacter = null;
if (target is Character character)
@@ -1162,8 +1191,11 @@ namespace Barotrauma
}
if (targetCharacter != null && !targetCharacter.Removed)
{
ActionType? actionType = null;
if (entity is Item item && item.UseInHealthInterface) { actionType = type; }
float reduceAmount = amount * GetAfflictionMultiplier(entity, targetCharacter, deltaTime);
float prevVitality = targetCharacter.Vitality;
targetCharacter.CharacterHealth.ReduceAffliction(targetLimb, reduceAffliction.First, reduceAmount);
targetCharacter.CharacterHealth.ReduceAffliction(targetLimb, affliction, reduceAmount, treatmentAction: actionType);
if (user != null && user != targetCharacter)
{
if (!targetCharacter.IsDead)
@@ -1305,111 +1337,124 @@ namespace Barotrauma
});
}
}
foreach (ItemSpawnInfo itemSpawnInfo in spawnItems)
if (spawnItemRandomly)
{
for (int i = 0; i < itemSpawnInfo.Count; i++)
SpawnItem(spawnItems.GetRandom());
}
else
{
foreach (ItemSpawnInfo itemSpawnInfo in spawnItems)
{
switch (itemSpawnInfo.SpawnPosition)
for (int i = 0; i < itemSpawnInfo.Count; i++)
{
case ItemSpawnInfo.SpawnPositionType.This:
Entity.Spawner.AddToSpawnQueue(itemSpawnInfo.ItemPrefab, position + Rand.Vector(itemSpawnInfo.Spread, Rand.RandSync.Server), onSpawned: newItem =>
{
Projectile projectile = newItem.GetComponent<Projectile>();
if (projectile != null && user != null && sourceBody != null && entity != null)
{
var rope = newItem.GetComponent<Rope>();
if (rope != null && sourceBody.UserData is Limb sourceLimb)
{
rope.Attach(sourceLimb, newItem);
}
float spread = MathHelper.ToRadians(Rand.Range(-itemSpawnInfo.AimSpread, itemSpawnInfo.AimSpread));
var worldPos = sourceBody.Position;
float rotation = itemSpawnInfo.Rotation;
if (user.Submarine != null)
{
worldPos += user.Submarine.Position;
}
switch (itemSpawnInfo.RotationType)
{
case ItemSpawnInfo.SpawnRotationType.Fixed:
rotation = sourceBody.TransformRotation(itemSpawnInfo.Rotation);
break;
case ItemSpawnInfo.SpawnRotationType.Target:
rotation = MathUtils.VectorToAngle(entity.WorldPosition - worldPos);
break;
case ItemSpawnInfo.SpawnRotationType.Limb:
rotation = sourceBody.TransformedRotation;
break;
case ItemSpawnInfo.SpawnRotationType.Collider:
rotation = user.AnimController.Collider.Rotation;
break;
case ItemSpawnInfo.SpawnRotationType.MainLimb:
rotation = user.AnimController.MainLimb.body.TransformedRotation;
break;
default:
throw new NotImplementedException("Not implemented: " + itemSpawnInfo.RotationType);
}
rotation += MathHelper.ToRadians(itemSpawnInfo.Rotation * user.AnimController.Dir);
projectile.Shoot(user, ConvertUnits.ToSimUnits(worldPos), ConvertUnits.ToSimUnits(worldPos), rotation + spread, ignoredBodies: user.AnimController.Limbs.Where(l => !l.IsSevered).Select(l => l.body.FarseerBody).ToList(), createNetworkEvent: true);
}
else
{
newItem.body?.ApplyLinearImpulse(Rand.Vector(1) * itemSpawnInfo.Speed);
newItem.Rotation = itemSpawnInfo.Rotation;
}
});
break;
case ItemSpawnInfo.SpawnPositionType.ThisInventory:
{
Inventory inventory = null;
if (entity is Character character && character.Inventory != null)
{
inventory = character.Inventory;
}
else if (entity is Item item)
{
inventory = item?.GetComponent<ItemContainer>()?.Inventory;
}
if (inventory != null && inventory.CanBePut(itemSpawnInfo.ItemPrefab))
{
Entity.Spawner.AddToSpawnQueue(itemSpawnInfo.ItemPrefab, inventory, spawnIfInventoryFull: false);
}
}
break;
case ItemSpawnInfo.SpawnPositionType.ContainedInventory:
{
Inventory thisInventory = null;
if (entity is Character character)
{
thisInventory = character.Inventory;
}
else if (entity is Item item)
{
thisInventory = item?.GetComponent<ItemContainer>()?.Inventory;
}
if (thisInventory != null)
{
foreach (Item item in thisInventory.AllItems)
{
Inventory containedInventory = item.GetComponent<ItemContainer>()?.Inventory;
if (containedInventory != null && containedInventory.CanBePut(itemSpawnInfo.ItemPrefab))
{
Entity.Spawner.AddToSpawnQueue(itemSpawnInfo.ItemPrefab, containedInventory, spawnIfInventoryFull: false);
}
break;
}
}
}
break;
SpawnItem(itemSpawnInfo);
}
}
}
void SpawnItem(ItemSpawnInfo chosenItemSpawnInfo)
{
switch (chosenItemSpawnInfo.SpawnPosition)
{
case ItemSpawnInfo.SpawnPositionType.This:
Entity.Spawner.AddToSpawnQueue(chosenItemSpawnInfo.ItemPrefab, position + Rand.Vector(chosenItemSpawnInfo.Spread, Rand.RandSync.Server), onSpawned: newItem =>
{
Projectile projectile = newItem.GetComponent<Projectile>();
if (projectile != null && user != null && sourceBody != null && entity != null)
{
var rope = newItem.GetComponent<Rope>();
if (rope != null && sourceBody.UserData is Limb sourceLimb)
{
rope.Attach(sourceLimb, newItem);
}
float spread = MathHelper.ToRadians(Rand.Range(-chosenItemSpawnInfo.AimSpread, chosenItemSpawnInfo.AimSpread));
var worldPos = sourceBody.Position;
float rotation = chosenItemSpawnInfo.Rotation;
if (user.Submarine != null)
{
worldPos += user.Submarine.Position;
}
switch (chosenItemSpawnInfo.RotationType)
{
case ItemSpawnInfo.SpawnRotationType.Fixed:
rotation = sourceBody.TransformRotation(chosenItemSpawnInfo.Rotation);
break;
case ItemSpawnInfo.SpawnRotationType.Target:
rotation = MathUtils.VectorToAngle(entity.WorldPosition - worldPos);
break;
case ItemSpawnInfo.SpawnRotationType.Limb:
rotation = sourceBody.TransformedRotation;
break;
case ItemSpawnInfo.SpawnRotationType.Collider:
rotation = user.AnimController.Collider.Rotation;
break;
case ItemSpawnInfo.SpawnRotationType.MainLimb:
rotation = user.AnimController.MainLimb.body.TransformedRotation;
break;
default:
throw new NotImplementedException("Not implemented: " + chosenItemSpawnInfo.RotationType);
}
rotation += MathHelper.ToRadians(chosenItemSpawnInfo.Rotation * user.AnimController.Dir);
projectile.Shoot(user, ConvertUnits.ToSimUnits(worldPos), ConvertUnits.ToSimUnits(worldPos), rotation + spread, ignoredBodies: user.AnimController.Limbs.Where(l => !l.IsSevered).Select(l => l.body.FarseerBody).ToList(), createNetworkEvent: true);
}
else
{
newItem.body?.ApplyLinearImpulse(Rand.Vector(1) * chosenItemSpawnInfo.Speed);
newItem.Rotation = chosenItemSpawnInfo.Rotation;
}
});
break;
case ItemSpawnInfo.SpawnPositionType.ThisInventory:
{
Inventory inventory = null;
if (entity is Character character && character.Inventory != null)
{
inventory = character.Inventory;
}
else if (entity is Item item)
{
inventory = item?.GetComponent<ItemContainer>()?.Inventory;
}
if (inventory != null && inventory.CanBePut(chosenItemSpawnInfo.ItemPrefab))
{
Entity.Spawner.AddToSpawnQueue(chosenItemSpawnInfo.ItemPrefab, inventory, spawnIfInventoryFull: false);
}
}
break;
case ItemSpawnInfo.SpawnPositionType.ContainedInventory:
{
Inventory thisInventory = null;
if (entity is Character character)
{
thisInventory = character.Inventory;
}
else if (entity is Item item)
{
thisInventory = item?.GetComponent<ItemContainer>()?.Inventory;
}
if (thisInventory != null)
{
foreach (Item item in thisInventory.AllItems)
{
Inventory containedInventory = item.GetComponent<ItemContainer>()?.Inventory;
if (containedInventory != null && containedInventory.CanBePut(chosenItemSpawnInfo.ItemPrefab))
{
Entity.Spawner.AddToSpawnQueue(chosenItemSpawnInfo.ItemPrefab, containedInventory, spawnIfInventoryFull: false);
}
break;
}
}
}
break;
}
}
}
ApplyProjSpecific(deltaTime, entity, targets, hull, position, playSound: true);
Character CharacterFromTarget(ISerializableEntity target)
static Character CharacterFromTarget(ISerializableEntity target)
{
Character targetCharacter = target as Character;
if (targetCharacter == null)
@@ -1494,22 +1539,24 @@ namespace Barotrauma
foreach (Affliction affliction in element.Parent.Afflictions)
{
Affliction multipliedAffliction = affliction;
if (!element.Parent.disableDeltaTime && !element.Parent.setValue) { multipliedAffliction = affliction.CreateMultiplied(deltaTime); }
Affliction newAffliction = affliction;
if (target is Character character)
{
if (character.Removed) { continue; }
character.AddDamage(character.WorldPosition, multipliedAffliction.ToEnumerable(), stun: 0.0f, playSound: false, attacker: element.User);
newAffliction = element.Parent.GetMultipliedAffliction(affliction, element.Entity, character, deltaTime);
var result = character.AddDamage(character.WorldPosition, newAffliction.ToEnumerable(), stun: 0.0f, playSound: false, attacker: element.User);
element.Parent.RegisterTreatmentResults(element.Entity, result.HitLimb, affliction, result);
}
else if (target is Limb limb)
{
if (limb.character.Removed || limb.Removed) { continue; }
limb.character.DamageLimb(limb.WorldPosition, limb, multipliedAffliction.ToEnumerable(), stun: 0.0f, playSound: false, attackImpulse: 0.0f, attacker: element.User);
newAffliction = element.Parent.GetMultipliedAffliction(affliction, element.Entity, limb.character, deltaTime);
var result = limb.character.DamageLimb(limb.WorldPosition, limb, newAffliction.ToEnumerable(), stun: 0.0f, playSound: false, attackImpulse: 0.0f, attacker: element.User);
element.Parent.RegisterTreatmentResults(element.Entity, limb, affliction, result);
}
}
foreach (Pair<string, float> reduceAffliction in element.Parent.ReduceAffliction)
foreach (var (affliction, amount) in element.Parent.ReduceAffliction)
{
Limb targetLimb = null;
Character targetCharacter = null;
@@ -1524,8 +1571,11 @@ namespace Barotrauma
}
if (targetCharacter != null && !targetCharacter.Removed)
{
ActionType? actionType = null;
if (element.Entity is Item item && item.UseInHealthInterface) { actionType = element.Parent.type; }
float reduceAmount = amount * element.Parent.GetAfflictionMultiplier(element.Entity, targetCharacter, deltaTime);
float prevVitality = targetCharacter.Vitality;
targetCharacter.CharacterHealth.ReduceAffliction(targetLimb, reduceAffliction.First, reduceAffliction.Second * deltaTime);
targetCharacter.CharacterHealth.ReduceAffliction(targetLimb, affliction, reduceAmount, treatmentAction: actionType);
if (element.User != null && element.User != targetCharacter)
{
if (!targetCharacter.IsDead)
@@ -1554,6 +1604,48 @@ namespace Barotrauma
}
}
private float GetAfflictionMultiplier(Entity entity, Character targetCharacter, float deltaTime)
{
float multiplier = !setValue && !disableDeltaTime ? deltaTime : 1.0f;
if (entity is Item sourceItem && sourceItem.HasTag("medical"))
{
multiplier *= 1 + targetCharacter.GetStatValue(StatTypes.MedicalItemEffectivenessMultiplier);
}
return multiplier;
}
private Affliction GetMultipliedAffliction(Affliction affliction, Entity entity, Character targetCharacter, float deltaTime)
{
float afflictionMultiplier = GetAfflictionMultiplier(entity, targetCharacter, deltaTime);
if (!MathUtils.NearlyEqual(afflictionMultiplier, 1.0f))
{
return affliction.CreateMultiplied(afflictionMultiplier);
}
return affliction;
}
private void RegisterTreatmentResults(Entity entity, Limb limb, Affliction affliction, AttackResult result)
{
if (entity is Item item && item.UseInHealthInterface)
{
foreach (Affliction limbAffliction in limb.character.CharacterHealth.GetAllAfflictions())
{
if (result.Afflictions.Any(a => a.Prefab == limbAffliction.Prefab) &&
(!affliction.Prefab.LimbSpecific || limb.character.CharacterHealth.GetAfflictionLimb(affliction) == limb))
{
if (type == ActionType.OnUse)
{
limbAffliction.AppliedAsSuccessfulTreatmentTime = Timing.TotalTime;
}
else if (type == ActionType.OnFailure)
{
limbAffliction.AppliedAsFailedTreatmentTime = Timing.TotalTime;
}
}
}
}
}
static partial void UpdateAllProjSpecific(float deltaTime);
public static void StopAll()
@@ -5,6 +5,7 @@ using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Barotrauma.Extensions;
using System.Xml.Linq;
namespace Barotrauma
{
@@ -443,6 +444,42 @@ namespace Barotrauma
}
}
/// <summary>
/// Constructs a string from XML in a way that allows replacing one or more variables with hard-coded or localized values. Usage example in the method's comments.
/// </summary>
public static void ConstructDescription(ref string Description, XElement descriptionElement)
{
/*
<Description tag="talentdescription.simultaneousskillgain">
<Replace tag="[skillname1]" value="skillname.helm"/>
<Replace tag="[skillname2]" value="skillname.weapons"/>
<Replace tag="[somevalue]" value="45.3"/>
</Description>
*/
string extraDescriptionLine = Get(descriptionElement.GetAttributeString("tag", string.Empty));
if (string.IsNullOrEmpty(extraDescriptionLine)) { return; }
foreach (XElement replaceElement in descriptionElement.Elements())
{
if (replaceElement.Name.ToString().ToLowerInvariant() != "replace") { continue; }
string tag = replaceElement.GetAttributeString("tag", string.Empty);
string[] replacementValues = replaceElement.GetAttributeStringArray("value", new string[0]);
string replacementValue = string.Empty;
for (int i = 0; i < replacementValues.Length; i++)
{
replacementValue += Get(replacementValues[i], returnNull: true) ?? replacementValues[i];
if (i < replacementValues.Length - 1)
{
replacementValue += ", ";
}
}
extraDescriptionLine = extraDescriptionLine.Replace(tag, replacementValue);
}
if (!string.IsNullOrEmpty(Description)) { Description += "\n"; }
Description += extraDescriptionLine;
}
public static string FormatServerMessage(string textId)
{
return $"{textId}~";