Client LuaCsForBarotrauma
MentalStateManager.cs
2 using System;
3 using System.Linq;
4 
5 namespace Barotrauma
6 {
7  partial class MentalStateManager
8  {
9  private float mentalStateTimer;
10  private const float MentalStateInterval = 7.5f;
11 
12  private float mentalBehaviorTimer;
13  private const float MentalBehaviorInterval = 7.5f;
14 
15  private readonly Character character;
16  private readonly HumanAIController humanAIController;
17 
18  public bool Active { get; set; }
19  public MentalType CurrentMentalType { get; private set; }
20  public enum MentalType
21  {
22  Normal,
23  Confused, // No effects other than special dialogue
24  Afraid, // Will retreat from whoever is nearby
25  Desperate, // Will defensively attack/arrest whoever is nearby
26  Berserk // turns fully hostile using team change logic
27  }
28 
29  private const string MentalTeamChange = "mental";
30 
31  public MentalStateManager(Character character, HumanAIController humanAIController)
32  {
33  this.character = character;
34  this.humanAIController = humanAIController;
35  }
36 
37  public void Update(float deltaTime)
38  {
39  if (!Active) { return; }
40  mentalStateTimer -= deltaTime;
41  if (mentalStateTimer <= 0.0f)
42  {
43  UpdateMentalState();
44  mentalStateTimer = MentalStateInterval * Rand.Range(0.75f, 1.25f);
45  }
46 
47  mentalBehaviorTimer = Math.Max(0f, mentalBehaviorTimer - deltaTime);
48  }
49 
50  private void UpdateMentalState()
51  {
52  MentalType newMentalType = GetMentalType(character.CharacterHealth.GetAffliction("psychosis"));
53  bool createdCombat = false;
54 
55  switch (newMentalType)
56  {
57  case MentalType.Normal:
58  case MentalType.Confused:
59  // remove combat if we became normal again
60  mentalBehaviorTimer = 0f;
61  break;
62  case MentalType.Afraid:
63  case MentalType.Desperate:
64  case MentalType.Berserk:
65  // berserk is not removed unless we drop to normal behavior again
66  if (CurrentMentalType == MentalType.Berserk)
67  {
68  newMentalType = MentalType.Berserk;
69  }
70  // give players a full interval to react to mental changes
71  if (newMentalType == CurrentMentalType)
72  {
73  createdCombat = CreateCombatBehavior(CurrentMentalType);
74  }
75  break;
76  }
77 
78  if (!createdCombat)
79  {
80  CreateDialogueBehavior(newMentalType);
81  }
82 
83  if (newMentalType != MentalType.Berserk)
84  {
85  character.TryRemoveTeamChange(MentalTeamChange);
86  }
87 
88  CurrentMentalType = newMentalType;
89  }
90 
91  private int mentalTypeCount;
92  private int MentalTypeCount
93  {
94  get
95  {
96  if (mentalTypeCount == 0)
97  {
98  mentalTypeCount = Enum.GetNames(typeof(MentalType)).Length;
99  }
100  return mentalTypeCount;
101  }
102  }
103 
104  private MentalType GetMentalType(Affliction affliction)
105  {
106  if (affliction == null)
107  {
108  return MentalType.Normal;
109  }
110  int psychosisIndex = (int)(affliction.Strength / (affliction.Prefab.MaxStrength / MentalTypeCount) * Rand.Range(1f, 1.2f));
111  psychosisIndex = Math.Clamp(psychosisIndex, 0, 4);
112  MentalType mentalType = psychosisIndex switch
113  {
114  0 => MentalType.Normal,
115  1 => MentalType.Confused,
116  2 => MentalType.Afraid,
117  3 => MentalType.Desperate,
118  4 => MentalType.Berserk,
119  _ => throw new ArgumentOutOfRangeException(psychosisIndex.ToString()),
120  };
121  return mentalType;
122  }
123 
124  public bool CreateCombatBehavior(MentalType mentalType)
125  {
126  Character mentalAttackTarget = Character.CharacterList.Where(
127  possibleTarget => HumanAIController.IsActive(possibleTarget) &&
128  (possibleTarget.TeamID != character.TeamID || mentalType == MentalType.Berserk) &&
129  humanAIController.VisibleHulls.Contains(possibleTarget.CurrentHull) &&
130  possibleTarget != character).GetRandomUnsynced();
131 
132  if (mentalAttackTarget == null)
133  {
134  return false;
135  }
136 
137  var combatMode = AIObjectiveCombat.CombatMode.None;
138  bool holdFire = mentalType == MentalType.Afraid && character.IsSecurity;
139  switch (mentalType)
140  {
141  case MentalType.Afraid:
142  combatMode = character.IsSecurity ? AIObjectiveCombat.CombatMode.Arrest : AIObjectiveCombat.CombatMode.Retreat;
143  break;
144  case MentalType.Desperate:
145  // might be unnecessary to explicitly declare as arrest against non-humans
146  combatMode = character.IsSecurity && mentalAttackTarget.IsHuman ? AIObjectiveCombat.CombatMode.Arrest : AIObjectiveCombat.CombatMode.Defensive;
147  break;
148  case MentalType.Berserk:
149  combatMode = AIObjectiveCombat.CombatMode.Offensive;
150  break;
151  }
152 
153  // using this as an explicit time-out for the behavior. it's possible it will never run out because of the manager being disabled, but combat objective has failsafes for that
154  mentalBehaviorTimer = MentalBehaviorInterval;
155  humanAIController.AddCombatObjective(combatMode, mentalAttackTarget, allowHoldFire: holdFire, abortCondition: obj => mentalBehaviorTimer <= 0f);
156  Identifier textIdentifier = $"dialogmentalstatereaction{combatMode}".ToIdentifier();
157  character.Speak(TextManager.Get(textIdentifier).Value, delay: Rand.Range(0.5f, 1.0f), identifier: textIdentifier, minDurationBetweenSimilar: 25f);
158 
159  if (mentalType == MentalType.Berserk && !character.HasTeamChange(MentalTeamChange))
160  {
161  // TODO: could this be handled in the switch block above?
162  character.TryAddNewTeamChange(MentalTeamChange, new ActiveTeamChange(CharacterTeamType.None, ActiveTeamChange.TeamChangePriorities.Absolute, aggressiveBehavior: true));
163  }
164 
165  return true;
166  }
167 
168  public void CreateDialogueBehavior(MentalType mentalType)
169  {
170  if (mentalType == MentalType.Normal) { return; }
171  Identifier textIdentifier = $"dialogmentalstate{mentalType}".ToIdentifier();
172  character.Speak(TextManager.Get(textIdentifier).Value, delay: Rand.Range(0.5f, 1.0f), identifier: textIdentifier, minDurationBetweenSimilar: 35f);
173  }
174  }
175 }
static bool IsActive(Character c)
bool CreateCombatBehavior(MentalType mentalType)
MentalStateManager(Character character, HumanAIController humanAIController)
void CreateDialogueBehavior(MentalType mentalType)
void Update(float deltaTime)