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

MoveEvent Infinity Rooms - TFS 1.3

Sarah Wesker

ƐƖєgαηт Sуηтαx ❤
Staff member
TFS Developer
Support Team
Joined
Mar 16, 2017
Messages
1,408
Solutions
154
Reaction score
1,958
Location
London
GitHub
MillhioreBT
Twitch
millhiorebt
This was inspired by this thread.

data/scripts/infinityrooms.lua
Lua:
-- Infinity Room Config
local IRConfig = {
    startPos = Position(2887, 1756, 7), -- upper left corner
    tpInAid = 65000, -- actionid for enter teleport
    tpOutAid = 65001, -- actionid for exit teleport
    foodAid = 65002, -- actionid for infinity food (only work with EventCallback)
    maxRoomsPer = { -- 10 x 10 = 100 rooms max
        row = 10,
        col = 10
    },
    items = {
        {1050}, --horizontal wall
        {1049}, --vertical wall
        {1057}, --corner wall
        {1051}, --pole wall
        {426}, --tile train
        {407}, --tile base
        {407, 1484}, --coal basin
        {407, 1387, "teleport"}, --teleport
        {407, 1642, 7159, "foodhuge"}, --food panel
        {407, "Monk"} --training monster name
    },
    map = { -- room drawing mask
        {4,1,1,1,1},
        {2,10,7,10,2},
        {2,7,5,7,2},
        {2,8,6,9,2},
        {2,1,1,1,3}
    }
}

local IRoom, mapDistX, mapDistY = {}, #IRConfig.map[1], #IRConfig.map
IRoom.__index = IRoom
if not IRoomList then
    IRoomList = {}
end

function IRoom.new(pos, fromPos, index)
    local iroom = {}
    setmetatable(iroom, IRoom)
    iroom.pos = pos
    iroom.fromPos = fromPos
    for x = 1, mapDistX do
        for y = 1, mapDistY do
            local tilePos = Position(pos.x+x, pos.y+y, pos.z)
            local tileIndex = IRConfig.map[y][x]
            if tileIndex == 5 then iroom.center = tilePos end
            local lastItem
            for _, it in pairs(IRConfig.items[tileIndex]) do
                local thingType = type(it)
                if thingType == "number" then
                    lastItem = Game.createItem(it, 1, tilePos)
                    if not lastItem then
                        iroom:destroy()
                        debugPrint("[Warning - IRoom::new] the room could not be created correctly.")
                        return
                    end
                elseif thingType == "string" then
                    if it == "teleport" then
                        lastItem:setCustomAttribute("roomIndex", index)
                        lastItem:setAttribute(ITEM_ATTRIBUTE_ACTIONID, IRConfig.tpOutAid)
                    elseif it == "foodhuge" then
                        if EventCallback then
                            lastItem:setAttribute(ITEM_ATTRIBUTE_ACTIONID, IRConfig.foodAid)
                        end
                    else        
                        Game.createMonster(it, tilePos)
                    end
                end
            end
        end
    end
    IRoomList[index] = iroom
    return iroom
end

function IRoom:destroy()
    for x = 1, mapDistX do
        for y = 1, mapDistY do
            local pos = Position(self.pos.x+x, self.pos.y+y, self.pos.z)
            local tile = Tile(pos)
            if tile then
                local thingCount = tile:getThingCount()
                for index = thingCount, 0, -1 do
                    local thing = tile:getThing(index)
                    if thing then
                        if thing:isPlayer() then
                            thing:teleportTo(self.fromPos)
                        else
                            thing:remove()
                        end
                    end
                end
            end
        end
    end
    IRoomList[self.index] = nil
end

local function getNextPosition()
    local x, y, z = IRConfig.startPos.x, IRConfig.startPos.y, IRConfig.startPos.z
    local index, indey, pos = 1, 0, Position(x, y, z)
    for _, iroom in pairs(IRoomList) do
        if iroom.pos ~= pos then
            break
        else
            pos = Position(x + (mapDistX * index), y + (mapDistY * indey), z)
            index = index +1
            if index % IRConfig.maxRoomsPer.row == 0 then
                index, indey = 0, indey + 1
                if indey > IRConfig.maxRoomsPer.col then
                    return
                end
            end
        end
    end
    return pos, index
end

local moveOutTp = MoveEvent()
function moveOutTp.onStepIn(creature, item, position, fromPosition)
    local player = creature:getPlayer()
    if player then
        local roomIndex = item:getCustomAttribute("roomIndex")
        if roomIndex then
            local iroom = IRoomList[roomIndex]
            if iroom then
                player:teleportTo(iroom.fromPos)
                iroom.fromPos:sendMagicEffect(CONST_ME_TELEPORT)
                iroom:destroy()
                return true
            end
        end
    end
    return true
end
moveOutTp:aid(IRConfig.tpOutAid)
moveOutTp:register()

local moveInTp = MoveEvent()
function moveInTp.onStepIn(creature, item, position, fromPosition)
    local player = creature:getPlayer()
    if player then
        local pos, index = getNextPosition()
        if not pos then
            player:teleportTo(fromPosition, false)
            player:sendCancelMessage("There are no free training rooms, try later.")
            return true
        end
        local iroom = IRoom.new(pos, player:getTown():getTemplePosition(), index)
        if iroom then
            iroom.index = index
            player:teleportTo(iroom.center)
            iroom.center:sendMagicEffect(CONST_ME_TELEPORT)
        end
    end
    return true
end
moveInTp:aid(IRConfig.tpInAid)
moveInTp:register()

local foodAction = Action()
function foodAction.onUse(player, item, toPosition, target, fromPosition, isHotkey)
    local condition = player:getCondition(CONDITION_REGENERATION, CONDITIONID_DEFAULT)
    if condition and math.floor(condition:getTicks() / 1000 + (10 * 12)) >= 1200 then
        player:sendTextMessage(MESSAGE_STATUS_SMALL, "You are full.")
    else
        player:feed(120)
        player:say("Mmmm.", TALKTYPE_MONSTER_SAY)
    end
    return true
end
foodAction:aid(IRConfig.foodAid)
foodAction:register()

if EventCallback then
    local ec = EventCallback
    function ec.onMoveItem(player, item, count, fromPosition, toPosition, fromCylinder, toCylinder)
        if item:getActionId() == IRConfig.foodAid then
            return false
        end
        return true
    end
    ec:register(1)
end

In the configuration table you can establish the position from where the rooms are generated and you can also change the id of the items used, for example for the walls, tiles and everything that makes up the room, including the monster that you will use for the training.

Although the name refers to infinity, there is a limit, and that limit can be configured, by default I have left 10 rooms per columns and 10 columns, this means that a maximum of 100 rooms can be generated.

create the portal in your preferred place and set the actionid of this portal with the value stored in the variable: tpInAid = 65000 this will be the one you use to enter to train room

 
I really like this script and the idea, but I don't like servers that use this kind of training room because we can't even know how many players are training or not :(
 
Lua:
local function getNextPosition()
    local x, y, z = IRConfig.startPos.x, IRConfig.startPos.y, IRConfig.startPos.z
    local index, indey, pos = 1, 0, Position(x, y, z)
    for _, iroom in pairs(IRoomList) do
        if iroom.pos ~= pos then
            break
        else
            pos = Position(x + (mapDistX * index), y + (mapDistY * indey), z)
            index = index +1
            if index % IRConfig.maxRoomsPer.row == 0 then
                index, indey = 0, indey + 1
                if indey > IRConfig.maxRoomsPer.col then
                    return
                end
            end
        end
    end
    return pos, index
end

The above code's time complexity could be improved. It currently stands at
  1. O(1) time complexity for the best case (No one is training so it fetches the very first position).
  2. O(N*M) for the average/worst case, where N is the number of rows and M is the number of columns. (Assume the server has plenty of afk players training for hours. So half of the spots would be already filled up and this would require plenty of iterations to find the very first empty position).
  3. You don't really need to create the training rooms row-wise. As the user doesn't really care about where they're put, they only care about training. This could be exploited to drop the average/worst time complexity.
So the idea is pretty simple. We're going to exploit the last point where we don't really care about the training room position. We just need to find any empty place and find it quickly(Also make sure this empty place is within the N*M grid).

  1. On the initial load of the script. Fill up a table of size N*M, where each index corresponds to a training room. We'll call this array empty_rooms as it holds all of the currently free rooms where we can place a potential player.
  2. Once a player goes into the teleport to start training, fetch the last placed training room in empty_rooms which would be empty_rooms.remove() and remove it from the table. This operation would be O(1) since we're removing the last room and nothing is actually moved around.
  3. Once a player leaves the training room, fetch the training room coordinates(Either attach it to the player or heuristically find the nearest training room from the teleport position) and add this training room back again into the empty_rooms at the end. This is also an O(1) operation since we're pushing at the end of the table.
From the above 3 points. We achieve a worst/average case of O(1) and O(N*M) pre-processing which is actually fine since no-one is online during that time to feel the impact.

Thanks :D
 
Last edited:
Back
Top