[OTClient] Generate full minimap for your server

Example for Tibia version 8.54

TO GENERATE MINIMAP FOR BIG MAPS – LIKE RL TIBIA – YOU NEED TO COMPILE 64-bit VERSION OF OTCLIENT!
Loading big map requires few GB RAM. 32-bit (x86) version of OTClient can load maps up to ~40MB OTBM. Using it with bigger map will result in not loading whole map -> there will be places missing on minimap!

Put client Tibia.spr and Tibia.dat files in data/things/854/

Put server files items.otb, world.otbm (map) and map .xml files in data/things/854/

Run OTClient and open Terminal (CTRL+T in client). Type:

g_game.setClientVersion(854)
g_things.loadOtb('things/854/items.otb')
g_map.loadOtbm('things/854/world.otbm')
g_minimap.saveOtmm('minimap.otmm')

Close client.

Your minimap file will be in folder:

  • Windows: %HOMEPATH%/.otclient (paste it as path to folder in Windows Explorer)
  • Linux: $HOME/.otclient (cd to it)

Copy it to data folder of OTClient. OTClient searches for files first in this folder. That way you can share it with players as part of your client.

[OTClient] Compile on Linux Ubuntu 18.04

Install required libraries:

sudo apt-get install -y build-essential cmake git-core libboost-all-dev libphysfs-dev libssl-dev liblua5.1-0-dev libglew-dev libvorbis-dev libopenal-dev zlib1g-dev g++ make cmake automake libogg-dev libncurses5-dev mercurial

Replace physfs from Ubuntu repository to version that works:

hg clone -r stable-3.0 http://hg.icculus.org/icculus/physfs/ && cd physfs && mkdir build && cd build && cmake .. && make && sudo make install && sudo mv /usr/local/lib/libphysfs.a /usr/lib/x86_64-linux-gnu/.

Download OTClient:

git clone git://github.com/edubart/otclient.git

Go into otclient directory:

cd otclient

Edit file CMakesList.txt
Find:

set(CMAKE_CXX_LINK_FLAGS "${CMAKE_CXX_LINK_FLAGS} -Wl,-Map=${PROJECT_NAME}.map")

Replace with:

set(CMAKE_CXX_LINK_FLAGS "${CMAKE_CXX_LINK_FLAGS} -fPIC -no-pie -Wl,-Map=${PROJECT_NAME}.map")

Compile OTClient:

mkdir -p build && cd build && cmake .. && make

DONE! You can run OTClient (it’s in build directory):

./otclient

TFS – Jak zrobić skrypt Loop, żeby wykonywał się co sekundę przez określoną w skrypcie ilość razy?

W językach skryptowych nie ma znanego z innych języków polecenia ‘sleep’, żeby poczekać np. sekundę – to zatrzymało by działanie całego serwera gry.
W takich językach najczęściej dostępne są funkcje do ‘planowania zdarzeń’ – to znaczy specjalna lista gdzie są spisane funkcje i czas po jakim mają być wykonane.

Przykład w LUA w serwerze gry TFS 1.3 (funkcja do tego służąca: addEvent ).
Do funkcji addEvent można przekazywać parametry, ale nie mogą to być obiekty zdefiniowane w silniku TFS jak Player, Creature, Item itp. – może to doprowadzić do crasha serwera i każde takie użycie generuje komunikat w konsoli.

Ta funkcja (akcja do dodania w actions) wykona się 3 sekundy po użyciu przedmiotu i wyświetli animacje ‘POFF’ na pozycji gracza.
Zamiast przekazywać jako parametr gracza (obiekt Player), przekazujemy jego ID (numer), a w momencie wykonania ‘zdarzenia’ zamieniamy go ponownie na obiekt Player.

function wyslijAnimacjeNaPozycjeGracza(playerId)
   -- zamieniamy liczbę playerId w obiekt player
   local player = Player(playerId)
   -- UWAGA: gracz moze w tym czasie zginac/wylogowac sie, wiec zawsze sprawdzamy czy udalo sie utworzyc obiekt 'player'
   if player then
      player:getPosition():sendMagicEffect(CONST_ME_POFF)
   end
end

function onUse(player, item, fromPosition, target, toPosition, isHotkey)
   addEvent(wyslijAnimacjeNaPozycjeGraczaZaSekunde, 3000, player:getId())
   return true
end

Ta funkcja zostanie wykonana 2 razy w odstępach sekundy:

function twojaFunkcjaDoWykonaniaKilkaRazy(iloscPowtorzen, czasMiedzyPowtorzeniami)
    -- kod odpowiadajacy za powtarzanie
    iloscPowtorzen = iloscPowtorzen - 1
    if (iloscPowtorzen > 0 ) then
        -- parametry przekazywane do funkcji 'addEvent' to kolejno:
        -- 1: nazwa funkcji ktora ma sie wykonac
        -- 2: czas po jakim ma sie wykonac, 1000 = 1 sekunda
        -- 3 i kolejne - sa opcjonalne - wszystkie kolejne parametry zostana przekazane do wykonywanej funkcji w takiej kolejnosci jak zostaly podane
        addEvent(twojaFunkcjaDoWykonaniaKilkaRazy, czasMiedzyPowtorzeniami, iloscPowtorzen, czasMiedzyPowtorzeniami)
    end

    -- kod ktory ma sie wykonywac (twoj wlasny):
    print('zostalo powtorzen:', iloscPowtorzen)
end

twojaFunkcjaDoWykonaniaKilkaRazy(2, 1000, 'testowy test', 12345)

Przykład z tą samą funkcją, ale teraz z przekazaniem parametrów. W funkcji wykonywanej ‘później’ nie masz dostępu do zmiennych które są dostępne w ‘aktualnie’ wykonywanym skrypcie (tym który ‘planuje’ wykonanie funkcji), więc trzeba je przekazać.

function twojaFunkcjaDoWykonaniaKilkaRazyZParametrami(iloscPowtorzen, czasMiedzyPowtorzeniami, parametr1, parametr2)
   -- kod odpowiadajacy za powtarzanie
   iloscPowtorzen = iloscPowtorzen - 1
   if (iloscPowtorzen > 0 ) then
      addEvent(twojaFunkcjaDoWykonaniaKilkaRazyZParametrami, czasMiedzyPowtorzeniami, iloscPowtorzen, czasMiedzyPowtorzeniami, parametr1, parametr2)
   end

   -- kod ktory ma sie wykonywac:
   print('parametr 1 ma wartosc:', parametr1)
   print('parametr 2 ma wartosc:', parametr2)
end

twojaFunkcjaDoWykonaniaKilkaRazyZParametrami(5, 1000, 'testowy test', 12345)

OTC Module – Debug UI

When you create new module, you always got problems with OTUI design. Why X element is on that position? What am I clicking?

I can’t solve all these problems, but my module can help you debug your UI without milion prints to console.

With this module you can easily view tree of elements on given position (under mouse cursor):

It also works before login:

modules/client_ui_debug/client_ui_debug.otmod

Module
  name: client_ui_debug
  description: Draws tree of widgets on mouse position
  author: Gesior.pl
  reloadable: false
  sandboxed: true
  scripts: [ client_ui_debug ]
  @onLoad: init()
  @onUnload: terminate()

modules/client_ui_debug/client_ui_debug.otui

HighlightWidget < Panel
  id: highlightWidget
  background-color: #AA000099
  focusable: false
  phantom: true

Panel
  id: clientUiDebug
  anchors.top: parent.top
  anchors.left: parent.left
  anchors.right: parent.right
  height: 20
  margin-top: 1
  focusable: false

  UILabel
    id: clientUiDebugLabel
    color: #FF7777
    background-color: #00000099
    anchors.fill: parent
    text-align: left
    text-auto-resize: false
    padding: 2
    font: verdana-11px-antialised

modules/client_ui_debug/client_ui_debug.lua

local clientUiDebug
local clientUiDebugLabel
local clientUiDebugHighlightWidget

function onClientUiDebuggerMouseMove(mouseBindWidget, mousePos, mouseMove)
    local widgets = rootWidget:recursiveGetChildrenByPos(mousePos)

    local smallestWidget
    for _, widget in pairs(widgets) do
        if (widget:getId() ~= 'highlightWidget' and widget:getId() ~= 'toolTip') then
            if (not smallestWidget or
                    (widget:getSize().width <= smallestWidget:getSize().width and widget:getSize().height <= smallestWidget:getSize().height)
            ) then
                smallestWidget = widget
            end
        end
    end
    if smallestWidget then
        clientUiDebugHighlightWidget:setPosition(smallestWidget:getPosition())
        clientUiDebugHighlightWidget:setSize(smallestWidget:getSize())
        clientUiDebugHighlightWidget:raise()
    end

    local widgetNames = {}
    for wi = #widgets, 1, -1 do
        local widget = widgets[wi]
        if (widget:getId() ~= 'highlightWidget') then
            table.insert(widgetNames, widget:getClassName() .. '#' .. widget:getId())
        end
    end
    clientUiDebugLabel:setText(table.concat(widgetNames, " -> "))
end

function init()
    connect(rootWidget, {
        onMouseMove = onClientUiDebuggerMouseMove,
    })
    clientUiDebug = g_ui.displayUI('client_ui_debug')
    clientUiDebugLabel = clientUiDebug:getChildById('clientUiDebugLabel')
    clientUiDebugHighlightWidget = g_ui.createWidget('HighlightWidget', rootWidget)
end

function terminate()
    disconnect(rootWidget, {
        onMouseMove = onClientUiDebuggerMouseMove,
    })
    clientUiDebug:destroy()
    clientUiDebugHighlightWidget:destroy()
end

at end of modules list in modules/client/client.otmod add:

- client_ui_debug

Autowalk/cavebot for OTClient

Someone asked me about script that will make character run to given position and after reaching it run to another. I made it for free, so I decided to publish it as it may be interesting for others.

It’s not ready-to-run module, but I think it’s very close. For tests I pasted parts of it to ‘battle’ module and it worked.

This code requires ‘BOT_PROTECTION’ disabled in OTClient.

local walkEvent = nil
local positionsList = {
    { x = 32608, y = 32131, z = 7 },
    { x = 32601, y = 32131, z = 7 },
    { x = 32601, y = 32138, z = 7 },
    { x = 32608, y = 32138, z = 7 },
}

local isAttacking = 0
local isFollowing = 0
local currentTargetPositionId = 1

local autowalkTargetPosition = positionsList[currentTargetPositionId]

function init()
    connect(LocalPlayer, {
        onPositionChange = onCreaturePositionChange
    })

    connect(g_game, {
        onAttackingCreatureChange = onAttack,
        onFollowingCreatureChange = onFollow,
        onDisappear = onCreatureDisappear
    })
end

function terminate()
    disconnect(LocalPlayer, {
        onPositionChange = onCreaturePositionChange
    })

    disconnect(g_game, {
        onAttackingCreatureChange = onAttack,
        onFollowingCreatureChange = onFollow,
        onDisappear = onCreatureDisappear
    })
end

function walkToTarget()
    if not g_game.isOnline() then
        walkEvent = scheduleEvent(walkToTarget, 500)
        return
    end

    if g_game.getLocalPlayer():getStepTicksLeft() > 0 then
        -- wait until walk animation finish
        walkEvent = scheduleEvent(walkToTarget, g_game.getLocalPlayer():getStepTicksLeft())
        return
    end

    if isAttacking or isFollowing or not autowalkTargetPosition then
        -- do not walk with selected attack/follow target or when there is no target position
        walkEvent = scheduleEvent(walkToTarget, 100)
        return
    end

    -- fast search path on minimap (known tiles)
    steps, result = g_map.findPath(g_game.getLocalPlayer():getPosition(), autowalkTargetPosition, 5000, 0)
    if result == PathFindResults.Ok then
        g_game.walk(steps[1], true)
    elseif result == PathFindResults.Position then
        -- on target position
        currentTargetPositionId = currentTargetPositionId + 1
        autowalkTargetPosition = positionsList[(currentTargetPositionId % #positionsList) + 1]
    else
        -- slow search path on minimap, if not found, start 'scanning' map
        steps, result = g_map.findPath(g_game.getLocalPlayer():getPosition(), autowalkTargetPosition, 25000, 1)
        if result == PathFindResults.Ok then
            g_game.walk(steps[1], true)
        end
    end

    -- limit steps to 10 per second (100 ms between steps)
    walkEvent = scheduleEvent(walkToTarget, math.max(100, g_game.getLocalPlayer():getStepTicksLeft()))
end

function onCreaturePositionChange(creature, newPos, oldPos)
    if not walkEvent then
        -- start automoving 1 second after logging into game
        walkEvent = scheduleEvent(walkToTarget, 1000)
    end
end

function onAttack(creature)
    isAttacking = creature and creature:getId() or 0
end

function onFollow(creature)
    isFollowing = creature and creature:getId() or 0
end

function onCreatureDisappear(creature)
    if isAttacking == creature:getId() then
        isAttacking = 0
    end
    if isFollowing == creature:getId() then
        isFollowing = 0
    end
end