| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173 |
- -- Movement and passenger delivery are separate native operations with separate receipts.
- local AI = require("mission_ember.combat_ai")
- local ARRIVAL_SETTLE_MS = 3000
- local EXTRA_DROP_DELAY_MS = 3000
- -- FNV-1("exit_25"), authored by Harvester action controller 80FE21CE.
- -- Empty group requests an exact, unique name lookup in this Harvester's native action table.
- local EXIT_ACTION = 0x7B0D3643
- local NO_GROUP = 0x811C9DC5
- return function(mission, passengers)
- local objective = assert(mission.Slot.EMBER_POWERHOUSE_BRIDGE_OBJECTIVE)
- local ships = {}
- -- A/B cross the bridge deck; C/D are fly-through ships with no bridge passengers.
- local manifest = {{1, 3}, {2, 4}, {}, {}}
- assert(#passengers.squads == 4 and passengers.reserved)
- for index, letter in ipairs({"A", "B", "C", "D"}) do
- local prefix = "BRIDGE_CROSSING_DROPSHIP_" .. letter
- ships[index] = {
- squad = assert(mission.Squad[prefix .. "_SQUAD"]),
- sensor = assert(mission.Slot[prefix .. "_SQUAD"]),
- member = assert(mission.Slot[prefix .. "_SQUAD_HARVESTER"]),
- entry = assert(mission.Slot[prefix .. "_ENTRY_SEQUENCE"]),
- exit = assert(mission.Slot[prefix .. "_EXIT_SEQUENCE"]),
- cargo = manifest[index],
- }
- end
- local function generation(state) return state:variable("ember.transport.generation") or 1 end
- local function timer_name(state, kind, index)
- local gen = generation(state)
- return "ember.transport." .. kind .. "." .. index .. (gen == 1 and "" or "." .. gen)
- end
- local function path(context, state, ship, revision)
- context:slot(ship.member):play_actor_path{generation = generation(state), revision = revision,
- path = context:slot(revision == 1 and ship.entry or ship.exit)}
- end
- -- Five bits per ship: entry finished, delivery finished, exit requested, retired, unload requested.
- local function depart(context, state, index)
- local shift = (index - 1) * 5
- local flags = state:variable("ember.transport.flags") or 0
- if ((flags >> shift) & 31) ~= 19 then return end
- local seen = state:variable(passengers.tag .. ".seen") or 0
- for _, cargoIndex in ipairs(ships[index].cargo) do
- if (seen & (1 << (cargoIndex - 1))) == 0 then return end
- end
- -- Native delivery is now prompt. Keep a brief authored-position hover after
- -- detach completes; native departure action follows the exit path with no timer.
- local holds = state:variable("ember.transport.departures") or 0
- local scheduled, ready = 1 << (index - 1), 1 << (index + 3)
- if (holds & scheduled) == 0 then
- context:set_variable("ember.transport.departures", holds | scheduled)
- context:start_timer(timer_name(state, "depart", index), 4000)
- return
- end
- if (holds & ready) == 0 then return end
- context:set_variable("ember.transport.flags", flags | (4 << shift))
- path(context, state, ships[index], 2)
- end
- local function unload(context, state, index)
- local shift = (index - 1) * 5
- local flags = state:variable("ember.transport.flags") or 0
- local previous = (flags >> shift) & 31
- if (previous & 25) ~= 1 then return end
- local cargo = {}
- for _, cargoIndex in ipairs(ships[index].cargo) do
- cargo[#cargo + 1] = context:slot(passengers.squads[cargoIndex].sensor)
- end
- if #cargo > 0 then
- context:slot(ships[index].member):deliver_squads{generation = generation(state), revision = 1, squads = cargo}
- context:set_variable("ember.transport.flags", flags | (16 << shift))
- else
- context:set_variable("ember.transport.flags", flags | (18 << shift))
- depart(context, state, index)
- end
- end
- local function prepare(context, state)
- if state:variable("ember.transport.prepared") then return end
- context:set_variable("ember.transport.prepared", true)
- -- Prepare the requests before enabling Ghost. These reserved squads and empty
- -- parents create no actors; only the later member entry command starts a ship.
- passengers:enter(context, state)
- context:set_variable("ember.transport.ai", 0)
- for _, ship in ipairs(ships) do
- local parent = context:squad(ship.squad)
- local counts = parent:counts()
- for index = 1, #parent.default_counts do counts:set(index, 0) end
- parent:place{counts = counts, mode = context.sdk.squad_modes.replace}
- -- Keep zero ordinary spawn requests: the member's path creates the ship.
- -- Objective membership lets the native squad evaluate authored behavior.
- AI.assign(context, ship.sensor, objective, -1, false)
- end
- end
- return {
- reset = function(context, state)
- local old = generation(state)
- for index, ship in ipairs(ships) do
- context:cancel_timer(timer_name(state, "depart", index))
- context:cancel_timer(timer_name(state, "unload", index))
- context:slot(ship.member):retire_actor{generation = old + 1}
- end
- context:set_variable("ember.transport.generation", old + 2)
- for _, key in ipairs({"prepared", "started", "flags", "departures", "exit_actions", "ai"}) do
- context:clear_variable("ember.transport." .. key)
- end
- end,
- prepare = prepare,
- start = function(context, state)
- if state:variable("ember.transport.started") then return end
- prepare(context, state)
- context:set_variable("ember.transport.started", true)
- context:set_variable("ember.transport.flags", 0)
- for _, ship in ipairs(ships) do path(context, state, ship, 1) end
- end,
- on_path = function(context, state, event)
- if not state:variable("ember.transport.started") or event.generation ~= generation(state) then return end
- for index, ship in ipairs(ships) do
- local member = context:slot(ship.member)
- if event.registry_key == member.registry_key and event.slot_type == member.slot_type
- and event.slot_index == member.slot_index then
- local bits = 0
- if event.revision == 1 and event.path_state == 1 then bits = bits | 1 end
- if event.delivery_revision == 1 and event.delivery_state == 0 then bits = bits | 2 end
- local flags = state:variable("ember.transport.flags") or 0
- local previous = (flags >> ((index - 1) * 5)) & 31
- local exits = state:variable("ember.transport.exit_actions") or 0
- local requested = (exits & (1 << (index - 1))) ~= 0
- if not event.dead and event.revision == 2 and event.path_state == 1
- and (previous & 12) == 4 and not requested then
- context:set_variable("ember.transport.exit_actions", exits | (1 << (index - 1)))
- member:play_actor_action{generation = generation(state), revision = 3, group = NO_GROUP, action = EXIT_ACTION}
- end
- if event.dead or (event.revision == 3 and event.path_state == 1 and requested) then
- bits = bits | 8
- if (previous & 8) == 0 then member:retire_actor{generation = generation(state) + 1} end
- end
- context:set_variable("ember.transport.flags", flags | (bits << ((index - 1) * 5)))
- if (bits & 1) ~= 0 and (previous & 9) == 0 and not event.dead then
- context:start_timer(timer_name(state, "unload", index), ARRIVAL_SETTLE_MS + EXTRA_DROP_DELAY_MS)
- end
- depart(context, state, index)
- return
- end
- end
- end,
- on_timer = function(context, state, event)
- if not state:variable("ember.transport.started") then return end
- for index = 1, #ships do
- if event.timer_name == timer_name(state, "depart", index) then
- local holds = state:variable("ember.transport.departures") or 0
- local scheduled = 1 << (index - 1)
- if (holds & scheduled) ~= 0 then
- context:set_variable("ember.transport.departures", holds | (1 << (index + 3)))
- depart(context, state, index)
- end
- return
- end
- if event.timer_name == timer_name(state, "unload", index) then
- unload(context, state, index)
- return
- end
- end
- end,
- on_squad = function(context, state, event)
- if not state:variable("ember.transport.prepared") then return end
- for index, ship in ipairs(ships) do
- local sensor = context:slot(ship.sensor)
- if event.registry_key == sensor.registry_key and event.slot_type == sensor.slot_type
- and event.slot_index == sensor.slot_index then
- AI.update(context, state, "ember.transport.ai", ship.sensor, objective, 14, event, index, false)
- end
- if state:variable("ember.transport.started") then depart(context, state, index) end
- end
- end,
- }
- end
|