Advertisement
GravityScore

Darklands Content

Mar 11th, 2013
1,635
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 187.15 KB | None | 0 0
  1. MKDIR rpg
  2. MKFIL rpg/log
  3. WRITE 1
  4. 7.574: Log opened.
  5. MKFIL rpg/rpg
  6. WRITE 2609
  7. --[[
  8.  
  9.                     RPG Engine
  10.                 Created by Nitrogen Fingers
  11.    
  12.     Version: -0.4a (pre-alpha)
  13.    
  14.     Created: 29/06/2012
  15.     Last Updated: 10/03/2013
  16.    
  17.     : This program allows the running of a fairly simple, Ultima-style RPG. It includes the ability to run simple :
  18.     : scripts to define enemies, towns, quests, weapons and so on and will include them within the game engine    :
  19.     : dynamically. Note this is solely the engine- the game itself is defined by it's scripts, which are kept in  :
  20.     : the script, towns and world files in this directory
  21.    
  22.     Immediate Goals for next version:
  23.     - Skill sheet
  24.     - Greater control over random generation of enemies (type, level etc)
  25.     - Dynamic dungeons- levers, secret doors and more
  26.     - More graphical elements in dungeons
  27.    
  28.     Goals for later versions:
  29.     - Magic
  30.     - Serialization of some sort
  31.     - Music
  32.  
  33. ]]--
  34.  
  35. w,h = term.getSize()
  36. gameOver = false
  37. debugging = false
  38.  
  39.                 --[[SECTION: GLOBAL parameters & variables]]--
  40.  
  41. --name: How the quest appears in your logbook
  42. --activestage: At what stage the quest is up to. 0 is inactive, -1 is complete.
  43. --variables: You may insert anything you like here- space for the filling!
  44. --stages: the ID for each stage and what it means:
  45.     --desc: a short description of what's needed to complete this stage of the quest
  46.     --condition: a function that checks the state of the world and each action the player makes,
  47.         --if this returns true it runs a function that modifies the game state (usually a display or something)
  48.         --and updates the quest stage.
  49. --reward: a table with the type and value of the reward (at present types are GOLD and ITEM).
  50. quests = {}
  51. --name: The town's title
  52. --xpos,ypos: coordinates it should appear on the world map
  53. --startx,starty: coordinates the player should appear when entering/exiting the town
  54. --level: the environment of the town itself
  55. --dialogue: dialog options that are true for everyone in the town. Used when personal dialog choices are exhasted
  56. --visible: Whether or not the town is presently visible
  57. --npc: a list of all npcs in the town
  58.     --name: The name of the npc
  59.     --xpos,ypos: Position of the x and y
  60.     --moving: boolean, whether or not the NPC walks around. They never move more than 2 or 3 steps from where they start.
  61.     --dialog: A list of every dialog option with the word or phrase needed to start it. # tags are added, and similies should # the actual option
  62.         --Defaults: onfirstgreet, ongreet, onfarewell
  63.     --job: How the user interacts with the npc
  64.     --level: Affects the equipment the NPC sells, and how much XP each quest he assigns is worth
  65.     --inventory: Smiths/Mystics only: The items they have available for sale
  66.     --priceModifier: Smiths/Merchants/Mystics only: Their buying or selling modifier price, normalized percentage (ex. 0.75 for 75%)
  67.     --goldLimit: Merchants only: Their maximum buying price on any item (will never pay more than this)
  68. town = { }
  69. --Global dialogue- dialogue shared by all members of the game
  70. globalDialogue = { }
  71. --name: The title of the dungeon, as given in the over display
  72. --xpos,ypos: coordinates it should appear on the world map
  73. --startx,starty,startlevel: the coodinates the player starts at within the dungeon. Place these NEXT to E, not in an E.
  74. --startfx, startfy: the facing the player starts at
  75. --level: the level itself, divided into sublevels by index
  76. --creatures: A list of all enemies currently in the dungeon. Can start at zero, if there are none yet.
  77. --creaturesSpawn: A boolean- if true enemies will occasionally spawn at random within the dungeon.
  78. dungeons = { }
  79. creatures = { }
  80. items = { }
  81. enchantments = { }
  82. --The function to be run when the game starts. Here you should initialize things like the player's class, stats,
  83. --and any other initialization your script will require.
  84. onInit = nil
  85. --A 2D array of strings, for displaying images. You can use this however you like, the displayImage function will do it for you.
  86. images = { }
  87.  
  88. --Quest State information, to do with conversations.
  89.     --activeDialog: the name of the person the PC engaged in conversation with
  90. activeDialog = nil
  91.  
  92.                 --[[SECTION: Character, RP Stats]]--
  93.                
  94. charName = "Unnamed"
  95. charClass = "Adventurer"
  96. charLevel = 1
  97. charHealth = 80
  98. charMaxHealth = 80
  99. charXP = 0
  100. charNextXP = 50
  101. charGold = 0
  102.  
  103. --The player's RPG stats, modifiers for his combat ability
  104. local charStrength = 10
  105. local charEndurance = 10
  106. local charAgility = 10
  107. local charSpeed = 10
  108.  
  109. activeQuests = {}
  110. inventory = {}
  111.  
  112. --What the player has equipped
  113. charEquipment = {
  114.     swordhand = { value = nil, suffix = "(S)" };
  115.     offhand = { value = nil, suffix = "(O)" };
  116.     head = { value = nil, suffix = "(H)" };
  117.     chest = { value = nil, suffix = "(C)" };
  118.     legs = { value = nil, suffix = "(L)" };
  119.     feet = { value = nil, suffix = "(F)" };
  120. }
  121. --The last page the journal was opened to
  122. local journalPage = 1
  123.  
  124. --A little timesavaer
  125. local directions = { {x = -1, y = 0}, {x = 0, y = -1}, {x = 1, y = 0}, {x = 0, y = 1} }
  126.  
  127.                 --[[SECTION: Overword Information]]--
  128.  
  129. overworld = { }
  130. --Overcreatures also track:
  131. --chaseturns: How many turns the overcreature has been chasing the player. If this exceeds 10, they should give up
  132. --stillturns: How many turns the overcreature has been unable to move. If this exceeds 3, they should give up
  133. local overcreatures = { }
  134. worldname = "The World"
  135. local mapw = 29
  136. local maph = 16
  137.  
  138. pox = 1
  139. poy = 1
  140.  
  141. local overEventList = {}
  142.  
  143.                 --[[SECTION: Town information]]--
  144.  
  145. activeTown = 0
  146. inTown = false
  147.  
  148. townpx, townpy = 0,0
  149. local townEventList = {}
  150.  
  151.                     --[[SECTION: Dungeon, State Attributes]]--
  152.  
  153. --The player's position and facing
  154. dunpx = 0
  155. dunpy = 0
  156. dunlevel = 0
  157. dunfx = 0
  158. dunfy = 0
  159.  
  160. inDungeon = false
  161. activeDungeon = 0
  162.  
  163. local dungeonEventList = {}
  164.  
  165.                     --[[SECTION: General, Graphics Interface]]--
  166.                    
  167. local inventoryInterface = {
  168.     "//=============================\\ /===============\\\\",
  169.     "||                              |       __       ||",
  170.     "||                              |      /  \\H     ||",
  171.     "||                              |      \\__/      ||",
  172.     "||                              | /--^--  --^--\\ ||",
  173.     "||                              | S----\\  /----O ||",
  174.     "||                              |     C|  |      ||",
  175.     "||                              |      |  |      ||",
  176.     "||                              |      |  |L     ||",
  177.     "||                              |      /  \\      ||",
  178.     "||                              |     |_.._|F    ||",
  179.     "||                              |                ||",
  180.     "**------------------------------*----------------**",
  181. }
  182. local journalInterface = {
  183.     " _______________________ _ _______________________ ",
  184.     "/                       [ ]                       \\",
  185.     "|                       | |                       |",
  186.     "\\_______________________[_]_______________________/"
  187. }
  188. local shopInterface = {
  189.     "//===========================\\\\",
  190.     "||                           ||",
  191.     "||                           |/-~-~-~-~-~-~-~-~-~-~-\\",
  192.     "||                           ||                     |",
  193.     "\\\\===========================//-~-~-~-~-~-~-~-~-~-~-/"
  194. }
  195.  
  196.                     --[[SECTION: Dungeon, Display Components]]--
  197.  
  198. --The algorithms behind this are ingenius, but complicated. They involve overlaying various sprites on top of one another
  199. --at given areas to give the illusion of 3D.
  200. local cor = {
  201.     "\\                 /",
  202.     " \\               / ",
  203.     "  \\             /  ",
  204.     "   \\           /   ",
  205.     "    \\         /    ",
  206.     "     \\       /     ",
  207.     "      \\     /      ",
  208.     "       \\   /       ",
  209.     "        / \\        ",
  210.     "        \\ /        ",
  211.     "       /   \\       ",
  212.     "      /     \\      ",
  213.     "     /       \\     ",
  214.     "    /         \\    ",
  215.     "   /           \\   ",
  216.     "  /             \\  ",
  217.     " /               \\ ",
  218.     "/                 \\",
  219. }
  220. local corx = 1
  221. local cory = 1
  222.  
  223. local twoend = {
  224.     " ___ ",
  225.     "|   |",
  226.     "|   |",
  227.     "|   |",
  228.     " --- "
  229. }
  230. local twoexitend = {
  231.     " ___ ",
  232.     "| _ |",
  233.     "|/ \\|",
  234.     "|| ||",
  235.     ",- -."
  236. }
  237. local twoendx = 10
  238. local twoendy = 7
  239.  
  240. local oneend = {
  241.     "\\_________/",
  242.     "|         |",
  243.     "|         |",
  244.     "|         |",
  245.     "|         |",
  246.     "|         |",
  247.     "|         |",
  248.     "|         |",
  249.     "|_________|"
  250. }
  251. local oneexitend = {
  252.     "\\_________/",
  253.     "|         |",
  254.     "|  _____  |",
  255.     "| /     \\ |",
  256.     "| |     | |",
  257.     "| |     | |",
  258.     "| |     | |",
  259.     "| |     | |",
  260.     "|_|     |_|"
  261. }
  262. local oneendx = 7
  263. local oneendy = 5
  264.  
  265. local trightturn = {
  266.     " |",          
  267.     "||",        
  268.     "||",        
  269.     "||",          
  270.     "||",        
  271.     " |"    
  272. }
  273. local trightturnx = 14
  274. local trightturny = 7
  275.  
  276. local tleftturn = {
  277.     "| ",          
  278.     "||",        
  279.     "||",        
  280.     "||",          
  281.     "||",        
  282.     "| "    
  283. }
  284. local tleftturnx = 9
  285. local tleftturny = 7
  286.  
  287. local rightturn = {
  288.     "   |",
  289.     "   |",
  290.     " __|",
  291.     "|  |",
  292.     "|  |",
  293.     "|  |",
  294.     "|  |",
  295.     "|  |",
  296.     "|  |",
  297.     "|  |",
  298.     "|__|",
  299.     "   |",
  300.     "   |",
  301.     "   |"
  302. }
  303. local rightturnx = 17
  304. local rightturny = 3
  305.  
  306. local leftturn = {
  307.     "|   ",
  308.     "|   ",
  309.     "|__ ",
  310.     "|  |",
  311.     "|  |",
  312.     "|  |",
  313.     "|  |",
  314.     "|  |",
  315.     "|  |",
  316.     "|  |",
  317.     "|__|",
  318.     "|   ",
  319.     "|   ",
  320.     "|   "
  321. }
  322. local leftturnx = 4
  323. local leftturny = 3
  324.  
  325. local bwturn = {
  326.     "    ",
  327.     "    ",
  328.     "    ",
  329.     "    ",
  330.     "____",
  331.     "    ",
  332.     "    ",
  333.     "    ",
  334.     "    ",
  335.     "    ",
  336.     "    ",
  337.     "    ",
  338.     "____",
  339.     "    ",
  340.     "    ",
  341.     "    ",
  342.     "    ",
  343.     "    "
  344. }
  345. local bwleftturnx = 1
  346. local bwrightturnx = 20
  347. local bwturny = 1
  348.  
  349. local ladderd = {
  350.     " |---| ",
  351.     " |   | ",
  352.     "/|---|\\",
  353.     "\\|___|/"
  354. }
  355. local ladderdx = 9
  356. local ladderdy = 13
  357.  
  358. local ladderu = {
  359.     " _____ ",
  360.     "/|---|\\",
  361.     "\\|   |/",
  362.     " |---| ",
  363.     " |   | ",
  364.     " |---| ",
  365.     " |   | ",
  366.     " |---| ",
  367.     " |   | ",
  368.     " |---| ",
  369.     " |   | ",
  370.     " |---| ",
  371.     " |   | ",
  372.     " |---| ",
  373.     " |   | "  
  374. }
  375. local ladderux = 9
  376. local ladderuy = 2
  377.  
  378. local ladderud = {
  379.     " _____ ",
  380.     "/|---|\\",
  381.     "\\|   |/",
  382.     " |---| ",
  383.     " |   | ",
  384.     " |---| ",
  385.     " |   | ",
  386.     " |---| ",
  387.     " |   | ",
  388.     " |---| ",
  389.     " |   | ",
  390.     " |---| ",
  391.     " |   | ",
  392.     "/|---|\\",
  393.     "\\|___|/"
  394. }
  395. local ladderudx = 9
  396. local ladderudy = 2
  397.  
  398. local chestc = {
  399.     "/-------\\",
  400.     "|   0   |",
  401.     "|_______|"
  402. }
  403. local chestcx = 8
  404. local chestcy = 14
  405.  
  406. local chesto = {
  407.     " _______ ",
  408.     "/       \\",
  409.     "\\_______/",
  410.     "|   0   |",
  411.     "|_______|"
  412. }
  413. local chestox = 8
  414. local chestoy = 12
  415.            
  416. local compass = {
  417.     "  ___  ",
  418.     " /   \\ ",
  419.     "|     |",
  420.     "|     |",
  421.     " \\___/ "
  422.  
  423. }
  424.  
  425. --Determines the left facing depending on the current facing
  426. function getLeft(curx, cury)
  427.     if curx == 1 then return 0,-1 end
  428.     if curx == -1 then return 0,1 end
  429.     if cury == 1 then return 1,0 end
  430.     if cury == -1 then return -1,0 end
  431. end
  432.  
  433. --Determines the right facing depending on the current facing
  434. function getRight(curx, cury)
  435.     if curx == 1 then return 0,1 end
  436.     if curx == -1 then return 0,-1 end
  437.     if cury == 1 then return -1,0 end
  438.     if cury == -1 then return 1,0 end
  439. end
  440.  
  441.                 --[[SECTION: Overworld, Helper]]--
  442.  
  443. --Determines if the chosen letter is a vowel. Used mostly for determining articles.
  444. function isVowel(letter)
  445.     return letter == "a" or letter == "e" or letter == "i" or letter == "o" or letter == "u"
  446. end
  447.                
  448. --Prints a string with left justification, given a height and an optional offset
  449. function printLeft(msg, height, offset)
  450.     if not offset then offset = 0 end
  451.     term.setCursorPos(1 + offset, height)
  452.     term.write(msg)
  453. end
  454.  
  455. --A more advanced version of the above method including wrapping over specified width.
  456. --It will move to a new line each time and return the number of lines it takes.
  457. function wprintLeft(msg, height, width, offset)
  458.     local inc = 0
  459.     local ops = 1
  460.     while #msg - ops > width do
  461.         local nextspace = 0
  462.         while string.find(msg, " ", ops + nextspace) and
  463.                 string.find(msg, " ", ops + nextspace) - ops < width do
  464.             nextspace = string.find(msg, " ", nextspace + ops) + 1 - ops
  465.         end
  466.         local ox,oy = term.getCursorPos()
  467.         term.setCursorPos(offset, height + inc)
  468.         inc = inc + 1
  469.         term.write(string.sub(msg, ops, nextspace + ops - 1))
  470.         ops = ops + nextspace
  471.     end
  472.     term.setCursorPos(offset, height + inc)
  473.     term.write(string.sub(msg, ops))
  474.    
  475.     return inc + 1
  476. end
  477.  
  478. --Prints a string with right justfication, given a height and optional offset
  479. function printRight(msg, height, offset)
  480.     if not offset then offset = 0 end
  481.     term.setCursorPos(w - #msg - offset, height)
  482.     term.write(msg)
  483. end
  484.  
  485. --Prints a string in the center of the space specified- with the given height and offset
  486. function printOffCenter(msg, height, width, offset)
  487.     if not offset then offset = 0 end
  488.     term.setCursorPos(width/2 - #msg/2 + offset, height)
  489.     term.write(msg)
  490. end
  491.  
  492. --A slightly more advanced version of the above method, to include text wrapping over the
  493. --specified width. It will move to a new line each time, and return the number of lines it takes.
  494. function wprintOffCenter(msg, height, width, offset)
  495.     local inc = 0
  496.     local ops = 1
  497.     while #msg - ops > width do
  498.         local nextspace = 0
  499.         while string.find(msg, " ", ops + nextspace) and
  500.                 string.find(msg, " ", ops + nextspace) - ops < width do
  501.             nextspace = string.find(msg, " ", nextspace + ops) + 1 - ops
  502.         end
  503.         local ox,oy = term.getCursorPos()
  504.         term.setCursorPos(width/2 - (nextspace)/2 + offset, height + inc)
  505.         inc = inc + 1
  506.         term.write(string.sub(msg, ops, nextspace + ops - 1))
  507.         ops = ops + nextspace
  508.     end
  509.     term.setCursorPos(width/2 - #string.sub(msg, ops)/2 + offset, height + inc)
  510.     term.write(string.sub(msg, ops))
  511.    
  512.     return inc + 1
  513. end
  514.  
  515. --Displays an image statically, in a frame and waits on a key event to dispel it. Offset on width and height can be chosen, leaving them null will center the image.
  516. --Note- any images with an uneven length will have spaces appended to them to make them fit.
  517. function displayStaticImage(index, woffset, hoffset)
  518.     local image = images[index]
  519.     local longest = 0
  520.     for i=1,#image do longest = math.max(longest, #image[i]) end
  521.    
  522.     if not woffset then
  523.         woffset = w/2 - longest/2
  524.     end
  525.     if not hoffset then
  526.         hoffset = h/2 - #image/2
  527.     end
  528.    
  529.     printLeft("/"..string.rep("-",longest).."\\", hoffset-1, woffset-1)
  530.     printLeft("\\"..string.rep("-",longest).."/", hoffset+#image+1, woffset-1)
  531.    
  532.     for i=1,#image do
  533.         printLeft("|"..image[i]..string.rep(" ",longest - #image[i]).."|", hoffset-1+i, woffset-1)
  534.     end
  535.    
  536.     os.pullEvent("key")
  537. end
  538.  
  539. --Displays an animated series of images in a frame with the start and end index of the image buffer. A key event will close the image. The time delay indicates the
  540. --space of time between each frame.
  541. function displayAnimatedImage(startindex, endindex, timedelay, woffset, hoffset)
  542.  
  543. end
  544.  
  545.                 --[[SETION: Overworld, Initialization]]--
  546.                
  547. --Opens the overworld directory and adds it to the overworld parameters
  548. function readOverWorld()
  549.     local file = io.open(shell.resolve(".").."/scripts/world", "r")
  550.     if not file then error("Error: \'world\' file not present!") end
  551.     local fline = file:read()
  552.     local count = 1
  553.     while fline do
  554.         overworld[count] = { }
  555.         for i=1,#fline do
  556.             overworld[count][i] = string.sub(fline,i,i)
  557.         end
  558.         fline = file:read()
  559.         count = count + 1
  560.     end
  561.     file:close()
  562.     print("Overworld successfully parsed.")
  563. end
  564.  
  565.                 --[[SECTION: Town, Helper]]--
  566.  
  567. --This will set any given quest to be active and in the user's set. Though this occurs most commonly in town, it can be done anywhere.
  568. function activateQuest(quest)
  569.     table.insert(activeQuests, quest)
  570.     if quest.onActivation then quest:onActivation() end
  571.     addEvent("New Quest: "..quest.name)
  572.    
  573.     if debugging then
  574.         writeToLog("New Quest Added", "\""..activeQuests[#activeQuests].name.."\"", activeQuests[#activeQuests].activeStage)
  575.     end
  576. end
  577.  
  578. --Completes a quest, and removes it from the active quest list. You can specify the message you want to display when the quest is removed, otherwise a default one is used.
  579. function deactivateQuest(quest, message)
  580.     table.remove(activeQuests, quest)
  581.     if message then
  582.         addEvent(message)
  583.     else
  584.         addEvent("Quest Complete!")
  585.     end
  586. end
  587.                
  588. --This checks anytime you want to see if a quest objective (or more than one) have been completed. The boolean determines
  589. --if only one is checked (i.e. if true once then it stops), or if it keeps going one after another.
  590. function checkQuestUpdate(event)
  591.     local questupdated = false
  592.     for i=1,#activeQuests do
  593.         --It only allows one per quest giver (the newest)
  594.         local quest, qstage = activeQuests[i], activeQuests[i].activestage
  595.         local statechange = quest.stages[qstage].condition(quest, event)
  596.         if statechange then
  597.             if activeQuests[i].activestage == -1 then
  598.                 deactivateQuest(i, quest.name.." Complete!")
  599.             end
  600.             return statechange
  601.         end
  602.     end
  603.     return nil
  604. end            
  605.                
  606. --Finds an NPC by name, if he exists within the town (to access his details but not change them)
  607. function findNPC(name, townindex)
  608.     for i=1,#town[townindex].npc do
  609.         local locnpc = town[townindex].npc[i]
  610.         if locnpc.name == name then return locnpc end
  611.     end
  612.     return nil
  613. end            
  614.  
  615. --Allows you to change an NPC's dialog, by name and town
  616. function setNPCDialogue(name, townindex, subject, dialogue)
  617.     for i=1,#town[townindex].npc do
  618.         local locnpc = town[townindex].npc[i]
  619.         if locnpc.name == name then
  620.             town[townindex].npc[i].dialogue[subject] = dialogue
  621.             return
  622.         end
  623.     end
  624. end
  625.  
  626. --Moves an NPC to a new location
  627. function setNPCPosition(name, townindex, newx, newy)
  628.     for i=1,#town[townindex].npc do
  629.         local locnpc = town[townindex].npc[i]
  630.         if locnpc.name == name then
  631.             town[townindex].npc[i].xpos = newx
  632.             town[townindex].npc[i].ypos = newy
  633.             return
  634.         end
  635.     end
  636. end
  637.  
  638. --Determines how much a given item will be valued by any NPC.
  639. function getNPCValueOn(locnpc, item)
  640.     if not item then return 0 end
  641.  
  642.     local value = math.floor(locnpc.priceModifier * item.value)
  643.     if value < 1 then value = 1
  644.     elseif locnpc.job == "merchant" and value > locnpc.goldLimit then value = locnpc.goldLimit end
  645.    
  646.     return value
  647. end
  648.  
  649. --This takes everyNPC in the world and generates a fresh inventory for them
  650. function refreshNPCInventory()
  651.     for t=1,#town do
  652.         for n=1,#town[t].npc do
  653.             local chosenNPC = town[t].npc[n]
  654.            
  655.             if chosenNPC.job == "smith" then
  656.                 chosenNPC.inventory = { }
  657.                 for i=1, math.random(5,15) do
  658.                     table.insert(chosenNPC.inventory, generateLoot(chosenNPC.level + 3))
  659.                 end
  660.             end
  661.         end
  662.     end
  663. end
  664.  
  665. --Simply returns the character's name- for dynamic use in dialogues etc.
  666. function getCharName()
  667.     return charName
  668. end
  669.  
  670. --Sets the character's name... weird bug means I have to do this. Sorry.
  671. function setCharName(newName)
  672.     charName = newName
  673. end
  674.  
  675.                 --[[SECTION: Dungeon, Initialization]]--
  676.                
  677. --This adds a 2 point space to each part of the level. It's not necessary but it makes level design easier. Part of the level parse.
  678. function bufferLevel(index)
  679.     for i=1,#dungeons[index].level do
  680.         for j=1, #dungeons[index].level[i] do
  681.             dungeons[index].level[i][j] = "  "..dungeons[index].level[i][j].."  "
  682.         end
  683.         table.insert(dungeons[index].level[i], 1, string.rep(" ", #dungeons[index].level[i][1]))
  684.         table.insert(dungeons[index].level[i], 1, string.rep(" ", #dungeons[index].level[i][1]))
  685.         table.insert(dungeons[index].level[i], string.rep(" ", #dungeons[index].level[i][1]))
  686.         table.insert(dungeons[index].level[i], string.rep(" ", #dungeons[index].level[i][1]))
  687.     end
  688.     dungeons[index].startx = dungeons[index].startx + 2
  689.     dungeons[index].starty = dungeons[index].starty + 2
  690. end
  691.  
  692.                 --[[SECTION: Combat, Helper]]--
  693.  
  694. --This takes an index from the creature library, creates a clone and returns it
  695. --You can use a numerical index or the creatures name, if it's easier
  696. function cloneCreature(index, xpos, ypos, level)
  697.     if type(index) == "string" then
  698.         for i,v in ipairs(creatures) do
  699.             if v.name == index then
  700.                 index = i
  701.                 break
  702.             end
  703.         end
  704.     end
  705.     if type(index) == "string" then error(index.." creature not found!") end
  706.     local newcreature = {
  707.         name = creatures[index].name,
  708.         xpos = xpos,
  709.         ypos = ypos,
  710.         levelpos = level,
  711.         level = creatures[index].level,
  712.         hit = creatures[index].hit,
  713.         xp = creatures[index].xp,
  714.         weapon = creatures[index].weapon,
  715.         ac = creatures[index].ac,
  716.         imageone = creatures[index].imageone,
  717.         imagetwo = creatures[index].imagetwo
  718.     }
  719.     return newcreature
  720. end
  721.  
  722. --This function adds a creature to a random position somewhere on the overworld map.
  723. function addCreatureToWorldMap()
  724.     --It only tries 5 times right now
  725.     for i=1,5 do
  726.         local xval = math.random(pox - 3, pox + 3)
  727.         local yval = math.random(poy - 3, poy + 3)
  728.        
  729.         if xval > 0 and yval > 0 and xval <= #overworld[1] and yval <= #overworld and
  730.                 overworld[yval][xval] == " " and (xval ~= pox or yval ~= poy) then
  731.             local safe = true
  732.             for k,v in pairs(overcreatures) do
  733.                 if v.xpos == xval and v.ypos == yval then
  734.                     safe = false
  735.                     break
  736.                 end
  737.             end
  738.        
  739.             if safe then
  740.                 local newCreature = cloneCreature(math.random(1,2), xval, yval, nil)
  741.                 newCreature.chaseturns = 0
  742.                 newCreature.stillturns = 0
  743.                 table.insert(overcreatures, newCreature)
  744.                 addEvent(newCreature.name.." has appeared!")
  745.                 return
  746.             end
  747.         end
  748.     end
  749. end
  750.  
  751. --This function adds a creature to a random position somewhere nearby the player in a dungeon.
  752. function addCreatureToDungeon()
  753.     local viableSpots = {}
  754.     for xval = dunpx - 3, dunpx + 3 do
  755.         for yval = dunpy - 3, dunpy + 3 do
  756.             if xval >= 2 and yval >= 2 and xval <= #dungeons[activeDungeon].level[dunlevel] - 2 and yval <= #dungeons[activeDungeon].level[dunlevel] - 2 and
  757.                     dungeonTileAt(xval,yval,dunlevel) ~= " " and (xval ~= dunpx or yval ~= dunpy) then
  758.                 table.insert(viableSpots, { x = xval, y = yval })
  759.             end
  760.         end
  761.     end
  762.    
  763.     --I have no idea, but this apparently is happening. Solve for later
  764.     if #viableSpots == 0 then return end
  765.    
  766.     local spot = viableSpots[math.random(1, #viableSpots)]
  767.     local possibleMonsters = {}
  768.     for k,v in pairs(creatures) do
  769.         if v.level <= charLevel then table.insert(possibleMonsters, k) end
  770.     end
  771.        
  772.     local newCreature = cloneCreature(possibleMonsters[math.random(1,#possibleMonsters)], spot.x, spot.y, dunlevel)
  773.     table.insert(dungeons[activeDungeon].creatures, newCreature)
  774.     addEvent("You hear a noise nearby.")
  775.     return
  776. end
  777.  
  778. --This determines how much is done, by a given weapon
  779. function calculateDamage(weapon, isPlayer)
  780.     return math.random(weapon.base - weapon.base/5, weapon.base + weapon.base/5)
  781. end
  782.  
  783. --Adds XP and increases the player's level
  784. function addXP(amount)
  785.     charXP = charXP + amount
  786.     if charXP >= charNextXP then
  787.         charXP = charXP - charNextXP
  788.         charNextXP = charNextXP * 2
  789.         charMaxHealth = charMaxHealth + 10
  790.         charHealth = charMaxHealth
  791.         charLevel = charLevel + 1
  792.         addEvent("You are now level "..charLevel.."!")
  793.     end
  794. end
  795.  
  796. --Adds Gold to the player's inventory
  797. function addGold(amount)
  798.     charGold = charGold + amount
  799.     addEvent("Got "..amount.." gold!")
  800. end
  801.  
  802. --This section determines how much damage is deflected by a given creatures, or player (using nil)
  803.  
  804.                 --[[SECTION: Inventory, Helper]]--
  805.  
  806. --This takes an index from the item library, creates a clone and returns it.
  807. function cloneItem(index)
  808.     return {
  809.         name = items[index].name,
  810.         class = items[index].class,
  811.         desc = items[index].desc,
  812.         bodypart = items[index].bodypart,
  813.         level = items[index].level,
  814.         requirements = items[index].requirements,
  815.         base = items[index].base,
  816.         enchantment = items[index].enchantment,
  817.         enchantTypes = items[index].enchantTypes,
  818.         value = items[index].value,
  819.         weight = items[index].weight
  820.     }
  821. end
  822.  
  823. --This takes a string index from the enchantment library, creates a clone and returns it.
  824. function cloneEnchantment(index)
  825.     if string.find(index, "#") == 1 then return { } end
  826.     if not enchantments[index] then return { } end
  827.     return {
  828.         name = enchantments[index].name,
  829.         desc = enchantments[index].desc,
  830.         prefix = enchantments[index].prefix,
  831.         skill = enchantments[index].skill,
  832.         effect = enchantments[index].effect,
  833.         level = enchantments[index].level,
  834.         value = enchantments[index].value
  835.     }
  836. end
  837.  
  838. --This generates a random bit of loot, based on the provided level, and returns it.
  839. function generateLoot(maxlevel)
  840.     --We have a 1 in 5 of the loot being enchanted.
  841.     local hasEnchant = math.random(1,5) == 1
  842.    
  843.     local viableItems = {}
  844.     for i=1,#items do
  845.         --We copy every index of value to us before creating the item
  846.         if items[i].level <= maxlevel then
  847.             table.insert(viableItems, i)   
  848.         end
  849.     end
  850.    
  851.     local finalItem = cloneItem(viableItems[math.random(1,#viableItems)])
  852.     if not hasEnchant then return finalItem end
  853.    
  854.     --If we need to enchant...
  855.     local viableEnchant = {}
  856.     --First we filter down so we have only "base" enchants (not categories, or #'s)
  857.    
  858.     for i=1,#finalItem.enchantTypes do
  859.         table.insert(viableEnchant, finalItem.enchantTypes[i])
  860.     end
  861.    
  862.     local allBaseEnchants = true
  863.     repeat
  864.         allBaseEnchants = true
  865.         for i=1,#viableEnchant do
  866.             local currEnchant = viableEnchant[i]
  867.             if string.find(currEnchant, "#") == 1 then
  868.                 table.remove(viableEnchant, i)
  869.                 for j=1,#enchantments[currEnchant] do
  870.                     table.insert(viableEnchant, enchantments[currEnchant][j])
  871.                 end
  872.                 allBaseEnchants = false
  873.                 break
  874.             end
  875.         end
  876.     until allBaseEnchants
  877.    
  878.     --Then we whittle off those that the weapon can't handle
  879.     local it = 1
  880.     while it <= #viableEnchant do
  881.         if enchantments[viableEnchant[it]].level > finalItem.level then
  882.             table.remove(viableEnchant, it)
  883.         else
  884.             it = it+1
  885.         end
  886.     end
  887.    
  888.     --Finally, we randomly pick one of those enchantments and bam! Cut, print, that's the show.
  889.     finalItem.enchantment = cloneEnchantment(viableEnchant[math.random(1,#viableEnchant)])
  890.     finalItem.value = math.floor(finalItem.value * finalItem.enchantment.value)
  891.     if finalItem.value < 1 then finalItem.value = 1 end
  892.     if finalItem.enchantment.skill == "base" then
  893.         if math.abs(finalItem.enchantment.effect) > (0.5 * finalItem.enchantment.effect) then
  894.             finalItem.base = math.floor(finalItem.base + finalItem.enchantment.effect)
  895.         else
  896.             finalItem.base = math.floor((finalItem.base/2) * (finalItem.enchantment.effect/math.abs(finalItem.enchantment.effect)))
  897.         end
  898.     end
  899.     return finalItem
  900. end
  901.  
  902.                 --[[SECTION: General, Interface]]--
  903.  
  904. --Runs a display for the interface, overlaying the interface screen and runs input on that interface
  905. --until a user termiantes the interaction.
  906. --We use an NPC here- depending on the profession we buy from his inventory or sell from our own.
  907. --This is toggled by the buy tag as the second parameter.
  908. function runTradeInterface(chosenNPC, buying)
  909.     local selected = 1
  910.     local scroll = 1
  911.     local selectInv = nil
  912.     if buying then selectInv = inventory
  913.     else selectInv = chosenNPC.inventory end
  914.    
  915.     while true do
  916.         --This is actually a tidy little time saver- shows the player's gold and his stats!
  917.         printTownInterface()
  918.         --First we draw our display, as always
  919.         printLeft(shopInterface[1], 1)
  920.         for i=2,12 do printLeft(shopInterface[2], i) end
  921.         printLeft(shopInterface[3], 13)
  922.         for i=14,18 do printLeft(shopInterface[4], i) end
  923.         printLeft(shopInterface[5], 19)
  924.        
  925.         for i=scroll,math.min(scroll+16,scroll+#selectInv-1) do
  926.             local prefix = "  "
  927.             if i==selected then prefix = ">>" end
  928.             local suffix = ""
  929.             for __,v in pairs(charEquipment) do
  930.                 if v.value == selectInv[i] then
  931.                     if selectInv[i].bodypart == "both" then suffix = "(S/O)"
  932.                     else suffix = v.suffix end
  933.                     break
  934.                 end
  935.             end
  936.            
  937.             if selectInv[i].enchantment then
  938.                 if selectInv[i].enchantment.prefix then prefix = prefix..selectInv[i].enchantment.name.." "
  939.                 else suffix = selectInv[i].enchantment.name..suffix end
  940.             end
  941.  
  942.             printLeft(prefix..selectInv[i].name.." "..suffix, i-scroll+2, 2)
  943.         end
  944.        
  945.         term.setCursorPos(31, 14)
  946.         if chosenNPC.job == "merchant" then
  947.             printLeft("Trader buys at "..(chosenNPC.priceModifier * 100).."%", 14, 31)
  948.             printLeft("Gold limit: "..chosenNPC.goldLimit, 15, 31)
  949.             printLeft("Final price: "..getNPCValueOn(chosenNPC, selectInv[selected]), 17, 31)
  950.         else
  951.             printLeft("Trader sells at "..(chosenNPC.priceModifier * 100).."%", 14, 31)
  952.             printLeft("Final price: "..getNPCValueOn(chosenNPC, selectInv[selected]), 17, 31)
  953.         end
  954.        
  955.         local id,key = os.pullEvent("key")
  956.         if key == keys.enter then break
  957.         elseif key == keys.up and selected > 1 then
  958.             selected = selected-1
  959.             if selected < scroll + 2 and scroll > 1 then
  960.                 scroll = scroll - 1
  961.             end
  962.        
  963.         elseif key == keys.down and selected < #selectInv then
  964.             selected = selected+1
  965.             if selected > scroll + 14 and selected < #selectInv - 1 then
  966.                 scroll = scroll+1
  967.             end
  968.         elseif key == keys.space then
  969.             if buying then
  970.                 charGold = charGold + getNPCValueOn(chosenNPC, selectInv[selected])
  971.                 if isEquipped(selected) then equipItem(selected) end
  972.                 table.remove(selectInv, selected)
  973.                
  974.                 if selected > #selectInv then selected = selected-1 end
  975.                 if #selectInv == 0 then
  976.                     local ctitle = chosenNPC.name.." the "..chosenNPC.job.." says:"
  977.                     displayConfirmDialogue(ctitle, "That's all your gear! Pleasure doing business with you.")
  978.                     break
  979.                 end
  980.             elseif not buying and charGold >= getNPCValueOn(chosenNPC, selectInv[selected]) then
  981.                 charGold = charGold - getNPCValueOn(chosenNPC, selectInv[selected])
  982.                 table.insert(inventory, selectInv[selected])
  983.                 table.remove(selectInv, selected)
  984.                
  985.                 if selected > #selectInv then selected = selected-1 end
  986.                 if #selectInv == 0 then
  987.                     local ctitle = chosenNPC.name.." the "..chosenNPC.job.." says:"
  988.                     displayConfirmDialogue(ctitle, "I'm sold out of stock! Come back when I have some more stuff.")
  989.                     break
  990.                 end
  991.             end
  992.         end
  993.     end
  994. end
  995.  
  996. --Runs a display interface that displays each quest the user has, and allows them to cycle between them.
  997. --A quest is displayed on each page of a book, with the user able to flip pages left and right to see other
  998. --quests.
  999. function runJournalInterface()
  1000.     if #activeQuests == 0 then
  1001.         addEvent("You have no currently active quests.")
  1002.         return
  1003.     end
  1004.    
  1005.     if journalPage * 2 - 1 > #activeQuests then journalPage = 1 end
  1006.    
  1007.     while true do
  1008.         --Draw the backdrop
  1009.         for i=1,2 do
  1010.             term.setCursorPos(1,i)
  1011.             term.write(journalInterface[i])
  1012.         end
  1013.         for i=3,h-1 do
  1014.             term.setCursorPos(1,i)
  1015.             term.write(journalInterface[3])
  1016.         end
  1017.         term.setCursorPos(1,h)
  1018.         term.write(journalInterface[4])
  1019.        
  1020.         --Draw the quest on each page
  1021.         local dquest = activeQuests[journalPage * 2 - 1]
  1022.         if dquest then
  1023.             wprintLeft(dquest.name, 3, 22, 3)
  1024.             local _,ny = term.getCursorPos()
  1025.             ny = ny + 3
  1026.             wprintLeft(dquest.generalDescription, ny, 22, 3)
  1027.             local _,ny = term.getCursorPos()
  1028.             ny = ny + 3
  1029.             wprintLeft(dquest.stages[dquest.activestage].desc, ny, 22, 3)
  1030.             wprintOffCenter("Page "..(journalPage * 2 - 1), h-1, 22, 3)
  1031.         end
  1032.        
  1033.         dquest = activeQuests[journalPage * 2]
  1034.         if dquest then
  1035.             wprintLeft(dquest.name, 3, 22, 29)
  1036.             local _,ny = term.getCursorPos()
  1037.             ny = ny + 3
  1038.             wprintLeft(dquest.generalDescription, ny, 22, 29)
  1039.             local _,ny = term.getCursorPos()
  1040.             ny = ny + 3
  1041.             wprintLeft(dquest.stages[dquest.activestage].desc, ny, 22, 29)
  1042.             wprintOffCenter("Page "..(journalPage * 2), h-1, 22, 29)
  1043.         end
  1044.        
  1045.         --Left and right arrow events shift from one page to the next, enter quits.
  1046.         local _,key = os.pullEvent("key")
  1047.        
  1048.         if key == keys.left and journalPage > 1 then
  1049.             journalPage = journalPage - 1
  1050.         elseif key == keys.right and activeQuests[(journalPage + 1) * 2 - 1] then
  1051.             journalPage = journalPage + 1
  1052.         elseif key == keys.enter then break end
  1053.     end
  1054. end
  1055.                
  1056. --[[INCOMPLETE]]--
  1057. --Displays a screen with each of the player's attributes
  1058. function printPlayerStats()
  1059.    
  1060. end
  1061.  
  1062.                 --[[SECTION: Inventory, Interface]]--
  1063.  
  1064. --This method creates a display for the interface, overlaying the current screen, and runs input on
  1065. --that interface until the user terminates the interaction.
  1066. function runInventoryInterface()
  1067.     local selected = 1
  1068.     local scroll = 1
  1069.    
  1070.     if #inventory == 0 then
  1071.         displayConfirmDialogue("*", "Your inventory is empty.")
  1072.         return
  1073.     end
  1074.    
  1075.     while true do
  1076.         --This actually produces the physical display for the interface
  1077.         for i=1,#inventoryInterface do
  1078.             term.setCursorPos(1, i)
  1079.             term.write(inventoryInterface[i])
  1080.         end
  1081.         for i=#inventoryInterface+1,h-1 do
  1082.             term.setCursorPos(1,i)
  1083.             term.clearLine()
  1084.         end
  1085.         term.setCursorPos(1,h)
  1086.         term.write(inventoryInterface[#inventoryInterface])
  1087.         for i=scroll,math.min(scroll+10,scroll+#inventory-1) do
  1088.             local prefix = "  "
  1089.             if i==selected then prefix = ">>" end
  1090.             local suffix = ""
  1091.             for __,v in pairs(charEquipment) do
  1092.                 if v.value == inventory[i] then
  1093.                     if inventory[i].bodypart == "both" then suffix = "(S/O)"
  1094.                     else suffix = v.suffix end
  1095.                     break
  1096.                 end
  1097.             end
  1098.            
  1099.             if inventory[i].enchantment then
  1100.                 if inventory[i].enchantment.prefix then prefix = prefix..inventory[i].enchantment.name.." "
  1101.                 else suffix = inventory[i].enchantment.name..suffix end
  1102.             end
  1103.  
  1104.             printLeft(prefix..inventory[i].name.." "..suffix, i-scroll+2, 2)
  1105.         end
  1106.         local itemdesc = inventory[selected].desc
  1107.         if inventory[selected].enchantment then
  1108.             itemdesc = itemdesc.." "..inventory[selected].enchantment.desc
  1109.             printOffCenter(inventory[selected].enchantment.skill.." "..inventory[selected].enchantment.effect, h-1, w, 0)
  1110.         end
  1111.        
  1112.         wprintOffCenter(itemdesc, #inventoryInterface + 1, w-4, 2)
  1113.         if inventory[selected].class == "weapon" then
  1114.             printLeft("Damage:"..inventory[selected].base, h-1, 1)
  1115.         elseif inventory[selected].class == "shield" then
  1116.             printLeft("Block: "..(inventory[selected].base*100).."%", h-1, 1)
  1117.         elseif inventory[selected].class == "armour" then
  1118.             printLeft("AC:"..inventory[selected].base, h-1, 1)
  1119.         end
  1120.         printRight("Value: "..inventory[selected].value, h-1, 1)
  1121.        
  1122.         --This handles input, which is repeated until the method is complete
  1123.         local id, key = os.pullEvent("key")
  1124.         if key == keys.enter then break
  1125.         elseif key == keys.down and selected < #inventory then
  1126.             selected = selected+1
  1127.             if selected > scroll + 8 and selected < #inventory - 1 then
  1128.                 scroll = scroll+1
  1129.             end
  1130.         elseif key == keys.up and selected > 1 then
  1131.             selected = selected-1
  1132.             if selected < scroll + 2 and scroll > 1 then
  1133.                 scroll = scroll - 1
  1134.             end
  1135.         elseif key == keys.space then
  1136.             equipItem(selected)
  1137.         end
  1138.     end
  1139. end
  1140.  
  1141. --This takes a selectedIndex and equips it (or unequips it)
  1142. function equipItem(itemIndex)
  1143.     if inventory[itemIndex].bodypart == "both" then
  1144.         if charEquipment["swordhand"].value == inventory[itemIndex] then
  1145.             charEquipment["swordhand"].value = nil
  1146.             charEquipment["offhand"].value = nil
  1147.         else
  1148.             charEquipment["swordhand"].value = inventory[itemIndex]
  1149.             charEquipment["offhand"].value = inventory[itemIndex]
  1150.         end
  1151.     elseif charEquipment[inventory[itemIndex].bodypart].value == inventory[itemIndex] then
  1152.         charEquipment[inventory[itemIndex].bodypart].value = nil
  1153.     else
  1154.         local oldValue = charEquipment[inventory[itemIndex].bodypart].value
  1155.         if oldValue and oldValue.bodypart == "both" then
  1156.             charEquipment["swordhand"].value = nil
  1157.             charEquipment["offhand"].value = nil
  1158.         end
  1159.         charEquipment[inventory[itemIndex].bodypart].value = inventory[itemIndex]
  1160.     end
  1161. end
  1162.  
  1163. --Returns true or false depending on whether or not the item has been equipped
  1164. function isEquipped(itemIndex)
  1165.     local selItem = inventory[itemIndex]
  1166.     if selItem.bodypart == "both" then
  1167.         return charEquipment["swordhand"].value == selItem
  1168.     else
  1169.         return charEquipment[selItem.bodypart].value == selItem
  1170.     end
  1171. end
  1172.  
  1173.                 --[[SECTION: Overworld, Display]]--
  1174.  
  1175. --Prints the world map for the overworld
  1176. function printWorldMap()
  1177.     local moffx = 1
  1178.     local moffy = 1
  1179.    
  1180.     if(pox > math.ceil(mapw / 2)) then moffx = pox - math.ceil(mapw/2) end
  1181.     if(pox > #overworld[1] - math.ceil(mapw / 2) + 1) then moffx = #overworld[1] - mapw end
  1182.    
  1183.     if(poy > math.ceil(maph / 2)) then moffy = poy - math.ceil(maph/2) end
  1184.     if(poy > #overworld - math.ceil(maph / 2) + 1) then moffy = #overworld - maph + 1 end
  1185.    
  1186.     --local mpx = mapw/2
  1187.     --local mpy = maph/2
  1188.    
  1189.     for y = moffy, moffy + maph -1 do
  1190.         term.setCursorPos(2, y - moffy + 3)
  1191.         local line = ""
  1192.         for x = moffx, moffx + mapw - 1 do
  1193.             line = line..overworld[y][x]
  1194.         end
  1195.         --term.write(string.sub(overworld[i + moffy - 1], moffx, mapw + moffx - 1))
  1196.         term.write(line)
  1197.     end
  1198.    
  1199.     --For every town within the world, it prints a "T", where it can be found
  1200.     for i = 1, #town do
  1201.         if town[i].visible and town[i].xpos >= moffx and town[i].xpos <= moffx + mapw - 1 and
  1202.                 town[i].ypos >= moffy and town[i].ypos <= moffy + maph - 1 then
  1203.             term.setCursorPos(2 + town[i].xpos - moffx, 3 + town[i].ypos - moffy)
  1204.             term.write("T")
  1205.         end
  1206.     end
  1207.     --For every dungeon within the world, the numeral "0" is used (to indicate cave opening)
  1208.     for i = 1, #dungeons do
  1209.         if dungeons[i].visible and dungeons[i].xpos >= moffx and dungeons[i].xpos <= moffx + mapw - 1 and
  1210.                 dungeons[i].ypos >= moffy and dungeons[i].ypos <= moffy + maph - 1 then
  1211.             term.setCursorPos(2 + dungeons[i].xpos - moffx, 3 + dungeons[i].ypos - moffy)
  1212.             term.write("0")
  1213.         end
  1214.     end
  1215.     --Monsters in the overworld are displayed with the first letter of their name.
  1216.     for i = 1, #overcreatures do
  1217.         term.setCursorPos(2 + overcreatures[i].xpos - moffx, 3 + overcreatures[i].ypos - moffy)
  1218.         term.write(string.sub(overcreatures[i].name, 1, 1))
  1219.     end
  1220.    
  1221.     term.setCursorPos(2 + pox - moffx - 1, 3 + poy - moffy - 1)
  1222.     term.write("@")
  1223.    
  1224. end
  1225.  
  1226. --This draws the "external" interface, the map frame, player stats and the history log
  1227. function printOverworldInterface()
  1228.     term.clear()
  1229.     --Draws the frame
  1230.     term.setCursorPos(2,2)
  1231.     term.write(string.rep("_", mapw))
  1232.     term.setCursorPos(2,maph + 3)
  1233.     term.write(string.rep("-", mapw))
  1234.     for i=3,maph+2 do
  1235.         term.setCursorPos(1, i)
  1236.         term.write("("..string.rep(" ", mapw)..")")
  1237.     end
  1238.     --Draws the frame title
  1239.     printOffCenter(worldname, 1, mapw, 2)
  1240.    
  1241.     --Draws player information
  1242.     printOffCenter(charName, 2, (w - mapw - 5), mapw + 5)
  1243.     printOffCenter(string.rep("=", #charName), 3, (w - mapw - 5), mapw + 5)
  1244.    
  1245.     term.setCursorPos(mapw + 4, 5)
  1246.     term.write("Level "..charLevel.." "..charClass)
  1247.     term.setCursorPos(mapw + 4, 7)
  1248.     term.write("HP: "..charHealth.."/"..charMaxHealth)
  1249.     term.setCursorPos(mapw + 4, 8)
  1250.     term.write("XP: "..charXP.."/"..charNextXP)
  1251.     term.setCursorPos(mapw + 4, 9)
  1252.     term.write("Gold: "..charGold)
  1253.     term.setCursorPos(mapw + 4, 10)
  1254.     term.write(string.rep("-", w - (mapw + 4)))
  1255.    
  1256.     --Draws the event log
  1257.     local hoffset = 0
  1258.     for i=1,#overEventList do
  1259.         hoffset = hoffset + wprintLeft(overEventList[i], h - 9 + i + hoffset, w - (mapw + 4), mapw + 4) - 1
  1260.     end
  1261. end
  1262.  
  1263.                 --[[SECTION: Town, Display]]--
  1264.  
  1265. --This draws the "external" interface, the map frame, player stats and the history log
  1266. function printTownInterface()
  1267.     term.clear()
  1268.    
  1269.     --Draws the event log (this may/perhaps should? be eclipsed)
  1270.     local hoffset = 0
  1271.     for i=1,#townEventList do
  1272.         hoffset = hoffset + wprintLeft(townEventList[i], h - 9 + i + hoffset, w - (mapw + 4), mapw + 4) - 1
  1273.     end
  1274.    
  1275.     --Draws the frame
  1276.     term.setCursorPos(1,2)
  1277.     term.write("/"..string.rep("=", mapw).."\\")
  1278.     term.setCursorPos(1,maph + 3)
  1279.     term.write("\\"..string.rep("=", mapw).."/")
  1280.     --This draws the town itself
  1281.     for i=3,maph+2 do
  1282.         term.setCursorPos(1, i)
  1283.         term.write("|"..town[activeTown].level[i-2].."|")
  1284.     end
  1285.     --Draws each NPC within the town
  1286.     for i=1,#town[activeTown].npc do
  1287.         term.setCursorPos(town[activeTown].npc[i].xpos + 1, town[activeTown].npc[i].ypos + 2)
  1288.         term.write("&")
  1289.     end
  1290.     --Draws the frame title
  1291.     printOffCenter(town[activeTown].name, 1, mapw, 2)
  1292.     term.setCursorPos(1, 10)
  1293.    
  1294.    
  1295.     --Draws player information
  1296.     printOffCenter(charName, 2, (w - mapw - 5), mapw + 5)
  1297.     printOffCenter(string.rep("=", #charName), 3, (w - mapw - 5), mapw + 5)
  1298.    
  1299.     term.setCursorPos(mapw + 4, 5)
  1300.     term.write("Level "..charLevel.." "..charClass)
  1301.     term.setCursorPos(mapw + 4, 7)
  1302.     term.write("HP: "..charHealth.."/"..charMaxHealth)
  1303.     term.setCursorPos(mapw + 4, 8)
  1304.     term.write("XP: "..charXP.."/"..charNextXP)
  1305.     term.setCursorPos(mapw + 4, 9)
  1306.     term.write("Gold: "..charGold)
  1307.     term.setCursorPos(mapw + 4, 10)
  1308.     term.write(string.rep("-", w - (mapw + 4)))
  1309.    
  1310.     --Draws the player himself
  1311.     term.setCursorPos(1 + townpx, 2 + townpy)
  1312.     term.write("@")
  1313. end
  1314.  
  1315.                     --[[SECTION: Dungeon, Display]]--
  1316.  
  1317. --This, of course, draws the corridor.
  1318. function drawDungeonCorridor()
  1319.     term.clear()
  1320.     --Draws the corridor (the basis for every scene)
  1321.     for i=1,#cor do
  1322.         term.setCursorPos(corx + 2, cory + i - 1)
  1323.         term.write(cor[i])
  1324.     end
  1325.    
  1326.     --These point to coordinates left and right of the player
  1327.     local rx,ry = getRight(dunfx,dunfy)
  1328.     local lx,ly = getLeft(dunfx,dunfy)
  1329.    
  1330.     --[[Section: Two tiles away]]--
  1331.    
  1332.     --If there is a turn on the left or right of the forward tile, this draws the branch
  1333.     local turningtright = false
  1334.     if string.sub(dungeons[activeDungeon].level[dunlevel][dunpy + dunfy + ry], dunpx + dunfx + rx, dunpx + dunfx + rx) ~= " " then
  1335.         turningtright = true
  1336.         for i=1,#trightturn do
  1337.             term.setCursorPos(trightturnx, trightturny + i - 1)
  1338.             term.write(trightturn[i]);
  1339.         end
  1340.     end
  1341.    
  1342.     local turningtleft = false
  1343.     if string.sub(dungeons[activeDungeon].level[dunlevel][dunpy + dunfy + ly], dunpx + dunfx + lx, dunpx + dunfx + lx) ~= " " then
  1344.         turningtleft = true
  1345.         for i=1,#tleftturn do
  1346.             term.setCursorPos(tleftturnx, tleftturny + i - 1)
  1347.             term.write(tleftturn[i]);
  1348.         end
  1349.     end
  1350.    
  1351.     --Determines if there is a wall 2 tiles from the facing position- draws a wall ahead
  1352.     local twoendtile = string.sub(dungeons[activeDungeon].level[dunlevel][dunpy + dunfy + dunfy], dunpx + dunfx + dunfx, dunpx + dunfx + dunfx)
  1353.     if twoendtile == " " or twoendtile == "E" then
  1354.         for i=1,#twoend do
  1355.             term.setCursorPos(twoendx, twoendy + i - 1)
  1356.             if twoendtile == " " then term.write(twoend[i])
  1357.             else term.write(twoexitend[i]) end
  1358.             --If turning left or right, the wall needs to be slightly modified to remove the "extra" edge
  1359.             if turningtright then
  1360.                 term.setCursorPos(trightturnx, trightturny + i - 1)
  1361.                 if i == 1 then term.write("_")
  1362.                 elseif i == #twoend then term.write("-")
  1363.                 else term.write(" ") end
  1364.             end
  1365.             if turningtleft then
  1366.                 term.setCursorPos(tleftturnx + #tleftturn[i] - 1, tleftturny + i - 1)
  1367.                 if i == 1 then term.write("_")
  1368.                 elseif i == #twoend then term.write("-")
  1369.                 else term.write(" ") end
  1370.             end
  1371.         end
  1372.     end
  1373.    
  1374.     --Draws creatures two tiles away, if they're visible
  1375.     for i=1, #dungeons[activeDungeon].creatures do
  1376.         local c = dungeons[activeDungeon].creatures[i]
  1377.        
  1378.         if c.xpos == dunpx + dunfx + dunfx and c.ypos == dunpy + dunfy + dunfy and c.levelpos == dunlevel and
  1379.                 string.sub(dungeons[activeDungeon].level[dunlevel][dunpy+dunfy], dunpx+dunfx, dunpx+dunfx) ~= " " then
  1380.             for j=1,#c.imagetwo do
  1381.                 term.setCursorPos(23/2 - #c.imagetwo[j]/2 + 1, 12 - (#c.imagetwo - j))
  1382.                 term.write(c.imagetwo[j])
  1383.             end
  1384.         end
  1385.     end
  1386.    
  1387.     --[[Section: On the immediate tile]]--
  1388.    
  1389.     --If there is a left or right turn on the current tile, this draws the branch
  1390.     local turningright = false
  1391.     if string.sub(dungeons[activeDungeon].level[dunlevel][dunpy + ry], dunpx + rx, dunpx + rx) ~= " " then
  1392.         turningright = true
  1393.         for i=1,#rightturn do
  1394.             term.setCursorPos(rightturnx, rightturny + i - 1)
  1395.             term.write(rightturn[i])
  1396.         end
  1397.     end
  1398.    
  1399.     local turningleft = false
  1400.     if string.sub(dungeons[activeDungeon].level[dunlevel][dunpy + ly], dunpx + lx, dunpx + lx) ~= " " then
  1401.         turningleft = true
  1402.         for i=1,#leftturn do
  1403.             term.setCursorPos(leftturnx, leftturny + i - 1)
  1404.             term.write(leftturn[i])
  1405.         end
  1406.     end
  1407.    
  1408.     --Determines if there is a wall immediately facing the player- draws the wall ahead
  1409.     local oneendtile = string.sub(dungeons[activeDungeon].level[dunlevel][dunpy + dunfy], dunpx + dunfx, dunpx + dunfx)
  1410.     if oneendtile == " " or oneendtile == "E" then
  1411.         for i=1,#oneend do
  1412.             term.setCursorPos(oneendx, oneendy + i - 1)
  1413.             if oneendtile == " " then term.write(oneend[i])
  1414.             else term.write(oneexitend[i]) end
  1415.             --If turning left or right, the wall needs to be slightly modified to remove the "extra" edge
  1416.             if turningright then
  1417.                 term.setCursorPos(rightturnx, rightturny + i + 1)
  1418.                 if i == 1 or i == #oneend then term.write("_")
  1419.                 else term.write(" ") end
  1420.             end
  1421.             if turningleft then
  1422.                 term.setCursorPos(leftturnx + #leftturn[i] - 1, leftturny + i + 1)
  1423.                 if i == 1 or i == #oneend then term.write("_")
  1424.                 else term.write(" ") end
  1425.             end
  1426.         end
  1427.     end
  1428.    
  1429.     --If the player has their back to a wall, the "front corners" of the corridor need to be erased
  1430.     if turningleft and string.sub(dungeons[activeDungeon].level[dunlevel][dunpy - dunfy], dunpx - dunfx, dunpx - dunfx) == " " then
  1431.         for i=1,#bwturn do
  1432.             term.setCursorPos(bwleftturnx, bwturny + i - 1)
  1433.             term.write(bwturn[i])
  1434.         end
  1435.     end if turningright and string.sub(dungeons[activeDungeon].level[dunlevel][dunpy - dunfy], dunpx - dunfx, dunpx - dunfx) == " " then
  1436.         for i=1,#bwturn do
  1437.             term.setCursorPos(bwrightturnx, bwturny + i - 1)
  1438.             term.write(bwturn[i])
  1439.         end
  1440.     end
  1441.    
  1442.     --[[Section: Physical objects]]--
  1443.    
  1444.     --This draws creatures on the immediate tile
  1445.    
  1446.     for i = 1, #dungeons[activeDungeon].creatures do
  1447.         local c = dungeons[activeDungeon].creatures[i]
  1448.        
  1449.         if c.xpos == dunpx + dunfx and c.ypos == dunpy + dunfy  and c.levelpos == dunlevel then
  1450.             for j=1,#c.imageone do
  1451.                 term.setCursorPos(23/2 - #c.imageone[j]/2 + 1, 15 - (#c.imageone - j))
  1452.                 term.write(c.imageone[j])
  1453.             end
  1454.         end
  1455.     end
  1456.  
  1457.     --Ladders- Up, Down and Both, and chests Open and Closed
  1458.     if string.sub(dungeons[activeDungeon].level[dunlevel][dunpy], dunpx, dunpx) == "D" then
  1459.         for i=1,#ladderd do
  1460.             term.setCursorPos(ladderdx, ladderdy + i - 1)
  1461.             term.write(ladderd[i])
  1462.         end
  1463.     elseif string.sub(dungeons[activeDungeon].level[dunlevel][dunpy], dunpx, dunpx) == "U" then
  1464.         for i=1,#ladderu do
  1465.             term.setCursorPos(ladderux, ladderuy + i - 1)
  1466.             term.write(ladderu[i])
  1467.         end
  1468.     elseif string.sub(dungeons[activeDungeon].level[dunlevel][dunpy], dunpx, dunpx) == "B" then
  1469.         for i=1,#ladderud do
  1470.             term.setCursorPos(ladderudx, ladderudy + i - 1)
  1471.             term.write(ladderud[i])
  1472.         end
  1473.     elseif string.sub(dungeons[activeDungeon].level[dunlevel][dunpy], dunpx, dunpx) == "C" then
  1474.         for i=1,#chestc do
  1475.             term.setCursorPos(chestcx, chestcy + i - 1)
  1476.             term.write(chestc[i])
  1477.         end
  1478.     elseif string.sub(dungeons[activeDungeon].level[dunlevel][dunpy], dunpx, dunpx) == "O" then
  1479.         for i=1,#chesto do
  1480.             term.setCursorPos(chestox, chestoy + i - 1)
  1481.             term.write(chesto[i])
  1482.         end
  1483.     end
  1484. end
  1485.  
  1486. --This draws the side interface
  1487. function drawDungeonInterface()
  1488.     --Draws a simple border on the side of the screen
  1489.     for i=1,h do
  1490.         term.setCursorPos(24, i)
  1491.         term.write("#")
  1492.     end
  1493.    
  1494.     printOffCenter(dungeons[activeDungeon].name, 1, w-25, 25)
  1495.     printOffCenter(string.rep("*", #dungeons[activeDungeon].name), 2, w-25, 25)
  1496.     if dungeons[activeDungeon].playerHasCompass then drawCompass(26,2) end
  1497.    
  1498.     term.setCursorPos(38, 4)
  1499.     term.write("Level "..charLevel)
  1500.     term.setCursorPos(38, 5)
  1501.     term.write("HP: "..charHealth.."/"..charMaxHealth)
  1502.     term.setCursorPos(38, 6)
  1503.     term.write("XP: "..charXP.."/"..charNextXP)
  1504.     term.setCursorPos(38, 7)
  1505.     term.write("Gold: "..charGold)
  1506.     term.setCursorPos(25, 9)
  1507.     term.write(string.rep("#",w-25))
  1508.    
  1509.     --Draws an altimeter (for lack of a better word)
  1510.     for i=1,#dungeons[activeDungeon].level do
  1511.         term.setCursorPos(33, i + 3)
  1512.         if dunlevel == i then term.write("["..i.."]")
  1513.         else term.write(" "..i.." ") end
  1514.     end
  1515.    
  1516.     --Draws the event log
  1517.     local hoffset = 0
  1518.     for i=1,#dungeonEventList do
  1519.         hoffset = hoffset + wprintLeft(dungeonEventList[i], h - 10 + i + hoffset, w - 25, 25) - 1
  1520.     end
  1521. end
  1522.  
  1523. --Uses facing and nearby enemies to draw a simple compass
  1524. function drawCompass(x,y)
  1525.     for i=1,#compass do
  1526.         term.setCursorPos(x, y + i)
  1527.         term.write(compass[i])
  1528.     end
  1529.     if dunfx == -1 then
  1530.         term.setCursorPos(x + 1, y + 3)
  1531.         term.write("__")
  1532.     elseif dunfx == 1 then
  1533.         term.setCursorPos(x + 4, y + 3)
  1534.         term.write("__")
  1535.     elseif dunfy == -1 then
  1536.         term.setCursorPos(x + 3, y + 2)
  1537.         term.write("|")
  1538.         term.setCursorPos(x + 3, y + 3)
  1539.         term.write("|")
  1540.     elseif dunfy == 1 then
  1541.         term.setCursorPos(x + 3, y + 4)
  1542.         term.write("|")
  1543.         term.setCursorPos(x + 3, y + 5)
  1544.         term.write("|")
  1545.     end
  1546.    
  1547.     --If an enemy is nearby, within 1 or 2 squares (and visible to the player, not behind walls)
  1548.     --then the compass will draw a x or a X to indicate danger in that direction.
  1549.     local nn = 0
  1550.     local ns = 0
  1551.     local ne = 0
  1552.     local nw = 0
  1553.     for i=1,#dungeons[activeDungeon].creatures do
  1554.         local c = dungeons[activeDungeon].creatures[i]
  1555.         if c.levelpos == dunlevel then
  1556.        
  1557.             if dunpx - c.xpos == 1 and dunpy - c.ypos == 0 then
  1558.                 ne = 1
  1559.                 term.setCursorPos(x, y + 3)
  1560.                 term.write("X")
  1561.             elseif ne ~= 1 and dunpx - c.xpos == 2 and dunpy - c.ypos == 0 then
  1562.                 term.setCursorPos(x, y + 3)
  1563.                 term.write("x")
  1564.             end
  1565.            
  1566.             if dunpx - c.xpos == -1 and dunpy - c.ypos == 0 then
  1567.                 nw = 1
  1568.                 term.setCursorPos(x + 6, y + 3)
  1569.                 term.write("X")
  1570.             elseif nw ~= 1 and dunpx - c.xpos == -2 and dunpy - c.ypos == 0 then
  1571.                 term.setCursorPos(x + 6, y + 3)
  1572.                 term.write("x")
  1573.             end
  1574.            
  1575.             if dunpy - c.ypos == 1 and dunpx - c.xpos == 0 then
  1576.                 nn = 1
  1577.                 term.setCursorPos(x + 3, y + 1)
  1578.                 term.write("X")
  1579.             elseif nn ~= 1 and dunpy - c.ypos == 2 and dunpx - c.xpos == 0 then
  1580.                 term.setCursorPos(x + 3, y + 1)
  1581.                 term.write("x")
  1582.             end
  1583.            
  1584.             if dunpy - c.ypos == -1 and dunpx - c.xpos == 0 then
  1585.                 ns = 1
  1586.                 term.setCursorPos(x + 3, y + 5)
  1587.                 term.write("X")
  1588.             elseif ns ~= 1 and dunpy - c.ypos == -2 and dunpx - c.xpos == 0 then
  1589.                 term.setCursorPos(x + 3, y + 5)
  1590.                 term.write("x")
  1591.             end
  1592.         end
  1593.     end
  1594. end
  1595.  
  1596. --This draws a dungeon map, with the player, his orientation and the location of all enemies
  1597. function drawDungeonMap()
  1598.     term.setCursorPos(3, 3)
  1599.     term.write("*"..string.rep("-", #dungeons[activeDungeon].level[dunlevel][1]).."*")
  1600.     term.setCursorPos(3, 3 + #dungeons[activeDungeon].level[dunlevel] + 1)
  1601.     term.write("*"..string.rep("-", #dungeons[activeDungeon].level[dunlevel][1]).."*")
  1602.     for i=1,#dungeons[activeDungeon].level[dunlevel] do
  1603.         local str = dungeons[activeDungeon].level[dunlevel][i]
  1604.         str = string.gsub(str, "C", "#")
  1605.         str = string.gsub(str, "O", "C")
  1606.         term.setCursorPos(3, 3 + i)
  1607.         term.write("|"..str.."|")
  1608.     end
  1609.    
  1610.     --[[
  1611.     for i=1,#dungeons[activeDungeon].creatures do
  1612.         local crea = dungeons[activeDungeon].creatures[i]
  1613.         if crea.levelpos == dunlevel then
  1614.             term.setCursorPos(3 + crea.xpos, 3 + crea.ypos)
  1615.             term.write(string.sub(crea.name, 1, 1))
  1616.         end
  1617.     end]]--
  1618.     term.setCursorPos(3 + dunpx, 3 + dunpy)
  1619.     term.write("@")
  1620. end
  1621.  
  1622.                 --[[SECTION: Overword, Input]]--
  1623.  
  1624. --A simple text input reader, this is more powerful than "io.read" as it doesn't blank the line it's working
  1625. --on, it has a numerical limit and it trims whitespace. Non-sensible or null lims are set to the end of the screen.
  1626. function readInput(lim)
  1627.     sleep(0.1)
  1628.     term.setCursorBlink(true)
  1629.  
  1630.     local inputString = ""
  1631.     local ox,oy = term.getCursorPos()
  1632.     term.write(" ")
  1633.     term.setCursorPos(ox, oy)
  1634.     if not lim or type(lim) ~= "number" or lim < 1 then lim = w - ox end
  1635.    
  1636.     while true do
  1637.         local id,key = os.pullEvent()
  1638.        
  1639.         if id == "key" and key == 14 and #inputString > 0 then
  1640.             inputString = string.sub(inputString, 1, #inputString-1)
  1641.             term.setCursorPos(ox + #inputString,oy)
  1642.             term.write(" ")
  1643.         elseif id == "key" and key == 28 and inputString ~= string.rep(" ", #inputString) then
  1644.             break
  1645.         elseif id == "char" and #inputString < lim then
  1646.             inputString = inputString..key
  1647.         end
  1648.         term.setCursorPos(ox,oy)
  1649.         term.write(inputString)
  1650.         term.setCursorPos(ox + #inputString, oy)
  1651.     end
  1652.    
  1653.     while string.sub(inputString, 1, 1) == " " do
  1654.         inputString = string.sub(inputString, 2, #inputString)
  1655.     end
  1656.     while string.sub(inputString, #inputString, #inputString) == " " do
  1657.         inputString = string.sub(inputString, 1, #inputString-1)
  1658.     end
  1659.     term.setCursorBlink(false)
  1660.    
  1661.     return inputString
  1662. end
  1663.                
  1664. --Handles all input from the overworld
  1665. function handleOverworldInput()
  1666.     while true do
  1667.         local id,key = os.pullEvent("key")
  1668.         local desX = pox
  1669.         local desY = poy
  1670.  
  1671.         if key == keys.left then desX = pox - 1
  1672.         elseif key == keys.right then desX = pox + 1
  1673.         elseif key == keys.up then desY = poy - 1
  1674.         elseif key == keys.down then desY = poy + 1
  1675.         elseif key == keys.i then runInventoryInterface() return
  1676.         elseif key == keys.j then runJournalInterface() return
  1677.         elseif key == keys.space then break
  1678.         elseif key == keys.enter then
  1679.             gameOver = true
  1680.             break
  1681.         end
  1682.    
  1683.         --In the event the player has come upon a town
  1684.         for i=1,#town do
  1685.             if town[i].visible and town[i].xpos == desX - 1 and town[i].ypos == desY - 1 then
  1686.                 activeTown = i
  1687.                 inTown = true
  1688.                 townpx = town[i].startx
  1689.                 townpy = town[i].starty
  1690.            
  1691.                 while #overEventList > 0 do table.remove(overEventList, 1) end
  1692.                 addEvent("Healing...")
  1693.                 charHealth = charMaxHealth
  1694.                 qUpdate = {
  1695.                     type = "move";
  1696.                     town = activeTown;
  1697.                     xpos,ypos = townpx,townpy;
  1698.                 }
  1699.                 qUpdate = checkQuestUpdate(qUpdate)
  1700.                 if qUpdate then displayConfirmDialogue("Quest Update", qUpdate) end
  1701.                 return
  1702.             end
  1703.         end
  1704.         for i=1,#dungeons do
  1705.             if dungeons[i].visible and dungeons[i].xpos == desX - 1 and dungeons[i].ypos == desY - 1 then
  1706.                 activeDungeon = i
  1707.                 inDungeon = true
  1708.                 dunpx = dungeons[i].startx
  1709.                 dunpy = dungeons[i].starty
  1710.                 dunlevel = dungeons[i].startlevel
  1711.                 dunfx = dungeons[i].startfx
  1712.                 dunfy = dungeons[i].startfy
  1713.            
  1714.                 clearEvents()
  1715.                 qUpdate = {
  1716.                     type = "move";
  1717.                     dungeon = activeDungeon;
  1718.                     xpos,ypox,zpos = dunpx,dunpy,dunlevel;
  1719.                 }
  1720.                 qUpdate = checkQuestUpdate(qUpdate)
  1721.                 if qUpdate then displayConfirmDialogue("Quest Update", qUpdate) end
  1722.                 return
  1723.             end
  1724.         end
  1725.         if desX ~= pox or desY ~= poy then
  1726.             --We attack creatures with our chosen weapon by "walking into them", for simplicity's sake
  1727.             for i=1,#overcreatures do
  1728.                 if overcreatures[i].xpos + 1 == desX and overcreatures[i].ypos + 1 == desY then
  1729.                     if charEquipment["swordhand"].value ~= nil then
  1730.                         local damagedone = calculateDamage(charEquipment["swordhand"].value, true)
  1731.                         addEvent("You strike for "..damagedone)
  1732.                         overcreatures[i].hit = overcreatures[i].hit - damagedone
  1733.                         if overcreatures[i].hit <= 0 then
  1734.                             addEvent(overcreatures[i].name.." has been slain")
  1735.                             term.setCursorPos(1,1)
  1736.                             addXP(overcreatures[i].xp)
  1737.                             addEvent("You earned "..overcreatures[i].xp.."XP")
  1738.                             local qUpdate = {
  1739.                                 type = "kill";
  1740.                                 monster = overcreatures[i];
  1741.                             }
  1742.                             table.remove(overcreatures, i)
  1743.                             qUpdate = checkQuestUpdate(qUpdate)
  1744.                             if qUpdate then displayConfirmDialogue("Quest Update", qUpdate) end
  1745.                         end
  1746.                     else
  1747.                         addEvent("You have no weapon!")
  1748.                     end
  1749.                     return
  1750.                 end
  1751.             end
  1752.            
  1753.             if overworld[desY - 1][desX - 1] == " " then
  1754.                 pox, poy = desX, desY
  1755.                 local qUpdate = {
  1756.                     type = "move";
  1757.                     xpos,ypos = pox,poy
  1758.                 }
  1759.                 qUpdate = checkQuestUpdate(qUpdate)
  1760.                 if qUpdate then displayConfirmDialogue("Quest Update", qUpdate) end
  1761.                 break
  1762.             end
  1763.         end
  1764.     end
  1765. end
  1766.  
  1767.                 --[[SECTION: Overworld, Helper]]--
  1768.  
  1769. --Handles state and monster updates from the overworld
  1770. function updateOverworld()
  1771.     for i = 1, #overcreatures do
  1772.         if not overcreatures[i] then
  1773.             table.remove(overcreatures, i)
  1774.             i = i+1
  1775.         else
  1776.    
  1777.         local desx = pox - overcreatures[i].xpos - 1
  1778.         local desy = poy - overcreatures[i].ypos - 1
  1779.        
  1780.         local ndesx = desx/math.abs(desx)
  1781.         local ndesy = desy/math.abs(desy)
  1782.         if desx == 0 then ndesx = 0 end
  1783.         if desy == 0 then ndesy = 0 end
  1784.        
  1785.         if math.abs(desx) + math.abs(desy) == 1 then
  1786.             local damagedone = calculateDamage(overcreatures[i].weapon, false)
  1787.             addEvent(overcreatures[i].name.." strikes for "..damagedone)
  1788.             charHealth = charHealth - damagedone
  1789.             overcreatures[i].chaseturns = 0
  1790.             overcreatures[i].stillturns = 0
  1791.             if charHealth <= 0 then
  1792.                 displayConfirmDialogue("RIP!", "You were slain by a "..overcreatures[i].name.."! Better luck next time")
  1793.                 gameOver = true
  1794.             end
  1795.         else
  1796.             if debugging then
  1797.                 writeToLog("index "..i, " X:", overcreatures[i].xpos, " Y:", overcreatures[i].ypos, " Desx:", ndesx, " Desy", ndesy)
  1798.             end
  1799.             overcreatures[i].chaseturns = overcreatures[i].chaseturns + 1
  1800.            
  1801.             if ndesx ~= 0 and math.abs(desx) - math.abs(desy) >= 0 and overworld[overcreatures[i].ypos][overcreatures[i].xpos + ndesx] == " " then
  1802.                     --We check for other monsters too
  1803.                     local obstructed = false
  1804.                     for j=1,#overcreatures do
  1805.                         local oc = overcreatures[j]
  1806.                         if i ~= j and overcreatures[i].xpos + ndesx == oc.xpos and overcreatures[i].ypos == oc.ypos then
  1807.                             obstructed = true
  1808.                         end
  1809.                     end
  1810.                    
  1811.                     if not obstructed then
  1812.                         overcreatures[i].xpos = overcreatures[i].xpos + ndesx
  1813.                     end
  1814.                 elseif ndesy ~= 0 and overworld[overcreatures[i].ypos][overcreatures[i].xpos + ndesx] == " " then
  1815.                     --We check for other monsters too
  1816.                     local obstructed = false
  1817.                     for j=1,#overcreatures do
  1818.                         local oc = overcreatures[j]
  1819.                         if i ~= j and overcreatures[i].xpos == oc.xpos and overcreatures[i].ypos + ndesy == oc.ypos then
  1820.                             obstructed = true
  1821.                         end
  1822.                     end
  1823.                    
  1824.                     if not obstructed then
  1825.                         overcreatures[i].ypos = overcreatures[i].ypos + ndesy
  1826.                     end
  1827.                 else
  1828.                     overcreatures[i].stillturns = overcreatures[i].stillturns + 1
  1829.                 end
  1830.             end
  1831.        
  1832.             if overcreatures[i].chaseturns > 10 or overcreatures[i].stillturns > 3 then
  1833.                 addEvent(overcreatures[i].name.." gave up")
  1834.                 table.remove(overcreatures, i)
  1835.                 i = i - 1
  1836.             end
  1837.         end
  1838.     end
  1839.    
  1840.     --We've used a fixed value here of 1 in 30 chance
  1841.     if math.random(1,30) == 1 then
  1842.         addCreatureToWorldMap()
  1843.     end
  1844. end
  1845.  
  1846.                 --[[SECTION: Dungeon, Helper]]--
  1847.  
  1848. --Gets the tile at the currently active dungeon at the specified x, y and level
  1849. function dungeonTileAt(x,y,lev)
  1850.     return string.sub(dungeons[activeDungeon].level[lev][y], x, x)
  1851. end
  1852.  
  1853.                 --[[SECTION: Logging, Helper]]--
  1854.  
  1855. --Adds an event to the current event list- over event, town event or dungeon event
  1856. function addEvent(msg)
  1857.     if inTown then
  1858.         if debugging then
  1859.             writeToLog("TownEvent:", msg)
  1860.         end
  1861.         table.insert(townEventList, 1, msg)
  1862.         if #townEventList > 7 then
  1863.             table.remove(townEventList, #townEventList)
  1864.         end
  1865.     elseif inDungeon then
  1866.         if debugging then
  1867.             writeToLog("DungeonEvent:", msg)
  1868.         end
  1869.         table.insert(dungeonEventList, 1, msg)
  1870.         if #dungeonEventList > 9 then
  1871.             table.remove(dungeonEventList, #dungeonEventList)
  1872.         end
  1873.     else
  1874.         if debugging then
  1875.             writeToLog("OverEvent:", msg)
  1876.         end
  1877.         table.insert(overEventList, 1, msg)
  1878.         if #overEventList > 7 then
  1879.             table.remove(overEventList, #overEventList)
  1880.         end
  1881.     end
  1882. end
  1883.  
  1884. --Clears all events the active event log (should be called when changing between modes)
  1885. function clearEvents()
  1886.     if inTown then
  1887.         while #townEventList > 0 do table.remove(townEventList, 1) end
  1888.     elseif inDungeon then
  1889.         while #dungeonEventList > 0 do table.remove(dungeonEventList, 1) end
  1890.     else
  1891.         while #overEventList > 0 do table.remove(overEventList, 1) end
  1892.     end
  1893. end
  1894.  
  1895.                 --[[SECTION: Town, Input]]--
  1896.  
  1897. --Handles key events and all actions that occur in town
  1898. function handleTownInput(key)
  1899.     --Contextually a space bar movement is important- it handles input across town
  1900.     if key == 57 then
  1901.         --Most importantly, it's used for dialog
  1902.         for i=1,#town[activeTown].npc do
  1903.             local currnpc = town[activeTown].npc[i]
  1904.             for d=1,#directions do
  1905.                 if currnpc.xpos + directions[d].x == townpx and currnpc.ypos + directions[d].y == townpy then
  1906.                     converseWith(currnpc)
  1907.                     break
  1908.                 end
  1909.             end
  1910.         end
  1911.         return
  1912.     end
  1913.    
  1914.     if key == keys.i then
  1915.         runInventoryInterface()
  1916.         return
  1917.     end
  1918.    
  1919.     if key==keys.j then
  1920.         runJournalInterface()
  1921.         return
  1922.     end
  1923.  
  1924.     --In all other cases, input handled manages movement.
  1925.     local desX = townpx
  1926.     local desY = townpy
  1927.     local posmove = true
  1928.  
  1929.     if key == 203 then desX = townpx - 1
  1930.     elseif key == 205 then desX = townpx + 1
  1931.     elseif key == 200 then desY = townpy - 1
  1932.     elseif key == 208 then desY = townpy + 1
  1933.     else posmove = false end
  1934.    
  1935.     for i=1,#town[activeTown].npc do
  1936.         if town[activeTown].npc[i].xpos == desX and town[activeTown].npc[i].ypos == desY
  1937.         --Tiny optimizer
  1938.                 or not posmove then
  1939.             posmove = false
  1940.             break
  1941.         end
  1942.     end
  1943.    
  1944.     if posmove and not(desX < 1 or desX > mapw or desY < 1 or desY > maph) and string.sub(town[activeTown].level[desY], desX, desX) == " " then
  1945.         townpx, townpy = desX, desY
  1946.         --We check the quests in the event of a successful move
  1947.         if townpx == town[activeTown].startx and townpy == town[activeTown].starty then
  1948.             inTown = false
  1949.             activeTown = 0
  1950.             while #townEventList > 0 do table.remove(townEventList, 1) end
  1951.             local qUpdate = {
  1952.                 type = "move";
  1953.                 xpos,ypos = pox,poy
  1954.             }
  1955.             qUpdate = checkQuestUpdate(qUpdate)
  1956.             if qUpdate then displayConfirmDialogue("Quest Update", qUpdate) end
  1957.         else
  1958.             local qUpdate = {
  1959.                 type = "move";
  1960.                 town = activeTown;
  1961.                 xpos,ypos = townpx,townpy
  1962.             }
  1963.             qUpdate = checkQuestUpdate(qUpdate)
  1964.             if qUpdate then displayConfirmDialogue("Quest Update", qUpdate) end
  1965.         end
  1966.     end
  1967. end
  1968.    
  1969. --Handles the display and all input for when an NPC performs a dialog. This function may encompass
  1970. --more than one, depending on how complex they get
  1971. --NOTE: This method is now DEPRECATED. "Converse With" is now the updated method.
  1972. function displayDialog(locnpc)
  1973.     local msg = locnpc.dialogue
  1974.     activeDialog = locnpc.name
  1975.    
  1976.     --DEPRECATED- no longer functional
  1977.     --if checkQuestUpdate(false) then return end
  1978.    
  1979.     --If they're a quest giver, they say their piece
  1980.     if locnpc.quest ~= nil then
  1981.         local lquest = quests[locnpc.quest]
  1982.         lquest.activestage = 1
  1983.         msg = lquest.acceptMessage
  1984.         table.insert(activeQuests, lquest)
  1985.         locnpc.quest = nil
  1986.     end
  1987.    
  1988.     --If they're a merchant they you can purchase some items
  1989.     local toTrade = false
  1990.     if not msg then
  1991.         if stocktrade[locnpc.job] then
  1992.             if locnpc.job == "merchant" and #inventory == 0 or ((locnpc.job == "smith" or locnpc.job == "mystic") and
  1993.                 #locnpc.inventory == 0) then
  1994.                 msg = stocksoldout[locnpc.job][#stocksoldout[locnpc.job]]
  1995.             else
  1996.                 toTrade = true
  1997.                 msg = stocktrade[locnpc.job][#stocktrade[locnpc.job]]
  1998.             end
  1999.         else
  2000.             msg = stockmessages[math.random(1, #stockmessages)]
  2001.         end
  2002.     end
  2003.     msg = "\""..msg.."\""
  2004.     local ctitle = locnpc.name.." the "..locnpc.job.." says:"
  2005.    
  2006.     displayConfirmDialogue(ctitle, msg)
  2007.     if toTrade then runTradeInterface(locnpc) end
  2008.     --This needs to be reset (so as not to confuse other quest objectives)
  2009.     activeDialog = nil
  2010. end
  2011.  
  2012. --This is a typical function used to display any message, with a title and dialog. It can be used for NPC
  2013. --dialogues, notifications of quest updates and other things. The title is displayed at the top, the message
  2014. --in the center and the aftermessage (optional, like "type a key to continue") near the bottom.
  2015. function displayConfirmDialogue(ctitle, msg)
  2016.     local dialogoffset = 5
  2017.     --We actually print twice- once to get the lines, second time to print proper. Easier this way.
  2018.     local lines = wprintOffCenter(msg, 6, w - (dialogoffset+2) * 2, dialogoffset + 2)
  2019.    
  2020.     --This cluster of statements prints a nice box with the message the NPC has for the player in it.
  2021.     term.setCursorPos(dialogoffset, 3)
  2022.     term.write(string.rep("*", w - dialogoffset * 2))
  2023.     term.setCursorPos(dialogoffset, 4)
  2024.     term.write("* "..ctitle..string.rep(" ", w - (dialogoffset) * 2 - #ctitle - 3).."*")
  2025.     for i=5,6+lines do
  2026.         term.setCursorPos(dialogoffset, i)
  2027.         term.write("*"..string.rep(" ", w - (dialogoffset) * 2 - 2).."*")
  2028.     end
  2029.     term.setCursorPos(dialogoffset, 6 + lines + 1)
  2030.     term.write(string.rep("*", w - (dialogoffset) * 2))
  2031.     wprintOffCenter(msg, 6, w - (dialogoffset+2) * 2, dialogoffset + 2)
  2032.    
  2033.     --In the event of a message, the player hits anything to continue
  2034.     os.pullEvent("key")
  2035. end
  2036.  
  2037. --This dialogue is much the same as a confirm but offers explicit options rather than requiring manual input.
  2038. --The options are simply fit on the one line and designed to be short (yes/no sort of things). Returns the option
  2039. --chosen. A yes/no option set is default for empty or null lists.
  2040. function displayOptionDialogue(ctitle, msg, options)
  2041.     if not options or type(options) ~= "table" or #options == 0 then
  2042.         options = {"yes", "no"}
  2043.     end
  2044.  
  2045.     local dialogoffset = 5
  2046.     --We actually print twice- once to get the lines, second time to print proper. Easier this way.
  2047.     local lines = wprintOffCenter(msg, 6, w - (dialogoffset+2) * 2, dialogoffset + 2)
  2048.    
  2049.     --This cluster of statements prints a nice box with the message the NPC has for the player in it.
  2050.     term.setCursorPos(dialogoffset, 3)
  2051.     term.write(string.rep("*", w - dialogoffset * 2))
  2052.     term.setCursorPos(dialogoffset, 4)
  2053.     term.write("* "..ctitle..string.rep(" ", w - (dialogoffset) * 2 - #ctitle - 3).."*")
  2054.     for i=5,8+lines do
  2055.         term.setCursorPos(dialogoffset, i)
  2056.         term.write("*"..string.rep(" ", w - (dialogoffset) * 2 - 2).."*")
  2057.     end
  2058.     term.setCursorPos(dialogoffset, 8 + lines + 1)
  2059.     term.write(string.rep("*", w - (dialogoffset) * 2))
  2060.     wprintOffCenter(msg, 6, w - (dialogoffset+2) * 2, dialogoffset + 2)
  2061.    
  2062.    
  2063.     --Next we display our options- this is done regularly
  2064.     local selection = 1
  2065.     while true do
  2066.         local optoffset = 0
  2067.         for i=1,#options do
  2068.             term.setCursorPos(dialogoffset + optoffset + 1, 8 + lines)
  2069.             if selection==i then
  2070.                 term.write("["..options[i].."]")
  2071.             else
  2072.                 term.write(" "..options[i].." ")
  2073.             end
  2074.             optoffset = optoffset + 3 + #options[i]
  2075.         end
  2076.        
  2077.         local _,key = os.pullEvent("key")
  2078.         if key == keys.left and selection > 1 then selection = selection-1
  2079.         elseif key == keys.right and selection < #options then selection = selection+1
  2080.         elseif key == keys.enter then return options[selection] end
  2081.     end
  2082. end
  2083.  
  2084. --This displays an input dialogue- it's essentially identical to the "displayConfirmDialogue" but rather than simply
  2085. --displaying a message and an keypress ending it, instead it requests a text input to terminate. This returns the
  2086. --input the user has given.
  2087. function displayInputDialogue(ctitle, msg, prompt)
  2088.     if not prompt then prompt = ">" end
  2089.  
  2090.     local dialogoffset = 5
  2091.     --We actually print twice- once to get the lines, second time to print proper. Easier this way.
  2092.     local lines = wprintOffCenter(msg, 6, w - (dialogoffset+2) * 2, dialogoffset + 2)
  2093.    
  2094.     --This cluster of statements prints a nice box with the message the NPC has for the player in it.
  2095.     term.setCursorPos(dialogoffset, 3)
  2096.     term.write(string.rep("*", w - dialogoffset * 2))
  2097.     term.setCursorPos(dialogoffset, 4)
  2098.     term.write("* "..ctitle..string.rep(" ", w - (dialogoffset) * 2 - #ctitle - 3).."*")
  2099.     for i=5,8+lines do
  2100.         term.setCursorPos(dialogoffset, i)
  2101.         term.write("*"..string.rep(" ", w - (dialogoffset) * 2 - 2).."*")
  2102.     end
  2103.     term.setCursorPos(dialogoffset, 8 + lines + 1)
  2104.     term.write(string.rep("*", w - (dialogoffset) * 2))
  2105.     wprintOffCenter(msg, 6, w - (dialogoffset+2) * 2, dialogoffset + 2)
  2106.    
  2107.     term.setCursorPos(dialogoffset + 1, 8 + lines)
  2108.     term.write(" "..prompt)
  2109.    
  2110.     --We get our input here
  2111.     local input = readInput(#prompt + (w - (dialogoffset+2) * 2 + 1))
  2112.    
  2113.     return input
  2114. end
  2115.  
  2116. --This allows the user to have a conversation with an NPC
  2117. function converseWith(locnpc)
  2118.     local input = ""
  2119.     if locnpc.greeted then
  2120.         input = "ongreet"
  2121.     else
  2122.         locnpc.greeted = true
  2123.         input = "onfirstgreet"
  2124.     end
  2125.    
  2126.     local dialogue = nil
  2127.     while true do
  2128.         local qUpdate = {
  2129.             type = "dialogue";
  2130.             town = activeTown;
  2131.             npc = locnpc;
  2132.             topic = input;
  2133.         }
  2134.         local qUpdate = checkQuestUpdate(qUpdate)
  2135.         dialogue = qUpdate
  2136.        
  2137.         if not dialogue then
  2138.             dialogue = locnpc.dialogue["#"..input]
  2139.         end
  2140.         if not dialogue and locnpc.useGeneral and inTown then
  2141.             dialogue = town[activeTown].dialogue["#"..input]
  2142.         end
  2143.         if not dialogue then
  2144.             dialogue = globalDialogue["#"..input]
  2145.         end
  2146.         if not dialogue then
  2147.             dialogue = locnpc.nodialogue
  2148.         end
  2149.         --This really, should never happen. Be careful with your scripting guys.
  2150.         if not dialogue then dialogue = "I don't have an answer to your question." end
  2151.        
  2152.         if input=="onfarewell" then break end
  2153.        
  2154.         if type(dialogue) == "function" then
  2155.             dialogue = dialogue(locnpc)
  2156.             if not dialogue then break end
  2157.         end
  2158.        
  2159.         if type(dialogue) == "table" then
  2160.             local locd = nil
  2161.             for i=1,#dialogue-1 do
  2162.                 locd = replaceDialogueTags(dialogue[i])
  2163.                 if inTown then printTownInterface() end
  2164.                 displayConfirmDialogue(locnpc.name.." the "..locnpc.job.." says:", dialogue)
  2165.             end
  2166.             locd = replaceDialogueTags(dialogue[#dialogue])
  2167.             if inTown then printTownInterface() end
  2168.             input = string.lower(displayInputDialogue(locnpc.name.." the "..locnpc.job.." says:", dialogue, "Ask>"))
  2169.            
  2170.         elseif string.sub(dialogue, 1, 1) == "#" then input = string.sub(dialogue, 2, #dialogue)
  2171.         else
  2172.             printTownInterface()
  2173.             if inTown then dialogue = replaceDialogueTags(dialogue) end
  2174.             input = string.lower(displayInputDialogue(locnpc.name.." the "..locnpc.job.." says:", dialogue, "Ask>"))
  2175.         end
  2176.     end
  2177.    
  2178.     if inTown then printTownInterface() end
  2179.     if dialogue then
  2180.         dialogue = replaceDialogueTags(dialogue)
  2181.         displayConfirmDialogue(locnpc.name.." the "..locnpc.job.." says:", dialogue)
  2182.     end
  2183. end
  2184.  
  2185. --Takes the tags from a conversation and replaces them with user attributes
  2186. function replaceDialogueTags(dialogue)
  2187.     local findex = string.find(dialogue, "#name")
  2188.     while findex do
  2189.         dialogue = string.sub(dialogue, 1, findex-1)..getCharName()..string.sub(dialogue, findex+5, #dialogue)
  2190.         findex = string.find(dialogue, "#name")
  2191.     end
  2192.     findex = string.find(dialogue, "#class")
  2193.     while findex do
  2194.         dialogue = string.sub(dialogue, 1, findex-1)..charClass..string.sub(dialogue, findex+6, #dialogue)
  2195.         findex = string.find(dialogue, "#class")
  2196.     end
  2197.    
  2198.     return dialogue
  2199. end
  2200.  
  2201.                 --[[SECTION: Dungeon, Input]]--
  2202.  
  2203. --Replaces a tile in a dungeon with a new tile- used for removing chests, secret doors, etc.
  2204. function subDungeonTile(index, x, y, level, char)
  2205.     dungeons[index].level[level][y] = string.sub(dungeons[index].level[level][y], 1, x - 1)..char..
  2206.         string.sub(dungeons[index].level[level][y], x + 1, #dungeons[index].level[level][y])
  2207. end
  2208.                
  2209. --This moves the player according to the key specified
  2210. function handleDungeonInput(key)
  2211.     local currtile = string.sub(dungeons[activeDungeon].level[dunlevel][dunpy], dunpx, dunpx)
  2212.    
  2213.     if key==keys.m then
  2214.         if dungeons[activeDungeon].playerHasMap then
  2215.             drawDungeonMap()
  2216.             os.pullEvent("key")
  2217.         else
  2218.             addEvent("You don't have a dungeon map!")
  2219.         end
  2220.     end
  2221.    
  2222.     if key==keys.i then
  2223.         runInventoryInterface()
  2224.     end
  2225.    
  2226.     if key==keys.j then
  2227.         runJournalInterface()
  2228.     end
  2229.    
  2230.     if key==keys.up and string.sub(dungeons[activeDungeon].level[dunlevel][dunpy + dunfy], dunpx + dunfx, dunpx + dunfx) ~= " " then
  2231.         --If a creature occupies the desired space, combat occurs
  2232.         for i = 1, #dungeons[activeDungeon].creatures do
  2233.             local c = dungeons[activeDungeon].creatures[i]
  2234.             if c.xpos == dunpx + dunfx and c.ypos == dunpy + dunfy and c.levelpos == dunlevel then
  2235.                 if charEquipment["swordhand"].value ~= nil then
  2236.                     local damagedone = calculateDamage(charEquipment["swordhand"].value, true)
  2237.                     addEvent("You strike for "..damagedone)
  2238.                     c.hit = c.hit - damagedone
  2239.                     if c.hit <= 0 then
  2240.                         addEvent(c.name.." has been slain!")
  2241.                         addXP(c.xp)
  2242.                         addEvent("You earned "..c.xp.."XP")
  2243.                         local qUpdate = {
  2244.                             type = "kill";
  2245.                             dungeon = activeDungeon;
  2246.                             monster = dungeons[activeDungeon].creatures[i]
  2247.                         }
  2248.                         qUpdate = checkQuestUpdate(qUpdate)
  2249.                         if qUpdate then displayConfirmDialogue("Quest Update:", qUpdate) end
  2250.                         table.remove(dungeons[activeDungeon].creatures, i)
  2251.                     end
  2252.                 else
  2253.                     addEvent("You have no weapon!")
  2254.                 end
  2255.                 return
  2256.             end
  2257.         end
  2258.        
  2259.         --Otherwise, the player simply moves forward (how much easier is that!)
  2260.         dunpx = dunpx + dunfx
  2261.         dunpy = dunpy + dunfy
  2262.         --Quest updates are checked on movement (direction doesn't matter)
  2263.         local qUpdate = {
  2264.             type = "move";
  2265.             dungeon = activeDungeon;
  2266.             xpos = dunpx;
  2267.             ypos = dunpy;
  2268.             zpos = dunlevel;
  2269.         }
  2270.         qUpdate = checkQuestUpdate(qUpdate)
  2271.         if qUpdate then displayConfirmDialogue("Quest Update: ", qUpdate) end
  2272.     elseif key==keys.left then
  2273.         dunfx, dunfy = getLeft(dunfx, dunfy)
  2274.     elseif key==keys.right then
  2275.         dunfx, dunfy = getRight(dunfx, dunfy)
  2276.     elseif key==keys.down then
  2277.         dunfx = dunfx * -1
  2278.         dunfy = dunfy * -1
  2279.     elseif key==keys.pageUp and (currtile == "U" or currtile == "B") then
  2280.         dunlevel = dunlevel - 1
  2281.         local qUpdate = {
  2282.             type = "move";
  2283.             dungeon = activeDungeon;
  2284.             xpos = dunpx;
  2285.             ypos = dunpy;
  2286.             zpos = dunlevel;
  2287.         }
  2288.         qUpdate = checkQuestUpdate(qUpdate)
  2289.         if qUpdate then displayConfirmDialogue("Quest Update: ", qUpdate) end
  2290.     elseif key==keys.pageDown and (currtile == "D" or currtile == "B") then
  2291.         dunlevel = dunlevel + 1
  2292.         local qUpdate = {
  2293.             type = "move";
  2294.             dungeon = activeDungeon;
  2295.             xpos = dunpx;
  2296.             ypos = dunpy;
  2297.             zpos = dunlevel;
  2298.         }
  2299.         qUpdate = checkQuestUpdate(qUpdate)
  2300.         if qUpdate then displayConfirmDialogue("Quest Update: ", qUpdate) end
  2301.        
  2302.     --The player opens a chest
  2303.     elseif key==keys.space and currtile == "C" then
  2304.         subDungeonTile(activeDungeon, dunpx, dunpy, dunlevel, "O")
  2305.        
  2306.         --It is possible to have scripted loot, given by a quest...
  2307.         local qUpdate = {
  2308.             type = "chest";
  2309.             dungeon = activeDungeon;
  2310.             xpos = dunpx;
  2311.             ypos = dunpy;
  2312.             zpos = dunlevel;
  2313.         }
  2314.         qUpdate = checkQuestUpdate(qUpdate)
  2315.         if qUpdate then
  2316.             displayConfirmDialogue("Quest Update: ", qUpdate)
  2317.         else
  2318.             --Or scripted loot given by the dungeon
  2319.             local cscriptFound = false
  2320.             for i,v in ipairs(dungeons[activeDungeon].chests) do
  2321.                 if v.xpos == dunpx and v.ypos == dunpy and v.zpos == dunlevel then
  2322.                     cscriptFound = true
  2323.                     if type(v.content == "string") then
  2324.                         if v.content == "item" then
  2325.                             local newItem = generateLoot(charLevel)
  2326.                             table.insert(inventory, newItem)
  2327.                             addEvent("Chest contained a "..newItem.name)
  2328.                         elseif v.content == "map" then
  2329.                             dungeons[activeDungeon].playerHasMap = true
  2330.                             addEvent("Chest contained a dungeon map!")
  2331.                         elseif v.content == "compass" then
  2332.                             dungeons[activeDungeon].playerHasCompass = true
  2333.                             addEvent("Chest contained a compass!")
  2334.                         else
  2335.                             cscriptFound = false
  2336.                         end
  2337.                     elseif type(v.content == "table") then
  2338.                         table.insert(inventory, v.content)
  2339.                         addEvent("Chest contained a "..v.content.name)
  2340.                     else
  2341.                         charGold = charGold + v.content
  2342.                         addEvent("Chest contained "..v.content.." gold")
  2343.                     end
  2344.                     break
  2345.                 end
  2346.             end
  2347.            
  2348.             --Or just some random amount of gold.
  2349.             if not cscriptFound then
  2350.                 local basegold = math.pow(2, charLevel + 1)
  2351.                 basegold = math.random(basegold - basegold/2, basegold + basegold/2)
  2352.                 addEvent("Chest contained "..basegold.." gold!")
  2353.                 charGold = charGold + basegold
  2354.             end
  2355.         end
  2356.     end
  2357.    
  2358.     if string.sub(dungeons[activeDungeon].level[dunlevel][dunpy], dunpx, dunpx) == "E" then
  2359.         inDungeon = false
  2360.         activeDungeon = 0
  2361.         while #dungeonEventList > 0 do table.remove(dungeonEventList, 1) end
  2362.         local qUpdate = {
  2363.             type = "move";
  2364.             xpos = pox;
  2365.             ypos = poy;
  2366.         }
  2367.         qUpdate = checkQuestUpdate(qUpdate)
  2368.         if qUpdate then displayConfirmDialogue(qUpdate) end
  2369.     end
  2370. end
  2371.  
  2372. --This updates the dungeon
  2373. function updateDungeon()
  2374.     --This moves, and if necessary, causes creaturse to attack
  2375.     for i=1,#dungeons[activeDungeon].creatures do
  2376.         local c = dungeons[activeDungeon].creatures[i]
  2377.        
  2378.         --Ensuring the creature is close enough to need to act- just out of viewing distance, but this can be changed later
  2379.         --(maybe make it 3 or 4 for a more difficult game?
  2380.         if c.levelpos == dunlevel and math.abs(c.xpos - dunpx) + math.abs(c.ypos - dunpy) <= 3 then
  2381.             local desx = dunpx - c.xpos
  2382.             local desy = dunpy - c.ypos
  2383.            
  2384.             local ndesx = desx/math.abs(desx)
  2385.             local ndesy = desy/math.abs(desy)
  2386.             if desx == 0 then ndesx = 0 end
  2387.             if desy == 0 then ndesy = 0 end
  2388.            
  2389.             if math.abs(desx) + math.abs(desy) == 1 then
  2390.                 local toBlock = math.random(1,100)
  2391.                 if charEquipment["offhand"] and charEquipment["offhand"].class == "shield" and toBlock <=
  2392.                         charEquipment["offhand"].base * 100 then
  2393.                     addEvent("You block strike by "..c.name.."!")
  2394.                 else
  2395.                     local damagedone = calculateDamage(c.weapon, false)
  2396.                     addEvent(c.name.." strikes for "..damagedone)
  2397.                     charHealth = charHealth - damagedone
  2398.                     if charHealth <= 0 then
  2399.                         displayConfirmDialogue("RIP!", "You were slain by a "..c.name.."! Better luck next time")
  2400.                         gameOver = true
  2401.                     end
  2402.                 end
  2403.             else
  2404.                 --It should be noted monsters can't follow you up and down ladders (for obvious reasons!)
  2405.                 if ndesx ~= 0 and math.abs(desx) - math.abs(desy) >= 0 and dungeonTileAt(c.xpos + ndesx, c.ypos, c.levelpos) ~= " " then
  2406.                     --We check for other monsters too
  2407.                     local obstructed = false
  2408.                     for j=1,#dungeons[activeDungeon].creatures do
  2409.                         local oc = dungeons[activeDungeon].creatures[j]
  2410.                         if i ~= j and c.xpos + ndesx == oc.xpos and c.ypos == oc.ypos and c.levelpos == oc.levelpos then
  2411.                             obstructed = true
  2412.                         end
  2413.                     end
  2414.                    
  2415.                     if not obstructed then
  2416.                         c.xpos = c.xpos + ndesx
  2417.                     end
  2418.                 elseif ndesy ~= 0 and dungeonTileAt(c.xpos, c.ypos + ndesy, c.levelpos) ~= " " then
  2419.                     --We check for other monsters too
  2420.                     local obstructed = false
  2421.                     for j=1,#dungeons[activeDungeon].creatures do
  2422.                         local oc = dungeons[activeDungeon].creatures[j]
  2423.                         if i ~= j and c.xpos == oc.xpos and c.ypos + ndesy == oc.ypos and c.levelpos == oc.levelpos then
  2424.                             obstructed = true
  2425.                         end
  2426.                     end
  2427.                    
  2428.                     if not obstructed then
  2429.                         c.ypos = c.ypos + ndesy
  2430.                     end
  2431.                 end
  2432.             end
  2433.         end
  2434.     end
  2435.    
  2436.     --Decides whether or not to add a creature to the dungeon
  2437.     if dungeons[activeDungeon].creaturesSpawn and math.random(1,15) == 1 then
  2438.         addCreatureToDungeon()
  2439.     end
  2440. end
  2441.    
  2442.                 --[[SECTION: Parser, Helper]]--
  2443.  
  2444. --Reads in a file, the town, and uses it to produce each town map in the game (NPC's etc. are handled in the script)
  2445. function parseTowns()
  2446.     local file = io.open(shell.resolve(".").."/scripts/towns", "r")
  2447.     if not file then error("\'"..towns.."\' file not found.") end
  2448.     local flines = { }
  2449.     flines [1] = file:read()
  2450.     while true do
  2451.         local towntitle = ""
  2452.         towntitle, flines[#flines] = readFromQuoteMarks(flines[#flines])
  2453.         if not towntitle then
  2454.             print("Malformed string, breaking")
  2455.             break
  2456.         end
  2457.         local coords = split(flines[#flines], ",")
  2458.         local townlevel = { }
  2459.         for i=1,16 do
  2460.             local townline = file:read()
  2461.             if #townline > mapw then
  2462.                 townline = string.sub(townline, 1, mapw)
  2463.             elseif #townline < mapw then
  2464.                 townline = townline..string.rep(" ", mapw - #townline)
  2465.             end
  2466.             table.insert(townlevel, townline)
  2467.         end
  2468.         table.insert(town, {
  2469.             name = towntitle,
  2470.             xpos = tonumber(coords[1]),
  2471.             ypos = tonumber(coords[2]),
  2472.             startx = tonumber(coords[3]),
  2473.             starty = tonumber(coords[4]),
  2474.             visible = true,
  2475.             level = townlevel,
  2476.             npc = { }
  2477.         })
  2478.         print("Created town "..towntitle.." ("..tonumber(coords[1])..","..tonumber(coords[2])..")")
  2479.         flines[#flines + 1] = file:read()
  2480.         if not (flines[#flines] and string.find(flines[#flines], "TOWN") == 1) then break end
  2481.     end
  2482.     file:close()
  2483. end
  2484.  
  2485. --This returns two things- the word in quotation marks, and the rest of the string
  2486. function readFromQuoteMarks(quotestr)
  2487.     local firstQuote = quotestr:find("\"")
  2488.     if not firstQuote then return nil end
  2489.     local lastQuote = quotestr:find("\"", firstQuote + 1)
  2490.     if not lastQuote then return nil end
  2491.     if not firstQuote or not lastQuote then error("Malformed quotation marks!") end
  2492.    
  2493.     return string.sub(quotestr, firstQuote + 1, lastQuote - 1), string.sub(quotestr, lastQuote + 1)
  2494. end
  2495.  
  2496. --Splits a string according to a pattern into a table              
  2497. function split(str, pattern)
  2498.   local t = { }
  2499.   local fpat = "(.-)" .. pattern
  2500.   local last_end = 1
  2501.   local s, e, cap = str:find(fpat, 1)
  2502.   while s do
  2503.     if s ~= 1 or cap ~= "" then
  2504.       table.insert(t,cap)
  2505.     end
  2506.     last_end = e+1
  2507.     s, e, cap = str:find(fpat, last_end)
  2508.   end
  2509.   if last_end <= #str then
  2510.     cap = str:sub(last_end)
  2511.     table.insert(t, cap)
  2512.   end
  2513.   return t
  2514. end
  2515.  
  2516. --Finds if one of a series of words can be found in a given string
  2517. function findIn(...)
  2518.     local str = arg[1]
  2519.    
  2520.     for i=2,#arg do
  2521.         if string.find(str, arg[i]) then return true end
  2522.     end
  2523.     return false
  2524. end
  2525.  
  2526.                 --[[SECTION: Logging]]--
  2527.  
  2528. --This clears the log file.
  2529. function clearLog()
  2530.   local file = io.open(shell.resolve(".").."/log", "w")
  2531.   file:write(os.time()..": Log opened.\n")
  2532.   file:close()
  2533. end
  2534.  
  2535. --This writes debug info to the log file
  2536. function writeToLog(...)
  2537.   local file = io.open(shell.resolve(".").."/log", "a")
  2538.   file:write(os.time()..": ")
  2539.   for k,v in pairs(arg) do
  2540.     if not v then file:write("<nil> ")
  2541.     else file:write(v, " ") end
  2542.   end
  2543.   file:write("\n")
  2544.   file:close()
  2545. end
  2546.  
  2547. --Runs all the scripts
  2548. function executeScripts()
  2549.     local scripts = { "script", "equipment", "creatures", "quest", "npc", "dungeons" }
  2550.  
  2551.     local scriptSuccessful = true
  2552.  
  2553.     for _,v in pairs(scripts) do
  2554.         print("Running "..v.." script...")
  2555.         if not fs.exists(shell.resolve(".").."/scripts/"..v) then error("Cannot find "..v.." file!") return false end
  2556.         if not shell.run("scripts/"..v) then
  2557.             print(v.." failed to execute")
  2558.             scriptSuccessful = false
  2559.             break
  2560.         end
  2561.     end
  2562.  
  2563.     return scriptSuccessful
  2564. end
  2565.  
  2566.                 --[[SECTION: Main]]--
  2567.  
  2568. --The main function- prepares the game to play and manages game states
  2569. function main()
  2570.     shell.run("clear")
  2571.  
  2572.     readOverWorld()
  2573.     parseTowns()
  2574.     if not executeScripts() then error("Error in script!") return end
  2575.     refreshNPCInventory()
  2576.    
  2577.     print("Initializing game...")
  2578.     sleep(1)
  2579.     onInit()
  2580.     clearLog()
  2581.     if debugging then writeToLog("Game Started") end
  2582.    
  2583.     for i = 1, #dungeons do bufferLevel(i) end
  2584.    
  2585.     while not gameOver do
  2586.         --In the event the player is in a town
  2587.         if inTown then
  2588.             printTownInterface()
  2589.             local id, key = os.pullEvent("key")
  2590.             if key == 28 then break
  2591.             else handleTownInput(key) end
  2592.         --In the event the player is in a dungeon
  2593.         elseif inDungeon then
  2594.             term.clear()
  2595.             drawDungeonCorridor()
  2596.             drawDungeonInterface()
  2597.             local id,key = os.pullEvent("key")
  2598.             if key == 28 then break
  2599.             else handleDungeonInput(key) end
  2600.             if inDungeon then updateDungeon() end
  2601.         else
  2602.         --In the event the player is in the world map
  2603.             printOverworldInterface()
  2604.             printWorldMap()
  2605.             handleOverworldInput()
  2606.             if not inDungeon and not inTown then updateOverworld() end
  2607.         end
  2608.     end
  2609.     shell.run("clear")
  2610.     print("Thanks for playing!")
  2611.    
  2612.     if debugging then writeToLog("Game terminated successfully.") end
  2613. end
  2614.  
  2615. main()
  2616. MKDIR rpg/sacalin
  2617. MKFIL rpg/sacalin/script
  2618. WRITE 1076
  2619. --[[
  2620.         Darklands: Tales from Transylvania
  2621.        
  2622.         --Fighter
  2623.         You are still but a young squire, consripted into the service under the command of Lord Klint, a
  2624.         Moldavian defector and an arrogant, cruel man. When he was not slaughtering men from whichever
  2625.         side appeared most likely to succeed, he was punishing his servants with the lash with almost
  2626.         sadistic pleasure.
  2627.        
  2628.         When first the call came for a scout amongst the ranks, embittered by your evil master, you rushed
  2629.         at all speed from your quarters to volunteer. There's no evil below that could stand toe to toe
  2630.         with what you have suffered through, and you are immediately conscripted.
  2631.        
  2632.         --Theif
  2633.         Profit always follows war, or so it's said. The life of an urchin in peace time is difficult and
  2634.         wearisome, but in war, where corpses lie where they fall, along with their purses and satchels,
  2635.         a young man can make good of himself, if only for a spell. So you have spent your youth, hiding
  2636.         in shadows and picking from the bones of conflits between those greater than yourself.
  2637.        
  2638.         So you had hoped would be the case with this latest invasion, but after weeks of hard travel shadowing
  2639.         the invasion force not so much as a drop of blood had been spilled. Starving and desparate, you
  2640.         made an attempt on a supply trailer, but the soldiers were too quick, and the rest of the journey
  2641.         you spent in chains. When the call came out for a scouting mission almost guaranteed to be suicide,
  2642.         it was no surprise to see you finding an audience with the general himself.
  2643.        
  2644.         --Mage
  2645.         Just six months from your training, barely a whelp, your magistrates saw fit to send you to the
  2646.         Carpathians in the hopes you would set up a herbarium. Though the first few quiet, contemplative
  2647.         weeks are relaxing, you soon find yourself venturing farther and father into the reaches of the
  2648.         mountains, to break the monotony.
  2649.        
  2650.         It is on one of your longer journeys that you come across the soldiers, camping above the ridge in
  2651.         an abandoned keep. As a member of the mystics you extend your authority to demand a meeting with the
  2652.         general and order him to hault his invasion- but he has other things in mind.
  2653. ]]--
  2654.  
  2655. --Note: throughout the script several "print" statements have been scattered to demonstrate progress. These are nonessential, but useful for error detection.
  2656.  
  2657. --A simple single town, with a single quest giver and two storefronts, as well as one random NPC.
  2658. --This demonstrates the primary features of writing a town script, and a simple dungeon with quest queues.
  2659.  
  2660.         --[[SECTION: Initialization]]--
  2661.  
  2662. function debugDisplay(str)
  2663.     term.setCursorPos(1,1)
  2664.     print("Debug: "..str)
  2665.     os.pullEvent("key")
  2666. end
  2667.  
  2668. --Quick function that allows the user to choose his class, which is then assigned equipment, stats & gold
  2669. function selectClass()
  2670.     term.clear()
  2671.    
  2672.     local str = "From what vocation do you hail, "..charName.."?"
  2673.     term.setCursorPos(w/2 - #str/2, 3)
  2674.     term.write(str)
  2675.    
  2676.     list = {"Squire", "Urchin", "Journeyman"}
  2677.     local sel = 1
  2678.    
  2679.     while true do
  2680.         for i=1,#list do
  2681.             term.setCursorPos(w/2 - #list[i]/2 - 2, 3 + i * 3)
  2682.             term.clearLine()
  2683.             if sel == i then term.write("[ "..list[i].." ]")
  2684.             else term.write("  "..list[i].."  ") end
  2685.         end
  2686.        
  2687.         local id,key = os.pullEvent("key")
  2688.         if key == keys.up and sel > 1 then sel = sel - 1
  2689.         elseif key == keys.down and sel < #list then sel = sel + 1
  2690.         elseif key == keys.space or key == keys.enter then
  2691.             charClass = list[sel]
  2692.             break
  2693.         end
  2694.     end
  2695.    
  2696.     --All players start with rough robes and sandals
  2697.     local robes = cloneItem(6)
  2698.     robes.enchantment = cloneEnchantment("rough")
  2699.     local sandals = cloneItem(7)
  2700.     table.insert(inventory, robes)
  2701.     table.insert(inventory, sandals)
  2702.    
  2703.     if sel == 1 then
  2704.         --Squires are peniless but well equipped by their master
  2705.         table.insert(inventory, cloneItem(3))
  2706.         table.insert(inventory, cloneItem(5))
  2707.         charGold = 0
  2708.     elseif sel == 2 then
  2709.         --Urchins have a good weapon but little coin to their name
  2710.         local dagger = cloneItem(1)
  2711.         dagger.enchantment = cloneEnchantment("sharp")
  2712.         table.insert(inventory, dagger)
  2713.         charGold = 5
  2714.     elseif sel == 3 then
  2715.         --Journeymen have pathetic weapons but much more money
  2716.         local staff = cloneItem(2)
  2717.         staff.enchantment = cloneEnchantment("rotting")
  2718.         table.insert(inventory, staff)
  2719.         charGold = 30
  2720.     end
  2721.    
  2722.     for i=1,#inventory do
  2723.         equipItem(i)
  2724.     end
  2725. end
  2726.        
  2727. function onDebugInit()
  2728.     shell.run("clear")
  2729.     print("Type a key to begin debug session")
  2730.    
  2731.     table.insert(inventory, cloneItem(6))
  2732.     table.insert(inventory, cloneItem(7))
  2733.     table.insert(inventory, cloneItem(2))
  2734.     charName = "Debug"
  2735.    
  2736.     for i=1,#inventory do
  2737.         equipItem(i)
  2738.     end
  2739.    
  2740.     charGold = 100
  2741.    
  2742.     os.pullEvent("key")
  2743. end
  2744.        
  2745. function onInit()
  2746.     local darkSword = {
  2747.         "   /\\",
  2748.         "   ||",
  2749.         "   ||",
  2750.         "   ||",
  2751.         "   ||",
  2752.         "   ||",
  2753.         "   /\\",
  2754.         "o======o",
  2755.         "   ||",
  2756.         "   ||",
  2757.         "   __"
  2758.     }
  2759.    
  2760.     term.clear()
  2761.     sleep(1)
  2762.    
  2763.     for i=1,#darkSword do
  2764.         printLeft(darkSword[i], 2+i, w/2 - #darkSword[8]/2 - 1)
  2765.     end
  2766.    
  2767.     sleep(1.5)
  2768.     term.setCursorPos(w/2 - 9/2, h-4)
  2769.     textutils.slowPrint("DarkLands")
  2770.     sleep(1)
  2771.     printOffCenter("Tales from Transylvania", h-3, w, 0)
  2772.     printOffCenter("Press a key to begin", h-1, w, 0)
  2773.     os.pullEvent(key)
  2774.    
  2775.     term.clearLine()
  2776.    
  2777.     printLeft("Enter your name, hero: ", h-1, 2, 0)
  2778.     term.setCursorBlink(true)
  2779.     setCharName(io.read())
  2780.     term.setCursorBlink(false)
  2781.    
  2782.     --The generic story preface is told here.
  2783.    
  2784.     term.clear()
  2785.     sleep(1)
  2786.     term.setCursorPos(1, 1)
  2787.     print("It is 1430.\n")
  2788.     print(
  2789.         "Seeking to relieve the strain from a lengthy war with Moldava and widespread famines in the "..
  2790.         "south, the king of Wallachia ordered an invasion on Transylvania. The warlords of Transylvania, often "..
  2791.         "as busy fighting each other as foreign invaders are considered by His Grace a means to relieve the "..
  2792.         "strain on the armed forces, and restore faith in the monarchy.\n")
  2793.     print(
  2794.         "In November of that month, an army of 300 men assembled at the base "..
  2795.         "of the Carpathian mountain ranges, one of the most trecherous stretches of land in Europe. Two weeks "..
  2796.         "of bitter cold and snowstorms, with limited rations and low morale, they finally came upon a keep "..
  2797.         "within the mountains.\n")
  2798.     term.setCursorPos(1, h)
  2799.     term.write("Type any key to continue")
  2800.     os.pullEvent("key")
  2801.    
  2802.     term.clear()
  2803.     term.setCursorPos(1, 2)
  2804.     print(
  2805.         "The abandoned keep, Groazagol, afforded a view over the southern reaches of the country, but the land, in day "..
  2806.         "and night was shrouded in an impenetrable darkness. It was not long before rumours spread through "..
  2807.         "the garrison, of evil sweeping the country side- the black death, horsemen of the apocalypse, or "..
  2808.         "some other sinister force of evil.\n")
  2809.     print(
  2810.         "The commander general of the Wallachian forces Lord Jeirbe, fearing his men would mutiny or flee "..
  2811.         "at an order to trave into the abyss, called for a single scout to map the underlying country, "..
  2812.         "provide insight into what was happening, and inform the army on their next move.\n")
  2813.     term.setCursorPos(1, h)
  2814.     term.write("Type any key to continue")
  2815.     os.pullEvent("key")
  2816.    
  2817.     --The player selects their class here:
  2818.    
  2819.     selectClass()
  2820.    
  2821.     pox = 29
  2822.     poy = 24
  2823. end
  2824.  
  2825. --Comment out this line as needed.
  2826. --onInit = onDebugInit
  2827.  
  2828.         --[[SECTION: Images]]--
  2829.        
  2830.  
  2831.  
  2832.         --[[SECTION: Items]]--
  2833.  
  2834. print("Loading items...")      
  2835.  
  2836. --Note: Stores use these (not creatures) so be sure not to include creature weapons in this list- just
  2837. --create those within the creature's insert page.
  2838.  
  2839. table.insert(items, { name = "Copper Dagger",
  2840.     class = "weapon",
  2841.     desc = "A copper dagger, weathered and nicked from age.",
  2842.     bodypart = "swordhand",
  2843.     level = 1,
  2844.     requirements = { },
  2845.     base = 6,
  2846.     enchantment = nil,
  2847.     enchantTypes = {"#blade", "#metal", "#aged"},
  2848.     value = 5,
  2849.     weight = 5
  2850. })
  2851. table.insert(items, { name = "Wooden Staff",
  2852.     class = "weapon",
  2853.     desc = "A solid but soft yew staff, made from a fallen branch.",
  2854.     bodypart = "both",
  2855.     level = 1,
  2856.     requirements = { },
  2857.     base = 8,
  2858.     enchantment = nil,
  2859.     enchantTypes = {"#wood", "#aged"},
  2860.     value = 2,
  2861.     weight = 8
  2862. })
  2863. table.insert(items, { name = "Iron Gladius",
  2864.     class = "weapon",
  2865.     desc = "Shortsword of antiquity.",
  2866.     bodypart = "swordhand",
  2867.     level = 3,
  2868.     requirements = { },
  2869.     base = 8,
  2870.     enchantment = nil,
  2871.     enchantTypes = {"#iron", "#blade", "#aged"},
  2872.     value = 15,
  2873.     weight = 6
  2874. })
  2875. table.insert(items, { name = "Quarterstaff",
  2876.     class = "weapon",
  2877.     desc = "An oaken staff, without ornament.",
  2878.     bodypart = "both",
  2879.     level = 3,
  2880.     requirements = { },
  2881.     base = 11,
  2882.     enchantment = nil,
  2883.     enchantTypes = {"#wood"},
  2884.     value = 12,
  2885.     weight = 9
  2886. })
  2887. table.insert(items, { name = "Practice Buckler",
  2888.     class = "shield",
  2889.     desc = "A fist-sized shield made from wooden boards, repurposed from sword training.",
  2890.     bodypart = "offhand",
  2891.     level = 1,
  2892.     requirements = { },
  2893.     base = 0.5,
  2894.     enchantment = nil,
  2895.     enchantTypes = {"#wood", "#aged"},
  2896.     value = 8,
  2897.     weight = 4
  2898. })
  2899. table.insert(items, { name = "Hessian Robe",
  2900.     class = "armour",
  2901.     desc = "Old hessian robes, with flax fastenings and cotten piping.",
  2902.     bodypart = "chest",
  2903.     level = 1,
  2904.     requirements = { },
  2905.     base = 0,
  2906.     enchantment = nil,
  2907.     enchantTypes = {"#fabric", "#aged"},
  2908.     value = 3,
  2909.     weight = 8
  2910. })
  2911. table.insert(items, { name = "Sandals",
  2912.     class = "armour",
  2913.     desc = "Well worn flax sandals.",
  2914.     bodypart = "feet",
  2915.     level = 1,
  2916.     requirements = { },
  2917.     base = 0,
  2918.     enchantment = nil,
  2919.     enchantTypes = {"#fabric", "#aged"},
  2920.     value = 1,
  2921.     weight = 3
  2922. })
  2923. table.insert(items, { name = "Iron Mace",
  2924.     class = "weapon",
  2925.     desc = "An iron-shafed mace with a droplet-shaped head.",
  2926.     bodypart = "swordhand",
  2927.     level = 4,
  2928.     requirements = { },
  2929.     base = 10,
  2930.     enchantment = nil,
  2931.     enchantTypes = {"#mace", "#iron"},
  2932.     value = 12,
  2933.     weight = 9
  2934. })
  2935.  
  2936.         --[[SECTION: Enchantments]]--
  2937.  
  2938. enchantments["#blade"] = {"dull", "sharp", "ornamental"};
  2939. enchantments["#metal"] = {"pure", "impure", "blistered", "smooth"};
  2940. enchantments["#wood"] = {"rotting", "firm"};
  2941. enchantments["#iron"] = {"#metal", "pig", "wrought", "cast"};
  2942. enchantments["#fabric"] = {"rough", "quality"};
  2943. enchantments["#aged"] = {"enduring", "weakened"};
  2944. enchantments["#mace"] = {"flanged", "ornamental"};
  2945.    
  2946. enchantments["dull"] = {
  2947.         name = "Dull",
  2948.         desc = "The edge of the blade has dulled.",
  2949.         prefix = true,
  2950.         skill = "base",
  2951.         effect = -2,
  2952.         level = 1,
  2953.         value = 0.5
  2954.     };
  2955. enchantments["sharp"] = {
  2956.         name = "Sharp",
  2957.         desc = "The edge of the blade is very sharp.",
  2958.         prefix = true,
  2959.         skill = "base",
  2960.         effect = 2,
  2961.         level = 1,
  2962.         value = 1.5
  2963.     };
  2964. enchantments["pure"] = {
  2965.         name = "Pure",
  2966.         desc = "It's pure metal base has left it well balanced.",
  2967.         prefix = true,
  2968.         skill = "speed",
  2969.         effect = 1,
  2970.         level = 1,
  2971.         value = 1.7
  2972.     };
  2973. enchantments["impure"] = {
  2974.         name = "Impure",
  2975.         desc = "It's impure metal base has left it unbalanced.",
  2976.         prefix = true,
  2977.         skill = "speed",
  2978.         effect = -1,
  2979.         level = 1,
  2980.         value = 0.3
  2981.     };
  2982. enchantments["blistered"] = {
  2983.         name = "Blistered",
  2984.         desc = "It has not been well forged, and the metal is inconsistent throughout.",
  2985.         prefix = true,
  2986.         skill = "strength",
  2987.         effect = -1,
  2988.         level = 1,
  2989.         value = 0.25
  2990.     };
  2991. enchantments["smooth"] = {
  2992.         name = "Smooth",
  2993.         desc = "The metal was carefully forged, and is free of imperfections.",
  2994.         prefix = true,
  2995.         skill = "strength",
  2996.         effect = 1,
  2997.         level = 1,
  2998.         value = 1.75
  2999.     };
  3000. enchantments["rotting"] = {
  3001.         name = "Rotten",
  3002.         desc = "Moisture has significantly weakened the strength of the wood.",
  3003.         prefix = true,
  3004.         skill = "strength",
  3005.         effect = -1,
  3006.         level = 1,
  3007.         value = 0.25
  3008.     };
  3009. enchantments["firm"] = {
  3010.         name = "Firm",
  3011.         desc = "Particularly hard wood was used in its crafting.",
  3012.         prefix = true,
  3013.         skill = "strength",
  3014.         effect = 1,
  3015.         level = 1,
  3016.         value = 1.75
  3017.     };
  3018. enchantments["pig"] = {
  3019.         name = "Pig",
  3020.         desc = "The unrefined iron is quite brittle.",
  3021.         prefix = true,
  3022.         skill = "durability",
  3023.         effect = 0.5,
  3024.         level = 1,
  3025.         value = 0.75
  3026.     };
  3027. enchantments["wrought"] = {
  3028.         name = "Wrought",
  3029.         desc = "The bloomer-smelted iron is sturdy but quite heavy.",
  3030.         prefix = true,
  3031.         skill = "weight",
  3032.         effect = 1.2,
  3033.         level = 1,
  3034.         value = 0.8
  3035.     };
  3036. enchantments["cast"] = {
  3037.         name = "Hard",
  3038.         desc = "Though a little heavy, you feel the strength the forge lends the iron.",
  3039.         prefix = true,
  3040.         skill = "strength",
  3041.         effect = 2,
  3042.         level = 1,
  3043.         value = 2.5
  3044.     };
  3045. enchantments["rough"] = {
  3046.         name = "Rough",
  3047.         desc = "The weave of the material is inconsistent and holes have appeared.",
  3048.         prefix = true,
  3049.         skill = "endurance",
  3050.         effect = -1,
  3051.         level = 1,
  3052.         value = 0.4
  3053.     };
  3054. enchantments["quality"] = {
  3055.         name = "of Quality",
  3056.         desc = "The careful weave has given the fabric extra strength.",
  3057.         prefix = false,
  3058.         skill = "endurance",
  3059.         effect = 1,
  3060.         level = 1,
  3061.         value = 1.6
  3062.     };
  3063. enchantments["enduring"] = {
  3064.         name = "Enduring",
  3065.         desc = "Quality has been well preserved despite the advanced age.",
  3066.         prefix = true,
  3067.         skill = "durability",
  3068.         effect = 1.5,
  3069.         level = 2,
  3070.         value = 1.2
  3071.     };
  3072. enchantments["weakened"] = {
  3073.         name = "of Age",
  3074.         desc = "After years of use, it is now a shadow of it's former self.",
  3075.         prefix = false,
  3076.         skill = "durability",
  3077.         effect = 0.5,
  3078.         level = 1,
  3079.         value = 0.1
  3080.     };
  3081. enchantments["flanged"] = {
  3082.     name = "Flanged",
  3083.     desc = "The sharp flanges on the mace head make it lethal against armoured opponents.",
  3084.     prefix = false,
  3085.     skill = "base",
  3086.     effect = 2,
  3087.     level = 4,
  3088.     value = 2.2
  3089. }
  3090. enchantments["ornamental"] = {
  3091.     name = "Ornamental",
  3092.     desc = "Though beautifully decorated, it clearly was not designed for combat.",
  3093.     prefix = false,
  3094.     skill = "base",
  3095.     effect = -4,
  3096.     level = 1,
  3097.     value = 3.5
  3098. }
  3099.  
  3100. print("Loading creatures...")  
  3101.    
  3102.         --[[SECTION: Creatures]]--
  3103.  
  3104. --Rat: A common pest in Sacalin
  3105. table.insert(creatures, { name = "Rat",
  3106.     xpos = -1,
  3107.     ypos = -1,
  3108.     levelpos = -1,
  3109.     level = 1,
  3110.     xp = 10,
  3111.     hit = 8,
  3112.     weapon = {
  3113.         name = "claws",
  3114.         base = 4,
  3115.     },
  3116.     ac = 0,
  3117.     imageone = {
  3118.           "_---_",
  3119.          "/     \\",
  3120.         "/ (\\-/) \\",
  3121.         "| /o o\\ |",
  3122.         "| \\ v / |",
  3123.          "\\-\\_/-/",
  3124.           "_M   M_"
  3125.     },
  3126.     imagetwo = {
  3127.          "___",
  3128.         "/o_o\\",
  3129.         "|\\_/|",
  3130.         "M M"
  3131.     }
  3132. })
  3133. --Snake: Oversized lizards with sharp fangs, but quite weak.
  3134. table.insert(creatures, { name = "Snake",
  3135.     xpos = -1,
  3136.     ypos = -1,
  3137.     levelpos = -1,
  3138.     level = 1,
  3139.     xp = 15,
  3140.     hit = 6,
  3141.     weapon = {
  3142.         name = "fangs",
  3143.         base = 8
  3144.     },
  3145.     ac = 0,
  3146.     imageone = {
  3147.         "/0-0\\",
  3148.         "\\___/",
  3149.         " | | ",
  3150.         "_\\  \\",
  3151.        "(__\\___)"
  3152.     },
  3153.     imagetwo = {
  3154.         " ;oo;",
  3155.         " _|| ",
  3156.         "(__) "
  3157.     }
  3158. })
  3159. --Tyrat: Small humanoid figures with sharp faces and carrying sickles. Weak and stupid.
  3160. table.insert(creatures, { name = "Tyrat",
  3161.     xpos = -1,
  3162.     ypos = -1,
  3163.     levelpos = -1,
  3164.     level = 1,
  3165.     xp = 12,
  3166.     hit = 10,
  3167.     weapon = {
  3168.         name = "sickle",
  3169.         base = 6
  3170.     },
  3171.     ac = 0,
  3172.     imageone = {
  3173.         " _____ ",
  3174.         "< V V >",
  3175.         " \\___/ ",
  3176.         "  / \\  ",
  3177.         " ( _ )",
  3178.         "/_) (_\\"
  3179.     },
  3180.     imagetwo = {
  3181.         " __ ",
  3182.         "<..>",
  3183.         " /\\ ",
  3184.         " || "
  3185.     }
  3186. })
  3187. --Wraith: Evil spirits that haunt caves. Tied to quest "Shadows over Aemelius"
  3188. table.insert(creatures, { name = "Wraith",
  3189.     xpos = -1,
  3190.     ypos = -1,
  3191.     levelpos = -1,
  3192.     level = 3,
  3193.     xp = 50,
  3194.     hit = 30,
  3195.     ac = 0,
  3196.     weapon = {
  3197.         name = "WraithBlade",
  3198.         base = 6
  3199.     },
  3200.     imageone = {
  3201.         "  /:\\  ",
  3202.         " |O#O| ",
  3203.         " |{x}| ",
  3204.         " /   \\ ",
  3205.         "|  /:",
  3206.         ": /||",
  3207.         " |/ |/ ",
  3208.         ":~:~:~:",
  3209.         " :~:~: ",
  3210.     },
  3211.     imagetwo = {
  3212.         " /:\\ ",
  3213.         " |:| ",
  3214.         " |/| ",
  3215.         " ~~~ "
  3216.     }
  3217. })
  3218.  
  3219. print("Loading towns & npcs...")   
  3220.  
  3221.         --[[SECTION: Towns]]--
  3222.  
  3223. --You can use in-text "#name", it will replace it with the player's name, and in-text "#class" for the player's class.
  3224. --Dialogues are flexible. They can be:
  3225.     --Strings: This will simply display the message as though the NCP is delivering it
  3226.     --Tables: Work the same as strings but will cycle through each message, giving user the ability to respond at the end of the conversation.
  3227.     --Functions: These can do... anything you like really. Usually used for talking with NPC's and simultaneously updating info (such as quests). Functions can return
  3228.         --Strings and tables, which work the same way as they do for typical dialogues, or nil, which will break out of the conversation immediately.
  3229. globalDialogue = {
  3230.     ["#name"] = function(me)
  3231.         local r = math.random(1,3)
  3232.         if r == 1 then return "You can call me "..me.name.."."
  3233.         elseif r == 2 then return "My name is "..me.name.."."
  3234.         elseif r == 3 then return "I am "..me.name.."." end
  3235.     end;
  3236.     ["#job"] = function(me)
  3237.         if me.job == "smith" then
  3238.             return "I'm a smith. I work a forge and produce weapons and armour. I can trade my inventory with you if you like."
  3239.         elseif me.job == "merchant" then
  3240.             return "I'm a merchant, buying goods from adventurers like yourselves and selling them to interested buyers. I'll trade good gold for any items you're carrying."
  3241.         elseif me.job == "mystic" then
  3242.             return "I'm a mystic, a trainer in the arts of magic. I can teach you spells, for a fee."
  3243.         else
  3244.             local article = "a"
  3245.             if isVowel(string.sub(me.job, 1, 1)) then article = "an" end
  3246.             return "I work as "..article.." "..me.job.."."
  3247.         end
  3248.     end;
  3249.     ["#hello"] = "#ongreet";
  3250.     ["#bye"] = "#onfarewell";
  3251.     ["#goodbye"] = "#onfarewell";
  3252.     ["#exit"] = "#onfarewell";
  3253.     ["#farewell"] = "#onfarewell";
  3254.     ["#leave"] = "#onfarewell";
  3255.     ["#work"] = "#quest";
  3256.     ["#quest"] = "I'm afraid I don't have any work for you, #name.";
  3257. }
  3258.  
  3259. --INDEX 1: NEBUN
  3260. --The Mad Town. Home to a series of eccentrics that worship false gods and sacrifice food, supplies and life
  3261. --to appease them.
  3262.  
  3263. --I keep a list of "on activation" of quests here.
  3264. local function onActivateKP(me)
  3265.     printTownInterface()
  3266.     displayConfirmDialogue("Quest Offer by Jeremiah: ", "It is a somewhat complicated matter. Kershel has lived here all his life and has always been lax in his obligations to the city.")
  3267.     local response = displayOptionDialogue("Quest Offer by Jeremiah: ", "Would you be able to extract his monthly due of 50 gold pieces? I would gladly share a portion of the sum with you.")
  3268.     if response == "yes" then
  3269.         activateQuest(quests["Kershel's Problem"])
  3270.         setNPCDialogue(me.name, 1, "#ongreet", "Hello again #name, have you recovered Kershel's debt yet?")
  3271.         setNPCDialogue(me.name, 1, "#quest", "Ask Kershel about his debt, and return to me when you have the money.")
  3272.         return "Great! You should be able to find Kershel in town- ask him about his debt, and let me know when you have the money."
  3273.     else
  3274.         return "Let me know if you change your mind."
  3275.     end
  3276. end
  3277. local function onActivateEITH(me)
  3278.     printTownInterface()
  3279.     displayConfirmDialogue("Quest Offer by Jeremiah: ", "There have been troubling reports from the hills to the north of Aemelius. Farms appear to be going... missing.")
  3280.     displayConfirmDialogue("Quest Offer by Jeremiah: ", "I would consider it a great favour should you venture to the hills, discover all that you can and report back to me.")
  3281.     local response = displayOptionDialogue("Quest Offer by Jeremiah: ", "I can probably put together a few pieces of gold too. Will you help us?")
  3282.     if response == "yes" then
  3283.         activateQuest(quests["Evil in the Hills"])
  3284.         setNPCDialogue(me.name, 1, "#ongreet", "Greetings #name, I trust you bring word of your adventures in the hills? Or do you wish something else of me?")
  3285.         setNPCDialogue(me.name, 1, "#quest", "Travel to the hills north of Aemelius, discover all that you can and report on what you find.")
  3286.         return "I thank you sir, and shall await your report with great anticipation."
  3287.     else
  3288.         return "The job will be waiting for you if you so desire."
  3289.     end
  3290. end
  3291.  
  3292. town[1].dialogue = {
  3293.     ["#transylvania"] = "";
  3294.     ["#nebun"] = "";
  3295.     ["#groazagol"] = "";
  3296.     ["#dark"] = "";
  3297.  
  3298.     ["#castle aemelius"] = "#aemelius";
  3299.     ["#castle"] = "#aemelius";
  3300.     ["#aemelius"] = "Aemelius is the only town in Sacalin. It's just a little port on the route from Turkey to Romania. It's not heavily trafficked, so it's quiet here.";
  3301.     ["#hills"] = "They're about a league north of town. A few farmers up that way, but it's pretty unremarkable.";
  3302.     ["#crypts"] = "The town crypts are about two leagues east of town- follow the river. No one's been there for months... not sure what's there now.";
  3303.     ["#spire"] = "Why would you want to go there? Well it's just a bit north of town, near the coast, but only a few kooky wizards are up that way.";
  3304.     ["#quest"] = "You should ask Jeremiah if you're looking for work- he would probably have something for you.";
  3305. }
  3306.  
  3307. table.insert(town[1].npc, { name = "Daedelus",
  3308.         job = "smith",
  3309.         xpos = 17, ypos = 6,
  3310.         level = 1,
  3311.         dialogue = {
  3312.             ["#onfirstgreet"] = "Greetings stranger. I'm Daedelus, town smith here. I can give you an offer on some of my goods if you want to trade." ;
  3313.             ["#ongreet"] = "Good morrow to you, #name. In need of fresh iron?";
  3314.             ["#name"] = "My name is Daedelus. It's an old Greek name on my mother's side, if you're curious.";
  3315.             ["#job"] = "I work the smithy here. We don't have much in the way of goods but I can offer you what I have, if you'd like to trade.";
  3316.             ["#kershel"] = "That bum? He's the town fool- spends most of his time dead to the world on drink. He has helped in the workshop when he's short, though.";
  3317.             ["#jeremiah"] = "He's a nervous fellow but runs the town quite well.";
  3318.             ["#erika"] = "She's just settled down here, used to travel a lot. I sometimes buy from her inventory to stock up my own supplies. Not bad prices.";
  3319.             ["#daedelus"] = "Yes, I'm Daedelus.";
  3320.             ["#buy"] = "#trade";
  3321.             ["#trade"] = function(me)
  3322.                 if #me.inventory == 0 then
  3323.                     return "Sorry, I don't have any inventory at the moment. I should have some new stuff ready soon."
  3324.                 else
  3325.                     displayConfirmDialogue(me.name.." the "..me.job.." says:", "Take a look, see if anything catches your fancy:")
  3326.                     runTradeInterface(me, false)
  3327.                     return "Thanks for your business, stranger."
  3328.                 end
  3329.             end;
  3330.             ["#onfarewell"] = "Good travels to you, #name."
  3331.         },
  3332.         nodialogue = "I'm not sure what you're talking about, friend. Maybe someone else does?",
  3333.         inventory = { },
  3334.         priceModifier = 2.5,
  3335.         goldLimit = 0
  3336.     })
  3337. table.insert(town[1].npc, { name = "Erika",
  3338.         job = "merchant",
  3339.         xpos = 22, ypos = 6,
  3340.         level = 1,
  3341.         dialogue = {
  3342.             ["#onfirstgreet"] = "It's #name is it? I'm Erika. Can give you a good price on any goods you want to sell.";
  3343.             ["#ongreet"] = "Looking to sell off some of your goods #name?";
  3344.             ["#kershel"] = "The town fool. He's pretty harmless.";
  3345.             ["#jeremiah"] = "Jeremiah? Don't know him too well, just moved here. Seems to do a good enough job, given the circumstances.";
  3346.             ["#erika"] = "I've just freshly arrived here in Aemelius- travelled the mediterranian before that.";
  3347.             ["#daedelus"] = "He's a good man. We occasionally do business. His goods are a little on the pricy side but they're good quality.";
  3348.             ["#trade"] = function(me)
  3349.                 if #inventory == 0 then
  3350.                     return "Looks like you're out of goods- let me know when you have something, I'll give you a good price on it."
  3351.                 else
  3352.                     displayConfirmDialogue(me.name.." the "..me.job.." says:", "All right, lets see what you've got...")
  3353.                     runTradeInterface(me, true)
  3354.                     return "Appreciate your business, #name."
  3355.                 end
  3356.             end;
  3357.             ["#sell"] = "#trade";
  3358.             ["#onfarewell"] = "Until next time.";
  3359.         },
  3360.         nodialogue = "Couldn't tell you, #name.",
  3361.         inventory = { },
  3362.         priceModifier = 0.75,
  3363.         goldLimit = 25
  3364.     })
  3365. table.insert(town[1].npc, { name = "Kershel",
  3366.         job = "fool",
  3367.         xpos = 18, ypos = 12,
  3368.         level = 1,
  3369.         dialogue = {
  3370.             ["#onfirstgreet"] = "Ah another fool to arrive in Sacalin. I am Kershel, the town fool.";
  3371.             ["#ongreet"] = "The fool returns... what is it you wish of me?";
  3372.             ["#name"] = "I am known by many names, but best by Kershel.";
  3373.             ["#job"] = "My trade is my own, one characterized by ignorance, and rewarded with bliss.";
  3374.             ["#kershel"] = "The town fool, at your service.";
  3375.             ["#jeremiah"] = "The taxman bleeds even my pockets dry.";
  3376.             ["#erika"] = "Is it that the road to hell is paved with the best of intentions, or the gold of the less fortunate?";
  3377.             ["#daedelus"] = "That rolling stone has gathered much moss.";
  3378.             ["#trade"] = "Were it that I carried anything of worth, I would not be a fool.";
  3379.             ["#onfarewell"] = "Shall we meet again? Only time will tell...";
  3380.         },
  3381.         nodialogue = "A wise man asks questions, but a fool asks questions and doesn't listen."
  3382.     })
  3383. table.insert(town[1].npc, { name = "Jeremiah",
  3384.         job = "mayor",
  3385.         xpos = 2, ypos = 8,
  3386.         level = 1,
  3387.         dialogue = {
  3388.             ["#onfirstgreet"] = "A stranger! Greetings, and welcome to the town of Aemelius. I am Jeremiah, the town mayor. If you plan to stay for awhile I may have some work for you.";
  3389.             ["#ongreet"] = "Greetings, #name, I trust all is well. Are you looking for work?";
  3390.             ["#job"] = "Oh, I'm mayor here in Aemelius. I manage the city's finances and make most decisions regarding our future. I also employ adventurers to help the town-  let me know if you're looking for work.";
  3391.             ["#kershel"] = "Kershel is a thorn in my side. He consistently dodges tax collection. In fact I would appreciate your help on that matter...";
  3392.             ["#jeremiah"] = "Yes, I am Jeremiah. How can I help you?";
  3393.             ["#erika"] = "Erika is a new resident to Aemelius, she appeared a few months ago and has set up shop here. So far she has made a valuable contribution to the city.";
  3394.             ["#daedelus"] = "Daedelus has lived here for many years, and is a fine smith.";
  3395.             ["#trade"] = "Both Daedelus and Erika offer trading services if you wish to purchase or sell of equipment.";
  3396.             ["#onfarewell"] = "Farewell, #name. Seek me out if you wish anything of our town.";
  3397.             ["#quest"] = onActivateKP;
  3398.         },
  3399.         nodialogue = "I'm afraid I can't help you with that.",
  3400.     })
  3401.  
  3402. print("Loading quests...")     
  3403.  
  3404.         --[[SECTION: Quests]]--
  3405.        
  3406. --Quest: Kershel's Problem: Petition members of Aemelius for money to pay Kershel's debt to the mayor
  3407.  
  3408. quests["Kershel's Problem"] = { name = "Kershel's Problem",
  3409.     activestage = 1,
  3410.     variables = { },
  3411.     generalDescription = "Kersehl of Aemelius is behind on his taxes, and owes 50 gold to the mayor, Jeremiah. Help Jeremiah to recover the owed money.",
  3412.     stages = {
  3413.         [1] = {
  3414.             desc = "Speak to Kershel about his overdue taxes",
  3415.             condition = function(quest, event)
  3416.                 if event.type == "dialogue" and event.town == 1 and event.npc.name == "Kershel" and findIn(event.topic, "debt", "loan", "tax") then
  3417.                     setNPCDialogue("Kershel", 1, "#ongreet", "Have you spoken to Erika about my debt?")
  3418.                     quest.activestage = 2
  3419.                     return "You have come to collect my taxes? Though all may laugh at a fool, it seems none shall lend him money... so I must seek what is owed of"..
  3420.                     " me. The merchant has lent me a sum of moneys in the past. Ask her to increase this debt and I'm sure she will pay."
  3421.                 end
  3422.             end
  3423.         }, [2] = {
  3424.             desc = "Ask Erika in Aemelius to increase Kershel's debt",
  3425.             condition = function(quest, event)
  3426.                 if event.type == "dialogue" and event.town ==  1 and event.npc.name == "Erika" and findIn(event.topic, "debt", "loan", "kershel", "tax") then
  3427.                     quest.activestage = 3
  3428.                     return "Kershel requires me to raise his debt? I can help but I do not have enough to cover him altogether. Here is what I can spare, and tell him that he must pay me back double for us to be square"
  3429.                 end
  3430.             end
  3431.         }, [3] = {
  3432.             desc = "Return to Kershel to speak about his taxes",
  3433.             condition = function(quest, event)
  3434.                 if event.type == "dialogue" and event.town ==  1 and event.npc.name == "Kershel" and findIn(event.topic, "debt", "loan", "tax") then
  3435.                     quest.activestage = 4
  3436.                     setNPCDialogue("Kershel", 1, "#ongreet", "Seek out Daedelus to see if he will buoy our funds.")
  3437.                     return "It seems we are in need of further funds. I have in the past helped Daedelus man his forge- perhaps he could part with his money just as all fools are."
  3438.                 end
  3439.             end
  3440.         }, [4] = {
  3441.             desc = "Ask Daedelus to extend a debt to Kershel",
  3442.             condition = function(quest, event)
  3443.                 if event.type == "dialogue" and event.town == 1 and event.npc.name == "Daedelus" and findIn(event.topic, "debt", "kershel", "tax") then
  3444.                     quest.activestage = 5
  3445.                     setNPCDialogue("Kershel", 1, "#ongreet", "It appears that the monies have been collected. Waste no time- return these to Jeremiah, that I may be left alone to my foolishness.")
  3446.                     return "This is the LAST time I cover Kerhsel's debts. Take this to Jeremiah, and if you run into Kershel tell him he needs to get a job. And no, town fool doesn't count!"
  3447.                 end
  3448.             end
  3449.         }, [5] = {
  3450.             desc = "Return Kershel's debt money to Jeremiah",
  3451.             condition = function(quest, event)
  3452.                 if event.type == "dialogue" and event.town == 1 and event.npc.name == "Jeremiah" and findIn(event.topic, "debt", "kershel", "tax", "loan") then
  3453.                     quest.activestage = -1
  3454.                     setNPCDialogue("Kershel", 1, "#ongreet", "My leaseman has returned! This humble fool offers his welcome and thanks.")
  3455.                     setNPCDialogue("Jeremiah", 1, "#ongreet", "Welcome back friend. There is a matter that troubles me- I would appreciate your assistance if you're looking for work.")
  3456.                     setNPCDialogue("Jeremiah", 1, "#quest", onActivateEITH)
  3457.                     addGold(25)
  3458.                     return "Excellent, this meets the required amount! Now half the tax brings us to... 25 gold pieces I think? Job well done, however, another matter has arise, that may need attention..."
  3459.                 end
  3460.             end
  3461.         }
  3462.     }
  3463. }
  3464. --Quest: Evil in the Hills: Explore the hills north of Aemelius and learn what you can about the creatures that dwell there
  3465. quests["Evil in the Hills"] = { name = "Evil in the Hills",
  3466.     activestage = 1,
  3467.     variables = { [1] = true, [2] = true, [3] = true, [4] = true, [5] = true },
  3468.     generalDescription = "Jeremiah of Aemelius fears evil has taken root in the hills to the north. Head for the north and see what you can discover.",
  3469.     stages = {
  3470.         [1] = {
  3471.             desc = "Investigate the hills north of Aemelius",
  3472.             condition = function(quest, event)
  3473.                 if inDungeon and activeDungeon == 1 then
  3474.                     displayConfirmDialogue("You step inside...", "You step into the dark cave to see a slew of rough passages cut from the clay walls, and you hear the squeals of wild beasts from within.")
  3475.                     return 2
  3476.                 end
  3477.                 return nil
  3478.             end
  3479.         }, [2] = {
  3480.             desc = "Explore the Hillside Caverns, north of Aemelius",
  3481.             --This function shows an example of multiple conditions occurring, which don't change the game state but provide the player with clues.
  3482.             --At certain dead-ends in the dungeon, messages regarding the dungeon will be given
  3483.             condition = function(quest, event)
  3484.                 if inDungeon and activeDungeon == 1 and dunpx == 3 + 2 and dunpy == 3 + 2 and dunlevel == 2 then
  3485.                     displayConfirmDialogue("You notice something:", "The walls here are freshly dug- the creatures that inhabit this place must need more space.")
  3486.                     return nil
  3487.                 elseif inDungeon and activeDungeon == 1 and dunpx == 8 + 2 and dunpy == 5 + 2 and dunlevel == 1 then
  3488.                     displayConfirmDialogue("You notice something:", "There is a ladder leading down here- it appears these tunnels are not just the work of simple rats.")
  3489.                     return nil
  3490.                 elseif inDungeon and activeDungeon == 1 and dunpx == 1 + 2 and dunpy == 1 + 2 and dunlevel == 3 then
  3491.                     displayConfirmDialogue("Oh no!", "You grasp the ladder but just as you do it gives way, plummeting you into the bottom of the tunnel. You'll have to find another way out.")
  3492.                     return nil
  3493.                 elseif inDungeon and activeDungeon == 1 and dunpx == 2 + 2 and dunpy == 1 + 2 and dunlevel == 3 then
  3494.                     displayConfirmDialogue("You notice something", "Up ahead you spy a small creature- a Tyrat, mean little humanoids that like to steal metals from farmers. They're undoubtedly the creators of these tunnels.")
  3495.                     return nil
  3496.                 elseif inDungeon and activeDungeon == 1 and dunpx == 1 + 2 and dunpy == 2 + 2 and dunlevel == 1 then
  3497.                     displayConfirmDialogue("At last!", "Finally you can see daylight- you have found a way out! You should return to Aemelius and report what you've found.")
  3498.                     return 3
  3499.                 end
  3500.                 return nil
  3501.             end
  3502.         }, [3] = {
  3503.             desc = "Report to Jeremiah of Aemelius about the northern hills",
  3504.             condition = function(quest, event)
  3505.                 if activeTown == 1 and activeDialog == "Jeremiah" then
  3506.                     displayConfirmDialogue("Jeremiah the mayor says:", "You found infestations in the northern hills? I hope you were able to clear them out... thankyou for your help, but now I must ask of you an even greater task.")
  3507.                     setNPCDialogue("Kershel", 1, "Is Sacalin really going to sink?")
  3508.                     setNPCQuest("Jeremiah", 1, "Shadows over Aemelius")
  3509.                     setNPCDialogue("Jeremiah", 1, "The fate of this village depends on you... good luck!")
  3510.                     return -1
  3511.                 end
  3512.                 return nil
  3513.             end
  3514.         }
  3515.     }
  3516. }
  3517. --Quest: Shadows over Aemelius: A vengeful spirit has threatened to sink the island of Sacalin if he is not appeased.
  3518. quests["Shadows over Aemelius"] = { name = "Shadows over Aemelius",
  3519.     acceptMessage = "A cave to the east of Aemelius has opened, and we have seen an evil spirit within its depths! Left as it is, who knows what could happen? Shut this open maw to the depths of Hades, and I shall see you well rewarded.",
  3520.     activestage = 1,
  3521.     variables = { },
  3522.     stages = {
  3523.         [1] = {
  3524.             desc = "Find the newly opened cave east of Aemelius",
  3525.             --This condition actively changes the state of the world (adds a dungeon)
  3526.             condition = function()
  3527.                 if activeTown == 0 then
  3528.                     displayConfirmDialogue("You notice something:", "Just as you step outside you hear a great explosion, and a plume of smoke rise in the foothills to the east. Something has changed.")
  3529.                     addDungeonToWorld(mausoleum, 3)
  3530.                     return 2
  3531.                 end
  3532.             end
  3533.         }, [2] = {
  3534.             desc = "Find the newly opened cave east of Aemelius",
  3535.             condition = function()
  3536.                 if inDungeon and activeDungeon == 3 then
  3537.                     displayConfirmDialogue("You step inside...", "The halls here are lined with stone bricks that look centuries old, and a frozen breeze chills you to the bone. You can feel evil emanating from every inch of this place.")
  3538.                     return 3
  3539.                 end
  3540.             end
  3541.         }, [3] = {
  3542.             desc = "Close the Mausoelum east of Aemelius",
  3543.             condition = function()
  3544.                 if inDungeon and activeDungeon == 3 and dunpx == 3 + 2 and dunpy == 1 + 2 and dunlevel == 1 then
  3545.                     displayConfirmDialogue("You hear a quiet whisper:", "\"Those who enter shall have the honour of meeting Death- turn back now!\"")
  3546.                     return nil
  3547.                 elseif inDungeon and activeDungeon == 3 and dunpx == 9 + 2 and dunpy == 5 + 2 and dunlevel == 1 then
  3548.                     displayConfirmDialogue("You hear a quiet whisper:", "\"Do you like my home, trespasser? You should know you face a foce you cannot possibly reckon\"")
  3549.                     return nil
  3550.                 elseif inDungeon and activeDungeon == 3 and dunpx == 8 + 2 and dunpy == 8 + 2 and dunlevel == 2 then
  3551.                     displayConfirmDialogue("You hear a quiet whisper:", "\"Know that I was once a citizen of Aemelius, but by foul murder I was taken from my home, banished to the realm of spectres.\"")
  3552.                     return nil
  3553.                 elseif inDungeon and activeDungeon == 3 and dunpx == 2 + 2 and dunpy == 8 + 2 and dunlevel == 3 then
  3554.                     displayConfirmDialogue("You hear a quiet whisper:", "\"With every breath of my body I shall wreak vengance on this place- by My almighty fury this island shall sink into the ocean!\"")
  3555.                     return nil
  3556.                 elseif inDungeon and activeDungeon == 3 and dunpx == 5 + 2 and dunpy == 8 + 2 and dunlevel == 3 then
  3557.                     displayConfirmDialogue("You notice something:", "A frightning sight is ahead of you- the wraith that haunts this place, floating gently above the ground. You cannot see his face, but you are certain his expression is menacing.")
  3558.                     return 4
  3559.                 end
  3560.             end
  3561.         }, [4] = {
  3562.             desc = "Defeat the Wraith of the Mausoleum",
  3563.             condition = function ()
  3564.                 if not inDungeon then
  3565.                     displayConfirmDialogue("Quest Updated!", "You burst out from the mausolean, out of breath and barely alive. The wraith has been defeated, and with it, the mausoleum has been sealed. Aemelius is finally safe!")
  3566.                     return 5
  3567.                 end
  3568.             end
  3569.         }, [5] = {
  3570.             desc = "Return to Jeremiah of Aemelius to inform him of the battle",
  3571.             condition = function ()
  3572.                 if activeTown == 1 and activeDialog == "Jeremiah" then
  3573.                     displayConfirmDialogue("Jeremiah the mayor says:", "The wraith has been defeated? We of this town owe you the greatest of gratitudes! You shall forever be a friend of Aemelius and all of Sacalin!")
  3574.                     setNPCDialogue("Kershel", 1, "Were I less foolish perhaps I could have helped...")
  3575.                     setNPCDialogue("Jeremiah", 1, "We are forever in your debt!")
  3576.                     return -1
  3577.                 end
  3578.                 return nil
  3579.             end
  3580.         }
  3581.     },
  3582.     reward = { "GOLD", 100 }
  3583. }
  3584.  
  3585. print("Loading dungeons...")   
  3586.  
  3587.         --[[SECTION: Dungeons]]--
  3588.  
  3589. --Dungeon 1: Hillside Caverns. Associated with the quest "Evil in the Hills"
  3590. dungeons[1] = { name = "Hillside Caverns",
  3591.     xpos = 18,
  3592.     ypos = 5,
  3593.     startx = 8, starty = 1, startlevel = 1,
  3594.     startfx = -1, startfy = 0,
  3595.     level = {
  3596.         [1] = {
  3597.            --123456789
  3598.             "E C# # #E",--1
  3599.             "#  # ### ",--2
  3600.             "##### # #",--3
  3601.             "  #   ###",--4
  3602.             "C####D D " --5
  3603.             --Monsters at: 6,2 7,4
  3604.         },
  3605.         [2] = {
  3606.             "D #####C ",
  3607.             "## #  #  ",
  3608.             " ######  ",
  3609.             " #    ## ",
  3610.             "C# D#U U "
  3611.         },
  3612.         [3] = {
  3613.             "##### C #",
  3614.             "  # ### #",
  3615.             " ###  ###",
  3616.             "##  C # #",
  3617.             "C  U#### ",
  3618.         }
  3619.     },
  3620.     creatures = {
  3621.         cloneCreature(4, 6 + 2, 2 + 2, 1),
  3622.         cloneCreature(3, 5 + 2, 1 + 2, 3),
  3623.         cloneCreature(3, 6 + 2, 5 + 2, 3),
  3624.         cloneCreature(3, 5 + 2, 5 + 2, 2),
  3625.         cloneCreature(3, 1 + 2, 3 + 2, 1)
  3626.     },
  3627.     creaturesSpawn = true
  3628. }
  3629. --Dungeon 2: Gallery. This is a debug program written for Tommy Royall to view the monsters he's created
  3630. dungeons[2] = { name = "Monster Gallery",
  3631.     xpos = 11,
  3632.     ypos = 10,
  3633.     startx = 2, starty = 1, startlevel = 1,
  3634.     startfx = 1, startfy = 0,
  3635.     creatures = {
  3636.         cloneCreature(1, 3 + 2, 3 + 2, 1)
  3637.     },
  3638.     creatureSpawn = false,
  3639.     level = {
  3640.         [1] = {
  3641.             "E######E",
  3642.             "########",
  3643.             "########",
  3644.             "########",
  3645.             "########",
  3646.             "########",
  3647.             "E######E"
  3648.         }
  3649.     }
  3650. }
  3651. --Dungeon 3: Mausoleum. Associated with the quest "Shadows over Aemelius". Note this dungeon is dynamically added (always to index 2)
  3652. mausoleum = { name = "Mausoleum",
  3653.     xpos = 30,
  3654.     ypos = 9,
  3655.     startx = 2, starty = 1, startlevel = 1,
  3656.     startfx = 1, startfy = 0,
  3657.     creatures = { cloneCreature(4, 8 + 2, 8 + 2, 3) },
  3658.     creaturesSpawn = true,
  3659.     level = {
  3660.         [1] = {
  3661.             "E## #### ",
  3662.             "  ###  #C",
  3663.             " C#  ### ",
  3664.             "C  ### # ",
  3665.             "####  ###",
  3666.             " # # C# D",
  3667.             "## ##    ",
  3668.             " C  ###C "
  3669.         }, [2] = {
  3670.             "D# ### C ",
  3671.             " ### ####",
  3672.             "  #    # ",
  3673.             "###C #  #",
  3674.             "#   C####",
  3675.             "####  # B",
  3676.             " # ##### ",
  3677.             "D#  #  C "
  3678.         }, [3] = {
  3679.             "U# C#####",
  3680.             " ### #  #",
  3681.             "#    #C  ",
  3682.             "#####  #C",
  3683.             "#C# #### ",
  3684.             "    #  #U",
  3685.             " ###     ",
  3686.             "U# #####E"
  3687.         },
  3688.     }
  3689. }
  3690.  
  3691. print("Script run successfully!")
  3692.  
  3693. --This MUST be included- it indicates the script was successful. Failure to call/reach this will result in the game not running.
  3694. return true
  3695. MKFIL rpg/sacalin/towns
  3696. WRITE 34
  3697. TOWN "Castle Aemelius" 7,11,27,9
  3698.                          \ /
  3699. -__--__--__--__--__--__--| |
  3700. _______________/_\__/_\__| |
  3701. |             //_\\//_\\  |  
  3702. |             // \\// \\  |  
  3703. -------\=/    /   \/   \  |
  3704.        | |    |_ _||_ _|  |  
  3705.        |_|                |--
  3706.                              
  3707.        \=/                |--
  3708.        | |                |
  3709. -------|_|                |  
  3710. |                        www
  3711. |                        \ /
  3712. -__--__--_ --__--__--__--| |
  3713.                          | |
  3714. TOWN "Ancient Spire" 7,5,27,7
  3715.    /// \\\               \ /
  3716. ^-/_/___\_\-^-^-^/_\-^-^-| |
  3717. __|  : :  |_____//_\\____| |
  3718.   | :   ::|     // \\     |  
  3719.   |  :    |     /   \     |  
  3720.   |:  : : |     |_ _|     {--
  3721.   |  :    |                  
  3722.   |/-----\|               {--
  3723.   |       |               |  
  3724.    \_| |_/                {  
  3725.                           |  
  3726.   ,           ,           ,  
  3727.  / \         / \         / \
  3728.  \ /         \ /         \ /
  3729. -| |-^-^-^-^-| |-^-^-^-^-| |
  3730.  |_|         |_|         |_|
  3731. MKFIL rpg/sacalin/world
  3732. WRITE 17
  3733. ....................................
  3734. ................___.................
  3735. ...../\......../   \_...............
  3736. .....||__-----/      \..............
  3737. ..../|       /___ ____\--*--____....
  3738. ...|                     *      \...
  3739. ...|                    *        \..
  3740. ../                 ****     _   |..
  3741. ..|       ******   *        / \  |..
  3742. ..|  ###   ********        _    /...
  3743. ..|  #      ***           / \  /....
  3744. ..\  ###                       |....
  3745. ...\___                       /.....
  3746. .......\___                  /......
  3747. ...........\________________/.......
  3748. ....................................
  3749. ....................................
  3750. MKDIR rpg/saves
  3751. MKDIR rpg/scripts
  3752. MKFIL rpg/scripts/creatures
  3753. WRITE 168
  3754. --[[
  3755.         Creatures Script
  3756.        
  3757.         This defines the mosters that the player will encounter, both in the overworld and in dungeons.
  3758. ]]--
  3759.  
  3760. --[[Creatures can have the following attributes:
  3761.     xpos:int = the x position (set to -1 in this file)
  3762.     ypos:int = the y position (set to -1 in this file)
  3763.     levelpos:int = the level of a dungeon the creature is on (again, -1 in this file)
  3764.     level:int = the player level at which this enemy starts appearing randomly
  3765.     xp:int = the amount of XP earnt for slaying it
  3766.     hit:int = the number of hit points the enemy has
  3767.     weapon:table = an item the enemy uses as a weapon. This can be anything specified in the equipment sheet, or a custom item
  3768.     ac:int = the creature's armour class- how easily it can deflect/avoid strikes
  3769.     imageone:table = an ASCII image of the enemy when 1 tile away from the player
  3770.     imagetwo:table = an ASCII image of the enemy when 2 tiles away from the player
  3771.    
  3772.     Warning- make sure the images are consistent in their spacing, or opponents may look... 'segmented'
  3773. ]]--
  3774.  
  3775. --Rat: A common pest in Transylvania
  3776. table.insert(creatures, { name = "Rat",
  3777.     xpos = -1,
  3778.     ypos = -1,
  3779.     levelpos = -1,
  3780.     level = 1,
  3781.     xp = 10,
  3782.     hit = 12,
  3783.     weapon = {
  3784.         name = "claws",
  3785.         base = 4,
  3786.     },
  3787.     ac = 0,
  3788.     imageone = {
  3789.           "_---_",
  3790.          "/     \\",
  3791.         "/ (\\-/) \\",
  3792.         "| /o o\\ |",
  3793.         "| \\ v / |",
  3794.          "\\-\\_/-/",
  3795.           "_M   M_"
  3796.     },
  3797.     imagetwo = {
  3798.          "___",
  3799.         "/o_o\\",
  3800.         "|\\_/|",
  3801.         "M M"
  3802.     }
  3803. })
  3804. --Snake: Oversized lizards with sharp fangs, but quite weak.
  3805. table.insert(creatures, { name = "Snake",
  3806.     xpos = -1,
  3807.     ypos = -1,
  3808.     levelpos = -1,
  3809.     level = 1,
  3810.     xp = 15,
  3811.     hit = 8,
  3812.     weapon = {
  3813.         name = "fangs",
  3814.         base = 8
  3815.     },
  3816.     ac = 0,
  3817.     imageone = {
  3818.         "/0-0\\",
  3819.         "\\___/",
  3820.         " | | ",
  3821.         "_\\  \\",
  3822.        "(__\\___)"
  3823.     },
  3824.     imagetwo = {
  3825.         " ;oo;",
  3826.         " _|| ",
  3827.         "(__) "
  3828.     }
  3829. })
  3830. --Tyrat: Small humanoid figures with sharp faces and carrying sickles. Weak and stupid.
  3831. table.insert(creatures, { name = "Tyrat",
  3832.     xpos = -1,
  3833.     ypos = -1,
  3834.     levelpos = -1,
  3835.     level = 1,
  3836.     xp = 20,
  3837.     hit = 16,
  3838.     weapon = {
  3839.         name = "sickle",
  3840.         base = 6
  3841.     },
  3842.     ac = 0,
  3843.     imageone = {
  3844.         " _____ ",
  3845.         "< V V >",
  3846.         " \\___/ ",
  3847.         "  / \\  ",
  3848.         " ( _ )",
  3849.         "/_) (_\\"
  3850.     },
  3851.     imagetwo = {
  3852.         " __ ",
  3853.         "<..>",
  3854.         " /\\ ",
  3855.         " || "
  3856.     }
  3857. })
  3858. --Skeletons: Living dead, prowling tombs and now the darklands.
  3859. table.insert(creatures, { name = "Skeleton",
  3860.     xpos = -1,
  3861.     ypos = -1,
  3862.     levelpos = -1,
  3863.     level = 5,
  3864.     xp = 25,
  3865.     hit = 25,
  3866.     weapon = {
  3867.         name = "fingers",
  3868.         base = 8
  3869.     },
  3870.     ac = 1,
  3871.     imageone = {
  3872.         " /o o\\";
  3873.         " | ^ |";
  3874.         "  \\W/";
  3875.         "/--|--\\";
  3876.         "|/-|-\\\|";
  3877.         "||-|-||";
  3878.         "w|-|-|w";
  3879.         " /0-0\\";
  3880.         " |   |";
  3881.         " V   V";
  3882.         " |   |";
  3883.     },
  3884.     imagetwo = {
  3885.         " {}";
  3886.         "/()\\";
  3887.         " ||";
  3888.         " ||";
  3889.     }
  3890. })
  3891. --Wraith: Evil spirits that haunt caves.
  3892. table.insert(creatures, { name = "Wraith",
  3893.     xpos = -1,
  3894.     ypos = -1,
  3895.     levelpos = -1,
  3896.     level = 10,
  3897.     xp = 100,
  3898.     hit = 50,
  3899.     ac = 0,
  3900.     weapon = {
  3901.         name = "WraithBlade",
  3902.         base = 10
  3903.     },
  3904.     imageone = {
  3905.         "  /:\\  ",
  3906.         " |O#O| ",
  3907.         " |{x}| ",
  3908.         " /   \\ ",
  3909.         "|  /:",
  3910.         ": /||",
  3911.         " |/ |/ ",
  3912.         ":~:~:~:",
  3913.         " :~:~: ",
  3914.     },
  3915.     imagetwo = {
  3916.         " /:\\ ",
  3917.         " |:| ",
  3918.         " |/| ",
  3919.         " ~~~ "
  3920.     }
  3921. })
  3922. MKFIL rpg/scripts/dungeons
  3923. WRITE 193
  3924. --Dungeon 1: Fargo's Place. Associated with the quest "An Impossible Errand"
  3925.  
  3926. --Chests can contain
  3927.     -- Tables: These are items, constructed in much the same way standard items are constructed
  3928.     -- Number: A fixed amount of gold
  3929.     -- String: Can be one of the following: map, compass, item, gold
  3930.     -- Nothing: Either a small amount of gold or a piece of random equipment
  3931. dungeons[1] = { name = "Fargo's Place",
  3932.     xpos = 3,
  3933.     ypos = 13,
  3934.     startx = 3, starty = 2, startlevel = 1,
  3935.     startfx = 0, startfy = 1,
  3936.     level = {
  3937.         [1] = {
  3938.            --1234567890123456789012
  3939.             "  E ## ##### #D  ###  ",--1
  3940.             "C #  ###   ###  ## ##E",--2
  3941.             "### C  ###   #C #     ",--3
  3942.             " #  #### #####  # #C##",--4
  3943.             "##C   #      #  D D  #",--5
  3944.             "#   ### #### #   C  ##",--6
  3945.             "##### #C#  D # D##### ",--7
  3946.         },
  3947.         [2] = {
  3948.            --1234567890123456789012
  3949.             "### ###### ###U ######",--1
  3950.             "# ### C  # #    #   # ",--2
  3951.             "#  #   ### ##C ## C###",--3
  3952.             "## ##  # ##    #     #",--4
  3953.             " #  ##### #### #U U ##",--5
  3954.             "#####   #    #    ### ",--6
  3955.             "C  #   C###U ##U ## #C",--7
  3956.         }
  3957.     },
  3958.     creatures = { },
  3959.     chests = { },
  3960.     creaturesSpawn = true,
  3961.     playerHasMap = false,
  3962.     playerHasCompass = false,
  3963.     visible = true
  3964. }
  3965.  
  3966. --Dungeon 2: Fell's Cave. Associataed with the quest "Fuel for the Pyre"
  3967. dungeons[2] = { name = "Fell's Cave",
  3968.     xpos = 9,
  3969.     ypos = 24,
  3970.     startx = 2, starty = 1, startlevel = 1,
  3971.     startfx = 1, startfy = 0,
  3972.     level = {
  3973.         [1] = {
  3974.            --12345678901
  3975.             "E## ###   C",--1
  3976.             "# # # # ###",--2
  3977.             "# ### ###  ",--3
  3978.             "# C     ###",--4
  3979.             "##   C    #",--5
  3980.             " ### #  ###",--6
  3981.             "   ###### C",--7
  3982.         }
  3983.     },
  3984.     creatures = { },
  3985.     chests = { {xpos = 11+2, ypos = 1+2, zpos = 1, content="map"},
  3986.                {xpos = 6+2, ypos = 5+2, zpos = 1, content="item"},
  3987.                {xpos = 11+2, ypos = 7+2, zpos = 1, content=25},
  3988.                {xpos = 3+2, ypos = 4+2, zpos = 1, content="compass"}
  3989.              };
  3990.     creatureSpawn = true,
  3991.     playerHasMap = false,
  3992.     playerHasCompass = false,
  3993.     visible = true
  3994. }
  3995.  
  3996. --Dungeon 3: Nebun Mausoleum. Associated with the quest "Laid to Rest"
  3997. dungeons[3] = { name = "Nebun Mausoleum",
  3998.     xpos = 54,
  3999.     ypos = 24,
  4000.     startx = 11, starty = 2, startlevel = 1,
  4001.     startfx = 0, startfy = 1,
  4002.     level = {
  4003.         [1] = {
  4004.            --1234567890123456789012
  4005.             " E C # C  E  C C #  E ",--1
  4006.             " # # # #  #  # # #  # ",--2
  4007.             " D ###############  D ",--3
  4008.             "          #           ",--4
  4009.             "  C # C # # # C # C   ",--5
  4010.             "  #################   ",--6
  4011.             "  C # # C C C # # C   ",--7
  4012.             "      #         #     ",--8
  4013.             "    #####   ######    ",--9
  4014.             "   ##   #C C#    #D   ",--0
  4015.         },
  4016.         [2] = {     --1         2
  4017.            --1234567890123456789012
  4018.             "  ##D         D###### ",--1
  4019.             " ##    D            # ",--2
  4020.             " U     #  C##  #### U ",--3
  4021.             "# C  ###C   #  #  #   ",--4
  4022.             "######  #####C## ##C  ",--5
  4023.             "#  C #     #   # #    ",--6
  4024.             " C  ####   ###  #####C",--7
  4025.             " ####  #C ## ####   # ",--8
  4026.             "##  ##  ###     # ### ",--9
  4027.             " ### C    ##C C## U #C",--0
  4028.         },
  4029.         [3] = {
  4030.            --1234567890123456789012
  4031.             "    U     ####U       ",--1
  4032.             "    #  U  # #         ",--2
  4033.             "    #  #  # #         ",--3
  4034.             "    #  #### #         ",--4
  4035.             "    ##      #         ",--5
  4036.             "     ########         ",--6
  4037.             "                      ",--7
  4038.             "                      ",--8
  4039.             "                      ",--9
  4040.             "                      ",--0
  4041.         }
  4042.     },
  4043.     creatures = {
  4044.        
  4045.     },
  4046.     chests = {
  4047.         {xpos = 11+2, ypos = 7+2, zpos = 1, content="compass"},
  4048.         {xpos = 9+2, ypos = 7+2, zpos = 1, content="item"},
  4049.         {xpos = 19+2, ypos = 7+2, zpos = 1, content="item"},
  4050.         {xpos = 15+2, ypos = 5+2, zpos = 1, content="item"},
  4051.         {xpos = 3+2, ypos = 5+2, zpos = 1, content="item"},
  4052.         {xpos = 5+2, ypos = 10+2, zpos = 2, content="item"},
  4053.         {xpos = 4+2, ypos = 6+2, zpos = 2, content="item"},
  4054.         {xpos = 11+2, ypos = 3+2, zpos = 2, content="item"},
  4055.         {xpos = 22+2, ypos = 7+2, zpos = 2, content="item"},
  4056.         {xpos = 15+2, ypos = 10+2, zpos = 2, content="item"},
  4057.         {xpos = 3+2, ypos = 4+2, zpos = 2, content="item"},
  4058.     },
  4059.     creaturesSpawn = true,
  4060.     playerHasMap = false,
  4061.     playerHasCompass = false,
  4062.     visible = true
  4063. }
  4064.  
  4065. --Dungeon 4: Lichen Cave. Associated with the quest "The Empty Larder"
  4066. dungeons[4] = { name = "Lichen Cave",
  4067.     xpos = 59,
  4068.     ypos = 17,
  4069.     startx = 2, starty = 1, startlevel = 1,
  4070.     startfx = 1, startfy = 0,
  4071.     level = {
  4072.         [1] = {
  4073.            --123456789
  4074.             "E## #### ",--1
  4075.             "  ###  #C",--2
  4076.             " C#  ### ",--3
  4077.             "C  ### # ",--4
  4078.             "####  ###",--5
  4079.             " # # C# D",--6
  4080.             "## ##    ",--7
  4081.             " C  ###C "--8
  4082.         }, [2] = {
  4083.            --123456789
  4084.             "D# ### C ",--1
  4085.             " ### ####",--2
  4086.             "  #    # ",--3
  4087.             "###C #  #",--4
  4088.             "#   C####",--5
  4089.             "####  # B",--6
  4090.             " # ##### ",--7
  4091.             "D#  #  C "--8
  4092.         }, [3] = {
  4093.            --123456789
  4094.             "U# C#####",--1
  4095.             " ### #  #",--2
  4096.             "#    #C  ",--3
  4097.             "#####  #C",--4
  4098.             "#C# #### ",--5
  4099.             "    #  #U",--6
  4100.             " ###     ",--7
  4101.             "U# #####E"--8
  4102.         },
  4103.     },
  4104.     creatures = { },
  4105.     chests = {
  4106.         {xpos = 6+2, ypos = 6+2, zpos = 1, content="compass"},
  4107.         {xpos = 4+2, ypos = 1+2, zpos = 3, content="map"},
  4108.         {xpos = 8+2, ypos = 1+2, zpos = 2, content="item"},
  4109.         {xpos = 2+2, ypos = 8+2, zpos = 1, content="item"},
  4110.         {xpos = 2+2, ypos = 5+2, zpos = 3, content="item"},
  4111.     },
  4112.     creaturesSpawn = true,
  4113.     playerHasMap = false,
  4114.     playerHasCompass = false,
  4115.     visible = true
  4116. }
  4117. MKFIL rpg/scripts/equipment
  4118. WRITE 311
  4119. --[[
  4120.         Equipment Script
  4121.        
  4122.         This defines all possible items the player can own, carry and equip, as well as enchantments that can be
  4123.         applied to them.
  4124. ]]--
  4125.  
  4126.  
  4127. --An item contains the following fields:
  4128. --[[name:string = the name of the item, as it appears in one's inventory. Take care not to make too long
  4129.     desc:string = a short description of the item's appearance
  4130.     bodypart:string = can be any of the following:
  4131.         - swordhand: usually weapons
  4132.         - offhand: usually dual-wield weapons, shields or other tools like torches
  4133.         - both: usually two-handed weapons
  4134.         - head: helmets, cowls and the like
  4135.         - chest: chestplates, robes, tunics
  4136.         - legs: greaves, pants
  4137.         - feet: boots, shoes
  4138.     level:int = at what level this item will start appearing in loot- it apppears 3 levels earlier in stores
  4139.     requirements:table = a list of tables, including:
  4140.             name:string = can be endurance, strength, agility, speed, level
  4141.             level:int = the level the aforementioned trait needs to be to equip the item.
  4142.     base:int = WEAPONS, the base damage they do, SHIELDS, the normalized percentage of block effectiveness
  4143.                 ARMOUR, the base AC they provide the player with
  4144.     enchantment:table = the enchantment the item has, if any. Note- you should NEVER apply an enchantment in
  4145.             this screen, but in the RPG script itself.
  4146.     enchantTypes:table = a list of tags indicating which enchantment classes the item can carry
  4147.     value:int = the item's base gold value to merchants
  4148.     weight:int = how much the item weights
  4149. ]]--
  4150. table.insert(items, { name = "Copper Dagger",
  4151.     class = "weapon",
  4152.     desc = "A copper dagger, weathered and nicked from age.",
  4153.     bodypart = "swordhand",
  4154.     level = 1,
  4155.     requirements = { },
  4156.     base = 6,
  4157.     enchantment = nil,
  4158.     enchantTypes = {"#blade", "#metal", "#aged"},
  4159.     value = 5,
  4160.     weight = 5
  4161. })
  4162. table.insert(items, { name = "Wooden Staff",
  4163.     class = "weapon",
  4164.     desc = "A solid but soft yew staff, made from a fallen branch.",
  4165.     bodypart = "both",
  4166.     level = 1,
  4167.     requirements = { },
  4168.     base = 8,
  4169.     enchantment = nil,
  4170.     enchantTypes = {"#wood", "#aged"},
  4171.     value = 2,
  4172.     weight = 8
  4173. })
  4174. table.insert(items, { name = "Iron Gladius",
  4175.     class = "weapon",
  4176.     desc = "Shortsword of antiquity.",
  4177.     bodypart = "swordhand",
  4178.     level = 3,
  4179.     requirements = { },
  4180.     base = 8,
  4181.     enchantment = nil,
  4182.     enchantTypes = {"#iron", "#blade", "#aged"},
  4183.     value = 15,
  4184.     weight = 6
  4185. })
  4186. table.insert(items, { name = "Quarterstaff",
  4187.     class = "weapon",
  4188.     desc = "An oaken staff, without ornament.",
  4189.     bodypart = "both",
  4190.     level = 3,
  4191.     requirements = { },
  4192.     base = 11,
  4193.     enchantment = nil,
  4194.     enchantTypes = {"#wood"},
  4195.     value = 12,
  4196.     weight = 9
  4197. })
  4198. table.insert(items, { name = "Practice Buckler",
  4199.     class = "shield",
  4200.     desc = "A fist-sized shield made from wooden boards, repurposed from sword training.",
  4201.     bodypart = "offhand",
  4202.     level = 1,
  4203.     requirements = { },
  4204.     base = 0.5,
  4205.     enchantment = nil,
  4206.     enchantTypes = {"#wood", "#aged"},
  4207.     value = 8,
  4208.     weight = 4
  4209. })
  4210. table.insert(items, { name = "Hessian Robe",
  4211.     class = "armour",
  4212.     desc = "Old hessian robes, with flax fastenings and cotten piping.",
  4213.     bodypart = "chest",
  4214.     level = 1,
  4215.     requirements = { },
  4216.     base = 0,
  4217.     enchantment = nil,
  4218.     enchantTypes = {"#fabric", "#aged"},
  4219.     value = 3,
  4220.     weight = 8
  4221. })
  4222. table.insert(items, { name = "Sandals",
  4223.     class = "armour",
  4224.     desc = "Well worn flax sandals.",
  4225.     bodypart = "feet",
  4226.     level = 1,
  4227.     requirements = { },
  4228.     base = 0,
  4229.     enchantment = nil,
  4230.     enchantTypes = {"#fabric", "#aged"},
  4231.     value = 1,
  4232.     weight = 3
  4233. })
  4234. table.insert(items, { name = "Iron Mace",
  4235.     class = "weapon",
  4236.     desc = "An iron-shafed mace with a droplet-shaped head.",
  4237.     bodypart = "swordhand",
  4238.     level = 4,
  4239.     requirements = { },
  4240.     base = 10,
  4241.     enchantment = nil,
  4242.     enchantTypes = {"#mace", "#iron"},
  4243.     value = 12,
  4244.     weight = 9
  4245. })
  4246.  
  4247.         --[[SECTION: Enchantments]]--
  4248.  
  4249. --[[An enchantment can either encompass a specific enchantment, one that can be applied to an item, or
  4250.     represent a class of enchantments that may apply to a number of different items. For example, being a
  4251.     rough item is an enchantment that hurts durability of cloth items, but #fabric is a class that encomasses
  4252.     any number enchantments that can apply to fabric items- roughness being one of them
  4253.    
  4254.     When items are generated there is a probablity (or it can be forced) that a random enchantment is applied
  4255.     to them, and the use of class tags allows the scripter to specify which enchantments are allowed.
  4256.    
  4257.     Nesting classes of enchantments is okay as well- an iron weapon is still made of metal, so exclusive iron
  4258.     enchantments like being cast or wrought iron apply as well as metal ones like purity and blistering.
  4259. ]]--
  4260. enchantments["#blade"] = {"dull", "sharp", "ornamental"};
  4261. enchantments["#metal"] = {"pure", "impure", "blistered", "smooth"};
  4262. enchantments["#wood"] = {"rotting", "firm"};
  4263. enchantments["#iron"] = {"#metal", "pig", "wrought", "cast"};
  4264. enchantments["#fabric"] = {"rough", "quality"};
  4265. enchantments["#aged"] = {"enduring", "weakened"};
  4266. enchantments["#mace"] = {"flanged", "ornamental"};
  4267.    
  4268. --[[An enchantment specific can have the following properties:
  4269.     - name:string = the name the enchantment appears as when the item is looked at
  4270.     - desc:string = a short description of the enchantment, appended to the existing item description
  4271.     - prefix:bool = if the enchantment name is added to the front or back of the item name: "Dull Sword" vs. "Sword of Dullness"
  4272.     - skill:string = what the enchantment effects- can be a user atttribute or an item attribute
  4273.     - effect:int = the amount to be added (or removed) from the specified attribute
  4274.     - level:int = the level at which these enchantments begin to appear on weapons- merchants 3 levels earlier
  4275.     - value:number = the percentage by which this modifies the item's value- 1 leaves it the same, 0.5 to 50% etc.
  4276. ]]--
  4277. enchantments["dull"] = {
  4278.     name = "Dull",
  4279.     desc = "The edge of the blade has dulled.",
  4280.     prefix = true,
  4281.     skill = "base",
  4282.     effect = -2,
  4283.     level = 1,
  4284.     value = 0.5
  4285. }
  4286. enchantments["sharp"] = {
  4287.     name = "Sharp",
  4288.     desc = "The edge of the blade is very sharp.",
  4289.     prefix = true,
  4290.     skill = "base",
  4291.     effect = 2,
  4292.     level = 1,
  4293.     value = 1.5
  4294. }
  4295. enchantments["pure"] = {
  4296.     name = "Pure",
  4297.     desc = "It's pure metal base has left it well balanced.",
  4298.     prefix = true,
  4299.     skill = "speed",
  4300.     effect = 1,
  4301.     level = 1,
  4302.     value = 1.7
  4303. }
  4304. enchantments["impure"] = {
  4305.     name = "Impure",
  4306.     desc = "It's impure metal base has left it unbalanced.",
  4307.     prefix = true,
  4308.     skill = "speed",
  4309.     effect = -1,
  4310.     level = 1,
  4311.     value = 0.3
  4312. }
  4313. enchantments["blistered"] = {
  4314.     name = "Blistered",
  4315.     desc = "It has not been well forged, and the metal is inconsistent throughout.",
  4316.     prefix = true,
  4317.     skill = "strength",
  4318.     effect = -1,
  4319.     level = 1,
  4320.     value = 0.25
  4321. }
  4322. enchantments["smooth"] = {
  4323.     name = "Smooth",
  4324.     desc = "The metal was carefully forged, and is free of imperfections.",
  4325.     prefix = true,
  4326.     skill = "strength",
  4327.     effect = 1,
  4328.     level = 1,
  4329.     value = 1.75
  4330. }
  4331. enchantments["rotting"] = {
  4332.     name = "Rotten",
  4333.     desc = "Moisture has significantly weakened the strength of the wood.",
  4334.     prefix = true,
  4335.     skill = "strength",
  4336.     effect = -1,
  4337.     level = 1,
  4338.     value = 0.25
  4339. }
  4340. enchantments["firm"] = {
  4341.     name = "Firm",
  4342.     desc = "Particularly hard wood was used in its crafting.",
  4343.     prefix = true,
  4344.     skill = "strength",
  4345.     effect = 1,
  4346.     level = 1,
  4347.     value = 1.75
  4348. }
  4349. enchantments["pig"] = {
  4350.     name = "Pig",
  4351.     desc = "The unrefined iron is quite brittle.",
  4352.     prefix = true,
  4353.     skill = "durability",
  4354.     effect = 0.5,
  4355.     level = 1,
  4356.     value = 0.75
  4357. }
  4358. enchantments["wrought"] = {
  4359.     name = "Wrought",
  4360.     desc = "The bloomer-smelted iron is sturdy but quite heavy.",
  4361.     prefix = true,
  4362.     skill = "weight",
  4363.     effect = 1.2,
  4364.     level = 1,
  4365.     value = 0.8
  4366. }
  4367. enchantments["cast"] = {
  4368.     name = "Hard",
  4369.     desc = "Though a little heavy, you feel the strength the forge lends the iron.",
  4370.     prefix = true,
  4371.     skill = "strength",
  4372.     effect = 2,
  4373.     level = 1,
  4374.     value = 2.5
  4375. }
  4376. enchantments["rough"] = {
  4377.     name = "Rough",
  4378.     desc = "The weave of the material is inconsistent and holes have appeared.",
  4379.     prefix = true,
  4380.     skill = "endurance",
  4381.     effect = -1,
  4382.     level = 1,
  4383.     value = 0.4
  4384. }
  4385. enchantments["quality"] = {
  4386.     name = "of Quality",
  4387.     desc = "The careful weave has given the fabric extra strength.",
  4388.     prefix = false,
  4389.     skill = "endurance",
  4390.     effect = 1,
  4391.     level = 1,
  4392.     value = 1.6
  4393. }
  4394. enchantments["enduring"] = {
  4395.     name = "Enduring",
  4396.     desc = "Quality has been well preserved despite the advanced age.",
  4397.     prefix = true,
  4398.     skill = "durability",
  4399.     effect = 1.5,
  4400.     level = 2,
  4401.     value = 1.2
  4402. }
  4403. enchantments["weakened"] = {
  4404.     name = "of Age",
  4405.     desc = "After years of use, it is now a shadow of it's former self.",
  4406.     prefix = false,
  4407.     skill = "durability",
  4408.     effect = 0.5,
  4409.     level = 1,
  4410.     value = 0.1
  4411. }
  4412. enchantments["flanged"] = {
  4413.     name = "Flanged",
  4414.     desc = "The sharp flanges on the mace head make it lethal against armoured opponents.",
  4415.     prefix = true,
  4416.     skill = "base",
  4417.     effect = 2,
  4418.     level = 4,
  4419.     value = 2.2
  4420. }
  4421. enchantments["ornamental"] = {
  4422.     name = "Ornamental",
  4423.     desc = "Though beautifully decorated, it clearly was not designed for combat.",
  4424.     prefix = true,
  4425.     skill = "base",
  4426.     effect = -4,
  4427.     level = 1,
  4428.     value = 3.5
  4429. }
  4430. MKFIL rpg/scripts/npc
  4431. WRITE 315
  4432.  
  4433. --I keep a list of "on activation" functions here, to save me writing them out in long-hand later. Dialogue-activated quests are
  4434. --stored here- a second list can be found in the quest script, for quest-actiavted quests (there's a mouthful :P)
  4435. local function onActivateReport(me)
  4436.     printTownInterface()
  4437.     displayConfirmDialogue("Quest Offer by Petre: ", "Yes, as you may know there is a plan here to dispel the darkness of our valley, but it is rather... demanding of our people.")
  4438.     local response = displayOptionDialogue("Quest Offer by Petre: ", "I need a man to collect a report from the members of our community on the progress of this 'plan'. Can you do this?")
  4439.     if response == "yes" then
  4440.         activateQuest(quests["The Report"])
  4441.         setNPCDialogue(me.name, 1, "#ongreet", "I trust you are here to deliver to me the report, or do you have other questions about it?")
  4442.         setNPCDialogue(me.name, 1, "#quest", "You will need to speak with Fane, Matheus, Noff and Jocelyn. Ask them to report on their progress, then bring that information to me.")
  4443.         setNPCDialogue(me.name, 1, "#fane", "Fane can be found on the north side of town. You may visit him there.")
  4444.         setNPCDialogue(me.name, 1, "#matheus", "Matheus works as a smith in the merchant district to the north. I am particularly eager to here of his progress.")
  4445.         setNPCDialogue(me.name, 1, "#noff", "I believe Noff is currently in town. If so he is probably in the courtyard near the town gates.")
  4446.         setNPCDialogue(me.name, 1, "#jocelyn", "I don't know where Jocelyn is. You may need to enquire.")
  4447.         return "Good. You will need to speak with 4 people- Fane the merchant, Matheus the smith, Noff the farmer and Jocelyn the woodsman. Simply ask them about the report and return with their responses."
  4448.     else
  4449.         return "Then leave. I have no further use for you."
  4450.     end
  4451. end
  4452.  
  4453. --You can use in-text "#name", it will replace it with the player's name, and in-text "#class" for the player's class.
  4454. --Dialogues are flexible. They can be:
  4455.     --Strings: This will simply display the message as though the NCP is delivering it
  4456.     --Tables: Work the same as strings but will cycle through each message, giving user the ability to respond at the end of the conversation.
  4457.     --Functions: These can do... anything you like really. Usually used for talking with NPC's and simultaneously updating info (such as quests). Functions can return
  4458.         --Strings and tables, which work the same way as they do for typical dialogues, or nil, which will break out of the conversation immediately.
  4459.  
  4460. --Global dialogue is the core of most NPC's logic in conversation. This gives the most basic of functions, and therefore
  4461. --I recommend not playing with it too much. Information the entire world knows or specific functioanlity you want to add to
  4462. --convesations can be added here but I don't recommend messing with it too much.       
  4463. globalDialogue = {
  4464.     ["#name"] = function(me)
  4465.         local r = math.random(1,3)
  4466.         if r == 1 then return "You can call me "..me.name.."."
  4467.         elseif r == 2 then return "My name is "..me.name.."."
  4468.         elseif r == 3 then return "I am "..me.name.."." end
  4469.     end;
  4470.     ["#job"] = function(me)
  4471.         if me.job == "smith" then
  4472.             return "I'm a smith. I work a forge and produce weapons and armour. I can trade my inventory with you if you like."
  4473.         elseif me.job == "merchant" then
  4474.             return "I'm a merchant, buying goods from adventurers like yourselves and selling them to interested buyers. I'll trade good gold for any items you're carrying."
  4475.         elseif me.job == "mystic" then
  4476.             return "I'm a mystic, a trainer in the arts of magic. I can teach you spells, for a fee."
  4477.         else
  4478.             local article = "a"
  4479.             if isVowel(string.sub(me.job, 1, 1)) then article = "an" end
  4480.             return "I work as "..article.." "..me.job.."."
  4481.         end
  4482.     end;
  4483.     ["#hello"] = "#ongreet";
  4484.     ["#bye"] = "#onfarewell";
  4485.     ["#goodbye"] = "#onfarewell";
  4486.     ["#exit"] = "#onfarewell";
  4487.     ["#farewell"] = "#onfarewell";
  4488.     ["#leave"] = "#onfarewell";
  4489.     ["#work"] = "#quest";
  4490.     ["#quest"] = "I am in no need of your help, #name.";
  4491. }
  4492.  
  4493. --INDEX 1: NEBUN
  4494. --The Mad Town. Home to a series of eccentrics that worship false gods and sacrifice food, supplies and life
  4495. --to appease them.
  4496.  
  4497. town[1].dialogue = {
  4498.     ["#transylvania"] = "What would we know of the world around us? A pit of evil and darkness. We keep to our town, and ward off the dark as best as we can.";
  4499.     ["#nebun"] = "Our town, ruled by Donmur Petre. Fane and Matheus are our merchants, and Hemlar our mystic. Do not linger here long. We care not for strangers.";
  4500.     ["#groazagol"] = "I know nothing of that place, nor any other outside these walls.";
  4501.     ["#darkness"] = "#dark";
  4502.     ["#dark"] = "Forces of unimaginable evil are responsible for the shroud. But we will banish it. Our Lord has a plan.";
  4503.     ["#plan"] = "It is not to be discussed with outsiders.";
  4504.     ["#lake"] = "Our water is drawn from Lake Cavril, but it too is plagued with evil. We drink only once it is boiled.";
  4505.     ["#cemetary"] = "#graveyard";
  4506.     ["#graveyard"] = "We bury our dead at the mouth of the river, where life flows from the mountains to us.";
  4507.     ["#mountain"] = "The ranges to the south contain as much evil as they keep at bay.";
  4508.     ["#carpathian"] = "#mountain";
  4509.     ["#forest"] = "They extend for miles, in all directions.";
  4510. }
  4511.  
  4512. table.insert(town[1].npc, { name = "Helmar",
  4513.     job = "mystic",
  4514.     xpos = 13, ypos = 10,
  4515.     level = 1,
  4516.     dialogue = {
  4517.         ["#onfirstgreet"] = "What is this face, that comes before my tired eyes? Be ye demon or angel, or neither?";
  4518.         ["#ongreet"] = "Must the lights endlessly torment me, like fireflies over the grave?";
  4519.         ["#name"] = "Did you ask my name, or was it just the whisper of the wind? I shall answer neither, demon.";
  4520.         ["#demon"] = "You see what you are, what you shall become? Perhaps you come in charity or goodwill but you are just a tool of fate, a knot in the weave.";
  4521.         ["#job"] = "Silver light surrounds all, consumes my work and my thoughts. It is all I know, all I can do. I cannot help you...";
  4522.         ["#jocelyn"] = function(me)
  4523.             me.dialogue["#jocelyn"] = "I pray he has found peace... remember to bring him the tonic."
  4524.             activateQuest(quests["The Unwilling Mage"])
  4525.             printTownInterface()
  4526.             displayConfirmDialogue(me.name.." the "..me.job.." says:", "Jocelyn... the woodsman, I remember. He came to me, to speak, for he too has the gift, like I. He is a lunamagus.")
  4527.             printTownInterface()
  4528.             displayConfirmDialogue(me.name.." the "..me.job.." says:", "So terrified, and for good reason- the talent threatened to rip him apart, as it does to all cursed with it.")
  4529.             return "He fled northwest, into the woods. If you can, find him and bring him this tonic. It should ease his mind."
  4530.         end;
  4531.         ["#onfarewell"] = "Yes... let me rest my eyes, my poor eyes...";
  4532.     },
  4533.     useGeneral = false,
  4534.     nodialogue = "Dreams and riddles, mysteries and enigmas. Must they plague me?",
  4535. })
  4536.  
  4537. table.insert(town[1].npc, { name = "Fane",
  4538.     job = "merchant",
  4539.     xpos = 9, ypos = 6,
  4540.     level = 1,
  4541.     dialogue = {
  4542.         ["#onfirstgreet"] = "You're a new face- quite a relief to meet you! I am Fane, merchant here in Nebun.";
  4543.         ["#ongreet"] = "Greetings once again #name, how do you fare today? If you've got some extra goods on you I'd gladly trade.";
  4544.         ["#name"] = "I am Fane. Not sure how I came by the name.";
  4545.         ["#nebun"] = "Hostile to strangers isn't it? I'm an outsider as well- and it is no coincidence my business is poor.";
  4546.         ["#sell"] = "#trade";
  4547.         ["#jocelyn"] = "I wish I knew where he was, he was my only friend in Nebun. He left mysteriously some days ago- I last saw him speaking with the mystic, Helmar.";
  4548.         ["#plan"] = "I don't mean to be evasive stranger, but I fear the consequences should I divulge any detail. We all trust in Petre and the Gods to do what is right.";
  4549.         ["#trade"] = function(me)
  4550.             if #inventory == 0 then
  4551.                 return "You have nothing to sell? I'm afraid we can't do business then."
  4552.             else
  4553.                 printTownInterface()
  4554.                 displayConfirmDialogue(me.name.." the "..me.job.." says:", "Show me what you've come up with, I'll see what offers I can make.")
  4555.                 runTradeInterface(me, true)
  4556.                 return "Hope to see you again- come back if you find anything valuable."
  4557.             end
  4558.         end;
  4559.         ["#onfarewell"] = "Good travels to you, #name."
  4560.     },
  4561.     useGeneral = true,
  4562.     nodialogue = "I'm not sure what you're talking about, friend. Maybe someone else does?",
  4563.     inventory = { },
  4564.     priceModifier = 0.8,
  4565.     goldLimit = 50
  4566. })
  4567.  
  4568. table.insert(town[1].npc, { name = "Matheus",
  4569.     job = "smith",
  4570.     xpos = 2, ypos = 4,
  4571.     level = 1,
  4572.     dialogue = {
  4573.         ["#onfirstgreet"] = "A new arrival... you were allowed into the town? Well, I am Matheus, smith here." ;
  4574.         ["#ongreet"] = "You've returned... what is it you want?";
  4575.         ["#name"] = "My name is Matheus.";
  4576.         ["#job"] = "I have been smith here ten and five years now. I make iron and trade some of my inventory.";
  4577.         ["#father"] = "If you wish to remain in my good graces stranger, do not mention my father to me.";
  4578.         ["#jocelyn"] = "A fool, no doubt. He has fled some nights ago, though I do not know where to. Were it up to me, his head would adorn my shop window.";
  4579.         ["#buy"] = "#trade";
  4580.         ["#trade"] = function(me)
  4581.             if #me.inventory == 0 then
  4582.                 return "I have no stock to trade, stranger."
  4583.             else
  4584.                 printTownInterface()
  4585.                 displayConfirmDialogue(me.name.." the "..me.job.." says:", "Very well, but be quick. It would not do to be seen trading with an outsider.")
  4586.                 runTradeInterface(me, false)
  4587.                 return "Right. Take your purchases and please leave."
  4588.             end
  4589.         end;
  4590.         ["#onfarewell"] = "Good travels to you, #name."
  4591.     },
  4592.     useGeneral = true,
  4593.     nodialogue = "On this subject I know nothing.",
  4594.     inventory = { },
  4595.     priceModifier = 2.5,
  4596.     goldLimit = 0
  4597. })
  4598. table.insert(town[1].npc, { name = "Noff",
  4599.     job = "farmer",
  4600.     xpos = 25, ypos = 7,
  4601.     level = 1,
  4602.     dialogue = {
  4603.         ["#onfirstgreet"] = "Ah... you are not from here? Well... I am Noff. Greetings.";
  4604.         ["#ongreet"] = "Yes, #name, of course. You don't plan to stay long do you?";
  4605.         ["#name"] = "It's Noff. Can you get to the point?";
  4606.         ["#jocelyn"] = "He was here and now he is not. I neither know nor care where he is now.";
  4607.         ["#job"] = "I grow grain outside the city walls. Now if you'll excuse me...";
  4608.         ["#trade"] = "I do not trade with outsiders, you may find your own grain.";
  4609.         ["#onfarewell"] = "Until next time.";
  4610.     },
  4611.     nodialogue = "I have no idea. Now please, leave me.";
  4612. })
  4613. table.insert(town[1].npc, { name = "Kershel",
  4614.     job = "fool",
  4615.     xpos = 11, ypos = 14,
  4616.     level = 1,
  4617.     dialogue = {
  4618.         ["#onfirstgreet"] = "Ah another fool to arrive in a town already full. I am Kershel, this one's fool named.";
  4619.         ["#ongreet"] = "You return... what is it you wish of me?";
  4620.         ["#name"] = "I am known by many names, but best by Kershel.";
  4621.         ["#job"] = "My trade is my own, one characterized by ignorance, and rewarded with bliss.";
  4622.         ["#kershel"] = "The town fool, at your service.";
  4623.         ["#petre"] = "Though you may not yet know, stranger, you have wandered into a town of children, and Donmur Petre is the piper that will lead them to their doom.";
  4624.         ["#matheus"] = "A rolling stone that has gathered much moss.";
  4625.         ["#helmar"] = "Proof that the wisest men choose to be fools, in fear of too much wisdom.";
  4626.         ["#jocelyn"] = "An enigma is it not? Very few come to Nebun but fewer leave... someone must know his secret.";
  4627.         ["#fane"] = "A foreign man from a foreign land. Like you.";
  4628.         ["#plan"] = "Oh, word has reached your ear of Petre's plan? It too has reached mine. We can only pray it never comes to fruit.";
  4629.         ["#onfarewell"] = "Shall we meet again? Only time will tell...";
  4630.     },
  4631.     useGeneral = true,
  4632.     nodialogue = "A wise man asks questions, but a fool asks questions and doesn't listen."
  4633. })
  4634. table.insert(town[1].npc, { name = "Erika",
  4635.     job = "widow",
  4636.     xpos = 24, ypos = 14,
  4637.     level = 1,
  4638.     dialogue = {
  4639.         ["#onfirstgreet"] = "Oh please, sir, can you please help me?";
  4640.         ["#ongreet"] = "Please sir... please help me...";
  4641.         ["#help"] = "#quest";
  4642.         ["#name"] = "I am Erika, sir.";
  4643.         ["#job"] = "I... I was a faithful wife for many years, but now I am alone.";
  4644.         ["#joachim"] = "I don't know... I'm sorry.";
  4645.         ["#quest"] = function(me)
  4646.             printTownInterface()
  4647.             displayConfirmDialogue("Erika the window says:", "Must fate be so cruel to those of us who mean no harm? So fearful I was of starving this winter I asked my husband to hunt one eve a month ago.")
  4648.             local response = displayOptionDialogue("Erika the widow says:", "But I came upon him two weeks ago, little more than a skeleton! Please, please would you put my dear husband's bones to rest?")
  4649.  
  4650.             if response == "yes" then
  4651.                 me.dialogue["#ongreet"] = "Sir, you have returned from the mausoleum?"
  4652.                 me.dialogue["#onfarewell"] = "Farewell, kind sir."
  4653.                 activateQuest(quests["Laid to Rest"])
  4654.                 printTownInterface()
  4655.                 displayConfirmDialogue("Erika the widow says:", "Thank you sir! Please, take his remains to the mausoelum south east of here, where he may rest with his ancestors.")
  4656.                 return "It's not much, but take this map, to help you find your way. His bones need rest on the third floor."
  4657.             else
  4658.                 return "Please sir... I beg of you!"
  4659.             end
  4660.         end;
  4661.         ["#onfarewell"] = "Please don't leave... I beg of your help...";
  4662.     },
  4663.     useGeneral = true,
  4664.     nodialogue = "I don't know... I don't know what you speak of."
  4665. })
  4666. table.insert(town[1].npc, { name = "Donmur Petre",
  4667.     job = "ruler",
  4668.     xpos = 5, ypos = 14,
  4669.     level = 1,
  4670.     dialogue = {
  4671.         ["#onfirstgreet"] = "A new traveller to these lands. Know outsider, we do not customarily deal with your kind, and were it for the darkness, your head would be resting on a spike.";
  4672.         ["#ongreet"] = "So you have returned, not yet consumed by the darkness I see. I trust your reason to see me is important?";
  4673.         ["#dark"] = "This shroud has laid over our town for over a year, and the people have grown fearful, but there is yet hope. I have a plan. Perhaps you wish to help?";
  4674.         ["#job"] = "I am ruler of Nebun, priest of our church and ward from the forces of evil. You no doubt wish to ask if I have work for you?";
  4675.         ["#kershel"] = "Pay little heed to that fool. He entertains me from time to time, but speaks dangerous nonsense when free of my presence.";
  4676.         ["#Petre"] = "That is my name. What do you want?";
  4677.         ["#fane"] = "He is not from here. We have allowed him to stay, and ply his trade but he is not well liked.";
  4678.         ["#matheus"] = "Matheus has been smithy for this town for 5 and 10 years. I mourn the passing of his father, as we all do here.";
  4679.         ["#helmar"] = "He is a lunamagus, and his mind is well lost. But if you are patient you may gleam something of use from him.";
  4680.         ["#trade"] = "You shall find traders in our town- look for their storefronts in the north. Do not linger long however, lest you make the townsfolk nervous.";
  4681.         ["#onfarewell"] = "Return only if the need is urgent. We care not for strangers.";
  4682.         ["#plan"] = "This is something I, nor anyone here will discuss with you. Should you need to know, you will when the time is right.";
  4683.         ["#help"] = "#quest";
  4684.         ["#quest"] = onActivateReport;
  4685.         ["#report"] = "#quest";
  4686.     },
  4687.     useGeneral = true,
  4688.     nodialogue = "I know not what you speak of.",
  4689. })
  4690.  
  4691. --INDEX 2: GROAZAGOL
  4692. --The staging post for Lord Jeirbe's assault. Currently unimplemented
  4693.  
  4694. --INDEX 3: JOCELYN'S SHACK
  4695. --The hiding place of a frightened mage
  4696.  
  4697. table.insert(town[3].npc, { name = "Jocelyn",
  4698.     job = "woodsman",
  4699.     xpos = 17, ypos = 11,
  4700.     level = 1,
  4701.     dialogue = {
  4702.         ["#onfirstgreet"] = "#ongreet";
  4703.         ["#ongreet"] = "Why have you returned? I have nothing for you, please leave me to my fate. I fear being in any other's present, should I harm them...";
  4704.         ["#name"] = "What good is a name to me now, in a world where none can so much as be near me? I was once Joachim... but I am just an abomination now.";
  4705.         ["#job"] = "I was once a woodsman, and a fine one at that. But now I am little more than a rat in a warren, hiding from the world.";
  4706.         ["#lunamagus"] = "A gift, Helmar called it. To summon the light of the moon into one's body and project it as magic. The 'gift' has already take 2 lives.";
  4707.         ["#tonic"] = function(me)
  4708.             addXP(20)
  4709.             addGold(10)
  4710.             me.dialogue["#tonic"] = "It has helped to calm my nerves somewhat... thank you again for bringing it to me."
  4711.             me.dialogue["#ongreet"] = "Ah... #name, hello again. What can this poor lunamagus do for you?"
  4712.             return "One of Helmar's tonics? He is a kind man, and he truly understands my troubles. Thank you- please take this as thanks."
  4713.         end
  4714.     },
  4715.     useGeneral = false,
  4716.     nodialogue = "I can't answer your question.",
  4717. })
  4718.  
  4719. --INDEX 4: MOUNTAIN CAMP
  4720. --The epilogue for the game
  4721.  
  4722. table.insert(town[4].npc, { name = "Nestaayha",
  4723.     job = "stranger",
  4724.     xpos = 17, ypos = 11,
  4725.     level = 1,
  4726.     dialogue = {
  4727.         ["#onfirstgreet"] = function(me)
  4728.             printTownInterface()
  4729.             displayConfirmDialogue("A stranger appears", "You're not sure what brought you here, but there is a small man, dressed in black, standing outside a campfire.")
  4730.             printTownInterface()
  4731.             displayConfirmDialogue("Nestaayha says:", "So you have found me at last, no idea why I suppose... no matter, of course this will all come in time. You have no idea the role in which you are cast?")
  4732.             printTownInterface()
  4733.             displayConfirmDialogue("Nestaayha says:", "You have helped the fool Petre with his idiotic plan to dispel the clouds over the Darklands- see how he fruitlessly attempts to stop it!")
  4734.             printTownInterface()
  4735.             displayConfirmDialogue("You can see:", "Staring through the pitch you can just make out Nebun- it is alight! The town has been set on fire!")
  4736.             printTownInterface()
  4737.             displayConfirmDialogue("Nestaayha says:", "This is no simple darkness... this is a curse. An insidious, evil spell created by one of the blackest creatures in Transylvania. I am his advisor.")
  4738.             printTownInterface()
  4739.             displayConfirmDialogue("Nestaayha says:", "You shall come to know me in time... but for now you must be patient. Leave this place, and watch the skies for a sign. When the weave is ready for you, you will know.")
  4740.             printTownInterface()
  4741.             gameOver = true
  4742.             return "#farewell"
  4743.         end;
  4744.         ["#onfarewell"] = "This concludes the Darklands: Tales from Transylvania demo. Hope you enjoyed it! -NF"
  4745.     }
  4746. })
  4747. MKFIL rpg/scripts/quest
  4748. WRITE 335
  4749. --Quest 1: The Report - gather information regarding the progress of Petre's grand plan
  4750. quests["The Report"] = { name = "The Report",
  4751.     activestage = 1,
  4752.     variables = { fane = false, matheus = false, noff = false, jocelyn = false, grainActivated = false},
  4753.     generalDescription = "Donmur Petre has asked you gather information regarding his secret plan",
  4754.     stages = {
  4755.         [1] = {
  4756.             desc = "Ask for a report from the following people: Fane Matheus Noff Jocelyn",
  4757.             condition = function(quest, event)
  4758.                 local retstring = nil
  4759.                 if event.type == "dialogue" and event.town == 1 and event.npc.name == "Matheus" and findIn(event.topic, "report")
  4760.                         and not quest.variables.matheus then
  4761.                     setNPCDialogue("Matheus", 1, "#report", "You have my response. Return it to Petre, if you have not already.")
  4762.                     quest.variables.matheus = true
  4763.                     retstring = "You are collecting the report? Well you may inform Petre that I have met expectations, and the equipment he requires is almost complete."
  4764.                 end
  4765.                 if event.type == "dialogue" and event.town == 1 and event.npc.name == "Fane" and findIn(event.topic, "report") and
  4766.                         not quest.variables.fane then
  4767.                     setNPCDialogue("Fane", 1, "#report", "I hope Petre is satisfied with my progress.")
  4768.                     quest.variables.fane = true
  4769.                     printTownInterface()
  4770.                     displayConfirmDialogue("Fane the merchant says:", "Petre has trusted you with this task? I am surprised- it is something he would not usually so much as discuss with an outsider...")
  4771.                     retstring = "You may inform him that my accounts are in order and my earnings are... sufficient, if a little below expectations. I will send him a full account soon."
  4772.                 end
  4773.                 if event.type == "dialogue" and event.town == 3 and event.npc.name == "Jocelyn" and findIn(event.topic, "report") and
  4774.                         not quest.variables.jocelyn then
  4775.                     quest.variables.jocelyn = true
  4776.                     setNPCDialogue("Jocelyn", 3, "#report", "Still you pester me with this? I have nothing for Petre, nor shall I. He can find another unwitting soul to fulfill his lunacy.")
  4777.                     return "Petre's report? You may tell him I am no longer woodsman of his town, in fact you may tell him I am dead, or as good as. He shall receive no aid from me."
  4778.                 end
  4779.                 if event.type == "dialogue" and event.town == 1 and event.npc.name == "Noff" and findIn(event.topic, "report") and
  4780.                         not quest.variables.noff then
  4781.                     if not quest.variables.grainActivated then
  4782.                         printTownInterface()
  4783.                         displayConfirmDialogue("Noff the farmer says:", "Oh, you're here about that... there is a small problem. I have reached expectations, but a great deal of my grain has been stolen, by Tyrats...")
  4784.                         local response = displayOptionDialogue("Noff the farmer says:", "Of course, if you would be willing to find their lair and retrieve my grain... that would be a great help to me.")
  4785.                         if response == "yes" then
  4786.                             retstring = "Ah... what a relief. The tyrats took it somewhere to the west. Those cave-dwelling fools will pay for their larceny."
  4787.                             activateQuest(quests["Fuel for the Pyre"])
  4788.                             quest.variables.grainActivated = true
  4789.                         else
  4790.                             retstring = "Well without your help I cannot complete my report for Petre. So it is up to you."
  4791.                         end
  4792.                     elseif quests["Fuel for the Pyre"].activestage == -1 then
  4793.                         quest.variables.noff = true
  4794.                         setNPCDialogue("Noff", 1, "#report", "I have nothing more to say on the matter. Take the information to Petre, and quickly.")
  4795.                         retstring = "Yes, the report. Well thanks to you I am at least somewhat prepared for the report. You may give Petre this- my current reserves."
  4796.                     else
  4797.                         retstring = "I can't discuss my progress without that grain. Please find it."
  4798.                     end
  4799.                 end
  4800.                
  4801.                 local newdesc = "Ask for a report from the following people:"
  4802.                 if not quest.variables.fane then newdesc = newdesc.." Fane" end
  4803.                 if not quest.variables.matheus then newdesc = newdesc.." Matheus" end
  4804.                 if not quest.variables.noff then newdesc = newdesc.." Noff" end
  4805.                 if not quest.variables.jocelyn then newdesc = newdesc.." Jocelyn" end
  4806.                 quest.stages[1].desc = newdesc
  4807.                
  4808.                 if quest.variables.fane and quest.variables.matheus and quest.variables.noff and quest.variables.jocelyn then
  4809.                     quest.activestage = 2
  4810.                     printTownInterface()
  4811.                     displayConfirmDialogue(event.npc.name.." the "..event.npc.job.." says:", retstring)
  4812.                     retstring = "I think you've spoken to everyone now- you should return your report to Donmur Petre."
  4813.                 end
  4814.                
  4815.                 return retstring
  4816.             end
  4817.         }, [2] = {
  4818.             desc = "Return to Donmur Petre with the completed report.",
  4819.             condition = function(quest, event)
  4820.                 if event.type == "dialogue" and event.town ==  1 and event.npc.name == "Donmur Petre" and findIn(event.topic, "report", "quest") then
  4821.                     quest.activestage = -1
  4822.                     addXP(150)
  4823.                     addGold(20)
  4824.                     setNPCDialogue("Donmur Petre", 1, "#report", "Thank you for returning the report to me.")
  4825.                     setNPCDialogue("Donmur Petre", 1, "#quest", function(me)
  4826.                         printTownInterface()
  4827.                         displayConfirmDialogue(me.name.." the "..me.job.." says:", "Part of my plan calls for a specific kind of plant that grows in the caves near here. It is hazardous to handle, but very useful.")
  4828.                         printTownInterface()
  4829.                         local response = displayOptionDialogue(me.name.." the "..me.job.." says:", "Would you consider visiting Lichen cave to collect it?")
  4830.                         if response == "yes" then
  4831.                             activateQuest(quests["The Empty Larder"])
  4832.                             setNPCDialogue("Donmur Petre", 1, "#lichen", "#quest")
  4833.                             setNPCDialogue("Donmur Petre", 1, "#quest", "Please make haste in collecting the lichen. It grows in a cave east of here.")
  4834.                             return "Good. Go to Lichen Cave on the river a bit east of here, and gather up as much as you can. Just be careful with it..."
  4835.                         else
  4836.                             return "Very well. If you do not wish to help, I would ask you leave our town. There is no place here for those who cannot work."
  4837.                         end
  4838.                     end)
  4839.                    
  4840.                     return "It sounds as though all is going according to plan. I thank you, stranger. If you insist on staying I have another task for you."
  4841.                 end
  4842.             end
  4843.         }
  4844.     }
  4845. }
  4846.  
  4847. --Quest 2: The Unwilling Mage - find Jocelyn, the woodsman
  4848. quests["The Unwilling Mage"] = { name = "The Unwilling Mage",
  4849.     activestage = 1,
  4850.     variables = { },
  4851.     generalDescription = "Helmar the mystic has told you that Jocelyn fled upon learning he was a lunamagus.",
  4852.     onActivation = function(quest)
  4853.         town[3].visible = true
  4854.     end;
  4855.     stages = {
  4856.         [1] = {
  4857.             desc = "Find Jocelyn, in the woods to the northwest of Nebun",
  4858.             condition = function(quest, event)
  4859.                 if event.type == "dialogue" and event.npc.name == "Jocelyn" then
  4860.                     quest.activestage = -1
  4861.                     addXP(50)
  4862.                     printTownInterface()
  4863.                     displayConfirmDialogue("Quest Update!", "You have found a man, sitting on a tree stump and staring at the ground morosely.")
  4864.                     return "You have found me? Helmar has sent you? Well, here I am. I am Jocelyn, the lunamagus... the accursed lunamagus. And you have found me, it seems."
  4865.                 end
  4866.             end
  4867.         }
  4868.     }
  4869. }
  4870.  
  4871. --Quest 3: Fuel for the Pyre - Gather stolen grain from a nearby cave
  4872. quests["Fuel for the Pyre"] = { name = "Fuel for the Pyre",
  4873.     activestage = 1,
  4874.     variables = {
  4875.         { x = 3+2, y = 3+2, level = 1, message = "This cave has been freshly dug- the scratches are new and the sweaty Tyrat stench has yet to take a firm hold." },
  4876.         { x = 7+2, y = 1+2, level = 1, message = "In the darkness you can hear the sounds of twittering and the clink of metal- the tunnel is not empty." }
  4877.     },
  4878.     generalDescription = "Several bags of grain have been stolen from Noff by Tyrats.",
  4879.     onActivation = function(quest)
  4880.         subDungeonTile(2, 1+2, 3+2, 1, "C")
  4881.         table.insert(dungeons[2].creatures, cloneCreature(3, 11+2, 2+2, 1))
  4882.         table.insert(dungeons[2].creatures, cloneCreature(3, 11+2, 6+2, 1))
  4883.         table.insert(dungeons[2].creatures, cloneCreature(3, 6+2, 7+2, 1))
  4884.         table.insert(dungeons[2].creatures, cloneCreature(3, 1+2, 4+2, 1))
  4885.     end;
  4886.     stages = {
  4887.         [1] = {
  4888.             desc = "Find Fell's Cave, east of Nebun",
  4889.             condition = function(quest, event)
  4890.                 if event.type == "move" and event.dungeon == 2 then
  4891.                     quest.activestage = 2
  4892.                     return "The sign of pickaxe scratchings and discarded metal strewn about this cave marks it as a lair for Tyrats. This must be the cave Noff spoke of. You take deep breath... and step inside."
  4893.                 end
  4894.             end
  4895.         },
  4896.         [2] = {
  4897.             desc = "Find the missing grain in Fell's Cave",
  4898.             condition = function(quest, event)
  4899.                 local chestLocation = { x = 1+2, y = 3+2, z = 1 }
  4900.                 if event.type == "move" and event.dungeon == 2 then
  4901.                     for i,v in ipairs(quest.variables) do
  4902.                         if event.xpos == v.x and event.ypos == v.y and event.zpos == v.level then
  4903.                             table.remove(quest.variables, i)
  4904.                             return v.message
  4905.                         end
  4906.                     end
  4907.                 end
  4908.                
  4909.                 if event.type == "chest" and event.dungeon == 2 and event.xpos == chestLocation.x and event.ypos == chestLocation.y
  4910.                         and event.zpos == chestLocation.z then
  4911.                     quest.activestage = 3
  4912.                     addGold(15)
  4913.                     return "Within the chest you find several large sacks of grain, along with a few coins. You gather them up, and prepare to head back to Noff."
  4914.                 end
  4915.             end
  4916.         },
  4917.         [3] = {
  4918.             desc = "Return the grain sacks to Noff in Nebun.",
  4919.             condition = function(quest, event)
  4920.                 if event.type == "dialogue" and event.town == 1 and event.npc.name == "Noff" and findIn(event.topic, "grain", "sack") then
  4921.                     quest.activestage = -1
  4922.                     addGold(20)
  4923.                     addXP(40)
  4924.                     return "Ah you found my grain. Well, thank you, stranger. This has taken a weight off my mind."
  4925.                 end
  4926.             end
  4927.         }
  4928.     }
  4929. }
  4930.  
  4931. --Quest 4: Laid to Rest - Leave the bones of a loved one in the local mausoleum
  4932. quests["Laid to Rest"] = { name = "Laid to Rest",
  4933.     activestage = 1,
  4934.     variables = {
  4935.         { x = 4+2, y = 10+2, level = 1, message = "It appears as though there was once a way down here, but the way has since collapsed in. You wonder how safe these halls still are." },
  4936.         { x = 20+2, y = 9+2, level = 2, message = "The second part of the mausoleum is much less organized, with the stone passageways sprawling almost randomly." },
  4937.         { x = 8+2, y = 2+2, level = 3, message = "All here is deathly still. The air has no feel, and all you can smell is the musty scent of damp earth. It is pitch black." }
  4938.     },
  4939.     generalDescription = "Erika has asked you lay the bones of her deceased husband to rest in a nearby mausoleum.",
  4940.     onActivation = function(quest)
  4941.         dungeons[3].playerHasMap = true
  4942.     end;
  4943.     stages = {
  4944.         [1] = {
  4945.             desc = "Enter the Nebun Mausoleum, southeast of Nebun.",
  4946.             condition = function(quest, event)
  4947.                 if event.type == "move" and event.dungeon == 3 then
  4948.                     quest.activestage = 2
  4949.                     return "Shrouded in dust and grime, you manage to find the ancient stone passageway leading into the crypt. You feel greatly uneasy as you step inside."
  4950.                 end
  4951.             end
  4952.         },
  4953.         [2] = {
  4954.             desc = "Reach the third level of the mausoleum",
  4955.             condition = function(quest, event)
  4956.                 local corpseLocation = { x = 11+2, y = 3+2, z = 3 }
  4957.                 if event.type == "move" and event.dungeon == 3 then
  4958.                     for i,v in ipairs(quest.variables) do
  4959.                         if event.xpos == v.x and event.ypos == v.y and event.zpos == v.level then
  4960.                             table.remove(quest.variables, i)
  4961.                             return v.message
  4962.                         end
  4963.                     end
  4964.                 end
  4965.                
  4966.                 if event.type == "move" and event.dungeon == 3 and event.xpos == corpseLocation.x and event.ypos == corpseLocation.y
  4967.                         and event.zpos == corpseLocation.z then
  4968.                     quest.activestage = 3
  4969.                     dungeons[3].creaturesSpawn = false
  4970.                     displayConfirmDialogue("Quest Update", "You find an empty spot at last, and carefully place the remains Erika gave you into the space.")
  4971.                     local wraith = cloneCreature(5, 11+2, 2+2, 3)
  4972.                     table.insert(dungeons[3].creatures, wraith)
  4973.                     quest.variables.wraith = wraith
  4974.                     return "Just as you do, you feel a freezing wind blow past you- the bones rise from their resting place and reassemble themselves into a mosnterous apparition!"
  4975.                 end
  4976.             end
  4977.         },
  4978.         [3] = {
  4979.             desc = "Slay the undead Wraith",
  4980.             condition = function(quest, event)
  4981.                 if event.type == "kill" and event.monster == quest.variables.wraith then
  4982.                     quest.activestage = 4
  4983.                     addGold(50)
  4984.                     return "Your final blow leaves blows the wraith to pieces, leaving nothing but a soft mist in the air. The darkness that consumes this places seems to have lifted."
  4985.                 end
  4986.             end
  4987.         },
  4988.         [4] = {
  4989.             desc = "Speak with Erika in Nebun",
  4990.             condition = function(quest, event)
  4991.                 if event.type == "dialogue" and event.town == 1 and event.npc.name == "Erika" and findIn(event.topic, "ongreet") then
  4992.                     quest.activestage = -1
  4993.                     setNPCDialogue("Erika", 1, "#onfarewell", "May you spend eternity in hell, you foolish do-gooder!")
  4994.                     printTownInterface()
  4995.                     displayConfirmDialogue("Erika the widow says:", "You fool! You've dispelled the curse I cast on that mausoleum- how can you still be alive! Everything is ruined!")
  4996.                     printTownInterface()
  4997.                     displayConfirmDialogue("Quest Update:", "You watch as Erika slowly turns into pure silver light, before disappearing in a flash, with only the whispers of her voice in the wind.")
  4998.                     setNPCPosition("Erika", 1, -1, -1)
  4999.                     return "#onfarewell"
  5000.                 end
  5001.             end
  5002.         }
  5003.     }
  5004. }
  5005.  
  5006. --Quest 5: The Empty Larder - Collect Lichen from a nearby cave
  5007. quests["The Empty Larder"] = { name = "The Empty Larder",
  5008.     activestage = 1,
  5009.     variables = {
  5010.         { x = 9+2, y = 2+2, level = 3, message = "Although the chest is empty, you can see the green lichen Petre mentioned growing around the edges. You gather it into your pack." },
  5011.         { x = 4+2, y = 4+2, level = 2, message = "Although the chest is empty, you can see the green lichen Petre mentioned growing around the edges. You gather it into your pack." },
  5012.         { x = 9+2, y = 2+2, level = 1, message = "Although the chest is empty, you can see the green lichen Petre mentioned growing around the edges. You gather it into your pack." }
  5013.     },
  5014.     generalDescription = "Donmur Petre has asked you to collect some special lichen from a nearby caves.",
  5015.     onActivation = function(quest)
  5016.         dungeons[3].playerHasMap = true
  5017.     end;
  5018.     stages = {
  5019.         [1] = {
  5020.             desc = "Find 3 good samples of green lichen from Lichen Cave.",
  5021.             condition = function(quest, event)
  5022.                 if event.type == "chest" and event.dungeon == 4 then
  5023.                     for i,v in ipairs(quest.variables) do
  5024.                         if event.xpos == v.x and event.ypos == v.y and event.zpos == v.level then
  5025.                             table.remove(quest.variables, i)
  5026.                             if #quest.variables == 0 then
  5027.                                 quest.activestage = 2
  5028.                                 table.insert(quest.variables, "steppedOutside", false)
  5029.                                 displayConfirmDialogue("Quest Update:", v.message)
  5030.                                 return "You have a good collection of lichen now- you should return to Donmur Petre to collect your reward."
  5031.                             else
  5032.                                 quest.stages[1].desc = "Find "..#quest.variables.." good samples of green lichen from Lichen Cave."
  5033.                                 return v.message
  5034.                             end
  5035.                         end
  5036.                     end
  5037.                 end
  5038.             end
  5039.         },
  5040.         [2] = {
  5041.             desc = "Return the supplies to Donmur Petre",
  5042.             condition = function(quest, event)
  5043.                 if event.type == "move" and not event.town and not event.dungeon and not quest.variables["steppedOutside"] then
  5044.                     quest.stages[2].desc = "Return empty-handed to Donmur Petre"
  5045.                     quest.variables["steppedOutside"] = true
  5046.                     return "Just as you take your first step outside you hear a bang- you pack has exploded! You rush to put it out, but are unable to save the lichen, which seemed to combust as soon as it entered daylight!"
  5047.                 elseif event.type == "dialogue" and event.town == 1 and event.npc.name == "Donmur Petre" and findIn(event.topic, "lichen", "quest") then
  5048.                     quest.activestage = -1
  5049.                     setNPCDialogue("Donmur Petre", 1, "#lichen", "Yes it's too bad about the lichen... it can be somewhat volatile in sunlight.")
  5050.                     setNPCDialogue("Donmur Petre", 1, "#quest", function(me)
  5051.                         printTownInterface()
  5052.                         displayConfirmDialogue(me.name.." the "..me.job.." says:", "As you seem so adept at surviving impossible odds, perhaps you would like to help us retrieve a very important ornamental artefact for our town?")
  5053.                         printTownInterface()
  5054.                         local response = displayOptionDialogue(me.name.." the "..me.job.." says:", "It's in a secret cave, filled with treasure to the east of here. Are you willing to help us?")
  5055.                         if response == "yes" then
  5056.                             activateQuest(quests["An Impossible Errand"])
  5057.                             setNPCDialogue("Donmur Petre", 1, "#mace", "#quest")
  5058.                             setNPCDialogue("Donmur Petre", 1, "#quest", "Fargo's Place is east, past the river. You'll find the mace in there.")
  5059.                             return "Excellent! I'll mark Fargo's Place on your map- the cave lies east of here. The object is an ornamental mace. Good hunting..."
  5060.                         else
  5061.                             return "Very well. If you do not wish to help, I would ask you leave our town. There is no place here for those who cannot work."
  5062.                         end
  5063.                     end)
  5064.                     printTownInterface()
  5065.                     displayConfirmDialogue("Donmur Elenko the ruler says:", "The lichen exploded? And you survived? Well... that is unfortunate.")
  5066.                     displayConfirmDialogue("Donmur Elenko the ruler says:", "What was before an insistence is now an order. Our plan is now ready to execute and we shall not have you interfere. You are hereby banished from Nebun, never to return.")
  5067.                     printTownInterface()
  5068.                     displayConfirmDialogue("Quest Update!", "With the snap of his fingers, Elenko orders two men to collect you and throw you from the town, closing the town gate behind you.")
  5069.                     pox = town[1].xpos
  5070.                     poy = town[1].ypos - 2
  5071.                     overworld[town[1].ypos-1][town[1].xpos] = "#"
  5072.                     inTown = false
  5073.                     activeTown = 0
  5074.                     printOverworldInterface()
  5075.                     printWorldMap()
  5076.                     town[4].visible = true
  5077.                     setNPCDialogue("Donmur Petre", 1, "#onfarewell", "You are banished. Be gone from here. There is naught left for you.")
  5078.                     return "#farewell"
  5079.                 end
  5080.             end
  5081.         }
  5082.     }
  5083. }
  5084. MKFIL rpg/scripts/script
  5085. WRITE 220
  5086. --[[
  5087.         Darklands: Tales from Transylvania
  5088.        
  5089.         An undead invasion threatens the kingdom of Wallachia- as a Moldavan outlander, you must find and dispel the
  5090.         dark curse from the land.
  5091.        
  5092.         This file contains the header, onInit methods and any specific features you want your game to hvae that are not
  5093.         provided in the standard RPG- overwrites of the interface etc.
  5094. ]]--
  5095.  
  5096. --Note: throughout the script several "print" statements have been scattered to demonstrate progress.
  5097. --These are nonessential, but useful for error detection.
  5098.  
  5099. --A simple single town, with a single quest giver and two storefronts, as well as one random NPC.
  5100. --This demonstrates the primary features of writing a town script, and a simple dungeon with quest queues.
  5101.  
  5102. local function debugDisplay(str)
  5103.     term.setCursorPos(1,1)
  5104.     print("Debug: "..str)
  5105.     os.pullEvent("key")
  5106. end
  5107.  
  5108. --This makes a few little changes- hides certain towns for example
  5109. local function modifyWorld()
  5110.     town[2].visible = false
  5111.     town[3].visible = false
  5112.     town[4].visible = false
  5113.     dungeons[1].visible = false
  5114. end
  5115.  
  5116. local function selectClass()
  5117.     term.clear()
  5118.    
  5119.     local str = "From what vocation do you hail, "..charName.."?"
  5120.     term.setCursorPos(w/2 - #str/2, 3)
  5121.     term.write(str)
  5122.    
  5123.     list = {"Squire", "Urchin", "Journeyman"}
  5124.     local sel = 1
  5125.    
  5126.     while true do
  5127.         for i=1,#list do
  5128.             term.setCursorPos(w/2 - #list[i]/2 - 2, 3 + i * 3)
  5129.             term.clearLine()
  5130.             if sel == i then term.write("[ "..list[i].." ]")
  5131.             else term.write("  "..list[i].."  ") end
  5132.         end
  5133.        
  5134.         local id,key = os.pullEvent("key")
  5135.         if key == keys.up and sel > 1 then sel = sel - 1
  5136.         elseif key == keys.down and sel < #list then sel = sel + 1
  5137.         elseif key == keys.space or key == keys.enter then
  5138.             charClass = list[sel]
  5139.             break
  5140.         end
  5141.     end
  5142.    
  5143.     --All players start with rough robes and sandals
  5144.     local robes = cloneItem(6)
  5145.     robes.enchantment = cloneEnchantment("rough")
  5146.     local sandals = cloneItem(7)
  5147.     table.insert(inventory, robes)
  5148.     table.insert(inventory, sandals)
  5149.    
  5150.     term.clear()
  5151.     term.setCursorPos(1, 2)
  5152.     if sel == 1 then
  5153.         --Squires are peniless but well equipped by their master
  5154.         table.insert(inventory, cloneItem(3))
  5155.         table.insert(inventory, cloneItem(5))
  5156.         charGold = 0
  5157.         print(
  5158.             [[You have spent most of your natural life sworn to the service of the cruel Turkish defector, Lord Akalay. Bitter and tyrannical to his wards, you spend your days in hard training, and your nights at his beck and call. His drunken temper has left you with many scars.
  5159.            
  5160.             When word reached you that Lord Jeirbe was looking for volunteers for a solitary scouting mission, you ran as quickly as you could to beg his audience.]])
  5161.     elseif sel == 2 then
  5162.         --Urchins have a good weapon but little coin to their name
  5163.         local dagger = cloneItem(1)
  5164.         dagger.enchantment = cloneEnchantment("sharp")
  5165.         table.insert(inventory, dagger)
  5166.         charGold = 5
  5167.         print(
  5168.             [[You've spent your entire life living in the streets of Bucharest, scavenging what little food you can through theft and begging. By chance, you heard word a war party passing through on its way to Transylvania. Hoping to pick from the bones left by the soldiers, you followed in secret.
  5169.            
  5170.             For two weeks you lived well, sleeping in caves and trees and even picking up a few lost coins by the soldiers, but one evening you got too bold, and were caught pickpocketing a night guard.
  5171.            
  5172.             After a short time as prisoner, Lord Jeirbe approached you personally, with an offer for your freedom.]])
  5173.     elseif sel == 3 then
  5174.         --Journeymen have pathetic weapons but much more money
  5175.         local staff = cloneItem(2)
  5176.         staff.enchantment = cloneEnchantment("rotting")
  5177.         table.insert(inventory, staff)
  5178.         charGold = 30
  5179.         print(
  5180.             [[As an alchemist in training, you have been sent by your guild to set up a small herbarium at the foot of the Carpathian mountain range. There you have spent 3 months, earning a few pieces of gold and plying your peaceful trade.
  5181.            
  5182.             This came to an abrupt end when the war party stormed through your hut on their way north, robbing you of most of your possessions and forcing you to join their ranks. You remained unwilling prisoner in the garrison, treating the other soldiers until today- Lord Jeirbe has summoned you to speak with him.]])
  5183.     end
  5184.    
  5185.     for i=1,#inventory do
  5186.         equipItem(i)
  5187.     end
  5188.    
  5189.     term.setCursorPos(1, h)
  5190.     term.write("Type any key to continue")
  5191.     os.pullEvent("key")
  5192.    
  5193.     term.clear()
  5194.     wprintOffCenter("This is the demo of Darklands: Tales from Transylvania.", 3, w, 0)
  5195.     wprintOffCenter("You should start by heading to Nebun, a small village north of here.", 6, w, 0)
  5196.     wprintOffCenter("Good luck...", 8, w, 0)
  5197.     term.setCursorPos(1, h)
  5198.     term.write("Type any key to continue")
  5199.     os.pullEvent("key")
  5200.     sleep(1)
  5201. end
  5202.        
  5203. local function onDebugInit()
  5204.     shell.run("clear")
  5205.     print("Type a key to begin debug session")
  5206.    
  5207.     table.insert(inventory, cloneItem(6))
  5208.     table.insert(inventory, cloneItem(7))
  5209.     table.insert(inventory, cloneItem(2))
  5210.     charName = "Admin"
  5211.     charClass = "Debugger"
  5212.     worldname = "The Darklands"
  5213.     debugging = true
  5214.    
  5215.     for i=1,#inventory do
  5216.         equipItem(i)
  5217.     end
  5218.    
  5219.     charGold = 100
  5220.     pox = 32
  5221.     poy = 15
  5222.    
  5223.     modifyWorld()
  5224.     activateQuest(quests["The Empty Larder"])
  5225.     quests["The Empty Larder"].activestage = 2
  5226.    
  5227.     os.pullEvent("key")
  5228. end
  5229.        
  5230. function onInit()
  5231.     local darkSword = {
  5232.         "   /\\",
  5233.         "   ||",
  5234.         "   ||",
  5235.         "   ||",
  5236.         "   ||",
  5237.         "   ||",
  5238.         "   /\\",
  5239.         "o======o",
  5240.         "   ||",
  5241.         "   ||",
  5242.         "   __"
  5243.     }
  5244.    
  5245.     term.clear()
  5246.     sleep(1)
  5247.    
  5248.     for i=1,#darkSword do
  5249.         printLeft(darkSword[i], 2+i, w/2 - #darkSword[8]/2 - 1)
  5250.     end
  5251.    
  5252.     sleep(1.5)
  5253.     term.setCursorPos(w/2 - 9/2, h-4)
  5254.     textutils.slowPrint("DarkLands")
  5255.     sleep(1)
  5256.     printOffCenter("Tales from Transylvania", h-3, w, 0)
  5257.     printOffCenter("Press a key to begin", h-1, w, 0)
  5258.     os.pullEvent(key)
  5259.    
  5260.     term.clearLine()
  5261.    
  5262.     printLeft("Enter your name, hero: ", h-1, 2, 0)
  5263.     term.setCursorBlink(true)
  5264.     setCharName(io.read())
  5265.     worldname = "The Darklands"
  5266.     term.setCursorBlink(false)
  5267.    
  5268.     --The generic story preface is told here.
  5269.    
  5270.     term.clear()
  5271.     sleep(1)
  5272.     term.setCursorPos(1, 1)
  5273.     print("It is 1430.\n")
  5274.     print(
  5275.         [[Seeking to relieve the strain from a lengthy war with Moldava and widespread famines in the south, the king of Wallachia ordered an invasion on Transylvania. The warlords of Transylvania, often as busy fighting each other as foreign invaders are considered by His Grace a means to relieve the strain on the armed forces, and restore faith in the monarchy.
  5276.        
  5277.         In November of that month, an army of 300 men assembled at the base of the Carpathian mountain ranges, one of the most trecherous stretches of land in Europe. Two weeks of bitter cold and snowstorms, with limited rations and low morale, they finally came upon a keep within the mountains.]])
  5278.     term.setCursorPos(1, h)
  5279.     term.write("Type any key to continue")
  5280.     os.pullEvent("key")
  5281.    
  5282.     term.clear()
  5283.     term.setCursorPos(1, 2)
  5284.     print(
  5285.         [[The abandoned keep, Groazagol, afforded a view over the southern reaches of the country, but the land, in day and night was shrouded in an impenetrable darkness. It was not long before rumours spread through the garrison, of evil sweeping the country side- the black death, horsemen of the apocalypse, or some other sinister force of evil.
  5286.        
  5287.         The commander general of the Wallachian forces Lord Jeirbe, fearing his men would mutiny or flee at an order to trave into the abyss, called for a single scout to map the underlying country, provide insight into what was happening, and inform the army on their next move.]])
  5288.     term.setCursorPos(1, h)
  5289.     term.write("Type any key to continue")
  5290.     os.pullEvent("key")
  5291.    
  5292.     --The player selects their class here:
  5293.    
  5294.     selectClass()
  5295.    
  5296.     pox = 29
  5297.     poy = 25
  5298.    
  5299.     modifyWorld()
  5300. end
  5301.  
  5302. --Comment out this line as needed.
  5303. --onInit = onDebugInit
  5304.  
  5305. return true
  5306. MKFIL rpg/scripts/towns
  5307. WRITE 68
  5308. TOWN "Nebun" 37,16,27,9
  5309.                         \ /
  5310. \__/\__/\__/\__/\__/\__/\| |
  5311. |------|-----|------|____| |
  5312. |      |     |      |     ^  
  5313. |  ____|_/ \ |O_/ \_|     |  
  5314. |/ \_O_|   |_|            ^
  5315.                          |  
  5316.     |----|------|        |--
  5317.     |____|------|          
  5318.          |_  _O_|        |--
  5319.                          ^
  5320. /\_. ._/\                 |  
  5321. ||     ||  |-- -O-|-- ---,^.
  5322. ||     ||  |      |      \ /
  5323. \__/\__/\_ /\__/\__/\__/\| |
  5324.                         | |
  5325. TOWN "Groazagol" 27,25,27,9
  5326. www]     /        \     [www]
  5327. | |  |--/  /\  /\  \--|  | |
  5328. | |_-|                |_-| |
  5329. | |--|________________|--| |
  5330. |||  |                |  |||  
  5331. |   |____|      |____|   | /
  5332. |        |      |        |o  
  5333. |            /|\         \
  5334. |           / | \          
  5335. |     /|\                /o
  5336. |    / | \     /|\       | o
  5337. www]           / | \    [www]
  5338. | |                      | |
  5339. | |-_-_-_-_-_-_-_-_-_-_-_| |
  5340. | |----------------------| |
  5341. | |                      | |
  5342. TOWN "Jocelyn's Shack" 6,7,16,15
  5343.            /__  __\        
  5344.               ||            
  5345.         /\                  
  5346.    /\  /  \                
  5347.   /  \/_  _\        /\      
  5348.  /    \ || ____    /  \  
  5349. /__  __\  /    \  /_  _\    
  5350.    ||    /______\   ||      
  5351.     /\   |      |    /\    
  5352.    /  \  |_  _O_|   /  \    
  5353.   /_  _\           /    \
  5354.     ||            /__  __\  
  5355.         /\           ||    
  5356.        /  \                
  5357.       /_  _\ o o            
  5358.         ||   | |            
  5359. TOWN "Mountain Camp", 57,9,16,15
  5360.                            
  5361.                            
  5362.                            
  5363.           ______            
  5364.          /######\          
  5365.  _______/########\_______
  5366. / o                  ..  \  
  5367. /      .                   \__
  5368.             _--_       .    
  5369. o/\.        \__/       _   ..
  5370.                       / \ .
  5371.      .                   o  /\
  5372.    .   ..        o/\        .
  5373.   o      .       . .     /\
  5374.      ./\     o o      o...  
  5375.              | |            
  5376. MKFIL rpg/scripts/world
  5377. WRITE 26
  5378.        /\-----------------DEMO---..----------------------------*
  5379.       /  \                     ...                             |
  5380.      /    \                ......                              |
  5381.    /\  /\/\          .........                                 |
  5382.   /\ /\ ** *       .....                       *               |
  5383.  /\    *  **      ...                       *        /\        |
  5384. /\    *  *  *    ---                     *   *   /\    /\      |
  5385. /\ *  *   *                                * /\                |
  5386. /\   *   *        ---                    *      /\    /\   /\   |
  5387. /\ * *     *     ..                       *   /  \ /\      /\  |
  5388. /\ *    * *      ..               *   *  *   /\    /  \   /\    |
  5389. /\              ..                     *   *     /\/\   /\      D
  5390. /\          .....               *   *      *  *     *           E
  5391. /\/\      ...                            *  *   *              M
  5392.  /  \   ....                    0== ==0  *  *     *            O
  5393. \/    \ ...                      |     |                        |
  5394. \........               * * *   |     |.....              ......
  5395.  \/\                   *  *     0=............    ..........   |
  5396. /\                        *          ...............            |
  5397. /\                     *   *                 .....             |
  5398. /\                                               ..             |
  5399. /\                                                 ..           |
  5400.  \/\                     /\/\      /\          /\  ..          |
  5401.  /  \/\  /\      /\   /\/\      /\/\/\   /\/\ /  \  .. /\ /\   |
  5402. /    \ \/ /\  /\/  \/\ /\     /\   /\   /  \ /    \.../  \  /\ |
  5403. /        \   /\ /\   \ \  /\/\/\  /\  /\/    \      \..        /\
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement