--[[
MARK: HunterSystem
Author: @MillhioreBT {Sarah Wesker}
Version: 1.0
Conversion To: - TFS 1.5
by : Abdala Ragab
]] --
---
---@enum HunterStatus
HunterStatus = {
ACTIVE = 0,
FINISHED = 1
}
HunterSystem = {
command = "!hunter",
commission = 10,
refund = 50,
chunkSize = 10,
maxTimeHunted = 60 * 60 * 24 * 7,
loaded = false,
forceSave = false,
cache = {}
}
local fmt = string.format
local function debugPrint(...)
print("[HunterSystem]", ...)
end
local function createTable()
local query = [[
CREATE TABLE IF NOT EXISTS `hunter_system` (
`id` INT NOT NULL AUTO_INCREMENT,
`owner` INT NOT NULL,
`target` INT NOT NULL,
`status` INT NOT NULL,
`reward_gold` INT NOT NULL,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`finished_at` TIMESTAMP NULL,
PRIMARY KEY (`id`),
INDEX (`owner`),
INDEX (`target`)
);
]]
if not db.query(query) then
debugPrint("Error while creating table: hunter_system")
return false
end
return true
end
local globalEvent = GlobalEvent("HunterSystemLoad")
function globalEvent.onStartup()
if not createTable() then
print("[HunterSystem] Error while creating table")
return false
end
if not HunterSystem:loadAllHunters() then
print("[HunterSystem] Warning while loading hunters")
end
return true
end
globalEvent:register()
function HunterSystem:loadHunters(owner)
if self.cache[owner] then return true end
local query = fmt([[
SELECT `owner`, `target`, `status`, `reward_gold`,
UNIX_TIMESTAMP(`created_at`) AS `created_at`,
UNIX_TIMESTAMP(`updated_at`) AS `updated_at`,
UNIX_TIMESTAMP(`finished_at`) AS `finished_at`
FROM `hunter_system` WHERE `owner` = %d;
]], owner)
local store = db.storeQuery(query)
if not store then
self.cache[owner] = {}
return true
end
local hunters = {}
repeat
local hunter = setmetatable({
owner = result.getNumber(store, "owner"),
target = result.getNumber(store, "target"),
status = result.getNumber(store, "status"),
rewardGold = result.getNumber(store, "reward_gold"),
createdAt = result.getNumber(store, "created_at"),
updatedAt = result.getNumber(store, "updated_at"),
finishedAt = result.getNumber(store, "finished_at")
}, Hunter)
hunters[#hunters + 1] = hunter
until not result.next(store)
result.free(store)
self.cache[owner] = hunters
return true
end
function HunterSystem:saveHunters(owner)
local hunters = self.cache[owner]
if not hunters then return true end
local deleteQuery = fmt("DELETE FROM `hunter_system` WHERE `owner` = %d;", owner)
if not db.query(deleteQuery) then
debugPrint("Error while deleting hunters")
return false
end
for _, hunter in ipairs(hunters) do
if not hunter:isExpired() then
local insertQuery = fmt([[
INSERT INTO `hunter_system`
(`owner`, `target`, `status`, `reward_gold`, `created_at`, `updated_at`, `finished_at`)
VALUES (%d, %d, %d, %d, FROM_UNIXTIME(%d), FROM_UNIXTIME(%d), FROM_UNIXTIME(%d));
]], hunter.owner, hunter.target, hunter.status, hunter.rewardGold,
hunter.createdAt, hunter.updatedAt, hunter.finishedAt)
if not db.query(insertQuery) then
debugPrint("Error while inserting hunter")
return false
end
end
end
return true
end
function HunterSystem:addHunter(owner, target, rewardGold)
local hunters = self.cache[owner]
if not hunters then
hunters = {}
self.cache[owner] = hunters
end
for _, hunter in ipairs(hunters) do
if hunter.target == target then
return false
end
end
local timeNow = os.time()
local hunter = setmetatable({
owner = owner,
target = target,
status = HunterStatus.ACTIVE,
rewardGold = rewardGold,
createdAt = timeNow,
updatedAt = timeNow,
finishedAt = timeNow + self.maxTimeHunted
}, Hunter)
hunters[#hunters + 1] = hunter
return true
end
function HunterSystem:removeHunter(hunter)
local hunters = self.cache[hunter.owner]
if not hunters then
return false
end
for i, h in ipairs(hunters) do
if h == hunter then
table.remove(hunters, i)
return true
end
end
return false
end
function HunterSystem:isExists(owner, target)
local hunters = self.cache[owner]
if hunters then
for _, hunter in ipairs(hunters) do
if hunter.status == HunterStatus.ACTIVE and not hunter:isExpired() and hunter.target == target then
return true
end
end
end
return false
end
function HunterSystem:loadAllHunters()
if self.loaded then return true end
local owners = db.storeQuery("SELECT DISTINCT `owner` FROM `hunter_system`")
if not owners then
self.loaded = true
return true
end
repeat
self:loadHunters(result.getNumber(owners, "owner"))
until not result.next(owners)
result.free(owners)
self.loaded = true
return true
end
function HunterSystem:getOwnerByName(name)
local player = Player(name)
if player then return player:getGuid() end
local store = db.storeQuery(fmt("SELECT `id` FROM `players` WHERE `name` = %s;",
db.escapeString(name)))
if not store then return nil end
local id = result.getNumber(store, "id")
result.free(store)
return id
end
function HunterSystem:getNameByOwner(owner)
local player = Player(owner)
if player then return player:getName() end
local store = db.storeQuery(fmt("SELECT `name` FROM `players` WHERE `id` = %d;", owner))
if not store then return nil end
local name = result.getString(store, "name")
result.free(store)
return name
end
function HunterSystem:getHuntersByTarget(target)
local hunters = {}
for _, hs in pairs(self.cache) do
for _, hunter in ipairs(hs) do
if hunter.status == HunterStatus.ACTIVE and not hunter:isExpired() and hunter.target == target then
hunters[#hunters + 1] = hunter
end
end
end
return hunters
end
function HunterSystem:getValidHunters(owner)
local hunters = self.cache[owner]
if not hunters then return {} end
local validHunters = {}
for _, hunter in ipairs(hunters) do
if hunter.status == HunterStatus.ACTIVE and not hunter:isExpired() then
validHunters[#validHunters + 1] = hunter
end
end
return validHunters
end
function HunterSystem:getAllValidHunters()
local hunters = {}
for _, hs in pairs(self.cache) do
for _, hunter in ipairs(hs) do
if hunter.status == HunterStatus.ACTIVE and not hunter:isExpired() then
hunters[#hunters + 1] = hunter
end
end
end
return hunters
end
Hunter = {}
Hunter.__index = Hunter
function Hunter:isExpired()
return os.time() >= self.finishedAt
end
local loginEvent = CreatureEvent("HunterSystemLogin")
function loginEvent.onLogin(player)
player:registerEvent("HunterSystemDeath")
player:registerEvent("HunterSystemLogout")
HunterSystem:loadHunters(player:getGuid())
return true
end
loginEvent:register()
local logoutEvent = CreatureEvent("HunterSystemLogout")
function logoutEvent.onLogout(player)
HunterSystem:saveHunters(player:getGuid())
return true
end
logoutEvent:register()
local function shuffle(t)
local n = #t
while n > 2 do
local k = math.random(n)
t[n], t[k] = t[k], t[n]
n = n - 1
end
return t
end
local function checkCount(count)
if not count then return -1 end
return math.min(2147483647, count)
end
local function getCount(str)
local b, e = str:find("%d+")
if not b or not e then
return -1
end
return checkCount(tonumber(str:sub(b, e)))
end
local function displayHuntersList(player, hunters, title)
if #hunters == 0 then
player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "No hunters found.")
return
end
local message = title .. "\n"
for i, hunter in ipairs(hunters) do
local targetName = HunterSystem:getNameByOwner(hunter.target) or "Unknown"
local ownerName = HunterSystem:getNameByOwner(hunter.owner) or "Unknown"
message = message .. string.format("%d. %s -> %s | Reward: %d gold\n",
i, ownerName, targetName, hunter.rewardGold)
end
player:popupFYI(message)
end
local talkAction = TalkAction(HunterSystem.command)
function talkAction.onSay(player, words, param)
local split = {}
for part in string.gmatch(param, "[^,]+") do
split[#split + 1] = part:trim()
end
local action = split[1] and split[1]:lower()
if action == "add" then
if not split[2] or not split[3] then
player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Usage: !hunter add <player>,<amount>")
return false
end
local target = HunterSystem:getOwnerByName(split[2])
if not target then
player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Player not found.")
return false
end
if target == player:getGuid() then
player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "You can't hunt yourself.")
return false
end
if HunterSystem:isExists(player:getGuid(), target) then
player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "You are already hunting this player.")
return false
end
local rewardGold = getCount(split[3])
if rewardGold <= 0 then
player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Invalid reward gold.")
return false
end
rewardGold = math.floor(rewardGold * (1 + HunterSystem.commission / 100))
if checkCount(rewardGold) <= 0 then
player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Invalid reward gold.")
return false
end
if player:getMoney() < rewardGold then
player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "You don't have enough gold coins.")
return false
end
if not player:removeMoney(rewardGold) then
player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Error while removing gold coins.")
return false
end
if not HunterSystem:addHunter(player:getGuid(), target, rewardGold) then
player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Error while adding hunter.")
return false
end
local targetName = HunterSystem:getNameByOwner(target) or "Unknown"
player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Hunter added successfully!")
Game.broadcastMessage(string.format("%s started hunting %s for %d gold coins.",
player:getName(), targetName, rewardGold), MESSAGE_EVENT_ADVANCE)
HunterSystem:saveHunters(player:getGuid())
return false
end
if action == "remove" then
if not split[2] then
player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Usage: !hunter remove <player>")
return false
end
local target = HunterSystem:getOwnerByName(split[2])
if not target then
player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Player not found.")
return false
end
local hunters = HunterSystem:getValidHunters(player:getGuid())
if not hunters or #hunters == 0 then
player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "You are not hunting this player.")
return false
end
local found = nil
for _, hunter in ipairs(hunters) do
if hunter.target == target then
if not HunterSystem:removeHunter(hunter) then
player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Error while removing hunter.")
return false
end
found = hunter
break
end
end
if found then
local refundGold = math.floor(found.rewardGold * HunterSystem.refund / 100)
if checkCount(refundGold) <= 0 then
player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Error while calculating refund.")
return false
end
player:addMoney(refundGold)
local targetName = HunterSystem:getNameByOwner(target) or "Unknown"
player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE,
string.format("Hunter removed. Refund: %d gold coins.", refundGold))
Game.broadcastMessage(string.format("%s stopped hunting %s.", player:getName(), targetName),
MESSAGE_EVENT_ADVANCE)
HunterSystem:saveHunters(player:getGuid())
return false
end
player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "You are not hunting this player.")
return false
end
if action == "list" then
if not split[2] then
player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Usage: !hunter list <player>")
return false
end
local target = HunterSystem:getOwnerByName(split[2])
if not target then
player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Player not found.")
return false
end
local hunters = HunterSystem:getValidHunters(target)
if not hunters or #hunters == 0 then
player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "This player is not being hunted.")
return false
end
local targetName = HunterSystem:getNameByOwner(target) or "Unknown"
displayHuntersList(player, hunters, string.format("Players hunting %s:", targetName))
return false
end
if action == "all" then
if not HunterSystem:loadAllHunters() then
player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Error while loading hunters.")
return false
end
local hunters = HunterSystem:getAllValidHunters()
if #hunters == 0 then
player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "No active hunters found.")
return false
end
hunters = shuffle(hunters)
displayHuntersList(player, hunters, "All Active Hunters:")
return false
end
if action == "my" then
local hunters = HunterSystem:getValidHunters(player:getGuid())
if #hunters == 0 then
player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "You are not hunting anyone.")
return false
end
displayHuntersList(player, hunters, "Your active hunts:")
return false
end
local helpText = [[
Hunter System Commands:
!hunter add <player>,<amount> - Place a bounty on a player
!hunter remove <player> - Remove bounty from a player
!hunter list <player> - Show who is hunting a player
!hunter all - Show all active bounties
!hunter my - Show your active hunts
Example: !hunter add PlayerName, 1000
]]
player:popupFYI(helpText)
return false
end
talkAction:separator(" ")
talkAction:register()
local deathEvent = CreatureEvent("HunterSystemDeath")
function deathEvent.onDeath(creature)
local player = creature:getPlayer()
if not player then return true end
local timeNow = os.time()
local killers = {}
local totalDamage = 0
local damageMap = player:getDamageMap()
for uid, cb in pairs(damageMap) do
local killer = Player(uid)
if killer and killer ~= player and (timeNow - cb.ticks) <= 60000 then
killers[#killers + 1] = {killer = killer, damage = cb.total}
totalDamage = totalDamage + cb.total
end
end
if #killers == 0 then return true end
local hunters = HunterSystem:getHuntersByTarget(player:getGuid())
if #hunters == 0 then return true end
local maxHealth = player:getMaxHealth()
if maxHealth <= 0 then maxHealth = 1 end
for _, hunter in ipairs(hunters) do
if hunter.status == HunterStatus.ACTIVE and not hunter:isExpired() then
local rewardGold = math.max(1, math.floor(hunter.rewardGold * totalDamage / maxHealth))
for _, cb in ipairs(killers) do
cb.killer:addMoney(rewardGold)
cb.killer:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE,
string.format("You received %d gold coins for hunting %s.", rewardGold, player:getName()))
end
hunter.status = HunterStatus.FINISHED
hunter.updatedAt = timeNow
hunter.finishedAt = timeNow
HunterSystem:saveHunters(hunter.owner)
end
end
return true
end
deathEvent:register()