diff --git a/data/items/items.xml b/data/items/items.xml
index c3f3f9dd6a..049db54ba8 100644
--- a/data/items/items.xml
+++ b/data/items/items.xml
@@ -33359,10 +33359,12 @@
-
+
-
+
diff --git a/data/migrations/27.lua b/data/migrations/27.lua
index d0ffd9c0cb..57e7775526 100644
--- a/data/migrations/27.lua
+++ b/data/migrations/27.lua
@@ -1,3 +1,22 @@
function onUpdateDatabase()
- return false
+ print("> Updating database to version 27 (guildhalls, guild banks #2213)")
+ db.query("ALTER TABLE `houses` ADD `type` ENUM('HOUSE', 'GUILDHALL') NOT NULL DEFAULT 'HOUSE' AFTER `id`")
+ db.query("ALTER TABLE `guilds` ADD `balance` bigint(20) UNSIGNED NOT NULL DEFAULT '0'")
+ db.query([[
+ CREATE TABLE IF NOT EXISTS `guild_transactions` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `guild_id` int(11) NOT NULL,
+ `guild_associated` int(11) DEFAULT NULL,
+ `player_associated` int(11) DEFAULT NULL,
+ `type` ENUM('DEPOSIT', 'WITHDRAW') NOT NULL,
+ `category` ENUM ('OTHER', 'RENT', 'MATERIAL', 'SERVICES', 'REVENUE', 'CONTRIBUTION') NOT NULL DEFAULT 'OTHER',
+ `balance` bigint(20) UNSIGNED NOT NULL DEFAULT '0',
+ `time` bigint(20) NOT NULL,
+ PRIMARY KEY (`id`),
+ FOREIGN KEY (`guild_id`) REFERENCES `guilds`(`id`) ON DELETE CASCADE,
+ FOREIGN KEY (`guild_associated`) REFERENCES `guilds`(`id`) ON DELETE SET NULL,
+ FOREIGN KEY (`player_associated`) REFERENCES `players`(`id`) ON DELETE SET NULL
+ ) ENGINE=InnoDB;
+ ]])
+ return true
end
diff --git a/data/migrations/28.lua b/data/migrations/28.lua
new file mode 100644
index 0000000000..d0ffd9c0cb
--- /dev/null
+++ b/data/migrations/28.lua
@@ -0,0 +1,3 @@
+function onUpdateDatabase()
+ return false
+end
diff --git a/data/npc/GuildBanker.xml b/data/npc/GuildBanker.xml
new file mode 100644
index 0000000000..b067bda0f7
--- /dev/null
+++ b/data/npc/GuildBanker.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/data/npc/scripts/guildbank.lua b/data/npc/scripts/guildbank.lua
new file mode 100644
index 0000000000..a7bf3fcbb4
--- /dev/null
+++ b/data/npc/scripts/guildbank.lua
@@ -0,0 +1,490 @@
+local keywordHandler = KeywordHandler:new()
+local npcHandler = NpcHandler:new(keywordHandler)
+NpcSystem.parseParameters(npcHandler)
+
+local count = {}
+local transfer = {}
+
+function onCreatureAppear(cid) npcHandler:onCreatureAppear(cid) end
+function onCreatureDisappear(cid) npcHandler:onCreatureDisappear(cid) end
+function onCreatureSay(cid, type, msg) npcHandler:onCreatureSay(cid, type, msg) end
+function onThink() npcHandler:onThink() end
+
+local function greetCallback(cid)
+ count[cid], transfer[cid] = nil, nil
+ return true
+end
+
+local topicList = {
+ NONE = 0,
+ DEPOSIT_GOLD = 1,
+ DEPOSIT_CONSENT = 2,
+ WITHDRAW_GOLD = 3,
+ WITHDRAW_CONSENT = 4,
+ TRANSFER_TYPE = 5,
+ TRANSFER_PLAYER_GOLD = 6,
+ TRANSFER_PLAYER_WHO = 7,
+ TRANSFER_PLAYER_CONSENT = 8,
+ TRANSFER_GUILD_GOLD = 9,
+ TRANSFER_GUILD_WHO = 10,
+ TRANSFER_GUILD_CONSENT = 11,
+ LEDGER_CONSENT = 12
+}
+
+local function creatureSayCallback(cid, type, msg)
+ if not npcHandler:isFocused(cid) then
+ return false
+ end
+ local player = Player(cid)
+ local guild = player:getGuild()
+ if not guild then
+ npcHandler:say("I'm too busy serving guilds, perhaps my colleague the {Banker} can assist you with your personal bank account.", cid)
+ npcHandler.topic[cid] = topicList.NONE
+ return true
+ end
+ if msgcontains(msg, "balance") then
+ npcHandler.topic[cid] = topicList.NONE
+ npcHandler:say("The {guild account} balance of " .. guild:getName() .. " is " .. guild:getBankBalance() .. " gold.", cid)
+ return true
+ elseif msgcontains(msg, "deposit") then
+ count[cid] = player:getBankBalance()
+ if count[cid] < 1 then
+ npcHandler:say("Your {personal} bank account looks awefully empty, please deposit money there first, I don't like dealing with heavy coins.", cid)
+ npcHandler.topic[cid] = topicList.NONE
+ return false
+ end
+ if string.match(msg,"%d+") then
+ count[cid] = getMoneyCount(msg)
+ if count[cid] > 0 and count[cid] <= player:getBankBalance() then
+ npcHandler:say("Would you really like to deposit " .. count[cid] .. " gold to the guild " .. guild:getName() .. "?", cid)
+ npcHandler.topic[cid] = topicList.DEPOSIT_CONSENT
+ return true
+ else
+ npcHandler:say("You cannot afford to deposit " .. count[cid] .. " gold to the guild " .. guild:getName() .. ". You only have " .. player:getBankBalance() .. " gold in your account!", cid)
+ npcHandler.topic[cid] = topicList.NONE
+ return false
+ end
+ else
+ npcHandler:say("Please tell me how much gold it is you would like to deposit.", cid)
+ npcHandler.topic[cid] = topicList.DEPOSIT_GOLD
+ return true
+ end
+ elseif npcHandler.topic[cid] == topicList.DEPOSIT_GOLD then
+ count[cid] = getMoneyCount(msg)
+ if count[cid] > 0 and count[cid] <= player:getBankBalance() then
+ npcHandler:say("Would you really like to deposit " .. count[cid] .. " gold to the guild " .. guild:getName() .. "?", cid)
+ npcHandler.topic[cid] = topicList.DEPOSIT_CONSENT
+ return true
+ else
+ npcHandler:say("You do not have enough gold.", cid)
+ npcHandler.topic[cid] = topicList.NONE
+ return true
+ end
+ elseif npcHandler.topic[cid] == topicList.DEPOSIT_CONSENT then
+ if msgcontains(msg, "yes") then
+ local deposit = tonumber(count[cid])
+ if deposit > 0 and player:getBankBalance() >= deposit then
+ player:setBankBalance(player:getBankBalance() - deposit)
+ player:save()
+ guild:setBankBalance(guild:getBankBalance() + deposit)
+ npcHandler:say("Alright, we have added the amount of " .. deposit .. " gold to the guild " .. guild:getName() .. ".", cid)
+ local currentTime = os.time()
+ local insertData = table.concat({
+ guild:getId(),
+ player:getGuid(),
+ "'DEPOSIT'",
+ "'CONTRIBUTION'",
+ deposit,
+ currentTime
+ },',')
+ db.query("INSERT INTO `guild_transactions` (`guild_id`,`player_associated`,`type`,`category`,`balance`,`time`) VALUES ("..insertData..");")
+ local receipt = Game.createItem(ITEM_RECEIPT_SUCCESS, 1)
+ receipt:setAttribute(ITEM_ATTRIBUTE_TEXT, "Date: " .. os.date("%d. %b %Y - %H:%M:%S", currentTime) .. "\nType: Guild Deposit\nGold Amount: " .. deposit .. "\nReceipt Owner: " .. player:getName() .. "\nRecipient: The " .. guild:getName() .. "\n\nWe are happy to inform you that your transfer request was successfully carried out.")
+ player:addItemEx(receipt)
+ else
+ npcHandler:say("You do not have enough gold.", cid)
+ end
+ elseif msgcontains(msg, "no") then
+ npcHandler:say("As you wish. Is there something else I can do for you?", cid)
+ end
+ npcHandler.topic[cid] = topicList.NONE
+ return true
+ elseif msgcontains(msg, "withdraw") then
+ if string.match(msg,"%d+") then
+ count[cid] = getMoneyCount(msg)
+ if count[cid] > 0 and count[cid] <= guild:getBankBalance() then
+ npcHandler:say("Are you sure you wish to withdraw " .. count[cid] .. " gold from the guild " .. guild:getName() .. "?", cid)
+ npcHandler.topic[cid] = topicList.WITHDRAW_CONSENT
+ else
+ npcHandler:say("There is not enough gold in the guild " .. guild:getName() .. ". Their available balance is currently " .. guild:getBankBalance() .. ".", cid)
+ npcHandler.topic[cid] = topicList.NONE
+ end
+ return true
+ else
+ npcHandler:say("Please tell me how much gold you would like to withdraw.", cid)
+ npcHandler.topic[cid] = topicList.WITHDRAW_GOLD
+ return true
+ end
+ elseif npcHandler.topic[cid] == topicList.WITHDRAW_GOLD then
+ count[cid] = getMoneyCount(msg)
+ if count[cid] > 0 and count[cid] <= guild:getBankBalance() then
+ npcHandler:say("Are you sure you wish to withdraw " .. count[cid] .. " gold from the guild " .. guild:getName() .. "?", cid)
+ npcHandler.topic[cid] = topicList.WITHDRAW_CONSENT
+ else
+ npcHandler:say("There is not enough gold in the guild " .. guild:getName() .. ". Their available balance is currently " .. guild:getBankBalance() .. ".", cid)
+ npcHandler.topic[cid] = topicList.NONE
+ end
+ return true
+ elseif npcHandler.topic[cid] == topicList.WITHDRAW_CONSENT then
+ if msgcontains(msg, "yes") then
+ local withdraw = count[cid]
+ if withdraw > 0 and withdraw <= guild:getBankBalance() then
+ if player:getGuid() == guild:getOwnerGUID() or player:getGuildLevel() == 2 then
+ guild:setBankBalance(guild:getBankBalance() - withdraw)
+ player:setBankBalance(player:getBankBalance() + withdraw)
+ player:save()
+ npcHandler:say("Alright, we have removed the amount of " .. withdraw .. " gold from the guild " .. guild:getName() .. ", and added it to your {personal} account.", cid)
+ local currentTime = os.time()
+ local insertData = table.concat({
+ guild:getId(),
+ player:getGuid(),
+ "'WITHDRAW'",
+ withdraw,
+ currentTime
+ },',')
+ db.query("INSERT INTO `guild_transactions` (`guild_id`,`player_associated`,`type`,`balance`,`time`) VALUES ("..insertData..");")
+ local receipt = Game.createItem(ITEM_RECEIPT_SUCCESS, 1)
+ receipt:setAttribute(ITEM_ATTRIBUTE_TEXT, "Date: " .. os.date("%d. %b %Y - %H:%M:%S", currentTime) .. "\nType: Guild Withdraw\nGold Amount: " .. withdraw .. "\nReceipt Owner: " .. player:getName() .. "\nRecipient: The " .. guild:getName() .. "\n\nWe are happy to inform you that your transfer request was successfully carried out.")
+ player:addItemEx(receipt)
+ else
+ npcHandler:say("Sorry, you are not authorized for withdrawals. Only Leaders and Vice-leaders are allowed to withdraw funds from guild accounts.", cid)
+ end
+ else
+ npcHandler:say("There is not enough gold in the guild " .. guild:getName() .. ". Their available balance is currently " .. guild:getBankBalance() .. ".", cid)
+ end
+ npcHandler.topic[cid] = topicList.NONE
+ elseif msgcontains(msg, "no") then
+ npcHandler:say("Come back anytime you want to if you wish to {withdraw} your money.", cid)
+ npcHandler.topic[cid] = topicList.NONE
+ end
+ return true
+ elseif msgcontains(msg, "guild transfer") or (npcHandler.topic[cid] == topicList.TRANSFER_TYPE and msgcontains(msg, "guild")) then
+ if player:getGuid() ~= guild:getOwnerGUID() then
+ npcHandler:say("Sorry, you are not authorized for withdrawals. Only Guild Leaders are allowed to transfer funds between guilds.", cid)
+ npcHandler.topic[cid] = topicList.NONE
+ return true
+ end
+
+ npcHandler:say("Please tell me the amount of gold you would like to transfer to another guild.", cid)
+ npcHandler.topic[cid] = topicList.TRANSFER_GUILD_GOLD
+ elseif npcHandler.topic[cid] == topicList.TRANSFER_GUILD_GOLD then
+ count[cid] = getMoneyCount(msg)
+ if count[cid] < 0 or guild:getBankBalance() < count[cid] then
+ npcHandler:say("There is not enough gold in your guild account.", cid)
+ npcHandler.topic[cid] = topicList.NONE
+ return true
+ end
+ npcHandler:say("Which guild would you like transfer " .. count[cid] .. " gold to?", cid)
+ npcHandler.topic[cid] = topicList.TRANSFER_GUILD_WHO
+ elseif npcHandler.topic[cid] == topicList.TRANSFER_GUILD_WHO then
+
+ local query = db.storeQuery("SELECT `id`, `name` FROM `guilds` WHERE `name`=" .. db.escapeString(msg))
+ if not query then
+ npcHandler:say("There are no guild in my record who has the name: ["..msg.."]", cid)
+ npcHandler.topic[cid] = topicList.NONE
+ return true
+ end
+ transfer[cid] = {
+ ["id"] = result.getNumber(query, "id"),
+ ["name"] = result.getString(query, "name")
+ }
+ result.free(query)
+
+ if guild:getName() == transfer[cid].name then
+ npcHandler:say("Fill in this field with guild who receives your gold!", cid)
+ npcHandler.topic[cid] = topicList.NONE
+ return true
+ end
+
+ npcHandler:say("So you would like to transfer " .. count[cid] .. " gold from the guild " .. guild:getName() .. " to the guild " .. transfer[cid].name .. "?", cid)
+ npcHandler.topic[cid] = topicList.TRANSFER_GUILD_CONSENT
+ elseif npcHandler.topic[cid] == topicList.TRANSFER_GUILD_CONSENT then
+ if msgcontains(msg, "yes") then
+ if not transfer[cid] or count[cid] < 1 or count[cid] > guild:getBankBalance() then
+ transfer[cid] = nil
+ npcHandler:say("Your guild account cannot afford this transfer.", cid)
+ npcHandler.topic[cid] = topicList.NONE
+ return true
+ end
+
+ -- Transfer between the guilds
+ local transferAmount = count[cid]
+ guild:setBankBalance(guild:getBankBalance() - transferAmount)
+ local transferGuild = Guild(transfer[cid].name)
+ if transferGuild then
+ transferGuild:setBankBalance(transferGuild:getBankBalance() + transferAmount)
+ else
+ db.query("UPDATE `guilds` SET `balance` = (`balance`+"..transferAmount..") WHERE `id`="..transfer[cid].id)
+ end
+
+ -- Log: Withdraw from main guild
+ local currentTime = os.time()
+ local insertData = table.concat({
+ guild:getId(),
+ transfer[cid].id,
+ player:getGuid(),
+ "'WITHDRAW'",
+ transferAmount,
+ currentTime
+ },',')
+ db.query("INSERT INTO `guild_transactions` (`guild_id`,`guild_associated`,`player_associated`,`type`,`balance`,`time`) VALUES ("..insertData..");")
+
+ -- Log: Deposit to transfer guild
+ insertData = table.concat({
+ transfer[cid].id,
+ guild:getId(),
+ player:getGuid(),
+ "'DEPOSIT'",
+ transferAmount,
+ currentTime
+ },',')
+ db.query("INSERT INTO `guild_transactions` (`guild_id`,`guild_associated`,`player_associated`,`type`,`balance`,`time`) VALUES ("..insertData..");")
+
+ npcHandler:say("Very well. You have transfered " .. transferAmount .. " gold to " .. transfer[cid].name ..".", cid)
+ transfer[cid] = nil
+ return true
+ elseif msgcontains(msg, "no") then
+ npcHandler:say("Alright, is there something else I can do for you?", cid)
+ end
+ npcHandler.topic[cid] = topicList.NONE
+ elseif msgcontains(msg, "player transfer") or (npcHandler.topic[cid] == topicList.TRANSFER_TYPE and msgcontains(msg, "player")) then
+ local parts = msg:split(" ")
+
+ if #parts < 3 then
+ if #parts == 2 then
+ -- Immediate topic 11 simulation
+ count[cid] = getMoneyCount(parts[2])
+ if count[cid] < 0 or guild:getBankBalance() < count[cid] then
+ npcHandler:say("There is not enough gold in your guild account.", cid)
+ npcHandler.topic[cid] = topicList.NONE
+ return true
+ end
+ npcHandler:say("Who would you like transfer " .. count[cid] .. " gold to?", cid)
+ npcHandler.topic[cid] = topicList.TRANSFER_PLAYER_WHO
+ else
+ npcHandler:say("Please tell me the amount of gold you would like to transfer.", cid)
+ npcHandler.topic[cid] = topicList.TRANSFER_PLAYER_GOLD
+ end
+ else -- "transfer 250 playerName" or "transfer 250 to playerName"
+ local receiver = ""
+
+ local seed = 3
+ if #parts > 3 then
+ seed = parts[3] == "to" and 4 or 3
+ end
+ for i = seed, #parts do
+ receiver = receiver .. " " .. parts[i]
+ end
+ receiver = receiver:trim()
+
+ -- Immediate topic 11 simulation
+ count[cid] = getMoneyCount(parts[2])
+ if count[cid] < 0 or guild:getBankBalance() < count[cid] then
+ npcHandler:say("There is not enough gold in your guild account.", cid)
+ npcHandler.topic[cid] = topicList.NONE
+ return true
+ end
+ -- Topic 12
+ transfer[cid] = getPlayerDatabaseInfo(receiver)
+ if player:getName() == transfer[cid].name then
+ npcHandler:say("Fill in this field with person who receives your gold!", cid)
+ npcHandler.topic[cid] = topicList.NONE
+ return true
+ end
+
+ if transfer[cid] then
+ if transfer[cid].vocation == VOCATION_NONE and Player(cid):getVocation() ~= 0 then
+ npcHandler:say("I'm afraid this character only holds a junior account at our bank. Do not worry, though. Once he has chosen his vocation, his account will be upgraded.", cid)
+ npcHandler.topic[cid] = topicList.NONE
+ return true
+ end
+ npcHandler:say("So you would like to transfer " .. count[cid] .. " gold from the guild " .. guild:getName() .. " to " .. transfer[cid].name .. "?", cid)
+ npcHandler.topic[cid] = topicList.TRANSFER_PLAYER_CONSENT
+ else
+ npcHandler:say("This player does not exist.", cid)
+ npcHandler.topic[cid] = topicList.NONE
+ end
+ end
+ return true
+ elseif msgcontains(msg, "transfer") then
+ if player:getGuid() == guild:getOwnerGUID() or player:getGuildLevel() == 2 then
+ npcHandler:say("Would you like to transfer money to a {guild} or a {player}?", cid)
+ npcHandler.topic[cid] = topicList.TRANSFER_TYPE
+ else
+ npcHandler:say("Sorry, you are not authorized for withdrawals. Only Leaders and Vice-leaders are allowed to withdraw funds from guild accounts.", cid)
+ npcHandler.topic[cid] = topicList.NONE
+ end
+ return true
+ elseif npcHandler.topic[cid] == topicList.TRANSFER_PLAYER_GOLD then
+ count[cid] = getMoneyCount(msg)
+ if count[cid] < 0 or guild:getBankBalance() < count[cid] then
+ npcHandler:say("There is not enough gold in your guild account.", cid)
+ npcHandler.topic[cid] = topicList.NONE
+ return true
+ end
+ npcHandler:say("Who would you like transfer " .. count[cid] .. " gold to?", cid)
+ npcHandler.topic[cid] = topicList.TRANSFER_PLAYER_WHO
+ elseif npcHandler.topic[cid] == topicList.TRANSFER_PLAYER_WHO then
+ transfer[cid] = getPlayerDatabaseInfo(msg)
+ if player:getName() == transfer[cid].name then
+ npcHandler:say("Fill in this field with person who receives your gold!", cid)
+ npcHandler.topic[cid] = topicList.NONE
+ return true
+ end
+
+ if transfer[cid] then
+ if transfer[cid].vocation == VOCATION_NONE and Player(cid):getVocation() ~= 0 then
+ npcHandler:say("I'm afraid this character only holds a junior account at our bank. Do not worry, though. Once he has chosen his vocation, his account will be upgraded.", cid)
+ npcHandler.topic[cid] = topicList.NONE
+ return true
+ end
+ npcHandler:say("So you would like to transfer " .. count[cid] .. " gold from the guild " .. guild:getName() .. " to " .. transfer[cid].name .. "?", cid)
+ npcHandler.topic[cid] = topicList.TRANSFER_PLAYER_CONSENT
+ else
+ npcHandler:say("This player does not exist.", cid)
+ npcHandler.topic[cid] = topicList.NONE
+ end
+ elseif npcHandler.topic[cid] == topicList.TRANSFER_PLAYER_CONSENT then
+ if msgcontains(msg, "yes") then
+ if not transfer[cid] or count[cid] < 1 or count[cid] > guild:getBankBalance() then
+ transfer[cid] = nil
+ npcHandler:say("Your guild account cannot afford this transfer.", cid)
+ npcHandler.topic[cid] = topicList.NONE
+ return true
+ end
+ local transferAmount = count[cid]
+ guild:setBankBalance(guild:getBankBalance() - transferAmount)
+ player:setBankBalance(player:getBankBalance() + transferAmount)
+ if not player:transferMoneyTo(transfer[cid], transferAmount) then
+ npcHandler:say("You cannot transfer money to this account.", cid)
+ player:setBankBalance(player:getBankBalance() - transferAmount)
+ else
+ npcHandler:say("Very well. You have transfered " .. transferAmount .. " gold to " .. transfer[cid].name ..".", cid)
+ transfer[cid] = nil
+ local currentTime = os.time()
+ local insertData = table.concat({
+ guild:getId(),
+ player:getGuid(),
+ "'WITHDRAW'",
+ transferAmount,
+ currentTime
+ },',')
+ db.query("INSERT INTO `guild_transactions` (`guild_id`,`player_associated`,`type`,`balance`,`time`) VALUES ("..insertData..");")
+ end
+ elseif msgcontains(msg, "no") then
+ npcHandler:say("Alright, is there something else I can do for you?", cid)
+ end
+ npcHandler.topic[cid] = topicList.NONE
+ elseif msgcontains(msg, "ledger") then
+ if player:getGuid() ~= guild:getOwnerGUID() then
+ npcHandler.topic[cid] = topicList.NONE
+ npcHandler:say("Sorry, this is confidential between me and your Guild Leader!", cid)
+ return true
+ end
+ npcHandler.topic[cid] = topicList.LEDGER_CONSENT
+ npcHandler:say("To your advantage, I'm a man who got his papers sorted out. I have ledger records of all transaction requests for your {guild account}. Would you like to get a copy?", cid)
+ return true
+ elseif msgcontains(msg, "yes") and npcHandler.topic[cid] == topicList.LEDGER_CONSENT then
+ local dbTransactions = db.storeQuery([[
+ SELECT
+ `g`.`name` as `guild_name`,
+ `g2`.`name` as `guild_associated_name`,
+ `p`.`name` as `player_name`,
+ `t`.`type`,
+ `t`.`balance`,
+ `t`.`time`
+ FROM `guild_transactions` as `t`
+ JOIN `guilds` as `g`
+ ON `t`.`guild_id` = `g`.`id`
+ LEFT JOIN `guilds` as `g2`
+ ON `t`.`guild_associated` = `g2`.`id`
+ LEFT JOIN `players` as `p`
+ ON `t`.`player_associated` = `p`.`id`
+ WHERE `guild_id` = ]] .. guild:getId() .. [[
+ ORDER BY `t`.`time` DESC
+ ]])
+ local ledger_text = "Ledger Date: " .. os.date("%d. %b %Y - %H:%M:%S", os.time()) .. ".\nOfficial ledger for Guild: " .. guild:getName() .. ".\nGuild balance: " .. guild:getBankBalance() .. ".\n\n"
+ local records = {}
+
+ if dbTransactions ~= false then
+ repeat
+ local guild_name = result.getString(dbTransactions, 'guild_name')
+ local guild_associated_name = result.getString(dbTransactions, 'guild_associated_name')
+ local player_name = result.getString(dbTransactions, 'player_name')
+ local type = (result.getString(dbTransactions, 'type') == "WITHDRAW" and "Withdraw" or "Deposit")
+ local balance = result.getNumber(dbTransactions, 'balance')
+ local time = result.getNumber(dbTransactions, 'time')
+ if guild_associated_name ~= "" then
+ guild_associated_name = "\nReceiving Guild: The " .. guild_associated_name
+ else
+ guild_associated_name = ""
+ end
+ table.insert(records, "Date: " .. os.date("%d. %b %Y - %H:%M:%S", time) .. "\nType: Guild "..type.."\nGold Amount: " .. balance .. "\nReceipt Owner: " .. player_name .. "\nReceipt Guild: The " .. guild_name .. guild_associated_name)
+
+ until not result.next(dbTransactions)
+ result.free(dbTransactions)
+ else -- No transactions exist
+ npcHandler.topic[cid] = topicList.NONE
+ npcHandler:say("Ohh, your ledger is actually empty. You should start using your {guild account}!", cid)
+ return true
+ end
+
+ local ledger = Game.createItem(ITEM_DOCUMENT_RO, 1)
+ ledger:setAttribute(ITEM_ATTRIBUTE_TEXT, ledger_text .. table.concat(records, "\n\n"))
+ player:addItemEx(ledger)
+
+ npcHandler.topic[cid] = topicList.NONE
+ npcHandler:say("Here is your ledger "..player:getName()..". Feel free to come back anytime should you need an updated copy.", cid)
+
+ return true
+ elseif msgcontains(msg, "no") and npcHandler.topic[cid] == topicList.LEDGER_CONSENT then
+ npcHandler.topic[cid] = topicList.NONE
+ npcHandler:say("No worries, I will keep it updated for a later date then.", cid)
+ return true
+ end
+ return true
+end
+
+keywordHandler:addKeyword({"help"}, StdModule.say, {
+ npcHandler = npcHandler,
+ text = "You can check the {balance} of your guild account and {deposit} money to it. Guild Leaders and Vice-leaders can also {withdraw}, Guild Leaders can {transfer} money to other guilds and check their guild {ledger}."
+})
+keywordHandler:addAliasKeyword({'money'})
+keywordHandler:addAliasKeyword({'guild account'})
+
+keywordHandler:addKeyword({"job"}, StdModule.say, {
+ npcHandler = npcHandler,
+ text = "I work in this bank. I can {help} you with your {guild account}."
+})
+keywordHandler:addAliasKeyword({'functions'})
+keywordHandler:addAliasKeyword({'basic'})
+
+keywordHandler:addKeyword({"rent"}, StdModule.say, {
+ npcHandler = npcHandler,
+ text = "Once you have acquired a guildhall the rent will be charged automatically from your {guild account} every month."
+})
+
+keywordHandler:addKeyword({"personal"}, StdModule.say, {
+ npcHandler = npcHandler,
+ text = "Head over to my colleague known as {Banker}, he will help you get your funds into your own bank account."
+})
+
+keywordHandler:addKeyword({"banker"}, StdModule.say, {
+ npcHandler = npcHandler,
+ text = "Banker is my colleague, he loves flipping coins between his fingers. He will help you exchange money, check your balance and help you withdraw and deposit your funds."
+})
+
+npcHandler:setMessage(MESSAGE_GREET, "Welcome to the bank, |PLAYERNAME|! Need some help with your {guild account}?")
+npcHandler:setCallback(CALLBACK_GREET, greetCallback)
+npcHandler:setCallback(CALLBACK_MESSAGE_DEFAULT, creatureSayCallback)
+npcHandler:addModule(FocusModule:new())
diff --git a/data/talkactions/scripts/buyhouse.lua b/data/talkactions/scripts/buyhouse.lua
index 3320556fce..c6638fafb3 100644
--- a/data/talkactions/scripts/buyhouse.lua
+++ b/data/talkactions/scripts/buyhouse.lua
@@ -19,23 +19,81 @@ function onSay(player, words, param)
return false
end
- if house:getOwnerGuid() > 0 then
- player:sendCancelMessage("This house already has an owner.")
+ -- HOUSE_TYPE_NORMAL
+ if house:getType() == HOUSE_TYPE_NORMAL then
+ if house:getOwnerGuid() > 0 then
+ player:sendCancelMessage("This house already has an owner.")
+ return false
+ end
+
+ if player:getHouse() then
+ player:sendCancelMessage("You are already the owner of a house.")
+ return false
+ end
+
+ local price = house:getTileCount() * housePrice
+ if not player:removeMoney(price) then
+ player:sendCancelMessage("You do not have enough money.")
+ return false
+ end
+
+ house:setOwnerGuid(player:getGuid())
+ player:sendTextMessage(MESSAGE_INFO_DESCR, "You have successfully bought this house, be sure to have the money for the rent in the bank.")
return false
end
- if player:getHouse() then
- player:sendCancelMessage("You are already the owner of a house.")
+ -- HOUSE_TYPE_GUILDHALL
+ if house:getOwnerGuild() > 0 then
+ player:sendCancelMessage("This guildhall already has an owner.")
+ return false
+ end
+
+ local guild = player:getGuild()
+ if guild:getHouseId() then
+ player:sendCancelMessage("Your guild already owns a guildhall.")
+ return false
+ end
+
+ if guild:getOwnerGuid() ~= player:getGuid() then
+ player:sendCancelMessage("Only Guild Leaders can buy guildhalls.")
return false
end
local price = house:getTileCount() * housePrice
- if not player:removeTotalMoney(price) then
- player:sendCancelMessage("You do not have enough money.")
+ local balance = guild:getBankBalance()
+ if price > balance then
+ player:sendCancelMessage("Your guild bank do not have enough money, it is missing ".. price - balance .. " gold.")
return false
end
+ guild:setBankBalance(balance - price)
+ local currentTime = os.time()
+ local insertData = table.concat({
+ guild:getId(),
+ player:getGuid(),
+ "'WITHDRAW'",
+ "'RENT'",
+ price,
+ currentTime
+ },',')
+ db.query("INSERT INTO `guild_transactions` (`guild_id`, `player_associated`, `type`, `category`, `balance`, `time`) VALUES ("..insertData..");")
+
+ local receipt = Game.createItem(ITEM_RECEIPT_SUCCESS, 1)
+
+ receipt:setAttribute(ITEM_ATTRIBUTE_TEXT, table.concat({
+ "Date: " .. os.date("%d. %b %Y - %H:%M:%S", currentTime),
+ "Type: Guild Withdraw",
+ "Category: House rent",
+ "House: ".. house:getName(),
+ "Gold Amount: " .. price,
+ "Receipt Owner: " .. player:getName(),
+ "Recipient: The " .. guild:getName(),
+ "\nWe are happy to inform you that your transfer request was successfully carried out."
+ },"\n"))
+
+ player:addItemEx(receipt)
+
house:setOwnerGuid(player:getGuid())
- player:sendTextMessage(MESSAGE_INFO_DESCR, "You have successfully bought this house, be sure to have the money for the rent in the bank.")
+ player:sendTextMessage(MESSAGE_INFO_DESCR, "You have successfully bought this guildhall, be sure to have the money for the rent in the guild bank.")
return false
end
diff --git a/data/world/forgotten-house.xml b/data/world/forgotten-house.xml
index 4279d5650a..3d97b635ce 100644
--- a/data/world/forgotten-house.xml
+++ b/data/world/forgotten-house.xml
@@ -27,7 +27,7 @@
-
+
@@ -55,8 +55,8 @@
-
-
+
+
@@ -102,7 +102,7 @@
-
+
diff --git a/schema.sql b/schema.sql
index 54b444c157..371495974e 100644
--- a/schema.sql
+++ b/schema.sql
@@ -132,6 +132,7 @@ CREATE TABLE IF NOT EXISTS `guilds` (
`name` varchar(255) NOT NULL,
`ownerid` int(11) NOT NULL,
`creationdata` int(11) NOT NULL,
+ `balance` bigint(20) unsigned NOT NULL DEFAULT '0',
`motd` varchar(255) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
UNIQUE KEY (`name`),
@@ -139,6 +140,21 @@ CREATE TABLE IF NOT EXISTS `guilds` (
FOREIGN KEY (`ownerid`) REFERENCES `players`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8;
+CREATE TABLE IF NOT EXISTS `guild_transactions` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `guild_id` int(11) NOT NULL,
+ `guild_associated` int(11) DEFAULT NULL,
+ `player_associated` int(11) DEFAULT NULL,
+ `type` ENUM('DEPOSIT', 'WITHDRAW') NOT NULL,
+ `category` ENUM ('OTHER', 'RENT', 'MATERIAL', 'SERVICES', 'REVENUE', 'CONTRIBUTION') NOT NULL DEFAULT 'OTHER',
+ `balance` bigint(20) UNSIGNED NOT NULL DEFAULT '0',
+ `time` bigint(20) NOT NULL,
+ PRIMARY KEY (`id`),
+ FOREIGN KEY (`guild_id`) REFERENCES `guilds`(`id`) ON DELETE CASCADE,
+ FOREIGN KEY (`guild_associated`) REFERENCES `guilds`(`id`) ON DELETE SET NULL,
+ FOREIGN KEY (`player_associated`) REFERENCES `players`(`id`) ON DELETE SET NULL
+) ENGINE=InnoDB;
+
CREATE TABLE IF NOT EXISTS `guild_invites` (
`player_id` int(11) NOT NULL DEFAULT '0',
`guild_id` int(11) NOT NULL DEFAULT '0',
@@ -207,6 +223,7 @@ CREATE TABLE IF NOT EXISTS `houses` (
`highest_bidder` int(11) NOT NULL DEFAULT '0',
`size` int(11) NOT NULL DEFAULT '0',
`beds` int(11) NOT NULL DEFAULT '0',
+ `type` ENUM('HOUSE', 'GUILDHALL') NOT NULL DEFAULT 'HOUSE',
PRIMARY KEY (`id`),
KEY `owner` (`owner`),
KEY `town_id` (`town_id`)
@@ -349,7 +366,7 @@ CREATE TABLE IF NOT EXISTS `towns` (
UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8;
-INSERT INTO `server_config` (`config`, `value`) VALUES ('db_version', '26'), ('motd_hash', ''), ('motd_num', '0'), ('players_record', '0');
+INSERT INTO `server_config` (`config`, `value`) VALUES ('db_version', '27'), ('motd_hash', ''), ('motd_num', '0'), ('players_record', '0');
DROP TRIGGER IF EXISTS `ondelete_players`;
DROP TRIGGER IF EXISTS `oncreate_guilds`;
diff --git a/src/const.h b/src/const.h
index 5280ea7a71..77f9a6bfd9 100644
--- a/src/const.h
+++ b/src/const.h
@@ -501,6 +501,8 @@ enum item_t : uint16_t {
ITEM_LETTER = 2597,
ITEM_LETTER_STAMPED = 2598,
ITEM_LABEL = 2599,
+ ITEM_RECEIPT_SUCCESS = 24301,
+ ITEM_RECEIPT_FAIL = 24302,
ITEM_AMULETOFLOSS = 2173,
diff --git a/src/enums.h b/src/enums.h
index 5168a33f0f..9bb0b887fc 100644
--- a/src/enums.h
+++ b/src/enums.h
@@ -459,6 +459,10 @@ enum ReturnValue {
RETURNVALUE_TRADEPLAYERHIGHESTBIDDER,
RETURNVALUE_YOUCANNOTTRADETHISHOUSE,
RETURNVALUE_YOUDONTHAVEREQUIREDPROFESSION,
+ RETURNVALUE_TRADEPLAYERNOTINAGUILD,
+ RETURNVALUE_TRADEGUILDALREADYOWNSAHOUSE,
+ RETURNVALUE_TRADEPLAYERNOTGUILDLEADER,
+ RETURNVALUE_YOUARENOTGUILDLEADER,
RETURNVALUE_CANNOTMOVEITEMISNOTSTOREITEM,
RETURNVALUE_ITEMCANNOTBEMOVEDTHERE,
};
diff --git a/src/guild.cpp b/src/guild.cpp
index dd211103bd..00e6325f58 100644
--- a/src/guild.cpp
+++ b/src/guild.cpp
@@ -81,3 +81,11 @@ void Guild::addRank(uint32_t rankId, const std::string& rankName, uint8_t level)
{
ranks.emplace_back(std::make_shared(rankId, rankName, level));
}
+
+void Guild::setBankBalance(uint64_t balance) {
+ bankBalance = balance;
+ Database& db = Database::getInstance();
+ std::ostringstream query;
+ query << "UPDATE `guilds` SET `balance`=" << bankBalance << " WHERE `id`=" << id;
+ db.executeQuery(query.str());
+}
diff --git a/src/guild.h b/src/guild.h
index d3343c8dea..d8434e02fa 100644
--- a/src/guild.h
+++ b/src/guild.h
@@ -57,6 +57,25 @@ class Guild
memberCount = count;
}
+ uint64_t getBankBalance() const {
+ return bankBalance;
+ }
+ void setBankBalance(uint64_t balance);
+
+ uint32_t getHouseId() const {
+ return houseId;
+ }
+ void setHouseId(uint32_t id) {
+ houseId = id;
+ }
+
+ uint32_t getOwnerGUID() const {
+ return ownerGUID;
+ }
+ void setOwnerGUID(uint32_t guid) {
+ ownerGUID = guid;
+ }
+
const std::vector& getRanks() const {
return ranks;
}
@@ -79,6 +98,9 @@ class Guild
std::string motd;
uint32_t id;
uint32_t memberCount = 0;
+ uint64_t bankBalance = 0;
+ uint32_t ownerGUID = 0;
+ uint32_t houseId = 0;
};
#endif
diff --git a/src/house.cpp b/src/house.cpp
index e011080395..07d5498212 100644
--- a/src/house.cpp
+++ b/src/house.cpp
@@ -38,26 +38,80 @@ void House::addTile(HouseTile* tile)
houseTiles.push_back(tile);
}
-void House::setOwner(uint32_t guid, bool updateDatabase/* = true*/, Player* player/* = nullptr*/)
+std::tuple House::initializeOwnerDataFromDatabase(uint32_t guid_guild, HouseType_t type) {
+ if (guid_guild == 0) {
+ return std::make_tuple(0, 0, "", 0, "");
+ }
+
+ Database& db = Database::getInstance();
+
+ if (type == HOUSE_TYPE_NORMAL) {
+ std::ostringstream query;
+ query << "SELECT `id`, `name`, `account_id` FROM `players` WHERE `id`=" << guid_guild;
+ if (DBResult_ptr result = db.storeQuery(query.str())) {
+ return std::make_tuple(
+ result->getNumber("id"), // sqlPlayerGuid
+ result->getNumber("account_id"), // sqlAccountId
+ result->getString("name"), // sqlPlayerName
+ 0, // sqlGuildId
+ "" // sqlGuildName
+ );
+ }
+ throw std::runtime_error("Error in House::setOwner - Failed to find player GUID");
+ }
+
+ // HOUSE_TYPE_GUILDHALL
+ std::ostringstream query;
+ query << "SELECT `g`.`id`, `g`.`name` as `guild_name`, `g`.`ownerid`, `p`.`name`, `p`.`account_id` ";
+ query << "FROM `guilds` as `g` INNER JOIN `players` AS `p` ON `g`.`ownerid` = `p`.`id` ";
+ query << "WHERE `g`.`id`=" << guid_guild;
+ if (DBResult_ptr result = db.storeQuery(query.str())) {
+ return std::make_tuple(
+ result->getNumber("ownerid"), // sqlPlayerGuid
+ result->getNumber("account_id"), // sqlAccountId
+ result->getString("name"), // sqlPlayerName
+ result->getNumber("id"), // sqlGuildId
+ result->getString("guild_name") // sqlGuildName
+ );
+ }
+ throw std::runtime_error("Error in House::setOwner - Failed to find guild ID");
+}
+
+// Param: guid_guild is either 0 (remove owner), player->getGUID() or guild->getId()
+void House::setOwner(uint32_t guid_guild, bool updateDatabase/* = true*/, Player* previousPlayer/* = nullptr*/)
{
- if (updateDatabase && owner != guid) {
+
+ uint32_t sqlAccountId, sqlPlayerGuid, sqlGuildId;
+ std::string sqlPlayerName, sqlGuildName;
+
+ try {
+ std::tie(sqlPlayerGuid, sqlAccountId, sqlPlayerName, sqlGuildId, sqlGuildName) = initializeOwnerDataFromDatabase(guid_guild, type);
+ } catch (const std::runtime_error& err) {
+ std::cout << err.what();
+ return;
+ }
+
+ // If the old owner of this house is not the new owner
+ if (updateDatabase && owner != guid_guild) {
Database& db = Database::getInstance();
std::ostringstream query;
- query << "UPDATE `houses` SET `owner` = " << guid << ", `bid` = 0, `bid_end` = 0, `last_bid` = 0, `highest_bidder` = 0 WHERE `id` = " << id;
+ query << "UPDATE `houses` SET `owner` = " << guid_guild << ", `bid` = 0, `bid_end` = 0, `last_bid` = 0, `highest_bidder` = 0 WHERE `id` = " << id;
db.executeQuery(query.str());
}
- if (isLoaded && owner == guid) {
+ if (isLoaded && owner == guid_guild) {
return;
}
isLoaded = true;
+ // If there was a previous owner to his house
+ // clean the house and return items to owner
if (owner != 0) {
//send items to depot
- if (player) {
- transferToDepot(player);
+ if (previousPlayer) {
+ transferToDepot(previousPlayer);
} else {
transferToDepot();
}
@@ -106,12 +160,16 @@ void House::setOwner(uint32_t guid, bool updateDatabase/* = true*/, Player* play
rentWarnings = 0;
- if (guid != 0) {
- std::string name = IOLoginData::getNameByGuid(guid);
- if (!name.empty()) {
- owner = guid;
- ownerName = name;
- ownerAccountId = IOLoginData::getAccountIdByPlayerName(name);
+ // Save the new owner to the house object
+ if (guid_guild != 0) {
+ owner = guid_guild;
+ ownerAccountId = sqlAccountId;
+ if (type == HOUSE_TYPE_GUILDHALL) {
+ std::ostringstream ss;
+ ss << "The " << sqlGuildName;
+ ownerName = ss.str();
+ } else {
+ ownerName = sqlPlayerName;
}
}
@@ -120,11 +178,16 @@ void House::setOwner(uint32_t guid, bool updateDatabase/* = true*/, Player* play
void House::updateDoorDescription() const
{
+ std::string houseType = "house";
+ if (type == HOUSE_TYPE_GUILDHALL) {
+ houseType = "guildhall";
+ }
+
std::ostringstream ss;
if (owner != 0) {
- ss << "It belongs to house '" << houseName << "'. " << ownerName << " owns this house.";
+ ss << "It belongs to " << houseType << " '" << houseName << "'. " << ownerName << " owns this " << houseType << ".";
} else {
- ss << "It belongs to house '" << houseName << "'. Nobody owns this house.";
+ ss << "It belongs to " << houseType << " '" << houseName << "'. Nobody owns this " << houseType << ".";
const int32_t housePrice = g_config.getNumber(ConfigManager::HOUSE_PRICE);
if (housePrice != -1 && g_config.getBoolean(ConfigManager::HOUSE_DOOR_SHOW_PRICE)) {
@@ -142,25 +205,37 @@ AccessHouseLevel_t House::getHouseAccessLevel(const Player* player)
if (!player) {
return HOUSE_OWNER;
}
-
- if (g_config.getBoolean(ConfigManager::HOUSE_OWNED_BY_ACCOUNT)) {
- if (ownerAccountId == player->getAccount()) {
- return HOUSE_OWNER;
- }
- }
-
if (player->hasFlag(PlayerFlag_CanEditHouses)) {
return HOUSE_OWNER;
}
- if (player->getGUID() == owner) {
- return HOUSE_OWNER;
+ uint32_t guid = player->getGUID();
+
+ if (type == HOUSE_TYPE_NORMAL) {
+ if (g_config.getBoolean(ConfigManager::HOUSE_OWNED_BY_ACCOUNT)) {
+ if (ownerAccountId == player->getAccount()) {
+ return HOUSE_OWNER;
+ }
+ }
+ if (guid == owner) {
+ return HOUSE_OWNER;
+ }
+
+ } else { // HOUSE_TYPE_GUILDHALL
+ Guild* guild = player->getGuild();
+ if (guild && guild->getId() == owner) {
+ if (guild->getOwnerGUID() == guid) {
+ return HOUSE_OWNER;
+ }
+ if (player->getGuildRank() == guild->getRankByLevel(2)) {
+ return HOUSE_SUBOWNER;
+ }
+ }
}
if (subOwnerList.isInList(player)) {
return HOUSE_SUBOWNER;
}
-
if (guestList.isInList(player)) {
return HOUSE_GUEST;
}
@@ -226,18 +301,42 @@ bool House::transferToDepot() const
return false;
}
- Player* player = g_game.getPlayerByGUID(owner);
- if (player) {
- transferToDepot(player);
- } else {
- Player tmpPlayer(nullptr);
- if (!IOLoginData::loadPlayerById(&tmpPlayer, owner)) {
- return false;
+ if (type == HOUSE_TYPE_NORMAL) {
+ Player* player = g_game.getPlayerByGUID(owner);
+ if (player) {
+ transferToDepot(player);
+ } else {
+ Player tmpPlayer(nullptr);
+ if (!IOLoginData::loadPlayerById(&tmpPlayer, owner)) {
+ return false;
+ }
+
+ transferToDepot(&tmpPlayer);
+ IOLoginData::savePlayer(&tmpPlayer);
}
+ } else { // HOUSE_TYPE_GUILDHALL
+ Guild* guild = g_game.getGuild(owner);
+ if (!guild) {
+ guild = IOGuild::loadGuild(owner);
+ if (!guild) {
+ std::cout << "Warning: [Houses::transferToDepot] Failed to find guild associated to guildhall = " << id << ". Guild = " << owner << std::endl;
+ return false;
+ }
+ }
+ Player* player = g_game.getPlayerByGUID(guild->getOwnerGUID());
+ if (player) {
+ transferToDepot(player);
+ } else {
+ Player tmpPlayer(nullptr);
+ if (!IOLoginData::loadPlayerById(&tmpPlayer, guild->getOwnerGUID())) {
+ return false;
+ }
- transferToDepot(&tmpPlayer);
- IOLoginData::savePlayer(&tmpPlayer);
+ transferToDepot(&tmpPlayer);
+ IOLoginData::savePlayer(&tmpPlayer);
+ }
}
+
return true;
}
@@ -408,7 +507,13 @@ bool House::executeTransfer(HouseTransferItem* item, Player* newOwner)
return false;
}
- setOwner(newOwner->getGUID());
+ if (type == HOUSE_TYPE_NORMAL) {
+ setOwner(newOwner->getGUID());
+ } else {
+ Guild* newOwnerGuild = newOwner->getGuild();
+ setOwner(newOwnerGuild->getId());
+ }
+
transferItem = nullptr;
return true;
}
@@ -652,12 +757,51 @@ bool Houses::loadHousesXML(const std::string& filename)
house->setRent(pugi::cast(houseNode.attribute("rent").value()));
house->setTownId(pugi::cast(houseNode.attribute("townid").value()));
+ if (houseNode.attribute("guildhall").as_bool()) {
+ house->setType(HOUSE_TYPE_GUILDHALL);
+ }
house->setOwner(0, false);
}
return true;
}
+time_t Houses::increasePaidUntil(RentPeriod_t rentPeriod, time_t paidUntil) const
+{
+ switch (rentPeriod) {
+ case RENTPERIOD_DAILY:
+ return paidUntil += 24 * 60 * 60;
+ case RENTPERIOD_WEEKLY:
+ return paidUntil += 7 * 24 * 60 * 60;
+ case RENTPERIOD_MONTHLY:
+ return paidUntil += 30 * 24 * 60 * 60;
+ case RENTPERIOD_YEARLY:
+ return paidUntil += 365 * 24 * 60 * 60;
+ case RENTPERIOD_DEV:
+ return paidUntil += 5 * 60;
+ default:
+ return paidUntil;
+ }
+}
+
+std::string Houses::getRentPeriod(RentPeriod_t rentPeriod) const
+{
+ switch (rentPeriod) {
+ case RENTPERIOD_DAILY:
+ return "daily";
+ case RENTPERIOD_WEEKLY:
+ return "weekly";
+ case RENTPERIOD_MONTHLY:
+ return "monthly";
+ case RENTPERIOD_YEARLY:
+ return "annual";
+ case RENTPERIOD_DEV:
+ return "dev";
+ default:
+ return "never";
+ }
+}
+
void Houses::payHouses(RentPeriod_t rentPeriod) const
{
if (rentPeriod == RENTPERIOD_NEVER) {
@@ -682,73 +826,93 @@ void Houses::payHouses(RentPeriod_t rentPeriod) const
continue;
}
- Player player(nullptr);
- if (!IOLoginData::loadPlayerById(&player, ownerId)) {
- // Player doesn't exist, reset house owner
- house->setOwner(0);
- continue;
- }
-
- if (player.getBankBalance() >= rent) {
- player.setBankBalance(player.getBankBalance() - rent);
-
- time_t paidUntil = currentTime;
- switch (rentPeriod) {
- case RENTPERIOD_DAILY:
- paidUntil += 24 * 60 * 60;
- break;
- case RENTPERIOD_WEEKLY:
- paidUntil += 24 * 60 * 60 * 7;
- break;
- case RENTPERIOD_MONTHLY:
- paidUntil += 24 * 60 * 60 * 30;
- break;
- case RENTPERIOD_YEARLY:
- paidUntil += 24 * 60 * 60 * 365;
- break;
- default:
- break;
+ if (house->getType() == HOUSE_TYPE_NORMAL) {
+ Player player(nullptr);
+ if (!IOLoginData::loadPlayerById(&player, ownerId)) {
+ // Player doesn't exist, reset house owner
+ house->setOwner(0);
+ continue;
}
- house->setPaidUntil(paidUntil);
- } else {
- if (house->getPayRentWarnings() < 7) {
- int32_t daysLeft = 7 - house->getPayRentWarnings();
+ if (player.getBankBalance() >= rent) {
+ player.setBankBalance(player.getBankBalance() - rent);
+
+ time_t paidUntil = increasePaidUntil(rentPeriod, currentTime);
+ house->setPaidUntil(paidUntil);
+ } else {
+ if (house->getPayRentWarnings() < 7) {
+ int32_t daysLeft = 7 - house->getPayRentWarnings();
- Item* letter = Item::CreateItem(ITEM_LETTER_STAMPED);
- std::string period;
+ Item* letter = Item::CreateItem(ITEM_LETTER_STAMPED);
+ std::string period = getRentPeriod(rentPeriod);
- switch (rentPeriod) {
- case RENTPERIOD_DAILY:
- period = "daily";
- break;
+ std::ostringstream ss;
+ ss << "Warning! \nThe " << period << " rent of " << house->getRent() << " gold for your house \"" << house->getName() << "\" is payable. Have it within " << daysLeft << " days or you will lose this house.";
+ letter->setText(ss.str());
+ g_game.internalAddItem(player.getInbox(), letter, INDEX_WHEREEVER, FLAG_NOLIMIT);
+ house->setPayRentWarnings(house->getPayRentWarnings() + 1);
+ } else {
+ house->setOwner(0, true, &player);
+ }
+ }
+ IOLoginData::savePlayer(&player);
+
+ } else { // HOUSE_TYPE_GUILDHALL
+ Guild* guild = g_game.getGuild(ownerId);
+ if (!guild) {
+ guild = IOGuild::loadGuild(ownerId);
+ if (!guild) {
+ house->setOwner(0);
+ continue;
+ }
+ }
- case RENTPERIOD_WEEKLY:
- period = "weekly";
- break;
+ // If guild can afford paying rent
+ if (guild->getBankBalance() >= rent) {
+ std::cout << "[Info - Houses::payHouses] Paying rent info"
+ << " - Name: " << house->getName()
+ << " - House id: " << house->getId()
+ << " - Guild: " << guild->getName()
+ << " - Balance " << guild->getBankBalance()
+ << " - Rent " << rent
+ << " - New balance " << guild->getBankBalance() - rent << std::endl;
+ guild->setBankBalance(guild->getBankBalance() - rent);
+
+ // Log guild transaction
+ Database& db = Database::getInstance();
+ std::ostringstream query;
+ query << "INSERT INTO `guild_transactions` (`guild_id`,`type`,`category`,`balance`,`time`) VALUES (" << ownerId << ",'WITHDRAW','RENT'," << rent << "," << currentTime << ");";
+ db.executeQuery(query.str());
+
+ time_t paidUntil = increasePaidUntil(rentPeriod, currentTime);
+ house->setPaidUntil(paidUntil);
+ } else { // guild cannot afford rent
+ std::cout << "a guild cannot afford their rent " << house->getPayRentWarnings() << std::endl;
+ Player player(nullptr);
+ if (!IOLoginData::loadPlayerById(&player, guild->getOwnerGUID())) {
+ // Player doesn't exist, reset house owner
+ house->setOwner(0);
+ std::ostringstream ss;
+ ss << "Error: Guild " << guild->getName() << " has an owner that does not exist: " << guild->getOwnerGUID();
+ std::cout << ss.str() << std::endl;
+ continue;
+ }
- case RENTPERIOD_MONTHLY:
- period = "monthly";
- break;
+ if (house->getPayRentWarnings() < 7) {
+ int32_t daysLeft = 7 - house->getPayRentWarnings();
- case RENTPERIOD_YEARLY:
- period = "annual";
- break;
+ Item* letter = Item::CreateItem(ITEM_LETTER_STAMPED);
+ std::string period = getRentPeriod(rentPeriod);
- default:
- break;
+ std::ostringstream ss;
+ ss << "Warning! \nThe " << period << " rent of " << house->getRent() << " gold for your house \"" << house->getName() << "\" is payable. Have it within " << daysLeft << " days or you will lose this house.";
+ letter->setText(ss.str());
+ g_game.internalAddItem(player.getInbox(), letter, INDEX_WHEREEVER, FLAG_NOLIMIT);
+ house->setPayRentWarnings(house->getPayRentWarnings() + 1);
+ } else {
+ house->setOwner(0, true, &player);
}
-
- std::ostringstream ss;
- ss << "Warning! \nThe " << period << " rent of " << house->getRent() << " gold for your house \"" << house->getName() << "\" is payable. Have it within " << daysLeft << " days or you will lose this house.";
- letter->setText(ss.str());
- g_game.internalAddItem(player.getInbox(), letter, INDEX_WHEREEVER, FLAG_NOLIMIT);
- house->setPayRentWarnings(house->getPayRentWarnings() + 1);
- } else {
- house->setOwner(0, true, &player);
}
}
-
- IOLoginData::savePlayer(&player);
}
}
diff --git a/src/house.h b/src/house.h
index 1a73a02f1f..0738fbb4a9 100644
--- a/src/house.h
+++ b/src/house.h
@@ -108,6 +108,13 @@ enum AccessHouseLevel_t {
HOUSE_OWNER = 3,
};
+// this enum should represent DB `houses`.`type`
+// MySQL enum indexes start at 1
+enum HouseType_t {
+ HOUSE_TYPE_NORMAL = 1,
+ HOUSE_TYPE_GUILDHALL = 2,
+};
+
using HouseTileList = std::list;
using HouseBedItemList = std::list;
@@ -161,7 +168,7 @@ class House
return houseName;
}
- void setOwner(uint32_t guid, bool updateDatabase = true, Player* player = nullptr);
+ void setOwner(uint32_t guid_guild, bool updateDatabase = true, Player* previousPlayer = nullptr);
uint32_t getOwner() const {
return owner;
}
@@ -198,6 +205,13 @@ class House
return id;
}
+ void setType(HouseType_t type) {
+ this->type = type;
+ }
+ HouseType_t getType() const {
+ return type;
+ }
+
void addDoor(Door* door);
void removeDoor(Door* door);
Door* getDoorByNumber(uint32_t doorId) const;
@@ -224,6 +238,8 @@ class House
}
private:
+ std::tuple initializeOwnerDataFromDatabase(uint32_t guid_guild, HouseType_t type);
+
bool transferToDepot() const;
bool transferToDepot(Player* player) const;
@@ -252,6 +268,8 @@ class House
Position posEntry = {};
+ HouseType_t type = HOUSE_TYPE_NORMAL;
+
bool isLoaded = false;
};
@@ -263,6 +281,7 @@ enum RentPeriod_t {
RENTPERIOD_MONTHLY,
RENTPERIOD_YEARLY,
RENTPERIOD_NEVER,
+ RENTPERIOD_DEV,// 5 minutes rent period for testing purposes
};
class Houses
@@ -303,6 +322,8 @@ class Houses
bool loadHousesXML(const std::string& filename);
void payHouses(RentPeriod_t rentPeriod) const;
+ std::string getRentPeriod(RentPeriod_t rentPeriod) const;
+ time_t increasePaidUntil(RentPeriod_t rentPeriod, time_t paidUntil) const;
const HouseMap& getHouses() const {
return houseMap;
diff --git a/src/ioguild.cpp b/src/ioguild.cpp
index ed48b14560..a27c8eb9e4 100644
--- a/src/ioguild.cpp
+++ b/src/ioguild.cpp
@@ -27,9 +27,25 @@ Guild* IOGuild::loadGuild(uint32_t guildId)
{
Database& db = Database::getInstance();
std::ostringstream query;
- query << "SELECT `name` FROM `guilds` WHERE `id` = " << guildId;
+ /* Readable SQL representation
+ query << "
+ SELECT
+ `g`.`name`,
+ `g`.`balance`,
+ `g`.`ownerid`,
+ IFNULL(`h`.`id`, 0) as `house_id`
+ FROM `guilds` AS `g`
+ LEFT JOIN `houses` AS `h`
+ ON `h`.`type` = 'Guildhall'
+ AND `h`.`owner` = " << guildId << "
+ WHERE `g`.`id` = " << guildId;
+ */
+ query << "SELECT `g`.`name`, `g`.`balance`, `g`.`ownerid`, IFNULL(`h`.`id`, 0) as `house_id` FROM `guilds` AS `g` LEFT JOIN `houses` AS `h` ON `h`.`type` = 'Guildhall' AND `h`.`owner` = " << guildId << " WHERE `g`.`id` = " << guildId;
if (DBResult_ptr result = db.storeQuery(query.str())) {
Guild* guild = new Guild(guildId, result->getString("name"));
+ guild->setBankBalance(result->getNumber("balance"));
+ guild->setOwnerGUID(result->getNumber("ownerid"));
+ guild->setHouseId(result->getNumber("house_id"));
query.str(std::string());
query << "SELECT `id`, `name`, `level` FROM `guild_ranks` WHERE `guild_id` = " << guildId;
diff --git a/src/iologindata.cpp b/src/iologindata.cpp
index 49b506ee25..c1fd6df112 100644
--- a/src/iologindata.cpp
+++ b/src/iologindata.cpp
@@ -1011,12 +1011,13 @@ void IOLoginData::increaseBankBalance(uint32_t guid, uint64_t bankBalance)
Database::getInstance().executeQuery(query.str());
}
-bool IOLoginData::hasBiddedOnHouse(uint32_t guid)
+// guid_guild = player->getGUID() or guild->getId()
+bool IOLoginData::hasBiddedOnHouse(uint32_t guid_guild)
{
Database& db = Database::getInstance();
std::ostringstream query;
- query << "SELECT `id` FROM `houses` WHERE `highest_bidder` = " << guid << " LIMIT 1";
+ query << "SELECT `id` FROM `houses` WHERE `highest_bidder` = " << guid_guild << " LIMIT 1";
return db.storeQuery(query.str()).get() != nullptr;
}
diff --git a/src/iomapserialize.cpp b/src/iomapserialize.cpp
index af606c2147..a05decc357 100644
--- a/src/iomapserialize.cpp
+++ b/src/iomapserialize.cpp
@@ -272,7 +272,7 @@ bool IOMapSerialize::loadHouseInfo()
{
Database& db = Database::getInstance();
- DBResult_ptr result = db.storeQuery("SELECT `id`, `owner`, `paid`, `warnings` FROM `houses`");
+ DBResult_ptr result = db.storeQuery("SELECT `id`, CAST(`type` as UNSIGNED) AS `type`, `owner`, `paid`, `warnings` FROM `houses`");
if (!result) {
return false;
}
@@ -318,10 +318,10 @@ bool IOMapSerialize::saveHouseInfo()
DBResult_ptr result = db.storeQuery(query.str());
if (result) {
query.str(std::string());
- query << "UPDATE `houses` SET `owner` = " << house->getOwner() << ", `paid` = " << house->getPaidUntil() << ", `warnings` = " << house->getPayRentWarnings() << ", `name` = " << db.escapeString(house->getName()) << ", `town_id` = " << house->getTownId() << ", `rent` = " << house->getRent() << ", `size` = " << house->getTiles().size() << ", `beds` = " << house->getBedCount() << " WHERE `id` = " << house->getId();
+ query << "UPDATE `houses` SET `owner` = " << house->getOwner() << ", `type` = " << house->getType() << ", `paid` = " << house->getPaidUntil() << ", `warnings` = " << house->getPayRentWarnings() << ", `name` = " << db.escapeString(house->getName()) << ", `town_id` = " << house->getTownId() << ", `rent` = " << house->getRent() << ", `size` = " << house->getTiles().size() << ", `beds` = " << house->getBedCount() << " WHERE `id` = " << house->getId();
} else {
query.str(std::string());
- query << "INSERT INTO `houses` (`id`, `owner`, `paid`, `warnings`, `name`, `town_id`, `rent`, `size`, `beds`) VALUES (" << house->getId() << ',' << house->getOwner() << ',' << house->getPaidUntil() << ',' << house->getPayRentWarnings() << ',' << db.escapeString(house->getName()) << ',' << house->getTownId() << ',' << house->getRent() << ',' << house->getTiles().size() << ',' << house->getBedCount() << ')';
+ query << "INSERT INTO `houses` (`id`, `type`, `owner`, `paid`, `warnings`, `name`, `town_id`, `rent`, `size`, `beds`) VALUES (" << house->getId() << ',' << house->getType() << ',' << house->getOwner() << ',' << house->getPaidUntil() << ',' << house->getPayRentWarnings() << ',' << db.escapeString(house->getName()) << ',' << house->getTownId() << ',' << house->getRent() << ',' << house->getTiles().size() << ',' << house->getBedCount() << ')';
}
db.executeQuery(query.str());
diff --git a/src/luascript.cpp b/src/luascript.cpp
index cb4f828926..6052055dce 100644
--- a/src/luascript.cpp
+++ b/src/luascript.cpp
@@ -1533,6 +1533,11 @@ void LuaScriptInterface::registerFunctions()
registerEnum(ITEM_WILDGROWTH)
registerEnum(ITEM_WILDGROWTH_PERSISTENT)
registerEnum(ITEM_WILDGROWTH_SAFE)
+ registerEnum(ITEM_LETTER)
+ registerEnum(ITEM_LETTER_STAMPED)
+ registerEnum(ITEM_DOCUMENT_RO)
+ registerEnum(ITEM_RECEIPT_SUCCESS)
+ registerEnum(ITEM_RECEIPT_FAIL)
registerEnum(PlayerFlag_CannotUseCombat)
registerEnum(PlayerFlag_CannotAttackPlayer)
@@ -1735,6 +1740,9 @@ void LuaScriptInterface::registerFunctions()
// Use with house:getAccessList, house:setAccessList
registerEnum(GUEST_LIST)
registerEnum(SUBOWNER_LIST)
+ // Use with house:getType
+ registerEnum(HOUSE_TYPE_NORMAL)
+ registerEnum(HOUSE_TYPE_GUILDHALL)
// Use with npc:setSpeechBubble
registerEnum(SPEECHBUBBLE_NONE)
@@ -1836,6 +1844,10 @@ void LuaScriptInterface::registerFunctions()
registerEnum(RETURNVALUE_TRADEPLAYERHIGHESTBIDDER)
registerEnum(RETURNVALUE_YOUCANNOTTRADETHISHOUSE)
registerEnum(RETURNVALUE_YOUDONTHAVEREQUIREDPROFESSION)
+ registerEnum(RETURNVALUE_TRADEPLAYERNOTINAGUILD)
+ registerEnum(RETURNVALUE_TRADEGUILDALREADYOWNSAHOUSE)
+ registerEnum(RETURNVALUE_TRADEPLAYERNOTGUILDLEADER)
+ registerEnum(RETURNVALUE_YOUARENOTGUILDLEADER)
registerEnum(RELOAD_TYPE_ALL)
registerEnum(RELOAD_TYPE_ACTIONS)
@@ -2510,6 +2522,12 @@ void LuaScriptInterface::registerFunctions()
registerMethod("Guild", "getMotd", LuaScriptInterface::luaGuildGetMotd);
registerMethod("Guild", "setMotd", LuaScriptInterface::luaGuildSetMotd);
+ registerMethod("Guild", "getBankBalance", LuaScriptInterface::luaGuildGetBankBalance);
+ registerMethod("Guild", "setBankBalance", LuaScriptInterface::luaGuildSetBankBalance);
+
+ registerMethod("Guild", "getOwnerGUID", LuaScriptInterface::luaGuildGetOwnerGUID);
+ registerMethod("Guild", "getHouseId", LuaScriptInterface::luaGuildGetHouseId);
+
// Group
registerClass("Group", "", LuaScriptInterface::luaGroupCreate);
registerMetaMethod("Group", "__eq", LuaScriptInterface::luaUserdataCompare);
@@ -2566,6 +2584,7 @@ void LuaScriptInterface::registerFunctions()
registerMetaMethod("House", "__eq", LuaScriptInterface::luaUserdataCompare);
registerMethod("House", "getId", LuaScriptInterface::luaHouseGetId);
+ registerMethod("House", "getType", LuaScriptInterface::luaHouseGetType);
registerMethod("House", "getName", LuaScriptInterface::luaHouseGetName);
registerMethod("House", "getTown", LuaScriptInterface::luaHouseGetTown);
registerMethod("House", "getExitPosition", LuaScriptInterface::luaHouseGetExitPosition);
@@ -2573,6 +2592,7 @@ void LuaScriptInterface::registerFunctions()
registerMethod("House", "getOwnerGuid", LuaScriptInterface::luaHouseGetOwnerGuid);
registerMethod("House", "setOwnerGuid", LuaScriptInterface::luaHouseSetOwnerGuid);
+ registerMethod("House", "getOwnerGuild", LuaScriptInterface::luaHouseGetOwnerGuild);
registerMethod("House", "startTrade", LuaScriptInterface::luaHouseStartTrade);
registerMethod("House", "getBeds", LuaScriptInterface::luaHouseGetBeds);
@@ -10426,6 +10446,61 @@ int LuaScriptInterface::luaGuildSetMotd(lua_State* L)
return 1;
}
+int LuaScriptInterface::luaGuildGetBankBalance(lua_State* L)
+{
+ // guild:getBankBalance()
+ Guild* guild = getUserdata(L, 1);
+ if (guild) {
+ lua_pushnumber(L, guild->getBankBalance());
+ } else {
+ lua_pushnil(L);
+ }
+ return 1;
+}
+
+int LuaScriptInterface::luaGuildSetBankBalance(lua_State* L)
+{
+ // guild:setBankBalance(balance)
+ uint64_t balance = getNumber(L, 2);
+ Guild* guild = getUserdata(L, 1);
+ if (guild) {
+ guild->setBankBalance(balance);
+ pushBoolean(L, true);
+ } else {
+ lua_pushnil(L);
+ }
+ return 1;
+}
+
+int LuaScriptInterface::luaGuildGetOwnerGUID(lua_State* L)
+{
+ // guild:getOwnerGUID()
+ Guild* guild = getUserdata(L, 1);
+ if (guild) {
+ lua_pushnumber(L, guild->getOwnerGUID());
+ } else {
+ lua_pushnil(L);
+ }
+ return 1;
+}
+
+int LuaScriptInterface::luaGuildGetHouseId(lua_State* L)
+{
+ // guild:getHouseId()
+ Guild* guild = getUserdata(L, 1);
+ if (guild) {
+ uint32_t houseId = guild->getHouseId();
+ if (houseId > 0) {
+ lua_pushnumber(L, houseId);
+ } else {
+ lua_pushnil(L);
+ }
+ } else {
+ lua_pushnil(L);
+ }
+ return 1;
+}
+
// Group
int LuaScriptInterface::luaGroupCreate(lua_State* L)
{
@@ -10889,6 +10964,18 @@ int LuaScriptInterface::luaHouseGetId(lua_State* L)
return 1;
}
+int LuaScriptInterface::luaHouseGetType(lua_State* L)
+{
+ // house:getType()
+ House* house = getUserdata(L, 1);
+ if (house) {
+ lua_pushnumber(L, house->getType());
+ } else {
+ lua_pushnil(L);
+ }
+ return 1;
+}
+
int LuaScriptInterface::luaHouseGetName(lua_State* L)
{
// house:getName()
@@ -10949,7 +11036,27 @@ int LuaScriptInterface::luaHouseGetOwnerGuid(lua_State* L)
// house:getOwnerGuid()
House* house = getUserdata(L, 1);
if (house) {
- lua_pushnumber(L, house->getOwner());
+ if (house->getType() == HOUSE_TYPE_NORMAL) {
+ lua_pushnumber(L, house->getOwner());
+ } else {
+ lua_pushnil(L);
+ }
+ } else {
+ lua_pushnil(L);
+ }
+ return 1;
+}
+
+int LuaScriptInterface::luaHouseGetOwnerGuild(lua_State* L)
+{
+ // house:getOwnerGuild()
+ House* house = getUserdata(L, 1);
+ if (house) {
+ if (house->getType() == HOUSE_TYPE_GUILDHALL) {
+ lua_pushnumber(L, house->getOwner());
+ } else {
+ lua_pushnil(L);
+ }
} else {
lua_pushnil(L);
}
@@ -10961,9 +11068,22 @@ int LuaScriptInterface::luaHouseSetOwnerGuid(lua_State* L)
// house:setOwnerGuid(guid[, updateDatabase = true])
House* house = getUserdata(L, 1);
if (house) {
- uint32_t guid = getNumber(L, 2);
+ uint32_t guid_guild = getNumber(L, 2);
bool updateDatabase = getBoolean(L, 3, true);
- house->setOwner(guid, updateDatabase);
+ // If house is guildhall and player happens to be in a guild
+ // We can pull the guild_id and pass it over to house->setOwner
+ if (house->getType() == HOUSE_TYPE_GUILDHALL) {
+ Database& db = Database::getInstance();
+ std::ostringstream query;
+ query << "SELECT `guild_id` FROM `guild_membership` WHERE `player_id`=" << guid_guild;
+ if (DBResult_ptr result = db.storeQuery(query.str())) {
+ guid_guild = result->getNumber("guild_id");
+ } else {
+ pushBoolean(L, false);
+ return 1;
+ }
+ }
+ house->setOwner(guid_guild, updateDatabase);
pushBoolean(L, true);
} else {
lua_pushnil(L);
@@ -10988,17 +11108,63 @@ int LuaScriptInterface::luaHouseStartTrade(lua_State* L)
return 1;
}
- if (house->getOwner() != player->getGUID()) {
+ if (house->getType() == HOUSE_TYPE_NORMAL) {
+ if (house->getOwner() != player->getGUID()) {
+ lua_pushnumber(L, RETURNVALUE_YOUDONTOWNTHISHOUSE);
+ return 1;
+ }
+
+ if (g_game.map.houses.getHouseByPlayerId(tradePartner->getGUID())) {
+ lua_pushnumber(L, RETURNVALUE_TRADEPLAYERALREADYOWNSAHOUSE);
+ return 1;
+ }
+
+ if (IOLoginData::hasBiddedOnHouse(tradePartner->getGUID())) {
+ lua_pushnumber(L, RETURNVALUE_TRADEPLAYERHIGHESTBIDDER);
+ return 1;
+ }
+
+ Item* transferItem = house->getTransferItem();
+ if (!transferItem) {
+ lua_pushnumber(L, RETURNVALUE_YOUCANNOTTRADETHISHOUSE);
+ return 1;
+ }
+
+ transferItem->getParent()->setParent(player);
+ if (!g_game.internalStartTrade(player, tradePartner, transferItem)) {
+ house->resetTransferItem();
+ }
+
+ lua_pushnumber(L, RETURNVALUE_NOERROR);
+ return 1;
+ }
+
+ // HOUSE_TYPE_GUILDHALL
+ Guild* guild = player->getGuild();
+ if (!guild || house->getOwner() != guild->getId()) {
lua_pushnumber(L, RETURNVALUE_YOUDONTOWNTHISHOUSE);
return 1;
}
- if (g_game.map.houses.getHouseByPlayerId(tradePartner->getGUID())) {
- lua_pushnumber(L, RETURNVALUE_TRADEPLAYERALREADYOWNSAHOUSE);
+ Guild* tradeGuild = tradePartner->getGuild();
+ if (!tradeGuild) {
+ lua_pushnumber(L, RETURNVALUE_TRADEPLAYERNOTINAGUILD);
+ return 1;
+ }
+ if (tradeGuild->getHouseId() > 0) {
+ lua_pushnumber(L, RETURNVALUE_TRADEGUILDALREADYOWNSAHOUSE);
+ return 1;
+ }
+ if (player->getGUID() != guild->getOwnerGUID()) {
+ lua_pushnumber(L, RETURNVALUE_YOUARENOTGUILDLEADER);
+ return 1;
+ }
+ if (tradePartner->getGUID() != tradeGuild->getOwnerGUID()) {
+ lua_pushnumber(L, RETURNVALUE_TRADEPLAYERNOTGUILDLEADER);
return 1;
}
- if (IOLoginData::hasBiddedOnHouse(tradePartner->getGUID())) {
+ if (IOLoginData::hasBiddedOnHouse(tradeGuild->getId())) {
lua_pushnumber(L, RETURNVALUE_TRADEPLAYERHIGHESTBIDDER);
return 1;
}
diff --git a/src/luascript.h b/src/luascript.h
index 8332b391de..afd41a636f 100644
--- a/src/luascript.h
+++ b/src/luascript.h
@@ -1049,6 +1049,12 @@ class LuaScriptInterface
static int luaGuildGetMotd(lua_State* L);
static int luaGuildSetMotd(lua_State* L);
+ static int luaGuildGetBankBalance(lua_State* L);
+ static int luaGuildSetBankBalance(lua_State* L);
+
+ static int luaGuildGetOwnerGUID(lua_State* L);
+ static int luaGuildGetHouseId(lua_State* L);
+
// Group
static int luaGroupCreate(lua_State* L);
@@ -1101,12 +1107,14 @@ class LuaScriptInterface
static int luaHouseCreate(lua_State* L);
static int luaHouseGetId(lua_State* L);
+ static int luaHouseGetType(lua_State* L);
static int luaHouseGetName(lua_State* L);
static int luaHouseGetTown(lua_State* L);
static int luaHouseGetExitPosition(lua_State* L);
static int luaHouseGetRent(lua_State* L);
static int luaHouseGetOwnerGuid(lua_State* L);
+ static int luaHouseGetOwnerGuild(lua_State* L);
static int luaHouseSetOwnerGuid(lua_State* L);
static int luaHouseStartTrade(lua_State* L);
diff --git a/src/otserv.cpp b/src/otserv.cpp
index e92c4e9fde..344c196f42 100644
--- a/src/otserv.cpp
+++ b/src/otserv.cpp
@@ -303,6 +303,8 @@ void mainLoader(int, char*[], ServiceManager* services)
rentPeriod = RENTPERIOD_MONTHLY;
} else if (strRentPeriod == "daily") {
rentPeriod = RENTPERIOD_DAILY;
+ } else if (strRentPeriod == "dev") {
+ rentPeriod = RENTPERIOD_DEV;
} else {
rentPeriod = RENTPERIOD_NEVER;
}
diff --git a/src/tools.cpp b/src/tools.cpp
index a95f18862e..d92fe2c8ae 100644
--- a/src/tools.cpp
+++ b/src/tools.cpp
@@ -1213,6 +1213,18 @@ const char* getReturnMessage(ReturnValue value)
case RETURNVALUE_TRADEPLAYERALREADYOWNSAHOUSE:
return "Trade player already owns a house.";
+ case RETURNVALUE_TRADEPLAYERNOTINAGUILD:
+ return "Trade player is not in a guild.";
+
+ case RETURNVALUE_TRADEGUILDALREADYOWNSAHOUSE:
+ return "Trade guild already owns a guildhall.";
+
+ case RETURNVALUE_TRADEPLAYERNOTGUILDLEADER:
+ return "Trade player is not a guild leader.";
+
+ case RETURNVALUE_YOUARENOTGUILDLEADER:
+ return "You are not a guild leader.";
+
case RETURNVALUE_TRADEPLAYERHIGHESTBIDDER:
return "Trade player is currently the highest bidder of an auctioned house.";