Ir para conteúdo
  • Cadastre-se

    [TFS 0.x] Advanced Roulette System

    Neutraz
    Por Neutraz,

    Advanced Roulette System (TFS 0.3.6)

     

    Hi everyone! This is my first contribution to the Tibia community, and I hope you find it very useful. It's a gacha-style roulette system with dynamic speed mechanics and multi-key functionality.

     

    What's it about?

    This script adds a roulette (Tested on: Tibia 8.6 -- TFS 0.3.6) where players can use keys to spin the roulette and win rewards. The system includes:

    • Multi-key support: using 1 to 4 keys per spin, increasing the chances of winning.
    • Dynamic speedThe roulette gradually accelerates during the spin to make it more exciting.
    • Configurable rewardsYou can define which items can be won and with what probability. You can even add "reroll" value to increase the difficulty!
    • Visual effectsIncludes simple effects and text animations to make the experience more engaging.
    • Activity logKeeps a record of which players won which items.

     

    Demo Video (x1.5 Speed)

     

     

    Installation

    1. Copy the `Roulette.lua` file to the `data/actions/scripts` folder of your TFS server.
      --[[
      ================================================================================
      =                   ROULETTE SYSTEM FOR TFS                                     =
      =                                                                               =
      = Author: Neutras                                                               =
      = Version: 2.1                                                                  =
      = Description: Gacha-style roulette system with dynamic speed mechanics         =
      =              and multi-key feature.                                           =
      =                                                                               =
      = Features:                                                                     =
      = - Multi-key support (1-4 keys per spin).                                      =
      = - Dynamic speed animation with configurable initial and final speeds.         =
      = - Persistent "Winner Slot" effects and animated texts.                        =
      = - Configurable rewards with reroll chances.                                   =
      = - Logging system to track player rewards.                                     =
      =                                                                               =
      = Compatible with TFS 0.3.7 (Tibia 8.6).                                        =
      ================================================================================
      --]]
      
      -- ================= LOGGING SYSTEM ================= --
      local logPath = "data/logs/"
      local logFileName = "roulette.log"
      
      -- Logs player rewards to a file.
      -- @param cid: Player ID.
      -- @param keyName: Name of the key used.
      -- @param items: Table of items won.
      -- @param keyCount: Number of keys used.
      local function logEntry(cid, keyName, items, keyCount)
          local file = io.open(logPath .. logFileName, "a")
          if file then
              local itemStrings = {}
              for _, item in ipairs(items) do
                  table.insert(itemStrings, string.format("x%d %s", item.count, getItemNameById(item.id)))
              end
              file:write(string.format("[%s] %s used %d '%s' and won: %s\n",
                  os.date("%Y-%m-%d %H:%M:%S"),
                  getPlayerName(cid),
                  keyCount,
                  keyName,
                  table.concat(itemStrings, ", ")))
              file:close()
          end
      end
      
      -- ================= BASE CONFIGURATION ================= --
      -- Levers Action IDs to key item IDs.
      local keyByAid = {
          [1354] = 9971, -- Key for reward level 1 (Copper)
          [1355] = 9972, -- Key for reward level 2 (Silver)
          [1356] = 9973  -- Key for reward level 3 (Golden)
      }
      
      -- Levers Action IDs to reward levels.
      local rewardByAid = {
          [1354] = 1, -- Reward level 1 (Copper)
          [1355] = 2, -- Reward level 2 (Silver)
          [1356] = 3  -- Reward level 3 (Golden)
      }
      
      -- Relative positions of the slots in the roulette.
      local rouletteSpinOffset = {
          {1, -4}, {2, -4}, {3, -4}, {3, -3}, {4, -3},
          {4, -2}, {4, -1}, {5, -1}, {5, 0},  {5, 1},
          {4, 1},  {4, 2},  {4, 3},  {3, 3},  {3, 4},
          {2, 4},  {1, 4},  {0, 4},  {-1, 4}, {-2, 4},
          {-3, 4}, {-3, 3}, {-4, 3}, {-4, 2}, {-4, 1},
          {-5, 1}, {-5, 0}, {-5, -1},{-4, -1},{-4, -2},
          {-4, -3},{-3, -3},{-3, -4},{-2, -4},{-1, -4},
          {0, -4}
      }
      
      -- ================= MAIN CONFIGURATION ================= --
      local config = {
          rouletteCD = 30,           -- Global cooldown in seconds.
          globalStoCd = 22600,       -- Storage ID for cooldown.
          globalStoKeyCount = 22601, -- Storage ID for key count.
          maxLoops = 100,            -- Maximum iterations per spin.
          initialSpeed = 50,         -- Initial speed in milliseconds.
          finalSpeed = 400,          -- Final speed in milliseconds.
          effectLever = 35,          -- Effect when activating the lever.
          effectRewardPlayer = 28,   -- Effect on the player when winning.
          effectReward = 28,         -- Effect on the winning slot.
      
          -- Reward table by level.
          -- Formula: Real Probability = (Item Chance / Total Chances) * (1 - (Reroll % / 100))
          items = {
              [1] = { 
                  {id = 1, chance = 80, count = 5},
              },
              [2] = {
                  {id = 2, chance = 70, count = 1, porc_cambio = 30},
              },
              [3] = {
                  {id = 3, chance = 25, count = 1, porc_cambio = 70}
              }
          }    
      }
      
      -- ================= PROBABILITY CACHING ================= --
      -- Precalculates cumulative probabilities for each reward level.
      local cumulativeChanceCache = {}
      for rewardId, items in pairs(config.items) do
          local total = 0
          local cumulative = {}
          for _, item in ipairs(items) do
              total = total + item.chance
              table.insert(cumulative, {item = item, threshold = total})
          end
          cumulativeChanceCache[rewardId] = {total = total, items = cumulative}
      end
      
      -- ================= UTILITY FUNCTIONS ================= --
      -- Calculates the speed of the roulette animation based on progress.
      -- @param progress: Current progress (0 to 1).
      -- @return: Speed in milliseconds.
      local function calculateSpeed(progress)
          return config.initialSpeed + (config.finalSpeed - config.initialSpeed) * progress^3
      end
      
      -- Selects a random item from the reward table, considering reroll chances.
      -- @param rewardId: Reward level ID.
      -- @return: Selected item.
      local function chooseRouletteItem(rewardId)
          local cache = cumulativeChanceCache[rewardId]
          local roll = math.random(cache.total)
          for _, entry in ipairs(cache.items) do
              if roll <= entry.threshold then
                  if entry.item.porc_cambio and math.random(100) <= entry.item.porc_cambio then
                      return chooseRouletteItem(rewardId)
                  end
                  return entry.item
              end
          end
          return cache.items[#cache.items].item
      end
      
      -- Rotates the slots in the roulette.
      -- @param slots: Table of slots.
      local function rotateSlots(slots)
          local last = slots[36]
          for i = 36, 2, -1 do slots[i] = slots[i-1] end
          slots[1] = last
      end
      
      -- Updates the visual display of the roulette.
      -- @param cpos: Center position of the roulette.
      -- @param slots: Table of slots.
      -- @param isFillingPhase: Whether the slots are being filled for the first time.
      local function updateRouletteDisplay(cpos, slots, isFillingPhase)
          for i = 1, 36 do
              local pos = {
                  x = cpos.x + rouletteSpinOffset[i][1],
                  y = cpos.y + rouletteSpinOffset[i][2],
                  z = cpos.z
              }
              doCleanTile(pos)
              if slots[i] then
                  doCreateItem(slots[i].id, slots[i].count, pos)
                  -- Show puff effect only during the initial filling phase.
                  if isFillingPhase then
                      doSendMagicEffect(pos, 14)
                  end
              end
          end
      end
      
      -- ================= WINNER SLOTS AND EFFECTS ================= --
      -- Shows "Winner Slot" animated text on winning slots.
      -- @param cpos: Center position of the roulette.
      -- @param keyCount: Number of keys used.
      local function showWinnerSlots(cpos, keyCount)
          local winningSlots = {}
          if keyCount == 1 then
              winningSlots = {36}
          elseif keyCount == 2 then
              winningSlots = {36, 18}
          elseif keyCount == 3 then
              winningSlots = {36, 18, 9}
          elseif keyCount == 4 then
              winningSlots = {36, 18, 9, 27}
          else
              winningSlots = {36} -- Default to one winning slot if keyCount is invalid.
          end
      
          for _, slot in ipairs(winningSlots) do
              local pos = {
                  x = cpos.x + rouletteSpinOffset[slot][1],
                  y = cpos.y + rouletteSpinOffset[slot][2],
                  z = cpos.z
              }
              doSendAnimatedText(pos, "Winner Slot", TEXTCOLOR_YELLOW)
          end
      end
      
      -- Shows the number of keys in use.
      -- @param cpos: Center position of the roulette.
      local function showKeyCount(cpos)
          local keyCount = getGlobalStorageValue(config.globalStoKeyCount)
          keyCount = (keyCount < 1 or keyCount > 4) and 1 or keyCount
          local pos = {x = 1013, y = 995, z = 7}
          doSendAnimatedText(pos, string.format("Keys: %d", keyCount), TEXTCOLOR_LIGHTBLUE)
      end
      
      -- ================= MAIN ROULETTE LOGIC ================= --
      -- Main animation function, recursively called to simulate the roulette spin.
      -- @param cid: Player ID.
      -- @param cpos: Center position of the roulette.
      -- @param rewardId: ID of the reward level.
      -- @param nloop: Current iteration number.
      -- @param slots: Table of slots (items).
      -- @param keyName: Name of the key used.
      -- @param keyCount: Number of keys used.
      local function shuffle(cid, cpos, rewardId, nloop, slots, keyName, keyCount)
          if nloop > config.maxLoops then
              if isPlayer(cid) then
                  -- Determine winning slots based on the number of keys used.
                  local winningSlots = {}
                  if keyCount == 1 then
                      winningSlots = {36}
                  elseif keyCount == 2 then
                      winningSlots = {36, 18}
                  elseif keyCount == 3 then
                      winningSlots = {36, 18, 9}
                  elseif keyCount == 4 then
                      winningSlots = {36, 18, 9, 27}
                  else
                      winningSlots = {36} -- Default to one winning slot if keyCount is invalid.
                  end
      
                  -- Get the winning items and their positions.
                  local wonItems = {}
                  local winPositions = {}
                  for _, slot in ipairs(winningSlots) do
                      if slots[slot] then
                          table.insert(wonItems, slots[slot])
                          local pos = {
                              x = cpos.x + rouletteSpinOffset[slot][1],
                              y = cpos.y + rouletteSpinOffset[slot][2],
                              z = cpos.z
                          }
                          table.insert(winPositions, pos)
                      end
                  end
      
                  -- Award the items and display visual effects.
                  if #wonItems > 0 then
                      for _, pos in ipairs(winPositions) do
                          doSendAnimatedText(pos, "Winner Slot", TEXTCOLOR_YELLOW)
                          doSendMagicEffect(pos, config.effectReward)
                      end
                      for _, item in ipairs(wonItems) do
                          doPlayerAddItem(cid, item.id, item.count)
                      end
                      doSendMagicEffect(getCreaturePosition(cid), config.effectRewardPlayer)
                      
                       -- Display a message to the player with all the rewards.
                      local itemList = {}
                      for _, item in ipairs(wonItems) do
                          table.insert(itemList, string.format("x%d %s", item.count, getItemNameById(item.id)))
                      end
                      doPlayerSendTextMessage(cid, MESSAGE_STATUS_CONSOLE_BLUE, "[ROULETTE] You won: " .. table.concat(itemList, ", "))
                      
                      -- Log the player's rewards.
                      logEntry(cid, keyName, wonItems, keyCount)
                  else
                      doPlayerSendTextMessage(cid, MESSAGE_STATUS_CONSOLE_BLUE, "[ROULETTE] No items won.")
                  end
                  setGlobalStorageValue(config.globalStoCd, 0)
              end
              return
          end
      
          -- Initial filling phase of the roulette slots.
          if nloop <= 36 then
              slots[nloop] = chooseRouletteItem(rewardId)
              updateRouletteDisplay(cpos, slots, true)
          else
              -- Rotate the slots and update the display.
              rotateSlots(slots)
              updateRouletteDisplay(cpos, slots, false)
              
              -- Show effects on the winning slots every 5 iterations.
              if nloop % 5 == 0 then
                  local winningSlots = {}
                  if keyCount == 1 then
                      winningSlots = {36}
                  elseif keyCount == 2 then
                      winningSlots = {36, 18}
                  elseif keyCount == 3 then
                      winningSlots = {36, 18, 9}
                  elseif keyCount == 4 then
                      winningSlots = {36, 18, 9, 27}
                  else
                      winningSlots = {36} -- Default to one winning slot if keyCount is invalid.
                  end
      
                  for _, slot in ipairs(winningSlots) do
                      local pos = {
                          x = cpos.x + rouletteSpinOffset[slot][1],
                          y = cpos.y + rouletteSpinOffset[slot][2],
                          z = cpos.z
                      }
                      doSendMagicEffect(pos, config.effectReward)
                  end
              end
          end
      
          -- Schedule the next iteration with dynamic speed.
          local progress = nloop / config.maxLoops
          addEvent(shuffle, calculateSpeed(progress), cid, cpos, rewardId, nloop + 1, slots, keyName, keyCount)
      end
      
      -- ================= PERIODIC EFFECTS AND TEXTS ================= --
      -- Shows effects and texts periodically.
      -- @param cpos: Center position of the roulette.
      local function showEffectsAndTexts(cpos)
          local keyCount = getGlobalStorageValue(config.globalStoKeyCount)
          keyCount = (keyCount < 1 or keyCount > 4) and 1 or keyCount -- Ensure keyCount is within range.
      
          -- Show "Winner Slot" on the winning slots.
          showWinnerSlots(cpos, keyCount)
      
          -- Show the number of keys in use.
          showKeyCount(cpos)
      
          -- Schedule the next execution.
          addEvent(showEffectsAndTexts, 1500, cpos)
      end
      
      -- ================= EFFECT SCRIPT INITIALIZATION ================= --
      -- Start the periodic effects and texts when the script is loaded.
      local cpos = {x = 1012, y = 994, z = 7} -- Center position of the roulette.
      addEvent(function()
          showEffectsAndTexts(cpos)
      end, 5000) -- 5 seconds delay since server start.
      
      -- ================= MAIN OBJECT USE FUNCTION ================= --
      -- Called when the roulette object is used.
      function onUse(cid, item, frompos, item2, topos)
          -- Handle the key change lever.
          if item.aid == 1360 then
              local current = getGlobalStorageValue(config.globalStoKeyCount)
              current = (current < 1 or current > 4) and 1 or (current % 4) + 1
              setGlobalStorageValue(config.globalStoKeyCount, current)
              doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, string.format("Now using %d keys per spin.", current))
              doSendMagicEffect(getThingPos(item.uid), CONST_ME_MAGIC_GREEN)
              return true
          end
      
          -- Handle the roulette levers.
          if not keyByAid[item.aid] then return false end
      
          local key = keyByAid[item.aid]
          local keyName = getItemNameById(key)
          local requiredKeys = getGlobalStorageValue(config.globalStoKeyCount)
          requiredKeys = (requiredKeys < 1 or requiredKeys > 4) and 1 or requiredKeys
      
          if getPlayerAccess(cid) < 5 and getPlayerItemCount(cid, key) < requiredKeys then
              doPlayerSendCancel(cid, string.format("You need %d %s to play!", requiredKeys, keyName))
              doSendMagicEffect(topos, 14)
              return true
          end
      
          local rewardId = rewardByAid[item.aid] or 1 -- Get the reward level based on the lever. Default to 1 if not found.
          local pos = {x = 1012, y = 994, z = 7} -- Center position of the roulette.
      
          if getGlobalStorageValue(config.globalStoCd) > os.time() and getPlayerAccess(cid) < 5 then
              local remaining = getGlobalStorageValue(config.globalStoCd) - os.time()
              doPlayerSendCancel(cid, "Wait " .. remaining .. " seconds to play again.")
              return true
          end
      
          setGlobalStorageValue(config.globalStoCd, os.time() + config.rouletteCD) -- Set the cooldown.
          doTransformItem(item.uid, item.itemid == 9825 and 9826 or 9825) -- Change the lever's appearance.
      
          -- Clear the tiles around the roulette and add magic effects.
          for i = 1, 36 do
              local rpos = {
                  x = pos.x + rouletteSpinOffset[i][1],
                  y = pos.y + rouletteSpinOffset[i][2],
                  z = pos.z
              }
              doCleanTile(rpos)
              doSendMagicEffect(rpos, config.effectReward)
          end
      
          if key > 0 then doPlayerRemoveItem(cid, key, requiredKeys) end -- Remove the keys from the player's inventory.
          doSendMagicEffect(pos, config.effectLever) -- Play the lever activation effect.
      
          math.randomseed(os.time() + getPlayerGUID(cid)) -- Seed the random number generator.
          addEvent(shuffle, config.initialSpeed, cid, pos, rewardId, 1, {}, keyName, requiredKeys) -- Start the roulette animation.
          return true
      end

       

    2. Add the following lines within the `<actions>` tag in your `data/actions/actions.xml` file:
      <action actionid="XXX;YYYY;ZZZZ;AAAA" event="script" value="Roulette.lua"/>

      Replace `XXXX`, `YYYY`, and `ZZZZ` with the unique IDs of the levers that will activate the roulette (make sure they are not in use!).
      The ID `AAAA` is for the lever that changes the number of keys to use.

       

    3. Open the `Roulette.lua` file and adjust the `config.items` table with the items you want players to be able to win. Remember to balance the probabilities.

     

     

    Code Explanation
    The script is divided into several sections to facilitate understanding:

    • LOGGING SYSTEM: Handles logging the rewards obtained by players.
    • BASE CONFIGURATIONDefines object IDs and reward levels.
    • MAIN CONFIGURATION: Contains the main options of the system, such as the wait time between spins, the speed of the roulette, and the rewards.
    • PROBABILITY CACHING: Optimizes the calculation of probabilities so that the system runs smoothly.
    • UTILITY FUNCTIONS: Helper functions to calculate speed, choose items randomly, and update the roulette display.
    • WINNER SLOTS AND EFFECTS: Displays the "Winner Slot" text and other visual effects in the winning spaces.
    • MAIN ROULETTE LOGIC: The main function that controls the roulette animation and the delivery of rewards.
    • PERIODIC EFFECTS AND TEXTS: Displays effects and texts periodically.
    • SCRIPT INITIALIZATION: Initializes the effect system when the script is loaded.
    • MAIN OBJECT USE FUNCTION: The function that is executed when a player interacts with a lever.

      I hope this roulette system is a great addition to your server! If you have any questions or suggestions, please feel free to leave a comment. Thank you for your support!

     

     


    Tibia Ginius Versão 1.1 by L3K0T

    L3K0T
    Por L3K0T,

     

    Sem título.png

     

    Tibia Ginius Versão 1.1 by L3K0T

     

    O Tibia Genius, desenvolvido por L3K0T, é uma ferramenta essencial para desenvolvedores de OTServ que desejam otimizar e simplificar o processo de desenvolvimento dos seus servidores. Essa ferramenta integra diversas funcionalidades que eliminam a necessidade de utilizar múltiplos programas para tarefas distintas. Entre suas principais funcionalidades, destacam-se:

     

    1. Item Editor: Permite a edição detalhada dos itens do jogo, facilitando a criação e modificação de itens conforme a necessidade do servidor.
    2. RME (Remere's Map Editor): Um editor de mapas completo que oferece uma interface intuitiva para criar e modificar mapas do jogo.
    3. Object Builder: Ferramenta para a construção e edição de objetos no jogo, essencial para personalizar a experiência do jogador.
    4. Mapa Converter: Utilitário que converte mapas entre diferentes formatos, garantindo compatibilidade e facilitando a integração de mapas em diversos servidores.
    5. Servidor Integrado: Opção para iniciar o servidor diretamente no ambiente Windows para testes, eliminando a necessidade de configurar servidores externos para verificações rápidas.
    6. Acesso Facilitado às Pastas: Botões dedicados para acessar rapidamente as pastas do servidor, aumentando a eficiência no gerenciamento dos arquivos do servidor.

     

    Antes de utilizar o Tibia Genius, é necessário importar o servidor. Isso é feito através do botão "Selecionar Servidor", onde o usuário deve especificar a pasta do servidor para que todas as funcionalidades possam ser utilizadas de forma integrada. Da mesma forma, para utilizar o RME e o Item Editor, é preciso importar os arquivos SPR e DAT do cliente do jogo.

    Com o Tibia Genius, l3k0t proporciona uma solução tudo-em-um para desenvolvedores de OTServ, tornando o processo de desenvolvimento mais ágil e centralizado, ao mesmo tempo que oferece ferramentas poderosas e de fácil acesso para a criação e manutenção de servidores personalizados.

     

     

    Sem títulower.png

     

     

    Sem títulowewerr.png

     

     

    Espero que ajude os preguiçosos risos, qualquer coisa reporte e ajude a melhorar, ideias são todas bem vindas!

     

    Download GitHub:  https://github.com/l3k0t/Tibia_Ginius

    Scan Virus Total: https://www.virustotal.com/gui/file/f406f1310f8c5f403c35115eaaddc609ccdff2ff56b4a96b619ac7df914829e1?nocache=1

    Discord do Projeto: https://discord.gg/MGD55hPWYf




×
×
  • Criar Novo...

Informação Importante

Confirmação de Termo