1
0

DamagedSiliconAccentSystem.cs 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. using System.Text;
  2. using Content.Server.PowerCell;
  3. using Content.Shared.Speech.Components;
  4. using Content.Shared.Damage;
  5. using Content.Shared.FixedPoint;
  6. using Robust.Shared.Random;
  7. namespace Content.Server.Speech.EntitySystems;
  8. public sealed class DamagedSiliconAccentSystem : EntitySystem
  9. {
  10. [Dependency] private readonly IRobustRandom _random = default!;
  11. [Dependency] private readonly PowerCellSystem _powerCell = default!;
  12. public override void Initialize()
  13. {
  14. base.Initialize();
  15. SubscribeLocalEvent<DamagedSiliconAccentComponent, AccentGetEvent>(OnAccent, after: [typeof(ReplacementAccentSystem)]);
  16. }
  17. private void OnAccent(Entity<DamagedSiliconAccentComponent> ent, ref AccentGetEvent args)
  18. {
  19. var uid = ent.Owner;
  20. if (ent.Comp.EnableChargeCorruption)
  21. {
  22. var currentChargeLevel = 0.0f;
  23. if (ent.Comp.OverrideChargeLevel.HasValue)
  24. {
  25. currentChargeLevel = ent.Comp.OverrideChargeLevel.Value;
  26. }
  27. else if (_powerCell.TryGetBatteryFromSlot(uid, out var battery))
  28. {
  29. currentChargeLevel = battery.CurrentCharge / battery.MaxCharge;
  30. }
  31. currentChargeLevel = Math.Clamp(currentChargeLevel, 0.0f, 1.0f);
  32. // Corrupt due to low power (drops characters on longer messages)
  33. args.Message = CorruptPower(args.Message, currentChargeLevel, ref ent.Comp);
  34. }
  35. if (ent.Comp.EnableDamageCorruption)
  36. {
  37. var damage = FixedPoint2.Zero;
  38. if (ent.Comp.OverrideTotalDamage.HasValue)
  39. {
  40. damage = ent.Comp.OverrideTotalDamage.Value;
  41. }
  42. else if (TryComp<DamageableComponent>(uid, out var damageable))
  43. {
  44. damage = damageable.TotalDamage;
  45. }
  46. // Corrupt due to damage (drop, repeat, replace with symbols)
  47. args.Message = CorruptDamage(args.Message, damage, ref ent.Comp);
  48. }
  49. }
  50. public string CorruptPower(string message, float chargeLevel, ref DamagedSiliconAccentComponent comp)
  51. {
  52. // The first idxMin characters are SAFE
  53. var idxMin = comp.StartPowerCorruptionAtCharIdx;
  54. // Probability will max at idxMax
  55. var idxMax = comp.MaxPowerCorruptionAtCharIdx;
  56. // Fast bails, would not have an effect
  57. if (chargeLevel > comp.ChargeThresholdForPowerCorruption || message.Length < idxMin)
  58. {
  59. return message;
  60. }
  61. var outMsg = new StringBuilder();
  62. var maxDropProb = comp.MaxDropProbFromPower * (1.0f - chargeLevel / comp.ChargeThresholdForPowerCorruption);
  63. var idx = -1;
  64. foreach (var letter in message)
  65. {
  66. idx++;
  67. if (idx < idxMin) // Fast character, no effect
  68. {
  69. outMsg.Append(letter);
  70. continue;
  71. }
  72. // use an x^2 interpolation to increase the drop probability until we hit idxMax
  73. var probToDrop = idx >= idxMax
  74. ? maxDropProb
  75. : (float)Math.Pow(((double)idx - idxMin) / (idxMax - idxMin), 2.0) * maxDropProb;
  76. // Ensure we're in the range for Prob()
  77. probToDrop = Math.Clamp(probToDrop, 0.0f, 1.0f);
  78. if (_random.Prob(probToDrop)) // Lose a character
  79. {
  80. // Additional chance to change to dot for flavor instead of full drop
  81. if (_random.Prob(comp.ProbToCorruptDotFromPower))
  82. {
  83. outMsg.Append('.');
  84. }
  85. }
  86. else // Character is safe
  87. {
  88. outMsg.Append(letter);
  89. }
  90. }
  91. return outMsg.ToString();
  92. }
  93. private string CorruptDamage(string message, FixedPoint2 totalDamage, ref DamagedSiliconAccentComponent comp)
  94. {
  95. var outMsg = new StringBuilder();
  96. // Linear interpolation of character damage probability
  97. var damagePercent = Math.Clamp((float)totalDamage / (float)comp.DamageAtMaxCorruption, 0, 1);
  98. var chanceToCorruptLetter = damagePercent * comp.MaxDamageCorruption;
  99. foreach (var letter in message)
  100. {
  101. if (_random.Prob(chanceToCorruptLetter)) // Corrupt!
  102. {
  103. outMsg.Append(CorruptLetterDamage(letter));
  104. }
  105. else // Safe!
  106. {
  107. outMsg.Append(letter);
  108. }
  109. }
  110. return outMsg.ToString();
  111. }
  112. private string CorruptLetterDamage(char letter)
  113. {
  114. var res = _random.NextDouble();
  115. return res switch
  116. {
  117. < 0.0 => letter.ToString(), // shouldn't be less than 0!
  118. < 0.5 => CorruptPunctuize(), // 50% chance to replace with random punctuation
  119. < 0.75 => "", // 25% chance to remove character
  120. < 1.00 => CorruptRepeat(letter), // 25% to repeat the character
  121. _ => letter.ToString(), // shouldn't be greater than 1!
  122. };
  123. }
  124. private string CorruptPunctuize()
  125. {
  126. const string punctuation = "\"\\`~!@#$%^&*()_+-={}[]|\\;:<>,.?/";
  127. return punctuation[_random.NextByte((byte)punctuation.Length)].ToString();
  128. }
  129. private string CorruptRepeat(char letter)
  130. {
  131. // 25% chance to add another character in the streak
  132. // (kind of like "exploding dice")
  133. // Solved numerically in closed form for streaks of bernoulli variables with p = 0.25
  134. // Can calculate for different p using python function:
  135. /*
  136. * def prob(streak, p):
  137. * if streak == 0:
  138. * return scipy.stats.binom(streak+1, p).pmf(streak)
  139. * return prob(streak-1) * p
  140. * def prob_cum(streak, p=.25):
  141. * return np.sum([prob(i, p) for i in range(streak+1)])
  142. */
  143. var numRepeats = _random.NextDouble() switch
  144. {
  145. < 0.75000000 => 2,
  146. < 0.93750000 => 3,
  147. < 0.98437500 => 4,
  148. < 0.99609375 => 5,
  149. < 0.99902344 => 6,
  150. < 0.99975586 => 7,
  151. < 0.99993896 => 8,
  152. < 0.99998474 => 9,
  153. _ => 10,
  154. };
  155. return new string(letter, numRepeats);
  156. }
  157. }