diff --git a/chat.lua b/chat.lua index 9d80eed..3e10d9d 100644 --- a/chat.lua +++ b/chat.lua @@ -10,17 +10,127 @@ minetest.register_privilege("towny_admin", { give_to_singleplayer = false }) +-- API + +-- Send message to all town members who are online +function towny.chat:announce_to_members(town,message) + local tdata = towny.towns[town] + if tdata then + for member in pairs(tdata.members) do + if minetest.get_player_by_name(member) then + minetest.chat_send_player(member,message) + end + end + end +end + -- Commands +local function invite_player(town,player,target) + local utown = towny:get_player_town(player) + if not utown or utown ~= town then + return false, "You are not in a town." + end + + local target_town = towny:get_player_town(target) + if target_town then + return false, "This player is already in a town!" + end + + if towny.chat.invites[town.."-"..target] then + return false, "This player has already been invited to join your town!" + end + + local tdata = towny.towns[town] + + minetest.chat_send_player(target, "You have been invited to join town '"..tdata.name.."' by "..player) + minetest.chat_send_player(target, "You can accept this invite by typing '/town invite accept' or deny '/town invite deny'") + + towny.chat.invites[town.."-"..target] = { rejected = false, town = town, player = target, invited = player } + return true, "Player "..target.." has been invited to join your town." +end + +local function join_town(town,player,from_invite) + local tdata = towny.towns[town] + if not tdata then return false, "No such town" end + if (not from_invite and not tdata.flags['joinable']) then return false, "You cannot join this town." end + towny.chat:announce_to_members(town, minetest.colorize("#02aacc", player.." has joined the town!")) + minetest.chat_send_player(player, "You have successfully joined the town '"..tdata.name.."'!") + tdata.members[player] = {} + return true +end + +local function invite_respond(player,response) + local utown = towny:get_player_town(player) + if utown or utown ~= town then + return false, "You are already in a town." + end + + for id,data in pairs(towny.chat.invites) do + if data.player == player then + if not data.rejected then + if response == true then + towny.chat.invites[id] = nil + return join_town(data.town,player,true) + else + towny.chat.invites[id] = { rejected = true } + return true, "You have rejected the join request." + end + end + end + end + + return false, "You do not have any pending invites." +end + +local function send_flags (player,flags,message) + local shiny = {} + for flag,value in pairs(flags) do + if type(value) == "table" then + if value.x and value.y and value.z then + value = minetest.pos_to_string(value) + else + value = dump(value) + end + elseif type(value) == "boolean" then + local str_value = "true" + if value == false then str_value = "false" end + value = str_value + end + shiny[#shiny+1] = flag..": "..value + end + + return true, message ..": "..table.concat( shiny, ", " ) +end + local function town_command (name, param) if not minetest.get_player_by_name(name) then return false, "Can't run command on behalf of offline player." end local pr1, pr2 = string.match(param, "^([%a%d_-]+) (.+)$") local town = towny:get_player_town(name) -- Pre town requirement + local print_town_info = nil if (pr1 == "create" or pr1 == "new") and pr2 then return towny:create_town(nil, name, pr2) + elseif (pr1 == "invite" and not minetest.get_player_by_name(pr2)) then + local tyes = pr2:lower() + return invite_respond(name, (tyes == "accept" or tyes == "yes" or tyes == "y")) + elseif pr1 == "join" and towny:get_town_by_name(pr2) and not town then + return join_town(pr2,name,false) + elseif pr1 == "show" or pr1 == "info" then + if towny:get_town_by_name(pr2) then + print_town_info = pr2 + else + return false, "No such town." + end + elseif param == "" and town then + print_town_info = town + end + + -- Print town information + if print_town_info then + return false, "Not yet implemented!" end if not town then @@ -39,6 +149,11 @@ local function town_command (name, param) elseif param == "visualize" then towny.regions:visualize_town(town) return true + elseif param == "flags" then + local flags = towny:get_flags(town) + if flags then + return send_flags(player,flags,"Flags of your town") + end elseif (param == "delete" or param == "abandon") or (pr1 == "delete" or pr1 == "abandon") then if towny.chat['delete_verify_' .. name] and pr2 == "I WANT TO DELETE MY TOWN" then towny.chat['delete_verify_' .. name] = nil @@ -49,12 +164,45 @@ local function town_command (name, param) "WARNING! Deleting your town will render ALL of the buildings in it without protection!")) return false, "Please run the command again with 'I WANT TO DELETE MY TOWN' in all caps written after it." end + elseif pr1 == "kick" then + return towny:kick_member(town,name,pr2) + elseif pr1 == "set" then + local flag, value = string.match(pr2, "^([%a%d_-]+) (.+)$") + return towny:set_town_flags(nil,name,flag,value) end -- Plot management commands if pr1 == "plot" then local pl1, pl2 = string.match(pr2, "^([%a%d_-]+) (.+)$") - + if pr2 == "claim" then + return towny:claim_plot(nil,name) + elseif pr2 == "abandon" then + return towny:abandon_plot(nil,name) + elseif pr2 == "delete" then + return towny:delete_plot(nil,name) + elseif pr2 == "flags" then + local flags = towny:get_plot_flags(town,nil,name) + if flags then + return send_flags(player,flags,"Flags of this plot") + else + return false, "There's no plot here." + end + elseif pl1 == "set" and pl2 then + local flag, value = string.match(pl2, "^([%a%d_-]+) (.+)$") + return towny:set_plot_flags(nil,name,flag,value) + elseif pl1 == "member" and pl2 then + local action, user = string.match(pl2, "^([%a%d_-]+) (.+)$") + if action == "add" then + return towny:plot_member(nil,name,user,1) + elseif action == "remove" or action == "del" or action == "kick" then + return towny:plot_member(nil,name,user,0) + elseif action == "set" then + local target, flag, value = string.match(user, "^([%a%d_-]+) ([%a%d_-]+) (.+)$") + return towny:set_plot_member_flags(nil,name,target,flag,value) + end + end + elseif pr1 == "invite" and minetest.get_player_by_name(pr2) then + return invite_player(town,name,pr2) end return false, "Invalid command usage." diff --git a/init.lua b/init.lua index ec24733..919a84d 100644 --- a/init.lua +++ b/init.lua @@ -3,19 +3,23 @@ local modpath = minetest.get_modpath(minetest.get_current_modname()) towny = { - claimbonus = minetest.settings:get('towny_claim_bonus') or 8, - regions = { - size = minetest.settings:get('towny_claim_size') or 16, - maxclaims = minetest.settings:get('towny_claim_max') or 128, - distance = minetest.settings:get('towny_distance') or 80, + claimbonus = tonumber(minetest.settings:get('towny_claim_bonus')) or 8, + regions = { + size = tonumber(minetest.settings:get('towny_claim_size')) or 16, + maxclaims = tonumber(minetest.settings:get('towny_claim_max')) or 128, + distance = tonumber(minetest.settings:get('towny_distance')) or 80, -- Regions loaded into memory cache, see "Town regions data structure" memloaded = {}, }, -- See "Town data structure" flatfile = {}, - towns = {}, - chat = {}, + towns = {}, + chat = { + chatmod = (minetest.settings:get('towny_chat') == "true") or true, + questionaire = (minetest.settings:get('towny_questionaire') == "true") or true, + invites = {}, + }, -- Set to true if files need to be updated dirty = false, diff --git a/town.lua b/town.lua index 21d538f..3f7a857 100644 --- a/town.lua +++ b/town.lua @@ -17,6 +17,7 @@ function towny:get_player_town(name) end function towny:get_town_by_name(name) + if not name then return nil end for town,data in pairs(towny.towns) do if data.name:lower() == name:lower() then return town @@ -165,7 +166,7 @@ function towny:abridge_town(pos,player) return true end -function towny:leave_town(player) +function towny:leave_town(player,kick) local town = towny:get_player_town(player) if not town then return err_msg(player, "You're not currently in a town!") @@ -207,11 +208,39 @@ function towny:leave_town(player) pdata.members = members end + local msg = "You successfully left the town." + if kick then + msg = "You were kicked form town." + end + towny:mark_dirty(town, false) - minetest.chat_send_player(player, "You successfully left the town.") + minetest.chat_send_player(player, msg) return true end +function towny:kick_member(town,player,member) + local towny_admin = minetest.check_player_privs(player, { towny_admin = true }) + local data = towny.towns[town] + + if data.mayor ~= player and not towny_admin then + return err_msg(player, "You do not have permission to kick people from this town.") + end + + if not data.members[member] then + return err_msg(player, "User "..member.." is not in this town.") + end + + if member == data.mayor then + return err_msg(player, "You cannot kick the town mayor.") + end + + if player == member then + return err_msg(player, "You cannot kick yourself from town.") + end + + return towny:leave_town(member,true) +end + function towny:delete_town(pos,player) local towny_admin = minetest.check_player_privs(player, { towny_admin = true }) if not pos then @@ -267,7 +296,7 @@ function towny:delete_plot(pos,player) return err_msg(player, "You do not have permission to delete this plot.") end - towny.regions:set_plot(c,t,nil) + towny.regions:set_plot(c[1],t,nil) data.plots[p] = nil towny:mark_dirty(t, true) @@ -320,12 +349,174 @@ function towny:create_plot(pos,player) return true end -local function flag_typeify(value) +function towny:claim_plot(pos,player) + if not pos then + pos = minetest.get_player_by_name(player):get_pos() + end + + local town = towny:get_player_town(player) + if not town then + return err_msg(player, "You're not currently in a town!") + end + + local t,p,c = towny.regions:get_town_at(pos) + if not t or t ~= town then + return err_msg(player, "You are not in any town you can modify.") + end + + local tdata = towny.towns[t] + if p ~= nil then + local plot_data = tdata.plots[p] + if plot_data.flags['claimable'] or player == tdata.mayor then + if plot_data.owner == player or plot_data.members[player] then + return err_msg(player, "You are already a member of this plot.") + end + + -- TODO: enconomy + tdata.plots[p] = { + owner = player, + members = {[player] = {}}, + flags = {}, + } + + towny:mark_dirty(t, false) + + minetest.chat_send_player(player, "Successfully claimed the plot!") + towny.regions:visualize_radius(vector.subtract(c[1], {x=tr/2,y=tr/2,z=tr/2})) + + return true + else + return err_msg(player, "This plot is not for sale.") + end + end + + return towny:create_plot(pos,player) +end + +function towny:abandon_plot(pos,player) + if not pos then + pos = minetest.get_player_by_name(player):get_pos() + end + + local town = towny:get_player_town(player) + if not town then + return err_msg(player, "You're not currently in a town!") + end + + local t,p,c = towny.regions:get_town_at(pos) + if not t or t ~= town then + return err_msg(player, "You are not in any town you can modify.") + end + + if p == nil then + return err_msg(player, "There is no plot here.") + end + + local tdata = towny.towns[t] + local pdata = tdata.plots[p] + + if not pdata.members[player] then + return err_msg(player, "You are not a member of this plot.") + end + + -- Update plot members + local members = {} + if pdata.owner == player then + pdata.owner = nil + if pdata.flags["greeting"] ~= nil then + pdata.flags["greeting"] = nil + end + end + + for mem,dat in pairs(pdata.members) do + if mem ~= player then + -- Transfer ownership to the first other member + if pdata.owner == nil then + pdata.owner = mem + end + members[mem] = dat + end + end + pdata.members = members + towny:mark_dirty(t, false) + minetest.chat_send_player(player, "Successfully abandoned the plot!") + + return true +end + +function towny:plot_member(pos,player,member,action) + local towny_admin = minetest.check_player_privs(player, { towny_admin = true }) + if not pos then + pos = minetest.get_player_by_name(player):get_pos() + end + + local town = towny:get_player_town(player) + if not town then + return err_msg(player, "You're not currently in a town!") + end + + local t,p,c = towny.regions:get_town_at(pos) + if not t or t ~= town then + return err_msg(player, "You are not in any town you can modify.") + end + + if p == nil then + return err_msg(player, "There is no plot here.") + end + + local tdata = towny.towns[t] + local pdata = tdata.plots[p] + + if pdata.owner ~= player and player ~= tdata.mayor and not towny_admin then + return err_msg(player, "You do not have permission to modify this plot.") + end + + if not tdata.members[member] then + return err_msg(player, "User '"..member.."' is not part of this town.") + end + + -- Update plot members + local members = {} + local action_desc = "add yourself to" + if action == 0 then + action_desc = "remove yourself from" + end + + if member == pdata.owner then + return err_msg(player, "You cannot "..action_desc.." from this plot.") + end + + if action == 0 then + action_desc = "removed "..member.." from" + for mem,dat in pairs(pdata.members) do + if mem ~= member then + -- Transfer ownership to the first other member + members[mem] = dat + end + end + else + action_desc = "added "..member.." to" + members = pdata.members + members[member] = {} + end + + pdata.members = members + towny:mark_dirty(t, false) + minetest.chat_send_player(player, "Successfully "..action_desc.." plot!") + + return true +end + +local function flag_typeify(value,pos) if type(value) == "string" then if value == "true" then value = true elseif value == "false" then value = false + elseif value == "here" then + value = pos + elseif value == "none" or value == "null" or value == "nil" then + value = nil elseif tonumber(value) ~= nil then value = tonumber(value) elseif minetest.string_to_pos(value) ~= nil then @@ -335,7 +526,10 @@ local function flag_typeify(value) return value end +-- Set flags + function towny:set_plot_flags(pos,player,flag,value) + if not flag then return false end local towny_admin = minetest.check_player_privs(player, { towny_admin = true }) if not pos then pos = minetest.get_player_by_name(player):get_pos() @@ -351,7 +545,7 @@ function towny:set_plot_flags(pos,player,flag,value) return err_msg(player, "You are not in any town you can modify.") end - if p ~= nil then + if p == nil then return err_msg(player, "There is no plot here! Please stand in the plot you wish to modify.") end @@ -362,11 +556,50 @@ function towny:set_plot_flags(pos,player,flag,value) end minetest.chat_send_player(player, "Successfully set the plot flag '" .. flag .."' to '" .. value .. "'!") + plot_data.flags[flag] = flag_typeify(value,pos) + towny:mark_dirty(t, false) +end + +function towny:set_plot_member_flags(pos,player,member,flag,value) + if not member or not flag then return false end + local towny_admin = minetest.check_player_privs(player, { towny_admin = true }) + if not pos then + pos = minetest.get_player_by_name(player):get_pos() + end + + local town = towny:get_player_town(player) + if not town and not towny_admin then + return err_msg(player, "You're not currently in a town!") + end + + local t,p,c = towny.regions:get_town_at(pos) + if not t or (t ~= town and not towny_admin) then + return err_msg(player, "You are not in any town you can modify.") + end + + if p == nil then + return err_msg(player, "There is no plot here! Please stand in the plot you wish to modify.") + end + + local data = towny.towns[t] + local plot_data = data.plots[p] + if data.mayor ~= player and plot_data.owner ~= player and not towny_admin then + return err_msg(player, "You do not have permission to modify this plot.") + end + + if not plot_data.members[member] then + return err_msg(player, "There is no such member in this plot.") + end + + if flag == "build" then flag = "plot_build" end + + minetest.chat_send_player(player, "Successfully set the plot member "..member.."'s flag '" .. flag .."' to '" .. value .. "'!") + plot_data.members[member][flag] = flag_typeify(value,pos) towny:mark_dirty(t, false) - plot_data.flags[flag] = flag_typeify(value) end function towny:set_town_flags(pos,player,flag,value) + if not flag then return false end local towny_admin = minetest.check_player_privs(player, { towny_admin = true }) if not pos then pos = minetest.get_player_by_name(player):get_pos() @@ -392,8 +625,33 @@ function towny:set_town_flags(pos,player,flag,value) end minetest.chat_send_player(player, "Successfully set the town flag '" .. flag .."' to '" .. value .. "'!") + data.flags[flag] = flag_typeify(value,pos) towny:mark_dirty(t, false) - data.flags[flag] = flag_typeify(value) +end + +-- Get flags + +function towny:get_flags(town,plot) + local tdata = towny.towns[town] + if not tdata then return nil end + if not plot then return tdata.flags end + if not tdata.plots[plot] then return nil end + return tdata.plots[plot].flags +end + +function towny:get_plot_flags(town,pos,player) + local towny_admin = minetest.check_player_privs(player, { towny_admin = true }) + if not pos and player then + pos = minetest.get_player_by_name(player):get_pos() + end + + local t,p,c = towny.regions:get_town_at(pos) + if not t or (t ~= town and not towny_admin) then + return err_msg(player, "You are not in any town you can access.") + end + + if not t or not p then return nil end + return towny:get_flags(t,p) end function towny:get_claims_total(town)