apex.lua 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. -- Light's End: authored access squads, damage-gated reactor, cell delivery and escape.
  2. return function(m, a, ending)
  3. local A = {region = 0}
  4. local sides = {"EAST", "WEST"}
  5. local access = {"access1", "access2", "electron_controllers", "dispenser", "security"}
  6. local reactor = {"reactor_east_entry", "reactor_west_entry", "reactor_east_reinforce", "reactor_west_reinforce",
  7. "reactor_east_final", "reactor_west_final", "coffin_east", "coffin_west", "coffin_interior"}
  8. local triggers = {"ACCESS_DOOR_INNER_PLAYER_TRIGGER", "ACCESS_JUMP_ONE_PLAYER_TRIGGER",
  9. "ACCESS_JUMP_TWO_PLAYER_TRIGGER", "SECURITY_CENTER_PLAYER_TRIGGER", "SECURITY_DOOR_PLAYER_TRIGGER",
  10. "SECURITY_LEDGE_PLAYER_TRIGGER", "APEX_DIRECTIVE_REACTOR_GOTO_PLAYER_TRIGGER",
  11. "APEX_REACTOR_CLAMSHELL_001_DIALOG_START_PLAYER_TRIGGER", "REACTOR_ARM_EAST_ENTRANCE_PLAYER_TRIGGER",
  12. "REACTOR_MOTHER_BRAIN_ENTRY_PLAYER_TRIGGGER", "DIALOG_APEX_SECURITY_001_TEST_PLAYER_TRIGGER",
  13. "DIALOG_APEX_SECURITY_002_PLAYER_TRIGGER", "DIALOG_APEX_MOTHER_BRAIN_001_PLAYER_TRIGGER",
  14. "DIALOG_APEX_MOTHER_BRAIN_002_PLAYER_TRIGGER", "DIALOG_APEX_MOTHER_BRAIN_003_PLAYER_TRIGGER",
  15. "DIALOG_APEX_MOTHER_BRAIN_004_PLAYER_TRIGGER"}
  16. local escape_triggers = {"APEX_MOTHER_BRAIN_005_DIALOG_PLAYER_TRIGGER", "APEX_MOTHER_BRAIN_006_DIALOG_PLAYER_TRIGGER",
  17. "APEX_MOTHER_BRAIN_007_DIALOG_PLAYER_TRIGGER", "APEX_MOTHER_BRAIN_008_DIALOG_PLAYER_TRIGGER",
  18. "APEX_DIRECTIVE_REACTOR_RAILS_ESCAPE_PLAYER_TRIGGER"}
  19. local function phase(s) return s:variable("ember.apex.phase") or 0 end
  20. local function set(c, p) c:set_variable("ember.apex.phase", p) end
  21. local function generation(s) return s:variable("ember.apex.generation") or 1 end
  22. local function dead(s, target) return s:variable("ember.apex.dead." .. target) == true end
  23. local function vent_timer(s) return "ember.apex.vents." .. generation(s) end
  24. local function lane(c, name, transition, snap)
  25. a.slot(c, name):transition{transition = c.sdk.device_transitions[transition], snap = snap or false}
  26. end
  27. local function unlock(c, name)
  28. lane(c, name, "unlock"); lane(c, name, "power_on")
  29. end
  30. local function bridges(c, extended, snap)
  31. -- As with the landing bridge, the authored position-0 pose is extended.
  32. for _, side in ipairs(sides) do
  33. a.device(c, "CLAMSHELL_TO_COFFIN_" .. side .. "_BRIDGE_DEVICE", not extended, snap)
  34. end
  35. end
  36. local function coffin_doors(c, open, snap)
  37. for _, side in ipairs(sides) do
  38. a.device(c, "REACTOR_COFFIN_DOOR_" .. side .. "_DEVICE", open, snap)
  39. a.device(c, "REACTOR_COFFIN_LIGHT_" .. side .. "_DEVICE", open, snap)
  40. lane(c, "REACTOR_COFFIN_LIGHT_" .. side .. "_DEVICE", open and "power_on" or "power_off", snap)
  41. end
  42. a.device(c, "REACTOR_SHIELD_DEVICE", open, snap)
  43. end
  44. local function beam(c, active, snap)
  45. -- Both devices address the placed ring/laser objects and their native VFX graphs.
  46. a.device(c, "SPECOPS_APEX_RING_LASER_DEVICE", active, snap)
  47. a.device(c, "SPECOPS_APEX_RING_RING_DEVICE", active, snap)
  48. -- FX power is independent of mechanical position; pulse both authored graphs.
  49. lane(c, "SPECOPS_APEX_RING_LASER_DEVICE", active and "power_on" or "power_off", snap)
  50. lane(c, "SPECOPS_APEX_RING_RING_DEVICE", active and "power_on" or "power_off", snap)
  51. end
  52. local function hazards(c, s, enabled)
  53. a.effect(c, s, "REACTOR_MOTHER_BRAIN_HOT_PIPES_THERMAL_HOP_ON",
  54. "REACTOR_MOTHER_BRAIN_HOT_PIPES_OBJECT_FILTER_80B3C09F",
  55. {players = true, inside_any = {
  56. a.slot(c, "REACTOR_MOTHER_BRAIN_HOT_PIPES_02_TRIGGER_VOLUME"),
  57. a.slot(c, "REACTOR_MOTHER_BRAIN_HOT_PIPES_03_TRIGGER_VOLUME"),
  58. a.slot(c, "SLOT_0005_80B3C09F"), a.slot(c, "SLOT_0006_80B3C09F"),
  59. a.slot(c, "SLOT_0008_80B3C09F")}}, enabled)
  60. a.effect(c, s, "AOD_REACTOR_RAIL_TOP_HOP_ON", "AOD_REACTOR_RAIL_TOP_OBJECT_FILTER",
  61. {players = true, inside = a.slot(c, "SLOT_019E")}, enabled)
  62. end
  63. local function doors(c, side, open, snap)
  64. for _, part in ipairs({"DOOR_A", "DOOR_B", "LIGHT_A", "LIGHT_B", "TARGET"}) do
  65. local name = "REACTOR_CLAMSHELL_" .. side .. "_" .. part .. "_DEVICE"
  66. a.device(c, name, open, snap)
  67. if part == "LIGHT_A" or part == "LIGHT_B" or part == "TARGET" then
  68. a.slot(c, name):transition{transition = open and c.sdk.device_transitions.power_on
  69. or c.sdk.device_transitions.power_off, snap = snap or false}
  70. end
  71. end
  72. end
  73. local function target(c, s, name)
  74. a.slot(c, name .. "_TARGET_OBJECT"):set_interactable_object{generation = generation(s)}
  75. -- Bind on the object's presence receipt: Auth delivery and entity creation
  76. -- happen on different ticks. A missing/unloaded object is not a kill.
  77. end
  78. -- One encounter clock survives the clamshell -> coffin transition. Durations are
  79. -- reconstructed from the reference's warning, exposure and recovery windows.
  80. local function cycle(c, s, step, snap)
  81. c:set_variable("ember.apex.vent_step", step)
  82. c:set_variable("ember.apex.vents_open", step == "open")
  83. if step == "warning" then
  84. beam(c, true)
  85. if phase(s) == 3 then
  86. for _, side in ipairs(sides) do
  87. if not dead(s, side) then a.slot(c, "REACTOR_CLAMSHELL_" .. side .. "_ALARM_SEQUENCE"):play_sequence{} end
  88. end
  89. else a.slot(c, "REACTOR_COFFIN_ALARM_SEQUENCE"):play_sequence{} end
  90. else
  91. if step == "closed" then beam(c, false, snap) end
  92. if phase(s) == 3 then
  93. for _, side in ipairs(sides) do if not dead(s, side) then doors(c, side, step == "open", snap) end end
  94. elseif phase(s) == 4 then
  95. coffin_doors(c, step == "open", snap)
  96. if step == "open" then a.cue(c, s, 46) end
  97. end
  98. end
  99. c:start_timer(vent_timer(s), ({closed = 14000, warning = 6000, open = 10000})[step])
  100. end
  101. local function initialize_devices(c, s)
  102. if s:variable("ember.apex.devices_ready") then return end
  103. c:set_variable("ember.apex.devices_ready", true)
  104. unlock(c, "SECURITY_DOOR_DEVICE")
  105. for _, side in ipairs(sides) do
  106. for _, part in ipairs({"DOOR_A", "DOOR_B"}) do unlock(c, "REACTOR_CLAMSHELL_" .. side .. "_" .. part .. "_DEVICE") end
  107. unlock(c, "CLAMSHELL_TO_COFFIN_" .. side .. "_BRIDGE_DEVICE")
  108. lane(c, "REACTOR_COFFIN_DOOR_" .. side .. "_DEVICE", "lock")
  109. end
  110. lane(c, "MOTHER_BRAIN_DOOR_DEVICE", "lock")
  111. unlock(c, "SPECOPS_APEX_RING_LASER_DEVICE")
  112. unlock(c, "SPECOPS_APEX_RING_RING_DEVICE")
  113. beam(c, false, true)
  114. end
  115. local function start_reactor(c, s)
  116. if phase(s) >= 3 then return end
  117. initialize_devices(c, s)
  118. set(c, 3)
  119. a.checkpoint(c, 0, 0xDF59C25C, "reactor")
  120. a.darkness(c, s, true)
  121. a.device(c, "SECURITY_DOOR_DEVICE", true)
  122. for _, side in ipairs(sides) do target(c, s, "REACTOR_CLAMSHELL_" .. side) end
  123. a.spawn(c, s, {"reactor_east_entry", "reactor_west_entry"})
  124. a.cue(c, s, 40); cycle(c, s, "closed", true)
  125. c:start_timer("ember.apex.explain." .. generation(s), 9000)
  126. end
  127. local carry = require("mission_ember.carry")(a, "apex", "MOTHER_BRAIN_CARRY_OBJECT", "MOTHER_BRAIN_INTERACT_OBJECT",
  128. function(c, s) a.cue(c, s, 50); A.guidance(c, s) end,
  129. function(c, s)
  130. if phase(s) ~= 5 then return end
  131. set(c, 6)
  132. c:start_timer("ember.apex.hazards", 1)
  133. a.checkpoint(c, 0, 0x45920385, "escape")
  134. a.device(c, "MOTHER_BRAIN_CONSOLE_DEVICE", true)
  135. a.device(c, "MOTHER_BRAIN_ENGINE_LEFT_DEVICE", true)
  136. a.device(c, "MOTHER_BRAIN_ENGINE_RIGHT_DEVICE", true)
  137. a.device(c, "REACTOR_GETAWAY_SHIP_DEVICE", true)
  138. a.objects(c, {"REACTOR_GETAWAY_SHIP_OBJECT", "SUNBURN_DAMAGE_OBJECT"}, true)
  139. a.device(c, "SPECOPS_APEX_RING_LASER_DEVICE", true)
  140. a.device(c, "SPECOPS_APEX_RING_RING_DEVICE", true)
  141. a.scene(c, "MOTHER_BRAIN_HOLE_EXPLOSION_SCENE")
  142. a.scene(c, "EMBER_APEX_EXPLOSION_SEQUENCE_PREFAB_TRIGGERED_EXPLOSIONS_SCENE")
  143. for _, name in ipairs(escape_triggers) do a.slot(c, name):fire_trigger{} end
  144. a.cue(c, s, 51); A.guidance(c, s)
  145. end)
  146. function A.enter(c, s)
  147. if s:variable("ember.apex.restart_pending") then
  148. c:clear_variable("ember.apex.restart_pending")
  149. start_reactor(c, s); A.guidance(c, s); return
  150. end
  151. if phase(s) > 0 then return end
  152. set(c, 1)
  153. c:start_timer("ember.apex.setup." .. generation(s), 1)
  154. a.device(c, "ACCESS_DOOR_INNER_DEVICE", true)
  155. a.device(c, "ACCESS_DOOR_OUTER_DEVICE", true)
  156. a.device(c, "SECURITY_DOOR_DEVICE", false, true)
  157. a.slot(c, "SECURITY_PLACED_INTERCEPTOR_OBJECT"):set_interactable_object{generation = 1}
  158. a.objects(c, {"SPECOPS_APEX_RING_LASER_OBJECT",
  159. "SPECOPS_APEX_RING_CORE_OBJECT", "SPECOPS_APEX_RING_RING_OBJECT"}, true)
  160. for _, side in ipairs(sides) do
  161. doors(c, side, false, true)
  162. a.device(c, "CLAMSHELL_PIPES_" .. side .. "_DEVICE", false, true)
  163. a.device(c, "CLAMSHELL_TO_COFFIN_" .. side .. "_BRIDGE_DEVICE", true, true)
  164. a.device(c, "REACTOR_COFFIN_DOOR_" .. side .. "_DEVICE", false, true)
  165. end
  166. a.device(c, "REACTOR_SHIELD_DEVICE", false, true)
  167. a.device(c, "COFFIN_BUNKER_DOOR_SOUTH_DEVICE", false, true)
  168. a.device(c, "MOTHER_BRAIN_DOOR_DEVICE", false, true)
  169. for _, name in ipairs(triggers) do a.slot(c, name):fire_trigger{} end
  170. a.spawn(c, s, {"access1"}); A.guidance(c, s)
  171. end
  172. function A.trigger(c, s, e)
  173. if phase(s) <= 2 then
  174. if a.matches(c, e, "ACCESS_JUMP_TWO_PLAYER_TRIGGER") then a.spawn(c, s, {"access2"}) end
  175. if a.matches(c, e, "SECURITY_CENTER_PLAYER_TRIGGER") or a.matches(c, e, "SECURITY_LEDGE_PLAYER_TRIGGER")
  176. or a.matches(c, e, "SECURITY_DOOR_PLAYER_TRIGGER") then
  177. set(c, 2); a.spawn(c, s, access)
  178. a.device(c, "DISPENSER_MONSTER_CLOSET_LIGHT_DEVICE", true)
  179. if not s:variable("ember.apex.dispenser") then
  180. c:set_variable("ember.apex.dispenser", true)
  181. a.slot(c, "DISPENSER_MONSTER_CLOSET_SEQUENCE"):play_sequence{}
  182. end
  183. a.cue(c, s, 38)
  184. end
  185. end
  186. if a.matches(c, e, "APEX_DIRECTIVE_REACTOR_GOTO_PLAYER_TRIGGER")
  187. or a.matches(c, e, "APEX_REACTOR_CLAMSHELL_001_DIALOG_START_PLAYER_TRIGGER")
  188. or a.matches(c, e, "REACTOR_ARM_EAST_ENTRANCE_PLAYER_TRIGGER") then start_reactor(c, s) end
  189. -- The explanatory cue follows the arrival line, even if its trigger was crossed in the same tick.
  190. if phase(s) == 5 and a.matches(c, e, "DIALOG_APEX_MOTHER_BRAIN_001_PLAYER_TRIGGER") then a.cue(c, s, 50) end
  191. if phase(s) == 6 then
  192. if a.matches(c, e, "APEX_MOTHER_BRAIN_006_DIALOG_PLAYER_TRIGGER") then a.cue(c, s, 52) end
  193. if a.matches(c, e, "APEX_MOTHER_BRAIN_007_DIALOG_PLAYER_TRIGGER") then a.cue(c, s, 53) end
  194. if a.matches(c, e, "APEX_MOTHER_BRAIN_008_DIALOG_PLAYER_TRIGGER") then a.cue(c, s, 54) end
  195. if a.matches(c, e, "APEX_DIRECTIVE_REACTOR_RAILS_ESCAPE_PLAYER_TRIGGER") then
  196. set(c, 7); a.darkness(c, s, false)
  197. hazards(c, s, false)
  198. c:cancel_timer("ember.apex.hazards")
  199. c:cancel_timer(vent_timer(s))
  200. ending.start(c, s)
  201. end
  202. end
  203. A.guidance(c, s)
  204. end
  205. function A.cleared(c, s, name)
  206. if name == "access1" and phase(s) <= 2 then a.spawn(c, s, {"access2"}) end
  207. if name == "electron_controllers" and phase(s) <= 2 then
  208. unlock(c, "SECURITY_DOOR_DEVICE")
  209. a.device(c, "SECURITY_DOOR_DEVICE", true)
  210. end
  211. if phase(s) == 3 or phase(s) == 4 then
  212. for _, side in ipairs({"east", "west"}) do
  213. if name == "reactor_" .. side .. "_entry" then a.spawn(c, s, {"reactor_" .. side .. "_reinforce"}) end
  214. if name == "reactor_" .. side .. "_reinforce" then a.spawn(c, s, {"reactor_" .. side .. "_final"}) end
  215. end
  216. end
  217. A.guidance(c, s)
  218. end
  219. local function destroyed(c, s, which)
  220. if dead(s, which) or (which == "COFFIN" and phase(s) ~= 4)
  221. or (which ~= "COFFIN" and phase(s) ~= 3) then return end
  222. c:set_variable("ember.apex.dead." .. which, true)
  223. if which ~= "COFFIN" then
  224. doors(c, which, true)
  225. a.scene(c, "APEX_REACTOR_CLAMSHELL_" .. which .. "_DESTROYED_SCENE")
  226. a.device(c, "CLAMSHELL_PIPES_" .. which .. "_DEVICE", true)
  227. a.cue(c, s, dead(s, "EAST") and dead(s, "WEST") and 44 or 43)
  228. if dead(s, "EAST") and dead(s, "WEST") then
  229. set(c, 4) -- Keep the current warning/exposure deadline for the central reactor.
  230. -- Remaining authored waves stay alive and retain their objectives.
  231. a.spawn(c, s, {"reactor_east_reinforce", "reactor_west_reinforce", "reactor_east_final", "reactor_west_final"})
  232. c:start_timer("ember.apex.core." .. generation(s), 1)
  233. end
  234. else
  235. set(c, 5)
  236. c:cancel_timer(vent_timer(s)); c:cancel_timer("ember.apex.explain." .. generation(s))
  237. coffin_doors(c, true)
  238. beam(c, true)
  239. unlock(c, "MOTHER_BRAIN_DOOR_DEVICE")
  240. unlock(c, "COFFIN_BUNKER_DOOR_SOUTH_DEVICE")
  241. a.device(c, "MOTHER_BRAIN_DOOR_DEVICE", true)
  242. a.device(c, "COFFIN_BUNKER_DOOR_SOUTH_DEVICE", true)
  243. carry.start(c, s); a.cue(c, s, 48)
  244. end
  245. A.guidance(c, s)
  246. end
  247. function A.damage(c, s, e)
  248. if e.revision ~= generation(s) or e.health == nil or e.health ~= e.health or e.health < 0 then return end
  249. local which
  250. for _, side in ipairs(sides) do if a.matches(c, e, "REACTOR_CLAMSHELL_" .. side .. "_TARGET_DAMAGE") then which = side end end
  251. if a.matches(c, e, "REACTOR_COFFIN_TARGET_DAMAGE") then which = "COFFIN" end
  252. if not which or dead(s, which) or (which == "COFFIN" and phase(s) ~= 4)
  253. or (which ~= "COFFIN" and phase(s) ~= 3) then return end
  254. local seen = "ember.apex.healthy." .. which
  255. if e.health > 0 then c:set_variable(seen, true); return end
  256. if not s:variable(seen) then return end -- Absent/unloaded targets are never destruction receipts.
  257. destroyed(c, s, which)
  258. end
  259. function A.timer(c, s, e)
  260. if e.timer_name == "ember.apex.hazards" then
  261. if phase(s) == 6 and s:variable("ember.region") == 0 then hazards(c, s, true) end
  262. return true
  263. end
  264. if carry.timer(c, s, e) then return true end
  265. if e.timer_name == "ember.apex.setup." .. generation(s) then
  266. if s:variable("ember.region") == 0 then initialize_devices(c, s) end
  267. return true
  268. end
  269. if e.timer_name == "ember.apex.explain." .. generation(s) then
  270. if phase(s) == 3 and s:variable("ember.region") == 0 then a.cue(c, s, 41); A.guidance(c, s) end
  271. return true
  272. end
  273. if e.timer_name == vent_timer(s) then
  274. if (phase(s) == 3 or phase(s) == 4) and s:variable("ember.region") == 0 then
  275. cycle(c, s, ({closed = "warning", warning = "open", open = "closed"})[s:variable("ember.apex.vent_step")] or "closed")
  276. A.guidance(c, s)
  277. end
  278. return true
  279. end
  280. if e.timer_name == "ember.apex.core." .. generation(s) and phase(s) == 4 then
  281. if s:variable("ember.region") ~= 0 or s:variable("ember.apex.core_ready") then return true end
  282. c:set_variable("ember.apex.core_ready", true)
  283. bridges(c, true)
  284. for _, side in ipairs(sides) do unlock(c, "REACTOR_COFFIN_DOOR_" .. side .. "_DEVICE") end
  285. unlock(c, "REACTOR_SHIELD_DEVICE")
  286. coffin_doors(c, s:variable("ember.apex.vents_open") == true)
  287. target(c, s, "REACTOR_COFFIN")
  288. a.spawn(c, s, {"coffin_east", "coffin_west", "coffin_interior"})
  289. A.guidance(c, s); return true
  290. end
  291. return false
  292. end
  293. function A.guidance(c, s)
  294. if s:variable("ember.region") ~= 0 then return end
  295. local p = phase(s)
  296. if p == 1 then a.directive(c, s, "4E4862BB", "SECURITY_INTERCEPTOR_NAV_POINT", a.combat(s, access))
  297. elseif p == 2 then a.directive(c, s, s:variable("ember.apex.interceptor_boarded") and "4E4862BB" or "3CBFC90B",
  298. s:variable("ember.apex.interceptor_boarded") and "APEX_DIRECTIVE_REACTOR_CLAMSHELL_GOTO_NAV_POINT" or "SECURITY_INTERCEPTOR_NAV_POINT", a.combat(s, access))
  299. elseif p == 3 then a.directive(c, s, "26C3C19D", "APEX_DIRECTIVE_REACTOR_CLAMSHELL_GOTO_NAV_POINT", a.combat(s, reactor))
  300. elseif p == 4 then a.directive(c, s, "61D2B286", "APEX_DIRECTIVE_REACTOR_COFFIN_TARGET_NAV_POINT", a.combat(s, reactor))
  301. elseif p == 5 then a.directive(c, s, "4746660F",
  302. carry.held(s) and "EMBER_DIRECTIVE_REACTOR_MOTHER_BRAIN_DELIVERY_NAV_POINT" or "APEX_WEAPON_NAV_POINT", a.combat(s, reactor))
  303. elseif p == 6 then a.directive(c, s, "40FC40AD", "APEX_DIRECTIVE_REACTOR_RAILS_ESCAPE_NAV_POINT", false) end
  304. end
  305. function A.reset(c, s, name)
  306. if name == "escape" then
  307. c:start_timer("ember.apex.hazards", 1)
  308. set(c, 6)
  309. for _, t in ipairs(escape_triggers) do a.slot(c, t):fire_trigger{} end
  310. a.darkness(c, s, true)
  311. else
  312. c:cancel_timer(vent_timer(s)); c:cancel_timer("ember.apex.core." .. generation(s))
  313. c:cancel_timer("ember.apex.explain." .. generation(s)); c:cancel_timer("ember.apex.setup." .. generation(s))
  314. a.reset(c, reactor); carry.reset(c, s)
  315. c:set_variable("ember.apex.generation", generation(s) + 2)
  316. c:clear_variable("ember.apex.core_ready")
  317. for _, side in ipairs({"EAST", "WEST", "COFFIN"}) do
  318. c:clear_variable("ember.apex.dead." .. side); c:clear_variable("ember.apex.healthy." .. side)
  319. end
  320. for _, name in ipairs({"EMBER_APEX_REACTOR_CLAMSHELL_EAST_OBJECTIVE", "EMBER_APEX_REACTOR_CLAMSHELL_WEST_OBJECTIVE",
  321. "EMBER_APEX_REACTOR_COFFIN_OBJECTIVE"}) do a.slot(c, name):reset_objectives{} end
  322. a.objects(c, {"REACTOR_COFFIN_TARGET_OBJECT"}, false)
  323. for _, side in ipairs(sides) do
  324. a.device(c, "CLAMSHELL_TO_COFFIN_" .. side .. "_BRIDGE_DEVICE", true, true)
  325. a.device(c, "REACTOR_COFFIN_DOOR_" .. side .. "_DEVICE", false, true)
  326. lane(c, "REACTOR_COFFIN_DOOR_" .. side .. "_DEVICE", "lock")
  327. end
  328. lane(c, "MOTHER_BRAIN_DOOR_DEVICE", "lock")
  329. a.device(c, "MOTHER_BRAIN_DOOR_DEVICE", false, true)
  330. a.device(c, "COFFIN_BUNKER_DOOR_SOUTH_DEVICE", false, true)
  331. a.device(c, "REACTOR_SHIELD_DEVICE", false, true)
  332. set(c, 2)
  333. -- Publish the reset now; repopulate on the first playable client report
  334. -- after the wipe handshake. A one-millisecond timer can expire during the fade.
  335. c:set_variable("ember.apex.restart_pending", true)
  336. end
  337. c:clear_variable("ember.r.guidance")
  338. end
  339. function A.resume(c, s)
  340. carry.resume(c, s)
  341. if phase(s) == 4 and not s:variable("ember.apex.core_ready") then
  342. c:start_timer("ember.apex.core." .. generation(s), 1)
  343. end
  344. a.darkness(c, s, phase(s) >= 3 and phase(s) <= 6)
  345. if phase(s) == 3 or phase(s) == 4 then
  346. cycle(c, s, "closed", true)
  347. if phase(s) == 3 and not s:variable("ember.r.cue.41") then c:start_timer("ember.apex.explain." .. generation(s), 9000) end
  348. end
  349. end
  350. function A.interaction(c, s, e)
  351. if a.matches(c, e, "SECURITY_PLACED_INTERCEPTOR_OBJECT") and e.generation == 1
  352. and not s:variable("ember.apex.interceptor_boarded") then
  353. c:set_variable("ember.apex.interceptor_boarded", true)
  354. end
  355. carry.interaction(c, s, e)
  356. A.guidance(c, s)
  357. end
  358. function A.object(c, s, e)
  359. carry.state(c, s, e)
  360. if e.generation == generation(s) then
  361. for _, which in ipairs({"EAST", "WEST", "COFFIN"}) do
  362. local prefix = which == "COFFIN" and "REACTOR_COFFIN" or ("REACTOR_CLAMSHELL_" .. which)
  363. if a.matches(c, e, prefix .. "_TARGET_OBJECT") and not dead(s, which)
  364. and ((which == "COFFIN" and phase(s) == 4) or (which ~= "COFFIN" and phase(s) == 3)) then
  365. local seen = "ember.apex.healthy." .. which
  366. if e.present and e.alive then
  367. if not s:variable(seen) then
  368. c:set_variable(seen, true)
  369. a.slot(c, prefix .. "_TARGET_DAMAGE"):watch_damage{
  370. target = a.slot(c, prefix .. "_TARGET_OBJECT"), revision = generation(s)}
  371. end
  372. elseif e.present and e.alive == false and s:variable(seen) then
  373. destroyed(c, s, which)
  374. end
  375. end
  376. end
  377. end
  378. A.guidance(c, s)
  379. end
  380. return A
  381. end