4 using Microsoft.Xna.Framework;
6 using System.Collections.Generic;
11 partial class StatusEffect
13 private List<ParticleEmitter> particleEmitters;
15 private readonly
static HashSet<StatusEffect> ActiveLoopingSounds =
new HashSet<StatusEffect>();
16 private static double LastMuffleCheckTime;
17 private readonly List<RoundSound> sounds =
new List<RoundSound>();
18 public IEnumerable<RoundSound>
Sounds {
get {
return sounds; } }
21 private Entity soundEmitter;
22 private double loopStartTime;
23 private bool loopSound;
30 private bool forcePlaySounds;
34 partial
void InitProjSpecific(
ContentXElement element,
string parentDebugName)
36 particleEmitters =
new List<ParticleEmitter>();
38 foreach (var subElement
in element.
Elements())
40 switch (subElement.Name.ToString().ToLowerInvariant())
42 case "particleemitter":
47 if (sound?.
Sound !=
null)
49 loopSound = subElement.GetAttributeBool(
"loop",
false);
50 if (subElement.GetAttribute(
"selectionmode") !=
null)
52 if (Enum.TryParse(subElement.GetAttributeString(
"selectionmode",
"Random"), out
SoundSelectionMode selectionMode))
54 soundSelectionMode = selectionMode;
65 partial
void ApplyProjSpecific(
float deltaTime, Entity entity, IReadOnlyList<ISerializableEntity> targets, Hull hull, Vector2 worldPosition,
bool playSound)
69 PlaySound(entity, hull, worldPosition);
75 float particleRotation = 0.0f;
76 bool mirrorAngle =
false;
79 bool entityAngleAssigned =
false;
80 Limb targetLimb =
null;
81 if (entity is Item item && item.body !=
null)
83 angle = item.body.Rotation + ((item.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi);
84 particleRotation = -item.body.Rotation;
87 particleRotation += MathHelper.Pi;
90 entityAngleAssigned =
true;
96 targetLimb = c.AnimController.GetLimb(l);
100 for (
int i = 0; i < targets.Count; i++)
102 if (targets[i] is Limb limb)
110 if (targetLimb !=
null && !targetLimb.Removed)
112 angle = targetLimb.body.Rotation + ((targetLimb.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi);
113 particleRotation = -targetLimb.body.Rotation;
114 float offset = targetLimb.Params.GetSpriteOrientation() - MathHelper.PiOver2;
115 particleRotation += offset;
118 angle = targetLimb.body.Rotation + ((targetLimb.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi);
119 particleRotation = -targetLimb.body.Rotation;
120 if (targetLimb.body.Dir < 0.0f)
122 particleRotation += MathHelper.Pi;
129 emitter.
Emit(deltaTime, worldPosition, hull, angle: angle, particleRotation: particleRotation, mirrorAngle: mirrorAngle);
133 private bool ignoreMuffling;
135 private void PlaySound(Entity entity, Hull hull, Vector2 worldPosition)
137 if (sounds.Count == 0) {
return; }
139 if (soundChannel ==
null || !soundChannel.
IsPlaying || forcePlaySounds)
141 if (soundChannel !=
null && soundChannel.
IsPlaying)
147 foreach (RoundSound sound
in sounds)
149 if (sound?.
Sound ==
null)
151 string errorMsg = $
"Error in StatusEffect.ApplyProjSpecific1 (sound \"{sound?.Filename ?? "unknown
"}\" was null)\n" + Environment.StackTrace.CleanupStackTrace();
152 GameAnalyticsManager.AddErrorEventOnce(
"StatusEffect.ApplyProjSpecific:SoundNull1" + Environment.StackTrace.CleanupStackTrace(), GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
155 PlaySoundOrDelayIfNotLoaded(sound);
160 int selectedSoundIndex;
163 selectedSoundIndex = item.ID % sounds.Count;
165 else if (soundSelectionMode ==
SoundSelectionMode.CharacterSpecific && entity is Character user)
167 selectedSoundIndex = user.ID % sounds.Count;
171 selectedSoundIndex = Rand.Int(sounds.Count);
173 var selectedSound = sounds[selectedSoundIndex];
174 if (selectedSound?.
Sound ==
null)
176 string errorMsg = $
"Error in StatusEffect.ApplyProjSpecific2 (sound \"{selectedSound?.Filename ?? "unknown
"}\" was null)\n" + Environment.StackTrace.CleanupStackTrace();
177 GameAnalyticsManager.AddErrorEventOnce(
"StatusEffect.ApplyProjSpecific:SoundNull2" + Environment.StackTrace.CleanupStackTrace(), GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
180 PlaySoundOrDelayIfNotLoaded(selectedSound);
185 soundChannel.
Position =
new Vector3(worldPosition, 0.0f);
188 if (soundChannel !=
null && soundChannel.
Looping)
190 ActiveLoopingSounds.Add(
this);
191 soundEmitter = entity;
192 loopStartTime = Timing.TotalTime;
195 void PlaySoundOrDelayIfNotLoaded(RoundSound selectedSound)
197 if (playSoundAfterLoadedCoroutine !=
null) {
return; }
198 if (selectedSound.Sound.Loading)
200 playSoundAfterLoadedCoroutine = CoroutineManager.StartCoroutine(PlaySoundAfterLoaded(selectedSound));
204 PlaySound(selectedSound);
208 IEnumerable<CoroutineStatus> PlaySoundAfterLoaded(RoundSound selectedSound)
210 float maxWaitTimer = 1.0f;
211 while (selectedSound.Sound.Loading && maxWaitTimer > 0.0f)
213 maxWaitTimer -= CoroutineManager.DeltaTime;
214 yield
return CoroutineStatus.Running;
216 if (!selectedSound.Sound.Loading)
218 PlaySound(selectedSound);
220 yield
return CoroutineStatus.Success;
223 void PlaySound(RoundSound selectedSound)
226 System.Diagnostics.Debug.Assert(
228 "A StatusEffect attempted to play a sound, but an looping sound is already playing. The looping sound should be stopped before playing a new one, or it will keep looping indefinitely.");
230 soundChannel = SoundPlayer.PlaySound(selectedSound.Sound, worldPosition, selectedSound.Volume, selectedSound.Range, hullGuess: hull, ignoreMuffling: selectedSound.IgnoreMuffling, freqMult: selectedSound.GetRandomFrequencyMultiplier());
231 ignoreMuffling = selectedSound.IgnoreMuffling;
232 if (soundChannel !=
null) { soundChannel.
Looping = loopSound; }
236 static partial
void UpdateAllProjSpecific(
float deltaTime)
238 bool doMuffleCheck = Timing.TotalTime > LastMuffleCheckTime + 0.2;
239 if (doMuffleCheck) { LastMuffleCheckTime = Timing.TotalTime; }
240 foreach (StatusEffect statusEffect
in ActiveLoopingSounds)
242 if (statusEffect.soundChannel ==
null) {
continue; }
246 if (Timing.TotalTime > statusEffect.loopStartTime + 0.1 && !
DurationList.Any(e => e.Parent == statusEffect))
248 statusEffect.soundChannel.FadeOutAndDispose();
249 statusEffect.soundChannel =
null;
251 else if (statusEffect.soundEmitter is { Removed: false })
253 statusEffect.soundChannel.Position =
new Vector3(statusEffect.soundEmitter.WorldPosition, 0.0f);
254 if (doMuffleCheck && !statusEffect.ignoreMuffling)
256 statusEffect.soundChannel.Muffled = SoundPlayer.ShouldMuffleSound(
257 Character.Controlled, statusEffect.soundEmitter.WorldPosition, statusEffect.soundChannel.Far,
Character.Controlled?.CurrentHull);
261 ActiveLoopingSounds.RemoveWhere(s => s.soundChannel ==
null);
IEnumerable< ContentXElement > Elements()
bool GetAttributeBool(string key, bool def)
readonly ParticleEmitterPrefab Prefab
void Emit(float deltaTime, Vector2 position, Hull? hullGuess=null, float angle=0.0f, float particleRotation=0.0f, float velocityMultiplier=1.0f, float sizeMultiplier=1.0f, float amountMultiplier=1.0f, Color? colorMultiplier=null, ParticlePrefab? overrideParticle=null, bool mirrorAngle=false, Tuple< Vector2, Vector2 >? tracerPoints=null)
readonly ParticleEmitterProperties Properties
static ? RoundSound Load(ContentXElement element, bool stream=false)
bool FadingOutAndDisposing
static readonly List< DurationListElement > DurationList
IEnumerable< RoundSound > Sounds
readonly LimbType[] targetLimbs
Which types of limbs this effect can target? Only valid when targeting characters or limbs.