cinder.lua 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. -- Route policy reconstructed from the video and authored volume positions.
  2. return function(m, a)
  3. local C = {region = 40}
  4. local steps = {
  5. {trigger = "READY_ROOM_01_SPAWN_SET_02_PLAYER_TRIGGER_80B3C618", groups = {"ready1_reinforce"}, phase = 1},
  6. {trigger = "READY_ROOM_01_SPAWN_SET_03_PLAYER_TRIGGER", groups = {"ready1_reinforce"}, phase = 1},
  7. {trigger = "TUMBLER_ENTRY_PLAYER_TRIGGER", groups = {"tumbler"}, phase = 2},
  8. {trigger = "DECK_EAST_ENTRY_PLAYER_TRIGGER_80B3C6E8", groups = {"sunburn_east"}, phase = 3, cue = 26},
  9. {trigger = "DECK_BRIDGE_025_PERCENT_PLAYER_TRIGGER_80B3C6E8", groups = {"sunburn_bridge"}, phase = 4, cue = 27},
  10. {trigger = "DECK_BRIDGE_100_PERCENT_PLAYER_TRIGGER", groups = {"sunburn_west"}, phase = 5},
  11. -- The deck exit volume is still outdoors. Preload defenders there, but
  12. -- begin the indoor checkpoint/dialogue only beyond the far Sunside door.
  13. {trigger = "DECK_WEST_EXIT_PLAYER_TRIGGER_80B3C6E8", groups = {"ready2_entry"}, phase = 5},
  14. {trigger = "DIALOG_CINDER_READY_ROOM_02_001_PLAYER_TRIGGER", groups = {"ready2_entry"}, phase = 6, cue = 28},
  15. {trigger = "READY_ROOM_02_SPAWN_SET_01_PLAYER_TRIGGER", groups = {"ready2_entry"}, phase = 6},
  16. {trigger = "READY_ROOM_02_SPAWN_SET_02_PLAYER_TRIGGER", groups = {"ready2_reinforce"}, phase = 6},
  17. {trigger = "READY_ROOM_02_SPAWN_SET_03_PLAYER_TRIGGER", groups = {"ready2_reinforce"}, phase = 6},
  18. {trigger = "READY_ROOM_02_TO_CHAMBER_PLAYER_TRIGGER_80B3C67D", groups = {"chamber"}, phase = 7, cue = 30},
  19. {trigger = "CHAMBER_SPAWN_SET_01_PLAYER_TRIGGER", groups = {"chamber"}, phase = 7},
  20. {trigger = "MEAT_GRINDER_SPAWN_SET_01_PLAYER_TRIGGER", groups = {"meat1"}, phase = 8},
  21. {trigger = "MEAT_GRINDER_SPAWN_SET_02_PLAYER_TRIGGER", groups = {"meat2"}, phase = 9},
  22. {trigger = "ASCENT_SPAWN_SET_01_PLAYER_TRIGGER", groups = {"ascent"}, phase = 10, cue = 31},
  23. {trigger = "OVERLOOK_TO_FOUNDRY_PLAYER_TRIGGER", groups = {"foundry_entry"}, phase = 11, cue = 33},
  24. {trigger = "FOUNDRY_SPAWN_SET_02_PLAYER_TRIGGER", groups = {"foundry_mid"}, phase = 12},
  25. {trigger = "FOUNDRY_SPAWN_SET_03_PLAYER_TRIGGER", groups = {"foundry_final"}, phase = 13},
  26. }
  27. local extras = {"DECK_EAST_SECRET_PLAYER_TRIGGER", "ASCENT_SPAWN_SET_02_PLAYER_TRIGGER",
  28. "ASCENT_SPAWN_SET_03_PLAYER_TRIGGER", "ASCENT_SPAWN_SET_04_PLAYER_TRIGGER",
  29. "CHUTE_FIRST_WINDOW_PLAYER_TRIGGER", "APEX_GOTO_001_DIALOG_PLAYER_TRIGGER",
  30. "DIALOG_CINDER_SUNBURN_001_PLAYER_TRIGGER", "DIALOG_CINDER_READY_ROOM_02_002_PLAYER_TRIGGER",
  31. "CINDER_FOUNDRY_001_DIALOG_PLAYER_TRIGGER"}
  32. local current_groups = {{"ready1_entry", "ready1_reinforce"}, {"tumbler"}, {"sunburn_east"},
  33. {"sunburn_bridge"}, {"sunburn_west"}, {"ready2_entry", "ready2_reinforce"}, {"chamber"},
  34. {"meat1"}, {"meat2"}, {"ascent"}, {"foundry_entry", "foundry_mid", "foundry_final"}}
  35. local function phase(s) return s:variable("ember.cinder.phase") or 0 end
  36. local function set(c, p) c:set_variable("ember.cinder.phase", p) end
  37. local function retreat(c, s, area, letter)
  38. local group = area:lower() .. "_retreat_" .. letter:lower()
  39. if a.groups[group]:enter(c, s) then a.scene(c, area .. "_SQUAD_RETREAT_INSTANCE_" .. letter .. "_PREFAB_RETREAT_SCENE") end
  40. end
  41. local function arm(c, minimum)
  42. for _, row in ipairs(steps) do if row.phase >= (minimum or 0) then a.slot(c, row.trigger):fire_trigger{} end end
  43. for _, name in ipairs(extras) do a.slot(c, name):fire_trigger{} end
  44. end
  45. local function foundry_doors(c, open, snap)
  46. for _, name in ipairs({"FOUNDRY_HATCH_DOOR_LEFT_DEVICE", "FOUNDRY_HATCH_DOOR_RIGHT_DEVICE",
  47. "FOUNDRY_RAMROD_LEFT_DEVICE", "FOUNDRY_RAMROD_RIGHT_DEVICE", "FOUNDRY_WARNING_LIGHT_DEVICE"}) do
  48. a.device(c, name, open, snap)
  49. end
  50. end
  51. function C.enter(c, s)
  52. if phase(s) > 0 then return end
  53. set(c, 1)
  54. a.objects(c, {"CORE_WEAPON_LASER_BEAM_OBJECT_80B3C22D"}, true)
  55. a.enable_device(c, "CINDER_CORE_WEAPON_LASER_BEAM_DEVICE")
  56. -- This is the entrance to Ready Room 1; its defenders are beyond the hatch.
  57. a.device(c, "TUMBLER_HATCH_DOOR_DEVICE", true)
  58. a.slot(c, "TUMBLER_HATCH_DOOR_KLAXON_AUDIO_SEQUENCE"):play_sequence{}
  59. a.device(c, "READY_ROOM_01_WINDOW_CONSOLE_DEVICE", false, true)
  60. foundry_doors(c, false, true)
  61. arm(c)
  62. -- Exact 60/118 volume backing DECK_WHOLE_PLAYER_TRIGGER in 80B3C6E8.
  63. a.effect(c, s, "DECK_HEAT_SHIMMER_HOP_ON", "FOUNDRY_THERMAL_DOT_OBJECT_FILTER",
  64. {players = true, inside = a.slot(c, "SLOT_0076_80B3C6E8")}, true)
  65. for _, name in ipairs({"WEAPON_DOWN", "NO_COMBAT_ABILITIES"}) do
  66. a.effect(c, s, name .. "_HOP_ON", name .. "_OBJECT_FILTER",
  67. {players = true, inside = a.slot(c, "TV_CHUTE_MOTION_BLUR_TRIGGER_VOLUME")}, true)
  68. end
  69. a.spawn(c, s, {"ready1_entry"})
  70. a.cue(c, s, 19)
  71. C.guidance(c, s)
  72. end
  73. local function advance(c, s, row)
  74. local previous = phase(s)
  75. if row.phase < previous then return end
  76. -- Spatial fallback catches a skipped narrow volume within this room, never a completed room.
  77. if row.phase > previous then
  78. set(c, row.phase)
  79. if row.phase == 6 then a.checkpoint(c, 40, 0x82328D63, "ready2") end
  80. if row.phase >= 11 and previous < 11 then a.checkpoint(c, 40, 0x782CAF4C, "foundry") end
  81. end
  82. if row.phase == 6 then a.spawn(c, s, {"ready2_entry"}) end
  83. if row.phase >= 11 and row.phase <= 13 then
  84. a.spawn(c, s, {"foundry_entry"})
  85. if row.phase >= 12 then a.spawn(c, s, {"foundry_mid"}) end
  86. end
  87. a.spawn(c, s, row.groups)
  88. if row.phase == 6 then a.darkness(c, s, not a.clear(s, current_groups[6]))
  89. elseif row.phase >= 11 and row.phase <= 13 then a.darkness(c, s, not a.clear(s, current_groups[11])) end
  90. if row.cue then a.cue(c, s, row.cue) end
  91. if row.phase == 3 then
  92. retreat(c, s, "SUNBURN", "A"); retreat(c, s, "SUNBURN", "B"); retreat(c, s, "SUNBURN", "C")
  93. end
  94. end
  95. function C.trigger(c, s, e)
  96. for _, row in ipairs(steps) do
  97. if a.matches(c, e, row.trigger) then
  98. advance(c, s, row); C.guidance(c, s); return
  99. end
  100. end
  101. if a.matches(c, e, "DECK_EAST_SECRET_PLAYER_TRIGGER") and phase(s) <= 5 then a.spawn(c, s, {"sunburn_secret"}) end
  102. for index, letter in ipairs({"A", "B", "C"}) do
  103. if a.matches(c, e, "ASCENT_SPAWN_SET_0" .. (index + 1) .. "_PLAYER_TRIGGER") and phase(s) <= 10 then
  104. retreat(c, s, "ASCENT", letter)
  105. end
  106. end
  107. if a.matches(c, e, "DIALOG_CINDER_SUNBURN_001_PLAYER_TRIGGER") then a.cue(c, s, 23) end
  108. if a.matches(c, e, "DIALOG_CINDER_READY_ROOM_02_002_PLAYER_TRIGGER") and phase(s) == 6 then a.cue(c, s, 20) end
  109. if a.matches(c, e, "CHUTE_FIRST_WINDOW_PLAYER_TRIGGER") and phase(s) >= 14 then a.cue(c, s, 35) end
  110. if a.matches(c, e, "APEX_GOTO_001_DIALOG_PLAYER_TRIGGER") and phase(s) >= 14 then a.cue(c, s, 36) end
  111. C.guidance(c, s)
  112. end
  113. function C.cleared(c, s, name)
  114. if name == "ready1_entry" then a.spawn(c, s, {"ready1_reinforce"}) end
  115. if (name == "ready1_entry" or name == "ready1_reinforce") and a.clear(s, current_groups[1]) then
  116. a.device(c, "READY_ROOM_01_WINDOW_CONSOLE_DEVICE", true)
  117. a.spawn(c, s, {"tumbler"})
  118. end
  119. if name == "ready2_entry" then a.spawn(c, s, {"ready2_reinforce"}) end
  120. if (name == "ready2_entry" or name == "ready2_reinforce") and phase(s) == 6 and a.clear(s, current_groups[6]) then a.darkness(c, s, false) end
  121. if name == "foundry_entry" then a.spawn(c, s, {"foundry_mid"}) end
  122. if name == "foundry_mid" then a.spawn(c, s, {"foundry_final"}) end
  123. if phase(s) >= 11 and phase(s) <= 13
  124. and (name == "foundry_entry" or name == "foundry_mid" or name == "foundry_final")
  125. and a.clear(s, current_groups[11]) then
  126. set(c, 14)
  127. foundry_doors(c, true)
  128. a.slot(c, "FOUNDRY_HATCH_DOOR_CENTER_KLAXON_AUDIO_SEQUENCE"):play_sequence{}
  129. a.darkness(c, s, false)
  130. a.cue(c, s, 34)
  131. end
  132. C.guidance(c, s)
  133. end
  134. function C.guidance(c, s)
  135. if s:variable("ember.region") ~= 40 then return end
  136. local p = phase(s)
  137. local combat = a.combat(s, current_groups[math.min(p, 11)] or {})
  138. if p <= 2 then a.directive(c, s, "ECECF63D", "CINDER_DIRECTIVE_SUNBURN_GOTO_NAV_POINT")
  139. elseif p <= 5 then a.directive(c, s, "D496059B", "CINDER_DIRECTIVE_READY_ROOM_02_GOTO_NAV_POINT")
  140. elseif p == 6 then a.directive(c, s, combat and "7FF69D75" or "DF93A91C", "CINDER_DIRECTIVE_MEAT_GRINDER_GOTO_NAV_POINT")
  141. elseif p == 7 then a.directive(c, s, "62E3AEFB", "CINDER_DIRECTIVE_MEAT_GRINDER_GOTO_NAV_POINT")
  142. elseif p <= 10 then a.directive(c, s, "BAD7D583", "CINDER_DIRECTIVE_FOUNDRY_GOTO_NAV_POINT")
  143. elseif p <= 13 then a.directive(c, s, "7FF69D75", "FOUNDRY_HATCH_DOOR_CENTER_NAV_POINT")
  144. else a.directive(c, s, "FF7AB219", "CINDER_DIRECTIVE_CHUTE_GOTO_NAV_POINT") end
  145. end
  146. function C.resume(c, s)
  147. if (s:variable("ember.r.furthest") or 0) > 2 then return end
  148. local p = phase(s)
  149. a.darkness(c, s, (p == 6 and not a.clear(s, current_groups[6]))
  150. or (p >= 11 and p <= 13 and not a.clear(s, current_groups[11])))
  151. end
  152. function C.reset(c, s, name)
  153. local first = name == "ready2" and 6 or 11
  154. a.reset(c, current_groups[first])
  155. if first == 11 then foundry_doors(c, false, true) end
  156. a.slot(c, first == 6 and "EMBER_CINDER_READY_ROOM_02_OBJECTIVE" or "EMBER_CINDER_FOUNDRY_OBJECTIVE"):reset_objectives{}
  157. set(c, first); arm(c, first)
  158. a.spawn(c, s, {first == 6 and "ready2_entry" or "foundry_entry"})
  159. a.darkness(c, s, true)
  160. c:clear_variable("ember.r.guidance"); C.guidance(c, s)
  161. end
  162. return C
  163. end