Spike Tasks Quest
Help the Gnomes to fight their enemies around The Spike!
Originally scripted on TFS 1.2, 10.77. Also tested on 10.95
http://tibia.wikia.com/wiki/Spike_Tasks_Quest/Spoiler
Initial Setup
You need to have common sense, patience and at least an average ot knowledge to install this quest on your real-map server, so you can know why an error occurs while you do it. This has been thoroughly tested to avoid bugs and errors, and written efficiently with Sublime Text 3. I'm open to discussion and suggestions for code improvement and storyline similarity.Help the Gnomes to fight their enemies around The Spike!
Originally scripted on TFS 1.2, 10.77. Also tested on 10.95
http://tibia.wikia.com/wiki/Spike_Tasks_Quest/Spoiler
Initial Setup
Add @data/lib/core/constants.lua
Code:
SPIKE_FAME_POINTS = 27890
SPIKE_UPPER_PACIFIER_MAIN = 27891
SPIKE_UPPER_PACIFIER_DAILY = 27892
SPIKE_UPPER_MOUND_MAIN = 27893
SPIKE_UPPER_MOUND_DAILY = 27894
SPIKE_UPPER_TRACK_MAIN = 27895
SPIKE_UPPER_TRACK_DAILY = 27896
SPIKE_UPPER_KILL_MAIN = 27897
SPIKE_UPPER_KILL_DAILY = 27898
SPIKE_MIDDLE_CHARGE_MAIN = 27899
SPIKE_MIDDLE_CHARGE_DAILY = 27900
SPIKE_MIDDLE_MUSHROOM_MAIN = 27901
SPIKE_MIDDLE_MUSHROOM_DAILY = 27902
SPIKE_MIDDLE_NEST_MAIN = 27903
SPIKE_MIDDLE_NEST_DAILY = 27904
SPIKE_MIDDLE_KILL_MAIN = 27905
SPIKE_MIDDLE_KILL_DAILY = 27906
SPIKE_LOWER_PARCEL_MAIN = 27907
SPIKE_LOWER_PARCEL_DAILY = 27908
SPIKE_LOWER_UNDERCOVER_MAIN = 27909
SPIKE_LOWER_UNDERCOVER_DAILY = 27910
SPIKE_LOWER_LAVA_MAIN = 27911
SPIKE_LOWER_LAVA_DAILY = 27912
SPIKE_LOWER_KILL_MAIN = 27913
SPIKE_LOWER_KILL_DAILY = 27914
Code:
function Player.setExhaustion(self, value, time)
return self:setStorageValue(value, time + os.time())
end
function Player.getExhaustion(self, value)
local storage = self:getStorageValue(value)
if storage <= 0 then
return 0
end
return storage - os.time()
end
function Player.addFamePoint(self)
local points = self:getStorageValue(SPIKE_FAME_POINTS)
local current = math.max(0, points)
self:setStorageValue(SPIKE_FAME_POINTS, current + 1)
self:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "You have received a fame point.")
end
function Player.getFamePoints(self)
local points = self:getStorageValue(SPIKE_FAME_POINTS)
return math.max(0, points)
end
function Player.removeFamePoints(self, amount)
local points = self:getStorageValue(SPIKE_FAME_POINTS)
local current = math.max(0, points)
self:setStorageValue(SPIKE_FAME_POINTS, current - amount)
end
Add @data/lib/core/position.lua
Code:
function Position:compare(position)
return self.x == position.x and self.y == position.y and self.z == position.z
end
function Position:isInRange(fromPosition, toPosition)
return (self.x >= fromPosition.x and self.y >= fromPosition.y and self.z >= fromPosition.z
and self.x <= toPosition.x and self.y <= toPosition.y and self.z <= toPosition.z)
end
function Position:isWalkable()
local tile = Tile(self)
if not tile then
return false
end
local ground = tile:getGround()
if not ground or ground:hasProperty(CONST_PROP_BLOCKSOLID) then
return false
end
local items = tile:getItems()
for i = 1, tile:getItemCount() do
local item = items[i]
local itemType = item:getType()
if itemType:getType() ~= ITEM_TYPE_MAGICFIELD and not itemType:isMovable() and item:hasProperty(CONST_PROP_BLOCKSOLID) then
return false
end
end
return true
end
function getFreePosition(from, to)
local result, tries = Position(from.x, from.y, from.z), 0
repeat
local x, y, z = math.random(from.x, to.x), math.random(from.y, to.y), math.random(from.z, to.z)
result = Position(x, y, z)
tries = tries + 1
if tries >= 20 then
return result
end
until result:isWalkable()
return result
end
function getFreeSand()
local from, to = ghost_detector_area.from, ghost_detector_area.to
local result, tries = Position(from.x, from.y, from.z), 0
repeat
local x, y, z = math.random(from.x, to.x), math.random(from.y, to.y), math.random(from.z, to.z)
result = Position(x, y, z)
tries = tries + 1
if tries >= 50 then
return result
end
until result:isWalkable() and Tile(result):getGround():getName() == "grey sand"
return result
end
Add @data/lib/core/string.lua
Code:
string.diff = function(diff)
local format = {
{'day', diff / 60 / 60 / 24},
{'hour', diff / 60 / 60 % 24},
{'minute', diff / 60 % 60},
{'second', diff % 60}
}
local out = {}
for k, t in ipairs(format) do
local v = math.floor(t[2])
if(v > 0) then
table.insert(out, (k < #format and (#out > 0 and ', ' or '') or ' and ') .. v .. ' ' .. t[1] .. (v ~= 1 and 's' or ''))
end
end
local ret = table.concat(out)
if ret:len() < 16 and ret:find('second') then
local a, b = ret:find(' and ')
ret = ret:sub(b+1)
end
return ret
end
Create folder data/actions/scripts/spike tasks
Add @data/actions/actions.xml
Code:
<!-- Spike Tasks Quest -->
<action itemid="21553" script="spike tasks/spirit shovel.lua"/>
<action itemid="21554" script="spike tasks/tuning fork.lua"/>
<action itemid="21555" script="spike tasks/ghost detector.lua"/>
<action itemid="21556" script="spike tasks/thermometer.lua"/>
<action itemid="21557" script="spike tasks/lodestone.lua"/>
<action itemid="21559" script="spike tasks/nests.lua"/>
<action itemid="21564" script="spike tasks/fertilizer.lua"/>
<action itemid="21566" script="spike tasks/lodestone.lua"/>
<action itemid="21568" script="spike tasks/lodestone.lua"/>
Create folder data/creaturescripts/scripts/spike tasks
Add @data/creaturescripts/creaturescripts.xml
Code:
<!-- Spike Tasks Quest -->
<event type="kill" name="UpperSpikeKill" script="spike tasks/upperspikekill.lua"/>
<event type="kill" name="MiddleSpikeKill" script="spike tasks/middlespikekill.lua"/>
<event type="kill" name="LowerSpikeKill" script="spike tasks/lowerspikekill.lua"/>
Register @data/creaturescripts/scripts/login.lua
Code:
player:registerEvent("UpperSpikeKill")
player:registerEvent("MiddleSpikeKill")
player:registerEvent("LowerSpikeKill")
Add @data/items/items.xml
Code:
<item id="21559" article="a" name="monster nest" />
<item id="21560" article="a" name="destroyed monster nest">
<attribute key="decayTo" value="21559" />
<attribute key="duration" value="120" />
</item>
<item id="21561" article="an" name="ominous mound" />
<item id="21562" article="an" name="opened ominous mound">
<attribute key="decayTo" value="21561" />
<attribute key="duration" value="120" />
</item>
<item id="21564" article="a" name="flask of mushroom fertilizer">
<attribute key="weight" value="180" />
<attribute key="description" value="It holds a liquid concentrate developed by the gnomes to fertilise mushrooms." />
</item>
<item id="21565" article="a" name="gardener mushroom"/>
<item id="21566" article="a" name="partically charged lodestone">
<attribute key="weight" value="300" />
</item>
<item id="21567" article="a" name="magnetic monolith"/>
<item id="21568" article="a" name="highly charged lodestone">
<attribute key="weight" value="300" />
</item>
<item id="21713" article="a" name="chargeless monolith">
<attribute key="decayTo" value="21567" />
<attribute key="duration" value="120" />
</item>
Create @data/npc/Gnommander.xml
Code:
<?xml version="1.0" encoding="UTF-8"?>
<npc name="Gnommander" script="Gnommander.lua" walkinterval="2000" floorchange="0">
<health now="100" max="100" />
<look type="493" head="59" body="57" legs="39" feet="38" addons="0" />
<parameters>
<parameter key="message_greet" value="Hi there! Welcome to the spike." />
</parameters>
</npc>
Create @data/npc/Gnomux.xml
Code:
<?xml version="1.0" encoding="UTF-8"?>
<npc name="Gnomux" script="Gnomux.lua" walkinterval="2000" floorchange="0">
<health now="100" max="100" />
<look type="493" head="12" body="82" legs="39" feet="114" addons="0" />
<parameters>
<parameter key="message_greet" value="Hi!" />
</parameters>
</npc>
Create @data/npc/scripts/Gnommander.lua
Code:
local keywordHandler = KeywordHandler:new()
local npcHandler = NpcHandler:new(keywordHandler)
NpcSystem.parseParameters(npcHandler)
local talkState = {}
local speech = {
"I'm the operating commander of the Spike, the latest great accomplishment of the gnomish race.",
"The Spike is a crystal structure, created by our greatest crystal experts. It has grown from a crystal the size of my fist to the structure you see here and now.",
"Of course this did not happen from one day to the other. It's the fruit of the work of several gnomish generations. Its purpose has changed in the course of time.",
"At first it was conceived as a fast growing resource node. Then it was planned to become the prototype of a new type of high security base.",
"Now it has become a military base and a weapon. With our foes occupied elsewhere, we can prepare our strike into the depths of the earth.",
"This crystal can withstand extreme pressure and temperature, and it's growing deeper and deeper even as we speak.",
"The times of the fastest growth have come to an end, however, and we have to slow down in order not to risk the structural integrity of the Spike. But we are on our way and have to do everything possible to defend the Spike."
}
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
function creatureSayCallback(cid, type, msg)
if not npcHandler:isFocused(cid) then
return false
end
local player = Player(cid)
if msgcontains(msg, 'commander') then
return npcHandler:say('I\'m responsible for the security and reward heroes to our cause. If you are looking for missions, talk to Gnomilly, Gnombold and Gnomagery.', cid)
end
if msgcontains(msg, 'reward') then
return npcHandler:say('I can sell special outfit parts. If your fame is high enough, you might be {worthy} of such a reward.', cid)
end
if msgcontains(msg, 'spike') then
return npcHandler:say(speech, cid)
end
if msgcontains(msg, 'worthy') then
if player:getFamePoints() < 100 then
return npcHandler:say('You are not worthy of a special reward yet.', cid)
end
talkState[cid] = 'worthy'
return npcHandler:say('You can acquire the {basic} outfit for 1000 Gold, the {first} addon for 2000 gold and the {second} addon for 3000 gold. Which do you want to buy?', cid)
end
if talkState[cid] == 'worthy' then
if msgcontains(msg, 'basic') then
if getPlayerLevel(cid) < 25 then
talkState[cid] = nil
return npcHandler:say('You do not have enough level yet.', cid)
end
if player:hasOutfit(player:getSex() == 0 and 575 or 574) then
talkState[cid] = nil
return npcHandler:say('You already have that outfit.', cid)
end
talkState[cid] = 'basic'
return npcHandler:say('Do you want to buy the basic outfit for 1000 Gold?', cid)
elseif msgcontains(msg, 'first') then
if getPlayerLevel(cid) < 50 then
talkState[cid] = nil
return npcHandler:say('You do not have enough level yet.', cid)
end
if not player:hasOutfit(player:getSex() == 0 and 575 or 574) then
talkState[cid] = nil
return npcHandler:say('You do not have the Cave Explorer outfit.', cid)
end
if player:hasOutfit(player:getSex() == 0 and 575 or 574, 1) then
talkState[cid] = nil
return npcHandler:say('You already have that addon.', cid)
end
talkState[cid] = 'first'
return npcHandler:say('Do you want to buy the first addon for 2000 Gold?', cid)
elseif msgcontains(msg, 'second') then
if getPlayerLevel(cid) < 80 then
talkState[cid] = nil
return npcHandler:say('You do not have enough level yet.', cid)
end
if not player:hasOutfit(player:getSex() == 0 and 575 or 574) then
talkState[cid] = nil
return npcHandler:say('You do not have the Cave Explorer outfit.', cid)
end
if player:hasOutfit(player:getSex() == 0 and 575 or 574, 2) then
talkState[cid] = nil
return npcHandler:say('You already have that addon.', cid)
end
talkState[cid] = 'second'
return npcHandler:say('Do you want to buy the second addon for 3000 Gold?', cid)
end
end
if talkState[cid] == 'basic' then
if msgcontains(msg, 'yes') then
if not player:removeMoney(1000) then
talkState[cid] = nil
return npcHandler:say('You do not have that money.', cid)
end
end
player:removeFamePoints(100)
player:addOutfit(player:getSex() == 0 and 575 or 574)
talkState[cid] = nil
return npcHandler:say('Here it is.', cid)
elseif talkState[cid] == 'first' then
if msgcontains(msg, 'yes') then
if not player:removeMoney(2000) then
talkState[cid] = nil
return npcHandler:say('You do not have that money.', cid)
end
end
player:removeFamePoints(100)
player:addOutfitAddon(player:getSex() == 0 and 575 or 574, 1)
talkState[cid] = nil
return npcHandler:say('Here it is.', cid)
elseif talkState[cid] == 'second' then
if msgcontains(msg, 'yes') then
if not player:removeMoney(3000) then
talkState[cid] = nil
return npcHandler:say('You do not have that money.', cid)
end
end
player:removeFamePoints(100)
player:addOutfitAddon(player:getSex() == 0 and 575 or 574, 2)
talkState[cid] = nil
return npcHandler:say('Here it is.', cid)
end
return true
end
npcHandler:setCallback(CALLBACK_MESSAGE_DEFAULT, creatureSayCallback)
npcHandler:addModule(FocusModule:new())
Create @data/npc/scripts/Gnomux.lua
Code:
local keywordHandler = KeywordHandler:new()
local npcHandler = NpcHandler:new(keywordHandler)
NpcSystem.parseParameters(npcHandler)
local talkState = {}
local spike_items = {
[21564] = {250, 4, SPIKE_MIDDLE_MUSHROOM_MAIN},
[21555] = {150, 3, SPIKE_UPPER_TRACK_MAIN},
[21569] = {100, 4, SPIKE_LOWER_PARCEL_MAIN},
[21557] = {250, 1, SPIKE_MIDDLE_CHARGE_MAIN},
[21553] = {150, 4, SPIKE_UPPER_MOUND_MAIN},
[21556] = {500, 1, SPIKE_LOWER_LAVA_MAIN},
[21554] = {150, 7, SPIKE_UPPER_PACIFIER_MAIN}
}
local onBuy = function(cid, item, subType, amount, ignoreCap, inBackpacks)
if not doPlayerRemoveMoney(cid, spike_items[item][1]*amount) then
selfSay("You don't have enough money.", cid)
else
doPlayerAddItem(cid, item, amount)
selfSay("Here you are!", cid)
end
return true
end
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
function creatureSayCallback(cid, type, msg)
if not npcHandler:isFocused(cid) then
return false
end
local player, canBuy, shopWindow = Player(cid), false, {}
for itemid, data in pairs(spike_items) do
if not isInArray({-1, data[2]}, player:getStorageValue(data[3])) then
canBuy = true
table.insert(shopWindow, {id = itemid, subType = 0, buy = data[1], sell = 0, name = ItemType(itemid):getName()})
end
end
if msgcontains(msg, 'trade') then
if canBuy then
openShopWindow(cid, shopWindow, onBuy, onSell)
return npcHandler:say("Here you are.", cid)
else
return npcHandler:say("Sorry, there's nothing for you right now.", cid)
end
return true
end
if msgcontains(msg, 'job') then
npcHandler:say("I'm responsible for resupplying foolish adventurers with equipment that they may have lost. If you're one of them, just ask me about a {trade}. ", cid)
end
if msgcontains(msg, 'gnome') then
npcHandler:say("What could I say about gnomes that anyone would not know? I mean, we're interesting if not fascinating, after all.", cid)
end
if msgcontains(msg, 'spike') then
npcHandler:say({"I came here as a crystal farmer and know the Spike all the way back to when it was a little baby crystal. I admit I feel a little fatherly pride in how big and healthy it has become.","When most other crystal experts left for new assignments, I decided to stay and help here a bit."}, cid)
end
return true
end
npcHandler:setCallback(CALLBACK_MESSAGE_DEFAULT, creatureSayCallback)
npcHandler:addModule(FocusModule:new())
Map positions for NPCs
Gnommander @32246, 32603, 8
Gnomilly @32243, 32598, 9
Gnombold @32242, 32598, 11
Gnomux @32242, 32611, 13
Gnomargery @32243, 32597, 14
Gnome Trooper @32321, 32586, 13
Gnome Trooper @32166, 32642, 13
Gnome Trooper @32289, 32507, 14
Gnome Trooper @32175, 32654, 14
Gnome Trooper @32157, 32515, 15
A Drillworm @32232, 32684, 13
A Nightmare Scion @32334, 32523, 13
A Vulcongra @32132, 32562, 13
A Dragon Lord @32273, 32629, 14
A Lost Basher @32171, 32630, 14
A Lost Thrower @32306, 32546, 14
A Behemoth @32306, 32582, 15
A Lost Husher @32209, 32531, 15
A Wyrm @32174, 32598, 15
Create magic forcefield with actionid 1000 @32242, 32611, 11