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

Lua Xikini's Free Scripting Service TFS 1.4.2

Xikini

I whore myself out for likes
Senator
Joined
Nov 17, 2010
Messages
6,814
Solutions
585
Reaction score
5,383
Request whatever you want, within reason.

Please do not request for things that require source editing or database queries.

------------------------------------
If I've reacted to your post, it means I've read it. 👍😍🤣😲🙁😡🤔😉

------------------------------------
Completed Requests

A boss that randomly creates effects on tiles, that when stepped on, gives the player increased damage/mitigation to that boss.
Kill monster and gain X% extra loot chance or Y% extra experience for Z minutes
A pack of 6 useful simple scripts.
-> (1) Simple quest npc
-> (2,3,4,5) use levers in specific order / stand with 4 players / use 4 objects / place items -> to open wall
-> (6) use lever to add/remove/replace objects
Wave type spell, with multiple effects
-> Same spell, but with default combat system
House market system. (owner places price on blackboard and object on the ground, other players buy with a lever)
Respawn System (closest anchor (like darks souls bonfire) / tavern / temple)
Vocation Death Protection (protect the noobs!)
Spell that shows area it will damage, before it strikes.
RAID SYSTEM - rebuilt in Lua, with some more features.
Modal Window - Teleport Item with saving/deleting of positions
Show top 3 Online Players (outfit, name, level) (as monsters, in set positions)
onLook function showing kill count of player.
Modal Window - Use Item -> choose reward.
Talkaction - !backpacks - to buy backpacks (or single items) anywhere. uses bank & current money on players
Quest/Event? Turn a bunch of objects with a monster, spawn portal.
Spawn Monsters, after they've all been killed, give global increased experience/loot in specific area's
Evolving Weapons - Kill X amount of specific creatures, evolve weapon and gain extra damage to those creatures.
Random Portals spawn at XX:XX time(s). (can exit 'off' portals, if you have the storage.)
Monster that adjusts speed based on target
Monster that increases damage output based on # of players nearby
Experience recovery Item. Die -> Use Item -> gain % of lost experience
Character Snapshot - Keeps track of all skills & level, for resetting later.
Fire Dagger - Physical Damage Melee Weapon, with % added damage
Players in specific level ranges don't lose skills/loot/experience, when they die.
Multiclient Limit Check - with admin account not being counted towards limit
Capacity Increasing Items
Upgradeable Protection Amulet - 10% to all elements
-> upgrade amulet, but for all items.
onKill - give reward to all players who dealt damage
-> example: give reward based on damage contribution
Quest Book - Record your quest progress into a book.
Stat System, using modal windows
Holy Tible (POI) - Require Item to use teleport tiles
Item Upgrade System
Skill Stages
-> individual stages for each skill type
-> talkaction to check rates (by @Extrodus)
Random Reward Item - gives different rewards based on vocation
Bounty Hunter System
NPC & Player Walk System (Follow Nodes to destination)
Health/Mana gain permanent - limited use items

------------------------------------
Support

If you have an issue with one of my scripts, I will attempt to help you, but not in this thread.
Make a thread in the support board.
Ensure to follow all rules of the support board.
Without all necessary information it's impossible to help you.

------------------------------------
I will only be scripting for TFS 1.4.2

Not TFS 1.1 / 1.2
Not OTServBR / OTX
and certainly not TFS 0.4

When requesting a script, don't ask for "this script I saw on super popular OT".

I don't care where the idea came from.
I don't want to see a video of the script in action.

Just describe what the script is supposed to do, and I'll try to make it.

Any script that I make in response to a request from this thread will be shared publicly, here, in this thread.

I'm not going to make anything in private, so post your request in this thread only.
Please, for the love of god, don't pm me asking to make a script.
I will actually add you to my ignore list if you do that.
--------------

Anyways!

Thanks for coming by and checking the thread out.
If you think there is a better way to script something, feel free to let me know.
I'm here to learn.

Cheers,

Xikini
---------

P.S.
I've been doing free scripting service's on/off for awhile.
And if you want to see the previous threads, go check them here.

 
Last edited by a moderator:
Hi @Xikini , Could you help me create a revscripts of actions that "x" item adds "x" amount of life and "y" item adds "y" amount of mana. I also need to have a limiter on how many stones I could use.
 
@Xikini I need help with this script. The script gives double and sometimes triple reward even though it is set differently in the script I don't know what is done wrong here. I've been trying to fix it for a few days and have no results. please help a lot.
TFS 1.5 NEKIRO 8.60 DOWNGRADE

Lua:
function onKill(creature, target)
    local monsterName = "Verminor"
    if target:getName() ~= monsterName then
        return true
    end

    local rewardedPlayers = {}

    for pid, _ in pairs(target:getDamageMap()) do
        if not rewardedPlayers[pid] then
            local attackerPlayer = Player(pid)
            if attackerPlayer then
                attackerPlayer:addItem(2160, 30)
                attackerPlayer:addLevel(2)
                creature:sendTextMessage(MESSAGE_INFO_DESCR, "Congratulations.")
                rewardedPlayers[pid] = true
            end
        end
    end
    return true
end

Using onDeath and the creatures damageMap will ensure that every player who damaged the creature (or by proxy, a players summon) get's a single reward.

Lua:
local monsters = {
    "Verminor",
    "demon",
    "troll"
}

for i, monster in ipairs(monsters) do
    monsters[i] = string.lower(monster)
end


local creatureevent = CreatureEvent("onDeath_GiveRewardToAllWhoDamaged")

function creatureevent.onDeath(creature, corpse, killer, mostDamageKiller, lastHitUnjustified, mostDamageUnjustified)
    local bossDamageMap = {}
    for creatureId, damage in pairs(creature:getDamageMap()) do
        local _creature = Creature(creatureId)
        if _creature then
            local master = _creature:getMaster()
            if master and master:isPlayer() then
                _creature = master
            end
            if _creature:isPlayer() then
                local playerId = _creature:getId()
                bossDamageMap[playerId] = (bossDamageMap[playerId] or 0) + damage.total
            end
        end
    end
  
    for playerId, _ in pairs(bossDamageMap) do
        local player = Player(playerId)
        if player then
            -- now reward the players however you want
            print("got reward")
        end
    end
    return true
end

creatureevent:register()


local eventCallback = EventCallback

function eventCallback.onSpawn(creature, position, startup, artificial)
    if table.contains(monsters, creature:getName():lower()) then
        creature:registerEvent("onDeath_GiveRewardToAllWhoDamaged")
    end
    return true
end

eventCallback:register(-666)
Post automatically merged:

don't want to abuse requesting too many stuff, but i've seen many threads requesting skill stages. For example, from axe 10 to 50 have * 5 rate, from 50 to 70 *4, from 70 to 90 *3 and so on. This for all the skills: fist, axe, sword, club, fishing, shielding, etc. Any chance to add this to the queque? thanks a lot in advance! :)

edit---
I have another idea that I want to share, maybe it's intresting. Everytime a creature, let's use "Bandit" for the example, spawns on their normal spawn area. Would be possible to spawn it with a color randomization on their outfits? This way we can have Bandits with different colors. Also apply to other creatures such as Thief, Smuggler, Warlock, Amazons, or whatever monster that you would like to have with the system. Regards!

Looks like there is already a release for the your 2nd request. here

skillStages is below.

data/scripts/skillStages.lua
Lua:
local skillStages = {
    [{  1,   8}] = {multiplier = 5},
    [{  9,  20}] = {multiplier = 4},
    [{ 21,  50}] = {multiplier = 3},
    [{ 51, 100}] = {multiplier = 2},
    [{101}]      = {multiplier = 1}
}

local ec = EventCallback

ec.onGainSkillTries = function(self, skill, tries)
    if skill == SKILL_LEVEL then
        return tries
    end
    local playerLevel = skill == SKILL_MAGLEVEL and self:getMagicLevel() or self:getSkillLevel(skill)
    for k, v in pairs(skillStages) do
        if playerLevel >= k[1] then
            if not k[2] or playerLevel <= k[2] then
                tries = tries * v.multiplier
                print(v.multiplier)
            end
        end
    end  
    return tries
end

ec:register()
Post automatically merged:

all players join a room like this where they have the same speed and can't diagonal movement only south, north, east, west they use !bomb to place a bomb that exploud with 1x1 and kill every player in the explosion area, the green grass is destroyable using these explosions but not the black grass ones.when you exploud the green grass you find orbs below that gives speed, makes your explosion bigger or faster (it takes 3 seconds at the begin with 1x1 area) until 1 player left then he wins the event. you can't use haste spells, magic walls, rush woods or push players.

I mean, it looks like the event already exists.
I wouldn't be adding anything new or special to the event based on this description.

Idk, I don't see a reason to script it.
 
Last edited:
Request whatever you want, within reason.

Please do not request for things that require source editing or database queries.

------------------------------------
If I've reacted to your post, it means I've read it. 👍😍🤣😲🙁😡🤔😉

------------------------------------
Completed Requests

A boss that randomly creates effects on tiles, that when stepped on, gives the player increased damage/mitigation to that boss.
Kill monster and gain X% extra loot chance or Y% extra experience for Z minutes
A pack of 6 useful simple scripts.
-> (1) Simple quest npc
-> (2,3,4,5) use levers in specific order / stand with 4 players / use 4 objects / place items -> to open wall
-> (6) use lever to add/remove/replace objects
Wave type spell, with multiple effects
-> Same spell, but with default combat system
House market system. (owner places price on blackboard and object on the ground, other players buy with a lever)
Respawn System (closest anchor (like darks souls bonfire) / tavern / temple)
Vocation Death Protection (protect the noobs!)
Spell that shows area it will damage, before it strikes.
RAID SYSTEM - rebuilt in Lua, with some more features.
Modal Window - Teleport Item with saving/deleting of positions
Show top 3 Online Players (outfit, name, level) (as monsters, in set positions)
onLook function showing kill count of player.
Modal Window - Use Item -> choose reward.
Talkaction - !backpacks - to buy backpacks (or single items) anywhere. uses bank & current money on players
Quest/Event? Turn a bunch of objects with a monster, spawn portal.
Spawn Monsters, after they've all been killed, give global increased experience/loot in specific area's
Evolving Weapons - Kill X amount of specific creatures, evolve weapon and gain extra damage to those creatures.
Random Portals spawn at XX:XX time(s). (can exit 'off' portals, if you have the storage.)
Monster that adjusts speed based on target
Monster that increases damage output based on # of players nearby
Experience recovery Item. Die -> Use Item -> gain % of lost experience
Character Snapshot - Keeps track of all skills & level, for resetting later.
Fire Dagger - Physical Damage Melee Weapon, with % added damage
Players in specific level ranges don't lose skills/loot/experience, when they die.
Multiclient Limit Check - with admin account not being counted towards limit
Capacity Increasing Items
Upgradeable Protection Amulet - 10% to all elements
onKill - give reward to all players who dealt damage

Pending Requests (AKA "the queue")

Quest Book - Read/Write?
Basic stat system, modal windows | main request | information request |
Monster auto turn / look specific direction | might not be doable


Requested more information to complete request.


None.


------------------------------------
Support

If you have an issue with one of my scripts, I will attempt to help you, but not in this thread.
Make a thread in the support board.
Ensure to follow all rules of the support board.
Without all necessary information it's impossible to help you.

------------------------------------
I will only be scripting for TFS 1.4.2

Not TFS 1.1 / 1.2
Not OTServBR / OTX
and certainly not TFS 0.4

When requesting a script, don't ask for "this script I saw on super popular OT".

I don't care where the idea came from.
I don't want to see a video of the script in action.

Just describe what the script is supposed to do, and I'll try to make it.

Any script that I make in response to a request from this thread will be shared publicly, here, in this thread.

I'm not going to make anything in private, so post your request in this thread only.
Please, for the love of god, don't pm me asking to make a script.
I will actually add you to my ignore list if you do that.
--------------

Anyways!

Thanks for coming by and checking the thread out.
If you think there is a better way to script something, feel free to let me know.
I'm here to learn.

Cheers,

Xikini
---------

P.S.
I've been doing free scripting service's on/off for awhile.
And if you want to see the previous threads, go check them here.

So I need source editing or database queries. Can you do that please?
 
Request 1: Adventurer Journal
An idea initially from Red Dead Redemption (2) as you progress through the story,
you can look back at your journal and see drawings and little scrabbles of what you did in the past.


ezgif-3-6b6461a912.gif


(QName) = Quest Name
(MName) = Mission Name
(MState) = Mission State

How would this work in Tibia?
You can use a Book and it will state all the completed mission states of a quest.
This way you can have a "physical" copy of your story in-game to add to your empty bookcase,
without the ability for others to overwrite them, and is somewhat more personalized.

With a "personalized" (onLook) with |playername| so that each book, even the same, are unique to a player.
5b0cc7f3a21d42dee743d761915abd29.png

a809d7f8ef0b8a833a1035084c4a2760.png

With some imagination, you could add anything, not only quests to these books.
For example we can create a bestiary out of this script or tell some more about locations discovered.
This could even take over Quest Tracker altogether and make it so players have to read their book to see how further to proceed.

------------
How does the book gets updated?
The book must be "writing itself" according to the mission states by the owner
but it would be weird if that happens if the book is not on the player at all,
we also want the ability to choose if we want to update the book at all.
- If the owner of the book uses a Quill on a book, the book gets updated.
- If the owner of the book just uses the book, it just gets read.

205f1a09b40e3fbc81ed1c360b7ea503.png


- We can set specific books for each quest, so for example:
Red Tome will be used for Quest 1, Green Tome for Quest 2

OR
- Some sort of pages? Each page is a Quest, so essentially you fill one book with many pages.


The question I'm still having, how do we even set which quest the book shows haha


------------------
In Short~
Perhaps we could use a Desk(Any platform) as a base to use to state a Quest for a book if that makes it easier to script
  • Player puts book on platform.
  • Player uses Quill on the book and states Quest Name; the book gets personalized and Quest and Mission States are pulled and written in the book.
(Perhaps these steps can be skipped but I was thinking of the market system and blackboard configuration and perhaps that can be used the same 🤔 )

- From here on out, wherever the player is, the owner of the book can use Quill on the book to refresh the Mission States.
(Note: Players are not allowed to write in this book like you normally can just type in any book as this will be considered a special book)
a88269e3ac34aba938cb96f99a5d3fcc.png


the lord of the rings GIF
Scared Cringe GIF by O&O, Inc
Craft Beer Sourbeer GIF by Vault City Brewing

The quest log is automatically parsed at server start-up to find all the quests and mission states.
So make sure your quest log is 'normal', like this..
XML:
<?xml version="1.0" encoding="UTF-8"?>
<quests>
    <quest name="Example Quest I" startstorageid="1001" startstoragevalue="1">
        <mission name="Example Mission 1" storageid="1001" startvalue="1" endvalue="3" ignoreendvalue="true">
            <missionstate id="1" description="Example description 1" />
            <missionstate id="2" description="Example description 2" />
            <missionstate id="3" description="Example description 3" />
        </mission>
        <mission name="Example Mission 2" storageid="1001" startvalue="4" endvalue="5">
            <missionstate id="4" description="Example description 1" />
            <missionstate id="5" description="Example description 2" />
        </mission>
    </quest>
</quests>

Need to create the books from a quest, or using an npc.
Lua:
local book = createQuestBook(1953, "Example Quest I")
player:addItemEx(book, true)

bandicam2024-02-1415-38-14-407-ezgif.com-video-to-gif-converter.gif

data/lib/core/core.lua -- add
Lua:
dofile('data/lib/core/xikiniCustomFunctions.lua')
data/lib/core/xikiniCustomFunctions.lua -- add
Lua:
--[[ example usage
    local book = createQuestBook(1953, "Example Quest I")
    player:addItemEx(book, true)
]]--

function createQuestBook(bookId, bookname)
    local book = Game.createItem(bookId, 1)
    book:setCustomAttribute("BOOK_NAME", bookname)
    book:setAttribute(ITEM_ATTRIBUTE_TEXT, bookname .. "\n\nNo mission progress logged.")
    book:setAttribute(ITEM_ATTRIBUTE_WRITER, "no author")
    book:setAttribute(ITEM_ATTRIBUTE_DESCRIPTION, "It currently has no author.\nIt tells the story of " .. bookname)
    return book
end
function parseXML(xml)
    local quests = {}
    xml:gsub("<quest (.-)>(.-)</quest>", function(questAttrs, questContent)
        local quest = {}
        questAttrs:gsub("(%w+)%s*=%s*\"(.-)\"", function(attr, value)
            quest[attr] = value
        end)
        quest.missions = {}

        questContent:gsub("<mission(.-)>(.-)</mission>", function(missionAttrs, missionContent)
            local mission = {}
            missionAttrs:gsub("(%w+)%s*=%s*\"(.-)\"", function(k, v)
                mission[k] = v
            end)
            mission.missionstates = {}

            missionContent:gsub("<missionstate(.-)/>", function(stateAttrs)
                local state = {}
                stateAttrs:gsub('(%w+)%s*=%s*\"(.-)\"', function(k, v)
                    state[k] = v
                end)
                table.insert(mission.missionstates, state)
            end)

            table.insert(quest.missions, mission)
        end)

        table.insert(quests, quest)
    end)
    return quests
end

function readXmlFile(filePath)
    local file = io.open(filePath, "r")
    if not file then
        error("Cannot open file: " .. filePath)
        return nil
    end
    local content = file:read("*all")
    file:close()
    return content
end
data/scripts/questBook.lua
Lua:
local filePath = "data/XML/quests.xml"
local xmlContent = readXmlFile(filePath)
local questsTable = parseXML(xmlContent)

--[[
for _, quest in ipairs(questsTable) do
    print("Quest Name:", quest.name, "StartStorageID:", quest.startstorageid, "StartStorageValue:", quest.startstoragevalue)
    for _, mission in ipairs(quest.missions) do
        print("\tMission Name:", mission.name, "StorageID:", mission.storageid, "StartValue:", mission.startvalue, "EndValue:", mission.endvalue, "IgnoreEndValue:", mission.ignoreendvalue or "false")
        for _, state in ipairs(mission.missionstates) do
            print("\t\tState ID:", state.id, "Description:", state.description)
        end
    end
    print("\n\n")
end
]]--


local inkwell = 2600


local action = Action()

function action.onUse(player, item, fromPosition, target, toPosition, isHotkey)
    if item:getId() == inkwell then
        if not target:isItem() then
            player:sendTextMessage(MESSAGE_STATUS_SMALL, "Not a valid book target.")
            return true
        end
        local bookname = target:getCustomAttribute("BOOK_NAME")
        if not bookname then
            player:sendTextMessage(MESSAGE_STATUS_SMALL, "Not a quest book.")
            return true
        end
        local bookOwner = target:getAttribute(ITEM_ATTRIBUTE_WRITER)
        if bookOwner and bookOwner ~= "no author" and bookOwner ~= player:getName() then
            player:sendTextMessage(MESSAGE_STATUS_SMALL, "Not original author. Unable to modify book.")
            return true
        end
        bookname = bookname:lower()
        for _, quest in ipairs(questsTable) do
            if quest.name:lower() == bookname then
                local text = ""
                if player:getStorageValue(tonumber(quest.startstorageid)) < tonumber(quest.startstoragevalue) then
                    player:sendTextMessage(MESSAGE_STATUS_SMALL, "Quest not started. Unable to record information that has never happened.")
                    return true
                end
                text = quest.name
                for _, mission in ipairs(quest.missions) do
                    if player:getStorageValue(tonumber(mission.storageid)) >= tonumber(mission.startvalue) then
                        text = text .. "\n\n" .. mission.name
                        for _, state in ipairs(mission.missionstates) do
                            if player:getStorageValue(tonumber(mission.storageid)) >= tonumber(state.id) then
                                text = text .. "\n> " .. state.description
                            end
                        end
                    end
                end
                if text == quest.name then
                    text = text .. "\n\nNo mission progress logged."
                end
                target:setAttribute(ITEM_ATTRIBUTE_TEXT, text)
                target:setAttribute(ITEM_ATTRIBUTE_DATE, os.time())
                target:setAttribute(ITEM_ATTRIBUTE_WRITER, player:getName())
                target:setAttribute(ITEM_ATTRIBUTE_DESCRIPTION, "It was written by " .. player:getName() .. ".\nIt tells the story of " .. quest.name)
                return true
            end
        end
        player:sendTextMessage(MESSAGE_STATUS_SMALL, "Somehow not a valid quest title.. tell gm to fix their quest log or npc who gave this book")
        return true
    end
    return true
end

action:id(inkwell)
action:register()
Post automatically merged:

monsters can't be moved and 1 of them is hostile but one isn't
So yeah. I tested and unless the monster is non-hostile, it won't work.
The monster will otherwise target nearby players and look towards them.
No amount of forcing their direction south will make it look good, cuz they'd just be constantly looking towards player, and then south again.

Same for the rotating creature.
 
Last edited:
b307d96ae8d1f82f8e49d74ecb55088f.png

Thank you!
This is such a more immersive way to show a quest log.
Essentially with good quest description writing, you can have a story unfold and see all mission states instead of only the last one.

You can also create a Tutorial Book out of it, just make a quest, unlock all the storages at first login, give book and a quill to player at firstitems and voila​
 
So yeah. I tested and unless the monster is non-hostile, it won't work.
The monster will otherwise target nearby players and look towards them.
No amount of forcing their direction south will make it look good, cuz they'd just be constantly looking towards player, and then south again.

Same for the rotating creature.
can you do it turn all directions for hostile monster?
 
I mean, it looks like the event already exists.
I wouldn't be adding anything new or special to the event based on this description.

Idk, I don't see a reason to script it.
i will try but i think i will make mistakes in movement tp check and globalevent tp appear on winner rewards or storages to check joined players
 
can you do it turn all directions for hostile monster?

Okay, I give up trying to explain it.
You can just experience the mechanic yourself. lol

data/scripts/rotateMonsters.lua
Lua:
local monsters = {
    ["scarab"] = {directions = {DIRECTION_SOUTH}, delay = 500},
    ["ancient scarab"] = {directions = {DIRECTION_NORTH, DIRECTION_EAST, DIRECTION_SOUTH, DIRECTION_WEST}, delay = 1000}
}

local function rotateEndlessly(creatureId, directions, delay, lastDirection)
    local creature = Creature(creatureId)
    if not creature then
        return
    end
    lastDirection = lastDirection == #directions and 1 or lastDirection + 1
    creature:setDirection(directions[lastDirection])
    addEvent(rotateEndlessly, delay, creatureId, directions, delay, lastDirection)
end



local creatureevent = CreatureEvent("onThink_rotateEndlessly")

function creatureevent.onThink(creature, interval)
    local index = monsters[creature:getName():lower()]
    rotateEndlessly(creature:getId(), index.directions, index.delay, 0)
    creature:unregisterEvent("onThink_rotateEndlessly")
    return true
end

creatureevent:register()



local eventCallback = EventCallback

function eventCallback.onSpawn(creature, position, startup, artificial)
    if monsters[creature:getName():lower()] then
        creature:registerEvent("onThink_rotateEndlessly")
    end
    return true
end

eventCallback:register(-666)
 
Okay, I give up trying to explain it.
You can just experience the mechanic yourself. lol

data/scripts/rotateMonsters.lua
Lua:
local monsters = {
    ["scarab"] = {directions = {DIRECTION_SOUTH}, delay = 500},
    ["ancient scarab"] = {directions = {DIRECTION_NORTH, DIRECTION_EAST, DIRECTION_SOUTH, DIRECTION_WEST}, delay = 1000}
}

local function rotateEndlessly(creatureId, directions, delay, lastDirection)
    local creature = Creature(creatureId)
    if not creature then
        return
    end
    lastDirection = lastDirection == #directions and 1 or lastDirection + 1
    creature:setDirection(directions[lastDirection])
    addEvent(rotateEndlessly, delay, creatureId, directions, delay, lastDirection)
end



local creatureevent = CreatureEvent("onThink_rotateEndlessly")

function creatureevent.onThink(creature, interval)
    local index = monsters[creature:getName():lower()]
    rotateEndlessly(creature:getId(), index.directions, index.delay, 0)
    creature:unregisterEvent("onThink_rotateEndlessly")
    return true
end

creatureevent:register()



local eventCallback = EventCallback

function eventCallback.onSpawn(creature, position, startup, artificial)
    if monsters[creature:getName():lower()] then
        creature:registerEvent("onThink_rotateEndlessly")
    end
    return true
end

eventCallback:register(-666)

thank you, its working like i want but the question is it will lag on 10 monsters and delay 500?
 
@ralke

I'm not sure if you noticed or not, but there was an error in the Lua raid system.

I've reuploaded the file in this thread with the fix, but you can manually change it yourself.

change
Lua:
v.raidAttempts = raidIndex.raid.raidAttempts
for
Lua:
v.raidAttempts = raidIndex.raid.forceAfterAttempts

Post automatically merged:

thank you, its working like i want but the question is it will lag on 10 monsters and delay 500?
No it will not lag.
It's an extremely small function.
 
@ralke

I'm not sure if you noticed or not, but there was an error in the Lua raid system.

I've reuploaded the file in this thread with the fix, but you can manually change it yourself.

change
Lua:
v.raidAttempts = raidIndex.raid.raidAttempts
for
Lua:
v.raidAttempts = raidIndex.raid.forceAfterAttempts

Post automatically merged:


No it will not lag.
It's an extremely small function.
Thanks a lot @Xikini, at the moment I have just tested the less complex systems; been working a lot on my server, step by step, so I will eventually step up in the scripts folder again (i've been traducing the whole tfs to spanish so it will be a mess if I just go and test the script with wrong creature names, etc.). But I promise I will test asap, i'm planning to trigger different scenarios so I can bring better feedback ^^

Regards!
 
Yes this is the way forward: "1 storage for points accumulated
2nd storage for points spent

and then 1 storage per stat, to tell me how many points have been spent on that attribute?"

If modal is to complicated task to do this maybe we can use .
!stats with menu in OnLook text instead of Modal to make it easier?
!stats reset, !stats add,magic !stats remove,magic

Adding or removal of stats will remove 1 point at the time. If this is to much I understand. ;)

Is this possible list of stats?

{"life increase", 100},
{"mana increase", 100,
{"magic", 1},
{"melee", 1}, -- sword/axe/club
{"distance", 1},
{"shield", 1},
{"critical hit chance", 1},
{"critical hit damage", 1},
{"life leech chance", 1},
{"life leech amount", 1},
{"mana leech chance", 1},
{"mana leech amount", 1},

Probably needs more features added, such as limits to each stat, and possibly restrictions per vocation..
But the base system is there.

bandicam2024-02-1503-40-48-620-ezgif.com-video-to-gif-converter.gif

data/lib/core/core.lua -- add
Lua:
dofile('data/lib/core/xikiniCustomFunctions.lua')
data/lib/core/xikiniCustomFunctions.lua -- add
Lua:
--[[ quick reference of events

    CREATURE_EVENT_NONE
    CREATURE_EVENT_LOGIN
    CREATURE_EVENT_LOGOUT
    CREATURE_EVENT_THINK
    CREATURE_EVENT_PREPAREDEATH
    CREATURE_EVENT_DEATH
    CREATURE_EVENT_KILL
    CREATURE_EVENT_ADVANCE
    CREATURE_EVENT_MODALWINDOW
    CREATURE_EVENT_TEXTEDIT
    CREATURE_EVENT_HEALTHCHANGE
    CREATURE_EVENT_MANACHANGE
    CREATURE_EVENT_EXTENDED_OPCODE
]]--

function Player:hasEvent(type, name)
    for k, v in pairs(self:getEvents(type)) do
        if v == name then
            return true
        end
    end
    return false
end
data/scripts/statSystem.lua
Lua:
local conditionSubId = 45064 -- must be a unique subId not used for other buffs in your server
local config  = {
    statStorageKey = 45064, -- give players points with this storage
    ["statMain"] = {
        -- flat_bonus_stats
        {storageKey = 45065, statType = "life increase", value = 50},
        {storageKey = 45066, statType = "mana increase", value = 50},
        {storageKey = 45067, statType = "magic", value = 1},
        {storageKey = 45068, statType = "fist", value = 1},
        {storageKey = 45069, statType = "melee", value = 1},
        {storageKey = 45070, statType = "distance", value = 1},
        {storageKey = 45071, statType = "shield", value = 1},
        {storageKey = 45072, statType = "fishing", value = 1},
        {storageKey = 45073, statType = "critical hit chance", value = 1},
        {storageKey = 45074, statType = "critical hit damage", value = 1},
        {storageKey = 45075, statType = "life leech chance", value = 1},
        {storageKey = 45076, statType = "life leech amount", value = 1},
        {storageKey = 45077, statType = "mana leech chance", value = 1},
        {storageKey = 45078, statType = "mana leech amount", value = 1}
    },
    ["statSpeed"] = {
        {storageKey = 45079, statType = "speed", value = 2}
    },
    ["statRegen"] = {
        {storageKey = 45080, statType = "life regen", value = 5, ticks = 5000}, -- ticks in milliseconds}
        {storageKey = 45081, statType = "mana regen", value = 5, ticks = 5000}  -- can't go lower then 1000
    },
    ["statSoulRegen"] = { -- you can remove entire categories
        {storageKey = 45082, statType = "soul regen", value = 1, ticks = 5000} -- or individual stats, if you don't want to use them
    },
  
    modalWindow = {
        id = 1002, -- must be unique
        title = "Stat System",
        message = "",
        eventText = "ModalWindow_XikiniStatSystem",
        buttons = {
            {text = "Reset"},
            {text = "Remove"},
            {text = "Add", defaultEnterButton = true},
            {text = "Close", defaultEscapeButton = true},
        }
    }
}

-- Choose Flat or Percentage stats. Cannot use both.

-- Flat Stats
--[[
    ["statMain"] = {
        -- flat_bonus_stats
        {storageKey = 45065, statType = "life increase", value = 50},
        {storageKey = 45066, statType = "mana increase", value = 50},
        {storageKey = 45067, statType = "magic", value = 1},
        {storageKey = 45068, statType = "fist", value = 1},
        {storageKey = 45069, statType = "melee", value = 1},
        {storageKey = 45070, statType = "distance", value = 1},
        {storageKey = 45071, statType = "shield", value = 1},
        {storageKey = 45072, statType = "fishing", value = 1},
        {storageKey = 45073, statType = "critical hit chance", value = 1},
        {storageKey = 45074, statType = "critical hit damage", value = 1},
        {storageKey = 45075, statType = "life leech chance", value = 1},
        {storageKey = 45076, statType = "life leech amount", value = 1},
        {storageKey = 45077, statType = "mana leech chance", value = 1},
        {storageKey = 45078, statType = "mana leech amount", value = 1}
    },
    ["statSpeed"] = {
        {storageKey = 45079, statType = "speed", value = 2}
    },
    ["statRegen"] = {
        {storageKey = 45080, statType = "life regen", value = 5, ticks = 5000}, -- ticks in milliseconds}
        {storageKey = 45081, statType = "mana regen", value = 5, ticks = 5000}  -- can't go lower then 1000
    },
    ["statSoulRegen"] = { -- you can remove entire categories
        {storageKey = 45082, statType = "soul regen", value = 1, ticks = 5000} -- or individual stats, if you don't want to use them
    },
]]--


-- Percent Stats
--[[
    ["statMain"] = {
        -- percent_bonus_stats
        {storageKey = 45065, statType = "life increase percent", value = 1},
        {storageKey = 45066, statType = "mana increase percent", value = 1},
        {storageKey = 45067, statType = "magic percent", value = 1},
        {storageKey = 45068, statType = "fist percent", value = 1},
        {storageKey = 45069, statType = "melee percent", value = 1},
        {storageKey = 45070, statType = "distance percent", value = 1},
        {storageKey = 45071, statType = "shield percent", value = 1},
        {storageKey = 45072, statType = "fishing percent", value = 1},
        {storageKey = 45073, statType = "critical hit chance", value = 1},
        {storageKey = 45074, statType = "critical hit damage", value = 1},
        {storageKey = 45075, statType = "life leech chance", value = 1},
        {storageKey = 45076, statType = "life leech amount", value = 1},
        {storageKey = 45077, statType = "mana leech chance", value = 1},
        {storageKey = 45078, statType = "mana leech amount", value = 1}
    },
    ["statSpeed"] = {
        {storageKey = 45079, statType = "speed", value = 2}
    },
    ["statRegen"] = {
        {storageKey = 45080, statType = "life regen", value = 5, ticks = 5000}, -- ticks in milliseconds}
        {storageKey = 45081, statType = "mana regen", value = 5, ticks = 5000}  -- can't go lower then 1000
    },
    ["statSoulRegen"] = {
        {storageKey = 45082, statType = "soul regen", value = 1, ticks = 5000}
    },
]]--


-- END OF CONFIG

local choiceDictionary = {}

local conditions = {
    ["life increase"] = {CONDITION_PARAM_STAT_MAXHITPOINTS},
    ["mana increase"] = {CONDITION_PARAM_STAT_MAXMANAPOINTS},
    ["speed"] = {CONDITION_PARAM_SPEED},
    ["magic"] = {CONDITION_PARAM_STAT_MAGICPOINTS},
    ["melee"] = {CONDITION_PARAM_SKILL_MELEE},
    ["fist"] = {CONDITION_PARAM_SKILL_FIST},
    ["club"] = {CONDITION_PARAM_SKILL_CLUB},
    ["sword"] = {CONDITION_PARAM_SKILL_SWORD},
    ["axe"] = {CONDITION_PARAM_SKILL_AXE},
    ["distance"] = {CONDITION_PARAM_SKILL_DISTANCE},
    ["shield"] = {CONDITION_PARAM_SKILL_SHIELD},
    ["fishing"] = {CONDITION_PARAM_SKILL_FISHING},
    ["critical hit chance"] = {CONDITION_PARAM_SPECIALSKILL_CRITICALHITCHANCE},
    ["critical hit damage"] = {CONDITION_PARAM_SPECIALSKILL_CRITICALHITAMOUNT},
    ["life leech chance"] = {CONDITION_PARAM_SPECIALSKILL_LIFELEECHCHANCE},
    ["life leech amount"] = {CONDITION_PARAM_SPECIALSKILL_LIFELEECHAMOUNT},
    ["mana leech chance"] = {CONDITION_PARAM_SPECIALSKILL_MANALEECHCHANCE},
    ["mana leech amount"] = {CONDITION_PARAM_SPECIALSKILL_MANALEECHAMOUNT},
    ["life increase percent"] = {CONDITION_PARAM_STAT_MAXHITPOINTSPERCENT},
    ["mana increase percent"] = {CONDITION_PARAM_STAT_MAXMANAPOINTSPERCENT},
    ["magic percent"] = {CONDITION_PARAM_STAT_MAGICPOINTSPERCENT},
    ["melee percent"] = {CONDITION_PARAM_SKILL_MELEEPERCENT},
    ["fist percent"] = {CONDITION_PARAM_SKILL_FISTPERCENT},
    ["club percent"] = {CONDITION_PARAM_SKILL_CLUBPERCENT},
    ["sword percent"] = {CONDITION_PARAM_SKILL_SWORDPERCENT},
    ["axe percent"] = {CONDITION_PARAM_SKILL_AXEPERCENT},
    ["distance percent"] = {CONDITION_PARAM_SKILL_DISTANCEPERCENT},
    ["shield percent"] = {CONDITION_PARAM_SKILL_SHIELDPERCENT},
    ["fishing percent"] = {CONDITION_PARAM_SKILL_FISHINGPERCENT},
    ["life regen"] = {CONDITION_PARAM_HEALTHGAIN, CONDITION_PARAM_HEALTHTICKS},
    ["mana regen"] = {CONDITION_PARAM_MANAGAIN, CONDITION_PARAM_MANATICKS},
    ["soul regen"] = {CONDITION_PARAM_SOULGAIN, CONDITION_PARAM_SOULTICKS}
}

local main_attributes = {CONDITION_ATTRIBUTES, CONDITION_HASTE, CONDITION_REGENERATION, CONDITION_SOUL}
local main_stats = {"statMain", "statSpeed", "statRegen", "statSoulRegen"}

-- choiceDictionary Setup
for i = 1, 4 do
    local statCategory = main_stats[i]
    if config[statCategory] then
        for index, stat in ipairs(config[statCategory]) do
            choiceDictionary[#choiceDictionary + 1] = config[statCategory][index]
        end
    end
end

local function updateStatBonus(player)
    -- remove all previous buffs
    for i = 1, 4 do
        if player:getCondition(main_attributes[i], conditionSubId) then
            player:removeCondition(main_attributes[i], conditionSubId)
        end
    end
 
    -- add all buffs
    for i = 1, 4 do
        local statCategory = main_stats[i]
        if config[statCategory] then
            local condition = Condition(main_attributes[i], conditionSubId)
            condition:setParameter(CONDITION_PARAM_TICKS, -1)
            for _, stat in ipairs(config[statCategory]) do
                local storageValue = player:getStorageValue(stat.storageKey)
                if storageValue > 0 then
                    for conditionParam = 1, #conditions[stat.statType] do
                        condition:setParameter(conditions[stat.statType][conditionParam], (stat.value * storageValue))
                    end
                    player:addCondition(condition)
                end
            end
        end
    end
    return true
end

local creatureevent = CreatureEvent("onLogin_updateStatBonus")

function creatureevent.onLogin(player)
    updateStatBonus(player)
    return true
end

creatureevent:register()



local function createStatWindow(playerId)
    local player = Player(playerId)
    if not player then
        return
    end

    if player:hasEvent(CREATURE_EVENT_MODALWINDOW, config.modalWindow.eventText) then
        player:unregisterEvent(config.modalWindow.eventText)
    end
    player:registerEvent(config.modalWindow.eventText)
  
    local storageValue = player:getStorageValue(config.statStorageKey)
    local modalWindow = ModalWindow(config.modalWindow.id, config.modalWindow.title, "You have " .. (storageValue > 0 and storageValue or 0) .. " stat points to spend.")
  
    for id, button in ipairs(config.modalWindow.buttons) do
        modalWindow:addButton(id, button.text)
        if button.defaultEscapeButton then
            modalWindow:setDefaultEscapeButton(id)
        elseif button.defaultEnterButton then
            modalWindow:setDefaultEnterButton(id)
        end
    end
  
    for id, stat in ipairs(choiceDictionary) do
        local storageValue = player:getStorageValue(stat.storageKey)
        storageValue = storageValue > 0 and storageValue or 0
        modalWindow:addChoice(id, "[" .. storageValue .. "] " .. stat.statType)
    end
  
    modalWindow:hasPriority()
    modalWindow:sendToPlayer(player)
end


local creatureevent = CreatureEvent(config.modalWindow.eventText)

function creatureevent.onModalWindow(player, modalWindowId, buttonId, choiceId)
    player:unregisterEvent(config.modalWindow.eventText)
  
    if modalWindowId == config.modalWindow.id then
        local buttonChoice = config.modalWindow.buttons[buttonId].text
      
        local statStorageValue = player:getStorageValue(config.statStorageKey)
        if buttonChoice == "Add" then
            if statStorageValue > 0 then
                local stat = choiceDictionary[choiceId]
                local storageValue = player:getStorageValue(stat.storageKey)
                storageValue = storageValue > 0 and storageValue or 0
                player:setStorageValue(stat.storageKey, storageValue + 1)
                player:setStorageValue(config.statStorageKey, statStorageValue - 1)
            end
        elseif buttonChoice == "Remove" then
            if statStorageValue > 0 then
                local stat = choiceDictionary[choiceId]
                local storageValue = player:getStorageValue(stat.storageKey)
                storageValue = storageValue > 0 and storageValue or 0
                if storageValue > 0 then
                    player:setStorageValue(stat.storageKey, storageValue - 1)
                    player:setStorageValue(config.statStorageKey, statStorageValue + 1)
                end
            end
        elseif buttonChoice == "Reset" then
            local totalResetPoints = 0
            for id, stat in ipairs(choiceDictionary) do
                local storageValue = player:getStorageValue(stat.storageKey)
                storageValue = storageValue > 0 and storageValue or 0
                player:setStorageValue(stat.storageKey, 0)
                totalResetPoints = totalResetPoints + storageValue
            end
            player:setStorageValue(config.statStorageKey, statStorageValue + totalResetPoints)
        else
            -- "Close" button
            return true
        end
      
        updateStatBonus(player)
        addEvent(createStatWindow, 0, player:getId())
    end
    return true
end

creatureevent:register()


local talkaction = TalkAction("!stats")

function talkaction.onSay(player, words, param, type)
    createStatWindow(player:getId())
    return false
end

talkaction:separator(" ")
talkaction:register()
Post automatically merged:

It is sweet!

I wonder if could you create alternate version of this script, in which you can set many different items to be upgradeable, not only single amulet. I am thinking about any other equipment part, or maybe even whole group of items (like all shields, or all armors). In such script you would be able to give the item ID's that you want to be upgradeable one by one.

Thanks in advance!

from script here

change -- line 1
Lua:
local amuletItemId = 2173
to
Lua:
local upgradeableItems = {2173}


change -- line 68
Lua:
if target:getId() ~= amuletItemId then
to
Lua:
if not table.contains(upgradeableItems, target:getId())
Post automatically merged:

Thank you for your last script! @Xikini

Now I am having issues with a specific part of a POI script (known as holy tibe). If possible, I would like to ask for that part to be redone.

How it works: If the player has an item, they can step on a specific SQM; if they don't have the item, they return to the position where they attempted to move. If they have the item, they are teleported to a certain coordinate. The same logic applies to the other side, but obviously, they are teleported to a different coordinate.

I will also provide the last reference script that I tested and did not work for me: canary/data-otservbr-global/scripts/movements/quests/pits_of_inferno/holy_tible_tile.lua at main · opentibiabr/canary (https://github.com/opentibiabr/canary/blob/main/data-otservbr-global/scripts/movements/quests/pits_of_inferno/holy_tible_tile.lua)

Thank you in advance!

bandicam2024-02-1504-10-39-480-ezgif.com-video-to-gif-converter.gif

data/scripts/holyTible.lua
Lua:
local holyTibleId = 1970

local positions = {
--  [actionId] = destination
    [45083] = Position(982, 993, 7),
    [45084] = Position(981, 992, 7)
}

local moveevent = MoveEvent()

function moveevent.onStepIn(creature, item, position, fromPosition)
    if not creature:isPlayer() then
        return true
    end
    if creature:getItemCount(holyTibleId) == 0 then
        creature:teleportTo(fromPosition, true)
        return true
    end
    position:sendMagicEffect(CONST_ME_TELEPORT)
    creature:teleportTo(positions[item:getActionId()])
    creature:getPosition():sendMagicEffect(CONST_ME_TELEPORT)
    return true
end

for actionId, _ in pairs(positions) do
    moveevent:aid(actionId)
end
moveevent:register()
 
Last edited:
here goes another one! :p

would be possible to give an item based on damage map? for example I create a monster, "The Mutated Pumpkin", and if it is killed, give a certain item to every player who contributed on killing the creature. let's use crystal coins as example, so it would be nice if it has a local parameter that defines how much is the maxium amount of crystal coins. then if the monster dies, share the reward to each player who contributed on monster's killing proportionally based on maxium amount (of crystal coins in this case) and damage map.

Nimbus bot throw me this, but i'm not sure if it is the best way to do it:
Lua:
function onKill(cid, target)
    local monsterName = "The Mutated Pumpkin" -- Replace with the actual monster name
    local itemID = 2160 -- Replace with the item ID of crystal coins
    local maxCoins = 100 -- Replace with the maximum amount of crystal coins

    if getCreatureName(target) == monsterName then
        local players = getDamageMap(target)
        local totalDamage = 0

        -- Calculate the total damage dealt by all players
        for _, damageInfo in ipairs(players) do
            totalDamage = totalDamage + damageInfo.damage
        end

        -- Distribute the reward to each player based on their damage contribution
        for _, damageInfo in ipairs(players) do
            local player = damageInfo.cid
            local damage = damageInfo.damage
            local coins = math.floor((damage / totalDamage) * maxCoins)

            doPlayerAddItem(player, itemID, coins)
            doPlayerSendTextMessage(player, MESSAGE_EVENT_ADVANCE, "You received " .. coins .. " crystal coins for your contribution!")
        end
    end
end

So this ties in as a good example case of another script I just made.
and nimbus bot did a decent job.

bandicam2024-02-1504-46-21-857-ezgif.com-video-to-gif-converter.gif

data/scripts/onDeath_giveRewardBasedOnContribution.lua
Lua:
local monsters = {
--  ["monster name in lowercase letters"]
    ["troll"] = {itemId = 2160, dropAmount = 100, minimumAmount = 2} -- minimumAmount basically ensures that everyone get's SOMETHING if they helped. But can set to 0, so if they don't help enough, they get nothing.
}

local function getItemNameString(item)
    return item:getNameDescription(item:getSubType(), true)
end

local creatureevent = CreatureEvent("onDeath_giveRewardBasedOnContribution")

function creatureevent.onDeath(creature, corpse, killer, mostDamageKiller, lastHitUnjustified, mostDamageUnjustified)

    local monsterName = creature:getName():lower()
    local bossDamageMap = {}
    local totalDamage = 0
   
    for creatureId, damage in pairs(creature:getDamageMap()) do
        totalDamage = totalDamage + damage.total
        local _creature = Creature(creatureId)
        if _creature then
            local master = _creature:getMaster()
            if master and master:isPlayer() then
                _creature = master
            end
            if _creature:isPlayer() then
                local playerId = _creature:getId()
                bossDamageMap[playerId] = (bossDamageMap[playerId] or 0) + damage.total
            end
        end
    end
   
    local monster = monsters[monsterName]
    for playerId, _ in pairs(bossDamageMap) do
        local player = Player(playerId)
        if player then       
            local damage = bossDamageMap[playerId]
            local lootAmount = math.max(math.floor((damage / totalDamage) * monster.dropAmount), monster.minimumAmount)
            if lootAmount > 0 then
                local rewardItem = Game.createItem(monster.itemId, lootAmount)
                player:addItemEx(rewardItem, true)
                doPlayerSendTextMessage(player, MESSAGE_EVENT_ADVANCE, "You received " .. getItemNameString(rewardItem) .. " for your contribution!")
            end
        end
    end
    return true
end

creatureevent:register()


local eventCallback = EventCallback

function eventCallback.onSpawn(creature, position, startup, artificial)
    if monsters[creature:getName():lower()] then
        creature:registerEvent("onDeath_giveRewardBasedOnContribution")
    end
    return true
end

eventCallback:register(-666)
 
Last edited:
@Xikini testing logs of raid system. First try:
1707990905018.png
1707990723384.png

My raid configuration:
Lua:
local raidStorageKey = 45026 -- used to track what raid monsters were spawned in
local nonSpawnableItemTiles = {1510, 1511, 1513, 2579, 4208, 25331} -- if these items are found on a tile, don't allow monsters to spawn on them (currently just traps)

local raids = {

    {    raid = {
            name = "Minotauros", -- must be unique to this raid, or it'll show error in console
            specificTimes = {
                -- "hh:mm:ss", -- start raid at specified times, based on local computer clock (ignores startDelay/interval/chance et cetera.)
                -- "07:00:00", -- 7am
                -- "19:00:00", -- 7pm
                -- "20:30:24" -- 8h 30m 24s pm
            }, 
            startDelay = 30, -- minutes -- how long after starting server before the raid can execute
            interval = 15, -- minutes -- how long inbetween attempts at spawning the raid.
            chance = 5000, -- chance / 10000 -- so 100 chance = 1% | 1 chance = 0.01%
            forceAfterAttempts = 20, -- if raid is unlucky, force the raid to start after x attempts.
            --> (5 minutes * 20 attempts -> 100 minutes of attempts. force on attempt 21)
            --> -1 = never force -- so it is completely random chance
            --> 0 = force on first attempt (aka: every x minutes)
            cooldown = 120, -- minutes -- how long before this raid can be triggered again
            forceNewRaidInstance = 1 -- -1 = ignore 0 = false, 1 = true
            --> if true, and raid attempts to execute again, will remove all previous raid monsters.
            --> if false, will not attempt to execute until all previous raid monsters have been eliminated
            --> if ignore, will not remove previous raid monsters, and will just proceed as if raid was occurring for the first time.
        },        
        nonSpawnableAreas = { -- for any areas you don't want raid monsters to spawn
            {from = Position(1118, 981, 7), to = Position(1120, 983, 7)}, -- from = top left corner | to = bottom right corner
            --{from = Position(966, 986, 7), to = Position(978, 996, 7)}
        },
        waves = {
            {
                broadcast = {messageType = MESSAGE_STATUS_WARNING, text = "Raid is starting soon in some place"},
                nextWaveTimer = 10 -- seconds (if not included, defaults to 60 seconds)
            },
            { -- wave 1 (example with RANDOM monsters only)
                randomMonsters = {
                    {
                        amount = {min = 5, max = 20},
                        monsterList = {"minotauro", "lobo", "trasgo"},
                        spawnAreas = {
                            {from = Position(1107, 979, 7), to = Position(1121, 992, 7)}
                        }
                    },
                    {
                        amount = {min = 20, max = 20},
                        monsterList = {"gusano"},
                        spawnAreas = {
                            {from = Position(1107, 979, 7), to = Position(1121, 992, 7)}, -- from = top left corner | to = bottom right corner
                            {from = Position(1107, 979, 6), to = Position(1121, 992, 6)}  -- having 2 different z positions, allows for multiple floor spawning
                        }
                    },
                },
                nextWaveTimer = 10 -- seconds
            },
            { -- wave 2 (example with STATIC monsters only)
                staticMonsters = {
                    {position = Position(1111, 986, 7), name = "ciclope"}, -- since these are setup manually, we don't do additional checks. We assume it's a good spawn location and force it
                    {position = Position(1119, 986, 7), name = "dragon"}
                },
                nextWaveTimer = 10 -- seconds
            },
            { -- wave 3 (example with both STATIC & RANDOM monsters)
                staticMonsters = {
                    {position = Position(1115, 991, 7), name = "dragon lord"}
                },
                randomMonsters = {
                    {
                        amount = {min = 20, max = 20},
                        monsterList = {"avispa", "lobo", "oso"},
                        spawnAreas = {
                            {from = Position(1107, 979, 7), to = Position(1121, 992, 7)},
                            {from = Position(1107, 979, 6), to = Position(1121, 992, 6)}
                        }
                    }
                },
                nextWaveTimer = 10 -- seconds
            },
            { -- wave 4 (example Boss?)
                staticMonsters = {
                    {position = Position(1107, 986, 7), name = "demon"}
                }
            }
        }
    },
    
    {    raid = {
            name = "Esqueletos",
            specificTimes = {
                -- "hh:mm:ss"
                "07:00:00", -- 7am
                "19:00:00"  -- 7pm
            }, 
            startDelay = 30,
            interval = 15,
            chance = 10000,
            forceAfterAttempts = 20,
            cooldown = 120,
            forceNewRaidInstance = -1
        },        
        nonSpawnableAreas = {
            {from = Position(1070, 928, 7), to = Position(1075, 928, 7)},
            {from = Position(1070, 928, 7), to = Position(1075, 928, 7)},
            {from = Position(1072, 935, 7), to = Position(1074, 937, 7)}
        },
        waves = {
            { -- wave 1
                broadcast = {messageType = MESSAGE_STATUS_WARNING, text = "Esqueletos is starting in 60 seconds."}
            },
            { -- wave 2
                broadcast = {messageType = MESSAGE_STATUS_WARNING, text = "Wave example with RANDOM monsters only"},
                randomMonsters = {
                    {
                        amount = {min = 5, max = 20},
                        monsterList = {"rata", "rata cavernaria", "trasgo"},
                        spawnAreas = {
                            {from = Position(1069, 930, 7), to = Position(1077, 942, 7)}
                        }
                    },
                    {
                        amount = {min = 20, max = 20},
                        monsterList = {"gusano"},
                        spawnAreas = {
                            {from = Position(1069, 930, 7), to = Position(1077, 942, 7)}
                        }
                    },
                },
                nextWaveTimer = 10
            },
            { -- wave 3
                broadcast = {messageType = MESSAGE_STATUS_WARNING, text = "Wave example with STATIC monsters only"},
                staticMonsters = {
                    {position = Position(1073, 933, 7), name = "ciclope"},
                    {position = Position(1073, 939, 7), name = "dragon"}
                },
                nextWaveTimer = 10
            },
            { -- wave 4
                broadcast = {messageType = MESSAGE_STATUS_WARNING, text = "Wave example with both STATIC & RANDOM monsters"},
                staticMonsters = {
                    {position = Position(1007, 1015, 7), name = "dragon supremo"}
                },
                randomMonsters = {
                    {
                        amount = {min = 20, max = 20},
                        monsterList = {"bicho", "lobo", "oso"},
                        spawnAreas = {
                            {from = Position(1069, 930, 7), to = Position(1077, 942, 7)}
                        }
                    }
                },
                nextWaveTimer = 10
            },
            { -- wave 5
                broadcast = {messageType = MESSAGE_STATUS_WARNING, text = "Wave example Boss?"},
                staticMonsters = {
                    {position = Position(1073, 939, 7), name = "dragon"}
                }
            }
        }
    }
}

It triggers, but this start automatically after sending the raid. Same situation for programmed raids than inmediate raids ^^ I'll be attentive to further tests, regards!
 
@Xikini testing logs of raid system. First try:

My raid configuration:
Lua:
local raidStorageKey = 45026 -- used to track what raid monsters were spawned in
local nonSpawnableItemTiles = {1510, 1511, 1513, 2579, 4208, 25331} -- if these items are found on a tile, don't allow monsters to spawn on them (currently just traps)

local raids = {

    {    raid = {
            name = "Minotauros", -- must be unique to this raid, or it'll show error in console
            specificTimes = {
                -- "hh:mm:ss", -- start raid at specified times, based on local computer clock (ignores startDelay/interval/chance et cetera.)
                -- "07:00:00", -- 7am
                -- "19:00:00", -- 7pm
                -- "20:30:24" -- 8h 30m 24s pm
            },
            startDelay = 30, -- minutes -- how long after starting server before the raid can execute
            interval = 15, -- minutes -- how long inbetween attempts at spawning the raid.
            chance = 5000, -- chance / 10000 -- so 100 chance = 1% | 1 chance = 0.01%
            forceAfterAttempts = 20, -- if raid is unlucky, force the raid to start after x attempts.
            --> (5 minutes * 20 attempts -> 100 minutes of attempts. force on attempt 21)
            --> -1 = never force -- so it is completely random chance
            --> 0 = force on first attempt (aka: every x minutes)
            cooldown = 120, -- minutes -- how long before this raid can be triggered again
            forceNewRaidInstance = 1 -- -1 = ignore 0 = false, 1 = true
            --> if true, and raid attempts to execute again, will remove all previous raid monsters.
            --> if false, will not attempt to execute until all previous raid monsters have been eliminated
            --> if ignore, will not remove previous raid monsters, and will just proceed as if raid was occurring for the first time.
        },       
        nonSpawnableAreas = { -- for any areas you don't want raid monsters to spawn
            {from = Position(1118, 981, 7), to = Position(1120, 983, 7)}, -- from = top left corner | to = bottom right corner
            --{from = Position(966, 986, 7), to = Position(978, 996, 7)}
        },
        waves = {
            {
                broadcast = {messageType = MESSAGE_STATUS_WARNING, text = "Raid is starting soon in some place"},
                nextWaveTimer = 10 -- seconds (if not included, defaults to 60 seconds)
            },
            { -- wave 1 (example with RANDOM monsters only)
                randomMonsters = {
                    {
                        amount = {min = 5, max = 20},
                        monsterList = {"minotauro", "lobo", "trasgo"},
                        spawnAreas = {
                            {from = Position(1107, 979, 7), to = Position(1121, 992, 7)}
                        }
                    },
                    {
                        amount = {min = 20, max = 20},
                        monsterList = {"gusano"},
                        spawnAreas = {
                            {from = Position(1107, 979, 7), to = Position(1121, 992, 7)}, -- from = top left corner | to = bottom right corner
                            {from = Position(1107, 979, 6), to = Position(1121, 992, 6)}  -- having 2 different z positions, allows for multiple floor spawning
                        }
                    },
                },
                nextWaveTimer = 10 -- seconds
            },
            { -- wave 2 (example with STATIC monsters only)
                staticMonsters = {
                    {position = Position(1111, 986, 7), name = "ciclope"}, -- since these are setup manually, we don't do additional checks. We assume it's a good spawn location and force it
                    {position = Position(1119, 986, 7), name = "dragon"}
                },
                nextWaveTimer = 10 -- seconds
            },
            { -- wave 3 (example with both STATIC & RANDOM monsters)
                staticMonsters = {
                    {position = Position(1115, 991, 7), name = "dragon lord"}
                },
                randomMonsters = {
                    {
                        amount = {min = 20, max = 20},
                        monsterList = {"avispa", "lobo", "oso"},
                        spawnAreas = {
                            {from = Position(1107, 979, 7), to = Position(1121, 992, 7)},
                            {from = Position(1107, 979, 6), to = Position(1121, 992, 6)}
                        }
                    }
                },
                nextWaveTimer = 10 -- seconds
            },
            { -- wave 4 (example Boss?)
                staticMonsters = {
                    {position = Position(1107, 986, 7), name = "demon"}
                }
            }
        }
    },
   
    {    raid = {
            name = "Esqueletos",
            specificTimes = {
                -- "hh:mm:ss"
                "07:00:00", -- 7am
                "19:00:00"  -- 7pm
            },
            startDelay = 30,
            interval = 15,
            chance = 10000,
            forceAfterAttempts = 20,
            cooldown = 120,
            forceNewRaidInstance = -1
        },       
        nonSpawnableAreas = {
            {from = Position(1070, 928, 7), to = Position(1075, 928, 7)},
            {from = Position(1070, 928, 7), to = Position(1075, 928, 7)},
            {from = Position(1072, 935, 7), to = Position(1074, 937, 7)}
        },
        waves = {
            { -- wave 1
                broadcast = {messageType = MESSAGE_STATUS_WARNING, text = "Esqueletos is starting in 60 seconds."}
            },
            { -- wave 2
                broadcast = {messageType = MESSAGE_STATUS_WARNING, text = "Wave example with RANDOM monsters only"},
                randomMonsters = {
                    {
                        amount = {min = 5, max = 20},
                        monsterList = {"rata", "rata cavernaria", "trasgo"},
                        spawnAreas = {
                            {from = Position(1069, 930, 7), to = Position(1077, 942, 7)}
                        }
                    },
                    {
                        amount = {min = 20, max = 20},
                        monsterList = {"gusano"},
                        spawnAreas = {
                            {from = Position(1069, 930, 7), to = Position(1077, 942, 7)}
                        }
                    },
                },
                nextWaveTimer = 10
            },
            { -- wave 3
                broadcast = {messageType = MESSAGE_STATUS_WARNING, text = "Wave example with STATIC monsters only"},
                staticMonsters = {
                    {position = Position(1073, 933, 7), name = "ciclope"},
                    {position = Position(1073, 939, 7), name = "dragon"}
                },
                nextWaveTimer = 10
            },
            { -- wave 4
                broadcast = {messageType = MESSAGE_STATUS_WARNING, text = "Wave example with both STATIC & RANDOM monsters"},
                staticMonsters = {
                    {position = Position(1007, 1015, 7), name = "dragon supremo"}
                },
                randomMonsters = {
                    {
                        amount = {min = 20, max = 20},
                        monsterList = {"bicho", "lobo", "oso"},
                        spawnAreas = {
                            {from = Position(1069, 930, 7), to = Position(1077, 942, 7)}
                        }
                    }
                },
                nextWaveTimer = 10
            },
            { -- wave 5
                broadcast = {messageType = MESSAGE_STATUS_WARNING, text = "Wave example Boss?"},
                staticMonsters = {
                    {position = Position(1073, 939, 7), name = "dragon"}
                }
            }
        }
    }
}

It triggers, but this start automatically after sending the raid. Same situation for programmed raids than inmediate raids ^^ I'll be attentive to further tests, regards!

You didn't install everything.
Check the custom lib.

 
You didn't install everything.
Check the custom lib.

lol I must be sleepy forgot to register at core.lua. Amazing system:
1707991534104.png
1707991549795.png
1707991570392.png
1707992262591.png

Also triggered a raid when one was occuring. It removes all the creatures without problems. It also waits till' all the waves are thrown before cancelling the raid, works perfectly. The only thing i'm missing is how to remove all monsters and don't send the raid again? I mean just stop it in case this accidentally provokes too many player deaths or something (like a panic talkaction to avoid a disaster).

Thanks a lot for the script, I really appreciate it bro!
So this ties in as a good example case of another script I just made.
and nimbus bot did a decent job.

View attachment 82146

data/scripts/onDeath_giveRewardBasedOnContribution.lua
Lua:
local monsters = {
--  ["monster name in lowercase letters"]
    ["troll"] = {itemId = 2160, dropAmount = 100, minimumAmount = 2} -- minimumAmount basically ensures that everyone get's SOMETHING if they helped. But can set to 0, so if they don't help enough, they get nothing.
}

local function getItemNameString(item)
    return item:getNameDescription(item:getSubType(), true)
end

local creatureevent = CreatureEvent("onDeath_GiveRewardToAllWhoDamaged")

function creatureevent.onDeath(creature, corpse, killer, mostDamageKiller, lastHitUnjustified, mostDamageUnjustified)

    local monsterName = creature:getName():lower()
    local bossDamageMap = {}
    local totalDamage = 0
  
    for creatureId, damage in pairs(creature:getDamageMap()) do
        totalDamage = totalDamage + damage.total
        local _creature = Creature(creatureId)
        if _creature then
            local master = _creature:getMaster()
            if master and master:isPlayer() then
                _creature = master
            end
            if _creature:isPlayer() then
                local playerId = _creature:getId()
                bossDamageMap[playerId] = (bossDamageMap[playerId] or 0) + damage.total
            end
        end
    end
  
    local monster = monsters[monsterName]
    for playerId, _ in pairs(bossDamageMap) do
        local player = Player(playerId)
        if player then      
            local damage = bossDamageMap[playerId]
            local lootAmount = math.max(math.floor((damage / totalDamage) * monster.dropAmount), monster.minimumAmount)
            if lootAmount > 0 then
                local rewardItem = Game.createItem(monster.itemId, lootAmount)
                player:addItemEx(rewardItem, true)
                doPlayerSendTextMessage(player, MESSAGE_EVENT_ADVANCE, "You received " .. getItemNameString(rewardItem) .. " for your contribution!")
            end
        end
    end
    return true
end

creatureevent:register()


local eventCallback = EventCallback

function eventCallback.onSpawn(creature, position, startup, artificial)
    if monsters[creature:getName():lower()] then
        creature:registerEvent("onDeath_GiveRewardToAllWhoDamaged")
    end
    return true
end

eventCallback:register(-666)
Going to test this one asap! The backpack one I asked before had me with some troubles, i'll try to test it again and post the feed. :p
 
The only thing i'm missing is how to remove all monsters and don't send the raid again? I mean just stop it in case this accidentally provokes too many player deaths or something (like a panic talkaction to avoid a disaster).

:p

!executeRaid raidName, cancel

Go find script from post again. xD

bandicam2024-02-1505-31-56-177-ezgif.com-video-to-gif-converter.gif
 
Back
Top