• 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!

Boss arena

Xarah

Member
Joined
Apr 18, 2018
Messages
39
Reaction score
7
Hello, I have a problem with a boss. The script spawns a creature when someone enters the arena area. After you kill the boss, a tile should unlock so you can walk through it. The issue is that, once the boss is killed, the storage doesn’t register that it’s been defeated—it’s really strange. Anyone have an idea how to fix it?

LUA:
-- data/scripts/arena_boss_system.lua
print("[ArenaSystem] arena_boss_system.lua loaded")

local config = {
    bossName = "Demon",          -- Nazwa bossa (identyczna jak w pliku .xml potwora)
    bossSpawnPos = Position(32911, 32408, 9), -- Pozycja spawnu bossa
    arenaFromPos = Position(32895, 32392, 9), -- Lewy górny róg areny
    arenaToPos   = Position(32932, 32431, 9), -- Prawy dolny róg areny
    globalStorage = 40001,                    -- Global Storage dla stanu bossa (1 = aktywny, 0 = brak bossa)
    checkInterval = 5,                        -- Co ile sekund global event sprawdza arenę
    minDamagePercent = 3,                     -- Minimalny procent obrażeń, by otrzymać storage
    playerDamageStorage = 41000               -- Storage u gracza, jeśli zadał wystarczająco dmg
}

-- Sprawdza, czy w obszarze są jacyś gracze
local function arePlayersInArea(fromPos, toPos)
    print("[ArenaSystem] Checking if players are in area...")
    local spectators = Game.getSpectators(
        fromPos, -- pozycja startowa
        false,   -- checkMultiFloor
        true,    -- checkPlayers
        toPos.x - fromPos.x + 1,
        toPos.x - fromPos.x + 1,
        toPos.y - fromPos.y + 1,
        toPos.y - fromPos.y + 1
    )
    for _, spec in ipairs(spectators) do
        if spec:isPlayer() then
            print("[ArenaSystem] Found player in area:", spec:getName())
            return true
        end
    end
    print("[ArenaSystem] No players in area.")
    return false
end

-- Sprawdza, czy boss jest żywy
local function isBossAlive(bossName)
    local creature = Creature(bossName)
    local alive = (creature ~= nil)
    print("[ArenaSystem] isBossAlive?", alive)
    return alive
end

-- Usuwa bossa, jeśli istnieje
local function removeBoss(bossName)
    print("[ArenaSystem] Trying to remove boss:", bossName)
    local creature = Creature(bossName)
    if creature then
        creature:remove()
        print("[ArenaSystem] Boss removed.")
    else
        print("[ArenaSystem] Boss not found.")
    end
    -- Po usunięciu bossa na wszelki wypadek ustawiamy globalStorage na 0
    Game.setStorageValue(config.globalStorage, 0)
end

-- Tworzy bossa
local function spawnBoss()
    print("[ArenaSystem] Attempting to spawn boss:", config.bossName, "at", config.bossSpawnPos)
    local boss = Game.createMonster(config.bossName, config.bossSpawnPos, false, true)
    if boss then
        Game.setStorageValue(config.globalStorage, 1)
        print("[ArenaSystem] Boss spawned successfully!")
    else
        print("[ArenaSystem] Failed to spawn boss. Check monster name or spawn position.")
    end
end

-- Funkcja wywoływana przy StepIn na arenę
function onArenaStepIn(player)
    print("[ArenaSystem] onArenaStepIn called by:", player:getName())
    local storageValue = Game.getStorageValue(config.globalStorage)
    print("[ArenaSystem] Current globalStorage value:", storageValue)
    -- Jeśli boss nie jest aktywny (≠ 1), to go zrespawnuj
    if storageValue ~= 1 then
        print("[ArenaSystem] Boss is not active, spawning boss.")
        spawnBoss()
    else
        print("[ArenaSystem] Boss is already active.")
    end
    return true
end

-- Funkcja wywoływana przy śmierci bossa
function onBossDeath(creature)
    print("[ArenaSystem] onBossDeath triggered for:", creature:getName())
    -- Najważniejsza poprawka: resetujemy globalStorage do 0
    Game.setStorageValue(config.globalStorage, 0)

    local damageMap = creature:getDamageMap()
    if not damageMap then
        print("[ArenaSystem] No damage map found. Possibly TFS version issue or boss not attacked by players.")
        return
    end

    -- Zliczamy łączną liczbę obrażeń
    local totalDamage = 0
    for _, damage in pairs(damageMap) do
        totalDamage = totalDamage + damage
    end
    print("[ArenaSystem] totalDamage =", totalDamage)

    local minDamage = (config.minDamagePercent / 100) * totalDamage
    print("[ArenaSystem] minDamage needed =", minDamage)

    for attackerId, damage in pairs(damageMap) do
        local attacker = Player(attackerId)
        if attacker then
            print(string.format("[ArenaSystem] Attacker: %s, Damage: %d", attacker:getName(), damage))
            if damage >= minDamage then
                attacker:setStorageValue(config.playerDamageStorage, 1)
                print("[ArenaSystem] ->", attacker:getName(), "dealt enough damage. Storage", config.playerDamageStorage, "set to 1.")
            else
                print("[ArenaSystem] ->", attacker:getName(), "did NOT deal enough damage.")
            end
        end
    end
end

-- Funkcja wywoływana przez global event co config.checkInterval sek.
function onCheckArena()
    print("[ArenaSystem] onCheckArena triggered.")
    if isBossAlive(config.bossName) then
        if not arePlayersInArea(config.arenaFromPos, config.arenaToPos) then
            print("[ArenaSystem] Boss is alive but no players on arena, removing boss.")
            removeBoss(config.bossName)
        end
    else
        print("[ArenaSystem] Boss is not alive or not found, nothing to remove.")
    end
    return true
end

LUA:
function onStepIn(creature, item, position, fromPosition)
    if creature:isPlayer() then
        onArenaStepIn(creature)
    end
    return true
end


LUA:
function onDeath(creature, corpse, killer, mostDamageKiller, unjustified)
    -- Sprawdzamy nazwę, czy to nasz boss
    if creature:getName():lower() == "demon" then
        onBossDeath(creature)
    end
    return true
end


LUA:
local requiredStorage = 41000 -- musi być taki sam, jak w arena_boss_system.lua

function onStepIn(creature, item, position, fromPosition)
    if creature:isPlayer() then
        local dmgStorage = creature:getStorageValue(requiredStorage)
        if dmgStorage ~= 1 then
            creature:sendTextMessage(MESSAGE_STATUS_SMALL, "You have not dealt enough damage to the boss to pass.")
            creature:teleportTo(fromPosition)
            return false
        else
            -- Opcjonalnie reset storage po przejściu:
            -- creature:setStorageValue(requiredStorage, -1)
        end
    end
    return true
end

LUA:
function onThink(interval, lastExecution)
    return onCheckArena()
end

1734815792412.webp
 
Solution
So, it seems like you want a script where killing the boss sets a storage value for the player, allowing them to pass through the door with storage 3355, right? If they deal at least 5% of the damage — for example, if the Demon has 8200 health, 5% would be 410 damage — the storage will be set for the player to unlock and pass through the door. Correct?

But you didn't mention the version of TFS, for example, TFS 1.4.2 protocol 1098. So, I carefully made the script from scratch.


To optimize performance, I used indexed tables ({}) to track players in the arena and the boss presence, avoiding the use of Game.getSpectators to reduce CPU consumption from scanning the game map, simplifying checks for player positions and boss status...
demon needs to have the onDeath registered after it's successfully spawned.
LUA:
-- Tworzy bossa
local function spawnBoss()
    print("[ArenaSystem] Attempting to spawn boss:", config.bossName, "at", config.bossSpawnPos)
    local boss = Game.createMonster(config.bossName, config.bossSpawnPos, false, true)
    if boss then
        boss:registerEvent("WHATEVER_YOU_NAMED_THE_ONDEATH_EVENT_IN_CREATURESCRIPTS.XML") -- here
        Game.setStorageValue(config.globalStorage, 1)
        print("[ArenaSystem] Boss spawned successfully!")
    else
        print("[ArenaSystem] Failed to spawn boss. Check monster name or spawn position.")
    end
end

-- edit
Probably just ignore this post.
I strongly dislike how this script was made.
There's too many issues to solve. It'd almost be easier to rescript this from scratch.
 
Last edited by a moderator:
I slightly modified the script, and now I have a different problem. The script is supposed to work so that if someone is in the arena, it won't create a new boss, but the opposite happens—bosses are created every time a new player enters.

LUA:
-- Konfiguracja
local triggerTileUid = 3334 -- UID kratki aktywującej
local gateTileUid = 3335 -- UID kratki przejścia
local bossName = "Boss of the plague" -- Nazwa bossa
local bossSpawnPosition = Position(32911, 32408, 9) -- Pozycja spawnu bossa
local bossArea = {from = Position(32895, 32392, 9), to = Position(32932, 32431, 9)} -- Obszar bossa
local requiredDamagePercent = 5 -- Minimalny procent obrażeń, by zaliczyć zabicie bossa

-- Funkcja sprawdzająca obecność gracza na obszarze bossa
local function isPlayerInBossArea()
    print("Debug: Sprawdzanie obecności gracza na arenie...")
    local spectators = Game.getSpectators(bossArea.from, false, true, bossArea.to.x - bossArea.from.x, bossArea.to.y - bossArea.from.y, 0)
    for _, spectator in ipairs(spectators) do
        if spectator:isPlayer() then
            local pos = spectator:getPosition()
            if pos:isInRange(bossArea.from, bossArea.to) then
                print(string.format("Debug: Gracz [%s] znajduje się na pozycji [%d, %d, %d] w obszarze bossa.", spectator:getName(), pos.x, pos.y, pos.z))
                return true
            else
                print(string.format("Debug: Gracz [%s] jest poza zakresem na pozycji [%d, %d, %d].", spectator:getName(), pos.x, pos.y, pos.z))
            end
        end
    end
    print("Debug: Żaden gracz nie znajduje się w obszarze bossa.")
    return false
end

-- Funkcja sprawdzająca, czy boss istnieje na arenie
local function isBossInArena()
    local spectators = Game.getSpectators(bossArea.from, false, false, bossArea.to.x - bossArea.from.x, bossArea.to.y - bossArea.from.y, 0)
    print("Debug: Sprawdzanie obecności bossa na arenie...")
    for _, spectator in ipairs(spectators) do
        if spectator:isMonster() then
            print(string.format("Debug: Wykryto potwora [%s] na pozycji [%d, %d, %d].", spectator:getName(), spectator:getPosition().x, spectator:getPosition().y, spectator:getPosition().z))
            if spectator:getName():lower() == bossName:lower() then
                print("Debug: Boss już istnieje na arenie.")
                return true
            end
        end
    end
    print("Debug: Boss nie istnieje na arenie.")
    return false
end

-- Funkcja aktywowana przy wejściu na kratkę triggerTileUid (tworzenie bossa)
function onStepIn(creature, item, position, fromPosition)
    if not creature:isPlayer() then
        return true
    end

    if item.uid == triggerTileUid then
        -- Sprawdzenie, czy na arenie znajduje się gracz
        if isPlayerInBossArea() then
            creature:sendTextMessage(MESSAGE_INFO_DESCR, "Ktoś już znajduje się na arenie. Poczekaj, aż arena będzie wolna.")
            return true
        end

        -- Sprawdzenie, czy boss już istnieje na arenie
        if isBossInArena() then
            creature:sendTextMessage(MESSAGE_INFO_DESCR, "Boss już znajduje się na arenie.")
            return true
        end

        -- Tworzenie bossa, jeśli arena jest pusta i nie ma bossa
        local boss = Game.createMonster(bossName, bossSpawnPosition)
        if boss then
            boss:registerEvent("BossDeath")
            creature:sendTextMessage(MESSAGE_INFO_DESCR, "Boss został stworzony. Powodzenia w walce!")
            print("Debug: Boss został stworzony na arenie.")
        else
            print("Debug: Nie udało się stworzyć bossa.")
        end
        return true
    end

    if item.uid == gateTileUid then
        -- Sprawdzenie, czy gracz może przejść przez bramkę
        local canPass = creature:getStorageValue(3335)
        if canPass and canPass == 1 then
            creature:sendTextMessage(MESSAGE_INFO_DESCR, "Możesz przejść przez bramkę.")
        else
            creature:sendTextMessage(MESSAGE_INFO_DESCR, "Nie możesz jeszcze przejść przez bramkę. Zabij bossa!")
            creature:teleportTo(fromPosition)
            fromPosition:sendMagicEffect(CONST_ME_MAGIC_BLUE)
            return false
        end
        return true
    end

    return true
end


LUA:
local requiredDamagePercent = 5 -- Minimalny procent obrażeń

local function handleBossKill(player, boss)
    local totalDamage = boss:getMaxHealth()
    local damageMap = boss:getDamageMap()
    local playerDamage = 0

    if damageMap[player:getId()] then
        playerDamage = damageMap[player:getId()].total or 0 -- Pobranie całkowitej liczby obrażeń zadanych przez gracza
    end

    if (playerDamage / totalDamage) * 100 >= requiredDamagePercent then
        player:setStorageValue(3335, 1) -- Oznaczenie, że gracz może przejść przez kratkę
        player:sendTextMessage(MESSAGE_INFO_DESCR, "Zabiłeś bossa i możesz przejść przez bramkę.")
    else
        player:sendTextMessage(MESSAGE_INFO_DESCR, "Nie zadałeś wystarczająco obrażeń, aby zaliczyć zabicie bossa.")
    end
end

function onDeath(creature, corpse, killer, mostDamageKiller, unjustified, mostDamageUnjustified)
    local damageMap = creature:getDamageMap()
    for playerId, _ in pairs(damageMap) do
        local player = Player(playerId)
        if player then
            handleBossKill(player, creature)
        end
    end
    return true
end
Post automatically merged:
 
Last edited:
So, it seems like you want a script where killing the boss sets a storage value for the player, allowing them to pass through the door with storage 3355, right? If they deal at least 5% of the damage — for example, if the Demon has 8200 health, 5% would be 410 damage — the storage will be set for the player to unlock and pass through the door. Correct?

But you didn't mention the version of TFS, for example, TFS 1.4.2 protocol 1098. So, I carefully made the script from scratch.


To optimize performance, I used indexed tables ({}) to track players in the arena and the boss presence, avoiding the use of Game.getSpectators to reduce CPU consumption from scanning the game map, simplifying checks for player positions and boss status efficiently.

This script is located in data/scripts/file.name and uses the Revscriptsys system, which allows for efficient script management without requiring XML configuration.


LUA:
-- Configuration
local config = {
    debug = true, -- Set to true to enable debugging, false to disable
    bossName = "Demon",
    bossSpawnPos = Position(32911, 32408, 9),
    arenaFromPos = Position(32895, 32392, 9),
    arenaToPos = Position(32932, 32431, 9),
    requiredDamagePercent = 5, -- Minimum percentage of damage required
    allowedDistance = 10, -- Maximum distance allowed
    storages = {
        damageEligibility = 3355 -- Storage for damage eligibility
    },
    messages = {
        bossSpawn = "The boss %s has appeared!",
        bossDeath = "Congratulations! You dealt the required damage to pass through the door!",
        needKill = "You need to defeat the boss with sufficient damage to pass!",
        contribute = "You contributed a total of %d damage to defeat the boss!",
        insufficientDamage = "You did not deal enough damage to pass through the door! Required: %d damage.",
        topDamager = "You were the top contributor with %d damage!",
        bossRemoved = "The boss %s was removed due to the absence of players in the arena!",
        bossExists = "The boss is already in the arena! You need to defeat it before summoning another one!"
    }
}

-- Utility function for debugging
local function debugLog(message, ...)
    if config.debug then
        print("[DEBUG]", string.format(message, ...))
    end
end

local playersInArena = {}
local damageTracker = {}
local bossInArena = nil

local function addPlayerToArena(player)
    local playerId = player:getId()
    if not playersInArena[playerId] then
        playersInArena[playerId] = true
        debugLog("Player added to arena: %s", player:getName())
    end
end

local function removePlayerFromArena(player)
    local playerId = player:getId()
    if playersInArena[playerId] then
        if not player:getPosition():isInRange(config.arenaFromPos, config.arenaToPos) then
            playersInArena[playerId] = nil
            debugLog("Player removed from arena: %s", player:getName())
        end
    end
end

local function spawnBoss(player)
    if bossInArena then
        player:sendTextMessage(MESSAGE_INFO_DESCR, config.messages.bossExists)
        return false
    end

    local boss = Game.createMonster(config.bossName, config.bossSpawnPos)
    if boss then
        bossInArena = boss:getId()
        boss:registerEvent("Boss_Arena")
        for playerId, _ in pairs(playersInArena) do
            local currentPlayer = Player(playerId)
            if currentPlayer then
                currentPlayer:sendTextMessage(MESSAGE_INFO_DESCR, string.format(config.messages.bossSpawn, config.bossName))
            end
        end
    end
    debugLog("Boss spawned: %s", config.bossName)
    return true
end

local function calculateRequiredDamage(totalDamage)
    if totalDamage <= 0 then
        return 0
    end
    return math.ceil(totalDamage * (config.requiredDamagePercent / 100))
end

local Boss_Arena_Event = CreatureEvent("Boss_Arena")
function Boss_Arena_Event.onDeath(creature, lastHitKiller, mostDamageKiller)
    if creature:getName():lower() == config.bossName:lower() then
        local topDamage = 0
        local topDamagerId = nil
        local totalDamage = 0

        for attackerId, damageData in pairs(creature:getDamageMap()) do
            local totalPlayerDamage = damageData.total or 0
            damageTracker[attackerId] = (damageTracker[attackerId] or 0) + totalPlayerDamage
            totalDamage = totalDamage + totalPlayerDamage
            if damageTracker[attackerId] > topDamage then
                topDamage = damageTracker[attackerId]
                topDamagerId = attackerId
            end
        end

        debugLog("Total Damage: %d", totalDamage)
        debugLog("Required Damage: %d", calculateRequiredDamage(totalDamage))

        local requiredDamage = calculateRequiredDamage(totalDamage)

        for attackerId, totalPlayerDamage in pairs(damageTracker) do
            local player = Player(attackerId)
            if player then
                debugLog("Player: %s, Damage: %d", player:getName(), totalPlayerDamage)
                player:sendTextMessage(MESSAGE_INFO_DESCR, string.format(config.messages.contribute, totalPlayerDamage))

                if totalPlayerDamage >= requiredDamage then
                    player:setStorageValue(config.storages.damageEligibility, 1)
                    player:sendTextMessage(MESSAGE_INFO_DESCR, config.messages.bossDeath)
                else
                    player:sendTextMessage(MESSAGE_INFO_DESCR, string.format(config.messages.insufficientDamage, requiredDamage))
                end
            end
        end

        if topDamagerId then
            local topPlayer = Player(topDamagerId)
            if topPlayer then
                topPlayer:sendTextMessage(MESSAGE_INFO_DESCR, string.format(config.messages.topDamager, topDamage))
            end
        end

        bossInArena = nil
        damageTracker = {}
    end
    return true
end
Boss_Arena_Event:register()

local CheckArena = GlobalEvent("CheckArena")
function CheckArena.onThink(interval)
    if bossInArena then
        local boss = Creature(bossInArena)
        if boss then
            local hasPlayers = false
            for playerId, _ in pairs(playersInArena) do
                local player = Player(playerId)
                if player and player:getPosition():isInRange(config.arenaFromPos, config.arenaToPos) then
                    hasPlayers = true
                    break
                end
            end

            if not hasPlayers then
                boss:remove()
                bossInArena = nil
                playersInArena = {}
                debugLog("Boss %s removed - No players in arena", config.bossName)
            else
                debugLog("Players are still in the arena.")
            end
        end
    end
    return true
end
CheckArena:interval(10000)
CheckArena:register()

local Entrance = MoveEvent()
function Entrance.onStepIn(creature, item, position, fromPosition)
    local player = creature:getPlayer()
    if not player then return true end

    addPlayerToArena(player)
    spawnBoss(player)
    return true
end
Entrance:type("stepin")
Entrance:aid(4100)
Entrance:register()

local Exit = MoveEvent()
function Exit.onStepOut(creature, item, position, fromPosition)
    local player = creature:getPlayer()
    if not player then return true end

    if not position:isInRange(config.arenaFromPos, config.arenaToPos) then
        removePlayerFromArena(player)
    end
    return true
end
Exit:type("stepout")
Exit:aid(4100)
Exit:register()

local DoorCheck = MoveEvent()
function DoorCheck.onStepIn(creature, item, position, fromPosition)
    local player = creature:getPlayer()
    if not player then return true end

    if player:getStorageValue(config.storages.damageEligibility) ~= 1 then
        player:sendTextMessage(MESSAGE_INFO_DESCR, config.messages.needKill)
        player:teleportTo(fromPosition)
        player:getPosition():sendMagicEffect(CONST_ME_TELEPORT)
        return false
    end

    return true
end
DoorCheck:type("stepin")
DoorCheck:aid(4101)
DoorCheck:register()

I would say that adding it as an action for the door to check storage for passage is not the best approach for this case.

Doors are quite a complex system, and I don't think a simple action script would cover all scenarios.

I would suggest using an actionId on the tile below the door. By using onStepIn, you can prevent a player from passing if they haven't dealt enough damage, forcing them to return and kill the boss to complete the required damage and unlock the storage to pass through the door (e.g., actionId 4101 on the tile below the door).
boss.gif
 
Solution
So, it seems like you want a script where killing the boss sets a storage value for the player, allowing them to pass through the door with storage 3355, right? If they deal at least 5% of the damage — for example, if the Demon has 8200 health, 5% would be 410 damage — the storage will be set for the player to unlock and pass through the door. Correct?

But you didn't mention the version of TFS, for example, TFS 1.4.2 protocol 1098. So, I carefully made the script from scratch.


To optimize performance, I used indexed tables ({}) to track players in the arena and the boss presence, avoiding the use of Game.getSpectators to reduce CPU consumption from scanning the game map, simplifying checks for player positions and boss status efficiently.

This script is located in data/scripts/file.name and uses the Revscriptsys system, which allows for efficient script management without requiring XML configuration.


LUA:
-- Configuration
local config = {
    debug = true, -- Set to true to enable debugging, false to disable
    bossName = "Demon",
    bossSpawnPos = Position(32911, 32408, 9),
    arenaFromPos = Position(32895, 32392, 9),
    arenaToPos = Position(32932, 32431, 9),
    requiredDamagePercent = 5, -- Minimum percentage of damage required
    allowedDistance = 10, -- Maximum distance allowed
    storages = {
        damageEligibility = 3355 -- Storage for damage eligibility
    },
    messages = {
        bossSpawn = "The boss %s has appeared!",
        bossDeath = "Congratulations! You dealt the required damage to pass through the door!",
        needKill = "You need to defeat the boss with sufficient damage to pass!",
        contribute = "You contributed a total of %d damage to defeat the boss!",
        insufficientDamage = "You did not deal enough damage to pass through the door! Required: %d damage.",
        topDamager = "You were the top contributor with %d damage!",
        bossRemoved = "The boss %s was removed due to the absence of players in the arena!",
        bossExists = "The boss is already in the arena! You need to defeat it before summoning another one!"
    }
}

-- Utility function for debugging
local function debugLog(message, ...)
    if config.debug then
        print("[DEBUG]", string.format(message, ...))
    end
end

local playersInArena = {}
local damageTracker = {}
local bossInArena = nil

local function addPlayerToArena(player)
    local playerId = player:getId()
    if not playersInArena[playerId] then
        playersInArena[playerId] = true
        debugLog("Player added to arena: %s", player:getName())
    end
end

local function removePlayerFromArena(player)
    local playerId = player:getId()
    if playersInArena[playerId] then
        if not player:getPosition():isInRange(config.arenaFromPos, config.arenaToPos) then
            playersInArena[playerId] = nil
            debugLog("Player removed from arena: %s", player:getName())
        end
    end
end

local function spawnBoss(player)
    if bossInArena then
        player:sendTextMessage(MESSAGE_INFO_DESCR, config.messages.bossExists)
        return false
    end

    local boss = Game.createMonster(config.bossName, config.bossSpawnPos)
    if boss then
        bossInArena = boss:getId()
        boss:registerEvent("Boss_Arena")
        for playerId, _ in pairs(playersInArena) do
            local currentPlayer = Player(playerId)
            if currentPlayer then
                currentPlayer:sendTextMessage(MESSAGE_INFO_DESCR, string.format(config.messages.bossSpawn, config.bossName))
            end
        end
    end
    debugLog("Boss spawned: %s", config.bossName)
    return true
end

local function calculateRequiredDamage(totalDamage)
    if totalDamage <= 0 then
        return 0
    end
    return math.ceil(totalDamage * (config.requiredDamagePercent / 100))
end

local Boss_Arena_Event = CreatureEvent("Boss_Arena")
function Boss_Arena_Event.onDeath(creature, lastHitKiller, mostDamageKiller)
    if creature:getName():lower() == config.bossName:lower() then
        local topDamage = 0
        local topDamagerId = nil
        local totalDamage = 0

        for attackerId, damageData in pairs(creature:getDamageMap()) do
            local totalPlayerDamage = damageData.total or 0
            damageTracker[attackerId] = (damageTracker[attackerId] or 0) + totalPlayerDamage
            totalDamage = totalDamage + totalPlayerDamage
            if damageTracker[attackerId] > topDamage then
                topDamage = damageTracker[attackerId]
                topDamagerId = attackerId
            end
        end

        debugLog("Total Damage: %d", totalDamage)
        debugLog("Required Damage: %d", calculateRequiredDamage(totalDamage))

        local requiredDamage = calculateRequiredDamage(totalDamage)

        for attackerId, totalPlayerDamage in pairs(damageTracker) do
            local player = Player(attackerId)
            if player then
                debugLog("Player: %s, Damage: %d", player:getName(), totalPlayerDamage)
                player:sendTextMessage(MESSAGE_INFO_DESCR, string.format(config.messages.contribute, totalPlayerDamage))

                if totalPlayerDamage >= requiredDamage then
                    player:setStorageValue(config.storages.damageEligibility, 1)
                    player:sendTextMessage(MESSAGE_INFO_DESCR, config.messages.bossDeath)
                else
                    player:sendTextMessage(MESSAGE_INFO_DESCR, string.format(config.messages.insufficientDamage, requiredDamage))
                end
            end
        end

        if topDamagerId then
            local topPlayer = Player(topDamagerId)
            if topPlayer then
                topPlayer:sendTextMessage(MESSAGE_INFO_DESCR, string.format(config.messages.topDamager, topDamage))
            end
        end

        bossInArena = nil
        damageTracker = {}
    end
    return true
end
Boss_Arena_Event:register()

local CheckArena = GlobalEvent("CheckArena")
function CheckArena.onThink(interval)
    if bossInArena then
        local boss = Creature(bossInArena)
        if boss then
            local hasPlayers = false
            for playerId, _ in pairs(playersInArena) do
                local player = Player(playerId)
                if player and player:getPosition():isInRange(config.arenaFromPos, config.arenaToPos) then
                    hasPlayers = true
                    break
                end
            end

            if not hasPlayers then
                boss:remove()
                bossInArena = nil
                playersInArena = {}
                debugLog("Boss %s removed - No players in arena", config.bossName)
            else
                debugLog("Players are still in the arena.")
            end
        end
    end
    return true
end
CheckArena:interval(10000)
CheckArena:register()

local Entrance = MoveEvent()
function Entrance.onStepIn(creature, item, position, fromPosition)
    local player = creature:getPlayer()
    if not player then return true end

    addPlayerToArena(player)
    spawnBoss(player)
    return true
end
Entrance:type("stepin")
Entrance:aid(4100)
Entrance:register()

local Exit = MoveEvent()
function Exit.onStepOut(creature, item, position, fromPosition)
    local player = creature:getPlayer()
    if not player then return true end

    if not position:isInRange(config.arenaFromPos, config.arenaToPos) then
        removePlayerFromArena(player)
    end
    return true
end
Exit:type("stepout")
Exit:aid(4100)
Exit:register()

local DoorCheck = MoveEvent()
function DoorCheck.onStepIn(creature, item, position, fromPosition)
    local player = creature:getPlayer()
    if not player then return true end

    if player:getStorageValue(config.storages.damageEligibility) ~= 1 then
        player:sendTextMessage(MESSAGE_INFO_DESCR, config.messages.needKill)
        player:teleportTo(fromPosition)
        player:getPosition():sendMagicEffect(CONST_ME_TELEPORT)
        return false
    end

    return true
end
DoorCheck:type("stepin")
DoorCheck:aid(4101)
DoorCheck:register()


View attachment 89057
briliant changes with remove scan entire "event map" and place creatures id to table's, good job!
 
So, it seems like you want a script where killing the boss sets a storage value for the player, allowing them to pass through the door with storage 3355, right? If they deal at least 5% of the damage — for example, if the Demon has 8200 health, 5% would be 410 damage — the storage will be set for the player to unlock and pass through the door. Correct?

But you didn't mention the version of TFS, for example, TFS 1.4.2 protocol 1098. So, I carefully made the script from scratch.


To optimize performance, I used indexed tables ({}) to track players in the arena and the boss presence, avoiding the use of Game.getSpectators to reduce CPU consumption from scanning the game map, simplifying checks for player positions and boss status efficiently.

This script is located in data/scripts/file.name and uses the Revscriptsys system, which allows for efficient script management without requiring XML configuration.


LUA:
-- Configuration
local config = {
    debug = true, -- Set to true to enable debugging, false to disable
    bossName = "Demon",
    bossSpawnPos = Position(32911, 32408, 9),
    arenaFromPos = Position(32895, 32392, 9),
    arenaToPos = Position(32932, 32431, 9),
    requiredDamagePercent = 5, -- Minimum percentage of damage required
    allowedDistance = 10, -- Maximum distance allowed
    storages = {
        damageEligibility = 3355 -- Storage for damage eligibility
    },
    messages = {
        bossSpawn = "The boss %s has appeared!",
        bossDeath = "Congratulations! You dealt the required damage to pass through the door!",
        needKill = "You need to defeat the boss with sufficient damage to pass!",
        contribute = "You contributed a total of %d damage to defeat the boss!",
        insufficientDamage = "You did not deal enough damage to pass through the door! Required: %d damage.",
        topDamager = "You were the top contributor with %d damage!",
        bossRemoved = "The boss %s was removed due to the absence of players in the arena!",
        bossExists = "The boss is already in the arena! You need to defeat it before summoning another one!"
    }
}

-- Utility function for debugging
local function debugLog(message, ...)
    if config.debug then
        print("[DEBUG]", string.format(message, ...))
    end
end

local playersInArena = {}
local damageTracker = {}
local bossInArena = nil

local function addPlayerToArena(player)
    local playerId = player:getId()
    if not playersInArena[playerId] then
        playersInArena[playerId] = true
        debugLog("Player added to arena: %s", player:getName())
    end
end

local function removePlayerFromArena(player)
    local playerId = player:getId()
    if playersInArena[playerId] then
        if not player:getPosition():isInRange(config.arenaFromPos, config.arenaToPos) then
            playersInArena[playerId] = nil
            debugLog("Player removed from arena: %s", player:getName())
        end
    end
end

local function spawnBoss(player)
    if bossInArena then
        player:sendTextMessage(MESSAGE_INFO_DESCR, config.messages.bossExists)
        return false
    end

    local boss = Game.createMonster(config.bossName, config.bossSpawnPos)
    if boss then
        bossInArena = boss:getId()
        boss:registerEvent("Boss_Arena")
        for playerId, _ in pairs(playersInArena) do
            local currentPlayer = Player(playerId)
            if currentPlayer then
                currentPlayer:sendTextMessage(MESSAGE_INFO_DESCR, string.format(config.messages.bossSpawn, config.bossName))
            end
        end
    end
    debugLog("Boss spawned: %s", config.bossName)
    return true
end

local function calculateRequiredDamage(totalDamage)
    if totalDamage <= 0 then
        return 0
    end
    return math.ceil(totalDamage * (config.requiredDamagePercent / 100))
end

local Boss_Arena_Event = CreatureEvent("Boss_Arena")
function Boss_Arena_Event.onDeath(creature, lastHitKiller, mostDamageKiller)
    if creature:getName():lower() == config.bossName:lower() then
        local topDamage = 0
        local topDamagerId = nil
        local totalDamage = 0

        for attackerId, damageData in pairs(creature:getDamageMap()) do
            local totalPlayerDamage = damageData.total or 0
            damageTracker[attackerId] = (damageTracker[attackerId] or 0) + totalPlayerDamage
            totalDamage = totalDamage + totalPlayerDamage
            if damageTracker[attackerId] > topDamage then
                topDamage = damageTracker[attackerId]
                topDamagerId = attackerId
            end
        end

        debugLog("Total Damage: %d", totalDamage)
        debugLog("Required Damage: %d", calculateRequiredDamage(totalDamage))

        local requiredDamage = calculateRequiredDamage(totalDamage)

        for attackerId, totalPlayerDamage in pairs(damageTracker) do
            local player = Player(attackerId)
            if player then
                debugLog("Player: %s, Damage: %d", player:getName(), totalPlayerDamage)
                player:sendTextMessage(MESSAGE_INFO_DESCR, string.format(config.messages.contribute, totalPlayerDamage))

                if totalPlayerDamage >= requiredDamage then
                    player:setStorageValue(config.storages.damageEligibility, 1)
                    player:sendTextMessage(MESSAGE_INFO_DESCR, config.messages.bossDeath)
                else
                    player:sendTextMessage(MESSAGE_INFO_DESCR, string.format(config.messages.insufficientDamage, requiredDamage))
                end
            end
        end

        if topDamagerId then
            local topPlayer = Player(topDamagerId)
            if topPlayer then
                topPlayer:sendTextMessage(MESSAGE_INFO_DESCR, string.format(config.messages.topDamager, topDamage))
            end
        end

        bossInArena = nil
        damageTracker = {}
    end
    return true
end
Boss_Arena_Event:register()

local CheckArena = GlobalEvent("CheckArena")
function CheckArena.onThink(interval)
    if bossInArena then
        local boss = Creature(bossInArena)
        if boss then
            local hasPlayers = false
            for playerId, _ in pairs(playersInArena) do
                local player = Player(playerId)
                if player and player:getPosition():isInRange(config.arenaFromPos, config.arenaToPos) then
                    hasPlayers = true
                    break
                end
            end

            if not hasPlayers then
                boss:remove()
                bossInArena = nil
                playersInArena = {}
                debugLog("Boss %s removed - No players in arena", config.bossName)
            else
                debugLog("Players are still in the arena.")
            end
        end
    end
    return true
end
CheckArena:interval(10000)
CheckArena:register()

local Entrance = MoveEvent()
function Entrance.onStepIn(creature, item, position, fromPosition)
    local player = creature:getPlayer()
    if not player then return true end

    addPlayerToArena(player)
    spawnBoss(player)
    return true
end
Entrance:type("stepin")
Entrance:aid(4100)
Entrance:register()

local Exit = MoveEvent()
function Exit.onStepOut(creature, item, position, fromPosition)
    local player = creature:getPlayer()
    if not player then return true end

    if not position:isInRange(config.arenaFromPos, config.arenaToPos) then
        removePlayerFromArena(player)
    end
    return true
end
Exit:type("stepout")
Exit:aid(4100)
Exit:register()

local DoorCheck = MoveEvent()
function DoorCheck.onStepIn(creature, item, position, fromPosition)
    local player = creature:getPlayer()
    if not player then return true end

    if player:getStorageValue(config.storages.damageEligibility) ~= 1 then
        player:sendTextMessage(MESSAGE_INFO_DESCR, config.messages.needKill)
        player:teleportTo(fromPosition)
        player:getPosition():sendMagicEffect(CONST_ME_TELEPORT)
        return false
    end

    return true
end
DoorCheck:type("stepin")
DoorCheck:aid(4101)
DoorCheck:register()


View attachment 89057
Thank you very much, exactly what I meant, the TFS also matches.
 
Back
Top