Darkhaos' Pet System 2.0
This pet system brings a lot of features, of course more features will be added with the new versions.
V1.0 Rev: 4.
Update Log:
//TODO
First of all, add this lib to your server.
Now create a file on data/talkactions/scripts called pet.lua and paste:
Paste this at talkactions.xml:
Create a fil called petkill.lua at data/creaturescripts/scripts and paste:
Create a file called petdeath.lua at data/creaturescripts/scripts and paste:
Create a file called petstats.lua at data/creaturescripts/scripts and paste:
Add this to your creaturescripts/scripts/login.lua:
Now paste this at creaturescripts.xml:
You need to paste this variable on every monster file that you use as pet, after </flags>.
This is the script to revive the pet via npc:
Monster should have <flag convinceable="1"/>
This is how you configure a new pet (Water Elemental used as example, only one attack used as example):
The pet system commands are:
NOTE: I used tibia creatures, remember that these creatures already has attacks, you can create new creatures to use it as pet.
Any bug you find, post here.
Enjoy.
This pet system brings a lot of features, of course more features will be added with the new versions.
V1.0 Rev: 4.
Update Log:
Code:
[B]V2.0 Rev: 1:[/B]
[B]Rev: 1 - [01 / 10 / 2011][/B]
Added posibility to buy pets, and also have more than one pet.
Fixed some bugs.
[B]V1.0 Rev: 4:[/B]
[B]Rev: 4 - [08 / 08 / 2011][/B]
Fixed some bugs and changes were made, lib was updated.
Added a new script to preven pet attacks owner (petstats.lua).
[B]Rev: 3 - [07 / 08 / 2011][/B]
Fixed some bugs, lib was updated.
Added a new command only for gamemasters.
Now pets uses mana to attack. Added the new variable 'mana' to the attacks config.
Now you can't call pet on protection zone.
Fixed some text erros.
[B]Rev: 2 - [07 / 08 / 2011][/B]
Optimized playerDeath.lua
Optimized playerKill.lua
Lib was updated, added a missing function.
- Level system. Pet gain level and experience.
- Pet gain ticks like players (Health, Mana) you can configure time and amount.
- Pet gain health/mana for each level, like players. You can configure how many.
- You can configure the pet's exp-rate.
- Pets can carry items. You can configure how many.
- You can block items, so pets won't carry these items.
- You can configure how much cost to revive a pet.
- You can add how many pets you want.
- You can configure level required for each pet.
- You can configure vocations that can use that pet.
- You can add/remove and configure attacks for each pet.
- This pet system supports area spells and distance spells.
- You can configure the level that a pet needs to use an attack.
- You can confugre the level that player needs to use an attack.
- You can configure range (Distance attacks) for each attack.
- For now, attacks works only with normal areas (Circles). Support for wave/beam areas will be added soon.
- You can move your pet with a command.
- You can make pets says the text you want.
First of all, add this lib to your server.
Lua:
--Circles
AREA_CIRCLE2X2 =
{
{0, 1, 1, 1, 0},
{1, 1, 1, 1, 1},
{1, 1, 3, 1, 1},
{1, 1, 1, 1, 1},
{0, 1, 1, 1, 0}
}
AREA_CIRCLE3X3 =
{
{0, 0, 1, 1, 1, 0, 0},
{0, 1, 1, 1, 1, 1, 0},
{1, 1, 1, 1, 1, 1, 1},
{1, 1, 1, 3, 1, 1, 1},
{1, 1, 1, 1, 1, 1, 1},
{0, 1, 1, 1, 1, 1, 0},
{0, 0, 1, 1, 1, 0, 0}
}
-- Crosses
AREA_CROSS1X1 =
{
{0, 1, 0},
{1, 3, 1},
{0, 1, 0}
}
AREA_CROSS5X5 =
{
{0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0},
{0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0},
{0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0},
{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0},
{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0},
{1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1},
{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0},
{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0},
{0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0},
{0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0},
{0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0}
}
AREA_CROSS6X6 =
{
{0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0},
{0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0},
{0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0},
{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0},
{1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1},
{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0},
{0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0},
{0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0},
{0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0}
}
--Squares
AREA_SQUARE1X1 =
{
{1, 1, 1},
{1, 3, 1},
{1, 1, 1}
}
-- Walls
AREA_WALLFIELD = {
{1, 1, 3, 1, 1}
}
AREADIAGONAL_WALLFIELD =
{
{0, 0, 0, 0, 1},
{0, 0, 0, 1, 1},
{0, 1, 3, 1, 0},
{1, 1, 0, 0, 0},
{1, 0, 0, 0, 0},
}
emoteAttack = true
petEmoteAttack = true
refillStatsAtLevel = true
TYPE_NEAR = 1
TYPE_DISTANCE = 2
petExhaust = 3 --in seconds
petSayExhaust = 3 --in seconds
petGainTicks =
{
health = {func = doCreatureAddHealth, time = 3000, count = 1},
mana = {func = doCreatureAddMana, time = 3000, count = 2}
}
petGainHealth = 30
petGainMana = 30
petExpRate = 1.3
carryItems = 10
petItemsBase = 1250
petItems = {}
blockedItems = {6132, 2195}
for i = 1, carryItems do
table.insert(petItems, petItemsBase + i)
end
petBase = 6120
petStorages =
{
pet = petBase + 1000,
level = petBase + 2000,
exp = petBase + 3000,
items = petBase + 4000,
isPet = petBase + 5000,
isDead = petBase + 6000,
exhaust = petBase + 7000,
sayExhaust = petBase + 8000,
moveExhaust = petBase + 9000,
health = petBase + 10000,
mana = petBase + 11000,
buyed = petBase + 12000
}
tables =
{
pet = {},
level = {},
exp = {},
items = {},
isDead = {},
exhaust = {},
health = {},
mana = {},
buyed = {}
}
reviveCost = 1000
pets =
{
[1] =
{
monster = "orc spearman",
vocations = {1, 2, 3, 4},
level = 30,
attacks =
{
[1] = {name = "", level = 30, petLevel = 30, mana = 50, type = TYPE_DISTANCE, range = 10, combat = COMBAT_PHYSICALDAMAGE, effect = CONST_ME_NONE, distEffect = CONST_ANI_SPEAR, damageMin = 0.7, damageMax = 1.0}
}
},
[2] =
{
monster = "fire devil",
vocations = {1, 2},
level = 60,
attacks =
{
[1] = {name = "Fire Strike", level = 60, petLevel = 60, mana = 100, type = TYPE_DISTANCE, combat = COMBAT_FIREDAMAGE, effect = CONST_ME_FIREDAMAGE, distEffect = CONST_ANI_FIRE, damageMin = 1.5, damageMax = 2.0}
}
},
[3] =
{
monster = "minotaur guard",
vocations = {3, 4},
level = 60,
cost = 50,
attacks =
{
[1] = {name = "Punch", level = 60, petLevel = 60, mana = 55, type = TYPE_NEAR, combat = COMBAT_PHYSICALDAMAGE, effect = CONST_ME_BLOOD, distEffect = CONST_ANI_NONE, damageMin = 1.5, damageMax = 2.0}
}
},
[4] =
{
monster = "water elemental",
vocations = {2},
level = 95,
cost = 15000,
attacks =
{
[1] = {name = "Ice Bomb", level = 100, petLevel = 100, mana = 250, type = TYPE_DISTANCE, range = 3, combat = COMBAT_ICEDAMAGE, effect = CONST_ME_ICEAREA, distEffect = CONST_ANI_ICE, damageMin = 1.5, damageMax = 2.0, area = AREA_CROSS1X1},
[2] = {name = "Poison Strike", level = 95, petLevel = 95, mana = 80, type = TYPE_DISTANCE, range = 8, combat = COMBAT_POISONDAMAGE, effect = CONST_ME_GREENRINGS, distEffect = CONST_ANI_POISON, damageMin = 1.2, damageMax = 1.8}
}
},
[5] =
{
monster = "fire elemental",
vocations = {1},
level = 95,
attacks =
{
[1] = {name = "Fire Bomb", level = 100, petLevel = 100, mana = 250, type = TYPE_DISTANCE, range = 3, combat = COMBAT_FIREDAMAGE, effect = CONST_ME_FIRE, distEffect = CONST_ANI_FIRE, damageMin = 1.5, damageMax = 2.0, area = AREA_CROSS1X1},
[2] = {name = "Fire Strike", level = 95, petLevel = 95, mana = 80, type = TYPE_DISTANCE, range = 3, combat = COMBAT_FIREDAMAGE, effect = CONST_ME_FIREDAMAGE, distEffect = CONST_ANI_FIRE, damageMin = 1.2, damageMax = 1.8}
}
},
[6] =
{
monster = "orc warlord",
vocations = {4},
level = 95,
attacks =
{
[1] = {name = "Throw Knife", level = 95, petLevel = 95, mana = 80, type = TYPE_DISTANCE, combat = COMBAT_PHYSICALDAMAGE, effect = CONST_ME_BLOOD, distEffect = CONST_ANI_THROWINGKNIFE, damageMin = 1.5, damageMax = 2.0}
}
},
[7] =
{
monster = "golem",
vocations = {3},
level = 95,
attacks =
{
[1] = {name = "Throw Stone", level = 95, petLevel = 95, mana = 80, type = TYPE_DISTANCE, combat = COMBAT_PHYSICALDAMAGE, effect = CONST_ME_BLOOD, distEffect = CONST_ANI_LARGEROCK, damageMin = 1.5, damageMax = 2.0}
}
},
[8] =
{
monster = "wyrm",
vocations = {1, 2, 3, 4},
level = 135
},
[9] =
{
monster = "dragon lord",
vocations = {1, 2, 3, 4},
level = 200,
attacks =
{
[1] = {name = "Fire Bomb", level = 200, petLevel = 200, mana = 300, type = TYPE_DISTANCE, range = 3, combat = COMBAT_FIREDAMAGE, effect = CONST_ME_FIREAREA, distEffect = CONST_ANI_FIRE, damageMin = 1.5, damageMax = 2.0, area = AREA_CROSS1X1},
[2] = {name = "Fire Storm", level = 205, petLevel = 220, mana = 700, type = TYPE_NEAR, range = 8, combat = COMBAT_FIREDAMAGE, effect = CONST_ME_FIREAREA, distEffect = CONST_ANI_FIRE, damageMin = 1.8, damageMax = 2.5, area = AREA_CROSS5X5},
[3] = {name = "Fire Explosion", level = 203, petLevel = 210, mana = 450, type = TYPE_DISTANCE, range = 6, combat = COMBAT_FIREDAMAGE, effect = CONST_ME_EXPLOSIONHIT, distEffect = CONST_ANI_FIRE, damageMin = 1.5, damageMax = 2.1, area = AREA_CIRCLE2X2},
[4] = {name = "Scratch", level = 201, petLevel = 205, mana = 150, type = TYPE_NEAR, range = 1, combat = COMBAT_PHYSICALDAMAGE, effect = CONST_ME_HITAREA, distEffect = CONST_ANI_FIRE, damageMin = 1.3, damageMax = 1.8},
[5] = {name = "Fire Strike", level = 203, petLevel = 210, mana = 100, type = TYPE_DISTANCE, range = 3, combat = COMBAT_FIREDAMAGE, effect = CONST_ME_EXPLOSIONAREA, distEffect = CONST_ANI_FIRE, damageMin = 1.8, damageMax = 1.9}
}
}
}
for i = 1, #pets do
table.insert(tables.pet, petStorages.pet + i)
table.insert(tables.level, petStorages.level + i)
table.insert(tables.exp, petStorages.exp + i)
table.insert(tables.items, petStorages.items + i)
table.insert(tables.isDead, petStorages.isDead + i)
table.insert(tables.exhaust, petStorages.exhaust + i)
table.insert(tables.health, petStorages.health + i)
table.insert(tables.mana, petStorages.mana + i)
table.insert(tables.buyed, petStorages.buyed + i)
end
function gainStat(pid, stat)
if pid and pid > 0 and isMonster(pid) then
stat.func(pid, stat.count)
end
addEvent(gainStat, stat.time, pid, stat)
end
function getLevelByExp(exp)
return math.floor((math.sqrt(3) * math.sqrt(243*(exp+1)^2-48600*(exp+1)+3680000)+27 * (exp+1)-2700)^(1/3)/30^(2/3)-(5*10^(2/3))/(3^(1/3)*(math.sqrt(3)*math.sqrt(243*(exp+1)^2-48600*(exp+1)+3680000)+27*(exp+1)-2700)^(1/3))+2)
end
function getPetInfo(pet)
if isNumber(pet) then
return pets[pet] or false
else
for _, v in pairs(pets) do
if pet:lower() == v.monster then
return v
end
end
end
return false
end
function getPetByLevel(cid)
local level = getPlayerLevel(cid)
local pet
for i = 1, #pets do
v = pets[i]
if level >= v.level and isInArray(v.vocations, getPlayerVocation(cid)) then
pet = v
end
end
return (pet ~= nil and pet or false)
end
function getAttackFormula(pid, attack)
return {
min = ((getPetLevel(pid) * 2) * (1 + attack.damageMin) + getPetLevel(pid)) / 2.5,
max = ((getPetLevel(pid) * 3) * (1 + attack.damageMax) + getPetLevel(pid)) / 2.5
}
end
function doAttack(pid, target, param)
local pet = getPetInfo(getCreatureName(pid))
if pet then
if pet.attacks and pet.attacks[param] then
local attack = pet.attacks[param]
if target > 0 and pid ~= getCreatureTarget(getCreatureMaster(pid)) then
if attack.type and attack.type == TYPE_DISTANCE and getDistanceBetween(getCreaturePosition(pid), getCreaturePosition(target)) > (attack.range or 3) or attack.type == TYPE_NEAR and getDistanceBetween(getCreaturePosition(pid), getCreaturePosition(target)) > (attack.range or 1) then
return doPlayerSendCancel(getCreatureMaster(pid), "Target is too far.") and doSendMagicEffect(getCreaturePosition(pid), CONST_ME_POFF)
end
if not isSightClear(getCreaturePosition(pid), getCreaturePosition(target), true) then
return doPlayerSendCancel(getCreatureMaster(pid), "There is not enough room.") and doSendMagicEffect(getCreaturePosition(pid), CONST_ME_POFF)
end
else
return doPlayerSendCancel(getCreatureMaster(pid), "Please select a target first.") and doSendMagicEffect(getCreaturePosition(pid), CONST_ME_POFF)
end
if getPlayerLevel(getCreatureMaster(pid)) < attack.level then
return doPlayerSendCancel(getCreatureMaster(pid), "You need level " .. attack.level .. " or higher to use this attack.") and doSendMagicEffect(getCreaturePosition(pid), CONST_ME_POFF)
end
if getPetLevel(pid) < attack.petLevel then
return doPlayerSendCancel(getCreatureMaster(pid), "Your pet needs level " .. attack.petLevel .. " or higher to use this attack.") and doSendMagicEffect(getCreaturePosition(pid), CONST_ME_POFF)
end
if attack.mana and getCreatureMana(pid) < attack.mana then
return doPlayerSendCancel(getCreatureMaster(pid), "Your pet does not have enough mana.") and doSendMagicEffect(getCreaturePosition(pid), CONST_ME_POFF)
end
if attack.area then
doCastAreaAttack(pid, target, attack.area, attack)
else
doTargetCombatHealth(pid, target, attack.combat, -getAttackFormula(pid, attack).min, -getAttackFormula(pid, attack).max, attack.effect)
end
doCreatureAddMana(pid, -attack.mana)
if attack.name ~= "" then
if emoteAttack then
doCreatureSay(getCreatureMaster(pid), getCreatureName(pid) .. ", use " .. attack.name .. "!", TALKTYPE_SAY)
end
if petEmoteAttack then
doCreatureSay(pid, attack.name, TALKTYPE_MONSTER)
end
end
return doSendDistanceShoot(getCreaturePosition(pid), (attack.type == TYPE_DISTANCE and getCreaturePosition(target) or getCreaturePosition(pid)), attack.distEffect)
else
return doSendMagicEffect(getCreaturePosition(pid), CONST_ME_POFF)
end
else
return doSendMagicEffect(getCreaturePosition(pid), CONST_ME_POFF) and doPlayerSendCancel(getCreatureMaster(pid), "There is a tecnical problem, please contact a gamemaster.")
end
end
function callPet(cid, petId)
for i = 1, #tables do
for y = 1, #tables[i] do
if getCreatureStorage(cid, tables[i][y]) < 0 then
doCreatureSetStorage(cid, tables[i][y], 0)
end
end
end
local pet = getPetInfoById(petId)
if not pet then
return doPlayerSendCancel(cid, "Something is wrong.")
end
if getPlayerLevel(cid) < pet.level then
return doPlayerSendCancel(cid, "You need level " .. pet.level .. " or higher to call this pet.")
end
if pet.cost and pet.cost > 0 then
if getCreatureStorage(cid, tables.buyed[petId]) < 1 then
return doPlayerSendCancel(cid, "You need to buy this pet first.") and doSendMagicEffect(getCreaturePosition(cid), CONST_ME_POFF)
end
end
local ret = doCreateMonster(pet.monster, getCreaturePosition(cid))
if getCreatureStorage(cid, tables.level[petId]) < 1 then
doCreatureSetStorage(cid, tables.level[petId], pet.level)
end
if getCreatureStorage(cid, tables.exp[petId]) < 1 then
doCreatureSetStorage(cid, tables.exp[petId], getExperienceForLevel(pet.level))
end
if getCreatureStorage(cid, tables.level[petId]) < pet.level then
doCreatureSetStorage(cid, tables.level[petId], pet.level)
elseif getPlayerLevel(cid) * 3 < getCreatureStorage(cid, tables.level[petId]) then
doCreatureSetStorage(cid, tables.level[petId], pet.level)
doCreatureSetStorage(cid, tables.exp[petId], getExperienceForLevel(pet.level))
end
if getLevelByExp(getCreatureStorage(cid, tables.exp[petId])) < getCreatureStorage(cid, tables.level[petId]) then
doCreatureSetStorage(cid, tables.exp[petId], getExperienceForLevel(getCreatureStorage(cid, tables.level[petId])))
end
if getCreatureStorage(cid, tables.health[petId]) < 1 then
doCreatureSetStorage(cid, tables.health[petId], getCreatureStorage(cid, tables.level[petId]) * petGainHealth)
end
if getCreatureStorage(cid, tables.mana[petId]) < 1 then
doCreatureSetStorage(cid, tables.mana[petId], getCreatureStorage(cid, tables.level[petId]) * petGainMana)
end
doCreatureSetStorage(ret, tables.level[petId], getCreatureStorage(cid, tables.level[petId]))
doCreatureSetStorage(ret, tables.exp[petId], getCreatureStorage(cid, tables.exp[petId]))
doConvinceCreature(cid, ret)
doCreatureSetStorage(ret, petStorages.isPet, 1)
doSendMagicEffect(getCreaturePosition(ret), CONST_ME_TELEPORT)
setCreatureMaxHealth(ret, getPetLevel(ret) * petGainHealth)
doCreatureAddHealth(ret, -getCreatureHealth(ret) + getCreatureStorage(cid, tables.health[petId]))
setCreatureMaxMana(ret, getPetLevel(ret) * petGainMana)
doCreatureAddMana(ret, -getCreatureMana(ret) + getCreatureStorage(cid, tables.mana[petId]))
for _, v in pairs(petGainTicks) do
gainStat(ret, v)
end
return doCreatureSay(cid, "Go pet!", TALKTYPE_SAY)
end
function doCastAreaAttack(pid, target, area, attack)
local center = {}
local areaxx = {}
center.y = math.floor(#area/2)+1
for y = 1, #area do
for x = 1, #area[y] do
local number = area[y][x]
if number > 0 then
center.x = math.floor(table.getn(area[y])/2)+1
if attack.type == TYPE_DISTANCE then
table.insert(areaxx, {x = getCreaturePosition(target).x + x - center.x, y = getCreaturePosition(target).y + y - center.y, z = getCreaturePosition(target).z})
else
table.insert(areaxx, {x = getCreaturePosition(pid).x + x - center.x, y = getCreaturePosition(pid).y + y - center.y, z = getCreaturePosition(pid).z})
end
end
end
end
for i = 1, #areaxx do
doAreaCombatHealth(pid, attack.combat, areaxx[i], 0, -getAttackFormula(pid, attack).min, -getAttackFormula(pid, attack).max, attack.effect)
end
end
function isPet(pid)
local id = getPetIdByName(getCreatureName(pid))
return getCreatureStorage(pid, petStorages.isPet) > 0 and true or false
end
function doPetAddExperience(pid, exp)
local id = getPetIdByName(getCreatureName(pid))
exp = math.ceil(exp)
doCreatureSetStorage(getCreatureMaster(pid), tables.exp[id], getPetExperience(pid) + exp)
doSendAnimatedText(getCreaturePosition(pid), exp, getConfigValue("gainExperienceColor"))
return true
end
function getPetExperience(pid)
local id = getPetIdByName(getCreatureName(pid))
return getCreatureStorage(getCreatureMaster(pid), tables.exp[id])
end
function getPetLevel(pid)
local id = getPetIdByName(getCreatureName(pid))
return getCreatureStorage(getCreatureMaster(pid), tables.level[id])
end
function doPetSetLevel(pid, level)
local id = getPetIdByName(getCreatureName(pid))
doCreatureSetStorage(getCreatureMaster(pid), tables.level[id], level)
setCreatureMaxHealth(pid, getPetLevel(pid) * petGainHealth)
setCreatureMaxMana(pid, getPetLevel(pid) * petGainMana)
return true
end
function getPlayerPet(cid)
local pet
if #getCreatureSummons(cid) < 1 then
pet = false
end
for _, it in ipairs(getCreatureSummons(cid)) do
if isPet(it) then
pet = it
break
end
end
return pet
end
function getPetIdByName(name)
local id = 0
for k, it in pairs(pets) do
if it.monster:lower() == name:lower() then
id = k
break
end
end
return (id > 0 and id or false)
end
function getTopItem(p)
p.stackpos = 0
local v = getThingFromPos(p)
repeat
p.stackpos = p.stackpos + 1
v = getThingFromPos(p)
until v.itemid == 0
p.stackpos = p.stackpos - 1
return getThingFromPos(p)
end
function getPetInfoById(pet)
for k, v in pairs(pets) do
if k == pet then
return v
end
end
return false
end
if not getCreatureStorage then
getCreatureStorage = getPlayerStorageValue
doCreatureSetStorage = doPlayerSetStorageValue
end
Now create a file on data/talkactions/scripts called pet.lua and paste:
Lua:
local l =
{
["north"] = NORTH,
["east"] = EAST,
["south"] = SOUTH,
["west"] = WEST,
["southwest"] = SOUTHWEST,
["southeast"] = SOUTHEAST,
["northwest"] = NORTHWEST,
["northeast"] = NORTHEAST
}
function onSay(cid, words, param, channel)
if(param == '') then
doPlayerSendTextMessage(cid, MESSAGE_STATUS_CONSOLE_BLUE, "Command param required.")
return true
end
if param:lower() == "info" then
local pet = getPlayerPet(cid)
if pet then
local pet_ = getPetInfo(getCreatureName(pet))
local attacks = ""
if pet_.attacks and #pet_.attacks > 0 then
for i = 1, #pet_.attacks do
attacks = attacks .. "Attack ID: " .. i .. "\n" .. (pet_.attacks[i].name ~= "" and " Name: " .. pet_.attacks[i].name .. "\n" or "") .. " Level: " .. pet_.attacks[i].level .. "\n Pet level: " .. pet_.attacks[i].petLevel .. "\n Pet mana: " .. pet_.attacks[i].mana .. "\n"
end
end
return doShowTextDialog(cid, 1948, "Here is your pet info:\n" ..
"\nName: " .. getCreatureName(pet) ..
"\nHealth: " .. getCreatureHealth(pet) .. "-" .. getCreatureMaxHealth(pet) ..
"\nMana: " .. getCreatureMana(pet) .. "-" .. getCreatureMaxMana(pet) ..
"\nLevel: " .. getPetLevel(pet) ..
"\nExperience: " .. getPetExperience(pet) ..
"\n-----Attacks-----\n" .. (attacks ~= "" and attacks or "No attacks"))
else
return doPlayerSendCancel(cid, "Please call your pet first.")
end
elseif param:lower() == "take" then
local pet = getPlayerPet(cid)
if not pet then
return doPlayerSendCancel(cid, "Please call your pet first.")
end
local slot = 0
for i = 1, carryItems do
if getCreatureStorage(cid, petItems[i]) < 1 then
slot = i
break
end
end
if slot == 0 then
return doPlayerSendCancel(cid, "You only can carry " .. carryItems .. " items.")
end
local item = getTopItem(getCreaturePosition(pet))
if getItemWeightById(item.itemid, 1) and getItemWeightById(item.itemid, 1) < 1 then
return doPlayerSendCancel(cid, "There is no item to carry.")
end
if isInArray(blockedItems, item.itemid) then
return doPlayerSendCancel(cid, "You cannot carry this item.")
end
doCreatureSetStorage(cid, petItems[slot], item.itemid * 1000 + (item.type > 0 and item.type or 1))
doRemoveItem(item.uid)
doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, "You take an item.")
return true
elseif param:lower() == "items" then
local list = "Here is the list of items:\n"
for i = 1, carryItems do
if getCreatureStorage(cid, petItems[i]) < 1 then
list = list .. "\n" .. i .. ". Empty."
else
local thing = getCreatureStorage(cid, petItems[i])
local item = math.floor(thing / 1000)
local count = thing - item * 1000
list = list .. "\n" .. i .. ". x" .. count .. " " .. getItemNameById(item) .. "."
end
end
return doShowTextDialog(cid, 1948, list)
elseif param:lower() == "back" then
local pet = getPlayerPet(cid)
if not pet then
return doPlayerSendCancel(cid, "You don't have any pet released.")
end
local petId = getPetIdByName(getCreatureName(pet))
doCreatureSetStorage(cid, tables.health[petId], getCreatureHealth(pet))
doCreatureSetStorage(cid, tables.mana[petId], getCreatureMana(pet))
doSendMagicEffect(getCreaturePosition(pet), CONST_ME_POFF)
doRemoveCreature(pet)
return doCreatureSay(cid, "It's enough!", TALKTYPE_SAY)
else
param = string.explode(param, ":")
if param[1]:lower() == "call" then
if not param[2] or param[2] == "" then
return doPlayerSendCancel(cid, "No pet specified.")
end
if getTilePzInfo(getCreaturePosition(cid)) then
return doPlayerSendCancel(cid, "You cannot call a pet in protection zone.")
end
if #getCreatureSummons(cid) > 0 then
return doPlayerSendCancel(cid, "You only can call a pet.")
end
local pet = getPetIdByName(param[2])
if pet then
if getCreatureStorage(cid, tables.isDead[pet]) > 0 then
return doPlayerSendCancel(cid, "You need to revive this pet first.")
end
return callPet(cid, pet)
else
return doPlayerSendCancel(cid, "There is not pet with that name.")
end
elseif param[1]:lower() == "attack" then
local pet = getPlayerPet(cid)
if not pet then
return doPlayerSendCancel(cid, "Please call your pet first.")
end
if not param[2] or not isNumber(param[2]) or tonumber(param[2]) < 1 then
return doPlayerSendCancel(cid, "No attack index specified.")
end
if exhaustion.get(cid, petStorages.exhaust) then
return doPlayerSendCancel(cid, "You are exhausted.") and doSendMagicEffect(getCreaturePosition(getCreatureSummons(cid)[1]), CONST_ME_POFF)
end
doAttack(pet, getCreatureTarget(cid), tonumber(param[2]))
exhaustion.set(cid, petStorages.exhaust, petExhaust)
elseif param[1]:lower() == "give" then
local pet = getPlayerPet(cid)
if not pet then
return doPlayerSendCancel(cid, "Please call your pet first.")
end
if not param[2] or not isNumber(param[2]) or tonumber(param[2]) < 1 then
return doPlayerSendCancel(cid, "No slot specified.")
end
if getCreatureStorage(cid, petItems[tonumber(param[2])]) > 0 then
local thing = getCreatureStorage(cid, petItems[tonumber(param[2])])
local item = math.floor(thing/1000)
local count = thing-item*1000
doPlayerAddItem(cid, item, count)
doCreatureSetStorage(cid, petItems[tonumber(param[2])], 0)
doPlayerSendTextMessage(cid, MESSAGE_INFO_DESCR, "You get an item.")
else
return doPlayerSendCancel(cid, "This slot is empty.")
end
elseif param[1]:lower() == "say" then
local pet = getPlayerPet(cid)
if not pet then
return doPlayerSendCancel(cid, "Please call your pet first.")
end
if not param[2] or param[2] == "" then
return doPlayerSendCancel(cid, "Command param required.")
end
if exhaustion.get(cid, petStorages.sayExhaust) then
return doPlayerSendCancel(cid, "You are exhausted.") and doSendMagicEffect(getCreaturePosition(getCreatureSummons(cid)[1]), CONST_ME_POFF)
end
doCreatureSay(pet, param[2], TALKTYPE_MONSTER)
exhaustion.set(cid, petStorages.sayExhaust, petExhaust)
elseif param[1]:lower() == "move" then
local pet = getPlayerPet(cid)
if not pet then
return doPlayerSendCancel(cid, "Please call your pet first.")
end
if not param[2] or param[2] == "" then
return doPlayerSendCancel(cid, "Command param required.")
end
if exhaustion.get(cid, petStorages.moveExhaust) then
return doPlayerSendCancel(cid, "You are exhausted.") and doSendMagicEffect(getCreaturePosition(getCreatureSummons(cid)[1]), CONST_ME_POFF)
end
local dir
if l[param[2]:lower()] then
dir = l[param[2]:lower()]
local toPos = getPosByDir(getCreaturePosition(pet), dir, 1)
local ret = queryTileAddThing(pet, toPos)
if ret == RETURNVALUE_NOERROR then
doMoveCreature(pet, dir)
exhaustion.set(cid, petStorages.moveExhaust, petExhaust)
else
return doPlayerSendCancel(cid, "There is not enough room.") and doSendMagicEffect(getCreaturePosition(pet), CONST_ME_POFF)
end
else
return doPlayerSendCancel(cid, "No direction specified.")
end
elseif isInArray({"mana", "health"}, param[1]:lower()) then
if getPlayerGroupId(cid) < 3 then
return false
end
local pet = getPlayerPet(cid)
if not pet then
return doPlayerSendCancel(cid, "Please call your pet first.")
end
if not param[2] or not isNumber(param[2]) then
return doPlayerSendCancel(cid, "Command param required.")
end
if param[1]:lower() == "health" then
doCreatureAddHealth(pet, tonumber(param[2]))
elseif param[1]:lower() == "mana" then
doCreatureAddMana(pet, tonumber(param[2]))
end
return doSendMagicEffect(getCreaturePosition(pet), (param[1]:lower() == "health" and CONST_ME_MAGIC_RED or CONST_ME_MAGIC_BLUE))
end
end
return true
end
Paste this at talkactions.xml:
XML:
<talkaction words="!pet" event="script" value="pet.lua"/>
Create a fil called petkill.lua at data/creaturescripts/scripts and paste:
Lua:
function onKill(cid, target, lastHit)
if not isMonster(target) or getConfigValue("rateExperience") < 0.1 or getMonsterInfo(getCreatureName(target)) and getMonsterInfo(getCreatureName(target)).experience < 1 then return true end
local pet = getPlayerPet(cid)
if pet then
doPetAddExperience(pet, getMonsterInfo(getCreatureName(target)).experience * petExpRate)
if getLevelByExp(getPetExperience(pet)) > getPetLevel(pet) then
doPlayerSendTextMessage(cid, MESSAGE_EVENT_ADVANCE, "Your pet advanced from level " .. getPetLevel(pet) .. " to level " .. getLevelByExp(getPetExperience(pet)) .. ".")
doPetSetLevel(pet, getLevelByExp(getPetExperience(pet)))
if refillStatsAtLevel then
doCreatureAddHealth(pet, getCreatureMaxHealth(pet) - getCreatureHealth(pet))
doCreatureAddMana(pet, getCreatureMaxMana(pet) - getCreatureMana(pet))
end
end
end
return true
end
Create a file called petdeath.lua at data/creaturescripts/scripts and paste:
Lua:
function onDeath(cid, corpse, deathList)
if not isPet(cid) then return true end
local id = getPetIdByName(getCreatureName(cid))
doCreatureSetStorage(getCreatureMaster(cid), tables.isDead[id], 1)
doPlayerSendTextMessage(getCreatureMaster(cid), MESSAGE_STATUS_CONSOLE_BLUE, "Your pet is dead.")
return true
end
Create a file called petstats.lua at data/creaturescripts/scripts and paste:
Lua:
function onStatsChange(cid, attacker, type, combat, value)
if getPlayerPet(cid) and getPlayerPet(cid) == attacker then
return false
end
return true
end
Add this to your creaturescripts/scripts/login.lua:
Lua:
registerCreatureEvent(cid, "petKill")
registerCreatureEvent(cid, "petDeath")
registerCreatureEvent(cid, "petStats")
Now paste this at creaturescripts.xml:
XML:
<event type="kill" name="petKill" event="script" value="petkill.lua"/>
<event type="death" name="petDeath" event="script" value="petdeath.lua"/>
<event type="statschange" name="petStats" event="script" value="petstats.lua"/>
You need to paste this variable on every monster file that you use as pet, after </flags>.
XML:
<script>
<event name="petDeath"/>
</script>
This is the script to revive the pet via npc:
Lua:
local keywordHandler = KeywordHandler:new()
local npcHandler = NpcHandler:new(keywordHandler)
NpcSystem.parseParameters(npcHandler)
local talkState = {}
function onCreatureAppear(cid) npcHandler:onCreatureAppear(cid) end
function onCreatureDisappear(cid) npcHandler:onCreatureDisappear(cid) end
function onCreatureSay(cid, type, msg) npcHandler:onCreatureSay(cid, type, msg) end
function onThink() npcHandler:onThink() end
local dead = 0
local pet
function creatureSayCallback(cid, type, msg)
if(not npcHandler:isFocused(cid)) then
return false
end
local talkUser = NPCHANDLER_CONVBEHAVIOR == CONVERSATION_PRIVATE and 0 or cid
if msgcontains(msg, 'offer') then
local b = false
local c = 0
local response = "Here %s the following %s that you can buy:"
for _, it in pairs(pets) do
if it.cost and it.cost > 0 then
c = c + 1
b = true
local voc, sep = "", ", "
for i = 1, #it.vocations do
if i == #it.vocations - 1 then
sep = " and "
elseif i == #it.vocations then
sep = ""
end
voc = voc .. getVocationInfo(it.vocations[i]).name .. sep
end
response = response .. "\n Name: " .. it.monster .. ", Cost: " .. it.cost .. ", Level: " .. it.level .. ", Vocations: " .. voc .. "."
end
end
response = string.format(response, (c > 1 and "are" or "is"), (c > 1 and "pets" or "pet"))
npcHandler:say(response, cid)
elseif msgcontains(msg, 'revive') then
for i = 1, #pets do
if getCreatureStorage(cid, tables.isDead[i]) > 0 then
dead = dead + 1
end
end
if dead > 0 then
npcHandler:say('Do you want to revive ' .. dead .. ' of your pets for ' .. reviveCost * dead .. ' gold coins?.', cid)
talkState[talkUser] = 1
else
npcHandler:say('Any of your pet is dead.', cid)
talkState[talkUser] = 0
end
elseif msgcontains(msg, 'buy') then
npcHandler:say('Please tell me the name of the pet that you can buy.', cid)
talkState[talkUser] = 2
elseif msgcontains(msg, 'yes') and talkState[talkUser] == 1 then
if doPlayerRemoveMoney(cid, reviveCost * dead) then
for i = 1, #tables.isDead do
doCreatureSetStorage(cid, tables.isDead[i], 0)
end
npcHandler:say('Your pets has been revived.', cid)
talkState[talkUser] = 0
else
npcHandler:say('You do not have enough money.', cid)
talkState[talkUser] = 0
end
elseif talkState[talkUser] == 2 then
pet = getPetIdByName(msg)
if not pet then
npcHandler:say('There is not any pet with that name.', cid)
talkState[talkUser] = 0
return true
end
if getCreatureStorage(cid, tables.buyed[pet]) > 0 then
npcHandler:say('You already bought this pet.', cid)
talkState[talkUser] = 0
return true
end
if pets[pet].cost and pets[pet].cost > 0 then
npcHandler:say('Do you want to buy ' .. pets[pet].monster .. ' [' .. pets[pet].level .. '] for ' .. pets[pet].cost .. ' gold coins?', cid)
talkState[talkUser] = 3
else
npcHandler:say('This pet is not buyable, please tell me the name of the pet that you can buy.', cid)
talkState[talkUser] = 2
end
elseif msgcontains(msg, 'yes') and talkState[talkUser] == 3 then
if doPlayerRemoveMoney(cid, pets[pet].cost) then
doCreatureSetStorage(cid, tables.buyed[pet], 1)
npcHandler:say('Congratulations! you bought ' .. pets[pet].monster .. ' [' .. pets[pet].level .. '].', cid)
talkState[talkUser] = 0
else
npcHandler:say('You do not have enough money to buy this pet.', cid)
talkState[talkUser] = 0
end
end
return true
end
npcHandler:setCallback(CALLBACK_MESSAGE_DEFAULT, creatureSayCallback)
npcHandler:addModule(FocusModule:new())
Monster should have <flag convinceable="1"/>
This is how you configure a new pet (Water Elemental used as example, only one attack used as example):
Lua:
[4] = --Id, should be in order
{
monster = "water elemental", --The monster name.
vocations = {2}, --Vocations that can use this pet.
level = 95, --Level required to use this pet.
[cost, = 15000] --you can declare it or not. If you declare it and cost > 0 then pet will be buyable
attacks =
{
[1] =
{
name = "Ice Bomb", --Attack name, pet will say the name when you use the attack.
level = 100, --Level that player needs to use the attack.
petLevel = 100, --Level that pets needs to use the attack.
mana = 200, --Mana that the pets needs to use the attack.
type = TYPE_DISTANCE, --(TYPE_NEAR/TYPE_DISTANCE) _NEAR = Used for melee attacks. _NEAR = Used for distance attacks. (Declare this correct or you'll get some bugs.)
range = 3, --Attack range, used only for distance attacks.
combat = COMBAT_ICEDAMAGE, --Damage type.
effect = CONST_ME_ICEAREA, --Floor effect.
distEffect = CONST_ANI_ICE, --Distance effect.
damageMin = 1.5, --Damage min.
damageMax = 2.0, --Damage max.
area = AREA_CROSS1X1 --Area for the attack, if you want an attack without area, remove this param.
}
}
},
The pet system commands are:
- !pet callet_name // Call pet.
- !pet back // Call pet back.
- !pet info // It shows your pet info (Name, health, mana, level, experience and attacks)
- !pet take // It takes the item above the pet.
- !pet items // It shows the items you carry.
- !pet attack:index // Attack with your pet. Ex: !pet attack:2
- !pet give:index // Get a carried item. Ex: !pet give:4
- !pet say:text // Makes your pet says something. Ex: !pet say:Hello World
- !pet move:direction //Makes your moves. Ex: !pet move:northeast
- !pet mana/health:amount // Add/remove mana/health to your pet (Only for gamemasters)
NOTE: I used tibia creatures, remember that these creatures already has attacks, you can create new creatures to use it as pet.
Any bug you find, post here.
Enjoy.
Last edited: