cinder.lua 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  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. -- This is the entrance to Ready Room 1; its defenders are beyond the hatch.
  56. a.device(c, "TUMBLER_HATCH_DOOR_DEVICE", true)
  57. a.slot(c, "TUMBLER_HATCH_DOOR_KLAXON_AUDIO_SEQUENCE"):play_sequence{}
  58. a.device(c, "READY_ROOM_01_WINDOW_CONSOLE_DEVICE", false, true)
  59. foundry_doors(c, false, true)
  60. arm(c)
  61. -- Exact 60/118 volume backing DECK_WHOLE_PLAYER_TRIGGER in 80B3C6E8.
  62. a.effect(c, s, "DECK_HEAT_SHIMMER_HOP_ON", "FOUNDRY_THERMAL_DOT_OBJECT_FILTER",
  63. {players = true, inside = a.slot(c, "SLOT_0076_80B3C6E8")}, true)
  64. for _, name in ipairs({"WEAPON_DOWN", "NO_COMBAT_ABILITIES"}) do
  65. a.effect(c, s, name .. "_HOP_ON", name .. "_OBJECT_FILTER",
  66. {players = true, inside = a.slot(c, "TV_CHUTE_MOTION_BLUR_TRIGGER_VOLUME")}, true)
  67. end
  68. a.spawn(c, s, {"ready1_entry"})
  69. a.cue(c, s, 19)
  70. C.guidance(c, s)
  71. end
  72. local function advance(c, s, row)
  73. local previous = phase(s)
  74. if row.phase < previous then return end
  75. -- Spatial fallback catches a skipped narrow volume within this room, never a completed room.
  76. if row.phase > previous then
  77. set(c, row.phase)
  78. if row.phase == 6 then a.checkpoint(c, 40, 0x82328D63, "ready2") end
  79. if row.phase >= 11 and previous < 11 then a.checkpoint(c, 40, 0x782CAF4C, "foundry") end
  80. end
  81. if row.phase == 6 then a.spawn(c, s, {"ready2_entry"}) end
  82. if row.phase >= 11 and row.phase <= 13 then
  83. a.spawn(c, s, {"foundry_entry"})
  84. if row.phase >= 12 then a.spawn(c, s, {"foundry_mid"}) end
  85. end
  86. a.spawn(c, s, row.groups)
  87. if row.phase == 6 then a.darkness(c, s, not a.clear(s, current_groups[6]))
  88. elseif row.phase >= 11 and row.phase <= 13 then a.darkness(c, s, not a.clear(s, current_groups[11])) end
  89. if row.cue then a.cue(c, s, row.cue) end
  90. if row.phase == 3 then
  91. retreat(c, s, "SUNBURN", "A"); retreat(c, s, "SUNBURN", "B"); retreat(c, s, "SUNBURN", "C")
  92. end
  93. end
  94. function C.trigger(c, s, e)
  95. for _, row in ipairs(steps) do
  96. if a.matches(c, e, row.trigger) then
  97. advance(c, s, row); C.guidance(c, s); return
  98. end
  99. end
  100. if a.matches(c, e, "DECK_EAST_SECRET_PLAYER_TRIGGER") and phase(s) <= 5 then a.spawn(c, s, {"sunburn_secret"}) end
  101. for index, letter in ipairs({"A", "B", "C"}) do
  102. if a.matches(c, e, "ASCENT_SPAWN_SET_0" .. (index + 1) .. "_PLAYER_TRIGGER") and phase(s) <= 10 then
  103. retreat(c, s, "ASCENT", letter)
  104. end
  105. end
  106. if a.matches(c, e, "DIALOG_CINDER_SUNBURN_001_PLAYER_TRIGGER") then a.cue(c, s, 23) end
  107. if a.matches(c, e, "DIALOG_CINDER_READY_ROOM_02_002_PLAYER_TRIGGER") and phase(s) == 6 then a.cue(c, s, 20) end
  108. if a.matches(c, e, "CHUTE_FIRST_WINDOW_PLAYER_TRIGGER") and phase(s) >= 14 then a.cue(c, s, 35) end
  109. if a.matches(c, e, "APEX_GOTO_001_DIALOG_PLAYER_TRIGGER") and phase(s) >= 14 then a.cue(c, s, 36) end
  110. C.guidance(c, s)
  111. end
  112. function C.cleared(c, s, name)
  113. if name == "ready1_entry" then a.spawn(c, s, {"ready1_reinforce"}) end
  114. if (name == "ready1_entry" or name == "ready1_reinforce") and a.clear(s, current_groups[1]) then
  115. a.device(c, "READY_ROOM_01_WINDOW_CONSOLE_DEVICE", true)
  116. a.spawn(c, s, {"tumbler"})
  117. end
  118. if name == "ready2_entry" then a.spawn(c, s, {"ready2_reinforce"}) end
  119. 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
  120. if name == "foundry_entry" then a.spawn(c, s, {"foundry_mid"}) end
  121. if name == "foundry_mid" then a.spawn(c, s, {"foundry_final"}) end
  122. if phase(s) >= 11 and phase(s) <= 13
  123. and (name == "foundry_entry" or name == "foundry_mid" or name == "foundry_final")
  124. and a.clear(s, current_groups[11]) then
  125. set(c, 14)
  126. foundry_doors(c, true)
  127. a.slot(c, "FOUNDRY_HATCH_DOOR_CENTER_KLAXON_AUDIO_SEQUENCE"):play_sequence{}
  128. a.darkness(c, s, false)
  129. a.cue(c, s, 34)
  130. end
  131. C.guidance(c, s)
  132. end
  133. function C.guidance(c, s)
  134. if s:variable("ember.region") ~= 40 then return end
  135. local p = phase(s)
  136. local combat = a.combat(s, current_groups[math.min(p, 11)] or {})
  137. if p <= 2 then a.directive(c, s, "ECECF63D", "CINDER_DIRECTIVE_SUNBURN_GOTO_NAV_POINT")
  138. elseif p <= 5 then a.directive(c, s, "D496059B", "CINDER_DIRECTIVE_READY_ROOM_02_GOTO_NAV_POINT")
  139. elseif p == 6 then a.directive(c, s, combat and "7FF69D75" or "DF93A91C", "CINDER_DIRECTIVE_MEAT_GRINDER_GOTO_NAV_POINT")
  140. elseif p == 7 then a.directive(c, s, "62E3AEFB", "CINDER_DIRECTIVE_MEAT_GRINDER_GOTO_NAV_POINT")
  141. elseif p <= 10 then a.directive(c, s, "BAD7D583", "CINDER_DIRECTIVE_FOUNDRY_GOTO_NAV_POINT")
  142. elseif p <= 13 then a.directive(c, s, "7FF69D75", "FOUNDRY_HATCH_DOOR_CENTER_NAV_POINT")
  143. else a.directive(c, s, "FF7AB219", "CINDER_DIRECTIVE_CHUTE_GOTO_NAV_POINT") end
  144. end
  145. function C.resume(c, s)
  146. if (s:variable("ember.r.furthest") or 0) > 2 then return end
  147. local p = phase(s)
  148. a.darkness(c, s, (p == 6 and not a.clear(s, current_groups[6]))
  149. or (p >= 11 and p <= 13 and not a.clear(s, current_groups[11])))
  150. end
  151. function C.reset(c, s, name)
  152. local first = name == "ready2" and 6 or 11
  153. a.reset(c, current_groups[first])
  154. if first == 11 then foundry_doors(c, false, true) end
  155. a.slot(c, first == 6 and "EMBER_CINDER_READY_ROOM_02_OBJECTIVE" or "EMBER_CINDER_FOUNDRY_OBJECTIVE"):reset_objectives{}
  156. set(c, first); arm(c, first)
  157. a.spawn(c, s, {first == 6 and "ready2_entry" or "foundry_entry"})
  158. a.darkness(c, s, true)
  159. c:clear_variable("ember.r.guidance"); C.guidance(c, s)
  160. end
  161. return C
  162. end