factions/factions.lua

1402 lines
43 KiB
Lua

--read some basic information
local factions_worldid = minetest.get_worldpath()
--! @class factions
--! @brief main class for factions
factions = {}
--! @brief runtime data
factions.factions = {}
factions.parcels = {}
factions.players = {}
---------------------
--! @brief returns whether a faction can be created or not (allows for implementation of blacklists and the like)
--! @param name String containing the faction's name
factions.can_create_faction = function(name)
if #name > factions_config.faction_name_max_length then
return false
elseif factions.factions[name] then
return false
else
return true
end
end
factions.Faction = {
}
util = {
coords3D_string = function(coords)
return coords.x..", "..coords.y..", "..coords.z
end
}
factions.Faction.__index = factions.Faction
starting_ranks = {["leader"] = {"build","door","container","name","description","motd","invite","kick"
,"title","set_spawn","unset_spawn","with_draw","territory","claim","access","disband","flags","create_ranks","edit_ranks","delete_ranks","set_def_ranks","reset_ranks","promote"},
["moderator"] = {"claim","door","build","set_spawn","invite","kick","promote"},
["member"] = {"build","container","door"}
}
-- Faction permissions:
--
-- build: dig and place nodes
-- pain_build: dig and place nodes but take damage doing so
-- door: open/close or dig doors
-- container: be able to use containers like chest
-- name: set the faction's name
-- description: Set the faction description
-- motd: set the faction's message of the day
-- invite: (un)invite players to join the faction
-- kick: kick players off the faction
-- title: set territory titles
-- set_spawn: set the faction's spawn
-- unset_spawn: delete the faction's spawn
-- with_draw: withdraw money from the faction's bank
-- spawn: be able to teleport to the faction's spawn
-- territory: claim or unclaim territory
-- claim: (un)claim parcels of land
-- access: manage access to territory and parcels of land to players or factions
-- disband: disband the faction
-- flags: manage faction's flags
-- create_ranks: create new ranks
-- edit_ranks: edit rank name and permissions
-- delete_ranks: delete ranks
-- set_def_ranks: set the default rank given to new players
-- reset_ranks: reset the ranks back to the default ones
-- promote: set a player's rank
-- declare_war: be able to declare war with another faction
-- neutral: be able to send a neutral request to another faction
-- alliance: be able to send a alliance request to another faction and break alliance treaties too
-- accept_treaty: be able to accept a treaty request from another faction
-- refuse_treaty: be able to refuse a treaty request from another faction
factions.permissions = {"build","pain_build","door","container","name","description","motd","invite","kick"
,"title","set_spawn","unset_spawn","with_draw","territory","claim","access","disband","flags","create_ranks","edit_ranks","delete_ranks","set_def_ranks","reset_ranks","promote"}
factions.permissions_desc = {"dig and place nodes","dig and place nodes but take damage doing so","open/close or dig","be able to use containers like chest","set the faction's name"
,"Set the faction description","set the faction's message of the day","(un)invite players to join the faction","kick players off the faction","set territory titles","set the faction's spawn"
,"delete the faction's spawn","withdraw money from the faction's bank","claim or unclaim territory","(un)claim parcels of land","manage access to territory and parcels of land to players or factions"
,"disband the faction","manage faction's flags","create new ranks","edit rank permissions","delete ranks","set the default rank given to new players","reset the ranks back to the default ones","set a player's rank"}
-- open: can the faction be joined without an invite?
-- monsters: can monsters spawn on your land?
-- tax_kick: will players be kicked for not paying tax?
-- animals: can animals spawn on your land?
factions.flags = {"open","monsters","tax_kick","animals"}
factions.flags_desc = {"can the faction be joined without an invite?","can monsters spawn on your land?(unused)","will players be kicked for not paying tax?(unused)","can animals spawn on your land?(unused)"}
if factions_config.faction_diplomacy == true then
table.insert(factions.permissions,"declare_war")
table.insert(factions.permissions,"neutral")
table.insert(factions.permissions,"alliance")
table.insert(factions.permissions,"accept_treaty")
table.insert(factions.permissions,"refuse_treaty")
table.insert(factions.permissions_desc,"be able to declare war with another faction")
table.insert(factions.permissions_desc,"be able to send a neutral request to another faction")
table.insert(factions.permissions_desc,"be able to send a alliance request to another faction and break alliance treaties too")
table.insert(factions.permissions_desc,"be able to accept a treaty request from another faction")
table.insert(factions.permissions_desc,"be able to refuse a treaty request from another faction")
local lt = starting_ranks["leader"]
table.insert(lt,"declare_war")
table.insert(lt,"neutral")
table.insert(lt,"alliance")
table.insert(lt,"accept_treaty")
table.insert(lt,"refuse_treaty")
starting_ranks["leader"] = lt
end
if factions_config.spawn_teleport == true then
table.insert(factions.permissions,"spawn")
table.insert(factions.permissions_desc,"be able to teleport to the faction's spawn")
table.insert(starting_ranks["leader"],"spawn")
table.insert(starting_ranks["moderator"],"spawn")
table.insert(starting_ranks["member"],"spawn")
end
function factions.Faction:new(faction)
faction = {
name = "",
--! @brief power of a faction (needed for parcel claiming)
power = factions_config.power,
--! @brief maximum power of a faction
maxpower = factions_config.maxpower,
--! @brief power currently in use
usedpower = 0.,
--! @brief list of player names
players = {},
--! @brief list of player names online
onlineplayers = {},
--! @brief list of player names offline
offlineplayers = {},
--! @brief table of ranks/permissions
ranks = starting_ranks,
--! @brief name of the leader
leader = nil,
--! @brief spawn of the faction
spawn = {x=0, y=0, z=0},
--! @brief default joining rank for new members
default_rank = "member",
--! @brief default rank assigned to the leader
default_leader_rank = "leader",
--! @brief faction's description string
description = "Default faction description.",
--! @brief faction's message of the day.
message_of_the_day = "",
--! @brief list of players currently invited (can join with /f join)
invited_players = {},
--! @brief table of claimed parcels (keys are parcelpos strings)
land = {},
--! @brief table of allies
allies = {},
--
request_inbox = {},
--! @brief table of enemies
enemies = {},
--!
neutral = {},
--! @brief table of parcels/factions that are under attack
attacked_parcels = {},
--! @brief whether faction is closed or open (boolean)
join_free = false,
--! @brief gives certain privileges
is_admin = false,
--! @brief if a player on the faction has a nil rank
rankless = false,
--! @brief last time anyone logged on
last_logon = os.time(),
--! @brief how long this has been without parcels
no_parcel = os.time(),
} or faction
setmetatable(faction, self)
return faction
end
--! @brief create a new empty faction
function factions.new_faction(name,do_not_save)
local faction = factions.Faction:new(nil)
faction.name = name
factions.factions[name] = faction
faction:on_create()
minetest.after(1,
function(f)
f:on_no_parcel()
end,faction)
if not do_not_save then
factions.save()
end
return faction
end
function factions.start_diplomacy(name,faction)
for i,fac in pairs(factions.factions) do
if i ~= name and not (faction.neutral[i] or faction.allies[i] or faction.enemies[i]) then
faction:new_enemy(i)
fac:new_enemy(name)
end
end
end
function factions.Faction.set_name(self, name)
local oldname = self.name
local oldfaction = factions.factions[oldname]
self.name = name
for i,fac in pairs(factions.factions) do
if i ~= oldname then
if fac.neutral[oldname] then
fac.neutral[oldname] = nil
fac.neutral[name] = true
end
if fac.allies[oldname] then
fac.allies[oldname] = nil
fac.allies[name] = true
end
if fac.enemies[oldname] then
fac.enemies[oldname] = nil
fac.enemies[name] = true
end
end
end
for parcel in pairs(self.land) do
factions.parcels[parcel] = self.name
end
for playername in pairs(self.players) do
factions.players[playername] = self.name
end
factions.factions[oldname] = nil
factions.factions[name] = oldfaction
factions.factions[name].name = name
for playername in pairs(self.onlineplayers) do
updateFactionName(playername,name)
end
self:on_set_name(oldname)
factions.save()
end
function factions.Faction.increase_power(self, power)
self.power = self.power + power
if self.power > self.maxpower - self.usedpower then
self.power = self.maxpower - self.usedpower
end
for i in pairs(self.onlineplayers) do
updateHudPower(minetest.get_player_by_name(i),self)
end
factions.save()
end
function factions.Faction.decrease_power(self, power)
self.power = self.power - power
for i in pairs(self.onlineplayers) do
updateHudPower(minetest.get_player_by_name(i),self)
end
factions.save()
end
function factions.Faction.increase_maxpower(self, power)
self.maxpower = self.maxpower + power
for i in pairs(self.onlineplayers) do
updateHudPower(minetest.get_player_by_name(i),self)
end
factions.save()
end
function factions.Faction.decrease_maxpower(self, power)
self.maxpower = self.maxpower - power
if self.maxpower < 0. then -- should not happen
self.maxpower = 0.
end
for i in pairs(self.onlineplayers) do
updateHudPower(minetest.get_player_by_name(i),self)
end
end
function factions.Faction.increase_usedpower(self, power)
self.usedpower = self.usedpower + power
for i in pairs(self.onlineplayers) do
updateHudPower(minetest.get_player_by_name(i),self)
end
end
function factions.Faction.decrease_usedpower(self, power)
self.usedpower = self.usedpower - power
if self.usedpower < 0. then
self.usedpower = 0.
end
for i in pairs(self.onlineplayers) do
updateHudPower(minetest.get_player_by_name(i),self)
end
end
-- power-per-players only.
function factions.Faction.check_power(self)
if factions_config.enable_power_per_player then
for player,unused in pairs(self.players) do
local ip = factions_ip.player_ips[player]
local notsame = true
for i,k in pairs(self.players) do
local other_ip = factions_ip.player_ips[k]
if other_ip == ip then
notsame = false
break
end
end
if notsame then
self:increase_maxpower(factions_config.powermax_per_player)
end
end
end
end
function factions.Faction.count_land(self)
local count = 0.
for k, v in pairs(self.land) do
count = count + 1
end
return count
end
minetest.register_on_prejoinplayer(function(name, ip)
factions_ip.player_ips[name] = ip
end)
function factions.Faction.add_player(self, player, rank)
self:on_player_join(player)
self.players[player] = rank or self.default_rank
factions.players[player] = self.name
self.invited_players[player] = nil
if factions_config.enable_power_per_player then
local ip = factions_ip.player_ips[player]
local notsame = true
for i,k in pairs(self.players) do
local other_ip = factions_ip.player_ips[k]
if other_ip == ip then
notsame = false
break
end
end
if notsame then
self:increase_maxpower(factions_config.powermax_per_player)
end
end
local pdata = minetest.get_player_by_name(player)
local ipc = pdata:is_player_connected(player)
if ipc then
createHudFactionName(pdata,self.name)
createHudPower(pdata,self)
self.offlineplayers[player] = nil
self.onlineplayers[player] = 1
else
self.offlineplayers[player] = 1
self.onlineplayers[player] = nil
end
factions.save()
end
function factions.Faction.check_players_in_faction(self)
for i,k in pairs(self.players) do
return true
end
self:disband("Zero players on faction.")
return false
end
function factions.Faction.remove_player(self, player)
self.players[player] = nil
factions.players[player] = nil
self:on_player_leave(player)
self:check_players_in_faction(self)
if factions_config.enable_power_per_player then
local ip = factions_ip.player_ips[player]
local notsame = true
for i,k in pairs(self.players) do
local other_ip = factions_ip.player_ips[k]
if other_ip == ip then
notsame = false
break
end
end
if notsame then
self:decrease_maxpower(factions_config.powermax_per_player)
end
end
local pdata = minetest.get_player_by_name(player)
local ipc = pdata:is_player_connected(player)
if ipc then
removeHud(pdata,"factionName")
removeHud(pdata,"powerWatch")
end
self.offlineplayers[player] = nil
self.onlineplayers[player] = nil
factions.save()
end
--! @param parcelpos position of the wanted parcel
--! @return whether this faction can claim a parcelpos
function factions.Faction.can_claim_parcel(self, parcelpos)
local fac = factions.parcels[parcelpos]
if fac then
if factions.factions[fac].power < 0. and self.power >= factions_config.power_per_parcel and not self.allies[factions.factions[fac].name] and not self.neutral[factions.factions[fac].name] then
return true
else
return false
end
elseif self.power < factions_config.power_per_parcel then
return false
end
return true
end
--! @brief claim a parcel, update power and update global parcels table
function factions.Faction.claim_parcel(self, parcelpos)
-- check if claiming over other faction's territory
local otherfac = factions.parcels[parcelpos]
if otherfac then
local faction = factions.factions[otherfac]
faction:unclaim_parcel(parcelpos)
faction:parcelless_check()
end
factions.parcels[parcelpos] = self.name
self.land[parcelpos] = true
self:decrease_power(factions_config.power_per_parcel)
self:increase_usedpower(factions_config.power_per_parcel)
self:on_claim_parcel(parcelpos)
self:parcelless_check()
factions.save()
end
--! @brief claim a parcel, update power and update global parcels table
function factions.Faction.unclaim_parcel(self, parcelpos)
factions.parcels[parcelpos] = nil
self.land[parcelpos] = nil
self:increase_power(factions_config.power_per_parcel)
self:decrease_usedpower(factions_config.power_per_parcel)
self:on_unclaim_parcel(parcelpos)
self:parcelless_check()
factions.save()
end
function factions.Faction.parcelless_check(self)
if self.land then
local count = 0
for index, value in pairs(self.land) do
count = count + 1
break
end
if count > 0 then
if self.no_parcel ~= -1 then
self:broadcast("Faction " .. self.name .. " will not be disbanded because it now has parcels.")
end
self.no_parcel = -1
else
self.no_parcel = os.time()
self:on_no_parcel()
end
end
end
--! @brief disband faction, updates global players and parcels table
function factions.Faction.disband(self, reason)
if not self.is_admin then
for i,v in pairs(factions.factions) do
if v.name ~= self.name then
if v.enemies[self.name] then
v:end_enemy(self.name)
end
if v.allies[self.name] then
v:end_alliance(self.name)
end
if v.neutral[self.name] then
v:end_neutral(self.name)
end
end
end
for k, _ in pairs(self.players) do -- remove players affiliation
factions.players[k] = nil
end
for k, v in pairs(self.land) do -- remove parcel claims
factions.parcels[k] = nil
end
self:on_disband(reason)
for i,l in pairs(self.onlineplayers) do
removeHud(i,"factionName")
removeHud(i,"powerWatch")
end
factions.factions[self.name] = nil
factions.save()
end
end
--! @brief change the faction leader
function factions.Faction.set_leader(self, player)
if self.leader then
self.players[self.leader] = self.default_rank
end
self.leader = player
self.players[player] = self.default_leader_rank
self:on_new_leader()
factions.save()
end
function factions.Faction.set_message_of_the_day(self,text)
self.message_of_the_day = text
factions.save()
end
--! @brief check permissions for a given player
--! @return boolean indicating permissions. Players not in faction always receive false
function factions.Faction.has_permission(self, player, permission)
local p = self.players[player]
if not p then
return false
end
local perms = self.ranks[p]
if perms then
for i in ipairs(perms) do
if perms[i] == permission then
return true
end
end
else
if not self.rankless then
self.rankless = true
factions.save()
end
end
return false
end
function factions.Faction.set_description(self, new)
self.description = new
self:on_change_description()
factions.save()
end
--! @brief places player in invite list
function factions.Faction.invite_player(self, player)
self.invited_players[player] = true
self:on_player_invited(player)
factions.save()
end
--! @brief removes player from invite list (can no longer join via /f join)
function factions.Faction.revoke_invite(self, player)
self.invited_players[player] = nil
self:on_revoke_invite(player)
factions.save()
end
--! @brief set faction openness
function factions.Faction.toggle_join_free(self, bool)
self.join_free = bool
self:on_toggle_join_free()
factions.save()
end
--! @return true if a player can use /f join, false otherwise
function factions.Faction.can_join(self, player)
return self.join_free or self.invited_players[player]
end
function factions.Faction.new_alliance(self, faction)
self.allies[faction] = true
self:on_new_alliance(faction)
if self.enemies[faction] then
self:end_enemy(faction)
end
if self.neutral[faction] then
self:end_neutral(faction)
end
factions.save()
end
function factions.Faction.end_alliance(self, faction)
self.allies[faction] = nil
self:on_end_alliance(faction)
factions.save()
end
function factions.Faction.new_neutral(self, faction)
self.neutral[faction] = true
self:on_new_neutral(faction)
if self.allies[faction] then
self:end_alliance(faction)
end
if self.enemies[faction] then
self:end_enemy(faction)
end
factions.save()
end
function factions.Faction.end_neutral(self, faction)
self.neutral[faction] = nil
self:on_end_neutral(faction)
factions.save()
end
function factions.Faction.new_enemy(self, faction)
self.enemies[faction] = true
self:on_new_enemy(faction)
if self.allies[faction] then
self:end_alliance(faction)
end
if self.neutral[faction] then
self:end_neutral(faction)
end
factions.save()
end
function factions.Faction.end_enemy(self, faction)
self.enemies[faction] = nil
self:on_end_enemy(faction)
factions.save()
end
--! @brief faction's member will now spawn in a new place
function factions.Faction.set_spawn(self, pos)
self.spawn = {x=pos.x, y=pos.y, z=pos.z}
self:on_set_spawn()
factions.save()
end
function factions.Faction.tp_spawn(self, playername)
player = minetest.get_player_by_name(playername)
if player then
player:moveto(self.spawn, false)
end
end
--! @brief create a new rank with permissions
--! @param rank the name of the new rank
--! @param rank a list with the permissions of the new rank
function factions.Faction.add_rank(self, rank, perms)
self.ranks[rank] = perms
self:on_add_rank(rank)
factions.save()
end
--! @brief replace an rank's permissions
--! @param rank the name of the rank to edit
--! @param add or remove permissions to the rank
function factions.Faction.replace_privs(self, rank, perms)
self.ranks[rank] = perms
self:on_replace_privs(rank)
factions.save()
end
function factions.Faction.remove_privs(self, rank, perms)
local revoked = false
local p = self.ranks[rank]
for index, perm in pairs(p) do
if table_Contains(perms,perm) then
revoked = true
table.remove(p,index)
end
end
self.ranks[rank] = p
if revoked then
self:on_remove_privs(rank,perms)
else
self:broadcast("No privilege was revoked from rank "..rank..".")
end
factions.save()
end
function factions.Faction.add_privs(self, rank, perms)
local added = false
local p = self.ranks[rank]
for index, perm in pairs(perms) do
if not table_Contains(p,perm) then
added = true
table.insert(p,perm)
end
end
self.ranks[rank] = p
if added then
self:on_add_privs(rank,perms)
else
self:broadcast("The rank "..rank.." already has these privileges.")
end
factions.save()
end
function factions.Faction.set_rank_name(self, oldrank, newrank)
local copyrank = self.ranks[oldrank]
self.ranks[newrank] = copyrank
self.ranks[oldrank] = nil
for player, r in pairs(self.players) do
if r == oldrank then
self.players[player] = newrank
end
end
if oldrank == self.default_leader_rank then
self.default_leader_rank = newrank
self:broadcast("The default leader rank has been set to "..newrank)
end
if oldrank == self.default_rank then
self.default_rank = newrank
self:broadcast("The default rank given to new players is set to "..newrank)
end
self:on_set_rank_name(oldrank, newrank)
factions.save()
end
function factions.Faction.set_def_rank(self, rank)
for player, r in pairs(self.players) do
if r == rank or r == nil or not self.ranks[r] then
self.players[player] = rank
end
end
self.default_rank = rank
self:on_set_def_rank(rank)
self.rankless = false
factions.save()
end
function factions.Faction.reset_ranks(self)
self.ranks = starting_ranks
self.default_rank = "member"
self.default_leader_rank_rank = "leader"
for player, r in pairs(self.players) do
if not player == leader and (r == nil or not self.ranks[r]) then
self.players[player] = self.default_rank
elseif player == leader then
self.players[player] = self.default_leader_rank_rank
end
end
self:on_reset_ranks()
self.rankless = false
factions.save()
end
--! @brief delete a rank and replace it
--! @param rank the name of the rank to be deleted
--! @param newrank the rank given to players who were previously "rank"
function factions.Faction.delete_rank(self, rank, newrank)
for player, r in pairs(self.players) do
if r == rank then
self.players[player] = newrank
end
end
self.ranks[rank] = nil
self:on_delete_rank(rank, newrank)
if rank == self.default_leader_rank then
self.default_leader_rank = newrank
self:broadcast("The default leader rank has been set to "..newrank)
end
if rank == self.default_rank then
self.default_rank = newrank
self:broadcast("The default rank given to new players is set to "..newrank)
end
factions.save()
end
--! @brief set a player's rank
function factions.Faction.promote(self, member, rank)
self.players[member] = rank
self:on_promote(member)
end
--! @brief send a message to all members
function factions.Faction.broadcast(self, msg, sender)
local message = self.name.."> "..msg
if sender then
message = sender.."@"..message
end
message = "Faction<"..message
for k, _ in pairs(self.onlineplayers) do
minetest.chat_send_player(k, message)
end
end
--! @brief checks whether a faction has at least one connected player
function factions.Faction.is_online(self)
for playername, _ in pairs(self.onlineplayers) do
if minetest.get_player_by_name(playername) then
return true
end
end
return false
end
function factions.Faction.attack_parcel(self, parcelpos)
if factions_config.attack_parcel then
local attacked_faction = factions.get_parcel_faction(parcelpos)
if attacked_faction then
if not self.allies[attacked_faction.name] then
self.power = self.power - factions_config.power_per_attack
if attacked_faction.attacked_parcels[parcelpos] then
attacked_faction.attacked_parcels[parcelpos][self.name] = true
else
attacked_faction.attacked_parcels[parcelpos] = {[self.name] = true}
end
attacked_faction:broadcast("Parcel ("..parcelpos..") is being attacked by "..self.name.."!!")
if self.power < 0. then -- punish memers
minetest.chat_send_all("Faction "..self.name.." has attacked too much and has now negative power!")
end
factions.save()
else
self:broadcast("You can not attack that parcel because it belongs to an ally.")
end
end
end
end
function factions.Faction.stop_attack(self, parcelpos)
local attacked_faction = factions.parcels[parcelpos]
if attacked_faction then
attacked_faction = factions.factions[attacked_faction]
if attacked_faction.attacked_parcels[parcelpos] then
attacked_faction.attacked_parcels[parcelpos][self.name] = nil
attacked_faction:broadcast("Parcel ("..parcelpos..") is no longer under attack from "..self.name..".")
self:broadcast("Parcel ("..parcelpos..") has been reconquered by "..attacked_faction.name..".")
end
factions.save()
end
end
function factions.Faction.parcel_is_attacked_by(self, parcelpos, faction)
if self.attacked_parcels[parcelpos] then
return self.attacked_parcels[parcelpos][faction.name]
else
return false
end
end
--------------------------
-- callbacks for events --
function factions.Faction.on_create(self) --! @brief called when the faction is added to the global faction list
minetest.chat_send_all("Faction "..self.name.." has been created.")
end
function factions.Faction.on_set_name(self,oldname)
minetest.chat_send_all("Faction "..oldname.." has been changed its name to "..self.name..".")
end
function factions.Faction.on_no_parcel(self)
local now = os.time() - self.no_parcel
local l = factions_config.maximum_parcelless_faction_time
self:broadcast("This faction will disband in "..l-now.." seconds, because it has no parcels.")
end
function factions.Faction.on_player_leave(self, player)
self:broadcast(player.." has left this faction.")
end
function factions.Faction.on_player_join(self, player)
self:broadcast(player.." has joined this faction.")
end
function factions.Faction.on_claim_parcel(self, pos)
self:broadcast("Parcel ("..pos..") has been claimed.")
end
function factions.Faction.on_unclaim_parcel(self, pos)
self:broadcast("Parcel ("..pos..") has been unclaimed.")
end
function factions.Faction.on_disband(self, reason)
local msg = "Faction "..self.name.." has been disbanded."
if reason then
msg = msg.." ("..reason..")"
end
minetest.chat_send_all(msg)
end
function factions.Faction.on_new_leader(self)
self:broadcast(self.leader.." is now the leader of this faction.")
end
function factions.Faction.on_change_description(self)
self:broadcast("Faction description has been modified to: "..self.description)
end
function factions.Faction.on_player_invited(self, player)
minetest.chat_send_player(player, "You have been invited to faction "..self.name)
end
function factions.Faction.on_toggle_join_free(self, player)
if self.join_free then
self:broadcast("This faction is now invite-free.")
else
self:broadcast("This faction is no longer invite-free.")
end
end
function factions.Faction.on_new_alliance(self, faction)
self:broadcast("This faction is now allied with "..faction)
end
function factions.Faction.on_end_alliance(self, faction)
self:broadcast("This faction is no longer allied with "..faction.."!")
end
function factions.Faction.on_new_neutral(self, faction)
self:broadcast("This faction is now neutral with "..faction)
end
function factions.Faction.on_end_neutral(self, faction)
self:broadcast("This faction is no longer neutral with "..faction.."!")
end
function factions.Faction.on_new_enemy(self, faction)
self:broadcast("This faction is now at war with "..faction)
end
function factions.Faction.on_end_enemy(self, faction)
self:broadcast("This faction is no longer at war with "..faction.."!")
end
function factions.Faction.on_set_spawn(self)
self:broadcast("The faction spawn has been set to ("..util.coords3D_string(self.spawn)..").")
end
function factions.Faction.on_add_rank(self, rank)
self:broadcast("The rank "..rank.." has been created with privileges: "..table.concat(self.ranks[rank], ", "))
end
function factions.Faction.on_replace_privs(self, rank)
self:broadcast("The privileges in rank "..rank.." have been delete and changed to: "..table.concat(self.ranks[rank], ", "))
end
function factions.Faction.on_remove_privs(self, rank,privs)
self:broadcast("The privileges in rank "..rank.." have been revoked: "..table.concat(privs, ", "))
end
function factions.Faction.on_add_privs(self, rank,privs)
self:broadcast("The privileges in rank "..rank.." have been added: "..table.concat(privs, ", "))
end
function factions.Faction.on_set_rank_name(self, rank,newrank)
self:broadcast("The name of rank "..rank.." has been changed to "..newrank)
end
function factions.Faction.on_delete_rank(self, rank, newrank)
self:broadcast("The rank "..rank.." has been deleted and replaced by "..newrank)
end
function factions.Faction.on_set_def_rank(self, rank)
self:broadcast("The default rank given to new players has been changed to "..rank)
end
function factions.Faction.on_reset_ranks(self)
self:broadcast("All of the faction's ranks have been reset to the default ones.")
end
function factions.Faction.on_promote(self, member)
minetest.chat_send_player(member, "You have been promoted to "..self.players[member])
end
function factions.Faction.on_revoke_invite(self, player)
minetest.chat_send_player(player, "You are no longer invited to faction "..self.name)
end
function factions.get_parcel_pos(pos)
if factions_config.protection_style == "2d" then
return math.floor(pos.x / factions_config.parcel_size)..","..math.floor(pos.z / factions_config.parcel_size)
elseif factions_config.protection_style == "3d" then
return math.floor(pos.x / factions_config.parcel_size)..","..math.floor(pos.y / factions_config.parcel_size)..","..math.floor(pos.z / factions_config.parcel_size)
end
end
function factions.get_player_faction(playername)
local facname = factions.players[playername]
if facname then
local faction = factions.factions[facname]
return faction
end
return nil
end
function factions.get_parcel_faction(parcelpos)
local facname = factions.parcels[parcelpos]
if facname then
local faction = factions.factions[facname]
return faction
end
return nil
end
function factions.get_faction(facname)
return factions.factions[facname]
end
function factions.get_faction_at(pos)
local y = pos.y
if factions_config.protection_depth_height_limit and (pos.y < factions_config.protection_max_depth or pos.y > factions_config.protection_max_height) then
return nil
end
local parcelpos = factions.get_parcel_pos(pos)
return factions.get_parcel_faction(parcelpos)
end
-------------------------------------------------------------------------------
-- name: add_faction(name)
--
--! @brief add a faction
--! @memberof factions
--! @public
--
--! @param name of faction to add
--!
--! @return faction object/false (succesfully added faction or not)
-------------------------------------------------------------------------------
function factions.add_faction(name)
if factions.can_create_faction(name) then
local fac = factions.new_faction(name)
fac:on_create()
return fac
else
return nil
end
end
-------------------------------------------------------------------------------
-- name: get_faction_list()
--
--! @brief get list of factions
--! @memberof factions
--! @public
--!
--! @return list of factions
-------------------------------------------------------------------------------
function factions.get_faction_list()
local retval = {}
for key,value in pairs(factions.factions) do
table.insert(retval,key)
end
return retval
end
-------------------------------------------------------------------------------
-- name: save()
--
--! @brief save data to file
--! @memberof factions
--! @private
-------------------------------------------------------------------------------
function factions.save()
--saving is done much more often than reading data to avoid delay
--due to figuring out which data to save and which is temporary only
--all data is saved here
--this implies data needs to be cleant up on load
local file,error = io.open(factions_worldid .. "/" .. "factions.conf","w")
if file ~= nil then
file:write(minetest.serialize(factions.factions))
file:close()
else
minetest.log("error","MOD factions: unable to save factions world specific data!: " .. error)
end
factions_ip.save()
end
-------------------------------------------------------------------------------
-- name: load()
--
--! @brief load data from file
--! @memberof factions
--! @private
--
--! @return true/false
-------------------------------------------------------------------------------
function factions.load()
local filename = "factions.conf"
local file,error = io.open(factions_worldid .. "/" .. filename,"r")
if file ~= nil then
local raw_data = file:read("*a")
local current_version = misc_mod_data.data.factions_version
misc_mod_data.load()
local old_version = misc_mod_data.data.factions_version
local tabledata = minetest.deserialize(raw_data)
file:close()
if tabledata then
factions.factions = tabledata
if current_version ~= old_version or factions.is_old_file(tabledata) then
if factions.convert(filename) then
minetest.after(5,
function()
minetest.chat_send_all("Factions successfully converted.")
end)
end
end
for facname, faction in pairs(factions.factions) do
minetest.log("action", facname..","..faction.name)
for player, rank in pairs(faction.players) do
minetest.log("action", player..","..rank)
factions.players[player] = facname
end
for parcelpos, val in pairs(faction.land) do
factions.parcels[parcelpos] = facname
end
setmetatable(faction, factions.Faction)
if not faction.maxpower or faction.maxpower <= 0. then
faction.maxpower = faction.power
if faction.power < 0. then
faction.maxpower = 0.
end
end
if not faction.attacked_parcels then
faction.attacked_parcels = {}
end
if not faction.usedpower then
faction.usedpower = faction:count_land() * factions_config.power_per_parcel
end
if #faction.name > factions_config.faction_name_max_length then
faction:disband()
end
if not faction.last_logon then
faction.last_logon = os.time()
end
if faction.no_parcel ~= -1 then
faction.no_parcel = os.time()
end
if faction:count_land() > 0 then
faction.no_parcel = -1
end
faction.onlineplayers = {}
faction.offlineplayers = {}
if faction.players then
for i, _ in pairs(faction.players) do
faction.offlineplayers[i] = _
end
end
end
misc_mod_data.data.factions_version = current_version
misc_mod_data.save()
factions.save()
else
minetest.after(5,
function()
minetest.chat_send_all("Failed to deserialize saved file.")
end)
end
end
factions_ip.load()
end
function factions.is_old_file(oldfactions)
local tempfaction = factions.Faction:new(nil)
local pass = false
for facname, faction in pairs(oldfactions) do
for ni, nl in pairs(tempfaction) do
pass = false
for key, value in pairs(faction) do
if key == ni then
pass = true
break
end
end
if not pass then
tempfaction = nil
return true
end
end
-- Only check one faction to save time.
if not pass then
tempfaction = nil
return true
else
tempfaction = nil
return false
end
end
tempfaction = nil
return false
end
function factions.convert(filename)
local file, error = io.open(factions_worldid .. "/" .. filename, "r")
if not file then
minetest.chat_send_all("Cannot load file "..filename..". "..error)
return false
end
local raw_data = file:read("*a")
file:close()
local data = minetest.deserialize(raw_data)
local old_permissions = {"disband", "claim", "playerslist", "build", "description", "ranks", "spawn", "promote","diplomacy"}
for facname,faction in pairs(data) do
local newfac = factions.new_faction(facname,true)
for oi, ol in pairs(faction) do
if newfac[oi] then
newfac[oi] = ol
end
end
if faction.players then
newfac.players = faction.players
end
if faction.land then
newfac.land = faction.land
end
if faction.ranks then
newfac.ranks = faction.ranks
end
if faction.rankless then
newfac.rankless = faction.rankless
else
newfac.rankless = false
end
for rank,perm in pairs(faction.ranks) do
for index,value in pairs(perm) do
if value == "playerslist" then
table.remove(faction.ranks[rank],index)
table.insert(faction.ranks[rank],"kick")
table.insert(faction.ranks[rank],"invite")
elseif value == "ranks" then
table.remove(faction.ranks[rank],index)
table.insert(faction.ranks[rank],"create_ranks")
table.insert(faction.ranks[rank],"edit_ranks")
table.insert(faction.ranks[rank],"delete_ranks")
table.insert(faction.ranks[rank],"set_def_ranks")
table.insert(faction.ranks[rank],"reset_ranks")
elseif value == "diplomacy" then
table.remove(faction.ranks[rank],index)
table.insert(faction.ranks[rank],"declare_war")
table.insert(faction.ranks[rank],"neutral")
table.insert(faction.ranks[rank],"alliance")
table.insert(faction.ranks[rank],"accept_treaty")
table.insert(faction.ranks[rank],"refuse_treaty")
elseif value == "spawn" then
if not factions_config.spawn_teleport == true then
table.remove(faction.ranks[rank],index)
end
table.insert(faction.ranks[rank],"set_spawn")
table.insert(faction.ranks[rank],"unset_spawn")
end
end
end
factions.start_diplomacy(facname,newfac)
newfac:check_power()
end
-- Create runtime data.
for facname,faction in pairs(factions.factions) do
if faction.players then
for player, unused in pairs(faction.players) do
factions.players[player] = faction.name
end
end
if faction.land then
for l, unused in pairs(faction.land) do
factions.parcels[l] = facname
end
end
end
return true
end
minetest.register_on_dieplayer(
function(player)
local faction = factions.get_player_faction(player:get_player_name())
if not faction then
return true
end
faction:decrease_power(factions_config.power_per_death)
return true
end
)
function factions.faction_tick()
local now = os.time()
for facname, faction in pairs(factions.factions) do
if faction:is_online() then
if factions_config.enable_power_per_player then
local t = faction.onlineplayers
local count = 0
for _ in pairs(t) do count = count + 1 end
faction:increase_power(factions_config.power_per_player*count)
else
faction:increase_power(factions_config.power_per_tick)
end
end
if now - faction.last_logon > factions_config.maximum_faction_inactivity or (faction.no_parcel ~= -1 and now - faction.no_parcel > factions_config.maximum_parcelless_faction_time) then
faction:disband()
end
end
end
minetest.register_on_joinplayer(
function(player)
local name = player:get_player_name()
minetest.after(5,createHudfactionLand,player)
local faction = factions.get_player_faction(name)
if faction then
faction.last_logon = os.time()
minetest.after(5,createHudFactionName,player,faction.name)
minetest.after(5,createHudPower,player,faction)
faction.offlineplayers[name] = nil
faction.onlineplayers[name] = 1
if faction.no_parcel ~= -1 then
local now = os.time() - faction.no_parcel
local l = factions_config.maximum_parcelless_faction_time
minetest.chat_send_player(name,"This faction will disband in "..l-now.." seconds, because it has no parcels.")
end
if faction:has_permission(name, "accept_treaty") or faction:has_permission(name, "refuse_treaty") then
for _ in pairs(faction.request_inbox) do minetest.chat_send_player(name,"You have diplomatic requests in the inbox.") break end
end
if faction:has_permission(name, "ranks") then
if faction.rankless then
minetest.chat_send_player(name,"You need to reset the default rank because there are rankless players in this faction. reset all the ranks back to default using /f reset_ranks (You will lose all of your custom ranks) or use /f change_def_rank")
end
end
if faction.message_of_the_day and (faction.message_of_the_day ~= "" or faction.message_of_the_day ~= " ") then
minetest.chat_send_player(name,faction.message_of_the_day)
end
end
end
)
minetest.register_on_leaveplayer(
function(player)
local name = player:get_player_name()
local faction = factions.get_player_faction(name)
local id_name1 = name .. "factionLand"
if hud_ids[id_name1] then
hud_ids[id_name1] = nil
end
if faction then
local id_name2 = name .. "factionName"
local id_name3 = name .. "powerWatch"
if hud_ids[id_name2] then
hud_ids[id_name2] = nil
end
if hud_ids[id_name3] then
hud_ids[id_name3] = nil
end
faction.offlineplayers[name] = 1
faction.onlineplayers[name] = nil
factions.save()
end
end
)
minetest.register_on_respawnplayer(
function(player)
local faction = factions.get_player_faction(player:get_player_name())
if not faction then
return false
else
if not faction.spawn then
return false
else
player:setpos(faction.spawn)
return true
end
end
end
)
local default_is_protected = minetest.is_protected
minetest.is_protected = function(pos, player)
local y = pos.y
if factions_config.protection_depth_height_limit and (pos.y < factions_config.protection_max_depth or pos.y > factions_config.protection_max_height) then
return false
end
local parcelpos = factions.get_parcel_pos(pos)
local parcel_faction = factions.get_parcel_faction(parcelpos)
local player_faction = factions.get_player_faction(player)
-- no faction
if not parcel_faction then
return default_is_protected(pos, player)
elseif player_faction then
if parcel_faction.name == player_faction.name then
if parcel_faction:has_permission(player, "pain_build") then
local p = minetest.get_player_by_name(player)
p:set_hp(p:get_hp() - 0.5)
end
return not (parcel_faction:has_permission(player, "build") or parcel_faction:has_permission(player, "pain_build"))
elseif parcel_faction.allies[player_faction.name] then
if player_faction:has_permission(player, "pain_build") then
local p = minetest.get_player_by_name(player)
p:set_hp(p:get_hp() - 0.5)
end
return not (player_faction:has_permission(player, "build") or player_faction:has_permission(player, "pain_build"))
else
return not parcel_faction:parcel_is_attacked_by(parcelpos, player_faction)
end
else
return true
end
end
function factionUpdate()
factions.faction_tick()
minetest.after(factions_config.tick_time,factionUpdate)
end