--[[
`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
if Appkit.plugins then
for k,v in pairs(Appkit.plugins) do
if k.render then k:render(window) end
end
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.manage_world 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