Forgottenserver: Revscriptsys

Created on 27 Mar 2019  路  15Comments  路  Source: otland/forgottenserver

I didn't find this to be on a TODO list even not for 2.0
I'm wondering if there is still any kind of interest in it.
Summ and I have been working on it years ago.
It's an easy drag and drop system where the lua files get load on startup (or on occasional load)
It doesn't break backwards compatibility and works normaly alongside the other xml stuff.
It would give the ability to write long quests and such easily in one file for organisation manner.
I'll work towards a PR if there's some real interest, else the time ammount to rebase this on newest sources wouldn't be worth it.

It invokes metatables for:

  • Action()
  • CreatureEvent()
  • GlobalEvent()
  • MoveEvent()
  • TalkAction()
  • Weapon()
  • Spell()

with their corresponding functions to be able to set them up properly.

Supports:

  • [x] Actions
  • [x] Creaturescripts
  • [x] Globalevents
  • [x] Movements
  • [x] Talkactions
  • [x] Monsters
  • [x] Weapons (as of #2571)
  • [x] Spells (as of #2584)

Delayed for 1.4/2.0:

  • [ ] NPC (Extensive change and lots of testing required)

Here are some working examples:

Action

local shovel = Action()

local holes = {468, 481, 483}
function shovel.onUse(player, item, fromPosition, target, toPosition, isHotkey)
    if toPosition.x == CONTAINER_POSITION then
        return false
    end

    local tile = Tile(toPosition)
    if not tile then
        return false
    end

    local ground = tile:getGround()
    if not ground then
        return false
    end

    local groundId = ground:getId()
    if isInArray(holes, groundId) then
        ground:transform(groundId + 1)
        ground:decay()

        toPosition.z = toPosition.z + 1
        tile:relocateTo(toPosition)
    elseif groundId == 231 then
        local randomValue = math.random(1, 100)
        if randomValue == 1 then
            Game.createItem(2159, 1, toPosition)
        elseif randomValue > 95 then
            Game.createMonster("Scarab", toPosition)
        end
        toPosition:sendMagicEffect(CONST_ME_POFF)
    else
        return false
    end

    return true
end

shovel:id(2554)
shovel:register()

Globalevents

local shutdownAtServerSave = false
local cleanMapAtServerSave = false

local function serverSave()
    if shutdownAtServerSave then
        Game.setGameState(GAME_STATE_SHUTDOWN)
    else
        Game.setGameState(GAME_STATE_CLOSED)

        if cleanMapAtServerSave then
            cleanMap()
        end

        Game.setGameState(GAME_STATE_NORMAL)
    end
end

local function secondServerSaveWarning()
    broadcastMessage("Server is saving game in one minute. Please logout.", MESSAGE_STATUS_WARNING)
    addEvent(serverSave, 60000)
end

local function firstServerSaveWarning()
    broadcastMessage("Server is saving game in 3 minutes. Please logout.", MESSAGE_STATUS_WARNING)
    addEvent(secondServerSaveWarning, 120000)
end

local event = GlobalEvent("Server Save")

function event.onTime(interval)
    broadcastMessage("Server is saving game in 5 minutes. Please logout.", MESSAGE_STATUS_WARNING)
    Game.setGameState(GAME_STATE_STARTUP)
    addEvent(firstServerSaveWarning, 120000)
    return not shutdownAtServerSave
end

event:time("09:55:00")
event:register()

Creaturescripts

local login = CreatureEvent("PlayerLogin")

function login.onLogin(player)
    local loginStr = "Welcome to " .. configManager.getString(configKeys.SERVER_NAME) .. "!"
    if player:getLastLoginSaved() <= 0 then
        loginStr = loginStr .. " Please choose your outfit."
        player:sendOutfitWindow()
    else
        if loginStr ~= "" then
            player:sendTextMessage(MESSAGE_STATUS_DEFAULT, loginStr)
        end

        loginStr = string.format("Your last visit was on %s.", os.date("%a %b %d %X %Y", player:getLastLoginSaved()))
    end
    player:sendTextMessage(MESSAGE_STATUS_DEFAULT, loginStr)

    -- Stamina
    nextUseStaminaTime[player.uid] = 0

    -- Promotion
    local vocation = player:getVocation()
    local promotion = vocation:getPromotion()
    if player:isPremium() then
        local value = player:getStorageValue(STORAGEVALUE_PROMOTION)
        if not promotion and value ~= 1 then
            player:setStorageValue(STORAGEVALUE_PROMOTION, 1)
        elseif value == 1 then
            player:setVocation(promotion)
        end
    elseif not promotion then
        player:setVocation(vocation:getDemotion())
    end

    -- Events
    player:registerEvent("PlayerDeath")
    player:registerEvent("DropLoot")
    return true
end

login:register()

Talkactions

local talk = TalkAction("/pos")

function talk.onSay(player, words, param)
    if player:getGroup():getAccess() and param ~= "" then
        local split = param:split(",")
        player:teleportTo(Position(split[1], split[2], split[3]))
    else
        local position = player:getPosition()
        player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Your current position is: " .. position.x .. ", " .. position.y .. ", " .. position.z .. ".")
    end
    return false
end

talk:separator(" ")
talk:register()

Movements (stepin/stepout/additem/removeitem) with pre defined items.xml values

local campfireStepIn = MoveEvent()
campfireStepIn:type("stepin")
campfireStepIn:id(1423, 1424, 1425)
campfireStepIn:register()

local campfireAddItem = MoveEvent()
campfireAddItem:type("additem")
campfireAddItem:id(1423, 1424, 1425)
campfireAddItem:register()

Movements (stepin/stepout/additem/removeitem) with a lua script return value

local increasing = {[416] = 417, [426] = 425, [446] = 447, [3216] = 3217, [3202] = 3215, [11062] = 11063}
local decreasing = {[417] = 416, [425] = 426, [447] = 446, [3217] = 3216, [3215] = 3202, [11063] = 11062}

local tileStepIn = MoveEvent()

function tileStepIn.onStepIn(creature, item, position, fromPosition)
    if not increasing[item.itemid] then
        return true
    end

    local player = creature:getPlayer()
    if player == nil or player:isInGhostMode() then
        return true
    end

    item:transform(increasing[item.itemid])

    if item.actionid >= 1000 then
        if player:getLevel() < item.actionid - 1000 then
            player:teleportTo(fromPosition, false)
            position:sendMagicEffect(CONST_ME_MAGIC_BLUE)
            player:sendTextMessage(MESSAGE_INFO_DESCR, "The tile seems to be protected against unwanted intruders.")
        end
        return true
    end

    if Tile(position):hasFlag(TILESTATE_PROTECTIONZONE) then
        local lookPosition = player:getPosition()
        lookPosition:getNextPosition(player:getDirection())
        local depotItem = Tile(lookPosition):getItemByType(ITEM_TYPE_DEPOT)
        if depotItem ~= nil then
            local depotItems = player:getDepotChest(getDepotId(depotItem:getUniqueId()), true):getItemHoldingCount()
            player:sendTextMessage(MESSAGE_STATUS_DEFAULT, "Your depot contains " .. depotItems .. " item" .. (depotItems > 1 and "s." or "."))
            return true
        end
    end

    if item.actionid ~= 0 and player:getStorageValue(item.actionid) <= 0 then
        player:teleportTo(fromPosition, false)
        position:sendMagicEffect(CONST_ME_MAGIC_BLUE)
        player:sendTextMessage(MESSAGE_INFO_DESCR, "The tile seems to be protected against unwanted intruders.")
        return true
    end
    return true
end

for k, v in pairs(increasing) do
    tileStepIn:id(k)
end
tileStepIn:register()

local tileStepOut = MoveEvent()

function tileStepOut.onStepOut(creature, item, position, fromPosition)
    if not decreasing[item.itemid] then
        return true
    end

    if creature:isPlayer() and creature:isInGhostMode() then
        return true
    end

    item:transform(decreasing[item.itemid])
    return true
end

for k, v in pairs(decreasing) do
    tileStepOut:id(k)
end
tileStepOut:register()

Movements (equip/deequip) with pre defined like movements.xml values

-- Equip armor(s) for sorceres and druids at any level
local equip = MoveEvent()
equip.onEquip = defaultEquip
equip:type("equip")
equip:slot("armor")
equip:vocation("sorcerer", true, false) -- showInDescription / lastVoc
equip:vocation("master sorcerer")
equip:vocation("druid", true, true)
equip:vocation("elder druid")
equip:id(8819, 8892, 8870, 8871)
equip:register()

Movements (equip/deequip) with lua script attached and combination of pre defined values

-- Equip armor(s) for sorceres and druids at any level
local equip = MoveEvent()
function equip.onEquip(player, item, slot)
    print("I do work")
    return true
end
equip:type("equip")
equip:slot("armor")
equip:vocation("sorcerer", true, false) -- showInDescription / lastVoc
equip:vocation("master sorcerer")
equip:vocation("druid", true, true)
equip:vocation("elder druid")
equip:id(8819, 8892, 8870, 8871)
equip:register()
feature

Most helpful comment

The last part of "revscriptsys" core: NPC's has been postponed for a future release due to alot of work needed to get it done, alot of testing etc. That means this issue as of now is completed, and a new issue will be created for future milestones. This issue represents what to expect in 1.3.

Beside the NPC components, revscriptsys is working, and its pretty awesome! Lets give it a good go, iron out the last bugs should we find them, and tag a release soon!

All 15 comments

Nice. 馃憤
This looks like the correct way of doing mods? (The obsolete XML pack interface used in some 0.4 distro was just messy).

Could you plop in a startup script that register multiple different actions in one file?

Edit: Just realized the mods system I was referring to was made by @slawkens. No offense meant, your system was innovative and awesome at the time. Just bothersome to debug as most code color highlight editors struggled separating XML and Lua scopes in the same file.

Yes that's mostly what it is meant for, so you don't have to split different or same actions into different files.
Which makes quests like POI and such a lot easier to manage.

It's not in the roadmap for 2.0 but a lengthy discussion about this topic exists in issue #1057. Based on that issue it seems like such a pull request would be welcomed.

Honestly, this idea is amazing. I think that pugixml now will be only for XML files, for example (items.xml, the XML folder etc ...)

question: how I can register dynamically action ids? for examples: new doors don't work with default script actions/scripts/others/doors.lua, because the difference of open door and the closed door is not one. 12356 (open door), 12357 (closed door). So I tried to create a key-values table to map and transform the item and make flatten unique table with keys and values to register the action. but Action():id(...) requires varargs and not a table

Either call action:id(id) multiple times for each id you want to register or action:id(unpack(yourTableWithIds)) where yourTableWithIds is something like {12356, 12357}

thanks, Summ! got it!
i have refactored some doors systems using script and looks pretty good.

https://github.com/gpedro/OTServBR-Global/blob/fix/doors/data/scripts/actions/newdoors.lua#L30-L32

Monster is not working.

Lua Script Error: [Scripts Interface]
D:\tfs\data\scripts\monsters\example.lua
D:\tfs\data\scripts\monsters\example.lua:90: attempt to index local 'mType' (a nil value)
stack traceback:
        [C]: in function '__index'
        D:\tfs\\data\scripts\monsters\example.lua:90: in main chunk
> example.lua [error]

image

I noticed something wrong with the formula.

Monster is not working.

Lua Script Error: [Scripts Interface]
D:\tfs\data\scripts\monsters\example.lua
D:\tfs\data\scripts\monsters\example.lua:90: attempt to index local 'mType' (a nil value)
stack traceback:
        [C]: in function '__index'
        D:\tfs\\data\scripts\monsters\example.lua:90: in main chunk
> example.lua [error]

You can use Game.createMonsterType(...)

image

I noticed something wrong with the formula.

The formula is exactly the same as the default one, maybe your monster has fire % resistence.

Monster is not working.

Lua Script Error: [Scripts Interface]
D:\tfs\data\scripts\monsters\example.lua
D:\tfs\data\scripts\monsters\example.lua:90: attempt to index local 'mType' (a nil value)
stack traceback:
        [C]: in function '__index'
        D:\tfs\\data\scripts\monsters\example.lua:90: in main chunk
> example.lua [error]

You can use Game.createMonsterType(...)

image
I noticed something wrong with the formula.

The formula is exactly the same as the default one, maybe your monster has fire % resistence.

Not a monster, it's in the player

Edit

The Game.createMonsterType solved the problem of monster, thanks

image

Does not it work in a similar way to the formula of monsters elements? Because the base is 100% and -100 (positive damage) and 100+ (negative damage)?
I noticed that by changing the number to 170/200 the damage was in 80/100 lathes, but not less than 80

Does not it work in a similar way to the formula of monsters elements? Because the base is 100% and -100 (positive damage) and 100+ (negative damage)?
I noticed that by changing the number to 170/200 the damage was in 80/100 lathes, but not less than 80

players take half the supposed damage, theres nothing wrong.

ohh, sorry ... kkkkkkk
long time away from tibia

The last part of "revscriptsys" core: NPC's has been postponed for a future release due to alot of work needed to get it done, alot of testing etc. That means this issue as of now is completed, and a new issue will be created for future milestones. This issue represents what to expect in 1.3.

Beside the NPC components, revscriptsys is working, and its pretty awesome! Lets give it a good go, iron out the last bugs should we find them, and tag a release soon!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Olrios picture Olrios  路  4Comments

EPuncker picture EPuncker  路  3Comments

Imperians picture Imperians  路  4Comments

souzajunior picture souzajunior  路  4Comments

irenicus30 picture irenicus30  路  5Comments