<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta woas_permanent="1" http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<script woas_permanent="1" type="text/javascript">
/* <![CDATA[ */

/* DFQRCTsKlU-0000368-START */

var woas = {"version": "0.12.0"};

var __marker = "DFQRCTsKlU-0000368";

woas["config"] = {
"permit_edits":true,
"dblclick_edit":false,
"save_on_quit":false,
"allow_diff":false,
"key_cache":true,
"cumulative_save":false,
"auto_save":0,
"debug_mode":false,
"safe_mode":false,
"nav_history":false,
"new_tables_syntax":true,
"store_mts":false,
"folding_style":0,
"import_settings":107,
"wsif_author":'Anonymous',
"wsif_ds":'',
"wsif_ds_lock":false,
"wsif_ds_multi":false,
"main_page":'Let\'s Build a Roguelike'};

var current = 'Let\'s Build a Roguelike';

var backstack = [
];

var page_titles = [
'::Menu',
'WoaS::Aliases',
'WoaS::Hotkeys',
'WoaS::CSS::Custom',
'Special::About',
'Special::Advanced',
'Special::Options',
'Special::Import',
'Special::Lock',
'Special::Search',
'Special::Embed',
'Special::Export',
'Special::License',
'Special::ExportWSIF',
'Special::ImportWSIF',
'WoaS::Plugins',
'WoaS::CSS::Core',
'WoaS::Template::Button',
'WoaS::Template::Info',
'WoaS::Template::Search',
'WoaS::CSS::Boot',
'WoaS::ImportSettings',
'WoaS::Template::Example::Transclusion',
'WoaS::Help::Security',
'WoaS::Help::Deprecated syntax',
'WoaS::Help::Special::Options',
'WoaS::Help::WSIF',
'WoaS::Help::Macros',
'WoaS::Help::Index',
'WoaS::Help::Syntax',
'WoaS::Help::Special::Advanced',
'WoaS::Help::Special::Erase Wiki',
'WoaS::Help::CSS',
'WoaS::Help::Special::Dead Pages',
'WoaS::Help::General usage',
'WoaS::Help::Nowiki text',
'WoaS::Help::Wiki is read-only',
'WoaS::Help::Create a page',
'WoaS::Help::Delete a page',
'WoaS::Help::Editor',
'WoaS::Help::Edit a page',
'WoaS::Help::Erase all pages',
'WoaS::Help::Edit the menu',
'WoaS::Help::Headers',
'WoaS::Help::Rulers',
'WoaS::Help::Include text file',
'WoaS::Help::Include web page',
'WoaS::Help::Include wiki page',
'WoaS::Help::JavaScript',
'WoaS::Help::Join lines of text',
'WoaS::Help::Namespaces',
'WoaS::Help::Text formatting',
'WoaS::Help::Rename a page',
'WoaS::Help::Page scrolling',
'WoaS::Help::Table of Contents',
'WoaS::Help::Tags',
'WoaS::Help::Publish wiki online',
'WoaS::Help::Run wiki on a webserver',
'WoaS::Help::Requirements',
'WoaS::Help::Lists',
'WoaS::Help::Tables',
'WoaS::Help::Links',
'WoaS::Help::Images',
'WoaS::Help::Import',
'WoaS::Help::Export',
'WoaS::Help::Transclusion',
'WoaS::Help::Special::ExportWSIF',
'WoaS::Help::Special::ImportWSIF',
'WoaS::Help::Hotkeys',
'WoaS::Help::Plugins',
'WoaS::Help::Aliases',
'Let\'s Build a Roguelike',
'About This Wiki Book',
'1: The Title Screen',
'License',
'2: Improving the Code',
'3: The Main Menu',
'4: The RPG System',
'5: Character Generation',
'6: The Intro',
'7: Dungeon Building',
'8: The Dungeon',
'9: Moving Around',
'10: Main Display',
'11: Inventory Structure',
'12: Generating Items',
'13: Character Inventory',
'14: Character Inventory II',
'15: Armor and Shields',
'16: Weapons',
'17: Monsters',
'18: Monster Movement',
'19: Melee Combat',
'20: Resting Model',
'21: Character Improvement',
'22: Projectile Combat',
'23: Scaling Monster Attacks',
'24: Wands',
'25: Generalizing Character Attributes',
'26: Potions',
'27: Scaling Difficulty Levels',
'28: Jewelry',
'29: Weapon Magic',
'30: Tweaking the Game 1',
'31:Armor Magic',
'32: Jewelry Magic',
'33: Spell Books',
'34: Spell Casting',
'35: Tweaking the Game 2',
'36: Locked Doors',
'37: Closing Doors',
'38: Traps',
'39: Searching',
'40: The Wandering Merchant',
'41: Tweaking the Game 3',
'42: Inscribing Spell Books',
'43: Monster Magic',
'44: The Amulet',
'45: Morgue File',
'46: Save and Load',
'47: Winning and Losing',
'49: The End and Beginning',
'RPG System',
'Procedures',
'Permadeath',
'48: Instructions',
'tWidgets',
'InitWidgets',
'tButton',
'tWin',
'tMsgBox',
'tEdit',
'tInputBox',
'tList'
];

/* DFQRCTsKlU-0000368-DATA */
var page_attrs = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];

var page_mts = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];

var pages = [
'[[Let\'s Build a Roguelike]]\n[[About This Wiki Book]]\n[[License]]\n----\n*Chapters*\n[[1: The Title Screen]]\n[[2: Improving the Code]]\n[[3: The Main Menu]]\n[[4: The RPG System]]\n[[5: Character Generation]]\n[[6: The Intro]]\n[[7: Dungeon Building]]\n[[8: The Dungeon]]\n[[9: Moving Around]]\n[[10: Main Display]]\n[[11: Inventory Structure]]\n[[12: Generating Items]]\n[[13: Character Inventory]]\n[[14: Character Inventory II]]\n[[15: Armor and Shields]]\n[[16: Weapons]]\n[[17: Monsters]]\n[[18: Monster Movement]]\n[[19: Melee Combat]]\n[[20: Resting Model]]\n[[21: Character Improvement]]\n[[22: Projectile Combat]]\n[[23: Scaling Monster Attacks]]\n[[24: Wands]]\n[[25: Generalizing Character Attributes]]\n[[26: Potions]]\n[[27: Scaling Difficulty Levels]]\n[[28: Jewelry]]\n[[29: Weapon Magic]]\n[[30: Tweaking the Game 1]]\n[[31:Armor Magic]]\n[[32: Jewelry Magic]]\n[[33: Spell Books]]\n[[34: Spell Casting]]\n[[35: Tweaking the Game 2]]\n[[36: Locked Doors]]\n[[37: Closing Doors]]\n[[38: Traps]]\n[[39: Searching]]\n[[40: The Wandering Merchant]]\n[[41: Tweaking the Game 3]]\n[[42: Inscribing Spell Books]]\n[[43: Monster Magic]]\n[[44: The Amulet]]\n[[45: Morgue File]]\n[[46: Save and Load]]\n[[47: Winning and Losing]]\n[[48: Instructions]]\n[[49: The End and Beginning]]\n*Notes*\n[[RPG System]]\n[[Procedures]]\n[[Permadeath]]\n----\n[[tWidgets]]\n[[InitWidgets]]\n[[tButton]]\n[[tWin]]\n[[tMsgBox]]\n[[tEdit]]\n[[tInputBox]]\n[[tList]]\n\n\n----\n*System*\n[[Special::All Pages]]\n[[Special::New Page]]\n[[Special::Duplicate Page]]\n[[Special::Go to]]\n[[Special::Delete Page]]\n[[Special::Backlinks]]\n[[Special::Search]]\n\n\n\n\n\n\n\n',
'',
'$SAVE	s\n$EDIT	e\n$PRINT	p\n$HELP	h\n$GOTO	g\n$CANCEL	0x1b\n$BACK	0x8\n',
'/* Your CSS customization goes here */\n\n.code:before {\n	background-color: #696969;\n	color: white;\n	font-family: monospace;\n	content: "CODE";\n	display: block;\n	font-weight: bold;\n	text-align: center;\n	}\n.code {\n	background-color: #DCDCDC;\n	border: 1px solid black;\n	font-family: monospace;\n        margin-left: auto; \n        margin-right: auto;\n	width: 95%;\n	white-space: pre-wrap;\n	}\n',
'[[Special::TOC]]\n= Welcome to Wiki on a Stick!\nWiki on a Stick (in short *WoaS*) is a [[http://en.wikipedia.org/wiki/Wiki|wiki]] that lives in *one self-modifying XHTML file*. It\'s perfect to be used at home or at office, on your laptop or USB pen drive. It can be used as a personal notepad, calendar, repository for software documentation and for many other purposes; it allows full customization and extension by your own scripts and plugins. It also offers [[WoaS::Help::Security|AES encryption]] of pages.\n\n== Version\n\nCurrent version is *v0.12.0*\nVersions 0.9&tilde;0.12.0 by Daniele C. ([[http://sf.net/users/legolas558/|legolas558]])\nOriginal version (v0.01 &tilde; v0.04) by Andr\u00e9 Wagner\n\n\x3Cbig\x3E[[Special::License|Wiki on a Stick is licensed under the GNU/GPL license]].\x3C/big\x3E\n\n== Official Website\n\n\x3Cimg style="display: inline; visibility: visible;" id="img_logo" alt="WoaS Logo" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J%2FAAAAAXNSR0IArs4c6QAAAAJiS0dEAP%2BHj8y%2FAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH2gEcDzUY4t9JowAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAI7SURBVEjHtdVPSFRRFAbw33ujljETWhaGo9YiCGoVuHDTIggE2xRBbgeEoFWQFARBq3ZCELQKgqJtbWtTEAitgqBNCZJFGGb%2B18Hxz20xr3HSmXEU%2Bt7ivnvf%2B757z3feOydSAZNh2QrW0IAm%2Bx2LVMS25e9h0UbFF5u1aI1qCvwMs9ZVRyStK6oq8D3M2xn7HdYSVRAYC3n1ocHRslDi4vCtbjprpspmcTH2BbtBwddQJjATZu0Wi36FvwFZqOk861atbUttvDku1SAHeUsKFb6MueQM8c%2BwUcOuJatVnxYDb8hX3XtFoWZoxZM3LFkpW0wlfmxY29HIvNnQEsV5fQql62IyVqO%2FknPegNtgFfG6wyZ80GtaQRrjcnpdN5eQhvTrNwaG5Tx3S0%2BSH%2BIga9SItA8mZTGs21MZTxKBnMfuJLOsLzLOupL4RJySNeadGyWBT645adAbMO6xnCHvwUNcMmg0cYy4Ubc30vp9NaG7FG1TYueQHve9sAjSBr10xj3QiLhZp1F96PRWJ854ZsIjF8CK07LeYQ4PfJRyCuzTGhEfkJVyAeeMyOKmTy5bkAODbhsw4ZyrGDekz2t3kSnWg5nwWbAXHHcsIm6NDu2J%2FrfMxrRJ7UGgffNvPBQd2TU9UyrzMZyIDu6K3qRja13o0Fw3PaWrrKiWbmbCDwt17d7lSFSlsYyG3zukNKNjS3fa0md%2BhUnzVRPXXqFDVmiZU2HOouV%2Fjn1QRntUV3PdxHRYRUpb5H%2FiD%2FUerIBA%2BEx5AAAAAElFTkSuQmCC" /\x3E [[http://stickwiki.sourceforge.net|WoaS Official Website]]\n\n== Download\nWiki on a Stick can always be downloaded through the official SF.net WoaS project file download service:\n\x3Cbig\x3E[[https://sourceforge.net/projects/stickwiki/files/|Download Wiki on a Stick]]\x3C/big\x3E\n\n== Get support\n\nYou can use the [[http://woas.iragan.com/|official WoaS forums]] and the [[http://sf.net/projects/stickwiki/|SF.net WoaS project facilities]].\n\n== Thanks to\nThis project wouldn\'t be what it is without the help of other people. If you contributed to Wiki on a Stick and your name is not here (or if you do not want it to be listed here), please [[http://sourceforge.net/sendmessage.php?touser=799761|contact the mantainer]].\n* *Jeremy Ruston* - the creator of the wonderful [[http://www.tiddlywiki.com/|TiddlyWiki]], who served as inspiration for Wiki on a Stick\n* *Philip O\'Donnel* - creator of the table syntax & parsing code\n* *Tim Lord*\n* *Knut Kohl*\n* *Rob Schmersel*\n* *Michiel van Everdingen* - [[http://home.versatel.nl/MAvanEverdingen/Code/|original javascript implementation]] of AES encryption\n* *Cyril Mazard* - search feature\n* *jimmac* - author of [[http://jimmac.musichall.cz/i.php?i=gorilla-stock|the icons set]] which we are currently using in WoaS\n* *martinellison* - transclusion patch\n* *Nilton Castillo* - bugfixes and enhancements (original pluggable macro syntax)\n* *Little Girl* - documentation, overall testing, various improvements\n* [[http://weekendtesting.com/archives/833|Europe Weekend Testing]] - which provided a testing session for WoaS\n* [[http://testconsultant.blogspot.com/|Jeroen Rosink]] - for testing WoaS releases\n\n== Important links\n* [[http://sf.net/projects/stickwiki/|SourceForge project home page]] - useful for contributing\n* [[http://woas.iragan.com/|Official WoaS forums]] - our discussion forums where you can also participate to the decision processes\n* [[http://sourceforge.net/tracker/?group_id=155218&atid=794925|SF.net WoaS project bug tracker]] - if you find a bug, post it here\n* [[http://sourceforge.net/tracker/?group_id=155218&atid=794928|SF.net WoaS project feature tracker]] - if you have have suggestions for improvements\n* [[http://www.iragan.com/woas_irc/|Official WoaS IRC Channel]] - #woas room at FreeNode servers\n* [[https://lists.sourceforge.net/lists/listinfo/stickwiki-devel|SF.net WoaS Development mailing list]] - for public discussion',
'== Pages\n* [[Locked::]], [[Unlocked::]]: a list of all encrypted/plain pages\n* [[Special::All Pages]]: a list of all user created pages in this wiki\n* [[Special::Dead Pages]]: a list of nonexistent pages that other pages link to \n* [[Special::Delete Page]]: delete specific page\n* [[Special::Go to]]: go to specific page\n* [[Special::New Page]]: create a new page\n* [[Special::Orphaned Pages]]: a list of pages that no other pages link to\n* [[Special::Recentchanges]]: view recently modified pages\n* [[Special::Search]]: search all wiki pages\n* [[Tagged::]], [[Untagged::]]: a list of all tagged/untagged pages\n\n== Advanced features\n* [[Special::Options]]: general WoaS options\n* [[WoaS::Aliases]]: view/edit page title aliases\n* [[WoaS::CSS::Core]], [[WoaS::CSS::Custom]]: view the core CSS and edit the custom CSS\n* [[WoaS::Hotkeys]]: view/edit WoaS access keys and default key bindings\n* [[WoaS::Plugins]]: enable/disable and view/edit WoaS JavaScript plugins\n\n== Maintenance\n* [[Special::Import]]: import wiki from an early (or current) version\n* [[Special::ImportWSIF]]: import a wiki page (or collection of) in [[WoaS::Help::WSIF|WSIF]] format\n* [[Special::Export]]: export wiki into multiple linked static XHTML files\n* [[Special::ExportWSIF]]: export wiki into multiple or single [[WoaS::Help::WSIF|WSIF]] files\n* [[Special::Erase Wiki]]: delete ALL wiki pages and reset with default data',
'= Options\n\n== User Interface\n\x3Clabel for="cb_dblclick_edit"\x3E\x3Cinput type="checkbox" id="cb_dblclick_edit" onclick="woas.config.dblclick_edit = d$.checked(\'cb_dblclick_edit\')" /\x3EDouble click to edit pages/menu\x3C/label\x3E\n\x3Clabel for="cb_key_cache"\x3E\x3Cinput type="checkbox" id="cb_key_cache" onclick="woas.config.key_cache = !d$.checked(\'cb_key_cache\')" /\x3EDo not temporarily cache the AES key\x3C/label\x3E\n\x3Clabel for="cb_rem_history"\x3E\x3Cinput type="checkbox" id="cb_rem_history" onclick="woas.config.nav_history = d$.checked(\'cb_rem_history\')" /\x3ERemember current page and navigation history\x3C/label\x3E\n\x3Clabel for="cb_fixed_layout"\x3E\x3Cinput type="checkbox" id="cb_fixed_layout" onclick="_set_layout(d$.checked(\'cb_fixed_layout\'))" /\x3EFixed menu area and navigation bar\x3C/label\x3E\n\x3Clabel for="cb_debug_mode"\x3E\x3Cinput type="checkbox" id="cb_debug_mode" onclick="woas.config.debug_mode = d$.checked(\'cb_debug_mode\')" /\x3EEnable debug mode and debug console (*requires WoaS reload*)\x3C/label\x3E\n\n== Saving\n\x3Clabel for="cb_allow_diff"\x3E\x3Cinput type="checkbox" id="cb_allow_diff" onclick="woas.config.allow_diff = d$.checked(\'cb_allow_diff\')" /\x3EMake merge/diff of WoaS XHTML/WSIF datasource easier\x3C/label\x3E\n\x3Clabel for="cb_save_on_quit"\x3E\x3Cinput type="checkbox" id="cb_save_on_quit" onclick="woas.config.save_on_quit = d$.checked(\'cb_save_on_quit\')" /\x3EAutomatically save options (if changed) before quitting\x3C/label\x3E\n\x3Clabel for="cb_cumulative_save"\x3E\x3Cinput type="checkbox" id="cb_cumulative_save" onclick="woas.config.cumulative_save = d$.checked(\'cb_cumulative_save\')" /\x3EEnable cumulative save (will save to disk only when quitting)\x3C/label\x3E\n\x3Clabel for="cb_safe_mode"\x3E\x3Cinput type="checkbox" id="cb_safe_mode" onclick="woas.config.safe_mode = d$.checked(\'cb_safe_mode\')" /\x3EEnable safe mode (embedded &lt;script/&gt; blocks and WoaS::Plugins not executed, *requires WoaS reload*)\x3C/label\x3E\n\x3Clabel for="cb_store_mts"\x3E\x3Cinput type="checkbox" id="cb_store_mts" onclick="woas.config.store_mts = d$.checked(\'cb_store_mts\')" /\x3EStore and display last modified timestamp\x3C/label\x3E\n\x3Clabel for="cb_new_tables_syntax"\x3E\x3Cinput type="checkbox" id="cb_new_tables_syntax" onclick="woas.config.new_tables_syntax = d$.checked(\'cb_new_tables_syntax\')" /\x3EEnhanced tables syntax\x3C/label\x3E\n\n== Datasource\n\x3Clabel for="cb_wsif_ds_lock"\x3E\x3Cinput type="checkbox" id="cb_wsif_ds_lock" onclick="woas.config.wsif_ds_lock = d$.checked(\'cb_wsif_ds_lock\')" /\x3ELock WSIF datasource when editing\x3C/label\x3E\n\x3Clabel for="cb_wsif_ds_multi"\x3E\x3Cinput type="checkbox" id="cb_wsif_ds_multi" onclick="woas.config.wsif_ds_multi = d$.checked(\'cb_wsif_ds_multi\')" /\x3EUse multiple files in WSIF datasource\x3C/label\x3E\n/Disabling above option will not remove WSIF files created for each page/\nWSIF data source: \x3Cinput id="txt_wsif_ds" type="text" size="60" value="" onchange="woas.config.wsif_ds=this.value" /\x3E\n/Relative path to WSIF datasource to use (blank to disable); file will be overwritten./\nA file named as above WSIF data source and ending with ".lck" will also be created to manage locking.\n\nWSIF data author: \x3Cinput id="txt_author" type="text" size="12" value="" onchange="woas.config.wsif_author=this.value" /\x3E\n/Used in exported WSIF content or in WSIF datasources./\n\n\x3Cp\x3E\x3Cinput class="woas_button" type="button" onclick="save_options()" value="Save options" /\x3E\x3C/p\x3E\n== Permanent changes\n\x3Cp\x3E&nbsp;&nbsp;\x3Cinput class="woas_button" type="button" onclick="ro_woas()" value="Disallow edits" /\x3E\n/Caution: to re-enable editing you will have to manually edit WoaS/\x3C/p\x3E\n\x3Cscript type="text/javascript"\x3E\nd$("cb_allow_diff").checked = bool2chk(woas.config.allow_diff);\nd$("cb_rem_history").checked = bool2chk(woas.config.nav_history);\nd$("cb_store_mts").checked = bool2chk(woas.config.store_mts);\nd$("cb_new_tables_syntax").checked = bool2chk(woas.config.new_tables_syntax);\nd$("cb_key_cache").checked = bool2chk(!woas.config.key_cache);\nd$("cb_dblclick_edit").checked = bool2chk(woas.config.dblclick_edit);\nd$("cb_debug_mode").checked = bool2chk(woas.config.debug_mode);\nd$("cb_save_on_quit").checked = bool2chk(woas.config.save_on_quit);\nd$("cb_cumulative_save").checked = bool2chk(woas.config.cumulative_save);\nd$("cb_safe_mode").checked = bool2chk(woas.config.safe_mode);\nd$("cb_wsif_ds_multi").checked = bool2chk(woas.config.wsif_ds_multi);\nd$("cb_wsif_ds_lock").checked = bool2chk(woas.config.wsif_ds_lock);\nd$("cb_fixed_layout").checked = (d$("woas_wiki_header").style.position == "fixed");\n\nif (woas.browser.ie6) d$("cb_fixed_layout").disabled = true;\n\nd$("txt_author").value = woas.config.wsif_author;\nd$("txt_wsif_ds").value = woas.config.wsif_ds;\n\nd$("txt_wsif_ds").onfocus = d$("txt_author").onfocus = woas.ui.focus_textbox;\nd$("txt_wsif_ds").onblur = d$("txt_author").onblur = woas.ui.blur_textbox;\n\x3C/script\x3E',
'Import a wiki /.html/ source from an early (or current) version:\n\nFile: \x3Cinput id="filename_" type="file" /\x3E&nbsp;\x3Cinput id="btn_import" class="woas_button" type="button" value="Import" onclick="import_wiki()" /\x3E\n\n== Source\n\x3Clabel for="woas_cb_import_config"\x3E\x3Cinput type="checkbox" id="woas_cb_import_config" checked="checked"\x3E&nbsp;Import configuration (except WSIF datasource settings)\x3C/label\x3E\n\x3Clabel for="woas_cb_import_content"\x3E\x3Cinput type="checkbox" id="woas_cb_import_content" checked="checked"\x3E&nbsp;Import pages\x3C/label\x3E\n\x3Clabel for="woas_cb_import_styles"\x3E\x3Cinput type="checkbox" id="woas_cb_import_styles"\x3E&nbsp;Import CSS\x3C/label\x3E\n[[Include::WoaS::ImportSettings]]\n\n\x3Cscript type="text/javascript"\x3E\nwoas.ui._import_xhtml_load();\n\x3C/script\x3E',
'== AES encryption\nRead more about the [[WoaS::Help::Security|embedded AES encryption]] used in Wiki On a Stick.\n\n== Password\nEnter your password for AES encryption. The more complex your password is, the more difficult it will be to attack the encryption. It is suggested *not* to use passwords whose textbox color is \x3Cspan style="background-color:red"\x3E&nbsp;red&nbsp;\x3C/span\x3E, and to accept passwords only when the textbox color is \x3Cspan style="background-color:lime"\x3E&nbsp;green&nbsp;\x3C/span\x3E. Punctuation and Mixed case characters will exponentially increase the strength of your password.\n\x3Cdl\x3E\x3Cdt\x3E\x3Clabel for="pw1"\x3EEnter password:\x3C/label\x3E\x3C/dt\x3E\x3Cdd\x3E\x3Cinput type="password" id="pw1" style="background-color:red" value="" size="50" onfocus="woas.ui.focus_textbox()" onblur="woas.ui.blur_textbox()" onkeyup="pw_quality()" /\x3E&nbsp;\x3Cspan id="txtBits"\x3E&nbsp;\x3C/span\x3E\x3C/dd\x3E\x3Cdt\x3E\x3Clabel for="pw2"\x3ERe-enter password:\x3C/label\x3E\x3C/dt\x3E\x3Cdd\x3E\x3Cinput type="password" id="pw2" value="" size="50" onfocus="woas.ui.focus_textbox()" onblur="woas.ui.blur_textbox()" /\x3E\x3C/dd\x3E\x3C/dl\x3E\x3Cinput id="btn_lock" class="woas_button" type="button" value="" /\x3E\n\x3Cscript type="text/javascript"\x3E\n	_lock_page();\n\x3C/script\x3E',
'= Search in Wiki\n\n\x3Cinput id="string_to_search" type="text" onfocus="search_focus(true)" onblur="search_focus(false)" /\x3E&nbsp;\x3Cinput class="woas_button" type="button" value="Search" onclick="ssearch_do_search()" /\x3E&nbsp;\x3Cinput class="woas_button" type="button" value="Clear" onclick="woas.ui.clear_search()" /\x3E\n\x3Cdiv id="woas_search_results"\x3E&nbsp;\x3C/div\x3E\n\n\x3Cscript type="text/javascript" language="javascript"\x3E\nwoas._search_load();\n\x3C/script\x3E',
'= Embed %1\n\n\x3Cinput id="filename_" type="file" /\x3E&nbsp;\x3Cinput class="woas_button" type="button" value="Embed" onclick="woas._embed_process(\'%1\')" /\x3E',
'= Export content into XHTML files\n\n==Paths\nSpecify the destination directory which will contain all the exported XHTML files.\n\nNote: *Directory must exist if using a non-Mozilla browser.*\n\n\x3Cinput id="woas_ep_xhtml" type="text" size="80" /\x3E\n\nSpecify the destination directory which will contain all the exported embedded pictures.\n\nNote: *Directory must exist if using a non-Mozilla browser.*\n\n\x3Cinput id="woas_ep_img" type="text" size="80" /\x3E\n\n==File naming\n\nDefault extension for generated XHTML files\n\n\x3Cinput id="woas_ep_ext" type="text" size="8" /\x3E\n\n\x3Clabel for="woas_cb_index_main"\x3E\x3Cinput type="checkbox" id="woas_cb_index_main" checked="checked"\x3EThe main page will be named index.htm\x3C/label\x3E\n\x3Clabel for="woas_cb_unix_norm"\x3E\x3Cinput type="checkbox" id="woas_cb_unix_norm" checked="checked"\x3EApply UNIX-style normalization (lowercase and spaces replaced by underscores) to generated filenames\x3C/label\x3E\n\n==Custom JavaScripts\n\x3Clabel for="woas_cb_js_ign"\x3E\x3Cinput type="radio" id="woas_cb_js_ign" name="woas_cb_js" checked="checked"\x3EDo not export\x3C/label\x3E\n\x3Clabel for="woas_cb_js_dyn"\x3E\x3Cinput type="radio" id="woas_cb_js_dyn" name="woas_cb_js"\x3ERun before exporting but do not export the javascript\x3C/label\x3E\n\x3Clabel for="woas_cb_js_exp"\x3E\x3Cinput type="radio" id="woas_cb_js_exp" name="woas_cb_js"\x3EExport javascript as-is\x3C/label\x3E\n\x3Clabel for="woas_cb_js_head"\x3E\x3Cinput type="radio" id="woas_cb_js_head" name="woas_cb_js" disabled="disabled"\x3EExport javascript into &lt;head /&gt; (you might need to include the framework code)\x3C/label\x3E\n\n\x3Clabel for="woas_cb_js_fw"\x3E\x3Cinput type="checkbox" id="woas_cb_js_fw" disabled="disabled"\x3EExport the javascript framework too\x3C/label\x3E\n\n==Other options\nMETA author (optional)\n\n\x3Cinput id="woas_ep_author" type="text" size="12" value="" /\x3E\n\n\x3Clabel for="woas_cb_sep_css"\x3E\x3Cinput type="checkbox" id="woas_cb_sep_css" checked="checked"\x3EExport CSS into shared file\x3C/label\x3E\n\x3Clabel for="woas_cb_export_menu"\x3E\x3Cinput type="checkbox" id="woas_cb_export_menu" checked="checked"\x3EExport menu and submenus\x3C/label\x3E\n\n\x3Cinput class="woas_button" type="button" value="Export XHTML" onclick="woas.export_wiki()" /\x3E\n\n\x3Cscript type="text/javascript"\x3E\nvar path = woas.ROOT_DIRECTORY + "export" + woas.DIRECTORY_SEPARATOR;\nd$(\'woas_ep_xhtml\').value = path;\nd$(\'woas_ep_img\').value = path;\nd$(\'woas_ep_ext\').value = woas.exporter._default_ext;\n\nd$(\'woas_ep_xhtml\').onfocus = d$(\'woas_ep_img\').onfocus = d$(\'woas_ep_ext\').onfocus = woas.ui.focus_textbox;\nd$(\'woas_ep_xhtml\').onblur = d$(\'woas_ep_img\').onblur = d$(\'woas_ep_ext\').onblur = woas.ui.blur_textbox;\nd$(\'woas_ep_author\').value = woas.config.wsif_author;\n\n\x3C/script\x3E',
'= Special::License\n\nRetrieved from [[http://www.gnu.org/licenses/gpl2.txt]]:\n\n\x3Ciframe border="0" width="100%" height="300" src="http://www.gnu.org/licenses/gpl2.txt"\x3E\x3C/iframe\x3E',
'= Export into WSIF format\n\nSee also [[WoaS::Help::WSIF]].\n\n== Destination path\nDestination directory that will contain the exported WSIF file(s).\n\nNote: *Directory must exist if using a non-Mozilla browser.*\n\n\x3Cinput id="woas_ep_wsif" type="text" size="80" /\x3E\n\n== File generation\n\n\x3Clabel for="woas_cb_single_wsif"\x3E\x3Cinput type="checkbox" id="woas_cb_single_wsif" checked="checked"\x3EExport all pages into a single WSIF file\x3C/label\x3E\n\x3Clabel for="woas_cb_inline_wsif"\x3E\x3Cinput type="checkbox" id="woas_cb_inline_wsif"\x3EDo not export embedded files as external file blobs\x3C/label\x3E\n\n== Other options\nAuthor: \x3Cinput id="woas_ep_author" type="text" size="12" value="" /\x3E\n\n\x3Cinput class="woas_button" type="button" value="Export WSIF" onclick="woas.export_wiki_wsif()" /\x3E\n\n\x3Cscript type="text/javascript"\x3E\nvar path = woas.ROOT_DIRECTORY + "wsif" + woas.DIRECTORY_SEPARATOR;\nd$(\'woas_ep_wsif\').value = path;\n\nd$(\'woas_ep_author\').onfocus = $(\'woas_ep_wsif\').onfocus = woas.ui.focus_textbox;\nd$(\'woas_ep_author\').onblur = $(\'woas_ep_wsif\').onblur = woas.ui.blur_textbox;\nd$(\'woas_ep_author\').value = woas.config.wsif_author;\n\n\x3C/script\x3E',
'Import a wiki page (or collection of) in [[WoaS::Help::WSIF|WSIF format]].\n\nFile: \x3Cinput id="filename_" type="file" /\x3E&nbsp;\x3Cinput id="btn_import" class="woas_button" type="button" value="Import WSIF" onclick="woas.import_wiki_wsif()" /\x3E\n\n[[Include::WoaS::ImportSettings]]\n\n\x3Cscript type="text/javascript"\x3E\nwoas.ui._import_wsif_load();\n\x3C/script\x3E',
'= Plugins\nThis page contains options for the active WoaS plugins; see [[WoaS::Help::Plugins]] for more information.\n\x3Cinput class="woas_button" title="Create a new plugin" value="Create plugin..." onclick="javascript:_woas_new_plugin()" type="button" /\x3E&nbsp;&nbsp;\x3Cinput class="woas_button" title="Import a plugin" value="Import plugin..." onclick="go_to(\'Special::ImportWSIF\')" type="button" /\x3E',
'/* BODY */\nbody {\n	font-family: Georgia, verdana;\n}\n.woas_background {\n	background-color: #F0F0F5;\n}\n\n/* BUTTONS */\n.woas_button {\n	background-color: #F0F0F5;\n	border: 1pt solid grey;\n	color: darkblue;\n	cursor: pointer;\n	font-variant: normal;\n	margin-top: 0.438em;\n	text-align: center;\n}\n.woas_button:hover {\n	background-color: darkgrey;\n}\n\n/* DEBUG CONSOLE */\n#woas_debug_button {\n	margin-top: 0.5%;\n}\n#woas_debug_console_title {\n	color: white;\n	font-weight: bold;\n	text-align: left;\n}\n#woas_debug_log {\n	background-color: #F0F0F5;\n	border: 1px solid black;\n	margin-top: 0.5%;\n	width: 100%;\n}\n#woas_debug_log_clear_button {\n	margin-top: 0.3%;\n	margin-bottom: 0.3%;\n}\n#woas_debug_panel {\n	background-color: #048db4;\n	border: 0;\n	margin-top: -0.4em;\n}\n\n/* EDITOR */\n#woas_edit_page_title{\n	margin-top: 2.188em;\n}\n.woas_editor_button {\n	margin-top: 0.063em;\n	margin-bottom: 0.063em;\n}\n\n/* EMBEDDED FILES */\npre.woas_embedded {\n	background-color: #9E9EFF;\n}\n\n/* ENCRYPTION */\n.woas_password_desc {\n	background-color: #FAFAFB;\n	border: 2pt solid navy;\n	font-size: 0.8em;\n	margin-right: 25%;\n	margin-left: 25%;\n}\n#woas_pwd_mask {\n	font-size: large;\n	height: 100%;\n	left: 0em;\n	position: absolute;\n	text-align: center;\n	top: 0em;\n	visibility: hidden;\n	width: 99%;\n	z-index: 99;\n}\n#woas_pwd_query {/* ask password overlay */\n	background-color: #d6dff7;\n	border: 1pt solid #666666; \n	font-size: large;\n	height: 100%;\n	left: 0em;\n	padding-top: 25%;\n	position: absolute;\n	text-align: center;\n	top: 0em;\n	visibility: hidden;\n	width: 99%;\n	z-index: 100;\n}\n\n/* FOOTER */\n#woas_footer {\n	background-color: #048db4;\n	border: 1pt solid black;\n	font-size: 95%;\n	font-weight: bold;\n	line-height: 1em;\n	margin-top: 0.75em;\n	margin-right: 0.85em;\n	margin-left: 15.5%;\n	padding-top: 0;\n	padding-right: 0.5%;\n	padding-bottom: 0;\n	padding-left: 0.5%;\n}\n#woas_footer a {\n	color: white ! important;\n}\n#woas_go_to_top_button {\n	margin-bottom: -0.4em;\n}\n\n/* HEADERS */\nh1.woas_header {\n	font-size: 1.438em;\n}\nh2.woas_header {\n	font-size: 1.25em;\n}\nh3.woas_header {\n	font-size: 1.063em;\n}\nh4.woas_header {\n	font-size: 0.875em;\n}\nh5.woas_header {\n	font-size: 0.75em;\n}\nh6.woas_header {\n	font-size: 0.625em;\n}\n\n/* HELP PAGES */\n.woas_help_background {\n	background-attachment:fixed;\n	background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A%2FwD%2FoL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9oDDAonCzKHvroAAAEFSURBVHja7duxDYIhFIVRIA7EKk7qKoxkb2Ns1PzfObWNuRfyAB0DAAAAAAAAAAAAAAAAAAAAAAAAAIC%2FMEtf9pxzf%2FeZvfdDAYLBV4swBd8uwhR%2BuwhT%2BO0SLOE7BQg%2FvAssa8AOYPWHd4FbtfmvIVZnhyX8xnlfAVCA8kpPHAO%2FNUReqUQKEA7fDDDcHC7hmwGEHx4gp%2BDbpwc%2FCYsfHafw2%2FcG2beAevCpHeDT1V%2B6LfQWEA5fAeLhKwAKoAAAAOR4DRzt3wl6DYyXwGtgvAQeg%2BIlcBEUpwAKgALgHsAg6BTgHsAO0CyBfwwDAAAAAAAAAAAAAAAAAAAAAAAAAAAAv%2FIER4Nn%2B7YDG3wAAAAASUVORK5CYII%3D);\n}\n.woas_help_button {\n	font-size: 1em;\n	margin-top: 0.75em;\n	position: fixed;\n	right: 0.625em;\n	top: 0.188em;\n}\n.woas_helpcode {\n	font-family: monospace;\n	padding: 0.438em;\n}\n.woas_helptext {\n	font-family: lucida;\n}\n\n/* HOTKEYS */\n#woas_custom_accesskeys {\n	display: none;\n	visibility: hidden;\n}\n\n/* LAST MODIFIED LINE */\n.woas_page_mts {\n	font-size: small;\n	margin-top: 0.75em;\n	margin-right: 0.80em;\n	margin-left: 15.5%;\n	z-index: 0;\n}\n\n/* LINK - CURSOR */\na {\n	cursor: pointer;\n}\n\n/* LINK - EXISTENT */\na.woas_link {\n	color: blue;\n}\na.woas_link:hover {\n	text-decoration: underline;\n}\n\n/* LINK - EXTERNAL */\na.woas_world_link {\n	color: darkgreen;\n	text-decoration: underline;\n}\n\n/* LINK - NONEXISTENT */\na.woas_unlink {\n	color: red;\n}\na.woas_unlink:hover {\n	text-decoration: underline;\n}\n\n/* MAIN TEXT AREA */\ndiv.woas_text_area {/* any text area div */\n	background-color: white;\n	border: 1pt solid black;\n	padding: 0.475em;\n}\n#woas_wiki_area {/* main text area */\n	left: 15.5%;\n	margin-top: 1.313em;\n	margin-bottom: 0.75em;\n	position: relative;\n	text-align: justify;\n	width: 82.3%;\n	z-index: 0;\n}\n#woas_wiki_area ol li {\n	margin-left: -0.5em;\n}\n#woas_wiki_area ul li {\n	margin-left: -0.7em;\n}\n\n/* MAIN WIKI PANE WHEN LOCKED */\n.woas_text_area.locked, #woas_pwd_mask {\n	background-color: #eeeeee;\n	background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAAAAACMmsGiAAAALHRFWHRDcmVhdGlvbiBUaW1lAGdpbyAyOSBtYXIgMjAwNyAxMTo1MToxMCArMDEwME%2BTNUAAAAAHdElNRQfXAx0JNByGxueSAAAACXBIWXMAAAsSAAALEgHS3X78AAAABGdBTUEAALGPC%2FxhBQAAABlJREFUeNpj%2FM%2FwmYHpMwMvAxOQwcvECyQBO1oE%2BlG5iisAAAAASUVORK5CYII%3D);\n}\n\n/* MENU */\n#i_woas_menu_area {\n	background-color: white;\n	border: 1pt solid grey;\n	font-size: 0.75em;\n	left: 1%;\n	padding: 0.499em;\n	width: 13.2%;\n}\n#woas_menu_area ol li, #woas_ns_menu_area ol li {\n	margin-left: -1.55em;\n}\n#woas_menu_area ul li, #woas_ns_menu_area ul li {\n	margin-left: -1.85em;\n}\n.woas_menu_button {\n	margin-top: 0.438em;\n	width: 100%;\n}\n\n/* NAVIGATION BAR */\n#woas_wiki_header {\n	border: 1pt solid #aaa;\n	left: 0em;\n	opacity: .65;\n	padding-right: 0.75em;\n	top: 0em;\n	width: 99%;\n	z-index: 1;\n}\n#woas_title {\n	display: inline;\n	font-size: 2em;\n	font-weight: bold;\n	margin-top: 0em;\n	margin-bottom: 0em;\n	text-shadow: #888 0.13em 0.13em 0.065em;\n}\n\n/* NOWIKI TEXT */\n.woas_nowiki {\n	background-color: #F5F5F5;\n	white-space: pre-wrap; /* CSS 3 */\n	padding-right: 0.27em;\n	padding-left: 0.27em;\n}\n.woas_nowiki_multiline {\n	border: 0.063em solid grey;\n	padding-left: 0.4em;\n}\n\n/* PAGE LISTING HEADER */\n.woas_listing_options {\n	background-color: #AACCBB;\n	border: 0.1em solid blue;\n	padding: 0.2em;\n}\n\n/* SEARCH */\npre.woas_search_results {\n	background-color: #f9f9f9;\n	border: 1pt solid #aaa;\n	clear: both;\n	padding: 0.313em;\n	margin-top: 1em;\n}\n.woas_search_highlight {\n	background-color: yellow;\n}\n\n/* TABLE OF CONTENTS */\ndiv.woas_toc {\n	border: 1pt dashed #aaa;\n	background-color: #f9f9f9;\n	padding: 0.313em;\n	width: 50%;\n	font-size: 95%;\n}\ndiv.woas_toc ol, li {\n	margin-left: 3.3em;\n	padding-left: 0em;\n}\np.woas_toc_title {\n	font-weight: bold;\n	text-align: center;\n}\n\n/* TABLES */\ntable.woas_text_area {\n	border: 1pt solid black;\n	border-spacing: 0em;\n}\ntable.woas_text_area td {\n	border: 1pt solid;\n	padding: 0.313em;\n}\ntable.woas_text_area caption {\n	font-style: italic;\n	font-weight: bold;\n}\n\n/* TAG - CONTAINER */\ndiv.woas_taglinks {\n	border: 1pt solid #aaa;\n	margin-top: 1em;\n	overflow: auto;\n	padding: 0.313em;\n}\n\n/* TAG - LINKS */\ndiv.woas_taglinks \x3E a {\n	background-color: gainsboro;\n	border: 2px solid blue;\n	/* border-radius: 7px; */\n	color: navy;\n	font-size: 90%;\n	font-weight: bold;\n	line-height: 2.4;\n	margin: 2px 2px 2px 0;\n	-moz-border-radius: 7px;\n	padding: 3px 5px 3px 5px;\n	text-decoration: none;\n	-webkit-border-radius: 7px;\n}\ndiv.woas_taglinks \x3E a:hover {\n	background-color: grey;\n	border: 2px solid blue;\n	color: white;\n	text-decoration: none;\n	font-weight: bold;\n}\n\n/* WIKI CORE PAGES */\n.woas_core_page {\n	background-color: #bbccff;\n}\n\n/* WIKI LOADING OVERLAY */\n#loading_overlay {\n	background-color: white;\n	font-size: large;\n	height: 100%;\n	left: 0em;\n	padding-top: 25%;\n	position: absolute;\n	text-align: center;\n	top: 0em;\n	width: 99%;\n	z-index: 100;\n}\n\n/* WIKI WAIT TEXT */\n.woas_wait_text {\n	color: navy;\n}',
'\x3Cinput type="button" class="woas_button woas_help_button" value="%1" title="%1" onclick="%2"\x3E',
'\x3Cdiv style="background-color: #FFFFAA; border: 0.1em solid; padding: 0.2em;"\x3E\x3Cimg src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAAXNSR0IArs4c6QAAArtQTFRF8ox%2FAAAAAAABAQEBAQECAgICAwMDBAQFBQUABgYGCQgJCgoLDAsMDQwPDQ0NDw4PDw8HEhETExMUFRQUGRkNGhsUGxscHx8eISATIR8lJCQVJiUsJyUqKCcuLCwtLiw0Ly4vMzE4NzYoQT9FQz9ERkZGSkdTS0hMTEhNUk5bU1JVY15kYWBkZWFubGh5bmt1cGt6c212cm5%2BcXFwfHaDfXxehoKQjIiUkYyfkoyllZKal5CpmJGqmJGrmZKtmpaim5WunJaunZewn5mxoJqyopquoZuxpJylopyyopy1pJ22pJ24pJ61pJ%2B1paC1pqC1qaC0pqG1qKG7qqKuqaKxqKK4paWkq6O7qaS3q6p%2BraW4rae6rKmvrqm1rqm6r6nDsKu1sKu9sKu%2Bs6yxs7GDsay9sq29s667tK7FuK%2FBtbDAtbDBt7HEubS%2BubTCurTKurXFvLqjvLfGvLjEv72LvL2VvbjLvr%2BPwL%2BKvbvBw7nHv7vDwMGHw7rHwb3Fwr3Lw8SQxb%2FRxsDRxsHMxcLGxsPLyMPQx8XKyMXIysuOycbIzMfVzcrMz8zU0NGi0czZ0M%2FL0c%2FL0c%2FN0s7X08%2FZ1NPN1ten1tTR1tXP19XS2tfc3Nyt3N2s3dni397W4N%2FX4N%2FZ4t7m4eDY4t%2Fm4eHY4uHb4uDl4uLX4%2BLa5OLo5OTb5ubd6OjJ6Ofh6erE6efr6efs6ujs6%2Bju6%2B3B6%2Brj6%2Bnt6%2Brl7O6%2F6%2Bvk7Ovr7Ovu7evu7vDC7ezv7e3p7u7p7%2B7w7%2B7x7%2B%2Fx8fPF8PDt8e%2F18fDs8fDy8fDz8%2FXC8fHx8%2FD38vLt8vLu8vL08%2FPt8%2FPu9PP09vjI9fXw9vX09vbx9%2FX59%2Fby9%2Fb39%2Ffx%2BPf4%2Bfj5%2Bfj6%2Bfnz%2Bfn1%2Bfr1%2B%2F7K%2Bvr2%2B%2Fr5%2FPr%2F%2B%2Fv7%2FP%2FR%2Ffv%2B%2Ffz9%2F%2F7%2FFnlyNwAAAAF0Uk5TAEDm2GYAAAABYktHRACIBR1IAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH2gMHDxg0DfX8UQAAAUBJREFUKM9jYCAIGBl5hLgYGTGEuXViWpvTzZVZUaUYFdofXzu4YW53o5kgsgyj3LIXFy8e2rphclaZBS%2BSDHvlsxvPb5%2FbtXFKQVyGHkKCUev08TtPbp3Yu7KtKMnfUxIuw2h19MCVu9dP7Z41c3qeR5AmXII1YdOuy%2Ffv3dyyZMfqLhdvA7gEZ%2BzC9UcePrp6%2Fuz%2B%2BdWOXiYIo%2Bz7F%2B%2B8cGb5qkUz6vxsww0REvpN9b3zjq3oKU7zcbLOVEdIyOSkZk%2FatrYj1NfZNiCYH8mDuvmB1Qv2TYxwsHGt0kDyICObUXz01KUT3OyiarVRAouJWc0ysSU3JNldBT18mYUL16ybXaHEiBkfNQ%2B290WKyGNKNFzaMy1FQhZTovzp4TklUtKYEp0nN5eaCihiSPBxiIapMopjSw5iLIzGhBMNAHCEbLFZHo5dAAAAAElFTkSuQmCC" alt="Info" border="0" /\x3E %1\x3C/div\x3E',
'\x3Cinput style="font-size:10px;" id="menu_string_to_search" type="text" size="12" onkeydown="return menu_key_hook(event)" onfocus="menu_search_focus(true)" onblur="menu_search_focus(false)" /\x3E&nbsp;\x3Cinput style="font-size: 10px;" class="woas_button" type="button" value="Search" onclick="menu_do_search()" /\x3E',
'/* CSS used when loading/saving WoaS - readonly */\nbody {\n	font-family: Georgia, verdana;\n}\n\n#woas_custom_accesskeys {\n	visibility: hidden;\n	display: none;\n}\n\n#woas_wiki_area {\n	position: relative;\n	left: 15.5%;\n	width: 82.3%;\n	z-index: 0;\n	margin-top: 1.313em;\n	margin-bottom: 0.75em;\n	text-align:justify;\n}\n\n#woas_wiki_header {\n	top: 0em;\n	left: 0em;\n	border: 1pt solid #aaa;\n	opacity:.65;\n	z-index: 1;\n	width: 99%;\n	padding-right:0.75em;\n}\n\n#loading_overlay {\n	position: absolute;\n	width: 99%;\n	height: 100%;\n	padding-top: 25%;\n	z-index: 100;\n	background-color: white;\n	left: 0em;\n	top: 0em;\n	font-size: large;\n	text-align: center;\n}\n\n.woas_wait_text {\n	color: navy;\n}\n\n.woas_background, #woas_debug_panel, #woas_debug_console, #woas_debug_log,\n#woas_edit_page_title, .woas_editor_button, #woas_pwd_query, #woas_pwd_mask,\n.woas_password_desc,.woas_about_footer,.woas_help_background,.woas_help_button,\n.woas_helpcode,.woas_helptext, .woas_page_mts, div.woas_text_area, .woas_text_area.locked,\n#woas_pwd_mask,.woas_core_page,div.menu,#woas_menu_area,.woas_menu_button,#woas_title,\n.woas_nowiki, .woas_nowiki_multiline,pre.woas_search_results,div.woas_toc,p.woas_toc_title,\ntable.woas_text_area,div.woas_taglinks { }',
'== Existing pages\n\x3Clabel for="woas_import_erase"\x3E\x3Cinput type="radio" id="woas_import_erase" name="woas_import_overwrite" onclick="woas.importer.i_overwrite=0"\x3EErase all user pages before import process\x3C/label\x3E\n\x3Clabel for="woas_import_ignore"\x3E\x3Cinput type="radio" id="woas_import_ignore" name="woas_import_overwrite" onclick="woas.importer.i_overwrite=1" checked="checked"\x3EIgnore\x3C/label\x3E\n\x3Clabel for="woas_import_overwrite"\x3E\x3Cinput type="radio" id="woas_import_overwrite" name="woas_import_overwrite" onclick="woas.importer.i_overwrite=2"\x3EOverwrite\x3C/label\x3E\n\x3Clabel for="woas_import_ask"\x3E\x3Cinput type="radio" id="woas_import_ask" name="woas_import_overwrite" onclick="woas.importer.i_overwrite=3"\x3EAsk before overwrite\x3C/label\x3E\n== Security\n\x3Clabel for="woas_cb_import_comment_js"\x3E\x3Cinput type="checkbox" id="woas_cb_import_comment_js" checked="checked"\x3EComment JavaScript tags\x3C/label\x3E\n\x3Clabel for="woas_cb_import_comment_macros"\x3E\x3Cinput type="checkbox" id="woas_cb_import_comment_macros" checked="checked"\x3EComment macro definition/call\x3C/label\x3E\n\x3Clabel for="woas_cb_import_woas_ns"\x3E\x3Cinput type="checkbox" id="woas_cb_import_woas_ns" checked="checked"\x3EImport pages of the [[WoaS::]] namespace ([[WoaS::Hotkeys|hotkeys]], [[WoaS::Plugins|plugins]], [[WoaS::Aliases|aliases]] and [[WoaS::CSS::Custom|custom CSS]])\x3C/label\x3E',
'\x3Cbig style="color:lime"\x3E%1 had a little %2\x3C/big\x3E',
'== AES encryption\n[[http://en.wikipedia.org/wiki/Advanced_Encryption_Standard|AES]] 256-bit high security data encryption standard is supported to protect a single page or groups of them.\n\nWoaS has two different interfaces for encryption passwords, namely one for encryption (called *Locking*) and one for decryption (called *Unlocking*).\n\n== Encryption\n\nAn interface for page encryption shows also a colored meter for estimated password quality; encryption password is not stored in your saved files (true AES encryption is used).\n\n[[Include::WoaS::Template::Info|*If you forget the password there is no way to retrieve it or your data.*]]\n\nIn order to encrypt a page you will have to click on the /lock/ icon in the navigation bar. The page locking interface will have two password boxes (the second for verification); the background color of the first box will hint about password estimated strength.\n\nOnce you\'ve chosen and typed in your password, click the *Lock* button to encrypt the page.\n\nWhen encrypting additional pages during a session, you\'ll be offered to use the same password you used to encrypt the first page. Accept by pressing the *OK* button or decline by pressing the *Cancel* button. If you decline, the password interface will open, allowing you to set a new password for the page. There is no limit to how many passwords you can use.\n\n== Decryption\n\nClick the *Set password* icon in the navigation bar to set the decryption password used for next page.\n\nThe password for decrypting a page (or pages) is cached so that you only need to enter it once per session. If you close or reload the wiki, you will need to reenter the password to work with the encrypted page(s). To disable the password cache, check the box for *Do not temporarily cache the AES key* in the wiki [[WoaS::Help::Special::Options|Options]]. If you\'ve used more than one password for encrypting your pages, you\'ll need to enter the password to unlock each encrypted page.\n\n== Security level\nSecurity is never absolute. Data encrypted with these pages might be compromised in a variety of ways, including but not limited to the following:\n\n* WoaS uses random numbers generated by the browser itself, and they are not secure (user-generated entropy will be used in future versions)\n* User-generated passwords are weak by nature (PBKDF2 will be used in future)\n* You have not disabled the AES key cache, so when you will enter the password for the first time other users (if not you) will be able to see encrypted pages and could possibly get your key\n* Your Web browser and/or JavaScript interpreter may contain bugs or deliberate security violations which report activity on your computer back to some other Internet site\n* Some other applet running on another page in your browser, perhaps without you being aware of its existence, is spying on other windows\n* Some other "spyware" application running on your computer may have compromised your system\'s security and be snooping on your activity\n* The implementation of the encryption may contain a bug which makes its results insecure, Wiki on a Stick is open-source, you can judge for yourself whether the tool merits your confidence.\n* Your computer\'s security may have been compromised physically; when\'s the last time you checked that a bug that transmits your keystrokes and/or screen contents to that white van parked down the street wasn\'t lurking inside your computer cabinet?\n\nApart from the above, your normal usage of encrypted pages can be considered secure.',
'= Deprecated syntax\n\nA summary of WoaS deprecated syntax; you are invited to _no more_ use deprecated syntax which is still supported because they will be declared /unsupported/ in some future release of WoaS.\x3Cstyle type="text/css"\x3E\n.bad_syntax {\n  color: red;\n  font-weight: bold;\n}\n.good_syntax {\n  color: green;\n  font-weight: bold;\n}\n\x3C/style\x3E\n== Lists\n\nLists with \'+ sign are *unsupported*.\n\x3Cspan class="bad_syntax"\x3E{{{\n+ unordered lists with + sign\n+ unordered lists with + sign\n}}}\x3C/span\x3E\n\x3Cspan class="good_syntax"\x3E{{{\n* unordered lists with an asterisk\n* unordered lists with an asterisk\n}}}\x3C/span\x3E\n== Headers\n\x3Cspan class="bad_syntax"\x3E{{{! headers using the exclamation mark}}}\x3C/span\x3E\n\n\x3Cspan class="good_syntax"\x3E{{{= header which uses the equal sign}}}\x3C/span\x3E\n== Pre tags\n{{{\x3Cpre /\x3E}}} acting as nowiki blocks are *unsupported*. Now {{{\x3Cpre /\x3E}}} tags have no special meaning and are treated as any other XHTML tag.\n\n\x3Cspan class="bad_syntax"\x3EPre tags acting as &lt;pre&gt;nowiki&lt;pre &#047;&gt; blocks.\x3C/span\x3E\n\n\x3Cspan class="good_syntax"\x3EYou should use {\x3C!-- --\x3E{{nowiki}}\x3C!-- --\x3E} blocks instead.\x3C/span\x3E\n== Rulers\n\x3Cspan class="bad_syntax"\x3E{{{---\nhorizontal rulers using only 3 hyphens}}}\x3C/span\x3E\n\n\x3Cspan class="good_syntax"\x3E{{{----\nYou are invited to use at least 4 hyphens\n}}}\n== Tags\n\x3Cspan class="bad_syntax"\x3E{{{[[Tag::some tag|other tag|another tag]]}}}\x3C/span\x3E\nThis syntax does not support transclusion of Tagged:: namespace; still supported but deprecated.\n\n\x3Cspan class="good_syntax"\x3E{{{[[Tag::some tag, some other tag]] and [[Tag::some tag]]}}}\x3C/span\x3E\n\n== Inline scripts\n\x3Cspan class="bad_syntax"\x3E{{{\x3Cscript\x3E...\x3C/script\x3E}}}\x3C/span\x3E\n\n\x3Cspan class="good_syntax"\x3E{{{\x3Cscript type="text/javascript"\x3E...\x3C/script\x3E}}}\x3C/span\x3E\n\nThis is not enforced by WoaS wiki parser but is instead a correct XHTML practice.\n',
'= Options\n\n=== Use consistent newlines in saved data\nForces pages to be saved using consistent newlines splitting. This is particularly useful if you use a versioning system like Subversion (SVN), git or CVS.\n\n=== Double click to edit pages/menu\nAllows to switch to edit mode when double-clicking the page content or menu area.\n\n=== Automatically save options (if changed) before quitting\nAllows option changes to be automatically saved without having to click the *Save options* button at the bottom of the page.\n\n=== Do not temporarily cache the AES key\nClears AES key right after decryption, useful if you leave WoaS open to possible unauthorized access; will require to re-enter the decryption key each time you want to read a page.\n\n=== Fixed menu area and navigation bar\nFixes the menu and navigation bar in place, while allowing the page to scroll.\n\n=== Return to last saved page when quitting\nAutomatically opens the last saved page.\n\n=== Enable cumulative save\nWill save to disk only when quitting\n\n=== Disallow edits (button)\nPuts the wiki into /server mode/; users will be told that it is not possible to save their changes, but editing will be allowed for sandbox (testing) purposes.\n',
'= WSIF\n\n*W*iki on a *S*tick *I*nterchange *F*ormat is the preferred format for exporting full WoaS data into a single file and share it with whoever you like.\n\n== Format description\n\nWSIF format is similar to MIME email attachments format; each line comes like an /email header/ definition, as in following example:\n\n{{{header.name: header-value}}}\n\nAll WSIF content must be ASCII, so /header-value/ must be ECMA-escaped when necessary; this corresponds to escaping slashes "\\" to "\\\\" and any UTF-8 sequence to the corresponding "\\u0000" string, where /0000/ is the lowercase hexadecimal value of the UTF-8 character (like in ECMAScript).\n\nWSIF files can contain custom headers, given that the following namespaces are currently reserved for official WSIF format support:\n* *wsif*\n* *woas*\n\nFor example you could define a new namespace for header definitions called /custom/:\n\n{{{\ncustom.x: 100\ncustom.y: 200\ncustom.content: Hello world!\n}}}\nIt is up to you then to parse such header-value couples and give them proper representation.\n== Inline boundaries\n\nA line starting with two dashes (/--/) introduces an *inline boundary*, which is a properly encoded snippet.\n\nExample boundary:\n\n{{{\n--my-random-id\nthis is the inline snippet\n--my-random-id\n}}}\n/my-random-id/ must be a (usually random) string *unique inside the boundary content*, but it does not need to be unique inside the whole WSIF file, which must be read sequentially (like in a state machine).\n\n= wsif namespace\n\nThe *wsif.* namespace currently defines only an information header:\n\n* *wsif.version* - WSIF format version into which content is offered\n* *wsif.generator* - program/library used to generate WSIF content\nVersion 1.1.0 and above are currently supported.\n= woas namespace\n\nThe *woas.* namespace defines general WoaS content properties and specific WoaS pages; the following headers are defined for general context:\n\n* *woas.version* - version of WoaS used to produce the WSIF file\n* *woas.pages* - total count of WoaS pages stored inside WSIF file\n\nThe following are header definitions specific for WoaS pages:\n\n* *woas.page.title* - ECMA-escaped page title; this header has a special meaning because it is the page start marker inside the state machine; it is suggested that WSIF implementations write it as first header for page definition stanzas;\n* *woas.page.attributes* - a positive integer value in decimal format specifying the page attributes;\n* *woas.page.last_modified* - a positive integer value in decimal format representing seconds lasted from the UTC epoch up to last modified time; this header is defined only if data is available from WoaS;\n* *woas.page.length* - content length of the inline snippet; only inline files define it; this is not the decoded page length but the length of the content inside the inline boundaries;\n* *woas.page.original_length* - real page length as seen by WoaS; it is currently defined only for encrypted pages;\n* *woas.page.encoding* - snippet encoding format, see the *Encoding* section below;\n* *woas.page.disposition* - tells whether page is /inline/ or /external/;\n* *woas.page.boundary* - string used for inline boundary recognition (without leading dashes); this header is specified only for inline snippets;\n* *woas.page.disposition.filename* - filename used for external page content, not ECMA-escaped; defined only with external pages;\n* *woas.page.mime* - mime type of external pages which have a defined mime type, like images for example;\n\nAll headers are mandatory (under their specific state definition branch), except /last_modified/ and /original_length/.\n\n= Encoding\n\nThe *woas.page.encoding* header can contain one of the following values:\n\n* *8bit/plain* - data is ASCII text which can use 8bits to represent characters; only ASCII text should be used and not UTF-8;\n* *ecma/plain* - data is ECMA-escaped UTF-8 text;\n* *8bit/base64* - data is base64-encoded binary data;\n* *text/wsif* - data is WSIF (currently used only for external WSIF pages)\n\nPages or header values are encoded in *ecma/plain* format when UTF-8 sequences are found inside the value string, otherwise *8bit/plain* is used; *8bit/base64* is instead used for binary files or encrypted pages.\n\n= Usage notes\n\nWSIF format is pretty straightforward to produce or interpret, however please consider the following facts:\n\n* *woas.page.disposition.filename* must be plain ASCII (this limitation might be removed in future versions of the format) and should be a relative filename;\n* it is safe to embed a WSIF file inside another, given that boundaries are properly implemented\n\nWoaS project offers an official PHP library (libwsif) for reading/writing WSIF files, as well as a documented example.',
'= Macros\n\nWiki on a Stick (version 0.10.7 and above) supports wiki macros for pluggable wiki syntax.\n\n== Definition\n\nA macro block is defined as follows:\n\n{{{\x3C\x3C\x3C ... the macro content here ... \x3E\x3E\x3E}}}\n\nHowever the above macro example is not parsed in any particular way by WoaS. If you want to run a plugged-in parser on the macro content you should use this syntax instead:\n\n{{{\x3C\x3C\x3Cmacroname:\n24\n56\n89\n28\n19\n33\n\x3E\x3E\x3E}}}\n\n= Real example\n\nFor example, let\'s define the sum macro:\n\n{{{\x3C\x3C\x3C%my_sum:\n\n  var elements = macro.text.split(/[\\s\\n]+/), total = 0;\n  for (var i=0,l=elements.length;i\x3Cl;++i) {\n    if (!elements[i].length) continue;\n    try { total += parseInt(elements[i]); } catch(e) { }\n  }\n  macro.text = total.toString();\n\x3E\x3E\x3E}}}\n\x3C\x3C\x3C%my_sum:\n\n  var elements = macro.text.split(/[\\s\\n]+/), total = 0;\n  for (var i=0,l=elements.length;i\x3Cl;++i) {\n    if (!elements[i].length) continue;\n    try { total += parseInt(elements[i]); } catch(e) { }\n  }\n  macro.text = total.toString();\n\x3E\x3E\x3E\nNote that macro definition happens with the special \'%\' sign before the macro name. Macros cannot be overriden with the macro definition.\nSuch defined macro uses the macro object which has two standard properties:\n* *reprocess* - set to true when text can be re-processed by WoaS parser (default false)\n* *text* - the content of macro block after the double colon\n\nNow we call the macro:\n\n{{{\x3C\x3C\x3Cmy_sum:\n24\n56\n89\n28\n19\n33\n\x3E\x3E\x3E}}}\n\nAnd this is the actual result:\n\x3C\x3C\x3Cmy_sum:\n24\n56\n89\n28\n19\n33\n\x3E\x3E\x3E\n\n== Macros with arguments\nStarting with WoaS 0.12.0 macros do support named arguments and should return a true/false value to tell about their success/failure at rendering the macro text.\n\nExample:\n{{{\x3C\x3C\x3C%my_arg_macro(name, color):\n\nmacro.text = "\x3Cstrong\x3E"+name+"\x3C/strong\x3E says: \x3Cspan style=\\"color: "+color+"\\"\x3E"+macro.text+"\x3C/span\x3E";\n\nreturn true;\n\n\x3E\x3E\x3E}}}\x3C\x3C\x3C%my_arg_macro(name, color):\nmacro.text = "\x3Cstrong\x3E"+name+"\x3C/strong\x3E says: \x3Cspan style=\\"color: "+color+"\\"\x3E"+macro.text+"\x3C/span\x3E";\nreturn true;\n\x3E\x3E\x3E\nWhich is then called with:\n\n{{{\x3C\x3C\x3Cmy_arg_macro("Simon", "green"):\nI have 3 fruits in my basket\n\x3E\x3E\x3E}}}\nAnd the result is:\n\n\x3C\x3C\x3Cmy_arg_macro("Simon", "green"):\nI have 3 fruits in my basket\n\x3E\x3E\x3E\n\nThe passed arguments must be valid JavaScript expressions.\n\n== Global macros\nMacros can be available on all pages if they are registered by plugins.\n\nIn order to define a global macro you should create or edit a plugin and then add JavaScript code like this:\n\n{{{woas.macro.register(\'my.arg_macro\', function(macro, name, color) {\nmacro.text = "\x3Cstrong\x3E"+name+"\x3C/strong\x3E says: \x3Cspan style=\\"color: "+color+"\\"\x3E"+macro.text+"\x3C/span\x3E";\nreturn true;\n});\n}}}\n',
'= Help index\nThe first time you save a change to Wiki on a Stick, your browser will ask for permission to save data to the disk. This is normal and necessary for the wiki to make changes to itself. You can give permission each time or make the setting permanent.\n\nYou will find the *Help* icon in the navigation bar on every page in this wiki. When you click on it, it will either open this page or a specific help page that explains how to work with the page you currently have open.\n\nWhen browsing the pages in this Help pop-up, you will find a *Back* button in the upper right corner that takes you back to this page or a *Close* button in the same place that closes the pop-up.\n== Contents:\n* *Features*\n** [[WoaS::Help::Security]]\n** [[WoaS::Help::WSIF]]\n* *Interface*\n** [[WoaS::Help::Aliases]]\n** [[WoaS::Help::CSS]]\n** [[WoaS::Help::Edit the menu]]\n** [[WoaS::Help::Editor]]\n** [[WoaS::Help::Export]]\n** [[WoaS::Help::General usage]]\n** [[WoaS::Help::Hotkeys]]\n** [[WoaS::Help::Import]]\n** [[WoaS::Help::Requirements]]\n* *Online use*\n** [[WoaS::Help::Publish wiki online]]\n** [[WoaS::Help::Run wiki on a webserver]]\n* *Page management*\n** [[WoaS::Help::Create a page]]\n** [[WoaS::Help::Delete a page]]\n** [[WoaS::Help::Edit a page]]\n** [[WoaS::Help::Erase all pages]]\n** [[WoaS::Help::Namespaces]]\n** [[WoaS::Help::Page scrolling]]\n** [[WoaS::Help::Rename a page]]\n* *Specific Help pages*\n** [[WoaS::Help::Special::Advanced]]\n** [[WoaS::Help::Special::Dead Pages]]\n** [[WoaS::Help::Special::Erase Wiki]]\n** [[WoaS::Help::Special::ExportWSIF]]\n** [[WoaS::Help::Special::ImportWSIF]]\n** [[WoaS::Help::Special::Options]]\n[[Include::WoaS::Help::Syntax]]\n* *Troubleshooting*\n** [[WoaS::Help::Wiki is read-only]]',
'* *Syntax*\n** [[WoaS::Help::Deprecated syntax]]\n** [[WoaS::Help::Headers]]\n** [[WoaS::Help::Images]]\n** [[WoaS::Help::Include text file]]\n** [[WoaS::Help::Include web page]]\n** [[WoaS::Help::Include wiki page]]\n** [[WoaS::Help::JavaScript]]\n** [[WoaS::Help::Join lines of text]]\n** [[WoaS::Help::Links]]\n** [[WoaS::Help::Lists]]\n** [[WoaS::Help::Macros]]\n** [[WoaS::Help::Nowiki text]]\n** [[WoaS::Help::Rulers]]\n** [[WoaS::Help::Table of Contents]]\n** [[WoaS::Help::Tables]]\n** [[WoaS::Help::Tags]]\n** [[WoaS::Help::Text formatting]]\n** [[WoaS::Help::Transclusion]]',
'= Advanced\nThe Advanced page offers some tools for administration of your wiki. Descriptions are provided, and most of the Special links lead to pages that have individual help available. Below are links to help pages for the ones that don\'t offer individual help automatically.\n* *WoaS::CSS::Core* and *WoaS::CSS::Custom* -  See the [[WoaS::Help::CSS]] page.\n* *WoaS::Plugins* - See the [[WoaS::Help::Plugins]] page.\n* *WoaS::Aliases* - See the [[WoaS::Help::Aliases]] page.\n* *WoaS::Hotkeys* - See the [[WoaS::Help::Hotkeys]] page.\n* *Special::Erase Wiki* - See the [[WoaS::Help::Special::Erase Wiki]] page\n',
'= Erase Wiki\nWhen you click the *Special::Erase Wiki* link in the {{{[[Special::Advanced]]}}} page you can erase all user-created pages. Doing this will also erase the {{{[[Main Page]]}}} and all content from the menu, which will then be re-populated with some common-use Special:: links. Global core CSS settings will be preserved, while the {{{[[WoaS::CSS::Custom]]}}} page will be reset to default values.\n\nIf you have not given permanent permission to your browser for editing wiki pages, you will receive a security warning when attempting to erase the wiki. If you deny permission to the browser, you will receive a message that saving the file failed. You will be returned to your wiki, but the Main Page will be blank. Reloading the wiki after a failure to save restores WoaS to the previous status.\n',
'= CSS\nThe look and feel of WoaS can be customized by specifying custom CSS in the [[WoaS::CSS::Custom]] reserved page. You shall use WoaS core CSS definitions available in [[WoaS::CSS::Core]] as a reference when you are writing CSS customization.\n\n\x3C\x3C\x3Cwoas.include:WoaS::Template::Info\nA tutorial about CSS is available at [[http://www.w3schools.com/css/]].\x3E\x3E\x3E\n== Customizing CSS\n# Access [[WoaS::CSS::Core]] either directly or from [[Special::Advanced]]\n# Copy the CSS definitions you want to customize\n# Access [[WoaS::CSS::Custom]] either directly or from [[Special::Advanced]]\n# Edit the page and paste the classes and ids you copied in previous step\n# Define the rules for the CSS classes and ids.\n# Save your changes by clicking the *Save* icon in the navigation bar.\n\nAll changes will be visible immediately.\n\n== Restore the default CSS settings\nYou can always restore WoaS default CSS by deleting the content of [[WoaS::CSS::Custom]].\n\n== Import CSS rules into your wiki\nYou can import the CSS rules from another copy of WoaS by following the instructions for [[WoaS::Help::Import|importing]] another wiki into the current one and placing a check in the box next to *Import also CSS.*\n\nImporting from older versions of WoaS can garble the layout of the wiki. There are two ways to avoid this:\n# Manually transfer the CSS from the older version of WoaS into the [[WoaS::CSS::Custom]] page by copying and pasting it, checking the class and id of each entry against the default CSS rules on the [[WoaS::CSS::Core]] page to verify that it\'s still in use before saving your changes.\n# Create a new page with style tags in it and paste your CSS into that page to test it before adding it to the [[WoaS::CSS::Custom]] page.\n\n=== Create a test page for your custom CSS\n# Access [[Special::Options]] either directly or from [[Special::Advanced]]\n# Make sure that the box is /not/ checked next to *Remember current page and navigation history*. Save the change if you had to uncheck the box.\n# Create a [[Special::New Page|new page]]\n# Insert this opening tag at the top of the page:\x3Cbr/\x3E{{{\n\x3Cstyle type="text/CSS"\x3E\n}}}\n# Paste your CSS rules beneath the tag.\n# Insert this closing tag at the bottom of the page:\x3Cbr/\x3E{{{\n\x3C/style\x3E\n}}}\n# Save changes\n\nOnce you have tested your CSS on a single page you can choose whether to move such CSS definitions to [[WoaS::CSS::Custom]] or not.',
'= Dead Pages\nThis page displays a list of pages that contain links to nonexistent pages. Both the link and the page it\'s on are shown in each entry.\n\nFixing dead pages is a good way to not break end-user browsing experience.',
'= General usage\n\n== Cancel any action\nCancel any action by clicking the *Cancel* icon in the navigation bar, clicking the *Cancel* button on any alert that pops up, or pressing the *Esc* key.\n\n== Navigation\nUse the *Back* and *Forward* arrow icons in the navigation bar to go back or forward one page, the *Main Page* icon in the navigation bar to return to the {{{[[Main page]]}}}, or click the buttons and links in the menu and wiki pages.\n\n== Pages linking to the current page\nClick the *Backlinks* link in the menu to see which pages link to the page you currently have open.\n\n== Print a page\nClick the *Print* icon in the navigation bar. A new browser window will open with a printer-friendly version of the main text area. Use the browser\'s print functions to print the page.\n\n== Syntax\nThis wiki accepts plain text, WoaS wiki syntax, and XHTML. See the *Syntax* section of the [[WoaS::Help::Index|Help index]] by clicking the *Help* icon in the navigation bar for help with using the syntax in your pages.\n\n[[Include::WoaS::Template::Info|WoaS syntax is partially compatible with Creole 0.1 syntax.]]\n',
'= Nowiki text\n/Nowiki text/ is a section of raw or preformatted text that is not parsed as wiki syntax. Anything you place inside nowiki brackets will be displayed in monospace font exactly as you typed it.\n\n== Multi-line blocks\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page:\x3C/div\x3E\n\x3Cdiv class="woas_helpcode"\x3E{\\\n{{\nThis is a code block\nmade of one\nor more lines.\n}}\\\n}\x3C/div\x3E\n\x3Cdiv class="woas_helptext"\x3ETo create this:\x3C/div\x3E\n{{{\nThis is a code block\nmade of one\nor more lines.}}}\n== Inline nowiki blocks\n\nA normal sentence can contain an inline nowiki block, like {{\x3C!-- --\x3E{this block}}\x3C!-- --\x3E}.\n\nExample (with the mathematical equation inside a nowiki block):\n\nThe equation {{{E = mc^2}}} indicates that energy always exhibits mass in whatever form the energy takes.\n\n== Nowiki blocks inside lists\nA nowiki block can be added to a list item by inserting the opening curly brackets at the end of the list item.\n\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page:\x3C/div\x3E\n\x3Cdiv class="woas_helpcode"\x3E* List item:&#123;&#123;&#123;\nThis is a code block\nmade of one\nor more lines.\n&#125;&#125;&#125;\x3C/div\x3E\n\x3Cdiv class="woas_helptext"\x3ETo create this:\x3C/div\x3E\n* List item:{{{\nThis is a code block\nmade of one\nor more lines.\n}}}\n\nTo insert a newline between the list item and the nowiki block, you can use HTML {{{\x3Cbr\x3E}}} tags before the opening curly brackets.\n\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page:\x3C/div\x3E\n\x3Cdiv class="woas_helpcode"\x3E* List item:&lt:br&gt;&lt;br&gt;&#123;&#123;&#123;\nThis is a code block\nmade of one\nor more lines.\n&#125;&#125;&#125;\x3C/div\x3E\n\x3Cdiv class="woas_helptext"\x3ETo create this:\x3C/div\x3E\n* List item:\x3Cbr\x3E\x3Cbr\x3E{{{\nThis is a code block\nmade of one\nor more lines.\n}}}\n\n== Nowiki block with custom CSS style\nThe example uses /lightcyan/ and /purple/ colors, but you can replace those with whatever colors you like, using [[http://www.w3schools.com/Html/html_colornames.asp|color names or hexadecimal notation]].\n\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page:\x3C/div\x3E\x3Cdiv class="woas_helpcode"\x3E\n&lt;span style="background-color:lightcyan; color:purple;"&gt;&#123;&#123;&#123;\nThis is a code block\nmade of one\nor more lines.\n&#125;&#125;&#125;&lt;/span&gt;\n\x3C/div\x3E\n\x3Cdiv class="woas_helptext"\x3ETo create this:\x3C/div\x3E\n\x3Cspan style="background-color:lightcyan; color:purple;"\x3E{{{\nThis is a code block\nmade of one\nor more lines.\n}}}\x3C/span\x3E\n',
'= Wiki is read-only\nIf you have a read-only copy of WoaS and you want a write-enabled copy you should ask the author to provide it; read-only protection is by no way a security mechanism (see [[WoaS::Help::Security]]), but instead a /fair use/ invite e.g. you disable the write features because you assume that they won\'t be useful to end-user.\n\nIf you want to re-enable the write features manually you should:\n# Back up your WoaS\n# Open it in a text editor\n# Do a search for: {{{"permit_edits":false,}}}\n# Replace it with: {{{"permit_edits":true,}}}\n# Save the file\n\n*NOTE:* this way of re-enabling WoaS write mode is *unsupported*, e.g. it might not work in future versions of WoaS.',
'= Create a page\n# Access [[Special::New Page]]\n# Type the page title. Maximum length is 256 characters, although you might want to use a shorter title to not interfere with the navigation bar. Page titles are case sensitive, all characters are allowed except angled brackets, square brackets, curly braces and pipe characters ("&lt;&gt;[]{}|")\n# Press the *Enter* key or click the *OK* button\n# When asked if you\'d like to add a link into the main menu, accept by pressing the *Enter* key or clicking the *OK* button, or refuse by pressing the Escape key or clicking the *Cancel* button\n# Edit the contents of the main text area by typing normally. You can freely change the default text (a level one header with the page name)\n# Use the editor\'s buttons to insert wiki syntax to format the text or type the wiki syntax manually (plain text does not need any wiki syntax)\n# Save your changes\n\n== Create a missing page\n\nAn alternate method of creating a page is to type a new page title inside square brackets and then attempt to access the unexisting page.\n\n*Example:*\n{{{\n[[My new page]]\n}}}\n\x3Cdiv class="woas_helptext"\x3EIs rendered as:\x3C/div\x3E\n[[My new page]]\n\n= Troubleshooting\nThere are some issues which could prevent you from creating a page.\n\n* You didn\'t type a title for the page in the input box that popped up\n* There is already a page by that title\n* The wiki is being run in [[WoaS::Help::Publish wiki online|read-only mode]], so no changes are allowed. See the [[WoaS::Help::Wiki is read-only]] page for instructions on how to return the wiki to editing mode\n* You are trying to use a [[WoaS::Help::Namespaces|namespace]] that is reserved by WoaS. In this case a message will pop up letting you know the namespace is reserved and giving you an opportunity to choose a different title/namespace for the page\n',
'= By action link\n# Access [[Special::Delete Page]] directly or either from [[Special::Advanced]]\n# You\'ll be asked for the exact title of the page and then for confirmation\n# When the page is deleted, you\'ll be returned to the page you were on before deleting the page.\n\n= By editing\n# Edit the page you wish to delete\n# Select everything inside the main text area and delete it\n# Save your changes, you will be asked for deletion confirmation\n\n= References to deleted pages\nAccess [[Special::Dead Pages]] directly or either from [[Special::Advanced]] to remove references to the deleted page',
'= Editor features\n* Cancel all changes by clicking the *Cancel* icon in the navigation bar at any time.\n* Change the title of a page by editing the contents of the *Page title* box.\n* Save all changes by clicking the *Save* icon in the navigation bar at any time.\n* See an example of how tables are built by clicking the *About tables* link.\n* Toggle the display of the button rows by clicking the *Show/Hide wiki buttons|Show/Hide XHTML buttons* links.\n\n== Wiki buttons\nButtons to ease printing [[WoaS::Help::Syntax|wiki syntax]].\n{|\n|+ Wiki Code buttons\n| *Button* || *What it does*\n| Wiki URL || Add a wiki link to a page.\n| URL || Add an external link to a page.\n| {{{=}}} || Insert wiki syntax for a level one header. Use this at the beginning of any line.\n| {{{==}}} || Insert wiki syntax for a level two header. Use this at the beginning of any line.\n| Img || Include a wiki image in the page.\n| *B* || Wrap selected text with bold syntax.\n| /i/ || Wrap italic text with bold syntax.\n| u || Wrap selected text with underline syntax.\n| &#123;&#123;&#123; &#125;&#125;&#125; || Wrap wiki syntax around the selected text to make it nowiki (raw) text.\n| * || Insert the wiki syntax for a bulleted list item.\n| # || Insert the wiki syntax for a numbered list item.\n| @ || Insert the wiki syntax for an alphanumeric list item.\n| Ruler || Insert a ruler (horizontal line). Use this at the beginning of any line.\n| Tags || Interface for inserting tags in your page to categorize it. Use a comma between multiple tags.\n|}\n\n*Note:* it\'s recommended to use [[WoaS::Help::Syntax|wiki syntax]] instead of XHTML syntax, because this is a wiki.\n\n== XHTML buttons\n{|\n|+ XHTML Code buttons\n| *Button* || *What it does*\n| *B* || Wrap selected text with bold tags.\n| /i/ || Wrap selected text with emphasis tags (italics).\n| u || Wrap selected text with underline tags.\n| s || Wrap selected text with strike-through tags.\n| Sup || Wrap selected text with superscript tags (appearing half a character above the baseline).\n| Sub || Wrap selected text with subscript tags (appearing half a character below the baseline).\n| Left || Wrap selected text with a left-aligned div.\n| Center || Wrap selected text with a center-aligned div.\n| Right || Wrap selected text with a right-aligned div.\n| Justify || Wrap selected text with a justified div.\n| List || Add unordered list tags around the selected text.\n| List = || Add ordered list tags around the selected text.\n| li[*] || Wrap selected text with list item tags.\n| Blockquote || Wrap selected text with blockquote tags, indenting it beneath the current line.\n| Image || Include an external image.\n| Link || Include an external link URL.\n|}',
'= Edit a page\nAll user-created pages (and some which are part of reserved [[WoaS::Help::Namespaces|namespaces]]) can be edited.\n\nIf a page is editable the relative Edit icon will be available in the navigation bar; you start editing the page by clicking such icon. The user interface will switch to an [[Woas::Help::Editor|editor-mode set of buttons and edit areas]].\n\n== Editing\n\nIn order to edit pages, click the *Edit* icon in the navigation bar. You can find all the user pages by accessing [[Special::All Pages]] directly or either from [[Special::Advanced]].\n# Click the *Edit* icon in the navigation bar or double-click on the page if you\'ve enabled that setting in the wiki Options.\x3Cbr\x3E\x3Cbr\x3E\n# Edit the contents of the main text area by typing normally.\x3Cbr\x3E\x3Cbr\x3E\n# Use the editor\'s buttons to insert code to format the text, or type the code in manually. Note that the use of plain text doesn\'t require any code.\x3Cbr\x3E\x3Cbr\x3E\n# Save your changes by clicking on the *Save* icon in the navigation bar.\n\n== Deleting\nSee [[WoaS::Help::Delete a page]].',
'= Erase all pages\nTo erase all user-created pages in this wiki and start fresh:\n# Access [[Special::Erase Wiki]] directly or either from [[Special::Advanced]]\n[[Include::WoaS::Template::Info|Make sure you are using a wiki you don\'t mind losing. If not, make a copy and erase the copy.]]\nSee also the [[WoaS::Help::Special::Erase Wiki]] page.\n',
'= Edit the menu\n# Click the \x3Cspan class="woas_menu_button" style="font-size: medium"\x3EEdit menu\x3C/span\x3E button at the bottom of the menu\n# Edit the contents of the main text area; the menu page is like a normal wiki page\n# Save your changes by clicking on the *Save* icon in the top right navigation panel\n\nYou can use various styles for your menu. A detailed explanation of list styles can be found in the [[WoaS::Help::Lists]] page.\n\n== Add a sub-menu beneath the menu\nSee the *Namespace sub-menu* section of the [[WoaS::Help::Namespaces]] page.\n',
'= Headers\nBelow are examples on how to use wiki markup to create headers in this wiki. There are six levels of supported headers.\n\n{{{\n= Header level 1\n}}}\n= Header level 1 =\n\n{{{\n== Header level 2\n}}}\n== Header level 2\n\n{{{\n== Header level 3\n}}}\n== Header level 3\n\n{{{\n== Header level 4\n}}}\n== Header level 4\n\n{{{\n== Header level 5\n}}}\n== Header level 5\n\n{{{\n== Header level 6\n}}}\n== Header level 6\n',
'= Rulers \nRulers (horizontal rules or lines) use the full width of the page by default. They can be styled to a smaller width by surrounding them with HTML &lt;div&gt; tags. Styling can also control their position.\n\n== Standard ruler\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page:\x3C/div\x3E\x3Cbr\x3E{{{\n----\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create this:\x3C/div\x3E\x3Cbr\x3E\n----\n\n\n== Custom width ruler aligned left\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page, replacing 50% with the width you like:\x3C/div\x3E\x3Cbr\x3E{{{\n\x3Cdiv style="width:50%";\x3E\n----\n\x3C/div\x3E\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create this:\x3C/div\x3E\n\x3Cdiv style="width:50%";\x3E\n----\n\x3C/div\x3E\n\n== Custom width ruler aligned right\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page, replacing 50% with the width you like:\x3C/div\x3E\x3Cbr\x3E{{{\n\x3Cdiv style="width:50%; float:right;"\x3E\x3Chr\x3E\x3C/div\x3E\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create this:\x3C/div\x3E\n\x3Cdiv style="width:50%; float:right;"\x3E\x3Chr\x3E\x3C/div\x3E\n\n== Custom width centered ruler\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page, replacing 50% with the width you like:\x3C/div\x3E\x3Cbr\x3E{{{\n\x3Ccenter\x3E\x3Cdiv style="width:50%";\x3E\n----\n\x3C/div\x3E\x3C/center\x3E\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create this:\x3C/div\x3E\n\x3Ccenter\x3E\x3Cdiv style="width:50%";\x3E\n----\n\x3C/div\x3E\x3C/center\x3E\n',
'= Include text file\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page, replacing PATH with the full path to the file:\x3C/div\x3E\x3Cbr\x3E{{{\n\x3Ciframe height="400" width="100%" src="file:///PATH"\x3E\x3C/iframe\x3E\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo include a text file in the page inside an iFrame.\x3C/div\x3E\n* *Linux example:*\x3Cbr\x3E\x3Cbr\x3E{{{\n\x3Ciframe height="400" width="100%" src="file:///home/username/Desktop/file.txt"\x3E\x3C/iframe\x3E\n}}}\x3Cbr\x3E\n* *Windows example:*\x3Cbr\x3E\x3Cbr\x3E{{{\n\x3Ciframe height="400" width="100%" src="file:///C:/Users/Username/Documents/file.txt"\x3E\x3C/iframe\x3E\n}}}\n',
'= Include web page\nWeb pages can be included in a page as embedded objects or in iFrames.\n\n[[Include::WoaS::Template::Info|Some pages may not render correctly depending on how they\'re coded. Embedding more than one web page in a wiki page will cause the final embedded web page to steal the focus when the page is loaded, so it\'s recommended to have only one per page.]]\n== Embed a web page as an object\nThis method makes the embedded web page become a seamless part of the page.\n\n* *Embed a local HTML file as an object:*\x3Cbr\x3E\x3Cbr\x3E\n** \x3Cdiv class="woas_helptext"\x3EUse this code in a page, replacing PATH with the full path to the HTML file you wish to include, and changing the width and height to suit your taste:\x3C/div\x3E\x3Cbr\x3E{{{\n\x3Cobject data="PATH" style="width:100%; height:630px;"\x3EFile not found.\x3C/object\x3E\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo embed a local HTML file in a page.\x3C/div\x3E\x3Cbr\x3E\n*** For example, if you want to embed the {{{/Work/MyProject.html}}} file as an object, you\'d use this command:\x3Cbr\x3E\x3Cbr\x3E{{{\n\x3Cobject data="/Work/MyProject.html" style="width:100%; height:630px;"\x3EFile not found.\x3C/object\x3E\n}}}\x3Cbr\x3E\x3Cbr\x3E\n* *Embed a remote URL as an object:*\x3Cbr\x3E\x3Cbr\x3E\n** \x3Cdiv class="woas_helptext"\x3EUse this code in a page, replacing URL with the URL of the page you wish to include, and changing the width and height to suit your taste:\x3C/div\x3E\x3Cbr\x3E{{{\n\x3Cobject data="URL" style="width:100%; height:630px;"\x3EURL not found.\x3C/object\x3E\x3C\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo embed an external URL in a page.\x3C/div\x3E\x3Cbr\x3E\n*** For example, if you want to embed the WoaS official website at {{{http://stickwiki.sf.net/}}} as an object, you\'d use this command:\x3Cbr\x3E\x3Cbr\x3E{{{\n\x3Cobject data="http://stickwiki.sf.net/" style="width:100%; height:630px;"\x3EURL not found.\x3C/object\x3E\n}}}\n\n== Embed a web page in an iFrame\nThis method creates a frame around the embedded web page. The code shown below is for a plain frame, but you can style the iFrame further by adding a border, changing the border style to dotted, dashed, solid, double, groove, ridge, inset, or outset, changing the border width to whatever thickness you like, and the border color to whatever color you like.\n\n* *Embed a local HTML file in an iFrame:*\x3Cbr\x3E\x3Cbr\x3E\n** \x3Cdiv class="woas_helptext"\x3EUse this code in a page, replacing PATH with the full path to HTML file you wish to include, and changing the width and height to suit your taste:\x3C/div\x3E\x3Cbr\x3E{{{\n\x3Ciframe height="400" width="100%" height:630px; src="PATH"\x3E\x3C/iframe\x3E\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo include a local HTML file in a wiki page.\x3C/div\x3E\x3Cbr\x3E\n*** For example, if you want to embed the {{{/Work/MyProject.html}}} file in an iFrame, you\'d use this command:\x3Cbr\x3E\x3Cbr\x3E{{{\n\x3Ciframe height="400" width="100%" height:630px; src="/Work/MyProject.html"\x3E\x3C/iframe\x3E\n}}}\x3Cbr\x3E\x3Cbr\x3E\n* *Embed a remote URL in an iFrame:*\x3Cbr\x3E\x3Cbr\x3E\n** \x3Cdiv class="woas_helptext"\x3EUse this code in a page, replacing URL with the URL of the page you wish to include, and changing the width and height to suit your taste:\x3C/div\x3E\x3Cbr\x3E{{{\n\x3Ciframe height="400" width="100%" height:630px; src="URL"\x3E\x3C/iframe\x3E\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo include a web page in a wiki page.\x3C/div\x3E\x3Cbr\x3E\n*** For example, if you want to embed the WoaS official website at {{{http://stickwiki.sf.net/}}} in an iFrame, you\'d use this command:\x3Cbr\x3E\x3Cbr\x3E{{{\n\x3Ciframe height="400" width="100%" height:630px; src="http://stickwiki.sf.net/"\x3E\x3C/iframe\x3E\n}}}\n',
'= Include wiki page\nTo include the contents of a wiki page inside another wiki page, use an {{{[[Include::]]}}} tag on a line by itself, inserting the name of the page you\'d like to include after the double colons in the tag.\n\n[[Include::WoaS::Template::Info|Page names are case sensitive.]]\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page, replacing Page Name with the name of the page you wish to include:\x3C/div\x3E\n\x3Cdiv class="woas_helpcode"\x3E[\x3C!-- --\x3E[Include::Page Name]]\x3C/div\x3E\n\x3Cdiv class="woas_helptext"\x3ETo include the contents of another wiki page in the current page.\x3C/div\x3E\n\n== Example\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page:\x3C/div\x3E\n\x3Cdiv class="woas_helpcode"\x3E[\x3C!-- --\x3E[Include::Special::Search]\x3C!-- --\x3E]\x3C/div\x3E\n\x3Cdiv class="woas_helptext"\x3ETo create this:\x3C/div\x3E\n[[Include::Special::Search]]\n',
'= JavaScript\n== Adding JavaScript to your page\nTo add JavaScript to your page, you must enclose it inside JavaScript tags.\n\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page, replacing YOUR_CODE_HERE with your JavaScript code:\x3C/div\x3E\x3Cbr\x3E{{{\n\x3Cscript type="text/javascript"\x3E\nYOUR_CODE_HERE\n\x3C/script\x3E\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo add JavaScript to your page.\x3C/div\x3E\n== Adding JavaScript to the wiki source\nIt is possible to add &lt;script/&gt; tags to your wiki source and they will be correctly rendered in the document &lt;head/&gt; tag upon page loading; also external scripts (using the /src/ attribute) will be parsed and activated, always respecting the order of declaration of the &lt;script /&gt; tags.\n\n[[Include::WoaS::Template::Info|Since the JavaScript tags are activated from within the &lt;head /&gt; tag, you cannot use document.write() or similar inline JavaScript statements.]]\n== Using external JavaScript\nYou can include external JavaScript on a page by placing the script file in the same directory as this wiki and then inserting a reference to it in the body of the page.\n\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page, replacing SCRIPTNAME.JS with the name of your script:\x3C/div\x3E\x3Cbr\x3E{{{\n\x3Cscript src="SCRIPTNAME.JS"\x3E\x3C/script\x3E\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo include an external JavaScript in a page.\x3C/div\x3E\n=== Example\nThis page contains this code:\x3Cbr\x3E\x3Cbr\x3E{{{\n\x3Cscript src="example.js"\x3E\x3C/script\x3E\n}}}\nTo see an example using external JavaScript:\n# Create a text file in the same directory as this wiki.\x3Cbr\x3E\x3Cbr\x3E\n# Paste this into the file:\x3Cbr\x3E\x3Cbr\x3E{{{\nalert("This is an example alert!");\n}}}\x3Cbr\x3E\n# Save the file as *example.js*\x3Cbr\x3E\x3Cbr\x3E\n# Reload the wiki.\x3Cbr\x3E\x3Cbr\x3E\n# Open this page from the menu (not in the help pop-up) to run the *example.js* script and see the alert.\n\n== Using JavaScript to create a temporary page\nUsing this code, you can get this wiki to create a link to a dynamically created temporary page with custom content (it accepts HTML tags as part of the content) under the JavaScript namespace. The temporary page will be created each time the link is clicked and uncreated as soon as you navigate to another page or close the wiki.\n\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page, replacing CONTENT with your content, NAME with the page name, and LINK with the text you want the link to have:\x3C/div\x3E\x3Cbr\x3E{{{\n\x3Cscript type="text/javascript"\x3Efunction temporary(arg) {return \'CONTENT\';}\x3C/script\x3E[[Javascript::temporary(\'NAME\')|LINK]]\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create a dynamically created temporary page.\x3C/div\x3E\n[[Include::WoaS::Template::Info|Only one of these links may be used per page.]]\nPaste each of these into a new page one by one to see what they do:\n* This one uses the temporary page name as part of its content:\x3Cbr\x3E\x3Cbr\x3E{{{\n\x3Cscript type="text/javascript"\x3Efunction temporary(arg) {return \'This page was dynamically created using *\'+arg+\'* as content!\';}\x3C/script\x3E[[Javascript::temporary(\'sample\')|Click here]]\n}}}\x3Cbr\x3E\n* This one demonstrates the use of various HTML tags in the content of the page:\x3Cbr\x3E\x3Cbr\x3E{{{\n\x3Cscript type="text/javascript"\x3Efunction temporary(arg) {return \'This page contains more than one line.\x3Cbr\x3EThis line contains \x3Cb\x3Ebold\x3C/b\x3E, \x3Ci\x3Eitalic\x3C/i\x3E, \x3Cu\x3Eunderlined\x3C/u\x3E, and \x3Cfont color="red"\x3Ecolored\x3C/font\x3E text.\x3Cbr\x3E\x3Cbr\x3EAnd here is a list:\x3Cbr\x3E\x3Cul\x3E\x3Cli\x3Eitem1\x3C/li\x3E\x3Cli\x3Eitem2\x3C/li\x3E\x3Cli\x3Eitem3\x3C/li\x3E\x3C/ul\x3E\';}\x3C/script\x3E[[Javascript::temporary(\'My Page\')|Click here]]\n}}}\n\n== Using JavaScript to reload the wiki\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page:\x3C/div\x3E\x3Cbr\x3E{{{\n\x3Cinput type="button" value="Reload wiki" onclick="window.location.href=window.location.href"\x3E\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create a button that reloads the wiki when you click it.\x3C/div\x3E\n',
'= Join lines of text\nLengthy wiki page content can be broken up into shorter lines in the editor (while still being parsed as a single line when the page is viewed) by using a backslash at the *end* of any line; such a line will be joined with the one below it.\n\n[[Include::WoaS::Template::Info|Backslashes *within* a line will be ignored by the wiki parser and will not influence how the text is displayed.]]\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page:\x3C/div\x3E\x3Cbr\x3E{{{\nPutting a \\ at the end of any line \\\nmakes it join together \\\nwith the line below it.\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create this:\x3C/div\x3E\nPutting a \\ at the end of any line \\\nmakes it join together \\\nwith the line below it.\n',
'= Namespaces\nWhen you create a page with double colons (*::*) as a separator in the page name, you are using a namespace to define parent and child pages. Sub-namespaces are created by adding more than one *::* separator to the page name. Each namespace or sub-namespace can have as many child pages under it as you like (within the resource limits of your hardware). The full path to each page is displayed in the navigation bar with links to its namespace and sub-namespace(s) in the title.\n\nFor example, let\'s say you have these pages:\n* {{{[[Recipes::Desserts]]}}}\n* {{{[[Recipes::Desserts::Cakes]]}}}\n* {{{[[Recipes::Main dishes]]}}}\n* {{{[[Recipes::Side dishes]]}}}\n* {{{[[Recipes::Soups]]}}}\n\n*Recipes::* is a namespace. *Desserts::* is a sub-namespace of the *Recipes::* namespace. *Cakes::* is a sub-namespace of the *Desserts::* sub-namespace.\n\nIf you open the {{{[[Recipes::Desserts::Cakes]]}}} page, the title contains links to *Recipes* and *Desserts*. The *Recipes* link opens a page listing all pages in the *Recipes::* namespace, and the *Desserts* link opens a page listing all pages in the *Desserts::* sub-namespace.\n\n=== Namespace sub-menu\nAll pages in any namespace can be given a custom sub-menu by creating a *::Menu* page inside the namespace. A sub-menu will be temporarily added to the main menu when any pages belonging to that page\'s namespace are opened. You can determine what goes into the sub-menu by editing the page with *::Menu* in its name. The sub-menu will vanish as soon as you navigate away from its namespace.\n\nFor example, if you create the {{{[[Recipes::Menu]]}}} page with *My favorite recipes!* as its content, then whenever you open a page in the {{{Recipes::}}} namespace, a sub-menu will appear with *My favorite recipes!* in it.\n\n\x3Cdiv style="background-color: #FFFFAA; border: 0.1em solid; padding: 0.2em;"\x3E\x3Cimg src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAAXNSR0IArs4c6QAAArtQTFRF8ox%2FAAAAAAABAQEBAQECAgICAwMDBAQFBQUABgYGCQgJCgoLDAsMDQwPDQ0NDw4PDw8HEhETExMUFRQUGRkNGhsUGxscHx8eISATIR8lJCQVJiUsJyUqKCcuLCwtLiw0Ly4vMzE4NzYoQT9FQz9ERkZGSkdTS0hMTEhNUk5bU1JVY15kYWBkZWFubGh5bmt1cGt6c212cm5%2BcXFwfHaDfXxehoKQjIiUkYyfkoyllZKal5CpmJGqmJGrmZKtmpaim5WunJaunZewn5mxoJqyopquoZuxpJylopyyopy1pJ22pJ24pJ61pJ%2B1paC1pqC1qaC0pqG1qKG7qqKuqaKxqKK4paWkq6O7qaS3q6p%2BraW4rae6rKmvrqm1rqm6r6nDsKu1sKu9sKu%2Bs6yxs7GDsay9sq29s667tK7FuK%2FBtbDAtbDBt7HEubS%2BubTCurTKurXFvLqjvLfGvLjEv72LvL2VvbjLvr%2BPwL%2BKvbvBw7nHv7vDwMGHw7rHwb3Fwr3Lw8SQxb%2FRxsDRxsHMxcLGxsPLyMPQx8XKyMXIysuOycbIzMfVzcrMz8zU0NGi0czZ0M%2FL0c%2FL0c%2FN0s7X08%2FZ1NPN1ten1tTR1tXP19XS2tfc3Nyt3N2s3dni397W4N%2FX4N%2FZ4t7m4eDY4t%2Fm4eHY4uHb4uDl4uLX4%2BLa5OLo5OTb5ubd6OjJ6Ofh6erE6efr6efs6ujs6%2Bju6%2B3B6%2Brj6%2Bnt6%2Brl7O6%2F6%2Bvk7Ovr7Ovu7evu7vDC7ezv7e3p7u7p7%2B7w7%2B7x7%2B%2Fx8fPF8PDt8e%2F18fDs8fDy8fDz8%2FXC8fHx8%2FD38vLt8vLu8vL08%2FPt8%2FPu9PP09vjI9fXw9vX09vbx9%2FX59%2Fby9%2Fb39%2Ffx%2BPf4%2Bfj5%2Bfj6%2Bfnz%2Bfn1%2Bfr1%2B%2F7K%2Bvr2%2B%2Fr5%2FPr%2F%2B%2Fv7%2FP%2FR%2Ffv%2B%2Ffz9%2F%2F7%2FFnlyNwAAAAF0Uk5TAEDm2GYAAAABYktHRACIBR1IAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH2gMHDxg0DfX8UQAAAUBJREFUKM9jYCAIGBl5hLgYGTGEuXViWpvTzZVZUaUYFdofXzu4YW53o5kgsgyj3LIXFy8e2rphclaZBS%2BSDHvlsxvPb5%2FbtXFKQVyGHkKCUev08TtPbp3Yu7KtKMnfUxIuw2h19MCVu9dP7Z41c3qeR5AmXII1YdOuy%2Ffv3dyyZMfqLhdvA7gEZ%2BzC9UcePrp6%2Fuz%2B%2BdWOXiYIo%2Bz7F%2B%2B8cGb5qkUz6vxsww0REvpN9b3zjq3oKU7zcbLOVEdIyOSkZk%2FatrYj1NfZNiCYH8mDuvmB1Qv2TYxwsHGt0kDyICObUXz01KUT3OyiarVRAouJWc0ysSU3JNldBT18mYUL16ybXaHEiBkfNQ%2B290WKyGNKNFzaMy1FQhZTovzp4TklUtKYEp0nN5eaCihiSPBxiIapMopjSw5iLIzGhBMNAHCEbLFZHo5dAAAAAElFTkSuQmCC" alt="Info" border="0" /\x3E Sub-menus work the same way menus do. For more information on menus, see the [[WoaS::Help::Edit the menu]] page.\x3C/div\x3E\n== Reserved namespaces\nSome namespaces are reserved by the wiki for its own internal use or for specific use by the user:\n* *File::* for embedded files\n* *Image::* for embedded images\n* *Include::* for transclusion\n* *Javascript::* for direct JavaScript execution\n* *Lock::* used internally for encrypted pages\n* *Locked::* for encrypted pages\n* *::Menu* for sub-menus\n* *Special::* for all the special pages\n* *Tagged::* for all the tagged pages\n* *Tag::* for tagging pages\n* *Unlock::* used internally for encrypted pages\n* *Unlocked::* for plain (unencrypted) pages\n* *WoaS::* for core WoaS pages\n',
'= Text formatting\n== Bold\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page:\x3C/div\x3E\x3Cbr\x3E{{{\n*This is bold text.*\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create this:\x3C/div\x3E\n*This is bold text.*\n\n== Colored\n\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page:\x3C/div\x3E\x3Cbr\x3E{{{\n\x3Cspan style="color:#FF00FF"\x3EThis is colored text.\x3C/span\x3E\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create this:\x3C/div\x3E\n\x3Cspan style="color:#FF00FF"\x3EThis is colored text.\x3C/span\x3E\n\n\x3Cdiv class="woas_helptext"\x3EOr use this code in a page:\x3C/div\x3E\x3Cbr\x3E{{{\n\x3Cspan style="color:fuchsia"\x3EThis is colored text.\x3C/span\x3E\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create this:\x3C/div\x3E\n\x3Cspan style="color:fuchsia"\x3EThis is colored text.\x3C/span\x3E\n\n== Italic\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page:\x3C/div\x3E\x3Cbr\x3E{{{\n/This is italic text./\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create this:\x3C/div\x3E\n/This is italic text./\n\n== Plain\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page:\x3C/div\x3E\x3Cbr\x3E{{{\nThis is plain text.\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create this:\x3C/div\x3E\nThis is plain text.\n\n== Underlined\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page:\x3C/div\x3E\x3Cbr\x3E{{{\n_This is underlined text._\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create this:\x3C/div\x3E\n_This is underlined text._\n\n== Underlined Bold Italic\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page:\x3C/div\x3E\x3Cbr\x3E{{{\n_*/This is underlined and bold and italic text./*_\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create this:\x3C/div\x3E\n_*/This is underlined and bold and italic text./*_\n',
'= Rename a page\n# Click the *Edit* icon in the navigation bar.\n# Edit the contents of the text inside the *Page title* area at the top of the page.\n# Save your changes by clicking on the *Save* icon in the navigation bar.\n\n= Reference links\nRenaming a page will automatically rename also the references to such page in other pages.',
'= Page scrolling\nBy default, this wiki offers two choices for scrolling. You can change these settings by clicking the *Advanced* icon in the navigation bar and then clicking the {{{[[Special::Options]]}}} link.\n\nThe *Fixed menu area and navigation bar* option can be enabled and disabled by placing a check mark in the box next to the option. Enabling it locks the navigation bar and menu in place while allowing the rest of the page to scroll. Disabling it allows the entire page to scroll as one connected unit.\n',
'= Table of Contents\nYou can insert a Table of Contents into your page by using a special wiki tag. It will be automatically populated with all the headers on a page. The placement of the Table of Contents is  determined by where you put the tag in the page.\n\n\x3Cdiv class="woas_helptext"\x3EUse this code on a line by itself in a page:\x3C/div\x3E\x3Cbr\x3E{{{\n[[Special::TOC]]\n}}}\n\x3Cdiv class="woas_helptext"\x3ETo add a Table of Contents to the page.\x3C/div\x3E\n\n[[Include::WoaS::Template::Info|The Table of Contents expects the first header on a page to be a Level 1 header. If you use any other level header first, that header and any headers after it will be represented by hash marks until a Level 1 header is encountered. From that point on, the headers will be numbered properly.]]\n== Example\nCreate a page with these contents:\x3Cbr\x3E\x3Cbr\x3E{{{\n[[Special::TOC]]\n= My level one header\n== My level two header\n=== My level three header\n==== My level four header\n===== My level five header\n====== My level six header\n}}}\nWhen you save the page, there will be a Table of Contents that looks like this at the top of the page:\n\n\x3Cdiv style="border:1pt dashed #aaa; background-color:#f9f9f9; padding:0.313em; \nwidth:50%; font-size:95%;"\x3E\x3Ccenter\x3E\x3Cb\x3ETable of Contents\x3C/b\x3E\x3C/center\x3E\n&nbsp;&nbsp;&nbsp;1. \x3Cspan style="color:blue; text-decoration:underline;"\x3EMy level one header\x3C/span\x3E\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;1. \x3Cspan style="color:blue; text-decoration:underline;"\x3EMy level two header\x3C/span\x3E\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;1. \x3Cspan style="color:blue; text-decoration:underline;"\x3EMy level three header\x3C/span\x3E\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;1. \x3Cspan style="color:blue; text-decoration:underline;"\x3EMy level four header\x3C/span\x3E\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;1. \x3Cspan style="color:blue; text-decoration:underline;"\x3EMy level five header\x3C/span\x3E\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;1. \x3Cspan style="color:blue; text-decoration:underline;"\x3EMy level six header\x3C/span\x3E\n\x3C/div\x3E\n',
'= Tags\nTags are used to group pages by categories/features/topics so they can easily be found again.\n\nTags can be placed anywhere on a page, but choosing a standard location like the bottom of the page makes them easier to find and edit as your wiki grows.\n\nTag a page by using a {{{[[Tag::tag1]]}}} link, adding the tag name or names after the double colons; multiple tags should be separated with commas.\n\nYou can also use the *Tag* button in the editor.\n\n=== Add a tag to a page (method 1)\n# Click the *Tags* button in the editor.\x3Cbr\x3E\x3Cbr\x3E\n# Type in a name for the tag.\x3Cbr\x3E\x3Cbr\x3E\n# Click the *OK* button.\n\n=== Add a tag to a page (method 2)\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page, replacing EXAMPLE with the name of the tag you\'d like to add:\x3C/div\x3E\n\x3Cdiv class="woas_helpcode"\x3E[\x3C!-- --\x3E[Tag::EXAMPLE]]\x3C/div\x3E\n\x3Cdiv class="woas_helptext"\x3ETo add a tag to your page.\x3C/div\x3E\n=== Add more than one tag to a page (method 1)\n# Click the *Tags* button in the editor.\x3Cbr\x3E\x3Cbr\x3E\n# Type in names for the tags, separating each from the one before it with a comma. For example:\x3Cbr\x3E\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3EUse this code in a page, replacing EXAMPLE1, EXAMPLE2, and EXAMPLE3 with the names of the tags you\'d like to add:\x3C/div\x3E\x3Cbr\x3E\x3Cdiv class="woas_helpcode"\x3E{{{[[Tag::EXAMPLE1, EXAMPLE2, EXAMPLE3]]}}}\x3C/div\x3E\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo add three tags to your page.\x3C/div\x3E\x3Cbr\x3E\n# Click the *OK* button.\n\n=== Add more than one tag to a page (method 2)\nTo use more than one tag on a page, use one tag link and add as many tags as you like, separating each from the one before it with a comma. For example: {{{[[Tag::Recipes, Chocolate, Layered cakes]]}}}.\n\n=== Choosing tag names\nTag names are case sensitive. For example: {{{[[Tag::Recipes]]}}} and {{{[[Tag::recipes]]}}} are two different tags.\n\nTag names can be more than one word long with a space between each word. For example: {{{[[Tag::Layered cakes]]}}}.\n\n=== Add a link to tagged pages\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page, replacing EXAMPLE with the tag you\'d like to use:\x3C/div\x3E\n\x3Cdiv class="woas_helpcode"\x3E{{{[[Tagged::EXAMPLE]]}}}\x3C/div\x3E\n\x3Cdiv class="woas_helptext"\x3ETo create a listing of pages tagged with EXAMPLE.\x3C/div\x3E\n\n=== Add a filtered link to tagged pages\nBy putting an exclamation mark (*!*) in front of the tag name, you can filter out a specific tag from a tagged pages list:\x3Cbr\x3E\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page, replacing EXAMPLE with the tag you\'d like to filter out:\x3C/div\x3E\n\x3Cdiv class="woas_helpcode"\x3E[\x3C!-- --\x3E[Tagged::!EXAMPLE]\x3C!-- --\x3E]\x3C/div\x3E\n\x3Cdiv class="woas_helptext"\x3ETo create a filtered listing of tagged pages that are not tagged with EXAMPLE.\x3C/div\x3E\n\n=== Include a listing of tagged pages\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page, replacing EXAMPLE with the tag you\'d like to use:\x3C/div\x3E\n\x3Cdiv class="woas_helpcode"\x3E[\x3C!-- --\x3E[Include::Tagged::EXAMPLE]\x3C!-- --\x3E]\x3C/div\x3E\n\x3Cdiv class="woas_helptext"\x3ETo display a listing of pages tagged with EXAMPLE inside the current page.\x3C/div\x3E\n\n=== Include a filtered listing of tagged pages\nBy putting an exclamation mark (*!*) in front of the tag name, you can filter out a specific tag from a tagged pages list:\x3Cbr\x3E\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page, replacing EXAMPLE with the tag you\'d like to filter out:\x3C/div\x3E\n\x3Cdiv class="woas_helpcode"\x3E[\x3C!-- --\x3E[Include::Tagged::!EXAMPLE]\x3C!-- --\x3E]\x3C/div\x3E\n\x3Cdiv class="woas_helptext"\x3ETo display a filtered listing of tagged pages that are not tagged with EXAMPLE inside the current page.\x3C/div\x3E\n',
'= Publish wiki online\nThis wiki can be published online as a single-file, framed web page by making it read-only. The read-only format can also be used for sharing your work with others by email. Most email programs accept .html pages as attachments.\n# Make a backup copy of this wiki.\n# Click the *Advanced* icon in the navigation bar.\n# Scroll to the bottom of the page.\n# Click the *Disallow edits* button to make the file read-only.\n# Upload the wiki to a web server and publish a link to it.\nTo enable wiki editing again, see the [[WoaS::Help::Wiki is read-only]] page.\n',
'= Run wiki on a webserver\nWiki on a Stick is a client software not designed to be used on a webserver; WoaS will anyway recognize that it is being run from a webserver and prevent save operations with an appropriate user message informing about the impossibility to save permanent changes.\n\n== Server integration\n\nWoaS 1.0 will offer an API to allow server-side integrations.',
'= Requirements =\n\n== Supported Browsers\nWiki on a Stick is actively supported on the following browsers:\n\n* Firefox 1.5\n* Firefox 2\n* Firefox 3\n* Firefox 3.5+\n* Opera\n* Internet Explorer 6\n* Internet Explorer 7*\n* Internet Explorer 8*\n* Chrome\n* Safari\n\x3Cdiv class="alertnote"\x3E* On IE7 (and above) you might want to rename your application to .hta to enable its trusted privileges.\x3C/div\x3E\n== Limitations\n* Firefox (and possibly other browsers, too) cannot save to filenames containing UTF8 characters. Use unix-style filenames instead.\n* Internet Explorer 6 and 7 do not support embedded images (that\'s why you will not see button icons).\n* Internet Explorer does not allow saving to a network path in the form /\\\\machine\\share\\bookonastick.htm/. To use this wiki over a network with Internet Explorer, map network paths to network drives first.\n* Internet Explorer 6 does not allow a fixed menu.\n\nIf you are using Chrome, Safari or Opera you will need the TiddlySaver Java applet, hence you need that Java is enabled in your browser and that it properly works.\n\n== TiddlySaver\n\nThe Java applet "TiddlySaver.jar", a very small file, must be placed in the same directory as your WoaS file. In order to use it you will need to give it the necessary privileges by editing your .java.policy file.\n\nFor Windows, the file will be at C:\\Documents and Settings\\your-user-name\\.java.policy. Add the following lines (substituting the directory  as appropriate):\n\n{{{\ngrant codeBase "file:${user.home}/My Documents/tiddlywiki-folder/*" {\n  permission java.io.FilePermission "${user.home}${/}My Documents${/}stickwiki-folder${/}*", "read,write";\n};\n}}}\nOn Mac OS X, the file is found at {{{/Users/your-user-name/.java.policy}}}:\n\n{{{\ngrant codeBase "file:${user.home}/Documents/tiddlywiki-folder/*" {\n  permission java.io.FilePermission "${user.home}${/}Documents${/}stickwiki-folder${/}*", "read,write";\n};\n}}}\nIt can be tricky creating files whose name starts with a period, so you can use this [[https://stickwiki.svn.sourceforge.net/svnroot/stickwiki/trunk/woas/.java.policy|pre-built .java.policy file]]. The same file is suitable for Macs too, just edit it and delete the "My " bit, leaving just "Documents". Make sure you save it in the right place for each operating system!\n\nIf you have trouble setting up the permissions correctly, you can try granting broader permissions to the applet like this:\n\n{{{\ngrant codeBase "file://localhost/home/users/Desktop/\nTiddlySaver.jar"\n { permission java.security.AllPermission; };\n}}}\nThe TiddlySaver java applet was written by J.Rouston.\n',
'= Lists\nSimple single-level lists can be created by using the list buttons in the editor.\n* The *{{{*}}}* button inserts the wiki syntax for a bulleted list item\n* The *{{{#}}}* button inserts the wiki syntax for a numbered list item.\n* The *{{{@}}}* button inserts the wiki syntax for an alphanumeric list item.\n* The *{{{List}}}* button wraps HTML ul tags around the selected text to create an unordered list.\n* The *{{{List =}}}* button wraps HTML ol tags around the selected text to create an ordered list.\n* The *{{{li[*]}}}* button wraps HTML li tags around the selected text to create a list item.\nIf you\'d like to create mixed lists, nested lists, or lists with additional types of list items, the methods below can be used instead.\n\n== Wiki markup lists\nWiki markup lists are created with between one and five specific characters ({{{@}}}, {{{*}}}, or {{{#}}}) at the beginning of a line, followed by a space, and then whatever text you\'d like the list item to have. Each list must begin with a list item with only one character at the beginning of the line, and there can be no blank lines between entries in the list. To create a blank line beneath a list item, you can insert two HTML {{{\x3Cbr\x3E}}} tags at the end of of the list item\'s text. Wiki and HTML markup are accepted in list items.\n\n=== Alphanumeric List\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page:\x3C/div\x3E\x3Cbr\x3E{{{\n@ Test\n@ Test\n@ Test\n@@ Test\n@@@ Test\n@@@@ Test\n@ Test\n@ Test\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create this:\x3C/div\x3E\n@ Test\n@ Test\n@ Test\n@@ Test\n@@@ Test\n@@@@ Test\n@ Test\n@ Test\n\n=== Bulleted List\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page:\x3C/div\x3E\x3Cbr\x3E{{{\n* Test\n* Test\n** Test\n*** Test\n**** Test\n* Test\n* Test\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create this:\x3C/div\x3E\x3Cbr\x3E\n* Test\n* Test\n** Test\n*** Test\n**** Test\n* Test\n* Test\n\n=== Fancy List\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page:\x3C/div\x3E\x3Cbr\x3E{{{\n* ITEM 1 - ITEM 1 SUBTEXT \x3Cblockquote\x3EITEM 1 MORE SUBTEXT\x3C/blockquote\x3E\n* ITEM 2 - ITEM 2 SUBTEXT \x3Cblockquote\x3EITEM 2 MORE SUBTEXT\x3C/blockquote\x3E\n* ITEM 3 - ITEM 3 SUBTEXT \x3Cblockquote\x3EITEM 3 MORE SUBTEXT\x3C/blockquote\x3E\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create this:\x3C/div\x3E\x3Cbr\x3E\n* ITEM 1 - ITEM 1 SUBTEXT \x3Cblockquote\x3EITEM 1 MORE SUBTEXT\x3C/blockquote\x3E\n* ITEM 2 - ITEM 2 SUBTEXT \x3Cblockquote\x3EITEM 2 MORE SUBTEXT\x3C/blockquote\x3E\n* ITEM 3 - ITEM 3 SUBTEXT \x3Cblockquote\x3EITEM 3 MORE SUBTEXT\x3C/blockquote\x3E\n\n=== Numbered List\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page:\x3C/div\x3E\x3Cbr\x3E{{{\n# Test\n# Test\n## Test\n### Test\n#### Test\n# Test\n# Test\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create this:\x3C/div\x3E\x3Cbr\x3E\n# Test\n# Test\n## Test\n### Test\n#### Test\n# Test\n# Test\n\n=== Numbered Fancy List\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page:\x3C/div\x3E\x3Cbr\x3E{{{\n# ITEM 1 - ITEM 1 SUBTEXT \x3Cblockquote\x3EITEM 1 MORE SUBTEXT\x3C/blockquote\x3E\n# ITEM 2 - ITEM 2 SUBTEXT \x3Cblockquote\x3EITEM 2 MORE SUBTEXT\x3C/blockquote\x3E\n# ITEM 3 - ITEM 3 SUBTEXT \x3Cblockquote\x3EITEM 3 MORE SUBTEXT\x3C/blockquote\x3E\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create this:\x3C/div\x3E\x3Cbr\x3E\n# ITEM 1 - ITEM 1 SUBTEXT \x3Cblockquote\x3EITEM 1 MORE SUBTEXT\x3C/blockquote\x3E\n# ITEM 2 - ITEM 2 SUBTEXT \x3Cblockquote\x3EITEM 2 MORE SUBTEXT\x3C/blockquote\x3E\n# ITEM 3 - ITEM 3 SUBTEXT \x3Cblockquote\x3EITEM 3 MORE SUBTEXT\x3C/blockquote\x3E\n\n== HTML Lists\nHTML lists work the same way as they do in any web page. Specifying the list type can be done in the {{{\x3Col\x3E}}}, {{{\x3Cul\x3E}}}, or {{{\x3Cli\x3E}}} tags, depending on how you need to use them. Wiki markup is accepted in list items.\n\n=== Specifying list type in the &lt;ol&gt; tag\nThis method is useful when an entire list will use one list item type. This way you only need to specify the type once, and all the list items can be entered normally.\n\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page:\x3C/div\x3E\x3Cbr\x3E{{{\n\x3Col style="list-style-type:lower-alpha;"\x3E\n\x3Cli\x3Elower-alpha\x3C/li\x3E\n\x3Cli\x3Elower-alpha\x3C/li\x3E\n\x3Cli\x3Elower-alpha\x3C/li\x3E\n\x3C/ol\x3E\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create this:\x3C/div\x3E\n\x3Col style="list-style-type:lower-alpha;"\x3E\n\x3Cli\x3Elower-alpha\x3C/li\x3E\n\x3Cli\x3Elower-alpha\x3C/li\x3E\n\x3Cli\x3Elower-alpha\x3C/li\x3E\n\x3C/ol\x3E\n\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page:\x3C/div\x3E\x3Cbr\x3E{{{\n\x3Col style="list-style-type:upper-alpha;"\x3E\n\x3Cli\x3Eupper-alpha\x3C/li\x3E\n\x3Cli\x3Eupper-alpha\x3C/li\x3E\n\x3Cli\x3Eupper-alpha\x3C/li\x3E\n\x3C/ol\x3E\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create this:\x3C/div\x3E\n\x3Col style="list-style-type:upper-alpha;"\x3E\n\x3Cli\x3Eupper-alpha\x3C/li\x3E\n\x3Cli\x3Eupper-alpha\x3C/li\x3E\n\x3Cli\x3Eupper-alpha\x3C/li\x3E\n\x3C/ol\x3E\n\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page:\x3C/div\x3E\x3Cbr\x3E{{{\n\x3Col style="list-style-type:decimal;"\x3E\n\x3Cli\x3Edecimal\x3C/li\x3E\n\x3Cli\x3Edecimal\x3C/li\x3E\n\x3Cli\x3Edecimal\x3C/li\x3E\n\x3C/ol\x3E\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create this:\x3C/div\x3E\n\x3Col style="list-style-type:decimal;"\x3E\n\x3Cli\x3Edecimal\x3C/li\x3E\n\x3Cli\x3Edecimal\x3C/li\x3E\n\x3Cli\x3Edecimal\x3C/li\x3E\n\x3C/ol\x3E\n\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page:\x3C/div\x3E\x3Cbr\x3E{{{\n\x3Col style="list-style-type:lower-roman;"\x3E\n\x3Cli\x3Elower-roman\x3C/li\x3E\n\x3Cli\x3Elower-roman\x3C/li\x3E\n\x3Cli\x3Elower-roman\x3C/li\x3E\n\x3C/ol\x3E\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create this:\x3C/div\x3E\n\x3Col style="list-style-type:lower-roman;"\x3E\n\x3Cli\x3Elower-roman\x3C/li\x3E\n\x3Cli\x3Elower-roman\x3C/li\x3E\n\x3Cli\x3Elower-roman\x3C/li\x3E\n\x3C/ol\x3E\n\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page:\x3C/div\x3E\x3Cbr\x3E{{{\n\x3Col style="list-style-type:upper-roman;"\x3E\n\x3Cli\x3Eupper-roman\x3C/li\x3E\n\x3Cli\x3Eupper-roman\x3C/li\x3E\n\x3Cli\x3Eupper-roman\x3C/li\x3E\n\x3C/ol\x3E\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create this:\x3C/div\x3E\n\x3Col style="list-style-type:upper-roman;"\x3E\n\x3Cli\x3Eupper-roman\x3C/li\x3E\n\x3Cli\x3Eupper-roman\x3C/li\x3E\n\x3Cli\x3Eupper-roman\x3C/li\x3E\n\x3C/ol\x3E\n\n=== Specifying list type in the &lt;ul&gt; tag\nThis method is useful when an entire list will use one list item type. This way you only need to specify the type once, and all the list items can be entered normally.\n\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page:\x3C/div\x3E\x3Cbr\x3E{{{\n\x3Cul style="list-style-type:circle;"\x3E\n\x3Cli\x3Ecircle\x3C/li\x3E\n\x3Cli\x3Ecircle\x3C/li\x3E\n\x3Cli\x3Ecircle\x3C/li\x3E\n\x3C/ul\x3E\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create this:\x3C/div\x3E\n\x3Cul style="list-style-type:circle;"\x3E\n\x3Cli\x3Ecircle\x3C/li\x3E\n\x3Cli\x3Ecircle\x3C/li\x3E\n\x3Cli\x3Ecircle\x3C/li\x3E\n\x3C/ul\x3E\n\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page:\x3C/div\x3E\x3Cbr\x3E{{{\n\x3Cul style="list-style-type:disc;"\x3E\n\x3Cli\x3Edisc\x3C/li\x3E\n\x3Cli\x3Edisc\x3C/li\x3E\n\x3Cli\x3Edisc\x3C/li\x3E\n\x3C/ul\x3E\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create this:\x3C/div\x3E\n\x3Cul style="list-style-type:disc;"\x3E\n\x3Cli\x3Edisc\x3C/li\x3E\n\x3Cli\x3Edisc\x3C/li\x3E\n\x3Cli\x3Edisc\x3C/li\x3E\n\x3C/ul\x3E\n\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page:\x3C/div\x3E\x3Cbr\x3E{{{\n\x3Cul style="list-style-type:none;"\x3E\n\x3Cli\x3Enone\x3C/li\x3E\n\x3Cli\x3Enone\x3C/li\x3E\n\x3Cli\x3Enone\x3C/li\x3E\n\x3C/ul\x3E\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create this:\x3C/div\x3E\n\x3Cul style="list-style-type:none;"\x3E\n\x3Cli\x3Enone\x3C/li\x3E\n\x3Cli\x3Enone\x3C/li\x3E\n\x3Cli\x3Enone\x3C/li\x3E\n\x3C/ul\x3E\n\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page:\x3C/div\x3E\x3Cbr\x3E{{{\n\x3Cul style="list-style-type:square;"\x3E\n\x3Cli\x3Esquare\x3C/li\x3E\n\x3Cli\x3Esquare\x3C/li\x3E\n\x3Cli\x3Esquare\x3C/li\x3E\n\x3C/ul\x3E\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create this:\x3C/div\x3E\n\x3Cul style="list-style-type:square;"\x3E\n\x3Cli\x3Esquare\x3C/li\x3E\n\x3Cli\x3Esquare\x3C/li\x3E\n\x3Cli\x3Esquare\x3C/li\x3E\n\x3C/ul\x3E\n\n=== Specifying list type in the &lt;li&gt; tag\nThis method is useful when creating a fancy list with more than one list item type.\n\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page:\x3C/div\x3E\x3Cbr\x3E{{{\n\x3Col\x3E\n\x3Cli style="list-style-type:lower-alpha;"\x3Elower-alpha\x3C/li\x3E\n\x3Cli style="list-style-type:lower-alpha;"\x3Elower-alpha\x3C/li\x3E\n\x3Cli style="list-style-type:lower-alpha;"\x3Elower-alpha\x3C/li\x3E\n\x3C/ol\x3E\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create this:\x3C/div\x3E\n\x3Col\x3E\n\x3Cli style="list-style-type:lower-alpha;"\x3Elower-alpha\x3C/li\x3E\n\x3Cli style="list-style-type:lower-alpha;"\x3Elower-alpha\x3C/li\x3E\n\x3Cli style="list-style-type:lower-alpha;"\x3Elower-alpha\x3C/li\x3E\n\x3C/ol\x3E\n\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page:\x3C/div\x3E\x3Cbr\x3E{{{\n\x3Col\x3E\n\x3Cli style="list-style-type:upper-alpha;"\x3Eupper-alpha\x3C/li\x3E\n\x3Cli style="list-style-type:upper-alpha;"\x3Eupper-alpha\x3C/li\x3E\n\x3Cli style="list-style-type:upper-alpha;"\x3Eupper-alpha\x3C/li\x3E\n\x3C/ol\x3E\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create this:\x3C/div\x3E\n\x3Col\x3E\n\x3Cli style="list-style-type:upper-alpha;"\x3Eupper-alpha\x3C/li\x3E\n\x3Cli style="list-style-type:upper-alpha;"\x3Eupper-alpha\x3C/li\x3E\n\x3Cli style="list-style-type:upper-alpha;"\x3Eupper-alpha\x3C/li\x3E\n\x3C/ol\x3E\n\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page:\x3C/div\x3E\x3Cbr\x3E{{{\n\x3Col\x3E\n\x3Cli style="list-style-type:circle;"\x3Ecircle\x3C/li\x3E\n\x3Cli style="list-style-type:circle;"\x3Ecircle\x3C/li\x3E\n\x3Cli style="list-style-type:circle;"\x3Ecircle\x3C/li\x3E\n\x3C/ol\x3E\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create this:\x3C/div\x3E\n\x3Col\x3E\n\x3Cli style="list-style-type:circle;"\x3Ecircle\x3C/li\x3E\n\x3Cli style="list-style-type:circle;"\x3Ecircle\x3C/li\x3E\n\x3Cli style="list-style-type:circle;"\x3Ecircle\x3C/li\x3E\n\x3C/ol\x3E\n\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page:\x3C/div\x3E\x3Cbr\x3E{{{\n\x3Col\x3E\n\x3Cli style="list-style-type:decimal;"\x3Edecimal\x3C/li\x3E\n\x3Cli style="list-style-type:decimal;"\x3Edecimal\x3C/li\x3E\n\x3Cli style="list-style-type:decimal;"\x3Edecimal\x3C/li\x3E\n\x3C/ol\x3E\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create this:\x3C/div\x3E\n\x3Col\x3E\n\x3Cli style="list-style-type:decimal;"\x3Edecimal\x3C/li\x3E\n\x3Cli style="list-style-type:decimal;"\x3Edecimal\x3C/li\x3E\n\x3Cli style="list-style-type:decimal;"\x3Edecimal\x3C/li\x3E\n\x3C/ol\x3E\n\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page:\x3C/div\x3E\x3Cbr\x3E{{{\n\x3Col\x3E\n\x3Cli style="list-style-type:disc;"\x3Edisc\x3C/li\x3E\n\x3Cli style="list-style-type:disc;"\x3Edisc\x3C/li\x3E\n\x3Cli style="list-style-type:disc;"\x3Edisc\x3C/li\x3E\n\x3C/ol\x3E\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create this:\x3C/div\x3E\n\x3Col\x3E\n\x3Cli style="list-style-type:disc;"\x3Edisc\x3C/li\x3E\n\x3Cli style="list-style-type:disc;"\x3Edisc\x3C/li\x3E\n\x3Cli style="list-style-type:disc;"\x3Edisc\x3C/li\x3E\n\x3C/ol\x3E\n\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page:\x3C/div\x3E\x3Cbr\x3E{{{\n\x3Col\x3E\n\x3Cli style="list-style-type:lower-roman;"\x3Elower-roman\x3C/li\x3E\n\x3Cli style="list-style-type:lower-roman;"\x3Elower-roman\x3C/li\x3E\n\x3Cli style="list-style-type:lower-roman;"\x3Elower-roman\x3C/li\x3E\n\x3C/ol\x3E\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create this:\x3C/div\x3E\n\x3Col\x3E\n\x3Cli style="list-style-type:lower-roman;"\x3Elower-roman\x3C/li\x3E\n\x3Cli style="list-style-type:lower-roman;"\x3Elower-roman\x3C/li\x3E\n\x3Cli style="list-style-type:lower-roman;"\x3Elower-roman\x3C/li\x3E\n\x3C/ol\x3E\n\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page:\x3C/div\x3E\x3Cbr\x3E{{{\n\x3Col\x3E\n\x3Cli style="list-style-type:none;"\x3Enone\x3C/li\x3E\n\x3Cli style="list-style-type:none;"\x3Enone\x3C/li\x3E\n\x3Cli style="list-style-type:none;"\x3Enone\x3C/li\x3E\n\x3C/ol\x3E\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create this:\x3C/div\x3E\n\x3Col\x3E\n\x3Cli style="list-style-type:none;"\x3Enone\x3C/li\x3E\n\x3Cli style="list-style-type:none;"\x3Enone\x3C/li\x3E\n\x3Cli style="list-style-type:none;"\x3Enone\x3C/li\x3E\n\x3C/ol\x3E\n\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page:\x3C/div\x3E\x3Cbr\x3E{{{\n\x3Col\x3E\n\x3Cli style="list-style-type:square;"\x3Esquare\x3C/li\x3E\n\x3Cli style="list-style-type:square;"\x3Esquare\x3C/li\x3E\n\x3Cli style="list-style-type:square;"\x3Esquare\x3C/li\x3E\n\x3C/ol\x3E\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create this:\x3C/div\x3E\n\x3Col\x3E\n\x3Cli style="list-style-type:square;"\x3Esquare\x3C/li\x3E\n\x3Cli style="list-style-type:square;"\x3Esquare\x3C/li\x3E\n\x3Cli style="list-style-type:square;"\x3Esquare\x3C/li\x3E\n\x3C/ol\x3E\n\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page:\x3C/div\x3E\x3Cbr\x3E{{{\n\x3Col\x3E\n\x3Cli style="list-style-type:upper-roman;"\x3Eupper-roman\x3C/li\x3E\n\x3Cli style="list-style-type:upper-roman;"\x3Eupper-roman\x3C/li\x3E\n\x3Cli style="list-style-type:upper-roman;"\x3Eupper-roman\x3C/li\x3E\n\x3C/ol\x3E\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create this:\x3C/div\x3E\n\x3Col\x3E\n\x3Cli style="list-style-type:upper-roman;"\x3Eupper-roman\x3C/li\x3E\n\x3Cli style="list-style-type:upper-roman;"\x3Eupper-roman\x3C/li\x3E\n\x3Cli style="list-style-type:upper-roman;"\x3Eupper-roman\x3C/li\x3E\n\x3C/ol\x3E\n\n=== Mixed HTML list\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page:\x3C/div\x3E\x3Cbr\x3E{{{\n\x3Col\x3E\n\x3Cli style="list-style-type:lower-alpha;"\x3Elower-alpha\x3C/li\x3E\n   \x3Col\x3E\n   \x3Cli style="list-style-type:upper-alpha;"\x3Eupper-alpha\x3C/li\x3E\n      \x3Col\x3E\n      \x3Cli style="list-style-type:circle;"\x3Ecircle\x3C/li\x3E\n         \x3Col\x3E\n         \x3Cli style="list-style-type:decimal;"\x3Edecimal\x3C/li\x3E\n            \x3Col\x3E\n            \x3Cli style="list-style-type:disc;"\x3Edisc\x3C/li\x3E\n               \x3Col\x3E\n               \x3Cli style="list-style-type:lower-roman;"\x3Elower-roman\x3C/li\x3E\n                  \x3Col\x3E\n                  \x3Cli style="list-style-type:none;"\x3Enone\x3C/li\x3E\n                     \x3Col\x3E\n                     \x3Cli style="list-style-type:square;"\x3Esquare\x3C/li\x3E\n                        \x3Col\x3E\n                        \x3Cli style="list-style-type:upper-roman;"\x3Eupper-roman\x3C/li\x3E\n                        \x3C/ol\x3E\n                     \x3C/ol\x3E\n                  \x3C/ol\x3E\n               \x3C/ol\x3E\n            \x3C/ol\x3E\n         \x3C/ol\x3E\n      \x3C/ol\x3E\n   \x3C/ol\x3E\n\x3C/ol\x3E\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create this:\x3C/div\x3E\n\x3Col\x3E\n\x3Cli style="list-style-type:lower-alpha;"\x3Elower-alpha\x3C/li\x3E\n   \x3Col\x3E\n   \x3Cli style="list-style-type:upper-alpha;"\x3Eupper-alpha\x3C/li\x3E\n      \x3Col\x3E\n      \x3Cli style="list-style-type:circle;"\x3Ecircle\x3C/li\x3E\n         \x3Col\x3E\n         \x3Cli style="list-style-type:decimal;"\x3Edecimal\x3C/li\x3E\n            \x3Col\x3E\n            \x3Cli style="list-style-type:disc;"\x3Edisc\x3C/li\x3E\n               \x3Col\x3E\n               \x3Cli style="list-style-type:lower-roman;"\x3Elower-roman\x3C/li\x3E\n                  \x3Col\x3E\n                  \x3Cli style="list-style-type:none;"\x3Enone\x3C/li\x3E\n                     \x3Col\x3E\n                     \x3Cli style="list-style-type:square;"\x3Esquare\x3C/li\x3E\n                        \x3Col\x3E\n                        \x3Cli style="list-style-type:upper-roman;"\x3Eupper-roman\x3C/li\x3E\n                        \x3C/ol\x3E\n                     \x3C/ol\x3E\n                  \x3C/ol\x3E\n               \x3C/ol\x3E\n            \x3C/ol\x3E\n         \x3C/ol\x3E\n      \x3C/ol\x3E\n   \x3C/ol\x3E\n\x3C/ol\x3E\n',
'= Tables syntax\n\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page:\x3C/div\x3E\x3Cbr\x3E{{{\n{|\n| CELL1 || CELL2\n| CELL1 || CELL2\n|}\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create this:\x3C/div\x3E\n{|\n| CELL1 || CELL2\n| CELL1 || CELL2\n|}\n\n\x3Cdiv class="woas_helptext"\x3EOr use this code in a page:\x3C/div\x3E\x3Cbr\x3E{{{\n{|\n| CELL1 || CELL2\n|-\n| CELL1 || CELL2\n|}\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create this:\x3C/div\x3E\n{|\n| CELL1 || CELL2\n|-\n| CELL1 || CELL2\n|}\n\nThe line that starts with {{{|- }}} is a comment line. You can type some text after it which will only be visible when viewing the source:\n\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page:\x3C/div\x3E\x3Cbr\x3E{{{\n{|\n| CELL1 || CELL2\n|- This is a comment.\n| CELL1 || CELL2\n|}\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create this:\x3C/div\x3E\n{|\n| CELL1 || CELL2\n|- This is a comment.\n| CELL1 || CELL2\n|}\n\n== Header rows\nTables can have a specially formatted header row created by adding a pipe followed by a plus symbol and a space before the title text (example: {{{|+ Title}}}), and can contain internal and external [[WoaS::Help::Links|wiki links]].\n\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page:\x3C/div\x3E\x3Cbr\x3E{{{\n{|\n|+ TITLE\n| *HEADER1* || *HEADER2* || *HEADER3*\n|-\n| CELL1 || CELL2 || CELL3\n|-\n| CELL1 || CELL2 || CELL3\n|-\n| CELL1 || CELL2 || CELL3\n|-\n| CELL1 || || CELL3\n|}\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create this:\x3C/div\x3E\n{|\n|+ TITLE\n| *HEADER1* || *HEADER2* || *HEADER3*\n|-\n| CELL1 || CELL2 || CELL3\n|-\n| CELL1 || CELL2 || CELL3\n|-\n| CELL1 || CELL2 || CELL3\n|-\n| CELL1 || || CELL3\n|}\n\n\x3Cdiv class="woas_helptext"\x3EOr use this code in a page:\x3C/div\x3E\x3Cbr\x3E{{{\n{|\n|+ TITLE\n| *HEADER1* || *HEADER2* || *HEADER3*\n| CELL1 || CELL2 || CELL3\n| CELL1 || CELL2 || CELL3\n| CELL1 || CELL2 || CELL3\n| CELL1 || || CELL3\n|}\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create this:\x3C/div\x3E\n{|\n|+ TITLE\n| *HEADER1* || *HEADER2* || *HEADER3*\n| CELL1 || CELL2 || CELL3\n| CELL1 || CELL2 || CELL3\n| CELL1 || CELL2 || CELL3\n| CELL1 || || CELL3\n|}\n\n= Extended customization of layout\n\nStyling, formatting, and alignment can be used to control the look and behavior of tables. All example tables on this page use table comment lines ( {{{|-}}} ) to make it easier to see what is involved in creating the tables. These lines are not necessary when creating your tables.\n\n== Add some custom CSS rules to the wiki\nTo create a custom table defined by [[WoaS::Help::CSS|CSS]], add some table rules to the wiki CSS using a class name of your choosing.\n* \x3Cdiv class="woas_help_text"\x3EUse these CSS rules in your wiki, replacing CLASS_NAME with the class name you\'d like to use, and CSS_RULES with the rules you\'d like to define for the table, the caption, the headers, the cells, and a [[#Styling table cells|special cell]]:\x3C/div\x3E\x3Cbr\x3E{{{\ntable.CLASS_NAME {CSS_RULES}\ncaption.CLASS_NAME {CSS_RULES}\ntable.CLASS_NAME th {CSS_RULES}\ntable.CLASS_NAME td {CSS_RULES}\ntable.CLASS_NAME td.special {CSS_RULES}\n}}}\x3Cbr\x3ESee the [[#The CSS rules used by the example tables|custom CSS rules]] used by the examples on this page.\n\n== Assign a class to a table\nWhen custom CSS rules have been added to the wiki, you can replace the default {{{{|}}} table opening line with a custom one to assign the custom class to the table.\n\n\x3Cdiv class="woas_help_text"\x3EOpen a table with this line of code:\x3C/div\x3E\x3Cbr\x3E{{{\n| class="CLASS_NAME"\n}}}\x3Cbr\x3E\x3Cdiv class="woas_help_text"\x3ETo use a custom class for the table.\x3C/div\x3E\n\n\x3Ctable width="100%"\x3E\x3Ctr\x3E\x3Ctd width="40%"\x3E\x3Cdiv class="woas_help_text"\x3EExample default table:\x3C/div\x3E\x3C/td\x3E\x3Ctd width="60%"\x3E\x3Cdiv class="woas_help_text"\x3EUse this code to create the table:\x3C/div\x3E\x3C/td\x3E\x3C/tr\x3E\x3Ctr\x3E\x3Ctd valign="top"\x3E\n{|\n|-\n| cell 1 || cell 2 || cell 3\n|-\n| cell 1 || cell 2 || cell 3\n|-\n| cell 1 || cell 2 || cell 3\n|-\n|} \n\x3C/td\x3E\x3Ctd valign="top"\x3E\n{{{\n{|\n|-\n| cell 1 || cell 2 || cell 3\n|-\n| cell 1 || cell 2 || cell 3\n|-\n| cell 1 || cell 2 || cell 3\n|-\n|} \n}}}\n\x3C/td\x3E\x3C/tr\x3E\n\x3Ctr\x3E\x3Ctd width="40%"\x3E\x3Cdiv class="woas_help_text"\x3EExample custom table:\x3C/div\x3E\x3C/td\x3E\x3Ctd width="60%"\x3E\x3Cdiv class="woas_help_text"\x3EUse this code to create the table:\x3C/div\x3E\x3C/td\x3E\x3C/tr\x3E\x3Ctr\x3E\x3Ctd valign="top"\x3E\n{| class="mycustomtable"\n|-\n| cell 1 || cell 2 || cell 3\n|-\n| cell 1 || cell 2 || cell 3\n|-\n| cell 1 || cell 2 || cell 3\n|-\n|} \n\x3C/td\x3E\x3Ctd valign="top"\x3E\n{{{\n{| class="mycustomtable"\n|-\n| cell 1 || cell 2 || cell 3\n|-\n| cell 1 || cell 2 || cell 3\n|-\n| cell 1 || cell 2 || cell 3\n|-\n|} \n}}}\n\x3C/td\x3E\x3C/tr\x3E\x3C/table\x3E\n\n== Alignment\nTables are aligned to the left by default, but alignment can be controlled by surrounding the cell contents with extra spaces or tabs. Forcing content to the left is not necessary, but it may help to use forced alignment for debugging complex tables.\n\n{|\n| *Alignment* || *Example result* || *Effect*\n| {space}text{space} || text || default\n| {space}text{space}{space} || text  || text is aligned to the left\n| {space}{space}text{space} ||  text || text is aligned to the right\n| {space}{space}text{space}{space} ||  text  || text is centered\n|}\n\n== Captions\nPlacement of the caption determines whether it\'s above or below the table.\n\n\x3Cdiv class="woas_help_text"\x3EUse this code in a table, replacing CAPTION with the caption of the table:\x3C/div\x3E\x3Cbr\x3E{{{\n|+ CAPTION\n}}}\x3Cbr\x3E\x3Cdiv class="woas_help_text"\x3ETo add a caption to a table.\x3C/div\x3E\n\n\x3Ctable width="100%"\x3E\x3Ctr\x3E\x3Ctd width="40%"\x3E\x3Cdiv class="woas_help_text"\x3EExample default table:\x3C/div\x3E\x3C/td\x3E\x3Ctd width="60%"\x3E\x3Cdiv class="woas_help_text"\x3EUse this code to create the table:\x3C/div\x3E\x3C/td\x3E\x3C/tr\x3E\x3Ctr\x3E\x3Ctd valign="top"\x3E\n{|\n|-\n|+ caption above\n|-\n| cell 1 || cell 2 || cell 3\n|-\n| cell 1 || cell 2 || cell 3\n|-\n| cell 1 || cell 2 || cell 3\n|-\n|}\n\x3C/td\x3E\x3Ctd valign="top"\x3E\n{{{\n{|\n|-\n|+ caption above\n|-\n| cell 1 || cell 2 || cell 3\n|-\n| cell 1 || cell 2 || cell 3\n|-\n| cell 1 || cell 2 || cell 3\n|-\n|}\n}}}\n\x3C/td\x3E\x3C/tr\x3E\n\x3Ctr\x3E\x3Ctd width="40%"\x3E\x3Cdiv class="woas_help_text"\x3EExample custom table:\x3C/div\x3E\x3C/td\x3E\x3Ctd width="60%"\x3E\x3Cdiv class="woas_help_text"\x3EUse this code to create the table:\x3C/div\x3E\x3C/td\x3E\x3C/tr\x3E\x3Ctr\x3E\x3Ctd valign="top"\x3E\n{| class="mycustomtable"\n|-\n|+ caption above\n|-\n| cell 1 || cell 2 || cell 3\n|-\n| cell 1 || cell 2 || cell 3\n|-\n| cell 1 || cell 2 || cell 3\n|-\n|}\n\x3C/td\x3E\x3Ctd valign="top"\x3E\n{{{\n{| class="mycustomtable"\n|-\n|+ caption above\n|-\n| cell 1 || cell 2 || cell 3\n|-\n| cell 1 || cell 2 || cell 3\n|-\n| cell 1 || cell 2 || cell 3\n|-\n|}\n}}}\n\x3C/td\x3E\x3C/tr\x3E\n\x3Ctr\x3E\x3Ctd width="40%"\x3E\x3Cdiv class="woas_help_text"\x3EExample default table:\x3C/div\x3E\x3C/td\x3E\x3Ctd width="60%"\x3E\x3Cdiv class="woas_help_text"\x3EUse this code to create the table:\x3C/div\x3E\x3C/td\x3E\x3C/tr\x3E\x3Ctr\x3E\x3Ctd valign="top"\x3E\n{|\n|-\n| cell 1 || cell 2 || cell 3\n|-\n| cell 1 || cell 2 || cell 3\n|-\n| cell 1 || cell 2 || cell 3\n|-\n|+ caption below\n|-\n|}\n\x3C/td\x3E\x3Ctd valign="top"\x3E\n{{{\n{|\n|-\n| cell 1 || cell 2 || cell 3\n|-\n| cell 1 || cell 2 || cell 3\n|-\n| cell 1 || cell 2 || cell 3\n|-\n|+ caption below\n|-\n|}\n}}}\n\x3C/td\x3E\x3C/tr\x3E\n\x3Ctr\x3E\x3Ctd width="40%"\x3E\x3Cdiv class="woas_help_text"\x3EExample custom table:\x3C/div\x3E\x3C/td\x3E\x3Ctd width="60%"\x3E\x3Cdiv class="woas_help_text"\x3EUse this code to create the table:\x3C/div\x3E\x3C/td\x3E\x3C/tr\x3E\x3Ctr\x3E\x3Ctd valign="top"\x3E\n{| class="mycustomtable"\n|-\n| cell 1 || cell 2 || cell 3\n|-\n| cell 1 || cell 2 || cell 3\n|-\n| cell 1 || cell 2 || cell 3\n|-\n|+ caption below\n|-\n|}\n\x3C/td\x3E\x3Ctd valign="top"\x3E\n{{{\n{| class="mycustomtable"\n|-\n| cell 1 || cell 2 || cell 3\n|-\n| cell 1 || cell 2 || cell 3\n|-\n| cell 1 || cell 2 || cell 3\n|-\n|+ caption below\n|-\n|}\n}}}\n\x3C/td\x3E\x3C/tr\x3E\x3C/table\x3E\n\n== Empty cells\nEmpty cells can be created by inserting just one space after a pipe in your table.\n\n\x3Ctable width="100%"\x3E\x3Ctr\x3E\x3Ctd width="40%"\x3E\x3Cdiv class="woas_help_text"\x3EExample plain table:\x3C/div\x3E\x3C/td\x3E\x3Ctd width="60%"\x3E\x3Cdiv class="woas_help_text"\x3EUse this code to create the table:\x3C/div\x3E\x3C/td\x3E\x3C/tr\x3E\x3Ctr\x3E\x3Ctd valign="top"\x3E\n{|\n|-\n| || cell 2 || cell 3\n|-\n| cell 1 || || cell 3\n|-\n| cell 1 || cell 2 || \n|-\n|}\n\x3C/td\x3E\x3Ctd valign="top"\x3E\n{{{\n{|\n|-\n| || cell 2 || cell 3\n|-\n| cell 1 || || cell 3\n|-\n| cell 1 || cell 2 || \n|-\n|}\n}}}\n\x3C/td\x3E\x3C/tr\x3E\n\x3Ctr\x3E\x3Ctd width="40%"\x3E\x3Cdiv class="woas_help_text"\x3EExample fancy table:\x3C/div\x3E\x3C/td\x3E\x3Ctd width="60%"\x3E\x3Cdiv class="woas_help_text"\x3EUse this code to create the table:\x3C/div\x3E\x3C/td\x3E\x3C/tr\x3E\x3Ctr\x3E\x3Ctd valign="top"\x3E\n{| class="mycustomtable"\n|-\n| || cell 2 || cell 3\n|-\n| cell 1 || || cell 3\n|-\n| cell 1 || cell 2 || \n|-\n|}\n\x3C/td\x3E\x3Ctd valign="top"\x3E\n{{{\n{| class="mycustomtable"\n|-\n| || cell 2 || cell 3\n|-\n| cell 1 || || cell 3\n|-\n| cell 1 || cell 2 || \n|-\n|}\n}}}\n\x3C/td\x3E\x3C/tr\x3E\x3C/table\x3E\n\n== Headers\nIn default tables, table header cells ({{{\x3Cth\x3E}}}) are created by surrounding the cell content in asterisks. In custom tables, use a single {{{=}}} symbol before the cell content to change the table data cell ({{{\x3Ctd\x3E}}}) into a table header cell {{{\x3Cth\x3E}}}, which can be styled differently through CSS.\n\n\x3Ctable width="100%"\x3E\x3Ctr\x3E\x3Ctd width="40%"\x3E\x3Cdiv class="woas_help_text"\x3EExample default table:\x3C/div\x3E\x3C/td\x3E\x3Ctd width="60%"\x3E\x3Cdiv class="woas_help_text"\x3EUse this code to create the table:\x3C/div\x3E\x3C/td\x3E\x3C/tr\x3E\x3Ctr\x3E\x3Ctd valign="top"\x3E\n{|\n|-\n| *cell 1* || *cell 2* || *cell 3*\n|-\n| cell 1 || cell 2 || cell 3\n|-\n| cell 1 || cell 2 || cell 3\n|-\n|}\n\x3C/td\x3E\x3Ctd valign="top"\x3E\n{{{\n{|\n|-\n| *cell 1* || *cell 2* || *cell 3*\n|-\n| cell 1 || cell 2 || cell 3\n|-\n| cell 1 || cell 2 || cell 3\n|-\n|}\n}}}\n\x3C/td\x3E\x3C/tr\x3E\n\x3Ctr\x3E\x3Ctd width="40%"\x3E\x3Cdiv class="woas_help_text"\x3EExample custom table:\x3C/div\x3E\x3C/td\x3E\x3Ctd width="60%"\x3E\x3Cdiv class="woas_help_text"\x3EUse this code to create the table:\x3C/div\x3E\x3C/td\x3E\x3C/tr\x3E\x3Ctr\x3E\x3Ctd valign="top"\x3E\n{| class="mycustomtable"\n|-\n| =cell 1 || =cell 2 || =cell 3\n|-\n| cell 1 || cell 2 || cell 3\n|-\n| cell 1 || cell 2 || cell 3\n|-\n|}\n\x3C/td\x3E\x3Ctd valign="top"\x3E\n{{{\n{| class="mycustomtable"\n|-\n| =cell 1 || =cell 2 || =cell 3\n|-\n| cell 1 || cell 2 || cell 3\n|-\n| cell 1 || cell 2 || cell 3\n|-\n|}\n}}}\n\x3C/td\x3E\x3C/tr\x3E\x3C/table\x3E\n\nPlacement of table header cells ({{{\x3Cth\x3E}}}) can be anywhere in a table.\n\n\x3Ctable width="100%"\x3E\x3Ctr\x3E\x3Ctd width="40%"\x3E\x3Cdiv class="woas_help_text"\x3EExample default table:\x3C/div\x3E\x3C/td\x3E\x3Ctd width="60%"\x3E\x3Cdiv class="woas_help_text"\x3EUse this code to create the table:\x3C/div\x3E\x3C/td\x3E\x3C/tr\x3E\x3Ctr\x3E\x3Ctd valign="top"\x3E\n{|\n|-\n| *cell 1* || cell 2 || cell 3\n|-\n| cell 1 || cell 2 || cell 3\n|-\n| cell 1 || cell 2 || *cell 3*\n|-\n|}\n\x3C/td\x3E\x3Ctd valign="top"\x3E\n{{{\n{|\n|-\n| *cell 1* || cell 2 || cell 3\n|-\n| cell 1 || cell 2 || cell 3\n|-\n| cell 1 || cell 2 || *cell 3*\n|-\n|}\n}}}\n\x3C/td\x3E\x3C/tr\x3E\n\x3Ctr\x3E\x3Ctd width="40%"\x3E\x3Cdiv class="woas_help_text"\x3EExample custom table:\x3C/div\x3E\x3C/td\x3E\x3Ctd width="60%"\x3E\x3Cdiv class="woas_help_text"\x3EUse this code to create the table:\x3C/div\x3E\x3C/td\x3E\x3C/tr\x3E\x3Ctr\x3E\x3Ctd valign="top"\x3E\n{| class="mycustomtable"\n|-\n| =cell 1 || cell 2 || cell 3\n|-\n| cell 1 || cell 2 || cell 3\n|-\n| cell 1 || cell 2 || =cell 3\n|-\n|}\n\x3C/td\x3E\x3Ctd valign="top"\x3E\n{{{\n{| class="mycustomtable"\n|-\n| =cell 1 || cell 2 || cell 3\n|-\n| cell 1 || cell 2 || cell 3\n|-\n| cell 1 || cell 2 || =cell 3\n|-\n|}\n}}}\n\x3C/td\x3E\x3C/tr\x3E\x3C/table\x3E\n\nTo get the contents of a cell to start with an equal symbol, use an HTML comment ( {{{\x3C!-- --\x3E}}} ) before the equal symbol:\n\n\x3Ctable width="100%"\x3E\x3Ctr\x3E\x3Ctd width="40%"\x3E\x3Cdiv class="woas_help_text"\x3EExample default table:\x3C/div\x3E\x3C/td\x3E\x3Ctd width="60%"\x3E\x3Cdiv class="woas_help_text"\x3EUse this code to create the table:\x3C/div\x3E\x3C/td\x3E\x3C/tr\x3E\x3Ctr\x3E\x3Ctd valign="top"\x3E\n{|\n|-\n| \x3C!-- --\x3E=cell 1 || cell 2 || cell 3\n|-\n| cell 1 || cell 2 || cell 3\n|-\n| cell 1 || cell 2 || \x3C!-- --\x3E=cell 3\n|-\n|}\n\x3C/td\x3E\x3Ctd valign="top"\x3E\n{{{\n{|\n|-\n| \x3C!-- --\x3E=cell 1 || cell 2 || cell 3\n|-\n| cell 1 || cell 2 || cell 3\n|-\n| cell 1 || cell 2 || \x3C!-- --\x3E=cell 3\n|-\n|}\n}}}\n\x3C/td\x3E\x3C/tr\x3E\n\x3Ctr\x3E\x3Ctd width="40%"\x3E\x3Cdiv class="woas_help_text"\x3EExample custom table:\x3C/div\x3E\x3C/td\x3E\x3Ctd width="60%"\x3E\x3Cdiv class="woas_help_text"\x3EUse this code to create the table:\x3C/div\x3E\x3C/td\x3E\x3C/tr\x3E\x3Ctr\x3E\x3Ctd valign="top"\x3E\n{| class="mycustomtable"\n|-\n| \x3C!-- --\x3E=cell 1 || cell 2 || cell 3\n|-\n| cell 1 || cell 2 || cell 3\n|-\n| cell 1 || cell 2 || \x3C!-- --\x3E=cell 3\n|-\n|}\n\x3C/td\x3E\x3Ctd valign="top"\x3E\n{{{\n{| class="mycustomtable"\n|-\n| \x3C!-- --\x3E=cell 1 || cell 2 || cell 3\n|-\n| cell 1 || cell 2 || cell 3\n|-\n| cell 1 || cell 2 || \x3C!-- --\x3E=cell 3\n|-\n|}\n}}}\n\x3C/td\x3E\x3C/tr\x3E\x3C/table\x3E\n\n== Spans\nBy removing all contents in a cell you can cause the content in the cell to the left or right of it to span more than one column.\n\n\x3Ctable width="100%"\x3E\x3Ctr\x3E\x3Ctd width="40%"\x3E\x3Cdiv class="woas_help_text"\x3EExample default table:\x3C/div\x3E\x3C/td\x3E\x3Ctd width="60%"\x3E\x3Cdiv class="woas_help_text"\x3EUse this code to create the table:\x3C/div\x3E\x3C/td\x3E\x3C/tr\x3E\x3Ctr\x3E\x3Ctd valign="top"\x3E\n{|\n|-\n| *cell 1* || cell 2 || cell 3\n|-\n| *cell 1* || span last 2 columns ||\n|-\n||| span just last 2 columns ||\n|-\n| span all columns ||||\n|-\n|}\n\x3C/td\x3E\x3Ctd valign="top"\x3E\n{{{\n{|\n|-\n| *cell 1* || cell 2 || cell 3\n|-\n| *cell 1* || span last 2 columns ||\n|-\n||| span just last 2 columns ||\n|-\n| span all columns ||||\n|-\n|}\n}}}\n\x3C/td\x3E\x3C/tr\x3E\n\x3Ctr\x3E\x3Ctd width="40%"\x3E\x3Cdiv class="woas_help_text"\x3EExample custom table:\x3C/div\x3E\x3C/td\x3E\x3Ctd width="60%"\x3E\x3Cdiv class="woas_help_text"\x3EUse this code to create the table:\x3C/div\x3E\x3C/td\x3E\x3C/tr\x3E\x3Ctr\x3E\x3Ctd valign="top"\x3E\n{| class="mycustomtable"\n|-\n| =cell 1 || cell 2 || cell 3\n|-\n| =cell 1 || span last 2 columns ||\n|-\n||| span just last 2 columns ||\n|-\n| span all columns ||||\n|-\n|}\n\x3C/td\x3E\x3Ctd valign="top"\x3E\n{{{\n{| class="mycustomtable"\n|-\n| =cell 1 || cell 2 || cell 3\n|-\n| =cell 1 || span last 2 columns ||\n|-\n||| span just last 2 columns ||\n|-\n| span all columns ||||\n|-\n|}\n}}}\n\x3C/td\x3E\x3C/tr\x3E\n\x3Ctr\x3E\x3Ctd width="40%"\x3E\x3Cdiv class="woas_help_text"\x3EExample default table:\x3C/div\x3E\x3C/td\x3E\x3Ctd width="60%"\x3E\x3Cdiv class="woas_help_text"\x3EUse this code to create the table:\x3C/div\x3E\x3C/td\x3E\x3C/tr\x3E\x3Ctr\x3E\x3Ctd valign="top"\x3E\n{|\n|-\n| *cell 1* || *cell 2* || *cell 3*\n|-\n| cell 1 || cell 2 || cell 3\n|-\n| cell 1 || span last 2 columns ||\n|-\n||| span just last 2 columns ||\n|-\n| span all columns ||||\n|-\n|}\n\x3C/td\x3E\x3Ctd valign="top"\x3E\n{{{\n{|\n|-\n| *cell 1* || *cell 2* || *cell 3*\n|-\n| cell 1 || cell 2 || cell 3\n|-\n| cell 1 || span last 2 columns ||\n|-\n||| span just last 2 columns ||\n|-\n| span all columns ||||\n|-\n|}\n}}}\n\x3C/td\x3E\x3C/tr\x3E\n\x3Ctr\x3E\x3Ctd width="40%"\x3E\x3Cdiv class="woas_help_text"\x3EExample custom table:\x3C/div\x3E\x3C/td\x3E\x3Ctd width="60%"\x3E\x3Cdiv class="woas_help_text"\x3EUse this code to create the table:\x3C/div\x3E\x3C/td\x3E\x3C/tr\x3E\x3Ctr\x3E\x3Ctd valign="top"\x3E\n{| class="mycustomtable"\n|-\n| =cell 1 || =cell 2 || =cell 3\n|-\n| cell 1 || cell 2 || cell 3\n|-\n| cell 1 || span last 2 columns ||\n|-\n||| span just last 2 columns ||\n|-\n| span all columns ||||\n|-\n|}\n\x3C/td\x3E\x3Ctd valign="top"\x3E\n{{{\n{| class="mycustomtable"\n|-\n| =cell 1 || =cell 2 || =cell 3\n|-\n| cell 1 || cell 2 || cell 3\n|-\n| cell 1 || span last 2 columns ||\n|-\n||| span just last 2 columns ||\n|-\n| span all columns ||||\n|-\n|}\n}}}\n\x3C/td\x3E\x3C/tr\x3E\x3C/table\x3E\n\n== Styling table cells\nDefinitions can be used within tables to style the cells. A definition affects only the cells *following* it. If you wish to apply the definition to the entire table, insert it directly beneath the table opening tag.\n\n=== Method 1 - define styles in the wiki CSS\nThis method requires that you add a custom "special" [[WoaS::Help::CSS|CSS rule]] to the wiki so that any table cell in the "special" class will use that rule. See the *table td.special* rule in the [[#The CSS rules used by the example tables|custom CSS]] used by this page for an example.\n\nYou can add a "special" class definition by using {{{|$ class="special"}}} in a table. The definition can be used by any row below the definition by adding {{{==}}} before the contents of a cell.\n\n\x3Ctable width="100%"\x3E\x3Ctr\x3E\x3Ctd width="40%"\x3E\x3Cdiv class="woas_help_text"\x3EExample default table:\x3C/div\x3E\x3C/td\x3E\x3Ctd width="60%"\x3E\x3Cdiv class="woas_help_text"\x3EUse this code to create the table:\x3C/div\x3E\x3C/td\x3E\x3C/tr\x3E\x3Ctr\x3E\x3Ctd valign="top"\x3E\n{|\n|-\n|$ class="special"\n|-\n| *cell 1* || *cell 2* || *cell 3*\n|-\n| ==cell 1 || cell 2 || ==cell 3\n|-\n| cell 1 || ==cell 2 || cell 3\n|-\n|}\n\x3C/td\x3E\x3Ctd valign="top"\x3E\n{{{\n{|\n|-\n|$ class="special"\n|-\n| *cell 1* || *cell 2* || *cell 3*\n|-\n| ==cell 1 || cell 2 || ==cell 3\n|-\n| cell 1 || ==cell 2 || cell 3\n|-\n|}\n}}}\n\x3C/td\x3E\x3C/tr\x3E\n\x3Ctr\x3E\x3Ctd width="40%"\x3E\x3Cdiv class="woas_help_text"\x3EExample custom table:\x3C/div\x3E\x3C/td\x3E\x3Ctd width="60%"\x3E\x3Cdiv class="woas_help_text"\x3EUse this code to create the table:\x3C/div\x3E\x3C/td\x3E\x3C/tr\x3E\x3Ctr\x3E\x3Ctd valign="top"\x3E\n{| class="mycustomtable"\n|-\n|$ class="special"\n|-\n| =cell 1 || =cell 2 || =cell 3\n|-\n| ==cell 1 || cell 2 || ==cell 3\n|-\n| cell 1 || ==cell 2 || cell 3\n|-\n|}\n\x3C/td\x3E\x3Ctd valign="top"\x3E\n{{{\n{| class="mycustomtable"\n|-\n|$ class="special"\n|-\n| =cell 1 || =cell 2 || =cell 3\n|-\n| ==cell 1 || cell 2 || ==cell 3\n|-\n| cell 1 || ==cell 2 || cell 3\n|-\n|}\n}}}\n\x3C/td\x3E\x3C/tr\x3E\x3C/table\x3E\n\n=== Method 2 - define styles on the fly\nYou can add a style definition by using {{{|$ style="CSSRULE"}}} in a table. The definition can be used in any row below the definition by adding {{{==}}} before the contents of a cell.\n\n\x3Ctable width="100%"\x3E\x3Ctr\x3E\x3Ctd width="40%"\x3E\x3Cdiv class="woas_help_text"\x3EExample default table:\x3C/div\x3E\x3C/td\x3E\x3Ctd width="60%"\x3E\x3Cdiv class="woas_help_text"\x3EUse this code to create the table:\x3C/div\x3E\x3C/td\x3E\x3C/tr\x3E\x3Ctr\x3E\x3Ctd valign="top"\x3E\n{|\n|-\n|$ style="background:springgreen"\n|-\n| *cell 1* || *cell 2* || *cell 3*\n|-\n| ==cell 1 || cell 2 || ==cell 3\n|-\n| cell 1 || ==cell 2 || cell 3\n|-\n|}\n\x3C/td\x3E\x3Ctd valign="top"\x3E\n{{{\n{|\n|-\n|$ style="background:springgreen"\n|-\n| *cell 1* || *cell 2* || *cell 3*\n|-\n| ==cell 1 || cell 2 || ==cell 3\n|-\n| cell 1 || ==cell 2 || cell 3\n|-\n|}\n}}}\n\x3C/td\x3E\x3C/tr\x3E\n\x3Ctr\x3E\x3Ctd width="40%"\x3E\x3Cdiv class="woas_help_text"\x3EExample custom table:\x3C/div\x3E\x3C/td\x3E\x3Ctd width="60%"\x3E\x3Cdiv class="woas_help_text"\x3EUse this code to create the table:\x3C/div\x3E\x3C/td\x3E\x3C/tr\x3E\x3Ctr\x3E\x3Ctd valign="top"\x3E\n{| class="mycustomtable"\n|-\n|$ style="background:springgreen"\n|-\n| =cell 1 || =cell 2 || =cell 3\n|-\n| ==cell 1 || cell 2 || ==cell 3\n|-\n| cell 1 || ==cell 2 || cell 3\n|-\n|}\n\x3C/td\x3E\x3Ctd valign="top"\x3E\n{{{\n{| class="mycustomtable"\n|-\n|$ style="background:springgreen"\n|-\n| =cell 1 || =cell 2 || =cell 3\n|-\n| ==cell 1 || cell 2 || ==cell 3\n|-\n| cell 1 || ==cell 2 || cell 3\n|-\n|}\n}}}\n\x3C/td\x3E\x3C/tr\x3E\x3C/table\x3E\n\nMultiple style definitions can be added to a table. The first definition can be used by adding {{{==}}} before the contents of a cell. The second definition can be used by adding {{{===}}} before the contents of a cell. The third definition can be used by adding {{{====}}} before the contents of any cell. And so on.\n\n\x3Ctable width="100%"\x3E\x3Ctr\x3E\x3Ctd width="40%"\x3E\x3Cdiv class="woas_help_text"\x3EExample default table:\x3C/div\x3E\x3C/td\x3E\x3Ctd width="60%"\x3E\x3Cdiv class="woas_help_text"\x3EUse this code to create the table:\x3C/div\x3E\x3C/td\x3E\x3C/tr\x3E\x3Ctr\x3E\x3Ctd valign="top"\x3E\n{|\n|-\n|$ style="background:springgreen"\n|-\n|$ style="background:#ff00ff"\n|-\n|$ style="background:cyan"\n|-\n| *cell 1* || *cell 2* || *cell 3*\n|-\n| ==cell 1 || ===cell 2 || ====cell 3\n|-\n| ====cell 1 || =====cell 2 || ===cell 3\n|-\n|}\n\x3C/td\x3E\x3Ctd valign="top"\x3E\n{{{\n{|\n|-\n|$ style="background:springgreen"\n|-\n|$ style="background:#ff00ff"\n|-\n|$ style="background:cyan"\n|-\n| *cell 1* || *cell 2* || *cell 3*\n|-\n| ==cell 1 || ===cell 2 || ====cell 3\n|-\n| ====cell 1 || =====cell 2 || ===cell 3\n|-\n|}\n}}}\n\x3C/td\x3E\x3C/tr\x3E\n\x3Ctr\x3E\x3Ctd width="40%"\x3E\x3Cdiv class="woas_help_text"\x3EExample custom table:\x3C/div\x3E\x3C/td\x3E\x3Ctd width="60%"\x3E\x3Cdiv class="woas_help_text"\x3EUse this code to create the table:\x3C/div\x3E\x3C/td\x3E\x3C/tr\x3E\x3Ctr\x3E\x3Ctd valign="top"\x3E\n{| class="mycustomtable"\n|-\n|$ style="background:springgreen"\n|-\n|$ style="background:#ff00ff"\n|-\n|$ style="background:cyan"\n|-\n| =cell 1 || =cell 2 || =cell 3\n|-\n| ==cell 1 || ===cell 2 || ====cell 3\n|-\n| ====cell 1 || =====cell 2 || ===cell 3\n|-\n|}\n\x3C/td\x3E\x3Ctd valign="top"\x3E\n{{{\n{| class="mycustomtable"\n|-\n|$ style="background:springgreen"\n|-\n|$ style="background:#ff00ff"\n|-\n|$ style="background:cyan"\n|-\n| =cell 1 || =cell 2 || =cell 3\n|-\n| ==cell 1 || ===cell 2 || ====cell 3\n|-\n| ====cell 1 || =====cell 2 || ===cell 3\n|-\n|}\n}}}\n\x3C/td\x3E\x3C/tr\x3E\x3C/table\x3E\n\n=== Method 3 - combination of method 1 and method 2\nThese examples use a combination of method 1 and method 2. The first definition can be used by adding {{{==}}} before the contents of a cell. The second definition can be used by adding {{{===}}} before the contents of a cell. The third definition can be used by adding {{{====}}} before the contents of any cell. The fourth definition can be used by adding {{{=====}}} before the contents of any cell. And so on.\n\n\x3Ctable width="100%"\x3E\x3Ctr\x3E\x3Ctd width="40%"\x3E\x3Cdiv class="woas_help_text"\x3EExample default table:\x3C/div\x3E\x3C/td\x3E\x3Ctd width="60%"\x3E\x3Cdiv class="woas_help_text"\x3EUse this code to create the table:\x3C/div\x3E\x3C/td\x3E\x3C/tr\x3E\x3Ctr\x3E\x3Ctd valign="top"\x3E\n{|\n|-\n|$ class="special"\n|-\n|$ style="background:springgreen"\n|-\n|$ style="background:silver"\n|-\n|$ style="background:cyan"\n|-\n| *cell 1* || *cell 2* || *cell 3*\n|-\n| ==cell 1 || ===cell 2 || ====cell 3\n|-\n| ====cell 1 || =====cell 2 || ===cell 3\n|-\n|}\n\x3C/td\x3E\x3Ctd valign="top"\x3E\n{{{\n{|\n|-\n|$ class="special"\n|-\n|$ style="background:springgreen"\n|-\n|$ style="background:silver"\n|-\n|$ style="background:cyan"\n|-\n| *cell 1* || *cell 2* || *cell 3*\n|-\n| ==cell 1 || ===cell 2 || ====cell 3\n|-\n| ====cell 1 || =====cell 2 || ===cell 3\n|-\n|}\n}}}\n\x3C/td\x3E\x3C/tr\x3E\n\x3Ctr\x3E\x3Ctd width="40%"\x3E\x3Cdiv class="woas_help_text"\x3EExample custom table:\x3C/div\x3E\x3C/td\x3E\x3Ctd width="60%"\x3E\x3Cdiv class="woas_help_text"\x3EUse this code to create the table:\x3C/div\x3E\x3C/td\x3E\x3C/tr\x3E\x3Ctr\x3E\x3Ctd valign="top"\x3E\n{| class="mycustomtable"\n|-\n|$ class="special"\n|-\n|$ style="background:springgreen"\n|-\n|$ style="background:silver"\n|-\n|$ style="background:cyan"\n|-\n| =cell 1 || =cell 2 || =cell 3\n|-\n| ==cell 1 || ===cell 2 || ====cell 3\n|-\n| ====cell 1 || =====cell 2 || ===cell 3\n|-\n|}\n\x3C/td\x3E\x3Ctd valign="top"\x3E\n{{{\n{| class="mycustomtable"\n|-\n|$ class="special"\n|-\n|$ style="background:springgreen"\n|-\n|$ style="background:silver"\n|-\n|$ style="background:cyan"\n|-\n| =cell 1 || =cell 2 || =cell 3\n|-\n| ==cell 1 || ===cell 2 || ====cell 3\n|-\n| ====cell 1 || =====cell 2 || ===cell 3\n|-\n|}\n}}}\n\x3C/td\x3E\x3C/tr\x3E\x3C/table\x3E\n\n== The CSS rules used by the example tables\nThese are the custom CSS rules used by the example tables on this page:\x3Cbr\x3E\x3Cbr\x3E{{{\n/* CUSTOM CSS FOR custom tables */\ntable.mycustomtable {\n	color: #404040;\n	background-color: #fafafa;\n	border-collapse: collapse;\n	border-spacing: 0px;\n	border: 1px solid #6699CC;\n	font-family: Verdana, sans-serif, Arial;\n	font-size: 11px;\n	font-weight: normal;\n	/*UNCOMMENT THE WIDTH LINE BELOW TO CONTROL THE WIDTH OF THE TABLE*/\n	/*width: 500px;*/\n}\ncaption.mycustomtable {\n	color: #404040;\n	border: 1px solid #6699CC;\n	text-align: center;\n	width: 580px;\n}\ntable.mycustomtable th { \n	color: #404040;\n	background-color: #BEC8D1;\n	border-bottom: 2px solid #6699CC;\n	border-left: 1px solid #6699CC;\n	font-weight: bold;\n	text-indent: 2px;\n}\ntable.mycustomtable td {\n	color: #404040;\n	border-top: 0px;\n	border-right: 0px;\n	border-bottom: 1px solid #9CF;\n	border-left: 1px solid #9CF;\n	text-indent: 2px;\n}\ntable.mycustomtable td.special {\n	background-color: #ff00ff;\n}\n\n/* CUSTOM CSS FOR DEFAULT TABLES */\ntable td.special {\n	background-color: #ff00ff;\n}\n}}}\n\n\x3C!-- NOTE: BELOW ARE CUSTOM CSS RULES EMBEDDED ON THIS PAGE FOR USE BY THIS PAGE. --\x3E\n\x3Cstyle type="text/css"\x3E\n/* CUSTOM CSS FOR custom tables */\ntable.mycustomtable {\n	color: #404040;\n	background-color: #fafafa;\n	border-collapse: collapse;\n	border-spacing: 0px;\n	border: 1px solid #6699CC;\n	font-family: Verdana, sans-serif, Arial;\n	font-size: 11px;\n	font-weight: normal;\n	/*UNCOMMENT THE WIDTH LINE BELOW TO CONTROL THE WIDTH OF THE TABLE*/\n	/*width: 500px;*/\n}\ncaption.mycustomtable {\n	color: #404040;\n	border: 1px solid #6699CC;\n	text-align: center;\n	width: 580px;\n}\ntable.mycustomtable th { \n	color: #404040;\n	background-color: #BEC8D1;\n	border-bottom: 2px solid #6699CC;\n	border-left: 1px solid #6699CC;\n	font-weight: bold;\n	text-indent: 2px;\n}\ntable.mycustomtable td {\n	color: #404040;\n	border-top: 0px;\n	border-right: 0px;\n	border-bottom: 1px solid #9CF;\n	border-left: 1px solid #9CF;\n	text-indent: 2px;\n}\ntable.mycustomtable td.special {\n	background-color: #ff00ff;\n}\n\n/* CUSTOM CSS FOR DEFAULT TABLES */\ntable td.special {\n	background-color: #ff00ff;\n}\n\x3C/style\x3E\n',
'= Links\nThis wiki accepts wiki and HTML links.\n== Wiki links\nBelow are methods for creating links using wiki syntax.\n\n=== Anchor link\nYou can use a special anchor link to link to a header on the current page. After clicking an anchor link, use the browser\'s back button to return to where you were on the page.\n\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page, replacing HEADER NAME with the name of the header you\'d like to link to:\x3C/div\x3E\x3Cbr\x3E{{{\n[[#HEADER NAME]]\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create an anchor link.\x3C/div\x3E\n=== Email link\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page, replacing USERNAME with the name of the person, SERVER with the name of their server, and LABEL with the text you\'d like displayed as a link:\x3C/div\x3E\x3Cbr\x3E{{{\n[[mailto://USERNAME@SERVER|LABEL]]\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create this:\x3C/div\x3E\n[[mailto://USERNAME@SERVER|LABEL]]\n\n=== Email link with subject\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page, replacing USERNAME with the name of the person, SERVER with the name of their server, SUBJECT with the subject of the email, and LABEL with the text you\'d like displayed as a link:\x3C/div\x3E\x3Cbr\x3E{{{\n[[mailto://USERNAME@SERVER?subject=SUBJECT|LABEL]]\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create this:\x3C/div\x3E\n[[mailto://USERNAME@SERVER?subject=SUBJECT|LABEL]]\n\n=== External link (labeled)\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page, replacing URL with the address of the web page, and LABEL with the text for the link:\x3C/div\x3E\x3Cbr\x3E{{{\n[[URL|LABEL]]\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create a labeled external link like this one:\x3C/div\x3E\n[[http://www.gnu.org/|Click here for GNU]]\n\n=== External link (plain)\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page, replacing URL with the address of the web page:\x3C/div\x3E\x3Cbr\x3E{{{\n[[URL]]\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create a plain external link like this one:\x3C/div\x3E\n[[http://www.gnu.org/]]\n\n=== Image as link\n==== Base64 image as link\nSee the [[WoaS::Help::Images]] page.\n==== Image as link to wiki page\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page, replacing IMAGENAME with the name of an embedded image page, and PAGENAME with the name of a page in this wiki:\x3C/div\x3E\n\x3Cdiv class="woas_helpcode"\x3E[\x3C!-- --\x3E[I\x3C!-- --\x3Enclude:\x3C!-- --\x3E:Image:\x3C!-- --\x3E:IMAGENAME|style="cursor:pointer" oncl\x3C!-- --\x3Eick="go_\x3C!-- --\x3Eto(\'PA\x3C!-- --\x3EGENAME\')"]]\x3C/div\x3E\n\x3Cdiv class="woas_helptext"\x3ETo create an image that links to a page in this wiki.\x3C/div\x3E\n\n==== Image as link to web page\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page, replacing Image::IMAGENAME with the name of an embedded image page, and URL with the address of a web page:\x3C/div\x3E\n\x3Cdiv class="woas_helpcode"\x3E[\x3C!-- --\x3E[In\x3C!-- --\x3Eclude:\x3C!-- --\x3E:Image:\x3C!-- --\x3E:IMAGENAME|style="cursor:pointer" border="0" onc\x3C!-- --\x3Elick="win\x3C!-- --\x3Edow.o\x3C!-- --\x3Epen(\'URL\')"]\x3C!-- --\x3E]\x3C/div\x3E\n\x3Cdiv class="woas_helptext"\x3ETo create an image that links to a web page.\x3C/div\x3E\n\n=== Include link\nYou can include the contents of another page of this wiki in the current page by using an include link.\n\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page, replacing PAGENAME with the name of the page you\'d like to include:\x3C/div\x3E\n\x3Cdiv class="woas_helpcode"\x3E[\x3C!-- --\x3E[Include::PAGENAME]\x3C!-- --\x3E]\x3C/div\x3E\n\x3Cdiv class="woas_helptext"\x3ETo include the contents of another page in the current page.\x3C/div\x3E\n\n=== Internal action link (labeled)\nThese are internal wiki actions in the form of links:\x3Cbr\x3E\x3Cbr\x3E\n{|\n| *Use this code in a page:* || *To create this:*\n| {{{[[Special::All Pages|All pages]]}}} || All pages listing\n| {{{[[Special::Backlinks|Backlinks]]}}} || Listing of pages linking to page\n| {{{[[Special::Dead Pages|Dead links]]}}} || Pages without any link to them\n| {{{[[Special::Delete Page|Delete page]]}}} || Delete page prompt\n| {{{[[Special::Duplicate Page|Duplicate page]]}}} || Duplicate page\n| {{{[[WoaS::CSS::Custom|Custom CSS]]}}} || Custom CSS\n| {{{[[Special::Go to|Go to...]]}}} || Go to page prompt\n| {{{[[Locked::|Locked pages]]}}} || Listing of encrypted pages\n| {{{[[Special::Main Page|Main page]]}}} || Main Page (whatever title it has)\n| {{{[[Special::New Page|New page]]}}} || New page prompt\n| {{{[[Special::Orphaned Pages|Orphaned pages]]}}} || Orphaned pages listing\n| {{{[[Special::Recentchanges|Recentchanges]]}}} || Recently changed pages listing\n| {{{[[Special::Search|Search]]}}} || Search into pages interface\n| {{{[[Tagged::Syntax|Syntax pages]]}}} || Pages tagged with \'Syntax\'\n| {{{[[Tagged::|Tagged pages]]}}} || Pages which contain tags\n| {{{[[Unlocked::|Unlocked pages]]}}} || Listing of pages without encryption\n| {{{[[Untagged::|Untagged pages]]}}} || Listing of pages without any tag\n|}\n\n=== Internal link (labeled)\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page, replacing PAGENAME with the page you want to link to and LABEL with the link text:\x3C/div\x3E\x3Cbr\x3E{{{\n[[PAGENAME|LABEL]]\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create a labeled internal link.\x3C/div\x3E\n\n=== Internal link (plain)\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page, replacing PAGENAME with the page you want to link to:\x3C/div\x3E\x3Cbr\x3E{{{\n[[PAGENAME]]\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create a plain internal link.\x3C/div\x3E\n\n== HTML links\nBelow are methods for creating links using HTML syntax.\n\n=== Alert box link (button)\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page, replacing MESSAGE with the content for the alert, and BUTTONTEXT with the text for your button:\x3C/div\x3E\x3Cbr\x3E{{{\n\x3Cscript type="text/javascript"\x3E\nfunction alertme () {alert("MESSAGE");}\n\x3C/script\x3E\n\x3Cinput type=button value="BUTTONTEXT" onClick="alertme()"\x3E\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create a button that opens an alert.\x3C/div\x3E\n\n=== Alert box link (labeled)\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page, replacing MESSAGE with the content for the alert, and LABEL with the text for your link:\x3C/div\x3E\x3Cbr\x3E{{{\n\x3Ca href=\'javascript:alert("MESSAGE");\'\x3ELABEL\x3C/a\x3E\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create a labeled text link that opens an alert.\x3C/div\x3E\n\n=== Anchor links to anywhere on a page\nWith a combination of HTML and wiki syntax, you can create anchor links to anywhere on a page, rather than just to headers.\n\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page, replacing NAME with the name of the anchor:\x3C/div\x3E\x3Cbr\x3E{{{\n\x3Ca name="NAME"\x3E\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3EUse this code somewhere else on the same page, replacing NAME with the anchor name:\x3C/div\x3E\x3Cbr\x3E{{{\n[[#NAME]]\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create an anchor link to anywhere on a page.\x3C/div\x3E\n\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3EOr use this code somewhere else on the same page, replacing NAME with the anchor name and LABEL with the text for the link:\x3C/div\x3E\x3Cbr\x3E{{{\n[[#NAME|LABEL]]\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create a labeled anchor link to anywhere on a page.\x3C/div\x3E\n\n=== Edit page link\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page, replacing LABEL with the text for your link:\x3C/div\x3E\x3Cbr\x3E{{{\n\x3Ca class="woas_link" onclick="javascript:edit()" title="Edit this page"\x3ELABEL\x3C/a\x3E\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create a link that opens the current page in the editor.\x3C/div\x3E\n\n=== External link (button)\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page, replacing BUTTONTEXT with the text for your button, and URL with the address of the web page:\x3C/div\x3E\x3Cbr\x3E{{{\n\x3Cinput type="button" value="BUTTONTEXT" onclick="window.open(\'URL\');"\x3E\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create a button that opens an external link in another tab or window.\x3C/div\x3E\n\n=== Internal link (button)\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page, replacing BUTTONTEXT with the text you want on the button and PAGENAME with the page you want to link to:\x3C/div\x3E\x3Cbr\x3E{{{\n\x3Cinput value="BUTTONTEXT" onclick="woas.ui.go_to(\'PAGENAME\')" type="submit" /\x3E\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create a button that opens an internal link in another tab or window.\x3C/div\x3E\n',
'= Images\nWiki on a Stick offers various ways of using images in your pages. You can link to local image files on your drive, remote image files, or embed image files into the wiki.\n\n== Add an external image from the internet\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page, replacing URL with the full link to the image:\x3C/div\x3E\x3Cbr\x3E{{{\n\x3Cimg src="URL" /\x3E\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo add an external image from the internet.\x3C/div\x3E\n\n* *Example:*\x3Cbr\x3E\x3Cbr\x3E\n** \x3Cdiv class="woas_helptext"\x3EUse this code:\x3C/div\x3E\x3Cbr\x3E{{{\n\x3Cimg src="http://www.gnu.org/graphics/heckert_gnu.small.png" /\x3E\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create this:\x3C/div\x3E\x3Cbr\x3E\x3Cimg src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJEAAACMCAAAAABZ%2BxS6AAAACXBIWXMAAAsSAAALEgHS3X78AAAAB3RJTUUH1AkZAiQF53xPugAAGFFJREFUeNrNXH9MVHe2%2F1QmS1OSaTrJmOUdZnYWybDsBJgSUXgID62BpayUEvz1RMKqhfW39Wmrr2qtGik1UnWFwKoLq0RXKWgJ9EHUrU83UOFJkUgDD3iwYKA7E9hlwmxmdoac98e9M8zcucMPq%2B1%2B%2F5mZ7733ez%2F3fM%2F3nM8553vnJcY%2FWVPMfNgyOm6xAAHBUKnUPzSi8QdfP%2B6xeHQE6mLiU4Oe%2FVbO%2FuG%2FjluhfFWp0s3wdC%2FJz9poXWNbgCFaF6IKETq%2BdT4d6Wl%2FGpixW%2FdMcIZr73bZVa8tBEz%2FMNmhjF2yMnweiNrK7ryamh7rK4%2Fh6ivjv94bOG88fYVNuoz4aKVLFwZ6vmnt1GVsDJY7mX1aczal1TuE7476hzZm7j6TueFM8yQz28oiEm7z%2FJrtpDb3kU%2Fv2PW12k0dvmf7IBrKpxT3LRsTiEJX7VlKQjNm7mvksX20aUiAa5sToPsJK5qZebLjwqal%2B%2B472NZ83iQc6d2jFYfyaJJZc5acC9q%2FEcDjyoWBgXfaXP0Bhk4AStUgCg4q2g%2F1rE8PGf%2FqyjXNjHNlDQIwfryuYK%2Bi6UrfU6FTtajTDuXuzcKSenyw%2F%2FiamfRodFtb%2Bgk1AGS0e66y8BMxn5%2Bwbtmi7KqtW1SqQu2VNgSE9fTMvPLOjq5UPLgWfTwMFUcVqZ2DQOo3w%2BKxRTFme%2FJ2wHmqZPUphV89ehipvy58uyrOk757cmhogpmZJyfFk86PMbNtiHdkzjJd9dpQ7YY%2FMvOxVZcn2HFhRT1PHFmXFJeUJgyuHRDOypz0p0f1%2BoRuZp7ovn8jQkTk0qih1ubmIYfnhRMfaJtn02hjvfAMvZIDjrXC6PmCsuozx%2BT1qGGb7jM1cCdP%2BJlqABa9BYw%2BaOnsmwKAgDBDVFSYCrAP999pmCpOn23RN717Imv612DPoBUIMRgAc4oZAJCs%2BXFyNFo3%2FrwqSEaP2leHfKYGnMmDAIDwuwBgqalrgy42epEmwDzc0%2FX1uOvsRb%2FYMgev0rT%2FtfSfAhifGP22a1xl%2BDHsXf2qNzcaTHZ8e%2FYeACi%2FUqIlb1m5wgfReKrzi2AAJYXC78KNgKXkoj02PdVjRZm7vwXwmvqnyrmZRmtTe78TQEiQ%2BmdRgj0cbbzZnnw0DLiXAwAoOAw0bNty2Eez87UdzMzNemGKI23MNUbtnl5%2BAa17q77IwSyYudAhZq6hRqlm11MZM3dkC3gyL4%2BxbQ%2FlDvALao8S0kx8O4WIiOKuM%2FMm%2FYg3ItviVQ5m3iSsyhFmnsx2WYIX0iY3GHuZO3YSEdEx5rHIXG9Ep7W9zMwrBNDM7Mg0dvCLbI53jEPMQ0REtI6Z613ztkBwxhfzwgBAWGZqAAcHq6NnU9vx4eHh0WcmZqWGAjteFSgFgPTET5wejO2ifQcAjNoBAEqg9kZ12Ezjjd5p6RqcAgDoYt9aJuF9zrtffj1sgea1sPhUlX9IbxYeFZbsIAAcSm14y7367XFZhwHg7CkAUJYsty7LOjwDnq7iO4YlP9colYD9ac%2BXLaqNWzysgbP2VEBWjC4QFnNf%2B734LfH%2Bhml%2Fuy56cxMA1MUA2Gypdq%2F%2BW1oTM%2FMxIiJaMcBcZJz0rwFjW0OPeBmFkdMRxlvuX0%2FSjNen%2FY2tJmGDXwuyJ5sdB4iIjB3MfJ%2BG3Jq9KZeZuUZY%2BWPMptAZltkfIzb4cBpbkXaP%2BAwVoflj3ip8OeKCn6FGQjuY84mIIpiZF1cwMysAWO%2BdAoAoQZY98TgfnOV3xioO798tfLM%2F7jEDqpCohQh87638jEs6wLm%2F9j8LAGC0fdAeHGUAoNiYWNB%2FXDbCCM6qisYwAGgAIFVwqcx8Wys8n5GIiM7z2AwiuqCtF%2BnuplCRH1DKBRvzZLa%2Bnh3vUFzFCNtqMoUji4%2BZmJknM99xyI7WEemwaYmI3mdmvq13%2Bf7jbXUAgG11AJBcVXLxK3%2Fk%2FuLx0nQAzsrKQc9u3bkYOLd9kTHYCSBgZVSl2U328nYrAWtOyG9kx1t2KiALAIrXALD8%2FG44sABAT4wYIgAADKjL8AeoQgB0L%2FmoFyAMvl0LxW%2FfrOsE4nOCmk4tWe06Yi9f9jkQVNX5oTyizp8pAeApAChDuiEiWiR4%2B3sAoNk12rXcD6CGo0fTAeu7OYPSI1O7LgKlGev3pra0Ve8Navhql5vwjG8vsCLoD3UlciMa2pW7AeDsAwAI6xH1iJo9iOx%2FcSP5WfoPQ08yc28cybYLQmxyPyJpYmQr6Y8Zpw8lmZgf6e%2FLxSlp7EggIoocYuYDOwUvMopgAM4yAEDiLzAYLE%2Fo%2B7akHgRa04flBXj0%2BKEAAImXBsuCS4ttF%2FOmjWb%2Fm32IKdwuc2HwX6EoAIAJCwD1qDBrTgQAUJQGAsCwE1Z5cmjeuOgc0LTe6s8ulI8qACB%2BYzWwpgqleR5OZ6MZWalb7D7XBA4DdQDwngGASkQEwd8ZjgLAYDUgKyJr9o8uKdCSb%2FfvXESumToKIPE966BHDD2cY0VhgKx2t7QAiCkAgCCLC5Eg3zU6APhE%2Fpb2HEuVCj15U7PnWRRBAJAX1BLr6QoPQVF%2Bs0nmqhIACBDW%2BbgLkRkAhjMGAWDchKcy1737TaUG5nXWmQjBhLi4VAAQFG72siHVN6Ap3C8lL0%2BDEA8AbW%2F3uboWAIANgDWrCwDwa9nI%2BXjduWhYc8wzUp5vhY8oqxkArIvGvY4e7kHWG7ucktUSAiGmsVg9EAnCDqrSAIB6O4KHfSbuWvnhVGB%2F10x4VMHiY2rKD%2FUBj3uy7nkr4jY7PhqVWKX%2BMASuAbDoZrQHomDh4cIblgFQmhCOdqllPLCxACis8w%2FHcKqts2HUJPyIL%2BmEZdf2WonO9XwIZXlpq1dfd6zzbBmg%2Fmw6T7YAQKCgAKrdAPpTS6JUn3sP1bor8ThwzcfoKjWipkRdalofjIWGBpeKZ3Wt%2Bvf1H1dXnYrxPL%2FqBgzHd3vOpaVt5d1TU4D5PADAFCCuj4WCejTtAgB7w%2FbVlf%2Bh9rKMYeUKNB3wHDxgSXKMQQn0tY0G6cTQEOs%2BTRSp8JXi4uXQAQhshyGk06XP%2B9XL1%2FQXXJtmJjcMOuHel%2FARAMu%2FiF4k%2B6SQoxBMfi5PROR7GPpeY5KJ%2BWHotFMwbqpx%2BxmvrNYB%2FWUHM%2FMto4s2DtFOZh6pyRcGj%2Bhl3jrNTCYiG8VohOgWM%2B9MEznkzk3M%2FLdc8Z4HmK%2FTBQ9AxgHmh0Koq82%2FNeDNdE578ckLEQlnupn3nHf36K8KnwNbiYgowcSOd3Jdz7N1FbONiCjz6ggz86p8EVHZYiGou72BiOgMMx8g15hXIxIGmG%2FriYi0B3zo7FXy7po4n0lJ109nuyWXvcf17Vao6HQdW5OamZkdB4xDzJN6F2FjjigSEd2nvwiQVhARFU0yO%2FZR%2FgAz966jXBNzhZaIaJM05h5ZG0nkItUdpyf4PjPz2OWE3D1J3S4cIudkl5yTBpgvaNddbr6QkNTLlzO103mqAWoUEU1oa5iZeYeY%2Blp1nbnGSCnZCWSsYR7LJyIy1nvDKVuXnUBERrd0QkPjSMBs23Tm9CZmnixad5Ivh7ovvC3oUj1z774Vcbm%2FszFHiloUaWLmy9oJV0brzbBzAK4cdFuXJsBa12kLNqwMRO3RcQDJ57xDwcKS4KgmAKs%2FdfXcK38AMSi49iC8%2Fxwsq%2FqBm7FN22pd1u%2BTc4K92p7s4hOvCzY1JyxWBax23nTFa2f0NmbuuHUyQZTS9AJqFnKGJyUT1kgJk%2FeJiFo9Oov2pDEzPyky7otoZT5JRGttzEdS3Asz0xUrdDBz75MxQWoUMSGsy%2BvueE38ziYxeUQdbOplZu4VszcfSDXaSI3cLOYQPEPCDma2%2FW7Ph93MtgiitQ5mHiGTO1nuym%2Fqn3Cvnkg0OFTEzHwkwjadG9mQJgzosjiRWqLzzA9FZpotjW0ekZG5mcgoptp7mzsczMwD%2BzwIKxHVDzEzR0wLst5t0zqSPBhxqInZFHqaXREkkJfXEg88rnazdQCFwSEbBYesPKeQRpGIBwYRXq4Bxq81DauUPbG%2FB6BzDrodVCcQcOPO3w%2Fq8Oq0w09PFz2N2SOtmpcOAGcDt3jmIVNfuQk0dcF5zsNTvCwyhFPrJUW3SFVpPMzvxhco4Lz0ID05GLAmn0gF8PjEJRe9PngFsTeB9hjEF08nI4aX%2BXI%2B9X8rgcF%2FO7xlmh8Bh9o%2BB1L37m3zjHhEQDESQPjy1ep44JWq7QqM1yZXrQ8GEJR6BQCix4%2B7zrIBGQBiYJ%2FwSJxoVkrxpGdoKgB8qMnzYqKJ6SeWK4HKFgSbpA9xUPL7QVWdDiIbV7mLGppaIensJkVB2PUrIQ7WGDyuTpUyW4Fi1951OWBRRjhhPQDg9ZanbVLe%2F4Z3%2Fsf%2Bub1apujntFnGAWDjqItnqpAIy73q6os3yj1PTPa%2Bbr%2B6FQDMR1cnSqqi6hO7EtcjGuiyBOm8uOJeya3fmubSACztw%2F1f9kMzMoWm9QDiU1tFnV2Ee2X3pgAEPvB8goWG6dFDnuJX6QcBOHcGfuTFswEgK%2BdoDwC0aOpygAD3VfGSdKQgQpe%2BFb%2Bec%2FBiPzD8clT8RQsAHG1xGX6U3J0CAPvBG54DrHbLECd0OmVYqRMoaSlX%2BiDC4UV5ZgABDeExwHvu7gJZFuviptVuSl5YXdcHAJoYcdrCFgX8qQAA3gio9rx0jRqAUoOjyoD4gwZArcDnpz6KkatmjSxeJRAXh36fLZQWbyCtlhLkUwCu5HOctugvJhetcrEA16f2SBwRUe%2FJOAmDIaLr1NyYxtwscMUjfqpZT%2FSZQiktd4zXhnbk0wdE5%2BXz46vELxsSJnlCS0S0j1nKVs5rQ4mI%2FsLeaUDOJqJHW5td0Cu0Bxz%2B6rQP9UkDzMxjzOevc3ZKPWnHZBE1u2Q0ZNzk4GNEpH%2FEvT4p%2BVwioiOOh42SUrCeqGLsichidtDpGSrHHcZIkc1MMm%2FoPUm58pN2%2BkP36Gmrmrlm37FudhxzdEtOm4hb1dzdmhD5RNJfQ7RTZAPXjYtvz1jLHkmjrS7hP%2BFsqpFHlFkxnYr9XULmmVvNzVfXPuQyH1lqUyIpdKfP9TsodIKZe88sDT0yMXMtG85zZxUZecKKd4ajUzZPMhpb5xmIDbYPDr8cnq525lVJzywsMSS%2FuaZTmkW0b2vK0nV1jqrXyNQNffAP7NFSSlGzjbmDNsiL6DxNyHUXnfStx6QcY15R79ufRpTy4X25DC7kEt9FSUTalK25VCFf9lkcKbf%2BLlyWucEjbTefXOe7VaGe%2FNXuIN89cHnnCi3RkOzB65Qy96rVgTRuJqMPWBM9mh8iZmbHUqN80TyOsueOaCziqk2fHbpCahq0ZX4uWDBDykzzsmx%2F2fB86miqguP2jNgmZUald3%2F0FX%2FZsRkeLztOtuwbmrQvYR7FxonQottJ7CjTrmr1yhtrEzasG5vfrHGaHKLJFdT8RGubB6QP9A9pgvlJGqVd8ChsNa6NoxvzQzSgTZPp3UofMKfcnweiAe0mGmJmR022loxF04bjDD2ROd3%2FPjbngalE397CuvDDQE5dop%2FNYXXtFqjDohI9bKJuzTWYNYAiK8veXVdetT4%2B%2BBUAPbV1bxvmpUf7KNe3GnFBCNEcv5QtVAzla8mYnZ0ZSUZP92OK9Mj%2BjLzvKk8sPeOY1%2BpvpiTfC4pIzFV1nJStlunPCPsInuyg%2FAlP95Y0ycwD9YL62YaGhoaGxuZrj4roso9Sb6UVLtWsMflesiL04bTeRsR5%2BI5G%2FdI9G4xESU9mVzu%2FiPaISSUPESTQhunJ8jW5j%2Bjk7RUUeUQQ7cg6yp42%2BU%2ByKWLD1asrZt2zNAOiVUkSs39AS%2BSYaagKOqKNKLp%2F%2FoJ7z2CoVMyTmRueGVENbfX82ft%2BKOmJZhzqDBFdFciey51G7pM8w%2FXQWS2Z%2FOq3FpfDHa3au1oaOhG0%2FfWNM%2FuLMAA6wNz8j4AYIUJLX5KzrdTrDgZ796ybP2RXcRJliia%2F%2BViKlogijph4bJZZm4yI1E9wt56IaIe4kib3ZXpR3SF%2FrHRmGdlz%2Bt8UHq1vVycQaFiyLDEQUKnGB2faTBK0vaE6CHesgNJ%2Bs%2FMzNQAEneq6FpzjSURn3foih%2BhG%2F%2FpCBQB05ZgRMPWeK4gMb2mXIrKWjGqiXOmOgm4AMQBif3O6cmeVMLRBYpftsyGSYyNNykJFz5UGc2WWeXtnNFTTitLic%2Bq5mAx3%2FkXx6d%2BB%2BO0BiFF%2BVNpyRe52TxH8LDJqD%2F9ToZAuKDgIj5Ka2lVB88gxpXqqu0IN4OB2uxpI33vpV7LRefizyGiqPac7Jt0ApL83XTMFAPxYemrGJcAyLJaDRAqmVAPA9qB2mT1UleqoZ5HR0XNRB3XAOFQArJ6IfEQeCCCw88ZUeRDQN%2BiZP1Ns7orxMSqbzccVeJbV79mI3Ib%2FtCQe9iBlW5kn3pEYA5%2BUwWQmveOY1WYvmJ0pB06njhr8UOzDX9%2FBjbckxkCaiRvPa8sonV1Ec5CR21%2F26knrx8DdSrD5sKm%2FSSK0SDo2F8q5YB5hRVhP3tQd%2BUNvBZVZpAH5q167bzYX2M8dnstd5oMISIbFz5FtxTPtZ3%2F87r81RdVm4TkhMns6U38bjtM1fo3x4NnUjM6VWGSY42PPNq168iBZk5Tv77zr5IeMTTR3jDDz%2B1Q2t9BlVkRx5JnZCE3wt3wdCQkzrmxHLuVPPhfNVnvOGkIG179bIW9qTwyem9EU%2F%2F7ivW3PRY9C4LlFLBot1Ycvymt9%2BtkZd3HgF6vvmp8HojAMeJrChvK34UcWhaqC8Zk3tMP5PDS7niTpKxuRH658X%2FKehUTDT2vnlHZ6abY36kZj0ebtYGNHdRrd5jDZ%2FaRRpV6%2Bw9TS1j%2FyD7yiekXV%2F3hKPfN%2BZtl3auRa3NMSb49V8bEVSL0kd%2B6VQ0hPjl4kMOKulpZ%2BL5N1Yk7v5M2O6EBVzse%2BKdjkKvkNzoUtADRqmEemAJ1BFwiYx0dHR1GQp5mbgZwd0YP1IV9JncIWU%2FUSfxb6TkvfUztUi6Jfj1%2Fo7i0u%2FkaJ52Sz2RFJ0lz%2BYj%2FlEv%2Btkeb81sDsfk2xGo1SbUc65tdC8Pe5njoHT7sR1ZIeHbrwwtocEOneGLwnKd3iUNv8btMH9XNEhB2QuLItKvPbb5fU9s0d0V1l2FxPnQPvRWzi3S4vcqOpvtjX2QaIoa%2FfZjGZLRYgJHj4Tuqcwb80l7ege1L%2B9Zq0rysVwJu%2F9ePB%2Bga%2FGex7Op1DV36he54yQnjepTvSvQxKAPiiMk%2FGILV1edCF4Kjw4JdfiVc9XxnBujLgrrRGdm10vNai%2FhoQXwoV29k%2FeEVQlSvnvdjmZrbuaz%2BU6b0tZN0ca23eoaOWKKVxcuBqxNzioXmyWnc8%2B0eZUJaWMrNtK4149%2FeeueVgZo7Q214cIs6NGJF2lRGdZMfpCAqVv8REK%2FgFIppMSpuU5uApcoLfF194k8330jO8JDjnCDLoM4tkc%2FUVIEOJmwCGK%2FpqD8kw1uLA48%2FgRuZRAzJ%2BIKkK0C9drwYJ71d6MgZR%2B7pfnIwAXXVTsZcvUePrQ04xZ5Yq4WOjrQCQjgcvwtNOhyXXPvd0cKrKIFRmBcqkHQDgawB4xW%2Be4DllIsI%2Bq%2FbMd0bfCEb7XWGz0og0Bdot7PcLebGIoK5ubfCEdDcdAHbrgHuSXFejAmY8PqRc%2FiI1W6x%2B1EvzVJRiMhKleW2T6NBeHtl0W0%2F1L9AeTVPmL73N4Fqixhryrp09MmoHMiN89iu%2FIEQ85L3QHb%2BktY4EItKeFMU0cUxLS%2BOI9kw8AyB%2B6bv%2FS8zmpqCe1tVTAALLVwKWi8IWu4DS9GcabsF3xeMsb0IUlpQGALBbgTvLioUVH%2FZsgL4zohvxxxF4CEivBICFaN3syo%2F0dP0giB7vHVWtvhsNCBGUBcXTe2D3O38IRP%2BL452f6gBcqxMKMX%2BePta5zfkDIHodQvHDIjj5WqtnLeeLjHY4HwNm5%2FeIKGyvsOX4mgVRAMzv%2FtrzaGdGiO4U%2BioU36dm7xp%2FDAAPsP09APjiE0l5Sbm%2FeP3GeY2o%2BI6IFL9pFWKl%2FaMBUwB6pEFUlvMPwd%2BvPcISAMiCRbNb7uhTZ2n8%2FMYLOPpcEhqhE08SYv%2FPLSCl0%2BUK1JeXf79rzd0%2BUvUpTi2D8CcTUNWJeYLEuiXzHek5yQiICfzRjzL%2F3I31if%2FD%2BNtPihe0TwGa5d0tpp%2FOT1dfer7%2Fx1b5ifXjZdX3Hk%2B9sUP35TfDdvXPlhnmOcRzRoTx2i7N%2BmDnoPnpT5Y82wgv%2FdP9Z92CfzZA%2BH8h6HFxmnJRXwAAAABJRU5ErkJggg%3D%3D" /\x3E\n\n== Add a local image from your hard drive\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page, replacing PATH with the full path to the local file you\'d like to display:\x3C/div\x3E\x3Cbr\x3E{{{\n\x3Cimg src="file://PATH" /\x3E\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo add a local image from your hard drive.\x3C/div\x3E\n\n* *Linux example using a default Kubuntu image:*\x3Cbr\x3E\x3Cbr\x3E{{{\n\x3Cimg src="file:///usr/share/icons/default.kde/16x16/actions/stop.png" /\x3E\n}}}\x3Cbr\x3E\n* *Windows example using a default Windows 7 image:*\x3Cbr\x3E\x3Cbr\x3E{{{\n\x3Cimg src="file://localhost/C:/Documents and Settings/All Users/Documents/My Pictures/Sample Pictures/Blue hills.jpg" /\x3E\n}}}\n\n== Embed an image in the wiki\nThis wiki provides an interface for embedding images. Each image will be given its own page, which you can use in an include link to insert the image anywhere in this wiki.\n\n[[Include::WoaS::Template::Info|Embedded images are encoded with Base64 and are 33% bigger than the original files. Since this wiki is a single file, excessive use of images will slow down performance.\x3Cbr\x3E\x3Cbr\x3EIt is recommended to keep the extension in the image name because some browsers infer the mime type of the image from the extension.]]\n\x3Cspan class="woas_helptext"\x3EReplace NAME in these instructions with the name of your image:\x3C/span\x3E\n=== From the menu\n# Click the *New Page* link in the menu.\x3Cbr\x3E\x3Cbr\x3E\n# Type {{{Image::NAME}}} into the box that comes up. This will bring up the image embedding interface.\x3Cbr\x3E\x3Cbr\x3E\n# Browse to the image on your computer.\x3Cbr\x3E\x3Cbr\x3E\n# Click the *Embed* button.\n\n=== From any page\n# Create an include link for a new image by typing {{{[[Include::Image::NAME]]}}} into a page, or click the editor\'s *Image* button and type in a name for the image.\x3Cbr\x3E\x3Cbr\x3E\n# Click the *Save* icon in the navigation bar to save the page.\x3Cbr\x3E\x3Cbr\x3E\n# Part of the include link you just created will be red, indicating that the image doesn\'t exist yet.\x3Cbr\x3E\x3Cbr\x3E\n# Click on the red part of the link to launch the embedding interface.\x3Cbr\x3E\x3Cbr\x3E\n# Browse to the image you\'d like to embed.\x3Cbr\x3E\x3Cbr\x3E\n# Click the *Embed* button. You\'ll be taken to the *Image::NAME* page with your image embedded in it.\x3Cbr\x3E\x3Cbr\x3E\n\n== Use an embedded image\nOnce an image is embedded, you can use the same include link you used to add the embedded image to include the image anywhere in the wiki. \n# Create a new page or open an existing page in the editor by clicking the *Edit* icon in the navigation bar.\x3Cbr\x3E\x3Cbr\x3E\n# Create an include link for an already embedded image. There are two ways you can do this:\x3Cbr\x3E\x3Cbr\x3E\n## Use this code wherever you like in a page:\x3Cpre\x3E[\\\n[In\\\nclude::Image::N\\\nAME]\\\n]\x3C/pre\x3E\n## Or click the editor\'s *Image* button and type in the name of the image.\x3Cbr\x3E\x3Cbr\x3E\n# Click the *Save* icon in the navigation bar to save the page. The image should now be where the include link was.\n\n== Use a data:URI image\n\nIt is always better and advisable to embed an image instead of using a data:URI image, mostly because embedded images can be reused inside the wiki and do not garble the edit window when editing the page.\n\nHowever if you want to use anyway a data:URI image you can do it by directly adding the XHTML snippet:\n\n{{{\x3Cimg src="data:image/EXTENSION;ENCODING,ENCODED_IMAGE" /\x3E}}}\n\n\x3Cdiv class="woas_help_text"\x3EExample:\x3C/div\x3E\x3Cbr\x3E{{{\n\x3Cimg src="data:image/png;base64,R0lGODlhBgAFAIABAAAAAP///yH5BAEAAAEALAAAAAAGAAUAAAIJDGJ4ic1qgAwFADs=" /\x3E\n}}}\n\x3Cdiv class="woas_helptext"\x3EResult:\x3C/div\x3E\x3Cimg src="data:image/png;base64,R0lGODlhBgAFAIABAAAAAP///yH5BAEAAAEALAAAAAAGAAUAAAIJDGJ4ic1qgAwFADs=" /\x3E\n\nA famous online service to create such URIs is [[http://software.hixie.ch/utilities/cgi/data/data|The data: URI kitchen]]. The recommended encoding to be used in WoaS is base64.\n\n== Create a data:URI image button\n\x3Cdiv class="woas_helptext"\x3EUse this code in a page, replacing YOUR_MESSAGE with the message to be displayed and DATA_URI_GOES_HERE with the data:URI of your image:\x3C/div\x3E\x3Cbr\x3E{{{\x3Cinput type="image" onclick=\'alert("YOUR_MESSAGE")\' src="DATA_URI_GOES_HERE"\x3E}}}\n\n* \x3Cdiv class="woas_helptext"\x3EUse this example code:\x3C/div\x3E\x3Cbr\x3E{{{\n\x3Cinput type="image" onclick=\'alert("Hello world!")\' src="data:image/png;base64,R0lGODlhBgAFAIABAAAAAP///yH5BAEAAAEALAAAAAAGAAUAAAIJDGJ4ic1qgAwFADs=" /\x3E\n}}}\x3Cbr\x3E\x3Cdiv class="woas_helptext"\x3ETo create this:\x3C/div\x3E\x3Cinput type="image" onclick=\'alert("Hello world!")\' src="data:image/png;base64,R0lGODlhBgAFAIABAAAAAP///yH5BAEAAAEALAAAAAAGAAUAAAIJDGJ4ic1qgAwFADs=" /\x3E\n',
'= Importing full WoaS\nA WoaS .html file from an older version or a current version can always be imported into your WoaS.\n\n[[Include::WoaS::Template::Info|To import WoaS \x3C 0.9.6B you will need a WoaS 0.11.x release]]\n\n== Import wiki\nAn older Wiki on a Stick can be imported into the current wiki by using the [[Special::Import]] page\n# Access [[Special::Import]] directly or either from [[Special::Advanced#Maintenance]]\n# Click the *Browse* button and select the wiki you\'d like to import\n# Choose the settings you\'d like to use.\x3Cbr\x3E\x3Cbr\x3E[[Include::WoaS::Template::Info|If you are importing WoaS \x3C 0.12.0 it is much better to import CSS manually and not automatically because CSS definitions have changed]]\x3Cbr\x3E\n# Click the *Import* button.\x3Cbr\x3E\x3Cbr\x3E\n# A warning will pop up letting you know that this will overwrite pages with the same title and asking if you\'re sure.\x3Cbr\x3E\x3Cbr\x3E\n# Click the *OK* button if you\'re sure. Otherwise, click the *Cancel* button.\n\n== Importing WSIF\nThis method is used to import pages that have been exported using the WSIF format (see the [[WoaS::Help::WSIF]] page for more information).\n\n# Access [[Special::ImportWSIF]] directly or either from [[Special::Advanced#Maintenance]]\n# Click the *Browse* button and select the .wsif file to import\n# Choose the options to use\n# Click the *Import WSIF* button\n',
'= Exporting into XHTML static pages\nYour WoaS pages can be exported into web-compatible XHTML, CSS and eventually attachment files/images; generated pages do not use JavaScript for rendering and can be efficiently cached by webservers.\n\n# Access [[Special::Export]] directly or either from [[Special::Advanced#Maintenance]]\n# Modify the export directories if necessary\n# Choose the settings for the export.\x3Cbr\x3E\x3Cbr\x3E[[Include::WoaS::Template::Info|Note that the "Run before exporting but do not export the javascript" option means that JavaScript is run and then the XHTML is exported, but the JavaScript source code itself is not. This is useful if you have JavaScript that does custom parsing.]]\x3Cbr\x3E\n# Click the *Export XHTML* button. A confirmation will pop up\n\n== Exporting into WSIF\n# Access [[Special::ExportWSIF]] directly or iether from [[Special::Advanced#Maintenance]]\n# Modify the export directory if necessary\n# Choose the settings for the export.\x3Cbr\x3E\x3Cbr\x3E[[Include::WoaS::Template::Info|Note that you can export into a single WSIF file or multiple WSIF files. Embedded files are exported as individual files unless you choose not to export them as external file blobs, in which case they\'ll be embedded in the WSIF file(s).]]\x3Cbr\x3E\n# Click the *Export WSIF* button. A confirmation will pop up, telling you how many pages were exported\n\nThe WSIF file(s) in the export directory can now be copied, shared, or stored for backup purposes. See also [[WoaS::Help::WSIF]].\n',
'= Transclusion\nTransclusion is a special way of [[WoaS::Help::Include wiki page|including a wiki page]] in another wiki page. Instead of just displaying the contents of the included page as is, you can change how the content is displayed depending on how you write the link.\n\nThis method requires that you create a page to be used as a template. This page must contain variables represented by %1, %2, %3, and so on.\n\nYou can then create a transclusion link or links to that page by creating a normal include link with the values for the variables added after the page name. Each value must be preceded by a pipe ({{{|value}}}). The values you define in the link will be used from left to right to replace the numbered variables on the page you\'re including.\n\n\x3Cdiv class="helptext"\x3EUse this code, replacing PAGENAME with the name of the page to be included, and VALUE1 and VALUE2 with the values you\'d like the two variables to have when the included page is displayed:\x3C/div\x3E\n\x3Cdiv class="helpcode"\x3E[\x3C!-- --\x3E[Include::PAGENAME|VALUE1|VALUE2|VALUE3]\x3C!-- --\x3E]\x3C/div\x3E\n== Examples\nThese examples use the [[WoaS::Template::Example::Transclusion]] page which contains two variables. The contents of that page are:\n\n{{{\n\x3Cbig style="color:lime"\x3E%1 had a little %2\x3C/big\x3E\n}}}\n\n\x3Cdiv class="helptext"\x3EUse this code:\x3C/div\x3E\n\x3Cdiv class="helpcode"\x3E[\x3C!-- --\x3E[Include::WoaS::Template::Example::Transclusion|Mary|lamb]\x3C!-- --\x3E]\x3C/div\x3E\n\x3Cdiv class="helptext"\x3ETo create this:\x3C/div\x3E\n[[Include::WoaS::Template::Example::Transclusion|Mary|lamb]]\n\n\x3Cdiv class="helptext"\x3EOr use this code:\x3C/div\x3E\n\x3Cdiv class="helpcode"\x3E[\x3C!-- --\x3E[Include::WoaS::Template::Example::Transclusion|Fred|snack]\x3C!-- --\x3E]\x3C/div\x3E\n\x3Cdiv class="helptext"\x3ETo create this:\x3C/div\x3E\n[[Include::WoaS::Template::Example::Transclusion|Fred|snack]]\n\n\x3Cdiv class="helptext"\x3EOr use this code:\x3C/div\x3E\n\x3Cdiv class="helpcode"\x3E[\x3C!-- --\x3E[Include::WoaS::Template::Example::Transclusion|Bob|Jane|Jack]\x3C!-- --\x3E]\x3C/div\x3E\n\x3Cdiv class="helptext"\x3ETo create this:\x3C/div\x3E\n[[Include::WoaS::Template::Example::Transclusion|Bob|Jane|Jack]]\n== Wiki and HTML code in transclusion\nSome wiki code and HTML tags are accepted in the values.\n\n\x3Cdiv class="helptext"\x3EUse this code:\x3C/div\x3E\n\x3Cdiv class="helpcode"\x3E[\x3C!-- --\x3E[Include::WoaS::Template::Example::Transclusion|&#042;Mary&#042;|&#042;lamb&#042;.&lt;br&gt;&lt;br&gt;Its &#047;fleece&#047; was white as &#095;snow&#095;.]\x3C!-- --\x3E]\x3C/div\x3E\n\x3Cdiv class="helptext"\x3ETo create this:\x3C/div\x3E\n[[Include::WoaS::Template::Example::Transclusion|*Mary*|*lamb*.\x3Cbr\x3E\x3Cbr\x3EIts /fleece/ was white as _snow_.]]\n\n== Infinite recursion\nInfinite recursion happens when you create an include link to the current page or to another page that that has an include link to the current page. The included page will be repeated multiple times followed by the include link itself being displayed in bold red text surrounded by infinity symbols like this:\n\n\x3Cfont color="red"\x3E*{{{\u221e [[Include::PAGENAME]] \u221e}}}*\x3C/font\x3E\n\nYou should always prevent infinite recursions from happening.',
'[[Include::WoaS::Help::WSIF]]\n',
'= Importing WSIF files\nThe [[Special::ImportWSIF]] page allows to import a [[WoaS::Help::WSIF|WSIF]] file which contains one or more wiki pages; wiki pages imported into the *WoaS::* namespace will offer special features.\n== Existing pages\nAvailabe options for imported pages whose title collides with existing page titles:\n* *Erase all user pages before import process*\n* *Ignore* - do not import colliding pages\n* *Overwrite* - replace existing page with imported page\n* *Ask before overwrite* - ask for each colliding page before replacing\n== Security\nGeneral JavaScript security options to prevent possibly malicious code from being imported.\n* *Comment JavaScript tags*: convert all {{{\x3Cscript ...\x3E...\x3C/script\x3E}}} tags to {{{\x3Cdisabled_script ...\x3E...\x3C/disabled_script\x3E}}}\n* *Comment macro definition/call*: comment all macro definitions/calls {{{\x3C\x3C\x3C...\x3E\x3E\x3E}}} so that they are not parsed/called\n* *Import pages of the WoaS:: namespace*: allow importing hotkeys, plugins, aliases and custom CSS\n\n*Note:* if you have enabled some JavaScript code by mistake you should enable *safe mode* in [[Special::Options]]',
'= Hotkeys and access keys\n\nYou can define key bindings for hotkeys and access keys by editing the [[WoaS::Hotkeys]] page.\n\nExample:\n\n{{{$SAVE s\n$EDIT e\n}}}\n\nThe *$SAVE* and *$EDIT* definitions identify a WoaS action, while the /\'s\'/ and /\'e\'/ characters identify the associated hotkey.\n\nSee also [[http://en.wikipedia.org/wiki/Access_keys]].\n\n== Custom hotkeys\n\nYou can create a custom hotkey associated to a JavaScript function to be called when that hotkey is activated.\n\nExample:\n\n{{{$CUSTOM(myfunction)     k}}}\n\nThe /myfunction/ JavaScript function will be called (without arguments) when the /\'k\'/ hotkey is activated.',
'= Plugins\n\nAll pages of the *WoaS::Plugins::* namespace are WoaS plugins; plugins are executed when loading WoaS and remain active during WoaS usage.\n\nYou can share WoaS plugins as WSIF files by exporting them.\n\n== External plugins\n\nA WoaS plugin is just JavaScript code, unless you create a plugin whose name starts with \'@\'. Such plugins are made up of external file references and a specific notation to force browser into a specific inclusion mode.\n\nExample:\n{{{path/to/mylib1.js = +*\npath/to/mylib2.js = @*}}}\n\nThe first resource, {{{path/to/mylib1.js}}}, will be included as an inline JavaScript (e.g. like source code inside a {{{\x3Cscript /\x3E}}} tag), while the second, {{{path/to/mylib2.js}}}, will be included as external ({{{\x3Cscript /\x3E}}} tag with proper \'src\' attribute).\n\n=== Inclusion operators\n\nThere are two inclusion operators which are valid for resource definitions:\n\n* *+* - which leads to inline inclusion\n* *@* - which leads to external inclusion\n\nThe word which directly follows such inclusion operator can be any of the following supported tokens:\n\n* *{{{*}}}* catch-all, which means any browser/engine\n* *ie* Internet Explorer\n* *firefox* Mozilla Firefox (-)\n* *opera* Opera Internet Explorer\n* *safari* Apple Safari (-)\n* *chrome* Google Chrome\n* *gecko* Gecko engine (Mozilla)\n* *webkit* WebKit engine (Safari/Chrome)\n* *presto* Presto engine (Opera)\n* *trident* Trident engine (Internet Explorer)\n\n*Note: * tokens marked with (-) do not support version matching\n\nNow let\'s consider a more complex example:\n\n{{{path/to/mylib1.js = +* @gecko(1.9+)\npath/to/mylib2.js = @*}}}\n\n{{{path/to/mylib1.js}}} will be loaded as inline for all browsers except those which have a Gecko engine with version 1.9 or above (i.e. Firefox 3.6 and above).',
'= Aliases\n\nAliases are replaceable shortcuts used in wiki links.\n\n*Example:*\n\n{{{[[$JD::Bio]]}}}\n\nWhere /$JD/ is defined as:\n{{{$JD    John Doe}}}\n\nAliases can turn out to be particularly useful to shorten typing of long page names.\n\n== Add an alias\n\nIn order to add an alias you should edit the [[WoaS::Aliases]] reserved page; you should add a line to the page, like the below example:\n\n{{{$JD    John Doe}}}\n\n*$JD* is the alias, which will be replaced when found in wiki page title references, and /"John Doe"/ is the text which will replace *$JD*. You can specify any part of page titles in aliases, like namespaces or page prefixes/suffixes.',
'= Let\'s Build a Roguelike\n\nRoguelike games are typically dungeon crawlers where the Hero must explore a randomly generated dungeon, battle monsters, recover the \u201cItem of Interest\u201d and return to the surface alive. The very first roguelike, Rogue, created around 1980 by Michael Toy and Glenn Wichman, is the game that started the genre and from where the term \u201croguelike\u201d originates.  Rogue became a favorite game on the Unix computer system due to the use of characters to display the dungeons (rather than just blocks of text) and each dungeon was randomly generated so each time the game was played, the player could explore a different dungeon.\n\nIt is amazing really that roguelikes continue to thrive today in our graphics intensive gaming environment. The reasons that roguelikes are popular today are the same reasons they were popular in 1980: procedurally generated content, simple learning curve, and challenging game play. While many games feature advanced graphics, the ASCII character set is still the medium of choice for displaying the dungeon and monsters in roguelikes. Adherents to the ASCII model argue that the stark character-based display frees the imagination to envision the dungeon and monsters however the player desires. It also frees up the programmer to concentrate on the game mechanics, creating depth to the game experience rather than concentrating on getting the latest video card to work with the game. \n\nThere are many variations on the roguelike theme that exist today, so it is hard to come up with a definitive definition. However, most roguelikes share the following features:\n\n* RPG system\n* Procedurally generated dungeons\n* Monsters\n* Randomly placed items\n* Single goal to reach; usually an item at the lowest level of the dungeon\n* Returning to starting point after finding goal item\n* Permanent death\n\nPermanent death is distinctive to roguelike games. In most games, if the player character dies, you can simply reload the game and try again. In most roguelikes, once you die that is it. The game is over for that character. You can save and restore the game while the character is alive, but once the character dies, that save game is gone for good. Permanent death, or permadeath as it is called, has been a feature of roguelikes since the beginning, and is a topic of debate, even today. I have my own feeling on the topic which I discuss in the Notes section of the book.\n\nSooner or later the programmer-player will want to create their own roguelike so that they can have exactly what they want in the game. Roguelikes are really a good programmer\'s game, due to the procedurally generated game content. After working six months on a game, the programmer knows it all by heart and there are very few, if any, surprises. The game never gets played by the programmer, other than testing, because he knows every nook and cranny of the game. You don\'t have that in a roguelike.\n\nDue to the generated content, each time you play a roguelike, it is a different game. And just because you programmed the game doesn\'t mean that you will master it. Roguelikes are one of the few game types that can be a joy to play even after you have spent six months (or six years) working on it. And there is always something to tweak, something new to try out in the code, something else to add to the inventory. It is a game genre that has no limit, no horizon. If there is a limit, it is only your imagination.\n\nNot only is building a roguelike fun, it is a good project to hone your programming skills. The simple-to-play game can be quite sophisticated underneath, using a wide range of algorithms and techniques, from dynamic content generation to advanced AI. The roguelike genre is one game genre that is always evolving as new techniques are developed and implemented, all toward creating that perfect game.\n',
'= About This Wiki Book\n\nAs the title implies, this is a book about creating a roguelike game. It provides a step-by-step approach to building a roguelike game from the ground up. You won\'t build the perfect roguelike using this book, but you will have your first roguelike game completed and will have a good understanding of the basic elements of the genre. This books provides the stepping stones that you can use to advance your programming abilities to eventually create that perfect game.\n\nThis book uses the FreeBasic compiler as the language for creating the game. FreeBasic is a free, multi-platform Basic compiler that produces native code executables. The Basic language is easy to learn and use, and FreeBasic is powerful enough to implement anything we want in the game. FreeBasic can be found at [[http://www.freebasic.net | http://www.freebasic.net]].\n',
'= 1: The Title Screen\n\x3Cdiv align="center" /\x3E\x3Cimg src=\'images/title.png\'/\x3E\x3C/div\x3E\n\nBuilding a roguelike takes some planning, but there is one thing we can do right from the start: create the title screen. This gets the project going, something that can be hard to do at times. It is easy to get caught up in the "How shall I start this?" syndrome, but by coding the title screen, we get the project rolling. It is quite simple to do, allows us to set up some files for our project and gets us in the coding mindset.\n\nFor a title screen we need obviously need a title. Since we are building a classic roguelike, all of the action takes place in a dungeon so we can start there. /Dungeon of/ what though? We need it to sound ominous and dangerous, so how about /Doom/? /Dungeon of Doom/ has a nice ring to it, so this will be the title.\n\nThis will be an ASCII-based roguelike to keep things simple and to concentrate on the algorithms rather than the graphics, but that doesn\'t mean we have to settle for a plain text screen. You may not remember or have even seen the old Bulletin Boards, or BBSes, as they were called, that existed in the pre-Internet days of the modem, but many BBSes had spectacular ANSI graphics. We can use a similar technique to spice up our ASCII game by creating an ASCII graphic for the title screen. The process is actually quite simple.\n\nThe first thing we need to decide on is the type of screen we are going to use. We could just use a console screen, but then we are limiting ourselves to 16 colors and there is really no reason to do this. For this game, we are going to use a graphic screen in 32bit mode so we can use the full RGB color palette. A screen resolution of 640x480 will be sufficient for what we need, and in 32bit mode we can use RGB colors for the display which will give us a great deal of flexibility in our presentation. In 640x480 mode we can set the text mode to 80 columns by 60 rows which will give us quite a bit of screen real estate and if we use the 8x8 font, we will have a compact but readable display.\n\nAs you can see from the image above, the title screen looks much like a bitmap. It is in fact a converted bitmap. I created the bitmap, title.bmp (which you find in the images folder) in a paint program and then converted the colors to an array which are displayed on the screen. The array holds the color information of the bitmap and the "graphic" was displayed using ASCII character 219, the solid block. Simple and quite effective. Here is the process:\n\n* Create an empty bitmap 80 x 60 to match the text screen.\n* Create the background using a modified brick image.\n* Add the text using the graphic text tool.\n* Add the monster graphic again using a tile from the RL Tile set.\n* Save as a 24-bit bitmap.\n\nOnce the bitmap is finished we need to convert it to a color array.\n\n* Load the bitmap into a graphic screen.\n* Iterate through the bitmap placing the colors into an UInteger array. I just used the Point method here since speed isn\'t really an issue and it is simple to implement.\n* Save the color array to a file.\n\nThe output of the file looks like this:\n\n\n\x3Cdiv class="code"\x3E\n/title.bi/\n{{{\n\'Useage: (uinteger) pixel = title(x + y * titlew)\n#Define titlew 80\n#Define titleh 60\nDim Shared title(4800) As UInteger = { _\n&hFF6D5452,&hFF201917,&hFF211C19,&hFF443831,&hFF544438,&hFF5F534B,&hFF5A4F49,&hFF5B4E47, _\n&hFF544841,&hFF554442,&hFF54433E,&hFF564945,&hFF574A44,&hFF68534A,&hFF775955,&hFF765A59, _\n&hFF796257,&hFF7C6855,&hFF7E6556,&hFF7A5B5E,&hFF795D5F,&hFF75615B,&hFF6F5B56,&hFF6F5B58, _\n&hFF776167,&hFF7A5D61,&hFF745750,&hFF6F534A,&hFF6A5347,&hFF5A4C35,&hFF5B4E3A,&hFF675447, _\n.\n.\n.\n&hFFC00000,&hFF200000,&hFF200000,&hFF800000,&hFF400000,&hFF600000,&hFF300000,&hFF403000, _\n&hFFA07800,&hFF806000,&hFFC09000,&hFFA07800,&hFFA07800,&hFF806000,&hFF302400,&hFF604800, _\n&hFF800000,&hFFA00000,&hFFFF0000,&hFF600000,&hFFFF0000,&hFFC00000,&hFF600000,&hFFE00000, _\n&hFFE00000,&hFF600000,&hFFC00000,&hFF600000,&hFF300000,&hFF600000,&hFFFF0000,&hFFE00000}\n}}}\n\x3C/div\x3E\n\nAs you can see from the code, the output is in hexadecimal format to save space in the file.\n\nTo display this we need to create a subroutine that will iterate through the array and draw the ASCII block character on the screen in the appropriate color.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Displays the game title screen.\nSub DisplayTitle\n   Dim As String txt\n   Dim As Integer tx, ty\n   \n   \'Set up the copyright notice.\n   txt = "Copyright (C) 2011, by Richard D. Clark"\n   tx = (txcols / 2) - (Len(txt) / 2)\n   ty = txrows - 2\n   \n   \'Lock the screen while we update it.\n   ScreenLock\n   Cls\n   \'Iterate through the array, drawing the block character in the array color.\n   For x As Integer = 0 To titlew - 1\n      For y As Integer = 0 To titleh - 1\n         \'Get the color out of the array using the formula.\n         Dim clr As UInteger = title(x + y * titlew)\n         \'Use draw string as it is faster and we don\'t need to worry about locate statements.\n         Draw String (x * charw, y * charh), Chr(219), clr \n      Next\n   Next\n   \'Draw the copyright notice.\n   Draw String (x * charw, y * charh), acBlock, clr\n   ScreenUnLock\n   Sleep\n   \'Clear the key buffer.\n   Do:Sleep 1:Loop While InKey \x3C\x3E ""\nEnd Sub\n}}}\n\x3C/div\x3E\nThe first thing we do here is to set up the copyright notice. The code {{{tx = (txcols / 2) - (Len(txt) / 2)}}} centers the text string on the screen and {{{ty = txrows - 2}}} places the text two rows above the bottom of the screen. The two symbolic constants /txcols/ and /txrows/ are /#Define/s that have the number of text rows and columns.\n\nThe screen is then locked for the update using /ScreenLock/. This makes the update process faster and gets rid of any flicker on slower machines. We could access the screen buffer directly, but since we are working with a small number of columns and rows, it really wouldn\'t do much for us much in terms of speed and would just complicate the code.\n\nThe two For-Next statements iterate through the color array, /title/ and then use the /Draw String/ statement to draw ASCII character 219, defined in acBlock, to the screen. By using Draw String we don\'t need the Color-Locate-Print statements as these are combined in Draw String. Draw String also uses pixel coordinates for the /(x, y)/ parameters so we have the capability to place text anywhere on the screen, not just in character columns and rows. However, in this case, we do want to use character columns and rows, so we offset the x and y coordinates by the width and height of the font, which are defined in /charw/ and /charh/.\n\nThe code {{{Dim clr As UInteger = title(x + y * titlew)}}} may look a bit odd at first glance. As you can see in the code snippet from title.bi, the color array is a one-dimensional array. It represents a two-dimensional array though, the number of text columns and rows. To convert from a one-dimensional array to two-dimensional coordinates we use the formula /title(x + y * titlew)/ where the x and y represent the array indices and titlew represents the width of the text screen. Obviously in order for this to work, the array must be loaded in the same fashion. For example, you could use the following code to load the array:\n{{{\ncolorvals(x + y * imagewidth) = point(x, y)\n}}}\nYou can also use the same formula to load higher dimension arrays sing the extra indexes.\n\nThe DisplayTitle subroutine closes by unlocking the screen with /ScreenUnlock/ which updates the display. We then wait for the player to continue by /Sleep/ing until a key is pressed. The last bit of code {{{Do:Sleep 1:Loop While InKey \x3C\x3E ""}}} just clears the key buffer from the user key press. Sleep doesn\'t clear the key buffer, and whatever key the user pressed will still be in the key buffer. When we display the main menu, we don\'t want to pick up that junk key, so we clear the buffer now to give us a clean key buffer when the menu is displayed.\n\nEven though the program only displays a title screen, we already have a start on our project files. The file list right now is:\n* dod.bas: This is the main program code file.\n* defs.bi: Contains our data definitions.\n* title.bi: Contains our title screen color data.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n/\'****************************************************************************\n*\n* Name: dod.bas\n*\n* Synopsis: Dungeon of Doom\n*\n* Description: A simple roguelike as detailed in the wikibook, Let\'s Build a \n*              Roguelike. This is the main program file.\n*\n* Copyright 2010, Richard D. Clark\n*\n*                          The Wide Open License (WOL)\n*\n* Permission to use, copy, modify, distribute and sell this software and its\n* documentation for any purpose is hereby granted without fee, provided that\n* the above copyright notice and this license appear in all source copies. \n* THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY OF\n* ANY KIND. See http://www.dspguru.com/wol.htm for more information.\n*\n*****************************************************************************\'/\n#Include "title.bi"\n#Include "defs.bi"\n\n\'Displays the game title screen.\nSub DisplayTitle\n   Dim As String txt\n   Dim As Integer tx, ty\n   \n   \'Set up the copyright notice.\n   txt = "Copyright (C) 2010, by Richard D. Clark"\n   tx = (txcols / 2) - (Len(txt) / 2)\n   ty = txrows - 2\n   \n   \'Lock the screen while we update it.\n   ScreenLock\n   Cls\n   \'Iterate through the array, drawing the block character in the array color.\n   For x As Integer = 0 To titlew - 1\n      For y As Integer = 0 To titleh - 1\n         \'Get the color out of the array using the formula.\n         Dim clr As UInteger = title(x + y * titlew)\n         \'Use draw string as it is faster and we don\'t need to worry about locate statements.\n         Draw String (x * charw, y * charh), acBlock, clr \n      Next\n   Next\n   \'Draw the copyright notice.\n   Draw String (tx * charw, ty * charw), txt, fbYellow\n   ScreenUnLock\n   Sleep\n   \'Clear the key buffer.\n   Do:Sleep 1:Loop While InKey \x3C\x3E ""\nEnd Sub\n\n\n\'Using 640x480 32bit screen with 80x60 text.\nScreenRes 640, 480, 32\nWidth charw, charh\nWindowTitle "Dungeon of Doom"\n\n\'Draw the title screen.\nDisplayTitle\n}}}\n\x3C/div\x3E\nNot much is happening in dod.bas right now. We include the title and defs files using the /#Include/ directive. When the compiler sees the #Include directive, it will stop compiling the current file and load in the referecned file, and then resume compiling. It operates just as if we inserted the file contents into the source file where the #Include statement is located. This allows us to break the program into manageable chunks rather than one huge file. To compiler, it is just one huge file, but to us, it is in different files so it is easier to work on the code.\n\nWe then define our DisplayTitle subroutine and set up the graphic screen using /ScreenRes/ which sets the screen to 640 by 480 with a color depth of 32. The text display is set up using the /Width/ command and we set the title of the window using the /WindowTitle/ statement. We then display the title screen, wait for a key press and exit the program.\n\n\x3Cdiv class="code"\x3E\n/defs.bi/\n{{{\n/\'****************************************************************************\n*\n* Name: defs.bi\n*\n* Synopsis: Data definitions for DOD.\n*\n* Description: This file contains the various data definitions used in the game.  \n*\n* Copyright 2010, Richard D. Clark\n*\n*                          The Wide Open License (WOL)\n*\n* Permission to use, copy, modify, distribute and sell this software and its\n* documentation for any purpose is hereby granted without fee, provided that\n* the above copyright notice and this license appear in all source copies. \n* THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY OF\n* ANY KIND. See http://www.dspguru.com/wol.htm for more information.\n*\n*****************************************************************************\'/\n\n\'Using 8x8 characters.\n#Define charw 8\n#Define charh 8\n\'Text mode 80x60\n#Define txcols 80\n#Define txrows 60\n\'Colors\nConst fbYellow = RGB(255, 255, 0)\n\'Ascii Chars\nConst acBlock = Chr(219)\n}}}\n\x3C/div\x3E\nThe defs file doesn\'t have a lot in it yet either, just some basic defines and constants that are needed to display the title screen. Notice we are using both /#Defines/ and /Const/ statements. The #Defines are used for numeric constants and for macros, while the Consts are used for calculated values. We don\'t want to calculate the RGB colors for fbYellow each time we need to use the color yellow in the program. Why not use just use Const everywhere? Because Const does a look-up into the symbol table for the value. For simple numeric values, we can place the actual value via a #Define in the code which doesn\'t require a look-up. The preprocessor will replace each instance of /charw/ with 8 in the code, so it behaves just like we had typed 8. Rather than typing 8 though, we use a #Define in case the value needs to be changed at some point in the development or maintenance process. It would be very difficult to remember every place we put 8 in for the font width in the program.\n\nAll of the source code for the program as it stands right now can be found in the chap01 folder.\n\nThis is a good time to give the program an icon, so there are two more files in the package, dod.ico which is located in the /images/ folder and dod.rc which tells the FB compiler where to get the program icon.\n\n\x3Cdiv class="code"\x3E\n/dod.rc/\n{{{\nFB_PROGRAM_ICON ICON images/dod.ico\n}}}\n\x3C/div\x3E\nThe rc file just contains the one line, the program icon location. In order for the compiler to set the program icon, you need to pass the rc file on the command line. If you wish to compile this program yourself, you will need to modify dod.rc to point to the program icon location on your system. You can modify dod.rc with a simple text editor.\n\nTo compile this just use:\n{{{\nfbc -exx dod.bas dod.rc\n}}}\nAssuming the FreeBasic compiler, fbc, is in the system path. If it isn\'t, you will need to qualify the compiler location when you call it.\n\nNotice that the compile is using the -exx parameter. This is the extended error checking mode and will notify us anytime we exceed array bounds or use a bad pointer. We will use -exx when we compile the program during the development process so we can catch at least some of the preventable run-time errors that will occur. We also will compile this so that we have the console window available during development. If the program hangs up for some reason, it gives us an easy way to shut it down.\n\nOnce the program is ready for distribution, we will compile without the console window and without the extended error checking, as this will slow down the program. We can do that with the following:\n{{{\nfbc dod.bas dod.rc -s gui\n}}}\nThe /-s gui/ indicates that we want to compile the program in GUI mode, that is, without the console.\n\nWe have completed our initial objective, get the project up and started. We have a long way to go, but at least we have some running code and something more than just a good idea.\n',
'= License\n/Let\'s Build a Roguelike/ is copyright 2011, Richard D. Clark\n/Dungeon of Doom/ software is copyright 2011, Richard D. Clark\n\nPermission is granted to copy and distribute /Let\'s Build a Roguelike/ and /Dungeon of Doom/ software as long as this copyright notice is left intact in both the text of this book and the source code.\n\n/Dungeon of Doom/ is licensed under the Wide Open License.\n\n                          The Wide Open License (WOL)\n\nPermission to use, copy, modify, distribute and sell this software and its documentation for any purpose is hereby granted without fee, provided that the above copyright notice and this license appear in all source copies. THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY OF ANY KIND. See [[http://www.dspguru.com/wol.htm | http://www.dspguru.com/wol.htm]] for more information.\n',
'= 2: Improving the Code\nLooking at the code in the previous chapter we can see that there can be some improvements made in the code. Specifically, there are some things that we may need to do in more than just the DisplayTitle subroutine. For example, clearing the key buffer and centering a string on the screen are functions we will probably use more than once, so it makes sense to add a new file to the project and add in these functions as utility functions. We will call the new file, utils.bi.\n\n\x3Cdiv class="code"\x3E\n/utils.bi/\n{{{\n/\'****************************************************************************\n*\n* Name: utils.bi\n*\n* Synopsis: Utility routines for DOD.\n*\n* Description: This file contains misc utility routines used in the program.  \n*\n* Copyright 2010, Richard D. Clark\n*\n*                          The Wide Open License (WOL)\n*\n* Permission to use, copy, modify, distribute and sell this software and its\n* documentation for any purpose is hereby granted without fee, provided that\n* the above copyright notice and this license appear in all source copies. \n* THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY OF\n* ANY KIND. See http://www.dspguru.com/wol.htm for more information.\n*\n*****************************************************************************\'/\n\n\'Clears key board buffer.\nSub ClearKeys\n   Do:Sleep 1:Loop While Inkey \x3C\x3E ""\nEnd Sub\n}}}\n\x3C/div\x3E\nWe have added just one routine to our utility file, ClearKeys. ClearKeys is just the code we used in the DisplayTitle subroutine transplanted to its own subroutine. We will end up using this quite often so it makes a good candidate for a utility routine. It may seem silly to create a file for just one routine, but we will be adding many more before the program is finished.\n\nThe other function that we may need more than once is a routine that will return a centered X coordinate for a string. Rather than creating a function for this and incurring a function call, we can do this inline using a macro. The CenterX macro is defined in our defs.bi file.\n{{{\n#Define CenterX(ct) ((txcols / 2) - (Len(ct) / 2))\n}}}\nAgain, we simply transplanted the code we had already written into the macro. We will use this new code in the DisplayTitle subroutine.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Displays the game title screen.\nSub DisplayTitle\n   Dim As String txt\n   Dim As Integer tx, ty\n   \n   \'Set up the copyright notice.\n   txt = "Copyright (C) 2010, by Richard D. Clark"\n   tx = CenterX(txt)\n   ty = txrows - 2\n   \'Lock the screen while we update it.\n   ScreenLock\n   Cls\n   \'Iterate through the array, drawing the block character in the array color.\n   For x As Integer = 0 To titlew - 1\n      For y As Integer = 0 To titleh - 1\n         \'Get the color out of the array using the formula.\n         Dim clr As UInteger = title(x + y * titlew)\n         \'Use draw string as it is faster and we don\'t need to worry about locate statements.\n         Draw String (x * charw, y * charh), acBlock, clr\n      Next\n   Next\n   \'Draw the copyright notice.\n   Draw String (tx * charw, ty * charh), txt, fbYellow\n   ScreenUnlock\n   Sleep\n   \'Clear the key buffer.\n   ClearKeys\nEnd Sub\n}}}\n\x3C/div\x3E\nThe new sections are {{{tx = CenterX(txt)}}} and the end of the subroutine after the /Sleep/ statement. If you compile and test the new code you will find it works as expected.\n\nThese changes are quite minor but we need to look at opportunities such as these to generalize and optimize as we go along. Over the course of the development cycle, these small changes will add up to big changes for the project as a whole. The time spent now will be time saved since we won\'t have to go back into the code and rewrite something we should have generalized or optimized when we wrote the initial code. It will also pay big dividends in the maintenance cycle of the project when the program is done and we need to debug or add new features to the program. \n\nHow do we know when we should generalize? If you end up using a routine more than once, it makes sense to place the code into its own subroutine or function. This gives us a single place to look for problems, a single place to go to if we need to make changes.\n\nOptimizing code isn\'t quite so simple. It is more of a performance issue and sometimes we really won\'t see the bottlenecks until several things are happening in the program at once. In the case of our macro CenterX, the code is simple and a good candidate for inline code, hence the macro. The macro saves us a function call, a minor savings in time at this point, but it may help us down the road; or it may not really matter. It is hard to tell at this point in the program. However, inline code is generally faster than calling a function, especially simple code such as this. If the code was more complex, then a macro wouldn\'t be appropriate and we would just have to live with a function call.\n\nAs we go along in the coding process, we will use this strategy of writing code and keeping an eye out for situations where we can generalize and optimize. The goal is to create a solid game that will be easy to maintain after the initial coding process. Programs are never completely finished; there is always something to tweak, something to add. A little thought now will save us time and heartache down the road.\n\n',
'= 3: The Main Menu\n\x3Cdiv align="center" /\x3E\x3Cimg src=\'images/mainmenu.png\' /\x3E\x3C/div\x3E\n\nThe next thing we can add to the program is the main menu. Like the title screen we don\'t have to settle for plain old ASCII. We can be creative and spice up the presentation a bit. As you can see in the above image, we have created another bitmap image and converted it to a color map using the same process as the title screen. It makes a nice presentation and adds that little extra "something" to the program.\n\nWe display the background as we did in the title screen, just iterating through the color map and drawing ASCII character 219 to the screen. However, since we are doing the same thing twice now, we can invoke our rule of generalization and make a background drawing subroutine that will draw any color map to the screen. We have added a new routine to our utils.bi file called /DrawBackgound/.\n\n\x3Cdiv class="code"\x3E\n/utils.bi/\n{{{\n\'Draws a background image using passed color map.\nSub DrawBackground(cmap() As UInteger)\n   \'Iterate through the array, drawing the block character in the array color.\n   For x As Integer = 0 To txcols - 1\n      For y As Integer = 0 To txrows - 1\n         \'Get the color out of the array using the formula.\n         Dim clr As UInteger = cmap(x + y * txcols)\n         \'Use draw string as it is faster and we don\'t need to worry about locate statements.\n         Draw String (x * charw, y * charh), acBlock, clr\n      Next\n   Next\nEnd Sub\n}}}\n\x3C/div\x3E\nAs you can see it is the exact same code we had in the DisplayTitle subroutine, except we are passing the color map array as a parameter to this sub. Here it is in the DisplayTitle sub.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Displays the game title screen.\nSub DisplayTitle\n   Dim As String txt\n   Dim As Integer tx, ty\n   \n   \'Set up the copyright notice.\n   txt = "Copyright (C) 2010, by Richard D. Clark"\n   tx = CenterX(txt)\n   ty = txrows - 2\n   \'Lock the screen while we update it.\n   ScreenLock\n   \'Draw the background.\n   DrawBackground title()\n   \'Draw the copyright notice.\n   Draw String (tx * charw, ty * charh), txt, fbYellow\n   ScreenUnlock\n   Sleep\n   \'Clear the key buffer.\n   ClearKeys\nEnd Sub\n}}}\n\x3C/div\x3E\nThe changed code is {{{DrawBackground title()}}}. We will see this sub again when we examine the menu code.\n\nThere is one other slight change in the code here. The CLS statement has been removed. Since we are drawing the background here, we don\'t need to clear anything, so having CLS is just wasted code. It is easy to miss things like this when you are in the midst of creating the initial code. Going back into the code for a change such as this is a good opportunity to see if you have any useless code that you can trim. Now, we are ready to look at the menu code. We start by creating a new file /mmenu.bi/.\n\n\x3Cdiv class="code"\x3E\n/mmenu.bi/\n{{{\n/\'****************************************************************************\n*\n* Name: mmenu.bi\n*\n* Synopsis: Main menu file.\n*\n* Description: This is the main menu routines that display and return the menu\n*              selection to the main program.  \n*\n* Copyright 2010, Richard D. Clark\n*\n*                          The Wide Open License (WOL)\n*\n* Permission to use, copy, modify, distribute and sell this software and its\n* documentation for any purpose is hereby granted without fee, provided that\n* the above copyright notice and this license appear in all source copies. \n* THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY OF\n* ANY KIND. See http://www.dspguru.com/wol.htm for more information.\n*\n*****************************************************************************\'/\n\'Wrap this in a namespace since we only need this at the beginning of the program.\nNamespace mmenu\n\'Background color map.\n#Include "menuback.bi"\n\n\'These are the menu return values.\nEnum mmenuret\n   mNew\n   mLoad\n   mInstruction\n   mQuit\nEnd Enum\n\n\'Draws the menu to the screen.\nSub DrawMenu(m() As String, midx As Integer, mx As Integer, my As Integer)\n   Dim As Integer x = mx, y = my\n   \n   \'Iterate through the menu array and draw items to the screen.\n   For i As Integer = mNew To mQuit\n      If midx = i Then\n         Draw String (x * charw, y * charh), m(i), fbWhite\n      Else\n         Draw String (x * charw, y * charh), m(i), fbGray\n      EndIf\n      y += 2\n   Next\n   \nEnd Sub\n\n\'This draws the menu and returns the selected value.\nFunction MainMenu() As mmenuret\n   Dim As mmenuret idx = mNew\n   Dim menuitems(mNew To mQuit) As String\n   Dim As Integer mx, my, done = FALSE, tx, ty\n   Dim  As String mkey, mtitle \n   \n   \'Set the menu text.\n   menuitems(mNew) = "New Game    "\n   menuitems(mLoad) = "Load Game   "   \n   menuitems(mInstruction) = "Instructions"   \n   menuitems(mQuit) = "Quit        "\n   \'Set the menu items x, y\n   mx = CenterX(menuitems(3))\n   my = CenterY(UBound(menuitems) * 2)\n   ScreenLock   \n   \'Draw the menu background.\n   DrawBackground menuback()\n   \'Draw the title with drop shadow.\n   mtitle = "Dungeon of Doom v." & dodver\n   tx = CenterX(mtitle) * charw\n   ty = (10 * charh)\n   Draw String (tx + 1, ty + 1), mtitle, fbBlack\n   Draw String (tx, ty), mtitle, fbYellow\n   \'Draw the menu text.\n   DrawMenu menuitems(), idx, mx, my\n   ScreenUnLock\n   Do\n      \'Get the current key.\n      mkey = InKey\n      \'Did user press a key?\n      If mkey \x3C\x3E "" Then\n         \'If user presses escape or close button, then exit with quit id.\n         If (mkey = key_esc) Or (mkey = key_close) Then\n            idx = mQuit\n            done = TRUE\n         EndIf\n         \'User pressed up arrow.\n         If mkey = key_up Then\n            \'Decrement the menu index.\n            idx -= 1\n            \'Wrap around to bottom of menu.\n            If idx \x3C mNew Then idx = mQuit\n            \'Draw the menu.\n            ScreenLock\n            DrawMenu menuitems(), idx, mx, my\n            ScreenUnLock\n         EndIf\n         \'User pressed down arrow.\n         If mkey = key_dn Then\n            \'Increment the menu index.\n            idx += 1\n            \'Wrap around to top of menu.\n            If idx \x3E mQuit Then idx = mNew\n            \'Draw the menu.\n            ScreenLock\n            DrawMenu menuitems(), idx, mx, my\n            ScreenUnLock\n         EndIf\n         \'User pressed enter key.\n         If mkey = key_enter Then\n            \'Exit menu.\n            done = TRUE\n         EndIf\n      EndIf\n      Sleep 10\n   Loop Until done = TRUE\n   \'Clear any keys.\n   ClearKeys\n   Return idx\nEnd Function\n\nEnd Namespace\n}}}\n\x3C/div\x3E\nThe code is actually quite simple, but there are some new items here that we want to look at in detail. The first thing you will notice that the code is wrapped in a /Namespace/. The Namespace creates a scoped block of code that is isolated from the rest of the program. A Namespace has a name associated with it, in this case {{{mmenu}}} and we will use this name to access elements from within the Namespace. You will see how this is done in the main bas file when we get to it.\n\nThe reason we want to use a Namespace here, is that this code is only used once in the program, when the user first loads the program, and is never used again while the program is running. This one-time use means that we don\'t want anything here affecting anything else in the code. By using a Namespace, we have isolated this section of code, preventing any chance of conflicts in the rest of the code. We can "code and forget" this section of code, because we know it won\'t be a factor in the rest of the code.\n\nThe next thing we need to do is to define an /Enumeration/ that we will use for our menu ids.\n\n\x3Cdiv class="code"\x3E\n/mmenu.bi/\n{{{\n\'These are the menu return values.\nEnum mmenuret\n   mNew\n   mLoad\n   mInstruction\n   mQuit\nEnd Enum\n}}}\n\x3C/div\x3E\nAn Enumeration is a set of values that the compiler will create for you when a program is compiled. In our code, mNew would be assigned a value of 0, mLoad a value of 1, and so on. You can also set the start value yourself when you define the Enumeration, or even define all the values if you want. Enumerations are most useful though, when the values are in sequence, as you will see when we draw the menu.\n\nThis Enumeration is going to do several things for us in the code. It is going to supply the return values for the menu, it is going to indicate which menu item is active and it is going to help use define the scope of our menu. This is another aspect of optimization, one that is frequently overlooked. The more a section of code does, the less code we have to write. Less code means fewer things to break. It also means we have fewer things to look at when we do have problems, and less to update when we want to modify, add or fix the code. In our case it also makes the code much more readable as you will see next.\n\nThe next section of code draws the menu text on the screen.\n\n\x3Cdiv class="code"\x3E\n/mmenu.bi/\n{{{\n\'Draws the menu to the screen.\nSub DrawMenu(m() As String, midx As Integer, mx As Integer, my As Integer)\n   Dim As Integer x = mx, y = my\n   \n   \'Iterate through the menu array and draw items to the screen.\n   For i As Integer = mNew To mQuit\n      If midx = i Then\n         Draw String (x * charw, y * charh), m(i), fbWhite\n      Else\n         Draw String (x * charw, y * charh), m(i), fbGray\n      EndIf\n      y += 2\n   Next\n   \nEnd Sub\n}}}\n\x3C/div\x3E\n/DrawMenu/ draws the menu items on the screen which are passed in the m() array. The /midx/ parameter is the current active menu item. We check this when we draw the menu item strings to the screen. The currently active menu item will be drawn in the color white, while the other items will be drawn in the color gray. This is a visual cue that lets the user know what menu item is currently selected.\n\nThe /mx/ and /my/ parameters are the position of the first menu item, which in this case is mNew. The For-Next uses the Enumeration ids to print out each menu item. Notice that by using the Enumeration ids the code is much more readable than just using values like 0 to 3. This is the self-documenting aspect of using symbolic constants and is one of the benefits of using named constants rather than just numbers.\n\nAll of the menu items have the same x coordinate, but we want to print them vertically, so the y coordinate must change for each menu item. The /y += 2/ skips down to two lines after a menu item is drawn putting a blank line between each item. This keeps the menu from looking too crowded and makes it much more readable.\n\nThe next section of code, {{{MainMenu}}} is the menu function that is called from the main program.\n\n\x3Cdiv class="code"\x3E\n/mmenu.bi/\n{{{\n\'This draws the menu and returns the selected value.\nFunction MainMenu() As mmenuret\n   Dim As mmenuret idx = mNew\n   Dim menuitems(mNew To mQuit) As String\n   Dim As Integer mx, my, done = FALSE, tx, ty\n   Dim  As String mkey, mtitle \n...\n}}}\n\x3C/div\x3E\nThe return value of the function is the Enumeration that we defined earlier in the code. This tells the main program what menu item the user selected, or if the user wants to quit the program.\n\nWe then define the menu variables that we need in order to manage the actual menu. The variable /idx/ is the currently selected menu item and is initialized to mNew so that when the menu is drawn, we have a menu item preselected. The actual menu items strings are contained within the string array /menuitems/. Once again we are using the Enumeration ids to define our array, both for clarity and to make the program easier to manage. If we need to add a new menu item to the list, we don\'t need to redefine our array if we don\'t change the position of mNew or mQuit. The enumeration will automatically renumber the ids, making the array the needed size. \n\nThe other variables are just working variables that we will see in action as we examine the code.\n\n\x3Cdiv class="code"\x3E\n/mmenu.bi/\n{{{\n...\n\'Set the menu text.\n   menuitems(mNew) = "New Game    "\n   menuitems(mLoad) = "Load Game   "   \n   menuitems(mInstruction) = "Instructions"   \n   menuitems(mQuit) = "Quit        "\n   \'Set the menu items x, y\n   mx = CenterX(menuitems(3))\n   my = CenterY(UBound(menuitems) * 2)\n   ScreenLock   \n   \'Draw the menu background.\n   DrawBackground menuback()\n   \'Draw the title with drop shadow.\n   mtitle = "Dungeon of Doom v." & dodver\n   tx = CenterX(mtitle) * charw\n   ty = (10 * charh)\n   Draw String (tx + 1, ty + 1), mtitle, fbBlack\n   Draw String (tx, ty), mtitle, fbYellow\n   \'Draw the menu text.\n   DrawMenu menuitems(), idx, mx, my\n   ScreenUnLock\n...\n}}}\n\x3C/div\x3E\nAfter we define our variables, we need to set up the menu item strings, calculate the position of the menu and draw the background and menu title. We have added a new function to the program, /CenterY/ that will calculate the vertical position of the menu items. CenterY is defined in defs.bi.\n\n\x3Cdiv class="code"\x3E\n/defs.bi/\n{{{\n#Define CenterY(ni)((txrows / 2) - (ni / 2))\n}}}\n\x3C/div\x3E\nThis is defined as a macro since the functionality is similar to the CenterX macro. In CenterY we are passing the number of items to center vertically in /ni/. We don\'t care about the strings here, just how many of them we have. However, we don\'t just want to pass the number of array elements in menuitems because we are printing a blank line between each item, so we double the amount of items that need to be centered so that we have an accurate position. You see this in the code {{{my = CenterY(UBound(menuitems) * 2)}}}. \n\nOnce we calculate the x and y positions of the menu items, we draw the background using our DrawBackground subroutine, draw the title and then draw the menu itself. Notice that the title is drawn twice; once in the color Black and once in the color Yellow. When we draw the title in Black, we offset the string by 1 pixel. This gives a drop shadow of 1 pixel that helps the title stand out on the background. The effect is subtle but noticeable. Try commenting out the {{{Draw String (tx + 1, ty + 1), mtitle, fbBlack}}} code and you will see the difference when you compile and run the program.\n\nAfter the screen is drawn, we enter a Do-Loop to get the input from the user.\n\n\x3Cdiv class="code"\x3E\n/mmenu.bi/\n{{{\n...\n   Do\n      \'Get the current key.\n      mkey = InKey\n      \'Did user press a key?\n      If mkey \x3C\x3E "" Then\n         \'If user presses escape or close button, then exit with quit id.\n         If (mkey = key_esc) Or (mkey = key_close) Then\n            idx = mQuit\n            done = TRUE\n         EndIf\n         \'User pressed up arrow.\n         If mkey = key_up Then\n            \'Decrement the menu index.\n            idx -= 1\n            \'Wrap around to bottom of menu.\n            If idx \x3C mNew Then idx = mQuit\n            \'Draw the menu.\n            ScreenLock\n            DrawMenu menuitems(), idx, mx, my\n            ScreenUnLock\n         EndIf\n         \'User pressed down arrow.\n         If mkey = key_dn Then\n            \'Increment the menu index.\n            idx += 1\n            \'Wrap around to top of menu.\n            If idx \x3E mQuit Then idx = mNew\n            \'Draw the menu.\n            ScreenLock\n            DrawMenu menuitems(), idx, mx, my\n            ScreenUnLock\n         EndIf\n         \'User pressed enter key.\n         If mkey = key_enter Then\n            \'Exit menu.\n            done = TRUE\n         EndIf\n      EndIf\n      Sleep 10\n   Loop Until done = TRUE\n   \'Clear any keys.\n   ClearKeys\n   Return idx\nEnd Function\n}}}\n\x3C/div\x3E\nWe use /Inkey/ to get the key press from the user. We could use /MultiKey/ here as well, but Inkey is easier and more consistent than using MultiKey. MultiKey also cannot detect when the user clicks the Close button on the window, so in order to trap that event, we need to use Inkey anyway, so we might as well use it for all the user input. \n\nThe different key codes that Inkey will return are defined in defs.bi.\n\n\x3Cdiv class="code"\x3E\n/defs.bi/\n{{{\n\'Key consts\nConst xk = Chr(255)\nConst key_up = xk + "H"\nConst key_dn = xk + "P"\nConst key_rt = xk + "M"\nConst key_lt = xk + "K"\nConst key_close = xk + "k"\nConst key_esc = Chr(27)\nConst key_enter = Chr(13)\n}}}\n\x3C/div\x3E\nInkey returns a two-character string when an extended key is pressed. The extended keys are the arrow keys, editing keys and function keys. For the extended keys, the first character in the string has an ASCII value of 255 so we define that in the /xk/ constant and use that to build the rest of the key codes. String comparisons are relatively slow, which is why MultiKey is preferred for time critical applications since it does a faster numeric comparison. In our case though, it doesn\'t matter since we are waiting for the user to input a key and not drawing anything in real-time.\n\nAfter we call Inkey we check to see if the string has anything in it, and if it does, we check to see if it is a key that we are interested in. The valid keys for the menu are the Up and Down arrow keys, the ESCape key, the Close button on the window, and the Enter key. We check for each one and execute the appropriate code associated with the key.\n\nWe want the menu to wrap around when the current item is either at the top or bottom of the menu and the user is pressing the up or down key. We do this by adjusting the idx value of the key when it goes out of range, as shown in the following code.\n\n\x3Cdiv class="code"\x3E\n/mmenu.bi/\n{{{\n\'User pressed up arrow.\nIf mkey = key_up Then\n   \'Decrement the menu index.\n   idx -= 1\n   \'Wrap around to bottom of menu.\n   If idx \x3C mNew Then idx = mQuit\n   \'Draw the menu.\n   ScreenLock\n   DrawMenu menuitems(), idx, mx, my\n   ScreenUnLock\nEndIf\n}}}\n\x3C/div\x3E\nFor the up arrow, we decrement the value in idx and then check to see if we are out of the menu enumeration range. If we are we reset the idx value to mQuit to set the current item to the bottom of the menu. The down key works in a similar fashion, except idx is incremented rather than decremented. Once the new menu item has been set, we then draw the menu on the screen.\n\nTo select an item from the menu, the user needs to press the Enter key.\n\n\x3Cdiv class="code"\x3E\n/mmenu.bi/\n{{{\n\'User pressed enter key.\nIf mkey = key_enter Then\n    \'Exit menu.\n    done = TRUE\nEndIf\n}}}\n\x3C/div\x3E\nAll we need to do here is just exit the Do-Loop by setting the done flag to TRUE. The values TRUE and FALSE are new definitions that we have added to defs.bi to represent Boolean values.\n\n\x3Cdiv class="code"\x3E\n/defs.bi/\n{{{\n\'Define true and false.\n#Ifndef FALSE\n   #Define FALSE 0\n#EndIf\n#Ifndef TRUE\n   #Define TRUE -1\n#EndIf\n}}}\n\x3C/div\x3E\nWe are using the value of 0 for FALSE and the value of -1 for TRUE. We could have just as easily used 1 for TRUE, but by using -1 we can do /Not FALSE/ (which evaluates to -1) if we need to. It may not be necessary, but at least the capability is there, and we are being consistent in the way FreeBasic handles the /Not/ operator. Why not define TRUE as (Not FALSE)? Because that would incur an extra evaluation each time we need to test for a TRUE value, and there is no reason to do that. We could have used the Const statement, but then we are incurring a look-up for each evaluation of TRUE. We are already incurring a look-up when we use the done variable to compare against, so we are saving one look-up by using a /#Define/.\n\nOnce we exit the loop we return the idx value which contains the currently selected menu item. We will use that in our main program.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Get the menu selection\nDim mm As mmenu.mmenuret\n\'Loop until the user selects New, Load or Quit.\nDo\n   \'Draw the main menu.\n    mm = mmenu.MainMenu\n   \'Process the menu selection.\n   If mm = mmenu.mNew Then\n      \'Generate the character.\n   ElseIf mm = mmenu.mLoad Then\n      \'Load the save game.\n   ElseIf mm = mmenu.mInstruction Then\n      \'Print the instructions.\n   EndIf\nLoop Until mm \x3C\x3E mmenu.mInstruction\n\n\'Main game loop.\nIf mm \x3C\x3E mmenu.mQuit Then\n   \'Generate the map.\n   \'Display the main screen.\n   \'Get key input until user exits.\nEndIf\n}}}\n\x3C/div\x3E\nThis is our menu code defined in the main program. The first thing we need to do is to create a variable to hold our menu selection {{{Dim mm As mmenu.mmenuret}}}. Since the menu code is wrapped in a Namespace, we need to qualify the data type by using the Namespace name, /mmenu/ along with the dot operator and then the enumeration name. The format mmenu./object/ is the format needed to access any value or object from the Namespace. We could use the /Using name/ statement, which allows us to directly access the Namespace elements, but then we are defeating the purpose of the Namespace and we might as well not us it if we go that route.\n\nOnce we have an Enumeration variable, we call the /MainMenu/ function to draw the menu and get the user response. The program will stay in the MainMenu function until the user makes a selection. Once the selection is made, we execute the appropriate action by examining the return value and executing the appropriate code.\n\nNotice that we loop if the user selects the Instructions menu items. If the user wants instructions, we will display the Instruction screen(s) and then drop back into the menu so that the user can either create a New game or Load an existing game, or just Quit. \n\nThe /If/ statements define a program outline that we will need to fill in with code. It gives us a road map and that we can follow as we continue to expand the program into a working game. Even though we have added just a few lines of code, we have made considerable progress. We have a good start on the file structure of the project, we have some basic data and utility structures in place and we have a basic outline for the rest of the program that we can follow. We are well on our way to having a playable roguelike.\n',
'= 4: The RPG System\nMost roguelikes implement some sort of role-playing system in the game. It gives the player something to do besides just running around killing monsters, although it is perfectly legitimate to create a roguelike where all you do is run around and kill monster if that is the type of game you wish to create. What role-playing gives a game is a level of interaction that games without a role-playing aspect do not have. It also lends a greater strategic depth to the game, because the player must make decisions on what aspects of his character he wants to improve over the long term, and balance that with the current needs that a character has to just survive the moment. It also gives a game replay value; the player can try different things each time they start a new game and see how the character develops over the course of the game. Role-playing gives a game depth that will keep the player coming back to the game again and again.\n\nIf you want to implement role-playing in your game, you can use an existing system or create the system from scratch. There many free systems on the Internet, and well as some commercial systems that have been opened up for gamers such as the D20 system by Wizards of the Coast and the Dominion Rules by Dominion Games. It takes some work to covert an existing system to work in a computer game, as most are designed for person-to-person gaming sessions, but it may be worth the effort to get a system that has been tested in actual use. However, one of the fun things about roguelikes is designing the role-play yourself, and that is what we are going to do for our game, while borrowing some techniques from other systems.\n\n*Character Attributes*\n\nWe are going to start with the character attributes, called "stats" or "abilities" in other games, since attributes describe the nature of the character, what the character\'s natural abilities are in the game. We will use attributes to see if a character can do something in the game like bash open a door or read a spell book. The attributes will encompass both the mental and physical aspects of the character.\n\n* Strength: Describes how strong the character is compared to objects in the world.\n* Stamina: Describes how long the character can exert himself over a period of time.\n* Dexterity: Describes the ability of the character to manipulate objects in the world.\n* Agility: Describes movement ability of the character\'s body as a whole. \n* Intelligence: Describes the ability of the character to understand artifacts or to comprehend spell books.\n* Hit Points (Strength + Stamina): Describes how well the character can take punishment before succumbing to death.\n\nWhen building our character, we will generate these attributes using random numbers to create an initial profile of the character. Over the course of the game, the player will be able to increase these attributes by spending experience points gained in interaction with the environment. Gaining and spending experience points to improve attributes represents the maturing of the character as they progress through the game. Just as in the real world, doing something over and over will lead to better performance of the action over time as the character learns both from the successes and failures.\n\n*Skills*\n\nWhile attributes describe the abilities of the character, skills describe how those abilities translate into actions. Skills are based on attributes, and when an attribute is adjusted, the skills that rely on those attributes will be adjusted as well. This maintains a close connection between the current ability of the character and what he can do with those abilities. The skills should be tailored to fit the scope of the game; that is, a game shouldn\'t have unnecessary skills that will never get used, or missing skills that the character may need in the game. If a game had a forest to explore, then hunting might be a needed skill so the player can find food. There would be little use for a hunting skill in a simple dungeon crawler. For /Dungeon of Doom/ the skill set will be related to combat, as this is the primary game mechanic we have in the game. The skill set we are going to use is outlined in the miniature war game book /The Book of Mars/ by David Tennes.\n\n* Unarmed Combat Factor (Strength + Agility): Combat using bare hands and feet.\n* Armed Combat Factor (Strength + Dexterity): Combat using hand held weapons.\n* Projectile Combat Factor (Dexterity + Intelligence): Combat using projectile weapons.\n* Magic Combat Factor (Intelligence + Stamina): Magic attack factor; used when casting spells.\n* Combat Defense Factor (Strength + Agility): Defense factor against non-Magic attacks.\n* Magic Defense Factor (Agility + Intelligence): Defense against magic attacks.\n\nThe skills are composite factors that are based on the character\'s attributes. Skills cannot be changed using experience points, but will be recalculated whenever an attribute changes. This direct connection between attributes and skills reflects the relationship between what the character is able to do, and what they can put into practice. If the character increases an ability, the skill will increase as well. If an attribute falls, due to a spell for example, the skills will also be less effective. This model reflects what we see in the real world, at least to some degree. If a person is sick, they simply cannot do as much as when they are fully healthy and we will see this in our attribute-skill model.\n\n*Closed vs. Open Models*\n\nMost role-playing systems fall into two major groups, closed and open systems. A closed system is where the attributes and/or skills have a maximum value. This works fine for games where the advancement path is scripted and the player is moved along the game by the story encounters that he must complete before he can advance in the game. It doesn\'t work so well though in roguelikes due to the procedurally generated content. While it is possible to build scripted events in a procedural manner, the process is complicated and the results are generally poor. It is better to just build a classic role-playing game if you wish to have a consistent and compelling story line to the game. For roguelikes, the better choice is an open system.\n\nIn an open system, the attribute-skill set does not have any maximum value. The player is free to increase any attribute value to any level they want. Since our skill set is based on attributes, this also means that skills do not have any maximum value. This though brings its own challenges. How do you set difficulty levels for actions? And how do you resolve those actions? The answer is actually quite simple. \n\nThe key notion is that anyone can fail, even an expert. The expert is more likely to succeed more often than the novice due to their training and experience, but they can still fail. This means that there is a range of ability for both the expert and the novice. The range for the expert may be from 1 to 100, and for the novice 1 to 25. If the expert attempts an action, they have 75% chance of succeeding over the novice. This gives us the answer on how to resolve actions using an open system.\n\n*Action Resolution*\n\nAction resolution is anything that the character must try to do in the game world, from bashing open a door to fighting a monster. The skill or attribute that the player is using for the action will be the attempt range for that action. For example, if a character has a Strength of 50 and is attempting to bash open a door, than the roll for that attempt will be in the range of 1 to 50. If the roll is greater than the strength rating of the door, the character will bash open the door. For most inanimate objects like doors and scrolls, a roll that is greater than the difficulty rating of the object will be a success.\n\nWe will use the same idea in combat, but both the character and monster will make a roll to determine who wins the round. If the character is attacking a monster, the character roll will be made on one of the combat factors. The monster will roll on one of the defense factors. Whoever has the greater roll will win the encounter. Ties will go to the defender. If a monster attacks the character, the monster will roll on a combat factor and the character roll will be on a defense factor. Again, the greater rolls wins while a tie goes to the defender. \n\nThis means that a relatively weak player could win against an advanced monster or a weak monster could do real damage to a strong player. However, since the rolls are based on ranges, whoever has the greater range will win more often than one who doesn\'t have as great a range just as you would expect. This method also prevents one major flaw of many role-playing games: the automatic win. In some systems, character can become so powerful that they will win every time, and the game quickly becomes stale with little challenge. In this system, no matter how powerful the character becomes, they could still fail. There is no automatic win. Each encounter will have meaning and real consequences.\n\nThis also enables us to keep the difficulty of the game within the capabilities of the character. If a game is too hard or too easy, the player will lose interest and won\'t be playing the game. Difficulty balancing is one of the hardest things to get right in a game, but we have an edge since we can simply keep the action ranges close to the character action ranges and offer a challenging but not impossible game.  \n\nThis is our basic role-playing system we re going to use in /Dungeon of Doom/. Relatively simple, but as you will see, quite effective. Now that the system is in place, we can code up our character generation routines.\n',
'= 5: Character Generation\n\x3Cdiv align="center" /\x3E\x3Cimg src=\'images/genchar.png\' /\x3E\x3C/div\x3E\n\nNow that we have a basic idea of what we need for our roleplaying system, we can code up a character for the game. We have added a new file to the project called /character.bi/. In it we have created a Type definition called /characterinfo/ that contains all the attributes and skills of our character as well as some additional information.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n\'Character attribute type def.\nType characterinfo\n   cname As String * 40 \'Name of character.\n   stratt(2) As Integer \'Strength attribute (0), str bonus (1)\n   staatt(2) As Integer \'Stamina attribute (0), sta bonus (1)\n   dexatt(2) As Integer \'Dexterity attribute (0), dex bonus (1)\n   aglatt(2) As Integer \'Agility attribute (0), sta bonus (1)\n   intatt(2) As Integer \'Intelligence attribute (0), int bonus (1)\n   currhp As Integer    \'Current HP\n   maxhp As Integer     \'Max HP\n   ucfsk(2) As Integer  \'Unarmed combat skill (0), ucf bonus (1)\n   acfsk(2) As Integer  \'Armed combat skill (0), acf bonus (1)\n   pcfsk(2) As Integer  \'Projectile combat skill (0), pcf bonus (1)\n   mcfsk(2) As Integer  \'Magic combat skill (0), mcf bonus (1)\n   cdfsk(2) As Integer  \'Combat defense skill (0), cdf bonus (1)\n   mdfsk(2) As Integer  \'Magic defense skill (0), mdf bonus (1)\n   currxp As Integer    \'Current spendable XP amount.\n   totxp As Integer     \'Lifetime XP amount.0\n   currgold As Integer  \'Current gold amount.\n   totgold As Integer   \'Lifetime gold amount.\n   locx As Integer      \'Current x position on map.\n   locy As Integer      \'Current y location on map.\nEnd Type\n}}}\n\x3C/div\x3E\nAs you can see we have all of the attributes and skills that we defined in our outline of the role-playing system. Each attribute and skill is set up as an array with index 0 being the actual value and index 1 being a bonus value. When we get to creating our items, some items will confer a bonus to an attribute or skill if used or consumed. The bonus value will be added to the attribute or skill when resolving actions. To keeps things simple, and a bit more challenging for the player, only one bonus for a particular attribute or skill can be active at any time. You could of course increase the number of elements in each attribute/skill array to handle multiple adjustments.\n\nNow, you may be wondering why we are not using Integer pointers here to have variable length arrays for each attribute or skill. When we get to it, we are going to use this Type Def as part of the save file and we can\'t save pointer values since they do not have any meaningful values outside of the program. Since we are limiting the adjustment of an attribute or skill to a pair of values, the amount of memory we would save by using pointers isn\'t as advantageous as having the ability to simply dump this Type Def to disk using a single /Put/ statement and loading it back into memory using a single /Get/ statement. This reduces the amount of code needed to implement the save and load routines, and also reduces the chance of translation errors in the program. We will go into this in detail when we get to the save and load routines.\n\nThe characterinfo Type is part of another Type definition which is our character object that we will be using in the game. This is defined as /character/.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n\'Character object.\nType character\n   Private:\n   _cinfo As characterinfo\n   Public:\n   Declare Sub PrintStats ()\n   Declare Function GenerateCharacter() As Integer\nEnd Type\n}}}\n\x3C/div\x3E\nNotice that we have both a /Private:/ section and /Public:/ section in our object. The Private: section contains the data and procedure elements that we want to hide from the rest of the program. In this case our characterinfo Type is a private variable within our character object. By placing the character data in a Private: variable we are restricting access to that data, and hiding the implementation of the data.\n\nSince the characterinfo data is Private: it cannot be accessed directly outside of the character Type. As we need to access that data, we will add Property, Sub and Function members to our character object to retrieve and modify the character data. These Properties, Subs and Function comprise the data interface of the object. This gives us complete control over what is going into the object and what is coming out of the object. Data going into the object can be validated to make sure it is in range or of the proper type. Data coming out of the object can be formatted correctly or converted as needed for use by the rest of the program. By restricting access to the data to a single point, we are ensuring the integrity of the data. The rest of the program does not see the internal representation of the data; they only see the interface. We can change the internal implementation and as far as the rest of the program is concerned, nothing has been changed. This makes updating the program much easier.\n\nRight now we don\'t have any way to change or retrieve the data from the characterinfo type since we are just generating the character data and displaying that data to the user. As we move along in the project we will come back and add in what we need, as we need it. This incremental approach will enable us to code exactly what we need and thoroughly test any changes before adding in new code. This makes the code much more compact and robust, and makes debugging any problems that much easier since most bugs will be in the new code we just added to the program.\n\nWhen the player selects New Game from the main menu we will call the /GenerateCharacter/ function.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Get the menu selection\nDim mm As mmenu.mmenuret\n\'Loop until the user selects New, Load or Quit.\nDo\n   \'Draw the main menu.\n    mm = mmenu.MainMenu\n   \'Process the menu selection.\n   If mm = mmenu.mNew Then\n      \'Generate the character.\n      Var ret = pchar.GenerateCharacter\n      \'Do not exit menu when user presses ESC.\n      If ret = FALSE Then\n         \'Set this so we loop.\n         mm = mmenu.mInstruction\n      EndIf\n   ElseIf mm = mmenu.mLoad Then\n      \'Load the save game.\n   ElseIf mm = mmenu.mInstruction Then\n      \'Print the instructions.\n   EndIf\nLoop Until mm \x3C\x3E mmenu.mInstruction\n}}}\n\x3C/div\x3E\nThe /GenerateCharacter/ will return FALSE if the player presses the Escape key and TRUE when the player selects the Enter key. If the player presses the Escape key, then we set the variable mm to /mInstruction/ which will just cause the loop to cycle and display the main menu again. If Enter is pressed, the menu loop will exit and enter into the main game loop.\n\n\x3Cdiv class="code"\x3E\n/character.bi /\n{{{\n\'Generates a new character.\nFunction character.GenerateCharacter() As Integer\n   Dim As String chname, prompt, skey\n   Dim As Integer done = FALSE, ret = TRUE, tx, ty\n   \n   \'Set up user input prompt.\n   prompt = "Press \x3Cr\x3E to roll again, \x3Center\x3E to accept, \x3Cesc\x3E to exit to menu."\n   tx = (CenterX(prompt)) * charw\n   ty = (txrows - 6) * charh   \n   \'Get the name of the character.\n   Do\n      Cls\n      \'Using simple input here.\n      Input "Enter your character\'s name (40 chars max):",chname\n      \'Validate the name here. \n      If Len(chname) \x3E 0 And Len(chname) \x3C 40 Then\n         done = TRUE\n      Else\n         \'Let the user know what they did wrong.\n         Cls\n         If Len(chname) = 0 Then\n            Print "Name is required. \x3CPress any key.\x3E"\n            Sleep\n            ClearKeys\n         EndIf\n         If Len(chname) \x3E 40 Then\n            Print "Name is too long. 40 chars max. \x3CPress any key.\x3E"\n            Sleep\n            ClearKeys\n         EndIf\n      EndIf\n      Sleep 10\n   Loop Until done = TRUE\n   done = FALSE\n   \'Generate the character data.\n   Do\n      With _cinfo\n         .cname = chname\n         .stratt(0) = RandomRange (1, 20)\n         .staatt(0) = RandomRange (1, 20)\n         .dexatt(0) = RandomRange (1, 20)\n         .aglatt(0) = RandomRange (1, 20)\n         .intatt(0) = RandomRange (1, 20)\n         .currhp = .stratt(0) + .staatt(0) \n         .maxhp = .currhp\n         .ucfsk(0) = .stratt(0) + .aglatt(0) \n         .acfsk(0) = .stratt(0) + .dexatt(0) \n         .pcfsk(0) = .dexatt(0) + .intatt(0)\n         .mcfsk(0) = .intatt(0) + .staatt(0)\n         .cdfsk(0) = .stratt(0) + .aglatt(0)\n         .mdfsk(0) = .aglatt(0) + .intatt(0)\n         .currxp = RandomRange (100, 200)\n         .totxp = .currxp\n         .currgold = RandomRange (50, 100)\n         .totgold = .currgold\n         .locx = 0\n         .locy = 0\n      End With\n      \'Print out the current character stats.\n      PrintStats\n      DrawStringShadow tx, ty, prompt\n      \'Get the user command.\n      Do\n         \'Get the key press.\n         skey = Inkey\n         \'Fornat to lower case.\n         skey = LCase(skey)\n         \'If escape exit back to menu.\n         If skey = key_esc Then\n            done = TRUE\n            ret = FALSE\n         EndIf\n         \'If enter continue with game.\n         If skey = key_enter Then\n            done = TRUE\n         EndIf\n         Sleep 10\n      Loop Until (skey = "r") Or (skey = key_esc) Or (skey = key_enter)\n   Loop Until done = TRUE \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nThe method to generate a character isn\'t too complicated. The first part of the code sets up the prompt that you see displayed on the bottom of the image above. It is simply a list of commands that the player can enter to re-roll the character, accept the character or escape back to the main menu. \n\nOnce the prompt is created, we need to get the name of the character. Since the name field in the characterinfo Type is 40 characters in length, we want to make sure that the player enters in a name that is greater than 0 characters and no more than 40 characters in length. While this may not be strictly necessary, as the compiler will truncate any characters over 40 when using a fixed-length string, it makes the program much more professional. It tells the user that we have spent some time thinking about what we are doing, that we want them to have a good experience playing the game, and that we care about even the little things in the game.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n...\n   \'Get the name of the character.\n   Do\n      Cls\n      \'Using simple input here.\n      Input "Enter your character\'s name (40 chars max):",chname\n      \'Validate the name here. \n      If Len(chname) \x3E 0 And Len(chname) \x3C 41 Then\n         done = TRUE\n      Else\n         \'Let the user know what they did wrong.\n         Cls\n         If Len(chname) = 0 Then\n            Print "Name is required. \x3CPress any key.\x3E"\n            Sleep\n            ClearKeys\n         EndIf\n         If Len(chname) \x3E 40 Then\n            Print "Name is too long. 40 chars max. \x3CPress any key.\x3E"\n            Sleep\n            ClearKeys\n         EndIf\n      EndIf\n      Sleep 10\n   Loop Until done = TRUE\n...\n}}}\n\x3C/div\x3E\nWe use the Input statement to get the name from the player and then validate the length of the name to make sure that it is greater than 0 and less then or equal to 40. If the name is in range, then we exit the loop. If not, we call the Input statement to get another name from the user. After we get the name, we roll the new character.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n   \'Generate the character data.\n   Do\n      With _cinfo\n         .cname = chname\n         .stratt(0) = RandomRange (1, 20)\n         .staatt(0) = RandomRange (1, 20)\n         .dexatt(0) = RandomRange (1, 20)\n         .aglatt(0) = RandomRange (1, 20)\n         .intatt(0) = RandomRange (1, 20)\n         .currhp = .stratt(0) + .staatt(0) \n         .maxhp = .currhp\n         .ucfsk(0) = .stratt(0) + .aglatt(0) \n         .acfsk(0) = .stratt(0) + .dexatt(0) \n         .pcfsk(0) = .dexatt(0) + .intatt(0)\n         .mcfsk(0) = .intatt(0) + .staatt(0)\n         .cdfsk(0) = .stratt(0) + .aglatt(0)\n         .mdfsk(0) = .aglatt(0) + .intatt(0)\n         .currxp = RandomRange (100, 200)\n         .totxp = .currxp\n         .currgold = RandomRange (50, 100)\n         .totgold = .currgold\n         .locx = 0\n         .locy = 0\n      End With\n}}}\n\x3C/div\x3E\nThe attributes are generated using random numbers from 1 to 20. Once we have a set of numbers for the attributes, we then follow the equations that we defined when we created our role-playing system to build the skills. Index 0 is the current value for the attribute or skill so we are using that value in all our calculations. The gold and experience represent the "life before the game" for the character. The experience points allow the player to improve the character if needed and the gold allows the player to purchase some equipment from the /Wandering Merchant/ if desired. The Wandering Merchant will be covered in a later chapter.\n\nWe are using a new function here called RandomRange. This is defined in utils.bi.\n\n\x3Cdiv class="code"\x3E\n/utils.bi/\n{{{\n\'Returns a random number within range.\nFunction RandomRange(lowerbound As Integer, upperbound As Integer) As Integer\n	Return Int((upperbound - lowerbound + 1) * Rnd + lowerbound)\nEnd Function\n}}}\n\x3C/div\x3E\nThis returns an Integer from low to high inclusive. What is nice about this function is that we can use it for negative numbers as well as positive numbers or a range of numbers from negative to positive. This comes in handy when we want to create a range of negative to positive values such as {{{bonus = RandomRange(-10, 10)}}}. We will use this technique when we add monsters to the game to set the attributes of the monsters.\n\nOnce the attributes and skills are generated, we call the /PrintStats/ subroutine to print out the current character information.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n\'Prints out the current stats for character.\nSub character.PrintStats ()\n   Dim As Integer tx, ty, row = 8\n   Dim As String sinfo\n   \n   ScreenLock\n   \'Draw the background.\n   DrawBackground charback()\n   \'Draw the title.\n   sinfo = Trim(_cinfo.cname) & " Attributes and Skills" \n   ty = row * charh\n   tx = (CenterX(sinfo)) * charw\n   DrawStringShadow tx, ty, sinfo, fbYellow\n   \'Draw the attributes.\n   row += 4\n   ty = row * charh\n   tx = 70\n   sinfo = "Strength:     " & _cinfo.stratt(0)\n   DrawStringShadow tx, ty, sinfo\n   row += 2\n   ty = row * charh\n   sinfo = "Stamina:      " & _cinfo.staatt(0)\n   DrawStringShadow tx, ty, sinfo\n   row += 2\n   ty = row * charh\n   sinfo = "Dexterity:    " & _cinfo.dexatt(0)\n   DrawStringShadow tx, ty, sinfo\n   row += 2\n   ty = row * charh\n   sinfo = "Agility:      " & _cinfo.aglatt(0)\n   DrawStringShadow tx, ty, sinfo\n   row += 2\n   ty = row * charh\n   sinfo = "Intelligence: " & _cinfo.intatt(0)\n   DrawStringShadow tx, ty, sinfo\n   row += 2\n   ty = row * charh\n   sinfo = "Hit Points:   " & _cinfo.currhp\n   DrawStringShadow tx, ty, sinfo\n   row += 3\n   ty = row * charh\n   sinfo = "Unarmed Combat:    " & _cinfo.ucfsk(0)\n   DrawStringShadow tx, ty, sinfo\n   row += 2\n   ty = row * charh\n   sinfo = "Armed Combat:      " & _cinfo.acfsk(0)\n   DrawStringShadow tx, ty, sinfo\n   row += 2\n   ty = row * charh\n   sinfo = "Projectile Combat: " & _cinfo.pcfsk(0)\n   DrawStringShadow tx, ty, sinfo\n   row += 2\n   ty = row * charh\n   sinfo = "Magic Combat:      " & _cinfo.mcfsk(0)\n   DrawStringShadow tx, ty, sinfo\n   row += 2\n   ty = row * charh\n   sinfo = "Combat Defense:    " & _cinfo.cdfsk(0)\n   DrawStringShadow tx, ty, sinfo\n   row += 2\n   ty = row * charh\n   sinfo = "Magic Defense:     " & _cinfo.mdfsk(0)\n   DrawStringShadow tx, ty, sinfo\n   row += 3\n   ty = row * charh\n   sinfo = "Experience: " & _cinfo.currxp\n   DrawStringShadow tx, ty, sinfo\n   row += 2\n   ty = row * charh\n   sinfo = "Gold:       " & _cinfo.currgold\n   DrawStringShadow tx, ty, sinfo\n\n   ScreenUnLock\nEnd Sub\n}}}\n\x3C/div\x3E\nThis simply prints out each attribute and skill to the screen. Like we have done in previous screens, we display an ascii graphic to make the presentation a bit more enjoyable, and then use a new subroutine we have added to utils.bi, DrawStringShadow, to print out the information.\n\n\x3Cdiv class="code"\x3E\n/utils.bi/\n{{{\n\'Draw a string with drop shadow.\nSub DrawStringShadow(x As Integer, y As Integer, txt As String, fcolor As UInteger = fbWhite)\n   Draw String (x + 1, y + 1), txt, fbBlack   \n   Draw String (x, y), txt, fcolor   \nEnd Sub\n}}}\n\x3C/div\x3E\nThis prints out the string using the drop shadow technique we have used before. Since we are going to be doing this many times, it makes sense to put this into a subroutine that we can call from anywhere in the program. The subroutine prints out the "shadow" of the string using the color Black offset by 1 pixel. It then prints the same string using either White if no color is passed in the /fcolor/ parameter, or the passed color. The x and y parameters are pixel locations not text locations. If we use this to print out text locations, like on our PrintStats routine, then we will calculate the x and y position of the text column and row ahead of time, and pass that to the subroutine. \n\nOnce the character information is printed to the screen, we display the prompt across the bottom of the screen and then loop until we get the player\'s input. This input loop is inside the main Generate/Print loop that we have since the player may want to re-roll the character and we will need to create a new set of attributes and skills and then display the new values. Once the player is satisfied with the character, they can press the Enter key and continue on with the game.\n\nEven though we don\'t have much yet, this gives us a start on our character. There are many features we will need to add to the character but what we have is enough to get us to our first milestone in the project, walking around a map. And that isn\'t too far off now.',
'= 6: The Intro\n\x3Cdiv align="center" /\x3E\x3Cimg src=\'images/introscreen.png\' /\x3E\x3C/div\x3E\n\nWhile it is not necessary to add an introductory screen, it does add some flavor to the game and you can use it as an opportunity to tell the new player a bit about the game, what might be expected, what the game objectives are and what the outcome will be if the player wins or loses. It doesn\'t have to be all that elaborate; it should be interesting enough for the player to look at and it should convey some information about the game. If you read through the text given in the image above, you can see that the back-story, here written in the form of a letter, tells the player about why he is going into the Dungeon of Doom, and both the payoff if he succeeds or the consequences if he fails.\n\nThis helps set the mood of the game and conveys to the player that you have tried to build a world here to explore, not just a game. By using the name of the character that the player entered in during the character generation process, you are putting the character squarely into this world. This is one of the reasons I like to have some experience and gold generated during the character creation process; it makes the character seem like they belong here, and by extension, the player.\n\nWhat you cannot see in the image above, is the fire animation going on behind the text. The animation adds some life to the screen, makes it more engaging and the player may actually stop a moment and read the text. You will notice in the text that fire is a recurring theme, from the "fires of evil" to the "Amulet of Crystal Fire". The background fire adds to this sense of disaster and helps reinforce the text, while hinting at things to come. Hopefully, questions are raised in the player\'s mind that will draw them into the game to find the answers.\n\nAll the code for the intro is in /intro.bi/. Since the intro is a self-contained unit, it is wrapped in the Namespace /intro/. There is one callable subroutine, DoIntro. that we call right after we generate a character. Since the intro will only be displayed when the player starts a new game, we place the call after the program returns from the character generation code.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n \'Process the menu selection.\n   If mm = mmenu.mNew Then\n      \'Generate the character.\n      Var ret = pchar.GenerateCharacter\n      \'Do not exit menu when user presses ESC.\n      If ret = FALSE Then\n         \'Set this so we loop.\n         mm = mmenu.mInstruction\n      Else\n         \'Do the intro.\n         intro.DoIntro\n      EndIf\n  \n}}}\n\x3C/div\x3E\nIf the player hits the Escape key then we don\'t want to display the intro, so we place it as an /Else/ clause to our return value check. Since the intro code is in the intro Namespace, we need to preface the call with the Namespace identifier.\n\nThe intro code itself is quite simple. It is just the classic demo fire effect with the parchment graphic displayed over it and the text over the parchment graphic. The fire effect is a fairly simple effect to achieve, it just requires a color palette, an array of particles and a cooling map that gives the fire some variation.\n\n\x3Cdiv class="code"\x3E\n/intro.bi/\n{{{\nConst maxage = 80\n\nDim Shared pal(maxage) As UInteger = { _\n&hFFF9F7D4,&hFFF9F7D4,&hFFF9F7D4,&hFFF9F7D4,&hFFF9F7D4,&hFFF9F7D4,&hFFF9F7D4,&hFFF9F7D4, _\n&hFFF9F7D4,&hFFF9F7D4,&hFFF9F7D4,&hFFF9F7D4,&hFFF9F7D4,&hFFF9F7D4,&hFFF9F7D4,&hFFF9F7D4, _\n&hFFF9F7D4,&hFFF9F7D4,&hFFF9F7D4,&hFFF9F7D4,&hFFF9F7D4,&hFFF9F7D4,&hFFF9F7D4,&hFFF9F7D4, _\n&hFFF9F7D4,&hFFF9F6B6,&hFFF8F48E,&hFFF8F364,&hFFF8F139,&hFFF9EC14,&hFFFAD51B,&hFFFCBE22, _\n&hFFFDA52A,&hFFFF8C31,&hFFFA802E,&hFFF4752A,&hFFEE6A26,&hFFE95E22,&hFFE3531D,&hFFDE471A, _\n&hFFD53613,&hFFCC250D,&hFFC31207,&hFFBB0100,&hFFAC0000,&hFF9D0000,&hFF8E0000,&hFF7F0000, _\n&hFF700000,&hFF610000,&hFF5A0000,&hFF550000,&hFF510000,&hFF4D0000,&hFF480000,&hFF440000, _\n&hFF3F0000,&hFF3B0000,&hFF370000,&hFF320000,&hFF2E0000,&hFF2A0000,&hFF250000,&hFF210000, _\n&hFF1C0000,&hFF180000,&hFF130000,&hFF100000,&hFF0B0000,&hFF070000,&hFF020000,&hFF000000, _\n&hFF000000,&hFF000000,&hFF000000,&hFF000000,&hFF000000,&hFF000000,&hFF000000,&hFF000000}\n}}}\n\x3C/div\x3E\nThis is the fire color palette that is used to display the different colors of the fire. A fire particle starts at the bottom of the screen and then is moved up towards the top of the screen. As it moves up, it ages, changing the color. The age value of the fire particle is used as the index into the fire palette to give the particle the proper color.\n\n\x3Cdiv class="code"\x3E\n/intro.bi/ \n{{{\n\'Moves each particle up on the screen, with a chance of moving side to side.\nSub MoveParticles\n   Dim As Integer x, y, tage, xx\n   Dim As Single r\n    \n   For x = 0 To txcols - 1\n      For y = 1 To txrows - 1\n         \'Get the current age of the particle.\n         tage = fire(x, y) \n         \'Moves particle left (-1) or right (1) or keeps it in current column (0). \n         xx = RandomRange(-1, 1) + x\n         \'Wrap around the screen.\n         If xx \x3C 0 Then xx = txcols - 1\n         If xx \x3E txcols - 1 Then xx = 0\n         \'Set the particle age.\n         tage += coolmap(xx, y - 1) + 1\n         \'Make sure the age is in range.         \n         If tage \x3C 0 Then tage = 0\n         If tage \x3E (maxage - 1) Then tage = maxage - 1\n         fire(xx, y - 1) = tage\n      Next\n   Next\n                    \nEnd Sub\n}}}\n\x3C/div\x3E\nEach particle in the fire array is moved up one location and possibly left or right as well, depending on the return value of the call {{{RandomRange(-1, 1)}}}. This is an example of RandomRange giving us a range from negative to positive, which is why this function is so handy. As the particle is moved up, its age is combined with the coolmap value to either make the age go up or down, depending on the value in the coolmap. By using a cooling map like this, you can create some interesting variations in the fire to make it look a bit more realistic.\n\nOnce all the old particles are moved up, new particles are added to the bottom of the screen.\n\n\x3Cdiv class="code"\x3E\n/intro.bi/\n{{{\n\'Adds particles to the fire along bottom of screen.\nSub AddParticles\n    Dim As Integer x\n    \n    For x= 0 To txcols - 1\n        fire(x, txrows - 1) = RandomRange(0, 20)\n    Next\n    \nEnd Sub\n}}}\n\x3C/div\x3E\nThe age of the new particle can be anything from 0 to 20. This will give the fire some variations along the bottom of the screen that will propagate upwards making a nice effect. Once the new particles are added, the screen is ready to be drawn.\n\n\x3Cdiv class="code"\x3E\n/intro.bi/\n{{{\n\'Draws the fire or parchment on the screen.\nSub DrawScreen(egg As Integer) \n   Dim As Integer x, y, cage, tx, ty, wid = 68\n   Dim As UInteger clr\n   Dim As String st, tt\n   \n    \n   ScreenLock\n   MoveParticles\n   AddParticles\n   For x = 0 To txcols - 1\n      For y = 0 To txrows - 1\n         If fire(x, y) \x3C maxage Then\n            cage = Smooth(fire(), x, y)\n            cage += 10\n            If cage \x3E maxage Then cage = maxage\n            \'If background color is 0, then use fire color. \n            clr = introback(x + y * txcols)\n            \'Check to see if we draw the parchment.\n            If clr = &hFF000000 Then clr = pal(cage)\n            \'Do the easter egg.\n            If egg = TRUE Then\n               clr = pal(cage)\n            EndIf\n            Draw String (x* 8, y* 8), Chr(219), clr\n         End If\n      Next\n   Next\n...\n}}}\n\x3C/div\x3E\nInstead of drawing the screen twice, we iterate through the parchment array and look for the transparent color /&hFF000000/. If the current location is the transparent color, then we draw the fire instead of the parchment. For any other color, we draw the parchment graphic. Once the graphics have been drawn, we draw the background story text.\n\n\x3Cdiv class="code"\x3E\n/intro.bi/\n{{{\n\'Draw the story text.\n   tx = 6 * charw\n   ty = 3 * charh\n   st = "Dear " & pchar.CharName & ","\n   DrawStringShadow tx, ty, st\n   ty += (charh * 2)\n   st = "My friend and ally... Dark days are upon us and the fires of evil are threatening the beloved land. "\n   st &= "The evil wizard Deboza has escaped from her prison, the magical crystal shard in the center of the "\n   st &= "Amulet of Crystal Fire. How she escaped from this prison, I do not know, but it must have required "\n   st &= "great magic to accomplish. She is even now gathering an army to do battle with the good folk of the "\n   st &= "land and it is only a matter of time before she overruns the weak defenses of the of the King. Long "\n   st &= "peace, though welcome, has its own dangers."\n   Do\n	   tt = WordWrap(st, wid)\n		DrawStringShadow tx, ty, tt\n		ty += charh + 2\n  	Loop Until Len(tt) = 0\n}}}\n\x3C/div\x3E\nWe are using our familiar DrawStringShadow and a new function, /WordWrap/ which is contained within utils.bi. WordWrap splits text /st/ at the nearest space position to /wid/. It returns the string back in /tt/ while /st/ is trimmed of the returned string. This is why WordWrap is called in a loop; as each string is returned it is drawn on the screen. When /st/ is empty, the loop exits. DrawScreen is called in a loop by the subroutine DoIntro.\n\n\x3Cdiv class="code"\x3E\n/intro.bi/\n{{{\n\'Executes the game intro.\nSub DoIntro()\n	Dim As Single t\n   Dim As String eg\n   Dim As Integer doegg = FALSE\n   \n   CreateCoolMap\n   Do\n      eg = InKey\n      If eg = "f" Then\n         doegg = Not doegg\n         eg = ""\n      EndIf\n      t = Timer\n      \'Draws the screen.\n      DrawScreen doegg\n      Do While (Timer - t) \x3C FD \n	Sleep 1\n      Loop\n   Loop Until eg \x3C\x3E "" \n   ClearKeys\nEnd Sub\n}}}\n\x3C/div\x3E\nThe first thing we do here is to create the coolmap.\n\n\x3Cdiv class="code"\x3E\n/intro.bi/\n{{{\n\'Creates a cool map that will combined with the fire value to give a nice effect.\nSub CreateCoolMap\n   Dim As Integer i, j, x, y\n    \n   For x = 0 To txcols - 1 \n      For y = 0 To txrows - 1\n          coolmap(x, y) = RandomRange(-10, 10)\n      Next\n   Next\n    \n   For j = 1 To 10\n      For x = 1 To txcols - 2 \n         For y = 1 To txrows - 2\n            coolmap(x, y) = Smooth(coolmap(), x, y)\n         Next\n      Next\n   Next\nEnd Sub\n}}}\n\x3C/div\x3E\nThe coolmap is just a duplicate of the fire array, except that it contains static values that are used to age the fire particles. The Smooth function takes the average of the neighboring positions to smooth out the border areas so that the fire has a more natural look. Once the coolmap is created we enter a loop and draw the screen.\n\n\x3Cdiv class="code"\x3E\n/intro.bi/\n{{{\n      Do While (Timer - t) \x3C FD \n	Sleep 1\n      Loop\n}}}\n\x3C/div\x3E\nThis section of code simply locks the frames to the value of FD which is defined as {{{Const FD = 1 / 60}}}, or 60 frames a second. This helps keep the animation consistent across different processor speeds.\n\nYou may have also noticed a little Easter egg here in the code. If the player presses the *f* key, the parchment will not be drawn so that you can watch the fire behind the text. A completely useless feature, but fun nonetheless.\n\nNow that the intro is done, we can start getting into the meat of the program such as building the dungeon and getting our hero to walk around the screen.',
'= 7: Dungeon Building\nThe dungeon is where all the action takes place. The player must explore the dungeon, fight monsters, find items, and find the stairs down to go to the next level. Ultimately, the goal is to get to the last level, recover the "item of interest" and return to the surface world. For roguelikes, the dungeon level is procedurally generated, so that each level is different. This makes the replay value of roguelikes quite high, and sets the genre apart from other types of games. In order to build a dungeon we need to know the elements that make up a dungeon.\n\nThere are mainly two types of dungeons, persistent and non-persistent dungeons. Persistent dungeons have levels with the same layout regardless of how many times a character visits a level. Non-persistent dungeons generate a completely new level each time the character enters the level. You don\'t necessarily have to have either/or; you can combine both in a single game. In this game we are going to implement a non-persistent dungeon since we have a limited game and a persistent dungeon might become tedious and boring over the long term.\n\nA dungeon level is actually a grid that starts out filled with "rock" or impassible terrain. Within this expanse of rock, we carve out rooms, which are tiles that are marked as passable, usually floor tiles, but it could also be water, grass, lava, or any number of other terrain types. We add the rooms in a random fashion based on one of several algorithms (or a combination of algorithms) until we reach the desired number of rooms. Once the rooms have been added, we need to connect the rooms with hallways, which are usually narrow passages marked as passable terrain. Once all the rooms are connected, we have our dungeon. We can then populate the dungeon with items, monsters--whatever we have defined in the game.\n\nSo we can break this down into a set of steps:\n\n* Fill the level array with impassible terrain.\n* Iteratively add rooms until we reach the desired number of rooms (or we do not have space for a new room).\n* Connect all the rooms.\n* Populate the dungeon.\n\nThere are some things we need to keep in mind:\n\n* The process has to be fairly quick. No one wants to wait five minutes for the level to be built.\n* The number of rooms should be reasonable. If the player has to explore hundreds of rooms, it is quite possible that boredom will set in before he finds the stairs down to the next level.\n* Every room needs to be reachable. There is nothing worse than to find that the level exit is in a room the player can\'t reach.\n\nSo the obvious question is, what algorithm do we use to build the rooms? The answer is, there are many. This can be good or bad when trying to decide which algorithm to use. I\'ll highlight some of the more popular methods, and then explain in detail the method we are going to use in /Dungeon of Doom/.\n\n*Trial and Error Method*\n\nThe Trial and Error method, as the name suggests, randomly picks a spot on the level and tries to place a room. If the room fits, that is it is not covering an existing room, the room is carved and a new random spot is selected. This technique is executed a set number of times or until the maximum number of rooms are placed. It is a very simple method, but it does have some problems.\n\nAs the level is filled with rooms, it becomes increasingly difficult to place new rooms. One way to address this problem is to start with the maximum room size, and if it doesn\'t fit, make the room smaller and try again. You can do this several times until you get to the minimum room size, and if it still doesn\'t fit, then you choose another location. You also need to set the number of attempts to a reasonable number, as it is easy to get into an infinite loop with this technique.\n\nSince there is no pattern to the rooms, getting all the rooms connected can be a problem. A simple solution is to use a room list to keep track of all the placed rooms. The elements of the list would be a Type Def that has all the necessary room information, such as the corner coordinates and a flag indicating if the room is connected. As a room is added to the level, a room record is created containing the room information and the record is added to the list. Once all the rooms are added, the list would be used to connect the rooms. You will see this used in the Grid-Based method described below.\n\nThe advantage of this technique is that it is quite simple and creates a very randomized dungeon. The variety is very interesting, as you end up with large rooms, small rooms, and all kinds of rooms in between, all laid out in interesting patterns that never repeat. If you like highly random dungeons, this is a good technique to use.\n\n*Room-Corridor Method*\n\nAnother favorite is the room-corridor method. You start by adding a room to a random location. You then select a random wall, draw a corridor a random length, and then draw another room. You do this until you cannot add another room, or you reach the maximum number of rooms. This is best done as a recursive algorithm, where you draw the room, pick a wall that doesn\'t have a corridor, draw the corridor and then call the function again to draw the room. By using recursion, you move from wall to wall in each room, drawing corridors and rooms, until you fill the level with the desired number of rooms, or exit when the space has been filled. This technique also has the advantage of making sure each room is connected to another room, since you are drawing the rooms and corridors at the same time. \n\nThe potential problem with this technique is that you can create a situation where you have large open spaces in the level where the algorithm didn\'t get to or couldn\'t place a room of any size. I call this a potential problem, since this may not matter at all in your game. You do have the opportunity to use those spaces for things like secret rooms. You may also end up with dead-end corridors if a room doesn\'t fit in the current location, but again, this may or may not be a problem. \n\n*BSP Method*\n\nThe BSP, or Binary Space Partition method works by recursively dividing up level into smaller and smaller regions until the region size reaches the desired room size. BSP uses a tree structure to store the dungeon information, where each level of the tree contains a sub region within the dungeon and the leaf nodes contain the actual rooms that are drawn on the level.\n\nYou start by splitting the level, either horizontally or vertically, into two regions of random sizes. Once split, the region information is saved in the tree, one node for each region. You then select one of the saved regions, and split it again, saving the two new regions in the tree under the region node that you selected. This creates the binary tree structure. You continue this process until you reach the desired number of rooms. Once the tree is built, you walk the leaf nodes creating rooms in each leaf region. \n\nThe BSP method also makes it easy to connect all the rooms. You start at the leaf nodes, and connect sister nodes. You then move up one level and connect sister regions by selecting a room room in each region and drawing a corridor between them. You continue walking up the tree and when you reach the root node, the dungeon is fully connected.\n\nThe BSP method gives you a lot of control over the dungeon building process. You can create very regular layouts to mimic the inside of a house or random layouts just by varying the parameters of the splitting process. For regular layouts, you would use a uniform splitting model. For a random layout, a more random splitting model. \n\nThere is a very good example of the [[http://roguebasin.roguelikedevelopment.org/index.php?title=Basic_BSP_Dungeon_generation|BSP Method]] on the RogueBasin wiki.\n\n*Maze Method*\n\nThe Maze method, as the name implies, uses a maze algorithm to generate the dungeon. There are many maze generating algorithms available, but you need an algorithm that generates a perfect mazes, so that there is always a path from the start of the maze to the end of the maze. This is important since all areas of the dungeon need to be reachable. You build the maze using the desired maze algorithm and then select a location within the maze and enlarge it into a room. You then select another location and again create a room. After creating the desired number of rooms, you have a dungeon with maze-like corridors that lead to the different rooms in the level.\n\n*Grid-Based Method*\n\nThe grid-based method is similar to the BSP method, in that the level is divided up into regions, but in the grid-based method, each region is the same size. The randomness of the dungeon comes from iteratively selecting regions in the grid, drawing a room in the selected region, and then connecting the rooms. This technique is much like the technique used in the original Rogue game, and produces dungeons that look like this classic game.\n\nThere are some advantages to using the grid-based method. It is quite fast, since you are randomly selecting regions rather than space locations. The grid allows you to create regular dungeon layouts, like a town or city setting, or random layouts depending on how you select the grid regions. Since each region is the same size, it is easier to create non-rectangular rooms and know that the room will "fit". It also makes it easy to create custom pre-designed rooms that could be stored in a file. Since each region in the grid is a uniform size, you can design complex room layouts that are sure to fit within the level.\n\nThe disadvantage is that the dungeons will not have the variety of the Trial and Error or the Room and Corridor method since you are working with a regular grid. However, the speed and ease of use make it ideal for a general dungeon builder and is the method we are going to use for /Dungeon of Doom/.\n\n*Grid-Based Method in Detail*\n\nSince this is the method we are going to implement in DOD, let\'s take a closer look at some grid-based generation code. We start by defining some structures to hold our grid information.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Min and max room dimensions\n#Define roommax 8 \n#Define roommin 4\n#Define nroommin 20\n#Define nroommax 50 \n\'Empty cell flag.\n#Define emptycell 0\n\'Level size\n#Define mapw 100 \'map width\n#Define maph 100 \'map height\n\n\'Grid cell size (width and height)\nConst csize = 10\n\'Grid dimensions.\nConst gw = mapw \\ csize\nConst gh = maph \\ csize\n\n\'Coordinates.\nType mcoord\n    x As Integer\n    y As Integer\nEnd Type\n\n\'Room dimensions.\nType rmdim\n    rwidth As Integer\n    rheight As Integer\n    rcoord As mcoord\nEnd Type\n\n\'Room information\nType roomtype\n    roomdim As rmdim  \'Room width and height.\n    tl As mcoord      \'Room rect\n    br As mcoord\nEnd Type\n\n\'Grid cell structure.\nType celltype\n    cellcoord As mcoord \'The cell position.\n    room As Integer     \'Room id. This is an index into the room array.\nEnd Type\n\nDim Shared rooms(1 To nroommax) As roomtype    \'Room array.\nDim Shared grid(1 To gw, 1 To gh) As celltype \'Grid of cells.\nDim Shared as Integer numrooms                \'Number of rooms in map.\n}}}\n\x3C/div\x3E\nThe /mcoord/ Type Def defines a location in the level array. It is the x and y location of a point. The /rmdim/ Type Def defines the dimensions of a room. The /rcoord/ field defines the center of the room. This is used in the corridor building subroutine where we want our corridor to head towards the center of the room. This makes the corridor building process easier, and also we don\'t have to worry about using any strange math if the room is non-rectangular. The /roomtype/ Type Def is the room information record with /tl/ containing the top left coordinates of the room and /br/ containing the bottom right coordinates of the room. Even though we don\'t necessarily need the /br/ field since we already know the width and height of the room, it makes the code easier to manage by pre-calculating the room rect. The roomtype Type Def is used to create a room array which will contain our list of rooms.\n\nThe /celltype/ Type Def represents a region, or cell, within the grid. It just contains the region location and an id into the room array. The id links the region to a room so we know what room is in what region. The grid array is a two dimensional array of /celltype/ and defines the grid. The the number of cells in the grid is defined by two Const values /Const gw = mapw \\ csize/ for the width and /Const gh = maph \\ csize/ for the height. Since the mapw and maph are defined as 100 and the  region size, or csize, is defined as 10, this gives us a 10x10 grid, covering 100x100 tiles. The room sizes will be from /roommax/ defined as 8 to /roommin/ defined as 4. This will place the room inside the region and give us a border around each room.\n\nFinally, numrooms is the number of rooms in the grid and will be some value between /nroommin/ defined as 20 and /nroommax/ defined as 50. This will give us 20 to 50 rooms, more than enough for a level of the dungeon. Since we are using at most, 50% of the grid, the generation will be quite fast.\n\n*Initializing the Grid*\n\nThe first thing we need to do is to initialize the grid.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Init the grid and room arrays\nSub InitGrid\n   Dim As Integer i, j, x, y, gx = 1, gy = 1\n	\n   \'Clear room array.		\n   For i = 1 To nroommax\n   	rooms(i).roomdim.rwidth = 0\n   	rooms(i).roomdim.rheight = 0\n   	rooms(i).roomdim.rcoord.x = 0\n   	rooms(i).roomdim.rcoord.y = 0\n   	rooms(i).tl.x = 0\n   	rooms(i).tl.y = 0\n   	rooms(i).br.x = 0\n   	rooms(i).br.y = 0\n   Next \n   \'How many rooms\n   numrooms = RandomRange(nroommin, nroommax)\n   \'Build some rooms\n   For i = 1 To numrooms\n   	rooms(i).roomdim.rwidth = RandomRange(roommin, roommax)\n    	rooms(i).roomdim.rheight = RandomRange(roommin, roommax)\n   Next\n    \'Clear the grid array\n   For i = 1 To gw \n   	For j = 1 To gh\n    		grid(i, j).cellcoord.x = gx\n    		grid(i, j).cellcoord.y = gy\n     		grid(i, j).Room = emptycell\n     		gy += csize\n   	Next\n   	gy = 1\n   	gx += csize\n   Next\n   \'Add rooms to the grid\n   For i = 1 To numrooms\n   	\'Find an empty spot in the grid\n   	Do\n   		x = RandomRange(2, gw - 1)\n   		y = RandomRange(2, gh - 1)\n   	Loop Until grid(x, y).Room = emptycell\n   	\'Room center\n   	rooms(i).roomdim.rcoord.x = grid(x, y).cellcoord.x + (rooms(i).roomdim.rwidth \\ 2)   \n   	rooms(i).roomdim.rcoord.y = grid(x, y).cellcoord.y + (rooms(i).roomdim.rheight \\ 2)\n	\'Set the room rect\n	rooms(i).tl.x = grid(x, y).cellcoord.x \n	rooms(i).tl.y = grid(x, y).cellcoord.y \n	rooms(i).br.x = grid(x, y).cellcoord.x + rooms(i).roomdim.rwidth + 1\n	rooms(i).br.y = grid(x, y).cellcoord.y + rooms(i).roomdim.rheight + 1\n   	\'Save the room index\n   	grid(x, y).Room = i\n   Next\nEnd Sub \n}}}\n\x3C/div\x3E\n\n/InitGrid/ will first clear the room array so that we can populate it with new rooms.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n...\n   \'Clear room array.		\n   For i = 1 To nroommax\n   	rooms(i).roomdim.rwidth = 0\n   	rooms(i).roomdim.rheight = 0\n   	rooms(i).roomdim.rcoord.x = 0\n   	rooms(i).roomdim.rcoord.y = 0\n   	rooms(i).tl.x = 0\n   	rooms(i).tl.y = 0\n   	rooms(i).br.x = 0\n   	rooms(i).br.y = 0\n   Next \n...\n}}}\n\x3C/div\x3E\n\nThis simply iterates through the array and clears all the room fields. Next we create some rooms that will go into the grid.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n...\n   \'How many rooms\n   numrooms = RandomRange(nroommin, nroommax)\n   \'Build some rooms\n   For i = 1 To numrooms\n   	rooms(i).roomdim.rwidth = RandomRange(roommin, roommax)\n    	rooms(i).roomdim.rheight = RandomRange(roommin, roommax)\n   Next\n...\n}}}\n\x3C/div\x3E\n/numrooms/ contains a randomly generated number of rooms. We then iterate through the room list, generating the room dimensions. You will notice that we are generating the rooms before we generate the grid. Since the grid regions are a fixed size, and we know that size ahead of time, we can generate rooms without having to see if they will fit on the map. We know our rooms will fit. We could also do some other things in this step such as set the shape of the room, or set an id that looks into a file for a pre-defined room. There are a number of possibilities here.\n\nOnce the rooms are set up, we need to create the actual grid. The grid gives u a guide on where we need to place the rooms in the level array.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n...\n    \'Clear the grid array\n   For i = 1 To gw \n   	For j = 1 To gh\n    		grid(i, j).cellcoord.x = gx\n    		grid(i, j).cellcoord.y = gy\n     		grid(i, j).Room = emptycell\n     		gy += csize\n   	Next\n   	gy = 1\n   	gx += csize\n   Next\n...\n}}}\n\x3C/div\x3E\nThe gx and gy parameters are the starting point of the grid. We simply iterate through the grid array, saving the coordinates of the grid regions and setting each room id to the emptycell marker. This lets us know that the region is currently empty. Now we are ready to place the rooms.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n...\n   \'Add rooms to the grid\n   For i = 1 To numrooms\n   	\'Find an empty spot in the grid\n   	Do\n   		x = RandomRange(2, gw - 1)\n   		y = RandomRange(2, gh - 1)\n   	Loop Until grid(x, y).Room = emptycell\n   	\'Room center\n   	rooms(i).roomdim.rcoord.x = grid(x, y).cellcoord.x + (rooms(i).roomdim.rwidth \\ 2)   \n   	rooms(i).roomdim.rcoord.y = grid(x, y).cellcoord.y + (rooms(i).roomdim.rheight \\ 2)\n	\'Set the room rect\n	rooms(i).tl.x = grid(x, y).cellcoord.x \n	rooms(i).tl.y = grid(x, y).cellcoord.y \n	rooms(i).br.x = grid(x, y).cellcoord.x + rooms(i).roomdim.rwidth + 1\n	rooms(i).br.y = grid(x, y).cellcoord.y + rooms(i).roomdim.rheight + 1\n   	\'Save the room index\n   	grid(x, y).Room = i\n   Next\n...\n}}}\n\x3C/div\x3E\nHere we select an empty region from the grid, set the room center location, and then define the room\'s upper left and lower right coordinates. These coordinates are the actual coordinates in the level array. The room index is then saved in the grid array so that we know the current region is occupied. You will notice that we are leaving a border of empty grid regions around the level map. This is just for display reasons since it results in a better map display.\n\n*Drawing the Rooms*\n\nNow that we have defined the rooms, we need to draw them on the actual level array.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Transfer grid data to map array.\nSub DrawMapToArray\n    Dim As Integer i, x, y, pr, rr, rl, ru, kr\n	\n    \'Draw the first room to map array\n    For x = rooms(1).tl.x + 1 To rooms(1).br.x - 1\n        For y = rooms(1).tl.y + 1 To rooms(1).br.y - 1\n	    level.lmap(x, y).terrid = tfloor\n	Next\n    Next\n    \'Draw the rest of the rooms to the map array and connect them.\n    For i = 2 To numrooms\n        For x = rooms(i).tl.x + 1 To rooms(i).br.x - 1\n	    For y = rooms(i).tl.y + 1 To rooms(i).br.y - 1\n	        level.lmap(x, y).terrid = tfloor\n            Next\n        Next\n        ConnectRooms i, i - 1\n    Next\nEnd Sub\n}}}\n\x3C/div\x3E\nHere we draw the first room to the level array. The level array has already been filled to an impassable tile, so all we need to do is to set the room rect to a floor tile and we have our room. Once the first room is drawn, we then draw the rest of the rooms. The reason we draw the first room separately is to have two rooms available for building the corridor between them. This is done in the /ConnectRooms/ subroutine.\n\n*Connecting Rooms*\n\nOnce we have two rooms drawn, we need to connect the rooms.\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Connect all the rooms.\nSub ConnectRooms( r1 As Integer, r2 As Integer)\n    Dim As Integer idx, x, y\n    Dim As mcoord currcell, lastcell\n    Dim As Integer wflag\n    \n    currcell = rooms(r1).roomdim.rcoord\n    lastcell = rooms(r2).roomdim.rcoord\n        \n    x = currcell.x\n    If x \x3C lastcell.x Then\n        wflag = FALSE\n        Do\n            x += 1\n            If level.lmap(x, currcell.y).terrid = twall Then wflag = TRUE\n            If (level.lmap(x, currcell.y).terrid = tfloor) And (wflag = TRUE) Then\n                Exit Sub\n            EndIf\n            level.lmap(x, currcell.y).terrid = tfloor\n        Loop Until x = lastcell.x\n    End If\n    \n    If x \x3E lastcell.x Then\n        wflag = FALSE\n        Do\n            x -= 1\n            If level.lmap(x, currcell.y).terrid = twall Then wflag = TRUE\n            If (level.lmap(x, currcell.y).terrid = tfloor) And (wflag = TRUE) Then \n                Exit Sub\n            EndIf\n            level.lmap(x, currcell.y).terrid = tfloor\n        Loop Until x = lastcell.x\n    EndIf\n    \n    y = currcell.y\n    If y \x3C lastcell.y Then\n        wflag = FALSE\n        Do\n            y += 1\n            If level.lmap(x, y).terrid = twall Then wflag = TRUE\n            If (level.lmap(x, y).terrid = tfloor) And (wflag = TRUE) Then \n                Exit Sub\n            EndIf\n            level.lmap(x, y).terrid = tfloor\n        Loop Until y = lastcell.y\n    EndIf\n    \n    If y \x3E lastcell.y Then\n        Do\n            y -= 1\n            If level.lmap(x, y).terrid = twall Then wflag = TRUE\n            If (level.lmap(x, y).terrid = tfloor) And (wflag = TRUE) Then \n                Exit Sub\n            EndIf\n            level.lmap(x, y).terrid = tfloor\n        Loop Until y = lastcell.y\n    EndIf\n         \nEnd Sub\n}}}\n\x3C/div\x3E\nThis code looks more complicated than it is; it is just a Manhattan Distance algorithm that walks the level array from the first room to the second room, drawing the corridor as it goes along. The first thing we do is to get the starting point and ending point.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n...\n    currcell = rooms(r1).roomdim.rcoord\n    lastcell = rooms(r2).roomdim.rcoord\n...\n}}}\n\x3C/div\x3E\nOnce we have that we check to see if the target room x coordinate is left or right of the starting room x coordinate. If so, we move either left /x -= 1/ or right /x += 1/ until the corridor x coordinate matches the target room x coordinate. If the x coordinates are already the same, we do nothing. Once the x coordinates match up, we do the same for the y coordinates. By matching up the x and y coordinates, we know we have reached the target room. \n\nThe /wflag/ value tells us if we hit a floor tile before we have reached the target room. The corridor may cross another corridor, or another room, and if so, we can terminate the corridor building algorithm since we don\'t have to go any farther. We could just keep going until we reach the target room if we want some extra corridor crossings and room connections. Exiting early speeds up the process a bit.\n\nSince we are drawing a room and connecting it to the last room, we will probably draw a room over an existing corridor. There is nothing wrong with this, and it makes our room automatically connected, but we still need to do the connection process since it would take time to see if the new room is connected, and having multiple entries into a room helps vary the room configurations.\n\nThis is all the code we need to implement our dungeon generator. While this is only an outline, it represents the majority of code that we can use in DOD. The next step is to put this code into our game, add in a display routine and we will be one step closer to being able to walk around our dungeon.\n\n',
'= 8: The Dungeon\n\x3Cdiv align="center"\x3E\x3Cimg src=\'images/map.png\' /\x3E\x3C/div\x3E\n\nSince we have gone over the main idea behind building a dungeon level, it is time to fire up the editor and put it in code. As you can see from the image, we have our dungeon map. You have already seen some of the code, but the dungeon level is going to be an object, so there are some minor changes that we need to make to the code to work as an object. All of the dungeon code is in the file map.bi. We will start by looking at the Defines and Const values.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Size of the map.\n#Define mapw 100\n#Define maph 100\n\'Min and max room dimensions\n#Define roommax 8 \n#Define roommin 4\n#Define nroommin 20\n#Define nroommax 50 \n\'Empty cell flag.\n#Define emptycell 0\n\'Viewport width and height.\n#Define vw 40 \n#Define vh 55 \n\'Grid cell size (width and height)\n#Define csizeh 10\n#Define csizew 10\n\'Grid dimensions.\nConst gw = mapw \\ csizew\nConst gh = maph \\ csizeh\n}}}\n\x3C/div\x3E\nAs you can see we are using many of the same data elements as in the example code. There are some new ones though. /vw/ and /vh/ are the viewport width and height. This is the display area of the map that will be shown on the screen. We have also made the cell size two values, /csizeh/ for the height of a grid cell and /csizew/ for the width of a cell. Making the cell width and height separate values will enable us to change the grid dimensions if we want or need to at some point in time. Everything else is the same as in the example code.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'The types of terrain in the map.\nEnum terrainids\n   tfloor = 0  \'Walkable terrain.\n   twall       \'Impassable terrain.\n   tdooropen   \'Open door.\n   tdoorclosed \'Closed door.\n   tstairup    \'Stairs up.\n   tstairdn   \'Stairs down.\nEnd Enum\n \n\'Room dimensions.\nType rmdim\n    rwidth As Integer\n    rheight As Integer\n    rcoord As mcoord\nEnd Type\n\n\'Room information\nType roomtype\n    roomdim As rmdim  \'Room width and height.\n    tl As mcoord      \'Room rect\n    br As mcoord\n    secret As Integer         \nEnd Type\n\n\'Grid cell structure.\nType celltype\n    cellcoord As mcoord \'The cell position.\n    Room As Integer     \'Room id. This is an index into the room array.\nEnd Type\n\n\'Map info type\nType mapinfotype\n    terrid As terrainids  \'The terrain type.\n    hasmonster As Integer \'Current cell has a monster in it.\n    monidx As Integer     \'Index into monster array.\n    hasitem As Integer    \'Current cell has an item in it.\n    visible As Integer    \'Character can see cell.\n    seen As Integer       \'Character has seen cell.\nEnd Type\n\n\'Dungeon level information.\nType levelinfo\n   numlevel As Integer \'Current level number.\n   lmap(1 To mapw, 1 To maph) As mapinfotype \'Map array.\nEnd Type\n}}}\n\x3C/div\x3E\nYou have already seen most of these Type Defs before, however we have added a new Enum, /terrainids/ which is a list of terrain types, and a new Type Def /mapinfotype/ which is the information for a single tile in the level array. \n\nEach tile has several data items associated with it. A tile has a terrain type which is contained in /terrid/. If the tile has a monster in it, /hasmonster/ will be TRUE and the /monidx/ field will contain the index into the monster array. The /hasitem/ field will be TRUE if the tile has an item in it. When we get to the inventory code, we will be able to do a look-up into the item array and find out what type of item is in the tile. The two last fields /visible/ and /seen/ indicate that the tile is visible or invisible to the character and whether or not the character has seen this tile already. The other new Type Def, /levelinfo/ contains the current level id and the actual map array. As you can see the map array is an array of mapinfotype so we can keep track of all the information associated with a tile in the map. \n\nAll of these Type Defs are used in our dungeon level object.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Dungeon level object.\nType levelobj\n   Private:\n   _level As levelinfo                 \'The level map structure.\n   _numrooms As Integer                \'The number of rooms in the level.\n   _rooms(1 To nroommax) As roomtype   \'Room information.\n   _grid(1 To gw, 1 To gh) As celltype \'Grid infromation.\n   _blockingtiles As Integer Ptr       \'Set of blocking tiles.\n   _blocktilecnt As Integer            \'Number of blocking tiles.\n   Declare Function _BlockingTile(tx As Integer, ty As Integer) As Integer \'Returns true if blocking tile.\n   Declare Function _LineOfSight(x1 As Integer, y1 As Integer, x2 As Integer, y2 As Integer) As Integer \'Returns true if line of sight to tile.\n   Declare Function _CanSee(tx As Integer, ty As Integer) As Integer \'Can character see tile.\n   Declare Sub _CalcLOS () \'Calculates line of sight with post processing to remove artifacts.\n   Declare Function _GetMapSymbol(tile As terrainids) As String \'Returns the ascii symbol for terrian id.\n   Declare Function _GetMapSymbolColor(tile As terrainids) As UInteger\n   Declare Sub _InitGrid() \'Inits the grid.\n   Declare Sub _ConnectRooms( r1 As Integer, r2 As Integer) \'Connects rooms.\n   Declare Sub _AddDoorsToRoom(i As Integer) \'Adds doors to a room.\n   Declare Sub _AddDoors() \'Iterates through all rooms adding doors to each room.\n   Declare Sub _DrawMapToArray() \'Transfers room data to map array. \n   Public:\n   Declare Constructor ()\n   Declare Destructor ()\n   Declare Property LevelID(lvl As Integer) \'Sets the current level.\n   Declare Property LevelID() As Integer \'Returns the current level number.\n   Declare Sub DrawMap () \'Draws the map on the screen.\n   Declare Sub GenerateDungeonLevel() \'Generates a new dungeon level.\nEnd Type\n}}}\n\x3C/div\x3E\nAs you can see most of the code in the object is Private, since it is only used in the dungeon generation and display code. You have seen most of the this code already. First, let\'s take a look at the data items.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n...\n   _level As levelinfo                 \'The level map structure.\n   _numrooms As Integer                \'The number of rooms in the level.\n   _rooms(1 To nroommax) As roomtype   \'Room information.\n   _grid(1 To gw, 1 To gh) As celltype \'Grid infromation.\n   _blockingtiles As Integer Ptr       \'Set of blocking tiles.\n   _blocktilecnt As Integer            \'Number of blocking tiles.\n...\n}}}\n\x3C/div\x3E\n/level/ is the levelinfo Type Def. /numrooms/, /rooms/, and /grid/ are the same as in the example code. They are used to generate the grid and rooms of the level. The /blockingtiles/ is a list of terrain types that block line of sight. /blocktilecnt/ is the number of items in the list. This list is used in the line-of-sight calculation to determine if a tile blocks the line of sight of the character. The rest of the functions and subroutines are used to create the dungeon, so we will look at the implementation of each of them.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Generate a new dungeon level.\nSub levelobj.GenerateDungeonLevel()\n    Dim As Integer x, y\n\n    \'Clear level\n    For x = 1 To mapw\n        For y = 1 To maph\n            \'Set to wall tile\n            _level.lmap(x, y).terrid = twall\n            _level.lmap(x, y).visible = FALSE\n            _level.lmap(x, y).seen = FALSE\n            _level.lmap(x, y).hasmonster = FALSE\n            _level.lmap(x, y).hasitem = FALSE\n        Next\n    Next\n    _InitGrid\n    _DrawMapToArray\nEnd Sub\n}}}\n\x3C/div\x3E\nThe GenerateDungeonLevel is called from the main program to create a new dungeon. The first thing we need to do here is to clear the current level information so we can generate a new level. We then call InitGrid, which you have already seen in the example code, and then /DrawMapToArray/ which transfers the data from grid to the map array. Let\'s take a look at DrawMapToArray since we have added some new functions to it that were not in the example code.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Transfer grid data to map array.\nSub levelobj._DrawMapToArray()\n    Dim As Integer i, x, y, pr, rr, rl, ru, kr\n    \n    \'Draw the first room to map array\n        For x = _rooms(1).tl.x + 1 To _rooms(1).br.x - 1\n            For y = _rooms(1).tl.y + 1 To _rooms(1).br.y - 1\n                _level.lmap(x, y).terrid = tfloor\n            Next\n        Next\n    \'Draw the rest of the rooms to the map array and connect them.\n    For i = 2 To _numrooms\n        For x = _rooms(i).tl.x + 1 To _rooms(i).br.x - 1\n            For y = _rooms(i).tl.y + 1 To _rooms(i).br.y - 1\n                _level.lmap(x, y).terrid = tfloor\n            Next\n        Next\n        _ConnectRooms i, i - 1\n    Next\n    \'Add doors to selected rooms.\n    _AddDoors\n    \'Set up player location.\n    x = _rooms(1).roomdim.rcoord.x + (_rooms(1).roomdim.rwidth \\ 2) \n    y = _rooms(1).roomdim.rcoord.y + (_rooms(1).roomdim.rheight \\ 2)\n    pchar.Locx = x - 1\n    pchar.Locy = y - 1\n    \'Set up the stairs up.\n    _level.lmap(pchar.Locx, pchar.Locy).terrid = tstairup\n    \'Set up stairs down in last room.\n    x = _rooms(_numrooms).roomdim.rcoord.x + (_rooms(_numrooms).roomdim.rwidth \\ 2) \n    y = _rooms(_numrooms).roomdim.rcoord.y + (_rooms(_numrooms).roomdim.rheight \\ 2)\n    _level.lmap(x - 1, y - 1).terrid = tstairdn\nEnd Sub\n}}}\n\x3C/div\x3E\nMost of this is the same, but we have added an /AddDoors/ subroutine, which obviously adds doors to the rooms, and we have added code to set up the location of our player character along with the stair terrain types. The stairs up to the previous level is located where the player character is located, which is the first room in the room list. \n\nThe stairs down is located in the last room in the list. Since the rooms are randomly generated, these will be in random locations, but never in the same room. Using a room list makes this quite easy. Of course the two rooms may be next to each other, but chances are good that there is some separation between the two.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Adds doors to rooms.\nSub levelobj._AddDoors()\n    For i As Integer = 1 To _numrooms\n        _AddDoorsToRoom i\n    Next\nEnd Sub\n}}}\n\x3C/div\x3E\nThe AddDoors subroutine just iterates through all the rooms and calls AddDoorsToRoom for each room.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Add doors to a room.\nSub levelobj._AddDoorsToRoom(i As Integer)\n    Dim As Integer row, col, dd1, dd2\n    \n    \'Iterate along top of the room.\n    For col = _rooms(i).tl.x To _rooms(i).br.x\n        dd1 = _rooms(i).tl.y\n        dd2 = _rooms(i).br.y\n        \'If a floor space in the wall.\n        If _level.lmap(col, dd1).terrid = tfloor Then\n            \'Add door.\n            _level.lmap(col, dd1).terrid = tdoorclosed\n        EndIf\n        \'Iterate along bottom of room.\n        If _level.lmap(col, dd2).terrid = tfloor Then\n            _level.lmap(col, dd2).terrid = tdoorclosed\n        End If\n    Next\n    \'Iterate along left side of room.\n    For row = _rooms(i).tl.y To _rooms(i).br.y\n        dd1 = _rooms(i).tl.x\n        dd2 = _rooms(i).br.x\n        If _level.lmap(dd1, row).terrid = tfloor Then\n            _level.lmap(dd1, row).terrid = tdoorclosed\n        End If\n        \'Iterate along right side of room.\n        If _level.lmap(dd2, row).terrid = tfloor Then\n            _level.lmap(dd2, row).terrid = tdoorclosed\n        EndIf\n    Next\nEnd Sub\n}}}\n\x3C/div\x3E\nThis code just walks each wall in the room using the room\'s rect and if it finds a floor tile, it sets the tile terrain type to a closed door. It does two walls at a time, top and bottom, and left and right. Since we are using the room array to get the room information, we don\'t need to search the whole map for door areas. This makes the process quite efficient.\n\nOnce the dungeon is generated, we need to display the map on the screen and that requires determining which tiles the character can see. There are two terms that describe the visible set of tiles on the map: FOV or field of view and LOS or line of sight. You will sometimes see these two terms used interchangeably but the two terms refer to different aspects of the visibility map and are calculated differently.\n\n/Field of View/ represents the area that an actor can see and is expressed in degrees. If you have had your peripheral vision checked at the optometrist, what they are checking is your field of view, how much area you can see at one time. Most first-person shooters use an FOV of 90 degrees along the horizontal axis, with a vertical FOV scaled to the display. This gives a good display and reduces distortion effects. To calculate FOV you need a heading and degrees of vision. You then use some trigonometry to calculate the area that the actor can see. Most roguelikes have a 360 degree FOV so we can effectively ignore any FOV calculations. However, if you were to write a stealth-based roguelike then you would need to calculate the FOV before you calculate what an actor can see in the map. This way the character could sneak up on monsters or NPCs without them being able to see him.\n\nFOV only gives you the set of potentially visible tiles. Whether or not the character can actually see a tile depends on the /Line of Sight/ or LOS calculation. If a door is within the FOV of the character, but a monster is standing in front of the door, then the monster is blocking the line of sight of the door and the character can\'t see it. We would represent this on the map by not drawing the door.\n\nThere are various methods to calculate line of sight, from extremely complex algorithms, to basic raycasting. We are going to use basic raycasting as it is fast, it produces good results (with a post processing step) and it is quite simple. For raycasting, you simply select a tile on the map that is within the FOV of the character, draw a line from the tile to the character, and if you reach the character before hitting any blocking tiles, the character can see the tile. Let\'s take a look at the code.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Caclulate los with post processing.\nSub levelobj._CalcLOS ()\n    Dim As Integer i, j, x, y, v = vw / 2, h = vh / 2\n    Dim As Integer x1, x2, y1, y2\n    \n    \'Clear the vismap\n    For i = 1 To mapw\n    For j = 1 To maph\n        _level.lmap(i, j).visible = FALSE\n    Next\n    Next\n    \'Only check within viewport\n    x1 = pchar.Locx - v\n    If x1 \x3C 1 Then x1 = 1\n    y1 = pchar.Locy - h\n    If y1 \x3C 1 Then y1 = 1\n    \n    x2 = pchar.Locx + v\n    If x2 \x3E mapw - 1 Then x2 = mapw - 1\n    y2 = pchar.Locy + h\n    If y2 \x3E maph - 1 Then y2 = maph - 1\n    \'Iterate through vision area.\n    For i = x1 To x2\n        For j = y1 To y2\n        \'Don\'t recalc seen tiles\n          If _level.lmap(i, j).visible = FALSE Then\n             If _CanSee(i, j) = TRUE Then\n                _level.lmap(i, j).visible = TRUE\n                _level.lmap(i, j).seen = TRUE\n             End If\n          End If\n      Next\n    Next\n...\n}}}\n\x3C/div\x3E\nThis is the actual LOS code. The rest of the subroutine is the post processing step, which we will cover in a moment. The first thing we need to do is to clear the visibility map. This marks all tiles on the map as invisible. Now we only need to check those tiles that are within the display area of the map (since the rest of the map won\'t be seen anyway) so we create a viewport that matches the display area with the character in the center of the viewport and only check those tiles. \n\nHowever, if the character is at the edge of the map, then we may end up with negative coordinate values when we do this calculation. This is why we check to make sure the box is within the dimensions of the map, and if it isn\'t, force the box coordinates to match the map dimensions. We then just iterate though the viewport and see if the character can see a tile by calling the /CanSee/ function.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Determines if player can see object.\nFunction levelobj._CanSee(tx As Integer, ty As Integer) As Integer\n   Dim As Integer ret = FALSE, px = pchar.Locx, py = pchar.Locy\n   Dim As Integer dist\n        \n    dist = CalcDist(pchar.Locx, tx, pchar.Locy, ty)\n    If dist \x3C= vh Then\n        ret = _LineOfSight(tx, ty, px, py)\n    End If\n    \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nThe first thing we do here is to calculate the distance to the tile we are examining. If it is greater than the vertical distance of the display, then we don\'t check it. That is, we are giving our character a vision range of vh which is the limit that our character can see. We don\'t necessarily have to do this calculation, since we are defining the FOV of the character by creating our viewport. However, we may want to add races to the game, and an Elf may be able to see farther than Gnome, and we can simply replace vh with the vision attribute of the character. The distance calculation is the fast version of the standard distance formula and does not make a noticeable impact on performance. /CalcDist/ is contained within /utils.bi/.\n\nIf the tile is in within the vision range of the character, we do the line of sight calculation by casting a ray from the tile to the character.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Bresenhams line algo\nFunction levelobj._LineOfSight(x1 As Integer, y1 As Integer, x2 As Integer, y2 As Integer) As Integer\n    Dim As Integer i, deltax, deltay, numtiles\n    Dim As Integer d, dinc1, dinc2\n    Dim As Integer x, xinc1, xinc2\n    Dim As Integer y, yinc1, yinc2\n    Dim isseen As Integer = TRUE\n    \n    deltax = Abs(x2 - x1)\n    deltay = Abs(y2 - y1)\n\n    If deltax \x3E= deltay Then\n        numtiles = deltax + 1\n        d = (2 * deltay) - deltax\n        dinc1 = deltay Shl 1\n        dinc2 = (deltay - deltax) Shl 1\n        xinc1 = 1\n        xinc2 = 1\n        yinc1 = 0\n        yinc2 = 1\n    Else\n        numtiles = deltay + 1\n        d = (2 * deltax) - deltay\n        dinc1 = deltax Shl 1\n        dinc2 = (deltax - deltay) Shl 1\n        xinc1 = 0\n        xinc2 = 1\n        yinc1 = 1\n        yinc2 = 1\n    End If\n\n    If x1 \x3E x2 Then\n        xinc1 = - xinc1\n        xinc2 = - xinc2\n    End If\n    \n    If y1 \x3E y2 Then\n        yinc1 = - yinc1\n        yinc2 = - yinc2\n    End If\n\n    x = x1\n    y = y1\n    \n    For i = 2 To numtiles\n      If _BlockingTile(x, y) Then\n        isseen = FALSE\n        Exit For\n      End If\n      If d \x3C 0 Then\n          d = d + dinc1\n          x = x + xinc1\n          y = y + yinc1\n      Else\n          d = d + dinc2\n          x = x + xinc2\n          y = y + yinc2\n        End If\n    Next\n    \n    Return isseen\nEnd Function\n}}}\n\x3C/div\x3E\nAs you can see we are simply using Bresenham\'s line algorithm. There are numerous tutorials on this algorithm on the Internet, so Google Bresenham if you would like a detailed explanation of the process. We cast from the tile to the character rather than from the character to the tile so that we have symmetrical LOS. This helps prevent the peeking-around-the-corner problem that can occur at times.\n\nIn order to determine if a tile in the ray blocks line of sight, we check the tile against our blocking list.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Returns True if tile is blocking tile.\nFunction levelobj._BlockingTile(tx As Integer, ty As Integer) As Integer\n   Dim ret As Integer = FALSE\n   Dim tid As terrainids = _level.lmap(tx, ty).terrid\n   \n   \'If tile contains a monster it is blocking.\n   If _level.lmap(tx, ty).hasmonster = TRUE Then\n      ret = TRUE\n   Else\n      \'Make sure the pointer was initialzed.\n      If _blockingtiles \x3C\x3E NULL Then\n         \'Look for the current tile in the list.\n         For i As Integer = 0 To _blocktilecnt - 1\n            \'Found it so must be blocking.\n            If _blockingtiles[i] = tid Then\n               ret = TRUE\n               Exit For\n            EndIf\n         Next\n      End If\n   EndIf\n    Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nFirst we check to see if the tile has a monster in it, and if so, we mark it blocked. We could skip this step, but this adds a bit of challenge to the game and forces the player to use a bit of strategy because they don\'t know what\'s behind the monster they do see.  Is there another monster waiting for them or nothing at all? Of course this may seem silly if the monster is a common rat, so we can always add a blocking attribute to a monster that we can check to see if they block line of sight or not. We will think about this some more when we get to adding monsters to the game.\n\nIf the tile is free of monsters, we check our blocking list to see if the tile blocks line of sight. A wall and closed door would block line of sight. As we progress in the development process, we can always add more items to the list. We are using a pointer array here simply because we don\'t have a complete set of blocking tiles at this stage of development. A pointer array acts like a variable length array and makes updating the code a bit easier.\n\nThe main benefit of using Bresenham\'s line algorithm is that it is very fast, but it does have the problem of producing artifacts in the display. It uses Integer math and sometimes can\'t detect the wall tiles which normally you would think should be visible. This happens because the algorithm never reaches the walls tiles due to the Integer math. We can fix that by running a post processing step on the map.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n    \'Post process the map to remove artifacts.\n    For i = x1 To x2\n        For j = y1 To y2\n            If (_BlockingTile(i, j) = TRUE) And (_level.lmap(i, j).visible = FALSE) Then\n                x = i\n                y = j - 1\n                If (x \x3E 0) And (x \x3C mapw + 1) Then\n                    If (y \x3E 0) And (y \x3C maph + 1) Then\n                        If (_level.lmap(x, y).terrid = tfloor) And (_level.lmap(x, y).visible = TRUE) Then\n                            _level.lmap(i, j).visible = TRUE\n                            _level.lmap(i, j).seen = TRUE\n                        EndIf\n                    EndIf\n                EndIf \n\n}}}\n\x3C/div\x3E\nThis is only part of the post processing code. The rest of the code is the same as this, it only checks different cases. It looks a bit confusing, but most of it just checks to make sure that the tile being examined is within the map area.\n\nWe iterate through the viewport as before. For the current tile, we check to see if it is a tile that blocks line of sight and is invisible, {{{If (BlockingTile(i, j) = TRUE) And (level.lmap(i, j).visible = FALSE Then}}}. We then check the tile next to it to see if it is visible, in this case the tile directly below the invisible tile {{{y = j - 1}}}. If that tile (the one below the invisible tile) is visible and a floor tile, we make the invisible blocking tile visible {{{level.lmap(i, j).visible = TRUE}}}. \n\nThis particular case handles the situation where a wall runs horizontally and the floor tiles next to the wall have been been marked as visible, but the wall tiles are marked invisible since the LOS calculation couldn\'t get to them. This can happen in a horizontal hallway if the character is standing in the middle of the hallway. This makes the whole wall visible as you would expect it to be. The rest of the post processing code works the same way, it just checks the various other situations where the LOS might have missed a tile during processing.\n\nOnce the LOS calculation is complete, we can display our map.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Draws the map on the screen.\nSub levelobj.DrawMap ()\n   Dim As Integer i, j, w = vw, h = vh, x, y, px, py, pct\n   Dim As UInteger tilecolor, bcolor\n   Dim As String mtile\n   Dim As terrainids tile\n   \n    _CalcLOS\n    \'Get the view coords\n    i = pchar.Locx - (w / 2)\n    j = pchar.Locy - (h / 2)\n    If i \x3C 1 Then i = 1\n    If j \x3C 1 Then j = 1\n    If i + w \x3E mapw Then i = mapw - w\n    If j + h \x3E mapw Then j = mapw - h\n    \'Draw the visible portion of the map.\n     For x = 1 To w\n         For y = 1 To h\n             \'Clears current location to black.\n             tilecolor = fbBlack \n             PutText acBlock, y, x, tilecolor\n             \'Print the tile.\n             If _level.lmap(i + x, j + y).visible = True Then\n                 \'Get tile id\n                 tile = _level.lmap(i + x, j + y).terrid\n                 \'Get the tile symbol\n                 mtile = _GetMapSymbol(tile)\n                 \'Get the tile color\n                 tilecolor = _GetMapSymbolColor(tile)\n                 \'Print the item marker.\n                 If _level.lmap(i + x, j + y).hasitem = True Then\n                    \'Item info here.\n                 EndIf\n                PutText mtile, y, x, tilecolor\n                 \'If the current location has a monster print that monster.\n                 If _level.lmap(i + x, j + y).hasmonster = TRUE Then\n                    \'Put monster info here.\n                 EndIf\n             Else\n                \'Not in los.\n                If _level.lmap(i + x, j + y).seen = TRUE Then\n                    If _level.lmap(i + x, j + y).hasitem = True Then\n                        PutText "?", y, x, fbSlateGrayDark\n                    Else\n                        PutText mtile, y, x, fbSlateGrayDark\n                    End If\n                End If\n             End If\n         Next \n     Next\n   \'Draw the player\n    px = pchar.Locx - i\n    py = pchar.Locy - j\n    pct = Int((pchar.CurrHP / pchar.MaxHP) * 100) \n    If pct \x3E 74 Then\n        PutText acBlock, py, px, fbBlack\n        PutText "@", py, px, fbGreen\n    ElseIf (pct \x3E 24) AndAlso (pct \x3C 75) Then\n        PutText acBlock, py, px, fbBlack\n        PutText "@", py, px, fbYellow\n    Else\n        PutText acBlock, py, px, fbBlack\n        PutText "@", py, px, fbRed\n    EndIf\n\nEnd Sub\n}}}\n\x3C/div\x3E\nThe first thing we do here is to calculate the LOS and then create our viewport. We then iterate though the viewport, looking at each tile and if it is visible, we get the tile icon and the tile color and draw it on the screen. We are using a new function here, PutText to draw the tiles.\n\n\x3Cdiv class="code"\x3E\n/utils.bi/\n{{{\n\'Writes text at specified row and column.\nSub PutText(txt As String, row As Integer, col As Integer, fcolor As UInteger = fbWhite)\n    Dim As Integer x, y\n    \n    x = (col - 1) * charw\n    y = (row - 1) * charh\n    Draw String (x, y), txt, fcolor\nEnd Sub\n}}}\n\x3C/div\x3E\nThis subroutine converts a text row and column to a pixel location and draws the string at that location. The viewport dimensions are screen text dimensions, that is columns and rows, so we use PutText to draw our map much like we would do if we were using the /Locate/ statement.\n\nYou will notice that the DrawMap routine has placeholders for monsters and items. We will come to this code once we get to that stage of the program. For now, we don\'t have any monsters or items, so we merely draw the empty dungeon.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n...\n\'Not in los.\nIf _level.lmap(i + x, j + y).seen = TRUE Then\n    If _level.lmap(i + x, j + y).hasitem = True Then\n        PutText "?", y, x, fbSlateGrayDark\n    Else\n        PutText mtile, y, x, fbSlateGrayDark\n    End If\nEnd If\n...\n}}}\n\x3C/div\x3E\nThis section of code represents the "memory" of the character. The tile currently isn\'t in line of sight of the character, but they have seen the tile so we draw it in a different color and put a /?/ where an item icon would normally be. This would be valid for a lit dungeon, but for a dark dungeon you would skip over this section and just draw the tiles that are within the LOS and are illuminated by a torch or lantern. Remember that distance calculation we do in the CalcLOS subroutine? We can use that for the illumination range of a torch so we will revisit that subroutine when we get to our item code.\n\nThe last bit of code simply draws the character icon /@/.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n   \'Draw the player\n    px = pchar.Locx - i\n    py = pchar.Locy - j\n    pct = Int((pchar.CurrHP / pchar.MaxHP) * 100) \n    If pct \x3E 74 Then\n        PutText acBlock, py, px, fbBlack\n        PutText "@", py, px, fbGreen\n    ElseIf (pct \x3E 24) AndAlso (pct \x3C 75) Then\n        PutText acBlock, py, px, fbBlack\n        PutText "@", py, px, fbYellow\n    Else\n        PutText acBlock, py, px, fbBlack\n        PutText "@", py, px, fbRed\n    EndIf\n}}}\n\x3C/div\x3E\nThe color of the character icon depends on the current health of the character. We calculate a percentage of health based on the current HP and the maximum HP and then select the color based on that percentage. By using a percentage value, we don\'t care what the absolute vales are, just the ratio of the values. If the character is in good health, the icon is green. If the character is near death, the icon will be red. This gives a constant visual clue on the health of the character and adds to the tension of the game as the player sees his hero go from green to yellow to red.\n\nThe last thing we need to look at in our dungeon object is the /Constructor/ and /Destructor/ code.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Initializes object.\nConstructor levelobj ()\n   \'Set the number of blocking tiles.\n   _blocktilecnt = 3\n   \'Set up block tile list.\n   _blockingtiles = Callocate(_blocktilecnt * SizeOf(Integer))\n   \'Add blocking tiles to list.\n   _blockingtiles[0] = twall\n   _blockingtiles[1] = tdoorclosed\n   _blockingtiles[2] = tstairup\nEnd Constructor\n\n\'Cleans up object.\nDestructor levelobj ()\n   If _blockingtiles \x3C\x3E NULL Then\n      DeAllocate _blockingtiles\n      _blockingtiles = NULL\n   EndIf\nEnd Destructor\n}}}\n\x3C/div\x3E\nThe Constructor is run when the object is created and it sets up our blocking tile list. We are using a pointer here since we may want to add items to the list, maybe a statue terrain type for example, and this makes it easy to create a variable length array in our Type Def. We could of course just use a fixed array, but having a pointer array is a good example of creating a variable length array in a Type Def.\n\nThe Destructor is called when the object goes out of scope and is destroyed. Here we just check to check to make sure that the pointer is valid, and if so, we clear the allocated memory. It is always good practice to set a cleared pointer to NULL so that you know the pointer is invalid.\n\nWe have also added a few new properties to our character object to support the map building process.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n\'Character object.\nType character\n   Private:\n   _cinfo As characterinfo\n   Public:\n   Declare Property CharName() As String  \'Character name. \n   Declare Property Locx(xx As Integer)   \'Sets X coord of character.\n   Declare Property Locx() As Integer     \'Returns the X coord of character.\n   Declare Property Locy(xx As Integer)   \'Sets X coord of character.\n   Declare Property Locy() As Integer     \'Returns the X coord of character.\n   Declare Property CurrHP(hp As Integer) \'Sets the hp.\n   Declare Property CurrHP() As Integer   \'Returns the current HP.\n   Declare Property MaxHP() As Integer   \'Returns the current HP.\n   Declare Sub PrintStats ()\n   Declare Function GenerateCharacter() As Integer\nEnd Type\n}}}\n\x3C/div\x3E\nThe /Locx/ and /Locy/ properties set and return the current x and y location of the character. We will need to set the value when we get to character movement, and we need to get current x and y values to build a viewport centered on our character. The /CurrHP/ and /MaxHP/ you saw in the /DrawMap/ routine. CurrHP has both a setter and getter since that value will change due to combat or when a healing herb is consumed. The MaxHP is a calculated value so we only need a getter for that field. We will never actually set that value outside the internal calculation.\n\nThis is our first pass at our dungeon generator. We will be visiting the dungeon generation code again as we expand the capabilities of our game, but first we need to be able to explore the dungeon just to make sure everything is working as expected. It is hard to tell if our dungeon generator is working correctly when we can\'t move around the dungeon. This means we need to implement some movement code for our character and we\'ll do that in the next chapter.\n',
'= 9: Moving Around\n\x3Cdiv align="center"\x3E\x3Cimg src=\'images/moving.png\' /\x3E\x3C/div\x3E\n\nWe have reached a milestone in the development of /Dungeon of Doom/ with the addition of movement code for our character. We can move around the dungeon, we can open doors, we can even go down the level stairs and explore a new level of the dungeon. The dungeon is empty at this stage, of course, but DOD is starting to look more like a real game.\n\n\x3Cstrong\x3EFixing Bugs\x3C/strong\x3E\n\nBefore we get into the actual movement code, we need to go over some corrections that were needed in the previous game code. As I mentioned in the last chapter, the movement code would likely show us if any bugs had crept into the dungeon generation code, and one did so we will take a look at the problem and the correction. This book is about real world development, and these type of problems are part of the real world.\n\nThe bug was a simple one in the /DrawMap/ code. The old code looked like this:\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n     \'Draw the visible portion of the map.\n     For x = 1 To w\n         For y = 1 To h\n             \'Clears current location to black.\n             tilecolor = fbBlack \n             PutText acBlock, y, x, tilecolor\n             \'Print the tile.\n             If _level.lmap(i + x, j + y).visible = True Then\n                 \'Get tile id\n                 tile = _level.lmap(i + x, j + y).terrid\n                 \'Get the tile symbol\n                  mtile = _GetMapSymbol(tile)\n                 \'Get the tile color\n                 tilecolor = _GetMapSymbolColor(tile)\n                 \'Print the item marker.\n                 If _level.lmap(i + x, j + y).hasitem = True Then\n                    \'Item info here.\n                 EndIf\n                PutText mtile, y, x, tilecolor\n                 \'If the current location has a monster print that monster.\n                 If _level.lmap(i + x, j + y).hasmonster = TRUE Then\n                    \'Put monster info here.\n                 EndIf\n             Else\n                \'Not in los.\n                If _level.lmap(i + x, j + y).seen = TRUE Then\n                    If _level.lmap(i + x, j + y).hasitem = True Then\n                        PutText "?", y, x, fbSlateGrayDark\n                    Else\n                        PutText mtile, y, x, fbSlateGrayDark\n                    End If\n                End If\n             End If\n         Next \n     Next\n}}}\n\x3C/div\x3E\nNotice where we put the two functions /GetMapSymbol/ and /GetMapSymbolColor/--inside the LOS code. What happens when the code drops down to the /Else/ clause? It isn\'t picking up the actual tile of the map, it is using what ever was left over in the variable /mtile/. The fix here was easy: just move the two functions outside the /If/ statement, and everything worked as expected.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n     \'Draw the visible portion of the map.\n     For x = 1 To w\n         For y = 1 To h\n             \'Clears current location to black.\n             tilecolor = fbBlack \n             PutText acBlock, y, x, tilecolor\n             \'Get tile id\n             tile = _level.lmap(i + x, j + y).terrid\n             \'Get the tile symbol\n             mtile = _GetMapSymbol(tile)\n             \'Get the tile color\n             tilecolor = _GetMapSymbolColor(tile)\n             \'Print the tile.\n             If _level.lmap(i + x, j + y).visible = True Then\n                 \'Print the item marker.\n                 If _level.lmap(i + x, j + y).hasitem = True Then\n                    \'Item info here.\n                 EndIf\n                PutText mtile, y, x, tilecolor\n                 \'If the current location has a monster print that monster.\n                 If _level.lmap(i + x, j + y).hasmonster = TRUE Then\n                    \'Put monster info here.\n                 EndIf\n             Else\n                \'Not in los.\n                If _level.lmap(i + x, j + y).seen = TRUE Then\n                    If _level.lmap(i + x, j + y).hasitem = True Then\n                        PutText "?", y, x, fbSlateGrayDark\n                    Else\n                        PutText mtile, y, x, fbSlateGrayDark\n                    End If\n                End If\n             End If\n         Next \n     Next\n}}}\n\x3C/div\x3E\n\x3Cstrong\x3EMoving Our Character\x3C/strong\x3E\n\nIn order to move around the map we need to get the keyboard input from the player. For DOD we are going to use both the arrow keys and the number pad for movement keys. The number pad should have the NUM Lock on, so we are reading the numbers rather than the control keys. This is a note that we will want to add to the help file for the game. Using the number pad allows us to get a key command for each point on the compass, while using the arrows keys allows quick movement through the dungeon. Some games implement key mappings like the VI key set, but we are going to keep things simple by using the arrows and number pad. One enhancement to the game would be to allow the player to define their own key set, but we are not going to implement key mappings in this version of the game. \n\nThe program logic here is quite simple. After getting the player selections from the menu, we build the first level of the dungeon and display it, and then enter a loop gathering key presses. If the player presses a direction key, then we attempt to move the character. If the player presses the "down stairs" key, we generate a new level of the dungeon.  Finally, if the player presses the Escape key, we exit the loop and program.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Main game loop.\nIf mm \x3C\x3E mmenu.mQuit Then\n    \'Build the first level of dungeon\n    level.GenerateDungeonLevel\n    \'Display the main screen.\n    DrawMainScreen\n    Do\n        ckey = InKey\n        If ckey \x3C\x3E "" Then\n            \'Get direction key from numpad or arrows.\n            \'Check for up arrow or 8\n            If (ckey = key_up) OrElse (ckey = "8") Then\n                mret = MoveChar(north)\n                If mret = TRUE Then DrawMainScreen\n            EndIf\n            \'Check for 9\n            If ckey = "9" Then\n                 mret = MoveChar(neast)\n                If mret = TRUE Then DrawMainScreen\n            EndIf\n            \'Check for right arrow or 6.\n            If (ckey = key_rt) OrElse (ckey = "6") Then\n                mret = MoveChar(east)\n                If mret = TRUE Then DrawMainScreen\n            EndIf\n            \'Check for 3\n            If ckey = "3" Then\n                mret = MoveChar(seast)\n                If mret = TRUE Then DrawMainScreen\n            EndIf\n            \'Check for down arrow or 2.\n            If (ckey = key_dn) OrElse (ckey = "2") Then\n                mret = MoveChar(south)\n                If mret = TRUE Then DrawMainScreen\n            EndIf\n            \'Check for 1\n            If ckey = "1" Then\n                mret = MoveChar(swest)\n                If mret = TRUE Then DrawMainScreen\n            EndIf\n            \'Check for left arrow or 4.\n            If (ckey = key_lt) OrElse (ckey = "4") Then\n                mret = MoveChar(west)\n                If mret = TRUE Then DrawMainScreen\n            EndIf\n            \'Check for 7\n            If ckey = "7" Then\n                mret = MoveChar(nwest)\n                If mret = TRUE Then DrawMainScreen\n            EndIf\n            \'Check for down stairs.\n            If ckey = "\x3E" Then\n                \'Check to make sure on down stairs.\n                If level.GetTileID(pchar.Locx, pchar.Locy) = tstairdn Then\n                    \'Build a new level.\n                    level.GenerateDungeonLevel\n                    \'Draw Screen.\n                    DrawMainScreen\n                End If\n            EndIf\n        End if\n      Sleep 1\n   Loop Until ckey = key_esc\nEndIf\n}}}\n\x3C/div\x3E\nFirst we check to see if a key was pressed. /Inkey/ returns a string if a key was pressed or an empty string if there is no key waiting in the key buffer. If there is not a key waiting to be processed, we skip the key section of the code. If a key was pressed however, we need to see what key was pressed and take the appropriate action. We do this with a number of /If/ statements.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n...\n            \'Check for up arrow or 8\n            If (ckey = key_up) OrElse (ckey = "8") Then\n                mret = MoveChar(north)\n                If mret = TRUE Then DrawMainScreen\n            EndIf\n...\n}}}\n\x3C/div\x3E\nHere is the code that checks for an up arrow or the number 8 on the number pad. All the other direction keys work in the same manner. If an up arrow or the 8 key is pressed, we execute the /MoveChar/ function to attempt to move the character. The parameter to MoveChar is a compass direction, in this case /North/ and the return value will be either /True/, the character moved successfully, or /False/ the character couldn\'t move. If the character didn\'t move we don\'t need to update the display; this will save some computing time and make the program a bit more responsive. Let\'s take a look at the MoveChar function.\n\n\x3Cdiv class="code"\x3E\n/commands.bi/\n{{{\n\'Move the character based on the compass direction.\nFunction MoveChar(comp As compass) As Integer\n   Dim As Integer ret = FALSE, block\n   Dim As vec vc = vec(pchar.Locx, pchar.Locy) \'Creates a vector object.\n   Dim As terrainids tileid\n   \n   vc+= comp\n   \'Check to make sure we don\'t move off map.\n   If (vc.vx \x3E= 1) And (vc.vx \x3C= mapw) Then\n      If (vc.vy \x3E= 1) And (vc.vy \x3C= maph) Then\n         \'Check for blocking tile.\n         block = level.IsBlocking(vc.vx, vc.vy)\n         \'Move character.\n         If block = FALSE Then\n            \'Set the new character position.\n            pchar.Locx = vc.vx\n            pchar.Locy = vc.vy\n            ret = TRUE\n         Else \'Check for special tiles.\n            \'Get tile id.\n            tileid = level.GetTileID(vc.vx, vc.vy)\n            Select Case tileid\n               Case tdoorclosed \'Check for closed door.\n                  ret = OpenDoor(vc.vx, vc.vy)\n                  \'If false then print message.\n                  If ret = FALSE Then\n                     \'print message here.\n                  Else\n                     \'Set the new character position.\n                     pchar.Locx = vc.vx\n                     pchar.Locy = vc.vy\n                     ret = TRUE\n                  EndIf\n               Case tstairup \'Enable move onto up stairs.\n                  \'Set the new character position.\n                  pchar.Locx = vc.vx\n                  pchar.Locy = vc.vy\n                  ret = TRUE\n            End Select\n         EndIf\n      EndIf\n   EndIf\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nThe first thing you will notice is that we are using a vector object in the code. The vector object, which we will examine in a moment, simplifies the coordinate calculations. The /+=/ operator of the vector object has been overloaded to accept a compass direction which will automatically give us a new set of coordinates for the desired direction. Once we have the new coordinates, we check to make sure the new coordinates are inside the map array, and then we check to see if the new coordinates point to a blocking tile. The function /IsBlocking/ is a new function we have added to our map object.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Returns true of tile at x, y is blocking.\nFunction levelobj.IsBlocking(x As Integer, y As Integer) As Integer\n   Return _BlockingTile(x, y)         \nEnd Function\n}}}\n\x3C/div\x3E\nAs you can see it simply calls the Private function /BlockingTile/ to return /True/ if the tile is blocking, or /False/ if not. We use this return value to see if the new coordinate is passable to the character. If it is, we simply update the character location and return /True/ indicating that the player moved successfully.\n\n\x3Cdiv class="code"\x3E\n/commands.bi/\n{{{\n...\n         \'Move character.\n         If block = FALSE Then\n            \'Set the new character position.\n            pchar.Locx = vc.vx\n            pchar.Locy = vc.vy\n            ret = TRUE\n...\n}}}\n\x3C/div\x3E\nSo far so good. In the case of a blocking tile though, we need to check for a couple of exceptions: the stairs up and a closed door.\n\n\x3Cdiv class="code"\x3E\n/commands.bi/\n{{{\n...\n         Else \'Check for special tiles.\n            \'Get tile id.\n            tileid = level.GetTileID(vc.vx, vc.vy)\n            Select Case tileid\n               Case tdoorclosed \'Check for closed door.\n                  ret = OpenDoor(vc.vx, vc.vy)\n                  \'If false then print message.\n                  If ret = FALSE Then\n                     \'print message here.\n                  Else\n                     \'Set the new character position.\n                     pchar.Locx = vc.vx\n                     pchar.Locy = vc.vy\n                     ret = TRUE\n                  EndIf\n               Case tstairup \'Enable move onto up stairs.\n                  \'Set the new character position.\n                  pchar.Locx = vc.vx\n                  pchar.Locy = vc.vy\n                  ret = TRUE\n            End Select\n         EndIf\n...\n}}}\n\x3C/div\x3E\nIn order to determine if the blocking tile is an exception tile, we need to get the tile id. The /GetTileID/ is another new function that we have added to our map object.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Returns the tile id at x, y.\nFunction levelobj.GetTileID(x As Integer, y As Integer) As terrainids\n   Return _level.lmap(x, y).terrid         \nEnd Function\n}}}\n\x3C/div\x3E\nThis function accesses the Private map array and returns the tile id at the passed x and y coordinate. We use this value in MoveChar to determine if the tile is an exception by using a /Select Case/ statement block. There are two cases we are interested in, a closed door, indicated by a tile id of /tdoorclosed/ and the stairs up indicated by the id /tstairup /. We will look at the closed door code in a moment, but let\'s handle the easy case first, the stairs up. \n\nEven though the stairs up is marked as blocking, which will block line of sight, we want the character to be able to move onto the tile so that they can go up to the previous level if they desire. To do this, we simply update the character\'s location and return /True/ indicating that the character moved.\n\nFor closed doors, we want the door to open automatically if the door isn\'t locked. It would be quite tedious for the player to press "o" to open a door each time the player came to a door, and most people won\'t play a tedious game for long. To make things easier for the player, we will just open the door for them when they bump into it. This means though, we need to check to see if the door is locked or unlocked, and open the unlocked door. We do this in the /OpenDoor/ function.\n\n\x3Cdiv class="code"\x3E\n/commands.bi/\n{{{\n\'Opens a closed door if not locked.\nFunction OpenDoor (x As Integer, y As Integer) As Integer\n   Dim As Integer ret = TRUE, doorlocked\n   \n  \'Check for locked door.\n   doorlocked = level.IsDoorLocked(x, y)\n   If doorlocked = FALSE Then\n      \'Open the door.\n      level.SetTile x, y, tdooropen\n   Else\n      \'Door is locked and cannot be opened.\n      ret = FALSE\n   End If\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nTo check for a locked door we use the new map function /IsDoorLocked/.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Returns True if door is locked.\nFunction levelobj.IsDoorLocked(x As Integer,y As Integer) As Integer\n   Return _level.lmap(x, y).doorinfo.locked\nEnd Function\n}}}\n\x3C/div\x3E\nAs you can see we are calling a new structure in our map object, /doorinfo/. The following code shows the new door structure information.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Door type.\nType doortype\n   locked As Integer   \'True if locked.\n   lockdr As Integer   \'Lock pick difficulty.\n   dstr As Integer     \'Strength of door (for bashing).\nEnd Type\n}}}\n\x3C/div\x3E\nThis is the door Type Def that holds the information for the door. If the door is locked, /locked/ will be /True/ and /False/ if unlocked. The /lockdr/ is the difficulty of the lock and will be used when the character tries to pick the lock. We will come back to this field when we implement lock picking. The final field in the Type Def, /dstr/ is the strength of the door. This is used to measure how strong the door is when the character attempts to bash open a door. We will revisit this field as well when we implement bashing. We use this door Type Def in our /mapinfo/ Type Def.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Map info type\nType mapinfotype\n    terrid As terrainids  \'The terrain type.\n    hasmonster As Integer \'Current cell has a monster in it.\n    hasitem As Integer    \'Current cell has an item in it.\n    monidx As Integer     \'Index into monster array.\n    visible As Integer    \'Character can see cell.\n    seen As Integer       \'Character has seen cell.\n    doorinfo As doortype  \'Door information.\nEnd Type\n}}}\n\x3C/div\x3E\nThe /doorinfo/ field is defined as our doortype Type Def. As we have already seen, the mapinfotype is part of the map array in the /levelinfo/ Type Def.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Dungeon level information.\nType levelinfo\n   numlevel As Integer \'Current level number.\n   lmap(1 To mapw, 1 To maph) As mapinfotype \'Map array.\nEnd Type\n}}}\n\x3C/div\x3E\nThe reason we want to place the door information at this level of the map object is so we can save and restore the information when we code the save and load routines. By putting this information in the levelinfo Type Def, we will be able to save and load all the information of the level as a unit.\n\nIn order for the doors to work properly, we need to set the door information when we create a door in the level generation process. However, before we do that, we need to clear the door information when we clear the level in our map generation subroutine.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Generate a new dungeon level.\nSub levelobj.GenerateDungeonLevel()\n    Dim As Integer x, y\n\n    \'Clear level\n    For x = 1 To mapw\n        For y = 1 To maph\n            \'Set to wall tile\n            _level.lmap(x, y).terrid = twall\n            _level.lmap(x, y).visible = FALSE\n            _level.lmap(x, y).seen = FALSE\n            _level.lmap(x, y).hasmonster = FALSE\n            _level.lmap(x, y).hasitem = FALSE\n            _level.lmap(x, y).doorinfo.locked = FALSE\n            _level.lmap(x, y).doorinfo.lockdr = 0\n            _level.lmap(x, y).doorinfo.dstr = 0\n        Next\n    Next\n    _InitGrid\n    _DrawMapToArray\nEnd Sub\n}}}\n\x3C/div\x3E\nEach field in the /doorinfo/ Type Def is cleared along with the rest of the level. Now when we add doors, we have a clean set of doors we can update with the necessary door information.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Add doors to a room.\nSub levelobj._AddDoorsToRoom(i As Integer)\n    Dim As Integer row, col, dd1, dd2\n    \n    \'Iterate along top room.\n    For col = _rooms(i).tl.x To _rooms(i).br.x\n        dd1 = _rooms(i).tl.y\n        dd2 = _rooms(i).br.y\n        \'If a floor space in the wall.\n        If _level.lmap(col, dd1).terrid = tfloor Then\n            \'Add door.\n            _level.lmap(col, dd1).terrid = tdoorclosed\n            _level.lmap(col, dd1).doorinfo.locked = FALSE\n            If _level.lmap(col, dd1).doorinfo.locked = TRUE Then\n               _level.lmap(col, dd1).doorinfo.lockdr = 0\n               _level.lmap(col, dd1).doorinfo.dstr = 0\n            End If\n        EndIf\n        \'Iterate along bottom of room.\n        If _level.lmap(col, dd2).terrid = tfloor Then\n            _level.lmap(col, dd2).terrid = tdoorclosed\n            _level.lmap(col, dd2).doorinfo.locked = FALSE\n            If _level.lmap(col, dd2).doorinfo.locked = TRUE Then\n               _level.lmap(col, dd2).doorinfo.lockdr = 0\n               _level.lmap(col, dd2).doorinfo.dstr = 0\n            End If\n        End If\n    Next\n    \'Iterate along left side of room.\n    For row = _rooms(i).tl.y To _rooms(i).br.y\n        dd1 = _rooms(i).tl.x\n        dd2 = _rooms(i).br.x\n        If _level.lmap(dd1, row).terrid = tfloor Then\n            _level.lmap(dd1, row).terrid = tdoorclosed\n            _level.lmap(dd1, row).doorinfo.locked = FALSE\n            If _level.lmap(dd1, row).doorinfo.locked = TRUE Then\n               _level.lmap(dd1, row).doorinfo.lockdr = 0\n               _level.lmap(dd1, row).doorinfo.dstr = 0\n            End If\n        End If\n        \'Iterate along right side of room.\n        If _level.lmap(dd2, row).terrid = tfloor Then\n            _level.lmap(dd2, row).terrid = tdoorclosed\n            _level.lmap(dd2, row).doorinfo.locked = FALSE\n            If _level.lmap(dd2, row).doorinfo.locked = TRUE Then\n               _level.lmap(dd2, row).doorinfo.lockdr = 0\n               _level.lmap(dd2, row).doorinfo.dstr = 0\n            End If\n        EndIf\n    Next\n    \nEnd Sub\n}}}\n\x3C/div\x3E\nHere when we add a closed door, we also set the locked state of the door. Right now all the doors are unlocked, but later we will add in some code to set some doors to the locked state. When a door is locked, we also add in some code to set the lock difficulty and door strength. All of this code sets up a particular door in the map. We still need to open the door though, and we do that in our /DoorOpen/ function in commands.bi.\n\n\x3Cdiv class="code"\x3E\n/commands.bi/\n{{{\n\'Opens a closed door if not locked.\nFunction OpenDoor (x As Integer, y As Integer) As Integer\n   Dim As Integer ret = TRUE, doorlocked\n   \n  \'Check for locked door.\n   doorlocked = level.IsDoorLocked(x, y)\n   If doorlocked = FALSE Then\n      \'Open the door.\n      level.SetTile x, y, tdooropen\n   Else\n      \'Door is locked and cannot be opened.\n      ret = FALSE\n   End If\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nIf the /IsDoorLocked/ function returns /False/ then we need to open the door. We do that by setting the tile id to  /tdooropen/ and we do that with one more new function in our map object /SetTile/.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Sets the tile at x, y of map.\nSub levelobj.SetTile(x As Integer, y As Integer, tileid As terrainids)\n   _level.lmap(x, y).terrid = tileid\nEnd Sub\n}}}\n\x3C/div\x3E\nThis just sets the tile at the x and y map coordinate to the passed tileid. Since an open door is a non-blocking tile, once we open the door, the character can move through the tile with no problems and it doesn\'t affect line of sight.\n\nThe OpenDoor function will return /True/ if the door opened successfully or /False/ if the door could not be opened. Getting back to the MoveChar function, we can see the whole process now when the character attempts to open a door.\n\n\x3Cdiv class="code"\x3E\n/commands.bi/\n{{{\nCase tdoorclosed \'Check for closed door.\n    ret = OpenDoor(vc.vx, vc.vy)\n    \'If false then print message.\n    If ret = FALSE Then\n        \'print message here.\n    Else\n        \'Set the new character position.\n        pchar.Locx = vc.vx\n        pchar.Locy = vc.vy\n        ret = TRUE\n    EndIf\n}}}\n\x3C/div\x3E\nThis is just the case where a door is closed. If OpenDoor returns /False/ then a message will be printed informing the player that the door could not be opened, and the player isn\'t moved. MoveChar will return /False/ to the main program indicating failure. If the door can be opened, then the player\'s location is updated and MoveChar returns /True/ indicating success. \n\nAs you can see, adding new features to the game is an iterative process, where we revisit code we have already written to support the new features. It is very important that during this process, we test each change to make sure that we haven\'t introduced bugs into the existing code base. It may seem wasteful to write and then rewrite code, but in reality, it ensures that the program is working correctly, and that when we do find a bug, we know it is in the code we just wrote, so it is much easier to track down these problems and correct them. \n\nIf we sat down and coded all the features at one sitting, and then found out something wasn\'t working correctly, where do we start looking for the problem? It could be in any of the dozen or so features that were implemented, each of which could be many lines of code. It could take a long time to track down the bugs and correct them, and it wouldn\'t be an easy process. In the long run, the iterative process of coding and recoding is both faster and more reliable and will result in a better code base with a lot less frustration.\n\nWe aren\'t quite done yet with the MoveChar code. We still have one more file to look at in our code and that is the vector object we built for the movement code. To remind you, here is some of the vector code in /MoveChar/.\n\n\x3Cdiv class="code"\x3E\n/commands.bi/\n{{{\n   vc+= comp\n   \'Check to make sure we don\'t move off map.\n   If (vc.vx \x3E= 1) And (vc.vx \x3C= mapw) Then\n      If (vc.vy \x3E= 1) And (vc.vy \x3C= maph) Then\n         \'Check for blocking tile.\n         block = level.IsBlocking(vc.vx, vc.vy)\n         \'Move character.\n         If block = FALSE Then\n            \'Set the new character position.\n            pchar.Locx = vc.vx\n            pchar.Locy = vc.vy\n            ret = TRUE\n...\n}}}\n\x3C/div\x3E\nTo get a new vector location, we simply add a compass direction to the object using the overloaded += operator, and then access the x and y fields of the object to get the new coordinates. To see how this works, let\'s look at the vector object code.\n\n\x3Cdiv class="code"\x3E\n/vec.bi/\n{{{\n\'Compass directions\nEnum compass\n    north\n    neast\n    east\n    seast\n    south\n    swest\n    west\n    nwest\nEnd Enum\n\n\'2D Vector type.\nType vec\n    Private:\n    _x As Integer\n    _y As Integer\n    _dirmatrix(north To nwest) As mcoord = {(0, -1), (1, -1), (1, 0), (1, 1), (0, 1), (-1, 1), (-1, 0), (-1, -1)}\n    Public:\n    Declare Constructor ()\n    Declare Constructor (x As Integer, y As Integer)\n    Declare Property vx (x As Integer)\n    Declare Property vx () As Integer\n    Declare Property vy (y As Integer)\n    Declare Property vy () As Integer\n    Declare Operator += (cd As compass)\n    Declare Sub ClearVec()\nEnd Type\n\n\'Empty constructor. Used when part of another object.\nConstructor vec ()\n    _x = 0\n    _y = 0\nEnd Constructor\n\n\'Initialzed vector.\nConstructor vec (x As Integer, y As Integer)\n    _x = x\n    _y = y\nEnd Constructor\n\n\'Properties to set and return the x and y components.\nProperty vec.vx (x As Integer)\n    _x = x\nEnd Property\n\nProperty vec.vx () As Integer\n    Return _x\nEnd Property\n\nProperty vec.vy (y As Integer)\n    _y = y\nEnd Property\n\nProperty vec.vy () As Integer\n    Return _y\nEnd Property\n\n\'Updates x and y using compass direction.\nOperator vec.+= (cd As compass)\n   If (cd \x3E= north) And (cd \x3C= nwest) Then\n      _x += _dirmatrix(cd).x\n      _y += _dirmatrix(cd).y\n   End If\nEnd Operator\n\n\'Sets vector to 0.\nSub vec.Clearvec ()\n    _x = 0\n    _y = 0\nEnd Sub\n}}}\n\x3C/div\x3E\nThe first thing we do is to define a set of compass directions in the Enum /Compass/. This defines every location that an actor can move in the game. We can also use it to examine each tile around an actor, such as when the character is searching a location.\n\nThe vector object has two private members that define the coordinates, x and y, as well as an array, dirmatrix, that defines the offsets for each compass direction. The matrix array is used in the overloaded /+=/ operator.\n\n\x3Cdiv class="code"\x3E\n/vec.bi/\n{{{\n\'Updates x and y using compass direction.\nOperator vec.+= (cd As compass)\n   If (cd \x3E= north) And (cd \x3C= nwest) Then\n      _x += _dirmatrix(cd).x\n      _y += _dirmatrix(cd).y\n   End If\nEnd Operator\n}}}\n\x3C/div\x3E\nThe parameter /cd/ is the compass direction. We check to make sure the compass direction is valid, and then we add the offsets in /dirmatrix/ to the current x and y values of the vector object. The assumption here is that the vector has been intialized to a value, such as the current character location. \n\nAn example will illustrate the operation. In the dirmatrix array North is defined as /(0, -1)/, where 0 is the x value and -1 is the y value. Each value of the matrix is added to the corresponding x and y value of the vector object. In this case, x will remain unchanged since the matrix x value is 0, and the y value is decremented by 1 since the matrix y value is -1. So, we get a new vector by just adding in a compass direction as we saw in the MoveChar function: /vc+= comp/. Overloading the += operator for our object allows us to use the compass direction just like we would any other variable or number in the program, making it very simple to generate a new vector. This is the power of objects, and how they can simplify the coding process.\n\nThe /Constructor/ code can be used to set the initial values of the vector, as we did in the MoveChar function: {{{Dim As vec vc = vec(pchar.Locx, pchar.Locy)}}}. When using this construction we must wrap the parameters in a vec declaration to tell the compiler that we are initializing a vector object. \n\nThe Constructor that has no parameters just sets the x and y components of the vector to 0. The initial values of the vector must then be set using the /vx/ and /vy/ Properties. You would use this construction if, for example, you were going to use the vector object in a loop and needed to initialize the x and y components at the beginning of the loop cycle. We will see this in action when we implement the search functionality.\n\nThe /ClearVec/ subroutine just sets the vector to a 0 state, and is used if we need to clear the vector for any reason. For many objects, a clear routine like this would clear any allocated resources, and it is always good to have a way to reset an object back to an uninitialized state, ready for a new initialization.\n\nThe vector object is rather simple, but as you can see, quite helpful by simplifying the movement code. A general purpose object such as this will be useful in many areas of our code so we will be using this again as we expand the capabilities of our program.\n\nGetting back to our key processing code in dod.bas, the next command we need to handle is going down the stairs to the next level of the dungeon.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n    \'Check for down stairs.\n    If ckey = "\x3E" Then\n        \'Check to make sure on down stairs.\n        If level.GetTileID(pchar.Locx, pchar.Locy) = tstairdn Then\n            \'Build a new level.\n            level.GenerateDungeonLevel\n            \'Draw Screen.\n            DrawMainScreen\n        End If\n    EndIf\n}}}\n\x3C/div\x3E\nHere we are using the /GetTileID/ function to make sure that the character is standing on the down stairs tile. We don\'t want the character just dropping through the floor! If the tile is the down stairs, then we generate a new level and display the starting room of the new level. There is quite a bit of code here to support the character movement, but it is really only the beginning. There is much more to come.\n\nSo, our character can move around and explore the dungeon now, but the main display is rather sparse. We need to fix that, and in the next chapter we will create a proper main display for our game.\n',
'= 10: Main Display\n\x3Cdiv align="center"\x3E\x3Cimg src=\'images/maindisplay.png\' /\x3E\x3C/div\x3E\n\nHere we have the preliminary main display for the game. The idea is to put the most pertinent information on the main display. We don\'t have a huge amount of information, this being a basic roguelike game, so most of it will fit on the display along with the major key mappings. Keep in mind that this is just the preliminary design and subject to change as we move along in the project.\n\nYou will notice that the display is divided into regions. The map is display to the left while the character information is displayed to the right. You could switch the two locations if you wanted, but this format is fairly standard in roguelike games. Along the bottom of the screen we have the message list. The message list will display the last 4 messages that were generated by the program. If the need arises we could display a message log to show more messages, but anything past 4 is usually not necessary.\n\nIn the previous version of the code, the map was displayed starting from character position 1. In order to get a border around the map, we have added an offset to the print statements in /DrawMap/. Here is an example of the change.\n\n\x3Cdiv class="code"\x3E\n/DrawMap:map.bi/\n{{{\nPutText mtile, y + 1, x + 1, tilecolor\n}}}\n\x3C/div\x3E\nAll of the print routines, including the character icon are offset by 1. This pushes the display down and to the right by 1 character space giving us a nice border around the map.\n\nMost of the action for this display happens in the /DrawMainDisplay/ subroutine in /dod.bas/.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Draws the main game screen.\nSub DrawMainScreen ()\n   Dim As Integer x, y, j, pct, row, col\n   Dim As Double pctd\n   Dim As UInteger clr\n   Dim As String txt\n   Dim As terrainids terr\n   \n   ScreenLock\n   level.DrawMap\n   \'Draw the message area.\n   PrintMessage ""\n   \'Draw the information area.\n   y = 2\n   For j = 0 To vh - 1\n      For x = vw + 3 To txcols - 1\n         PutText acBlock, y + j, x, fbBlack\n      Next\n   Next\n   \'Draw the level number.\n   DrawStringShadow charw, 0, "Dungeon Level: " & level.LevelID\n   \'Draw the character name.\n   row = 3\n   col = vw + 4\n   PutText pchar.CharName, row, col, fbYellowBright\n   row += 2 \n   \'Draw the character health bar.\n   pctd = pchar.CurrHP / pchar.MaxHP\n   pct = Int(pctd * 100) \n   If pct \x3E 74 Then\n   	clr = fbGreen\n   ElseIf (pct \x3E 24) And (pct \x3C 75) Then\n   	clr = fbYellow\n   Else\n   	clr = fbRed\n   EndIf\n   \'Build the health bar.\n   txt = String((txcols - 1) - (col + 4), acBlock)\n   PutText "    " & txt, row, col, fbBlack\n   txt = String((txcols - 1) - (col + 4), Chr(176))\n   PutText "    " & txt, row, col, fbGray\n   txt = String(Len(txt) * pctd, acBlock)\n   PutText "    " & txt, row, col, clr\n   PutText "HP: ", row, col\n   \'Draw the health amount.\n   txt = pchar.CurrHP & "/" & pchar.MaxHP\n   DrawStringShadow (txcols - (Len(txt) + 2)) * charw, (row - 1) * charh, txt\n   \'Draw the main stats.\n   row += 2\n   PutText "Stats", row, col, fbYellowBright\n   row += 2\n   If pchar.BonStr \x3E -1 Then\n      txt = " +"\n   Else\n      txt = " -"\n   EndIf\n   PutText "Str: " & pchar.CurrStr & txt & pchar.BonStr, row, col\n   row += 1\n   If pchar.BonSta \x3E -1 Then\n      txt = " +"\n   Else\n      txt = " -"\n   EndIf\n   PutText "Sta: " & pchar.CurrSta & txt & pchar.BonSta, row, col\n   row += 1\n   If pchar.BonDex \x3E -1 Then\n      txt = " +"\n   Else\n      txt = " -"\n   EndIf\n   PutText "Dex: " & pchar.CurrDex & txt & pchar.BonDex, row, col\n   row += 1\n   If pchar.BonAgl \x3E -1 Then\n      txt = " +"\n   Else\n      txt = " -"\n   EndIf\n   PutText "Agl: " & pchar.CurrAgl & txt & pchar.BonAgl, row, col\n   row += 1\n   If pchar.BonInt \x3E -1 Then\n      txt = " +"\n   Else\n      txt = " -"\n   EndIf\n   PutText "Int: " & pchar.CurrInt & txt & pchar.BonInt, row, col\n   row += 1\n   PutText "Curr XP: " & pchar.CurrXP, row, col \n   \n   row += 2\n   PutText "Combat Factors", row, col, fbYellowBright\n   row += 2\n   If pchar.BonUcf \x3E -1 Then\n      txt = " +"\n   Else\n      txt = " -"\n   EndIf\n   PutText "UCF: " & pchar.CurrUcf & txt & pchar.BonUcf, row, col\n   row += 1\n   If pchar.BonAcf \x3E -1 Then\n      txt = " +"\n   Else\n      txt = " -"\n   EndIf\n   PutText "ACF: " & pchar.CurrAcf & txt & pchar.BonAcf, row, col\n   row += 1\n   If pchar.BonPcf \x3E -1 Then\n      txt = " +"\n   Else\n      txt = " -"\n   EndIf\n   PutText "PCF: " & pchar.CurrPcf & txt & pchar.BonPcf, row, col\n   row += 1\n   If pchar.BonMcf \x3E -1 Then\n      txt = " +"\n   Else\n      txt = " -"\n   EndIf\n   PutText "MCF: " & pchar.CurrMcf & txt & pchar.BonMcf, row, col\n   row += 1\n   If pchar.BonCdf \x3E -1 Then\n      txt = " +"\n   Else\n      txt = " -"\n   EndIf\n   PutText "CDF: " & pchar.CurrCdf & txt & pchar.BonCdf, row, col\n   row += 1\n   If pchar.BonMdf \x3E -1 Then\n      txt = " +"\n   Else\n      txt = " -"\n   EndIf\n   PutText "MDF: " & pchar.CurrMdf & txt & pchar.BonMdf, row, col\n   row += 2\n   PutText "Equipment", row, col, fbYellowBright\n   row += 2\n   PutText "Gold: " & pchar.CurrGold, row, col\n   row += 1\n   PutText "RT Hand: ", row, col\n   row += 1\n   PutText "LT Hand: ", row, col\n   row += 1\n   PutText "Armor: ", row, col\n   row += 2\n   PutText "Keys", row, col, fbYellowBright\n   row += 2\n   PutText "Move:.Arrows or Numpad", row, col\n   row += 1\n   PutText "?.....Help", row, col\n   row += 1\n   PutText "v.....Inventory", row, col\n   row += 1\n   PutText "x.....Improve Character", row, col\n   row += 1\n   PutText "l.....Spell Book", row, col\n   row += 1\n   PutText "\x3E.....Down Level", row, col\n   row += 1\n   PutText "\x3C.....Up Level", row, col\n   row += 1\n   PutText "i.....Inspect Tile", row, col\n   row += 1\n   PutText "s.....Search Area", row, col\n   row += 1\n   PutText "t.....Target Enemy", row, col\n   row += 1\n   PutText "r.....Read Scroll", row, col\n   row += 1\n   PutText "c.....Cast Spell", row, col\n   row += 1\n   PutText "e.....Drink/Eat Item", row, col\n   row += 1\n   PutText "g.....Get Item", row, col\n   row += 1\n   PutText "d.....Drop Item", row, col\n   row += 1\n   PutText "p.....Pick Lock", row, col\n   row += 1\n   PutText "b.....Bash Door", row, col\n   \'Check to see if character standing on an item.\n   If level.HasItem(pchar.Locx, pchar.Locy) = TRUE Then\n      \'Get item desc.\n   Else\n      \'See if character is on special tile.\n      terr = level.GetTileID(pchar.Locx, pchar.Locy)\n      If (terr = tstairup) OrElse (terr = tstairdn) Then\n         txt = level.GetTerrainDescription(pchar.Locx, pchar.Locy)\n         PrintMessage txt\n      End If\n   EndIf\n   ScreenUnLock\nEnd Sub\n}}}\n\x3C/div\x3E\nAs you can see, the subroutine draws the map, then draws the information and message spaces, and then fills in all the spaces with the relevant information. We are using row and column variables to position the text so that we can use calculated values rather than absolute values for the position of the text. This makes updating the display quite easy as we can just set the beginning row and column of the text, and then just increment the row as needed to add new lines of text to the display. If we need to move stuff around, we don\'t need to worry about changing row and column numbers, the calculated values do all the work for us.\n\nMost of this code is easy to understand, as it it just printing text to the screen. Most of the information comes from the character object, and we have added a number of properties of the object to expose the character values. Here is a list of new Properties in our character object:\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n   Declare Property CurrHP(hp As Integer)   \'Sets the hp.\n   Declare Property CurrHP() As Integer     \'Returns the current HP.\n   Declare Property MaxHP() As Integer      \'Returns the current HP.\n   Declare Property CurrStr() As Integer    \'Returns the current strength value.\n   Declare Property CurrStr(amt As Integer) \'Sets the current strength value. \n   Declare Property BonStr() As Integer     \'Returns the current strength bonus value..\n   Declare Property BonStr(amt As Integer)  \'Sets the current strength bonus value.\n   Declare Property CurrSta() As Integer    \'Returns the current stamina value.\n   Declare Property CurrSta(amt As Integer) \'Sets the current stamina value. \n   Declare Property BonSta() As Integer     \'Returns the current stamina bonus value..\n   Declare Property BonSta(amt As Integer)  \'Sets the current stamina bonus value.\n   Declare Property CurrDex() As Integer    \'Returns the current dexterity value.\n   Declare Property CurrDex(amt As Integer) \'Sets the current dexterity value. \n   Declare Property BonDex() As Integer     \'Returns the current dexterity bonus value..\n   Declare Property BonDex(amt As Integer)  \'Sets the current dexterity bonus value.\n   Declare Property CurrAgl() As Integer    \'Returns the current agility value.\n   Declare Property CurrAgl(amt As Integer) \'Sets the current agility value. \n   Declare Property BonAgl() As Integer     \'Returns the current agility bonus value..\n   Declare Property BonAgl(amt As Integer)  \'Sets the current agility bonus value.\n   Declare Property CurrInt() As Integer    \'Returns the current intelligence value.\n   Declare Property CurrInt(amt As Integer) \'Sets the current intelligence value. \n   Declare Property BonInt() As Integer     \'Returns the current intelligence bonus value..\n   Declare Property BonInt(amt As Integer)  \'Sets the current intelligence bonus value.\n   Declare Property CurrUcf() As Integer    \'Returns the current unarmed combat value.\n   Declare Property BonUcf() As Integer     \'Returns the current unarmed combat bonus value..\n   Declare Property BonUcf(amt As Integer)  \'Sets the current unarmed combat bonus value.\n   Declare Property CurrAcf() As Integer    \'Returns the current armed combat value.\n   Declare Property BonAcf() As Integer     \'Returns the current armed combat bonus value..\n   Declare Property BonAcf(amt As Integer)  \'Sets the current armed combat bonus value.\n   Declare Property CurrPcf() As Integer    \'Returns the current projectile combat value.\n   Declare Property BonPcf() As Integer     \'Returns the current projctile combat bonus value..\n   Declare Property BonPcf(amt As Integer)  \'Sets the current projectile combat bonus value.\n   Declare Property CurrMcf() As Integer    \'Returns the current magic combat value.\n   Declare Property BonMcf() As Integer     \'Returns the current magic combat bonus value..\n   Declare Property BonMcf(amt As Integer)  \'Sets the current magic combat bonus value.\n   Declare Property CurrCdf() As Integer    \'Returns the current combat defense value.\n   Declare Property BonCdf() As Integer     \'Returns the current combat defense bonus value..\n   Declare Property BonCdf(amt As Integer)  \'Sets the current combat defense bonus value.\n   Declare Property CurrMdf() As Integer    \'Returns the current magic defense value.\n   Declare Property BonMdf() As Integer     \'Returns the current magic defense bonus value..\n   Declare Property BonMdf(amt As Integer)  \'Sets the current magic defense bonus value.\n   Declare Property CurrXP() As Integer     \'Returns the current xp points.\n   Declare Property CurrXP(amt As Integer)  \'Sets the current xp points.\n   Declare Property TotXP() As Integer      \'Returns the total xp points.\n   Declare Property TotXP(amt As Integer)   \'Sets the total total points.\n   Declare Property CurrGold() As Integer     \'Returns the current gold amount.\n   Declare Property CurrGold(amt As Integer)  \'Sets the current gold amount.\n   Declare Property TotGold() As Integer     \'Returns the total gold amount.\n   Declare Property TotGold(amt As Integer)  \'Sets the total gold amount.\n}}}\n\x3C/div\x3E\nThe only thing these Properties do is to expose the Private variables in the object. We will be needing these in other parts of the program, such as combat, so they are going to be used in more than just the display. All of the Properties are coded in a similar manner to the strength stat below.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n\'Returns the current strength value.\nProperty character.CurrStr() As Integer    \n   Return _cinfo.stratt(0)\nEnd Property\n\n\'Sets the current strength value.\nProperty character.CurrStr(amt As Integer) \n   _cinfo.stratt(0) = amt\nEnd Property\n}}}\n\x3C/div\x3E\nWe have also updated our map object with some new methods that we need to display messages. If the character walks on a special tile, such as stairs up or down, then we want to message the fact to the player. This ties the icon on the map to the object it represents helping the player learn the iconography of the game. When we add items to the game, we will follow the same process.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Returns terrain description.\nFunction levelobj._GetTerrainDesc(x As Integer, y As Integer) As String\n	Dim tile As terrainids\n   Dim ret As String\n   \n   \'Must be a tile.\n   tile = _level.lmap(x, y).terrid\n   Select Case tile\n   	Case twall\n   		ret = "Wall"\n      Case tfloor\n   		ret = "Floor"\n      Case tstairup\n   		ret = "Stairs up"\n      Case tstairdn\n   		ret = "Stairs down"\n      Case tdooropen\n   		ret = "Open door"\n   	Case tdoorclosed\n   		ret = "Closed door"\n      Case Else\n         ret = "Uknown"\n   End Select\n   \n   Return ret\nEnd Function\n\n\'Returns item description at coordinate.\nFunction levelobj.GetTerrainDescription(x As Integer, y As Integer) As String\n   Return _GetTerrainDesc(x, y)\nEnd Function\n\n\'Returns True if coordinate has item.\nFunction levelobj.HasItem(x As Integer, y As Integer) As Integer\n   Return _level.lmap(x, y).hasitem   \nEnd Function\n}}}\n\x3C/div\x3E\nThe Public function /GetTerrainDescription/ calls the Private function /GetTerrainDesc/ to return a string of the tile at the x and y coordinate. The /HasItem/ function will return /True/ if the tile has an item on it. These are used in the following section of code in /DrawMainDisplay/.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n   \'Check to see if character standing on an item.\n   If level.HasItem(pchar.Locx, pchar.Locy) = TRUE Then\n      \'Get item desc.\n   Else\n      \'See if character is on special tile.\n      terr = level.GetTileID(pchar.Locx, pchar.Locy)\n      If (terr = tstairup) OrElse (terr = tstairdn) Then\n         txt = level.GetTerrainDescription(pchar.Locx, pchar.Locy)\n         PrintMessage txt\n      End If\n   EndIf\n}}}\n\x3C/div\x3E\nWe have added a placeholder for the items in the message stream. Since there aren\'t any items yet, we examine the current tile that the character is standing on and see if it is a special tile. If it is, we print out a tile description using the subroutine /PrintMessage/.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Prints any messages to screen.\nSub PrintMessage(txt As String)\n   Dim As Integer i, x, y\n   \n   If Len(txt) \x3E 0 Then\n      \'Move all messages down by 1.\n      For i = 3 To 1 Step -1\n         mess(i + 1) = mess(i)\n      Next\n      mess(1) = txt\n   End If\n   \'Clear current messages.\n   ClearMessageArea\n   \'Print out messages.\n   y =  1 + vh + 2\n   x = 3\n   For i = 1 To 4\n      PutText mess(i), y, x, messcolor(i)\n      y += 1\n   Next\nEnd Sub\n}}}\n\x3C/div\x3E\nThere are two arrays that we are using, one for the actual messages, and one a color for each line.\n\n\x3Cdiv class="code"\x3E\n/defs.bi/\n{{{\n\'Message list.\nDim Shared mess(1 To 4) As String\nDim Shared messcolor(1 To 4) As UInteger = {fbWhite, fbWhite1, fbWhite2, fbWhite3}\n}}}\n\x3C/div\x3E\nWe want the main message to be printed in white, with subsequent messages printed in darker shades of gray. This helps show that the top message is the current message in the message list.\n\nWhen a new message is passed to /PrintMessage/ we move down all the messages by 1, and then add the new message in the first index location of the array. This makes printing the messages quite easy as we can just iterate through the array and print the message in the appropriate color.\n\nThe last item in this update is the background which shows around the border of the screen. This is just a converted wood bitmap in /mainback.bi/ which is displayed at the start of the program.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Main game loop.\nIf mm \x3C\x3E mmenu.mQuit Then\n   \'Set the first level.\n   level.LevelID = 1\n	\'Build the first level of dungeon\n	level.GenerateDungeonLevel\n	\'Set the background for the main screen.\n	DrawBackground mainback()\n   \'Display the main screen.\n   DrawMainScreen\n   Do\n...\n}}}\n\x3C/div\x3E\nWe only need to do this once since we are only updating the regions of the map that contain information. This will save some computing time as the display is updated after each character action.\n\nThat is all there is to the main display code. Not a huge update to the program, but we now have a program that is looking like a real game. ',
'= 11: Inventory Structure\nCreating a data structure for a roguelike, or any game, that has to manage inventory items presents an interesting challenge for the programmer. The data structure must be able to accommodate items that have different properties, put those items into collections, and be able to manage those items easily throughout the life of the game. We also need the data structure to be efficient and self-contained so that we do not end up manipulating a whole host of different structures just to manage our inventory. The task sounds near impossible, but actually it is quite simple. We will create a composite data type that we can use to hold our different data items utilizing Type Defs and a Union.\n\nHere is the main inventory type.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Inventory type.\nType invtype\n   classid As classids     \'This indicates what class is in the union.\n   desc As String * 30     \'Plain text description.\n   icon As String * 1      \'This is the item icon.\n   iconclr As UInteger     \'This is the item\'s icon color.\n   Union                   \'Union of item types.\n      gold As goldtype     \'Gold coins. \n      supply As supplytype \'Supplies.\n   End Union\nEnd Type\n}}}\n\x3C/div\x3E\nIt doesn\'t look too bad at all. In fact, it may look too simple to meet our needs. How can this structure fulfill that dire description that started this chapter? The key here is the Union and a little explanation is in order in case you don\'t know what a Union actually does for us.\n\nA Union is a segment of memory that can hold different values, depending on what we put into the Union. The size of the memory segment used by a Union is set to the largest member of the Union. For example, a Union may contain an Integer and 4 Bytes. An Integer is 4 bytes long, so 4 Byte variables fit into a single Integer variable. Whether the Union holds an Integer or 4 Bytes depends on what we have loaded into the Union. The following code (that isn\'t in DOD) illustrates how this works.\n\n\x3Cdiv class="code"\x3E\n/Example of Union/\n{{{\n\'Pixel type.\nType Pixel_Color\n    B As UByte\n    G As Ubyte\n    R As Ubyte\n    A As Ubyte\nEnd Type\n\'Color type.\nUnion Pixel\n    Channel As Pixel_Color\n    Value   As Uinteger\nEnd Union\n}}}\n\x3C/div\x3E\nHere we have a Pixel type composed of 4 UByte variables. This type is used in a Union along with an UInteger value. In the Union the UInteger and the Pixel Type occupy the same memory segment. This means that if we load an RGB value into the Value field, the different bytes of the UInteger are mapped to the Pixel Type. If we wanted the B (for blue) value of the RGB color, we could access the B field of the Type. If we wanted to change the blue color to a different value, we simply set the B field to a different value and can get the new color using the Value field. Changing a field in the Type is the same as changing a byte in Value, and changing the Value will change the different fields in Pixel since they are occupying the same memory segment.\n\nNow, for our inventory system, we don\'t want to do any conversions like this, but we do want to take advantage of the fact that no matter how many different items we have in our inventory list, we are only using a single memory segment. This makes our inventory Type very efficient since the amount of space we are using isn\'t the sum of all the different items Types, just the largest. Not only is it efficient, it is easy to use. Instead of many different Types, we have a single composite Type that can represent many different items. The support routines that we will build will only have to be able to handle this single Type; we don\'t have to write routines to handle all the different item Types we will have in the game. And there is one more advantage to using a Type-Union combination. We can plug in new items into our system, and we don\'t have to change any existing routines since they all work with our single composite Type. This makes updating the inventory system a snap.\n\nLet\'s take a deeper look at our composite Type.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Inventory type.\nType invtype\n   classid As classids     \'This indicates what class is in the union.\n   desc As String * 30     \'Plain text description.\n   icon As String * 1      \'This is the item icon.\n   iconclr As UInteger     \'This is the item\'s icon color.\n   Union                   \'Union of item types.\n      gold As goldtype     \'Gold coins. \n      supply As supplytype \'Supplies.\n   End Union\nEnd Type\n}}}\n\x3C/div\x3E\nSince the Union can hold any of our item Types (which we will look at in a moment), we need to be able to determine what is in our Union. The /classid/ field will tell us what is in our Union.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Item class ids.\nEnum classids\n   clNone\n   clGold\n   clSupplies \n   clPotion\n   clWand\n   clWeapon\n   clArmor\n   clLight\n   clAmmo\n   clShield\n   clScrolls\n   clSpellBook\nEnd Enum\n}}}\n\x3C/div\x3E\nThis is a Enumeration that defines the different item class Types that will have in our game. An item class defines one or more items. The items are grouped into classes since many items will have similar properties and can be represented by a single Type. The /desc/, /icon/, and /iconclr/ fields in the composite Type, contain the description, icon and icon color of the particular class item contained within the Union.\n\nFor each item in the game, we will create a Type Def structure that will contain the necessary data for the item. Right now we only have 2 class types in the game: Gold and Supplies. Let\'s take a look at the Gold items.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Gold item ids.\nEnum goldids\n   gldGoldNone   \'No gold.\n   gldGold       \'Gold coins.\n   gldBagGold    \'Bag of gold.\nEnd Enum\n\n\'Gold type.\nType goldtype\n   id As goldids    \'Type of gold item.\n   amt As Integer   \'Number of gold coins.\nEnd Type\n}}}\n\x3C/div\x3E\nThe goldid indicates what type of gold item is contained within the Type. It can be no gold, gold coins or a bag of gold. The only difference between gold coins and a bag of gold is the amount of gold found. A Bag of Gold will contain more gold than coins. The no gold id is important, because we use that id to reset the id of the Type. Remember that this is contained within the Union, a shared memory space, so we want to manage the items carefully in order not to corrupt the data when we load a different Type value into the Union.\n\nThe supply items are a bit more complicated, but not by much.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Effects ids.\nEnum effectsid\n   effEffectNone  \'No effect.\n   effMaxHealing  \'Heal to max HP.\n   effStrongMeat  \'Adds bonus to Str for count time.\n   effBreadLife   \'Cures any poison.\n   effSeeAll      \'Set all tiles to visible.\nEnd Enum\n\n\'Item use id.\nEnum itemuse\n   useNone      \'No Use\n   useDrinkEat  \'Eat or drink item.\n   useWieldWear \'Wield or wear.\nEnd Enum\n\n\'Supply item ids.\nEnum supplyids\n   supSupplyNone  \'No supply id.\n   supHealingHerb \'Healing herb-50% of max HP healing effect.\n   supHunkMeat    \'25% of max HP healing effect.\n   supBread       \'10% of max HP healing effect.\nEnd Enum\n\n\'Supply type def.\nType supplytype\n   id As supplyids      \'This indicates what supply is in the type.\n   evaldr As Integer    \'Evaluation difficulty rating. Used to evaluate magical effects: 0 = nonMagic.\n   eval As Integer      \'True if item has been evaluated.\n   effect As effectsid  \'The type of magical effect.\n   sdesc As String * 30 \'Secret name/description for magical items. Revealed when evaluated.\n   noise As Integer     \'The amount of noise item generates, includes use and in character inventory.\n   use As itemuse       \'How the item is used.\nEnd Type\n}}}\n\x3C/div\x3E\nThe supplies are all consumable items. That is, you either eat, drink or use them. The eatable and drinkable ones have health benefits. Since these items have a chance of being magic, they have a few more fields to support the magic effects. Each magic effect corresponds to a particular item. /effMaxHealing/ is the magic effect of the Healing Herb, /effStrongMeat/ is the effect of a Hunk of Meat and /effBreadLife/ is the effect of eating magic Bread. \n\nThe /use/ field indicates what you can do with the item, either /useDrinkEat/ or /useWieldWear/ which is used in the command portion of the inventory screen when the player chooses the Eat/Drink or Wield/Wear. We can use this field to filter the display to those items that can be consumed or put on.\n\nThe /evaldr/ field indicates the difficulty of evaluating an item to reveal the magic effects and /eval/ indicates if an item has been successfully evaluated. The magic effects are only active if an item has been successfully evaluated. We will go over the evaluation process when we get to the inventory screen commands.\n\nThe /sdesc/ field is the secret description of the item. It contains the item\'s magic name if the item is magic. If the item is not magic, it simply contains the standard item description. The secret description is used whenever an item has been successfully evaluated. \n\nThe last field /noise/ indicates how much noise the item generates. Since the character is carrying around these items in a backpack (we assume) the items will be jostling around generating noise. When we get to the monster AI routines, we will use this noise to generate a sound map as the character moves around. If a monster is in the vicinity, it will use the sound map to "hear" the character and take the appropriate action.\n\nThat sets up our inventory data structure. We don\'t have too many items yet, but we have a good data structure in place to build upon. The way we have structured the inventory composite Type, we can just plug in the new items Types as we create them and add in the support routines as needed. The next step is to generate some items and put them on the map, which is what we will do in the next chapter.\n',
'= 12: Generating Items\n\nNow that we have the inventory structure in place, we can generate items and populate the map with some stuff for the player to find. In order to have items in the map, we need to set up the inventory map structure.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Dungeon level information.\nType levelinfo\n   numlevel As Integer \'Current level number.\n   lmap(1 To mapw, 1 To maph) As mapinfotype \'Map array.\n   linv(1 To mapw, 1 To maph) As invtype     \'Map inventory type.\nEnd Type\n}}}\n\x3C/div\x3E\nThe /linv/ array is the inventory composite Type that we created in the last chapter. Each tile in the map can have an inventory item on it, with the limitation that only one item can occupy a particular tile. Some roguelikes allow stacking of items on a tile, but to keep things simple, we will only allow one item per tile. It wouldn\'t be hard to extend the map structure to allow for item stacking, but in my opinion, it doesn\'t add that much to the game, while making the code more complex and harder to manage. Stacking is usually implemented to facilitate item dropping, but even with a single item per tile, there are 9 tiles available around the player character in an empty dungeon room, so there are plenty of areas to drop items. There are only 3 spaces in an empty corridor, but rather than being a problem, it adds a bit of strategy to the game since the player has to choose carefully what items to drop if they need to do so in a corridor.\n\nOne objection to this method is the fact that there is some wasted space in the map inventory array, since many of the tiles will be wall tiles, but this is where the Union in our composite Type helps us. The amount of memory we are using is only equal to the largest item Type, plus the extra fields in the composite Type. The amount of memory being used isn\'t really all that great. Yes, there is some wasted space, but it certainly will not be a problem on virtually any machine the program might run on. If we were creating a game for a mobile device, where memory is an issue, we might want to look at some alternative like a list of inventory items, but for a desk or laptop computer, having a few bytes of wasted space isn\'t going to be an issue.\n\nThis isn\'t just lazy programming, it is purposefully choosing a less complex data structure, which reduces the complexity of the game, making for less bugs and easier maintenance. Many programmer\'s have the idea that the more complex the code is, the better the code. In fact the opposite is true. Complex code leads to complex bugs, bugs which are hard to find and correct. Some look upon complex code may the height of programing skill, but if it doesn\'t work correctly, or the programmer can\'t really maintain the code without rewriting major parts of the program, what benefit is that? Users don\'t like buggy code, and simply won\'t use a program that doesn\'t work correctly. \n\nEven if the complex code works as expected, overly complex code simply isn\'t maintained since it is too much trouble to 1) figure out how something works if it hasn\'t been looked at in months or years, and 2) modifying complex code is extremely difficult since the very fact that it is complex, makes the modifications more work that it is usually worth. In my 20+ years as a programmer, working in virtually every programming field from banking to the oil industry, working with large and small firms, I have found out that simple code that does the job has a robustness and longevity that complex code doesn\'t, because it can\'t be debugged or maintained easily.\n\nWith all that being said, there are instances where you are forced into making things complicated because of hardware or the task can only be accomplished by advanced algorithms that are complex by nature. In those cases you pick your fights carefully. Factor out the simple stuff so that the complex is as minimal as possible. It is amazing that even complicated jobs are mostly made up of simple steps, steps that can be clearly defined and coded, easy to follow and easy to maintain. The key to mastering the complex is to document the complex, both in the code and with programmer manuals. It is a bit more work, but the benefits will be apparent when you need to go back into the code and do some maintenance work. It is very hard to remember the brilliant idea you had when you first wrote the code if you haven\'t looked at the code in weeks or months or years.\n\nSo, we have an inventory array in the map structure, now it is time to generate some items. We do that in our map generation routines.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Generate a new dungeon level.\nSub levelobj.GenerateDungeonLevel()\n    Dim As Integer x, y, i\n   \n    \'Clear level\n    For x = 1 To mapw\n        For y = 1 To maph\n            _level.lmap(x, y).terrid = twall           \'Set tile to wall.\n            _level.lmap(x, y).visible = FALSE          \'Not visible.\n            _level.lmap(x, y).seen = FALSE             \'Not seen.\n            _level.lmap(x, y).hasmonster = FALSE       \'No monster.\n            _level.lmap(x, y).doorinfo.locked = FALSE  \'Door not locked.\n            _level.lmap(x, y).doorinfo.lockdr = 0      \'No lock DR.\n            _level.lmap(x, y).doorinfo.dstr = 0        \'No door strength.\n            ClearInv _level.linv(x,y)                  \'Clear inventory slot.\n        Next\n    Next\n    _InitGrid\n    _DrawMapToArray\n    _GenerateItems\nEnd Sub\n}}}\n\x3C/div\x3E\nIn the /GenerateDungeonLevel/ we are calling a new subroutine /GenerateItems/ which creates new items for the map. Before we do that however, we need to clear out the current inventory items, if any, by calling a new subroutine /ClearInv/ which is located in the new file /inv.bi/. \n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Generate items for the map.\nSub levelobj._GenerateItems()\n   Dim As Integer i, x, y\n   \n    \'Generate some items for the level. \n    For i = 1 To 10\n       Do\n          \'Get a spot in the dungeon.\n          x = RandomRange(2, mapw - 1)\n          y = RandomRange(2, maph - 1)\n       \'Look for floor tile that doesn\'t have an item on it.\n       Loop Until (_level.lmap(x, y).terrid = tfloor) And (HasItem(x, y) = FALSE)\n       GenerateItem _level.linv(x, y), _level.numlevel\n    Next\n   \nEnd Sub\n}}}\n\x3C/div\x3E\nRight now we just generate 10 items in the map. This is so we can test the item generation routines to make sure everything is working as expected. Later, we will come up with a better algorithm for generating items. The first thing we do is select a spot for the item by looking at random tiles. The tile must be a floor tile of course, and it must not already have an item on it. We use the function /HasItem/ to determine if the tile has an item. HasItem has changed to support the new inventory structure.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Returns True if coordinate has item.\nFunction levelobj.HasItem(x As Integer, y As Integer) As Integer\n      \'Look at inventory slot. If no class id then slot empty.\n      If _level.linv(x, y).classid = clNone Then\n         Return FALSE\n      Else\n         Return TRUE\n      EndIf\nEnd Function\n}}}\n\x3C/div\x3E\nTo determine if the tile has an item we look at the /classid/ of the composite structure. If the classid is /clNone/ we know that the tile is empty. Since we have already cleared out the inventory array by calling the ClearInv routine in the map generation sub, what we are checking for here is the chance that we selected a tile that we may have already visited. The chances are quite low that this will happen, but we need to check for it anyway.\n\nIf the tile is empty, then we can generate a new item by calling the inventory subroutine /GenerateItem/.\n\n\x3Cdiv class="code"\x3E\n/map.bi/ \n{{{\nGenerateItem _level.linv(x, y), _level.numlevel\n}}}\n\x3C/div\x3E\nNotice that we are passing the actual inventory array item to GenerateItem along with the current level number. By passing the individual inventory item, the inventory routines doesn\'t have to have access to the whole map inventory array. Not only does this simplify the code, it ensures that we don\'t have unwanted side effects. The only thing that the inventory routines can change is this one inventory slot, and if a problem arises, we know exactly where to look. If the inventory routines had access to the whole map inventory array, finding a problem could be very difficult, and we may not even know there was a problem until the program had been in production for some time (if ever). It is better to limit access where we can, to maintain a good level of security in our data structures.\n\nWe also pass the level number because it used to determine the chance of generating a magical item. The higher the level number, the more likely a magical item will be generated. Again, we explicitly pass the level number to the routine to hide the internal working of our level object. \n\nHaving set up the map for items, we need to generate them and we do that in /GenerateItem/.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Generates a new item and places it into the invetory slot.\nSub GenerateItem(inv As invtype, currlevel As Integer)\n   Dim iclass As classids = RandomRange(clGold, clSupplies)\n   \n   \'Clear current item if not cleared.\n   If inv.classid \x3C\x3E clNone Then\n      ClearInv inv\n   EndIf\n   \'Set the item class.\n   inv.classid = iclass\n   \'Generate item based on class id.\n   Select Case iclass\n      Case clGold\n         GenerateGold inv\n      Case clSupplies\n         GenerateSupplies inv, currlevel\n   End Select\n   \nEnd Sub\n}}}\n\x3C/div\x3E\nThe /iclass/ variable is initialized to a random class id that will be the class item generated. The first thing we do here is to clear the inventory slot if it isn\'t empty. We shouldn\'t really need to use this code at this point, but it doesn\'t hurt to have it in case we decide at some point to recycle inventory slots and we won\'t have to worry about making sure the slot is empty before we get a new item.\n\nOnce we have made sure the slot is empty, we then fill it with the particular inventory item. Since we only have two item classes at the moment, we call one of the two item routines, /GenerateGold/ or /GenerateSupplies/. As we add more items to the game, we will add more generation routines. Let\'s look at each subroutine we have so far.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Generate new gold item.\nSub GenerateGold(inv As invtype)\n   Dim As Integer rng = RandomRange(1, 10)\n   \n   \'Set the gold item id.\n   If rng = 1 Then \n      inv.gold.id = gldBagGold\n   Else\n      inv.gold.id = gldGold\n   EndIf\n   Select Case inv.gold.id\n      Case gldGold\n         inv.desc = "Gold Coins"\n         inv.gold.amt = RandomRange(1, 10)\n         inv.icon = Chr(147)\n         inv.iconclr = fbGold\n      Case gldBagGold\n         inv.desc = "Bag of Gold"\n         inv.gold.amt = RandomRange(10, 100)\n         inv.icon = Chr(147)\n         inv.iconclr = fbGold\n   End Select\nEnd Sub\n}}}\n\x3C/div\x3E\nThe GenerateGold subroutine is quite simple. There is a 1 in 10 chance that the gold item will be a Bag of Gold rather than Gold Coins. As you can see, the only real difference between the two is the amount of gold. Even though both use the same icon and color, the two fields are loaded separately since we may decide to use a different icon for a Bag of Gold than the Gold Coins. Since we are working with the ASCII character set, we have a limited number of icons available, and we have more items to add so we will wait and see what we need to use before making a final decision. If we keep things the way they are now, we can move the icon and icon color fields out of the Select Case. Of course, it doesn\'t really pose a problem to keep them in the Select Case, but I like to minimize the lines of code; less code means less chance of errors.\n\nThe supplies generation routine is only slightly more complex. The general format is the same as the gold generation, but we need to determine if an item is magical and this is where the level number is used. To determine if an item is magical, we use the /isMagic/ function.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Returns True if item is magic.\nFunction ItemIsMagic(currlevel As Integer) As Integer\n   Dim As Integer num\n   \n   \'Get a random number from 1 to 100\n   num = RandomRange(1, maxlevel * 2)\n   \'If number matches or is less than current level, item is magic.\n   If num \x3C= currlevel Then\n      Return TRUE\n   Else\n      Return FALSE\n   EndIf\n   \nEnd Function\n{{{\nHere we generate a number between 1 and the maximum number of levels x2. If the generated number is less than or equal to the current level, then the item is magical. This means that as the character progresses down into the dungeon, the chance of getting a magic item increases, yet at the same time, it keeps the magic items from being generated all the time. Using this formula, as the character reaches the lowest level, the chance of getting a magical item is around 50%. So half the items will be magical, at a time when the character will need magical items the most. We don\'t want a lot of magical items, since by nature magical items should be relatively rare since they add an advantage to the character. The character doesn\'t need that advantage as much in the early levels, since the monsters won\'t be as powerful as they will be in the lower levels, where magical items will play a much more important role. This is an area that may need to be tweaked as we play-test the game, and we can do that here in this routine if needed.\n\nThis function is used in the GenerateSupplies routine when a supply item is created.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Generate new supply item.\nSub GenerateSupplies(inv As invtype, currlevel As Integer)\n   Dim item As supplyids = RandomRange(supHealingHerb, supBottleOil) \n   Dim As Integer isMagic = ItemIsMagic(currlevel) \n   \n   \'Set the supply item id.\n   inv.supply.id = item\n   Select Case item\n      Case supHealingHerb \n         inv.desc = "Healing Herb"\n         inv.supply.noise = 1\n         inv.icon = Chr(157)\n         inv.iconclr = fbGreen\n         inv.supply.eval = FALSE\n         inv.supply.use = useDrinkEat\n         \'Set the magic properties.\n         If isMagic = TRUE Then\n            inv.supply.evaldr = RandomRange(currlevel, currlevel * 2)\n            inv.supply.effect = effMaxHealing\n            inv.supply.sdesc = "Herb of Max Health"\n         Else \n            \'Set secret description to main desciption.\n            inv.supply.sdesc = inv.desc\n         EndIf\n      Case supHunkMeat\n         inv.desc = "Hunk of Meat"\n         inv.supply.noise = 1\n         inv.icon = Chr(224)\n         inv.iconclr = fbSalmon\n         inv.supply.eval = FALSE\n         inv.supply.use = useDrinkEat\n         \'Set the magic properties.\n         If isMagic = TRUE Then\n            inv.supply.evaldr = RandomRange(currlevel, currlevel * 2)\n            inv.supply.effect = effStrongMeat\n            inv.supply.sdesc = "Hunk of Strong Meat"\n         Else \n            \'Set secret description to main desciption.\n            inv.supply.sdesc = inv.desc\n         EndIf\n      Case supBread       \n         inv.desc = "Loaf of Bread"\n         inv.supply.noise = 1\n         inv.icon = Chr(247)\n         inv.iconclr = fbHoneydew\n         inv.supply.eval = FALSE \n         inv.supply.use = useDrinkEat\n         \'Set the magic properties.\n         If isMagic = TRUE Then\n            inv.supply.evaldr = RandomRange(currlevel, currlevel * 2)\n            inv.supply.effect = effBreadLife\n            inv.supply.sdesc = "Bread of Cure Poison"\n         Else \n            \'Set secret description to main desciption.\n            inv.supply.sdesc = inv.desc\n         EndIf\n   End Select\nEnd Sub\n}}}\n\x3C/div\x3E\nThe first two things we do is to pick a supply id from the supply Enumeration and then determine if the supply is magical. Once we have those two pieces of information, we can create a new item. Each item has two sections that require attention; the basic item information, and the magic information if the item is magical. If the item is not magical, we simply don\'t fill the magic effect fields as they won\'t be used. \n\nHow do we determine if an item is magical? By looking at the /evaldr/ field. If it is zero, then the item isn\'t magic. The player doesn\'t know this of course. The item has the /eval/ field set to /FALSE/ so it will show up as unevaluated in the character\'s inventory. We do this so as not to tip our hand on what items are magical and what items are not. This forces the player to interact with the inventory, to discover if the item is magical or not. It most cases it will not be magical, but there is that chance that it will be, and to have only magical items show up as unevaluated takes away the fun of discovering which items are magical and which are not.\n\nThe rest of the fields are filled in with information that relates to the particular item. Unlike the gold, we put in the amount of noise an item makes when carrying or using it. The noise that gold will make will depend on the total amount of gold the character is carrying. Here, each item has a set amount of noise, all set to 1 except for the bottle of oil which generates a noise factor of 4. A noise factor of 4 really isn\'t all that much, since the noise routine will use a "square of the distance" reduction factor which means that a bottle of oil will generate 1 or 2 tiles of noise at most. Where noise plays in the game is the overall noise the character generates when walking around or in combat, and this is derived from the sum of the noise factors from the character\'s inventory and equipment.\n\nThe code presented so far puts an item into the map inventory, but we want that item to actually show up in the map so we need to revisit our map drawing routine.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n    \'Draw the visible portion of the map.\n     For x = 1 To w\n         For y = 1 To h\n             \'Clears current location to black.\n             tilecolor = fbBlack \n             PutText acBlock, y + 1, x + 1, tilecolor\n             \'Get tile id\n             tile = _level.lmap(i + x, j + y).terrid\n             \'Get the tile symbol\n            mtile = _GetMapSymbol(tile)\n            \'Get the tile color\n            tilecolor = _GetMapSymbolColor(tile)\n            \'Print the tile.\n            If _level.lmap(i + x, j + y).visible = True Then\n                \'Print the item marker.\n                If HasItem(i + x, j + y) = True Then\n                    \'Get the item symbol.\n                    mtile = _level.linv(i + x, j + y).icon\n                    \'Get the item color.\n                    tilecolor = _level.linv(i + x, j + y).iconclr\n                EndIf\n                PutText mtile, y + 1, x + 1, tilecolor\n                \'If the current location has a monster print that monster.\n                If _level.lmap(i + x, j + y).hasmonster = TRUE Then\n                    \'Put monster info here.\n                EndIf\n            Else\n                \'Not in los. Don\'t print monsters when not in LOS.\n                If _level.lmap(i + x, j + y).seen = TRUE Then\n                    If HasItem(i + x, j + y) = True Then\n                        PutText "?", y + 1, x + 1, fbSlateGrayDark\n                    Else\n                        PutText mtile, y + 1, x + 1, fbSlateGrayDark\n                    End If\n                 End If\n            End If\n         Next \n     Next\n}}}\n\x3C/div\x3E\nHere look and see if the current tile has an item on it, and if it does, we grab the icon and the icon color. If the tile doesn\'t have an item on it, then use the terrain icon and color. We then draw that tile, either item or terrain at the current location. For items that the character has seen, but are not currently in the character\'s LOS, we draw a question mark to indicate that the tile contains an item, but the item identification is masked since the character can\'t see the item directly. \n\nThe last thing we need to do is to print the item description when the character walks over an item. We do this in our main screen routine.\n\n\x3Cdiv class="code"\x3E\n/dod.bas:DrawMainScreen/\n{{{\n   \'Check to see if character standing on an item.\n   If level.HasItem(pchar.Locx, pchar.Locy) = TRUE Then\n      txt = level.GetItemDescription(pchar.Locx, pchar.Locy)\n      PrintMessage txt\n   Else\n      \'See if character is on special tile.\n      terr = level.GetTileID(pchar.Locx, pchar.Locy)\n      If (terr = tstairup) OrElse (terr = tstairdn) Then\n         txt = level.GetTerrainDescription(pchar.Locx, pchar.Locy)\n         PrintMessage txt\n      End If\n   EndIf\n}}}\n\x3C/div\x3E\nFor an item description we call the level object function /GetItemDescription/.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Returns item description at x, y coordinate.\nFunction levelobj.GetItemDescription(x As Integer, y As Integer) As String\n   Dim As String ret = "None"\n   \n   If _level.linv(x, y).classid \x3C\x3E clNone Then\n      ret = GetInvItemDesc(_level.linv(x, y))\n   EndIf\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nHere we check to make sure that an item actually exists at the location specified and if so, we call the inventory function /GetInvItemDesc/.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Returns the item desc for item at x, y coordinate.\nFunction GetInvItemDesc(inv As invtype) As String\n   Dim As String ret = "None"\n   \n   \'If classid is None then nothing to do.\n   If inv.classid \x3C\x3E clNone Then\n      \'Get the gold description.\n      If inv.classid = clGold Then\n         ret = inv.desc\n      EndIf\n   EndIf\n   \'Get the supply description. \n   If inv.classid = clSupplies Then\n      \'If not evaluated, then return main description.\n      If inv.supply.eval = FALSE Then\n         ret = inv.desc\n      Else\n         \'Return secret description.\n         ret = inv.supply.sdesc\n      EndIf\n   EndIf\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nHere we return either the base description or the secret description depending on the evaluation state of the item. This may seem a round-about way of getting the item description, but we are going to need these descriptions in more than just the map routines. We will also use this to display descriptions in the character\'s inventory, so it is better to have a single function to return a description rather than putting description functions in several places. We have only one routine we need to update as we add more items to the game that can be used by both the map and character inventory routines keeping both in sync.\n\nSo now we have items in the map. The next step is get the items into the character\'s hands, and we will do that in the next chapter.\n',
'= 13: Character Inventory\n\x3Cdiv align="center"\x3E\x3Cimg src=\'images/charinv.png\'/\x3E\x3C/div\x3E\n\nNow that the map has items on it, we want to get those items into the character\'s hands. In order to do that, we need to build the character\'s inventory code. Just like in the map object, we will start by adding an inventory collection to the player object.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n\'Character attribute type def.\nType characterinfo\n   cname As String * 35 \'Name of character.\n   stratt(3) As Integer \'Strength attribute (0), str bonus (1), bonus length in turns(2)\n   staatt(3) As Integer \'Stamina  \n   dexatt(3) As Integer \'Dexterity \n   aglatt(3) As Integer \'Agility \n   intatt(3) As Integer \'Intelligence \n   currhp As Integer    \'Current HP\n   maxhp As Integer     \'Max HP\n   currmana As Integer  \'Current mana\n   maxmana As Integer   \'Max mana\n   ucfsk(3) As Integer  \'Unarmed combat skill\n   acfsk(3) As Integer  \'Armed combat skill\n   pcfsk(3) As Integer  \'Projectile combat skill\n   mcfsk(3) As Integer  \'Magic combat skill \n   cdfsk(3) As Integer  \'Combat defense skill\n   mdfsk(3) As Integer  \'Magic defense skill \n   currxp As Integer    \'Current spendable XP amount.\n   totxp As Integer     \'Lifetime XP amount.\n   currgold As Integer  \'Current gold amount.\n   totgold As Integer   \'Lifetime gold amount.\n   ploc As mcoord       \'Character current x and y location.\n   cinv(97 To 122) As invtype \'Character inventory-using ascii codes for index values.\nEnd Type\n}}}\n\x3C/div\x3E\nThe last definition in the list is the character\'s inventory. Notice the odd subscript ranges; these are the ASCII codes for the characters a through z. The character will have 26 inventory slots in addition to those items he is wearing and wielding. The inventory slots will be "numbered" a through z, so all we need to do is take the ASCII code of the player selection and translate it into a subscript into the character\'s inventory array. What we are doing here is taking advantage of the flexible array subscripts that FreeBasic offers to make our job easier and the program more robust. Many programmers fail to take advantage of their language\'s flexibility, and end up with cumbersome, hard to maintain code. \n\nWhat we need to do now is enable the player to pick-up an item from the map, and we do that by coding the "g" (Get) key function.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n \'Get an item from the dungeon and put into character\'s inventory.\n  If ckey = "g" Then\n    \'Inventory type.\n     Dim inv As invtype\n     \'Make sure character is standing on an item.\n     If level.HasItem(pchar.Locx, pchar.Locy) = TRUE Then\n         \'Check for gold. Just add gold to gold total.\n         Dim iclass As classids = level.GetInvClassID(pchar.Locx, pchar.Locy)\n         If iclass = clGold Then\n             \'Get the gold from the map.\n             level.GetItemFromMap pchar.Locx, pchar.Locy, inv\n             \'Add to gold total.\n             pchar.CurrGold = pchar.CurrGold + inv.gold.amt\n             pchar.TotGold = pchar.TotGold + inv.gold.amt\n             \'Add to experience.\n             pchar.CurrXP = pchar.CurrXP + inv.gold.amt\n             pchar.TotXP = pchar.TotXP + inv.gold.amt\n             \'Message player. \n             PrintMessage inv.gold.amt & " gold coins collected."\n             DrawMainScreen\n         Else\n             \'Look for free inventory slot.\n             Dim As Integer cidx = pchar.GetFreeInventoryIndex\n             \'If found a slot load inventory item.\n             If cidx \x3C\x3E -1 Then\n                 level.GetItemFromMap pchar.Locx, pchar.Locy, inv\n                 \'Put it into character inventory.\n                 pchar.AddInvItem cidx, inv\n                 PrintMessage "Item added to inventory."\n             Else  \n                 \'No slots open.\n                 PrintMessage "No free inventory slots."\n             EndIf\n         EndIf\n     Else\n         PrintMessage "Nothing to get."\n     EndIf\n  EndIf\n}}}\n\x3C/div\x3E\nOnce the player presses the "g" key the first we thing we need to do is to check to make sure that the character is standing on an item. If there is an item, we get the class id of that item. We do this to differentiate gold from other items. Gold doesn\'t go into the character\'s inventory; it is simply added to the character\'s gold total and, as a bonus, adds some experience points to the character. If the item isn\'t gold, then we add the item to the character\'s inventory.\n\nWhen adding an item to the character\'s inventory we need to check to see if there are any free slots. We do that in /GetFreeInventoryIndex/.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n\'Returns free inventory slot index or -1.\nFunction character.GetFreeInventoryIndex() As Integer\n   Dim As Integer ret = -1\n   \n   \'Look for an empty inventory slot.\n   For i As Integer = LBound(_cinfo.cinv) To UBound(_cinfo.cinv)\n      \'Examine class id.\n      If _cinfo.cinv(i).classid = clNone Then\n         \'Empty slot.\n         ret = i\n         Exit For\n      EndIf\n   Next\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nThis function iterates through the character\'s inventory array and looks for a /classid/ of /clNone/, which indicates that the slot is currently empty. If the function finds an empty slot, then the index of that slot is returned. If all the slots are currently filled, the function returns -1. Once we have a free slot, we can add the item to the character\'s inventory using /AddInvItem/.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n\'Adds inventory item to character inventory slot.\nSub character.AddInvItem(idx As Integer, inv As invtype)\n   \'Validate the index.\n   If idx \x3E= LBound(_cinfo.cinv) And idx \x3C= UBound(_cinfo.cinv) Then\n      \'Clear the inventory slot.\n      ClearInv _cinfo.cinv(idx)\n      \'Set the item in the inv slot.\n      _cinfo.cinv(idx) = inv\n   End If   \nEnd Sub\n}}}\n\x3C/div\x3E\nWe check to make sure the index is valid (since they may be called from other areas of the program), clear the current inventory slot (again, in case this is called from another procedure) and set the slot to the inventory item the player picked up from the map. To let the player know the item was added successfully we print a message to that effect in the main code where we processed the get command. Now that the item is in the character\'s inventory, we need to display this and any other items the player may have picked up. This brings us to the character inventory screen.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n    \'Draw the inventory screen.\n    If ckey = "i" Then\n        ManageInventory\n        \'Need to redraw back ground here.\n        DrawBackground mainback()\n        DrawMainScreen\n    EndIf\n}}}\n\x3C/div\x3E\nWhen the player presses the "i" key we invoke the /ManageInventory/ subroutine which handles the inventory commands. Once the player exits the inventory screen, the main display is redrawn on the screen. So, for the character inventory, all the action happens in ManageInventory.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Manages character inventory.\nSub ManageInventory()\n   Dim As String kch, ich\n   Dim As Integer ret\n   \n   DrawInventoryScreen\n   Do\n      kch = InKey\n      kch = UCase(kch)\n      \'Check to see if we have a key.\n      If kch \x3C\x3E "" Then\n         \'Process the eval command.\n         If kch = "E" Then\n            ret = ProcessEval()\n            \'Screen changed.\n            If ret = TRUE Then\n               DrawInventoryScreen\n            EndIf\n         EndIf\n      EndIf\n      Sleep 1\n   Loop Until kch = key_esc\n   ClearKeys\nEnd Sub\n}}}\n\x3C/div\x3E\nThe first thing we do is to draw the inventory screen. We then process any commands that the player may execute while in the inventory screen. The only command active at this point is "Evaluate" which is processed through the "e" key. Once the player has done what they want here, we exit back to the main program code. We will look at the function /ProcessEval/ in a moment, but first let\'s look at the /DrawInventoryScreen/ subroutine that, as the name implies, draws the inventory screen.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Draws the player inventory.\nSub DrawInventoryScreen()\n   Dim As Integer col, row, iitem, ret, srow, ssrow, cnt, i\n   Dim As String txt, txt2, desc\n   Dim As invtype inv\n   Dim As UInteger clr\n   \n   ScreenLock\n	\'Set the background for the inventory screen.\n	DrawBackground leatherback()\n	\'Add the title.\n	txt = "Current Inventory for " & Trim(pchar.CharName)\n   col = CenterX(txt)\n   row = 1\n   \'Draw title with drop shadow.\n   PutTextShadow txt, row, col, fbYellowBright\n   \'Add the current held equipment.   \n   col = 2\n   row += 3\n   txt = "1 Primary: "\n   PutTextShadow txt, row, col, fbWhite\n   col = txcols / 2\n   txt = "4 Neck: "\n   PutTextShadow txt, row, col, fbWhite\n   col = 2\n   row += 2\n   txt = "2 Secondary: "\n   PutTextShadow txt, row, col, fbWhite\n   col = txcols / 2\n   txt = "5 Ring Right: "\n   PutTextShadow txt, row, col, fbWhite\n   col = 2\n   row += 2\n   txt = "3 Armor: "\n   PutTextShadow txt, row, col, fbWhite\n   col = txcols / 2\n   txt = "6 Ring Left: "\n   PutTextShadow txt, row, col, fbWhite\n   \'Draw the divider line.\n   row += 2\n   col = 1 \n   txt = String(80, Chr(205))\n   Mid(txt, 2) = " Equipment  (*) = Not Evaluated "\n   txt2 = " Gold: " & pchar.CurrGold & " "\n   Mid(txt, 80 - Len(txt2)) = txt2\n   PutTextShadow txt, row, col, fbYellowBright\n   row += 2\n   col = 2\n   srow = row\n   \'Print out the inventory items.\n   For i = pchar.LowInv To pchar.HighInv\n      \'See if character has item in slot.\n      iitem = pchar.HasInvItem(i)\n      If iitem = TRUE Then\n         \'Get the inv item.\n         pchar.GetInventoryItem i, inv\n         \'Is the item evaluated.\n         ret = IsEval(inv)\n         \'Get the description.\n         desc = GetInvItemDesc(inv)\n         \'Get the color of the item.\n         clr = inv.iconclr\n         \'Build the text string.\n         txt = Chr(i) & " " & desc & " "\n         \'If not evaluated, mark so player knows it hasn\'t been evaluated.\n         If ret = FALSE Then\n            txt &= "(*)"\n         EndIf\n      Else\n         txt = Chr(i)\n         clr = fbWhite\n      EndIf\n      \'Move column over when reached half of the list.\n      cnt += 1\n      If cnt =  14 Then\n         col = txcols / 2\n         \'Save the end of the first column so we can draw line.\n         ssrow = row\n         row = srow\n      EndIf\n      \'Draw the text.\n      PutTextShadow txt, row, col, clr\n      row += 2\n   Next\n   \'Draw the divider line.\n   row = ssrow + 1\n   col = 1\n   txt = String(80, Chr(205))\n   Mid(txt, 2) = " Spells Learned "\n   PutTextShadow txt, row, col, fbYellowBright\n   \'Draw the spell slots.\n   col = 2\n   row += 2\n   cnt = 0\n   srow = row\n   For i = 65 To 78\n      txt = Chr(i)\n      \'Move column over when reached half of the list.\n      cnt += 1\n      If cnt =  8 Then\n         col = txcols / 2\n         \'Save the end of the first column so we can draw line.\n         ssrow = row\n         row = srow\n      EndIf\n      \'Draw the text.\n      PutTextShadow txt, row, col, clr\n      row += 2\n   Next\n   \'Draw the divider line.\n   row = ssrow + 1\n   col = 1\n   txt = String(80, Chr(205))\n   Mid(txt, 2) = " Commands "\n   PutTextShadow txt, row, col, fbYellowBright\n   \'Draw the command list\n   row += 2\n   txt = "(D)rop - (E)val - (W)ield/Wear - (R)ead - (E)at/Drink - (I)spect"\n   col = CenterX(txt)\n   PutTextShadow txt, row, col, fbWhite\n   ScreenUnLock\nEnd Sub\n}}}\n\x3C/div\x3E\nAs you can see, the format here is the same as the main display drawing routine. The screen has three sections to it, the top section which has all the current items the character is wearing and wielding, the inventory collection (imagine a backpack filled with all this stuff) and the third section which has a list of spells the character has learned by reading spell books. The only section that has any information in it right now is what is in the backpack--the inventory collection.\n\n\x3Cdiv class="code"\x3E\n/dod.bas:DrawInventoryScreen/\n{{{\n      \'See if character has item in slot.\n      iitem = pchar.HasInvItem(i)\n      If iitem = TRUE Then\n         \'Get the inv item.\n         pchar.GetInventoryItem i, inv\n         \'Is the item evaluated.\n         ret = IsEval(inv)\n         \'Get the description.\n         desc = GetInvItemDesc(inv)\n         \'Get the color of the item.\n         clr = inv.iconclr\n         \'Build the text string.\n         txt = Chr(i) & " " & desc & " "\n         \'If not evaluated, mark so player knows it hasn\'t been evaluated.\n         If ret = FALSE Then\n            txt &= "(*)"\n         EndIf\n      Else\n         txt = Chr(i)\n         clr = fbWhite\n      EndIf\n}}}\n\x3C/div\x3E\nTo populate the display, we check each slot by using the character /HasInvItem/ function, and if that returns TRUE then we grab the inventory item using /GetInventoryItem/ so we can get the item information. We check its evaluation state using /IsEval/ since we will mark items not evaluated when we print the list, and grab the item description using /GetInvItemDesc/. The color we use to display the description will be the same as the icon color, to help make the player make the connection between an item\'s icon and what it is. We then draw the item on the screen in the proper slot. If the slot is empty, we simply draw the slot "number" which in our case will be a letter from "a" to "z". Let\'s go over each of the functions we are calling here.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n\'Returns True if item exists in inventory slot.\nFunction character.HasInvItem(idx As Integer) As Integer \n   \'Validate the index.\n   If idx \x3E= LBound(_cinfo.cinv) And idx \x3C= UBound(_cinfo.cinv) Then\n      \'Check the class id.\n      If _cinfo.cinv(idx).classid = clNone Then\n         Return FALSE\n      Else\n         Return TRUE\n      EndIf\n   Else\n      Return FALSE\n   End If   \n   \nEnd Function\n}}}\n\x3C/div\x3E\nThe code here needs little explanation at this point. We check the /classid/ and if /clNone/ we return FALSE, otherwise we return TRUE.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Returns True if item has been evaluated.\nFunction IsEval(inv As invtype) As Integer\n   Dim As Integer ret\n   \n   \'If nothing then mark as evaluated.\n   If inv.classid = clNone Then\n      ret = TRUE\n   Else\n      \'Selecvt the item type.\n      Select Case inv.classid \'Don\'t need to eval gold.\n         Case clGold\n            ret = TRUE\n         Case clSupplies  \'Return supply eval glag.\n            ret = inv.supply.eval\n      End Select\n   EndIf\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nThe /IsEval/ function simply returns the current state of the /Eval/ flag in the inventory item. Since we only have supplies right now, that is the only item class we check. As we add more item classes, we will expand this function.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n\'Gets an item from an inventory slot.\nSub character.GetInventoryItem(idx As Integer, inv As invtype)\n   \n   \'Clear the inv item.\n   ClearInv inv\n   \'Validate the index.\n   If idx \x3E= LBound(_cinfo.cinv) And idx \x3C= UBound(_cinfo.cinv) Then\n      inv = _cinfo.cinv(idx)\n   End If   \nEnd Sub\n}}}\n\x3C/div\x3E\nWe have looked at this before. We check the index to make sure it is valid and then return the item at the requested slot. No attempt here is made to validate if the slot is empty or full. We can use this subroutine to grab both an empty slot (as we did in the last chapter) or a full slot as we are doing here.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Returns the item desc for item at x, y coordinate.\nFunction GetInvItemDesc(inv As invtype) As String\n   Dim As String ret = "None"\n   \n   \'If classid is None then nothing to do.\n   If inv.classid \x3C\x3E clNone Then\n      \'Get the gold description.\n      If inv.classid = clGold Then\n         ret = inv.desc\n      EndIf\n   EndIf\n   \'Get the supply description. \n   If inv.classid = clSupplies Then\n      \'If not evaluated, then return main description.\n      If inv.supply.eval = FALSE Then\n         ret = inv.desc\n      Else\n         \'Return secret description.\n         ret = inv.supply.sdesc\n      EndIf\n   EndIf\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nOnce again we have familiar code and a simple process. If the item has been evaluated, we print out the secret description, otherwise we print out the basic description. To refresh your memory, magical items will have a secret description that describes their magical properties. For non-magical items, the standard and secret descriptions are the same.\n\nOnce all the inventory information has been display on the screen, the player can manage the items by selecting the appropriate key commands listed along the bottom of the screen. The only command we have implemented at this point is the /Evaluation/ command.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Processes the eval command.\nFunction ProcessEval() As Integer\n   Dim As String res, mask, desc\n   Dim As Integer i, iret, iitem, evaldr, pint, rollp, rolle, ret = FALSE\n   Dim As invtype inv\n   Dim As tWidgets.btnID btn\n   Dim As tWidgets.tInputbox ib\n      \n   \'Make sure there is something to evaluate in the inventory.\n   For i = pchar.LowInv To pchar.HighInv\n      iitem = pchar.HasInvItem(i)\n      If iitem = TRUE Then\n         \'Get the inv item.\n         pchar.GetInventoryItem i, inv\n         \'Is the item evaluated.\n         iret = IsEval(inv)\n         \'An item to evaluate.\n         If iret = FALSE Then\n            \'Build the mask.\n            mask &= Chr(i)\n         End If\n      EndIf\n   Next\n   If Len(mask) = 0 Then\n      ShowMsg "Evaluate", "Nothing to evaluate.", tWidgets.MsgBoxType.gmbOK\n   Else\n      \'Draws an input box on screen.\n      ib.Title = "Evaluate"\n      ib.Prompt = "Select item(s) to evaluate (" & mask & ")"\n      ib.Row = 39\n      ib.EditMask = mask\n      ib.MaxLen = Len(mask)\n      ib.InputLen = Len(mask)\n      btn = ib.Inputbox(res)\n      \'Evaluate each item in the list.\n      If (btn \x3C\x3E tWidgets.btnID.gbnCancel) And (Len(res) \x3E 0) Then\n         \'Evaluate the list of items.\n         For i = 1 To Len(res)\n            iitem = Asc(res, i) \'Get index into character inventory.\n            \'Get the inv item.\n            pchar.GetInventoryItem iitem, inv\n            \'Get the eval DR.\n            evalDR = GetEvalDR(inv)\n            \'Use the intelligence attribute to calculate the eval attempt.\n             pint = pchar.CurrInt + pchar.BonInt\n             \'Roll for evaldr.\n             rolle = RandomRange(0, evalDR)\n             \'Roll for player.\n             rollp = RandomRange(0, pint)\n             \'Get the item description.\n             desc = GetInvItemDesc(inv)\n             \'If the player rolls =\x3E eval roll, item is evaluated.\n             If rollp \x3E rolle Then\n               desc &= " was succesfully evaluated."\n               ShowMsg "Evaluate", desc, tWidgets.MsgBoxType.gmbOK    \n               SetInvEval inv, TRUE \'Set eval to true.\n               pchar.AddInvItem iitem, inv \'Put item back into inv.\n               ret = TRUE \'Flag caller that screen has changed.\n             Else\n                desc &= " was not evaluated."\n               ShowMsg "Evaluate", desc, tWidgets.MsgBoxType.gmbOK    \n             EndIf\n         Next\n      EndIf\n   EndIf\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nFirst, you will notice some declarations in the tWidgets Namespace. These are contained in the file /tWidgets.bi/. tWidgets (for Text Widgets) are a set of widgets that implement a text-based GUI system. I won\'t be discussing the widgets here, but they are fully explained in the Appendix section.\n\nThe first thing we do is to check to see if there are any items that need to be evaluated. If there are unevaluated items, a list is generated in the mask variable for presentation to the player. If everything has been evaluated, then the player is told so and the function exits.\n\nOnce we have a list of items, the player selects which items he wants to apptempt to evaluate. The process is as follows: \n\n* Get the eval DR (difficulty rating) of the item. {{{evalDR = GetEvalDR(inv)}}}\n* Get the character\'s Intelligence rating and any Intelligence bonus. {{{pint = pchar.CurrInt + pchar.BonInt}}}\n* Roll a random number for the item from 0 to the eval DR. {{{rolle = RandomRange(0, evalDR)}}}\n* Roll a random number from 0 to the character\'s combined Int and Bonus Int. {{{rollp = RandomRange(0, pint)}}}\n* If the character roll is greater than the item roll, the item is evaluated. Remember that ties go to the defender, and in this case the item is the "defender". {{{If rollp \x3E rolle Then}}}\n* If the item is evaluated, set the item eval state to TRUE and inform the player. {{{SetInvEval inv, TRUE}}}\n* If the item is not evaluated, inform the player.\n\nWe get the item DR by calling the /GetEvalDR/ function.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Returns the eval difficulty rating.\nFunction GetEvalDR(inv As invtype) As Integer\n   Dim As Integer ret\n   \n   \'If nothing then evaldr is 0.\n   If inv.classid = clNone Then\n      ret = 0\n   Else\n      \'Select the item.\n      Select Case inv.classid\n         Case clGold \'Don\'t need to eval gold.\n            ret = 0\n         Case clSupplies\n            ret = inv.supply.evaldr \'Return supply eval dr.\n      End Select\n   EndIf\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nWe simply return the DR if we have an item or 0 otherwise. When an item is evaluated, we need to update the eval flag and we do that in /SetInvEval/.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Sets eval state to passed type.\nSub SetInvEval(inv As invtype, state As Integer)\n   \n   \'If nothing then no eval.\n   If inv.classid \x3C\x3E clNone Then\n      \'Select the item.\n      Select Case inv.classid\n         Case clSupplies\n            inv.supply.eval = state \'Set the eval state.\n      End Select\n   EndIf\nEnd Sub\n}}}\n\x3C/div\x3E\nHere, we just set the eval state to whatever is passed to the subroutine. This gives us the option to set an item to unevaluated if we want to implement that functionality. We might want to do that as the result of a monster spell or some other mechanism. Since we haven\'t gotten that far in the project yet, it is better to leave our options open at this point in the development cycle.\n\nThe evaluation process returns TRUE to ManageInventory so that we can redraw the inventory screen to show the updated state of the items. This keeps the inventory display current so that we accurately display the current state of all of the inventory items. \n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Manages character inventory.\nSub ManageInventory()\n   Dim As String kch, ich\n   Dim As Integer ret\n   \n   DrawInventoryScreen\n   Do\n      kch = InKey\n      kch = UCase(kch)\n      \'Check to see if we have a key.\n      If kch \x3C\x3E "" Then\n         \'Process the eval command.\n         If kch = "E" Then\n            ret = ProcessEval()\n            \'Screen changed.\n            If ret = TRUE Then\n               DrawInventoryScreen\n            EndIf\n         EndIf\n      EndIf\n      Sleep 1\n   Loop Until kch = key_esc\n   ClearKeys\nEnd Sub\n}}}\n\x3C/div\x3E\nThere are other commands on the inventory screen that we haven\'t implemented yet since we have a limited set of items. Wield/wear for example is only relevant to weapons, armor, necklaces and rings. Read is only valid for scrolls and spell books, which we also don\'t have at the moment. However, Drop, Eat/Drink and Inspect can be implemented with the item set we have now, and we will do that in the next chapter.\n',
'= 14: Character Inventory II\n\x3Cdiv align="center"\x3E\x3Cimg src=\'images/inspectitem.png\' /\x3E\x3C/div\x3E\n\nIn this chapter we will finish the code to handle our supply items so that we can Drop, Eat/Drink and Inspect an item. Up until this point, the different code sections worked mostly in isolation. Now, we are getting to the point in the code where the code sections are beginning to interact with each other, especially in the Eat/Drink routines. Consuming a healing herb will heal the character, a hunk of meat will add some health and may impart a bonus to the strength attribute, and bread has the potential to cure poison. From this point on, the different code modules will increasingly begin to interact with each other as we build a dynamic system that will describe our game world. This is the exciting part of the coding process; when you start to see all these different modules beginning to work together, and the world we have created starts to come to life.\n\nBefore we get into the actual code in this chapter, take a look at the main menu of the program. The inventory commands that were on the main screen have been removed since they are also contained within the inventory screen. This puts them where they need to be, with the inventory, since they affect the inventory. For example, the drop command needs to know what to drop, and this requires the player to be able to view the inventory items in order to make the selection. To put it another way, the presentation context matches the command. Placing commands in the wrong context generates clutter and reduces the effectiveness of the presentation. A good presentation presents only what the player needs to know at any given moment, and it should all be relevant to the task at hand. Many programs, not just games, are rendered ineffective simply because the presentation is poorly executed.\n\nYou will notice that the command set on the main screen is broken into two sections. The top section relates to the other major screens of the program: Help, Inventory and Character Improvement. The bottom section are the key commands that relate to what is happening on the main screen. Going up or down stairs, searching, targeting an enemy, etc. This cleans up the command presentation quite a bit, and makes it much easier for the player to find what he is looking for while playing the game. By grouping the information into logical sections we reduce the eye travel time, making the game easier to play and more rewarding for the player. Of course the player doesn\'t realize this and probably wouldn\'t even notice it--which is a good thing. It means we have done our job correctly. It is when the player does start noticing these little things that we should stop and rethink our methods.\n\nThe supply items we have right now are mostly healing objects, and when consumed, affect the health of the character. If the item is magical there is an additional bonus or effect. In order to implement the effects of the items we need to update our character structure.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n\'Character attribute type def.\nType characterinfo\n   cname As String * 35 \'Name of character.\n   stratt(3) As Integer \'Strength attribute (0), str bonus (1), bonus length in turns(2)\n   staatt(3) As Integer \'Stamina  \n   dexatt(3) As Integer \'Dexterity \n   aglatt(3) As Integer \'Agility \n   intatt(3) As Integer \'Intelligence \n   currhp As Integer    \'Current HP\n   maxhp As Integer     \'Max HP\n   currmana As Integer  \'Current mana\n   maxmana As Integer   \'Max mana\n   ucfsk(3) As Integer  \'Unarmed combat skill\n   acfsk(3) As Integer  \'Armed combat skill\n   pcfsk(3) As Integer  \'Projectile combat skill\n   mcfsk(3) As Integer  \'Magic combat skill \n   cdfsk(3) As Integer  \'Combat defense skill\n   mdfsk(3) As Integer  \'Magic defense skill \n   currxp As Integer    \'Current spendable XP amount.\n   totxp As Integer     \'Lifetime XP amount.\n   currgold As Integer  \'Current gold amount.\n   totgold As Integer   \'Lifetime gold amount.\n   ploc As mcoord       \'Character current x and y location.\n   cinv(97 To 122) As invtype \'Character inventory-using ascii codes for index values.\n   isPoisoned As Integer  \'The Poisoned flag. True = character is poisoned.\n   PoisonStr As Integer   \'The strength of the poison. Poison is accumlative.\nEnd Type\n}}}\n\x3C/div\x3E\nYou will notice that we have added an /isPoisoned/ and /PoisonStr/ field. We have also added the necessary properties so we can access these fields.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\nDeclare Property Poisoned() As Integer     \'Returns the poinsoned flag.\nDeclare Property Poisoned(flag As Integer) \'Set the poisoned flag.\nDeclare Property PoisonStr() As Integer     \'Returns the poinsoned flag.\nDeclare Property PoisonStr(amt As Integer) \'Set the poisoned flag.\n}}}\n\x3C/div\x3E\nEffects like these are timed effects, so we need a way to update the status of these effects during each game turn. We do that with a /DoTimedActions/ subroutine in our character object.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n\'Goes through all bonus settings and adjusts counts, applies poison, etc.\nSub character.DoTimedActions ()\n   Dim As Integer roll1, roll2, v1, v2\n   \n   \'Poison will affect character based on strength of poison.\n   If _cinfo.IsPoisoned = TRUE Then\n      \'Get the strength of the poison.\n      v1 = _cinfo.PoisonStr\n      \'Get character stamina + bonus\n      v2 = _cinfo.staatt(0) + _cinfo.staatt(1)\n      \'Roll for the poison.\n      roll1 = RandomRange(1, v1)\n      roll2 = RandomRange(1, v2)\n      \'Poison won.\n      If roll1 \x3E roll2 Then\n         \'Take one off the health.\n         _cinfo.currhp = _cinfo.currhp - 1 \n      EndIf\n   EndIf\n   \'Check the counts\n   If _cinfo.stratt(2) \x3E 0 Then\n      \'Dec the count.\n      _cinfo.stratt(2) -= 1\n      If _cinfo.stratt(2) \x3C= 0 Then\n         \'Reset bnous amt.\n         _cinfo.stratt(1) = 0\n      EndIf\n   EndIf\n   \n   If _cinfo.staatt(2) \x3E 0 Then\n      _cinfo.staatt(2) -= 1\n      If _cinfo.staatt(2) \x3C= 0 Then\n         _cinfo.staatt(1) = 0\n      End If\n   EndIf\n\n   If _cinfo.dexatt(2) \x3E 0 Then\n      _cinfo.dexatt(2) -= 1\n      If _cinfo.dexatt(2) \x3C= 0 Then\n         _cinfo.dexatt(1) = 0\n      End If\n   EndIf\n\n   If _cinfo.aglatt(2) \x3E 0 Then\n      _cinfo.aglatt(2) -= 1\n      If _cinfo.aglatt(2) \x3C= 0 Then\n         _cinfo.aglatt(1) = 0\n      End If\n   EndIf\n\n   If _cinfo.intatt(2) \x3E 0 Then\n      _cinfo.intatt(2) -= 1\n      If _cinfo.intatt(2) \x3C= 0 Then\n         _cinfo.intatt(1) = 0\n      End If\n   EndIf\n\n   If _cinfo.ucfsk(2) \x3E 0 Then\n      _cinfo.ucfsk(2) -= 1\n      If _cinfo.ucfsk(2) \x3C= 0 Then\n         _cinfo.ucfsk(1) = 0\n      End If\n   EndIf\n\n   If _cinfo.acfsk(2) \x3E 0 Then\n      _cinfo.acfsk(2) -= 1\n      If _cinfo.acfsk(2) \x3C= 0 Then\n         _cinfo.acfsk(1) = 0\n      End If\n   EndIf\n\n   If _cinfo.pcfsk(2) \x3E 0 Then\n      _cinfo.pcfsk(2) -= 1\n      If _cinfo.pcfsk(2) \x3C= 0 Then\n         _cinfo.pcfsk(1) = 0\n      End If\n   EndIf\n\n   If _cinfo.mcfsk(2) \x3E 0 Then\n      _cinfo.mcfsk(2) -= 1\n      If _cinfo.mcfsk(2) \x3C= 0 Then\n         _cinfo.mcfsk(1) = 0\n      End If\n   EndIf\n\n   If _cinfo.cdfsk(2) \x3E 0 Then\n      _cinfo.cdfsk(2) -= 1\n      If _cinfo.cdfsk(2) \x3C= 0 Then\n         _cinfo.cdfsk(1) = 0\n      End If\n   EndIf\n\n   If _cinfo.mdfsk(2) \x3E 0 Then\n      _cinfo.mdfsk(2) -= 1\n      If _cinfo.mdfsk(2) \x3C= 0 Then\n         _cinfo.mdfsk(1) = 0\n      End If\n   EndIf\nEnd Sub\n}}}\n\x3C/div\x3E\nIf the character is poisoned, then we roll for the poison using the poison strength, and roll for the character, using the character\'s stamina. We then compare the two rolls just like we will do for combat. If the poison wins, the character loses 1 HP. After the poison combat is resolved, we then decrement each stat bonus count if it is greater than 0. The counts are contained in the third element of each stat array. Just to review, the actual stat value is in element 0, the stat bonus is in element 1 and the count is in element 2. Once a count reaches 0, the bonus amount is set to 0. The DoTimedActions is called in the main processing loop of the program.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\nIf ckey \x3C\x3E "" Then\n\n    ...\n\n    \'Since the player pressed a key, do any timed actions.\n    pchar.DoTimedActions\n    \'Check for whether character is dead.\n    If pchar.CurrHP \x3C= 0 Then\n      isdead = TRUE\n    EndIf\nEnd if\n}}}\n\x3C/div\x3E\nSince this is a turn-based game, every time the player presses a key, "time" passes in the game world, so we execute the /DoTimeActions/ subroutine. If the player doesn\'t press a key, then no "time" passes and we do nothing. If this were a real-time game, we would check the current time against some preset interval and when the interval expires, execute the timed actions. Once we execute the timed actions, we check to see if the character is dead. We need to do this since we have things like poison that affect the health of the character. When the character\'s health reaches 0, then the game is over.\n\nIf the character dies, we just print a message indicating the fact.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\nPrint dead mesage.\nIf isdead = TRUE Then\n   Cls\n   Print pchar.CharName & " has died."\n   Sleep\nEndIf\n}}}\n\x3C/div\x3E\nLater, we will develop better ending routines to cover both winning and losing.\n\nFor the new inventory routines, we have updated the /ManageInventory/ subroutine which directs the inventory commands to the proper processing functions.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Manages character inventory.\nSub ManageInventory()\n   Dim As String kch, ich\n   Dim As Integer ret\n   \n   DrawInventoryScreen\n   Do\n      kch = InKey\n      kch = UCase(kch)\n      \'Check to see if we have a key.\n      If kch \x3C\x3E "" Then\n         \'Process the eval command.\n         If kch = "V" Then\n            ret = ProcessEval()\n            \'Screen changed.\n            If ret = TRUE Then\n               DrawInventoryScreen\n            EndIf\n         EndIf\n         \'Process the eat and drink command.\n         If kch = "E" Then\n            ret = ProcessEatDrink()\n            \'Screen changed.\n            If ret = TRUE Then\n               DrawInventoryScreen\n            EndIf\n         EndIf\n         \'Process drop command.\n         If kch = "D" Then\n            ret = ProcessDrop()\n            \'Screen changed.\n            If ret = TRUE Then\n               DrawInventoryScreen\n            EndIf\n         EndIf\n         \'Process inspect command.\n         If kch = "I" Then\n            ret = ProcessInspect()\n            \'Screen changed.\n            If ret = TRUE Then\n               DrawInventoryScreen\n            EndIf\n         EndIf\n      EndIf\n      Sleep 1\n   Loop Until kch = key_esc\n   ClearKeys\nEnd Sub\n}}}\n\x3C/div\x3E\nWe have changed one key assignment here. Evaluation is now the /v/ key instead of the /e/ key. This is so we can use the e key for eating and drinking. Let\'s examine each of these new functions, starting with /Inspect/.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Processes the inspect command.\nFunction ProcessInspect() As Integer\n   Dim As String res, mask, desc\n   Dim As Integer i, iitem, ret = FALSE\n   Dim As invtype inv\n   Dim As tWidgets.btnID btn\n   Dim As tWidgets.tInputbox ib\n   Dim lines() As String\n      \n   \'Make sure there is something to evaluate in the inventory.\n   For i = pchar.LowInv To pchar.HighInv\n      iitem = pchar.HasInvItem(i)\n      If iitem = TRUE Then\n         \'Get the inv item.\n         pchar.GetInventoryItem i, inv\n         \'Build the mask.\n         mask &= Chr(i)\n      EndIf\n   Next\n   If Len(mask) = 0 Then\n      ShowMsg "Inpsect Items", "Nothing to inspect.", tWidgets.MsgBoxType.gmbOK\n   Else\n      \'Draws an input box on screen.\n      ib.Title = "Inspect Items"\n      ib.Prompt = "Select item(s) to drop (" & mask & ")"\n      ib.Row = 39\n      ib.EditMask = mask\n      ib.MaxLen = Len(mask)\n      ib.InputLen = Len(mask)\n      btn = ib.Inputbox(res)\n      \'Evaluate each item in the list.\n      If (btn \x3C\x3E tWidgets.btnID.gbnCancel) And (Len(res) \x3E 0) Then\n         \'Evaluate the list of items.\n         For i = 1 To Len(res)\n            iitem = Asc(res, i) \'Get index into character inventory.\n            \'Get the inv item.\n            pchar.GetInventoryItem iitem, inv\n            GetFullDesc lines(), inv\n            If UBound(lines) \x3E 0 Then\n               ShowMsgLines "Inspect Items", lines(), tWidgets.MsgBoxType.gmbOK\n            EndIf\n         Next\n      EndIf\n   EndIf\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nThe method here is much the same method we used for the Eval function we covered in the previous chapter. We iterate through the character inventory list and check each slot to see if it has an item in it by using the /HasInvItem/ method of the character object. HasInvItem checks both the character inventory array bounds and the item\'s classid to make sure we have a valid inventory item. If the item is valid, we add the array index to the mask and then present the user with an Inputbox so that he can make his selection.\n\nOnce the player has made his selection, we then grab each inventory item from the character\'s inventory and call the subroutine /GetFullDesc/ to get the full description of the item. GetFullDesc takes a variable length string array which it will fill with data. When the program returns from the description subroutine, we can check the upper bound of the array to make sure we have some data. If the upper bound is greater than 0, we know we have some data to display. Array index 0 is used as a sentinel value here, i.e., it is used to signal an empty array. It won\'t contain any data; it just tells us if the array has any data in it. It also simplifies the process of expanding the array as we will see. \n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Returns an extended description of an item.\nSub GetFullDesc(lines() As String, inv As invtype)\n   Dim As Integer idx = 0\n   \n   \'Reset the array.\n   ReDim lines(0 To idx) As String\n   \'Make sure we have something to inspect.\n   If inv.classid \x3C\x3E clNone Then\n      \'Select the item.\n      Select Case inv.classid\n         Case clSupplies \n            idx += 1\n            ReDim Preserve lines(0 to idx) As String\n            lines(idx) = inv.desc\n            Select Case inv.supply.id\n               Case supHealingHerb\n                  idx += 1\n                  ReDim Preserve lines(0 to idx) As String\n                  lines(idx) = "* Adds 50% max HP to current HP"\n                  idx += 1\n                  ReDim Preserve lines(0 to idx) As String\n                  lines(idx) = "* Magic: Max healing"\n               Case supHunkMeat\n                  idx += 1\n                  ReDim Preserve lines(0 to idx) As String\n                  lines(idx) = "* Adds 25% max HP to current HP"\n                  idx += 1\n                  ReDim Preserve lines(0 to idx) As String\n                  lines(idx) = "* Magic: Bonus to STR stat"\n               Case supBread\n                  idx += 1\n                  ReDim Preserve lines(0 to idx) As String\n                  lines(idx) = "* Adds 10% max HP to current HP"\n                  idx += 1\n                  ReDim Preserve lines(0 to idx) As String\n                  lines(idx) = "* Magic: Cure poison"\n               Case supBottleOil\n                  idx += 1\n                  ReDim Preserve lines(0 to idx) As String\n                  lines(idx) = "* Fuel for lantern"\n                  idx += 1\n                  ReDim Preserve lines(0 to idx) As String\n                  lines(idx) = "* Magic: See all tiles on map"\n            End Select\n            idx += 1\n            ReDim Preserve lines(0 to idx) As String\n            If IsEval(inv) = TRUE Then\n               lines(idx) = "* Item is evaluated"\n            Else\n               lines(idx) = "* Item is not evaluated"\n            End If\n      End Select\n   EndIf\n   \nEnd Sub\n}}}\n\x3C/div\x3E\nThe first thing we do in /GetFullDesc/ is to clear out the array by using the /ReDim/ statement. Our index value /idx/ is set to zero, so after the ReDim statement executes, our array has an upper bound of 0, and the array contents, if any, are cleared.\n\nWe then look at the passed inventory classid to determine the class of item. Since all we have right now are some supply items, this is all we need to check. We then check the id of the item, so that we know exactly what item we are examining so that we can build the description. Notice how we are expanding the array:\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\nidx += 1\nReDim Preserve lines(0 to idx) As String\n}}}\n\x3C/div\x3E\nWe increment idx and then use /ReDim Preserve/ to expand the array. The Preserve statement makes sure we don\'t lose any data we already have in the array. Since 0 is our sentinel value, we simply skip over it and start adding data at index 1. Once the array has been expanded, we add a line of description, and then repeat the process until we have a full description for the object. The string array is dynamic, since each item type will have different pieces of information associated with it, so the line count will vary from item to item. We will simply use 1 as the base index value and the UBound function to present however many lines of information we have in the array.\n\nIn the /ProcessInspect/ subroutine we display this description to the user in a multi-line messagebox as shown in the picture at the top of this chapter. Next is the drop command.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Processes the drop command.\nFunction ProcessDrop() As Integer\n   Dim As String res, mask, desc\n   Dim As Integer i, iret, iitem, ret = FALSE\n   Dim As invtype inv\n   Dim As tWidgets.btnID btn\n   Dim As tWidgets.tInputbox ib\n   Dim As vec mvec\n      \n   \'Make sure there is something to evaluate in the inventory.\n   For i = pchar.LowInv To pchar.HighInv\n      iitem = pchar.HasInvItem(i)\n      If iitem = TRUE Then\n         \'Get the inv item.\n         pchar.GetInventoryItem i, inv\n         \'Build the mask.\n         mask &= Chr(i)\n      EndIf\n   Next\n   If Len(mask) = 0 Then\n      ShowMsg "Drop Items", "Nothing to drop.", tWidgets.MsgBoxType.gmbOK\n   Else\n      \'Draws an input box on screen.\n      ib.Title = "Drop Items"\n      ib.Prompt = "Select item(s) to drop (" & mask & ")"\n      ib.Row = 39\n      ib.EditMask = mask\n      ib.MaxLen = Len(mask)\n      ib.InputLen = Len(mask)\n      btn = ib.Inputbox(res)\n      \'Evaluate each item in the list.\n      If (btn \x3C\x3E tWidgets.btnID.gbnCancel) And (Len(res) \x3E 0) Then\n         \'Evaluate the list of items.\n         For i = 1 To Len(res)\n            iitem = Asc(res, i) \'Get index into character inventory.\n            \'Get the inv item.\n            pchar.GetInventoryItem iitem, inv\n             \'Get the item description.\n             desc = GetInvItemDesc(inv)\n            \'Look for an empty space on the map.\n            iret = level.GetEmptySpot(mvec)\n            \'Found an enpty spot.\n            If iret = TRUE Then\n               \'Put the item on the map.\n               level.PutItemOnMap mvec.vx, mvec.vy, inv\n               \'Clear the item.\n               ClearInv inv\n               \'Put the item back into inventory.\n               pchar.AddInvItem iitem, inv\n               ret = TRUE\n               desc &= " was dropped."\n               ShowMsg "Drop Items", desc, tWidgets.MsgBoxType.gmbOK\n            Else\n               \'No empty spots.\n               desc = "No empty map tiles to drop item."\n               ShowMsg "Drop Items", desc, tWidgets.MsgBoxType.gmbOK\n               Exit For\n            EndIf\n         Next\n      EndIf\n   EndIf\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nIn the drop command handler we gather the item list in the same manner as before and then present the list to the player. Once they have made a selection, we iterate through the selected list and attempt to drop each item. Obviously, if we want to drop an item, we need to find an empty tile and we do that in the level object\'s /GetEmptySpot/.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Returns true if an empty spot is found and returns coords in vec.\nFunction levelobj.GetEmptySpot(v As vec) As Integer\n   Dim As Integer ret = FALSE, hi\n   Dim As vec ev\n   Dim As terrainids tid\n   \n   \'Check character spot.\n   ev.vx = pchar.Locx\n   ev.vy = pchar.Locy\n   hi = HasItem(ev.vx, ev.vy) \n   If  hi = FALSE Then\n      ret = TRUE\n      v = ev\n   Else\n      \'Check each tile around character.\n      For i As compass = north To nwest\n         ev.vx = pchar.Locx\n         ev.vy = pchar.Locy\n         ev += i\n         \'Get the tile type. \n         tid = GetTileID(ev.vx, ev.vy)\n         \'Check to see if it already has an item.\n         hi = HasItem(ev.vx, ev.vy)\n         \'If floor and no item, found space.\n         If (tid = tfloor) And (hi = FALSE) Then\n            v = ev\n            ret = TRUE\n            Exit For\n         EndIf\n      Next\n   EndIf\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nGetEmptySpot takes a vector object as a parameter. The vector object is defined in /vec.bi/ and it contains an x and y coordinate and some overloaded operators to make translating coordinates easier. Since the += operator is overloaded in the vector object, we can create a new vector simply by adding a compass direction. This makes it quite easy to iterate through all the possible map locations around the character. We do this in a simple For-Next loop and exit the loop if we find an empty spot. \n\nThe criteria for an empty tile is that it has to be a floor tile and it has to be empty. If the character was standing in a corridor, there will be 3 possible tiles, the tile where the character is standing and the tile in front of the character and the tile behind the character. If the character is in a room, there will be 9 possible locations; 1 that the character is standing on, and 1 for each compass direction. Walls, doors and tiles that are not empty, would not be counted as an empty tile. \n\nOnce a tile is found, the x and y location is loaded into the passed vector and the function returns TRUE to indicate that an empty tile was found. A return value of FALSE indicates that an empty tile could not be found.\n\nIf an empty tile is found we call the subroutine /PutItemOnMap/ in the map object.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Puts an item from passed inventory type to map.\nSub levelobj.PutItemOnMap(x As Integer, y As Integer, inv As invtype)\n    ClearInv _level.linv(x, y)\n    _level.linv(x, y) = inv\nEnd Sub\n}}}\n\x3C/div\x3E\nThis simply clears the target map inventory slot and places the item in the map inventory. We don\'t really need to clear the target location in this instance, since we know the spot is empty before we call this procedure, but in case we need to drop items for different reasons, we clear the inventory slot so that we keep the integrity of the data intact.\n\nOnce the item is dropped we then clear the character inventory slot and let the player know that the drop was successful.\n\n\x3Cdiv class="code"\x3E\n/dod.bas:ProcessDrop/\n{{{\n  \'Put the item on the map.\n  level.PutItemOnMap mvec.vx, mvec.vy, inv\n  \'Clear the item.\n  ClearInv inv\n  \'Put the item back into inventory.\n  pchar.AddInvItem iitem, inv\n  ret = TRUE\n  desc &= " was dropped."\n  ShowMsg "Drop Items", desc, tWidgets.MsgBoxType.gmbOK\n}}}\n\x3C/div\x3E\nWhen the player returns to the main display, they will see the dropped items on the map, just as you would expect.\n\nThe last procedure we need to look at is /ProcessEatDrink/.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\n...\n\n         For i = 1 To Len(res)\n            iitem = Asc(res, i) \'Get index into character inventory.\n            \'Get the inv item.\n            pchar.GetInventoryItem iitem, inv\n            \'Check the eval state.\n            evalstate =  IsEval(inv)\n           \'Check for magic item.\n            evalDR = GetEvalDR(inv)\n             \'Get the item description.\n             desc1 = GetInvItemDesc(inv)\n             desc2 = ""\n            \'Apply item to character.\n            If inv.supply.id = supHealingHerb Then\n               \'Evaluated magical item.\n               If (evalstate = TRUE) And (evalDR \x3E 0) Then\n                  pchar.CurrHP = pchar.MaxHP\n                  desc2 = " completely healed you!"\n               Else\n                  \'Does 50% healing.\n                  pchar.CurrHP = pchar.CurrHP + (pchar.MaxHP * .5)\n                  If pchar.CurrHP \x3E pchar.MaxHP Then\n                     pchar.CurrHP = pchar.MaxHP\n                  EndIf\n                  pchar.CurrHP = pchar.MaxHP\n                  desc2 = " added some health!"\n               EndIf\n            ElseIf inv.supply.id = supHunkMeat Then\n              \'Does 25% healing.\n               pchar.CurrHP = pchar.CurrHP + (pchar.MaxHP * .25)\n               If pchar.CurrHP \x3E pchar.MaxHP Then\n                  pchar.CurrHP = pchar.MaxHP\n                  desc2 = " added some health!"\n               EndIf\n               \'Evaluated magical item.\n               If (evalstate = TRUE) And (evalDR \x3E 0) Then\n                  pchar.BonStr = RandomRange(1, pchar.CurrStr)\n                  pchar.BonStrCnt = RandomRange(1, 100)  \n                  desc2 = " healed you and added some strength!"\n               EndIf\n            ElseIf inv.supply.id = supBread Then\n              \'Does 10% healing.\n               pchar.CurrHP = pchar.CurrHP + (pchar.MaxHP * .1)\n               If pchar.CurrHP \x3E pchar.MaxHP Then\n                  pchar.CurrHP = pchar.MaxHP\n                  desc2 = " added some health!"\n               EndIf\n               \'Evaluated magical item.\n               If (evalstate = TRUE) And (evalDR \x3E 0) Then\n                  \'Cure the poison if any.\n                  If pchar.Poisoned = TRUE Then\n                     pchar.Poisoned = FALSE\n                     pchar.PoisonStr = 0\n                     desc2 = " added some health and cured your poison!"\n                  End If\n               EndIf\n            EndIf\n            ShowMsg "Eat/Drink", desc1 & desc2, tWidgets.MsgBoxType.gmbOK    \n            \'Clear the item.\n            ClearInv inv\n            \'Put the item back into inventory.\n            pchar.AddInvItem iitem, inv\n         Next\n\n...\n}}}\n\x3C/div\x3E\nSince the selection process is identical to the other procedures, we are only going to look at the main code here, which implements the effects the item being consumed. Notice that we get the evalualtion state of the item {{{evalstate =  IsEval(inv)}}}. Magical effects only work if an item is evaluated, so we need to check that before any action is taken. Of course the item needs to magical for a magical effect, and we determine that by getting the DR rating of the item {{{evalDR = GetEvalDR(inv)}}}. In order to apply the magical effect, we need to check both of these states {{{If (evalstate = TRUE) And (evalDR \x3E 0) Then}}}. If the /If/ statement is TRUE then we apply the magical effect along with the base effect as shown in the following snippet.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n...\n    ElseIf inv.supply.id = supHunkMeat Then\n        \'Does 25% healing.\n        pchar.CurrHP = pchar.CurrHP + (pchar.MaxHP * .25)\n        If pchar.CurrHP \x3E pchar.MaxHP Then\n            pchar.CurrHP = pchar.MaxHP\n            desc2 = " added some health!"\n        EndIf\n        \'Evaluated magical item.\n        If (evalstate = TRUE) And (evalDR \x3E 0) Then\n            pchar.BonStr = RandomRange(1, pchar.CurrStr)\n            pchar.BonStrCnt = RandomRange(1, 100)  \n            desc2 = " healed you and added some strength!"\n        EndIf\n...\n}}}\n\x3C/div\x3E\nThe exception here is the healing herb, since the magical effect of the healing herb is to completely restore the health of the character there is no point in applying the base effect which applies 50% of max HP to the current HP. As in the other functions, a message is presented to the player to let them know the result of consuming the item.\n\nThe next command we need to implement is Wield/Wear and in order to do that we need to add some armor and weapons. There are other items we need to add as well, such as rings, necklaces and spell books, but we are working toward our next major milestone, a basic playable game. Once we have armor and weapons we will add some monsters and we will have a real playable game. This will really make our game world come alive and will be a nice reward for all the hard work we have already put into the game.\n',
'= 15: Armor and Shields\n\x3Cdiv align="center"\x3E\x3Cimg src=\'images/armor.png\' /\x3E\x3C/div\x3E\n\nThe next set of items we need to add to the program is armor and shields. In order for these to work properly, we also have to code in the Wield/Wear inventory command. This is where a slight complication sets in. There are really two processes at work here; putting an item on and taking an item off. Since we are actually working with two different actions, it makes sense to create two commands. One to put something on, that is E(q)uip and item, and one to take it off, that is (U)equip an item. That is what we are going to implement in this chapter.\n\nAdding in new inventory items is the easy part here. We have already coded all the methods we need to implement inventory items, we simply need to plug in the armor and shield definitions to our exiting code base, and we will have the new items available to us. However, we need to update the character object to hold the items, and we need to update the existing inventory commands to handle the held items. So there is quite a bit of code work we need to do in this chapter, but when we finish this update, adding the rest of the items to the program will be easy.\n\nFirst, let\'s look at the armor and shield definitions.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Armor ids\nEnum armorids\n   armArmorNone\n   armCloth        \'Cloth armor 1% damage reduction\n   armLeather      \'Leather armor: 5% damage reduction\n   armCuirboli     \'Cuirboli armor: 10 % damage reduction\n   armRing         \'Ring armor: 20% damage reduction\n   armBrigantine   \'Brigantine armor: 30% dam reduction\n   armChain        \'Chain armor: 50 % dam reduction\n   armScale        \'Scale armor: 70% dam reduction\n   armPlate        \'Plate armor: 90% dam reduction\nEnd Enum\n\n\'Shield ids\nEnum shieldids\n   shldShieldNone\n   shldLeather    \'Shield amounts same as armor.\n   shldCuirboli\n   shldRing\n   shldBrigantine\n   shldChain\n   shldScale\n   shldPlate\nEnd Enum\n}}}\n\x3C/div\x3E\nThese are the type of armor and shields we are going to have in the game. As you can see from the comments, armor behaves a little differently in /Dungeon of Doom/ than it does in other role-playing systems. Armor doesn\'t affect the character\'s ability to be hit; it just reduces the amount of damage inflicted if the character is hit. Whether or not a character is hit is determined by the different defense factors of the character. If a character is hit, armor reduces the amount of damage by a certain percentage. Cloth armor reduces damage by 1% while plate armor reduces the damage inflicted by 90%. The protection comes at a price however; the more protection armor provides, the higher the strength required to use that armor. The strength requirement keeps the unpredictability of the random number generator from favoring a lucky player too much.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Wield slots in character inventory.\nEnum wieldpos\n   wNone\n   wPrimary\n   wSecondary\n   wArmor\n   wNeck\n   wRingRt\n   wRingLt\nEnd Enum\n\n\'Armor type\nType armortype\n   id As armorids       \'Armor type\n   evaldr As Integer    \'Evaluation difficutly. Evaldr \x3E 0 then magical item.\n   eval As Integer      \'Is item evaluated.\n   effect As Integer    \'Magical spell.\n   sdesc As String * 30 \'Secret name/description for magical items. Revealed when evaluated.\n   noise As Integer     \'The amount of noise item generates, includes use and in character inventory.\n   use As itemuse       \'How the item is used.\n   dampct As Single     \'Percentage of damage reduction.\n   struse As Integer    \'Strength required to use.\n   wslot(1 To 2) As wieldpos \'Slot item is held in. Could be in up to 2 slots.\nEnd Type\n\n\'Shield type\nType shieldtype\n   id As shieldids       \'Shield type\n   evaldr As Integer    \'Evaluation difficutly. Evaldr \x3E 0 then magical item.\n   eval As Integer      \'Is item evaluated.\n   effect As integer    \'Magical spell.\n   sdesc As String * 30 \'Secret name/description for magical items. Revealed when evaluated.\n   noise As Integer     \'The amount of noise item generates, includes use and in character inventory.\n   use As itemuse       \'How the item is used.\n   dampct As Single     \'Percentage of damage reduction.\n   struse As Integer    \'Strength required to use.\n   wslot(1 To 2) As wieldpos \'Slot item is held in. Could be in up to 2 slots.\nEnd Type\n}}}\n\x3C/div\x3E\nThe armor and shield Type Defs contain quite a bit of information. Some of the fields you have seen before, such as the id, eval, noise and use. The new fields are /dampct/ which is the damage reduction factor, /struse/ which is the strength required to use the item and the /wslot/ array which identifies which held slot the item is used as shown in the /wieldpos/ enumeration. You\'ll see that used when we get to the equip and unequip commands.\n\nIn DOD, magical armor and weapons contain spells that can be cast, they aren\'t just enchanted like the supply items. Since we haven\'t gotten to the magic code yet, we just put in some place holders for the item spells which we will fill in later in the project.\n\nTo generate the new items, we need to update each of the existing item subroutines in /inv.bi/. The process is simple; we just add a new section in each sub for armor and shields. For example, the /ClearInv/ sub is updated by adding a new case condition in the Select Case structure.\n\n\x3Cdiv class="code"\x3E\n/inv.bi:ClearInv/\n{{{\n         Case clArmor\n            inv.armor.id = armArmorNone\n            inv.armor.evaldr = 0\n            inv.armor.eval = FALSE\n            inv.armor.effect = 0\n            inv.armor.sdesc = ""\n            inv.armor.noise = 0\n            inv.armor.use = UseNone\n            inv.armor.dampct = 0\n            inv.armor.struse = 0\n            inv.armor.wslot(1) = wNone\n            inv.armor.wslot(2) = wNone\n         Case clShield\n            inv.shield.id = shldShieldNone\n            inv.shield.evaldr = 0\n            inv.shield.eval = FALSE\n            inv.shield.effect = 0\n            inv.shield.sdesc = ""\n            inv.shield.noise = 0\n            inv.shield.use = UseNone\n            inv.shield.dampct = 0\n            inv.shield.struse = 0\n            inv.shield.wslot(1) = wNone\n            inv.shield.wslot(2) = wNone\n\n}}}\n\x3C/div\x3E\nThe other subroutines are updated in a similar manner. To create an armor item, we invoke the /GenerateArmor/ subroutine.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Generate new armor item.\nSub GenerateArmor(inv As invtype, currlevel As Integer, arid As armorids = armArmorNone)\n   Dim item As armorids \n   Dim As Integer isMagic = ItemIsMagic(currlevel) \n\n   \'These are the common items.\n   If arid = armArmorNone Then\n      item = RandomRange(armCloth, armPlate)\n      inv.armor.id = item\n   Else\n      item = arid\n      inv.armor.id = item\n   End If\n   inv.icon = Chr(234)\n   inv.iconclr = fbEmeraldGreen\n   inv.armor.use = useWieldWear\n   inv.armor.eval = FALSE\n   inv.armor.wslot(1) = wArmor\n   \'magic item.\n   If IsMagic = TRUE Then\n      inv.armor.evaldr = RandomRange(currlevel, currlevel * 2)\n      inv.armor.effect = 0\n   EndIf\n   \'Set the armor type and amount.\n   Select Case item\n      Case armCloth\n         inv.desc = "Cloth Armor" \'Cloth armor: 1% damage reduction.\n         inv.armor.noise = 1\n         inv.armor.dampct = .01\n         inv.armor.struse = 10\n      Case armLeather \'Leather armor: 5% damage reduction\n         inv.desc = "Leather Armor"\n         inv.armor.noise = 5\n         inv.armor.dampct = .05\n         inv.armor.struse = 50\n      Case armCuirboli \'Cuirboli armor: 10 % damage reduction\n         inv.desc = "Cuirboli Armor"\n         inv.armor.noise = 10\n         inv.armor.dampct = .10\n         inv.armor.struse = 100\n      Case armRing  \'Ring armor: 20% damage reduction  \n         inv.desc = "Ring Armor"\n         inv.armor.noise = 15\n         inv.armor.dampct = .20\n         inv.armor.struse = 150\n      Case armBrigantine \'Brigantine armor: 30% dam reduction  \n         inv.desc = "Brigantine Armor"\n         inv.armor.noise = 20\n         inv.armor.dampct = .30\n         inv.armor.struse = 200\n      Case armChain \'Chain armor: 50 % dam reduction\n         inv.desc = "Chain Armor"\n         inv.armor.noise = 25\n         inv.armor.dampct = .50\n         inv.armor.struse = 250\n      Case armScale \'Scale armor: 70% dam reduction\n         inv.desc = "Scale Armor"\n         inv.armor.noise = 30\n         inv.armor.dampct = .70\n         inv.armor.struse = 300\n      Case armPlate \'Plate armor: 90% dam reduction\n         inv.desc = "Plate Armor"\n         inv.armor.noise = 35\n         inv.armor.dampct = .90\n         inv.armor.struse = 350\n   End Select\n   \'Set the complete secret description.\n   If IsMagic = TRUE Then\n      inv.armor.sdesc = inv.desc \n   Else\n      inv.armor.sdesc = inv.desc\n   EndIf\nEnd Sub\n}}}\n\x3C/div\x3E\nThis is nearly identical to the supply generation subroutine, it just contains the armor code. The shield generation subroutine, /GenerateShield/ is identical to the armor generation, since a shield is just an additional armor value that is held. We could have combined both subroutines into one, but for clarity, I have created two separate subroutines so that we can see the program flow better. \n\nIf you scan the rest of the code in inv.bi you will see that the same procedure is used in all of the inventory routines. We have just added a new /If/ or new /Case/ statement to handle the new items, and we will follow the same procedure for all the other items we need to add to the program.\n\nOnce the armor and shields are generated and placed on the map, we need to be able to equip them, and that brings us to the equip and unequip inventory commands.\n\nIn the /ManageInventory/ subroutine in /dod.bas/ we have added new selections for the equip and unequip commands.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n         \'Process unequip item.\n         If kch = "U" Then\n            ret = ProcessUnequip()\n            \'Screen changed.\n            If ret = TRUE Then\n               DrawInventoryScreen\n            EndIf\n         EndIf\n         \'Process equip item.\n         If kch = "Q" Then\n            ret = ProcessEquip()\n            \'Screen changed.\n            If ret = TRUE Then\n               DrawInventoryScreen\n            EndIf\n         EndIf\n\n}}}\n\x3C/div\x3E\nThe format of both of these new subroutines is the same as the other inventory commands, so they should look familiar.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Process the equip menu item.\nFunction ProcessEquip() As Integer\n   Dim As String res, mask, desc\n   Dim As Integer i, iret, iitem, idx, ret = FALSE\n   Dim As invtype inv\n   Dim As tWidgets.btnID btn\n   Dim As tWidgets.tInputbox ib\n   Dim As vec mvec\n   Dim slot As Integer\n      \n   \'Make sure there is something to process in the inventory.\n   For i = pchar.LowInv To pchar.HighInv\n      iitem = pchar.HasInvItem(i)\n      If iitem = TRUE Then\n         \'Get the inv item.\n         pchar.GetInventoryItem i, inv\n         \'Is the item evaluated.\n         iret = MatchUse(inv, useWieldWear)\n         \'An item to evaluate.\n         If iret = TRUE Then\n            \'Build the mask.\n            mask &= Chr(i)\n         End If\n      EndIf\n   Next\n   If Len(mask) = 0 Then\n      ShowMsg "Equip Items", "Nothing to equip.", tWidgets.MsgBoxType.gmbOK\n   Else\n      \'Draws an input box on screen.\n      ib.Title = "Equip Items"\n      ib.Prompt = "Select item(s) to equip (" & mask & ")"\n      ib.Row = 39\n      ib.EditMask = mask\n      ib.MaxLen = Len(mask)\n      ib.InputLen = Len(mask)\n      btn = ib.Inputbox(res)\n      \'Evaluate each item in the list.\n      If (btn \x3C\x3E tWidgets.btnID.gbnCancel) And (Len(res) \x3E 0) Then\n         \'Evaluate the list of items.\n         For i = 1 To Len(res)\n            iitem = Asc(res, i) \'Get index into wield inventory.\n            \'Get the inv item.\n            pchar.GetInventoryItem iitem, inv\n            \'Get the item description.\n            desc = GetInvItemDesc(inv)\n            iret = FALSE\n            idx = 0\n            \'Check slot 1.\n            slot = GetInvWSlot(inv, 1)\n            If slot \x3C\x3E wNone Then\n               \'Check character to see if slot is open.\n               If pchar.HasInvItem(slot) = FALSE Then\n                  idx = slot\n                  iret = TRUE\n               EndIf\n            Else\n               \'Check slot 2.\n               slot = GetInvWSlot(inv, 2)\n               If slot \x3C\x3E wNone Then\n                  \'Check character to see if slot is open.\n                  If pchar.HasInvItem(slot) = FALSE Then\n                     idx = slot\n                     iret = TRUE\n                  EndIf\n               End If\n            EndIf\n            \'Did we find an empty slot?\n            If iret = TRUE Then\n               \'Check to make sure character can equip item.\n               If pchar.CanWear(inv) = TRUE Then\n                  \'Put the item wield position.\n                  pchar.AddInvItem idx, inv\n                  \'Clear the item.\n                  ClearInv inv\n                  \'Update the held slot.\n                  pchar.AddInvItem iitem, inv\n                  ret = TRUE\n                  desc &= " was equipped."\n                  ShowMsg "Equip Items", desc, tWidgets.MsgBoxType.gmbOK\n               Else\n                  ShowMsg "Equip Items", "Not enough strength to equip " & desc & ".",tWidgets.MsgBoxType.gmbOK\n               End if\n            Else\n               \'No empty spots.\n               desc = "No empty slots to equip item."\n               ShowMsg "Equip Items", desc, tWidgets.MsgBoxType.gmbOK\n               Exit For\n            EndIf\n         Next\n      EndIf\n   EndIf\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nJust as we did in the inspect command subroutine, we are using the /MatchUse/ function to determine if the inventory item is a wearable item. Once we have the list, we present it to the user so that they make thier selection. Once they make the selection, we iterate through the selection list and see if we can place the item. Each type of wearable item has a location that it can fit into based on the position Enumeration defined in /inv.bi/. We get the slot that the item should go into in the /GetInvWSlot/ slot function.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Returns the inventory slots for wield items.\nFunction GetInvWSlot(inv As invtype, slotnum As Integer) As Integer\n   Dim As Integer ret = wNone\n   \n   If inv.classid = clArmor Then\n      If slotnum \x3E= LBound(inv.armor.wslot) And slotnum \x3C= UBound(inv.armor.wslot) Then \n         ret = inv.armor.wslot(slotnum)\n      End If\n   ElseIf inv.classid = clShield Then\n      If slotnum \x3E= LBound(inv.shield.wslot) And slotnum \x3C= UBound(inv.shield.wslot) Then \n         ret = inv.shield.wslot(slotnum)\n      End If\n   EndIf\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nThe /slotnum/ parameter corrsponds to the index of the /wslot/ array in the armor and shield Type Defs. Since there can be at most two slots an item may fit into, a weapon for example, could go into either the Primary or Secondary slots, we pass the array index we want to check. If the function returns wNone (0), then there is no slot indentifier in the array location we are examining. Armor can only fit in the armor slot, so wslot(1) would contain the wArmor identifer, and wslot(2) would contain wNone or 0.\n\nWhat do these slot numbers represent? They are the indexes into the held array defined in the character type def.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\ncwield(wPrimary To wRingLt) As invtype \'Active items: 1 = primary weapon, 2 = secondary weapon/shield, 3 = armor, 4 = necklace, 5 = ring rt, 6 = rint lt\n}}}\n\x3C/div\x3E\nThey are also the menu item selections in the inventory screen as shown in the image at the beginning of the chapter. By linking the menu item selections with the actual array dimensions we simplify the management of the items when we are trying to place or remove a held item. We see this in the code that actually places an item in the /ProcessEquip/ function.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n            \'Check slot 1.\n            slot = GetInvWSlot(inv, 1)\n            If slot \x3C\x3E wNone Then\n               \'Check character to see if slot is open.\n               If pchar.HasInvItem(slot) = FALSE Then\n                  idx = slot\n                  iret = TRUE\n               EndIf\n            Else\n               \'Check slot 2.\n               slot = GetInvWSlot(inv, 2)\n               If slot \x3C\x3E wNone Then\n                  \'Check character to see if slot is open.\n                  If pchar.HasInvItem(slot) = FALSE Then\n                     idx = slot\n                     iret = TRUE\n                  EndIf\n               End If\n            EndIf\n\n}}}\n\x3C/div\x3E\nNotice we use the index we have retrieved from the /GetInvWSlot/ to check to see if the item slot is free by calling the character /HasInvItem/ function, which we have updated to handle the held items.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n\'Returns True if item exists in inventory slot.\nFunction character.HasInvItem(idx As Integer) As Integer\n   \'Check held items.\n   If idx \x3E= LBound(_cinfo.cwield) And idx \x3C= UBound(_cinfo.cwield) Then\n      \'Check the class id.\n      If _cinfo.cwield(idx).classid = clNone Then\n         Return FALSE\n      Else\n         Return TRUE\n      EndIf\n   Else\n      \'Validate the index.\n      If idx \x3E= LBound(_cinfo.cinv) And idx \x3C= UBound(_cinfo.cinv) Then\n         \'Check the class id.\n         If _cinfo.cinv(idx).classid = clNone Then\n            Return FALSE\n         Else\n            Return TRUE\n         EndIf\n      Else\n         Return FALSE\n      End If   \n   EndIf\n   \nEnd Function\n}}}\n\x3C/div\x3E\nThe first /If/ selection checks to see if the index is in range of the held array contained in the character object. Since the id we have retrieved for the item corresponds to the actual held array index, it is trivial to match the item to its location. This is what one-to-one correspondence does for you; it enables you to simplify your code, and creates a code trail that is easy to follow and easy to debug.\n\nIf the slot is empty, then we simply place the item in the slot and clear the inventory item in the character inventory.\n\n\x3Cdiv class="code"\x3E\n/dod.bas:ProcessEquip/\n{{{\n            \'Did we find an empty slot?\n            If iret = TRUE Then\n               \'Check to make sure character can equip item.\n               If pchar.CanWear(inv) = TRUE Then\n                  \'Put the item wield position.\n                  pchar.AddInvItem idx, inv\n                  \'Clear the item.\n                  ClearInv inv\n                  \'Update the held slot.\n                  pchar.AddInvItem iitem, inv\n                  ret = TRUE\n                  desc &= " was equipped."\n                  ShowMsg "Equip Items", desc, tWidgets.MsgBoxType.gmbOK\n               Else\n                  ShowMsg "Equip Items", "Not enough strength to equip " & desc & ".",tWidgets.MsgBoxType.gmbOK\n               End if\n            Else\n               \'No empty spots.\n               desc = "No empty slots to equip item."\n               ShowMsg "Equip Items", desc, tWidgets.MsgBoxType.gmbOK\n               Exit For\n            EndIf\n\n}}}\n\x3C/div\x3E\nBefore we add the item to the held inventory we check to make sure that the character has enough stremgth to wear the item. This is where the strength restriction comes in to play for armor and shields. We have added a new function to the character object /CanWear/ to see if the character can wear the item.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n\'Returns True if character can wear item.\nFunction character.CanWear(inv As invtype) As Integer\n   Dim As Integer ret = TRUE\n   \n   If inv.classid = clArmor Then\n      If inv.armor.struse \x3E _cinfo.stratt(0) Then\n         ret = FALSE\n      EndIf\n   EndIf\n\n   If inv.classid = clShield Then\n      If inv.shield.struse \x3E _cinfo.stratt(0) Then\n         ret = FALSE\n      EndIf\n   EndIf\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nThis checks the /struse/ field of the armor or shield to see if the character has enough strength. Notice that the default return value is TRUE. The only items with a strength restricton are armor and shields, so for everything else, we will return TRUE so that the character can wear or wield them. If the character can wear the item, then we put the item into the held inventory and clear the corrsponding pack inventory slot.\n\nFor the unequip command, we just do the opposite of what we are doing here.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Process the unequip menu item.\nFunction ProcessUnEquip() As Integer\n   Dim As String res, mask, desc\n   Dim As Integer i, iret, iitem, ret = FALSE\n   Dim As invtype inv\n   Dim As tWidgets.btnID btn\n   Dim As tWidgets.tInputbox ib\n   Dim As vec mvec\n      \n   \'Make sure there is something to process in the inventory.\n   For i = wPrimary To wRingLt\n      iitem = pchar.HasInvItem(i)\n      If iitem = TRUE Then\n         \'Build the mask.\n         mask &= Str(i)\n      EndIf\n   Next\n   If Len(mask) = 0 Then\n      ShowMsg "Unequip Items", "Nothing to unequip.", tWidgets.MsgBoxType.gmbOK\n   Else\n      \'Draws an input box on screen.\n      ib.Title = "Unequip Items"\n      ib.Prompt = "Select item(s) to unequip (" & mask & ")"\n      ib.Row = 39\n      ib.EditMask = mask\n      ib.MaxLen = Len(mask)\n      ib.InputLen = Len(mask)\n      btn = ib.Inputbox(res)\n      \'Evaluate each item in the list.\n      If (btn \x3C\x3E tWidgets.btnID.gbnCancel) And (Len(res) \x3E 0) Then\n         \'Evaluate the list of items.\n         For i = 1 To Len(res)\n            iitem = Val(Mid(res, i, 1)) \'Get index into character inventory.\n            \'Get the inv item.\n            pchar.GetInventoryItem iitem, inv\n            \'Get the item description.\n            desc = GetInvItemDesc(inv)\n            \'Look for an empty inventory slot.\n            iret = pchar.GetFreeInventoryIndex\n            \'Did we find an empty slot?\n            If iret \x3E -1 Then\n               \'Put the item back into inventory.\n               pchar.AddInvItem iret, inv\n               \'Clear the item.\n               ClearInv inv\n               \'Update the held slot.\n               pchar.AddInvItem iitem, inv\n               ret = TRUE\n               desc &= " was unequipped."\n               ShowMsg "Unequip Items", desc, tWidgets.MsgBoxType.gmbOK\n            Else\n               \'No empty spots.\n               desc = "No empty inventory slots to unequip item."\n               ShowMsg "Unequip Items", desc, tWidgets.MsgBoxType.gmbOK\n               Exit For\n            EndIf\n         Next\n      EndIf\n   EndIf\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nInstead of iterating through the character inventory list to generate our selection, we are iterating through the held array, {{{For i = wPrimary To wRingLt}}}. We call /HasInvItem/ to see if the slot has an item, and if so, then we add the slot number to the selection list. The other difference here is that instead of using the ASCII code like we do for the pack inventory items, we are using the actual slot numbers {{{mask &= Str(i)}}}.\n\nOnce the list is generated and the player makes his selection, then we process the list almost exactly like we processed the drop list. Again, the difference here is that we need to use the actual number of the list item, and not the ASCII code {{{iitem = Val(Mid(res, i, 1))}}}. Other than that, the process is much like the drop process; we move the item from the held inventory into the character\'s pack inventory. Use the /GetFreeInventoryIndex/ function to find an empty inventory slot just as we did in the get item command, since in order to unequip an item, there needs to be an empty slot in the pack to put the item.\n\nNow that the character can wear an item, we also need to display that item on the main screen. In the /DrawMainScreen/ subroutine we have updated the code so that any held items will show up in the information panel.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n   \'Check for held item.\n   If pchar.HasInvItem(wPrimary) = TRUE Then\n      pchar.GetInventoryItem wPrimary, inv\n      idesc = GetInvItemDesc(inv)\n   Else\n      idesc = ""\n   EndIf\n   PutText "Primary: " & idesc, row, col\n   row += 1\n   If pchar.HasInvItem(wSecondary) = TRUE Then\n      pchar.GetInventoryItem wSecondary, inv\n      idesc = GetInvItemDesc(inv)\n   Else\n      idesc = ""\n   EndIf\n   PutText "Secondary: " & idesc, row, col\n   row += 1\n   If pchar.HasInvItem(wArmor) = TRUE Then\n      pchar.GetInventoryItem wArmor, inv\n      idesc = GetInvItemDesc(inv)\n   Else\n      idesc = ""\n   EndIf\n   PutText "Armor: " & idesc, row, col\n   row += 1\n   If pchar.HasInvItem(wNeck) = TRUE Then\n      pchar.GetInventoryItem wNeck, inv\n      idesc = GetInvItemDesc(inv)\n   Else\n      idesc = ""\n   EndIf\n   PutText "Neck: " & idesc, row, col\n   row += 1\n   If pchar.HasInvItem(wRingRt) = TRUE Then\n      pchar.GetInventoryItem wRingRt, inv\n      idesc = GetInvItemDesc(inv)\n   Else\n      idesc = ""\n   EndIf\n   PutText "Ring RT: " & idesc, row, col\n   row += 1\n   If pchar.HasInvItem(wRingLt) = TRUE Then\n      pchar.GetInventoryItem wRingLt, inv\n      idesc = GetInvItemDesc(inv)\n   Else\n      idesc = ""\n   EndIf\n   PutText "Ring LT: " & idesc, row, col\n}}}\n\x3C/div\x3E\nHere we just check each held slot for an item, and get the description of the item if the slot is not empty. The description is then printed to the screen.\n\nOnce last thing we have to do is to start the character with some cloth armor. We do that in the /GenerateCharacter/ subroutine.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n   \'Add cloth armor to character.\n   inv.classid = clArmor\n   GenerateArmor inv, 1, armCloth\n   SetInvEval inv, TRUE\n   AddInvItem wArmor, inv\n   \'Add dagger to character\n}}}\n\x3C/div\x3E\nAfter the character is created, we add in some cloth armor and put it into the armor slot. Notice that there is a place holder for adding a dagger. We will also start the character with a dagger so that they aren\'t completely defenseless when they start the game.\n\nNow that we have armor, we need weapons and we will add those in the next chapter.\n',
'= 16: Weapons\n\x3Cdiv align="center"\x3E\x3Cimg src=\'images/weapon.png\' /\x3E\x3C/div\x3E\n\nBefore we get to the main weapon code, we need to look at a couple of code updates. In the last update a strange bug appeared where a phantom item would be placed on the map. It contained a supply id but nothing else. Looking over the supply generation code I found that the enumeration used by the random generator was accidentally changed. Instead of an upper limit of supBottleOil, a bottle of oil, it was armPlate, plate armor. I am not sure how this happened, but it did and was generating phantom items. I fixed that problem, and added in some defensive coding so that if it happens again (and it may) it will be obvious something strange is going on. (Note: The oil was removed in later chapters due to not implementing dark dungeons. See the notes for how ideas on how to implement dark dungeon levels.)\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n      Case Else\n         inv.desc = "Uknown Supply"\n         inv.supply.noise = 0\n         inv.icon = Chr(63)\n         inv.iconclr = fbYellow\n         inv.supply.eval = FALSE\n         inv.supply.use = useNone\n\n}}}\n\x3C/div\x3E\nIn the generation subroutines I added a Case Else to mark an item that falls through the Select as /Unknown/. This will flag it as a problem and will make it easier to find where the problem is occurring. This isn\'t fool-proof, but it will certainly help and doesn\'t impact the performance of the system that much.\n\nThe other change is in the /GetFullDesc/ function which is used by the /ProcessInspect/ subroutine. We had a series of Select Case statements here, but only the supplies need a Select, so I simplified that code.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Returns an extended description of an item.\nSub GetFullDesc(lines() As String, inv As invtype)\n   Dim As Integer idx = 0\n   \n   \'Reset the array.\n   ReDim lines(0 To idx) As String\n   \'Make sure we have something to inspect.\n   If inv.classid \x3C\x3E clNone Then\n      \'Select the item.\n      Select Case inv.classid\n         Case clSupplies \n            idx += 1\n            ReDim Preserve lines(0 to idx) As String\n            lines(idx) = GetInvItemDesc(inv)\n            Select Case inv.supply.id\n               Case supHealingHerb\n                  idx += 1\n                  ReDim Preserve lines(0 to idx) As String\n                  lines(idx) = "* Adds 50% max HP to current HP"\n                  idx += 1\n                  ReDim Preserve lines(0 to idx) As String\n                  lines(idx) = "* Magic: Max healing"\n               Case supHunkMeat\n                  idx += 1\n                  ReDim Preserve lines(0 to idx) As String\n                  lines(idx) = "* Adds 25% max HP to current HP"\n                  idx += 1\n                  ReDim Preserve lines(0 to idx) As String\n                  lines(idx) = "* Magic: Bonus to STR stat"\n               Case supBread\n                  idx += 1\n                  ReDim Preserve lines(0 to idx) As String\n                  lines(idx) = "* Adds 10% max HP to current HP"\n                  idx += 1\n                  ReDim Preserve lines(0 to idx) As String\n                  lines(idx) = "* Magic: Cure poison"\n               Case supBottleOil\n                  idx += 1\n                  ReDim Preserve lines(0 to idx) As String\n                  lines(idx) = "* Fuel for lantern"\n                  idx += 1\n                  ReDim Preserve lines(0 to idx) As String\n                  lines(idx) = "* Magic: See all tiles on map"\n            End Select\n            idx += 1\n            ReDim Preserve lines(0 to idx) As String\n            If IsEval(inv) = TRUE Then\n               lines(idx) = "* Item is evaluated"\n            Else\n               lines(idx) = "* Item is not evaluated"\n            End If\n         Case clArmor\n            idx += 1\n            ReDim Preserve lines(0 to idx) As String\n            lines(idx) = GetInvItemDesc(inv)\n            idx += 1\n            ReDim Preserve lines(0 to idx) As String\n            lines(idx) = "* " & (inv.armor.dampct * 100) & "% damage reduction"\n            idx += 1\n            ReDim Preserve lines(0 to idx) As String\n            lines(idx) = "* Strength Required: " & inv.armor.struse\n            idx += 1\n            ReDim Preserve lines(0 to idx) As String\n            lines(idx) = "* Magic: Defense and Healing"\n            idx += 1\n            ReDim Preserve lines(0 to idx) As String\n            If IsEval(inv) = TRUE Then\n               lines(idx) = "* Item is evaluated"\n            Else\n               lines(idx) = "* Item is not evaluated"\n            End If\n         Case clShield\n            idx += 1\n            ReDim Preserve lines(0 to idx) As String\n            lines(idx) = GetInvItemDesc(inv)\n            idx += 1\n            ReDim Preserve lines(0 to idx) As String\n            lines(idx) = "* " & (inv.shield.dampct * 100) & "% damage reduction"\n            idx += 1\n            ReDim Preserve lines(0 to idx) As String\n            lines(idx) = "* Strength Required: " & inv.shield.struse\n            idx += 1\n            ReDim Preserve lines(0 to idx) As String\n            lines(idx) = "* Magic: Defense and Healing"\n            idx += 1\n            ReDim Preserve lines(0 to idx) As String\n            If IsEval(inv) = TRUE Then\n               lines(idx) = "* Item is evaluated"\n            Else\n               lines(idx) = "* Item is not evaluated"\n            End If\n         Case clWeapon\n            idx += 1\n            ReDim Preserve lines(0 to idx) As String\n            lines(idx) = GetInvItemDesc(inv)\n            idx += 1\n            ReDim Preserve lines(0 to idx) As String\n            lines(idx) = "* " & inv.weapon.dam & " weapon damage"\n            idx += 1\n            ReDim Preserve lines(0 to idx) As String\n            lines(idx) = "* Hands Required: " & inv.weapon.hands\n            idx += 1\n            ReDim Preserve lines(0 to idx) As String\n            lines(idx) = "* Magic: Offense and Defense"\n            idx += 1\n            ReDim Preserve lines(0 to idx) As String\n            If IsEval(inv) = TRUE Then\n               lines(idx) = "* Item is evaluated"\n            Else\n               lines(idx) = "* Item is not evaluated"\n            End If\n      End Select\n   EndIf\n   \nEnd Sub\n}}}\n\x3C/div\x3E\nNotice that armor, shield and weapons only have a single block of code and not a Select Case since they really don\'t need it. This makes the code much more simple and easier to manage. Now for the weapon code.\n\nLike we have done for all the other items, we need to build a list of weapons and create a Type Def to hold the data.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Weapon ids.\nEnum weaponids\n   wpNone\n   wpClub            \'1 hand, dam 4 chr:33\n   wpWarclub         \'1 hand, dam 4\n   wpCudgel          \'1 hand, dam 5\n   wpDagger          \'1 hand, dam 4 chr: 173\n   wpLongknife       \'1 hand, dam 6\n   wpSmallsword      \'1 hand, dam 4\n   wpShortsword      \'1 hand, dam 6\n   wpRapier          \'1 hand, dam 9\n   wpBroadsword      \'1 hands, dam 12\n   wpScimitar        \'1 hand, dam 10\n   wpKatana          \'2 hands, dam 12\n   wpLongsword       \'2 hands, dam 14\n   wpClaymore        \'2 hands, dam 16\n   wpGreatsword      \'2 hands, dam 18\n   wpOdinsword       \'2 hands, dam 20\n   wpHellguard       \'2 hands, dam 30\n   wpQuarterstaff    \'2 hands, dam 4\n   wpLongstaff       \'2 hands, dam 6\n   wpPolearm         \'2 hands, dam 8\n   wpLightspear      \'2 hands, dam 7 chr: 179\n   wpHeavyspear      \'2 hands, dam 9\n   wpTrident         \'2 hands, dam 10\n   wpGlaive          \'2 hands, dam 12\n   wpHandaxe         \'1 hand, dam 6 chr: 244\n   wpBattleaxe       \'1 hand, dam 9\n   wpGothicbattleaxe \'2 hands, dam 12\n   wpWaraxe          \'2 hands, dam 14\n   wpHalberd         \'2 hands, dam 16\n   wpPoleaxe         \'2 hands, dam 18\n   wpGreataxe        \'2 hands, dam 20\n   wpSmallmace       \'1 hand, dam 6 chr: 226\n   wpBattlemace      \'1 hand, dam 8\n   wpSpikedmace      \'1 hand, dam 10\n   wpDoubleballmace  \'1 hand, dam 12\n   wpWarhammer       \'1 hand, dam 14\n   wpMaul            \'2 hands, dam 16\n   wpGreatMaul       \'2 hands, dam 20\n   wpHellMaul        \'2 hands, dam 30\n   wpBullwhip        \'1 hand, dam 4 chr: 231\n   wpBallflail       \'1 hand, dam 6\n   wpSpikedflail     \'1 hand, dam 8\n   wpMorningstar     \'1 hand, dam 10\n   wpBattleflail     \'2 hand, dam 12\n   wpBishopsflail    \'2 hand, dam 14\n   wpSling           \'1 hand, dam 2 chr: 125\n   wpShortbow        \'2 hands, dam 8\n   wpLongbow         \'2 hands, dam 10\n   wpBonebow         \'2 hands, dam 14\n   wpAdaminebow      \'2 hands, dam 20\n   wpLightcrossbow   \'2 hands, dam 10 chr: 209\n   wpHeavycrossbow   \'2 hands, dam 14\n   wpBarrelcrossbow  \'2 hands, dam 18\n   wpAdaminecrossbow \'2 hands, dam 25\nEnd Enum\n\n\'Weapon type def.\nType weapontype\n   id As shieldids       \'Shield type\n   evaldr As Integer    \'Evaluation difficutly. Evaldr \x3E 0 then magical item.\n   eval As Integer      \'Is item evaluated.\n   effect As integer    \'Magical spell.\n   sdesc As String * 30 \'Secret name/description for magical items. Revealed when evaluated.\n   noise As Integer     \'The amount of noise item generates, includes use and in character inventory.\n   use As itemuse       \'How the item is used.\n   dam As integer       \'Damage weapons does.\n   hands As Integer     \'Number of hands weapon requires.\n   wslot(1 To 2) As wieldpos \'Slot item is held in. Could be in up to 2 slots.\nEnd Type\n}}}\n\x3C/div\x3E\nThe weapon list gives a good selection of weapons with varying damage ratings and number of hands required. Each weapon group has its own icon, but all the weapons will be displayed in the same color so that the player will recognize a weapon when they see it on the screen. The different icons relate to the different types of weapons, and try to give a visual representation of what the weapon does. Over time, the player will learn to identify an icon and will be able to recognize a weapon type just by the icon. We could just use a single icon for all the weapons, but having different icons adds some flavor to the game and gives the player something to discover as they play the game. Remember, we want to keep the player interested, and part of that is the discovery process.\n\nThe Type Def should look quite familiar by now. It follows the same format as all the other items. The only real differences here are the damage rating field /dam/ and the number of hands the weapons requires contained in the /hands/ field. Everything else is just like the other items. Magical weapons, like magical armor, will have spells associated with them, so when we get to the spell code, we will update the weapon code along with the armor code to enable spell casting. For now, we simply use a placeholder field, /effect/ which will be the spell associated with the weapon.\n\nI said in the last chapter that it would be quite simple to add new items into the project, and it actually is quite easy. For the weapons, all I needed to do was to update the various subs and functions by adding in a new Select Case or If statement to handle the weapons. Since we have seen all that code several times now, I\'ll leave it to you to glance through the changes. It should be obvious by now that each new code section is just an extension of previous code, and will remain so now that we have the core inventory code in place.\n\nThe only new inventory code is the weapon generation routine.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Generate a weapon.\nSub GenerateWeapon(inv As invtype, currlevel As Integer, wpid As weaponids = wpNone)\n   Dim item As weaponids = wpid \n   Dim As Integer isMagic = ItemIsMagic(currlevel) \n\n   \'Generate item is not passed.\n   If item = wpNone Then\n      item = RandomRange(wpClub, wpAdaminecrossbow)\n   EndIf\n   \'These are the common items.\n   inv.weapon.id = item\n   inv.iconclr = fbCadmiumYellow\n   inv.weapon.use = useWieldWear\n   inv.weapon.eval = FALSE\n   inv.weapon.wslot(1) = wPrimary\n   inv.weapon.wslot(2) = wSecondary\n   \'magic item.\n   If IsMagic = TRUE Then\n      inv.weapon.evaldr = RandomRange(currlevel, currlevel * 2)\n      inv.weapon.effect = 0\n   EndIf\n   \'Set the weapon type and amount.\n   Select Case item\n      Case wpClub            \'1 hand, dam 4 chr:33\n         inv.desc = "Club"\n         inv.icon = Chr(33)\n         inv.weapon.noise = 1\n         inv.weapon.dam = 4\n         inv.weapon.hands = 1\n      Case wpCudgel          \'1 hand, dam 5\n         inv.desc = "Cudgel"\n         inv.icon = Chr(33)\n         inv.weapon.noise = 2\n         inv.weapon.dam = 5\n         inv.weapon.hands = 1\n      Case wpWarclub         \'1 hand, dam 6\n         inv.desc = "War Club"\n         inv.icon = Chr(33)\n         inv.weapon.noise = 3\n         inv.weapon.dam = 6\n         inv.weapon.hands = 1\n      Case wpDagger          \'1 hand, dam 4 chr: 173\n         inv.desc = "Dagger"\n         inv.icon = Chr(173)\n         inv.weapon.noise = 1\n         inv.weapon.dam = 4\n         inv.weapon.hands = 1\n      Case wpLongknife       \'1 hand, dam 5\n         inv.desc = "Dagger"\n         inv.icon = Chr(173)\n         inv.weapon.noise = 2\n         inv.weapon.dam = 5\n         inv.weapon.hands = 1\n      Case wpSmallsword      \'1 hand, dam 6\n         inv.desc = "Small Sword"\n         inv.icon = Chr(173)\n         inv.weapon.noise = 3\n         inv.weapon.dam = 6\n         inv.weapon.hands = 1\n      Case wpShortsword      \'1 hand, dam 8\n         inv.desc = "Short Sword"\n         inv.icon = Chr(173)\n         inv.weapon.noise = 4\n         inv.weapon.dam = 8\n         inv.weapon.hands = 1\n      Case wpRapier          \'1 hand, dam 10\n         inv.desc = "Rapier"\n         inv.icon = Chr(173)\n         inv.weapon.noise = 5\n         inv.weapon.dam = 10\n         inv.weapon.hands = 1\n      Case wpBroadsword      \'1 hands, dam 12\n         inv.desc = "Broadsword"\n         inv.icon = Chr(173)\n         inv.weapon.noise = 6\n         inv.weapon.dam = 12\n         inv.weapon.hands = 1\n      Case wpScimitar        \'1 hand, dam 12\n         inv.desc = "Scimitar"\n         inv.icon = Chr(173)\n         inv.weapon.noise = 7\n         inv.weapon.dam = 12\n         inv.weapon.hands = 1\n      Case wpLongsword       \'2 hands, dam 14\n         inv.desc = "Longsword"\n         inv.icon = Chr(173)\n         inv.weapon.noise = 8\n         inv.weapon.dam = 16\n         inv.weapon.hands = 2\n      Case wpKatana          \'2 hands, dam 16\n         inv.desc = "Katana"\n         inv.icon = Chr(173)\n         inv.weapon.noise = 9\n         inv.weapon.dam = 16\n         inv.weapon.hands = 2\n      Case wpClaymore        \'2 hands, dam 18\n         inv.desc = "Claynore"\n         inv.icon = Chr(173)\n         inv.weapon.noise = 12\n         inv.weapon.dam = 18\n         inv.weapon.hands = 2\n      Case wpGreatsword      \'2 hands, dam 20\n         inv.desc = "Greatsword"\n         inv.icon = Chr(173)\n         inv.weapon.noise = 16\n         inv.weapon.dam = 20\n         inv.weapon.hands = 2\n      Case wpOdinsword       \'2 hands, dam 30\n         inv.desc = "Odinsword"\n         inv.icon = Chr(173)\n         inv.weapon.noise = 20\n         inv.weapon.dam = 30\n         inv.weapon.hands = 2\n      Case wpHellguard       \'2 hands, dam 40\n         inv.desc = "Hellguard"\n         inv.icon = Chr(173)\n         inv.weapon.noise = 30\n         inv.weapon.dam = 40\n         inv.weapon.hands = 2\n      Case wpQuarterstaff    \'2 hands, dam 4 chr: 179\n         inv.desc = "Quarterstaff"\n         inv.icon = Chr(179)\n         inv.weapon.noise = 1\n         inv.weapon.dam = 4\n         inv.weapon.hands = 2\n      Case wpLongstaff       \'2 hands, dam 6\n         inv.desc = "Longstaff"\n         inv.icon = Chr(179)\n         inv.weapon.noise = 2\n         inv.weapon.dam = 6\n         inv.weapon.hands = 2\n      Case wpLightspear      \'2 hands, dam 7 \n         inv.desc = "Light Spear"\n         inv.icon = Chr(179)\n         inv.weapon.noise = 3\n         inv.weapon.dam = 7\n         inv.weapon.hands = 2\n      Case wpPolearm         \'2 hands, dam 8\n         inv.desc = "Polearm"\n         inv.icon = Chr(179)\n         inv.weapon.noise = 4\n         inv.weapon.dam = 8\n         inv.weapon.hands = 2\n      Case wpHeavyspear      \'2 hands, dam 9\n         inv.desc = "Heavy Spear"\n         inv.icon = Chr(179)\n         inv.weapon.noise = 5\n         inv.weapon.dam = 9\n         inv.weapon.hands = 2\n      Case wpTrident         \'2 hands, dam 10\n         inv.desc = "Trident"\n         inv.icon = Chr(179)\n         inv.weapon.noise = 6\n         inv.weapon.dam = 10\n         inv.weapon.hands = 2\n      Case wpGlaive          \'2 hands, dam 12\n         inv.desc = "Glaive"\n         inv.icon = Chr(179)\n         inv.weapon.noise = 7\n         inv.weapon.dam = 12\n         inv.weapon.hands = 2\n      Case wpHandaxe         \'1 hand, dam 6 chr: 244\n         inv.desc = "Hand Axe"\n         inv.icon = Chr(244)\n         inv.weapon.noise = 2\n         inv.weapon.dam = 6\n         inv.weapon.hands = 1\n      Case wpBattleaxe       \'1 hand, dam 9\n         inv.desc = "Battle Axe"\n         inv.icon = Chr(244)\n         inv.weapon.noise = 3\n         inv.weapon.dam = 9\n         inv.weapon.hands = 1\n      Case wpGothicbattleaxe \'2 hands, dam 12\n         inv.desc = "Gothic Battle Axe"\n         inv.icon = Chr(244)\n         inv.weapon.noise = 4\n         inv.weapon.dam = 12\n         inv.weapon.hands = 2\n      Case wpWaraxe          \'2 hands, dam 14\n         inv.desc = "War Axe"\n         inv.icon = Chr(244)\n         inv.weapon.noise = 5\n         inv.weapon.dam = 14\n         inv.weapon.hands = 2\n      Case wpHalberd         \'2 hands, dam 16\n         inv.desc = "Halberd"\n         inv.icon = Chr(244)\n         inv.weapon.noise = 6\n         inv.weapon.dam = 16\n         inv.weapon.hands = 2\n      Case wpPoleaxe         \'2 hands, dam 18\n         inv.desc = "Pole Axe"\n         inv.icon = Chr(244)\n         inv.weapon.noise = 8\n         inv.weapon.dam = 18\n         inv.weapon.hands = 2\n      Case wpGreataxe        \'2 hands, dam 20\n         inv.desc = "Great Axe"\n         inv.icon = Chr(244)\n         inv.weapon.noise = 10\n         inv.weapon.dam = 20\n         inv.weapon.hands = 2\n      Case wpSmallmace       \'1 hand, dam 6 chr: 226\n         inv.desc = "Small Mace"\n         inv.icon = Chr(226)\n         inv.weapon.noise = 1\n         inv.weapon.dam = 6\n         inv.weapon.hands = 1\n      Case wpBattlemace      \'1 hand, dam 8\n         inv.desc = "Battle Mace"\n         inv.icon = Chr(226)\n         inv.weapon.noise = 4\n         inv.weapon.dam = 8\n         inv.weapon.hands = 1\n      Case wpSpikedmace      \'1 hand, dam 10\n         inv.desc = "Spiked Mace"\n         inv.icon = Chr(226)\n         inv.weapon.noise = 6\n         inv.weapon.dam = 10\n         inv.weapon.hands = 1\n      Case wpDoubleballmace  \'1 hand, dam 12\n         inv.desc = "Double-Ball Mace"\n         inv.icon = Chr(226)\n         inv.weapon.noise = 8\n         inv.weapon.dam = 12\n         inv.weapon.hands = 1\n      Case wpWarhammer       \'1 hand, dam 14\n         inv.desc = "War Hammer"\n         inv.icon = Chr(226)\n         inv.weapon.noise = 10\n         inv.weapon.dam = 14\n         inv.weapon.hands = 1\n      Case wpMaul            \'2 hands, dam 16\n         inv.desc = "Maul"\n         inv.icon = Chr(226)\n         inv.weapon.noise = 12\n         inv.weapon.dam = 16\n         inv.weapon.hands = 2\n      Case wpBullwhip        \'1 hand, dam 4 chr: 231\n         inv.desc = "Bull Whip"\n         inv.icon = Chr(231)\n         inv.weapon.noise = 2\n         inv.weapon.dam = 4\n         inv.weapon.hands = 1\n      Case wpBallflail       \'1 hand, dam 6\n         inv.desc = "Ball Flail"\n         inv.icon = Chr(231)\n         inv.weapon.noise = 4\n         inv.weapon.dam = 6\n         inv.weapon.hands = 1\n      Case wpSpikedflail     \'1 hand, dam 8\n         inv.desc = "Spiked Flail"\n         inv.icon = Chr(231)\n         inv.weapon.noise = 6\n         inv.weapon.dam = 8\n         inv.weapon.hands = 1\n      Case wpMorningstar     \'1 hand, dam 10\n         inv.desc = "Morning Star"\n         inv.icon = Chr(231)\n         inv.weapon.noise = 8\n         inv.weapon.dam = 10\n         inv.weapon.hands = 1\n      Case wpBattleflail     \'2 hand, dam 12\n         inv.desc = "Battle Flail"\n         inv.icon = Chr(231)\n         inv.weapon.noise = 10\n         inv.weapon.dam = 12\n         inv.weapon.hands = 2\n      Case wpBishopsflail    \'2 hand, dam 14\n         inv.desc = "Bishop\'s Flail"\n         inv.icon = Chr(231)\n         inv.weapon.noise = 12\n         inv.weapon.dam = 14\n         inv.weapon.hands = 2\n      Case wpSling           \'1 hand, dam 2 chr: 125\n         inv.desc = "Sling"\n         inv.icon = Chr(125)\n         inv.weapon.noise = 1\n         inv.weapon.dam = 2\n         inv.weapon.hands = 1\n      Case wpShortbow        \'2 hands, dam 8\n         inv.desc = "Short Bow"\n         inv.icon = Chr(125)\n         inv.weapon.noise = 4\n         inv.weapon.dam = 8\n         inv.weapon.hands = 2\n      Case wpLongbow         \'2 hands, dam 10\n         inv.desc = "Long Bow"\n         inv.icon = Chr(125)\n         inv.weapon.noise = 6\n         inv.weapon.dam = 10\n         inv.weapon.hands = 2\n      Case wpBonebow         \'2 hands, dam 14\n         inv.desc = "Dragon Bone Bow"\n         inv.icon = Chr(125)\n         inv.weapon.noise = 10\n         inv.weapon.dam = 14\n         inv.weapon.hands = 2\n      Case wpAdaminebow      \'2 hands, dam 20\n         inv.desc = "Adamine Bow"\n         inv.icon = Chr(125)\n         inv.weapon.noise = 12\n         inv.weapon.dam = 20\n         inv.weapon.hands = 2\n      Case wpLightcrossbow   \'2 hands, dam 10 chr: 209\n         inv.desc = "Light Crossbow"\n         inv.icon = Chr(209)\n         inv.weapon.noise = 4\n         inv.weapon.dam = 10\n         inv.weapon.hands = 2\n      Case wpHeavycrossbow   \'2 hands, dam 14\n         inv.desc = "Heavy Crossbow"\n         inv.icon = Chr(209)\n         inv.weapon.noise = 8\n         inv.weapon.dam = 14\n         inv.weapon.hands = 2\n      Case wpBarrelcrossbow  \'2 hands, dam 18\n         inv.desc = "Barrel Crossbow"\n         inv.icon = Chr(209)\n         inv.weapon.noise = 12\n         inv.weapon.dam = 18\n         inv.weapon.hands = 2\n      Case wpAdaminecrossbow \'2 hands, dam 25\n         inv.desc = "Adamine Crossbow"\n         inv.icon = Chr(209)\n         inv.weapon.noise = 16\n         inv.weapon.dam = 25\n         inv.weapon.hands = 2\n      Case Else\n         inv.desc = "Unknown Weapon"\n         inv.icon = Chr(63)\n         inv.weapon.noise = 0\n         inv.weapon.dam = 0\n         inv.weapon.hands = 0\n   End Select\n   \'Set the complete secret description.\n   If IsMagic = TRUE Then\n      inv.weapon.sdesc = inv.desc \n   Else\n      inv.weapon.sdesc = inv.desc\n   EndIf\nEnd Sub\n}}}\n\x3C/div\x3E \nAs you can see the process is virtually the same as for armor and shields. Since we start the character with a dagger, we have an extra parameter that passes a weapon id to the sub, just as we did with the armor generation. If you take a moment and compare all the generation routines, you will see that the code is virtually identical. All the items have similiar layouts and similiar properties. This enables us to create similiar generation routines; and this similiarity gives us a consistent method to generate items. A consistent generation method makes it much easier to spot problems when they arise. It also makes updating the code easier as well. We can essentially cut and paste a new generation routine, taking care to change the pasted code into what we need. Cut and paste does save some time, but can be a real killer if you do not examine the code carefully to make sure you don\'t have an errant refernce in the code.\n\nThe only other section we need to update is the E(q)uip command in the inventory screen.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Process the equip menu item.\nFunction ProcessEquip() As Integer\n   Dim As String res, mask, desc, msg\n   Dim As Integer i, iret, iitem, idx, ret = FALSE, freecnt\n   Dim As invtype inv\n   Dim As tWidgets.btnID btn\n   Dim As tWidgets.tInputbox ib\n   Dim As vec mvec\n   Dim slot As Integer\n      \n   \'Make sure there is something to process in the inventory.\n   For i = pchar.LowInv To pchar.HighInv\n      iitem = pchar.HasInvItem(i)\n      If iitem = TRUE Then\n         \'Get the inv item.\n         pchar.GetInventoryItem i, inv\n         \'Is the item evaluated.\n         iret = MatchUse(inv, useWieldWear)\n         \'An item to evaluate.\n         If iret = TRUE Then\n            \'Build the mask.\n            mask &= Chr(i)\n         End If\n      EndIf\n   Next\n   If Len(mask) = 0 Then\n      ShowMsg "Equip Items", "Nothing to equip.", tWidgets.MsgBoxType.gmbOK\n   Else\n      \'Draws an input box on screen.\n      ib.Title = "Equip Items"\n      ib.Prompt = "Select item(s) to equip (" & mask & ")"\n      ib.Row = 39\n      ib.EditMask = mask\n      ib.MaxLen = Len(mask)\n      ib.InputLen = Len(mask)\n      btn = ib.Inputbox(res)\n      \'Evaluate each item in the list.\n      If (btn \x3C\x3E tWidgets.btnID.gbnCancel) And (Len(res) \x3E 0) Then\n         \'Evaluate the list of items.\n         For i = 1 To Len(res)\n            iitem = Asc(res, i) \'Get index into inventory.\n            \'Get the inv item.\n            pchar.GetInventoryItem iitem, inv\n            \'Get the item description.\n            desc = GetInvItemDesc(inv)\n            iret = FALSE\n            idx = 0\n            \'Check slot 1.\n            slot = GetInvWSlot(inv, 1)\n            If slot \x3C\x3E wNone Then\n               \'Check character to see if slot is open.\n               If pchar.HasInvItem(slot) = FALSE Then\n                  idx = slot\n                  iret = TRUE\n               EndIf\n            End If\n            \'Check second slot.\n            If iret = FALSE Then\n               \'Check slot 2.\n               slot = GetInvWSlot(inv, 2)\n               If slot \x3C\x3E wNone Then\n                  \'Check character to see if slot is open.\n                  If pchar.HasInvItem(slot) = FALSE Then\n                     idx = slot\n                     iret = TRUE\n                  EndIf\n               End If\n            EndIf\n            \'No empty slots.\n            If iret = FALSE Then\n               msg = "No empty slots to equip " & desc & "."\n            Else\n               \'Check to see if this is a weapon and how many hands it has.\n               If inv.classid = clWeapon then\n                  freecnt = 0\n                  \'Check free slots.\n                  If pchar.HasInvItem(wPrimary) = FALSE Then\n                     freecnt += 1\n                  EndIf\n                  If pchar.HasInvItem(wSecondary) = FALSE Then\n                     freecnt += 1\n                  EndIf\n                  If inv.weapon.hands \x3E freecnt Then\n                     iret = FALSE\n                     msg = "Not enough free hands to equip " & desc & "."\n                  EndIf\n               End If\n               \'Check to see if character can wear armor\n               If inv.classid = clArmor Or inv.classid = clShield Then\n                  If pchar.CanWear(inv) = false Then\n                     iret = FALSE\n                     msg = "Not enough strength to equip " & desc & "."\n                  End If\n               EndIf\n            End If\n            \'Did we find an empty slot?\n            If iret = TRUE Then\n               \'Put the item wield position.\n               pchar.AddInvItem idx, inv\n               \'Clear the item.\n               ClearInv inv\n               \'Update the held slot.\n               pchar.AddInvItem iitem, inv\n               ret = TRUE\n               desc &= " was equipped."\n               ShowMsg "Equip Items", desc, tWidgets.MsgBoxType.gmbOK\n            Else\n               ShowMsg "Equip Items", msg, tWidgets.MsgBoxType.gmbOK\n            End If\n         Next\n      EndIf\n   EndIf\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nMost of this code is the same, we have just done some reorganizig of the code to make it more efficient and to handle the hands aspect of weapons. Like we did before, we generate a list of equip-able items and present that list to the user. We then iterate through the returned selection and see if we can equip the items. \n\nNotice that we have changed the flow of the program when we check for an empty slot. This is because a weapon could be in either the primary or secondary slot, if it is a one handed item. The character could also be holding a shield and a weapon. The code will check the first slot, and if full, the second slot. In the first iteration of the code, we were only dealing with shields so we didn\'t really need to check both slots, but we do now that we have multiple items that can go into the primary and secondary slots.\n\nThe next thing we need to do is determine if the item is a weapon, some armor or a shield. Both weapons and armor have restrictions associated with them; weapons have a hand restriction and armor and shields have a weight restriction, so we validate those restrictions to make sure the character can use the selected item. For a weapon, we check to make sure that if it is two-handed, there are two slots available. For armor, we check the strength of the character just as we did before. If any of these tests fail, we create a message that we will display to the user letting them know why the equip process failed. If all goes well, then we equip the item as before.\n\nWe now have weapons we can use in the game. The next thing we need to do is to add in some monsters and we will be ready to battle the denizens of the Dungeon of Doom.\n',
'= 17: Monsters\n\x3Cdiv align="center"\x3E\x3Cimg src=\'images/monsters.png\' /\x3E\x3C/div\x3E\n\nSince we have armor and weapons, it is time to add some monsters so that we will have something to fight. We have a new file in the project, /monster.bi/ that will hold our monster code.\n\nThe first thing we need to do is to define our monster ids.\n\n\x3Cdiv class="code"\x3E\n/monster.bi/\n{{{\n\'Monster ids.\nEnum monids\n   monNone\n   monDarkangel\n   monGiantbat\n   monGiantscorpion\n   monDragon\n   monElfwarrior\n   monWisp\n   monGiant\n   monHarpy\n   monIncubus\n   monJadegolem\n   monKraken\n   monLamia\n   monManticore\n   monNaga\n   monOgre\n   monPhantomfungus\n   monQuorn\n   monRockgolem\n   monSkeleton\n   monTroll\n   monUruk\n   monVampire\n   monWombat\n   monXerth\n   monYeek\n   monZombie\n   monFlameangel\n   monWerebear\n   monGiantcentipede\n   monDemonspawn\n   monElemental\n   monFlamegolem\n   monGolem\n   monHobgoblin\n   monInterloper\n   monRovingjelly\n   monKobold\n   monLich\n   monMage\n   monNazgul\n   monOrc\n   monPulsingeye\n   monTwinhead\n   monRogue\n   monShurik\n   monGianttarantula\n   monGiantbeetle\n   monVarghoul\n   monWraith\n   monXorn\n   monYekki\n   monGriffon\nEnd Enum\n}}}\n\x3C/div\x3E\nThese are all the monsters we will have in the game. There are 52 monsters, one for each lower and upper case letter. Next, we need to define the monster data structure. We will approach monsters like we did inventory; monsters will be part of the level object, so our monster code will look similar to our inventory code.\n\n\x3Cdiv class="code"\x3E\n/monster.bi/\n{{{\n\'Monster type def. \nType montype\n   id As monids         \'Monster id.\n   mname As String * 15 \'Monster name.\n   micon As String * 1  \'Icon\n   mcolor As UInteger   \'Color of icon.\n   ismagic As Integer   \'Can produce magic spells.\n   spell As Integer     \'The attack/defense spell\n   cd As Integer        \'The defense factor\n   cf As Integer        \'The combat factor.\n   md As Integer        \'The magic defense factor\n   mf As Integer        \'The magic combat factor.\n   currhp As Integer    \'Current HP\n   xp As Integer        \'XP amount character wins.\n   dropcount As Integer \'The number of items in inventory.\n   dropitem(1 To 4) As invtype \'What the monster drops when dead.\n   atkdam As Integer    \'The monster attack damage.\n   armval As Single     \'Monster armor value as percentage.   \n   atkrange As Integer  \'Range of attack. \n   psighted As Integer  \'Indicates that monster sighted player.\n   plastloc As mcoord   \'The position player last sighted.\n   currcoord As mcoord  \'Current coordinates.\n   flee As Integer      \'Monster is fleeing.\n   isdead As Integer    \'Is the monster dead.\nEnd Type\n}}}\n\x3C/div\x3E\nEach monster has its own set of combat and defense factors, a possible inventory contained within the /dropitem/ array, attack damage and armor value. The /psighted/ flag will indicate that the monster has seen the character and the /flee/ flag indicates that the monster is fleeing rather than attacking.\n\nUsing this monster definition, we can create a monster.\n\n\x3Cdiv class="code"\x3E\n/monster.bi/\n{{{\n\'Generates a monster.\nSub GenerateMonster(mon As montype)\n   Dim As invtype inv\n   Dim scaling As Integer \'Mon trribute scaling.\n   Dim stratt As Integer    \'Strength attribute \n   Dim staatt As Integer    \'Stamina  \n   Dim dexatt As Integer    \'Dexterity \n   Dim aglatt As Integer    \'Agility \n   Dim intatt As Integer    \'Intelligence \n   Dim ucfsk As Integer     \'Unarmed combat skill\n   Dim acfsk As Integer     \'Armed combat skill\n   Dim pcfsk As Integer     \'Projectile combat skill\n   \n   \'Set the monster attributes.\n   mon.id = RandomRange(monDarkangel, monGriffon)\n   scaling = pchar.CurrStr / 2\n   stratt = RandomRange(pchar.CurrStr - scaling, pchar.CurrStr + scaling)\n   scaling = pchar.CurrSta / 2\n   staatt = RandomRange(pchar.CurrSta - scaling, pchar.CurrSta + scaling)\n   scaling = pchar.CurrDex / 2\n   dexatt = RandomRange(pchar.CurrDex - scaling, pchar.CurrDex + scaling)\n   scaling = pchar.CurrAgl / 2\n   aglatt = RandomRange(pchar.CurrAgl - scaling, pchar.CurrAgl + scaling)\n   scaling = pchar.CurrInt / 2\n   intatt = RandomRange(pchar.CurrInt - scaling, pchar.CurrInt + scaling)\n   \'Calc the combat factors.\n   acfsk = stratt + dexatt \n   pcfsk = dexatt + intatt\n   \'Set the combat factors.\n   mon.cd = stratt + aglatt\n   mon.md = aglatt + intatt \n   mon.mf = intatt + staatt\n   mon.cf = stratt + aglatt \n   \n   \'Set the hp.\n   mon.currhp = stratt + staatt\n   \n   \'Not fleeing.\n   mon.flee = FALSE\n   \'Character not sighted.\n   mon.psighted = FALSE\n   \'No character last location.\n   mon.plastloc.x = 0\n   mon.plastloc.y = 0  \n   \'Icon color.\n   mon.mcolor = fbRedBright\n   \'Monster not dead.       \n   mon.isdead = FALSE\n   \n   \'Clear any inventory items.\n   For i As Integer = LBound(mon.dropitem) To UBound(mon.dropitem)\n      ClearInv mon.dropitem(i)\n   Next\n...\n}}}\n\x3C/div\x3E\nThis is the first part of GenerateMonster which creates the attributes and sets the common flags of the monster. Notice that the attributes are scaled according to the character attributes. For a monster attribute, we use the character attribute, + or - half of the attribute. This will keep the monsters in range of the character, some slightly weaker, some slightly stronger, and will change as the character attributes change. This is an easy way to scale the monsters so that as the character grows in power, the monsters don\'t become cannon fodder and it keeps the game challenging. \n\nThe actual attributes of the monster are only temporary, we do not store them in the monster definition. We need them to calculate the monster combat factors which will be used in our combat routines, and these we will store in the monster Type Def. The combat factors use the exact same formulas we use in generating a character, so the combat factors mean exactly the same thing for both actors. What this means for the game is that we aren\'t working with some arbitrary number for the monsters and we don\'t need any special conversion routines to translate the combat factors when we get to the point of implementing combat. Not only does this make a fair game, it simplifies the programming process.\n\nThe next step in the GenerateMonster routine is to define each individual monster. We do that by looking at the id, and adding the monster data according to the monster type.\n\n\x3Cdiv class="code"\x3E\n/monster.bi/\n{{{\n   \'Set individual atrributes.\n   Select Case mon.id\n      Case monDarkangel\n         mon.mname = "Dark Angel" \'Name\n         mon.micon = "A"          \'Map icon.\n         mon.ismagic = TRUE \'Magic flag.\n         mon.spell = 0      \'If true then spell.\n         mon.atkrange = 1 \'Attack range.\n         mon.dropcount = 1  \'Number of items in inventory.\n         GenerateWeapon inv, currlevel, RandomRange(wpSmallsword, wpBishopsflail) \'Weapon. \n         mon.dropitem(1) = inv \'Set the inv item.\n         mon.atkdam = mon.dropitem(1).weapon.dam \'Use weapon damage.\n         mon.armval = .8 \'Armor rating.\n         mon.cf = acfsk \'Combt attack factor.\n      Case monGiantbat\n         mon.mname = "Giant Bat" \'Name\n         mon.micon = "B"          \'Map icon.\n         mon.ismagic = FALSE \'Magic flag.\n         mon.spell = 0      \'If true then spell.\n         mon.atkrange = 1 \'Attack range.\n         mon.dropcount = 0  \'Number of items in inventory.\n         mon.atkdam = stratt / 4 \'How much damage mon does.\n         mon.armval = .1 \'Armor rating.\n      Case monGiantscorpion\n         mon.mname = "Giant Scorpion" \'Name\n         mon.micon = "C"          \'Map icon.\n         mon.ismagic = FALSE \'Magic flag.\n         mon.spell = 0      \'If true then spell.\n         mon.atkrange = 2 \'Attack range.\n         mon.dropcount = 0  \'Number of items in inventory.\n         mon.atkdam = stratt / 4 \'How much damage mon does.\n         mon.armval = .9 \'Armor rating.\n...\n}}}\n\x3C/div\x3E\nHere we have a Dark Angel, Giant Bat and Giant Scorpion. Each has different traits that create an individual monster. Notice the Dark Angel has 1 inventory item, a randomly generated weapon. The attack damage of the Dark Angel is the damage value of the weapon generated. The Dark Angel isn\'t carrying any armor, but it has natural armor that amounts to 80% damage absorption which is set in the /armval/ field. The Dark Angel is also capable of casting a spell, and when we get to the spell code, we will add in an appropriate spell for the Dark Angel.\n\nThe Giant Bat and Giant Scorpion are "natural" creatures which do not carry any inventory. This is indicated in the /dropcount/ field by setting it to 0. Each monster has a variable attack rating so no two Giant Bats or Giant Scorpions will do the same amount of damage. \n\nThere are also "humanoid" type monsters, different races that are trapped in the dungeon and do the bidding of the evil there. These are created with more inventory items to make them seem more like humaniod actors.\n\n\x3Cdiv class="code"\x3E\n/monster.bi/\n{{{\n      Case monElfwarrior\n         mon.mname = "Elf Warrior" \'Name\n         mon.micon = "E"      \'Map icon.\n         mon.ismagic = TRUE \'Magic flag.\n         mon.spell = 0      \'If true then spell.\n         mon.atkrange = 1 \'Attack range.\n         mon.dropcount = 3  \'Number of items in inventory.\n         GenerateWeapon inv, currlevel, RandomRange(wpSmallsword, wpBishopsflail) \'Weapon. \n         mon.dropitem(1) = inv \'Set the inv item.\n         mon.atkdam = mon.dropitem(1).weapon.dam\n         ClearInv inv\n         GenerateArmor inv, currlevel, RandomRange(armCloth, armPlate)\n         mon.dropitem(2) = inv \n         mon.armval = mon.dropitem(2).armor.dampct \'Armor rating.\n         ClearInv inv \'Extra item.\n         GenerateSupplies inv, currlevel\n         mon.dropitem(3) = inv\n         mon.cf = acfsk \'Combt attack factor.\n}}}\n\x3C/div\x3E\nHere an elf warrior has both a weapon and armor, so we use both values to set the damage and armor value of the monster.\n\nThe /atkrange/ field gives the maximum distance of a monster attack. Most monsters have an attack range of 1, but some can reach the character from a distance.\n\n\x3Cdiv class="code"\x3E\n/monster.bi/\n{{{\n      Case monDragon\n         mon.mname = "Dragon" \'Name\n         mon.micon = "D"      \'Map icon.\n         mon.ismagic = FALSE \'Magic flag.\n         mon.spell = 0      \'If true then spell.\n         mon.atkrange = 6 \'Attack range.\n         mon.dropcount = 0  \'Number of items in inventory.\n         mon.atkdam = stratt / 4 \'How much damage mon does.\n         mon.armval = .9 \'Armor rating.\n         mon.cf = pcfsk \'Combt attack factor.\n}}}\n\x3C/div\x3E\nThe Dragon has an attack range of 6. It is also 90% armored, so it is going to be a tough foe even if its attributes are close to the player. \n\nAs you can see, even though we are scaling the attributes of the monsters to the character\'s attributes, we do not have vanilla, cookie-cutter monsters. Each monster type is an individual, with some variation within the types as well. This will keep the monsters interesting while providing a reasonable challenge for the player.\n\nThat is all there is in monster.bi for now. In order to get monsters on the map, we need to generate them when we create a new dungeon level.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Map info type\nType mapinfotype\n    terrid As terrainids  \'The terrain type.\n    monidx As Integer     \'Index into monster array.\n    visible As Integer    \'Character can see cell.\n    seen As Integer       \'Character has seen cell.\n    doorinfo As doortype  \'Door information.\nEnd Type\n\n\'Dungeon level information.\nType levelinfo\n   numlevel As Integer                       \'Current level number.\n   lmap(1 To mapw, 1 To maph) As mapinfotype \'Map array.\n   linv(1 To mapw, 1 To maph) As invtype     \'Map inventory type.\n   nummon As Integer                         \'Number of monsters on the map: 10 to maxmonster.\n   moninfo(1 To nroommax) As montype         \'Array of monsters.   \nEnd Type\n}}}\n\x3C/div\x3E\nWe have updated our /mapinfotype/ with the field /monidx/ which will be an index into the monster array. Remember that the mapinfotype is our two-dimensional map array that contains our specific map information like terrain ids, visible tiles, etc. In the /levelinfo/ Type Def we have two new fields /nummon/ which indicates how many monsters are on the map, and /moninfo/ which is an array of the /montype/ Type Def. The question here is what is the maximum number of monsters we want on the map? To make things easier, we will put monsters in rooms so the natural choice is to have the number of rooms in the map the maximum number of monsters. This is why we are using /nroommax/ as the upper bound on the monster array.\n\nTo place monsters we start with the /GenerateDungeonLevel/ subroutine.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Generate a new dungeon level.\nSub levelobj.GenerateDungeonLevel()\n    Dim As Integer x, y, i\n   \n    \'Clear level\n    For x = 1 To mapw\n        For y = 1 To maph\n            _level.lmap(x, y).terrid = twall           \'Set tile to wall.\n            _level.lmap(x, y).visible = FALSE          \'Not visible.\n            _level.lmap(x, y).seen = FALSE             \'Not seen.\n            _level.lmap(x, y).monidx = 0               \'No monster.\n            _level.lmap(x, y).doorinfo.locked = FALSE  \'Door not locked.\n            _level.lmap(x, y).doorinfo.lockdr = 0      \'No lock DR.\n            _level.lmap(x, y).doorinfo.dstr = 0        \'No door strength.\n            _level.nummon = 0                          \'Set monster count to 0.\n            ClearInv _level.linv(x,y)                  \'Clear inventory slot.\n        Next\n    Next\n    _InitGrid\n    _DrawMapToArray\n    _GenerateItems\nEnd Sub\n}}}\n\x3C/div\x3E\nHere we set the /monindex/ to 0 for each tile in the map and set /nummon/ to 0 to reset the number of monsters. To place a monster we need to look at /DrawMapToArray/.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Transfer grid data to map array.\nSub levelobj._DrawMapToArray()\n    Dim As Integer i, x, y, pr, rr, rl, ru, kr, nid, moncnt\n    Dim As mcoord ncoord\n    \n    \'Draw the first room to map array\n    For x = _rooms(1).tl.x + 1 To _rooms(1).br.x - 1\n        For y = _rooms(1).tl.y + 1 To _rooms(1).br.y - 1\n            _level.lmap(x, y).terrid = tfloor\n        Next\n    Next\n    \'Draw the rest of the rooms to the map array and connect them.\n    For i = 2 To _numrooms\n        For x = _rooms(i).tl.x + 1 To _rooms(i).br.x - 1\n            For y = _rooms(i).tl.y + 1 To _rooms(i).br.y - 1\n                _level.lmap(x, y).terrid = tfloor\n            Next\n        Next\n        \'Get room center.\n       ncoord.x = _rooms(i).roomdim.rcoord.x + 1\n       ncoord.y = _rooms(i).roomdim.rcoord.y + 1\n       \'Add a monster to the room.\n      If RandomRange(-5, maxlevel) \x3C= currlevel Then\n         moncnt += 1\n         If moncnt \x3C= nroommax Then\n             _level.nummon = moncnt\n             GenerateMonster _level.moninfo(_level.nummon)\n             _level.lmap(ncoord.x, ncoord.y).monidx = _level.nummon\n             _level.moninfo(_level.nummon).currcoord = ncoord\n         End If \n       End If\n        _ConnectRooms i, i - 1\n    Next\n    \'Add doors to selected rooms.\n    _AddDoors\n    \'Set up player location.\n    x = _rooms(1).roomdim.rcoord.x + (_rooms(1).roomdim.rwidth \\ 2) \n    y = _rooms(1).roomdim.rcoord.y + (_rooms(1).roomdim.rheight \\ 2)\n    pchar.Locx = x - 1\n    pchar.Locy = y - 1\n    \'Set up the stairs up.\n    _level.lmap(pchar.Locx, pchar.Locy).terrid = tstairup\n    \'Set up stairs down in last room.\n    x = _rooms(_numrooms).roomdim.rcoord.x + (_rooms(_numrooms).roomdim.rwidth \\ 2) \n    y = _rooms(_numrooms).roomdim.rcoord.y + (_rooms(_numrooms).roomdim.rheight \\ 2)\n    _level.lmap(x - 1, y - 1).terrid = tstairdn\nEnd Sub\n}}}\n\x3C/div\x3E\nWe set the number of monsters based on the number of rooms. The code {{{If RandomRange(-5, maxlevel) \x3C= currlevel Then}}} sets the probability of a monster appearing in a room. For the first few levels there will be few monsters, but as the player descends into the dungeon, monsters will appear more frequently. This will give the player an increasing challenge as they descend the dungeon.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n      \'Get room center.\n      ncoord.x = _rooms(i).roomdim.rcoord.x + 1\n      ncoord.y = _rooms(i).roomdim.rcoord.y + 1\n      \'Add a monster to the room.\n      If RandomRange(-5, maxlevel) \x3C= currlevel Then\n         moncnt += 1\n         If moncnt \x3C= nroommax Then\n             _level.nummon = moncnt\n             GenerateMonster _level.moninfo(_level.nummon)\n             _level.lmap(ncoord.x, ncoord.y).monidx = _level.nummon\n             _level.moninfo(_level.nummon).currcoord = ncoord\n         End If \n       End If\n}}}\n\x3C/div\x3E\nOnce we add a room, we also possibly add a monster. We first call the GenerateMonster routine which will give us a new monster. We save the monster id in the map array and set the monster\'s current coordinates in the /currcoord/ field. This puts our monster on the map.\n\nThe last step is to display the monster on the map and we do that in the /DrawMap/ procedure.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'If the current location has a monster print that monster.\nIf _level.lmap(i + x, j + y).monidx \x3E 0 Then\n    monid = _level.lmap(i + x, j + y).monidx\n    mtile = _level.moninfo(monid).micon\n    tilecolor = _level.moninfo(monid).mcolor\n    PutText acBlock, y + 1, x + 1, fbBlack\n    PutText mtile, y + 1, x + 1, tilecolor\nEndIf\n}}}\n\x3C/div\x3E\nI am only showing the actual monster code here. If the monster is in the visible range of the character, we want to print the monster. As you can see the process is quite simple. If the /monidx/ is greater than zero, get the monster id and look in the monster array for the monster icon and color. The /acBlock/ code just clears the current tile, and the monster icon is printed in the monster color at the tile location.\n\nMonsters that are not currently visible will not be printed. Since the monsters move each turn, they don\'t qualify as an item that the character can "remember" (like an item on the map) so they will not show on the map. This adds some tension to the game, since the player will not be exactly sure where a monster has gotten to on the map.\n\nWe now have monsters on the map. The next step is to make them move around and we will do that in the next chapter.\n ',
'= 18: Monster Movement\n\x3Cdiv align="center"\x3E\x3Cimg src=\'images/monmove.png\' /\x3E\x3C/div\x3E\n\nWe have monsters in the game now, but it will not be very exciting if the monsters just stand there and let the player pound them to death. Monsters need to move, attack the player, and flee if need be. In this chapter we will implement monster movement, both toward and away from the player.\n\nMonster movement can be viewed as a state machine where the movement actions follow from the current state of the monster. There are several states that the monster can be in any moment.\n\n* The monster has sighted the character.\n* The monster has lost sight of the character but remembers the character\'s last position.\n* The monster has lost sight of the character, doesn\'t have a last position but hears the character.\n* The monster is fleeing the character.\n* The monster hasn\'t encountered or lost the character completely.\n\nEach of these bullet items represents a particular state that a monster can be in. For each state, we need to code a response so that the monster will behave appropriately.\n\n* The monster has sighted the character.\n** Set the sighted flag to TRUE. \n** Remember last character position. \n** Check for attack and attack if possible.\n** If no attack, Move toward character.\n* The monster has lost sight of the character but remembers the character\'s last position.\n** Move toward last position until at last position.\n* The monster has lost sight of the character, doesn\'t have a last position but hears the character.\n** Move toward higher sound value.\n* The monster is fleeing the character.\n** Move away from character.\n* The monster hasn\'t encountered or lost the character completely.\n** Do nothing.\n\nEach monster will move after the character has moved, so the different states are checked each time a monster is about to move. The highest state, sighting the character, is checked each time and is analogous to the monster scanning the area around him for the player. If there is no sight lines, the monster will then move to where it last saw the character in the hopes of catching a glimpse of the character. If that fails, it will listen for the character since the character will generate some sound as he is moving around in the dungeon. This will enable the monster to home in on the character if, for instance, the character has ducked into a cross hallway out of sight of the monster. If the monster\'s health reaches a certain point, it will not pursue the character and try to flee, although it will perform a fighting retreat. Lastly, if the monster has never seen the character or has lost the character completely (unlikely given the previous bullet items, but possible) the monster will simply rest where it is.\n\nThis simple state machine produces rather dramatic results. Once a monster has latched onto the character, it will doggedly pursue the character until it either dies in combat or has successfully escaped if fleeing. In most cases the player will have to stand and fight since it isn\'t easy to escape a monster that is hot on his heels. Of course, once we get into the magic aspect of the game, the character could possibly teleport out of trouble, but there is no guarantee that the destination will be better than the current situation. Roguelikes are primarily about combat, and relentless monsters highlight that game mechanic.\n\nNotice that there isn\'t anything in the different states about roaming. Since we are placing monsters inside of rooms, the player will be forced to encounter a monster while exploring the dungeon since the player will have to visit rooms to at least find the stairs down to the next level. In this scenario, the monster don\'t need to roam; the character will come to them. This simplifies the game, and forces the player to decide the best course of action. Should I kill the monster now, or flee and end up with several monsters chasing me? These type of tactical decisions make the game interesting and fun to play.\n\nThe code for monster movement is part of the level object and is called, not surprisingly, /MoveMonsters/. \n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Moves all living monsters.\nSub levelobj.MoveMonsters ()\n   Dim As mcoord nxt\n   Dim As Integer pdist\n         \n   \'Iterate through each monster.\n   For i As Integer = 1 To _level.nummon\n      \'Make sure monster is not dead.\n      If _level.moninfo(i).isdead = FALSE Then\n         \'Is the monster fleeing?\n         If _level.moninfo(i).flee = FALSE Then\n            \'Is the character in line of sight?\n            If  _level.lmap(_level.moninfo(i).currcoord.x, _level.moninfo(i).currcoord.y).visible = TRUE Then\n               \'Set the sighted flag.\n               _level.moninfo(i).psighted = TRUE\n               \'Set the character location.\n               _level.moninfo(i).plastloc.x = pchar.Locx\n               _level.moninfo(i).plastloc.y = pchar.Locy\n               \'Check the distance to character.\n               pdist = CalcDist(_level.moninfo(i).currcoord.x, pchar.Locx, _level.moninfo(i).currcoord.y, pchar.Locy)\n               \'Check the attack range of monster.\n               If pdist \x3C= _level.moninfo(i).atkrange Then\n                  \'Attack character.\n                  \n               Else\n                  \'Get the next closest tile to player.\n                  nxt = _GetNextTile(_level.moninfo(i).currcoord.x, _level.moninfo(i).currcoord.y, _ \n                  pchar.Locx, pchar.Locy)\n                  \'Set the new coords for monster.\n                  _level.lmap(_level.moninfo(i).currcoord.x, _level.moninfo(i).currcoord.y).monidx = 0\n                  _level.lmap(nxt.x, nxt.y).monidx = i\n                  _level.moninfo(i).currcoord = nxt\n               End If   \n            Else\n               \'Character not in line of sight. Was player sighted.\n               If _level.moninfo(i).psighted = TRUE Then\n                  \'Make sure we have a location.\n                  If (_level.moninfo(i).plastloc.x \x3E -1) And (_level.moninfo(i).plastloc.y \x3E -1) Then \n                     \'Move toward last sighted position.\n                     nxt = _GetNextTile(_level.moninfo(i).currcoord.x, _level.moninfo(i).currcoord.y, _\n                                        _level.moninfo(i).plastloc.x, _level.moninfo(i).plastloc.y)\n                     \'Set the new monster coordinates.\n                     _level.lmap(_level.moninfo(i).currcoord.x, _level.moninfo(i).currcoord.y).monidx = 0\n                     _level.lmap(nxt.x, nxt.y).monidx = i\n                     _level.moninfo(i).currcoord = nxt\n                     \'Are we at the character last coordinates?\n                     If (nxt.x = _level.moninfo(i).plastloc.x) And (nxt.y = _level.moninfo(i).plastloc.y) Then\n                        \'Reset monster target.\n                        _level.moninfo(i).psighted = FALSE\n                        _level.moninfo(i).plastloc.x = -1\n                        _level.moninfo(i).plastloc.y = -1\n                     EndIf\n                  Else\n                     \'Lost sight of character so reset monster target.\n                     _level.moninfo(i).psighted = FALSE\n                     _level.moninfo(i).plastloc.x = -1\n                     _level.moninfo(i).plastloc.y = -1\n                  End If\n               Else\n                  \'Check sound map here.\n                  If _level.lmap(_level.moninfo(i).currcoord.x, _level.moninfo(i).currcoord.y).sndvol \x3E 0 Then\n                     nxt = _GetNextSndTile(_level.moninfo(i).currcoord.x, _level.moninfo(i).currcoord.y)\n                     \'Set the new monster coordinates.\n                     _level.lmap(_level.moninfo(i).currcoord.x, _level.moninfo(i).currcoord.y).monidx = 0\n                     _level.lmap(nxt.x, nxt.y).monidx = i\n                     _level.moninfo(i).currcoord = nxt\n                  EndIf\n               EndIf\n            EndIf\n         Else\n            If (_level.moninfo(i).psighted = TRUE) Or (_level.lmap(_level.moninfo(i).currcoord.x, _level.moninfo(i).currcoord.y).visible = TRUE) Then\n               \'Check the distance to character.\n               pdist = CalcDist(_level.moninfo(i).currcoord.x, pchar.Locx, _level.moninfo(i).currcoord.y, pchar.Locy)\n               \'Check the attack range of monster.\n               If pdist \x3C= _level.moninfo(i).atkrange Then\n                  \'Attack character.\n               \n               Else\n                  \'Move away from the character.\n                  nxt = _GetNextTile(_level.moninfo(i).currcoord.x, _level.moninfo(i).currcoord.y, pchar.Locx, pchar.Locy, TRUE)\n                  \'Set the new monster coordinates.\n                  _level.lmap(_level.moninfo(i).currcoord.x, _level.moninfo(i).currcoord.y).monidx = 0\n                  _level.lmap(nxt.x, nxt.y).monidx = i\n                  _level.moninfo(i).currcoord = nxt\n               EndIf\n            End If\n         End If\n      EndIf\n   Next\n   \nEnd Sub\n}}}\n\x3C/div\x3E\nThe first thing we need to do is to check to make sure that the monster is still alive. Dead monster obviously cannot move. We then check to see if the monster is fleeing. If it is fleeing, we will execute a fighting retreat, otherwise the monster will move toward the character based on the current state:\n\n{{{If  _level.lmap(_level.moninfo(i).currcoord.x, _level.moninfo(i).currcoord.y).visible = TRUE Then}}}\n\nThe first state in our bullet list was that the monster sighted the player. We do this by checking the visibility map to see if the monster tile is currently visible to the character. We can do this because, as we will see in a moment, we let the player move first, update the map and then move the monsters. Since the map has already been updated when we reach this subroutine, we can use the "if I see you, you can see me" rule. This saves some computing cycles and provides a symmetrical visibility model.\n\nIf the visibility check is TRUE we then update the monster data which represents the monster memory.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n               \'Set the sighted flag.\n               _level.moninfo(i).psighted = TRUE\n               \'Set the character location.\n               _level.moninfo(i).plastloc.x = pchar.Locx\n               _level.moninfo(i).plastloc.y = pchar.Locy\n\n}}}\n\x3C/div\x3E\nThe sighted flag is set to TRUE and the character\'s current location is saved for later reference.\n\nWe then check to see if the monster can attack the character by looking at the distance to the character and comparing that to the attack range of the monster.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n...\n  \'Check the distance to character.\n  pdist = CalcDist(_level.moninfo(i).currcoord.x, pchar.Locx, _level.moninfo(i).currcoord.y, pchar.Locy)\n  \'Check the attack range of monster.\n  If pdist \x3C= _level.moninfo(i).atkrange Then\n    \'Attack character.\n                  \n  Else\n...\n}}}\n\x3C/div\x3E \nNotice that we don\'t move if the character is in attack range. Attacking takes precedence over moving and allows the monster to perform repeated ranged attack. If the monster cannot attack, then it will move toward the player.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n...\n  Else\n    \'Get the next closest tile to player.\n    nxt = _GetNextTile(_level.moninfo(i).currcoord.x, _level.moninfo(i).currcoord.y, _ \n    pchar.Locx, pchar.Locy)\n    \'Set the new coords for monster.\n    _level.lmap(_level.moninfo(i).currcoord.x, _level.moninfo(i).currcoord.y).monidx = 0\n    _level.lmap(nxt.x, nxt.y).monidx = i\n    _level.moninfo(i).currcoord = nxt\n  End If   \n...\n}}}\n\x3C/div\x3E\nHere we can use a helper function that will give us a coordinate that is one tile closer to the character. Once we get a new coordinate we update the monster and map information.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Returns the next tile closest to x, y.\nFunction levelobj._GetNextTile(mx As Integer, my As Integer, x As Integer, y As Integer, flee As Integer = FALSE) _\n                                                                                                          As mcoord\n   Dim As Integer pdist, mdist, xx, yy\n   Dim v As vec\n   Dim As mcoord ret\n   \n   \'Set current location.\n   xx = mx\n   yy = my\n   \'Set reference number.\n   If flee = FALSE Then\n      pdist = 1000\n   Else\n      pdist = 0\n   End If\n   \'Iterate through all local tiles and find closest tile to character.\n   For j As compass = north To nwest\n      v.vx = mx\n      v.vy = my\n      v += j\n      \'Make sure tile isn\'t a blocking tile.\n      If (_BlockingTile(v.vx, v.vy) = FALSE) And (_level.lmap(v.vx, v.vy).monidx = 0) Then\n         \'Get the tile distance.\n         mdist = CalcDist(v.vx, x, v.vy, y)\n         \'Moving toward character.\n         If flee = FALSE Then\n            \'If less than last diatance, set new coords.\n            If mdist \x3C= pdist Then\n               xx = v.vx\n               yy = v.vy\n               pdist = mdist\n            EndIf\n         Else\n            \'Moving away from character.\n            If mdist \x3E= pdist Then\n               xx = v.vx\n               yy = v.vy\n               pdist = mdist\n            EndIf\n         End If\n      EndIf\n   Next\n   ret.x = xx\n   ret.y = yy\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nThe /GetNextTile/ function works for both moving toward the character and away from the character since the operations are the same, it is just the direction that is different. The process is quite simple. A vector object is initialized to the current coordinate passed in through mx, my. Each compass point is then checked for both a blocking tile and monster; if the tile is free, the distance to x and y is checked. In the case of moving toward the character, the new tile distance must be less than or equal to the saved tile distance, and in the case of fleeing, the new tile distance must be greater than or equal to the saved tile distance. If either case is TRUE then the new tile location is saved and the next compass point is checked.\n\nIn the case of no tile being available, the function just returns the current monster coordinate and the monster doesn\'t move. This can happen if, for instance, there is a group of monsters and the current monster being checked is surrounded by walls or other monsters and cannot move. \n\nIf the monster isn\'t within visibility range of the character, the next step is to move toward the last known position of the character. By doing this, the monster has a good chance of gaining sight of the character.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n...\n  \'Character not in line of sight. Was player sighted.\n  If _level.moninfo(i).psighted = TRUE Then\n    \'Make sure we have a location.\n    If (_level.moninfo(i).plastloc.x \x3E -1) And (_level.moninfo(i).plastloc.y \x3E -1) Then \n      \'Move toward last sighted position.\n      nxt = _GetNextTile(_level.moninfo(i).currcoord.x, _level.moninfo(i).currcoord.y, _\n            _level.moninfo(i).plastloc.x, _level.moninfo(i).plastloc.y)\n      \'Set the new monster coordinates.\n      _level.lmap(_level.moninfo(i).currcoord.x, _level.moninfo(i).currcoord.y).monidx = 0\n      _level.lmap(nxt.x, nxt.y).monidx = i\n      _level.moninfo(i).currcoord = nxt\n      \'Are we at the character last coordinates?\n      If (nxt.x = _level.moninfo(i).plastloc.x) And (nxt.y = _level.moninfo(i).plastloc.y) Then\n        \'Reset monster target.\n        _level.moninfo(i).psighted = FALSE\n        _level.moninfo(i).plastloc.x = -1\n        _level.moninfo(i).plastloc.y = -1\n      EndIf\n    Else\n      \'Lost sight of character so reset monster target.\n      _level.moninfo(i).psighted = FALSE\n      _level.moninfo(i).plastloc.x = -1\n      _level.moninfo(i).plastloc.y = -1\n    End If\n...\n}}}\n\x3C/div\x3E\nThis is exactly the same process as moving toward the character, except that we are moving toward the last known location of the character. The first thing we need to do though is to make sure that we have a last location. If not we just reset the monster data and wait for contact. If there is a location, then we get the next closest tile to that location and move there. If we arrive at the location and the character isn\'t in sight, then we just reset the monster data and wait for contact.\n\nIf these two states fail, the next step is listening for the character. \n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n...\n  \'Check sound map here.\n  If _level.lmap(_level.moninfo(i).currcoord.x, _level.moninfo(i).currcoord.y).sndvol \x3E 0 Then\n    nxt = _GetNextSndTile(_level.moninfo(i).currcoord.x, _level.moninfo(i).currcoord.y)\n    \'Set the new monster coordinates.\n    _level.lmap(_level.moninfo(i).currcoord.x, _level.moninfo(i).currcoord.y).monidx = 0\n    _level.lmap(nxt.x, nxt.y).monidx = i\n    _level.moninfo(i).currcoord = nxt\n  EndIf\n...\n}}}\n\x3C/div\x3E\nEach time the character moves a sound map is generated based on what the character is carrying. If the monster happens to be within the range of the sound map, the monster will look for the next tile that has the larger sound value. It does that by calling the /GetNextSndTile/ function.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Returns the next higher sound tile.\nFunction levelobj._GetNextSndTile(x As Integer, y As Integer) As mcoord\n   Dim As mcoord ret\n   Dim As Integer xx, yy, msnd = 0, psnd = 0\n   Dim v As vec\n   \n   \'Set current location.\n   xx = x\n   yy = y\n   \'Iterate through all local tiles and find closest tile to character.\n   For j As compass = north To nwest\n      v.vx = x\n      v.vy = y\n      v += j\n      \'Make sure tile isn\'t a blocking tile.\n      If (_BlockingTile(v.vx, v.vy) = FALSE) And (_level.lmap(v.vx, v.vy).monidx = 0) Then\n         \'Get the current sound value.\n         msnd = _level.lmap(v.vx, v.vy).sndvol\n         \'If greater than last value then save new coords.\n         If msnd \x3E= psnd Then\n            xx = v.vx\n            yy = v.vy\n            psnd = msnd\n         EndIf\n      EndIf\n   Next\n   ret.x = xx\n   ret.y = yy\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nThis works exactly like the GetNextTile function but looks at the sound map rather than the distance to the target tile. The sound map is part of the map definition file.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Map info type\nType mapinfotype\n    terrid As terrainids  \'The terrain type.\n    monidx As Integer     \'Index into monster array.\n    visible As Integer    \'Character can see cell.\n    seen As Integer       \'Character has seen cell.\n    doorinfo As doortype  \'Door information.\n    sndvol As Integer     \'Sound volume at current tile.\nEnd Type\n}}}\n\x3C/div\x3E\nThe field sndvol is the sound factor at any given tile. Most tiles will be zero, except right around the character. The sound map is generated each move.\n\n\x3Cdiv class="code"\x3E\n/dod.bas:MoveChar/\n{{{\n...\n  \'Move character.\n  If block = FALSE Then\n    \'Set the new character position.\n    pchar.Locx = vc.vx\n    pchar.Locy = vc.vy\n    ret = TRUE\n    \'Generate the sound map.\n    level.ClearSoundMap\n    snd = pchar.GetNoise()\n    level.GenSoundMap(pchar.Locx, pchar.Locy, snd)\n...\n}}}\n\x3C/div\x3E\nIf the character can move successfully, the character is moved and the sound map is generated. The first thing to do is to clear the sound map.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Clears the current sound map.\nSub levelobj.ClearSoundMap()\n   For x As Integer = 1 To mapw\n      For y As Integer = 1 To maph\n         _level.lmap(x, y).sndvol = 0\n      Next\n   Next\nEnd Sub\n}}}\n\x3C/div\x3E\nThis just walks through the sound array and sets all tile locations to 0. The next step is to look at the character\'s inventory and get the total amount of sound he is generating. This is done the /GetNoise/ function.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n\'Returns the current noise volume.\nFunction character.GetNoise() As Integer\n   Dim As Integer ret = 0\n   \n   \'Get gold amount.\n   ret = _cinfo.currgold / 10\n   \'Get inventory amounts.\n   For i As Integer = LBound(_cinfo.cinv) To UBound(_cinfo.cinv) \n      ret += GetItemNoise(_cinfo.cinv(i))\n   Next\n   \'Get held amounts.\n   For i As Integer = LBound(_cinfo.cwield) To UBound(_cinfo.cwield) \n      ret += GetItemNoise(_cinfo.cwield(i))\n   Next\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nThis function iterates through the carried inventory as well as any held item and accumulates the total amount of sound. Since the sound is a property of the inventory item, we have added a new function to the inventory code to look at an item and return its noise value.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Returns the noise factor item.\nFunction GetItemNoise(inv As invtype) As Integer\n   Dim As Integer ret = 0\n   \n   \'If nothing then no use.\n   If inv.classid \x3C\x3E clNone Then\n      \'Select the item.\n      Select Case inv.classid\n         \'Get the noise factor for the item.\n         Case clSupplies\n            ret = inv.supply.noise\n         Case clArmor\n            ret = inv.armor.noise\n         Case clShield\n            ret = inv.shield.noise\n         Case clWeapon\n            ret = inv.weapon.noise\n      End Select\n   EndIf\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nLike we do with all inventory items, we check the class id of the item and then look at the appropriate noise field to get the item\'s noise value.\n\nOnce we get the total amount of noise the character is generating, we call the level object\'s /GenSoundMap/ subroutine.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Generate the sound map using the passed noise factor.\nSub levelobj.GenSoundMap(x As Integer, y As Integer, sndvol As Integer)\n    Dim csound As Integer\n    Dim sdist As Integer\n    \n    \'Set up exit conditions.\n    If x \x3C 0 Or x \x3E mapw Then Exit Sub\n    If y \x3C 0 Or y \x3E mapw Then Exit Sub\n    If sndvol \x3C= 0 Then Exit Sub\n    If _BlockingTile(x, y) Then Exit Sub\n    If _level.lmap(x, y).sndvol \x3E 0 Then Exit Sub\n    \'Attenuate the sound using square of distance.\n    sdist = CalcDist(pchar.Locx, x, pchar.Locy, y)\n    csound = sndvol - (sdist * sdist)\n    \'No sound so exit.\n    If csound \x3C= 0 Then Exit Sub\n    \'Recursively call the routine to build the map.\n    _level.lmap(x, y).sndvol = csound\n    GenSoundMap x+1, y, csound\n    GenSoundMap x, y+1, csound\n    GenSoundMap x-1, y, csound \n    GenSoundMap x, y-1, csound \n    GenSoundMap x+1, y+1, csound \n    GenSoundMap x-1, y+1, csound \n    GenSoundMap x-1, y-1, csound \n    GenSoundMap x+1, y-1,  csound\nEnd Sub\n}}}\n\x3C/div\x3E\nAs you can see this is a recursive function that terminates when either the sound volume reaches 0, or a blocking tile is encountered. This is actually a simple flood-fill routine that has been modified to generate the sound. The square of the distance rule is used to attenuate the sound, and while not terribly realistic, prevents the whole map from being flooded with sound. Even with this sound attenuation, when the character is fully loaded, he will producing a fairly large sound footprint, which will be a magnet for nearby monsters.\n\nThe last active step the monster will take is fleeing. This is just like moving toward the player, except going in the opposite direction.\n\n\x3Cdiv class="code"\x3E\n/map.bi:MoveMonsters/\n{{{\n...\n  Else\n    If (_level.moninfo(i).psighted = TRUE) Or (_level.lmap(_level.moninfo(i).currcoord.x, _\n        _level.moninfo(i).currcoord.y).visible = TRUE) Then\n        \'Check the distance to character.\n        pdist = CalcDist(_level.moninfo(i).currcoord.x, pchar.Locx, _level.moninfo(i).currcoord.y, pchar.Locy)\n        \'Check the attack range of monster.\n        If pdist \x3C= _level.moninfo(i).atkrange Then\n          \'Attack character.\n               \n          \'Move away from the character.\n          nxt = _GetNextTile(_level.moninfo(i).currcoord.x, _level.moninfo(i).currcoord.y, pchar.Locx, _\n                             pchar.Locy, TRUE)\n          \'Set the new monster coordinates.\n          _level.lmap(_level.moninfo(i).currcoord.x, _level.moninfo(i).currcoord.y).monidx = 0\n          _level.lmap(nxt.x, nxt.y).monidx = i\n          _level.moninfo(i).currcoord = nxt\n        EndIf\n      End If\n    End If\n...\n}}}\n\x3C/div\x3E\nFirst we check to see if the character is still in sight. If he is, we want to check to see if he is in range of the monster\'s attack, and if so, fire away. If the character isn\'t in range, yet still visible, then the monster will move until until the character passes out of sight range.\n\nAs you can see the monster movement is based on the particular state the monster is in at any movement cycle. These states represent the basis of a form of behavioral modeling that is similar to what you find in nature. A lion for example, can pick up the scent of a prey animal and will advance to the point where the animal is in sight. It then may lie in wait or try to sneak up on the animal to get close enough to attack the animal. Each of these actions is based on the current state of the lion: scent tracking, visual tracking using stealth and then all out attack. While we are not using all the nuances of an actual predator in DOD, using state-based behavioral modeling, we can create monsters that can track the character effectively, and flee if necessary.\n\nThe next step is combat, where we will expand the AI of our monsters into combat decisions, again using a state-based approach that will give our monsters several options in attacking.\n',
'= 19: Melee Combat\n\x3Cdiv align="center"\x3E\x3Cimg src=\'images/mcombat.png\' /\x3E\x3C/div\x3E\n\nWe have now reached the point where we can engage in melee combat. To fight, we simply bump into the monster and attack with whatever we have at hand at the moment. We do that in the /MoveChar/ subroutine.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Move the character based on the compass direction.\nFunction MoveChar(comp As compass) As Integer\n   Dim As Integer ret = FALSE, block\n   Dim As vec vc = vec(pchar.Locx, pchar.Locy) \'Creates a vector object.\n   Dim As terrainids tileid\n   Dim As Integer snd\n   \n   vc+= comp\n   \'Check to make sure we don\'t move off map.\n   If (vc.vx \x3E= 1) And (vc.vx \x3C= mapw) Then\n      If (vc.vy \x3E= 1) And (vc.vy \x3C= maph) Then\n         \'Check for blocking tile.\n         block = level.IsBlocking(vc.vx, vc.vy)\n         \'Check for monster.\n         If block = FALSE Then\n            block = level.IsMonster(vc.vx, vc.vy)\n            \'Check to see if we bumped into a monster.\n            If block = TRUE Then\n               \'Attack the monster.\n               DoMeleeCombat vc.vx, vc.vy\n               ret = TRUE\n            EndIf\n         EndIf\n}}}\n\x3C/div\x3E\nThe /IsMonster/ function is part of the level object and returns TRUE is a monster is at the target x and y coordinates.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Returns true if monster at location.\nFunction levelobj.IsMonster(x As Integer, y As Integer) As Integer\n   Dim As Integer ret = FALSE\n   \n   If _level.lmap(x, y).monidx \x3E 0 Then\n      ret = TRUE\n   EndIf\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nIf the return is TRUE, then we call the /DoMeleeeCombat/ subroutine to resolve the combat.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Perform melee combat.\nSub DoMeleeCombat(mx As Integer, my As Integer)\n   Dim As Integer cf, df, croll, mroll, dam, isdead, midx, mxp, xp \n   Dim As String txt, mname\n   Dim As Single marm\n   \n   \'Get the monster defense factor.\n   df = level.GetMonsterDefense(mx, my)\n   mname = level.GetMonsterName(mx, my)\n   \'Get monster xp.\n   mxp = level.GetMonsterXP(mx, my)\n   \'Make sure we got something.\n   If df \x3E 0 Then\n      \'Returns the combat factor based on what the character is holding.\n      cf = pchar.GetMeleeCombatFactor()\n      \'Get the rolls.\n      croll = RandomRange(1, cf)\n      mroll = RandomRange(1, df)\n      \'See if the character hit monster.      \n      If croll \x3E mroll Then\n         \'Get total weapon damage.\n         dam = pchar.GetWeaponDamage()\n         \'Get monster armor rating.\n         marm = level.GetMonsterArmor(mx, my)\n         \'Adjust the damage value based on armor rating.\n         dam = dam - (dam * marm)\n         If dam \x3C= 0 Then dam = 1\n         \'Set the monster hp.\n         isdead = level.ApplyDamage(mx, my, dam)\n         \'Print the message.\n         If isdead = TRUE Then\n            \'Add xp amount to character.\n            xp = pchar.CurrXP\n            xp += mxp\n            pchar.CurrXP = xp\n            xp = pchar.TotXP\n            xp += mxp\n            pchar.TotXP = xp\n            \'Print message.\n            txt = pchar.CharName & " killed the " & mname & " with " & dam & " damage points."\n            PrintMessage txt\n         Else\n            txt = pchar.CharName & " hit the " & mname & " for " & dam & " damage points."\n            PrintMessage txt\n         EndIf\n      Else\n         txt = pchar.CharName & " missed the " & mname & "."\n         PrintMessage txt\n      EndIf\n   EndIf\nEnd Sub\n}}}\n\x3C/div\x3E\nAs you can see the process isn\'t overly complex. The first thing we do is to gather the offensive combat factor for the character and the defensive combat factor for the monster. Once we have those values we make two rolls, one for the attack and one for the defense. If the attack roll is greater than the defense roll, the character has hit the monster. Remember that ties go to the defender, so the character roll must be greater than the monster roll.\n\nIf the character hits, then the damage of the weapon is adjusted based on the monster armor value. Since we are multiplying the armor value, which is a Single, to an Integer value, the possibility exists that the adjusted value of the damage may be 0. In this case we give the character a point of damage since he was able to hit the monster.\n\nThe damage is then applied to the monster through the new subroutine /ApplyDamage/ and the character gets some experience points that amount to the total amount of hit points of the monster. The result of the combat is displayed on the main screen using the /PrintMessage/ subroutine and the subroutine exits. Since a monster is a blocking tile, the character will not move this turn, as expected. Let\'s look at the supporting functions we have for combat.\n\nThe first thing we do is to get the defense factor of the monster.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Returns the defense factor of monster.\nFunction levelobj.GetMonsterDefense(mx As Integer, my As Integer) As Integer\n   Dim As Integer ret = 0, midx = 0\n   \n   \'Make sure there is a monster here.\n   If _level.lmap(mx, my).monidx \x3E 0 Then\n      midx = _level.lmap(mx, my).monidx\n      ret = _level.moninfo(midx).cd\n   EndIf\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nThe x and y coordinates of the monster in question is passed to the function. A check is made to make sure that we have a monster index at the passed coordinates, and if so, we retrieve the index into the monster array. The /cd/ field, which is the monster\'s defense factor, is then returned.\n\nNext we grab the monster name which we will use in the message to the player.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Returns the monster type.\nFunction levelobj.GetMonsterName(mx As Integer, my As Integer) As String\n   Dim As String ret\n   Dim As Integer midx\n   \n   \'Make sure there is a monster here.\n   If _level.lmap(mx, my).monidx \x3E 0 Then\n      midx = _level.lmap(mx, my).monidx\n      ret = _level.moninfo(midx).mname\n   EndIf\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nOnce again, we check the coordinates and get the index and then return the name in the /mname/ field.\n\nSince the character could kill the monster, we also get the XP of the monster so that we can add it to the character total.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Returns the monster xp amount.\nFunction levelobj.GetMonsterXP(mx As Integer, my As Integer) As Integer\n   Dim As Integer midx, ret\n   \n   \'Make sure there is a monster here.\n   If _level.lmap(mx, my).monidx \x3E 0 Then\n      midx = _level.lmap(mx, my).monidx\n      ret = _level.moninfo(midx).xp\n   EndIf\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nThe monster XP is generated when the monster is created and is contained within the /xp/ field.\n\nOnce we have this information we need to get the character\'s combat factor. Since the combat factor is based on what the character is currently using as a weapon, i.e., no weapon means using the UCF, a sword means using the ACF, etc., we need to check the wield slots of the character to determine what combat factor to use.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n\'Returns the current combat factor based on what the character is wielding.\nFunction character.GetMeleeCombatFactor() As Integer\n   Dim As Integer ret\n   \n   \'Unarmed combat.\n   If (_cinfo.cwield(wPrimary).classid \x3C\x3E clWeapon) And (_cinfo.cwield(wSecondary).classid \x3C\x3E clWeapon) Then\n      ret = CurrUcf + BonUcf\n   Else\n      \'Check primary slot.\n      If _cinfo.cwield(wPrimary).classid = clWeapon Then\n         \'Wielding a melee weapon.\n         If _cinfo.cwield(wPrimary).weapon.id \x3C wpSling Then\n            ret = CurrAcf + BonAcf\n         Else\n          \'Return UCF as projectile weapon isn\'t melee weapon.\n           ret = CurrUcf + BonUcf\n         EndIf\n      Else\n         If _cinfo.cwield(wSecondary).classid = clWeapon Then\n            \'Wielding a melee weapon.\n            If _cinfo.cwield(wSecondary).weapon.id \x3C wpSling Then\n               ret = CurrAcf + BonAcf\n            Else\n               \'Return UCF as projectile weapon isn\'t melee weapon.\n               ret = CurrUcf + BonUcf\n            End If\n         EndIf\n      End If\n   EndIf\n\n    Return ret     \nEnd Function\n}}}\n\x3C/div\x3E\nWhat we do here is to check the slots to see what the character is holding. If the character is unarmed or holding a projectile weapon, then the unarmed combat factor is returned. Otherwise the character is holding a melee weapon, that is a sword, staff, mace, etc. so the armed combat factor is returned. When we implement projectile combat we will add a similar routine to this for projectile combat.\n\nIf the character is able to hit the monster, then we need to get the current weapon damage. \n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n\'Returns the amount of weapon damage.\nFunction character.GetWeaponDamage() As Integer\n   Dim As Integer ret = 0\n   \n   \'See if we have any weapons.\n   If (_cinfo.cwield(wPrimary).classid \x3C\x3E clWeapon) And (_cinfo.cwield(wSecondary).classid \x3C\x3E clWeapon) Then\n      \'If no weapon get the stength value plus bonus.\n      ret = (_cinfo.stratt(0) + _cinfo.stratt(1)) / 2\n   Else\n      \'Have one or more weapons.\n      If _cinfo.cwield(wPrimary).classid = clWeapon Then\n         \'Get the current weapon damage.\n         ret = _cinfo.cwield(wPrimary).weapon.dam\n      EndIf\n      If _cinfo.cwield(wSecondary).classid = clWeapon Then\n         ret += _cinfo.cwield(wSecondary).weapon.dam\n      EndIf\n   EndIf\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nFirst we check to see if the character is unarmed and if so, we return half the strength value. If the character is wielding a weapon, we then return the weapon\'s damage rating. If the character is wielding two weapons, both of the weapons are considered to hit, so the damage value of both weapons is returned.\n\nWe also have to consider the monster\'s armor value which is used to modify the weapon damage value, so we need to get the monster armor rating.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Returns the monster armor rating.\nFunction levelobj.GetMonsterArmor(mx As Integer, my As Integer) As Single\n   Dim As Single ret\n   Dim As Integer midx\n   \n   \'Make sure there is a monster here.\n   If _level.lmap(mx, my).monidx \x3E 0 Then\n      midx = _level.lmap(mx, my).monidx\n      ret = _level.moninfo(midx).armval\n   EndIf\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nThe armor value is kept in the /armval/ field and we return that value back to the combat subroutine. The armor value is a percentage, so what this means is that the armor absorbs a portion of the attack, letting the rest go through to the monster. It works this way for the character as well.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n         \'Get monster armor rating.\n         marm = level.GetMonsterArmor(mx, my)\n         \'Adjust the damage value based on arnor rating.\n         dam = dam - (dam * marm)\n         If dam \x3C= 0 Then dam = 1\n         \'Set the monster hp.\n         isdead = level.ApplyDamage(mx, my, dam)\n\n}}}\n\x3C/div\x3E\nWe multiply the monster\'s armor value to the damage to get a portion of the damage, and then subtract that from the total damage to get an adjusted damage value, with a minimum of 1 point of damage for successfully hitting the monster. That damage is then applied to the monster in /ApplyDamage/.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Applies damage to monsters. Returns true if monster is dead.\nFunction levelobj.ApplyDamage(mx As Integer, my As Integer, dam As Integer) As Integer\n   Dim As Integer midx, i, ret = FALSE\n   Dim As vec v\n   \n   \'Make sure there is a monster here.\n   If _level.lmap(mx, my).monidx \x3E 0 Then\n      midx = _level.lmap(mx, my).monidx\n      _level.moninfo(midx).currhp = _level.moninfo(midx).currhp - dam\n      \'Check for flee.\n      If _level.moninfo(midx).currhp \x3C 2 Then _level.moninfo(midx).flee = TRUE \n      \'Check to see if monster is dead.\n      If _level.moninfo(midx).currhp \x3C= 0 Then\n         \'Monster is dead.\n         ret = TRUE\n         \'Flag the monster as dead.\n         _level.moninfo(midx).isdead = TRUE\n         \'Remove from map.\n         _level.lmap(mx, my).monidx = 0\n         \'Drop any items.\n         If _level.moninfo(midx).dropcount \x3E 0 Then\n            For i = 1 To _level.moninfo(midx).dropcount\n               For j As compass = north To nwest\n                  v.vx = mx\n                  v.vy = my\n                  v += j\n                  \'If empty drop item.\n                  If (_level.lmap(v.vx, v.vy).terrid = tFloor) And (_level.linv(v.vx, v.vy).classid = clNone) Then\n                     PutItemOnMap v.vx, v.vy, _level.moninfo(midx).dropitem(i)\n                     Exit For\n                  EndIf\n               Next\n               ClearInv _level.moninfo(midx).dropitem(i)\n            Next\n         EndIf\n      EndIf\n   EndIf\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nWe follow the same procedure here that we have with the other monster routines. We get the monster index and we apply the damage to the /currhp/ field of the monster record. If the character manages to kill the monster, we then set the /isdead/ flag to TRUE so that we don\'t try and move the monster in the next movement cycle. We also need to set the map index to 0 so that the monster won\'t show up when we redraw the map. Finally, we check the /dropcount/ field and if the monster is carrying any inventory, we drop the inventory onto the map provided there is space to do so.\n\nOf course, the player isn\'t the only actor that gets to attack. The monsters get their turn as well and they do that in their movement cycle.\n\n\x3Cdiv class="code"\x3E\n/map.bi:MoveMonsters/\n{{{\n...\n  \'Check the distance to character.\n  pdist = CalcDist(_level.moninfo(i).currcoord.x, pchar.Locx, _level.moninfo(i).currcoord.y, pchar.Locy)\n  \'Check the attack range of monster.\n  If pdist \x3C= _level.moninfo(i).atkrange Then\n    \'Attack character.\n    MonsterAttack _level.moninfo(i).currcoord.x, _level.moninfo(i).currcoord.y\n  Else\n...\n}}}\n\x3C/div\x3E\nWe have already examined this subroutine in the last chapter and the only update here is to add the /MonsterAttack/ subroutine.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Monster attacks character.\nSub levelobj.MonsterAttack(mx As Integer, my As Integer)\n   Dim As Integer midx, cd, mc, rollc, rollm, chp, dam\n   Dim As String txt\n   Dim As Single arm\n   \n   \'Make sure there is a monster here.\n   If _level.lmap(mx, my).monidx \x3E 0 Then\n      midx = _level.lmap(mx, my).monidx\n      \'Get character defense factor.\n      cd = pchar.GetDefenseFactor()\n      \'Get monster attack factor.\n      mc = _level.moninfo(midx).cf\n      \'Get the rolls.\n      rollc = RandomRange(1, cd)\n      rollm = RandomRange(1, mc)\n      \'Compare monster roll to character roll.\n      If rollm \x3E rollc Then\n         \'Get the damage.\n         dam = _level.moninfo(midx).atkdam\n         \'Get any shield values if any.\n         arm = pchar.GetShieldArmorValue()\n         \'Make sure the character has some armor.\n         If arm \x3E 0 Then\n            dam = dam - (dam * arm)\n         End If\n         \'Get the character armor value.\n         arm = pchar.GetArmorValue ()\n         \'Make sure the character has some armor.\n         If arm \x3E 0 Then\n            dam = dam - (dam * arm)\n         End If\n         If dam \x3C 1 Then dam = 1\n         \'Get character hp.\n         chp = pchar.CurrHP\n         \'Subtract damage from it.\n         chp -= dam\n         If chp \x3C 0 Then chp = 0\n         \'Reset character hp.\n         pchar.CurrHP = chp\n         txt = "The " &  _level.moninfo(midx).mname & " hits for " & dam & " damage points."\n      Else\n         txt = "The " &  _level.moninfo(midx).mname & " misses."\n      EndIf\n      PrintMessage txt\n   End If   \nEnd Sub\n}}}\n\x3C/div\x3E\nThe process here is almost identical to the character attack routine. We grab the monster offensive combat factor, and the character defense factor and then make two rolls, with the higher roll winning the encounter. If the monster wins, then we grab the character\'s armor value and any shield armor values and apply the values to the damage. We apply the armor values in two steps. If the character is carrying a shield, we apply that value first, since this is the first line of defense. We then apply the armor value to the remaining damage. As with the character, the monster gets at least 1 point of damage for making a successful hit. The damage is then applied to the character\'s HP and a message is sent to indicate the result of the encounter.\n\nWe have added some new functions to our character object to return these combat values. The first returns the current defense factor plus any bonuses.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n\'Returns the character\'s defense factor.\nFunction character.GetDefenseFactor () As Integer\n   Return currCdf + BonCdf\nEnd Function\n}}}\n\x3C/div\x3E\nWe also need to get the character\'s armor value.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n\'Returns the current armor value.\nFunction character.GetArmorValue() As Single\n   Dim As Single ret = 0.0\n   \n   \'Check for armor.\n   If _cinfo.cwield(wArmor).classid \x3C\x3E clNone Then\n      ret = _cinfo.cwield(wArmor).armor.dampct \n   EndIf\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nThis looks at the armor slot of the character and if the character is wearing any armor, it returns the armor percentage. Next we get the sheild value if any.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n\'Returns any shield armor value.\nFunction character.GetShieldArmorValue () As Single\n   Dim As Single ret = 0.0\n   Dim As Integer cnt\n      \n   \'Check for any shields.\n   If _cinfo.cwield(wPrimary).classid = clShield Then\n      ret += _cinfo.cwield(wPrimary).shield.dampct\n      cnt = 1\n   EndIf   \n   If _cinfo.cwield(wSecondary).classid = clShield Then\n      ret += _cinfo.cwield(wSecondary).shield.dampct\n      cnt += 1\n   EndIf\n   \n   \'Get the average of the values if any.\n   If cnt \x3E 0 Then\n      ret = ret / cnt\n   EndIf\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nIf the character is carrying 1 or more shields, we then get the average of the shield values and return those back to the attack routines. We use the average here since we are assuming the blow would strike both shields simultaneously, and the average of the shield armor values seems reasonable in this type of situation.\n\nWe have already added a check to the main loop of the program to check if the character has died.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n         \'Since the player pressed a key, do any timed actions.\n         pchar.DoTimedActions\n         \'Check for whether character is dead.\n         If pchar.CurrHP \x3C= 0 Then\n            isdead = TRUE\n         EndIf\n}}}\n\x3C/div\x3E\nWe added this check when we added the poisoned flag to the character.\n\nWe have finally reached another major milestone to the game, melee combat. If you play the game a bit, you will find that you really can\'t survive for long. This will change as we add in more items, a resting model and spells. We can feel good about our progress as we are quite close to having a fully functional game to play.\n',
'= 20: Resting Model\n\x3Cdiv align="center"\x3E\x3Cimg src=\'images/resting.png\' /\x3E\x3C/div\x3E\nResting is a common game mechanic to allow a character to heal in the game. In most games, resting involves some cost, whether it be some resource or the increased chance of a monster appearing. In /Dungeon of Doom/ resting will cost 1 mana point which will restore 1 health point. \n\nThe first thing to do is to update the key display on the main screen by adding a new command, Rest using the "r" key.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n   PutText "Commands", row, col, fbYellowBright\n   row += 2\n   PutText "Move:.Arrows or Numpad", row, col\n   row += 1\n   PutText "?.....Help", row, col\n   row += 1\n   PutText "i.....Inventory", row, col\n   row += 1\n   PutText "x.....Improve Character", row, col\n   row += 2\n   PutText "\x3E.....Down Level", row, col\n   row += 1\n   PutText "\x3C.....Up Level", row, col\n   row += 1\n   PutText "n.....Inspect Tile", row, col\n   row += 1\n   PutText "s.....Search Area", row, col\n   row += 1\n   PutText "t.....Target Enemy", row, col\n   row += 1\n   PutText "c.....Cast Spell", row, col\n   row += 1\n   PutText "g.....Get Item", row, col\n   row += 1\n   PutText "p.....Pick Lock", row, col\n   row += 1\n   PutText "b.....Bash Door", row, col\n   row += 1\n   PutText "r.....Rest (1 Mana = 1 HP)", row, col\n}}}\n\x3C/div\x3E\nWe need to add the new key to our main game loop.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n...\n         \'Rest the character.\n         If ckey = "r" Then\n            RestCharacter\n            DrawMainScreen\n            level.MoveMonsters\n         EndIf\n...\n}}}\n\x3C/div\x3E\nThis key section calls the /RestCharacter/ subroutine. As you can see, we need to update the main screen, and we will also allow the monsters to move too. If the player decides to rest within striking range of a monster, it probably won\'t go well for the character.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Rests the character by converting 1 mana to 1 hp.\nSub RestCharacter ()\n   Dim As Integer cman, chp, thp\n   \n   \'Get the current mana and hp values.\n   cman = pchar.CurrMana\n   chp = pchar.CurrHP\n   thp = pchar.MaxHP\n   \'Check to see if the character needs to rest.\n   If chp = thp Then\n      PrintMessage pchar.CharName & " doesn\'t need to rest."\n   Else\n      \'Make sure we have some mana to spend.\n      If cman = 0 Then\n         PrintMessage pchar.CharName & " doesn\'t have any mana to spend."\n      Else\n         \'Decrement the mana and add to hp.\n         cman -= 1\n         pchar.CurrMana = cman\n         chp += 1\n         pchar.CurrHP = chp\n         PrintMessage pchar.CharName & " spent 1 mana for 1 health point."\n      EndIf\n   EndIf\nEnd Sub\n}}}\n\x3C/div\x3E\nHere we get the current Mana and HP values and check them to make that the character needs to rest and that he has enough Mana to spend. If all is well, then we subtract a mana point from the current mana total and add it to the current HP.\n\nSince the character will be consuming mana we need to add a Mana Orb to the game so that the character can replenish his mana supply.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Supply item ids.\nEnum supplyids\n   supSupplyNone  \'No supply id.\n   supHealingHerb \'Healing herb-50% of max HP healing effect.\n   supHunkMeat    \'25% of max HP healing effect.\n   supBread       \'10% of max HP healing effect.\n   supManaOrb     \'10% of max mana to mana total.\nEnd Enum\n}}}\n\x3C/div\x3E\nHere we have replaced the Bottle of Oil, which we weren\'t using at the moment, with a Mana Orb. We also need to update the supply generation code to handle the mana orb.\n\n\x3Cdiv class="code"\x3E\n/inv.bi:GenerateSupplies/\n{{{\n      Case supManaOrb   \n         inv.desc = "Mana Orb"\n         inv.supply.noise = 4\n         inv.icon = Chr(229)\n         inv.iconclr = fbYellowGreen\n         inv.supply.eval = FALSE\n         inv.supply.use = useEatDrink\n         \'Set the magic properties.\n         If isMagic = TRUE Then\n            inv.supply.evaldr = RandomRange(currlevel, currlevel * 2)\n            inv.supply.effect = effMaxMana\n            inv.supply.sdesc = "Restore Mana"\n         Else \n            \'Set secret description to main desciption.\n            inv.supply.sdesc = inv.desc\n         EndIf\n}}}\n\x3C/div\x3E\nHere we have updated the magical effect from the See-All that the oil had to Restore Mana which will, obviously, fully restore the character\'s mana. The last thing we need to update is the Eat/Drink routine in the main code module.\n\n\x3Cdiv class="code"\x3E\n/dod.bas;ProcessEatDrink/\n{{{\n...\n            ElseIf inv.supply.id = supManaOrb Then\n              \'Does 10% healing.\n               pchar.CurrMana = pchar.CurrMana + (pchar.MaxMana * .1)\n               If pchar.CurrMana \x3E pchar.MaxMana Then\n                  pchar.CurrMana = pchar.MaxMana\n                  desc2 = " added some mana!"\n               EndIf\n               \'Evaluated magical item.\n               If (evalstate = TRUE) And (evalDR \x3E 0) Then\n                  \'Cure the poison if any.\n                  If pchar.CurrMana = pchar.MaxMana Then\n                     desc2 = " restored all your mana!"\n                  End If\n               EndIf\n            EndIf\n...\n}}}\n\x3C/div\x3E\nThis little section processes the mana when the character consumes it. \n\nA simple update to the program, but one which will help our character survive for a bit longer in the hostile world of /Dungeon of Doom/.\n',
'= 21: Character Improvement\n\x3Cdiv align="center"\x3E\x3Cimg src=\'images/improve.png\' /\x3E\x3C/div\x3E\n\nOne thing that will help the character survive in the dungeon is to improve the stats of the character. We already have a key on our main display for this, "x", so we just need to add in the functionality.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n         \'Improve the character.\n         If ckey = "x" Then\n            ImproveCharacter\n            \'Need to redraw back ground here.\n	    DrawBackground mainback()\n            DrawMainScreen\n         EndIf\n\n}}}\n\x3C/div\x3E\nAs you can see we invoke a new subroutine, /ImproveCharacter/.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Applies XP to character attributes.\nSub ImproveCharacter ()\n   Dim As Integer sel, xp\n   \n   \'Make sure we have some xp to work with.\n   If pchar.CurrXP \x3E 10 Then\n      Do\n         If pchar.CurrXP \x3E 10 Then\n            \'Print Current stats.\n            pchar.PrintStats\n            PutTextShadow "Cost = 10 XP to 1 point improvement.", 49, 10\n            PutTextShadow "Enter attribute 1 to 5 to improve, enter to exit: ", 51, 10\n            \'Position input.\n            Locate 53, 10\n            \'Get the input.\n            Input sel\n            \'If attribute selected then improve. \n            If sel = 1 Then\n               pchar.ImproveStrength 1\n               \'Subtract the xp amount.\n               xp = pchar.CurrXP\n               xp -= 10\n               pchar.CurrXP = xp \n            EndIf\n            If sel = 2 Then\n               pchar.ImproveStamina 1\n               \'Subtract the xp amount.\n               xp = pchar.CurrXP\n               xp -= 10\n               pchar.CurrXP = xp \n            EndIf\n            If sel = 3 Then\n               pchar.ImproveDexterity 1\n               \'Subtract the xp amount.\n               xp = pchar.CurrXP\n               xp -= 10\n               pchar.CurrXP = xp \n            EndIf      \n            If sel = 4 Then\n               pchar.ImproveAgility 1\n               \'Subtract the xp amount.\n               xp = pchar.CurrXP\n               xp -= 10\n               pchar.CurrXP = xp \n            EndIf\n            If sel = 5 Then\n               pchar.ImproveIntelligence 1\n               \'Subtract the xp amount.\n               xp = pchar.CurrXP\n               xp -= 10\n               pchar.CurrXP = xp \n            EndIf\n         Else\n            sel = 0\n         End If\n      Loop Until sel = 0\n   Else\n      PrintMessage "Not enough XP to spend."\n   End If\nEnd Sub\n}}}\n\x3C/div\x3E\nThe first thing we need to so here is to make sure the character has enough XP to improve any attributes. There is a cost of 10 XP per 1 point of improvement, so the character needs to have at least 10 XP to improve an attribute. If the character has enough XP we then call the /PrintStats/ subroutine to print out the character attributes. We implemented this at the beginning of the project, to display the generated character stats, and we can make use of it here.\n\nWe then use a simple /Input/ statement to get the selection from the player. To exit, the player can just hit enter which will return 0 into our /sel/ variable and the subroutine will exit. It will also exit when the character has exhausted all his XP.\n\nWhen we improve an attribute, we also need to improve the associated combat factors and HP so we have added a few subroutines to our character object to make updating a bit easier.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n\'Improves attribute and updates associated stats.\nSub character.ImproveStamina(increase As Integer)\n   \'Make sure we have something to update.\n   If increase \x3E 0 Then\n      \'Update the attribute\n      _cinfo.staatt(0) = _cinfo.staatt(0) + increase\n      \'Update the combat factor. \n      _cinfo.currhp = _cinfo.stratt(0) + _cinfo.staatt(0)\n      _cinfo.maxhp = _cinfo.currhp \n      _cinfo.currmana = _cinfo.intatt(0) + _cinfo.staatt(0) \n      _cinfo.maxmana = _cinfo.currmana\n      _cinfo.mcfsk(0) = _cinfo.intatt(0) + _cinfo.staatt(0)\n   EndIf\nEnd Sub\n\n\'Improves attribute and updates associated stats.\nSub character.ImproveDexterity(increase As Integer)\n   \'Make sure we have something to update.\n   If increase \x3E 0 Then\n      \'Update the attribute\n      _cinfo.dexatt(0) = _cinfo.dexatt(0) + increase\n      \'Update the combat factor. \n      _cinfo.acfsk(0) = _cinfo.stratt(0) + _cinfo.dexatt(0)\n      _cinfo.pcfsk(0) = _cinfo.dexatt(0) + _cinfo.intatt(0)\n   EndIf\nEnd Sub\n\n\'Improves attribute and updates associated stats.\nSub character.ImproveAgility(increase As Integer)\n   \'Make sure we have something to update.\n   If increase \x3E 0 Then\n      \'Update the attribute\n      _cinfo.aglatt(0) = _cinfo.aglatt(0) + increase\n      \'Update the combat factor. \n      _cinfo.ucfsk(0) = _cinfo.stratt(0) + _cinfo.aglatt(0)\n      _cinfo.mdfsk(0) = _cinfo.aglatt(0) + _cinfo.intatt(0)\n      _cinfo.cdfsk(0) = _cinfo.stratt(0) + _cinfo.aglatt(0)\n   EndIf\nEnd Sub\n\n\'Improves attribute and updates associated stats. \nSub character.ImproveIntelligence(increase As Integer)\n   \'Make sure we have something to update.\n   If increase \x3E 0 Then\n      \'Update the attribute\n      _cinfo.intatt(0) = _cinfo.intatt(0) + increase\n      \'Update the mana factor. \n      _cinfo.currmana = _cinfo.intatt(0) + _cinfo.staatt(0) \n      _cinfo.maxmana = _cinfo.currmana\n      \'Update the combat factors.\n      _cinfo.pcfsk(0) = _cinfo.dexatt(0) + _cinfo.intatt(0)\n      _cinfo.mcfsk(0) = _cinfo.intatt(0) + _cinfo.staatt(0)\n      _cinfo.mdfsk(0) = _cinfo.aglatt(0) + _cinfo.intatt(0)\n   EndIf\nEnd Sub\n}}}\n\x3C/div\x3E\nAs you can see all the subroutines work in the same manner. We make sure we have a value for improvement and then apply the value to the relevant attribute. We then update the associated combat, health and mana factors. There is a positive side-effect here for the character: when they improve an attribute associated with health or mana, the current health and mana values are replenished to the new maximum values. \n\nThis is all that is needed to improve a character. I think you can begin to see that by carefully structuring the program in a modular fashion, we can add new functionality with ease, and with minimum negative side-effects. ',
'= 22: Projectile Combat\nProjectile combat is one of those seemingly simple actions to implement. Set up a targeting system, let the projectile fly and see if you hit something. However, once you get into the details of the system, it quickly becomes apparent that quite a bit of code is actually needed to implement projectile combat. Here is a list of things we need to implement in order for projectile combat to work properly.\n\n* Targeting system\n* Ammo type and number\n* Weapon reload system\n* Projectile animation\n* Resolve combat\n\nAs you can see there is a lot going on, so we are going to cover quite a bit of code in this chapter.\n\nAs we normally do, we will start with the data definitions needed to implement projectiles. You have already seen the weapon Type Def, but we needed to update the structure to handle the ammo aspects of the weapon. That naturally leads us to our ammo definition which is basically an inventory structure that will be held in the character\'s backpack and dipped into when needed. So, let\'s start with the new weapon definition.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Weapon type def.\nType weapontype\n   id As weaponids           \'Weapon type\n   evaldr As Integer         \'Evaluation difficutly. Evaldr \x3E 0 then magical item.\n   eval As Integer           \'Is item evaluated.\n   spell As spellid          \'Magical spell.\n   noise As Integer          \'The amount of noise item generates, includes use and in character inventory.\n   use As itemuse            \'How the item is used.\n   dam As integer            \'Damage weapons does.\n   hands As Integer          \'Number of hands weapon requires.\n   wslot(1 To 2) As wieldpos \'Slot item is held in. Could be in up to 2 slots.\n   weapontype As weaptype    \'Type of weapon: melee, projectile.\n   capacity As Integer       \'The number of rounds the weapon uses.\n   ammotype As ammoids       \'Type of ammo used.\n   ammocnt As Integer        \'Weapon ammo. Can be loaded to capacity.\nEnd Type\n}}}\n\x3C/div\x3E\nThe updated and new fields are at the end of the Type definition. /Weapontype/ is an Enum that allows us to easily identify if the held weapon is a melee or projectile weapon.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Weapon type.\nEnum weaptype\n   wtNone\n   wtMelee\n   wtProjectile\nEnd Enum\n}}}\n\x3C/div\x3E \nBy using an enumeration here we simplify the identification code quite a bit as we will soon see.\n\nThe next field in the weapon Type Def is /capacity/ which indicates how much ammo the weapon can hold. This will allow us to create multiple shot weapons (the barrel crossbow in our case) that do not have to be reloaded after each shot. Most of the weapons in DOD are single shot weapons, since we are implementing a fantasy game using medieval-style weapons. However, we can use this very same structure to create a futuristic game where a ray gun may be able to shoot one hundred times on a single charge. The beauty of this system is that it also works seamlessly for single shot weapons, so we have the best of both worlds.\n\nThe next field is the ammotype which matches the ammoids enumeration.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Ammo ids.\nEnum ammoids\n   amNone\n   amBagStones\n   amCaseBolts\n   amQuiverArrows\nEnd Enum\n}}}\n\x3C/div\x3E\nHere we see we have three types of ammo, stones for slings, bolts for crossbows and arrows for bows. Each of these will hold multiple items to provide a store of ammo for a weapon. Notice that we don\'t have the single items, like a stone or arrow. This is simply a design choice and my dislike of single item ammo. A single arrow is next to useless in a game such as this, so I have dispensed with the single items and replaced them with ammo "boxes". However, this does introduce one contentious factor in the game: when a shot misses, the ammo will vanish as there are no single ammo items in the game. While this may seem a bit odd, the rationale here is that the ammo simply becomes useless after it is used. An arrow or stone bouncing off some plate armor will probably shatter in "real life" so there is some justification for this. Mostly though, it is a simplification and my own pet peeve about single item ammo.\n\nThe last field in the weapon Type is /ammocnt/ and this is the amount of ammo currently loaded in the weapon. In most cases this will be 1, since the capacity for most weapons is 1, however in the case of the barrel crossbow, the capacity is 6, so ammocnt could be anywhere from 0 to 6.\n\nWhen we generate a new projectile weapon we need to fill out these fields.\n\n\x3Cdiv class="code"\x3E\n/inv.bi:GenerateWeapon/\n{{{\n      Case wpBarrelcrossbow  \'2 hands, dam 18\n         inv.desc = "Barrel Crossbow"\n         inv.icon = Chr(209)\n         inv.weapon.noise = 12\n         inv.weapon.dam = 18\n         inv.weapon.hands = 2\n         inv.weapon.weapontype = wtProjectile\n         inv.weapon.capacity = 6\n         inv.weapon.ammotype =  amCaseBolts\n}}}\n\x3C/div\x3E\nHere is our six-shooter weapon, the barrel crossbow. Notice the capacity is set to 6, so that we can have up to 6 bolts in this weapon. The ammo type is set to /amCaseBolts/ so that we know that this weapon uses bolts and not arrows or stones.\n\nNow that the weapon data is set, let\'s look at the ammo data.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Ammo type.\nType ammotype\n   id As ammoids\n   cnt As Integer\n   noise As Integer\n   eval as Integer\nEnd Type\n}}}\n\x3C/div\x3E\nThis is quite straightforward and should be self-explanatory. The only reason we need an ammo type is so the character can have some ammo in his inventory to reload the appropriate weapon. The key field here is /cnt/ which indicates how much ammo is contained within this ammo box. The character does not need to evaluate ammo, as ammo doesn\'t have any special properties, but we need the field so that our evaluation routine will work properly. When we create the ammo, we will mark it as evaluated so that it shows up properly in the inventory list.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Generate an ammo item.\nSub GenerateAmmo(inv As invtype, currlevel As Integer, ammoid As ammoids = amNone)\n   Dim item As ammoids\n\n   If ammoid = amNone Then\n      item = RandomRange(amBagStones, amQuiverArrows)\n   Else\n      item = ammoid\n   End If\n   \'Set the ammo item id.\n   inv.classid = clAmmo\n   inv.ammo.id = item\n   inv.iconclr = fbCadmiumYellow\n   inv.ammo.eval = TRUE\n   Select Case item\n      Case amBagStones   \n         inv.desc = "Bag of Stones"\n         inv.ammo.cnt = RandomRange(10, 20)\n         inv.ammo.noise = inv.ammo.cnt \n         inv.icon = Chr(167)\n      Case amCaseBolts\n         inv.desc = "Case of Bolts"\n         inv.ammo.cnt = RandomRange(10, 20)\n         inv.ammo.noise = inv.ammo.cnt \n         inv.icon = Chr(22)\n      Case amQuiverArrows\n         inv.desc = "Quiver of Arrows"\n         inv.ammo.cnt = RandomRange(10, 20)\n         inv.ammo.noise = inv.ammo.cnt \n         inv.icon = Chr(173)\n      Case Else\n         inv.desc = "Unknown Ammo"\n         inv.ammo.cnt = 0\n         inv.ammo.noise = 0 \n         inv.icon = "?"\n   End Select\n   \nEnd Sub\n}}}\n\x3C/div\x3E\nAs you can see we are using our standard format here for creating an item. You will notice that throughout this program, we follow the same template in most of the subroutines and functions. This gives the program consistency, and more importantly, it allows you to see at a glance if everything is set up properly. This consistent structure also helps reduce the number of bugs, since each item is created in the same way, and follows the same process. Using this "template" idea in your programs makes the program easier to code, easier to maintain and easier to debug. \n\nSince each ammo item is an ammo box, we set /cnt/ to a random number between 10 and 20. Cnt here is the key field that we will use use to load our weapon with ammo.\n\nA projectile weapon is useless unless it has ammo in it, so we need to create a load routine. We do that in /LoadAmmo/.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Loads ammo into projectile weapon.\nSub LoadAmmo ()\n   Dim amid As ammoids\n   Dim As Integer ret\n   \n   \'Make sure character has projectile weapon.\n   If pchar.ProjectileEquipped(wAny) = TRUE Then\n      \'Check first slot.\n      If pchar.ProjectileEquipped(wPrimary) = TRUE Then\n         If pchar.IsLoaded(wPrimary) = FALSE Then\n            \'Get the ammo id for weapon.\n            amid = pchar.GetAmmoID(wPrimary)\n            \'Make sure that we have a valid id.\n            If amid \x3C\x3E amNone Then\n               \'Check the inventory and load weapon if ammo is present.\n               ret = pchar.LoadProjectile(amid, wPrimary)\n               If ret = TRUE Then\n                  PrintMessage "Weapon is loaded."\n               Else\n                  PrintMessage "Weapon could not be loaded."\n               EndIf\n            EndIf\n         Else\n            PrintMessage "Weapon is already loaded."\n         EndIf\n      End If\n      If pchar.ProjectileEquipped(wSecondary) = TRUE Then\n         If pchar.IsLoaded(wSecondary) = FALSE Then\n            \'Check the inventoru and load weapon if ammo is present.\n            ret = pchar.LoadProjectile(amid, wSecondary)\n            If ret = TRUE Then\n               PrintMessage "Weapon is loaded."\n            Else\n               PrintMessage "Weapon could not be loaded."\n            EndIf\n         Else\n            PrintMessage "Weapon is already loaded."\n         EndIf\n      EndIf\n   Else\n      PrintMessage "Not carrying a projectile weapon."\n   EndIf\nEnd Sub\n}}}\n\x3C/div\x3E\nThere are two sections to this code, one to handle a weapon in the primary slot and one to handle a weapon in the secondary slot. Most projectile weapons are two-handed, except for the sling, which can be used with one hand. If there were no one-handed weapons, we would only need one section of code. Both sections of code work in exactly the same way, so we will look at the primary section only.\n\nThe first thing we do is to check to make sure that the character is holding a projectile weapon. We do that in the /ProjectileEquipped/ method of the character object.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n\'Returns True if character has pojectile weapon equipped.\nFunction character.ProjectileEquipped(slot As wieldpos) As Integer \n   Dim As Integer ret = FALSE\n   \n   \'Make sure the classid is a weapon.\n   If _cinfo.cwield(wPrimary).classid = clWeapon Then\n      \'Make sure the weapon is a projectile.\n      If _cinfo.cwield(wPrimary).weapon.weapontype = wtProjectile Then\n         \'Check the slot.\n         If (slot = wAny) Or (slot = wPrimary) Then\n            ret = TRUE\n         End If\n      EndIf\n   Else\n      \'Make sure the classid is a weapon.\n      If _cinfo.cwield(wSecondary).classid = clWeapon Then\n         \'Make sure the weapon is a projectile.\n         If _cinfo.cwield(wSecondary).weapon.weapontype = wtProjectile Then\n            \'Check the slot.\n            If (slot = wAny) Or (slot = wSecondary) Then\n               ret = TRUE\n            End If\n         EndIf\n      EndIf\n   EndIf\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nFirst we check to make sure that the character is holding a weapon in either the primary or secondary slot. We then check to see if the weapon is marked as a projectile weapon using the /weapontype/ field. If the weapon is marked as a projectile, we then return TRUE. Notice that we not only check against each slot, but we check for the /wAny/ flag as well. In the /LoadAmmo/ subroutine we first check for a projectile in either hand by sending wAny as a parameter to this function. The first thing we need to know when loading a weapon is if the character is holding a projectile weapon in any hand. If he isn\'t we can exit the LoadAmmo subroutine early and not waste time looking at each slot.\n\nIn LoadAmmo, once we determine if the character is carrying a projectile weapon, we then proceed to examine each slot for the weapon.\n\n\x3Cdiv class="code"\x3E\n/dod.bas:LoadAmmo/\n{{{\n...\n      If pchar.ProjectileEquipped(wPrimary) = TRUE Then\n         If pchar.IsLoaded(wPrimary) = FALSE Then\n...\n}}} \n\x3C/div\x3E\nThe next thing we need to check is to make sure that the weapon needs to be loaded. If it is already loaded, we don\'t need to reload the weapon. The /IsLoaded/ function tells u if the current weapon is loaded or not.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n\'Returns true if projectile weapon isn slot has ammo.\nFunction character.IsLoaded(slot As wieldpos) As Integer\n   Dim As Integer ret = FALSE\n   \n   \'Make sure we have a weapon.\n   If _cinfo.cwield(slot).classid = clWeapon Then\n      \'Make sure it is a projectile.\n      If _cinfo.cwield(slot).weapon.weapontype = wtProjectile Then\n         \'Check the loaded flag.\n         ret = (_cinfo.cwield(slot).weapon.ammocnt \x3C _cinfo.cwield(slot).weapon.capacity)\n      EndIf\n   EndIf\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nThis just looks at the /ammocnt/ field of the relevant weapon slot to see if it is less than the weapons capacity. Remember that a weapon may hold more than one shot, and any multi-shot weapon can be reloaded any time the ammocnt is less than the capacity.\n\nOnce we determine that the weapon needs to be loaded, we need to find out what kind of ammo the weapon needs.\n\n\x3Cdiv class="code"\x3E\n/dod.bas:LoadAmmo/\n{{{\n...\n   \'Get the ammo id for weapon.\n   amid = pchar.GetAmmoID(wPrimary)\n   \'Make sure that we have a valid id.\n   If amid \x3C\x3E amNone Then\n...\n}}}\n\x3C/div\x3E\nThe /GetAmmoId/ will return the ammo type for the selected weapon.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n\'Returns the ammoid for projectile weapon in slot.\nFunction character.GetAmmoID(slot As wieldpos) As ammoids\n   Dim ret As armorids\n   \n   \'Check to see if slot is holding a weapon.\n   If _cinfo.cwield(slot).classid = clWeapon Then\n      \'Make sure it is projectile weapon.\n      If _cinfo.cwield(slot).weapon.weapontype = wtProjectile Then\n         ret = _cinfo.cwield(slot).weapon.ammotype\n      Else\n         ret = amNone\n      EndIf\n   Else\n      ret = amNone\n   EndIf\n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nThis looks at the proper slot and returns the /ammotype/ for the weapon that we set when we created the ammo.\n\n\x3Cdiv class="code"\x3E\n/dod.bas:LoadAmmo/\n{{{\n...\n  \'Check the inventory and load weapon if ammo is present.\n  ret = pchar.LoadProjectile(amid, wPrimary)\n  If ret = TRUE Then\n    PrintMessage "Weapon is loaded."\n  Else\n    PrintMessage "Weapon could not be loaded."\n  EndIf\n...\n}}}\n\x3C/div\x3E\nIf we get a proper ammo id, then we can load the weapon. We do that in /LoadProjectile/.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n\'Returns True if weapon was loaded.\nFunction character.LoadProjectile(aid As ammoids, slot As wieldpos) As Integer \n   Dim As Integer ret = FALSE, i, iitem\n   Dim As invtype inv\n   \n   \'Look through the inventory and see if we have any ammo matching ammo id.\n   For i = LowInv To HighInv\n      \'If the weapon is full, then exit.\n      If _cinfo.cwield(slot).weapon.ammocnt = _cinfo.cwield(slot).weapon.capacity Then\n         Exit For\n      EndIf\n      iitem = HasInvItem(i)\n      If iitem = TRUE Then\n         \'Get the inv item.\n         GetInventoryItem i, inv\n         \'Check the class id.\n         If inv.classid = clAmmo Then\n            \'See if it is the same ammo as passed id.\n            If inv.ammo.id = aid Then\n               \'Load weapon to capacity.\n               Do While _cinfo.cwield(slot).weapon.ammocnt \x3C _cinfo.cwield(slot).weapon.capacity\n                  \'Load the weapon.\n                  _cinfo.cwield(slot).weapon.ammocnt += 1\n                  inv.ammo.cnt -= 1\n                  \'If no more ammo then exit.\n                  If inv.ammo.cnt = 0 Then\n                     Exit Do\n                  EndIf\n               Loop\n               \'Update the inventory.\n               If inv.ammo.cnt \x3E 0 Then\n                  AddInvItem i,inv\n               Else\n                  ClearInv inv\n                  AddInvItem i,inv\n               EndIf\n               \'Does the weapon have ammo?\n               If _cinfo.cwield(slot).weapon.ammocnt \x3E 0 Then\n                  ret = TRUE\n               EndIf\n            EndIf\n         EndIf\n      EndIf\n   Next\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nIn this function we iterate through the inventory using the LowInv and HighInv properties and look for some ammo that matches the weapon ammo type. If we find some, we then loop until the weapon is loaded in the While block {{{Do While _cinfo.cwield(slot).weapon.ammocnt \x3C _cinfo.cwield(slot).weapon.capacity}}}. We also need to be sure we decrement the ammo in the inventory {{{inv.ammo.cnt -= 1}} and then check to make sure we haven\'t run out of ammo {{{If inv.ammo.cnt = 0 Then}}}. After the weapon is loaded, we check the ammo count in the inventory object. If it is 0, we clear the inventory object and update the character\'s inventory, otherwise we just put the inventory object back into the character\'s inventory.\n\nNow that the weapon is loaded, we can use it. The first thing we need to do is to target an enemy and we do that in /TargetEnemy/.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Targets an enemy and attacks with projectile weapon.\nSub TargetEnemy ()\n   Dim As vec target, source\n   Dim As Integer ret\n   \n   \'Check to see if character is carrying projectile weapon.\n   If pchar.ProjectileEquipped(wAny) = TRUE Then\n      \'Get target vector.\n      ret = GetTargetCoord(target)\n      source.vx = pchar.Locx\n      source.vy = pchar.Locy\n      \'Player pressed enter.\n      If ret = TRUE Then\n         \'Check first slot to see if it has weapon.\n         If pchar.ProjectileEquipped(wPrimary) = TRUE Then\n            \'Make sure it is loaded.\n            If pchar.IsLoaded(wPrimary) = TRUE Then\n               \'Fire the weapon.\n               level.AnimateProjectile source, target\n               \'Remove prjectile from weapon.\n               pchar.DecrementAmmo wPrimary\n               \'Make sure we have targeted a monster.\n               If level.IsMonster(target.vx, target.vy) = TRUE Then\n                  \'Resolve hit if any.\n                  DoProjectileCombat target.vx, target.vy, wPrimary\n               Else\n                  PrintMessage "No monster at target location."\n               End If\n            Else\n               PrintMessage "Weapon is not loaded."\n            EndIf\n         Else \'Must be second slot.\n            \'Check to make sure the weapon is loaded.\n            If pchar.IsLoaded(wSecondary) = FALSE Then\n               \'Fire the weapon.\n               level.AnimateProjectile source, target\n               \'Remove prjectile from weapon.\n               pchar.DecrementAmmo wSecondary\n               \'Make sure we have targeted a monster.\n               If level.IsMonster(target.vx, target.vy) = TRUE Then\n                  \'Resolve hit if any.\n                  DoProjectileCombat target.vx, target.vy, wPrimary\n               Else\n                  PrintMessage "No monster at target location."\n               End If\n            Else\n               PrintMessage "Weapon is not loaded."\n            EndIf\n         EndIf\n      EndIf\n   Else\n      PrintMessage "Not carrying a projectile weapon."\n   EndIf\nEnd Sub\n}}}\n\x3C/div\x3E\nThe first thing we do here is check to make sure the character is carrying a projectile weapon. If so, we then get the target coordinates /GetTargetCoord/.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Returns true if character targeted enemy, false if not. Coords passed through vt.\nFunction GetTargetCoord(vt As vec) As Integer\n   Dim As String ch, rtl = "*"\n   Dim As vec v, vtmp\n   Dim As Integer ret = FALSE\n   \n   \'Set the reticule coordinates.\n   v.vx = pchar.Locx\n   v.vy = pchar.Locy\n   \'Set new target.\n   level.SetTarget(v.vx, v.vy, Asc(rtl), fbYellowBright)\n   level.DrawMap\n   Do\n      ch = InKey\n      If ch \x3C\x3E "" Then\n         If ch = key_up Then\n            \'Set the current vector.\n            vtmp = v\n            \'Update temp vector.\n            vtmp += north\n            \'Check the tile.\n            If level.IsTileVisible(vtmp.vx, vtmp.vy) = TRUE Then\n               \'Clear previous target space.\n               level.SetTarget(v.vx, v.vy, 0)\n               \'Save the current location.\n               v = vtmp\n               \'Set new target.\n               level.SetTarget(v.vx, v.vy, Asc(rtl), fbYellowBright)\n               \'Redraw the map.\n               ScreenLock\n               level.DrawMap\n               ScreenUnLock\n            End If\n         EndIf\n         If ch = key_dn Then\n            \'Set the current vector.\n            vtmp = v\n            \'Update temp vector.\n            vtmp += south\n            \'Check the tile.\n            If level.IsTileVisible(vtmp.vx, vtmp.vy) = TRUE Then\n               \'Clear previous target space.\n               level.SetTarget(v.vx, v.vy, 0)\n               \'Save the current location.\n               v = vtmp\n               \'Set new target.\n               level.SetTarget(v.vx, v.vy, Asc(rtl), fbYellowBright)\n               \'Redraw the map.\n               ScreenLock\n               level.DrawMap\n               ScreenUnLock\n            End If\n         EndIf\n         If ch = key_rt Then\n            \'Set the current vector.\n            vtmp = v\n            \'Update temp vector.\n            vtmp += east\n            \'Check the tile.\n            If level.IsTileVisible(vtmp.vx, vtmp.vy) = TRUE Then\n               \'Clear previous target space.\n               level.SetTarget(v.vx, v.vy, 0)\n               \'Save the current location.\n               v = vtmp\n               \'Set new target.\n               level.SetTarget(v.vx, v.vy, Asc(rtl), fbYellowBright)\n               \'Redraw the map.\n               ScreenLock\n               level.DrawMap\n               ScreenUnLock\n            End If\n         EndIf\n         If ch = key_lt Then\n            \'Set the current vector.\n            vtmp = v\n            \'Update temp vector.\n            vtmp += west\n            \'Check the tile.\n            If level.IsTileVisible(vtmp.vx, vtmp.vy) = TRUE Then\n               \'Clear previous target space.\n               level.SetTarget(v.vx, v.vy, 0)\n               \'Save the current location.\n               v = vtmp\n               \'Set new target.\n               level.SetTarget(v.vx, v.vy, Asc(rtl), fbYellowBright)\n               \'Redraw the map.\n               ScreenLock\n               level.DrawMap\n               ScreenUnLock\n            End If\n         EndIf\n      EndIf\n      Sleep 1\n   Loop Until (ch = key_enter) Or (ch = key_esc)\n   If ch = key_enter Then \n      ret = TRUE\n   EndIf\n   \'Clear previous target space.\n   level.SetTarget(v.vx, v.vy, 0)\n   level.DrawMap   \n   vt = v\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nIn this function we enter into an input loop and gather key strokes to move a cursor around the screen. The cursor coordinates will be stored in the passed vector /vt/ and will return the selected target. Since it doesn\'t make sense to target a tile the character can\'t see, only those tiles that are visible can selected, so we check the tile visibility using the /IsTileVisible/ function. When a direction key is pressed, we need to update the cursor position, and we do that by calling the /SetTarget/ subroutine of the level object, followed by a call to /DrawMap/ to actually display the new location.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Sets target map at x, y.\nSub levelobj.SetTarget(x As Integer, y As Integer, id As Integer, tcolor As UInteger = fbBlack) \n   _level.lmap(x, y).target.id = id\n   _level.lmap(x, y).target.tcolor = tcolor\nEnd Sub\n}}}\n\x3C/div\x3E\n/SetTarget/ updates a new data structure in our map object, target.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Target type.\nType targettype\n   id As Integer      \'Ascii code of icon.\n   tcolor As UInteger \'Color of icon.\nEnd Type\n\n\'Map info type\nType mapinfotype\n   terrid As terrainids  \'The terrain type.\n   monidx As Integer     \'Index into monster array.\n   visible As Integer    \'Character can see cell.\n   seen As Integer       \'Character has seen cell.\n   doorinfo As doortype  \'Door information.\n   sndvol As Integer     \'Sound volume at current tile.\n   target As targettype  \'Target and projectile map.\nEnd Type\n}}}\n\x3C/div\x3E\nThe target type contains fields for the ASCII code of the target cursor and the color of the cursor. This is contained within the /mapinfotype/ so that each tile in the map has a target location that we can display on the screen.\n\nLet\'s go over the process in /GetTargetCoord/ for a single direction. All the directions behave in the same manner, the only difference is the direction.\n\n\x3Cdiv class="code"\x3E\n/dod.bas:GetTargetCoord/\n{{{\n         If ch = key_up Then\n            \'Set the current vector.\n            vtmp = v\n            \'Update temp vector.\n            vtmp += north\n            \'Check the tile.\n            If level.IsTileVisible(vtmp.vx, vtmp.vy) = TRUE Then\n               \'Clear previous target space.\n               level.SetTarget(v.vx, v.vy, 0)\n               \'Save the current location.\n               v = vtmp\n               \'Set new target.\n               level.SetTarget(v.vx, v.vy, Asc(rtl), fbYellowBright)\n               \'Redraw the map.\n               ScreenLock\n               level.DrawMap\n               ScreenUnLock\n            End If\n         EndIf\n}}}\n\x3C/div\x3E\nHere the player has pushed the up key. We update the direction vector with the North value which places the vector just above the current position. We then check to make sure the tile is visible. If it is, then we clear the current cursor position by passing a 0 value to /SetTarget/. We then save the new position for the next key press, update the target array with the new cursor position, and finally redraw the map. This will provide the animation we need to move the cursor around the visible tiles.\n\n\x3Cdiv class="code"\x3E\n/dod.bas:GetTargetCoord/\n{{{\n   Loop Until (ch = key_enter) Or (ch = key_esc)\n   If ch = key_enter Then \n      ret = TRUE\n   EndIf\n}}}\n\x3C/div\x3E\nWe exit the input loop when the player presses either the Enter or Esc key. If the user pressed the Enter key, we return TRUE indicating that the player has selected a target. Esc will return FALSE, indicating that the player cancelled the target selection process.\n\nIf we get a return value of TRUE in /TargetEnemy/ then we process the target selection.\n\n\x3Cdiv class="code"\x3E\n/dod.bas:TargetEnemy/\n{{{\n      \'Player pressed enter.\n      If ret = TRUE Then\n         \'Check first slot to see if it has weapon.\n         If pchar.ProjectileEquipped(wPrimary) = TRUE Then\n            \'Make sure it is loaded.\n            If pchar.IsLoaded(wPrimary) = TRUE Then\n               \'Fire the weapon.\n               level.AnimateProjectile source, target\n}}}\n\x3C/div\x3E\nLike we did in /LoadAmmo/, we need to check both the primary and secondary slots for a projectile weapon. The process is the same for both, so we will look at the primary slot. The /IsLoaded/ function tells us the weapon is loaded and ready to fire. We then animate the projectile on the screen using the /AnimateProjectile/ subroutine of the level object.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Animates projectile.\nSub levelobj.AnimateProjectile (source As vec, target As vec)\n   Dim As Integer x = source.vx, y = source.vy, d = 0, dx = target.vx - source.vx \n   Dim As Integer dy = target.vy - source.vy, c, m, xinc = 1, yinc = 1\n   Dim As Integer delay = 10\n   \n   If dx \x3C 0 Then\n      xinc = -1\n      dx = -dx\n   EndIf\n   If dy \x3C 0 Then\n      yinc = -1\n      dy = -dy\n   EndIf\n   If dy \x3C dx Then\n      c = 2 * dx\n      m = 2 * dy\n      Do While x \x3C\x3E target.vx\n         _level.lmap(x, y).target.id = 7\n         _level.lmap(x, y).target.tcolor = fbYellow\n         DrawMap\n         Sleep delay\n         _level.lmap(x, y).target.id = 0\n         _level.lmap(x, y).target.tcolor = fbBlack\n         DrawMap\n         x += xinc\n         d += m\n         If d \x3E dx Then\n            y += yinc\n            d -= c\n         EndIf\n      Loop\n   Else\n      c = 2 * dy\n      m = 2 * dx\n      Do While y \x3C\x3E target.vy\n         _level.lmap(x, y).target.id = 7\n         _level.lmap(x, y).target.tcolor = fbYellow\n         DrawMap\n         Sleep delay\n         _level.lmap(x, y).target.id = 0\n         _level.lmap(x, y).target.tcolor = fbBlack\n         DrawMap\n         y += yinc\n         d += m\n         If d \x3E dy Then\n            x += xinc\n            d -= c\n         EndIf\n      Loop\n   EndIf\n   _level.lmap(x, y).target.id = 249\n   _level.lmap(x, y).target.tcolor = fbYellow\n   DrawMap\n   Sleep delay\n   _level.lmap(x, y).target.id = 0\n   _level.lmap(x, y).target.tcolor = fbBlack\n   DrawMap\nEnd Sub\n}}}\n\x3C/div\x3E\nThe source vector is the character position and the target vector is the selected coordinates. We are using Bresenham\'s line algorithm here, updating the target array for each tile visited and then redrawing the map. When the subroutine executes, we have a projectile flying across the screen to the target.\n\nAfter we animate the projectile in /TargetEnemy/ we need to decrement the ammo in the weapon.\n\n\x3Cdiv class="code"\x3E\n/dod.bas:TargetEnemy/\n{{{\n   \'Fire the weapon.\n   level.AnimateProjectile source, target\n   \'Remove projectile from weapon.\n   pchar.DecrementAmmo wPrimary\n}}}\n\x3C/div\x3E\nThe /DecrementAmmo/ subroutine of the character object will remove one shot from the selected weapon slot.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n\'Decrements the ammo in held weapon.\nSub character.DecrementAmmo(slot As wieldpos)\n   _cinfo.cwield(slot).weapon.ammocnt -= 1\n   If _cinfo.cwield(slot).weapon.ammocnt \x3C 0 Then \n      _cinfo.cwield(slot).weapon.ammocnt = 0\n   EndIf\nEnd Sub\n}}}\n\x3C/div\x3E\nAs you can see, we just decrease the /ammocnt/ of the weapon by one.\n\nNext we need to see if we hit something in /TargetEnemy/ and we do that next.\n\n\x3Cdiv class="code"\x3E\n/dod.bas:TargetEnemy/\n{{{\n    \'Remove projectile from weapon.\n    pchar.DecrementAmmo wPrimary\n    \'Make sure we have targeted a monster.\n    If level.IsMonster(target.vx, target.vy) = TRUE Then\n      \'Resolve hit if any.\n      DoProjectileCombat target.vx, target.vy, wPrimary\n    Else\n      PrintMessage "No monster at target location."\n    End If\n  Else\n    PrintMessage "Weapon is not loaded."\n  EndIf\n}}}\n\x3C/div\x3E\nWe first check to make sure a monster is in the selected location. If so, we then resolve the projectile combat in /DoProjectileCombat/.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Perform projectile combat.\nSub DoProjectileCombat(mx As Integer, my As Integer, wslot As wieldpos)\n   Dim As Integer cf, df, croll, mroll, dam, isdead, midx, mxp, xp \n   Dim As String txt, mname\n   Dim As Single marm\n   \n   \'Get the monster defense factor.\n   df = level.GetMonsterDefense(mx, my)\n   mname = level.GetMonsterName(mx, my)\n   \'Get monster xp.\n   mxp = level.GetMonsterXP(mx, my)\n   \'Make sure we got something.\n   If df \x3E 0 Then\n      \'Returns the combat factor based on what the character is holding.\n      cf = pchar.GetProjectileCombatFactor()\n      \'Get the rolls.\n      croll = RandomRange(1, cf)\n      mroll = RandomRange(1, df)\n      \'See if the character hit monster.      \n      If croll \x3E mroll Then\n         \'Get total weapon damage.\n         dam = pchar.GetWeaponDamage(wslot)\n         \'Get monster armor rating.\n         marm = level.GetMonsterArmor(mx, my)\n         \'Adjust the damage value based on armor rating.\n         dam = dam - (dam * marm)\n         If dam \x3C= 0 Then dam = 1\n         \'Set the monster hp.\n         isdead = level.ApplyDamage(mx, my, dam)\n         \'Print the message.\n         If isdead = TRUE Then\n            \'Add xp amount to character.\n            xp = pchar.CurrXP\n            xp += mxp\n            pchar.CurrXP = xp\n            xp = pchar.TotXP\n            xp += mxp\n            pchar.TotXP = xp\n            \'Print message.\n            txt = pchar.CharName & " killed the " & mname & " with " & dam & " damage points."\n            PrintMessage txt\n         Else\n            txt = pchar.CharName & " hit the " & mname & " for " & dam & " damage points."\n            PrintMessage txt\n         EndIf\n      Else\n         txt = pchar.CharName & " missed the " & mname & "."\n         PrintMessage txt\n      EndIf\n   EndIf\nEnd Sub\n}}}\n\x3C/div\x3E\nThe process here is exactly the same as in melee combat, we are just using the projectile combat values rather than the melee values. The /GetProjectileCombatFactor/ returns the combined combat factors.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n\'Return projectile combat factor.\nFunction character.GetProjectileCombatFactor() As Integer\n   Return CurrPCF + BonPcf\nEnd Function\n}}}\n\x3C/div\x3E\nJust as we do in melee combat, we generate two rolls and compare the two. If the character hits the monster, weapon damage is applied, minus any monster armor values. If the character manages to kill the monster, then the character gains some experience points and the monster is removed from the game.\n\nAs you can see, what seems to be so simple, actually requires quite a lot of code to implement the functionality correctly. Projectile combat though, adds a lot of depth to the game and is well worth the effort.',
'= 23: Scaling Monster Attacks\n\nAt this point in the game development, we can make some tweaks to the game so that it plays a bit better. Right now, the monsters are really tough. We need to fix that and it is quite a simple fix. What we will do is to modify the attack damage of the monster by a scaling factor based on the level. What we want to do is to scale back the damage by a percentage amount, and as the player dives deeper into the dungeon, gradually let the damage increase to its full amount. By the time the player has reached the final level, his character should be strong enough to take on the monsters in their full power.\n\nWe will implement the scaling in the /GenerateMonster/ subroutine.\n\n\x3Cdiv class="code"\x3E\n/monster.bi/\n{{{\nSub GenerateMonster(mon As montype, currlevel As Integer)\n   Dim As invtype inv\n   Dim scaling As Integer \'Mon trribute scaling.\n   Dim stratt As Integer    \'Strength attribute \n   Dim staatt As Integer    \'Stamina  \n   Dim dexatt As Integer    \'Dexterity \n   Dim aglatt As Integer    \'Agility \n   Dim intatt As Integer    \'Intelligence \n   Dim ucfsk As Integer     \'Unarmed combat skill\n   Dim acfsk As Integer     \'Armed combat skill\n   Dim pcfsk As Integer     \'Projectile combat skill\n   Dim pct As Double        \'Percentage adjustment for monsters.\n   \n   \'Calculate the current percentage based on the level.\n   pct = currlevel / maxlevel\n...\n}}}\n\x3C/div\x3E\nHere we have added a new variable to the list, /pct/. This is a double value that contains a scaling percentage for the current level which is passed in through currlevel. At level 1 the percentage is .02 and at maxlevel, the percentage is 1. \n\nAfter we calculate the attack damage for the monster, we then adjust the value by the percentage.\n\n\x3Cdiv class="code"\x3E\n/monster.bi/\n{{{\n...\n   \'Set the xp and max hp values.\n   mon.xp = mon.currhp\n   \'Adjust the attack value by level percent.\n   mon.atkdam = mon.atkdam * pct\n   If mon.atkdam \x3C 1 Then mon.atkdam = 1\nEnd Sub\n}}}\n\x3C/div\x3E\nSo for example, if a monster attacks for 30 damage points, at level 1 then amount of damage would be 0.6 which we will round up to 1. At maxlevel, the attack damage would be the full 30 points.\n\nJust a few lines of code, and we now have monsters that our character can reasonably stand against.',
'= 24: Wands\nWands are a staple in most roguelikes, and /Dungeon of Doom/ wouldn\'t seem complete if we didn\'t implement wands. There are usually two ways wands are used in a game; as a spell caster and as an offensive weapon. I personally view wands much like the Wizard\'s staff used in the /Lord of the Rings/, that is, as an offensive weapon. Since we already have projectile combat in place, it is quite natural to make wands just another projectile weapon, and that is how we are going to implement them in the game. As you will see, adding wands is quite easy.\n\nFirst, let\'s establish some rules for wands. There will be different types, based on different metals such as an Iron Wand and a Gold Wand, with the metal rarity a sign for the power of the wand. Iron wands will be the lowest power while Gold Wands will have the most power. To balance the wands, we will give the lower power wands more capacity, and the higher power wands the least capacity. Wands will have a certain number of charges and once a wand runs out, it cannot be "reloaded". When we get to the magic system, we will add a spell for recharging a wand, but for now, once it is empty, that is it. Even though wands are really just projectile weapons, we will classify them as magical, so we will resolve wand combat using the magic combat and defense factors rather than the projectile combat and defense factors.\n\nThe first thing we need to do is to add some wands to our weapon list.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Weapon ids.\nEnum weaponids\n...\n   wpAdaminecrossbow \'2 hands, dam 25\n   wpIronWand        \'1 hand, dam 10\n   wpBrassWand       \'1 hand, dam 20\n   wpCopperWand      \'1 hand, dam 40\n   wpSilverWand      \'1 hand, dam 80\n   wpGoldWand        \'1 hand, dam 100\nEnd Enum\n}}}\n\x3C/div\x3E\nHere we have our wand ids. As you can see in the comments, wands are powerful items which is why we need to limit the wands use in the game.\n\nThe next thing we need to do is to add in the wands to our /GenerateWeapon/ subroutine.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n      Case wpIronWand \'1 hand, dam 10\n         inv.desc = "Iron Wand"\n         inv.icon = Chr(139)\n         inv.weapon.noise = 1\n         inv.weapon.dam = 10\n         inv.weapon.hands = 1\n         inv.weapon.weapontype = wtProjectile\n         inv.weapon.capacity = RandomRange(1, 100)\n         inv.weapon.ammotype =  amNone\n         inv.weapon.ammocnt = inv.weapon.capacity\n         inv.weapon.iswand = TRUE\n      Case wpBrassWand \'1 hand, dam 20\n         inv.desc = "Brass Wand" \n         inv.icon = Chr(139)\n         inv.weapon.noise = 1\n         inv.weapon.dam = 20\n         inv.weapon.hands = 1\n         inv.weapon.weapontype = wtProjectile\n         inv.weapon.capacity = RandomRange(1, 80)\n         inv.weapon.ammotype =  amNone\n         inv.weapon.ammocnt = inv.weapon.capacity \n         inv.weapon.iswand = TRUE\n      Case wpCopperWand \'1 hand, dam 40\n         inv.desc = "Copper Wand"\n         inv.icon = Chr(139)\n         inv.weapon.noise = 1\n         inv.weapon.dam = 40\n         inv.weapon.hands = 1\n         inv.weapon.weapontype = wtProjectile\n         inv.weapon.capacity = RandomRange(1, 40)\n         inv.weapon.ammotype =  amNone\n         inv.weapon.ammocnt = inv.weapon.capacity \n         inv.weapon.iswand = TRUE\n      Case wpSilverWand \'1 hand, dam 80\n         inv.desc = "Silver Wand"\n         inv.icon = Chr(139)\n         inv.weapon.noise = 1\n         inv.weapon.dam = 80\n         inv.weapon.hands = 1\n         inv.weapon.weapontype = wtProjectile\n         inv.weapon.capacity = RandomRange(1, 20)\n         inv.weapon.ammotype =  amNone\n         inv.weapon.ammocnt = inv.weapon.capacity \n         inv.weapon.iswand = TRUE\n      Case wpGoldWand \'1 hand, dam 100\n         inv.desc = "Gold Wand"\n         inv.icon = Chr(139)\n         inv.weapon.noise = 1\n         inv.weapon.dam = 100\n         inv.weapon.hands = 1\n         inv.weapon.weapontype = wtProjectile\n         inv.weapon.capacity = RandomRange(1, 10)\n         inv.weapon.ammotype =  amNone\n         inv.weapon.ammocnt = inv.weapon.capacity \n         inv.weapon.iswand = TRUE\n}}}\n\x3C/div\x3E\nNotice that we have added a new field to our weapon Type Def, /iswand/. We will use this field when we resolve the combat for wands. For most weapons, this will be FALSE. We set it to TRUE for wands when we create the wand.\n\nThe key here is the /capacity/ field for wands. The Iron Wand has a capacity of 1 to 100, while the GoldWand only has a capacity range of 1 to 10. Once we set the capacity of the wand, we use that value for the number of charges the wand has in the /ammocnt/ field. Since wands cannot be reloaded, the /ammotype/ is set to /amNone/.\n\nThat is all we need to do to have wands as a new inventory item. In order to use wands we need to make a couple of changes to the projectile combat routine.\n\n\x3Cdiv class="code"\x3E\n/dod.bas:DoProjectileCombat/\n{{{\n...\n   \'Get the monster defense factor.\n   If pchar.ProjectileIsWand(wslot) = TRUE Then\n      df = level.GetMonsterMagicDefense(mx, my)\n   Else\n      df = level.GetMonsterDefense(mx, my)\n   EndIf\n\n...\n\n      \'Returns the combat factor based on what the character is holding.\n      If pchar.ProjectileIsWand(wslot) = TRUE Then\n         cf = pchar.GetMagicCombatFactor()\n      Else\n         cf = pchar.GetProjectileCombatFactor()\n      EndIf\n...\n}}}\n\x3C/div\x3E\nThe /ProjectileIsWand/ function simply returns the value in the /iswand/ field. If it is TRUE, then we get the monster magic defense factor from /GetMonsterMagicDefense/ and the combat factor for the character from /GetMagicCombatFactor/. Believe it or not, this is the only change we need to make to the projectile combat.\n\nLet\'s take a quick look at the two new functions.\n\n\x3Cdiv class="code"\x3E\n/level.bi/\n{{{\n\'Returns the magic defense of the monster.\nFunction levelobj.GetMonsterMagicDefense(mx As Integer, my As Integer) As Integer\n   Dim As Integer ret = 0, midx = 0\n   \n   \'Make sure there is a monster here.\n   If _level.lmap(mx, my).monidx \x3E 0 Then\n      midx = _level.lmap(mx, my).monidx\n      ret = _level.moninfo(midx).md\n   EndIf\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nHere we just check to make sure that we have a monster at the passed coordinates and then return the /md/ field which is the monster magic defense factor.\n\nThe character magic combat factor is equally simple.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n\'Return magic combat factor.\nFunction character.GetMagicCombatFactor() As Integer\n   Return CurrMCF + BonMcf\nEnd Function\n}}}\n\x3C/div\x3E\nThis just returns the magic combat factor and any applied bonuses.\n\nThe last thing we need to do is to update the item description subroutine to handle the new fields.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n...\n         Case clWeapon\n            idx += 1\n            ReDim Preserve lines(0 to idx) As String\n            lines(idx) = GetInvItemDesc(inv)\n            idx += 1\n            ReDim Preserve lines(0 to idx) As String\n            lines(idx) = "* " & inv.weapon.dam & " weapon damage"\n            idx += 1\n            ReDim Preserve lines(0 to idx) As String\n            lines(idx) = "* Hands Required: " & inv.weapon.hands\n            If inv.weapon.weapontype = wtProjectile Then\n               idx += 1\n               ReDim Preserve lines(0 to idx) As String\n               lines(idx) = "* Capacity: " & inv.weapon.capacity\n               idx += 1\n               ReDim Preserve lines(0 to idx) As String\n               lines(idx) = "* Ammo Cnt: " & inv.weapon.ammocnt\n            EndIf\n            idx += 1\n            ReDim Preserve lines(0 to idx) As String\n            lines(idx) = "* Magic: Offense and Defense"\n            idx += 1\n            ReDim Preserve lines(0 to idx) As String\n            If IsEval(inv) = TRUE Then\n               lines(idx) = "* Item is evaluated"\n               idx += 1\n               ReDim Preserve lines(0 to idx) As String\n               lines(idx) = "* Spell: " & GetSpellDescriptions(inv.weapon.spell)\n            Else\n               lines(idx) = "* Item is not evaluated"\n            End If\n...\n}}}\n\x3C/div\x3E\nWe have added an IF section here to handle the projectile weapon capacity and ammo counts. This not only works for wands, but any projectile weapon.\n\nThat is all there is to adding wands to the game. As you can see, we have leveraged our projectile code to add a new item to the game, once that is both familiar and exotic. The key here is the capacity field in the projectile weapons. Without it, we couldn\'t have added in wands so easily, but that single field and associated code, generalized the projectile weapons so that we can handle even exotic items like wands.\n',
'= 25: Generalizing Character Attributes\nIn the next chapter we will be adding potions to the game. Potions will affect the character\'s attributes, and right now, accessing the character attributes is a bit cryptic. If you remember, we set up the attributes using a three-element array, with index 0 being the base value, index 1 the bonus amount, and index 2 the length the bonus is in effect measured in number of turns. While this works for what we have now, trying to remember what 0 stands for in the code may be a problem if we want to update the code some weeks or months from now. Rather than using 0, we can use a mnemonic value, like /idxAttr/ to stand for the base attribute value. This is much more clear, and allows us to modify the program very easily by just changing the value of idxAttr.\n\nSince we have arrays for both the attributes and combat factors, the easiest way to generalize this is to use an Enumeration.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n\'Indexes into attribute/factor arrays.\nEnum attrindex\n   idxAttr = 0 \'Attribute/factor\n   idxAttrBon \'Bonus\n   idxAttrCnt \'Count\n   idxArrMax \'Array max index. Insert new index before this entry.\nEnd Enum\n}}}\n\x3C/div\x3E\nWe have added a new Enum to the character file called /attrindex/. The values start with 0, and are incremented by one for each Enum id. So /idxAttr/ = 0, /idxAttrBon/ = 1, /idxAttrCnt/ = 2 and /idxArrMax/ = 3. The last value, idxArrMax is the number of elements in both the attribute and factor arrays. Now, we could have just used a /#Define/ for these values, however using an enumeration offers us some advantages.\n\nSuppose we need to add a new element to the array to handle some additional functionality. All we need to do is just insert the new element /before/ the idxArrmax entry, and the array will automatically be re-sized to the correct dimensions since the value of idxArrMax will automatically increase by 1 when the enumeration is created. We won\'t have to fiddle with the array elements in our character object, all we would need to do is to add in the new functions necessary to handle the new array element. This makes updating the character object a breeze.\n\nEven though we have quite a few places in the code where we access the different array elements, updating the code is actually quite easy. We simple use our editor\'s search and replace function and replace /(0)/ with /(idxAttr)/ wherever we are accessing an attribute or factor array element. This requires some caution of course, but using /find and replace/ rather than the global /replace all/ will allow us to inspect each replacement to make sure we are updating the correct line of code.\n\nLet\'s look at some examples of how this is going to benefit us.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n\'Character attribute type def.\nType characterinfo\n   cname As String * 35 \'Name of character.\n   stratt(idxArrMax) As Integer \'Strength attribute (0), str bonus (1), bonus length in turns(2)\n   staatt(idxArrMax) As Integer \'Stamina  \n   dexatt(idxArrMax) As Integer \'Dexterity \n   aglatt(idxArrMax) As Integer \'Agility \n   intatt(idxArrMax) As Integer \'Intelligence \n   currhp As Integer    \'Current HP\n   maxhp As Integer     \'Max HP\n   currmana As Integer  \'Current mana\n   maxmana As Integer   \'Max mana\n   ucfsk(idxArrMax) As Integer  \'Unarmed combat skill\n   acfsk(idxArrMax) As Integer  \'Armed combat skill\n   pcfsk(idxArrMax) As Integer  \'Projectile combat skill\n   mcfsk(idxArrMax) As Integer  \'Magic combat skill \n   cdfsk(idxArrMax) As Integer  \'Combat defense skill\n   mdfsk(idxArrMax) As Integer  \'Magic defense skill \n...\n}}}\n\x3C/div\x3E\nHere we are creating our attribute and factor arrays. Notice that instead of using the cryptic 3 we had before, we are using /idxArrMax/ now. As you can see, if we update the enumeration, we don\'t have to do anything here. The code will automatically create the proper size array. Quite nifty.\n\n\x3Cdiv class="code"\x3E\n/character.bi:GenerateCharacter/\n{{{\n...\n   \'Display character.\n   If ret = TRUE Then\n      \'Generate the character data.\n      Do\n         With _cinfo\n            .cname = chname\n            .stratt(idxAttr) = RandomRange (10, 20) \'Generate new stat.\n            .staatt(idxAttr) = RandomRange (10, 20)\n            .dexatt(idxAttr) = RandomRange (10, 20)\n            .aglatt(idxAttr) = RandomRange (10, 20)\n            .intatt(idxAttr) = RandomRange (10, 20)\n            .stratt(idxAttrBon) = 0                   \'Clear any bonuses.\n            .staatt(idxAttrBon) = 0\n            .dexatt(idxAttrBon) = 0\n            .aglatt(idxAttrBon) = 0\n            .intatt(idxAttrBon) = 0\n            .stratt(idxAttrCnt) = 0                   \'Clear counts.\n            .staatt(idxAttrCnt) = 0\n            .dexatt(idxAttrCnt) = 0\n            .aglatt(idxAttrCnt) = 0\n            .intatt(idxAttrCnt) = 0\n            .currhp = .stratt(idxAttr) + .staatt(idxAttr) \n            .maxhp = .currhp\n            .currmana = .intatt(idxAttr) + .staatt(idxAttr) \n            .maxmana = .currmana\n            .ucfsk(idxAttr) = .stratt(idxAttr) + .aglatt(idxAttr) \n            .acfsk(idxAttr) = .stratt(idxAttr) + .dexatt(idxAttr) \n            .pcfsk(idxAttr) = .dexatt(idxAttr) + .intatt(idxAttr)\n            .mcfsk(idxAttr) = .intatt(idxAttr) + .staatt(idxAttr)\n            .cdfsk(idxAttr) = .stratt(idxAttr) + .aglatt(idxAttr)\n            .mdfsk(idxAttr) = .aglatt(idxAttr) + .intatt(idxAttr)\n            .ucfsk(idxAttrBon) = 0 \'Clear bonuses. \n            .acfsk(idxAttrBon) = 0 \n            .pcfsk(idxAttrBon) = 0\n            .mcfsk(idxAttrBon) = 0\n            .cdfsk(idxAttrBon) = 0\n            .mdfsk(idxAttrBon) = 0\n            .ucfsk(idxAttrCnt) = 0 \'Clear counts.\n            .acfsk(idxAttrCnt) = 0 \n            .pcfsk(idxAttrCnt) = 0\n            .mcfsk(idxAttrCnt) = 0\n            .cdfsk(idxAttrCnt) = 0\n            .mdfsk(idxAttrCnt) = 0\n...\n}}}\n\x3C/div\x3E\nHere we have a fragment of our /GenerateCharacter/ code. As you can see, this is much more clear as to what is going on. Instead of using 0, 1 and 2, we now have human-readable tags for the indexes which give us a lot more information. We can tell at a glance what is the base attribute or factor, what is the bonus amount and what is the duration. And if we need to change around the indexes for some reason, we don\'t have to change this code at all. The compiler will take of it for us.\n\nAs an example of how easy this makes updating the code, let\'s add in a /BestArmor/ function so that when the player creates a character, we give them the best armor available. If you remember, there is a strength restriction on armor, so we will need to look at the character\'s strength value in order to determine the armor.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n\'Returns the best armor based on strength.\nFunction character._GetBestArmor() As armorids\n   Dim As armorids ret = armArmorNone\n\n   If _cinfo.stratt(idxAttr) \x3E= strPlate Then\n      ret = armPlate\n   ElseIf _cinfo.stratt(idxAttr) \x3E= strScale Then\n      ret = armScale\n   ElseIf _cinfo.stratt(idxAttr) \x3E= strChain Then\n      ret = armChain   \n   ElseIf _cinfo.stratt(idxAttr) \x3E= strBrigantine Then\n      ret = armBrigantine   \n   ElseIf _cinfo.stratt(idxAttr) \x3E= strRing Then\n      ret = armRing\n   ElseIf _cinfo.stratt(idxAttr) \x3E= strCuirboli Then\n      ret = armCuirboli\n   ElseIf _cinfo.stratt(idxAttr) \x3E= strLeather Then\n      ret = armLeather   \n   ElseIf _cinfo.stratt(idxAttr) \x3E= strCloth Then\n      ret = armCloth\n   EndIf\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nThe underscore in the function name indicates that this is a private function and can only be called from within the object. We start out with the default, no armor. We then look at each armor strength value and work our way down from there. We are using an /If-ElseIf/ structure so that we exit the structure when we hit upon a match and not fall through to the last item Cloth armor.\n\nThe armor strength values are contained in inv.bi.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Armor strength ratings.\nConst strCloth = 10\nConst strLeather = 50\nConst strCuirboli = 100\nConst strRing = 150\nConst strBrigantine = 200\nConst strChain = 250\nConst strScale = 300\nConst strPlate = 350\n}}}\n\x3C/div\x3E\nWe are using /Const/ values here to indicate the required strength values for each type of armor. A #Define would have worked just as well too. I am using a Const in case at some point we convert this to its own Namespace or object. Const is a scoped function, where #Define is global, so by making it a Const value, we can advantage of the scope of a Namespace or object. We may never need it, but it is there just in case.\n\nOf course, we can use these values now when we create the armor.\n\n\x3Cdiv class="code"\x3E\n/inv.bi:GenerateArmor/\n{{{\n   \'Set the armor type and amount.\n   Select Case item\n      Case armCloth\n         inv.desc = "Cloth Armor" \'Cloth armor: 1% damage reduction.\n         inv.armor.noise = 1\n         inv.armor.dampct = .01\n         inv.armor.struse = strCloth\n      Case armLeather \'Leather armor: 5% damage reduction\n         inv.desc = "Leather Armor"\n         inv.armor.noise = 5\n         inv.armor.dampct = .05\n         inv.armor.struse = strLeather\n...\n}}}\n\x3C/div\x3E\nJust as we did with the attributes, we use the symbolic values here to make updating the values much easier. All we need to do is to change the Const values and we are good to go. We also use these values in the GetBestArmor routine as well.\n\nNow we need to update the character generation subroutine to give the character his armor.\n\n\x3Cdiv class="code"\x3E\n/character.bi:GenerateCharacter/\n{{{\n...\n   \'Add best armor.\n   arm = _GetBestArmor\n   GenerateArmor inv, 1, arm\n   SetInvEval inv, TRUE\n   AddInvItem wArmor, inv\n...\n}}}\n\x3C/div\x3E\nThe variable /arm/ is defined as an armor id and will contain the armor value from GetBestArmor. We then generate the armor by passing the armor id to /GenerateArmor/ and add it to the character\'s armor slot. The character now has the best possible armor he can have based on his strength.\n\nI hope you can see that using symbolic values rather than just plain numbers adds a tremendous amount of flexibility to the program. In a real sense, it allows the program to reprogram itself when changes need to be made, which makes our job much easier and results in a more robust program.\n',
'= 26: Potions\n\x3Cdiv align="center"\x3E\x3Cimg src=\'images/potion.png\' /\x3E\x3C/div\x3E\n\nPotions are a staple in RPGs and we would be remiss if we didn\'t include them in /Dungeon of Doom/. There are a number of ways you can implement potions, from making them another spell mechanism, to adding or subtracting from the character\'s attributes. Since potions are consumed, it makes sense to me to go with the school that has potions affect the character in some way. In /Dungeon of Doom/ potions modify attributes. \n\nThe way we have set up the character attributes, there are the base attributes, like Strength and Stamina, and the calculated combat factors which use the base attributes. Since we have two types of attributes, potions will affect each class of attribute differently. For the base attributes, the potions effect is permanent. If the character imbibes a Potion of Strength, the Strength will go up permanently. For the calculated combat factors, the effect lasts for only a certain number of turns. I think this makes sense, and works well with the way we have our attribute system set up.\n\nRemember, there can only be one effect active at any given time for the combat factors. If a potion gives +10 to the character\'s Armed Combat Factor and lasts 20 turns, and then the character drinks another potion of Armed Combat that gives +5 for 10 turns, the last potion consumed will overwrite the previous potion. This will prevent stacking of effects which can easily overbalance the game. \n\nWith the ground rules in place, let\'s look at the potion code.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'The effect of the potion.\nEnum poteffect\n   potEffectNone\n   potStrength\n   potStamina\n   potDexterity\n   potAgility\n   potIntelligence\n   potUCF\n   potACF\n   potPCF\n   potMCF\n   potCDF\n   potMDF\n   potHealing\n   potMana\nEnd Enum\n\n\'Potion ids.\nEnum potionids\n   potNone\n   potWhite \'str\n   potBlack \'sta\n   potBlue \'dex\n   potGreen \'agl\n   potCyan \'int\n   potRed \'ucf\n   potMagenta \'acf\n   potYellow \'pcf\n   potGray \'mcf\n   potSilver \'cdf\n   potGold \'mdf\n   potOrange \'healing\n   potPink \'mana\nEnd Enum\n\n\'Potion type.\nType pottype\n   id As potionids\n   potname As String * 30 \'Name of potion. Scrambled until evaluated.\n   evaldr As Integer    \'Evaluation difficulty rating. Used to evaluate magical effects: 0 = nonMagic.\n   eval As Integer      \'True if item has been evaluated.\n   noise As Integer     \'The amount of noise item generates, includes use and in character inventory.\n   use As itemuse       \'How the item is used.\n   amt As Integer       \'Effect amount.\n   cnt As Integer       \'Effect duration.\n   effect As poteffect  \'The effect type.\nEnd Type\n}}}\n\x3C/div\x3E\nHere we have two Enumerations, /poteffect/ which lists the attribute the potion affects, and /potionids/ which are the actual potions. Using colors for potions is a common device in roguelikes, so we will use it here. The /pottype/ Type Def is our potion data structure. As you can see it follows the same general format as our other items. The /effect/ field will contain one of our effect enumerations, and we place it in the Type Def just to make things easier when we apply the effect to the character.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Generate a potion.\nSub GeneratePotion(inv As invtype, currlevel As Integer, potid As potionids = potNone)\n   Dim item As potionids \n   Dim As Integer effmax = 100\n   item = potid\n   \'Generate item is not passed.\n   If item = potNone Then\n      item = RandomRange(potWhite, potPink)\n   EndIf\n   \'These are the common items.\n   inv.classid = clPotion\n   inv.potion.id = item\n   inv.potion.use = useEatDrink\n   inv.potion.eval = FALSE\n   inv.potion.evaldr = RandomRange(currlevel, currlevel * 2)\n   inv.icon = Chr(147)\n   inv.potion.noise = 10\n   inv.potion.amt = RandomRange(-1, 10)\n   If inv.potion.amt = 0 Then inv.potion.amt = 1 \n   inv.potion.cnt = 0\n   \'Set the weapon type and amount.\n   Select Case item\n      Case potWhite \'str\n         inv.iconclr = fbWhite\n         inv.potion.potname = "Potion of Strength"\n         inv.desc = "White Potion"\n         inv.potion.effect = potStrength\n      Case potBlack \'sta\n         inv.iconclr = fbBlack\n         inv.potion.potname = "Potion of Stamina"\n         inv.desc = "Black Potion"\n         inv.potion.effect = potStamina\n      Case potBlue \'dex\n         inv.iconclr = fbBlue\n         inv.potion.potname = "Potion of Dexterity"\n         inv.desc = "Blue Potion"\n         inv.potion.effect = potDexterity\n      Case potGreen \'agl\n         inv.iconclr = fbGreen\n         inv.potion.potname = "Potion of Agility"\n         inv.desc = "Green Potion"\n         inv.potion.effect = potAgility\n      Case potCyan \'int\n         inv.iconclr = fbCyan\n         inv.potion.potname = "Potion of Intelligence"\n         inv.desc = "Cyan Potion"\n         inv.potion.effect = potIntelligence\n      Case potRed \'ucf\n         inv.iconclr = fbRed\n         inv.potion.potname = "Potion of Unarmed Comabt"\n         inv.desc = "Red Potion"\n         inv.potion.cnt = RandomRange(1, effmax)\n         inv.potion.effect = potUCF\n      Case potMagenta \'acf\n         inv.iconclr = fbMagenta\n         inv.potion.potname = "Potion of Armed Combat"\n         inv.desc = "Magenta Potion"\n         inv.potion.cnt = RandomRange(1, effmax)\n         inv.potion.effect = potACF\n      Case potYellow \'pcf\n         inv.iconclr = fbYellow\n         inv.potion.potname = "Potion of Projectile Combat"\n         inv.desc = "Yellow Potion"\n         inv.potion.cnt = RandomRange(1, effmax)\n         inv.potion.effect = potPCF\n      Case potGray \'mcf\n         inv.iconclr = fbGray\n         inv.potion.potname = "Potion of Magic Comabt"\n         inv.desc = "Gray Potion"\n         inv.potion.cnt = RandomRange(1, effmax)\n         inv.potion.effect = potMCF\n      Case potSilver \'cdf\n         inv.iconclr = fbSilver\n         inv.potion.potname = "Potion of Combat Defense"\n         inv.desc = "Silver Potion"\n         inv.potion.cnt = RandomRange(1, effmax)\n         inv.potion.effect = potCDF\n      Case potGold \'mdf\n         inv.iconclr = fbGold\n         inv.potion.potname = "Potion of Magic Defense"\n         inv.desc = "Gold Potion"\n         inv.potion.cnt = RandomRange(1, effmax)\n         inv.potion.effect = potMDF\n      Case potOrange \'healing\n         inv.iconclr = fbOrange\n         inv.potion.potname = "Potion of Healing"\n         inv.desc = "Orange Potion"\n         inv.potion.effect = potHealing\n      Case potPink \'mana\n         inv.iconclr = fbPink\n         inv.potion.potname = "Potion of Mana"\n         inv.desc = "Pink Potion"\n         inv.potion.effect = potMana\n      Case Else\n         inv.iconclr = fbWhite\n         inv.icon = "?"\n         inv.potion.potname = "Unknown Potion"\n         inv.desc = inv.potion.potname\n         inv.potion.effect = potEffectNone \n   End Select   \nEnd Sub\n}}}\n\x3C/div\x3E\nThis should like quite familiar by now; we are following the same procedure as in other generation routines. One thing to note here is the /amt/ field. There is a small negative value of -1 (and 0) to each potion. A value of -1 doesn\'t seem like much, but can play a big difference in the game at a critical moment. Potion effects are applied regardless of whether a potion is evaluated, so having a small negative effect will cause the player to pause before deciding whether to drink a potion. It adds an element of chance to the game, along with some interest. We may want to come back and scale the amount of potions, but for now we will leave it as it is.\n\nThe other item of interest here is the /desc/ field. Potions are considered "magic" items, and so the actual potion name, such as /Potion of Mana/ is not revealed until the item is evaluated. While the item is unevaluated, we use the color as the name such as /Pink Potion/ and then when the item is successfully evaluated, we will change the name to the actual potion name.\n\nWe can see that in our updated /SetInvEval/ subroutine.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Sets eval state to passed type.\nSub SetInvEval(inv As invtype, state As Integer)\n   \n   \'If nothing then no eval.\n   If inv.classid \x3C\x3E clNone Then\n      \'Select the item.\n      Select Case inv.classid\n         Case clSupplies\n            inv.supply.eval = state \'Set the eval state.\n         Case clArmor\n            inv.armor.eval = state \n         Case clShield\n            inv.shield.eval = state \n         Case clWeapon\n            inv.weapon.eval = state \n         Case clAmmo\n            inv.ammo.eval = TRUE\n         Case clPotion\n            inv.potion.eval = state\n            \'Set unscrambled name.\n            If state = TRUE Then\n               inv.desc = inv.potion.potname\n            EndIf\n      End Select\n   EndIf\nEnd Sub\n}}}\n\x3C/div\x3E\nHere when we pass a TRUE state to the routine and the item is a potion, we set the inventory description to the secret name contained in the field /potname/.\n\nSince we are adding a new item to our game, we will need to go into each inventory item routine and update them to handle the potions. We have gone over those several times now so I won\'t go over each one here. The procedure is exactly the same as we have done with all the other new items.\n\nSince potions are consumables, we need to update the Eat/Drink routine in the main program, so let\'s look at those changes.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Processes the eat/drink command.\nFunction ProcessEatDrink() As Integer\n   Dim As String res, mask, desc\n   Dim As Integer i, iret, iitem, ret = FALSE\n   Dim As invtype inv\n   Dim As tWidgets.btnID btn\n   Dim As tWidgets.tInputbox ib\n      \n   \'Make sure there is something to evaluate in the inventory.\n   For i = pchar.LowInv To pchar.HighInv\n      iitem = pchar.HasInvItem(i)\n      If iitem = TRUE Then\n         \'Get the inv item.\n         pchar.GetInventoryItem i, inv\n         \'Is the item evaluated.\n         iret = MatchUse(inv, useEatDrink)\n         \'An item to evaluate.\n         If iret = TRUE Then\n            \'Build the mask.\n            mask &= Chr(i)\n         End If\n      EndIf\n   Next\n   If Len(mask) = 0 Then\n      ShowMsg "Eat/Drink", "Nothing to consume.", tWidgets.MsgBoxType.gmbOK\n   Else\n      \'Draws an input box on screen.\n      ib.Title = "Eat/Drink"\n      ib.Prompt = "Select item(s) to eat/drink (" & mask & ")"\n      ib.Row = 39\n      ib.EditMask = mask\n      ib.MaxLen = Len(mask)\n      ib.InputLen = Len(mask)\n      btn = ib.Inputbox(res)\n      \'Evaluate each item in the list.\n      If (btn \x3C\x3E tWidgets.btnID.gbnCancel) And (Len(res) \x3E 0) Then\n         ret = TRUE\n         \'Evaluate the list of items.\n         For i = 1 To Len(res)\n            iitem = Asc(res, i) \'Get index into character inventory.\n            \'Get the inv item.\n            pchar.GetInventoryItem iitem, inv\n            desc = pchar.ApplyInvItem(inv) \n            ShowMsg "Eat/Drink", desc, tWidgets.MsgBoxType.gmbOK    \n            \'Clear the item.\n            ClearInv inv\n            \'Put the item back into inventory.\n            pchar.AddInvItem iitem, inv\n         Next\n      EndIf\n   EndIf\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nIf you remember the previous version, we processed the supply effects here when the item is consumed. In this version they are missing. They have been moved to a new routine /ApplyInvItem/ in the character object.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n\'Applies the inv type to the character.\nFunction character.ApplyInvItem(inv As invtype) As String\n   Dim As Integer evalstate, evaldr, amt, amt2, amt3\n   Dim As String ret\n   \n   \'Check the eval state.\n   evalstate =  IsEval(inv)\n   \'Check for magic item.\n   evalDR = GetEvalDR(inv)\n   \n   If inv.classid = clSupplies Then\n      \'Apply item to character.\n      If inv.supply.id = supHealingHerb Then\n         \'Evaluated magical item.\n         If (evalstate = TRUE) And (evalDR \x3E 0) Then\n            CurrHP = MaxHP\n            ret = "The Healing Herb completely healed you!"\n         Else\n            \'Does 50% healing.\n            amt = MaxHP * .5 \'Calc the amount.\n            CurrHP = CurrHP + amt\n            If CurrHP \x3E MaxHP Then\n               CurrHP = MaxHP\n            EndIf\n            ret = "The Healing Herb added " & amt & " health!"\n         EndIf\n      ElseIf inv.supply.id = supHunkMeat Then\n        \'Does 25% healing.\n        amt = MaxHP * .25\n         CurrHP = CurrHP + amt \n         If CurrHP \x3E MaxHP Then\n            CurrHP = MaxHP\n         EndIf\n         ret = "The Hunk of Meat added " & amt & " health!"\n         \'Evaluated magical item.\n         If (evalstate = TRUE) And (evalDR \x3E 0) Then\n            amt2 = RandomRange(1, CurrStr)\n            BonStr = amt2\n            amt3 = RandomRange(1, 100)\n            BonStrCnt = amt3  \n            ret = "The Hunk of Meat added " & amt & " health and added " & amt2 & " strength for " & amt3 & " turns!" \n         EndIf\n      ElseIf inv.supply.id = supBread Then\n        \'Does 10% healing.\n        amt = MaxHP * .1\n         CurrHP = CurrHP + amt \n         If CurrHP \x3E MaxHP Then\n            CurrHP = MaxHP\n         EndIf\n         ret = "The Bread added " & amt & " health!"\n         \'Evaluated magical item.\n         If (evalstate = TRUE) And (evalDR \x3E 0) Then\n            \'Cure the poison if any.\n            If Poisoned = TRUE Then\n               Poisoned = FALSE\n               PoisonStr = 0\n               ret = "The Bread added " & amt & " health and cured your poison!"\n            End If\n         EndIf\n      ElseIf inv.supply.id = supManaOrb Then\n         \'Adds 25% mana.\n         amt = MaxMana * .25\n         CurrMana = CurrMana + amt \n         If CurrMana \x3E MaxMana Then\n            CurrMana = MaxMana\n         EndIf\n         ret = "The Mana Orb added " & amt & " mana!"\n         \'Evaluated magical item.\n         If (evalstate = TRUE) And (evalDR \x3E 0) Then\n            \'Cure the poison if any.\n            If CurrMana = MaxMana Then\n               ret = "The Mana Orb restored all your mana!"\n            End If\n         EndIf\n      EndIf\n   ElseIf inv.classid = clPotion Then\n      Select Case inv.potion.effect\n         Case potStrength\n            ChangeStrength inv.potion.amt\n            If inv.potion.amt \x3E 0 Then\n               ret = "You gained " & inv.potion.amt & " strength!" \n            Else\n               ret = "You lost " & inv.potion.amt & " strength!"\n            EndIf\n         Case potStamina\n            ChangeStamina inv.potion.amt\n            If inv.potion.amt \x3E 0 Then\n               ret = "You gained " & inv.potion.amt & " stamina!" \n            Else\n               ret = "You lost " & inv.potion.amt & " stamina!"\n            EndIf\n         Case potDexterity\n            ChangeDexterity inv.potion.amt\n            If inv.potion.amt \x3E 0 Then\n               ret = "You gained " & inv.potion.amt & " dexterity!" \n            Else\n               ret = "You lost " & inv.potion.amt & " dexterity!"\n            EndIf\n         Case potAgility\n            ChangeAgility inv.potion.amt\n            If inv.potion.amt \x3E 0 Then\n               ret = "You gained " & inv.potion.amt & " agility!" \n            Else\n               ret = "You lost " & inv.potion.amt & " agility!"\n            EndIf\n         Case potIntelligence\n            ChangeIntelligence inv.potion.amt\n            If inv.potion.amt \x3E 0 Then\n               ret = "You gained " & inv.potion.amt & " intelligence!" \n            Else\n               ret = "You lost " & inv.potion.amt & " intelligence!"\n            EndIf\n         Case potUCF\n            BonUcf = inv.potion.amt\n            BonUcfCnt = inv.potion.cnt\n            If inv.potion.amt \x3E 0 Then\n               ret = "You gained " & inv.potion.amt & " Unarmed Combat for " & inv.potion.cnt & " turns!"\n            Else\n               ret = "You lost " & inv.potion.amt & " Unarmed Combat for " & inv.potion.cnt & " turns!"\n            EndIf\n         Case potACF\n            BonAcf = inv.potion.amt\n            BonAcfCnt = inv.potion.cnt\n            If inv.potion.amt \x3E 0 Then\n               ret = "You gained " & inv.potion.amt & " Armed Combat for " & inv.potion.cnt & " turns!"\n            Else\n               ret = "You lost " & inv.potion.amt & " Armed Combat for " & inv.potion.cnt & " turns!"\n            EndIf\n         Case potPCF\n            BonPcf = inv.potion.amt\n            BonPcfCnt = inv.potion.cnt\n            If inv.potion.amt \x3E 0 Then\n               ret = "You gained " & inv.potion.amt & " Projectile Combat for " & inv.potion.cnt & " turns!"\n            Else\n               ret = "You lost " & inv.potion.amt & " Projectile Combat for " & inv.potion.cnt & " turns!"\n            EndIf\n         Case potMCF\n            BonMcf = inv.potion.amt\n            BonMcfCnt = inv.potion.cnt\n            If inv.potion.amt \x3E 0 Then\n               ret = "You gained " & inv.potion.amt & " Magic Combat for " & inv.potion.cnt & " turns!"\n            Else\n               ret = "You lost " & inv.potion.amt & " Magic Combat for " & inv.potion.cnt & " turns!"\n            EndIf\n         Case potCDF\n            BonCdf = inv.potion.amt\n            BonCdfCnt = inv.potion.cnt\n            If inv.potion.amt \x3E 0 Then\n               ret = "You gained " & inv.potion.amt & " Combat Defense for " & inv.potion.cnt & " turns!"\n            Else\n               ret = "You lost " & inv.potion.amt & " Combat Defense for " & inv.potion.cnt & " turns!"\n            EndIf\n         Case potMDF\n            BonMdf = inv.potion.amt\n            BonMdfCnt = inv.potion.cnt\n            If inv.potion.amt \x3E 0 Then\n               ret = "You gained " & inv.potion.amt & " Magic Defense for " & inv.potion.cnt & " turns!"\n            Else\n               ret = "You lost " & inv.potion.amt & " Magic Defense for " & inv.potion.cnt & " turns!"\n            EndIf\n         Case potHealing\n            CurrHP = CurrHP + inv.potion.amt\n            If CurrHP \x3E MaxHP Then CurrHP = MaxHP \n            If inv.potion.amt \x3E 0 Then\n               ret = "You gained " & inv.potion.amt & " health!"\n            Else\n               ret = "You lost " & inv.potion.amt & " health!"\n            EndIf\n         Case potMana\n            CurrMana = CurrMana + inv.potion.amt\n            If CurrMana \x3E MaxMana Then CurrMana = MaxMana\n            If inv.potion.amt \x3E 0 Then\n               ret = "You gained " & inv.potion.amt & " mana!"\n            Else\n               ret = "You lost " & inv.potion.amt & " mana!"\n            EndIf\n      End Select\n   EndIf\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nWe are now at the point where we are adding a number of effects to the game, so putting them into a separate subroutine will make it easier to add new effects as we need. Since we have placed them in our character object, we also have access to the character data structures which will make applying the effects much easier.\n\nNotice that we have coded this according to our ground rules for potions. The base attributes effects are permanent, while the factor effects are of limited duration. You can also see that by updating the attribute array indexes, we have greatly simplified the code here and made it much more readable. \n\nThat is all we need to do to add potions to the game. Potions add yet another layer of strategy to the game, and although they are for the most part positive additions, the small chance of a negative effect will help keep the player from just drinking every potion he comes across without pausing to see if he can afford the resultant effect.',
'= 27: Scaling Difficulty Levels\nSince we are close to having a complete game now, let\'s take a moment and see if we can make the game a bit more playable. Right now, the difficulty of the game is quite high, so we need to scale that so that the game is easier in the early levels and progressively gets harder as the character descends the dungeon. We can do that by adjusting the difficulty levels of the game. The different difficulty levels of the game include the evaluation of items, the monster stats, the monster attacks and the monster armor values. We want these manageable early in the game, which means scaling these values based on the current level.\n\nTo help us do that, we have added a new routine in our /utils.bi/ file.\n\n\x3Cdiv class="code"\x3E\n/utils.bi/\n{{{\n\'Returns a scaled factor based on passed base value.\nFunction GetScaledFactor(basevalue As Integer, currlevel As Integer) As Integer\n   Dim As Double pct\n   Dim As Integer ret, range, fac, fac2 \n   \n   \'Get the percentage factor for the current level.\n   pct = currlevel / maxlevel\n   \'Get the range for the base value.\n   range = basevalue * .25\n   \'Generate the factor based on the range.\n   fac = RandomRange(-range, range)\n   \'Get the value based on the range + basevalue\n   ret = basevalue + fac\n   \'Get the level percentage amount.\n   fac2 = (ret * pct) + 1 \n   \'Add in the percentage of the level.\n   ret = ret + fac2\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nThe GetScaledFacor uses a base value and the current level to generate a scaled number. The number returned will be 25% of the base value, plus or minus, with the current level amount added. The level amount is a percentage value that will approach 100% as the character descends the level. This percentage is applied to the calculated scaled number plus 1, so that we have a minimum of 1 for an adjustment, which is then added to the calculated scaled number.\n\nWe calculate the current level percentage in the /pct/ variable. The /range/ variable is the /basevalue/ * .25 or 25% percent of the value. We then generate a number from negative range to positive range in /fac/ and add that to the base value. The level amount is calculated and placed in /fac2/ which will be a minimum value of 1, but will approach 100% of the scaled value as the character descends the dungeon. /fac2/ is then added to the calculated scaled value in /ret/ and returned to the caller.\n\nThe /basevalue/ passed to the function is whatever the difficulty rating is based upon. For evaluations, the base value will the be character\'s intelligence value. For monster attributes, it will be the character attributes. Let\'s look at the monster generation routine to see how we are going to use this function.\n\n\x3Cdiv class="code"\x3E\n/monster.bi/\n{{{\n...\n   \'Set the monster attributes.\n   mon.id = RandomRange(monDarkangel, monGriffon)\n   stratt = GetScaledFactor(pchar.CurrStr, currlevel)\n   staatt = GetScaledFactor(pchar.CurrSta, currlevel)\n   dexatt = GetScaledFactor(pchar.CurrDex, currlevel)\n   aglatt = GetScaledFactor(pchar.CurrAgl, currlevel)\n   intatt = GetScaledFactor(pchar.CurrInt, currlevel)\n...\n}}}\n\x3C/div\x3E\nHere we have the monster attribute generation and for each attribute we pass the character\'s attribute value. This will scale the monster to the character, plus or minus 25% with the current level adding to the total. This keeps the monster difficulty within range of the character but slowly increases the monster\'s abilities as the character descends the dungeon. If the player decides to dive the levels, he will soon find out that the monsters will quickly become too powerful, as the level adjustment will outpace the scaling of the monster attributes. On the other hand the player may want to skim levels, but he will find the monster will keep pace with him since they are scaled to the character attributes. This should keep the game challenging as the character grows in strength and as he descends deeper into the dungeon.\n\nThe other difficulty levels we have are the evaluation routines.\n\n\x3Cdiv class="code"\x3E\n/inv.bi:GenerateWeapon/\n{{{\n...\n   \'Magic item.\n   If IsMagic = TRUE Then\n      inv.weapon.evaldr = GetScaledFactor(charint, currlevel)\n      inv.weapon.spell = 0\n   EndIf\n...\n}}}\n\x3C/div\x3E\nHere we have a portion of the weapon generation routine. We are using the character\'s intelligence value, contained in the global variable /charint/ to generate the difficulty level for the weapon. We have to use a global variable here since the inventory routines are included before the character routines so the intelligence value doesn\'t exist yet to the compiler. It is a bit of a hack, but works. All the other /evaldr/ variables are set in the same manner.\n\nIn a previous chapter we scaled the monster attacks based on the level. We neglected the monster armor values though, so we will correct that in this chapter.\n\n\x3Cdiv class="code"\x3E\n/monster.bi:GenerateMonster/\n{{{\n...\n   \'Adjust the attack value by level percent.\n   mon.atkdam = mon.atkdam * pct\n   If mon.atkdam \x3C 1 Then mon.atkdam = 1\n   \'Adjust the armor value by level percent.\n   mon.armval = mon.armval * pct\n...\n}}}\n\x3C/div\x3E\nRemember that armor values are already percentage values, such as .8, so we simply multiply the armor percentage by the level percentage value to get an adjusted value. As the character descends the dungeon, the level percentage will approach 1, and 1 multiplied by any value will be the value, so that the armor values will approach their full value the closer the character gets to the last level.\n\nThe net result of all of these adjustments will mean that on the last level of the dungeon, all the difficulty values will be at their maximum values, just as you would expect them to be. The last level of the game should be the most challenging, and our scaling will ensure that this is exactly what the player will get when he reaches the final level.\n',
'= 28: Jewelry\nIn /Dungeon of Doom/ we have two types of jewelry, rings and necklaces. These items have value and can be magical, conferring a bonus on the character when they wear them. Both items work in the same way, so rather than having data structures for each item, we will create a single data structure and qualify the item type, i.e., ring or necklace, in the data structure.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Jewelry ids\nEnum jewleryids\n   jewNone\n   jewSteel\n   jewBronze\n   jewCopper\n   jewBrass\n   jewSilver\n   jewGold\n   jewAgate\n   jewOpal\n   jewAmethyst\n   jewRuby\n   jewEmerald\n   jewJade\n   jewPearl\n   jewQuartz\n   jewSapphire\n   jewDiamond\nEnd Enum\n\n\'Type of jewlery\nEnum jewtype\n   jNone\n   jRing\n   jNecklace\nEnd Enum\n\n\'Armor type\nType jewelrytype\n   id As jewleryids          \'Jewlery ids\n   jtype As jewtype          \'Ring or necklace\n   evaldr As Integer         \'Evaluation difficutly. Evaldr \x3E 0 then magical item.\n   eval As Integer           \'Is item evaluated.\n   spell As spellid          \'Magical spell.\n   noise As Integer          \'The amount of noise item generates, includes use and in character inventory.\n   use As itemuse            \'How the item is used.\n   jslot(1 To 2) As wieldpos \'Slot item is held in. Could be in up to 2 slots.\nEnd Type\n}}}\n\x3C/div\x3E\nNotice that we have two Enumerations for our jewelery. The manufactured type in /jewleryids/, which will determine the value of the item, and the type of jewelry in /jewtype/ which identifies the item as a ring or necklace. The Type Def /jewelrytype/ is our standard item type that you have seen before. The /id/ indicates the manufactured type while the /jtype/ lets us know what it is, i.e., ring or necklace. By combining both types of jewelry into a single Type, we simplify the management of the item. We can do this because we really have a single item, jewelry, which contains two sub-types, rings and necklaces.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Generate new jewlery item.\nSub GenerateJewlery(inv As invtype, currlevel As Integer)\n   Dim item As jewleryids\n   Dim jtype As jewtype \n   Dim As Integer isMagic \n   \n   item = RandomRange(jewSteel, jewDiamond)\n   jtype = RandomRange(jRing, jNecklace)\n   isMagic = ItemIsMagic(currlevel)\n   \'Magic Item.\n   If IsMagic = TRUE Then\n      inv.jewelry.evaldr = GetScaledFactor(charint, currlevel)\n      inv.jewelry.spell = splNone\n   EndIf\n   inv.jewelry.eval = FALSE\n   \'These are the common items.\n   inv.jewelry.id = item\n   inv.jewelry.jtype = jtype\n   inv.iconclr = fbTurquoise\n   inv.jewelry.use = useWieldWear\n   inv.jewelry.noise = 1\n   If jtype = jRing Then\n      inv.icon = "o"\n      inv.jewelry.jslot(1) = wRingRt\n      inv.jewelry.jslot(2) = wRingLt\n      inv.desc = " Ring"\n   Else\n      inv.icon = "0"\n      inv.jewelry.jslot(1) = wNeck\n      inv.jewelry.jslot(2) = wNone\n      inv.desc = " Necklace"\n   EndIf\n   Select Case item\n      Case jewSteel\n         inv.desc = "Steel" & inv.desc\n      Case jewBronze\n         inv.desc = "Bronze" & inv.desc\n      Case jewCopper\n         inv.desc = "Copper" & inv.desc\n      Case jewBrass\n         inv.desc = "Brass" & inv.desc\n      Case jewSilver\n         inv.desc = "Silver" & inv.desc\n      Case jewGold\n         inv.desc = "Gold" & inv.desc\n      Case jewAgate\n         inv.desc = "Agate" & inv.desc\n      Case jewOpal\n         inv.desc = "Opal" & inv.desc\n      Case jewAmethyst\n         inv.desc = "Amethyst" & inv.desc\n      Case jewRuby\n         inv.desc = "Ruby" & inv.desc\n      Case jewEmerald\n         inv.desc = "Emerald" & inv.desc\n      Case jewJade\n         inv.desc = "Jade" & inv.desc\n      Case jewPearl\n         inv.desc = "Pearl" & inv.desc\n      Case jewQuartz\n         inv.desc = "Quartz" & inv.desc\n      Case jewSapphire\n         inv.desc = "Sapphire" & inv.desc\n      Case jewDiamond\n         inv.desc = "Diamond" & inv.desc\n   End Select\nEnd Sub\n}}}\n\x3C/div\x3E\nCreating the jewelry item is quite easy. We need to generate both the manufactured type in /item/ and the item type in /jtype/. Depending on the item type, we set the wield position. For rings, the item can be either in the /wRingRt/ or /wRingLt/ slot, which means the character can wear two rings, and for necklaces, the slot is /wNeck/, which means the character can wear one necklace. The Select Case just generates the name of the item based on its manufactured type. Of course as we have done with all the other items, each inventory subroutine and function needs to be updated to handle jewelry. I\'ll let you glance over the code to see the implementation, but the process is the same as for all other items.\n\nThat is all that is required to implement jewelry within the game. Since our inventory screen commands have been generalized to handle any item type we need to add, we do not need to do anything with that code to support the new item type. This is why we have spent so much time generalizing our code. We can essentially just drop in new items, update the inventory methods and we have a new item type in the game. This makes extending the game very easy.\n',
'= 29: Weapon Magic\n\x3Cdiv align="center"\x3E\x3Cimg src=\'images/weaponspell.png\' /\x3E\x3C/div\x3E\n\nWe are now ready to start implementing the magic system and we will start with the weapon magic. Weapon magic is a spell associated with a weapon that is "cast" when the character makes a successful hit on the target. In order for the spell to be active, the character must have already successfully evaluated the weapon. It sounds simple enough, but there is actually quite a bit code necessary in order for this work correctly as we will see. Let\'s start with spell data type.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Effects ids.\nEnum spellid\n   splNone        \'No effect.\n   splMaxHealing  \'Heal to max HP.\n   splStrongMeat  \'Adds bonus to Str for count time.\n   splBreadLife   \'Cures any poison.\n   splMaxMana     \'Restore full mana.\n   splSerpentBite \'Weapon: Inflict poison damage.\n   splRend        \'Weapon: Decrease armor of target.\n   splSunder      \'Weapon: Decrease target weapon damage.\n   splReaper      \'Weapon: Causes monster to flee in fright.\n   splFire        \'Weapon: Does fire damage to target for lvl turns.\n   splGoliath     \'Weapon: Adds str to attack.\n   splStun        \'Weapon: Stuns target for lvl turns.\n   splChaos       \'Weapon: Random amount of additonal damage.\n   splWraith      \'Weapon: Decreases random stat of target.\n   splThief       \'Weapon: Steals random stat amount from target and adds to character.\nEnd Enum\n}}}\n\x3C/div\x3E\nWe have added some new spell ids to our /spellid/ enumeration. The first five you have already seen as these are the spells associated with the supply items, the other though are the spells available for weapons. By putting these ids together we can select one at random by just using the first and last id when we make our random selection. This way we can use the same enumeration for all the spells we will have in the game and we don\'t need to keep track of several different enumerations.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Spell info type.\nType spelltype\n   id As spellid          \'The spell id.\n   lvl As Integer         \'Spell level.\n   splname As String * 30 \'Spell name.\n   spldesc As String * 60 \'Spell desc.\n   manacost As Integer    \'Cost in mana.\n   dam As Integer         \'The amount of damage the spell does.\nEnd Type\n}}}\n\x3C/div\x3E\nIf you remember we just used an /Integer/ placeholder to specify the spell of an item. However, what we want is a common data structure for all our spells, not just the one associated with items. Again, we don\'t want to have a bunch of different data structures in use for the different types of spells, we want a common representation in order to make our job easier. This Type Def contains all the fields necessary for item spells and spells that are cast, and by using this common data structure, we can build a common set of routines that will manage the spells, regardless of the type of spell. \n\nThe /id/ field is the id from our spellid enumeration. The /lvl/ field is the current level of the spell. The higher the level, the more powerful the spell becomes. The /splname/ and /spldesc/ fields are used in the Inspect routine in the inventory management screen to display to the user the spell name and spell description. The manacost is how much mana the spell requires to cast. We won\'t be using this in our weapon spells since they are part of the weapon and don\'t require mana to cast, but it will play a role in spell casting. The /dam/ field is the amount of damage the spell inflicts to the target. Not all spells are damage spells though, so this field could be used for other purposes as well.\n\nSince we are now using a Type Def instead of an integer, we need to update the weapon Type Def.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Weapon type def.\nType weapontype\n   id As weaponids           \'Weapon type\n   evaldr As Integer         \'Evaluation difficutly. Evaldr \x3E 0 then magical item.\n   eval As Integer           \'Is item evaluated.\n   spell As spelltype        \'Magical spell.\n   noise As Integer          \'The amount of noise item generates, includes use and in character inventory.\n   use As itemuse            \'How the item is used.\n   dam As integer            \'Damage weapons does.\n   hands As Integer          \'Number of hands weapon requires.\n   wslot(1 To 2) As wieldpos \'Slot item is held in. Could be in up to 2 slots.\n   weapontype As weaptype    \'Type of weapon: melee, projectile.\n   capacity As Integer       \'The number of rounds the weapon uses.\n   ammotype As ammoids       \'Type of ammo used.\n   ammocnt As Integer        \'Weapon ammo. Can be loaded to capacity.\n   iswand As Integer         \'Marks item as a wand.\nEnd Type\n}}}\n\x3C/div\x3E \nThe only change here is the {{{spell as spelltype}}}. When we create a new weapon, the spell Type Def will filled with the appropriate data that describes the spell attached to a weapon. We now have to integrate this into our weapon routines, so let\'s start by looking at the /GenerateWeapon/ subroutine.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Generate a weapon.\nSub GenerateWeapon(inv As invtype, currlevel As Integer, wpid As weaponids = wpNone)\n   Dim item As weaponids \n   Dim As Integer isMagic \n\n   isMagic = ItemIsMagic(currlevel)\n   item = wpid\n   \'Generate item is not passed.\n   If item = wpNone Then\n      item = RandomRange(wpClub, wpGoldWand)\n   EndIf\n   \'These are the common items.\n   inv.classid = clWeapon\n   inv.weapon.id = item\n   inv.iconclr = fbCadmiumYellow\n   inv.weapon.use = useWieldWear\n   inv.weapon.eval = FALSE\n   inv.weapon.wslot(1) = wPrimary\n   inv.weapon.wslot(2) = wSecondary\n   inv.weapon.iswand = FALSE\n   ClearSpell inv.weapon.spell\n   \'Magic item.\n   If IsMagic = TRUE Then\n      inv.weapon.evaldr = GetScaledFactor(charint, currlevel) \'The eval dr.\n      inv.weapon.spell.id = RandomRange(splSerpentBite, splThief) \'Spell id.\n      GenerateSpell inv.weapon.spell, currlevel\n   EndIf\n   \'Set default weapon/ammo types.\n   inv.weapon.weapontype = wtMelee\n   inv.weapon.ammotype =  amNone\n   inv.weapon.ammocnt = 0\n   \'Set the weapon type and amount.\n   Select Case item\n...\n}}}\n\x3C/div\x3E\nThere isn\'t much we need to do in order to support the new spell Type Def. We have added some new routines though. /ClearSpell/ just clears the spell data in the Type Def. /GenerateSpell/ loads the spell Type Def with the appropriate data based on the spell selection in the /RandomRange/ function. Here you can see why we want to place all the spell ids together. When we want to add new spells to the weapon spell list, we can simply insert them in between the current end marker ids and update the /GenerateSpell/ routine. That is all we needed to do here. Let\'s look at the two new spell routines.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Clears the spell type.\nSub ClearSpell (spl As spelltype)\n   \'Clear the spell type.\n   spl.id = splNone\n   spl.lvl = 0\n   spl.splname = ""\n   spl.spldesc = ""\n   spl.manacost = 0\n   spl.dam = 0\nEnd Sub\n}}}\n\x3C/div\x3E\nAs you can see, the /ClearSpell/ subroutine just clears the Type Def data. Notice that we are passing just the spell Type Def here. Remember that this Type Def is part of the weapon type def, but rather than passing the whole weapon Type Def we just pass the spell type within the weapon Type by explicitly referencing the weapon spell {{{ClearSpell inv.weapon.spell}}}. This is a nice trick that you can use for passing partial information around in your program, and makes adding helper subroutines much easier. It also avoids side effects that may creep into the program when working with code that shouldn\'t affect the whole data structure.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Generates spell data.\nSub GenerateSpell(spl As spelltype, currlevel As Integer)\n   \n   \'Generate spell based on spell id.\n   Select Case spl.id\n      Case splSerpentBite \'Weapon: Inflict poison damage.\n         spl.lvl = currlevel \'Current level is used for spell level.\n         spl.splname = GetSpellName(spl.id) \'Sets the spell name.\n         spl.spldesc = GetSpellEffect(spl.id) \'Sets the spell description.\n         spl.manacost = 0 \'Cost in mana.\n         spl.dam = currlevel \'How much damage the spell does.\n      Case splRend        \'Weapon: Decrease armor of target.\n         spl.lvl = currlevel \n         spl.splname = GetSpellName(spl.id) \n         spl.spldesc = GetSpellEffect(spl.id)\n         spl.manacost = 0 \n         spl.dam = currlevel \n      Case splSunder      \'Weapon: Decrease target weapon damage.\n         spl.lvl = currlevel \n         spl.splname = GetSpellName(spl.id) \n         spl.spldesc = GetSpellEffect(spl.id)\n         spl.manacost = 0 \n         spl.dam = currlevel \n      Case splReaper      \'Weapon: Causes monster to flee.\n         spl.lvl = currlevel \n         spl.splname = GetSpellName(spl.id) \n         spl.spldesc = GetSpellEffect(spl.id)\n         spl.manacost = 0 \n         spl.dam = currlevel\n      Case splFire        \'Weapon: Does fire damage to target for lvl turns.\n         spl.lvl = currlevel \n         spl.splname = GetSpellName(spl.id) \n         spl.spldesc = GetSpellEffect(spl.id)\n         spl.manacost = 0 \n         spl.dam = currlevel \n      Case splGoliath     \'Weapon: Adds str to attack.\n         spl.lvl = currlevel \n         spl.splname = GetSpellName(spl.id) \n         spl.spldesc = GetSpellEffect(spl.id)\n         spl.manacost = 0 \n         spl.dam = currlevel \n      Case splStun        \'Weapon: Stuns target for lvl turns.\n         spl.lvl = currlevel \n         spl.splname = GetSpellName(spl.id) \n         spl.spldesc = GetSpellEffect(spl.id)\n         spl.manacost = 0 \n         spl.dam = currlevel \n      Case splChaos       \'Weapon: Random amount of additonal damage.\n         spl.lvl = currlevel \n         spl.splname = GetSpellName(spl.id) \n         spl.spldesc = GetSpellEffect(spl.id)\n         spl.manacost = 0 \n         spl.dam = RandomRange(1, currlevel) \n      Case splWraith      \'Weapon: Decreases random stat of target.\n         spl.lvl = currlevel \n         spl.splname = GetSpellName(spl.id) \n         spl.spldesc = GetSpellEffect(spl.id)\n         spl.manacost = 0 \n         spl.dam = currlevel \n      Case splThief       \'Weapon: Steals random stat amount from target and adds to character.\n         spl.lvl = currlevel \n         spl.splname = GetSpellName(spl.id) \n         spl.spldesc = GetSpellEffect(spl.id)\n         spl.manacost = 0 \n         spl.dam = currlevel \n      Case Else\n         spl.lvl = 0 \n         spl.splname = "Unknown spell"\n         spl.spldesc = "Unknown spell effect."\n         spl.manacost = 0 \'Cost in mana.\n         spl.dam = 0 \n   End Select\nEnd Sub\n}}}\n\x3C/div\x3E\nThe /GenerateSpell/ subroutine fills the spell Type Def with data based on the spell id and current dungeon level. The level is used to set the level, or power of the spell, as well as the damage the spell does. As the character descends deeper into the dungeon, magically items will become more powerful, offering the player more tactical decisions in the game. They may have a powerful weapon, but weak spell. Should they use a weaker weapon that has a more powerful spell or keep their current weapon? We want to always present the player with a choice; this makes the game more interesting and allows the player to try different things the more they play the game.\n\nAs you see, the format of this subroutine follows the same pattern as our other generation routines. I have mentioned this before, but it bears repeating: creating program patterns like this helps simplify the code, makes it easier to maintain, and is self-documenting. When you come back to this weeks or months later, you can easily see what you were doing, and since the program pattern is the same for all like subroutines, you can easily add in new elements by just extending the pattern. This makes the program much more robust and introduces fewer errors when you make updates.\n\nThe GenerateSpell is fairly simple, it just sets the basic information for the spell. We have two new functions here for the spell name and spell description that we will look at next.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Returns a description for a spell.\nFunction GetSpellName(spl As spellid) As String\n   Dim As String ret = ""\n   \n   Select Case spl\n      Case splMaxHealing  \'Heal to max HP.\n         ret = "Spell of Maximum healing."\n      Case splStrongMeat \'Adds str bonus for time. \n         ret = "Spell of Enhance Strength."\n      Case splBreadLife   \'Cures any poison.\n         ret = "Spell of Cure Poison."\n      Case splMaxMana     \'Restore full mana.\n         ret = "Spell of Restore Mana."\n      Case splSerpentBite \'Weapon: Inflict poison damage.\n         ret = "Serpent\'s Bite"\n      Case splRend        \'Weapon: Decrease armor of target.\n         ret = "Spell of Rend Armor"\n      Case splSunder      \'Weapon: Decrease target weapon damage.\n         ret = "Spell of Sunder"\n      Case splReaper      \'Weapon: Causes monster to flee.\n         ret = "Spell of The Reaper"\n      Case splFire        \'Weapon: Does fire damage to target for lvl turns.\n         ret = "Spell of Fire"\n      Case splGoliath     \'Weapon: Adds str to attack.\n         ret = "The Strength of Goliath"\n      Case splStun        \'Weapon: Stuns target for lvl turns.\n         ret = "Spell of Stun"\n      Case splChaos       \'Weapon: Random amount of additonal damage.\n         ret = "Chaos Attack"\n      Case splWraith      \'Weapon: Decreases random stat of target.\n         ret = "Hand of the Wraith"\n      Case splThief       \'Weapon: Steals random stat from target and adds to character.\n         ret = "Spell of The Phantom"              \n      Case Else\n         ret = "No spell."\n   End Select\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nThis function just looks at the id and returns the name associated with that id. Nothing too complicated here.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Returns spell effect. Used in spell descriptions.\nFunction GetSpellEffect(spl As spellid) As String\n   Dim As String ret = ""\n   \n   Select Case spl\n      Case splMaxHealing  \'Heal to max HP.\n         ret = "Restore full health."\n      Case splStrongMeat \'Adds str bonus for time. \n         ret = "Adds bonus to strength."\n      Case splBreadLife   \'Cures any poison.\n         ret = "Cures poison."\n      Case splMaxMana     \'Restore full mana.\n         ret = "Restore full mana."\n      Case splSerpentBite \'Weapon: Inflict poison damage.\n         ret = "Inflicts poison damage over time."\n      Case splRend        \'Weapon: Decrease armor of target.\n         ret = "Decreases armor of target."\n      Case splSunder      \'Weapon: Decrease target weapon damage.\n         ret = "Decreases target weapon damage."\n      Case splReaper      \'Weapon: Causes monster to flee.\n         ret = "Causes monster to flee."\n      Case splFire        \'Weapon: Does fire damage to target for lvl turns.\n         ret = "Inflicts fire damage over time."\n      Case splGoliath     \'Weapon: Adds str to attack.\n         ret = "Adds additional strength to damage."\n      Case splStun        \'Weapon: Stuns target for lvl turns.\n         ret = "Stuns target doing damage for a time."\n      Case splChaos       \'Weapon: Random amount of additonal damage.\n         ret = "Adds random damage to attack."\n      Case splWraith      \'Weapon: Decreases random stat of target.\n         ret = "Decreases random attribute of target."\n      Case splThief       \'Weapon: Steals random stat from target and adds to character.\n         ret = "Steals random attribute amount and adds to character."              \n      Case Else\n         ret = "No effect."\n   End Select\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\n/GetSpellEffect/ returns the spell description. It just describes to the player what the spell does so they know the effect of the spell. Both the spell name and description are displayed when the player Inspects an item. By providing the description, the player doesn\'t need to memorize the spells, they can just look at the description. This is one of those convenience factors, like the widget library, that enhances the game and makes it more professional and pleasing to play.\n\nLet\'s take a look at how we are displaying this information to the player.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Returns an extended description of an item.\nSub GetFullDesc(lines() As String, inv As invtype)\n   Dim As Integer idx = 0\n   \n   \'Reset the array.\n   ReDim lines(0 To idx) As String\n   \'Make sure we have something to inspect.\n   If inv.classid \x3C\x3E clNone Then\n      \'Select the item.\n      Select Case inv.classid\n         ...\n\n         Case clWeapon\n            idx += 1\n            ReDim Preserve lines(0 to idx) As String\n            lines(idx) = GetInvItemDesc(inv)\n            idx += 1\n            ReDim Preserve lines(0 to idx) As String\n            lines(idx) = "* " & inv.weapon.dam & " weapon damage"\n            idx += 1\n            ReDim Preserve lines(0 to idx) As String\n            lines(idx) = "* Hands Required: " & inv.weapon.hands\n            If inv.weapon.weapontype = wtProjectile Then\n               idx += 1\n               ReDim Preserve lines(0 to idx) As String\n               lines(idx) = "* Capacity: " & inv.weapon.capacity\n               idx += 1\n               ReDim Preserve lines(0 to idx) As String\n               lines(idx) = "* Ammo Cnt: " & inv.weapon.ammocnt\n            EndIf\n            If IsEval(inv) = TRUE Then\n               If inv.weapon.spell.id \x3C\x3E splNone Then\n                  idx += 1\n                  ReDim Preserve lines(0 to idx) As String\n                  lines(idx) = "* Spell: " & Trim(inv.weapon.spell.splname)\n                  idx += 1\n                  ReDim Preserve lines(0 to idx) As String\n                  lines(idx) = "* Effect: " & Trim(inv.weapon.spell.spldesc)\n               End If\n               idx += 1\n               ReDim Preserve lines(0 to idx) As String\n               lines(idx) = "* Item is evaluated"\n            Else\n               idx += 1\n               ReDim Preserve lines(0 to idx) As String\n               lines(idx) = "* Item is not evaluated"\n            End If\n\n            ...\n\n     End Select\n   EndIf\nEnd Sub\n}}}\n\x3C/div\x3E\nI am only showing the weapon section here for clarity. We get the item description and weapon damage as we did before. We then check to see if the item is evaluated. If it is, we grab the spell name and spell description and display them to the user. If the weapon is not evaluated, then we don\'t show the spell information, we just indicate to the player that the item hasn\'t been evaluated yet. The player will not know if a weapon is magic until it has been evaluated, which adds additional interest to the game.\n\nNow that we have the spells in place, we need to add them to the combat routines.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Perform melee combat.\nSub DoMeleeCombat(mx As Integer, my As Integer)\n   Dim As Integer cf, df, croll, mroll, dam, isdead, midx, mxp, xp \n   Dim As String txt, mname\n   Dim As Single marm\n   Dim As spelltype spl1, spl2\n   \n   \'Get the monster defense factor.\n   df = level.GetMonsterDefense(mx, my)\n   mname = level.GetMonsterName(mx, my)\n   \'Get monster xp.\n   mxp = level.GetMonsterXP(mx, my)\n   \'Make sure we got something.\n   If df \x3E 0 Then\n      \'Returns the combat factor based on what the character is holding.\n      cf = pchar.GetMeleeCombatFactor()\n      \'Get the rolls.\n      croll = RandomRange(1, cf)\n      mroll = RandomRange(1, df)\n      \'See if the character hit monster.      \n      If croll \x3E mroll Then\n         \'Get total weapon damage.\n         dam = pchar.GetWeaponDamage()\n         \'Get monster armor rating.\n         marm = level.GetMonsterArmor(mx, my)\n         \'Adjust the damage value based on arnor rating.\n         dam = dam - (dam * marm)\n         If dam \x3C= 0 Then dam = 1\n         \'Check for magic weapon in both slots. Only returns if evaluated.\n         spl1 = pchar.GetWeaponSpell(wPrimary)\n         spl2 = pchar.GetWeaponSpell(wSecondary)\n         \'Check for Goliath spell.\n         If spl1.id = splGoliath Then dam += pchar.CurrStr\n         If spl2.id = splGoliath Then dam += pchar.CurrStr\n         \'Set the monster hp.\n         isdead = level.ApplyDamage(mx, my, dam)\n         \'Make sure monster is still alive.\n         If isdead = FALSE Then\n            \'Apply any spells.\n            If spl1.id \x3C\x3E splNone Then\n               isdead = DoWeaponSpell(spl1, mx, my)\n            EndIf\n            If isdead = FALSE Then\n               If spl2.id \x3C\x3E splNone Then\n                  isdead = DoWeaponSpell(spl2, mx, my)\n               End If\n            EndIf\n         EndIf\n         \'Print the message.\n         If isdead = TRUE Then\n            \'Add xp amount to character.\n            xp = pchar.CurrXP\n            xp += mxp\n            pchar.CurrXP = xp\n            xp = pchar.TotXP\n            xp += mxp\n            pchar.TotXP = xp\n            \'Print message.\n            txt = pchar.CharName & " killed the " & mname & " with " & dam & " damage points."\n            PrintMessage txt\n         Else\n            txt = pchar.CharName & " hit the " & mname & " for " & dam & " damage points."\n            PrintMessage txt\n         EndIf\n      Else\n         txt = pchar.CharName & " missed the " & mname & "."\n         PrintMessage txt\n      EndIf\n   EndIf\nEnd Sub\n}}}\n\x3C/div\x3E\nAs you can see, we do not need to add a whole lot new code here to implement the magic system. Here is the relevant code changes.\n\n\x3Cdiv class="code"\x3E\n/dod.bas:DoMeleeCombat/\n{{{\n         \'Check for magic weapon in both slots. Only returns if evaluated.\n         spl1 = pchar.GetWeaponSpell(wPrimary)\n         spl2 = pchar.GetWeaponSpell(wSecondary)\n         \'Check for Goliath spell.\n         If spl1.id = splGoliath Then dam += pchar.CurrStr\n         If spl2.id = splGoliath Then dam += pchar.CurrStr\n         \'Set the monster hp.\n         isdead = level.ApplyDamage(mx, my, dam)\n         \'Make sure monster is still alive.\n         If isdead = FALSE Then\n            \'Apply any spells.\n            If spl1.id \x3C\x3E splNone Then\n               isdead = DoWeaponSpell(spl1, mx, my)\n            EndIf\n            If isdead = FALSE Then\n               If spl2.id \x3C\x3E splNone Then\n                  isdead = DoWeaponSpell(spl2, mx, my)\n               End If\n            EndIf\n         EndIf\n\n}}}\n\x3C/div\x3E\nThe /GetWeaponSpell/ returns the spell information in a temporary spell Type Def. We need all of the information when we apply the spell to the monster. We also have to check both the primary and secondary slots since the character may be wielding two one-handed weapons. We then check to see if either spell is the Goliath spell, which adds the character strength to the weapon attack damage. Since this spell is applied via the damage property, we need to apply that effect here rather than later, as with most spells. If the monster is still alive after the initial attack, we then apply the spells to the monsters. Again, we have to check both slots since there is the potential for two magic weapons.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n\'Returns spell info.\nFunction character.GetWeaponSpell(wslot As wieldpos) As spelltype\n   Dim ret As spelltype\n   \n   \'Clearsa the spell type.\n   ClearSpell ret\n         \n   \'Check to see if we have a weapon in slot.\n   If _cinfo.cwield(wslot).classid = clWeapon Then\n      \'Check to see if weapon is evaluated.\n      If _cinfo.cwield(wslot).weapon.eval = TRUE Then\n         \'Return the spell; may be splNone.\n         ret = _cinfo.cwield(wslot).weapon.spell\n      End If\n   End If\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nThe /GetWeaponSpell/ function is part of the character object and just returns the spell information if the weapon has been evaluated. The /ClearSpell/ subroutine sets the spell id to /splNone/ so if the weapon hasn\'t been evaluated, splNone is returned to the caller. What if the weapon doesn\'t have a spell? Then splNone will be returned since we called ClearSpell in the weapon generation subroutine to explicitly set the spell id. This ensures that no matter what the state of the weapon, the correct spell id is returned.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Applies any active weapon spell.\nFunction DoWeaponSpell (spl As spelltype, mx As Integer, my As Integer) As Integer\n   Dim As Integer ret = FALSE, statamt, rstat\n   Dim As monStats mstat\n   Dim As String splname\n   \n   If spl.id = splThief Then\n      \'Get random stat.\n      mstat = RandomRange(CombatFactor, MagicDefense)\n      statamt = level.GetMonsterStatAmt(mstat, mx, my)\n      rstat = RandomRange(1, statamt - 1)\n      \'Add stat amount to char for lvl turns.\n      Select Case mstat\n         Case CombatFactor\n            pchar.BonAcf = rstat\n            pchar.BonAcfCnt = spl.lvl\n         Case CombatDefense\n            pchar.BonCdf = rstat\n            pchar.BonCdfCnt = spl.lvl\n         Case MagicCombat\n            pchar.BonMcf = rstat\n            pchar.BonMcfCnt = spl.lvl\n         Case MagicDefense\n            pchar.BonMdf = rstat\n            pchar.BonMdfCnt = spl.lvl\n      End Select\n   Else\n      \'Apply spell to monster.\n      ret = level.ApplySpell(spl, mx, my)\n   EndIf\n   \n   Return ret\n      \nEnd Function\n}}}\n\x3C/div\x3E\n/DoWeaponSpell/ applies the current spell to the monster. Notice that we are explicitly checking for the /Thief/ spell since that spell steals a combat factor from the monster and adds it to the character for a period of time. The different factors are defined in an enumeration within the /monster.bi/ file.\n\n\x3Cdiv class="code"\x3E\n/monster.bi/\n{{{\n\'Monster stats used in spells.\nEnum monStats\n   CombatFactor = 1\n   CombatDefense\n   MagicCombat\n   MagicDefense\nEnd Enum\n}}}\n\x3C/div\x3E\nA random combat factor is selected and then the data is retrieved from the monster through the /GetMonsterStatAmt/.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Returns the current monster stat amt.\nFunction levelobj.GetMonsterStatAmt(stat As monStats, mx As Integer, my As Integer) As Integer\n   Dim As Integer midx, ret = 0\n   \n   If _level.lmap(mx, my).monidx \x3E 0 Then\n      midx = _level.lmap(mx, my).monidx\n      If stat = CombatFactor Then\n         ret = _level.moninfo(midx).cf \n      ElseIf stat = CombatDefense Then\n         ret = _level.moninfo(midx).cd\n      ElseIf stat = MagicCombat Then\n         ret = _level.moninfo(midx).mf\n      ElseIf stat = MagicDefense Then\n         ret = _level.moninfo(midx).md\n      EndIf\n   End If\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nSince monsters are an attribute of the map object, we need to get the information from the level object. This simply looks at the passed combat factor and returns the appropriate amount. \n\nOnce we get the stat information we desire, we then apply that to the character.\n\n\x3Cdiv class="code"\x3E\n/dod.bas:DoWeaponSpell/\n{{{\n...\n      Select Case mstat\n         Case CombatFactor\n            pchar.BonAcf = rstat\n            pchar.BonAcfCnt = spl.lvl\n...\n}}}\n\x3C/div\x3E\nThe value goes into the bonus fields of the appropriate stat and the length of the bonus is determined by the spell level. Since the level is based on the dungeon level, the higher the spell level, the longer the bonus will be in effect. One thing to keep in mind here though is that there can only be one bonus active at any time, and they are not accumulative. This will require that the player decide what magic weapon to use and for how long. This adds even more tactical aspects to the game, and also prevents the player from becoming too powerful. It would be easy to build up huge bonus amounts if the spells were accumulative, which would make the game much to easy and much too boring to play.\n\nIf the spell passed to DoWeaponSpell isn\'t the Thief spell, then we call /ApplySpell/ in the level object to apply the spell to the monster.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Applies spell to monster.\nFunction levelobj.ApplySpell(spl As spelltype, mx As Integer, my As Integer) As Integer\n   Dim As Integer ret, midx\n   Dim stat As monStats\n   Dim As String txt\n   \n   \'Make sure there is a monster here.\n   If _level.lmap(mx, my).monidx \x3E 0 Then\n      \'Get the monster index.\n      midx = _level.lmap(mx, my).monidx\n      \'Set the monster flags based on spell.\n      Select Case spl.id\n         Case splSerpentBite \'Weapon: Inflict poison damage.\n            ret = ApplyDamage(mx, my, spl.dam)\n            \'If not dead set the timed flag.\n            If ret = FALSE Then\n               _level.moninfo(midx).effects(mePoison).cnt = spl.lvl\n               _level.moninfo(midx).effects(mePoison).dam = spl.dam\n            EndIf\n            txt = "Sperpent Bite Spell does " & spl.dam & " to " & _level.moninfo(midx).mname & "."\n            PrintMessage txt\n         Case splRend        \'Weapon: Decrease armor of target.\n            _level.moninfo(midx).armval = _level.moninfo(midx).armval - (spl.dam / maxlevel)\n            If _level.moninfo(midx).armval \x3C 0.0 Then\n               _level.moninfo(midx).armval = 0.0\n            EndIf\n            txt = "Rend Spell reduces armor to  " & _level.moninfo(midx).armval & " for " & _level.moninfo(midx).mname & "."\n            PrintMessage txt\n         Case splSunder      \'Weapon: Decrease target weapon damage.\n            _level.moninfo(midx).atkdam = _level.moninfo(midx).atkdam - spl.dam\n            If _level.moninfo(midx).atkdam \x3C 1 Then\n               _level.moninfo(midx).atkdam = 1\n            EndIf\n            txt = "Sunder Spell reduces attack damage to  " & _level.moninfo(midx).atkdam & " for " & _level.moninfo(midx).mname & "."\n            PrintMessage txt\n         Case splReaper \'Weapon: Causes monster to flee.\n            _level.moninfo(midx).flee = TRUE\n            txt = "Reaper Spell is making " & _level.moninfo(midx).mname & " flee."\n            PrintMessage txt\n         Case splFire        \'Weapon: Does fire damage to target for lvl turns.\n            ret = ApplyDamage(mx, my, spl.dam)\n            \'If not dead set the timed flag.\n            If ret = FALSE Then\n               _level.moninfo(midx).effects(meFire).cnt = spl.lvl\n               _level.moninfo(midx).effects(meFire).dam = spl.dam\n            EndIf\n            txt = "Fire Spell inflicted  " & spl.dam & " to " & _level.moninfo(midx).mname & "."\n            PrintMessage txt\n         Case splStun        \'Weapon: Stuns target for lvl turns.\n            _level.moninfo(midx).effects(meStun).cnt = spl.lvl\n            _level.moninfo(midx).effects(meStun).dam = 0\n            txt = "Stun Spell stunned " & _level.moninfo(midx).mname & "."\n            PrintMessage txt\n         Case splChaos       \'Weapon: Random amount of additonal damage.\n            ret = ApplyDamage(mx, my, spl.dam)\n            txt = "Chaos Spell inflicted  " & spl.dam & " additional damage to " & _level.moninfo(midx).mname & "."\n            PrintMessage txt\n         Case splWraith      \'Weapon: Decreases random stat of target.\n            \'Get the stat.\n            stat = RandomRange(CombatFactor, MagicDefense)\n            Select Case stat\n               Case CombatFactor\n                  _level.moninfo(midx).cf = _level.moninfo(midx).cf - spl.dam\n                  If _level.moninfo(midx).cf \x3C 0 Then\n                     _level.moninfo(midx).cf = 1\n                  EndIf\n                  txt = "Wraith Spell reduced " & _level.moninfo(midx).mname & " combat factor by " & spl.dam & "."\n                  PrintMessage txt\n               Case CombatDefense\n                  _level.moninfo(midx).cd = _level.moninfo(midx).cd - spl.dam\n                  If _level.moninfo(midx).cd \x3C 0 Then\n                     _level.moninfo(midx).cd = 1\n                  EndIf\n                  txt = "Wraith Spell reduced " & _level.moninfo(midx).mname & " combat defense by " & spl.dam & "."\n                  PrintMessage txt\n               Case MagicCombat\n                  _level.moninfo(midx).mf = _level.moninfo(midx).mf - spl.dam\n                  If _level.moninfo(midx).mf \x3C 0 Then\n                     _level.moninfo(midx).mf = 1\n                  EndIf\n                  txt = "Wraith Spell reduced " & _level.moninfo(midx).mname & " magic combat by " & spl.dam & "."\n                  PrintMessage txt\n               Case MagicDefense\n                  _level.moninfo(midx).md = _level.moninfo(midx).md - spl.dam\n                  If _level.moninfo(midx).md \x3C 0 Then\n                     _level.moninfo(midx).md = 1\n                  EndIf\n                  txt = "Wraith Spell reduced " & _level.moninfo(midx).mname & " magic defense by " & spl.dam & "."\n                  PrintMessage txt\n            End Select\n      End Select\n   End If\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nThe first thing we need to do here is to make sure we are applying the spell to an active monster. If so, we then apply the spell to the monster based on the spell id. Each spell does something different, and affects a monster in different ways. Notice that we are only concerned here with spells, not the source of the spell. This means that we can add any of the spells to this routine, those associated with items and those which are actively cast. This is why we needed to generalize the spell data, so that we can manage all the spells with a common set of routines.\n\nYou can go over the code and see what each spell does to a monster. Notice that the /lvl/ and /dam/ fields are used in various ways. The /dam/ field, as I said, is multipurpose, not only causing damage, but reducing attributes as well. Some of the spells affect a monster over time; The /Reaper/ spell causes a monster to flee by setting the /flee/ attribute to TRUE. The /Stun/ spell freezes the monster for a time, disabling all attacks and movement. Most of the period effects are based on the level of the spell, so the higher the level, the longer the monster will be affected.\n\nNot only do we have melee combat, but we also have projectile combat, so we need to add the magic system to the projectile combat routines as well.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Perform projectile combat.\nSub DoProjectileCombat(mx As Integer, my As Integer, wslot As wieldpos)\n   Dim As Integer cf, df, croll, mroll, dam, isdead, midx, mxp, xp \n   Dim As String txt, mname\n   Dim As Single marm\n   Dim As spelltype spl1, spl2\n   \n   \'Get the monster defense factor.\n   If pchar.ProjectileIsWand(wslot) = TRUE Then\n      df = level.GetMonsterMagicDefense(mx, my)\n   Else\n      df = level.GetMonsterDefense(mx, my)\n   EndIf\n   \'Get monster name.\n   mname = level.GetMonsterName(mx, my)\n   \'Get monster xp.\n   mxp = level.GetMonsterXP(mx, my)\n   \'Make sure we got something.\n   If df \x3E 0 Then\n      \'Returns the combat factor based on what the character is holding.\n      If pchar.ProjectileIsWand(wslot) = TRUE Then\n         cf = pchar.GetMagicCombatFactor()\n      Else\n         cf = pchar.GetProjectileCombatFactor()\n      EndIf\n      \'Get the rolls.\n      croll = RandomRange(1, cf)\n      mroll = RandomRange(1, df)\n      \'See if the character hit monster.      \n      If croll \x3E mroll Then\n         \'Get total weapon damage.\n         dam = pchar.GetWeaponDamage(wslot)\n         \'Get monster armor rating.\n         marm = level.GetMonsterArmor(mx, my)\n         \'Adjust the damage value based on armor rating.\n         dam = dam - (dam * marm)\n         If dam \x3C= 0 Then dam = 1\n         \'Check for magic weapon in both slots. Only returns if evaluated.\n         spl1 = pchar.GetWeaponSpell(wPrimary)\n         spl2 = pchar.GetWeaponSpell(wSecondary)\n         \'Check for Goliath spell.\n         If spl1.id = splGoliath Then dam += pchar.CurrStr\n         If spl2.id = splGoliath Then dam += pchar.CurrStr\n         \'Set the monster hp.\n         isdead = level.ApplyDamage(mx, my, dam)\n         \'Make sure monster is still alive.\n         If isdead = FALSE Then\n            \'Apply any spells.\n            If spl1.id \x3C\x3E splNone Then\n               isdead = DoWeaponSpell(spl1, mx, my)\n            EndIf\n            If isdead = FALSE Then\n               If spl2.id \x3C\x3E splNone Then\n                  isdead = DoWeaponSpell(spl2, mx, my)\n               End If\n            EndIf\n         EndIf\n         \'Print the message.\n         If isdead = TRUE Then\n            \'Add xp amount to character.\n            xp = pchar.CurrXP\n            xp += mxp\n            pchar.CurrXP = xp\n            xp = pchar.TotXP\n            xp += mxp\n            pchar.TotXP = xp\n            \'Print message.\n            txt = pchar.CharName & " killed the " & mname & " with " & dam & " damage points."\n            PrintMessage txt\n         Else\n            txt = pchar.CharName & " hit the " & mname & " for " & dam & " damage points."\n            PrintMessage txt\n         EndIf\n      Else\n         txt = pchar.CharName & " missed the " & mname & "."\n         PrintMessage txt\n      EndIf\n   EndIf\nEnd Sub\n}}}\n\x3C/div\x3E\nHere is the updated projectile code, and as you can see, it follows the melee combat almost exactly. We get the spell information, if any, and then check for the Goliath spell. We then call /DoWeaponSpell/ to apply the spell to the monster. That is all we need to do to update the projectile combat to handle spells.\n\nWe have added quite a bit of code to the project for the spell system, but most of has been fairly simple code because we generalized the spell data. Now that we have the basic system in place, it will be easy to add spells to the armor and jewelry, since we can use the foundation we have created here and just update the various routines to handle the new spells for the various items in the game.',
'= 30: Tweaking the Game 1\nIt is a good idea from time to time to look over the game and see what needs to be tweaked to improve game play, or fix a bug or make the program a bit more responsive. Tweaking means minor updates, not major overhauls. They are usually just a line or two added or removed from the game. If a section of code needs a lot of work, then it should be approached as a major revision to the code, rather than just a quick update. The difference is in the impact to the existing code base. A tweak shouldn\'t impact the game much at all. If later you find the tweak not working out as expected, you should be able to back out the changes with minimal effect as well. After looking over the game a bit, I found a few areas that could be reworked to enhance the game.\n\nThe first tweak was a minor bug on the level display in the upper left corner of screen. Since the current level was display on the background image, it wasn\'t being updated properly since the background was not being redrawn between level changes. The original idea was that the background needed only to be drawn once since the main screen is redrawn in sections. One section is the map area and the other is the status area. By breaking out the display into sections, we can update only those sections that need to be updated, making the game more responsive. However, I neglected to take into account the level change. The fix was easy though. Just redraw the background when we change levels. \n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\nSub DrawMainScreen (db As Integer = FALSE)\n   Dim As Integer x, y, j, pct, row, col\n   Dim As Double pctd\n   Dim As UInteger clr\n   Dim As String txt, idesc\n   Dim As terrainids terr\n   Dim inv As invtype\n   Dim spl As spelltype\n   \n   ScreenLock\n   \'Set the background for the main screen.\n   If db = TRUE Then\n      DrawBackground mainback()\n   End If\n...\n}}}\n\x3C/div\x3E\nHere we added a parameter to the /DrawMainScreen/ subroutine that indicates if we should draw the background. Since the parameter has a default value of FALSE, we don\'t need to update any of the calling code except when we process the command to go down the stairs.\n\n\x3Cdiv class="code"\x3E\n/dod.bas: Main Game Loop/\n{{{\n...\n\'Main game loop.\nIf mm \x3C\x3E mmenu.mQuit Then\n   \'Set the first level.\n   level.LevelID = 1\n	\'Build the first level of dungeon\n	level.GenerateDungeonLevel\n   \'Display the main screen.\n   DrawMainScreen TRUE\n\n...\n         \'Check for down stairs.\n         If ckey = "\x3E" Then\n            \'Check to make sure on down stairs.\n            If level.GetTileID(pchar.Locx, pchar.Locy) = tstairdn Then\n               \'Set the level id.\n               level.LevelID = level.LevelID + 1\n               \'Build a new level.\n           	   level.GenerateDungeonLevel\n               \'Draw Screen.\n               DrawMainScreen TRUE\n            End If\n         EndIf\n...\n}}}\n\x3C/div\x3E\nWe need to update the call in two places. The first is when we initially draw the main screen after the menu selection. This is shown in the first code section. The second place is when we change levels. We have added TRUE when we call the DrawMainScreen subroutine to redraw the background so that the level information is displayed correctly. A simple fix, with minimal impact on the code.\n\nThe next tweak involves the spending of XP. The current cost to upgrade an attribute was 10 XP per 1 point of attribute. However, the problem here is that the character can advance much too quickly and it introduced a way for the player to "cheat" the game by allowing the player to enter a new level, upgrade the character and tromp all the existing monsters on the level. (I put cheat in quotes, because it isn\'t a real cheat, just a flaw in the game that an astute player could take advantage of when playing the game.) Remember that we scale the monsters according to the current player attributes when we generate the level. If the player waits to upgrade the character when entering a new level, then the character can be quite a bit more powerful than the monsters, which effectively kills the challenge of the game.\n\nThe fix here is to just increase the cost ratio of 10 to 1, to 100 to 1. This slows the advancement process and makes the player and monsters attributes more in line with each other. The player could still wait until there is a good amount of XP to upgrade the character, but the effect will only last for the current level, and then the playing field evens out for the next few levels until the XP store is built up again.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Applies XP to character attributes.\nSub ImproveCharacter ()\n   Dim As Integer sel, xp, reqamt = 100\n   \n   \'Make sure we have some xp to work with.\n   If pchar.CurrXP \x3E reqamt Then\n      Do\n         If pchar.CurrXP \x3E reqamt Then\n            \'Print Current stats.\n            pchar.PrintStats\n            PutTextShadow "Cost = " & reqamt & " XP for 1 point improvement.", 49, 10\n            PutTextShadow "Enter attribute 1 to 5 to improve, enter to exit: ", 51, 10\n            \'Position input.\n            Locate 53, 10\n            \'Get the input.\n            Input sel\n            \'If attribute selected then Change. \n            If sel = 1 Then\n               pchar.ChangeStrength 1\n               \'Subtract the xp amount.\n               xp = pchar.CurrXP\n               xp -= reqamt\n               pchar.CurrXP = xp \n            EndIf\n            If sel = 2 Then\n               pchar.ChangeStamina 1\n               \'Subtract the xp amount.\n               xp = pchar.CurrXP\n               xp -= reqamt\n               pchar.CurrXP = xp \n            EndIf\n            If sel = 3 Then\n               pchar.ChangeDexterity 1\n               \'Subtract the xp amount.\n               xp = pchar.CurrXP\n               xp -= reqamt\n               pchar.CurrXP = xp \n            EndIf      \n            If sel = 4 Then\n               pchar.ChangeAgility 1\n               \'Subtract the xp amount.\n               xp = pchar.CurrXP\n               xp -= reqamt\n               pchar.CurrXP = xp \n            EndIf\n            If sel = 5 Then\n               pchar.ChangeIntelligence 1\n               \'Subtract the xp amount.\n               xp = pchar.CurrXP\n               xp -= reqamt\n               pchar.CurrXP = xp \n            EndIf\n         Else\n            sel = 0\n         End If\n      Loop Until sel = 0\n   Else\n      PrintMessage "Not enough XP to spend."\n   End If\nEnd Sub\n}}}\n\x3C/div\x3E\nWe have added a new variable to the /ImproveCharacter/ subroutine /reqamt / which is the required amount of XP needed to upgrade an attribute. By using a variable rather than a hard-coded value, like we had before, we can adjust this value as needed in one place rather than several places. It may turn out that 100 is just too much, so this value may need to change again, and it will be easy to try different values until we hit upon the right combination.\n\nThe next tweak is also associated with upgrading the character. When the strength or stamina was increased, the current and maximum HP was also updated to reflect the new values {{{HP = strength + stamina}}}. This introduced another back door that the player could take advantage of to lessen the challenge of the game. The fix here is to upgrade the maximum HP but leave the current HP alone.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n...\n   Declare Sub ChangeStrength(change As Integer, docurrhp As Integer = FALSE)  \n   Declare Sub ChangeStamina(change As Integer, docurrhp As Integer = FALSE) \n...\n\'Changes attribute and updates associated stats.\nSub character.ChangeStrength(change As Integer, docurrhp As Integer = FALSE)\n   \'Update the attribute\n   _cinfo.stratt(idxAttr) = _cinfo.stratt(idxAttr) + change\n   If _cinfo.stratt(idxAttr) \x3C 1 Then _cinfo.stratt(idxAttr) = 1\n   \'Update the HP. \n   _cinfo.maxhp = _cinfo.stratt(idxAttr) + _cinfo.staatt(idxAttr)\n   If docurrhp \x3C\x3E FALSE Then \n      _cinfo.currhp = _cinfo.maxhp\n   End If \n   _cinfo.ucfsk(idxAttr) = _cinfo.stratt(idxAttr) + _cinfo.aglatt(idxAttr)\n   _cinfo.acfsk(idxAttr) = _cinfo.stratt(idxAttr) + _cinfo.dexatt(idxAttr)\n   _cinfo.cdfsk(idxAttr) = _cinfo.stratt(idxAttr) + _cinfo.aglatt(idxAttr)\nEnd Sub\n\n\'Changes attribute and updates associated stats.\nSub character.ChangeStamina(change As Integer, docurrhp As Integer = FALSE)\n      \'Update the attribute\n      _cinfo.staatt(idxAttr) = _cinfo.staatt(idxAttr) + change\n      If _cinfo.staatt(idxAttr) \x3C 1 Then _cinfo.staatt(idxAttr) = 1\n      \'Update the HP. \n      _cinfo.maxhp = _cinfo.stratt(idxAttr) + _cinfo.staatt(idxAttr) \n      If docurrhp \x3C\x3E FALSE Then \n         _cinfo.currhp = _cinfo.maxhp\n      End If \n      _cinfo.currmana = _cinfo.intatt(idxAttr) + _cinfo.staatt(idxAttr) \n      _cinfo.maxmana = _cinfo.currmana\n      _cinfo.mcfsk(idxAttr) = _cinfo.intatt(idxAttr) + _cinfo.staatt(idxAttr)\nEnd Sub\n}}}\n\x3C/div\x3E\nHere we have added a flag to the /ChangeStength/ and /ChangeStamina/ subroutines of the character object to only increase the current HP to the levels when the /docurrhp/ flag is set to TRUE (or not FALSE). No other changes are required in the code aside from this. Now when the character is improved, the current HP will not change.\n\nWhy include a flag here at all? Because we may want to update the current HP when the player improves the character at some point in the development process. Suppose there is a spell that allows the character to go to full HP when improving the strength or stamina; we now have a way to do this without having to change anything in the code. Even if we don\'t add any sort of mechanism like this, we have left the option open and it doesn\'t hurt anything even if we never include that functionality.\n\nThe next tweak is a cosmetic one, but is another of those convenience features that will help the player in the game. In the inventory screen we mark items that haven\'t been evaluated. We will also mark magic items as well, so that the player can tell at a glance what is magic and what isn\'t so that they don\'t have to inspect every item just to see if it is worth keeping or not. \n\n\x3Cdiv class="code"\x3E\n/dod.bas: DrawInventoryScreen/\n{{{\n...\n         \'If not evaluated, mark so player knows it hasn\'t been evaluated.\n         If ret = FALSE Then\n            txt &= "(*)"\n         Else\n            \'If magic mark it.\n            If inv.classid = clWeapon Then\n               If inv.weapon.spell.id \x3C\x3E splNone Then\n                  txt &= " (M)"\n               End If\n            EndIf\n         EndIf\n...\n}}}\n\x3C/div\x3E\nHere we check to see if the inventory item has been evaluated. If it hasn\'t we just print the asterisk as normal, but if it has been evaluated, we then check to see if it is a weapon, and if magic, then mark it with an M. Simple, but it will help the player when there are a lot of items, and they want to make a quick scan to see if any of the evaluated items are magic. Of course, if it hasn\'t been evaluated, then even if it is magic, they won\'t know it until the item has been successfully evaluated.\n\nThe last tweak is again a cosmetic change, but also gives the player necessary information about the character. Since we have spells now that affect attributes and combat factors, showing the current bonus amount and length of time the bonus is active will help the player manage those effects better. We were giving the bonus amounts in the status screen, but I added the amount of time left, in number of turns, that a bonus will be active.\n\n\x3Cdiv class="code"\x3E\n/dod.bas: DrawMainScreen/\n{{{\n...\n   If pchar.BonStr \x3E -1 Then\n      txt = " +"\n   Else\n      txt = " -"\n   EndIf\n   PutText "Str: " & pchar.CurrStr & txt & pchar.BonStr & " (" & pchar.BonStrCnt & ")", row, col\n...\n}}}\n\x3C/div\x3E\nI am just showing the bonus for the strength attribute here. All of the other attributes and combat factors have similar formats. Since we are only allowing one bonus active at a time, this information will help the player decide whether to utilize a bonus modifier item, or keep the current bonus in place. Once again, we are giving the player choices and something to think about while they play the game.\n\nAs you can see, these tweaks are very simple to implement, but they have a profound effect on the game. The XP tweak by itself changes the whole dynamic of the game, hopefully for the better. This is the just first of the tweaks, and there will be more to come. Tweaking a game is an ongoing process, and there is always something to look at and modify to improve the play of the game.\n',
'= 31:Armor Magic\n\x3Cdiv align="center"\x3E\x3Cimg src=\'images/armormagic.png\' /\x3E\x3C/div\x3E\n\nLike weapons, armor and shields also have magical effects, and they complement the weapon types in the game, as we will see. First though, let\'s look at the updated spell list.\n\n\x3Cdiv class="code"\x3E\n{{{\n/inv.bi/\n\'Effects ids.\nEnum spellid\n   ...\n   splNoSlash     \'Armor: +% from slash attacks.\n   splNoCrush     \'Armor: +% from crush attacks.\n   splNoPierce    \'Armor: +% from pierce attacks.\n   splNoEnergy    \'Armor: +% from energy attacks.\n   splNoFire      \'Armor: +% from fire attacks.\n   splNoAcid      \'Armor: +% from acid attacks.\n   splNoMagic     \'Armor: +% from magic attacks.\nEnd Enum\n}}}\n\x3C/div\x3E\nEven though the comments indicate armor here, the spells are the same for both armor and shields. The spells are purely defensive, which makes sense for this type of item, and they are limited to different kinds of attacks. As you can see from the list, not only do we have obvious weapon attacks, but there are some strange ones like fire and acid, which are attacks by monsters that have natural weapons. When we get to the spell casting portion of the project, we may want to add additional attacks to the list, such as different elemental or mental effects.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Weapon damage type.\nEnum weapdamtype\n   wdNone\n   wdSlash\n   wdCrush\n   wdPierce\n   wdEnergy\n   wdFire\n   wdAcid\n   wdMagicProt\n   wdMagic\nEnd Enum\n}}}\n\x3C/div\x3E\nThis enumeration lists the attack effect ids that we will use to identify a particular attack. We have also updated the spell names and descriptions to reflect the new spells. See /GetSpellName/ and /GetSpellEffect/ in /inv.bi/ for the new descriptions.\n\nThe procedure for adding the spells to shields and armor is straight forward.\n\n\x3Cdiv class="code"\x3E\n/inv.bi: GenerateShield, GenerateArmor/\n{{{\n...\n   If IsMagic = TRUE Then\n      inv.shield.evaldr = GetScaledFactor(charint, currlevel) \'The eval dr.\n      inv.shield.spell.id = RandomRange(splNoSlash, splNoMagic) \'Spell id.\n      GenerateSpell inv.shield.spell, currlevel\n      inv.shield.spelleffect = GetArmorSpellEffect(inv.shield.spell.id)\n   EndIf\n...\n   If IsMagic = TRUE Then\n      inv.armor.evaldr = GetScaledFactor(charint, currlevel) \'The eval dr.\n      inv.armor.spell.id = RandomRange(splNoSlash, splNoMagic) \'Spell id.\n      inv.armor.spelleffect = GetArmorSpellEffect(inv.armor.spell.id)\n      GenerateSpell inv.armor.spell, currlevel\n   EndIf\n...\n}}}\n\x3C/div\x3E\nHere we have both the shield code and armor code and it is almost identical to the weapon spell code. The new addition here is the /GetArmorSpellEffect/.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Returns the spell effect for armor.\nFunction GetArmorSpellEffect(id As spellid) As weapdamtype\n   Dim ret As weapdamtype\n   \n   Select Case id\n      Case splNoSlash     \n         ret = wdSlash              \n      Case splNoCrush     \n         ret = wdCrush\n      Case splNoPierce    \n         ret = wdPierce\n      Case splNoEnergy    \n         ret = wdEnergy \n      Case splNoMagic     \n         ret = wdMagicProt\n      Case splNoFire      \n         ret = wdFire\n      Case wdAcid         \n         ret = wdAcid\n      Case Else\n         ret = wdNone\n   End Select\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nThis just returns the type of attack that the armor/shield protects against. We will use this in our armor value calculations to make things a bit easier on us. We\'ll look at that in a minute. Since we have added attack effects to the armor, we need to update the weapon and monster monster code to reflect these attack types.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Weapon type def.\nType weapontype\n   ...\n   weapontype As weaptype    \'Type of weapon: melee, projectile.\n   damtype As weapdamtype    \'Type of damage inflicted.\n   ...\nEnd Type\n}}}\n\x3C/div\x3E\n/damtype/ is the new field in our weapon Type Def.\n\n\x3Cdiv class="code"\x3E\n/inv.bi:GenerateWeapon/\n{{{\n   \'Set the weapon type and amount.\n   Select Case item\n      Case wpClub            \'1 hand, dam 4 chr:33\n         inv.desc = "Club"\n         inv.icon = Chr(33)\n         inv.weapon.noise = 1\n         inv.weapon.dam = 4\n         inv.weapon.hands = 1\n         inv.weapon.damtype = wdCrush\n    ...\n}}}\n\x3C/div\x3E\nHere is an example of setting the attack effect in the /GenerateWeapon/ subroutine. Each weapon has an attack effect, based on the type of weapon. A club or mace is a crushing weapon, while swords would be classified as slash weapons and bows would inflict pierce damage.\n\nWe also need to update the monster code to include the attack effects.\n\n\x3Cdiv class="code"\x3E\n/monster.bi/\n{{{\nType montype\n...\n   atkrange As Integer  \'Range of attack.\n   damtype As weapdamtype \'The type of attack.\n   damstr As String * 10 \'String that indicates type of damage.\n...\nEnd Type\n}}}\n\x3C/div\x3E\nWe have added two new fields to our monster definition, /damtype/ which is the attack effect, and /damstr/ which is a string that we will use to print the attack type to the player. By adding the attack effects in the message string, we add a little flavor to the game and individualize each monster for the player. Of course, we need to update the monster generation routine to fill these new fields.\n\n\x3Cdiv class="code"\x3E\n/monster.bi/\n{{{\n...\n   \'Set individual atrributes.\n   Select Case mon.id\n      Case monDarkangel\n         mon.mname = "Dark Angel" \'Name\n         mon.micon = "A"          \'Map icon.\n         mon.ismagic = TRUE \'Magic flag.\n         mon.spell.id = splNone      \'Castable spell.\n         mon.atkrange = 1 \'Attack range.\n         mon.dropcount = 1  \'Number of items in inventory.\n         GenerateWeapon inv, currlevel, RandomRange(wpSmallsword, wpBishopsflail) \'Weapon. \n         mon.dropitem(1) = inv \'Set the inv item.\n         mon.atkdam = mon.dropitem(1).weapon.dam \'Use weapon damage.\n         mon.armval = .8 \'Armor rating.\n         mon.cf = acfsk \'Combat attack factor.\n         mon.damtype = mon.dropitem(1).weapon.damtype \'Damage type.\n      Case monGiantbat\n         mon.mname = "Giant Bat" \'Name\n         mon.micon = "B"          \'Map icon.\n         mon.ismagic = FALSE \'Magic flag.\n         mon.spell.id = splNone      \'Castable spell.\n         mon.atkrange = 1 \'Attack range.\n         mon.dropcount = 0  \'Number of items in inventory.\n         mon.atkdam = stratt / 4 \'How much damage mon does.\n         mon.armval = .1 \'Armor rating.\n         mon.damtype = wdPierce\n...\n\n   \'Set the damage string.\n   Select Case mon.damtype\n      Case wdSlash\n         mon.damstr = "slash"\n      Case wdCrush\n         mon.damstr = "crush"\n      Case wdPierce\n         mon.damstr = "pierce"\n      Case wdEnergy\n         mon.damstr = "energy"\n      Case wdFire\n         mon.damstr = "fire"\n      Case wdAcid\n         mon.damstr = "acid"\n      Case wdMagic\n         mon.damstr = "magic"\n      Case Else\n         mon.damstr = ""\n   End Select\n...\n}}}\n\x3C/div\x3E\nI am only showing two examples of the new fields here. The DarkAngel attack effect is taken from the weapon it wields, while the Giant Bat has a natural attack which I classify as a pierce attack, thinking it would probably use its fangs as the attack mode. The second section of code just looks at the /damtype/ field and stores a string for later use.\n\nNow that we have the new attack effects in place, we need to include them in our armor calculations.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n\'Returns the current armor value.\nFunction character.GetArmorValue(wp As weapdamtype = wdNone) As Single\n   Dim As Single ret = 0.0\n   \n   \'Check for armor.\n   If _cinfo.cwield(wArmor).classid \x3C\x3E clNone Then\n      ret = _cinfo.cwield(wArmor).armor.dampct\n      \'Check to make sure we have an attack type.\n      If wp \x3C\x3E wdNone Then\n         \'Check for magical shield.\n         If _cinfo.cwield(wArmor).armor.eval = TRUE Then\n            \'Does shield have a spell.\n            If _cinfo.cwield(wArmor).armor.spell.id \x3C\x3E splNone Then\n               \'Does effect match attack effect.\n               If _cinfo.cwield(wArmor).armor.spelleffect = wp Then\n                  \'Add in the value.\n                  ret += (_cinfo.cwield(wArmor).armor.spell.lvl / 100)\n               EndIf\n            EndIf\n         EndIf\n      End If\n   EndIf\n   \n   Return ret\nEnd Function\n\n\'Returns any shield armor value.\nFunction character.GetShieldArmorValue (wp As weapdamtype = wdNone) As Single\n   Dim As Single ret = 0.0\n   Dim As Integer cnt\n      \n   \'Check for any shields.\n   If _cinfo.cwield(wPrimary).classid = clShield Then\n      ret += _cinfo.cwield(wPrimary).shield.dampct\n      cnt = 1\n      \'Check to make sure we have an attack type.\n      If wp \x3C\x3E wdNone Then\n         \'Check for magical shield.\n         If _cinfo.cwield(wPrimary).shield.eval = TRUE Then\n            \'Does shield have a spell.\n            If _cinfo.cwield(wPrimary).shield.spell.id \x3C\x3E splNone Then\n               \'Does effect match attack effect.\n               If _cinfo.cwield(wPrimary).shield.spelleffect = wp Then\n                  \'Add in the value.\n                  ret += (_cinfo.cwield(wPrimary).shield.spell.lvl / 100)\n                  cnt += 1 \n               EndIf\n            EndIf\n         EndIf\n      End If\n   EndIf   \n   If _cinfo.cwield(wSecondary).classid = clShield Then\n      ret += _cinfo.cwield(wSecondary).shield.dampct\n      cnt += 1\n      \'Make sure we have an attack type.\n      If wp \x3C\x3E wdNone Then\n         \'Check for magical shield.\n         If _cinfo.cwield(wSecondary).shield.eval = TRUE Then\n            \'Does shield have a spell.\n            If _cinfo.cwield(wSecondary).shield.spell.id \x3C\x3E splNone Then\n               \'Does effect match attack effect.\n               If _cinfo.cwield(wSecondary).shield.spelleffect = wp Then\n                  \'Add in the value.\n                  ret += (_cinfo.cwield(wSecondary).shield.spell.lvl / 100)\n                  cnt += 1 \n               EndIf\n            EndIf\n         EndIf\n      End If\n   EndIf\n   \n   \'Get the average of the values if any.\n   If cnt \x3E 0 Then\n      ret = ret / cnt\n   EndIf\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nWe have updated both the armor and shield calculations to include the magical item if present. Since shields may be wielded in either the primary or secondary slots, we need to check both slots. I have added a new parameter to each function, {{{wp As weapdamtype = wdNone}}} which has a default value of wdNone, that is, no effect. \n\nThe calculation process isn\'t very complicated. We first get the base armor value, we then check to make sure we have an attack type {{{If wp \x3C\x3E wdNone Then}}}. If we do, then we check to see if the armor has been evaluated {{{If _cinfo.cwield(wArmor).armor.eval = TRUE Then}}}. If it hasn\'t been evaluated, then even if it has a spell, it will not be active. We then check for a spell id {{{If _cinfo.cwield(wArmor).armor.spell.id \x3C\x3E splNone Then}}}. If it has a spell we compare the magic effect against the attack type {{{If _cinfo.cwield(wPrimary).shield.spelleffect = wp Then}}}. If the attack type matches the spell effect we then add in the additional armor value based on the level of the spell {{{ret += (_cinfo.cwield(wPrimary).shield.spell.lvl / 100)}}}. Since the level of the spell is based on the current dungeon level, the spell will at most add 50% to the armor value. The shields work the same way, except we check both slots.\n\nWe only need a minor update to the monster code to handle the new effects.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Monster attacks character.\nSub levelobj.MonsterAttack(mx As Integer, my As Integer)\n   Dim As Integer midx, cd, mc, rollc, rollm, chp, dam\n   Dim As String txt\n   Dim As Single arm\n   \n   \'Make sure there is a monster here.\n   If _level.lmap(mx, my).monidx \x3E 0 Then\n      midx = _level.lmap(mx, my).monidx\n      \'Get character defense factor.\n      cd = pchar.GetDefenseFactor()\n      \'Get monster attack factor.\n      mc = _level.moninfo(midx).cf\n      \'Get the rolls.\n      rollc = RandomRange(1, cd)\n      rollm = RandomRange(1, mc)\n      \'Compare monster roll to character roll.\n      If rollm \x3E rollc Then\n         \'Get the damage.\n         dam = _level.moninfo(midx).atkdam\n         \'Get any shield values if any.\n         arm = pchar.GetShieldArmorValue(_level.moninfo(midx).damtype)\n         \'Make sure the character has some armor.\n         If arm \x3E 0 Then\n            dam = dam - (dam * arm)\n         End If\n         \'Get the character armor value.\n         arm = pchar.GetArmorValue (_level.moninfo(midx).damtype)\n         \'Make sure the character has some armor.\n         If arm \x3E 0 Then\n            dam = dam - (dam * arm)\n         End If\n         If dam \x3C 1 Then dam = 1\n         \'Get character hp.\n         chp = pchar.CurrHP\n         \'Subtract damage from it.\n         chp -= dam\n         If chp \x3C 0 Then chp = 0\n         \'Reset character hp.\n         pchar.CurrHP = chp\n         txt = "The " &  _level.moninfo(midx).mname & " hits for " & dam & " " & _level.moninfo(midx).damstr & " damage points."\n      Else\n         txt = "The " &  _level.moninfo(midx).mname & " misses."\n      EndIf\n      PrintMessage txt\n   End If   \nEnd Sub\n}}}\n\x3C/div\x3E\nNotice we are now passing the monster attack type to /GetShieldArmorValue/ and /GetArmorValue /. This is all we need to do to implement the new armor magic. The string value we set in the monster generation subroutine is used here in the message to the player when the monster makes a successful hit on the character.\n\nThe last bit of code we need to update is our presentation routines. We need to add the "M" indicator on the main display and the inventory screens to match the magical weapon presentations.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Draws the main game screen.\nSub DrawMainScreen (db As Integer = FALSE)\n...\n   \'Check for held item.\n   If pchar.HasInvItem(wPrimary) = TRUE Then\n      pchar.GetInventoryItem wPrimary, inv\n      idesc = GetInvItemDesc(inv)\n      spl = pchar.GetItemSpell(wPrimary)\n      If spl.id \x3C\x3E splNone Then\n         idesc &= " (M)"\n      EndIf\n   Else\n      idesc = ""\n   EndIf\n   PutText "Primary: " & idesc, row, col\n...\n}}}\n\x3C/div\x3E\nWe have generalized this in the main display to handle any magical item so we don\'t need to keep updating this routine. Each slot has similar code that will print an "M" if the item is magical.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Draws the player inventory.\nSub DrawInventoryScreen()\n...\n         \'Get the inv item.\n         pchar.GetInventoryItem i, inv\n         \'Is the item evaluated.\n         ret = IsEval(inv)\n         \'Get the description.\n         desc = GetInvItemDesc(inv)\n         \'Get the color of the item.\n         clr = inv.iconclr\n         \'Build the text string.\n         txt = Chr(i) & " " & desc & " "\n         \'If not evaluated, mark so player knows it hasn\'t been evaluated.\n         If ret = FALSE Then\n            txt &= "(*)"\n         Else\n            spl = pchar.GetItemSpell(inv)\n            If spl.id \x3C\x3E splNone Then\n               txt &= " (M)"\n            EndIf\n         EndIf\n      Else\n...\n}}}\n\x3C/div\x3E\nHere we have overloaded the /GetItemSpell/ to handle an inventory item. The code is the same, we just changed the reference from a slot to an inventory item.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n\'Returns spell info.\nFunction character.GetItemSpell(inv As invtype) As spelltype\n   Dim ret As spelltype\n   \n   \'Clearsa the spell type.\n   ClearSpell ret\n         \n   \'Check to see if we have a weapon in slot.\n   If inv.classid = clWeapon Then\n      \'Check to see if weapon is evaluated.\n      If inv.weapon.eval = TRUE Then\n         \'Return the spell; may be splNone.\n         ret = inv.weapon.spell\n      End If\n   ElseIf inv.classid = clShield Then\n      \'Check to see if weapon is evaluated.\n      If inv.shield.eval = TRUE Then\n         \'Return the spell; may be splNone.\n         ret = inv.shield.spell\n      End If\n   ElseIf inv.classid = clArmor Then\n      \'Check to see if weapon is evaluated.\n      If inv.armor.eval = TRUE Then\n         \'Return the spell; may be splNone.\n         ret = inv.armor.spell\n      End If\n   ElseIf inv.classid = clRing Then\n      \'Check to see if weapon is evaluated.\n      If inv.jewelry.eval = TRUE Then\n         \'Return the spell; may be splNone.\n         ret = inv.jewelry.spell\n      End If\n   ElseIf inv.classid = clNecklace Then\n      \'Check to see if weapon is evaluated.\n      If inv.jewelry.eval = TRUE Then\n         \'Return the spell; may be splNone.\n         ret = inv.jewelry.spell\n      End If\n   End If\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nOverloading procedures is a nice way of generalizing routines to handle a wide variety of situations while lessening the impact on the code. Since the parameters are different in each function, the compiler knows what function to pick based on what is passed to the function. If we let the compiler do the work, it makes our job easier.\n\nThis is all we need to have magical armor and shields in our program. Next up, we need to add some magical jewelry to the game.\n',
'= 32: Jewelry Magic\n\x3Cdiv align="center"\x3E\x3Cimg src=\'images/jewelmagic.png\' /\x3E\x3C/div\x3E\n\nThis is the last of the item magic that we need to implement in our project. Jewelry acts much like potions, improving the character stats, but the effects last as long as the character is wearing the item. We will follow the same process that we followed when we implemented weapon and armor magic, so we will start with the new item spells.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Effects ids.\nEnum spellid\n   ...\n   splUCF         \'Jewelry: +% to UCF.\n   splACF         \'Jewelry: +% to ACF.\n   splPCF         \'Jewelry: +% to PCF.\n   splMCF         \'Jewelry: +% to MCF.\n   splCDF         \'Jewelry: +% to CDF.\n   splMDF         \'Jewelry: +% to MDF.\n   splRegenHP     \'Jewelry: +% to healing per turn.\n   splRegenMana   \'Jewelry: +% to mana per turn. \nEnd Enum\n}}}\n\x3C/div\x3E\nThese are the new spells and as you can see, they affect the character\'s combat factors. There are also two regeneration spells that will affect the character each turn, one for health and one for mana.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Jewelry effects.\nEnum jeffects\n   jwNone  \n   jwUCF  \n   jwACF  \n   jwPCF  \n   jwMCF  \n   jwCDF  \n   jwMDF  \n   jwRegenHP\n   jwRegenMana  \nEnd Enum\n}}}\n\x3C/div\x3E\nWe have also added an enumeration for the jewelry effects to make things easier when we code the effect implementation, which we will see in a moment.\n\nThe update to the jewelry generation code is very basic.\n\n\x3Cdiv class="code"\x3E\n/inv.bi:GenerateJewlery/\n{{{\n...\n   \'Magic Item.\n   If IsMagic = TRUE Then\n      inv.jewelry.evaldr = GetScaledFactor(charint, currlevel)\n      inv.jewelry.spell.id = RandomRange(splUCF, splRegenMana) \'Spell id.\n      GenerateSpell inv.jewelry.spell, currlevel\n      inv.jewelry.spelleffect = GetJewelrySpellEffect(inv.jewelry.spell.id)\n   EndIf\n...\n}}}\n\x3C/div\x3E\nYou have seen this before so it should look quite familiar. Just as we did with armor, we have added an effect field for the jewelry and load that field in /GetJewelrySpellEffect/.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Returns the jewlery spell effect.\nFunction GetJewelrySpellEffect(id As spellid) As jeffects\n   Dim ret As jeffects\n   \n   Select Case id\n      Case splUCF\n         ret = jwUCF\n      Case splACF\n         ret = jwACF\n      Case splPCF\n         ret = jwPCF\n      Case splMCF\n         ret = jwMCF\n      Case splCDF\n         ret = jwCDF\n      Case splMDF\n         ret = jwMDF\n      Case splRegenHP\n         ret = jwRegenHP\n      Case splRegenMana \n         ret = jwRegenMana\n      Case Else\n         ret = jwNone\n   End Select\n\n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nThis is a duplicate of the armor routine, we are just returning the jewelry enumeration values here. This gets the magic items into the game, now we need to process them.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n\'Returns the spell effect of any jewlery.\nFunction character.GetJewleryEffect(effect As jeffects, baseamt As Integer) As Integer\n   Dim As Integer ret = 0\n   \n   \'Check for necklace or ring.\n   If _cinfo.cwield(wNeck).classid = clNecklace Then\n      \'Make sure it is evaluated.\n      If _cinfo.cwield(wNeck).jewelry.eval = TRUE Then\n         \'Check for spell.\n         If _cinfo.cwield(wNeck).jewelry.spell.id \x3C\x3E splNone Then\n            \'Check for effect.\n            If _cinfo.cwield(wNeck).jewelry.spelleffect = effect Then\n               \'Add in the spell amount.\n               ret += Int((_cinfo.cwield(wNeck).jewelry.spell.lvl / 100) * baseamt) + 1\n            EndIf\n         End If\n      EndIf\n   EndIf\n   If _cinfo.cwield(wRingRt).classid = clRing Then\n      \'Make sure it is evaluated.\n      If _cinfo.cwield(wRingRt).jewelry.eval = TRUE Then\n         \'Check for spell.\n         If _cinfo.cwield(wRingRt).jewelry.spell.id \x3C\x3E splNone Then\n            \'Check for effect.\n            If _cinfo.cwield(wRingRt).jewelry.spelleffect = effect Then\n               \'Add in the spell amount.\n               ret += Int((_cinfo.cwield(wRingRt).jewelry.spell.lvl / 100) * baseamt) + 1\n            EndIf\n         End If\n      EndIf\n   EndIf\n   If _cinfo.cwield(wRingLt).classid = clRing Then\n      \'Make sure it is evaluated.\n      If _cinfo.cwield(wRingLt).jewelry.eval = TRUE Then\n         \'Check for spell.\n         If _cinfo.cwield(wRingLt).jewelry.spell.id \x3C\x3E splNone Then\n            \'Check for effect.\n            If _cinfo.cwield(wRingLt).jewelry.spelleffect = effect Then\n               \'Add in the spell amount.\n               ret += Int((_cinfo.cwield(wRingLt).jewelry.spell.lvl / 100) * baseamt) + 1\n            EndIf\n         End If\n      EndIf\n   EndIf\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nThis function in the character object looks at each jewelry slot and checks to see if there is a magic item in the slot. Just like the armor checks, we need to make sure that the item is evaluated, and that is has a spell. The effect id is passed to the function and this is compared to the item spell effect. If they match, then the bonus amount is calculated bases on the passed /baseamt/. We call this function in each of the combat functions.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n\'Returns the current combat factor based on what the character is wielding.\nFunction character.GetMeleeCombatFactor() As Integer\n   Dim As Integer ret\n   \n   \'Unarmed combat.\n   If (_cinfo.cwield(wPrimary).classid \x3C\x3E clWeapon) And (_cinfo.cwield(wSecondary).classid \x3C\x3E clWeapon) Then\n      ret = CurrUcf + BonUcf\n      ret += GetJewleryEffect(jwUCF, ret)\n   Else\n      \'Check primary slot.\n      If _cinfo.cwield(wPrimary).classid = clWeapon Then\n         \'Wielding a melee weapon.\n         If _cinfo.cwield(wPrimary).weapon.weapontype \x3C\x3E wtProjectile Then\n            ret = CurrAcf + BonAcf\n            ret += GetJewleryEffect(jwACF, ret)\n         Else\n          \'Return UCF as projectile weapon isn\'t melee weapon.\n           ret = CurrUcf + BonUcf\n           ret += GetJewleryEffect(jwACF, ret)\n         EndIf\n      Else\n         If _cinfo.cwield(wSecondary).classid = clWeapon Then\n            \'Wielding a melee weapon.\n            If _cinfo.cwield(wSecondary).weapon.weapontype \x3C\x3E wtProjectile Then\n               ret = CurrAcf + BonAcf\n               ret += GetJewleryEffect(jwACF, ret)\n            Else\n               \'Return UCF as projectile weapon isn\'t melee weapon.\n               ret = CurrUcf + BonUcf\n               ret += GetJewleryEffect(jwACF, ret)\n            End If\n         EndIf\n      End If\n   EndIf\n\n    Return ret     \nEnd Function\n}}}\n\x3C/div\x3E\nThis is the updated the melee combat factor. Notice that after we calculate the combat factor, we pass that to the /GetJewleryEffect/ function which either returns a value (or zero if no item or spell is active), which is then added to the base combat factor. The only thing we need to do here is to pass the relevant effect id for the appropriate combat factor. All of the other combat factors have been updated as well, and each calls the GetJewleryEffect function in this manner.\n\nThe last thing we need to do is code the two regeneration spells. We do that in /DoTimedEvents/.\n\n\x3Cdiv class="code"\x3E\n/character.bi:DoTimedEvents/\n{{{\n...\n   \'Check for necklaces and rings.\n   statamt = MaxHP\n   amt = GetJewleryEffect(jwRegenHP, statamt)\n   CurrHP = CurrHP + amt\n   statamt = MaxMana\n   amt = GetJewleryEffect(jwRegenMana, statamt)\n   CurrMana = CurrMana + amt\n...\n}}}\n\x3C/div\x3E\nRemember that /DoTimedEvents/ is called once per turn. /GetJewleryEffect/ is called just like in the combat factors and the returned value is added to both the health and mana totals. \n\nBelieve it or not, that is all we need to do to implement magic jewelry items. In the next chapter we will tackle the biggy: Spell Casting.\n',
'= 33: Spell Books\n\x3Cdiv align="center"\x3E\x3Cimg src=\'images/spellbook.png\' /\x3E\x3C/div\x3E\n\nWe have come to the point in the program where we can implement spells. Spells are actually going to be implemented in two sections; learning the spell by reading a spell book, and the actual casting of the spell. In this chapter we are going to go over the process of learning a new spell. In the next chapter, we will cover the casting of the spell. Before we get into the code in this chapter, let me give you an overview of the process of learning a new spell.\n\nA character starts the game without knowing any spells, and obviously, can\'t cast any spells until they are learned. To learn a new spell, the character must find a spell book and read it. When the character picks up a spell book it is initially unevaluated, and therefore cannot be read. Only evaluated spell books can be read. Once the book is successfully evaluated, the character can read the spell, which adds the spell to the character\'s spell list.\n\nIf the book contains a spell that the character already knows, the level of the spell is increased by 1. The level of the spell determines the power of the spell effect, so to get more powerful spells, the character will need to read books for existing spells, as well as reading books for new spells. However, to add a bit more interest to the game, some spell books are blank, but the character won\'t know it until the book is evaluated. This helps keeps the spell book process interesting. There is a 50% chance that a book will be blank, so the player will never know when they pick up a book whether it is just empty pages, or contains the spell they need.\n\nWhen we get into the code, you will see that spell books are just containers for spells. The spell book itself doesn\'t do anything, it just holds the spell until the character reads the book, and then the spell is transferred to the character\'s spell list. In order to make this a bit easier on us, spells are just another inventory object held in an array that is represented by the list of letters (capital) A through Z on the inventory screen. By making spells inventory objects, we can leverage our existing code and don\'t have to reinvent too many new wheels.\n\nSince a spell book is a container for a spell, let\'s look at the new spell list before we get into the spell book code.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\nEnum spellid\n   ...\n   splAcidFog     \'Spellbook: 5 dam over lvl turns\n   splFireCloak   \'Spellbook: Target gets 10 dam over lvl turns\n   splHeal        \'Spellbook: 1% x lvl\n   splMana        \'Spellbook: 1% x lvl (does not consume mana)\n   splRecharge    \'Spellbook: 1 x lvl recharge on wand\n   splFocus       \'Spellbook: +lvl to all combat factors for 1 turn\n   splLightning   \'Spellbook: 2 * lvl damage (ignores armor)\n   splBlind       \'Spellbook: Blinds target for lvl turns\n   splTeleport    \'Spellbook: Teleport to location lvl distance away (must be visible).\n   splOpen        \'Spellbook: Attempts to open a locked door (lvl vs. DR).\n   splFear        \'Spellbook: Makes monster flee for lvl turns.\n   splConfuse     \'Spellbook: Confuses monster for lvl turns.\n   splFireBomb    \'Spellbook: Area damage 20 x lvl. Sets monsters on fire lvl turns. \n   splEntangle    \'Spellbook: Immobilze target for lvel turns doing lvl damage each turn.\n   splCloudMind   \'Spellbook: Target cannot cast spells for lvl turns.\n   splFireball    \'Spellbook: Area damage 10 x lvl. Sets monsters on fire lvl turns.\n   splIceStatue   \'Spellbook: Freezes target for lvl turns. If frozen can be killed with single hit.\n   splRust        \'Spellbook: Reduces armor by lvl x 10%.\n   splShatter     \'Spellbook: Destroys target weapon, if any.\n   splMagicDrain  \'Spellbook: Lower target MDF by lvl% and adds to caster for 1 turn.\n   splPoison      \'Spellbook: Posions target 1 HP for lvl turns.\n   splEnfeeble    \'Spellbook: Lowers target combat factors lvl x 10%.\n   splShout       \'Spellbook: Stuns all visible monsters for lvl turns.\n   splStealHealth \'Spellbook: Lowers HP lvl% of target and adds to caster.\n   splMindBlast   \'Spellbook: Lowers target MCF and MDF lvl% for lvl turns.\n   splBlink       \'Spellbook: Teleport to random location lvl distance away.      \nEnd Enum\n}}}\n\x3C/div\x3E\nThis is the list of spells that the character will eventually be able to cast. There are 26 spells which match the 26 positions in the inventory screen. As the character learns spells, each position will be filled by a spell. \n\nSince we will be needing names and descriptions for the spells, we need to update the spell name and description functions.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Returns a description for a spell.\nFunction GetSpellName(spl As spellid) As String\n   Dim As String ret = ""\n   \n   Select Case spl\n      ...\n      Case splAcidFog     \'Spellbook: 5 dam over lvl turns\n         ret = "Spell of Acid Fog"\n      Case splFireCloak   \'Spellbook: Target gets 10 dam over lvl turns\n         ret = "Spell of Fire Cloak"\n      ...\n      Case Else\n         ret = "No spell."\n   End Select\n\n   Return ret\nEnd Function\n\n\'Returns spell effect. Used in spell descriptions.\nFunction GetSpellEffect(spl As spellid) As String\n   Dim As String ret = ""\n   \n   Select Case spl\n      ...\n      Case splAcidFog     \'Spellbook: 5 dam over lvl turns\n         ret = "Target receives 5 damage for level turns."\n      Case splFireCloak   \'Spellbook: Target gets 10 dam over lvl turns\n         ret = "Target receives 10 damage over level turns."\n      ...\n      Case Else\n         ret = "No effect."\n   End Select\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nI am only showing a couple of spells here just to give you an idea of the changes. In addition to these updates, a new function, /GetSpellShortName/ returns a short name for the spell. \n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Returns a description for a spell.\nFunction GetSpellShortName(spl As spellid) As String\n   Dim As String ret = ""\n   \n   Select Case spl\n      Case splMaxHealing  \'Heal to max HP.\n         ret = "Healing"\n      Case splStrongMeat \'Adds str bonus for time. \n         ret = "Strength"\n      Case splBreadLife   \'Cures any poison.\n         ret = "C Poison"\n      Case splMaxMana     \'Restore full mana.\n         ret = "R Mana"\n      Case splSerpentBite \'Weapon: Inflict poison damage.\n         ret = "S Bite"\n      Case splRend        \'Weapon: Decrease armor of target.\n         ret = "R Armor"\n      Case splSunder      \'Weapon: Decrease target weapon damage.\n         ret = "Sunder"\n      Case splReaper      \'Weapon: Causes monster to flee.\n         ret = "Reaper"\n      Case splFire        \'Weapon: Does fire damage to target for lvl turns.\n         ret = "Fire"\n      Case splGoliath     \'Weapon: Adds str to attack.\n         ret = "Goliath"\n      Case splStun        \'Weapon: Stuns target for lvl turns.\n         ret = "Stun"\n      Case splChaos       \'Weapon: Random amount of additonal damage.\n         ret = "Chaos"\n      Case splWraith      \'Weapon: Decreases random stat of target.\n         ret = "Wraith"\n      Case splThief       \'Weapon: Steals random stat from target and adds to character.\n         ret = "Phantom"\n      Case splNoSlash     \'Armor: +% from slash attacks.\n         ret = "Slash Pr"              \n      Case splNoCrush     \'Armor: +% from crush attacks.\n         ret = "Crush Pr"\n      Case splNoPierce    \'Armor: +% from pierce attacks.\n         ret = "Pierce P"\n      Case splNoEnergy    \'Armor: +% from energy attacks.\n         ret = "Energy P" \n      Case splNoMagic     \'Armor: +% from magic attacks.\n         ret = "Magic Pr"\n      Case splNoFire      \'Armor: +% from fire attacks.\n         ret = "Fire Res"\n      Case wdAcid         \'Armor: +% from acid attacks.\n         ret = "Res Acid"\n      Case splUCF         \'Jewelry: +% to UCF.\n         ret = "UCF"\n      Case splACF         \'Jewelry: +% to ACF.\n         ret = "ACF"\n      Case splPCF         \'Jewelry: +% to PCF.\n         ret = "PCF"\n      Case splMCF         \'Jewelry: +% to MCF.\n         ret = "MCF"\n      Case splCDF         \'Jewelry: +% to CDF.\n         ret = "CDF"\n      Case splMDF         \'Jewelry: +% to MDF.\n         ret = "MDF"\n      Case splRegenHP     \'Jewelry: +% to healing per turn.\n         ret = "Rgn Hlth"\n      Case splRegenMana   \'Jewelry: +% to mana per turn.\n         ret = "Rgn Mana" \n      Case splAcidFog     \'Spellbook: 5 dam over lvl turns\n         ret = "Acid Fog"\n      Case splFireCloak   \'Spellbook: Target gets 10 dam over lvl turns\n         ret = "F Cloak"\n      Case splHeal        \'Spellbook: 1% x lvl\n         ret = "Healing"\n      Case splMana        \'Spellbook: 1% x lvl (does not consume mana)\n         ret = "R Mana"\n      Case splRecharge    \'Spellbook: 1 x lvl recharge on wand\n         ret = "Rec Wand"\n      Case splFocus       \'Spellbook: +lvl to all combat factors for 1 turn\n         ret = "Focus"\n      Case splLightning   \'Spellbook: 2 * lvl damage (ignores armor)\n         ret = "Lightng"\n      Case splBlind       \'Spellbook: Blinds target for lvl turns\n         ret = "Blind"\n      Case splTeleport    \'Spellbook: Teleport to location lvl distance away (must be visible).\n         ret = "Teleport"\n      Case splOpen        \'Spellbook: Attempts to open a locked door (lvl vs. DR).\n         ret = "Op Door"\n      Case splFear        \'Spellbook: Makes monster flee for lvl turns.\n         ret = "Fear"\n      Case splConfuse     \'Spellbook: Confuses monster for lvl turns.\n         ret = "Confuse"\n      Case splFireBomb    \'Spellbook: Area damage 20 x lvl. Sets monsters on fire lvl turns.\n         ret = "F Bomb" \n      Case splEntangle    \'Spellbook: Immobilze target for lvel turns doing lvl damage each turn.\n         ret = "Entangle"\n      Case splCloudMind   \'Spellbook: Target cannot cast spells for lvl turns.\n         ret = "Cld Mind"\n      Case splFireball    \'Spellbook: Area damage 10 x lvl. Sets monsters on fire lvl turns.\n         ret = "F Ball"\n      Case splIceStatue   \'Spellbook: Freezes target for lvl turns. \n         ret = "I Statue"\n      Case splRust        \'Spellbook: Reduces armor by lvl x 10%.\n         ret = "Rust"\n      Case splShatter     \'Spellbook: Destroys target weapon, if any.\n         ret = "Shatter"\n      Case splMagicDrain  \'Spellbook: Lower target MDF by lvl% and adds to caster for 1 turn.\n         ret = "Dr Magic"\n      Case splPoison      \'Spellbook: Posions target 1 HP for lvl turns.\n         ret = "Poison"\n      Case splEnfeeble    \'Spellbook: Lowers target combat factors lvl x 10%.\n         ret = "Enfeeble"\n      Case splShout       \'Spellbook: Stuns all visible monsters for lvl turns.\n         ret = "Shout"\n      Case splStealHealth \'Spellbook: Lowers HP lvl% of target and adds to caster.\n         ret = "Vampire"\n      Case splMindBlast   \'Spellbook: Lowers target MCF and MDF lvl% for lvl turns.\n         ret = "M Blast" \n      Case splBlink       \'Spellbook: Teleport to random location lvl distance away. \n         ret = "Blink"     \n      Case Else\n         ret = "No spell."\n   End Select\n\n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nSince the inventory screen doesn\'t have room for the full spell names, we are using some abbreviated names to serve as mnemonics for the spells. The player can always Inspect a spell to see the full name and spell attributes. Notice that I have included all the spells, not just the spell book spells. Right now, we don\'t need short names for jewelry spells, for example, but if at some point want to display those we will be ready to do so.\n\nArmed with our spell list, we can now create some spell books.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Generate new spellbook item.\nSub GenerateSpellBook(inv As invtype, currlevel As Integer)\n   Dim item As spellbkids\n   \n   \'Set the class id.\n   inv.classid = clSpellBook\n   \'Two kinds of spell books, blank ones and one that contain a spell.\n   item = RandomRange(bkSpellBlank, bkSpellBook)\n   \'These are the common items.\n   inv.spellbook.evaldr = GetScaledFactor(charint, currlevel)\n   inv.spellbook.eval = FALSE\n   inv.spellbook.id = item\n   inv.iconclr = fbOrange\n   inv.icon = Chr(254)\n   inv.spellbook.use = useRead\n   inv.spellbook.noise = 1\n   inv.desc = "Spell Book"\n   \'If we have a non-blank, generate a spell for it.\n   If item = bkSpellBook Then\n      \'Get the spell.\n      inv.spellbook.spell.id = RandomRange(splAcidFog, splBlink) \'Spell id.\n      GenerateSpell inv.spellbook.spell, currlevel\n      inv.spellbook.spell.lvl = 1\n      inv.spellbook.spell.dam = 0 \n      \'Set damage/mana on spells.\n      Select Case inv.spellbook.spell.id\n         Case splAcidFog     \'Spellbook: 5 dam over lvl turns\n            inv.spellbook.spell.dam = 5\n            inv.spellbook.spell.manacost = 5  \n         Case splFireCloak   \'Spellbook: Target gets 10 dam over lvl turns\n            inv.spellbook.spell.dam = 10\n            inv.spellbook.spell.manacost = 10  \n         Case splLightning   \'Spellbook: 2 * lvl damage (ignores armor)\n            inv.spellbook.spell.dam = 2\n            inv.spellbook.spell.manacost = 6  \n         Case splFireBomb    \'Spellbook: Area damage 20 x lvl. Sets monsters on fire lvl turns.\n            inv.spellbook.spell.dam = 20 \n            inv.spellbook.spell.manacost = 20  \n         Case splEntangle    \'Spellbook: Immobilze target for lvel turns doing lvl damage each turn.\n            inv.spellbook.spell.dam = 4\n            inv.spellbook.spell.manacost = 4  \n         Case splFireball    \'Spellbook: Area damage 10 x lvl. Sets monsters on fire lvl turns.\n            inv.spellbook.spell.dam = 10\n            inv.spellbook.spell.manacost = 15  \n         Case splRust        \'Spellbook: Reduces armor by lvl x 10%.\n            inv.spellbook.spell.manacost = 8  \n         Case splMagicDrain  \'Spellbook: Lower target MDF by lvl% and adds to caster for 1 turn.\n            inv.spellbook.spell.manacost = 3  \n         Case splPoison      \'Spellbook: Posions target 1 HP for lvl turns.\n            inv.spellbook.spell.dam = 1\n            inv.spellbook.spell.manacost = 3  \n         Case splEnfeeble    \'Spellbook: Lowers target combat factors lvl x 10%.\n            inv.spellbook.spell.manacost = 9  \n         Case splHeal        \'Spellbook: 1% x lvl\n            inv.spellbook.spell.manacost = 4  \n         Case splMana        \'Spellbook: 1% x lvl (does not consume mana)\n            inv.spellbook.spell.manacost = 0  \n         Case splRecharge    \'Spellbook: 1 x lvl recharge on wand\n            inv.spellbook.spell.manacost = 2  \n         Case splFocus       \'Spellbook: +lvl to all combat factors for 1 turn\n            inv.spellbook.spell.manacost = 12  \n         Case splBlind       \'Spellbook: Blinds target for lvl turns\n            inv.spellbook.spell.manacost = 6  \n         Case splTeleport    \'Spellbook: Teleport to location lvl distance away (must be visible).\n            inv.spellbook.spell.manacost = 4  \n         Case splOpen        \'Spellbook: Attempts to open a locked door (lvl vs. DR).\n            inv.spellbook.spell.manacost = 2  \n         Case splFear        \'Spellbook: Makes monster flee for lvl turns.\n            inv.spellbook.spell.manacost = 4  \n         Case splConfuse     \'Spellbook: Confuses monster for lvl turns.\n            inv.spellbook.spell.manacost = 9  \n         Case splCloudMind   \'Spellbook: Target cannot cast spells for lvl turns.\n            inv.spellbook.spell.manacost = 12  \n         Case splIceStatue   \'Spellbook: Freezes target for lvl turns. If frozen can be killed with single hit.\n            inv.spellbook.spell.manacost = 14  \n         Case splShatter     \'Spellbook: Destroys target weapon, if any.\n            inv.spellbook.spell.manacost = 5  \n         Case splShout       \'Spellbook: Stuns all visible monsters for lvl turns.\n            inv.spellbook.spell.manacost = 12  \n         Case splStealHealth \'Spellbook: Lowers HP lvl% of target and adds to caster.\n            inv.spellbook.spell.manacost = 8  \n         Case splMindBlast   \'Spellbook: Lowers target MCF and MDF lvl% for lvl turns.\n            inv.spellbook.spell.manacost = 12  \n         Case splBlink       \'Spellbook: Teleport to random location lvl distance away. \n            inv.spellbook.spell.manacost = 4\n         Case Else\n            inv.spellbook.spell.splname = "Unknown Spell"\n            inv.spellbook.spell.splsname = "Unknown"\n      End Select\n   Else\n      ClearSpell inv.spellbook.spell\n   End If\nEnd Sub\n}}}\n\x3C/div\x3E\nAs you can see from the code, we are following the same pattern here as we have done with all the generation routines. We are using a new use flag here, /useRead/ which we have added to our /itemuse/ enumeration. We will use this flag to check for spellbooks when the player invokes the Read command on the inventory screen. \n\nJust in case things are not working as expected, we have an Else clause in the Select Case which will flag an errant spell and help us in tracking down the problem. We have implemented these in our other generation routines, and it is a good guard to put in place when you are developing the application. These guard statements don\'t really impact performance and don\'t take up any noticeable room, so should become standard in your programs. When you come back to the code to make enhancements, you\'ll know if something is amiss when a guard statement pops up.\n\nOf course, we need to update all the inventory subroutines and functions to handle the new spell book item, but we have looked at those many times before so I won\'t show them here. The real action happens in the inventory screen. \n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Manages character inventory.\nSub ManageInventory()\n   Dim As String kch, ich\n   Dim As Integer ret\n   \n   DrawInventoryScreen\n   Do\n      kch = InKey\n      kch = UCase(kch)\n      \'Check to see if we have a key.\n      If kch \x3C\x3E "" Then\n         \'Process the eval command.\n         If kch = "V" Then\n            ret = ProcessEval()\n            \'Screen changed.\n            If ret = TRUE Then\n               DrawInventoryScreen\n            EndIf\n         EndIf\n         \'Process the eat and drink command.\n         If kch = "E" Then\n            ret = ProcessEatDrink()\n            \'Screen changed.\n            If ret = TRUE Then\n               DrawInventoryScreen\n            EndIf\n         EndIf\n         \'Process drop command.\n         If kch = "D" Then\n            ret = ProcessDrop()\n            \'Screen changed.\n            If ret = TRUE Then\n               DrawInventoryScreen\n            EndIf\n         EndIf\n         \'Process inspect command.\n         If kch = "I" Then\n            ret = ProcessInspect()\n            \'Screen changed.\n            If ret = TRUE Then\n               DrawInventoryScreen\n            EndIf\n         EndIf\n         \'Process unequip item.\n         If kch = "U" Then\n            ret = ProcessUnequip()\n            \'Screen changed.\n            If ret = TRUE Then\n               DrawInventoryScreen\n            EndIf\n         EndIf\n         \'Process equip item.\n         If kch = "Q" Then\n            ret = ProcessEquip()\n            \'Screen changed.\n            If ret = TRUE Then\n               DrawInventoryScreen\n            EndIf\n         EndIf\n         \'Process the read item.\n         If kch = "R" Then\n            ret = ProcessRead()\n            \'Screen changed.\n            If ret = TRUE Then\n               DrawInventoryScreen\n            EndIf\n         EndIf\n      EndIf\n      Sleep 1\n   Loop Until kch = key_esc\n   ClearKeys\nEnd Sub\n}}}\n\x3C/div\x3E\nThe /ManageInventory/ subroutine handles the command keys for the inventory screen and we have added a new section for the Read command. /ProcessRead/ is where we actually read a spell book.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Process the read command.\nFunction ProcessRead() As Integer\n   Dim As String res, mask, desc\n   Dim As Integer i, iret, iitem, ret = FALSE\n   Dim As invtype inv\n   Dim As tWidgets.btnID btn\n   Dim As tWidgets.tInputbox ib\n      \n   \'Make sure there is something to evaluate in the inventory.\n   For i = pchar.LowInv To pchar.HighInv\n      iitem = pchar.HasInvItem(i)\n      If iitem = TRUE Then\n         \'Get the inv item.\n         pchar.GetInventoryItem i, inv\n         \'Is the item evaluated.\n         iret = MatchUse(inv, useRead)\n         \'An item to evaluate.\n         If iret = TRUE Then\n            \'Build the mask.\n            mask &= Chr(i)\n         End If\n      EndIf\n   Next\n   If Len(mask) = 0 Then\n      ShowMsg "Read", "Nothing to read.", tWidgets.MsgBoxType.gmbOK\n   Else\n      \'Draws an input box on screen.\n      ib.Title = "Read"\n      ib.Prompt = "Select item(s) to read (" & mask & ")"\n      ib.Row = 39\n      ib.EditMask = mask\n      ib.MaxLen = Len(mask)\n      ib.InputLen = Len(mask)\n      btn = ib.Inputbox(res)\n      \'Evaluate each item in the list.\n      If (btn \x3C\x3E tWidgets.btnID.gbnCancel) And (Len(res) \x3E 0) Then\n         ret = TRUE\n         \'Evaluate the list of items.\n         For i = 1 To Len(res)\n            iitem = Asc(res, i) \'Get index into character inventory.\n            \'Get the inv item.\n            pchar.GetInventoryItem iitem, inv\n            If IsEval(inv) = TRUE Then\n               desc = pchar.ApplyInvItem(inv) \n               ShowMsg "Read", desc, tWidgets.MsgBoxType.gmbOK    \n               \'Clear the item.\n               ClearInv inv\n               \'Put the item back into inventory.\n               pchar.AddInvItem iitem, inv\n            Else\n               ShowMsg "Read", "Cannot read unevaluated spell books.", tWidgets.MsgBoxType.gmbOK\n            End If\n         Next\n      EndIf\n   EndIf\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nAgain we are following the same pattern for reading a spell book as we are in all the other commands. We collect the items to use by checking the use flag, which in this case is /useRead/ in the statement {{{iret = MatchUse(inv, useRead)}}}. If the item is readable, the index of the item is saved in the list and we present the player with the available options.\n\nOnce the player makes a selection, we call the /ApplyInvItem/ in the character object to add or update the spell list. The ApplyInvItem was coded back when we added supplies to the game, and we are able to use that same routine here.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n\'Applies the inv type to the character.\nFunction character.ApplyInvItem(inv As invtype) As String\n   Dim As Integer evalstate, evaldr, amt, amt2, amt3, chk = FALSE\n   Dim As String ret\n   \n   \'Check the eval state.\n   evalstate =  IsEval(inv)\n   \'Check for magic item.\n   evalDR = GetEvalDR(inv)\n   \n   If inv.classid = clSupplies Then\n\n   ...\n\n   ElseIf inv.classid = clSpellBook Then\n      \'Check for blank spellbook.\n      If inv.spellbook.id = bkSpellBlank Then\n         ret = "The spell book is blank."\n      Else\n         \'First check to see if the spell is already in the list.\n         For i As Integer = 65 To 90\n            If _cinfo.cspells(i).spell.id = inv.spellbook.spell.id Then\n               \'Bump the level of the learned spell.\n               _cinfo.cspells(i).spell.lvl += inv.spellbook.spell.lvl\n               ret = "You gained a level in " & inv.spellbook.spell.splname & "!"\n               chk = TRUE\n               Exit For\n            EndIf\n         Next\n         \'Not in the list, so add it.\n         If chk = FALSE Then\n            \'Find empty slot.\n            For i As Integer = 65 To 90\n               If _cinfo.cspells(i).spell.id = splNone Then\n                  _cinfo.cspells(i).classid = clSpell\n                  _cinfo.cspells(i).spell.id = inv.spellbook.spell.id\n                  _cinfo.cspells(i).spell = inv.spellbook.spell\n                  ret = "You learned " & inv.spellbook.spell.splname & "!"\n                  Exit For\n               EndIf\n            Next\n         EndIf\n      End If\n   EndIf\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nThe first thing we do is to check for a blank spell book. If it is blank, we let the player know and exit the function. If it isn\'t blank, we then check to see if the spell is already in the character\'s spell list. Notice that we are using the indexes 65 to 90, which correspond to the letters (capital) A to Z. This is the technique we used in the main inventory list to simplify the access of the inventory items. We are using the same technique here.\n\nIf the spell contained in the spell book is already in the character\'s list, we simply bump the spell\'s level by one with the code {{{_cinfo.cspells(i).spell.lvl += inv.spellbook.spell.lvl}}}. This is how the player will be able to increase the power of a particular spell. We also set a check flag to let us know that the spell was found with the code {{{chk = TRUE}}}. We will check this when we exit the first loop.\n\nBut what about the spell list itself? This is contained within the character object.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n\'Character attribute type def.\nType characterinfo\n   ...\n   cinv(97 To 122) As invtype \'Character inventory-using ascii codes a-z for index values.\n   cspells(65 To 90) As invtype \'Character spells-using ascii codes A-Z for index values.\n   ...\nEnd Type\n}}}\n\x3C/div\x3E\nNotice that we have set it up just like the inventory list and it will operate in the same fashion.\n\n\x3Cdiv class="code"\x3E\n/character.bi:ApplyInvItem/\n{{{\n...\n         \'Not in the list, so add it.\n         If chk = FALSE Then\n            \'Find empty slot.\n            For i As Integer = 65 To 90\n               If _cinfo.cspells(i).spell.id = splNone Then\n                  _cinfo.cspells(i).classid = clSpell\n                  _cinfo.cspells(i).spell = inv.spellbook.spell\n                  ret = "You learned " & inv.spellbook.spell.splname & "!"\n                  Exit For\n               EndIf\n            Next\n         EndIf\n...\n}}}\n\x3C/div\x3E\nIf we get to this point in the function we know that the spell isn\'t in the character\'s spell list so we need to add it. The first thing we do is to find an empty slot. When we have found a slot, we then set the slot class id to /clSpell/ and transfer the spell information from the spell book to the spell inventory item using the code {{{_cinfo.cspells(i).spell = inv.spellbook.spell}}}. Both of these items are spell Type Defs we can just assign one to the other. This adds the new spell to the character\'s spell list.\n\nThe last thing to look at is the inventory definition, which now contain spells.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Inventory type.\nType invtype\n   classid As classids     \'This indicates what class is in the union.\n   desc As String * 30     \'Plain text description.\n   icon As String * 1      \'This is the item icon.\n   iconclr As UInteger     \'This is the item\'s icon color.\n   Union                   \'Union of item types.\n      gold As goldtype     \'Gold coins. \n      supply As supplytype \'Supplies.\n      armor As armortype   \'Armor\n      shield As shieldtype \'Shield\n      weapon As weapontype \'Weapon\n      ammo As ammotype     \'Ammo for projectile weapons.\n      potion As pottype    \'Potion.\n      jewelry As jewelrytype \'Rings and necklaces\n      spellbook As spellbktype \'Spell book.\n      spell As spelltype     \'Spells\n   End Union\nEnd Type\n}}}\n\x3C/div\x3E\nNotice that our Union now contains a spell field which is the field we use for the character\'s spell list. Of course we have also added the class id to the list of classids.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Item class ids.\nEnum classids\n   clNone\n   clGold\n   clSupplies \n   clArmor\n   clShield\n   clWeapon\n   clAmmo\n   clPotion\n   clRing\n   clNecklace\n   clSpellBook\n   clSpell\nEnd Enum\n}}}\n\x3C/div\x3E\nThe only change we made to the spell type was to add the new spell short name.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Spell info type.\nType spelltype\n   id As spellid          \'The spell id.\n   lvl As Integer         \'Spell level.\n   splname As String * 30 \'Spell name.\n   splsname As String * 8 \'Spell short name.\n   spldesc As String * 60 \'Spell desc.\n   manacost As Integer    \'Cost in mana.\n   dam As Integer         \'The amount of damage the spell does.\nEnd Type\n}}}\n\x3C/div\x3E\nSince we were able to use our existing code base, we didn\'t need to add a lot of new code to have spell books in the game. The reason is that we have set up the code to handle inventory items, and by making spell books and spells just two more inventory items, we can simply plug them in and we are good to go on with the program. I hope you can see that the time we spent to set up our inventory system has paid us big dividends. When we want to add new items to the game, even new spells, it will be just as easy as adding spell books to the game.\n\nNow that the character can learn new spells, the next step is casting them.',
'= 34: Spell Casting\n\x3Cdiv align="center"\x3E\x3Cimg src=\'images/spells.png\' /\x3E\x3C/div\x3E\n\nNow that spell books are in the game we can turn our attention to casting those spells. To cast a spell, the player uses the "c" key, which executes the following code in the main game loop.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Cast a spell.\nIf ckey = "c" Then\n  CastSpell\n  level.MoveMonsters\n  DrawMainScreen\nEndIf\n}}}\n\x3C/div\x3E\nAs you can see we have added a new subroutine to dod.bas called /CastSpell/.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Cast a spell.\nSub CastSpell ()\n   Dim As Integer splcnt, i, iitem, ret, mc, md, rollm, rollp \n   Dim As Integer tmp, snd, cancel = FALSE\n   Dim As invtype sinv, iinv\n   Dim As tWidgets.listtype splist()\n   Dim As tWidgets.btnID btn\n   Dim As tWidgets.tList lst\n   Dim As vec vt, pt\n   Dim tid As terrainids\n   \n   \'Check the spell list.\n   For i = pchar.LowISpell To pchar.HighISpell\n      iitem = pchar.HasInvItem(i)\n      If iitem = TRUE Then\n         \'Get the inventory item.\n         pchar.GetInventoryItem i, sinv\n         \'Add the spell name to the list.\n         splcnt += 1\n         ReDim Preserve splist(1 To splcnt)\n         \'Use the index as the id.\n         splist(splcnt).id = i\n         splist(splcnt).text = sinv.spell.splname \n      EndIf\n   Next\n   \'Make sure we have some spells.\n   If splcnt \x3E 0 Then\n      \'Set the id to 0.\n      iitem = 0\n      \'Set up the list window.\n      lst.Title = "Select Spell to Cast"\n      lst.Prompt = "Use Up or Dn key to cycle spells, Enter to select."\n      \'Get the player selection.\n      btn = lst.Listbox(splist(), iitem)\n      \'Check to make sure the player didn\'t cancel.\n      If (btn \x3C\x3E tWidgets.gbnCancel) And (iitem \x3C\x3E 0) Then\n         \'Get the inventory item based on the returned id.\n         pchar.GetInventoryItem iitem, sinv\n         \'Make sure the character has enough mana to cast spell.\n         If sinv.spell.manacost \x3E pchar.CurrMana Then\n            ShowMsg "Mana", "You do not have enough Mana to cast spell.", tWidgets.MsgBoxType.gmbOK\n         Else\n            \'Check for target spell.\n            ret = splSet.IsMember(sinv.spell.id)\n            \'This is a target spell.\n            If ret = TRUE then\n               \'Get the target coordinates.\n               ret = GetTargetCoord(vt)\n               \'Make sure player selected target.\n               If ret = TRUE Then\n                  \'Make sure we have a valid target.\n                  If level.IsMonster(vt.vx, vt.vy) = FALSE Then\n                     ShowMsg "Target", "Nothing to target!", tWidgets.MsgBoxType.gmbOK\n                  Else\n                     \'Animate the spell attack.\n                     pt.vx = pchar.Locx\n                     pt.vy = pchar.Locy\n                     level.AnimateProjectile pt, vt\n                     \'Get the monster magic defense factor.\n                     md = level.GetMonsterMagicDefense(vt.vx, vt.vy)\n                     \'Get the character magic offense factor.\n                     mc = pchar.CurrMcf + pchar.BonMcf\n                     \'Roll for attack.\n                     rollp = RandomRange(1, mc) \'offense\n                     rollm = RandomRange(1, md) \'defense\n                     \'Did the character hit the target?\n                     If rollp \x3E rollm Then\n                        \'Apply spell to monster.\n                        ret = level.ApplySpell(sinv.spell, vt.vx, vt.vy)\n                     Else\n                        \'Message miss.\n                        PrintMessage "The " & level.GetMonsterName(vt.vx, vt.vy) & " dispels the " & sinv.spell.splname & "!"\n                     EndIf\n                  EndIf\n               End If\n            Else\n               Select Case sinv.spell.id\n                  Case splHeal\n                     tmp = pchar.MaxHP * (sinv.spell.lvl / 100)\n                     If tmp \x3C 1 Then tmp = 1\n                     pchar.CurrHP = pchar.CurrHP + tmp\n                  Case splMana\n                     tmp = pchar.MaxMana * (sinv.spell.lvl / 100)\n                     If tmp \x3C 1 Then tmp = 1\n                     pchar.CurrMana = pchar.CurrMana + tmp\n                  Case splRecharge\n                     \'Look for wands in inventory and recharge based on level.\n                     For i = pchar.LowInv To pchar.HighInv\n                        iitem = pchar.HasInvItem(i)\n                        If iitem = TRUE Then\n                           \'Get the inventory item.\n                           pchar.GetInventoryItem i, iinv\n                           \'Check to see if it is a weapon.\n                           If iinv.classid = clWeapon Then\n                              \'Check to see if it is a wand.\n                              If iinv.weapon.iswand = TRUE Then\n                                 \'Increase the ammocnt (charges).\n                                 iinv.weapon.ammocnt += sinv.spell.lvl\n                                 \'Make sure we don\'t go over the capacity.\n                                 If iinv.weapon.ammocnt \x3E iinv.weapon.capacity Then\n                                    iinv.weapon.ammocnt = iinv.weapon.capacity\n                                 EndIf\n                                 \'Put the item back into the inventory.\n                                 pchar.AddInvItem iitem, iinv\n                              EndIf\n                           EndIf\n                        End If\n                     Next\n                     \'Check held positons and charge those too if carried.\n                     iitem = pchar.HasInvItem(wPrimary)\n                     If iitem = TRUE Then\n                        pchar.GetInventoryItem wPrimary, iinv\n                        If iinv.classid = clWeapon Then\n                           \'Check to see if it is a wand.\n                           If iinv.weapon.iswand = TRUE Then\n                              iinv.weapon.ammocnt += sinv.spell.lvl\n                              If iinv.weapon.ammocnt \x3E iinv.weapon.capacity Then\n                                 iinv.weapon.ammocnt = iinv.weapon.capacity\n                              EndIf\n                              pchar.AddInvItem wPrimary, iinv\n                           EndIf\n                        EndIf\n                     EndIf\n                     \'Check held positons and charge those too if carried.\n                     iitem = pchar.HasInvItem(wSecondary)\n                     If iitem = TRUE Then\n                        pchar.GetInventoryItem wSecondary, iinv\n                        If iinv.classid = clWeapon Then\n                           \'Check to see if it is a wand.\n                           If iinv.weapon.iswand = TRUE Then\n                              iinv.weapon.ammocnt += sinv.spell.lvl\n                              If iinv.weapon.ammocnt \x3E iinv.weapon.capacity Then\n                                 iinv.weapon.ammocnt = iinv.weapon.capacity\n                              EndIf\n                              pchar.AddInvItem wSecondary, iinv\n                           EndIf\n                        EndIf\n                     EndIf\n                  Case splFocus\n                     \'Increase all combat factors for 1 turn.\n                     pchar.BonUcf = sinv.spell.lvl\n                     pchar.BonUcfCnt = 1\n                     pchar.BonAcf = sinv.spell.lvl\n                     pchar.BonAcfCnt = 1\n                     pchar.BonPcf = sinv.spell.lvl\n                     pchar.BonPcfCnt = 1\n                     pchar.BonCdf = sinv.spell.lvl\n                     pchar.BonCdfCnt = 1\n                     pchar.BonMcf = sinv.spell.lvl\n                     pchar.BonMcfCnt = 1\n                     pchar.BonMdf = sinv.spell.lvl\n                     pchar.BonMdfCnt = 1\n                  Case splTeleport\n                     \'Get the target coordinates.\n                     ret = GetTargetCoord(vt, sinv.spell.lvl)\n                     If ret = TRUE Then\n                        \'Check to see if a monster is at location.\n                        If level.IsMonster(vt.vx, vt.vy) = TRUE Then\n                           \'Teleport to mosnter killing it.\n                           ret = level.ApplySpell(sinv.spell, vt.vx, vt.vy)\n                           \'Set the new character position.\n                           pchar.Locx = vt.vx\n                           pchar.Locy = vt.vy\n                           \'Generate the sound map.\n                           level.ClearSoundMap\n                           snd = pchar.GetNoise()\n                           level.GenSoundMap(pchar.Locx, pchar.Locy, snd)\n                        Else\n                           \'Make sure it isn\'t a blocking tile.\n                           If level.IsBlocking(vt.vx, vt.vy) = FALSE Then\n                              \'Set the new character position.\n                              pchar.Locx = vt.vx\n                              pchar.Locy = vt.vy\n                              \'Generate the sound map.\n                              level.ClearSoundMap\n                              snd = pchar.GetNoise()\n                              level.GenSoundMap(pchar.Locx, pchar.Locy, snd)\n                           Else\n                              ShowMsg "Teleport", "You can\'t teleport there.", tWidgets.MsgBoxType.gmbOK\n                              cancel = TRUE\n                           End If   \n                        EndIf\n                     Else\n                        cancel = TRUE\n                     EndIf\n                  Case splOpen\n                     \'Get the target coordinates.\n                     ret = GetTargetCoord(vt, sinv.spell.lvl)\n                     If ret = TRUE Then\n                        \'Get the terrain id.\n                        tid = level.GetTileID(vt.vx, vt.vy)\n                        \'Check for closed door.\n                        If tid = tDoorClosed Then\n                           \'See if it is locked.\n                           If level.IsDoorLocked(vt.vx, vt.vy) = TRUE Then\n                              ret = level.OpenLockedDoor(vt.vx, vt.vy, sinv.spell.lvl)\n                              If ret = TRUE Then\n                                 PrintMessage "Door was opened."\n                              EndIf\n                           Else\n                              \'Tell player door isn\'t locked.\n                              ShowMsg "Open Spell", "The door is not locked.", tWidgets.MsgBoxType.gmbOK\n                              cancel = TRUE\n                           EndIf\n                        EndIf\n                     Else\n                        cancel = TRUE\n                     End If\n                  Case splBlink\n                     \'Set the blink efect.\n                     pchar.SetSpellEffect sinv.spell.id, sinv.spell.lvl, 0\n               End Select\n            EndIf\n            If cancel = FALSE Then\n               \'Deduct mana cost.\n               pchar.CurrMana = pchar.CurrMana - sinv.spell.manacost\n            End If\n         EndIf\n      EndIf\n   Else\n      ShowMsg "Spells", "You have not learned any spells.", tWidgets.MsgBoxType.gmbOK\n   EndIf\n   \nEnd Sub\n}}}\n\x3C/div\x3E\nAs you can see there is a lot going on here, although most of it is just getting the spell information to pass along to other subroutines. Let\'s look at each section to get a better feel for what is going on here.\n\n\x3Cdiv class="code"\x3E\n/dod.bas:CastSpell/\n{{{\n...\n   \'Check the spell list.\n   For i = pchar.LowISpell To pchar.HighISpell\n      iitem = pchar.HasInvItem(i)\n      If iitem = TRUE Then\n         \'Get the inventory item.\n         pchar.GetInventoryItem i, sinv\n         \'Add the spell name to the list.\n         splcnt += 1\n         ReDim Preserve splist(1 To splcnt)\n         \'Use the index as the id.\n         splist(splcnt).id = i\n         splist(splcnt).text = sinv.spell.splname \n      EndIf\n   Next\n...\n}}}\n\x3C/div\x3E\nIn this For-Next loop we are looking at each spell inventory slot and checking to see if the slot has a spell in it. We are using two new properties in the character object, /pchar.LowISpell/ and /pchar.HighISpell/, which return the LBound and Ubound of the array.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n\'Returns the low index of spell array.\nProperty character.LowISpell() As Integer\n   Return LBound(_cinfo.cspells)\nEnd Property\n\n\'Returns the high index of spell array.\nProperty character.HighISpell() As Integer\n   Return UBound(_cinfo.cspells)\nEnd Property\n}}}\n\x3C/div\x3E\nIn the For loop we check to see if the character has a spell in the current slot using the /HasInvItem/ function, which have looked at before. If the slot has a spell, we grab the spell inventory object using /GetInventoryItem/. The next section of code copies the information from the inventory object to the /splist/, which a Type Def defined in the tWidgets object. This will be passed to the tWidgets List object so that we can display the list of spells to the player so they can make a selection.\n\n\x3Cdiv class="code"\x3E\n/dod.bas:CastSpell/\n{{{\n...\n   \'Make sure we have some spells.\n   If splcnt \x3E 0 Then\n      \'Set the id to 0.\n      iitem = 0\n      \'Set up the list window.\n      lst.Title = "Select Spell to Cast"\n      lst.Prompt = "Use Up or Dn key to cycle spells, Enter to select."\n      \'Get the player selection.\n      btn = lst.Listbox(splist(), iitem)\n      \'Check to make sure the player didn\'t cancel.\n      If (btn \x3C\x3E tWidgets.gbnCancel) And (iitem \x3C\x3E 0) Then\n         \'Get the inventory item based on the returned id.\n         pchar.GetInventoryItem iitem, sinv\n         \'Make sure the character has enough mana to cast spell.\n         If sinv.spell.manacost \x3E pchar.CurrMana Then\n            ShowMsg "Mana", "You do not have enough Mana to cast spell.", tWidgets.MsgBoxType.gmbOK\n         Else\n...\n}}}\n\x3C/div\x3E\nThe first thing we do here is to make sure that we have some spells to display using the /splcnt/ variable. If we do then we set the List object parameters and pass the /splist()/ array which will display the list of spells to the player. The player has the option of the selecting a spell or hitting Escape to cancel the operation. (The List object will be reviewed in the appendix, along with the other tWidget objects.)\n\nIf the player selects a spell, we grab the inventory item using the returned index which is stored in /iitem/. We then check to see if the character has enough mana to cast the spell. Each spell takes a certain amount of mana, so the current mana amount must be enough to fuel the spell.\n\n\x3Cdiv class="code"\x3E\n/dod.bas:CastSpell/\n{{{\n...\n   \'Check for target spell.\n   ret = splSet.IsMember(sinv.spell.id)\n...\n}}}\n\x3C/div\x3E\nThere are two type of spells, attack spells and buff spells. Attack spells do damage to the target, while buff spells enhance character attributes, such as healing or improve combat stats. Notice that we have created a new object to store the attack spell ids, and we check to see if the returned spell is in the list. This object acts like a set, hence the /IsMember/ function. The new object is contained in /set.bi/.\n\n\x3Cdiv class="code"\x3E\n/set.bi/\n{{{\n\'Check for existing defines.\n#Ifndef NULL\n#Define NULL 0\n#EndIf\n#Ifndef FALSE\n#Define FALSE 0\n#Define TRUE (Not FALSE)\n#EndIf\n\nType setobj\n   Private:\n   _set As Integer Ptr \'The actual set.\n   _setcnt As Integer  \'The number of elements in the set.\n   Declare Sub _DestroySet() \'Clears object and returns memory.\n   Public:\n   Declare Constructor ()\n   Declare Destructor ()\n   Declare Function AddToSet (item As Integer)As Integer \'Returns TRUE if item is added.\n   Declare Function IsMember(item As integer) As Integer \'Returns TRUE if item is in the set.\nEnd Type\n\n\'This clears the object and releases memory.\nSub setobj._DestroySet()\n   If _set \x3C\x3E NULL Then\n      DeAllocate _set\n      _set = NULL\n   EndIf\nEnd Sub\n\'Constructor doesn\'t do much at the moment.\nConstructor setobj ()\n   _DestroySet\nEnd Constructor\n\n\'Calls the destroy method to clear the object.\nDestructor setobj ()\n   _DestroySet\nEnd Destructor\n\n\'Returns TRUE if item is added.\nFunction setobj.AddToSet (item As Integer)As Integer\n   Dim As Integer ret = TRUE\n   \n   If _set = NULL Then\n      _setcnt += 1\n      _set = Callocate(_setcnt, SizeOf(Integer))\n      _set[_setcnt - 1] = item \n   Else\n      \'Check for existing item.\n      For i As Integer = 0 To _setcnt - 1\n         If _set[i] = item Then\n            ret = FALSE\n            Exit For\n         EndIf\n      Next\n      \'If not found add it.\n      If ret = TRUE Then\n         _setcnt += 1\n         _set = ReAllocate(_set, _setcnt * SizeOf(Integer))\n         If _set \x3C\x3E NULL Then\n            _set[_setcnt - 1] = item\n         EndIf\n      EndIf\n   EndIf\n   \n   Return ret\nEnd Function\n\n\'Returns TRUE if item is in the set.\nFunction setobj.IsMember(item As integer) As Integer\n   Dim As Integer ret = FALSE\n   \n   If _set \x3C\x3E NULL Then\n      For i As Integer = 0 To _setcnt - 1\n         If _set[i] = item Then\n            ret = TRUE\n            Exit For\n         EndIf\n      Next\n   EndIf\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nThis object is quite simple. The set, which is just a pointer array of integers, is contained in the private variable /_set/. The /Constructor/ and /Destructor/ both call /_DestroySet/ to clear any existing members and release the allocated memory. \n\nThe method /AddToSet/ adds a new element to the set. We are using a pointer array here because we can\'t use dynamic arrays in Type Defs. By using a pointer array, we can simulate a dynamic array, but we have to manage the memory ourselves. AddToSet uses /ReAllocate/ to expand the pointer array and the new element is added. Before we do that though, we first check to make sure that the passed item isn\'t already in the set. Each item in the set must be unique, and it doesn\'t make any sense to have duplicate items in the set anyway. The main purpose of the set object is to hold a collection of items so that we can identify a member of the collection when we come across it in the code, as we saw in the CastSpell subroutine. Of course we need to load the set first, and we do that in /InitTargetSpells/.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Initialzes the target spell set.\nSub InitTargetSpells()\n   Dim As Integer ret\n   \n   \'Add target spells to spell set.\n   ret = splSet.AddToSet(splAcidFog)     \n   ret = splSet.AddToSet(splFireCloak)   \n   ret = splSet.AddToSet(splLightning)   \n   ret = splSet.AddToSet(splBlind)       \n   ret = splSet.AddToSet(splFear)        \n   ret = splSet.AddToSet(splConfuse)     \n   ret = splSet.AddToSet(splFireBomb)     \n   ret = splSet.AddToSet(splEntangle)    \n   ret = splSet.AddToSet(splCloudMind)   \n   ret = splSet.AddToSet(splFireball)    \n   ret = splSet.AddToSet(splIceStatue)   \n   ret = splSet.AddToSet(splRust)        \n   ret = splSet.AddToSet(splShatter)     \n   ret = splSet.AddToSet(splMagicDrain)  \n   ret = splSet.AddToSet(splEnfeeble)    \n   ret = splSet.AddToSet(splStealHealth) \n   ret = splSet.AddToSet(splMindBlast)\nEnd Sub\n}}}\n\x3C/div\x3E\nAs you can see we have added all the target spells to the set. This subroutine is called in the main routine during the program initialization.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Using 640x480 32bit screen with 80x60 text.\nScreenRes sw, sh, 32\nWidth txcols, txrows\nWindowTitle "Dungeon of Doom"\nRandomize Timer \'Seed the rnd generator.\ntWidgets.InitWidgets \'Initialzie the widgets.\n\n\'Init the target spell set.\nInitTargetSpells\n\'Draw the title screen.\nDisplayTitle\n}}}\n\x3C/div\x3E\nThe set object itself is defined in /def.bi/ as a shared variable.\n\n\x3Cdiv class="code"\x3E\n/defs.bi/\n{{{\n\'Message list.\nDim Shared mess(1 To 4) As String\nDim Shared messcolor(1 To 4) As UInteger = {fbWhite, fbWhite1, fbWhite2, fbWhite3}\n\'Spell set.\nDim Shared splSet As setobj\n}}}\n\x3C/div\x3E\nIt may seem like overkill to create an object for this, since we could just use an array and do the same thing. But what if at some point we need a different set? Or severals sets? We could end up managing half a dozen arrays, which would be quite messy. By using an object, we do not need to worry about any of that. No matter how many sets we may need, we just create a new set object, load it up and we are good to go. Even if we don\'t need any more sets, the amount of code in the obect isn\'t any more than imnplementing an array, so we are still better off using the object.\n\nGetting back to the CastSpell subroutine, once we determine that the selected spell is a target spell, we need to get a target.\n\n\x3Cdiv class="code"\x3E\n/dod.bas:CastSpell/\n{{{\n...\n   \'Get the target coordinates.\n   ret = GetTargetCoord(vt)\n   \'Make sure player selected target.\n   If ret = TRUE Then\n     \'Make sure we have a valid target.\n     If level.IsMonster(vt.vx, vt.vy) = FALSE Then\n       ShowMsg "Target", "Nothing to target!", tWidgets.MsgBoxType.gmbOK\n     Else\n       \'Animate the spell attack.\n       pt.vx = pchar.Locx\n       pt.vy = pchar.Locy\n       level.AnimateProjectile pt, vt\n...\n}}}\n\x3C/div\x3E\nWe are using the same target code we created when we implemented projectile combat. Having the target selection as a separate function allows us to reuse the code, which is always a good thing. Just as we did with projectiles, we animate the spell attack to give the player some feedback that the spell was actually cast.\n\n\x3Cdiv class="code"\x3E\n/dod.bas:CastSpell/\n{{{\n...\n       \'Get the monster magic defense factor.\n       md = level.GetMonsterMagicDefense(vt.vx, vt.vy)\n       \'Get the character magic offense factor.\n       mc = pchar.CurrMcf + pchar.BonMcf\n       \'Roll for attack.\n       rollp = RandomRange(1, mc) \'offense\n       rollm = RandomRange(1, md) \'defense\n       \'Did the character hit the target?\n       If rollp \x3E rollm Then\n         \'Apply spell to monster.\n         ret = level.ApplySpell(sinv.spell, vt.vx, vt.vy)\n       Else\n         \'Message miss.\n         PrintMessage "The " & level.GetMonsterName(vt.vx, vt.vy) & " dispels the " & sinv.spell.splname & "!"\n       EndIf\n     EndIf\n   End If\n...\n}}}\n\x3C/div\x3E\nIn this section of code we determine if the spell hits or misses. Magic attack and defense use the magic combat factors, so we get the monster magic defense and the character magic combat and see who has the higher roll. Remember that ties go to the defender, so the character must have a higher roll than the monster. If the character hits, we then pass the spell information to the /ApplySpell/ function in the level object. We created ApplySpell when we implemented item magic, so we just need to add in the attack spells.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n         Case splStealHealth\n            ret = ApplyDamage(mx, my, dam)\n            pchar.CurrHP = pchar.CurrHP + dam\n            txt = "Steal Health Spell stole  " & dam & " health from " & _level.moninfo(midx).mname & "."\n            PrintMessage txt\n            _level.moninfo(midx).mcolor = fbMagenta\n         Case splLightning\n            ret = ApplyDamage(mx, my, spl.dam)\n            txt = "Lightning Spell inflicted  " & spl.dam & " damage to " & _level.moninfo(midx).mname & "."\n            PrintMessage txt\n            _level.moninfo(midx).mcolor = fbMagenta\n         Case splAcidFog     \'Spellbook: 5 dam over lvl turns\n            ret = ApplyDamage(mx, my, dam)\n            \'If not dead set the timed flag.\n            If ret = FALSE Then\n               _level.moninfo(midx).effects(meAcidFog).cnt = spl.lvl\n               _level.moninfo(midx).effects(meAcidFog).dam = dam\n            End If\n            txt = "Acid Fog Spell inflicted  " & dam & " damage to " & _level.moninfo(midx).mname & " for " & spl.lvl & " turns."\n            PrintMessage txt\n            _level.moninfo(midx).mcolor = fbMagenta\n         Case splFireCloak   \'Spellbook: Target gets 10 dam over lvl turns\n            ret = ApplyDamage(mx, my, dam)\n            \'If not dead set the timed flag.\n            If ret = FALSE Then\n               _level.moninfo(midx).effects(meFire).cnt = spl.lvl\n               _level.moninfo(midx).effects(meFire).dam = dam\n            End If\n            txt = "Fire Cloak Spell inflicted  " & dam & " damage to " & _level.moninfo(midx).mname & " for " & spl.lvl & " turns."\n            PrintMessage txt\n            _level.moninfo(midx).mcolor = fbMagenta\n         Case splBlind       \'Spellbook: Blinds target for lvl turns\n            txt = _level.moninfo(midx).mname & " is blinded for " & spl.lvl & " turns."\n            _level.moninfo(midx).effects(meBlind).cnt = spl.lvl\n            _level.moninfo(midx).effects(meBlind).dam = dam\n            PrintMessage txt\n            _level.moninfo(midx).mcolor = fbMagenta\n         Case splFear        \'Spellbook: Makes monster flee for lvl turns.\n            txt = _level.moninfo(midx).mname & " is filled with fear for " & spl.lvl & " turns."\n            _level.moninfo(midx).effects(meFear).cnt = spl.lvl\n            _level.moninfo(midx).effects(meFear).dam = dam\n            PrintMessage txt\n            _level.moninfo(midx).mcolor = fbMagenta\n         Case splConfuse     \'Spellbook: Confuses monster for lvl turns.\n            txt = _level.moninfo(midx).mname & " is confused for " & spl.lvl & " turns."\n            _level.moninfo(midx).effects(meConfuse).cnt = spl.lvl\n            _level.moninfo(midx).effects(meConfuse).dam = spl.lvl\n            PrintMessage txt\n            _level.moninfo(midx).mcolor = fbMagenta\n         Case splFireBomb, splFireBall    \'Spellbook: Area damage 20 (or 10) x lvl. Sets monsters on fire lvl turns.\n            \'Check to make sure monster isn\'t already on fire. \'Prevents infinite loop.\n            If _level.moninfo(midx).effects(meFire).cnt \x3C 1 Then\n               ret = ApplyDamage(mx, my, dam * spl.lvl)\n               \'If not dead set the timed flag.\n               If ret = FALSE Then\n                  _level.moninfo(midx).effects(meFire).cnt = spl.lvl\n                  _level.moninfo(midx).effects(meFire).dam = dam\n               End If\n               \'Check for spell type.\n               If spl.id = splFireBomb Then\n                  txt = "Fire Bomb Spell inflicted  " & dam & " damage to " & _level.moninfo(midx).mname & "."\n               Else\n                  txt = "Fire Ball Spell inflicted  " & dam & " damage to " & _level.moninfo(midx).mname & "."\n               End If\n               PrintMessage txt\n               \'We will call this recursively to hit any monsters next to target.\n               \'This will cause a chain reaction hitting all monsters next to\n               \'each other on the map.\n               For i As compass = north To nwest\n                  \'Set ititial position.\n                  vm.vx = mx\n                  vm.vy = my\n                  \'Get new position.\n                  vm += i\n                  \'Recusively call function.\n                  tmp = ApplySpell(spl, vm.vx, vm.vy)\n               Next\n            Else\n               txt = _level.moninfo(midx).mname & " is already on fire."\n               PrintMessage txt\n            _level.moninfo(midx).mcolor = fbMagenta\n            End If\n         Case splEntangle    \'Spellbook: Immobilze target for lvel turns doing lvl damage each turn.\n            ret = ApplyDamage(mx, my, dam)\n            \'If not dead set the timed flag.\n            If ret = FALSE Then\n               _level.moninfo(midx).effects(meEntangle).cnt = spl.lvl\n               _level.moninfo(midx).effects(meEntangle).dam = dam\n            End If\n            txt = "Entangle Spell inflicted  " & dam & " damage to " & _level.moninfo(midx).mname & " for " & spl.lvl & " turns."\n            PrintMessage txt\n            _level.moninfo(midx).mcolor = fbMagenta\n         Case splCloudMind   \'Spellbook: Target cannot cast spells for lvl turns.\n            txt = _level.moninfo(midx).mname & "mind is clouded for " & spl.lvl & " turns."\n            _level.moninfo(midx).effects(meEntangle).cnt = spl.lvl\n            _level.moninfo(midx).effects(meEntangle).dam = dam\n            PrintMessage txt\n            _level.moninfo(midx).mcolor = fbMagenta\n         Case splIceStatue   \'Spellbook: Freezes target for lvl turns. If frozen can be killed with single hit.\n            _level.moninfo(midx).effects(meIceStatue).cnt = spl.lvl\n            _level.moninfo(midx).effects(meIceStatue).dam = dam\n            txt = _level.moninfo(midx).mname & " is frozen for " & spl.lvl & " turns."\n            PrintMessage txt\n            _level.moninfo(midx).mcolor = fbMagenta\n         Case splRust        \'Spellbook: Reduces armor by lvl x 10%.\n            pct = spl.lvl * .10\n            _level.moninfo(midx).armval = _level.moninfo(midx).armval - pct\n            If _level.moninfo(midx).armval \x3C 0.0 Then\n               _level.moninfo(midx).armval = 0.0\n            EndIf\n            txt = _level.moninfo(midx).mname & " armor has been reduced."\n            PrintMessage txt\n            _level.moninfo(midx).mcolor = fbMagenta\n         Case splShatter     \'Spellbook: Destroys target weapon, if any.\n            ret = ApplyDamage(mx, my, spl.lvl)\n            \'If not dead set the timed flag.\n            If ret = FALSE Then\n               _level.moninfo(midx).atkdam = 0\n            End If\n            txt = "Shatter Spell destroyed " & _level.moninfo(midx).mname & " attack ability."\n            PrintMessage txt\n            _level.moninfo(midx).mcolor = fbMagenta\n         Case splMagicDrain  \'Spellbook: Lower target MDF by lvl% and adds to caster for 1 turn.\n            _level.moninfo(midx).effects(meMDF).cnt = spl.lvl\n            _level.moninfo(midx).effects(meMDF).dam = dam\n            txt = "Magic Drain Spell has lowered " & _level.moninfo(midx).mname & " magic defense."\n            PrintMessage txt\n            _level.moninfo(midx).mcolor = fbMagenta\n         Case splPoison      \'Spellbook: Posions target 1 HP for lvl turns.\n            ret = ApplyDamage(mx, my, dam)\n            \'If not dead set the timed flag.\n            If ret = FALSE Then\n               _level.moninfo(midx).effects(mePoison).cnt = spl.lvl\n               _level.moninfo(midx).effects(mePoison).dam = dam\n            EndIf\n            txt = "Poison Spell has poisoned " & _level.moninfo(midx).mname & " for " & spl.lvl & " turns."\n            PrintMessage txt\n            _level.moninfo(midx).mcolor = fbMagenta\n         Case splEnfeeble    \'Spellbook: Lowers target combat factors lvl x 10%.\n            _level.moninfo(midx).effects(meEnfeeble).cnt = spl.lvl\n            _level.moninfo(midx).effects(meEnfeeble).dam = dam\n            txt = "Enfeeble Spell has lowered " & _level.moninfo(midx).mname & " combat factors for " & spl.lvl & " turns."\n            PrintMessage txt\n            _level.moninfo(midx).mcolor = fbMagenta\n         Case splShout       \'Spellbook: Stuns all visible monsters for lvl turns.\n            For i As Integer = 1 To _level.nummon\n               If _level.moninfo(i).isdead = FALSE Then\n                  If  _level.lmap(_level.moninfo(i).currcoord.x, _level.moninfo(i).currcoord.y).visible = TRUE Then\n                     _level.moninfo(i).effects(meStun).cnt = spl.lvl\n                     _level.moninfo(i).effects(meStun).dam = dam\n                     txt = "Warrior Shout Spell has stunned " & _level.moninfo(i).mname & " for " & spl.lvl & " turns."\n                     PrintMessage txt\n                  EndIf\n               End If\n            Next\n            _level.moninfo(midx).mcolor = fbMagenta\n         Case splMindBlast   \'Spellbook: Lowers target MCF and MDF lvl% for lvl turns.\n            _level.moninfo(midx).effects(meMDF).cnt = spl.lvl\n            _level.moninfo(midx).effects(meMDF).dam = dam\n            _level.moninfo(midx).effects(meMCF).cnt = spl.lvl\n            _level.moninfo(midx).effects(meMCF).dam = dam\n            txt = "Mind Blast Spell has lowered " & _level.moninfo(midx).mname & " magic magic combat factors."\n            PrintMessage txt\n            _level.moninfo(midx).mcolor = fbMagenta\n         Case splTeleport\n            \'Apply enough damage to kill any monster.\n            ret = ApplyDamage(mx, my, 1000000)\n            txt = "You tleported into " & _level.moninfo(midx).mname & " killing it."\n            PrintMessage txt\n      End Select\n}}}\n\x3C/div\x3E\nThese are the attack spells, and most are self-explanatory. They operate just like the item spells, and either affect the monster immediately or have long term effects. In order to support the new long term effects, we needed to expand the effects array of the monster.\n\n\x3Cdiv class="code"\x3E\n/monster.bi/\n{{{\n\'Monster type def. \n\'Spell effects on monster.\nEnum monSpells\n   mePoison      \'Poison damage.\n   meFire        \'Fire damage.\n   meStun        \'Mosnter stunned.\n   meAcidFog     \'5 dam over lvl turns\n   meBlind       \'Blinds target for lvl turns\n   meFear        \'Makes monster flee for lvl turns.\n   meConfuse     \'Confuses monster for lvl turns.\n   meEntangle    \'Immobilze target for level turns doing lvl damage each turn.\n   meCloudMind   \'Target cannot cast spells for lvl turns.\n   meMagicDrain  \'Lower target MDF by lvl% and adds to caster for 1 turn.\n   meEnfeeble    \'Lowers target combat factors lvl x 10%.\n   meIceStatue   \'Freezes target for level turns.\n   meMDF         \'Lowers magic defense.\n   meMCF         \'Lowers magic defense.\nEnd Enum\n\nType montype\n...\n   effects(mePoison To meMCF) As monSpellEffects \'The current effects active on monster.\nEnd Type\n}}}\n\x3C/div\x3E\nAs you can see we have expanded the /monSpells/ enumeration and use the values as the array bounds. This enables us to reference an array index with the an enumeration name. To see how this works let\'s look at a spell that has some long term effect.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\nCase splPoison      \'Spellbook: Posions target 1 HP for lvl turns.\n  ret = ApplyDamage(mx, my, dam)\n  \'If not dead set the timed flag.\n  If ret = FALSE Then\n   _level.moninfo(midx).effects(mePoison).cnt = spl.lvl\n   _level.moninfo(midx).effects(mePoison).dam = dam\n  EndIf\n  txt = "Poison Spell has poisoned " & _level.moninfo(midx).mname & " for " & spl.lvl & " turns."\n  PrintMessage txt\n  _level.moninfo(midx).mcolor = fbMagenta\n}}}\n\x3C/div\x3E\nThis is the poison spell which will poison the monster over time. Notice we are using the enumeration value /mePoison/ as an index into the effect array. The level of the spell /spl.lvl/ determines how long the effect lasts and is measured in turns. As the character reads spell books, the level of the spell will increase, which also increases the time the spell lasts. Each effect is resolved in the /DoTimedEvents/ which is called each turn.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Resolves any timed events.\nsub levelobj.DoTimedEvents()\n   Dim As String txt\n   Dim As Integer tmp\n   \n   \'Iterate through each monster.\n   For i As Integer = 1 To _level.nummon\n      \'Make sure monster is not dead.\n      If _level.moninfo(i).isdead = FALSE Then\n         \'Examine each effect and apply any damages/effects.\n         If _level.moninfo(i).effects(mePoison).cnt \x3E 0 Then\n            tmp = ApplyDamage(_level.moninfo(i).currcoord.x, _level.moninfo(i).currcoord.y, _level.moninfo(i).effects(mePoison).dam)  \n            _level.moninfo(i).effects(mePoison).cnt -= 1\n         Else\n            _level.moninfo(i).mcolor = fbRedBright\n         EndIf\n...\n}}}\n\x3C/div\x3E\nHere is the poison event code. At each turn, the poison damage is applied to the monster and the count is decremented. Once the count reaches zero, the effect isn\'t applied. An extremely simple way to do timed events in a turned based game, but quite effective.\n\nWhat happens if the monsters dies from the poison? We see that in /ApplyDamage/.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Applies damage to monsters. Returns true if monster is dead.\nFunction levelobj.ApplyDamage(mx As Integer, my As Integer, dam As Integer) As Integer\n   Dim As Integer midx, i, ret = FALSE\n   Dim As vec v\n   Dim As String txt\n   \n   \'Make sure there is a monster here.\n   If _level.lmap(mx, my).monidx \x3E 0 Then\n      midx = _level.lmap(mx, my).monidx\n      _level.moninfo(midx).currhp = _level.moninfo(midx).currhp - dam\n      \'Check for flee.\n      If _level.moninfo(midx).currhp \x3C 2 Then _level.moninfo(midx).flee = TRUE \n      \'Check to see if monster is dead.\n      If (_level.moninfo(midx).currhp \x3C 1) Or (_level.moninfo(midx).effects(meIceStatue).cnt \x3E 0) Then\n         pchar.CurrXP = pchar.CurrXP + _level.moninfo(midx).xp\n         \'Monster is dead.\n         ret = TRUE\n         \'Flag the monster as dead.\n         _level.moninfo(midx).isdead = TRUE\n         \'Remove from map.\n         _level.lmap(mx, my).monidx = 0\n         \'Drop any items.\n         If _level.moninfo(midx).dropcount \x3E 0 Then\n            For i = 1 To _level.moninfo(midx).dropcount\n               For j As compass = north To nwest\n                  v.vx = mx\n                  v.vy = my\n                  v += j\n                  \'If empty drop item.\n                  If (_level.lmap(v.vx, v.vy).terrid = tFloor) And (_level.linv(v.vx, v.vy).classid = clNone) Then\n                     PutItemOnMap v.vx, v.vy, _level.moninfo(midx).dropitem(i)\n                     Exit For\n                  EndIf\n               Next\n               ClearInv _level.moninfo(midx).dropitem(i)\n            Next\n         EndIf\n      EndIf\n     \'Print the result of the combat.\n      If _level.moninfo(midx).isdead = TRUE Then\n         txt = pchar.CharName & " killed the " & _level.moninfo(midx).mname & " with " & dam & " damage points."\n      Else\n         txt = pchar.CharName & " hit the " & _level.moninfo(midx).mname & " for " & dam & " damage points."\n      EndIf\n      PrintMessage txt\n   EndIf\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nIf the monster dies, we credit the character with the monster experience points {{{pchar.CurrXP = pchar.CurrXP + _level.moninfo(midx).xp}}} and the monster drops any items it may be carrying. Not only is this subroutine called by ApplySpell and DoTimedEvents, but also in the melee and projectile combat in dod.bas. Again, reuse is our friend.\n\nThere are two spells in ApplySpell that warrant a closer look.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n         Case splFireBomb, splFireBall    \'Spellbook: Area damage 20 (or 10) x lvl. Sets monsters on fire lvl turns.\n            \'Check to make sure monster isn\'t already on fire. \'Prevents infinite loop.\n            If _level.moninfo(midx).effects(meFire).cnt \x3C 1 Then\n               ret = ApplyDamage(mx, my, dam * spl.lvl)\n               \'If not dead set the timed flag.\n               If ret = FALSE Then\n                  _level.moninfo(midx).effects(meFire).cnt = spl.lvl\n                  _level.moninfo(midx).effects(meFire).dam = dam\n               End If\n               \'Check for spell type.\n               If spl.id = splFireBomb Then\n                  txt = "Fire Bomb Spell inflicted  " & dam & " damage to " & _level.moninfo(midx).mname & "."\n               Else\n                  txt = "Fire Ball Spell inflicted  " & dam & " damage to " & _level.moninfo(midx).mname & "."\n               End If\n               PrintMessage txt\n               \'We will call this recursively to hit any monsters next to target.\n               \'This will cause a chain reaction hitting all monsters next to\n               \'each other on the map.\n               For i As compass = north To nwest\n                  \'Set ititial position.\n                  vm.vx = mx\n                  vm.vy = my\n                  \'Get new position.\n                  vm += i\n                  \'Recusively call function.\n                  tmp = ApplySpell(spl, vm.vx, vm.vy)\n               Next\n            Else\n               txt = _level.moninfo(midx).mname & " is already on fire."\n               PrintMessage txt\n            _level.moninfo(midx).mcolor = fbMagenta\n            End If\n}}}\n\x3C/div\x3E\nHere we have the Fire Bomb and Fire Ball spells. Since both spells operate in the same manner, we have just one Case statement to cover them. Unlike other spells that target a single monster, these spells produce collateral damage. That is, if two monsters are standing next to each other, both will get hit by the spell, even though only one monster was targeted. We do this by calling ApplySpell recursively.\n\nRecusrion is a powerful technique and simplifies a lot of programming tasks. Here we check each tile around the targeted monster and then call ApplySpell for that tile. If a monster is at the location, it will be attacked as if they were the target monster. We then look at each square around that monster, and so on. When using recursion, you need to make sure that you have an exit condition so that you can unwind the recursion stack, otherwise the program will just keeping calling the same procedure, eat up the stack, and crash. Here we have two exit conditions. If there isn\'t a monster at the location, the function exits. If a monster is already on fire, we exit. The last one is especially important. If we didn\'t check to see if a monster was on fire, it would keep setting the same monsters on fire, creating an infinite loop, and infinite loops equal a program crash. By checking both exit conditons, we ensure we exit the recursion cleanly and at the appropriate time.\n\nThis covers the attack spells. Now, let\'s look at the buff spells.\n\n\x3Cdiv class="code"\x3E\n/dod.bas:CastSpell/\n{{{\nSelect Case sinv.spell.id\n  Case splHeal\n    tmp = pchar.MaxHP * (sinv.spell.lvl / 100)\n    If tmp \x3C 1 Then tmp = 1\n    pchar.CurrHP = pchar.CurrHP + tmp\n  Case splMana\n    tmp = pchar.MaxMana * (sinv.spell.lvl / 100)\n    If tmp \x3C 1 Then tmp = 1\n    pchar.CurrMana = pchar.CurrMana + tmp\n    cancel = TRUE\n}}}\n\x3C/div\x3E\nThe Restore Health and Restore Mana do just what they say, they restore health and mana respectively. The difference is that the mana spell does not require mana to cast, a special case that is covered by the cancel flag. When we get to the mana calculation, you will see this cancel flag in action.\n\nThe Recharge Wands spell is a bit more complicated, but only because we have to look in the inventory for wands and the two held positions.\n\n\x3Cdiv class="code"\x3E\n/dod.bas:CastSpell/\n{{{\nCase splRecharge\n   \'Look for wands in inventory and recharge based on level.\n   For i = pchar.LowInv To pchar.HighInv\n      iitem = pchar.HasInvItem(i)\n      If iitem = TRUE Then\n         \'Get the inventory item.\n         pchar.GetInventoryItem i, iinv\n         \'Check to see if it is a weapon.\n         If iinv.classid = clWeapon Then\n            \'Check to see if it is a wand.\n            If iinv.weapon.iswand = TRUE Then\n               \'Increase the ammocnt (charges).\n               iinv.weapon.ammocnt += sinv.spell.lvl\n               \'Make sure we don\'t go over the capacity.\n               If iinv.weapon.ammocnt \x3E iinv.weapon.capacity Then\n                  iinv.weapon.ammocnt = iinv.weapon.capacity\n               EndIf\n               \'Put the item back into the inventory.\n               pchar.AddInvItem iitem, iinv\n            EndIf\n         EndIf\n      End If\n   Next\n   \'Check held positions and charge those too if carried.\n   iitem = pchar.HasInvItem(wPrimary)\n   If iitem = TRUE Then\n      pchar.GetInventoryItem wPrimary, iinv\n      If iinv.classid = clWeapon Then\n         \'Check to see if it is a wand.\n         If iinv.weapon.iswand = TRUE Then\n            iinv.weapon.ammocnt += sinv.spell.lvl\n            If iinv.weapon.ammocnt \x3E iinv.weapon.capacity Then\n               iinv.weapon.ammocnt = iinv.weapon.capacity\n            EndIf\n            pchar.AddInvItem wPrimary, iinv\n         EndIf\n      EndIf\n   EndIf\n   \'Check held positions and charge those too if carried.\n   iitem = pchar.HasInvItem(wSecondary)\n   If iitem = TRUE Then\n      pchar.GetInventoryItem wSecondary, iinv\n      If iinv.classid = clWeapon Then\n         \'Check to see if it is a wand.\n         If iinv.weapon.iswand = TRUE Then\n            iinv.weapon.ammocnt += sinv.spell.lvl\n            If iinv.weapon.ammocnt \x3E iinv.weapon.capacity Then\n               iinv.weapon.ammocnt = iinv.weapon.capacity\n            EndIf\n            pchar.AddInvItem wSecondary, iinv\n         EndIf\n      EndIf\n   EndIf\n}}}\n\x3C/div\x3E\nThe first For-Next checks each slot in the character\'s inventory to see if it is a wand. If a wand is found, we grab it and charge it up based on the spell level. Since we can\'t go over the capacity of the wand, we check to make sure that the wand amount isn\'t greater than the capacity. We then put the wand back into the same inventory slot.\n\nThe next two sections of code do exactly the same thing, except that the Primary and Secondary locations are examined. Since a wand could be held in either or both, we need to check both slots.\n\n\x3Cdiv class="code"\x3E\n/dod.bas:CastSpell/\n{{{\nCase splFocus\n   \'Increase all combat factors for 1 turn.\n   pchar.BonUcf = sinv.spell.lvl\n   pchar.BonUcfCnt = 1\n   pchar.BonAcf = sinv.spell.lvl\n   pchar.BonAcfCnt = 1\n   pchar.BonPcf = sinv.spell.lvl\n   pchar.BonPcfCnt = 1\n   pchar.BonCdf = sinv.spell.lvl\n   pchar.BonCdfCnt = 1\n   pchar.BonMcf = sinv.spell.lvl\n   pchar.BonMcfCnt = 1\n   pchar.BonMdf = sinv.spell.lvl\n   pchar.BonMdfCnt = 1\n}}}\n\x3C/div\x3E\nThe Focus spell boosts all the combat factors for a turn, so we set each of the combat bonus factors to the level of the spell and set the count to 1. Remember that we decided not to accumulate bonus factors in the game so that only a single bonus is active at time. If a bonus is active at the time this spell is cast, it will be overwritten and replaced with this data. \n\n\x3Cdiv class="code"\x3E\n/dod.bas:CastSpell/\n{{{\nCase splTeleport\n   \'Get the target coordinates.\n   ret = GetTargetCoord(vt, sinv.spell.lvl)\n   If ret = TRUE Then\n      \'Check to see if a monster is at location.\n      If level.IsMonster(vt.vx, vt.vy) = TRUE Then\n         \'Teleport to mosnter killing it.\n         ret = level.ApplySpell(sinv.spell, vt.vx, vt.vy)\n         \'Set the new character position.\n         pchar.Locx = vt.vx\n         pchar.Locy = vt.vy\n         \'Generate the sound map.\n         level.ClearSoundMap\n         snd = pchar.GetNoise()\n         level.GenSoundMap(pchar.Locx, pchar.Locy, snd)\n      Else\n         \'Make sure it isn\'t a blocking tile.\n         If level.IsBlocking(vt.vx, vt.vy) = FALSE Then\n            \'Set the new character position.\n            pchar.Locx = vt.vx\n            pchar.Locy = vt.vy\n            \'Generate the sound map.\n            level.ClearSoundMap\n            snd = pchar.GetNoise()\n            level.GenSoundMap(pchar.Locx, pchar.Locy, snd)\n         Else\n            ShowMsg "Teleport", "You can\'t teleport there.", tWidgets.MsgBoxType.gmbOK\n            cancel = TRUE\n         End If   \n      EndIf\n   Else\n      cancel = TRUE\n   EndIf\n}}}\n\x3C/div\x3E\nThe Teleport spell is directed at a specific location, so we invoke the the /GetTargetCoord/ function to get a location. Notice that the location may contain a monster, and if it does, the player will teleport into the monster killing it, which is why we call ApplySpell here, though technically, the teleport spell isn\'t an attack spell.\n\nOnce the player selects a valid location, that is a floor tile and not a wall tile, we move the character to that location, generating all the data, such as the sound map, just as if the character had walked to the location. We are using the cancel flag here too in case the player decides not to teleport after selecting the spell.\n\nThe next spell is the Open Door spell.\n\n\x3Cdiv class="code"\x3E\n/dod.bas:CastSpell/\n{{{\nCase splOpen\n   \'Get the target coordinates.\n   ret = GetTargetCoord(vt, sinv.spell.lvl)\n   If ret = TRUE Then\n      \'Get the terrain id.\n      tid = level.GetTileID(vt.vx, vt.vy)\n      \'Check for closed door.\n      If tid = tDoorClosed Then\n         \'See if it is locked.\n         If level.IsDoorLocked(vt.vx, vt.vy) = TRUE Then\n            ret = level.OpenLockedDoor(vt.vx, vt.vy, sinv.spell.lvl)\n            If ret = TRUE Then\n               PrintMessage "Door was opened."\n            EndIf\n         Else\n            \'Tell player door isn\'t locked.\n            ShowMsg "Open Spell", "The door is not locked.", tWidgets.MsgBoxType.gmbOK\n            cancel = TRUE\n         EndIf\n      EndIf\n   Else\n      cancel = TRUE\n   End If\n}}}\n\x3C/div\x3E\nThe Open Door spell attempts to open a locked door based on the level of the spell. We are using /GetTargetCoord/ once more and if a door is at the location selected, we call the /OpenLockedDoor/ function which we have added to the level object.\n\n\x3Cdiv class="code"\x3E\n/level.bi/\n{{{\n\'Attempts to open a locked door. \nFunction levelobj.OpenLockedDoor(x As Integer, y As Integer, dr As Integer) As Integer\n   Dim As Integer ret = TRUE, ddr, rolld, rollp\n   Dim tid As terrainids\n   \n   \'Make sure we have a door.\n   tid = GetTileID(x, y)\n   If tid = tDoorClosed Then\n     If IsDoorLocked(x, y) = TRUE Then\n        \'Get the difficulty rating of the door.\n        ddr = _level.lmap(x, y).doorinfo.lockdr\n        \'Get the rolls.\n        rollp = RandomRange(1, dr)\n        rolld = RandomRange(1, ddr)\n        If rollp \x3E rolld Then\n           \'Open door.\n           _level.lmap(x, y).doorinfo.locked = FALSE\n           SetTile x, y, tdooropen\n        Else\n           \'Didn\'t open the door.\n           ret = FALSE\n        EndIf\n     EndIf\n   EndIf\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nHere we compare the passed DR against the door\'s DR and generate two rolls, with the higher roll winning. The door\'s DR represents how difficult the lock on the door is, and how much effort will be required to open it. Not only can we use this function for spells, we will also use this when the character attempts to pick a lock. Right now we don\'t have locked doors, but we will soon, so we are ready for lock picking.\n\nThe last buff spell we have is Blink.\n\n\x3Cdiv class="code"\x3E\n/dod.bas:CastSpell/\n{{{\nCase splBlink\n \'Set the blink effect.\n pchar.SetSpellEffect sinv.spell.id, sinv.spell.lvl, 0\n}}}\n\x3C/div\x3E\nOriginally, Blink was going to be a random teleport, but since we already had a Teleport spell, I decided to use Blink as something else. Notice we are simply calling the /SetSpellEffect/ with the Blink id.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n\'Sets the spell effect.\nSub character.SetSpellEffect(splid As cspleffects, scnt As Integer, samt As Integer)\n   Select Case splid\n      Case cPoison\n         _cinfo.cseffect(cPoison).cnt = scnt\n         _cinfo.cseffect(cPoison).amt = samt\n      Case cBlink\n         _cinfo.cseffect(cBlink).cnt = scnt\n         _cinfo.cseffect(cBlink).amt = samt\n   End Select\nEnd Sub\n}}}\n\x3C/div\x3E\nAs you can see we have a new effect in the character\'s effect array, blink. You may remember that Poison was previously just a flag and count, but we really needed to have an effect array like we do for the monsters so the Poison flags were discarded and the effect array was added.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n\'Spell effects.\nEnum cspleffects\n   cPoison\n   cBlink\nEnd Enum\n\n\'Effects type.\nType cspleftype\n   cnt As Integer    \'The duration.\n   amt As Integer    \'The amt of effect.\nEnd Type\n\n\'Character attribute type def.\nType characterinfo\n...\n   cseffect(cPoison To cBlink) As cspleftype \'Spell effects array. \nEnd Type\n}}}\n\x3C/div\x3E\nHere is our character effect set up and as you can see, it looks just like the monster set up. We have an amount and count which we will manage through the /DoTimedEvents/ subroutine.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n\'Manages any timed events.\nSub character.DoTimedEvents()\n   Dim As Integer roll1, roll2, v1, v2, amt, statamt\n   \n   \'Poison will affect character based on strength of poison.\n   If Poisoned = TRUE Then\n      \'Get the strength of the poison.\n      v1 = PoisonStr\n      \'Get character stamina + bonus\n      v2 = CurrSta + BonSta\n      \'Roll for the poison.\n      roll1 = RandomRange(1, v1)\n      roll2 = RandomRange(1, v2)\n      \'If poison wins,\n      If roll1 \x3E roll2 Then\n         \'Take one off the health.\n         CurrHP = CurrHP - 1 \n      EndIf\n   EndIf\n   \'Check stat bonus counts and adjust if necessary.\n   \'Strength.\n   If BonStrCnt \x3E 0 Then\n      BonStrCnt = BonStrCnt - 1\n      If BonStrCnt \x3C 1 Then\n         BonStr = 0\n      EndIf\n   EndIf\n   \'Stamina\n   If BonStaCnt \x3E 0 Then\n      BonStaCnt = BonStaCnt - 1\n      If BonStaCnt \x3C 1 Then\n         BonSta = 0\n      EndIf\n   EndIf\n   \'Dexterity.\n   If BonDexCnt \x3E 0 Then\n      BonDexCnt = BonDexCnt - 1\n      If BonDexCnt \x3C 1 Then\n         BonDex = 0\n      EndIf\n   EndIf\n   \'Agility\n   If BonAglCnt \x3E 0 Then\n      BonAglCnt = BonAglCnt - 1\n      If BonAglCnt \x3C 1 Then\n         BonAgl = 0\n      EndIf\n   EndIf\n   \'Intelligence.\n   If BonIntCnt \x3E 0 Then\n      BonIntCnt = BonIntCnt - 1\n      If BonIntCnt \x3C 1 Then\n         BonInt = 0\n      EndIf\n      charint = _cinfo.intatt(idxAttr) + BonInt\n   EndIf\n   \'Unarmed combat.\n   If BonUcfCnt \x3E 0 Then\n      BonUcfCnt = BonUcfCnt - 1\n      If BonUcfCnt \x3C 1 Then\n         BonUcf = 0\n      EndIf\n   EndIf\n   \'Armed combat.\n   If BonAcfCnt \x3E 0 Then\n      BonAcfCnt = BonAcfCnt - 1\n      If BonAcfCnt \x3C 1 Then\n         BonAcf = 0\n      EndIf\n   EndIf\n   \'Projectile combat.\n   If BonPcfCnt \x3E 0 Then\n      BonPcfCnt = BonPcfCnt - 1\n      If BonPcfCnt \x3C 1 Then\n         BonPcf = 0\n      EndIf\n   EndIf\n   \'Magic combat.\n   If BonMcfCnt \x3E 0 Then\n      BonMcfCnt = BonMcfCnt - 1\n      If BonMcfCnt \x3C 1 Then\n         BonMcf = 0\n      EndIf\n   EndIf\n   \'Combat defense.\n   If BonCdfCnt \x3E 0 Then\n      BonCdfCnt = BonCdfCnt - 1\n      If BonCdfCnt \x3C 1 Then\n         BonCdf = 0\n      EndIf\n   EndIf\n   \'Magic Defense.\n   If BonMdfCnt \x3E 0 Then\n      BonMdfCnt = BonMdfCnt - 1\n      If BonMdfCnt \x3C 1 Then\n         BonMdf = 0\n      EndIf\n   EndIf\n   \'Check for necklaces and rings.\n   statamt = MaxHP\n   amt = GetJewleryEffect(jwRegenHP, statamt)\n   CurrHP = CurrHP + amt\n   statamt = MaxMana\n   amt = GetJewleryEffect(jwRegenMana, statamt)\n   CurrMana = CurrMana + amt\n   \'Check for blink spell.\n   If _cinfo.cseffect(cBlink).cnt \x3E 0 Then\n      _cinfo.cseffect(cBlink).cnt = _cinfo.cseffect(cBlink).cnt - 1\n      If _cinfo.cseffect(cBlink).cnt \x3C 0 Then _cinfo.cseffect(cBlink).cnt = 0\n   EndIf\nEnd Sub\n}}}\n\x3C/div\x3E\nThe two changes here are the Posion management, which was changed to access the array, and the last section of code which manages the Blink effect. But where is the Blink effect? We have to go to the monster attack code for that.\n\n\x3Cdiv class="code"\x3E\n/level.bi/\n{{{\n\'Monster attacks character.\nSub levelobj.MonsterAttack(mx As Integer, my As Integer)\n   Dim As Integer midx, cd, mc, rollc, rollm, chp, dam\n   Dim As String txt\n   Dim As Single arm\n   \n   \'Make sure there is a monster here.\n   If (_level.lmap(mx, my).monidx \x3E 0) And (pchar.BlinkActive = FALSE) Then\n   ...\n}}}\n\x3C/div\x3E\nAs you can see, if the Blink spell is active, the character is "invisible" to attacks, so the monster can\'t attack in this round. The /BlinkActive/ property in the character object returns the current state of the Blink flag.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n\'Returns TRUE if blink is active.\nProperty character.BlinkActive() As Integer\n   Return (_cinfo.cseffect(cBlink).cnt \x3E 0)\nEnd Property\n}}}\n\x3C/div\x3E\nThis may look a bit strange if you haven\'t seen this technique before. We are using the /\x3E/ operator here to check for a count greater than 0. Since the \x3E operator is internally a function which returns TRUE (-1) or FALSE (0), we can use that as our return value. It is a quick and efficient means to check for the current state of a variable.\n\nThe last bit of code in /CastSpell/ implements the mana cost of a spell.\n\n\x3Cdiv class="code"\x3E\n/dod.bas:CastSpell/\n{{{\n...\n  End Select\nEndIf\nIf cancel = FALSE Then\n  \'Deduct mana cost.\n  pchar.CurrMana = pchar.CurrMana - sinv.spell.manacost\nEnd If\n...\n}}}\n\x3C/div\x3E\nHere we see our cancel flag in operation. If we don\'t set the cancel flag, we deduct the mana cost of the spell from the character\'s current mana total. This is quite a bit of code to implement spells, but we are not quite done yet. There are some spells that freeze or confuse a monster and which ultimately affects the monster movement, so we have a minor change to the /MoveMonsters/ code.\n\n\x3Cdiv class="code"\x3E\n/level.bi/\n{{{\n\'Moves all living monsters.\nSub levelobj.MoveMonsters ()\n   Dim As mcoord nxt\n   Dim As Integer pdist\n         \n   \'Iterate through each monster.\n   For i As Integer = 1 To _level.nummon\n      \'Make sure monster is not dead.\n      If (_level.moninfo(i).isdead = FALSE) And _\n         (_level.moninfo(i).effects(meStun).cnt \x3C 1) And _\n         (_level.moninfo(i).effects(meBlind).cnt \x3C 1) And _\n         (_level.moninfo(i).effects(meEntangle).cnt \x3C 1) And _\n         (_level.moninfo(i).effects(meIceStatue).cnt \x3C 1) And _\n         (_level.moninfo(i).effects(meConfuse).cnt \x3C 1) Then\n         \'Is the monster fleeing?\n         If _level.moninfo(i).flee = FALSE Then\n...\n}}}\n\x3C/div\x3E\nHere we have all the spells that affect monster movement. Some do additional damage as well, such as Entangle. This gives the character some free shots at the monster so these spells are quite valuable. The Ice Statue spell also has an additonal benefit of killing the monster with a single blow when active.\n\n\x3Cdiv class="code"\x3E\n/level.bi/\n{{{\n\'Applies damage to monsters. Returns true if monster is dead.\nFunction levelobj.ApplyDamage(mx As Integer, my As Integer, dam As Integer) As Integer\n   Dim As Integer midx, i, ret = FALSE\n   Dim As vec v\n   Dim As String txt\n   \n   \'Make sure there is a monster here.\n   If _level.lmap(mx, my).monidx \x3E 0 Then\n      midx = _level.lmap(mx, my).monidx\n      _level.moninfo(midx).currhp = _level.moninfo(midx).currhp - dam\n      \'Check for flee.\n      If _level.moninfo(midx).currhp \x3C 2 Then _level.moninfo(midx).flee = TRUE \n      \'Check to see if monster is dead.\n      If (_level.moninfo(midx).currhp \x3C 1) Or (_level.moninfo(midx).effects(meIceStatue).cnt \x3E 0) Then\n         pchar.CurrXP = pchar.CurrXP + _level.moninfo(midx).xp\n         \'Monster is dead.\n         ret = TRUE\n         \'Flag the monster as dead.\n         _level.moninfo(midx).isdead = TRUE\n...\n}}}\n\x3C/div\x3E\nHere we see that if the Ice Statue spell is active, the monster immediately dies when receiving any damage. By placing the code for the spell here, we don\'t need to worry about updating all the combat routines. If at any time we decide to add new combat modes, the spell will cover those as well.\n\nAs you can see, spell casting is not a trivial update, but well worth the time to implement. It offers the player a new set of options while playing the game, and gives the player a chance to be actively involved in the management of the game resources. Keeping the player active in the game will help keep the interest up, and keep the player playing.\n',
'= 35: Tweaking the Game 2\n\x3Cdiv align="center"\x3E\x3Cimg src=\'images/confquit.png\' /\x3E\x3C/div\x3E\n\nOne thing we need to do is to make sure the player really wants to quit when the Escape key is pressed. In the heat of battle, the player may press the Escape key by error, and to just summarily quit the game is one way to make the player really upset. We can avoid that with a simple change.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n \'Check for escape key.\n If ckey = key_esc Then\n   Dim As tWidgets.tMsgbox conf\n   Dim As tWidgets.btnID btn\n            \n   \'Ask the player if he wants to quit.\n   conf.MessageStyle = tWidgets.MsgBoxType.gmbYesNo\n   conf.Title = "Confirm Exit"\n   btn = conf.MessageBox("Do you wish to quit?")\n     If btn = tWidgets.btnID.gbnYes Then\n       done = TRUE\n     EndIf\n EndIf\n}}}\n\x3C/div\x3E\nHere we simply create a Yes-No Messagebox and ask the player if they really wants to quit. If they do, then we exit the game by setting the done flag to TRUE. If they don\'t, then we simply continue on with the game. This will prevent any accidental exits from the game and prevent any ill will from the player. We will also be using this section of code to save the game on exit, so it will eventually do double duty for us. \n\nThe next tweak is both cosmetic and simplifies the equipping code. It also gives us some nice options for later enhancements. Right now, when the character equips a two handed weapon, one slot has the weapon name and one slot is empty. A blank inventory slot implies an available slot, but in the case of a two handed weapon, the blank slot is unavailable. This may be confusing to new players, so it would be nice to mark the blank slot as "Unavailable". Not only can we mark the blank slot as unavailable, but by doing so, we can simplify our code quite a bit.\n\nWe will start by creating a new inventory class id, /clUnavailable/.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Item class ids.\nEnum classids\n   clNone\n   clGold\n   clSupplies \n   clArmor\n   clShield\n   clWeapon\n   clAmmo\n   clPotion\n   clRing\n   clNecklace\n   clSpellBook\n   clSpell\n   clUnavailable\nEnd Enum\n}}} \n\x3C/div\x3E\nAs you might guess, the new Unavailable item will be an inventory item like our other items. This way we don\'t need to make an exception to our inventory handling code. The more consistent we are with the code, the more robust the code will be. Since this is an inventory item, we need a generate subroutine.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Generates an Unavailable inventory item.\nSub GenerateUnavail (inv As invtype)\n   ClearInv inv\n   inv.classid = clUnavailable\n   inv.desc = "Unavailable"\n   inv.iconclr = fbWhite\nEnd Sub\n}}}\n\x3C/div\x3E\nNot much here really. All we need are just these top-level items The class id is necessary of course so that we know what we are working with, the description so we can display the item in the inventory and main screen, and the icon color which will be used in the inventory screen display as well.\n\nThe only place this come into play right now, is when we add a two handed weapon to one of the wield slots. A minor update to the character /AddInvItem/ will cover this for us.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n\'Adds inventory item to character inventory slot.\nSub character.AddInvItem(idx As Integer, inv As invtype)\n   Dim As invtype inv2\n   \n   \'Check to see if the index is in bounds.\n   If idx \x3E= LBound(_cinfo.cwield) And idx \x3C= UBound(_cinfo.cwield) Then\n      \'Clear the inventory slot.\n      ClearInv _cinfo.cwield(idx)\n      \'Set the item in the inv slot.\n      _cinfo.cwield(idx) = inv\n      \'Check for two-handed weapon and set other hand unavaialble.\n      If inv.classid = clWeapon Then\n         \'If two handed weapon set the other hand to unavailable.\n         If inv.weapon.hands = 2 Then\n            \'Get new unavail item.\n            GenerateUnavail inv2\n            \'Set the item in the slot.\n            If idx = wPrimary Then\n               ClearInv _cinfo.cwield(wSecondary)\n               _cinfo.cwield(wSecondary) = inv2\n            Else\n               ClearInv _cinfo.cwield(wPrimary)\n               _cinfo.cwield(wPrimary) = inv2\n            EndIf\n         EndIf\n      End If\n   Else\n      \'Validate the inventory index.\n      If idx \x3E= LBound(_cinfo.cinv) And idx \x3C= UBound(_cinfo.cinv) Then\n         \'Clear the inventory slot.\n         ClearInv _cinfo.cinv(idx)\n         \'Set the item in the inv slot.\n         _cinfo.cinv(idx) = inv\n      End If   \n   EndIf\nEnd Sub\n}}}\n\x3C/div\x3E\nHere we check to see if the passed inventory item is a weapon, and then we check the number of hands required. If it is a two-handed weapon, then we set the opposite slot to unavailable. The inventory screen will show the weapon name in one slot and "Unavailable" in the other slot. \n\nSo, how does this help us? Let\'s take a look at our /ProcessEquip/ code.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Process the equip menu item.\nFunction ProcessEquip() As Integer\n   Dim As String res, mask, msg\n   Dim As Integer i, iitem, iret, ret = FALSE\n   Dim As invtype inv\n   Dim As tWidgets.btnID btn\n   Dim As tWidgets.tInputbox ib\n   Dim As wieldpos slot\n      \n   \'Make sure there is something to process in the inventory.\n   For i = pchar.LowInv To pchar.HighInv\n      iitem = pchar.HasInvItem(i)\n      If iitem = TRUE Then\n         \'Get the inv item.\n         pchar.GetInventoryItem i, inv\n         \'Is the item evaluated.\n         iret = MatchUse(inv, useWieldWear)\n         \'An item to evaluate.\n         If iret = TRUE Then\n            \'Build the mask.\n            mask &= Chr(i)\n         End If\n      EndIf\n   Next\n   If Len(mask) = 0 Then\n      ShowMsg "Equip Items", "Nothing to equip.", tWidgets.MsgBoxType.gmbOK\n   Else\n      \'Draws an input box on screen.\n      ib.Title = "Equip Items"\n      ib.Prompt = "Select item(s) to equip (" & mask & ")"\n      ib.Row = 39\n      ib.EditMask = mask\n      ib.MaxLen = Len(mask)\n      ib.InputLen = Len(mask)\n      btn = ib.Inputbox(res)\n      \'Evaluate each item in the list.\n      If (btn \x3C\x3E tWidgets.btnID.gbnCancel) And (Len(res) \x3E 0) Then\n         \'Evaluate the list of items.\n         For i = 1 To Len(res)\n            iitem = Asc(res, i) \'Get index into inventory.\n            \'Get the inv item.\n            pchar.GetInventoryItem iitem, inv\n            \'Look for a free slot.\n            slot = pchar.GetFreeSlot(inv, msg)\n            \'No empty slots.\n            If slot \x3C\x3E wNone Then\n               \'Put the item wield position.\n               pchar.AddInvItem slot, inv\n               \'Clear the item.\n               ClearInv inv\n               \'Update the inv slot.\n               pchar.AddInvItem iitem, inv\n               ret = TRUE\n               msg &= " was equipped."\n            End If\n            ShowMsg "Equip Items", msg, tWidgets.MsgBoxType.gmbOK\n         Next\n      EndIf\n   EndIf\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nIf you remember the old code, it was a lot more complicated than this. Where we now have the function /GetFreeSlot/ we had some complicated code on checking slots and taking into account the number of hands and so on. That is all gone, and the new function, GetFreeSlot, returns a slot to us that we know we can use here. Let\'s take a look at the new slot code.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n\'Returns an empty slot or wNone.\nFunction character.GetFreeSlot(inv As invtype, msg As String) As wieldpos\n   Dim As wieldpos rslot\n   Dim As String desc\n   Dim As invtype inv2\n   \n   \'Get item description.\n   desc = GetInvItemDesc(inv)\n   msg = desc\n   \'Get the slot for the inventory item.\n   rslot = GetInvWSlot(inv, 1)\n   If rslot \x3C\x3E wNone Then\n      \'Check character to see if slot is open.\n      If HasInvItem(rslot) = TRUE Then\n         rslot = wNone\n      EndIf\n   End If\n   \'Check slot two.\n   If rslot = wNone Then\n      rslot = GetInvWSlot(inv, 2)\n      If rslot \x3C\x3E wNone Then\n         \'Check character to see if slot is open.\n         If HasInvItem(rslot) = TRUE Then\n            rslot = wNone\n         End If\n      EndIf\n   EndIf\n   \'Found an empty slot.\n   If rslot \x3C\x3E wNone Then\n      \'Check to see if this is a weapon and how many hands it has.\n      If inv.classid = clWeapon Then\n         If inv.weapon.hands = 2 Then\n            If (HasInvItem(wPrimary) = TRUE) Or (HasInvItem(wSecondary) = TRUE) Then\n               msg = "Not enough free hands to equip " & desc & "."\n               rslot = wNone\n            EndIf\n         EndIf\n      ElseIf (inv.classid = clArmor) Or (inv.classid = clShield) Then\n         If CanWear(inv) = FALSE Then\n            msg = "Not enough strength to equip " & desc & "."\n            rslot = wNone\n         End If\n      End If\n   Else\n      msg = "No empty slots to equip " & desc & "."\n   EndIf\n   \n   Return rslot\nEnd Function\n}}}\n\x3C/div\x3E\nThis function will return a free slot based on the type of inventory item or /wNone/ if no free slots exist. Since the Unavailable item is an inventory item, all we have to do is to check if the slot has an item by using the function /HasInvItem/. HasInvItem returns FALSE if the slot has a class id of clNone. Since the Unavailable item has a class id of clUnavailable, HasInvItem will return TRUE. Just as we wanted, the slot will be marked occupied and hence unavailable. \n\nIf the character is currently wielding a two-handed sword and tries to equip a one-handed sword, the off hand slot will contain an Unavailable item and will be marked occupied and the player will receive the message that there are no free slots to equip the one-handed sword. Just want we want. We don\'t need to count free slots and check the hands of the weapon, since both slots will automatically filled and we can just check them with HasInvItem.\n\nThe nice thing about this is that it doesn\'t affect any of the other wield slots. /GetInvWSlot/ returns the slot position of the passed inventory item by looking at the wield array within the item. If it is a weapon, wPrimary or wSecondary is returned. If the item is a necklace, wNeck is returned. This operates exactly as before, so we have simplified our code without implementing a nasty exception.\n\nHaving a new Unavailable item also gives up options for future enhancements. Suppose we want to limit the number of inventory slots in the backpack based on the character strength or stamina. All we have to do is calculate the number of slots available, and set the rest to unavailable. As the character strength increases, we open slots by clearing the Unavailable items. There are a number of other ways we could use this, such as for critical hits or magic effects. Any of these enhancements will be much easier with the new Unavailable inventory item.\n\nIn both of these updates we have accomplished a lot. The updates provide cosmetic enhancements, they simplify our code, and open the door for future enhancements. This is what tweaking is all about; getting as much functionality as possible out of the code.',
'= 36: Locked Doors\nSince we have spell casting in place, it is time to work on getting the rest of the main menu items working. Two of the menu items are /Pick Lock/ and /Bash Door/. Both of these refer to locked doors, so we need to implement locked doors in the game so the character has something to pick and bash. We will implement locked doors in our existing door code.\n\nBefore we get to the actual door placement code, let\'s look over our door Type Def to remind us of the data we need.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Door type.\nType doortype\n   locked As Integer   \'True if locked.\n   lockdr As Integer   \'Lock pick difficulty.\n   dstr As Integer     \'Strength of door (for bashing).\nEnd Type\n}}}\n\x3C/div\x3E\nThe /locked/ flag indicates whether the door is locked or unlocked, with a TRUE value signifying a locked door. The /lockdr/ is the difficulty of the lock. That is, how hard it is to pick. We will use this value for the lock picking roll. The /dstr/ is the strength of the door, and we will use this value for the door bashing roll. This is our door data definition, so let\'s look at how we are filling in the data.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Add doors to a room.\nSub levelobj._AddDoorsToRoom(i As Integer)\n	Dim As Integer row, col, dd1, dd2, nid, roll\n	\n   \'Add the doors.\n   For col = _rooms(i).tl.x To _rooms(i).br.x\n     dd1 = _rooms(i).tl.y\n     dd2 = _rooms(i).br.y\n     \'If a floor space in the wall.\n     If _level.lmap(col, dd1).terrid = tfloor Then\n       \'Add door.\n       _level.lmap(col, dd1).terrid = tdoorclosed\n       \'Check to see if door is locked.\n       roll = RandomRange(0, maxlevel * 2)\n       _level.lmap(col, dd1).doorinfo.locked = (roll \x3C _level.numlevel)\n       If _level.lmap(col, dd1).doorinfo.locked = TRUE Then\n         _level.lmap(col, dd1).doorinfo.lockdr = _level.numlevel\n         _level.lmap(col, dd1).doorinfo.dstr = _level.numlevel * 10\n       End If\n     EndIf\n...\n}}}\n\x3C/div\x3E\nI am only showing the code for one wall of the room here as all the walls work in the same way. Let\'s review what we are doing here first before we get into the door code. Once a room is placed and connected, we iterate along the walls of the room looking for a floor tile. If we find one, we have found the opening in the wall that leads to a corridor. We then place a closed door in the opening, indicated by the /tdoorclosed/ terrain type.\n\nOnce we put the door in place, we roll a number between 0 and the maximum levels time two. This gives us a range of values from 0 to 100, since we currently have /maxlevel/ set at 50. Like we have done with other objects in the game, this range gives us increasing probability that a door will be locked as we descend into the dungeon. At level 50, there will be approximately a 50% chance of a locked door. Once we get the roll, we then compare this number to the current level. If the roll is less than the current level, the door is locked. We fill in the door data structure and we now have a locked door.\n\nNow that we have locked doors, we need to be able to open them, so let\'s look at the easy case first, bashing doors.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Attempt to bash a door.\nSub BashDoor\n   Dim As vec dt\n   Dim As Integer ret, proll\n   \n   \'Check for locked door.\n   ret = GetLockedDoor(dt)\n   \'Found locked door.\n   If ret = TRUE Then\n      \'Get the character roll.\n      proll = pchar.CurrStr + pchar.BonStr\n      ret = level.OpenLockedDoor(dt.vx, dt.vy, proll)\n      If ret = TRUE Then\n         PrintMessage "You bashed open the door."\n      Else\n         \'Subtract 1 from hp.\n         PrintMessage "The door remains closed."\n         pchar.CurrHP = pchar.CurrHP - 1\n      EndIf\n   Else\n      PrintMessage "No locked doors found."\n   EndIf\nEnd Sub\n}}}\n\x3C/div\x3E\nThe first thing we need to do is to check to make sure that a locked door is next to the character. We do that in /GetLockedDoor/ which we look at in a moment. If we have a locked door, we get the character\'s roll data, which is the strength of the character plus any strength bonus. We then pass that data along with thew locked door location to /OpenLockedDoor/. We created OpenLockedDoor when we implemented the Open Door spell, and we are reusing the code here. If the door is opened, all is well. If the door doesn\'t open, then the character takes one point of damage to signify that bashing against a stubborn door may not be the best way to open it.\n\nLet\'s take a look at the GetLockedDoor function since we will be using that in the pick lock subroutine as well.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Looks for a door at each compass point.\nFunction GetLockedDoor(dt As vec) As Integer\n   Dim As Integer ret = FALSE\n   Dim As terrainids tile\n   \n   \'Check for a door next to player.\n   dt.vx = pchar.Locx\n   dt.vy = pchar.Locy\n   \'Look north.\n   dt += north\n   \'Is the tile a door?\n   tile = level.GetTileID(dt.vx, dt.vy)\n   If tile = tdoorclosed Then\n      \'Check to see if it is locked.\n      ret = (level.IsDoorLocked(dt.vx, dt.vy) = TRUE)\n   EndIf\n   \'Check to see if we found a door.\n   If ret = FALSE Then\n      \'Look at next space for door.\n      dt.vx = pchar.Locx\n      dt.vy = pchar.Locy\n      \'Look east.\n      dt += east\n      \'Is the tile a door?\n      tile = level.GetTileID(dt.vx, dt.vy)\n      If tile = tdoorclosed Then\n         \'Check to see if it is locked.\n         ret = (level.IsDoorLocked(dt.vx, dt.vy) = TRUE)\n      EndIf\n   EndIf\n   \'Check to see if we found a door.\n   If ret = FALSE Then\n      \'Look at next space for door.\n      dt.vx = pchar.Locx\n      dt.vy = pchar.Locy\n      \'Look south.\n      dt += south\n      \'Is the tile a door?\n      tile = level.GetTileID(dt.vx, dt.vy)\n      If tile = tdoorclosed Then\n         \'Check to see if it is locked.\n         ret = (level.IsDoorLocked(dt.vx, dt.vy) = TRUE)\n      EndIf\n   EndIf\n   \'Check to see if we found a door.\n   If ret = FALSE Then\n      \'Look at next space for door.\n      dt.vx = pchar.Locx\n      dt.vy = pchar.Locy\n      \'Look west.\n      dt += west\n      \'Is the tile a door?\n      tile = level.GetTileID(dt.vx, dt.vy)\n      If tile = tdoorclosed Then\n         \'Check to see if it is locked.\n         ret = (level.IsDoorLocked(dt.vx, dt.vy) = TRUE)\n      EndIf\n   EndIf\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nIn this function we are using our vector object and looking at the tile at four compass points, north, south, east and west. The character can\'t open a door in a diagonal direction, since that really doesn\'t make much sense anyway. All of the directions work the same way, so let\'s look at just one compass direction.\n\n\x3Cdiv class="code"\x3E\n/dod.bas:GetLockedDoor/\n{{{\n   \'Check for a door next to player.\n   dt.vx = pchar.Locx\n   dt.vy = pchar.Locy\n   \'Look north.\n   dt += north\n   \'Is the tile a door?\n   tile = level.GetTileID(dt.vx, dt.vy)\n   If tile = tdoorclosed Then\n      \'Check to see if it is locked.\n      ret = (level.IsDoorLocked(dt.vx, dt.vy) = TRUE)\n   EndIf\n}}}\n\x3C/div\x3E\nWe first get the current character position and load that into the vector object. We then call the overloaded /+=/ operator of the vector object to get the new coordinates to check. We grab the tile id of the location and check to see if the id is a closed door. If it is, we check for a locked state in /IsDoorLocked/. \n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Returns True if door is locked.\nFunction levelobj.IsDoorLocked(x As Integer,y As Integer) As Integer\n   Return _level.lmap(x, y).doorinfo.locked\nEnd Function\n}}}\n\x3C/div\x3E\nAs you can see we just return the locked state of the passed location. This flag is passed back to GetLockedDoor which in turn passes it back to the bash subroutine. Bashing is one method of opening a door. The character can pick the lock too, so let\'s look at this method next.\n\nBefore we look at the pick lock code, we need to add some lock picking tools to the game. I made lock picking tools a supply item so we can leverage our existing code base. Here are the new lock pick items.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Supply item ids.\nEnum supplyids\n   supSupplyNone  \'No supply id.\n   supHealingHerb \'Healing herb-50% of max HP healing effect.\n   supHunkMeat    \'25% of max HP healing effect.\n   supBread       \'10% of max HP healing effect.\n   supManaOrb     \'10% of max mana to mana total.\n   supLockPick    \'Lock pick set.\n   supSkeletonKey \'Skeleton key.\nEnd Enum\n}}}\n\x3C/div\x3E\nFor some variety, we have added lock picks and skeleton keys. Both items can open a lock if wielded while standing next to a locked door. Even though these items are supply items, they need to be wielded like a weapon, so we need to update the supply type for wield positions.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Supply type def.\nType supplytype\n   id As supplyids      \'This indicates what sypply is in the type.\n   desc As String * 30  \'The supply effect.\n   evaldr As Integer    \'Evaluation difficulty rating. Used to evaluate magical effects: 0 = nonMagic.\n   eval As Integer      \'True if item has been evaluated.\n   spell As spelltype     \'The type of magical effect.\n   noise As Integer     \'The amount of noise item generates, includes use and in character inventory.\n   use As itemuse       \'How the item is used.\n   sslot(1 To 2) As wieldpos \'For lock picks and skeleton keys.\nEnd Type\n}}}\n\x3C/div\x3E\nNotice that we have added the /sslot/ field and this behaves just like the wield slot of a weapon. We can see this in the updated supply generation routine.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Generate new supply item.\nSub GenerateSupplies(inv As invtype, currlevel As Integer)\n\n...\n\n      Case supLockPick    \'Lock pick set.\n         inv.desc = "Lock Pick"\n         inv.supply.desc = "Opens Lock"\n         inv.supply.noise = 3\n         inv.icon = Chr(168)\n         inv.iconclr = fbSilver\n         inv.supply.eval = FALSE\n         inv.supply.use = useWieldWear\n         inv.supply.sslot(1) = wPrimary\n         inv.supply.sslot(2) = wSecondary\n      Case supSkeletonKey \'Skeleton key.\n         inv.desc = "Skeleton Key"\n         inv.supply.desc = "Opens Lock"\n         inv.supply.noise = 1\n         inv.icon = Chr(13)\n         inv.iconclr = fbSilver\n         inv.supply.eval = FALSE\n         inv.supply.use = useWieldWear\n         inv.supply.sslot(1) = wPrimary\n         inv.supply.sslot(2) = wSecondary\n      Case Else\n         inv.desc = "Uknown Supply"\n         inv.supply.noise = 0\n         inv.icon = Chr(63)\n         inv.iconclr = fbYellow\n         inv.supply.eval = FALSE\n         inv.supply.use = useNone\n   End Select\nEnd Sub\n}}}\n\x3C/div\x3E\nHere are the two new supply items and their initialization data. Notice that we are setting the wield positions to /wPrimary/ and /wSecondary/ and the use flag to /useWieldWear/. With these data items set, the character can wield these items just like a weapon, and the new equip code will behave as expected for these items. As I stated in the last chapter, we want to avoid exceptions if at all possible, since exceptions, by definition, complicate the code. These simple additions to a supply item will ensure that the lock pick and skeleton key will integrate seamlessly into our code. Of course, the other inventory routines need some minor updates to handle these items as well.\n\nNow, we can look at the pick lock code.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Attempt to pick a lock.\nSub PickLock\n   Dim As vec dt\n   Dim As Integer ret = FALSE, proll, haslp = FALSE\n   Dim As invtype inv\n   \n   \'Get locked door if any.\n   ret = GetLockedDoor(dt)\n   \'If we found a door, check to see if skeleton key or lock pick in hand.\n   If ret = TRUE Then\n      \'Check primary slot.\n      pchar.GetInventoryItem wPrimary, inv\n      \'Lock pick and skeleton key are supplies.\n      If inv.classid = clSupplies Then\n         If (inv.supply.id = supLockPick) Or (inv.supply.id = supSkeletonKey) Then\n            \'Have lock pick.\n            haslp = TRUE\n         End If\n      End If\n      \'Check secondary slot.\n      If haslp = FALSE Then\n         \'Check secondary slot.\n         pchar.GetInventoryItem wSecondary, inv\n         \'Lock pick and skeleton key are supplies.\n         If inv.classid = clSupplies Then\n            If (inv.supply.id = supLockPick) Or (inv.supply.id = supSkeletonKey) Then\n               \'Have lock pick.\n               haslp = TRUE\n            End If\n         EndIf\n      EndIf\n      \'If lock pick or skeleton key then try and open door.\n      If haslp = TRUE Then\n         \'Check to see if item is evaluated.\n         If IsEval(inv) = TRUE Then\n            \'If item is evaluated the dr is used to supplement the attempt roll.\n            proll = pchar.CurrDex + pchar.BonDex + inv.supply.evaldr \n         Else\n            \'Not evaluated.\n            proll = pchar.CurrDex + pchar.BonDex\n         EndIf\n         \'Attempt to open locked door.\n         ret = level.OpenLockedDoor(dt.vx, dt.vy, proll)\n         If ret = TRUE Then\n            PrintMessage "You opened the door."\n         Else\n            PrintMessage "You failed to open the door."\n         EndIf\n      Else\n         PrintMessage "You must equip a lock pick or skeleton key to open a locked door."\n      EndIf\n   Else\n      PrintMessage "No locked doors found."\n   EndIf\nEnd Sub\n}}}\n\x3C/div\x3E\nThis is a bit more complicated than the bash door code, but most of it is checking for the presence of a lock pick or skeleton key. The first thing we do is to check for a locked door using our function GetLockedDoor. If we find a door, then we need to check to see if the character is wielding either of the lock picking tools. We need to check both the primary and secondary positions since the tool may be in either slot. If the character isn\'t holding a lock pick tool, the variable /haslp/ will be FALSE and we tell the player they need to equip a tool before attempting to pick a lock. If they are carrying the tool, then we check to see if it is evaluated.\n\nThis is an important step. We use the character\'s Dexterity attribute as a base for the character roll and if the tool has been successfully evaluated, then the character gets a bonus equal to the evaluation difficulty rating. If the tool isn\'t evaluated, then they do not receive the bonus. This makes the evaluation process have some meaning when it comes to these two tools. If there wasn\'t a bonus, then the evaluation process would be meaningless, and the player wouldn\'t even bother. Actions should always have consequences, good or bad, so we should reward the player for their effort in evaluating the tool. Once we calculate the character\'s roll data we pass it along to the OpenLockedDoor routine to get the result. \n\nSince trying to open a door is an action that requires the character\'s skill, we should also reward the character for a successful attempt, so we have updated the OpenLockedDoor routine to give the player some experience points on a successful open.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Attempts to open a locked door. \nFunction levelobj.OpenLockedDoor(x As Integer, y As Integer, dr As Integer) As Integer\n   Dim As Integer ret = TRUE, ddr, rolld, rollp\n   Dim tid As terrainids\n   \n   \'Make sure we have a door.\n   tid = GetTileID(x, y)\n   If tid = tDoorClosed Then\n     If IsDoorLocked(x, y) = TRUE Then\n        \'Get the difficulty rating of the door.\n        ddr = _level.lmap(x, y).doorinfo.lockdr\n        \'Get the rolls.\n        rollp = RandomRange(1, dr)\n        rolld = RandomRange(1, ddr)\n        If rollp \x3E rolld Then\n           \'Open door.\n           _level.lmap(x, y).doorinfo.locked = FALSE\n           SetTile x, y, tdooropen\n           \'Give the character some experience points.\n           pchar.CurrXP = pchar.CurrXP + ddr \n        Else\n           \'Didn\'t open the door.\n           ret = FALSE\n        EndIf\n     EndIf\n   EndIf\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nHere you can see that if the character is successful, they get an amount of experience points equal to the door\'s difficulty rating. This will also work with the OpenDoor spell as well, which gives the player some incentive to utilize the spell if available.\n\nLocked doors may seem a trivial feature, but they help increase the interest of the game. Locked doors are a challenge that the character, and by extension, the player must overcome. It is really just a low-level puzzle, but it helps to keep some unexpected twists in the game. By occasionally putting in a locked door we offer the player a challenge to overcome. As long as we don\'t over do it, this sort of low-level puzzle keeps the game interesting, and the player interested in the game.\n',
'= 37: Closing Doors\nAt this point we can open doors, bash open doors and pick the lock on doors. Next we need to add the ability to close a door. This also gives us the opportunity to generalize our door code. Right now the door code we added was specific to opening a door, but with the need to close the door as well as open, we need to take a more general approach to our door code. First, let\'s look at the close door code.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Closes door.\nSub CloseDoor ()\n   Dim As vec dt\n   Dim As Integer ret\n   \n   \'Look for open door.\n   ret = GetDoorState(dt, dsOpen)\n   If ret = FALSE Then\n      PrintMessage "No open doors found."\n   Else\n      \'Make sure we don\'t have a monster in the doorway.\n      If level.IsMonster(dt.vx, dt.vy) = FALSE Then\n         \'Set the door to closed.\n         level.SetDoorState(dt.vx, dt.vy, dsClosed)\n      Else\n         PrintMessage "The door is blocked."\n      End If\n   EndIf\nEnd Sub\n}}}\n\x3C/div\x3E\nThis is a fairly simple subroutine. It calls a new function /GetDoorState/ which is actually the old /GetLockedDoor/ function. We have also added a new subroutine to our level object, /SetDoorState/ which, as the name implies, sets a door to a new state. Notice that we are checking to make sure that the doorway is free of monsters. You can\'t close a door if a monster is standing in it!\n\nLet\'s look at the new GetDoorState function and see how we have generalized the code.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Looks for a door at each compass point.\nFunction GetDoorState(dt As vec, dstate As doorstates = dsLocked) As Integer\n   Dim As Integer ret = FALSE\n   \n   \'Look at the four compass points, north east, south west.\n   For i As compass = north To west Step 2\n      \'Check for a door next to player.\n      dt.vx = pchar.Locx\n      dt.vy = pchar.Locy\n      \'Get the new location.\n      dt += i\n      \'Get door state.\n      ret = DoorState(dt, dstate)\n      If ret = TRUE Then \n         Exit For\n      EndIf\n   Next\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nGetDoorState looks at each of the four compass points to get the coordinates for that location. Instead of checking each compass point individually like we did in GetLockedDoor, we are now using a For-Next loop which iterates through the correct compass points by stepping 2 values. This works because of the way we have set up our compass enumeration.\n\n\x3Cdiv class="code"\x3E\n/vec.bi/\n{{{\n\'Compass directions\nEnum compass\n  north\n  neast\n  east\n  seast\n  south\n  swest\n  west\n  nwest\nEnd Enum\n}}}\n\x3C/div\x3E\nWe start the enumeration with /north/ and work our way, in order, around the different compass points. North starts at 0 in the enumeration and east is 2, south is 4 and west is 6. Since each point we want is a multiple of two, we can use a step value of 2 in our For-Next to get each compass point value. If you compare what we had before with this new code, you can see we were able to cut a lot of redundant code.\n\nGetDoorState also has a different parameter list than the old function. It includes a doorstates value which tells us the type of door to look for in the code. Doorstates is a new enumeration in our map code.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Door states.\nEnum doorstates\n   dsNone\n   dsLocked\n   dsOpen\n   dsClosed\nEnd Enum\n}}}\n\x3C/div\x3E\nHere we have three states defined, locked, open and closed. We pass one of these states to GetDoorState which calls a new function /DoorState/ that returns TRUE if the door state exists or FALSE if it doesn\'t.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Returns a door state.\nFunction DoorState(dt As vec, dstate As doorstates) As Integer\n   Dim As Integer ret = FALSE\n   Dim As terrainids tile\n   \n   \'Get the tile at location.\n   tile = level.GetTileID(dt.vx, dt.vy)\n   \'What state are we looking for.\n   If dstate = dsLocked Then\n   If tile = tDoorClosed Then\n         \'Check to see if it is locked.\n         ret = (level.IsDoorLocked(dt.vx, dt.vy) = TRUE)\n      End If\n   ElseIf dstate = dsClosed Then\n      If tile = tDoorClosed Then\n         \'Check to see if it is locked.\n         ret = TRUE\n      End If\n   ElseIf dstate = dsOpen Then\n      \'Look for open door.\n   If tile = tDoorOpen Then\n         ret = TRUE\n      EndIf\n   EndIf\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nThe first thing we need to do is to check to make sure we are looking at a door. If we are, then we compare the door to the passed state. If we are looking for a locked door we check for a closed door and return the lock state. If we are looking for a closed door, we return true if the tile id is a closed door. For an open door, we check to see if the tile id is an open door. With this code we can check all the possible states of a door.\n\nWhat about the bash and pick lock routines? All that we need to do is to change the call from GetLockedDoor to GetDoorState and we are good to go.\n\nIn the CloseDoor subroutine we called /SetDoorState/ to close an open door. Let\'s look at this subroutine next.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Sets the door state.\nSub levelobj.SetDoorState(x As Integer, y As Integer, state As doorstates)\n   Dim tid As terrainids\n   \n   \'Make sure we have a door.\n   tid = GetTileID(x, y)\n   If tid = tDoorClosed Then\n      If state = dsOpen Then\n         _level.lmap(x, y).terrid = tDoorOpen\n      ElseIf state = dsLocked Then\n         _level.lmap(x, y).doorinfo.locked = TRUE\n	 _level.lmap(x, y).doorinfo.lockdr = _level.numlevel\n	_level.lmap(x, y).doorinfo.dstr = _level.numlevel * 10\n      EndIf\n   ElseIf tid = tDoorOpen Then\n      If state = dsClosed Then\n         _level.lmap(x, y).terrid = tDoorClosed\n      ElseIf state = dsLocked Then\n         _level.lmap(x, y).terrid = tDoorClosed\n         _level.lmap(x, y).doorinfo.locked = TRUE\n	 _level.lmap(x, y).doorinfo.lockdr = _level.numlevel\n	_level.lmap(x, y).doorinfo.dstr = _level.numlevel * 10\n      EndIf\n   End If\n   \nEnd Sub\n}}}\n\x3C/div\x3E\nThe first thing we need to do is to get the tile id for the passed coordinates. We then check to see if it is in fact a door, either open or closed. We then look at the passed state and set the door to the passed state. Notice our enumeration is doing double duty for us. We are using it to check the current state of the door, and to change the state of a door. The passed enumeration value here is the door state we want the door to be in. If the door is closed and we want it opened, we pass dsOpen. If open and we want it closed, we pass dsClosed. We can also lock doors that are either closed or open. For a closed door, we set the lock properties. If the door is open, we close it and then set the lock properties.\n\nBy generalizing our door code, we now have the ability to change door states during the game. This might come in handy for traps, or giving monsters the ability to open and close doors. Once again, we have made our code cleaner and have paved the way for future enhancements. Coding is a process of creation and refinement, so even though our initial code may work for us, we can always revisit code to refine the process and make the program better.\n',
'= 38: Traps\nNo dungeon is complete without traps, and the /Dungeon of Doom/ is no exception. In this chapter we will implement a set of traps that correspond to the various armor defense spell we put in place when we added armor magic. This gives us and the player the opportunity to utilize those spells.\n\nAs we always do, we start by defining our new trap data type.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Trap ids.\nEnum trapid\n   trpNone\n   trpBlade  \'Slash trap.\n   trpHammer \'Crush trap.\n   trpSpike  \'Pierce trap.\n   trpEnergy \'Energy trap.\n   trpFire   \'Fire trap.\n   trpAcid   \'Acid trap. \nEnd Enum\n\n\'Trap types.\nType traptype\n   id As trapid           \'The trap id.\n   icon As String * 1     \'Map icon.\n   iconcolor As UInteger  \'Icon color.\n   desc As String * 20    \'The trap description.\n   sprung As Integer      \'FALSE = not triggered or found.\n   dr As Integer          \'The trap DR; compared to character agility.\n   dam As Integer         \'Damage trap does.\n   damtype As weapdamtype \'The type of damage.\nEnd Type\n\n\'Map info type\nType mapinfotype\n  terrid As terrainids  \'The terrain type.\n  monidx As Integer     \'Index into monster array.\n  visible As Integer    \'Character can see cell.\n  seen As Integer       \'Character has seen cell.\n  doorinfo As doortype  \'Door information.\n  sndvol As Integer     \'Sound volume at current tile.\n  target As targettype  \'Target and projectile map.\n  trap As traptype      \'Trap.\nEnd Type\n}}}\n\x3C/div\x3E\nThe /trapid/ enumeration lists the different types of traps that can be in the dungeon and they all correspond to the armor spells. The character\'s current armor value and spell level, if present and of the right type, will moderate any damage done by a trap.\n\nThe /traptype/ is our trap data definition, and as you can see, it follows the format of our other data definitions. Most of the fields should be self-explanatory. The /sprung/ field is a TRUE/FALSE flag that indicates if the trap has been spring or not. If the trap hasn\'t been sprung, it is invisible and can do damage. A sprung trap is visible and does no damage.\n\nThe next section of code actually builds a trap for the map.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Creates a trap.\nSub levelobj._BuildTrap(trp As traptype)\n   \n   \'Get the trap type.\n   trp.id = RandomRange(trpBlade, trpAcid) \n   trp.icon = Chr(240)\n   trp.iconcolor = fbRedBright\n   trp.sprung = FALSE\n   trp.dr = _level.numlevel\n   trp.dam = RandomRange(1, _level.numlevel)\n   \'Set data based on type.\n   If trp.id = trpBlade Then\n      trp.desc = "Blade Trap"\n      trp.damtype = wdSlash\n   ElseIf trp.id = trpHammer Then\n      trp.desc = "Hammer Trap"\n      trp.damtype = wdCrush\n   ElseIf trp.id = trpSpike Then\n      trp.desc = "Spike Trap"\n      trp.damtype = wdPierce\n   ElseIf trp.id = trpEnergy Then\n      trp.desc = "Energy Bolt Trap"\n      trp.damtype = wdPierce\n   ElseIf trp.id = trpFire Then\n      trp.desc = "Fire Trap"\n      trp.damtype = wdFire\n   ElseIf trp.id = trpAcid Then\n      trp.desc = "Acid Trap"\n      trp.damtype = wdAcid\n   EndIf\n   \nEnd Sub\n}}}\n\x3C/div\x3E\nHere we set generate a random trap type, and set the type and damage, along with the trap description and damage type based on the type of trap. Sprung is set to FALSE which loads the trap and makes it active. Next we need place the traps on the map.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Transfer grid data to map array.\nSub levelobj._DrawMapToArray()\n  Dim As Integer i, x, y, pr, rr, rl, ru, kr, nid, moncnt, tcnt\n  Dim As mcoord ncoord\n\n...\n\n  \'Add some traps to the level.\n  tcnt = _level.numlevel / 10\n  \'Add at least one trap.\n  If tcnt \x3C 1 Then tcnt = 1\n  \'Add in the traps.\n  For i = 1 To tcnt\n    x = RandomRange(2, mapw - 1)\n    y = RandomRange(2, maph - 1)\n    \'See if we have a floor tile.\n    If _level.lmap(pchar.Locx, pchar.Locy).terrid = tfloor Then\n       _BuildTrap _level.lmap(pchar.Locx, pchar.Locy).trap\n    EndIf\n  Next\nEnd Sub\n}}}\n\x3C/div\x3E\nOnce all the rooms and corridors are drawn on the map array, we get the number of traps on the level. The number of traps will be at most 5, and that on the final level, so we are not getting crazy here with traps. The minimum number of traps per level will be one, however a level may not have a trap at all; it depends on the tile at the random x and y coordinates. If it is a floor tile, then a trap gets placed. If not, then we move on. Using this scheme, the character will encounter an occasional trap, but it won\'t be overwhelming.\n\nTraps are triggered by walking over them, so lets look at our updated character movement code.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Move the character based on the compass direction.\nFunction MoveChar(comp As compass) As Integer\n   Dim As Integer ret = FALSE, block, tdam, snd, trp, tdr\n   Dim As vec vc = vec(pchar.Locx, pchar.Locy) \'Creates a vector object.\n   Dim As terrainids tileid\n   Dim As weapdamtype tdamtype\n   Dim As String tdesc\n\n...\n\n         \'Move character.\n         If block = FALSE Then\n            \'Set the new character position.\n            pchar.Locx = vc.vx\n            pchar.Locy = vc.vy\n            ret = TRUE\n            \'Generate the sound map.\n            level.ClearSoundMap\n            snd = pchar.GetNoise()\n            level.GenSoundMap(pchar.Locx, pchar.Locy, snd)\n            \'Check for trap at current location.\n            trp = level.IsTrap(pchar.Locx, pchar.Locy, tdam, tdamtype, tdr, tdesc)\n            If trp = TRUE Then\n               pchar.ApplyTrap tdr, tdam, tdamtype, tdesc\n            EndIf\n\n...\n\n}}}\n\x3C/div\x3E\nIf the character walks over a trap, then the trap is triggered and the damage is applied to the character. To determine if the player walked over a trap, we created the IsTrap function in the level object.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Returns TRUE if trap at x, y.\nFunction levelobj.IsTrap(x As Integer, y As Integer, ByRef dam As Integer, ByRef damtype As weapdamtype, _\n                         ByRef tdr As Integer, tdesc As String) As Integer\n   Dim As Integer ret\n   \n   \'Do we have a trap at the current location?\n   If _level.lmap(x, y).trap.id \x3C\x3E trpNone Then\n      \'Is it sprung yet?\n      If _level.lmap(x, y).trap.sprung = FALSE Then\n         \'There is a trap. Spring it and return damage amt and type.\n         ret = TRUE\n         _level.lmap(x, y).trap.sprung = TRUE\n         dam = _level.lmap(x, y).trap.dam\n         damtype = _level.lmap(x, y).trap.damtype\n         tdr = _level.lmap(x, y).trap.dr\n         tdesc = _level.lmap(x, y).trap.desc\n      EndIf\n   EndIf\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nHere we look at the mapinfo array and see if the tile has a trap id. If it does, we collect the trap information and pass that data back to the calling function through the /ByRef/ parameters. The function returns TRUE if a trap was found so that the caller knows a trap was triggered. Notice that the sprung flag is set to TRUE. This will prevent the trap from triggering again and will make it visible on the map.\n\nIf a trap is triggered, we need to apply the damage to the character, and we do that in /ApplyTrap/.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n\'Applies trap damage.\nSub character.ApplyTrap(tdr As Integer, tdam As Integer, tdamtype As weapdamtype, tdesc As String)\n   Dim As Integer troll, proll, pagl, lvl, dam\n   Dim As weapdamtype armeff\n    \n   \'Get the trap and character rolls.\n   troll = RandomRange(1, tdr)\n   \'Get the character agility and bonus amounts.\n   pagl = CurrAgl + BonAgl\n   proll = RandomRange(1, pagl)\n   \'See who wins.\n   If proll \x3E troll Then\n      \'Character evades trap and gains some experience.\n      PrintMessage "You evaded the " & tdesc & "!"\n      CurrXP = CurrXP + tdr\n   Else\n      \'Set base damage.\n      dam = tdam\n      \'Get armor spell effect if any.\n      If _cinfo.cwield(wArmor).classid = clArmor Then\n         \'Apply armor value to damage.\n         dam = tdam - (tdam * _cinfo.cwield(wArmor).armor.dampct)\n         \'Get spell effect.\n         armeff = _cinfo.cwield(wArmor).armor.spelleffect\n         \'Get spell level.\n         lvl = _cinfo.cwield(wArmor).armor.spell.lvl\n         \'If armor spell matches damage type moderate the damage.\n         If tdamtype = armeff Then\n            \'Get the new damage value.\n            dam = dam - (dam * (lvl / 100))\n         EndIf\n         \'Give at least 1 point of damage.\n         If dam \x3C 1 Then dam = 1\n      EndIf\n      \'Apply damage to character.\n      \n      PrintMessage "The " & tdesc & " did " & dam & " damage points!"   \n   EndIf\nEnd Sub\n}}}\n\x3C/div\x3E\nAs per our standard routine, we generate two rolls, one for the trap and one for the character. The character roll is based on the Agility attribute and any Agility bonuses. The two rolls are compared. If the character wins, the trap misses and the character gets some experience points. If the trap wins, then we need to apply the damage to the character.\n\nIf the character is wearing armor, then we modify the damage based on the armor value. We check to see if the trap damage type matches the armor spell effect {{{If tdamtype = armeff Then}}}. If the two match, we apply an additional damage reduction based on the level of the spell. The spell level is converted to a percentage amount and is used to decrease the trap damage. Once the damage has been modified as necessary, we apply it to the character {{{CurrHP = CurrHP - dam}}}. We always give the trap at least one damage point for winning against the character.\n\nThe last thing we need to do is to display a sprung trap on the map, and we do that in /DrawMap/.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Draws the map on the screen.\nSub levelobj.DrawMap ()\n   Dim As Integer i, j, w = vw, h = vh, x, y, px, py, pct, vis = vh, monid\n   Dim As UInteger tilecolor, bcolor, trpcolor\n   Dim As String mtile, trptile\n   Dim As terrainids tile\n\n...\n\n   \'Print the tile.\n   If _level.lmap(i + x, j + y).visible = TRUE Then\n     \'Print trap icon if trap is sprung.\n     If _level.lmap(i + x, j + y).trap.sprung = TRUE Then\n       \'Get the item symbol.\n       trptile = _level.lmap(i + x, j + y).trap.icon\n       \'Get the item color.\n       trpcolor = _level.lmap(i + x, j + y).trap.iconcolor\n     EndIf\n     PutText trptile, y + 1, x + 1, trpcolor\n     \'Print the item marker.\n     If HasItem(i + x, j + y) = TRUE Then\n...\n\n}}}\n\x3C/div\x3E\nThe trap icon is printed before any items on the map, so if the trap happens to be in the same place as an item,, the item will appear to be on top of the trap, as expected. \n\nNow that we have the occasional trap in place, we need a way to find and disarm them, and we will do that in the next chapter, Searching.\n',
'= 39: Searching\nSearching is a fun feature to add to the game since it always exciting to find something unexpected. It is also quite easy to implement.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Search immediate area around character.\nSub DoSearch ()\n   Dim As vec dt\n   Dim As Integer ret, found = FALSE, dam, tdr, pdat, sroll, proll\n   Dim As weapdamtype tdamtype\n   Dim As String tdesc\n   \n   \'Get the character\'s intelligence factor, which is used in searching.\n   pdat = pchar.CurrInt + pchar.BonInt\n   \'Look at each compass point.\n   For i As compass = north To nwest\n      \'Check for a door next to player.\n      dt.vx = pchar.Locx\n      dt.vy = pchar.Locy\n      \'Get the new location.\n      dt += i\n      \'Check for a trap.\n      ret = level.IsTrap(dt.vx, dt.vy, dam, tdamtype, tdr, tdesc)\n      If ret = TRUE Then\n         proll = RandomRange(1, pdat)\n         sroll = RandomRange(1, tdr)\n         If proll \x3E sroll Then\n            level.DisarmTrap dt.vx, dt.vy\n            \'Give the player some experience.\n            pchar.CurrXP = pchar.CurrXP + tdr\n            PrintMessage "You found and disarmed a " & tdesc & "!"\n            found = TRUE\n         End If   \n      EndIf\n   Next\n   If found = FALSE Then\n      PrintMessage "You did not find anything."\n   EndIf\nEnd Sub\n}}}\n\x3C/div\x3E\nThis should look familiar by now. We are using our vector object to search each tile around the character. Right now the only thing the character can find are traps, so we check to see if the tile has a trap on it. We don\'t bother checking for traps on the tile the character is standing on since it would have been triggered by stepping on it. If the character finds a trap, he can disarm it.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Disarms a trap.\nSub levelobj.DisArmTrap (x As Integer, y As Integer)\n   \n   \'Do we have a trap at the current location?\n   If _level.lmap(x, y).trap.id \x3C\x3E trpNone Then\n      \'Spring trap.\n      _level.lmap(x, y).trap.sprung = TRUE\n   End If\n   \nEnd Sub\n}}}\n\x3C/div\x3E\nDisarming simply sets the sprung flag so the trap won\'t trigger and will display on the map.\n\nSearching will give us a lot possibilities in the game. We could add magically hidden items that are revealed when searched, secret doors that lead to secret rooms, or even invisible monsters. There are a lot of possibilities we can now take advantage of now that the character can search the dungeon.\n',
'= 40: The Wandering Merchant\n\x3Cdiv align="center"\x3E\x3Cimg src=\'images/wmerchant.png\' /\x3E\x3C/div\x3E\n\nThe Wandering Merchant gives the player a chance to spend some of the gold they have acquired while exploring the dungeon. They can also sell the items they pick up along the way to earn some gold to buy other needed items. The Wandering Merchant doesn\'t really wander though, he is stationed next to each entry staircase so the player knows exactly where he is located if he wants to buy or sell any items.\n\nOf course the first thing we need to do is to create a Wandering Merchant. He is really just another terrain type since he doesn\'t move once placed on the map.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Transfer grid data to map array.\nSub levelobj._DrawMapToArray()\n  Dim As Integer i, x, y, pr, rr, rl, ru, kr, nid, moncnt, tcnt\n  Dim As mcoord ncoord\n	\n...\n  \'Set up the stairs up.\n  _level.lmap(pchar.Locx, pchar.Locy).terrid = tstairup\n  \'Add wandering merchant.\n  _level.lmap(pchar.Locx - 1, pchar.Locy).terrid = twmerch\n...\n}}}\n\x3C/div\x3E\nHere is where we place the Merchant by setting the terrain type /twmerch/ next to the up stairs. The player interacts with the Merchant by bumping into him.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Move the character based on the compass direction.\nFunction MoveChar(comp As compass) As Integer\n   Dim As Integer ret = FALSE, block, tdam, snd, trp, tdr\n   Dim As vec vc = vec(pchar.Locx, pchar.Locy) \'Creates a vector object.\n   Dim As terrainids tileid\n   Dim As weapdamtype tdamtype\n   Dim As String tdesc\n...\n  \'Get tile id.\n  tileid = level.GetTileID(vc.vx, vc.vy)\n  Select Case tileid\n...\n    Case twmerch\n      PrintMessage "Wandering Merchant"\n      \'Show the wandering merchant screen.\n      DoWanderingMerchant\n...\n}}}\n\x3C/div\x3E\nThe /DoWanderingMerchant/ subroutine displays the various Merchant dialogs that the player can use to buy or sell.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Shows the wandering merchant screen.\nSub DoWanderingMerchant ()\n   Dim As String ret\n   Dim As invtype sinv(), binv()\n   Dim As tWidgets.listtype sinvlist(), binvlist()\n   Dim As Integer cnt, idx()\n   \n   \'Build the character buy.\n   BuildMerchantSellList sinv(), sinvlist()\n   Do\n      ret = ShowMerchantInput()\n      \'Player wants to buy item.\n      If ret = "b" Then\n         ShowMerchantSellList sinv(), sinvlist()\n      ElseIf ret = "s" Then\n         \'Build the character sell list.\n         cnt = BuildMerchantBuyList(binv(), binvlist(), idx())\n         If cnt \x3E 0 Then\n            ShowMerchantBuyList binv(), binvlist(), idx()\n         Else\n            PrintMessage "Nothing to sell."\n         End If\n      EndIf\n\n   Loop Until ret = "q"\nEnd Sub\n}}}\n\x3C/div\x3E\nHere is the control subroutine for the Merchant. Notice that we are just calling some new subroutines and functions that correspond to buying and selling items. The process isn\'t complicated. We build two inventory lists, one for what the Merchant is selling and one for what the player wants to sell. We then display the appropriate list based on what the player selected on the input screen. The list that the Merchant is selling doesn\'t change, so we build that before we get into the Do-Loop. The list that the player is selling has to be built at each iteration since the list will change as the player sells items. Let\'s look at these new subs and functions.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Builds the sell list.\nSub BuildMerchantSellList(inv() As invtype, invlist() As tWidgets.listtype)\n   Dim As Integer cnt = 0\n   Dim spl As spelltype\n   Dim As String res, magic\n   \n   For i As classids = clSupplies To clSpellBook\n      \'Get an item from each category.\n      cnt += 1\n      ReDim Preserve inv(1 To cnt) As invtype\n      ReDim Preserve invlist(1 To cnt) As tWidgets.listtype\n      \'Clear inventory item.\n      ClearInv inv(cnt)\n      \'Generate item in category.\n      If i = clSpellBook Then\n         Do\n            GenerateItem inv(cnt), level.LevelID, i\n         Loop Until inv(cnt).spellbook.id \x3C\x3E bkSpellBlank\n      Else\n         GenerateItem inv(cnt), level.LevelID, i   \n      EndIf\n      \'Set the eval to true.\n      SetInvEval inv(cnt), TRUE\n      \'Check for spell item.\n      spl = pchar.GetItemSpell(inv(cnt))\n      If spl.id \x3C\x3E splNone Then\n         magic = " (M)"\n      Else\n         magic = ""\n      EndIf\n      \'Build the display string.\n      invlist(cnt).id = cnt\n      If inv(cnt).classid = clSpellbook Then\n         invlist(cnt).text = inv(cnt).spellbook.spell.splname & " Price: " & inv(cnt).buy\n      Else\n         invlist(cnt).text = inv(cnt).desc & magic & " Price: " & inv(cnt).buy\n      EndIf\n   Next\n   \nEnd Sub\n}}}\n\x3C/div\x3E\nThis is the list that the merchant is selling to the player. We iterate through each item category based on the class id {{{For i As classids = clSupplies To clSpellBook}}} and then generate an item for the current class {{{GenerateItem inv(cnt), level.LevelID, i}}}. We have a Do-Loop for the spell books since we don\'t want to sell a blank spell book.\n\nOnce we generate an item we mark it as evaluated {{{SetInvEval inv(cnt), TRUE}}}. We want to let the player know if an item has a spell associated with it, since it will cost more than a normal item, so we get the spell, if any, with {{{spl = pchar.GetItemSpell(inv(cnt))}}}. If it has a spell we set the /magic/ variable to /(M)/ for the display. We then build a description and display the price of the item.\n\nThe inventory and list arrays are dynamic arrays so we can load them with variable amounts of data. Once we have finished building the list, exit back to the DoWanderingMerchant sub so that the list can be displayed to the user.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Shows sell list.\nSub ShowMerchantSellList(inv() As invtype, invlist() As tWidgets.listtype)\n   Dim As tWidgets.tList lst\n   Dim As tWidgets.btnID btn\n   Dim As Integer iitem, cidx\n   \n   \'Set up the list window.\n   lst.Title = "Select Item to Buy (Gold: " & pchar.CurrGold & ")"\n   lst.Prompt = "Use Up or Dn key to cycle items, Enter to select."\n   \'Get the player selection.\n   btn = lst.Listbox(invlist(), iitem)\n   If btn = tWidgets.btnID.gbnOK Then\n      \'Make sure the character has enough gold.\n      If inv(iitem).buy \x3E pchar.CurrGold Then\n         PrintMessage "You do not have enough gold to buy item."\n      Else\n         \'Look for free inventory slot.\n         cidx = pchar.GetFreeInventoryIndex\n         \'If found a slot load inventory item.\n         If cidx \x3C\x3E -1 Then\n            pchar.AddInvItem cidx, inv(iitem)\n            pchar.CurrGold = pchar.CurrGold - inv(iitem).buy\n            PrintMessage "You bought " & inv(iitem).desc & " for " & inv(iitem).buy & " gold." \n         Else\n            PrintMessage "No empty inventory slots."\n         End If\n      EndIf\n   EndIf\nEnd Sub\n}}} \n\x3C/div\x3E\nHere is the sell list where the player can purchase items. The list widget uses the /invlist()/ and the /iitem/ variable is the item that the player selected from the list. Once the player makes a selection, the cost of the item is deducted from the character\'s current gold amount {{{pchar.CurrGold = pchar.CurrGold - inv(iitem).buy}}} and the item is added to the character\'s inventory {{{pchar.AddInvItem cidx, inv(iitem)}}} providing there is an empty slot to place the item.\n\nIf the character does not have enough gold, they can\'t purchase an item and the player is sent a message telling him of the fact. The item is also not purchased if there are no empty inventory slots available.\n\nThe process is the same when the player wants to sell an item.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Builds buy list from inventory items.\nFunction BuildMerchantBuyList(inv() As invtype, invlist() As tWidgets.listtype, idx() As Integer) As Integer\n   Dim As Integer cnt\n   Dim spl As spelltype\n   Dim As String res, magic\n   Dim As invtype tinv\n   \n   For i As Integer = pchar.LowInv To pchar.HighInv\n      \'Get character inventory item.\n      pchar.GetInventoryItem i, tinv\n      \'Make sure we have something.\n      If (tinv.classid \x3C\x3E clNone) And (tinv.classid \x3C\x3E clUnavailable) then \n         \'Get an item from each category.\n         cnt += 1\n         ReDim Preserve inv(1 To cnt) As invtype\n         ReDim Preserve invlist(1 To cnt) As tWidgets.listtype\n         ReDim Preserve idx(1 To cnt) As Integer\n         \'Save the index.\n         idx(cnt) = i\n         \'Clear inventory item.\n         ClearInv inv(cnt)\n         \'Set item in list.\n         inv(cnt) = tinv\n         \'Check for spell item.\n         spl = pchar.GetItemSpell(inv(cnt))\n         If spl.id \x3C\x3E splNone Then\n            magic = " (M)"\n         Else\n            magic = ""\n         EndIf\n         \'Build the display string.\n         invlist(cnt).id = cnt\n         If inv(cnt).classid = clSpellbook Then\n            invlist(cnt).text = inv(cnt).spellbook.spell.splname & " Price: " & inv(cnt).sell\n         Else\n            invlist(cnt).text = inv(cnt).desc & magic & " Price: " & inv(cnt).sell\n         EndIf\n      End If\n   Next\n   \n   Return cnt\nEnd Function\n}}}\n\x3C/div\x3E\nHere we iterate through the character\'s inventory {{{For i As Integer = pchar.LowInv To pchar.HighInv}}} looking for items that the player might want to sell. Only those items in the backpack can be sold; held items are not considered for sell. If we find an item in the character\'s inventory, then we go through the same process as the sell list, building up the description and selling price. The player is then presented with this list to select items to sell.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Displays the merchant buy list.\nSub ShowMerchantBuyList(inv() As invtype, invlist() As tWidgets.listtype, idx() As Integer)\n   Dim As tWidgets.tList lst\n   Dim As tWidgets.btnID btn\n   Dim As Integer iitem, cidx\n   Dim As invtype tinv\n   \n   \'Set up the list window.\n   lst.Title = "Select Item to Sell "\n   lst.Prompt = "Use Up or Dn key to cycle items, Enter to select."\n   \'Get the player selection.\n   btn = lst.Listbox(invlist(), iitem)\n   If btn = tWidgets.btnID.gbnOK Then\n      pchar.CurrGold = pchar.CurrGold + inv(iitem).sell\n      PrintMessage "You sold " & inv(iitem).desc & " for " & inv(iitem).sell & " gold." \n      \'Clear the inventory item.\n      pchar.GetInventoryItem idx(iitem), tinv\n      ClearInv tinv\n      pchar.AddInvItem idx(iitem), tinv\n   EndIf\nEnd Sub\n}}}\n\x3C/div\x3E\nHere the list is displayed to the player and they select the items they want to sell. The selling price of the item is then added to the character\'s current gold amount. The sell price of an item is less than the cost of an item when it is bought, just as we see in real life. Once the item is sold, the character\'s inventory slot is cleared. Sold items cannot be recovered and do not show up in the sell list.\n\nWhere do the buy and sell prices come from? We have added some new fields to our inventory items, a /buy/ field and a /sell/ field.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Inventory type.\nType invtype\n   classid As classids     \'This indicates what class is in the union.\n   desc As String * 30     \'Plain text description.\n   icon As String * 1      \'This is the item icon.\n   iconclr As UInteger     \'This is the item\'s icon color.\n   buy As Integer          \'How much it costs to buy.\n   sell As Integer         \'How much it sells for.\n   Union                   \'Union of item types.\n      gold As goldtype     \'Gold coins. \n      supply As supplytype \'Supplies.\n      armor As armortype   \'Armor\n      shield As shieldtype \'Shield\n      weapon As weapontype \'Weapon\n      ammo As ammotype     \'Ammo for projectile weapons.\n      potion As pottype    \'Potion.\n      jewelry As jewelrytype \'Rings and necklaces\n      spellbook As spellbktype \'Spell book.\n      spell As spelltype     \'Spells\n   End Union\nEnd Type\n}}}\n\x3C/div\x3E\nEach of the generation routines needs to be updated to have a buy and sell price so it can be displayed in the Wandering Merchant list. Here is an example of the process.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Generate a weapon.\nSub GenerateWeapon(inv As invtype, currlevel As Integer, wpid As weaponids = wpNone)\n   Dim item As weaponids \n   Dim As Integer isMagic \n...\n   \'Set the buy and sell amounts.\n   inv.buy = inv.weapon.dam * 10\n   inv.sell = inv.weapon.dam * 8\n   If inv.weapon.spell.id \x3C\x3E splNone Then\n      inv.buy += 100\n      inv.sell += 90\n   EndIf\nEnd Sub\n}}}\n\x3C/div\x3E\nHere the cost of a weapon is based on the damage it does. If a weapon has a spell associated with it, the buy price is bumped up by 100 and the sell price by 90 gold. Each of the other generation routines have code similar to this.\n\nThe Wandering Merchant is actually a fairly simple feature to add the program, but it helps enrich the game environment. Picking up items now has a lot more value associated with it then it did before. The player can buy needed items, and generate gold by selling unwanted items. It also increases the interactivity of the game, making the player more involved in managing the limited resources of the game. The best part is that the feature is unobtrusive and easy to use and one the player will probably utilize to a great extent while playing the game.\n',
'= 41: Tweaking the Game 3\nOften times when you are in the middle of coding, you forget about the code you have already set up, and you unintentionally scatter similar code in various places, rather then using or extending the code you already have in place. It is always a good idea to step back from time to time and look at the forest rather than the trees and see what you might be missing. In our case, we lost sight of the fact that when we implemented the spell book generation code, we already had a spell routine in place. Instead of putting the spell code in the /GenerateSpell/ subroutine, we put it in the /GenerateSpellBook/ subroutine. \n\nWhy does this make a difference? If we want to attach one of the casting spells to an object, an amulet for example, we can\'t do it, since it resides in the spell book code. It is much better to move the actual spell code out of the spell book code and move it where it should have gone, into the spell generation routine. We\'ll do that with this tweak. It is actually a very simple change, nothing more than a cut and paste operation.\n\nHere is the updated /GenerateSpell/ code.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Generates spell data.\nSub GenerateSpell(spl As spelltype, spllevel As Integer)\n   \n   \'Set the defaults.   \n   spl.lvl = spllevel \'Spell level.\n   spl.splname = GetSpellName(spl.id) \'Sets the spell name.\n   spl.splsname = GetSpellShortName(spl.id)\n   spl.spldesc = GetSpellEffect(spl.id) \'Sets the spell description.\n   spl.lvl = spllevel\n   spl.dam = spllevel \n   spl.manacost = 0\n   \'Set damage/mana on spells.\n   Select Case spl.id\n      Case splAcidFog     \'Spellbook: 5 dam over lvl turns\n         spl.dam = 5\n         spl.manacost = 5  \n      Case splFireCloak   \'Spellbook: Target gets 10 dam over lvl turns\n         spl.dam = 10\n         spl.manacost = 10  \n      Case splLightning   \'Spellbook: 2 * lvl damage (ignores armor)\n         spl.dam = 2\n         spl.manacost = 6  \n      Case splFireBomb    \'Spellbook: Area damage 20 x lvl. Sets monsters on fire lvl turns.\n         spl.dam = 20 \n         spl.manacost = 20  \n      Case splEntangle    \'Spellbook: Immobilze target for lvel turns doing lvl damage each turn.\n         spl.dam = 4\n         spl.manacost = 4  \n      Case splFireball    \'Spellbook: Area damage 10 x lvl. Sets monsters on fire lvl turns.\n         spl.dam = 10\n         spl.manacost = 15  \n      Case splRust        \'Spellbook: Reduces armor by lvl x 10%.\n         spl.manacost = 8  \n      Case splMagicDrain  \'Spellbook: Lower target MDF by lvl% and adds to caster for 1 turn.\n         spl.manacost = 3  \n      Case splPoison      \'Spellbook: Posions target 1 HP for lvl turns.\n         spl.dam = 1\n         spl.manacost = 3  \n      Case splEnfeeble    \'Spellbook: Lowers target combat factors lvl x 10%.\n         spl.manacost = 9  \n      Case splHeal        \'Spellbook: 1% x lvl\n         spl.manacost = 4  \n      Case splMana        \'Spellbook: 1% x lvl (does not consume mana)\n         spl.manacost = 0  \n      Case splRecharge    \'Spellbook: 1 x lvl recharge on wand\n         spl.manacost = 2  \n      Case splFocus       \'Spellbook: +lvl to all combat factors for 1 turn\n         spl.manacost = 12  \n      Case splBlind       \'Spellbook: Blinds target for lvl turns\n         spl.manacost = 6  \n      Case splTeleport    \'Spellbook: Teleport to location lvl distance away (must be visible).\n         spl.manacost = 4  \n      Case splOpen        \'Spellbook: Attempts to open a locked door (lvl vs. DR).\n         spl.manacost = 2  \n      Case splFear        \'Spellbook: Makes monster flee for lvl turns.\n         spl.manacost = 4  \n      Case splConfuse     \'Spellbook: Confuses monster for lvl turns.\n         spl.manacost = 9  \n      Case splCloudMind   \'Spellbook: Target cannot cast spells for lvl turns.\n         spl.manacost = 12  \n      Case splIceStatue   \'Spellbook: Freezes target for lvl turns. If frozen can be killed with single hit.\n         spl.manacost = 14  \n      Case splShatter     \'Spellbook: Destroys target weapon, if any.\n         spl.manacost = 5  \n      Case splShout       \'Spellbook: Stuns all visible monsters for lvl turns.\n         spl.manacost = 12  \n      Case splStealHealth \'Spellbook: Lowers HP lvl% of target and adds to caster.\n         spl.manacost = 8  \n      Case splMindBlast   \'Spellbook: Lowers target MCF and MDF lvl% for lvl turns.\n         spl.manacost = 12  \n      Case splBlink       \'Spellbook: Immune to attacks.    \n         spl.manacost = 4\n      Case Else\n         spl.splname = "Unknown Spell"\n         spl.splsname = "Unknown"\n   End Select\n   \nEnd Sub\n}}}\n\x3C/div\x3E\nAs you can see we just cut the Select Case from the spell book code and pasted it here in the spell code. The only change we needed to make was to replace {{{inv.spellbook.spell}}} to {{{spl}}}. Not too hard at all.\n\nHere is the new spell book code.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Generate new spellbook item.\nSub GenerateSpellBook(inv As invtype, currlevel As Integer)\n   Dim item As spellbkids\n   Dim As Integer r\n   \n   \'Set the class id.\n   inv.classid = clSpellBook\n   \'Two kinds of spell books, blank ones and one that contain a spell.\n   r = RandomRange(1, 10)\n   If r = 1 Then\n      item = bkSpellBlank\n   Else\n      item = bkSpellBook\n   EndIf\n   \'These are the common items.\n   inv.spellbook.evaldr = GetScaledFactor(charint, currlevel)\n   inv.spellbook.eval = FALSE\n   inv.spellbook.id = item\n   inv.iconclr = fbOrange\n   inv.icon = Chr(254)\n   inv.spellbook.use = useRead\n   inv.spellbook.noise = 1\n   inv.desc = "Spell Book"\n   inv.buy = 100\n   inv.sell = 90\n   \'If we have a non-blank, generate a spell for it.\n   If item = bkSpellBook Then\n      \'Get the spell.\n      inv.spellbook.spell.id = RandomRange(splAcidFog, splBlink) \'Spell id.\n      GenerateSpell inv.spellbook.spell, 1\n   Else\n      ClearSpell inv.spellbook.spell\n   End If\n   inv.buy += inv.spellbook.spell.dam + 10\n   inv.sell += inv.spellbook.spell.dam + 8\n   \nEnd Sub\n}}}\n\x3C/div\x3E\nThe Select Case is gone, but otherwise the code is the same.\n\nOf course, this is how we should have coded it in the beginning, but it is easy to get caught up in the details of the program, the trees of the forest to use the analogy above, and lose sight of the big picture, the forest. That is why it is always good to take a step back and look over the code and see if we might be missing something. In this case we were, but it was an easy fix, and now we are in a much better position to utilize the spells in a more general way.',
'= 42: Inscribing Spell Books\n\x3Cdiv align="center"\x3E\x3Cimg src=\'images/inscribe.png\' /\x3E\x3C/div\x3E\n\nBeing able to sell an item to the Wandering Merchant makes all items much more valuable. The exception though is a blank spell book, which is still a throw-away item. However, we can make blank spell books just as valuable as the rest of the items, by using the blank pages to inscribe a new spell. This will make a useless item useful, and will enable the player to pick specific spells to round out their character\'s spell list.\n\nHow do we inscribe spells? This would be a good job for the Wandering Merchant.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Shows the wandering merchant screen.\nSub DoWanderingMerchant ()\n   Dim As String ret\n   Dim As invtype sinv(), binv(), iinv()\n   Dim As tWidgets.listtype sinvlist(), binvlist(), iinvlist()\n   Dim As Integer cnt\n   \n   \'Build the character buy.\n   BuildMerchantSellList sinv(), sinvlist()\n   \'Build the spell list to inscribe on a book.\n   BuildInscribeList iinv(), iinvlist()\n   Do\n      ret = ShowMerchantInput()\n      \'Player wants to buy item.\n      If ret = "b" Then\n         ShowMerchantSellList sinv(), sinvlist()\n      ElseIf ret = "s" Then\n         \'Build the character sell list.\n         cnt = BuildMerchantBuyList(binv(), binvlist())\n         If cnt \x3E 0 Then\n            ShowMerchantBuyList binv(), binvlist()\n         Else \n            PrintMessage "Nothing to sell."\n         End If\n      ElseIf ret = "i" Then\n         ShowInscribeList iinv(), iinvlist()\n      EndIf\n      Sleep 1\n   Loop Until ret = "q"\nEnd Sub\n}}}\n\x3C/div\x3E\nAs you can see, we have added two new subroutines to the code, /BuildInscribeList / and /ShowInscribeList /. We have also added a new option to the input screen, /i/ which indicates that the player wants to (i)nscribe a new spell. Let\'s look at the new code.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Build the spell list to inscribe on a book.\nSub BuildInscribeList(inv() As invtype, invlist() As tWidgets.listtype)\n   Dim As Integer cnt, price\n   \n   For i As spellid = splAcidFog To splBlink\n      cnt += 1\n      \'Expand the arrays.\n      ReDim Preserve inv(1 To cnt) As invtype\n      ReDim Preserve invlist(1 To cnt) As tWidgets.listtype\n      \'Get the spell.\n      inv(cnt).spell.id = i\n      GenerateSpell inv(cnt).spell, 1\n      \'Calculate the price.\n      price = 100 + inv(cnt).spell.dam + RandomRange(1, 10)\n      \'Load the list data.\n      invlist(cnt).id = cnt\n      invlist(cnt).ldata = price\n      invlist(cnt).text = inv(cnt).spell.splname & " Price: " & price & "."\n   Next\n   \nEnd Sub\n}}}\n\x3C/div\x3E\nThe BuildInscribeList subroutine just iterates through all the spell book spells, calls /GenerateSpell/ to create the spell and saves the price information in the list data. We have a new field to the list data structure, /ldata/ which acts as a general data holder for information that may be needed later. It isn\'t used by the list object, so any sort of information could be put into it.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Displays the inscribe list.\nSub ShowInscribeList(inv() As invtype, invlist() As tWidgets.listtype)\n   Dim As invtype binv\n   Dim As Integer bidx = -1, iitem\n   Dim As tWidgets.tList lst\n   Dim As tWidgets.btnID btn\n   \n   \'Make sure the character has a blank spell book in inventory.\n   For i As Integer = pchar.LowInv To pchar.HighInv\n      \'Get the inventory item.\n      pchar.GetInventoryItem i, binv\n      If binv.classid = clSpellBook Then\n         If binv.spellbook.id = bkSpellBlank Then\n            bidx = i\n            Exit For\n         EndIf\n      EndIf\n   Next\n   \'Make sure the character has a blank spell book.\n   If bidx \x3E -1 Then\n      \'Set up the list window.\n      lst.Title = "Select Spell to Inscribe "\n      lst.Prompt = "Use Up or Dn key to cycle items, Enter to select."\n      \'Get the player selection.\n      btn = lst.Listbox(invlist(), iitem)\n      If btn = tWidgets.btnID.gbnOK Then\n         \'Make sure character has enough gold.\n         If pchar.CurrGold \x3E= invlist(iitem).ldata Then\n            \'Deduct gold.\n            pchar.CurrGold = pchar.CurrGold - invlist(iitem).ldata\n            \'Get the book from inventory.\n            pchar.GetInventoryItem bidx, binv\n            \'Set the spell data.\n            binv.spellbook.id = bkSpellBook\n            binv.spellbook.spell = inv(iitem).spell\n            \'Put it back into inventory.\n            pchar.AddInvItem bidx, binv\n            PrintMessage "The Merchant inscribed " & inv(iitem).spell.splname & " for " & invlist(iitem).ldata & " gold."\n         Else\n            PrintMessage "You do not have enough gold."\n         EndIf\n      EndIf\n   Else\n      PrintMessage "No blank spell books to inscribe."\n   EndIf\nEnd Sub\n}}}\n\x3C/div\x3E\nThe ShowInscribeList displays the list of available spells and allows the player to select a spell. Before we show the player the list though, we iterate through the character\'s inventory to make sure that he is carrying a blank spell book. If he is, the inventory slot is saved in /bidx/. bidx is set to -1 initially, so if the variable is still -1 when we exit the For-Next loop, we know that a blank spell book was not found.\n\nWe then show the list to the player so they can make their selection. Once the selection is made, we check to make sure that the character has enough gold to purchase the spell. If he does, then we grab the blank spell book from the inventory, load the spell into it, and then save it back into the character\'s inventory. The character now has a working spell book.\n\nIt is amazing how such a simple update can change the whole dynamic of the game. Blank spell books are now a major contributor to the game, and will be coveted by the player since they will be able to put any spell they want on it. Sometimes, it doesn\'t take huge changes to make dramatic differences. You just need to look at what you have and see what kind of twist you can put on it to make a dreary feature come alive.\n',
'= 43: Monster Magic\n\x3Cdiv align="center"\x3E\x3Cimg src=\'images/monmagic.png\' /\x3E\x3C/div\x3E\n\nSince we have the character magic in place, it is time to add some casting abilities to the monsters. The magic the monster wield will be rather simple and mirror the armor magic we implemented. Let\'s look at the new spells.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Spell ids.\nEnum spellid\n   splNone        \'No effect.\n...\n   splNoSlash     \'Armor: +% from slash attacks.\n   splNoCrush     \'Armor: +% from crush attacks.\n   splNoPierce    \'Armor: +% from pierce attacks.\n   splNoEnergy    \'Armor: +% from energy attacks.\n   splNoFire      \'Armor: +% from fire attacks.\n   splNoAcid      \'Armor: +% from acid attacks.\n   splNoMagic     \'Armor: +% from magic attacks.\n...\n   splMonClaw     \'Monster: Magic claw that slashes.\n   splMonFist     \'Monster: Magic fist that crushes.\n   splMonFang     \'Monster: Magic fang that pierces.\n   splMonPoison   \'Monster: Poisons character.\n   splMonBolt     \'Monster: Magic energy.\n   splMonFire     \'Monster: Magic fire.\n   splMonAcid     \'Monster: Magic acid.\nEnd Enum\n}}}\n\x3C/div\x3E\nAs you can see the spells match up with the armor magic and give the character a chance to exercise some armor magic in the game. We don\'t need to do any updates to the spell routines, but we have added a new function to the type of damage a monster spell inflicts.\n\n\x3Cdiv class="code"\x3E\n/inv.bi/\n{{{\n\'Returns the monster spell effect.\nFunction GetMonSpellEffect(id As spellid) As weapdamtype\n   Dim ret As weapdamtype\n   \n   Select Case id\n      Case splMonClaw     \'Monster: Magic claw that slashes.\n         ret = wdSlash\n      Case splMonFist     \'Monster: Magic fist that crushes.\n         ret = wdCrush\n      Case splMonFang     \'Monster: Magic fang that pierces.\n         ret = wdPierce\n      Case splMonPoison  \'Monster: Poison attack.\n         ret = wdPoison   \n      Case splMonBolt     \'Monster: Magic energy.\n         ret = wdEnergy\n      Case splMonFire     \'Monster: Magic fire.\n         ret = wdFire\n      Case splMonAcid     \'Monster: Magic fire.\n         ret = wdAcid\n   End Select\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nWe will use this to calculate the amount of damage the spell does to the character. If the character is wearing armor that has a spell for a particular damage type, the damage will be reduced by the armor spell level, so we need the above data to match against the character armor.\n\nThe update to the monster generation routine is quite simple.\n\n\x3Cdiv class="code"\x3E\n/monster.bi/\n{{{\n\'Generates a monster.\nSub GenerateMonster(mon As montype, currlevel As Integer)\n\n...\n   End Select\n   \'Get the monster spell.\n   If mon.ismagic = TRUE Then\n      mon.spell.id = RandomRange(splMonClaw, splMonAcid)\n      GenerateSpell mon.spell, currlevel\n      mon.spell.dam += mon.atkdam * (mon.spell.lvl / 100)\n   EndIf\n   \'Set the damage string.\n   Select Case mon.damtype\n      Case wdSlash\n         mon.damstr = "slash"\n      Case wdCrush\n         mon.damstr = "crush"\n      Case wdPierce\n         mon.damstr = "pierce"\n      Case wdEnergy\n         mon.damstr = "energy"\n      Case wdFire\n         mon.damstr = "fire"\n      Case wdAcid\n         mon.damstr = "acid"\n      Case wdPoison\n         mon.damstr = "poison"\n      Case Else\n         mon.damstr = ""\n   End Select\n   \'Set the xp and max hp values.\n   mon.xp = mon.currhp\n...\nEnd Sub\n}}}\n\x3C/div\x3E\nThe monster Type Def has a field called /ismagic/ that is set to TRUE if a monster can cast a spell. A random spell is then generated for the monster. The base damage of the spell is the current dungeon level plus an additional percentage of the attack damage based on the level of the spell, which is also the current dungeon level. The damage string is printed in the message area to let the player know what type of damage the monster inflicted.\n\nThe actual casting of the spell is done when the monsters moves.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Moves all living monsters.\nSub levelobj.MoveMonsters ()\n   Dim As mcoord nxt\n   Dim As Integer pdist, croll, aroll\n...\n  \'Check the distance to character.\n  pdist = CalcDist(_level.moninfo(i).currcoord.x, pchar.Locx, _level.moninfo(i).currcoord.y, pchar.Locy)\n  \'Check the attack range of monster.\n  If pdist \x3C= _level.moninfo(i).atkrange Then\n   \'If magic check for spell cast.\n   If _level.moninfo(i).ismagic = TRUE Then\n     \'Get the rolls for the attack.\n     croll = RandomRange(1, _level.moninfo(i).spell.dam)\n     aroll = RandomRange(0, _level.moninfo(i).atkdam) \n     \'Cast spell if magic roll higher.\n     If  croll \x3E aroll Then\n       CastSpell(_level.moninfo(i).currcoord.x, _level.moninfo(i).currcoord.y)\n     Else\n       \'Attack character.\n       MonsterAttack _level.moninfo(i).currcoord.x, _level.moninfo(i).currcoord.y\n     EndIf\n   Else\n     \'Not magic, just attack.\n     MonsterAttack _level.moninfo(i).currcoord.x, _level.moninfo(i).currcoord.y\n   End If\n...\n}}}\n\x3C/div\x3E\nThe question that we need to ask here is when does the monster cast a spell and when they do just attack? We could come up with some complicated algorithm to calculate probabilities and percentages, but in most cases this will be a lot of work for little gain. We are going to take an easier approach. We are going to make two rolls, one for the monster attack damage and one for the spell attack damage. Whichever roll is higher, that is what the monster will do. If the attack damage is higher than the spell damage, the monster will generally attack and occasionally cast a spell. If the spell damage is higher, the reverse is true.\n\nSurely this can\'t compete with a complicated algorithm? It may be counter-intuitive, but this method works just as well as a fancy algorithm. It gives the monster an attack strategy with a very natural feel, and it is simplicity itself to implement. Remember, if the player can\'t see it, it doesn\'t exist. That is, all that really matters is what the player is seeing, and if our roll approach and a fancy algorithm look the same to the player, why not go with the easier method? It will not make any difference to the player, but to us, simple code is more manageable and more robust.\n\nThe actual casting of the spell takes place in the CastSpell subroutine.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Monster casts spell.\nSub levelobj.CastSpell(mx As Integer, my As Integer)\n   Dim As Integer midx, cd, mc, rollc, rollm, chp, dam\n   Dim As String txt\n   Dim As Single arm\n   Dim As weapdamtype damtype\n   \n   \'Make sure there is a monster here.\n   If (_level.lmap(mx, my).monidx \x3E 0) And (pchar.BlinkActive = FALSE) Then\n      midx = _level.lmap(mx, my).monidx\n      \'Get character defense factor.\n      cd = pchar.GetMagicDefenseFactor()\n      \'Get monster attack factor.\n      mc = _level.moninfo(midx).mf\n      \'Get the rolls.\n      rollc = RandomRange(1, cd)\n      rollm = RandomRange(1, mc)\n      \'Compare monster roll to character roll.\n      If rollm \x3E rollc Then\n         \'If poison attack, poison character.\n         If _level.moninfo(midx).spell.id = splMonPoison Then\n            pchar.Poisoned = TRUE\n            pchar.PoisonStr = _level.moninfo(midx).spell.lvl\n            PrintMessage "You are poisoned."\n         EndIf\n         \'Get the damage.\n         dam = _level.moninfo(midx).spell.dam\n         damtype = GetMonSpellEffect(_level.moninfo(midx).spell.id)\n         \'Get any shield values if any.\n         arm = pchar.GetShieldArmorValue(damtype)\n         \'Make sure the character has some armor.\n         If arm \x3E 0 Then\n            dam = dam - (dam * arm)\n         End If\n         \'Get the character armor value.\n         arm = pchar.GetArmorValue(damtype)\n         \'Make sure the character has some armor.\n         If arm \x3E 0 Then\n            dam = dam - (dam * arm)\n         End If\n         \'Get character hp.\n         chp = pchar.CurrHP\n         \'Subtract damage from it.\n         chp -= dam\n         If chp \x3C 0 Then chp = 0\n         \'Reset character hp.\n         pchar.CurrHP = chp\n         txt = "The " &  _level.moninfo(midx).mname & " casts " & _\n         GetSpellName(_level.moninfo(midx).spell.id) & " for " & dam & " " & _\n         _level.moninfo(midx).damstr & " damage points."\n      Else\n         txt = "You dispell the " &  GetSpellName(_level.moninfo(midx).spell.id) & _\n               " cast by the " & _level.moninfo(midx).mname & "."\n      EndIf\n      PrintMessage txt\n   End If   \nEnd Sub\n}}}\n\x3C/div\x3E\nThis is our standard attack routine you have seen many times before. It follows the same pattern as our other attack routines. We get the character\'s magic defense factor, get the monster magic attack factor, compare the rolls and if the monster wins, we apply the damage to the character. Notice we are calling the /GetMonSpellEffect/ so that we can pass that value to /GetArmorValue/ and return the appropriate armor percentage. The damage is then modified and applied to the character.\n\nThe character object function /GetMagicDefenseFactor/ is a new function we have added for the monster spells.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n\'Return magic defense factor.\nFunction character.GetMagicDefense() As Integer\n   Dim As Integer ret\n   \n   ret = CurrMDF + BonMdf\n   \'Check for any jewelry effects.\n   ret += GetJewleryEffect(jwMDF, ret)\n   \'Check for armor effect.\n   If _cinfo.cwield(wArmor).classid = clArmor Then\n      \'Is the armor evaluated?\n      If _cinfo.cwield(wArmor).armor.eval = TRUE Then\n         \'Does the armor have a spell?\n         If _cinfo.cwield(wArmor).armor.spell.id \x3C\x3E splNone Then\n            \'Check for magic protection spell.\n            If _cinfo.cwield(wArmor).armor.spell.id = splNoMagic Then\n               \'Add in level percentage protection.\n               ret += (CurrMDF + BonMdf) * (_cinfo.cwield(wArmor).armor.spell.lvl / 100)\n            EndIf\n         EndIf\n      End If\n   EndIf\n   \n   Return ret \nEnd Function\n}}}\n\x3C/div\x3E\nHere we get the base magic defense factor, add in any jewelry effects (which we have looked at before) and then add in the armor bonus if the armor has the no-magic spell. The higher the no-magic level, the higher the bonus the character will receive. Of course the armor needs to be evaluated before the spell can have an effect.\n\nThat is all we need to do to implement monster magic. Have you noticed how easy it is now to update the program? The time we have spent building a solid foundation is really paying off for us. Most of the code we need to write now just calls existing code in various ways. Even the new routines, like the one above, do not require a lot of new code to implement. As I have said before, the time spent on laying a good foundation will be time gained in the later development cycle of the program.\n',
'= 44: The Amulet\nMost games have a goal, and this one is no different. To win the game, the character must descend the depths of the dungeon, recover the Amulet of Crystal Fire, and return to the surface world. We could implement the amulet as an inventory object, but since this is the primary win condition of the game, we are going to make it a dungeon feature that the character just needs to walk over to recover. \n\nOnce the player finds the amulet, we will set a flag in the character object so we know that the amulet was recovered. This will save a lot of code that we would need to implement in order to keep track of the amulet. There should be some sort of pay-off for finding the amulet, so we will give the character 1000 experience points, and bump all the learned spell levels by 20. We will start by adding the amulet to the dungeon feature enumeration.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'The types of terrain in the map.\nEnum terrainids\n   tfloor = 0  \'Walkable terrain.\n   twall       \'Impassable terrain.\n   tdooropen   \'Open door.\n   tdoorclosed \'Closed door.\n   tstairup    \'Stairs up.\n   tstairdn    \'Stairs down.\n   twmerch     \'Wandering Merchant.\n   tamulet     \'The amulet.\nEnd Enum\n}}}\n\x3C/div\x3E\nThe tamulet value is the amulet. Of course we need to update the map symbol information too.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Return ascii symbol for tile\nFunction levelobj._GetMapSymbol(tile As terrainids) As String\n	Dim As String ret\n	\n   Select Case tile\n     Case twall\n       ret = Chr(178)\n     Case tfloor\n       ret = Chr(249)\n     Case tstairup\n       ret = "\x3C"\n     Case tstairdn\n       ret = "\x3E"\n     Case tdooropen\n       ret = "\'"\n     Case tdoorclosed\n       ret = "\\"\n     Case twmerch\n       ret = "W"\n     Case tamulet\n       ret = Chr(21)\n     Case Else\n       ret = "?"\n   End Select\n   \n   Return ret\nEnd Function\n\n\'Returns the color for object.\nFunction levelobj._GetMapSymbolColor(tile As terrainids) As UInteger\n	Dim ret As UInteger\n	\n   Select Case tile\n      Case twall\n        ret = fbTan\n      Case tfloor\n        ret = fbWhite\n      Case tstairup\n        ret = fbYellowBright\n      Case tstairdn\n        ret = fbYellowBright\n      Case tdooropen\n        ret = fbTan\n      Case tdoorclosed\n        ret = fbSienna\n      Case twmerch\n        ret = fbGreen\n      Case tamulet\n         ret = fbFlame\n      Case Else\n         ret = fbWhite\n   End Select\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nThese two function will return the amulet symbol and color when the map is drawn to the screen in /DrawMap/. Since we have already generalized the DrawMap subroutine we don\'t need to make any changes there. Of course we need to place the amulet and we do that in /DrawMapToArray/.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Transfer grid data to map array.\nSub levelobj._DrawMapToArray()\n...\n	\'Set up stairs down in last room.\n	If _level.numlevel \x3C maxlevel Then\n	   x = _rooms(_numrooms).roomdim.rcoord.x + (_rooms(_numrooms).roomdim.rwidth \\ 2) \n	   y = _rooms(_numrooms).roomdim.rcoord.y + (_rooms(_numrooms).roomdim.rheight \\ 2)\n	   _level.lmap(x - 1, y - 1).terrid = tstairdn\n	Else\n	   \'Don\'t add a duplicate amulet. \n	   If pchar.HasAmulet = FALSE Then\n	      \'Set amulet location.\n	      x = _rooms(_numrooms).roomdim.rcoord.x + (_rooms(_numrooms).roomdim.rwidth \\ 2) \n	      y = _rooms(_numrooms).roomdim.rcoord.y + (_rooms(_numrooms).roomdim.rheight \\ 2)\n	      _level.lmap(x - 1, y - 1).terrid = tamulet\n	   End If\n	End If\n}}}\n\x3C/div\x3E\nNotice that we are checking the level number here. If the current level is less than the max level, then a down stairs is placed on the map. If we we are on the last level, then we place the amulet. We also need to check to make sure that the character doesn\'t already have the amulet to prevent the player from level surfing just to gain the amulet bonus. \n\nWhen the player finds the amulet, we will set a flag in the character object so we know that the amulet was recovered. We will also use this flag when we build the dungeon and create items, as we will see. First, let\'s look at the changes in the character object.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n\'Character attribute type def.\nType characterinfo\n...\n   hasam As Integer \'TRUE if the character has found the amulet.\nEnd Type\n}}}\n\x3C/div\x3E\nThe /hasam/ field is a simple TRUE/FALSE flag. When the character walks over the amulet, this flag will be set to TRUE. To set and get the amulet state, we need to add two new properties to the character object.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n\'Returns state of hasam flag.\nProperty character.HasAmulet() As Integer\n   Return _cinfo.hasam\nEnd Property\n\n\'Sets the state of the hasam flag.\nProperty character.HasAmulet(fnd As Integer)\n   _cinfo.hasam = fnd\n   \'If the amulet has been found, bump all the spell levels.\n   If fnd = TRUE Then\n      \'Add 1000 xp.\n      CurrXP = CurrXP + 1000\n      \'Bump all learned spells.\n      For i As Integer = LBound(_cinfo.cspells) To uBound(_cinfo.cspells)\n         \'Do we have a spell in location.\n         If _cinfo.cspells(i).spell.id \x3C\x3E splNone Then\n            \'Bump the spell level.\n            _cinfo.cspells(i).spell.lvl += 20\n         EndIf\n      Next\n   End If\nEnd Property\n}}}\n\x3C/div\x3E\nThe first property returns the current state of the flag. The second property does a bit more. If the amulet has been found, {{{fnd = TRUE}}} we first add 1000 experience to the character, and then we bump each spell level by 20. This is the payoff for making it to the bottom of the dungeon and finding the amulet.\n\nWe want to be careful not to do too much here. The player still needs to climb back up to the surface, and we don\'t want to make the player invincible and ruin the challenge of the game. This is a reasonable payoff and allows the player a chance to boost their character, but it doesn\'t give the house away either. Remember that a game where the player can\'t fail isn\'t a fun game.\n\nAs I said, the character needs to walk over the amulet, so we need to update the character movement routine.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Move the character based on the compass direction.\nFunction MoveChar(comp As compass) As Integer\n...\n         \'Move character.\n         If block = FALSE Then\n            \'Set the new character position.\n            pchar.Locx = vc.vx\n            pchar.Locy = vc.vy\n            ret = TRUE\n            \'Generate the sound map.\n            level.ClearSoundMap\n            snd = pchar.GetNoise()\n            level.GenSoundMap pchar.Locx, pchar.Locy, snd\n            \'Check for trap at current location.\n            trp = level.IsTrap(pchar.Locx, pchar.Locy, tdam, tdamtype, tdr, tdesc)\n            If trp = TRUE Then\n               pchar.ApplyTrap tdr, tdam, tdamtype, tdesc\n            EndIf\n            \'Check to see if character has found amulet.\n            If level.LevelID = maxlevel Then\n               level.IsAmulet pchar.Locx, pchar.Locy\n            End If \n...\n}}}\n\x3C/div\x3E\nThe amulet isn\'t a blocking tile, so when we pass into this section of the code, we will check to see if the tile has the amulet. Since we know that the amulet is only on the last level of the dungeon, we only need to check this if the current level is equal to the maximum level of the dungeon.\n\nLet\'s pause for a moment and ask a question: what happens if we change the maxlevel number? The answer is, nothing. We are using a symbolic constant here, so if the maxlevel changes to 25 or 100, this code will work without any changes. This is why we use these Const values in the code. By using a Const value, and generalizing the code, we put the work on the compiler. If maxlevel changes to 25, then the compiler will handle the new number for us and our current code will work just fine.\n\nNotice here we are calling a new subroutine in our level object /IsAmulet/. Let\'s take a look at that code.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Checks to see if x, y has amulet.\nSub levelobj.IsAmulet(x As Integer, y As Integer)\n   \n   \'Check the location for the amulet.\n   If _level.lmap(x, y).terrid = tamulet Then\n      \'Set the amulet flag.\n      pchar.HasAmulet = TRUE\n      \'Reset the map.\n      _level.lmap(x, y).terrid = tfloor\n      \'Tell the player they found the amulet.\n      PrintMessage "You found the Amulet of Crystal Fire!"\n   EndIf\n   \nEnd Sub\n}}}\n\x3C/div\x3E\nThis is very simple. We just check the tile at the x and y values to see if the tile is an amulet. If it is, we call the HasAmulet property of the character object to set the flag. We then change the tile to a floor tile to simulate picking up the amulet. Again, we do this so that the player won\'t miss a step here. By forcing a pick-up, we ensure that the win condition of the game is met.\n\nOnce last we do here, is to remind the player they have the amulet by printing that reminder on the main screen.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Draws the main game screen.\nSub DrawMainScreen (db As Integer = FALSE)\n...\n   \'Draw the level number.\n   txt = "Dungeon Level: " & level.LevelID & " of " & maxlevel\n   If pchar.HasAmulet = TRUE Then\n      txt = txt & " (Amulet)"\n   EndIf\n   DrawStringShadow charw, 0, txt \n...\n}}}\n\x3C/div\x3E\nWhen we draw the main screen, we print "Amulet" next to the dungeon level just so the player knows that he has met the win condition if he makes it out of the dungeon alive.\n\nSince we know that the amulet is on the last level of the dungeon, we can make use of that fact and bump the challenge and benefit of the game for the player when we generate monsters and items.\n\n\x3Cdiv class="code"\x3E\n/monster.bi/\n{{{\n\'Generates a monster.\nSub GenerateMonster(mon As montype, currlevel As Integer)\n...\n   Dim As Integer monrange = 20\n\n   \'Set the monster attributes.\n   mon.id = RandomRange(monDarkangel, monGriffon)\n   \'Check to see if character has amulet.\n   If pchar.HasAmulet = FALSE Then\n      stratt = GetScaledFactor(pchar.CurrStr, currlevel)\n      staatt = GetScaledFactor(pchar.CurrSta, currlevel)\n      dexatt = GetScaledFactor(pchar.CurrDex, currlevel)\n      aglatt = GetScaledFactor(pchar.CurrAgl, currlevel)\n      intatt = GetScaledFactor(pchar.CurrInt, currlevel)\n   Else\n      stratt = RandomRange(pchar.CurrStr - monrange, pchar.CurrStr + monrange) \n      staatt = RandomRange(pchar.CurrSta - monrange, pchar.CurrSta + monrange)\n      dexatt = RandomRange(pchar.CurrDex - monrange, pchar.CurrDex + monrange)\n      aglatt = RandomRange(pchar.CurrAgl - monrange, pchar.CurrAgl + monrange)\n      intatt = RandomRange(pchar.CurrInt - monrange, pchar.CurrInt + monrange)\n   End If\n...\n   End Select\n   \'Set the xp and max hp values.\n   mon.xp = mon.currhp\n   \'Adjust the attack and armor bvalue based on the level.\n   If pchar.HasAmulet = FALSE Then\n      \'Calculate the current percentage based on the level.\n      pct = currlevel / maxlevel\n      \'Adjust the attack value by level percent.\n      mon.atkdam = mon.atkdam * pct\n      If mon.atkdam \x3C 1 Then mon.atkdam = 1\n      \'Adjust the armor value by level percent.\n      mon.armval = mon.armval * pct\n   End If\nEnd Sub\n}}}\n\x3C/div\x3E\nAs you can see when we generate the attributes for a monster, we check to see if the character has the amulet, and if he does, we do not scale the monster attributes. The monster will be on par with the character, some a little weaker, some a little stronger. After the monster is generated, if the character has the amulet, we don\'t scale down the damage or armor values of the monster. The character will have to fight all the way to the surface.\n\nLet\'s face it; going back up to the surface is going to be tough. To help a bit, we will compensate by creating items at their full potential.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Generate items for the map.\nSub levelobj._GenerateItems()\n    Dim As Integer i, x, y\n   \n    \'Generate some items for the level. \n    For i = 1 To 10\n      Do\n        \'Get a spot in the dungeon.\n        x = RandomRange(2, mapw - 1)\n        y = RandomRange(2, maph - 1)\n        \'Look for floor tile that doesn\'t have an item on it.\n      Loop Until (_level.lmap(x, y).terrid = tfloor) And (HasItem(x, y) = FALSE)\n      \'Check for amulet.\n      If pchar.HasAmulet = FALSE Then\n        GenerateItem _level.linv(x, y), _level.numlevel\n      Else\n        GenerateItem _level.linv(x, y), maxlevel\n      End If\n   Next\n   \nEnd Sub\n}}}\n\x3C/div\x3E\nWhen the character doesn\'t have the amulet, we generate items based on the dungeon level. The deeper you go into the dungeon, the more powerful the item will be. After the character finds the amulet, we generate items based on the maximum value. This is only fair. If we unshackle the monsters, which are tough by any measure, then we also need to give the player a realistic chance, so we unshackle the items as well.\n\nThe last thing we need to implement is a way for the character to ascend the dungeon (he can only go down right now) so we need to implement the stairs up command.\n\n\x3Cdiv class="code"\x3E\n/dod.bas:Main Game Loop/\n{{{\n...\n         \'Check for up stairs.\n         If ckey = "\x3C" Then\n            \'Check to make sure on up stairs.\n            If level.GetTileID(pchar.Locx, pchar.Locy) = tstairup Then\n               \'If on first level and no amulet block escape.\n               If (level.LevelID = 1) And (pchar.HasAmulet = FALSE) Then\n                  PrintMessage "The way is magically blocked."\n               \'If on first level and has amulet, then set win and exit flag.\n               ElseIf (level.LevelID = 1) And (pchar.HasAmulet = FALSE) Then\n                  didwin = TRUE\n                  done = TRUE\n               Else\n                  \'Set the level id.\n                  level.LevelID = level.LevelID - 1\n                  \'Build a new level.\n              	   level.GenerateDungeonLevel\n                  \'Draw Screen.\n                  DrawMainScreen TRUE\n               EndIf\n            End If\n         EndIf\n...\n}}}\n\x3C/div\x3E\nNotice there are three conditions we must check here. Is the character on a stairs up tile? If so, is it the first level of the dungeon? If so, does the character have the amulet? Once the character enters the dungeon he can\'t leave until he finds the amulet, so we need to check both the level and the amulet flag before we proceed. If it is the first level and the character does have the amulet, we then set the win flag and exit the main game loop. The player has won the game! If none of these conditions are true, then we just generate a new level as usual, with a level number less than the previous level.\n\nThis sets up the win condition of the game. Of course there is also a lose condition. When the character dies, they lose the game. Normally, when a character either escapes or dies, the character\'s accomplishments are displayed so that the player can glory in their character. These are called morgue files, and we will implement one in the next chapter.',
'= 45: Morgue File\n\x3Cdiv align="center"\x3E\x3Cimg src=\'images/morgue.png\' /\x3E\x3C/div\x3E\n\nAlmost all roguelikes have a morgue file, a list of accomplishments that the character achieved while in the dungeon. It is called a morgue file, since it often is the last testament of a character who has valiantly died in the dungeon. Our game wouldn\'t be complete if we didn\'t also include a morgue file, so that is what we are going to implement in this chapter.\n\nTo get started we need to add some support code.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Type monstat\nType monstat\n   cnt As Integer\n   mname As String * 15\nEnd Type\n\n\'Dungeon level information.\nType levelinfo\n   numlevel As Integer                       \'Current level number.\n   lmap(1 To mapw, 1 To maph) As mapinfotype \'Map array.\n   linv(1 To mapw, 1 To maph) As invtype     \'Map inventory type.\n   nummon As Integer                         \'Number of monsters on the map.\n   moninfo(1 To nroommax) As montype         \'Array of monsters.   \n   killedmon(monDarkangel To monGriffon) As monstat     \'The list of vanquished monsters.\n   lastmon As monstat \'Last monster encountered.\nEnd Type\n}}}\n\x3C/div\x3E\nWe have added a new Type Def to our map object, /monstat/ which we will use to keep track of the number of monsters killed by the character. It contains a count of the monsters killed, as well as their names. The array /killedmon/ keeps a running total of the kills, indexed by the monster ids, while /lastmon/ is the last monster the character encountered, and usually the monster that delivers the killing blow.\n\nWe also need to update a couple of routines to keep track of this data while the game is being played.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Applies damage to monsters. Returns true if monster is dead.\nFunction levelobj.ApplyDamage(mx As Integer, my As Integer, dam As Integer) As Integer\n...\n      If (_level.moninfo(midx).currhp \x3C 1) Or (_level.moninfo(midx).effects(meIceStatue).cnt \x3E 0) Then\n         \'Bump the number of killed monsters.\n         _level.killedmon(_level.moninfo(midx).id).cnt += 1\n         _level.killedmon(_level.moninfo(midx).id).mname = _level.moninfo(midx).mname\n...\n}}}\n\x3C/div\x3E\nHere in /ApplyDamage/, if the monster dies, we add the kill to the running total in the killedmon array. We are also storing the name so it will be easy to print to our morgue file, as we will see shortly.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Monster attacks character.\nSub levelobj.MonsterAttack(mx As Integer, my As Integer)\n...\ntxt = "The " &  _level.moninfo(midx).mname & " hits for " & dam & " " & _level.moninfo(midx).damstr & " damage points."\n\'Set the last monster attack.\n_level.lastmon.mname = _level.moninfo(midx).mname\n}}}\n\x3C/div\x3E\nHere we set the last monster that has attacked the character. This value will also be printed in the morgue file.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Returns the number and name of dead monster for passed monster id.\nSub levelobj.GetDeadMonster(monid As monids, mstat As monstat)\n   \n   \'Verify the passed id.\n   If (monid \x3E= monDarkangel) And (monid \x3C= monGriffon) Then\n      mstat = _level.killedmon(monid)\n   EndIf\nEnd Sub\n\n\'Returns the stat for last monster encounter.\nSub levelobj.GetLastMonster(mstat As monstat)\n   \n   mstat = _level.lastmon\n   \nEnd Sub\n}}}\n\x3C/div\x3E\nThese two subroutines let us access the monster total array and the last monster data value. For /GetDeadMonster/ we use the parameter /monid/ to access the value in the array. This is why we have used the monster ids as indexes for the array; it makes things much more simple. In both routines, we are passing a monstat variable that we will load with data so that we can print the data to the screen. \n\nThat covers our support routines, so let\'s look at the actual morgue file code.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Print morgue file to screen and file.\nSub PrintMorgueFile(winner As Integer)\n   Dim As Integer fh, cnt\n   Dim lines() As String * txcols \'Array of lines the width of screen.\n   Dim As spelltype spl\n   Dim As invtype inv\n   Dim mstat As monstat\n   \n   \'First we will build the data and place it in the string array: lines.\n   \'Use a dynamic array so we do not need to keep track of array size.\n   cnt += 1\n   ReDim Preserve lines(1 To cnt) As String * txcols\n   lines(cnt) = "Dungeon of Doom " & dodver\n\n   cnt += 1\n   ReDim Preserve lines(1 To cnt) As String * txcols\n   lines(cnt) = "" \n\n   cnt += 1\n   ReDim Preserve lines(1 To cnt) As String * txcols\n   lines(cnt) = "Name: " & pchar.CharName\n\n   cnt += 1\n   ReDim Preserve lines(1 To cnt) As String * txcols\n   lines(cnt) = "" \n   \n   cnt += 1\n   ReDim Preserve lines(1 To cnt) As String * txcols\n   If pchar.HasAmulet = TRUE Then\n      lines(cnt) = "Found the Amulet of Crystal Fire"\n   Else\n      lines(cnt) = "Did not find the Amulet of Crystal Fire"\n   EndIf\n   \n   cnt += 1\n   ReDim Preserve lines(1 To cnt) As String * txcols\n   If winner = TRUE Then\n      lines(cnt) = "Escaped the dungeon."\n   Else\n      lines(cnt) = "Died on level " & level.LevelID\n   End If\n\n   cnt += 1\n   ReDim Preserve lines(1 To cnt) As String * txcols\n   level.GetLastMonster mstat\n   lines(cnt) = "Last monster encounter was with a " & mstat.mname \n\n   cnt += 1\n   ReDim Preserve lines(1 To cnt) As String * txcols\n   lines(cnt) = "" \n\n   cnt += 1\n   ReDim Preserve lines(1 To cnt) As String * txcols\n   lines(cnt) = "* Attributes *"\n\n   cnt += 1\n   ReDim Preserve lines(1 To cnt) As String * txcols\n   lines(cnt) = ""\n   \n   cnt += 1\n   ReDim Preserve lines(1 To cnt) As String * txcols\n   lines(cnt) = " Total Exp: " & pchar.TotXP\n\n   cnt += 1\n   ReDim Preserve lines(1 To cnt) As String * txcols\n   lines(cnt) = " Max HP: " & pchar.MaxHP\n\n   cnt += 1\n   ReDim Preserve lines(1 To cnt) As String * txcols\n   lines(cnt) = " Max Mana: " & pchar.MaxMana\n\n   cnt += 1\n   ReDim Preserve lines(1 To cnt) As String * txcols\n   lines(cnt) = "Strength: " & pchar.CurrStr & " (" & pchar.BonStr & ")"\n   \n   cnt += 1\n   ReDim Preserve lines(1 To cnt) As String * txcols\n   lines(cnt) = "Stamina: " & pchar.CurrSta & " (" & pchar.BonSta & ")"\n   \n   cnt += 1\n   ReDim Preserve lines(1 To cnt) As String * txcols\n   lines(cnt) = "Dexterity: " & pchar.CurrDex & " (" & pchar.BonDex & ")"\n   \n   cnt += 1\n   ReDim Preserve lines(1 To cnt) As String * txcols\n   lines(cnt) = "Agility: " & pchar.CurrAgl & " (" & pchar.BonAgl & ")"\n   \n   cnt += 1\n   ReDim Preserve lines(1 To cnt) As String * txcols\n   lines(cnt) = "Intelligence: " & pchar.CurrInt & " (" & pchar.BonInt & ")"\n   \n   cnt += 1\n   ReDim Preserve lines(1 To cnt) As String * txcols\n   lines(cnt) = ""\n   \n   cnt += 1\n   ReDim Preserve lines(1 To cnt) As String * txcols\n   lines(cnt) = "* Held Inventory *"\n    \n   cnt += 1\n   ReDim Preserve lines(1 To cnt) As String * txcols\n   lines(cnt) = ""\n\n   cnt += 1\n   ReDim Preserve lines(1 To cnt) As String * txcols\n   lines(cnt) = "Gold: " & pchar.TotGold\n   \n   cnt += 1\n   ReDim Preserve lines(1 To cnt) As String * txcols\n   lines(cnt) = ""\n   \n   \'First get the wield items of the character.\n   For i As wieldpos = wPrimary To wRingLt\n      pchar.GetInventoryItem i, inv\n      \'Make sure we have an item.\n      If inv.classid \x3C\x3E clNone Then\n         cnt += 1\n         ReDim Preserve lines(1 To cnt) As String * txcols\n         lines(cnt) = GetWieldString(i) & ": " & GetInvItemDesc(inv) & " (" & GetItemSpell(inv) & ")"\n      EndIf\n   Next\n   \n   cnt += 1\n   ReDim Preserve lines(1 To cnt) As String * txcols\n   lines(cnt) = ""\n\n   cnt += 1\n   ReDim Preserve lines(1 To cnt) As String * txcols\n   lines(cnt) = "* Backpack Inventory *"\n    \n   cnt += 1\n   ReDim Preserve lines(1 To cnt) As String * txcols\n   lines(cnt) = ""\n\n   \'Get inventory items in back pack.\n   For i As Integer = pchar.LowInv To pchar.HighInv\n      pchar.GetInventoryItem i, inv\n      \'Make sure we have an item.\n      If inv.classid \x3C\x3E clNone Then\n         cnt += 1\n         ReDim Preserve lines(1 To cnt) As String * txcols\n         lines(cnt) = GetInvItemDesc(inv) & " (" & GetItemSpell(inv) & ")"\n      EndIf\n   Next\n\n   cnt += 1\n   ReDim Preserve lines(1 To cnt) As String * txcols\n   lines(cnt) = ""\n\n   cnt += 1\n   ReDim Preserve lines(1 To cnt) As String * txcols\n   lines(cnt) = "* Spells Learned *"\n    \n   cnt += 1\n   ReDim Preserve lines(1 To cnt) As String * txcols\n   lines(cnt) = ""\n   \n   \'Get any learned spells.\n   For i As Integer = pchar.LowISpell To pchar.HighISpell\n      \'See if a spell is at index.\n      spl = pchar.GetLearnedSpell(i)\n      If spl.id \x3C\x3E splNone Then\n         cnt += 1\n         ReDim Preserve lines(1 To cnt) As String * txcols\n         lines(cnt) = spl.splname & " (" & spl.lvl & ")"\n      EndIf\n   Next\n   \n   cnt += 1\n   ReDim Preserve lines(1 To cnt) As String * txcols\n   lines(cnt) = ""\n\n   \'List killed monsters.\n   cnt += 1\n   ReDim Preserve lines(1 To cnt) As String * txcols\n   lines(cnt) = "* Monsters Vanquished *"\n    \n   cnt += 1\n   ReDim Preserve lines(1 To cnt) As String * txcols\n   lines(cnt) = ""\n   \n   For i As monids = monDarkangel To monGriffon\n      \'See if monster is in list.\n      level.GetDeadMonster i, mstat\n      If mstat.cnt \x3E 0 Then\n         cnt += 1\n         ReDim Preserve lines(1 To cnt) As String * txcols\n         lines(cnt) = mstat.mname & ": " & mstat.cnt\n      EndIf\n   Next\n   \n   \'Save the data to character file.\n   fh = FreeFile\n   Open pchar.CharName & ".txt" For Output As #fh\n   For i As Integer = 1 To UBound(lines)\n      Print #fh, Trim(lines(i))\n   Next\n   Close\n   \n   cnt = 1\n   \'Print to screen.\n   ScreenLock\n   \'Set the background.\n   DrawBackground leatherback()\n   For i As Integer = 1 To UBound(lines)\n      cnt += 1\n      PutTextShadow Trim(lines(i)), cnt, 2\n      If cnt = txrows - 2 Then\n         cnt += 1\n         PutTextShadow "Press any key...", cnt, 2\n         ScreenUnLock\n         Sleep\n         cnt = 1\n         ScreenLock\n	 DrawBackground leatherback()\n      EndIf\n   Next\n   cnt += 2\n   PutTextShadow "Press any key...", cnt, 2\n   ScreenUnLock\n   Sleep\nEnd Sub\n}}}\n\x3C/div\x3E\nThere is a lot of code here, but most of it is just loading the dynamic string array with data. We are using a string array so that we only need to load the data once. Morgue files are usually printed to the screen and saved to a file, so we don\'t want to have to build the data twice. By using a dynamic array, we also don\'t need to worry about having the correct dimensions in the declaration. We simply increment a counter, re-dimension the array, and load the data. If we need to change the data, maybe we want to add something new to the list, we don\'t have to count lines to get the right number of array elements. We will let the compiler do it for us.\n\nNotice that we are printing the basic information of the character, such as the attributes, the inventory items and spells learned. We are also using the new data items and subroutines we added to print the monster list and to print the last monster encounter.\n\nThe code to save the data to a file is very simple.\n\n\x3Cdiv class="code"\x3E\n/dod.bas:PrintMorgueFile/\n{{{\n   \'Save the data to character file.\n   fh = FreeFile\n   Open pchar.CharName & ".txt" For Output As #fh\n   For i As Integer = 1 To UBound(lines)\n      Print #fh, Trim(lines(i))\n   Next\n   Close\n}}}\n\x3C/div\x3E\nWe are just using the character name, adding an extension to it, and then printing the whole array to the file. To print to the screen, we need to take into account the limited number of lines available.\n\n\x3Cdiv class="code"\x3E\n/dod.bas:PrintMorgueFile/\n{{{\n   cnt = 1\n   \'Print to screen.\n   ScreenLock\n   \'Set the background.\n   DrawBackground leatherback()\n   For i As Integer = 1 To UBound(lines)\n      cnt += 1\n      PutTextShadow Trim(lines(i)), cnt, 2\n      If cnt = txrows - 2 Then\n         cnt += 1\n         PutTextShadow "Press any key...", cnt, 2\n         ScreenUnLock\n         Sleep\n         cnt = 1\n         ScreenLock\n	 DrawBackground leatherback()\n      EndIf\n   Next\n   cnt += 2\n   PutTextShadow "Press any key...", cnt, 2\n   ScreenUnLock\n   Sleep\n}}}\n\x3C/div\x3E\nWe set our /cnt/ variable to 1 and draw a background on the screen to use as a backdrop for the information. In the For-Next loop we increment the cnt variable by 1, and then draw the text string we saved at the current array index on the screen. We are locking the screen while we do this so we don\'t get any screen flicker, and it is much faster writing to a locked screen then to an unlocked screen. \n\nWe keep track of the number of lines we have printed in cnt, and when we reach the bottom of the screen, we redraw the background, reset the cnt variable, and print the next screen of information. When the loop exists, we give the player a chance to read through the data and then exit by pressing a key. Our morgue file is fairly simple, but it does give the player some tangible proof of how they fared in the dungeon. If at some point we want to add in some more data, we can do so easily since the basic structure is in place.',
'= 46: Save and Load\n\x3Cdiv align="center"\x3E\x3Cimg src=\'images/save.png\' /\x3E\x3C/div\x3E\n\nWe are finally ready to implement the save and load routines. If you were hoping for some complicated code in this chapter, you are going to quite disappointed. Saving and loading is simplicity itself. Let\'s start by adding a new file to the project /save.bi/ which contains our file routines.\n\n\x3Cdiv class="code"\x3E\n/save.bi/\n{{{\n\'This is our load and save type.\nType savedata\n   char As characterinfo \'The character data.\n   level As levelinfo    \'The level data.\nEnd Type\n\nDim Shared sg As savedata\n\n\'The save routine.\nFunction SaveGame() As Integer\n   Dim As Integer ret = TRUE, fh\n   \n   \'Load the character data into the type.\n   pchar.GetCharacterData sg.char\n   \'Load the level data.\n   level.GetLevelData sg.level\n   \'Save the file.\n   fh = FreeFile\n   If Open("dod.sav" For Binary As #fh) \x3C\x3E 0 Then\n      ret = FALSE\n   Else\n      Put #fh,, sg\n   EndIf\n   Close\n      \n   Return ret\nEnd Function\n\n\'The load routine.\nFunction LoadGame() As Integer\n   Dim As Integer ret = TRUE, fh\n   \n   \'Check for file on disk.\n   If Len(Dir("dod.sav")) = 0 Then\n      ret = FALSE\n   Else\n      \'Try to open file.\n      fh = FreeFile\n      If Open("dod.sav" For Binary As #fh) \x3C\x3E 0 Then\n         ret = FALSE\n      Else\n         Get #fh,,sg \n         \'Load the data into the objects.\n         pchar.SetCharacterData sg.char\n         level.SetLevelData sg.level\n      EndIf\n   EndIf\n   Close\n   \n   Return ret\nEnd Function\n}}}\n\x3C/div\x3E\nWe have created a new type, /savedata/ which will contain the character information and the map information. We will then save the file to disk as a binary file. Since FreeBasic supports the saving and loading of Type Defs, all we need to do is to load the information into our save type and we are ready to go. \n\nFirst let\'s look at the character subroutines to load the data into our type, and load the save data into our character object.\n\n\x3Cdiv class="code"\x3E\n/character.bi/\n{{{\n\'Gets the character data.\nSub character.GetCharacterData(cd As characterinfo)\n   cd = _cinfo\nEnd Sub\n\n\'Sets the character data.\nSub character.SetCharacterData(cd As characterinfo)\n   _cinfo = cd\nEnd Sub\n}}}\n\x3C/div\x3E\nThe first routine loads the data into our save data Type, and the second routine loads the save data into the character object. We are taking advantage of the fact that we can assign Types in FreeBasic. FreeBasic will copy over all the data for us, even data contained within arrays.\n\nThe level data is just as simple.\n\n\x3Cdiv class="code"\x3E\n/map.bi/\n{{{\n\'Get the level information.\nSub levelobj.GetLevelData(ld As levelinfo)\n   \'Get the level number.\n   ld  = _level\nEnd Sub\n\n\'Get the level information.\nSub levelobj.SetLevelData(ld As levelinfo)\n   \'Get the level number.\n   _level = ld\nEnd Sub\n}}}\n\x3C/div\x3E\nAgain, we are just assigning Types to each other. We let the compiler worry about copying the data. You will notice that we are not saving the grid and room info. The reason is that once a level is generated, we don\'t need that information any longer. All the data we need is in the /levelinfo/ Type Def. We can safely ignore the room and grid information.\n\nNow, we need to implement the Load menu option so we can load a level.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n...\n\'Load flag.\nDim isloaded As Integer = FALSE\n\'Get the menu selection\nDim mm As mmenu.mmenuret\n\'Loop until the user selects New, Load or Quit.\nDo\n   \'Draw the main menu.\n    mm = mmenu.MainMenu\n   \'Process the menu selection.\n   If mm = mmenu.mNew Then\n      \'Generate the character.\n      Var ret = pchar.GenerateCharacter\n      \'Do not exit menu when user presses ESC.\n      If ret = FALSE Then\n         \'Set this so we loop.\n         mm = mmenu.mInstruction\n      Else\n         \'Do the intro.\n         intro.DoIntro\n      EndIf\n   ElseIf mm = mmenu.mLoad Then\n      Dim As tWidgets.tMsgbox sok\n      Dim As tWidgets.btnID btn\n      \n      sok.MessageStyle = tWidgets.MsgBoxType.gmbOK\n      sok.Title = "Load Game"\n      If LoadGame() = FALSE Then\n         btn = sok.MessageBox("Could not load game.")\n         mm = mmenu.mInstruction\n      Else\n         btn = sok.MessageBox("Game loaded.")\n         isloaded = TRUE\n      EndIf\n   ElseIf mm = mmenu.mInstruction Then\n      \'Print the instructions.\n   EndIf\nLoop Until mm \x3C\x3E mmenu.mInstruction\n\n\'Set the dead flag.\nDim As Integer isdead = FALSE, didwin = FALSE, done = FALSE\n\'Main game loop.\nIf mm \x3C\x3E mmenu.mQuit Then\n   \'If we loaded game, we don\'t need to generate it.\n   If isloaded = FALSE Then\n      \'Set the first level.\n      level.LevelID = 1\n	   \'Build the first level of dungeon\n	   level.GenerateDungeonLevel\n   End If\n...\n}}}\n\x3C/div\x3E\nUnder the /mmenu.mLoad/ option we create a Messagebox widget and then attempt to load the data from the disk. If it is successful, we set the /isloaded/ flag so we know not to generate a new level. We then message the player to let them know that we have loaded the data and are ready to go. If the save file doesn\'t exist, then we tell the player we couldn\'t load the file, and return back to the menu.\n\nThis extremely simple code illustrates a key idea in programming: use your language to its full extent. If we were try and write this save and load code ourselves, it would require a large amount of code. By letting the compiler do it, not only is it much less code, but less prone to errors. It is easy to make a mistake when loading and converting large amounts of data. By letting the compiler do the hard work, we simplify the code, and get more robust code at the same time.\n',
'= 47: Winning and Losing\n\x3Cdiv align="center"\x3E\x3Cimg src=\'images/winscreen.png\' /\x3E\x3C/div\x3E\n\nThere are two possible outcomes to playing /Dungeon of Doom/. Winning and losing. Whether the player loses or wins, we will display a nice graphic for them, and then exit the game. The code for this comes after our main game loop.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n...\n   Loop Until done = TRUE\nEndIf\nClearKeys\n\'Print dead mesage.\nIf isdead = TRUE Then\n   DrawBackground char_dead()\n   ckey = pchar.CharName & " has died in the depths of the dungeon."\n   PutTextShadow ckey, 20, (txcols / 2) - (Len(ckey) / 2)\n   ckey = "Rest in peace valiant warrior!"\n   PutTextShadow ckey, 22, (txcols / 2) - (Len(ckey) / 2)\n   Sleep\n   ClearKeys\n   \'Print the moegue file.\n   PrintMorgueFile didwin\nEndIf\n\'Print the win screen.\nIf didwin = TRUE Then\n   DrawBackground char_win()\n   ckey = pchar.CharName & " has escaped the dungeon with the Amulet!"\n   PutTextShadow ckey, 12, (txcols / 2) - (Len(ckey) / 2)\n   ckey = "The Kingdom will be saved! Long live King " & pchar.CharName & "!"\n   PutTextShadow ckey, 14, (txcols / 2) - (Len(ckey) / 2)\n   ckey = "All Hail King " & pchar.CharName & "!"\n   PutTextShadow ckey, 16, (txcols / 2) - (Len(ckey) / 2)\n   Sleep\n   ClearKeys\n   \'Print the moegue file.\n   PrintMorgueFile didwin\nEndIf\n}}}\n\x3C/div\x3E\nIf the character dies, the /isdead/ flag will be set and we will show the dead screen and then print the morgue file. If the character escapes the dungeon with the amulet, then we display the win screen and again print the morgue file. Only one (or neither) of these conditions will be true at any time. Granted, it is a meager payoff for all the time spent by the player playing the game, but we hope that the real payoff is the enjoyment they received from playing the game.\n',
'= 49: The End and Beginning\nWell, we have covered a lot of ground in this book together. It has been a lot of work, but a lot fun as well. We started with an idea, and ended up with a working game. All books must have an ending, and this is as good a place as any to end this one. Yet, even though this is the end of the book, it is just the beginning of your adventure. We have really just scratched the surface of the roguelike genre. There is so much more than you can do. You might be asking, where do I go from here? The answer is anywhere you want.\n\nYou could start with this code. This book is unique in that the code here and in the distribution package is real. This is what you get when you create a real working program. The code isn\'t sanitized for these pages, it hasn\'t been reworked dozens of times before it made it into print. What you see here is the good, the bad and ugly. Just like you see in any real program. It was important for me when I started this book that you see what really happens when you start with an idea and work it to completion. You get good code, you get bad (buggy) code and you get ugly (inefficient) code. This is the way things really work. This code has it all, and as you work with it, you\'ll find out what we did right, bad and ugly, and learn from it.\n\nOnce you get a feel for the code, try your hand at fixing the bugs we missed and try adding some new features to the game. Many roguelikes have races, classes and over-worlds (surface worlds) that expand the options and playability of the game. Try your hand at some new dungeon generation algorithms to get some different levels to explore. Implement a party system where the player controls a party instead of a single character. There are so many things you can try. Above all, use your imagination and have fun.\n\nFeel free to use this code as base for your own projects. As you gain understanding and experience, you will want to create your own game from scratch, and that is as it should be. You will develop your own ideas and style, and there is nothing more satisfying than starting with an idea, and with a little time, hard work and patience, ending with a game that other play and enjoy. There is so much you can do in the roguelike genre that you could literally work on a program for years and still have things to do. That is the challenge and fun of roguelikes. \n\nIt is my sincere hope that this book has helped you get a feel for what it takes to create a roguelike game. I have enjoyed working on this book, and I look forward to playing the (better) games that you will create with your new knowledge. Remember, you never know what is behind that closed door...\n\nRichard D. Clark\n',
'= RPG System\nThe RPG system in /Dungeon of Doom/ is fairly standard fare as far as systems go. There isn\'t really anything innovative in the system and it works well in the game. For the most part I happy with the way it works, however the openness of the basic attributes is something I would like to change. The openness of the attributes was a conscience decision to help prevent the character from hitting the "stat wall", where they reached a plateau on their development and could go no further. Hitting a plateau like this can really kill the fun of the game and takes out a lot of the motivation to play the game. \n\nHowever, having limitless attributes also seems to take a lot of the strategy of the game, especially when it comes to item management. Not only that, it just doesn\'t feel right. No one, or creature, has limitless attributes (except Superman of course), so this aspect of the game needs to be looked at again. This is the sort of thing you run into when designing games. An idea seems great on paper, but may not have the intended effect when actually implemented in the game. When that happens, it is time to sit down and think things over and see what changes need to be made.\n\nThe other problem with the current system is that it is going to be difficult to implement races in the game that have individual attributes without adding a lot of modifiers to the game. Having a list of modifiers to manage add quite a bit of complexity to the code, and it tedious when it comes to attribute management. The D20 system uses modifiers like this in its races, and I have never liked the idea. It is too artificial, too clumsy and error prone.\n\nHowever, the D20 system does have a feature, maximum attribute or ability values, that we can use to address the openness of the attributes in this game, as well as offer an easy means of adding racial differences without mucking up the system with a bunch of modifiers. Instead of having modifiers, we will simply define maximum values for each of the attribute values, with one race having a certain value for an attribute, and another race having less or more depending on the race. In this way, we not only confine the player to a set of values, and force them to manage those values carefully, we also can add racial qualities without complicating the code.\n\nIn most RPG games, Humans race is the "average race" and the set the median for the other races. We can use that here to set the average value of a character and can then adjust the attributes up or down for another character depending on the race. The next question is, what max value can we use? \n\nOnce aspect of the game, combat factors, was an idea I lifted from a miniature war game book called /The Book of MARS/ by David Tennes. I liked the idea better than the D20 idea of defining armor classes to determine combat results. I always thought it was silly that armor made you harder to hit; if anything, armor would make you easier to hit since it is quite hard to dodge an attack lugging plate mail around. Since for our combat factor, we are adding together two attributes to come up the combat factor value, a good maximum value for each attribute would be 50. This would make the the max hit points 100 (Strength + Stamina), and 100 is a good number since we can now make percentage rolls against the values.\n\nSo, if a human is the average, then the max values of a Human would be around 25. This seems a little low though, so a better value would be 30. Even better, a range of values would make the character unique within a given race, so a range of max value +- 5 would be a nice range to use for the character. This would make humans have a low of 25, and a high of 30 for their max attributes. This seems just about right.\n\nWe can now use this as our standard and define other races. For Elves, we have the choice of D20 Elves or Tolkien Elves. I like Tolkien Elves much better, so an Elf might have the same max value of strength as a Human, but higher dexterity. A Halfling might have half the strength of a human, but higher agility. Using this process, we can create a host of races with different value ranges that make each race unique.\n\nYet, we once again faced with a potential problem, the stat wall. The D20 system might help us here too. In D20, a character can only improve an ability or attribute when they gain a level. By using a leveling scheme, we can manage the growth of the character while the game is in progress. D20 uses a class-based advancement scheme which I don\'t care for, since this is an aspect of the individual, that is the DNA of the person, not what he or she does as a living (although that does play a role). Instead of a class system, we can use the intelligence trait of the individual to measure their progress. This is where we can use the percentage factor to our advantage.\n\nSuppose a character has an intelligence of 20. If we subtract 20 from 100, we get 80. If we make that a percentage we get 80%. This 80% is what we use (based on an INT of 20) to determine how many experience points a character will need to advance to the next level. For example, if we start a level 0 character with 100 experience points, then to advance to level 1, we would take the experience at the current level and add in 80% of that, or 80, for the next level. So to get to level 1, the character would need to gain 180 experience points. If we use this model for each level, we get an nice expanding set of ranges without hard-coding any troublesome tables.\n\nHere is an example range:\n\n\x3Cdiv align="center"\x3E\n{|\n|+ Experience Advancement (INT 20)\n| *Current XP * || *XP Needed* || *Next Level*\n| 100 || 180 || 1\n| 180 || 324 || 2\n| 324 || 583 || 3\n| 583 || 1050 || 4\n| 1050 || 1890 || 5\n| 1890 || 3401 || 6\n| 3401 || 6122 || 7\n| 6122 || 11020 || 8\n| 11020 || 19836 || 9\n| 19836 || 35705 || 10\n|}\n\x3C/div\x3E\n\nNotice that each range keeps expanding, making each level more work to get to, just as you would expect. Using this scheme, we could allow the character unlimited advancement, or at least until they have reached the maximum values of each attribute. How many advancement points should we give a character as they gain a level? Maybe 1 to 5 for each level up to a certain point, and then 1 afterwords. This could even be a character trait. Humans gain 1 to 5, but a Dwarf may only gain 1 to 3 and so on. This is something that we can only really define and refine during testing. Of course, if they upgrade their intelligence value, then we simply adjust the formula.\n\nThis method gives us a lot of flexibility. If the character has reached the maximum of each attribute, we could award bonus points per level so that they could apply these to certain actions like opening a door, or wielding a certain weapon. If classes were added to the game, these could go toward class skills. There are a lot of possibilities. \n\nThe question is, does it work in a real game? That can only be answered when we try it. This is why making games is so interesting and fun. Coming up with a system like this and putting it to the test to see if it works, is one of the more satisfying aspects of game design and coding. Even if the system fails, the process itself has its rewards.',
'= Procedures\nIf you have looked at the code you will see that we used objects for the level and character data. I chose to use objects for the organizational aspect of object since all the data and methods are grouped together and it is clear what belongs where. This makes understanding the organization a bit easier, especially when you are looking at code you did not write yourself. It also ensures data integrity and hides the implementation details which make updating the code a lot easier. However, you can create a game like this without using objects at all. To let you see what the code looks like from a purely procedural standpoint, I converted the code from using objects to using procedures. You can find the updated code in the distribution under the /no_obj/ folder.\n\nConverting the code was actually quite easy. The first thing I did was to move the object declarations into separate files and then /#Include/d them into the project in /character.bi/ and /map.bi/. This is to prevent any forward references errors you might get from the order of the implementation.\n\nThe next step was to move the data members into the module code and set the scope to global using /Dim Shared/. After that I had to convert the /Properties/ into /Sub/ and /Function/ calls. Since /Set/ and /Get/ properties use the same name, I needed to change the names so as not to get duplicate definition errors. For the set properties, I changed the names to /Sub SetXXX/ and the get properties to /Function GetXXX/. It was then a matter of deleting the object prefixes, /pchar/ and /level/ in each of the files, and converting the properties over to the respective sub and function calls. \n\nThis wasn\'t as hard as it sounds. I just used the search function of the editor and changed the references one at a time. It was tedious, but not difficult. I didn\'t need to change any of the existing sub and functions; they worked just fine. All told I probably spent a couple of hours doing the conversion.\n\nWhile objects do offer some real benefits, it is certainly not the only way to write a program like this. Follow what you know and what you are comfortable doing. The main goal is to get a working program. How you get there is entirely up to you.',
'= Permadeath\nPermadeath, that is, when a character dies that character is lost forever, has been in roguelikes since the beginning. It is one of the defining features of the roguelike genre. Some argue that unless your game does not have permadeath, it isn\'t a roguelike. I dispute that.\n\nI want to make a game that people want to play. Not many people will play a game more than once when their hours of playing goes down the drain on the roll of the dice. The player, to a large extent, is at the mercy of the random number generator, and if the random number generator happens to be cruel, we shouldn\'t penalize the player.\n\nHowever, there are those that argue that permadeath adds a level of tension to the game that you don\'t get in other games. To some extent this is true. But at what point does this tension over-rule playability? Permadeath goes too far, in my opinion. It simply isn\'t fun.\n\nYet, I have implemented a comprise in the game, since after all this is a roguelike. The comprise is simple: I only allow one save file. This follows the spirit of the genre, and yet allows the player to not lose all the hours of play they have invested if they happen to be a victim of some bad rolls. By one having one save file, the player knows that decisions are important. If he plays himself into a corner that he can\'t load his way out of, then that is his own doing. If the player has been smart about his saves, then he can continue and try something different. I think this preserves the tension, and is kinder to the player.\n\nAfter all, we make games for people to actually play, and hopefully, play more than once.',
'= 48: Instructions\n\x3Cdiv align="center"\x3E\x3Cimg src=\'images/instruct.png\' /\x3E\x3C/div\x3E\n\nThe last feature we need to implement is some basic instructions on the game. Since all of the key commands are each screen, we do not need to go into great detail on the command set. The instructions will just give a general overview on how to play the game. We also do not want to tell everything in the instructions either. It is much more fun to discover the different spells you can acquire than just getting a list of spells that you need to fill out. This is part of the discovery process, and is a way to keep the player engaged in the game.\n\nWhat we do talk about in the instructions are the things that may not be readily apparent like how to learn a spell or what the Wandering Merchant is supposed to do for the character. These types of things can help prevent confusion and allow the player to get into the game quickly. The fact is however, that most people don\'t read instructions and if they do read them, it is after the fact. This is why we have worked so hard on making the game playable without instructions. The game follow standard conventions for the most part, and the other areas that may be unique to this game are not too hard to figure out. This is why all the commands are on each screen, and the commands do what you would expect them to do. Most people will never need to look over the instructions in order to play the game.\n\nThe code for the instructions is not complicated at all.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Loop until the user selects New, Load or Quit.\nDo\n   \'Draw the main menu.\n    mm = mmenu.MainMenu\n...\n   ElseIf mm = mmenu.mInstruction Then\n      DrawInstructions\n   EndIf\nLoop Until mm \x3C\x3E mmenu.mInstruction\n}}}\n\x3C/div\x3E\nWe have added a new subroutine to the main code /DrawInstructions/ which displays the instructions to the player. This is called from the main menu, and when the player is done reading the instructions, they are taken back to the main menu to make another selection.\n\nThe DrawInstructions subroutine follows the same pattern that we have used in our other information screens.\n\n\x3Cdiv class="code"\x3E\n/dod.bas/\n{{{\n\'Draws instructions to the screen.\nSub DrawInstructions()\n   Dim As Integer row, col, ch, page = 1, done = FALSE\n   Dim As String txt, txt1\n   \n   \'Set the dtarting row and column.\n   row = 2\n   col = 2\n   Do\n      ScreenLock\n      DrawBackground leatherback()\n      \'Draw each page.\n      If page = 1 Then\n         DrawBackground leatherback()\n         PutTextShadow "About", row, col, fbYellowBright\n         row += 1\n         txt = "Dungeon of Doom is a roguelike game where you must descend the dungeon "\n         txt &= "battling monsters to find the Amulet of Crystal Fire and return to the "\n         txt &= "surface world to save the Kingdom from destruction. Along the way you "\n         txt &= "will find many items to help you in your quest, from weapons and armor "\n         txt &= "to spell books. As you journey you will be able to increase your character\'s "\n         txt &= "abilities so that you can meet the ever growing challenge of getting to "\n         txt &= "the last level where the Amulet is hidden."\n         Do While Len(txt) \x3E 0\n            txt1 = WordWrap(txt, txcols - 2)\n            row += 1\n            PutTextShadow txt1, row, col\n         Loop\n         row += 2\n         PutTextShadow "Objective", row, col, fbYellowBright\n         row += 1\n         txt = "To win the game you must descend to the botton level of the dungeon, "\n         txt &= "level " & maxlevel & " and recover the Amulet of Crystal Fire and "\n         txt &= "return to the surface. To escape the dungeon, return to level 1 and "\n         txt &= "take the stairs up. In order to leave the dungeon you must have the "\n         txt &= "Amulet. To recover the Amulet, simply walk over it."\n         Do While Len(txt) \x3E 0\n            txt1 = WordWrap(txt, txcols - 2)\n            row += 1\n            PutTextShadow txt1, row, col\n         Loop\n         row += 2\n         PutTextShadow "Moving Around", row, col, fbYellowBright\n         row += 1\n         txt = "To move around you can use the arrow keys or number pad with numlock "\n         txt &= "on. When using the number pad, the numbers represent the direction "\n         txt &= "the character, \'@\' can move. The 5 key does not have a command "\n         txt &= "associated with it. To descend to a new level stand on the down stairs "\n         txt &= "\'\x3E\' and press the corresponding down key \'\x3E\'. To go up a level stand "\n         txt &= "on the up stairs \'\x3C\' and press the up key \'\x3C\'. "\n         Do While Len(txt) \x3E 0\n            txt1 = WordWrap(txt, txcols - 2)\n            row += 1\n            PutTextShadow txt1, row, col\n         Loop\n         row += 2\n         PutTextShadow "Key Commands", row, col, fbYellowBright\n         row += 1\n         txt = "All of the key commands are listed on the main display. Most commands "\n         txt &= "are active in the main display. Inventory and character advancement "\n         txt &= "are handled with seperate screens, and all the available commands "\n         txt &= "are listed on each screen. To exit the game, press escape. When you "\n         txt &= "exit the game, the game will be saved."\n         Do While Len(txt) \x3E 0\n            txt1 = WordWrap(txt, txcols - 2)\n            row += 1\n            PutTextShadow txt1, row, col\n         Loop\n         row += 2\n         PutTextShadow "Combat", row, col, fbYellowBright\n         row += 1\n         txt = "There are three types of comabt, melee, which is hand-to-hand combat, "\n         txt &= "projectile, using bows and crossbows, and spell casting. For melee "\n         txt &= "combat bump into an opponent. Any weapon that is equipped, other than "\n         txt &= "a bow or crossbow, will be used to attack. If the character is carrying "\n         txt &= "a bow or crossbow, then the character will engage in unarmed combat. "\n         txt & = "Projectile weapons must be loaded before they can be used. "\n         txt &= "For projectile weapons, you must target an opponent using the \'t\' command. "\n         txt &= "For spell casting use the \'c\' key. Spells require mana to cast, so the "\n         txt &= "character must have enough mana to cast a spell. Mana Orbs will replenish "\n         txt &= "mana. " \n         Do While Len(txt) \x3E 0\n            txt1 = WordWrap(txt, txcols - 2)\n            row += 1\n            PutTextShadow txt1, row, col\n         Loop\n         row += 2\n         PutTextShadow "Equipment", row, col, fbYellowBright\n         row += 1\n         txt = "Any items found may have magical properties, but they have to be "\n         txt &= "evaluated first before the magical properties manifext themselves. " \n         txt &= "In order to cast spells, the character must learn a spell by reading  "\n         txt &= "a spell book. Spell books must be evaluated before they can be read. "\n         txt &= "If the character reads a spell ook that he has already learned, the "\n         txt &= "spell level is increased. Blank spell books can have spells inscribed "\n         txt &= "on them by the Wandering Merchant."  \n         Do While Len(txt) \x3E 0\n            txt1 = WordWrap(txt, txcols - 2)\n            row += 1\n            PutTextShadow txt1, row, col\n         Loop\n         txt = "Press any key to continue, escape to exit."\n         row = txrows - 1\n         col = CenterX(txt)\n         PutTextShadow txt, row, col\n      ElseIf page = 2 Then\n         PutTextShadow "Wandering Merchant", row, col, fbYellowBright\n         row += 1\n         txt = "The Wandering Merchant, designated with a green \'W\' is a seller "\n         txt &= "and buyer of goods. He stands next to the down stairs on each level "\n         txt &= "and will sell you items or buy items that you have found in the dungeon. "\n         txt &= "Items that you find in the dungeon can be sold for gold that you can use "\n         txt &= "to purchase other itms, or to inscribe blank spell books. You can inscribe "\n         txt &= "any spell on a blank spell book. You must have an unread, blank spell book "\n         txt &= "in your iventory before you can inscribe a spell on it. Reading spell books "\n         txt &= "destroys them, so (I)nspect a spell book before reading it to see if it "\n         txt &= "is blank."\n         Do While Len(txt) \x3E 0\n            txt1 = WordWrap(txt, txcols - 2)\n            row += 1\n            PutTextShadow txt1, row, col\n         Loop\n         row += 2\n         PutTextShadow "Improving a Character", row, col, fbYellowBright\n         row += 1\n         txt = "You can improve a character by spending experience points. You can only "\n         txt &= "increase the basic attributes of the character. The comabt factors are "\n         txt &= "calculated values based on the current attributes. The combat factors are "\n         txt &= "Unarmed combat (UCF), armed combat (ACF), projectile combat (PCF) and magic "\n         txt &= "combat (MCF). There are two defense factors, combat defense (CDF) and magic "\n         txt &= "defense (MDF). The hihger the combat factor, the more likely the character "\n         txt &= "will hit a target. The higher the defense factor, the less likely the character "\n         txt &= "will be hit by an opponent. Note that armor only reduces the damage done by an "\n         txt &= "attack, it does not make the character harder to hit."  \n         Do While Len(txt) \x3E 0\n            txt1 = WordWrap(txt, txcols - 2)\n            row += 1\n            PutTextShadow txt1, row, col\n         Loop\n         row += 2\n         PutTextShadow "A Basic Game", row, col, fbYellowBright\n         row += 1\n         txt = "Dungeon of Doom is a playable game, but it is very basic and does not have "\n         txt &= "a lot of features found in other roguelike games. The purpose of this game "\n         txt &= "is to illustrate the concepts of the genre and is meant to be used in conjunction "\n         txt &= "with my ebook, Let\'s Build a Roguelike. The ebook covers the concepts and code "\n         txt &= "of this game in detail. The book is free and can be found at "\n         txt &= "http://users.freebasic-portal.de/rdc/tutorials.html. " \n         Do While Len(txt) \x3E 0\n            txt1 = WordWrap(txt, txcols - 2)\n            row += 1\n            PutTextShadow txt1, row, col\n         Loop\n         txt = "Press any key to continue, escape to exit."\n         row = txrows - 1\n         col = CenterX(txt)\n         PutTextShadow txt, row, col\n      End If\n      ScreenUnLock\n      \'Wait for a key.\n      ch = GetKey\n      \'If escape exit.\n      If ch = 27 Then\n         done = TRUE\n      Else\n         row = 2\n         col = 2\n         page += 1\n      EndIf\n   Loop Until (done = TRUE) Or (page \x3E 2)\nEnd Sub\n}}}\n\x3C/div\x3E\nAs you can see most of this is just filling in the text and printing it to the screen. Notice that we have broken the text up into pages, and the main Do Loop will just print each page and then wait for a key using {{{ch = GetKey}}}. GetKey will wait for a key, and return the ASCII code of the key press. The only key we need to know about here is the escape key, ASCII code 27, which is used to signal an early exit from the instructions. If any other key is pressed, we just increment the page number in the variable /page/ and then print the next screen. As I already mentioned, the instructions are minimal and only cover two pages.\n\nSince we are using an If ElseIf structure for each page, if we need to add more instructions to the game, we can just add in a new section of text and update the exit condition {{{Or (page \x3E 2)}}}. This page structure is a good way to present information to the user and makes updating the text very easy.\n\nWith this update to the code, the program is now complete. Or is it? Programs like this are never really finished. I talk about some of things you can do with the code in the next, and final, chapter.',
'= tWidgets\nThe tWidgets library is a set of GUI widgets that are used to gather and display information in the program. The widgets are text-based and are called through the /tWidgets/ namespace. To use the widgets in a program, you must include /tWidgets.bi/ in the main program.\n\n*Example*\n\n{{{\n#Include "twidgets.bi"\n}}}\n',
'= InitWidgets\nThe InitWidgets subroutine initializes the widget library. This must be called after setting a graphic screen, and before calling any of the widget functions.\n\n*Example*\n{{{\n\'Using 640x480 32bit screen with 80x60 text.\nScreenRes sw, sh, 32\nWidth txcols, txrows\nWindowTitle "Dungeon of Doom"\nRandomize Timer \'Seed the rnd generator.\ntWidgets.InitWidgets \'Initialzie the widgets.\n}}}',
'= tButton\nThe tButton widget displays a button on the screen that has a caption and can receive input. The button is used in the other widgets, but can be used alone if desired.\n\n*Properties*\n{{{\n   Declare Property Row (r As Integer)               \'Sets button row.\n   Declare Property Row () As Integer                \'Returns button row.\n   Declare Property Col (c As Integer)               \'Sets button column.\n   Declare Property Col () As Integer                \'Returns button column.\n   Declare Property Text (s As String)               \'Sets button caption.\n   Declare Property Text () As String                \'Returns button caption.\n   Declare Property SelText (s As String)            \'Sets selected button caption.\n   Declare Property SelText () As String             \'Returns selected button caption.\n   Declare Property BColor (clr As UInteger)         \'Sets non-selected color.\n   Declare Property BColor () As UInteger            \'Returns non-selected color.\n   Declare Property SelColor (clr As UInteger)       \'Sets selected color.\n   Declare Property SelColor () As UInteger          \'Returns selected color.\n   Declare Property Selected (b As Integer)          \'Sets button to selected state.\n   Declare Property Selected () As Integer           \'Returns button selected state.\n   Declare Property Shadow (s As Integer)            \'Sets the shadow flag. \n   Declare Property Shadow () As Integer             \'Returns the shadow flag.\n   Declare Property ID (i As Integer)                \'Sets the button id. \n   Declare Property ID () As Integer                 \'Returns the button id. \n}}}\n*Subroutines*\n{{{\n   Declare Sub DrawButton ()                         \'Draws a button on the screen.\n   Declare Sub DestroyButton ()                      \'Calls _DestroyButton.\n}}}\n',
'= tWin\nThe tWin widget is a basic window that is used by the other widgets as a drawing surface.\n\n*Enumerations*\n{{{\n\'Window line styles.\nEnum wlinestyle\n   wlNone\n   wlDoubleLine\n   wlSingleLine\n   wlDoubleSingleLine\n   wlSingleDoubleLine\n   wlSolidLine\nEnd Enum\n\n\'Title alignment ids.\nEnum wtitlealign\n   taCenter = 1\n   taLeft\n   taRight\nEnd Enum\n}}}\n*Properties*\n{{{\n   Declare Property InitOK () As Integer         \'Returns the init flag: OK = tTrue, Error = tFalse.\n   Declare Property Title(s As String)           \'Sets the window title.\n   Declare Property Title() As String            \'Returns the window title.\n   Declare Property AlignTitle(t As wtitlealign) \'Sets the window title alignment.\n   Declare Property AlignTitle() As wtitlealign  \'Returns the window title alignment.\n   Declare Property TitleColor(c As UInteger)    \'Sets the window title color.\n   Declare Property TitleColor() As UInteger     \'Returns the window title color.\n   Declare Property LineStyle (ls As wlinestyle) \'Sets the border line style.\n   Declare Property LineStyle () As wlinestyle   \'Returns the border line style.\n   Declare Property BorderColor(clr As UInteger) \'Sets the border color.\n   Declare Property BorderColor() As UInteger    \'Returns the border color.\n   Declare Property BackColor (clr As UInteger)  \'Sets the window background color.\n   Declare Property BackColor () As UInteger     \'Returns the window background color.\n   Declare Property RowTop (r As Integer)        \'Sets the top row.\n   Declare Property RowTop () As Integer         \'Returns the top row.\n   Declare Property ColLeft (c As Integer)       \'Sets the left column.\n   Declare Property ColLeft () As Integer        \'Returns the left column.\n   Declare Property RowBottom (r As Integer)     \'Sets the top row.\n   Declare Property RowBottom () As Integer      \'Returns the top row.\n   Declare Property ColRight (c As Integer)      \'Sets the right column.\n   Declare Property ColRight () As Integer       \'Returns the right column.\n   Declare Property ColorDepth () As Integer     \'Returns the current color depth.\n   Declare Property Cols() As Integer            \'Returns the number of columns on the screen.\n   Declare Property Rows() As Integer            \'Returns the number of rows on the screen.\n   Declare Property Shadow (s As Integer)                        \'Sets the shadow drawing on and off.\n   Declare Property Shadow () As Integer                         \'Returns current shadow flag setting.\n   Declare property ButtonSelected (index As Integer, sel As Integer) \'Sets or clears button selection state.\n   Declare property ButtonSelected (index As Integer) As Integer      \'Returns button selected state.\n}}}\n*Subroutines*\n{{{\n   Declare Sub DrawWindow()                                      \'Calls _DrawWindow.\n   Declare Sub DrawButton(index As Integer)                      \'Draws indexed button.\n   Declare Sub DestroyWindow ()                                  \'Calls _DestroyWindow.\n}}}\n*Functions*\n{{{\n   Declare Function AddButton (btn As tButton) As Integer \'Adds a button to the window returning the button index.\n}}}\n',
'= tMsgBox\nThe tMsgBox widget displays a message box on the screen. The message box can be configured for different types of input using the message type enumerations.\n\n*Enumerations*\n{{{\n\'GUI button ids\nEnum btnID\n   gbnNone\n   gbnOK\n   gbnYes\n   gbnNo\n   gbnCancel\nEnd Enum\n\n\'GUI messagebox type.\nEnum MsgBoxType\n   gmbOK = 1\n   gmbOKCancel\n   gmbYesNo\n   gmbYesNoCancel\nEnd Enum\n}}}\n*Properties*\n{{{\n   Declare Property WindowStyle (wstyle As wlinestyle)      \'Sets the window style.\n   Declare Property WindowStyle () As wlinestyle            \'Returns the window style.\n   Declare Property MessageStyle (m As MsgBoxType)          \'Sets the msgbox style.\n   Declare Property MessageStyle () As MsgBoxType           \'Returns the msgbox style.\n   Declare Property BackgroundColor (clr As UInteger)       \'Sets background color.\n   Declare Property BackgroundColor () As UInteger          \'Returns background color.\n   Declare Property BorderColor (clr As UInteger)           \'Sets the border color.\n   Declare Property BorderColor () As UInteger              \'Returns the border color.\n   Declare Property ButtonSelectColor (clr As UInteger)     \'Sets the color of the selected button.\n   Declare Property ButtonSelectColor () As UInteger        \'Returns the color of the selected button.\n   Declare Property ButtonColor (clr As UInteger)           \'Sets the color of the non-selected button.\n   Declare Property ButtonColor () As UInteger              \'Returns the color of the non-selected button.\n   Declare Property PromptColor (clr As UInteger)           \'Sets the prompt forecolor.\n   Declare Property PromptColor () As UInteger              \'Return the prompt forecolor.\n   Declare Property Title(s As String)                      \'Sets the window title.\n   Declare Property Title() As String                       \'Returns the window title.\n   Declare Property AlignTitle(t As wtitlealign)            \'Sets the window title alignment.\n   Declare Property AlignTitle() As wtitlealign             \'Returns the window title alignment.\n   Declare Property TitleColor(c As UInteger)               \'Sets the window title color.\n   Declare Property TitleColor() As UInteger                \'Returns the window title color.\n   Declare Property Shadow (s As Integer)                   \'Sets the shadow flag. \n   Declare Property Shadow () As Integer                    \'Returns the shadow flag. \n}}}\n*Functions*\n{{{\n   Declare Function MessageBox (prompt As String) As btnID  \'Draws messagebox on screen using prompt.\n   Declare Function MessageBox (lines() As String) As btnID  \'Draws messagebox on screen using array of lines.\n}}}\n*Example*\n{{{\n  Dim As tWidgets.tMsgbox sok\n  Dim As tWidgets.btnID btn\n      \n  sok.MessageStyle = tWidgets.MsgBoxType.gmbOK\n  sok.Title = "Load Game"\n  btn = sok.MessageBox("Game loaded.")\n}}}\n\nThe message box can also display multiple lines in the text area of the window.\n\n*Example*\n{{{\n\'Print message to user using msgbox.\nSub ShowMsgLines(title As String, mess() As String, mtype As tWidgets.MsgBoxType)\n   Dim As tWidgets.tMsgbox mb\n   Dim As tWidgets.btnID btn\n\n   mb.MessageStyle = mtype\n   mb.Title = title\n   btn = mb.MessageBox(mess())\n   \nEnd Sub\n}}}\n',
'= tEdit\nThe tEdit is a simple input field that supports input of text and the backspace key. tEdit is used by tInputBox to get input from the user.\n\n*Properties*\n{{{\n   Declare Property Row(r As Integer)          \'Sets row of input field.\n   Declare Property Row() As Integer           \'Returns row of input field.\n   Declare Property Col(c As Integer)          \'Sets column of input field.\n   Declare Property Col() As Integer           \'Returns column of input field.\n   Declare Property Text (t As String)         \'Sets the text property.\n   Declare Property Text () As String          \'Returns the text.\n   Declare Property EditMask (t As String)         \'Sets the mask property.\n   Declare Property EditMask () As String          \'Returns the mask.\n   Declare Property MaxLen(m As Integer)       \'Sets the max lengtgh of input text. 0 = unlimited.\n   Declare Property MaxLen() As Integer        \'Returns the max lengtgh of input text. 0 = unlimited.\n   Declare Property InputLen (i As Integer)    \'Sets the lengtgh of the input field. Default is 10 chars. \n   Declare Property InputLen () As Integer     \'Returns the lengtgh of the input field. Default is 10 chars.\n   Declare Property TextColor (c As UInteger)  \'Sets the color of the text.\n   Declare Property TextColor () As UInteger   \'Returns the color of the text.\n   Declare Property InputColor (c As UInteger) \'Sets the color of the input field. \n   Declare Property InputColor () As UInteger  \'Returns the color of the input field.\n   Declare Property InputChar (c As Integer)   \'Sets the input field to ascci code character. \n   Declare Property InputChar () As Integer    \'Returns the input field ascci code character.\n   Declare Property CaretColor (c As UInteger) \'Sets the caret color.\n   Declare Property CaretColor () As UInteger  \'Returns the caret color.\n   Declare Property Caret (c As Integer)       \'Sets the caret to ascii code character.\n   Declare Property Caret () As Integer        \'Returns the caret ascii code.\n   Declare Property CaretIns (c As Integer)    \'Sets the insert caret to ascii code character.\n   Declare Property CaretIns () As Integer     \'Returns the insert caret ascii code.\n}}}\n*Functions*\n{{{\n   Declare Function DrawEdit () As tKeys       \'Draws te edit field and returns the key command. \n}}}\n\n\n',
'= tInputBox\nThe tInputBox widget is used to get a line of text from the user.\n\n*Properties*\n{{{\n   Declare Property Row(r As Integer)                   \'Sets the inputbox row.\n   Declare Property Row() As Integer                    \'Returns the inputbox row.\n   Declare Property Col(c As Integer)                   \'Sets the inputbox column.\n   Declare Property Col() As Integer                    \'Returns the inputbox column.\n   Declare Property WindowStyle (wstyle As wlinestyle)  \'Sets the window style.\n   Declare Property WindowStyle () As wlinestyle        \'Returns the window style.\n   Declare Property BackgroundColor (clr As UInteger)   \'Sets background color.\n   Declare Property BorderColor (clr As UInteger)       \'Sets the border color.\n   Declare Property BorderColor () As UInteger          \'Returns the border color.\n   Declare Property ButtonSelectColor (clr As UInteger) \'Sets the color of the selected button.\n   Declare Property ButtonSelectColor () As UInteger    \'Returns the color of the selected button.\n   Declare Property ButtonColor (clr As UInteger)       \'Sets the color of the non-selected button.\n   Declare Property ButtonColor () As UInteger          \'Returns the color of the non-selected button.\n   Declare Property Prompt (p As String)                \'Sets the prompt string.\n   Declare Property Prompt () As String                 \'Return the prompt string.\n   Declare Property PromptColor (clr As UInteger)       \'Sets the prompt forecolor.\n   Declare Property PromptColor () As UInteger          \'Return the prompt forecolor.\n   Declare Property Title(s As String)                  \'Sets the window title.\n   Declare Property Title() As String                   \'Returns the window title.\n   Declare Property AlignTitle(t As wtitlealign)        \'Sets the window title alignment.\n   Declare Property AlignTitle() As wtitlealign         \'Returns the window title alignment.\n   Declare Property TitleColor(c As UInteger)           \'Sets the window title color.\n   Declare Property TitleColor() As UInteger            \'Returns the window title color.\n   Declare Property Shadow (s As Integer)               \'Sets the shadow flag. \n   Declare Property Shadow () As Integer                \'Returns the shadow flag. \n   Declare Property MaxLen(m As Integer)                \'Sets the max lengtgh of input text. 0 = unlimited.\n   Declare Property MaxLen() As Integer                 \'Returns the max lengtgh of input text. 0 = unlimited.\n   Declare Property InputLen (i As Integer)             \'Sets the lengtgh of the input field. Default is 10 chars. \n   Declare Property InputLen () As Integer              \'Returns the lengtgh of the input field. Default is 10 chars.\n   Declare Property TextColor (c As UInteger)           \'Sets the color of the text.\n   Declare Property TextColor () As UInteger            \'Returns the color of the text.\n   Declare Property InputColor (c As UInteger)          \'Sets the color of the input field. \n   Declare Property InputColor () As UInteger           \'Returns the color of the input field.\n   Declare Property InputChar (c As Integer)            \'Sets the input field to ascci code character. \n   Declare Property InputChar () As Integer             \'Returns the input field ascci code character.\n   Declare Property CaretColor (c As UInteger)          \'Sets the caret color.\n   Declare Property CaretColor () As UInteger           \'Returns the caret color.\n   Declare Property Caret (c As Integer)                \'Sets the caret to ascii code character.\n   Declare Property Caret () As Integer                 \'Returns the caret ascii code.\n   Declare Property CaretIns (c As Integer)             \'Sets the insert caret to ascii code character.\n   Declare Property CaretIns () As Integer              \'Returns the insert caret ascii code..\n   Declare Property BackColor (c As UInteger)            \'Sets the window back color.\n   Declare Property BackColor () As UInteger             \'Returns the window back color.\n   Declare Property EditMask (t As String)               \'Sets the mask property.\n   Declare Property EditMask () As String                \'Returns the mask.\n}}}\n*Functions*\n{{{\n   Declare Function Inputbox (result As String) As btnID \'Draws inputbox on screen.\n}}}\n*Example*\n{{{\n   Dim As tWidgets.btnID btn\n   Dim As tWidgets.tInputbox ib\n   ib.Title = "Character Name"\n   ib.Prompt = "Enter your character\'s name:"\n   ib.MaxLen = 35\n   ib.InputLen = 35\n   \'Get the name of the character.\n   btn = ib.Inputbox(chname)\n   If btn = tWidgets.btnID.gbnCancel Then\n      ret = FALSE\n   EndIf\n   If btn = tWidgets.btnID.gbnOK Then\n      ret = TRUE\n   EndIf\n}}}\n',
'= tList\nThe tList widgets displays a list to the user for selections.\n\n*Enumerations*\n{{{\nType listtype\n   id As Integer\n   text As String\n   ldata As Integer\nEnd Type\n}}}\n*Properties*\n{{{\n   Declare Property Row(r As Integer)                   \'Sets the inputbox row.\n   Declare Property Row() As Integer                    \'Returns the inputbox row.\n   Declare Property Col(c As Integer)                   \'Sets the inputbox column.\n   Declare Property Col() As Integer                    \'Returns the inputbox column.\n   Declare Property WindowStyle (wstyle As wlinestyle)  \'Sets the window style.\n   Declare Property WindowStyle () As wlinestyle        \'Returns the window style.\n   Declare Property BackColor (c As UInteger)            \'Sets the window back color.\n   Declare Property BackColor () As UInteger             \'Returns the window back color.\n   Declare Property BackgroundColor (clr As UInteger)   \'Sets background color.\n   Declare Property BorderColor (clr As UInteger)       \'Sets the border color.\n   Declare Property BorderColor () As UInteger          \'Returns the border color.\n   Declare Property ButtonSelectColor (clr As UInteger) \'Sets the color of the selected button.\n   Declare Property ButtonSelectColor () As UInteger    \'Returns the color of the selected button.\n   Declare Property ButtonColor (clr As UInteger)       \'Sets the color of the non-selected button.\n   Declare Property ButtonColor () As UInteger          \'Returns the color of the non-selected button.\n   Declare Property Prompt (p As String)                \'Sets the prompt string.\n   Declare Property Prompt () As String                 \'Return the prompt string.\n   Declare Property PromptColor (clr As UInteger)       \'Sets the prompt forecolor.\n   Declare Property PromptColor () As UInteger          \'Return the prompt forecolor.\n   Declare Property Title(s As String)                  \'Sets the window title.\n   Declare Property Title() As String                   \'Returns the window title.\n   Declare Property AlignTitle(t As wtitlealign)        \'Sets the window title alignment.\n   Declare Property AlignTitle() As wtitlealign         \'Returns the window title alignment.\n   Declare Property TitleColor(c As UInteger)           \'Sets the window title color.\n   Declare Property TitleColor() As UInteger            \'Returns the window title color.\n   Declare Property Shadow (s As Integer)               \'Sets the shadow flag. \n   Declare Property Shadow () As Integer                \'Returns the shadow flag. \n}}}\n*Functions*\n{{{\n   Declare Function Listbox (lines() As listtype, ByRef id As integer) As btnID \'Displays list and returns selected item id.\n}}}\n*Example*\n{{{\n   Dim As tWidgets.tList lst\n   Dim As tWidgets.btnID btn\n   \'Set up the list window.\n   lst.Title = "Select Item to Buy (Gold: " & pchar.CurrGold & ")"\n   lst.Prompt = "Use Up or Dn key to cycle items, Enter to select."\n   \'Get the player selection.\n   btn = lst.Listbox(invlist(), iitem)\n   If btn = tWidgets.btnID.gbnOK Then\n...\n}}}\n'
];

/* DFQRCTsKlU-0000368-END */

/* ]]> */ </script>
<title woas_permanent="1">Let's Build a Roguelike</title>
<style type="text/css" woas_permanent="1" woas_core_style="1">/* CSS used when loading/saving WoaS - readonly */
body {
	font-family: Georgia, verdana;
}

#woas_custom_accesskeys {
	visibility: hidden;
	display: none;
}

#woas_wiki_area {
	position: relative;
	left: 15.5%;
	width: 82.3%;
	z-index: 0;
	margin-top: 1.313em;
	margin-bottom: 0.75em;
	text-align:justify;
}

#woas_wiki_header {
	top: 0em;
	left: 0em;
	border: 1pt solid #aaa;
	opacity:.65;
	z-index: 1;
	width: 99%;
	padding-right:0.75em;
}

#loading_overlay {
	position: absolute;
	width: 99%;
	height: 100%;
	padding-top: 25%;
	z-index: 100;
	background-color: white;
	left: 0em;
	top: 0em;
	font-size: large;
	text-align: center;
}

.woas_wait_text {
	color: navy;
}

.woas_background, #woas_debug_panel, #woas_debug_console, #woas_debug_log,
#woas_edit_page_title, .woas_editor_button, #woas_pwd_query, #woas_pwd_mask,
.woas_password_desc,.woas_about_footer,.woas_help_background,.woas_help_button,
.woas_helpcode,.woas_helptext, .woas_page_mts, div.woas_text_area, .woas_text_area.locked,
#woas_pwd_mask,.woas_core_page,div.menu,#woas_menu_area,.woas_menu_button,#woas_title,
.woas_nowiki, .woas_nowiki_multiline,pre.woas_search_results,div.woas_toc,p.woas_toc_title,
table.woas_text_area,div.woas_taglinks { }</style>
<script woas_permanent="1" language="javascript" type="text/javascript">
/* <![CDATA[ */
/*** src/aes.js ***/
// AES encryption
// license: GNU/GPLv2
// original code from http://home.versatel.nl/MAvanEverdingen/Code/
// this is a javascript conversion of a C implementation by Mike Scott
// adapted for WoaS by legolas558
// namespace wrapping by pvhl

// namespace wrapper for woas.AES
(function() {
	
var bData = null,
	sData = null,
	aes_i = null,
	aes_j = null,
	tot = null,
	key =  [],
	wMax =  0xFFFFFFFF,
	aesNk = null,
	aesNr = null,
	aesPows = null,
	aesLogs = null,
	aesSBox = null,
	aesSBoxInv = null,
	aesRco = null,
	aesFtable = null,
	aesRtable = null,
	aesFi = null,
	aesRi = null,
	aesFkey = null,
	aesRkey = null;

function rotb(b,n){ return ( b<< n | b>>>( 8-n) ) & 0xFF; }
function rotw(w,n){ return ( w<< n | w>>>(32-n) ) & wMax; }
function getW(a,i){ return a[i]|a[i+1]<<8|a[i+2]<<16|a[i+3]<<24; }
function setW(a,i,w){ a.splice(i,4,w&0xFF,(w>>>8)&0xFF,(w>>>16)&0xFF,(w>>>24)&0xFF); }
function setWInv(a,i,w){ a.splice(i,4,(w>>>24)&0xFF,(w>>>16)&0xFF,(w>>>8)&0xFF,w&0xFF); }
function getB(x,n){ return (x>>>(n*8))&0xFF; }

function aesMult(x, y){ return (x&&y) ? aesPows[(aesLogs[x]+aesLogs[y])%255]:0; }

function aesPackBlock() {
	return [ getW(bData,aes_i), getW(bData,aes_i+4),
			getW(bData,aes_i+8), getW(bData,aes_i+12) ];
}

function aesUnpackBlock(packed){
  for ( var mj=0; mj<4; mj++,aes_i+=4) setW( bData, aes_i, packed[mj] );
}

function aesXTime(p){
  p <<= 1;
  return p&0x100 ? p^0x11B : p;
}

function aesSubByte(w){
  return aesSBox[getB(w,0)] | aesSBox[getB(w,1)]<<8 | aesSBox[getB(w,2)]<<16 | aesSBox[getB(w,3)]<<24;
}

function aesProduct(w1,w2){
  return aesMult(getB(w1,0),getB(w2,0)) ^ aesMult(getB(w1,1),getB(w2,1))
       ^ aesMult(getB(w1,2),getB(w2,2)) ^ aesMult(getB(w1,3),getB(w2,3));
}

function aesInvMixCol(x){
  return aesProduct(0x090d0b0e,x)     | aesProduct(0x0d0b0e09,x)<<8 |
         aesProduct(0x0b0e090d,x)<<16 | aesProduct(0x0e090d0b,x)<<24;
}

function aesByteSub(x){
  var y=aesPows[255-aesLogs[x]];
  x=y;  x=rotb(x,1);
  y^=x; x=rotb(x,1);
  y^=x; x=rotb(x,1);
  y^=x; x=rotb(x,1);
  return x^y^0x63;
}

// Generate static tables
(function(){
  var i,j,m,y;
  aesPows = [ 1,3 ];
  aesLogs = [ 0,0,null,1 ];
  aesSBox = new Array(256);
  aesSBoxInv = new Array(256);
  aesFtable = new Array(256);
  aesRtable = new Array(256);
  aesRco = new Array(30);

  for ( i=2; i < 256; i++){
    aesPows[i]=aesPows[i-1]^aesXTime( aesPows[i-1] );
    aesLogs[aesPows[i]]=i;
  }

  aesSBox[0]=0x63;
  aesSBoxInv[0x63]=0;
  for ( i=1; i < 256; i++){
    y=aesByteSub(i);
    aesSBox[i]=y; aesSBoxInv[y]=i;
  }

  for (i=0,y=1; i < 30; i++){ aesRco[i]=y; y=aesXTime(y); }

  for ( i=0; i < 256; i++){
    y = aesSBox[i];
    aesFtable[i] = aesXTime(y) | y<<8 | y<<16 | (y^aesXTime(y))<<24;
    y = aesSBoxInv[i];
    aesRtable[i]= aesMult(14,y) | aesMult(9,y)<<8 |
                  aesMult(13,y)<<16 | aesMult(11,y)<<24;
  }

  aesFi = new Array(12);
  aesRi = new Array(12);

  for (m=j=0;j<4;j++,m+=3){
    aesFi[m]=(j+1)%4;
    aesFi[m+1]=(j+2)%4;
    aesFi[m+2]=(j+3)%4;
    aesRi[m]=(4+j-1)%4;
    aesRi[m+1]=(4+j-2)%4;
    aesRi[m+2]=(4+j-3)%4;
  }

})();

function aesInit(){
  key=key.slice(0,43);
  var i,k;
  var j = 0;
  var l = key.length;

  while ( l!=16 && l!=24 && l!=32 && l!=43) key[l++]=key[j++];

  aesNk = key.length >>> 2;
  aesNr = 6 + aesNk;

  var N=4*(aesNr+1);
  aesFkey = new Array(N);
  aesRkey = new Array(N);

  for (i=j=0;i< aesNk;i++,j+=4) aesFkey[i]=getW(key,j);

  for (k=0,j=aesNk;j < N;j+=aesNk,k++){
    aesFkey[j]=aesFkey[j-aesNk]^aesSubByte(rotw(aesFkey[j-1], 24))^aesRco[k];
    if (aesNk<=6)
      for (i=1;i < aesNk && (i+j) < N;i++) aesFkey[i+j]=aesFkey[i+j-aesNk]^aesFkey[i+j-1];
    else{
      for (i=1;i<4 &&(i+j) < N;i++) aesFkey[i+j]=aesFkey[i+j-aesNk]^aesFkey[i+j-1];
      if ((j+4) < N) aesFkey[j+4]=aesFkey[j+4-aesNk]^aesSubByte(aesFkey[j+3]);
      for (i=5;i < aesNk && (i+j) < N;i++) aesFkey[i+j]=aesFkey[i+j-aesNk]^aesFkey[i+j-1];
    }
  }

  for (j=0;j<4;j++) aesRkey[j+N-4]=aesFkey[j];
  for (i=4;i < N-4;i+=4){
    k=N-4-i;
    for (j=0;j < 4;j++) aesRkey[k+j]=aesInvMixCol(aesFkey[i+j]);
  }
  for (j=N-4;j < N;j++) aesRkey[j-N+4]=aesFkey[j];
}

function aesClose(){
  aesFkey=aesRkey=null;
}

function aesRounds( block, key, table, inc, box ){
  var tmp = new Array( 4 );
  var i,j,m,r;

  for ( r=0; r<4; r++ ) block[r]^=key[r];
  for ( i=1; i < aesNr; i++ ){
    for (j=m=0;j<4;j++,m+=3){
      tmp[j]=key[r++]^table[block[j]&0xFF]^
			rotw(table[(block[inc[m  ]] >>> 8)&0xFF], 8)^
			rotw(table[(block[inc[m+1]] >>>16)&0xFF],16)^
			rotw(table[(block[inc[m+2]] >>>24)&0xFF],24);
    }
    var t=block; block=tmp; tmp=t;
  }

  for (j=m=0;j<4;j++,m+=3)
    tmp[j]=key[r++]^box[block[j]&0xFF]^
           rotw(box[(block[inc[m  ]] >>> 8)&0xFF], 8)^
           rotw(box[(block[inc[m+1]] >>>16)&0xFF],16)^
           rotw(box[(block[inc[m+2]] >>>24)&0xFF],24);
  return tmp;
}

function _encrypt(){
  aesUnpackBlock( aesRounds(aesPackBlock(), aesFkey, aesFtable, aesFi, aesSBox ) );
}

function _decrypt(){
  aesUnpackBlock( aesRounds(aesPackBlock(), aesRkey, aesRtable, aesRi, aesSBoxInv ) );
}

// Blockcipher

function blcEncrypt(enc){
	if (tot === 0){
		if (key.length < 1) return;
		// pre-pend random data to pad length? really?
		for (aes_i=0; aes_i<16; ++aes_i) bData.unshift( _rand(256) );
		while( bData.length%16 !== 0 ) bData.push(0);
		tot = bData.length;
		aesInit();
	}else{
		for (aes_j=aes_i; aes_j < aes_i+16; aes_j++)
		bData[aes_j] ^= bData[aes_j-16];
		enc();
	}
	if (aes_i >= tot) aesClose();
}

function blcDecrypt(dec){
	// initialize length
	if (tot === 0){
		if (key.length<1) return false;
		aes_i=16;
		tot = bData.length;
		if ( (tot%16) || (tot < aes_i) ) {
			woas.log('AES: Incorrect length (tot='+tot+', aes_i='+aes_i+')'); //log:1
			return false;
		}
		aesInit();
	} else {
		aes_i=tot-aes_i;
		dec();
		for (aes_j=aes_i-16; aes_j < aes_i; aes_j++) bData[aes_j] ^= bData[aes_j-16];
		aes_i = tot+32-aes_i;
	}
	if (aes_i>=tot){
		aesClose();
		bData.splice(0,16);
		// remove 0s added for padding (supposedly!)
		while(bData[bData.length-1] === 0) bData.pop();
	}
	return true;
}

// @module aes
woas.AES = {
	// sets global key to the utf-8 encoded key (byte array)
	setKey: function(sKey) {
		key = woas.utf8.encode_to_array(sKey);
	},
	clearKey: function(){
		key = [];
	},
	// keep 'key' private to this code
	isKeySet: function(){
		return key.length > 0;
	},
	// returns an array of encrypted characters
	encrypt: function(raw_data){
		bData = woas.utf8.encode_to_array(raw_data);
		
		aes_i=tot=0;
		do{ blcEncrypt(_encrypt); } while (aes_i<tot);
		
		var rv = bData;
		bData = null;
		return rv;
	},

	// decrypts an array of encrypted characters
	decrypt: function(raw_data){
		bData = raw_data;
		
		aes_i=tot=0;
		do {
			if (!blcDecrypt(_decrypt))
				return null;
		} while (aes_i<tot);
		
		sData = woas.utf8.decode_from_array(bData);
		bData = [];
		var rv = sData;
		sData = null;
		return rv;
	}
};

})(); // end of AES closure

/*** src/framework.js ***/

woas.browser = {
	// browsers - when different from 'false' it contains the version string
	ie: false, 
	firefox: false,
	opera: false,
	safari: false,
	chrome: false,
	
	// breeds - used internally, should not be used by external plugins
	ie6: false, ie8: false,
	firefox2: false,
	firefox3: false, firefox_new: false,
					
	// engines - set to true when present
	// gecko and webkit will contain the engine version
	gecko: false, webkit: false, presto: false, trident: false
};

// used to match browser version
var m;

if((navigator.userAgent).indexOf("Opera")!=-1) {
	m = navigator.userAgent.match(/Opera\/(\S*)/);
//	if (m && m[1])
		woas.browser.opera = m[1];
} else if (navigator.userAgent.indexOf("Chrome") != -1) {
	// detect version
	m = navigator.userAgent.match(/Chrome\/([^\s]+)/);
//	if (m && m[1])
		woas.browser.chrome = m[1];
} else if (navigator.userAgent.toLowerCase().indexOf("applewebkit") != -1) {
	// Safari never publicizes its version
	woas.browser.safari = true;
} else if(navigator.appName == "Netscape") {
	// check that it is Gecko first
	woas.browser.firefox = woas.browser.gecko = (new RegExp("Gecko\\/\\d")).test(navigator.userAgent) ? true : false;
	// match also development versions of Firefox "Shiretoko" / "Namoroka"
	if (woas.browser.gecko) {
		// match the last word of userAgent
		m = navigator.userAgent.match(/rv:([^\s\)]*)/);
//		if (m && m[1]) {
			woas.browser.gecko = m[1];
			switch (woas.browser.gecko.substr(3)) {
				case "1.8":
					woas.browser.firefox2 = true;
				break;
				case "1.9":
					woas.browser.firefox3 = true;
				break;
				default:
					// possibly Firefox4
					woas.browser.firefox_new = true;
			}
	} // not Gecko
} else if((navigator.appName).indexOf("Microsoft")!=-1) {
	woas.browser.ie8 = document.documentMode ? true : false;
	if (!woas.browser.ie8)
		woas.browser.ie6 = window.XMLHttpRequest ? false : true;
	// detect version
	m = navigator.userAgent.match(/MSIE\s([^;]*)/);
//	if (m && m[1])
		woas.browser.ie = m[1];
}

// finds out if Opera is trying to look like Mozilla
if (woas.browser.firefox && (navigator.product != "Gecko")) {
	woas.browser.firefox = woas.browser.firefox2
	= woas.browser.firefox3 = woas.browser.firefox_new = false;
	if (typeof window.opera != "undefined")
		woas.browser.opera = true;
}

// finds out if Opera is trying to look like IE
if (woas.browser.ie && (typeof window.opera != "undefined")) {
	woas.browser.ie = woas.browser.ie6 = woas.browser.ie8 = false;
	woas.browser.opera = true;
}

// detect engine type
if (woas.browser.ie)
	woas.browser.trident = true;
else if (woas.browser.chrome || woas.browser.safari) {
	m = navigator.userAgent.match(/AppleWebKit\/(\S*)/);
//    if (m && m[1])
		woas.browser.webkit = m[1];
} else if (woas.browser.opera)
	woas.browser.presto = true;

var is_windows = (navigator.appVersion.toLowerCase().indexOf("windows")!=-1);

woas._server_mode = (document.location.toString().match(/^file:\/\//) ? false:true);

// set to true if we need Java-based file load/save
woas.use_java_io = woas.browser.chrome || woas.browser.opera || woas.browser.safari;

// returns the DOM element object given its id - enables a try/catch mode when debugging
if (woas.config.debug_mode) {
	// returns the DOM element object given its id, alerting if the element is not found (but that would never happen, right?)
	function d$(id){ try{return document.getElementById(id);}catch(e){woas.crash("d$('"+id+"') invalid reference:\n\n"+e);} }
} else {
	// much faster version
	function d$(id){return document.getElementById(id);}
}

d$.checked = function(id) {
	//FIXME: configuration should not be changed by reading a property!!
	woas.cfg_changed = true;
	if (d$(id).checked)
		return true;
	return false;
};

d$.hide = function(id) {
	d$(id).style.display = "none";
	d$(id).style.visibility = "hidden";
};

d$.show = function(id) {
	d$(id).style.display = "inline";
	d$(id).style.visibility = "visible";
};

d$.hide_ni = function(id) {
	d$(id).style.visibility = "hidden";
};

d$.show_ni = function(id) {
	d$(id).style.visibility = "visible";
};

d$.is_visible = function(id) {
	return !!(d$(id).style.visibility == 'visible');
};

d$.toggle = function(id) {
	if (d$.is_visible(id))
		d$.hide(id);
	else
		d$.show(id);
};

d$.clone = function(obj) {
	var nobj = {};
	for (var i in obj) {
		nobj[i] = obj[i];
	}
	return nobj;
};

// logging function has not to be in WoaS object
if (woas.config.debug_mode) {
	// logging function - used in development
	woas.log = function (aMessage) {
	    var logbox = d$("woas_debug_log");
	    // count lines
	    if (!woas.tweak.integrity_test) {
			nls = logbox.value.match(/\n/g);
			// log maximum 1024 lines
			if (nls!=null && typeof(nls)==='object' && nls.length>1024)
				logbox.value = "";
		}
		logbox.value += aMessage + "\n";
		// keep the log scrolled down
		logbox.scrollTop = logbox.scrollHeight;
		if(window.opera)
			opera.postError(aMessage);
	};
} else {
	woas.log = function(aMessage) { };
}

// fixes the Array prototype for older browsers
if (typeof Array.prototype.push == "undefined") {
  Array.prototype.push = function(str) {
    this[this.length] = str;
  };
}

// the following methods complete the Array object for non-compliant browsers
if (typeof Array.prototype.splice == "undefined") {
  Array.prototype.splice = function(offset, length) {
    var temp = [];
    for (var i = this.length - 1; i >= 0; i--) {
      if (i < offset || i > (offset + length - 1)) {
        temp[temp.length] = this[i];
      }
      this.length--;
    }
    for (i = temp.length - 1; i >= 0; i--) {
      this[this.length] = temp[i];
    }
    return this;
  };
}

if (typeof Array.prototype.indexOf == "undefined") {
	Array.prototype.indexOf = function(val, fromIndex) {
		if (typeof(fromIndex) != 'number') fromIndex = 0;
		for (var index = fromIndex,len = this.length; index < len; index++)
			if (this[index] == val) return index;
		return -1;
	};
}

// implements a custom function which returns an array with unique elements - deprecated
Array.prototype.toUnique = function() {
	var a_o = {}, new_arr = [];
	var l=this.length;
	for(var i=0; i<l;i++) {
		if (a_o[this[i]]===undefined) {
			a_o[this[i]] = true;
			new_arr.push(this[i]);
		}
	}
	if (new_arr.length!=l)
		return new_arr;
	return this;
};

// provide regex escaping
// thanks to S.Willison
RegExp.escape = function(text) {
  if (!arguments.callee.sRE) {
    var specials = ['/', '.', '*', '+', '?', '|', '$', '(', ')', '[', ']', '{', '}', "\\" ];
    arguments.callee.sRE = new RegExp(
      "(\\" + specials.join("|\\") + ')', 'g'
    );
  }
  return text.replace(arguments.callee.sRE, "\\$1");
};

// repeat string s for n times
 if (typeof String.prototype.repeat == "undefined") {
	String.prototype.repeat = function(n) {
		var r = "";
		while (--n >= 0) r += this;
		return r;
	};
}

// return a random integer given the maximum value (scale)
function _rand(scale) {
	return Math.floor(Math.random() * scale);
}

// returns a random string of given string_length
function _random_string(string_length) {
	var chars = "ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz";
	var randomstring = '';
	for (var i=0; i < string_length; i++) {
		var rnum = _rand(chars.length);
		randomstring += chars.charAt(rnum);
	}
	return randomstring;
}

// converts the number of bytes to a human readable form
function _convert_bytes(bytes) {
	var U=['bytes','Kb','Mb','Gb','Pb'];
	var n=0;
	bytes=Math.ceil(bytes);
	while(bytes>=1024) {
		 ++n;
		 bytes /= 1024;
	}
	return bytes.toFixed(2).replace(/\.00$/, "") +' '+ U[n];
}

// implement an sprintf() bare function
String.prototype.sprintf = function() {
	// check that arguments are OK
	if (typeof arguments == "undefined") { return null; }
	// next argument to pick
	var i_pos = 0, max_pos = arguments.length - 1;
	var fmt_args = arguments;
	return this.replace(/%[sd]/g, function(str) {
		// replace with the original unparsed token in case of undefined parameter
		if (i_pos > max_pos)
			return str;
/*		if (str == '%d')
			return Number(arguments[i_pos++]); */
		// return '%s' string
		return fmt_args[i_pos++];
	});
};

// get filename of currently open file in browser
function _get_this_filename() {
	var filename = unescape(document.location.toString().split("?")[0]);
	if (woas.browser.opera)
		filename = filename.replace(/^file:\/\/[^\/]+/, '');
	else {
		if (filename.indexOf("file://") === 0) // all browsers
			filename = filename.substr(7);
		if (filename.indexOf("///")===0) // firefox
			filename = filename.substr(1);
	}
	//TODO: check that 'g' can be removed
	filename = filename.replace(/#.*$/g, ""); // remove fragment (if any)
	if (is_windows) {
		// convert unix path to windows path
		filename = filename.replace(/\//g, "\\");
		if (filename.substr(0,2)!="\\\\") { // if this is not a network path - will be true in case of Firefox for example
			// remove leading slash before unit:
			if (filename.match(/^\\\w:\\/))
				filename = filename.substr(1);
			if (filename.charAt(1)!=':') {
				if (woas.browser.ie)
					filename = "\\\\"+filename;
			}
		}
	}
	return filename;
}

function ff_fix_focus() {
	//runtime fix for Firefox bug 374786
	if (woas.browser.firefox)
		d$("woas_wiki_area").blur();
}

if (is_windows) {
	var reFwdSlash = new RegExp("/", "g");
	woas.fix_path_separators = function(path) {
		return path.replace(reFwdSlash, woas.DIRECTORY_SEPARATOR);
	};
} else { // UNIX or similar, no path change
	woas.fix_path_separators = function(path) {
		return path;
	};
}

woas.base64 = {
	_b64arr: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
	reValid: /^[A-Za-z0-9+\/=]+$/,
	
	_core_encode: function(c1, c2, c3) {
		var enc1, enc2, enc3, enc4;
		
		enc1 = c1 >> 2;
		enc2 = ((c1 & 3) << 4) | (c2 >> 4);
		enc3 = ((c2 & 15) << 2) | (c3 >> 6);
		enc4 = c3 & 63;

		if (isNaN(c2))	enc3 = enc4 = 64;
		else if (isNaN(c3))
			enc4 = 64;

		return this._b64arr.charAt(enc1) + this._b64arr.charAt(enc2) +
				this._b64arr.charAt(enc3) +	this._b64arr.charAt(enc4);
	},

	encode_array: function(input_arr) {
		var c1, c2, c3, i = 0, z = input_arr.length,output = "";
		
		do {
			c1 = input_arr[i++];
			if (i == z)
				c3 = c2 = null;
			else {
				c2 = input_arr[i++];
				if (i == z)
					c3 = null;
				else
					c3 = input_arr[i++];
			}
			output += this._core_encode(c1, c2, c3);
		} while (i < z);
		return output;
	},

	encode: function(input) {
		var c1, c2, c3, i = 0, z = input.length, output = "";
		
		do {
			c1 = input.charCodeAt(i++);
			c2 = input.charCodeAt(i++);
			c3 = input.charCodeAt(i++);
			
			output += this._core_encode(c1, c2, c3);
		} while (i < z);
		return output;
	},

	decode: function(input, z) {
		var c1, c2, c3, enc1, enc2, enc3, enc4, i = 0,
			output = "";
		
		var l=input.length;
		if (typeof z=='undefined') z = l;
		else if (z>l) z=l;

		do {
			enc1 = this._b64arr.indexOf(input.charAt(i++));
			enc2 = this._b64arr.indexOf(input.charAt(i++));
			enc3 = this._b64arr.indexOf(input.charAt(i++));
			enc4 = this._b64arr.indexOf(input.charAt(i++));

			c1 = (enc1 << 2) | (enc2 >> 4);
			c2 = ((enc2 & 15) << 4) | (enc3 >> 2);
			c3 = ((enc3 & 3) << 6) | enc4;

			output += String.fromCharCode(c1);
			if (enc3 != 64)
				output += String.fromCharCode(c2);
			if (enc4 != 64)
				output += String.fromCharCode(c3);

		} while (i < z);

		return output;
	},

	decode_array: function(input, z) {
		var c1, c2, c3, enc1, enc2, enc3, enc4, i = 0;
		var output = [];
		
		var l=input.length;
		if (typeof z=='undefined') z = l;
		else if (z>l) z=l;

		do {
			enc1 = this._b64arr.indexOf(input.charAt(i++));
			enc2 = this._b64arr.indexOf(input.charAt(i++));
			enc3 = this._b64arr.indexOf(input.charAt(i++));
			enc4 = this._b64arr.indexOf(input.charAt(i++));

			c1 = (enc1 << 2) | (enc2 >> 4);
			c2 = ((enc2 & 15) << 4) | (enc3 >> 2);
			c3 = ((enc3 & 3) << 6) | enc4;

			output.push(c1);
			if (enc3 != 64)
				output.push(c2);
			if (enc4 != 64)
				output.push(c3);
		} while (i < z);
		return output;
	}

};

woas.utf8 = {
	// encode from string to string
	encode: function(s) {
		return unescape( encodeURIComponent( s ) );
	},
	
	encode_to_array: function(s) {
		return woas.split_bytes( this.encode(s) );
	},
	decode: function(s) {
		return decodeURIComponent(escape(s));
	},
	decode_from_array: function(byte_arr) {
		try {
			return this.decode( woas.merge_bytes( byte_arr ) );
		}
		catch (e) {
			woas.log(e);	//log:1
		}
		return null;
	},
	
	reUTF8Space: /[^\u0000-\u007F]+/g,
	
	// convert UTF8 sequences of the XHTML source into &#dddd; sequences
	do_escape: function(src) {
		return src.replace(this.reUTF8Space, function ($1) {
			var l=$1.length;
			var s="";
			for(var i=0;i < l;i++) {
				s+="&#"+$1.charCodeAt(i)+";";
			}
			return s;
		});
	}

};

woas._last_filename = null;

woas._get_path = function(id) {
	if (this.browser.firefox3 || this.browser.firefox_new)
		return this.dirname(ff3_getPath(d$(id)));
	// use the last used path
	if (this.browser.opera)
		return this.dirname(this._last_filename);
	// on older browsers this was allowed
	return this.dirname(d$(id).value);
};

// tool to read/store flags in an integer
woas.bitfield = {
	// 32bit full mask
	_field_mask: [0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80,
				0x100, 0x200, 0x400, 0x800, 0x1000, 0x2000, 0x4000, 0x8000,
				0x10000, 0x20000, 0x40000, 0x80000, 0x100000, 0x200000, 0x400000, 0x800000,
				0x1000000, 0x2000000, 0x4000000, 0x8000000, 0x10000000, 0x20000000, 0x40000000, 0x80000000],
	
	get: function(bm, pos) {
		return (bm & this._field_mask[pos]) ? true : false;
	},
	
	set: function(bm, pos, value) {
		if (value)
			return bm | this._field_mask[pos];
		return bm & ~this._field_mask[pos];
	},
	
	// return an integer after having parsed given object with given order
	get_object: function(obj, order) {
		var rv=0;
		for(var i=0;i<order.length;++i) {
			if (obj[order[i]])
				rv |= this._field_mask[i];
		}
		return rv;
	},

	// set object properties to true/false after parsing the bits by given order
	set_object: function(obj, order, bm) {
		for(var i=0;i<order.length;++i) {
			obj[order[i]] = (bm & this._field_mask[i]) ? true : false;
		}
	}
};

// natural sorting algorithms by B.Huisman
// original algorithms by D.Koelle
// http://my.opera.com/GreyWyvern/blog/show.dml/1671288
woas.chunkify = function(t) {
    var tz = [], x = 0, y = -1, n = 0, i, j;

    while (i = (j = t.charAt(x++)).charCodeAt(0)) {
      var m = (i == 46 || (i >=48 && i <= 57));
      if (m !== n) {
        tz[++y] = "";
        n = m;
      }
      tz[y] += j;
    }
    return tz;
};

woas.strnatcmp = function(a, b) {
  var	aa = woas.chunkify(a),
		bb = woas.chunkify(b);

  for (x = 0; aa[x] && bb[x]; x++) {
    if (aa[x] !== bb[x]) {
      var c = Number(aa[x]), d = Number(bb[x]);
      if (c == aa[x] && d == bb[x]) {
        return c - d;
      } else return (aa[x] > bb[x]) ? 1 : -1;
    }
  }
  return aa.length - bb.length;
};

/*
Array.prototype.natsort = function(caseInsensitive) {
  for (var z = 0, t; t = this[z]; z++) {
    this[z] = [];
    var x = 0, y = -1, n = 0, i, j;

    while (i = (j = t.charAt(x++)).charCodeAt(0)) {
      var m = (i == 46 || (i >=48 && i <= 57));
      if (m !== n) {
        this[z][++y] = "";
        n = m;
      }
      this[z][y] += j;
    }
  }

  this.sort(function(a, b) {
    for (var x = 0, aa, bb; (aa = a[x]) && (bb = b[x]); x++) {
      if (caseInsensitive) {
        aa = aa.toLowerCase();
        bb = bb.toLowerCase();
      }
      if (aa !== bb) {
        var c = Number(aa), d = Number(bb);
        if (c == aa && d == bb) {
          return c - d;
        } else return (aa > bb) ? 1 : -1;
      }
    }
    return a.length - b.length;
  });

  for (var z = 0; z < this.length; z++)
    this[z] = this[z].join("");
}
*/

/*** src/loadsave.js ***/
// load modes which should be supported by load/save browser bindings
woas.file_mode = {
	UTF8_TEXT:		0,
	ASCII_TEXT:		1,
	DATA_URI:		2,
	BINARY:			3,
	BASE64:			4,
	UTF16_TEXT:		8
};

woas._guess_mime = function(filename) {
	var m=filename.match(/\.(\w+)$/);
	if (m===null) m = "";
	else m=m[1].toLowerCase();
	var guess_mime = "image";
	switch (m) {
		case "png":
		case "gif":
		case "bmp":
			guess_mime += "/"+m;
			break;
		case "jpg":
		case "jpeg":
			guess_mime = "image/jpeg";
			break;
	}
	return guess_mime;
};

// creates a DATA:URI from a plain content stream
woas._data_uri_enc = function(filename, ct, guess_mime) {
	if (typeof guess_mime != "string")
		guess_mime = this._guess_mime(filename);
	// perform base64 encoding
	return "data:"+guess_mime+";base64,"+this.base64.encode(ct);
};
woas._data_uri_enc_array = function(filename, arr, guess_mime) {
	if (typeof guess_mime != "string")
		guess_mime = this._guess_mime(filename);
	// perform base64 encoding
	return "data:"+guess_mime+";base64,"+this.base64.encode_array(arr);
};

// save the currently open WoaS
woas._save_this_file = function(new_data, old_data) {
	var filename = _get_this_filename();

	var r = woas.save_file(filename, this.file_mode.ASCII_TEXT,
		this.DOCTYPE + this.DOC_START + "<"+"script woas_permanent=\"1\" type=\"tex"+"t/javascript\">"
		+ new_data + "\n" + old_data + "<"+"/html>");
	if (r===true)
		woas.log("NOTICE: \""+filename+"\" saved successfully");	// log:1
	else {
		var msg = this.i18n.SAVE_ERROR.sprintf(filename) + "\n\n";
		if (this.use_java_io) {
			// try to understand what went bad with Java
			if (typeof document.applets.TiddlySaver == "undefined")
				msg += this.i18n.NO_TIDDLY_SAVER+" "+TIDDLY_HELP;
			else if (typeof java == "undefined")
				msg += this.i18n.NO_JAVA+" "+TIDDLY_HELP;
			else
				msg += this.i18n.UNSPECIFIED_JAVA_ERROR;
		} else
			msg += woas.i18n.UNSUPPORTED_BROWSER.sprintf(navigator.userAgent);
		this.alert(msg);
	}
	return r;
};

//API1.0: save-file handler
//NOTE: save_mode is not always enforced by browser binding
woas.save_file = function(fileUrl, save_mode, content) {
	var r = null;
	if (!this.use_java_io) {
		r = this.mozillaSaveFile(fileUrl, save_mode, content);
		if((r === null) || (r === false))
			r = this.ieSaveFile(fileUrl, save_mode, content);
		// fallback to try also with Java saving
	} else
		return this.javaSaveFile(fileUrl, save_mode, content);
	if((r === null) || (r === false))
		r = this.javaSaveFile(fileUrl, save_mode, content);
	return r;
};

var reDataUrlPfx = new RegExp("^data:\\s*([^;]*);\\s*base64,\\s*");
// get file content in FF3 without .enablePrivilege() (FBNil)
woas.mozillaLoadFileID = function(obj_id, load_mode, suggested_mime) {
	var obj = document.getElementById(obj_id);
	if(!window.Components || !obj.files)
		return null;
	var D=obj.files.item(0);
	if (D === null)
		return false;

	switch (load_mode) {
		case this.file_mode.DATA_URI:
			if (typeof suggested_mime != "string")
				return D.getAsDataURL();
			else // apply mime override
				return D.getAsDataURL().replace(/^data:(\s*)([^;]*)/, "data:$1"+suggested_mime);
			break;
		case this.file_mode.BASE64:
			return D.getAsDataURL().replace(reDataUrlPfx, '');
		case this.file_mode.BINARY:
			return D.getAsBinary();
		case this.file_mode.UTF8_TEXT:
			return D.getAsText("utf-8");
		case this.file_mode.UTF16_TEXT:
			return D.getAsText("utf-16");
		case this.file_mode.ASCII_TEXT:
			return D.getAsText("");
	}
	// not available
	this.crash(this.i18n.MODE_NOT_AVAIL.sprintf(load_mode.toString(16)));
	return null;
};

// see http://dev.w3.org/2006/webapi/FileAPI/
/*woas.ECMALoadFile = function(fileUrl, load_mode, suggested_mime) {
	if (typeof FileReaderSync == "undefined")
		return null;
	var D = new FileReaderSync();
	try {
		switch (load_mode) {
			case this.file_mode.DATA_URI:
				if (typeof suggested_mime != "string")
					return D.readAsDataURL(fileUrl);
				else // apply mime override
					return D.readAsDataURL(fileUrl).replace(/^data:(\s*)([^;]*)/, "data:$1"+suggested_mime);
				break;
			case this.file_mode.BASE64:
				return D.readAsDataURL(fileUrl).replace(reDataUrlPfx, '');
			case this.file_mode.BINARY:
				return D.readAsBinaryString();
			case this.file_mode.UTF8_TEXT:
				return D.readAsText("utf-8");
			case this.file_mode.UTF16_TEXT:
				return D.readAsText("utf-16");
			case this.file_mode.ASCII_TEXT:
				return D.readAsText("");
		}
	} catch (e) {
		woas.log("ECMALoadFile: "+e);
		return false;
	}
	// not available
	this.crash(this.i18n.MODE_NOT_AVAIL.sprintf(load_mode.toString(16)));
	return null;
};*/

// API1.0: load-file handler
woas.load_file = function(fileUrl, load_mode, mime){
	// parameter consistency check
	if (!load_mode) {
		woas.log("WARNING: No load mode specified, defaulting to UTF8_TEXT");
		// perhaps should be ASCII?
		load_mode = this.file_mode.UTF8_TEXT;
	}
	// try loading the file without using the path (FF3+)
	// (object id hardcoded here)
	var r = null;
	if (!this.use_java_io) {
		// correctly retrieve fileUrl
		if (fileUrl === null) {
			if (this.browser.firefox3 || this.browser.firefox_new)
				r = this.mozillaLoadFileID("filename_", load_mode, mime);
			else
				fileUrl = this.get_input_file_url();
		}
		if (r === null) // load file using file absolute path
			r = this.mozillaLoadFile(fileUrl, load_mode, mime);
		else return r;
		if(r === false)
			return false;
		// attempt using ECMAscript
		// disabled because no browser support it yet
/*		if (r === null)
			r = this.ECMALoadFile(fileUrl, load_mode, mime);
		else return r; */
		// no mozillas here, attempt the IE way
		if (r === null)
			r = this.ieLoadFile(fileUrl, load_mode, mime);
		else return r;
		if (r === false)
			return false;
//		if (r === null)
			// finally attempt to use Java
//			r = this.javaLoadFile(fileUrl, load_mode);
	} else {
		if (fileUrl === null)
			fileUrl = this.get_input_file_url();
		if (fileUrl === false)
			return false;
		r = this.javaLoadFile(fileUrl, load_mode, mime);
	}
	if (r === false)
		return false;
	if (r === null) {
		this.alert('Could not load "'+fileUrl+'"');
		return null;
	}
	// wow, java worked!
	return r;
};

// the following load/save bindings will return:
// * null if they can't do it
// * false if there's an error
// * true if it saved OK
// * string with content if content was read successfully

// save through ActiveX
woas.ieSaveFile = function(filePath, save_mode, content) {
	var s_mode;
	switch (save_mode) {
		default:
/*		case this.file_mode.BINARY:
		case this.file_mode.DATA_URI:
		case this.file_mode.BASE64: */
			this.crash(this.i18n.MODE_NOT_AVAIL.sprintf(save_mode.toString(16)));
			return null;
		break;
		case this.file_mode.ASCII_TEXT:
			s_mode = 0; // ASCII
		break;
		case this.file_mode.UTF8_TEXT:
		case this.file_mode.UTF16_TEXT:
			s_mode = -1; // Unicode mode 
		break;
	}
	// first let's see if we can do ActiveX
	var fso;
	try	{
		fso = new ActiveXObject("Scripting.FileSystemObject");
	}
	catch (e) {
		return null;
	}
	try {
		var file = fso.OpenTextFile(filePath, 2, true, s_mode);
		file.Write(content);
		file.Close();
	}
	catch(e) {
		woas.log("ERROR: exception while attempting to save: " + e.toString());	// log:1
		return false;
	}
	return true;
};

// load through ActiveX
woas.ieLoadFile = function(filePath, load_mode, suggested_mime) {
	var o_mode;
	switch (load_mode) {
		//TODO: use Java applet for these modes
		//TODO: allow these modes when file is supposedly ASCII
/*		case this.file_mode.DATA_URI:
		case this.file_mode.BINARY:
		case this.file_mode.BASE64: */
		default:
			// not available
			this.crash(this.i18n.MODE_NOT_AVAIL.sprintf(load_mode.toString(16)));
			return null;
		case this.file_mode.ASCII_TEXT:
			o_mode = 0; // ASCII
		break;
		case this.file_mode.UTF16_TEXT:
		case this.file_mode.UTF8_TEXT:
			o_mode = -1; // Unicode
		break;
	}
	var fso, content = null;
	try	{
		fso = new ActiveXObject("Scripting.FileSystemObject");
	}
	catch (e) {
		woas.log(e);
		return null;
	}
	try {
		// attempt to open as unicode
		var file = fso.OpenTextFile(filePath,1,false,o_mode);
		content = file.ReadAll();
		file.Close();
	}
	catch(e) {
		woas.log("ERROR: exception while attempting to load\n\n" + e.toString());	// log:1
		return false;
	}
	// return a valid DATA:URI
/*	if (load_mode == this.file_mode.DATA_URI)
		return this._data_uri_enc(filePath, content, suggested_mime);
	else if (load_mode == this.file_mode.BASE64)
		return this.base64.encode(content); */
	return content;
};

// save through UniversalXPConnect
woas.mozillaSaveFile = function(filePath, save_mode, content) {
	if (!window.Components)
		return null;
	//FIXME: save_mode is not considered here
	try	{
		netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
		var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
		file.initWithPath(filePath);
		if (!file.exists())
			file.create(0, 0664);
		else
			woas.log("NOTICE: file \""+filePath+"\" exists, overwriting");	// log:1
		var out = Components.classes["@mozilla.org/network/file-output-stream;1"].createInstance(Components.interfaces.nsIFileOutputStream);
		out.init(file, 0x08 | 0x20 | 0x02, 0700, 0);
		out.write(content, content.length);
		out.flush();
		out.close();
	}
	catch(e) {
		woas.log("NOTICE: exception while attempting to save:\n\n" + e);	// log:1
		return(false);
	}
	return(true);
};

// load through UniversalXPConnect
woas.mozillaLoadFile = function(filePath, load_mode, suggested_mime) {
	// this is available on Mozilla browsers only
	if (!window.Components)
		return null;
	try	{
		netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
		var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
		file.initWithPath(filePath);
		if (!file.exists()) {
			woas.log("NOTICE: unexisting file "+filePath);
			return false;
		}
		var inputStream = Components.classes["@mozilla.org/network/file-input-stream;1"].createInstance(Components.interfaces.nsIFileInputStream);
		inputStream.init(file, 0x01, 04, 0);
		var sInputStream = Components.classes["@mozilla.org/scriptableinputstream;1"].createInstance(Components.interfaces.nsIScriptableInputStream);
		sInputStream.init(inputStream);
		if ( (load_mode == this.file_mode.UTF8_TEXT) ||
			 (load_mode == this.file_mode.ASCII_TEXT))
			return sInputStream.read(sInputStream.available());
		// this byte-by-byte read allows retrieval of binary files
		var tot=sInputStream.available(), i=tot;
		var rd=[];
		while (i-->=0) {
			var c=sInputStream.read(1);
			rd.push(c.charCodeAt(0));
		}
		if (load_mode == this.file_mode.BINARY)
			return(this.merge_bytes(rd));
		else if (load_mode == this.file_mode.DATA_URI)
			return this._data_uri_enc_array(filePath, rd, suggested_mime);
		else if (load_mode == this.file_mode.BASE64)
			return this.base64.encode_array(rd);
	}
	catch(e) {
		woas.log("NOTICE: exception while attempting to load:\n\n" + e);	// log:1
	}
	return false;
};

//FIXME: save_mode is not enforced
woas.javaSaveFile = function(filePath,save_mode,content) {
	if ((save_mode != this.file_mode.ASCII_TEXT) && (save_mode != this.file_mode.UTF8_TEXT)) {
		woas.log("Only ASCII and UTF8 file modes are currently supported with Java/TiddlySaver");	//log:1
		return null;
	}
	var encoding = (save_mode === this.file_mode.ASCII_TEXT) ? "" : "UTF-8";
	try {
		if(document.applets.TiddlySaver) {
			var rv = document.applets.TiddlySaver.saveFile(filePath, encoding, content);
			if (typeof rv == "undefined") {
				woas.log("Save failure, this is usually a Java configuration issue");
				return null;
			} else {
				return rv ? true : false;
			}
		}
	} catch(ex) {
		// report but check next method
		woas.log("TiddlySaver applet not available"); //log:1
	}
	// check if no JRE is available
	if (typeof java == "undefined") {
		woas.log("No JRE detected"); //log:1
		return null;
	}
	// try reading the file with java.io
	try {
		var s = new java.io.PrintStream(new java.io.FileOutputStream(filePath));
		s.print(content);
		s.close();
	} catch(ex) {
		woas.log("Failed reading file directly with Java: "+ex.toString());
		return false;
	}
	return true;
};

//FIXME: UTF8_TEXT/BINARY is not enforced here
woas.javaLoadFile = function(filePath, load_mode, suggested_mime) {
	if ((load_mode != this.file_mode.ASCII_TEXT) && (load_mode != this.file_mode.UTF8_TEXT)) {
		woas.log("Only ASCII and UTF8 file modes are currently supported with Java/TiddlySaver");	//log:1
		return null;
	}
	var encoding = (load_mode === this.file_mode.ASCII_TEXT) ? "" : "UTF-8",
		content = null;
	try {
		if(document.applets.TiddlySaver) {
			content = document.applets.TiddlySaver.loadFile(filePath, encoding);
			if (content === null) {
				woas.log("Load failure, maybe file does not exist? "+filePath); //log:1
				return false;
			}
			// check that it is not an "undefined" string
			if (typeof content == "undefined") {
				woas.log("Load failure, this is usually a Java configuration issue"); //log:1
				return null;
			}
			// convert to string only after checking that it was successfully loaded
			content = String(content);
			if (load_mode == this.file_mode.DATA_URI)
				return this._data_uri_enc(filePath, content, suggested_mime);
			else if (load_mode == this.file_mode.BASE6)
				return this.base64.encode(content);
			return content;
		}
	} catch(ex) {
		// report but check next method
		woas.log("TiddlySaver applet not available"); //log:1
	}
	// check if no JRE is available
	if (typeof java == "undefined") {
		woas.log("No JRE detected"); //log:1
		return null;
	}
	content = "";
	var r, line;
	try {
		r = new java.io.BufferedReader(new java.io.FileReader(filePath));
		while((line = r.readLine()) !== null)
			content += new String(line) + "\n";
		r.close();
	} catch(ex) {
		woas.log("Exception in javaLoadFile(\""+filePath+"\"): "+ex);
		return false;
	}
	if (load_mode == this.file_mode.DATA_URI)
		return this._data_uri_enc(filePath, content, suggested_mime);
	else if (load_mode == this.file_mode.BASE64)
		return this.base64.encode(content);
	return content;
};

function printout_arr(arr, split_lines) {

	function elem_print(e) {
		return "'" + woas.js_encode(e, split_lines) + "'";
	}

	var s = "";
	for(var i=0;i < arr.length-1;i++) {
		s += elem_print(arr[i]) + ",\n";
	}
	if (arr.length>1)
		s += elem_print(arr[arr.length-1]) + "\n";
	return s;
}

function printout_mixed_arr(arr, split_lines, attrs) {

	function elem_print(e, attr) {
		if (attr & 2) {
			return "[" + printout_num_arr(e) + "]";
		}
		return "'" + woas.js_encode(e, split_lines) + "'";
	}

	var s = "";
	for(var i=0;i < arr.length-1;i++) {
		s += elem_print(arr[i], attrs[i]) + ",\n";
	}
	if (arr.length>1)
		s += elem_print(arr[arr.length-1], attrs[arr.length-1]) + "\n";
	return s;
}

// used to print out encrypted page bytes and attributes
function printout_num_arr(arr) {
	var s = "",it=arr.length-1;
	for(var i=0;i<it;i++) {
		if (arr[i]>=1000)
			s += "0x"+arr[i].toString(16) + ",";
		else
			s+=arr[i].toString() + ",";
	}
	// do not write comma on last element, workaround due to IE6 not recognizing it
	if (it>0) {
		if (arr[it]>=1000)
			s += "0x"+arr[it].toString(16);
		else
			s+=arr[it].toString();
	}

	return s;
}

function printout_fixed(elem, n) {
	var s = (elem+",").repeat(n-1);
	// do not write comma on last element, workaround due to IE6 not recognizing it
	if (n>1)
		s += elem;
	return s;
}

// save full WoaS to file
woas._save_to_file = function(full) {
	this.progress_init("Saving to file");

	// force full mode if WSIF datasource mode changed since last time loading/saving
	var ds_changed = (this.config.wsif_ds.length !== this._old_wsif_ds_len),
	// increase the marker only when performing full save
		new_marker = ((full | ds_changed) && !this.config.wsif_ds.length) ? this._inc_marker(__marker) : __marker,
		safe_current;

	// setup the page to be opened on next start
	if (this.config.nav_history) {
		if (!this.page_exists(current))
			safe_current = this.config.main_page;
		else safe_current = current;
	} else
		safe_current = this.config.main_page;
	
	// output the javascript header and configuration flags
	var computed_js = "\n/* <![CDATA[ */\n\n/* "+new_marker+"-START */\n\nvar woas = {\"version\": \""+this.version+
	"\"};\n\nvar __marker = \""+new_marker+"\";\n\nwoas[\"config\"] = {";
	for (var param in this.config) {
		computed_js += "\n\""+param+"\":";
		switch(typeof(this.config[param])) {
			case "boolean":
				computed_js += (this.config[param] ? "true" : "false")+",";
			break;
			case "string":
				computed_js += "'"+this.js_encode(this.config[param])+"',";
			break;
			default: // for numbers
				computed_js += this.config[param]+",";
			break;
		}
	}
	computed_js = computed_js.substr(0,computed_js.length-1);
	computed_js += "};\n";
	
	computed_js += "\nvar current = '" + this.js_encode(safe_current)+"';\n\n";
	
	computed_js += "var backstack = [\n" + printout_arr(this.config.nav_history ? backstack : [], false) + "];\n\n";
	
	// in WSIF datasource mode we will save empty arrays
	if (this.config.wsif_ds.length !== 0)
		computed_js += "var page_titles = [\n];\n\n";
	else
		computed_js += "var page_titles = [\n" + printout_arr(page_titles, false) + "];\n\n";
	
	computed_js += "/* " + new_marker + "-DATA */\n";

	if (full || ds_changed) {
		this._old_wsif_ds_len = this.config.wsif_ds.length;
		if (this.config.wsif_ds.length) {
			// everything empty when the javascript layer is not used
			computed_js += "var page_attrs = [];\n\n";
			computed_js += "var page_mts = [];\n\n";
			computed_js += "var pages = [\n];\n\n";
		} else {
			computed_js += "var page_attrs = [" + printout_num_arr(page_attrs) + "];\n\n";
			computed_js += "var page_mts = [" + (this.config.store_mts ? printout_num_arr(page_mts) : "0, ".repeat(page_mts.length-1)+"0") + "];\n\n";
			computed_js += "var pages = [\n" + printout_mixed_arr(pages, this.config.allow_diff, page_attrs) + "];\n\n";
		}
		computed_js += "/* " + new_marker + "-END */\n";
	}

	// cleanup the DOM before saving
	var bak_ed = d$("woas_editor").value,
		bak_tx = this.getHTMLDiv(d$("woas_wiki_area")),
		bak_mn = this.getHTMLDiv(d$("woas_menu_area")),
		bak_mts = this.getHTMLDiv(d$("woas_mts")),
		bak_mts_shown = d$.is_visible("woas_mts"),
		bak_wait_text = this.getHTML(d$("woas_wait_text")),
		bak_debug = d$("woas_debug_log").value,
	// clear titles and css as well as they will be set on load.
		bak_title = this.getHTMLDiv(d$("woas_title"));

	if (bak_mts_shown)
		d$.hide("woas_mts");
	d$("woas_editor").value = "";
	this.setHTMLDiv(d$("woas_wiki_area"), "");
	this.setHTMLDiv(d$("woas_menu_area"), "");
	this.setHTMLDiv(d$("woas_mts"), "");
	this.setHTMLDiv(d$("woas_title"), "");
	d$("woas_debug_log").value = "";

	// set the loading message
	this.setHTML(d$("woas_wait_text"), this.i18n.LOADING);
	// temporarily display such message
	d$.show("loading_overlay");
	var bak_cursor = document.body.style.cursor;
	document.body.style.cursor = "auto";

	var data = this._extract_src_data(__marker, document.documentElement.innerHTML, full | ds_changed, safe_current);
	
	// data is ready, now the actual save process begins
	var r=false;
	d$.hide("loading_overlay");
	this.setHTML(d$("woas_wait_text"), bak_wait_text);
	document.body.style.cursor = bak_cursor;

	//DEBUG check
	if (data.length === 0) {
		this.crash("Could not retrieve original DOM data!");
	} else {
	
//	if (!this.config.server_mode || (was_local && this.config.server_mode)) {
	if (!this._server_mode)
		r = this._save_this_file(computed_js, data);
//		was_local = false;
//	}

	// save was successful - trigger some events
	if (r) {
		this.after_config_saved();
		if (full)
			this.after_pages_saved();
	}
	} //DEBUG check

	d$("woas_editor").value = bak_ed;
	this.setHTMLDiv(d$("woas_wiki_area"), bak_tx);
	this.setHTMLDiv(d$("woas_menu_area"), bak_mn);
	this.setHTMLDiv(d$("woas_mts"), bak_mts);
	if (bak_mts_shown)
		d$.show("woas_mts");
	d$("woas_debug_log").value = bak_debug;
	this.setHTMLDiv(d$("woas_title"), bak_title);
	
	//TODO: re-run after parsing hooks
	// would fix issues with import page for example
	
	this.progress_finish();
	
	return r;
};

function reXHTMLFix_hook(str, tag) {
	var l=str.length;
	if (str.charAt(l-1)!=='/')
		str = str.substr(0, l-1)+" />";
	return str;
}
var reXHTMLFix = /<(img|hr|br|input|meta)[^>]*>/gi;

var reHeadTagEnd = new RegExp("<\\/"+"head[^>]*>", "ig");
	reHeadTagStart = new RegExp("<"+"head[^>]*>", "ig"),
	reTagStart = /<(\w+)([^>]*)>/g,
	reTagEnd = /<\/(\w+)[^>]*>/g,
	reEditorTA = new RegExp('<'+'textarea\\s+id="?woas_editor"?\\s*[^>]+>(.*?)<'+'/textarea>', 'gi'),
	reCKDiv = new RegExp('<'+'div\\s+id="?woas_custom_accesskeys"?\\s*>(.*?)<'+'/div>', 'gi');

woas._extract_src_data = function(marker, source, full, current_page, data_only) {
	var e_offset, s_offset;
	// find the start marker for safety checking
	s_offset = source.indexOf("/* "+marker+ "-START */");
	if (s_offset === -1) {
		this.alert(this.i18n.ERR_MARKER.sprintf("START"));
		return false;
	}			
	// find the end marker, necessary to make some DOM/XHTML fixes
	e_offset = source.indexOf("/* "+marker+ "-END */", s_offset);
	if (e_offset === -1) {
		this.alert(this.i18n.ERR_MARKER.sprintf("END"));
		return false;
	}
	// properly update offset (+2 is for newlines)
	e_offset += 3 + marker.length + 7 + 2;
	
	// used during import
	if (full && data_only) {
		return source.substring(s_offset, e_offset);
	}

	reHeadTagStart.lastIndex = 0;
	var head_start, head_end,
		m = reHeadTagStart.exec(source);
	if (m !== null)
		head_start = m.index+m[0].length;
	else {
		this.crash("Cannot find head start tag");
		return false;
	}
	// search for head end tag starting at data end offset
	reHeadTagEnd.lastIndex = e_offset;
	m = reHeadTagEnd.exec(source);
	if (m !== null)
		head_end = m.index + m[0].length;
	else {
		this.crash("Cannot find head end tag");
		return false;
	}
	
	// filter out the unimportant tags from head
	// build a list of replacements with offsets
	
	// first take away the head
	var needle, m2, l_attrs, the_head = source.substring(0, head_end),
		splicings = [], tag_end,
		rest_of_source = source.substring(head_end);
	// reset big string
	source = "";
	// skip non-head content
	reTagStart.lastIndex = head_start;
	
	m = reTagStart.exec(the_head);
	while (m !== null) {
		tag = m[1].toLowerCase();
		var broken=false;
		switch (tag) {
			case "script":
			case "style":
			case "title":
				reTagEnd.lastIndex = m.index + m[0].length;
				do {
					m2 = reTagEnd.exec(the_head);
					if (m2 === null) {
						woas.log("found "+m[1]+" without closing tag");
						// continue to check for permanent attribute
						broken = true;
						break;
					}
					close_tag = m2[1].toLowerCase();
				} while (close_tag !== tag);
				// fallback wanted to set tag end like if it was a single tag
				if (!broken) {
					tag_end = tag_end = m2.index+m2[0].length;
					break;
				}
			case "meta":
				tag_end = m.index+m[0].length;
				break;
			default:
//				woas.log("Unknown tag in head: "+tag);
				// continue to check for permanent attribute
				tag_end = m.index+m[0].length;
				broken = true;
			break;
		}
		
		l_attrs = m[2].toLowerCase();
		// this was marked as permanent tag
		var was_replaced = false;
		if (l_attrs.indexOf("woas_permanent=")!==-1) {
			if (!broken) {
			if (tag === "style") {
				was_replaced = true;
				if (l_attrs.indexOf("woas_core_style=")!==-1) {
					woas.log("Replacing CSS");
					needle = m[0]+woas.get_text("WoaS::CSS::Boot")+m2[0];
				} // else leave as-is
			} else if (tag === "title") {
				woas.log("Replacing title");
				needle = m[0]+woas.xhtml_encode(current_page)+m2[0];
				was_replaced = true;
			}
			}
			// will leave tag untouched
		} else {
			woas.log("Removing tag "+tag);
			needle = "";
			was_replaced = true;
		}
		if (was_replaced)
			// add this splicing
			splicings.push( { start: m.index, end: tag_end, needle: needle } );
		reTagStart.lastIndex = tag_end;
		m = reTagStart.exec(the_head);
	}

	// rebuild the source by using splicings
	if (splicings.length) {
		var prev_ofs = 0;
		for(var i=0;i < splicings.length;++i) {
			source += the_head.substring(prev_ofs, splicings[i].start) + splicings[i].needle;
			prev_ofs = splicings[i].end;
		} splicings = null;

		// XHTML hotfixes (FF doesn't either save correctly)
		source += the_head.substr(prev_ofs); the_head = null;
		
		// re-calculate offsets
		
		// find the start marker for safety checking
		s_offset = source.indexOf("/* "+marker+ "-START */");
		if (s_offset === -1) {
			this.alert(this.i18n.ERR_MARKER.sprintf("START"));
			return false;
		}			
		// find the end marker, necessary to make some DOM/XHTML fixes
		e_offset = source.indexOf("/* "+marker+ "-END */", s_offset);
		if (e_offset === -1) {
			this.alert(this.i18n.ERR_MARKER.sprintf("END"));
			return false;
		}
		// properly update offset (+2 is for newlines)
		e_offset += 3 + marker.length + 7 + 2;
	} else {
		source = the_head;
		the_head = null;
	}
	
	// remove the tail (if any)
	var tail_end_mark = "<"+"!-- "+marker+"-TAIL-END -"+"->",
		tail_st_mark = "<"+"!-- "+marker+"-TAIL-START --"+">",
		tail_start = rest_of_source.indexOf(tail_st_mark, e_offset);
	if (tail_start !== -1) {
		var tail_end = rest_of_source.indexOf(tail_end_mark, tail_start);
		if (tail_end === -1)
			woas.log("Cannot find tail end!"); //log:1
		else {
			// remove the tail content (but not the tail itself)
			rest_of_source =	rest_of_source.substring(0, tail_start + tail_st_mark.length)+
						rest_of_source.substring(tail_end+tail_end_mark.length);
		}
	}

	// replace textarea with a standard one
	source += rest_of_source.replace(reEditorTA, '<'+'textarea id="woas_editor" rows="0" cols="0">&'+'nbsp;<'+'/textarea>')
	// remove the custom keys defined
			.replace(reCKDiv, '<'+'div id="woas_custom_accesskeys">&'+'nbsp;<'+'/div>')
	// XHTML hotfixes (FF2/FF3 doesn't either save correctly)
			.replace(reXHTMLFix, reXHTMLFix_hook);
	rest_of_source = null;
	
	if (!full) {
		e_offset = source.indexOf("/* "+marker+ "-DATA */", s_offset);
		if (e_offset === -1) {
			this.alert(this.i18n.ERR_MARKER.sprintf("DATA"));
			return false;
		}
		e_offset += 3 + marker.length + 8 + 1 /* newlines */;
	}
	return source.substring(e_offset);
};

// increment the save-counter portion of the marker
var reMarker = /([^\-]*)\-(\d{7,7})$/;
woas._inc_marker = function(old_marker) {
	var m = old_marker.match(reMarker);
	if (m===null)
		return _random_string(10)+"-0000001";
	var n = new Number(m[2].replace(/^0+/, '')) + 1;
	n = n.toString();
	// marker part + 0-padded save count number
	return m[1]+"-"+String("0").repeat(7-n.length)+n;
};

// load URL via XHR
woas.remote_load = function(url) {
	var HttpReq = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP');
	HttpReq.open('GET', url, false);
	HttpReq.setRequestHeader('Content-Type', 'text/plain');
	HttpReq.send(null);
	return HttpReq.responseText;
};

/*** src/parser.js ***/
// module @parser
woas.parser = {
	render_title: null,			// title of page being rendered
	has_toc: null,
	toc: "",
	force_inline: false,		// used not to break layout when presenting search results
	inline_tags: 0,
	_parsing_menu: false,		// true when we are parsing the menu page
	// properties used by _transclude
	_snippets: null,
	_export_links: null,

	header_anchor: function(s) {
		// apply a hard normalization
		// WARNING: will not preserve header ids uniqueness
		return s.replace(this.reNormHeader, '_')
	},
	
	// @override to apply some further customization before parse output
	after_parse: function(P) {
	},
	
	// @override to parse further syntax before final replace
	extend_syntax: function(P) {
	},

	render_error: function(str, symbol) {
		//	if (typeof symbol == "undefined")
		//		symbol = "infin";
		symbol = "&"+symbol+";";
		return "<"+"span style=\"color:red;font-weight:bold;\">"+symbol+" "+str+" "+symbol+"<"+"/span>";
	},
	
	// a variety of regular expressions used by the parser
	reBoldSyntax: /([^\w\/\\])\*([^\*\n]+)\*/g,
	//DEPRECATED "!" syntax is supported but shall me removed soon
	reHeaders: /^([\!=]{1,6})\s*(.*)$/gm,
	reNormHeader: /[^a-zA-Z0-9]/g,
	sTOC: "[[Special::TOC]]",
	reHasDNL: new RegExp("^([ \\t]*\\n)"),
	_MAX_TRANSCLUSION_RECURSE: 256,

	marker: null,
	reBaseSnippet: null,
	_HR: "<"+"hr class=\"woas_ruler\" />",
	NL_MARKER: null,
	reNL_MARKER: null,
	_init: function() {
		this.marker = "#"+_random_string(8);
		this.reBaseSnippet = new RegExp("<\\!-- "+this.marker+"::(\\d+) -->", "g");
		this.NL_MARKER = "<!-- "+this.marker+"_NL -->";
		this.reNL_MARKER = new RegExp("<\\!-- "+this.marker+"_NL -->([ \\t]*\\n)?", "g");
	}
};

// initialize here because IE would fail otherwise
woas.parser._init();

woas.parser.header_replace = function(str, $1, $2) {
		var header = $2, len = $1.length,
		// remove the mirrored header syntax from right
			hpos = header.lastIndexOf($1),
			that = woas.parser;
		if ((hpos !== -1) && (hpos === header.length - len))
			header = header.substring(0, header.length - len);
		// automatically build the TOC if needed
		if (that.has_toc) {
			that.toc += String("#").repeat(len)+" <"+"a class=\"woas_link\" href=\"#" +
			that.header_anchor(header) + "\">" + header + "<\/a>\n";
		}
		return "<"+"h"+len+" class=\"woas_header\" id=\""+that.header_anchor(header)+"\">"+header+"<"+"/h"+len+">";
};

woas.parser.sublist = function (lst, ll, suoro, euoro) {   
	if (!lst.length)
		return '';
	
	if (lst[0][1].length > ll)
		return this.sublist(lst, ll+1, suoro, euoro);
	
	var item, subl, s = '';
	while (lst[0][1].length == ll ) {
                item = lst.shift();
                subl = this.sublist(lst, ll + 1, suoro, euoro);
                if (subl.length)
                    s += '<'+'li>' + item[2] + suoro + subl + euoro + '<'+'/li>' ;
                else
                    s += '<'+'li>' + item[2] + '<'+'/li>';
		if (!lst.length)
			break;
	}
	return s;  
};

// XHTML lists and tables parsing code by plumloco
// There is no limit to the level of nesting and produces valid XHTML markup
var reReapLists = /^([\*#@])[ \t].*(?:\n\1+[ \t].+)*/gm,
	reItems = /^([\*#@]+)[ \t]([^\n]+)/mg;
woas.parser.parse_lists = function(str, type, $2) {
	var uoro = (type!='*')?'ol':'ul',
		suoro = '<' + uoro + ((type=='@') ? " type=\"a\"":"")+'>',
		euoro = '</' + uoro + '>',
		// collect all items in a stack
		stk = [];

	str.replace( reItems, function(str, p1, p2) {
		stk.push([str, p1, p2]);
	} );

	return suoro + woas.parser.sublist(stk, 1, suoro, euoro) + euoro;
};

// new table parsing by FBNil
var reReapTablesNew = /^\{\|(.*)((?:\n\|.*)*)$/gm,
	reReapTablesNewSub1 = /\n\|([+ \t-\|])(.*)/g,
	reReapTablesNewSub2 = /(\|\|)(\s{0,1})(\s?)(?=\|\|)/g,
	reReapTablesNewSub3 = /(\|\|\s*)$/,
	reReapTablesNewSub4 = /\|\|\t/g,
	reReapTablesNewSub5 = /\t\|\|/g,
	reReapTablesHead = /^(\s*)(?:(=+)\s*)?(.*?)(\s*)$/;
woas.parser.parse_tables_new = function (str, prop, p1) {
    var caption = '',
		colgroup = '',
		stk = [],
		cols = [],
		CC = [];
	// variables used by replace callback
	var i, C, CL;
    p1.replace(reReapTablesNewSub1, function (str, pp1, pp2) {
		switch (pp1) {
			case '-': // comment
				return;
			case '+':
				return caption = caption || ('<'+'caption' + (stk.length > 0 ? ' style="caption-side:bottom">' : '>') + pp2 + '<'+'/caption>');
			case '*':
				return colgroup = pp2;
			case '$':
				return CC.push(pp2);
			case '|':
				pp2 = " |" + pp2;
			break;
			case ' ':
				if (pp2.match(/^\|/)) // fix empty first cell
					pp2 = "  " + pp2;
			break;
			default:
				// do not eat the first character if it's not a control character
				pp2 = pp1+pp2;
		}

        var cells = pp2.replace(reReapTablesNewSub2, "$1$2$3  ").
				replace(reReapTablesNewSub3, "$1 ").
				replace(reReapTablesNewSub4, "|| ").
				replace(reReapTablesNewSub5, " ||").split(" || "),
			row = [],
			stag = "",
			cs = 0;
        for (i = cells.length - 1; i >= 0; --i) {
            C = cells[i].match(reReapTablesHead);
            if (i && !C[3] && !C[1] && !C[2]) {
                ++cs;
                continue;
            } else if (i == 0 && !C[3]) C[3] = "&nbsp;";
            CL = C[2] ? C[2].length : 0;
            stag = '<' + (CL == 1 ? 'th' : 'td') + (CL > 1 ? ' ' + CC[CL - 2] || '' : '') + (cs ? ' colspan=' + (++cs) : '') + (C[1] ? ' align=' + (C[4] ? 'center' : 'right') : '') + '>';
            cs = 0;
            row.unshift(stag + C[3] + (CL == 1 ? '<'+'/th>' : '<'+'/td>'));
        }
        stk.push(row.join(""));
    });
    return '<'+'table ' + ((prop.indexOf("class=")!==-1) ? '' : 'class="woas_text_area" ') + prop + '>' + caption + colgroup + '<'+'tr>' + stk.join('<'+'/tr><'+'tr>') + '<'+'/tr>' + '<'+'/table>'
};

// extract the wiki tags from a wiki URL
woas._get_tags = function(text, last_tag) {
	var tags = [];
	// remove the starting part
	if (text.substr(0, 5) === "Tag::")
		text = this.trim(text.substring(5));
	else if (text.substr(0,6)==="Tags::") {
		woas.log("Using deprecated 'Tags' namespace");
		text = this.trim(text.substring(6));
	} else // not a valid tagging
		return tags;
	// check length only after having removed the part we don't need
	if (!text.length)
		return tags;
	var alltags = this.split_tags(text), tag;
	if (last_tag !== null) alltags = alltags.concat(this.split_tags(last_tag));
	for(var i=0;i < alltags.length;i++) {
		tag = this.trim(alltags[i]);
		if (tags.indexOf(tag)===-1)
			tags.push(tag);
	}
	return tags;
};

// split one or more tags
//NOTE: no trim applied
woas.split_tags = function(tlist) {
	if (tlist.indexOf(",")!==-1)
		return tlist.split(",");
	//DEPRECATED but still supported
	return tlist.split("|");
};

// elements which can have one dynamic newline
var reScripts = new RegExp("<"+"script([^>]*)>([\\s\\S]*?)<"+"\\/script>([ \t]*\n)?", "gi"),
	reStyles = new RegExp("<"+"style([^>]*)>[\\s\\S]*?<"+"\\/style>([ \t]*\n)?", "gi"),
	//DEPRECATED rulers with 3 hyphens (shall be 4)
	reRuler = /(\n\s*\-{3,}[ \t]*){1,}(\n|$)/g,
	reNowikiSL = /\n\{\{\{([\s\S]*?)\}\}\}[ \t]*\n/g,
	reNowiki = /\{\{\{([\s\S]*?)\}\}\}([ \t]*\n)?/g,
	reTransclusion = /\[\[Include::([\s\S]+?)\]\]([ \t]*\n)?/g,
	reMacros = /<<<([\s\S]*?)>>>([ \t]*\n)?/g,
	reComments = /<\!--([\s\S]*?)-->([ \t]*\n)?/g,
// the others have not
	reWikiLink = /\[\[([^\]\]]*?)\|(.*?)\]\]/g,
	reWikiLinkSimple = /\[\[([^\]]*?)\]\]/g,
	reMailto = /^mailto:\/\//,
	reCleanupNewlines = new RegExp('((<\\/h[1-6]>)|(<\\/[uo]l>))(\n+)', 'g');

woas.parser.place_holder = function (i, separator, dynamic_nl) {
	if (typeof separator == "undefined")
		separator = "";
	if (typeof dynamic_nl == "undefined")
		dynamic_nl = "";
	else if (dynamic_nl !== "") // put newlines after blocks which have an ending newline
		dynamic_nl = this.NL_MARKER+dynamic_nl;
	separator = ":"+separator+":";
	return "<!-- "+woas.parser.marker+separator+i+" -->"+dynamic_nl;
};

// create a preformatted block ready to be displayed
woas._make_preformatted = function(text, add_style) {
	var cls = "woas_nowiki", tag, p = text.indexOf("\n");
	if (p === -1) {
		tag = "tt";
	} else {
		// remove the first newline to be compliant with old parsing
		if (p===0)
			text = text.substr(1);
		cls += " woas_nowiki_multiline";
		tag = "pre";
	}
	return this._raw_preformatted(tag, text, cls, add_style);
};

woas._raw_preformatted = function(tag, text, cls, add_style) {
	var xhtml = this.xhtml_encode(text);
	// convert the newlines - not necessary with pre tags
//	if (this.browser.ie)		xhtml = xhtml.replace(/\n/g, "\r\n");
	// to ease copy/pasting
//	xhtml = xhtml.replace(/\n/g, "<"+"br />");
	if (typeof add_style != "undefined")
		add_style = " style=\""+add_style+"\"";
	else add_style = "";
	return "<"+tag+" class=\""+cls+"\""+add_style+">"+xhtml+"</"+tag+">";
};

// This method can be overriden to customize parsing
// Other useful callbacks to control flow of page display: woas.after_parse and woas.pager.browse

// 'text' is the raw wiki source
// 'export_links' is set to true when exporting wiki pages and is used to generate proper href for hyperlinks
// 'js_mode' controls javascript behavior. Allowed values are:
// 0 = leave script tags as they are (used for exporting)
// 1 - place script tags in head (dynamic),
// 2 - re-add script tags after parsing
// 3 - convert script tags to nowiki blocks
woas.parser.parse = function(text, export_links, js_mode) {
	if (woas.config.debug_mode) {
		if ((typeof text).toLowerCase() != "string") {
			woas.crash("Called parse() with invalid argument: "+(typeof text));	// log:1
			return null;
		}
	}
	// default fallback
	if (typeof export_links == "undefined")
		export_links = false;
	if (typeof js_mode == "undefined")
		js_mode = 1;
	
	// put text in an object
	var P = { body: text }; text = null;
	// snippets array will contain all the HTML snippets that will not be parsed by the wiki engine
	var snippets = [], r, backup_hook = this.after_parse;
	
	// comments and nowiki blocks
	this.pre_parse(P, snippets);

	// macros
	this.parse_macros(P, snippets);
	
	// transclude pages (templates)
	if (!this.force_inline) {
		// reset all groups
		this._ns_groups = { };
		// apply transclusion syntax
		this.transclude_syntax(P, snippets, export_links);
	}
	
	if (js_mode) {
		// reset the array of custom scripts for the correct target
		var script_target = this._parsing_menu ? "menu" : "page";
		woas.scripting.clear(script_target);
		// gather all script tags
		P.body = P.body.replace(reScripts, function (str, $1, $2, dynamic_nl) {
			if (js_mode==2) {
				r = woas.parser.place_holder(snippets.length, "", dynamic_nl);
				snippets.push(str);
				return r;
			} else if (js_mode==3) {
				// during safe mode do not activate scripts, transform them to nowiki blocks
				r = woas.parser.place_holder(snippets.length, "", dynamic_nl);
				snippets.push( woas._make_preformatted(str) );
				return r;
			} // else
			var m=$1.match(/src=(?:"|')([^\s'">]+)/),
				external = (m!==null);
			woas.scripting.add(script_target, external ? m[1] : $2, external);
			return "";
		});
	}
	
	// take away style blocks
	P.body = P.body.replace(reStyles, function(str, $1, dynamic_nl) {
		r = woas.parser.place_holder(snippets.length, "", dynamic_nl);
		snippets.push(str);
		return r;
	});
	
	// put a placeholder for the TOC
	var p = P.body.indexOf(this.sTOC);
	if (p !== -1) {
		this.has_toc = true;
		P.body = P.body.substring(0, p) + "<!-- "+woas.parser.marker+":TOC -->" +
				// dynamic newlines also after TOC
				P.body.substring(p+this.sTOC.length).replace(this.reHasDNL, this.NL_MARKER+"$1");
	} else this.has_toc = false;

	// wiki tags
	var tags = [];
	this.inline_tags = 0;
	
	this.syntax_parse(P, snippets, tags, export_links, this.has_toc);

	// sort tags at bottom of page, also when showing namespaces
	tags = tags.toUnique().sort();
	if (tags.length && !export_links) {
		var s;
		if (this.force_inline)
			s = "";
		else
			s = "<"+"div class=\"woas_taglinks\">";
		s += "Tags: ";
		for(var i=0;i < tags.length;++i) {
			s += "<"+"a class=\"woas_link\" onclick=\"woas.go_to('Tagged::"+woas.js_encode(tags[i])+"')\">"+
				woas.xhtml_encode(tags[i])+"<"+"/a>&nbsp;&nbsp;";
		}
		if (!this.force_inline) {
			s+="<"+"/div>";
			P.body += s;
		} else { // re-print the inline tags (works only on last tag definition?)
			P.body = P.body.replace(new RegExp("<\\!-- "+woas.parser.marker+":(\\d+) -->", "g"), function (str, $1) {
				// loose comparison is OK here
				if ($1 == woas.parser.inline_tags)
					return s;
				return "";
			});
		}
	}
	// reset the flaggers
	if (this.force_inline)
		this.force_inline = false;
	if (woas.macro.has_backup())
		// restore macros array
		woas.macro.pop_backup();

	// syntax parsing has finished, do you want to apply some final cosmethic?
	if (this.after_parse === backup_hook)
		this.after_parse(P);
	
	// finished
	return P.body;
};

woas.parser.parse_macros = function(P, snippets) {
	// put away stuff contained in user-defined macro multi-line blocks
	P.body = P.body.replace(reMacros, function (str, $1, dynamic_nl) {
		if (!woas.macro.has_backup())
			// take a backup copy of the macros, so that no new macros are defined after page processing
			woas.macro.push_backup();

		// ask macro_parser to prepare this block
		var macro = woas.macro.parser($1);
		// allow further parser processing
		if (macro.reprocess) {
			if (typeof dynamic_nl != "undefined" && dynamic_nl!=="")
				macro.text += woas.parser.NL_MARKER+dynamic_nl;
			return macro.text;
		}
		r = woas.parser.place_holder(snippets.length, "", dynamic_nl);
		// otherwise store it for later
		snippets.push(macro.text);
		return r;
	});
};

//NOTE: XHTML comments cannot be contained in nowiki or macro blocks
woas.parser.pre_parse = function(P, snippets) {
	// put away XHTML-style comments
	P.body = P.body.replace(reComments, function (str, comment, dynamic_nl) {
		// skip whitespace comments
		if (comment.match(/^\s+$/))
			return str;
		r = woas.parser.place_holder(snippets.length, "", dynamic_nl);
		snippets.push(str);
		return r;
	})
	// put away stuff contained in inline nowiki blocks {{{ }}}
	.replace(reNowikiSL, function (str, $1) {
		r = woas.parser.place_holder(snippets.length, "", "\n");
		snippets.push("\n"+woas._raw_preformatted("pre", $1, "woas_nowiki woas_nowikimultiline"));
		return r;
	})
	.replace(reNowiki, function (str, $1, dynamic_nl) {
		r = woas.parser.place_holder(snippets.length, "", dynamic_nl);
		snippets.push(woas._make_preformatted($1));
		return r;
	});

};

//API1.0
//TODO: offer transclusion parameters argument
woas.parser.transclude = function(title, snippets, export_links) {
	this._snippets = snippets;
	this._export_links = export_links ? true : false;
	var rv = this._transclude("[[Include::"+title+"]]", title);
	this._export_links = this._snippets = null;
	return rv;
};

woas.parser._snippets = null;
woas.parser._transcluding = 0;
woas.parser._transclude = function (str, $1, dynamic_nl) {
	var that = woas.parser,
		parts = $1.split("|"),
		templname = parts[0],
		is_emb = false, ns=woas.get_namespace(templname, true),
		// temporary page object
		P = { body: null };
	// increase transclusion depth
	++that._transcluding;
//	woas.log("Transcluding "+templname+"("+parts.slice(0).toString()+")");	// log:0
	// in case of embedded file, add the inline file or add the image
	if (woas.is_reserved(templname) || (templname.substring(templname.length-2)=="::"))
		P.body = woas.get_text_special(templname);
	else {
		var epi = woas.page_index(templname);
		// offer a link for uploading, to implement feature as before 0.11.0
		if (epi == -1)
			P.body = "[<!-- -->[Include::[["+templname+"]]]]";
		else {
			P.body = woas.get__text(epi);
			is_emb = woas.is_embedded(templname);
		}
	}
	// template retrieval error
	if (P.body === null) {
		var templs="[["+templname+"]]";
		if (parts.length>1)
			templs += "|"+parts.slice(1).join("|");
		r = woas.parser.place_holder(that._snippets.length, "", dynamic_nl);
		// show an error with empty set symbol
		that._snippets.push(woas.parser.render_error(str, "#8709"));
		--that._transcluding;
		return r;
	}
	if (is_emb) {
		r = woas.parser.place_holder(that._snippets.length, "", dynamic_nl);
//		woas.log("Embedded file transclusion: "+templname);	// log:0
		if (woas.is_image(templname)) {
			var img, img_name = woas.xhtml_encode(templname.substr(templname.indexOf("::")+2));
			if (woas.parser._export_links) {
				// check that the URI is valid
				var uri=woas.exporter._get_fname(templname);
				if (uri == '#')
					img = woas.parser.render_error(templname, "#8709");
				else
					img = "<"+"img class=\"woas_embedded\" src=\""+uri+"\" alt=\""+img_name+"\" ";
			} else
				img = "<"+"img class=\"woas_embedded\" src=\""+P.body+"\" ";
			if (parts.length>1) {
				img += parts[1];
				// always add the alt attribute to images
				if (!woas.parser._export_links && !parts[1].match(/alt=('|").*?\1/))
					img += " alt=\""+img_name+"\"";
			}
			that._snippets.push(img+" />");
		} else { // embedded file but not image
			if ((parts.length>1) && (parts[1]=="raw"))
				that._snippets.push(woas.base64.decode(P.body));
			else
				that._snippets.push("<"+"pre class=\"woas_embedded\">"+
						woas.xhtml_encode(woas.base64.decode(P.body))+"<"+"/pre>");
		}
		P.body = r;
	}
	
	if (!is_emb) {
		// take away XHTML comments and nowiki blocks
		that.pre_parse(P, that._snippets);
		
		that.parse_macros(P, that._snippets);
		
		// finally replace transclusion parameters
		if (parts.length) {
			P.body = P.body.replace(/%\d+/g, function(str) {
				var paramno = parseInt(str.substr(1));
				if (paramno < parts.length)
					return parts[paramno];
				else
					return str;
			} );
		}
	} // not embedded
	--that._transcluding;
	//add the previous dynamic newline
	if (typeof dynamic_nl != "undefined" && dynamic_nl!=="")
		P.body += that.NL_MARKER+dynamic_nl;
	return P.body;
};

woas.parser.transclude_syntax = function(P, snippets, export_links) {
	var trans_level = 0;
	this._snippets = snippets;
	this._export_links = export_links ? true : false;
	do {
		P.body = P.body.replace(reTransclusion, this._transclude);
		// keep transcluding when a transclusion was made and when transcluding depth is not excessive
	} while (++trans_level < this._MAX_TRANSCLUSION_RECURSE);
	this._snippets = null;
	if (trans_level === this._MAX_TRANSCLUSION_RECURSE) { // parse remaining inclusions as normal text
		P.body = P.body.replace(reTransclusion, function (str, $1, dynamic_nl) {
			r = woas.parser.place_holder(snippets.length, "", dynamic_nl);
			snippets.push(woas.parser.render_error(str, "infin"));
			return r;
		});
	}
	this._snippets = this._export_links = null;
};

// parse passive syntax only
woas.parser.syntax_parse = function(P, snippets, tags, export_links, has_toc) {
	// put away big enough HTML tags sequences (with attributes)
	P.body = P.body.replace(/(<\/?\w+[^>]*>[ \t]*)+/g, function (tag) {
		r = woas.parser.place_holder(snippets.length);
		snippets.push(tag);
		return r;
	})
	// links with pipe e.g. [[Page|Title]]
	.replace(reWikiLink, function(str, $1, $2) {
		return woas.parser._render_wiki_link($1, $2, snippets, tags, export_links);
	})
	// links without pipe e.g. [[Page]]
	.replace(reWikiLinkSimple, function(str, $1) {
		return woas.parser._render_wiki_link($1, null, snippets, tags, export_links);
	})
	// allow non-wrapping newlines
	.replace(/\\\n/g, "")
	// underline
	.replace(/(^|[^\w])_([^_]+)_/g, "$1"+woas.parser.marker+"uS#$2"+woas.parser.marker+"uE#")
	// italics - need a space after ':'
	.replace(/(^|[^\w:])\/([^\n\/]+)\//g, function (str, $1, $2) {
		// hotfix for URLs
		if ($2.indexOf("//")!=-1)
			return str;
		return $1+"<"+"em>"+$2+"<"+"/em>";
	})
	// ordered/unordered lists parsing (code by plumloco)
	.replace(reReapLists, this.parse_lists)
	// headers - only h1~h6 are parsed
	.replace(this.reHeaders, this.header_replace);
	// other custom syntax should go into this callback
	this.extend_syntax(P);
	
	// replace [[Special::TOC]]
	if (has_toc) {
		// replace the TOC placeholder with the real TOC
		P.body = P.body.replace("<!-- "+woas.parser.marker+":TOC -->",
				"<"+"div class=\"woas_toc\"><"+"p class=\"woas_toc_title\">Table of Contents<"+"/p>" +
				this.toc.replace(reReapLists, this.parse_lists)
				/*.replace("\n<", "<") */
				+ "<"+"/div>" );
		this.toc = "";
	}

	// add a dynamic newline at start to have consistent newlines for the ruler and other syntax regex
	P.body = this.NL_MARKER + "\n" + P.body;

	// use 'strong' tag for bold text
	P.body = P.body.replace(this.reBoldSyntax, "$1"+woas.parser.marker+"bS#$2"+woas.parser.marker+"bE#")

	.replace(new RegExp(woas.parser.marker+"([ub])([SE])#", "g"), function (str, $1, $2) {
		if ($2=='E') {
			if ($1=='u')
				return "<"+"/span>";
			return "<"+"/strong>";
		}
		if ($1=='u')
			tag = "<"+"span style=\"text-decoration:underline;\">";
		else
			tag = "<"+"strong>";
		return tag;
	})

	// 'hr' horizontal rulers made with 3 hyphens, 4 suggested
	// only white spaces are allowed before/after the hyphens
	.replace(reRuler, function () {
		var n = woas.trim(arguments[0].replace(/\s+/g, " ")).split(/\ /g).length,
			last_nl;
		if (arguments[arguments.length-3].length)
			last_nl = woas.parser.NL_MARKER+arguments[arguments.length-3];
		else last_nl = "";
		return woas.parser._HR.repeat(n)+last_nl;
	})
	// tables-parsing pass
	.replace(woas.config.new_tables_syntax ? reReapTablesNew : reReapTables,
				woas.config.new_tables_syntax ? this.parse_tables_new : this.parse_tables)
	
	// cleanup \n after headers and lists
	.replace(reCleanupNewlines, function (str, $1, $2, $3, trailing_nl) {
		if (trailing_nl.length>2)
			return $1+trailing_nl.substr(2);
		return $1;
	})
	
	// remove \n before list start tags
	.replace(/\n(<[uo]l>)/g, "$1")
	
	// clear dynamic newlines
	.replace(this.reNL_MARKER, "")

	// convert newlines to br tags
	.replace(/\n/g, "<"+"br />");
	
	// put back in place all snippets
	this.undry(P, snippets);	
	snippets = null;
};

// render a single wiki link
woas.parser._render_wiki_link = function(arg1, label, snippets, tags, export_links) {
	// apply aliases to page title
	var page = woas.title_unalias(arg1),
		hashloc = page.indexOf("#"),
		gotohash = "", r,
		r_label = (label === null) ? page : label;

	// check for protocol links
	if (page.match(/^\w+:\/\//)) {
		r = woas.parser.place_holder(snippets.length);
		// convert mailto:// to mailto:
		page = page.replace(reMailto, "mailto:");
		// always give title attribute
		snippets.push("<"+"a title=\""+woas.xhtml_encode(page)+"\" class=\"woas_world_link\" href=\"" + page +
			"\" target=\"_blank\">" + r_label + "<\/a>");
		return r;
	}
	
	// check for tags definitions
	if (typeof tags == "object") {
		var found_tags = woas._get_tags(page, label);
		if (found_tags.length > 0) {
			// do not use concat because 'tags' is passed byref
			for(var i=0,it=found_tags.length;i<it;++i) {
				tags.push(found_tags[i]);
			}
			if (!this.force_inline)
				return "";
			++this.inline_tags;
			return "<!-- "+woas.parser.marker+":"+inline_tags+" -->";
		}
	}

	if (hashloc > 0) {
		if (export_links)
			gotohash = page.substr(hashloc);
		else
			gotohash = "; window.location.hash= \"" + page.substr(hashloc) + "\"";
		page = page.substr(0, hashloc);
	}

	// get a snippet id which we will later fill
	r = woas.parser.place_holder(snippets.length);
	// create a title attribute only when page URI differs from page title
	var _c_title = (page !== label) ? ' title="'+woas.xhtml_encode(page)+'"' : '', wl;
	if (hashloc === 0) { // section reference URIs
		snippets.push("<"+"a"+_c_title+" class=\"woas_link\" href=\""+page+"\">" + r_label + "<\/a>");
	} else { // normal pages
		if (woas.page_exists(page)) {
			if (export_links) {
				wl = woas.exporter._get_fname(page);
				if (wl === '#') {
					snippets.push("<"+"span class=\"woas_broken_link\">" + r_label + "<\/span>");
					return r;
				}
				wl = " href=\""+wl+"\"";
			} else
				wl = " onclick=\"woas.go_to('" + woas.js_encode(page) +"')"+gotohash+"\"";
			snippets.push("<"+"a"+_c_title+" class=\"woas_link\""+wl+">" + r_label + "<\/a>");
		} else { // unexisting pages
			if (export_links) {
				snippets.push("<"+"span class=\"woas_broken_link\">" + r_label + "<\/span>");
			} else {
				wl = " onclick=\"woas.go_to('" +woas.js_encode(page)+"')\"";
				snippets.push("<"+"a"+_c_title+" class=\"woas_unlink\" "+wl+">" + r_label + "<\/a>");
			}
		}
	} // not # at start of page
	return r;
};

// take away macros, nowiki, comments blocks
woas.parser.dry = function(P, NP, snippets) {
	// put away text in XHTML comments and nowiki blocks
	NP.body = P.body.replace(reComments, function (str, $1, dynamic_nl) {
		var r = woas.parser.place_holder(snippets.length, "", dynamic_nl);
		snippets.push(str);
		return r;
	}).replace(reNowiki, function (str, $1, dynamic_nl) {
		var r = woas.parser.place_holder(snippets.length, "", dynamic_nl);
		snippets.push(str);
		return r;
	}).replace(reMacros, function (str, $1, dynamic_nl) {
		var r = woas.parser.place_holder(snippets.length, "", dynamic_nl);
		snippets.push(str);
		return r;
	});
};

woas.parser.undry = function(NP, snippets) {
	if (!snippets.length) return;
	NP.body = NP.body.replace(this.reBaseSnippet, function (str, $1) {
		return snippets[parseInt($1)];
	});
};

/*** src/special.js ***/
woas.special_encrypted_pages = function(locked) {
	var pg = [];
	for(var i=0,l=pages.length;i < l;i++) {
		if (this.is_reserved(page_titles[i]))
			continue;
		if (locked == this.is__encrypted(i))
			pg.push(page_titles[i]);
	}
	if (!pg.length)
		return "/No locked pages found/";
	else
		return this._join_list(pg); // TODO - Delete repeated data
};

woas.special_orphaned_pages = function() {
	var pg = [],
		found = false,
		i, j, l, tmp;
	for(j=0,l=page_titles.length; j < l; j++) {
		if (this.is_reserved(page_titles[j]))
			continue;
		if (this.is_menu(page_titles[j])) {	// check if the namespace has some pages
			var ns = this.get_namespace(page_titles[j]);
			if (ns === "") continue;
			for(i=0;i < page_titles.length;i++) {
				if (page_titles[i].indexOf(ns)===0) {
					found = true;
					break;
				}
			}
		} else {
		// search for pages that link to it
			var re = new RegExp("\\[\\[" + RegExp.escape(page_titles[j]) + "(\\]\\]|\\|)", "i");
			for(i=0,l=page_titles.length; i < l; i++) {
				if ((i==j) || this.is_reserved(page_titles[i]))
					continue;
				tmp = this.get_src_page(i);
				if (tmp===null)
					continue;
				if (tmp.search(re)!=-1) {
					found = true;
					break;
				}
			}
		}
		if(found === false) {
			pg.push( page_titles[j] );
		} else found = false;
	}
	if (!pg.length)
		return "/No orphaned pages found/";
	else
		return this._join_list(pg); // TODO - Delete repeated data
};

woas.special_backlinks = function() {
	var pg = [], pg_title, tmp;
	if (this.parser.render_title === null)
		pg_title = current;
	else {
		pg_title = this.parser.render_title;
		this.parser.render_title = null;
	}
	var reg = new RegExp("\\[\\["+RegExp.escape(pg_title)+"(\\||\\]\\])", "gi");
	for(var j=0,l=pages.length; j < l; j++) {
		if (this.is_reserved(page_titles[j]))
			continue;
		// search for pages that link to it
		tmp = this.get_src_page(j);
		// encrypted w/o key
		if (tmp===null)
			continue;
		if (tmp.match(reg))
			pg.push( page_titles[j] );
	}
	if(pg.length === 0)
		return "/No page links to [["+pg_title+"]]/";
	else
		return "== Links to [["+pg_title+"]]\n"+this._join_list(pg);
};

woas._reLastSearch = null;	// search highlighting regex

woas._nearby_chars = 200;		// amount of nearby characters to display

// cached search results
woas._cached_title_search = [];
woas._cached_body_search = [];
// last string searched
woas._last_search = null;

// create an index of searched pages (by miz & legolas558)
woas._cache_search = function( str ) {
	var count = 0, tmp,
		// matches the search string and nearby text
		reg = new RegExp( ".{0,"+this._nearby_chars+"}" + RegExp.escape(this.trim(str)).
					replace(/\s+/g, "(.|\n)*?") + ".{0,"+this._nearby_chars+"}", "gi" );

	this._reLastSearch = new RegExp("("+RegExp.escape(str)+")", "gi");
	
	this._last_search = str;

	for(var i=0,l=pages.length; i < l; i++) {

		//TODO: implement searching in help pages

		if (this.is_reserved(page_titles[i]) && !this.tweak.edit_override)
			continue;

		// this could be modified for wiki searching issues
		tmp = this.get_src_page(i, true);
		if (tmp===null)
			continue;
	
		// look for string in title
		if(page_titles[i].match(reg)) {
			this._cached_title_search.push(page_titles[i]);
		}

		// look for string in body
		res_body = tmp.match(reg);
		if (res_body !== null)
			this._cached_body_search.push( { title: page_titles[i],	 matches: res_body} );
	}
};

var reFindTags = /\[\[Tags?::([^\]]+)\]\]/g;
woas.special_tagged = function(filter_string) {
	var	pg = [], folds = {"[pages]":[]}, tagns,
		src, i, l, j, jl, tmp, tag,	filtering,
		alltags;
	// filtering setup
	if (typeof filter_string != "undefined") {
		filtering = woas.tagging._prepare_filter(filter_string);
	} else filtering = false;
	// scan all pages
	for(i=0,l=pages.length;i < l;++i) {
		if (this.is_reserved(page_titles[i]))
			continue;
		src = this.get_src_page(i);
		// encrypted w/o key
		if (src === null)
			continue;
		src.replace(reFindTags, function (str, $1) {
				if (str.charAt(5) === 's')
					woas.log("Using deprecated 'Tags' namespace");
				// get the tags and index the page under each tag
				tmp=woas.split_tags($1);
				alltags = [];
				for(j=0,jl=tmp.length;j < jl; ++j) {
					tag=woas.trim(tmp[j]);
					// skip invalid tags
					if (!tag.length) {
						woas.log("WARNING: check your tag separators");
						continue;
					}
					// call the filtering callback, if necessary
					if (filtering) {
						// skip this page from listing in case of negative filter result
						if (!woas.tagging._filter_not_cb(tag))
							break;
					}
					alltags.push(tag);
				} tmp = null;
				// check that page has at least one of the positive tags
				if (filtering && !woas.tagging._filter_ok_cb(alltags))
					return;
				// add page to proper leaves
				for(j=0,jl=alltags.length;j < jl; ++j) {
					tag = alltags[j];
					// we have a valid tag, check if it is already indexed
					tagns = "Tagged::"+tag;
					if (typeof folds[tagns] == "undefined") {
						folds[tagns] = {"[pages]":[page_titles[i]]};
					} else {
//						if (folds[tagns]["[pages]"].indexOf(page_titles[i])===-1)
							folds[tagns]["[pages]"].push(page_titles[i]);
					}
					// build also the flat list - without duplicates
					if (pg.indexOf(page_titles[i])===-1)
						pg.push(page_titles[i]);
				}
			});
	} // scan pages loop
	if (filtering)
		woas.tagging._finish();
	// parse tree with sorting
	return woas.ns_listing(folds, pg, false);
};

// @module 'tagging'
woas.tagging = {
	tags_ok: null,
	tags_not: null,
	
	_finish: function() {
		this.tags_ok = this.tags_not = null;
	},

	_prepare_filter: function(filter_string) {
		var i,l, pg = [];
		// allow tags filtering/searching
		var tags = woas.split_tags(filter_string);
		// reset filter
		this.tags_ok = [];
		this.tags_not = [];
		
		// parse filter
		for(i=0,l=tags.length;i < l;++i) {
			// skip empty tags
			var tag = woas.trim(tags[i]);
			if (!tags[i].length)
				continue;
			// add a negation tag
			if (tags[i].charAt(0) === '!')
				this.tags_not.push( tags[i].substr(1) );
			else // normal match tag
				this.tags_ok.push(tags[i]);
		} tags = null;
		
		return (this.tags_ok.length + this.tags_not.length > 0);
	},
	
	// return true if all OK tag are present
	_filter_ok_cb: function(tags) {
		var ok = 0;
		if (this.tags_ok.length) {
			for(var i=0,il=tags.length;i < il;++i) {
				if (this.tags_ok.indexOf(tags[i]) !== -1)
					++ok
			}
		}
		return (ok === this.tags_ok.length);
	},

	_filter_not_cb: function(tag) {
		// filter if "NOT" tag is present
		// we are applying this filter only to tagged pages
		// so a page without tags at all does not fit into this filtering
		if (this.tags_not.length) {
			if (this.tags_not.indexOf(tag) !== -1)
				return false;
		}
		// no failure, this page passes
		return true;
	}
		
};

var reHasTags = /\[\[Tags?::[^\]]+\]\]/;
woas.special_untagged = function() {
	var tmp;
	var pg = [];
	for(var i=0,l=pages.length; i < l; i++) {
		if (this.is_reserved(page_titles[i]))
			continue;
		tmp = this.get_src_page(i);
		if (tmp === null)
			continue;
		if (!tmp.match(reHasTags))
			pg.push(page_titles[i]);
	}
	if (!pg.length)
		return '/No untagged pages/';
	return this._join_list(pg, true);
};

// Returns a index of all pages
woas.special_all_pages = function() {
	var pg = [];
	for(var i=0, l=page_titles.length; i < l; i++) {
		if (!this.is_reserved(page_titles[i]))
			pg.push( page_titles[i] );
	}
	return this._join_list(pg);
};

// Returns a index of all dead pages
var reAllWikiLinks = /\[\[([^\]\]]*?)(\|([^\]\]]+))?\]\]/g;
woas.special_dead_pages = function() {
	var dead_pages = [];
	var from_pages = [];
	var tmp, page_done;
	for(var j=0,l=pages.length;j < l;j++) {
		if (this.is_reserved(page_titles[j]) && !this.tweak.edit_override)
			continue;
		tmp = this.get_src_page(j);
		if (tmp===null)
			continue;
		page_done = false;
		tmp.replace(reAllWikiLinks,
			function (str, $1, $2, $3) {
				if (page_done)
					return false;
				var p = woas.title_unalias($1),
					sectref = p.indexOf("#");
				if (sectref === 0)
					return;
				else if (sectref > 0)
					p = p.substr(0, sectref);
				if (p.search(/^\w+:\/\//)===0)
					return;
				if (p.match(/Tag(s|ged)?:/gi))
					return;
				// skip mailto URLs
				if (p.match(/^mailto:/gi))
					return;
				if (p === "Special::TOC")
					return;
				if ((p.substr(0, 9) === "Special::") &&
					(woas.shortcuts.indexOf(p.substr(9)) !== -1))
					return;
				if (!woas.page_exists(p) && (p!==page_titles[j])) {
					// true when page has been scanned for referrals
					page_done = false;
					// check that this not-existing page is already in the deads page list
					for(var i=0;i < dead_pages.length;i++) {
						// current page contains a link to an already indexed dead page,
						// save the reference
						if (dead_pages[i]===p) {
							// add only if not already there
							if (from_pages[i].indexOf(page_titles[j]) === -1)
								from_pages[i].push(page_titles[j]);
							page_done = true;
							break;
						}
					}
					// we have just found a dead page
					if (!page_done) {
						dead_pages.push(p);
						from_pages.push(new Array(page_titles[j]));
						page_done = true;
					}
				}
	        }
		);
	}

	// format the dead pages
	var pg = [], s;
	for(var i=0;i < dead_pages.length;i++) {
		s = "[["+dead_pages[i]+"]] from ";
		var from = from_pages[i];
		for(j=0;j < from.length-1;j++) {
			s+="[["+from[j]+"]], ";
		}
		if (from.length>0)
			s+="[["+from[from.length-1]+"]]";
		pg.push(s);
	}

	woas.pager.bucket.items = dead_pages.slice(0);
	if (!pg.length)
		return '/No dead pages/';
	return this._simple_join_list(pg, true);
};

// used in Special::Options
function bool2chk(b) {
	if (b) return "checked";
	return "";
}

// Used by Special::Options
function _set_layout(fixed) {
	d$("i_woas_menu_area").style.position = d$("woas_wiki_header").style.position = (fixed ? "fixed" : "absolute");
}

//Special::Recentchanges shows a sorted list of pages by modified timestamp
woas.special_recent_changes = function() {
	if (!this.config.store_mts) {
		return "/Last modified timestamp storage is disabled in [[Special::Options]]./";
	}
	// build an array of (key := page_index, val := last_modified_timestamp) couples
	var l=page_titles.length, hm = [], i;
	for(i=0;i < l;++i) {
		// skip pages with the 'magic' zero timestamp
		if (page_mts[i] === 0)
			continue;
		// skip reserved pages which are not editable
		if (!this.edit_allowed_reserved(page_titles[i]) && this.is_reserved(page_titles[i]))
			continue;
		hm.push([i,page_mts[i]]);
	}
	// sort the array
	hm.sort(function(a,b) { return (b[1]-a[1]); });
	// display results
	var pg=[];
	for(i=0,l=hm.length;i < l;++i) {
		pg.push("* [[" + page_titles[hm[i][0]] + "]] <"+"span style=\"font-size: smaller;\">"+this.last_modified(hm[i][1])+"<"+"/span>");
	}
	if (!pg.length)
		return "/No recently modified pages/";
	return this._simple_join_list(pg);
};

woas._simple_join_list = function(arr, sorted) {
	if (sorted)
		arr = arr.sort();
	// a newline is added here
	return arr.join("\n")+"\n";
};

// joins a list of pages - always sorted by default
woas._join_list = function(arr, sorted) {
	if (!arr.length)
		return "";
	if (typeof sorted == "undefined")
		sorted = true;
	// copy the array to currently selected pages
	woas.pager.bucket.items = arr.slice(0);
	// (1) create a recursable tree of namespaces
	var ns,folds={"[pages]":[]},i,ni,nt,key;
	for(i=0,it=arr.length;i < it;++i) {
		ns = arr[i].split("::");
		// remove first entry if empty
		if (ns.length>1) {
			// in case of special menu pages
			if (ns[0].length === 0) {
//				ns.shift();
//				ns[0] = "::"+ns[0];
				// this shan't be a namespace
			} else if (ns[ns.length-1].length === 0) {
				// namespace pages, do nothing and consider them as normal pages
			} else { // pages with some namespace, recurse their namespaces and finally the page
				this.ns_recurse(ns, folds, "");
				continue;
			}
		}
		folds["[pages]"].push(arr[i]);
	}
	// (2) output the tree
	return this.ns_listing(folds, arr, sorted);
};

woas.ns_recurse = function(ns_arr, folds, prev_ns) {
	var ns = prev_ns+ns_arr.shift()+"::", item, left=ns_arr.length;;
	if (typeof folds[ns] == "undefined") {
		// last item, build the array
		if (left == 1) {
			folds[ns] = {"[pages]": [ns+ns_arr[0]] };
			return;
		}
		// namespace, create object
		folds[ns] = {"[pages]":[]};
	} else { // object already exists, add only leaves
		if (left == 1) {
			folds[ns]["[pages]"].push(ns+ns_arr[0]);
			return;
		}
	}
	if (left > 1)
		this.ns_recurse(ns_arr, folds, ns);
};

// grab expansion setting from UI radio boxes
woas._ns_expanded = function(ns, items_count, id, list_id) {
	this._ns_groups[list_id].items.push(id);
	switch (this._ns_groups[list_id].option) {
		case 1:
			return false;
		case 2:
			return true;
	}
	// ?
	return false;
};

woas._visible_css = function(v){
	return v ? "visibility: visible; display: inline" : "visibility: hidden; display: none";
};

woas.ns_recurse_parse = function(folds, output, prev_ns, recursion, sorted) {
	var i,it=folds["[pages]"].length,fold_id;
	if (it !== 0) {
		// increase recursion depth
		++recursion;
		// disable folding for pages outside namespaces
		if (prev_ns.length) {
			// generate id for folding div
			fold_id = "woas_fold"+output.fold_no++;
			var vis_css = woas._visible_css(this._ns_expanded(prev_ns, it, fold_id, output.list_id));
			output.s += "<"+"h"+(recursion+1)+" id=\""+fold_id+"_head\"> [[Javascript::d$.toggle('"+fold_id+"')|"+prev_ns+"]]";
			output.s += " [["+prev_ns+"|"+String.fromCharCode(8594)+"]] ("+it+" pages)\n<"+"/h"+(recursion+1)+">";
			output.s += "<"+"div style=\""+vis_css+"\" id=\""+fold_id+"\">\n";
		}
		// apply sorting
		if (sorted)
			folds["[pages]"].sort();
		for(i=0;i < it;++i) {
			output.s += "* [["+folds["[pages]"][i]+"]]\n";
		}
		if (prev_ns.length)
			output.s += "<"+"/div>";
	}
	// sort the actual namespaces
	if (sorted) {
		var nslist=[];
		// get namespaces
		for(i in folds) {
			if (i !== "[pages]")
				nslist.push(i);
		}
		// sort alphabetically (case insensitive)
		nslist.sort(function(x,y){
		  var a = String(x).toUpperCase();
		  var b = String(y).toUpperCase();
		  if (a > b)
			 return 1;
		  if (a < b)
			 return -1;
		  return 0;
		});
		// parse second the sorted namespaces
		it=nslist.length;
		for(i=0;i < it;++i) {
			this.ns_recurse_parse(folds[nslist[i]], output, prev_ns+nslist[i], recursion, sorted);
		}
	} else { // directly parsed without any specific sorting
		for(i in folds) {
			if (i !== "[pages]") {
				this.ns_recurse_parse(folds[i], output, prev_ns+i, recursion, sorted);
			}
		}
	}
};

// cache of current namespace listings
woas._ns_groups = {};

function _WoaS_list_expand_change(list_id, v) {
	// store selected option both in global config variable and
	// in relative list option
	woas.config.folding_style = woas._ns_groups[list_id].option = parseInt(v);
	switch (woas._ns_groups[list_id].option) {
		case 1: // collapse all
			d$.show("WoaS_"+list_id+"_folds");
			d$.hide("WoaS_"+list_id+"_flat");

			for(var i=0,it=woas._ns_groups[list_id].items.length;i < it;++i) {
				d$.hide(woas._ns_groups[list_id].items[i]);
			}
		break;
		case 0: // flat list
			d$.hide("WoaS_"+list_id+"_folds");
			d$.show("WoaS_"+list_id+"_flat");
			break;
		case 2: // expand all
			d$.show("WoaS_"+list_id+"_folds");
			d$.hide("WoaS_"+list_id+"_flat");

			for(var i=0,it=woas._ns_groups[list_id].items.length;i < it;++i) {
				d$.show(woas._ns_groups[list_id].items[i]);
			}
		break;
	}
}

woas.ns_listing = function(folds, flat_arr, sorted) {
	if (flat_arr.length === 0)
		return "/No pages in this listing/";
	if (typeof sorted == "undefined")
		sorted = false;
	var i=0;
	// do not produce the header if it is transcluded
	if (woas.parser._transcluding)
		i = 1;
	else {
		// or if this has no subnamespaces
		for(var f in folds) {
			if (i++) break;
			if (f !== "[pages]") {
				i=2;
				break;
			}
		}
	}
	var output={"s": "","fold_no":0};
	if (i!==1) {
		// this is kept here for now until some more appropriate place is individuated
		output.list_id = _random_string(8);
		// setup the group object
		this._ns_groups[output.list_id] = { "items":[], "option": woas.config.folding_style};
		output.s = "<"+"span class=\"woas_listing_options\">List view:<"+"label for=\"WoaS_"+output.list_id+"_0\"><"+"input type=\"radio\" id=\"WoaS_"+output.list_id+"_0\" name=\"WoaS_"+output.list_id+"\" value=\"0\" "+(this._ns_groups[output.list_id].option === 0 ? " checked=\"checked\"" : "" )+"onclick=\"_WoaS_list_expand_change('"+output.list_id+"',0)\">Flat<"+"/label>&nbsp;|\
	<"+"label for=\"WoaS_"+output.list_id+"_1\"><"+"input type=\"radio\" id=\"WoaS_"+output.list_id+"_1\" name=\"WoaS_"+output.list_id+"\" value=\"1\" "+(this._ns_groups[output.list_id].option === 1 ? " checked=\"checked\"" : "" )+"onclick=\"_WoaS_list_expand_change('"+output.list_id+"',1)\" >By namespace, collapsed<"+"/label>&nbsp;|\
	<"+"label for=\"WoaS_"+output.list_id+"_2\"><"+"input type=\"radio\" id=\"WoaS_"+output.list_id+"_2\" name=\"WoaS_"+output.list_id+"\" value=\"2\" "+(this._ns_groups[output.list_id].option === 2 ? " checked=\"checked\"" : "" )+" onclick=\"_WoaS_list_expand_change('"+output.list_id+"',2)\">By namespace, expanded<"+"/label>\
	<"+"/span><"+"span style=\""+woas._visible_css(this._ns_groups[output.list_id].option !== 0)+"\" id=\"WoaS_"+output.list_id+"_folds\">\n";
		
		// first fill the span for foldings
		this.ns_recurse_parse(folds, output, "", 0, sorted);
		output.s += "<"+"/span>\n"+
					"<"+"span style=\""+woas._visible_css(this._ns_groups[output.list_id].option === 0)+"\" id=\"WoaS_"+output.list_id+"_flat\">\n";
	}
	// then generate the flat list
	if (sorted)
		flat_arr.sort();
	output.s += "* [["+flat_arr.join("]]\n* [[")+"]]\n";
	if (i !== 1)
		output.s += "<"+"/span>";
	return output.s;
};

woas._special_image_gallery = function(ns) {
	var iHTML = "", snippets, P = {body:null};
	for(var i=0, l=page_titles.length;i < l;++i) {
		if (page_titles[i].indexOf(ns)===0) {
		snippets = [];
		P.body = "* "+this.parser.transclude(page_titles[i], snippets)+" [["+page_titles[i]+"]]\n";
		this.parser.syntax_parse(P, snippets);
		iHTML += P.body;
		snippets = P.body = null;
		}
	}
	return "= Pages in "+ns+" namespace\n" + iHTML;
};

/*** src/core.js ***/
// core modules

// some tweak settings NOT to be touched - warranty void otherwise
woas.tweak = {
	// DANGER: might cause WoaS corruption!
	edit_override: false,
	// perform integrity test of browser features
	integrity_test: true
};

woas.cmd_duplicate_page = function() {
	var pname = this._new_page("Insert duplicate page title", true, current+" (duplicate)");
	if (pname === null)
		return;
	var pi = this.page_index(current);
	var dpi = this.page_index(pname);
	// duplicate the page
	if (this.is__encrypted(pi)) // encrypted pages are arrays
		pages[dpi] = pages[pi].slice(0);
	else // string copy is OK
		pages[dpi] = pages[pi]; // .slice ?
	page_attrs[dpi] = page_attrs[pi];	
	// go to new page
	go_to(pname);
	// commit changes
	this.commit([dpi]);
};

woas.cmd_new_page = function() {
	this._new_page(this.i18n.INSERT_NEW, false, '');
};

// used to create a new page in the wiki
woas._new_page = function(msg, fill_mode, def_title) {
	var title = this._prompt_title(msg, def_title);
	if (title === null)
		return null;
	return this._new_page_direct(title, fill_mode);
};

// will return a valid title for a next-to-be-created page
woas._prompt_title = function(msg, def_title) {
	// disallow editing when wiki is set to read-only
	if (!this.config.permit_edits) {
		this.alert(this.i18n.READ_ONLY);
		return null;
	}
	var title = def_title;
	do {
		title = prompt(msg, title);
		if (title === null)
			break;
		title = this.trim(title);
		if (this.valid_title(title))
			break;
	} while (1);
	if ((title!==null) && title.length) {
		if (this.page_index(title)!=-1)
			this.alert(this.i18n.PAGE_EXISTS.sprintf(title));
		else
			return title;
	}
	return null;
};

woas._new_page_direct = function(title, fill_mode) {
	// properly split page title in (namespace, title) -> (ns, cr)
	var ns = this.get_namespace(title, true), cr;
	if (ns.length) {
		cr = title.substr(ns.length);
		ns = ns.substr(0, ns.length-2);
	} else cr = title;

	// check if page deserves creation
	if ((ns==="File") || (ns==="Image")) {
		go_to(title);
		return title;
	}
	// create and edit the new page
	var ct;
	if (cr !== "Menu")
		ct = "= "+cr+"\n";
	else
		ct = "\n";
	this._create_page_direct(ns, cr, fill_mode, ct);

	var upd_menu = (cr==='Menu');
	if (!upd_menu && confirm(this.i18n.ASK_MENU_LINK)) {
		var menu = this.get_text("::Menu");
		// try to put the menu link in a good position
		p = menu.indexOf("\n\n");
		if (p === -1)
			menu += "\n[["+title+"]]";
		else
			menu = menu.substring(0,p)+"\n[["+title+"]]"+menu.substring(p)+"\n";
		this.set__text(this.page_index("::Menu"), menu);
		upd_menu = true;
	}
	if (upd_menu)
		this.refresh_menu_area();
	return title;
};

// used to eventually remove the new-to-be page when cancel is pressed
woas._ghost_page = false;

woas._create_page_direct = function(ns, cr, fill_mode, default_ct) {
	// actual page creation
	pages.push(default_ct);
	if (ns.length)
		cr = ns+"::"+cr;
	page_attrs.push(0);
	page_titles.push(cr);
	// set modified timestamp
	page_mts.push(this.config.store_mts ? Math.round(new Date().getTime()/1000) : 0);
//	this.log("Page "+cr+" added to internal array");	// log:1
	if (!fill_mode) {
		// DO NOT set 'current = cr' here!!!
		// enable ghost mode when creating a new-to-be page
		this._ghost_page = true;
		this.log("Ghost page enabled"); //log:1
		// proceed with a normal wiki source page
		this.edit_page(cr);
	}
};

woas.cmd_erase_wiki = function() {
	if (this.erase_wiki()) {
		if (!this.full_commit())
			this.alert(this.i18n.FAILED_ERASE);
		back_or(this.config.main_page);
	}
	return null;
};

// pages which shall never be modified
woas.static_pages = ["Special::About", "Special::Advanced", "Special::Options","Special::Import",
						"Special::Lock","Special::Search", "Special::Embed",
						"Special::Export", "Special::License", "Special::ExportWSIF",
						"Special::ImportWSIF"];

woas.static_pages2 = ["WoaS::Plugins", "WoaS::CSS::Core",
						"WoaS::Template::Button", "WoaS::Template::Info",
						"WoaS::Template::Search", "WoaS::CSS::Boot",
						"WoaS::ImportSettings", "WoaS::Template::Example::Transclusion"];

woas.static_pages = woas.static_pages.concat(woas.static_pages2);
						
woas.help_pages = null;
woas.default_pages = ["::Menu", "WoaS::Aliases", "WoaS::Hotkeys", "WoaS::CSS::Custom"];

woas.erase_wiki = function() {
	if (!this.config.permit_edits) {
		this.alert(this.i18n.READ_ONLY);
		return false;
	}
	if (!confirm(this.i18n.CONFIRM_DELETE_ALL1) ||
		!confirm(this.i18n.CONFIRM_DELETE_ALL2))
		return false;
	var i,l,l1,l2,pi,t;
	this.progress_init("Erasing...");
	var backup_pages = [];
	// attributes and last modified timestamps for default pages
	// first entry is for main page
	page_attrs = [0]; page_mts =   [0];
	// zero is the magic timestamp
	for (i=0;i < this.default_pages.length;++i) {
		page_attrs.push(0); page_mts.push(0);
	}
	// build the array of help pages only once
	var help_pfx = "WoaS::Help::";
	if (this.help_pages === null) {
		this.help_pages = [];
		for(i=0,l=page_titles.length;i < l;++i) {
			if (page_titles[i].substr(0, help_pfx.length) === help_pfx)
				this.help_pages.push(page_titles[i].substr(help_pfx.length));
		}
	}
	var copied_help_pages = [];
	// now pick the static pages
	for(i=0,l1=this.static_pages.length,l2=this.help_pages.length,l=l1+l2;i < l;++i) {
		if (i < l1)
			t = this.static_pages[i];
		else
			t = help_pfx+this.help_pages[i-l1];
		pi = this.page_index(t);
		if (pi==-1) {
			this.alert(this.i18n.STATIC_NOT_FOUND.sprintf(t));
			continue;
		} else if (i>=l1)
			copied_help_pages.push(t);
		backup_pages.push(pages[pi]);
		// reset attributes
		page_attrs.push(0);
		// reset timestamp
		page_mts.push(0);
		this.progress_status(i/l);
	}
	// build titles
	page_titles = [ this.config.main_page ];
	page_titles = page_titles.concat(this.default_pages);
	page_titles = page_titles.concat(this.static_pages);
	page_titles = page_titles.concat(copied_help_pages);
	// now build pages
	pages = ["A blank sheet is a catalyst for ideas", "[["+this.config.main_page+"]]\n\n[[Special::All Pages]]\n[[Special::New Page]]\n[[Special::Duplicate Page]]\n[[Special::Go to]]\n[[Special::Delete Page]]\n[[Special::Backlinks]]\n[[Special::Search]]",
			"", this.hotkey._cache_default(), "/* Your CSS customization goes here */"];
	pages = pages.concat(backup_pages); backup_pages = null;
	current = this.config.main_page;
	this.refresh_menu_area();
	this.history.clear();
	// reload all extensions
	this._load_aliases(this.get_text("WoaS::Aliases"));
	this.hotkey.load(this.get_text("WoaS::Hotkeys"));
	// remove all plugins
	this.plugins.clear();
	this.plugins.load();

	this.progress_finish();
	return true;
};

woas.cmd_main_page = function() {
	go_to(this.config.main_page);
	return null;
};

// used to edit many special pages
woas.cmd_edit_special = function(cr) {
	if (!this.config.permit_edits && !this.tweak.edit_override) {
		this.alert(this.i18n.READ_ONLY);
		return null;
	}
	_servm_alert();
	// get source text (ASCII/UTF-8)
	var tmp = this.get_text(cr);
	if (tmp === null)
		return null;
	// setup the wiki editor textbox
	this.current_editing(cr, this.config.permit_edits | this._server_mode);
	this.edit_ready(tmp);
	return null;
};

woas.cmd_go_to = function() {
	// don't go anywhere if editing
	if (this.ui.edit_mode)
		return;
	var pname;
	do {
		pname = prompt("Go to page:", current);
		if ((pname !== null) && pname.length)
			if (go_to(pname))
				return;
	} while (pname !== null);
};

woas.cmd_delete = function() {
	// disallow editing when wiki is set to read-only
	if (!this.config.permit_edits) {
		this.alert(this.i18n.READ_ONLY);
		return false;
	}
	var pname = prompt(this.i18n.DELETE_PAGE_PROMPT, current);
	if ((pname === null) || !pname.length)
		return false;
	var pi = this.page_index(pname);
	if (pi == -1) {
		this.alert(this.i18n.PAGE_NOT_EXISTS+pname);
		return false;
	}
	if (this.is_reserved(pname)) {
		this.alert(this.i18n.ERR_RESERVED_NS.sprintf(this.get_namespace(pname, true)));
		return false;
	}
	if (confirm(this.i18n.CONFIRM_DELETE.sprintf(pname))) {
		this.plugins.delete_check(pname);
		this.delete_page_i(pi);
		return true;
	}
	return false;
};

// javascript shortcuts for special pages
woas.shortcuts = ["New Page", "Duplicate Page", "All Pages", "Orphaned Pages", "Backlinks",
					"Dead Pages", "Erase Wiki", "Main Page", "Go to", "Delete Page", "Recentchanges"];
woas.shortcuts_js = ["cmd_new_page", "cmd_duplicate_page", "special_all_pages", "special_orphaned_pages", "special_backlinks",
					"special_dead_pages", "cmd_erase_wiki",	"cmd_main_page", "cmd_go_to", "cmd_delete",	"special_recent_changes"];
					
woas.unexportable_pages = ["New Page", "Duplicate Page", "Backlinks", "Erase Wiki", "Edit CSS",
								"Go to", "Delete Page", "Search"];

woas.unexportable_pages2 = ["WoaS::CSS::Custom", "WoaS::CSS::Core", "WoaS::Aliases", "WoaS::Hotkeys",
							"WoaS::Plugins"];

// return raw javascript tag to be included in XHTML page
woas.raw_js = function(code) {
	return "<"+"script type=\"text/javascript\">\n"+code+"\n<"+"/s"+"cript>";
};

//API1.0: delete a page given title (without aliases)
woas.delete_page = function(title) {
	var pi = page_titles.indexOf(title);
	//DEBUG line
	if (pi === -1) {
		this.crash("Requesting deletion of unexisting page!");
		return false;
	}
	return this.delete_page_i(pi);
};

//API1.0: delete a page given absolute page index
//API1.0: @protected
woas.delete_page_i = function(i) {
	var il, old_title = page_titles[i];
//	this.log("NOTICE: deleted page "+old_title);	// log:0
	// remove the elements
	page_titles.splice(i,1);
	pages.splice(i,1);
	page_attrs.splice(i,1);
	page_mts.splice(i,1);
	// be sure that this page is no more in history
	this.history.check_deleted(old_title);
	// if we were looking at the deleted page
	if (current === old_title)
		// go to an existing page
		this.set_current(this.history.previous(), true);
	// always refresh the menu because it could contain the deleted page link
	this.refresh_menu_area();
	//TODO: send proper save notification
	return this.commit_delete([i]);
};

woas.history = {
	backstack: [],
	forstack: [],			// forward history stack, discarded when saving
	
	check_deleted: function(old_title) {
		// remove the deleted page from history
		for(var i=0,it=this.backstack.length;i < it;++i) {
			// remove also duplicate sequences
			if (this.backstack[i] === old_title) {
				this.backstack.splice(i, 1);
				// fix the loop
				--it;
				// iterate again to remove duplicate sequences
				--i;
			}
		}
		// delete also from forstack
		for(var i=0,it=this.forstack.length;i < it;++i) {
			// remove also duplicate sequences
			if (this.forstack[i] === old_title) {
				this.forstack.splice(i,1);
				// fix the loop
				--it;
				// iterate again to remove duplicate sequences
				--i;
			}
		}
		//TODO: remove subsequent duplicates in final array
	},
	
	previous: function() {
		// go back or to main page, do not save history
		if (this.backstack.length > 0) {
			return this.backstack.pop();
		} else
			return woas.config.main_page;
	},
	
	back: function() {
		if(this.backstack.length > 0) {
			this.forstack.push(current);
			return this.backstack.pop()
		}
		woas.log("No back history");
		return null;
	},
	
	has_forstack: function() {
		return (this.forstack.length > 0);
	},
	has_backstack: function() {
		return (this.backstack.length > 0);
	},
	
	_forward_browse: false,	// used when browsing forward in the page queue
	
	MAX_BROWSE_HISTORY: 6,
	
	go: function(title) {
		if (!this._forward_browse) {
			this.store(title);
			// reset forward stack
			this.forstack = [];
		} else this._forward_browse = false;
	},
	
	// push a page into history
	store: function(page) {
		if (this.backstack.length > this.MAX_BROWSE_HISTORY)
			this.backstack = this.backstack.slice(1);
		this.backstack.push(page);
	},
	
	forward: function() {
		if(this.forstack.length > 0)
			return this.forstack.pop();
		woas.log("No forward history");
		return null;
	},
	
	clear: function() {
		this.forstack = [];
		this.backstack = [];
	}

};

woas.history.backstack = backstack;

// some general integrity tests - for debug purposes
woas.integrity_test = function() {
	woas.log("Starting integrity test"); //log:1
	// test integrity of data arrays
	var len = pages.length;
	if ((page_attrs.length != len) ||
			(page_titles.length != len) ||
			(page_mts.length != len)) {
			this.crash("FATAL: data arrays have mismatching length!\n"+
						"#pages = %d, #page_attrs = %d, #page_titles = %d, #page_mts = %d".
						sprintf(pages.length, page_attrs.length, page_titles.length,
						page_mts.length));
		return false;
	}
	// test integrity of ecma encoding for normal UTF-8
	var UTF8_TEST = "Di\u00e2critics are here: \u00e4 \u00e1y";
	if (this.ecma_decode(this.ecma_encode(UTF8_TEST)) !== UTF8_TEST) {
		this.crash("ECMA encoding not working:\n"+this.ecma_decode(this.ecma_encode(UTF8_TEST))+
		"\n"+UTF8_TEST);
		return false;
	}
	// test integrity of ecma encoding for 16bit UTF-8
	var UTF8_TEST2 = "\u30e9\u30c9\u30af\u30ea\u30d5";
	if (this.ecma_decode(this.ecma_encode(UTF8_TEST2)) !== UTF8_TEST2) {
		this.crash("ECMA encoding/decoding not working:\n"+this.ecma_decode(this.ecma_encode(UTF8_TEST2))+
		"\n"+UTF8_TEST2);
		return false;
	}
	// test integrity of load/save functions if not on remote server
	if (!this._server_mode) {
		if (!this.save_file(woas.ROOT_DIRECTORY+"itest.bin", this.file_mode.UTF8_TEXT,
				woas.utf8.encode(UTF8_TEST)
	//			UTF8_TEST
				)) {
			this.crash("Save failure during integrity test\n"+woas.ROOT_DIRECTORY);
			return false;
		}
		var ct = this.load_file(woas.ROOT_DIRECTORY+"itest.bin", this.file_mode.UTF8_TEXT);
		if ((ct === null)||(ct === false)) {
			if (ct === false)
				this.crash("Load failure during integrity test\n"+woas.ROOT_DIRECTORY);
			return false;
		}
		ct = woas.utf8.decode(ct);
		if (ct !== UTF8_TEST) {
			this.crash("UTF8 test failed.\nWritten:\n"+UTF8_TEST+"\nRead:\n"+ct);
			return false;
		}
	} else { // we are on a remote server
		woas.log("Skipping save integrity test because running from web server");	//log:1
		//TODO: remote load integrity test
	}
	// now test AES encryption
	woas.AES.setKey("WoaS");
	var testdata = "sample text here";
	var enc = woas.AES.encrypt(testdata);
	if (woas.AES.decrypt(enc) !== testdata) {
		this.crash("AES encryption is not working two-way!");
		woas.AES.clearKey();
		return false;
	}
	if (woas.AES.decrypt(woas.AES.encrypt(UTF8_TEST)) !== UTF8_TEST) {
		this.crash("AES encryption of UTF8 text is not working two-way!");
		woas.AES.clearKey();
		return false;
	}
	woas.AES.clearKey();
	woas.log("Integrity test successful"); //log:1
	return true;
};

// used in path normalization during export
woas.DIRECTORY_SEPARATOR = (navigator.appVersion.indexOf("Win")!=-1)?"\\":"/";

woas._dirname_regex = new RegExp("\\"+woas.DIRECTORY_SEPARATOR+"[^\\"+woas.DIRECTORY_SEPARATOR+"]*$");
woas._basename_regex = new RegExp("[\\\\/]([^\\\\/]+)$");		// "\\[\\\\/]([^\\\\/]+)$"

// hackish functions, might stay private for now
woas.dirname = function(fn) {
	return fn.replace(this._dirname_regex, woas.DIRECTORY_SEPARATOR);
};

woas.basename = function(fn) {
	var m = fn.match(this._basename_regex);
	if (m === null)
		return fn;
	return m[1];
};

// the export path used by export feature
woas.ROOT_DIRECTORY = woas.dirname(_get_this_filename());

//API1.0: get page attributes - can be overriden by plugins
//TODO: all code should use this function
woas.get_page_attrs = function(pi) {
	// no error check
	return page_attrs[pi];
};

//API1.0: set page attributes - can be overriden by plugins
//TODO: all code should use this function
woas.set_page_attrs = function(pi, attrs) {
	// no error check
	page_attrs[pi] = attrs;
	return true;
};

//API1.0: dynamically add a hotkey
// returns true if hotkey was added successfully or if already present
woas.add_hotkey = function(key, function_obj) {
	//FIXME: this needs to be finished!
};

woas.split_bytes = function(s) {
	var l=s.length;
	var arr=[];
	for(var i=0;i < l;i++)
		arr.push(s.charCodeAt(i));
	return arr;
};
	
woas.merge_bytes = function(byte_arr) {
	var l=byte_arr.length, s="";
	for(var i=0;i < l;i++) {
		s+=String.fromCharCode(byte_arr[i]);
	}
	return s;
};

var reReplaceBr = new RegExp("<"+"br\\s?\\/?>", "gi");
woas.xhtml_to_text = function(s) {
	return s.replace(reReplaceBr, "\n").replace(/<\/?\w+[^>]*>/g, ' ').
					replace(/&#?([^;]+);/g, function(str, $1) { if (!isNaN($1)) return String.fromCharCode($1); else return ""; });
};

// WoaS DOM manager
// all DOM modifications shall be indexed by this module
woas.dom = {
	// hashmap used to quickly reference some important DOM objects
	_cache: {},
	// DOM management area
	_objects: [],
	
	init: function() {
		this._cache.head = document.getElementsByTagName("head")[0];
		this._cache.body = document.getElementsByTagName("body")[0];
		if (woas.browser.ie)
			this._cache.stylesheet = document.styleSheets[0];
		else
			this._cache.stylesheet = document.getElementsByTagName("style")[0];
	},
	
	add_css: function(css_id, css_src, external, after_load) {
/*		if (document.createStyleSheet) {// check for MSIE
			this._cache.head.insertAdjacentHTML('beforeEnd',
				'<'+'span id="'+'" style="display:none">x<'+'/span>'  // MSIE needs this for some reason
				+ '<+'style id="'+'" type="text/css">'+css_text+'<'+'/style>');
			//TODO: check that style can then be properly removed
		  } else { */
		// always add this custom prefix
		css_id = "woas_css_"+css_id;
		
		// check if we have a duplicate instance
		if (this.index(css_id) !== -1) {
			woas.log("DOM: instance "+css_id+" already exists");
			return false;
		}
		
		var style;
		if (external) {
			style = document.createElement("link");
			style.setAttribute("rel", "stylesheet");
			style.setAttribute("type", "text/css");
			style.setAttribute("id", css_id);
			style.setAttribute("href", css_src);
			// only when external
			++this._loading;
		} else {
			style = document.createElement('style');
			style.type = "text/css";
			style.id = css_id;
			style.appendChild(document.createTextNode(css_text));
		}
		
		this._objects.push( {obj:style, parent: (woas.browser.ie ? this._cache.body:this._cache.head),
							instance:css_id, after_load: external ? after_load : null } );
		
		woas.log("DOM: "+css_id+" created "+this._show_load());
		// add a callback which informs us of the completion (not always possible)
		if (external) {
			// these engines don't support a callback :(
			if (woas.browser.gecko || woas.browser.webkit) {
				setTimeout("woas.dom._elem_onload('"+woas.js_encode(css_id)+"');", 100);
			} else { // good ol' IE
				style.onreadystatechange = woas._make_delta_func("woas.dom._elem_onload",
													"'"+woas.js_encode(css_id)+"'");
			}
		}
		
		// on IE inject directly in body
		if (woas.browser.ie) {
			this._cache.body.appendChild(style);
		} else {
			this._cache.head.appendChild(style);
		}
		return true;
	},
	
	remove_css: function(i) {
		return this.remove("woas_css_"+i);
	},

	remove_script: function(script_class, script_id) {
		return this.remove("woas_script_"+script_class+"_"+script_id);
	},
	
	remove: function(instance) {
		var found = this.index(instance);
		if (found === -1)
			return false;
		// delete DOM entry from parent container
		this._objects[found].parent.removeChild(this._objects[found].obj);
		// fix arrays
		this._objects.splice(found, 1);
		return true;
	},
	
	// counter of elements being loaded
	_loading: 0,
	
	// generic callback used when we want to run something after the file has been loaded
	// (usually CSS/JavaScript)
	_elem_onload: function(instance) {
		// go away if it is already loaded
		if (woas.dom.get_loaded(instance))
			return;
		// only for IE
		if (woas.browser.ie && (typeof this.readyState != "undefined")) {
			if (this.readyState != 'complete' && this.readyState != 'loaded')
				return;
			// remove handler
			this.onreadystatechange = null;
		} else if (woas.browser.firefox) {
		  // unplug handler
		  var dom_obj = woas.dom._objects[woas.dom.index(instance)];
		  dom_obj.obj.onload = null;
		}
		// set as loaded (shall not remove from arrays)
		woas.dom.set_loaded(instance);
	},
	
	get_loaded: function(instance) {
		var i = this.index(instance);
		if (i !== -1) {
			if (this._objects[i].instance === instance)
				return this._objects[i].loaded;
		}
		woas.log("DOM: "+instance+" is not indexed"+this._show_load());
		return false;
	},
	
	// get list of instances which are still loading
	get_loading: function() {
		var a=[];
		for(var i=0,it=this._objects.length;i < it;++i) {
			if (!this._objects[i].loaded)
				a.push(this._objects[i].instance);
		}
		return a;
	},
	
	// short-hand to get index of an instance
	index: function(instance) {
		for(var i=0,it=this._objects.length;i < it;++i) {
			if (this._objects[i].instance === instance)
				return i;
		}
		return -1;
	},

	set_loaded: function(instance) {
		var i = this.index(instance);
		if (i !== -1) {
			this._objects[i].loaded = true;
				
			// now call the associated callback
			if (typeof this._objects[i].after_load == "function") {
				(this._objects[i].after_load)();
			}
			
			// reduce the counter of 'hung' requests
			--this._loading;
			woas.log("DOM: "+instance+" completed loading"+this._show_load());
			// check if we need to launch the hook
			if (this._loading === 0)
				this._run_post_load_hook();
			
			return true;
		}
		woas.log("DOM: "+instance+" completed loading, but was not indexed"+this._show_load());
		return false;
	},
	
	_show_load: function() {
		return "";
		return " (%d/%d)".sprintf(this._loading, this._objects.length)+"\n"+
				"still loading: "+this.get_loading();
	},
	
	// regex used to remove some comments
	reJSComments: /^\s*\/\*[\s\S]*?\*\/\s*/g,
	
	_internal_add: function(script_token, script_content, external, after_load) {
		script_token = "woas_script_"+script_token;
		// check if we have a duplicate instance
		if (this.index(script_token) !== -1) {
			woas.log("DOM: instance "+script_token+" already exists");
			return false;
		}
		
		var s_elem = document.createElement("script");
		s_elem.type="text/javascript";
		s_elem.id = script_token;

		// register in our management arrays
		this._objects.push( {obj:s_elem, parent:this._cache.head, instance:script_token,
								external: external ? true : false,
								loaded: external ? false : true, after_load: external ? after_load : null} );
		// only external sources should be marked as "loading"
		if (external)
			++this._loading;
		woas.log("DOM: "+script_token+" created "+(external ? "("+script_content+") ":"(inline) ")+this._show_load());
		// add a callback which informs us of the completion
		if (external) {
			if (woas.browser.ie)
				s_elem.onreadystatechange = woas._make_delta_func("woas.dom._elem_onload",
													"'"+woas.js_encode(s_elem.id)+"'");
			else
				s_elem.onload = woas._make_delta_func("woas.dom._elem_onload",
													"'"+woas.js_encode(s_elem.id)+"'");
			// specify the external URL
			s_elem.src = script_content;
		}
				
		this._cache.head.appendChild(s_elem);
		if (!external) {
//			woas.alert("Inline code:\n"+script_content);
			woas.setHTML(s_elem, script_content);
		}
		return true;
	},
	
	add_script: function(script_class, script_id, script_content, external, after_load) {
		// remove the comments
		if (!external)
			script_content = script_content.replace(this.reJSComments, '');
		// it's not necessary to add this script tag
		if (!script_content.length) return false;
		return this._internal_add(script_class+"_"+script_id, script_content, external, after_load);
	},
	
	// remove all script objects
	remove_all: function() {
		var it=this._instances.length;
		for(var i=0;i < it;++i) {
			// remove the object
			this._objects[i].parent.removeChild(this._objects[i].obj);
		}
		// clear objects array
		this._objects = [];
	},
	
	// call the argument function when there aren't other libraries left to load
	// returns true if loading was already complete
	// returns false if we are async-waiting
	_post_load_hook: woas._dummy_fn,
	wait_loading: function(fn_object) {
		if (this._loading === 0) {
			(fn_object)();
			// always reset associated async hook
			this._post_load_hook = woas._dummy_fn;
			return true;
		}
		// associate the hook
		this._post_load_hook = fn_object;
		return false;
	},
	
	_run_post_load_hook: function() {
		// run in a thread so that the call to last object unload does not wait on the hook
		setTimeout('woas.dom._post_load_hook();woas.dom._post_load_hook=woas._dummy_fn;', 10);
	}
	
};

// generate a delta function and cache it
woas._delta_cache = {};
woas._make_delta_func = function(fn_name, fn_args) {
	var id=fn_name+' '+fn_args;
	var fn_obj;
	if (typeof woas._delta_cache[id] == "undefined") {
		eval("fn_obj = function() { "+fn_name+"("+fn_args+"); };");
		woas._delta_cache[id] = fn_obj;
	} else
		fn_obj = woas._delta_cache[id];
	return fn_obj;
};

// @module pager
woas.pager = {

	_decrypt_failed: false,	// the last decryption failed due to wrong password attempts
	
	decrypt_failed: function() {
		if (this._decrypt_failed) {
			this._decrypt_failed = false;
			return true;
		}
		return false;
	},

	get: function(title) {
		return woas.get_text(title);
	},
	
	get_by_index: function(i) {
		return woas.get__text(i);
	},
	
	// this yummy function needs can be overriden
	browse_hook: function(title) {
		return true;
	},
	
	// set content of specified page
	set_body: function(title, new_body) {
		var pi = woas.page_index(title);
		if (woas.debug_mode) {
			if (pi === -1) {
				woas.log("BUG: page \""+title+"\" does not exist!");	// log:1
				return;
			}
		}
		woas.set__text(pi, new_body);
	}
};

// @module pager.bucket
woas.pager.bucket = {
	items: [],		// multi-pages selection
	clear: function() {
		this.items = [];
	},
	add: function(title) {
		if (this.items.indexOf(title) === -1)
			this.items.push(title);
	},
	// used when we want to set a single page in bucket
	one: function(title) {
		this.items = [title];
	}
};

// namespace for custom stuff defined by macros/plugins
// if you are a JavaScript developer you should put singleton instance objects in here
woas.custom = { };

/*** src/stickwiki.js ***/
// the namespace(+subnamespaces) of the current page
woas.current_namespace = "";

// the last page on which the cached AES key was used on
woas.last_AES_page = "";

// tells if configuration has been changed
woas.cfg_changed = false;

// new variables will be properly declared here
woas.prev_title = current;		// previous page's title used when entering/exiting edit mode

woas.save_queue = [];		// pages which need to be saved and are waiting in the queue

// Auto-Save Thread
woas._autosave_thread = null;

// previous length of WSIF datasource
woas._old_wsif_ds_len = null;

// this will likely happen when javascript code block was corrupted
woas._on_load = woas_on_unload = function() { this.crash("Deferred load/unload function not available!");};

// default post-load hook
woas.post_load = function(){};

// left and right trim
// grabbed from http://blog.stevenlevithan.com/archives/faster-trim-javascript
woas.trim = function(str) {
//	return s.replace(/(^\s*)|(\s*$)/, '');
	str = str.replace(/^\s+/, '');
	for (var i = str.length - 1; i >= 0; i--) {
		if (/\S/.test(str.charAt(i))) {
			str = str.substring(0, i + 1);
			break;
		}
	}
	return str;
};

// used to craft XHTML pages
woas.DOCTYPE = "<"+"!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n";
woas.DOC_START = "<"+"html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\">\n<"+"head>\n"+
	"<"+"meta woas_permanent=\"1\" http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n";

// general javascript-safe string quoting
// NOTE: not completely binary safe!
// should be used only for titles (which ought not to contain binary bytes)
var reMinorG = new RegExp("<", "g");
woas.js_encode = function (s, split_lines) {
	// not to counfound browsers with saved tags
	s = s.replace(/\\/g, "\\\\").replace(reMinorG, "\\x3C").replace(/>/g, "\\x3E").
		replace(/'/g, "\\'");
	// escape newlines (\r\n happens only on the stupid IE) and eventually split the lines accordingly
	if (!split_lines)
		s = s.replace(new RegExp("\r\n|\n", "g"), "\\n");
	else
		s = s.replace(new RegExp("\r\n|\n", "g"), "\\n\\\n");
	return this._utf8_js_fix(s);
};

// perform ECMAScript encoding only on some UTF-8 sequences
woas.ecma_encode = function(s) {
	return this._utf8_js_fix(s.replace(/\\/g, "\\\\"));
};

woas._ecma_rx_test = new RegExp("[^\u0000-\u007F]");

// returns true if text needs ECMA encoding
// checks if there are UTF-8 characters
woas.needs_ecma_encoding = function(s) {
	return this._ecma_rx_test.test(s);
};

woas._utf8_js_fix = function(s) {
	// fix the >= 128 ascii chars (to prevent UTF-8 characters corruption)
	return s.replace(woas.utf8.reUTF8Space, function(str) {
		var r="";
		for(var a=0,l=str.length;a < l;++a) {
			var s = str.charCodeAt(a).toString(16);
			r += "\\u" + "0000".substr(s.length) + s;
		}
		return r;
	});
};

woas.ecma_decode = function(s) {
	return s.replace(new RegExp("(\\\\u[0-9a-f]{4})+", "g"), function (str, $1) {
		// this will perform real UTF-8 decoding
		var r = "";
		for (var ic=0,totc=str.length;ic < totc;ic+=6) {
			// get the hexa-numeric part
			var c = str.substr(ic+2, 4);
			// remove leading zeroes and convert to base10
			c = parseInt(c.replace(/^0*/,''), 16);
			// convert UTF-8 sequence to character
			r += String.fromCharCode(c);
		}
		return r;
	}).replace(/\\\\/g, "\\");
};

// used to escape blocks of source into HTML-valid output
woas.xhtml_encode = function(src) {
/*	return this.utf8.do_escape(src.replace(/[<>&]+/g, function ($1) {
		var l=$1.length;
		var s="";
		for(var i=0;i < l;i++) {
			switch ($1.charAt(i)) {
				case '<':
					s+="&lt;";
					break;
				case '>':
					s+="&gt;";
					break;
//				case '&':
				default:
					s+="&amp;";
			}
		}
		return s;
	})); */
	return this.utf8.do_escape(src.replace(/&/g, '&amp;').replace(reMinorG, '&lt;').
			replace(/>/g, '&gt;')); // .replace(/"/g, '&quot;').replace(/'/g, '&#39;');
};

// DO NOT modify this list! these are namespaces that are reserved to WoaS
var reserved_namespaces = ["Special", "Lock", "Locked", "Unlocked", "Unlock",
						"Tags" /*DEPRECATED*/,
						"Tagged", "Untagged", "Include", "Javascript",
						"WoaS"];

// create the regex for reserved namespaces
var reserved_rx = "^";
for(var i = (woas.tweak.edit_override ? 1 : 0);i < reserved_namespaces.length;i++) {
	reserved_rx += /*RegExp.Escape(*/reserved_namespaces[i] + "::";
	if (i < reserved_namespaces.length-1)
		reserved_rx += "|";
}
woas._reserved_rx = new RegExp(reserved_rx, "i"); reserved_namespaces = reserved_rx = null;

// return page index (progressive number) given its title
woas.page_index = function(title) {
	return page_titles.indexOf(this.title_unalias(title));
};

woas.is_reserved = function(page) {
	return (page.search(this._reserved_rx)==0);
};

woas.is_menu = function(title) {
	return (title.substr(title.length-6) === "::Menu");
};

// returns namespace with trailing ::
woas.get_namespace = function(page, root_only) {
	var p;
	if ((typeof root_only == "boolean") && (root_only == true))
		p = page.indexOf("::");
	else
		p = page.lastIndexOf("::");
	if (p==-1) return "";
	return page.substring(0,p+2);	
};

// page attributes bits are mapped to (readonly, encrypted, ...)

woas.is_readonly = function(page) {
	return this.is_readonly_id(this.page_index(page));
};

woas.is_readonly_id = function(pi) {
	return !!(page_attrs[pi] & 1);
};

woas.is__encrypted = function (pi) {
	return !!(page_attrs[pi] & 2);
};

woas.is_encrypted = function(page) {
	return this.is__encrypted(this.page_index(page));
};

woas.is__embedded = function(pi) {
	return !!(page_attrs[pi] & 4);
};
woas.is_embedded = function(page) {return this.is__embedded(this.page_index(page));};

woas.is__image = function(pi) {
	return !!(page_attrs[pi] & 8);
};
woas.is_image = function(page) { return this.is__image(this.page_index(page)); };

// a page physically exists if it is not part of a reserved namespace, if it is not a (sub)namespace and if it actually exists
woas.page_exists = function(page) {
	return (this.is_reserved(page) || (page.substring(page.length-2)==="::") || (this.page_index(page)!==-1));
};

// with two trailing double colon
woas._get_namespace_pages = function (ns) {
	var pg = [];
	switch (ns.substr(0, ns.length-2)) {
		case "Locked":
			return this.special_encrypted_pages(true);
		case "Unlocked":
			return this.special_encrypted_pages(false);
		case "Untagged":
			return this.special_untagged();
		case "Tagged": // to be used in wiki source
			return this.special_tagged();
		case "Image":
			return this._special_image_gallery(ns);
	}

	for(i=0, l=page_titles.length;i < l;++i) {
		if (page_titles[i].indexOf(ns)===0)
			pg.push(page_titles[i]);
	}
	return /*"= Pages in "+ns+" namespace\n" + */this._join_list(pg);
};

// return a plain page or a decrypted one if available through the latest key
woas.get_page = function(pi) {
	if (this.is__embedded(pi))
		return null;
	if (!this.is__encrypted(pi))
		return pages[pi];
	if (!this.AES.isKeySet()) {
		this.last_AES_page = "";
		return null;
	}
	// decrypt by using a copy of the array
	var pg = this.AES.decrypt(pages[pi].slice(0));
	this.last_AES_page = page_titles[pi];
	return pg;	
};

var reScriptTags = new RegExp("<"+"script[^>]*>((.|\\n)*?)<"+"\\/script>", "gi"),
	reAnyXHTML = /\<\/?\w+[^>]+>/g;
// get the text of the page, stripped of html tags
woas.get_src_page = function(pi, rawmode) {
	var pg = this.get_page(pi);
	if (pg===null) return null;
	if ((typeof rawmode == "undefined") || (rawmode == false))
		pg = pg.replace(/\{\{\{((.|\n)*?)\}\}\}/g, "");
	else
		pg = pg.replace(/(\{|\})(\1)(\1)/g, "$1<"+"!-- -"+"->$2<"+"!-- -"+"->$3");
	// remove wiki and html that should not be viewed when previewing wiki snippets
	return pg.replace(reScriptTags, "").
			replace(reAnyXHTML, "");
};

woas.get_text = function (title) {
	var pi = this.page_index(title);
	if (pi==-1)
		return null;
	return this.get__text(pi);
};

woas.get_text_special = function(title) {
	var ns = this.get_namespace(title);
	var text = null;
	if (ns.length) {
//		woas.log("namespace of "+title+" is "+namespace);	// log:0
		title = title.substring(ns.length);
		if (!title.length) return this._get_namespace_pages(ns);
		switch (ns) {
			case "Special::":
				text = this._get_special(title, false);
				if (text === false) text = null;
			break;
			case "Tagged::":
				text = this.special_tagged(title);
			break;
			default:
				text = this.get_text(ns+title);
		}
	} else
		text = this.get_text(title);
	return text;
};

woas.__last_title = null;

woas.__password_finalize = function(pwd_obj) {
	d$.show_ni("woas_wiki_area");
	document.title = this.__last_title;
	d$.hide("woas_pwd_mask");
//	scrollTo(0,0);
	// hide input form
	pwd_obj.value = "";
	pwd_obj.blur();
	this.ui.blur_textbox();
};

woas._set_password = function() {
	this.__last_title = document.title;
	document.title = "Enter password";
	// hide browser scrollbars and show mask
	d$.show("woas_pwd_mask");
	d$.hide_ni("woas_wiki_area");
	scrollTo(0,0);
	// show input form
	d$.show_ni("woas_pwd_query");
	d$("woas_password").focus();	
	this.ui.focus_textbox();
};

woas._password_cancel = function() {
	this.__password_finalize(d$("woas_password"));
};

woas._password_ok = function() {
	var pwd_obj = d$("woas_password");
	var pw = pwd_obj.value;
	if (!pw.length) {
		this.alert(this.i18n.PWD_QUERY);
		return;
	}
	this.AES.setKey(pw);
	this.__password_finalize(pwd_obj);
};

//TODO: specify interactive-mode
woas.get__text = function(pi) {
	// is the page encrypted or plain?
	if (!this.is__encrypted(pi))
		return pages[pi];
	this.pager._decrypt_failed = true;
	if (!this.AES.isKeySet()) {
		this.alert(this.i18n.ERR_NO_PWD.sprintf(page_titles[pi]));
		return null;
	}
	this.progress_init("AES decryption");
//	var pg = null;
			//TODO: use form-based password input
//			this._get_password('The latest entered password (if any) was not correct for page "'+page_titles[pi]+"\"");
//			var pw = prompt('The latest entered password (if any) was not correct for page "'+page_titles[pi]+"'\n\nPlease enter the correct password.", '');
//			if ((pw==null) || !pw.length) {
			// we are waiting for the password to be set programmatically
/*			if (!pw.length) {
				this.last_AES_page = "";
				AES_clearKey();
				document.body.style.cursor = "auto";
				return null;
			}
			AES_setKey(pw);
			retry++;
*/
//			return null;
//		}
		// pass a copied instance to the decrypt function
		// AES_decrypt can return null on failure, but can also return a garbled output
		var pg = this.AES.decrypt(pages[pi].slice(0));
		this.last_AES_page = page_titles[pi];
//		if (pg != null)
//			break;
	if (!this.config.key_cache)
		this.AES.clearKey();
	if (pg !== null) {
		this.pager._decrypt_failed = false;
//		if (this.config.key_cache)			this.last_AES_page = page_titles[pi];
	} else {
		this.alert(this.i18n.ACCESS_DENIED.sprintf(page_titles[pi]));
//		AES_clearKey();
		this.last_AES_page = "";
	}
	this.progress_finish();
	return pg;
};

woas.set__text = function(pi, text) {
	this.log("Setting wiki text for page #"+pi+" \""+page_titles[pi]+"\"");	// log:1
	if (this.is__embedded(pi) && !this.is__image(pi))
		text = this.base64.encode(text);
	if (!this.is__encrypted(pi)) {
		pages[pi] = text;
		return;
	}
	pages[pi] = this.AES.encrypt(text);
	this.last_AES_page = page_titles[pi];
};

woas.assert_current = function(page) {
	if( current !== page )
		go_to( page ) ;
	else
		this.set_current( page, true);
};

woas._get_embedded = function(cr, etype) {
	var pi = this.page_index(cr);
	if (pi === -1) {
		var P = {body:this.parser.transclude("Special::Embed|"+etype, [])};
		this.parser.syntax_parse(P, []);
		return P.body;
	}
	return this._get__embedded(cr, pi, etype);
};

woas._get__embedded = function (cr, pi, etype) {
	var text=this.get__text(pi);
	if (text==null) return null;
	var xhtml = "";
	
	if (etype=="file") {
		var fn = cr.substr(cr.indexOf("::")+2);
		var pview_data = this.base64.decode(text, 1024), pview_link = "",
			ext_size = Math.ceil((text.length*3)/4);
		//FIXME: is this even correct?
		if (ext_size-pview_data.length>10)
			pview_link = "<"+"div id='_part_display'><"+"em>"+this.i18n.FILE_DISPLAY_LIMIT+
			"<"+"/em><"+"br /><"+"a href='javascript:show_full_file("+pi+")'>"+this.i18n.DISPLAY_FULL_FILE+"<"+"/a><"+"/div>";
		var P = {body: "\n{{{[[Include::"+cr+"]]}}}"+
				"\n\nRaw transclusion:\n\n{{{[[Include::"+cr+"|raw]]}}}"};
		if (!this.is_reserved(cr))
			P.body += "\n\n\n<"+"a href=\"javascript:query_delete_file('"+this.js_encode(cr)+"')\">"+this.i18n.DELETE_FILE+"<"+"/a>\n";
		P.body += "\n";
		
		// correct syntax parsing of nowiki syntax
		var snippets = [];
		this.parser.pre_parse(P, snippets);
		this.parser.syntax_parse(P, snippets);
		this.parser.undry(P, snippets);
		
		xhtml = "<"+"pre id='woas_file_ct' class=\"woas_nowiki woas_embedded\">"+this.xhtml_encode(pview_data)+"<"+"/pre>"+
				pview_link+"<"+"br /><"+"hr />"+this.i18n.FILE_SIZE+": "+_convert_bytes(ext_size)+
				"<"+"br />" + this.last_modified(this.config.store_mts ? page_mts[pi] : 0)+
				"<"+"br /><"+"br />XHTML transclusion:"+
				P.body +
				"<"+"a href=\"javascript:query_export_file('"+this.js_encode(cr)+"')\">"+this.i18n.EXPORT_FILE+"<"+"/a><"+"br />";
		P = null;
	} else { // etype == image
		var img_name = cr.substr(cr.indexOf("::")+2);
		//TODO: do not create a dynamic script! use after_parse hook
		xhtml = this.parser.parse("= "+img_name+"\n\n"+
		"<"+"script type=\"text/javascript\"> setTimeout(\"_img_properties_show('"+
				text.match(/^data:\s*([^;]+);/)[1] + "', "+
				text.length + ", " +
				(text.match(/^data:\s*[^;]*;\s*[^,]*,\s*/)[0]).length+", "+
				(this.config.store_mts ? page_mts[pi] : 0 ) +
				")\");"+
		"<"+"/script>"+
		"<"+"img id=\"woas_img_tag\" class=\"woas_embedded\" src=\""+text+"\" alt=\""+this.xhtml_encode(img_name)+"\" />"+
		"\n\n<"+"div id=\"woas_img_desc\">"+this.i18n.LOADING+"<"+"/div>"+
		"\nSimple transclusion:\n\n{{{[[Include::"+cr+"]]}}}\n\nTransclusion with additional attributes:\n\n{{{[[Include::"+cr+"|border=\"0\" onclick=\"go_to('"+
		this.js_encode(cr)+"')\" style=\"cursor:pointer\"]]}}}\n"+
		"\n<"+"a href=\"javascript:query_delete_image('"+this.js_encode(cr)+"')\">"+this.i18n.DELETE_IMAGE+"<"+"/a>\n"+
		"\n<"+"a href=\"javascript:query_export_image('"+this.js_encode(cr)+"')\">"+this.i18n.EXPORT_IMAGE+"<"+"/a>\n");
	}
	return xhtml;
};

woas._embed_process = function(etype) {
	// pick the correct mode for file inclusion
	// normalize etype to the correspondant binary flag value
	var desired_mode;
	if (etype == "image") {
		desired_mode = this.file_mode.DATA_URI;
		etype = 12;
	} else {
		desired_mode = this.file_mode.BASE64;
		etype = 4;
	}
	
	// load the data in DATA:URI mode
	var ct = this.load_file(null, desired_mode);
	if ((ct === false) || ((typeof ct != "string") || !ct.length)) {
		this.alert(this.i18n.LOAD_ERR);
		return false;
	}
	
	pages.push(ct);
	page_attrs.push(etype);
	page_titles.push(current);
	// set modified timestamp to now
	page_mts.push(this.config.store_mts ? Math.round(new Date().getTime()/1000) : 0);
	
	// save this last page
	this.commit(page_titles.length-1);
	
	this.refresh_menu_area();
	return this.set_current(current, true);
};

woas._get_special = function(cr, interactive) {
	var text = null;
	var pi = this.shortcuts.indexOf(cr);
	cr = "Special::" + cr;
	if (pi != -1) {
		var fn = this.shortcuts_js[pi];
		var is_cmd = (fn.substr(0,4)=="cmd_");
		if (!interactive && is_cmd)
			return null;
		text = this[fn]();
		// skip the cmd shortcuts
		if (is_cmd)
			// return a special value for executed commands
			return false;
	} else
		text = this.get_text(cr);
	if(text === null) {
		if (interactive)
			this.alert(this.i18n.INVALID_SPECIAL);
	}
	return text;
};

// this is quite undocumented
woas.get_javascript_page = function(cr) {
	var emsg = "-", text;
	text = woas.eval(cr, true);
	if (this.eval_failed) {
		this.crash("Dynamic evaluation of '"+cr+"' failed!\n\nError message:\n\n"+this.eval_fail_msg);
		return null;
	}
	// for safety
	if ((typeof text).toLowerCase() != "string")
		return null;
	return text;
};

woas.eval = function(code, return_value) {
	var rv;
	try {
		if (return_value)
			eval("rv = "+code);
		else
			eval(code);
		woas.eval_failed = false;
	}
	catch (e) {
		this.eval_fail_msg = e.toString();
		this.eval_failed = true;
	}
//	woas.log("woas.eval(\""+code+"\", "+return_value+") = "+rv);	//log:0
	return rv;
};

// Load a new current page
// return true if page needs to be saved in history, false otherwise
woas.set_current = function (cr, interactive) {
	// call hooks which decide upon our navigation capabilities
	if (!woas.pager.browse_hook(cr))
		return false;
//	this.log("Setting current page to \""+cr+"\"");	//log:0
	var text, namespace, pi,
		// whether to reset bucket or not
		set_b = false;
	woas.pager.bucket.clear();
	// eventually remove the previous custom script
	if (cr.substring(cr.length-2)==="::") {
		text = this._get_namespace_pages(cr);
		namespace = cr.substring(0,cr.length-2);
		cr = "";
	} else {
		var p = cr.indexOf("::");
		// skip not found references but also null namespace references
		if (p>0) {
			namespace = cr.substring(0,p);
//			this.log("namespace of "+cr+" is "+namespace);	// log:0
			cr = cr.substring(p+2);
				switch (namespace) {
					case "Javascript":
					// this namespace may deprecate many others
					text = this.get_javascript_page(cr);
					if (text === null)
						return false;
					break;
					case "Special":
						text = this._get_special(cr, interactive);
						//the 'false' special value is returned in case of command execution
						if (text === false)
							return true;
						// no such special page exists
						if (text === null)
							return false;
						break;
					case "Tagged":
						text = this.special_tagged(cr);
						if (text === null)
							return false;
						break;
					case "Lock":
						// prevent special pages from being locked
						if (this.is_reserved(cr)) {
							this.alert(this.i18n.CANNOT_LOCK_RESERVED);
							return false;
						}
						pi = this.page_index(cr);
						if (this.AES.isKeySet()) {
							// display a message
							if (confirm(this.i18n.CONFIRM_LOCK.sprintf(cr)+
								(this.last_AES_page ? this.i18n.CONFIRM_LOCK_LAST.sprintf(last_AES_page) : ''))) {
								this._finalize_lock(pi);
								return false;
							}
						}
						text = this.get_text("Special::Lock");
						break;
					case "Unlock":
						pi = this.page_index(cr);
						if (!confirm(this.i18n.CONFIRM_REMOVE_ENCRYPT.sprintf(cr)))
							return false;
						text = this.get_text(cr);
						if (this.pager.decrypt_failed())
							return false;
						pages[pi] = text;
						page_attrs[pi] -= 2;
						if (!this.config.key_cache)
							this.AES.clearKey();
						if (this.set_current(cr, true)) {
							this.save_page(cr);
							return true;
						}
						return false;
					case "WoaS":
						pi = woas.page_index(namespace+"::"+cr);
						// unexisting page
						if (pi === -1)
							return false;
						var real_t = page_titles[pi];
/*						if (this.is__embedded(pi)) {
							//TODO: do not use namespace to guess the embedded file type
							text = this._get__embedded(real_t, pi, "file");
						} else { */
						// detect if showing a plugin
						var _pfx = "WoaS::Plugins::";
						if (real_t.substr(0, _pfx.length) === _pfx) {
							text = this.plugins.get(real_t.substr(_pfx.length));
							if (text !== null) {
								if (this.plugins.is_external) {
									text = this.plugins.describe_external(text);
								} else
									text = this._raw_preformatted('div', text, 'woas_core_page woas_nowiki woas_nowiki_multiline');
							}
						} else {
							text = this.get_text(real_t);
							if (text !== null) {
								switch (cr) {
									case "Plugins":
										text = this.parser.parse(text + this.plugins.list());
									break;
									case "Aliases":
									case "Hotkeys":
										case "CSS::Core":
									case "CSS::Boot":
									case "CSS::Custom":
										// page is stored plaintext
										text = this._raw_preformatted('div', text, 'woas_core_page woas_nowiki woas_nowiki_multiline');
									break;
									default:
										// help pages and related resources
										text = this.parser.parse(text);
								} // switch per page title
							} // text not null
						} // plugins/non-plugins

						if(text === null) {
							// called for reset purposes
							this.pager.decrypt_failed();
							return false;
						}
						this._add_namespace_menu(namespace);
						if (namespace.length)
							cr = real_t;
						return this.load_as_current(cr, text, this.config.store_mts ? page_mts[pi] : 0, true);
					case "File":
					case "Image":
						text = this._get_embedded(namespace+"::"+cr, namespace.toLowerCase());
						if(text === null) {
							// called for reset purposes
							this.pager.decrypt_failed();
							return false;
						}
						this._add_namespace_menu(namespace);
						if (namespace.length)
							cr = namespace + "::" + cr;
						var mts;
						if (this.config.store_mts)
							mts = page_mts[this.page_index(namespace+"::"+cr, namespace.toLowerCase())];
						else mts = 0;
						return this.load_as_current(cr, text, mts, true);
					default:
						text = this.get_text(namespace+"::"+cr);
						set_b = true;
				}

		} else {
			namespace = "";
			text = this.get_text(cr);
			set_b = true;
		}
	}
	
	// action taken when no such page exists (or decryption failed)
	if (text === null) {
		if (this.pager.decrypt_failed())
			return false;
		return this._new_page(this.i18n.PAGE_NOT_FOUND, false, namespace.length ? namespace+ "::"+ cr : cr);
	}
	
	this._add_namespace_menu(namespace);

	// hard-set the current page to the namespace page
	if (namespace.length) {
		cr = namespace + "::" + cr;
		pi = this.page_index(cr);
		if (pi)
			mts = page_mts[pi];
		else mts = null;
	} else {
		pi = this.page_index(cr);
		cr = page_titles[pi];
		mts = page_mts[pi];
	}
	// used by some special pages (e.g. backlinks) for page title override
	return this.load_as_current(cr, this.parser.parse(text, false, this.js_mode(cr)), this.config.store_mts ? mts : 0, set_b);
};

// enable safe mode for non-reserved pages
woas.js_mode = function(cr) {
	if (this.config.safe_mode)
		return this.is_reserved(cr) ? 1 : 3;
//	else
	return 1;
};

woas.last_modified = function(mts) {
	// do not show anything when the timestamp is magic (zero)
	if (mts == 0)
		return "";
	return this.i18n.LAST_MODIFIED + (new Date(mts*1000)).toLocaleString();
};

// actually load a page given the title and the proper XHTML
woas.load_as_current = function(title, xhtml, mts, set_b) {
	if (typeof title == "undefined") {
		this.crash("load_as_current() called with undefined title");
		return false;
	}
	
	// used by some special pages (e.g. backlinks) for page title override
	this.parser.render_title = title;
	// the bucket will contain only the rendered page
	if (set_b)
		this.pager.bucket.one(title);

	
	scrollTo(0,0);
	this.log("load_as_current(\""+title+"\", "+set_b+") - "+(typeof xhtml == "string" ? (xhtml.length+" bytes") : (typeof xhtml)));	// log:1
	this.setHTMLDiv(d$("woas_wiki_area"), xhtml);
	this.refresh_mts(mts);

	this._set_title(title);
	
	this.history.go(current);
	
	this.update_nav_icons(title);
	current = title;
	// active menu or page scripts
	this.scripting.activate(this.is_menu(current) ? "menu" : "page");
	return true;
};

woas._finalize_lock = function(pi) {
	this._perform_lock(pi);
	var title = page_titles[pi];
	this.set_current(title, true);
	if (!this.config.key_cache) {
		this.AES.clearKey();
		this.last_AES_page = "";
	} else
		this.last_AES_page = title;
	this.save_page_i(pi);
};

woas._perform_lock = function(pi) {
	pages[pi] = this.AES.encrypt(pages[pi]);
	page_attrs[pi] += 2;
};

woas._add_namespace_menu = function(namespace) {
	if (this.current_namespace === namespace)
		return;
	var pi;
	if (namespace === "")
		pi = -1;
	else // locate the menu for current namespace
		pi = this.page_index(namespace+"::Menu");
	if (pi === -1) {
//		this.log("no namespace menu found");	// log:0
		this.setHTMLDiv(d$("woas_ns_menu_area"), "");
		if (this.current_namespace !== "") {
			d$.hide("woas_ns_menu_area");
			d$.hide("woas_ns_menu_edit_button");
		}
		this.current_namespace = namespace;
		return;
	}
	var menu = this.get__text(pi);
	this.setHTMLDiv(d$("woas_ns_menu_area"), menu === null ? "" : this.parser.parse(menu, false, this.js_mode(namespace+"::Menu")) );
	// show sub-menu
	d$.show("woas_ns_menu_area");
	d$.show("woas_ns_menu_edit_button");
	this.current_namespace = namespace;	
};

// auto-save thread
woas._auto_saver = function() {
	if (woas.save_queue.length && !woas.ui.edit_mode) {
		woas.commit(woas.save_queue);
		woas.menu_display("save", false);
	}
	// always clear previous thread
	clearTimeout(woas._autosave_thread);
	// re-launch the thread
	if (_this.config.auto_save)
		woas._autosave_thread = setTimeout("woas._auto_saver()", woas.config.auto_save);
};

// save configuration on exit
woas._on_unload = function () {
	if (this.save_queue.length)
		this.commit(this.save_queue);
	else {
		if (this.config.save_on_quit && this.cfg_changed)
			this.cfg_commit();
	}
	return true;
};

 // DO NOT use setHTML for the document.body object in IE browsers
woas.setHTML = woas.getHTML = null;

// these are unchanged for all browsers
woas.getHTMLDiv = function(elem) {return elem.innerHTML;};
woas.setHTMLDiv = function(elem, html) {elem.innerHTML = html;};

// when the page is loaded - onload, on_load
woas._on_load = function() {
	// output platform information - note that revision is filled in only in releases
	woas.log("*** WoaS v"+this.version+"-r2239"+" started");	// log:1
	
	// store the old length to eventually force full save when entering/exiting WSIF datasource mode
	this._old_wsif_ds_len = this.config.wsif_ds.length;

	// (0) set some browser-tied functions
	if (this.browser.ie) {	// some hacks for IE
		this.setHTML = function(elem, html) {elem.text = html;};
		this.getHTML = function(elem) {return elem.text;};
		var obj = d$("woas_wiki_header");
		obj.style.filter = "alpha(opacity=75);";
		if (this.browser.ie6) {
			d$("woas_wiki_header").style.position = "absolute";
			d$("i_woas_menu_area").style.position = "absolute";
		}
		// IE6/7 can't display logo
		if (!this.browser.ie8) {
			d$.hide("img_logo");
			// replace with css when capability exists:
			d$("woas_logo").style.width = "1%";
		}
	} else {
		this.setHTML = this.setHTMLDiv;
		this.getHTML = this.getHTMLDiv;
		// everyone else needs a logo; will be better when done in css.
		d$("woas_logo").style.width = "35px";
		d$.show("img_logo");
	}
	
	// (1) check integrity of WoaS features - only in debug mode
	if (this.config.debug_mode) {
		this._set_debug(true);
		if (this.tweak.integrity_test) {
			if (!this.integrity_test())
				// test failed, stop loading
				return;
		}
	} else
		this._set_debug(false);

	// (2) load the actual pages (if necessary)
	if (this.config.wsif_ds.length) {
		if (!this._wsif_ds_load(this.config.wsif_ds, this.config.wsif_ds_lock)) {
			// the file load error is already documented to user
			if (this.wsif.emsg !== null) {
				// force debug mode
				this._set_debug(true);
				this.crash("Could not load WSIF pages data!\n"+this.wsif.emsg);
			}
			return;
		}
	}

	// (3) setup some DOM cage objects (read cache)
	this.dom.init();

	// (4) activate the CSS, with eventual fixups for some browsers
	this.css.set(this.get_text("WoaS::CSS::Core")+"\n"+this.get_text("WoaS::CSS::Custom"));
	
	// (5) continue with UI setup
	d$('woas_home_hl').title = this.config.main_page;
	d$('img_home').alt = this.config.main_page;
	
	// properly initialize navigation bar icons
	// this will cause the alternate text to display on IE6/IE7
	var nav_bar = ["back", "forward", "home", "edit", "print", "advanced",
					"cancel", "save", "lock", "unlock", "setkey", "help",
					"top", "debug"];
	for(var i=0,it=nav_bar.length;i < it;++i) {
		this.img_display(nav_bar[i], true);
	}
	
	// customized keyboard hook
	document.onkeydown = woas.ui._keyboard_event_hook;
	
	// Go straight to requested page
	var qpage=document.location.href.split("?")[1];
	if (qpage) {
		current = unescape(qpage);
		// extract the section (if any)
		var p=current.indexOf("#");
		if (p !== -1)
			current = current.substring(0,p);
	}
	
	// (6) initialize extensions - plugins go first so that external javascript/CSS
	// starts loading
	this.setHTML(d$("woas_wait_text"), "Initializing extensions...");
	this.plugins.load();
	this._load_aliases(this.get_text("WoaS::Aliases"));
	this.hotkey.load(this.get_text("WoaS::Hotkeys"));

	if (this.config.permit_edits)
		d$.show("menu_edit_button");
	else
		d$.hide("menu_edit_button");
	
	// enable the auto-save thread
	if (this.config.cumulative_save && this.config.auto_save)
		this._autosave_thread = setTimeout("woas._auto_saver()", this.config.auto_save);
	
	this._editor = new TextAreaSelectionHelper(d$("woas_editor"));

	this.setHTML(d$("woas_wait_text"), "Completing load process...");
	
	// set a hook to be called when loading process is complete
	if (!woas.dom.wait_loading(woas._early_render))
		woas._load_hangup_check(true);
};

// the first page rendering is a delicate process
// plugins and related libraries/CSS must be loaded
// before rendering the first page to prevent some glitches
// to happen, like: missing CSS, missing macros etc
woas._dummy_fn = function() { return; };
woas._load_hangup_check = function(first) {
	// first time we just re-create the spawning thread
	if (!first) {
		if (!woas.dom._loading) {
			woas.log("_load_hangup_check() finished");
			return
		}
		// ask user if he wishes to continue waiting for libraries to finish loading
		if (!confirm(this.i18n.CONTINUE_WAIT_LOAD)) {
			// run the hook like if nothing hung up
			woas.dom._run_post_load_hook();
			woas.log("_load_hangup_check() cancelled");
			return;
		}
	}
	// launch again this thread, every 3s
	woas.log("_load_hangup_check() respawned (still loading: "+woas.dom.get_loading()+")");
	setTimeout("woas._load_hangup_check(false);", 3000);
};
	
woas._early_render = function() {
	woas.history._forward_browse = true; // used to not store backstack
	woas.set_current(current, true);
	woas.refresh_menu_area();
	// feed the current title before running the disable edit mode code
	woas.prev_title = current;
	woas.disable_edit();
	
//	this.progress_finish();
	d$.hide("loading_overlay");
	
	// launching post-load hook
	woas.post_load();	
};

// disable edit-mode after cancel/save actions
woas.disable_edit = function() {
//	woas.log("DISABLING edit mode");	// log:0
	this.ui.edit_mode = false;
	// reset change buffer used to check for page changes
	this.change_buffer = null;
	this.old_title = null;
	// check for back and forward buttons - TODO grey out icons
	this.update_nav_icons(current);
	this.menu_display("home", true);
	if (this.config.cumulative_save)
		this.menu_display("save", (this.save_queue.length!==0));
	else
		this.menu_display("save", false);
	this.menu_display("cancel", false);
	this.menu_display("print", true);
	this.menu_display("setkey", true);
	d$.show("i_woas_text_area");
	// aargh, FF eats the focus when cancelling edit
	d$.hide("edit_area");
	this._set_title(this.prev_title);
};

function _lock_pages(arr) {
	this.alert("Not yet implemented");
}

function _unlock_pages(arr) {
	this.alert("Not yet implemented");
}

woas.edit_allowed = function(page) {
	// can always edit pages if they have an actual data representation
	if (this.tweak.edit_override)
		return (this.page_index(page) != -1);
	// force read-only
	if (!this.config.permit_edits)
		return false;
	if (this.edit_allowed_reserved(page))
		return true;
	// page in reserved namespace
	if (this.is_reserved(page))
		return false;
	// page has readonly bit set
	return !this.is_readonly(page);
};

woas.edit_allowed_reserved = function(page) {
	var _pfx = "WoaS::Plugins::";
	if (page.substr(0, _pfx.length) === _pfx)
		return true;
	// allow some reserved pages to be directly edited/saved
	switch (page) {
		case "WoaS::Aliases":
		case "WoaS::Hotkeys":
		case "WoaS::CSS::Custom":
			return true;
	}
	return false;
};

// setup the title boxes and gets ready to edit text
woas.current_editing = function(page, disabled) {
//	woas.log("current = \""+current+"\", current_editing(\""+page+"\", disabled: "+disabled+")");	// log:0
	this.prev_title = current;
	d$("wiki_page_title").disabled = (disabled && !this.tweak.edit_override ? "disabled" : "");
	d$("wiki_page_title").value = page;
	this.ui.edit_mode = true;
	this._set_title(this.i18n.EDITING.sprintf(page));
	// current must be set BEFORE calling enabling menu edit
//	woas.log("ENABLING edit mode");	// log:0
	this.menu_display("back", false);
	this.menu_display("forward", false);
	this.menu_display("advanced", false);
	this.menu_display("setkey", false);
	this.menu_display("home", false);
	this.menu_display("edit", false);
	this.menu_display("print", false);
	this.menu_display("save", true);
	this.menu_display("cancel", true);
	this.update_lock_icons(page);
	d$.hide("i_woas_text_area");

	// FIXME! hack to show the editor pane correctly on IE
	if (!this.browser.ie) {
		d$("woas_editor").style.width = window.innerWidth - 35 + "px";
		d$("woas_editor").style.height = window.innerHeight - 180 + "px";
	} else {
		d$("woas_editor").style.width = document.documentElement.clientWidth - 35 + "px";
		d$("woas_editor").style.height = document.documentElement.clientHeight - 180 + "px";
	}
	d$.show("edit_area");

	d$("woas_editor").focus();
	current = page;
	scrollTo(0,0);
};

woas.change_buffer = null;
woas.old_title = null;

// sets the text and allows changes monitoring
woas.edit_ready = function (txt) {
	d$("woas_editor").value = txt;
	// save copy of text to check if anything was changed
	// do not store it in case of ghost pages
	this.change_buffer = txt;
	this.old_title = d$("wiki_page_title").value;
};

woas.edit_page = function(page) {
	// can't edit again if already editing
	if (this.ui.edit_mode)
		return false;
	if (!this.edit_allowed(page)) {
		woas.log("Not allowed to edit page "+page);	// log:1
		return false;
	}
	_servm_alert();
	var tmp = this.get_text(page);
	if (tmp===null) return false;
	if (this.is_embedded(page) && !this.is_image(page))
		tmp = this.base64.decode(tmp);
	// setup the wiki editor textbox
	this.current_editing(page, this.is_reserved(page));
	this.edit_ready(tmp);
	return true;
};

//API1.0: check if a title is valid
woas.valid_title = function(title) {
	if (title.length == 0) {
		this.alert(this.i18n.EMPTY_TITLE);
		return false;
	}
	if (title.length > 256) {
		this.alert(this.i18n.TOO_LONG_TITLE.sprintf(256));
		return false;
	}
	if (title.indexOf("[")!==-1 || title.indexOf("]")!==-1 ||
		title.indexOf("{")!==-1 || title.indexOf("}")!==-1 ||
		title.indexOf("<") !== -1 || title.indexOf(">")!==-1 ||
		title.indexOf("|")!==-1 || title.indexOf(":::")!==-1) {
		this.alert(this.i18n.INVALID_TITLE);
		return false;
	}
	if (title.substr(-2)==="::") {
		this.alert(this.i18n.ERR_PAGE_NS);
		return false;
	}
	var ns = this.get_namespace(title, true);
	if (ns.length && this.is_reserved(ns+"::") && !this.tweak.edit_override) {
		this.alert(this.i18n.ERR_RESERVED_NS.sprintf(ns));
		return false;
	}
	return true;
};

woas.rename_page = function(previous, newpage) {
	woas.log("Renaming "+previous+" to "+newpage);	// log:1
	if (this.page_index(newpage)!=-1) {
		this.alert(this.i18n.PAGE_EXISTS.sprintf(newpage));
		return false;
	}
	// this is the actual rename operation
	var pi = this.page_index(previous);
	page_titles[pi] = newpage;
	// variables used to change all occurrencies of previous title to new title
	var reTitles = new RegExp("\\[\\[(Include::)?" + RegExp.escape(previous) + "(\\]\\]|\\|)", "g"),
		// the original page (dried)
		P = {body:null},
		// the expanded
		NP = {body:null},
		changed,
		snippets,
		ilen;
	for(var i=0,l=pages.length;i < l;i++) {
		P.body = this.get_page(i);
		if (P.body === null)
			continue;
		changed = false;
		snippets = [];
		// the parser will know what to take away from the page
		this.parser.dry(P, NP, snippets);
		// replace direct links and transclusion links
		NP.body = NP.body.replace(reTitles, function (str, inc) {
			changed = true;
			ilen = 2 + inc.length;
			return str.substr(0, ilen)+newpage+str.substr(previous.length+ilen);
		});
		if (changed) {
			// clear dynamic newlines
			NP.body = NP.body.replace(this.parser.reNL_MARKER, "");
			this.parser.undry(NP, snippets);
			this.set__text(i, NP.body);
		}
		P.body = NP.body = snippets = null;
	}
	if (previous === this.config.main_page)
		this.config.main_page = newpage;
	// make sure that previous title is consistent
	if (this.prev_title === previous)
		this.prev_title = newpage;
	//TODO: propagate rename to backstack and forstack!
	return true;
};

woas.get_raw_content = function() {
	var c=d$("woas_editor").value;
	// remove CR added by some browsers
	//TODO: check if ie8 still adds these
	if (this.browser.ie || this.browser.opera)
		c = c.replace("\r\n", "\n");
	return c;
};

// action performed when save is clicked
woas.save = function() {
	// you can't save if not editing
	if (!this.ui.edit_mode)
		return false;
	// we will always save ghost pages if save button was hit
	var null_save = !this._ghost_page, was_ghost = this._ghost_page;
	// always reset ghost page flag
	this._ghost_page = false;
	woas.log("Ghost page disabled"); //log:1
	// when this function is called in non-edit mode we perform a full commit
	// for cumulative save
	if (this.config.cumulative_save && !this.ui.edit_mode) {
		this.full_commit();
		this.menu_display("save", false);
		return;
	}
	var raw_content = this.get_raw_content();
	
	// check if this is a null save only if page was not a ghost page
	if (null_save)
		null_save = (raw_content === this.change_buffer);
	
	var can_be_empty = false, skip = false, renaming = false;
	switch(current) {
		case "WoaS::CSS::Custom":
			if (!null_save) {
				this.css.set(this.get_text("WoaS::CSS::Core")+"\n"+raw_content);
				this.pager.set_body(current, raw_content);
			}
			back_to = this.prev_title;
			break;
		case "WoaS::Aliases":
			if (!null_save)
				this._load_aliases(raw_content);
			can_be_empty = true;
			// fallback wanted
			skip = true;
		case "WoaS::Hotkeys":
			if (!skip && !null_save)
				this.hotkey.load(raw_content);
			// fallback wanted
		default:
			// check if text is empty (page deletion)
			if (!null_save && !can_be_empty && (raw_content === "")) {
				if (confirm(this.i18n.CONFIRM_DELETE.sprintf(current))) {
					this.plugins.delete_check(current);
					this.delete_page(current);
					this.disable_edit();
					back_or(this.config.main_page);
				}
				return;
			} else {
				var new_title = this.trim(d$("wiki_page_title").value);
				renaming = (this.old_title !== new_title);
				// do not glitch when creating a new page
				if (was_ghost)
					this.prev_title = new_title;
				// here the page gets actually saved
				if (!null_save || renaming) {
					// disallow empty titles
					if (renaming && !this.valid_title(new_title))
						return false;
					// rename before eventually setting the changes
					if (renaming) {
						if (!this.rename_page(this.old_title, new_title))
							return false;
					}
					// actually set text only if it was changed
					if (!null_save)
						this.pager.set_body(new_title, raw_content);
					var maybe_plugin;
					if (this.is_menu(new_title)) {
						if (renaming || !null_save) {
							this.refresh_menu_area();
							back_to = this.prev_title;
						}
						maybe_plugin = false;
					} else {
						back_to = new_title;
						maybe_plugin = true;
					}
					// update the plugin if this was a plugin page
					// NOTE: plugins are not allowed to be renamed, so
					// old title is equal to new title
					if (maybe_plugin) {
						var _pfx = "WoaS::Plugins::";
						if (new_title.substr(0, _pfx.length) === _pfx) {
							// we do not directly call _update_plugin because
							// plugin does not exist before creation so disabling it
							// would fail
							this.plugins.disable(new_title.substr(_pfx.length));
							this.plugins.enable(new_title.substr(_pfx.length));
						}
					}
				} else { // not renaming and not changed text
					back_to = this.prev_title;
					// do not glitch when creating a new page
				}
			}
	}

	var saved = current;
//	if (back_to !== null)
		this.set_current(back_to, true);
/*	else { // used for CSS editing
		back_or(this.config.main_page);
		//TODO: refresh mts?
	} */
	if (!null_save || renaming)
		this.refresh_menu_area();
	this.disable_edit();
	if (!null_save || renaming)
		this.save_page(saved);
};

woas.save_page = function(title) {
	return this.save_page_i(this.page_index(title));
};

woas.save_page_i = function(pi) {
	// update the modified time timestamp (only when not in dev-mode)
	if (!this.tweak.edit_override)
		page_mts[pi] = this.config.store_mts ? Math.round(new Date().getTime()/1000) : 0;
	// this is the dummy function that will allow more efficient file saving in future
	if (this.config.cumulative_save) {
		// add the page to the bucket, if it isn't already in
		if (!this.save_queue.length) {
			this.save_queue.push(pi);
			this.menu_display("save", true);
		} else {
			if (this.save_queue.indexOf(pi)==-1)
				this.save_queue.push(pi);
		}
		woas.log("save_queue = ("+this.save_queue+")");	// log:1
		return true;
	}
	return this.commit([pi]);
};

woas.create_breadcrumb = function(title) {
	var tmp=title.split("::");
	if (tmp.length===1)
		return title;
	var s="", partial="", js="";
	for(var i=0;i < tmp.length-1;i++) {
		// editing is active
		if (this.ui.edit_mode)
			s+= tmp[i]+" :: ";
		else {
			partial += tmp[i]+"::";
			js = "go_to('"+this.js_encode(partial)+"')";
			s += "<"+"a title=\""+this.xhtml_encode(tmp[i])+"\" href=\"javascript:"+js+"\" onclick=\""+js+"; return false;\">"+tmp[i]+"<"+"/a> :: ";
		}
	}
	// add page title
	return s+tmp[tmp.length-1];
};

/*** src/extensions.js ***/
// @module scripting
// @description manages custom scripts declared in menu and main page
// All custom scripts can be defined in one of the two
woas.scripting = {
	menu: [],	// scripts active in menu
	page: [],	// scripts active in page
	_menu_stacked: 0,	// number of elements which shall be cleared
	_page_stacked: 0,	// ''
	
	// remove all scripts from specified array (can be 'menu' or 'page')
	clear: function(which) {
		for(var i=0;i < this["_"+which+"_stacked"];++i) {
			woas.dom.remove_script(which, i);
		}
		this["_"+which+"_stacked"] = 0;
	},
	
	// add a custom script for later activation
	add: function(which, content, external) {
		this[which].push([content, external]);
	},
	
	activate: function(which) {
		for(var i=0;i < this[which].length;++i) {
			if (woas.dom.add_script(which, i, this[which][i][0], this[which][i][1]))
				++this["_"+which+"_stacked"];
		}
		// free memory
		this[which] = [];
	},
	
	remove: function(which, index) {
		if (woas.dom.remove_script(which, index)) {
			--this["_"+which+"_stacked"];
			return true;
		}
		return false;
	}
	
};

// @module 'plugins'
woas.plugins = {
	
	// flag set by last call to get()
	is_external: false,
	_all: [],
	_active: [],	// list of enabled plugins
	
	get: function(name) {
		this.is_external = false;
		var text = woas.pager.get("WoaS::Plugins::"+name);
		// check if this is an external page reference
		if (name.charAt(0) === '@') {
			// hack for external files loading at run-time
			// each source file specified in a new line as a definition with flags for different browsers
			var uris = this._parse_external(text);
			if (uris !== null) {
				// we got it
				this.is_external = true;
				text = uris;
			}
		}
		return text;
	},
	
	_parse_external: function(text) {
		var uris=[], uri_def, p, sb;
		text = text.split("\n");
		for(var i=0;i < text.length;++i) {
			uri_def = woas.trim(text[i]);
			if (!uri_def.length)
				continue;
			// check this URI for validity
			p = uri_def.indexOf("=");
			if (p === -1)
				continue;
			sb = this._craft_object(uri_def.substr(p+1));
			if (sb === null) // no mode was activated by this definition
				continue;
			sb.src = woas.trim(uri_def.substr(0,p));
//			woas.log(sb.src+" " +sb.is_inline+" "+sb.is_async);
			// finally add it to basket
			uris.push(sb);
		}
		if (uris.length == 0)
			woas.log("no valid source URIs found");
		return uris;
	},
	
	reIncludeDef: new RegExp("([\\+@])(\\*|([a-zA-Z0-9_]+)(\\([0-9\\.]+[-\\+]?\\))?)", "g"),
	
	// parse definitions for browser include type
	_craft_object: function(s) {
		var sb = {
			is_inline: false,
			is_async: false
		}, inline_init = false, async_init = false;
		s.replace(this.reIncludeDef, function(str, sym, full_browser_str,browser_str, version) {
			// a handy shortcut
			switch (browser_str) {
				case "ff":
					browser_str = "firefox";
				break;
				// not supported
				case "ie6": case "ie8": case "firefox2": case "firefox3": case "firefox_new":
					woas.log("Invalid browser token: "+browser_str);
					return;
			}
			// check the catch-all case
			if (full_browser_str === '*') {
				if (sym === '+') {
					if (!inline_init) {
						inline_init = sb.is_inline = true;
					} else woas.log("Ignored catch-all inline because a restricting criteria was already specified");
				} else { // if (sym === '@')
					if (!async_init) {
						async_init = sb.is_async = true;
					} else woas.log("Ignored catch-all async because a restricting criteria was already specified");
				}
				return;
			}
			// check that this browser token is valid
			var t = typeof woas.browser[browser_str];
			if (t == "undefined") {
				woas.log("Invalid browser token: "+browser_str);
				return;
			}
			// parse the symbol if that browser token is active and version matches
			if (woas.browser[browser_str]) {
				// make a version check only if necessary
				var its_ok = false;
				if (version.length !== 0) {
					// check that we have a version string, before all
					if (t != "string") {
						woas.log("No version string for browser token \""+browser_str+"\"");
						return;
					}
					// remove parenthesis
					version = version.substr(1, version.length-2);
					// check the last character
					var lc = version.charAt(version.length-1);
					if (lc === '+') { // match version and above
						its_ok = (woas.strnatcmp(woas.browser[browser_str], version) >= 0);
					} else if (lc === '-') { // match version and below
						its_ok = (woas.strnatcmp(woas.browser[browser_str], version) <= 0);
					} else { // plain version match
						its_ok = (woas.strnatcmp(woas.browser[browser_str], version) == 0);
					}
				} else its_ok = true;
				// version was not OK
				if (!its_ok) return;
				// operate the operator
				if (sym === '+') {
					sb.is_inline = true;
					sb.is_async = false;
					inline_init = true;
				} else {
					sb.is_async = true;
					sb.is_inline = false;
					async_init = true;
				}
			}
			// continue with next token
		});
		// if nothing was specified, fail
		if (!inline_init && !async_init) return null;
		return sb;
	},

	// disable one single plugin
	disable: function(name) {
		var i = this._active.indexOf(name);
		if (i !== -1) {
			_mapped_name = this._mapping(name);
			// attempt removing the script block and fail otherwise
			if (!woas.dom.remove("plugin", _mapped_name))
				return false;
			// external plugin, try to remove the lib blocks created
			if (name.charAt(0) === '@') {
				var p = this.get(name);
				if (this.is_external && (p.length>1)) { // *special* external plugins
					for(var i=0;i < p.length;++i) {
						woas.dom.remove_script("lib", _mapped_name+'_'+i);
					}
				}
			}
			this._active.splice(i, 1);
			return true;
		}
		return false;
	},

	update: function(name) {
		return this.disable(name) && this.enable(name);
	},
	
	_internal_add: function(name, s) {
		if (s.is_async) {
			if (woas.dom.add_script("plugin", this._mapping(name), s.src, true)) {
				this._active.push( name );
				return true;
			}
		} else /*if (s.is_inline) */ { // create an inline javascript (slower)
			var ct = woas.load_file(woas.ROOT_DIRECTORY+woas.fix_path_separators(s.src)),
				t = (typeof ct);
			// write some nice message
			if (t.toLowerCase() != "string") {
				woas.log("could not load inline javascript source "+s.src);
				return false;
			} else {
				// add the inline block
				if (woas.dom.add_script("plugin", this._mapping(name), ct, false)) {
					this._active.push( name );
					return true;
				}
			}
		}
		// failed adding DOM script
		return false;
	},

	// enable a single plugin
	enable: function(name) {
		if (this._active.indexOf(name) !== -1) {
			woas.log("BUG: Plugin "+name+" is already active");
			return true;
		}
		// generate the script element
		var p = this.get(name);
		if (this.is_external) { // *special* external plugins
			// single reference, do not create script block
			if (p.length === 1) {
				return this._internal_add(name, p[0] );
			} else {
				var js="";
				for(var i=0;i < p.length;++i) {
					if (p[i].is_async)
						js += 'woas.dom.add_script("lib", "'+this._mapping(name)+'_'+i+"\", \""+woas.js_encode(p[i].src)+"\", true);\n";
					else /*if (p[i].is_inline) */ {
						var ct = woas.load_file(woas.ROOT_DIRECTORY+woas.fix_path_separators(p[i].src)),
							t = (typeof ct);
						// write some nice message
						if (t.toLowerCase() !== "string")
							js += "/* woas.load_file(\"::/"+p[i].src+"\") returned "+ct+" ("+t+") */\n";
						else {
							// add the loaded code
							js += ct; ct = null;
						}
					} // no other symbols for now
				} //efor
				// finally add the real script block
				if (woas.dom.add_script("plugin", this._mapping(name), js, false)) {
					this._active.push( name );
					return true;
				}
				return false;
			}
		}
		// normal plugins
		if (woas.dom.add_script("plugin", this._mapping(name),
					p, false)) {
			this._active.push( name );
			return true;
		}
		return false;
	},
	
	// remove DOM object for all active plugins
	clear: function() {
		for(var i=0,it=this._active.length;i < it;++i) {
			// remove the DOM object
			this.dom.remove_script("plugin", this._mapping(this._active[i]));
		}
		// reset array
		this._active = [];
		this._all = [];
	},
	
	// map a plugin name to an unique name
	_mapping_cache:[],
	_mapping: function(name) {
		var i = this._mapping_cache.indexOf(name);
		if (i == -1) {
			i = this._mapping_cache.length;
			this._mapping_cache.push(name);
		}
		// return more descriptive names
		if (woas.config.debug_mode)
			return woas._unix_normalize(name).replace("@", '')+"_"+i;
		return i;
	},
	
	load: function() {
		//TODO: get plugins configuration

		// get list of plugins
		var _pfx = "WoaS::Plugins::", l=_pfx.length, name;
		for(var i=0,it=page_titles.length;i < it;++i) {
			if (page_titles[i].substr(0, l) === _pfx) {
				name = page_titles[i].substr(_pfx.length);
				// generate the script element
				this.enable(name);
				this._all.push(name);
			}
		} //efor
	},
	
	list: function() {
		var pt = this._all.length;
		if (pt === 0)
			return "\n\n/No plugins installed/";
		var pg=[];
		for(var i=0;i < pt;++i){
			pg.push("* [[WoaS::Plugins::"+this._all[i]+"|"+this._all[i]+"]]"+
					//TODO: some CSS for the plugin actions
					"&nbsp;&nbsp;[[Javascript::woas.plugins.remove('"+this._all[i]+"')|Delete]]"+
					"&nbsp;&nbsp;[[Javascript::woas._edit_plugin('"+this._all[i]+"')|Edit...]]"+
					"\n");
		}
		return "\n\n"+woas._simple_join_list(pg);
	},
	
	describe_external: function(uris) {
		// show a list of external sources
		var ntext = "<"+"p>This plugin is made up of the following external sources:<"+"/p><"+"ul>", uri;
		for(var i=0;i < uris.length;++i) {
			ntext += "<"+"li>" + "<"+"big>"+(uris[i].is_inline ? "(inline)" : "(external)")+"<"+"/big>&nbsp;";
			ntext += "<"+"a href=\""+uris[i].src+"\" target=\"_blank\">"+uris[i].src+"<"+"/a><"+"/li>\n";
		}
		return ntext+"<"+"/ul>";
	},
	
	// if given page name is a plugin, disable it
	// used when deleting pages
	delete_check: function(pname) {
		var _pfx = "WoaS::Plugins::";
		if (pname.substr(0, _pfx.length) === _pfx)
			this.disable(pname.substr(_pfx.length));
	},

	remove: function(name) {
		var page_name = "WoaS::Plugins::"+name;
		if (!confirm(woas.i18n.CONFIRM_DELETE.sprintf(page_name)))
			return false;
		// first we attempt to disable it, ignoring failure (because it could just not be active)
		this.disable(name);
		woas.delete_page(page_name);
		// remove from array
		var i = this._all.indexOf(name);
		this._all.splice(i,1);
		if (current === "WoaS::Plugins") {
			//HACK: reload plugins
			woas.setHTMLDiv(d$("woas_wiki_area"), woas.parser.parse(woas.get_text("WoaS::Plugins") + this.list()));
		}
		return true;
	}

};

// @module hotkey
woas.hotkey = {
	
	all: {
		"save":		"s",
		"edit":		"e",
		"print":	"p",
		"help":		"h",
		"goto":		"g",
		"cancel":	0x1b,
		"back":		0x8
	},
	cached_default: null,
	custom_accesskeys: [],

	_update_accesskeys: function(new_custom_accesskeys) {
		var ak, a, b, at, bt;
		// we store the length of old access keys before looping because
		// other entries might be added during the cycles
		bt=this.custom_accesskeys.length;
		for(a=0,at=new_custom_accesskeys.length;a < at;++a) {
			found = false;
			for (b=0;b < bt;++b) {
				// access key already exists
				if (this.custom_accesskeys[b].key === new_custom_accesskeys[a].key) {
					found = true;
					break;
				}
			}
			// proceed to addition
			if (!found) {
				ak = document.createElement("a");
				this._hook_fn(ak, new_custom_accesskeys[a].fn);
				ak.accessKey = new_custom_accesskeys[a].key;
				// store the new access key
				this.custom_accesskeys.push({"fn":new_custom_accesskeys[a].fn,"key":new_custom_accesskeys[a].key,
											 "obj":ak});
				d$("woas_custom_accesskeys").appendChild(ak);
			}
		}
		// (3) clear the div content if no custom access key is there (just for safety)
		if (this.custom_accesskeys.length === 0)
			this.setHTML(d$("woas_custom_accesskeys"), "&nbsp;");
	},
	
	_hook_fn: function(obj, fn) {
		woas.log("setting onclick for "+fn);
		if (woas.browser.gecko || woas.browser.webkit)
			obj.setAttribute("onclick", fn+"()");
		else //HACK
			obj.onclick = eval(fn);
	},

	// return the default hotkeys/key bindings
	_cache_default: function() {
		if (this.cached_default === null) {
			this.cached_default="";
			var k;
			for(var hkey in this.all) {
				k = this.all[hkey];
				switch(typeof k) {
					case "string":
					break;
					default: // Number
						k = "0x"+k.toString(16);
				}
				this.cached_default += "$"+hkey.toUpperCase()+"\t"+k+"\n";
			}
		}
		return this.cached_default;
	},

	validate: function(k) {
		// validate hexadecimal hotkey
		if (k.substr(0, 2) == "0x") {
			k = parseInt(k.substr(2), 16);
			if (!k)
				return null;
			return k;
		}
		// validate single ASCII character
		if (k.length>1)
			return null;
		return k;
	},

	load: function(s) {
		var new_custom_accesskeys=[];
		// identify valid alias lines and get the key binding/hotkey
		s.replace(this.reHotkeys, function(str, hkey, lambda, binding) {
			// check that binding is a valid key
			binding = woas.hotkey.validate(binding);
			if (binding === null) {
				woas.log("Skipping invalid key binding for hotkey "+hkey);	//log:1
				return;
			}
			// associate a custom key binding
			if (hkey === "CUSTOM") {
				// store the custom definition for later update
				lambda = lambda.substr(1, lambda.length-2);
				new_custom_accesskeys.push({"fn":lambda, "key":binding});
			} else {
				// convert hotkey to lowercase
				hkey = hkey.toLowerCase();
				// check that hotkey exists
				if (typeof woas.hotkey.all[hkey] == "undefined") {
					woas.log("Skipping unknown hotkey "+hkey);	//log:1
					return;
				}
				// associate hotkey and key binding
				woas.hotkey.all[hkey] = binding;
			}
		});
		// once finished loading hotkey definitions, associate them
		d$("woas_save_hl").accessKey = this.all.save;
		d$("woas_edit_hl").accessKey = this.all.edit;
		d$("woas_print_hl").accessKey = this.all.print;
		d$("woas_help_hl").accessKey = this.all.help;
		// set access key for goto feature
		new_custom_accesskeys.push({fn:"woas.cmd_go_to", key: this.all.goto});
		
		// (1) delete access keys which no more exist
		var found,a,b;
		for(a=0,at=this.custom_accesskeys.length;a < at;++a) {
			found = false;
			for (b=0,bt=new_custom_accesskeys.length;b < bt;++b) {
				if (this.custom_accesskeys[a].key === new_custom_accesskeys[b].key) {
					found = true;
					// access key element was found, update the associated function (if necessary)
					if (this.custom_accesskeys[a].fn !== new_custom_accesskeys[b].fn) {
						this._hook_fn(this.custom_accesskeys[a].obj, new_custom_accesskeys[b].fn);
					}
					break;
				}
			}
			// proceed to removal
			if (!found) {
				d$("woas_custom_accesskeys").removeChild(this.custom_accesskeys[a].obj);
				delete this.custom_accesskeys[a].obj;
				this.custom_accesskeys.splice(a,1);
				--at;
			}
		}
		// (2) add new access keys
		this._update_accesskeys(new_custom_accesskeys);
	},
	
	reHotkeys: /^\$([A-Za-z0-9_]{2,})(\([A-Za-z0-9_]+\))?\s+([\S]+)\s*$/gm
};

// call once during code setup to store the current default hotkeys
woas.hotkey._cache_default();

woas._edit_plugin = function(name) {
	if (go_to("WoaS::Plugins::"+name))
		woas.edit_page(current);
};

var reAliases = /^(\$[A-Za-z0-9_]{2,})\s+(.*?)$/gm;
// match all aliases defined in a page
woas._load_aliases = function(s) {
	this.aliases = [];
	if (s==null || !s.length) return;
	s.replace(reAliases, function(str, alias, value) {
		// save the array with the alias regex and alias value
		var cpok = [ new RegExp(RegExp.escape(alias), "g"), value];
		woas.aliases.push(cpok);
	});
};

woas.aliases = [];

woas.title_unalias = function(aliased_title) {
	// apply aliases on title, from newest to oldest
	for(var i=0,l=this.aliases.length;i < l;++i) {
		aliased_title = aliased_title.replace(this.aliases[i][0], this.aliases[i][1]);
	}
	return aliased_title;
};

woas.macro = {
	// macro definition regex
	reMacroDef: /^(%?[A-Za-z0-9_\.]+)\s*(\(.*?\))?\s*:([\s\S]*)$/,

	// macro syntax plugin code adapted from FBNil's implementation
	parser: function(text){
		// standard macro object
		var macro = { "reprocess": false, "text": text };
		// match macro definition/call
		var M=text.match(this.reMacroDef);
		// if no double colon declaration was found, then do not process anything
		if (M !== null) {
			var fn = M[1];
			// check validity of macro name
			if ((fn.indexOf("..") !== -1) || (fn.charAt(0) === '.') ||
				(fn.charAt(fn.length-1) === '.')) {
					woas.log("Invalid macro name: "+fn);	//log:1
					return macro;
			}
			// check that this is not a macro definition request
			if (fn.charAt(0) === '%') {
				fn = fn.substr(1);
				// when macro is not defined, define it
				if (this.create(fn, M[2], M[3])) {
					// we totally remove the block
					macro.reprocess = false;
					macro.text = "";
		//			macro.text = "<!-- defined "+fn+" macro -->";
				} else { // set some error message
					macro.reprocess = false;
					macro.text = woas._make_preformatted(M[0], "color:red;font-weight:bold");
				}
				return macro;
			}
			var fi = this.names.indexOf(fn);
			if (fi !== -1) {
				macro.text = M[3];
				// if we have no parameters, direct call function
				var pl, rv;
				if (typeof M[2] == "undefined")
					pl = 0;
				else pl = M[2].length;
				try {
					if (pl === 0)
						(this.functions[fi])(macro);
					else {
						// inline insertion of parameters
						// cannot use woas.eval because we need context for 'macro'
						rv = eval( "(woas.macro.functions["+fi+"])"+
									"(macro,"+M[2].substr(1,pl-2)+");"
							);
					}
				}
				catch(e) {
					woas.log("Error during macro execution: "+e);
				}
				// analyze return value
				if (typeof rv == "undefined")
					woas.log("WARNING: "+this.names[fi]+" did not return any value");
				else {
					// when macro returns false we automatically highlight it
					if (!rv) {
						macro.reprocess = false;
						macro.text = woas._make_preformatted(M[0], "color:red;font-weight:bold");
					}
				}
			} else {
				woas.log("Undefined macro "+fn);	//log:1
				macro.text = woas._make_preformatted(macro.text, "color:red");
			}
		}
		return macro;
	},

	// this is the function to be called to register a  macro
	// each macro function must accept a macro object as parameter and modify
	// such object (it is always passed by reference)
	register: function(fn_name, fn_object) {
		if (this.names.indexOf(fn_name) != -1) {
			woas.log("cannot redefine macro "+fn_name); //log:1
			return false;
		}
		this.names.push(fn_name);
		this.functions.push(fn_object);
		return true;
	},

	// some default macros
	default_macros: {
		// advanced transclusion: each newline creates a parameter
		"include" : function(m) {
			var params = m.text.split("\n");
			// embedded transclusion not supported
			if (!params.length || !woas.page_exists(params[0]) || woas.is_embedded(params[0]))
				return false;
			var nt = woas.get_text_special(params[0]);
			if (nt === null)
				return false;
			if (params.length) { // replace transclusion parameters
				nt = nt.replace(/%\d+/g, function(str) {
					var paramno = parseInt(str.substr(1));
					if (paramno < params.length)
						return params[paramno];
					else
						return str;
					} );
			}
			m.text = nt;
			m.reprocess = true;
			return true;
		}
	},

	// the name of macros as they are called from wiki text
	names: [],
	// the actual function objects being called
	functions: [],

	// a reduced charset for javascript argument names
	reFindArgDef: /([a-z0-9_]+)\s*,/gi,
	reFindArgDefLast: /([a-z0-9_]+)\s*$/gi,
	create: function(fn_name, fn_params, fn_code) {
		// duplicated from register function
		if (this.names.indexOf(fn_name) != -1) {
			woas.log("cannot redefine macro "+fn_name); //log:1
			return false;
		}
		// prepare for passing other params
		// parameter definitions can be very limited in charset
		var real_params=[], other_params = "";
		if (((typeof fn_params).toLowerCase() == "string") && fn_params.length) {
			// remove parenthesis
			fn_params = fn_params.substr(1, fn_params.length-2);
			this.reFindArgDef.lastIndex = 0;
			fn_params.replace(this.reFindArgDef, function(str, pname) {
				real_params.push(pname);
			});
			
			this.reFindArgDefLast.lastIndex = this.reFindArgDef.lastIndex;
			fn_params.replace(this.reFindArgDefLast, function(str, pname) {
				real_params.push(pname);
			});
		}

		if (real_params.length)
			other_params = ","+real_params.join(",");
		else other_params = "";
		var obj = woas.eval("function (macro"+other_params+") {\n"+fn_code+"\n}", true);
		if (woas.eval_failed) {
			woas.log("cannot define function "+fn_name+": "+woas.eval_fail_msg); //log:1
			return false;
		}
		return this.register(fn_name, obj);
	},
	
	// code used to save backups of macro definitions
	// called by parser
	_backup: [],
	push_backup: function() {
		this._backup.push(this.names.slice(0));
		this._backup.push(this.functions.slice(0));
	},
	
	pop_backup: function() {
		this.functions = this._backup.pop();
		this.names = this._backup.pop();
	},
	
	has_backup: function() {
		return (this._backup.length !== 0);
	}

}; // end of woas.macro module

// some initialization
woas.macro.names.push("woas.include");
woas.macro.functions.push(woas.macro.default_macros.include);

/*** src/datalayer.js ***/
/*
 *  API for the data layer abstraction
*/

// @module lock
// provides locking facilities by using a file describing locked entries
woas.lock = {
	// hashmap with a lock entry for each active filename
	datasources : {},
	
	_reset : function() {
		// clear object
		this.datasources = {};
		//TODO: add one entry for each expected datasource
	},
	
	_generate_magic_lock : function() {
		return _random_string(10);
	},
	
	_load_locks : function() {
		// locking currently disabled
		return true;
		var lck_file = woas.ROOT_DIRECTORY + woas.config.wsif_ds + ".lock";
		// attempt reading the lock file index
		var lock_data = woas.load_file(lck_file);
		// fail in case of no loading API available
		if (lock_data === null)
			return false;
		var need_save = false;
		// the lock file does not exist, it's time to initialize it
		if (lock_data === false) {
			this._reset();
			// we will write the file
			need_save = true;
		} else {
			// check that
		}
		return true;
	},
	
	_update_locks : function() {
		//TODO: write the lock file index
		return true;
	},
	
	is_locked : function(filename) {
		// if lock is not defined, it is not active of course
		if (typeof this.datasources[filename] == "undefined")
			return false;
		return this.datasources[filename].active;
	},
	
	// called when editing some page which modifies one or more datasources
	// returns true if lock could be held successfully
	hold : function(filename, whom) {
		// locking currently disabled
		return true;
		// (1) each time there is an attempt to lock/unlock something we read the lock file index
		// and check if the datasource has been locked or if there have been changes to some lock
		if (!this._load_locks())
			return false;
		
		// (2) is this file already locked?
		if (this.is_locked(filename)) {
			var lock = this.datasources[filename];
			if (!confirm("%s was locked by %s at %s.\nDo you want to ignore this lock?\n\nWARNING: PRESS OK ONLY IF NOBODY ELSE IS EDITING".
						sprintf(filename, lock.owner, lock.when.toLocaleString()
						)))
				return false;
			// otherwise ignore lock
			lock = null;
		}
		
		// (3) re-create the lock
		this.datasources[filename] = {
			magic : this._generate_magic_lock(),		// magic is used to compare two locks
			owner : whom,								// author of locking
			when : new Date(),							// when locking happened
			active : true
		};
		
		// update the lock index
		return this._update_locks();
	},
	
	release : function(filename) {
		// locking currently disabled
		return true;
		// (1) each time there is an attempt to lock/unlock something we read the lock file index
		// and check if the datasource has been locked or if there have been changes to some lock
		if (!this._load_locks())
			return false;
		// (2) check if datasource is actually locked
		if (typeof this.datasources[filename] == "undefined") {
//			woas.log("BUG: NO LOCK exists for "+filename);	//log:0
			return false;
		}
		// unactive the lock object (but keep it to check if file was modified)
		this.datasources[filename].active = false;
		// update the lock index
		return this._update_locks();
	}
};

woas._wsif_ds_save = function(subpath, plist) {
	// if we have a native sub-path, trigger the WSIF datasource save
	if (subpath.length === 0)
		return;
	// always save in the root directory
	// code disabled since we always save the full backup
//	if (typeof plist != "undefined" )
//		done = this._native_wsif_save(path,	subpath, true, true, "", true, plist); else
	this._native_wsif_save(woas.ROOT_DIRECTORY, subpath, this.config.wsif_ds_lock,
							!this.config.wsif_ds_multi,	!this.config.wsif_ds_multi,
							this.config.wsif_author, true);
};

//API1.0: save all pages
woas.full_commit = function() {
	this._wsif_ds_save(this.config.wsif_ds, this.config.wsif_ds_lock);
	return this._save_to_file(true);
};

//API1.0: save WoaS configuration
woas.cfg_commit = function() {
	if (this.config.wsif_ds.length !== this._old_wsif_ds_len)
		this._wsif_ds_save(this.config.wsif_ds, this.config.wsif_ds_lock);
	return this._save_to_file(false);
};

//API1.0: save specific list of pages
// plist is a list of page indexes which need to be saved
woas.commit = function(plist) {
	this._wsif_ds_save(this.config.wsif_ds, this.config.wsif_ds_lock, plist);
	// performs full save, while the single page + global header could be saved instead
	return this._save_to_file(true);
};

//API1.0: delete specific list of pages
// plist is a list of page indexes which need to be saved (not allowed to be empty)
woas.commit_delete = function(plist) {
	// update only the native WSIF index (leaves back deleted pages)
	this._wsif_ds_save(this.config.wsif_ds, this.config.wsif_ds_lock, []);
	// performs full save, while the single page + global header could be saved instead
	return this._save_to_file(true);
};

//API1.0: event called after some page is being saved
// plist can be undefined if all pages were saved
woas.after_pages_saved = function(plist) {
	// we assume that everything was saved
	if (typeof plist == "undefined") {
		this.save_queue = [];
		return;
	}
	// we remove each of the saved pages from queue (needs TESTING!)
	for(var i=0,l=plist.length;i<l;i++) {
		var p = this.save_queue.indexOf(plist[i]);
		if (p !== -1)
			this.save_queue.splice(p,1);
	}
};

//API1.0: event called when the config was successfully saved
woas.after_config_saved = function() {
	woas.cfg_changed = false;
};

/*** src/i18n.js ***/

// internationalization language resources
woas.i18n = {
	ACCESS_DENIED: "Access denied to page '%s'",
	ALT_BROWSER_INPUT: "Please specify full path to file '%s' because your browser does not allow such path to be read directly for security reasons.",
	ASK_MENU_LINK: "Do you want to add a link into the main menu?",
	B64_REQ: " (requires %s due to base64 encoding)",
	CANCEL_EDITING:"Changes to this page will not be saved.",
	CONFIRM_OVERWRITE: "Page \"%s\" already exists. Overwrite it?",
	CONFIRM_DELETE:"Are you sure you want to DELETE page '%s'?",
	CONFIRM_DELETE_ALL1: "Are you going to ERASE all your pages?",
	CONFIRM_DELETE_ALL2: "This is the last confirm needed in order to ERASE all your pages.\n\nALL YOUR PAGES WILL BE LOST\n\nAre you sure you want to continue?",
	CONFIRM_DELETE_FILE: "Are you sure you want to delete this file?",
	CONFIRM_DELETE_IMAGE: "Are you sure you want to delete image '%s'?",
	CONFIRM_EXPORT: "Do you want to export %s in the below specified path?",
	CONFIRM_LOCK: "Do you want to use the last password entered to lock this page '%s'?",
	CONFIRM_LOCK_LAST: "The password was last time used on page: %s",
	CONFIRM_READ_ONLY: "Are you sure you want to set this WoaS as read-only? You will have to manually edit the file to revert this change.",
	CONFIRM_REMOVE_ENCRYPT: "Do you want to remove encryption for page '%s'?",
	DELETE:"Delete page:",
 	DELETE_FILE:"Delete embedded file",
 	DELETE_IMAGE:"Delete embedded image",
	DELETE_PAGE_PROMPT: "Delete page:",
	DISPLAY_FULL_FILE: "Display full file",
	DUP_NS_ERROR: "Cannot duplicate into File:: or Image:: namespace!",
	EDITING:"Editing %s",
	EMPTY_TITLE: "Pages with empty title are not allowed",
	ERR_MARKER: "%s marker not found!",
	ERR_NO_PWD: "No password set for decryption of page '%s'\nPlease click the key icon and enter a password",
	ERR_PAGE_NS: "You cannot create a page as a namespace",
	ERR_RESERVED_NS: "Namespace '%s' is reserved",
	ERR_SEL_FILE: "A file must be selected",
	ERR_VAR_MARKER: "Marker variable not found!",
 	EXPORT_FILE:"Export file",
 	EXPORT_IMAGE:"Export image",
	EXPORT_OK: "%d/%d pages exported successfully",
	FAILED_ERASE:"Saving the erased WoaS has failed, please reload it otherwise you will be working with unconsistent data",
	FILE_DISPLAY_LIMIT: "Only the first 1024 bytes are displayed",
	FILE_SELECT_ERR: "A file must be selected",
	FILE_SIZE: "File size",
	HEIGHT: "Height",
	IMPORT_INCOMPAT: "Incompatible version: %s",
	IMPORT_OLD_VER: "If this is a WoaS older than v0.9.6B, you should import it with WoaS 0.11.x",
	IMPORT_OK: "%s pages imported successfully (%d pages skipped)",
	IMPORT_UNRECON: "Unrecognized format",
	INSERT_NEW:"Insert new page title",
	INVALID_ALIAS: "Invalid alias '%s', ignoring it",
	INVALID_DATA:"Invalid collected data!",
	INVALID_SPECIAL: "Invalid special page.",
	INVALID_TITLE: "Title cannot contain ':::' and any of these characters:\n< > | [ ] { }",
	LAST_MODIFIED: "Last Modified: ",
	LOADING: "Loading Wiki on a Stick...",
	LOAD_ERR: "Cannot load specified file",
	MIME_TYPE: "Mime type",
	NO_ERROR: "No error",
	NO_JAVA:"It was not possible to use TiddlySaver java applet nor direct Java saving.",
	NO_TIDDLY_SAVER:"The TiddlySaver java applet was not available.",
	PAGE_EXISTS: "A page with title '%s' already exists!",
	PAGE_NOT_EXISTS: "Page does not exist: ",
	PAGE_NOT_FOUND: "Page not found. Do you want to create it?",
	PRINT_MODE_WARN: "Sorry, you cannot browse the wiki while in print mode",
	PWD_QUERY: "Please enter a password",
	READ_ONLY: "This Wiki on a Stick is read-only",
	SAVE_ERROR:"Unable to save file '%s'.",
	SERVER_MODE: "You are using Wiki on a Stick on a webserver; your changes cannot be saved neither through the webserver or locally.\n\n"+
					"The correct usage of Wiki on a Stick is local, so you should use a local copy of this page to exploit the save features.\n"+
					"All changes made to this copy of Wiki on a Stick will be lost.",
	STATIC_NOT_FOUND: "Static page '%s' not found!",
	TIDDLY_HELP:"Please check that TiddlySaver.jar is in the same directory of this WoaS and that you have enabled Java permissions for it",
	TOO_LONG_TITLE: "Maximum title length is %d characters",
	UNSPECIFIED_JAVA_ERROR: "Please check the Java console for errors regarding last operation and check that it's not due to your browser/java restrictions",
	UNSUPPORTED_BROWSER: "Your browser might not be supported, please report a bug with your UserAgent string:\n%s",
	WIDTH: "Width",
	WRITE_PROTECTED: "Sorry, this Wiki on a Stick is already write-protected",
	WSIF_BAD_HV: "Could not locate end of header value",
	WSIF_NO_HN: "Could not locate header name",
	WSIF_NO_VER: "Could not read WSIF version",
	WSIF_NS_VER: "WSIF version %s not supported!",
	CANNOT_LOCK_RESERVED: "Cannot lock a page in a reserved namespace",
	CONTINUE_WAIT_LOAD: "The loading process seems stuck.\nPlease click OK to keep waiting or Cancel to break.",
	MODE_NOT_AVAIL: "File mode 0x%s is not available on this browser"
};

// do not use any copyrighted wordlist here
woas.i18n.common_words = ['the','of','to' ,'and' ,'a' ,'in' ,'is' ,'it' ,'you' ,'that' ,'he' ,'was' ,'for','on' ,'are' ,'with' ,'as' ,'I' ,'his' ,'they' ,'be' ,'at' ,'one' ,'have' ,'this' ,'from' ,'or' ,'had' ,'by' , 'an', 'all' ];

/*** src/import.js ***/
// @module importer

/* NOTES ABOUT PREVIOUS VERSIONS
		0.12.0:
			* introduced some config options: new_tables_syntax, store_mts, folding_style
			* introduced plugins (which deprecate WoaS::Bootscript)
			* dropping support for all WoaS before 0.9.6B
		0.11.7:
			* 'nav_history' config option replaces 'open_last_page'
		0.11.2:
			* introduced parsing mechanism which does not mess with var declarations inside JavaScript strings
			* introduced WoaS::Hotkeys, WoaS::CSS::Core, WoaS::CSS::Custom
			* introduced woas.config.safe_mode option
			* introduced woas.config.wsif_ds* options
		0.10.7:
			* introduced WoaS::Plugins and changed WoaS::Bootscript page type from embedded to normal
		0.10.0:
			* introduced page_mts for global page modified timestamp
		0.9.7:
			* introduced WoaS::Aliases
*/

woas.importer = {
//public:
	pages_imported: 0,
	total: 0,

//private:
	// property names used to retrieve default values from stored bitmask
	_settings_props: ["i_comment_js", "i_comment_macros", "i_woas_ns",
	// the last 3 options are ignored for WSIF import
					"i_config", "i_styles", "i_content"],
	// the overwrite option covers bits 6,7
	_OVR_ID: 6,
	// options
	i_config: true,					// import configuration (XHTML only)
	i_styles: false,				// import stylesheet (XHTML only)
	i_content: true,				// import content pages (XHTML only)
	i_comment_js: true,				// disable 'script' tags
	i_comment_macros: true,			// disable macro blocks '<<<...>>>'
	i_woas_ns: true,				// import pages from WoaS:: namespace
	i_overwrite: 1,					// overwrite mode (0 - erase, 1 - ignore, 2 - overwrite, 3 - ask)

	// used internally
	new_main_page: null,
	current_mts: null,
	pages: [],			// imported page objects array
	_reference: [],		// linear array containing page id or null, used privately by _get_import_vars()
	
	// runtime dynamic update variables
	_plugins_update: [],
	_plugins_add: [],
	_update_css: false,
	_update_hotkeys: false,
	_update_aliases: false,
	_bootscript_code: "",

	_fix_mts_val: function(mts, old_version) {
		// we did not have a timestamp before 0.10.0
		if (old_version < 100)
			return this.current_mts;
		// will catch the 'undefined' ones also
		if (!mts)
			return 0;
		// fixup the mts value in some old buggy version
		if ((old_version===100) || (old_version===101)) {
			// ignore the old null value
			if (mts === 0x4b61cbad)
				mts = 0;
		}
		return mts;
	},
	
	_filter_by_title: function(title) {
		// we are not using is_reserved() because will be inconsistant in case of enabled edit_override
		// check pages in WoaS:: namespace
		if (title.substr(0,6) === "WoaS::") {
			// can we import from WoaS namespace?
			if (!woas.importer.i_woas_ns)
				return false;
			// never overwrite help pages with old ones
			if (title.indexOf("WoaS::Help::") === 0)
				return false;
			// skip other core WoaS:: pages
			if (woas.static_pages2.indexOf(title) !== -1)
				return false;
			if (title === "WoaS::Custom::CSS")
				// custom CSS is allowed only when importing CSS
				return woas.importer.i_styles;
			
			// here we allow Plugins, Aliases, Hotkeys
			
			return true;
		} else if (title.substr(0, 9) === "Special::") {
			// always skip special pages and consider them system pages

			return false;
		}
		
		// if not on bad list, it's OK
		return true;
	},

	// function used to collect variables
	_get_import_vars: function(data, ignore_array, old_version) {
		var jstrings=[], fail = false;
		// (1) take away all javascript strings (most notably: content and titles)
		// WARNING: quoting hacks lie here!
		data = data.replace(/\\'/g, ":-"+woas.parser.marker).replace(this.reJString, function (str) {
			// restore quotes
			jstrings.push(str.substr(1, str.length-2).replace(woas.importer.reRequote, "\\'"));
			return woas.parser.marker+":"+(jstrings.length-1).toString();
		});
		// (2) rename the variables
		data.replace(/([^\\])\nvar\s+(\w+)\s*=\s*([^;]+);/g, function (str, $1, var_name, definition) {
			if (fail) return;
			// it must not be in array
			if (ignore_array.indexOf(var_name) !== -1)
				return;
			if (
					// main_page was not in config object for 0.10.8 and below
					(old_version <= 108) && (var_name === "main_page") ) {
				woas.importer.new_main_page = woas.eval(definition.replace(woas.importer.reJStringRep,
								function (str, id) { return "'"+jstrings[id]+"'";}
							), true);
				return;
			}
			// the rest of variables are for content, so exit if we don't want content
			if (!woas.importer.i_content)
				return;
			// save evaluation if we don't want last modified timestamp
			if (!woas.config.store_mts && (var_name === "page_mts"))
				return;
			
			// evaluate the real array
			var the_var = woas.eval(definition.replace(woas.importer.reJStringRep,
								function (str, id) { return "'"+jstrings[id]+"'";}
							), true);

			// OK, we want this variable, evaluate it
			switch (var_name) {
				case "page_titles":
					// titles come before other properties, so we create all objects now
					for(var i=0,it=the_var.length;i < it;++i) {
						// call the title filtering hook
						if (woas.importer._filter_by_title(the_var[i])) {
							woas.importer.pages.push( { title: the_var[i], attrs: 0,
												mts: (old_version > 97) ? 0 : (woas.config.store_mts ? woas.importer.current_mts : 0),
												body: null } );
							// add object by-ref
							woas.importer._reference.push( woas.importer.pages[woas.importer.pages.length-1] );
						} else // no page reference
							woas.importer._reference.push( null );
					}
				break;
				case "page_attrs":
					// consistency check
					if (the_var.length !== woas.importer._reference.length) {
						woas.log("ERROR: page attributes array is not consistent ("+the_var.length+" != "+woas.importer._reference.length+")");
						fail = true;
						break;
					}
					for(var i=0,it=the_var.length;i < it;++i) {
						// skip filtered pages
						if (woas.importer._reference[i] === null)
							continue;
						woas.importer._reference[i].attrs = the_var[i];
					}
				break;
				case "page_mts": // available only on 0.10.0 and above
					// consistency check
					if (the_var.length !== woas.importer._reference.length) {
						woas.log("WARNING: page timestamps array is not consistent ("+the_var.length+" != "+woas.importer._reference.length+")");
						break;
					}
					for(var i=0,it=the_var.length;i < it;++i) {
						// skip filtered pages
						if (woas.importer._reference[i] === null)
							continue;
						woas.importer._reference[i].mts = woas.importer._fix_mts_val(the_var[i], old_version);
					}
				break;
				case "pages":
					// consistency check
					if (the_var.length !== woas.importer._reference.length) {
						woas.log("ERROR: page bodies array is not consistent ("+the_var.length+" != "+woas.importer._reference.length+")");
						fail = true;
						break;
					}
					for(var i=0,it=the_var.length;i < it;++i) {
						// skip filtered pages
						if (woas.importer._reference[i] === null)
							continue;
						woas.importer._reference[i].body = the_var[i];
					}
				break;
				// silently ignore these
				case "backstack": case "current": break;
				default:
					woas.log("Ignoring unexpected variable "+var_name);
			} // switch
				
		});
		// finished importing variables, clear references array
		this._reference = [];
		// clear partial results on failure
		if (fail)
			this.pages = [];
		woas.log("get_import_vars() scanned "+this.pages.length+" page definitions");
	},

	// apply options i.e. javascript security settings
	_hotfix_on_import: function(NP) {
		// exit if no replace is needed
		if (!this.i_comment_js && !this.i_comment_macros)
			return;
		// only plain wiki and locked pages can be hotfixed
		if (NP.attrs > 1)
			return;
		// comment out all javascript blocks
		var snippets = [];
		// put away XHTML comments and nowiki blocks
		NP.body = NP.body.replace(reComments, function (str, $1, dynamic_nl) {
				var r = woas.parser.place_holder(snippets.length, "", dynamic_nl);
				snippets.push(str);
				return r;
		}).replace(reNowiki, function (str, $1, dynamic_nl) {
				var r = woas.parser.place_holder(snippets.length, "", dynamic_nl);
				snippets.push(str);
				return r;
		});
		if (this.i_comment_js)
			NP.body = NP.body.replace(reScripts, "<"+"disabled_script$1>$2<"+"/disabled_script>$3");
		if (this.i_comment_macros)
			NP.body = NP.body.replace(reMacros, "<<< Macro disabled\n$1>>>$2");
		// clear dynamic newlines
		NP.body = NP.body.replace(woas.parser.reNL_MARKER, "");
		// restore everything
		woas.parser.undry(NP, snippets);
	},
	
	// add directly without checking for duplicates
	//NOTE: will not set 'pi' property
	_inject_import_hook: function(page) {
		page_titles.push(page.title);
		pages.push(page.body);
		page_attrs.push( page.attrs );
		// during import timestamp is already fixed when originally reading the variable
		page_mts.push(page.mts);
		// set self reference
		page.i = pages.length-1;
		return true;
	},
	
	_core_import_hook: function(page) {
//		woas.log("Importing page "+page.title);	//log:0
		var pi = woas.page_index(page.title);
		if (pi === -1) { // new page title
			woas.importer._inject_import_hook(page);
			page.pi = -1;
		} else { // page already existing, overwriting
			if (woas.importer.i_overwrite === 1) {
				// ignore already-existing pages
				return false;
			} else if (woas.importer.i_overwrite === 3) {
				if (!confirm(woas.i18n.CONFIRM_OVERWRITE.sprintf(page.title)))
					return false;
			}
			page_titles[pi] = page.title;
			pages[pi] = page.body;
			page_attrs[pi] = page.attrs;
			page_mts[pi] = page.mts;
			page.i = page.pi = pi;
		}
		return true;
	},
	
	// normal import hook - shared for XHTML and WSIF import
	_import_hook: function(page) {
		var that = woas.importer;
		that._hotfix_on_import(page);

		that._core_import_hook(page);

		// take note of plugin pages and other special runtime stuff
		var _pfx = "WoaS::Plugins::";
		if (page.title.substr(0, _pfx.length) === _pfx) {
			// does plugin already exist?
			if (page.pi !== -1)
				that._plugins_update.push(page.title.substr(_pfx.length));
			else
				that._plugins_add.push(page.title.substr(_pfx.length));
		} else if (page.title === "WoaS::Aliases")
			// check if we need to update aliases and hotkeys
			that._update_aliases = true;
		else if (page.title === "WoaS::Hotkeys")
			that._update_hotkeys = true;
		else if (page.title === "WoaS::CSS::Custom")
			that._update_css = true;
		
		return true;
	},
	
	_clear: function() {
//		this.pages = [];
		this._update_css = false;
		this._update_aliases = false;
		this._update_hotkeys = false;
		this._plugins_add = [];
		this._plugins_update = [];
	},
	
	_old_version: null,
	_upgrade_content: function (P) {
		var that = woas.importer;
		// fix the old bootscript page
		if (that._old_version < 120) {
			if (P.title === "WoaS::Bootscript") {
				// convert old base64 bootscript to plain text
				if (that._old_version < 107)
					that._bootscript_code = woas.base64.decode(P.body);
				else
					that._bootscript_code = P.body;
				that.pages_imported++;
				woas.progress_status(that.pages_imported/that.pages.length);
				return false;
			}
		} // since 0.12.0 we no more have a bootscript page

		// check that imported image is valid
		if (P.attrs & 8) {
			// the image is not valid as-is, attempt to fix it
			if (!that.reValidImage.test(P.body)) {
				// do not continue with newer versions or if not base64-encoded
				if ((that._old_version>=117) || !woas.base64.reValid.test(P.body)) {
					woas.log("Skipping invalid image "+P.title);
					return false;
				}
				// we assume that image is double-encoded
				P.body = woas.base64.decode(P.body);
				// check again for validity
				if (!that.reValidImage.test(P.body)) {
					woas.log("WARNING: skipping invalid image "+P.title); //log:1
					return false;
				}
				woas.log("Fixed double-encoded image "+P.title); //log:1
			}
			// try to fix the 'undefined' mime type bug
			if (that._old_version < 120) {
				if (that.reImageBadMime.test(P.body))
					// attempt to find the correct mime
					P.body = "data:"+woas._guess_mime(P.title)+P.body.substr(14);
			}
		} // check images

		// fix the trailing nul bytes bug in encrypted pages
		// extended from 0.10.4 to 0.12.0 because was not fixed on new pages
		if ((that._old_version>=102) && (that._old_version<120)
			&& (P.attrs & 2)) {
			var rest = P.body.length % 16;
			if (rest) {
				woas.log("removing "+rest+" trailing bytes from page "+P.title); //log:1
				while (rest-- > 0) {P.body.pop();}
			}
		}
			
		// proceed to actual import
		if (that._import_hook(P)) {
			++that.pages_imported;
			woas.progress_status(that.pages_imported/that.pages.length);
		}
		return true;
	},
	
	_import_content: function(old_version) {
		this._old_version = old_version;
		for (var i=0; i < this.pages.length; i++) {
			this._upgrade_content(this.pages[i]);
		}
	},
	
	do_import: function(ct) {
		// initialize
		this.pages_imported = 0;
		this.total = 0;

		var fail=false;
		
		do { // a fake do...while to ease failure return
		
		// get WoaS version
		var old_version,
			ver_str = ct.match(/var woas = \{"version":\s+"([^"]+)"\s*\};(\r\n|\n)/);
		if (!ver_str || !ver_str.length) {
			woas.alert("Could not determine WoaS version\n"+woas.i18n.IMPORT_OLD_VER);
			fail = true;
			break;
		}
		ver_str = ver_str[1];
		woas.log("Imported file version string: "+ver_str);	// log:1
		switch (ver_str) {
			case "0.9.6B":
			case "0.9.7B": // never released officially
				ver_str = ver_str.substr(0, ver_str.length-2);
			case "0.10.0": case "0.10.1":
			case "0.10.2": case "0.10.3":
			case "0.10.4": case "0.10.5":
			case "0.10.6": case "0.10.7":
			case "0.10.8": case "0.10.9":
			case "0.11.0": case "0.11.1":
			case "0.11.2": case "0.11.3":
			case "0.11.4": case "0.11.5":
			case "0.11.6": case "0.11.7":
			case "0.11.8": case "0.11.9":
			case "0.12.0":
			old_version = Number(ver_str.substr(2).replace(/\./g, ""));
				break;
			default:
				woas.alert(woas.i18n.IMPORT_INCOMPAT.sprintf(ver_str)+woas.i18n.IMPORT_OLD_VER);
				fail=true;
		}
		if (fail) break;

		// import the variables
		var	imported_css = null,
			// used during import from older versions
			old_cfg;
		if (this.i_config)
			old_cfg = d$.clone(woas.config);
			
		this.new_main_page = woas.config.main_page;

		// locate the random marker
		var old_marker = ct.match(/\nvar __marker = "([A-Za-z\-\d]+)";(\r\n|\n)/);
		if (!old_marker) {
			woas.alert(woas.i18n.ERR_MARKER);
			fail = true;
		}
		if (fail) break;
		old_marker = old_marker[1];

		// import the CSS head tag in versions before 0.11.2
		if (this.i_styles && (old_version < 112)) {
			ct.replace(this.reOldStyleBlock, function (str, $1) {
				imported_css = $1;
			});
		} // 0.11.2+, we'll manage CSS import at the page level

		var data = woas._extract_src_data(old_marker, ct, true, this.new_main_page, true);
		// some GC help: we no more need the big content variable
		ct = null;		
		
		if (this.i_config) {
			var cfgStartMarker = 'woas["'+'config"] = {',
				// grab the woas config definition
				cfg_start = data.indexOf(cfgStartMarker),
				cfg_found = false;
			
			if (cfg_start !== -1) {
				var cfg_end = data.indexOf('}', cfg_start+cfgStartMarker.length);
				if (cfg_end !== -1) {
					woas.config = woas.eval(data.substring(cfg_start+cfgStartMarker.length-1, cfg_end+1), true);
					cfg_found = !woas.eval_failed;
				}
			}
						
			if (!cfg_found) {
				woas.log("Failed to import old configuration object");
			} else {
				// add the new debug option
				if (old_version<=107)
					woas.config.debug_mode = old_cfg.debug_mode;
				// add the new safe mode
				if (old_version < 112) {
					woas.config.safe_mode = old_cfg.safe_mode;
					//NOTE: WSIF datasource options are not imported at all
				}
				// renamed one config option
				if (old_version < 117) {
					woas.config.nav_history = woas.config.open_last_page;
					delete woas.config.open_last_page;
				}
				// introduced new options
				if (old_version < 120) {
					woas.config.new_tables_syntax = old_cfg.new_tables_syntax;
					woas.config.store_mts = old_cfg.store_mts;
					woas.config.folding_style = old_cfg.folding_style;
					woas.config.import_settings = old_cfg.import_settings;
				}
				// check for any undefined config property - for safety
				for(p in woas.config) {
					// remove things from the past
					if (typeof old_cfg[p] == "undefined") {
						woas.log("BUG: removing invalid config option '"+p+"'");
						delete woas.config[p];
						continue;
					}
					if ((typeof woas.config[p] == "undefined") && (typeof old_cfg[p] != "undefined"))
						woas.config[p] = old_cfg[p];
				}
				
				// put back the old values for WSIF datasource
				woas.config.wsif_author = old_cfg.wsif_author;
				woas.config.wsif_ds = old_cfg.wsif_ds;
				woas.config.wsif_ds_lock = old_cfg.wsif_ds_lock;
				woas.config.wsif_ds_multi = old_cfg.wsif_ds_multi;
				
			} // done importing config object
		} // i_config

		// modified timestamp for pages before 0.10.0
		this.current_mts = Math.round(new Date().getTime()/1000);
		
		// import the pages data
		this._get_import_vars(data, ['woas', '__marker', 'version', '__config'],
							old_version);
		// some GC help
		data = null;
		
		// apply upgrade fixes
		if (this.i_content) {
			this._import_content(old_version);
			this.total = this.pages.length;
			// GC cleanup
			this.pages = [];
		}
		
		// eventually add the new missing page
		if (old_version <= 112) {
			// take care of custom CSS (if any)
			if (imported_css !== null) {
				// import it as a page
				this._import_hook( {
					title: "WoaS::CSS::Custom",
					attrs: 0,
					mts: woas.config.store_mts ? this.current_mts : 0,
					body: imported_css
				} );
			}
		}
		// set the new config variable
		if (this.i_config) {
			if (old_version<=108)
				woas.config.main_page = old_cfg.main_page;
			// apply the new main page if that page exists
			if ((this.new_main_page !== old_cfg.main_page) && woas.page_exists(this.new_main_page))
				woas.config.main_page = this.new_main_page;
		}
		
		} while (false); // fake do..while ends here
		
		// fix configuration for older versions
		if (old_version < 114) {
			if (!woas.config.nav_history) {
				// reset some variables which were not reset in those older versions
				backstack = [];
				current = woas.config.main_page;
			}
		}

		// if there is bootscript code, create a new plugin for it
		// skip empty bootscripts and also default bootscript
		var trimmed_bs = woas.trim(this._bootscript_code);
		if ((trimmed_bs.length !== 0) && (trimmed_bs !== '/* insert here your boot script */')
			&& (trimmed_bs !== '// Put here your boot javascript')) {
			var chosen_name = "WoaS::Plugins::Bootscript", base_name = chosen_name, i=0;
			while (page_titles.indexOf(chosen_name) !== -1) {
				chosen_name = base_name + "_" + (i++).toString();
			}
			woas.log("Old bootscript code will be imported as "+chosen_name);
			// now create such plugin by directly importing it
			this._import_hook( {
				title: chosen_name,
				body: "/* This JavaScript code was automatically imported by WoaS from your former WoaS::Bootscript */\n"+this._bootscript_code,
				attrs: 0,
				mts: woas.config.store_mts ? this.current_mts : 0
			} );
		}
		
		// always update run-time stuff, even on failure
		this._after_import();
		
		// clear everything else
		this._clear();

		// return false on failure
		return !fail;
	},
	
	_after_import: function() {
		var that = woas.importer;
		// refresh in case of CSS, aliases and/or hotkeys modified
		if (that._update_css)
			woas.css.set(woas.get_text("WoaS::CSS::Core")+"\n"+woas.get_text("WoaS::CSS::Custom"));
		if (that._update_aliases)
			woas._load_aliases(woas.get_text("WoaS::Aliases"));
		if (that._update_hotkeys)
			woas.hotkey.load(woas.get_text("WoaS::Hotkeys"));
		
		// add/update plugins
		for(var i=0,it=that._plugins_update.length;i < it;++i) {
			woas.plugins.update(that._plugins_update[i]);
		}
		for(var i=0,it=that._plugins_add.length;i < it;++i) {
			// plugin should be on the list
			woas.plugins._all.push(that._plugins_add[i]);
			woas.plugins.enable(that._plugins_add[i]);
		}
		
	},

	// regular expressions used to not mess with title/content strings
	reRequote: new RegExp(":-"+woas.parser.marker, "g"),
	reJString: new RegExp("'[^']*'", "g"),
	reJStringRep: new RegExp(woas.parser.marker+":"+"(\\d+)", "g"),
	reValidImage: /^data:\s*[^;]*;\s*base64,\s*/,
	reImageBadMime: /^data:undefined;\s*base64,\s*/,
	reOldStyleBlock: new RegExp("<"+"style\\s.*?type=\"?text\\/css\"?[^>]*>((\\n|.)*?)<"+"\\/style>", "i")

};

// called from Special::Import - import WoaS from XHTML file
woas.import_wiki = function() {
	if (!woas._import_pre_up(true))
		return false;

	// set hourglass
	this.progress_init("Import WoaS");

	// file will be loaded as ASCII to overcome browsers' limitations
	var ct = this.load_file(null, this.file_mode.ASCII_TEXT);
	if (ct === null) {
		// remove hourglass
		this.progress_finish();
		return false;
	}
	if (ct === false) {
		this.alert(this.i18n.LOAD_ERR);
		// remove hourglass
		this.progress_finish();
		return false;
	}

	var rv = this.importer.do_import(ct);
	
	// remove hourglass
	this.progress_finish();
	
	if (rv) {
		// inform about the imported pages / total pages present in file
		this.alert(this.i18n.IMPORT_OK.sprintf(this.importer.pages_imported+"/"+this.importer.total,
												this.importer.total - this.importer.pages_imported));
		
		// move to main page
		current = this.config.main_page;
	}
	
	// always save if we have erased the wiki
	if ((this.importer.i_overwrite === 0) || rv)
		this.full_commit();

	if (rv) {
		this.refresh_menu_area();
		this.set_current(this.config.main_page, true);
	}
	
	// supposedly, everything went OK
	return rv;
};

woas._file_ext = function(fn) {
	var m=fn.match(/\.(\w+)$/);
	if (m === null) return "";
	return "."+m[1];
};

woas._import_pre_up = function(all_options) {
	// check if this WoaS is read-only
	if (!this.config.permit_edits) {
		this.alert(woas.i18n.READ_ONLY);
		return false;
	}
	// grab the common options
	this.importer.i_comment_js = d$.checked("woas_cb_import_comment_js");
	this.importer.i_comment_macros = d$.checked("woas_cb_import_comment_macros");
	this.importer.i_woas_ns = d$.checked("woas_cb_import_woas_ns");
	//NOTE: i_overwrite is automatically set when clicking
	if (all_options) {
		// grab the XHTML-only options
		this.importer.i_styles = d$.checked('woas_cb_import_styles');
		this.importer.i_config = d$.checked('woas_cb_import_config');
		this.importer.i_content = d$.checked('woas_cb_import_content');
	} else {
		// these options are not available for WSIF
		this.importer.i_styles = this.importer.i_content = true;
	}
	
	var old_is = woas.config.import_settings;
	// now store these values
	woas.config.import_settings = this.bitfield.get_object(this.importer, this.importer._settings_props);
	// set also bits for overwrite options
	woas.config.import_settings = this.bitfield.set(this.config.import_settings, this.importer._OVR_ID,
									this.importer.i_overwrite & 1, this.config.import_settings);
	woas.config.import_settings = this.bitfield.set(this.config.import_settings, this.importer._OVR_ID+1,
									this.importer.i_overwrite & 2, this.config.import_settings);
	// check if configuration changed
	this.cfg_changed |= (woas.config.import_settings !== old_is);
	// check if user wants total erase before going on
	if (this.importer.i_overwrite === 0) {
		if (!this.erase_wiki())
			return false;
	}
	
	return true;
};

// called from Special::ImportWSIF
woas.import_wiki_wsif = function() {
	if (!woas._import_pre_up(false))
		return false;
	
	// automatically retrieve the filename (will call load_file())
	var done = woas._native_wsif_load(null, false /* no locking */, false /* not native */, 0,
			this.importer._upgrade_content, this.importer._filter_by_title,
			this.importer._after_import);
	if (done === false && (woas.wsif.emsg !== null))
		this.crash(woas.wsif.emsg);

	if (done !== false) {
		// add some info about total pages
		var skipped;
		if (this.wsif.expected_pages !== null) {
			skipped = this.wsif.expected_pages-done;
			done = String(done)+"/"+woas.wsif.expected_pages;
		} else skipped = 0;
		this.alert(woas.i18n.IMPORT_OK.sprintf(done, skipped));
		this.refresh_menu_area();
		// now proceed to actual saving
		this.commit(woas.wsif.imported);
	} else {
		// always save if we have erased the wiki
		if (this.importer.i_overwrite === 0)
			this.full_commit();
	}

	return done;
};

/*** src/export.js ***/
// WoaS 'exporter' module
woas.exporter = {
	_unix_norm: false,
	_default_ext: 'html',

	// arrays/objects using during export
	_title2fn: {},
	_further_pages: [],
	_export_fnames_array: [],
	_settings: {},
	
	clear: function() {
		this._export_fnames_array = [];
		this._title2fn = {};
		this._further_pages = [];
		this._settings = {};
	},
	
	// export wiki in XHTML/CSS and attachment files
	do_export: function () {
		this._settings.custom_scripts = "";
		var data;
		if (this._settings.js_mode==2) {
			//export all active plugins as javascript code
			var js_fn;
			for(var pi=0,pt=woas.plugins._active.length;pi<pt;++pi) {
				data = woas.plugins.get(woas.plugins._active[pi]);
				if (woas.plugins.is_external) {
					// data is an array of sources, go through it
					for(var i=0;i<data.length;++i) {
						this._settings.custom_scripts += '<'+'sc'+'ript type="text/javascript" src="'+data[i]+'"><'+"/script>\n";
					}
				} else {
					js_fn = woas._unix_normalize(woas.plugins._active[pi])+".js";
					if (woas.save_file(this._settings.xhtml_path+js_fn,
										woas.file_mode.ASCII, data)) {
						this._settings.custom_scripts += '<'+'script type="text/javascript" src="'+js_fn+'"><'+"/script>\n";
					}
				}
			}
		}

		var l = page_titles.length, fname = "", done = 0, total = 0, mnupos;
		data = null;
		for (var pi=0;pi < l;pi++) {
			// do skip physical special pages
			if (page_titles[pi].match(/^Special::/)) continue;
			if (woas.static_pages2.indexOf(page_titles[pi]) !== -1) continue;
			// skip also the unexportable WoaS pages
			if (woas.unexportable_pages2.indexOf(page_titles[pi]) !== -1) continue;
			// do skip menu pages (they are already included in each page)
			mnupos = page_titles[pi].indexOf("::Menu");
			if ((mnupos !== -1) &&
				(mnupos === page_titles[pi].length-6)) continue;
			data = woas.get_text_special(page_titles[pi]);
			// skip pages which could not be decrypted
			if (data === null) continue;
			fname = this._get_fname(page_titles[pi], true);
			// will skip WoaS::Plugins::* and WoaS::Aliases
			if (fname === '#') {
	//			log("skipping "+page_titles[pi]);
				continue;
			}
			++total;
			if (woas.is__embedded(pi)) {
				if (woas.is__image(pi)) {
					if (!woas._b64_export(data, this._settings.img_path+fname))
						break;
					// image export was successful, continue to next page
					else { ++done; continue; }
				} else {
					// fully export the file
					if (!woas._b64_export(data, this._settings.xhtml_path+fname))
						break;
					// image export was successful, continue to next page
					else { ++done; continue; }
				}
			} else
				data = woas.export_parse(data, this._settings.js_mode);
			if (!this._one_page(data, page_titles[pi], fname, woas.config.store_mts ? page_mts[pi] : 0))
				break;
			++done;
		}
		log("pages yet to process: "+this._further_pages);	// log:1
		// process further pages
		var title;
		// exchange arrays to parse at some extent
		var eatable = this._further_pages.slice(0);
		while (eatable.length) {
			this._further_pages = [];
			for(var i=0,el=eatable.length;i < el;i++) {
				title = eatable[i];
				data = woas.get_text_special(title);
				if (data===null) {
					log("cannot process "+title);
					continue;
				}
				// TODO: allow special pages to have extended attributes
				data = woas.export_parse(data, this._settings.js_mode);
				++total;
				if (this._one_page(data, title, this._title2fn[title]))
					++done;
			}
			eatable = this._further_pages.slice(0);
		}
		// return total exported pages/objects
		return [done, total];
	},
	
	_one_page: function (data, title, fname, mts) {
		data = woas.utf8.do_escape(data);
		// prepare the raw text for later description/keywords generation
		var raw_text = woas.trim(woas.xhtml_to_text(data));
		if (this._settings.exp_menus) {
			var _exp_menu = woas.get_text("::Menu");
			if (_exp_menu === null)
				_exp_menu = "";
			var _ns = woas.get_namespace(title);
				if (_ns.length) {
					var mpi = woas.page_index(_ns+"::Menu");
					if (mpi !== -1) {
						var tmp=woas.get_text_special(_ns+"::Menu");
						if (tmp!==null)
							_exp_menu += tmp;
					}
				}
				// parse the exported menu
				if (_exp_menu.length) {
					_exp_menu = woas.parser.parse(_exp_menu, true, this._settings.js_mode);
					if (this._settings.js_mode)
						woas._activate_scripts();
					// fix also the encoding in the menus
					_exp_menu = woas.utf8.do_escape(_exp_menu);
				}
				//TODO: use correct ids/class names
				data = '<'+'div id="i_woas_menu_area" style="position: absolute;"><'+'div class="woas_wiki" id="woas_menu_area">'+_exp_menu+'<'+'/div><'+
						'/div><'+'div class="woas_text_area" id="woas_wiki_area">'+data+'<'+'/div>';
			}
			// craft a nice XHTML page
			data = "<"+"title>"+woas.xhtml_encode(title)+"<"+"/title>"+
					// add the exported CSS
					this._settings.css+
					// add the last-modified header
					(mts ? '<'+'meta http-equiv="last-modified" content="'+
					(new Date(mts*1000)).toGMTString()+'" />'+"\n" : '')+
					// other useful META stuff
			'<'+'meta name="generator" content="Wiki on a Stick v'+woas.version+' - http://stickwiki.sf.net/" />'+"\n"+
			'<'+'meta name="keywords" content="'+woas.utf8.do_escape(woas._attrib_escape(_auto_keywords(raw_text)))+'" />'+"\n"+
			'<'+'meta name="description" content="'+
			woas.utf8.do_escape(woas._attrib_escape(raw_text.replace(/\s+/g, " ").substr(0,max_description_length)))+'" />'+"\n"+
			this._settings.meta_author+
			this._settings.custom_scripts+
			'<'+'/head><'+'body>'+data+
			(mts ? "<"+"div class=\"woas_page_mts\">"+woas.last_modified(mts)+"<"+"/div>" : "")+
			"<"+"/body><"+"/html>\n"; raw_text = null;
		return woas.save_file(this._settings.xhtml_path+fname, woas.file_mode.ASCII, woas.DOCTYPE+woas.DOC_START+data);
	},
	
	_get_fname: function (title, create_mode) {
		if (typeof(this._title2fn[title]) != 'undefined') {
			// return a cached title
			if (!create_mode) {
				// do not escape the null-page URL
				if (this._title2fn[title] == '#')
					return '#';
				return escape(this._title2fn[title]);
			}
			return this._title2fn[title];
		}
		var sp, orig_title = title;
		// handle the valid exportable special pages
		if (title.match(/::$/))
			sp = true;
		else if (woas.is_reserved(title)) {
			var nogo;
			if (title.match(/^WoaS::/))
				nogo = (woas.unexportable_pages2.indexOf(title)!==-1);
			else if (title.match(/^Special::/))
				nogo = (woas.unexportable_pages.indexOf(title.substr(9)) !== -1);
			else // other reserved pages, deny
				nogo = true;
			if (nogo) {
				log("Reserved page will not be exported: "+title);
				this._title2fn[title] = "#";
				return "#";
			} else // do export these special pages later
				sp = true;
		}
		var pi;
		if (sp) {
			// save a reference to this namespace or reserved page
			if (this._further_pages.indexOf(title)==-1)
				this._further_pages.push(title);
		} else {
			pi=woas.page_index(title);
			if (pi === -1) {
				woas.alert(woas.i18n.PAGE_NOT_EXISTS+title);
				this._title2fn[title] = "#";
				return "#";
			}
			// beware: a special page or namespace index page cannot be main page
			// considering the below code
			if (this._settings._main_index && (title === woas.config.main_page)) {
				this._title2fn[title] = "index."+ this._default_ext;
				this._export_fnames_array.push(this._title2fn[title]);
				return this._title2fn[title];
			}
		}
		var ext = "";
		if (!sp && woas.is__embedded(pi)) {
			// remove the namespace
			title = title.substr(title.indexOf("::")+2);
	//		if (!this.is__image(pi))
	//			ext = "."+this._default_ext;
	//		emb = true;
		} else {
			ext = "."+this._default_ext;
	//		emb = false;
		}
		var fname = title
		// convert UTF8 characters to something else (cross-browser safe cheap solution)
		.replace(/[^\u0000-\u007F]+/g, function ($1) {
			var l=$1.length, r="";
			for(var i=0;i < l;i++) {
				/*switch ($1[i]) {
					// TODO: add most common diacritics
					case "\u00e2":
						r+="a";
						break;
					default:
						r+="_";
				}*/
				// until that day ...
				r += ($1[i] == "\u00e2") ? "a" : "_";
			}
			return r;
		})
		// escape some path-unsafe characters
		.replace(/[:\\\/<>?#=!]+/g, function($1) {
			return "_".repeat($1.length);
		});
		// fix the directory separator chars
		if (this._settings._unix_norm)
			fname = woas._unix_normalize(fname);
		else
			fname = fname.replace(/::/g, " - ");
		var test_fname = fname+ext, i=0;
		while (this._export_fnames_array.indexOf(test_fname) !== -1) {
			log(test_fname+" already created, checking next fname");	// log:1
			test_fname = fname+"_".repeat(++i)+ext;
		}
	//	if (i)		_export_replace_fname[fname+"_".repeat(i-1)+ext] = test_fname;
		this._export_fnames_array.push(test_fname);
		this._title2fn[orig_title] = test_fname;
		if (!create_mode)
			return escape(test_fname);
		return test_fname;
	}

};

woas._unix_normalize = function(s) {
	return s.toLowerCase().replace(/\s+/g, "_").replace(/::/g, "-")
};

// save a base64 data: stream into an external file
woas._b64_export = function(data, dest_path) {
	// decode the base64-encoded data
	data = this.base64.decode(data.replace(/^data:\s*[^;]*;\s*base64,\s*/, ''));
	// attempt to save the file
	return this.save_file(dest_path, this.file_mode.BINARY, data);
};

woas._attrib_escape = function(s) {
	return s.replace(/"/g, '&quot;');
};

woas.export_parse = function (data, js_mode) {
	// a normal wiki page, parse it and eventually execute the attached javascript
	data = this.parser.parse(data, true, js_mode);
	if (js_mode) {
		this.setHTMLDiv(d$("woas_wiki_area"), data);
		this._activate_scripts();
		data = this.getHTMLDiv(d$("woas_wiki_area"));
	}
	return data;
};

woas.export_wiki = function() {
	var sep_css;
	// parse user export options
	try {
		this.exporter._settings.xhtml_path = d$("woas_ep_xhtml").value;
		this.exporter._settings.img_path = d$("woas_ep_img").value;
		this.exporter._settings.js_mode = 0;
		if (d$("woas_cb_js_dyn").checked)
			this.exporter._settings.js_mode = 1;
		else if (d$("woas_cb_js_exp").checked)
			this.exporter._settings.js_mode = 2;
		sep_css = d$("woas_cb_sep_css").checked;
		this.exporter._settings.exp_menus = d$("woas_cb_export_menu").checked;
		this.exporter._settings._main_index = d$("woas_cb_index_main").checked;
		this.exporter._default_ext = d$("woas_ep_ext").value;
		this.exporter._settings.meta_author = this.trim(d$("woas_ep_author").value);
		if (this.exporter._settings.meta_author.length)
			this.exporter._settings.meta_author = '<'+'meta name="author" content="'+this._attrib_escape(this.xhtml_encode(this.exporter._settings.meta_author))+'" />'+"\n";
		this.exporter._settings._unix_norm = d$("woas_cb_unix_norm").checked;
	} catch (e) { this.crash(e); return false; }
	
	this.progress_init("Exporting XHTML");

	this.exporter._settings.css = this.css.get();
	// add some other CSS which is not used by live WoaS
	this.exporter._settings.css += "\n.woas_broken_link { color: red; font-decoration: strike-through;}\n";
	if (sep_css) {
		this.exporter._settings.css_path = "woas.css";
		this.exporter._export_fnames_array.push(this.exporter._settings.css_path);
		this.save_file(this.exporter._settings.xhtml_path+this.exporter._settings.css_path, this.file_mode.ASCII, this.exporter._settings.css);
		this.exporter._settings.css = "<"+"link rel=\"stylesheet\" type=\"text/css\" media=\"all\" href=\""+this.exporter._settings.css_path+"\" /"+">";
		} else
	this.exporter._settings.css = "<"+"style type=\"text/css\">"+this.exporter._settings.css+"<"+"/style>";
	
	// actual exporting
	var stats = this.exporter.do_export();
	
	// clear allocated resources during export
	this.exporter.clear();
	
	// refresh if javascript was ran
	if (this.exporter._settings.js_mode) {
		this.refresh_menu_area();
		this.set_current(current, false);
	}
	this.progress_finish();
	this.alert(this.i18n.EXPORT_OK.sprintf(stats[0], stats[1]));
	return true;
};

var max_keywords_length = 250;
var max_description_length = 250;
// proper autokeywords generation functions begin here
var reKeywords = new RegExp("[^\\s\x01-\x2F\x3A-\x40\x5B-\x60\x7B-\x7E]{2,}", "g");
function _auto_keywords(source) {
	if (!source.length) return "";
	var words = source.match(reKeywords);
	if ((words===null) || !words.length) return "";
	var nu_words = [], density = [], wp=0, cond;
	for(var i=0;i < words.length;i++) {
		if (words[i].length === 0)
			continue;
		cond = (woas.i18n.common_words.indexOf(words[i].toLowerCase())<0);
		if (cond) {
			wp = nu_words.indexOf(words[i]);
			if (wp < 0) {
				nu_words = nu_words.concat(new Array(words[i]));
				density[nu_words.length-1] = {"i":nu_words.length-1, "w":1};
			} else
				density[wp].w = density[wp].w + 1;
		}
	}
	if (!density.length) return "";
	words = [];
	var keywords = "", nw = "";
	density = density.sort(function(a,b){return b.w - a.w;});
	var ol=0;
	for(i=0;i < density.length;i++) {
		nw = nu_words[density[i].i];
		if (ol+nw.length > max_keywords_length)
			break;
		keywords = keywords+","+nw;
		ol+=nw.length;
	}
	return keywords.substr(1);
}

// used to export files/images
woas.export_file = function(page, dest_path) {
	var pi=this.page_index(page);
	if (pi==-1)
		return false;
	var data=this.get__text(pi);
	if (data==null)
		return false;
	// attempt to save the file (binary mode)
	return this.save_file(dest_path, this.file_mode.BINARY, this.base64.decode(data));
};

// export a base64-encoded image to a file
woas.export_image = function(page, dest_path) {
	var pi=this.page_index(page);
	if (pi==-1)
		return false;
	var data=this.get__text(pi);
	if (data==null)
		return false;
	return this._b64_export(data, dest_path);
};

/*** src/wsif.js ***/
// @module wsif

woas.wsif = {
	version: "1.3.1",			// version of WSIF format being used
	DEFAULT_INDEX: "index.wsif",
	emsg: null,
	imported: [],				// _native_page_def() will always add imported pages here
	expected_pages: null,
	global_progress: 0,
	generator: { version: null, name: null },
	header: function(header_name, value) {
		return header_name+": "+value+"\n";
	},
	inline: function(boundary, content) {
		return "\n--"+boundary+"\n"+content+"\n--"+boundary+"\n";
	},
	_generate_random_boundary: function(old_boundary, text) {
		var b = old_boundary;
		if (!b.length)
			b = _random_string(10);
		while (text.indexOf(b) != -1) {
			b = _random_string(10);
		}
		return b;
	},
	do_error: function(msg) {
		woas.log("WSIF ERROR: "+msg);	//log:1
		this.emsg = msg;
	},
	
	reMimeMatch: /^data:\s*([^;]*);\s*base64,\s*/
};

// properly calculate an incremental value for version strings
// we consider each place is worth 10^2
woas._version_to_int = function(s) {
	// split into all tokens
	var v = s.split("."), rv = 0;
	for(var i=v.length-1;i >= 0;--i) {
		rv += (v[i] * Math.pow(100, v.length-i-1));
	}
	return rv;
};

woas._normver = function(s) {
	// to be enabled after 1.0.0 release
	//var rv = this._version_to_int(s);
	//if (rv >= 10000) return rv;
	return parseInt(s.replace(/\./g, ""));
};

// default behavior:
// - wiki pages go inline (utf-8), no container encoding
// - embedded files/images go outside as blobs
// - encrypted pages go inline in base64
woas._native_wsif_save = function(path, src_fname, locking, single_wsif, inline_wsif, author,
							save_all, plist) {

	this.progress_init("WSIF save");
	
	// prepare the extra headers
	var extra = this.wsif.header('wsif.version', this.wsif.version);
	extra += this.wsif.header('wsif.generator', 'woas');
	extra += this.wsif.header('wsif.generator.version', this.version);
	if (author.length)
		extra += this.wsif.header('woas.author', author);

	// boundary used for inline attachments
	var full_wsif = "", boundary = __marker,
		p = boundary.indexOf("-"),
		l, done = 0, full_save;
	// use the 1st part of the global marker
	if (p !== -1)
		boundary = boundary.substr(0,p);

	if (typeof plist == "undefined") {
		full_save = true;
		l = page_titles.length;
	} else {
		l = plist.length;
		full_save = false;
	}
	// save count of total pages which need to be exported
	this.wsif.expected_pages = l;
	// the attributes prefix, we do not use the page index here for better versioning
	var pfx = "woas.page.",
		pi, pl;
	for (var ipi=0;ipi < l;++ipi) {
		if (full_save)
			pi = ipi;
		else
			pi = plist[ipi];
		// do skip physical special pages
		if (!save_all) {
			if (page_titles[pi].match(/^Special::/)) continue;
			if (this.static_pages2.indexOf(page_titles[pi]) !== -1) continue;
			if (page_titles[pi].match(/^WoaS::Help::/)) continue;
		}
		var record = this.wsif.header(pfx+"title", this.ecma_encode(page_titles[pi]))+
					this.wsif.header(pfx+"attributes", page_attrs[pi]);
		// specify timestamp only if not magic
		if (this.config.store_mts && (page_mts[pi] !== 0))
			record += this.wsif.header(pfx+"last_modified", page_mts[pi]);
		var ct = null, orig_len = null;
		
		// normalize the page content, set encoding&disposition
		var encoding = null, mime = null, disposition = "inline";
		if (this.is__encrypted(pi)) {
			ct = this.base64.encode_array(pages[pi]);
			encoding = "8bit/base64";
			// special header used for encrypted pages
			orig_len = pages[pi].length;
		} else {
			ct = pages[pi];
			if (this.is__embedded(pi)) {
				// if not forced to do them inline, convert for export
				var m;
				if (!inline_wsif) {
					disposition = "external";
					encoding = "8bit/plain";
					// decode the base64-encoded data
					if (this.is__image(pi)) {
						m = ct.match(this.wsif.reMimeMatch);
						record += this.wsif.header(pfx+"mime", m[1]);
						// remove the matched part
						ct = this.base64.decode(ct.substr(m[0].length));
					} else { // no data:uri for files
						ct = this.base64.decode(ct);
					}
				} else {
					encoding = "8bit/base64";
					if (this.is__image(pi)) {
						m = ct.match(this.wsif.reMimeMatch);
						if (m === null) {
							this.crash(page_titles[pi]+" is not a valid image!"+ct);							
							continue;
						} else {
							mime = m[1];
							// remove the matched part
							ct = ct.substr(m[0].length);
							m = null;
						}
						record += this.wsif.header(pfx+"mime", mime);
					}
				}
			} else { // normal wiki pages
				// check if ECMA encoding is necessary
				if (this.needs_ecma_encoding(ct)) {
					ct = this.ecma_encode(ct);
					encoding = "ecma/plain";
				} else
					encoding = "8bit/plain";
			}
		}
		//DEBUG check
		if (encoding === null) {
			this.crash("Encoding for page "+page_titles[pi]+" is set to null!");
			continue;
		}
		// update the index (if needed)
		if (!single_wsif && full_save) {
			full_wsif += this.wsif.header(pfx+"title", this.ecma_encode(page_titles[pi]));
			// a new mime type
			full_wsif += this.wsif.header(pfx+"encoding", "text/wsif");
			full_wsif += this.wsif.header(pfx+"disposition", "external");
			// add reference to the external WSIF file
			full_wsif += this.wsif.header(pfx+"disposition.filename", pi.toString()+".wsif")+
							"\n";
		}
		// update record
		if (!this.config.allow_diff)
			record += this.wsif.header(pfx+"length", ct.length);
		record += this.wsif.header(pfx+"encoding", encoding);
		record += this.wsif.header(pfx+"disposition", disposition);
		// note: when disposition is inline, encoding cannot be 8bit/plain for embedded/encrypted files
		if (disposition == "inline") {
			// output the original length header (if available)
			if (orig_len !== null)
				record += this.wsif.header(pfx+"original_length", orig_len);
			// create the inline page
			boundary = this.wsif._generate_random_boundary(boundary, ct);
			record += this.wsif.header(pfx+"boundary", boundary);
			// add the inline content
			record += this.wsif.inline(boundary, ct); ct = null;
		} else {
			// create the blob filename
			var blob_fn = "blob" + pi.toString() + woas._file_ext(page_titles[pi]);
			// specify path to external filename
			record += this.wsif.header(pfx+"disposition.filename", blob_fn)+"\n";
			// export the blob
			if (!this.save_file(path + blob_fn,
							(encoding === "8bit/plain") ?
							this.file_mode.BINARY : this.file_mode.ASCII_TEXT, ct))
				woas.log("Could not save "+blob_fn);	//log:1
			// release any lock held previously
			if (locking)
				this.lock.release(path+blob_fn);
		}
		// the page record is now ready, proceed to save
		if (single_wsif) {// append to main page record
			full_wsif += record;
			++done;
		} else { // save each page separately
			if (this.save_file(path+pi.toString()+".wsif",
								this.file_mode.ASCII_TEXT,
								// also add the pages counter (single)
								extra + this.wsif.header("woas.pages", 1) +
								"\n" + record))
				++done;
		}
		this.progress_status(done/l);
		// reset the record
		record = "";
	} // foreach page
	// add the total pages number
	if (full_save || single_wsif)
		extra += this.wsif.header('woas.pages', done);
	else
		extra += this.wsif.header('woas.pages', page_titles.length);
	// build (artificially) an index of all pages
	if (!full_save && !single_wsif) {
		for (pi=0,pl=page_titles.length;pi<pl;++pi) {
			full_wsif += this.wsif.header(pfx+"title", this.ecma_encode(page_titles[pi]));
			// a new mime type
			full_wsif += this.wsif.header(pfx+"encoding", "text/wsif");
			full_wsif += this.wsif.header(pfx+"disposition", "external");
			// add reference to the external WSIF file
			full_wsif += this.wsif.header(pfx+"disposition.filename", pi.toString()+".wsif")+
							"\n";
		}
	}
	// output the index WSIF file now
	if (!this.save_file(path+src_fname,
						this.file_mode.ASCII_TEXT,
						extra + "\n" + full_wsif)) {
		if (single_wsif)
			done = 0;
	} // we do not increment page counter when saving index.wsif
	
	// release any lock held previously
	if (locking)
		this.lock.release(path+src_fname);
	
	this.progress_finish();
	return done;
};

woas._wsif_ds_load = function(subpath, locking) {
	// we reset the arrays before loading the real data from index.wsif
	pages = [];
	page_attrs = [];
	page_titles = [];
	page_mts = [];
	// get the data
	return this._native_wsif_load(woas.ROOT_DIRECTORY+subpath, locking, true, 0,
			this.importer._inject_import_hook);
};

/* description of parameters:
 - path: WSIF file path which will be loaded, can be null to use file specified in '_filename' element
 - locking: resource locking (not yet implemented)
 - _native: true when WoaS is in native mode (private use only)
 - recursing: recursion depth variable, starts with 0
 - import_hook: callback used to actually import the page
 - title_filter_hook: callback used to choose if page can be imported or not by title - optional
 - finalization_hook: callback used when import is finished
*/
woas._native_wsif_load = function(path, locking, _native, recursing, import_hook,
								title_filter_hook, finalization_hook) {
	if (!recursing) {
		this.wsif.emsg = null;
		this.progress_init("Initializing WSIF import");
	}
	var ct;
	// allow remote loading when running in native WSIF mode
	if (this._server_mode && (this.config.wsif_ds.length !== 0))
		ct = this.remote_load(path);
	else
		ct = this.load_file(path, this.file_mode.ASCII_TEXT);
	if (typeof ct != "string") {
		if (!recursing)
			this.progress_finish();
		if (ct === false)
			this.alert(this.i18n.LOAD_ERR);
		return false;
	}
	// reset when not recursing
	if (!recursing) {
		this.wsif.expected_pages = null;
		this.wsif.emsg = this.i18n.NO_ERROR;
		this.wsif.imported = [];
		this.wsif.global_progress = 0;
		this.wsif.generator = { version: null, name: null };
	}
	var pfx = "\nwoas.page.", pfx_len = pfx.length;
	// start looping to find each page
	var p = ct.indexOf(pfx), fail = false,
	// this is used to mark end-of-block
		previous_h = null;
	// too early failure
	if (p === -1)
		this.wsif.do_error("Invalid WSIF file");
	else { // OK, first page was located, now get some general WSIF info
		var wsif_v = ct.substring(0,p).match(/^wsif\.version:\s+(.+)$/m);
		if (wsif_v === null) {
			this.wsif.do_error(this.i18n.WSIF_NO_VER);
			p = -1;
			fail = true;
		} else {
			// convert to a number
			wsif_v = wsif_v[1];
			var wsif_v_n = this._normver(wsif_v);
			// check if WSIF is from future of it is the unsupported 1.0.0
			if ((wsif_v_n == 100) || (wsif_v_n > this._normver(this.wsif.version))) {
				this.wsif.do_error(this.i18n.WSIF_NS_VER.sprintf(wsif_v));
				p = -1;
				fail = true;
			} else {
				if (!recursing) {
					// get number of expected pages
					this.wsif.expected_pages = ct.substring(0,p).match(/^woas\.pages:\s+(\d+)$/m);
					if (this.wsif.expected_pages !== null)
						this.wsif.expected_pages = Number(this.wsif.expected_pages[1]);
					// gather generator information
					this.wsif.generator.name = ct.substring(0,p).
						match(/^wsif\.generator:\s+(.+)$/m);
					if (this.wsif.generator.name !== null) {
						this.wsif.generator.name = this.wsif.generator.name[1];
						this.wsif.generator.version = ct.substring(0,p).
							match((wsif_v_n < 131) ? /^woas\.version:\s+(.+)$/m : /^wsif\.generator\.version:\s+(.+)$/m);
						if (this.wsif.generator.version !== null) {
							this.wsif.generator.version = this.wsif.generator.version[1];
						}
					}

					// was generator information truly necessary?
					if (this.wsif.generator.name === "woas") {
						if (this.wsif.generator.version === null) {
							this.wsif.do_error("WSIF generator version is not available");
							p = -1;
							fail = true;
						} else {
							if (!_native)
								// copy to importer module
								this.importer._old_version = this._normver(this.wsif.generator.version);
							else { // native mode
								if (this.wsif.generator.version !== this.version) {
									this.wsif.do_error("WSIF generator version should match WoaS version");
									p = -1;
									fail = true;
								}
							}
						}
					} else {
						if (!_native) {
							// assume that any content generated with libwsif is up-to-date for import
							this.importer._old_version = this._normver(this.version);
						} else { // native mode
							this.wsif.do_error("WSIF generator is not 'woas'");
							p = -1;
							fail = true;
						}
					}

				} // !recursing
			} // valid version
		} // has version
	}
	var title = null,	attrs = null,
		last_mod = null,	len = null,
		encoding = null,	disposition = null,
		d_fn = null,	boundary = null,	mime = null;
	// position of last header end-of-line
	var sep, s, v, last_offset;
	while (p !== -1) {
		// save last entry offset, used by page definition
		last_offset = p;
		// remove prefix
		sep = ct.indexOf(":", p+pfx_len);
		if (sep === -1) {
			this.wsif.do_error(this.i18n.WSIF_NO_HN);
			fail = true;
			break;
		}
		// get attribute name
		s = ct.substring(p+pfx_len, sep);
		// get value
		p = ct.indexOf("\n", sep+1);
		if (p == -1) {
			this.wsif.do_error(this.i18n.WSIF_BAD_HV);
			fail = true;
			break;
		}
		// all headers except the title header can mark an end-of-block
		// the end-of-block is used to find boundaries and content inside
		// them
		if (s != "title")
			// save the last header position
			previous_h = p;
		// get value and apply left-trim
		v = ct.substring(sep+1, p).replace(/^\s*/, '');
		switch (s) {
			case "title":
				// we have just jumped over a page definition
				if (title !== null) {
					// do not import special/reserved pages
					var skip_page;
					if (typeof title_filter_hook == "function")
						skip_page = !title_filter_hook(title);
					else skip_page = false;
					if (skip_page) {
						title = attrs = last_mod = encoding = len =
							 boundary = disposition = mime = d_fn = null;
					} else {
						// store the previously parsed page definition
						var rv = this._native_page_def(path,ct,
								previous_h, last_offset,	// offsets to grab the page content
								import_hook,
								title,attrs,last_mod,len,encoding,disposition,
								d_fn,boundary,mime);
						// save page index for later analysis
						var was_title = title;
						title = attrs = last_mod = encoding = len =
							 boundary = disposition = mime = d_fn = null;
						if (!rv) // show a message but continue parsing
							woas.log("Import failure for "+was_title); //log:1
					}
					// delete the whole entry to free up memory to GC
					// will delete also the last read header (p != last_offset)
					ct = ct.substr(p);
					last_offset = p = 0;
					previous_h = null;
					// update status if not recursing
					if (!recursing && (this.wsif.expected_pages !== null))
						this.progress_status(this.wsif.global_progress++/this.wsif.expected_pages);
				}
				// let's start with the next page
				title = this.ecma_decode(v);
			break;
			case "attributes":
				attrs = Number(v);
			break;
			case "last_modified":
				if (this.config.store_mts)
					last_mod = Number(v);
			break;
			case "length":
				len = Number(v);
			break;
			case "encoding":
				encoding = v;
			break;
			case "disposition":
				disposition = v;
			break;
			case "disposition.filename":
				//TODO: ecma-escape
				d_fn = v;
			break;
			case "boundary":
				boundary = v;
			break;
			case "mime":
				mime = v;
			break;
			case "original_length":
				// this should be used for trimming base64 encrypted pages
				// ignored for now
			break;
			default:
				woas.log("Unknown WSIF header: "+s);	//log:1
		} // end switch(s)
		if (fail)
			break;
		// set pointer to next entry
		p = ct.indexOf(pfx, p);
	}
	if (fail) {
		if (!recursing)
			this.progress_finish();
		return false;
	}
	// process the last page (if any)
	if ((previous_h !== null) && (title !== null)) {
		var skip_page;
		if (typeof title_filter_hook == "function")
			skip_page = !title_filter_hook(title);
		else skip_page = false;
		if (!skip_page) {
			var rv = this._native_page_def(path,ct,previous_h,last_offset,
					import_hook,
					title,attrs,last_mod,len,encoding,disposition,
					d_fn,boundary,mime);
			// save page index for later analysis
			if (!rv)
				this.wsif.do_error( "Import error for page "+title+" after import!" );
			// update status if not recursing
			if (!recursing && (this.wsif.expected_pages !== null))
				this.progress_status(this.wsif.global_progress++/this.wsif.expected_pages);
		}
	}
	if (!recursing) {
		this.progress_finish();
		// call the finalization callback (if any)
		if (typeof finalization_hook == "function")
			finalization_hook();
	}
	// return total imported pages
	return this.wsif.imported.length;
};

// returns true if a page was defined, and save it in wsif.imported array
woas._native_page_def = function(path,ct,p,last_p,import_hook,
								title,attrs,last_mod,len,encoding,disposition,d_fn,boundary,mime) {
	var bpos_e, page;
	// attributes must be defined
	if (attrs === null) {
		woas.log("No attributes defined for page "+title);	//log:1
		return false;
	}
	// disposition must be defined
	if (disposition === null) {
		woas.log("No disposition defined for page \""+title+"\"");	//log:1
		return false;
	}
	// last modified timestamp can be omitted
	if (last_mod === null)
		last_mod = 0;
		
	switch (disposition) {
		case "inline":
		// craft the exact boundary match string
		boundary = "\n--"+boundary+"\n";
		// locate start and ending boundaries
		var bpos_s = ct.indexOf(boundary, p);
		if (bpos_s == -1) {
			this.wsif.do_error( "Failed to find start boundary "+boundary+" for page "+title );
			return false;
		}
		bpos_e = ct.indexOf(boundary, bpos_s+boundary.length);
		if (bpos_e == -1) {
			this.wsif.do_error( "Failed to find end boundary "+boundary+" for page "+title );
			return false;
		}
		// retrieve full page content
		page = ct.substring(bpos_s+boundary.length, bpos_e);
		// length used to check correctness of data segments
		var check_len = page.length;
		// split encrypted pages into byte arrays
		if (attrs & 2) {
			if (encoding != "8bit/base64") {
				woas.log("Encrypted page "+title+" is not encoded as 8bit/base64");	//log:1
				return false;
			}
//			check_len = page.length;
			page = this.base64.decode_array(page);
			// trim to correct length
			// perhaps we could use woas.page.original_length field
			// also, we could not make this check if version is 0.10.4
			// but for now it's a good safety
			var rest = page.length % 16;
			if (rest)
				woas.log("NOTICE: removing "+rest+" trailing bytes from page "+title); //log:1
			while (rest-- > 0) {page.pop();}
		} else if (attrs & 8) { // embedded image, not encrypted
			// NOTE: encrypted images are not obviously processed, as per previous 'if'
			if (encoding != "8bit/base64") {
				woas.log("Image "+title+" is not encoded as 8bit/base64");	//log:1
				return false;
			}
			if (mime === null) {
				woas.log("Image "+title+"has no mime type defined");		//log:1
				return false;
			}
			// re-add data:uri to images
			page = "data:"+mime+";base64,"+page;
		} else { // a normal wiki page
			switch (encoding) {
				case "8bit/base64":
					// base64 files will stay encoded
					if (!(attrs & 4))
						// WoaS does not encode pages normally, but this is supported by WSIF format
						page = this.base64.decode(page);
				break;
				case "ecma/plain":
					page = this.ecma_decode(page);
				break;
				case "8bit/plain": // plain wiki pages are supported
				break;
				default:
					woas.log("Normal page "+title+" comes with unknown encoding "+encoding);	//log:1
					return false;
			}
		}
		// check length (if any were passed)
		if (len !== null) {
			if (len != check_len)
				// show a simple log message
				woas.log("Length mismatch for page %s: ought to be %d but was %d".sprintf(title, len, check_len)); //log:1
		}
		// fallback wanted to go to define the page
	break;
	case "external":	// import an external WSIF file
		if (d_fn === null) {
			this.wsif.do_error( "Page "+title+" is external but no filename was specified");
			return false;
		}
		// use last filename to get path
		var the_dir;
		if (path === null) {
			the_dir = this._get_path("filename_");
			if (the_dir === false) {
				this.wsif.do_error( "Cannot retrieve path name in this browser");
				return false;
			}
		} else {
			this.wsif.do_error( "Recursive WSIF import not implemented");
			return false;
		}
		// embedded image/file, not encrypted
		if ((attrs & 4) || (attrs & 8)) {
			if (encoding !== "8bit/plain") {
				this.wsif.do_error( "Page "+title+" is an external file/image but not encoded as 8bit/plain");
				return false;
			}
			// load file and apply base64 encoding if embedded
			var wanted_mode;
			// images
			if ((attrs & 4) && (attrs & 8))
				wanted_mode = this.file_mode.DATA_URI;
			// files
			else if (attrs & 4)
				wanted_mode = this.file_mode.BASE64;
			else // dunnowhat
				wanted_mode = this.file_mode.BINARY;
			page = this.load_file(the_dir+d_fn, wanted_mode, mime);
			if (typeof page != "string") {
				this.wsif.do_error( "Failed load of external "+the_dir+d_fn);
				return false;
			}
			// fallback wanted to apply real page definition later
		} else {
			if (encoding != "text/wsif") {
				this.wsif.do_error( "Page "+title+" is external but not encoded as text/wsif");
				return false;
			}
			// check the result of external import
			var rv = this._native_wsif_load(the_dir+d_fn, locking, _native,
											1, import_hook, title_filter_hook);
			if (rv === false)
				this.wsif.do_error( "Failed import of external "+the_dir+d_fn);
			// return pointer after last read header
			return rv;
		}
	break;
	default: // no disposition or unknown disposition
		this.wsif.do_error( "Page "+title+" has invalid disposition: "+disposition);
		return false;
	} // end of switch

	// create the page object
	var NP = { "title": title, "attrs": attrs, "body": page, "mts": last_mod };
	
	// check that this was imported successfully
	if (import_hook( NP ) ) {
		// all OK
		this.wsif.imported.push(NP.i);
		return true;
	}

	return false;
};

/*** src/legacy.js ***/
// deprecated/legacy functions

//DEPRECATED but still supported
var log = woas.log;

//DEPRECATED
function home() {
	woas.log("WARNING: Called deprecated function: home");
	woas.ui.home();
}

//DEPRECATED
function help() {
	woas.log("WARNING: Called deprecated function: help");
	woas.ui.help();
}

// when Advanced is clicked
//DEPRECATED
function advanced() {
	woas.log("WARNING: Called deprecated function: advanced");
	woas.ui.advanced();
}

//DEPRECATED
function go_to(t) {
	woas.log("WARNING: Called deprecated function: go_to");
	return woas.go_to(t);
}
//DEPRECATED
function go_back() {
	woas.log("WARNING: Called deprecated function: go_back");
	woas.ui.back();
}
//DEPRECATED
function go_forward() {
	woas.log("WARNING: Called deprecated function: go_forward");
	woas.ui.forward();
}
//DEPRECATED
function edit() {
	woas.log("WARNING: Called deprecated function: edit");
	woas.ui.edit();
}

//DEPRECATED
function lock() {
	woas.log("WARNING: Called deprecated function: lock");
	woas.ui.lock();
}

//DEPRECATED
function unlock() {
	woas.log("WARNING: Called deprecated function: unlock");
	woas.ui.unlock();
}

//DEPRECATED
function save() {
	woas.log("WARNING: Called deprecated function: save");
	woas.save();
}

// old tables parsing syntax - DEPRECATED
var reReapTables = /^\{\|.*((?:\n\|.*)*)$/gm,
	reReapTableRows = /\n\|([+ -])(.*)/g;
woas.parser.parse_tables =  function (str, p1) {
	var caption = false,
		stk = [];
	p1.replace(reReapTableRows, function(str, pp1, pp2) {
			if (pp1 == '-')
				return;
			else if (pp1 == '+') {
				caption = caption || pp2;
				return;
			}
			stk.push('<'+'td>' + pp2.split('||').join('<'+'/td><'+'td>') + '<'+'/td>');
		} 
	);
	if (stk.length)
		return  '<'+'table class="woas_text_area">' +
				(caption?('<'+'caption>' + caption + '<'+'/caption>'):'') +
				'<'+'tr>' + stk.join('<'+'/tr><'+'tr>') + '<'+'/tr>' +
			'<'+'/table>';
	return str;
};

/*** src/ui.js ***/
// @module ui
// User Interface bindings
woas.ui = {
	edit_mode: false,		// set to true when inside an edit textarea
	_textbox_focus: false,	// true when a text box is currently focused
	focus_textbox: function() { // called when a textbox has currently focus
		this._textbox_focus = true;
	},
	blur_textbox: function() { // called when a textbox looses focus
		this._textbox_focus = false;
		ff_fix_focus();
		// reset event handler
		this._textbox_enter_event = this._textbox_enter_event_dummy;
	},
	// event (to be overriden) to run in case of enter key pressed
	// for example, searching
	_textbox_enter_event_dummy: function() {
	},
	_textbox_enter_event: null,
	// custom event handler which can be overriden to process the keypresses
	_custom_key_hook: function(orig_e) {
		// continue parsing as normal
		return true;
	},
	
	// event called on key press
	//NOTE: since this is attached directly to DOM, you should not use 'this'
	_keyboard_event_hook: function(orig_e) {
		if (!orig_e)
			e = window.event;
		else
			e = orig_e;
		var ck = woas.browser.ie ? e.keyCode : e.which;
		
		if (!woas.ui.edit_mode) {
			// there is a custom focus active, call the hook
			// and return if it told us to do so
			if (!woas.ui._custom_key_hook(orig_e))
				return orig_e;
			if (woas.ui._textbox_focus) {
				// return key
				if (ck == 13) {
					// clear focus
					ff_fix_focus();
					// run attached event
					(woas.ui._textbox_enter_event)();
					return false;
				}
				return orig_e;
			}
			// back or cancel keys
			if ((ck == woas.hotkey.all.back) || (ck == woas.hotkey.all.cancel)) {
				go_back();
				ff_fix_focus();
				return false;
			}
		}
		// cancel key
		if (ck == woas.hotkey.all.cancel) {
			woas.ui.cancel();
			ff_fix_focus();
			return false;
		}

		return orig_e;
	},
	// could have a better name
	help: function() {
		var wanted_page, pi;
		// we are editing
		if (this.edit_mode) {
			wanted_page = "WoaS::Help::Editor";
			pi = woas.page_index(wanted_page);
		} else {
			var htitle = null;
			// change the target page in some special cases
			if (current.substr(0, 6) === "WoaS::") {
				if (woas.help_system._help_lookup.indexOf(current.substr(6)) !== -1)
					htitle = current.substr(6);
				else
					htitle = current;
			} else htitle = current;
			var npi = woas.page_index("WoaS::Help::"+htitle);
			if (npi !== -1) {
				wanted_page = "WoaS::Help::"+htitle;
				pi = npi;
			} else {
				wanted_page = "WoaS::Help::Index";
				pi = woas.page_index(wanted_page);
			}
		}
		woas.help_system.go_to(wanted_page, pi);
	},
	tables_help: function() {
		woas.help_system.go_to("WoaS::Help::Tables");
	},
	clear_search: function(no_render) {
//		woas.log("Clearing search"); //log:0
		if (current === "Special::Search") {
			d$("string_to_search").value = "";
			d$("string_to_search").focus();
		}
		// clear search results
		woas._cached_body_search = [];
		woas._cached_title_search = [];
		woas._last_search = null;
		woas.pager.bucket.clear();
		if (!no_render)
			this._search_render();
	},
	// when user clicks the about link
	about: function() {
		if (!this.edit_mode)
			woas.go_to("Special::About");
	},
	_search_render: function() {
		// render search results
		if (current === "Special::Search") {
			woas._search_load();
		} else // will call _search_load() on its own
			woas.go_to("Special::Search");
	},
	// used by Special::Import
	_import_xhtml_load: function() {
		this._import_load(0);
	},
	// used by Special::ImportWSIF
	_import_wsif_load: function() {
		this._import_load(3);
	},
	
	_import_load: function(except) {
		if (!woas.config.permit_edits)
			d$("btn_import").disabled = true;
		// restore default settings - with some exceptions
		for(var i=0;i < woas.importer._settings_props.length-except;++i) {
			d$("woas_cb_import"+woas.importer._settings_props[i].substr(1)).checked =
							woas.bitfield.get(woas.config.import_settings, i);
		}
		// restore the overwrite option which covers other 2 bits
		var ovr = 0;
		if (woas.bitfield.get(woas.config.import_settings, woas.importer._OVR_ID))
			ovr += 1;
		if (woas.bitfield.get(woas.config.import_settings, woas.importer._OVR_ID+1))
			ovr += 2;
		var params = ["erase", "ignore", "overwrite", "ask"];
		// apply parameter
		d$('woas_import_'+params[ovr]).checked = true;
	},
	// click on edit icon
	edit: function() {
		woas.edit_page(current);
	},
	cancel: function() {
		if (!this.edit_mode)
			return;
		// there was some change, ask for confirm before cancelling
		if ((woas.get_raw_content() !== woas.change_buffer) ||
			(woas.trim(d$("wiki_page_title").value) !== woas.old_title)) {
			if (!confirm(woas.i18n.CANCEL_EDITING))
				return;
		}
		// we will cancel the creation of last page
		if (woas._ghost_page) {
			// we assume that the last page is the ghost page
			pages.pop();
			page_mts.pop();
			page_titles.pop();
			page_attrs.pop();
			woas._ghost_page = false;
			woas.log("Ghost page disabled"); //log:1
		}
		woas.disable_edit();
		current = woas.prev_title;
	},
	// when back button is clicked
	back: function() {
		if (this.edit_mode)
			return false;
		var p = woas.history.back();
		if (p === null)
			return false;
		woas.history._forward_browse = true;
		return woas.set_current(p, true);
	},
	// when Forward button is clicked
	forward: function() {
		//var _b_current = current,
		var p = woas.history.forward();
		if (p === null)
			return false;
		return woas.set_current(p, true)
	//	if (woas.set_current(p, true))
	//		woas.history.store(_b_current);
	},
	// when home is clicked
	home: function() {
		woas.go_to(woas.config.main_page);
	},
	ns_menu_dblclick: function() {
		if (!woas.config.dblclick_edit)
			return false;
		woas.ui.edit_ns_menu();
		return true;
	},
	edit_ns_menu: function() {
		woas.edit_page(woas.current_namespace+"::Menu");
	},
	lock: function() {
		if (woas.pager.bucket.items.length>1)
			_lock_pages(woas.pager.bucket.items);
		else
			woas.go_to("Lock::" + current);
	},
	unlock: function() {
		if (woas.pager.bucket.items.length>1)
			_unlock_pages(woas.pager.bucket.items);
		else
			woas.go_to("Unlock::" + current);
	},
	// scroll to top of page
	top: function() {
		scrollTo(0,0);
	},
	advanced: function() {
		woas.go_to("Special::Advanced");
	}
};

woas.ui._textbox_enter_event = woas.ui._textbox_enter_event_dummy;

//API1.0
woas.go_to = function(cr) {
	// won't go anywhere while editing!
	if (this.ui.edit_mode)
		return false;
	if (cr === current)
		return true;
	return this.set_current(cr, true)
};

function back_or(or_page) {
	if (!go_back())
		woas.set_current(or_page, true);
}

// when cancel is clicked
function cancel() {
	woas.log("Called deprecated function: cancel");
	woas.ui.cancel();
}

// @module help_system
woas.help_system = {
	popup_window: null,
	page_title: null,
	going_back: false,
	previous_page: [],
	_index_btn: '<'+'input class="woas_button" type="button" value="Index" onclick="help_go_index()" /'+'>',

	_mk_help_button: function(n) {
		var w = "[[Include::WoaS::Template::Button|";
		if (n)
			w += "Back|help_go_back";
		else
			w += "Close|window.close";
		w += "();]]\n";
		return w;
	},

	_help_lookup: ["Plugins", "CSS", "Aliases", "Hotkeys"],
	cPopupCode: "\nfunction get_parent_woas() {\n	if (window.opener && !window.opener.closed)\n		return window.opener.woas;\n	else return null;\n}\nwoas = {\ngo_to: function(page) { var woas = get_parent_woas();\n	if (woas !== null)\n		woas.help_system.go_to(page);\n}\n}\n// used in help popups to access index\nfunction help_go_index() {\n	var woas = get_parent_woas();\n	if (woas === null) return;\n	woas.help_system.go_to('WoaS::Help::Index');\n}\n// used in help popups to go back to previous page\nfunction help_go_back() {\n	var woas = get_parent_woas();\n	if (woas === null) return;\n	if (woas.browser.chrome || woas.browser.safari || woas.browser.opera) {\n		woas.help_system.going_back = true;\n		woas.help_system.go_to(woas.help_system.previous_page.pop());\n		return;\n	}\n	// this works for other browsers\n	scrollTo(0,0);\n	history.go(0);\n}\n",
	go_to: function(wanted_page, pi) {
//		woas.log("help_system.go_to(\""+wanted_page+"\")");	//log:0
		if (typeof pi == "undefined")
			pi = woas.page_index(wanted_page);
		var text;
		// this is a namespace
		if (pi === -1) {
			go_to(wanted_page);
			return;
		} else {
			// see if this page shall be opened in the main wiki or in the help popup
			var _pfx = "WoaS::Help::";
			if (page_titles[pi].substr(0, _pfx.length) === _pfx)
				text = woas.get__text(pi);
			else { // open in main wiki
				go_to(page_titles[pi]);
				return;
			}
		}
		if (text === null)
			return;
		// save previous page and set new
		if (this.going_back)
			this.going_back = false;
		else if (this.page_title !== null)
			this.previous_page.push( this.page_title );
		// now create the popup
		if ((this.popup_window === null) || this.popup_window.closed) {
			this.previous_page = [];
			this.popup_window = woas._customized_popup(wanted_page, this._index_btn+woas.parser.parse(
					this._mk_help_button(0)+text),
					this.cPopupCode,
				"", " class=\"woas_help_background\"");
		} else { // hotfix the page
			this.popup_window.document.title = wanted_page;
			woas.setHTMLDiv(this.popup_window.document.body, this._index_btn+woas.parser.parse(this._mk_help_button(this.previous_page.length)+text));
			this.popup_window.scrollTo(0,0);
		}
		this.page_title = wanted_page;
	}
};

function menu_dblclick() {
	if (!woas.config.dblclick_edit)
		return false;
	edit_menu();
	return true;
}

function page_dblclick() {
	if (!woas.config.dblclick_edit)
		return false;
	edit();
	return true;
}

function edit_menu() {
	woas.edit_page("::Menu");
}

/** Used by search box **/
function menu_do_search() {
	// directly use the search page if it is active
    if (current === "Special::Search") {
		d$('string_to_search').value = d$('menu_string_to_search').value;
    }
    woas.do_search(d$('menu_string_to_search').value);
}

// Used by Special::Search
// make the actual search and cache the results
function ssearch_do_search() {
	var search_string = d$("string_to_search").value;
	if ( !search_string.length )
		return;
	woas.do_search(search_string);
}

function menu_key_hook(orig_e) {
    var e;
    if (!orig_e)
        e = window.event;
    else
        e = orig_e;
	var ck = woas.browser.ie ? e.keyCode : e.which;
    if (ck == 13) {
		ff_fix_focus();
		menu_do_search();
        return false;
     }
     return orig_e;
}

//FIXME: this is entirely a bad hack
function menu_search_focus(f) {
	if (f) {
		// prevent focus on the menu search box when the search page is active
		if (current == "Special::Search") {
//		ff_fix_focus();
			d$('string_to_search').focus();
		} else {
			woas.ui.focus_textbox();
		}
	} else {
		if (current != "Special::Search")
			woas.ui.blur_textbox();
	}
}

//NOTE: this is attached to onkeydown of menu's search box, so you can't use 'this'
woas.do_search = function(str, noclear) {
	// clear previous search results
	if (!noclear)
		woas.ui.clear_search(true);

	woas.progress_init("Searching");
	// reset result pages
	woas.pager.bucket.clear();

	// cache new search results
	woas._cache_search( str );
	woas.progress_finish();

	// refresh the search page, or go to it if we are not
	woas.ui._search_render();
};

// Used by Special::Options page
function save_options() {
	if (!woas.config.permit_edits) {
		alert(woas.i18n.READ_ONLY);
		return false;
	}
	woas.cfg_commit();
	woas.set_current("Special::Advanced", true);
}

function ro_woas() {
	if (!woas.config.permit_edits) {
		alert(woas.i18n.WRITE_PROTECTED);
		return false;
	}
	if (confirm(woas.i18n.CONFIRM_READ_ONLY)) {
		woas.config.permit_edits = false;
		woas.cfg_commit();
		woas.set_current("Special::Advanced", true);
	}
}

// Used by Special::Lock
function lock_page(page) {
	var pwd = d$("pw1").value;
	if (!pwd.length) {
		d$("pw1").focus();
		return;
	}
	if (pwd !== d$("pw2").value) {
		d$("pw2").focus();
		return;
	}
	var pi = woas.page_index(page);
	woas.AES.setKey(pwd);
	woas._finalize_lock(pi);
}

// import wiki from external file
function import_wiki() {
	if (!woas.config.permit_edits) {
		alert(woas.i18n.READ_ONLY);
		return false;
	}
	woas.import_wiki();
	woas.refresh_menu_area();
}

function set_key() {
	woas._set_password();
}

// below function is used by Special::Lock

var _pw_q_lock = false;

function pw_quality() {

	if (_pw_q_lock)
		return;
		
	_pw_q_lock = true;

// used to get a red-to-green color tone
function _hex_col(tone) {
	var s=Math.floor(tone).toString(16);
	if (s.length==1)
		return "0"+s;
	return s;
}

	// original code from http://lxr.mozilla.org/seamonkey/source/security/manager/pki/resources/content/password.js
	var pw=d$('pw1').value;

	//length of the password
	var pwlength=pw.length;
	
	if (pwlength!==0) {

	//use of numbers in the password
	  var numnumeric = pw.match(/[0-9]/g);
	  var numeric=(numnumeric!==null)?numnumeric.length/pwlength:0;

	//use of symbols in the password
	  var symbols = pw.match(/\W/g);
	  var numsymbols= (symbols!==null)?symbols.length/pwlength:0;

	//use of uppercase in the password
	  var numupper = pw.match(/[^A-Z]/g);
	  var upper=numupper!==null?numupper.length/pwlength:0;
	// end of modified code from Mozilla
	
	var numlower = pw.match(/[^a-z]/g);
	var lower = numlower!==null?numlower.length/pwlength:0;
	
	var u_lo = upper+lower;
	  
	// 80% of security defined by length (at least 16, best 22 chars), 10% by symbols, 5% by numeric presence and 5% by upper case presence
	var pwstrength = ((pwlength/18) * 65) + (numsymbols * 10 + u_lo*20 + numeric*5);
	
	var repco = woas.split_bytes(pw).toUnique().length/pwlength;
	if (repco < 0.8)
		pwstrength *= (repco+0.2);
//		woas.log("pwstrength = "+(pwstrength/100).toFixed(2)+", repco = "+repco);	// log:1
	} else
		pwstrength = 0;
  
	if (pwstrength>100)
		color = "#00FF00";
	else
		color = "#" + _hex_col((100-pwstrength)*255/100) + _hex_col((pwstrength*255)/100) + "00";
  
	d$("pw1").style.backgroundColor = color;
	woas.setHTMLDiv(d$("txtBits"), "Key size: "+(pwlength*8).toString() + " bits");
	
	_pw_q_lock = false;
}

// used by embedded file show page
function show_full_file(pi) {
	var text = woas.get__text(pi);
	if (text===null)
		return;
	// put WoaS in loading mode
	woas.progress_init("Loading full file");
	// clear the partial display and put in the whole file content
	woas.setHTML(d$('_part_display'), '');
	woas.setHTML(d$('woas_file_ct'), woas.xhtml_encode(woas.base64.decode(text)));
	// finished loading the file
	woas.progress_finish();
}

function query_export_file(cr) {
	var fn = cr.substr(cr.indexOf("::")+2);
	if (confirm(woas.i18n.CONFIRM_EXPORT.sprintf(cr)+"\n\n"+woas.ROOT_DIRECTORY+fn))
		woas.export_file(cr, woas.ROOT_DIRECTORY+fn);
}

function query_export_image(cr) {
	var img_name = cr.substr(cr.indexOf("::")+2);
	if (confirm(woas.i18n.CONFIRM_EXPORT.sprintf(img_name)+"\n\n"+woas.ROOT_DIRECTORY+img_name))
		woas.export_image(cr, woas.ROOT_DIRECTORY+img_name);
}

function query_delete_file(cr) {
	if (!confirm(woas.i18n.CONFIRM_DELETE.sprintf(cr)))
		return;
	// do not check for plugin deletion here
	woas.delete_page(cr);
	back_or(woas.config.main_page);
}

// delayed function called after page loads and runs the script tag
function _img_properties_show(mime, tot_len, enc_len, mts) {
	var img=d$('woas_img_tag');
	woas.setHTML(d$('woas_img_desc'),
		woas.i18n.MIME_TYPE+": "+mime+"<"+"br /"+
		">"+woas.i18n.FILE_SIZE+": about "+_convert_bytes(((tot_len-enc_len)*3)/4)+
		woas.i18n.B64_REQ.sprintf(_convert_bytes(tot_len))+
	"<"+"br />"+woas.last_modified(mts)+
	"<"+"br />"+woas.i18n.WIDTH+": "+img.width+"px<"+"br />"+woas.i18n.HEIGHT+": "+img.height+"px");
}

function query_delete_image(cr) {
	if (!confirm(woas.i18n.CONFIRM_DELETE_IMAGE.sprintf(cr)))
		return;
	// do not check for plugin deletion here
	woas.delete_page(cr);
	back_or(woas.config.main_page);
}

// triggered by UI graphic button
function page_print() {
	woas._customized_popup(current, woas.getHTMLDiv(d$("woas_wiki_area")),
			'function go_to(page) { alert("'+woas.js_encode(woas.i18n.PRINT_MODE_WARN)+'");}');
}

woas._customized_popup = function(page_title, page_body, additional_js, additional_css, body_extra) {
	var css_payload = "";
	if (woas.browser.ie && !woas.browser.ie8) {
		if (woas.browser.ie6)
			css_payload = "div.woas_toc { align: center;}";
		else
			css_payload = "div.woas_toc { position: relative; left:25%; right: 25%;}";
	} else
		css_payload = "div.woas_toc { margin: 0 auto;}\n";
	
	if (additional_js.length)
		additional_js = woas.raw_js(additional_js);
	// create the popup
	return woas.popup(
		"print_popup",
		Math.ceil(screen.width*0.75),
		Math.ceil(screen.height*0.75),
		",status=yes,menubar=yes,resizable=yes,scrollbars=yes",
		// head
		"<"+"title>" + page_title + "<"+"/title>" + "<"+"style type=\"text/css\">"
		+ css_payload + woas.css.get() + additional_css +
		"<"+"/sty" + "le>" + additional_js,
		page_body,
		body_extra
	);
};

// below functions used by Special::Export

woas.export_wiki_wsif = function () {
	var path, author, single_wsif, inline_wsif;
	try {
		path = d$("woas_ep_wsif").value;
		author = this.trim(d$("woas_ep_author").value);
		single_wsif = d$("woas_cb_single_wsif").checked ? true : false;
		inline_wsif = d$("woas_cb_inline_wsif").checked ? true : false;
	} catch (e) { this.crash(e); return false; }
	
	var done = this._native_wsif_save(path, this.wsif.DEFAULT_INDEX, false, single_wsif, inline_wsif, author, false);

	this.alert(this.i18n.EXPORT_OK.sprintf(done, this.wsif.expected_pages));
	return true;
};

// workaround to get full file path on FF3
// by Chris
function ff3_getPath(fileBrowser) {
	try {
		netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
	} catch (e) {
	    alert('Unable to access local files due to browser security settings. '
	    +'To overcome this, follow these steps: (1) Enter "about:config" in the URL field; '+ 
	    '(2) Right click and select New->Boolean; (3) Enter "signed.applets.codebase_principal_support" '+
	     '(without the quotes) as a new preference name; (4) Click OK and try loading the file'+
	     ' again.');
	    return false;
	}
	var fileName=fileBrowser.value;
	return fileName;
}

// create a centered popup given some options
woas.popup = function(name,fw,fh,extra,head,body, body_extra) {
	if (typeof body_extra == "undefined")
		body_extra = "";
	var hpos=Math.ceil((screen.width-fw)/2);
	var vpos=Math.ceil((screen.height-fh)/2);
	var wnd = window.open("about:blank",name,"width="+fw+",height="+fh+		
	",left="+hpos+",top="+vpos+extra);
	wnd.focus();
	wnd.document.writeln(this.DOCTYPE+"<"+"html><"+"head>"+head+
						"<"+"/head><"+"body"+body_extra+">"+
						body+"<"+"/body></"+"html>\n");
	wnd.document.close();
	return wnd;
};

// tell user how much work was already done
woas.progress_status = function (ratio) {
	// no progress indicators in debug mode
	if (this.config.debug_mode) return;
	this.setHTML(d$("woas_wait_text"), this._progress_section + "\n" +
				Math.ceil(ratio*100)+"% done");
};

// used to debug progress indicators
woas._progress_section = false;

// reset progress indicator
woas.progress_init = function(section) {
	if (this._progress_section !== false) {
		this.crash("Progress indicator already started for "+this._progress_section+
					", will not start a new one for "+section);
		return;
	}
	this._progress_section = section;
	if (typeof section == "undefined")
		section = "";
	else section = "\n" + section;
	// no progress indicators in debug mode
	if (this.config.debug_mode) return;
	this.setHTML(d$("woas_wait_text"), section);
	document.body.style.cursor = "wait";
	// put in busy mode and block interaction for a while
	d$.show("loading_overlay");
	d$("loading_overlay").focus();
};

woas.progress_finish = function(section) {
	if (this._progress_section === false) {
		this.crash("Cannot finish an unexisting progress indicator section");
		return;
	}
	// no progress indicators in debug mode
	if (!this.config.debug_mode) {
		document.body.style.cursor = "auto";
		this.setHTML(d$("woas_wait_text"), this.i18n.LOADING);
		// hide the progress area
		d$.hide("loading_overlay");
	}
	this._progress_section = false;
};

function search_focus(focused) {
	if (focused) {
		woas.ui._textbox_enter_event = ssearch_do_search;
		woas.ui.focus_textbox();
	} else {
		woas.ui.blur_textbox();
		ff_fix_focus();
	}
}

woas._hl_marker = _random_string(10)+":%d:";

woas._hl_marker_rx = new RegExp(woas._hl_marker+":(\\d+):", "g");

// display search results
woas._search_load = function() {
	woas.log("called _search_load()");
	var P = {body: ""};
	if (this._last_search === null) {
//		woas.log("No search done, returning blank");	//log:0
	} else {
		// proceed to parsing if there are matching pages
		if (this._cached_title_search.length + this._cached_body_search.length !== 0) {
		
			// (1) prepare the title results
			for(var i=0,it=this._cached_title_search.length;i<it;++i) {
				P.body += "* [["+ this._cached_title_search[i] + "]]\n";
				woas.pager.bucket.add(this._cached_title_search[i]);
			}
			
			// (2) parse the body snippets
			for(var i=0,it=this._cached_body_search.length;i<it;++i) {
				P.body += "* [[" + this._cached_body_search[i].title + "]]: found " + this._hl_marker+":"+i+":";
				woas.pager.bucket.add(this._cached_body_search[i].title);
			}

			P.body = 'Results for <'+'strong class="woas_search_highlight">' + woas.xhtml_encode(woas._last_search) + "<"+"/strong>\n" + P.body;
		} else
			P.body = "/No results found for *"+woas.xhtml_encode(woas._last_search)+"*/";

	}

	// position cursor back in search box
	d$("string_to_search").focus();
	
	if (P.body.length) {
		// parse results before applying search terms highlighting
		woas.parser.syntax_parse( P, [] );
		
		P.body = P.body.replace(this._hl_marker_rx, function(str, i) {
			var r="",count=0;
			for(var a=0,at=woas._cached_body_search[i].matches.length;a<at;++a) {
				r += "<"+"pre class=\"woas_nowiki woas_search_results\">" +
						// apply highlighting
						woas._cached_body_search[i].matches[a].replace(woas._reLastSearch, function(str, $1) {
							++count;
								return '<'+'span class="woas_search_highlight">'+$1+'<'+'/span>';
						})+"\n<"+"/pre>";
			}
			return " <"+"strong>"+count+"<"+"/strong> times: "+r;
		});
	}
	
	// finally output XHTML content
	woas.setHTMLDiv(d$('woas_search_results'), P.body);
};

var _servm_shown = false;

function _servm_alert() {
	if (woas._server_mode) {
		// show the message only once
		if (!_servm_shown) {
			woas.alert(woas.i18n.SERVER_MODE);
			_servm_shown = true;
		}
	}
}

woas.update_nav_icons = function(page) {
	this.menu_display("back", this.history.has_backstack());
	this.menu_display("forward", this.history.has_forstack());
	this.menu_display("advanced", (page != "Special::Advanced"));
	this.menu_display("edit", this.edit_allowed(page));
	this.update_lock_icons(page);
};

woas.update_lock_icons = function(page) {
	var cyphered, can_lock, can_unlock;
	if (woas.pager.bucket.items.length < 2) {
		var pi = this.page_index(page);
		if (pi==-1) {
			can_lock = can_unlock = false;
			cyphered = false;
		} else {
			can_unlock = cyphered = this.is__encrypted(pi);
			can_lock = !can_unlock && this.config.permit_edits;
		}
	} else {
		can_lock = can_unlock = (woas.pager.bucket.items.length>0);
		cyphered = false;
	}
	
	// update the encryption icons accordingly
	this.menu_display("lock", !woas.ui.edit_mode && can_lock);
	this.menu_display("unlock", !woas.ui.edit_mode && can_unlock);
	// we can always input decryption keys by clicking the setkey icon
	//this.menu_display("setkey", cyphered);
	var cls;
	if (cyphered || (page.indexOf("Locked::")==0))
		cls = "woas_text_area locked";
	else
		cls = "woas_text_area";
	d$("woas_wiki_area").className = cls;
};

// when the page is resized
woas._onresize = function() {
	var we = d$("woas_editor");
	we.style.width = window.innerWidth - 30 + "px";
	we.style.height = window.innerHeight - 150 + "px";
};

if (!woas.browser.ie)
	window.onresize = woas._onresize;

woas._set_debug = function(status) {
	if (status) {
		// activate debug panel
		d$.show_ni("woas_debug_panel");
		d$.show("woas_debug_log");
		// hide the progress area
		d$.hide("loading_overlay");
	} else {
		d$.hide_ni("woas_debug_panel");
		d$.hide("woas_debug_console");
	}
};

woas.refresh_menu_area = function() {
	var tmp = this.current_namespace;
 	this.current_namespace = this.parser.marker;
	this._add_namespace_menu(tmp);
	var menu = this.get_text("::Menu");
	if (menu == null)
		this.setHTMLDiv(d$("woas_menu_area"), "");
	else {
		this.parser._parsing_menu = true;
		this.setHTMLDiv(d$("woas_menu_area"), this.parser.parse(menu, false, this.js_mode("::Menu")));
		this.parser._parsing_menu = false;
		this.scripting.clear("menu");
		this.scripting.activate("menu");
	}
};

woas._gen_display = function(id, visible, prefix) {
	if (visible)
		d$.show(prefix+"_"+id);
	else
		d$.hide(prefix+"_"+id);
};

woas.img_display = function(id, visible) {
	if (!this.browser.ie || this.browser.ie8) {
		this._gen_display(id, visible, "img");
		this._gen_display(id, !visible, "alt");
	} else {
		this._gen_display(id, !visible, "img");
		this._gen_display(id, visible, "alt");
	}
};

woas.menu_display = function(id, visible) {
	this._gen_display(id, visible, "menu");
//	log("menu_"+id+" is "+d$("menu_"+id).style.display);
};

woas.refresh_mts = function(mts) {
	// generate the last modified string to append
	if (mts) {
		this.setHTMLDiv(d$("woas_mts"), this.last_modified(mts));
		d$.show("woas_mts");
	} else
		d$.hide("woas_mts");
};

woas._set_title = function (new_title) {
	this.setHTMLDiv(d$("woas_title"), this.create_breadcrumb(new_title));
	document.title = new_title;
};

// function which hooks all messages shown by WoaS
// can be fed with multiple messages to show consecutively
woas.alert = function() {
	for(var i=0,l=arguments.length;i < l;++i) {
		alert("WoaS: "+arguments[i]);
	}
};

// same as above, but for unhandled errors
woas.crash = function() {
	for(var i=0,l=arguments.length;i < l;++i) {
		alert("WoaS Unhandled error\n----\n"+arguments[i]);
	}
};

// called from Special::Lock page
function _lock_page() {
	// do not call if not on a page locking context
	if (current.indexOf("Lock::")!==0)
		return;
	var page = current.substring(6);

	d$("btn_lock").value = "Lock "+page;
	d$("pw1").focus();
	//TODO: check for other browsers too
	if (woas.browser.firefox)
		d$("btn_lock").setAttribute("onclick", "lock_page('"+woas.js_encode(page)+"')");
	else
		d$("btn_lock").onclick = woas._make_delta_func('lock_page', "'"+woas.js_encode(page)+"'");
}

function _woas_new_plugin() {
	var title = woas._prompt_title("Please enter plugin name", "Myplugin");
	if (title === null)
		return;
	var def_text;
	// provide special include page support
	// --UNSUPPORTED FEATURE--
	if (title.charAt(0) === '@') {
		def_text = "plugins/"+title.substr(1)+".js\n";
//		title = title.substr(1);
	} else {
		def_text = "/* "+title+" plugin */\n";
	}
	woas._create_page_direct("WoaS::Plugins", title, false, def_text);
	// now we will be editing the plugin code
}

// get file URL from input XHTML element
// this might not work on some browsers
// not to be called for Mozilla-based browsers
woas.get_input_file_url = function() {
	var r = false;
	if (this.browser.opera || this.browser.webkit) {
		// ask user for path, since browser do not allow us to see where file really is
		r = d$("filename_").value;
		r = prompt(this.i18n.ALT_BROWSER_INPUT.sprintf(this.basename(r)), this.ROOT_DIRECTORY);
		if ((r === null) || !r.length)
			r = false;
		else
			this._last_filename = r;
	} else { // we have requested a direct read of the file from the input object
		r = d$("filename_").value;
		if (!r.length)
			r = false;
	}
	if (r === false)
		this.alert(this.i18n.FILE_SELECT_ERR);
	return r;
};

/*
woas.css.ff2 (string:valid CSS): css added if browser == ff2 when !raw 
woas.css.set(css, raw)
  css (string:valid CSS): the raw css to be set; replaces old css
  raw (boolean, optional): if true browser fixes will not be applied to css
woas.css.get(): returns currently set CSS (string:valid CSS)
*/
//FIXME: could be part of woas.ui object
woas.css = {
	FF2: "\n.woas_nowiki { white-space: -moz-pre-wrap !important; }\n",
	IE: "\n.woas_nowiki { word-wrap: break-word !important; }\n",
	OPERA: "\n.woas_nowiki { white-space: -o-pre-wrap !important; }\n",
	
	// TODO: replace with factory function for just this browser
	set: function(css, raw) {
		raw = raw || false;
		/*
		This object could become much better in the future, grabbing content
		from appropriate sources, or filtering WoaS::CSS (whatever) to match
		the current browser ... this need only be done once on loading page,
		with a callback function for CSS editing (e.g. woas.css.load and/or
		woas.css.merge, etc.). Note that this change does not affect efficiency
		much but now we can completely change the way this works (over time)
		without having to modify other code. This is generally applicable
		with OOP design concepts. The code is therefore much less fragile
		and many programmers can work on it with changes tending to be much
		more confined to the object they are modifying.
		*/
		// add some browser-specific wrapping fixes
		if (!raw) {
			if (woas.browser.firefox2)
				// fixes are added first so they can be overridden
				// (although they have an !important property attribute)
				css = this.FF2 + css;
			else if (woas.browser.trident)
				css = this.IE + css;
			else if (woas.browser.presto)
				css = this.OPERA + css;
		}
		if (woas.browser.ie)
			woas.dom._cache.stylesheet.cssText = css;
		else {
			if (woas.browser.chrome || woas.browser.safari)
				woas.dom._cache.stylesheet.innerText = css;
			else
				woas.dom._cache.stylesheet.innerHTML = css;
		}
	},
	
	// Not used/needed in public API; can't easily fix this with current code.
	// Can't see this being a problem though.
	get: function() {
		if (woas.browser.ie)
			return woas.dom._cache.stylesheet.cssText;
		// on Chrome/Safari innerHTML contains br tags
		if (woas.browser.chrome || woas.browser.safari)
			return woas.dom._cache.stylesheet.innerText;
		return woas.dom._cache.stylesheet.innerHTML;
	}
};

/*** src/editor.js ***/
// @module ui.editor
// this sub-module shall contain the whole editor code
woas.ui.editor = {
	_insert_ruler: function() {
		TagThis("\n----\n", "");
	}
};

woas.wiki_buttons_display = function (v) {
	d$('wiki_format_buttons').style.display = v ? 'block' : 'none';
	d$('wiki_format_buttons').style.visibility = v ? 'visible' : 'hidden';
};

woas.html_buttons_display = function (v) {
	d$('html_format_buttons').style.display = v ? 'block' : 'none';
	d$('html_format_buttons').style.visibility = v ? 'visible' : 'hidden';
};

// class for managing textarea selection - by pr0xy
function TextAreaSelectionHelper(obj) {
	this.target=obj;
	this.target.carretHandler=this; // ?
	this.target.onchange=_textareaSaver;
	this.target.onclick=_textareaSaver;
	this.target.onkeyup=_textareaSaver;
	this.target.onfocus=_textareaSaver;
	if(!document.selection)
		this.target.onSelect=_textareaSaver; // ?
 
	this.start=-1;
	this.end=-1;
	this.scroll=-1;
	this.iesel=null;
}

TextAreaSelectionHelper.prototype.getSelectedText=function() {
	if (this.iesel)
		return this.iesel.text;
	// Fixes a problem in FF3 where the selection was not being stored in this.start and this.end when selecting multilines
	this.start = d$("woas_editor").selectionStart;
	this.end = d$("woas_editor").selectionEnd;
	return ((this.start>=0)&&(this.end>this.start))? this.target.value.substring(this.start,this.end): "";
};

TextAreaSelectionHelper.prototype.setSelectedText=function(text, secondtag) {
	if(this.iesel) {
	if(typeof(secondtag)=="string") {
	  var l=this.iesel.text.length;
		 this.iesel.text=text+this.iesel.text+secondtag;
	  this.iesel.moveEnd("character", -secondtag.length);
	   this.iesel.moveStart("character", -l);   
	} else {
	  this.iesel.text=text;
	}
	this.iesel.select();
 } else if(this.start>=0&&this.end>=this.start) {
    var left=this.target.value.substring(0,this.start);
    var right=this.target.value.substr(this.end);
 var scont=this.target.value.substring(this.start, this.end);
 if(typeof(secondtag)=="string") {
   this.target.value=left+text+scont+secondtag+right;
   this.end=this.target.selectionEnd=this.start+text.length+scont.length;
   this.start=this.target.selectionStart=this.start+text.length;    
 } else {
      this.target.value=left+text+right;
   this.end=this.target.selectionEnd=this.start+text.length;
   this.start=this.target.selectionStart=this.start+text.length;
 }
 this.target.scrollTop=this.scroll;
 this.target.focus();
 } else {
   this.target.value+=text + ((typeof(secondtag)=="string")? secondtag: "");
if(this.scroll>=0) this.target.scrollTop=this.scroll;
 }
};

TextAreaSelectionHelper.prototype.getText=function() {
	return this.target.value;
};

TextAreaSelectionHelper.prototype.setText=function(text) {
	this.target.value=text;
};

function _textareaSaver() {
	if (document.selection) {
		this.carretHandler.iesel = document.selection.createRange().duplicate();
	} else if(typeof(this.selectionStart)!="undefined") {
		this.carretHandler.start=this.selectionStart;
		this.carretHandler.end=this.selectionEnd;
		this.carretHandler.scroll=this.scrollTop;
	} else
		this.carretHandler.start=this.carretHandler.end = -1;
}

function DivTagThis(align) {
	TagThis('<'+'div align="'+align+'">', '<'+'/div>');
}

function TagThis(starttag, endtag){
	woas._editor.setSelectedText(starttag, endtag);
}

function FullTagThis(tag){
	woas._editor.setSelectedText('<'+tag+'>','<'+'/'+tag+'>');
}

function setUrl(starttag,centertag,endtag) {
	var url=prompt('Link:','http://');
	if (url===null) return;
	var comm=prompt('Link text:','');
	if (comm===null) return;
		woas._editor.setSelectedText(starttag+woas.js_encode(url)+centertag,comm+endtag);
}

function setWikiImage() {
	setImage('[[Include::Image::',']]');
}

function setHTMLImage() {
	setImage('<'+"img src='","' /"+">");
}

function setWikiUrl() {
	setUrl('[[','|',']]');
}

function setWikiIUrl() {
	var url=prompt('Wiki page to link to:','');
	if (url===null) return;
	var comm=prompt('Link text:', url);
	if (comm===null) return;
	if (comm == url)
		woas._editor.setSelectedText('[['+url+']]');
	else
		woas._editor.setSelectedText('[['+url+'|',comm+']]');
}

function setHTMLUrl() {
	setUrl('<'+'a href="','" target="_blank">','<'+'/a>');
}

function setImage(starttag,endtag) {
	var pic=prompt('Image:','');
	if (pic!==null)
		woas._editor.setSelectedText(starttag,woas.js_encode(pic)+endtag);
}

function setTag() {
        var tag=prompt('Set tag:','');
	if (tag!==null)
	woas._editor.setSelectedText("[[Tag::",tag+"]]");
}


/* ]]> */ </script></head><body class="woas_background" id="body" style="cursor: auto;" onload="woas._on_load()" onunload="woas._on_unload()">
<div style="display: inline; visibility: visible;" id="loading_overlay"><noscript><h1>If you are seeing this text, your browser is not javascript-enabled.</h1></noscript><div id="woas_wait_text">Loading Wiki on a Stick...</div></div>
<div style="display: none; visibility: hidden;" id="woas_pwd_mask">
<div style="visibility: visible;" id="woas_pwd_query">
<form id="stealthpwd" action="" onsubmit="woas._password_ok(); return false">
<h3>Enter a decryption password</h3>
<p><input name="woas_password" id="woas_password" size="16" type="password" /></p>
<p class="woas_password_desc">Password will be used next time you attempt to access an encrypted page, and then kept or removed second the AES key caching option you have chosen.</p>
<p><input value="OK" type="submit" />&nbsp;<input value="Cancel" onclick="woas._password_cancel()" class="woas_button" type="button" /></p>
</form>
</div>
</div>
<div class="woas_background" id="woas_wiki_header" style="position: absolute;">
<table width="100%" cellspacing="0">
<tbody><tr>
<td style="width: 35px;" id="woas_logo"><img style="display: inline; visibility: visible;" id="img_logo" alt="WoaS Logo" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J%2FAAAAAXNSR0IArs4c6QAAAAJiS0dEAP%2BHj8y%2FAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH2gEcDzUY4t9JowAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAI7SURBVEjHtdVPSFRRFAbw33ujljETWhaGo9YiCGoVuHDTIggE2xRBbgeEoFWQFARBq3ZCELQKgqJtbWtTEAitgqBNCZJFGGb%2B18Hxz20xr3HSmXEU%2Bt7ivnvf%2B757z3feOydSAZNh2QrW0IAm%2Bx2LVMS25e9h0UbFF5u1aI1qCvwMs9ZVRyStK6oq8D3M2xn7HdYSVRAYC3n1ocHRslDi4vCtbjprpspmcTH2BbtBwddQJjATZu0Wi36FvwFZqOk861atbUttvDku1SAHeUsKFb6MueQM8c%2BwUcOuJatVnxYDb8hX3XtFoWZoxZM3LFkpW0wlfmxY29HIvNnQEsV5fQql62IyVqO%2FknPegNtgFfG6wyZ80GtaQRrjcnpdN5eQhvTrNwaG5Tx3S0%2BSH%2BIga9SItA8mZTGs21MZTxKBnMfuJLOsLzLOupL4RJySNeadGyWBT645adAbMO6xnCHvwUNcMmg0cYy4Ubc30vp9NaG7FG1TYueQHve9sAjSBr10xj3QiLhZp1F96PRWJ854ZsIjF8CK07LeYQ4PfJRyCuzTGhEfkJVyAeeMyOKmTy5bkAODbhsw4ZyrGDekz2t3kSnWg5nwWbAXHHcsIm6NDu2J%2FrfMxrRJ7UGgffNvPBQd2TU9UyrzMZyIDu6K3qRja13o0Fw3PaWrrKiWbmbCDwt17d7lSFSlsYyG3zukNKNjS3fa0md%2BhUnzVRPXXqFDVmiZU2HOouV%2Fjn1QRntUV3PdxHRYRUpb5H%2FiD%2FUerIBA%2BEx5AAAAAElFTkSuQmCC" /></td>
<td><span id="woas_title"></span></td>
<td align="right">
<div id="menu_back" style="display: inline; cursor: pointer; visibility: visible;"><a title="Back" onclick="woas.ui.back()"><img id="img_back" style="display: inline; visibility: visible;" alt="Back" src="data:image/png;base64, " /><span id="alt_back" style="display: none; visibility: hidden;">&nbsp;<strong>[Back]</strong>&nbsp;</span></a></div>
<div id="menu_forward" style="display: none; cursor: pointer; visibility: hidden;"><a title="Forward" onclick="woas.ui.forward()"><img id="img_forward" style="display: inline; visibility: visible;" alt="Forward" src="data:image/png;base64, " /><span id="alt_forward" style="display: none; visibility: hidden;">&nbsp;<strong>[Forward]</strong>&nbsp;</span></a></div>
<div id="menu_advanced" style="display: inline; cursor: pointer; visibility: visible;"><a title="Advanced" onclick="woas.ui.advanced()"><img id="img_advanced" style="display: inline; visibility: visible;" alt="Advanced" src="data:image/png;base64, " /><span id="alt_advanced" style="display: none; visibility: hidden;">&nbsp;<strong>[Advanced]</strong>&nbsp;</span></a></div>
<div id="menu_home" style="display: inline; cursor: pointer; visibility: visible;"><a id="woas_home_hl" title="Let's Build a Roguelike" onclick="woas.ui.home()"><img id="img_home" style="display: inline; visibility: visible;" alt="Let's Build a Roguelike" src="data:image/png;base64, " /><span id="alt_home" style="display: none; visibility: hidden;">&nbsp;<strong>[Home]</strong>&nbsp;</span></a></div>
<div id="menu_unlock" style="display: none; cursor: pointer; visibility: hidden;"><a title="Unlock" onclick="woas.ui.unlock()"><img id="img_lock" style="display: inline; visibility: visible;" alt="Unlock" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA%2FwD%2FAP%2BgvaeTAAAEnUlEQVR42o2WX2wUVRTGf3NnZ7a7tkvTdA1rlxSWthgwEpoawh9jS8SHxofqAyaGBzBBE15IBLTxwfgCGBITfZAXk%2FYBjDE09QGJpqKFGBKiDYkuVChd7KbbbbtLy3bb2dk%2Fs3N96M52t63ASU5m5t7c853zne%2FeOwoV9vDhwxpWWSgUypVelYphWfKnmnACrxd8z549rwB2yYslfwuoB9zAayWgXYBrXQQneKU3NzfvcrLs7%2B%2BXsVhMTk1NyVu3bsmK7N8EZCqVcr4PlUDXVlBpBw4ceDEajd7u7e0tJpNJDh48SDqdJpVK4ff7iUQi9Pf3A1wBWFpa4s6dOwDfA92rQdYAzM3NvQRw%2BvRp9erVq1YwGGT79u3s2LGDrVu3Eg6H87t376avr6%2B8ZnZ2lgcPHgAMAi2V%2FaribXp6WllcXLw4MTHByMgIR44ccW3YsOFDr9c76vP54vF4vKOnp6cvGo3S1NRUlVhrayvAp6vEsLYCALd7pUqPx3O%2FpaVl9OjRoxPnzp27%2FH9qOX78OMAMMF2psKoKxsfHBYBpmuUxr9c729raagQCAftJcrxw4QKACeQrx8sA4XBYDAwM1K5eaBiGb3h4uOn69es2z2bFdQG%2B%2FOiDd4bvz%2FQBDA4OkkgknAb%2BtjrCsWPHqK1dyUXXdS5dusThw4cvAreBfxyaFGeDhUIhMxUfJT8%2Fibn4iHzGIJtZYPFxCjOTAUDTNPQaN8Klorlr2NXTSywWI5FI0N7eDvAV8DUQKW3O6h4ouTS59AxGIkrWWCLzOMlcMsGSsQQSPJ4adK8HVRVo6spJUVdXx82bN9m3b98J4LsSwFoV2baNLC5TKG2wFXtZcxKEEKAoKELBtiWvn%2FqBzz45iRCCuro6VFV1wmwCtHVVBBIpJUiJlEViMynGJmYxM1kE0FBfy6aNcPZKnEgkghCCTIm%2BxsZGhoeH6erqugy8CvwJ5KopEipCCBShIIQk8Hw9HpEjs7CIRCBqdHq%2FvceNGzcoFAqMj49jWRaKoqDrOps3b2ZkZISOjo7fgZ3AXREKhbIrHBWxihZWvoCVL%2FDocZrJxBJT81kSCxne%2FfxXzp45g5TLlWqaRltbG21tbWzZsoX6%2Bnr8fj%2Fnz58H%2BAtocJUUlF0mSEFVVFSXC6G6aPR5EVkPGbeNXVJ3Z1cXhmGgKArbtm1D1%2FXl6hWl%2FOzs7HRS3ljdA9tCFjIUcovkc2mi8QTzyXlMw0RzucoBHDdNk0AgAEA0Gi2PO6BA%2FaoeKEhVQwg3Lk0nuLGBWlVimu4qwTmBQqEQwBeAbG5uPjU5OYmiKOTz%2Bf857Io2kiKyWMAqFLByuVJPLGTRAuCXoaEyHQCH3mg%2FeeLtnaecHW1ZFkNDQ870QtU9%2B%2FM3HzPx7zjZhSRZM0sulyWfzVKwiggUajw6g6Murl27hmEYaJpGMBgEIBaLoes6Y2Nj7N%2B%2FH%2BA94McywMt%2B%2FYW%2Fk%2FmpZznN9u7dy8DAALa9cv6Fw2GklHR3dwP0Aj8BdysrUAAV0EvP1X8Slfacz%2BeLp9Pp9ebeB%2F4A7gE55SnJPmneBTQAAaDO4bx06cwDFsB%2F1pf2UYdfB6cAAAAASUVORK5CYII%3D" /><span id="alt_unlock" style="display: none; visibility: hidden;">&nbsp;<strong>[Unlock]</strong>&nbsp;</span></a></div>
<div id="menu_lock" style="display: inline; cursor: pointer; visibility: visible;"><a title="Lock" onclick="woas.ui.lock()"><img id="img_unlock" style="display: inline; visibility: visible;" alt="Lock" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAB3RJTUUH1wMbFisGhO%2Br5wAAAAlwSFlzAAALEwAACxMBAJqcGAAAAARnQU1BAACxjwv8YQUAAANZSURBVHja3VTPT1NZGD3vvZYWGGqVNgKyEBZqwsRYtRpjJpkdMbNk5YrMZjYQF27c%2Bxe4MCxcIJmdyRAjGxMX%2FohKUGMgMAt%2BTCJ0CtqR2tLS9%2Fp%2B3eu5r8UEZphS48qbnL57b9895%2FvO970LfK%2FjKjFJlIhHxH0i9a3Ib%2Fb396%2BPj4%2FLjY0NOTMzIycmJvxEImHyv1%2BbJTP2rG8PDw9fn5ycjPX19aFYLCIWi6Gnp0cbGBgI5%2FP5XzKZjMb3nn1N5FfS6bRZKBTk%2FPx8ZWho6C337GQymR0ZGbHn5ubk1NSUHBwcLNctbHo8YHRycXFRplKpP7ieIS4TP8Xj8eejo6OmsmtsbMzh3r2m2elxMZfLyenp6QqXK8TPRCuhLEn39vZm19bWpKoN1wsH5dV3Jp2dnZFKpYJyuawIbGKeqBJq%2FSabzYrNzU10d3Wp15MHFQjtTJaWlm6xe4bry8eEl2zVb3UfO3rt71wRoegPePniBeUEWiPhoyFDlwQOd7SjI9EN27ZRyr9HV4eB5Wzx3rYbdJyl7RGMEXHCJ3KEW1h5AiE0eJ6A8F3OHVjFf%2Fj0gtSE70G4Nnzbgl%2B18ObhXfz2%2B7riOkvMhvYIlOr4MjSyWPlVlHKr8Cng2xXYTpWJCEgpKcw9x4S3XYBjbkEEssHo2lWD%2FYYQPiSjrC3okFarOnQdmq5B09QGaTQ1N0j4xRRjVw32HzKIlD9YWPgTy6vrsMxqEJlQbdYWxYnjx5CIePBdZiPkrtMNBTSdUdWjPX%2FpAk6fs%2BDRJk9lRZuEWw1sK7ERAuo9VW2cAS1SZJ7jYPbt7P4ZtNTIQ0aoOQHJUwa9NUJhnLuYxunUKbhsSZ%2BiyjqpCuxWsJ3jnPZE2w%2FzVKYJAeEFNrhOBXOzK%2FtmEIcXNITREm22BjqkEWYdIjh74Qx%2BPHOSNbAhpAgKr%2Fz3XQvlDwZaolG4bqTJGvhsU6hWdWFuleHwg1K9L%2Bqtqz4yzzah0cJo2yGU88Vdx40G9O3p%2FrbLbRo9%2FpiBVSrALH9ClVCdJDybNVDZaAxCh7W9haevlvH6nfmUZ3mv4K%2F%2FZaf6RT7uoHbhHRRPiBvEeeBfXftfGjhC9KD26YcbWlq7xz4SWeJDI4EdEUXccoCAdoZqJZf49Bkd5rOdXLtvgwAAAABJRU5ErkJggg%3D%3D" /><span id="alt_lock" style="display: none; visibility: hidden;">&nbsp;<strong>[Lock]</strong>&nbsp;</span></a></div>
<div id="menu_print" style="cursor: pointer; display: inline; visibility: visible;"><a accesskey="p" id="woas_print_hl" title="Print" onclick="page_print()"><img id="img_print" style="display: inline; visibility: visible;" alt="Print" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAB3RJTUUH1wcECDIDhMeZAwAAAAlwSFlzAAAewQAAHsEBw2lUUwAAAARnQU1BAACxjwv8YQUAAABjUExURf8f7XNzc1JSUmNjY1paWmtra3t7e0pKSggICCkpKSEhIRgYGBAQEDExMcbGxgAAADk5OYSEhK2trYyMjJycnJSUlKWlpc7OzkJCQr29vdbW1rW1td7e3v%2F%2F%2F%2Ff39%2Bfn5%2B%2Fv7w37xZMAAAABdFJOUwBA5thmAAABKUlEQVR42n2S2a6DIBRFZRKhBRkPU9X%2B%2F1de1A7el64HcsLKDmTDMPzCcnyB1vc%2BTJgSxgg9IMK8hRfLWsDVZWddvPwIBRkzrHKrWqeLsDMHz1hkMq3P5zWRGzeICIvysl4FCRRv69ZEzOH5%2BIoHB1OXddssZWjZE%2Bd986xiTqnqYunIdVnsPN3LMLTJGm%2BaLi0RhEjVXQRFhsFNGjNmitbegIJHerCpZrqLxu6BiGwyRGEIply1U7iC5BhtABKbxA3A6VdCp2gMz0qIWASr%2FbRTWNApuRhKyrduUHVBHWIMuleUUl%2FyLThVXDyFjfDGKUUUxI8IX7Cx4F9i9CF%2BCBCi9bvQs7F2%2FIelE%2BpdoUlIeduR8hikuKujRYRJrxXtj94HNhJOf%2F2cP%2B6HGtUMDq8TAAAAAElFTkSuQmCC" /><span id="alt_print" style="visibility: hidden; display: none;">&nbsp;<strong>[Print]</strong>&nbsp;</span></a></div>
<div id="menu_setkey" style="display: inline; cursor: pointer; visibility: visible;"><a title="Set password" onclick="set_key()"><img id="img_setkey" style="display: inline; visibility: visible;" alt="Set Key" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A%2FwD%2FoL2nkwAAAAlwSFlzAAAu6AAALugBK4TfhAAAA2ZJREFUSMe1ls1rXGUUxn%2FvnTsfma80JmkzWpvaVsii0lqx1VYpbWrRSYsiLbhRiqvOwoXSpSD%2BAW5cOCKIFtSFIoLWEaolIFKJMRQrRMU2HRXDtNNkviczc%2B%2B8r4ueGW%2FbaZoscuHlwuFynnOe9zzPucoYw1o%2BFmv82KuqJpVRgPKEjE4nl6VArYQiK5XxA0E5toBowAWaQFOnk%2B6qAaxUxgIiQAwIAyEBsATAARpADagA9Vs7uiOAlcrYQD%2BwDogLUB%2Fg9wC0gCWgCpSAIlDW6aReFkAqHwAGgXsEqNNFQADaQk9dqi8Ci8ACUOp00vOSJ3Z%2BsCsSLDxZrg%2BFfpo7nF2sjfgEJCL34PPwX%2FHEjMRbAnxzByc%2F3xeP2v9%2BEg5WJhQ3unTcYGv6yuEz52Zf%2BFWo6vN0UAfKUvl1IA9cA64C13U6qbsdjL15ynrukb%2B%2FDNrV%2FQ0nPJ%2BvbDwPrB%2Fpz%2B57%2FMEzzxfrw9GZ7HgeiEq1jnDvl8q9F96hstEFeGr72SNBu7p%2FqRWbPf3DGy%2FnK4k4kHh4dHJuYuf7J%2FZs%2FWbvTPbgeVD9krQJFCR5UxL3yaQFbwMI2pVxgHz1vnfzlURTkgQu%2FHVg7rFtmcJQdH7gtadThwyqKzSFMYBRSt4YgzJGgak2Rl%2BC5Ge2Z2AVQFv7tAipc3rYiVLcSE7Ablh%2Bn%2BNVN27bbmuDslIZ1QUoLW34IxIosSH%2Bz4shf32q4YQ1oHds%2Bn7bYDQ3sFAdqb1z7q1fgCGhoQbkj%2B9%2Be9NYYmqzMRYoUGgWavd%2B%2Bt7k69%2FqdNJ0AWYu7z69bnvu1XCgvCc1fuqjXPGBadvXun%2FjwJ9PgOHHS0cvyMRozx2UlNIuwOz83u8s5ba3DF886LZDLRnXm8f0xMcHRgcj2a%2BioeJDnZjjBp2pueTZyd%2BOXRJVRzxTVBtL%2FBwb6b8SyJU3X%2Fx9%2FtFpICfnqk4nm7cpOfbKh%2F5DO754NmRXd5WWhtTU5Weyhdp6vySP9dBBRabJq4NrwKJOJ%2FWdrCICDAvfg2IbcY%2BSvVbR8aGC2EReRNZYbh%2FUxVs6E%2BSKqS3nRSVRdFHid3VTSyjpUBOV6fF7AByPm5Ylec1r2XfbB0qSRlewD6pAa8X7oEc3ATm3brRWr8T%2FL1VjVn3Uya%2FVir9d69%2BW%2FwCh5sidoCxDaQAAAABJRU5ErkJggg%3D%3D" /><span id="alt_setkey" style="display: none; visibility: hidden;">&nbsp;<strong>[Set Key]</strong>&nbsp;</span></a></div>
<div id="menu_help" style="display: inline; cursor: pointer; visibility: visible;"><a accesskey="h" title="Help" id="woas_help_hl" onclick="woas.ui.help()"><img id="img_help" style="display: inline; visibility: visible;" alt="Help" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A%2FwD%2FoL2nkwAAAAlwSFlzAAAN1wAADdcBQiibeAAAAAd0SU1FB9oDCxIoGlBNSPYAAAcJSURBVEjHfZZrUJXHGcf%2Fu%2B%2FtvO%2B5QCwckItcFAMSg8WmhFiqUYuXRG3N5GLajmOa0dZG05HRzrRe%2BqHTzDTTRjL2g9iONBrTqo3BNMqkBR1BCKERKgpChHO4eTCcHM7hvHDOeS%2B7%2FVBgrNN2Z555Zndn%2Fs%2FuPvt7dgn%2BSxNFUcjKyqJ%2Bv988evSoPDY2liTLsirLskAIQTwetxOJRCwlJSVy4MABA%2F%2BnkYf66sWLF%2Fe4XK6vJkwzcf369SFJEFILCwsr3W53HqWUEEJgWRaPRCJDfX199SYwtGnDhmRKiNLU1NRRVVVV%2B78CpLe0tHx6p6fnoynbbjECYzWrV6505JY8Dk3TIEkSCCHgnINzDtM0oU9NYeyTT%2FFhW6tFPO43ChcVLAuFQnl79%2B59Stf1KAAIs%2BotLS3Xr169WhsKBhvyIvqpHQmipT22BOqifC5QShhjc%2BKUUgiCAFVR%2BLybPaTszgAdNhPLGztuHCwrL48XFBQcbmho%2BP1cgJqamo0TodAT94dHzq4j8odrVY9Kih%2FlTBCInZZCEpYFwzCQSCRgmiZs2wbnHNCnCO%2FqBnU4eMH9oJIkSS%2Be7fjsrc1btjxx7969YH9%2Ff58IAMtLS5d%2B3Nh4u9ih%2FflJ1SOjuAimQyFT%2FiFMj4ygLdmJP9TUoL29HZFIBNnZ2dj92mt4wT0PauA%2BklJSiZyTg%2BV9n9NRl6f2SnPTe7m5ucsA%2FFUEgIiuj9u6fmQbcaj2wlxuuDQS7h%2FAvabr%2BKL7Dp7%2FrPk%2FboLP58P%2Bqir4svOxu7wCZGEcj6R4IXtT%2Bab%2BAc%2Bvuf1KRNf3A4AIAOcuvO%2FYvW6jzCHAlCWifzGO4eZW%2BG93IxgO8yWpXrJ1104sWLCAc875hbo6Wn%2FpEs4FRrB1YAAyY1AZoGRnEvaNcqw1Y2LtX84TABBPnjwp%2Bny%2BgszyMgEeD6dT0yT6fh18HR0YiUxwz%2BNL8Vz2KiMSjb7Q2dnZTinlaenpZQAuhLmNyPQ0j%2FgGiXfrZmjfeRaKIPD80VGS3nRtUVVVlSR2d3cnlZSUVGqqCg4QJovQnqnEo%2Fk5ME6fIb0iNa6NDmc11V0cf%2BCUegAgLdXL8nZ8jyatqgApLoItiqCEEJfTifz8%2FGe6urp%2BQRVFUZOTk%2FMlSQLnHLZlw5iOQfGmIv2l5xFzadsfEhcAvA3AzshZcIitXQUxPQ3WdAyccxBC4HA44Ha7F1iWpVBCiEgpdRDyb%2BYYY2CMgds2wsEgmGE2PUT7MQCVAF5Zu2ZN7UQwyAnn4LPkEgJKKSilVJIkSh%2BuHXRmiBACURQhiuKDtO8H8EMAbwF4Z0Zobn52kbNAMsZAbdu2TNOMMcbmCBVFEYIkIT0jA5yQygcC%2FHjGvwkANuflWTk5oIoMQRDmSolhGLAsizHGGI3H49OhUOiOYRizlRSyUwMb8EOru4xlKd4TJd%2BsWDIjnD3jx9KKCrPLFxb8aV5zG8SrLZDcLgiCANu2EYvFEAqF7pqmGaNFRUXhW7du%2FS2q6wAhXKAUSZ23oR38JWIXL%2FOV3nRy%2BMVtN392%2BPBOABQADh458v3anx%2FyrStdLkSPneDJvzkGcf8RmMMjMCYn%2BUQ4jN7e3o%2FGx8cjBABe37dv95aVq6q%2FLjtEUVagLF6E%2BLETuN9wBcHpaT5vz05ib1iLseEREEKQlZsD540uTB76FfeqKpHmp4E9twlTPj%2FCg4O8LyMNJ%2F13f3L%2BnVNviwDw8raX%2BKUP6hIFnw%2BJjrx8LubmEGXndsyPJaA2N5PpkqWQqICKigoAwMjoKMyiAmRoTkIz0sG2PgvmVDF56zbX%2B%2FpIo70k7pIVAbNbNhOGJjmd776XpE3ovgESOXMWtkAhHtyHlIM%2FRXLJY3Bp2lymnZqGpOxs0DcOAT%2FaAZaeiokr1xDt7ycf52ZGXPl5tQldTwJAKQDp1KlT3UuLiyvq7%2FbtalLlaLizE8E3q3m8%2FQbItzciSdWQnJwMwzBgGAY8Hg80RYG8cgUMI4H7te%2FyL1s%2FwQ3vvMQHo8OvrygrK%2FMPDvYCcFEA8vHjx5sDgUDgB6%2B%2B%2Bt3fdXftOvOIM6QPDRH9ZhfMWJzP8iFJ0tzLZlkWYvE4120L0Z4ecjnLG60e9u%2F5bXX1xtOnTydaW1uvAJAJAA2AAkCrrq4%2Bt3jxYsffGxtb7ERi%2B5qiJc6iym8Rj9sNRVHmIDQMA7FYDOFwGB1tbazrH%2B3mlEP947qnny49f%2F48raurezkSiUwCMMhMHpQZE9avX7959erVX4vqOm9pa9M9mvaV0tLSJ%2BfPn79QURSVc454PB4PBAL%2Bjo6OVsu2v3xqxQpnbGpKqK%2Bv%2F2d7e3sdAAuACSBOHvoAyACkB0wAoGRmZnrcbrdKKRU554QxZuu6Hg8EApOMMQOAPSNqPSBuALD%2FBaO%2BPTDD5E81AAAAAElFTkSuQmCC" /><span id="alt_help" style="display: none; visibility: hidden;">&nbsp;<strong>[Help]</strong>&nbsp;</span></a></div>
<div id="menu_edit" style="display: inline; cursor: pointer; visibility: visible;"><a accesskey="e" id="woas_edit_hl" title="Edit" onclick="woas.ui.edit()"><img id="img_edit" style="display: inline; visibility: visible;" alt="Edit" src="data:image/png;base64, " /><span id="alt_edit" style="display: none; visibility: hidden;">&nbsp;<strong>[Edit]</strong>&nbsp;</span></a></div>
<div id="menu_cancel" style="display: none; cursor: pointer; visibility: hidden;"><a title="Cancel" onclick="woas.ui.cancel()"><img id="img_cancel" style="display: inline; visibility: visible;" alt="Cancel" src="data:image/png;base64, " /><span id="alt_cancel" style="display: none; visibility: hidden;">&nbsp;[Cancel]&nbsp;</span></a></div>
<div id="menu_save" style="display: none; cursor: pointer; visibility: hidden;"><a accesskey="s" id="woas_save_hl" title="Save" onclick="woas.save()"><img id="img_save" style="display: inline; visibility: visible;" alt="Save" src="data:image/png;base64, " /><span id="alt_save" style="display: none; visibility: hidden;">&nbsp;<strong>[Save]</strong>&nbsp;</span></a></div>
</td></tr></tbody></table></div><p style="font-size: 20px;">&nbsp;</p>
<div id="i_woas_text_area" style="display: inline; visibility: visible;">
<div id="i_woas_menu_area" style="position: absolute;">
<div class="woas_wiki" id="woas_menu_area" ondblclick="menu_dblclick()"></div>
<div id="menu_edit_button" style="display: inline; visibility: visible;"><input class="woas_button woas_menu_button" value="Edit menu" title="Edit menu" onclick="edit_menu()" type="button" /></div>
<div class="woas_wiki" id="woas_ns_menu_area" ondblclick="woas.ui.ns_menu_dblclick()" style="display: none; visibility: hidden;"></div>
<div id="woas_ns_menu_edit_button" style="display: none; visibility: hidden;"><input class="woas_button woas_menu_button" value="Edit namespace menu" onclick="woas.ui.edit_ns_menu()" type="button" /></div>
</div>
<div style="visibility: visible;" class="woas_text_area" id="woas_wiki_area" ondblclick="page_dblclick()"></div>
<div style="display: none; visibility: hidden;" class="woas_page_mts" id="woas_mts"></div></div>
<div id="edit_area" style="display: none; visibility: hidden;">
<div id="woas_edit_page_title" style="display: block;">Page title: <input id="wiki_page_title" size="50" value="" />&nbsp;<a style="color: blue;" onclick="woas.wiki_buttons_display(d$('wiki_format_buttons').style.display != 'block')">Show/Hide wiki buttons</a>&nbsp;|&nbsp;<a style="color: blue;" onclick="woas.html_buttons_display(d$('html_format_buttons').style.display != 'block')">Show/Hide XHTML buttons</a></div>
<div id="wiki_format_buttons" style="display: block; visibility: visible;">
Wiki Code:
<input class="woas_editor_button woas_button" name="btn11" value="Wiki URL" title="Wiki page link" style="text-decoration: underline; width: 70px;" onclick="setWikiIUrl()" type="button" />
<input class="woas_editor_button woas_button" name="btn10" value="URL" title="External link" style="text-decoration: underline; width: 40px;" onclick="setWikiUrl()" type="button" />
<input class="woas_editor_button woas_button" name="btn7" value="=" title="Level 1 header" style="width: 40px;" onclick="TagThis('= ','')" type="button" />
<input class="woas_editor_button woas_button" name="btn8" value="==" title="Level 2 header" style="width: 40px;" onclick="TagThis('== ','')" type="button" />
<input class="woas_editor_button woas_button" name="btn9" value="Img" title="Include wiki image" style="width: 40px;" onclick="setWikiImage()" type="button" />
<input class="woas_editor_button woas_button" name="btn1" value=" B " title="Bold text" style="font-weight: bold; width: 30px;" onclick="TagThis('*', '*')" type="button" />
<input class="woas_editor_button woas_button" name="btn2" value=" i " title="Italic text" style="font-style: italic; width: 30px;" onclick="TagThis('/', '/')" type="button" />
<input class="woas_editor_button woas_button" name="btn3" value=" u " title="Underlined text" style="text-decoration: underline; width: 30px;" onclick="TagThis('_', '_')" type="button" />
<input class="woas_editor_button woas_button" name="btn4" value="{{{ }}}" title="Nowiki text" style="width: 70px;" onclick="TagThis('{{{\n', '\n}}}')" type="button" />
<input class="woas_editor_button woas_button" name="btn5" value="*" title="Bulleted list item" style="width: 40px;" onclick="TagThis('* ','')" type="button" />
<input class="woas_editor_button woas_button" name="btn6" value="#" title="Numbered list item" style="width: 40px;" onclick="TagThis('# ','')" type="button" />
<input class="woas_editor_button woas_button" name="btn7" value="@" title="Alphanumeric list item" style="width: 40px;" onclick="TagThis('@ ','')" type="button" />
<input class="woas_editor_button woas_button" name="btn8" value="Ruler" title="Ruler (horizontal line)" style="width: 46px;" onclick="woas.ui.editor._insert_ruler()" type="button" />
<input class="woas_editor_button woas_button" name="btn9" value="Tags" title="Wiki tag or tags" style="width: 43px;" onclick="setTag()" type="button" />
<a class="woas_world_link" onclick="woas.ui.tables_help()">Tables syntax</a>
</div>
<div id="html_format_buttons" style="display: block; visibility: visible;">XHTML Code:
<input class="woas_editor_button woas_button" name="b" title="Bold text" id="b" value="B" style="font-weight: bold;" onclick="FullTagThis('strong')" type="submit" />
<input class="woas_editor_button woas_button" name="i" title="Italic text" id="i" value="i" style="font-style: italic;" onclick="FullTagThis('em')" type="submit" />
<input class="woas_editor_button woas_button" name="u" title="Underlined text" id="u" value="u" style="text-decoration: underline;" onclick="FullTagThis('u')" type="submit" />
<input class="woas_editor_button woas_button" name="s" title="Strike-through text" id="s" value="S" style="text-decoration: overline;" onclick="FullTagThis('s')" type="submit" />
<input class="woas_editor_button woas_button" name="sup" title="Superscript text" id="sup" value="Sup" onclick="FullTagThis('sup')" type="submit" />
<input class="woas_editor_button woas_button" name="sub" title="Subscript text" id="sub" value="Sub" onclick="FullTagThis('sub')" type="submit" />
<input class="woas_editor_button woas_button" name="left" title="Align selection left" id="left" value="Left" onclick="DivTagThis('left')" type="submit" />
<input class="woas_editor_button woas_button" name="center" title="Align selection center" id="center" value="Center" onclick="DivTagThis('center')" type="submit" />
<input class="woas_editor_button woas_button" name="right" title="Align selection right" id="right" value="Right" onclick="DivTagThis('right')" type="submit" />
<input class="woas_editor_button woas_button" name="justify" title="Justify selection" id="justify" value="Justify" onclick="DivTagThis('justify')" type="submit" />
<input class="woas_editor_button woas_button" name="list" title="Unordered list" id="list" value="List" onclick="FullTagThis('ul')" type="submit" />
<input class="woas_editor_button woas_button" name="list_eq" title="Ordered list" id="list_eq" value="List=" onclick="FullTagThis('ol')" type="submit" />
<input class="woas_editor_button woas_button" name="li" id="li" title="List item" value="li [*]" onclick="FullTagThis('li')" type="submit" />
<input class="woas_editor_button woas_button" name="blockquote" title="Blockquote (indented text)" id="blockquote" value="Blockquote" onclick="FullTagThis('blockquote')" type="submit" />
<input class="woas_editor_button woas_button" name="img" id="img" title="Image link" value="Image" onclick="setHTMLImage()" type="submit" />
<input class="woas_editor_button woas_button" name="url" id="url" title="External link" value="Link" onclick="setHTMLUrl()" type="submit" />
</div>
<textarea style="width: 989px; height: 398px;" id="woas_editor" rows="0" cols="0">&nbsp;</textarea> 
</div>
<div id="woas_footer"><table width="100%"><tbody><tr>
<td width="15%" align="left"><span style="visibility: hidden;" id="woas_debug_panel"><a title="Toggle debug display" onclick="d$.toggle('woas_debug_console')"><img style="display: inline; visibility: visible;" id="img_debug" alt="[Debug]" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAAXNSR0IArs4c6QAAAGBQTFRFeWgJnLPK5%2FL%2BobXKwtryrMXeWXKK2Ov%2Bk6zFy%2BT9pLzTvNXtSmJ6tc7njKW9nLDE9vn%2BvdLovtfwfZauutLpkKjA%2Bvz%2Fp8DZaYKbmLHKvNTrx%2BD4y%2BD18ff%2BwNjv%2F%2F%2F%2FN2g9%2FwAAAAF0Uk5TAEDm2GYAAAABYktHRACIBR1IAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH2gUXFDkoWkFbUwAAAINJREFUGNN9j9sOwiAQRNstF7lUEKRIq87%2F%2F6WkpQkmxnk72bOb2WH4HdR8sXNlQjd23hPlU8Kkny%2FDMPOMg72%2F3QMUiBL2%2FcqmsjIyNoVWxEih8YCRsEUss2pCZQZaeElM7Dc0EVTIJb1Xeyhac5nHdBX2LIKLVBuzXXs8hBD491yXD6ahCoFA3xZkAAAAAElFTkSuQmCC" /><!-- Needed for IE6 and IE7 --><span id="alt_debug" style="display: none; visibility: hidden;">[toggle
debug]</span></a></span></td>
<td width="70%" align="center"><span><a class="woas_link" title="About Wiki on a Stick" onclick="woas.ui.about()">About Wiki on a Stick v0.12.0</a></span></td>
<td width="15%" align="right"><a title="Go to top of page" onclick="javascript:woas.ui.top()"><img style="display: inline; visibility: visible;" id="img_top" alt="" src="data:image/png;base64, " /><span id="alt_top" style="display: none; visibility: hidden;"></span></a></td></tr><tr>
<td colspan="3"><span id="woas_debug_console" style="visibility: hidden; display: none;"><div id="woas_debug_console_title">Debug console</div>
<textarea style="display: inline; visibility: visible;" id="woas_debug_log" rows="12" readonly="readonly" cols="60"></textarea><br /><input id="woas_debug_log_clear_button" title="Clear debug log" onclick="d$('woas_debug_log').value=''" value="Clear" class="woas_button" type="button" /></span></td></tr></tbody></table>
</div><script type="text/javascript">
//<![CDATA[
// this code needs to stay here for some Opera/Safari/Chrome restriction
// on DOM generation from extenal JavaScript
if (woas.use_java_io) {
	document.write("<"+"!-- "+__marker+"-TAIL-START -"+"->" +
					 "<applet style='position:absolute;left:-1px' name='TiddlySaver' code='TiddlySaver.class' archive='TiddlySaver.jar' width='1' height='1'></applet>"
					 + "<"+"!-- "+__marker+"-TAIL-END -"+"->");
}
//]]>
</script>
<div id="woas_custom_accesskeys">&nbsp;</div>
</body></html>