weaponfsm.c 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776
  1. /**@class WeaponFSM
  2. * @brief weapon finite state machine
  3. **/
  4. class WeaponFSM extends HFSMBase<WeaponStateBase, WeaponEventBase, WeaponActionBase, WeaponGuardBase>
  5. {
  6. private static const int MAX_SYNCHRONIZE_ATTEMPTS = 12;
  7. private static const int MIN_SYNCHRONIZE_INTERVAL = 3000; // ms
  8. private static const int RESET_SYNCHRONIZE_THRESHOLD = 3600000; // ms
  9. private int m_SynchronizeAttempts;
  10. private int m_LastSynchronizeTime;
  11. protected int m_NextStateId = 0; /// counter for InternalID: each state in a fsm is assigned an unique number
  12. protected ref array<WeaponStateBase> m_UniqueStates = new array<WeaponStateBase>; /// unique list of states in this machine (automation of save/load)
  13. protected void SetInternalID(WeaponStateBase state)
  14. {
  15. if (state && state.GetInternalStateID() == -1)
  16. {
  17. state.SetInternalStateID(m_NextStateId);
  18. //if (LogManager.IsWeaponLogEnable()) { wpnDebugSpam("[wpnfsm] " + Object.GetDebugName(m_weapon) + " unique state=" + state + " has id=" + m_NextStateId); }
  19. m_UniqueStates.Insert(state);
  20. ++m_NextStateId;
  21. }
  22. }
  23. /**@fn AddTransition
  24. * @brief adds transition into transition table
  25. * As a side effect registers the state(s) into m_UniqueStates
  26. **/
  27. override void AddTransition(FSMTransition<WeaponStateBase, WeaponEventBase, WeaponActionBase, WeaponGuardBase> t)
  28. {
  29. super.AddTransition(t);
  30. SetInternalID(t.m_srcState);
  31. SetInternalID(t.m_dstState);
  32. }
  33. override protected ProcessEventResult ProcessLocalTransition(FSMTransition<WeaponStateBase, WeaponEventBase, WeaponActionBase, WeaponGuardBase> t, WeaponEventBase e)
  34. {
  35. if (LogManager.IsInventoryHFSMLogEnable()) fsmDebugPrint("[hfsm] (local) state=" + t.m_srcState.ToString() + "-------- event=" + e.ToString() + "[G=" + t.m_guard.ToString() +"]/A=" + t.m_action.ToString() + " --------|> dst=" + t.m_dstState.ToString());
  36. if (t.m_action)
  37. t.m_action.Action(e); // 2) execute transition action (if any)
  38. m_State = t.m_dstState; // 3) change state to new
  39. if (t.m_dstState != NULL)
  40. {
  41. m_State.OnEntry(e); // 4a) call onEntry on new state
  42. if (GetOwnerState())
  43. GetOwnerState().OnSubMachineChanged(t.m_srcState, t.m_dstState); // 5a) notify owner state about change in submachine
  44. if (m_State)
  45. m_State.OnStateChanged(t.m_srcState, t.m_dstState); // 5b) notify current state about change in machine
  46. ValidateAndRepair();
  47. return ProcessEventResult.FSM_OK;
  48. }
  49. else
  50. {
  51. if (LogManager.IsInventoryHFSMLogEnable()) fsmDebugPrint("[hfsm] terminating fsm: state=" + t.m_srcState.ToString() + " event=" + e.ToString());
  52. if (GetOwnerState())
  53. GetOwnerState().OnSubMachineChanged(t.m_srcState, NULL); // 5) notify owner state about change in submachine
  54. ValidateAndRepair();
  55. return ProcessEventResult.FSM_TERMINATED; // 4b) or terminate
  56. }
  57. }
  58. override protected ProcessEventResult ProcessAbortTransition(FSMTransition<WeaponStateBase, WeaponEventBase, WeaponActionBase, WeaponGuardBase> t, WeaponEventBase e)
  59. {
  60. if (LogManager.IsInventoryHFSMLogEnable()) fsmDebugPrint("[hfsm] (local abort) state=" + t.m_srcState.ToString() + "-------- ABORT event=" + e.ToString() + "[G=" + t.m_guard.ToString() +"]/A=" + t.m_action.ToString() + " --------|> dst=" + t.m_dstState.ToString());
  61. if (t.m_action)
  62. t.m_action.Action(e); // 2) execute transition action (if any)
  63. auto tmp = t.m_srcState.GetParentState();
  64. if (tmp == t.m_dstState.GetParentState())
  65. {
  66. m_State = t.m_dstState; // 3) change state to new (or NULL)
  67. if (t.m_dstState != NULL)
  68. {
  69. m_State.OnEntry(e); // 4a1) call onEntry on new state (see 4a2) )
  70. ValidateAndRepair();
  71. return ProcessEventResult.FSM_OK;
  72. }
  73. else
  74. {
  75. if (LogManager.IsInventoryHFSMLogEnable()) fsmDebugPrint("[hfsm] abort & terminating fsm: state=" + t.m_srcState.ToString() + " event=" + e.ToString());
  76. return ProcessEventResult.FSM_TERMINATED; // 4b) or terminate
  77. }
  78. }
  79. else
  80. {
  81. m_State = NULL;
  82. ValidateAndRepair();
  83. return ProcessEventResult.FSM_ABORTED; // 4c) or signal abort to parent (with appropriate transition)
  84. }
  85. }
  86. override WeaponStateBase ProcessAbortEvent (WeaponEventBase e, out ProcessEventResult result)
  87. {
  88. if (LogManager.IsInventoryHFSMLogEnable())
  89. {
  90. if (GetOwnerState())
  91. fsmDebugPrint("[hfsm] SUB! " + GetOwnerState().Type().ToString() + "::ProcessAbortEvent(" + e.Type().ToString() + ")");
  92. else
  93. fsmDebugPrint("[hfsm] root::ProcessAbortEvent(" + e.Type().ToString() + ")");
  94. }
  95. // 1) look in submachine first (if any)
  96. if (m_State && m_State.HasFSM())
  97. {
  98. HFSMBase<WeaponStateBase, WeaponEventBase, WeaponActionBase, WeaponGuardBase> a = m_State.GetFSM();
  99. ProcessEventResult subfsm_res;
  100. WeaponStateBase abort_dst = a.ProcessAbortEvent(e, subfsm_res);
  101. switch (subfsm_res)
  102. {
  103. case ProcessEventResult.FSM_OK:
  104. {
  105. if (LogManager.IsInventoryHFSMLogEnable()) fsmDebugPrint("[hfsm] event processed by sub machine=" + m_State.ToString());
  106. result = subfsm_res; // 1.1) submachine accepted event
  107. ValidateAndRepair();
  108. return NULL;
  109. }
  110. case ProcessEventResult.FSM_ABORTED:
  111. {
  112. if (LogManager.IsInventoryHFSMLogEnable()) fsmDebugPrint("[hfsm] aborted sub machine=" + m_State.ToString());
  113. m_State.OnAbort(e); // 1.2) submachine aborted, abort submachine owner (i.e. this)
  114. if (GetOwnerState() == abort_dst.GetParentState())
  115. {
  116. if (LogManager.IsInventoryHFSMLogEnable()) fsmDebugPrint("[hfsm] aborted sub machine=" + m_State.ToString() + " & abort destination reached.");
  117. m_State = abort_dst;
  118. m_State.OnEntry(e); // 1.3) submachine aborted, call onEntry on new state (cross-hierarchy transition)
  119. result = ProcessEventResult.FSM_OK;
  120. ValidateAndRepair();
  121. return NULL;
  122. }
  123. else
  124. {
  125. result = ProcessEventResult.FSM_ABORTED; // 1.4) submachine has aborted, look for destination state in parent
  126. ValidateAndRepair();
  127. return NULL;
  128. }
  129. break;
  130. }
  131. case ProcessEventResult.FSM_TERMINATED:
  132. {
  133. break; // submachine has finished, look for local transitions from exited submachine
  134. }
  135. case ProcessEventResult.FSM_NO_TRANSITION:
  136. {
  137. if (LogManager.IsInventoryHFSMLogEnable()) fsmDebugPrint("[hfsm] aborted (but no transition) sub machine=" + m_State.ToString());
  138. break; // submachine has no transition, look for transitions in local machine
  139. }
  140. }
  141. }
  142. // 2) local transitions
  143. int i = FindFirstUnguardedTransition(e);
  144. if (i == -1)
  145. {
  146. if (LogManager.IsInventoryHFSMLogEnable()) fsmDebugPrint("[hfsm] abort event has no transition: src=" + m_State.ToString() + " e=" + e.Type().ToString());
  147. result = ProcessEventResult.FSM_NO_TRANSITION;
  148. ValidateAndRepair();
  149. return NULL;
  150. }
  151. m_State.OnAbort(e);
  152. i = FindFirstUnguardedTransition(e);
  153. if (i == -1)
  154. {
  155. if (LogManager.IsInventoryHFSMLogEnable()) fsmDebugPrint("[hfsm] abort event has no transition: src=" + m_State.ToString() + " e=" + e.Type().ToString());
  156. result = ProcessEventResult.FSM_NO_TRANSITION;
  157. ValidateAndRepair();
  158. return NULL;
  159. }
  160. FSMTransition<WeaponStateBase, WeaponEventBase, WeaponActionBase, WeaponGuardBase> t = m_Transitions.Get(i);
  161. ProcessEventResult res = ProcessAbortTransition(t, e);
  162. result = res;
  163. switch (res)
  164. {
  165. case ProcessEventResult.FSM_OK:
  166. {
  167. //if (LogManager.IsWeaponLogEnable()) fsmDebugSpam("[hfsm] abort event processed by machine=" + m_State.ToString());
  168. ValidateAndRepair();
  169. return NULL; // machine accepted event
  170. }
  171. case ProcessEventResult.FSM_ABORTED:
  172. {
  173. if (LogManager.IsInventoryHFSMLogEnable()) fsmDebugPrint("[hfsm] aborted sub machine=" + m_State.ToString() + " will fall-through to dst=" + t.m_dstState);
  174. ValidateAndRepair();
  175. return t.m_dstState; // store destination state for parent(s)
  176. }
  177. case ProcessEventResult.FSM_TERMINATED:
  178. {
  179. if (LogManager.IsInventoryHFSMLogEnable()) fsmDebugPrint("[hfsm] aborted & terminated sub machine=" + m_State.ToString());
  180. break; // submachine has finished, look for local transitions from exited submachine
  181. }
  182. case ProcessEventResult.FSM_NO_TRANSITION:
  183. {
  184. break; // submachine has no transition, look for transitions in local machine
  185. }
  186. }
  187. return NULL;
  188. }
  189. override ProcessEventResult ProcessEvent (WeaponEventBase e)
  190. {
  191. if (LogManager.IsInventoryHFSMLogEnable())
  192. {
  193. if (GetOwnerState())
  194. fsmDebugPrint("[hfsm] SUB!::" + GetOwnerState().Type().ToString() + "::ProcessEvent(" + e.Type().ToString() + ")");
  195. else
  196. fsmDebugPrint("[hfsm] root::ProcessEvent(" + e.Type().ToString() + " =" + e.DumpToString());
  197. }
  198. // 1) completion transitions have priority (if any)
  199. if (m_HasCompletions)
  200. ProcessCompletionTransitions();
  201. // 2) submachine then (if any)
  202. if (m_State && m_State.HasFSM())
  203. {
  204. ProcessEventResult subfsm_res = m_State.ProcessEvent(e);
  205. switch (subfsm_res)
  206. {
  207. case ProcessEventResult.FSM_OK:
  208. {
  209. if (LogManager.IsWeaponLogEnable()) fsmDebugSpam("[hfsm] event processed by sub machine=" + m_State.ToString());
  210. return subfsm_res; // submachine accepted event
  211. }
  212. case ProcessEventResult.FSM_TERMINATED:
  213. {
  214. break; // submachine has finished, look for local transitions from exited submachine
  215. }
  216. case ProcessEventResult.FSM_NO_TRANSITION:
  217. {
  218. break; // submachine has no transition, look for transitions in local machine
  219. }
  220. }
  221. }
  222. // 3) local transitions
  223. int i = FindFirstUnguardedTransition(e);
  224. if (i == -1)
  225. {
  226. if (LogManager.IsInventoryHFSMLogEnable()) fsmDebugPrint("[hfsm] event has no transition: src=" + m_State.ToString() + " e=" + e.Type().ToString());
  227. return ProcessEventResult.FSM_NO_TRANSITION;
  228. }
  229. m_State.OnExit(e);
  230. // 3.5) find correct transition after handled exit
  231. i = FindFirstUnguardedTransition(e);
  232. if (i == -1)
  233. {
  234. if (LogManager.IsInventoryHFSMLogEnable()) fsmDebugPrint("[hfsm] event has no transition: src=" + m_State.ToString() + " e=" + e.Type().ToString());
  235. return ProcessEventResult.FSM_NO_TRANSITION;
  236. }
  237. FSMTransition<WeaponStateBase, WeaponEventBase, WeaponActionBase, WeaponGuardBase> row = m_Transitions.Get(i);
  238. ProcessEventResult res;
  239. if (row.m_dstState != NULL)
  240. {
  241. // this is regular transition
  242. if (row.m_srcState.GetParentState() == row.m_dstState.GetParentState())
  243. res = LocalTransition(i, e); // transition is within this state machine
  244. else
  245. Error("cross-hierarchy transition or misconfigured transition detected!");
  246. //res = HierarchicTransition(i, e); // transition has to cross hierarchy
  247. }
  248. else
  249. {
  250. // this is terminating transition
  251. if (row.m_srcState.GetParentState() == GetOwnerState())
  252. res = LocalTransition(i, e); // terminating transition is within this state machine
  253. else
  254. Error("cross-hierarchy transition or misconfigured transition detected!");
  255. //res = HierarchicTransition(i, e); // source node crosses hierarchy (terminate lies always within this machine)
  256. }
  257. return res;
  258. }
  259. /**@fn FindStateForInternalID
  260. * @brief retrieve base state that matches given internal id
  261. * @param[in] id the id stored in database during save
  262. **/
  263. WeaponStateBase FindStateForInternalID(int id)
  264. {
  265. int state_count = m_UniqueStates.Count();
  266. for (int idx = 0; idx < state_count; ++idx)
  267. {
  268. int state_id = m_UniqueStates.Get(idx).GetInternalStateID();
  269. if (state_id == id)
  270. return m_UniqueStates.Get(idx);
  271. }
  272. return null;
  273. }
  274. /**@fn FindStableStateForID
  275. * @brief load from database - reverse lookup for state from saved id
  276. * @param[in] id the id stored in database during save
  277. **/
  278. WeaponStableState FindStableStateForID(int id)
  279. {
  280. if (id == 0)
  281. return null;
  282. int count = m_Transitions.Count();
  283. for (int i = 0; i < count; ++i)
  284. {
  285. WeaponTransition trans = m_Transitions.Get(i);
  286. WeaponStableState state = WeaponStableState.Cast(trans.m_srcState);
  287. if (state && id == state.GetCurrentStateID())
  288. return state;
  289. }
  290. return null;
  291. }
  292. protected bool LoadAndSetCurrentFSMState(ParamsReadContext ctx, int version)
  293. {
  294. int curr_state_id = -1;
  295. if (!ctx.Read(curr_state_id))
  296. {
  297. Error("[wpnfsm] LoadCurrentFSMState - cannot read current state");
  298. return false;
  299. }
  300. WeaponStateBase state = FindStateForInternalID(curr_state_id);
  301. if (state)
  302. {
  303. Terminate();
  304. if (LogManager.IsWeaponLogEnable()) { wpnDebugPrint("[wpnfsm] synced current state=" + state + " id=" + curr_state_id); }
  305. m_State = state;
  306. Start(null, true);
  307. return true;
  308. }
  309. else
  310. Error("[wpnfsm] LoadCurrentFSMState - cannot find state for id=" + curr_state_id);
  311. return false;
  312. }
  313. /**@fn LoadCurrentFSMState
  314. * @brief load current state of fsm
  315. **/
  316. bool LoadCurrentFSMState(ParamsReadContext ctx, int version)
  317. {
  318. if (LoadAndSetCurrentFSMState(ctx, version))
  319. {
  320. bool res = m_State.LoadCurrentFSMState(ctx, version);
  321. if (LogManager.IsWeaponLogEnable()) { wpnDebugSpam("[wpnfsm] LoadCurrentFSMState - loaded current state=" + GetCurrentState()); }
  322. return res;
  323. }
  324. return false;
  325. }
  326. bool LoadCurrentUnstableFSMState(ParamsWriteContext ctx, int version)
  327. {
  328. if (LoadAndSetCurrentFSMState(ctx, version))
  329. {
  330. // read all substates
  331. int state_count = m_UniqueStates.Count();
  332. for (int idx = 0; idx < state_count; ++idx)
  333. {
  334. if (LogManager.IsWeaponLogEnable()) { wpnDebugSpam("[wpnfsm] LoadCurrentUnstableFSMState " + idx + "/" + state_count + " id=" + m_UniqueStates.Get(idx).GetInternalStateID() + " state=" + m_UniqueStates.Get(idx)); }
  335. if (!m_UniqueStates.Get(idx).LoadCurrentFSMState(ctx, version))
  336. Error("[wpnfsm] LoadCurrentUnstableFSMState - cannot load unique state " + idx + "/" + state_count + " with id=" + m_UniqueStates.Get(idx).GetInternalStateID() + " state=" + m_UniqueStates.Get(idx));
  337. }
  338. return true;
  339. }
  340. return false;
  341. }
  342. /**@fn SaveCurrentFSMState
  343. * @brief save current state of fsm
  344. **/
  345. bool SaveCurrentFSMState(ParamsWriteContext ctx)
  346. {
  347. WeaponStateBase state = GetCurrentState();
  348. int curr_state_id = state.GetInternalStateID();
  349. if (LogManager.IsWeaponLogEnable()) { wpnDebugPrint("[wpnfsm] SaveCurrentFSMState - saving current state=" + GetCurrentState() + " id=" + curr_state_id); }
  350. if (!ctx.Write(curr_state_id))
  351. {
  352. Error("[wpnfsm] SaveCurrentFSMState - cannot save curr_state_id=" + curr_state_id);
  353. return false;
  354. }
  355. // write only current state
  356. if (!state.SaveCurrentFSMState(ctx))
  357. {
  358. Error("[wpnfsm] SaveCurrentFSMState - cannot save currrent state=" +state);
  359. return false;
  360. }
  361. return true;
  362. }
  363. bool SaveCurrentUnstableFSMState(ParamsWriteContext ctx)
  364. {
  365. WeaponStateBase state = GetCurrentState();
  366. int curr_state_id = state.GetInternalStateID();
  367. if (LogManager.IsWeaponLogEnable()) { wpnDebugPrint("[wpnfsm] SaveCurrentUnstableFSMState - saving current state=" + GetCurrentState() + " id=" + curr_state_id); }
  368. if (!ctx.Write(curr_state_id))
  369. {
  370. Error("[wpnfsm] SaveCurrentFSMState - cannot save curr_state_id=" + curr_state_id);
  371. return false;
  372. }
  373. // write all substates
  374. int state_count = m_UniqueStates.Count();
  375. for (int idx = 0; idx < state_count; ++idx)
  376. {
  377. int state_id = m_UniqueStates.Get(idx).GetInternalStateID();
  378. if (state_id != -1)
  379. {
  380. if (LogManager.IsWeaponLogEnable()) { wpnDebugSpam("[wpnfsm] SaveCurrentUnstableFSMState " + idx + "/" + state_count + " id=" + state_id + " name=" + m_UniqueStates.Get(idx)); }
  381. if (!m_UniqueStates.Get(idx).SaveCurrentFSMState(ctx))
  382. Error("SaveCurrentUnstableFSMState - cannot save unique state=" + m_UniqueStates.Get(idx) + " idx=" + idx + "/" + state_count + " with id=" + state_id);
  383. }
  384. else
  385. Error("[wpnfsm] SaveCurrentUnstableFSMState state=" + m_UniqueStates.Get(idx) + " with unassigned ID!");
  386. }
  387. return true;
  388. }
  389. /**@fn ValidateAndRepair
  390. * @brief validate the state of the gun and repair if mismatch
  391. **/
  392. void ValidateAndRepair()
  393. {
  394. Internal_ValidateAndRepair();
  395. }
  396. /**@fn Internal_ValidateAndRepair
  397. * @brief validate the state of the gun and repair if mismatch
  398. * @return bool whether it performed repairing or not
  399. **/
  400. protected bool Internal_ValidateAndRepair()
  401. {
  402. bool repaired = false;
  403. // Only repair stable states
  404. WeaponStableState state = WeaponStableState.Cast(m_State);
  405. if (state && state.IsRepairEnabled())
  406. {
  407. Weapon_Base weapon = state.m_weapon;
  408. if (weapon)
  409. {
  410. repaired |= ValidateAndRepairHelper(weapon,
  411. "MagazineRepair",
  412. state.HasMagazine(), ( weapon.GetMagazine(0) != null ),
  413. new WeaponEventAttachMagazine, new WeaponEventDetachMagazine,
  414. state);
  415. repaired |= ValidateAndRepairHelper(weapon,
  416. "JammedRepair",
  417. state.IsJammed(), weapon.IsJammed(),
  418. new WeaponEventTriggerToJam, new WeaponEventUnjam,
  419. state);
  420. if (weapon.IsJammed())
  421. return repaired;
  422. int nMuzzles = weapon.GetMuzzleCount();
  423. switch (nMuzzles)
  424. {
  425. case 1:
  426. {
  427. repaired |= ValidateAndRepairHelper(weapon,
  428. "ChamberFiredRepair",
  429. state.IsChamberFiredOut(0), weapon.IsChamberFiredOut(0),
  430. new WeaponEventTrigger, new WeaponEventMechanism,
  431. state);
  432. repaired |= ValidateAndRepairHelper(weapon,
  433. "ChamberRepair",
  434. state.IsChamberFull(0), weapon.IsChamberFullEx(0),
  435. new WeaponEventLoad1Bullet, new WeaponEventMechanism,
  436. state);
  437. break;
  438. }
  439. default:
  440. {
  441. for (int i = 0; i < nMuzzles; ++i)
  442. {
  443. repaired |= ValidateAndRepairHelper(weapon,
  444. "ChamberFiredRepair",
  445. state.IsChamberFiredOut(i), weapon.IsChamberFiredOut(i),
  446. null, null, // A bit brute forced, not really any clean way to transition
  447. state);
  448. repaired |= ValidateAndRepairHelper(weapon,
  449. "ChamberRepair",
  450. state.IsChamberFull(i), weapon.IsChamberFull(i),
  451. null, null, // A bit brute forced, not really any clean way to transition
  452. state);
  453. }
  454. break;
  455. }
  456. }
  457. }
  458. }
  459. return repaired;
  460. }
  461. protected bool ValidateAndRepairHelper(Weapon_Base weapon, string name, bool stateCondition, bool gunCondition, WeaponEventBase e1, WeaponEventBase e2, out WeaponStableState state)
  462. {
  463. if (LogManager.IsWeaponLogEnable()) { wpnDebugPrint("[wpnfsm] " + weapon.GetDebugName(weapon) + " ValidateAndRepair - " + name + " - " + m_State + " - state: " + stateCondition + " & weapon: " + gunCondition); }
  464. if (stateCondition != gunCondition)
  465. {
  466. WeaponStableState repairedState;
  467. // Seeing this message is not TOO bad, it just means this system is working
  468. // It is simply being listed in the logs to identify how much the FSM state and weapon state still desyncs
  469. // Which can be because of a myriad of causes, such as incorrectly set up transitions
  470. // Or simply certain timings of certain actions or interrupts lined up perfectly, which can have repro rates such as 1/300
  471. Error(string.Format("[wpnfsm] ValidateAndRepair Attempting to repair: %1 - %2 - %3 - state: %4 != weapon: %5",
  472. weapon.GetDebugName(weapon), name, m_State, stateCondition, gunCondition));
  473. if (e1 && e2)
  474. repairedState = ValidateAndRepairStateFinder(gunCondition, e1, e2, state);
  475. if (repairedState)
  476. {
  477. Terminate();
  478. m_State = repairedState;
  479. Start(null, true);
  480. state = repairedState;
  481. weapon.SyncSelectionState(state.HasBullet(), state.HasMagazine());
  482. repairedState.SyncAnimState();
  483. if (LogManager.IsWeaponLogEnable()) { wpnDebugPrint("[wpnfsm] " + weapon.GetDebugName(weapon) + " ValidateAndRepair - " + name + " - Result - " + m_State); }
  484. return true;
  485. }
  486. else
  487. {
  488. // Last ditch effort
  489. if (m_SynchronizeAttempts < MAX_SYNCHRONIZE_ATTEMPTS)
  490. {
  491. int currentTime = g_Game.GetTime();
  492. int timeDiff = currentTime - m_LastSynchronizeTime;
  493. // Careful with calling synchronize
  494. if (timeDiff > MIN_SYNCHRONIZE_INTERVAL)
  495. {
  496. // If a lot of time passed since last attempt
  497. // There is a possibility the weapon was fixed for a period
  498. if (timeDiff > RESET_SYNCHRONIZE_THRESHOLD)
  499. m_SynchronizeAttempts = 0;
  500. // Only call this on server or in SP
  501. // Synchronize will ask the server for its FSM state anyways
  502. if (g_Game.IsServer())
  503. weapon.RandomizeFSMState();
  504. weapon.Synchronize();
  505. ++m_SynchronizeAttempts;
  506. m_LastSynchronizeTime = currentTime;
  507. }
  508. }
  509. else
  510. {
  511. OnFailThresholdBreached(weapon, name, stateCondition, gunCondition);
  512. }
  513. }
  514. }
  515. return false;
  516. }
  517. protected void OnFailThresholdBreached(Weapon weapon, string name, bool stateCondition, bool gunCondition)
  518. {
  519. // Now seeing THIS one, after the one above, THIS one CAN be bad
  520. // As the state was identified as being desynced with the actual weapon state
  521. // But the system was unable to fix it, so the weapon is now working improperly or not at all
  522. // There is even the possibility that this weapon is now permanently broken
  523. Error(string.Format("[wpnfsm] %1 ValidateAndRepair THRESHOLD BREACH - %2 - %3 - state: %4 != weapon: %5",
  524. weapon.GetDebugName(weapon), name, m_State, stateCondition, gunCondition));
  525. // At this point might even consider just deleting the weapon :c
  526. }
  527. protected WeaponStableState ValidateAndRepairStateFinder(bool condition, WeaponEventBase e1, WeaponEventBase e2, WeaponStableState state)
  528. {
  529. WeaponStateBase interState;
  530. if (condition)
  531. interState = FindTransitionState(state, e1);
  532. else
  533. interState = FindTransitionState(state, e2);
  534. WeaponEventBase e = new WeaponEventHumanCommandActionFinished;
  535. return WeaponStableState.Cast(FindGuardedTransitionState(interState, e));
  536. }
  537. /**@fn OnStoreLoad
  538. * @brief load state of fsm
  539. **/
  540. bool OnStoreLoad(ParamsReadContext ctx, int version)
  541. {
  542. int id = 0;
  543. ctx.Read(id);
  544. WeaponStableState state = FindStableStateForID(id);
  545. if (state)
  546. {
  547. Terminate();
  548. if (LogManager.IsWeaponLogEnable()) { wpnDebugPrint("[wpnfsm] OnStoreLoad - loading current state=" + state + " id=" + id); }
  549. m_State = state;
  550. Start(null, true);
  551. }
  552. else
  553. {
  554. Print("[wpnfsm] Warning! OnStoreLoad - cannot load curent weapon state, id=" + id);
  555. }
  556. return true;
  557. }
  558. /**@fn GetCurrentStateID
  559. * @brief tries to return identifier of current stable state
  560. *
  561. * if the weapon is in stable state, i.e. not reloading, firing, detaching, ... than
  562. * the identifier of the stable state is used directly.
  563. * otherwise if the weapon is in unstable state, than the function uses abort information
  564. * to determine the closest stable state that is coherent with weapon's state.
  565. *
  566. * @return integer id that will be stored to database
  567. **/
  568. int GetCurrentStableStateID()
  569. {
  570. // 1) if current state is stable state then return ID directly
  571. WeaponStableState state = WeaponStableState.Cast(GetCurrentState());
  572. if (state)
  573. return state.GetCurrentStateID();
  574. // 2) otherwise find closest stable state (by looking on abort event)
  575. WeaponStateBase abort_dst = FindAbortDestinationState(new WeaponEventHumanCommandActionAborted(null));
  576. WeaponStableState closest_stable_state = WeaponStableState.Cast(abort_dst);
  577. if (closest_stable_state)
  578. {
  579. Print("[wpnfsm] Save occured in fsm transition! current state=" + GetCurrentState() + " closes stable state=" + closest_stable_state + " id=" + closest_stable_state.GetCurrentStateID());
  580. return closest_stable_state.GetCurrentStateID();
  581. }
  582. Print("[wpnfsm] Warning! Save occured in fsm transition! GetCurrentStateID - cannot find closest weapon stable state.");
  583. return 0;
  584. }
  585. /**@fn GetCurrentStateID
  586. * @brief return internal identifier of current state
  587. **/
  588. int GetInternalStateID()
  589. {
  590. WeaponStateBase curr = GetCurrentState();
  591. int id = 0;
  592. if (curr)
  593. id = curr.GetInternalStateID();
  594. return id;
  595. }
  596. /**@fn OnStoreSave
  597. * @brief save state of fsm
  598. **/
  599. void OnStoreSave(ParamsWriteContext ctx)
  600. {
  601. int id = GetCurrentStableStateID();
  602. if (LogManager.IsWeaponLogEnable()) { wpnDebugSpamALot("[wpnfsm] OnStoreSave - saving current state=" + GetCurrentState() + " id=" + id); }
  603. ctx.Write(id);
  604. }
  605. /**@fn RandomizeFSMState
  606. * @brief Deprecated, use RandomizeFSMStateEx for better results
  607. **/
  608. void RandomizeFSMState(bool hasBullet, bool hasMagazine, bool isJammed)
  609. {
  610. array<MuzzleState> muzzleStates;
  611. if (hasBullet)
  612. muzzleStates = { MuzzleState.L };
  613. else
  614. muzzleStates = { MuzzleState.E };
  615. RandomizeFSMStateEx(muzzleStates, hasMagazine, isJammed);
  616. }
  617. /**@fn RandomizeFSMStateEx
  618. * @brief With the parameters given, selects a random suitable state for the FSM of the weapon
  619. * @NOTE: It is better to use Weapon_Base.RandomizeFSMState instead of calling this one
  620. * @WARNING: Weapon_Base.Synchronize call might be needed, if this method is called while clients are connected
  621. **/
  622. void RandomizeFSMStateEx(array<MuzzleState> muzzleStates, bool hasMagazine, bool isJammed)
  623. {
  624. array<WeaponStableState> candidates = new array<WeaponStableState>;
  625. int tc = m_Transitions.Count();
  626. foreach (WeaponTransition trans : m_Transitions)
  627. {
  628. WeaponStableState state = WeaponStableState.Cast(trans.m_srcState);
  629. if (state && state.HasMagazine() == hasMagazine && state.IsJammed() == isJammed)
  630. {
  631. if (state.IsSingleState())
  632. {
  633. // There is only one, insert it and stop
  634. candidates.Insert(state);
  635. break;
  636. }
  637. int nMuzzles = muzzleStates.Count();
  638. int nMuzzlesState = state.GetMuzzleStateCount();
  639. if (nMuzzles != nMuzzlesState)
  640. {
  641. ErrorEx(string.Format("Number of muzzles on the weapon (%1) does not correspond with what state has configured (%2).", nMuzzles, nMuzzlesState));
  642. continue;
  643. }
  644. bool equal = true;
  645. for (int i = 0; i < nMuzzles; ++i)
  646. {
  647. if (muzzleStates[i] != state.GetMuzzleState(i))
  648. {
  649. equal = false;
  650. break;
  651. }
  652. }
  653. if (equal)
  654. candidates.Insert(state);
  655. }
  656. }
  657. int cc = candidates.Count();
  658. if (cc)
  659. {
  660. int randomIndex = Math.RandomInt(0, cc);
  661. WeaponStableState selected = candidates.Get(randomIndex);
  662. Terminate();
  663. m_State = selected;
  664. if (!Internal_ValidateAndRepair())
  665. Start(null, true);
  666. if (LogManager.IsWeaponLogEnable()) { wpnDebugPrint("[wpnfsm] RandomizeFSMState - randomized current state=" + m_State + " id=" + selected.GetCurrentStateID()); }
  667. selected.SyncAnimState();
  668. }
  669. else
  670. {
  671. if (LogManager.IsWeaponLogEnable()) { wpnDebugPrint("[wpnfsm] RandomizeFSMState - warning - cannot randomize, no states available"); }
  672. }
  673. }
  674. };