--[[
================================================================================
= 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). =
================================================================================
--]]
--[[
================================================================================
= 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 = 4871, chance = 100, count = 1},
},
[2] = {
{id = 4872, chance = 100, count = 1},
},
[3] = {
{id = 4870, chance = 100, count = 1},
},
[4] = {
{id = 5899, chance = 100, count = 1},
},
[5] = {
{id = 2349, chance = 100, count = 1},
},
[6] = {
{id = 9003, chance = 100, count = 1},
},
[7] = {
{id = 2300, chance = 100, count = 1},
},
[8] = {
{id = 2309, chance = 100, count = 1},
},
[9] = {
{id = 2272, chance = 100, count = 1},
},
[10] = {
{id = 2306, chance = 100, count = 1},
},
[11] = {
{id = 2447, chance = 100, count = 1},
},
[12] = {
{id = 7739, chance = 100, count = 1},
},
[13] = {
{id = 7737, chance = 100, count = 1},
},
[14] = {
{id = 5805, chance = 100, count = 1},
},
[15] = {
{id = 2446, chance = 100, count = 1}
}
}
}
-- ================= 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", 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 = 266, y = 233, z = 15}
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 = 266, y = 233, z = 15} -- 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 = 266, y = 233, z = 15} -- 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
Pergunta
Muvuka 1
Olha Como o Meu tá e Não ta Funcionando Olha Mapa: ONDE EU TO É A POS: x266 y233 z15
Link para o comentário
https://xtibia.com/forum/topic/259988-advanced-roulette-system-tfs-036-socorro-me-ajuda/Compartilhar em outros sites
0 respostass a esta questão
Posts Recomendados