Client LuaCsForBarotrauma
BarotraumaShared/SharedSource/Items/Components/Repairable.cs
3 using Microsoft.Xna.Framework;
4 using System;
5 using System.Collections.Generic;
6 using System.Globalization;
7 using System.Linq;
8 
10 {
15  {
16  public float CurrentStress { get; }
17  }
18 
20  {
21  private readonly LocalizedString header;
22 
23  private float deteriorationTimer;
25  {
26  get;
27  private set;
28  }
29 
30  private int updateDeteriorationCounter;
31  private const int UpdateDeteriorationInterval = 10;
32 
33  private int prevSentConditionValue;
34  private string conditionSignal;
35 
36  bool wasBroken;
37  bool wasGoodCondition;
38 
39  public float LastActiveTime;
40 
41  [Serialize(0.0f, IsPropertySaveable.Yes, description: "How fast the condition of the item deteriorates per second."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f, DecimalCount = 2)]
42  public float DeteriorationSpeed
43  {
44  get;
45  set;
46  }
47 
48  [Serialize(0.0f, IsPropertySaveable.Yes, description: "Minimum initial delay before the item starts to deteriorate."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f, DecimalCount = 2)]
49  public float MinDeteriorationDelay
50  {
51  get;
52  set;
53  }
54 
55  [Serialize(0.0f, IsPropertySaveable.Yes, description: "Maximum initial delay before the item starts to deteriorate."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f, DecimalCount = 2)]
56  public float MaxDeteriorationDelay
57  {
58  get;
59  set;
60  }
61 
62  [Serialize(50.0f, IsPropertySaveable.Yes, description: "The item won't deteriorate spontaneously if the condition is below this value. For example, if set to 10, the condition will spontaneously drop to 10 and then stop dropping (unless the item is damaged further by external factors). Percentages of max condition."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f)]
64  {
65  get;
66  set;
67  }
68 
69  [Serialize(0f, IsPropertySaveable.Yes, description: "How low a traitor must get the item's condition for it to start breaking down.")]
70  public float MinSabotageCondition
71  {
72  get;
73  set;
74  }
75 
76  [Serialize(60f, IsPropertySaveable.Yes, description: "How long will the item spontaneously deteriorate after being sabotaged.")]
78  {
79  get;
80  set;
81  }
82 
83  [Serialize(80.0f, IsPropertySaveable.Yes, description: "The condition of the item has to be below this for it to become repairable. Percentages of max condition."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f)]
84  public float RepairThreshold
85  {
86  get;
87  set;
88  }
89 
90  [Serialize(1.0f, IsPropertySaveable.Yes, description: "How much faster the device can deteriorate when under stress (e.g. when operating at full speed/power)."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f, DecimalCount = 2)]
92  {
93  get;
94  set;
95  }
96 
97  [Serialize(0.5f, IsPropertySaveable.Yes, description: "At what speed/power must the device be operating at to be considered \"under stress\"."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f, DecimalCount = 2)]
99  {
100  get;
101  set;
102  }
103 
104  [Serialize(0.1f, IsPropertySaveable.Yes, description: "How fast the deterioration speed increases when under stress."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f, DecimalCount = 2)]
106  {
107  get;
108  set;
109  }
110 
111  [Serialize(0.1f, IsPropertySaveable.Yes, description: "How fast the deterioration speed decreases when not under stress."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f, DecimalCount = 2)]
113  {
114  get;
115  set;
116  }
117 
118  [Serialize(100.0f, IsPropertySaveable.Yes, description: "The amount of time it takes to fix the item with insufficient skill levels."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f)]
119  public float FixDurationLowSkill
120  {
121  get;
122  set;
123  }
124 
125  [Serialize(10.0f, IsPropertySaveable.Yes, description: "The amount of time it takes to fix the item with sufficient skill levels."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f)]
126  public float FixDurationHighSkill
127  {
128  get;
129  set;
130  }
131 
132  private float skillRequirementMultiplier;
133 
134  [Serialize(1.0f, IsPropertySaveable.Yes)]
136  {
137  get { return skillRequirementMultiplier; }
138  set
139  {
140  var oldValue = skillRequirementMultiplier;
141  skillRequirementMultiplier = value;
142 #if CLIENT
143  if (!MathUtils.NearlyEqual(oldValue, skillRequirementMultiplier))
144  {
145  RecreateGUI();
146  }
147 #endif
148  }
149  }
150 
151  private bool isTinkering;
152  public bool IsTinkering
153  {
154  get { return isTinkering; }
155  private set
156  {
157  if (isTinkering == value) { return; }
158  isTinkering = value;
159 
160  if (tinkeringPowersDevices)
161  {
162  foreach (Powered powered in item.GetComponents<Powered>())
163  {
164  if (powered is PowerContainer) { continue; }
165  powered.PoweredByTinkering = isTinkering;
166  }
167  }
168  }
169  }
170 
171  public Character CurrentFixer { get; private set; }
172  private Item currentRepairItem;
173 
174  private float tinkeringDuration;
175  private float tinkeringStrength;
176 
177  public float StressDeteriorationMultiplier { get; private set; } = 1.0f;
178 
179  public float TinkeringStrength => tinkeringStrength;
180 
181  private bool tinkeringPowersDevices;
182  public bool TinkeringPowersDevices => tinkeringPowersDevices;
183 
185 
187 
188  public enum FixActions : int
189  {
190  None = 0,
191  Repair = 1,
192  Sabotage = 2,
193  Tinker = 3,
194  }
195 
196  private FixActions currentFixerAction = FixActions.None;
198  {
199  get => currentFixerAction;
200  private set { currentFixerAction = value; }
201  }
202 
204  : base(item, element)
205  {
206  IsActive = true;
207  canBeSelected = true;
208 
209  this.item = item;
210  header =
211  TextManager.Get(element.GetAttributeString("header", "")).Fallback(
212  TextManager.Get(item.Prefab.ConfigElement.GetAttributeString("header", ""))).Fallback(
213  element.GetAttributeString("name", ""));
214 
215  //backwards compatibility
216  var repairThresholdAttribute =
217  element.Attributes().FirstOrDefault(a => a.Name.ToString().Equals("showrepairuithreshold", StringComparison.OrdinalIgnoreCase)) ??
218  element.Attributes().FirstOrDefault(a => a.Name.ToString().Equals("airepairthreshold", StringComparison.OrdinalIgnoreCase));
219  if (repairThresholdAttribute != null)
220  {
221  if (float.TryParse(repairThresholdAttribute.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out float repairThreshold))
222  {
223  RepairThreshold = repairThreshold;
224  }
225  }
226 
227  // Modify damage (not stun) caused by repair failure based on campaign settings
228  if (GameMain.GameSession?.Campaign is CampaignMode campaign
229  && statusEffectLists != null
230  && statusEffectLists.TryGetValue(ActionType.OnFailure, out var onFailureEffects))
231  {
232  foreach (var effect in onFailureEffects)
233  {
234  foreach (Affliction affliction in effect.Afflictions)
235  {
236  if (affliction.Prefab.AfflictionType == Tags.Stun) { continue; }
237  affliction.Strength *= campaign.Settings.RepairFailMultiplier;
238  }
239  }
240  }
241 
242  InitProjSpecific(element);
243  }
244 
245  public override void OnItemLoaded()
246  {
247  deteriorationTimer = Rand.Range(MinDeteriorationDelay, MaxDeteriorationDelay);
248  }
249 
250  partial void InitProjSpecific(ContentXElement element);
251 
255  public bool CheckCharacterSuccess(Character character, Item bestRepairItem)
256  {
257  if (character == null) { return false; }
258 
259  if (statusEffectLists == null) { return true; }
260 
261  if (bestRepairItem != null && bestRepairItem.Prefab.CannotRepairFail) { return true; }
262 
263  // unpowered (electrical) items can be repaired without a risk of electrical shock
264  if (RequiredSkills.Any(s => s != null && s.Identifier == "electrical"))
265  {
266  if (item.GetComponent<Reactor>() is Reactor reactor)
267  {
268  if (MathUtils.NearlyEqual(reactor.CurrPowerConsumption, 0.0f, 0.1f)) { return true; }
269  }
270  else if (item.GetComponent<Powered>() is Powered powered && powered.Voltage < 0.1f)
271  {
272  return true;
273  }
274  }
275 
276  bool success = Rand.Range(0.0f, 0.5f) < RepairDegreeOfSuccess(character, RequiredSkills);
277  ActionType actionType = success ? ActionType.OnSuccess : ActionType.OnFailure;
278 
279  ApplyStatusEffectsAndCreateEntityEvent(this, actionType, character);
280  ApplyStatusEffectsAndCreateEntityEvent(this, ActionType.OnUse, character);
281  if (bestRepairItem != null && bestRepairItem.GetComponent<Holdable>() is Holdable holdable)
282  {
283  ApplyStatusEffectsAndCreateEntityEvent(holdable, actionType, character);
284  ApplyStatusEffectsAndCreateEntityEvent(holdable, ActionType.OnUse, character);
285  }
286  static void ApplyStatusEffectsAndCreateEntityEvent(ItemComponent ic, ActionType actionType, Character character)
287  {
288  ic.ApplyStatusEffects(actionType, 1.0f, character);
289  if (GameMain.NetworkMember is { IsServer: true } && ic.statusEffectLists != null && ic.statusEffectLists.ContainsKey(actionType))
290  {
291  GameMain.NetworkMember.CreateEntityEvent(ic.Item, new Item.ApplyStatusEffectEventData(actionType, ic, character));
292  }
293  }
294  return success;
295  }
296 
297  public override float GetSkillMultiplier()
298  {
300  }
301 
302  public float RepairDegreeOfSuccess(Character character, List<Skill> skills)
303  {
304  if (skills.Count == 0) { return 1.0f; }
305  if (character == null) { return 0.0f; }
306 
307  float skillSum = (from t in skills let characterLevel = character.GetSkillLevel(t.Identifier) select (characterLevel - (t.Level * SkillRequirementMultiplier))).Sum();
308  float average = skillSum / skills.Count;
309 
310  return ((average + 100.0f) / 2.0f) / 100.0f;
311  }
312 
313  public void RepairBoost(bool qteSuccess)
314  {
315  if (CurrentFixer == null) { return; }
316  if (qteSuccess)
317  {
318  item.Condition += RepairDegreeOfSuccess(CurrentFixer, RequiredSkills) * 3 * (currentFixerAction == FixActions.Repair ? 1.0f : -1.0f);
319  }
320  else if (Rand.Range(0.0f, 2.0f) > RepairDegreeOfSuccess(CurrentFixer, RequiredSkills))
321  {
322  ApplyStatusEffects(ActionType.OnFailure, 1.0f, CurrentFixer);
323 #if SERVER
324  GameMain.Server?.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(ActionType.OnFailure, this, CurrentFixer));
325 #endif
326  }
327  }
328 
329  public bool StartRepairing(Character character, FixActions action)
330  {
331  if (character == null || character.IsDead || action == FixActions.None)
332  {
333  DebugConsole.ThrowError("Invalid repair command!");
334  return false;
335  }
336  else
337  {
338  if (CurrentFixerAction == FixActions.Tinker && action != FixActions.Tinker)
339  {
340  CurrentFixer?.CheckTalents(AbilityEffectType.OnStopTinkering);
341  }
342 
343  Item bestRepairItem = GetBestRepairItem(character);
344 #if SERVER
345  if (CurrentFixer != character || currentFixerAction != action)
346  {
347  if (!CheckCharacterSuccess(character, bestRepairItem))
348  {
349  GameServer.Log($"{GameServer.CharacterLogName(character)} failed to {(action == FixActions.Sabotage ? "sabotage" : "repair")} {item.Name}", ServerLog.MessageType.ItemInteraction);
350  return false;
351  }
352 
353  if ((character != prevLoggedFixer || action != prevLoggedFixAction) && (character.TeamID == CharacterTeamType.Team1 || character.TeamID == CharacterTeamType.Team2))
354  {
355  GameServer.Log($"{GameServer.CharacterLogName(character)} started {(action == FixActions.Sabotage ? "sabotaging" : "repairing")} {item.Name}", ServerLog.MessageType.ItemInteraction);
356  item.CreateServerEvent(this);
357  prevLoggedFixer = character;
358  prevLoggedFixAction = action;
359  }
360  }
361 #else
362  if (GameMain.Client == null && (CurrentFixer != character || currentFixerAction != action) && !CheckCharacterSuccess(character, bestRepairItem)) { return false; }
363 #endif
364  CurrentFixer = character;
365  currentRepairItem = bestRepairItem;
366  CurrentFixerAction = action;
367  if (action == FixActions.Tinker)
368  {
369  tinkeringStrength = 1f + CurrentFixer.GetStatValue(StatTypes.TinkeringStrength);
370  tinkeringPowersDevices = CurrentFixer.HasAbilityFlag(AbilityFlags.TinkeringPowersDevices);
371 
372  if (character.HasAbilityFlag(AbilityFlags.CanTinkerFabricatorsAndDeconstructors) && item.GetComponent<Deconstructor>() != null || item.GetComponent<Fabricator>() != null)
373  {
374  // fabricators and deconstructors can be tinkered indefinitely (more or less)
375  tinkeringDuration = float.MaxValue;
376  }
377  else
378  {
379  tinkeringDuration = CurrentFixer.GetStatValue(StatTypes.TinkeringDuration);
380  }
381  }
382  return true;
383 
384  static Item GetBestRepairItem(Character character)
385  {
386  return character.HeldItems.OrderByDescending(i => i.Prefab.AddedRepairSpeedMultiplier).FirstOrDefault();
387  }
388 
389  }
390  }
391 
392  public bool StopRepairing(Character character)
393  {
394  if (CurrentFixer == character)
395  {
396 #if SERVER
397  if (CurrentFixer != character || currentFixerAction != FixActions.None)
398  {
399  item.CreateServerEvent(this);
400  }
401 #endif
402  if (currentRepairItem != null)
403  {
404  foreach (var ic in currentRepairItem.GetComponents<ItemComponent>())
405  {
406  ic.ApplyStatusEffects(ActionType.OnSuccess, 1.0f, character);
407  }
408  }
409  if (CurrentFixerAction == FixActions.Tinker)
410  {
412  }
414  CurrentFixer = null;
415  currentRepairItem = null;
416  currentFixerAction = FixActions.None;
417 #if CLIENT
418  qteTimer = QteDuration;
419  qteCooldown = 0.0f;
420  repairSoundChannel?.FadeOutAndDispose();
421  repairSoundChannel = null;
422 #endif
423  return true;
424  }
425  else
426  {
427  return false;
428  }
429  }
430 
431  public override void UpdateBroken(float deltaTime, Camera cam)
432  {
433  Update(deltaTime, cam);
434  }
435 
436  public void ResetDeterioration()
437  {
438  deteriorationTimer = Rand.Range(MinDeteriorationDelay, MaxDeteriorationDelay);
440 #if SERVER
441  //let the clients know the deterioration delay
442  item.CreateServerEvent(this);
443 #endif
444  }
445 
446  public override void Update(float deltaTime, Camera cam)
447  {
448  UpdateProjSpecific(deltaTime);
449  IsTinkering = false;
450 
451  if (prevSentConditionValue != (int)item.ConditionPercentage || conditionSignal == null)
452  {
453  prevSentConditionValue = (int)item.ConditionPercentage;
454  conditionSignal = prevSentConditionValue.ToString();
455  }
456 
457  item.SendSignal(conditionSignal, "condition_out");
458 
459  foreach (var component in item.Components)
460  {
461  if (component is IDeteriorateUnderStress deteriorateUnderStress)
462  {
463  if (deteriorateUnderStress.CurrentStress >= StressDeteriorationThreshold)
464  {
466  }
467  else
468  {
470  }
471  }
472  }
473 
474  if (ForceDeteriorationTimer > 0.0f)
475  {
476  ForceDeteriorationTimer -= deltaTime;
477  if (ForceDeteriorationTimer <= 0.0f)
478  {
479 #if SERVER
480  //let the clients know the deterioration delay
481  item.CreateServerEvent(this);
482 #endif
483  }
484  }
485  if (CurrentFixer == null)
486  {
487  updateDeteriorationCounter++;
488  if (updateDeteriorationCounter >= UpdateDeteriorationInterval)
489  {
490  UpdateDeterioration(deltaTime * UpdateDeteriorationInterval);
491  updateDeteriorationCounter = 0;
492  }
493  return;
494  }
495 
496  UpdateFixAnimation(CurrentFixer);
497 
498  if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
499 
501  {
503  return;
504  }
505 
506  if (currentFixerAction == FixActions.Tinker)
507  {
508  tinkeringDuration -= deltaTime;
509  // not great to interject it here, should be less reliant on returning
510 
511  float conditionDecrease = deltaTime * (CurrentFixer.GetStatValue(StatTypes.TinkeringDamage) / item.Prefab.Health) * 100f;
512  item.Condition -= conditionDecrease;
513 
514  if (!CanTinker(CurrentFixer) || tinkeringDuration <= 0f)
515  {
517  }
518  else
519  {
520  IsTinkering = true;
521  }
522  return;
523  }
524 
525  float successFactor = RequiredSkills.Count == 0 ? 1.0f : RepairDegreeOfSuccess(CurrentFixer, RequiredSkills);
526 
527  //item must have been below the repair threshold for the player to get an achievement or XP for repairing it
529  {
530  wasBroken = true;
531  }
533  {
534  wasGoodCondition = true;
535  }
536 
537  float talentMultiplier = CurrentFixer.GetStatValue(StatTypes.RepairSpeed);
538  foreach (Skill skill in RequiredSkills)
539  {
540  if (skill.Identifier == "mechanical")
541  {
542  talentMultiplier += CurrentFixer.GetStatValue(StatTypes.MechanicalRepairSpeed);
543  }
544  else if (skill.Identifier == "electrical")
545  {
546  talentMultiplier += CurrentFixer.GetStatValue(StatTypes.ElectricalRepairSpeed);
547  }
548  }
549 
550  float fixDuration = MathHelper.Lerp(FixDurationLowSkill, FixDurationHighSkill, successFactor);
551  fixDuration /= 1 + talentMultiplier + currentRepairItem?.Prefab.AddedRepairSpeedMultiplier ?? 0f;
552  fixDuration /= 1 + item.GetQualityModifier(Quality.StatType.RepairSpeed);
553 
554  item.MaxRepairConditionMultiplier = GetMaxRepairConditionMultiplier(CurrentFixer);
555 
556  if (currentFixerAction == FixActions.Repair)
557  {
558  if (fixDuration <= 0.0f)
559  {
561  }
562  else
563  {
564  // scale with prefab's health instead of real health to ensure repair speed remains static with upgrades
565  float conditionIncrease = deltaTime / (fixDuration / item.Prefab.Health);
566  item.Condition += conditionIncrease;
567 #if SERVER
568  GameMain.Server.KarmaManager.OnItemRepaired(CurrentFixer, this, conditionIncrease);
569 #endif
570  }
571 
572  if (item.IsFullCondition)
573  {
574  if (wasBroken)
575  {
576  foreach (Skill skill in RequiredSkills)
577  {
579  }
580  AchievementManager.OnItemRepaired(item, CurrentFixer);
581  CurrentFixer.CheckTalents(AbilityEffectType.OnRepairComplete, new AbilityRepairable(item));
582  }
583  if (CurrentFixer?.SelectedItem == item) { CurrentFixer.SelectedItem = null; }
584  deteriorationTimer = Rand.Range(MinDeteriorationDelay, MaxDeteriorationDelay);
585  wasBroken = false;
587 #if SERVER
588  prevLoggedFixer = null;
589  prevLoggedFixAction = FixActions.None;
590 #endif
591  }
592  }
593  else if (currentFixerAction == FixActions.Sabotage)
594  {
595  if (fixDuration <= 0.0f)
596  {
598  }
599  else
600  {
601  // scale with prefab's health instead of real health to ensure sabotage speed remains static with (any) upgrades
602  float conditionDecrease = deltaTime / (fixDuration / item.Prefab.Health);
603  item.Condition -= conditionDecrease;
604  }
605 
607  {
608  if (wasGoodCondition)
609  {
610  foreach (Skill skill in RequiredSkills)
611  {
612  float characterSkillLevel = CurrentFixer.GetSkillLevel(skill.Identifier);
614  SkillSettings.Current.SkillIncreasePerSabotage / Math.Max(characterSkillLevel, 1.0f));
615  }
616 
617  deteriorationTimer = 0.0f;
620  wasGoodCondition = false;
621  }
623  }
624  }
625  else
626  {
627  throw new NotImplementedException(currentFixerAction.ToString());
628  }
629  }
630 
631  private void UpdateDeterioration(float deltaTime)
632  {
633  if (item.Condition <= 0.0f) { return; }
634  if (!ShouldDeteriorate()) { return; }
635 
636  //forced deterioration doesn't tick down the timer for spontaneous deterioration
637  if (deteriorationTimer > 0.0f && ForceDeteriorationTimer <= 0.0f)
638  {
639  if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
640  {
641  deteriorationTimer -= deltaTime * GetDeteriorationDelayMultiplier();
642 #if SERVER
643  if (deteriorationTimer <= 0.0f) { item.CreateServerEvent(this); }
644 #endif
645  }
646  return;
647  }
648 
650  {
651  float deteriorationSpeed = item.StatManager.GetAdjustedValueMultiplicative(ItemTalentStats.DetoriationSpeed, DeteriorationSpeed);
652  if (ForceDeteriorationTimer > 0.0f) { deteriorationSpeed = Math.Max(deteriorationSpeed, 1.0f); }
653  item.Condition -= deteriorationSpeed * StressDeteriorationMultiplier * deltaTime;
654  }
655  }
656 
657  private float GetMaxRepairConditionMultiplier(Character character)
658  {
659  if (character == null) { return 1.0f; }
660  // kind of rough to keep this in update, but seems most robust
661  if (RequiredSkills.Any(s => s != null && s.Identifier == "mechanical"))
662  {
663  return 1 + character.GetStatValue(StatTypes.MaxRepairConditionMultiplierMechanical);
664  }
665  if (RequiredSkills.Any(s => s != null && s.Identifier == "electrical"))
666  {
667  return 1 + character.GetStatValue(StatTypes.MaxRepairConditionMultiplierElectrical);
668  }
669  return 1.0f;
670  }
671 
672  private bool IsTinkerable(Character character)
673  {
674  if (!character.HasAbilityFlag(AbilityFlags.CanTinker)) { return false; }
675  if (item.GetComponent<Engine>() != null) { return true; }
676  if (item.GetComponent<Pump>() != null) { return true; }
677  if (item.HasTag(Tags.TurretAmmoSource)) { return true; }
678  if (!character.HasAbilityFlag(AbilityFlags.CanTinkerFabricatorsAndDeconstructors)) { return false; }
679  if (item.GetComponent<Fabricator>() != null) { return true; }
680  if (item.GetComponent<Deconstructor>() != null) { return true; }
681  return false;
682  }
683 
684  private Affliction GetTinkerExhaustion(Character character)
685  {
686  return character.CharacterHealth.GetAffliction("tinkerexhaustion");
687  }
688 
689  private bool CanTinker(Character character)
690  {
691  if (!IsTinkerable(character)) { return false; }
692  if (GetTinkerExhaustion(character) is Affliction tinkerExhaustion && tinkerExhaustion.Strength <= tinkerExhaustion.Prefab.MaxStrength) { return false; }
693  return true;
694  }
695 
696  partial void UpdateProjSpecific(float deltaTime);
697 
698  public void AdjustPowerConsumption(ref float powerConsumption)
699  {
701  {
702  powerConsumption *= MathHelper.Lerp(1.5f, 1.0f, item.Condition / item.MaxCondition);
703  }
704  }
705 
706  private bool ShouldDeteriorate()
707  {
708  if (ForceDeteriorationTimer > 0.0f) { return true; }
709 
710  if (Level.IsLoadedFriendlyOutpost) { return false; }
711 #if CLIENT
712  if (GameMain.GameSession?.GameMode is TutorialMode) { return false; }
713 #endif
714 
715  if (LastActiveTime > Timing.TotalTime) { return true; }
716  foreach (ItemComponent ic in item.Components)
717  {
718  if (ic is Fabricator || ic is Deconstructor)
719  {
720  //fabricators and deconstructors rely on LastActiveTime
721  return false;
722  }
723  else if (ic is PowerTransfer pt)
724  {
725  //power transfer items (junction boxes, relays) don't deteriorate if they're no carrying any power
726  if (pt.Voltage > 0.1f) { return true; }
727  }
728  else if (ic is PowerContainer pc)
729  {
730  //batteries don't deteriorate if they're not charging/discharging
731  if (Math.Abs(pc.CurrPowerConsumption) > 0.1f || Math.Abs(pc.CurrPowerOutput) > 0.1f) { return true; }
732  }
733  else if (ic is Engine engine)
734  {
735  //engines don't deteriorate if they're not running
736  if (Math.Abs(engine.Force) > 1.0f) { return true; }
737  }
738  else if (ic is Pump pump)
739  {
740  //pumps don't deteriorate if they're not running
741  if (Math.Abs(pump.FlowPercentage) > 1.0f && pump.IsActive && pump.HasPower) { return true; }
742  }
743  else if (ic is Reactor reactor)
744  {
745  //reactors don't deteriorate if they're not powered up
746  if (reactor.Temperature > 0.1f) { return true; }
747  }
748  else if (ic is OxygenGenerator oxyGenerator)
749  {
750  //oxygen generators don't deteriorate if they're not running
751  if (oxyGenerator.CurrFlow > 0.1f) { return true; }
752  }
753  else if (ic is Powered powered && powered is not LightComponent)
754  {
755  if (powered.Voltage >= powered.MinVoltage) { return true; }
756  }
757  }
758 
759  return false;
760  }
761 
762  private float GetDeteriorationDelayMultiplier()
763  {
764  foreach (ItemComponent ic in item.Components)
765  {
766  if (ic is Engine engine)
767  {
768  return Math.Abs(engine.Force) / 100.0f;
769  }
770  else if (ic is Pump pump)
771  {
772  return Math.Abs(pump.FlowPercentage) / 100.0f;
773  }
774  else if (ic is Reactor reactor)
775  {
776  return (reactor.FissionRate + reactor.TurbineOutput) / 200.0f;
777  }
778  }
779  return 1.0f;
780  }
781 
782  private void UpdateFixAnimation(Character character)
783  {
784  if (character == null || character.IsDead || character.IsIncapacitated) { return; }
785  character.AnimController.UpdateUseItem(false, item.WorldPosition + new Vector2(0.0f, 100.0f) * ((item.Condition / item.MaxCondition) % 0.1f));
786  }
787 
788  public override void ReceiveSignal(Signal signal, Connection connection)
789  {
790  //do nothing
791  //Repairables should always stay active, so we don't want to use the default behavior
792  //where set_active/set_state signals can disable the component
793  }
794  }
795 
796  internal sealed class AbilityRepairable : AbilityObject, IAbilityItem
797  {
798  public Item Item { get; set; }
799 
800  public AbilityRepairable(Item item)
801  {
802  Item = item;
803  }
804  }
805 }
virtual float Strength
Definition: Affliction.cs:31
readonly AfflictionPrefab Prefab
Definition: Affliction.cs:12
readonly Identifier AfflictionType
Arbitrary string that is used to identify the type of the affliction.
float GetSkillLevel(string skillIdentifier)
void CheckTalents(AbilityEffectType abilityEffectType, AbilityObject abilityObject)
bool CanInteractWith(Character c, float maxDist=200.0f, bool checkVisibility=true, bool skipDistanceCheck=false)
Item????????? SelectedItem
The primary selected item. It can be any device that character interacts with. This excludes items li...
float GetStatValue(StatTypes statType, bool includeSaved=true)
IEnumerable< Item >?? HeldItems
Items the character has in their hand slots. Doesn't return nulls and only returns items held in both...
void ApplySkillGain(Identifier skillIdentifier, float baseGain, bool gainedFromAbility=false, float maxGain=2f)
Increases the characters skill at a rate proportional to their current skill. If you want to increase...
void IncreaseSkillLevel(Identifier skillIdentifier, float increase, bool gainedFromAbility=false)
Increase the skill by a specific amount. Talents may affect the actual, final skill increase.
string? GetAttributeString(string key, string? def)
IEnumerable< XAttribute > Attributes()
static GameSession?? GameSession
Definition: GameMain.cs:88
static NetworkMember NetworkMember
Definition: GameMain.cs:190
static GameClient Client
Definition: GameMain.cs:188
void ApplyStatusEffects(ActionType type, float deltaTime, Character character=null, Limb limb=null, Entity useTarget=null, bool isNetworkEvent=false, Vector2? worldPosition=null)
Executes all StatusEffects of the specified type. Note that condition checks are ignored here: that s...
float ConditionPercentageRelativeToDefaultMaxCondition
Condition percentage disregarding MaxRepairConditionMultiplier (i.e. this can go above 100% if the it...
void SendSignal(string signal, string connectionName)
float GetQualityModifier(Quality.StatType statType)
The base class for components holding the different functionalities of the item
void ApplyStatusEffects(ActionType type, float deltaTime, Character character=null, Limb targetLimb=null, Entity useTarget=null, Character user=null, Vector2? worldPosition=null, float afflictionMultiplier=1.0f)
readonly Dictionary< ActionType, List< StatusEffect > > statusEffectLists
bool CheckCharacterSuccess(Character character, Item bestRepairItem)
Check if the character manages to succesfully repair the item
override void OnItemLoaded()
Called when all the components of the item have been loaded. Use to initialize connections between co...
static bool IsLoadedFriendlyOutpost
Is there a loaded level set, and is it a friendly outpost (FriendlyNPC or Team1). Does not take reput...
readonly Identifier Identifier
Definition: Skill.cs:7
static SkillSettings Current
Interface for entities that the clients can send events to the server
Interface for entities that the server can send events to the clients
AbilityFlags
AbilityFlags are a set of toggleable flags that can be applied to characters.
Definition: Enums.cs:615
ActionType
ActionTypes define when a StatusEffect is executed.
Definition: Enums.cs:19
AbilityEffectType
Definition: Enums.cs:125
StatTypes
StatTypes are used to alter several traits of a character. They are mostly used by talents.
Definition: Enums.cs:180