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

Roulette System

Xikini

I whore myself out for likes
Senator
Premium User
Joined
Nov 17, 2010
Messages
6,787
Solutions
581
Reaction score
5,354
Idea from here.

roulette.gif

Click a lever.
Player spends an item, and the roulette spins.. slows down, and gives a reward.

The rewards are rolled randomly, based on a chance that you can set in the script.

Credits to @LeOnArd0 for the idea, and explanation on how everything was meant to work.

Lua:
local config = {
    actionId = 18562, -- on lever
    lever = {
        left = 1945,
        right = 1946
    },
    playItem = {
        itemId = 5197, -- item required to pull lever
        count = 1
    },
    rouletteOptions = {
        rareItemChance_broadcastThreshold = 500,
        ignoredItems = {1617}, -- if you have tables/counters/other items on the roulette tiles, add them here
        winEffects = {CONST_ANI_FIRE, CONST_ME_SOUND_YELLOW, CONST_ME_SOUND_PURPLE, CONST_ME_SOUND_BLUE, CONST_ME_SOUND_WHITE}, -- first effect needs to be distance effect
        effectDelay = 333,
        spinTime = {min = 8, max = 12}, -- seconds
        spinSlowdownRamping = 5,
        rouletteStorage = 48550 -- required storage to avoid player abuse (if they logout/die before roulette finishes.. they can spin again for free)
    },
    prizePool = {
        {itemId = 2160, count = {1, 10},   chance = 10000}, -- {itemId = itemid, count = {min, max}, chance = chance/10000} (crystal coins)
        {itemId = 2488, count = {1, 1},    chance = 9000 }, -- crown legs
        {itemId = 2195, count = {1, 1},    chance = 8500 }, -- boots of haste
        {itemId = 2498, count = {1, 1},    chance = 7500 }, -- royal helmet
        {itemId = 5226, count = {1, 70},   chance = 6500 }, -- old sudden death rune -- runes are given as stackable items, even tho they have 'charges'
        {itemId = 5184, count = {50, 100}, chance = 5000 }, -- loot seller chest     -- items with 'charges' and have 'showCharges' in items.xml will be given charges
        {itemId = 5197, count = {1, 3},    chance = 4000 }, -- roulette token
        {itemId = 2470, count = {1, 1},    chance = 3000 }, -- golden legs
        {itemId = 2472, count = {1, 1},    chance = 1500 }, -- magic plate armor
        {itemId = 2646, count = {1, 1},    chance = 500  }  -- golden boots
    
    },
    roulettePositions = { -- hard-coded to 7 positions.
        Position(32344, 32218, 4),
        Position(32345, 32218, 4),
        Position(32346, 32218, 4),
        Position(32347, 32218, 4), -- position 4 in this list is hard-coded to be the reward location, which is the item given to the player
        Position(32348, 32218, 4),
        Position(32349, 32218, 4),
        Position(32350, 32218, 4),
    }
}

local chancedItems = {} -- used for broadcast. don't edit

local function resetLever(position)
    local lever = Tile(position):getItemById(config.lever.right)
    lever:transform(config.lever.left)
end

local function updateRoulette(newItemInfo)
    local positions = config.roulettePositions
    for i = #positions, 1, -1 do
        local item = Tile(positions[i]):getTopVisibleThing()
        if item and item:getId() ~= Tile(positions[i]):getGround():getId() and not table.contains(config.rouletteOptions.ignoredItems, item:getId()) then
            if i ~= 7 then
                item:moveTo(positions[i + 1])
            else
                item:remove()
            end
        end
    end
    if ItemType(newItemInfo.itemId):hasShowCharges() then
        local item = Game.createItem(newItemInfo.itemId, 1, positions[1])
        item:setAttribute("charges", newItemInfo.count)
    else
        Game.createItem(newItemInfo.itemId, newItemInfo.count, positions[1])
    end
end

local function clearRoulette(newItemInfo)
    local positions = config.roulettePositions
    for i = #positions, 1, -1 do
        local item = Tile(positions[i]):getTopVisibleThing()
        if item and item:getId() ~= Tile(positions[i]):getGround():getId() and not table.contains(config.rouletteOptions.ignoredItems, item:getId()) then
            item:remove()
        end
        if newItemInfo == nil then
            positions[i]:sendMagicEffect(CONST_ME_POFF)
        else
            if ItemType(newItemInfo.itemId):hasShowCharges() then
                local item = Game.createItem(newItemInfo.itemId, 1, positions[i])
                item:setAttribute("charges", newItemInfo.count)
            else
                Game.createItem(newItemInfo.itemId, newItemInfo.count, positions[i])
            end
        end
    end
end

local function chanceNewReward()
    local newItemInfo = {itemId = 0, count = 0}
    
    local rewardTable = {}
    while #rewardTable < 1 do
        for i = 1, #config.prizePool do
            if config.prizePool[i].chance >= math.random(10000) then
                rewardTable[#rewardTable + 1] = i
            end
        end
    end
    
    local rand = math.random(#rewardTable)
    newItemInfo.itemId = config.prizePool[rewardTable[rand]].itemId
    newItemInfo.count = math.random(config.prizePool[rewardTable[rand]].count[1], config.prizePool[rewardTable[rand]].count[2])
    chancedItems[#chancedItems + 1] = config.prizePool[rewardTable[rand]].chance
    
    return newItemInfo
end

local function initiateReward(leverPosition, effectCounter)
    if effectCounter < #config.rouletteOptions.winEffects then
        effectCounter = effectCounter + 1
        if effectCounter == 1 then
            config.roulettePositions[1]:sendDistanceEffect(config.roulettePositions[4], config.rouletteOptions.winEffects[1])
            config.roulettePositions[7]:sendDistanceEffect(config.roulettePositions[4], config.rouletteOptions.winEffects[1])
        else
            for i = 1, #config.roulettePositions do
                config.roulettePositions[i]:sendMagicEffect(config.rouletteOptions.winEffects[effectCounter])
            end
        end
        if effectCounter == 2 then
            local item = Tile(config.roulettePositions[4]):getTopVisibleThing()
            local newItemInfo = {itemId = item:getId(), count = item:getCount()}
            clearRoulette(newItemInfo)
        end
        addEvent(initiateReward, config.rouletteOptions.effectDelay, leverPosition, effectCounter)
        return
    end
    resetLever(leverPosition)
end

local function rewardPlayer(playerId, leverPosition)
    local player = Player(playerId)
    if not player then
        return
    end
    
    local item = Tile(config.roulettePositions[4]):getTopVisibleThing()
    
    if ItemType(item:getId()):hasShowCharges() then
        local addedItem = player:addItem(item:getId(), 1, true)
        addedItem:setAttribute("charges", item:getCharges())
    else
        player:addItem(item:getId(), item:getCount(), true)
    end

    player:setStorageValue(config.rouletteOptions.rouletteStorage, -1)
    if chancedItems[#chancedItems - 3] <= config.rouletteOptions.rareItemChance_broadcastThreshold then
        Game.broadcastMessage("The player " .. player:getName() .. " has won " .. item:getName() .. " from the roulette!", MESSAGE_EVENT_ADVANCE)
    end
end

local function roulette(playerId, leverPosition, spinTimeRemaining, spinDelay)
    local player = Player(playerId)
    if not player then
        resetLever(leverPosition)
        return
    end
    
    local newItemInfo = chanceNewReward()
    updateRoulette(newItemInfo)
    
    if spinTimeRemaining > 0 then
        spinDelay = spinDelay + config.rouletteOptions.spinSlowdownRamping
        addEvent(roulette, spinDelay, playerId, leverPosition, spinTimeRemaining - (spinDelay - config.rouletteOptions.spinSlowdownRamping), spinDelay)
        return
    end
    
    initiateReward(leverPosition, 0)
    rewardPlayer(playerId, leverPosition)
end

local casinoRoulette = Action()

function casinoRoulette.onUse(player, item, fromPosition, target, toPosition, isHotkey)
    if item:getId() == config.lever.right then
        player:sendTextMessage(MESSAGE_STATUS_SMALL, "Casino Roulette is currently in progress. Please wait.")
        return true
    end
    
    if player:getItemCount(config.playItem.itemId) < config.playItem.count then
        if player:getStorageValue(config.rouletteOptions.rouletteStorage) < 1 then
            player:sendTextMessage(MESSAGE_STATUS_SMALL, "Casino Roulette requires " .. config.playItem.count .. " " .. (ItemType(config.playItem.itemId):getName()) .. " to use.")
            return true
        end
        -- player:sendTextMessage(MESSAGE_STATUS_SMALL, "Free Spin being used due to a previous unforeseen error.")
    end
    
    item:transform(config.lever.right)
    clearRoulette()
    chancedItems = {}
    
    player:removeItem(config.playItem.itemId, config.playItem.count)
    player:setStorageValue(config.rouletteOptions.rouletteStorage, 1)
    
    local spinTimeRemaining = math.random((config.rouletteOptions.spinTime.min * 1000), (config.rouletteOptions.spinTime.max * 1000))
    roulette(player:getId(), toPosition, spinTimeRemaining, 100)
    return true
end

casinoRoulette:aid(config.actionId)
casinoRoulette:register()


local disableMovingItemsToRoulettePositions = EventCallback

disableMovingItemsToRoulettePositions.onMoveItem = function(self, item, count, fromPosition, toPosition, fromCylinder, toCylinder)
    for v, k in pairs(config.roulettePositions) do
        if toPosition == k then
            return false
        end
    end
    return true
end

disableMovingItemsToRoulettePositions:register()
 
This is perfect! It turned out sensational and with all the possibilities I was thinking about.

Thank you very much for your intelligence in building this script.

As I said, his work is very inspiring, it's satisfying to watch him create a complex script from scratch.

Congratulations and it's already implemented in the project I'm thinking of for an old custom tibia =).
 
Is there any possible to make the transition sliding instead "disappear and appear" on the next sqm?

I saw this slide in a 13x server called rubinot
 
@Xikini Excellent system, but there is a point to be urgently improved.

When a player clicks on the lever he can log out, with that the roulette stops but the player does not win the prize.
My suggestion is that the player cannot log out until he receives the item.

The detail explained by @Onimusha , would also be very nice
 
Just add an onlogout block using the storage he set in the script, and about move items smoothly its best way would be like Movie did with monsters magic-roulette/animation.lua at master · lyuz1n/magic-roulette (https://github.com/lyuz1n/magic-roulette/blob/master/lib/animation.lua)
He would need to rewrite some parts of the code to make it work like this or you can try using the one from MovieBR, also has the thing you asked to if player logout add items onLogin, you can make everything in revs and use it @DiegoRulez @Onimusha
 
@Xikini Excellent system, but there is a point to be urgently improved.

When a player clicks on the lever he can log out, with that the roulette stops but the player does not win the prize.
My suggestion is that the player cannot log out until he receives the item.

The detail explained by @Onimusha , would also be very nice
That's the point of the storage value.
Player get's a 'free spin' if they die/logout.

It's in the greentext..
 
Congratulations on the system, very good! Thanks for sharing.
This system has also been made by Lyu.
Click here to go to the repository.

Some changes are necessary if you are going to use the one from the repository.
I would rather use Xiniki's version than floating my datapack with files for a simple system
Not to mention your post is kinda rude, I dont see the point in bringing that up
 
I would rather use Xiniki's version than floating my datapack with files for a simple system
Not to mention your post is kinda rude, I dont see the point in bringing that up
Literally what I was thinking - at first read I was like "Okay, sheesh.. how good is your system that you're commenting on other peoples threads saying its been done already" then I click the link. ~face palm~
 
It's alright, guys. xD

The sliding monsters is very clever for the animation.

I've also never really worked with databases very much, as I've always preferred to keep everything vanilla.
Mostly laziness on my part. I just always imagine trying to upgrade my datapack.. then realising there's like 80 modifications to my database.. Dx

The big sticking point in my version is 2-fold.

I was building around the issue of a player spending currency to roll the roulette.. and then not getting a prize. (due to a server crash / logout / death / or a gm using /reload)

The best solution I came up with, was giving the player a 'free spin', so they wouldn't lose out on the currency.
Which inevitably caused the rest of code to follow that decision.

  • Stop roulette early, so another player could play.
  • Knowing that players would log out intentionally to abuse the system into giving them free spins if they didn't like the reward coming up.. I made it a point to make the roulette random enough, that this shouldn't be an issue. (if it is an issue, there is some fixes.. like making the area near this system a non-logout zone, or increasing the timing variance from 8-12 to something like.. 6-15, and/or setting the ramping slowdown to 0, so there is no way to tell where the roulette will stop)

--
The only other idea I had was to 'roll' the roulette entirely virtually, and save the reward into 2 storage values.. and then just give the player the reward after the animation was done..

But I discarded this idea because it felt too gimmicky.
In hindsight, it's probably the better option, but has it's own drawbacks.. so 🤷‍♂️

I think the last sticking point, is that I didn't make it possible to setup multiple locations.. but again hindsight, because I was short on time and scripting it live for Leo.
By the time I realized the corner I scripted myself into, I was already half-way done.

--
Anyways, it was fun to script, and I fixed the core issues I set out to fix from the 2 other roulette systems I saw in that support thread.
Some learning opportunities all around, and helped someone along the way.
I'd call it a wash. :)
 
I think because it's a fun and functional system, anything outside of that is just aesthetic.
 
Back
Top