Topic readme.md
Readme, explanation & example
Introduction
This page/document should hopefully help explain how the host handles various bits and pieces relevant to plugins. It includes explanations and a fully working Gerenium Quest example which will be live (or already is) on the test server and subsequently main server. It will be updated as the live script is updated to give an idea of how to make a quest.
NOTE: In the code blocks containing definitions, x4 spaces represent a tab. This is an issue with LDoc replacing tab characters with a fixed number of spaces. It is stressed that for any definitions, tabs rather than spaces are used.
Tools
Lua Documentation
Item Generator
Files
A plugin comprises of just a single lua file and is usually coupled with a zone file.
Zone/Map File explanation
A zone/map file (they're the same thing, the terms are interchangable) defines a map or area on the server. it represents one quadrant on the map either in space or the underspace. Cythris, TYV, Starbase etc are each defined in separate zone files. In a zone file, the majority of content for a zone is defined - items, mobs, tiles, fields.
Valid definitions
Zone files can have a number of definitions for different objects ingame. Allowed definitions include:
- #DEF_ITEM
- #DEF_PLUGIN
- #DEF_COMBINE
- #DEF_MOBILE
The format for these is as followed with x4 spaces replaced with \t (tab). The initial field is case-insensitive.
#DEF_COMBINE FINAL_ITEM_ID SKILL_REQUIRED LEVEL ITEM_REQUIRED_ID ITEM_REQUIRED_ID ITEM_REQUIRED_ID ITEM_REQUIRED_ID ITEM_REQUIRED_ID ITEM_REQUIRED_ID ITEM_REQUIRED_ID ITEM_REQUIRED_ID ITEM_REQUIRED_ID #DEF_PLUGIN file Name
Both #DEF_ITEM, #DEF_COMBINE, please use the web tools provided to generate definitions for these.
IDs
The host uses IDs to identify everything however there are a few "special" ranges. Any ID below 10000 is absolute - it will not be changed, it will stick with what it's been given. These are reserved for very common or global items such as ores - Silver, Gold etc. Any ID above 10000 inside a zone file will be modified to a calculated offset for that zone. This means that it is possible to have relative IDs in zonefiles eliminating the need to have to keep track of what item numbers do what. For example, we may have a zonefile Cythris.txt:
#DEF_ITEM 10001 WEAP TestWeapon 5 7 0 2 1 0 20 0 2 4 0 30 5000
It is then possible in the Lua file to find TestWeapon by using the item(id) function which handles fixing up offsets for you, e.g:
local testweaponid = item(plugin_id, 1)
For all quests it is necessary to use IDs beginning from 10000.
Lua file
The lua file is the quest script and has to implement at a minimum, 3 functions.
Each plugin doesn't have to implement any functions although it is recommended to use the init(id) to initialize some variables and cache the plugin id
plugin_id function init(id) plugin_id = id end
The server will run the init function once when the script is definition is read at startup or when the script is reloaded. It can be used for things such as adding chat to mob and creating the start callback as seen in the Gerenium example further down.
All functions called from Lua -> HS Server are in a hs namespace (e.g, hs.give_exp)
Hooks
It is now possible for the script to 'hook' certain host functions. These functions all end with a _hook and are not in the hs namespace. These are fired only when the event occurs and only if the server contains a certain hook. An example:
function player_sell_hook(playerid, planetid, itemid, quantity) local planet = get_planet_details(planetid) create_news_message(string.format("%s sold x%d [%d] to %s", get_player_name(playerid), quantity, itemid, planet["name"]) end
This will broadcast a message every time a player sells something to a planet.
Gerenium Quest Example
StarbaseL1.txt
#DEF_ZONETITLE Starbase Level 1 #DEF_ITEM 10001 QUEST Gerenium Ore 26 24 1 0 0 0 0 0 0 0 0 0 1 #DEF_ITEM 10002 QUEST Gerenium Container 1 26 1 0 0 0 0 0 0 0 0 0 1 #DEF_ITEM 10003 QUEST Contained Gerenium 1 10 1 0 0 0 0 0 0 0 0 0 1 #Def_Item 10004 SYSTEM Gerenium Plate 33 26 0 3 4 10 3 0 0 0 0 1 250 #Def_Item 10005 SYSTEM Gerenium Plate 34 26 0 3 4 10 3 0 0 0 0 2 250 #Def_Item 10006 SYSTEM Gerenium Plate 35 26 0 3 3 10 3 0 0 0 0 3 250 #Def_Item 10007 SYSTEM Gerenium Plate 36 26 0 3 3 10 3 0 0 0 0 4 250 #DEF_COMBINE 10003 0 0 10001 10001 10001 10001 10001 10001 10002 0 0 0 #Def_Plugin gerenium.lua Gerenium Quest
Gerenium.lua
plugin_id = 0 quest_level = 30 fes_rebels = {} persistent = {} function init(id) plugin_id = id fes_rebels["defmob"] = find_mob_by_name("Fes Rebels") -- Set up all the chat messages for Fes Rebels hs.add_talk_to_mob(plugin_id, fes_rebels["defmob"],"[CLICK]","Hello there, %P, I am the famous and immobile %N. Do you need a [job]?") hs.add_talk_to_mob(plugin_id, fes_rebels["defmob"],"job","Great, I think I'm going to be able to help you. A slight distance away in the west lay the Scythe fleet.") hs.add_talk_to_mob(plugin_id, fes_rebels["defmob"],"job","They hold a rare ore called [gerenium]") hs.add_talk_to_mob(plugin_id, fes_rebels["defmob"],"gerenium","Yes, by combining SIX (6) gerenium in a container, it will produce a strong compound used for armor.") hs.add_talk_to_mob(plugin_id, fes_rebels["defmob"],"gerenium","Would you like to [start] this quest?") hs.add_talk_to_mob_callback(plugin_id, fes_rebels["defmob"],"start","", "start") --These responses require the quest to be active in order to be executed hs.add_talk_to_mob_callback(plugin_id, fes_rebels["defmob"],"container","Here is one container. Fabricate it (/fabricate) it with 6 Gerenium Ore to create Contained Gerenium.", "giveContainer") hs.add_talk_to_mob(plugin_id, fes_rebels["defmob"],"container","Once you have Contained Gerenium, come back and ask me for a [Fore],[Aft],[Port] or [sb] plate.",1) --allow the player to manually try to finish hs.add_talk_to_mob_callback(plugin_id, fes_rebels["defmob"],"finish","I hope we meet again.","finish") hs.add_talk_to_mob_callback(plugin_id, fes_rebels["defmob"],"fore","Let me check if you have the ore.", "handleFore") hs.add_talk_to_mob_callback(plugin_id, fes_rebels["defmob"],"port","Let me check if you have the ore.", "handlePort") hs.add_talk_to_mob_callback(plugin_id, fes_rebels["defmob"],"aft","Let me check if you have the ore.", "handleAft") hs.add_talk_to_mob_callback(plugin_id, fes_rebels["defmob"],"sb","Let me check if you have the ore.", "handleSb") -- allow the player to abort the quest -- callback must be executed only if player is on this quest hs.add_talk_to_mob_callback(plugin_id, fes_rebels["defmob"],"abort","", "abort") -- set_quest_summary("This quest requires farming of certain materials from the Scythe Fleet for Fes Rebels.") -- set_quest_summary("In return for your work, you will be rewarded with a collection of Gerenium plates with generous low-level stats.") -- set_quest_summary("The quest can be repeated infinitely but experience will only be rewarded for the first completion.") -- Save his ID to a global variable so we don't have to run a search for him again end function start(playerid, mobid) log("Start was called ["..playerid.."]["..mobid.."]") --Check if we have a record of this player starting the quest before if(persistent[playerid] == nil) then persistent[playerid] = {} elseif(persistent[playerid]["started"] == "y") then log("Firing started") create_chat(mobid, playerid, "You have already started this quest. You may still [abort] this quest") create_chat(mobid, playerid, "Travel West from Cythris Prime and enter the Scyhte system.") create_chat(mobid, playerid, "Destroy the Scythe infestation and collect their precious Gerenium ore") create_chat(mobid, playerid, "Once you have 6 (/fabricate) them with a gerenimum [container] and return to me for you reward") else log("Firing not started") --Set this player as started persistent[playerid]["started"] = "y" create_chat(mobid, playerid, "Good. I can provide you with a [container] if needs be. I look forward to seeing you later.") end end function handleFore(playerid, mobid) --Call handleplate with ID of a Fore gerenium plate and the prefix handlePlate(item(plugin_id, 4), "fore", playerid, mobid) end function handleAft(playerid, mobid) --Call handleplate with ID of an Aft gerenium plate and the prefix handlePlate(item(plugin_id, 5), "aft", playerid, mobid) end function handlePort(playerid, mobid) --Call handleplate with ID of a Port gerenium plate and the prefix handlePlate(item(plugin_id, 6), "port", playerid, mobid) end function handleSb(playerid, mobid) --Call handleplate with ID of a Starboard gerenium plate and the prefix handlePlate(item(plugin_id, 7), "sb", playerid, mobid) end --Handle plate function, will check for an ID and give an item if the player has contained gerenium function handlePlate(id, side, playerid, mobid) --check if they've got a contained gerenium local contained_gerenium = item(plugin_id, 3) if(get_player_item_count(playerid, contained_gerenium) > 0) then create_chat(mobid, playerid, "Here you go sir.") --remove a contained gerenium remove_item(playerid, contained_gerenium) --give the item an id give_item(playerid, id) if(persistent[playerid][side] ~= "y") then persistent[playerid][side] = "y" give_exp(playerid, quest_level) end -- check if the player has finished checkIfFinished() else --tell the user the problem create_chat(mobid, playerid, string.format("Unfortunately I cannot supply a %s plate without a Contained Gerenium.", side)) end end function giveContainer(playerid, mobid) --Give the item a container hs.give_item(playerid, item(plugin_id, 2)) end --Unset the parameters for each plate from the player. --This allows the quest to be done more than once, but experience --to only be given for one cycle local function unsetPlateParameters(playerid) --reset the values for this player persistent[playerid]["fore"] = nil persistent[playerid]["aft"] = nil persistent[playerid]["port"] = nil persistent[playerid]["sb"] = nil end -- Prevent players from selling the gerenium (handled host-side anyway but can't hurt function player_sell_hook(playerid, planetid, itemid, quantity) if(itemid == item(plugin_id, 2)) then hs.write_text_to_screen(playerid, "You cannot sell contained gerenium!") return 1 end return 0 end -- Check ID of mobs the player kills, if scythe then give an ore function player_kill_mob_hook(playerid, mobid, level, name, qx, qy, z) if(string.match(name, "Scythe") ~= nil) then if(math.random(0,99) < 50) then hs.give_item(playerid, item(plugin_id, 1)) hs.write_text_to_screen(playerid, "You found a Gerenium Ore!", 29) end end end --function to check if the player has satisfied all of the requirements function checkIfFinished(playerid, mobid) local fore = false local aft = false local port = false local sb = false local txtadded = false --Check which of the plates the player has created if(persistent[playerid]["fore"] == "y") then fore = true end if(persistent[playerid]["aft"] == "y") then aft = true end if(persistent[playerid]["port"] == "y") then port = true end if(persistent[playerid]["sb"] == "y") then sb = true end --if they have made a complete set if(fore == true and aft == true and port == true and sb == true) then --check if the player has already finished the quest before local alreadyFinished = persistent[playerid]["geren_finish"] --if not, give them some experience and set that they have as a parameter if(alreadyFinished ~= "y") then give_exp(playerid) persistent[playerid]["finished"] = "y" end --create a message from Fes create_chat_from_mob(fes_rebels, playerid, "Well done for completing the quest. Good luck in your future endeavors.") --reset the status of the plates so the quest can be done again unsetPlateParameters() --deactivate the quest from the player set_finish() else --Guess player has more to do, inform them what's left to do local txt = "You can still collect rewards for fabricating " if(fore == false) then txt = txt .. "Fore ," txtadded = true end if(aft == false) then txt = txt .. "Aft ," txtadded = true end if(port == false) then txt = txt .."Port ," txtadded = true end -- If we've added text, add the word "and" to make it eaiser to read if(txtadded == true) then txt = txt .. "and " end if(sb == false) then txt = txt .. "Starboard " end hs.create_chat(mobid, playerid, txt .. "Gerenium Plates.") end end function finish(playerid, mobid) --set the quest finished on the player, this means update() no longer runs for this player checkIfFinished(playerid, mobid) end function abort(playerid, mobid) -- basic abort response if(persistent[playerid] == nil or persistent[playerid]["started"] == nil) then hs.create_chat(mobid, playerid, "You haven't started this quest yet.") else hs.create_chat(mobid, playerid, "I'm sorry you don't want to help with the struggle.") hs.create_chat(mobid, playerid, "Please return to me if you change your mind.") -- clear plate progress unsetPlateParameters(playerid) -- clear quest started param persistent[playerid]["started"] = nil -- actually abort quest -- set_abort() end end