local sk_definition_writer = require('sk_definition_writer') local sk_scene = require('sk_scene') local room_export = require('tools.level_scripts.room_export') local signals = require('tools.level_scripts.signals') local animation = require('tools.level_scripts.animation') local yaml_loader = require('tools.level_scripts.yaml_loader') local util = require('tools.level_scripts.util') sk_definition_writer.add_header('"../build/src/audio/clips.h"') local function cutscene_index(cutscenes, name) for _, cutscene in pairs(cutscenes) do if cutscene.name == name then return cutscene.macro end end return -1 end local function generate_locations() local result = {} local location_data = {} for _, location in pairs(sk_scene.nodes_for_type("@location")) do local position, rotation, scale = location.node.full_transformation:decompose() local room_index = room_export.node_nearest_room_index(location.node) local name = location.arguments[1] or '' table.insert(result, { name = name, room_index = room_index, macro = sk_definition_writer.raw(sk_definition_writer.add_macro('LOCATION_' .. name, #result)), }) table.insert(location_data, { { position = position, rotation = rotation, scale = scale, }, roomIndex = room_index, }) end sk_definition_writer.add_definition("locations", "struct Location[]", "_geo", location_data) return result, location_data end local locations, location_data = generate_locations() local function find_location_index(name) for _, location in pairs(locations) do if location.name == name then return location.macro end end print('Could not find a location with the name ' .. name) return 0 end local function find_label_locations(steps) local result = {} local current_index = 1 while current_index <= #steps do local step = steps[current_index] if step.command == "label" and #step.args >= 1 then result[step.args[1]] = current_index table.remove(steps, current_index) else current_index = current_index + 1 end end return result end local function string_starts_with(str, prefix) return string.sub(str, 1, #prefix) == prefix end local function generate_cutscene_step(cutscene_name, step, step_index, label_locations, cutscenes) local result = {} if step.command == "play_sound" or step.command == "start_sound" and #step.args >= 1 then result.type = step.command == "play_sound" and sk_definition_writer.raw('CutsceneStepTypePlaySound') or sk_definition_writer.raw('CutsceneStepTypeStartSound') result.playSound = { sk_definition_writer.raw(string_starts_with(step.args[1], "SOUNDS_") and step.args[1] or ("SOUNDS_" .. step.args[1])), tonumber(step.args[2] or "1") * 255, math.floor(tonumber(step.args[3] or "1") * 64 + 0.5), } elseif step.command == "q_sound" and #step.args >= 3 then result.type = sk_definition_writer.raw('CutsceneStepTypeQueueSound') result.queueSound = { sk_definition_writer.raw(string_starts_with(step.args[1], "SOUNDS_") and step.args[1] or ("SOUNDS_" .. step.args[1])), sk_definition_writer.raw(step.args[2]), sk_definition_writer.raw(step.args[3]), tonumber(step.args[4] or "1") * 255, } elseif step.command == "wait_for_channel" and #step.args >= 1 then result.type = sk_definition_writer.raw('CutsceneStepTypeWaitForChannel') result.waitForChannel = { sk_definition_writer.raw(step.args[1]), } elseif step.command == "delay" and #step.args >= 1 then result.type = sk_definition_writer.raw('CutsceneStepTypeDelay') result.delay = tonumber(step.args[1]) elseif step.command == "open_portal" and #step.args >= 1 then result.type = sk_definition_writer.raw('CutsceneStepTypeOpenPortal') result.openPortal = { find_location_index(step.args[1]), step.args[2] == "1" and 1 or 0, step.args[3] == "pedestal" and 1 or 0 } elseif step.command == "close_portal" and #step.args >= 1 then result.type = sk_definition_writer.raw('CutsceneStepTypeClosePortal') result.closePortal = { step.args[1] == "1" and 1 or 0, } elseif (step.command == "set_signal" or step.command == "clear_signal") and #step.args >= 1 then result.type = sk_definition_writer.raw('CutsceneStepTypeSetSignal') result.setSignal = { signals.signal_index_for_name(step.args[1]), step.command == 'set_signal' and 1 or 0, } elseif step.command == "wait_for_signal" and #step.args >= 1 then result.type = sk_definition_writer.raw('CutsceneStepTypeWaitForSignal') result.waitForSignal = { signals.signal_index_for_name(step.args[1]), } elseif step.command == "teleport_player" and #step.args >= 2 then result.type = sk_definition_writer.raw('CutsceneStepTypeTeleportPlayer') result.teleportPlayer = { find_location_index(step.args[1]), find_location_index(step.args[2]), } elseif step.command == "load_level" and #step.args >= 1 then result.type = sk_definition_writer.raw('CutsceneStepTypeLoadLevel') result.loadLevel = { find_location_index(step.args[1]), -- -1 means next level -1, } elseif step.command == "goto" and #step.args >= 1 then result.type = sk_definition_writer.raw('CutsceneStepTypeGoto') local label_location = label_locations[step.args[1]] if not label_location then error("Unrecognized label '" .. step.args[1] .. "' in cutscene " .. cutscene_name) end result.gotoStep = { label_location - step_index, } elseif step.command == "start_cutscene" and #step.args >= 1 then result.type = sk_definition_writer.raw('CutsceneStepTypeStartCutscene') result.cutscene = { cutscene_index(cutscenes, step.args[1]), } elseif step.command == "stop_cutscene" and #step.args >= 1 then result.type = sk_definition_writer.raw('CutsceneStepTypeStopCutscene') result.cutscene = { cutscene_index(cutscenes, step.args[1]), } elseif step.command == "wait_for_cutscene" and #step.args >= 1 then result.type = sk_definition_writer.raw('CutsceneStepTypeWaitForCutscene') result.cutscene = { cutscene_index(cutscenes, step.args[1]), } elseif step.command == "hide_pedestal" then result.type = sk_definition_writer.raw('CutsceneStepTypeHidePedestal') elseif step.command == "point_pedestal" and #step.args >= 1 then result.type = sk_definition_writer.raw('CutsceneStepTypePointPedestal') result.pointPedestal = { find_location_index(step.args[1]), } elseif step.command == "play_animation" then result.type = sk_definition_writer.raw('CutsceneStepPlayAnimation') local armature = animation.get_armature_index_with_name(step.args[1]) local animation = animation.get_animation_with_name(step.args[1], step.args[2]) if not armature then error("Unrecognized animator " .. step.args[1]) end if not animation then error("Unrecognized animation " .. step.args[2]) end result.playAnimation = { armature, animation, step.args[3] and (tonumber(step.args[3]) * 127) or 127, } elseif step.command == "pause_animation" then result.type = sk_definition_writer.raw('CutsceneStepSetAnimationSpeed') result.setAnimationSpeed = { animation.get_armature_index_with_name(step.args[1]) or 0, 0, } elseif step.command == "resume_animation" then result.type = sk_definition_writer.raw('CutsceneStepSetAnimationSpeed') result.setAnimationSpeed = { animation.get_armature_index_with_name(step.args[1]) or 0, step.args[2] and (tonumber(step.args[2]) * 127) or 127, } elseif step.command == "wait_for_animation" then result.type = sk_definition_writer.raw('CutsceneStepWaitForAnimation') result.waitForAnimation = { animation.get_armature_index_with_name(step.args[1]) or 0, } elseif step.command == "save_checkpoint" then result.type = sk_definition_writer.raw('CutsceneStepSaveCheckpoint') elseif step.command == "kill_player" then result.type = sk_definition_writer.raw('CutsceneStepKillPlayer') result.killPlayer = { step.args[1] == 'water' and 1 or 0, } elseif step.command == "show_prompt" then result.type = sk_definition_writer.raw('CutsceneStepShowPrompt') result.showPrompt = { sk_definition_writer.raw(step.args[1]), } elseif step.command == "rumble" then result.type = sk_definition_writer.raw('CutsceneStepRumble') result.rumble = { tonumber(step.args[1]), } elseif step.command == "activate_signage" then result.type = sk_definition_writer.raw('CutsceneStepActivateSignage') result.activateSignage = { tonumber(step.args[1]), } else error("Unrecognized cutscene step " .. step.command) result.type = sk_definition_writer.raw('CutsceneStepTypeNoop') table.noop = 0; end return result end local function generate_cutscenes() local cutscenes_result = {} local cutscene_data = {} local cutscene_json = yaml_loader.json_contents.cutscenes or {} local cutscenes = {} for cutscene_name, cutscene_steps in pairs(cutscene_json) do table.insert(cutscenes, { name = cutscene_name, steps = cutscene_steps, macro = sk_definition_writer.raw(sk_definition_writer.add_macro("CUTSCENE_" .. cutscene_name, #cutscenes)), }) end for _, cutscene in pairs(cutscenes) do local other_steps = {} for _, step_string in pairs(cutscene.steps) do local args = util.string_split(step_string, ' ') table.insert(other_steps, { command = args[1], args = {table.unpack(args, 2)}, }) end local label_locations = find_label_locations(other_steps) local steps = {} for step_index, step in pairs(other_steps) do table.insert(steps, generate_cutscene_step(cutscene.name, step, step_index, label_locations, cutscenes)) end sk_definition_writer.add_definition(cutscene.name .. '_steps', 'struct CutsceneStep[]', '_geo', steps) table.insert(cutscenes_result, { name = cutscene.name, steps = steps, macro = sk_definition_writer.raw(sk_definition_writer.add_macro("CUTSCENE_" .. cutscene.name, #cutscenes_result)), }) table.insert(cutscene_data, { sk_definition_writer.reference_to(steps, 1), #steps, }) end return cutscenes_result, cutscene_data end local function parse_trigger_signal(signal) if not signal or signal == '-1' then return -1 end return signals.signal_index_for_name(signal) end local function signal_type_index(index, is_hover) if index == 3 then if is_hover then return sk_definition_writer.raw('ObjectTriggerTypeCubeHover') end return sk_definition_writer.raw('ObjectTriggerTypeCube') end return sk_definition_writer.raw('ObjectTriggerTypePlayer') end local function generate_triggers(cutscenes) local result = {} for _, trigger in pairs(sk_scene.nodes_for_type('@trigger')) do local first_mesh = trigger.node.meshes[1] local triggers = {} for i = 1, #trigger.arguments, 2 do local cutscene_name = trigger.arguments[i] local cutscene = cutscene_index(cutscenes, cutscene_name) table.insert(triggers, { signal_type_index(i, string.sub(cutscene_name, 1, 6) == "HOVER_"), cutscene, parse_trigger_signal(trigger.arguments[i + 1]), }) end if first_mesh then local transformed = first_mesh:transform(trigger.node.full_transformation) table.insert(result, { transformed.bb, sk_definition_writer.reference_to(triggers, 1), #triggers, }) sk_definition_writer.add_definition("trigger_targets", "struct ObjectTriggerInfo[]", "_geo", triggers) end end return result end local cutscenes, cutscene_data = generate_cutscenes() local triggers = generate_triggers(cutscenes) sk_definition_writer.add_definition("triggers", "struct Trigger[]", "_geo", triggers) sk_definition_writer.add_definition("cutscenes", "struct Cutscene[]", "_geo", cutscene_data) local function find_cutscene_index(name) return cutscene_index(cutscenes, name) end return { triggers = triggers, cutscene_data = cutscene_data, location_data = location_data, find_location_index = find_location_index, find_cutscene_index = find_cutscene_index, }