--[[ `Appkit` provides management for Editor Test Level and basic application objects, such as `Window`. It also exposes an interface to manage `World` objects, `Level` objects, and arbitrary game objects. The core usage of `Appkit` is to call `Appkit.setup_application` at Init time, then `Appkit.Update`, `Appkit.Render`, and `Appkit.Shutdown` from the respective engine Lua hooks. ------------------------------------------------------------------------------- World Management ------------------------------------------------------------------------------- -- Registers a `World` to be managed by Appkit. The `world` will update -- each frame, and render each frame with any enabled cameras and its -- shading environment. See the Appkit.WorldWrapper lua for information -- on using WorldWrapper objects. local world_wrapper = Appkit.manage_world(Project.world) -- Gets a managed `WorldWrapper` from the Appkit for a given `World`, or -- nil if the world is not managed by appkit. local world_wrapper = Appkit.get_managed_world(Project.world) ------------------------------------------------------------------------------- Level Management ------------------------------------------------------------------------------- -- Registers a `Level` to be managed by Appkit. The `Level` will update -- each frame. See the Appkit.LevelWrapper lua for information -- on using LevelWrapper objects. local Level_wrapper = Appkit.manage_Level(Project.Level) -- Gets a managed `LevelWrapper` from the Appkit for a given `Level`, or -- nil if the level is not managed by appkit. local level_wrapper = Appkit.get_managed_level(Project.level) -- Defines custom level unload and load functions for manaaged levels. -- This is used by the Appkit's Level / Change Level flow node and by -- Appkit.SimpleProject's change_level function. See the below Config -- section for default Appkit managed level load/unload behavior. Appkit.custom_unload_level_function = Project.unload_level Appkit.custom_load_level_function = Project.load_level ]]-- Appkit = Appkit or {} -- Plugin modules if stingray.GwNavWorld then -- Ensure Navigation runtime plugin is loaded in engine if pcall(function() require 'gwnav/lua/safe_require' end) then -- Use a pcall to ensure that Navigation editor plugin was activated in editor at data compile safe_require 'gwnav/lua/runtime/navflowcallbacks' end end require 'core/wwise/lua/wwise_flow_callbacks' require 'core/humanik/lua/humanik_flow_callbacks' require 'core/animation/lua/runtime/animationrequires' require 'core/scaleform_studio/lua/scaleform_studio_flow_callbacks' -- Appkit modules require 'core/appkit/lua/appkit_flow_callbacks' require 'core/appkit/lua/scaleform_studio_input' local ComponentManager = require 'core/appkit/lua/component_manager' local InputMapper = require 'core/appkit/lua/input_mapper' local LevelWrapper = require 'core/appkit/lua/level_wrapper' local Util = require 'core/appkit/lua/util' local WorldWrapper = require 'core/appkit/lua/world_wrapper' -- Cached modules local Window = stingray.Window local Keyboard = stingray.Keyboard local Application = stingray.Application local World = stingray.World local Level = stingray.Level -------------------------------------------------------------------------------- -- Config -- Optional overrides used by the Appkit when changing level triggered via the -- Appkit.set_pending_level_change function. The default behavior will: -- a) Check if the level to unload is a managed level and shutdown the -- LevelWrapper and unmanage the level. -- b) Destroy the level to unload. -- c) Create a new level using the same world as the unloaded level. -- d) Manage the level. Appkit.custom_unload_level_function = Appkit.custom_unload_level_function or nil Appkit.custom_load_level_function = Appkit.custom_load_level_function or nil -------------------------------------------------------------------------------- -- Main Appkit objects Appkit.was_setup = Appkit.was_setup or false -- Access to Editor's Test Level name. Will be nil if not currently running an -- Editor Test Level or Appkit.setup_application() was not called. Appkit.test_level_name = Appkit.test_level_name or nil Appkit.test_level_resource_name = Appkit.test_level_resource_name or nil Appkit.input_mapper = Appkit.input_mapper or nil Appkit.managed_world = Appkit.managed_world or nil -- todo: multiple worlds Appkit.managed_levels = Appkit.managed_levels or {} Appkit.pending_level_change = Appkit.pending_level_change or {current_level = nil, next_level_resource_name = nil} Appkit.boxed_editor_view_position = Appkit.boxed_editor_view_position or nil Appkit.boxed_editor_view_rotation = Appkit.boxed_editor_view_rotation or nil -------------------------------------------------------------------------------- -- External Interface function Appkit.is_standalone() return LEVEL_EDITOR_TEST == nil end -- Returns the viewport position from the Editor, if available for example when -- run as an editor Test Level. function Appkit.get_editor_view_position() if Appkit.boxed_editor_view_position then return Appkit.boxed_editor_view_position:unbox() end return nil end -- Returns the viewport rotation from the Editor, if available for example when -- run as an editor Test Level. function Appkit.get_editor_view_rotation() if Appkit.boxed_editor_view_rotation then return Appkit.boxed_editor_view_rotation:unbox() end return nil end -- Main initialization hook for the appkit. Must be called to use the Appkit. Note -- this is automatically called by BasicProject, the default fallback project -- that is used if the user project does not implement the main engine lua hooks. function Appkit.setup_application() -- Window does not exist on some platforms if Window and Util.use_touch() then Window.set_show_cursor(true) end Appkit.input_mapper = InputMapper() --Support for the Editor Test Level if LEVEL_EDITOR_TEST then Appkit.test_level_resource_name = "__level_editor_test" Appkit.test_level_name = Application.get_data([[LevelEditing]], [[level_resource_name]]) if not Appkit.test_level_name then Appkit.test_level_name = "" -- untitled, unsaved level end if Application.has_data("LevelEditor", "camera", "position") then Appkit.boxed_editor_view_position = stingray.Vector3Box(Application.get_data("LevelEditor", "camera", "position")) end if Application.has_data("LevelEditor", "camera", "rotation") then Appkit.boxed_editor_view_rotation = stingray.QuaternionBox(Application.get_data("LevelEditor", "camera", "rotation")) end if stingray.Application.can_get("package", Appkit.test_level_resource_name) then Application.autoload_resource_package(Appkit.test_level_resource_name) end if LEVEL_EDITOR_TEST_LEVEL_NAME then if stingray.Application.can_get("package", LEVEL_EDITOR_TEST_LEVEL_NAME) then Application.autoload_resource_package(Appkit.test_level_resource_name) end end else if Window then Window.set_focus() Window.set_mouse_focus(true) end end if Appkit.plugins then for k,v in pairs(Appkit.plugins) do if k.init then k:init() end end end Appkit.was_setup = true end local function update_pending_level_change() if not Appkit.pending_level_change.next_level_resource_name then return end local current_level = Appkit.pending_level_change.current_level local world = Level.world(current_level) -- unload current level if Appkit.custom_unload_level_function then Appkit.custom_unload_level_function(current_level) else local level_wrapper = Appkit.get_managed_level_wrapper(current_level) if level_wrapper then level_wrapper:shutdown() Appkit.unmanage_level(current_level) end World.destroy_level(world, current_level) end Appkit.pending_level_change.current_level = nil -- load next level if Appkit.custom_load_level_function then Appkit.custom_load_level_function(Appkit.pending_level_change.next_level_resource_name) else local next_level = World.load_level(world, Appkit.pending_level_change.next_level_resource_name) Appkit.manage_level(next_level, true) end Appkit.pending_level_change.next_level_resource_name = nil end -- Must be called to use the Appkit. Note -- this is automatically called by BasicProject, the default fallback project -- that is used if the user project does not implement the main engine lua hooks. function Appkit.update(dt) Appkit.input_mapper:update(dt) -- update editor Test Level support if LEVEL_EDITOR_TEST then local f5_pressed = Keyboard.pressed(Keyboard.button_id('f5')) local esc_pressed = Keyboard.pressed(Keyboard.button_id('esc')) if f5_pressed or esc_pressed or (Window and (Window.is_closing() or Window.id() == 0)) then print("Stopping test level.") Application.console_send { type = 'stop_testing' } return end end update_pending_level_change() ComponentManager.update_managers(dt, ComponentManager.update_pre_world) if scaleform then -- Update advances all active scenes scaleform.Stingray.update() if Appkit.Util.use_touch() then Appkit.Scaleform.send_touch_input() end if Appkit.Util.is_pc() then Appkit.Scaleform.send_mouse_input() Appkit.Scaleform.send_keyboard_input() end end -- Make a local copy of managed levels to avoid issues with modifying -- the table during iteration. local managed = {} for k,v in pairs(Appkit.managed_levels) do managed[k] = v end for _, level_wrapper in pairs(managed) do level_wrapper:update(dt) end local managed_world = Appkit.managed_world if managed_world then managed_world:update(dt) end ComponentManager.update_managers(dt, ComponentManager.update_post_world) if Appkit.plugins then for k,v in pairs(Appkit.plugins) do if k.update then k:update(dt) end end end end -- Must be called to use the Appkit. Note -- this is automatically called by BasicProject, the default fallback project -- that is used if the user project does not implement the main engine lua hooks. function Appkit.render(window) local managed_world = Appkit.managed_world if managed_world then managed_world:render(window) end end -- Appkit shutdowns managed objects so call it before destroying things like levels, worlds -- which were given to Appkit to manage. Must be called to use the Appkit. Note -- this is automatically called by BasicProject, the default fallback project -- that is used if the user project does not implement the main engine lua hooks. function Appkit.shutdown() if Appkit.plugins then for k,v in pairs(Appkit.plugins) do if k.shutdown then k:shutdown() end end end for level, level_wrapper in pairs(Appkit.managed_levels) do ComponentManager.notify_managers_level_shutdown(level) level_wrapper:shutdown() end Appkit.managed_levels = {} ComponentManager.shutdown_managers() local managed_world = Appkit.managed_world if managed_world then managed_world:shutdown() Appkit.managed_world = nil end end -- Appkit will handle world update, camera update, render, debug Print To Screen functionality. -- you can pass an optional viewport template parameter used for this world function Appkit.manage_world(world, viewport) if not world then print "Error in Appkit.manage_world. world is nil." return end local world_wrapper = WorldWrapper(world, viewport) Appkit.managed_world = world_wrapper if Appkit.plugins then for k,v in pairs(Appkit.plugins) do if k.update then k:manage_world(world) end end end return world_wrapper end -- Returns an Appkit.WorldWrapper or nil if world is not managed -- todo multiple worlds function Appkit.get_managed_world_wrapper(world) local managed_world = Appkit.managed_world if managed_world.world == world then return managed_world end return nil end function Appkit.set_shading_environment(world, env, name) local managed_world = Appkit.managed_world if managed_world and world == managed_world.world then managed_world:set_shading_environment(env, name) else World.set_shading_environment(world, env, name) end end -- Appkit will handle shading environment init, level background init, and -- trigger of per-frame Level Update flow node. -- level_name necessary for lightmap loading. It should match the original level -- file resource name (not the test level resource name). function Appkit.manage_level(level, level_name, should_set_shading_env) if not level then print "Error in Appkit.manage_level. level is nil." return end local level_wrapper = LevelWrapper(level, level_name, (should_set_shading_env == nil) or should_set_shading_env) Appkit.managed_levels[level] = level_wrapper return level_wrapper end function Appkit.unmanage_level(level) local managed_level = Appkit.managed_levels[level] if managed_level then Appkit.managed_levels[level] = nil end end -- Returns an Appkit.LevelWrapper. Will return nil if level is not being managed by Appkit. function Appkit.get_managed_level_wrapper(level) return Appkit.managed_levels[level] end function Appkit.manage_level_object(level, type, object) local level_wrapper = Appkit.managed_levels[level] if level_wrapper then level_wrapper:manage_object(type, object) else print "Error in Appkit.manage_level_object. Given level is not managed by appkit." end end function Appkit.unmanage_level_object(level, type, object) local level_wrapper = Appkit.managed_levels[level] if level_wrapper then level_wrapper:unmanage_object(type, object) else print "Error in Appkit.unmanage_level_object. Given level is not managed by appkit." end end -- Triggers the Level Loaded flow node in the level flow. This is exposed as a -- separate call to allow the Project to determine when a level is considered -- "loaded". Note ths is automatically called by the Appkit default BasicProject, when used. function Appkit.trigger_level_loaded_flow(level) local level_wrapper = Appkit.managed_levels[level] if level_wrapper then level_wrapper:trigger_level_loaded_flow() else Level.trigger_level_loaded(level) end end -- Triggers a level change from the given current_level to the next level. -- This is used by the Appkit's "Change Level" flow node. function Appkit.set_pending_level_change(current_level, next_level_resource_name) if not current_level then print "Error in Appkit.set_pending_level_change: passed in current_level is nil." return end Appkit.pending_level_change.current_level = current_level Appkit.pending_level_change.next_level_resource_name = next_level_resource_name end function Appkit.register_plugin(plugin) if Appkit.plugins == nil then Appkit.plugins = {} end if Appkit.plugins[plugin] == nil then Appkit.plugins[plugin] = true if Appkit.was_setup and plugin.init then plugin:init() end end end return Appkit