Compare commits

...

4 commits
v1 ... master

Author SHA1 Message Date
a7ed39131c Fix another crash. 2025-12-01 19:07:49 -05:00
c54579e426 Fix crash when rejoining as Vix. 2025-12-01 11:27:38 -05:00
85817a33b8 Fix a bug with state restoration. 2025-11-30 11:08:25 -05:00
9096c33a48 Add the rest of the game. 2025-11-29 17:45:59 -05:00
49 changed files with 892 additions and 81 deletions

BIN
menu/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 B

View file

@ -5,7 +5,7 @@ artifact = {
-- This toggles a lot of things, including whether initialization takes place,
-- whether nodes can be pointed, and whether the player is permitted to
-- bypass certain restrictions imposed by the story.
debug = true,
debug = false,
colors = {
gold = "#faf17a",
red = "#e94f3f",
@ -54,6 +54,25 @@ function Promise(fn)
}
end
function artifact.play_sound(def)
def.max_hear_distance = def.range
--def.gain = nil
if def.pos then
for _, m in pairs(artifact.players) do
local dist = vector.distance(m.pos, def.pos)
if dist <= (def.range or 32) and m.name ~= def.exclude_player and not (def.to_player and m.name ~= def.to_player) then
def.to_player = m.name
def.gain = (def.gain or 1) *(1 -math.sqrt(dist /(def.range or 32)))
minetest.sound_play(def, def)
end
end
else
minetest.sound_play(def.name, def)
end
end
-- HACK: Lookup table for getting a rotation from a
-- facedir (because Minetest doesn't have any way
-- to get this information normally)

View file

@ -34,7 +34,9 @@ function ns.apply_vix(m)
m.eye_height = 1.5
-- Switch hand appearance.
m.inv:set_stack("main", 1, ItemStack("input_"..m.character))
if m.healthbar then
m.object:hud_change(m.healthbar, "text", "artifact_heart_vix.png")
end
if m.blackrod then
m.blackrod:remove()
m.blackrod = nil
@ -42,6 +44,15 @@ function ns.apply_vix(m)
end
function ns._swap_character(m)
-- If Key was pointing at something Vix shouldn't be able to interact with,
-- but Vix is also pointing at it, then remove the interaction marker since
-- it would be misleading.
if m.pointed_obj and m.interaction_marker and (not m.pointed_obj._can_interact or m.pointed_obj:_can_interact(m)) then
m.object:hud_remove(m.interaction_marker)
m.interaction_marker = nil
m.interaction_start = nil
end
if m.character == "vix" then
artifact.sidekick.character = "vix"
m:set_character("key")
@ -52,6 +63,17 @@ function ns._swap_character(m)
ns.apply_vix(m)
end
-- If Vix was pointing at something whackable, and the player then switches
-- to Key who is pointing at the same thing, we should show the whack icon.
if m.character == "key" and m.pointed_node and minetest.registered_nodes[m.pointed_node.node_under.name].groups.whackable then
m.whack_hud = m.object:hud_add {
type = "image_waypoint",
world_pos = m.pointed_node.under,
scale = {x=3,y=3},
text = "artifact_icon_whack.png"
}
end
-- We don't need to have the sidekick entity during testing.
if artifact.sidekick.pos or not artifact.debug then
-- `m.pos` includes eye_height, and we don't want that here.
@ -124,6 +146,10 @@ function ns.swap_character(m)
duration = 0.3
}
}
artifact.play_sound {
name = "artifact_character_swap",
to_player = m.name
}
minetest.after(0.3, function()
ns._swap_character(m)
fade:animate {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1,005 B

After

Width:  |  Height:  |  Size: 1.9 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 749 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View file

@ -13,26 +13,28 @@ minetest.register_entity(":artifact:burst", {
},
static_save = false,
},
on_activate = function(e, rot)
on_activate = function(e)
e.object:set_armor_groups{immortal = 1}
end,
on_deactivate = function(e)
for _, x in ipairs(e._particles) do
for _, x in ipairs(e._particles or {}) do
minetest.delete_particlespawner(x)
end
end,
on_step = function(e, dtime, movement)
-- Minetest's collision is rather bad, but we also shouldn't collide with players,
-- hence we must implement our own rudimentary collision detection.
-- In this case, any intersection on the segment betwee our last position and our
-- In this case, any intersection on the segment between our last position and our
-- current one should be considered a collision and result in detonation (and
-- at the exact intersection point, if available).
local collision
if not e._critical then
for x in minetest.raycast(e.old_pos or e.object:get_pos(), e.object:get_pos(), true, true, {nodes = {}, objects = {playser = false}}) do
collision = x
break
end
if collision or movement.collides then
end
if collision or movement and movement.collides then
if collision then
e.object:set_pos(collision.intersection_point)
end
@ -61,6 +63,10 @@ minetest.register_entity(":artifact:burst", {
drag = 1,
time = 0.1,
}
artifact.play_sound {
name = "artifact_burst_impact",
pos = e.object:get_pos()
}
e.object:remove()
if movement and movement.collisions[1] and movement.collisions[1].type == "node" then
local pos = movement.collisions[1].node_pos
@ -116,6 +122,10 @@ minetest.register_entity(":artifact:burst", {
end
})
function ns.do_shoot(m)
minetest.add_entity(m.pos +m.dir, "artifact:burst", tostring(m.yaw)):get_luaentity():impulse(m.dir *30)
function ns.do_shoot(m, dir)
artifact.play_sound {
name = "artifact_burst_fire",
pos = dir and m or m.pos
}
minetest.add_entity(dir and m +dir or m.pos +m.dir, "artifact:burst"):get_luaentity():impulse((dir or m.dir) *30)
end

Binary file not shown.

Before

Width:  |  Height:  |  Size: 186 B

After

Width:  |  Height:  |  Size: 194 B

Before After
Before After

View file

@ -59,6 +59,11 @@ minetest.register_entity(":artifact:color_swapper_display", {
e._no_interact = true
e.object:set_animation({x=7,y=8}, 1, 1, false)
m:set_color(e.color)
artifact.play_sound {
name = "artifact_color_swap",
pos = e.object:get_pos(),
range = 8,
}
minetest.after(1, function()
if not e._active then return end
e.object:set_animation({x=0,y=6}, 1, 1, true)
@ -227,10 +232,11 @@ minetest.register_entity(":artifact:color_target_display", {
if m.color == e.color then
e._no_interact = true
m:set_color(nil)
local receivers = minetest.deserialize(minetest.get_meta(e.object:get_pos():round()):get("receivers") or "return nil")
if receivers then
artifact.dispatch_event(receivers, {type = "pulse"})
end
artifact.play_sound {
name = "artifact_color_use",
pos = e.object:get_pos(),
range = 8,
}
-- Update the entity's texture every globalstep, because that's the only way to do a texture animation.
local progress = 0
local delay = 0
@ -241,6 +247,10 @@ minetest.register_entity(":artifact:color_target_display", {
if progress >= 255 then
dir = false
minetest.after(1, update)
local receivers = minetest.deserialize(minetest.get_meta(e.object:get_pos():round()):get("receivers") or "return nil")
if receivers then
artifact.dispatch_event(receivers, {type = "pulse"})
end
return
end
else
@ -303,6 +313,7 @@ artifact.register_node("color_target", {
paramtype = "light",
paramtype2 = "facedir",
groups = {call_on_load = 1},
sounds = artifact.sounds.metal,
on_construct = target_onload,
on_load = target_onload,
on_destruct = function(pos)

View file

@ -73,7 +73,7 @@ minetest.register_entity(":artifact:door", {
if e._locked then
e._no_interact = true
end
if e.inverted then e:invert() end
if e.inverted or nm:get("inverted") == "true" then e:invert() end
doors[e.object:get_pos():round():to_string()] = e
end,
on_deactivate = function(e)
@ -200,6 +200,10 @@ minetest.register_entity(":artifact:door", {
texture = "artifact_door_wood.png^[sheet:2x8:0,3",
time = 0.1
}
artifact.play_sound {
name = "artifact_door_break",
pos = pos
}
return true
end
end

View file

@ -268,6 +268,7 @@ artifact.register_node("forcefield_generator", {
1, 0.5, 0.5
}
},
sounds = artifact.sounds.metal,
on_construct = onload,
on_load = onload,
on_rightclick = artifact.debug and function(pos)
@ -286,6 +287,10 @@ artifact.register_node("forcefield_generator", {
minetest.after(0, function()
minetest.set_node(pos, node)
end)
artifact.play_sound {
name = "artifact_forcefield_generator_destruct",
pos = pos,
}
minetest.add_particlespawner {
pos = pos,
vel = vector.zero(),
@ -338,6 +343,7 @@ artifact.register_node("forcefield_generator_off", {
1, 0.5, 0.5
}
},
sounds = artifact.sounds.metal,
on_impact = artifact.debug and function(pos)
local node = minetest.get_node(pos)
node.name = "forcefield_generator"

View file

@ -1,5 +1,6 @@
local large_doors = {}
artifact.large_doors = large_doors
minetest.register_entity(":artifact:large_door_display", {
initial_properties = {
@ -99,6 +100,23 @@ minetest.register_entity(":artifact:large_door_display", {
local pos = e.object:get_pos():round()
minetest.get_meta(pos):set_string("open", "true")
artifact.play_sound {
name = "artifact_large_door_open",
pos = e.object:get_pos(),
range = 10,
}
local m = minetest.get_meta(pos)
local on_open = m:get("on_open")
if not artifact.debug and on_open then
if on_open == "play_final_scene" then
artifact.story.play_final_scene()
elseif on_open == "play_end_scene" then
artifact.story.play_end_scene()
end
m:set_string("on_open", "")
end
minetest.after(0.75, function()
e._animating = nil
end)
@ -110,6 +128,13 @@ minetest.register_entity(":artifact:large_door_display", {
e.object:set_animation({x=1,y=2}, 1.25, 0.1, false)
local pos = e.object:get_pos():round()
minetest.get_meta(pos):set_string("open", "false")
artifact.play_sound {
name = "artifact_large_door_close",
pos = e.object:get_pos(),
range = 10,
}
minetest.after(0.75, function()
e._animating = nil
end)
@ -185,9 +210,9 @@ local function onload(pos)
if not m:contains("initialized") then
m:set_string("initialized", "true")
local rot = artifact.facedir_to_rotation(minetest.get_node(pos).param2)
local locks = {red = false, blue = false, green = false, "red", "green", "blue"}
local locks = minetest.deserialize(m:get("locks") or "return nil") or {red = false, blue = false, green = false, "red", "green", "blue"}
minetest.add_entity(pos, "artifact:large_door_display", minetest.serialize{locks = locks}):get_luaentity():rotate(rot)
minetest.get_meta(pos):set_string("locks", minetest.serialize(locks))
m:set_string("locks", minetest.serialize(locks))
end
end
@ -204,6 +229,47 @@ local function onsignal(pos, event, channel)
if locks[channel] ~= nil then
locks[channel] = true
end
artifact.play_sound {
name = "artifact_lock_light",
pos = e.object:get_pos(),
range = 10,
}
local idx = table.indexof(locks, channel)
local rot = artifact.facedir_to_rotation(minetest.get_node(pos).param2)
minetest.add_particlespawner {
pos = pos +vector.new(-0.6 +(idx *0.3), 2.25, -0.2):rotate(rot),
vel = {
min = vector.new(-1, 0, -1):rotate(rot),
max = vector.new(1, 2, -0.2):rotate(rot),
},
acc = vector.new(0, -9.81, 0),
texture = {
name = "[fill:1x1:0,0:"..artifact.colors[channel],
alpha_tween = {1, 0}
},
glow = 8,
time = 0.1,
size = 0.25,
amount = 20,
exptime = 0.3
}
minetest.add_particlespawner {
pos = pos +vector.new(-0.6 +(idx *0.3), 2.25, 0.2):rotate(rot),
vel = {
min = vector.new(-1, 0, 0.2):rotate(rot),
max = vector.new(1, 2, 1):rotate(rot),
},
acc = vector.new(0, -8, 0),
texture = {
name = "[fill:1x1:0,0:"..artifact.colors[channel],
alpha_tween = {1, 0}
},
glow = 8,
time = 0.1,
size = 0.25,
amount = 20,
exptime = 0.3
}
e:set_locks(locks)
minetest.get_meta(pos):set_string("locks", minetest.serialize(locks))
else

View file

@ -10,7 +10,8 @@ Player = setmetatable({
local m = setmetatable({
object = p,
pitch = 0,
yaw = 0
yaw = 0,
pos = p:get_pos()
}, {__index = Player})
m.name = p:get_player_name()
@ -528,13 +529,22 @@ Player = setmetatable({
end
})
-- Skip the death screen. Once we have an actual damage
-- system, this can be customized properly.
function minetest.show_death_screen(p, reason)
p:respawn()
end
-- Override respawning, so we can save progress.
minetest.register_on_respawnplayer(function(p)
local m = artifact.players[p:get_player_name()]
return true
--[[ Disabled due to current lack of purpose.
if m.spawn_point then
p:set_pos(m.spawn_point)
return true
end
--]]
end)
-- Mirror the player's HP in our custom HUD.
@ -584,14 +594,6 @@ function artifact.register_input(name)
local m = artifact.players[p:get_player_name()]
if not m._swapping_character and (artifact.debug or artifact.story.get_state() > artifact.story.states.pre_vix) then
artifact.swap_character(m)
-- If Key was pointing at something Vix shouldn't be able to interact with,
-- but Vix is also pointing at it, then remove the interaction marker since
-- it would be misleading.
if m.pointed_obj and m.interaction_marker and (not m.pointed_obj._can_interact or m.pointed_obj:_can_interact(m)) then
m.object:hud_remove(m.interaction_marker)
m.interaction_marker = nil
m.interaction_start = nil
end
end
return s
end,
@ -634,6 +636,13 @@ minetest.register_on_joinplayer(function(p)
end
end)
-- Imposters will be kicked. (But why would you run this on a server anyway?)
minetest.register_on_prejoinplayer(function(name)
if name == "Key" or name == "Vix" then
return "That name is already taken by one of the characters!"
end
end)
minetest.register_on_leaveplayer(function(p)
artifact.players[p:get_player_name()] = nil
end)

View file

@ -4,7 +4,7 @@ artifact.story = {
"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.
"ended", -- The game is over.
},
poi = {
initial_cutscene = {
@ -30,18 +30,43 @@ minetest.register_entity(":artifact:vix_scene", {
initial_properties = {
visual = "mesh",
mesh = "artifact_scene_vix.gltf",
textures = {"artifact_vix.png"}
textures = {"artifact_vix.png"},
backface_culling = false
},
on_activate = function(e)
if state > ns.states.pre_vix then
e.object:remove()
return
end
e.object:set_armor_groups{immortal = 1}
e.object:set_animation({x=0,y=2}, 0.5, 0.1, true)
e.particles = minetest.add_particlespawner {
pos = e.object:get_pos():offset(0, 0.15, 0),
vel = {
min = vector.new(-0.5, 0,-0.5),
max = vector.new( 0.5, 0, 0.5)
},
acc = vector.new(0, -9.81, 0),
texture = {
name = "artifact_light_gold.png",
alpha_tween = {0.75, 0}
},
collisiondetection = true,
time = 0,
amount = 100,
exptime = 5
}
e._track_pos = e.object:get_pos():offset(0, 0.5, 0)
e._rot = vector.zero()
vix_scene = e
end,
on_deactivate = function(e)
vix_scene = nil
if e.particles then
minetest.delete_particlespawner(e.particles)
end
end,
on_step = function(e)
if e._can_check then
@ -51,10 +76,53 @@ minetest.register_entity(":artifact:vix_scene", {
e._can_check = false
end
end
elseif e._track then
local m = artifact.players[next(artifact.players)]
local rot = e._track_pos:direction(m.pos):rotate(e._rot):dir_to_rotation()
if rot.y > -math.pi *(2/3) and rot.y < math.pi *(2/3) then
rot.y = math.pi *(2/3) *math.sign(rot.y)
end
rot.y = -rot.y +math.pi
rot.x = -math.max(-math.pi /3, math.min(math.pi /3, rot.x)) +0.25
e.object:set_bone_override("Head", {
rotation = {
vec = rot,
interpolation = 0.4
}
})
end
end
})
local help
local function pre_vix_on_whacked(type, target)
local checkpoint = db:get("checkpoint:pre_vix")
if checkpoint == "begin" then -- We're still in the start closet.
if type == "object" then
help:cancel()
vix_scene._can_check = true
end
db:set_string("checkpoint:pre_vix", "in_room")
elseif checkpoint == "in_room" then -- We're actually in the room.
if type == "node" and target.node_under.name:find "forcefield_generator" then
local num = db:get_int("checkpoint:pre_vix_fields_broken") +1
db:set_int("checkpoint:pre_vix_fields_broken", num)
if num == 1 then -- Key breaks his first generator.
minetest.after(1, function()
artifact.push_chat_message("Hehe.", "Key", "artifact_key_splash_low.png")
end)
elseif num >= 5 then -- All generators are down.
vix_scene._can_check = nil
-- Wait just a bit, so the player can look up.
minetest.after(0.5, function()
ns.play_vix_scene()
end)
end
end
end
end
function ns.enter_pre_vix_state()
for _, m in pairs(artifact.players) do
m:add_health_bar()
@ -66,40 +134,65 @@ function ns.enter_pre_vix_state()
m:set_spawnpoint(artifact.origin:offset(0, -73.5, -4))
end
minetest.add_entity(artifact.origin:offset(-16.5, -72.5, -17), "artifact:vix_scene")
local help = minetest.after(15, function()
help = minetest.after(15, function()
for _, m in pairs(artifact.players) do
artifact.show_help_message(m, "Certain nodes can be broken by punching them with the blackrod.", "info")
end
end)
db:set_string("checkpoint:pre_vix", "begin")
artifact.on_whacked = function(type, target)
local checkpoint = db:get("checkpoint:pre_vix")
if checkpoint == "begin" then -- We're still in the start closet.
if type == "object" then
help:cancel()
vix_scene._can_check = true
end
db:set_string("checkpoint:pre_vix", "in_room")
elseif checkpoint == "in_room" then -- We're actually in the room.
if target.node_under.name:find "forcefield_generator" then
local num = db:get_int("checkpoint:pre_vix_fields_broken") +1
db:set_int("checkpoint:pre_vix_fields_broken", num)
if num == 1 then -- Key breaks his first generator.
minetest.after(1, function()
artifact.push_chat_message("Hehe.", "Key", "artifact_key_splash_low.png")
end)
elseif num == 5 then -- All generators are down.
vix_scene._can_check = nil
end
end
end
end
artifact.on_whacked = pre_vix_on_whacked
end
function ns.enter_main_state()
minetest.add_entity(vix_scene.object:get_pos():offset(1.5, -0.8, -0.5), "artifact:sidekick"):set_rotation(vector.new(0, -math.pi /2, 0))
vix_scene.object:remove()
end
function ns.enter_ended_state()
for _, m in pairs(artifact.players) do
local bg = artifact.hud_add(m, {
type = "image",
image = "[fill:16x16:0,0:#000",
opacity = 0,
pos = {x=0.5,y=0.5},
scale = {x=1000,y=1000},
})
bg:animate {
opacity = {
value = 256,
duration = 0.3
}
}
minetest.after(0.3, function()
local txt = artifact.hud_add(m, {
type = "text",
color = "#000",
text = "To be continued...",
pos = {x=0.5,y=0.5},
size = {x=3,y=0},
color = minetest.colorspec_to_table("#000")
})
txt:animate {
color = {
value = minetest.colorspec_to_table("#888"),
duration = 0.3
}
}
minetest.after(8, function()
txt:animate {
color = {
value = minetest.colorspec_to_table("#000"),
duration = 0.3
}
}
minetest.after(0.3, function()
minetest.request_shutdown("You completed the game.")
end)
end)
end)
end
end
function ns.enter_state(to)
state = to
if state == ns.states.init then
@ -108,6 +201,8 @@ function ns.enter_state(to)
ns.enter_pre_vix_state()
elseif state == ns.states.main then
ns.enter_main_state()
elseif state == ns.states.ended then
ns.enter_ended_state()
end
db:set_int("state", state)
end
@ -133,7 +228,7 @@ function artifact.look_at(m, pos, pos2)
m.object:set_look_vertical(-rot.x)
end
if artifact.debug then
minetest.register_chatcommand("splash", {
func = function(name)
local m = artifact.players[name]
@ -147,6 +242,478 @@ minetest.register_chatcommand("splash", {
end
})
minetest.register_chatcommand("flash", {
func = function(name)
minetest.add_particle {
pos = artifact.origin:offset(-16.5, -71.5, -17),
velocity = vector.zero(),
texture = {
name = "artifact_flash.png",
alpha_tween = {0, 1}
},
size = 60,
expirationtime = 0.1
}
end
})
end
artifact.register_node("stasis_beacon", {
drawtype = "mesh",
mesh = "artifact_stasis_beacon.gltf",
tiles = {"artifact_stasis_beacon.png"},
use_texture_alpha = "blend",
paramtype = "light"
})
-- The 'cutscene' played after the player frees Vix.
function ns.play_vix_scene()
-- Kaboom.
minetest.add_particle {
pos = artifact.origin:offset(-16.5, -71.5, -17),
velocity = vector.zero(),
texture = {
name = "artifact_flash.png",
alpha_tween = {0, 1}
},
size = 60,
expirationtime = 0.1
}
artifact.play_sound {
name = "artifact_free_vix",
pos = artifact.origin:offset(-16.5, -72.5, -17)
}
vix_scene.object:set_animation({x=3,y=150}, 1, 0.1, false)
minetest.delete_particlespawner(vix_scene.particles)
vix_scene.particles = nil
-- Key can walk around freely, so Vix should try to face him once she wakes.
local i = 7
minetest.after(i, function()
vix_scene._track = true
end)
i = i +1
minetest.after(i, function()
-- Is he the one they sent?
artifact.push_chat_message("...Who are you?", minetest.colorize("#f5dd66", "???"), "artifact_vix_splash_low.png")
end)
i = i +4
minetest.after(i, function()
-- Awkward.
artifact.push_chat_message("Uh... Wow, this is awkward. I did not think that through far enough.", "Key", "artifact_key_splash_low.png")
end)
i = i +7
minetest.after(i, function()
-- Oops, that didn't help.
artifact.push_chat_message("Wait, that was bad, let's try this again.", "Key", "artifact_key_splash_low.png")
end)
i = i +4
minetest.after(i, function()
-- Better.
artifact.push_chat_message("Hi. I'm Key.", "Key", "artifact_key_splash_low.png")
end)
i = i +4
minetest.after(i, function()
-- ...He's not?
artifact.push_chat_message("...Hi.", minetest.colorize("#f5dd66", "???"), "artifact_vix_splash_low.png")
-- [ She sits up ]
vix_scene._track = nil
vix_scene.object:set_bone_override("Head", {
rotation = {
vec = vector.zero(), interpolation = 0.4
}
})
end)
i = i +5
minetest.after(i, function()
vix_scene._track = true
-- So what's the deal?
artifact.push_chat_message("How did you get here?", minetest.colorize("#f5dd66", "???"), "artifact_vix_splash_low.png")
end)
i = i +5
minetest.after(i, function()
artifact.push_chat_message("It's actually a long story, but the short version is that I fell in a hole.", "Key", "artifact_key_splash_low.png")
end)
i = i +4
minetest.after(i, function()
artifact.push_chat_message("...", minetest.colorize("#f5dd66", "???"), "artifact_vix_splash_low.png")
end)
i = i +4
minetest.after(i, function()
artifact.push_chat_message("Maintenance might want to have a look at the closet over there.", "Key", "artifact_key_splash_low.png")
end)
i = i +6
minetest.after(i, function()
-- Okay... Wait, how long has it been, anyway? The room is awfully overgrown...
artifact.push_chat_message("I don't think maintenance will mind...", minetest.colorize("#f5dd66", "???"), "artifact_vix_splash_low.png")
end)
i = i +4
minetest.after(i, function()
artifact.push_chat_message("Probably so...", "Key", "artifact_key_splash_low.png")
-- [ Vix climbs off the pedestal ]
vix_scene._track = nil
vix_scene.object:set_bone_override("Head", {
rotation = {
vec = vector.zero(), interpolation = 0.4
}
})
vix_scene._track_pos = vix_scene.object:get_pos():offset(1.5, 0, -0.5)
vix_scene._rot = vector.new(0, -math.pi /2, 0)
end)
i = i +7
minetest.after(i, function()
vix_scene._track = true
artifact.push_chat_message("So, uh, what's _your_ name?", "Key", "artifact_key_splash_low.png")
end)
i = i +4
minetest.after(i, function()
artifact.push_chat_message("...Vix.", "Vix", "artifact_vix_splash_low.png")
end)
i = i +4
minetest.after(i, function()
artifact.push_chat_message("Formally pleased to meet you.", "Key", "artifact_key_splash_low.png")
end)
i = i +5
minetest.after(i, function()
-- How trustworthy is he?
artifact.push_chat_message("Why did you... break the forcefields?", "Vix", "artifact_vix_splash_low.png")
end)
i = i +4
minetest.after(i, function()
artifact.push_chat_message("Well, when I saw you locked inside all those forcefields, I thought 'Wow, she doesn't look too happy'.", "Key", "artifact_key_splash_low.png")
end)
i = i +7
minetest.after(i, function()
artifact.push_chat_message("Then I thought, 'Hmm, she must have been there a while, what with all the vines over here and not over there.'", "Key", "artifact_key_splash_low.png")
end)
i = i +6
minetest.after(i, function()
artifact.push_chat_message("_Then_ I thought, 'Whoever's fault this is probably isn't going to be back. Why not help her out?'", "Key", "artifact_key_splash_low.png")
end)
i = i +7
minetest.after(i, function()
artifact.push_chat_message("So then I used this cool stick I found to trash all the forcefields and let you out.", "Key", "artifact_key_splash_low.png")
end)
i = i +7
minetest.after(i, function()
-- ..._Are_ they going to be back...?
artifact.push_chat_message("What year is it?", "Vix", "artifact_vix_splash_low.png")
end)
i = i +4
minetest.after(i, function()
artifact.push_chat_message("I dunno, I lost count a while back. Somewhere around the turn of the century.", "Key", "artifact_key_splash_low.png")
end)
i = i +5
minetest.after(i, function()
-- ... I guess not.
artifact.push_chat_message("...", "Vix", "artifact_vix_splash_low.png")
end)
i = i +4
minetest.after(i, function()
-- You know what, what am I even doing here?
-- (It's not like staying is a workable option, but that's not really what Vix has in mind.)
artifact.push_chat_message("We need to leave... I don't suppose we could get out the way you got in?", "Vix", "artifact_vix_splash_low.png")
end)
i = i +6
minetest.after(i, function()
artifact.push_chat_message("...Nah.", "Key", "artifact_key_splash_low.png")
end)
i = i +4
minetest.after(i, function()
artifact.push_chat_message("I guess that leaves the hard way.", "Vix", "artifact_vix_splash_low.png")
end)
i = i +4
minetest.after(i, function()
artifact.push_chat_message("Let's see if this works...", "Vix", "artifact_vix_splash_low.png")
minetest.after(1, function()
local burst = minetest.add_entity(vix_scene.object:get_pos():offset(0, 1, -1.5), "artifact:burst")
burst:get_luaentity()._critical = true
burst:set_attach(vix_scene.object, "RightArm", vector.new(0, 100, 0))
minetest.after(0, function()
burst:set_detach()
artifact.play_sound {
name = "artifact_burst_fire",
pos = burst:get_pos()
}
burst:get_luaentity():impulse(burst:get_pos():direction(artifact.origin:offset(18, -71, -13)) *30)
end)
end)
end)
i = i +8
minetest.after(i, function()
artifact.push_chat_message("Wow, that's epic.", "Key", "artifact_key_splash_low.png")
end)
i = i +2
minetest.after(i, function()
ns.enter_state(ns.states.main)
minetest.after(10, function()
local m = artifact.players[next(artifact.players)]
if m.character ~= "vix" then
artifact.show_help_message(m, "You can switch between Key and Vix using the Drop control.", "info")
end
end)
end)
i = i +45
minetest.after(i, ns.dialogue_1)
end
function ns.dialogue_1()
local i = 0
artifact.push_chat_message("So, what's the long version of you you got here?", "Vix", "artifact_vix_splash_low.png")
i = i +5
minetest.after(i, function()
artifact.push_chat_message("Well, one fine day, I was strolling along through a little town called Birchwood.", "Key", "artifact_key_splash_low.png")
end)
i = i +5
minetest.after(i, function()
artifact.push_chat_message("On the street corner, some guy was doing a huge ad for an adventuring supply company.", "Key", "artifact_key_splash_low.png")
end)
i = i +6
minetest.after(i, function()
artifact.push_chat_message("Naturally, I suggested that his wares were overpriced because he was spending so much on advertising.", "Key", "artifact_key_splash_low.png")
end)
i = i +6
minetest.after(i, function()
artifact.push_chat_message("I won't bore you with the details, but there was a whole thing that ended with me betting him five gold pieces that I could steal some artifact whose name I forget without any supplies at all.", "Key", "artifact_key_splash_low.png")
end)
i = i +8
minetest.after(i, function()
artifact.push_chat_message("(I told him it was probably guarded by a few bats and a couple average-sized spiders, but he wouldn't believe me.)", "Key", "artifact_key_splash_low.png")
end)
i = i +6
minetest.after(i, function()
artifact.push_chat_message("I was wrong about there not being any traps, though. That's why I'm down here and not five gold pieces richer. (Yet.)", "Key", "artifact_key_splash_low.png")
end)
i = i +6
minetest.after(i, function()
artifact.push_chat_message("So you came all this way for five gold pieces?", "Vix", "artifact_vix_splash_low.png")
end)
i = i +5
minetest.after(i, function()
artifact.push_chat_message("No, no, I came all this way to prove a point. I'd probably forget I had the gold pieces within the hour.", "Key", "artifact_key_splash_low.png")
end)
i = i +6
minetest.after(i, function()
artifact.push_chat_message("Wow.", "Vix", "artifact_vix_splash_low.png")
end)
i = i +15
minetest.after(i, function()
artifact.push_chat_message("So, how about you? What's the story with that awesome burst ability?", "Key", "artifact_key_splash_low.png")
end)
i = i +5
minetest.after(i, function()
artifact.push_chat_message("Have you ever heard of Iron Dragon?", "Vix", "artifact_vix_splash_low.png")
end)
i = i +4
minetest.after(i, function()
artifact.push_chat_message("Nope.", "Key", "artifact_key_splash_low.png")
end)
i = i +5
minetest.after(i, function()
artifact.push_chat_message("They're... Well, they were... a vigilante group, who were fairly powerful fifty-some years ago. My father was one of the generals.", "Vix", "artifact_vix_splash_low.png")
end)
i = i +6
minetest.after(i, function()
artifact.push_chat_message("Early on, they learned to synthesize a controlled form of energy to power tools; you saw some of that in the forcefields.", "Vix", "artifact_vix_splash_low.png")
end)
i = i +6
minetest.after(i, function()
artifact.push_chat_message("Eventually, someone — my aunt, actually — floated the idea that feeding that energy into a human might give them new abilities.", "Vix", "artifact_vix_splash_low.png")
end)
i = i +6
minetest.after(i, function()
artifact.push_chat_message("No one liked it at first. Eventually, though, when we were weakened and all but defeated, my father became desperate enough to consider it.", "Vix", "artifact_vix_splash_low.png")
end)
i = i +6
minetest.after(i, function()
artifact.push_chat_message("He wanted me to be the first test subject. Looking back, I think he wanted to protect me, keep me out of the combat if things got that bad.", "Vix", "artifact_vix_splash_low.png")
end)
i = i +6
minetest.after(i, function()
artifact.push_chat_message("I trusted his judgement.", "Vix", "artifact_vix_splash_low.png")
end)
i = i +4
minetest.after(i, function()
artifact.push_chat_message("Incredibly, though, he was right... It makes no sense physiologically, but somehow it worked.", "Vix", "artifact_vix_splash_low.png")
end)
i = i +8
minetest.after(i, function()
artifact.push_chat_message("Now here I am half a century later, and it's all gone...", "Vix", "artifact_vix_splash_low.png")
end)
i = i +6
minetest.after(i, function()
artifact.push_chat_message("Wow. Imagine an experiment that worked on the first try.", "Key", "artifact_key_splash_low.png")
end)
i = i +4
minetest.after(i, function()
artifact.push_chat_message("Right? I though there could be something to it, but this? It's been half a century, and I feel even better than I did before.", "Vix", "artifact_vix_splash_low.png")
end)
i = i +6
minetest.after(i, function()
artifact.push_chat_message("That's what I was thinking. You look sixteen, not 60.", "Key", "artifact_key_splash_low.png")
end)
i = i +4
minetest.after(i, function()
artifact.push_chat_message("Really?", "Vix", "artifact_vix_splash_low.png")
end)
i = i +4
minetest.after(i, function()
artifact.push_chat_message("Yep.", "Key", "artifact_key_splash_low.png")
end)
i = i +4
minetest.after(i, function()
artifact.push_chat_message("...Huh. That's...", "Vix", "artifact_vix_splash_low.png")
end)
i = i +4
minetest.after(i, function()
artifact.push_chat_message("Really weird?", "Key", "artifact_key_splash_low.png")
end)
i = i +4
minetest.after(i, function()
artifact.push_chat_message("Yeah. That's exactly how old I was when this happened...", "Vix", "artifact_vix_splash_low.png")
end)
i = i +5
minetest.after(i, function()
artifact.push_chat_message("Most intriguing...", "Key", "artifact_key_splash_low.png")
end)
end
-- Play the final scene.
function ns.play_final_scene()
local scn = minetest.add_entity(artifact.origin:offset(132, -69.5, -22), "display")
scn:set_properties {
visual = "mesh",
mesh = "artifact_scene_final.gltf",
textures = {"artifact_tav.png"}
}
local i = 2
minetest.after(i, function()
artifact.push_chat_message("Hey, who's that?", "Key", "artifact_key_splash_low.png")
minetest.after(1, function()
scn:set_animation({x=0,y=120}, 1, 0, false)
end)
end)
i = i +4
minetest.after(i, function()
artifact.push_chat_message("I don't know...", "Vix", "artifact_vix_splash_low.png")
end)
i = i +4
minetest.after(i, function()
artifact.push_chat_message("You.", minetest.colorize("#666", "???"), "artifact_tav_splash_low.png")
end)
i = i +3
minetest.after(i, function()
artifact.push_chat_message("Huh?", "Key", "artifact_key_splash_low.png")
end)
i = i +5
minetest.after(i, function()
artifact.push_chat_message("What have you done?", minetest.colorize("#666", "???"), "artifact_tav_splash_low.png")
end)
i = i +4
minetest.after(i, function()
artifact.push_chat_message("I just walked in here, why?", "Key", "artifact_key_splash_low.png")
end)
i = i +5
minetest.after(i, function()
artifact.push_chat_message("Vix.", minetest.colorize("#666", "???"), "artifact_tav_splash_low.png")
end)
i = i +4
minetest.after(i, function()
artifact.push_chat_message("What?", "Vix", "artifact_vix_splash_low.png")
end)
i = i +5
minetest.after(i, function()
artifact.push_chat_message("Hm.", minetest.colorize("#666", "???"), "artifact_tav_splash_low.png")
end)
i = i +3
minetest.after(i, function()
artifact.push_chat_message("So you really did?", minetest.colorize("#666", "???"), "artifact_tav_splash_low.png")
end)
i = i +4
minetest.after(i, function()
artifact.push_chat_message("I don't understand...", "Vix", "artifact_vix_splash_low.png")
end)
i = i +5
minetest.after(i, function()
artifact.push_chat_message("Is that so?", minetest.colorize("#666", "???"), "artifact_tav_splash_low.png")
end)
i = i +4
minetest.after(i, function()
artifact.push_chat_message("Yes?", "Vix", "artifact_vix_splash_low.png")
end)
i = i +4
minetest.after(i, function()
artifact.push_chat_message("Traitor.", minetest.colorize("#666", "???"), "artifact_tav_splash_low.png")
end)
i = i +2
minetest.after(i, function()
artifact.push_chat_message("Wow.", "Key", "artifact_key_splash_low.png")
end)
i = i +2
minetest.after(i, function()
artifact.push_chat_message("Huh? Iron Dragon is gone. What are you accusing me of?", "Vix", "artifact_vix_splash_low.png")
end)
i = i +4
minetest.after(i, function()
artifact.push_chat_message("So you don't know. Unfortunate.", minetest.colorize("#666", "???"), "artifact_tav_splash_low.png")
end)
i = i +4
minetest.after(i, function()
artifact.push_chat_message("Who are you, anyway?", "Key", "artifact_key_splash_low.png")
end)
i = i +4
minetest.after(i, function()
artifact.push_chat_message("A ghost.", minetest.colorize("#666", "???"), "artifact_tav_splash_low.png")
end)
i = i +4
minetest.after(i, function()
artifact.push_chat_message("Whoa, stop being so ambiguous! I don't think we know what you think we know.", "Key", "artifact_key_splash_low.png")
end)
i = i +5
minetest.after(i, function()
artifact.push_chat_message("I'd love to banter, but right now I have more important things to do.", minetest.colorize("#666", "???"), "artifact_tav_splash_low.png")
end)
i = i +5
minetest.after(i, function()
artifact.push_chat_message("As far as I'm concerned, you can rot down here — mother.", minetest.colorize("#666", "???"), "artifact_tav_splash_low.png")
minetest.after(1, function()
local door = artifact.large_doors[artifact.origin:offset(137, -69, -22):round():to_string()]
door:open()
minetest.after(3, function()
door:close()
end)
end)
end)
i = i +5
minetest.after(i, function()
artifact.push_chat_message("Excuse me!?", "Key", "artifact_key_splash_low.png")
artifact.push_chat_message("Excuse me!?", "Vix", "artifact_vix_splash_low.png")
end)
i = i +3
minetest.after(i, function()
artifact.push_chat_message("Hey! You're being so cliche right now!", "Key", "artifact_key_splash_low.png")
end)
i = i +7
minetest.after(i, function()
artifact.push_chat_message("Well, that was weird.", "Key", "artifact_key_splash_low.png")
end)
i = i +9
minetest.after(i, function()
artifact.push_chat_message("...So, Vix, you wouldn't happen to know of any ventilation shafts conveniently placed nearby, would you?", "Key", "artifact_key_splash_low.png")
end)
i = i +6
minetest.after(i, function()
artifact.push_chat_message("Well, there is one...", "Vix", "artifact_vix_splash_low.png")
end)
i = i +4
minetest.after(i, function()
artifact.push_chat_message("Nice. Let's get out of this place.", "Key", "artifact_key_splash_low.png")
end)
i = i +4
minetest.after(i, function()
ns.enter_state(ns.states.ended)
end)
end
-- Play the opening cutscene.
function ns.play_intro_cutscene()
ns.camera = minetest.add_entity(artifact.origin:offset(0,-0.75,0), "display")
@ -391,7 +958,7 @@ function ns.load_map()
m.hud.loading_map_bg.remove_after = 0.3
m.object:set_pos(start)
end
ns.enter_state(artifact.story.states.pre_vix)
ns.enter_state(artifact.story.states.init)
end)
end)
end)
@ -402,6 +969,11 @@ include "objectives.lua"
local started = false
minetest.register_on_joinplayer(function(p)
local m = artifact.players[p:get_player_name()]
if state == ns.states.ended then
minetest.request_shutdown("You completed the game.")
end
-- 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)
@ -417,6 +989,15 @@ minetest.register_on_joinplayer(function(p)
wielditem = true
}
end
if state == ns.states.pre_vix then
artifact.on_whacked = pre_vix_on_whacked
end
if state == ns.states.pre_vix and db:get_int("checkpoint:pre_vix_fields_broken") >= 5 then
-- If the player left during the scene for some reason, start it over when they join back.
ns.play_vix_scene()
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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 517 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -103,6 +103,35 @@ local function rep(tx, size)
end
-- Sound tables for material types.
artifact.sounds = {
stone = {
footstep = "artifact_step_stone"
},
mossy_stone = {
footstep = "artifact_step_stone_mossy"
},
metal = {
footstep = "artifact_step_stone" -- We don't have a separate sound for this, so just use stone for now.
},
wood = {
footstep = "artifact_step_stone"
},
leaves = {
footstep = "artifact_step_leaves"
},
dry_leaves = {
footstep = "artifact_step_leaves_dry"
},
glass = {
footstep = "artifact_step_glass"
},
water = {
footstep = "artifact_water"
}
}
-- These nodes are only used for the intro cutscene.
artifact.register_node("stone_brown", {
@ -144,38 +173,49 @@ artifact.register_node("torch_standing", {
-- End ad-hoc nodes.
artifact.register_node("stone", {
tiles = {{name = "artifact_stone.png", align_style = "world"}},
_variants = {"stair", "slab"}
_variants = {"stair", "slab"},
sounds = artifact.sounds.stone,
})
artifact.register_node("stone_mossy", {
tiles = {{name = rep("artifact_stone.png", 4).."^artifact_moss.png", align_style = "world", scale = 4}},
_variants = {"stair", "slab"},
sounds = artifact.sounds.mossy_stone,
})
artifact.register_node("stone_bricks", {
tiles = {{name = "artifact_stone_bricks.png", align_style = "world"}},
_variants = {"stair", "slab"},
sounds = artifact.sounds.stone,
})
artifact.register_node("stone_bricks_mossy", {
tiles = {{name = rep("artifact_stone_bricks.png", 4).."^artifact_moss_bricks.png", align_style = "world", scale = 4}},
_variants = {"stair", "slab"},
sounds = artifact.sounds.mossy_stone,
})
artifact.register_node("stone_bricks_small", {
tiles = {{name = "artifact_stone_bricks_small.png", align_style = "world"}},
_variants = {"stair", "slab"},
sounds = artifact.sounds.stone,
})
artifact.register_node("stone_bricks_small_mossy", {
tiles = {{name = rep("artifact_stone_bricks_small.png", 4).."^artifact_moss_bricks_small.png", align_style = "world", scale = 4}},
_variants = {"stair", "slab"},
sounds = artifact.sounds.mossy_stone,
})
artifact.register_node("stone_tile", {
tiles = {{name = "artifact_stone_tile.png", align_style = "world"}},
_variants = {"stair", "slab"},
sounds = artifact.sounds.stone,
})
artifact.register_node("stone_tile_small", {
tiles = {{name = "artifact_stone_tile_small.png", align_style = "world"}},
_variants = {"stair", "slab"},
sounds = artifact.sounds.stone,
})
-- Why does making this texture a tile animation darken it!?
@ -188,6 +228,7 @@ artifact.register_node("water", {
pointable = artifact.debug,
liquid_move_physics = true,
post_effect_color = "#2d5a7c55",
sounds = artifact.sounds.water,
liquidtype = "source",
-- Minetest pro tip: Do not try to use aliases for these.
@ -213,6 +254,7 @@ artifact.register_node("water_flowing", {
liquid_move_physics = true,
post_effect_color = "#2d5a7c55",
drop = "",
sounds = artifact.sounds.water,
liquidtype = "flowing",
liquid_alternative_source = "artifact:water",
@ -240,6 +282,7 @@ artifact.register_node("water_static", {
pointable = artifact.debug,
liquid_move_physics = true,
post_effect_color = "#2d5a7c55",
sounds = artifact.sounds.water,
})
@ -263,7 +306,8 @@ artifact.register_node("vines", {
paramtype = "light",
paramtype2 = "facedir",
tiles = {"artifact_vines.png"},
use_texture_alpha = "clip"
use_texture_alpha = "clip",
sounds = artifact.sounds.leaves,
})
artifact.register_node("vines_dry", {
drawtype = "nodebox",
@ -286,27 +330,50 @@ artifact.register_node("vines_dry", {
paramtype2 = "facedir",
tiles = {"artifact_vines_dry.png"},
use_texture_alpha = "clip",
groups = {whackable = 1}
groups = {whackable = 1},
sounds = artifact.sounds.dry_leaves,
on_destruct = function(pos)
artifact.play_sound {
name = "artifact_step_leaves",
pos = pos,
gain = 1.5
}
end
})
artifact.register_node("leaves", {
drawtype = "allfaces",
-- paramtype = "light",
tiles = {"artifact_leaves.png"},
use_texture_alpha = "clip"
use_texture_alpha = "clip",
sounds = artifact.sounds.leaves,
})
artifact.register_node("leaves_dry", {
drawtype = "allfaces",
-- paramtype = "light",
tiles = {"artifact_leaves_dry.png"},
use_texture_alpha = "clip",
groups = {whackable = 1}
groups = {whackable = 1},
sounds = artifact.sounds.dry_leaves,
on_destruct = function(pos)
artifact.play_sound {
name = "artifact_step_leaves",
pos = pos,
gain = 1.5
}
end
})
artifact.register_node("wood_planks", {
tiles = {{name = "artifact_wood_planks.png", align_style = "world"}},
_variants = {"stair", "slab"},
sounds = artifact.sounds.wood,
})
artifact.register_node("crate", {
tiles = {{name = "artifact_crate.png", align_style = "world"}},
sounds = artifact.sounds.wood,
})
artifact.register_node("ladder_wood", {
@ -316,7 +383,8 @@ artifact.register_node("ladder_wood", {
paramtype2 = "facedir",
tiles = {"artifact_ladder_wood.png"},
walkable = false,
climbable = true
climbable = true,
sounds = artifact.sounds.wood,
})
artifact.register_node("ladder_iron", {
@ -326,7 +394,8 @@ artifact.register_node("ladder_iron", {
paramtype2 = "facedir",
tiles = {"artifact_ladder_iron.png"},
walkable = false,
climbable = true
climbable = true,
sounds = artifact.sounds.metal,
})
@ -335,6 +404,7 @@ artifact.register_node("glass", {
use_texture_alpha = "clip",
tiles = {"artifact_glass.png"},
_variants = {"stair", "slab"},
sounds = artifact.sounds.glass,
})
@ -364,6 +434,7 @@ local function register_lamp(color, brightness)
}
}
},
sounds = artifact.sounds.glass,
})
artifact.register_node("lamp_"..color.."_wall", {
drawtype = "mesh",
@ -398,7 +469,8 @@ local function register_lamp(color, brightness)
1/16, 6/16, 8/16
},
}
}
},
sounds = artifact.sounds.glass,
})
artifact.register_node("lamp_"..color.."_hanging", {
drawtype = "mesh",
@ -424,7 +496,8 @@ local function register_lamp(color, brightness)
2/16, 4/16, 2/16
}
}
}
},
sounds = artifact.sounds.glass,
})
end

View file

@ -1,5 +1,6 @@
local vm_data = {}
local c_air = minetest.get_content_id("air")
local c_stone = minetest.get_content_id("artifact:stone")
-- Singlenode, but the single node is stone.
@ -11,9 +12,12 @@ minetest.register_on_generated(function(vm, minp, maxp)
vm:get_data(vm_data)
for i in va:iterp(minp, maxp) do
vm_data[i] = c_stone
end
-- If we load a giant schematic initially, the parts outside view range get eaten by mapgen.
-- for i in va:iterp(minp, maxp) do
-- if vm_data[i] == c_air then
-- vm_data[i] = c_stone
-- end
-- end
vm:set_data(vm_data)
vm:calc_lighting()

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 331 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8 KiB