route_support.lua 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. -- Shared, transactional helpers for the later mission. No progress is kept in module locals.
  2. local Encounter = require("mission_ember.encounter")
  3. return function(mission, on_clear)
  4. local api = {groups = {}}
  5. local music = require("mission_ember.music")(mission)
  6. local directives = {
  7. ["4746660F"] = mission.Directive.DELIVER_THE_FINAL_BLOW_TO_THE_ALMIGHTY_S_WEAPON_SYSTEMS,
  8. ["40FC40AD"] = mission.Directive.ESCAPE_THE_ALMIGHTY,
  9. ["03285502"] = mission.Directive.FIND_AND_DISABLE_THE_ALMIGHTY_S_WEAPONS,
  10. ["127B96D6"] = mission.Directive.FIND_AND_DISABLE_THE_ALMIGHTY_S_WEAPONS_127B96D6,
  11. ["2700C0C5"] = mission.Directive.FIND_AND_DISABLE_THE_ALMIGHTY_S_WEAPONS_2700C0C5,
  12. ["3CBFC90B"] = mission.Directive.FIND_AND_DISABLE_THE_ALMIGHTY_S_WEAPONS_3CBFC90B,
  13. ["4E4862BB"] = mission.Directive.FIND_AND_DISABLE_THE_ALMIGHTY_S_WEAPONS_4E4862BB,
  14. ["4EA80049"] = mission.Directive.FIND_AND_DISABLE_THE_ALMIGHTY_S_WEAPONS_4EA80049,
  15. ["57050F62"] = mission.Directive.FIND_AND_DISABLE_THE_ALMIGHTY_S_WEAPONS_57050F62,
  16. ["591B1D88"] = mission.Directive.FIND_AND_DISABLE_THE_ALMIGHTY_S_WEAPONS_591B1D88,
  17. ["62E3AEFB"] = mission.Directive.FIND_AND_DISABLE_THE_ALMIGHTY_S_WEAPONS_62E3AEFB,
  18. ["65D5979D"] = mission.Directive.FIND_AND_DISABLE_THE_ALMIGHTY_S_WEAPONS_65D5979D,
  19. ["6D8E0897"] = mission.Directive.FIND_AND_DISABLE_THE_ALMIGHTY_S_WEAPONS_6D8E0897,
  20. ["7FF69D75"] = mission.Directive.FIND_AND_DISABLE_THE_ALMIGHTY_S_WEAPONS_7FF69D75,
  21. ["883C188D"] = mission.Directive.FIND_AND_DISABLE_THE_ALMIGHTY_S_WEAPONS_883C188D,
  22. ["A70DA4A6"] = mission.Directive.FIND_AND_DISABLE_THE_ALMIGHTY_S_WEAPONS_A70DA4A6,
  23. ["AFBF882A"] = mission.Directive.FIND_AND_DISABLE_THE_ALMIGHTY_S_WEAPONS_AFBF882A,
  24. ["BAD7D583"] = mission.Directive.FIND_AND_DISABLE_THE_ALMIGHTY_S_WEAPONS_BAD7D583,
  25. ["D496059B"] = mission.Directive.FIND_AND_DISABLE_THE_ALMIGHTY_S_WEAPONS_D496059B,
  26. ["DF93A91C"] = mission.Directive.FIND_AND_DISABLE_THE_ALMIGHTY_S_WEAPONS_DF93A91C,
  27. ["ECECF63D"] = mission.Directive.FIND_AND_DISABLE_THE_ALMIGHTY_S_WEAPONS_ECECF63D,
  28. ["FF7AB219"] = mission.Directive.FIND_AND_DISABLE_THE_ALMIGHTY_S_WEAPONS_FF7AB219,
  29. ["26C3C19D"] = mission.Directive.SABOTAGE_THE_ALMIGHTY_S_WEAPON_SYSTEMS,
  30. ["61D2B286"] = mission.Directive.SABOTAGE_THE_ALMIGHTY_S_WEAPON_SYSTEMS_61D2B286,
  31. ["6C7F39DC"] = mission.Directive.SABOTAGE_THE_ALMIGHTY_S_WEAPON_SYSTEMS_6C7F39DC,
  32. ["9DD48A8A"] = mission.Directive.SABOTAGE_THE_ALMIGHTY_S_WEAPON_SYSTEMS_9DD48A8A,
  33. }
  34. local roster = require("mission_ember.route_roster")
  35. for _, row in ipairs(roster) do
  36. local squads = {}
  37. for index, name in ipairs(row.squads) do
  38. squads[#squads + 1] = {name = name, id = assert(mission.Squad[name], name),
  39. sensor = assert(mission.Slot[name], name), objective = assert(mission.Slot[row.objective], row.objective),
  40. task_groups = row.task_groups, fixed_task = row.fixed_tasks and row.fixed_tasks[index]}
  41. end
  42. api.groups[row.name] = Encounter.new("ember.r." .. row.name, squads,
  43. function(c, s) on_clear(c, s, row.name) end)
  44. end
  45. function api.slot(c, name) return c:slot(assert(mission.Slot[name], "missing Ember slot: " .. name)) end
  46. function api.matches(c, event, name)
  47. local slot = api.slot(c, name)
  48. return event.registry_key == slot.registry_key and event.slot_type == slot.slot_type
  49. and event.slot_index == slot.slot_index
  50. end
  51. function api.spawn(c, s, names)
  52. for _, name in ipairs(names) do assert(api.groups[name], name):enter(c, s) end
  53. end
  54. function api.clear(s, names)
  55. for _, name in ipairs(names) do if api.groups[name]:phase(s) ~= 2 then return false end end
  56. return true
  57. end
  58. function api.combat(s, names)
  59. for _, name in ipairs(names) do if api.groups[name]:phase(s) == 1 then return true end end
  60. return false
  61. end
  62. function api.reset(c, names)
  63. for _, name in ipairs(names) do api.groups[name]:reset(c) end
  64. end
  65. function api.squad(c, s, event)
  66. for _, row in ipairs(roster) do api.groups[row.name]:on_squad_state(c, s, event) end
  67. end
  68. function api.device(c, name, open, snap)
  69. api.slot(c, name):transition{transition = open and c.sdk.device_transitions.open or c.sdk.device_transitions.close,
  70. snap = snap or false}
  71. end
  72. function api.objects(c, names, active)
  73. -- Each is an exact type-4 entry at its package transform, including native components.
  74. for _, name in ipairs(names) do api.slot(c, name):set_object_active{active = active} end
  75. end
  76. function api.cue(c, s, index)
  77. local key = "ember.r.cue." .. index
  78. if s:variable(key) then return end
  79. c:set_variable(key, true)
  80. api.slot(c, "M_DIALOG_SENSOR_80B3C90A"):play_dialogue_cue{
  81. cue = assert(mission.DialogueCue.M_DIALOG_SENSOR_80B3C90A["CUE_" .. index])}
  82. end
  83. function api.scene(c, name) c:scene(assert(mission.Scene[name], name)):activate{} end
  84. function api.darkness(c, s, enabled)
  85. if s:variable("ember.darkness.enabled") == enabled then return end
  86. c:set_variable("ember.darkness.enabled", enabled)
  87. api.slot(c, "HARD_WIPE_GLOBALS"):set_darkness_zone{enabled = enabled}
  88. end
  89. function api.effect(c, s, effect, filter, selection, enabled)
  90. local key = "ember.effect." .. effect
  91. local revision = (s:variable(key) or 0) + 1
  92. c:set_variable(key, revision)
  93. if enabled then api.slot(c, filter):set_object_filter(selection) end
  94. api.slot(c, effect):set_mission_effect{filter = enabled and api.slot(c, filter) or nil,
  95. enabled = enabled, revision = revision}
  96. end
  97. function api.checkpoint(c, region, hash, name)
  98. c:set_variable("ember.checkpoint.region", region)
  99. c:set_variable("ember.checkpoint.hash", hash)
  100. c:set_variable("ember.checkpoint.name", name)
  101. end
  102. -- Each authored directive carries both HUD lines: `title` is the main objective and
  103. -- `description` the sub-objective, so the banner advances by selecting a new element.
  104. -- Native type-68 state 0 ENTERS the directive, and the Lua surface always publishes it,
  105. -- so every republication replays the banner's entry animation. Republishing whenever the
  106. -- navpoint changed therefore made the objective flash on each combat transition while its
  107. -- text stayed put. The directive is now keyed on its element alone and carries one stable
  108. -- navpoint, so the body changes only at a real milestone and enters exactly once there.
  109. function api.directive(c, s, hash, marker, combat)
  110. local rank = ({[56] = 1, [40] = 2, [0] = 3})[s:variable("ember.region")] or 0
  111. if rank < (s:variable("ember.r.furthest") or 0) then return end
  112. music.update(c, s)
  113. local shown = marker or ""
  114. local signature = hash .. ":" .. shown
  115. if s:variable("ember.r.guidance") == signature then return end
  116. local binding = assert(directives[hash], "missing authored directive " .. hash)
  117. local region = s:variable("ember.region")
  118. local engagement = ({[0] = "M_ENGAGEMENT_SENSOR_80B3C21C",
  119. [40] = "M_ENGAGEMENT_SENSOR_80B3C22D", [56] = "M_ENGAGEMENT_SENSOR_80B3C8F7"})[region]
  120. local audience = engagement and api.slot(c, engagement)
  121. if audience and not s:variable("ember.hud.audience." .. region) then
  122. -- Native 9F2A00 includes active players when flag bit0 is clear.
  123. audience:set_engagement_state{flags = 0, revision = 1}
  124. c:set_variable("ember.hud.audience." .. region, true)
  125. end
  126. api.slot(c, "M_DIRECTIVE_SENSOR_80B3C90A"):set_directive{
  127. directive = binding, audience = audience, navpoint = shown ~= "" and api.slot(c, shown) or nil}
  128. c:set_variable("ember.r.guidance", signature)
  129. end
  130. return api
  131. end