2019-06-09 00:55:25 +02:00
|
|
|
local Event = require 'utils.event'
|
|
|
|
local Token = require 'utils.token'
|
|
|
|
local Task = require 'utils.task'
|
|
|
|
local Global = require 'utils.global'
|
2019-06-10 15:15:14 +02:00
|
|
|
local Command = require 'utils.command'
|
|
|
|
local Debug = require 'utils.debug'
|
2019-06-10 16:11:12 +02:00
|
|
|
local Gui = require 'utils.gui'
|
2019-06-10 15:15:14 +02:00
|
|
|
|
|
|
|
local set_timeout_in_ticks = Task.set_timeout_in_ticks
|
|
|
|
local debug_print = Debug.print
|
|
|
|
|
|
|
|
local is_valid_sound_path
|
|
|
|
local get_player
|
2019-06-09 00:55:25 +02:00
|
|
|
|
2019-06-10 16:11:12 +02:00
|
|
|
local skip_btn_name = Gui.uid_name()
|
|
|
|
|
2019-06-09 00:55:25 +02:00
|
|
|
local Public = {}
|
|
|
|
local handler
|
|
|
|
|
|
|
|
local cutscene_functions = {}
|
|
|
|
local running_cutscenes = {}
|
2019-06-10 16:32:13 +02:00
|
|
|
local replay = {
|
|
|
|
identifier = nil,
|
|
|
|
final_transition_time = nil
|
|
|
|
}
|
2019-06-09 00:55:25 +02:00
|
|
|
Global.register(
|
|
|
|
{
|
|
|
|
cutscene_functions = cutscene_functions,
|
2019-06-10 15:15:14 +02:00
|
|
|
running_cutscenes = running_cutscenes,
|
|
|
|
replay = replay
|
2019-06-09 00:55:25 +02:00
|
|
|
},
|
|
|
|
function(tbl)
|
|
|
|
cutscene_functions = tbl.cutscene_functions
|
|
|
|
running_cutscenes = tbl.running_cutscenes
|
2019-06-10 15:15:14 +02:00
|
|
|
replay = tbl.replay
|
2019-06-09 00:55:25 +02:00
|
|
|
end
|
|
|
|
)
|
|
|
|
|
|
|
|
local function valid(entity)
|
|
|
|
return entity and entity.valid
|
|
|
|
end
|
|
|
|
|
2019-06-10 15:15:14 +02:00
|
|
|
local play_sound_delayed =
|
|
|
|
Token.register(
|
|
|
|
function(params)
|
|
|
|
params.player.play_sound {path = params.path}
|
|
|
|
end
|
|
|
|
)
|
|
|
|
|
|
|
|
function Public.play_sound(player, path, times, delay, initial_delay)
|
|
|
|
if not is_valid_sound_path(path) then
|
|
|
|
debug_print('Provided SoundPath is invalid. Try opening /radio and browse for a valid path')
|
|
|
|
return
|
|
|
|
end
|
|
|
|
times = times or 1
|
|
|
|
if times == 1 and not delay and initial_delay then
|
|
|
|
delay = initial_delay
|
|
|
|
end
|
|
|
|
if times > 1 or delay then
|
|
|
|
delay = delay or 20
|
|
|
|
initial_delay = initial_delay or 0
|
|
|
|
for i = 1, times, 1 do
|
|
|
|
set_timeout_in_ticks(initial_delay + delay * i, play_sound_delayed, {player = player, path = path})
|
2019-06-10 01:31:26 +02:00
|
|
|
end
|
2019-06-10 15:15:14 +02:00
|
|
|
else
|
|
|
|
player.play_sound {path = path}
|
2019-06-10 01:31:26 +02:00
|
|
|
end
|
2019-06-10 15:15:14 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
local remove_renderings =
|
|
|
|
Token.register(
|
|
|
|
function(renderings)
|
|
|
|
for _, v in pairs(renderings) do
|
|
|
|
if rendering.is_valid(v) then
|
|
|
|
rendering.destroy(v)
|
|
|
|
debug_print('Deleted rendering with id: ' .. v)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
)
|
2019-06-10 01:31:26 +02:00
|
|
|
|
2019-06-09 00:55:25 +02:00
|
|
|
---Asserts if a given variable is of the expected type using type().
|
|
|
|
---
|
|
|
|
---@param expected_type string
|
|
|
|
---@param given any
|
|
|
|
---@param variable_reference_message string displayed when the expectation is not met
|
2019-06-10 15:15:14 +02:00
|
|
|
local function assert_type(expected_type, given, variable_reference_message, allow_nil)
|
2019-06-09 00:55:25 +02:00
|
|
|
local given_type = type(given)
|
2019-06-10 15:15:14 +02:00
|
|
|
if given_type ~= expected_type and (allow_nil and given_type ~= 'nil') then
|
2019-06-09 00:55:25 +02:00
|
|
|
error('Argument ' .. variable_reference_message .. " must be of type '" .. expected_type .. "', given '" .. given_type .. "'")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-06-09 23:59:16 +02:00
|
|
|
function Public.register_cutscene_function(identifier, waypoints, func, terminate_func)
|
2019-06-09 00:55:25 +02:00
|
|
|
assert_type('string', identifier, 'identifier of function cutscene_controller.register_cutscene_function')
|
|
|
|
assert_type('table', waypoints, 'waypoints of function cutscene_controller.register_cutscene_function')
|
2019-06-10 15:15:14 +02:00
|
|
|
assert_type('number', func, 'func of function cutscene_controller.register_cutscene_function')
|
|
|
|
assert_type('number', terminate_func, 'func of function cutscene_controller.register_cutscene_function', true)
|
2019-06-09 00:55:25 +02:00
|
|
|
|
2019-06-09 23:59:16 +02:00
|
|
|
cutscene_functions[identifier] = {func = func, waypoints = waypoints, update = false, terminate_func = terminate_func}
|
2019-06-09 00:55:25 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
function Public.register_running_cutscene(player_index, identifier, final_transition_time)
|
|
|
|
assert_type('number', player_index, 'player_index of function cutscene_controller.register_running_cutscene')
|
|
|
|
assert_type('string', identifier, 'identifier of function cutscene_controller.register_running_cutscene')
|
2019-06-10 15:15:14 +02:00
|
|
|
assert_type('number', final_transition_time, 'identifier of function cutscene_controller.register_running_cutscene', true)
|
2019-06-09 00:55:25 +02:00
|
|
|
|
2019-06-10 15:15:14 +02:00
|
|
|
local player = get_player(player_index)
|
2019-06-09 00:55:25 +02:00
|
|
|
if not valid(player) then
|
|
|
|
return
|
|
|
|
end
|
2019-06-09 12:26:51 +02:00
|
|
|
|
2019-06-09 00:55:25 +02:00
|
|
|
local cutscene_function = cutscene_functions[identifier]
|
|
|
|
if not cutscene_function then
|
|
|
|
return
|
|
|
|
end
|
2019-06-09 23:59:16 +02:00
|
|
|
|
2019-06-09 00:55:25 +02:00
|
|
|
local waypoints = cutscene_function.waypoints
|
|
|
|
if not waypoints then
|
|
|
|
return
|
|
|
|
end
|
2019-06-09 23:59:16 +02:00
|
|
|
|
2019-06-10 15:15:14 +02:00
|
|
|
if running_cutscenes[player_index] then
|
|
|
|
player.print({'cutscene_controller.cannot_start_new'})
|
2019-06-10 01:31:26 +02:00
|
|
|
return
|
|
|
|
end
|
|
|
|
|
2019-06-09 23:59:16 +02:00
|
|
|
running_cutscenes[player_index] = {
|
|
|
|
func = cutscene_function.func,
|
|
|
|
waypoints = waypoints,
|
|
|
|
update = cutscene_function.update,
|
|
|
|
final_transition_time = final_transition_time,
|
|
|
|
character = player.character,
|
|
|
|
terminate_func = cutscene_function.terminate_func,
|
|
|
|
rendering = {}
|
|
|
|
}
|
2019-06-10 15:15:14 +02:00
|
|
|
local running_cutscene = running_cutscenes[player_index]
|
|
|
|
|
2019-06-09 23:59:16 +02:00
|
|
|
if player.controller_type == defines.controllers.cutscene then
|
2019-06-10 15:15:14 +02:00
|
|
|
debug_print('' .. player.name .. ' was already in another cutscene not controlled by this module. It has been stopped')
|
2019-06-09 23:59:16 +02:00
|
|
|
player.exit_cutscene()
|
|
|
|
end
|
|
|
|
player.set_controller {type = defines.controllers.ghost}
|
|
|
|
|
2019-06-09 00:55:25 +02:00
|
|
|
final_transition_time = final_transition_time >= 0 and final_transition_time or 60
|
2019-06-10 15:15:14 +02:00
|
|
|
running_cutscene.final_transition_time = final_transition_time
|
|
|
|
running_cutscene.identifier = identifier
|
2019-06-09 00:55:25 +02:00
|
|
|
player.set_controller {
|
|
|
|
type = defines.controllers.cutscene,
|
|
|
|
waypoints = waypoints,
|
|
|
|
final_transition_time = final_transition_time
|
|
|
|
}
|
|
|
|
|
2019-06-10 16:11:12 +02:00
|
|
|
local btn = player.gui.top.add {type = 'sprite-button', name = skip_btn_name, caption = 'Skip cutscene'}
|
|
|
|
btn.style.minimal_height = 28
|
|
|
|
btn.style.minimal_width = 150
|
|
|
|
btn.style.font = 'default-large-bold'
|
|
|
|
btn.style.font_color = {r = 255, g = 215, b = 0}
|
|
|
|
running_cutscene.btn = btn
|
|
|
|
|
2019-06-09 00:55:25 +02:00
|
|
|
handler({player_index = player_index, waypoint_index = -1})
|
|
|
|
end
|
|
|
|
|
|
|
|
local function restart_cutscene(player_index, waypoints, start_index)
|
|
|
|
local current_running = running_cutscenes[player_index]
|
|
|
|
local final_transition_time = current_running.final_transition_time
|
|
|
|
current_running.update = false
|
2019-06-09 12:26:51 +02:00
|
|
|
local character = current_running.character
|
|
|
|
|
2019-06-10 15:15:14 +02:00
|
|
|
if not character then
|
|
|
|
log('Player index: ' .. player_index .. ' managed to lose their character in a cutscene')
|
|
|
|
end
|
|
|
|
|
2019-06-09 12:26:51 +02:00
|
|
|
local end_waypoint = {
|
|
|
|
-- end waypoint
|
|
|
|
position = character.position,
|
|
|
|
transition_time = final_transition_time,
|
|
|
|
time_to_wait = 1,
|
|
|
|
zoom = 1,
|
|
|
|
terminate = true
|
|
|
|
}
|
|
|
|
|
|
|
|
table.insert(waypoints, end_waypoint)
|
2019-06-09 00:55:25 +02:00
|
|
|
|
|
|
|
running_cutscenes[player_index] = {
|
|
|
|
func = current_running.func,
|
|
|
|
waypoints = waypoints,
|
|
|
|
update = false,
|
|
|
|
final_transition_time = final_transition_time,
|
2019-06-09 23:59:16 +02:00
|
|
|
character = character,
|
|
|
|
terminate_func = current_running.terminate_func,
|
2019-06-10 16:11:12 +02:00
|
|
|
rendering = current_running.rendering,
|
|
|
|
btn = current_running.btn
|
2019-06-09 00:55:25 +02:00
|
|
|
}
|
|
|
|
|
2019-06-10 15:15:14 +02:00
|
|
|
debug_print('Updating cutscene for player_index ' .. player_index)
|
|
|
|
debug_print(running_cutscenes[player_index])
|
|
|
|
|
|
|
|
local player = get_player(player_index)
|
2019-06-09 00:55:25 +02:00
|
|
|
if not valid(player) then
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
if player.controller_type == defines.controllers.cutscene then
|
2019-06-09 12:26:51 +02:00
|
|
|
player.exit_cutscene()
|
2019-06-09 00:55:25 +02:00
|
|
|
player.set_controller {type = defines.controllers.ghost}
|
|
|
|
end
|
|
|
|
|
|
|
|
player.set_controller {
|
|
|
|
type = defines.controllers.cutscene,
|
|
|
|
waypoints = waypoints,
|
|
|
|
final_transition_time = final_transition_time
|
|
|
|
}
|
|
|
|
|
|
|
|
if start_index then
|
|
|
|
player.jump_to_cutscene_waypoint(start_index + 1)
|
|
|
|
else
|
|
|
|
start_index = -1
|
|
|
|
end
|
|
|
|
|
|
|
|
handler({player_index = player_index, waypoint_index = start_index})
|
|
|
|
end
|
|
|
|
|
|
|
|
function Public.inject_waypoint(player_index, waypoint, waypoint_index, override)
|
|
|
|
local running_cutscene = running_cutscenes[player_index]
|
|
|
|
if not running_cutscene then
|
|
|
|
return
|
|
|
|
end
|
|
|
|
local waypoints = running_cutscene.waypoints
|
|
|
|
if not waypoints then
|
|
|
|
return
|
|
|
|
end
|
|
|
|
local copy_waypoints = {}
|
|
|
|
for i = 1, #waypoints do
|
|
|
|
table.insert(copy_waypoints, waypoints[i])
|
|
|
|
end
|
|
|
|
if override then
|
|
|
|
copy_waypoints[waypoint_index] = waypoint
|
|
|
|
else
|
|
|
|
table.insert(copy_waypoints, waypoint_index, waypoint)
|
|
|
|
end
|
2019-06-10 15:15:14 +02:00
|
|
|
running_cutscene.update = copy_waypoints
|
2019-06-09 00:55:25 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
local callback_function =
|
|
|
|
Token.register(
|
|
|
|
function(params)
|
2019-06-10 16:11:12 +02:00
|
|
|
local player_index = params.player_index
|
|
|
|
if (running_cutscenes[player_index]) then
|
|
|
|
Token.get(params.func)(player_index, params.waypoint_index, params.params)
|
|
|
|
else
|
|
|
|
debug_print('Skipping callback function. Cutscene got terminated!')
|
|
|
|
end
|
2019-06-09 00:55:25 +02:00
|
|
|
end
|
|
|
|
)
|
|
|
|
|
|
|
|
local reconnect_character =
|
|
|
|
Token.register(
|
|
|
|
function(params)
|
2019-06-09 23:59:16 +02:00
|
|
|
local player_index = params.player_index
|
2019-06-10 15:15:14 +02:00
|
|
|
local player = get_player(player_index)
|
2019-06-10 16:11:12 +02:00
|
|
|
local running_cutscene = params.running_cutscene
|
|
|
|
local character = running_cutscene.character
|
|
|
|
local func = running_cutscene.terminate_func
|
2019-06-09 12:26:51 +02:00
|
|
|
if valid(player) and valid(character) then
|
|
|
|
player.exit_cutscene()
|
|
|
|
player.set_controller {type = defines.controllers.character, character = character}
|
2019-06-09 23:59:16 +02:00
|
|
|
if func then
|
|
|
|
Token.get(func)(player_index)
|
|
|
|
end
|
2019-06-10 16:11:12 +02:00
|
|
|
Token.get(remove_renderings)(running_cutscene.rendering)
|
|
|
|
running_cutscene.btn.destroy()
|
2019-06-09 23:59:16 +02:00
|
|
|
running_cutscenes[player_index] = nil
|
2019-06-09 12:26:51 +02:00
|
|
|
end
|
2019-06-09 00:55:25 +02:00
|
|
|
end
|
|
|
|
)
|
|
|
|
|
2019-06-09 12:26:51 +02:00
|
|
|
function Public.terminate_cutscene(player_index, ticks)
|
2019-06-09 00:55:25 +02:00
|
|
|
local running_cutscene = running_cutscenes[player_index]
|
|
|
|
if not running_cutscene then
|
|
|
|
return
|
|
|
|
end
|
2019-06-09 12:26:51 +02:00
|
|
|
ticks = ticks and ticks or 1
|
2019-06-10 15:15:14 +02:00
|
|
|
debug_print('Terminating cutscene in ' .. ticks .. ' Ticks')
|
2019-06-09 00:55:25 +02:00
|
|
|
|
2019-06-10 15:15:14 +02:00
|
|
|
set_timeout_in_ticks(
|
2019-06-09 12:26:51 +02:00
|
|
|
ticks,
|
|
|
|
reconnect_character,
|
|
|
|
{
|
|
|
|
player_index = player_index,
|
2019-06-10 16:11:12 +02:00
|
|
|
running_cutscene = running_cutscene
|
2019-06-09 12:26:51 +02:00
|
|
|
}
|
|
|
|
)
|
2019-06-09 00:55:25 +02:00
|
|
|
end
|
|
|
|
|
2019-06-09 23:59:16 +02:00
|
|
|
function Public.register_rendering_id(player_index, render_id)
|
|
|
|
if type(render_id) ~= 'table' then
|
|
|
|
render_id = {render_id}
|
|
|
|
end
|
|
|
|
local running_cutscene = running_cutscenes[player_index]
|
|
|
|
for _, id in pairs(render_id) do
|
|
|
|
if rendering.is_valid(id) then
|
|
|
|
if not running_cutscene then
|
2019-06-10 15:15:14 +02:00
|
|
|
debug_print('The rendering with id ' .. id .. ' was not added. Destroying it instead')
|
2019-06-09 23:59:16 +02:00
|
|
|
rendering.destroy(id)
|
|
|
|
else
|
2019-06-10 15:15:14 +02:00
|
|
|
table.insert(running_cutscene.rendering, id)
|
2019-06-09 23:59:16 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-06-10 15:15:14 +02:00
|
|
|
function Public.register_replay(identifier, final_transition_time)
|
2019-06-10 16:32:13 +02:00
|
|
|
replay.identifier = identifier
|
|
|
|
replay.final_transition_time = final_transition_time
|
|
|
|
debug_print('Identifier ' .. identifier .. ' registered as replay cutscene')
|
2019-06-10 15:15:14 +02:00
|
|
|
end
|
|
|
|
|
2019-06-09 00:55:25 +02:00
|
|
|
handler = function(event)
|
|
|
|
local player_index = event.player_index
|
|
|
|
local waypoint_index = event.waypoint_index
|
|
|
|
|
2019-06-10 15:15:14 +02:00
|
|
|
debug_print('Waypoint_index ' .. waypoint_index .. ' has finished')
|
2019-06-09 00:55:25 +02:00
|
|
|
|
|
|
|
local running_cutscene = running_cutscenes[player_index]
|
|
|
|
if not running_cutscene then
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
local update = running_cutscene.update
|
|
|
|
if update then
|
|
|
|
restart_cutscene(player_index, update, waypoint_index)
|
|
|
|
return
|
|
|
|
end
|
|
|
|
local ticks = running_cutscene.waypoints[waypoint_index + 2]
|
|
|
|
if ticks then
|
|
|
|
ticks = ticks.transition_time
|
|
|
|
else
|
2019-06-10 15:15:14 +02:00
|
|
|
ticks = running_cutscene.final_transition_time
|
2019-06-09 00:55:25 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
local func = running_cutscene.func
|
|
|
|
if not func then
|
|
|
|
return
|
|
|
|
end
|
|
|
|
local current_waypoint = running_cutscene.waypoints[waypoint_index + 2]
|
2019-06-09 12:26:51 +02:00
|
|
|
if not current_waypoint or current_waypoint.terminate then
|
|
|
|
Public.terminate_cutscene(player_index, ticks)
|
|
|
|
return
|
2019-06-09 00:55:25 +02:00
|
|
|
end
|
|
|
|
local params = {
|
|
|
|
position = current_waypoint.position,
|
|
|
|
time_to_wait = current_waypoint.time_to_wait,
|
|
|
|
transition_time = current_waypoint.transition_time,
|
|
|
|
zoom = current_waypoint.zoom
|
|
|
|
}
|
|
|
|
|
2019-06-10 15:15:14 +02:00
|
|
|
debug_print('Waypoint_index ' .. waypoint_index + 1 .. ' (waypoint #' .. waypoint_index + 2 .. ') callback in ' .. ticks .. ' ticks')
|
|
|
|
|
|
|
|
set_timeout_in_ticks(ticks, callback_function, {func = running_cutscene.func, player_index = player_index, waypoint_index = waypoint_index, params = params})
|
2019-06-09 00:55:25 +02:00
|
|
|
end
|
|
|
|
|
2019-06-09 12:26:51 +02:00
|
|
|
local function restore(event)
|
|
|
|
Public.terminate_cutscene(event.player_index)
|
|
|
|
end
|
|
|
|
|
2019-06-09 00:55:25 +02:00
|
|
|
Event.add(defines.events.on_cutscene_waypoint_reached, handler)
|
2019-06-09 12:26:51 +02:00
|
|
|
Event.add(defines.events.on_pre_player_left_game, restore)
|
|
|
|
Event.add(defines.events.on_player_joined_game, restore)
|
2019-06-09 00:55:25 +02:00
|
|
|
|
2019-06-10 15:15:14 +02:00
|
|
|
Event.on_init(
|
|
|
|
function()
|
|
|
|
is_valid_sound_path = game.is_valid_sound_path
|
|
|
|
get_player = game.get_player
|
|
|
|
end
|
|
|
|
)
|
|
|
|
|
|
|
|
local replay_cutscene =
|
|
|
|
Token.register(
|
|
|
|
function(params)
|
|
|
|
Public.register_running_cutscene(params.event.player_index, replay.identifier, replay.final_transition_time)
|
|
|
|
end
|
|
|
|
)
|
|
|
|
|
|
|
|
local function replay_handler(_, player)
|
|
|
|
if not replay.identifier then
|
|
|
|
player.print({'cutscene_controller.cannot_replay'})
|
|
|
|
return
|
|
|
|
end
|
|
|
|
Token.get(replay_cutscene)({event = {player_index = player.index}})
|
|
|
|
end
|
|
|
|
|
|
|
|
Command.add(
|
|
|
|
'replay',
|
|
|
|
{
|
|
|
|
description = {'cutscene_controller.replay'},
|
|
|
|
capture_excess_arguments = false,
|
|
|
|
allowed_by_server = false
|
|
|
|
},
|
|
|
|
replay_handler
|
|
|
|
)
|
|
|
|
|
|
|
|
local function skip_cutscene(_, player)
|
|
|
|
if not player or not player.valid then
|
|
|
|
return
|
|
|
|
end
|
|
|
|
if player.controller_type == defines.controllers.cutscene then
|
|
|
|
Public.terminate_cutscene(player.index)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
Command.add(
|
|
|
|
'skip',
|
|
|
|
{
|
|
|
|
description = {'cutscene_controller.skip'},
|
|
|
|
capture_excess_arguments = false,
|
|
|
|
allowed_by_server = false
|
|
|
|
},
|
|
|
|
skip_cutscene
|
|
|
|
)
|
|
|
|
|
2019-06-10 16:11:12 +02:00
|
|
|
Gui.on_click(
|
|
|
|
skip_btn_name,
|
|
|
|
function(event)
|
|
|
|
skip_cutscene(nil, get_player(event.player_index))
|
|
|
|
end
|
|
|
|
)
|
|
|
|
|
2019-06-09 00:55:25 +02:00
|
|
|
return Public
|