Follow along with the video below to see how to install our site as a web app on your home screen.
Note: This feature may not be available in some browsers.
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!
Like Xikini i'm only gonna take quick scripts and i don't wanna do spells, weapons, raids or monster scripts, websites edits and database queries. Source edits need to be a really small thing(would be very boring to keep cloning TFS repo to test each one). Post in the topic too, no dm.
I wanna reserve this for interesting scripts too and try to detail as much as possible how the script need to work.
I found a gif to make the explanation of the game mechanics more simple.
The player is in an enclosed space, with 'switches' on the floor, that can only be activated by pushing a box on top of them.
When all switches have been pressed, the game is over and you win.
For tibia's implementation,
Players starting location is important.
Players must push a box, by walking into it. (no diagonals allowed.)
Players cannot stand on a box.
The boxes cannot go on top of other boxes, and can't be pushed into the walls.
A more simple option would be to have the players use the box, and drag the player in the direction the box is moving.
Ways in and out of the puzzle.
- A simple teleport to get inside, with a 10 minute timer before the player is kicked from the area, and the puzzle is reset.
- - (to stop the player from destroying the puzzle by accident while teleporting, restrict player movement for 2 seconds.)
- - (countdown timer per 30 seconds visible to player. Last 30 seconds counts down per 5 seconds, and last 5 seconds counts down per 1 second.)
- - (10:00, 9:30, ... 1:00, 0:30, 0:25, ... 0:10, 0:05, 0:04, ... 0:01)
- If the puzzle is solved, teleport player to a reward area, and of course reset the puzzle.
- To leave the area prior to the timer counting to zero, have the player 'double/triple click' any exterior wall.
Anti-cheating safeguards.
- 1 person limit.
- make sure players cannot logout inside the area.
- if server crash, have a safeguard to remove them from the puzzle area.
- Players cannot move the box with their mouse.
- Basically, make the box an immoveable, unpickupable, undraggable, unwalkable, unopenable, untradeable object.. like a wall, that you can only interact with by smashing your body into the side of it. xD
- Players cannot place any object onto the playing surface. (do not destroy the players griefing item attempts. Simply deny them the opportunity to place the items down from their inventory.)
(I can't think of anything else for player griefing, but you might find something while testing that I'm simply forgetting.)
Script working perfect. I had to add some more functions like spawning diferent monsters, and if your life gets to zero you get teleported to starting zone and your time gets shorter. Also added a boss spawn after all monsters are killed, instead of finishing the event.
Now im battling to save the player's best score (higher level with best time) somewhere to be shown in a talkaction and website >.<
Thank you sir!
EDIT: Making the time shorter isnt working. The only thing that was doing is changing the time it says you accomplish the rift, but the timelimit event happens after 15 minutes anyway. Is there a way to change the time an event will happen that is already in motion?
EDIT2: Thinking on changing the timelimit event and setting a separated event on global to check if getplayerStorageValue(player, os.time) > 15*60 then
so i can add +30 seconds to playerstorage os.time on death.
Script working perfect. I had to add some more functions like spawning diferent monsters, and if your life gets to zero you get teleported to starting zone and your time gets shorter. Also added a boss spawn after all monsters are killed, instead of finishing the event.
Now im battling to save the player's best score (higher level with best time) somewhere to be shown in a talkaction and website >.<
You can do a select first and then if it's a higher level with a better time you update, so each player would get one row. When life becomes 0/die and these changes i guess that everyone can do as they want, could just set the storages back too.
EDIT: Making the time shorter isnt working. The only thing that was doing is changing the time it says you accomplish the rift, but the timelimit event happens after 15 minutes anyway. Is there a way to change the time an event will happen that is already in motion?
EDIT2: Thinking on changing the timelimit event and setting a separated event on global to check if getplayerStorageValue(player, os.time) > 15*60 then
so i can add +30 seconds to playerstorage os.time on death.
stopEvent(rift.timelimit)
local time = os.date("*t", os.time() - player:getStorageValue(rift.storages.playerTime))
local timerless = ((15 * 60) - time) - 30
addEvent(rift.timelimit, timerless , player.uid)
EDIT: Code not working since time is a table.
Tried to use it like this:
Code:
local time = os.date("*t", os.time() - player:getStorageValue(rift.storages.playerTime))
if time.min > 0 then
time = time.min .. " minutes and " .. time.sec .. " seconds"
else
time = time.sec .. " seconds"
end
But i cannot withdraw 30 seconds from this time, or at least i dont know how. Any ideas? Thanks.
EDIT2:
I did figure out how to do the time thing. But when i try to addEvent, it says that cant load rift.timelimit event. Getting error on last line pasted here.
Code:
stopEvent(rift.timelimit)
local time = os.date("*t", os.time() - player:getStorageValue(rift.storages.playerTime))
if time.min > 0 then
time = ( time.min * 60 ) + time.sec
else
time = time.sec
end
-- print(time)
-- print(time + 30)
local timerless = ((15 * 60) - time) + 30
addEvent(rift.timelimit, timerless, player.uid)
function onPrepareDeath(player, corpse, killer, mostDamageKiller, unjustified, mostDamageUnjustified)
if not player then
return true
end
player:setDropLoot(false)
player:teleportTo(Position(908,859,7))
player:addHealth(player:getMaxHealth())
player:sendTextMessage(MESSAGE_INFO_DESCR, 'You died! 30 seconds have been rested to your timer.')
stopEvent(rift.timelimit)
local time = os.date("*t", os.time() - player:getStorageValue(rift.storages.playerTime))
if time.min > 0 then
time = ( time.min * 60 ) + time.sec
else
time = time.sec
end
print(time)
print(time + 30)
local timerless = ((15 * 60) - (time + 30)
addEvent(timeLimit, timerless, player.uid)
return true
end
Your missing 1 pressure point bottom left middle btw. xD
(I'd also add a constme_poff, anytime an 'illegal move' is attempted. (box into box/wall, and diag))
Yea, however that's the way i found to avoid grief:
It removes anything that is not a crate, the spark or that + spot. I don't think that someone would throw a rare item to grief, but it's possible to move them to a room or give back to the player.
Crates , + spot and spark are spawned from a lua table so i can reset it to the original position when the player leave the room.
Yea, however that's the way i found to avoid grief:
It removes anything that is not a crate, the spark or that + spot. I don't think that someone would throw a rare item to grief, but it's possible to move them to a room or give back to the player.
Crates , + spot and spark are spawned from a lua table so i can reset it to the original position when the player leave the room.
You could use the event Player: onMoveItem(...) for that.
Lua:
function Player:onMoveItem(item, count, fromPosition, toPosition, fromCylinder, toCylinder)
if self:isInSokobanGame() then
return false
end
return true
end
Something like that.
Note: You need to activate this function on events.xml also.
You could use the event Player: onMoveItem(...) for that.
Lua:
function Player:onMoveItem(item, count, fromPosition, toPosition, fromCylinder, toCylinder)
if self:isInSokobanGame() then
return false
end
return true
end
Something like that.
Note: You need to activate this function on events.xml also.
that's already done inside the sources whenever you use onAddItem anyways, every time you move an item it gets the registered event for that tile and executes the move event based on whether you're adding the item or removing it
either way it's not like it'll slow the code down at all
the difference is that it's easier to use onMoveItem than having to register action ids inside the map and movements for multiple games
plus you don't have to check for action ids inside of onMoveItem, like river said you can check if they're inside a game or not (storage value)
that's already done inside the sources whenever you use onAddItem anyways, every time you move an item it gets the registered event for that tile and executes the move event based on whether you're adding the item or removing it
either way it's not like it'll slow the code down at all
the difference is that it's easier to use onMoveItem than having to register action ids inside the map and movements for multiple games
plus you don't have to check for action ids inside of onMoveItem, like river said you can check if they're inside a game or not (storage value)
local crates = {{x = 996, y = 968, z = 7}, {x = 997, y = 969, z = 7}, {x = 997, y = 970, z = 7}, {x = 994, y = 972, z = 7}, {x = 996, y = 972, z = 7}
, {x = 997, y = 972, z = 7}, {x = 998, y = 972, z = 7}}
sokoban = {spot = 11993, crateId = 12284, gameStorage = 3153, storage = 3152, specialAid = 2413, thingsUid = {}}
local levels = {[2412] = true}
local groundId = 10846
local maxSpots = 7
local tpBack = {x = 1022, y = 1015, z = 7}
function onStepIn(creature, item, position, fromPosition)
local player = creature:getPlayer()
if not player then
return false
end
if item.actionid ~= sokoban.specialAid and item.itemid ~= groundId and not levels[item.actionid] then
return false
end
sokobanSetLevel(player, item.actionid, 2412, crates, 7)
if runSokoban(player, item, fromPosition, position) == 0 then
sokobanStop(player)
doTeleportThing(player.uid, tpBack)
player:say("Congratulations, you won!", TALKTYPE_MONSTER_SAY)
end
return true
end
Go to events and change onMoveItem in the xml to enabled="1"
Lua:
function Player:onMoveItem(item, count, fromPosition, toPosition, fromCylinder, toCylinder)
if self:getStorageValue(sokoban.gameStorage) == 1 then
return false
end
return true
end
lib.lua
Code:
dofile('data/lib/sokobanGame.lua')
Lua:
function runSokoban(player, item, fromPosition, position, specialAid)
if player:getStorageValue(sokoban.gameStorage) ~= 1 then
return
end
if (fromPosition.x == position.x+1 and fromPosition.y == position.y+1) then
doTeleportThing(player.uid, fromPosition, true)
return
elseif (fromPosition.x == position.x-1 and fromPosition.y == position.y+1) then
doTeleportThing(player.uid, fromPosition, true)
return
elseif (fromPosition.x == position.x+1 and fromPosition.y == position.y-1) then
doTeleportThing(player.uid, fromPosition, true)
return
elseif (fromPosition.x == position.x-1 and fromPosition.y == position.y-1) then
doTeleportThing(player.uid, fromPosition, true)
return
end
if (item.actionid ~= sokoban.specialAid) then
return
end
local nextPos
if (fromPosition.x < position.x) then
nextPos = {x = (position.x+1), y = position.y, z = position.z}
elseif (fromPosition.x > position.x) then
nextPos = {x = (position.x-1), y = position.y, z = position.z}
elseif (fromPosition.y < position.y) then
nextPos = {x = (position.x), y = position.y+1, z = position.z}
elseif (fromPosition.y > position.y) then
nextPos = {x = (position.x), y = position.y-1, z = position.z}
else
return
end
if getTileThingByTopOrder(nextPos, 1).itemid ~= 0 and getTileThingByTopOrder(nextPos, 1).itemid ~= sokoban.spot or getTileItemById(nextPos, sokoban.crateId).itemid == sokoban.crateId or getTileThingByTopOrder(nextPos, 2).itemid ~= 0 or getTileThingByTopOrder(nextPos, 3).itemid ~= 0 then
doTeleportThing(player.uid, fromPosition, true)
return
end
if getTileThingByTopOrder(nextPos, 1).itemid == sokoban.spot then
player:setStorageValue(sokoban.storage, player:getStorageValue(sokoban.storage) - 1)
elseif getTileThingByTopOrder(position, 1).itemid == sokoban.spot and getTileThingByTopOrder(nextPos, 1).itemid ~= sokoban.spot then
player:setStorageValue(sokoban.storage, player:getStorageValue(sokoban.storage) + 1)
end
doTeleportThing(item.uid, nextPos, true)
doTeleportThing(getTileItemById(position, 8047).uid, nextPos, true)
return player:getStorageValue(sokoban.storage)
end
function sokobanStop(player)
for _, someThing in ipairs(sokoban.thingsUid) do
doRemoveItem(someThing.uid)
sokoban.thingsUid[_] = nil
end
player:setStorageValue(sokoban.storage, 0)
player:setStorageValue(sokoban.gameStorage, 0)
end
function sokobanBuildCrates(player, crates, specialAid)
for _, crate in ipairs(crates) do
if getTileItemById(crate, sokoban.spot).itemid == sokoban.spot then
player:setStorageValue(sokoban.storage, player:getStorageValue(sokoban.storage) - 1)
end
local c = doCreateItem(sokoban.crateId, 9500, crate)
local spark = doCreateItem(8047, 0, crate)
Item(c):setActionId(specialAid)
sokoban.thingsUid[#sokoban.thingsUid + 1] = Item(c)
sokoban.thingsUid[#sokoban.thingsUid + 1] = Item(spark)
end
end
function sokobanSetLevel(player, itemActionid, tpActionid, crates, spots)
if itemActionid ~= tpActionid then
return
end
player:setStorageValue(sokoban.storage, spots)
sokobanBuildCrates(player, crates, sokoban.specialAid)
player:setStorageValue(sokoban.gameStorage, 1)
return true
end
Put restrictions as you want, you can do an addEvent to remove the player with time limit, or remove the TP, do a command or npc to talk if the player failed, just call sokobanStop(player) for that, it will remove everything and set storages back.
2412 as set in movements.xml is the actionid for the tp. 2413 need to be the same as specialAid in the script, it's the crate, 10846 is the ground id(don't need to set the actionId for every tile, just put the id there).