384 lines
17 KiB
Lua
384 lines
17 KiB
Lua
--this minigame has a globalstep to update the board, because the arena_lib on_time_tick
|
|
--is too slow for the fast-paced nature of snake. However, lag has been tested to be low
|
|
--(please confirm as needed)
|
|
|
|
|
|
|
|
|
|
|
|
minetest.register_globalstep(function(dtime)
|
|
|
|
|
|
--every server_step, check if the players in-game are attached to their worm heads. Iff they are not, then spawn a new entity and attach them
|
|
--note, the entities have a 10 min timeout (wormball games last 5 min). A MT 5.3 engine bug can detach players unexpectedly, this is a workaround.
|
|
|
|
|
|
for _,player in ipairs(minetest.get_connected_players()) do
|
|
local pl_name = player:get_player_name()
|
|
|
|
if arena_lib.is_player_in_arena(pl_name, "wormball") and not(arena_lib.is_player_spectating(pl_name)) then
|
|
local arena = arena_lib.get_arena_by_player(pl_name)
|
|
if not(arena.in_queue == true) and not (arena.in_loading == true) and not(arena.in_celebration == true) and arena.enabled == true then
|
|
local stats = arena.players[pl_name]
|
|
if stats.alive == true then
|
|
if not player:get_attach() then
|
|
local pos_head = arena.players[pl_name].nodes[1]
|
|
local att = minetest.add_entity(pos_head, 'wormball:player_att')
|
|
|
|
player:set_attach(att, "", {x=0,y=0,z=0}, {x=0,y=0,z=0})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
-- -- this makes spectating possible... Hopefully, and needs testing
|
|
if arena_lib.is_player_in_arena(pl_name, "wormball") and arena_lib.is_player_spectating(pl_name) then
|
|
local att = player:get_attach()
|
|
|
|
if att and att:is_player() then
|
|
local attatt = att:get_attach()
|
|
if attatt and attatt:get_luaentity() and attatt:get_luaentity().name and attatt:get_luaentity().name == "wormball:player_att" then
|
|
player:set_attach(attatt)
|
|
end
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
--clear HUDs when not needed
|
|
|
|
for _,player in ipairs(minetest.get_connected_players()) do
|
|
local p_name = player:get_player_name()
|
|
|
|
if not arena_lib.is_player_in_arena(p_name, "wormball") then
|
|
if wormball.HUD[p_name] then
|
|
|
|
player:hud_remove(wormball.HUD[p_name].scores)
|
|
|
|
wormball.HUD[p_name] = nil
|
|
end
|
|
end
|
|
end
|
|
|
|
--we will only run worm movement code about every 0.4 seconds, if not 0.4 seconds yet; return
|
|
|
|
wormball.timer = wormball.timer+dtime
|
|
if wormball.timer < .4 then return
|
|
end
|
|
--reset the timer
|
|
wormball.timer = 0
|
|
|
|
|
|
|
|
--because this is a global callback, I have to get the name from the player, and the arena from the name, and check that the
|
|
--arena is in-game before doing anything
|
|
|
|
--check all connected players
|
|
for _,player in ipairs(minetest.get_connected_players()) do
|
|
|
|
local pl_name = player:get_player_name()
|
|
|
|
--only mess with stuff if they are in the wormball minigame
|
|
if arena_lib.is_player_in_arena(pl_name, "wormball") and not(arena_lib.is_player_spectating(pl_name)) then
|
|
|
|
local arena = arena_lib.get_arena_by_player(pl_name)
|
|
|
|
--only mess with stuff if the arena is in-game, add they are supposed to be attached
|
|
if not(arena.in_queue == true) and not (arena.in_loading == true) and not(arena.in_celebration == true) and arena.enabled == true and arena.players[pl_name].attached == true then
|
|
|
|
----------------------------------------------
|
|
---- mimic the normal for pl_name, stats ----
|
|
----------------------------------------------
|
|
local stats = arena.players[pl_name]
|
|
|
|
local color = stats.color
|
|
|
|
local remove_tail = true
|
|
|
|
local died --used to determine whether to elim player
|
|
|
|
if stats.eliminated == true then
|
|
died = true
|
|
arena.players[pl_name].eliminated = false
|
|
end
|
|
|
|
|
|
|
|
-- if players are alive, move the worms (add to the length, subtract from the tail)
|
|
|
|
if stats.alive == true then
|
|
|
|
|
|
local old_dir = arena.players[pl_name].old_direction or {x=0,y=1,z=0} --grab the old_dir info before its updated
|
|
|
|
local player = minetest.get_player_by_name(pl_name)
|
|
|
|
local control = player:get_player_control() --ref: {jump=bool, right=bool, left=bool, LMB=bool, RMB=bool, sneak=bool, aux1=bool, down=bool, up=bool}
|
|
|
|
local look_dir = wormball.get_look_dir(arena,player) --in globals file; returns a string, one of: px, nx, pz, nz for the approximation of player look direction
|
|
|
|
died = false --used to determine whether to elim player
|
|
|
|
--get player direction from current input, first check up or down, then look direction
|
|
|
|
if control.jump == true then --if we are going up
|
|
arena.players[pl_name].direction = {x=0,y=1,z=0}
|
|
elseif control.sneak == true then --if we are going down
|
|
arena.players[pl_name].direction = {x=0,y=-1,z=0}
|
|
elseif look_dir == 'pz' then -- if we are looking in the +z direction,
|
|
arena.players[pl_name].direction = {x=0,y=0,z=1}
|
|
elseif look_dir == 'nx' then -- if we are looking in the -x direction
|
|
arena.players[pl_name].direction = {x=-1,y=0,z=0}
|
|
elseif look_dir == 'nz' then -- if we are looking in the -z direction
|
|
arena.players[pl_name].direction = {x=0,y=0,z=-1}
|
|
elseif look_dir == 'px' then -- if we are looking in the +x direction
|
|
arena.players[pl_name].direction = {x=1,y=0,z=0}
|
|
end
|
|
|
|
|
|
--save the direction info for next round
|
|
arena.players[pl_name].old_direction = arena.players[pl_name].direction
|
|
|
|
|
|
|
|
|
|
-- localize the direction for easy reference
|
|
local new_move = stats.direction
|
|
--was:stats.direction
|
|
|
|
--stats.nodes is the positions of the worm body parts, idx 1 is the head
|
|
local head_pos = stats.nodes[1]
|
|
|
|
--get the new head pos, from the old pos and the new move pos
|
|
local new_pos = {x = head_pos.x + new_move.x, y = head_pos.y + new_move.y, z = head_pos.z + new_move.z}
|
|
|
|
--get the node at the tenative new head location.
|
|
local new_node = minetest.get_node(new_pos).name
|
|
|
|
|
|
----------------------------------------------
|
|
--- if the new node is air, move the head, delete the tail, length remains the same
|
|
--- if the new node is a powerup of a different color, move the head, but keep the tail, so the snake grows 1
|
|
--- if the new node is a powerup of the same color, set move to false, so the next round, the head will not be moved as the tail is deleted to shrink it
|
|
--- if the new node is none of the above, it is an obstacle, and the player loses
|
|
----------------------------------------------
|
|
--- note: if stats.move is set to false, then the next round, the head will not move, but move will be reset
|
|
--- note: if the local var remove_tail is true, then after checking the node, the tail is removed
|
|
-----------------
|
|
--- note: the function wormball.place_node is in the globals file, and performs the math to place the head and the
|
|
--- -- first body segment rotated according to the current and previous movement directions.
|
|
----------------------------------------------
|
|
--- note: we ALWAYS move forward, unless there is an arena obstacle. However, for the dots of the same color,
|
|
--- -- we do not move forward NEXT turn. This is so that dots are always *eaten*
|
|
----------------------------------------------
|
|
|
|
if new_node == 'air' then
|
|
|
|
|
|
if arena.players[pl_name].move == true then
|
|
|
|
-- place the head location into the player's body locations table
|
|
table.insert(arena.players[pl_name].nodes,1,new_pos)
|
|
|
|
--draw the head and the first body segment
|
|
wormball.place_node(arena.players[pl_name].nodes,arena.players[pl_name].direction,old_dir,look_dir,color)
|
|
|
|
|
|
--move the player's attached entity (use invisible entities so movements are smoother)
|
|
local att = player:get_attach()
|
|
|
|
--nil check
|
|
if att then
|
|
att:move_to(new_pos, true)
|
|
end
|
|
|
|
else
|
|
arena.players[pl_name].move = true
|
|
end
|
|
|
|
|
|
elseif new_node == "wormball:power_"..color then --oops, hit own color, remove 1 length
|
|
|
|
|
|
|
|
if arena.players[pl_name].move == true then
|
|
|
|
|
|
--delete the memory of the dot that was 'eaten'
|
|
for _,dot in pairs(arena.dots) do
|
|
if dot == new_pos then
|
|
table.remove(arena.dots,_)
|
|
end
|
|
end
|
|
|
|
|
|
-- place the head location into the player's body locations table
|
|
table.insert(arena.players[pl_name].nodes,1,new_pos)
|
|
|
|
|
|
--draw the head and the first body segment
|
|
wormball.place_node(arena.players[pl_name].nodes,arena.players[pl_name].direction,old_dir,look_dir,color)
|
|
|
|
--subtract 1 from the player's score
|
|
arena.players[pl_name].score = arena.players[pl_name].score - 1
|
|
|
|
--send an HUD message
|
|
arena_lib.HUD_send_msg('broadcast', pl_name, 'YUCK! You Lost a point.', 2, 'wormball_yuck',0xFF0000)
|
|
|
|
|
|
--move the player's attached entity
|
|
local att = player:get_attach()
|
|
if att then
|
|
att:move_to(new_pos, true)
|
|
end
|
|
|
|
--we will be removing the tail, to visually shorten the worm
|
|
remove_tail = true
|
|
|
|
--we will not be moving forward NEXT turn (after eating a same color dot)
|
|
arena.players[pl_name].move = false
|
|
|
|
else
|
|
arena.players[pl_name].move = true
|
|
end
|
|
|
|
|
|
|
|
|
|
elseif string.find(new_node,'wormball:power_') then --we found a powerup dot!
|
|
|
|
|
|
|
|
if arena.players[pl_name].move == true then
|
|
|
|
--delete the memory of the dot that was 'eaten'
|
|
for _,dot_pos in pairs(arena.dots) do
|
|
if dot_pos == new_pos then
|
|
table.remove(arena.dots,_)
|
|
end
|
|
end
|
|
|
|
|
|
|
|
-- place the head location into the player's body locations table
|
|
table.insert(arena.players[pl_name].nodes,1,new_pos)
|
|
|
|
|
|
--draw the head and the first body segment
|
|
wormball.place_node(arena.players[pl_name].nodes,arena.players[pl_name].direction,old_dir,look_dir,color)
|
|
|
|
|
|
-- add 1 to the player's score
|
|
arena.players[pl_name].score = arena.players[pl_name].score + 1
|
|
|
|
|
|
-- send HUD message
|
|
arena_lib.HUD_send_msg('broadcast', pl_name, 'Yay! You are now '..arena.players[pl_name].score..' long.', 2, 'wormball_powerup',0x00FF11)
|
|
|
|
|
|
--move the player's attached entity
|
|
local att = player:get_attach()
|
|
if att then
|
|
att:move_to(new_pos, true)
|
|
end
|
|
|
|
--we will be not removing the tail this round, we are growing
|
|
remove_tail = false
|
|
|
|
else
|
|
arena.players[pl_name].move = true
|
|
end
|
|
|
|
|
|
else --we have run into an arena obstacle, another snake, or ourselves set died to true so the player will be removed and their peices converted
|
|
|
|
|
|
|
|
if not arena.in_celebration then
|
|
|
|
|
|
arena.players[pl_name].alive = false
|
|
|
|
died = true
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
----------------------------------------------------------------------------
|
|
--- the following code we run regardless of whether the player is alive or not
|
|
----------------------------------------------------------------------------
|
|
|
|
if remove_tail == true then --true by default, only false when having eaten a 'good' dot
|
|
|
|
local len = #arena.players[pl_name].nodes --worm length
|
|
|
|
local tail_pos = arena.players[pl_name].nodes[len] --position to change
|
|
|
|
if tail_pos then --nil check
|
|
|
|
-- if the player is dead, then we will convert them into food (of their color)
|
|
-- if they are still alive, then we will place air there to delete the tail
|
|
if arena.players[pl_name].alive == false then
|
|
local item = "wormball:power_"..color
|
|
minetest.set_node(tail_pos, {name=item})
|
|
else
|
|
minetest.set_node(tail_pos, {name="air"})
|
|
end
|
|
--forget the tail position in the player's body postions
|
|
table.remove(arena.players[pl_name].nodes,len)
|
|
end
|
|
end
|
|
|
|
-- if players have no nodes, eliminate them. (this can happen if they eat a 'bad' dot while only 1 long) if they have run into an obstacle, elim also
|
|
if #arena.players[pl_name].nodes == 0 or died == true then --if you eat a bad powerup with only 1 point, then you lose!
|
|
|
|
if arena.mode and arena.mode == 'singleplayer' then
|
|
minetest.chat_send_player(pl_name, 'Game Over! Your score is '..arena.players[pl_name].score)
|
|
end
|
|
|
|
--esp for multiplayer games, save the score in the record for end of game documentation.
|
|
table.insert( arena.multi_scores , 1 , { arena.players[ pl_name ] .score , pl_name } )
|
|
|
|
for _,node_pos in pairs(arena.players[pl_name].nodes) do
|
|
local item = "wormball:power_"..color
|
|
minetest.set_node(node_pos, {name=item})
|
|
end
|
|
arena.players[pl_name].alive = false
|
|
|
|
wormball.detach(pl_name)
|
|
if arena.mode == 'multiplayer' then
|
|
minetest.sound_play('sumo_lose', {
|
|
to_player = pl_name,
|
|
gain = 2.0,
|
|
})
|
|
end
|
|
minetest.after(.3,function(pl_name, arena)
|
|
if arena.mode == 'singleplayer' then
|
|
arena_lib.load_celebration('wormball', arena, pl_name)
|
|
else
|
|
arena_lib.remove_player_from_arena(pl_name, 1)
|
|
end
|
|
|
|
end,pl_name, arena)
|
|
|
|
end
|
|
end
|
|
end
|
|
|
|
end
|
|
end)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|