d4c8fd5649
commit 1ec6577ad95e1d7ebc8cbfa261c9893c55ea987c Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Mon Feb 4 17:10:31 2019 +0200 Don't attempt to move character's hands in UpdateUseItem if the character is disabled. May be causing "attempted to move pulljoint extremely far" errors (see #895) commit 1aabc5110ce8e0ec208adce6380cbafd255b576f Author: ezjamsen <ezjames.fi@gmail.com> Date: Mon Feb 4 16:47:50 2019 +0200 removed syringes from the fabricator commit 0a80a2d8029275f6cffdae580d1ba86cd44dd1a2 Merge: 039c04818 686d4c482 Author: Eetu <pitkanen.eetu@gmail.com> Date: Mon Feb 4 16:43:55 2019 +0200 Merge branch 'dev' of https://github.com/Regalis11/Barotrauma into dev commit 039c04818e3598f2e4cdef4065434000a48bb229 Author: Eetu <pitkanen.eetu@gmail.com> Date: Mon Feb 4 16:43:51 2019 +0200 Updated: Alien artifacts to spawn inside containers tagged artifactholders in salvage missions & artifact events commit 686d4c482f692bcc31eb37aeaea0d5074a537fe9 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Mon Feb 4 16:42:11 2019 +0200 Fixed a bunch more buttons/sliders being invisible while pressed commit 823728511547f14a3dcb4fcf5025db592ea3dac9 Author: itchyOwl <lauri.harkanen@gmail.com> Date: Mon Feb 4 16:13:36 2019 +0200 Reset the unsaved changes when exitting the character editor (Not applied when the switching between the game view and the character editor). commit 9ecc56db4e73dd2ccd577130014020a1a502ae15 Merge: efbeda895 b824fc3f2 Author: itchyOwl <lauri.harkanen@gmail.com> Date: Mon Feb 4 16:04:57 2019 +0200 Merge branch 'dev' of https://github.com/Regalis11/Barotrauma into dev commit efbeda895fceecb8b4701f23ae5a51057aa44efb Author: itchyOwl <lauri.harkanen@gmail.com> Date: Mon Feb 4 16:03:54 2019 +0200 Fix crash on trying to undo limb/joint deletion. Undo does not currently work on these cases, but the ragdoll can be reset to the original state from the reset button. commit b824fc3f281aed76e8e0ed78ffb16d2213d2fda1 Author: ezjamsen <ezjames.fi@gmail.com> Date: Mon Feb 4 16:03:03 2019 +0200 removed syringe from medical doctor default kit commit c40c716962c7ae1a16d791b0fbd744627a2283c5 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Mon Feb 4 16:01:33 2019 +0200 Nerfed door welding (takes 4x longer to weld a door shut now) commit 7a27fcab1cec322fe75bcfad553aa42a37ec7d53 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Mon Feb 4 16:01:09 2019 +0200 Fixed StatusEffects not being able to target item components. Caused doors to be impossible to weld and most likely other issues with item StatusEffects as well. Closes #1053 commit 50c09ad85ee44018418f537827c009892f4feb23 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Mon Feb 4 15:52:38 2019 +0200 Fixed depth charges disappearing from loaders when interacting them with both hand slots full. Closes #1046 commit 648a5ba889623f776f184fb30b99133a8551234a Author: itchyOwl <lauri.harkanen@gmail.com> Date: Mon Feb 4 15:47:16 2019 +0200 Allow to delete the main limb in the character editor. Should not crash anymore. commit 751bda4d29b81c58c1b1df1653158543a3b7710d Author: itchyOwl <lauri.harkanen@gmail.com> Date: Mon Feb 4 15:46:53 2019 +0200 Add a bunch of null checks to prevent crashes when the main limb is null. commit 05c068f7aa35f7070e2cf19c526eff0c8d196b53 Author: ezjamsen <ezjames.fi@gmail.com> Date: Mon Feb 4 15:35:20 2019 +0200 Syringes removed from medical.xml due to being legacy items we should no longer support. commit 74389aa95e29fc7415de48230c8f12fd4a0a6847 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Mon Feb 4 15:30:13 2019 +0200 Attempt to fix LightSource removal causing nullref exceptions. commit 0907516bcac599b5ae20dc8e35f3a66adb8704b5 Merge: 659e1c58d f042994f4 Author: Eetu <pitkanen.eetu@gmail.com> Date: Mon Feb 4 15:25:07 2019 +0200 Merge branch 'dev' of https://github.com/Regalis11/Barotrauma into dev commit 659e1c58d3fb03d21acf2bb6065d07d1ac2ab914 Author: Eetu <pitkanen.eetu@gmail.com> Date: Mon Feb 4 15:24:59 2019 +0200 Added: Yes to All / No to All to the prompts when deleting multiple containers with contents. Fixed: Aforementioned prompts staying active when quitting the submarine editor. Moved: Logic for the prompts from ItemContainer.cs to SubEditorScreen.cs commit f042994f4b7eeff8cf5a6a68715c7c6aa6b8ddfb Merge: 70a968a16 5a2ca22bd Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Mon Feb 4 15:08:15 2019 +0200 Merge branch 'dev' of https://github.com/Regalis11/Barotrauma into dev commit 70a968a165c383c2ee765164b4507aa6072e0504 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Mon Feb 4 15:08:01 2019 +0200 Use inventory icons in the extra cargo menu, layout fixes commit 5a2ca22bde4be43f4577d20ae4e0a1ec20a23497 Merge: b2f89ea59 9ca7440be Author: ezjamsen <ezjames.fi@gmail.com> Date: Mon Feb 4 15:06:11 2019 +0200 Merge branch 'dev' of https://github.com/Regalis11/Barotrauma into dev commit b2f89ea59d481c109046d1edbd2807a516502b0c Author: ezjamsen <ezjames.fi@gmail.com> Date: Mon Feb 4 15:05:39 2019 +0200 redesigned walls in junction compartment of Typhon, closing #1042 commit 9ca7440be41a4fc2082b73618e90312af57c0817 Author: itchyOwl <lauri.harkanen@gmail.com> Date: Mon Feb 4 15:05:35 2019 +0200 Add some null checks to prevent crashing if the xml document or the source element is not found. commit 6d1331e9b86a5583761bc374c5dfb42868b03f45 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Mon Feb 4 15:03:53 2019 +0200 Fixed the layout of the extra cargo menu in server settings. Closes #1038 commit 02ac14076ace0418a5d35df7a574f99811d56017 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Mon Feb 4 14:10:34 2019 +0200 Fixed fire sounds persisting in menus. Closes #1047 commit d7d4516ddc9e21a2dba8f7bfd14a61ba7a805d4a Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Mon Feb 4 14:06:03 2019 +0200 Added a null check to CampaignUI to help diagnose #1029 commit 0fee58808c261cecd4a602db22e39dfb855ee018 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Mon Feb 4 13:55:50 2019 +0200 Fixed "attempting to remove an already removed item" console errors when ending a round. commit 5e4448024ce00231e9e479aa238ed4b134b07321 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Mon Feb 4 13:53:59 2019 +0200 Fixed crashing when ending a round while a character is outside the sub. Closes #1040 commit e3ff050561a8f823fc8abfddfec9d22ad4dc14a5 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Mon Feb 4 13:35:28 2019 +0200 Fixed battery positioning in charging docks. Closes #1041 commit d383aec6cb7f4525906fa858375729d4ad4fc0d4 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Mon Feb 4 13:26:00 2019 +0200 Fixed debugconsole adding the file number after the extension ("file123.txt (2)" instead of "file123 (2).txt"). Closes #1043 commit fee18b7ebcaea949c3f7d0c8027ab6253769b49c Merge: d03807237 c06e8990f Author: Eetu <pitkanen.eetu@gmail.com> Date: Mon Feb 4 12:10:12 2019 +0200 Merge branch 'dev' of https://github.com/Regalis11/Barotrauma into dev commit d038072372ab1993a44542298fe4031db6448ae4 Author: Eetu <pitkanen.eetu@gmail.com> Date: Mon Feb 4 12:10:04 2019 +0200 Fixed: Setting an item's condition to zero in the SubmarineEditor causing its OnBroken status effects to be fired commit c06e8990f97af7ae9b4788b281bf2801cdd8e2ff Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Mon Feb 4 11:43:35 2019 +0200 Fixed chatbox being deselected in the net lobby when receiving a lobby update from the server. Closes #1019 commit 2962647c37cec01fdb01092b94c25063c74c9ee0 Merge: b77bc3654 c89df8750 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Mon Feb 4 10:41:24 2019 +0200 Merge branch 'dev' of https://github.com/Regalis11/Barotrauma into dev commit b77bc365470c8cfb86dc83e4b21e5b36e0e28918 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Mon Feb 4 10:41:08 2019 +0200 Warning about automatic hull generation not working correctly with sloped walls when using the autohull command commit 85ef20a48532e06ef0c2774aa81e1fb517814b14 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Mon Feb 4 10:40:20 2019 +0200 Log the name of the item in the "attempting to remove an already removed item" error commit b6e32d4009a898db9c97316bcb6236170a7a92fe Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Mon Feb 4 10:39:46 2019 +0200 Fixed sliders not moving in the PowerContainer interface when an AI character is operating it commit 4b4848b5caa7bf3893e4ae2f5afef3c980f60e46 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Mon Feb 4 10:39:09 2019 +0200 Nullref fix in AIObjectiveGoto commit c89df87500c9026b3a6e75139391e64a4b1073e7 Author: Juan Pablo Arce <juanjp600@users.noreply.github.com> Date: Sun Feb 3 21:02:59 2019 -0300 whoops commit a85cc7705ea7611fff02fff7c18bc9826f03029a Author: Juan Pablo Arce <juanjp600@users.noreply.github.com> Date: Sun Feb 3 21:01:51 2019 -0300 ReduceAffliction loops back around if there's a surplus on index 0 commit e0047df3e089fd8be1646aecbd53b86b69949b13 Author: Juan Pablo Arce <juanjp600@users.noreply.github.com> Date: Sun Feb 3 19:45:52 2019 -0300 Fixed ReduceAffliction freezing Thanks to nilanthanimosus for catching this
881 lines
36 KiB
C#
881 lines
36 KiB
C#
using Microsoft.Xna.Framework;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Xml.Linq;
|
|
using Barotrauma.Items.Components;
|
|
#if CLIENT
|
|
using Barotrauma.Particles;
|
|
using Barotrauma.Sounds;
|
|
#endif
|
|
|
|
namespace Barotrauma
|
|
{
|
|
class DurationListElement
|
|
{
|
|
public StatusEffect Parent;
|
|
public Entity Entity;
|
|
public List<ISerializableEntity> Targets;
|
|
public float Timer;
|
|
}
|
|
|
|
partial class StatusEffect
|
|
{
|
|
[Flags]
|
|
public enum TargetType
|
|
{
|
|
This = 1,
|
|
Parent = 2,
|
|
Character = 4,
|
|
Contained = 8,
|
|
NearbyCharacters = 16,
|
|
NearbyItems = 32,
|
|
UseTarget = 64,
|
|
Hull = 128,
|
|
Limb = 256,
|
|
AllLimbs = 512
|
|
}
|
|
|
|
class ItemSpawnInfo
|
|
{
|
|
public enum SpawnPositionType
|
|
{
|
|
This,
|
|
ThisInventory,
|
|
ContainedInventory
|
|
}
|
|
|
|
public readonly ItemPrefab ItemPrefab;
|
|
public readonly SpawnPositionType SpawnPosition;
|
|
public readonly float Speed;
|
|
public readonly float Rotation;
|
|
|
|
public ItemSpawnInfo(XElement element, string parentDebugName)
|
|
{
|
|
if (element.Attribute("name") != null)
|
|
{
|
|
//backwards compatibility
|
|
DebugConsole.ThrowError("Error in StatusEffect config (" + element.ToString() + ") - use item identifier instead of the name.");
|
|
string itemPrefabName = element.GetAttributeString("name", "");
|
|
ItemPrefab = MapEntityPrefab.List.Find(m => m is ItemPrefab && (m.NameMatches(itemPrefabName) || m.Tags.Contains(itemPrefabName))) as ItemPrefab;
|
|
if (ItemPrefab == null)
|
|
{
|
|
DebugConsole.ThrowError("Error in StatusEffect \""+ parentDebugName + "\" - item prefab \"" + itemPrefabName + "\" not found.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
string itemPrefabIdentifier = element.GetAttributeString("identifier", "");
|
|
if (string.IsNullOrEmpty(itemPrefabIdentifier)) itemPrefabIdentifier = element.GetAttributeString("identifiers", "");
|
|
if (string.IsNullOrEmpty(itemPrefabIdentifier))
|
|
{
|
|
DebugConsole.ThrowError("Invalid item spawn in StatusEffect \"" + parentDebugName + "\" - identifier not found in the element \"" + element.ToString() + "\"");
|
|
}
|
|
ItemPrefab = MapEntityPrefab.List.Find(m => m is ItemPrefab && m.Identifier == itemPrefabIdentifier) as ItemPrefab;
|
|
if (ItemPrefab == null)
|
|
{
|
|
DebugConsole.ThrowError("Error in StatusEffect config - item prefab with the identifier \"" + itemPrefabIdentifier + "\" not found.");
|
|
return;
|
|
}
|
|
}
|
|
|
|
Speed = element.GetAttributeFloat("speed", 0.0f);
|
|
Rotation = MathHelper.ToRadians(element.GetAttributeFloat("rotation", 0.0f));
|
|
|
|
string spawnTypeStr = element.GetAttributeString("spawnposition", "This");
|
|
if (!Enum.TryParse(spawnTypeStr, out SpawnPosition))
|
|
{
|
|
DebugConsole.ThrowError("Error in StatusEffect config - \"" + spawnTypeStr + "\" is not a valid spawn position.");
|
|
}
|
|
}
|
|
}
|
|
|
|
private TargetType targetTypes;
|
|
protected HashSet<string> targetIdentifiers;
|
|
|
|
private List<RelatedItem> requiredItems;
|
|
|
|
#if CLIENT
|
|
private List<ParticleEmitter> particleEmitters;
|
|
|
|
private List<RoundSound> sounds = new List<RoundSound>();
|
|
private SoundSelectionMode soundSelectionMode;
|
|
private SoundChannel soundChannel;
|
|
private bool loopSound;
|
|
#endif
|
|
|
|
public string[] propertyNames;
|
|
private object[] propertyEffects;
|
|
|
|
private List<PropertyConditional> propertyConditionals;
|
|
|
|
private bool setValue;
|
|
|
|
private bool disableDeltaTime;
|
|
|
|
private HashSet<string> tags;
|
|
|
|
private readonly float duration;
|
|
public static readonly List<DurationListElement> DurationList = new List<DurationListElement>();
|
|
|
|
public bool CheckConditionalAlways; //Always do the conditional checks for the duration/delay. If false, only check conditional on apply.
|
|
|
|
public bool Stackable = true; //Can the same status effect be applied several times to the same targets?
|
|
|
|
private readonly int useItemCount;
|
|
|
|
private readonly bool removeItem;
|
|
|
|
public readonly ActionType type = ActionType.OnActive;
|
|
|
|
private Explosion explosion;
|
|
|
|
private List<ItemSpawnInfo> spawnItems;
|
|
|
|
public readonly float FireSize;
|
|
|
|
public HashSet<string> TargetIdentifiers
|
|
{
|
|
get { return targetIdentifiers; }
|
|
}
|
|
|
|
public List<Affliction> Afflictions
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
private List<Pair<string, float>> ReduceAffliction;
|
|
|
|
//only applicable if targeting NearbyCharacters or NearbyItems
|
|
public float Range
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
public string Tags
|
|
{
|
|
get { return string.Join(",", tags); }
|
|
set
|
|
{
|
|
tags.Clear();
|
|
if (value == null) return;
|
|
|
|
string[] newTags = value.Split(',');
|
|
foreach (string tag in newTags)
|
|
{
|
|
string newTag = tag.Trim();
|
|
if (!tags.Contains(newTag)) tags.Add(newTag);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
public static StatusEffect Load(XElement element, string parentDebugName)
|
|
{
|
|
if (element.Attribute("delay") != null)
|
|
{
|
|
return new DelayedEffect(element, parentDebugName);
|
|
}
|
|
|
|
return new StatusEffect(element, parentDebugName);
|
|
}
|
|
|
|
protected StatusEffect(XElement element, string parentDebugName)
|
|
{
|
|
requiredItems = new List<RelatedItem>();
|
|
spawnItems = new List<ItemSpawnInfo>();
|
|
Afflictions = new List<Affliction>();
|
|
ReduceAffliction = new List<Pair<string, float>>();
|
|
tags = new HashSet<string>(element.GetAttributeString("tags", "").Split(','));
|
|
|
|
Range = element.GetAttributeFloat("range", 0.0f);
|
|
|
|
#if CLIENT
|
|
particleEmitters = new List<ParticleEmitter>();
|
|
#endif
|
|
|
|
IEnumerable<XAttribute> attributes = element.Attributes();
|
|
List<XAttribute> propertyAttributes = new List<XAttribute>();
|
|
propertyConditionals = new List<PropertyConditional>();
|
|
|
|
foreach (XAttribute attribute in attributes)
|
|
{
|
|
switch (attribute.Name.ToString())
|
|
{
|
|
case "type":
|
|
try
|
|
{
|
|
type = (ActionType)Enum.Parse(typeof(ActionType), attribute.Value, true);
|
|
}
|
|
|
|
catch
|
|
{
|
|
DebugConsole.ThrowError("Invalid action type \"" + attribute.Value + "\" in StatusEffect (" + parentDebugName + ")");
|
|
}
|
|
break;
|
|
case "target":
|
|
string[] Flags = attribute.Value.Split(',');
|
|
foreach (string s in Flags)
|
|
{
|
|
targetTypes |= (TargetType)Enum.Parse(typeof(TargetType), s, true);
|
|
}
|
|
|
|
break;
|
|
case "disabledeltatime":
|
|
disableDeltaTime = attribute.GetAttributeBool(false);
|
|
break;
|
|
case "setvalue":
|
|
setValue = attribute.GetAttributeBool(false);
|
|
break;
|
|
case "targetnames":
|
|
DebugConsole.ThrowError("Error in StatusEffect config (" + parentDebugName + ") - use identifiers or tags to define the targets instead of names.");
|
|
break;
|
|
case "targetidentifiers":
|
|
string[] identifiers = attribute.Value.Split(',');
|
|
targetIdentifiers = new HashSet<string>();
|
|
for (int i = 0; i < identifiers.Length; i++)
|
|
{
|
|
targetIdentifiers.Add(identifiers[i].Trim().ToLowerInvariant());
|
|
}
|
|
break;
|
|
case "duration":
|
|
duration = attribute.GetAttributeFloat(0.0f);
|
|
break;
|
|
case "stackable":
|
|
Stackable = attribute.GetAttributeBool(true);
|
|
break;
|
|
case "checkconditionalalways":
|
|
CheckConditionalAlways = attribute.GetAttributeBool(false);
|
|
break;
|
|
case "sound":
|
|
DebugConsole.ThrowError("Error in StatusEffect " + element.Parent.Name.ToString() +
|
|
" - sounds should be defined as child elements of the StatusEffect, not as attributes.");
|
|
break;
|
|
default:
|
|
propertyAttributes.Add(attribute);
|
|
break;
|
|
}
|
|
}
|
|
|
|
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++;
|
|
}
|
|
|
|
foreach (XElement subElement in element.Elements())
|
|
{
|
|
switch (subElement.Name.ToString().ToLowerInvariant())
|
|
{
|
|
case "explosion":
|
|
explosion = new Explosion(subElement, parentDebugName);
|
|
break;
|
|
case "fire":
|
|
FireSize = subElement.GetAttributeFloat("size",10.0f);
|
|
break;
|
|
case "use":
|
|
case "useitem":
|
|
useItemCount++;
|
|
break;
|
|
case "remove":
|
|
case "removeitem":
|
|
removeItem = true;
|
|
break;
|
|
case "requireditem":
|
|
case "requireditems":
|
|
RelatedItem newRequiredItem = RelatedItem.Load(subElement, parentDebugName);
|
|
if (newRequiredItem == null)
|
|
{
|
|
DebugConsole.ThrowError("Error in StatusEffect config - requires an item with no identifiers.");
|
|
continue;
|
|
}
|
|
requiredItems.Add(newRequiredItem);
|
|
break;
|
|
case "conditional":
|
|
IEnumerable<XAttribute> conditionalAttributes = subElement.Attributes();
|
|
foreach (XAttribute attribute in conditionalAttributes)
|
|
{
|
|
if (attribute.Name.ToString().ToLowerInvariant() == "targetitemcomponent") { continue; }
|
|
propertyConditionals.Add(new PropertyConditional(attribute));
|
|
}
|
|
break;
|
|
case "affliction":
|
|
AfflictionPrefab afflictionPrefab;
|
|
if (subElement.Attribute("name") != null)
|
|
{
|
|
DebugConsole.ThrowError("Error in StatusEffect (" + parentDebugName + ") - define afflictions using identifiers instead of names.");
|
|
string afflictionName = subElement.GetAttributeString("name", "").ToLowerInvariant();
|
|
afflictionPrefab = AfflictionPrefab.List.Find(ap => ap.Name.ToLowerInvariant() == afflictionName);
|
|
if (afflictionPrefab == null)
|
|
{
|
|
DebugConsole.ThrowError("Error in StatusEffect (" + parentDebugName + ") - Affliction prefab \"" + afflictionName + "\" not found.");
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
string afflictionIdentifier = subElement.GetAttributeString("identifier", "").ToLowerInvariant();
|
|
afflictionPrefab = AfflictionPrefab.List.Find(ap => ap.Identifier.ToLowerInvariant() == afflictionIdentifier);
|
|
if (afflictionPrefab == null)
|
|
{
|
|
DebugConsole.ThrowError("Error in StatusEffect (" + parentDebugName + ") - Affliction prefab with the identifier \"" + afflictionIdentifier + "\" not found.");
|
|
continue;
|
|
}
|
|
}
|
|
|
|
float afflictionStrength = subElement.GetAttributeFloat(1.0f, "amount", "strength");
|
|
Afflictions.Add(afflictionPrefab.Instantiate(afflictionStrength));
|
|
|
|
break;
|
|
case "reduceaffliction":
|
|
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>(
|
|
subElement.GetAttributeString("name", "").ToLowerInvariant(),
|
|
subElement.GetAttributeFloat(1.0f, "amount", "strength", "reduceamount")));
|
|
}
|
|
else
|
|
{
|
|
string name = subElement.GetAttributeString("identifier", null) ?? subElement.GetAttributeString("type", null);
|
|
name = name.ToLowerInvariant();
|
|
|
|
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")));
|
|
}
|
|
else
|
|
{
|
|
DebugConsole.ThrowError("Error in StatusEffect (" + parentDebugName + ") - Affliction prefab with the identifier or type \"" + name + "\" not found.");
|
|
}
|
|
}
|
|
break;
|
|
case "spawnitem":
|
|
var newSpawnItem = new ItemSpawnInfo(subElement, parentDebugName);
|
|
if (newSpawnItem.ItemPrefab != null) spawnItems.Add(newSpawnItem);
|
|
break;
|
|
#if CLIENT
|
|
case "particleemitter":
|
|
particleEmitters.Add(new ParticleEmitter(subElement));
|
|
break;
|
|
case "sound":
|
|
var sound = Submarine.LoadRoundSound(subElement);
|
|
loopSound = subElement.GetAttributeBool("loop", false);
|
|
if (subElement.Attribute("selectionmode") != null)
|
|
{
|
|
if (Enum.TryParse(subElement.GetAttributeString("selectionmode", "Random"), out SoundSelectionMode selectionMode))
|
|
{
|
|
soundSelectionMode = selectionMode;
|
|
}
|
|
}
|
|
|
|
sounds.Add(sound);
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool HasTargetType(TargetType targetType)
|
|
{
|
|
return (targetTypes & targetType) != 0;
|
|
}
|
|
|
|
public virtual bool HasRequiredItems(Entity entity)
|
|
{
|
|
if (requiredItems == null) return true;
|
|
foreach (RelatedItem requiredItem in requiredItems)
|
|
{
|
|
if (entity == null)
|
|
{
|
|
return false;
|
|
}
|
|
else if (entity is Item item)
|
|
{
|
|
if (!requiredItem.CheckRequirements(null, item)) return false;
|
|
}
|
|
else if (entity is Character character)
|
|
{
|
|
if (!requiredItem.CheckRequirements(character, null)) return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public void GetNearbyTargets(Vector2 worldPosition, List<ISerializableEntity> targets)
|
|
{
|
|
if (Range <= 0.0f) { return; }
|
|
if (HasTargetType(TargetType.NearbyCharacters))
|
|
{
|
|
foreach (Character c in Character.CharacterList)
|
|
{
|
|
if (!c.Enabled) { continue; }
|
|
float xDiff = Math.Abs(c.WorldPosition.X - worldPosition.X);
|
|
if (xDiff > Range) { continue; }
|
|
float yDiff = Math.Abs(c.WorldPosition.Y - worldPosition.Y);
|
|
if (yDiff > Range) { continue; }
|
|
|
|
if (xDiff * xDiff + yDiff * yDiff < Range * Range) { targets.Add(c); }
|
|
}
|
|
}
|
|
if (HasTargetType(TargetType.NearbyItems))
|
|
{
|
|
foreach (Item item in Item.ItemList)
|
|
{
|
|
float xDiff = Math.Abs(item.WorldPosition.X - worldPosition.X);
|
|
if (xDiff > Range) { continue; }
|
|
float yDiff = Math.Abs(item.WorldPosition.Y - worldPosition.Y);
|
|
if (yDiff > Range) { continue; }
|
|
|
|
if (xDiff * xDiff + yDiff * yDiff < Range * Range) { targets.Add(item); }
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
public virtual bool HasRequiredConditions(List<ISerializableEntity> targets)
|
|
{
|
|
if (!propertyConditionals.Any()) return true;
|
|
foreach (ISerializableEntity target in targets)
|
|
{
|
|
if (target == null || target.SerializableProperties == null) continue;
|
|
foreach (PropertyConditional pc in propertyConditionals)
|
|
{
|
|
if (pc.Matches(target)) return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
protected bool IsValidTarget(ISerializableEntity entity)
|
|
{
|
|
if (entity is Item item)
|
|
{
|
|
if (item.HasTag(targetIdentifiers)) return true;
|
|
if (targetIdentifiers.Any(id => id == item.Prefab.Identifier)) return true;
|
|
}
|
|
else if (entity is ItemComponent itemComponent)
|
|
{
|
|
if (itemComponent.Item.HasTag(targetIdentifiers)) return true;
|
|
if (targetIdentifiers.Any(id => id == itemComponent.Item.Prefab.Identifier)) return true;
|
|
}
|
|
else if (entity is Structure structure)
|
|
{
|
|
if (targetIdentifiers.Any(id => id == structure.Prefab.Identifier)) return true;
|
|
}
|
|
else if (entity is Character character)
|
|
{
|
|
if (targetIdentifiers.Any(id => id == character.SpeciesName)) return true;
|
|
}
|
|
|
|
return targetIdentifiers.Any(id => id == entity.Name);
|
|
}
|
|
|
|
public void SetUser(Character user)
|
|
{
|
|
foreach (Affliction affliction in Afflictions)
|
|
{
|
|
affliction.Source = user;
|
|
}
|
|
}
|
|
|
|
public virtual void Apply(ActionType type, float deltaTime, Entity entity, ISerializableEntity target)
|
|
{
|
|
if (this.type != type || !HasRequiredItems(entity)) return;
|
|
|
|
if (targetIdentifiers != null && !IsValidTarget(target)) return;
|
|
|
|
if (duration > 0.0f && !Stackable)
|
|
{
|
|
//ignore if not stackable and there's already an identical statuseffect
|
|
DurationListElement existingEffect = DurationList.Find(d => d.Parent == this && d.Targets.Count == 1 && d.Targets[0] == target);
|
|
if (existingEffect != null)
|
|
{
|
|
existingEffect.Timer = Math.Max(existingEffect.Timer, duration);
|
|
return;
|
|
}
|
|
}
|
|
|
|
List<ISerializableEntity> targets = new List<ISerializableEntity> { target };
|
|
|
|
if (!HasRequiredConditions(targets)) return;
|
|
|
|
Apply(deltaTime, entity, targets);
|
|
}
|
|
|
|
public virtual void Apply(ActionType type, float deltaTime, Entity entity, List<ISerializableEntity> targets)
|
|
{
|
|
if (this.type != type) return;
|
|
|
|
//remove invalid targets
|
|
if (targetIdentifiers != null)
|
|
{
|
|
targets.RemoveAll(t => !IsValidTarget(t));
|
|
if (targets.Count == 0) return;
|
|
}
|
|
|
|
if (!HasRequiredItems(entity) || !HasRequiredConditions(targets)) return;
|
|
|
|
if (duration > 0.0f && !Stackable)
|
|
{
|
|
//ignore if not stackable and there's already an identical statuseffect
|
|
DurationListElement existingEffect = DurationList.Find(d => d.Parent == this && d.Targets.SequenceEqual(targets));
|
|
if (existingEffect != null)
|
|
{
|
|
existingEffect.Timer = Math.Max(existingEffect.Timer, duration);
|
|
return;
|
|
}
|
|
}
|
|
|
|
Apply(deltaTime, entity, targets);
|
|
}
|
|
|
|
protected void Apply(float deltaTime, Entity entity, List<ISerializableEntity> targets)
|
|
{
|
|
Hull hull = null;
|
|
if (entity is Character)
|
|
{
|
|
hull = ((Character)entity).AnimController.CurrentHull;
|
|
}
|
|
else if (entity is Item)
|
|
{
|
|
hull = ((Item)entity).CurrentHull;
|
|
}
|
|
#if CLIENT
|
|
if (entity != null && sounds.Count > 0)
|
|
{
|
|
if (soundChannel == null || !soundChannel.IsPlaying)
|
|
{
|
|
if (soundSelectionMode == SoundSelectionMode.All)
|
|
{
|
|
foreach (RoundSound sound in sounds)
|
|
{
|
|
soundChannel = SoundPlayer.PlaySound(sound.Sound, sound.Volume, sound.Range, entity.WorldPosition, hull);
|
|
if (soundChannel != null) soundChannel.Looping = loopSound;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int selectedSoundIndex = 0;
|
|
if (soundSelectionMode == SoundSelectionMode.ItemSpecific && entity is Item item)
|
|
{
|
|
selectedSoundIndex = item.ID % sounds.Count;
|
|
}
|
|
else if (soundSelectionMode == SoundSelectionMode.CharacterSpecific && entity is Character user)
|
|
{
|
|
selectedSoundIndex = user.ID % sounds.Count;
|
|
}
|
|
else
|
|
{
|
|
selectedSoundIndex = Rand.Int(sounds.Count);
|
|
}
|
|
var selectedSound = sounds[selectedSoundIndex];
|
|
soundChannel = SoundPlayer.PlaySound(selectedSound.Sound, selectedSound.Volume, selectedSound.Range, entity.WorldPosition, hull);
|
|
if (soundChannel != null) soundChannel.Looping = loopSound;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
foreach (ISerializableEntity serializableEntity in targets)
|
|
{
|
|
Item item = serializableEntity as Item;
|
|
if (item == null) continue;
|
|
|
|
Character targetCharacter = targets.FirstOrDefault(t => t is Character character && !character.Removed) as Character;
|
|
if (targetCharacter == null)
|
|
{
|
|
foreach (var target in targets)
|
|
{
|
|
if (target is Limb limb && limb.character != null && !limb.character.Removed) targetCharacter = ((Limb)target).character;
|
|
}
|
|
}
|
|
for (int i = 0; i < useItemCount; i++)
|
|
{
|
|
if (item.Removed) continue;
|
|
item.Use(deltaTime, targetCharacter, targets.FirstOrDefault(t => t is Limb) as Limb);
|
|
}
|
|
}
|
|
|
|
if (removeItem)
|
|
{
|
|
foreach (Item item in targets.FindAll(t => t is Item).Cast<Item>())
|
|
{
|
|
Entity.Spawner?.AddToRemoveQueue(item);
|
|
}
|
|
}
|
|
|
|
if (duration > 0.0f)
|
|
{
|
|
DurationListElement element = new DurationListElement
|
|
{
|
|
Parent = this,
|
|
Timer = duration,
|
|
Entity = entity,
|
|
Targets = targets
|
|
};
|
|
|
|
DurationList.Add(element);
|
|
}
|
|
else
|
|
{
|
|
foreach (ISerializableEntity target in targets)
|
|
{
|
|
if (target is Entity targetEntity)
|
|
{
|
|
if (targetEntity.Removed) continue;
|
|
}
|
|
|
|
for (int i = 0; i < propertyNames.Length; i++)
|
|
{
|
|
if (target == null || target.SerializableProperties == null ||
|
|
!target.SerializableProperties.TryGetValue(propertyNames[i], out SerializableProperty property)) continue;
|
|
ApplyToProperty(property, propertyEffects[i], deltaTime);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (explosion != null && entity != null) explosion.Explode(entity.WorldPosition, entity);
|
|
|
|
foreach (ISerializableEntity target in targets)
|
|
{
|
|
foreach (Affliction affliction in Afflictions)
|
|
{
|
|
Affliction multipliedAffliction = affliction;
|
|
if (!disableDeltaTime) multipliedAffliction = affliction.CreateMultiplied(deltaTime);
|
|
|
|
if (target is Character character)
|
|
{
|
|
character.LastDamageSource = entity;
|
|
character.CharacterHealth.ApplyAffliction(null, multipliedAffliction);
|
|
}
|
|
else if (target is Limb limb)
|
|
{
|
|
limb.character.CharacterHealth.ApplyAffliction(limb, multipliedAffliction);
|
|
}
|
|
}
|
|
|
|
foreach (Pair<string, float> reduceAffliction in ReduceAffliction)
|
|
{
|
|
float reduceAmount = disableDeltaTime ? reduceAffliction.Second : reduceAffliction.Second * deltaTime;
|
|
if (target is Character)
|
|
{
|
|
((Character)target).CharacterHealth.ReduceAffliction(null, reduceAffliction.First, reduceAmount);
|
|
}
|
|
else if (target is Limb limb)
|
|
{
|
|
limb.character.CharacterHealth.ReduceAffliction(limb, reduceAffliction.First, reduceAmount);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (FireSize > 0.0f && entity != null)
|
|
{
|
|
var fire = new FireSource(entity.WorldPosition, hull);
|
|
fire.Size = new Vector2(FireSize, fire.Size.Y);
|
|
}
|
|
|
|
if (GameMain.Client == null && entity != null && Entity.Spawner != null) //clients are not allowed to spawn items
|
|
{
|
|
foreach (ItemSpawnInfo itemSpawnInfo in spawnItems)
|
|
{
|
|
switch (itemSpawnInfo.SpawnPosition)
|
|
{
|
|
case ItemSpawnInfo.SpawnPositionType.This:
|
|
Entity.Spawner.AddToSpawnQueue(itemSpawnInfo.ItemPrefab, entity.WorldPosition);
|
|
break;
|
|
case ItemSpawnInfo.SpawnPositionType.ThisInventory:
|
|
{
|
|
if (entity is Character character)
|
|
{
|
|
if (character.Inventory != null && character.Inventory.Items.Any(it => it == null))
|
|
{
|
|
Entity.Spawner.AddToSpawnQueue(itemSpawnInfo.ItemPrefab, character.Inventory);
|
|
}
|
|
}
|
|
else if (entity is Item item)
|
|
{
|
|
var inventory = item?.GetComponent<ItemContainer>()?.Inventory;
|
|
if (inventory != null && inventory.Items.Any(it => it == null))
|
|
{
|
|
Entity.Spawner.AddToSpawnQueue(itemSpawnInfo.ItemPrefab, inventory);
|
|
}
|
|
}
|
|
}
|
|
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.Items)
|
|
{
|
|
if (item == null) continue;
|
|
Inventory containedInventory = item.GetComponent<ItemContainer>()?.Inventory;
|
|
if (containedInventory == null || !containedInventory.Items.Any(i => i == null)) continue;
|
|
Entity.Spawner.AddToSpawnQueue(itemSpawnInfo.ItemPrefab, containedInventory);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
#if CLIENT
|
|
if (entity != null)
|
|
{
|
|
foreach (ParticleEmitter emitter in particleEmitters)
|
|
{
|
|
float angle = 0.0f;
|
|
if (emitter.Prefab.CopyEntityAngle)
|
|
{
|
|
if (entity is Item it)
|
|
{
|
|
angle = it.body == null ? 0.0f : it.body.Rotation;
|
|
}
|
|
}
|
|
|
|
emitter.Emit(deltaTime, entity.WorldPosition, hull, angle);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
private void ApplyToProperty(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() is float))
|
|
{
|
|
float floatValue = Convert.ToSingle(value) * deltaTime;
|
|
|
|
if (!setValue) floatValue += (float)property.GetValue();
|
|
property.TrySetValue(floatValue);
|
|
}
|
|
else if (type == typeof(int) && value is int)
|
|
{
|
|
int intValue = (int)((int)value * deltaTime);
|
|
if (!setValue) intValue += (int)property.GetValue();
|
|
property.TrySetValue(intValue);
|
|
}
|
|
else if (type == typeof(bool) && value is bool)
|
|
{
|
|
property.TrySetValue((bool)value);
|
|
}
|
|
else if (type == typeof(string))
|
|
{
|
|
property.TrySetValue((string)value);
|
|
}
|
|
else
|
|
{
|
|
DebugConsole.ThrowError("Couldn't apply value " + value.ToString() + " (" + type + ") to property \"" + property.Name + "\" (" + property.GetValue().GetType() + ")! "
|
|
+ "Make sure the type of the value set in the config files matches the type of the property.");
|
|
}
|
|
}
|
|
|
|
public static void UpdateAll(float deltaTime)
|
|
{
|
|
DelayedEffect.Update(deltaTime);
|
|
for (int i = DurationList.Count - 1; i >= 0; i--)
|
|
{
|
|
DurationListElement element = DurationList[i];
|
|
|
|
if (element.Parent.CheckConditionalAlways && !element.Parent.HasRequiredConditions(element.Targets))
|
|
{
|
|
DurationList.RemoveAt(i);
|
|
continue;
|
|
}
|
|
|
|
element.Targets.RemoveAll(t =>
|
|
(t is Entity entity && entity.Removed) ||
|
|
(t is Limb limb && (limb.character == null || limb.character.Removed)));
|
|
if (element.Targets.Count == 0)
|
|
{
|
|
DurationList.RemoveAt(i);
|
|
continue;
|
|
}
|
|
|
|
foreach (ISerializableEntity target in element.Targets)
|
|
{
|
|
for (int n = 0; n < element.Parent.propertyNames.Length; n++)
|
|
{
|
|
if (target == null || target.SerializableProperties == null || !target.SerializableProperties.TryGetValue(element.Parent.propertyNames[n], out SerializableProperty property)) continue;
|
|
element.Parent.ApplyToProperty(property, element.Parent.propertyEffects[n], CoroutineManager.UnscaledDeltaTime);
|
|
}
|
|
|
|
foreach (Affliction affliction in element.Parent.Afflictions)
|
|
{
|
|
if (target is Character)
|
|
{
|
|
((Character)target).CharacterHealth.ApplyAffliction(null, affliction.CreateMultiplied(deltaTime));
|
|
}
|
|
else if (target is Limb limb)
|
|
{
|
|
limb.character.CharacterHealth.ApplyAffliction(limb, affliction.CreateMultiplied(deltaTime));
|
|
}
|
|
}
|
|
|
|
foreach (Pair<string, float> reduceAffliction in element.Parent.ReduceAffliction)
|
|
{
|
|
if (target is Character)
|
|
{
|
|
((Character)target).CharacterHealth.ReduceAffliction(null, reduceAffliction.First, reduceAffliction.Second * deltaTime);
|
|
}
|
|
else if (target is Limb limb)
|
|
{
|
|
limb.character.CharacterHealth.ReduceAffliction(limb, reduceAffliction.First, reduceAffliction.Second * deltaTime);
|
|
}
|
|
}
|
|
}
|
|
|
|
element.Timer -= deltaTime;
|
|
|
|
if (element.Timer > 0.0f) continue;
|
|
DurationList.Remove(element);
|
|
}
|
|
}
|
|
|
|
public static void StopAll()
|
|
{
|
|
CoroutineManager.StopCoroutines("statuseffect");
|
|
DelayedEffect.DelayList.Clear();
|
|
DurationList.Clear();
|
|
}
|
|
|
|
public void AddTag(string tag)
|
|
{
|
|
if (tags.Contains(tag)) return;
|
|
tags.Add(tag);
|
|
}
|
|
|
|
public bool HasTag(string tag)
|
|
{
|
|
if (tag == null) return true;
|
|
|
|
return (tags.Contains(tag) || tags.Contains(tag.ToLowerInvariant()));
|
|
}
|
|
}
|
|
}
|