• There is NO official Otland's Discord server and NO official Otland's server list. The Otland's Staff does not manage any Discord server or server list. Moderators or administrator of any Discord server or server lists have NO connection to the Otland's Staff. Do not get scammed!

[TFS 1.4.2] Boss Reward Chest or Boss Reward Bag

Mateus Robeerto

Excellent OT User
Joined
Jun 5, 2016
Messages
1,337
Solutions
71
Reaction score
697
Location
ლ(ಠ益ಠლ)
The @Lbtg member of Otland had a good idea about using the command example '/chestevent start, Rotworm'. Only this arena can be activated for 6 hours, chance to drop, and if you want to activate in other locations still... If you want to end the event, just use '/chestevent stop, Rotworm' to end it. Very simple this way!!.. :)

Soon I will modify it to automatically start the event at a specific location and day/week/hour...

Is there a video? Yes, just take a look!

Lua:
local config = {
    duration = 6 * 60 * 60, -- Duration of the event in seconds (6 hours)
    rewardBag = 26144, -- Item ID for the reward bag
    talkactionCommand = "/chestevent", -- Command used to activate the event
    eventMessageStart = "Chest Event activated for 6 hours.", -- Message when the event starts
    eventMessageStop = "Chest Event deactivated.", -- Message when the event stops

    localMessageStart = "Event started at location %s.", -- Message when event starts at a specific location
    localMessageEnd = "Event ended at location %s.", -- Message when event ends at a specific location

    eventBosses = { -- List of event bosses
        {
            location = "Rotworm", -- Location identifier for the first boss
            names = {"Rotworm", "Rotworm Boss", "Rotworm Skeleton"}, -- Names of the boss creatures
            levelRanges = { min = 0, max = 50 }, -- Level range for players to participate in this boss event
            fromPos = Position(716, 601, 8), -- Starting position of the boss event area
            toPos = Position(726, 608, 8), -- Ending position of the boss event area
            dropChances = { -- Drop chances for items from this boss event
                { itemId = 6527, chance = 1.0, count = 3 }, -- Item ID, drop chance (1.0 = 100%), number of items dropped
                { itemId = 2160, chance = 1.0, count = 2 }  -- Another item drop configuration
            },
            storageKey = 1000, -- Storage key used to track this boss event
        },
        {
            location = "demon", -- Location identifier for the second boss
            names = {"Demon"}, -- Name of the demon boss creature
            levelRanges = { min = 51, max = 100 }, -- Level range for players to participate in this boss event
            fromPos = Position(716, 600, 7), -- Starting position of the boss event area
            toPos = Position(726, 608, 7), -- Ending position of the boss event area
            dropChances = { -- Drop chances for items from this boss event
                { itemId = 9876, chance = 0.05, count = 1 }, -- Item ID, drop chance (0.05 = 5%), number of items dropped
                { itemId = 5432, chance = 0.95, count = 2 }  -- Another item drop configuration
            },
            storageKey = 1001, -- Storage key used to track this boss event
        },
        {
            location = "rat", -- Location identifier for the third boss
            names = {"Rat"}, -- Name of the rat boss creature
            levelRanges = { min = 101, max = 150 }, -- Level range for players to participate in this boss event
            fromPos = Position(895, 816, 10), -- Starting position of the boss event area
            toPos = Position(907, 824, 10), -- Ending position of the boss event area
            dropChances = { -- Drop chances for items from this boss event
                { itemId = 1111, chance = 0.5, count = 1 }, -- Item ID, drop chance (0.5 = 50%), number of items dropped
                { itemId = 2222, chance = 0.5, count = 1 }  -- Another item drop configuration
            },
            storageKey = 1002, -- Storage key used to track this boss event
        },
        -- You can add more boss configurations as needed
    },
    activeLocations = {},
}

local function isPositionInRange(position, fromPos, toPos)
    return position.x >= fromPos.x and position.x <= toPos.x
       and position.y >= fromPos.y and position.y <= toPos.y
       and position.z == fromPos.z
end

local function getStorageKey(location)
    for _, boss in ipairs(config.eventBosses) do
        if boss.location == location then
            return boss.storageKey
        end
    end
    return nil
end

local function isPlayerInEvent(player, bossName)
    local playerLevel = player:getLevel()
    local playerPos = player:getPosition()

    for _, boss in ipairs(config.eventBosses) do
        for _, name in ipairs(boss.names) do
            if name:lower() == bossName:lower() then
                local storageKey = getStorageKey(boss.location)
                local eventEndTime = Game.getStorageValue(storageKey) or 0

                if os.time() <= eventEndTime and playerLevel >= boss.levelRanges.min and playerLevel <= boss.levelRanges.max then
                    return isPositionInRange(playerPos, boss.fromPos, boss.toPos)
                end
            end
        end
    end

    return false
end

local creatureevent = CreatureEvent("BossKill")

function creatureevent.onKill(creature, target)
    local targetMonster = target:getMonster()
    if not targetMonster then
        return true
    end

    local player = creature:getPlayer()
    if not player then
        return true
    end

    local monsterName = targetMonster:getName()

    if not isPlayerInEvent(player, monsterName) then
        return true
    end

    for _, boss in ipairs(config.eventBosses) do
        for _, name in ipairs(boss.names) do
            if name:lower() == monsterName:lower() then
                local dropIndex = math.random(1, #boss.dropChances)
                local chosenDrop = boss.dropChances[dropIndex]
                local dropChance = math.random()

                if dropChance <= chosenDrop.chance then
                    local itemCount = chosenDrop.count or 1
                    local backpack = player:addItem(config.rewardBag, false)
                    local isStackable = ItemType(chosenDrop.itemId):isStackable()

                    if isStackable then
                        local fullStacks = math.floor(itemCount / 100)
                        local remainder = itemCount % 100

                        for i = 1, fullStacks do
                            local addedItem = backpack:addItem(chosenDrop.itemId, 100)
                            if not addedItem then
                                break
                            end
                        end

                        if remainder > 0 then
                            local addedRemainder = backpack:addItem(chosenDrop.itemId, remainder)
                            if not addedRemainder then
                            end
                        end
                    else
                        local addedNonStackable = backpack:addItem(chosenDrop.itemId, itemCount)
                        if not addedNonStackable then
                        end
                    end

                    targetMonster:getPosition():sendMagicEffect(CONST_ME_MAGIC_GREEN)
              
                    player:sendTextMessage(MESSAGE_INFO_DESCR, "Reward Bag added to your inventory: " .. ItemType(config.rewardBag):getName() .. ", " .. ItemType(chosenDrop.itemId):getName() .. " (" .. itemCount .. "x)")
                end
                break
            end
        end
    end

    return true
end

creatureevent:register()

local creatureevent2 = CreatureEvent("registerlogin")

function creatureevent2.onLogin(player)
    player:registerEvent("BossKill")
    return true
end

creatureevent2:register()

local talkaction = TalkAction(config.talkactionCommand)

function talkaction.onSay(player, words, param, type)
    if not player:getGroup():getAccess() then
        return true
    end

    if player:getAccountType() < ACCOUNT_TYPE_GOD then
        return false
    end

    local params = param:split(",")

    if words == config.talkactionCommand then
        if params[1] == "start" and params[2] then
            local location = params[2]:trim()

            if config.activeLocations[location] then
                player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "The event at location " .. location .. " is already active.")
            else
                local storageKey = getStorageKey(location)
                if storageKey then
                    config.activeLocations[location] = true
                    Game.setStorageValue(storageKey, os.time() + config.duration)
                    Game.broadcastMessage(config.eventMessageStart, MESSAGE_STATUS_WARNING)
                    Game.broadcastMessage(string.format(config.localMessageStart, location), MESSAGE_EVENT_DEFAULT)
                    player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Event started at location " .. location .. " (Storage Key: " .. storageKey .. ")")
                else
                    player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "The location '" .. location .. "' is not configured as a valid event.")
                end
            end
        elseif params[1] == "stop" and params[2] then
            local location = params[2]:trim()

            if not config.activeLocations[location] then
                player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "The event at location " .. location .. " is not active.")
            else
                local storageKey = getStorageKey(location)
                if storageKey then
                    Game.setStorageValue(storageKey, 0)
                    config.activeLocations[location] = nil
                    Game.broadcastMessage(config.eventMessageStop, MESSAGE_STATUS_WARNING)
                    Game.broadcastMessage(string.format(config.localMessageEnd, location), MESSAGE_EVENT_DEFAULT)
                    player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Event ended at location " .. location .. " (Storage Key: " .. storageKey .. ")")
                else
                    player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "The location '" .. location .. "' is not configured as a valid event.")
                end
            end
        else
            player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Invalid parameter. Use '/chestevent start, LOCATION_NAME' or '/chestevent stop, LOCATION_NAME'.")
        end
    end

    return false
end

talkaction:separator(" ")
talkaction:register()
 
Last edited:
Great release but i would suggest using ip limitation because this event could be easily abused by MC players and will put a lot of people in bad situations because of those farmers, so limiting it only just once or twice per ip would solve it.
 
Great release but i would suggest using ip limitation because this event could be easily abused by MC players and will put a lot of people in bad situations because of those farmers, so limiting it only just once or twice per ip would solve it.
I appreciate the suggestion; it's indeed an excellent idea. I'll work on limiting the functionality, such as within the fromPos and toPos functions or utilizing Game.getSpectators, to ascertain if there's a player with the same IP address within the arena. If such a situation arises, appropriate action will be taken, possibly canceling actions or preventing item drops if a player enters the arena but the MC can't access the area. Additionally, a message will be displayed notifying that the detected IP is being used within the arena. The implementation of this feature should not pose significant difficulty, and I plan to address it later.
 
And i would suggest making new variable in table for monsters like monsters = {... ...} and not using string:split(",") because its hella slow if there is a lot of monsters you should use pairs just for i loop. Its always good to optimize the code because current code is hella slow
 
Alright, I fixed and repaired the script. I set 'allowMCs = 2', limiting it to two MCs IPs that can hunt for drops, okay? If there are 3 MCs, it will display a message in the arena indicating that no drops will occur. Players will need to leave with one MC, keeping two MCs inside the arena for the count to be correct, and then the drops will work as usual. Feel free to increase it if you wish.

Edited: I tried to edit the previous post, but the 'edit' option disappeared, so I posted another one here.

Lua:
local config = {
    duration = 6 * 60 * 60, -- Event duration in seconds (6 hours)
    rewardBag = 26144, -- Item ID for the reward bag
    talkactionCommand = "/chestevent", -- Command used to activate the event
    eventMessageStart = "Chest Event activated for 6 hours.", -- Message when the event starts
    eventMessageStop = "Chest Event deactivated.", -- Message when the event stops
    allowMCs = 2, -- Limit within the arena for dropping items if there are multiple players with the same IP inside; items won't drop in this case

    localMessageStart = "Event started at location %s.", -- Message when event starts at a specific location
    localMessageEnd = "Event ended at location %s.", -- Message when event ends at a specific location
 
    eventBosses = {
        {
            location = "Rotworm", -- Location identifier for the first boss
            names = {"Rotworm", "Rotworm Boss", "Rotworm Skeleton"}, -- Names of the boss creatures
            levelRanges = { min = 0, max = 121 }, -- Level range for players to participate in this boss event
            fromPos = Position(716, 601, 8), -- Starting position of the boss event area
            toPos = Position(726, 608, 8), -- Ending position of the boss event area
            dropChances = { -- Drop chances for items from this boss event
                { itemId = 6527, chance = 1.0, count = 3 }, -- Item ID, drop chance (1.0 = 100%), number of items dropped
                { itemId = 2160, chance = 1.0, count = 2 }  -- Another item drop configuration
            },
            storageKey = 1000, -- Storage key used to track this boss event
        },
        {
            location = "Demon", -- Location identifier for the second boss
            names = {"Demon"}, -- Name of the demon boss creature
            levelRanges = { min = 51, max = 100 }, -- Level range for players to participate in this boss event
            fromPos = Position(716, 600, 7), -- Starting position of the boss event area
            toPos = Position(726, 608, 7), -- Ending position of the boss event area
            dropChances = { -- Drop chances for items from this boss event
                { itemId = 9876, chance = 0.05, count = 1 }, -- Item ID, drop chance (0.05 = 5%), number of items dropped
                { itemId = 5432, chance = 0.95, count = 2 }  -- Another item drop configuration
            },
            storageKey = 1001, -- Storage key used to track this boss event
        },
        {
            location = "Rat", -- Location identifier for the third boss
            names = {"Rat"}, -- Name of the rat boss creature
            levelRanges = { min = 101, max = 150 }, -- Level range for players to participate in this boss event
            fromPos = Position(895, 816, 10), -- Starting position of the boss event area
            toPos = Position(907, 824, 10), -- Ending position of the boss event area
            dropChances = { -- Drop chances for items from this boss event
                { itemId = 1111, chance = 0.5, count = 1 }, -- Item ID, drop chance (0.5 = 50%), number of items dropped
                { itemId = 2222, chance = 0.5, count = 1 }  -- Another item drop configuration
            },
            storageKey = 1002, -- Storage key used to track this boss event
        },
        -- You can add more boss configurations as needed
    },
    activeLocations = {},
}


local monsters = {}
local ipTable = {}

for _, boss in ipairs(config.eventBosses) do
    for _, name in ipairs(boss.names) do
        monsters[name:lower()] = boss
    end
end

local function isPositionInRange(position, fromPos, toPos)
    return position.x >= fromPos.x and position.x <= toPos.x
       and position.y >= fromPos.y and position.y <= toPos.y
       and position.z == fromPos.z
end

local function getStorageKey(location)
    local boss = monsters[location:lower()]
    return boss and boss.storageKey
end

local function isPlayerInEvent(player, bossName)
    local playerLevel = player:getLevel()
    local playerPos = player:getPosition()
    local boss = monsters[bossName:lower()]

    if not playerPos or not boss or os.time() > (Game.getStorageValue(boss.storageKey) or 0)
        or playerLevel < boss.levelRanges.min or playerLevel > boss.levelRanges.max then
        return false
    end

    if not isPositionInRange(playerPos, boss.fromPos, boss.toPos) then
        return false
    end

    local playerIP = player:getIp()
    local sameIPPlayers = 0

    for _, spectator in ipairs(Game.getSpectators(playerPos, false, true, 7, 7)) do
        if spectator:isPlayer() and spectator:getPosition().z == playerPos.z then
            local spectatorIP = spectator:getIp()
            if spectatorIP == playerIP then
                sameIPPlayers = sameIPPlayers + 1
            end
        end
    end
 
    if sameIPPlayers > config.allowMCs then
        player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Please note that drop rewards will not be granted if multiple players with the same IP are in the arena. It's advisable to exit the arena and collect rewards under normal circumstances")
        return false
    end

    return true
end


local creatureevent = CreatureEvent("BossKill")

function creatureevent.onKill(creature, target)
    local targetMonster = target:getMonster()
    if not targetMonster then return true end

    local player = creature:getPlayer()
    if not player then return true end

    local monsterName = targetMonster:getName()
    local boss = monsters[monsterName:lower()]

    if not boss or not isPlayerInEvent(player, monsterName) then return true end

    local dropIndex = math.random(1, #boss.dropChances)
    local chosenDrop = boss.dropChances[dropIndex]
    local dropChance = math.random()

    if dropChance <= chosenDrop.chance then
        local itemCount = chosenDrop.count or 1
        local backpack = player:addItem(config.rewardBag, false)
        local isStackable = ItemType(chosenDrop.itemId):isStackable()

        if isStackable then
            local fullStacks = math.floor(itemCount / 100)
            local remainder = itemCount % 100

            for i = 1, fullStacks do
                backpack:addItem(chosenDrop.itemId, 100)
            end

            if remainder > 0 then
                backpack:addItem(chosenDrop.itemId, remainder)
            end
        else
            backpack:addItem(chosenDrop.itemId, itemCount)
        end

        targetMonster:getPosition():sendMagicEffect(326)
        player:sendTextMessage(MESSAGE_INFO_DESCR, "Reward Bag added to your inventory: " .. ItemType(config.rewardBag):getName() .. ", " .. ItemType(chosenDrop.itemId):getName() .. " (" .. itemCount .. "x)")
    end

    return true
end

creatureevent:register()

local creatureevent2 = CreatureEvent("registerlogin")

function creatureevent2.onLogin(player)
    player:registerEvent("BossKill")
    return true
end

creatureevent2:register()

local talkaction = TalkAction(config.talkactionCommand)

function talkaction.onSay(player, words, param, type)
    if not player:getGroup():getAccess() or player:getAccountType() < ACCOUNT_TYPE_GOD then
        return false
    end
    local params = param:split(",")
    local command = params[1] and params[1]:lower()

    if words == config.talkactionCommand and command then
        if command == "start" and params[2] then
            local location = params[2]:trim()
            local storageKey = getStorageKey(location)

            if not storageKey or config.activeLocations[location] then
                player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, not storageKey and "The location '" .. location .. "' is not configured as a valid event." or "The event at location " .. location .. " is already active.")
                return false
            end

            config.activeLocations[location] = true
            Game.setStorageValue(storageKey, os.time() + config.duration)
            Game.broadcastMessage(config.eventMessageStart, MESSAGE_STATUS_WARNING)
            Game.broadcastMessage(string.format(config.localMessageStart, location), MESSAGE_EVENT_DEFAULT)
            player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Event started at location " .. location .. " (Storage Key: " .. storageKey .. ")")
        elseif command == "stop" and params[2] then
            local location = params[2]:trim()
            local storageKey = getStorageKey(location)

            if not storageKey or not config.activeLocations[location] then
                player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, not storageKey and "The location '" .. location .. "' is not configured as a valid event." or "The event at location " .. location .. " is not active.")
                return false
            end
            Game.setStorageValue(storageKey, 0)
            config.activeLocations[location] = nil
            Game.broadcastMessage(config.eventMessageStop, MESSAGE_STATUS_WARNING)
            Game.broadcastMessage(string.format(config.localMessageEnd, location), MESSAGE_EVENT_DEFAULT)
            player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Event ended at location " .. location .. " (Storage Key: " .. storageKey .. ")")
        else
            player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Invalid parameter. Use '/chestevent start, LOCATION_NAME' or '/chestevent stop, LOCATION_NAME'.")
        end
    end

    return false
end

talkaction:separator(" ")
talkaction:register()
 
Last edited:
Back
Top