Add the intro cutscene, a start to the map, and various other things.
This commit is contained in:
parent
d0c0a3ebb6
commit
1b2199705b
46 changed files with 1401 additions and 91 deletions
|
|
@ -1,19 +1,49 @@
|
|||
artifact.story = {
|
||||
states = enum { -- We use an enum for this so that we can use relational operators to determine if the current state is before or after a target state.
|
||||
"loading", -- For mapgen
|
||||
"init", -- For the opening cutscene
|
||||
"pre_vix", -- The player doesn't have Vix yet
|
||||
"loading", -- Mapgen is not yet complete.
|
||||
"init", -- For the opening cutscene.
|
||||
"pre_vix", -- The player doesn't have Vix yet.
|
||||
"main", -- The main game state. Progress is managed by checkpoints here.
|
||||
"end", -- The game is over.
|
||||
},
|
||||
poi = {
|
||||
initial_cutscene = {
|
||||
start_pos = vector.new(0, 0, 0),
|
||||
end_pos = vector.new(0,0,0)
|
||||
}
|
||||
}
|
||||
}
|
||||
local ns = artifact.story
|
||||
local db = minetest.get_mod_storage()
|
||||
|
||||
local state = db:get_int("state") -- Defaults to zero, i.e. "loading".
|
||||
artifact.origin = vector.from_string(db:get("origin") or "(0,0,0)")
|
||||
|
||||
function ns.set_state(to)
|
||||
if artifact.debug then state = ns.states.main end
|
||||
|
||||
function ns.enter_init_state()
|
||||
ns.play_intro_cutscene()
|
||||
end
|
||||
|
||||
|
||||
function ns.enter_pre_vix_state()
|
||||
for _, m in pairs(artifact.players) do
|
||||
m:add_health_bar()
|
||||
m.object:hud_set_flags {
|
||||
crosshair = true,
|
||||
wielditem = true,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
function ns.enter_state(to)
|
||||
state = to
|
||||
minetest.log("State changed to "..to..".")
|
||||
if state == ns.states.init then
|
||||
ns.enter_init_state()
|
||||
elseif state == ns.states.pre_vix then
|
||||
ns.enter_pre_vix_state()
|
||||
end
|
||||
db:set_int("state", state)
|
||||
end
|
||||
|
||||
|
|
@ -21,12 +51,330 @@ function ns.get_state()
|
|||
return state
|
||||
end
|
||||
|
||||
function ns.before_state(target)
|
||||
|
||||
-- Used for marking the start position in schematics.
|
||||
-- Disappears when not in debug mode.
|
||||
artifact.register_node("start_pos", {
|
||||
drawtype = not artifact.debug and "airlike" or nil,
|
||||
paramtype = "light",
|
||||
walkable = artifact.debug or false,
|
||||
pointable = artifact.debug or false,
|
||||
tiles = {artifact.debug and "artifact_start_pos.png" or "blank.png"}
|
||||
})
|
||||
|
||||
function artifact.look_at(m, pos, pos2)
|
||||
local rot = (pos2 and pos or m.object:get_pos()):direction(pos2 or pos):dir_to_rotation()
|
||||
m.object:set_look_horizontal(rot.y)
|
||||
-- Pitch seems to be flipped on the player?
|
||||
m.object:set_look_vertical(-rot.x)
|
||||
end
|
||||
|
||||
minetest.register_globalstep(function()
|
||||
if state == "init" then
|
||||
|
||||
|
||||
minetest.register_chatcommand("splash", {
|
||||
func = function(name)
|
||||
local m = artifact.players[name]
|
||||
minetest.show_formspec(m.name, "artifact:lock_camera", [[
|
||||
formspec_version[10]
|
||||
size[32,18]
|
||||
padding[0,0]
|
||||
bgcolor[#000]
|
||||
animated_image[0,0;32,18;;artifact_splash.png;60;100;;]
|
||||
]])
|
||||
end
|
||||
})
|
||||
|
||||
-- Play the opening cutscene.
|
||||
function ns.play_intro_cutscene()
|
||||
ns.camera = minetest.add_entity(artifact.origin:offset(0,-0.75,0), "display")
|
||||
ns.scene = minetest.add_entity(artifact.origin:offset(-2.25,-0.5,7 -1/16), "display")
|
||||
ns.scene:set_properties {
|
||||
visual = "mesh",
|
||||
mesh = "artifact_cutscene_a.gltf",
|
||||
textures = {"artifact_key.png", "artifact_statue.png"}
|
||||
}
|
||||
ns.scene:set_animation({x=0,y=25}, 1, 0.1, false)
|
||||
for _, m in pairs(artifact.players) do
|
||||
m.object:set_attach(ns.camera)
|
||||
minetest.show_formspec(m.name, "artifact:lock_camera", [[
|
||||
formspec_version[10]
|
||||
size[32,18]
|
||||
padding[0,0]
|
||||
allow_close[false]
|
||||
bgcolor[#0000]
|
||||
]])
|
||||
m.object:set_look_vertical(0)
|
||||
m.object:set_look_horizontal(0)
|
||||
end
|
||||
-- Begin mess.
|
||||
minetest.after(17, function()
|
||||
for x = -1, 1 do
|
||||
for z = -1, 1 do
|
||||
minetest.remove_node(artifact.origin:offset(x, -1, z -5))
|
||||
minetest.add_particlespawner {
|
||||
pos = {
|
||||
min = artifact.origin:offset(x -0.5, -1, z -5 -0.5),
|
||||
max = artifact.origin:offset(x +0.5, -0.5, z -5 +0.5)
|
||||
},
|
||||
vel = {
|
||||
min = vector.new(-1, 0, -1) *1.5,
|
||||
max = vector.new(1, 2, 1) *1.5
|
||||
},
|
||||
acc = vector.new(0,-9.81,0),
|
||||
collisiondetection = true,
|
||||
amount = 50,
|
||||
node = {name = "artifact:stone_tile_brown"},
|
||||
time = 0.1
|
||||
}
|
||||
end
|
||||
end
|
||||
end)
|
||||
minetest.after(3.5, function()
|
||||
-- Slowly move back as Key walks forward.
|
||||
ns.camera:set_acceleration(vector.new(0,0,-0.5))
|
||||
minetest.after(1, function()
|
||||
-- Decelerate before switching angles, for smoothness.
|
||||
ns.camera:set_acceleration(vector.new(0,0,0.3))
|
||||
end)
|
||||
minetest.after(2, function()
|
||||
ns.camera:set_acceleration(vector.zero())
|
||||
ns.camera:set_velocity(vector.zero())
|
||||
ns.camera:set_pos(artifact.origin:offset(-5, 3, -4))
|
||||
for _, m in pairs(artifact.players) do
|
||||
artifact.look_at(m, ns.camera:get_pos(), artifact.origin:offset(0,0,1))
|
||||
end
|
||||
local time = minetest.get_us_time()
|
||||
-- Pan to follow Key as he moves toward the pedestal.
|
||||
local function interpolate()
|
||||
local fac = (minetest.get_us_time() -time) /4000000
|
||||
local offset = artifact.interpolate(1, 4, fac)
|
||||
for _, m in pairs(artifact.players) do
|
||||
artifact.look_at(m, ns.camera:get_pos(), artifact.origin:offset(0,0,-offset))
|
||||
end
|
||||
-- Do a globalstep callback the lazy way.
|
||||
if fac < 1 then minetest.after(0, interpolate) end
|
||||
end
|
||||
minetest.after(0, interpolate)
|
||||
minetest.after(4, function()
|
||||
-- Dramatically move backward as Key stares at the statue.
|
||||
ns.camera:set_pos(artifact.origin:offset(-0.2, -0.5, -9))
|
||||
ns.camera:set_velocity(vector.new(0,0,-0.5))
|
||||
for _, m in pairs(artifact.players) do
|
||||
m.object:set_look_vertical(0)
|
||||
m.object:set_look_horizontal(0)
|
||||
end
|
||||
minetest.after(6, function()
|
||||
-- Cut back to where we were before, so we get a good view of Key falling in the hole.
|
||||
ns.camera:set_pos(artifact.origin:offset(-5, 3, -4))
|
||||
ns.camera:set_velocity(vector.new(0,0,0))
|
||||
for _, m in pairs(artifact.players) do
|
||||
artifact.look_at(m, ns.camera:get_pos(), artifact.origin:offset(0,0,-4))
|
||||
end
|
||||
minetest.after(3, function()
|
||||
-- Show epic splash animation while Key finishes falling down the hole.
|
||||
for _, m in pairs(artifact.players) do
|
||||
artifact.hud_add(m, {
|
||||
type = "image",
|
||||
name = "background",
|
||||
pos = {x=0.5,y=0.5},
|
||||
scale = {x=1000,y=1000},
|
||||
image = "[fill:16x16:0,0:#000",
|
||||
opacity = 0
|
||||
})
|
||||
m.hud.background:animate {
|
||||
opacity = {
|
||||
value = 256,
|
||||
duration = 0.3
|
||||
}
|
||||
}
|
||||
end
|
||||
minetest.after(0.3, function()
|
||||
for _, m in pairs(artifact.players) do
|
||||
minetest.show_formspec(m.name, "artifact:lock_camera", [[
|
||||
formspec_version[10]
|
||||
size[32,18]
|
||||
padding[0,0]
|
||||
allow_close[false]
|
||||
bgcolor[#0000]
|
||||
animated_image[0,0;32,18;;artifact_splash.png;60;100;;]
|
||||
]])
|
||||
end
|
||||
end)
|
||||
minetest.after(6.3, function()
|
||||
for _, m in pairs(artifact.players) do
|
||||
minetest.show_formspec(m.name, "artifact:lock_camera", [[
|
||||
formspec_version[10]
|
||||
size[32,18]
|
||||
padding[0,0]
|
||||
allow_close[false]
|
||||
bgcolor[#0000]
|
||||
]])
|
||||
m.hud.background:animate {
|
||||
opacity = {
|
||||
value = 0,
|
||||
duration = 0.3
|
||||
}
|
||||
}
|
||||
m.hud.background.remove_after = 0.3
|
||||
end
|
||||
ns.camera:set_pos(artifact.origin:offset(-1, -73, -6))
|
||||
ns.camera:set_velocity(vector.new(0,0,0))
|
||||
for _, m in pairs(artifact.players) do
|
||||
artifact.look_at(m, ns.camera:get_pos(), artifact.origin:offset(1, -74, -3))
|
||||
end
|
||||
ns.scene:remove()
|
||||
ns.scene = minetest.add_entity(artifact.origin:offset(-1, -73.5, -6), "display")
|
||||
ns.scene:set_properties {
|
||||
visual = "mesh",
|
||||
mesh = "artifact_cutscene_b.gltf",
|
||||
textures = {"artifact_key.png", "artifact_blackrod.png"}
|
||||
}
|
||||
minetest.after(0.3, function()
|
||||
artifact.push_chat_message("Ow.", "Key", "artifact_key_splash_low.png")
|
||||
minetest.after(1, function()
|
||||
ns.scene:set_animation({x=0,y=25}, 1, 0.1, false)
|
||||
end)
|
||||
minetest.after(9, function()
|
||||
artifact.push_chat_message("Interesting...", "Key", "artifact_key_splash_low.png")
|
||||
end)
|
||||
minetest.after(13, function()
|
||||
ns.scene:remove()
|
||||
for _, m in pairs(artifact.players) do
|
||||
m.object:set_detach()
|
||||
minetest.close_formspec(m.name, "artifact:lock_camera")
|
||||
m.object:set_pos(artifact.origin:offset(0, -73.5, -4))
|
||||
artifact.look_at(m, ns.camera:get_pos(), artifact.origin:offset(0, -73.5, -8))
|
||||
end
|
||||
ns.enter_state(ns.states.pre_vix)
|
||||
minetest.after(3, function()
|
||||
artifact.push_chat_message("Interesting...", "Key", "artifact_key_splash_low.png")
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
-- End mess.
|
||||
end
|
||||
|
||||
-- Do mapgen. This isn't technically story-related, but it's here
|
||||
-- anyway because we only need to do it for state == "loading"
|
||||
-- and it's the only mapgen we do.
|
||||
function ns.load_map()
|
||||
-- Notify the player that we must mapgen first.
|
||||
for _, m in pairs(artifact.players) do
|
||||
artifact.hud_add(m, {
|
||||
type = "image",
|
||||
name = "loading_map_bg",
|
||||
pos = {x=0.5,y=0.5},
|
||||
scale = {x=1000,y=1000}, -- Cover the whole window without having to get the window size.
|
||||
image = "[fill:16x16:0,0:#000"
|
||||
})
|
||||
artifact.hud_add(m, {
|
||||
type = "text",
|
||||
name = "loading_map",
|
||||
text = "Loading map...",
|
||||
pos = {x=0.5,y=0.5},
|
||||
size = {x=3,y=0},
|
||||
color = minetest.colorspec_to_table("#000")
|
||||
})
|
||||
m.hud.loading_map:animate {
|
||||
color = {
|
||||
value = minetest.colorspec_to_table("#888"),
|
||||
duration = 0.3,
|
||||
}
|
||||
}
|
||||
end
|
||||
-- Make sure the loading HUD fades in first.
|
||||
minetest.after(0.3, function()
|
||||
-- Emerge the area so we have something to write to.
|
||||
-- This is one of the relatively few cases where the
|
||||
-- Promise API is actually more than a little helpful,
|
||||
-- because we can simply 'yield' until the emerge is
|
||||
-- completely finished in a semi-clean way.
|
||||
Promise(function(r)
|
||||
minetest.emerge_area(vector.new(-1,-1,-1) *100, vector.new(1, 1, 1) *100, function(bpos, action, remaining) if remaining == 0 then r() end end)
|
||||
end).after(function()
|
||||
for _, m in pairs(artifact.players) do
|
||||
m.hud.loading_map:animate {
|
||||
color = {
|
||||
value = minetest.colorspec_to_table("#000"),
|
||||
duration = 0.3,
|
||||
}
|
||||
}
|
||||
end
|
||||
-- The mapgen code is here, but the actual world schematic should live in artifact_world.
|
||||
local path = minetest.get_modpath("artifact_world").."/schems/map"
|
||||
local pos1 = vector.new(-5,-7,-5)
|
||||
local pos2 = pos1 +artifact.get_schem_size(path)
|
||||
artifact.load_schematic(pos1, path)
|
||||
-- Wait a bit to make quite sure that the schematic is in place (and allow the HUD to fade out).
|
||||
minetest.after(0.3, function()
|
||||
local nodes = minetest.find_nodes_in_area(pos1, pos2, "start_pos")
|
||||
-- If we can't find a start marker, fall back to the global origin.
|
||||
local start = nodes[1] or vector.zero()
|
||||
artifact.origin = start
|
||||
db:set_string("origin", start:to_string())
|
||||
for _, m in pairs(artifact.players) do
|
||||
m.hud.loading_map:remove(m)
|
||||
m.hud.loading_map_bg:animate {
|
||||
opacity = {
|
||||
value = 0,
|
||||
duration = 0.3
|
||||
}
|
||||
}
|
||||
m.hud.loading_map_bg.remove_after = 0.3
|
||||
m.object:set_pos(start)
|
||||
end
|
||||
ns.enter_state(artifact.story.states.init)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
include "objectives.lua"
|
||||
|
||||
local started = false
|
||||
minetest.register_on_joinplayer(function(p)
|
||||
local m = artifact.players[p:get_player_name()]
|
||||
-- Only add the HUD etc. if the player is actually in the game.
|
||||
if state == ns.states.init then
|
||||
m.object:set_attach(ns.camera)
|
||||
minetest.show_formspec(m.name, "artifact:lock_camera", [[
|
||||
formspec_version[10]
|
||||
allow_close[false]
|
||||
bgcolor[#0000]
|
||||
]])
|
||||
elseif state > ns.states.init then
|
||||
m:add_health_bar()
|
||||
m.object:hud_set_flags {
|
||||
crosshair = true,
|
||||
wielditem = true
|
||||
}
|
||||
end
|
||||
-- So we only call this when the _first_ player joins.
|
||||
-- Sure, we're technically a singleplayer game, but,
|
||||
-- as they say, better to have it and not need it than
|
||||
-- need it and not have it.
|
||||
if not started then
|
||||
started = true
|
||||
if state == ns.states.loading then
|
||||
ns.load_map()
|
||||
elseif state == "init" then
|
||||
ns.play_intro_cutscene()
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
|
||||
if artifact.debug then
|
||||
minetest.register_chatcommand("map", {
|
||||
func = function()
|
||||
local path = minetest.get_modpath("artifact_world").."/schems/map"
|
||||
local pos1 = vector.new(-5,-7,-5)
|
||||
artifact.load_schematic(pos1, path)
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue