Advertisement
nitrogenfingers

DarkLands Repository

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