Release v0.15.12.0
This commit is contained in:
@@ -137,12 +137,14 @@ namespace Barotrauma
|
||||
|
||||
public readonly ItemPrefab ItemPrefab;
|
||||
public readonly SpawnPositionType SpawnPosition;
|
||||
public readonly bool SpawnIfInventoryFull;
|
||||
public readonly float Speed;
|
||||
public readonly float Rotation;
|
||||
public readonly int Count;
|
||||
public readonly float Spread;
|
||||
public readonly SpawnRotationType RotationType;
|
||||
public readonly float AimSpread;
|
||||
public readonly bool Equip;
|
||||
|
||||
public ItemSpawnInfo(XElement element, string parentDebugName)
|
||||
{
|
||||
@@ -173,12 +175,14 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
SpawnIfInventoryFull = element.GetAttributeBool("spawnifinventoryfull", false);
|
||||
Speed = element.GetAttributeFloat("speed", 0.0f);
|
||||
|
||||
Rotation = element.GetAttributeFloat("rotation", 0.0f);
|
||||
Count = element.GetAttributeInt("count", 1);
|
||||
Spread = element.GetAttributeFloat("spread", 0f);
|
||||
AimSpread = element.GetAttributeFloat("aimspread", 0f);
|
||||
Equip = element.GetAttributeBool("equip", false);
|
||||
|
||||
string spawnTypeStr = element.GetAttributeString("spawnposition", "This");
|
||||
if (!Enum.TryParse(spawnTypeStr, ignoreCase: true, out SpawnPosition))
|
||||
@@ -193,6 +197,18 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public class GiveTalentInfo
|
||||
{
|
||||
public string[] TalentIdentifiers;
|
||||
public bool GiveRandom;
|
||||
|
||||
public GiveTalentInfo(XElement element, string parentDebugName)
|
||||
{
|
||||
TalentIdentifiers = element.GetAttributeStringArray("talentidentifiers", new string[0], convertToLowerInvariant: true);
|
||||
GiveRandom = element.GetAttributeBool("giverandom", false);
|
||||
}
|
||||
}
|
||||
|
||||
public class CharacterSpawnInfo : ISerializableEntity
|
||||
{
|
||||
public string Name => $"Character Spawn Info ({SpeciesName})";
|
||||
@@ -223,6 +239,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 +283,11 @@ namespace Barotrauma
|
||||
public readonly List<Explosion> Explosions;
|
||||
|
||||
private readonly List<ItemSpawnInfo> spawnItems;
|
||||
private readonly bool spawnItemRandomly;
|
||||
private readonly List<CharacterSpawnInfo> spawnCharacters;
|
||||
|
||||
public readonly List<GiveTalentInfo> giveTalentInfos;
|
||||
|
||||
private readonly List<AITrigger> aiTriggers;
|
||||
|
||||
private readonly List<EventPrefab> triggeredEvents;
|
||||
@@ -294,7 +319,12 @@ 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 float AfflictionMultiplier = 1.0f;
|
||||
|
||||
public List<Affliction> Afflictions
|
||||
{
|
||||
@@ -302,12 +332,17 @@ namespace Barotrauma
|
||||
private set;
|
||||
}
|
||||
|
||||
private readonly bool modifyAfflictionsByMaxVitality;
|
||||
|
||||
public IEnumerable<CharacterSpawnInfo> SpawnCharacters
|
||||
{
|
||||
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;
|
||||
|
||||
public float Duration => duration;
|
||||
|
||||
@@ -351,18 +386,26 @@ namespace Barotrauma
|
||||
{
|
||||
requiredItems = new List<RelatedItem>();
|
||||
spawnItems = new List<ItemSpawnInfo>();
|
||||
spawnItemRandomly = element.GetAttributeBool("spawnitemrandomly", false);
|
||||
spawnCharacters = new List<CharacterSpawnInfo>();
|
||||
giveTalentInfos = new List<GiveTalentInfo>();
|
||||
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)>();
|
||||
modifyAfflictionsByMaxVitality = element.GetAttributeBool("multiplyafflictionsbymaxvitality", false);
|
||||
|
||||
tags = new HashSet<string>(element.GetAttributeString("tags", "").Split(','));
|
||||
OnlyInside = element.GetAttributeBool("onlyinside", false);
|
||||
OnlyOutside = element.GetAttributeBool("onlyoutside", false);
|
||||
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);
|
||||
@@ -430,11 +473,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":
|
||||
@@ -486,13 +530,22 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (duration > 0.0f && !setValue)
|
||||
{
|
||||
//a workaround to "tags" possibly meaning either an item's tags or this status effect's tags:
|
||||
//if the status effect has a duration, assume tags mean this status effect's tags and leave item tags untouched.
|
||||
propertyAttributes.RemoveAll(a => a.Name.ToString().Equals("tags", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
int count = propertyAttributes.Count;
|
||||
|
||||
propertyNames = new string[count];
|
||||
propertyEffects = new object[count];
|
||||
|
||||
int n = 0;
|
||||
foreach (XAttribute attribute in propertyAttributes)
|
||||
{
|
||||
|
||||
propertyNames[n] = attribute.Name.ToString().ToLowerInvariant();
|
||||
propertyEffects[n] = XMLExtensions.GetAttributeObject(attribute);
|
||||
n++;
|
||||
@@ -536,6 +589,16 @@ namespace Barotrauma
|
||||
}
|
||||
requiredItems.Add(newRequiredItem);
|
||||
break;
|
||||
case "requiredaffliction":
|
||||
requiredAfflictions ??= new HashSet<(string, float)>();
|
||||
string[] ids = subElement.GetAttributeStringArray("identifier", null) ?? subElement.GetAttributeStringArray("type", new string[0]);
|
||||
foreach (string afflictionId in ids)
|
||||
{
|
||||
requiredAfflictions.Add((
|
||||
afflictionId,
|
||||
subElement.GetAttributeFloat("minstrength", 0.0f)));
|
||||
}
|
||||
break;
|
||||
case "conditional":
|
||||
foreach (XAttribute attribute in subElement.Attributes())
|
||||
{
|
||||
@@ -578,7 +641,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")));
|
||||
}
|
||||
@@ -589,9 +652,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
|
||||
{
|
||||
@@ -623,9 +684,19 @@ namespace Barotrauma
|
||||
var newSpawnCharacter = new CharacterSpawnInfo(subElement, parentDebugName);
|
||||
if (!string.IsNullOrWhiteSpace(newSpawnCharacter.SpeciesName)) { spawnCharacters.Add(newSpawnCharacter); }
|
||||
break;
|
||||
case "givetalentinfo":
|
||||
var newGiveTalentInfo = new GiveTalentInfo(subElement, parentDebugName);
|
||||
if (newGiveTalentInfo.TalentIdentifiers.Any()) { giveTalentInfos.Add(newGiveTalentInfo); }
|
||||
break;
|
||||
case "aitrigger":
|
||||
aiTriggers.Add(new AITrigger(subElement));
|
||||
break;
|
||||
case "giveexperience":
|
||||
giveExperiences.Add(subElement.GetAttributeInt("amount", 0));
|
||||
break;
|
||||
case "giveskill":
|
||||
giveSkills.Add((subElement.GetAttributeString("skillidentifier", ""), subElement.GetAttributeFloat("amount", 0)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
InitProjSpecific(element, parentDebugName);
|
||||
@@ -651,6 +722,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; }
|
||||
@@ -742,7 +824,16 @@ namespace Barotrauma
|
||||
{
|
||||
var target = targets.FirstOrDefault(t => t is Item || t is ItemComponent);
|
||||
var targetItem = target as Item ?? (target as ItemComponent)?.Item;
|
||||
if (targetItem?.ParentInventory == null) { continue; }
|
||||
if (targetItem?.ParentInventory == null)
|
||||
{
|
||||
//if we're checking for inequality, not being inside a valid container counts as success
|
||||
//(not inside a container = the container doesn't have a specific tag/value)
|
||||
if (pc.Operator == PropertyConditional.OperatorType.NotEquals)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
var owner = targetItem.ParentInventory.Owner;
|
||||
if (pc.TargetGrandParent && owner is Item ownerItem)
|
||||
{
|
||||
@@ -760,7 +851,7 @@ namespace Barotrauma
|
||||
if (HasRequiredConditions(container.AllPropertyObjects, pc.ToEnumerable(), targetingContainer: true)) { return true; }
|
||||
}
|
||||
}
|
||||
if (owner is Character character && HasRequiredConditions(character.ToEnumerable(), pc.ToEnumerable(), targetingContainer: true)) { return true; }
|
||||
if (owner is Character character && HasRequiredConditions(character.ToEnumerable(), pc.ToEnumerable(), targetingContainer: true)) { return true; }
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -785,7 +876,16 @@ namespace Barotrauma
|
||||
{
|
||||
var target = targets.FirstOrDefault(t => t is Item || t is ItemComponent);
|
||||
var targetItem = target as Item ?? (target as ItemComponent)?.Item;
|
||||
if (targetItem?.ParentInventory == null) { return false; }
|
||||
if (targetItem?.ParentInventory == null)
|
||||
{
|
||||
//if we're checking for inequality, not being inside a valid container counts as success
|
||||
//(not inside a container = the container doesn't have a specific tag/value)
|
||||
if (pc.Operator == PropertyConditional.OperatorType.NotEquals)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
var owner = targetItem.ParentInventory.Owner;
|
||||
if (pc.TargetGrandParent && owner is Item ownerItem)
|
||||
{
|
||||
@@ -1097,13 +1197,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, modifyAfflictionsByMaxVitality);
|
||||
character.LastDamageSource = entity;
|
||||
foreach (Limb limb in character.AnimController.Limbs)
|
||||
{
|
||||
@@ -1112,6 +1209,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; }
|
||||
}
|
||||
@@ -1120,14 +1218,15 @@ namespace Barotrauma
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
if (limb.character.Removed || limb.Removed) { continue; }
|
||||
newAffliction = GetMultipliedAffliction(affliction, entity, limb.character, deltaTime, modifyAfflictionsByMaxVitality);
|
||||
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)
|
||||
@@ -1141,8 +1240,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)
|
||||
@@ -1181,6 +1283,71 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
// these effects do not need to be run clientside, as they are replicated from server to clients anyway
|
||||
|
||||
foreach (int giveExperience in giveExperiences)
|
||||
{
|
||||
Character targetCharacter = CharacterFromTarget(target);
|
||||
if (targetCharacter != null && !targetCharacter.Removed)
|
||||
{
|
||||
targetCharacter?.Info?.GiveExperience(giveExperience);
|
||||
}
|
||||
}
|
||||
|
||||
if (giveSkills.Any())
|
||||
{
|
||||
foreach ((string skillIdentifier, float amount) in giveSkills)
|
||||
{
|
||||
Character targetCharacter = CharacterFromTarget(target);
|
||||
if (targetCharacter != null && !targetCharacter.Removed)
|
||||
{
|
||||
if (skillIdentifier?.ToLowerInvariant() == "randomskill")
|
||||
{
|
||||
targetCharacter.Info?.IncreaseSkillLevel(GetRandomSkill(), amount);
|
||||
|
||||
string GetRandomSkill()
|
||||
{
|
||||
return targetCharacter.Info?.Job?.Skills.Select(s => s.Identifier).GetRandom();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
targetCharacter.Info?.IncreaseSkillLevel(skillIdentifier?.ToLowerInvariant(), amount);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (giveTalentInfos.Any())
|
||||
{
|
||||
Character targetCharacter = CharacterFromTarget(target);
|
||||
if (targetCharacter?.Info == null) { continue; }
|
||||
if (!TalentTree.JobTalentTrees.TryGetValue(targetCharacter.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { continue; }
|
||||
// for the sake of technical simplicity, for now do not allow talents to be given if the character could unlock them in their talent tree as well
|
||||
IEnumerable<string> disallowedTalents = talentTree.TalentSubTrees.SelectMany(s => s.TalentOptionStages.SelectMany(o => o.Talents.Select(t => t.Identifier)));
|
||||
|
||||
foreach (GiveTalentInfo giveTalentInfo in giveTalentInfos)
|
||||
{
|
||||
IEnumerable<string> viableTalents = giveTalentInfo.TalentIdentifiers.Where(s => !targetCharacter.Info.UnlockedTalents.Contains(s) && !disallowedTalents.Contains(s));
|
||||
if (viableTalents.None()) { continue; }
|
||||
|
||||
if (giveTalentInfo.GiveRandom)
|
||||
{
|
||||
targetCharacter.GiveTalent(viableTalents.GetRandom(), true);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (string talent in viableTalents)
|
||||
{
|
||||
targetCharacter.GiveTalent(talent, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (FireSize > 0.0f && entity != null)
|
||||
@@ -1243,109 +1410,148 @@ namespace Barotrauma
|
||||
});
|
||||
}
|
||||
}
|
||||
foreach (ItemSpawnInfo itemSpawnInfo in spawnItems)
|
||||
{
|
||||
for (int i = 0; i < itemSpawnInfo.Count; i++)
|
||||
{
|
||||
switch (itemSpawnInfo.SpawnPosition)
|
||||
{
|
||||
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;
|
||||
if (spawnItemRandomly)
|
||||
{
|
||||
SpawnItem(spawnItems.GetRandom());
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (ItemSpawnInfo itemSpawnInfo in spawnItems)
|
||||
{
|
||||
for (int i = 0; i < itemSpawnInfo.Count; i++)
|
||||
{
|
||||
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) || chosenItemSpawnInfo.SpawnIfInventoryFull))
|
||||
{
|
||||
Entity.Spawner.AddToSpawnQueue(chosenItemSpawnInfo.ItemPrefab, inventory, spawnIfInventoryFull: chosenItemSpawnInfo.SpawnIfInventoryFull, onSpawned: item =>
|
||||
{
|
||||
if (chosenItemSpawnInfo.Equip && entity is Character character && character.Inventory != null)
|
||||
{
|
||||
//if the item is both pickable and wearable, try to wear it instead of picking it up
|
||||
List<InvSlotType> allowedSlots =
|
||||
item.GetComponents<Pickable>().Count() > 1 ?
|
||||
new List<InvSlotType>(item.GetComponent<Wearable>()?.AllowedSlots ?? item.GetComponent<Pickable>().AllowedSlots) :
|
||||
new List<InvSlotType>(item.AllowedSlots);
|
||||
allowedSlots.Remove(InvSlotType.Any);
|
||||
character.Inventory.TryPutItem(item, null, allowedSlots);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
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) || chosenItemSpawnInfo.SpawnIfInventoryFull))
|
||||
{
|
||||
Entity.Spawner.AddToSpawnQueue(chosenItemSpawnInfo.ItemPrefab, containedInventory, spawnIfInventoryFull: chosenItemSpawnInfo.SpawnIfInventoryFull);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ApplyProjSpecific(deltaTime, entity, targets, hull, position, playSound: true);
|
||||
|
||||
static Character CharacterFromTarget(ISerializableEntity target)
|
||||
{
|
||||
Character targetCharacter = target as Character;
|
||||
if (targetCharacter == null)
|
||||
{
|
||||
if (target is Limb targetLimb && !targetLimb.Removed)
|
||||
{
|
||||
targetCharacter = targetLimb.character;
|
||||
}
|
||||
}
|
||||
return targetCharacter;
|
||||
}
|
||||
}
|
||||
|
||||
partial void ApplyProjSpecific(float deltaTime, Entity entity, IEnumerable<ISerializableEntity> targets, Hull currentHull, Vector2 worldPosition, bool playSound);
|
||||
@@ -1353,38 +1559,31 @@ namespace Barotrauma
|
||||
private void ApplyToProperty(ISerializableEntity target, SerializableProperty property, object value, float deltaTime)
|
||||
{
|
||||
if (disableDeltaTime || setValue) { deltaTime = 1.0f; }
|
||||
Type type = value.GetType();
|
||||
if (type == typeof(float) || (type == typeof(int) && property.GetValue(target) is float))
|
||||
if (value is int || value is float)
|
||||
{
|
||||
float floatValue = Convert.ToSingle(value) * deltaTime;
|
||||
if (!setValue)
|
||||
var propertyValue = property.GetValue(target);
|
||||
if (propertyValue is float propertyValueF)
|
||||
{
|
||||
floatValue += (float)property.GetValue(target);
|
||||
float floatValue = Convert.ToSingle(value) * deltaTime;
|
||||
if (!setValue)
|
||||
{
|
||||
floatValue += propertyValueF;
|
||||
}
|
||||
property.TrySetValue(target, floatValue);
|
||||
return;
|
||||
}
|
||||
property.TrySetValue(target, floatValue);
|
||||
}
|
||||
else if (type == typeof(int) && value is int)
|
||||
{
|
||||
int intValue = (int)((int)value * deltaTime);
|
||||
if (!setValue)
|
||||
else if (propertyValue is int integer)
|
||||
{
|
||||
intValue += (int)property.GetValue(target);
|
||||
int intValue = (int)(Convert.ToInt32(value) * deltaTime);
|
||||
if (!setValue)
|
||||
{
|
||||
intValue += integer;
|
||||
}
|
||||
property.TrySetValue(target, intValue);
|
||||
return;
|
||||
}
|
||||
property.TrySetValue(target, intValue);
|
||||
}
|
||||
else if (type == typeof(bool) && value is bool)
|
||||
{
|
||||
property.TrySetValue(target, (bool)value);
|
||||
}
|
||||
else if (type == typeof(string))
|
||||
{
|
||||
property.TrySetValue(target, (string)value);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't apply value " + value.ToString() + " (" + type + ") to property \"" + property.Name + "\" (" + property.GetValue(target).GetType() + ")! "
|
||||
+ "Make sure the type of the value set in the config files matches the type of the property.");
|
||||
}
|
||||
property.TrySetValue(target, value);
|
||||
}
|
||||
|
||||
public static void UpdateAll(float deltaTime)
|
||||
@@ -1426,22 +1625,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, element.Parent.modifyAfflictionsByMaxVitality);
|
||||
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, element.Parent.modifyAfflictionsByMaxVitality);
|
||||
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;
|
||||
@@ -1456,8 +1657,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)
|
||||
@@ -1486,6 +1690,53 @@ 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 * AfflictionMultiplier;
|
||||
}
|
||||
|
||||
private Affliction GetMultipliedAffliction(Affliction affliction, Entity entity, Character targetCharacter, float deltaTime, bool modifyByMaxVitality)
|
||||
{
|
||||
float afflictionMultiplier = GetAfflictionMultiplier(entity, targetCharacter, deltaTime);
|
||||
if (modifyByMaxVitality)
|
||||
{
|
||||
afflictionMultiplier *= targetCharacter.MaxVitality / 100f;
|
||||
}
|
||||
|
||||
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 && limb != null)
|
||||
{
|
||||
foreach (Affliction limbAffliction in limb.character.CharacterHealth.GetAllAfflictions())
|
||||
{
|
||||
if (result.Afflictions != null && result.Afflictions.Any(a => a.Prefab == limbAffliction.Prefab) &&
|
||||
(!affliction.Prefab.LimbSpecific || limb.character.CharacterHealth.GetAfflictionLimb(affliction) == limb))
|
||||
{
|
||||
if (type == ActionType.OnUse)
|
||||
{
|
||||
limbAffliction.AppliedAsSuccessfulTreatmentTime = Timing.TotalTime;
|
||||
}
|
||||
else if (type == ActionType.OnFailure)
|
||||
{
|
||||
limbAffliction.AppliedAsFailedTreatmentTime = Timing.TotalTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static partial void UpdateAllProjSpecific(float deltaTime);
|
||||
|
||||
public static void StopAll()
|
||||
|
||||
Reference in New Issue
Block a user