Client LuaCsForBarotrauma
BarotraumaClient/ClientSource/Characters/Limb.cs
1 using Barotrauma.Lights;
5 using FarseerPhysics;
6 using Microsoft.Xna.Framework;
7 using Microsoft.Xna.Framework.Graphics;
8 using System;
9 using System.Collections.Generic;
10 using Barotrauma.IO;
11 using Barotrauma.Utils;
12 using System.Linq;
13 using System.Xml.Linq;
14 using SpriteParams = Barotrauma.RagdollParams.SpriteParams;
15 
16 namespace Barotrauma
17 {
18  partial class LimbJoint
19  {
20  public void UpdateDeformations(float deltaTime)
21  {
22  float diff = Math.Abs(UpperLimit - LowerLimit);
23  float strength = MathHelper.Lerp(0, 1, MathUtils.InverseLerp(0, MathHelper.Pi, diff));
24  float jointAngle = JointAngle * strength;
25 
27  JointBendDeformation limbBDeformation = LimbB.ActiveDeformations.Find(d => d is JointBendDeformation) as JointBendDeformation;
28 
29  if (limbADeformation != null && limbBDeformation != null)
30  {
31  UpdateBend(LimbA, limbADeformation, this.LocalAnchorA, -jointAngle);
32  UpdateBend(LimbB, limbBDeformation, this.LocalAnchorB, jointAngle);
33  }
34 
35  void UpdateBend(Limb limb, JointBendDeformation deformation, Vector2 localAnchor, float angle)
36  {
37  deformation.Scale = limb.DeformSprite.Size;
38 
39  Vector2 displayAnchor = ConvertUnits.ToDisplayUnits(localAnchor);
40  displayAnchor.Y = -displayAnchor.Y;
41  Vector2 refPos = displayAnchor + limb.DeformSprite.Origin;
42 
43  refPos.X /= limb.DeformSprite.Size.X;
44  refPos.Y /= limb.DeformSprite.Size.Y;
45 
46  if (Math.Abs(localAnchor.X) > Math.Abs(localAnchor.Y))
47  {
48  if (localAnchor.X > 0.0f)
49  {
50  deformation.BendRightRefPos = refPos;
51  deformation.BendRight = angle;
52  }
53  else
54  {
55  deformation.BendLeftRefPos = refPos;
56  deformation.BendLeft = angle;
57  }
58  }
59  else
60  {
61  if (localAnchor.Y > 0.0f)
62  {
63  deformation.BendUpRefPos = refPos;
64  deformation.BendUp = angle;
65  }
66  else
67  {
68  deformation.BendDownRefPos = refPos;
69  deformation.BendDown = angle;
70  }
71  }
72  }
73  }
74 
75  public void Draw(SpriteBatch spriteBatch)
76  {
77  //var mouthPos = ragdoll.GetMouthPosition();
78  //if (mouthPos != null)
79  //{
80  // var pos = ConvertUnits.ToDisplayUnits(mouthPos.Value);
81  // pos.Y = -pos.Y;
82  // ShapeExtensions.DrawPoint(spriteBatch, pos, GUIStyle.Red, size: 5);
83  //}
84 
85  // A debug visualisation on the bezier curve between limbs.
86  /*var start = LimbA.WorldPosition;
87  var end = LimbB.WorldPosition;
88  var jointAPos = ConvertUnits.ToDisplayUnits(LocalAnchorA);
89  var control = start + Vector2.Transform(jointAPos, Matrix.CreateRotationZ(LimbA.Rotation));
90  start.Y = -start.Y;
91  end.Y = -end.Y;
92  control.Y = -control.Y;
93  GUI.DrawRectangle(spriteBatch, start, Vector2.One * 5, Color.White, true);
94  GUI.DrawRectangle(spriteBatch, end, Vector2.One * 5, Color.Black, true);
95  GUI.DrawRectangle(spriteBatch, control, Vector2.One * 5, Color.Black, true);
96  GUI.DrawLine(spriteBatch, start, end, Color.White);
97  GUI.DrawLine(spriteBatch, start, control, Color.Black);
98  GUI.DrawLine(spriteBatch, control, end, Color.Black);
99  GUI.DrawBezierWithDots(spriteBatch, start, end, control, 1000, GUIStyle.Red);*/
100  }
101  }
102 
103  partial class Limb
104  {
105  //minimum duration between hit/attack sounds
106  public const float SoundInterval = 0.4f;
107  public float LastAttackSoundTime, LastImpactSoundTime;
108 
109  private float wetTimer;
110  private float dripParticleTimer;
111  private float deadTimer;
112  private Color? randomColor;
113 
118  private List<SpriteDeformation> Deformations { get; set; } = new List<SpriteDeformation>();
119  private List<SpriteDeformation> NonConditionalDeformations { get; set; } = new List<SpriteDeformation>();
120  private List<(ConditionalSprite, IEnumerable<SpriteDeformation>)> ConditionalDeformations { get; set; } = new List<(ConditionalSprite, IEnumerable<SpriteDeformation>)>();
121  public List<SpriteDeformation> ActiveDeformations { get; set; } = new List<SpriteDeformation>();
122 
123  public Sprite Sprite { get; protected set; }
124  public Sprite TintMask { get; protected set; }
125 
126  public Sprite HuskMask { get; protected set; }
127  public float TintHighlightThreshold { get; protected set; }
128  public float TintHighlightMultiplier { get; protected set; }
129 
130  private SpriteBatch.EffectWithParams tintEffectParams;
131  private SpriteBatch.EffectWithParams huskSpriteParams;
132 
133 
135 
137  {
138  get
139  {
140  var conditionalSprite = ConditionalSprites.FirstOrDefault(c => c.Exclusive && c.IsActive && c.DeformableSprite != null);
141  if (conditionalSprite != null)
142  {
143  return conditionalSprite.DeformableSprite;
144  }
145  else
146  {
147  return _deformSprite;
148  }
149  }
150  }
151 
152  public List<DecorativeSprite> DecorativeSprites { get; private set; } = new List<DecorativeSprite>();
153 
155  {
156  get
157  {
158  var conditionalSprite = ConditionalSprites.FirstOrDefault(c => c.Exclusive && c.IsActive && c.ActiveSprite != null);
159  if (conditionalSprite != null)
160  {
161  return conditionalSprite.ActiveSprite;
162  }
163  else
164  {
165  return _deformSprite != null ? _deformSprite.Sprite : Sprite;
166  }
167  }
168  }
169 
170  public Sprite GetActiveSprite(bool excludeConditionalSprites = true)
171  => excludeConditionalSprites ? (_deformSprite != null ? _deformSprite.Sprite : Sprite)
172  : ActiveSprite;
173 
174  public float DefaultSpriteDepth { get; private set; }
175 
176  public WearableSprite HairWithHatSprite { get; set; }
177  public WearableSprite HuskSprite { get; private set; }
178  public WearableSprite HerpesSprite { get; private set; }
179 
180  public void LoadHuskSprite() => HuskSprite = GetWearableSprite(WearableType.Husk);
181  public void LoadHerpesSprite() => HerpesSprite = GetWearableSprite(WearableType.Herpes);
182 
183  public float TextureScale => Params.Ragdoll.TextureScale;
184 
185  public Sprite DamagedSprite { get; private set; }
186 
187  public bool Hide
188  {
189  get => Params.Hide;
190  set => Params.Hide = value;
191  }
192 
193  public List<ConditionalSprite> ConditionalSprites { get; private set; } = new List<ConditionalSprite>();
194  private Dictionary<DecorativeSprite, SpriteState> spriteAnimState = new Dictionary<DecorativeSprite, SpriteState>();
195  private Dictionary<int, List<DecorativeSprite>> DecorativeSpriteGroups = new Dictionary<int, List<DecorativeSprite>>();
196 
197  class SpriteState
198  {
199  public float RotationState;
200  public float OffsetState;
201  public Vector2 RandomOffsetMultiplier = new Vector2(Rand.Range(-1.0f, 1.0f), Rand.Range(-1.0f, 1.0f));
202  public float RandomRotationFactor = Rand.Range(0.0f, 1.0f);
203  public float RandomScaleFactor = Rand.Range(0.0f, 1.0f);
204  public bool IsActive = true;
205  }
206 
208  {
209  get;
210  private set;
211  }
213  {
214  get;
215  private set;
216  }
217 
219  {
220  get;
221  private set;
222  }
223 
224  private float damageOverlayStrength;
226  {
227  get { return damageOverlayStrength; }
228  set { damageOverlayStrength = MathHelper.Clamp(value, 0.0f, 1.0f); }
229  }
230 
231  private float burnOverLayStrength;
232  public float BurnOverlayStrength
233  {
234  get { return burnOverLayStrength; }
235  set { burnOverLayStrength = MathHelper.Clamp(value, 0.0f, 1.0f); }
236  }
237 
238  public string HitSoundTag => Params?.Sound?.Tag;
239 
240  private readonly List<WearableSprite> wearableTypeHidingSprites = new List<WearableSprite>();
241  private readonly HashSet<WearableType> wearableTypesToHide = new HashSet<WearableType>();
242  private bool enableHuskSprite;
243  public bool EnableHuskSprite
244  {
245  get
246  {
247  return enableHuskSprite;
248  }
249  set
250  {
251  if (enableHuskSprite == value) { return; }
252  enableHuskSprite = value;
253  if (enableHuskSprite)
254  {
255  if (HuskSprite == null)
256  {
257  LoadHuskSprite();
258  }
259  }
260  if (HuskSprite != null)
261  {
262  if (enableHuskSprite)
263  {
264  OtherWearables.Insert(0, HuskSprite);
266  }
267  else
268  {
269  OtherWearables.Remove(HuskSprite);
271  }
272  }
273  }
274  }
275 
276  partial void InitProjSpecific(ContentXElement element)
277  {
278  for (int i = 0; i < Params.decorativeSpriteParams.Count; i++)
279  {
280  var param = Params.decorativeSpriteParams[i];
281  var decorativeSprite = new DecorativeSprite(param.Element, file: GetSpritePath(param.Element, param, ref _texturePath));
282  DecorativeSprites.Add(decorativeSprite);
283  int groupID = decorativeSprite.RandomGroupID;
284  if (!DecorativeSpriteGroups.ContainsKey(groupID))
285  {
286  DecorativeSpriteGroups.Add(groupID, new List<DecorativeSprite>());
287  }
288  DecorativeSpriteGroups[groupID].Add(decorativeSprite);
289  spriteAnimState.Add(decorativeSprite, new SpriteState());
290  }
291  TintMask = null;
292  float sourceRectScale = ragdoll.RagdollParams.SourceRectScale;
293  foreach (var subElement in element.Elements())
294  {
295  switch (subElement.Name.ToString().ToLowerInvariant())
296  {
297  case "sprite":
298  Sprite = new Sprite(subElement, file: GetSpritePath(subElement, Params.normalSpriteParams, ref _texturePath), sourceRectScale: sourceRectScale);
299  break;
300  case "damagedsprite":
301  DamagedSprite = new Sprite(subElement, file: GetSpritePath(subElement, Params.damagedSpriteParams, ref _damagedTexturePath), sourceRectScale: sourceRectScale);
302  break;
303  case "conditionalsprite":
304  var conditionalSprite = new ConditionalSprite(subElement, GetConditionalTarget(), file: GetSpritePath(subElement, null, ref _texturePath), sourceRectScale: sourceRectScale);
305  ConditionalSprites.Add(conditionalSprite);
306  if (conditionalSprite.DeformableSprite != null)
307  {
308  var conditionalDeformations = CreateDeformations(subElement.GetChildElement("deformablesprite"));
309  Deformations.AddRange(conditionalDeformations);
310  ConditionalDeformations.Add((conditionalSprite, conditionalDeformations));
311  }
312  break;
313  case "deformablesprite":
314  _deformSprite = new DeformableSprite(subElement, filePath: GetSpritePath(subElement, Params.deformSpriteParams, ref _texturePath), sourceRectScale: sourceRectScale);
315  var deformations = CreateDeformations(subElement);
316  Deformations.AddRange(deformations);
317  NonConditionalDeformations.AddRange(deformations);
318  break;
319  case "randomcolor":
320  randomColor = subElement.GetAttributeColorArray("colors", null)?.GetRandomUnsynced();
321  if (randomColor.HasValue)
322  {
323  Params.GetSprite().Color = randomColor.Value;
324  }
325  break;
326  case "lightsource":
327  LightSource = new LightSource(subElement, GetConditionalTarget())
328  {
329  ParentBody = body,
330  SpriteScale = Vector2.One * Scale * TextureScale
331  };
332  if (randomColor.HasValue)
333  {
334  LightSource.Color = new Color(randomColor.Value.R, randomColor.Value.G, randomColor.Value.B, LightSource.Color.A);
335  }
338  break;
339  case "tintmask":
340  ContentPath tintMaskPath = subElement.GetAttributeContentPath("texture");
341  if (!tintMaskPath.IsNullOrWhiteSpace())
342  {
343  TintMask = new Sprite(subElement, file: GetSpritePath(tintMaskPath), sourceRectScale: sourceRectScale);
344  TintHighlightThreshold = subElement.GetAttributeFloat("highlightthreshold", 0.6f);
345  TintHighlightMultiplier = subElement.GetAttributeFloat("highlightmultiplier", 0.8f);
346  }
347  break;
348  case "huskmask":
349  ContentPath huskMaskPath = subElement.GetAttributeContentPath("texture");
350  if (!huskMaskPath.IsNullOrWhiteSpace())
351  {
352  HuskMask = new Sprite(subElement, file: GetSpritePath(huskMaskPath), sourceRectScale: sourceRectScale);
353  }
354  break;
355  }
356 
357  ISerializableEntity GetConditionalTarget()
358  {
359  ISerializableEntity targetEntity;
360  string target = subElement.GetAttributeString("target", null);
361  if (string.Equals(target, "character", StringComparison.OrdinalIgnoreCase))
362  {
363  targetEntity = character;
364  }
365  else
366  {
367  targetEntity = this;
368  }
369  return targetEntity;
370  }
371 
372  IEnumerable<SpriteDeformation> CreateDeformations(XElement e)
373  {
374  List<SpriteDeformation> deformations = new List<SpriteDeformation>();
375  foreach (XElement animationElement in e.GetChildElements("spritedeformation"))
376  {
377  int sync = animationElement.GetAttributeInt("sync", -1);
378  SpriteDeformation deformation = null;
379  if (sync > -1)
380  {
381  // if the element is marked with the sync attribute, use a deformation of the same type with the same sync value, if there is one already.
382  string typeName = animationElement.GetAttributeString("type", "").ToLowerInvariant();
383  deformation = ragdoll.Limbs
384  .Where(l => l != null)
385  .SelectMany(l => l.Deformations)
386  .FirstOrDefault(d => d.TypeName == typeName && d.Sync == sync);
387  }
388  if (deformation == null)
389  {
390  deformation = SpriteDeformation.Load(animationElement, character.SpeciesName.Value);
391  if (deformation != null)
392  {
393  ragdoll.SpriteDeformations.Add(deformation);
394  }
395  }
396  if (deformation != null)
397  {
398  deformations.Add(deformation);
399  }
400  }
401  return deformations;
402  }
403  }
406  }
407 
408  private void RefreshDeformations()
409  {
410  if (_deformSprite == null) { return; }
411  if (ConditionalSprites.None())
412  {
413  ActiveDeformations = Deformations;
414  }
415  else
416  {
417  ActiveDeformations.Clear();
419  {
420  ActiveDeformations.AddRange(NonConditionalDeformations);
421  }
422  foreach (var conditionalDeformation in ConditionalDeformations)
423  {
424  if (conditionalDeformation.Item1.IsActive)
425  {
426  ActiveDeformations.AddRange(conditionalDeformation.Item2);
427  }
428  }
429  }
430  }
431 
432  public void RecreateSprites()
433  {
434  if (Sprite != null)
435  {
436  var source = Sprite.SourceElement;
437  Sprite.Remove();
438  Sprite = new Sprite(source, file: GetSpritePath(source, Params.normalSpriteParams, ref _texturePath));
439  }
440  if (_deformSprite != null)
441  {
442  var source = _deformSprite.Sprite.SourceElement;
444  _deformSprite = new DeformableSprite(source, filePath: GetSpritePath(source, Params.deformSpriteParams, ref _texturePath));
445  }
446  if (DamagedSprite != null)
447  {
448  var source = DamagedSprite.SourceElement;
450  DamagedSprite = new Sprite(source, file: GetSpritePath(source, Params.damagedSpriteParams, ref _damagedTexturePath));
451  }
452  for (int i = 0; i < ConditionalSprites.Count; i++)
453  {
454  var conditionalSprite = ConditionalSprites[i];
455  var source = conditionalSprite.ActiveSprite.SourceElement;
456  conditionalSprite.Remove();
457  ConditionalSprites[i] = new ConditionalSprite(source, character, file: GetSpritePath(source, null, ref _texturePath));
458  }
459  for (int i = 0; i < DecorativeSprites.Count; i++)
460  {
461  var decorativeSprite = DecorativeSprites[i];
462  var source = decorativeSprite.Sprite.SourceElement;
463  decorativeSprite.Remove();
464  DecorativeSprites[i] = new DecorativeSprite(source, file: GetSpritePath(source, Params.decorativeSpriteParams[i], ref _texturePath));
465  }
466  }
467 
468  private void CalculateHeadPosition(Sprite sprite)
469  {
470  if (type != LimbType.Head) { return; }
472  }
473 
474  private string _texturePath;
475  private string _damagedTexturePath;
476  private string GetSpritePath(ContentXElement element, SpriteParams spriteParams, ref string path)
477  {
478  if (path == null)
479  {
480  if (spriteParams != null)
481  {
482  //1. check if the variant file redefines the texture
483  ContentPath texturePath = character.Params.VariantFile?.Root?.GetAttributeContentPath("texture", character.Prefab.ContentPackage);
484  //2. check if the base prefab defines the texture
485  if (texturePath.IsNullOrEmpty() && !character.Prefab.VariantOf.IsEmpty)
486  {
487  Identifier speciesName = character.GetBaseCharacterSpeciesName();
488  RagdollParams parentRagdollParams = character.IsHumanoid ?
489  RagdollParams.GetDefaultRagdollParams<HumanRagdollParams>(speciesName, character.Params, character.Prefab.ContentPackage) :
490  RagdollParams.GetDefaultRagdollParams<FishRagdollParams>(speciesName, character.Params, character.Prefab.ContentPackage);
491 
492  texturePath = parentRagdollParams.OriginalElement?.GetAttributeContentPath("texture");
493  }
494  //3. "default case", get the texture from this character's XML
495  texturePath ??= ContentPath.FromRaw(spriteParams.Element.ContentPackage ?? character.Prefab.ContentPackage, spriteParams.GetTexturePath());
496  path = GetSpritePath(texturePath);
497  }
498  else
499  {
500  ContentPath texturePath = element.GetAttributeContentPath("texture");
501  texturePath = texturePath.IsNullOrWhiteSpace()
503  : texturePath;
504  path = GetSpritePath(texturePath);
505  }
506  }
507  return path;
508  }
509 
513  public static string GetSpritePath(ContentPath texturePath, CharacterInfo characterInfo)
514  {
515  string spritePath = texturePath.Value;
516  string spritePathWithTags = spritePath;
517  if (characterInfo != null)
518  {
519  spritePath = characterInfo.ReplaceVars(spritePath);
520  characterInfo.VerifySpriteTagsLoaded();
521  if (characterInfo.SpriteTags.Any())
522  {
523  string tags = "";
524  characterInfo.SpriteTags.ForEach(tag => tags += $"[{tag}]");
525 
526  spritePathWithTags = Path.Combine(
527  Path.GetDirectoryName(spritePath),
528  Path.GetFileNameWithoutExtension(spritePath) + tags + Path.GetExtension(spritePath));
529  }
530  }
531  return File.Exists(spritePathWithTags) ? spritePathWithTags : spritePath;
532  }
533 
534 
535  private string GetSpritePath(ContentPath texturePath)
536  {
537  if (!character.IsHumanoid) { return texturePath.Value; }
538  return GetSpritePath(texturePath, character.Info);
539  }
540 
541  partial void LoadParamsProjSpecific()
542  {
543  bool isFlipped = dir == Direction.Left;
544  Sprite?.LoadParams(Params.normalSpriteParams, isFlipped);
545  DamagedSprite?.LoadParams(Params.damagedSpriteParams, isFlipped);
546  _deformSprite?.Sprite.LoadParams(Params.deformSpriteParams, isFlipped);
547  for (int i = 0; i < DecorativeSprites.Count; i++)
548  {
549  DecorativeSprites[i].Sprite?.LoadParams(Params.decorativeSpriteParams[i], isFlipped);
550  }
551  }
552 
553  partial void AddDamageProjSpecific(bool playSound, AttackResult result)
554  {
555  float bleedingDamage = 0;
557  {
558  foreach (var affliction in result.Afflictions)
559  {
560  if (affliction is AfflictionBleeding bleeding && bleeding.Prefab.DamageParticles)
561  {
562  bleedingDamage += affliction.GetVitalityDecrease(null);
563  }
564  }
565  }
566  float damage = 0;
567  foreach (var affliction in result.Afflictions)
568  {
569  if (affliction.Prefab.DamageParticles && affliction.Prefab.AfflictionType == AfflictionPrefab.DamageType)
570  {
571  damage += affliction.GetVitalityDecrease(null);
572  }
573  }
574  float damageMultiplier = 1;
575  float bleedingDamageMultiplier = 1;
576  foreach (DamageModifier damageModifier in result.AppliedDamageModifiers)
577  {
578  if (damageModifier.MatchesAfflictionType(AfflictionPrefab.DamageType))
579  {
580  damageMultiplier *= damageModifier.DamageMultiplier;
581  }
582  else if (damageModifier.MatchesAfflictionType(AfflictionPrefab.BleedingType))
583  {
584  bleedingDamageMultiplier *= damageModifier.DamageMultiplier;
585  }
586  }
587  if (playSound)
588  {
589  string damageSoundType = (bleedingDamage > damage) ? "LimbSlash" : "LimbBlunt";
590  foreach (DamageModifier damageModifier in result.AppliedDamageModifiers)
591  {
592  if (!string.IsNullOrWhiteSpace(damageModifier.DamageSound))
593  {
594  damageSoundType = damageModifier.DamageSound;
595  break;
596  }
597  }
598  SoundPlayer.PlayDamageSound(damageSoundType, Math.Max(damage, bleedingDamage), WorldPosition);
599  }
600 
601  // spawn damage particles
602  float damageParticleAmount = damage < 1 ? 0 : Math.Min(damage / 5, 1.0f) * damageMultiplier;
603  if (damageParticleAmount > 0.001f)
604  {
605  foreach (ParticleEmitter emitter in character.DamageEmitters)
606  {
607  if (emitter?.Prefab == null) { continue; }
608  if (InWater && emitter.Prefab.ParticlePrefab.DrawTarget == ParticlePrefab.DrawTargetType.Air) { continue; }
609  if (!InWater && emitter.Prefab.ParticlePrefab.DrawTarget == ParticlePrefab.DrawTargetType.Water) { continue; }
610  ParticlePrefab overrideParticle = null;
611  foreach (DamageModifier damageModifier in result.AppliedDamageModifiers)
612  {
613  if (damageModifier.DamageMultiplier > 0 && !string.IsNullOrWhiteSpace(damageModifier.DamageParticle))
614  {
615  overrideParticle = ParticleManager.FindPrefab(damageModifier.DamageParticle);
616  break;
617  }
618  }
619  emitter.Emit(1.0f, WorldPosition, character.CurrentHull, amountMultiplier: damageParticleAmount, overrideParticle: overrideParticle);
620  }
621  }
622 
623  if (bleedingDamage > 0)
624  {
625  float bloodParticleAmount = Math.Min(bleedingDamage / 5, 1.0f) * bleedingDamageMultiplier;
626  float bloodParticleSize = MathHelper.Clamp(bleedingDamage / 5, 0.1f, 1.0f);
627 
628  foreach (ParticleEmitter emitter in character.BloodEmitters)
629  {
630  if (emitter?.Prefab == null) { continue; }
631  if (InWater && emitter.Prefab.ParticlePrefab.DrawTarget == ParticlePrefab.DrawTargetType.Air) { continue; }
632  if (!InWater && emitter.Prefab.ParticlePrefab.DrawTarget == ParticlePrefab.DrawTargetType.Water) { continue; }
633  emitter.Emit(1.0f, WorldPosition, character.CurrentHull, sizeMultiplier: bloodParticleSize, amountMultiplier: bloodParticleAmount);
634  }
635  }
636  }
637 
638  partial void UpdateProjSpecific(float deltaTime)
639  {
640  if (!body.Enabled) { return; }
641 
642  if (IsDead)
643  {
644  var spriteParams = Params.GetSprite();
645  if (spriteParams != null && spriteParams.DeadColorTime > 0 && deadTimer < spriteParams.DeadColorTime)
646  {
647  deadTimer += deltaTime;
648  }
649  }
650 
651  if (InWater)
652  {
653  wetTimer = 1.0f;
654  }
655  else
656  {
657  wetTimer -= deltaTime * 0.1f;
658  if (wetTimer > 0.0f)
659  {
660  dripParticleTimer += wetTimer * deltaTime * Mass * (wetTimer > 0.9f ? 50.0f : 5.0f);
661  if (dripParticleTimer > 1.0f)
662  {
663  float dropRadius = body.BodyShape == PhysicsBody.Shape.Rectangle ? Math.Min(body.Width, body.Height) : body.Radius;
664  GameMain.ParticleManager.CreateParticle(
665  "waterdrop",
666  WorldPosition + Rand.Vector(Rand.Range(0.0f, ConvertUnits.ToDisplayUnits(dropRadius))),
667  ConvertUnits.ToDisplayUnits(body.LinearVelocity),
669  dripParticleTimer = 0.0f;
670  }
671  }
672  }
673 
674  foreach (var conditionalSprite in ConditionalSprites)
675  {
676  conditionalSprite.CheckConditionals();
677  }
678 
679  if (LightSource != null)
680  {
682  LightSource.Rotation = (dir == Direction.Right) ? body.Rotation : body.Rotation - MathHelper.Pi;
683  if (LightSource.LightSprite != null)
684  {
686  }
688  {
690  }
692  }
693 
694  UpdateSpriteStates(deltaTime);
695  RefreshDeformations();
696  }
697 
698  public void Draw(SpriteBatch spriteBatch, Camera cam, Color? overrideColor = null, bool disableDeformations = false)
699  {
700  var spriteParams = Params.GetSprite();
701  if (spriteParams == null || Alpha <= 0) { return; }
702  float burn = spriteParams.IgnoreTint ? 0 : burnOverLayStrength;
703  float brightness = Math.Max(1.0f - burn, 0.2f);
704  Color tintedColor = spriteParams.Color;
705  if (!spriteParams.IgnoreTint)
706  {
707  tintedColor = tintedColor.Multiply(ragdoll.RagdollParams.Color);
708  if (character.Info != null)
709  {
710  tintedColor = tintedColor.Multiply(character.Info.Head.SkinColor);
711  }
712  if (character.CharacterHealth.FaceTint.A > 0 && type == LimbType.Head)
713  {
714  tintedColor = Color.Lerp(tintedColor, character.CharacterHealth.FaceTint.Opaque(), character.CharacterHealth.FaceTint.A / 255.0f);
715  }
717  {
718  tintedColor = Color.Lerp(tintedColor, character.CharacterHealth.BodyTint.Opaque(), character.CharacterHealth.BodyTint.A / 255.0f);
719  }
720  }
721  Color color = new Color(tintedColor.Multiply(brightness), tintedColor.A);
722  Color colorWithoutTint = new Color(spriteParams.Color.Multiply(brightness), spriteParams.Color.A);
723  Color blankColor = new Color(brightness, brightness, brightness, 1);
724  if (deadTimer > 0)
725  {
726  color = Color.Lerp(color, spriteParams.DeadColor, MathUtils.InverseLerp(0, spriteParams.DeadColorTime, deadTimer));
727  colorWithoutTint = Color.Lerp(colorWithoutTint, spriteParams.DeadColor, MathUtils.InverseLerp(0, spriteParams.DeadColorTime, deadTimer));
728  }
729 
730  color = overrideColor ?? color;
731  colorWithoutTint = overrideColor ?? colorWithoutTint;
732  blankColor = overrideColor ?? blankColor;
733  color *= Alpha;
734  blankColor *= Alpha;
735 
736  if (isSevered)
737  {
738  if (severedFadeOutTimer > SeveredFadeOutTime)
739  {
740  if (LightSource != null) { LightSource.Enabled = false; }
741  return;
742  }
743  else if (severedFadeOutTimer > SeveredFadeOutTime - 1.0f)
744  {
745  color *= SeveredFadeOutTime - severedFadeOutTimer;
746  colorWithoutTint *= SeveredFadeOutTime - severedFadeOutTimer;
747  }
748  }
749 
751 
752  bool hideLimb = Hide ||
753  OtherWearables.Any(w => w.HideLimb) ||
754  WearingItems.Any(w => w.HideLimb);
755 
756  bool drawHuskSprite = HuskSprite != null && !wearableTypesToHide.Contains(WearableType.Husk);
757 
758  var activeSprite = ActiveSprite;
759  if (type == LimbType.Head)
760  {
761  CalculateHeadPosition(activeSprite);
762  }
763 
765  float depthStep = 0.000001f;
766 
767  if (!hideLimb)
768  {
769  var deformSprite = DeformSprite;
770  if (deformSprite != null && !disableDeformations)
771  {
772  if (ActiveDeformations.Any())
773  {
774  var deformation = SpriteDeformation.GetDeformation(ActiveDeformations, deformSprite.Size);
775  deformSprite.Deform(deformation);
776  if (LightSource != null && LightSource.DeformableLightSprite != null)
777  {
778  deformation = SpriteDeformation.GetDeformation(ActiveDeformations, deformSprite.Size, dir == Direction.Left);
780  }
781  }
782  else
783  {
784  deformSprite.Reset();
785  }
786  body.Draw(deformSprite, cam, Vector2.One * Scale * TextureScale, color, Params.MirrorHorizontally);
787  }
788  else
789  {
790  bool useTintMask = TintMask != null && spriteBatch.GetCurrentEffect() is null;
791  if (useTintMask && Sprite?.Texture != null && TintMask?.Texture != null)
792  {
793  tintEffectParams.Effect ??= GameMain.GameScreen.ThresholdTintEffect;
794  tintEffectParams.Params ??= new Dictionary<string, object>();
795  var parameters = tintEffectParams.Params;
796  parameters["xBaseTexture"] = Sprite.Texture;
797  parameters["xTintMaskTexture"] = TintMask.Texture;
798  if (drawHuskSprite && HuskMask != null)
799  {
800  parameters["xCutoffTexture"] = HuskMask.Texture;
801  parameters["baseToCutoffSizeRatio"] = (float)Sprite.Texture.Width / (float)HuskMask.Texture.Width;
802  }
803  else
804  {
805  parameters["xCutoffTexture"] = GUI.WhiteTexture;
806  parameters["baseToCutoffSizeRatio"] = 1.0f;
807  }
808  parameters["highlightThreshold"] = TintHighlightThreshold;
809  parameters["highlightMultiplier"] = TintHighlightMultiplier;
810  spriteBatch.SwapEffect(tintEffectParams);
811  }
812  body.Draw(spriteBatch, activeSprite, color, null, Scale * TextureScale, Params.MirrorHorizontally, Params.MirrorVertically);
813  if (useTintMask)
814  {
815  spriteBatch.SwapEffect(null);
816  }
817  }
818  // Handle non-exlusive, i.e. additional conditional sprites
819  foreach (var conditionalSprite in ConditionalSprites)
820  {
821  // Exclusive conditional sprites are handled in the Properties
822  if (conditionalSprite.Exclusive) { continue; }
823  if (!conditionalSprite.IsActive) { continue; }
824  if (conditionalSprite.DeformableSprite != null)
825  {
826  var defSprite = conditionalSprite.DeformableSprite;
827  if (ActiveDeformations.Any())
828  {
829  var deformation = SpriteDeformation.GetDeformation(ActiveDeformations, defSprite.Size);
830  defSprite.Deform(deformation);
831  }
832  else
833  {
834  defSprite.Reset();
835  }
836  body.Draw(defSprite, cam, Vector2.One * Scale * TextureScale, color, Params.MirrorHorizontally);
837  }
838  else
839  {
840  body.Draw(spriteBatch, conditionalSprite.Sprite, color, depth: activeSprite.Depth - (depthStep * 50), Scale * TextureScale, Params.MirrorHorizontally, Params.MirrorVertically);
841  }
842  }
843  }
844  SpriteEffects spriteEffect = (dir == Direction.Right) ? SpriteEffects.None : SpriteEffects.FlipHorizontally;
845  if (LightSource != null)
846  {
847  LightSource.LightSpriteEffect = (dir == Direction.Right) ? SpriteEffects.None : SpriteEffects.FlipVertically;
848  }
849  float step = depthStep;
850  WearableSprite onlyDrawable = WearingItems.Find(w => w.HideOtherWearables);
851  if (Params.MirrorHorizontally)
852  {
853  spriteEffect = spriteEffect == SpriteEffects.None ? SpriteEffects.FlipHorizontally : SpriteEffects.None;
854  }
855  if (Params.MirrorVertically)
856  {
857  spriteEffect |= SpriteEffects.FlipVertically;
858  }
859  if (onlyDrawable == null)
860  {
861  if (HerpesSprite != null && !wearableTypesToHide.Contains(WearableType.Herpes) && herpesStrength > 0)
862  {
863  float alpha = Math.Min(herpesStrength * 2 / 100.0f, 1.0f);
864  DrawWearable(HerpesSprite, depthStep, spriteBatch, blankColor, alpha: alpha, spriteEffect);
865  depthStep += step;
866  }
867  if (drawHuskSprite)
868  {
869  bool useTintEffect = HuskMask != null && spriteBatch.GetCurrentEffect() is null;
870  if (useTintEffect)
871  {
872  huskSpriteParams.Effect ??= GameMain.GameScreen.ThresholdTintEffect;
873  huskSpriteParams.Params ??= new Dictionary<string, object>();
874  var parameters = huskSpriteParams.Params;
875  parameters["xCutoffTexture"] = GUI.WhiteTexture;
876  parameters["baseToCutoffSizeRatio"] = 1.0f;
877  spriteBatch.SwapEffect(huskSpriteParams);
878  }
879  DrawWearable(HuskSprite, depthStep, spriteBatch, color, alpha: color.A / 255f, spriteEffect);
880  if (useTintEffect)
881  {
882  spriteBatch.SwapEffect(null);
883  }
884  depthStep += step;
885  }
886  foreach (WearableSprite wearable in OtherWearables)
887  {
888  if (wearable.Type == WearableType.Husk) { continue; }
889  if (wearableTypesToHide.Contains(wearable.Type))
890  {
891  if (wearable.Type == WearableType.Hair)
892  {
893  if (HairWithHatSprite != null && !hideLimb)
894  {
895  DrawWearable(HairWithHatSprite, depthStep, spriteBatch, blankColor, alpha: color.A / 255f, spriteEffect);
896  depthStep += step;
897  continue;
898  }
899  }
900  else
901  {
902  continue;
903  }
904  }
905  DrawWearable(wearable, depthStep, spriteBatch, blankColor, alpha: color.A / 255f, spriteEffect);
906  //if there are multiple sprites on this limb, make the successive ones be drawn in front
907  depthStep += step;
908  }
909  }
910  foreach (WearableSprite wearable in WearingItems)
911  {
912  if (onlyDrawable != null && onlyDrawable != wearable && wearable.CanBeHiddenByOtherWearables) { continue; }
913  if (wearable.CanBeHiddenByItem.Any())
914  {
915  bool hiddenByOtherItem = false;
916  foreach (var otherWearable in WearingItems)
917  {
918  if (otherWearable == wearable) { continue; }
919  if (wearable.CanBeHiddenByItem.Contains(otherWearable.WearableComponent.Item.Prefab.Identifier))
920  {
921  hiddenByOtherItem = true;
922  break;
923  }
924  foreach (Identifier tag in wearable.CanBeHiddenByItem)
925  {
926  if (otherWearable.WearableComponent.Item.HasTag(tag))
927  {
928  hiddenByOtherItem = true;
929  break;
930  }
931  }
932  }
933  if (hiddenByOtherItem) { continue; }
934  }
935  DrawWearable(wearable, depthStep, spriteBatch, blankColor, alpha: color.A / 255f, spriteEffect);
936  //if there are multiple sprites on this limb, make the successive ones be drawn in front
937  depthStep += step;
938  }
939  if (!Hide && onlyDrawable == null)
940  {
941  foreach (var decorativeSprite in DecorativeSprites)
942  {
943  if (!spriteAnimState[decorativeSprite].IsActive) { continue; }
944  Color c = new Color(decorativeSprite.Color.R / 255f * brightness, decorativeSprite.Color.G / 255f * brightness, decorativeSprite.Color.B / 255f * brightness, decorativeSprite.Color.A / 255f);
945  if (deadTimer > 0)
946  {
947  c = Color.Lerp(c, spriteParams.DeadColor, MathUtils.InverseLerp(0, Params.GetSprite().DeadColorTime, deadTimer));
948  }
949  c = overrideColor ?? c;
950  float rotation = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState, spriteAnimState[decorativeSprite].RandomRotationFactor);
951  Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier) * Scale;
952  var ca = (float)Math.Cos(-body.Rotation);
953  var sa = (float)Math.Sin(-body.Rotation);
954  Vector2 transformedOffset = new Vector2(ca * offset.X + sa * offset.Y, -sa * offset.X + ca * offset.Y);
955  decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(body.DrawPosition.X + transformedOffset.X, -(body.DrawPosition.Y + transformedOffset.Y)), c,
956  -body.Rotation + rotation, decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale, spriteEffect,
957  depth: activeSprite.Depth - depthStep);
958  depthStep += step;
959  }
960  if (damageOverlayStrength > 0.0f && DamagedSprite != null)
961  {
962  DamagedSprite.Draw(spriteBatch,
963  new Vector2(body.DrawPosition.X, -body.DrawPosition.Y),
964  colorWithoutTint * damageOverlayStrength, activeSprite.Origin,
966  Scale, spriteEffect, activeSprite.Depth - depthStep * Math.Max(1, WearingItems.Count * 2)); // Multiply by 2 to get rid of z-fighting with some clothing combos
967  }
968  }
969 
970  if (GameMain.DebugDraw)
971  {
972  if (pullJoint != null)
973  {
974  Vector2 pos = ConvertUnits.ToDisplayUnits(pullJoint.WorldAnchorB);
975  GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)-pos.Y, 5, 5), GUIStyle.Red, true);
976  }
977  var bodyDrawPos = body.DrawPosition;
978  bodyDrawPos.Y = -bodyDrawPos.Y;
979  if (IsStuck)
980  {
981  Vector2 from = ConvertUnits.ToDisplayUnits(attachJoint.WorldAnchorA);
982  from.Y = -from.Y;
983  Vector2 to = ConvertUnits.ToDisplayUnits(attachJoint.WorldAnchorB);
984  to.Y = -to.Y;
985  var localFront = body.GetLocalFront(Params.GetSpriteOrientation());
986  var front = ConvertUnits.ToDisplayUnits(body.FarseerBody.GetWorldPoint(localFront));
987  front.Y = -front.Y;
988  GUI.DrawLine(spriteBatch, bodyDrawPos, front, Color.Yellow, width: 2);
989  GUI.DrawLine(spriteBatch, from, to, GUIStyle.Red, width: 1);
990  GUI.DrawRectangle(spriteBatch, new Rectangle((int)from.X, (int)from.Y, 12, 12), Color.White, true);
991  GUI.DrawRectangle(spriteBatch, new Rectangle((int)to.X, (int)to.Y, 12, 12), Color.White, true);
992  GUI.DrawRectangle(spriteBatch, new Rectangle((int)from.X, (int)from.Y, 10, 10), Color.Blue, true);
993  GUI.DrawRectangle(spriteBatch, new Rectangle((int)to.X, (int)to.Y, 10, 10), GUIStyle.Red, true);
994  GUI.DrawRectangle(spriteBatch, new Rectangle((int)front.X, (int)front.Y, 10, 10), Color.Yellow, true);
995 
996  //Vector2 mainLimbFront = ConvertUnits.ToDisplayUnits(ragdoll.MainLimb.body.FarseerBody.GetWorldPoint(ragdoll.MainLimb.body.GetFrontLocal(MathHelper.ToRadians(limbParams.Orientation))));
997  //mainLimbFront.Y = -mainLimbFront.Y;
998  //var mainLimbDrawPos = ragdoll.MainLimb.body.DrawPosition;
999  //mainLimbDrawPos.Y = -mainLimbDrawPos.Y;
1000  //GUI.DrawLine(spriteBatch, mainLimbDrawPos, mainLimbFront, Color.White, width: 5);
1001  //GUI.DrawRectangle(spriteBatch, new Rectangle((int)mainLimbFront.X, (int)mainLimbFront.Y, 10, 10), Color.Yellow, true);
1002  }
1003  //DrawDamageModifiers(spriteBatch, cam, bodyDrawPos, isScreenSpace: false);
1004  }
1005  }
1006 
1008  {
1009  alphaClipEffectParams?.Clear();
1010 
1011  wearableTypeHidingSprites.Clear();
1012 
1013  void addWearablesFrom(IReadOnlyList<WearableSprite> wearableSprites)
1014  {
1015  if (wearableSprites.Count <= 0) { return; }
1016 
1017  wearableTypeHidingSprites.AddRange(
1018  wearableSprites.Where(w => w.HideWearablesOfType.Count > 0));
1019  }
1020 
1021  addWearablesFrom(WearingItems);
1022  addWearablesFrom(OtherWearables);
1023 
1024  wearableTypesToHide.Clear();
1025 
1026  if (wearableTypeHidingSprites.Count <= 0) { return; }
1027 
1028  foreach (WearableSprite sprite in wearableTypeHidingSprites)
1029  {
1030  wearableTypesToHide.UnionWith(sprite.HideWearablesOfType);
1031  }
1032  }
1033 
1034  private void UpdateSpriteStates(float deltaTime)
1035  {
1036  foreach (int spriteGroup in DecorativeSpriteGroups.Keys)
1037  {
1038  for (int i = 0; i < DecorativeSpriteGroups[spriteGroup].Count; i++)
1039  {
1040  var decorativeSprite = DecorativeSpriteGroups[spriteGroup][i];
1041  if (decorativeSprite == null) { continue; }
1042  if (spriteGroup > 0)
1043  {
1044  // TODO
1045  //int activeSpriteIndex = ID % DecorativeSpriteGroups[spriteGroup].Count;
1046  //if (i != activeSpriteIndex)
1047  //{
1048  // spriteAnimState[decorativeSprite].IsActive = false;
1049  // continue;
1050  //}
1051  }
1052 
1053  //check if the sprite is active (whether it should be drawn or not)
1054  var spriteState = spriteAnimState[decorativeSprite];
1055  spriteState.IsActive = true;
1056  foreach (PropertyConditional conditional in decorativeSprite.IsActiveConditionals)
1057  {
1058  if (!conditional.Matches(this))
1059  {
1060  spriteState.IsActive = false;
1061  break;
1062  }
1063  }
1064  if (!spriteState.IsActive) { continue; }
1065 
1066  //check if the sprite should be animated
1067  bool animate = true;
1068  foreach (PropertyConditional conditional in decorativeSprite.AnimationConditionals)
1069  {
1070  if (!conditional.Matches(this)) { animate = false; break; }
1071  }
1072  if (!animate) { continue; }
1073  spriteState.OffsetState += deltaTime;
1074  spriteState.RotationState += deltaTime;
1075  }
1076  }
1077  }
1078 
1079  public void DrawDamageModifiers(SpriteBatch spriteBatch, Camera cam, Vector2 startPos, bool isScreenSpace)
1080  {
1081  foreach (var modifier in DamageModifiers)
1082  {
1083  //Vector2 up = VectorExtensions.Backward(-body.TransformedRotation + Params.GetSpriteOrientation() * Dir);
1084  //int width = 4;
1085  //if (!isScreenSpace)
1086  //{
1087  // width = (int)Math.Round(width / cam.Zoom);
1088  //}
1089  //GUI.DrawLine(spriteBatch, startPos, startPos + Vector2.Normalize(up) * size, GUIStyle.Red, width: width);
1090  Color color = modifier.DamageMultiplier > 1 ? GUIStyle.Red : GUIStyle.Green;
1091  float size = ConvertUnits.ToDisplayUnits(body.GetSize().Length() / 2);
1092  if (isScreenSpace)
1093  {
1094  size *= cam.Zoom;
1095  }
1096  int thickness = 2;
1097  if (!isScreenSpace)
1098  {
1099  thickness = (int)Math.Round(thickness / cam.Zoom);
1100  }
1101  float bodyRotation = -body.Rotation;
1102  float constantOffset = -MathHelper.PiOver2;
1103  Vector2 armorSector = modifier.ArmorSectorInRadians;
1104  float armorSectorSize = Math.Abs(armorSector.X - armorSector.Y);
1105  float radians = armorSectorSize * Dir;
1106  float armorSectorOffset = armorSector.X * Dir;
1107  float finalOffset = bodyRotation + constantOffset + armorSectorOffset;
1108  ShapeExtensions.DrawSector(spriteBatch, startPos, size, radians, 40, color, finalOffset, thickness);
1109  }
1110  }
1111 
1112  private (
1113  Color FinalColor,
1114  Vector2 Origin,
1115  float Rotation,
1116  float Scale,
1117  float Depth)
1118  CalculateDrawParameters(WearableSprite wearable, float depthStep, Color color, float alpha)
1119  {
1120  var sprite = ActiveSprite;
1121  if (wearable.InheritSourceRect)
1122  {
1123  if (wearable.SheetIndex.HasValue)
1124  {
1125  wearable.Sprite.SourceRect = new Rectangle(CharacterInfo.CalculateOffset(sprite, wearable.SheetIndex.Value), sprite.SourceRect.Size);
1126  }
1127  else if (type == LimbType.Head && character.Info != null)
1128  {
1129  wearable.Sprite.SourceRect = new Rectangle(CharacterInfo.CalculateOffset(sprite, character.Info.Head.SheetIndex.ToPoint()), sprite.SourceRect.Size);
1130  }
1131  else
1132  {
1133  wearable.Sprite.SourceRect = sprite.SourceRect;
1134  }
1135  }
1136 
1137  Vector2 origin;
1138  if (wearable.InheritOrigin)
1139  {
1140  origin = sprite.Origin;
1141  wearable.Sprite.Origin = origin;
1142  }
1143  else
1144  {
1145  origin = wearable.Sprite.Origin;
1146  // If the wearable inherits the origin, flipping is already handled.
1147  if (body.Dir == -1.0f)
1148  {
1149  origin.X = wearable.Sprite.SourceRect.Width - origin.X;
1150  }
1151  }
1152 
1153  float depth = wearable.Sprite.Depth;
1154 
1155  if (wearable.InheritLimbDepth)
1156  {
1157  depth = sprite.Depth - depthStep;
1158  Limb depthLimb = (wearable.DepthLimb == LimbType.None) ? this : character.AnimController.GetLimb(wearable.DepthLimb);
1159  if (depthLimb != null)
1160  {
1161  depth = depthLimb.ActiveSprite.Depth - depthStep;
1162  }
1163  }
1164  var wearableItemComponent = wearable.WearableComponent;
1165  Color wearableColor = Color.White;
1166  if (wearableItemComponent != null)
1167  {
1168  // Draw outer clothes on top of inner clothes.
1169  if (wearableItemComponent.AllowedSlots.Contains(InvSlotType.OuterClothes))
1170  {
1171  depth -= depthStep;
1172  }
1173  if (wearableItemComponent.AllowedSlots.Contains(InvSlotType.Bag))
1174  {
1175  depth -= depthStep * 4;
1176  }
1177  wearableColor = wearableItemComponent.Item.GetSpriteColor();
1178  }
1179  else if (character.Info != null)
1180  {
1181  if (wearable.Type == WearableType.Hair)
1182  {
1183  wearableColor = character.Info.Head.HairColor;
1184  }
1185  else if (wearable.Type == WearableType.Beard || wearable.Type == WearableType.Moustache)
1186  {
1187  wearableColor = character.Info.Head.FacialHairColor;
1188  }
1189  }
1190  float scale = wearable.Scale;
1191  if (wearable.InheritScale)
1192  {
1193  if (!wearable.IgnoreTextureScale)
1194  {
1195  scale *= TextureScale;
1196  }
1197  if (!wearable.IgnoreLimbScale)
1198  {
1199  scale *= Params.Scale;
1200  }
1201  if (!wearable.IgnoreRagdollScale)
1202  {
1203  scale *= ragdoll.RagdollParams.LimbScale;
1204  }
1205  }
1206  float rotation = -body.DrawRotation - wearable.Rotation * Dir;
1207  float finalAlpha = alpha * wearableColor.A;
1208  Color finalColor = color.Multiply(wearableColor);
1209  finalColor = new Color(finalColor.R, finalColor.G, finalColor.B, (byte)finalAlpha);
1210 
1211  return (finalColor, origin, rotation, scale, depth);
1212  }
1213 
1214  private static Effect alphaClipEffect;
1215  private Dictionary<WearableSprite, Dictionary<string, object>> alphaClipEffectParams;
1216  private void ApplyAlphaClip(SpriteBatch spriteBatch, WearableSprite wearable, WearableSprite alphaClipper, SpriteEffects spriteEffect)
1217  {
1218  SpriteRecorder.Command makeCommand(WearableSprite w)
1219  {
1220  var (_, origin, rotation, scale, _)
1221  = CalculateDrawParameters(w, 0f, Color.White, 0f);
1222 
1223  var command = SpriteRecorder.Command.FromTransform(
1224  texture: w.Sprite.Texture,
1225  pos: new Vector2(body.DrawPosition.X, -body.DrawPosition.Y),
1226  srcRect: w.Sprite.SourceRect,
1227  color: Color.White,
1228  rotationRad: rotation,
1229  origin: origin,
1230  scale: new Vector2(scale, scale),
1231  effects: spriteEffect,
1232  depth: 0f,
1233  index: 0);
1234 
1235  return command;
1236  }
1237 
1238  void spacesFromCommand(WearableSprite w, SpriteRecorder.Command command, out CoordinateSpace2D textureSpace, out CoordinateSpace2D worldSpace)
1239  {
1240  var (topLeft, bottomLeft, topRight) = spriteEffect switch
1241  {
1242  SpriteEffects.None
1243  => (command.VertexTL, command.VertexBL, command.VertexTR),
1244  SpriteEffects.FlipHorizontally | SpriteEffects.FlipVertically
1245  => (command.VertexBR, command.VertexTR, command.VertexBL),
1246  SpriteEffects.FlipHorizontally
1247  => (command.VertexTR, command.VertexBR, command.VertexTL),
1248  SpriteEffects.FlipVertically
1249  => (command.VertexBL, command.VertexTL, command.VertexBR)
1250  };
1251 
1252  textureSpace = new CoordinateSpace2D
1253  {
1254  Origin = topLeft.TextureCoordinate,
1255  I = topRight.TextureCoordinate - topLeft.TextureCoordinate,
1256  J = bottomLeft.TextureCoordinate - topLeft.TextureCoordinate
1257  };
1258 
1259  worldSpace = new CoordinateSpace2D
1260  {
1261  Origin = topLeft.Position.DiscardZ(),
1262  I = topRight.Position.DiscardZ() - topLeft.Position.DiscardZ(),
1263  J = bottomLeft.Position.DiscardZ() - topLeft.Position.DiscardZ()
1264  };
1265  }
1266 
1267  var wearableCommand = makeCommand(wearable);
1268  var clipperCommand = makeCommand(alphaClipper);
1269 
1270  spacesFromCommand(wearable, wearableCommand, out var wearableTextureSpace, out var wearableWorldSpace);
1271  spacesFromCommand(alphaClipper, clipperCommand, out var clipperTextureSpace, out var clipperWorldSpace);
1272 
1273  var wearableUvToClipperUv =
1274  wearableTextureSpace.CanonicalToLocal
1275  * wearableWorldSpace.LocalToCanonical
1276  * clipperWorldSpace.CanonicalToLocal
1277  * clipperTextureSpace.LocalToCanonical;
1278 
1279  alphaClipEffect ??= EffectLoader.Load("Effects/wearableclip");
1280  alphaClipEffectParams ??= new Dictionary<WearableSprite, Dictionary<string, object>>();
1281  if (!alphaClipEffectParams.ContainsKey(wearable)) { alphaClipEffectParams.Add(wearable, new Dictionary<string, object>()); }
1282 
1283  var paramsToPass = new SpriteBatch.EffectWithParams
1284  {
1285  Effect = alphaClipEffect,
1286  Params = alphaClipEffectParams[wearable]
1287  };
1288 
1289  paramsToPass.Params["wearableUvToClipperUv"] = wearableUvToClipperUv;
1290  paramsToPass.Params["stencilUVmin"] = new Vector2(
1291  (float)alphaClipper.Sprite.SourceRect.X / alphaClipper.Sprite.Texture.Width,
1292  (float)alphaClipper.Sprite.SourceRect.Y / alphaClipper.Sprite.Texture.Height);
1293  paramsToPass.Params["stencilUVmax"] = new Vector2(
1294  (float)alphaClipper.Sprite.SourceRect.Right / alphaClipper.Sprite.Texture.Width,
1295  (float)alphaClipper.Sprite.SourceRect.Bottom / alphaClipper.Sprite.Texture.Height);
1296  paramsToPass.Params["clipperTexelSize"] = 2f / alphaClipper.Sprite.Texture.Width;
1297  paramsToPass.Params["aCutoff"] = 2f / 255f;
1298  paramsToPass.Params["xTexture"] = wearable.Sprite.Texture;
1299  paramsToPass.Params["xStencil"] = alphaClipper.Sprite.Texture;
1300  spriteBatch.SwapEffect(paramsToPass);
1301  }
1302 
1303  private void DrawWearable(WearableSprite wearable, float depthStep, SpriteBatch spriteBatch, Color color, float alpha, SpriteEffects spriteEffect)
1304  {
1305  if (wearable.Sprite?.Texture == null) { return; }
1306  var (finalColor, origin, rotation, scale, depth) = CalculateDrawParameters(wearable, depthStep, color, alpha);
1307  var prevEffect = spriteBatch.GetCurrentEffect();
1308  var alphaClipper = WearingItems.Find(w => w.AlphaClipOtherWearables);
1309  bool shouldApplyAlphaClip = alphaClipper?.Sprite?.Texture != null && wearable != alphaClipper;
1310  if (shouldApplyAlphaClip)
1311  {
1312  ApplyAlphaClip(spriteBatch, wearable, alphaClipper, spriteEffect);
1313  }
1314  wearable.Sprite.Draw(spriteBatch, new Vector2(body.DrawPosition.X, -body.DrawPosition.Y), finalColor, origin, rotation, scale, spriteEffect, depth);
1315  if (shouldApplyAlphaClip)
1316  {
1317  spriteBatch.SwapEffect(effect: prevEffect);
1318  }
1319  }
1320 
1321  private WearableSprite GetWearableSprite(WearableType type)
1322  {
1323  var info = character.Info;
1324  if (info == null) { return null; }
1325  ContentXElement element = info.FilterElements(info.Wearables, info.Head.Preset.TagSet, type)?.FirstOrDefault();
1326  return element != null ? new WearableSprite(element.GetChildElement("sprite"), type) : null;
1327  }
1328 
1329  partial void RemoveProjSpecific()
1330  {
1331  Sprite?.Remove();
1332  Sprite = null;
1333 
1334  DamagedSprite?.Remove();
1335  DamagedSprite = null;
1336 
1338  _deformSprite = null;
1339 
1340  DecorativeSprites.ForEach(s => s.Remove());
1341  ConditionalSprites.Clear();
1342 
1343  ConditionalSprites.ForEach(s => s.Remove());
1344  ConditionalSprites.Clear();
1345 
1346  LightSource?.Remove();
1347  LightSource = null;
1348 
1349  OtherWearables.ForEach(w => w.Sprite.Remove());
1350  OtherWearables.Clear();
1351 
1352  HuskSprite?.Sprite?.Remove();
1353  HuskSprite = null;
1354 
1356  HairWithHatSprite = null;
1357 
1359  HerpesSprite = null;
1360 
1361  TintMask?.Remove();
1362  TintMask = null;
1363  }
1364  }
1365 }
AfflictionPrefab is a prefab that defines a type of affliction that can be applied to a character....
static readonly Identifier SpaceHerpesType
float GetAfflictionStrengthByType(Identifier afflictionType, bool allowLimbAfflictions=true)
Identifier GetBaseCharacterSpeciesName()
Stores information about the Character that is needed between rounds in the menu etc....
IEnumerable< ContentXElement > FilterElements(IEnumerable< ContentXElement > elements, ImmutableHashSet< Identifier > tags, WearableType? targetType=null)
static Point CalculateOffset(Sprite sprite, Point offset)
string???????????? Value
Definition: ContentPath.cs:27
IEnumerable< ContentXElement > Elements()
void Deform(Func< Vector2, Vector2 > deformFunction)
Deform the vertices of the sprite using an arbitrary function. The in-parameter of the function is th...
static GameScreen GameScreen
Definition: GameMain.cs:52
static bool DebugDraw
Definition: GameMain.cs:29
DeformableSprite DeformableLightSprite
Definition: LightSource.cs:454
readonly Ragdoll ragdoll
Note that during the limb initialization, character.AnimController returns null, whereas this field i...
Sprite GetActiveSprite(bool excludeConditionalSprites=true)
void DrawDamageModifiers(SpriteBatch spriteBatch, Camera cam, Vector2 startPos, bool isScreenSpace)
static string GetSpritePath(ContentPath texturePath, CharacterInfo characterInfo)
Get the full path of a limb sprite, taking into account tags, gender and head id
readonly List< WearableSprite > WearingItems
void LoadHuskSprite()
void Draw(SpriteBatch spriteBatch, Camera cam, Color? overrideColor=null, bool disableDeformations=false)
Limb(Ragdoll ragdoll, Character character, LimbParams limbParams)
readonly List< WearableSprite > OtherWearables
void LoadHerpesSprite()
float Alpha
Can be used by status effects
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)
static ParticlePrefab FindPrefab(string prefabName)
Vector2 GetLocalFront(float? spritesheetRotation=null)
Returns the farthest point towards the forward of the body. For capsules and circles,...
void Draw(DeformableSprite deformSprite, Camera cam, Vector2 scale, Color color, bool invert=false)
ContentPackage? ContentPackage
Definition: Prefab.cs:37
Limb GetLimb(LimbType limbType, bool excludeSevered=true)
Note that if there are multiple limbs of the same type, only the first (valid) limb is returned.
float SourceRectScale
Multiplies both the position and the size of the source rects. Used for scaling the textures when we ...
Does a rotational deformations around pivot points at the edges of the sprite.
static SpriteDeformation Load(string deformationType, string parentDebugName)
abstract void GetDeformation(out Vector2[,] deformation, out float multiplier, bool inverse)
void Draw(ISpriteBatch spriteBatch, Vector2 pos, float rotate=0.0f, float scale=1.0f, SpriteEffects spriteEffect=SpriteEffects.None)
ContentXElement SourceElement
Reference to the xml element from where the sprite was created. Can be null if the sprite was not def...
ImmutableHashSet< Identifier > CanBeHiddenByItem
Tags or identifiers of items that can hide this one.
bool InheritScale
Does the wearable inherit all the scalings of the wearer? Also the wearable's own scale is used!