encounter.lua 3.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. -- One encounter's progress lives in transactional mission state, never module-local tables.
  2. local Encounter = {}
  3. Encounter.__index = Encounter
  4. function Encounter.new(tag, squads, cleared)
  5. assert(#squads > 0 and #squads <= 30, "encounter needs 1..30 squads")
  6. return setmetatable({tag = tag, squads = squads, cleared = cleared,
  7. full = (1 << #squads) - 1}, Encounter)
  8. end
  9. function Encounter:phase(state)
  10. return state:variable(self.tag .. ".phase") or 0
  11. end
  12. function Encounter:enter(context, state)
  13. if self:phase(state) ~= 0 then
  14. return false
  15. end
  16. -- Resolve everything before staging the first placement. Missing SDK rows are errors.
  17. local handles = {}
  18. for index, squad in ipairs(self.squads) do
  19. handles[index] = context:squad(squad.id)
  20. local count = 0
  21. for _, value in ipairs(handles[index].default_counts) do
  22. assert(value >= 0, "squad has unresolved authored counts: " .. squad.name)
  23. count = count + value
  24. end
  25. assert(count > 0, "encounter squad has no authored members: " .. squad.name)
  26. end
  27. context:set_variable(self.tag .. ".seen", 0)
  28. context:set_variable(self.tag .. ".alive", 0)
  29. context:set_variable(self.tag .. ".phase", 1)
  30. for _, handle in ipairs(handles) do
  31. handle:place{mode = context.sdk.squad_modes.replace}
  32. end
  33. return true
  34. end
  35. function Encounter:on_squad_state(context, state, event)
  36. if self:phase(state) ~= 1 then
  37. return
  38. end
  39. local bit
  40. for index, squad in ipairs(self.squads) do
  41. local slot = context:slot(squad.sensor)
  42. if event.registry_key == slot.registry_key and event.slot_type == slot.slot_type
  43. and event.slot_index == slot.slot_index then
  44. bit = 1 << (index - 1)
  45. break
  46. end
  47. end
  48. if bit == nil or event.alive_count == nil then
  49. return
  50. end
  51. local seen = state:variable(self.tag .. ".seen") or 0
  52. local alive = state:variable(self.tag .. ".alive") or 0
  53. if event.removal_flag then
  54. context:set_variable(self.tag .. ".seen", seen & ~bit)
  55. context:set_variable(self.tag .. ".alive", alive & ~bit)
  56. return
  57. end
  58. -- Initial empty Sense and an unloaded squad are not evidence of a combat clear.
  59. if event.alive_count > 0 or (event.previous_alive_count or 0) > 0 then
  60. seen = seen | bit
  61. end
  62. if event.alive_count > 0 then
  63. alive = alive | bit
  64. else
  65. alive = alive & ~bit
  66. end
  67. context:set_variable(self.tag .. ".seen", seen)
  68. context:set_variable(self.tag .. ".alive", alive)
  69. if seen == self.full and alive == 0 then
  70. context:set_variable(self.tag .. ".phase", 2)
  71. self.cleared(context, state)
  72. end
  73. end
  74. -- Called only by a confirmed checkpoint reset, not by region transit or script reload.
  75. function Encounter:reset(context)
  76. context:clear_variable(self.tag .. ".phase")
  77. context:clear_variable(self.tag .. ".seen")
  78. context:clear_variable(self.tag .. ".alive")
  79. end
  80. return Encounter