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

[Tutorial] How to Display a Custom Image and Sounds on Screen Using a Client Mod (OTClient + TFS 1.4.2)

darcioantonio

www.adventurerpg.com.br
Joined
Jul 30, 2013
Messages
170
Solutions
1
Reaction score
16
Location
Brasil
GitHub
darcioantonio
Twitch
darcio_
YouTube
MundoOTServer
🔉: In this tutorial, I’ll show you how to create a client mod that allows the server to display custom images and play sounds on the player's screen using ExtendedOpcode. This is perfect for showing quest hints, warnings, or even fun animations and sound effects!

✅ Client-Side Setup​

  1. Create a mod folder:
    Inside your client folder, navigate to mods/ and create a new folder named mod_imagem
  2. Inside mod_imagem, add the following files:
    1749666901475.webp

mod_imagem.otmod​

LUA:
Module
  name: mod_imagem
  description: Displays an image when triggered by opcode
  author: YourName
  version: 1.0
  autoload: true
  sandboxed: true
  scripts: [ mod_imagem ]
  @onLoad: init()
  @onUnload: terminate()

mod_imagem.lua​

LUA:
local window = nil
local imageWidget = nil
local soundChannel = nil
local lastSoundName = nil
function init()
    g_ui.importStyle("mod_imagem.otui")
    ProtocolGame.registerExtendedOpcode(45, onExtendedOpcode)
    rootWidget:grabKeyboard()
    g_keyboard.bindKeyDown(KeyEscape, onKeyPress)
end
function terminate()
    ProtocolGame.unregisterExtendedOpcode(45)
    if window then
        window:destroy()
        window = nil
    end
    if soundChannel then
        soundChannel:stop()
        soundChannel = nil
    end
    rootWidget:ungrabKeyboard()
end
function onExtendedOpcode(protocol, opcode, buffer)
    if opcode ~= 45 then return end
    showImage(buffer)
end
function showImage(buffer)
    if window then
        window:destroy()
        window = nil
    end
    local imageName = nil
    local soundName = nil
    -- Se o buffer conter '=', tratamos como imagem=som ou só imagem
    if string.find(buffer, "=") then
        for part in string.gmatch(buffer, "([^,]+)") do
            local key, value = string.match(part, "(%w+)=(.+)")
            if key == "imagem" then
                imageName = value
            elseif key == "som" then
                soundName = value
            end
        end
    else
        -- Se for só um nome simples, trata como som direto
        playSound(buffer)
        return
    end
    -- Exibir imagem (se houver)
    if imageName then
        window = g_ui.createWidget('ImagemViewerWindow', rootWidget)
        if not window then
            print('ERRO: não foi possível criar a janela')
            return
        end
        imageWidget = window:getChildById('image')
        if imageWidget then
            local imagePath = '/mods/mod_imagem/img/' .. imageName .. '.png'
            imageWidget:setImageSource(imagePath)
        else
            print('ERRO: não encontrou o widget da imagem')
            window:destroy()
            window = nil
            return
        end
        local closeButton = window:getChildById('closeButton')
        if closeButton then
            closeButton.onClick = function()
                if window then
                    window:destroy()
                    window = nil
                end
            end
        else
            print('ERRO: não encontrou o botão de fechar')
        end
        window:raise()
        window:focus()
    end
    -- Tocar som (se houver)
    if soundName then
        playSound(soundName)
    end
end
function playSound(name)
    if not name then return end
    local soundPath = '/mods/mod_imagem/sounds/' .. name .. '.ogg'
    if soundChannel then
        soundChannel:stop()
        soundChannel = nil
    end
    soundChannel = g_sounds.play(soundPath)
    lastSoundName = name
end
function onKeyPress(keyCode, keyboardModifiers)
    if keyCode == KeyEscape and window then
        window:destroy()
        window = nil
        return true
    end
    return false
end

mod_imagem.otui​

LUA:
ImagemViewerWindow < MainWindow
  size: 810 630
  anchors.centerIn: parent
  image-source: /images/ui/window
  draggable: true
  padding: 10

  Label
    id: image
    size: 800 600
    anchors.centerIn: parent
    image-fixed-ratio: false
    image-smooth: true
    margin-top: 10

  UIButton
    id: closeButton
    text: Close
    anchors.top: parent.top
    anchors.right: parent.right
    margin-top: -5
    margin-right: 10
    focusable: true
    color: red

3. Create a folder named img/ inside mod_imagem/ and other folder named sounds/ inside mod_imagem/
• Place your PNG images there. For example:
/mods/mod_imagem/img/imagem_01.png
/mods/mod_imagem/img/imagem_02.png
1749660940483.webp
🔉: Always use images sized 800x600 for the best display quality, format PNG.
• Place your Sounds .OGG there. For example:
/mods/mod_imagem/sounds/som_01.ogg
1749667145903.webp


🚨: The image and sound filenames don’t need to follow a specific format like image_01 or sound_01. You can name them whatever you want, including using spaces. For example: sound test one.ogg or image test test.png

4. Register the mod in interface.otmod

Open modules/game_interface/interface.otmod and at the bottom, add:
- mod_imagem
1749661178261.webp

(Make sure the indentation matches the rest of the list.)


🧠 Server-Side Setup (TFS 1.4.2 used: BlackTek Server)
1. Register the ExtendedOpcode event:
data/creaturescripts/creaturescripts.xml

LUA:
<event type="extendedopcode" name="ExtendedOpcode" script="extendedopcode.lua" />
2. Create data/creaturescripts/scripts/extendedopcode.lua
LUA:
-- Lista de opcodes utilizados
local OPCODE_LANGUAGE     = 1
local OPCODE_MOD_IMAGEM = 45  -- usado pelo mod para exibir mensagem no client
function onExtendedOpcode(player, opcode, buffer)
    if opcode == OPCODE_LANGUAGE then
        if buffer == "pt" or buffer == "en" then
        end
    elseif opcode == OPCODE_MOD_IMAGEM then
    else
    end
end

🧩 Triggering the Image (RevScript)
To trigger the image when a player steps on a tile ActionID 4454, create a new file
data/scripts/movements/enviar_imagem_mod.lua
LUA:
-- Script ativado ao pisar no tile com actionid 4452
local OPCODE_ID = 45 -- mesmo usado no cliente
local tileActionId = 4452
local imagem = "imagem_01"
local som = "som_01"
local tileTrigger = MoveEvent()
function tileTrigger.onStepIn(creature, item, position, fromPosition)
  if not creature:isPlayer() then return true end
  local player = creature:getPlayer()
  if player then
    sendCustomOpcode(player, imagem, som)
    -- Example: Send only an imagem
    -- player:sendExtendedOpcode(OPCODE_ID, imagem, false)
    -- Example: Send only a sound
    -- player:sendExtendedOpcode(OPCODE_ID, false, sound)
    -- Example: Send both imagem and sound
    -- player:sendExtendedOpcode(OPCODE_ID, imagem, sound)
  end
  return true
end
-- Envia imagem e/ou som como ExtendedOpcode
function sendCustomOpcode(player, imagem, som)
  local params = {}
  if imagem and imagem ~= false then
    table.insert(params, "imagem=" .. imagem)
  end
  if som and som ~= false then
    table.insert(params, "som=" .. som)
  end
  if #params > 0 then
    player:sendExtendedOpcode(OPCODE_ID, table.concat(params, ","))
  end
end
tileTrigger:aid(tileActionId)
tileTrigger:type("stepin")
tileTrigger:register()

LUA:
🚨 Example use in direct other scripts:
player:sendExtendedOpcode(45, "imagem=imagem_01") -- Send only an image:
player:sendExtendedOpcode(45, "som=som_01") -- Send only an sound:
player:sendExtendedOpcode(45, "imagem=imagem_01,som=som_01") -- Send only an image and sound:

-- Send only an image:

LUA:
🚨 Attention: 🚨
This mod uses opcode 45, so be careful not to have another mod using opcode 45.
If you do, change it to another one, for example 33. The maximum is up to 250, if I’m not mistaken.
Therefore, whenever you see 45, you need to change it in the following files:
• mod_imagem.lua (Client)
• extendedopcode.lua (Server)
• enviar_imagem_mod.lua (Server)


🔁 Example Use Cases​


  • Display a quest hint image once by using storage to check if the player already saw it.
  • Show a tutorial image for new players.
  • Show warning images on special areas (e.g., boss rooms).

You can trigger it manually from scripts too:
player:sendExtendedOpcode(45, "imagem_03")

As long as there's a matching PNG image in:
/mods/mod_imagem/img/imagem_03.png

⚠️ Notes​

  • Image names must not include the .png extension.
  • All images must be .png and placed inside the img/ folder in the mod.
 

Attachments

  • 1749660747492.webp
    1749660747492.webp
    10.9 KB · Views: 41 · VirusTotal
Last edited:
Back
Top