Traitor/mods/lobby/buttons.lua

415 lines
20 KiB
Lua

--[[
Ideally all the level data should be read/written from storage, rather than node metadata. Maybe store in metadata until the final submission.
This would allow for a node to be connected to an already existing ID, which could be useful if a spawn changed for any reason.
This can be changed in the future I expect, as the data all exists in both locations currently.
In addition to the already existing level data it would be nice to capture some statistics that the owner/builders could see.
Stats might include things like, number of times played, number of singleplayer visits, average group size, and counts of player/traitor wins.
These stats could help a level editor decide if they need to change the amount of XP or tasks in the level.
]]
local esc = minetest.formspec_escape
local function random_pos()
local pos_x = math.random(-30000,30000)
local pos_y = math.random(-100,30000)
local pos_z = math.random(-30000,30000)
local pos = (pos_x..','..pos_y..','..pos_z)
return pos
end
local function button_create(id, name, player_count, xp, pos_string, desc, builders, solo)
local pos = minetest.string_to_pos(pos_string)
local clean_pos = pos.x..','..pos.y..','..pos.z
builders = builders or ''
solo = solo or 'true'
local formspec =
'formspec_version[3]'..
'size[12,8.5]'..
'label[.5,.75;Map ID: '..esc(id)..']'..
'field[4.375,.5;3.25,.5;name;Map Name;'..esc(name)..']'..
'tooltip[name;Will be used in chat messages and as the infotext]'..
'checkbox[8.25,.75;solo;Allow Singleplayer;'..solo..']'..
'field[0.5,1.5;3.25,.5;player_count;Required # of Players;'..esc(player_count)..']'..
'field[4.375,1.5;3.25,.5;xp;Required XP;'..esc(xp)..']'..
'field[8.25,1.5;3.25,.5;pos;Spawn POS (x,y,z);'..esc(clean_pos)..']'..
'field[0.5,2.5;11,.5;builders;Allowed Builders;'..esc(builders)..']'..
'tooltip[builders;List of players that should be able to visit the map as builders.]'..
'textarea[0.5,3.5;11,3.5;desc;Description of map;'..esc(desc)..']'..
'button_exit[1,7.25;2,.75;save;Save]'..
'tooltip[save;Saves values without making the button active.]'..
'button_exit[5,7.25;2,.75;submit;Submit]'..
'tooltip[submit;Saves values and makes the button active.]'..
'button_exit[9,7.25;2,.75;discard;Unclaim]'..
'tooltip[discard;Clears all values and unclaims the button.]'
return formspec
end
local function button_display(id, name, owner, player_count, xp, pos, desc)
local formspec =
'formspec_version[3]'..
'size[12,8]'..
--'image_button[10.5,.5;1,1;lobby_report_icon.png;report;;;false]'..
--'tooltip[report;Report this level as incomplete.]'..
'style_type[textarea;textcolor=#ccddff;border=true]'..
'label[0.5,.7;Map Name:]'..
'textarea[3.25,.55;7,.4;;;'..esc(name)..' (by '..owner..')]'..
'label[0.5,1.1;Map ID:]'..
'textarea[3.25,.95;7,.4;;;'..esc(id)..']'..
'label[0.5,1.5;Req. # of Players:]'..
'textarea[3.25,1.35;4,.4;;;'..esc(player_count)..']'..
'label[0.5,1.9;Req. XP:]'..
'textarea[3.25,1.75;4,.4;;;'..esc(xp)..']'..
'label[0.5,2.5;Description:]'..
'textarea[0.5,2.75;11,2.75;;;'..esc(desc)..']'..
'style_type[textarea;textcolor=#999999]'..
'textarea[0.5,6.4;9,1.1;;;Sneak punch to visit without starting a game. Return with /lobby.\n'..
'Be sure all interested parties are standing within three nodes of the button before punching.]'..
'button[10,6.75;1.5,0.75;stats;Stats]'
return formspec
end
local button_claim =
'formspec_version[3]'..
'size[12,6]'..
'label[4,1;Claim this button (15XP)]'..
'button_exit[3.5,4;4,1;claim;Claim This Button]'
minetest.register_node('lobby:button_0', {
description = 'Unconfigured Button',
tiles = {'lobby_button_0.png'},
groups = {breakable=1, not_in_creative_inventory=1},
light_source = 5,
after_place_node = function(pos, placer)
local meta = minetest.get_meta(pos)
meta:set_string('infotext', 'Unconfigured/Unclaimed button')
meta:set_string('name', '')
meta:set_string('player_count', '')
meta:set_string('xp', '')
meta:set_string('pos', '')
meta:set_string('desc', '')
meta:set_string('builders', '')
meta:set_string('solo', 'true')
end,
on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
local player_name = clicker:get_player_name()
local meta = minetest.get_meta(pos)
local owner = meta:get_string('owner')
local id = meta:get_string('id')
local name = meta:get_string('name')
local player_count = meta:get_string('player_count')
local xp = meta:get_string('xp')
local pos = meta:get_string('pos')
local desc = meta:get_string('desc')
local builders = meta:get_string('builders')
local solo = meta:get_string('solo')
if owner == '' then
meta:set_string('formspec', button_claim)
elseif owner == player_name or minetest.check_player_privs(clicker, {server = true}) then
meta:set_string('formspec', button_create(id, name, player_count, xp, pos, desc, builders, solo))
else
minetest.show_formspec(player_name, 'lobby:button_display', button_display(id, name, owner, player_count, xp, pos, desc))
lobby.stat[player_name] = id
end
end,
on_punch = function(pos, node, puncher, pointed_thing)
local meta = minetest.get_meta(pos)
local owner = meta:get_string('owner')
local loc = meta:get_string('pos')
local pos = minetest.string_to_pos(loc)
local name = puncher:get_player_name()
local map_id = meta:get_string('id')
local builders = meta:get_string('builders')
local player_attributes = puncher:get_meta()
if name == owner then
puncher:set_pos(pos)
minetest.chat_send_player(name, 'Taking you to the level. Return to the lobby with /spawn')
player_attributes:set_string('mode', 'builder')
lobby.game[name] = map_id..'_builder'
elseif minetest.check_player_privs(name, { creative = true }) and puncher:get_player_control().sneak and string.find(builders, name) then
puncher:set_pos(pos)
minetest.chat_send_player(name, 'Taking you to the level. Return to the lobby with /spawn')
player_attributes:set_string('mode', 'builder')
lobby.game[name] = map_id..'_builder'
end
end,
on_receive_fields = function(pos, formname, fields, sender)
local meta = minetest.get_meta(pos)
local owner = meta:get_string('owner')
local player_name = sender:get_player_name()
if fields ['claim'] then --Should make it so only builders can claim a button.
if lobby.take_xp(sender, 15) then
minetest.show_formspec(player_name, 'lobby:get_started', lobby.getting_started)
meta:set_string('owner', player_name)
local map_id = lobby.create_id(player_name)
lobby.savedata.IDs[map_id] = true
meta:set_string('id', map_id)
local pos_string = random_pos()
local pos = minetest.string_to_pos(pos_string)
local run = true
while run do
if not minetest.is_protected(pos, sender) and run then
minetest.load_area(pos)
meta:set_string('pos', pos_string)
worldedit.cube(pos, 5, 1, 5, 'color:grey_blocks', false)
minetest.set_node(pos, {name = 'lights:block_4'})
run = false
end
end
else
minetest.chat_send_player(player_name, 'You need more XP!')
end
end
if owner == player_name or minetest.check_player_privs(sender, {server = true}) then
local map_id = meta:get_string('id')
if not string.find(map_id, owner) then --Using old player assigned IDs
local map_id_new = lobby.create_id(owner)
lobby.savedata.IDs[map_id_new] = true
meta:set_string('id', map_id_new)
if lobby.savedata.stats[map_id] then
local stats = lobby.savedata.stats[map_id] or {}
local stats_new = {}
stats_new.solo_play = stats.solo_play or 0
stats_new.multi_play = stats.multi_play or 0
stats_new.winner_traitor = stats.winner_traitor or 0
stats_new.winner_team = stats.winner_team or 0
stats_new.player_count = stats.player_count or 0
stats_new.max_players = stats.max_players or 0
lobby.savedata.stats[map_id_new] = stats_new
lobby.savedata.IDs[map_id] = nil
lobby.savedata.stats[map_id] = nil
lobby.savedata.data[map_id] = nil
end
map_id = map_id_new
end
if fields.solo then
meta:set_string('solo', fields.solo)
elseif fields ['save'] then
meta:set_string('infotext', fields.name)
meta:set_string('name', fields.name)
meta:set_string('player_count', fields.player_count)
meta:set_string('xp', fields.xp)
meta:set_string('pos', fields.pos)
meta:set_string('desc', fields.desc)
meta:set_string('builders', fields.builders)
elseif fields ['submit'] then
meta:set_string('infotext', fields.name)
meta:set_string('name', fields.name)
meta:set_string('player_count', fields.player_count)
meta:set_string('xp', fields.xp)
meta:set_string('pos', fields.pos)
meta:set_string('desc', fields.desc)
meta:set_string('builders', fields.builders)
local dist = tonumber(minetest.settings:get("map_generation_limit") or 31000)
local x, y, z = string.match(fields.pos, "^(-?%d+),(-?%d+),(-?%d+)$")
local pos_x = tonumber(x)
local pos_y = tonumber(y)
local pos_z = tonumber(z)
local saveable = 0
if fields.name == '' then
minetest.chat_send_player(player_name, 'Sorry, a name is required. You can always change it later')
saveable = 1
end
if pos_x > dist or pos_x < -dist or pos_y > dist or pos_y < -dist or pos_z > dist or pos_z < -dist then
minetest.chat_send_player(player_name, 'Double check your spawn pos, values seem to be invalid.')
saveable = 1
end
if not lobby.is_integer(fields.player_count ) then
minetest.chat_send_player(player_name, 'An Integer is a whole number, your required player count is not.')
saveable = 1
end
if not lobby.is_integer(fields.xp) then
minetest.chat_send_player(player_name, 'An Integer is a whole number, your required XP amount is not.')
saveable = 1
end
if saveable == 0 then
local data = {}
data.builders = fields.builers
data.description = fields.desc
data.level_pos = {x = pos_x, y = pos_y+1, z = pos_z}
data.map_name = fields.name
data.owner_name = owner
data.required_players = math.max(fields.player_count, 3)
data.xp = math.max(tonumber(fields.xp),1)
lobby.savedata.data[map_id] = data
lobby.savedata.name_2_id[fields.name] = map_id
lobby.savedata.id_2_name[map_id] = fields.name
lobby.savedata.IDs[map_id] = true
minetest.swap_node(pos, {name = 'lobby:button_1'})
end
elseif fields ['discard'] then
meta:set_string('owner', '')
meta:set_string('infotext', 'Unconfigured/Unclaimed Button')
meta:set_string('id', '')
meta:set_string('name', '')
meta:set_string('player_count', '')
meta:set_string('xp', '')
meta:set_string('pos', '')
meta:set_string('desc', '')
meta:set_string('builders', '')
meta:set_string('solo', 'true')
local stats = lobby.savedata.stats[map_id]
if not stats then
lobby.savedata.IDs[map_id] = nil
end
lobby.give_xp(sender, 15)
end
end
end,
})
minetest.register_node('lobby:button_1', {
description = 'Configured Button',
tiles = {'lobby_button_1.png'},
groups = {not_in_creative_inventory=1},
drop = 'lobby:button_0',
light_source = 14,
on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
local meta = minetest.get_meta(pos)
local id = meta:get_string('id')
local item = itemstack:get_name()
local player_name = clicker:get_player_name()
local owner = meta:get_string('owner')
if item == 'creative:tool_breaking' or item == 'tasks:configurator' then
if owner == player_name or minetest.check_player_privs(clicker, {server = true}) then
minetest.swap_node(pos, {name = 'lobby:button_0'})
end
else
local name = meta:get_string('name')
local player_count = meta:get_string('player_count')
local xp = meta:get_string('xp')
local pos = meta:get_string('pos')
local desc = meta:get_string('desc')
local owner = meta:get_string('owner')
minetest.show_formspec(player_name, 'lobby:button_display', button_display(id, name, owner, player_count, xp, pos, desc))
lobby.stat[player_name] = id
end
end,
on_punch = function(pos, node, puncher, pointed_thing)
local name = puncher:get_player_name()
local meta = minetest.get_meta(pos)
local map_id = meta:get_string('id')
local game_data = lobby.savedata.data[map_id]
local game_pos = game_data['level_pos']
local builders = meta:get_string('builders')
local player_attributes = puncher:get_meta()
if minetest.check_player_privs(name, { creative = true }) and puncher:get_player_control().sneak and string.find(builders, name) then
puncher:set_pos({x=game_pos.x+(math.random(-2,2)),y=game_pos.y+2,z=game_pos.z+(math.random(-2,2))})
minetest.chat_send_player(name, 'Taking you to the level. Return to the lobby with /spawn')
player_attributes:set_string('mode', 'builder')
lobby.game[name] = map_id..'_builder'
else
local needed_players = tonumber(meta:get_string('player_count'))
local objs = minetest.get_objects_inside_radius(pos, 3)
local player_count = 0
local map_players = {}
for _, obj in pairs(objs) do
if obj:is_player() then
player_count = player_count + 1
local player_name = obj:get_player_name()
table.insert(map_players, player_name)
end
end
if player_count >= needed_players then
if lobby.map[map_id] == 0 or lobby.map[map_id] == nil then
minetest.chat_send_player(name, 'you can start the game!')
minetest.log('action', name..' just started a game on '..map_id)
lobby.map[map_id] = player_count
lobby.update_stats(map_id, 'player', '', player_count)
local traitor = math.random(1, player_count)
lobby.xp[map_id] = 0
lobby.votes[map_id] = 0
lobby.corpses[map_id] = {}
lobby.sabotage[map_id] = false
lobby.sabotage_level[map_id] = 5
sabotage.timer[map_id] = 0
for i=1,player_count do
minetest.chat_send_player(map_players[i], 'Hold tight, loading the level.')
local player = minetest.get_player_by_name(map_players[i])
local player_attributes = player:get_meta()
player_attributes:set_string('voting', 'false')
player_attributes:set_string('mode', 'player')
lobby.voted[map_players[i]] = true
if i == traitor then
minetest.chat_send_player(map_players[i], 'You\'re the imposter. Try to kill all the other players.')
minetest.chat_send_player(map_players[i], 'You\'ll get a weapon in a few seconds.')
lobby.traitors[map_id] = map_players[i]
local player = minetest.get_player_by_name(map_players[i])
local player_inv = player:get_inventory()
minetest.after(10, function()
player_inv:add_item('main', 'lobby:shank')
end)
player_attributes:set_string('mode', 'traitor')
end
local privs = minetest.get_player_privs(map_players[i])
local player_inv = player:get_inventory()
player_inv:set_list('main', {})
player_inv:set_size('main', 16)
lobby.game[map_players[i]] = map_id
player:set_pos({x=game_pos.x+(math.random(-2,2)),y=game_pos.y,z=game_pos.z+(math.random(-2,2))})
privs.fly = nil
privs.fast = nil
privs.creative = nil
privs.areas = nil
minetest.set_player_privs(map_players[i], privs)
end
else
minetest.chat_send_player(puncher:get_player_name(), 'You can\'t join an in progress game.\nYou can possibly play solo by sneak+punching.')
end
elseif puncher:get_player_control().sneak then
local solo = meta:get_string('solo')
if solo == 'true' then
lobby.update_stats(map_id, 'solo')
local name = puncher:get_player_name()
puncher:set_nametag_attributes({
color = {a = 0, r = 255, g = 255, b = 255}
})
puncher:set_properties({visual_size = {x = 0, y = 0}, collisionbox = {-0.3, 0.0, -0.3, 0.3, 1.7, 0.3}})
lobby.builder_to_player(puncher)
puncher:set_pos(game_pos)
lobby.game[name] = map_id..'_solo'
minetest.chat_send_player(name, 'Travel back to the lobby with the /lobby chat command.')
else
minetest.chat_send_player(name, 'Sorry this level is not able to be played solo.')
end
else
minetest.chat_send_player(puncher:get_player_name(), 'Maybe not enough players? Right click the button for more information.')
end
end
end,
})
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname == 'lobby:button_display' then
local name = player:get_player_name()
local map_id = lobby.stat[name]
if fields.stats then
minetest.show_formspec(name, 'lobby:stats', lobby.retrieve_stats(map_id))
elseif fields.report then
if not lobby.is_builder(player) then
minetest.chat_send_player(name, 'Sorry, only builders can report levels.')
else
minetest.show_formspec(name, 'lobby:report', lobby.report_level(map_id))
end
end
elseif formname == 'lobby:report' then
local name = player:get_player_name()
local map_id = lobby.stat[name]
if fields.save then
local reason = fields.reason
if reason then
if lobby.take_xp(player, 1) then
local input = fields.input
print (reason)
print (input)
minetest.chat_send_player(name, 'Thanks for your report')
else
minetest.chat_send_player(name, 'Sorry, you need 1 XP to submit this report.')
end
else
minetest.chat_send_player(name, 'You need to select a reason.')
end
end
end
end)