Night Wolf
I don't bite.
- Joined
- Feb 10, 2008
- Messages
- 582
- Solutions
- 8
- Reaction score
- 929
- Location
- Spain
- GitHub
- andersonfaaria
Dear all,
I'm going to post a few systems/changes that I've done for my former project, Empire Server, that we didn't utilized or are improved versions of past systems I have done. I'm not going to go too much in detail as the goal is to serve as study material, especially considering some of those systems have libs/src edittings that are exclusive of the server and possibly won't work if you just copy and paste.
This isn't exactly a tutorial but I didn't knew where to fit this, here you'll find from complex codes to simple ideas to solve old problems.
1. Preventing books to be edited.
Description: A very common problem everyone that has a server possibly suffered once is to be limited in book ids that can be written and can't be edited (for quests).
This becomes mainly a problem when you want to build RPG and is worried about trolls messing up what's written or simply erasing the quest instructions/tips.
To workaround this, a programmer that worked with me in the past (Godely) came up with a brillant idea that I've never thought before.
The idea is pretty simple though, we can use editable books and put a 'interface layer' on top of it. This interface would have a action id that once activated, it creates a simulation of the book itself. While the interface item prevents the book to be moved, once activated it simulates the book, which makes any changes done on the local instantiation not affect the real book content.
2. Chests with random rewards.
Description: This was initially created as a workaround to have some of the empty chests filled with a few random low tier rewards.
3. Complete Spellbook
Description: Back when I had developed this, we didn't had any spellbook that showed for example, spells that doesn't have mana cost or that uses mana percent or that don't have level to use, so I had developed this version.
4. Animations and "industrial revolution".
Description: Tibia has always had a few elements to show they had arrived the steam era, so to create some animations of doors/bridges being open/lift with steam, I made this systemof a drawbridge moved by steam.
5. Animations for movement through 'unreachable places'
Description: Created this simple script to simulate a player climbing a rope in a dungeon. The place is unreachable but by righting click it you can access and start a animation of the player going up slowly until reaching the hidden area.
6. Improving realism on Tibia
Description: One day when I broke a house item that had liquid inside, I have noted the sprite of spilled liquid was on top of the broken item. I have tested on global and noted it also behave like this. It's almost a visual glitch and I believe the person who did it to 'make as close as tibia as possible' seriously should have though this better. Besides this I have added a check to increase the chance of breaking an item based on the attack of the weapon you're using, all directly in the destroyItem function of the lib.
7. Improving UX
Description: Now tibia UX is a bit better, but when you constantly find yourself taking hands out of arrow keys to press hotkeys you know something was badly designed.
To improve this I've some simple scripts that I have no idea why no one have ever though/done before. Basically when clicking on closed hole/rope spot it checks if you have the shovel/rope and do the action automatically without the need to 'use with' or to have hotkeys.
8. Improving Fishing experience
Description: The fishing system in tibia is nearly the same as it was from the day it was implemented. Inspired by a few systems I've seen in the forums, I've built a new version that gives you different fishes and also gives it based on their weight/chance considering your fishing skill.
9. The best mining-cart system you have ever saw!
Description: Whoever here comes from pre 7.9 era, those kinda of systems were pretty hyped back then. It's a system where you click on a mining cart and you go through the route in the rails.
@Don Daniello the auto merge system is preventing me to continue this thread.
Everything I try to type it says I've reached the limit of 25k characters. Can you please look into it?
I'm going to post a few systems/changes that I've done for my former project, Empire Server, that we didn't utilized or are improved versions of past systems I have done. I'm not going to go too much in detail as the goal is to serve as study material, especially considering some of those systems have libs/src edittings that are exclusive of the server and possibly won't work if you just copy and paste.
This isn't exactly a tutorial but I didn't knew where to fit this, here you'll find from complex codes to simple ideas to solve old problems.
1. Preventing books to be edited.
Description: A very common problem everyone that has a server possibly suffered once is to be limited in book ids that can be written and can't be edited (for quests).
This becomes mainly a problem when you want to build RPG and is worried about trolls messing up what's written or simply erasing the quest instructions/tips.
To workaround this, a programmer that worked with me in the past (Godely) came up with a brillant idea that I've never thought before.
The idea is pretty simple though, we can use editable books and put a 'interface layer' on top of it. This interface would have a action id that once activated, it creates a simulation of the book itself. While the interface item prevents the book to be moved, once activated it simulates the book, which makes any changes done on the local instantiation not affect the real book content.
Lua:
function onUse(player, item, fromPosition, target, toPosition, isHotkey)
local items = Tile(item:getPosition()):getItems()
for _,item in pairs(items) do
local text = item:getAttribute(ITEM_ATTRIBUTE_TEXT)
if string.len(text) > 0 then
player:showTextDialog(item:getId(), text)
return true
end
end
return false
end
2. Chests with random rewards.
Description: This was initially created as a workaround to have some of the empty chests filled with a few random low tier rewards.
Lua:
local rewards = { -- chanceMin, chanceMax, itemID, count
{1, 36}, -- nothing
{37, 46, 2148, 80}, -- gold coin
{47, 55, 2148, 50}, -- gold coin
{56, 64, 2671, 5}, -- ham
{65, 73, 2789, 5}, -- brown mushroom
{74, 81, 7620}, -- mana potion
{82, 87, 7618}, -- health potion
{88, 92, 9811}, -- rusty legs (common)
{93, 96, 9808}, -- rusty armor (common)
{97, 100, 2213} -- dwarven ring
}
function onUse(player, item, fromPosition, target, toPosition, isHotkey)
if (player:getStorageValue(PlayerStorageKeys.crateUsable)) <= os.time() then
local totalChance = math.random(100)
for i = 1, #rewards do
local reward = rewards[i]
if totalChance >= reward[1] and totalChance <= reward[2] then
if reward[3] then
local item = ItemType(reward[3])
local count = reward[4] or 1
player:addItem(reward[3], count)
local str = ("You found %s %s!"):format(count > 1 and count or item:getArticle(), count > 1 and item:getPluralName() or item:getName())
player:say(str, TALKTYPE_MONSTER_SAY, false, player, toPosition)
player:setStorageValue(PlayerStorageKeys.crateUsable, os.time() + 20 * 60 * 60)
else
player:say("You found nothing useful.", TALKTYPE_MONSTER_SAY, false, player, toPosition)
end
break
end
end
else
player:say("You found nothing useful.", TALKTYPE_MONSTER_SAY, false, player, toPosition)
end
return true
end
3. Complete Spellbook
Description: Back when I had developed this, we didn't had any spellbook that showed for example, spells that doesn't have mana cost or that uses mana percent or that don't have level to use, so I had developed this version.
Lua:
function onUse(player, item, fromPosition, target, toPosition, isHotkey)
local text = ""
local tlvl = {}
local tml = {}
for _, spell in ipairs(player:getInstantSpells()) do
if spell.level ~= 0 or spell.mlevel ~= 0 then
if spell.manapercent > 0 then
spell.mana = spell.manapercent .. "%"
end
if spell.level > 0 then
tlvl[#tlvl+1] = spell
elseif spell.mlevel > 0 then
tml[#tml+1] = spell
end
end
end
table.sort(tlvl, function(a, b) return a.level < b.level end)
local prevLevel = -1
for i, spell in ipairs(tlvl) do
local line = ""
if prevLevel ~= spell.level then
if i ~= 1 then
line = "\n"
end
line = line .. "Spells for Level " .. spell.level .. "\n"
prevLevel = spell.level
end
text = text .. line .. " " .. spell.words .. " - " .. spell.name .. " : " .. spell.mana .. "\n"
end
text = text.."\n"
table.sort(tml, function(a, b) return a.mlevel < b.mlevel end)
local prevmLevel = -1
for i, spell in ipairs(tml) do
local line = ""
if prevLevel ~= spell.mlevel then
if i ~= 1 then
line = "\n"
end
line = line .. "Spells for Magic Level " .. spell.mlevel .. "\n"
prevmLevel = spell.mlevel
end
text = text .. line .. " " .. spell.words .. " - " .. spell.name .. " : " .. spell.mana .. "\n"
end
player:showTextDialog(item:getId(), text)
return true
end
4. Animations and "industrial revolution".
Description: Tibia has always had a few elements to show they had arrived the steam era, so to create some animations of doors/bridges being open/lift with steam, I made this systemof a drawbridge moved by steam.
Lua:
local speed = 300
local function airMoving(pos, i, leverpos)
if pos and pos.y then
pos.y = pos.y + 1
end
Position(pos):sendMagicEffect(i <= 4 and 3 or 68)
if i == 5 or i == 6 then
local tile = Tile(pos)
if tile and tile:getItemById(3679) then
tile:getItemById(3679):transform(3681)
if tile:getItemById(i == 5 and 23052 or 23047) then
tile:getItemById(i == 5 and 23052 or 23047):remove()
end
elseif tile and tile:getItemById(3681) then
tile:getItemById(3681):remove()
Game.createItem(i == 5 and 23052 or 23047, 1, Position(pos))
Game.createItem(3679, 1, Position(pos)):setActionId(11203)
end
end
if i < 6 then
addEvent(airMoving, speed, pos, i+1, leverpos)
else
local lever = Tile(leverpos):getItemById(1946)
if lever then
lever:transform(1945)
end
end
return true
end
function onUse(player, item, fromPosition, target, toPosition, isHotkey)
local airpos = {x = toPosition.x, y = toPosition.y, z = toPosition.z}
local leverpos = {x = fromPosition.x, y= fromPosition.y, z = fromPosition.z}
if item:getId() == 1945 then
item:transform(1946)
toPosition.x = airpos.x + 1
toPosition:sendMagicEffect(68)
addEvent(airMoving, speed, airpos, 1, leverpos)
else
player:sendCancelMessage("Wait for the steam to power up.")
end
return true
end
5. Animations for movement through 'unreachable places'
Description: Created this simple script to simulate a player climbing a rope in a dungeon. The place is unreachable but by righting click it you can access and start a animation of the player going up slowly until reaching the hidden area.
Lua:
function onUse(player, item, fromPosition, target, toPosition, isHotkey)
local position = fromPosition
player:teleportTo(toPosition, true)
player:setDirection(0)
local speed = 450
addEvent(moveUp, speed, player:getId(), position, 2, 1, speed)
return true
end
Lua:
function moveUp(uid, position, maxi, ci, speed)
local player = Player(uid)
if player then
position.z = position.z - 1
if ci < maxi then
addEvent(moveUp, speed, uid, position, maxi, ci + 1, speed)
else
position.y = position.y - 1
end
player:teleportTo(position)
player:setDirection(0)
end
return true
end
Lua:
local function moveOut(uid)
local player = Player(uid)
if player then
player:teleportTo(Position(2197, 2614, 6), true)
end
return true
end
function onStepIn(creature, item, position, fromPosition)
local speed = 450
moveDown(creature:getId(), position, 2, 1, speed)
addEvent(moveOut, speed * 2, creature:getId())
return true
end
6. Improving realism on Tibia
Description: One day when I broke a house item that had liquid inside, I have noted the sprite of spilled liquid was on top of the broken item. I have tested on global and noted it also behave like this. It's almost a visual glitch and I believe the person who did it to 'make as close as tibia as possible' seriously should have though this better. Besides this I have added a check to increase the chance of breaking an item based on the attack of the weapon you're using, all directly in the destroyItem function of the lib.
Lua:
function destroyItem(player, item, target, toPosition)
if target == nil or type(target) ~= 'userdata' or not target:isItem() then
return false
end
if target:hasAttribute(ITEM_ATTRIBUTE_UNIQUEID) or target:hasAttribute(ITEM_ATTRIBUTE_ACTIONID) then
return false
end
if toPosition.x == CONTAINER_POSITION then
return false
end
local targetId = target:getId()
local destroyId = ItemType(targetId):getDestroyId()
if destroyId == 0 then
return false
end
if math.random(1, 80) <= ItemType(item.itemid):getAttack() then
if target:isContainer() then
for i = target:getSize() - 1, 0, -1 do
local containerItem = target:getItem(i)
if containerItem then
containerItem:moveTo(toPosition)
end
end
end
target:remove(1)
if target:getFluidType() ~= 0 then
local fluid = Game.createItem(2016, target:getFluidType(), toPosition)
if fluid ~= nil then
fluid:decay()
end
end
local itemn = Game.createItem(destroyId, 1, toPosition)
if itemn ~= nil then
itemn:decay()
end
end
toPosition:sendMagicEffect(CONST_ME_POFF)
return true
end
7. Improving UX
Description: Now tibia UX is a bit better, but when you constantly find yourself taking hands out of arrow keys to press hotkeys you know something was badly designed.
To improve this I've some simple scripts that I have no idea why no one have ever though/done before. Basically when clicking on closed hole/rope spot it checks if you have the shovel/rope and do the action automatically without the need to 'use with' or to have hotkeys.
Lua:
function onUse(player, item, fromPosition, target, toPosition, isHotkey)
if player:getItemCount(2120) > 0 or player:getItemCount(7731) > 0 then
if Tile(toPosition:moveUpstairs()):hasFlag(TILESTATE_PROTECTIONZONE) and player:isPzLocked() then
player:sendTextMessage(MESSAGE_STATUS_SMALL, Game.getReturnMessage(RETURNVALUE_PLAYERISPZLOCKED))
return true
end
player:teleportTo(toPosition, false)
return true
end
return false
end
Lua:
function onUse(player, item, fromPosition, target, toPosition, isHotkey)
if player:getItemCount(2554) > 0 or player:getItemCount(5710) > 0 then
item:transform(item.itemid+1)
item:decay()
return true
end
return false
end
8. Improving Fishing experience
Description: The fishing system in tibia is nearly the same as it was from the day it was implemented. Inspired by a few systems I've seen in the forums, I've built a new version that gives you different fishes and also gives it based on their weight/chance considering your fishing skill.
Lua:
local waterIds = {493, 4608, 4609, 4610, 4611, 4612, 4613, 4614, 4615, 4616, 4617, 4618, 4619, 4620, 4621, 4622, 4623, 4624, 4625, 4665, 7236, 10499, 15401, 15402}
local lootTrash = {2234, 2238, 2376, 2509, 2667}
local lootCommon = {2152, 2167, 2168, 2669, 7588, 7589}
local lootRare = {2143, 2146, 2149, 7158, 7159}
local lootVeryRare = {7632, 7633, 10220}
local useWorms = true
function onUse(player, item, fromPosition, target, toPosition, isHotkey)
local targetId = target.itemid
if not isInArray(waterIds, target.itemid) then
return false
end
if targetId == 10499 then
local owner = target:getAttribute(ITEM_ATTRIBUTE_CORPSEOWNER)
if owner ~= 0 and owner ~= player:getId() then
player:sendTextMessage(MESSAGE_STATUS_SMALL, "You are not the owner.")
return true
end
toPosition:sendMagicEffect(CONST_ME_WATERSPLASH)
target:remove()
local rareChance = math.random(1, 100)
if rareChance == 1 then
player:addItem(lootVeryRare[math.random(#lootVeryRare)], 1)
elseif rareChance <= 3 then
player:addItem(lootRare[math.random(#lootRare)], 1)
elseif rareChance <= 10 then
player:addItem(lootCommon[math.random(#lootCommon)], 1)
else
player:addItem(lootTrash[math.random(#lootTrash)], 1)
end
return true
end
if targetId ~= 7236 then
toPosition:sendMagicEffect(CONST_ME_LOSEENERGY)
end
if targetId == 493 or targetId == 15402 then
return true
end
if math.random(1, 100) <= math.min(math.max(10 + (player:getEffectiveSkillLevel(SKILL_FISHING) - 10) * 0.597, 10), 50) then
if useWorms and not player:removeItem(3976, 1) then
return true
end
player:addSkillTries(SKILL_FISHING, 1)
if targetId == 15401 then
target:transform(targetId + 1)
target:decay()
if math.random(1, 100) >= 97 then
player:addItem(15405, 1)
return true
end
elseif targetId == 7236 then
target:transform(targetId + 1)
target:decay()
local rareChance = math.random(1, 100)
if rareChance == 1 then
player:addItem(7158, 1)
return true
elseif rareChance <= 4 then
player:addItem(2669, 1)
return true
elseif rareChance <= 10 then
player:addItem(7159, 1)
return true
end
end
local weight = math.random(18, math.min(math.max(10 + (player:getEffectiveSkillLevel(SKILL_FISHING) - 10) * 0.597, 10), 50) * 4) * 10
local fishid = 2667 -- Fish
if weight >= 1500 then
fishid = 7963 -- Marlin
elseif weight >= 1200 then
fishid = 7158 -- Rainbow Trout
elseif weight >= 1000 then
fishid = 2669 -- Northern Pike
elseif weight >= 800 then
fishid = 7159 --Green Perch
end
local item = Game.createItem(fishid, 1)
item:setAttribute("weight", weight) -- peso
item:setAttribute(ITEM_ATTRIBUTE_WRITER, player:getGuid()) -- quem pescou
item:setAttribute(ITEM_ATTRIBUTE_DATE, os.time()) -- quando
player:addItemEx(item, true)
end
return true
end
9. The best mining-cart system you have ever saw!
Description: Whoever here comes from pre 7.9 era, those kinda of systems were pretty hyped back then. It's a system where you click on a mining cart and you go through the route in the rails.
Lua:
-[[
DIRECTION_NORTH 0
DIRECTION_EAST 1
DIRECTION_SOUTH 2
DIRECTION_WEST 3
]]
local DIRECTIONERS = {
[7121] = DIRECTION_NORTH,
[7122] = DIRECTION_NORTH,
[7123] = {DIRECTION_EAST, DIRECTION_SOUTH},
[7124] = {DIRECTION_WEST, DIRECTION_SOUTH},
[7125] = {DIRECTION_EAST, DIRECTION_NORTH},
[7126] = {DIRECTION_WEST, DIRECTION_NORTH},
[7127] = {
[DIRECTION_NORTH] = DIRECTION_NORTH,
[DIRECTION_EAST] = DIRECTION_NORTH, -- Not Valid
[DIRECTION_SOUTH] = {DIRECTION_SOUTH, DIRECTION_WEST}, -- WEST as optional
--[DIRECTION_WEST] = DIRECTION_NORTH, --Not Valid
},
[7128] = {
[DIRECTION_NORTH] = DIRECTION_WEST,
[DIRECTION_EAST] = {DIRECTION_EAST, DIRECTION_SOUTH}, -- SOUTH as optional
--[DIRECTION_SOUTH] = DIRECTION_EAST, -- Not Valid
[DIRECTION_WEST] = DIRECTION_WEST,
},
[7129] = {
--[DIRECTION_NORTH] = DIRECTION_NORTH, -- Not Valid
[DIRECTION_EAST] = {DIRECTION_EAST, DIRECTION_NORTH}, -- NORTH as optional
[DIRECTION_SOUTH] = DIRECTION_WEST,
[DIRECTION_WEST] = DIRECTION_WEST,
},
[7130] = {
[DIRECTION_NORTH] = DIRECTION_NORTH,
--[DIRECTION_EAST] = DIRECTION_NORTH, -- Not Valid
[DIRECTION_SOUTH] = {DIRECTION_SOUTH, DIRECTION_EAST}, -- EAST as optional
[DIRECTION_WEST] = DIRECTION_NORTH,
},
}
local RAILS = {7121, 7122, 7123, 7124, 7125, 7126, 7127, 7128, 7129, 7130, 7133, 7134, 7135, 7136}
local CART = {[0] = 7132, [2] = 7132, [3] = 7131, [1] = 7131}
local reverse = {[0] = 2, 3, 0, 1} -- All that table was made by nord.
local stoppers = {7133, 7134, 7135, 7136}
local function getNextDir(uid, direction)
local rail = Item(uid)
if not rail then return direction end
local rar = rail.itemid
local tab = DIRECTIONERS[rar]
if tab and type(tab) == 'table' then
if rar >= 7127 then
--print("Dir: ".. direction .." e tab[dir]: " .. tab[direction] ..".")
local choice = tab[direction]
if tab[direction] and type(tab[direction]) == 'table' then
if rail.actionid == 11218 then
choice = tab[direction][2]
else
choice = tab[direction][1]
end
end
return choice or direction
else
return tab[tab[1] == reverse[direction] and 2 or 1]
end
end
return direction
end
local function findRail(p)
local p_ = {x=p.x, y=p.y, z=p.z}
for i=0,10 do
p_.stackpos = i
local t = getTileThingByPos(p_)
if isInArray(RAILS, t.itemid) then
return t.itemid, t.uid
end
end
return false
end
local function walkingCart(uid, direction, change)
local player = Player(uid)
if not player then return false end
-- Change outfit on next iteration
local pos = player:getPosition()
pos:getNextPosition(direction)
if change then
doSetItemOutfit(uid, CART[direction], -1, CART_CONDITION_SUBID)
change = false
end
---
local rar, rail = findRail(pos)
if not rar or isInArray(stoppers, rar) then
player:setNoMove(false)
player:removeCondition(CONDITION_OUTFIT, CONDITIONID_COMBAT, CART_CONDITION_SUBID) -- Remove a condição de outfit com subid do cart
player:removeCondition(CONDITION_OUTFIT) -- Remove qualquer outra condição que continue sobrando
local tmp1 = player:getPosition()
local tmp2 = player:getPosition()
if direction % 2 == 0 then
tmp1.x = tmp1.x + 1
tmp2.x = tmp2.x - 1
else
tmp1.y = tmp1.y + 1
tmp2.y = tmp2.y - 1
end
if tmp2:isWalkable(false, false, true, true, true) then
player:teleportTo(tmp2, true)
elseif tmp1:isWalkable(false, false, true, true, true) then
player:teleportTo(tmp1, true)
end
return false
else
direction = getNextDir(rail, direction)
change = true
end
---
player:teleportTo(pos, true)
local speed = player:getSpeed() / 2
local delay = 400 - (math.min(math.max(speed, 100), 750) / 3)
if speed < 200 then delay = delay + (2 * (200 - speed)) end
addEvent(walkingCart, delay, uid, direction, change)
return true
end
function onUse(player, item, fromPosition, target, toPosition, isHotkey)
if player:getCondition(CONDITION_INVISIBLE) then
player:removeCondition(CONDITION_INVISIBLE)
end
if player:getCondition(CONDITION_OUTFIT, CONDITIONID_COMBAT, CART_CONDITION_SUBID) then
return false
end
player:teleportTo(toPosition, true)
player:setNoMove(true)
addEvent(doSetCreatureOutfit, 100, player:getId(), {lookTypeEx = item:getId()}, -1, CART_CONDITION_SUBID)
local initialDir = DIRECTION_NORTH
local initialPos = Position(toPosition.x, toPosition.y, toPosition.z)
if item:getId() == 7132 then
initialPos:getNextPosition(DIRECTION_NORTH)
local rar = findRail(initialPos)
initialDir = (rar and not isInArray(stoppers, rar)) and DIRECTION_NORTH or DIRECTION_SOUTH
elseif item:getId() == 7131 then
initialPos:getNextPosition(DIRECTION_EAST)
local rar = findRail(initialPos)
initialDir = (rar and not isInArray(stoppers, rar)) and DIRECTION_EAST or DIRECTION_WEST
end
addEvent(walkingCart, 250, player:getId(), initialDir)
return true
end
Post automatically merged:
@Don Daniello the auto merge system is preventing me to continue this thread.
Everything I try to type it says I've reached the limit of 25k characters. Can you please look into it?
Last edited: