umoria-5.7.10+20181022/0000755000175000017500000000000013363423022012743 5ustar arielarielumoria-5.7.10+20181022/historical/0000755000175000017500000000000013363422757015122 5ustar arielarielumoria-5.7.10+20181022/historical/README.md0000644000175000017500000000424013363422757016401 0ustar arielariel# Historical Documents This directory contains various documents found in the original UMoria archives. They have been renamed, with some minor reformatting to the Markdown format, to help make consumption easier. ### Table of Contents * `history.md` - Articles posted to various mailing lists giving a brief history of Moria, by the original creator, Robert Koeneke, and long time contributor James Wilson. * `features.md` - A list of all of the important _user visible changes_ from UMoria 4.87 to UMoria 5.x. * `dragons.md` - Information on dragons. Contains spoilers. * `CHANGELOG` - A detailed list of all changes made between May 1987 to October 2008, covering versions 4.81 through 5.5.2. * `errors.txt` - A list of known errors for UMoria v5.x. ## Original Copyright Message The following copyright message was included in the original `main.c` source file. It is no longer valid, but has been included here for historical reasons. ``` Moria Version 4.8 COPYRIGHT (c) Robert Alan Koeneke I lovingly dedicate this game to hackers and adventurers everywhere... Designer and Programmer : Robert Alan Koeneke University of Oklahoma Assistant Programmers : Jimmey Wayne Todd University of Oklahoma Gary D. McAdoo University of Oklahoma UNIX Port : James E. Wilson UC Berkeley wilson@kithrup.com MSDOS Port : Don Kneller 1349 - 10th ave San Francisco, CA 94122 kneller@cgl.ucsf.EDU ...ucbvax!ucsfcgl!kneller kneller@ucsf-cgl.BITNET BRUCE Moria : Christopher Stuart Monash University Melbourne, Victoria, AUSTRALIA cjs@moncsbruce.oz Amiga Port : Corey Gehman Clemson University cg377170@eng.clemson.edu Version 5.6 : David Grabiner grabiner@alumni.princeton.edu ```umoria-5.7.10+20181022/historical/dragons.md0000644000175000017500000000510713363422757017104 0ustar arielariel# Dragon Information Every breath weapon does 1/3 of the monster's current hit points, except lightning, which does 1/4. Acid damage is then halved if you have any armor to corrode, or divided by 4 if you have resist acid and some armor. Cold or fire damage is divided by 3 if you have permanent resistance (from a ring, armor, or weapon) or temporary resistance (from a potion or priest spell), and by 9 if you have both. Lightning damage is divided by 3 if you have resistance. Gas damage always has full effect. Ancient Multi-Hued Dragon: (40/276) 2080 total hit points lightning: 520/173 acid: 693/347/173 fire/cold: 693/231/77 gas: 693 Even a 40th level half-troll warrior with 18/100 constitution, heroism, and superheroism would probably not have 693 hit points. Thus you must never fight AMHD's if you are giving them any opportunity to breathe on you. I don't like to go down to level 40 unless I have 231 hit points and resistance, so that I can survive 4 out of 5 breaths from off-screen AMHD's. Ancient Green Dragon: (39/269) 720 total hit points gas: 240 ---- Ancient White Dragon: (38/263) 704 total hit points cold: 235/78/26 ---- Ancient Blue Dragon: (39/268) 696 total hit points lightning: 174/58 ---- Ancient Black Dragon: (39/270) 720 total hit points acid: 240/120/60 ---- Ancient Red Dragon: (40/273) 840 total hit points fire: 280/93/31 ---- Balrog: 3000 total hit points fire: 1000/333/111 Thus a mage with less than 333 hp can be killed by one breath from the Balrog, before he gets a chance to drink invulnerability potions. For a mage to get 333 hp, he must be human, and he must use the grape jelly trick. Mature Multi-Hued Dragon: (38/262) 640 total hit points lightning: 160/53 acid: 213/107/53 fire/cold: 213/71/24 gas: 213 ---- Mature Green Dragon: (36/256) 384 total hit points gas: 128 ---- Mature White Dragon: (35/250) 384 total hit points cold: 128/43/14 ---- Mature Blue Dragon: (36/255) 384 total hit points lightning: 96/32 ---- Mature Black Dragon: (37/261) 464 total hit points acid: 155/77/39 ---- Mature Red Dragon: (37/260) 480 total hit points fire: 160/53/18 ---- Young Multi-Hued Dragon: (36/254) 320 total hit points lightning: 80/27 acid: 107/53/27 fire/cold: 107/36/12 gas: 107 umoria-5.7.10+20181022/historical/CHANGELOG0000644000175000017500000050164413363422757016346 0ustar arielariel# Umoria Changelog 1987-2008 ### 4.81 / 1987-4-30 Fixes before 1987-5-1 fixed moving into column one tunnel through permanent rock prints tunnel through air? message printing of 18/ stats ^C stops find flag, resting, searching monsters bash doors with 0 hit points what if pto_hit is negative? test_hit, attack_blows, critical_blows are wrong mon_tot_mult bug fixed always have same stats when start enchant armor scroll wizard_create does not set level!! should treasure level be set to finding level? after cure blindness need a move_char(5) to restore light lose_exp in spells.c mass_genocide when there are no monsters drop_throw, throwing object against wall goes into infinite loop player titles for level wrong? td_destroy should unlock chests use of 8's in generate, set wallstk in build_tunnel door/trap destroy should replace doors with corr2.floor, delete_object when staff has 0 charges left, don't get warning message recharge spell ### 4.81 / 1987-5-1 injection from types of wands to metals extra space at end of scroll names printing numbers in a field of 6 warning if wielding too heavy of a weapon attack_blows, penalty for wielding heavy weapon too severe, save routine, doesn't exit if you type ESCAPE, shouldn't if doing panic save selling items to stores, they are still unidentified item prices in stores line up on the right rogue_like key binding option spelling fixes fixed getlogin() call in death.c disable local special characters (^Y, ^R, ^W, ^V, ^O) ### 4.81 / 1987-5-2 fixed . problem caused above fixed problem with speed, only call search_off in signal.c if search_flag set food problems, fix initialization of py structure in variables.h ### 4.81 / 1987-5-2 after ^C, if type n, erase the line at top of screen put refresh in erase_line(), so that lines really do get erased monster 'o' Ogre Magi casting unknown spells m_level problems, not calculating right numbers for monster generation changed damroll/max_hd so that they no longer clear 'd' in strings increased size of dtype for damroll strings increased size of all defined strings, need one more char for the '\0' wands of wonder, not generating new flag correctly only print 'too heavy weapon' message if actually wielding weapon wand of heal monster, causes them to scream when hit put (void) before all sscanf calls implemented shell_out and (no_)controlz functions fixed build_tunnel, removed leftover fragment of earlier incorrect fix to build_tunnel display_inventory doesn't clear last line when 12 items on one page 11 other store_purchase when over 12 items in store passed calculated wrong count of number of items on second page should put priv_switch stuff back in, i.e. setuid game should be setuid ### 4.82 / 1987-5-3 fixed equip list bug when taking off items changed shell escape command to '!' changed version number to 4.82 restore terminal modes in signal.c before core dumping fixed bug in save.c, it was writting/reading too much for magic_spell refixed m_level code, m_level[0] must be added after the randint wrote convert program to fix old save files ### 4.82 / 1987-5-4 put sigsetmask in signals.c, now dumps core on QUIT signal fixed empty chests, set flags to zero so no treasure, if search then identify it as empty missing break for amulets in magic_treasurein misc.c, caused cursed amulets to have positive attributes dispell evil staffs gives segmentation fault, didn't check for muptr == 0 ### 4.82 / 1987-5-5 your resist the affects -> you resist the effects only print "%s appears healthier" if can see the monster check all uses of muptr, make sure test it not zero before using cost for amulets of searching is too high evil iggy prints wierd messages when he hits, break missing ### 4.82 / 1987-5-6 fixed index bug in misc.c objdes(), used 0 as null string pointer added sun to 'getuid' ifdefs in death.c and misc.c ### 4.82 / 1987-5-8 fixed help page for rogue mode, searching mode command is ^S when stun monster by bashing, now does not print name if can't see monster allow controlz again, except when setuid or accessing save files ### 4.82 / 1987-5-10 added break after cure_poison() in potions.c items sold in stores do not appear in the inventory when last on second page signals.c should restore local special chars before core dump exit monsters bashing doors end up on top of them can't hit the monsters, can't cast spells at them, etc. ### 4.82 / 1987-5-11 fixed cast_spell, cast was not initialized to FALSE infravision in misc.c should be multiplied by 10 food items become known in a more reasonable fashion ### 4.82 / 1987-5-13 if cast from empty spell book, print error message the base height/weight for female/male characters were reversed ### 4.83 / 1987-5-14 refixed empty spell book to distinguish the three cases, cas_spell now has three return values removed the numbers (1 2 3 4 6 7 8 9) from get_dir prompt changed some % chars to %% in print_monster() in files.c changed HD from %d to %s in print_monster() fixed jamdoor() was setting i_ptr->p1 instead of t_ptr->p1 redefine Search mode command for rogue_like option to # changed message for rest_on, press '^C' not 'any key' fixed potential infinite loop problem, must initialize curses before install signal handlers extensive mods to the save routine, added protection mechanisms changed version number to 4.83 minor fixes so that source passes lint, removed inkey_delay function in store2.c, fix display_inventory() and purchase_haggle() so that items will not be listed with 0 price for high chr characters ### 4.83 / 1987-5-15 check result of fopen for writing scoreboard in top_twenty() etc. misspelled in print_monsters() added 'drools' and 'insults' messages in print_monsters() more mods to save routine, compress cave before writing to save file wrote wizard restore_char() function fixed test for Nighttime in generate.c, should be turn/5000 not turn%5000 set moria_flag true after a call to restore_char() in moria.c update game_version() to include info about me ran through spell again ### 4.83 / 1987-5-16 should call prt_stat_block after wizard restore command setuid code was just plain wrong, fixed it ---------------------------- Summer vacation! ### 4.83 / 1987-8-26 checked every switch statement, found five missing breaks ### 4.83 / 1987-8-28 changed use of geteuid() for random seeds, didn't work if setuid to root ### 4.83 / 1987-9-4 - 1987-9-6 port to SYS V changed store type (and save file format) to avoid warning about nonportable signed character comparison ### 4.83 / 1987-9-9 added bug fixes from doc@s.cc.purdue.edu, mainly dealing with setuid code ### 4.83 / 1987-9-11 correct spelling of receive_offer() (was recieve) ^R in store will 'really' clear screen now get_haggle will not accept negative numbers now disarm_all caused segment faults, missing level of braces after t_ptr = &... more spelling errors: Delila(+h) Slim(-e)y Smok(-e)y ### 4.84 / 1987-9-16 took out old compatibility code in save.c changed version number to 4.84 changed clear_screen and really_clear_screen so that they set msg_flag FALSE ### 4.84 / 1987-9-17 removed loop_flag = FALSE after prt_comment6(), stops wierd behavior if when buy/sell give a price greater/less than that offered put entry in scoreboard when quit modified get_char so that it will read 4.83 save files ### 4.84 / 1987-9-18 fix code so that it passes lint on ultrix and sun fix curses bugs with Ultrix, by adding ifdefs for BUGGY_CURSES remove calls to nl(), and nonl(), don't write chars in last column ### 4.84 / 1987-9-19 port to SYSTEM V again breakup all files so that they are less than 64K, except the doc file ### 4.84 / 1987-10-?? change all instances of (index(...) > 0) to (index(...) != 0) fixed three errors, calling randint with arg of 0 fixed bug that allowed one to buy objects for negative gold pieces fixed many other errors, but forgot to document them (sorry!) ### 4.85 / 1987-10-26 add comment about defining _DNO_SIGNED_CHARS for 3Bx series computers SUN4 has variable class in math.h, added ifdefs to misc1.c and externs.h to avoid the conflict make variables.h monsters.h treasure1.h and treasure2.h into c files to avoid problems compiling the HUGE main.c file on a 3B20 added an option '-o' to use the original style key bindings added compiler option to Makefile, so that installer can choose which key binding style should be the default control-c puts you in scoreboard only after character generation complete changed all USG savetty/resetty to saveterm/resetterm, it still seems to work changed version number to 4.85 for distribution purposes started mailing to comp.sources.games ### 4.85 / 1987-10-27 moved clear_screen in top_twenty to before the wizard1 test in wizard_restore, don't exit if can't chmod the save file when save file in wizard mode, leave it readable, this is necessary to allow the wiazrd to restore other player's 'touch'ed save files fixed up the install option in the makefile, added comments to INSTALL it is possible to get 'final offer = 0' when selling an object, fixed by making sure that min_buy and max_buy are >= 1 ### 4.85 / 1987-10-29 fixed breath, was calling poison_gas with dam = 0 put note into MISC_NOTES on how to restore touched save files finished mailing to comp.sources.games all bug fixes listed above are included in 4.85 ### 4.85 / 1987-11-7 more fixes for SYSV shell out problem ### 4.85 / 1988-1-11 fixed "You are too confused to scanf..." message ### 4.85 / 1988-1-14 for SYS V, replaced rand() with lrand(), also fixed init_seeds, set_seed, reset_seed fixed randint, it now returns 32 bits for both BSD & SYS V replaced st_ctime with st_atime in save.c deleted "? for help" messages during character creation check_pswd now accepts CR or LF to end password, affected those who used BUGGY_CURSES option when ^C to quit, now get "killed by quitting" message fixed searching of trapped chests, printed wrong message if control-C while resting, don't get asked if want to quit added setgid(getgid) to main.c remove extraneous refresh/putqio calls from io.c, greatly reduce CPU usage casting blind monster on a monster that normally doesn't move, causes it to start moving, fixed problem if control-C during -more- prompt fixed after fork, close open file descriptors (scoreboard) ### 4.85 / 1988-1-15 after change of dex/str, misc stats are updated checked all randint for possible randint(0) calls remove bonuses for old object before adding bonus for new object in wear() this affected increase stat rings mostly deleted unneccesary loop from restore_levels in spells.c fixed 'no more' message for items that don't start with ampersand, in desc.c fixed dprint in death.c, not printing last line of tombstone fixed special effects (bolt, ball, breath) in spells.c by adding put_qio calls locked chests were treated as trapped during searching in moria1.c C command to file showed equipment list starting with b), files.c when 21 items in inventory, equip list was not updated after removing an item, unwear() in moria1.c ### 4.85 / 1988-1-18 fixed test of py.misc.fos in moria2.c, this number could go negative in io.c, clear_screen cleared all used_line flags instead of just the ones actually cleared in io.c, change loops that modify used_line to stop at 22, not 23 ### 4.85 / 1988-1-22 in scrolls.c, tmp[] was accessed starting at index one instead of zero in store2.c, when 12 items in store, offered to sell a-m instead of a-l ### 4.85 / 1988-2-6 added check of characters hit point in get_char, this ensures that dead characters will not be given a chance to restore their hit points dead characters can be saved by a signal config.h was non-ansi, macro expanding inside of string constants not allowed ### 4.85 / 1988-2-7 fixed speed monster problem, infinite loop if point wand at wall ### 4.85 / 1988-2-12 compact_objects will no longer delete doors if are on the town level in store_create, index to inven_init was off by one, this will fix store inventories made init_seeds generate a more random initial setup removed Public Domain messages, contradicted the 'all rights reserved' phrase changed all msg_line, msg_line places to msg_line, 0 changed pause_exit in io.c, so that ESC does not quit game, this is inconsistent with rest of game, expecially get_char quart_height, quart_width, msg_line changed to be constants changed turn and store_open to be longs, before store_open was a short which meant that stores could not become locked after 64K turns another spelling check of all files fixed sell_haggle and purchase_haggle so that prices will not go the wrong way, i.e. no more negative numbers while selling changed time check in save.c to 5 secs so that it matched sleeping time ### 4.85 / 1988-2-14 changed inner loop of print_map to use strcat instead of sprintf, this gives a big time savings here!! ### 4.85 / 1988-2-15 changed inner loop of printf_map to index a character pointer instead of using strcat removed strcpy from lite_spot delete unused code from rest check in dungeon.c, no longer refreshes screen removed refresh from msg_print eliminated most abs calls, speed up commonly called routines speed up many of the most commonly used functions eliminate test_light call in prt_map in misc1.c, inline the code image avoided by typing ^R changed rogue-like summon monster from ^U to ^S fixed throw_object, did not draw object on screen as it moved ### 4.85 / 1988-2-18 fixed print_objects in files.c, had an exclamation mark before the get_string ### 4.85 / 1988-2-19 ooze bug?, cursor disappears, invulnerability, fixed by changing delete monster so that pushm() is ALWAYS called no more silver bug, changed all object definitions so that 'number' is 1 player_save did not take level into account, two calls did not add it in get_hitdie could return a negative number for increase in hit points add rerolling of characters ### 4.85 / 1988-2-21 changed superb before excellent in likert() in misc1.c to be superb after excellent ### 4.85 / 1988-2-27 fixed some signal problems upon forking a subprocess stone-to-mud that kills a creature now prints death message added ' of' to name of infra-vision potion can now enchant boots with enchant armor scroll removed caps for 'Some' in treasure1.c strip 'some' at start of string in objdes when pref is false ### 4.85 / 1988-3-5 always treat boots as armor, fixed scrolls.c, minus_ac in moria1.c ask for confirmation if try to cast spell without enough mana fix msg_print, put flush if -more- had been printed back in fix control-c handler, add test for 0 in get_com of io.c ### 4.85 / 1988-3-7 many grammar/spelling errors fixed, complements Col Sicherman ### 4.85 / 1988-3-17 noticed that character rerolling has somehow disappeared?!?!? ### 4.85 / 1988-3-28 exit find mode if character takes damage (for any reason) flush input, exit search/rest modes if creature attacks broke up line in place_object, misc2.c to avoid reported compiler bug fixed problem with range attacks vs filthy urchin, mon_take_hit returns -1 if no monster dies not 0 as before if monster in unlit area lit by ranged attack then teleports, symbol not cleared, set in mon_spell_cast, set cast to TRUE when cast ### 4.85 / 1988-3-31 set_lightning_destroy was returning the inverse set in generate.c, change "doorptr <= 100" to "doorptr < 100", so that array doorstk[100] accessed correctly ### 4.85 / 1988-4-6 off by one error in place_trap call in vault_trap in generate.c ### 4.85 / 1988-4-16 increase dam/increase ac rings were setting cost incorrectly in misc1.c ### 4.85 / 1988-5-1 salt water not clear POISONED message, call cure_poison instead of clearing poisoned flag lose_exp takes off one too many mana points, wasn't rounding correctly in death.c, print out 'mighty Queen' if female character can 'kill' the program twice to get two save files, added var that only allows one signal handler to execute fixed use of reset_flag for free moves, for many commands cure blindness does not redraw monsters, added creatures(FALSE) call to dungeon.c eliminate any abs that calls randnor (misc1.c), since abs may be defined as a macro off by one bug for all player_exp references, could not reach level40, con_adj() used before class adjustments to abilities made in create.c, so I moved it afterwards bless prints max hit points, dungeon.c, the calls were deleted ### 4.86 / 1988-5-4 changed version number to 4.86, first diffs sent out ### 4.86 / 1988-5-6 fixed recharge in spells.c, did not redraw srceen properly ### 4.86 / 1988-5-7 in dungeon.c, moria1.c, moria2.c, scrolls.c, substituted INVEN_* constants in dungeon.c, fast code called msg_print before slowing player, creative killing could make character permanently hasted ### 4.86 / 1988-5-10 after save character, set a global so that will not save again if catch a signal exchange x command should report 'can not wield heavy weapon' gain_level problem, could reach level 41 with ^J, change player_max_exp in main.c so that this won't happen, also changed player_exp[39] in var.c ### 4.86 / 1988-5-17 fix typos in misc2.c, io.c, dungeon.c, store2.c, moria2.c to get program to compile ### 4.86 / 1988-5-18 fixed typo 'of' -> 'or' in dungeon.c another savefile/signal problem, set char_saved before sleep more 'do action before calling msg_print' bugs in dungeon.c fixed, see 5/7 level 50 objects never created, make t_level 51 element array instead of 50 bashing effects (stunned) not cumulative ### 4.86 / 1988-5-20 added some SYS III notes to INSTALL lamp fill messages added to moria2.c, to make it more informative when player overeats, he is temporarily slowed modified objects in treasure1.c and magic_treasure() in misc1.c so that all object pluses are part of name changed subval of mush so that it is not same as slime mold cast which spell? message does not go away if hit escape, fixed get_spell erase_line now clear msg_flag if row == 0 now save stack of last ten messages, changes to io.c and dungeon.c original commands changed, wiz pass ^P -> ^W, last message ^M -> ^P, create object ^W -> ^Q, ^M did not work on Ultrix for last message cloaks incorrectly priced, misc1.c was adding 100*to_ac to cost, this add is done in store1.c treat boots like other armor, add to disenchant list in creature.c turn over store contents every 1000 turns, if not on town level ### 4.86 / 1988-5-21 only print monster name if visible, moria2.c: throw_object() creature.c: confuse monster check in make_move() spells.c: sleep_monster1(), fire_bolt(), hp_monster(), drain_life(), speed_monster(), confuse_creature(), sleep_monster(), wall_to_mud(), poly_monster(), speed_monsters(), sleep_monsters2(), dispel_creature(), turn_undead(), lite_line() cast which spell? prompt cleared when ask for list of spells, misc2.c fix spells.c so that all spells return TRUE when they should create food no longer destroys object under player ### 4.86 / 1988-5-22 add -cont.- message to store display, store2.c ``There is something there already'' *before* asking which drop item, moria2.c ### 4.86 / 1988-5-24 added panic_save, don't save scores for games from panic save files modified death.c save.c signals.c could 'ESC' from learn_spell without learning a spell, misc2.c area_affect() moria1.c, exit immediately if clear find_flag ### 4.86 / 1988-5-25 change prt_field in misc2.c to only pad to 13 char, avoid Ultrix display prob delete unnecessary space in prt_num in misc2.c, avoid Ultrix display prob more mods to the repeat old message code change create object command from ^Q to @ because of problems with control-flow ### 4.87 / 1988-5-26 don't flush keyboard buffer in create.c, inkey_flush no longer used anywhere ifdefs SLOW around sleeping code, so that it is optional allow much longer names to be entered to create command, wizard.c clear store -more- message when number of items drop below 12 eliminate large number of uses of the strings " " and "" looking at shop entrance was giving: you see no more The entrance to the magic shop.. changed version number to 4.87 increased stack to 20 messages fixed spacing of equipment list header in file_character(), files.c stop run at edge of screen, changed get_panel in moria1.c add store_maint() call when restore save file, one for every day the file is old ### 4.87 / 1988-5-27 fixed *enchant armor* so that it randomly picks an object to enchant, scrolls.c fix town_gen() of generate.c so that stairs always put in same place in msg_print() of io.c, clear find flag to terminate a run made sure that documentation race/stat class/stat adjustments corresponded with the program the save adjustments based on classes were all wrong mages fos (i.e. perception) was too high (i.e. bad) added range adjustments to stealth and perception, so that display of this abilities gave better results, misc2.c files.c ### 4.87 / 1988-5-28 added character rerolling again to create.c fixed indentation of race and class choices in create.c added zillions of register declarations eliminated the three different Makefile, put all configurable stuff in config.h ### 4.87 / 1988-6-1 when spell casters lose exp, they lose spells too quickly lose spell when: not high enough level, num spells more than int allows should lose spells when lose int/wis brought all miscellaneous documentation files up to date store_open and turn changed back to worlint/int for save file compatibility reasons put new version on ucbarpa, and announced it on the net ### 4.87 / 1988-6-3 when removing item in wear() moria1.c, do it after inven_destroy, otherwise inventory may increase to 23 objects thereby overwritting wielded obj add message about void/int declaration of signal() to signals.c ### 4.87 / 1988-6-4 added message about gcc needing -fwritable-strings to Makefile and INSTALL reworked Makefile to make it easier to use ### 4.87 / 1988-6-6 fix check_pwsd in misc2.c, was strcpy 12 char (13 with \0) into 12 char array ### 4.87 / 1988-6-9 added separate SYS_III and SYS_V defines to config.h added indirection to function call in spells.c, damage() -> (*damage)() fixed initialization of damstring in gold_list, treasure2.c ### 4.87 / 1988-6-14 updated README and INSTALL documentation new Flexnames sed file for 4.87 umoria, created by shortc add refresh call to shell_out, move duplicate code in dungeon.c into shell_out SYS_III compiler gives register allocation errors for *= and /= operators only 23 of them, so I removed them all ### 4.87 / 1988-6-27 treasure1.c: rings of weakness are now initially cursed spells.c: cdefense & 0x80000000 should be cmove, correct comments many files, check places where find_flag cleared and make sure move_char(5) is called if necessary, particularly msg_print caused a problem ### 4.87 / 1988-6-28 Makefile: updated dependency list for .c files many files: change move_char(5) to move_light(char_row, char_col, char_row, char_col), avoid problems with potential recursive calls to move_char() # Working on: Cheating/Tricks: should potion stat increase (in_statp) be based on cstr (including ring plus) or on the characters base str (without ring plus) presently cstr which allows cheating by manipulation of cstr should de_statp be changed also? should de_statt followed by in_statt increase stat to next 18/xx mod 10? grape jelly trick, restore levels take you back to old hit point levels or give new levels based on current stats? can circle monsters, by only using diagonals in a diamond shape Things to be fixed: check plurals "~" in object names, esp. Pint in treasure2.c make get_panel run stops user customizable? objects in outside corners do not stop run moria1.c: area_affect does not need to check "if (find_flag)" carefully check everyplace that clears find_flag, there are a lot of unnecessary checks, perhaps even some recursive calls to move_char check setting of moria_flag in dungeon.c, esp. after restore_char () add "moria ^file" option to automatically do wizard restore, do the umask calls in the program instead of forcing user to type them don't ask for quit on char '\0', should instead ignore it check for EOF when reading characters complaint that a hangup (kill -1) results in a game whose score will no longer be posted to the scoreboard resting should have small but finite chance of waking a monster perhaps check once every 10 turns against randint(6) > stealth cursor should be positioned somewhere before each inkey call all npc spells should print descriptive message allow rerolling after choose race/class remove "clear" from random potion names? pad should take a char not a string print a number with the repeat_msg command Message line overextending into second line should be erased identify staffs/wands as having zero charges, when they run out ``can't carry that much weight'' before ``that many items'' is wrong? I like it the way it is now, though still too slow on i/o, perhaps rewrite to avoid refresh() calls? perhaps use only motion optimization stuff? Dot tunneling command encrypt info before writing it to the save file perhaps xor with stat buffer dprint in death.c is stupid!! prt_map in misc1.c is stupid!! draw_block and sub2_move_light is pretty stupid also, in moria1.c!! problems reported with many calls to detect_sdoors, I think this is a result of the vault_trap bug fixed 4/6 subval, missile_ctr could be short ints note that some armor can decrease to-hit values in docs, should these be displayed in name? missing amulets, 1-4 strength, constitution, intelligence, dexterity missing rings 5-6 gain wisdom, gain charisma note that missing rings/amulets are disjoint missing staff 9 genocide missing staff 24 mass_genocide high level offensive spells give have a far lower damage/mana ratio than magic missiles how to abort run on systems without ^C? perhaps some ifdefed code which does no-blocking I/O for them? add death due to starvation wielding heavy wepaons, fix + to hit on screen? don't let dragons breathe on first attack, AMHD is nasty especially after the screen changes to the next panel checking both ((!m_ptr->ml) or (!see_invis && mon invis)) is probably redundant when determining whether or not player can see a monster list of spell books not cleared when get cast which spell? prompt should we call m_name if spell only applies to visible creatures? many spells do not print desciptive comment, aggravate_monster for instance stores: haggling gets very boring add prompt to fix price of all objects with same starting price always go to minimum price after player has exhibited his excellence at haggling when moving on objects, say what character moved onto (like rogue) be able to move onto objects without picking them up (like rogue) for throw command, ask for direction first (like rogue) SYS V does not lock scoreboard when changing it compressing save files would make many people happy it is pretty easy to defeat the file protection mechanism, should I toughen it up? if amulet raises stat over 118, stat will be lowered (more than it should be) when take amulet off, fix this by having 4 values for each stat this file should be in reverse chronological order! clonning shimmering molds, one droppped a chest, went over to search chest, panic save, get panic save everytime restore character, this may be related to old trap generation problem fixed 4/6 weight problem reported, character can not pick anything up? what if can't write into current directory? (saves files, etc.) should detect monster work on invisible creatures if have see invisible ability from ring/sword/etc.? does not understand ~ in path names loss of strength doesn't force you to drop items in general, loss of an ability should have many affects, non of which are implemented, for example, lose of intelligence should cause a mage to lose spells cases of permanent slowness reported? add user name to scoreboard? also add points and experience to scoreboard change cost in sell_haggle to a long change item_value to return a long change turn to a long store_open should be a long use of reset_flag for free moves still a little inconsistent fix save files to include obj desc, then only need one random number generator which can use table program replace magic numbers with constants name objects if use did not identify them add a pull-back map command like the PC version, display entire map on screen only showing important features, like stairs Very Hard things to add: recenter character on screen? can't look in any direction, only in the 8 dirs can't cast in any direction, Long term stuff, enhancement suggestions: give player something to do with money, i.e. 1Million gp for a HA +10 +10 sword 'flavor' the levels, dragons on one level, undead on another, etc. what's been discovered list use environment variables to specify rogue-like/original keys use environment variable for default save file add option to restore files from default save filename commands not close enough to rogue style fixed item lettering in inventory, in-line/cmd-line options? scoreboard have all scores, not top twenty command line option to print out times open can't drop, identify, pick up, or throw items in equipment list Y destroy command, allow destroy from equipment list let o, c, D commands automatically pick direction when they can give rogue's a chance to steal, e.g. let monsters carry items that they pick up give magic users a chance at pre identifying scrolls could use a help system, like VMS version make scroll of identify more common, to help non-MU character classes Features: hero/superhero do not subtract hit points when effect wears off WOR scroll can save you with minus hit points!! detect monster does not detect invisible monsters (normally) can hit monsters in walls, but can not cast at them can not enchant something with negative numbers (i.e. cursed items) how does armor protect against breath/gas? it doesn't!!! run stops one character before room lights, because it is supposed to ### 5.0 / 1989-2-26 all files: merged the umoria and PC-Moria sources creature.c: get_moves(), 12 numbers were wrong for monster movement wizard.c: a few more int to bigint_t changes needed in wizard mode commands files.c: fix columns in monster dictionary so that everything lines up misc2.c, moria2.c: line up columns for print_new_spells() and examine_book() 8 files: replace == with = in comments and strings magic.c, misc1.c, misc2.c, moria2.c, signals.c: missing/misplaced breaks monsters.c: 2 grey snakes, renamed one to green misc1.c: compact_objects, stmt which decrements cur_dis in wrong place spells.c: disarm_all should not set traps to zero, should just clear trap bits moria2.c: in jamdoor() i_ptr->p1 should be t_ptr->p1 moria2.c: in place_trap(), covered pits should transform to open pits moria2.c: mon_take_hit(), int i should be bigint_t i io.c: more prompt will now accept space/escape/return/linefeed io.c: EOF code did not work on machine with unsigned characters ### 5.0 / 1989-2-27 save.c: missing controlz calls on error returns creature.c: don't let non-moving creature move if glyph prevented it from attacking creature.c: give monsters a chance to recover from Bash, depends on square of monster level moria2.c: give monsters a chance to ignore bash, function of hitpoints and level spells.c: fixed restore_level, needed while loop moria2.c: monster_multiply(), i=18 statement in wrong place, would not always multiply monster when it should ### 5.0 / 1989-3-2 moria1.c: get_panel(), add one to y and x on lines 945/950, prevent scrolling while inside rooms ### 5.0 / 1989-3-9 wizard.c: tmp_val = -999; for gold changed to tmp_lval signals.c: added SIGXCPU to list of signals that cause panic save spells.c: call monster_name before mon_take_hit in fire_bolt() moria2.c: move_char (), stop before attacking a creature dungeon.c: added move_char (5) to see_infra and see_invis setting and clearing ### 5.0 / 1989-3-10 creature.c: added take_hit (0, ddesc) line to every monster spell, ensure that player leaves resting/find/search modes potions.c: remove chp += mhp code from gain const potion, now chp = mhp moria2.c: change monster_death(), summon_object 4d2 instead of 4d3 so that comments match code creature.c: make_move(), prevent monster from moving again after opening a door, also, make chance of opening locked door dependant on monster level monsters.c: giant red ant lion, not hurt by fire, move&attack normally ### 5.0 / 1989-5-5 generate.c: fixed error in generation of type 3 (subtype 3) rooms misc2.c, config.c: added alternate wizard code/definitions treasur1.c: restore mana potion made more valuable (35 -> 350) staff of speed value increased (800 -> 1000) ring of sustain charisma level changed (7 -> 44) ring of stupidity level decreased (20 -> 7) potion of gain strength value increased (200 -> 300) variable.c: halfling clan elder had 0% chance gave warrior higher base to hit chance than paladin store1.c: digging tools must be identified before price is adjusted misc1.c: do not adjust cost of digging tools based on value of p1 change all mods to fields toac/todam/tohit to be += or -= several items (mostly cursed items) needed %P1 added to name for digging tools, inc p1 instead of replacing it, so that tools will still have same minimum values as specified in treasur1.c monsters.c: Forest Wight should be able to open doors. Grey Wraith should not drain mana moria1.c: fix stealth so that it uses the value of p1 treasur2.c: change 'a's to '&'s for doors spells.c: word of destruction should not put wall over character Could not use wand of drain life at a distance moria2.c: when in find mode, attack if run into creature only if can't see it ### 5.0 / 1989-5-7 moria1.c,moria2.c: remove heavy weapon message from test_hit and put it in py_attack instead spells.c: teleport_away now calls unlite_spot if necessary, fixes bug that I thought had been fixed 3/28/88 save.c: for MSDOS: when restore save file, must search object list for secret doors/traps, and convert the '#/.' char to wallsym/floorsym, when save file, must do the reverse translation spells.c: unlight_area() was really messed up, is much shorter now, will always print "darkness" message now if it should misc1.c: cleaned up prt_map(), probably much faster now moria1.c: cleaned up light_room(), draw_block(), sub2_move_light(), they are probably much faster now all: changed print(char *, int, int) to print(char, int, int) in io.c misc1.c: crown of the magi, now has RL instead of see invisible ### 5.0 / 1989-5-8 all: checked uses of fm/pl/tl for consistency,eliminate extra parens/tests/etc death.c: changed call to exit(1) to exit_game instead, io.c, dungeon.c: new function move_cursor_relative to position cursor on map ### 5.0 / 1989-5-13 Moria.doc: added changes to bring it up-to-date, ran through Spell again ### 5.0 / 1989-5-18 spells.c: polymorph monster spell incorrect, missing an else flag = TRUE line moria2.c: fix setting of reset_flag in find mode in move_char() routine Moria.doc: created MacWrite version for Macintosh, plus some more editing corrections ### 5.0 / 1989-5-29 death.c, externs.h, files.c, io.c, misc2.c, moria2.c: eliminated the function pad, replaced most calls with printf %-30s formats death.c: rewrote fill_str and renamed it to center_string, previously was returning pointer to local variable death.c: eliminated dprint, replaced it with calls to put_buffer signals.c: suspend handler now saves/restore special local chars (^R, ^W, etc) generate.c: rewrote blank_cave, should be a bit faster now externs.h, io.c, misc1.c, variable.c: eliminate used_line array, no longer call erase_line in prt_map in misc1.c misc2.c, moria1.c, store2.c: make sure cursor is always in the right place after an inkey call create.c, misc2.c: rewrite put_character and associated routines, line up columns, eliminate strcat/strcpy calls files.c: rewrote file_character so that it matches put_character, etc. io.c: fixed put_buffer so that it won't print past right edge of screen files.c, misc1.c, moria1.c, ms_misc1.c: loc_symbol rewritten, now checks values of pl/tl/fm, did not correctly handle monsters which were visible, but were not in a lighted spot moria1.c, io.c: rest() only erases MSG_LINE now if original 'hit ... to quit' message displayed, new function in io.c erase_non_msg does this misc1.c: magic_treasure(), fixed helms which had wrong else level of cursed items, added cursed rings of searching, set p1 value for amulet of magi ### 5.0 / 1989-5-30 treasure1.c, treasure2.c: three axes changed from type 21 to 22, this prevents the Priest shop from buying these weapons ### 5.0 / 1989-6-2 creature.c, moria1.c, moria2.c, spells.c, types.h: massive reorganization of the way that visible monsters are handled, now ml is set if and only if the creature is visible on screen creature.c, externs.h, moria2.c: procedure check_mon_lite removed, calls to it replaced by calls to update_mon () misc1.c: delete unused code creature.c, moria2.c: two places needed to test fm in addition to pl and tl spells.c: need to set fm/tl/pl before call to lite_spot/change_trap, eliminate some unncessary calls to lite_spot, eliminate unneccsary clearing of fm ### 5.0 / 1989-6-8 dungeon.c, moria1.c, moria2.c: allow searching while blind, decrease chance of successs if blind or have no light moria1.c, moria2.c, store2.c: change prt("", x, y) to erase_line (x, y) misc2.c: replace all "\0" with just "" io.c, moria2.c, scrolls.c, store2.c: add '.' to end of sentences dungeon.c, misc2.c, moria2.c, scrolls.c, store2.c: add msg_flag = FALSE after lots of msg_prints, to make sure never get " -more" message line creature.c, dungeon.c, spells.c, staff.c: don't print 'light' message if blind moria2.c: disarm_trap(), make no_light and blind same penalty, increase blind/no_light and confused penalties moria1.c: rewrote area_affect, delete redundant code, don't stop at doorway if no_light and it is unlit generate.c: fixed error in build_tunnel, stack points could overrun stack ### 5.0 / 1989-6-9 moria1.c: change test_hit so that always miss 1/20, and always hit 1/20 moria1.c, moria2.c, spells.c: changed fval incorrectly when manipulating doors, only change to corr_floor2 if next_to4 a room space creature.c: in get_moves, change 1.732 to 2, makes it faster, also can no longer circle monsters by moving in a diamond shape death.c, io.c, files.c, main.c: for MSDOS, don't leave the highscore_fd file open permanently save.c: save value of total_winner, in case of a forced call to save_char() all, types.h: deleted MSDOS ifdefs, the bytlint/byteint/wordint/worlint type definitions were confusing, so changed all to int8/int8u/int16u/int16 generate.c: rewrote loops to try to speed up level generation ### 5.0 / 1989-6-12 misc1.c: for items with duplicates in treasure1.c, set level of all equal to that of lowest treasur2.c: several items did not match same item in treasure1.c ### 5.0 / 1989-6-13 treasur2.c: wands/staffs in store now have same level as those in dungeon dungeon.c, misc2.c, moria1.c, ms_misc.c, store2.c: removed some ASCII dependencies ### 5.0 / 1989-6-16 externs.h, constants.c, generate.c, misc1.c: fixes to make level generation faster, rearrange order build_tunnel makes comparisons, call correct_dir directly instead of through rand_dir, rewrite loops, rewrite next_to4, rewrite next_to8 externs.h, files.c, misc1.c, moria1.c, ms_misc.c: loc_symbol returns char == the following are due to CJS (Bruce Moria) create.c: in get_money(), charisma was subtracted and added, now only add desc.c: unquote() did not work at all, rewrote the whole thing in identify, string[0] == 'T' should be string[1] moria1.c: area_affect, for direction 1, the 3 should be a 2 misc2.c, moria2.c, scrolls.c, spells.c, store2.c: change msg_print(" ") to "" store2.c: eliminate erase_line/display_commands at end of purchase_haggle config.h: for defined(Pyramid), define ultrix eat.c: py.stats.intel to s_ptr->intel creature.c: in make_move(), set m_ptr at begining of function, in mon_move(), set m_ptr at begining of function, test cmove before calling randint in mon_move() change if (randint(10) > py.misc.stl) to && (randint(...)) dungeon.c: move random teleport outside innermost loop, put with other checks == end CJS fixes == creature.c: collapse a large number of if (...) if (...) constructions to if (... && ...) constant.h, desc.c, externs.h, generate.c, misc1.c, rnd.c, save.c,variables.c: all versions now use rnd(), rnd does not use SYSV calling conventions anymore BSD random() states eliminated generate.c: added code to build_tunnel to prevent infinite loops ### 5.0 / 1989-6-19 *.c: collapse a large number of if (...) if (...) constructions to if (... && ...), delete unneeded braces ### 5.0 / 1989-6-21 creature.c, spells.c: build_wall and earthquake, now kill monster if it can not move through walls, and it can not move out of the way of the new wall, also, does more damage to those that escape, in creature.c, if in wall, must escape to empty space ### 5.0 / 1989-7-17 main.c: added a setbuf call, for buffered output ### 5.0 / 1989-8-4 merging changes due to Christopher J Stuart...zillions of changes stat structure changed externs.h: move functions definitions for lint to here create.c: help functions sorta' added, money now more dependent on stats, really good/bad characters eliminated, etc... ### 5.0 / 1989-8-8 creature.c: new function disturb used, stop attacks if player dead, add message for two attacks which had no message, confuse-monster only works if monster actually hits, door bashing different and noisy too!, creatures only eat lower level creatures, monster drawing changed, monster memory added, etc... ### 5.0 / 1989-8-9 desc.c: eliminate lots of unnecessary strcpy/strlen/etc calls, can inscribe objects, store/dungeon objects will be merged in the inventory when objects are identified, if curse a special object it loses its special ability, %d code for damage, %a code for armor, etc... eat.c: misc changes... ### 5.0 / 1989-8-11 help.c: moved help text into files, added monster recall info, etc... ### 5.0 / 1989-8-14 Moria.doc: editing changes, removed lots of unnecessary hyphens io.c: rebuilt it (accidentally deleted current version) death.c: scoring procedures moved elsewhere, print_tomb more efficient and uses far less stack, final char display different, upon_death changed to exit_game, etc... generate.c: added STATIC and void magic.c: reduce indentation, increase spell damage, 7 dam/mana for Magic Missle, 4 d/m for balls, 5 d/m for bolts, etc... misc1.c: crowns higher chance of magic, monster dist changed to increase chance of higher level monsters, unnecessary uid/eid calls deleted, m_level changed to include level[0] monsters, highlighting for ores, disappear percentages for objects in compact_obj() modified, '!' char added to magic descriptions, damage added to bows ### 5.0 / 1989-8-15 misc2.c: print message if gold/object created under player, stat handling completely changed, new func title_strings, search/rest/paralysis/count message print changed, new func prt_speed(), new stat functions, can hide stat changes if unknown, get_name uses loginname if none entered, change_name can print to file, inven drop/carry/etc changed to be a little simpler, new func join_names, spell_chance/learn_spell/etc chnaged to be a little simpler, get_spell with capital letter verifies choice, etc... ### 5.0 / 1989-8-16 monsters.c: no change treasure1.c: %d added to weapons, %a added to armor, plus to dam added to bows, mage's guide should be mages' guide?? appostrophe removed ### 5.0 / 1989-8-17 treasure2.c: change & in traps to a/an, scroll/potion subvals changed to 200 plus value in treasure1.c, etc... potions.c: every potion prints message, ident true only if something noticable happens, greatly reduce indentation, stat handling different, etc... prayer.c: reduce indentation greatly, use up mana if prayer fails, etc... scrolls.c: reduce indentation, only ident if something noticable happens, for identify must search for new location of scroll, etc... sets.c: ifdef out unused functions dungeon.c: add command counts, use new func disturb(), double regen if searching, damage if really hungry, messages for protevil resist_heat and resist_cold, new code for quiting rest mode, move teleport code outside inner loop, add code to check strength/weight, find mode done differently now, allow ^char for control characters, all command chars translated to rogue_like chars, new commands = (options) { (inscribe) V (view scores) M deleted but W improved : (map area) rogue-like summon ^S -> &, etc... ### 5.0 / 1989-8-24 files.c: init_scorefile deleted, intro->read_times, don't modify argv, new func helpfile, print_map deleted, print_monsters deleted, file_character now takes filename as argument, etc... io.c: lint defs, new suspend function, much better curses handling, new func moriaterm and restore_term, inkey does refresh on ^R, flush should work now, new funct get_check, save_screen, restore_screen, etc... constant.h: add defs for STATIC, ESCAPE, stats, increase treasure in streamers, change store min and turnaround amount, MAX_MON_NATTACK, MAX_EXP wands.c: inscribe wand if empty, reduce indentation, etc... staffs.c: inscribe staff if empty, reduce indentation, etc... spells.c: identify- update stats if affected, allow ident of equip items; better light_line messages; when set pl false, must set fm false also; misc, etc... ### 5.0 / 1989-8-29 moria1.c: new function enchanted(), part of py_bonuses split into calc_bonuses, cur_char1/2 deleted, complete rewrite of inven_command routines, new functions check_weight/verify, can display weights, get_item can show either inven or equip, options added, run code completely rewritten, misc, etc... ### 5.0 / 1989-8-30 store1.c: item_value has *item->number removed, many routines changed to pass item pointer, identify removed from store_carry, known1/2 put in store_create, store_maint alg changed, etc... store2.c: clean up handling of msg_flag, incremental haggling in get_haggle(), display_command moved from purchase/sell_haggle to store_purchase/sell, enter_store code cleaned up, etc... config.h: misc... externs.h: add new variables, delete old variables, rearrange stuff, etc... types.h: added logging structure and recall structure, etc... variables.c: add new variables, delete old variables, rearrange stuff, remove learn from spell structure, change titles so none shared between classes, etc... ### 5.0 / 1989-8-31 moria2.c: weight bug fixed, py_attack did not decrement inven_weight when inven_wield was a missile, and the player was carrying more than one of them ### 5.0 / 1989-9-1 moria2.c: carry has pickup option, new functions for inscribing objects, summon_objects returns type/number of objects created, ditto monster death, new function check_view, add todam for using bow/arrow, bashing changes, new sub py_bash extracted from old bash code, jamdoor p1 values changed successive spikes have smaller effect, etc... ### 5.0 / 1989-9-2 main.c: add bruce moria comment, new options, read environment variables, etc. wizard.c: wizard can change weight and speed of character, etc... unix.c: new file, contains UNIX specific code for user_name, check_input, and system() signals.c: completely rewritten to be much nicer, same functionality recall.c: new file, for printing out monster memory info save.c: completely rewritten, same functionality, I think ### 5.0 / 1989-9-7 lint on mips machine ### 5.0 / 1989-9-8 lint on AIX (SYS V) ### 5.0 / 1989-9-9 lint on ultrix ### 5.0 / 1989-9-11 fix anonymous errors in order to get the program to compile... io.c, ms_misc.c: put screen_map in io.c, and made it a default feature signals.c, main.c, io.c: on EOF, the program now returns ESCAPE char until exits dungeon(), then saves with no penalty ### 5.0 / 1989-9-12 externs.h, desc.c, variables.c, types.h: mushrooms/colors/rocks/etc changed to char pointers instead of char arrays, saves space, make random init faster, change player title to char pointers also moria1.c, potions.c, misc2.c, eat.c, dungeon.c, creature.c: cleanup handling of chp and cmana, call prt_c* only once every time they change dungeon.c: set pointers only once at start of procedure eat.c: reduce indentation io.c, dungeon.c: remove msg_flag = FALSE line from inkey and put it in dungeon.c where get command ### 5.0 / 1989-9-14 creature.c: change put_qio calls to just setting screen_change to true dungeon.c: remove superfluous erase_line and put_qio calls many: make character numbers more clear, 'a' for 97, DELETE for 127, etc... desc.c: objdes() bug with ins_buf handling, now clear ins_buf if none many, moria1.c: made "Which dir?" default for get_dir() ### 5.0 / 1989-9-15 misc1.c, monsters.c, treasur[12].c, many others: changed all hit ponts from strings to a char array of size two, changed damroll to take two characters instead of a string, this eliminates most all scanf calls ### 5.0 / 1989-9-18 monsters.c, creature.c, types.h: replaced strings containing monster attacks with 4 byte array containing index into a monster attack table, this eliminates the rest of the scanf calls, and saves space creature.c: many duplicate damroll calls in make_attack collapsed into a single damroll call moria2.c: in chest_trap(), moved exploding chest trap to end to avoid dangling pointer problem; in twall, only light spot if c_ptr->tl is true; wizard.c, recall.c, desc.c, files.c: changes to fix handling of new damage types for monsters and weapons ### 5.0 / 1989-9-19 many files: eliminated redundant trap_lista, fixed place_trap so that it no longer takes a typ parameter, change_type no longer calls place_trap, negative level values for traps all made positive and diasrm_object() changed appropriately externs.h, dungeon.c, misc2.c, moria1.c, variables.c: eliminated print_stat variable since it was not used very much ### 5.0 / 1989-9-21 create.c, externs.h, variable.c: eliminated bit_array variable variable.c: eliminated names of unused Rogue class spells many files...: changed the floor/wall definitions from variables to constants, changed the values to make tests easier, all fval comparisons now use manifest constants, door type floors eliminated since new find/run code made them unnecessary constant.h, misc2.c, externs.h, variable.c: changed var stat_column to constant, eliminated password variables many files: changed moria_flag to new_level_flag, and reset_flag to free_turn_flag ### 5.0 / 1989-9-23 merged Xenix diffs and Atari ST diffs treasure1.c: potion of sleep no longer cures blindness wands.c: wand of wonder was wrong had 2 << randint() instead of 1 << randint() eat.c, potions.c, scrolls.c, staffs.c, treasur2.c, wands.c: added store bought flag 0x80000000 to all food/potion/scroll/staff/wand objects in treasure2.c, modifed the code so that these objects do not give experience when used all files: removed all floating point code except for randnor() and los() many files: hp_player(-damage,"") calls did not work, change them all to direct calls to take_hit(damage), hp_player string parameter removed since no longer used ### 5.0 / 1989-9-25 constant.h, config.h, externs.h, variable.c, misc1.c, misc2.c, potions.c: floating point randnor code replaced by integer on that uses a table, definition of MAXINT removed so there are now no differences between 16 bit and 32 bit versions of moria, MAXSHORT and MAXLONG defined Makefile, misc1.c, externs.h: calls to floor removed, math library no longer used!!, DONT_DEFINE_CLASS def for SUN4 no longer needed ### 5.0 / 1989-9-27 misc1.c: replaced los code with an integer arithmetic version by jnh (Joseph Hall), moria now uses no floating point numbers dungeon.c, files.c, moria1.c, store2.c, wizard.c: removed all sscanf calls except for those in ms_misc.c, and one "%lx" in wizard.c ### 5.0 / 1989-9-28 treasure1.c, treasure2.c, types.h: change subval and number to 8 bit integers, move flags after name, move number before weight, save 12 bytes per constants.h, etc: INVEN_MAX definition removed, INVEN_ARRAY_SIZE 1 smaller variable.c: store_choice now array of 8 bit integers monsters.c, treasure2.c: change t_level and m_level to array of 16 bit ints many: interpretation of subval changed to fit it into byte, new range uses p1 to decide whether stacking permitted, torches can now stack create.c, types.h: changed size of history from 400 to 240 characters, also print as lines of 60 instead of lines of 70 variable.c, misc2.c, types.h: changed definition of spell_type from 12 to 6 bytes, new array spell_names to prevent duplicate strings, no longer have entry for warriors variable.c, types.h: human female too light, made 150 from 120, also changed every base/mod field from 16 bits to 8 bits types.h, variable.c: in background_type, change next and bonus fields to 8 bit integers, added 50 to bonus to make it positive ### 5.0 / 1989-9-29 monsters.c: massive changes to monster file to fix inconsistencies ### 5.0 / 1989-9-30 set.c: make flasks of oil vulnerable to frost and fire, like potions moria2.c: if set off a trap, temp set confused to zero to ensure that player will move onto the trap instead of in random direction io.c, externs.h: confirm() no longer used, deleted treasur1.c, treasur2.c: give non overlapping subvals to potions/scrolls, duplicate ones caused problems for giving random names desc.c, treasur1.c: %M for mushrooms changed to %m recall.c: fix printing of monster hit points, two numbers instead of a string moria2.c: new look code from bruce moria, look in arcs so that can see anything on the screen, allows access to monster memories generate.c, misc2.c, moria1.c, moria2.c: various fixes to reduce code size, increase speed, etc... misc1.c, etc...: popm and popt no longer take a pointer, instead return int, pusht takes an int8u now instead of an int as a parameter ### 5.0 / 1989-10-1 all: added copyright notice to every *.[ch] file ### 5.0 / 1989-10-3 config.h, creature.c, recall.c, save.c: fixed a few bugs picked up by the ultrix compiler dungeon.c: disabled the save command ### 5.0 / 1989-10-6 create.c, files.c, dungeon.c, main.c, save.c: wrote helpfile function, changed calling sequence so that caller does not have to do anything constant.h, desc.c, variables.c: eliminated lots of excess colors/rocks/etc. eliminated all duplicates except between potions and mushrooms types.h: fixed _frac problem with exp and chp, had to be unsigned shorts misc1.c: made traps invisible again moria1.c, moria2.c, store2.c: replaced strcpy(string, "constant") code with p = "constant" where possible treasure1.c, treasure2.c: changed subvals for wands/staffs/amulets so that they start at zero, and don't have any missing numbers spells.c: teleport_away() must clear m_ptr->ml before calling update_mon() moria1.c, spells.c: check lite_spot() calls, eliminate unnecessary ones death.c, misc2.c, wizard.c: eliminated lots of unnecessary blanks in string constants ### 5.0 / 1989-10-7 misc1.c, moria2.c, treasure1.c, treasure2.c: fixed lamp/torch subvals, again can not let torches have subval >= 64, the torch wield code will not work store1.c: don't let items with subval >= 192 stack in stores, they are always handled as a group anyways spells.c: fire_bolt and fire_ball fixed in regards creature lighting variable.c: store_choice for temple wrong because broad aze renumbered earlier, changed 13-15 to 12-14 constant.h, dungeon.c, moria1.c: added print ac flag to status field, calc_bonus sets flag, prevent printing AC while in store create.c: dis_ac must have dis_tac added to it spells.c: detect_monster evil and insivible now set m_ptr->ml when print monster on screen, then creature() will correctly erase them ### 5.0 / 1989-10-8 types.h: fixed _frac problem with cmana, had to be unsigned short moria1.c: in inven_command wear code, print 'too heavy' message after print wielding message store1.c: item_value, must multiply value by item->number before returning store1.c: sell_price return zero if item should not be put into store monsters.c: disenchanter bat sleep set from 0 to 1 all: eliminated all unnecessary elipses constants.h, creature.c, dungeon.c, eat.c: new defines for the creature move, spell, and defense fields, eliminated all hex constants from the 3 c files ### 5.0 / 1989-10-9 moria2.c: fixed falling rock trap in move_char(), step back so that player is not under the rubble variable.c: put copyright message in string, so that it appears in executable spells.c: create_trap modified to light up trap, in case it is an open pit all:replace prt calls with put_buffer where ever possible, because it's faster externs.h, io.c, signals.c: put ignore/default/restore_signal code back in wizard.c: rewrote the version info dungeon.c, moria2.c, signals.c: modified to use get_check() for consistency ### 5.0 / 1989-10-10 several spelling errors in CJS code misc1.c: forgot to change magic_treasure code when changed subvals a couple of days ago for wands/staffs/amulets/rings externs.h, files.c, misc2.c, types.h: wrote new stat code, all stat bugs fixed now, 6 stat values now, get_dis_stat() eliminated, new function modify_stat(), misc2.c: get_obj_num changed, to increase probability of getting higher level objects misc1.c, treasur1.c: had to change gain stat bits, the low 6 order bits of treasure flags, because changed the order earlier ### 5.0 / 1989-10-11 all: changed all 4 or less case switch statements except for one large one in generate.c to nested if statements, this is smaller and faster ### 5.0 / 1989-10-12 dungeon.c, generate.c, moria1.c: new_spot moved from dungeon to generate because alloc_mon and place_win_mon use character position, fixed minor new_spot bug constants.c, misc2.c, dungeon.c: bst_stat() can not call prt_stat, changed to set py.flags.status, new stat change flags defined, and stat print code added to dungeon.c creature.c: many duplicate disturb/strcat/msg_print brought outside the switch statement in mon_cast_spell all: changed all unlite_spot() calls to lite_spot() calls, changed 'if (test_light()) lite_spot()' code to just call lite_spot, this fixes many subtle bugs, mostly dealing with mon vis to infravision, unlite_spot code deleted files.c, misc2.c: added stat_adj(A_INT) to srh which was wrong misc2.c, spells.c, moria1.c, types.h: id_stat() and hid_stat[] eliminated no more hidden stat values, display arg dropped from bst_stat() files.c, misc2.c: hex values replaced with constants ### 5.0 / 1989-10-14 constants.h, store1.c: increased store turn around from 4 (avg 2.5) to 9 (avg 5), changed store_main() to always call store create/destroy regardless of bounds, lowered values of bounds MIN and MAX ### 5.0 / 1989-10-18 moria2.c: when open chest, clear TR_CURSED flag, which just also happens to be monster win flag, to prevent easy wins constants.h, misc1.c->wizard.c: replaced remaining hex magic numbers with manifest constants dungeon.c: added code that gives a player a chance of noticing that items in inventory/equipment list are magical, the chance is much higher for warriors than for magi ### 5.0 / 1989-10-19 dungeon.c: ^P^P did not clear the line with the pause_line message misc2.c: prt_state() did not clear all 3 digits of repeat counts all: moved lots of procedures around to increase number of static fuctions, also made sure every C file less than 60K create.c, extern.h, misc2.c, spells.c, variables.c: change way that mhp are calculated, new function calc_hitpoints(), new array player_hp[] which contains player mhp for each level extern.h, main.c, misc2.c, potions.c, spells.c: change way that cmana are calculated, new function calc_mana(), called whenever gain level, lose level, or learn/forget spells extern.h, main.c, misc2.c, spells.c: change the way that spells are learned and lost, learn_spell/learn_prayer deleted, new function calc_spells, new spell type state forgotten, new var spell_forgotten, etc. ### 5.0 / 1989-10-20 monsters.c: made spirit troll into 'G', made invisible, pickup, carrys obj, added drain wisdom attack, added drain intelligence attack to green glutton ghost all: moved all msg_print(NULL) calls into io.c, specifically, clear_screen(), erase_line() and prt() moria2.c, spells.c: two places that called test_light to see if monster or moving object lit were wrong, changed to eliminate check of fm creature.c: update_mon() failed when hallucinating, changed print(cchar,...) to lite_spot() call variables.c: adjusted call stat adjustments, mainly to make priest harder, gave priest negative str adjust, inc expfact from 10% to 20% misc2.c: new funcs prt_int() and prt_long(), same as prt_num/prt_lnum() except no string argument, ": " moved into prt_num/prt_lnum string spells.c: all wands now have limits on range monsters.c: doubled Balrog eye sight from 20->40, evil iggy from 20->30 eat.c: increase hp gain for 22/23/24, increase hp loss for 27 constant.c, eat.c, potions.c, scrolls.c, staffs.c, treasur2.c wands.c: eliminate store bought flag, only gain exp when use an item if the use identifies the item ### 5.0 / 1989-10-23 magic.c, wands.c: spell/wand damage did not match creature.c: monster stuck door bashing got sign of i_ptr->p1 wrong recall.c: change corrosive gases to poison gases externs.h, moria1.c: draw_block(), minmax(), maxmin() deleted, minmax and maxmin calls were unnecessary, draw_block only called from one place, sub2_move_light combined with sub1_move_light, sub4_move_light combined with sub3_move_light, creature.c: fix mon_move() so that creatures that never move increment their r_attack[0] when standing next to the player, for Quylthulgs spells.c: detect object and detect treasure set tl, which could also light up monsters, they now set fm ### 5.0 / 1989-10-24 spells.c: can not gain exp by disarming traps created by a scroll types.h, magic.c, prayer.c, variable.c, misc2.c, constant.h: removed sname from spell_type since it was a trivial value, changed sexp from 16 bits to 8 by dividing value by 4, saves about 310 bytes many files: eliminated long stretches of blanks in strings, remove repeated chars from strings, typically " : ", saves over 1K of data size ### 5.0 / 1989-10-25 spells.c: aiming a ball type wand/spell at a 1-thick wall resulted in damage to creatures on the other side of the wall, now it doesn't misc2.c: inc_stat changed so that player gains 1/6 to 1/3 of the distance from the max 18/100 stat, 0 to 100 takes 13 gain stat potions, dec_stat changed so that player loses 1/4 to 1/2 of the distance from the max 18/100 stat, 100 to 0 takes 13 lose stat potions misc2.c: print_spells() modified so that spell are always listed with the same letter, i.e. the letter does not depend on whether or not you know the spells that precede it ### 5.0 / 1989-10-26 death.c: day string was size 11 should be 12 dungeon.c: make 'magik' detect same chance for each class, need to set i_ptr = &inventory[INVEN_LIGHT] before testing player_light, this could cause the light to go off permanently misc2.c: calc_mana and calc_spells use p_prt->lev - class[].first_spell_lev-1 instead of just the player lev types.h, variable.c: add new field to class info, first level in which the player can learn a spell, used immediately above variable.c: green glutton ghost no longer drains int misc2.c: print 'welcome to level message' before learning spells ### 5.0 / 1989-10-28 config.h, externs.h, dungeon.c, wizard.c: version info put in a help file, game_version() function deleted files.c: removed obsolete print_map() and print_monster() code treasur1.c: made ring of WOE decrease wisdom, to balance ring of stupidity, removed trailing ^ secret symbols which made no sense, changed constant values from known2 to known1, such as ring of lordly protect armor class values store1.c, store2.c, externs.h, types.h: add storenice feature by Dan Berstein, if (good bargain > 3 * bad bagains + 20) then always get final price in that store, two new functions, noneedtobargain() and updatebargain() moria1.c: fixed wear command so that it prints inven letter of item removed, and equip letter of item worn, added extra param to remove() generate.c, moria1.c, moria2.c, types.h: added an 'lr' flag to cave_type, this is set for every space in or next to a room, lr is true for lit rooms, and false for dark rooms, find_light() function deleted as it was no longer necessary, twall lights spots that have lr set moria1.c, moria2.c: decrease search chances if hallucinating, decrease disarm chances if hallucinating, look fails completely eat.c: print message "You feel drugged." for mushroom of hallucination moria1.c: eliminate unnecessary in_bounds() calls io.c: check to make sure tabs are all 8 character apart treasur2.c: made object_ident array bss misc2.c, moria1.c: fix calc_bonuses() so that it when wielding heavy bonuses, it subtracts the weight penalty from dis_th (display to hit), check_strength() modified to call calc_bonuses() misc1.c: fixed compact_objects() and compact_monster() so that they update screen when deleting objects/monsters creature.c, spells.c: put message in aggravate_monster(), took out redundant mesasge in creature.c moria2.c: modify py_attack(), inven_throw() and py_bash() to make it more difficult to hit monsters which are not lit ### 5.0 / 1989-10-31 moria1.c: sub3_move_light() modified so that it does not print @ when in find mode recall.c: had an 'i == 11 | i == 18' which should have been a || printed out 11st,12nd,13rd levels incorrectly, now uses 'th' moria1.c: fix see_wall, it assumed walls are # which is not true for PC/Atari spells.c: breath() could do damage on the other side of walls, added an los() call to prevent this moria1.c: area_affect treated 'potential corner' and 'branching side corridor' identically, which is wrong, added see_nothing() so that these could be handled separately ### 5.0 / 1989-11-1 moria1.c: when wearing object and must remove current object, fix code to use inven_check_num to see it is possible; in wear code, when remove old object check to see if inven_ctr increases before actually increasing wear_high constants.h, misc2.c, moria1.c, store1.c, store2.c, treasure1.c, treasure2.c: change definition of subval 192 so it can be used for torches, it only stacks with others if have same p1 value, but it is always treated as a single object, change ITEM_* definitions in constant.h to match new definition, fix all code fragments that test subvals desc.c: change to use new ITEM_ definition moria1.c: fixed the potential corner/corridor intersection bug in the find code again, except this time it works better than the original code ### 5.0 / 1989-11-3 variables.c: decrease priestly HP bonus from 3 to 2 moria1.c: fixed wear code bug, called inven_check_num() with item not slot ### 5.0 / 1989-11-4 moria2.c: in tunnel(), print message if player tries to dig with hands moria2.c: several places where object/treasure picked up did not clear c_ptr->fm, this is necessary since it might be set by detect spell ### 5.0 / 1989-11-7 moria1.c: fixed find_init so that moves straight into a corner would correctly run around the corner moria1.c: changed the examine potential corner code in area_affect(), it would run into rooms if corridor entrance next to a wall, at least it doesn't do that for lighted rooms anymore ### 5.0 / 1989-11-8 prayer.c: in remove curse spell, only clear flag for items that are wielded or worn io.c: truncate strings at col 79 instead of 80 if BUGGY_CURSES defined moria1.c: print weights in show_inven and show_equip at col 71 not 72 io.c, misc1.c, moria1.c: change highlight mode for walls to display '%' instead, this is much more portable that original code that used standout mode and the high character bit, changed print(),loc_symbol(), and see_wall() help.c: change help documentation for % character wizard.c: modify wizard_create and change_character functions, add whitespace, exit if get_string fails (i.e. typing ESCAPE exits), modify get_string limits to more reasonable values, remove switch for setting tchar moria_wiz_help, moria_owiz_help: correct errors in command list death.c: add msg_print(NULL) after show_inven() in print_tomb() so that inventory can't accidentally scroll of screen before exiting creature.c, moria2.c, spells.c: can't call prt_experience() in mon_take_hit() as this gives "new level" message before "killed monster", move prt_exp calls to after msg_print at everyplace that calls mon_take_hit() dungeon.c: modified repeat count code so that can edit the number entered leave cursor on msg line while entering repeat count moria2.c: change summon_object() so that it will only create objects within los of point where monster dies ### 5.0 / 1989-11-9 store1.c: stacking code in store_check_num() and store_carry() did not handle torches correctly, changed to be the same as inven_check_num code in moria2.c wizard.c: fixed mistake in change_character(), put *tmp_str != '\0' test in inside if statement, not outside one externs.h, misc2.c, moria2.c: fix printing of spell characters from get_spell, print_spells changed consec TRUE to nonconsec == -1 for consecutive case, otherwise spells start with nonconsec=='a', added new arg to get_spells() first_spell which becomes nonconsec when call print_spell wizard.c: move 'gold' from end of change_character to right after 'mana' since it is more frequently used than the rest misc2.c: alloc_object(), fix it so that objects are not created underneath the player, this was a problem for rubble and traps misc1.c: allow sling ammo to be enchanted in magic_treasure() store1.c: fix search_list() call for ammo in item_value(), was passing 1 ?!? instead of correct subval treasur2.c: subval for spike wrong, change from 192 to 193 misc1.c: remove see invisible from slay monster arrows, since it doesn't make sense variable.c: change blows_table entry for STR/W .5-.7 and DEX 10-18 from one to two, the value of one made things too difficult for low level mages monsters.c: for all monsters which used to be sleep value of 1 (old style), change their new style sleep values from 0 to 1 recall.c: stupid typo, change CM_CARRY_OBJ|CM_CARRY_OBJ to ..OBJ|CM_CARRY_GOLD ### 5.0 / 1989-11-10 moria2.c: disarm() had signs switched for trap chances, "tot - 100 + level" changed to "tot + 100 - level" variable.c: change "tin plated" to "tin-plated" and etc. ### 5.0 / 1989-11-13 save.c: changes to get the save code working again moria2.c: when move onto a trap, instead of restoring confused count, should increment it by the old value, since the trap may have set it generate.c: for type 2 rooms, make them dark below level 25, not 30 moria2.c: when close a door, print "Something" instead of monster name if the monster blocking the door is invisible moria2.c: in tunnel(), make the digging chance for non digging weapons equal to their max damage + tohit bonus instead of proportional to weight, the old code made lances better for digging than shovels moria2.c: change py_bash() chance, make it a function of monster hp + monsters average max hp instead of level ### 5.0 / 1989-11-15 save.c, undef.c: restore_stats function eliminated, replaced by call to read py.stats from file, other fixes to get restore code working creature.c: if creature never moves, kill it if it happens to be in a wall instead of moving it out of the wall generate.c: everywhere that TMP1_WALL is used, must also set fopen FALSE so that place_object(), vault_monster() etc will work correctly prayer.c: removed cure_blindness() and cure_confusion() from Holy Word spell, since could not pray while blind/confused signals.c, io.c, externs.h: eliminate redundant definition of signal() in signals.c, also eliminate suspend_handler and assignment to it from signal(), make suspend() non-static moria2.c: in look(), put blind and image tests before get_alldir() moria2.c: in look() change [y to recall] to [(r)ecall] creature.c, externs.h, generate.c, misc1.c, monsters.c, moria1.c, moria2.c, save.c, spells.c, types.h, constant.h: change m_list from linked list to a linear list, muptr deleted, monster_type nptr field deleted, function pushm deleted, now all scans of m_list go from mfptr (which always points at last element) to MON_MINIX == 2 misc1.c: compact_monster no longer calls prt_map(), which is unnecessary ### 5.0 / 1989-11-16 eat.c: fixed restore charisma mushroom, print 'stops' instead of 'starts' moria2.c: fix calculation of avg_max_hp in py_bash(), didn't check CD_MAX_HP monsters.c: spirit troll too hard to hit when can't be seen, lower AC 56->40 treasure2.c: fixed scare_monster trap, the subtype value was wrong 63->99, and the level needed 100 added to it misc2.: fix get_spells(), must add first_spell to *sn instead of subtracting from spell[i] creature.c: in mon_move, return immediately if creature is killed because it is in rock ### 5.0 / 1989-11-17 externs.h, spells.c, scrolls.c, prayer.c, staffs.c: change dispell_creature() to dispel_creature() spells.c: dispel_creature uses m_ptr->ml after calling mon_take_hit() which is wrong, now save value before calling mon_take_hit() moria2.c: turn find_flag into a counter, when it reaches 100, exit find mode with message "You stop running to catch your breath.", prevents infinite loop generate.c: fixes to help prevent isolated rooms, in build_tunnel() make sure tunnel has gone at least 10 spaces from start before stop it, in cave_gen(), copy [yx]loc[0] to [yx]loc[k] so that can build tunnel from last room to first spells.c: light_line() called lower_monster_name() which is wrong, change to monster_name() call externs.h, creature.c, moria2.c, spells.c: removed seen argument from mon_take_hit, as it was used inconsistently, only get credit for a kill if monster is visible when killed save.c, dungeon.c: call disturb in save_char() to turn off searching and resting, remove search_off call in dungeon.c as is no longer needed variable.c: remove initialized data for py and old_msg since were (mostly) zero, set f_ptr->food and f_ptr->food_digested in main.c constant.h: delete some unused constants, rearrange the order of a few others ### 5.0 / 1989-11-18 variables.c, externs.h, monsters.c, treasure2.c, save.c: every variable written to save file needs definate size, can not be just 'int', also spell_forgotten now read/written ### 5.0 / 1989-11-20 spells.c: sleep_monsters1() did not set NO_SLEEP info for monster memory externs.h, moria1.c: changed new_spot to accept short pointers since char_row and char_col are now shorts creatures.c, spells.c: multiply_monster(), removed slp parameter since it was always FALSE, added monptr, to fix the m_list bug creatures.c: new m_list code fails when call delete monster from within creatures(), fixed it by changing some calls to delete_monster() to call delete_monster() only if the dead monster is between the current monster and mfptr, otherwise, call fix1_delete_monster() externs.h, creatures.c, spells.c: breath() has new argument monptr, to fix the m_list bug externs.h, creatures.c, spells.c: two new functions, fix1_delete_monster() and fix2_delete_monster() which together equal delete_monster, these are called from within creatures() when a monster is killed many: eliminate the fopen field since I had 5 bitfields in the cave structure, and it is much easier to only save 4, added a BLOCKED_CORR case for secret/closed/locked/stuck doors and rubble, all code that used to test fopen now tests fval, most code that used to set fopen is now gone since it was redundant with fval setting code moria2.c: fixed disarm() so that if there is a valid trap to disarm, it will print "Something/monster_name is in the way!" just like close door." generate.c: place_stairs() could place them on the boundary, make sure that x/y can never be 0 or cur_xx-1, also, in town_gen(), call place_ boundary() before place_stairs(), looks better, though not necessary moria2.c: fixed openobject() so that if there is something to open, it will print an message just like closeobject if there is a monster in the way save.c: fix another save file problem, noscorefile defined as int in get_char, just eliminated it and use noscore instead ### 5.0 / 1989-11-22 moria2.c: fix1_delete_monster() has to set m_ptr->hp to -1 creature.c: monster eating code checked monster exp wrong, was using cptr to index into the c_list creature.c: in creatures(), call fix2_delete_monster if hp < 0 moria2.c, spells.c: dragon breath, wand bolts, wand balls, and thrown objects were visible even when blind, added checks, only print them if can see moria2.c: tunnel(), print message "* is in the way" if there is a monster in the spot where the player is trying to tunnel ### 5.0 / 1989-11-23 save.c: player_hp was not saved/restored, total_winner and panic_save were not restored ### 5.0 / 1989-11-27 moria2.c: in tunnel, add the weapon damage to its digging ability create.c: get_history(), clear the history strings before writing them with new values, to get rid of old strings variable.c: for background strings, change "1st child" to "first child" store2.c: add parameter to get_haggle() last_offer, in sell_haggle and purchase_haggle must set new_offer to 0, in get_haggle only allow incremental bid if new_offer == last_offer, i.e. at least one previous bid has been made dungeon.c: in main loop, call creatures(FALSE) to light/unlight creatures when ever player_light value changes ### 5.0 / 1989-11-28 save.c: new save code, to reduce size of save file, and to make the savefiles portable constant.h, desc.c, generate.c, misc1.c, misc2.c, moria2.c, save.c: changed the t_list from a linked list into a linear array of entries, just like the previous m_list change, new constant MIN_TRIX, all pusht calls changed to delete_object where ever appropriate, etc. ### 5.0 / 1989-11-29 misc1.c: increase p1 value for crowns of seeing by multiplying by 5 ### 5.0 / 1989-12-4 creatures.c: change stealth code, instead of randint(10) < stl, it now cubes randint(64) and compares against 1 << (17 - stl), the result is that a change of one in stl always decreases monster notice chance by 80%, and perfect stealth is impossible wizard.c: allow wizard to enter any stealth between -1 and 17 save.c: extensive changes to save code competed, save files are now xor encrypted, and are supposedly portable, all savefile protection code is gone ### 5.0.8 / 1989-12-5 save.c, undef.c, misc1.c: fix a few lint errors changed version number to 5.0.8 and release the sources creature.c: changed stealth code, amount monster woken by from 100 to 125 (used to be 75 before 12/4) ### 5.0.8 / 1989-12-6 spells.c, creature.c: changed the way wall building wands work again, kill creature if it never moves, otherwise do a little damage, if creature can not move out of rock in mon_move, then do more damage and remove the wall ontop of the creature various: check use of "?", especially in get_check() and verify() prompts, verify changed to replace the period with a question mark spells.c, creature.c, misc1.c: breath(), update_mon(), and loc_symbol() changed to use blind status bit instead of blind counter, this helps make the display look better, i.e. nothing disappears until blindness takes affect in dungeon.c monsters.c: more monster fixes from DJG monsters.c, misc1.c, moria2.c, treasur1.c: change (SM) to (SA), slay monster to slay animal monsters.c: set the new 'animal' bit correctly for all monsters recall.c: change 'unnatural creature' to 'natural creature', change a few constants to defines save.c: add error exits to every loop/array index in the get_char() routine to prevent crashes due to bad save files store1.c: in item_value, set value to zero if known cursed, this prevents store from buying known cursed items ### 5.0.8 / 1989-12-7 store2.c: remove switches in prt_comment*() functions, replace with table look up to get proper string, saves much code space creature.c: changed stealth code again, if resting/paralyzed monster has a chance of noticing once every 50 turns instead of never like before, change 64 to 1024, and 17 to 29 for a better distribution constant.h, save.c: read noscore variable from savefile of dead character, now only read/write noscore for live characters, changed patch_level to 9 so that old savefiles can still be read save.c: in get_char(), the loop error prevention code was missing two gotos store2.c: get_haggle(), when check for valid first use of incremental haggle, ignore leading spaces in the string moria2.c: add_food() should increment slow value, not just set it when bloated save.c: when can't create savefile, test for from_savefile before giving wizard message "Overwrite old?", when exit in wizard mode do the "readable to all?" check after closing the file save.c: from_savefile set wrong, should always be set if have read from a file not just when a complete character is read moria2.c: bash(), do not put character off balance if bash is successful misc2.c: get_spell(), instead of beeping for unknown spell, print a message recall.c: for monsters with sleep value of 0, could never learn that fact, change it so that this will be known if have killed 10 of them ### 5.0.8 / 1989-12-9 dungeon.c: call disturb when player takes poison damage, or starvation damage variable.c: change the three doors from 'a ...' to '& ...', needed because of wall-to-mud wand wizard.c: create_object wrong, was calling popt() then delete_object() then setting c_ptr->tptr, this fails because the delete objects will move the location (in t_list) of the just created object will change treasur2.c: change 'nothing' item to be stackable, so that it can be safely picked up save.c: was calling store_maint before objects were reidentified, this caused stores to end up with multple entries for stackable identified items externs.h, misc2.c: new function inven_check_weight(), check toosee whether picking up an object will change the players speed moria2.c: carry(), if picking up an object would slow you down, prompt with question "Exceed your weight limit?" to give player a choice moria2.c, misc1.c: only show rock types in look() if highlight_seams option is set, also change option text from "highlight" to "HL and notice" spells.c: in fire_bolt() and fire_ball(), set recall info if monster takes extra damage because of weakness or reduced damage because of breath, in dispel_creature() set EVIL/UNDEAD flag if creature hurt, in turn_undead() set UNDEAD if creature hurt, in drain_life() set CD_UNDEAD if creature not hurt recall.c: "may carry one or two treasures" needs the plural, but the others remain singular/collective ### 5.0.8 / 1989-12-11 creature.c: stealth value changed again from 125 to 100 wizard.c, variable.c: change rogue base stealth from 4 to 5, let wizard set stealth to 18 monster.c: white worm mass sleep value from 10 to 1, crystal ooze 60 -> 1 creature.c: in drain mana spell, if r1 greater than cmana, then set r1 equal to cmana to limit creature's hp gain recall.c: when print out creature attacks, exit loop if i >= 4, caused problems for umber hulk, etc. which have 4 attacks store1.c: instead of destroying all single stackable objects, only destroy one half on average (randint(number)), makes store 1 and 5 inventories look better, oil is much more common, identify a little more common treasure2.c: accidentally change blank_treasure tval earlier, set tval back to 0, and set subval to 64 store1.c: change store_carry(), for group items, must update scost; for any stackable item, set ipos so that inventory will scroll to show it; let group items (except torches) stack over 24 since each group is created with a set number to begin with; ipos must be set to -1 at start not 0; store2.c: store_sell(), pass a pointer to cur_top instead of just its value, if sold object not on the page shown, scroll to show the object ### 5.0.8 / 1989-12-13 recall.c: only print out known attacks, i.e. check r_attack, change i to count from 0 to 3, fixes bug with knowdamage(), add variable j to count attacks actually printed out save.c: put limit on number of times store inventory changed in get_char() store2.c: rewrite incremental haggle code again, add new parameter 'num_offer' to receive_offer, change get_offer parm last_offer to num_offer, only allow inc haggle if num_offer != 0; must reset new_offer after rejected offer in recieve_offer and sell/purchase_haggle ### 5.0.8 / 1989-12-14 misc2.c, lots: change way spell learning works, new command 'G'ain magic spells, new function gain_spells() in misc2.c formerly part of calc_spells(), new status flag PY_STUDY controls printing of 'Study' in status line, new field py.flags.new_spells number of spells can learn origcmds.hlp, roglcmds.hlp: document the 'M'ap and 'G'ain magic commands variables.c, potions.c: remove potion of learning as it is no longer useful ### 5.0.9 / 1990-1-1 creatures.c: in mon_cast_spell(), must update_mon() to light monsters before they cast a spell ### 5.0.9 / 1990-1-2 moria1.c: inven_command(), could not wear item which replaced equip item if inven full, only fail if inven_check_num fails for torches misc2.c: change spacing for stats on change_character page, was printing in last column for max stat moria2.c: tunnel(), if no shovel/pick, divide total tabil chance by two monsters.c: change eye-sight and sleep values for consistency, as noted by djgrabin help.c: change the monster recall so that it lists high level monsters first creature.c: when monster bashes down a door, call disturb() to halt resting io.c: fix msg_print() so that -more- is always printed on screen, fix put_buffer() so that does not fail if col argument greater than 79/80 misc1.c: change chance for ego weapons/missiles to 3*special/2 instead of just special, needed because treasure distribution change has made ego weapons not as common as before ### 5.0.9 / 1990-1-11 monsters.c: let nether wraith phase, set Black/Red/Multi ancient dragons to a sleep value of 700 creature.c: increase r_attacks if either notice true or r_attacks is nonzero (i.e. it has been noticed before) moria2.c: move_char(), when step back after setting off a falling rock trap, check to see if have stepped back onto another trap moria1.c: inven_command(), when restart it after taking off an item, don't print the 'have to drop something' message because that is confusing misc2.c: modify_stat(), could overflow 118 when increasing stats, add extra case to enforce max of 118 ### 5.0.10 / 1990-1-12 all: apply william setzer's objdes() diffs, 500K patch file, change version number to 5.0.10 and distribute desc.c, misc2.c, store1.c: remove several obsolete uses of index(), insert_num() no longer used anywhere constant.h, desc.c: change ID_TRIED,ID_KNOWN1 to OD_TRIED,OD_KNOWN1 desc.c: eliminate titles[], rename title_space[] to titles[] all: fixed lint errors all: make sure that all objdes() strings are bigvtype desc.c: fix printing of bows/arrows/picks, needed new p1_use define, and rearrange code in objdes() switch statement treasur1.c, treasur2.c: remove -100 tohit from books, was being printed, and is silly anyways constant.h, externs.h, save.c, treasur2.c: change object_ident[] from MAX_OBJECTS to OBJECT_IDENT_SIZE = 7*64, see object_offset() in desc.c to see why this is necessary ### 5.0.10 / 1990-1-13 externs.h, misc2.c, store1.c: deleted obsolete references to treasure_type desc.c: always print toac field if known2 and non-negative config.h, io.c, misc1.c: eliminated BUGGY_CURSES define, caused too much trouble, just assume that can never print in last column of screen constant.h, desc.c, misc1.c, moria2.c, spells.c, treasur1.c, types.h: created special_names table to hold all possible name2 strings, name2 changed to an int8u which holds index to special_names table externs.h, main.c, treasure1.c: sort_objects() function deleted, sorting now done by init_t_level using O(n) bin sort, into sorted_objects[] array files.c, misc2.c: object_list[] references that assumed it was sorted now indirect through sorted_objects[] array main.c, variable.c: player_init indexes now point into object_list instead of inventory_init ### 5.0.10 / 1990-1-14 constant.h, externs.h, main.c, store1.c, treasur1.c, treasur2.c, variable.c: deleted inventory_init array, stores objects now in object_list, MAX_OBJECT adjusted, MAX_DUNGEON_OBJ defined, store_choice[] fixed all: put all objects in object_list, trap_list, door_list, store_door, gold_list, up_stair, down_stair, rubble, much, blank_treasure, scare_monster are not separate variables anymore all: invcopy now takes an object_list index as second parameter, name field deleted from inven_type and fields rearranged ### 5.0.11 / 1990-1-15 store1.c: search_list() deleted, use i_ptr->index instead to get raw item cost save.c: obsolete treasure_type references deleted, save files work again creature.c, magic.c, misc2.c, prayer.c, save.c, wands.c: fixed long/short problems with shift, mostly change (1 << i) to (1L << i) all: add new register declaration, check indentation, fix 32/16bit int problems constant.h: change version number to 5.0.11 ### 5.0.11 / 1990-1-17 spells.c: aggravate was not initiallized in aggravate_monster() monsters.c: novice rogue now evil like other rogues misc2.c: 2 "... learn ... new spells ..." messages changed to print prayers instead for priests moria2.c: drop_throw(), "The %s hits %s" changed to "The %s hits the %s." many: fixed problems with setting of free_turn_flag ### 5.0.11 / 1990-1-19 misc2.c: fix inven_carry(), to stack store/dungeon items properly desc.c: objdes(), set p1_use = IGNORED for food and potions, changed is_a_vowel(tmp_val[1]) to [2] moria2.c: "exceed weight limit?" message now prints item name also desc.c, spells.c, staffs.c, wands.c: empty wands are not incribed empty if known2, empty removed when wands are recharged desc.c: put spaces between inscribed words dungeon.c: always inscribe majik when character notices this, not just for inventory items as that helps priests more than fighters store1.c: sell_price(), succeed only if both item->cost and item_value() are positive creature.c: don't increase a number of attacks when monster repelled in make_attack(), i.e. no damage done so shouldn't learn about damage moria1.c: regenhp and regenmana, could get chp/cmana equal to max and _frac non-zero, change c > max test to c >= max test constant.h, dungeon.c, misc2.c: hp and mana were printed while in store/inv, fix calc_hitpoints() and calc_mana() to set flags instead of printing, in dungeon.c, checks flags and print values ### 5.0.12 / 1990-1-20 store2.c: redisplay store prices if charisma changes during the inven_command calls (actually just redraw everything) moria2.c: monster_death(), dump must be set to 0 is number is 0 constant.h, moria2.c, recall.c, spells.c: recall printing of treasure changed, instead of setting CM_ bits, store largest number of treasure ever generated in those 5 CM_TREASURE bits, changed monster_death() to set the bits the new way, changed uses of monster_death() to replace old treasure bits with new ones, the vague print out treasure amounts replace with printout of precise number of treasures generated externs.h, moria2.c: monster_death() changed to int32 monster_death() moria1.c: new_spot() contained line of code "i = randint(10)" that did not belong there, was formerly testing code desc.c: objdes(), force zero charges to always print, also, always print digging plusses even if zero main.c: items that initialize players inventory are marked store_bought(), this is needed so that dagger/armor bonuses will be shown for them desc.c: objdes(), fixed printing of doors, known1() clear the tried flag, magic_init() don't let scroll names end with a blank misc2.c: gain_spells(), call calc_mana() if py.misc.mana == 0, this is for characters who did not know any spells before the 'G' command, can't gain spells if blind/no light/confused moria1.c: infinite loop entered with 'id' and screen_change, also, 'n' to the continuing with inventory prompt did not work, must clear doing_inven before returning from inven_command() in these cases desc.c: identify(), don't merge items in equip list, only merge items that are single stackable misc2.c: inven_carry(), inven_check_num(), fix item stacking, items can only stack if they are both identified or both unknown (store items are always identified) constant.h: change version number to 5.0.12 and distribute ### 5.0.12 / 1990-1-21 desc.c: objdes() bug, change p1_use = IGNORED to p1_use == IGNORED moria2.c: move_char(), moving into a wall is no longer a free turn misc2.c: gain_spells(), priests no longer need book before can learn spells from it, mages still need the books, calc_spells() delete unused var spells[], remove limit of 23 from number of new spells dungeon.c: made scribe_object() a free turn again recall.c: only print monster speed if at least one movement flag known unix.c: missing */ after Gakk!! -CJS-. desc.c: stacking did not work right, fixed known1_p() to return OD_KNOWN1 instead of TRUE for store items, so that known1_p() == known1_p() will work consistently monsters.c: creeping coins hit instead of bite, and touch instead of breath variable.c: fixed problems with temple/alchemist store inventories desc.c, store1.c: fixed food prices, only check unknown if subval puts it among the molds/mushrooms ### 5.0.13 / 1990-1-24 misc2.c, types.h: change inscrip back to char *, fix inscribe() to malloc space for string, and then copy inscription into the malloced space, save.c: fixed (compatibly) to write/read new inscriptions desc.c: free malloced inscription space in invcopy death.c, misc1.c: delete max tcptr code constant.h, desc.c, misc1.c: define new const ID_SHOW_TOHIT, in magic_treasure set this bit for every weapon,digging tool,missile,bow, in objdes() print out tohit/todam if this bit set and object identified, this makes it easy to tell ident weapons from unident weapons main.c: char_inven_init() must set ID_SHOW_TOHIT flag for initial weapon death.c, constant.h, misc1.c: change MAX_TALLOC from 225 to 150, delete temporary code which printed out max tcptr values misc2.c: allow inscriptions of up to 24 characters constant.h: change version number to 5.0.13 and distribute ### 5.0.13 / 1990-1-26 wizard.c: wizard_create(), initialize the name2 and inscrip fields to 0, init the ident field to known2 and storebought constant.h: changed MAX_TITLES from 60 to 45 desc.c: magic_init(), wrote to a char *tmp; modified to write to a vtype string recall.c: change printing of treasure again, only print 'sometimes' is 60% is only bit set, otherwise always print 'often' if only one object, for two objects, print 'often' only if 60% and 90% are only bits set scrolls.c: recharge scrolls not used up when read, fixed setting of used_up spells.c: res = TRUE; moved out of if-stmt so only needed once treasure.c: change wiz obj name from "& wizard object" to "{wizard object}" ### 5.0.13 / 1990-1-29 moria2.c: tunnel(), give player attack on creature if it is in the way misc2.c: gain_spell(), remove limit of 22 on learning spells, fix print_spells so that it will only show the first 22 spells constant.h, desc.c, misc1.c: change ID_SHOW_TOHIT to ID_SHOW_HITDAM, set this bit for gauntlet/rings of slaying, and irritation/enveloping cloaks, fix objdes() so that it only prints both tohit/todam if this bit is set otherwise, only prints one number, if either of tohit/todam nonzero desc.c, treasure.c: remove "the entrance to the " from store doors and put in objdes() in desc.c misc2.c: put_misc2(), add "Exp to Adv." to character display constant.h, desc.c, misc1.c: added defines for ID_NOSHOW_P1 and ID_SHOW_P1 to improve printing of object names, objdes() always prints p1 if ID_SHOW_P1 set, and never prints it if NOSHOW set, these override the default p1_use setting, NOSHOW added to sustain stat rings, SHOW added to clumsy/weak gloves, boots of speed/stealth/slowness, helms of int/wis/infra/might/lordliness/magi/beauty/seeing/ stupid/dull/weak/ugly, cloaks of stealth creature.c: increase effect of aggravate_monster attack from range of 5 to 20 spells.c: dispel_creature(), prt_exp() was only called when visible creatures died, now always called when creature dies misc1.c: added messages to compact_objects() and compact_monsters() so that can tell if they are ever called store1.c: rings/amulets which are not known2 now have base value from object list instead of real value moria2.c, staffs.c, wands.c: cast level to int, so that calculations will be done with signed arithmetic, in openobject() change chance of opening a chest, instead of -2*level use -level, this help eliminate chests which are impossible to open ### 5.0.13 / 1990-1-30 recall.c: print ' at normal speed' for monsters with mspeed = 1 ### 5.0.14 / 1990-2-10 spells.c: turn_undead, speed_monsters, sleep_monsters2, and dispel_creature modified so that they only affect creatures in los of player externs.h, misc2.c, variable.c: new array spell_order[], holds order in which spells are learned, in calc_spell() spells are now remembered in the order that they were learned, and forgotten in the reverse order learned, in gain_spell() must modify spell_order when spell learned main.c: spell_order initialized in inven_char_init to all 99s save.c: save/restore spell_order, for patch_level <= 13 savefiles create a reasonable spell_order on the fly misc2.c: was erasing wrong line in gain_spells, change `j+2' to `j+1' all: change file names to be 8.3 characters long constant.h: change version number to 5.0.14 and release ### 5.0.14 / 1990-2-10 dungeon.c: change rogue-like wiz help key from ^? to \ all: merged in Mac sources, all initialized variables are now in variable.c, rearranged directory structure ### 5.0.15 / 1990-2-11 all: merged in VMS sources changed to 5.0.15 and distribute ### 5.0.15 / 1990-2-13 moria2.c: carry(), printed 'worth of emeralds..' removed '.' from string desc.c: crowns did not print out AC because it is zero, add special case to objdes() so that tval == 33 (crowns) always have AC printed out, change (+5 charges) to (5 charges) ### 5.0.16 / 1990-2-14 moria2.c: inven_throw() created second copy of pointer to malloced inscription, it now creates new inscription for new object moria1.c: inven_command(), wear option copied malloced inscription, create new inscription for new object misc2.c: inven_destroy(), free inscription of just deleted object, clear inscrip pointer of last objects previous location, so that it will not be freed, inven_drop(), create new inscription for new item, inven_carry() create new inscription for new item, scribe_object() free space for old insription since it is deleted config.h, externs.h, misc2.c, moria1.c: rename remove() to takeoff() to avoid naming conflict with library routines externs.h, misc2.c, store1.c: deleted join_names(), no longer doing anything useful, and was wrongly creating duplicate pointers to malloc blocks moria2.c: make moving into walls a free turn again, but player can not attack invisible creatures in walls by moving into wall (hence preventing him from using 6 free turns to search for invis creature), must tunnel into wall instead, which always takes a turn dungeon.c, externs.h, moria2.c: pass direction to tunnel() instead of point digging to, eliminates a lot of duplicate code, tunnel() now checks confusion, and tries to tunnel in random direction if confused treasure.c: clone monster wands changed from level 2 to level 15, makes them recharge less, max 6 new charges instead of max 18 new charges files/version.hlp: add Dan Bernstein's name to credits magic.c, variable.c, wands.c: change stinking cloud from 16 to 12 damage as it was too close to lightning bolt at 18, reduce mana for mage/ranger from 4/6 to 3/5 moria1.c: show_inven() and show_equip(), don't print first two spaces when col is zero, since will blank the whole screen anyways moria2.c: py_attack(), only cleared confuse_monster flag if monster survived attack, now always clear this flag and try to confuse monster ### 5.0.16 / 1990-2-16 all: add in diffs to get MSDOS version working again, add files from binary PC-Moria distribution types.h: for PC, use 'unsigned char var : 1' to reduce sizeof cave_type from 5 to 4 desc.c, misc1.c, misc2.c, moria1.c, moria2.c, save.c, wizard.c, types.h: change inven_type inscrip field from pointer to an 13 char array, mallocing storage for inscriptions does not work, results in dangling pointers when inven_types are copied save.c: add 'time' to savefiles, use this to calculate age of savefile instead of using stat, much more portable, and prevents cheating, if time is newer than current time, set age to 0, when save file check to see if current time greater than start time, if not, then save start time plus one day in save file mac.c: delete getfileage since no longer used recall.c: change knowdamage(), add damage parameter so that higher damage attacks take longer to learn than lower damage attacks monsters.c: jellies and molds no longer have treasure save.c: two places have (*str != NULL), changed to (*str != '\0') save.c: remove support for pre 5.0.13 versions, rd_item() no longer needs patch_level monsters.c: move crystal ooze from level 31 to level 40, to make raising crystal oozes for treasure much more dangerous ### 5.0.16 / 1990-2-19 all: added defines for tvals, and substituted every where in program ### 5.0.16 / 1990-2-21 ms_misc.c, io.c: remove all uses of mvprintw() for ibmpc port, since PC curses also pulls in the fp library if mvprintw is used dungeon.c: in the recover from blindness code, the disturb() call must be before the creatures(FALSE) call getch.c, io.c, Makefile: more fixes for the VMS port of umoria, new getch.c file, and new copy of (4.87) io.c file ### 5.0.17 / 1990-2-22 all: check again for numbers that should be constants constant.h: change version number to 5.0.17 externs.h, main.c, moria2.c, signals.c, treasure.c: compile with gcc -Wall and fix all reported errors ### 5.0.17 / 1990-2-26 prayer.c: change (!class[].spell == PRIEST) to (class[].spell != PRIEST) store2.c: for good bargaining, use final_ask not min_buy in sell_haggle() dungeon.c: change "Sorry.." to "Sorry." misc1.c: for gloves/gauntlets, p1 and ID_SHOW_P1 set in wrong place creature.c: make_attack(), creature could be visible at start of attack but invisible at end due to a teleport, set notice/visible appropriately before monster attacks, and don't use m_ptr->ml afterwards monsters.c: creeping coins always carry gold now, 1d2 for copper, 1d2+60 for silver, and 1d2+90 for gold treasure.c, wizard.c: change name of wizard items to empty string, and inscribe them with "wizard item" desc.c: problem with rubble/stone-to-mud, change rubble case from like stairs to be like doors moria1.c: show_equip(), change non-weight limit from 57 to 60, change weight limit from 49 to 52 chars, for show_inven(), change limits from 66/74 to 68/76 characters creature.c, magic.c, prayer.c, spells.c, wands.c: fix get_flags(), fire_ball(), fire_bolt() and breath() to use defined constants for attack types moria1.c: show_inven(), show_equip(), make sure that col never less than zero all: merged in Atari ST diffs from Stephen A. Jacobs ### 5.1.0 / 1990-2-27 creature.c: fixed bug with doors, always open/light door instead of only opening/lighting them when in los of player moria2.c: when bash open door, only break it 50% of the time to be consistent with code in creature.c all: delete some obsolete files, annotate a few others, put README files in the subdirectories to explain what is present save.c: fix old savefile compatibility code to work with new version numbers constants.h: change version number to 5.1.0 and publicly announce ### 5.1.0 / 1990-2-28 recall.c: creature is worth... sprintf() call used %ld instead of %d moria2.c: look_ray(), every place GRADF multiplied by y, cast it to long to prevent overflow of 16 bit ints ms_misc.c: were still two refs to 'byteint', changed to 'int8u' MLINK.LNK: updated for umoria5.0, add undef and recall, change treasur[12] to treasure create.c: delete sleep(1) call in character rerolling code death.c: put "#ifndef L_SET" around the #define L_SET io.c: ifdef out suspend code, for USG systems with BSDisms that have SIGTSTP defined but don't actually implement it io.c: change name of variable 'border' to 'screen_border' to avoid naming conflict on SGI/SYS V machine INSTALL: moved from doc subdirectory to root directory, and brought up to date ### 5.1.0 / 1990-3-1 misc2.c: two places where (1L << j) occurred, and j could be 99 (unknown spell from spell_order[]), this is an undefined operation, fixed source to not shift if j == 99 ### 5.1.0 / 1990-3-3 misc1.c: set_options(), typing '-' on line 0 gave error, change i=max to i=max-1 store1.c: item_value(), calculating digging tool values wrong, subtract off objects initial p1 plusses before multiplying p1 by 100 dungeon.c: for paralysed/resting, move cursor to player before refresh call creature.c: disenchanter bat attacks could put tohit/todam/toac below zero, fixed so that they can not go below zero moria1.c: if have MAX_SHORT hp/mana, then could get overflow in regenhp/mana routines, added checks for overflow dungeon.c, io.c: save on EOF code did not work, in inkey(), remove casts to char in eof test, remove call to feof(), in dungeon(), add !eof_flag to innermost while loop to ensure it exits on error save.c: in sv_write(), clear death flag if eof_flag or panic_save set, so player can restart game to see tombstone main.c: set death flag if hit points are negative, can happen if a HANGUP savefile or panic savefile is restored save.c: if get_char(), don't overwrite died_from string if player is dead main.c: if specify -r or -o, force rogue_like to appropriate value after read the savefile, since get_char() will modify rogue_like_commands moria1.c: search(), "have found a trap.." fixed, removed extra period monsters.c: added CD_NO_SLEEP flag to jellies, oozes, elementals and the gelatinous cube dungeon.c: fix "which level" wizard prompt, limited to level 99 max ### 5.1.1 / 1990-3-4 all: merged in more Mac diffs, Mac version should compile now wizard.c: stealth max 87 should be 18 in printed string desc.c: identify(), if cursed incscribe with ID_DAMD, unsample(), do not clear the ID_DAMD flag, since an unsampled item is still cursed config: change version number to 5.1.1 and release ### 5.1.1 / 1990-3-5 monsters.c: added CD_NO_SLEEP flag to creeping coins recall.c: recharge(), change chance of failure from randint((num+40)/10)=1 to randint((num+40-i_ptr->p1-i_ptr->level)/10)=1 to make it harder to recharge high level and highlly charged wands recall.c: change j from int to int32u, since it is used with flag fields, change two %d uses of it to %ld desc.c: objdes(), don't print ID_TRIED value for store bought items moria2.c: a slow dart trap now has no effect if have free action io.c: must ifdef out the tbuf.c_cc code, since this causes the program to fail for some reason ### 5.1.1 / 1990-3-13 signals.c: add void to USG signal_hander definition externs, ms_misc.c: move fopen declaration from externs.h to ms_misc.c, and make static, and make FILE * not struct _iobuf ms_ansi.c: include stdio.h generate.c: change #if DEBUG to #ifdef DEBUG config.h, generate.c, types.h: added ifdefs for IBMPC_TURBO_C ### 5.1.1 / 1990-3-16 moria1.c: inven_command() "Wear" prompt changed to "Wear/Wield" monsters.c: earth elementals/spirits can now move through walls spells.c: wall_build(), for earth elementals, increase their hit points files.c: helpfile(), can ESC out now spells.c: cloning a monster will wake it up now dungeon.c: allow two-char control character sequences to be used with repeated commands (deleted else from "else if (command == '^')") roglcmds.hlp: document how to avoid problem with repeated dig left command, i.e. '13^H' doesn't work, type ^ then H, or type SPACE then ^H ### 5.1.1 / 1990-3-19 unix.c: initialize num_chars to zero in check_input(), in case FIONREAD absent ### 5.1.1 / 1990-3-21 all: fix most SYS_V lint errors ### 5.1.1 / 1990-3-23 dungeon.c, io.c: calling inkey() when check_input true is wrong since it traps ^R and does other stuff, instead call new function raw_inkey(), which only calls getch io.c: flush(), occasionally caused loss of output, instead of using fancy ioctls, drain input by using check_input/raw_inkey ### 5.1.2 / 1990-3-25 dungeon.c, io.c, unix.c, atarist.c: AIX can't check to see if input is pending, but can do non-blocking read, so check_input changed so that it consumes input when called constant.h: change version number to 5.1.2 and distribute ### 5.1.2 / 1990-3-26 save.c: when resurrect, clear poisoned flag and food flag to prevent character from immediately dying again main.c, misc2.c, unix.c, save.c: fix some BSD lint errors moria1.c: remove unnecessary flush() call from take_hit() creatures.c, misc1.c, externs.h, variable.c: fixed problem with calls to compact_monster() within creatures() by adding a horrible hack, new variable hack_monptr defined to hold current creature() monptr ### 5.1.2 / 1990-3-27 variable.c: add infra 3/2 for elf/half-elf, were both previously 0, increase gnome infra from 3 to 4 config.h, death.c, dungeon.c, externs.h, main.c, misc2.c, signals.c: modify wizard mode code, no longer need password or correct UID to enter, prints disclaimer then asks for confirmation atarist.c, death.c, misc2.c, ms_misc.c, save.c, signals.c: eliminate all getuid() etc. code, only place these are still used is in main.c and unix.c all: merge wizard/god mode, god mode nolonger exists io.c, unix.c: problems with flush() on EOF, don't call check_input if EOF, also, check_input returns zero if got an error on the read, also inkey() clears msg_flag on EOF to prevent infinite loops exit_game/ msg_print/inkey/exit_game/etc... moria2.c: monster_death(), increase tries from 10 to 20 so that treasure will be created more often constant.h: increase MAX_MALLOC from 101 to 125 to reduce compacting monsters messages, increase MAX_TALLOC from 150 to 175 because it is possible to get compacting objects message during level generation constant.h: BTH_HIT deleted, no longer used ### 5.1.3 / 1990-3-28 moria2.c: facts(), multiply damage for missile by 2/3/4 instead of adding when use proper weapons, makes missiles much more useful save.c: write store info and time into save file even if dead, so that can be restored on a resurrection constant.h: change version to 5.1.3 ### 5.1.3 / 1990-3-30 constant.h, externs.h, files.c, misc2.c, moria1.c, moria2.c, staffs.c, wands.c, variable.c: modify how misc abilities vary with level, new variable class_level_adj, which holds the new adjustment values, bth, bthb, device, disarm and save affected externs.h, misc2.c, moria2.c: critical_hits() has fourth paramter, that indicates whether to use CLA_BTH or CLA_BTHB class level adj moria2.c: penalty for attacking invisible creatures modified, was minus lev * BTH_LEV_ADJ-1, now minus level * BTH_LEV_ADJ/2, hence warriors have less penalty than mages/priests creature.c, externs.h, misc2.c, moria2.c: test_hit has a fifth paramter, that indicates whether to use CLA_BTH or CLA_BTHB (or even CLA_SAVE) io.c: add abort() calls to move_cursor_relative(), print(), and put_buffer() to help find bugs in the code save.c: eliminate support for versions 5.0.11 to 5.0.13 misc2.c: get_spell(), only say 'unknown spell' if it is actually an alphabetic character typed treasure.c: delete one ring of adornment and one amulet of adornment, add arrows at level 15, and bolts at level 25 ### 5.1.3 / 1990-3-31 store1.c: change missile cost to 5*pluses instead of 10*pluses, since missiles appear in groups of 20 or so, this is comparable to normal weapons which are 100*pluses distribute 5.1.3 sources ### 5.1.3 / 1990-4-3 externs.h: ifdef out definition of errno for MSDOS io.c: moved process.h include for MSDOS from middle of file to start io.c: in print(), put_buffer(), and move_cursor_relative(), clear msg_flag before printing error message, prevents problems with '-more-' prompt recall.c: roff_recall(), don't set normal movement bit near beginning, also delete code that prints 'never moves' if all move bits clear spells.c: teleport monster will wake it up scrolls.c: reading a second word of recall scroll does not reset timer spells.c: wall building now heals Xorns in addition to earth elementals spells.c: drain life damage increased from 50 to 75, to match other 50th level wand damages constant.h, creature.c, moria2.c, variable.c: defined new class level adj CLA_MISC_HIT and replace all test_hit uses of CLA_SAVE with this, document that this is identical to save, and assumes all values same treasure.c: two potions named poison, change first to weakness (lose str) ### 5.1.3 / 1990-4-4 save.c: add some __GNUC__ ifdefs so that the UNIX style code will be used, not the code for the broken atari st MWC libraries ### 5.1.3 / 1990-4-6 unix.c: two include files, sys/time.h and sys/resource.h are actually BSD specific, put #ifndef USG around them ### 5.1.3 / 1990-4-9 desc.c: remove all uses of "%+d", because AtariST/MWC and convex and probably others do not support it spells.c: replace_spot(), clear lr flag, so that after destruction scroll, destroyed areas will not be lit by a light scroll in a room creature.c: let monsters eat other monsters of equal exp, helps prevent using oozes as treasure factories moria2.c: mon_take_hit(), if kill Balrog, always update recall info even if Balrog invisible, since will always know it was the Balrog (winner!) recall.c: sleep values too hard to learn, change test from r_wake > sleep to (r_wake*r_wake)>sleep ### 5.1.3 / 1990-4-13 creature.c, moria2.c: mon_take_hit() was called within creatures() when monsters were trapped inside a wall, bracket call in creature.c with code to set hack_monptr, and add code to mon_take_hit() to check it creature.c: add code at end of creatures() loop to fix2_delete_monster() in case monster died during movement (maybe it was in a wall?) externs.h: add DGUX to USG ifdef for the 'extern int sprintf()' declaration io.c: added code to save/restore local mode word of tty driver, i.e. TIOCLSET and TIOCLGET ioctl calls save.c: for atarist && __GNUC__, call fopen with "rb" and "wb" for binary mode io.c, unix.c: for atarist && __GNUC__, don't include externs.h: define sprintf as int (), if defined(atarist) ### 5.1.3 / 1990-4-14 externs.h, unix.c: three new functions tilde(), topen(), and tfopen() which perform ~ expansion for file names, in externs.h, define open to topen and fopen to tfopen for unix machines config.h, dungeon.c, main.c, save.c, unix.c: add support for ANDREW distributed file system, SECURE define in config.h, disallows inferior shells, also instead of giving up setuid priviledges at start, calls ANDREW library functions bePlayer(), beGame(), and Authenticate() unix.c: add ifdefs to use select() when compiling for xenix ### 5.1.3 / 1990-4-15 creature.c, externs.h, files.c, io.c, main.c, misc1.c, moria2.c, recall.c, save.c, treasure.c: more Atari ST MWC fixes, eliminate fdopen(), make loc_symbol() return unsigned char, all (var & long_const) constructs assign long const to temp var first, ### 5.1.3 / 1990-4-16 Makefile: fixed problems with 'make install' ### 5.1.3 / 1990-4-19 atarist.c: add #include at start, and #endif at end misc2.c: tohit_adj() toac_adj() todis_adj() todam_adj() all used cur_stat instead of use_stat like they should have ### 5.1.4 / 1990-4-20 moria2.c: mon_take_hit code of 4/13 wrong, use monptr instead of i at end misc2.c: remembering code wrong, add (i < 32) to stop it at end of spell list moria1.c: when inven full and take off object, check for object under player before allowing/asking her to drop object save.c: for atarist MWC port, ifdef out all of the open() calls, and only leave the fopen() stuff in misc1.c: compact_monsters(), don't set delete_any if fix1_delete_monster() is called, because it does not decrement mfptr, call abort() if cur_dis goes below zero (i.e. can't delete any monsters) all: run lint on 4.3BSD and SYS V constant.h: change version number to 5.1.4 and distribute ### 5.1.4 / 1990-4-27 dungeon.c: add prt_map() at top after creatures(FALSE) call for ATARIST_MWC, fixes display problems for some reason ### 5.1.4 / 1990-5-1 moria1.c: inven_command(), clear msg line is ESC typed at "Drop all?" prompt ### 5.1.4 / 1990-5-3 all: eliminated upd_log(), and plog variable doc/*: update all documentation, except moria.ms ### 5.1.4 / 1990-5-4 all: visit every subdirectory, create README or ERRORS files as needed, make everything as up to do as possible, split files greater than 60K so that they can be posted to comp.sources.games easily all: deleted all tmp.* files externs.h, moria1.c, moria2.c, et al: moved functions out of moria1.c and moria2.c to reduce their size to less than 60K, many functions not moved were made static dungeon.c, externs.h, main.c, misc2.c, save.c, variable.c: new variable to_be_wizard, allows resurrect without entering wizard mode, resurrect sets noscore = 0x1, wizard mode sets noscore = 0x2, prt_winner prints "Is/Was wizard" or "Resurrect" is noscore set ### 5.1.4 / 1990-5-5 misc1.c: moved see invisible from slay animal to slay undead weapons doc/moria.ms: updated documentation to reflect changes from 4.873 to 5.1, added section which explains crowns, added some sections from Chris J Stuart's BRUCE Moria's documentation ### 5.1.5 / 1990-5-7 monsters.c: ogre mage is now evil files.c: added extern int error for MSDOS moria.ms: proof read it again misc1.c: reduce plus to dam for slay dragon arrows from +10 to +3, no longer need such high damages because ptodam is multiplied now when used with right weapon Makefile: test and fix 'make install' ibmpc/ms_ansi.c: change two escape characters to \033 mac/moria.r: change many control characters to \xxx, update version info strings to 5.1.5 constant.h: change the version number to 5.1.5 and distribute ### 5.2.0 / 1990-5-9 constant.h, mac/moria.r: change the version number to 5.2.0 save.c: fix support code for 5.1.0 savefiles, because of minor version number change, update compatibility code for 5.1 and 5.2 savefiles save.c: save/restore value of died_from string ### 5.2.0 / 1990-5-10 scrnmgr.r, scrntest.r: these files had control characters also, changed '...' to \311 resource.hqx: recompiled the mac resource files macscore.c, mac.c: changed control characters '...' to \311 all: distribute version 5.2.0 ### 5.2.0 / 1990-5-14 death.c, externs.h, io.c, ms_misc.c: Turbo C changes, void sleep() instead of unsigned sleep(), don't call reset_term() before exiting, ifdef out definition of sleep() in ms_misc.c, declare signal handler as void not int generate.c, types.h: change IBMPC_TURBO_C defines to the proper TURBOC define io.c: shell_out(), MSDOS code parameter to inkey() call deleted externs.h: count_msg_print() changed from (int) to (char *) io.c: get_check(), add code to use 'y' if LINT_ARGS defined, pause_exit() add code to use 'delay' for MSDOS machines ms_misc.c: clear_screen(0,0) changed to clear_screen() main.c: set stack size for TURBOC by declaring the _stksize variable misc2.c: player_saves(), split expression up because MPW C couldn't handle it save.c: include time.h for for the Mac, delete var 'i' in save_char (unused), get_char() add code for MAC to return FALSE if savefile doesn't exist mac.c: add call to initsavedefaults() in main() mac/ERRORS: document problem with mac Makefile ### 5.2.0 / 1990-5-15 externs.h: remove decl of py_bash(), which is in moria2.c and static ### 5.2.0 / 1990-5-17 dungeon.c: move search_off() call after check_view(), as panel_* variables must be valid before search_off() is called ### 5.2.0 / 1990-5-18 all: 7 files did not end with newlines, 5 hqx files, and origcmds.hlp, misc/README also did not end with a newline ### 5.2.1 / 1990-5-21 constant.h, version.hlp, moria.r: change version number to 5.2.1 ScrnMgr.doc: split all lines greater than 80 characters, to make the file more portable ### 5.2.1 / 1990-5-22 death.c: in mac sources, there was a 'true' instead of a 'TRUE' ms_misc.c: Turbo C sscanf() incorrectly reads a newline from a blank line, add check for newline to code that reads configuration file spells.c: door_creation(), called popt() before calling delete_object() which could then invalidate the value returned by popt, moved popt() call after the delete_object() call ### 5.2.1 / 1990-5-25 creature.c: fix the invincible/invisible monster bug, multiply_monster() was creating children on top of the parent, thus killing the parent for carnivorous monsters death.c, externs.h, generate.c, io.c, main.c, ms_misc.c, signals.c, types.h: change all TURBOC defines to __TURBOC__ util/printit: new version of the printit program by Carl Hommel, fixed up to be somewhat more portable and to use only moria defined constants ### 5.2.1 / 1990-5-27 player.c, tables.c, variable.c: Turbo C can not accept any file with more than 64K global variables, so variable.c split into three parts, also updated all makefiles to refer to all three files mac files: all three mac Makefiles modified to delete obsolete references to CInterface.o and CRuntime.o, scrnmgr Makefile modified to automatically combine the two ScrnMgr.c parts, README updated to document a problem with the Makefile not appending resources and text files sometimes ### 5.2.1 / 1990-5-31 all: released umoria 5.2.1 ibmpc/ms_misc.c: corrected warn() function to use va_list, the old function was not strictly correct ### 5.2.1 / 1990-6-4 misc2.c: prt_stat_block() must also call prt_study() misc2.c: random_object() could create objects outside of map boudaries, added an in_bounds() call to fix ### 5.2.1 / 1990-6-6 main.c: changed 'if (p = getenv(...))' to 'if ((p = getenv(...)) != NULL)' to avoid compiler warnings moria1.c: rest_on(), add check to make sure rest_num fits in short ibmpc/umoria.prj: new file, a project file for Turbo C 2.0 ### 5.2.1 / 1990-6-9 io.c: HP-UX does not have VEOL2, added #ifdef VEOL2 around its use dungeon.c: clear message line after change_character(), since as ESCAPE exit does not moria1.c: for 'x' and 'w' commands, clear heavy_weapon flag before calling check_strength, so that always get heavy message when wielding heavy weapon, and never get light message when wielding light weapon scrolls.c: identify scroll can move arbitrarily far after ident_spell(), replace simple 'moved one down' check with loop to search until found main.c: fixed starup code so that don't get 'savefile doesn't exist' message anymore when starting a new game all: apply fix to all uses of long constant with & | << operators to avoid a Mark Williams C bug on the Atari ST externs.h, files.c: removed MSDOS declaration of errno from files.c, and remove ifndef MSDOS from errno decl in externs.h misc1.c: changed name of variable 'clock' to 'clock_var' to avoid problem with a mac compiler (not sure which one) io.c: every io.c file, remove '\0' as alias for ESCAPE from get_com() and get_comdir() (MAC only function) moria1.c: light_dam() calls inven_damage(set_lightning_destroy, 3) which gives it a small chance of destroying objects in player's pack, this makes it just like the other *_dam() functions spells.c: teleport_to(), add call to in_bound() to make sure player will be teleported to somewhere inside the dungeon ### 5.2.2 / 1990-6-10 config.h: add note about using GCC on an Atari ST main.c, save.c, undef.c: eliminated the unused _new_log() function death.c, dungeon.c, externs.h, save.c, undef.c, variable.c: eliminated the mostly unused log_index variable, the few uses of it were replaced with references to character_saved externs.h, files.c, io.c, undef.c: init_scorefile() implemented, same as in umoria 4.87 main.c, undef.c: eliminate unused function init_files() constant.h, moria.r, version.hlp: change version number to 5.2.2 ### 5.2.2 / 1990-6-11 ibmpc/ms_misc.c: added space to GRAPHICS scanf, to try to eliminate problem with Turco C death.c, externs.h, files.c, save.c, variable.c: extensive changes to implement the scorefile, total_points(), highscores(), display_ scores(), set_fileptr(), rd_highscore(), and wr_highscore() defined scorefile holds 1000 entries max, has one sex/race/class entry per non-zero uid, has live characters also unix/Makefile: fix the treatment of score file, don't copy over old one, just use touch to create one if it doesn't exist undef.c: finally eliminated ### 5.2.2 / 1990-6-13 all: lint under 4.3 BSD and AIX 2.2.1 (SYS V) scores: readd scores file, since it makes micro distributions much easier death.c: modify savefile for single user machine, so that it tries to prevent a single character from appearing multiple times in the savefile, checks for identical names, and killed by = (saved) player.c: eliminated the unused dsp_race[] array death.c: added code to implement flock() for systems which don't have it, based on code written by CJS for BRUCE Moria ### 5.2.2 / 1990-6-17 all: fixes for VMS, define USG for VMS, merge in fixes from Todd Pierzina ### 5.2.2 / 1990-6-23 scrolls.c: for curse weapon, call py_bonuses(-1) before clear flags so that attributes will be turned off moria1.c: calc_bonuses(), handle AC just like everything else, previously did not add in AC bonus if armor was cursed creature.c, moria2.c: doors weren't being broken when bashed, change "randint(2) - 1" to "1 - randint(2)" ### 5.2.2 / 1990-7-14 all: merge in some changes indiciated by warnings/prototypes generated by MSC 5.x recall.c: compute monster kill exp in long, to avoid overflow of int all: added AMIGA support amiga/: new directory to hold support files for the amiga main.c: variable result was not initialized to false ### 5.2.2 / 1990-7-15 all: merge in more VMS patches death.c, dungeon.c: fixed display_scores() and 'V' command problems creature.c, dungeon.c, misc2.c, moria1.c: new feature '*' rest until reach max mana and hp, print error message for illegal rest count all: removed improper define of NULL, new define CNIL for (char *)0, used instead of NULL where appropriate, eliminate strange include orders that the previous NULL define required io.c: ifdefed out the include for termio.h, as this caused it to be included twice on some systems (curses.h includes it also) causing problems macrsrc.h: changed MAX_RESTART from 37 to 36 ### 5.2.2 / 1990-7-21 ms_misc.c: fixed reading of empty lines from config file for Turbo C ### 5.2.2 / 1990-7-22 io.c: fix Amiga code for flush() types.h: reduce size of cave_type structure for Amiga/Aztec C 5.0 death.c: fix display_scores(), so that it only shows entries belonging to current player when show_player is true ### 5.2.2 / 1990-8-29 misc2.c: deleted some redundant code in gain_spells() where it calculates which spells a player can learn spells.c: recharge(), could call randint() with 0 or negative numbers, check for these cases before calling randint() ### 5.2.2 / 1990-9-8 moria1.c: inven_command(), when drop/remove object and inven/equip are both empty, set inven weight to zero, just to be safe dungeon.c: jamdoor(), when jam a door, decrement inven_weight by weight of one spike moria1.c: inven_command(), make spikes wieldable objects, fight with them like missile weapons, i.e. they disappear after use desc.c: objdes(), add damage multiplier of bows to their name externs.h, io.c, misc1.c, save.c variable.c: new option sound_beep_flag, so that players can turn off bad character beep if desired desc.c, misc2.c: modify known1_p() to return TRUE for objects which never have a color, modify objdes() to work with the new convention, modify inven_carry() so that objects are added to the inventory sorted by subval if they are known1_p() ### 5.2.2 / 1990-9-9 misc1.c: add_food(), fix so that does not use f_ptr->slow to set f_ptr->food value, this makes it much less likely to overflow the food level misc2.c: inven_carry(), yesterday's insert in order fix does not work for items which can be unknown1p, only insert items ordered by subval if they are always known1p, i.e. object_offset() returns -1 externs.h, misc1.c, misc2.c, save.c variable.c: new option display rest/repeat counts, since these counts make resting at 1200 baud or less unbearably slow, people need to be able to turn them off store2.c: get_haggle(), let RET default to the last inc/dec amount when haggling misc1.c: compact_monsters(), return FALSE if could not delete any monsters instead of aborting, popm() return -1 if could not allocate monster (i.e. compact_monsters() failed), place_win_monster() abort if popm() fails which should never happen, place_monster() externs.h: place_monster() now int type not void, creature.c, misc1.c, spells.c: fix users of place_monster() to check result, and fail if place_monster() failed, really only necessary for the calls in creature.c, these fixes fix the monster list overflow bug dungeon.c, externs.h, misc1.c: change compact_monsters() from static to extern, call it from within main loop in dungeon.c if there are less than 10 spaces left in the monster list, compact_monsters() is much more likely to be successful when called here than when called from creatures() ### 5.2.2 / 1990-9-11 signals.c: delete extra definition of error_sig for ATARIST_MWC atari_st/README: update with GCC and Turbo C info files.c: fix typo, filname1 -> filename1 ### 5.2.2 / 1990-9-18 mac.c, ScrnMgr1.c: replace uses of KeyMapLM with GetKeys() call ### 5.2.2 / 1990-9-19 misc1.c: m_bonus(), could overflow on 16 bit machines when level >= 263 when calculating stand_dev death.c, externs.h, save.c: store the encryption byte for each savefile record immediately before it in the scorefile, makes the scorefile more robust in the face of a system crash, rd_highscore(), wr_highscore() no longer pass the encryption byte death.c: added version numbers to the savefile death.c, externs.h, save.c, variable.c: add max_score to savefile, which is the maximum score the character has reached so far, total_points() modified to use the larger of the computed score, and the value of max_score, this prevents a "(saved)" message on the score file for a dead character, which died having fewer points than when it was last saved ### 5.2.2 / 1990-9-26 death.c, externs.h, main.c, save.c, types.h, variable.c: new global variable birth_date, set to time when character created, saved in save files and scorefile, when loading character check to see if the a character in the scorefile has the same race/sex/class/uid/birth_date and its died_from is not "(saved)", in which case this character will not be scored misc2.c: print "Duplicate" on screen for such duplicate characters all: update all documentation, README, and other misc descriptive files all: add fixes for Atari ST/Turbo C 2.0 all: lint on Sun3/uVAX/MIPS/RT_PC recall.c: when printing out depth of monster, print Balrog as 50 not 100 ### 5.2.2 / 1990-10-3 all: fix all problems reported by maniscan/cshar, except for control-L characters on a line by themselves ### 5.2.2 / 1990-10-7 creature.c: mon_move(), cast m_ptr->fy-1 to int, so that negative values will correctly compare to it, in the wall building code spoilers: new file, lists spell damages misc2.c: prt_experience(), always check whether exp is greater than max_exp, previously did not when player was at level 40 creature.c: mon_move(), set the recall CM_ATTACK_ONLY bit when a monster should have moved but did not, instead of the old method of setting it when a non-moving monster attacked store2.c: get_haggle(), fix bugs in the automatic increment code, also, when reach final offer, set the automatic increment to the required diff, so that player need only hit return to bid the final amount moria2.c: tunnel(), before checking for tunneling into a invisible monster, check to make sure player is tunneling somewhere which has an effect, otherwise, player can use this to 'detect' invisible monsters in open spaces by trying to tunnel there moria1.c: calc_bonuses(), base AC should always be visible, as long as the item is not cursed ### 5.2.2 / 1990-10-8 spells.c: td_destroy2(), should not destroy chests, not disarms and unlocks them instead misc2.c: put_misc3(), clear_from(14) not 13, was clearing the gold value files.c: file_character(), add the "Max Exp" and "Exp to Adv" fields to the info written to a file, to match the on screen display files.c: file_character(), set fd to -1, not NULL, code was closing stdin (== 0 == NULL) when could not open file death.c: exit_game(), clear character_saved before calling highscores(), prevents inkey() from recursively calling exit_game() when eof on stdin has been detected all: released 5.2.2 sources ### 5.2.2 / 1990-10-23 death.c: highscores(), must do fseek() after reaching EOF before changing read/write mode, previously only did this for VMS, now do this for all systems ### 5.2.2 / 1990-10-26 config.h, death.c, io.c: fix VMS errors, declare 'string' in death.c duplicate_character(), fix typos in other files dungeon.c: for VMS, after calling kbhit(), must consume one character vms/Makefile: fixed, mostly change constants.h to constant.h ### 5.2.2 / 1990-10-30 death.c, externs.h, files.c, save.c: define STDIO_LOADED after including stdio.h, then use it to control declarations in externs.h constant.h, death.c: define SCOREFILE_SIZE as 1000, and use it in death.c moria2.c: cast_spell(), when don't have enough mana, use 'gods may think you presumptuous' message instead of 'summon your limited strength' ### 5.2.2 / 1990-11-4 ibmpc/moria.cnf: add ^M, since otherwise MSDOS moria can't read file properly io.c: include stdio.h explicitly, and define STDIO_LOADED, so savefile can be closed when shelling out store2.c: get_haggle(), clear increment when invalid increment entered store2.c, variables.c: last_store_inc contradictory declarations, changed both to int16 externs, misc2.c, sets.c, spells.c: set_*_destroy changed to take inven_type pointer instead of tval, modified so that (Rx) items won't be destroyed by attacks of type x death.c: for single user systems, use birth_date instead of name when looking for matching characters in savefile death.c: MSDOS setmode() needs file descriptor, not FILE pointer files/*: removed all TABS, since some systems do not handle them properly, specifically, Turbo C on the Atari ST and IBM-PC misc1.c: rings of searching cost was 100*p1, amulet of searching cost 20*p1, change both to 50*p1 mac/macio.c, ibmpc/tcio.c: modify beep() so that it uses the new option flag desc.c: changed the (*2) string for bows to (x2) spells.c: slow monster and confuse monster modified to wake up monster if the attacks fails spells.c: wall_to_mud() and dispel_creature(), print messages even if monster not visible since the effects are visible/audible moria2.c: for fire/acid/gas traps, print the trap message first, and then damage an item treasure.c: change name of lose memories potion to lose experience, so it won't be confused with monster memories spells.c: trap_creation(), don't create a trap under the player, to prevent strange occurances such as ending up under a falling rock moria2.c: tunnel(), when wielding heavy weapon, make tunneling more difficult ### 5.2.2 / 1990-11-6 vms/getch.c: New code for kb_hit(), which was tested and works under VMS 5.2 the old code did not work for VMS 5.2 ### 5.2.2 / 1990-11-9 misc1.c: compact_monsters(), distance test was backwards!, now delete monsters farther away first, allow a final pass with distance of 0, never delete the Balrog during compaction ### 5.2.2 / 1990-11-21 doc/history: rewrote history file to bring it up-to-date misc2.c: gain_spells(), don't need to be able to read spell books if learning priestly spells, don't fail if blind or no light death.c: fix #ifdef typo around include ### 5.2.2 / 1990-12-1 store2.c: when good bargainer, change string to "final offer", and allow return to accept final offer player.c: change rogue start inv from Soft Armor to Cloak, so that it is the same as the other non-Warrior classes dungeon.c: decrement command count after do_command() instead of before, so that all counted commands will work right, this fixes 3^P ### 5.2.2 / 1990-12-5 death.c: display_scores(), fix VMS bug, truncate uid to a short before comparing against the uid in the score file record, also move the uid computation outside the loop to speedup the procedure io.c: put_buffer (), for Atari ST, was printing the untruncated out_str instead of tmp_str ### 5.2.2 / 1990-12-14 atari_st/curscomp/curses.[ch], death.c, files.c, io.c, signals.c: add Atari ST TC fixes for the new (in 5.2.2) code ### 5.2.2 / 1991-1-4 spells.c: light_area(), always light area immediately next to player even if in a room, could be standing on the edge of a room monsters.c: Grave Wight, no longer has confusion spell, no other wight/wraith has it misc2.c: get_spell(), when enter invalid character, print "You don't know that prayer." instead of "spell" for priests/etc. creature.c: make_attack(), creatures which are repelled should not be confused because they did not hit the player death.c: exit_game(), delete #ifndef TURBOC around the restore_term() call io.c: restore_term(), delete the call to clear() in the MSDOS code, it was trying to use curses after curses had been exited ### 5.2.2 / 1991-1-22 files.c: call pause_line(23) after printing hours file constant.h, config.h: constant.h should always be included before config.h, because some systems redefine constants in config.h rnd.c: include config.h after constant.h main.c, misc2.c, save.c, signals.c: include constants.h before config.h misc2.c, vms/getch.c: new function user_name() for VMS, fix get_name() in misc2.c to call it ### 5.2.2 / 1991-1-30 moria2.c: hit_trap(), add msg_print(CNIL) for the trap door case ### 5.2.2 / 1991-2-4 io.c: for ATARIST_MWC, use 240 instead of '#' for walls save.c: for ATARIST_MWC, convert 240 to '#' when saving, and '#' to 240 when loading, to avoid conversion problems ### 5.2.2 / 1991-2-8 create.c: monval(), cast i to int, otherwise some compilers do the arithmetic with unsigned characters ### 5.2.2 / 1991-2-19 makefile: add new macro CURSES, define it for BSD/SYS V/and Xenix config.h: add config info for XENIX, define SYS_V and unix, only undefine register for MSC versions less than 600 (6.00?) creature.c: mon_move, comment out register decl for r_ptr for XENIX systems to avoid a compiler bug misc2.c: place_gold, comment out register decl for t_ptr for XENIX systems to avoid a compiler bug unix.c: ifdef out include of termio.h, for XENIX add include of sys/types.h and define bzero as memset, test for unix or M_XENIX at the top Makefile: add optional commands to install target which chown/chgrp everythin to bin, and put pointer to it at the top ### 5.2.2 / 1991-2-25 util/score: Two new utilities, prscore to print scorefiles, and delscore to delete one entry from a scorefile. config.h: add MORIA_* macros for the Atari ST with GCC death.c, externs.h, io.c, main.c, signals.c, variable.c: Apply Atari ST/GCC patches from Scott Kolodzieski. ### 5.2.2 / 1991-3-1 death.c: Amiga must open/close scorefile like MSDOS&VMS io.c: init_curses(),restore_term(), fix bugs in AMIGA code, add code to release resources amiga/*: updated versions of amiga source files, from cg37717@lion.eng.clemson.edu, Corey Gehman atari_st/curscomp: complete rewrite of the curses code by Hildo Biersma store2.c: get_haggle(), do not accept an increment value of zero, turn off increment flag instead of accepting it ### 5.2.2 / 1991-3-2 store2.c: store_purchase(), store_prt_gold call was inside `if' now after, did not update gold if store had 13 items and you bought the 13th ### 5.2.2 / 1991-3-11 moria1.c: sub3_move_light(), don't print over old location if find_flag is true, unless find_prself is also true, this speeds up movement in find mode by eliminating unnecessary drawing of characters moria2.c: hit_trap(), call move_light() for the teleport trap case, to light up the trap misc1.c, save.c, treasure.c: change ATARIST_MWC ifdefs for using graphic character to ATARI_ST which is true for both MWC and TC io.c: remove all ATARIST_MWC diffs which were needed for the old non-standard curses, change the rest to be ATARI_ST, since both MWC and TC need them ### 5.2.2 / 1991-3-14 source/*: add Mac THINK C support mac/dumpres/*: add Mac THINK C support mac/scrnmgr/*: add Mac Think C support moria1.c: find_init(), when !light_flag and !find_prself, must erase the player's '@', because sub3_move_light() won't, see 3/11 change above ### 5.2.2 / 1991-3-15 mac/*: add Mac THINK C support *: put file name and 1991 copyrights in all source files ### 5.2.2 / 1991-3-23 save.c: prevent resurrection of a total winner character constants.h, creature.c, monsters.c, recall.c: add new flag CM_ONLY_MAGIC, set this flag in creature.c, check the flag in recall.c, allows recall to print movement speed for Quylthulgs creature.c: when a wand is drained of charges, inscribe it as {empty} if it is not known2 ### 5.2.2 / 1991-3-24 files.c, ibmpc/ms_misc.c: ifdefed out msdos_intro(), since that routine is obsolete now doc/moria.6: add -S option to list at the top ibmpc/CONFIG.DOC: update for Umoria 5.x, remove kneller's address, and put in my address ### 5.3 / 1991-3-25 config.h, constant.h, */*.c: move VMS definition for ESCAPE from config.h to constant.h, now all files include config.h before constant.h *: linted all sources files, changed version numbers to 5.3 ### 5.3.1 / 1991-3-30 vms/*, ibmpc/ms_misc.c, config.h, death.c, dungeon.c, externs.h, files.c, io.c, save.c: merge in changes from Ralph Waters, which are needed to compile the sources under VMS and IBM-PC/Turbo C. moria2.c, store2.c, *.c: get_item(), show_inven() take new parameter mask, if mask is non-zero, they only list items indicated by mask array, store_sell() calculates a mask based on what store will buy store2.c: purchase_haggle(), sell_haggle(), if the auto increment is larger than the difference between the store's offer and the player's offer, then set the auto increment to the exact difference dungeon.c, externs.h, moria1.c, moria2.c, variable.c: eliminate search_flag, it was redundant, replace all uses with (py.flags.status & PY_SEARCH) tables.c: remove good armor items from armory, to force players to search for them in the dungeons, hard leather boots, iron helm, partial plate, full plate misc1.c: alloc_monster(), always create dragons sleeping here, to give the player a sporting chance moria1.c: inven_command(), when pack not empty and show_weights flag true, display capacity along with weigth carried on first line of inventory spells.c: build_wall(), permanently light walls created within range of player's lamp spells.c: earthquake(), fix it to act just like build_wall when a monster is trapped in a wall creature.c, externs.h: movement_rate(), now static *: release 5.3.1 sources ### 5.3.1 / 1991-4-27 ms_misc.c, externs.h: change declarations of warn() to match definition, change declaration and definition of error() to match warn(), externs.h: fix declarations for sleep(), find_init(), show_inven(), get_item() death.c: display_scores(), don't set player_uid for non UNIX/VMS system duplicate_character(), ifdef out code which is unreachable for non UNIX/VMS system, make all returns have a value sets.c: set_null(), add a #pragma argused for TURBO C ms_misc.c: fix three lines that had an assignment inside an if externs.h: add prototypes/declarations for VMS files getch.c and uexit.c moria1.c: see_wall(), change ATARIST_MWC ifdef to ATARI_ST atari_st/curscomp/curses.c: winsch(), first loop ran in wrong direction externs.h: add declarations for atari st functions atari_st/moria.prj: new file, TC project file for Umoria death.c: highscores (), change fseed to fseek, typing error creature.c, death.c, desc.c, dungeon.c, files.c, io.c, moria1.c, moria2.c, store2.c, wizard.c, atarist.c: include stdlib.h if ATARIST_TC to get prototypes for standard library functions generate.c: for ATARIST_TC, include atarist/curscomp/curses.h: change mvadd* macros from compound statements to conditional expressions, so that all returns values are error checked io.c: for ATARIST_TC, include ext.h to properly define (?) sleep config.h: for ATARIST_TC, define index strchr save.c: sv_write()/get_char(), define t_ptr for both MSDOS and ATARI_ST; get_char(), change ATARIST_MWC ifdef around chmod call to ATARI_ST include time.h for ATARIST_TC unix/Makefile: change ONWER to OWNER creature.c: creatures(), give moves to monsters trapped in rock, so that they will die/dig out immediately, mon_move() if a monster in rock is already dead, don't kill it again *: update address info in all files io.c: change __GNU_C_ to __GNUC__ config.h: the test for undefining 'register' was wrong, it was undefing it for all non-MSC compilers moria2.c: tunnel(), heavy weapon code wrong, eliminate >>2 of penalty, add penalty instead of subtracting it help.c: ident_char(), add period after Giant Frog. monsters.c: novice priest, change sleep from 10 to 5 to match other novice 'p' moria1.c, store2.c, *.c: get_item() new parameter 'message', when invalid letter hit, print this message if non-zero instead of beeping, store_sell() pass message "I do not buy such items.", fix all other callers to pass CNIL ### 5.3.1 / 1991-4-28 misc2.c, files.c: put_misc2(), file_character(), when player at max level, don't print a number for Exp to Adv, instead print ****** io.c: msg_print(), put multiple messages on the same line if they are short enough ### 5.3.1 / 1991-5-22 externs.h: ifdef out declaration of sprintf for NeXT io.c (init_curses): correct atarist/GNUC code for signal call, ifdef was wrong ### 5.3.1 / 1991-7-6 spells.c (unlight_area): Unlight all floor spaces with `lr' set, instead of just the room floors spaces. This darkens the doorways. moria1.c (light_room): Add code to set the fm flag, necessary so that the above fix does not unlight doors that it shouldn't. io.c (msg_print): Don't combine NULL messages with other messages. save.c (get_char): Use msg_print when printing the `departed spirit' message. ### 5.3.1 / 1991-7-26 store2.c (purchase_haggle, sell_haggle): If the automatic increment plus the last offer passes the shop keepers current ask, then clear the incr. ### 5.3.1 / 1991-10-5 *: Add changes needed to prevent warnings from the IBM-PC TURBO C compiler. misc[1234].c, moria[1234].c: misc[12].c and moria[12].c were each split into two files, because they were too large for TURBO C's integrated environment *: adjust all makefiles, externs.h, etc to account for new moria/misc files TCCONFIG.TCU, TCPICK.TCU: new files, uuencoded copies of Turbo C setup files config.h, ms_misc.c: New define USING_TCIO, used to prevent including curses.h in ms_misc.c. Defaults to defined if using TURBOC on an IBM-PC. io.c: delete special purpose AMIGA code, it now uses curses amiga/amiga.h: Deleted. amiga/amiga.c: Delete all curses stubs. ### 5.3.1 / 1991-10-6 macrsrc.h: change type of resType, ResID to long macrsrc.c: eliminated search_flag from macrsrc.c (see 3/30 changes) config.h: put back switches RSRC, RSRC_PART1 and RSRC_PART2 ScrnMgr.ro: changed def of MBAR #228 (fixes crash on Mac Plus) and INFO #1 (Make default window be full screen) ScrnMgr.c: check for reconfig flag enabled for THINK_C, add recognition of MacClassic (and LC?) keyboard, now assumes unknown keyboard type has control key, other misc cleanups moria.ro: changes version string macconf.c: config.h included for consistency mac.c: added support for 8-column tabs mac/Install.doc: new file, installation instructions for THINK C macconf.c, machelp.c, macscore.c scrnmgr.c: covered up error in THINK C includes files OK/Cancel for ok/cancel death.c, save.c: delete setmode calls for IBM-PC, instead open files in binary mode ### 5.4 / 1991-10-12 *: Changed version number to 5.4. save.c: change code to accept savefiles with version numbers greater than the version number of the game, savefile format frozen as of 5.2.2 externs.h: ifdef out the troublesome sprintf declaration config.h: force `unix' to be defined for unix systems, since some stupid systems (e.g. AIX) don't already define it ### 5.4 / 1991-10-15 externs.h, moria4.c, ms_misc.c: correct typos discovered under MSDOS ### 5.4 / 1991-10-19 spells.doc, exp.doc: New documentation files. ### 5.4 / 1991-10-26 vms/uexit.c, externs.h, io.c, signals.c: Define uexit as void, and ifdef out exit declarations when VMS. vms/moria.opt: add misc[34].obj and moria[34].obj ibmpc/ms_misc.c: correct typo in error() pr_items.c, pr_monst.c: main now returns 0 CONFIG.DOC, TERMCAP, ms_ansi.c: use le/do instead of obsolete bc/xd dragon.inf: moved from util/weapons to doc, and updated it to be accurate spoilers: Update from USENET FAQ posting. ### 5.4 / 1991-11-17 io.c: ifdef out code checking for 8 char TABS, because it assumes that the screen is exactly 80 characters wide moria[12].[ms/txt]: Correct a few typos. *: Fix all file permissions. ### 5.4 / 1992-7-16 Maintenance taken over by David Grabiner moria4.c: bash(), use random direction if player is confused spells.c: fire_ball(), fire_bolt(), don't update recall if monster not lit; this can happen if bolt hits an invisible monster spells.c: speed_monsters(), sleep_monsters2(), dispel_creature(), turn_undead(), only affect creatures within MAX_SIGHT spells.c: mass_poly(), area of effect should be <= MAX_SIGHT, was < spells.c: destroy_area(), remove light from player's spot spells.c: enchant(), add new variable limit, chance of failure is now (plusses/limit), with very slight chance of success over limit scrolls.c: when enchanting melee weapons to damage, set limit to weapon's maximum damage, otherwise use 10 to give behavior similar to old method misc2.c: magic_treasure(), make standard deviation of damage bonus on a melee weapon proportional to weapon's maximum damage; these changes mean that daggers can no longer become powerful weapons treasure.c: the Zweihander has now become a great weapon, value increased from 1000 to 1500 externs.h: fix declaration for enchant() staffs.c, wands.c: give everyone a slight chance to use difficult wands and staffs, otherwise a warrior will never be able to use many items ### 5.4 / 1992-7-23 death.c: print_tomb(), insert msg_print(CNIL) so that "You are using:" and "You are carrying:" don't get combined as one message; this made it impossible to see the equipment list store2.c: haggle_insults(), insert msg_print(CNIL) so that insult is always recognizable store2.c: purchase_haggle() and sell_haggle(), new variable didnt_haggle, don't call updatebargain if no haggle store1.c: noneedtobargain(), changed to sliding scale, (good-3*bad) must be more than 5 + (price/50) to skip haggling, so that haggling for cheap items is over quickly, but can still eventually skip haggle for all items store1.c: updatebargain(), now update for all items worth >9 gold, instead of just 10-999, since it is now possible to skip haggling for more valuable items as well ### 5.4 / 1992-7-25 moria4.c: bash(), unsuccessful bash takes a turn; otherwise, you can attempt to bash in different directions while confused or to locate invisible monsters; eliminate variable no_bash ### 5.4 / 1992-7-27 check all above changes moria4.c: bash(), get "You bash at empty space" method when bashing a wall, corrected to "nothing interesting happens"; this also prevents bashing from locating a secret door ### 5.4.1 / 1992-8-9 merge in all changes from 5.4.0 to 5.4.1 creature.c: update attack information only if monster is visible; update death information even if monster is not visible, since information will be on tombstone *: change version number to 5.5.0 ### 5.4.1 / 1992-8-12 spells.c: enchant(), guard against randint(0) if called with limit of 0 (it shouldn't be). moria4.c: throw_object(), py_bash(), don't do negative damage shortnam.sed, spells.c: fire_ball(), fix spelling of "envelops" doc/faq: remove old spoilers file, and put current FAQ here instead *: put my name (DJG) in credits as contact *: change copyright date in all source files to 1992 ### 5.5.0 / 1992-8-13 release umoria 5.5.0 ### 5.5.0 / 1992-10-26 doc/moria[12].[ms,txt]: correct some typos, and make changes for 5.5.0 ### 5.5.0 / 1992-10-31 misc4.c: scribe_object() allowed inscriptions longer than 12 characters if 13-24 characters availble for inscription, could overwrite other data ### 5.5.0 / 1994-6-6 scrolls.c: aggravate monster should give "humming noise" before "stirring" scrolls.c: always identify scrolls which print a message unix/unix.c: change from obsolete getpw() to getpwuid() to get UID death.c: #include seems to be needed on XENIX and SYSV death.c: fix #ifdef (...) || defined(...) save.c: set fd=-1 after closing file to prevent double close dungeon.c: move hero/superhero to first status check so that player's HP won't go below 0 in mid-turn (killing him) and then become positive doc/moria[12].ms: fixes so that file works with groff store1.c: sell_price(), check for consistent sale prices compared pointers rather than values create.c: get_all_stats(), set player level to 1 before calling set_use_stat with constitution (which used level to check hit points) misc3.c: misspelled variable "holderr" in MWC-specific code misc3.c: prt_experience(), put check against max level in while loop so that level gain is never tested if player is max level misc3.c: gain_level(), corrected comment for loss of extra experience when player gains multiple levels moria3.c: monster_death(), don't make player a winner if already dead store2.c: get_haggle(), %d should be %ld misc3.c: todis_adj(), case of dexterity 3 was omitted spells.c: wall_to_mud(), may find objects in rubble as with tunneling ### 5.5.0 / 1994-6-7 io.c, signals.c: included changes from Andrew Chernov for 386BSD support io.c, config.h: included changes from Berthold Gunreben for HP-UX support config.h, death.c, files.c: added patches for HP Apollo, which doesn't allow multiple processes to use the same file config.h: defined MORIA_LIB to contain pathname for moria/files, to simplify configuration moria1.c: inven_command(), get_item(), added code from Harry Johnston which allows use of 0-9 to specify an item with the corresponding inscription doc/moria[12].ms: documented above change files/version.hlp: my name appeared both as author and "other contributor" scrolls.c: set AC bonus on cursed weapon, hit/dam bonuses on cursed armor to zero (in case HA/DF/gauntlets of slaying had bonus) creature.c: don't print message when invisible monsters recover from bash creature.c, moria3.c, spells.c: reworked monster confusion, monster's confused value now gives duration, turn_undead has guaranteed duration equal to player's level, other confusion random creature.c: undead which have been turned will flee rather than moving randomly, attack only if cornered recall.c: print "It is resistant to" if monster has not cast any spells but breath flag is known (because monster resisted an attack) monsters.c: allow monsters to resist attacks if they have no breath weapon but use the attack type (so fire giants resist fire) sets.c: new function set_large(), identifies objects too large to fit in a chest or be carried by small creatures misc3.c: get_obj_num(), new parameter must_be_small to generate only small objects when appropriate; place_object() passes it files.c: random object sample passes must_be_small constant.h, treasure.c, monsters.c, moria3.c, recall.c: new constant CM_SMALL_OBJ for chests, and for monsters carrying only small objects, check it in monster_death() by setting a bit in treasure type, allow it to be recalled moria3.c: summon_object(), object must be small if bit flagged above many: change all other calls to place_object to set must_be_small to FALSE externs.h: fix declaration of get_obj_num(), place_object(), add set_large() store1.c: noneedtobargain(), change scale again, (good-3*bad-5) must be positive and its square must be at least price/50, this allows high-level characters to become good bargainers for expensive items ### 5.5.0 / 1994-6-8 lint all above changes, fix assorted typos recall.c: recalled spell frequency for non-spellcasters in wizard mode monsters.c: checked small objects/resistances for consistency, fixed error creature.c: creatures given resistance by setting of breath bits tried to cast spell, calling randint(0) moria3.c: error in testing type caused all monsters which should drop both objects and gold to drop only gold creature.c: turned undead must call get_moves so they know which way to flee ### 5.5.0 / 1994-6-9 moria1.c: inven_command(), get_item(), print 0-9 in prompt message when appropriate moria[12].ms: clarified that digit inscriptions work only on items in pack prayer.c: strengthened Holy Word player.c: reduced failure chance for Holy Word check all changes, fixed more typos ### 5.5.0 / 1994-6-10 moria1.c: inven_command(), get_item(), 0-9 was printed in wrong place ### 5.5.0 / 1994-6-22 spells.c: td_destroy(), td_destroy2(), don't disarm/unlock chests that are already empty treasure.c: up staircase had extra space after name doc/moria[12].ms: proofread, fix many typos ### 5.5.1 / 1994-6-25 monsters.c: allow thieves to pick up objects on the floor main.c,config.h,amiga/amiga.c,amiga/timer.c: included changes from Ronald Cook for Lattice C support on Amiga death.c,io.c,signals.c,unix.c,config.h: included changes from Brian Johnson for Linux support changed version number to 5.5.1 fix more lint errors util/mergemem: code from Eric Bazin to merge monster memories *: changed all copyright dates to 1994 released version 5.5.1 ### 5.5.1 / 1994-7-5 death.c: || !defined(APOLLO) should be && ### 5.5.1 / 1994-7-11 store2.c: get_haggle, changed %ld back to %d since variable is 16 bits ### 5.5.1 / 1994-7-20 treasure.c: fixed many inconsistencies, mostly prices and names misc2.c: magic_treasure(), fixed values of SU and SA, which weren't changed when see invisible was moved from SA to SU; also changed magical bonuses for these weapons store2.c: increase_insults(), don't clear out haggling record if player is thrown out (it might be worse than zero), just penalize as for bad bargain ### 5.5.2 / 1994-7-21 treasure.c: fixed a few more inconsistencies with items files.c, misc32.c: file_character(), put_misc2(), don't print "Exp to Adv" if over max_level (i.e., winner) files.c: file_character(), leave enough space for printing 8-digit experience points misc3.c: put_misc2(), make display consistent with above change misc3.c: new function prt_8lnum(), print a long variable in 8 digits of space, needed by revised put_misc2() above death.c: need flock hack for HPUX io.c: #include for HPUX was inside #if 0 ibmpc/*: fix several typos in PC-specific files, also one in config.h changed version to 5.5.2 released version 5.5.2 ### 5.5.2-1 / 2000-05-28 unstable; urgency=low * Initial Release. * Added DEBIAN_LINUX #define to handle code customizations. * Needed to include termios.h in io.c * All instances of signal() changed to sysv_signal() since libc6 defaults to BSD semantics. * Instead of redefining getuid, just ifdeffed it out of unix.c * Changed LOCK_EX and LOCK_SH definitions in death.c (to avoid redefinition warnings). * Library files are in /usr/lib/games/moria except for the scores file which is in /var/lib/games/moria and the hours file which has been renamed to /etc/moria-hours * Makefile changed to make the binary setgid instead of setuid, as required by Debian Policy 5.10. None of the code itself needed to be touched, since it already relinquished uid and gid in the original code. * Saved game file is named ".moria-save" not "moria-save" -- Rene Weber Sun, 28 May 2000 16:35:38 -0400 ### 5.5.2-2 / 2000-10-14 unstable; urgency=low * Fixed typos in the control/README.debian file * Updated the author's e-mail address in the copyright file * Updated the author's web page listed in the README.debian * Installed a new version of the FAQ (reflecting the author's move) -- Rene Weber Sat, 14 Oct 2000 10:23:48 -0700 ### 5.5.2-3 / 2001-01-06 unstable; urgency=low * Added a Build-Depends line. * Changed the handling of the build directory for the convenience of porters (should have no impact on users). * Changed short description so that it does not include the name of the package. * Corrected path to /usr/games/moria binary in the menu. Closes: #81353 -- Rene Weber Sat, 6 Jan 2001 02:47:02 -0700 ### 5.5.2-4 / 2001-07-09 unstable; urgency=low * Fixed dependencies (was compiled against libncurses4, what was I doing that day?). * Corrected explanation of the 'V' command in moria2.txt (but not in the nroff source for that documentation). * Removed use of dh_suidregister. -- Rene Weber Mon, 9 Jul 2001 23:31:01 -0400 ### 5.5.2-5 / 2001-10-24 unstable; urgency=low * Changed location of score file from /var/lib/games/moria/scores to /var/games/moria/scores per FHS. (Closes: #115849) * Fixed handling of score file on upgrades (do not null it out!). * Applied fixes to a few buffer overflows. (Thanks to Lars Helgeland for noticing the overflows and supplying the patches.) (Closes: #115745) * Upgraded policy to version 3.5.6.0. -- Rene Weber Wed, 24 Oct 2001 22:44:05 -0400 ### 5.5.25 / 2008-02-22 David Grabiner * types.h: use stdint.h to get guaranteed 16-bit and 32-bit types rather than relying on the length of a long, which varies by compiler. * config.h: use ## operator so that MORIA_LIB works correctly. * INSTALL: Note that default install is for Debian, so UNIX install needs to copy the Makefile. ### 5.5.2 / 2008-05-26 David Grabiner * config.h: Gave up on ## because it is so ugly in ANSI C. ### 5.5.2 / 2008-06-01 David Grabiner * config.h, externs.h, *.c: Include stdio.h and stdlib.h unconditionally rather than guarding them and sometimes dealing with incorrect prototypes * io.c: Include unistd.h for execl() on Unix and linux systems. ### 5.5.2 / 2008-09-01 David Grabiner * config.h: Add new LICENSE file for GPL. * dungeon.c (do_command,original_commands), origcmds.hlp, roglgmds.hlp: New command ^V to view the GPL. * files/news: Added reference to GPL. ### 5.5.2 / 2008-10-13 David Grabiner * All: Update all copyright/license notices to GPL; Files in atari_st/curscomp and mac/scrnmgr still refer to old licenses as they are stand-alone programs. umoria-5.7.10+20181022/historical/errors.md0000644000175000017500000010673313363422757016772 0ustar arielariel# Umoria, Known Errors * explain non obvious spells/prayers, many of the priest spells are confusing, really should have a general section on spells explaining the 'G' command, how int/wis and level affects number of spells/prayers, how area effect spells work, centering before casting an area effect spell is very useful and perhaps other stuff about spells/magic * explain the special inventory_items/equipment mode? * explain how misc abilities depend on level, give table of classes versus ability showing the relative gain per level values * mention too-heavy weapons? * says nothing about levels and EXP should mention that level 40 is the highest level that can be reached ---- * black ooze should resist acid, but can't code this because it casts spells and would breathe acid if it were given acid resistance * identifying items by one-digit inscriptions should work for items in equipment list as well as for items in inventory_items * saving and restoring takes a turn, this can be fatal in combat ## JEW's old ERRORS list, excluding things fixed by DJG * stores probably should buy items which have been marked `{damned}` by being worn and then cleared via remove curse, after all they buy unident damned items * creating traps can destroy staircases * chests should create treasure appropriate to the level they were found on, or to the level of the chest, not the level that they are opened on * color MacMoria bugs: the colors dialog box says greed not green - the help dialog lists the wrong versions - my address is wrong * when wearing a ring of speed, light should not be used up as fast, the faster you are moving, the more turns it should last - by the same reasoning, stores lockout are broken too, in fact just about anything using time won't work `correctly' when character is sped up * calling `put_qio()` every turn during running significantly slows it down on slow hosts which are using curses, I believe it was added only for aesthetics, and it probably isn't strictly necessary, could at least surround it with an #ifndef FAST_RUNNING or similar * when thief steals an object, say what was stolen in message when item in backpack is destroyed, indicate what item was destroyed inventory_items does not take a turn * display_scores(), at end of the game, should display top 15 or so scores, and list the current player's score below that if not in the top 15 list, at least, the value of show_player should be false at the end * better, display_scores can put a message line at the bottom like this `( ESC : return to game space : toggle all scores/your scores)` and then the player can easily choose which scores to see * the 32767 limit in the recall code for number of monsters killed is too easy to reach, making it unsigned will extend the range to 65535, making it a long would increase program size by 2K, I don't think that this change is worth the extra memory used * monsters should probably not be summoned on top of a rune of protection * could make scorefile code more efficient if I read/write more than one entry at a time * to floorsym?, shouldn't the save.c file change '.' to floorsym and vice versa, just like it already does for wallsym? * remove the hours support for all micros, or else give people the option in the config.h file to remove it * when functions are not used, ifdef out the calls instead of providing dummy functions, to save some space * in stores.c, the comment "Your mother was a troll!" doesn't mean anything if you are a half-troll, change to Ogre? * the ms_misc.c file maps the 'Ins' key, which is the '0' key to 'i', this confuses the hell out of a lot of people, this IS documented, but few people read the documentation * add a turns counter to the higscores entry, and check this when restoring a character, don't score the character if this value is different this prevents someone from playing a character, goofing, saving it while alive, and then going back to the first savefile * haste self is a level 1 potion, and super heroism is level 3, both seem far too low of a level for the potions considering how useful they are, increasing the levels would make them more common deeper in the dungeon restore potions are level 40 are too deep, they are available in the stores, and just aren't that powerful of a potion DJG: haste self is needed at level 1 for early encounters with nasty monsters super heroism could be higher * the sorting code does not work with foodstuffs, problem is that when restore a 5.2.1 character which has unsorted food, the new code may put a food ration at the start instead of merging it with the other unsorted food, thus overflowing the inventory_items size, there are few 5.2.1 characters left anyways, so perhaps this isn't much of a problem anymore * wine/ale should be potions to be quaffed, not foodstuffs to be eaten * `^M = ^J` when digging, can't really solve well, because of bugs in some version of curses which requires this to be true * perhaps add an option to control the Drop One/Drop All query for multiple stacking items * give warriors a better chance of detecting that ego weapons are magik, but only for ego weapons, not all items * an inscription is lost when two objects are merged, either choose longest, or ask player, try to combine them, at least do something * if you curse a special magic item (HA, RF, etc), it retains its very high value, its cost should be reset to the value in `game_objects[index]` * if you curse a special cursed object (noise, irritation), it will lose its special cursed ability (two wrongs make a right?!?), this should not happen, instead of clearing ALL flag bits, should only clear the 'good' ones * if wield a torch, and a silver jelly immediately drains it to 1, the program will be confused for one turn thinking that you still have light even though you don't * scoring unfair to humans/warriors/etc, since they have a lower max exp than other player_races/classes, should adjust exp downward by inflationary factors when calculating score? DJG: unnecessary * should reduce stack usage in generate.c * in the stores, should have some way to determine how close one is to getting perfect haggling * replace all of those heavy weapon messages with an automatic inscription, how should this be maintainted, should it change only when wielded, or should it change whenever the players strength changes? * throwing a lot of objects into a pile can cause problems? * make the printing of stuff under monsters an option for those who find it bothersome * make monster movement more intelligent: - for slow monsters, in movement_rate(), instead of using mod, use `(randint(2 - speed) == 1)` to make monster movement a little more uncertain for fast monsters, give a little variance to speed, i.e. 66% of time monster moves at speed, 17% of time move at speed+1, 17% of time move at speed-1 - monsters which are about to die should flee if intelligent - monsters should breathe at player if only separated by a single column - better spell selection, don't cast teleport_to when standing next to player, etc. - have monsters with multiple attacks spread the attacks over each turn if moving slower than player, instead of doing nothing one turn, and then attacking multiple times on the next turn - remember the previous two moves for each intelligent monster, so they can tell which direction they are moving * in `get_com()`, `get_dir()`, etc, put `-escape-` on the msg line if escape key pressed, useful if message doesn't get deleted, but doesn't this always happen?, apparently not on the Atari ST MWC port * rubble can be generated underneath a monster, which should not happen * why call sigsetmask(0)? delete signals() and nosignals() as unnecessary? * killing a shell -1 results in the game disappearing without leaving a save file, what happened? * the save.c code that says "file exists, overwrite?", can this ever be executed?, if so, does it do the right thing?, and also, what happens on EOF? will it go into an infinite loop on EOF? * what exactly does the damned flag mean, should it be on for all except known2 items, or should it be on when an item is known to be cursed? * add ptodam to damage before calling critical_hits - modify C command to print to dam bonuses modified by weapon, should print both the unmodified bonus, and the modified bonus - should take about 10 slay dragon arrows to kill AMHD, check this after ptodam code is changed, may need to increase damages from +3? - also check other ego weapons for play balance, maybe reduce ego multipliers * when rogues list Beginners Majik spell book, spells are listed 'a'-'e', when cast from the spell book, spells are listed 'b'-'f', the spell casting list should also go from 'a'-'e', but this looks hard to fix * allow SPACE to exit from inventory_items commands * no monster, except Balrog, can resist mass polymorph, should I make it more like polymorph? * mass polymorph is much higher level than polymorph, and is useful for dire emergencies, either don't give monsters a saving throw, or make is something like level/(3*MAX_MONS_LEVEL/2) which will make it possible but not certain that the staff will work on AMHD's * should mac port have a separate io.c file? * 'headless' monsters should not glare when recovering from a bash * version.inf file should not contain version number/copyright, these should be printed from the variable values * in misc1.c, the pusht code requires search of entire cave structure, this needs to be written more efficiently, perhaps put x,y into Inventory for every object located somewhere in the cave? * eliminate all #if 0 in the code ## Changes that can wait... * only let warriors get *GREAT* hits? * in critical_hits(), change the threshholds to 400-600-800, which will make all melee weapons eligible for great hits, and only let warriors get great hits * since this makes Mage's much worse fighters, swap Sleep II for Lightning Ball (giving a much needed intermediate Ball spell), and Recharge I for Object Dectection (to compensate for Priest's sense surroundings) DJG: object detection isn't very useful * increase the classes of weapons from 3 to 5, i.e. missile, blunt, assassin, melee (two handed weapons, etc., only these can do *GREAT* hits), and normal. Then modify fighting abilities as follows: ``` mis blu ass mel nor a) b) c) d) e) -- := suck Warrior + + + ++ + - := iffy Rogue + 0 ++ - 0 0 := unchanged Mage 0 0 0 -- 0 + := improved Ranger ++ 0 0 0 0 ++ := hyperb Priest 0 + - -- 0 Paladin 0 ++ 0 0 0 ``` the monster memory always prints monster attack types and damage types together, this can lead to strange situations, for example, if a grey mushroom patch releases spores, but does not confuse the player, nothing appears in the monster memory because the player does not know they were confusion spores, but he/she does know that that spores are released * show how many spells player can learn via the 'G' command * add max mana to display in addition to current mana * add inscriptions that apply to a classes of objects, not just a single instance * shouldn't be able to genocide invalid letters; escape out of genocide * have alternative messages to 'dies in a fit of agony', that gets a bit repetitive, and boring * a command to allow writing the monster memory to a file * get sources for nethack/omega, look for any really good ideas, also check to see how the implement Mac compatibility, portability, protection, and other general concepts * treasure.c has an arrow and a bolt out of order (arrow in the rings, and bolt in the amulets) * secret doors are always granite, would be nice to have secrets doors hidden in other types of rock, would make them less obvious when hightlight seams is on, also current scheme gives an advantage while searching since walls displayed as % obviously are not secret doors * MAX_SIGHT is 20, but rooms can be larger than twenty across, that gives strange situation where moving one step away from a monster in a large room can cause it to disappear, either increase MAX_SIGHT to be size of largest possible room size, or else use different MAX_SIGHT values in rooms/corridors/ town level? * instead of printing 'it can hit to attack and hit to attack' print 'it can hit to attack twice' in monster descriptions? * change haggling so that initial price depends on how many good bargains you have had * add key to accept price, instead of forcing player to type in price * when reach perfect haggling, should just give object to player without asking for any prompt, bad idea if someone mistypes a letter * add an option so that player can play with or without haggling, without haggling, give prices that are always, say, 30% above the minimum * add stores commands to buy/sell without haggling, with price say, 20%, less/greater than asking price * improve map command (see nethack), define an array of special characters, one for each type of composite wall, say, and then let end users specify the necessary graphic characters, allow for multiple character graphic sequences * combat in a room should have a chance of waking up sleeping monsters, except that it would not be fair, since monsters can send breath weapons through other monsters, but the player can only attack the nearest monsters * (high level?) monsters should slowly regenerate hit points, esp since the doc states that the Balrog does * note, if leave level and come back, the Balrog is regenerated (sic) with full hit points * every 128 turns, let monsters regenerate 1/64 of their total hit points? * have several different types of Balrog, i.e. a fire breathing one, a cold breathing one, a lightning breathing one, etc. * should be a chance of weapons breaking when try to dig with them? NO, it is too easy to dig with weapon by accident, shouldn't dull them unless there is some way to sharpen them which there currently isn't * can get small groups of rooms which are connected to each other, but not to the rest of the rooms on the level, perhaps check for cycles in tunnel/room graph before trying to build tunnels? * can not use counted commands while bashing a door, because of the paralyzation that sometimes occurs always stops a counted command * allow character building, by allocation of a limited number of stat points, instead of random rerolling?? * add another aux inventory_items slot, so that there is one for picks, and one for bows * decrease mithril values, increase gem values, more expensive items in the shops, should be greater difference between prices of low level objects and high level objects, more gold/silver/copper on lower levels, selling high enchanted objects should not be so profitable (good idea here, favorite suggestion for fixing the money problem) * in object_offset(), give 70/71 and 75/76 different codes, shift by 5 (32) everywhere instead of 6 (64), reduce size of identified_objects_size[] from 7*64 to 9*32, objects 71 must all have 2 added to subval so that they start at 96, objects 76 must all have 1 added to subval * all variables (including bss) should be initialized before they are used, see the mac sources * add a macro capability * most case statements should use table or if/else instead, table are much faster, both give much smaller code * if have one unidentified object, and sell it to a stores, and it gets identified, and you happen to have some similar stores items, you will get a 'combining' message, even though no combining is really taking place, perhaps adding an extra parameter to identify which indicates whether the item will remain in inventory_items after ident, if it does not remain, then should not give combining message if there is only one of them * many of the constants in constant.h should really be enums instead, this would make the game more maintainable * some fields of Treasure are const and could be deleted * missile weapons should be made more useful, make them stackable after identified if they are identical, increase the benefit of using proper bow/arrow combo, increase +todam, +tohit more than now, perhaps give an advantage here to fighters/paladins/rangers/rogues somehow, don't decrease ptohit so much in inven_throw() (currently reduced by distance) * should recall info be set in breath() for other monsters? * finish monster compiler, write defs for all monsters * should be able to increase digging plusses for weapons?? * should be able to increase ring plusses?? this would seriously unbalance the game if allowed * in the stores bargain code, separate the sell good bargains from the purchase good bargains? this is important for store_owners with very high inflate values as it is easy to reach sell final value, but very difficult to reach purchase final value, change from 20 to 10 if do this * change move to take a char-number i.e. '0' instead of a number, i.e. 0 * put all prt_ functions into a separate file, put all movement functions in a separate file * define constants for all possible tchars, number-directions, attack-types, last_line = 23, scroll flags, potion flags, etc * would be nice if 10-23 were all wielded objects, i.e. move light 15 outside this range * use scroll bars on the Mac version for showing the rest of the level, L/W, might be confusing since this won't scroll the whole window, only the dungeon view * have multiple windows, one for inventory_items, one resizable/scrollable for dungeon view, etc... * should eliminate need for 70/71 scrolls and 75/76 potions * replace hex flag fields in treasure with constants * should not get message "something rolls under feet" when step on a loose rock /* Hid Obj*/ trap?, also should not get this message when cast create food some people prefer to have these messages though ## ideas from Bron Campbell Nelson * potion of boldness should have duration, i.e. should be safe from fear for a little while after quaffing boldness potion (also heroism/super-heroism) the current length values seem a little long, perhaps just improve the characters resistance to fear? * warriors too hard, give them more hitpoints?, lower base-to-hit of other classes?, fix warrior so can withstand one AMHD poison breath attack? * boots of speed are too hard to find, 1/7000 is ring of speed, 1/14000+ is boots of speed, other evidence suggests that boots are about 10 times as rare as rings * items needed for win: see invisible, free action, resist fire, speed, if add black market, don't make it too easy to get these items * black market suggestions: - staff of teleportation 20000 - staff of speed 100000 - mushroom of restoring 20000 - and perhaps some powerful archery items * heavy armor is useless, does not protect against spells/breate, and reduces from to-hit, reduce penalty for characters with high STR? * new shop: enchantorium, where enchantments can be purchased for a high price, price proportional to how many enchants already there (i.e. enchanting a +9 item costs a fabulous amount, but enchanting a +0 item costs a little more than one enchant armor/weapon scroll), allow enchant to any value, i.e. AC/digging for HA/DF, digging for picks/shovels/etc, etc... * less reasonble suggestions follow... * too many weapon types, the variety is useless * have armor protect by subtracting ac/10 (sic) instead of reducing by % * speed is too important, need smaller time quanta * player_races/classes adjustments should be more meaningful, give races/classes max stat values which are not 18/100 * add an autosave option * add more up stairways? currently there are only randint(2) per level, perhaps guarantee that there will be at least 2 * warriors should automatically recognize ego weapons * dispel evil/undead are nearly worthless since they do not do much damage for their mana cost, change dispel_creature from mon_take_hit (i, randint(damage)) to mon_take_hit (i, damage) * rune of protection is too powerful, reduce OBJ_RUNE_PROT to 800, maybe 1000, if make this change, should make the spell somewhat easier to cast, say 50% for a level 33 priest DJG: this makes them much less useful against the Balrog * Balrog should not cast cause serious wounds as it does less damage than a normal attack, should not cast slow/paralyze since the player almost certainly will have some form of free action ---- * reincarnate, and lose 1/2 exp? * give enchant scrolls (or perhaps just *enchant*) a small probability of creating ego weapons * give enchant scrolls a small probability of destroying ego enchantments if they fail to increase pluses * make the number of hits per round probabilistic, i.e. instead of getting 1/2/3/4 hits per round, gives weapons 2.4 hits per round etc, to smoothen the curve * perhaps add option to order monsters differently? * give list of all monsters of that type, and then recall all if type 0, else only recall the one indicated by the number typed in change ident_char() in help.c * have all top level action functions (aim, move, throw, inven_command, etc.) return true if took a turn, otherwise false * add command to show number of turns played, time spent playing, date character started, perhaps even a calendar to give game date '1037 in the Third Age' * enter_store in store2.c calls draw_cave when exiting the stores, it should save the current screen contents away in memory and then restore from that saved screen * if type control-C while resting, sometimes the counter is not cleared, happens often when count less than 10, to fix, should move all I/O code out of signal handler, it should just set a flag and return * should examine_book(), scrolls(), and spells/prayers work when hallucinating? * change player attributes to counters, things like sustain_stat, etc. this would greatly simplify calc_bonuses() * should not be able to stun creates which do not move, and creatures which have no brains * los code used to determine if monsters are visible but not for objects, this leads to strange situations, when room lights up, one can see all objects in the room, but must enter the room before can see then monsters * have the monster generator make a table of undead monster indexes, to simplify summon_undead() * add some unique levels to make it more interesting, mazes, etc. * add a metric option * add diagonal corridors??? * add a storage rental shop, so players can save objects until they need them, but must pay for the priviledge, and have to go back to level 0 to retrieve the objects * create a safe haven for the player, where hp regen faster, poison/disease cure faster, and player must be in to win * add amulet of poison resistance, make it a high level item, people would have to take off amulet of the magi to wear it * add another set of gain stat potions, at about 1550 feet, to encourage people to go deeper in search of them * add a bank for saving/borrowing money (I don't like this), would rather make money less plentiful, perhaps make magic items less profitable to sell? * possible AD&D enhancements: - mages can't wear armor and cast at the same time - stealth decreases when wearing armor (for thieves) - priests can't use edged weapons - spell casting takes time (more than one turn for high level spells?) - ranger attack bonuses - paladin's laying on hands and wealth limits * give females better dexterity, and/or better spell casting ability, and/or more stealth * Orcs should always appear in groups? * possible balancing enhancements: - characters have max stat of 18/50+ initial player_races/classes mod, for example, human mage has max str of 18/50 - 5 = 18. - increase warrior HP so that a warrior can withstand one breath of any type frlom any dragon - dex is very important, so everybody should have a chance of getting high dex values * add option to find code so that it can optionally stop before lighting up or entering a room * add otpion to find code so that will enter corridor intersections * Change key-dispatch table in dungeon.c back to ORIGINAL mode. * put shell_out code in OS dependent files, unix.c/msdos.c/etc. * save all levels, problems with the sheer size of doing this, 10K * 50 levels is 500K total * change stats to straight 1-40 range * replace sets.c with an array ~110*16 with a flag for each type of damage faster and smaller than current scheme * add option to loc_symbol so that it can optionally print char to screen, most loc_symbol calls feed directly to print() * the look code uses a different algorithm from the los code, is recursive, hard to understand, has 3 gotos, I would prefer code more like los using the shading ideas of jnh * lose_dex is never called, well, we'd better fix that! * for characters with low dex, should have slight chance of dropping weapon, maybe less for fighters, more for mages and priests, should also trip occassionally when running * acid attacks should lower charisma? * the first nasty invisible monster is the Banshee at 1200 feet, so need to have see invisible items start appearing at about this time, the current balance seems OK to me though * black mold, and shimmering mold have 00000080 CMOVE flag set which has no effect * boots of great mass can be identified by checking the weight display, should eliminate them and replace with some other type of cursed boots suggestion: boots of two left feet, (- to dexterity) * add another field to Monster, treasurep, cloned/multiplyed monsters - should not have treasure, but if they pick up an object, then - again let them carry treasure * change randnor from table lookup to 8d99, save 512 bytes for table * remove ASCII dependencies - many places assume that a-z are consecutive, i.e. 'a'+25 = 'z' - the hallucination code assumes ascii - randint(95)+31 (misc1.c) * eliminate sscanf calls in ms_misc.c and wizard.c * allow numbers instead of letters in menus, i.e. spell lists, inventory_items/equip lists * don't use num-lock for tunneling in PC version, this is a locking key * characters accumulate too much cash, perhaps do this: - change OBJ_TOWN_LEVEL from 7 to 17, and change place_gold() in misc2.c ``` 293c293,294 < i = ((randint(dun_level+2)+2)/2.0) - 1; --- > /* NF: Change randint multiplier from (r(d+2)+2)/2 to (r(d+2)+4)/3 */ > i = ((randint(dun_level+2)+4)/3.0) - 1; ``` the fixes above (decrease mithril values ...) are preferable * show time elapsed playing, date character started, and number of turns elapsed * have option to put inventory_items/equipment list on one screen * set a timer, and if the game has been idle for 15 minutes, gracefully save and exit? * new monster suggestions by berman-andrew@yale, because there are no medium difficult monsters before level 40... ``` {"Electric Golem", 0x00020002, 0x00080004, 0x1080, 100, 380, 12, 85, 0, 'g', "70d8", "8 1 1d24|8 1 1d24", 35} {"Queen Harpy", 0x37120012, 0x00008208, 0x2036, 100, 1200, 20, 80, 2, 'h', "46d8", "1 3 2d6|1 3 2d6|1 2 2d12|3 7 0d0", 35} ``` ## new monster suggestions by Don Dodson * Nether Lich: invisible, phases through walls, similar to Emperor Lich in other respects * Archer: fires arrows that do normal damage, or poison, or reduce stat * Wizard: Like a sorceror but more powerful. Casts fireballs, summons all sorts of nasties, blindness, and hold person. Very high saving throw and resistant for fire/frost. * balls should affect player if inside radius, unfortunately, this is not really such a good idea, since the player is often forced to cast to the next space, would be OK if the player can specify any point for the target of the spell * when resting, a player should have a chance of not waking up when a monster enters a room * add wizard command that would create new MORIA_HOU file, since there already is a default def for it, otherwise, make days array bss * for HA weapons, p1 is used for both str increase, and sustain stat * rewrite my own sprintf routine that just takes strings %s and integers %d and nothing else, may be faster/smaller than the library version, or perhaps have a routine which cats 3 strings together, or 2 strings and an int * age characters before killing them of Ripe-Old-Age * these commands which take a dir should not work when confused, closeobject, jamdoor, openobject * monsters need a 'not carry large object' flag in CMOVE, have all chests (type 2), polearms (type 22) and staffs (type 55) be large objects, these should be rerolled if they occur when only small objects allowed, chests should have this bit set, also small creatures should have it set: kobolds, mummified kobolds, greedy little gnomes, scruffy-looking hobbits. * trap names are vague, have two names for each trap, a simple search discloses current vague name, setting it off, or a find/identify trap gives the long name * allow option of making aux files either hard coded paths, or paths relative to the executables directory, could then make the aux files relative, put exe file in same directory as aux files, and start up program with shell script that cds to aux file direcotry, this makes it easy to keep multiply moria versions around * allow rerolling after choose player_races/classes * still too slow on i/o, perhaps rewrite to avoid refresh() calls? perhaps use only motion optimization stuff? * should we call m_name if spell only applies to visible creatures? * when moving on objects, say what character moved onto (like rogue) * for throw command, ask for direction first (like rogue) * should be able to enchant to-hit to-dam of gauntlets of slaying? likewise, enchant toac of HA swords, etc. either should allow enchant of HA sword toac, or else disenchanter bats should not be allowed to reduce it * fix save files to include obj desc, then only need one random number generator which can use table program * give rogues detect treasure and detect objects, also give them a chance to steal objects from monsters * modify 'x' command to show monster hitpoints * replace magic numbers with constants * name objects if use did not identify them * when creature steals and then dies, the stolen item should appear, unless creature has teleported away in the meantime * always add exp to current and max, instead of just increasing max when current exceeds it, this would make attacking undead creatures a little more profitable since you are guaranteed a net gain of exp if you kill it, even if it drains a lot of EXP from you * allow chests to hold items * have a global variable, savefile field to keep track of the number of times a character has been resurrected * should not be able to use a two-handed weapon (two-handed sword, pole arm, bow, crossbow, flail, etc.) at same time as wearing a shield should be able to also choose to wield two weapons, but with a penalty of some sort, probably lowered AC * can't cast in any direction, should be able to, should also be able to cast down at feet (5), should be able to cast area affect spells (balls) at any point, so that the burst will affect any desired area * Long term stuff, enhancement suggestions: - give player something to do with money, i.e. 1Million gp for HA +10 +10 sword 'flavor' the levels, dragons on one level, undead on another, etc. - what's been discovered list - commands not close enough to rogue style - fixed item lettering in inventory_items, in-line/cmd-line options? - command line option to print out times open - Y destroy command, allow destroy from equipment list - let o, c, D commands automatically pick direction when they can - give rogue's a chance to steal, e.g. let monsters carry items that they pick up, if rogue fails, then monster gets a free hit, otherwise monster stays asleep, with a small chance of monster waking up - give magic users a chance at pre identifying scrolls - could use a help system, like VMS version - have multiple character parties, note that almost everything character dependent is stored in the py structure already * could reduce the size of the types.h file (and other source files too) if eliminate reduce type specifiers, i.e. int32u cmove;\n int32u spells; could be int32u cmove,\n spells; * add color, i.e. red dragons show up as a red 'D', etc * an inn on the town level, in which you must pay to stay, could be used by those who need to wait to get back into a stores, or who want to wait until a stores's inventory_items changes * when look at monsters, give monster state in addition to monster name, i.e. "You see a sleeping ancient red dragon." * add code to the get_dir() function so that one can optionally specific a hex instead of a direction, i.e. if you enter '.', then program goes into a cursor positioning mode, allowing you to move the cursor to the spot you want to specify * don't like the player_exp_levels values, the increase is not very even, especially in the upper levels * cloak of protection is misleading, it has no special features * need documentation on what each spell/prayer/staff/etc does, this is needed for priestly spells in particular, since these are non obvious also needed for special objects, such as crown of magi * eating elvish waybread for curing can cause a character to become bloated and start moving slower, either decrease food value of ew to 5000, or increase player food limit to 17500, or combination of both, or perhaps change the you are full message, maybe have another message at 7500?, or maybe make elvish waybread immune to the slowing effect * monsters should be more intelligent, should know about corridors so that they don't get stuck in dead ends, or continually retrace their movement * RNG for Moria should have a table shuffle, see Knuth * don't update stores inventories across save, unless the save file is at least, say, an hour old * when monster appears, print its name, may need some work to work reasonably with detect spells ## Features: * hero/superhero do not subtract hit points when effect wears off * WOR scroll can save you with minus hit points!! * detect monster does not detect invisible monsters (normally) * can hit monsters in walls, but can not cast at them * can not enchant something with negative numbers (i.e. cursed items) * how does armor protect against breath/gas? it doesn't!!! * missing amulets: strength, constitution, intelligence, dexterity * missing rings: gain wisdom, gain charisma note that missing rings/amulets are disjoint * missing detect monster potion (feature!) * missing blank scrolls (feature!) * missing staff genocide (feature!) * missing staff mass_genocide (feature!) ## D&D Inconsistencies, most of these should not be fixed since that would unbalance the game: * priests should not need light for prayers * magic missile should do 1d4+1 per level * detect invisible should last 5 rounds, instead of 1 * stinking cloud should be renamed cloudkill * fireball does 1d6 per level, should destroy all paper/wood and melt metal * lightning does 1d6 per level * hold monster 1 round per level of caster * phase door by touching a wall, and end up on other side of the wall * light spells should be mobile * heroism should not work when over level 11, superheroism should not work when over level 14 * rings can not be used cummulatively, rings or prot have no effect if wearing armor * Flame Tongue, +3 versus cold creatures and avian, +4 vs undead * Holy Avenger, +50% on all saving throws umoria-5.7.10+20181022/historical/features.md0000644000175000017500000003006013363422757017261 0ustar arielariel# Major Features introduced between v4.87 and 5.x You get a warning for spells or prayers beyond your strength. Inventory commands (wear, wield, exchange, take off, drop) now always take one move each. If anything significant happens during that move, such as a monster moving somewhere, the screen will be redisplayed. Inventories are displayed in the top-right corner of the screen, to minimize the number of characters that must be redrawn afterwards, this makes the game much more playable on slow speed modems. When selecting items or spells, you can select with a capital letter to print a description and confirm you really want that one. You can now associate counts with commands. The `?` command shows you the possibilities. In rogue command mode, simply type in the number. In this mode, numbers are no longer simple commands. In original moria command mode, enter a `#` to start a count. There are options (set with the = command) to display weights in an inventory, to highlight mineral seams, to ignore open doors while running, etc. Monster memory: use the / command to access info about a monster after you have seen/killed at least one of them. This can also be accessed from the look `l` command. Can enter control characters with two keystrokes using `^` key, useful for noisy phone lines? or stupid terminals? `M`ap command shows reduced size map. `L` allows you to scroll around entire map. The `L` command also will now center the character on the screen. Look `l` views all objects in an arc in the specified direction, or specify `5` and it will look in every direction. Can change your speed in wizard mode. New field in status line, used for rest/paralysed/searching/repeat counts. New field in status line shows speed. Can die of starvation. If carrying too much weight, you will be slowed immediately, the amount slowed depends on how much over the weight limit you are. Can not circle monsters (moving at the same speed as yourself) by moving around them in a diamond pattern. High level monsters have a chance of ignoring your bash, and they also have a chance of recovering immediately even if stunned. You can not bash the Balrog. Can abort rest/repeated command by hitting any character. Every duration affect prints a command when it wears off previously resist heat and resist cold, and perhaps others did not. New find/run algorithm which is much better than the old one. Also, better handling of dangerous conditions that occur during repeat commands/runs. An attack will always stop a repeated command and find/run mode. Can inscribe names onto objects. Some inscriptions are automatic: an empty wand/staff is inscribed 'empty'. An object which is used, but its use does not identify it is inscribed 'tried'. Cursed objects are inscribed 'damned' when identified as cursed. If you detect an object is enchanted, it is inscribed 'magik'. Scrolls have their titles removed after being identified. Scrolls/potions from the store/dungeon can stack after the dungeon item is identified. If you curse/damage a special object, such as a Holy Avenger sword, it will lose its special property. Will automatically save the game correctly if a HANGUP signal is received, i.e. dropped dialup connection, the network crashes, etc. The save file is not a panic save, instead the program plays the game for you (i.e. pretends that you are hitting ESCAPE) until it is safe to save the game. Better handling of inventory/equipment lists, i.e. you can drop items in the equipment list, can identify items in the equipment list, etc. More consistent monster definitions, i.e. creatures with frost attacks always take extra damage from fire (except those that also have fire attacks), and many other fixes. One source for the UNIX/IBM/Atari ST/Amiga/Mac/VMS versions, should make keeping the micro versions up-to-date much easier. The game is smaller and hopefully faster. Savefiles are now relatively machine independent, they should transport from one mini/micro to another without any problems. If you are hallucinating, `^R` won't help. Bows can have a plus to damage. These pluses only apply if you are using the bow to fire the appropriate type of missile (arrow/bolt/etc). Can haggle incrementally in the stores, i.e. can increase bid by 10 by typing +10, similarly -10 will decrease bid by 10. Beeps if invalid character entered at most prompts. Can type `^R` at any time to refresh the screen. Armor/weapons show their base AC/hit/dam values in their name. Any of `RETURN (^M)`, `LF (^J)`, `SPACE`, or `ESCAPE` can be typed at `-more-` prompts. When dropping a group of items, you have the choice of dropping only one, or all of them. There is a small chance that a player will notice that there are unidentified magic items in the inventory. The chance is larger if the item is in the equipment list. Object distribution has been changed so that when you are deep in the dungeon, there are many more high level objects created than low level objects. Monster distribution changed so that when you are deep in the dungeon, you will meet many more high level monsters than low level monsters. Additional spikes make a door harder to open, in a progressively decreasing sequence, that is, the more spikes you add, the less effect each additional spike has. When a stat changes, all abilities/etc that depend on the stat will immediately change. For example, if you CON increases, then your hit points will increase, if your INT/WIS increases then your mana will immediately increase and you will immediately learn new spells/prayers. This makes the Grape Jelly trick completely unnecessary, and, in fact, it will has no effect. Spell/prayer letters never change, i.e. if you know the second spell of a book, it will not be referred to as spell `a` if you do not know the first spell. When haggling, if you have reach the final offer much more often than you haven't, then the store will give you the benefit of the doubt and go directly to the final price. When wielding a weapon that is too heavy, the to hit penalty will be shown by the `C` command. It is harder to hit monsters when you can't see them. Perfect stealth is impossible now. Save files are now encrypted. Slay monster items are now slay animal. The previous slay monster item did not work as stated by the documentation, fixing it to work reasonably would have made it too powerful. If picking up an item will slow you down, you are asked if you really want to pick it up. Spells work differently now. You no longer learn them automatically when you gain a level. Instead, `Study` appears in the status line when are capable of learning new spells. Use the `G` command to actually learn the spells. The `G` command can be used anytime that you are eligible to learn new spells, as for example, after quaffing a restore intelligence/wisdom potion. The object naming routines are completely rewritten. This makes the program much smaller. Also, too long names will no longer cause a crash or error, they will be silently truncated as necessary. You can not attack an invisible/unlit monster in a wall by merely moving into the wall, instead you must tunnel into the wall. Since moving into a wall is a free turn, people could take advantage of this while fighting invis monsters by checking all of the walls first. To change the default moria save file name, define the environment variable "MORIA_SAV" to be the full pathname for where you want your save files. "moria -n" will start a new game, and will ignore "moria.save" if it exists Jellies and molds never carry treasure. Recharging a high level wand, or a wand which already has many charges, is much more likely to fail. Elves and half-elves have infravision now. There is no longer a distinction between wizard and god mode; only wizard mode exists now. Also, wizard mode no longer requires a password, and anyone (not just the installer) can enter wizard mode. Hence, wizard now works the same no matter whether you are playing on a micro or a mini. Enter wizard mode by using the `^W` command, help is `^H`. (Or for the rogue-like option, `^W` and `\`). Missile weapons are much more powerful when used with the proper weapon. For example, firing a bolt from a heavy crossbow multiplies its damage by 4. rounded pebble, iron shot: sling = 2x arrow: short bow = 2x, long bow = 3x, composite bow = 4x bolt: light crossbow = 3x, heavy crossbow = 4x Arrows and bolts appear more often deep in the dungeon. Magical missile weapon prices are more reasonable. The miscellaneous abilities `fighting`, `bows/throw`, `saving throw`, `disarm`, and `magical device` are all level dependent, i.e. they improve as you gain levels. Formerly, the rate of improvement was independent of class, now, however, the learning rate is clas dependent. For example, warriors gain proficiency at fighting twice as fast as mages/priests now. As a result, high level warriors are better fighters than before, and high level mages/priests do not fight as well as they used to. The complete table is: Magic Saving Fighting Bows Devices Disarm Throw Warrior 4 4 2 2 3 Mage 2 2 4 3 3 Priest 2 2 4 3 3 Rogue 3 4 3 4 3 Ranger 3 4 3 3 3 Paladin 3 3 3 2 3 In all cases, three is equivalent to the old rate. umoria now understands tilde (`~`) characters at the start of a file name. To resurrect a dead character, use `moria -w filename`. New status line shows "Is wizard" if in wizard mode, "Was wizard" if previously was in wizard mode, "Resurrected" if character was resurrected, and "Duplicate" if character is already on the scoreboard. Moved see invisible from slay animal to slay undead, since this makes a lot more sense. Dragon lightning breath attacks can now damage items in your inventory, just like all other dragon breath attacks. New scoreboard code added. It will hold 1000 scores by default, scores for saved characters are listed, can see all scores or just your scores. On multiuser OS's, each user can only have one entry in the scoreboard for each legal race/sex/class combination. The score file format is portable, and should be able to move from one machine to another with no problems. The `-s` option will show all scores in the scoreboard. The `-S` option will show only your scores. The `V` command will show all scores in the scoreboard. A second `V` command typed immediately afterwards will show only your scores. Typing `*` for the rest count will make you rest until you reach max mana and max hp. Spikes can now be wielded as a weapon for completeness. Note however that they make terrible weapons. The damage multiplier for bows is now displayed as part of their name. Two new `=` command options: can turn off sound, and can turn off the rest/ repeat counts displayed on the bottom line. The last is useful for those playing at 2400 baud or less, or on very slow machines. Many items are now added to the inventory in a sorted position, e.g. the spell books will always end up in the inventory sorted by their level. This automatic sorting does not work for objects which have a 'color', e.g. potions, wands, staffs, scrolls. Typing RETURN while haggling will default to the last increment/decrement amount used. Umoria 5.4: Prints multiple sentences on the message line if they are short enough. Umoria 5.5: The maximum damage enchantment for a weapon is now equal to its maximum damage, instead of being fixed at 10 (there is a slight chance of enchantment beyond this level); thus daggers are no longer the best weapons for mid-level characters. Umoria 5.5.1: If you inscribe an item with a single digit (using "{" to inscribe), you can refer to that item by the digit when it is in your inventory. You can thus label your pick as item 1, and wield item 1 whenever you need the pick without checking which letter the pick is. The prayers Turn Undead and Holy Word have been strengthened. Creatures with fire attacks now resist your fire attacks, and you will learn this in the recall information. _last modified 6/25/94_ umoria-5.7.10+20181022/historical/history.md0000644000175000017500000003767313363422757017165 0ustar arielariel# A Brief History of Moria ## The Early Days of Moria by Robert Alan Koeneke _posted to rec.games.roguelike.*, 21. February 1996_ I had some email show up asking about the origin of Moria, and its relation to Rogue. So I thought I would just post some text on the early days of Moria. First of all, yes, I really am the Robert Koeneke who wrote the first Moria. I had a lot of mail accusing me of pulling their leg and such. I just recently connected to Internet (yes, I work for a company in the dark ages where Internet is concerned) and was real surprised to find Moria in the news groups... Angband was an even bigger surprise, since I have never seen it. I probably spoke to its originator though... I have given permission to lots of people through the years to enhance, modify, or whatever as long as they freely distributed the results. I have always been a proponent of sharing games, not selling them. Anyway... Around 1980 or 81 I was enrolled in engineering courses at the University of Oklahoma. The engineering lab ran on a PDP 1170 under an early version of UNIX. I was always good at computers, so it was natural for me to get to know the system administrators. They invited me one night to stay and play some games, an early startrek game, The Colossal Cave Adventure (later just ’Adventure’), and late one night, a new dungeon game called ’Rogue’. So yes, I was exposed to Rogue before Moria was even a gleam in my eye. In fact, Rogue was directly responsible for millions of hours of play time wasted on Moria and its descendants... Soon after playing Rogue (and man, was I HOOKED), I got a job in a different department as a student assistant in computers. I worked on one of the early VAX 11/780’s running VMS, and no games were available for it at that time. The engineering lab got a real geek of an administrator who thought the only purpose of a computer was WORK! Imagine... Soooo, no more games, and no more rogue! This was intolerable! So I decided to write my own rogue game, Moria Beta 1.0. I had three languages available on my VMS system. Fortran IV, PASCAL V1.?, and BASIC. Since most of the game was string manipulation, I wrote the first attempt at Moria in VMS BASIC, and it looked a LOT like Rogue, at least what I could remember of it. Then I began getting ideas of how to improve it, how it should work differently, and I pretty much didn’t touch it for about a year. Around 1983, two things happened that caused Moria to be born in its recognizable form. I was engaged to be married, and the only cure for THAT is to work so hard you can’t think about it; and I was enrolled for fall to take an operating systems class in PASCAL. So, I investigated the new version of VMS PASCAL and found out it had a new feature. Variable length strings! Wow... That summer I finished Moria 1.0 in VMS PASCAL. I learned more about data structures, optimization, and just plain programming that summer then in all of my years in school. I soon drew a crowd of devoted Moria players... All at OU. I asked Jimmey Todd, a good friend of mine, to write a better character generator for the game, and so the skills and history were born. Jimmey helped out on many of the functions in the game as well. This would have been about Moria 2.0 In the following two years, I listened a lot to my players and kept making enhancements to the game to fix problems, to challenge them, and to keep them going. If anyone managed to win, I immediately found out how, and ’enhanced’ the game to make it harder. I once vowed it was ’unbeatable’, and a week later a friend of mine beat it! His character, ’Iggy’, was placed into the game as ’The Evil Iggy’, and immortalized... And of course, I went in and plugged up the trick he used to win... Around 1985 I started sending out source to other universities. Just before a OU / Texas football clash, I was asked to send a copy to the University of Texas... I couldn’t resist... I modified it so that the beggar on the town level was ’An OU football fan’ and they moved at maximum rate. They also multiplied at maximum rate... So the first step you took and woke one up, it crossed the floor increasing to hundreds of them and pounded you into oblivion... I soon received a call and provided instructions on how to ’de-enhance’ the game! Around 1986 - 87 I released Moria 4.7, my last official release. I was working on a Moria 5.0 when I left OU to go to work for American Airlines (and yes, I still work there). Moria 5.0 was a complete rewrite, and contained many neat enhancements, features, you name it. It had water, streams, lakes, pools, with water monsters. It had ’mysterious orbs’ which could be carried like torches for light but also gave off magical aura’s (like protection from fire, or aggravate monster...). It had new weapons and treasures... I left it with the student assistants at OU to be finished, but I guess it soon died on the vine. As far as I know, that source was lost... I gave permission to anyone who asked to work on the game. Several people asked if they could convert it to ’C’, and I said fine as long as a complete credit history was maintained, and that it could NEVER be sold, only given. So I guess one or more of them succeeded in their efforts to rewrite it in ’C’. I have since received thousands of letters from all over the world from players telling about their exploits, and from administrators cursing the day I was born... I received mail from behind the iron curtain (while it was still standing) talking about the game on VAX’s (which supposedly couldn’t be there due to export laws). I used to have a map with pins for every letter I received, but I gave up on that! I am very happy to learn my creation keeps on going... I plan to download it and Angband and play them... Maybe something has been added that will surprise me! That would be nice... I never got to play Moria and be surprised... Robert Alan Koeneke ## The Early Days of Umoria by James E Wilson _posted to rec.games.roguelike.moria. January 2006_ I started work on Umoria in February 1987. I somehow acquired play testers in April. I don’t recall exactly how, but I was at a university, so maybe they saw me playing it on a public terminal and asked for a copy. The game slowly spread around the Berkeley area. By November, the game was in good enough shape that I could post it to comp.sources.games. I think I was still calling it UNIX Moria then, to distinguish it from the original, but the comp.sources.games moderator shortened it to Umoria, and that name has stuck. After the comp.sources.games posting, the game was widely available, and things just grew from there. The Usenet rec.games.moria group was created sometime around Jan 1, 1988, and was where most of the early discussions about it occurred. I originally got involved with Moria because I was tired of playing other games. I spent a lot of time playing games such as rogue and hack, and was looking for something different to try. There was a game called UltraRogue that I enjoyed playing. Unfortunately, it had some very frustrating bugs, and it was only available as a binary. I spent some time investigating the possibilities of trying to decompile it and fix it. Before I got very far, I chanced upon a copy of the VMS Pascal Moria sources and quickly decided that porting the sources to Unix was a better project than trying to fix UltraRogue. After I gained some fame as the Umoria author, I did manage to get a copy of of the UltraRogue sources, but by then it was too late, and I wasn’t sure if I had a legal copy of the sources, so I never did anything with them. The original game was written in VAX/VMS Pascal with some VAX/VMS Assembler. I did not have access to a VMS machine, and both the Pascal and Assembler code would not work with Unix because of many uses of VMS extensions. Since C was a more common language for Unix anyways, I decided to just convert the whole thing to C. Much of the initial syntax conversion was done via emacs macros. Then it was just a matter of tweaking the sources until they would compile. The hardest part was finding and fixing all of the off-by-one errors, as Pascal array indexing starts at 1, and C array indexing starts at 0. It took me years to find and fix all of the off-by-one errors. I still can’t believe how hard it was to find them all. At least, I hope I found them all. Mostly what I remember about the early years of working on Umoria was that it was a good introduction to software engineering principles. I had to learn how to deal with bugs, bug fixes, release schedules, making and distributing releases, etc. It was just a game, but I learned many valuable lessons about real world software development while working on it. One of my favorite moments from my Umoria years is when I got mail from one of the authors of rogue. I recognized the name right away, and had to ask. He confirmed that he was one of the rogue authors, and that he was a fan of Umoria. That made my day. The credits in the PC/Windows game Diablo mention that it was inspired in part by Umoria. ## A History of Moria: written by Jim Wilson _first posted to rec.games.moria on November 27, 1990_ This history is somewhat biased towards the Umoria versions because those are the versions that I am most familiar with; info for the VMS versions, in particular, may be inaccurate. ### Moria 4.8: The last version developed by the original authors, R.A. Koeneke, etc., at the Univ Oklahoma. This is written in VMS Pascal, and is only available for VAX/VMS systems. All other versions of moria are based on this program. (Sometimes referred to as VMS Moria 4.8.) ### Moria UB 5.0 This version was developed at Univ Buffalo, hence the UB in the name. This is also written in VMS Pascal, and is only available for VAX/VMS systems. Some of the distinguishing features of this version are: a Black Market where one can purchase highly enchanted magic items at 100 times normal value, monsters on the town level worth negative experience (Mother and Baby, Small Child), *shadow* Balrogs start appearing at level 50 with the *real* Balrog at level 1200 (this makes it a bit difficult to win). There are also some new items, and many more new monsters. (Sometimes referred to as VMS Moria 5.0 or Moria 5.0. Should not be confused with Umoria 5.x.) This is based on the Moria 4.8 sources. ### Moria UB 5.1 The latest version of UB Moria. This version is not available outside Univ Buffalo as yet, and I do not know what any of the new features are. Only available for VAX/VMS systems. ### VMS Moria 6.0 This version was under development at Villanova by Rick Greene. However, it was never completed and was never released. I believe that it was abandoned because Rick lost computer and/or network access. This was based on the Moria UB 5.0 sources. ### Imoria This version was developed at the Univ. Washington. It is written in VMS Pascal, and is only available for VAX/VMX systems. I know very little about this version, but have been told by people at U Washington that it is far superior to any other Moria version that currently exists. It has many new monsters, new objects, new races, new classes, new terrain types (like water), etc. The sources are currently available from UBVMS. ### Imoria 4.9 The latest version of Imoria. ### PC-Moria 4.00+ This version is a port of the Moria 4.8 Pascal sources to the IBM-PC by John W. DeBoskey. This is a faithful port of the original game, unfortunately, this version has quite a few bugs and hence is not as good as the unrelated PC-Moria 4.87 version. ### Umoria (UNIX Moria) 4.83/4.85/4.87 This version was developed by Jim Wilson at UC Berkeley. It is written in UNIX/C and is much more portable than the original sources. These sources, at one time or another, were ported to VMS, IBM-PC, Atari ST, Amiga, Macintosh, Apple IIGS, VM/SP, Archimedes, are probably others too. This version fixes very many bugs, spelling errors, and inconsistencies in the original Moria 4.8 sources. This version has no help facility like the original program. It has character rerolling (in the later versions), but few other changes from the original game. (This version has many names, e.g. Umoria 4.87, UNIX Moria 4.87, PC-Moria 4.83, PC-Moria 4.873, Mac Moria 4.87, etc. Just about anything with a .83/.85/.87 in the name is a version of this program.) This is based on the Moria 4.8 sources. ### PC-Moria 4.83/4.873 This version was developed by Don Kneller, based on the Umoria sources. These sources are identical except that they will compile on machines with 16 bit integers, had many changes to reduce program size, and introduced the reduced map View. (Note: PC-Moria 4.83 is extremely buggy, and is unplayable. I strongly recommend that you upgrade to a newer version if you are still playing it.) ### Amiga Moria v3.0 This version was written by Richard and Bryan Henderson of Kettering, Ohio. It is based on the Umoria 4.85 sources. This version has bitmapped graphics, unlike the ascii graphics of all other versions. It has weapons of Godly Might (which make one practically invicible) and some other changes that make it far far easier than all other Moria versions. It also has several new monsters, such as Lavender Leprechauns. Sources for this version were never released. ### BRUCE Moria This version was developed by Christopher J. Stuart at Monash University, Clayton, Victoria Australia. This version has many great new features: monster memories, look any direction code, settable options, better run/find code, can center character on screen, stat code rewritten to fix bugs, rudimentory help facility added, plus many other enhancements. This was considered an experimental version, and source never publicly released. This was based on the Umoria 4.85 sources. ### UMoria 5.x This version is under developement by Jim Wilson at Cygnus Support. It has unified source for the UNIX/IBM-PC/Atari/Mac/VMS/Amiga ports of Umoria 4.87. It includes many new features borrowed from BRUCE Moria, many more bug fixes, all floating point code eliminated, many changes that affect play balance (hopefully for the better), many type/structure changes to reduce game size and allow fixes for pervasive bugs. See the doc/FEATURES.NEW file for a list of most user visible changes. (Sometimes called Umoria 5.0, Moria 5.0, Moria 5.x. Should not be confused with Moria UB 5.0.) ### Umoria 5.6 The latest version of Umoria 5.x. David Grabiner has taken over Umoria 5.x development but has made only minor changes. Umoria 5.6 is released under the GNU General Public License. ### Moria Genealogy Vertical position indicates roughly when the versions were made available, although the scale is not very accurate. 1983 Moria 1.0 | 1985 Moria 4.8 / \ 1987 UMoria 4.83 ------ ----------------------\ / \ | \ / ------- PC-Moria 4.83 | | UMoria 4.85 | | | / | \ | | Moria UB 5.0 1988 /------ | ------ | | | / | UMoria 4.87 | | | | BRUCE Moria | \ | | | Amiga Moria | | PC-Moria 4.873 | |---\ (surviving) | | / | | \ 1989 | /----------/ | | VMS Moria 6.0 | / | | (defunct) 1990 Umoria 5.0 | | | Imoria 4.9 Moria UB 5.1 | | | 1994 Umoria 5.5.1 (alive and well) (alive and well) | 2008 Umoria 5.6 | (alive and well) umoria-5.7.10+20181022/README.md0000644000175000017500000000751513363422757014250 0ustar arielariel# Umoria _The Dungeons of Moria_ is a single player dungeon simulation originally written by Robert Alan Koeneke, with v1.0 released in 1983. The game was originally written in VMS Pascal before being ported to the C language and released as _Umoria_ in 1988. Moria has had many variants over the years, with [_Angband_](http://rephial.org/) being the most well known, and was also an inspiration for one the most commercially successful action roguelike games, _Diablo_! Supported Platforms: - Windows - macOS - Linux* (Ubuntu, Debian, Fedora) _* other Linux distros may work, but have not yet been tested._ ## Umoria Restoration Release: v5.7 The main focus of the `v5.7` release is to provide support for the three main systems: Windows, macOS, and Linux. Support for all other outdated computer systems such as, MS DOS, "Classic" Mac OS (pre OS X), Amiga, and Atari ST has been removed. _Note: there are no gameplay changes in this release._ A great deal of _code restoration_ is also currently being undertaken in the hope to aid future development of the game. Examples of tasks completed so far includes, reformatting the source code with the help of `clang-tidy` and `clang-format`, modernizing the code to use standard C types, and fixing all warnings while compiling against recent versions of GCC. Full details of all changes can be found in the [CHANGELOG](CHANGELOG.md), and by browsing the commit history. Due to its lack of Windows and Mac support, Moria was unplayable for many people. Hopefully these changes will give more people a chance to play this classic roguelike game. ## Notes on Compiling Umoria At present Umoria has been tested against GCC `6.x`, `7.x`, and `8.1`, with `ncurses 6.x`, although earlier versions should also work fine. You will require these along with `CMake` and the C++ build tools for your system. ### macOS and Linux At the command prompt type the following: ``` $ cmake . $ make $ make install ``` A `umoria` directory will be created containing the game binary and data files, which you can then move to your `home` directory, or other location. ### Windows MinGW is used to provide GCC and Binutils for compiling on the Windows platform. The easiest solution to get set up is to use the [MSYS2 Installer](http://msys2.github.io/). Once installed you will use `pacman` to install `GCC`, `ncurses`, and the `make`/`cmake` build tools. At present you will need to provide an environment variable for the MinGW system you are compiling on. This will be either `mingw64` or `mingw32`. At the command prompt type the following, being sure to add the correct label to `MINGW=`: ``` $ MINGW=mingw64 cmake . $ make $ make install ``` As with the macOS/Linux builds, the files will be installed into a `umoria` directory, which you can then move to your games directory. ## Historical Documents Most of the original documents included in the Umoria 5.6 sources have been moved to the [historical](historical/) directory. You will even find the old CHANGELOG which tracks all the code changes made between versions 4.81 and 5.5.2 (1987-2008). If you'd like to learn more on the development history of Umoria, these can make for interesting reading. ## Code of Conduct and Contributions See here for details on our [Code of Conduct](CODE_OF_CONDUCT.md). For details on how to contribute to the Umoria project, please read our [contributing](CONTRIBUTING.md) guide. ## License Information Umoria is released under the [GNU General Public License v2.0](LICENSE). In 2007 Ben Asselstine and Ben Shadwick started the [_free-moria_](http://free-moria.sourceforge.net/) project to re-license UMoria 5.5.2 under GPL v2 by obtaining permission from all the contributing authors. A year later, they finally succeeded in their goal and in late 2008 the Moria maintainer, David Grabiner, released Umoria 5.6 under a GPL v2 license. umoria-5.7.10+20181022/CONTRIBUTING.md0000644000175000017500000001277513363422757015226 0ustar arielariel# Contributing to Umoria Thanks for your interest in contributing to the Umoria project! The following is a set of guidelines for contributing to Umoria, which is hosted in the [Dungeons of Moria organization](https://github.com/dungeons-of-moria) on GitHub. The following are mostly guidelines, not rules. Use your best judgement, and feel free to propose changes to this document in a pull request. ## Code of Conduct This project and everyone participating in it is governed by the [Umoria Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to [info@umoria.org](mailto:info@umoria.org). ## Questions and Discussions Currently the Umoria project does not have its own blog or community forum, however, if you wish to discuss the game or ask general questions about Umoria, the best place to start would be the [MoriaRL Reddit page](https://www.reddit.com/r/moriarl/). You'll get faster results than by using the resources below. For more general questions and discussions on the Roguelike genre, please visit the [Roguelike Reddit page](https://www.reddit.com/r/roguelikes/). These are both friendly and welcoming communities. ## What should I know before I get started? First, and most importantly, the current goal of the project is to not implement new gameplay changes or features to Umoria. The gameplay was fine-tuned over a period of many years and has been in its current state for more than 20 years. Perhaps at a future date gameplay changes will be considered, but for the moment it should not change. The focus of current work has been on cleaning up the code base; using _modern_ C/C++ coding styles, updating to compile against C++14, breaking up the many large and complex functions to create smaller more manageable blocks of code. Along with making the code easier to understand these changes will also make any future features easier to implement. One idea is to switch from NCurses to SDL to allow mouse support to be added. ## How Can I Contribute? ### Reporting Bugs This section guides you through submitting a bug report for Umoria. Following these guidelines helps maintainers and the community understand your report, reproduce the behavior, and find related reports. Before creating bug reports, please check out the [current issues](https://github.com/dungeons-of-moria/umoria/issues) as you might find out that you don't need to create one. When you are creating a bug report, please include as many details as possible, as indicated the next section. > **Note:** If you find a **Closed** issue that seems like it is the same thing that you're experiencing, open a new issue and include a link to the original issue in the body of your new one. #### How Do I Submit A (Good) Bug Report? Bugs are tracked as [GitHub issues](https://github.com/dungeons-of-moria/umoria/issues). Create an issue explaining the problem, and including any additional details that you think may help maintainers reproduce the problem: * **Use a clear and descriptive title** for the issue to identify the problem. * **Describe the exact steps to reproduce the problem** (if possible) with as many details as you can. * **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior. * **Explain which behavior you expected to see instead and why.** * **If you're reporting that Umoria crashed**, include a crash report with a stack trace from the operating system. On macOS, the crash report will be available in `Console.app` under "Diagnostic and usage information" > "User diagnostic reports". Include the crash report in the issue in a code block, a file attachment, or put it in a [gist](https://gist.github.com) and provide a link to that gist. Include details about your configuration and environment: * **Which version of Umoria are you using?** You can get the exact version by running `umoria -v` in your terminal. If you compiled directly from the source code, please state that. * **What's the name and version of the OS you're using**? * **Are you running Umoria in a virtual machine?** If so, which VM software are you using and which operating systems and versions are used for the host and the guest? * **Which keyboard layout are you using?** Umoria or traditional Roguelike keys (`hjkl`)? ### Code Contributions General points worth remembering before making your Pull Request (PR): * Avoid platform-dependent code. * Format your source code to the provided [`.clang-format`](.clang-format) style. At present there are no strong style requirements, but here are a few ideas that I would like to start thinking about. #### General Formatting * Indentation: one indent should be *4 spaces*, so please be careful to avoid the use of _tabs_. #### Use Standard Types At present I'm using only _standard types_, so please do continue this practice. The common standard types are: * `int8_t`, `int16_t`, `int32_t`, `int64_t` β€” signed integers * `uint8_t`, `uint16_t`, `uint32_t`, `uint64_t` β€” unsigned integers * `float` β€” standard 32-bit floating point * `double` - standard 64-bit floating point When representing ASCII characters we should be using the `char` type. #### Naming * **Classes / Structs / Types**: `CamelCase`, with the first character uppercase. * **Functions**: `camelCase`, with the first character lowercase. * **Variables**: `snake_case`, and all lowercase. I like the easy visual distinction from naming like this. You can see immediately what its function is and without having _noisy_ suffixes. umoria-5.7.10+20181022/LICENSE0000644000175000017500000004325413363422757013776 0ustar arielariel GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. umoria-5.7.10+20181022/src/0000755000175000017500000000000013363422757013550 5ustar arielarielumoria-5.7.10+20181022/src/scrolls.cpp0000644000175000017500000004502513363422757015743 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. #include "headers.h" // Note: naming of all the scroll functions needs verifying -MRC- static bool playerCanReadScroll(int &item_pos_start, int &item_pos_end) { if (py.flags.blind > 0) { printMessage("You can't see to read the scroll."); return false; } if (playerNoLight()) { printMessage("You have no light to read by."); return false; } if (py.flags.confused > 0) { printMessage("You are too confused to read a scroll."); return false; } if (py.unique_inventory_items == 0) { printMessage("You are not carrying anything!"); return false; } if (!inventoryFindRange(TV_SCROLL1, TV_SCROLL2, item_pos_start, item_pos_end)) { printMessage("You are not carrying any scrolls!"); return false; } return true; } static int inventoryItemIdOfCursedEquipment() { int item_count = 0; int items[6]; if (inventory[player_equipment::EQUIPMENT_BODY].category_id != TV_NOTHING) { items[item_count++] = player_equipment::EQUIPMENT_BODY; } if (inventory[player_equipment::EQUIPMENT_ARM].category_id != TV_NOTHING) { items[item_count++] = player_equipment::EQUIPMENT_ARM; } if (inventory[player_equipment::EQUIPMENT_OUTER].category_id != TV_NOTHING) { items[item_count++] = player_equipment::EQUIPMENT_OUTER; } if (inventory[player_equipment::EQUIPMENT_HANDS].category_id != TV_NOTHING) { items[item_count++] = player_equipment::EQUIPMENT_HANDS; } if (inventory[player_equipment::EQUIPMENT_HEAD].category_id != TV_NOTHING) { items[item_count++] = player_equipment::EQUIPMENT_HEAD; } // also enchant boots if (inventory[player_equipment::EQUIPMENT_FEET].category_id != TV_NOTHING) { items[item_count++] = player_equipment::EQUIPMENT_FEET; } int item_id = 0; if (item_count > 0) { item_id = items[randomNumber(item_count) - 1]; } if ((inventory[player_equipment::EQUIPMENT_BODY].flags & config::treasure::flags::TR_CURSED) != 0u) { item_id = player_equipment::EQUIPMENT_BODY; } else if ((inventory[player_equipment::EQUIPMENT_ARM].flags & config::treasure::flags::TR_CURSED) != 0u) { item_id = player_equipment::EQUIPMENT_ARM; } else if ((inventory[player_equipment::EQUIPMENT_OUTER].flags & config::treasure::flags::TR_CURSED) != 0u) { item_id = player_equipment::EQUIPMENT_OUTER; } else if ((inventory[player_equipment::EQUIPMENT_HEAD].flags & config::treasure::flags::TR_CURSED) != 0u) { item_id = player_equipment::EQUIPMENT_HEAD; } else if ((inventory[player_equipment::EQUIPMENT_HANDS].flags & config::treasure::flags::TR_CURSED) != 0u) { item_id = player_equipment::EQUIPMENT_HANDS; } else if ((inventory[player_equipment::EQUIPMENT_FEET].flags & config::treasure::flags::TR_CURSED) != 0u) { item_id = player_equipment::EQUIPMENT_FEET; } return item_id; } static bool scrollEnchantWeaponToHit() { Inventory_t &item = inventory[player_equipment::EQUIPMENT_WIELD]; if (item.category_id == TV_NOTHING) { return false; } obj_desc_t msg = {'\0'}; obj_desc_t desc = {'\0'}; itemDescription(desc, item, false); (void) sprintf(msg, "Your %s glows faintly!", desc); printMessage(msg); if (spellEnchantItem(item.to_hit, 10)) { item.flags &= ~config::treasure::flags::TR_CURSED; playerRecalculateBonuses(); } else { printMessage("The enchantment fails."); } return true; } static bool scrollEnchantWeaponToDamage() { Inventory_t &item = inventory[player_equipment::EQUIPMENT_WIELD]; if (item.category_id == TV_NOTHING) { return false; } obj_desc_t msg = {'\0'}; obj_desc_t desc = {'\0'}; itemDescription(desc, item, false); (void) sprintf(msg, "Your %s glows faintly!", desc); printMessage(msg); int16_t scroll_type; if (item.category_id >= TV_HAFTED && item.category_id <= TV_DIGGING) { scroll_type = (int16_t) maxDiceRoll(item.damage); } else { // Bows' and arrows' enchantments should not be // limited by their low base damages scroll_type = 10; } if (spellEnchantItem(item.to_damage, scroll_type)) { item.flags &= ~config::treasure::flags::TR_CURSED; playerRecalculateBonuses(); } else { printMessage("The enchantment fails."); } return true; } static bool scrollEnchantItemToAC() { int item_id = inventoryItemIdOfCursedEquipment(); if (item_id <= 0) { return false; } Inventory_t &item = inventory[item_id]; obj_desc_t msg = {'\0'}; obj_desc_t desc = {'\0'}; itemDescription(desc, item, false); (void) sprintf(msg, "Your %s glows faintly!", desc); printMessage(msg); if (spellEnchantItem(item.to_ac, 10)) { item.flags &= ~config::treasure::flags::TR_CURSED; playerRecalculateBonuses(); } else { printMessage("The enchantment fails."); } return true; } static int scrollIdentifyItem(int item_id, bool &is_used_up) { printMessage("This is an identify scroll."); is_used_up = spellIdentifyItem(); // The identify may merge objects, causing the identify scroll // to move to a different place. Check for that here. It can // move arbitrarily far if an identify scroll was used on // another identify scroll, but it always moves down. Inventory_t *item = &inventory[item_id]; while (item_id > 0 && (item->category_id != TV_SCROLL1 || item->flags != 0x00000008)) { item_id--; item = &inventory[item_id]; } return item_id; } static bool scrollRemoveCurse() { if (spellRemoveCurseFromAllItems()) { printMessage("You feel as if someone is watching over you."); return true; } return false; } static bool scrollSummonMonster() { bool identified = false; for (int i = 0; i < randomNumber(3); i++) { auto y = (int) py.row; auto x = (int) py.col; identified |= monsterSummon(y, x, false); } return identified; } static void scrollTeleportLevel() { dg.current_level += (-3) + 2 * randomNumber(2); if (dg.current_level < 1) { dg.current_level = 1; } dg.generate_new_level = true; } static bool scrollConfuseMonster() { if (!py.flags.confuse_monster) { printMessage("Your hands begin to glow."); py.flags.confuse_monster = true; return true; } return false; } static bool scrollEnchantWeapon() { Inventory_t &item = inventory[player_equipment::EQUIPMENT_WIELD]; if (item.category_id == TV_NOTHING) { return false; } obj_desc_t msg = {'\0'}; obj_desc_t desc = {'\0'}; itemDescription(desc, item, false); (void) sprintf(msg, "Your %s glows brightly!", desc); printMessage(msg); bool enchanted = false; for (int i = 0; i < randomNumber(2); i++) { if (spellEnchantItem(item.to_hit, 10)) { enchanted = true; } } int16_t scroll_type; if (item.category_id >= TV_HAFTED && item.category_id <= TV_DIGGING) { scroll_type = (int16_t) maxDiceRoll(item.damage); } else { // Bows' and arrows' enchantments should not be limited // by their low base damages scroll_type = 10; } for (int i = 0; i < randomNumber(2); i++) { if (spellEnchantItem(item.to_damage, scroll_type)) { enchanted = true; } } if (enchanted) { item.flags &= ~config::treasure::flags::TR_CURSED; playerRecalculateBonuses(); } else { printMessage("The enchantment fails."); } return true; } static bool scrollCurseWeapon() { Inventory_t &item = inventory[player_equipment::EQUIPMENT_WIELD]; if (item.category_id == TV_NOTHING) { return false; } obj_desc_t msg = {'\0'}; obj_desc_t desc = {'\0'}; itemDescription(desc, item, false); (void) sprintf(msg, "Your %s glows black, fades.", desc); printMessage(msg); itemRemoveMagicNaming(item); item.to_hit = (int16_t) (-randomNumber(5) - randomNumber(5)); item.to_damage = (int16_t) (-randomNumber(5) - randomNumber(5)); item.to_ac = 0; // Must call playerAdjustBonusesForItem() before set (clear) flags, and // must call playerRecalculateBonuses() after set (clear) flags, so that // all attributes will be properly turned off. playerAdjustBonusesForItem(item, -1); item.flags = config::treasure::flags::TR_CURSED; playerRecalculateBonuses(); return true; } static bool scrollEnchantArmor() { int item_id = inventoryItemIdOfCursedEquipment(); if (item_id <= 0) { return false; } Inventory_t &item = inventory[item_id]; obj_desc_t msg = {'\0'}; obj_desc_t desc = {'\0'}; itemDescription(desc, item, false); (void) sprintf(msg, "Your %s glows brightly!", desc); printMessage(msg); bool enchanted = false; for (int i = 0; i < randomNumber(2) + 1; i++) { if (spellEnchantItem(item.to_ac, 10)) { enchanted = true; } } if (enchanted) { item.flags &= ~config::treasure::flags::TR_CURSED; playerRecalculateBonuses(); } else { printMessage("The enchantment fails."); } return true; } static bool scrollCurseArmor() { int item_id; if (inventory[player_equipment::EQUIPMENT_BODY].category_id != TV_NOTHING && randomNumber(4) == 1) { item_id = player_equipment::EQUIPMENT_BODY; } else if (inventory[player_equipment::EQUIPMENT_ARM].category_id != TV_NOTHING && randomNumber(3) == 1) { item_id = player_equipment::EQUIPMENT_ARM; } else if (inventory[player_equipment::EQUIPMENT_OUTER].category_id != TV_NOTHING && randomNumber(3) == 1) { item_id = player_equipment::EQUIPMENT_OUTER; } else if (inventory[player_equipment::EQUIPMENT_HEAD].category_id != TV_NOTHING && randomNumber(3) == 1) { item_id = player_equipment::EQUIPMENT_HEAD; } else if (inventory[player_equipment::EQUIPMENT_HANDS].category_id != TV_NOTHING && randomNumber(3) == 1) { item_id = player_equipment::EQUIPMENT_HANDS; } else if (inventory[player_equipment::EQUIPMENT_FEET].category_id != TV_NOTHING && randomNumber(3) == 1) { item_id = player_equipment::EQUIPMENT_FEET; } else if (inventory[player_equipment::EQUIPMENT_BODY].category_id != TV_NOTHING) { item_id = player_equipment::EQUIPMENT_BODY; } else if (inventory[player_equipment::EQUIPMENT_ARM].category_id != TV_NOTHING) { item_id = player_equipment::EQUIPMENT_ARM; } else if (inventory[player_equipment::EQUIPMENT_OUTER].category_id != TV_NOTHING) { item_id = player_equipment::EQUIPMENT_OUTER; } else if (inventory[player_equipment::EQUIPMENT_HEAD].category_id != TV_NOTHING) { item_id = player_equipment::EQUIPMENT_HEAD; } else if (inventory[player_equipment::EQUIPMENT_HANDS].category_id != TV_NOTHING) { item_id = player_equipment::EQUIPMENT_HANDS; } else if (inventory[player_equipment::EQUIPMENT_FEET].category_id != TV_NOTHING) { item_id = player_equipment::EQUIPMENT_FEET; } else { item_id = 0; } if (item_id <= 0) { return false; } Inventory_t &item = inventory[item_id]; obj_desc_t msg = {'\0'}; obj_desc_t desc = {'\0'}; itemDescription(desc, item, false); (void) sprintf(msg, "Your %s glows black, fades.", desc); printMessage(msg); itemRemoveMagicNaming(item); item.flags = config::treasure::flags::TR_CURSED; item.to_hit = 0; item.to_damage = 0; item.to_ac = (int16_t) (-randomNumber(5) - randomNumber(5)); playerRecalculateBonuses(); return true; } static bool scrollSummonUndead() { bool identified = false; for (int i = 0; i < randomNumber(3); i++) { int y = py.row; int x = py.col; identified |= monsterSummonUndead(y, x); } return identified; } static void scrollWordOfRecall() { if (py.flags.word_of_recall == 0) { py.flags.word_of_recall = (int16_t) (25 + randomNumber(30)); } printMessage("The air about you becomes charged."); } // Scrolls for the reading -RAK- void scrollRead() { game.player_free_turn = true; int item_pos_start, item_pos_end; if (!playerCanReadScroll(item_pos_start, item_pos_end)) { return; } int item_id; if (!inventoryGetInputForItemId(item_id, "Read which scroll?", item_pos_start, item_pos_end, CNIL, CNIL)) { return; } // From here on, no free turn for the player game.player_free_turn = false; bool used_up = true; bool identified = false; Inventory_t *item = &inventory[item_id]; uint32_t item_flags = item->flags; while (item_flags != 0) { int scroll_type = getAndClearFirstBit(item_flags) + 1; if (item->category_id == TV_SCROLL2) { scroll_type += 32; } switch (scroll_type) { case 1: identified = scrollEnchantWeaponToHit(); break; case 2: identified = scrollEnchantWeaponToDamage(); break; case 3: identified = scrollEnchantItemToAC(); break; case 4: item_id = scrollIdentifyItem(item_id, used_up); identified = true; break; case 5: identified = scrollRemoveCurse(); break; case 6: identified = spellLightArea(py.row, py.col); break; case 7: identified = scrollSummonMonster(); break; case 8: playerTeleport(10); // Teleport Short, aka Phase Door identified = true; break; case 9: playerTeleport(100); // Teleport Long identified = true; break; case 10: scrollTeleportLevel(); identified = true; break; case 11: identified = scrollConfuseMonster(); break; case 12: spellMapCurrentArea(); identified = true; break; case 13: identified = monsterSleep(py.row, py.col); break; case 14: spellWardingGlyph(); identified = true; break; case 15: identified = spellDetectTreasureWithinVicinity(); break; case 16: identified = spellDetectObjectsWithinVicinity(); break; case 17: identified = spellDetectTrapsWithinVicinity(); break; case 18: identified = spellDetectSecretDoorssWithinVicinity(); break; case 19: printMessage("This is a mass genocide scroll."); (void) spellMassGenocide(); identified = true; break; case 20: identified = spellDetectInvisibleCreaturesWithinVicinity(); break; case 21: printMessage("There is a high pitched humming noise."); (void) spellAggravateMonsters(20); identified = true; break; case 22: identified = spellSurroundPlayerWithTraps(); break; case 23: identified = spellDestroyAdjacentDoorsTraps(); break; case 24: identified = spellSurroundPlayerWithDoors(); break; case 25: printMessage("This is a Recharge-Item scroll."); used_up = spellRechargeItem(60); identified = true; break; case 26: printMessage("This is a genocide scroll."); (void) spellGenocide(); identified = true; break; case 27: identified = spellDarkenArea(py.row, py.col); break; case 28: identified = playerProtectEvil(); break; case 29: spellCreateFood(); identified = true; break; case 30: identified = spellDispelCreature(config::monsters::defense::CD_UNDEAD, 60); break; case 33: identified = scrollEnchantWeapon(); break; case 34: identified = scrollCurseWeapon(); break; case 35: identified = scrollEnchantArmor(); break; case 36: identified = scrollCurseArmor(); break; case 37: identified = scrollSummonUndead(); break; case 38: playerBless(randomNumber(12) + 6); identified = true; break; case 39: playerBless(randomNumber(24) + 12); identified = true; break; case 40: playerBless(randomNumber(48) + 24); identified = true; break; case 41: scrollWordOfRecall(); identified = true; break; case 42: spellDestroyArea(py.row, py.col); identified = true; break; default: printMessage("Internal error in scroll()"); break; } } item = &inventory[item_id]; if (identified) { if (!itemSetColorlessAsIdentified(item->category_id, item->sub_category_id, item->identification)) { // round half-way case up py.misc.exp += (item->depth_first_found + (py.misc.level >> 1)) / py.misc.level; displayCharacterExperience(); itemIdentify(inventory[item_id], item_id); } } else if (!itemSetColorlessAsIdentified(item->category_id, item->sub_category_id, item->identification)) { itemSetAsTried(*item); } if (used_up) { itemTypeRemainingCountDescription(item_id); inventoryDestroyItem(item_id); } } umoria-5.7.10+20181022/src/spells.h0000644000175000017500000000650313363422757015227 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. #pragma once // Spell_t is a base data object. // Holds the base game data for a spell // Note: the names for the spells are stored in spell_names[] array at index i, +31 if priest typedef struct { uint8_t level_required; uint8_t mana_required; uint8_t failure_chance; uint8_t exp_gain_for_learning; // 1/4 of exp gained for learning spell } Spell_t; extern Spell_t magic_spells[PLAYER_MAX_CLASSES - 1][31]; extern const char *spell_names[62]; int castSpellGetId(const char *prompt, int item_id, int &spell_id, int &spell_chance); bool spellDetectTreasureWithinVicinity(); bool spellDetectObjectsWithinVicinity(); bool spellDetectTrapsWithinVicinity(); bool spellDetectSecretDoorssWithinVicinity(); bool spellDetectInvisibleCreaturesWithinVicinity(); bool spellLightArea(int y, int x); bool spellDarkenArea(int y, int x); void spellMapCurrentArea(); bool spellIdentifyItem(); bool spellAggravateMonsters(int affect_distance); bool spellSurroundPlayerWithTraps(); bool spellSurroundPlayerWithDoors(); bool spellDestroyAdjacentDoorsTraps(); bool spellDetectMonsters(); void spellLightLine(int x, int y, int direction); void spellStarlite(int y, int x); bool spellDisarmAllInDirection(int y, int x, int direction); void spellFireBolt(int y, int x, int direction, int damage_hp, int spell_type, const std::string &spell_name); void spellFireBall(int y, int x, int direction, int damage_hp, int spell_type, const std::string &spell_name); void spellBreath(int y, int x, int monster_id, int damage_hp, int spell_type, const std::string &spell_name); bool spellRechargeItem(int number_of_charges); bool spellChangeMonsterHitPoints(int y, int x, int direction, int damage_hp); bool spellDrainLifeFromMonster(int y, int x, int direction); bool spellSpeedMonster(int y, int x, int direction, int speed); bool spellConfuseMonster(int y, int x, int direction); bool spellSleepMonster(int y, int x, int direction); bool spellWallToMud(int y, int x, int direction); bool spellDestroyDoorsTrapsInDirection(int y, int x, int direction); bool spellPolymorphMonster(int y, int x, int direction); bool spellBuildWall(int y, int x, int direction); bool spellCloneMonster(int y, int x, int direction); void spellTeleportAwayMonster(int monster_id, int distance_from_player); void spellTeleportPlayerTo(int y, int x); bool spellTeleportAwayMonsterInDirection(int y, int x, int direction); bool spellMassGenocide(); bool spellGenocide(); bool spellSpeedAllMonsters(int speed); bool spellSleepAllMonsters(); bool spellMassPolymorph(); bool spellDetectEvil(); bool spellChangePlayerHitPoints(int adjustment); void spellEarthquake(); void spellCreateFood(); bool spellDispelCreature(int creature_defense, int damage); bool spellTurnUndead(); void spellWardingGlyph(); void spellLoseSTR(); void spellLoseINT(); void spellLoseWIS(); void spellLoseDEX(); void spellLoseCON(); void spellLoseCHR(); void spellLoseEXP(int32_t adjustment); bool spellSlowPoison(); void spellDestroyArea(int y, int x); bool spellEnchantItem(int16_t &plusses, int16_t max_bonus_limit); bool spellRemoveCurseFromAllItems(); bool spellRestorePlayerLevels(); umoria-5.7.10+20181022/src/staves.h0000644000175000017500000000047613363422757015235 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. #pragma once void staffUse(); void wandAim(); umoria-5.7.10+20181022/src/ui_io.cpp0000644000175000017500000004071513363422757015367 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. // Terminal I/O code, uses the curses package #include #include "headers.h" #include "curses.h" static bool curses_on = false; // Spare window for saving the screen. -CJS- static WINDOW *save_screen; int eof_flag = 0; // Is used to signal EOF/HANGUP condition bool panic_save = false; // True if playing from a panic save // Set up the terminal into a suitable state -MRC- static void moriaTerminalInitialize() { raw(); // disable control characters. I.e. Ctrl-C does not work! // cbreak(); // use raw() instead as it disables Ctrl chars noecho(); // do not echo typed characters nonl(); // disable translation return/newline for detection of return key keypad(stdscr, false); // disable keypad input as we handle that ourselves // curs_set(0); // sets the appearance of the cursor based on the value of visibility #ifdef __APPLE__ set_escdelay(50); // default delay on macOS is 1 second, let's do something about that! #endif curses_on = true; } // initializes the terminal / curses routines bool terminalInitialize() { initscr(); // Check we have enough screen. -CJS- if (LINES < 24 || COLS < 80) { (void) printf("Screen too small for moria.\n"); return false; } save_screen = newwin(0, 0, 0, 0); if (save_screen == nullptr) { (void) printf("Out of memory in starting up curses.\n"); return false; } moriaTerminalInitialize(); (void) clear(); (void) refresh(); return true; } // Put the terminal in the original mode. -CJS- void terminalRestore() { if (!curses_on) { return; } // Dump any remaining buffer putQIO(); // this moves curses to bottom right corner int y = 0; int x = 0; getyx(stdscr, y, x); mvcur(y, x, LINES - 1, 0); // exit curses endwin(); (void) fflush(stdout); curses_on = false; } void terminalSaveScreen() { overwrite(stdscr, save_screen); } void terminalRestoreScreen() { overwrite(save_screen, stdscr); touchwin(stdscr); } void terminalBellSound() { putQIO(); // The player can turn off beeps if they find them annoying. if (config::options::error_beep_sound) { (void) write(1, "\007", 1); } } // Dump the IO buffer to terminal -RAK- void putQIO() { // Let inventoryExecuteCommand() know something has changed. screen_has_changed = true; (void) refresh(); } // Flush the buffer -RAK- void flushInputBuffer() { if (eof_flag != 0) { return; } while (checkForNonBlockingKeyPress(0)); } // Clears screen void clearScreen() { if (message_ready_to_print) { printMessage(CNIL); } (void) clear(); } void clearToBottom(int row) { (void) move(row, 0); clrtobot(); } // move cursor to a given y, x position void moveCursor(Coord_t coords) { (void) move(coords.y, coords.x); } void addChar(char ch, Coord_t coords) { if (mvaddch(coords.y, coords.x, ch) == ERR) { abort(); } } // Dump IO to buffer -RAK- void putString(const char *out_str, Coord_t coords) { // truncate the string, to make sure that it won't go past right edge of screen. if (coords.x > 79) { coords.x = 79; } vtype_t str = {'\0'}; (void) strncpy(str, out_str, (size_t) (79 - coords.x)); str[79 - coords.x] = '\0'; if (mvaddstr(coords.y, coords.x, str) == ERR) { abort(); } } // Outputs a line to a given y, x position -RAK- void putStringClearToEOL(const std::string &str, Coord_t coords) { if (coords.y == MSG_LINE && message_ready_to_print) { printMessage(CNIL); } (void) move(coords.y, coords.x); clrtoeol(); putString(str.c_str(), coords); } // Clears given line of text -RAK- void eraseLine(Coord_t coords) { if (coords.y == MSG_LINE && message_ready_to_print) { printMessage(CNIL); } (void) move(coords.y, coords.x); clrtoeol(); } // Moves the cursor to a given interpolated y, x position -RAK- void panelMoveCursor(Coord_t coords) { // Real coords convert to screen positions coords.y -= dg.panel.row_prt; coords.x -= dg.panel.col_prt; if (move(coords.y, coords.x) == ERR) { abort(); } } // Outputs a char to a given interpolated y, x position -RAK- // sign bit of a character used to indicate standout mode. -CJS void panelPutTile(char ch, Coord_t coords) { // Real coords convert to screen positions coords.y -= dg.panel.row_prt; coords.x -= dg.panel.col_prt; if (mvaddch(coords.y, coords.x, ch) == ERR) { abort(); } } static Coord_t currentCursorPosition() { int y, x; getyx(stdscr, y, x); return Coord_t{y, x}; } // messageLinePrintMessage will print a line of text to the message line (0,0). // first clearing the line of any text! void messageLinePrintMessage(std::string message) { // save current cursor position Coord_t coords = currentCursorPosition(); // move to beginning of message line, and clear it move(0, 0); clrtoeol(); // truncate message if it's too long! message.resize(79); addstr(message.c_str()); // restore cursor to old position move(coords.y, coords.x); } // deleteMessageLine will delete all text from the message line (0,0). // The current cursor position will be maintained. void messageLineClear() { // save current cursor position Coord_t coords = currentCursorPosition(); // move to beginning of message line, and clear it move(0, 0); clrtoeol(); // restore cursor to old position move(coords.y, coords.x); } // Outputs message to top line of screen // These messages are kept for later reference. void printMessage(const char *msg) { int new_len = 0; int old_len = 0; bool combine_messages = false; if (message_ready_to_print) { old_len = (int) strlen(messages[last_message_id]) + 1; // If the new message and the old message are short enough, // we want display them together on the same line. So we // don't flush the old message in this case. if (msg != nullptr) { new_len = (int) strlen(msg); } else { new_len = 0; } if ((msg == nullptr) || new_len + old_len + 2 >= 73) { // ensure that the complete -more- message is visible. if (old_len > 73) { old_len = 73; } putString(" -more-", Coord_t{MSG_LINE, old_len}); char in_char; do { in_char = getKeyInput(); } while ((in_char != ' ') && (in_char != ESCAPE) && (in_char != '\n') && (in_char != '\r')); } else { combine_messages = true; } } if (!combine_messages) { (void) move(MSG_LINE, 0); clrtoeol(); } // Make the null string a special case. -CJS- if (msg == nullptr) { message_ready_to_print = false; return; } game.command_count = 0; message_ready_to_print = true; // If the new message and the old message are short enough, // display them on the same line. if (combine_messages) { putString(msg, Coord_t{MSG_LINE, old_len + 2}); strcat(messages[last_message_id], " "); strcat(messages[last_message_id], msg); } else { messageLinePrintMessage(msg); last_message_id++; if (last_message_id >= MESSAGE_HISTORY_SIZE) { last_message_id = 0; } (void) strncpy(messages[last_message_id], msg, MORIA_MESSAGE_SIZE); messages[last_message_id][MORIA_MESSAGE_SIZE - 1] = '\0'; } } // Print a message so as not to interrupt a counted command. -CJS- void printMessageNoCommandInterrupt(const std::string &msg) { // Save command count value int i = game.command_count; printMessage(msg.c_str()); // Restore count value game.command_count = i; } // Returns a single character input from the terminal. -CJS- // // This silently consumes ^R to redraw the screen and reset the // terminal, so that this operation can always be performed at // any input prompt. getKeyInput() never returns ^R. char getKeyInput() { putQIO(); // Dump IO buffer game.command_count = 0; // Just to be safe -CJS- while (true) { int ch = getch(); // some machines may not sign extend. if (ch == EOF) { // avoid infinite loops while trying to call getKeyInput() for a -more- prompt. message_ready_to_print = false; eof_flag++; (void) refresh(); if (!game.character_generated || game.character_saved) { endGame(); } playerDisturb(1, 0); if (eof_flag > 100) { // just in case, to make sure that the process eventually dies panic_save = true; (void) strcpy(game.character_died_from, "(end of input: panic saved)"); if (!saveGame()) { (void) strcpy(game.character_died_from, "panic: unexpected eof"); game.character_is_dead = true; } endGame(); } return ESCAPE; } if (ch != CTRL_KEY('R')) { return (char) ch; } (void) wrefresh(curscr); moriaTerminalInitialize(); } } // Prompts (optional) and returns ord value of input char // Function returns false if is input bool getCommand(const std::string &prompt, char &command) { if (!prompt.empty()) { putStringClearToEOL(prompt, Coord_t{0, 0}); } command = getKeyInput(); messageLineClear(); return command != ESCAPE; } // Gets a string terminated by // Function returns false if is input bool getStringInput(char *in_str, Coord_t coords, int slen) { (void) move(coords.y, coords.x); for (int i = slen; i > 0; i--) { (void) addch(' '); } (void) move(coords.y, coords.x); int start_col = coords.x; int end_col = coords.x + slen - 1; if (end_col > 79) { end_col = 79; } char *p = in_str; bool flag = false; bool aborted = false; while (!flag && !aborted) { int key = getKeyInput(); switch (key) { case ESCAPE: aborted = true; break; case CTRL_KEY('J'): case CTRL_KEY('M'): flag = true; break; case DELETE: case CTRL_KEY('H'): if (coords.x > start_col) { coords.x--; putString(" ", coords); moveCursor(coords); *--p = '\0'; } break; default: if ((isprint(key) == 0) || coords.x > end_col) { terminalBellSound(); } else { mvaddch(coords.y, coords.x, (char) key); *p++ = (char) key; coords.x++; } break; } } if (aborted) { return false; } // Remove trailing blanks while (p > in_str && p[-1] == ' ') { p--; } *p = '\0'; return true; } // Used to verify a choice - user gets the chance to abort choice. -CJS- bool getInputConfirmation(const std::string &prompt) { putStringClearToEOL(prompt, Coord_t{0, 0}); int y, x; getyx(stdscr, y, x); if (x > 73) { (void) move(0, 73); } else if (y != 0) { // use `y` to prevent compiler warning. } (void) addstr(" [y/n]"); char input = ' '; while (input == ' ') { input = getKeyInput(); } messageLineClear(); return (input == 'Y' || input == 'y'); } // Pauses for user response before returning -RAK- void waitForContinueKey(int line_number) { putStringClearToEOL("[ press any key to continue ]", Coord_t{line_number, 23}); (void) getKeyInput(); eraseLine(Coord_t{line_number, 0}); } // Provides for a timeout on input. Does a non-blocking read, consuming the data if // any, and then returns 1 if data was read, zero otherwise. // // Porting: // // In systems without the select call, but with a sleep for fractional numbers of // seconds, one could sleep for the time and then check for input. // // In systems which can only sleep for whole number of seconds, you might sleep by // writing a lot of nulls to the terminal, and waiting for them to drain, or you // might hack a static accumulation of times to wait. When the accumulation reaches // a certain point, sleep for a second. There would need to be a way of resetting // the count, with a call made for commands like run or rest. bool checkForNonBlockingKeyPress(int microseconds) { #ifdef _WIN32 (void) microseconds; // Ugly non-blocking read...Ugh! -MRC- timeout(8); int result = getch(); timeout(-1); return result > 0; #else struct timeval tbuf{}; int ch; int smask; // Return true if a read on descriptor 1 will not block. tbuf.tv_sec = 0; tbuf.tv_usec = microseconds; smask = 1; // i.e. (1 << 0) if (select(1, (fd_set *) &smask, (fd_set *) 0, (fd_set *) 0, &tbuf) == 1) { ch = getch(); // check for EOF errors here, select sometimes works even when EOF if (ch == -1) { eof_flag++; return false; } return true; } return false; #endif } // Find a default user name from the system. void getDefaultPlayerName(char *buffer) { // Gotta have some name const char *defaultName = "X"; #ifdef _WIN32 unsigned long bufCharCount = PLAYER_NAME_SIZE; if (!GetUserName(buffer, &bufCharCount)) { (void)strcpy(buffer, defaultName); } #else char *p = getlogin(); if ((p != nullptr) && (p[0] != 0)) { (void) strcpy(buffer, p); } else { struct passwd *pwline = getpwuid((int) getuid()); if (pwline != nullptr) { (void) strcpy(buffer, pwline->pw_name); } } if (buffer[0] == 0) { (void) strcpy(buffer, defaultName); } #endif } #ifndef _WIN32 // On unix based systems we should expand `~` to the users home path, // otherwise on Windows we can ignore all of this. -MRC- // undefine these so that tfopen and topen will work #undef fopen #undef open // open a file just as does fopen, but allow a leading ~ to specify a home directory FILE *tfopen(const char *file, const char *mode) { char expanded[1024]; if (tilde(file, expanded)) { return (fopen(expanded, mode)); } errno = ENOENT; return nullptr; } // open a file just as does open, but expand a leading ~ into a home directory name int topen(const char *file, int flags, int mode) { char expanded[1024]; if (tilde(file, expanded)) { return (open(expanded, flags, mode)); } errno = ENOENT; return -1; } // expands a tilde at the beginning of a file name to a users home directory bool tilde(const char *file, char *expanded) { if (file == nullptr) { return false; } *expanded = '\0'; if (*file == '~') { char user[128]; struct passwd *pw = nullptr; int i = 0; user[0] = '\0'; file++; while (*file != '/' && i < (int) sizeof(user)) { user[i++] = *file++; } user[i] = '\0'; if (i == 0) { char *login = getlogin(); if (login != nullptr) { (void) strcpy(user, login); } else if ((pw = getpwuid(getuid())) == nullptr) { return false; } } if (pw == nullptr && (pw = getpwnam(user)) == nullptr) { return false; } (void) strcpy(expanded, pw->pw_dir); } (void) strcat(expanded, file); return true; } #endif // Check user permissions on Unix based systems, // or if on Windows just return. -MRC- bool checkFilePermissions() { #ifndef _WIN32 if (0 != setuid(getuid())) { perror("Can't set permissions correctly! Setuid call failed.\n"); return false; } if (0 != setgid(getgid())) { perror("Can't set permissions correctly! Setgid call failed.\n"); return false; } #endif return true; } umoria-5.7.10+20181022/src/scores.h0000644000175000017500000000213613363422757015221 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. #pragma once #include // HighScore_t is a score object used for saving to the high score file // This structure is 64 bytes in size typedef struct { int32_t points; int32_t birth_date; int16_t uid; int16_t mhp; int16_t chp; uint8_t dungeon_depth; uint8_t level; uint8_t deepest_dungeon_depth; uint8_t gender; uint8_t race; uint8_t character_class; char name[PLAYER_NAME_SIZE]; char died_from[25]; } HighScore_t; // Number of entries allowed in the score file. constexpr uint16_t MAX_HIGH_SCORE_ENTRIES = 1000; extern FILE *highscore_fp; // TODO: these are implemented in `game_save.cpp` so need moving. void saveHighScore(HighScore_t const &score); void readHighScore(HighScore_t &score); void recordNewHighScore(); void showScoresScreen(); int32_t playerCalculateTotalPoints(); umoria-5.7.10+20181022/src/game_objects.cpp0000644000175000017500000001320013363422757016672 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. // Game object management #include "headers.h" int16_t sorted_objects[MAX_DUNGEON_OBJECTS]; int16_t treasure_levels[TREASURE_MAX_LEVELS + 1]; // If too many objects on floor level, delete some of them-RAK- static void compactObjects() { printMessage("Compacting objects..."); int counter = 0; int current_distance = 66; while (counter <= 0) { for (int y = 0; y < dg.height; y++) { for (int x = 0; x < dg.width; x++) { if (dg.floor[y][x].treasure_id != 0 && coordDistanceBetween(Coord_t{y, x}, Coord_t{py.row, py.col}) > current_distance) { int chance; switch (treasure_list[dg.floor[y][x].treasure_id].category_id) { case TV_VIS_TRAP: chance = 15; break; case TV_INVIS_TRAP: case TV_RUBBLE: case TV_OPEN_DOOR: case TV_CLOSED_DOOR: chance = 5; break; case TV_UP_STAIR: case TV_DOWN_STAIR: case TV_STORE_DOOR: // Stairs, don't delete them. // Shop doors, don't delete them. chance = 0; break; case TV_SECRET_DOOR: // secret doors chance = 3; break; default: chance = 10; } if (randomNumber(100) <= chance) { (void) dungeonDeleteObject(Coord_t{y, x});; counter++; } } } } if (counter == 0) { current_distance -= 6; } } if (current_distance < 66) { drawDungeonPanel(); } } // Gives pointer to next free space -RAK- int popt() { if (current_treasure_id == LEVEL_MAX_OBJECTS) { compactObjects(); } return current_treasure_id++; } // Pushes a record back onto free space list -RAK- // `dungeonDeleteObject()` should always be called instead, unless the object // in question is not in the dungeon, e.g. in store1.c and files.c void pusht(uint8_t treasure_id) { if (treasure_id != current_treasure_id - 1) { treasure_list[treasure_id] = treasure_list[current_treasure_id - 1]; // must change the treasure_id in the cave of the object just moved for (int y = 0; y < dg.height; y++) { for (int x = 0; x < dg.width; x++) { if (dg.floor[y][x].treasure_id == current_treasure_id - 1) { dg.floor[y][x].treasure_id = treasure_id; } } } } current_treasure_id--; inventoryItemCopyTo(config::dungeon::objects::OBJ_NOTHING, treasure_list[current_treasure_id]); } // Item too large to fit in chest? -DJG- // Use a DungeonObject_t since the item has not yet been created static bool itemBiggerThanChest(DungeonObject_t const &obj) { switch (obj.category_id) { case TV_CHEST: case TV_BOW: case TV_POLEARM: case TV_HARD_ARMOR: case TV_SOFT_ARMOR: case TV_STAFF: return true; case TV_HAFTED: case TV_SWORD: case TV_DIGGING: return (obj.weight > 150); default: return false; } } // Returns the array number of a random object -RAK- int itemGetRandomObjectId(int level, bool must_be_small) { if (level == 0) { return randomNumber(treasure_levels[0]) - 1; } if (level >= TREASURE_MAX_LEVELS) { level = TREASURE_MAX_LEVELS; } else if (randomNumber(config::treasure::TREASURE_CHANCE_OF_GREAT_ITEM) == 1) { level = level * TREASURE_MAX_LEVELS / randomNumber(TREASURE_MAX_LEVELS) + 1; if (level > TREASURE_MAX_LEVELS) { level = TREASURE_MAX_LEVELS; } } int object_id; // This code has been added to make it slightly more likely to get the // higher level objects. Originally a uniform distribution over all // objects less than or equal to the dungeon level. This distribution // makes a level n objects occur approx 2/n% of the time on level n, // and 1/2n are 0th level. do { if (randomNumber(2) == 1) { object_id = randomNumber(treasure_levels[level]) - 1; } else { // Choose three objects, pick the highest level. object_id = randomNumber(treasure_levels[level]) - 1; int j = randomNumber(treasure_levels[level]) - 1; if (object_id < j) { object_id = j; } j = randomNumber(treasure_levels[level]) - 1; if (object_id < j) { object_id = j; } int foundLevel = game_objects[sorted_objects[object_id]].depth_first_found; if (foundLevel == 0) { object_id = randomNumber(treasure_levels[0]) - 1; } else { object_id = randomNumber(treasure_levels[foundLevel] - treasure_levels[foundLevel - 1]) - 1 + treasure_levels[foundLevel - 1]; } } } while (must_be_small && itemBiggerThanChest(game_objects[sorted_objects[object_id]])); return object_id; } umoria-5.7.10+20181022/src/player_quaff.cpp0000644000175000017500000003312613363422757016737 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. // Code for potions #include "headers.h" enum class PotionSpellTypes { strength = 1, weakness, restore_strength, intelligence, lose_intelligence, restore_intelligence, wisdom, lose_wisdom, restore_wisdom, charisma, ugliness, restore_charisma, cure_light_wounds, cure_serious_wounds, cure_critical_wounds, healing, constitution, gain_experience, sleep, blindness, confusion, poison, haste_self, slowness, // 25 not used dexterity = 26, restore_dexterity, restore_constitution, cure_blindness, cure_confusion, cure_poison, // 32 not used // 33 not used lose_experience = 34, salt_water, invulnerability, heroism, super_heroism, boldness, restore_life_levels, resist_heat, resist_cold, detect_invisible, slow_poison, neutralize_poison, restore_mana, infra_vision, }; static bool playerDrinkPotion(uint32_t flags, uint8_t item_type) { bool identified = false; while (flags != 0) { int potion_id = getAndClearFirstBit(flags) + 1; if (item_type == TV_POTION2) { potion_id += 32; } // Potions switch ((PotionSpellTypes) potion_id) { case PotionSpellTypes::strength: if (playerStatRandomIncrease(py_attrs::A_STR)) { printMessage("Wow! What bulging muscles!"); identified = true; } break; case PotionSpellTypes::weakness: spellLoseSTR(); identified = true; break; case PotionSpellTypes::restore_strength: if (playerStatRestore(py_attrs::A_STR)) { printMessage("You feel warm all over."); identified = true; } break; case PotionSpellTypes::intelligence: if (playerStatRandomIncrease(py_attrs::A_INT)) { printMessage("Aren't you brilliant!"); identified = true; } break; case PotionSpellTypes::lose_intelligence: spellLoseINT(); identified = true; break; case PotionSpellTypes::restore_intelligence: if (playerStatRestore(py_attrs::A_INT)) { printMessage("You have have a warm feeling."); identified = true; } break; case PotionSpellTypes::wisdom: if (playerStatRandomIncrease(py_attrs::A_WIS)) { printMessage("You suddenly have a profound thought!"); identified = true; } break; case PotionSpellTypes::lose_wisdom: spellLoseWIS(); identified = true; break; case PotionSpellTypes::restore_wisdom: if (playerStatRestore(py_attrs::A_WIS)) { printMessage("You feel your wisdom returning."); identified = true; } break; case PotionSpellTypes::charisma: if (playerStatRandomIncrease(py_attrs::A_CHR)) { printMessage("Gee, ain't you cute!"); identified = true; } break; case PotionSpellTypes::ugliness: spellLoseCHR(); identified = true; break; case PotionSpellTypes::restore_charisma: if (playerStatRestore(py_attrs::A_CHR)) { printMessage("You feel your looks returning."); identified = true; } break; case PotionSpellTypes::cure_light_wounds: identified = spellChangePlayerHitPoints(diceRoll(Dice_t{2, 7})); break; case PotionSpellTypes::cure_serious_wounds: identified = spellChangePlayerHitPoints(diceRoll(Dice_t{4, 7})); break; case PotionSpellTypes::cure_critical_wounds: identified = spellChangePlayerHitPoints(diceRoll(Dice_t{6, 7})); break; case PotionSpellTypes::healing: identified = spellChangePlayerHitPoints(1000); break; case PotionSpellTypes::constitution: if (playerStatRandomIncrease(py_attrs::A_CON)) { printMessage("You feel tingly for a moment."); identified = true; } break; case PotionSpellTypes::gain_experience: if (py.misc.exp < config::player::PLAYER_MAX_EXP) { auto exp = (uint32_t) ((py.misc.exp / 2) + 10); if (exp > 100000L) { exp = 100000L; } py.misc.exp += exp; printMessage("You feel more experienced."); displayCharacterExperience(); identified = true; } break; case PotionSpellTypes::sleep: if (!py.flags.free_action) { // paralysis must == 0, otherwise could not drink potion printMessage("You fall asleep."); py.flags.paralysis += randomNumber(4) + 4; identified = true; } break; case PotionSpellTypes::blindness: if (py.flags.blind == 0) { printMessage("You are covered by a veil of darkness."); identified = true; } py.flags.blind += randomNumber(100) + 100; break; case PotionSpellTypes::confusion: if (py.flags.confused == 0) { printMessage("Hey! This is good stuff! * Hick! *"); identified = true; } py.flags.confused += randomNumber(20) + 12; break; case PotionSpellTypes::poison: if (py.flags.poisoned == 0) { printMessage("You feel very sick."); identified = true; } py.flags.poisoned += randomNumber(15) + 10; break; case PotionSpellTypes::haste_self: if (py.flags.fast == 0) { identified = true; } py.flags.fast += randomNumber(25) + 15; break; case PotionSpellTypes::slowness: if (py.flags.slow == 0) { identified = true; } py.flags.slow += randomNumber(25) + 15; break; case PotionSpellTypes::dexterity: if (playerStatRandomIncrease(py_attrs::A_DEX)) { printMessage("You feel more limber!"); identified = true; } break; case PotionSpellTypes::restore_dexterity: if (playerStatRestore(py_attrs::A_DEX)) { printMessage("You feel less clumsy."); identified = true; } break; case PotionSpellTypes::restore_constitution: if (playerStatRestore(py_attrs::A_CON)) { printMessage("You feel your health returning!"); identified = true; } break; case PotionSpellTypes::cure_blindness: identified = playerCureBlindness(); break; case PotionSpellTypes::cure_confusion: identified = playerCureConfusion(); break; case PotionSpellTypes::cure_poison: identified = playerCurePoison(); break; // case 33: break; // this is no longer useful, now that there is a 'G'ain magic spells command case PotionSpellTypes::lose_experience: if (py.misc.exp > 0) { printMessage("You feel your memories fade."); // Lose between 1/5 and 2/5 of your experience int32_t exp = py.misc.exp / 5; if (py.misc.exp > MAX_SHORT) { auto scale = (int32_t) (MAX_LONG / py.misc.exp); exp += (randomNumber((int) scale) * py.misc.exp) / (scale * 5); } else { exp += randomNumber((int) py.misc.exp) / 5; } spellLoseEXP(exp); identified = true; } break; case PotionSpellTypes::salt_water: (void) playerCurePoison(); if (py.flags.food > 150) { py.flags.food = 150; } py.flags.paralysis = 4; printMessage("The potion makes you vomit!"); identified = true; break; case PotionSpellTypes::invulnerability: if (py.flags.invulnerability == 0) { identified = true; } py.flags.invulnerability += randomNumber(10) + 10; break; case PotionSpellTypes::heroism: if (py.flags.heroism == 0) { identified = true; } py.flags.heroism += randomNumber(25) + 25; break; case PotionSpellTypes::super_heroism: if (py.flags.super_heroism == 0) { identified = true; } py.flags.super_heroism += randomNumber(25) + 25; break; case PotionSpellTypes::boldness: identified = playerRemoveFear(); break; case PotionSpellTypes::restore_life_levels: identified = spellRestorePlayerLevels(); break; case PotionSpellTypes::resist_heat: if (py.flags.heat_resistance == 0) { identified = true; } py.flags.heat_resistance += randomNumber(10) + 10; break; case PotionSpellTypes::resist_cold: if (py.flags.cold_resistance == 0) { identified = true; } py.flags.cold_resistance += randomNumber(10) + 10; break; case PotionSpellTypes::detect_invisible: if (py.flags.detect_invisible == 0) { identified = true; } playerDetectInvisible(randomNumber(12) + 12); break; case PotionSpellTypes::slow_poison: identified = spellSlowPoison(); break; case PotionSpellTypes::neutralize_poison: identified = playerCurePoison(); break; case PotionSpellTypes::restore_mana: if (py.misc.current_mana < py.misc.mana) { py.misc.current_mana = py.misc.mana; printMessage("Your feel your head clear."); printCharacterCurrentMana(); identified = true; } break; case PotionSpellTypes::infra_vision: if (py.flags.timed_infra == 0) { printMessage("Your eyes begin to tingle."); identified = true; } py.flags.timed_infra += 100 + randomNumber(100); break; default: printMessage("Internal error in potion()"); break; } } return identified; } // Potions for the quaffing -RAK- void quaff() { game.player_free_turn = true; if (py.unique_inventory_items == 0) { printMessage("But you are not carrying anything."); return; } int item_pos_begin, item_pos_end; if (!inventoryFindRange(TV_POTION1, TV_POTION2, item_pos_begin, item_pos_end)) { printMessage("You are not carrying any potions."); return; } int item_id; if (!inventoryGetInputForItemId(item_id, "Quaff which potion?", item_pos_begin, item_pos_end, CNIL, CNIL)) { return; } game.player_free_turn = false; bool identified; Inventory_t *item = &inventory[item_id]; if (item->flags == 0) { printMessage("You feel less thirsty."); identified = true; } else { identified = playerDrinkPotion(item->flags, item->category_id); } if (identified) { if (!itemSetColorlessAsIdentified(item->category_id, item->sub_category_id, item->identification)) { // round half-way case up py.misc.exp += (item->depth_first_found + (py.misc.level >> 1)) / py.misc.level; displayCharacterExperience(); itemIdentify(inventory[item_id], item_id); item = &inventory[item_id]; } } else if (!itemSetColorlessAsIdentified(item->category_id, item->sub_category_id, item->identification)) { itemSetAsTried(*item); } playerIngestFood(item->misc_use); itemTypeRemainingCountDescription(item_id); inventoryDestroyItem(item_id); } umoria-5.7.10+20181022/src/game.h0000644000175000017500000000773513363422757014646 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. #pragma once typedef struct { uint32_t magic_seed = 0; // Seed for initializing magic items (Potions, Wands, Staves, Scrolls, etc.) uint32_t town_seed = 0; // Seed for town generation bool character_generated = false; // Don't save score until character generation is finished bool character_saved = false; // Prevents save on kill after saving a character bool character_is_dead = false; // `true` if character has died bool total_winner = false; // Character beat the Balrog bool player_free_turn = false; // Player has a free turn, so do not move creatures bool to_be_wizard = false; // Player requests to be Wizard - used during startup, when -w option used bool wizard_mode = false; // Character is a Wizard when true int16_t noscore = 0; // Don't save a score for this game. -CJS- bool use_last_direction = false; // `true` when repeat commands should use last known direction char doing_inventory_command = 0; // Track inventory commands -CJS- char last_command = ' '; // Save of the previous player command int command_count = 0; // How many times to repeat a specific command -CJS- vtype_t character_died_from = {'\0'}; // What the character died from: starvation, Bat, etc. } Game_t; constexpr uint8_t TREASURE_MAX_LEVELS = 50; // Maximum level of magic in dungeon // Note that the following constants are all related, if you change one, you // must also change all succeeding ones. // Also, player_base_provisions[] and store_choices[] may also have to be changed. constexpr uint16_t MAX_OBJECTS_IN_GAME = 420; // Number of objects for universe constexpr uint16_t MAX_DUNGEON_OBJECTS = 344; // Number of dungeon objects constexpr uint16_t OBJECT_IDENT_SIZE = 448; // 7*64, see object_offset() in desc.cpp, could be MAX_OBJECTS o_o() rewritten // With LEVEL_MAX_OBJECTS set to 150, it's possible to get compacting // objects during level generation, although it is extremely rare. constexpr uint8_t LEVEL_MAX_OBJECTS = 175; // Max objects per level // definitions for the pseudo-normal distribution generation constexpr uint16_t NORMAL_TABLE_SIZE = 256; constexpr uint8_t NORMAL_TABLE_SD = 64; // the standard deviation for the table extern Game_t game; extern int eof_flag; extern bool panic_save; extern int16_t sorted_objects[MAX_DUNGEON_OBJECTS]; extern int16_t current_treasure_id; extern uint16_t normal_table[NORMAL_TABLE_SIZE]; extern int16_t treasure_levels[TREASURE_MAX_LEVELS + 1]; void seedsInitialize(uint32_t seed); void seedSet(uint32_t seed); void seedResetToOldSeed(); int randomNumber(int max); int randomNumberNormalDistribution(int mean, int standard); void setGameOptions(); bool validGameVersion(uint8_t major, uint8_t minor, uint8_t patch); bool isCurrentGameVersion(uint8_t major, uint8_t minor, uint8_t patch); int getRandomDirection(); bool getDirectionWithMemory(char *prompt, int &direction); bool getAllDirections(const char *prompt, int &direction); void exitProgram(); // game object management int popt(); void pusht(uint8_t treasure_id); int itemGetRandomObjectId(int level, bool must_be_small); // game files bool initializeScoreFile(); void displaySplashScreen(); void displayTextHelpFile(const std::string &filename); void displayDeathFile(const std::string &filename); void outputRandomLevelObjectsToFile(); bool outputPlayerCharacterToFile(char *filename); // game death void endGame(); // save/load bool saveGame(); bool loadGame(bool &generate); void setFileptr(FILE *file); // game_run.cpp // (includes the playDungeon() main game loop) void startMoria(int seed, bool start_new_game, bool use_roguelike_keys); umoria-5.7.10+20181022/src/game_run.cpp0000644000175000017500000021603613363422757016061 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. // Run the game: the main loop #include "headers.h" static void playDungeon(); static void initializeCharacterInventory(); static void initializeMonsterLevels(); static void initializeTreasureLevels(); static void priceAdjust(); static char originalCommands(char command); static void doCommand(char command); static bool validCountCommand(char command); static void playerRegenerateHitPoints(int percent); static void playerRegenerateMana(int percent); static bool itemEnchanted(Inventory_t const &item); static void examineBook(); static void dungeonGoUpLevel(); static void dungeonGoDownLevel(); static void dungeonJamDoor(); static void inventoryRefillLamp(); void startMoria(int seed, bool start_new_game, bool use_roguelike_keys) { priceAdjust(); // Show the game splash screen displaySplashScreen(); // Grab a random seed from the clock seedsInitialize(static_cast(seed)); // Init monster and treasure levels for allocate initializeMonsterLevels(); initializeTreasureLevels(); // Init the store inventories storeInitializeOwners(); // NOTE: base exp levels need initializing before loading a game playerInitializeBaseExperienceLevels(); // initialize some player fields - may or may not be needed -MRC- py.flags.spells_learnt = 0; py.flags.spells_worked = 0; py.flags.spells_forgotten = 0; // If -n is not passed, the calling routine will know // save file name, hence, this code is not necessary. // This restoration of a saved character may get ONLY the monster memory. In // this case, `loadGame()` returns false. It may also resurrect a dead character // (if you are the wizard). In this case, it returns true, but also sets the // parameter "generate" to true, as it does not recover any cave details. bool result = false; bool generate = false; if (!start_new_game && (access(config::files::save_game.c_str(), 0) == 0) && loadGame(generate)) { result = true; } // Executing after game load to override saved game settings if (use_roguelike_keys) { config::options::use_roguelike_keys = true; } // enter wizard mode before showing the character display, but must wait // until after loadGame() in case it was just a resurrection if (game.to_be_wizard) { if (!enterWizardMode()) { endGame(); } } if (result) { changeCharacterName(); // could be restoring a dead character after a signal or HANGUP if (py.misc.current_hp < 0) { game.character_is_dead = true; } } else { // Create character characterCreate(); py.misc.date_of_birth = getCurrentUnixTime(); initializeCharacterInventory(); py.flags.food = 7500; py.flags.food_digested = 2; // Spell and Mana based on class: Mage or Clerical realm. if (classes[py.misc.class_id].class_to_use_mage_spells == config::spells::SPELL_TYPE_MAGE) { clearScreen(); // makes spell list easier to read playerCalculateAllowedSpellsCount(py_attrs::A_INT); playerGainMana(py_attrs::A_INT); } else if (classes[py.misc.class_id].class_to_use_mage_spells == config::spells::SPELL_TYPE_PRIEST) { playerCalculateAllowedSpellsCount(py_attrs::A_WIS); clearScreen(); // force out the 'learn prayer' message playerGainMana(py_attrs::A_WIS); } // Set some default values -MRC- py.temporary_light_only = false; py.weapon_is_heavy = false; py.pack_heaviness = 0; // prevent ^c quit from entering score into scoreboard, // and prevent signal from creating panic save until this // point, all info needed for save file is now valid. game.character_generated = true; generate = true; } magicInitializeItemNames(); // // Begin the game // clearScreen(); printCharacterStatsBlock(); if (generate) { generateCave(); } // Loop till dead, or exit while (!game.character_is_dead) { // Dungeon logic playDungeon(); // check for eof here, see getKeyInput() in io.c // eof can occur if the process gets a HANGUP signal if (eof_flag != 0) { (void) strcpy(game.character_died_from, "(end of input: saved)"); if (!saveGame()) { (void) strcpy(game.character_died_from, "unexpected eof"); } // should not reach here, but if we do, this guarantees exit game.character_is_dead = true; } // New level if not dead if (!game.character_is_dead) { generateCave(); } } // Character gets buried. endGame(); } // Init players with some belongings -RAK- static void initializeCharacterInventory() { Inventory_t item{}; // this is needed for bash to work right, it can't hurt anyway for (auto &entry : inventory) { inventoryItemCopyTo(config::dungeon::objects::OBJ_NOTHING, entry); } for (auto item_id : class_base_provisions[py.misc.class_id]) { inventoryItemCopyTo(item_id, item); // this makes it spellItemIdentifyAndRemoveRandomInscription and itemSetAsIdentified itemIdentifyAsStoreBought(item); // must set this bit to display to_hit/to_damage for stiletto if (item.category_id == TV_SWORD) { item.identification |= config::identification::ID_SHOW_HIT_DAM; } (void) inventoryCarryItem(item); } // weird place for it, but why not? for (uint8_t &id : py.flags.spells_learned_order) { id = 99; } } // Initializes M_LEVEL array for use with PLACE_MONSTER -RAK- static void initializeMonsterLevels() { for (auto &level : monster_levels) { level = 0; } for (int i = 0; i < MON_MAX_CREATURES - config::monsters::MON_ENDGAME_MONSTERS; i++) { monster_levels[creatures_list[i].level]++; } for (int i = 1; i <= MON_MAX_LEVELS; i++) { monster_levels[i] += monster_levels[i - 1]; } } // Initializes T_LEVEL array for use with PLACE_OBJECT -RAK- static void initializeTreasureLevels() { for (auto &level : treasure_levels) { level = 0; } for (int i = 0; i < MAX_DUNGEON_OBJECTS; i++) { treasure_levels[game_objects[i].depth_first_found]++; } for (int i = 1; i <= TREASURE_MAX_LEVELS; i++) { treasure_levels[i] += treasure_levels[i - 1]; } // now produce an array with object indexes sorted by level, // by using the info in treasure_levels, this is an O(n) sort! // this is not a stable sort, but that does not matter int indexes[TREASURE_MAX_LEVELS + 1] = {}; for (auto &i : indexes) { i = 1; } for (int i = 0; i < MAX_DUNGEON_OBJECTS; i++) { int level = game_objects[i].depth_first_found; int object_id = treasure_levels[level] - indexes[level]; sorted_objects[object_id] = (int16_t) i; indexes[level]++; } } // Adjust prices of objects -RAK- static void priceAdjust() { #if (COST_ADJUSTMENT != 100) // round half-way cases up for (auto &item : game_objects) { item.cost = ((item.cost * COST_ADJUSTMENT) + 50) / 100; } #endif } // Moria game module -RAK- // The code in this section has gone through many revisions, and // some of it could stand some more hard work. -RAK- // It has had a bit more hard work. -CJS- // Reset flags and initialize variables static void resetDungeonFlags() { game.command_count = 0; dg.generate_new_level = false; py.running_tracker = 0; teleport_player = false; monster_multiply_total = 0; dg.floor[py.row][py.col].creature_id = 1; } // Check light status for dungeon setup static void playerInitializePlayerLight() { py.carrying_light = (inventory[player_equipment::EQUIPMENT_LIGHT].misc_use > 0); } // Check for a maximum level static void playerUpdateMaxDungeonDepth() { if (dg.current_level > py.misc.max_dungeon_depth) { py.misc.max_dungeon_depth = (uint16_t) dg.current_level; } } // Check light status static void playerUpdateLightStatus() { Inventory_t &item = inventory[player_equipment::EQUIPMENT_LIGHT]; if (py.carrying_light) { if (item.misc_use > 0) { item.misc_use--; if (item.misc_use == 0) { py.carrying_light = false; printMessage("Your light has gone out!"); playerDisturb(0, 1); // unlight creatures updateMonsters(false); } else if (item.misc_use < 40 && randomNumber(5) == 1 && py.flags.blind < 1) { playerDisturb(0, 0); printMessage("Your light is growing faint."); } } else { py.carrying_light = false; playerDisturb(0, 1); // unlight creatures updateMonsters(false); } } else if (item.misc_use > 0) { item.misc_use--; py.carrying_light = true; playerDisturb(0, 1); // light creatures updateMonsters(false); } } static void playerActivateHeroism() { py.flags.status |= config::player::status::PY_HERO; playerDisturb(0, 0); py.misc.max_hp += 10; py.misc.current_hp += 10; py.misc.bth += 12; py.misc.bth_with_bows += 12; printMessage("You feel like a HERO!"); printCharacterMaxHitPoints(); printCharacterCurrentHitPoints(); } static void playerDisableHeroism() { py.flags.status &= ~config::player::status::PY_HERO; playerDisturb(0, 0); py.misc.max_hp -= 10; if (py.misc.current_hp > py.misc.max_hp) { py.misc.current_hp = py.misc.max_hp; py.misc.current_hp_fraction = 0; printCharacterCurrentHitPoints(); } py.misc.bth -= 12; py.misc.bth_with_bows -= 12; printMessage("The heroism wears off."); printCharacterMaxHitPoints(); } static void playerActivateSuperHeroism() { py.flags.status |= config::player::status::PY_SHERO; playerDisturb(0, 0); py.misc.max_hp += 20; py.misc.current_hp += 20; py.misc.bth += 24; py.misc.bth_with_bows += 24; printMessage("You feel like a SUPER HERO!"); printCharacterMaxHitPoints(); printCharacterCurrentHitPoints(); } static void playerDisableSuperHeroism() { py.flags.status &= ~config::player::status::PY_SHERO; playerDisturb(0, 0); py.misc.max_hp -= 20; if (py.misc.current_hp > py.misc.max_hp) { py.misc.current_hp = py.misc.max_hp; py.misc.current_hp_fraction = 0; printCharacterCurrentHitPoints(); } py.misc.bth -= 24; py.misc.bth_with_bows -= 24; printMessage("The super heroism wears off."); printCharacterMaxHitPoints(); } static void playerUpdateHeroStatus() { // Heroism if (py.flags.heroism > 0) { if ((py.flags.status & config::player::status::PY_HERO) == 0) { playerActivateHeroism(); } py.flags.heroism--; if (py.flags.heroism == 0) { playerDisableHeroism(); } } // Super Heroism if (py.flags.super_heroism > 0) { if ((py.flags.status & config::player::status::PY_SHERO) == 0) { playerActivateSuperHeroism(); } py.flags.super_heroism--; if (py.flags.super_heroism == 0) { playerDisableSuperHeroism(); } } } static int playerFoodConsumption() { // Regenerate hp and mana int regen_amount = config::player::PLAYER_REGEN_NORMAL; if (py.flags.food < config::player::PLAYER_FOOD_ALERT) { if (py.flags.food < config::player::PLAYER_FOOD_WEAK) { if (py.flags.food < 0) { regen_amount = 0; } else if (py.flags.food < config::player::PLAYER_FOOD_FAINT) { regen_amount = config::player::PLAYER_REGEN_FAINT; } else if (py.flags.food < config::player::PLAYER_FOOD_WEAK) { regen_amount = config::player::PLAYER_REGEN_WEAK; } if ((py.flags.status & config::player::status::PY_WEAK) == 0) { py.flags.status |= config::player::status::PY_WEAK; printMessage("You are getting weak from hunger."); playerDisturb(0, 0); printCharacterHungerStatus(); } if (py.flags.food < config::player::PLAYER_FOOD_FAINT && randomNumber(8) == 1) { py.flags.paralysis += randomNumber(5); printMessage("You faint from the lack of food."); playerDisturb(1, 0); } } else if ((py.flags.status & config::player::status::PY_HUNGRY) == 0) { py.flags.status |= config::player::status::PY_HUNGRY; printMessage("You are getting hungry."); playerDisturb(0, 0); printCharacterHungerStatus(); } } // Food consumption // Note: Sped up characters really burn up the food! if (py.flags.speed < 0) { py.flags.food -= py.flags.speed * py.flags.speed; } py.flags.food -= py.flags.food_digested; if (py.flags.food < 0) { playerTakesHit(-py.flags.food / 16, "starvation"); // -CJS- playerDisturb(1, 0); } return regen_amount; } static void playerUpdateRegeneration(int amount) { if (py.flags.regenerate_hp) { amount = amount * 3 / 2; } if (((py.flags.status & config::player::status::PY_SEARCH) != 0u) || py.flags.rest != 0) { amount = amount * 2; } if (py.flags.poisoned < 1 && py.misc.current_hp < py.misc.max_hp) { playerRegenerateHitPoints(amount); } if (py.misc.current_mana < py.misc.mana) { playerRegenerateMana(amount); } } static void playerUpdateBlindness() { if (py.flags.blind <= 0) { return; } if ((py.flags.status & config::player::status::PY_BLIND) == 0) { py.flags.status |= config::player::status::PY_BLIND; drawDungeonPanel(); printCharacterBlindStatus(); playerDisturb(0, 1); // unlight creatures updateMonsters(false); } py.flags.blind--; if (py.flags.blind == 0) { py.flags.status &= ~config::player::status::PY_BLIND; printCharacterBlindStatus(); drawDungeonPanel(); playerDisturb(0, 1); // light creatures updateMonsters(false); printMessage("The veil of darkness lifts."); } } static void playerUpdateConfusion() { if (py.flags.confused <= 0) { return; } if ((py.flags.status & config::player::status::PY_CONFUSED) == 0) { py.flags.status |= config::player::status::PY_CONFUSED; printCharacterConfusedState(); } py.flags.confused--; if (py.flags.confused == 0) { py.flags.status &= ~config::player::status::PY_CONFUSED; printCharacterConfusedState(); printMessage("You feel less confused now."); if (py.flags.rest != 0) { playerRestOff(); } } } static void playerUpdateFearState() { if (py.flags.afraid <= 0) { return; } if ((py.flags.status & config::player::status::PY_FEAR) == 0) { if (py.flags.super_heroism + py.flags.heroism > 0) { py.flags.afraid = 0; } else { py.flags.status |= config::player::status::PY_FEAR; printCharacterFearState(); } } else if (py.flags.super_heroism + py.flags.heroism > 0) { py.flags.afraid = 1; } py.flags.afraid--; if (py.flags.afraid == 0) { py.flags.status &= ~config::player::status::PY_FEAR; printCharacterFearState(); printMessage("You feel bolder now."); playerDisturb(0, 0); } } static void playerUpdatePoisonedState() { if (py.flags.poisoned <= 0) { return; } if ((py.flags.status & config::player::status::PY_POISONED) == 0) { py.flags.status |= config::player::status::PY_POISONED; printCharacterPoisonedState(); } py.flags.poisoned--; if (py.flags.poisoned == 0) { py.flags.status &= ~config::player::status::PY_POISONED; printCharacterPoisonedState(); printMessage("You feel better."); playerDisturb(0, 0); return; } int damage; switch (playerStatAdjustmentConstitution()) { case -4: damage = 4; break; case -3: case -2: damage = 3; break; case -1: damage = 2; break; case 0: damage = 1; break; case 1: case 2: case 3: damage = ((dg.game_turn % 2) == 0 ? 1 : 0); break; case 4: case 5: damage = ((dg.game_turn % 3) == 0 ? 1 : 0); break; case 6: damage = ((dg.game_turn % 4) == 0 ? 1 : 0); break; default: damage = 0; break; } playerTakesHit(damage, "poison"); playerDisturb(1, 0); } static void playerUpdateFastness() { if (py.flags.fast <= 0) { return; } if ((py.flags.status & config::player::status::PY_FAST) == 0) { py.flags.status |= config::player::status::PY_FAST; playerChangeSpeed(-1); printMessage("You feel yourself moving faster."); playerDisturb(0, 0); } py.flags.fast--; if (py.flags.fast == 0) { py.flags.status &= ~config::player::status::PY_FAST; playerChangeSpeed(1); printMessage("You feel yourself slow down."); playerDisturb(0, 0); } } static void playerUpdateSlowness() { if (py.flags.slow <= 0) { return; } if ((py.flags.status & config::player::status::PY_SLOW) == 0) { py.flags.status |= config::player::status::PY_SLOW; playerChangeSpeed(1); printMessage("You feel yourself moving slower."); playerDisturb(0, 0); } py.flags.slow--; if (py.flags.slow == 0) { py.flags.status &= ~config::player::status::PY_SLOW; playerChangeSpeed(-1); printMessage("You feel yourself speed up."); playerDisturb(0, 0); } } static void playerUpdateSpeed() { playerUpdateFastness(); playerUpdateSlowness(); } // Resting is over? static void playerUpdateRestingState() { if (py.flags.rest > 0) { py.flags.rest--; // Resting over if (py.flags.rest == 0) { playerRestOff(); } } else if (py.flags.rest < 0) { // Rest until reach max mana and max hit points. py.flags.rest++; if ((py.misc.current_hp == py.misc.max_hp && py.misc.current_mana == py.misc.mana) || py.flags.rest == 0) { playerRestOff(); } } } // Hallucinating? (Random characters appear!) static void playerUpdateHallucination() { if (py.flags.image <= 0) { return; } playerEndRunning(); py.flags.image--; if (py.flags.image == 0) { // Used to draw entire screen! -CJS- drawDungeonPanel(); } } static void playerUpdateParalysis() { if (py.flags.paralysis <= 0) { return; } // when paralysis true, you can not see any movement that occurs py.flags.paralysis--; playerDisturb(1, 0); } // Protection from evil counter static void playerUpdateEvilProtection() { if (py.flags.protect_evil <= 0) { return; } py.flags.protect_evil--; if (py.flags.protect_evil == 0) { printMessage("You no longer feel safe from evil."); } } static void playerUpdateInvulnerability() { if (py.flags.invulnerability <= 0) { return; } if ((py.flags.status & config::player::status::PY_INVULN) == 0) { py.flags.status |= config::player::status::PY_INVULN; playerDisturb(0, 0); py.misc.ac += 100; py.misc.display_ac += 100; printCharacterCurrentArmorClass(); printMessage("Your skin turns into steel!"); } py.flags.invulnerability--; if (py.flags.invulnerability == 0) { py.flags.status &= ~config::player::status::PY_INVULN; playerDisturb(0, 0); py.misc.ac -= 100; py.misc.display_ac -= 100; printCharacterCurrentArmorClass(); printMessage("Your skin returns to normal."); } } static void playerUpdateBlessedness() { if (py.flags.blessed <= 0) { return; } if ((py.flags.status & config::player::status::PY_BLESSED) == 0) { py.flags.status |= config::player::status::PY_BLESSED; playerDisturb(0, 0); py.misc.bth += 5; py.misc.bth_with_bows += 5; py.misc.ac += 2; py.misc.display_ac += 2; printMessage("You feel righteous!"); printCharacterCurrentArmorClass(); } py.flags.blessed--; if (py.flags.blessed == 0) { py.flags.status &= ~config::player::status::PY_BLESSED; playerDisturb(0, 0); py.misc.bth -= 5; py.misc.bth_with_bows -= 5; py.misc.ac -= 2; py.misc.display_ac -= 2; printMessage("The prayer has expired."); printCharacterCurrentArmorClass(); } } // Resist Heat static void playerUpdateHeatResistance() { if (py.flags.heat_resistance <= 0) { return; } py.flags.heat_resistance--; if (py.flags.heat_resistance == 0) { printMessage("You no longer feel safe from flame."); } } static void playerUpdateColdResistance() { if (py.flags.cold_resistance <= 0) { return; } py.flags.cold_resistance--; if (py.flags.cold_resistance == 0) { printMessage("You no longer feel safe from cold."); } } static void playerUpdateDetectInvisible() { if (py.flags.detect_invisible <= 0) { return; } if ((py.flags.status & config::player::status::PY_DET_INV) == 0) { py.flags.status |= config::player::status::PY_DET_INV; py.flags.see_invisible = true; // light but don't move creatures updateMonsters(false); } py.flags.detect_invisible--; if (py.flags.detect_invisible == 0) { py.flags.status &= ~config::player::status::PY_DET_INV; // may still be able to see_invisible if wearing magic item playerRecalculateBonuses(); // unlight but don't move creatures updateMonsters(false); } } // Timed infra-vision static void playerUpdateInfraVision() { if (py.flags.timed_infra <= 0) { return; } if ((py.flags.status & config::player::status::PY_TIM_INFRA) == 0) { py.flags.status |= config::player::status::PY_TIM_INFRA; py.flags.see_infra++; // light but don't move creatures updateMonsters(false); } py.flags.timed_infra--; if (py.flags.timed_infra == 0) { py.flags.status &= ~config::player::status::PY_TIM_INFRA; py.flags.see_infra--; // unlight but don't move creatures updateMonsters(false); } } // Word-of-Recall Note: Word-of-Recall is a delayed action static void playerUpdateWordOfRecall() { if (py.flags.word_of_recall <= 0) { return; } if (py.flags.word_of_recall == 1) { dg.generate_new_level = true; py.flags.paralysis++; py.flags.word_of_recall = 0; if (dg.current_level > 0) { dg.current_level = 0; printMessage("You feel yourself yanked upwards!"); } else if (py.misc.max_dungeon_depth != 0) { dg.current_level = py.misc.max_dungeon_depth; printMessage("You feel yourself yanked downwards!"); } } else { py.flags.word_of_recall--; } } static void playerUpdateStatusFlags() { if ((py.flags.status & config::player::status::PY_SPEED) != 0u) { py.flags.status &= ~config::player::status::PY_SPEED; printCharacterSpeed(); } if (((py.flags.status & config::player::status::PY_PARALYSED) != 0u) && py.flags.paralysis < 1) { printCharacterMovementState(); py.flags.status &= ~config::player::status::PY_PARALYSED; } else if (py.flags.paralysis > 0) { printCharacterMovementState(); py.flags.status |= config::player::status::PY_PARALYSED; } else if (py.flags.rest != 0) { printCharacterMovementState(); } if ((py.flags.status & config::player::status::PY_ARMOR) != 0) { printCharacterCurrentArmorClass(); py.flags.status &= ~config::player::status::PY_ARMOR; } if ((py.flags.status & config::player::status::PY_STATS) != 0) { for (int n = 0; n < 6; n++) { if (((config::player::status::PY_STR << n) & py.flags.status) != 0u) { displayCharacterStats(n); } } py.flags.status &= ~config::player::status::PY_STATS; } if ((py.flags.status & config::player::status::PY_HP) != 0u) { printCharacterMaxHitPoints(); printCharacterCurrentHitPoints(); py.flags.status &= ~config::player::status::PY_HP; } if ((py.flags.status & config::player::status::PY_MANA) != 0u) { printCharacterCurrentMana(); py.flags.status &= ~config::player::status::PY_MANA; } } // Allow for a slim chance of detect enchantment -CJS- static void playerDetectEnchantment() { for (int i = 0; i < PLAYER_INVENTORY_SIZE; i++) { if (i == py.unique_inventory_items) { i = 22; } Inventory_t &item = inventory[i]; // if in inventory, succeed 1 out of 50 times, // if in equipment list, success 1 out of 10 times int chance = (i < 22 ? 50 : 10); if (item.category_id != TV_NOTHING && itemEnchanted(item) && randomNumber(chance) == 1) { // extern const char *describe_use(int); // FIXME: Why here? We have it in externs. vtype_t tmp_str = {'\0'}; (void) sprintf(tmp_str, "There's something about what you are %s...", playerItemWearingDescription(i)); playerDisturb(0, 0); printMessage(tmp_str); itemAppendToInscription(item, config::identification::ID_MAGIK); } } } static int getCommandRepeatCount(char &lastInputCommand) { putStringClearToEOL("Repeat count:", Coord_t{0, 0}); if (lastInputCommand == '#') { lastInputCommand = '0'; } char text_buffer[8]; int repeat_count = 0; while (true) { if (lastInputCommand == DELETE || lastInputCommand == CTRL_KEY('H')) { repeat_count /= 10; (void) sprintf(text_buffer, "%d", (int16_t) repeat_count); putStringClearToEOL(text_buffer, Coord_t{0, 14}); } else if (lastInputCommand >= '0' && lastInputCommand <= '9') { if (repeat_count > 99) { terminalBellSound(); } else { repeat_count = repeat_count * 10 + lastInputCommand - '0'; (void) sprintf(text_buffer, "%d", repeat_count); putStringClearToEOL(text_buffer, Coord_t{0, 14}); } } else { break; } lastInputCommand = getKeyInput(); } if (repeat_count == 0) { repeat_count = 99; (void) sprintf(text_buffer, "%d", repeat_count); putStringClearToEOL(text_buffer, Coord_t{0, 14}); } // a special hack to allow numbers as commands if (lastInputCommand == ' ') { putStringClearToEOL("Command:", Coord_t{0, 20}); lastInputCommand = getKeyInput(); } return repeat_count; } static char parseAlternateCtrlInput(char lastInputCommand) { if (game.command_count > 0) { printCharacterMovementState(); } if (getCommand("Control-", lastInputCommand)) { if (lastInputCommand >= 'A' && lastInputCommand <= 'Z') { lastInputCommand -= 'A' - 1; } else if (lastInputCommand >= 'a' && lastInputCommand <= 'z') { lastInputCommand -= 'a' - 1; } else { lastInputCommand = ' '; printMessage("Type ^ for a control char"); } } else { lastInputCommand = ' '; } return lastInputCommand; } // Accept a command and execute it static void executeInputCommands(char &command, int &find_count) { char lastInputCommand = command; // Accept a command and execute it do { if ((py.flags.status & config::player::status::PY_REPEAT) != 0u) { printCharacterMovementState(); } game.use_last_direction = false; game.player_free_turn = false; if (py.running_tracker != 0) { playerRunAndFind(); find_count -= 1; if (find_count == 0) { playerEndRunning(); } putQIO(); continue; } if (game.doing_inventory_command != 0) { inventoryExecuteCommand(game.doing_inventory_command); continue; } // move the cursor to the players character panelMoveCursor(Coord_t{py.row, py.col}); message_ready_to_print = false; if (game.command_count > 0) { game.use_last_direction = true; } else { lastInputCommand = getKeyInput(); // Get a count for a command. int repeat_count = 0; if ((config::options::use_roguelike_keys && lastInputCommand >= '0' && lastInputCommand <= '9') || (!config::options::use_roguelike_keys && lastInputCommand == '#')) { repeat_count = getCommandRepeatCount(lastInputCommand); } // Another way of typing control codes -CJS- if (lastInputCommand == '^') { lastInputCommand = parseAlternateCtrlInput(lastInputCommand); } // move cursor to player char again, in case it moved panelMoveCursor(Coord_t{py.row, py.col}); // Commands are always converted to rogue form. -CJS- if (!config::options::use_roguelike_keys) { lastInputCommand = originalCommands(lastInputCommand); } if (repeat_count > 0) { if (!validCountCommand(lastInputCommand)) { game.player_free_turn = true; lastInputCommand = ' '; printMessage("Invalid command with a count."); } else { game.command_count = repeat_count; printCharacterMovementState(); } } } // Flash the message line. messageLineClear(); panelMoveCursor(Coord_t{py.row, py.col}); putQIO(); doCommand(lastInputCommand); // Find is counted differently, as the command changes. if (py.running_tracker != 0) { find_count = game.command_count - 1; game.command_count = 0; } else if (game.player_free_turn) { game.command_count = 0; } else if (game.command_count != 0) { game.command_count--; } } while (game.player_free_turn && !dg.generate_new_level && (eof_flag == 0)); command = lastInputCommand; } static char originalCommands(char command) { int direction; switch (command) { case CTRL_KEY('K'): // ^K = exit command = 'Q'; break; case CTRL_KEY('J'): case CTRL_KEY('M'): command = '+'; break; case CTRL_KEY('P'): // ^P = repeat case CTRL_KEY('W'): // ^W = password case CTRL_KEY('X'): // ^X = save case CTRL_KEY('V'): // ^V = view license case ' ': case '!': case '$': break; case '.': if (getDirectionWithMemory(CNIL, direction)) { switch (direction) { case 1: command = 'B'; break; case 2: command = 'J'; break; case 3: command = 'N'; break; case 4: command = 'H'; break; case 6: command = 'L'; break; case 7: command = 'Y'; break; case 8: command = 'K'; break; case 9: command = 'U'; break; default: command = ' '; break; } } else { command = ' '; } break; case '/': case '<': case '>': case '-': case '=': case '{': case '?': case 'A': break; case '1': command = 'b'; break; case '2': command = 'j'; break; case '3': command = 'n'; break; case '4': command = 'h'; break; case '5': // Rest one turn command = '.'; break; case '6': command = 'l'; break; case '7': command = 'y'; break; case '8': command = 'k'; break; case '9': command = 'u'; break; case 'B': command = 'f'; break; case 'C': case 'D': case 'E': case 'F': case 'G': break; case 'L': command = 'W'; break; case 'M': break; case 'R': break; case 'S': command = '#'; break; case 'T': if (getDirectionWithMemory(CNIL, direction)) { switch (direction) { case 1: command = CTRL_KEY('B'); break; case 2: command = CTRL_KEY('J'); break; case 3: command = CTRL_KEY('N'); break; case 4: command = CTRL_KEY('H'); break; case 6: command = CTRL_KEY('L'); break; case 7: command = CTRL_KEY('Y'); break; case 8: command = CTRL_KEY('K'); break; case 9: command = CTRL_KEY('U'); break; default: command = ' '; break; } } else { command = ' '; } break; case 'V': break; case 'a': command = 'z'; break; case 'b': command = 'P'; break; case 'c': case 'd': case 'e': break; case 'f': command = 't'; break; case 'h': command = '?'; break; case 'i': break; case 'j': command = 'S'; break; case 'l': command = 'x'; break; case 'm': case 'o': case 'p': case 'q': case 'r': case 's': break; case 't': command = 'T'; break; case 'u': command = 'Z'; break; case 'v': case 'w': break; case 'x': command = 'X'; break; // wizard mode commands follow case CTRL_KEY('A'): // ^A = cure all break; case CTRL_KEY('B'): // ^B = objects command = CTRL_KEY('O'); break; case CTRL_KEY('D'): // ^D = up/down break; case CTRL_KEY('H'): // ^H = wizhelp command = '\\'; break; case CTRL_KEY('I'): // ^I = identify break; case CTRL_KEY('L'): // ^L = wizlight command = '*'; break; case ':': case CTRL_KEY('T'): // ^T = teleport case CTRL_KEY('E'): // ^E = wizchar case CTRL_KEY('F'): // ^F = genocide case CTRL_KEY('G'): // ^G = treasure case '@': case '+': break; case CTRL_KEY('U'): // ^U = summon command = '&'; break; default: command = '~'; // Anything illegal. break; } return command; } static bool moveWithoutPickup(char *command) { char com_val = *command; // hack for move without pickup. Map '-' to a movement command. if (com_val != '-') { return true; } int dir_val; // Save current game.command_count as getDirectionWithMemory() may change it int countSave = game.command_count; if (getDirectionWithMemory(CNIL, dir_val)) { // Restore game.command_count game.command_count = countSave; switch (dir_val) { case 1: com_val = 'b'; break; case 2: com_val = 'j'; break; case 3: com_val = 'n'; break; case 4: com_val = 'h'; break; case 6: com_val = 'l'; break; case 7: com_val = 'y'; break; case 8: com_val = 'k'; break; case 9: com_val = 'u'; break; default: com_val = '~'; break; } } else { com_val = ' '; } *command = com_val; return false; } static void commandQuit() { flushInputBuffer(); if (getInputConfirmation("Do you really want to quit?")) { game.character_is_dead = true; dg.generate_new_level = true; (void) strcpy(game.character_died_from, "Quitting"); } } static uint8_t calculateMaxMessageCount() { uint8_t max_messages = MESSAGE_HISTORY_SIZE; if (game.command_count > 0) { if (game.command_count < MESSAGE_HISTORY_SIZE) { max_messages = (uint8_t) game.command_count; } game.command_count = 0; } else if (game.last_command != CTRL_KEY('P')) { max_messages = 1; } return max_messages; } static void commandPreviousMessage() { uint8_t max_messages = calculateMaxMessageCount(); if (max_messages <= 1) { // Distinguish real and recovered messages with a '>'. -CJS- putString(">", Coord_t{0, 0}); putStringClearToEOL(messages[last_message_id], Coord_t{0, 1}); return; } terminalSaveScreen(); uint8_t lineNumber = max_messages; int16_t msg_id = last_message_id; while (max_messages > 0) { max_messages--; putStringClearToEOL(messages[msg_id], Coord_t{max_messages, 0}); if (msg_id == 0) { msg_id = MESSAGE_HISTORY_SIZE - 1; } else { msg_id--; } } eraseLine(Coord_t{lineNumber, 0}); waitForContinueKey(lineNumber); terminalRestoreScreen(); } static void commandFlipWizardMode() { if (game.wizard_mode) { game.wizard_mode = false; printMessage("Wizard mode off."); } else if (enterWizardMode()) { printMessage("Wizard mode on."); } printCharacterWinner(); } static void commandSaveAndExit() { if (game.total_winner) { printMessage("You are a Total Winner, your character must be retired."); if (config::options::use_roguelike_keys) { printMessage("Use 'Q' to when you are ready to quit."); } else { printMessage("Use -K when you are ready to quit."); } } else { (void) strcpy(game.character_died_from, "(saved)"); printMessage("Saving game..."); if (saveGame()) { endGame(); } (void) strcpy(game.character_died_from, "(alive and well)"); } } static void commandLocateOnMap() { if (py.flags.blind > 0 || playerNoLight()) { printMessage("You can't see your map."); return; } int y = py.row; int x = py.col; if (coordOutsidePanel(Coord_t{y, x}, true)) { drawDungeonPanel(); } int cy, cx, p_y, p_x; cy = dg.panel.row; cx = dg.panel.col; int dir_val; vtype_t out_val = {'\0'}; vtype_t tmp_str = {'\0'}; while (true) { p_y = dg.panel.row; p_x = dg.panel.col; if (p_y == cy && p_x == cx) { tmp_str[0] = '\0'; } else { (void) sprintf(tmp_str, "%s%s of", p_y < cy ? " North" : p_y > cy ? " South" : "", p_x < cx ? " West" : p_x > cx ? " East" : ""); } (void) sprintf(out_val, "Map sector [%d,%d], which is%s your sector. Look which direction?", p_y, p_x, tmp_str); if (!getDirectionWithMemory(out_val, dir_val)) { break; } // -CJS- // Should really use the move function, but what the hell. This // is nicer, as it moves exactly to the same place in another // section. The direction calculation is not intuitive. Sorry. while (true) { x += ((dir_val - 1) % 3 - 1) * SCREEN_WIDTH / 2; y -= ((dir_val - 1) / 3 - 1) * SCREEN_HEIGHT / 2; if (x < 0 || y < 0 || x >= dg.width || y >= dg.width) { printMessage("You've gone past the end of your map."); x -= ((dir_val - 1) % 3 - 1) * SCREEN_WIDTH / 2; y += ((dir_val - 1) / 3 - 1) * SCREEN_HEIGHT / 2; break; } if (coordOutsidePanel(Coord_t{y, x}, true)) { drawDungeonPanel(); break; } } } // Move to a new panel - but only if really necessary. if (coordOutsidePanel(Coord_t{py.row, py.col}, false)) { drawDungeonPanel(); } } static void commandToggleSearch() { if ((py.flags.status & config::player::status::PY_SEARCH) != 0u) { playerSearchOff(); } else { playerSearchOn(); } } static void doWizardCommands(char com_val) { int i, y, x; switch (com_val) { case CTRL_KEY('A'): // Cure all! (void) spellRemoveCurseFromAllItems(); (void) playerCureBlindness(); (void) playerCureConfusion(); (void) playerCurePoison(); (void) playerRemoveFear(); (void) playerStatRestore(py_attrs::A_STR); (void) playerStatRestore(py_attrs::A_INT); (void) playerStatRestore(py_attrs::A_WIS); (void) playerStatRestore(py_attrs::A_CON); (void) playerStatRestore(py_attrs::A_DEX); (void) playerStatRestore(py_attrs::A_CHR); if (py.flags.slow > 1) { py.flags.slow = 1; } if (py.flags.image > 1) { py.flags.image = 1; } break; case CTRL_KEY('E'): // Edit Character wizardCharacterAdjustment(); messageLineClear(); break; case CTRL_KEY('F'): // Mass Genocide, vanquish all monsters (void) spellMassGenocide(); break; case CTRL_KEY('G'): // Generate random items if (game.command_count > 0) { i = game.command_count; game.command_count = 0; } else { i = 1; } dungeonPlaceRandomObjectNear(Coord_t{py.row, py.col}, i); drawDungeonPanel(); break; case CTRL_KEY('D'): // Go up/down to specified depth if (game.command_count > 0) { if (game.command_count > 99) { i = 0; } else { i = game.command_count; } game.command_count = 0; } else { i = -1; vtype_t input = {0}; putStringClearToEOL("Go to which level (0-99) ? ", Coord_t{0, 0}); if (getStringInput(input, Coord_t{0, 27}, 10)) { (void) stringToNumber(input, i); } } if (i >= 0) { dg.current_level = (int16_t) i; if (dg.current_level > 99) { dg.current_level = 99; } dg.generate_new_level = true; } else { messageLineClear(); } break; case CTRL_KEY('O'): // Print random level object to a file outputRandomLevelObjectsToFile(); break; case '\\': // Display wizard help if (config::options::use_roguelike_keys) { displayTextHelpFile(config::files::help_roguelike_wizard); } else { displayTextHelpFile(config::files::help_wizard); } break; case CTRL_KEY('I'): // Identify an item (void) spellIdentifyItem(); break; case '*': // Light up entire dungeon wizardLightUpDungeon(); break; case ':': // Light up current panel spellMapCurrentArea(); break; case CTRL_KEY('T'): // Random player teleportation playerTeleport(100); break; case '%': // Generate a dungeon item! wizardGenerateObject(); drawDungeonPanel(); break; case '+': // Increase Experience if (game.command_count > 0) { py.misc.exp = game.command_count; game.command_count = 0; } else if (py.misc.exp == 0) { py.misc.exp = 1; } else { py.misc.exp = py.misc.exp * 2; } displayCharacterExperience(); break; case '&': // Summon a random monster y = py.row; x = py.col; (void) monsterSummon(y, x, true); updateMonsters(false); break; case '@': // Generate an object // NOTE: every field from the struct needs to be filled correctly wizardCreateObjects(); break; default: if (config::options::use_roguelike_keys) { putStringClearToEOL("Type '?' or '\\' for help.", Coord_t{0, 0}); } else { putStringClearToEOL("Type '?' or ^H for help.", Coord_t{0, 0}); } } } // TODO: use only commands here - don't just call the external functions. // TODO: E.g. split playerEat() into command/action functions: commandEat(), playerEat(). // Possibly the "setup" happens in the command, such as the food check/selection of playerEat(). // The command then calls playerEat() in player_eat.cpp - passing the selected food `item_id`. static void doCommand(char command) { bool do_pickup = moveWithoutPickup(&command); switch (command) { case 'Q': // (Q)uit (^K)ill commandQuit(); game.player_free_turn = true; break; case CTRL_KEY('P'): // (^P)revious message. commandPreviousMessage(); game.player_free_turn = true; break; case CTRL_KEY('V'): // (^V)iew license displayTextHelpFile(config::files::license); game.player_free_turn = true; break; case CTRL_KEY('W'): // (^W)izard mode commandFlipWizardMode(); game.player_free_turn = true; break; case CTRL_KEY('X'): // e(^X)it and save commandSaveAndExit(); game.player_free_turn = true; break; case '=': // (=) set options terminalSaveScreen(); setGameOptions(); terminalRestoreScreen(); game.player_free_turn = true; break; case '{': // ({) inscribe an object itemInscribe(); game.player_free_turn = true; break; case '!': // (!) escape to the shell case '$': // escaping to shell disabled -MRC- game.player_free_turn = true; break; case ESCAPE: // (ESC) do nothing. case ' ': // (space) do nothing. game.player_free_turn = true; break; case 'b': // (b) down, left (1) playerMove(1, do_pickup); break; case 'j': // (j) down (2) playerMove(2, do_pickup); break; case 'n': // (n) down, right (3) playerMove(3, do_pickup); break; case 'h': // (h) left (4) playerMove(4, do_pickup); break; case 'l': // (l) right (6) playerMove(6, do_pickup); break; case 'y': // (y) up, left (7) playerMove(7, do_pickup); break; case 'k': // (k) up (8) playerMove(8, do_pickup); break; case 'u': // (u) up, right (9) playerMove(9, do_pickup); break; case 'B': // (B) run down, left (. 1) playerFindInitialize(1); break; case 'J': // (J) run down (. 2) playerFindInitialize(2); break; case 'N': // (N) run down, right (. 3) playerFindInitialize(3); break; case 'H': // (H) run left (. 4) playerFindInitialize(4); break; case 'L': // (L) run right (. 6) playerFindInitialize(6); break; case 'Y': // (Y) run up, left (. 7) playerFindInitialize(7); break; case 'K': // (K) run up (. 8) playerFindInitialize(8); break; case 'U': // (U) run up, right (. 9) playerFindInitialize(9); break; case '/': // (/) identify a symbol identifyGameObject(); game.player_free_turn = true; break; case '.': // (.) stay in one place (5) playerMove(5, do_pickup); if (game.command_count > 1) { game.command_count--; playerRestOn(); } break; case '<': // (<) go down a staircase dungeonGoUpLevel(); break; case '>': // (>) go up a staircase dungeonGoDownLevel(); break; case '?': // (?) help with commands if (config::options::use_roguelike_keys) { displayTextHelpFile(config::files::help_roguelike); } else { displayTextHelpFile(config::files::help); } game.player_free_turn = true; break; case 'f': // (f)orce (B)ash playerBash(); break; case 'C': // (C)haracter description terminalSaveScreen(); changeCharacterName(); terminalRestoreScreen(); game.player_free_turn = true; break; case 'D': // (D)isarm trap playerDisarmTrap(); break; case 'E': // (E)at food playerEat(); break; case 'F': // (F)ill lamp inventoryRefillLamp(); break; case 'G': // (G)ain magic spells playerGainSpells(); break; case 'V': // (V)iew scores terminalSaveScreen(); showScoresScreen(); terminalRestoreScreen(); game.player_free_turn = true; break; case 'W': // (W)here are we on the map (L)ocate on map commandLocateOnMap(); game.player_free_turn = true; break; case 'R': // (R)est a while playerRestOn(); break; case '#': // (#) search toggle (S)earch toggle commandToggleSearch(); game.player_free_turn = true; break; case CTRL_KEY('B'): // (^B) tunnel down left (T 1) playerTunnel(1); break; case CTRL_KEY('M'): // cr must be treated same as lf. case CTRL_KEY('J'): // (^J) tunnel down (T 2) playerTunnel(2); break; case CTRL_KEY('N'): // (^N) tunnel down right (T 3) playerTunnel(3); break; case CTRL_KEY('H'): // (^H) tunnel left (T 4) playerTunnel(4); break; case CTRL_KEY('L'): // (^L) tunnel right (T 6) playerTunnel(6); break; case CTRL_KEY('Y'): // (^Y) tunnel up left (T 7) playerTunnel(7); break; case CTRL_KEY('K'): // (^K) tunnel up (T 8) playerTunnel(8); break; case CTRL_KEY('U'): // (^U) tunnel up right (T 9) playerTunnel(9); break; case 'z': // (z)ap a wand (a)im a wand wandAim(); break; case 'M': dungeonDisplayMap(); game.player_free_turn = true; break; case 'P': // (P)eruse a book (B)rowse in a book examineBook(); game.player_free_turn = true; break; case 'c': // (c)lose an object playerCloseDoor(); break; case 'd': // (d)rop something inventoryExecuteCommand('d'); break; case 'e': // (e)quipment list inventoryExecuteCommand('e'); break; case 't': // (t)hrow something (f)ire something playerThrowItem(); break; case 'i': // (i)nventory list inventoryExecuteCommand('i'); break; case 'S': // (S)pike a door (j)am a door dungeonJamDoor(); break; case 'x': // e(x)amine surrounds (l)ook about look(); game.player_free_turn = true; break; case 'm': // (m)agic spells getAndCastMagicSpell(); break; case 'o': // (o)pen something playerOpenClosedObject(); break; case 'p': // (p)ray pray(); break; case 'q': // (q)uaff quaff(); break; case 'r': // (r)ead scrollRead(); break; case 's': // (s)earch for a turn playerSearch(py.row, py.col, py.misc.chance_in_search); break; case 'T': // (T)ake off something (t)ake off inventoryExecuteCommand('t'); break; case 'Z': // (Z)ap a staff (u)se a staff staffUse(); break; case 'v': // (v)ersion of game displayTextHelpFile(config::files::versions_history); game.player_free_turn = true; break; case 'w': // (w)ear or wield inventoryExecuteCommand('w'); break; case 'X': // e(X)change weapons e(x)change inventoryExecuteCommand('x'); break; default: // Wizard commands are free moves game.player_free_turn = true; if (game.wizard_mode) { doWizardCommands(command); } else { putStringClearToEOL("Type '?' for help.", Coord_t{0, 0}); } } game.last_command = command; } // Check whether this command will accept a count. -CJS- static bool validCountCommand(char command) { switch (command) { case 'Q': case CTRL_KEY('W'): case CTRL_KEY('X'): case '=': case '{': case '/': case '<': case '>': case '?': case 'C': case 'E': case 'F': case 'G': case 'V': case '#': case 'z': case 'P': case 'c': case 'd': case 'e': case 't': case 'i': case 'x': case 'm': case 'p': case 'q': case 'r': case 'T': case 'Z': case 'v': case 'w': case 'W': case 'X': case CTRL_KEY('A'): case '\\': case CTRL_KEY('I'): case '*': case ':': case CTRL_KEY('T'): case CTRL_KEY('E'): case CTRL_KEY('F'): case CTRL_KEY('S'): case CTRL_KEY('Q'): return false; case CTRL_KEY('P'): case ESCAPE: case ' ': case '-': case 'b': case 'f': case 'j': case 'n': case 'h': case 'l': case 'y': case 'k': case 'u': case '.': case 'B': case 'J': case 'N': case 'H': case 'L': case 'Y': case 'K': case 'U': case 'D': case 'R': case CTRL_KEY('Y'): case CTRL_KEY('K'): case CTRL_KEY('U'): case CTRL_KEY('L'): case CTRL_KEY('N'): case CTRL_KEY('J'): case CTRL_KEY('B'): case CTRL_KEY('H'): case 'S': case 'o': case 's': case CTRL_KEY('D'): case CTRL_KEY('G'): case '+': return true; default: return false; } } // Regenerate hit points -RAK- static void playerRegenerateHitPoints(int percent) { int old_chp = py.misc.current_hp; int32_t new_chp = (int32_t) py.misc.max_hp * percent + config::player::PLAYER_REGEN_HPBASE; // div 65536 py.misc.current_hp += new_chp >> 16; // check for overflow if (py.misc.current_hp < 0 && old_chp > 0) { py.misc.current_hp = MAX_SHORT; } // mod 65536 int32_t new_chp_fraction = (new_chp & 0xFFFF) + py.misc.current_hp_fraction; if (new_chp_fraction >= 0x10000L) { py.misc.current_hp_fraction = (uint16_t) (new_chp_fraction - 0x10000L); py.misc.current_hp++; } else { py.misc.current_hp_fraction = (uint16_t) new_chp_fraction; } // must set frac to zero even if equal if (py.misc.current_hp >= py.misc.max_hp) { py.misc.current_hp = py.misc.max_hp; py.misc.current_hp_fraction = 0; } if (old_chp != py.misc.current_hp) { printCharacterCurrentHitPoints(); } } // Regenerate mana points -RAK- static void playerRegenerateMana(int percent) { int old_cmana = py.misc.current_mana; int32_t new_mana = (int32_t) py.misc.mana * percent + config::player::PLAYER_REGEN_MNBASE; // div 65536 py.misc.current_mana += new_mana >> 16; // check for overflow if (py.misc.current_mana < 0 && old_cmana > 0) { py.misc.current_mana = MAX_SHORT; } // mod 65536 int32_t new_mana_fraction = (new_mana & 0xFFFF) + py.misc.current_mana_fraction; if (new_mana_fraction >= 0x10000L) { py.misc.current_mana_fraction = (uint16_t) (new_mana_fraction - 0x10000L); py.misc.current_mana++; } else { py.misc.current_mana_fraction = (uint16_t) new_mana_fraction; } // must set frac to zero even if equal if (py.misc.current_mana >= py.misc.mana) { py.misc.current_mana = py.misc.mana; py.misc.current_mana_fraction = 0; } if (old_cmana != py.misc.current_mana) { printCharacterCurrentMana(); } } // Is an item an enchanted weapon or armor and we don't know? -CJS- // only returns true if it is a good enchantment static bool itemEnchanted(Inventory_t const &item) { if (item.category_id < TV_MIN_ENCHANT || item.category_id > TV_MAX_ENCHANT || (item.flags & config::treasure::flags::TR_CURSED) != 0u) { return false; } if (spellItemIdentified(item)) { return false; } if ((item.identification & config::identification::ID_MAGIK) != 0) { return false; } if (item.to_hit > 0 || item.to_damage > 0 || item.to_ac > 0) { return true; } if ((0x4000107fL & item.flags) != 0 && item.misc_use > 0) { return true; } if ((0x07ffe980L & item.flags) != 0) { return true; } return false; } // Examine a Book -RAK- static void examineBook() { int item_pos_start, item_pos_end; if (!inventoryFindRange(TV_MAGIC_BOOK, TV_PRAYER_BOOK, item_pos_start, item_pos_end)) { printMessage("You are not carrying any books."); return; } if (py.flags.blind > 0) { printMessage("You can't see to read your spell book!"); return; } if (playerNoLight()) { printMessage("You have no light to read by."); return; } if (py.flags.confused > 0) { printMessage("You are too confused."); return; } int item_id; if (inventoryGetInputForItemId(item_id, "Which Book?", item_pos_start, item_pos_end, CNIL, CNIL)) { int spell_index[31]; bool can_read = true; uint8_t treasure_type = inventory[item_id].category_id; if (classes[py.misc.class_id].class_to_use_mage_spells == config::spells::SPELL_TYPE_MAGE) { if (treasure_type != TV_MAGIC_BOOK) { can_read = false; } } else if (classes[py.misc.class_id].class_to_use_mage_spells == config::spells::SPELL_TYPE_PRIEST) { if (treasure_type != TV_PRAYER_BOOK) { can_read = false; } } else { can_read = false; } if (!can_read) { printMessage("You do not understand the language."); return; } uint32_t item_flags = inventory[item_id].flags; int spell_id = 0; while (item_flags != 0u) { item_pos_end = getAndClearFirstBit(item_flags); if (magic_spells[py.misc.class_id - 1][item_pos_end].level_required < 99) { spell_index[spell_id] = item_pos_end; spell_id++; } } terminalSaveScreen(); displaySpellsList(spell_index, spell_id, true, -1); waitForContinueKey(0); terminalRestoreScreen(); } } // Go up one level -RAK- static void dungeonGoUpLevel() { uint8_t tile_id = dg.floor[py.row][py.col].treasure_id; if (tile_id != 0 && treasure_list[tile_id].category_id == TV_UP_STAIR) { dg.current_level--; printMessage("You enter a maze of up staircases."); printMessage("You pass through a one-way door."); dg.generate_new_level = true; } else { printMessage("I see no up staircase here."); game.player_free_turn = true; } } // Go down one level -RAK- static void dungeonGoDownLevel() { uint8_t tile_id = dg.floor[py.row][py.col].treasure_id; if (tile_id != 0 && treasure_list[tile_id].category_id == TV_DOWN_STAIR) { dg.current_level++; printMessage("You enter a maze of down staircases."); printMessage("You pass through a one-way door."); dg.generate_new_level = true; } else { printMessage("I see no down staircase here."); game.player_free_turn = true; } } // Jam a closed door -RAK- static void dungeonJamDoor() { game.player_free_turn = true; int y = py.row; int x = py.col; int direction; if (!getDirectionWithMemory(CNIL, direction)) { return; } (void) playerMovePosition(direction, y, x); Tile_t const &tile = dg.floor[y][x]; if (tile.treasure_id == 0) { printMessage("That isn't a door!"); return; } Inventory_t &item = treasure_list[tile.treasure_id]; uint8_t item_id = item.category_id; if (item_id != TV_CLOSED_DOOR && item_id != TV_OPEN_DOOR) { printMessage("That isn't a door!"); return; } if (item_id == TV_OPEN_DOOR) { printMessage("The door must be closed first."); return; } // If we reach here, the door is closed and we can try to jam it -MRC- if (tile.creature_id == 0) { int item_pos_start, item_pos_end; if (inventoryFindRange(TV_SPIKE, TV_NEVER, item_pos_start, item_pos_end)) { game.player_free_turn = false; printMessageNoCommandInterrupt("You jam the door with a spike."); if (item.misc_use > 0) { // Make locked to stuck. item.misc_use = -item.misc_use; } // Successive spikes have a progressively smaller effect. // Series is: 0 20 30 37 43 48 52 56 60 64 67 70 ... item.misc_use -= 1 + 190 / (10 - item.misc_use); if (inventory[item_pos_start].items_count > 1) { inventory[item_pos_start].items_count--; py.inventory_weight -= inventory[item_pos_start].weight; } else { inventoryDestroyItem(item_pos_start); } } else { printMessage("But you have no spikes."); } } else { game.player_free_turn = false; vtype_t msg = {'\0'}; (void) sprintf(msg, "The %s is in your way!", creatures_list[monsters[tile.creature_id].creature_id].name); printMessage(msg); } } // Refill the players lamp -RAK- static void inventoryRefillLamp() { game.player_free_turn = true; if (inventory[player_equipment::EQUIPMENT_LIGHT].sub_category_id != 0) { printMessage("But you are not using a lamp."); return; } int item_pos_start, item_pos_end; if (!inventoryFindRange(TV_FLASK, TV_NEVER, item_pos_start, item_pos_end)) { printMessage("You have no oil."); return; } game.player_free_turn = false; Inventory_t &item = inventory[player_equipment::EQUIPMENT_LIGHT]; item.misc_use += inventory[item_pos_start].misc_use; if (item.misc_use > config::treasure::OBJECT_LAMP_MAX_CAPACITY) { item.misc_use = config::treasure::OBJECT_LAMP_MAX_CAPACITY; printMessage("Your lamp overflows, spilling oil on the ground."); printMessage("Your lamp is full."); } else if (item.misc_use > config::treasure::OBJECT_LAMP_MAX_CAPACITY / 2) { printMessage("Your lamp is more than half full."); } else if (item.misc_use == config::treasure::OBJECT_LAMP_MAX_CAPACITY / 2) { printMessage("Your lamp is half full."); } else { printMessage("Your lamp is less than half full."); } itemTypeRemainingCountDescription(item_pos_start); inventoryDestroyItem(item_pos_start); } // Main procedure for dungeon. -RAK- static void playDungeon() { // Note: There is a lot of preliminary magic going on here at first playerInitializePlayerLight(); playerUpdateMaxDungeonDepth(); resetDungeonFlags(); // Initialize find counter to `0` int find_count = 0; // Ensure we display the panel. Used to do this with a global var. -CJS- dg.panel.row = dg.panel.col = -1; // Light up the area around character dungeonResetView(); // must do this after `dg.panel.row` / `dg.panel.col` set to -1, because playerSearchOff() will // call dungeonResetView(), and so the panel_* variables must be valid before // playerSearchOff() is called if ((py.flags.status & config::player::status::PY_SEARCH) != 0u) { playerSearchOff(); } // Light, but do not move critters updateMonsters(false); // Print the depth printCharacterCurrentDepth(); // Note: yes, this last input command needs to be persisted // over different iterations of the main loop below -MRC- char lastInputCommand = {0}; // Loop until dead, or new level // Exit when `dg.generate_new_level` and `eof_flag` are both set do { // Increment turn counter dg.game_turn++; // turn over the store contents every, say, 1000 turns if (dg.current_level != 0 && dg.game_turn % 1000 == 0) { storeMaintenance(); } // Check for creature generation if (randomNumber(config::monsters::MON_CHANCE_OF_NEW) == 1) { monsterPlaceNewWithinDistance(1, config::monsters::MON_MAX_SIGHT, false); } playerUpdateLightStatus(); // // Update counters and messages // // Heroism and Super Heroism must precede anything that can damage player playerUpdateHeroStatus(); int regen_amount = playerFoodConsumption(); playerUpdateRegeneration(regen_amount); playerUpdateBlindness(); playerUpdateConfusion(); playerUpdateFearState(); playerUpdatePoisonedState(); playerUpdateSpeed(); playerUpdateRestingState(); // Check for interrupts to find or rest. int microseconds = (py.running_tracker != 0 ? 0 : 10000); if ((game.command_count > 0 || (py.running_tracker != 0) || py.flags.rest != 0) && checkForNonBlockingKeyPress(microseconds)) { playerDisturb(0, 0); } playerUpdateHallucination(); playerUpdateParalysis(); playerUpdateEvilProtection(); playerUpdateInvulnerability(); playerUpdateBlessedness(); playerUpdateHeatResistance(); playerUpdateColdResistance(); playerUpdateDetectInvisible(); playerUpdateInfraVision(); playerUpdateWordOfRecall(); // Random teleportation if (py.flags.teleport && randomNumber(100) == 1) { playerDisturb(0, 0); playerTeleport(40); } // See if we are too weak to handle the weapon or pack. -CJS- if ((py.flags.status & config::player::status::PY_STR_WGT) != 0u) { playerStrength(); } if ((py.flags.status & config::player::status::PY_STUDY) != 0u) { printCharacterStudyInstruction(); } playerUpdateStatusFlags(); // Allow for a slim chance of detect enchantment -CJS- // for 1st level char, check once every 2160 turns // for 40th level char, check once every 416 turns int chance = 10 + 750 / (5 + py.misc.level); if ((dg.game_turn & 0xF) == 0 && py.flags.confused == 0 && randomNumber(chance) == 1) { playerDetectEnchantment(); } // Check the state of the monster list, and delete some monsters if // the monster list is nearly full. This helps to avoid problems in // creature.c when monsters try to multiply. Compact_monsters() is // much more likely to succeed if called from here, than if called // from within updateMonsters(). if (MON_TOTAL_ALLOCATIONS - next_free_monster_id < 10) { (void) compactMonsters(); } // Accept a command? if (py.flags.paralysis < 1 && py.flags.rest == 0 && !game.character_is_dead) { executeInputCommands(lastInputCommand, find_count); } else { // if paralyzed, resting, or dead, flush output // but first move the cursor onto the player, for aesthetics panelMoveCursor(Coord_t{py.row, py.col}); putQIO(); } // Teleport? if (teleport_player) { playerTeleport(100); } // Move the creatures if (!dg.generate_new_level) { updateMonsters(true); } } while (!dg.generate_new_level && (eof_flag == 0)); } umoria-5.7.10+20181022/src/player_pray.cpp0000644000175000017500000002107013363422757016603 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. // Code for priest spells #include "headers.h" static bool playerCanPray(int &item_pos_begin, int &item_pos_end) { if (py.flags.blind > 0) { printMessage("You can't see to read your prayer!"); return false; } if (playerNoLight()) { printMessage("You have no light to read by."); return false; } if (py.flags.confused > 0) { printMessage("You are too confused."); return false; } if (classes[py.misc.class_id].class_to_use_mage_spells != config::spells::SPELL_TYPE_PRIEST) { printMessage("Pray hard enough and your prayers may be answered."); return false; } if (py.unique_inventory_items == 0) { printMessage("But you are not carrying anything!"); return false; } if (!inventoryFindRange(TV_PRAYER_BOOK, TV_NEVER, item_pos_begin, item_pos_end)) { printMessage("You are not carrying any Holy Books!"); return false; } return true; } // names based on spell_names[62] in data_player.cpp enum class PriestSpellTypes { detect_evil = 1, cure_light_wounds, bless, remove_fear, call_light, find_traps, detect_doors_stairs, slow_poison, blind_creature, portal, cure_medium_wounds, chant, sanctuary, create_food, remove_curse, resist_head_cold, neutralize_poison, orb_of_draining, cure_serious_wounds, sense_invisible, protect_from_evil, earthquake, sense_surroundings, cure_critical_wounds, turn_undead, prayer, dispel_undead, heal, dispel_evil, glyph_of_warding, holy_word, }; // Recite a prayers. static void playerRecitePrayer(int prayer_type) { int dir; switch ((PriestSpellTypes) (prayer_type + 1)) { case PriestSpellTypes::detect_evil: (void) spellDetectEvil(); break; case PriestSpellTypes::cure_light_wounds: (void) spellChangePlayerHitPoints(diceRoll(Dice_t{3, 3})); break; case PriestSpellTypes::bless: playerBless(randomNumber(12) + 12); break; case PriestSpellTypes::remove_fear: (void) playerRemoveFear(); break; case PriestSpellTypes::call_light: (void) spellLightArea(py.row, py.col); break; case PriestSpellTypes::find_traps: (void) spellDetectTrapsWithinVicinity(); break; case PriestSpellTypes::detect_doors_stairs: (void) spellDetectSecretDoorssWithinVicinity(); break; case PriestSpellTypes::slow_poison: (void) spellSlowPoison(); break; case PriestSpellTypes::blind_creature: if (getDirectionWithMemory(CNIL, dir)) { (void) spellConfuseMonster(py.row, py.col, dir); } break; case PriestSpellTypes::portal: playerTeleport((py.misc.level * 3)); break; case PriestSpellTypes::cure_medium_wounds: (void) spellChangePlayerHitPoints(diceRoll(Dice_t{4, 4})); break; case PriestSpellTypes::chant: playerBless(randomNumber(24) + 24); break; case PriestSpellTypes::sanctuary: (void) monsterSleep(py.row, py.col); break; case PriestSpellTypes::create_food: spellCreateFood(); break; case PriestSpellTypes::remove_curse: for (auto &entry : inventory) { // only clear flag for items that are wielded or worn if (entry.category_id >= TV_MIN_WEAR && entry.category_id <= TV_MAX_WEAR) { entry.flags &= ~config::treasure::flags::TR_CURSED; } } break; case PriestSpellTypes::resist_head_cold: py.flags.heat_resistance += randomNumber(10) + 10; py.flags.cold_resistance += randomNumber(10) + 10; break; case PriestSpellTypes::neutralize_poison: (void) playerCurePoison(); break; case PriestSpellTypes::orb_of_draining: if (getDirectionWithMemory(CNIL, dir)) { spellFireBall(py.row, py.col, dir, (diceRoll(Dice_t{3, 6}) + py.misc.level), magic_spell_flags::GF_HOLY_ORB, "Black Sphere"); } break; case PriestSpellTypes::cure_serious_wounds: (void) spellChangePlayerHitPoints(diceRoll(Dice_t{8, 4})); break; case PriestSpellTypes::sense_invisible: playerDetectInvisible(randomNumber(24) + 24); break; case PriestSpellTypes::protect_from_evil: (void) playerProtectEvil(); break; case PriestSpellTypes::earthquake: spellEarthquake(); break; case PriestSpellTypes::sense_surroundings: spellMapCurrentArea(); break; case PriestSpellTypes::cure_critical_wounds: (void) spellChangePlayerHitPoints(diceRoll(Dice_t{16, 4})); break; case PriestSpellTypes::turn_undead: (void) spellTurnUndead(); break; case PriestSpellTypes::prayer: playerBless(randomNumber(48) + 48); break; case PriestSpellTypes::dispel_undead: (void) spellDispelCreature(config::monsters::defense::CD_UNDEAD, (3 * py.misc.level)); break; case PriestSpellTypes::heal: (void) spellChangePlayerHitPoints(200); break; case PriestSpellTypes::dispel_evil: (void) spellDispelCreature(config::monsters::defense::CD_EVIL, (3 * py.misc.level)); break; case PriestSpellTypes::glyph_of_warding: spellWardingGlyph(); break; case PriestSpellTypes::holy_word: (void) playerRemoveFear(); (void) playerCurePoison(); (void) spellChangePlayerHitPoints(1000); for (int i = py_attrs::A_STR; i <= py_attrs::A_CHR; i++) { (void) playerStatRestore(i); } (void) spellDispelCreature(config::monsters::defense::CD_EVIL, (4 * py.misc.level)); (void) spellTurnUndead(); if (py.flags.invulnerability < 3) { py.flags.invulnerability = 3; } else { py.flags.invulnerability++; } break; default: break; } } // Pray like HELL. -RAK- void pray() { game.player_free_turn = true; int item_pos_begin, item_pos_end; if (!playerCanPray(item_pos_begin, item_pos_end)) { return; } int item_id; if (!inventoryGetInputForItemId(item_id, "Use which Holy Book?", item_pos_begin, item_pos_end, CNIL, CNIL)) { return; } int choice, chance; int result = castSpellGetId("Recite which prayer?", item_id, choice, chance); if (result < 0) { printMessage("You don't know any prayers in that book."); return; } if (result == 0) { return; } if (randomNumber(100) < chance) { printMessage("You lost your concentration!"); return; } Spell_t const &spell = magic_spells[py.misc.class_id - 1][choice]; // NOTE: at least one function called by `playerRecitePrayer()` sets `player_free_turn = true`, // e.g. `spellCreateFood()`, so this check is required. -MRC- game.player_free_turn = false; playerRecitePrayer(choice); if (!game.player_free_turn) { if ((py.flags.spells_worked & (1L << choice)) == 0) { py.misc.exp += spell.exp_gain_for_learning << 2; displayCharacterExperience(); py.flags.spells_worked |= (1L << choice); } } if (!game.player_free_turn) { if (spell.mana_required > py.misc.current_mana) { printMessage("You faint from fatigue!"); py.flags.paralysis = (int16_t) randomNumber((5 * (spell.mana_required - py.misc.current_mana))); py.misc.current_mana = 0; py.misc.current_mana_fraction = 0; if (randomNumber(3) == 1) { printMessage("You have damaged your health!"); (void) playerStatRandomDecrease(py_attrs::A_CON); } } else { py.misc.current_mana -= spell.mana_required; } printCharacterCurrentMana(); } } umoria-5.7.10+20181022/src/game_files.cpp0000644000175000017500000003375213363422757016361 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. // Misc code to access files used by Moria #include "headers.h" // This must be included after fcntl.h, which has a prototype for `open' on some // systems. Otherwise, the `open' prototype conflicts with the `topen' declaration. // initializeScoreFile // Open the score file while we still have the setuid privileges. Later // when the score is being written out, you must be sure to flock the file // so we don't have multiple people trying to write to it at the same time. // Craig Norborg (doc) Mon Aug 10 16:41:59 EST 1987 bool initializeScoreFile() { highscore_fp = fopen(config::files::scores.c_str(), (char *) "rb+"); return highscore_fp != nullptr; } // Attempt to open and print the file containing the intro splash screen text -RAK- void displaySplashScreen() { vtype_t in_line = {'\0'}; FILE *file1 = fopen(config::files::splash_screen.c_str(), "r"); if (file1 != nullptr) { clearScreen(); for (int i = 0; fgets(in_line, 80, file1) != CNIL; i++) { putString(in_line, Coord_t{i, 0}); } waitForContinueKey(23); (void) fclose(file1); } } // Open and display a text help file // File perusal, primitive, but portable -CJS- void displayTextHelpFile(const std::string &filename) { FILE *file = fopen(filename.c_str(), "r"); if (file == nullptr) { putStringClearToEOL("Can not find help file '" + filename + "'.", Coord_t{0, 0}); return; } terminalSaveScreen(); constexpr uint8_t max_line_length = 80; char line_buffer[max_line_length]; char input; while (feof(file) == 0) { clearScreen(); for (int i = 0; i < 23; i++) { if (fgets(line_buffer, max_line_length - 1, file) != CNIL) { putString(line_buffer, Coord_t{i, 0}); } } putStringClearToEOL("[ press any key to continue ]", Coord_t{23, 23}); input = getKeyInput(); if (input == ESCAPE) { break; } } (void) fclose(file); terminalRestoreScreen(); } // Open and display a "death" text file void displayDeathFile(const std::string &filename) { FILE *file = fopen(filename.c_str(), "r"); if (file == nullptr) { putStringClearToEOL("Can not find help file '" + filename + "'.", Coord_t{0, 0}); return; } clearScreen(); constexpr uint8_t max_line_length = 80; char line_buffer[max_line_length]; for (int i = 0; i < 23 && feof(file) == 0; i++) { if (fgets(line_buffer, max_line_length - 1, file) != CNIL) { putString(line_buffer, Coord_t{i, 0}); } } (void) fclose(file); } // Prints a list of random objects to a file. -RAK- // Note that the objects produced is a sampling of objects // which be expected to appear on that level. void outputRandomLevelObjectsToFile() { obj_desc_t input = {0}; putStringClearToEOL("Produce objects on what level?: ", Coord_t{0, 0}); if (!getStringInput(input, Coord_t{0, 32}, 10)) { return; } int level; if (!stringToNumber(input, level)) { return; } putStringClearToEOL("Produce how many objects?: ", Coord_t{0, 0}); if (!getStringInput(input, Coord_t{0, 27}, 10)) { return; } int count; if (!stringToNumber(input, count)) { return; } if (count < 1 || level < 0 || level > 1200) { putStringClearToEOL("Parameters no good.", Coord_t{0, 0}); return; } if (count > 10000) { count = 10000; } bool small_objects = getInputConfirmation("Small objects only?"); putStringClearToEOL("File name: ", Coord_t{0, 0}); vtype_t filename = {0}; if (!getStringInput(filename, Coord_t{0, 11}, 64)) { return; } if (strlen(filename) == 0) { return; } FILE *file_ptr = fopen(filename, "w"); if (file_ptr == nullptr) { putStringClearToEOL("File could not be opened.", Coord_t{0, 0}); return; } (void) sprintf(input, "%d", count); putStringClearToEOL(strcat(input, " random objects being produced..."), Coord_t{0, 0}); putQIO(); (void) fprintf(file_ptr, "*** Random Object Sampling:\n"); (void) fprintf(file_ptr, "*** %d objects\n", count); (void) fprintf(file_ptr, "*** For Level %d\n", level); (void) fprintf(file_ptr, "\n"); (void) fprintf(file_ptr, "\n"); int treasure_id = popt(); for (int i = 0; i < count; i++) { int object_id = itemGetRandomObjectId(level, small_objects); inventoryItemCopyTo(sorted_objects[object_id], treasure_list[treasure_id]); magicTreasureMagicalAbility(treasure_id, level); Inventory_t &item = treasure_list[treasure_id]; itemIdentifyAsStoreBought(item); if ((item.flags & config::treasure::flags::TR_CURSED) != 0u) { itemAppendToInscription(item, config::identification::ID_DAMD); } itemDescription(input, item, true); (void) fprintf(file_ptr, "%d %s\n", item.depth_first_found, input); } pusht((uint8_t) treasure_id); (void) fclose(file_ptr); putStringClearToEOL("Completed.", Coord_t{0, 0}); } // Write character sheet to the file static void writeCharacterSheetToFile(FILE *file1) { putStringClearToEOL("Writing character sheet...", Coord_t{0, 0}); putQIO(); const char *colon = ":"; const char *blank = " "; vtype_t statDescription = {'\0'}; (void) fprintf(file1, "%c\n\n", CTRL_KEY('L')); (void) fprintf(file1, " Name%9s %-23s", colon, py.misc.name); (void) fprintf(file1, " Age%11s %6d", colon, (int) py.misc.age); statsAsString(py.stats.used[py_attrs::A_STR], statDescription); (void) fprintf(file1, " STR : %s\n", statDescription); (void) fprintf(file1, " Race%9s %-23s", colon, character_races[py.misc.race_id].name); (void) fprintf(file1, " Height%8s %6d", colon, (int) py.misc.height); statsAsString(py.stats.used[py_attrs::A_INT], statDescription); (void) fprintf(file1, " INT : %s\n", statDescription); (void) fprintf(file1, " Sex%10s %-23s", colon, (playerGetGenderLabel())); (void) fprintf(file1, " Weight%8s %6d", colon, (int) py.misc.weight); statsAsString(py.stats.used[py_attrs::A_WIS], statDescription); (void) fprintf(file1, " WIS : %s\n", statDescription); (void) fprintf(file1, " Class%8s %-23s", colon, classes[py.misc.class_id].title); (void) fprintf(file1, " Social Class : %6d", py.misc.social_class); statsAsString(py.stats.used[py_attrs::A_DEX], statDescription); (void) fprintf(file1, " DEX : %s\n", statDescription); (void) fprintf(file1, " Title%8s %-23s", colon, playerRankTitle()); (void) fprintf(file1, "%22s", blank); statsAsString(py.stats.used[py_attrs::A_CON], statDescription); (void) fprintf(file1, " CON : %s\n", statDescription); (void) fprintf(file1, "%34s", blank); (void) fprintf(file1, "%26s", blank); statsAsString(py.stats.used[py_attrs::A_CHR], statDescription); (void) fprintf(file1, " CHR : %s\n\n", statDescription); (void) fprintf(file1, " + To Hit : %6d", py.misc.display_to_hit); (void) fprintf(file1, "%7sLevel : %7d", blank, (int) py.misc.level); (void) fprintf(file1, " Max Hit Points : %6d\n", py.misc.max_hp); (void) fprintf(file1, " + To Damage : %6d", py.misc.display_to_damage); (void) fprintf(file1, "%7sExperience : %7d", blank, py.misc.exp); (void) fprintf(file1, " Cur Hit Points : %6d\n", py.misc.current_hp); (void) fprintf(file1, " + To AC : %6d", py.misc.display_to_ac); (void) fprintf(file1, "%7sMax Exp : %7d", blank, py.misc.max_exp); (void) fprintf(file1, " Max Mana%8s %6d\n", colon, py.misc.mana); (void) fprintf(file1, " Total AC : %6d", py.misc.display_ac); if (py.misc.level >= PLAYER_MAX_LEVEL) { (void) fprintf(file1, "%7sExp to Adv : *******", blank); } else { (void) fprintf(file1, "%7sExp to Adv : %7d", blank, (int32_t) (py.base_exp_levels[py.misc.level - 1] * py.misc.experience_factor / 100)); } (void) fprintf(file1, " Cur Mana%8s %6d\n", colon, py.misc.current_mana); (void) fprintf(file1, "%28sGold%8s %7d\n\n", blank, colon, py.misc.au); int xbth = py.misc.bth + py.misc.plusses_to_hit * BTH_PER_PLUS_TO_HIT_ADJUST + (class_level_adj[py.misc.class_id][py_class_level_adj::CLASS_BTH] * py.misc.level); int xbthb = py.misc.bth_with_bows + py.misc.plusses_to_hit * BTH_PER_PLUS_TO_HIT_ADJUST + (class_level_adj[py.misc.class_id][py_class_level_adj::CLASS_BTHB] * py.misc.level); // this results in a range from 0 to 29 int xfos = 40 - py.misc.fos; if (xfos < 0) { xfos = 0; } int xsrh = py.misc.chance_in_search; // this results in a range from 0 to 9 int xstl = py.misc.stealth_factor + 1; int xdis = py.misc.disarm + 2 * playerDisarmAdjustment() + playerStatAdjustmentWisdomIntelligence(py_attrs::A_INT) + (class_level_adj[py.misc.class_id][py_class_level_adj::CLASS_DISARM] * py.misc.level / 3); int xsave = py.misc.saving_throw + playerStatAdjustmentWisdomIntelligence(py_attrs::A_WIS) + (class_level_adj[py.misc.class_id][py_class_level_adj::CLASS_SAVE] * py.misc.level / 3); int xdev = py.misc.saving_throw + playerStatAdjustmentWisdomIntelligence(py_attrs::A_INT) + (class_level_adj[py.misc.class_id][py_class_level_adj::CLASS_DEVICE] * py.misc.level / 3); vtype_t xinfra = {'\0'}; (void) sprintf(xinfra, "%d feet", py.flags.see_infra * 10); (void) fprintf(file1, "(Miscellaneous Abilities)\n\n"); (void) fprintf(file1, " Fighting : %-10s", statRating(12, xbth)); (void) fprintf(file1, " Stealth : %-10s", statRating(1, xstl)); (void) fprintf(file1, " Perception : %s\n", statRating(3, xfos)); (void) fprintf(file1, " Bows/Throw : %-10s", statRating(12, xbthb)); (void) fprintf(file1, " Disarming : %-10s", statRating(8, xdis)); (void) fprintf(file1, " Searching : %s\n", statRating(6, xsrh)); (void) fprintf(file1, " Saving Throw: %-10s", statRating(6, xsave)); (void) fprintf(file1, " Magic Device: %-10s", statRating(6, xdev)); (void) fprintf(file1, " Infra-Vision: %s\n\n", xinfra); // Write out the character's history (void) fprintf(file1, "Character Background\n"); for (auto &entry : py.misc.history) { (void) fprintf(file1, " %s\n", entry); } } static const char *equipmentPlacementDescription(int itemID) { switch (itemID) { case player_equipment::EQUIPMENT_WIELD: return "You are wielding"; case player_equipment::EQUIPMENT_HEAD: return "Worn on head"; case player_equipment::EQUIPMENT_NECK: return "Worn around neck"; case player_equipment::EQUIPMENT_BODY: return "Worn on body"; case player_equipment::EQUIPMENT_ARM: return "Worn on shield arm"; case player_equipment::EQUIPMENT_HANDS: return "Worn on hands"; case player_equipment::EQUIPMENT_RIGHT: return "Right ring finger"; case player_equipment::EQUIPMENT_LEFT: return "Left ring finger"; case player_equipment::EQUIPMENT_FEET: return "Worn on feet"; case player_equipment::EQUIPMENT_OUTER: return "Worn about body"; case player_equipment::EQUIPMENT_LIGHT: return "Light source is"; case player_equipment::EQUIPMENT_AUX: return "Secondary weapon"; default: return "*Unknown value*"; } } // Write out the equipment list. static void writeEquipmentListToFile(FILE *file1) { (void) fprintf(file1, "\n [Character's Equipment List]\n\n"); if (py.equipment_count == 0) { (void) fprintf(file1, " Character has no equipment in use.\n"); return; } obj_desc_t description = {'\0'}; int itemSlotID = 0; for (int i = player_equipment::EQUIPMENT_WIELD; i < PLAYER_INVENTORY_SIZE; i++) { if (inventory[i].category_id == TV_NOTHING) { continue; } itemDescription(description, inventory[i], true); (void) fprintf(file1, " %c) %-19s: %s\n", itemSlotID + 'a', equipmentPlacementDescription(i), description); itemSlotID++; } (void) fprintf(file1, "%c\n\n", CTRL_KEY('L')); } // Write out the character's inventory. static void writeInventoryToFile(FILE *file1) { (void) fprintf(file1, " [General Inventory List]\n\n"); if (py.unique_inventory_items == 0) { (void) fprintf(file1, " Character has no objects in inventory.\n"); return; } obj_desc_t description = {'\0'}; for (int i = 0; i < py.unique_inventory_items; i++) { itemDescription(description, inventory[i], true); (void) fprintf(file1, "%c) %s\n", i + 'a', description); } (void) fprintf(file1, "%c", CTRL_KEY('L')); } // Print the character to a file or device -RAK- bool outputPlayerCharacterToFile(char *filename) { int fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0644); if (fd < 0 && errno == EEXIST) { if (getInputConfirmation("Replace existing file " + std::string(filename) + "?")) { fd = open(filename, O_WRONLY, 0644); } } FILE *file; if (fd >= 0) { // on some non-unix machines, fdopen() is not reliable, // hence must call close() and then fopen(). (void) close(fd); file = fopen(filename, "w"); } else { file = nullptr; } if (file == nullptr) { if (fd >= 0) { (void) close(fd); } vtype_t msg = {'\0'}; (void) sprintf(msg, "Can't open file %s:", filename); printMessage(msg); return false; } writeCharacterSheetToFile(file); writeEquipmentListToFile(file); writeInventoryToFile(file); (void) fclose(file); putStringClearToEOL("Completed.", Coord_t{0, 0}); return true; } umoria-5.7.10+20181022/src/player_traps.cpp0000644000175000017500000001413013363422757016760 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. // Player functions related to traps #include "headers.h" static int playerTrapDisarmAbility() { int ability = py.misc.disarm; ability += 2; ability *= playerDisarmAdjustment(); ability += playerStatAdjustmentWisdomIntelligence(py_attrs::A_INT); ability += class_level_adj[py.misc.class_id][py_class_level_adj::CLASS_DISARM] * py.misc.level / 3; if (py.flags.blind > 0 || playerNoLight()) { ability = ability / 10; } if (py.flags.confused > 0) { ability = ability / 10; } if (py.flags.image > 0) { ability = ability / 10; } return ability; } static void playerDisarmFloorTrap(int y, int x, int total, int level, int dir, int16_t misc_use) { int confused = py.flags.confused; if (total + 100 - level > randomNumber(100)) { printMessage("You have disarmed the trap."); py.misc.exp += misc_use; (void) dungeonDeleteObject(Coord_t{y, x});; // make sure we move onto the trap even if confused py.flags.confused = 0; playerMove(dir, false); py.flags.confused = (int16_t) confused; displayCharacterExperience(); return; } // avoid randomNumber(0) call if (total > 5 && randomNumber(total) > 5) { printMessageNoCommandInterrupt("You failed to disarm the trap."); return; } printMessage("You set the trap off!"); // make sure we move onto the trap even if confused py.flags.confused = 0; playerMove(dir, false); py.flags.confused += confused; } static void playerDisarmChestTrap(int y, int x, int total, Inventory_t &item) { if (!spellItemIdentified(item)) { game.player_free_turn = true; printMessage("I don't see a trap."); return; } if ((item.flags & config::treasure::chests::CH_TRAPPED) != 0u) { int level = item.depth_first_found; if ((total - level) > randomNumber(100)) { item.flags &= ~config::treasure::chests::CH_TRAPPED; if ((item.flags & config::treasure::chests::CH_LOCKED) != 0u) { item.special_name_id = special_name_ids::SN_LOCKED; } else { item.special_name_id = special_name_ids::SN_DISARMED; } printMessage("You have disarmed the chest."); spellItemIdentifyAndRemoveRandomInscription(item); py.misc.exp += level; displayCharacterExperience(); } else if ((total > 5) && (randomNumber(total) > 5)) { printMessageNoCommandInterrupt("You failed to disarm the chest."); } else { printMessage("You set a trap off!"); spellItemIdentifyAndRemoveRandomInscription(item); chestTrap(y, x); } return; } printMessage("The chest was not trapped."); game.player_free_turn = true; } // Disarms a trap -RAK- void playerDisarmTrap() { int dir; if (!getDirectionWithMemory(CNIL, dir)) { return; } int y = py.row; int x = py.col; (void) playerMovePosition(dir, y, x); Tile_t const &tile = dg.floor[y][x]; bool no_disarm = false; if (tile.creature_id > 1 && tile.treasure_id != 0 && (treasure_list[tile.treasure_id].category_id == TV_VIS_TRAP || treasure_list[tile.treasure_id].category_id == TV_CHEST)) { objectBlockedByMonster(tile.creature_id); } else if (tile.treasure_id != 0) { int disarm_ability = playerTrapDisarmAbility(); Inventory_t &item = treasure_list[tile.treasure_id]; if (item.category_id == TV_VIS_TRAP) { playerDisarmFloorTrap(y, x, disarm_ability, item.depth_first_found, dir, item.misc_use); } else if (item.category_id == TV_CHEST) { playerDisarmChestTrap(y, x, disarm_ability, item); } else { no_disarm = true; } } else { no_disarm = true; } if (no_disarm) { printMessage("I do not see anything to disarm there."); game.player_free_turn = true; } } static void chestLooseStrength() { printMessage("A small needle has pricked you!"); if (py.flags.sustain_str) { printMessage("You are unaffected."); return; } (void) playerStatRandomDecrease(py_attrs::A_STR); playerTakesHit(diceRoll(Dice_t{1, 4}), "a poison needle"); printMessage("You feel weakened!"); } static void chestPoison() { printMessage("A small needle has pricked you!"); playerTakesHit(diceRoll(Dice_t{1, 6}), "a poison needle"); py.flags.poisoned += 10 + randomNumber(20); } static void chestParalysed() { printMessage("A puff of yellow gas surrounds you!"); if (py.flags.free_action) { printMessage("You are unaffected."); return; } printMessage("You choke and pass out."); py.flags.paralysis = (int16_t) (10 + randomNumber(20)); } static void chestSummonMonster(int y, int x) { for (int i = 0; i < 3; i++) { int cy = y; int cx = x; (void) monsterSummon(cy, cx, false); } } static void chestExplode(int y, int x) { printMessage("There is a sudden explosion!"); (void) dungeonDeleteObject(Coord_t{y, x});; playerTakesHit(diceRoll(Dice_t{5, 8}), "an exploding chest"); } // Chests have traps too. -RAK- // Note: Chest traps are based on the FLAGS value void chestTrap(int y, int x) { uint32_t flags = treasure_list[dg.floor[y][x].treasure_id].flags; if ((flags & config::treasure::chests::CH_LOSE_STR) != 0u) { chestLooseStrength(); } if ((flags & config::treasure::chests::CH_POISON) != 0u) { chestPoison(); } if ((flags & config::treasure::chests::CH_PARALYSED) != 0u) { chestParalysed(); } if ((flags & config::treasure::chests::CH_SUMMON) != 0u) { chestSummonMonster(y, x); } if ((flags & config::treasure::chests::CH_EXPLODE) != 0u) { chestExplode(y, x); } } umoria-5.7.10+20181022/src/player_run.cpp0000644000175000017500000003527213363422757016445 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. // The running algorithm: -CJS- #include "headers.h" // Overview: You keep moving until something interesting happens. If you are in // an enclosed space, you follow corners. This is the usual corridor scheme. If // you are in an open space, you go straight, but stop before entering enclosed // space. This is analogous to reaching doorways. If you have enclosed space on // one side only (that is, running along side a wall) stop if your wall opens // out, or your open space closes in. Either case corresponds to a doorway. // // What happens depends on what you can really SEE. (i.e. if you have no light, // then running along a dark corridor is JUST like running in a dark room.) The // algorithm works equally well in corridors, rooms, mine tailings, earthquake // rubble, etc, etc. // // These conditions are kept in static memory: // find_openarea You are in the open on at least one // side. // find_breakleft You have a wall on the left, and will // stop if it opens // find_breakright You have a wall on the right, and will // stop if it opens // // To initialize these conditions is the task of playerFindInitialize. If moving from the // square marked @ to the square marked . (in the two diagrams below), then two // adjacent squares on the left and the right (L and R) are considered. If either // one is seen to be closed, then that side is considered to be closed. If both // sides are closed, then it is an enclosed (corridor) run. // // LL L // @. L.R // RR @R // // Looking at more than just the immediate squares is significant. Consider the // following case. A run along the corridor will stop just before entering the // center point, because a choice is clearly established. Running in any of three // available directions will be defined as a corridor run. Note that a minor hack // is inserted to make the angled corridor entry (with one side blocked near and // the other side blocked further away from the runner) work correctly. The // runner moves diagonally, but then saves the previous direction as being // straight into the gap. Otherwise, the tail end of the other entry would be // perceived as an alternative on the next move. // // #.# // ##.## // .@... // ##.## // #.# // // Likewise, a run along a wall, and then into a doorway (two runs) will work // correctly. A single run rightwards from @ will stop at 1. Another run right // and down will enter the corridor and make the corner, stopping at the 2. // // #@ 1 // ########### ###### // 2 # // ############# // # // // After any move, the function playerAreaAffect is called to determine the new // surroundings, and the direction of subsequent moves. It takes a location (at // which the runner has just arrived) and the previous direction (from which the // runner is considered to have come). Moving one square in some direction places // you adjacent to three or five new squares (for straight and diagonal moves) to // which you were not previously adjacent. // // ...! ... EG Moving from 1 to 2. // .12! .1.! . means previously adjacent // ...! ..2! ! means newly adjacent // !!! // // You STOP if you can't even make the move in the chosen direction. You STOP if // any of the new squares are interesting in any way: usually containing monsters // or treasure. You STOP if any of the newly adjacent squares seem to be open, // and you are also looking for a break on that side. (i.e. find_openarea AND // find_break) You STOP if any of the newly adjacent squares do NOT seem to be // open and you are in an open area, and that side was previously entirely open. // // Corners: If you are not in the open (i.e. you are in a corridor) and there is // only one way to go in the new squares, then turn in that direction. If there // are more than two new ways to go, STOP. If there are two ways to go, and those // ways are separated by a square which does not seem to be open, then STOP. // // Otherwise, we have a potential corner. There are two new open squares, which // are also adjacent. One of the new squares is diagonally located, the other is // straight on (as in the diagram). We consider two more squares further out // (marked below as ?). // // .X // @.? // #? // // If they are both seen to be closed, then it is seen that no benefit is gained // from moving straight. It is a known corner. To cut the corner, go diagonally, // otherwise go straight, but pretend you stepped diagonally into that next // location for a full view next time. Conversely, if one of the ? squares is not // seen to be closed, then there is a potential choice. We check to see whether // it is a potential corner or an intersection/room entrance. If the square two // spaces straight ahead, and the space marked with 'X' are both blank, then it // is a potential corner and enter if run_examine_corners is set, otherwise must stop // because it is not a corner. // The cycle lists the directions in anticlockwise order, for over two complete // cycles. The chome array maps a direction on to its position in the cycle. -CJS- static int cycle[] = {1, 2, 3, 6, 9, 8, 7, 4, 1, 2, 3, 6, 9, 8, 7, 4, 1}; static int chome[] = {-1, 8, 9, 10, 7, -1, 11, 6, 5, 4}; static bool find_openarea, find_breakright, find_breakleft; static int find_prevdir; static int find_direction; // Keep a record of which way we are going. // Do we see a wall? Used in running. -CJS- static bool playerCanSeeDungeonWall(int dir, int y, int x) { // check to see if movement there possible if (!playerMovePosition(dir, y, x)) { return true; } char c = caveGetTileSymbol(Coord_t{y, x}); return c == '#' || c == '%'; } // Do we see anything? Used in running. -CJS- static bool playerSeeNothing(int dir, int y, int x) { // check to see if movement there possible return playerMovePosition(dir, y, x) && caveGetTileSymbol(Coord_t{y, x}) == ' '; } static void findRunningBreak(int dir, int row, int col) { bool deepLeft = false; bool deepRight = false; bool shortLeft = false; bool shortRight = false; int cycleIndex = chome[dir]; if (playerCanSeeDungeonWall(cycle[cycleIndex + 1], py.row, py.col)) { find_breakleft = true; shortLeft = true; } else if (playerCanSeeDungeonWall(cycle[cycleIndex + 1], row, col)) { find_breakleft = true; deepLeft = true; } if (playerCanSeeDungeonWall(cycle[cycleIndex - 1], py.row, py.col)) { find_breakright = true; shortRight = true; } else if (playerCanSeeDungeonWall(cycle[cycleIndex - 1], row, col)) { find_breakright = true; deepRight = true; } if (find_breakleft && find_breakright) { find_openarea = false; // a hack to allow angled corridor entry if ((dir & 1) != 0) { if (deepLeft && !deepRight) { find_prevdir = cycle[cycleIndex - 1]; } else if (deepRight && !deepLeft) { find_prevdir = cycle[cycleIndex + 1]; } } else if (playerCanSeeDungeonWall(cycle[cycleIndex], row, col)) { // else if there is a wall two spaces ahead and seem to be in a // corridor, then force a turn into the side corridor, must // be moving straight into a corridor here if (shortLeft && !shortRight) { find_prevdir = cycle[cycleIndex - 2]; } else if (shortRight && !shortLeft) { find_prevdir = cycle[cycleIndex + 2]; } } } else { find_openarea = true; } } void playerFindInitialize(int direction) { int y = py.row; int x = py.col; if (!playerMovePosition(direction, y, x)) { py.running_tracker = 0; } else { py.running_tracker = 1; find_direction = direction; find_prevdir = direction; find_breakright = false; find_breakleft = false; if (py.flags.blind < 1) { findRunningBreak(direction, y, x); } } // We must erase the player symbol '@' here, because sub3_move_light() // does not erase the previous location of the player when in find mode // and when `run_print_self` is false. The player symbol is not draw at all // in this case while moving, so the only problem is on the first turn // of find mode, when the initial position of the character must be erased. // Hence we must do the erasure here. if (!py.temporary_light_only && !config::options::run_print_self) { panelPutTile(caveGetTileSymbol(Coord_t{py.row, py.col}), Coord_t{py.row, py.col}); } playerMove(direction, true); if (py.running_tracker == 0) { game.command_count = 0; } } void playerRunAndFind() { // prevent infinite loops in find mode, will stop after moving 100 times if (py.running_tracker++ > 100) { printMessage("You stop running to catch your breath."); playerEndRunning(); return; } playerMove(find_direction, true); } // Switch off the run flag - and get the light correct. -CJS- void playerEndRunning() { if (py.running_tracker == 0) { return; } py.running_tracker = 0; dungeonMoveCharacterLight(Coord_t{py.row, py.col}, Coord_t{py.row, py.col}); } static bool areaAffectStopLookingAtSquares(int i, int dir, int new_dir, int y, int x, int &check_dir, int &option1, int &option2) { Tile_t const &tile = dg.floor[y][x]; // Default: Square unseen. Treat as open. bool invisible = true; if (py.carrying_light || tile.temporary_light || tile.permanent_light || tile.field_mark) { if (tile.treasure_id != 0) { int tileID = treasure_list[tile.treasure_id].category_id; if (tileID != TV_INVIS_TRAP && tileID != TV_SECRET_DOOR && (tileID != TV_OPEN_DOOR || !config::options::run_ignore_doors)) { playerEndRunning(); return true; } } // Also Creatures // The monster should be visible since monsterUpdateVisibility() checks // for the special case of being in find mode if (tile.creature_id > 1 && monsters[tile.creature_id].lit) { playerEndRunning(); return true; } invisible = false; } if (tile.feature_id <= MAX_OPEN_SPACE || invisible) { if (find_openarea) { // Have we found a break? if (i < 0) { if (find_breakright) { playerEndRunning(); return true; } } else if (i > 0) { if (find_breakleft) { playerEndRunning(); return true; } } } else if (option1 == 0) { // The first new direction. option1 = new_dir; } else if (option2 != 0) { // Three new directions. STOP. playerEndRunning(); return true; } else if (option1 != cycle[chome[dir] + i - 1]) { // If not adjacent to prev, STOP playerEndRunning(); return true; } else { // Two adjacent choices. Make option2 the diagonal, and // remember the other diagonal adjacent to the first option. if ((new_dir & 1) == 1) { check_dir = cycle[chome[dir] + i - 2]; option2 = new_dir; } else { check_dir = cycle[chome[dir] + i + 1]; option2 = option1; option1 = new_dir; } } } else if (find_openarea) { // We see an obstacle. In open area, STOP if on a side previously open. if (i < 0) { if (find_breakleft) { playerEndRunning(); return true; } find_breakright = true; } else if (i > 0) { if (find_breakright) { playerEndRunning(); return true; } find_breakleft = true; } } return false; } // Determine the next direction for a run, or if we should stop. -CJS- void playerAreaAffect(int direction, int y, int x) { if (py.flags.blind >= 1) { return; } int check_dir = 0; int option = 0; int option2 = 0; direction = find_prevdir; int max = (direction & 1) + 1; // Look at every newly adjacent square. for (int i = -max; i <= max; i++) { int new_dir = cycle[chome[direction] + i]; int row = y; int col = x; // Objects player can see (Including doors?) cause a stop. if (playerMovePosition(new_dir, row, col)) { areaAffectStopLookingAtSquares(i, direction, new_dir, row, col, check_dir, option, option2); } } if (find_openarea) { return; } // choose a direction. if (option2 == 0 || (config::options::run_examine_corners && !config::options::run_cut_corners)) { // There is only one option, or if two, then we always examine // potential corners and never cur known corners, so you step // into the straight option. if (option != 0) { find_direction = option; } if (option2 == 0) { find_prevdir = option; } else { find_prevdir = option2; } return; } // Two options! int row = y; int col = x; (void) playerMovePosition(option, row, col); if (!playerCanSeeDungeonWall(option, row, col) || !playerCanSeeDungeonWall(check_dir, row, col)) { // Don't see that it is closed off. This could be a // potential corner or an intersection. if (config::options::run_examine_corners && playerSeeNothing(option, row, col) && playerSeeNothing(option2, row, col)) { // Can not see anything ahead and in the direction we are // turning, assume that it is a potential corner. find_direction = option; find_prevdir = option2; } else { // STOP: we are next to an intersection or a room playerEndRunning(); } } else if (config::options::run_cut_corners) { // This corner is seen to be enclosed; we cut the corner. find_direction = option2; find_prevdir = option2; } else { // This corner is seen to be enclosed, and we deliberately // go the long way. find_direction = option; find_prevdir = option2; } } umoria-5.7.10+20181022/src/dungeon_tile.h0000644000175000017500000000355713363422757016407 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. #pragma once // Tile_t holds data about a specific tile in the dungeon. typedef struct { uint8_t creature_id; // ID for any creature occupying the tile uint8_t treasure_id; // ID for any treasure item occupying the tile uint8_t feature_id; // ID of cave feature; walls, floors, open space, etc. bool perma_lit_room : 1; // Room should be lit with perm light, walls with this set should be perm lit after tunneled out. bool field_mark : 1; // Field mark, used for traps/doors/stairs, object is hidden if fm is false. bool permanent_light : 1; // Permanent light, used for walls and lighted rooms. bool temporary_light : 1; // Temporary light, used for player's lamp light,etc. } Tile_t; // `fval` definitions: these describe the various types of dungeon floors and // walls, if numbers above 15 are ever used, then the test against MIN_CAVE_WALL // will have to be changed, also the save routines will have to be changed. constexpr uint8_t TILE_NULL_WALL = 0; constexpr uint8_t TILE_DARK_FLOOR = 1; constexpr uint8_t TILE_LIGHT_FLOOR = 2; constexpr uint8_t MAX_CAVE_ROOM = 2; constexpr uint8_t TILE_CORR_FLOOR = 3; constexpr uint8_t TILE_BLOCKED_FLOOR = 4; // a corridor space with cl/st/se door or rubble constexpr uint8_t MAX_CAVE_FLOOR = 4; constexpr uint8_t MAX_OPEN_SPACE = 3; constexpr uint8_t MIN_CLOSED_SPACE = 4; constexpr uint8_t TMP1_WALL = 8; constexpr uint8_t TMP2_WALL = 9; constexpr uint8_t MIN_CAVE_WALL = 12; constexpr uint8_t TILE_GRANITE_WALL = 12; constexpr uint8_t TILE_MAGMA_WALL = 13; constexpr uint8_t TILE_QUARTZ_WALL = 14; constexpr uint8_t TILE_BOUNDARY_WALL = 15; umoria-5.7.10+20181022/src/dice.h0000644000175000017500000000064513363422757014632 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. #pragma once typedef struct { uint8_t dice; uint8_t sides; } Dice_t; int diceRoll(Dice_t const &dice); int maxDiceRoll(Dice_t const &dice); umoria-5.7.10+20181022/src/dungeon_los.cpp0000644000175000017500000004552713363422757016605 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. // LOS (line-of-sight) and Looking functions #include "headers.h" // A simple, fast, integer-based line-of-sight algorithm. By Joseph Hall, // 4116 Brewster Drive, Raleigh NC 27606. Email to jnh@ecemwl.ncsu.edu. // // Returns true if a line of sight can be traced from x0, y0 to x1, y1. // // The LOS begins at the center of the tile [x0, y0] and ends at the center of // the tile [x1, y1]. If los() is to return true, all of the tiles this line // passes through must be transparent, WITH THE EXCEPTIONS of the starting and // ending tiles. // // We don't consider the line to be "passing through" a tile if it only passes // across one corner of that tile. // Because this function uses (short) ints for all calculations, overflow may // occur if deltaX and deltaY exceed 90. bool los(int from_y, int from_x, int to_y, int to_x) { int delta_x = to_x - from_x; int delta_y = to_y - from_y; // Adjacent? if (delta_x < 2 && delta_x > -2 && delta_y < 2 && delta_y > -2) { return true; } // Handle the cases where delta_x or delta_y == 0. if (delta_x == 0) { if (delta_y < 0) { int tmp = from_y; from_y = to_y; to_y = tmp; } for (int yy = from_y + 1; yy < to_y; yy++) { if (dg.floor[yy][from_x].feature_id >= MIN_CLOSED_SPACE) { return false; } } return true; } if (delta_y == 0) { if (delta_x < 0) { int tmp = from_x; from_x = to_x; to_x = tmp; } for (int xx = from_x + 1; xx < to_x; xx++) { if (dg.floor[from_y][xx].feature_id >= MIN_CLOSED_SPACE) { return false; } } return true; } // Now, we've eliminated all the degenerate cases. // In the computations below, dy (or dx) and m are multiplied by a scale factor, // scale = abs(delta_x * delta_y * 2), so that we can use integer arithmetic. { int xx; // x position int yy; // y position int scale; // above scale factor int scale_half; // above scale factor / 2 int x_sign; // sign of delta_x int y_sign; // sign of delta_y int slope; // slope or 1/slope of LOS int delta_multiply = delta_x * delta_y; scale_half = (int) std::abs((std::intmax_t) delta_multiply); scale = scale_half << 1; x_sign = delta_x < 0 ? -1 : 1; y_sign = delta_y < 0 ? -1 : 1; // Travel from one end of the line to the other, oriented along the longer axis. auto abs_delta_x = (int) std::abs((std::intmax_t) delta_x); auto abs_delta_y = (int) std::abs((std::intmax_t) delta_y); if (abs_delta_x >= abs_delta_y) { int dy; // "fractional" y position // We start at the border between the first and second tiles, where // the y offset = .5 * slope. Remember the scale factor. // // We have: slope = delta_y / delta_x * 2 * (delta_y * delta_x) // = 2 * delta_y * delta_y. dy = delta_y * delta_y; slope = dy << 1; xx = from_x + x_sign; // Consider the special case where slope == 1. if (dy == scale_half) { yy = from_y + y_sign; dy -= scale; } else { yy = from_y; } while ((to_x - xx) != 0) { if (dg.floor[yy][xx].feature_id >= MIN_CLOSED_SPACE) { return false; } dy += slope; if (dy < scale_half) { xx += x_sign; } else if (dy > scale_half) { yy += y_sign; if (dg.floor[yy][xx].feature_id >= MIN_CLOSED_SPACE) { return false; } xx += x_sign; dy -= scale; } else { // This is the case, dy == scale_half, where the LOS // exactly meets the corner of a tile. xx += x_sign; yy += y_sign; dy -= scale; } } return true; } int dx; // "fractional" x position dx = delta_x * delta_x; slope = dx << 1; yy = from_y + y_sign; if (dx == scale_half) { xx = from_x + x_sign; dx -= scale; } else { xx = from_x; } while ((to_y - yy) != 0) { if (dg.floor[yy][xx].feature_id >= MIN_CLOSED_SPACE) { return false; } dx += slope; if (dx < scale_half) { yy += y_sign; } else if (dx > scale_half) { xx += x_sign; if (dg.floor[yy][xx].feature_id >= MIN_CLOSED_SPACE) { return false; } yy += y_sign; dx -= scale; } else { xx += x_sign; yy += y_sign; dx -= scale; } } return true; } } /* An enhanced look, with peripheral vision. Looking all 8 -CJS- directions will see everything which ought to be visible. Can specify direction 5, which looks in all directions. For the purpose of hindering vision, each place is regarded as a diamond just touching its four immediate neighbours. A diamond is opaque if it is a wall, or shut door, or something like that. A place is visible if any part of its diamond is visible: i.e. there is a line from the view point to part of the diamond which does not pass through any opaque diamonds. Consider the following situation: @.... X X X X X .##.. / \ / \ / \ / \ / \ ..... X @ X . X . X 1 X . X \ / \ / \ / \ / \ / X X X X X Expanded view, with / \ / \ / \ / \ / \ diamonds inscribed X . X # X # X 2 X . X about each point, \ / \ / \ / \ / \ / and some locations X X X X X numbered. / \ / \ / \ / \ / \ X . X . X . X 3 X 4 X \ / \ / \ / \ / \ / X X X X X - Location 1 is fully visible. - Location 2 is visible, even though partially obscured. - Location 3 is invisible, but if either # were transparent, it would be visible. - Location 4 is completely obscured by a single #. The function which does the work is lookRay(). It sets up its own co-ordinate frame (global variables map back to the dungeon frame) and looks for everything between two angles specified from a central line. It is recursive, and each call looks at stuff visible along a line parallel to the center line, and a set distance away from it. A diagonal look uses more extreme peripheral vision from the closest horizontal and vertical directions; horizontal or vertical looks take a call for each side of the central line. Globally accessed variables: los_num_places_seen counts the number of places where something is seen. los_rocks_and_objects indicates a look for rock or objects. The others map coords in the ray frame to dungeon coords. dungeon y = py.row + los_fyx * (ray x) + los_fyy * (ray y) dungeon x = py.col + los_fxx * (ray x) + los_fxy * (ray y) */ static int los_fxx, los_fxy, los_fyx, los_fyy; static int los_num_places_seen; static bool los_hack_no_query; static int los_rocks_and_objects; // Intended to be indexed by dir/2, since is only // relevant to horizontal or vertical directions. static int los_dir_set_fxy[] = {0, 1, 0, 0, -1}; static int los_dir_set_fxx[] = {0, 0, -1, 1, 0}; static int los_dir_set_fyy[] = {0, 0, 1, -1, 0}; static int los_dir_set_fyx[] = {0, 1, 0, 0, -1}; // Map diagonal-dir/2 to a normal-dir/2. static int los_map_diagonals1[] = {1, 3, 0, 2, 4}; static int los_map_diagonals2[] = {2, 1, 0, 4, 3}; #define GRADF 10000 // Any sufficiently big number will do static bool lookRay(int y, int from, int to); static bool lookSee(int x, int y, bool &transparent); // Look at what we can see. This is a free move. // // Prompts for a direction, and then looks at every object in turn within a cone of // vision in that direction. For each object, the cursor is moved over the object, // a description is given, and we wait for the user to type something. Typing // ESCAPE will abort the entire look. // // Looks first at real objects and monsters, and looks at rock types only after all // other things have been seen. Only looks at rock types if the config::options::highlight_seams // option is set. void look() { if (py.flags.blind > 0) { printMessage("You can't see a damn thing!"); return; } if (py.flags.image > 0) { printMessage("You can't believe what you are seeing! It's like a dream!"); return; } int dir; if (!getAllDirections("Look which direction?", dir)) { return; } los_num_places_seen = 0; los_rocks_and_objects = 0; // Have to set this up for the lookSee los_hack_no_query = false; bool dummy; if (lookSee(0, 0, dummy)) { return; } bool abort; do { abort = false; if (dir == 5) { for (int i = 1; i <= 4; i++) { los_fxx = los_dir_set_fxx[i]; los_fyx = los_dir_set_fyx[i]; los_fxy = los_dir_set_fxy[i]; los_fyy = los_dir_set_fyy[i]; if (lookRay(0, 2 * GRADF - 1, 1)) { abort = true; break; } los_fxy = -los_fxy; los_fyy = -los_fyy; if (lookRay(0, 2 * GRADF, 2)) { abort = true; break; } } } else if ((dir & 1) == 0) { // Straight directions int i = dir >> 1; los_fxx = los_dir_set_fxx[i]; los_fyx = los_dir_set_fyx[i]; los_fxy = los_dir_set_fxy[i]; los_fyy = los_dir_set_fyy[i]; if (lookRay(0, GRADF, 1)) { abort = true; } else { los_fxy = -los_fxy; los_fyy = -los_fyy; abort = lookRay(0, GRADF, 2); } } else { int i = los_map_diagonals1[dir >> 1]; los_fxx = los_dir_set_fxx[i]; los_fyx = los_dir_set_fyx[i]; los_fxy = -los_dir_set_fxy[i]; los_fyy = -los_dir_set_fyy[i]; if (lookRay(1, 2 * GRADF, GRADF)) { abort = true; } else { i = los_map_diagonals2[dir >> 1]; los_fxx = los_dir_set_fxx[i]; los_fyx = los_dir_set_fyx[i]; los_fxy = los_dir_set_fxy[i]; los_fyy = los_dir_set_fyy[i]; abort = lookRay(1, 2 * GRADF - 1, GRADF); } } } while (!abort && config::options::highlight_seams && (++los_rocks_and_objects < 2)); if (abort) { printMessage("--Aborting look--"); return; } if (los_num_places_seen != 0) { if (dir == 5) { printMessage("That's all you see."); } else { printMessage("That's all you see in that direction."); } } else if (dir == 5) { printMessage("You see nothing of interest."); } else { printMessage("You see nothing of interest in that direction."); } } // Look at everything within a cone of vision between two ray lines emanating // from the player, and y or more places away from the direct line of view. // This is recursive. // // Rays are specified by gradients, y over x, multiplied by 2*GRADF. This is ONLY // called with gradients between 2*GRADF (45 degrees) and 1 (almost horizontal). // // (y axis)/ angle from // ^ / ___ angle to // | / ___ // ...|../.....___.................... parameter y (look at things in the // | / ___ cone, and on or above this line) // |/ ___ // @--------------------> direction in which you are looking. (x axis) // | // | static bool lookRay(int y, int from, int to) { // from is the larger angle of the ray, since we scan towards the // center line. If from is smaller, then the ray does not exist. if (from <= to || y > config::monsters::MON_MAX_SIGHT) { return false; } // Find first visible location along this line. Minimum x such // that (2x-1)/x < from/GRADF <=> x > GRADF(2x-1)/from. This may // be called with y=0 whence x will be set to 0. Thus we need a // special fix. auto x = (int) ((int32_t) GRADF * (2 * y - 1) / from + 1); if (x <= 0) { x = 1; } // Find last visible location along this line. // Maximum x such that (2x+1)/x > to/GRADF <=> x < GRADF(2x+1)/to auto max_x = (int) (((int32_t) GRADF * (2 * y + 1) - 1) / to); if (max_x > config::monsters::MON_MAX_SIGHT) { max_x = config::monsters::MON_MAX_SIGHT; } if (max_x < x) { return false; } // los_hack_no_query is a HACK to prevent doubling up on direct lines of // sight. If 'to' is greater than 1, we do not really look at // stuff along the direct line of sight, but we do have to see // what is opaque for the purposes of obscuring other objects. los_hack_no_query = (y == 0 && to > 1) || (y == x && from < GRADF * 2); bool transparent; if (lookSee(x, y, transparent)) { return true; } if (y == x) { los_hack_no_query = false; } if (transparent) { goto init_transparent; } while (true) { // Look down the window we've found. if (lookRay(y + 1, from, ((2 * y + 1) * (int32_t) GRADF / x))) { return true; } // Find the start of next window. do { if (x == max_x) { return false; } // See if this seals off the scan. (If y is zero, then it will.) from = ((2 * y - 1) * (int32_t) GRADF / x); if (from <= to) { return false; } x++; if (lookSee(x, y, transparent)) { return true; } } while (!transparent); init_transparent: // Find the end of this window of visibility. do { if (x == max_x) { // The window is trimmed by an earlier limit. return lookRay(y + 1, from, to); } x++; if (lookSee(x, y, transparent)) { return true; } } while (transparent); } } static bool lookSee(int x, int y, bool &transparent) { if (x < 0 || y < 0 || y > x) { obj_desc_t errorMessage = {'\0'}; (void) sprintf(errorMessage, "Illegal call to lookSee(%d, %d)", x, y); printMessage(errorMessage); } const char *description = nullptr; if (x == 0 && y == 0) { description = "You are on"; } else { description = "You see"; } int j = py.col + los_fxx * x + los_fxy * y; y = py.row + los_fyx * x + los_fyy * y; x = j; if (!coordInsidePanel(Coord_t{y, x})) { transparent = false; return false; } Tile_t const &tile = dg.floor[y][x]; transparent = tile.feature_id <= MAX_OPEN_SPACE; if (los_hack_no_query) { return false; // Don't look at a direct line of sight. A hack. } // This was uninitialized but the `query == ESCAPE` below was causing // a warning. Perhaps we can set it to `ESCAPE` here as default. -MRC- char query = ESCAPE; obj_desc_t msg = {'\0'}; if (los_rocks_and_objects == 0 && tile.creature_id > 1 && monsters[tile.creature_id].lit) { j = monsters[tile.creature_id].creature_id; (void) sprintf(msg, "%s %s %s. [(r)ecall]", description, isVowel(creatures_list[j].name[0]) ? "an" : "a", creatures_list[j].name); description = "It is on"; putStringClearToEOL(msg, Coord_t{0, 0}); panelMoveCursor(Coord_t{y, x}); query = getKeyInput(); if (query == 'r' || query == 'R') { terminalSaveScreen(); query = (char) memoryRecall(j); terminalRestoreScreen(); } } if (tile.temporary_light || tile.permanent_light || tile.field_mark) { const char *wall_description; if (tile.treasure_id != 0) { if (treasure_list[tile.treasure_id].category_id == TV_SECRET_DOOR) { goto granite; } if (los_rocks_and_objects == 0 && treasure_list[tile.treasure_id].category_id != TV_INVIS_TRAP) { obj_desc_t obj_string = {'\0'}; itemDescription(obj_string, treasure_list[tile.treasure_id], true); (void) sprintf(msg, "%s %s ---pause---", description, obj_string); description = "It is in"; putStringClearToEOL(msg, Coord_t{0, 0}); panelMoveCursor(Coord_t{y, x}); query = getKeyInput(); } } if (((los_rocks_and_objects != 0) || (msg[0] != 0)) && tile.feature_id >= MIN_CLOSED_SPACE) { switch (tile.feature_id) { case TILE_BOUNDARY_WALL: case TILE_GRANITE_WALL: granite: // Granite is only interesting if it contains something. if (msg[0] != 0) { wall_description = "a granite wall"; } else { wall_description = CNIL; // In case we jump here } break; case TILE_MAGMA_WALL: wall_description = "some dark rock"; break; case TILE_QUARTZ_WALL: wall_description = "a quartz vein"; break; default: wall_description = CNIL; break; } if (wall_description != nullptr) { (void) sprintf(msg, "%s %s ---pause---", description, wall_description); putStringClearToEOL(msg, Coord_t{0, 0}); panelMoveCursor(Coord_t{y, x}); query = getKeyInput(); } } } if (msg[0] != 0) { los_num_places_seen++; if (query == ESCAPE) { return true; } } return false; } umoria-5.7.10+20181022/src/player_move.cpp0000644000175000017500000004144113363422757016602 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. // Game initialization and maintenance related functions #include "headers.h" static void trapOpenPit(Inventory_t const &item, int dam) { printMessage("You fell into a pit!"); if (py.flags.free_fall) { printMessage("You gently float down."); return; } obj_desc_t description = {'\0'}; itemDescription(description, item, true); playerTakesHit(dam, description); } static void trapArrow(Inventory_t const &item, int dam) { if (playerTestBeingHit(125, 0, 0, py.misc.ac + py.misc.magical_ac, CLASS_MISC_HIT)) { obj_desc_t description = {'\0'}; itemDescription(description, item, true); playerTakesHit(dam, description); printMessage("An arrow hits you."); return; } printMessage("An arrow barely misses you."); } static void trapCoveredPit(Inventory_t const &item, int dam, int y, int x) { printMessage("You fell into a covered pit."); if (py.flags.free_fall) { printMessage("You gently float down."); } else { obj_desc_t description = {'\0'}; itemDescription(description, item, true); playerTakesHit(dam, description); } dungeonSetTrap(Coord_t{y, x}, 0); } static void trapDoor(Inventory_t const &item, int dam) { dg.generate_new_level = true; dg.current_level++; printMessage("You fell through a trap door!"); if (py.flags.free_fall) { printMessage("You gently float down."); } else { obj_desc_t description = {'\0'}; itemDescription(description, item, true); playerTakesHit(dam, description); } // Force the messages to display before starting to generate the next level. printMessage(CNIL); } static void trapSleepingGas() { if (py.flags.paralysis != 0) { return; } printMessage("A strange white mist surrounds you!"); if (py.flags.free_action) { printMessage("You are unaffected."); return; } py.flags.paralysis += randomNumber(10) + 4; printMessage("You fall asleep."); } static void trapHiddenObject(int y, int x) { (void) dungeonDeleteObject(Coord_t{y, x});; dungeonPlaceRandomObjectAt(Coord_t{y, x}, false); printMessage("Hmmm, there was something under this rock."); } static void trapStrengthDart(Inventory_t const &item, int dam) { if (playerTestBeingHit(125, 0, 0, py.misc.ac + py.misc.magical_ac, CLASS_MISC_HIT)) { if (!py.flags.sustain_str) { (void) playerStatRandomDecrease(py_attrs::A_STR); obj_desc_t description = {'\0'}; itemDescription(description, item, true); playerTakesHit(dam, description); printMessage("A small dart weakens you!"); } else { printMessage("A small dart hits you."); } } else { printMessage("A small dart barely misses you."); } } static void trapTeleport(int y, int x) { teleport_player = true; printMessage("You hit a teleport trap!"); // Light up the teleport trap, before we teleport away. dungeonMoveCharacterLight(Coord_t{y, x}, Coord_t{y, x}); } static void trapRockfall(int y, int x, int dam) { playerTakesHit(dam, "a falling rock"); (void) dungeonDeleteObject(Coord_t{y, x});; dungeonPlaceRubble(Coord_t{y, x}); printMessage("You are hit by falling rock."); } static void trapCorrodeGas() { // Makes more sense to print the message first, then damage an object. printMessage("A strange red gas surrounds you."); damageCorrodingGas("corrosion gas"); } static void trapSummonMonster(int y, int x) { // Rune disappears. (void) dungeonDeleteObject(Coord_t{y, x});; int num = 2 + randomNumber(3); for (int i = 0; i < num; i++) { int ty = y; int tx = x; (void) monsterSummon(ty, tx, false); } } static void trapFire(int dam) { // Makes more sense to print the message first, then damage an object. printMessage("You are enveloped in flames!"); damageFire(dam, "a fire trap"); } static void trapAcid(int dam) { // Makes more sense to print the message first, then damage an object. printMessage("You are splashed with acid!"); damageAcid(dam, "an acid trap"); } static void trapPoisonGas(int dam) { // Makes more sense to print the message first, then damage an object. printMessage("A pungent green gas surrounds you!"); damagePoisonedGas(dam, "a poison gas trap"); } static void trapBlindGas() { printMessage("A black gas surrounds you!"); py.flags.blind += randomNumber(50) + 50; } static void trapConfuseGas() { printMessage("A gas of scintillating colors surrounds you!"); py.flags.confused += randomNumber(15) + 15; } static void trapSlowDart(Inventory_t const &item, int dam) { if (playerTestBeingHit(125, 0, 0, py.misc.ac + py.misc.magical_ac, CLASS_MISC_HIT)) { obj_desc_t description = {'\0'}; itemDescription(description, item, true); playerTakesHit(dam, description); printMessage("A small dart hits you!"); if (py.flags.free_action) { printMessage("You are unaffected."); } else { py.flags.slow += randomNumber(20) + 10; } } else { printMessage("A small dart barely misses you."); } } static void trapConstitutionDart(Inventory_t const &item, int dam) { if (playerTestBeingHit(125, 0, 0, py.misc.ac + py.misc.magical_ac, CLASS_MISC_HIT)) { if (!py.flags.sustain_con) { (void) playerStatRandomDecrease(py_attrs::A_CON); obj_desc_t description = {'\0'}; itemDescription(description, item, true); playerTakesHit(dam, description); printMessage("A small dart saps your health!"); } else { printMessage("A small dart hits you."); } } else { printMessage("A small dart barely misses you."); } } enum class TrapTypes { open_pit = 1, arrow_pit, covered_pit, trap_door, sleeping_gas, hidden_object, dart_of_str, teleport, rockfall, corroding_gas, summon_monster, fire_trap, acid_trap, poison_gas, blinding_gas, confuse_gas, slow_dart, dart_of_con, secret_door, scare_monster = 99, general_store = 101, armory, weaponsmith, temple, alchemist, magic_shop, }; // Player hit a trap. (Chuckle) -RAK- static void playerStepsOnTrap(int y, int x) { playerEndRunning(); trapChangeVisibility(Coord_t{y, x}); Inventory_t const &item = treasure_list[dg.floor[y][x].treasure_id]; int damage = diceRoll(item.damage); switch ((TrapTypes) item.sub_category_id) { case TrapTypes::open_pit: trapOpenPit(item, damage); break; case TrapTypes::arrow_pit: trapArrow(item, damage); break; case TrapTypes::covered_pit: trapCoveredPit(item, damage, y, x); break; case TrapTypes::trap_door: trapDoor(item, damage); break; case TrapTypes::sleeping_gas: trapSleepingGas(); break; case TrapTypes::hidden_object: trapHiddenObject(y, x); break; case TrapTypes::dart_of_str: trapStrengthDart(item, damage); break; case TrapTypes::teleport: trapTeleport(y, x); break; case TrapTypes::rockfall: trapRockfall(y, x, damage); break; case TrapTypes::corroding_gas: trapCorrodeGas(); break; case TrapTypes::summon_monster: trapSummonMonster(y, x); break; case TrapTypes::fire_trap: trapFire(damage); break; case TrapTypes::acid_trap: trapAcid(damage); break; case TrapTypes::poison_gas: trapPoisonGas(damage); break; case TrapTypes::blinding_gas: trapBlindGas(); break; case TrapTypes::confuse_gas: trapConfuseGas(); break; case TrapTypes::slow_dart: trapSlowDart(item, damage); break; case TrapTypes::dart_of_con: trapConstitutionDart(item, damage); break; case TrapTypes::secret_door: break; case TrapTypes::scare_monster: break; // Town level traps are special, the stores. case TrapTypes::general_store: storeEnter(0); break; case TrapTypes::armory: storeEnter(1); break; case TrapTypes::weaponsmith: storeEnter(2); break; case TrapTypes::temple: storeEnter(3); break; case TrapTypes::alchemist: storeEnter(4); break; case TrapTypes::magic_shop: storeEnter(5); break; default: printMessage("Unknown trap value."); break; } } static bool playerRandomMovement(int dir) { // Never random if sitting if (dir == 5) { return false; } // 75% random movement bool playerRandomMove = randomNumber(4) > 1; bool playerIsConfused = py.flags.confused > 0; return playerIsConfused && playerRandomMove; } // Player is on an object. Many things can happen based -RAK- // on the TVAL of the object. Traps are set off, money and most objects // are picked up. Some objects, such as open doors, just sit there. static void carry(int y, int x, bool pickup) { Inventory_t &item = treasure_list[dg.floor[y][x].treasure_id]; int tileFlags = treasure_list[dg.floor[y][x].treasure_id].category_id; if (tileFlags > TV_MAX_PICK_UP) { if (tileFlags == TV_INVIS_TRAP || tileFlags == TV_VIS_TRAP || tileFlags == TV_STORE_DOOR) { // OOPS! playerStepsOnTrap(y, x); } return; } obj_desc_t description = {'\0'}; obj_desc_t msg = {'\0'}; playerEndRunning(); // There's GOLD in them thar hills! if (tileFlags == TV_GOLD) { py.misc.au += item.cost; itemDescription(description, item, true); (void) sprintf(msg, "You have found %d gold pieces worth of %s", item.cost, description); printCharacterGoldValue(); (void) dungeonDeleteObject(Coord_t{y, x});; printMessage(msg); return; } // Too many objects? if (inventoryCanCarryItemCount(item)) { // Okay, pick it up if (pickup && config::options::prompt_to_pickup) { itemDescription(description, item, true); // change the period to a question mark description[strlen(description) - 1] = '?'; pickup = getInputConfirmation("Pick up " + std::string(description)); } // Check to see if it will change the players speed. if (pickup && !inventoryCanCarryItem(item)) { itemDescription(description, item, true); // change the period to a question mark description[strlen(description) - 1] = '?'; pickup = getInputConfirmation("Exceed your weight limit to pick up " + std::string(description)); } // Attempt to pick up an object. if (pickup) { int locn = inventoryCarryItem(item); itemDescription(description, inventory[locn], true); (void) sprintf(msg, "You have %s (%c)", description, locn + 'a'); printMessage(msg); (void) dungeonDeleteObject(Coord_t{y, x});; } } else { itemDescription(description, item, true); (void) sprintf(msg, "You can't carry %s", description); printMessage(msg); } } // Moves player from one space to another. -RAK- void playerMove(int direction, bool do_pickup) { if (playerRandomMovement(direction)) { direction = randomNumber(9); playerEndRunning(); } int y = py.row; int x = py.col; // Legal move? if (!playerMovePosition(direction, y, x)) { return; } Tile_t const &tile = dg.floor[y][x]; Monster_t const &monster = monsters[tile.creature_id]; // if there is no creature, or an unlit creature in the walls then... // disallow attacks against unlit creatures in walls because moving into // a wall is a free turn normally, hence don't give player free turns // attacking each wall in an attempt to locate the invisible creature, // instead force player to tunnel into walls which always takes a turn if (tile.creature_id < 2 || (!monster.lit && tile.feature_id >= MIN_CLOSED_SPACE)) { // Open floor spot if (tile.feature_id <= MAX_OPEN_SPACE) { // Make final assignments of char coords int old_row = py.row; int old_col = py.col; py.row = (int16_t) y; py.col = (int16_t) x; // Move character record (-1) dungeonMoveCreatureRecord(Coord_t{old_row, old_col}, Coord_t{py.row, py.col}); // Check for new panel if (coordOutsidePanel(Coord_t{py.row, py.col}, false)) { drawDungeonPanel(); } // Check to see if they should stop if (py.running_tracker != 0) { playerAreaAffect(direction, py.row, py.col); } // Check to see if they've noticed something // fos may be negative if have good rings of searching if (py.misc.fos <= 1 || randomNumber(py.misc.fos) == 1 || ((py.flags.status & config::player::status::PY_SEARCH) != 0u)) { playerSearch(py.row, py.col, py.misc.chance_in_search); } if (tile.feature_id == TILE_LIGHT_FLOOR) { // A room of light should be lit. if (!tile.permanent_light && (py.flags.blind == 0)) { dungeonLightRoom(Coord_t{py.row, py.col}); } } else if (tile.perma_lit_room && py.flags.blind < 1) { // In doorway of light-room? for (int row = (py.row - 1); row <= (py.row + 1); row++) { for (int col = (py.col - 1); col <= (py.col + 1); col++) { if (dg.floor[row][col].feature_id == TILE_LIGHT_FLOOR && !dg.floor[row][col].permanent_light) { dungeonLightRoom(Coord_t{row, col}); } } } } // Move the light source dungeonMoveCharacterLight(Coord_t{old_row, old_col}, Coord_t{py.row, py.col}); // An object is beneath them. if (tile.treasure_id != 0) { carry(py.row, py.col, do_pickup); // if stepped on falling rock trap, and space contains // rubble, then step back into a clear area if (treasure_list[tile.treasure_id].category_id == TV_RUBBLE) { dungeonMoveCreatureRecord(Coord_t{py.row, py.col}, Coord_t{old_row, old_col}); dungeonMoveCharacterLight(Coord_t{py.row, py.col}, Coord_t{old_row, old_col}); py.row = (int16_t) old_row; py.col = (int16_t) old_col; // check to see if we have stepped back onto another trap, if so, set it off uint8_t id = dg.floor[py.row][py.col].treasure_id; if (id != 0) { int val = treasure_list[id].category_id; if (val == TV_INVIS_TRAP || val == TV_VIS_TRAP || val == TV_STORE_DOOR) { playerStepsOnTrap(py.row, py.col); } } } } } else { // Can't move onto floor space if ((py.running_tracker == 0) && tile.treasure_id != 0) { if (treasure_list[tile.treasure_id].category_id == TV_RUBBLE) { printMessage("There is rubble blocking your way."); } else if (treasure_list[tile.treasure_id].category_id == TV_CLOSED_DOOR) { printMessage("There is a closed door blocking your way."); } } else { playerEndRunning(); } game.player_free_turn = true; } } else { // Attacking a creature! int old_find_flag = py.running_tracker; playerEndRunning(); // if player can see monster, and was in find mode, then nothing if (monster.lit && (old_find_flag != 0)) { // did not do anything this turn game.player_free_turn = true; } else { playerAttackPosition(y, x); } } } umoria-5.7.10+20181022/src/player.h0000644000175000017500000002644213363422757015225 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. #pragma once enum py_class_level_adj { CLASS_BTH, CLASS_BTHB, CLASS_DEVICE, CLASS_DISARM, CLASS_SAVE, }; // Attribute indexes -CJS- enum py_attrs { A_STR, A_INT, A_WIS, A_DEX, A_CON, A_CHR, }; // this depends on the fact that py_class_level_adj::CLASS_SAVE values are all the same, // if not, then should add a separate column for this constexpr uint8_t CLASS_MISC_HIT = 4; constexpr uint8_t CLASS_MAX_LEVEL_ADJUST = 5; // Player constants constexpr uint8_t PLAYER_MAX_LEVEL = 40; // Maximum possible character level constexpr uint8_t PLAYER_MAX_CLASSES = 6; // Number of defined classes constexpr uint8_t PLAYER_MAX_RACES = 8; // Number of defined races constexpr uint8_t PLAYER_MAX_BACKGROUNDS = 128; // Number of types of histories for univ // Base to hit constants constexpr uint8_t BTH_PER_PLUS_TO_HIT_ADJUST = 3; // Adjust BTH per plus-to-hit constexpr uint8_t PLAYER_NAME_SIZE = 27; // ClassRankTitle_t of the player: Novice, Mage (5th), Paladin, etc. // Currently only used by the `playerRankTitle()` function. // TODO: perhaps use a plain std::string instead? typedef const char *ClassRankTitle_t; // Player_t contains everything to be known about our player character typedef struct { struct { char name[PLAYER_NAME_SIZE]; // Name of character bool gender; // Gender of character (Female = 0, Male = 1) int32_t date_of_birth; // Unix time for when the character was created int32_t au; // Gold int32_t max_exp; // Max experience int32_t exp; // Cur experience uint16_t exp_fraction; // Cur exp fraction * 2^16 uint16_t age; // Characters age uint16_t height; // Height uint16_t weight; // Weight uint16_t level; // Level uint16_t max_dungeon_depth; // Max level explored int16_t chance_in_search; // Chance in search int16_t fos; // Frequency of search int16_t bth; // Base to hit int16_t bth_with_bows; // BTH with bows int16_t mana; // Mana points int16_t max_hp; // Max hit pts int16_t plusses_to_hit; // Plusses to hit int16_t plusses_to_damage; // Plusses to dam int16_t ac; // Total AC int16_t magical_ac; // Magical AC int16_t display_to_hit; // Display +ToHit int16_t display_to_damage; // Display +ToDam int16_t display_ac; // Display +ToTAC int16_t display_to_ac; // Display +ToAC int16_t disarm; // % to Disarm int16_t saving_throw; // Saving throw int16_t social_class; // Social Class int16_t stealth_factor; // Stealth factor uint8_t class_id; // # of class uint8_t race_id; // # of race uint8_t hit_die; // Char hit die uint8_t experience_factor; // Experience factor int16_t current_mana; // Current mana points uint16_t current_mana_fraction; // Current mana fraction * 2^16 int16_t current_hp; // Current hit points uint16_t current_hp_fraction; // Current hit points fraction * 2^16 char history[4][60]; // History record } misc; // Stats now kept in arrays, for more efficient access. -CJS- struct { uint8_t max[6]; // What is restored uint8_t current[6]; // What is natural int16_t modified[6]; // What is modified, may be +/- uint8_t used[6]; // What is used } stats; struct { uint32_t status; // Status of player int16_t rest; // Rest counter int16_t blind; // Blindness counter int16_t paralysis; // Paralysis counter int16_t confused; // Confusion counter int16_t food; // Food counter int16_t food_digested; // Food per round int16_t protection; // Protection fr. evil int16_t speed; // Cur speed adjust int16_t fast; // Temp speed change int16_t slow; // Temp speed change int16_t afraid; // Fear int16_t poisoned; // Poisoned int16_t image; // Hallucinate int16_t protect_evil; // Protect VS evil int16_t invulnerability; // Increases AC int16_t heroism; // Heroism int16_t super_heroism; // Super Heroism int16_t blessed; // Blessed int16_t heat_resistance; // Timed heat resist int16_t cold_resistance; // Timed cold resist int16_t detect_invisible; // Timed see invisible int16_t word_of_recall; // Timed teleport level int16_t see_infra; // See warm creatures int16_t timed_infra; // Timed infra vision bool see_invisible; // Can see invisible bool teleport; // Random teleportation bool free_action; // Never paralyzed bool slow_digest; // Lower food needs bool aggravate; // Aggravate monsters bool resistant_to_fire; // Resistance to fire bool resistant_to_cold; // Resistance to cold bool resistant_to_acid; // Resistance to acid bool regenerate_hp; // Regenerate hit pts bool resistant_to_light; // Resistance to light bool free_fall; // No damage falling bool sustain_str; // Keep strength bool sustain_int; // Keep intelligence bool sustain_wis; // Keep wisdom bool sustain_con; // Keep constitution bool sustain_dex; // Keep dexterity bool sustain_chr; // Keep charisma bool confuse_monster; // Glowing hands. uint8_t new_spells_to_learn; // Number of spells can learn. uint32_t spells_learnt; // bit mask of spells learned uint32_t spells_worked; // bit mask of spells tried and worked uint32_t spells_forgotten; // bit mask of spells learned but forgotten uint8_t spells_learned_order[32]; // order spells learned/remembered/forgotten } flags; // location in dungeon int16_t row; int16_t col; // calculated base hp values at each level, store them so that // drain life + restore life does not affect hit points. uint16_t base_hp_levels[PLAYER_MAX_LEVEL]; // Base experience levels, may be adjusted up for race and/or class uint32_t base_exp_levels[PLAYER_MAX_LEVEL]; uint8_t running_tracker; // Tracker for number of turns taken during one run cycle bool temporary_light_only; // Track if temporary light about player int32_t max_score = 0; // Maximum score attained // TODO: create a pack/inventory object? int16_t unique_inventory_items = 0; int16_t inventory_weight = 0; // Current carried weight int16_t pack_heaviness; // Heaviness of pack - used to calculate if pack is too heavy -CJS- // TODO: create an equipment object? int16_t equipment_count = 0; // Number of equipped items bool weapon_is_heavy; // Weapon is too heavy -CJS- bool carrying_light; // `true` when player is carrying light } Player_t; extern Player_t py; extern bool teleport_player; extern ClassRankTitle_t class_rank_titles[PLAYER_MAX_CLASSES][PLAYER_MAX_LEVEL]; extern Race_t character_races[PLAYER_MAX_RACES]; extern Background_t character_backgrounds[PLAYER_MAX_BACKGROUNDS]; extern Class_t classes[PLAYER_MAX_CLASSES]; extern int16_t class_level_adj[PLAYER_MAX_CLASSES][CLASS_MAX_LEVEL_ADJUST]; extern uint16_t class_base_provisions[PLAYER_MAX_CLASSES][5]; extern uint8_t blows_table[7][6]; bool playerIsMale(); void playerSetGender(bool is_male); const char *playerGetGenderLabel(); bool playerMovePosition(int dir, int &new_y, int &new_x); void playerTeleport(int new_distance); bool playerNoLight(); void playerDisturb(int major_disturbance, int light_disturbance); void playerSearchOn(); void playerSearchOff(); void playerRestOn(); void playerRestOff(); void playerDiedFromString(vtype_t *description, const char *monster_name, uint32_t move); bool playerTestAttackHits(int attack_id, uint8_t level); void playerChangeSpeed(int speed); void playerAdjustBonusesForItem(Inventory_t const &item, int factor); void playerRecalculateBonuses(); void playerTakeOff(int item_id, int pack_position_id); bool playerTestBeingHit(int base_to_hit, int level, int plus_to_hit, int armor_class, int attack_type_id); void playerTakesHit(int damage, const char *creature_name); void playerSearch(int y, int x, int chance); int playerCarryingLoadLimit(); void playerStrength(); void playerGainSpells(); void playerGainMana(int stat); int playerWeaponCriticalBlow(int weapon_weight, int plus_to_hit, int damage, int attack_type_id); bool playerSavingThrow(); void playerGainKillExperience(Creature_t const &creature); void playerOpenClosedObject(); void playerCloseDoor(); bool playerTunnelWall(int y, int x, int digging_ability, int digging_chance); void playerAttackPosition(int y, int x); void playerCalculateAllowedSpellsCount(int stat); char *playerRankTitle(); // player_eat.cpp void playerEat(); void playerIngestFood(int amount); // player_bash.cpp void playerBash(); // player_magic.cpp bool playerCureConfusion(); bool playerCureBlindness(); bool playerCurePoison(); bool playerRemoveFear(); bool playerProtectEvil(); void playerBless(int adjustment); void playerDetectInvisible(int adjustment); int itemMagicAbilityDamage(Inventory_t const &item, int total_damage, int monster_id); // player_move.cpp void playerMove(int direction, bool do_pickup); // player_run.cpp void playerFindInitialize(int direction); void playerRunAndFind(); void playerEndRunning(); void playerAreaAffect(int direction, int y, int x); // player_stats.cpp void playerInitializeBaseExperienceLevels(); void playerCalculateHitPoints(); int playerAttackBlows(int weight, int &weight_to_hit); int playerStatAdjustmentWisdomIntelligence(int stat); int playerStatAdjustmentCharisma(); int playerStatAdjustmentConstitution(); void playerSetAndUseStat(int stat); bool playerStatRandomIncrease(int stat); bool playerStatRandomDecrease(int stat); bool playerStatRestore(int stat); void playerStatBoost(int stat, int amount); int playerToHitAdjustment(); int playerArmorClassAdjustment(); int16_t playerDisarmAdjustment(); int playerDamageAdjustment(); // player_throw.cpp void playerThrowItem(); // player_traps.cpp void playerDisarmTrap(); void chestTrap(int y, int x); // player_tunnel.cpp void playerTunnel(int direction); // player_quaff.cpp void quaff(); // player_pray.cpp void pray(); umoria-5.7.10+20181022/src/ui_inventory.cpp0000644000175000017500000012564613363422757017024 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. #include "headers.h" static void inventoryItemWeightText(char *text, int itemID) { int totalWeight = inventory[itemID].weight * inventory[itemID].items_count; int quotient = totalWeight / 10; int remainder = totalWeight % 10; (void) sprintf(text, "%3d.%d lb", quotient, remainder); } // Displays inventory items from `item_id_start` to `item_id_end` -RAK- // Designed to keep the display as far to the right as possible. -CJS- // The parameter col gives a column at which to start, but if the display // does not fit, it may be moved left. The return value is the left edge // used. If mask is non-zero, then only display those items which have a // non-zero entry in the mask array. int displayInventory(int item_id_start, int item_id_end, bool weighted, int column, const char *mask) { vtype_t descriptions[23]; int len = 79 - column; int lim; if (weighted) { lim = 68; } else { lim = 76; } // Generate the descriptions text for (int i = item_id_start; i <= item_id_end; i++) { if (mask != CNIL && (mask[i] == 0)) { continue; } obj_desc_t description = {'\0'}; itemDescription(description, inventory[i], true); // Truncate if too long. description[lim] = 0; (void) sprintf(descriptions[i], "%c) %s", 'a' + i, description); int l = (int) strlen(descriptions[i]) + 2; if (weighted) { l += 9; } if (l > len) { len = l; } } column = 79 - len; if (column < 0) { column = 0; } int current_line = 1; // Print the descriptions for (int i = item_id_start; i <= item_id_end; i++) { if (mask != CNIL && (mask[i] == 0)) { continue; } // don't need first two spaces if in first column if (column == 0) { putStringClearToEOL(descriptions[i], Coord_t{current_line, column}); } else { putString(" ", Coord_t{current_line, column}); putStringClearToEOL(descriptions[i], Coord_t{current_line, column + 2}); } if (weighted) { obj_desc_t text = {'\0'}; inventoryItemWeightText(text, i); putStringClearToEOL(text, Coord_t{current_line, 71}); } current_line++; } return column; } // Return a string describing how a given equipment item is carried. -CJS- const char *playerItemWearingDescription(int body_location) { switch (body_location) { case player_equipment::EQUIPMENT_WIELD: return "wielding"; case player_equipment::EQUIPMENT_HEAD: return "wearing on your head"; case player_equipment::EQUIPMENT_NECK: return "wearing around your neck"; case player_equipment::EQUIPMENT_BODY: return "wearing on your body"; case player_equipment::EQUIPMENT_ARM: return "wearing on your arm"; case player_equipment::EQUIPMENT_HANDS: return "wearing on your hands"; case player_equipment::EQUIPMENT_RIGHT: return "wearing on your right hand"; case player_equipment::EQUIPMENT_LEFT: return "wearing on your left hand"; case player_equipment::EQUIPMENT_FEET: return "wearing on your feet"; case player_equipment::EQUIPMENT_OUTER: return "wearing about your body"; case player_equipment::EQUIPMENT_LIGHT: return "using to light the way"; case player_equipment::EQUIPMENT_AUX: return "holding ready by your side"; default: return "carrying in your pack"; } } static const char *itemPositionDescription(int positionID, uint16_t weight) { switch (positionID) { case player_equipment::EQUIPMENT_WIELD: if (py.stats.used[py_attrs::A_STR] * 15 < weight) { return "Just lifting"; } return "Wielding"; case player_equipment::EQUIPMENT_HEAD: return "On head"; case player_equipment::EQUIPMENT_NECK: return "Around neck"; case player_equipment::EQUIPMENT_BODY: return "On body"; case player_equipment::EQUIPMENT_ARM: return "On arm"; case player_equipment::EQUIPMENT_HANDS: return "On hands"; case player_equipment::EQUIPMENT_RIGHT: return "On right hand"; case player_equipment::EQUIPMENT_LEFT: return "On left hand"; case player_equipment::EQUIPMENT_FEET: return "On feet"; case player_equipment::EQUIPMENT_OUTER: return "About body"; case player_equipment::EQUIPMENT_LIGHT: return "Light source"; case player_equipment::EQUIPMENT_AUX: return "Spare weapon"; default: return "Unknown value"; } } // Displays equipment items from r1 to end -RAK- // Keep display as far right as possible. -CJS- int displayEquipment(bool weighted, int column) { vtype_t descriptions[PLAYER_INVENTORY_SIZE - player_equipment::EQUIPMENT_WIELD]; int len = 79 - column; int lim; if (weighted) { lim = 52; } else { lim = 60; } // Range of equipment int line = 0; for (int i = player_equipment::EQUIPMENT_WIELD; i < PLAYER_INVENTORY_SIZE; i++) { if (inventory[i].category_id == TV_NOTHING) { continue; } // Get position const char *position_description = itemPositionDescription(i, inventory[i].weight); obj_desc_t description = {'\0'}; itemDescription(description, inventory[i], true); // Truncate if necessary description[lim] = 0; (void) sprintf(descriptions[line], "%c) %-14s: %s", line + 'a', position_description, description); int l = (int) strlen(descriptions[line]) + 2; if (weighted) { l += 9; } if (l > len) { len = l; } line++; } column = 79 - len; if (column < 0) { column = 0; } // Range of equipment line = 0; for (int i = player_equipment::EQUIPMENT_WIELD; i < PLAYER_INVENTORY_SIZE; i++) { if (inventory[i].category_id == TV_NOTHING) { continue; } // don't need first two spaces when using whole screen if (column == 0) { putStringClearToEOL(descriptions[line], Coord_t{line + 1, column}); } else { putString(" ", Coord_t{line + 1, column}); putStringClearToEOL(descriptions[line], Coord_t{line + 1, column + 2}); } if (weighted) { obj_desc_t text = {'\0'}; inventoryItemWeightText(text, i); putStringClearToEOL(text, Coord_t{line + 1, 71}); } line++; } eraseLine(Coord_t{line + 1, column}); return column; } // TODO: Most of the functions below should probably not be part of the UI, // TODO: but have dependencies on "screen state" variables, so need to be here for now. // All inventory commands (wear, exchange, take off, drop, inventory and // equipment) are handled in an alternative command input mode, which accepts // any of the inventory commands. // // It is intended that this function be called several times in succession, // as some commands take up a turn, and the rest of moria must proceed in the // interim. A global variable is provided, game.doing_inventory_command, which is normally // zero; however if on return from inventoryExecuteCommand() it is expected that // inventoryExecuteCommand() should be called *again*, (being still in inventory command // input mode), then game.doing_inventory_command is set to the inventory command character // which should be used in the next call to inventoryExecuteCommand(). // // On return, the screen is restored, but not flushed. Provided no flush of // the screen takes place before the next call to inventoryExecuteCommand(), the inventory // command screen is silently redisplayed, and no actual output takes place at // all. If the screen is flushed before a subsequent call, then the player is // prompted to see if we should continue. This allows the player to see any // changes that take place on the screen during inventory command input. // // The global variable, screen_has_changed, is cleared by inventoryExecuteCommand(), and set // when the screen is flushed. This is the means by which inventoryExecuteCommand() tell // if the screen has been flushed. // // The display of inventory items is kept to the right of the screen to // minimize the work done to restore the screen afterwards. -CJS- // Inventory command screen states. constexpr int BLANK_SCR = 0; constexpr int EQUIP_SCR = 1; constexpr int INVEN_SCR = 2; constexpr int WEAR_SCR = 3; constexpr int HELP_SCR = 4; constexpr int WRONG_SCR = 5; // Keep track of the state of the inventory screen. int screen_state, screen_left, screen_base; int wear_low, wear_high; static void displayInventoryScreen(int new_screen) { if (new_screen == screen_state) { return; } screen_state = new_screen; int line; switch (new_screen) { case BLANK_SCR: line = 0; break; case HELP_SCR: if (screen_left > 52) { screen_left = 52; } putStringClearToEOL(" ESC: exit", Coord_t{1, screen_left}); putStringClearToEOL(" w : wear or wield object", Coord_t{2, screen_left}); putStringClearToEOL(" t : take off item", Coord_t{3, screen_left}); putStringClearToEOL(" d : drop object", Coord_t{4, screen_left}); putStringClearToEOL(" x : exchange weapons", Coord_t{5, screen_left}); putStringClearToEOL(" i : inventory of pack", Coord_t{6, screen_left}); putStringClearToEOL(" e : list used equipment", Coord_t{7, screen_left}); line = 7; break; case INVEN_SCR: screen_left = displayInventory(0, py.unique_inventory_items - 1, config::options::show_inventory_weights, screen_left, CNIL); line = py.unique_inventory_items; break; case WEAR_SCR: screen_left = displayInventory(wear_low, wear_high, config::options::show_inventory_weights, screen_left, CNIL); line = wear_high - wear_low + 1; break; case EQUIP_SCR: screen_left = displayEquipment(config::options::show_inventory_weights, screen_left); line = py.equipment_count; break; default: line = 0; break; } if (line >= screen_base) { screen_base = line + 1; eraseLine(Coord_t{screen_base, screen_left}); return; } while (++line <= screen_base) { eraseLine(Coord_t{line, screen_left}); } } // Used to verify if this really is the item we wish to -CJS- // wear or read. static bool verify(const char *prompt, int item) { obj_desc_t description = {'\0'}; itemDescription(description, inventory[item], true); // change the period to a question mark description[strlen(description) - 1] = '?'; obj_desc_t msg = {'\0'}; (void) sprintf(msg, "%s %s", prompt, description); return getInputConfirmation(msg); } static void setInventoryCommandScreenState(char command) { // Take up where we left off after a previous inventory command. -CJS- if (game.doing_inventory_command != 0) { // If the screen has been flushed, we need to redraw. If the command // is a simple ' ' to recover the screen, just quit. Otherwise, check // and see what the user wants. if (screen_has_changed) { if (command == ' ' || !getInputConfirmation("Continuing with inventory command?")) { game.doing_inventory_command = 0; return; } screen_left = 50; screen_base = 0; } int savedState = screen_state; screen_state = WRONG_SCR; displayInventoryScreen(savedState); return; } screen_left = 50; screen_base = 0; // this forces exit of inventoryExecuteCommand() if selecting is not set true screen_state = BLANK_SCR; } static bool inventoryTakeOffItem(bool selecting) { if (py.equipment_count == 0) { printMessage("You are not using any equipment."); // don't print message restarting inven command after taking off something, it is confusing return selecting; } if (py.unique_inventory_items >= player_equipment::EQUIPMENT_WIELD && (game.doing_inventory_command == 0)) { printMessage("You will have to drop something first."); return selecting; } if (screen_state != BLANK_SCR) { displayInventoryScreen(EQUIP_SCR); } return true; } static bool inventoryDropItem(char *command, bool selecting) { if (py.unique_inventory_items == 0 && py.equipment_count == 0) { printMessage("But you're not carrying anything."); return selecting; } if (dg.floor[py.row][py.col].treasure_id != 0) { printMessage("There's no room to drop anything here."); return selecting; } if ((screen_state == EQUIP_SCR && py.equipment_count > 0) || py.unique_inventory_items == 0) { if (screen_state != BLANK_SCR) { displayInventoryScreen(EQUIP_SCR); } *command = 'r'; // Remove - or take off and drop. } else if (screen_state != BLANK_SCR) { displayInventoryScreen(INVEN_SCR); } return true; } static bool inventoryWearWieldItem(bool selecting) { // Note: simple loop to get wear_low value for (wear_low = 0; wear_low < py.unique_inventory_items && inventory[wear_low].category_id > TV_MAX_WEAR; wear_low++); // Note: simple loop to get wear_high value for (wear_high = wear_low; wear_high < py.unique_inventory_items && inventory[wear_high].category_id >= TV_MIN_WEAR; wear_high++); wear_high--; if (wear_low > wear_high) { printMessage("You have nothing to wear or wield."); return selecting; } if (screen_state != BLANK_SCR && screen_state != INVEN_SCR) { displayInventoryScreen(WEAR_SCR); } return true; } static void inventoryUnwieldItem() { if (inventory[player_equipment::EQUIPMENT_WIELD].category_id == TV_NOTHING && inventory[player_equipment::EQUIPMENT_AUX].category_id == TV_NOTHING) { printMessage("But you are wielding no weapons."); return; } if ((inventory[player_equipment::EQUIPMENT_WIELD].flags & config::treasure::flags::TR_CURSED) != 0u) { obj_desc_t description = {'\0'}; itemDescription(description, inventory[player_equipment::EQUIPMENT_WIELD], false); obj_desc_t msg = {'\0'}; (void) sprintf(msg, "The %s you are wielding appears to be cursed.", description); printMessage(msg); return; } game.player_free_turn = false; Inventory_t savedItem = inventory[player_equipment::EQUIPMENT_AUX]; inventory[player_equipment::EQUIPMENT_AUX] = inventory[player_equipment::EQUIPMENT_WIELD]; inventory[player_equipment::EQUIPMENT_WIELD] = savedItem; if (screen_state == EQUIP_SCR) { screen_left = displayEquipment(config::options::show_inventory_weights, screen_left); } playerAdjustBonusesForItem(inventory[player_equipment::EQUIPMENT_AUX], -1); // Subtract bonuses playerAdjustBonusesForItem(inventory[player_equipment::EQUIPMENT_WIELD], 1); // Add bonuses if (inventory[player_equipment::EQUIPMENT_WIELD].category_id != TV_NOTHING) { obj_desc_t msgLabel = {'\0'}; (void) strcpy(msgLabel, "Primary weapon : "); obj_desc_t description = {'\0'}; itemDescription(description, inventory[player_equipment::EQUIPMENT_WIELD], true); printMessage(strcat(msgLabel, description)); } else { printMessage("No primary weapon."); } // this is a new weapon, so clear the heavy flag py.weapon_is_heavy = false; playerStrength(); } // look for item whose inscription matches "which" static int inventoryGetItemMatchingInscription(char which, char command, int from, int to) { int item; if (which >= '0' && which <= '9' && command != 'r' && command != 't') { int m; // Note: simple loop to get id for (m = from; m <= to && m < PLAYER_INVENTORY_SIZE && ((inventory[m].inscription[0] != which) || (inventory[m].inscription[1] != '\0')); m++); if (m <= to) { item = m; } else { item = -1; } } else if (which >= 'A' && which <= 'Z') { item = which - 'A'; } else { item = which - 'a'; } return item; } static void buildCommandHeading(char *prt1, int from, int to, const char *swap, char command, const char *prompt) { from = from + 'a'; to = to + 'a'; const char *listItems = ""; if (screen_state == BLANK_SCR) { listItems = ", * to list"; } const char *digits = ""; if (command == 'w' || command == 'd') { digits = ", 0-9"; } (void) sprintf(prt1, "(%c-%c%s%s%s, space to break, ESC to exit) %s which one?", from, to, listItems, swap, digits, prompt); } static void drawInventoryScreenForCommand(char command) { if (command == 't' || command == 'r') { displayInventoryScreen(EQUIP_SCR); } else if (command == 'w' && screen_state != INVEN_SCR) { displayInventoryScreen(WEAR_SCR); } else { displayInventoryScreen(INVEN_SCR); } } static void swapInventoryScreenForDrop() { if (screen_state == EQUIP_SCR) { displayInventoryScreen(INVEN_SCR); } else if (screen_state == INVEN_SCR) { displayInventoryScreen(EQUIP_SCR); } } static int inventoryGetSlotToWearEquipment(int item) { int slot; // Slot for equipment switch (inventory[item].category_id) { case TV_SLING_AMMO: case TV_BOLT: case TV_ARROW: case TV_BOW: case TV_HAFTED: case TV_POLEARM: case TV_SWORD: case TV_DIGGING: case TV_SPIKE: slot = player_equipment::EQUIPMENT_WIELD; break; case TV_LIGHT: slot = player_equipment::EQUIPMENT_LIGHT; break; case TV_BOOTS: slot = player_equipment::EQUIPMENT_FEET; break; case TV_GLOVES: slot = player_equipment::EQUIPMENT_HANDS; break; case TV_CLOAK: slot = player_equipment::EQUIPMENT_OUTER; break; case TV_HELM: slot = player_equipment::EQUIPMENT_HEAD; break; case TV_SHIELD: slot = player_equipment::EQUIPMENT_ARM; break; case TV_HARD_ARMOR: case TV_SOFT_ARMOR: slot = player_equipment::EQUIPMENT_BODY; break; case TV_AMULET: slot = player_equipment::EQUIPMENT_NECK; break; case TV_RING: if (inventory[player_equipment::EQUIPMENT_RIGHT].category_id == TV_NOTHING) { slot = player_equipment::EQUIPMENT_RIGHT; } else if (inventory[player_equipment::EQUIPMENT_LEFT].category_id == TV_NOTHING) { slot = player_equipment::EQUIPMENT_LEFT; } else { slot = 0; // Rings. Give choice over where they go. do { char query; if (!getCommand("Put ring on which hand (l/r/L/R)?", query)) { slot = -1; } else if (query == 'l') { slot = player_equipment::EQUIPMENT_LEFT; } else if (query == 'r') { slot = player_equipment::EQUIPMENT_RIGHT; } else { if (query == 'L') { slot = player_equipment::EQUIPMENT_LEFT; } else if (query == 'R') { slot = player_equipment::EQUIPMENT_RIGHT; } else { terminalBellSound(); } if ((slot != 0) && !verify("Replace", slot)) { slot = 0; } } } while (slot == 0); } break; default: slot = -1; printMessage("IMPOSSIBLE: I don't see how you can use that."); break; } return slot; } static void inventoryItemIsCursedMessage(int itemID) { obj_desc_t description = {'\0'}; itemDescription(description, inventory[itemID], false); obj_desc_t itemText = {'\0'}; (void) sprintf(itemText, "The %s you are ", description); if (itemID == player_equipment::EQUIPMENT_HEAD) { (void) strcat(itemText, "wielding "); } else { (void) strcat(itemText, "wearing "); } printMessage(strcat(itemText, "appears to be cursed.")); } static bool selectItemCommands(char *command, char *which, bool selecting) { int itemToTakeOff; int slot = 0; int from, to; const char *prompt = nullptr; const char *swap = nullptr; while (selecting && game.player_free_turn) { swap = ""; if (*command == 'w') { from = wear_low; to = wear_high; prompt = "Wear/Wield"; } else { from = 0; if (*command == 'd') { to = py.unique_inventory_items - 1; prompt = "Drop"; if (py.equipment_count > 0) { swap = ", / for Equip"; } } else { to = py.equipment_count - 1; if (*command == 't') { prompt = "Take off"; } else { // command == 'r' prompt = "Throw off"; if (py.unique_inventory_items > 0) { swap = ", / for Inven"; } } } } if (from > to) { selecting = false; continue; } obj_desc_t headingText = {'\0'}; buildCommandHeading(headingText, from, to, swap, *command, prompt); // Abort everything. if (!getCommand(headingText, *which)) { *which = ESCAPE; selecting = false; continue; // can we just return false from the function? -MRC- } // Draw the screen and maybe exit to main prompt. if (*which == ' ' || *which == '*') { drawInventoryScreenForCommand(*command); if (*which == ' ') { selecting = false; } continue; } // Swap screens (for drop) if (*which == '/' && (swap[0] != 0)) { if (*command == 'd') { *command = 'r'; } else { *command = 'd'; } swapInventoryScreenForDrop(); continue; } // look for item whose inscription matches "which" int item_id = inventoryGetItemMatchingInscription(*which, *command, from, to); if (item_id < from || item_id > to) { terminalBellSound(); continue; } // Found an item! if (*command == 'r' || *command == 't') { // Get its place in the equipment list. itemToTakeOff = item_id; item_id = 21; do { item_id++; if (inventory[item_id].category_id != TV_NOTHING) { itemToTakeOff--; } } while (itemToTakeOff >= 0); if ((isupper((int) *which) != 0) && !verify((char *) prompt, item_id)) { item_id = -1; } else if ((inventory[item_id].flags & config::treasure::flags::TR_CURSED) != 0u) { item_id = -1; printMessage("Hmmm, it seems to be cursed."); } else if (*command == 't' && !inventoryCanCarryItemCount(inventory[item_id])) { if (dg.floor[py.row][py.col].treasure_id != 0) { item_id = -1; printMessage("You can't carry it."); } else if (getInputConfirmation("You can't carry it. Drop it?")) { *command = 'r'; } else { item_id = -1; } } if (item_id >= 0) { if (*command == 'r') { inventoryDropItem(item_id, true); // As a safety measure, set the player's inven // weight to 0, when the last object is dropped. if (py.unique_inventory_items == 0 && py.equipment_count == 0) { py.inventory_weight = 0; } } else { slot = inventoryCarryItem(inventory[item_id]); playerTakeOff(item_id, slot); } playerStrength(); game.player_free_turn = false; if (*command == 'r') { selecting = false; } } } else if (*command == 'w') { // Wearing. Go to a bit of trouble over replacing existing equipment. if ((isupper((int) *which) != 0) && !verify((char *) prompt, item_id)) { item_id = -1; } else { slot = inventoryGetSlotToWearEquipment(item_id); if (slot == -1) { item_id = -1; } } if (item_id >= 0 && inventory[slot].category_id != TV_NOTHING) { if ((inventory[slot].flags & config::treasure::flags::TR_CURSED) != 0u) { inventoryItemIsCursedMessage(slot); item_id = -1; } else if (inventory[item_id].sub_category_id == ITEM_GROUP_MIN && inventory[item_id].items_count > 1 && !inventoryCanCarryItemCount(inventory[slot])) { // this can happen if try to wield a torch, // and have more than one in inventory printMessage("You will have to drop something first."); item_id = -1; } } // OK. Wear it. if (item_id >= 0) { game.player_free_turn = false; // first remove new item from inventory Inventory_t savedItem = inventory[item_id]; Inventory_t *item = &savedItem; wear_high--; // Fix for torches if (item->items_count > 1 && item->sub_category_id <= ITEM_SINGLE_STACK_MAX) { item->items_count = 1; wear_high++; } py.inventory_weight += item->weight * item->items_count; // Subtracts weight inventoryDestroyItem(item_id); // Second, add old item to inv and remove // from equipment list, if necessary. item = &inventory[slot]; if (item->category_id != TV_NOTHING) { int savedCounter = py.unique_inventory_items; itemToTakeOff = inventoryCarryItem(*item); // If item removed did not stack with anything // in inventory, then increment wear_high. if (py.unique_inventory_items != savedCounter) { wear_high++; } playerTakeOff(slot, itemToTakeOff); } // third, wear new item *item = savedItem; py.equipment_count++; playerAdjustBonusesForItem(*item, 1); const char *text = nullptr; if (slot == player_equipment::EQUIPMENT_WIELD) { text = "You are wielding"; } else if (slot == player_equipment::EQUIPMENT_LIGHT) { text = "Your light source is"; } else { text = "You are wearing"; } obj_desc_t description = {'\0'}; itemDescription(description, *item, true); // Get the right equipment letter. itemToTakeOff = player_equipment::EQUIPMENT_WIELD; item_id = 0; while (itemToTakeOff != slot) { if (inventory[itemToTakeOff++].category_id != TV_NOTHING) { item_id++; } } obj_desc_t msg = {'\0'}; (void) sprintf(msg, "%s %s (%c)", text, description, 'a' + item_id); printMessage(msg); // this is a new weapon, so clear heavy flag if (slot == player_equipment::EQUIPMENT_WIELD) { py.weapon_is_heavy = false; } playerStrength(); if ((item->flags & config::treasure::flags::TR_CURSED) != 0u) { printMessage("Oops! It feels deathly cold!"); itemAppendToInscription(*item, config::identification::ID_DAMD); // To force a cost of 0, even if unidentified. item->cost = -1; } } } else { // command == 'd' // NOTE: initializing to `ESCAPE` as warnings were being given. -MRC- char query = ESCAPE; if (inventory[item_id].items_count > 1) { obj_desc_t description = {'\0'}; itemDescription(description, inventory[item_id], true); description[strlen(description) - 1] = '?'; obj_desc_t msg = {'\0'}; (void) sprintf(msg, "Drop all %s [y/n]", description); msg[strlen(description) - 1] = '.'; putStringClearToEOL(msg, Coord_t{0, 0}); query = getKeyInput(); if (query != 'y' && query != 'n') { if (query != ESCAPE) { terminalBellSound(); } messageLineClear(); item_id = -1; } } else if ((isupper((int) *which) != 0) && !verify((char *) prompt, item_id)) { item_id = -1; } else { query = 'y'; } if (item_id >= 0) { game.player_free_turn = false; inventoryDropItem(item_id, query == 'y'); playerStrength(); } selecting = false; // As a safety measure, set the player's inven weight // to 0, when the last object is dropped. if (py.unique_inventory_items == 0 && py.equipment_count == 0) { py.inventory_weight = 0; } } if (!game.player_free_turn && screen_state == BLANK_SCR) { selecting = false; } } return selecting; } // Put an appropriate header. static void inventoryDisplayAppropriateHeader() { if (screen_state == INVEN_SCR) { obj_desc_t msg = {'\0'}; int weightQuotient = py.inventory_weight / 10; int weightRemainder = py.inventory_weight % 10; if (!config::options::show_inventory_weights || py.unique_inventory_items == 0) { (void) sprintf(msg, "You are carrying %d.%d pounds. In your pack there is %s", weightQuotient, weightRemainder, (py.unique_inventory_items == 0 ? "nothing." : "-") ); } else { int limitQuotient = playerCarryingLoadLimit() / 10; int limitRemainder = playerCarryingLoadLimit() % 10; (void) sprintf(msg, "You are carrying %d.%d pounds. Your capacity is %d.%d pounds. In your pack is -", weightQuotient, weightRemainder, limitQuotient, limitRemainder ); } putStringClearToEOL(msg, Coord_t{0, 0}); } else if (screen_state == WEAR_SCR) { if (wear_high < wear_low) { putStringClearToEOL("You have nothing you could wield.", Coord_t{0, 0}); } else { putStringClearToEOL("You could wield -", Coord_t{0, 0}); } } else if (screen_state == EQUIP_SCR) { if (py.equipment_count == 0) { putStringClearToEOL("You are not using anything.", Coord_t{0, 0}); } else { putStringClearToEOL("You are using -", Coord_t{0, 0}); } } else { putStringClearToEOL("Allowed commands:", Coord_t{0, 0}); } eraseLine(Coord_t{screen_base, screen_left}); } static void displayInventory() { if (py.unique_inventory_items == 0) { printMessage("You are not carrying anything."); } else { displayInventoryScreen(INVEN_SCR); } } static void displayEquipment() { if (py.equipment_count == 0) { printMessage("You are not using any equipment."); } else { displayInventoryScreen(EQUIP_SCR); } } // This does all the work. void inventoryExecuteCommand(char command) { game.player_free_turn = true; terminalSaveScreen(); setInventoryCommandScreenState(command); do { if (isupper((int) command) != 0) { command = (char) tolower((int) command); } // Simple command getting and screen selection. bool selecting = false; switch (command) { case 'i': displayInventory(); break; case 'e': displayEquipment(); break; case 't': selecting = inventoryTakeOffItem(selecting); break; case 'd': selecting = inventoryDropItem(&command, selecting); break; case 'w': selecting = inventoryWearWieldItem(selecting); break; case 'x': inventoryUnwieldItem(); break; case ' ': // Dummy command to return again to main prompt. break; case '?': displayInventoryScreen(HELP_SCR); break; default: // Nonsense command terminalBellSound(); break; } // Clear the game.doing_inventory_command flag here, instead of at beginning, so that // can use it to control when messages above appear. game.doing_inventory_command = 0; // Keep looking for objects to drop/wear/take off/throw off char which = 'z'; selecting = selectItemCommands(&command, &which, selecting); if (which == ESCAPE || screen_state == BLANK_SCR) { command = ESCAPE; } else if (!game.player_free_turn) { // Save state for recovery if they want to call us again next turn. // Otherwise, set a dummy command to recover screen. if (selecting) { game.doing_inventory_command = command; } else { game.doing_inventory_command = ' '; } // flush last message before clearing screen_has_changed and exiting printMessage(CNIL); // This lets us know if the world changes screen_has_changed = false; command = ESCAPE; } else { inventoryDisplayAppropriateHeader(); putString("e/i/t/w/x/d/?/ESC:", Coord_t{screen_base, 60}); command = getKeyInput(); eraseLine(Coord_t{screen_base, screen_left}); } } while (command != ESCAPE); if (screen_state != BLANK_SCR) { terminalRestoreScreen(); } playerRecalculateBonuses(); } // Get the ID of an item and return the CTR value of it -RAK- bool inventoryGetInputForItemId(int &command_key_id, const char *prompt, int item_id_start, int item_id_end, char *mask, const char *message) { int screen_id = 1; bool full = false; if (item_id_end > player_equipment::EQUIPMENT_WIELD) { full = true; if (py.unique_inventory_items == 0) { screen_id = 0; item_id_end = py.equipment_count - 1; } else { item_id_end = py.unique_inventory_items - 1; } } if (py.unique_inventory_items < 1 && (!full || py.equipment_count < 1)) { putStringClearToEOL("You are not carrying anything.", Coord_t{0, 0}); return false; } command_key_id = 0; bool item_found = false; bool redraw_screen = false; do { if (redraw_screen) { if (screen_id > 0) { (void) displayInventory(item_id_start, item_id_end, false, 80, mask); } else { (void) displayEquipment(false, 80); } } vtype_t description = {'\0'}; if (full) { (void) sprintf( description, "(%s: %c-%c,%s%s / for %s, or ESC) %s", (screen_id > 0 ? "Inven" : "Equip"), item_id_start + 'a', item_id_end + 'a', (screen_id > 0 ? " 0-9," : ""), (redraw_screen ? "" : " * to see,"), (screen_id > 0 ? "Equip" : "Inven"), prompt ); } else { (void) sprintf( description, "(Items %c-%c,%s%s ESC to exit) %s", item_id_start + 'a', item_id_end + 'a', (screen_id > 0 ? " 0-9," : ""), (redraw_screen ? "" : " * for inventory list,"), prompt ); } putStringClearToEOL(description, Coord_t{0, 0}); bool command_finished = false; while (!command_finished) { char which = getKeyInput(); switch (which) { case ESCAPE: screen_id = -1; command_finished = true; game.player_free_turn = true; break; case '/': if (full) { if (screen_id > 0) { if (py.equipment_count == 0) { putStringClearToEOL("But you're not using anything -more-", Coord_t{0, 0}); (void) getKeyInput(); } else { screen_id = 0; command_finished = true; if (redraw_screen) { item_id_end = py.equipment_count; while (item_id_end < py.unique_inventory_items) { item_id_end++; eraseLine(Coord_t{item_id_end, 0}); } } item_id_end = py.equipment_count - 1; } putStringClearToEOL(description, Coord_t{0, 0}); } else { if (py.unique_inventory_items == 0) { putStringClearToEOL("But you're not carrying anything -more-", Coord_t{0, 0}); (void) getKeyInput(); } else { screen_id = 1; command_finished = true; if (redraw_screen) { item_id_end = py.unique_inventory_items; while (item_id_end < py.equipment_count) { item_id_end++; eraseLine(Coord_t{item_id_end, 0}); } } item_id_end = py.unique_inventory_items - 1; } } } break; case '*': if (!redraw_screen) { command_finished = true; terminalSaveScreen(); redraw_screen = true; } break; default: // look for item whose inscription matches "which" if (which >= '0' && which <= '9' && screen_id != 0) { int m; // Note: loop to find the inventory item for (m = item_id_start; m < player_equipment::EQUIPMENT_WIELD && (inventory[m].inscription[0] != which || inventory[m].inscription[1] != '\0'); m++); if (m < player_equipment::EQUIPMENT_WIELD) { command_key_id = m; } else { command_key_id = -1; } } else if (isupper((int) which) != 0) { command_key_id = which - 'A'; } else { command_key_id = which - 'a'; } if (command_key_id >= item_id_start && command_key_id <= item_id_end && (mask == CNIL || (mask[command_key_id] != 0))) { if (screen_id == 0) { item_id_start = 21; item_id_end = command_key_id; do { // Note: a simple loop to find first inventory item while (inventory[++item_id_start].category_id == TV_NOTHING); item_id_end--; } while (item_id_end >= 0); command_key_id = item_id_start; } if ((isupper((int) which) != 0) && !verify("Try", command_key_id)) { screen_id = -1; command_finished = true; game.player_free_turn = true; break; } screen_id = -1; command_finished = true; item_found = true; } else if (message != nullptr) { printMessage(message); // Set command_finished to force redraw of the question. command_finished = true; } else { terminalBellSound(); } break; } } } while (screen_id >= 0); if (redraw_screen) { terminalRestoreScreen(); } messageLineClear(); return item_found; } umoria-5.7.10+20181022/src/player_throw.cpp0000644000175000017500000002327613363422757017005 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. // Player throw functions #include "headers.h" static void inventoryThrow(int item_id, Inventory_t *treasure) { Inventory_t *item = &inventory[item_id]; *treasure = *item; if (item->items_count > 1) { treasure->items_count = 1; item->items_count--; py.inventory_weight -= item->weight; py.flags.status |= config::player::status::PY_STR_WGT; } else { inventoryDestroyItem(item_id); } } // Obtain the hit and damage bonuses and the maximum distance for a thrown missile. static void weaponMissileFacts(Inventory_t &item, int &base_to_hit, int &plus_to_hit, int &damage, int &distance) { int weight = item.weight; if (weight < 1) { weight = 1; } // Throwing objects damage = diceRoll(item.damage) + item.to_damage; base_to_hit = py.misc.bth_with_bows * 75 / 100; plus_to_hit = py.misc.plusses_to_hit + item.to_hit; // Add this back later if the correct throwing device. -CJS- if (inventory[player_equipment::EQUIPMENT_WIELD].category_id != TV_NOTHING) { plus_to_hit -= inventory[player_equipment::EQUIPMENT_WIELD].to_hit; } distance = (((py.stats.used[py_attrs::A_STR] + 20) * 10) / weight); if (distance > 10) { distance = 10; } // multiply damage bonuses instead of adding, when have proper // missile/weapon combo, this makes them much more useful // Using Bows, slings, or crossbows? if (inventory[player_equipment::EQUIPMENT_WIELD].category_id != TV_BOW) { return; } switch (inventory[player_equipment::EQUIPMENT_WIELD].misc_use) { case 1: if (item.category_id == TV_SLING_AMMO) { // Sling and ammo base_to_hit = py.misc.bth_with_bows; plus_to_hit += 2 * inventory[player_equipment::EQUIPMENT_WIELD].to_hit; damage += inventory[player_equipment::EQUIPMENT_WIELD].to_damage; damage = damage * 2; distance = 20; } break; case 2: if (item.category_id == TV_ARROW) { // Short Bow and Arrow base_to_hit = py.misc.bth_with_bows; plus_to_hit += 2 * inventory[player_equipment::EQUIPMENT_WIELD].to_hit; damage += inventory[player_equipment::EQUIPMENT_WIELD].to_damage; damage = damage * 2; distance = 25; } break; case 3: if (item.category_id == TV_ARROW) { // Long Bow and Arrow base_to_hit = py.misc.bth_with_bows; plus_to_hit += 2 * inventory[player_equipment::EQUIPMENT_WIELD].to_hit; damage += inventory[player_equipment::EQUIPMENT_WIELD].to_damage; damage = damage * 3; distance = 30; } break; case 4: if (item.category_id == TV_ARROW) { // Composite Bow and Arrow base_to_hit = py.misc.bth_with_bows; plus_to_hit += 2 * inventory[player_equipment::EQUIPMENT_WIELD].to_hit; damage += inventory[player_equipment::EQUIPMENT_WIELD].to_damage; damage = damage * 4; distance = 35; } break; case 5: if (item.category_id == TV_BOLT) { // Light Crossbow and Bolt base_to_hit = py.misc.bth_with_bows; plus_to_hit += 2 * inventory[player_equipment::EQUIPMENT_WIELD].to_hit; damage += inventory[player_equipment::EQUIPMENT_WIELD].to_damage; damage = damage * 3; distance = 25; } break; case 6: if (item.category_id == TV_BOLT) { // Heavy Crossbow and Bolt base_to_hit = py.misc.bth_with_bows; plus_to_hit += 2 * inventory[player_equipment::EQUIPMENT_WIELD].to_hit; damage += inventory[player_equipment::EQUIPMENT_WIELD].to_damage; damage = damage * 4; distance = 35; } break; default: // NOOP break; } } static void inventoryDropOrThrowItem(int y, int x, Inventory_t *item) { int pos_y = y; int pos_x = x; bool flag = false; if (randomNumber(10) > 1) { for (int k = 0; !flag && k <= 9;) { if (coordInBounds(Coord_t{pos_y, pos_x})) { if (dg.floor[pos_y][pos_x].feature_id <= MAX_OPEN_SPACE && dg.floor[pos_y][pos_x].treasure_id == 0) { flag = true; } } if (!flag) { pos_y = y + randomNumber(3) - 2; pos_x = x + randomNumber(3) - 2; k++; } } } if (flag) { int cur_pos = popt(); dg.floor[pos_y][pos_x].treasure_id = (uint8_t) cur_pos; treasure_list[cur_pos] = *item; dungeonLiteSpot(Coord_t{pos_y, pos_x}); } else { obj_desc_t description = {'\0'}; obj_desc_t msg = {'\0'}; itemDescription(description, *item, false); (void) sprintf(msg, "The %s disappears.", description); printMessage(msg); } } // Throw an object across the dungeon. -RAK- // Note: Flasks of oil do fire damage // Note: Extra damage and chance of hitting when missiles are used // with correct weapon. i.e. wield bow and throw arrow. void playerThrowItem() { if (py.unique_inventory_items == 0) { printMessage("But you are not carrying anything."); game.player_free_turn = true; return; } int item_id; if (!inventoryGetInputForItemId(item_id, "Fire/Throw which one?", 0, py.unique_inventory_items - 1, CNIL, CNIL)) { return; } int dir; if (!getDirectionWithMemory(CNIL, dir)) { return; } itemTypeRemainingCountDescription(item_id); if (py.flags.confused > 0) { printMessage("You are confused."); dir = getRandomDirection(); } Inventory_t thrown_item{}; inventoryThrow(item_id, &thrown_item); int tbth, tpth, tdam, tdis; weaponMissileFacts(thrown_item, tbth, tpth, tdam, tdis); char tile_char = thrown_item.sprite; bool visible; int y = py.row; int x = py.col; int old_y = py.row; int old_x = py.col; int current_distance = 0; bool flag = false; while (!flag) { (void) playerMovePosition(dir, y, x); if (current_distance + 1 > tdis) { break; } current_distance++; dungeonLiteSpot(Coord_t{old_y, old_x}); Tile_t const &tile = dg.floor[y][x]; if (tile.feature_id <= MAX_OPEN_SPACE && !flag) { if (tile.creature_id > 1) { flag = true; Monster_t const &m_ptr = monsters[tile.creature_id]; tbth -= current_distance; // if monster not lit, make it much more difficult to hit, subtract // off most bonuses, and reduce bth_with_bows depending on distance. if (!m_ptr.lit) { tbth /= current_distance + 2; tbth -= py.misc.level * class_level_adj[py.misc.class_id][py_class_level_adj::CLASS_BTHB] / 2; tbth -= tpth * (BTH_PER_PLUS_TO_HIT_ADJUST - 1); } if (playerTestBeingHit(tbth, (int) py.misc.level, tpth, (int) creatures_list[m_ptr.creature_id].ac, py_class_level_adj::CLASS_BTHB)) { int damage = m_ptr.creature_id; obj_desc_t description = {'\0'}; obj_desc_t msg = {'\0'}; itemDescription(description, thrown_item, false); // Does the player know what they're fighting? if (!m_ptr.lit) { (void) sprintf(msg, "You hear a cry as the %s finds a mark.", description); visible = false; } else { (void) sprintf(msg, "The %s hits the %s.", description, creatures_list[damage].name); visible = true; } printMessage(msg); tdam = itemMagicAbilityDamage(thrown_item, tdam, damage); tdam = playerWeaponCriticalBlow((int) thrown_item.weight, tpth, tdam, py_class_level_adj::CLASS_BTHB); if (tdam < 0) { tdam = 0; } damage = monsterTakeHit((int) tile.creature_id, tdam); if (damage >= 0) { if (!visible) { printMessage("You have killed something!"); } else { (void) sprintf(msg, "You have killed the %s.", creatures_list[damage].name); printMessage(msg); } displayCharacterExperience(); } } else { inventoryDropOrThrowItem(old_y, old_x, &thrown_item); } } else { // do not test tile.field_mark here if (coordInsidePanel(Coord_t{y, x}) && py.flags.blind < 1 && (tile.temporary_light || tile.permanent_light)) { panelPutTile(tile_char, Coord_t{y, x}); putQIO(); // show object moving } } } else { flag = true; inventoryDropOrThrowItem(old_y, old_x, &thrown_item); } old_y = y; old_x = x; } } umoria-5.7.10+20181022/src/rng.cpp0000644000175000017500000000643513363422757015052 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. // Random number generator #include "headers.h" // Define this to compile as a standalone test // #define TEST_RNG // This alg uses a prime modulus multiplicative congruential generator // // (PMMLCG), also known as a Lehmer Grammer, which satisfies the following // properties // // (i) modulus: m - a large prime integer // (ii) multiplier: a - an integer in the range 2, 3, ..., m - 1 // (iii) z[n+1] = f(z[n]), for n = 1, 2, ... // (iv) f(z) = az mod m // (v) u[n] = z[n] / m, for n = 1, 2, ... // // The sequence of z's must be initialized by choosing an initial seed z[1] // from the range 1, 2, ..., m - 1. The sequence of z's is a pseudo-random // sequence drawn without replacement from the set 1, 2, ..., m - 1. // The u's form a pseudo-random sequence of real numbers between (but not // including) 0 and 1. // // Schrage's method is used to compute the sequence of z's. // Let m = aq + r, where q = m div a, and r = m mod a. // Then f(z) = az mod m = az - m * (az div m) = // = gamma(z) + m * delta(z) // Where gamma(z) = a(z mod q) - r(z div q) // and delta(z) = (z div q) - (az div m) // // If r < q, then for all z in 1, 2, ..., m - 1: // (1) delta(z) is either 0 or 1 // (2) both a(z mod q) and r(z div q) are in 0, 1, ..., m - 1 // (3) absolute value of gamma(z) <= m - 1 // (4) delta(z) = 1 iff gamma(z) < 0 // // Hence each value of z can be computed exactly without overflow as long // as m can be represented as an integer. // a good random number generator, correct on any machine with 32 bit // integers, this algorithm is from: // // Stephen K. Park and Keith W. Miller, "Random Number Generators: // Good ones are hard to find", Communications of the ACM, October 1988, // vol 31, number 10, pp. 1192-1201. // // If this algorithm is implemented correctly, // then if z[1] = 1, // then z[10001] will equal 1043618065 // // Has a full period of 2^31 - 1. // Returns integers in the range 1 to 2^31-1. constexpr int32_t RNG_M = MAX_LONG; // m = 2^31 - 1 constexpr int32_t RNG_A = 16807L; constexpr int32_t RNG_Q = RNG_M / RNG_A; // m div a 127773L constexpr int32_t RNG_R = RNG_M % RNG_A; // m mod a 2836L // 32 bit seed static uint32_t rnd_seed; uint32_t getRandomSeed() { return rnd_seed; } void setRandomSeed(uint32_t seed) { // set seed to value between 1 and m-1 rnd_seed = (uint32_t) ((seed % (RNG_M - 1)) + 1); } // returns a pseudo-random number from set 1, 2, ..., RNG_M - 1 int32_t rnd() { auto high = (int32_t) (rnd_seed / RNG_Q); auto low = (int32_t) (rnd_seed % RNG_Q); auto test = (int32_t) (RNG_A * low - RNG_R * high); if (test > 0) { rnd_seed = (uint32_t) test; } else { rnd_seed = (uint32_t) (test + RNG_M); } return rnd_seed; } #ifdef TEST_RNG main() { setRandomSeed(0L); for (int32_t i = 1; i < 10000; i++) { (void)rnd(); } int32_t random = rnd(); printf("z[10001] = %ld, should be 1043618065\n", random); if (random == 1043618065L) { printf("success!!!\n"); } } #endif umoria-5.7.10+20181022/src/data_player.cpp0000644000175000017500000005747013363422757016556 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. // Player character data // clang-format off #include "headers.h" // Class rank titles for different levels ClassRankTitle_t class_rank_titles[PLAYER_MAX_CLASSES][PLAYER_MAX_LEVEL] = { // Warrior {"Rookie", "Private", "Soldier", "Mercenary", "Veteran(1st)", "Veteran(2nd)", "Veteran(3rd)", "Warrior(1st)", "Warrior(2nd)", "Warrior(3rd)", "Warrior(4th)", "Swordsman-1", "Swordsman-2", "Swordsman-3", "Hero", "Swashbuckler", "Myrmidon", "Champion-1", "Champion-2", "Champion-3", "Superhero", "Knight", "Superior Knt", "Gallant Knt", "Knt Errant", "Guardian Knt", "Baron", "Duke", "Lord (1st)", "Lord (2nd)", "Lord (3rd)", "Lord (4th)", "Lord (5th)", "Lord (6th)", "Lord (7th)", "Lord (8th)", "Lord (9th)", "Lord Gallant", "Lord Keeper", "Lord Noble"}, // Mage {"Novice", "Apprentice", "Trickster-1", "Trickster-2", "Trickster-3", "Cabalist-1", "Cabalist-2", "Cabalist-3", "Visionist", "Phantasmist", "Shadowist", "Spellbinder", "Illusionist", "Evoker (1st)", "Evoker (2nd)", "Evoker (3rd)", "Evoker (4th)", "Conjurer", "Theurgist", "Thaumaturge", "Magician", "Enchanter", "Warlock", "Sorcerer", "Necromancer", "Mage (1st)", "Mage (2nd)", "Mage (3rd)", "Mage (4th)", "Mage (5th)", "Wizard (1st)", "Wizard (2nd)", "Wizard (3rd)", "Wizard (4th)", "Wizard (5th)", "Wizard (6th)", "Wizard (7th)", "Wizard (8th)", "Wizard (9th)", "Wizard Lord"}, // Priests {"Believer", "Acolyte(1st)", "Acolyte(2nd)", "Acolyte(3rd)", "Adept (1st)", "Adept (2nd)", "Adept (3rd)", "Priest (1st)", "Priest (2nd)", "Priest (3rd)", "Priest (4th)", "Priest (5th)", "Priest (6th)", "Priest (7th)", "Priest (8th)", "Priest (9th)", "Curate (1st)", "Curate (2nd)", "Curate (3rd)", "Curate (4th)", "Curate (5th)", "Curate (6th)", "Curate (7th)", "Curate (8th)", "Curate (9th)", "Canon (1st)", "Canon (2nd)", "Canon (3rd)", "Canon (4th)", "Canon (5th)", "Low Lama", "Lama-1", "Lama-2", "Lama-3", "High Lama", "Great Lama", "Patriarch", "High Priest", "Great Priest", "Noble Priest"}, // Rogues {"Vagabond", "Footpad", "Cutpurse", "Robber", "Burglar", "Filcher", "Sharper", "Magsman", "Common Rogue", "Rogue (1st)", "Rogue (2nd)", "Rogue (3rd)", "Rogue (4th)", "Rogue (5th)", "Rogue (6th)", "Rogue (7th)", "Rogue (8th)", "Rogue (9th)", "Master Rogue", "Expert Rogue", "Senior Rogue", "Chief Rogue", "Prime Rogue", "Low Thief", "Thief (1st)", "Thief (2nd)", "Thief (3rd)", "Thief (4th)", "Thief (5th)", "Thief (6th)", "Thief (7th)", "Thief (8th)", "Thief (9th)", "High Thief", "Master Thief", "Executioner", "Low Assassin", "Assassin", "High Assassin", "Guildsmaster"}, // Rangers {"Runner (1st)", "Runner (2nd)", "Runner (3rd)", "Strider (1st)", "Strider (2nd)", "Strider (3rd)", "Scout (1st)", "Scout (2nd)", "Scout (3rd)", "Scout (4th)", "Scout (5th)", "Courser (1st)", "Courser (2nd)", "Courser (3rd)", "Courser (4th)", "Courser (5th)", "Tracker (1st)", "Tracker (2nd)", "Tracker (3rd)", "Tracker (4th)", "Tracker (5th)", "Tracker (6th)", "Tracker (7th)", "Tracker (8th)", "Tracker (9th)", "Guide (1st)", "Guide (2nd)", "Guide (3rd)", "Guide (4th)", "Guide (5th)", "Guide (6th)", "Guide (7th)", "Guide (8th)", "Guide (9th)", "Pathfinder-1", "Pathfinder-2", "Pathfinder-3", "Ranger", "High Ranger", "Ranger Lord"}, // Paladins {"Gallant", "Keeper (1st)", "Keeper (2nd)", "Keeper (3rd)", "Keeper (4th)", "Keeper (5th)", "Keeper (6th)", "Keeper (7th)", "Keeper (8th)", "Keeper (9th)", "Protector-1", "Protector-2", "Protector-3", "Protector-4", "Protector-5", "Protector-6", "Protector-7", "Protector-8", "Defender-1", "Defender-2", "Defender-3", "Defender-4", "Defender-5", "Defender-6", "Defender-7", "Defender-8", "Warder (1st)", "Warder (2nd)", "Warder (3rd)", "Warder (4th)", "Warder (5th)", "Warder (6th)", "Warder (7th)", "Warder (8th)", "Warder (9th)", "Guardian", "Chevalier", "Justiciar", "Paladin", "High Lord"}, }; // Race, STR, INT, WIS, DEX, CON, CHR, // ages, heights, and weights (male then female) // Racial Bases for: // dis, chance_in_search, stealth_factor, fos, bth, bth_with_bows, saving_throw_base, // hit_die, infra, exp base, choice-classes Race_t character_races[PLAYER_MAX_RACES] = { { "Human", 0, 0, 0, 0, 0, 0, 14, 6, 72, 6,180, 25, 66, 4,150, 20, 0, 0, 0, 0, 0, 0, 0, 10, 0, 100, 0x3F, }, { "Half-Elf", -1, 1, 0, 1, -1, 1, 24, 16, 66, 6,130, 15, 62, 6,100, 10, 2, 6, 1, -1, -1, 5, 3, 9, 2, 110, 0x3F, }, { "Elf", -1, 2, 1, 1, -2, 1, 75, 75, 60, 4,100, 6, 54, 4, 80, 6, 5, 8, 1, -2, -5, 15, 6, 8, 3, 120, 0x1F, }, { "Halfling", -2, 2, 1, 3, 1, 1, 21, 12, 36, 3, 60, 3, 33, 3, 50, 3, 15, 12, 4, -5,-10, 20, 18, 6, 4, 110, 0x0B, }, { "Gnome", -1, 2, 0, 2, 1, -2, 50, 40, 42, 3, 90, 6, 39, 3, 75, 3, 10, 6, 3, -3, -8, 12, 12, 7, 4, 125, 0x0F, }, { "Dwarf", 2, -3, 1, -2, 2, -3, 35, 15, 48, 3,150, 10, 46, 3,120, 10, 2, 7, -1, 0, 15, 0, 9, 9, 5, 120, 0x05, }, { "Half-Orc", 2, -1, 0, 0, 1, -4, 11, 4, 66, 1,150, 5, 62, 1,120, 5, -3, 0, -1, 3, 12, -5, -3, 10, 3, 110, 0x0D, }, { "Half-Troll", 4, -4, -2, -4, 3, -6, 20, 10, 96, 10,255, 50, 84, 8,225, 40, -5, -1, -2, 5, 20,-10, -8, 12, 3, 120, 0x05, }, }; // Background information Background_t character_backgrounds[PLAYER_MAX_BACKGROUNDS] = { {"You are the illegitimate and unacknowledged child ", 10, 1, 2, 25}, {"You are the illegitimate but acknowledged child ", 20, 1, 2, 35}, {"You are one of several children ", 95, 1, 2, 45}, {"You are the first child ", 100, 1, 2, 50}, {"of a Serf. ", 40, 2, 3, 65}, {"of a Yeoman. ", 65, 2, 3, 80}, {"of a Townsman. ", 80, 2, 3, 90}, {"of a Guildsman. ", 90, 2, 3, 105}, {"of a Landed Knight. ", 96, 2, 3, 120}, {"of a Titled Noble. ", 99, 2, 3, 130}, {"You are the black sheep of the family. ", 20, 3, 50, 20}, {"You are a credit to the family. ", 80, 3, 50, 55}, {"You are a well liked child. ", 100, 3, 50, 60}, {"Your mother was a Green-Elf. ", 40, 4, 1, 50}, {"Your father was a Green-Elf. ", 75, 4, 1, 55}, {"Your mother was a Grey-Elf. ", 90, 4, 1, 55}, {"Your father was a Grey-Elf. ", 95, 4, 1, 60}, {"Your mother was a High-Elf. ", 98, 4, 1, 65}, {"Your father was a High-Elf. ", 100, 4, 1, 70}, {"You are one of several children ", 60, 7, 8, 50}, {"You are the only child ", 100, 7, 8, 55}, {"of a Green-Elf ", 75, 8, 9, 50}, {"of a Grey-Elf ", 95, 8, 9, 55}, {"of a High-Elf ", 100, 8, 9, 60}, {"Ranger. ", 40, 9, 54, 80}, {"Archer. ", 70, 9, 54, 90}, {"Warrior. ", 87, 9, 54, 110}, {"Mage. ", 95, 9, 54, 125}, {"Prince. ", 99, 9, 54, 140}, {"King. ", 100, 9, 54, 145}, {"You are one of several children of a Halfling ", 85, 10, 11, 45}, {"You are the only child of a Halfling ", 100, 10, 11, 55}, {"Bum. ", 20, 11, 3, 55}, {"Tavern Owner. ", 30, 11, 3, 80}, {"Miller. ", 40, 11, 3, 90}, {"Home Owner. ", 50, 11, 3, 100}, {"Burglar. ", 80, 11, 3, 110}, {"Warrior. ", 95, 11, 3, 115}, {"Mage. ", 99, 11, 3, 125}, {"Clan Elder. ", 100, 11, 3, 140}, {"You are one of several children of a Gnome ", 85, 13, 14, 45}, {"You are the only child of a Gnome ", 100, 13, 14, 55}, {"Beggar. ", 20, 14, 3, 55}, {"Braggart. ", 50, 14, 3, 70}, {"Prankster. ", 75, 14, 3, 85}, {"Warrior. ", 95, 14, 3, 100}, {"Mage. ", 100, 14, 3, 125}, {"You are one of two children of a Dwarven ", 25, 16, 17, 40}, {"You are the only child of a Dwarven ", 100, 16, 17, 50}, {"Thief. ", 10, 17, 18, 60}, {"Prison Guard. ", 25, 17, 18, 75}, {"Miner. ", 75, 17, 18, 90}, {"Warrior. ", 90, 17, 18, 110}, {"Priest. ", 99, 17, 18, 130}, {"King. ", 100, 17, 18, 150}, {"You are the black sheep of the family. ", 15, 18, 57, 10}, {"You are a credit to the family. ", 85, 18, 57, 50}, {"You are a well liked child. ", 100, 18, 57, 55}, {"Your mother was an Orc, but it is unacknowledged. ", 25, 19, 20, 25}, {"Your father was an Orc, but it is unacknowledged. ", 100, 19, 20, 25}, {"You are the adopted child ", 100, 20, 2, 50}, {"Your mother was a Cave-Troll ", 30, 22, 23, 20}, {"Your father was a Cave-Troll ", 60, 22, 23, 25}, {"Your mother was a Hill-Troll ", 75, 22, 23, 30}, {"Your father was a Hill-Troll ", 90, 22, 23, 35}, {"Your mother was a Water-Troll ", 95, 22, 23, 40}, {"Your father was a Water-Troll ", 100, 22, 23, 45}, {"Cook. ", 5, 23, 62, 60}, {"Warrior. ", 95, 23, 62, 55}, {"Shaman. ", 99, 23, 62, 65}, {"Clan Chief. ", 100, 23, 62, 80}, {"You have dark brown eyes, ", 20, 50, 51, 50}, {"You have brown eyes, ", 60, 50, 51, 50}, {"You have hazel eyes, ", 70, 50, 51, 50}, {"You have green eyes, ", 80, 50, 51, 50}, {"You have blue eyes, ", 90, 50, 51, 50}, {"You have blue-gray eyes, ", 100, 50, 51, 50}, {"straight ", 70, 51, 52, 50}, {"wavy ", 90, 51, 52, 50}, {"curly ", 100, 51, 52, 50}, {"black hair, ", 30, 52, 53, 50}, {"brown hair, ", 70, 52, 53, 50}, {"auburn hair, ", 80, 52, 53, 50}, {"red hair, ", 90, 52, 53, 50}, {"blond hair, ", 100, 52, 53, 50}, {"and a very dark complexion.", 10, 53, 0, 50}, {"and a dark complexion.", 30, 53, 0, 50}, {"and an average complexion.", 80, 53, 0, 50}, {"and a fair complexion.", 90, 53, 0, 50}, {"and a very fair complexion.", 100, 53, 0, 50}, {"You have light grey eyes, ", 85, 54, 55, 50}, {"You have light blue eyes, ", 95, 54, 55, 50}, {"You have light green eyes, ", 100, 54, 55, 50}, {"straight ", 75, 55, 56, 50}, {"wavy ", 100, 55, 56, 50}, {"black hair, and a fair complexion.", 75, 56, 0, 50}, {"brown hair, and a fair complexion.", 85, 56, 0, 50}, {"blond hair, and a fair complexion.", 95, 56, 0, 50}, {"silver hair, and a fair complexion.", 100, 56, 0, 50}, {"You have dark brown eyes, ", 99, 57, 58, 50}, {"You have glowing red eyes, ", 100, 57, 58, 60}, {"straight ", 90, 58, 59, 50}, {"wavy ", 100, 58, 59, 50}, {"black hair, ", 75, 59, 60, 50}, {"brown hair, ", 100, 59, 60, 50}, {"a one foot beard, ", 25, 60, 61, 50}, {"a two foot beard, ", 60, 60, 61, 51}, {"a three foot beard, ", 90, 60, 61, 53}, {"a four foot beard, ", 100, 60, 61, 55}, {"and a dark complexion.", 100, 61, 0, 50}, {"You have slime green eyes, ", 60, 62, 63, 50}, {"You have puke yellow eyes, ", 85, 62, 63, 50}, {"You have blue-bloodshot eyes, ", 99, 62, 63, 50}, {"You have glowing red eyes, ", 100, 62, 63, 55}, {"dirty ", 33, 63, 64, 50}, {"mangy ", 66, 63, 64, 50}, {"oily ", 100, 63, 64, 50}, {"sea-weed green hair, ", 33, 64, 65, 50}, {"bright red hair, ", 66, 64, 65, 50}, {"dark purple hair, ", 100, 64, 65, 50}, {"and green ", 25, 65, 66, 50}, {"and blue ", 50, 65, 66, 50}, {"and white ", 75, 65, 66, 50}, {"and black ", 100, 65, 66, 50}, {"ulcerous skin.", 33, 66, 0, 50}, {"scabby skin.", 66, 66, 0, 50}, {"leprous skin.", 100, 66, 0, 50}, {"of a Royal Blood Line. ", 100, 2, 3, 140}, }; // Classes. Class_t classes[PLAYER_MAX_CLASSES] = { // class hp dis src stl fos bth btb sve s i w d co ch spell exp spl {"Warrior", 9, 25, 14, 1, 38, 70, 55, 18, 5, -2, -2, 2, 2, -1, config::spells::SPELL_TYPE_NONE, 0, 0}, {"Mage", 0, 30, 16, 2, 20, 34, 20, 36, -5, 3, 0, 1, -2, 1, config::spells::SPELL_TYPE_MAGE, 30, 1}, {"Priest", 2, 25, 16, 2, 32, 48, 35, 30, -3, -3, 3, -1, 0, 2, config::spells::SPELL_TYPE_PRIEST, 20, 1}, {"Rogue", 6, 45, 32, 5, 16, 60, 66, 30, 2, 1, -2, 3, 1, -1, config::spells::SPELL_TYPE_MAGE, 0, 5}, {"Ranger", 4, 30, 24, 3, 24, 56, 72, 30, 2, 2, 0, 1, 1, 1, config::spells::SPELL_TYPE_MAGE, 40, 3}, {"Paladin", 6, 20, 12, 1, 38, 68, 40, 24, 3, -3, 1, 0, 2, 2, config::spells::SPELL_TYPE_PRIEST, 35, 1}, }; // making it 16 bits wastes a little space, but saves much signed/unsigned // headaches in its use. // CLASS_MISC_HIT is identical to py_class_level_adj::CLASS_SAVE, which takes advantage of // the fact that the save values are independent of the class. // Columns: bth, bth_with_bows, device, disarm, save/misc hit int16_t class_level_adj[PLAYER_MAX_CLASSES][CLASS_MAX_LEVEL_ADJUST] = { { 4, 4, 2, 2, 3 }, // Warrior { 2, 2, 4, 3, 3 }, // Mage { 2, 2, 4, 3, 3 }, // Priest { 3, 4, 3, 4, 3 }, // Rogue { 3, 4, 3, 3, 3 }, // Ranger { 3, 3, 3, 2, 3 }, // Paladin }; // Warriors don't have spells, so there is no entry for them. // Note that this means you must always subtract one from the // py.misc.class_id before indexing into magic_spells[]. Spell_t magic_spells[PLAYER_MAX_CLASSES - 1][31] = { { // Mage { 1, 1, 22, 1}, { 1, 1, 23, 1}, { 1, 2, 24, 1}, { 1, 2, 26, 1}, { 3, 3, 25, 2}, { 3, 3, 25, 1}, { 3, 3, 27, 2}, { 3, 4, 30, 1}, { 5, 4, 30, 6}, { 5, 5, 30, 8}, { 5, 5, 30, 5}, { 5, 5, 35, 6}, { 7, 6, 35, 9}, { 7, 6, 50, 10}, { 7, 6, 40, 12}, { 9, 7, 44, 19}, { 9, 7, 45, 19}, { 9, 7, 75, 22}, { 9, 7, 45, 19}, { 11, 7, 45, 25}, { 11, 7, 99, 19}, { 13, 7, 50, 22}, { 15, 9, 50, 25}, { 17, 9, 50, 31}, { 19, 12, 55, 38}, { 21, 12, 90, 44}, { 23, 12, 60, 50}, { 25, 12, 65, 63}, { 29, 18, 65, 88}, { 33, 21, 80, 125}, { 37, 25, 95, 200} }, { // Priest { 1, 1, 10, 1}, { 1, 2, 15, 1}, { 1, 2, 20, 1}, { 1, 2, 25, 1}, { 3, 2, 25, 1}, { 3, 3, 27, 2}, { 3, 3, 27, 2}, { 3, 3, 28, 3}, { 5, 4, 29, 4}, { 5, 4, 30, 5}, { 5, 4, 32, 5}, { 5, 5, 34, 5}, { 7, 5, 36, 6}, { 7, 5, 38, 7}, { 7, 6, 38, 9}, { 7, 7, 38, 9}, { 9, 6, 38, 10}, { 9, 7, 38, 10}, { 9, 7, 40, 10}, { 11, 8, 42, 10}, { 11, 8, 42, 12}, { 11, 9, 55, 15}, { 13, 10, 45, 15}, { 13, 11, 45, 16}, { 15, 12, 50, 20}, { 15, 14, 50, 22}, { 17, 14, 55, 32}, { 21, 16, 60, 38}, { 25, 20, 70, 75}, { 33, 24, 90, 125}, { 39, 32, 80, 200} }, { // Rogue { 99, 99, 0, 0}, { 5, 1, 50, 1}, { 7, 2, 55, 1}, { 9, 3, 60, 2}, { 11, 4, 65, 2}, { 13, 5, 70, 3}, { 99, 99, 0, 0}, { 15, 6, 75, 3}, { 99, 99, 0, 0}, { 17, 7, 80, 4}, { 19, 8, 85, 5}, { 21, 9, 90, 6}, { 99, 99, 0, 0}, { 23, 10, 95, 7}, { 99, 99, 0, 0}, { 99, 99, 0, 0}, { 25, 12, 95, 9}, { 27, 15, 99, 11}, { 99, 99, 0, 0}, { 99, 99, 0, 0}, { 29, 18, 99, 19}, { 99, 99, 0, 0}, { 99, 99, 0, 0}, { 99, 99, 0, 0}, { 99, 99, 0, 0}, { 99, 99, 0, 0}, { 99, 99, 0, 0}, { 99, 99, 0, 0}, { 99, 99, 0, 0}, { 99, 99, 0, 0}, { 99, 99, 0, 0}, }, { // Ranger { 3, 1, 30, 1}, { 3, 2, 35, 2}, { 3, 2, 35, 2}, { 5, 3, 35, 2}, { 5, 3, 40, 2}, { 5, 4, 45, 3}, { 7, 5, 40, 6}, { 7, 6, 40, 5}, { 9, 7, 40, 7}, { 9, 8, 45, 8}, { 11, 8, 40, 10}, { 11, 9, 45, 10}, { 13, 10, 45, 12}, { 13, 11, 55, 13}, { 15, 12, 50, 15}, { 15, 13, 50, 15}, { 17, 17, 55, 15}, { 17, 17, 90, 17}, { 21, 17, 55, 17}, { 21, 19, 60, 18}, { 23, 25, 95, 20}, { 23, 20, 60, 20}, { 25, 20, 60, 20}, { 25, 21, 65, 20}, { 27, 21, 65, 22}, { 29, 23, 95, 23}, { 31, 25, 70, 25}, { 33, 25, 75, 38}, { 35, 25, 80, 50}, { 37, 30, 95, 100}, { 99, 99, 0, 0} }, { // Paladin { 1, 1, 30, 1}, { 2, 2, 35, 2}, { 3, 3, 35, 3}, { 5, 3, 35, 5}, { 5, 4, 35, 5}, { 7, 5, 40, 6}, { 7, 5, 40, 6}, { 9, 7, 40, 7}, { 9, 7, 40, 8}, { 9, 8, 40, 8}, { 11, 9, 40, 10}, { 11, 10, 45, 10}, { 11, 10, 45, 10}, { 13, 10, 45, 12}, { 13, 11, 45, 13}, { 15, 13, 45, 15}, { 15, 15, 50, 15}, { 17, 15, 50, 17}, { 17, 15, 50, 18}, { 19, 15, 50, 19}, { 19, 15, 50, 19}, { 21, 17, 50, 20}, { 23, 17, 50, 20}, { 25, 20, 50, 20}, { 27, 21, 50, 22}, { 29, 22, 50, 24}, { 31, 24, 60, 25}, { 33, 28, 60, 31}, { 35, 32, 70, 38}, { 37, 36, 90, 50}, { 39, 38, 90, 100} } }; const char *spell_names[62] = { // Mage Spells "Magic Missile", "Detect Monsters", "Phase Door", "Light Area", "Cure Light Wounds", "Find Hidden Traps/Doors", "Stinking Cloud", "Confusion", "Lightning Bolt", "Trap/Door Destruction", "Sleep I", "Cure Poison", "Teleport Self", "Remove Curse", "Frost Bolt", "Turn Stone to Mud", "Create Food", "Recharge Item I", "Sleep II", "Polymorph Other", "Identify", "Sleep III", "Fire Bolt", "Slow Monster", "Frost Ball", "Recharge Item II", "Teleport Other", "Haste Self", "Fire Ball", "Word of Destruction", "Genocide", // Priest Spells, start at index 31 "Detect Evil", "Cure Light Wounds", "Bless", "Remove Fear", "Call Light", "Find Traps", "Detect Doors/Stairs", "Slow Poison", "Blind Creature", "Portal", "Cure Medium Wounds", "Chant", "Sanctuary", "Create Food", "Remove Curse", "Resist Heat and Cold", "Neutralize Poison", "Orb of Draining", "Cure Serious Wounds", "Sense Invisible", "Protection from Evil", "Earthquake", "Sense Surroundings", "Cure Critical Wounds", "Turn Undead", "Prayer", "Dispel Undead", "Heal", "Dispel Evil", "Glyph of Warding", "Holy Word", }; // Each type of character starts out with a few provisions. // // Note that the entries refer to elements of the game_objects[] array. // 344 = Food Ration // 365 = Wooden Torch // 123 = Cloak // 318 = Beginners-Magick // 103 = Soft Leather Armor // 30 = Stiletto // 322 = Beginners Handbook uint16_t class_base_provisions[PLAYER_MAX_CLASSES][5] = { {344, 365, 123, 30, 103}, // Warrior {344, 365, 123, 30, 318}, // Mage {344, 365, 123, 30, 322}, // Priest {344, 365, 123, 30, 318}, // Rogue {344, 365, 123, 30, 318}, // Ranger {344, 365, 123, 30, 322} // Paladin }; umoria-5.7.10+20181022/src/dungeon_generate.cpp0000644000175000017500000012204113363422757017565 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. // Initialize/create a dungeon or town level #include "headers.h" static Coord_t doors_tk[100]; static int door_index; // Returns a Dark/Light floor tile based on dg.current_level, and random number static uint8_t dungeonFloorTileForLevel() { if (dg.current_level <= randomNumber(25)) { return TILE_LIGHT_FLOOR; } return TILE_DARK_FLOOR; } // Always picks a correct direction static void pickCorrectDirection(int &row_dir, int &col_dir, int y1, int x1, int y2, int x2) { if (y1 < y2) { row_dir = 1; } else if (y1 == y2) { row_dir = 0; } else { row_dir = -1; } if (x1 < x2) { col_dir = 1; } else if (x1 == x2) { col_dir = 0; } else { col_dir = -1; } if (row_dir != 0 && col_dir != 0) { if (randomNumber(2) == 1) { row_dir = 0; } else { col_dir = 0; } } } // Chance of wandering direction static void chanceOfRandomDirection(int &y, int &x) { int direction = randomNumber(4); if (direction < 3) { x = 0; y = -3 + (direction << 1); // direction=1 -> y=-1; direction=2 -> y=1 } else { y = 0; x = -7 + (direction << 1); // direction=3 -> x=-1; direction=4 -> x=1 } } // Blanks out entire cave -RAK- static void dungeonBlankEntireCave() { memset((char *) &dg.floor[0][0], 0, sizeof(dg.floor)); } // Fills in empty spots with desired rock -RAK- // Note: 9 is a temporary value. static void dungeonFillEmptyTilesWith(uint8_t rock_type) { // no need to check the border of the cave for (int y = dg.height - 2; y > 0; y--) { int x = 1; for (int j = dg.width - 2; j > 0; j--) { if (dg.floor[y][x].feature_id == TILE_NULL_WALL || dg.floor[y][x].feature_id == TMP1_WALL || dg.floor[y][x].feature_id == TMP2_WALL) { dg.floor[y][x].feature_id = rock_type; } x++; } } } #ifdef DEBUG #include #endif // Places indestructible rock around edges of dungeon -RAK- static void dungeonPlaceBoundaryWalls() { Tile_t(*left_ptr)[MAX_WIDTH]; Tile_t(*right_ptr)[MAX_WIDTH]; // put permanent wall on leftmost row and rightmost row left_ptr = (Tile_t(*)[MAX_WIDTH]) &dg.floor[0][0]; right_ptr = (Tile_t(*)[MAX_WIDTH]) &dg.floor[0][dg.width - 1]; for (int i = 0; i < dg.height; i++) { #ifdef DEBUG assert((Tile_t *)left_ptr == &floor[i][0]); assert((Tile_t *)right_ptr == &floor[i][dg.width - 1]); #endif ((Tile_t *) left_ptr)->feature_id = TILE_BOUNDARY_WALL; left_ptr++; ((Tile_t *) right_ptr)->feature_id = TILE_BOUNDARY_WALL; right_ptr++; } // put permanent wall on top row and bottom row Tile_t *top_ptr = &dg.floor[0][0]; Tile_t *bottom_ptr = &dg.floor[dg.height - 1][0]; for (int i = 0; i < dg.width; i++) { #ifdef DEBUG assert(top_ptr == &floor[0][i]); assert(bottom_ptr == &floor[dg.height - 1][i]); #endif top_ptr->feature_id = TILE_BOUNDARY_WALL; top_ptr++; bottom_ptr->feature_id = TILE_BOUNDARY_WALL; bottom_ptr++; } } // Places "streamers" of rock through dungeon -RAK- static void dungeonPlaceStreamerRock(uint8_t rock_type, int chance_of_treasure) { // Choose starting point and direction int pos_y = (dg.height / 2) + 11 - randomNumber(23); int pos_x = (dg.width / 2) + 16 - randomNumber(33); // Get random direction. Numbers 1-4, 6-9 int dir = randomNumber(8); if (dir > 4) { dir += 1; } // Place streamer into dungeon int t1 = 2 * config::dungeon::DUN_STREAMER_WIDTH + 1; // Constants int t2 = config::dungeon::DUN_STREAMER_WIDTH + 1; do { for (int i = 0; i < config::dungeon::DUN_STREAMER_DENSITY; i++) { int y = pos_y + randomNumber(t1) - t2; int x = pos_x + randomNumber(t1) - t2; if (coordInBounds(Coord_t{y, x})) { if (dg.floor[y][x].feature_id == TILE_GRANITE_WALL) { dg.floor[y][x].feature_id = rock_type; if (randomNumber(chance_of_treasure) == 1) { dungeonPlaceGold(Coord_t{y, x}); } } } } } while (playerMovePosition(dir, pos_y, pos_x)); } static void dungeonPlaceOpenDoor(int y, int x) { int cur_pos = popt(); dg.floor[y][x].treasure_id = (uint8_t) cur_pos; inventoryItemCopyTo(config::dungeon::objects::OBJ_OPEN_DOOR, treasure_list[cur_pos]); dg.floor[y][x].feature_id = TILE_CORR_FLOOR; } static void dungeonPlaceBrokenDoor(int y, int x) { int cur_pos = popt(); dg.floor[y][x].treasure_id = (uint8_t) cur_pos; inventoryItemCopyTo(config::dungeon::objects::OBJ_OPEN_DOOR, treasure_list[cur_pos]); dg.floor[y][x].feature_id = TILE_CORR_FLOOR; treasure_list[cur_pos].misc_use = 1; } static void dungeonPlaceClosedDoor(int y, int x) { int cur_pos = popt(); dg.floor[y][x].treasure_id = (uint8_t) cur_pos; inventoryItemCopyTo(config::dungeon::objects::OBJ_CLOSED_DOOR, treasure_list[cur_pos]); dg.floor[y][x].feature_id = TILE_BLOCKED_FLOOR; } static void dungeonPlaceLockedDoor(int y, int x) { int cur_pos = popt(); dg.floor[y][x].treasure_id = (uint8_t) cur_pos; inventoryItemCopyTo(config::dungeon::objects::OBJ_CLOSED_DOOR, treasure_list[cur_pos]); dg.floor[y][x].feature_id = TILE_BLOCKED_FLOOR; treasure_list[cur_pos].misc_use = (int16_t) (randomNumber(10) + 10); } static void dungeonPlaceStuckDoor(int y, int x) { int cur_pos = popt(); dg.floor[y][x].treasure_id = (uint8_t) cur_pos; inventoryItemCopyTo(config::dungeon::objects::OBJ_CLOSED_DOOR, treasure_list[cur_pos]); dg.floor[y][x].feature_id = TILE_BLOCKED_FLOOR; treasure_list[cur_pos].misc_use = (int16_t) (-randomNumber(10) - 10); } static void dungeonPlaceSecretDoor(int y, int x) { int cur_pos = popt(); dg.floor[y][x].treasure_id = (uint8_t) cur_pos; inventoryItemCopyTo(config::dungeon::objects::OBJ_SECRET_DOOR, treasure_list[cur_pos]); dg.floor[y][x].feature_id = TILE_BLOCKED_FLOOR; } static void dungeonPlaceDoor(int y, int x) { int door_type = randomNumber(3); if (door_type == 1) { if (randomNumber(4) == 1) { dungeonPlaceBrokenDoor(y, x); } else { dungeonPlaceOpenDoor(y, x); } } else if (door_type == 2) { door_type = randomNumber(12); if (door_type > 3) { dungeonPlaceClosedDoor(y, x); } else if (door_type == 3) { dungeonPlaceStuckDoor(y, x); } else { dungeonPlaceLockedDoor(y, x); } } else { dungeonPlaceSecretDoor(y, x); } } // Place an up staircase at given y, x -RAK- static void dungeonPlaceUpStairs(int y, int x) { if (dg.floor[y][x].treasure_id != 0) { (void) dungeonDeleteObject(Coord_t{y, x});; } int cur_pos = popt(); dg.floor[y][x].treasure_id = (uint8_t) cur_pos; inventoryItemCopyTo(config::dungeon::objects::OBJ_UP_STAIR, treasure_list[cur_pos]); } // Place a down staircase at given y, x -RAK- static void dungeonPlaceDownStairs(int y, int x) { if (dg.floor[y][x].treasure_id != 0) { (void) dungeonDeleteObject(Coord_t{y, x});; } int cur_pos = popt(); dg.floor[y][x].treasure_id = (uint8_t) cur_pos; inventoryItemCopyTo(config::dungeon::objects::OBJ_DOWN_STAIR, treasure_list[cur_pos]); } // Places a staircase 1=up, 2=down -RAK- static void dungeonPlaceStairs(int stair_type, int number, int walls) { for (int i = 0; i < number; i++) { bool placed = false; while (!placed) { int j = 0; do { // Note: // don't let y1/x1 be zero, // don't let y2/x2 be equal to dg.height-1/dg.width-1, // these values are always BOUNDARY_ROCK. int y1 = randomNumber(dg.height - 14); int x1 = randomNumber(dg.width - 14); int y2 = y1 + 12; int x2 = x1 + 12; do { do { if (dg.floor[y1][x1].feature_id <= MAX_OPEN_SPACE && dg.floor[y1][x1].treasure_id == 0 && coordWallsNextTo(Coord_t{y1, x1}) >= walls) { placed = true; if (stair_type == 1) { dungeonPlaceUpStairs(y1, x1); } else { dungeonPlaceDownStairs(y1, x1); } } x1++; } while ((x1 != x2) && (!placed)); x1 = x2 - 12; y1++; } while ((y1 != y2) && (!placed)); j++; } while ((!placed) && (j <= 30)); walls--; } } } // Place a trap with a given displacement of point -RAK- static void dungeonPlaceVaultTrap(int y, int x, int yd, int xd, int number) { for (int i = 0; i < number; i++) { bool placed = false; for (int count = 0; !placed && count <= 5; count++) { int y1 = y - yd - 1 + randomNumber(2 * yd + 1); int x1 = x - xd - 1 + randomNumber(2 * xd + 1); if (dg.floor[y1][x1].feature_id != TILE_NULL_WALL && dg.floor[y1][x1].feature_id <= MAX_CAVE_FLOOR && dg.floor[y1][x1].treasure_id == 0) { dungeonSetTrap(Coord_t{y1, x1}, randomNumber(config::dungeon::objects::MAX_TRAPS) - 1); placed = true; } } } } // Place a trap with a given displacement of point -RAK- static void dungeonPlaceVaultMonster(int y, int x, int number) { int pos_y, pos_x; for (int i = 0; i < number; i++) { pos_y = y; pos_x = x; (void) monsterSummon(pos_y, pos_x, true); } } // Builds a room at a row, column coordinate -RAK- static void dungeonBuildRoom(int y, int x) { uint8_t floor = dungeonFloorTileForLevel(); int height = y - randomNumber(4); int depth = y + randomNumber(3); int left = x - randomNumber(11); int right = x + randomNumber(11); // the x dim of rooms tends to be much larger than the y dim, // so don't bother rewriting the y loop. for (int i = height; i <= depth; i++) { for (int j = left; j <= right; j++) { dg.floor[i][j].feature_id = floor; dg.floor[i][j].perma_lit_room = true; } } for (int i = height - 1; i <= depth + 1; i++) { dg.floor[i][left - 1].feature_id = TILE_GRANITE_WALL; dg.floor[i][left - 1].perma_lit_room = true; dg.floor[i][right + 1].feature_id = TILE_GRANITE_WALL; dg.floor[i][right + 1].perma_lit_room = true; } for (int i = left; i <= right; i++) { dg.floor[height - 1][i].feature_id = TILE_GRANITE_WALL; dg.floor[height - 1][i].perma_lit_room = true; dg.floor[depth + 1][i].feature_id = TILE_GRANITE_WALL; dg.floor[depth + 1][i].perma_lit_room = true; } } // Builds a room at a row, column coordinate -RAK- // Type 1 unusual rooms are several overlapping rectangular ones static void dungeonBuildRoomOverlappingRectangles(int y, int x) { uint8_t floor = dungeonFloorTileForLevel(); int limit = 1 + randomNumber(2); for (int count = 0; count < limit; count++) { int height = y - randomNumber(4); int depth = y + randomNumber(3); int left = x - randomNumber(11); int right = x + randomNumber(11); // the x dim of rooms tends to be much larger than the y dim, // so don't bother rewriting the y loop. for (int i = height; i <= depth; i++) { for (int j = left; j <= right; j++) { dg.floor[i][j].feature_id = floor; dg.floor[i][j].perma_lit_room = true; } } for (int i = (height - 1); i <= (depth + 1); i++) { if (dg.floor[i][left - 1].feature_id != floor) { dg.floor[i][left - 1].feature_id = TILE_GRANITE_WALL; dg.floor[i][left - 1].perma_lit_room = true; } if (dg.floor[i][right + 1].feature_id != floor) { dg.floor[i][right + 1].feature_id = TILE_GRANITE_WALL; dg.floor[i][right + 1].perma_lit_room = true; } } for (int i = left; i <= right; i++) { if (dg.floor[height - 1][i].feature_id != floor) { dg.floor[height - 1][i].feature_id = TILE_GRANITE_WALL; dg.floor[height - 1][i].perma_lit_room = true; } if (dg.floor[depth + 1][i].feature_id != floor) { dg.floor[depth + 1][i].feature_id = TILE_GRANITE_WALL; dg.floor[depth + 1][i].perma_lit_room = true; } } } } static void dungeonPlaceRandomSecretDoor(int y, int x, int depth, int height, int left, int right) { switch (randomNumber(4)) { case 1: dungeonPlaceSecretDoor(height - 1, x); break; case 2: dungeonPlaceSecretDoor(depth + 1, x); break; case 3: dungeonPlaceSecretDoor(y, left - 1); break; default: dungeonPlaceSecretDoor(y, right + 1); break; } } static void dungeonPlaceVault(int y, int x) { for (int i = y - 1; i <= y + 1; i++) { dg.floor[i][x - 1].feature_id = TMP1_WALL; dg.floor[i][x + 1].feature_id = TMP1_WALL; } dg.floor[y - 1][x].feature_id = TMP1_WALL; dg.floor[y + 1][x].feature_id = TMP1_WALL; } static void dungeonPlaceTreasureVault(int y, int x, int depth, int height, int left, int right) { dungeonPlaceRandomSecretDoor(y, x, depth, height, left, right); dungeonPlaceVault(y, x); // Place a locked door int offset = randomNumber(4); if (offset < 3) { // 1 -> y-1; 2 -> y+1 dungeonPlaceLockedDoor(y - 3 + (offset << 1), x); } else { dungeonPlaceLockedDoor(y, x - 7 + (offset << 1)); } } static void dungeonPlaceInnerPillars(int y, int x) { for (int i = y - 1; i <= y + 1; i++) { for (int j = x - 1; j <= x + 1; j++) { dg.floor[i][j].feature_id = TMP1_WALL; } } if (randomNumber(2) != 1) { return; } int offset = randomNumber(2); for (int i = y - 1; i <= y + 1; i++) { for (int j = x - 5 - offset; j <= x - 3 - offset; j++) { dg.floor[i][j].feature_id = TMP1_WALL; } } for (int i = y - 1; i <= y + 1; i++) { for (int j = x + 3 + offset; j <= x + 5 + offset; j++) { dg.floor[i][j].feature_id = TMP1_WALL; } } } static void dungeonPlaceMazeInsideRoom(int depth, int height, int left, int right) { for (int y = height; y <= depth; y++) { for (int x = left; x <= right; x++) { if ((0x1 & (x + y)) != 0) { dg.floor[y][x].feature_id = TMP1_WALL; } } } } static void dungeonPlaceFourSmallRooms(int y, int x, int depth, int height, int left, int right) { for (int i = height; i <= depth; i++) { dg.floor[i][x].feature_id = TMP1_WALL; } for (int i = left; i <= right; i++) { dg.floor[y][i].feature_id = TMP1_WALL; } // place random secret door if (randomNumber(2) == 1) { int offsetX = randomNumber(10); dungeonPlaceSecretDoor(height - 1, x - offsetX); dungeonPlaceSecretDoor(height - 1, x + offsetX); dungeonPlaceSecretDoor(depth + 1, x - offsetX); dungeonPlaceSecretDoor(depth + 1, x + offsetX); } else { int offsetY = randomNumber(3); dungeonPlaceSecretDoor(y + offsetY, left - 1); dungeonPlaceSecretDoor(y - offsetY, left - 1); dungeonPlaceSecretDoor(y + offsetY, right + 1); dungeonPlaceSecretDoor(y - offsetY, right + 1); } } // Type 2 unusual rooms all have an inner room: // 1 - Just an inner room with one door // 2 - An inner room within an inner room // 3 - An inner room with pillar(s) // 4 - Inner room has a maze // 5 - A set of four inner rooms enum class InnerRoomTypes { plain = 1, treasure_vault, pillars, maze, four_small_rooms, }; // Builds a type 2 unusual room at a row, column coordinate -RAK- static void dungeonBuildRoomWithInnerRooms(int y, int x) { uint8_t floor = dungeonFloorTileForLevel(); int height = y - 4; int depth = y + 4; int left = x - 11; int right = x + 11; // the x dim of rooms tends to be much larger than the y dim, // so don't bother rewriting the y loop. for (int i = height; i <= depth; i++) { for (int j = left; j <= right; j++) { dg.floor[i][j].feature_id = floor; dg.floor[i][j].perma_lit_room = true; } } for (int i = (height - 1); i <= (depth + 1); i++) { dg.floor[i][left - 1].feature_id = TILE_GRANITE_WALL; dg.floor[i][left - 1].perma_lit_room = true; dg.floor[i][right + 1].feature_id = TILE_GRANITE_WALL; dg.floor[i][right + 1].perma_lit_room = true; } for (int i = left; i <= right; i++) { dg.floor[height - 1][i].feature_id = TILE_GRANITE_WALL; dg.floor[height - 1][i].perma_lit_room = true; dg.floor[depth + 1][i].feature_id = TILE_GRANITE_WALL; dg.floor[depth + 1][i].perma_lit_room = true; } // The inner room height = height + 2; depth = depth - 2; left = left + 2; right = right - 2; for (int i = (height - 1); i <= (depth + 1); i++) { dg.floor[i][left - 1].feature_id = TMP1_WALL; dg.floor[i][right + 1].feature_id = TMP1_WALL; } for (int i = left; i <= right; i++) { dg.floor[height - 1][i].feature_id = TMP1_WALL; dg.floor[depth + 1][i].feature_id = TMP1_WALL; } // Inner room variations switch ((InnerRoomTypes) randomNumber(5)) { case InnerRoomTypes::plain: dungeonPlaceRandomSecretDoor(y, x, depth, height, left, right); dungeonPlaceVaultMonster(y, x, 1); break; case InnerRoomTypes::treasure_vault: dungeonPlaceTreasureVault(y, x, depth, height, left, right); // Guard the treasure well dungeonPlaceVaultMonster(y, x, 2 + randomNumber(3)); // If the monsters don't get 'em. dungeonPlaceVaultTrap(y, x, 4, 10, 2 + randomNumber(3)); break; case InnerRoomTypes::pillars: dungeonPlaceRandomSecretDoor(y, x, depth, height, left, right); dungeonPlaceInnerPillars(y, x); if (randomNumber(3) != 1) { break; } // Inner rooms for (int i = x - 5; i <= x + 5; i++) { dg.floor[y - 1][i].feature_id = TMP1_WALL; dg.floor[y + 1][i].feature_id = TMP1_WALL; } dg.floor[y][x - 5].feature_id = TMP1_WALL; dg.floor[y][x + 5].feature_id = TMP1_WALL; dungeonPlaceSecretDoor(y - 3 + (randomNumber(2) << 1), x - 3); dungeonPlaceSecretDoor(y - 3 + (randomNumber(2) << 1), x + 3); if (randomNumber(3) == 1) { dungeonPlaceRandomObjectAt(Coord_t{y, x - 2}, false); } if (randomNumber(3) == 1) { dungeonPlaceRandomObjectAt(Coord_t{y, x + 2}, false); } dungeonPlaceVaultMonster(y, x - 2, randomNumber(2)); dungeonPlaceVaultMonster(y, x + 2, randomNumber(2)); break; case InnerRoomTypes::maze: dungeonPlaceRandomSecretDoor(y, x, depth, height, left, right); dungeonPlaceMazeInsideRoom(depth, height, left, right); // Monsters just love mazes. dungeonPlaceVaultMonster(y, x - 5, randomNumber(3)); dungeonPlaceVaultMonster(y, x + 5, randomNumber(3)); // Traps make them entertaining. dungeonPlaceVaultTrap(y, x - 3, 2, 8, randomNumber(3)); dungeonPlaceVaultTrap(y, x + 3, 2, 8, randomNumber(3)); // Mazes should have some treasure too.. for (int i = 0; i < 3; i++) { dungeonPlaceRandomObjectNear(Coord_t{y, x}, 1); } break; case InnerRoomTypes::four_small_rooms: dungeonPlaceFourSmallRooms(y, x, depth, height, left, right); // Treasure in each one. dungeonPlaceRandomObjectNear(Coord_t{y, x}, 2 + randomNumber(2)); // Gotta have some monsters. dungeonPlaceVaultMonster(y + 2, x - 4, randomNumber(2)); dungeonPlaceVaultMonster(y + 2, x + 4, randomNumber(2)); dungeonPlaceVaultMonster(y - 2, x - 4, randomNumber(2)); dungeonPlaceVaultMonster(y - 2, x + 4, randomNumber(2)); break; default: break; } } static void dungeonPlaceLargeMiddlePillar(int y, int x) { for (int i = y - 1; i <= y + 1; i++) { for (int j = x - 1; j <= x + 1; j++) { dg.floor[i][j].feature_id = TMP1_WALL; } } } // Builds a room at a row, column coordinate -RAK- // Type 3 unusual rooms are cross shaped static void dungeonBuildRoomCrossShaped(int y, int x) { uint8_t floor = dungeonFloorTileForLevel(); int random_offset = 2 + randomNumber(2); int height = y - random_offset; int depth = y + random_offset; int left = x - 1; int right = x + 1; for (int i = height; i <= depth; i++) { for (int j = left; j <= right; j++) { dg.floor[i][j].feature_id = floor; dg.floor[i][j].perma_lit_room = true; } } for (int i = height - 1; i <= depth + 1; i++) { dg.floor[i][left - 1].feature_id = TILE_GRANITE_WALL; dg.floor[i][left - 1].perma_lit_room = true; dg.floor[i][right + 1].feature_id = TILE_GRANITE_WALL; dg.floor[i][right + 1].perma_lit_room = true; } for (int i = left; i <= right; i++) { dg.floor[height - 1][i].feature_id = TILE_GRANITE_WALL; dg.floor[height - 1][i].perma_lit_room = true; dg.floor[depth + 1][i].feature_id = TILE_GRANITE_WALL; dg.floor[depth + 1][i].perma_lit_room = true; } random_offset = 2 + randomNumber(9); height = y - 1; depth = y + 1; left = x - random_offset; right = x + random_offset; for (int i = height; i <= depth; i++) { for (int j = left; j <= right; j++) { dg.floor[i][j].feature_id = floor; dg.floor[i][j].perma_lit_room = true; } } for (int i = height - 1; i <= depth + 1; i++) { if (dg.floor[i][left - 1].feature_id != floor) { dg.floor[i][left - 1].feature_id = TILE_GRANITE_WALL; dg.floor[i][left - 1].perma_lit_room = true; } if (dg.floor[i][right + 1].feature_id != floor) { dg.floor[i][right + 1].feature_id = TILE_GRANITE_WALL; dg.floor[i][right + 1].perma_lit_room = true; } } for (int i = left; i <= right; i++) { if (dg.floor[height - 1][i].feature_id != floor) { dg.floor[height - 1][i].feature_id = TILE_GRANITE_WALL; dg.floor[height - 1][i].perma_lit_room = true; } if (dg.floor[depth + 1][i].feature_id != floor) { dg.floor[depth + 1][i].feature_id = TILE_GRANITE_WALL; dg.floor[depth + 1][i].perma_lit_room = true; } } // Special features. switch (randomNumber(4)) { case 1: // Large middle pillar dungeonPlaceLargeMiddlePillar(y, x); break; case 2: // Inner treasure vault dungeonPlaceVault(y, x); // Place a secret door random_offset = randomNumber(4); if (random_offset < 3) { dungeonPlaceSecretDoor(y - 3 + (random_offset << 1), x); } else { dungeonPlaceSecretDoor(y, x - 7 + (random_offset << 1)); } // Place a treasure in the vault dungeonPlaceRandomObjectAt(Coord_t{y, x}, false); // Let's guard the treasure well. dungeonPlaceVaultMonster(y, x, 2 + randomNumber(2)); // Traps naturally dungeonPlaceVaultTrap(y, x, 4, 4, 1 + randomNumber(3)); break; case 3: if (randomNumber(3) == 1) { dg.floor[y - 1][x - 2].feature_id = TMP1_WALL; dg.floor[y + 1][x - 2].feature_id = TMP1_WALL; dg.floor[y - 1][x + 2].feature_id = TMP1_WALL; dg.floor[y + 1][x + 2].feature_id = TMP1_WALL; dg.floor[y - 2][x - 1].feature_id = TMP1_WALL; dg.floor[y - 2][x + 1].feature_id = TMP1_WALL; dg.floor[y + 2][x - 1].feature_id = TMP1_WALL; dg.floor[y + 2][x + 1].feature_id = TMP1_WALL; if (randomNumber(3) == 1) { dungeonPlaceSecretDoor(y, x - 2); dungeonPlaceSecretDoor(y, x + 2); dungeonPlaceSecretDoor(y - 2, x); dungeonPlaceSecretDoor(y + 2, x); } } else if (randomNumber(3) == 1) { dg.floor[y][x].feature_id = TMP1_WALL; dg.floor[y - 1][x].feature_id = TMP1_WALL; dg.floor[y + 1][x].feature_id = TMP1_WALL; dg.floor[y][x - 1].feature_id = TMP1_WALL; dg.floor[y][x + 1].feature_id = TMP1_WALL; } else if (randomNumber(3) == 1) { dg.floor[y][x].feature_id = TMP1_WALL; } break; case 4: // no special feature! break; default: break; } } // Constructs a tunnel between two points static void dungeonBuildTunnel(int y_start, int x_start, int y_end, int x_end) { Coord_t tunnels_tk[1000], walls_tk[1000]; // Main procedure for Tunnel // Note: 9 is a temporary value bool door_flag = false; bool stop_flag = false; int main_loop_count = 0; int start_row = y_start; int start_col = x_start; int tunnel_index = 0; int wall_index = 0; int row_dir, col_dir; pickCorrectDirection(row_dir, col_dir, y_start, x_start, y_end, x_end); do { // prevent infinite loops, just in case main_loop_count++; if (main_loop_count > 2000) { stop_flag = true; } if (randomNumber(100) > config::dungeon::DUN_DIR_CHANGE) { if (randomNumber(config::dungeon::DUN_RANDOM_DIR) == 1) { chanceOfRandomDirection(row_dir, col_dir); } else { pickCorrectDirection(row_dir, col_dir, y_start, x_start, y_end, x_end); } } int tmp_row = y_start + row_dir; int tmp_col = x_start + col_dir; while (!coordInBounds(Coord_t{tmp_row, tmp_col})) { if (randomNumber(config::dungeon::DUN_RANDOM_DIR) == 1) { chanceOfRandomDirection(row_dir, col_dir); } else { pickCorrectDirection(row_dir, col_dir, y_start, x_start, y_end, x_end); } tmp_row = y_start + row_dir; tmp_col = x_start + col_dir; } switch (dg.floor[tmp_row][tmp_col].feature_id) { case TILE_NULL_WALL: y_start = tmp_row; x_start = tmp_col; if (tunnel_index < 1000) { tunnels_tk[tunnel_index].y = y_start; tunnels_tk[tunnel_index].x = x_start; tunnel_index++; } door_flag = false; break; case TMP2_WALL: // do nothing break; case TILE_GRANITE_WALL: y_start = tmp_row; x_start = tmp_col; if (wall_index < 1000) { walls_tk[wall_index].y = y_start; walls_tk[wall_index].x = x_start; wall_index++; } for (int y = y_start - 1; y <= y_start + 1; y++) { for (int x = x_start - 1; x <= x_start + 1; x++) { if (coordInBounds(Coord_t{y, x})) { // values 11 and 12 are impossible here, dungeonPlaceStreamerRock // is never run before dungeonBuildTunnel if (dg.floor[y][x].feature_id == TILE_GRANITE_WALL) { dg.floor[y][x].feature_id = TMP2_WALL; } } } } break; case TILE_CORR_FLOOR: case TILE_BLOCKED_FLOOR: y_start = tmp_row; x_start = tmp_col; if (!door_flag) { if (door_index < 100) { doors_tk[door_index].y = y_start; doors_tk[door_index].x = x_start; door_index++; } door_flag = true; } if (randomNumber(100) > config::dungeon::DUN_TUNNELING) { // make sure that tunnel has gone a reasonable distance // before stopping it, this helps prevent isolated rooms tmp_row = y_start - start_row; if (tmp_row < 0) { tmp_row = -tmp_row; } tmp_col = x_start - start_col; if (tmp_col < 0) { tmp_col = -tmp_col; } if (tmp_row > 10 || tmp_col > 10) { stop_flag = true; } } break; default: // none of: NULL, TMP2, GRANITE, CORR y_start = tmp_row; x_start = tmp_col; } } while ((y_start != y_end || x_start != x_end) && !stop_flag); for (int i = 0; i < tunnel_index; i++) { dg.floor[tunnels_tk[i].y][tunnels_tk[i].x].feature_id = TILE_CORR_FLOOR; } for (int i = 0; i < wall_index; i++) { Tile_t &tile = dg.floor[walls_tk[i].y][walls_tk[i].x]; if (tile.feature_id == TMP2_WALL) { if (randomNumber(100) < config::dungeon::DUN_ROOM_DOORS) { dungeonPlaceDoor(walls_tk[i].y, walls_tk[i].x); } else { // these have to be doorways to rooms tile.feature_id = TILE_CORR_FLOOR; } } } } static bool dungeonIsNextTo(int y, int x) { if (coordCorridorWallsNextTo(Coord_t{y, x}) > 2) { bool vertical = dg.floor[y - 1][x].feature_id >= MIN_CAVE_WALL && dg.floor[y + 1][x].feature_id >= MIN_CAVE_WALL; bool horizontal = dg.floor[y][x - 1].feature_id >= MIN_CAVE_WALL && dg.floor[y][x + 1].feature_id >= MIN_CAVE_WALL; return vertical || horizontal; } return false; } // Places door at y, x position if at least 2 walls found static void dungeonPlaceDoorIfNextToTwoWalls(int y, int x) { if (dg.floor[y][x].feature_id == TILE_CORR_FLOOR && randomNumber(100) > config::dungeon::DUN_TUNNEL_DOORS && dungeonIsNextTo(y, x)) { dungeonPlaceDoor(y, x); } } // Returns random co-ordinates -RAK- static void dungeonNewSpot(int16_t &y, int16_t &x) { int pos_y, pos_x; Tile_t *tile = nullptr; do { pos_y = randomNumber(dg.height - 2); pos_x = randomNumber(dg.width - 2); tile = &dg.floor[pos_y][pos_x]; } while (tile->feature_id >= MIN_CLOSED_SPACE || tile->creature_id != 0 || tile->treasure_id != 0); y = (int16_t) pos_y; x = (int16_t) pos_x; } // Functions to emulate the original Pascal sets static bool setRooms(int tile_id) { return (tile_id == TILE_DARK_FLOOR || tile_id == TILE_LIGHT_FLOOR); } static bool setCorridors(int tile_id) { return (tile_id == TILE_CORR_FLOOR || tile_id == TILE_BLOCKED_FLOOR); } static bool setFloors(int tile_id) { return (tile_id <= MAX_CAVE_FLOOR); } // Cave logic flow for generation of new dungeon static void dungeonGenerate() { // Room initialization int row_rooms = 2 * (dg.height / SCREEN_HEIGHT); int col_rooms = 2 * (dg.width / SCREEN_WIDTH); bool room_map[20][20]; for (int row = 0; row < row_rooms; row++) { for (int col = 0; col < col_rooms; col++) { room_map[row][col] = false; } } int random_room_count = randomNumberNormalDistribution(config::dungeon::DUN_ROOMS_MEAN, 2); for (int i = 0; i < random_room_count; i++) { room_map[randomNumber(row_rooms) - 1][randomNumber(col_rooms) - 1] = true; } // Build rooms int location_id = 0; int16_t y_locations[400], x_locations[400]; for (int row = 0; row < row_rooms; row++) { for (int col = 0; col < col_rooms; col++) { if (room_map[row][col]) { y_locations[location_id] = (int16_t) (row * (SCREEN_HEIGHT >> 1) + QUART_HEIGHT); x_locations[location_id] = (int16_t) (col * (SCREEN_WIDTH >> 1) + QUART_WIDTH); if (dg.current_level > randomNumber(config::dungeon::DUN_UNUSUAL_ROOMS)) { int room_type = randomNumber(3); if (room_type == 1) { dungeonBuildRoomOverlappingRectangles(y_locations[location_id], x_locations[location_id]); } else if (room_type == 2) { dungeonBuildRoomWithInnerRooms(y_locations[location_id], x_locations[location_id]); } else { dungeonBuildRoomCrossShaped(y_locations[location_id], x_locations[location_id]); } } else { dungeonBuildRoom(y_locations[location_id], x_locations[location_id]); } location_id++; } } } for (int i = 0; i < location_id; i++) { int pick1 = randomNumber(location_id) - 1; int pick2 = randomNumber(location_id) - 1; int y1 = y_locations[pick1]; int x1 = x_locations[pick1]; y_locations[pick1] = y_locations[pick2]; x_locations[pick1] = x_locations[pick2]; y_locations[pick2] = (int16_t) y1; x_locations[pick2] = (int16_t) x1; } door_index = 0; // move zero entry to location_id, so that can call dungeonBuildTunnel all location_id times y_locations[location_id] = y_locations[0]; x_locations[location_id] = x_locations[0]; for (int i = 0; i < location_id; i++) { int y1 = y_locations[i]; int x1 = x_locations[i]; int y2 = y_locations[i + 1]; int x2 = x_locations[i + 1]; dungeonBuildTunnel(y2, x2, y1, x1); } // Generate walls and streamers dungeonFillEmptyTilesWith(TILE_GRANITE_WALL); for (int i = 0; i < config::dungeon::DUN_MAGMA_STREAMER; i++) { dungeonPlaceStreamerRock(TILE_MAGMA_WALL, config::dungeon::DUN_MAGMA_TREASURE); } for (int i = 0; i < config::dungeon::DUN_QUARTZ_STREAMER; i++) { dungeonPlaceStreamerRock(TILE_QUARTZ_WALL, config::dungeon::DUN_QUARTZ_TREASURE); } dungeonPlaceBoundaryWalls(); // Place intersection doors for (int i = 0; i < door_index; i++) { dungeonPlaceDoorIfNextToTwoWalls(doors_tk[i].y, doors_tk[i].x - 1); dungeonPlaceDoorIfNextToTwoWalls(doors_tk[i].y, doors_tk[i].x + 1); dungeonPlaceDoorIfNextToTwoWalls(doors_tk[i].y - 1, doors_tk[i].x); dungeonPlaceDoorIfNextToTwoWalls(doors_tk[i].y + 1, doors_tk[i].x); } int alloc_level = (dg.current_level / 3); if (alloc_level < 2) { alloc_level = 2; } else if (alloc_level > 10) { alloc_level = 10; } dungeonPlaceStairs(2, randomNumber(2) + 2, 3); dungeonPlaceStairs(1, randomNumber(2), 3); // Set up the character coords, used by monsterPlaceNewWithinDistance, monsterPlaceWinning dungeonNewSpot(py.row, py.col); monsterPlaceNewWithinDistance((randomNumber(8) + config::monsters::MON_MIN_PER_LEVEL + alloc_level), 0, true); dungeonAllocateAndPlaceObject(setCorridors, 3, randomNumber(alloc_level)); dungeonAllocateAndPlaceObject(setRooms, 5, randomNumberNormalDistribution(config::dungeon::objects::LEVEL_OBJECTS_PER_ROOM, 3)); dungeonAllocateAndPlaceObject(setFloors, 5, randomNumberNormalDistribution(config::dungeon::objects::LEVEL_OBJECTS_PER_CORRIDOR, 3)); dungeonAllocateAndPlaceObject(setFloors, 4, randomNumberNormalDistribution(config::dungeon::objects::LEVEL_TOTAL_GOLD_AND_GEMS, 3)); dungeonAllocateAndPlaceObject(setFloors, 1, randomNumber(alloc_level)); if (dg.current_level >= config::monsters::MON_ENDGAME_LEVEL) { monsterPlaceWinning(); } } // Builds a store at a row, column coordinate static void dungeonBuildStore(int store_id, int y, int x) { int yval = y * 10 + 5; int xval = x * 16 + 16; int y_height = yval - randomNumber(3); int y_depth = yval + randomNumber(4); int x_left = xval - randomNumber(6); int x_right = xval + randomNumber(6); int pos_y, pos_x; for (pos_y = y_height; pos_y <= y_depth; pos_y++) { for (pos_x = x_left; pos_x <= x_right; pos_x++) { dg.floor[pos_y][pos_x].feature_id = TILE_BOUNDARY_WALL; } } int tmp = randomNumber(4); if (tmp < 3) { pos_y = randomNumber(y_depth - y_height) + y_height - 1; if (tmp == 1) { pos_x = x_left; } else { pos_x = x_right; } } else { pos_x = randomNumber(x_right - x_left) + x_left - 1; if (tmp == 3) { pos_y = y_depth; } else { pos_y = y_height; } } dg.floor[pos_y][pos_x].feature_id = TILE_CORR_FLOOR; int cur_pos = popt(); dg.floor[pos_y][pos_x].treasure_id = (uint8_t) cur_pos; inventoryItemCopyTo(config::dungeon::objects::OBJ_STORE_DOOR + store_id, treasure_list[cur_pos]); } // Link all free space in treasure list together static void treasureLinker() { for (auto &item : treasure_list) { inventoryItemCopyTo(config::dungeon::objects::OBJ_NOTHING, item); } current_treasure_id = config::treasure::MIN_TREASURE_LIST_ID; } // Link all free space in monster list together static void monsterLinker() { for (auto &monster : monsters) { monster = blank_monster; } next_free_monster_id = config::monsters::MON_MIN_INDEX_ID; } static void dungeonPlaceTownStores() { int rooms[6]; for (int i = 0; i < 6; i++) { rooms[i] = i; } int rooms_count = 6; for (int y = 0; y < 2; y++) { for (int x = 0; x < 3; x++) { int room_id = randomNumber(rooms_count) - 1; dungeonBuildStore(rooms[room_id], y, x); for (int i = room_id; i < rooms_count - 1; i++) { rooms[i] = rooms[i + 1]; } rooms_count--; } } } static bool isNighTime() { return (0x1 & (dg.game_turn / 5000)) != 0; } // Light town based on whether it is Night time, or day time. static void lightTown() { if (isNighTime()) { for (int y = 0; y < dg.height; y++) { for (int x = 0; x < dg.width; x++) { if (dg.floor[y][x].feature_id != TILE_DARK_FLOOR) { dg.floor[y][x].permanent_light = true; } } } monsterPlaceNewWithinDistance(config::monsters::MON_MIN_TOWNSFOLK_NIGHT, 3, true); } else { // ...it is day time for (int y = 0; y < dg.height; y++) { for (int x = 0; x < dg.width; x++) { dg.floor[y][x].permanent_light = true; } } monsterPlaceNewWithinDistance(config::monsters::MON_MIN_TOWNSFOLK_DAY, 3, true); } } // I may have written the town level code, but I'm not exactly // proud of it. Adding the stores required some real slucky // hooks which I have not had time to re-think. -RAK- // Town logic flow for generation of new town static void townGeneration() { seedSet(game.town_seed); dungeonPlaceTownStores(); dungeonFillEmptyTilesWith(TILE_DARK_FLOOR); // make stairs before seedResetToOldSeed, so that they don't move around dungeonPlaceBoundaryWalls(); dungeonPlaceStairs(2, 1, 0); seedResetToOldSeed(); // Set up the character coords, used by monsterPlaceNewWithinDistance below dungeonNewSpot(py.row, py.col); lightTown(); storeMaintenance(); } // Generates a random dungeon level -RAK- void generateCave() { dg.panel.top = 0; dg.panel.bottom = 0; dg.panel.left = 0; dg.panel.right = 0; py.row = -1; py.col = -1; treasureLinker(); monsterLinker(); dungeonBlankEntireCave(); // We're in the dungeon more than the town, so let's default to that -MRC- dg.height = MAX_HEIGHT; dg.width = MAX_WIDTH; if (dg.current_level == 0) { dg.height = SCREEN_HEIGHT; dg.width = SCREEN_WIDTH; } dg.panel.max_rows = (int16_t) ((dg.height / SCREEN_HEIGHT) * 2 - 2); dg.panel.max_cols = (int16_t) ((dg.width / SCREEN_WIDTH) * 2 - 2); dg.panel.row = dg.panel.max_rows; dg.panel.col = dg.panel.max_cols; if (dg.current_level == 0) { townGeneration(); } else { dungeonGenerate(); } } umoria-5.7.10+20181022/src/config.cpp0000644000175000017500000004152213363422757015525 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. #include "headers.h" namespace config { // Data files used by Umoria // NOTE: use relative paths to the executable binary. namespace files { const std::string splash_screen = "data/splash.txt"; const std::string welcome_screen = "data/welcome.txt"; const std::string license = "LICENSE"; const std::string versions_history = "data/versions.txt"; const std::string help = "data/help.txt"; const std::string help_wizard = "data/help_wizard.txt"; const std::string help_roguelike = "data/rl_help.txt"; const std::string help_roguelike_wizard = "data/rl_help_wizard.txt"; const std::string death_tomb = "data/death_tomb.txt"; const std::string death_royal = "data/death_royal.txt"; const std::string scores = "scores.dat"; std::string save_game = "game.sav"; } // Game options as set on startup and with `=` set options command -CJS- namespace options { bool display_counts = true; // Display rest/repeat counts bool find_bound = false; // Print yourself on a run (slower) bool run_cut_corners = true; // Cut corners while running bool run_examine_corners = true; // Check corners while running bool run_ignore_doors = false; // Run through open doors bool run_print_self = false; // Stop running when the map shifts bool highlight_seams = false; // Highlight magma and quartz veins bool prompt_to_pickup = false; // Prompt to pick something up bool use_roguelike_keys = false; // Use classic Roguelike keys bool show_inventory_weights = false; // Display weights in inventory bool error_beep_sound = true; // Beep for invalid characters } // Dungeon generation values // Note: The entire design of dungeon can be changed by only slight adjustments here. namespace dungeon { const uint8_t DUN_RANDOM_DIR = 9; // 1/Chance of Random direction const uint8_t DUN_DIR_CHANGE = 70; // Chance of changing direction (99 max) const uint8_t DUN_TUNNELING = 15; // Chance of extra tunneling const uint8_t DUN_ROOMS_MEAN = 32; // Mean of # of rooms, standard dev2 const uint8_t DUN_ROOM_DOORS = 25; // % chance of room doors const uint8_t DUN_TUNNEL_DOORS = 15; // % chance of doors at tunnel junctions const uint8_t DUN_STREAMER_DENSITY = 5; // Density of streamers const uint8_t DUN_STREAMER_WIDTH = 2; // Width of streamers const uint8_t DUN_MAGMA_STREAMER = 3; // Number of magma streamers const uint8_t DUN_MAGMA_TREASURE = 90; // 1/x chance of treasure per magma const uint8_t DUN_QUARTZ_STREAMER = 2; // Number of quartz streamers const uint8_t DUN_QUARTZ_TREASURE = 40; // 1/x chance of treasure per quartz const uint16_t DUN_UNUSUAL_ROOMS = 300; // Level/x chance of unusual room namespace objects { const uint16_t OBJ_OPEN_DOOR = 367; const uint16_t OBJ_CLOSED_DOOR = 368; const uint16_t OBJ_SECRET_DOOR = 369; const uint16_t OBJ_UP_STAIR = 370; const uint16_t OBJ_DOWN_STAIR = 371; const uint16_t OBJ_STORE_DOOR = 372; const uint16_t OBJ_TRAP_LIST = 378; const uint16_t OBJ_RUBBLE = 396; const uint16_t OBJ_MUSH = 397; const uint16_t OBJ_SCARE_MON = 398; const uint16_t OBJ_GOLD_LIST = 399; const uint16_t OBJ_NOTHING = 417; const uint16_t OBJ_RUINED_CHEST = 418; const uint16_t OBJ_WIZARD = 419; const uint8_t MAX_GOLD_TYPES = 18; // Number of different types of gold const uint8_t MAX_TRAPS = 18; // Number of defined traps const uint8_t LEVEL_OBJECTS_PER_ROOM = 7; // Amount of objects for rooms const uint8_t LEVEL_OBJECTS_PER_CORRIDOR = 2; // Amount of objects for corridors const uint8_t LEVEL_TOTAL_GOLD_AND_GEMS = 2; // Amount of gold (and gems) } } // Note: Number of special objects, and degree of enchantments can be adjusted here. namespace treasure { const uint8_t MIN_TREASURE_LIST_ID = 1; // Minimum treasure_list index used const uint8_t TREASURE_CHANCE_OF_GREAT_ITEM = 12; // 1/n Chance of item being a Great Item // Magic Treasure Generation constants const uint8_t LEVEL_STD_OBJECT_ADJUST = 125; // Adjust STD per level * 100 const uint8_t LEVEL_MIN_OBJECT_STD = 7; // Minimum STD const uint8_t LEVEL_TOWN_OBJECTS = 7; // Town object generation level const uint8_t OBJECT_BASE_MAGIC = 15; // Base amount of magic const uint8_t OBJECT_MAX_BASE_MAGIC = 70; // Max amount of magic const uint8_t OBJECT_CHANCE_SPECIAL = 6; // magic_chance/# special magic const uint8_t OBJECT_CHANCE_CURSED = 13; // 10*magic_chance/# cursed items // Constants describing limits of certain objects const uint16_t OBJECT_LAMP_MAX_CAPACITY = 15000; // Maximum amount that lamp can be filled const uint8_t OBJECT_BOLTS_MAX_RANGE = 18; // Maximum range of bolts and balls const uint16_t OBJECTS_RUNE_PROTECTION = 3000; // Rune of protection resistance // definitions for objects that can be worn namespace flags { const uint32_t TR_STATS = 0x0000003FL; // the stats must be the low 6 bits const uint32_t TR_STR = 0x00000001L; const uint32_t TR_INT = 0x00000002L; const uint32_t TR_WIS = 0x00000004L; const uint32_t TR_DEX = 0x00000008L; const uint32_t TR_CON = 0x00000010L; const uint32_t TR_CHR = 0x00000020L; const uint32_t TR_SEARCH = 0x00000040L; const uint32_t TR_SLOW_DIGEST = 0x00000080L; const uint32_t TR_STEALTH = 0x00000100L; const uint32_t TR_AGGRAVATE = 0x00000200L; const uint32_t TR_TELEPORT = 0x00000400L; const uint32_t TR_REGEN = 0x00000800L; const uint32_t TR_SPEED = 0x00001000L; const uint32_t TR_EGO_WEAPON = 0x0007E000L; const uint32_t TR_SLAY_DRAGON = 0x00002000L; const uint32_t TR_SLAY_ANIMAL = 0x00004000L; const uint32_t TR_SLAY_EVIL = 0x00008000L; const uint32_t TR_SLAY_UNDEAD = 0x00010000L; const uint32_t TR_FROST_BRAND = 0x00020000L; const uint32_t TR_FLAME_TONGUE = 0x00040000L; const uint32_t TR_RES_FIRE = 0x00080000L; const uint32_t TR_RES_ACID = 0x00100000L; const uint32_t TR_RES_COLD = 0x00200000L; const uint32_t TR_SUST_STAT = 0x00400000L; const uint32_t TR_FREE_ACT = 0x00800000L; const uint32_t TR_SEE_INVIS = 0x01000000L; const uint32_t TR_RES_LIGHT = 0x02000000L; const uint32_t TR_FFALL = 0x04000000L; const uint32_t TR_BLIND = 0x08000000L; const uint32_t TR_TIMID = 0x10000000L; const uint32_t TR_TUNNEL = 0x20000000L; const uint32_t TR_INFRA = 0x40000000L; const uint32_t TR_CURSED = 0x80000000L; } // definitions for chests namespace chests { const uint32_t CH_LOCKED = 0x00000001L; const uint32_t CH_TRAPPED = 0x000001F0L; const uint32_t CH_LOSE_STR = 0x00000010L; const uint32_t CH_POISON = 0x00000020L; const uint32_t CH_PARALYSED = 0x00000040L; const uint32_t CH_EXPLODE = 0x00000080L; const uint32_t CH_SUMMON = 0x00000100L; } } namespace monsters { const uint8_t MON_CHANCE_OF_NEW = 160; // 1/x chance of new monster each round const uint8_t MON_MAX_SIGHT = 20; // Maximum dis a creature can be seen const uint8_t MON_MAX_SPELL_CAST_DISTANCE = 20; // Maximum dis creature spell can be cast const uint8_t MON_MAX_MULTIPLY_PER_LEVEL = 75; // Maximum reproductions on a level const uint8_t MON_MULTIPLY_ADJUST = 7; // High value slows multiplication const uint8_t MON_CHANCE_OF_NASTY = 50; // 1/x chance of high level creature const uint8_t MON_MIN_PER_LEVEL = 14; // Minimum number of monsters/level const uint8_t MON_MIN_TOWNSFOLK_DAY = 4; // Number of people on town level (day) const uint8_t MON_MIN_TOWNSFOLK_NIGHT = 8; // Number of people on town level (night) const uint8_t MON_ENDGAME_MONSTERS = 2; // Total number of "win" creatures const uint8_t MON_ENDGAME_LEVEL = 50; // Level where winning creatures begin const uint8_t MON_SUMMONED_LEVEL_ADJUST = 2; // Adjust level of summoned creatures const uint8_t MON_PLAYER_EXP_DRAINED_PER_HIT = 2; // Percent of player exp drained per hit const uint8_t MON_MIN_INDEX_ID = 2; // Minimum index in m_list (1 = py, 0 = no mon) const uint8_t SCARE_MONSTER = 99; // definitions for creatures, cmove field namespace move { const uint32_t CM_ALL_MV_FLAGS = 0x0000003FL; const uint32_t CM_ATTACK_ONLY = 0x00000001L; const uint32_t CM_MOVE_NORMAL = 0x00000002L; const uint32_t CM_ONLY_MAGIC = 0x00000004L; // For Quylthulgs, which have no physical movement. const uint32_t CM_RANDOM_MOVE = 0x00000038L; const uint32_t CM_20_RANDOM = 0x00000008L; const uint32_t CM_40_RANDOM = 0x00000010L; const uint32_t CM_75_RANDOM = 0x00000020L; const uint32_t CM_SPECIAL = 0x003F0000L; const uint32_t CM_INVISIBLE = 0x00010000L; const uint32_t CM_OPEN_DOOR = 0x00020000L; const uint32_t CM_PHASE = 0x00040000L; const uint32_t CM_EATS_OTHER = 0x00080000L; const uint32_t CM_PICKS_UP = 0x00100000L; const uint32_t CM_MULTIPLY = 0x00200000L; const uint32_t CM_SMALL_OBJ = 0x00800000L; const uint32_t CM_CARRY_OBJ = 0x01000000L; const uint32_t CM_CARRY_GOLD = 0x02000000L; const uint32_t CM_TREASURE = 0x7C000000L; const uint32_t CM_TR_SHIFT = 26; // used for recall of treasure const uint32_t CM_60_RANDOM = 0x04000000L; const uint32_t CM_90_RANDOM = 0x08000000L; const uint32_t CM_1D2_OBJ = 0x10000000L; const uint32_t CM_2D2_OBJ = 0x20000000L; const uint32_t CM_4D2_OBJ = 0x40000000L; const uint32_t CM_WIN = 0x80000000L; } // creature spell definitions namespace spells { const uint32_t CS_FREQ = 0x0000000FL; const uint32_t CS_SPELLS = 0x0001FFF0L; const uint32_t CS_TEL_SHORT = 0x00000010L; const uint32_t CS_TEL_LONG = 0x00000020L; const uint32_t CS_TEL_TO = 0x00000040L; const uint32_t CS_LGHT_WND = 0x00000080L; const uint32_t CS_SER_WND = 0x00000100L; const uint32_t CS_HOLD_PER = 0x00000200L; const uint32_t CS_BLIND = 0x00000400L; const uint32_t CS_CONFUSE = 0x00000800L; const uint32_t CS_FEAR = 0x00001000L; const uint32_t CS_SUMMON_MON = 0x00002000L; const uint32_t CS_SUMMON_UND = 0x00004000L; const uint32_t CS_SLOW_PER = 0x00008000L; const uint32_t CS_DRAIN_MANA = 0x00010000L; const uint32_t CS_BREATHE = 0x00F80000L; // may also just indicate resistance const uint32_t CS_BR_LIGHT = 0x00080000L; // if no spell frequency set const uint32_t CS_BR_GAS = 0x00100000L; const uint32_t CS_BR_ACID = 0x00200000L; const uint32_t CS_BR_FROST = 0x00400000L; const uint32_t CS_BR_FIRE = 0x00800000L; } // creature defense flags namespace defense { const uint16_t CD_DRAGON = 0x0001; const uint16_t CD_ANIMAL = 0x0002; const uint16_t CD_EVIL = 0x0004; const uint16_t CD_UNDEAD = 0x0008; const uint16_t CD_WEAKNESS = 0x03F0; const uint16_t CD_FROST = 0x0010; const uint16_t CD_FIRE = 0x0020; const uint16_t CD_POISON = 0x0040; const uint16_t CD_ACID = 0x0080; const uint16_t CD_LIGHT = 0x0100; const uint16_t CD_STONE = 0x0200; const uint16_t CD_NO_SLEEP = 0x1000; const uint16_t CD_INFRA = 0x2000; const uint16_t CD_MAX_HP = 0x4000; } } namespace player { const int32_t PLAYER_MAX_EXP = 9999999L; // Maximum amount of experience -CJS- const uint8_t PLAYER_USE_DEVICE_DIFFICULTY = 3; // x> Harder devices x< Easier devices const uint16_t PLAYER_FOOD_FULL = 10000; // Getting full const uint16_t PLAYER_FOOD_MAX = 15000; // Maximum food value, beyond is wasted const uint16_t PLAYER_FOOD_FAINT = 300; // Character begins fainting const uint16_t PLAYER_FOOD_WEAK = 1000; // Warn player that they're getting weak const uint16_t PLAYER_FOOD_ALERT = 2000; // Alert player that they're getting low on food const uint8_t PLAYER_REGEN_FAINT = 33; // Regen factor*2^16 when fainting const uint8_t PLAYER_REGEN_WEAK = 98; // Regen factor*2^16 when weak const uint8_t PLAYER_REGEN_NORMAL = 197; // Regen factor*2^16 when full const uint16_t PLAYER_REGEN_HPBASE = 1442; // Min amount hp regen*2^16 const uint16_t PLAYER_REGEN_MNBASE = 524; // Min amount mana regen*2^16 const uint8_t PLAYER_WEIGHT_CAP = 130; // "#"*(1/10 pounds) per strength point // definitions for the player's status field namespace status { const uint32_t PY_HUNGRY = 0x00000001L; const uint32_t PY_WEAK = 0x00000002L; const uint32_t PY_BLIND = 0x00000004L; const uint32_t PY_CONFUSED = 0x00000008L; const uint32_t PY_FEAR = 0x00000010L; const uint32_t PY_POISONED = 0x00000020L; const uint32_t PY_FAST = 0x00000040L; const uint32_t PY_SLOW = 0x00000080L; const uint32_t PY_SEARCH = 0x00000100L; const uint32_t PY_REST = 0x00000200L; const uint32_t PY_STUDY = 0x00000400L; const uint32_t PY_INVULN = 0x00001000L; const uint32_t PY_HERO = 0x00002000L; const uint32_t PY_SHERO = 0x00004000L; const uint32_t PY_BLESSED = 0x00008000L; const uint32_t PY_DET_INV = 0x00010000L; const uint32_t PY_TIM_INFRA = 0x00020000L; const uint32_t PY_SPEED = 0x00040000L; const uint32_t PY_STR_WGT = 0x00080000L; const uint32_t PY_PARALYSED = 0x00100000L; const uint32_t PY_REPEAT = 0x00200000L; const uint32_t PY_ARMOR = 0x00400000L; const uint32_t PY_STATS = 0x3F000000L; const uint32_t PY_STR = 0x01000000L; // these 6 stat flags must be adjacent const uint32_t PY_INT = 0x02000000L; const uint32_t PY_WIS = 0x04000000L; const uint32_t PY_DEX = 0x08000000L; const uint32_t PY_CON = 0x10000000L; const uint32_t PY_CHR = 0x20000000L; const uint32_t PY_HP = 0x40000000L; const uint32_t PY_MANA = 0x80000000L; } } namespace identification { // id's used for object description, stored in objects_identified array const uint8_t OD_TRIED = 0x1; const uint8_t OD_KNOWN1 = 0x2; // id's used for item description, stored in i_ptr->ident const uint8_t ID_MAGIK = 0x1; const uint8_t ID_DAMD = 0x2; const uint8_t ID_EMPTY = 0x4; const uint8_t ID_KNOWN2 = 0x8; const uint8_t ID_STORE_BOUGHT = 0x10; const uint8_t ID_SHOW_HIT_DAM = 0x20; const uint8_t ID_NO_SHOW_P1 = 0x40; const uint8_t ID_SHOW_P1 = 0x80; } namespace spells { // Class spell types const uint8_t SPELL_TYPE_NONE = 0; const uint8_t SPELL_TYPE_MAGE = 1; const uint8_t SPELL_TYPE_PRIEST = 2; // offsets to spell names in spell_names[] array const uint8_t NAME_OFFSET_SPELLS = 0; const uint8_t NAME_OFFSET_PRAYERS = 31; } namespace stores { const uint8_t STORE_MAX_AUTO_BUY_ITEMS = 18; // Max diff objects in stock for auto buy const uint8_t STORE_MIN_AUTO_SELL_ITEMS = 10; // Min diff objects in stock for auto sell const uint8_t STORE_STOCK_TURN_AROUND = 9; // Amount of buying and selling normally } } umoria-5.7.10+20181022/src/identification.h0000644000175000017500000000646313363422757016723 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. #pragma once // indexes into the special name table enum special_name_ids { SN_NULL, SN_R, SN_RA, SN_RF, SN_RC, SN_RL, SN_HA, SN_DF, SN_SA, SN_SD, SN_SE, SN_SU, SN_FT, SN_FB, SN_FREE_ACTION, SN_SLAYING, SN_CLUMSINESS, SN_WEAKNESS, SN_SLOW_DESCENT, SN_SPEED, SN_STEALTH, SN_SLOWNESS, SN_NOISE, SN_GREAT_MASS, SN_INTELLIGENCE, SN_WISDOM, SN_INFRAVISION, SN_MIGHT, SN_LORDLINESS, SN_MAGI, SN_BEAUTY, SN_SEEING, SN_REGENERATION, SN_STUPIDITY, SN_DULLNESS, SN_BLINDNESS, SN_TIMIDNESS, SN_TELEPORTATION, SN_UGLINESS, SN_PROTECTION, SN_IRRITATION, SN_VULNERABILITY, SN_ENVELOPING, SN_FIRE, SN_SLAY_EVIL, SN_DRAGON_SLAYING, SN_EMPTY, SN_LOCKED, SN_POISON_NEEDLE, SN_GAS_TRAP, SN_EXPLOSION_DEVICE, SN_SUMMONING_RUNES, SN_MULTIPLE_TRAPS, SN_DISARMED, SN_UNLOCKED, SN_SLAY_ANIMAL, SN_ARRAY_SIZE, // 56th item (size value for arrays) }; constexpr uint8_t MAX_COLORS = 49; // Used with potions constexpr uint8_t MAX_MUSHROOMS = 22; // Used with mushrooms constexpr uint8_t MAX_WOODS = 25; // Used with staffs constexpr uint8_t MAX_METALS = 25; // Used with wands constexpr uint8_t MAX_ROCKS = 32; // Used with rings constexpr uint8_t MAX_AMULETS = 11; // Used with amulets constexpr uint8_t MAX_TITLES = 45; // Used with scrolls constexpr uint8_t MAX_SYLLABLES = 153; // Used with scrolls extern uint8_t objects_identified[OBJECT_IDENT_SIZE]; extern const char *special_item_names[special_name_ids::SN_ARRAY_SIZE]; // Following are arrays for descriptive pieces extern const char *colors[MAX_COLORS]; extern const char *mushrooms[MAX_MUSHROOMS]; extern const char *woods[MAX_WOODS]; extern const char *metals[MAX_METALS]; extern const char *rocks[MAX_ROCKS]; extern const char *amulets[MAX_AMULETS]; extern const char *syllables[MAX_SYLLABLES]; void identifyGameObject(); void magicInitializeItemNames(); int16_t objectPositionOffset(int category_id, int sub_category_id); void itemSetAsIdentified(int category_id, int sub_category_id); bool itemSetColorlessAsIdentified(int category_id, int sub_category_id, int identification); void spellItemIdentifyAndRemoveRandomInscription(Inventory_t &item); bool spellItemIdentified(Inventory_t const &item); void spellItemRemoveIdentification(Inventory_t &item); void itemIdentificationClearEmpty(Inventory_t &item); void itemIdentifyAsStoreBought(Inventory_t &item); void itemSetAsTried(Inventory_t const &item); void itemIdentify(Inventory_t &item, int &item_id); void itemRemoveMagicNaming(Inventory_t &item); void itemDescription(obj_desc_t description, Inventory_t const &item, bool add_prefix); void itemChargesRemainingDescription(int item_id); void itemTypeRemainingCountDescription(int item_id); void itemInscribe(); void itemAppendToInscription(Inventory_t &item, uint8_t item_ident_type); void itemReplaceInscription(Inventory_t &item, const char *inscription); void objectBlockedByMonster(int monster_id); umoria-5.7.10+20181022/src/dungeon.cpp0000644000175000017500000005021713363422757015720 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. // The main command interpreter, updating player status #include "headers.h" // The Dungeon global // Yup, this initialization is ugly, we'll fix...eventually! -MRC- Dungeon_t dg = Dungeon_t{0, 0, {}, -1, 0, true, {}}; // dungeonDisplayMap shrinks the dungeon to a single screen void dungeonDisplayMap() { // Save the game screen terminalSaveScreen(); clearScreen(); int16_t priority[256] = {0}; priority[60] = 5; // char '<' priority[62] = 5; // char '>' priority[64] = 10; // char '@' priority[35] = -5; // char '#' priority[46] = -10; // char '.' priority[92] = -3; // char '\' priority[32] = -15; // char ' ' // Display highest priority object in the RATIO, by RATIO area constexpr uint8_t RATIO = 3; uint8_t panel_width = MAX_WIDTH / RATIO; uint8_t panel_height = MAX_HEIGHT / RATIO; char map[MAX_WIDTH / RATIO + 1] = {'\0'}; char line_buffer[80]; // Add screen border addChar('+', Coord_t{0, 0}); addChar('+', Coord_t{0, panel_width + 1}); for (uint8_t i = 0; i < panel_width; i++) { addChar('-', Coord_t{0, i + 1}); addChar('-', Coord_t{panel_height + 1, i + 1}); } for (uint8_t i = 0; i < panel_height; i++) { addChar('|', Coord_t{i + 1, 0}); addChar('|', Coord_t{i + 1, panel_width + 1}); } addChar('+', Coord_t{panel_height + 1, 0}); addChar('+', Coord_t{panel_height + 1, panel_width + 1}); putString("Hit any key to continue", Coord_t{23, 23}); int player_y = 0; int player_x = 0; int line = -1; // Shrink the dungeon! for (uint8_t y = 0; y < MAX_HEIGHT; y++) { uint8_t row = y / RATIO; if (row != line) { if (line >= 0) { sprintf(line_buffer, "|%s|", map); putString(line_buffer, Coord_t{line + 1, 0}); } for (uint8_t j = 0; j < panel_width; j++) { map[j] = ' '; } line = row; } for (uint8_t x = 0; x < MAX_WIDTH; x++) { uint8_t col = x / RATIO; char cave_char = caveGetTileSymbol(Coord_t{y, x}); if (priority[(uint8_t) map[col]] < priority[(uint8_t) cave_char]) { map[col] = cave_char; } if (map[col] == '@') { // +1 to account for border player_x = col + 1; player_y = row + 1; } } } if (line >= 0) { sprintf(line_buffer, "|%s|", map); putString(line_buffer, Coord_t{line + 1, 0}); } // Move cursor onto player character moveCursor(Coord_t{player_y, player_x}); // wait for any keypress (void) getKeyInput(); // restore the game screen terminalRestoreScreen(); } // Checks a co-ordinate for in bounds status -RAK- bool coordInBounds(Coord_t const &coord) { bool y = coord.y > 0 && coord.y < dg.height - 1; bool x = coord.x > 0 && coord.x < dg.width - 1; return y && x; } // Distance between two points -RAK- int coordDistanceBetween(Coord_t const &from, Coord_t const &to) { int dy = from.y - to.y; if (dy < 0) { dy = -dy; } int dx = from.x - to.x; if (dx < 0) { dx = -dx; } int a = (dy + dx) << 1; int b = dy > dx ? dx : dy; return ((a - b) >> 1); } // Checks points north, south, east, and west for a wall -RAK- // note that y,x is always coordInBounds(), i.e. 0 < y < dg.height-1, // and 0 < x < dg.width-1 int coordWallsNextTo(Coord_t const &coord) { int walls = 0; if (dg.floor[coord.y - 1][coord.x].feature_id >= MIN_CAVE_WALL) { walls++; } if (dg.floor[coord.y + 1][coord.x].feature_id >= MIN_CAVE_WALL) { walls++; } if (dg.floor[coord.y][coord.x - 1].feature_id >= MIN_CAVE_WALL) { walls++; } if (dg.floor[coord.y][coord.x + 1].feature_id >= MIN_CAVE_WALL) { walls++; } return walls; } // Checks all adjacent spots for corridors -RAK- // note that y, x is always coordInBounds(), hence no need to check that // j, k are coordInBounds(), even if they are 0 or cur_x-1 is still works int coordCorridorWallsNextTo(Coord_t const &coord) { int walls = 0; for (int y = coord.y - 1; y <= coord.y + 1; y++) { for (int x = coord.x - 1; x <= coord.x + 1; x++) { int tile_id = dg.floor[y][x].feature_id; int treasure_id = dg.floor[y][x].treasure_id; // should fail if there is already a door present if (tile_id == TILE_CORR_FLOOR && (treasure_id == 0 || treasure_list[treasure_id].category_id < TV_MIN_DOORS)) { walls++; } } } return walls; } // Returns symbol for given row, column -RAK- char caveGetTileSymbol(Coord_t const &coord) { Tile_t const &tile = dg.floor[coord.y][coord.x]; if (tile.creature_id == 1 && ((py.running_tracker == 0) || config::options::run_print_self)) { return '@'; } if ((py.flags.status & config::player::status::PY_BLIND) != 0u) { return ' '; } if (py.flags.image > 0 && randomNumber(12) == 1) { return (uint8_t) (randomNumber(95) + 31); } if (tile.creature_id > 1 && monsters[tile.creature_id].lit) { return creatures_list[monsters[tile.creature_id].creature_id].sprite; } if (!tile.permanent_light && !tile.temporary_light && !tile.field_mark) { return ' '; } if (tile.treasure_id != 0 && treasure_list[tile.treasure_id].category_id != TV_INVIS_TRAP) { return treasure_list[tile.treasure_id].sprite; } if (tile.feature_id <= MAX_CAVE_FLOOR) { return '.'; } if (tile.feature_id == TILE_GRANITE_WALL || tile.feature_id == TILE_BOUNDARY_WALL || !config::options::highlight_seams) { return '#'; } // Originally set highlight bit, but that is not portable, // now use the percent sign instead. return '%'; } // Tests a spot for light or field mark status -RAK- bool caveTileVisible(Coord_t const &coord) { return dg.floor[coord.y][coord.x].permanent_light || dg.floor[coord.y][coord.x].temporary_light || dg.floor[coord.y][coord.x].field_mark; } // Places a particular trap at location y, x -RAK- void dungeonSetTrap(Coord_t const &coord, int sub_type_id) { int free_treasure_id = popt(); dg.floor[coord.y][coord.x].treasure_id = (uint8_t) free_treasure_id; inventoryItemCopyTo(config::dungeon::objects::OBJ_TRAP_LIST + sub_type_id, treasure_list[free_treasure_id]); } // Change a trap from invisible to visible -RAK- // Note: Secret doors are handled here void trapChangeVisibility(Coord_t const &coord) { uint8_t treasure_id = dg.floor[coord.y][coord.x].treasure_id; Inventory_t &item = treasure_list[treasure_id]; if (item.category_id == TV_INVIS_TRAP) { item.category_id = TV_VIS_TRAP; dungeonLiteSpot(coord); return; } // change secret door to closed door if (item.category_id == TV_SECRET_DOOR) { item.id = config::dungeon::objects::OBJ_CLOSED_DOOR; item.category_id = game_objects[config::dungeon::objects::OBJ_CLOSED_DOOR].category_id; item.sprite = game_objects[config::dungeon::objects::OBJ_CLOSED_DOOR].sprite; dungeonLiteSpot(coord); } } // Places rubble at location y, x -RAK- void dungeonPlaceRubble(Coord_t const &coord) { int free_treasure_id = popt(); dg.floor[coord.y][coord.x].treasure_id = (uint8_t) free_treasure_id; dg.floor[coord.y][coord.x].feature_id = TILE_BLOCKED_FLOOR; inventoryItemCopyTo(config::dungeon::objects::OBJ_RUBBLE, treasure_list[free_treasure_id]); } // Places a treasure (Gold or Gems) at given row, column -RAK- void dungeonPlaceGold(Coord_t const &coord) { int free_treasure_id = popt(); int gold_type_id = ((randomNumber(dg.current_level + 2) + 2) / 2) - 1; if (randomNumber(config::treasure::TREASURE_CHANCE_OF_GREAT_ITEM) == 1) { gold_type_id += randomNumber(dg.current_level + 1); } if (gold_type_id >= config::dungeon::objects::MAX_GOLD_TYPES) { gold_type_id = config::dungeon::objects::MAX_GOLD_TYPES - 1; } dg.floor[coord.y][coord.x].treasure_id = (uint8_t) free_treasure_id; inventoryItemCopyTo(config::dungeon::objects::OBJ_GOLD_LIST + gold_type_id, treasure_list[free_treasure_id]); treasure_list[free_treasure_id].cost += (8L * (int32_t) randomNumber((int) treasure_list[free_treasure_id].cost)) + randomNumber(8); if (dg.floor[coord.y][coord.x].creature_id == 1) { printMessage("You feel something roll beneath your feet."); } } // Places an object at given row, column co-ordinate -RAK- void dungeonPlaceRandomObjectAt(Coord_t const &coord, bool must_be_small) { int free_treasure_id = popt(); dg.floor[coord.y][coord.x].treasure_id = (uint8_t) free_treasure_id; int object_id = itemGetRandomObjectId(dg.current_level, must_be_small); inventoryItemCopyTo(sorted_objects[object_id], treasure_list[free_treasure_id]); magicTreasureMagicalAbility(free_treasure_id, dg.current_level); if (dg.floor[coord.y][coord.x].creature_id == 1) { printMessage("You feel something roll beneath your feet."); // -CJS- } } // Allocates an object for tunnels and rooms -RAK- void dungeonAllocateAndPlaceObject(bool (*set_function)(int), int object_type, int number) { int y, x; for (int i = 0; i < number; i++) { // don't put an object beneath the player, this could cause // problems if player is standing under rubble, or on a trap. do { y = randomNumber(dg.height) - 1; x = randomNumber(dg.width) - 1; } while (!(*set_function)(dg.floor[y][x].feature_id) || dg.floor[y][x].treasure_id != 0 || (y == py.row && x == py.col)); switch (object_type) { case 1: dungeonSetTrap(Coord_t{y, x}, randomNumber(config::dungeon::objects::MAX_TRAPS) - 1); break; case 2: // NOTE: object_type == 2 is no longer used - it used to be visible traps. // FIXME: there was no `break` here so `case 3` catches it? -MRC- case 3: dungeonPlaceRubble(Coord_t{y, x}); break; case 4: dungeonPlaceGold(Coord_t{y, x}); break; case 5: dungeonPlaceRandomObjectAt(Coord_t{y, x}, false); break; default: break; } } } // Creates objects nearby the coordinates given -RAK- void dungeonPlaceRandomObjectNear(Coord_t coord, int tries) { do { for (int i = 0; i <= 10; i++) { Coord_t at = Coord_t{ coord.y - 3 + randomNumber(5), coord.x - 4 + randomNumber(7) }; if (coordInBounds(at) && dg.floor[at.y][at.x].feature_id <= MAX_CAVE_FLOOR && dg.floor[at.y][at.x].treasure_id == 0) { if (randomNumber(100) < 75) { dungeonPlaceRandomObjectAt(at, false); } else { dungeonPlaceGold(at); } i = 9; } } tries--; } while (tries != 0); } // Moves creature record from one space to another -RAK- // this always works correctly, even if y1==y2 and x1==x2 void dungeonMoveCreatureRecord(Coord_t const &from, Coord_t const &to) { int id = dg.floor[from.y][from.x].creature_id; dg.floor[from.y][from.x].creature_id = 0; dg.floor[to.y][to.x].creature_id = (uint8_t) id; } // Room is lit, make it appear -RAK- void dungeonLightRoom(Coord_t const &coord) { int height_middle = (SCREEN_HEIGHT / 2); int width_middle = (SCREEN_WIDTH / 2); int top = (coord.y / height_middle) * height_middle; int left = (coord.x / width_middle) * width_middle; int bottom = top + height_middle - 1; int right = left + width_middle - 1; for (int y = top; y <= bottom; y++) { for (int x = left; x <= right; x++) { Tile_t &tile = dg.floor[y][x]; if (tile.perma_lit_room && !tile.permanent_light) { tile.permanent_light = true; if (tile.feature_id == TILE_DARK_FLOOR) { tile.feature_id = TILE_LIGHT_FLOOR; } if (!tile.field_mark && tile.treasure_id != 0) { int treasure_id = treasure_list[tile.treasure_id].category_id; if (treasure_id >= TV_MIN_VISIBLE && treasure_id <= TV_MAX_VISIBLE) { tile.field_mark = true; } } panelPutTile(caveGetTileSymbol(Coord_t{y, x}), Coord_t{y, x}); } } } } // Lights up given location -RAK- void dungeonLiteSpot(Coord_t const &coord) { if (!coordInsidePanel(coord)) { return; } char symbol = caveGetTileSymbol(coord); panelPutTile(symbol, coord); } // Normal movement // When FIND_FLAG, light only permanent features static void sub1_move_light(Coord_t const &from, Coord_t const &to) { if (py.temporary_light_only) { // Turn off lamp light for (int y = from.y - 1; y <= from.y + 1; y++) { for (int x = from.x - 1; x <= from.x + 1; x++) { dg.floor[y][x].temporary_light = false; } } if ((py.running_tracker != 0) && !config::options::run_print_self) { py.temporary_light_only = false; } } else if ((py.running_tracker == 0) || config::options::run_print_self) { py.temporary_light_only = true; } for (int y = to.y - 1; y <= to.y + 1; y++) { for (int x = to.x - 1; x <= to.x + 1; x++) { Tile_t &tile = dg.floor[y][x]; // only light up if normal movement if (py.temporary_light_only) { tile.temporary_light = true; } if (tile.feature_id >= MIN_CAVE_WALL) { tile.permanent_light = true; } else if (!tile.field_mark && tile.treasure_id != 0) { int tval = treasure_list[tile.treasure_id].category_id; if (tval >= TV_MIN_VISIBLE && tval <= TV_MAX_VISIBLE) { tile.field_mark = true; } } } } // From uppermost to bottom most lines player was on. int top, left, bottom, right; if (from.y < to.y) { top = from.y - 1; bottom = to.y + 1; } else { top = to.y - 1; bottom = from.y + 1; } if (from.x < to.x) { left = from.x - 1; right = to.x + 1; } else { left = to.x - 1; right = from.x + 1; } for (int y = top; y <= bottom; y++) { // Leftmost to rightmost do for (int x = left; x <= right; x++) { panelPutTile(caveGetTileSymbol(Coord_t{y, x}), Coord_t{y, x}); } } } // When blinded, move only the player symbol. // With no light, movement becomes involved. static void sub3_move_light(Coord_t const &from, Coord_t const &to) { if (py.temporary_light_only) { for (int y = from.y - 1; y <= from.y + 1; y++) { for (int x = from.x - 1; x <= from.x + 1; x++) { dg.floor[y][x].temporary_light = false; panelPutTile(caveGetTileSymbol(Coord_t{y, x}), Coord_t{y, x}); } } py.temporary_light_only = false; } else if ((py.running_tracker == 0) || config::options::run_print_self) { panelPutTile(caveGetTileSymbol(from), from); } if ((py.running_tracker == 0) || config::options::run_print_self) { panelPutTile('@', to); } } // Package for moving the character's light about the screen // Four cases : Normal, Finding, Blind, and No light -RAK- void dungeonMoveCharacterLight(Coord_t const &from, Coord_t const &to) { if (py.flags.blind > 0 || !py.carrying_light) { sub3_move_light(from, to); } else { sub1_move_light(from, to); } } // Deletes a monster entry from the level -RAK- void dungeonDeleteMonster(int id) { Monster_t *monster = &monsters[id]; dg.floor[monster->y][monster->x].creature_id = 0; if (monster->lit) { dungeonLiteSpot(Coord_t{monster->y, monster->x}); } int last_id = next_free_monster_id - 1; if (id != last_id) { monster = &monsters[last_id]; dg.floor[monster->y][monster->x].creature_id = (uint8_t) id; monsters[id] = monsters[last_id]; } next_free_monster_id--; monsters[next_free_monster_id] = blank_monster; if (monster_multiply_total > 0) { monster_multiply_total--; } } // The following two procedures implement the same function as delete monster. // However, they are used within updateMonsters(), because deleting a monster // while scanning the monsters causes two problems, monsters might get two // turns, and m_ptr/monptr might be invalid after the dungeonDeleteMonster. // Hence the delete is done in two steps. // // dungeonDeleteMonsterFix1 does everything dungeonDeleteMonster does except delete // the monster record and reduce next_free_monster_id, this is called in breathe, and // a couple of places in creatures.c void dungeonDeleteMonsterFix1(int id) { Monster_t &monster = monsters[id]; // force the hp negative to ensure that the monster is dead, for example, // if the monster was just eaten by another, it will still have positive // hit points monster.hp = -1; dg.floor[monster.y][monster.x].creature_id = 0; if (monster.lit) { dungeonLiteSpot(Coord_t{monster.y, monster.x}); } if (monster_multiply_total > 0) { monster_multiply_total--; } } // dungeonDeleteMonsterFix2 does everything in dungeonDeleteMonster that wasn't done // by fix1_monster_delete above, this is only called in updateMonsters() void dungeonDeleteMonsterFix2(int id) { int last_id = next_free_monster_id - 1; if (id != last_id) { int y = monsters[last_id].y; int x = monsters[last_id].x; dg.floor[y][x].creature_id = (uint8_t) id; monsters[id] = monsters[last_id]; } monsters[last_id] = blank_monster; next_free_monster_id--; } // Creates objects nearby the coordinates given -RAK- int dungeonSummonObject(Coord_t coord, int amount, int object_type) { int real_type; if (object_type == 1 || object_type == 5) { real_type = 1; // object_type == 1 -> objects } else { real_type = 256; // object_type == 2 -> gold } int result = 0; do { for (int tries = 0; tries <= 20; tries++) { Coord_t at = Coord_t{ coord.y - 3 + randomNumber(5), coord.x - 3 + randomNumber(5), }; if (coordInBounds(at) && los(coord.y, coord.x, at.y, at.x)) { if (dg.floor[at.y][at.x].feature_id <= MAX_OPEN_SPACE && dg.floor[at.y][at.x].treasure_id == 0) { // object_type == 3 -> 50% objects, 50% gold if (object_type == 3 || object_type == 7) { if (randomNumber(100) < 50) { real_type = 1; } else { real_type = 256; } } if (real_type == 1) { dungeonPlaceRandomObjectAt(at, (object_type >= 4)); } else { dungeonPlaceGold(at); } dungeonLiteSpot(at); if (caveTileVisible(at)) { result += real_type; } tries = 20; } } } amount--; } while (amount != 0); return result; } // Deletes object from given location -RAK- bool dungeonDeleteObject(Coord_t const &coord) { Tile_t &tile = dg.floor[coord.y][coord.x]; if (tile.feature_id == TILE_BLOCKED_FLOOR) { tile.feature_id = TILE_CORR_FLOOR; } pusht(tile.treasure_id); tile.treasure_id = 0; tile.field_mark = false; dungeonLiteSpot(coord); return caveTileVisible(coord); } umoria-5.7.10+20181022/src/inventory.h0000644000175000017500000001100413363422757015752 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. #pragma once // Size of inventory array (DO NOT CHANGE) constexpr uint8_t PLAYER_INVENTORY_SIZE = 34; // Inventory stacking `sub_category_id`s - these never stack constexpr uint8_t ITEM_NEVER_STACK_MIN = 0; constexpr uint8_t ITEM_NEVER_STACK_MAX = 63; // these items always stack with others of same `sub_category_id`s, always treated as // single objects, must be power of 2; constexpr uint8_t ITEM_SINGLE_STACK_MIN = 64; constexpr uint8_t ITEM_SINGLE_STACK_MAX = 192; // see NOTE below // these items stack with others only if have same `sub_category_id`s and same `misc_use`, // they are treated as a group for wielding, etc. constexpr uint8_t ITEM_GROUP_MIN = 192; constexpr uint8_t ITEM_GROUP_MAX = 255; // NOTE: items with `sub_category_id`s = 192 are treated as single objects, // but only stack with others of same `sub_category_id`s if have the same // `misc_use` value, only used for torches. // Size of an inscription in the Inventory_t. Notice alignment, must be 4*x + 1 constexpr uint8_t INSCRIP_SIZE = 13; // Inventory_t is created for an item the player may wear about // their person, or store in their inventory pack. // // Only damage, ac, and tchar are constant; level could possibly be made // constant by changing index instead; all are used rarely. // // Extra fields x and y for location in dungeon would simplify pusht(). // // Making inscrip[] a pointer and malloc-ing space does not work, there are // two many places where `Inventory_t` are copied, which results in dangling // pointers, so we use a char array for them instead. typedef struct { uint16_t id; // Index to object_list uint8_t special_name_id; // Object special name char inscription[INSCRIP_SIZE]; // Object inscription uint32_t flags; // Special flags uint8_t category_id; // Category number (tval) uint8_t sprite; // Character representation - ASCII symbol (tchar) int16_t misc_use; // Misc. use variable (p1) int32_t cost; // Cost of item uint8_t sub_category_id; // Sub-category number uint8_t items_count; // Number of items uint16_t weight; // Weight int16_t to_hit; // Plusses to hit int16_t to_damage; // Plusses to damage int16_t ac; // Normal AC int16_t to_ac; // Plusses to AC Dice_t damage; // Damage when hits uint8_t depth_first_found; // Dungeon level item first found uint8_t identification; // Identify information } Inventory_t; // magic numbers for players equipment inventory array enum player_equipment { EQUIPMENT_WIELD = 22, // must be first item in equipment list EQUIPMENT_HEAD, EQUIPMENT_NECK, EQUIPMENT_BODY, EQUIPMENT_ARM, EQUIPMENT_HANDS, EQUIPMENT_RIGHT, EQUIPMENT_LEFT, EQUIPMENT_FEET, EQUIPMENT_OUTER, EQUIPMENT_LIGHT, EQUIPMENT_AUX, }; extern Inventory_t inventory[PLAYER_INVENTORY_SIZE]; uint32_t inventoryCollectAllItemFlags(); void inventoryDestroyItem(int item_id); void inventoryTakeOneItem(Inventory_t *to_item, Inventory_t *from_item); void inventoryDropItem(int item_id, bool drop_all); bool inventoryDiminishLightAttack(bool noticed); bool inventoryDiminishChargesAttack(uint8_t creature_level, int16_t &monster_hp, bool noticed); bool executeDisenchantAttack(); bool inventoryCanCarryItemCount(Inventory_t const &item); bool inventoryCanCarryItem(Inventory_t const &item); int inventoryCarryItem(Inventory_t &new_item); bool inventoryFindRange(int item_id_start, int item_id_end, int &j, int &k); void inventoryItemCopyTo(int from_item_id, Inventory_t &to_item); bool setNull(Inventory_t *item); bool setFrostDestroyableItems(Inventory_t *item); bool setLightningDestroyableItems(Inventory_t *item); bool setAcidDestroyableItems(Inventory_t *item); bool setFireDestroyableItems(Inventory_t *item); void damageCorrodingGas(const char *creature_name); void damagePoisonedGas(int damage, const char *creature_name); void damageFire(int damage, const char *creature_name); void damageCold(int damage, const char *creature_name); void damageLightningBolt(int damage, const char *creature_name); void damageAcid(int damage, const char *creature_name); umoria-5.7.10+20181022/src/helpers.h0000644000175000017500000000150413363422757015363 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. // Generic helper functions used throughout the code // Eventually we want these helpers to have no external dependencies // other than the standard library functions. #pragma once int getAndClearFirstBit(uint32_t &flag); void insertNumberIntoString(char *to_string, const char *from_string, int32_t number, bool show_sign); void insertStringIntoString(char *to_string, const char *from_string, const char *str_to_insert); bool isVowel(char ch); bool stringToNumber(const char *str, int &number); uint32_t getCurrentUnixTime(); void humanDateString(char *day); umoria-5.7.10+20181022/src/player_eat.cpp0000644000175000017500000002130713363422757016404 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. // Food code #include "headers.h" enum class FoodMagicTypes { poison = 1, blindness, paranoia, confusion, hallucination, cure_poison, cure_blindness, cure_paranoia, cure_confusion, weakness, unhealth, restore_str = 16, // 12-15 are no longer used restore_con, restore_int, restore_wis, restore_dex, restore_chr, first_aid, minor_cures, light_cures, major_cures = 26, // 25 no longer used poisonous_food, }; // Eat some food. -RAK- void playerEat() { game.player_free_turn = true; if (py.unique_inventory_items == 0) { printMessage("But you are not carrying anything."); return; } int item_pos_start, item_pos_end; if (!inventoryFindRange(TV_FOOD, TV_NEVER, item_pos_start, item_pos_end)) { printMessage("You are not carrying any food."); return; } int item_id; if (!inventoryGetInputForItemId(item_id, "Eat what?", item_pos_start, item_pos_end, CNIL, CNIL)) { return; } game.player_free_turn = false; bool identified = false; Inventory_t *item = &inventory[item_id]; uint32_t item_flags = item->flags; while (item_flags != 0) { switch ((FoodMagicTypes) (getAndClearFirstBit(item_flags) + 1)) { case FoodMagicTypes::poison: py.flags.poisoned += randomNumber(10) + item->depth_first_found; identified = true; break; case FoodMagicTypes::blindness: py.flags.blind += randomNumber(250) + 10 * item->depth_first_found + 100; drawCavePanel(); printMessage("A veil of darkness surrounds you."); identified = true; break; case FoodMagicTypes::paranoia: py.flags.afraid += randomNumber(10) + item->depth_first_found; printMessage("You feel terrified!"); identified = true; break; case FoodMagicTypes::confusion: py.flags.confused += randomNumber(10) + item->depth_first_found; printMessage("You feel drugged."); identified = true; break; case FoodMagicTypes::hallucination: py.flags.image += randomNumber(200) + 25 * item->depth_first_found + 200; printMessage("You feel drugged."); identified = true; break; case FoodMagicTypes::cure_poison: identified = playerCurePoison(); break; case FoodMagicTypes::cure_blindness: identified = playerCureBlindness(); break; case FoodMagicTypes::cure_paranoia: if (py.flags.afraid > 1) { py.flags.afraid = 1; identified = true; } break; case FoodMagicTypes::cure_confusion: identified = playerCureConfusion(); break; case FoodMagicTypes::weakness: spellLoseSTR(); identified = true; break; case FoodMagicTypes::unhealth: spellLoseCON(); identified = true; break; #if 0 // 12 through 15 are no longer used case 12: lose_int(); identified = true; break; case 13: lose_wis(); identified = true; break; case 14: lose_dex(); identified = true; break; case 15: lose_chr(); identified = true; break; #endif case FoodMagicTypes::restore_str: if (playerStatRestore(py_attrs::A_STR)) { printMessage("You feel your strength returning."); identified = true; } break; case FoodMagicTypes::restore_con: if (playerStatRestore(py_attrs::A_CON)) { printMessage("You feel your health returning."); identified = true; } break; case FoodMagicTypes::restore_int: if (playerStatRestore(py_attrs::A_INT)) { printMessage("Your head spins a moment."); identified = true; } break; case FoodMagicTypes::restore_wis: if (playerStatRestore(py_attrs::A_WIS)) { printMessage("You feel your wisdom returning."); identified = true; } break; case FoodMagicTypes::restore_dex: if (playerStatRestore(py_attrs::A_DEX)) { printMessage("You feel more dexterous."); identified = true; } break; case FoodMagicTypes::restore_chr: if (playerStatRestore(py_attrs::A_CHR)) { printMessage("Your skin stops itching."); identified = true; } break; case FoodMagicTypes::first_aid: identified = spellChangePlayerHitPoints(randomNumber(6)); break; case FoodMagicTypes::minor_cures: identified = spellChangePlayerHitPoints(randomNumber(12)); break; case FoodMagicTypes::light_cures: identified = spellChangePlayerHitPoints(randomNumber(18)); break; #if 0 // 25 is no longer used case 25: identified = hp_player(damroll(3, 6)); break; #endif case FoodMagicTypes::major_cures: identified = spellChangePlayerHitPoints(diceRoll(Dice_t{3, 12})); break; case FoodMagicTypes::poisonous_food: playerTakesHit(randomNumber(18), "poisonous food."); identified = true; break; #if 0 // 28 through 30 are no longer used case 28: take_hit(randint(8), "poisonous food."); identified = true; break; case 29: take_hit(damroll(2, 8), "poisonous food."); identified = true; break; case 30: take_hit(damroll(3, 8), "poisonous food."); identified = true; break; #endif default: printMessage("Internal error in playerEat()"); break; } } if (identified) { if (!itemSetColorlessAsIdentified(item->category_id, item->sub_category_id, item->identification)) { // use identified it, gain experience // round half-way case up py.misc.exp += (item->depth_first_found + (py.misc.level >> 1)) / py.misc.level; displayCharacterExperience(); itemIdentify(inventory[item_id], item_id); item = &inventory[item_id]; } } else if (!itemSetColorlessAsIdentified(item->category_id, item->sub_category_id, item->identification)) { itemSetAsTried(*item); } playerIngestFood(item->misc_use); py.flags.status &= ~(config::player::status::PY_WEAK | config::player::status::PY_HUNGRY); printCharacterHungerStatus(); itemTypeRemainingCountDescription(item_id); inventoryDestroyItem(item_id); } // Add to the players food time -RAK- void playerIngestFood(int amount) { if (py.flags.food < 0) { py.flags.food = 0; } py.flags.food += amount; if (py.flags.food > config::player::PLAYER_FOOD_MAX) { printMessage("You are bloated from overeating."); // Calculate how much of amount is responsible for the bloating. Give the // player food credit for 1/50, and also slow them for that many turns. int extra = py.flags.food - config::player::PLAYER_FOOD_MAX; if (extra > amount) { extra = amount; } int penalty = extra / 50; py.flags.slow += penalty; if (extra == amount) { py.flags.food = (int16_t) (py.flags.food - amount + penalty); } else { py.flags.food = (int16_t) (config::player::PLAYER_FOOD_MAX + penalty); } } else if (py.flags.food > config::player::PLAYER_FOOD_FULL) { printMessage("You are full."); } } umoria-5.7.10+20181022/src/helpers.cpp0000644000175000017500000001007113363422757015715 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. #include "headers.h" #include // Returns position of first set bit and clears that bit -RAK- int getAndClearFirstBit(uint32_t &flag) { uint32_t mask = 0x1; for (int i = 0; i < (int) sizeof(flag) * 8; i++) { if ((flag & mask) != 0u) { flag &= ~mask; return i; } mask <<= 1; } // no one bits found return -1; } // Insert a long number into a string (was `insert_lnum()` function) void insertNumberIntoString(char *to_string, const char *from_string, int32_t number, bool show_sign) { size_t from_len = strlen(from_string); char *to_str_tmp = to_string; char *str = nullptr; // must be int for strncmp() int flag = 1; while (flag != 0) { str = strchr(to_str_tmp, from_string[0]); if (str == nullptr) { flag = 0; } else { flag = strncmp(str, from_string, from_len); if (flag != 0) { to_str_tmp = str + 1; } } } if (str != nullptr) { vtype_t str1 = {'\0'}; vtype_t str2 = {'\0'}; (void) strncpy(str1, to_string, str - to_string); str1[str - to_string] = '\0'; (void) strcpy(str2, str + from_len); if (number >= 0 && show_sign) { (void) sprintf(to_string, "%s+%d%s", str1, number, str2); } else { (void) sprintf(to_string, "%s%d%s", str1, number, str2); } } } // Inserts a string into a string void insertStringIntoString(char *to_string, const char *from_string, const char *str_to_insert) { auto from_len = (int) strlen(from_string); auto to_len = (int) strlen(to_string); char *bound = to_string + to_len - from_len; char *pc = nullptr; for (pc = to_string; pc <= bound; pc++) { char *temp_obj = pc; const char *temp_mtc = from_string; int i; for (i = 0; i < from_len; i++) { if (*temp_obj++ != *temp_mtc++) { break; } } if (i == from_len) { break; } } if (pc <= bound) { char new_string[MORIA_MESSAGE_SIZE]; (void) strncpy(new_string, to_string, (pc - to_string)); new_string[pc - to_string] = '\0'; if (str_to_insert != nullptr) { (void) strcat(new_string, str_to_insert); } (void) strcat(new_string, (pc + from_len)); (void) strcpy(to_string, new_string); } } bool isVowel(char ch) { switch (ch) { case 'a': case 'e': case 'i': case 'o': case 'u': case 'A': case 'E': case 'I': case 'O': case 'U': return true; default: return false; } } // http://rus.har.mn/blog/2014-05-19/strtol-error-checking/ bool stringToNumber(const char *str, int &number) { // we need to reset `errno` errno = 0; char *endptr = nullptr; long num = strtol(str, &endptr, 10); if (errno == ERANGE) { switch (num) { case LONG_MIN: // underflow break; case LONG_MAX: // overflow break; default: // impossible assert(false); } return false; } // something else happened. die die die if (errno != 0) { return false; } // garbage at end of string if (*endptr != '\0') { return false; } number = (int) num; return true; } uint32_t getCurrentUnixTime() { return static_cast(time(nullptr)); } void humanDateString(char *day) { time_t now = time(nullptr); struct tm *datetime = localtime(&now); #ifdef _WIN32 strftime(day, 10, "%a %b %d", datetime); #else strftime(day, 10, "%a %b %e", datetime); #endif } umoria-5.7.10+20181022/src/headers.h0000644000175000017500000000301213363422757015330 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. // System specific headers // clang-format off #pragma once #ifdef _WIN32 #define _CRT_SECURE_NO_WARNINGS #define _CRT_NONSTDC_NO_DEPRECATE #define WIN32_LEAN_AND_MEAN #include #include #include #elif __APPLE__ || __linux__ #include #include #include #else # error "Unknown compiler" #endif // Headers we can use on all supported systems! #include #include #include #include #include #include #include #include #include #include #include #include // General Umoria headers #include "config.h" #include "types.h" #include "character.h" #include "dice.h" #include "ui.h" // before dungeon.h #include "game.h" // before dungeon.h #include "dungeon_tile.h" #include "dungeon.h" #include "helpers.h" #include "inventory.h" // before identification.h #include "identification.h" #include "mage_spells.h" #include "monster.h" #include "player.h" #include "recall.h" #include "rng.h" #include "scores.h" #include "scrolls.h" #include "spells.h" #include "staves.h" #include "store.h" #include "treasure.h" #include "wizard.h" umoria-5.7.10+20181022/src/staves.cpp0000644000175000017500000003575313363422757015576 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. #include "headers.h" enum class StaffSpellTypes { light = 1, detect_doors_stairs, trap_location, treasure_location, object_location, teleportation, earthquakes, summoning, // skipping 9 destruction = 10, starlight, haste_monsters, slow_monsters, sleep_monsters, cure_light_wounds, detect_invisible, speed, slowness, mass_polymorph, remove_curse, detect_evil, curing, dispel_evil, // skipping 24 darkness = 25, // skipping lots... store_bought_flag = 32, }; static bool staffPlayerIsCarrying(int &item_pos_start, int &item_pos_end) { if (py.unique_inventory_items == 0) { printMessage("But you are not carrying anything."); return false; } if (!inventoryFindRange(TV_STAFF, TV_NEVER, item_pos_start, item_pos_end)) { printMessage("You are not carrying any staffs."); return false; } return true; } static bool staffPlayerCanUse(Inventory_t &item) { int chance = py.misc.saving_throw; chance += playerStatAdjustmentWisdomIntelligence(py_attrs::A_INT); chance -= item.depth_first_found - 5; chance += class_level_adj[py.misc.class_id][py_class_level_adj::CLASS_DEVICE] * py.misc.level / 3; if (py.flags.confused > 0) { chance = chance / 2; } // Give everyone a slight chance if (chance < config::player::PLAYER_USE_DEVICE_DIFFICULTY && randomNumber(config::player::PLAYER_USE_DEVICE_DIFFICULTY - chance + 1) == 1) { chance = config::player::PLAYER_USE_DEVICE_DIFFICULTY; } if (chance < 1) { chance = 1; } if (randomNumber(chance) < config::player::PLAYER_USE_DEVICE_DIFFICULTY) { printMessage("You failed to use the staff properly."); return false; } if (item.misc_use < 1) { printMessage("The staff has no charges left."); if (!spellItemIdentified(item)) { itemAppendToInscription(item, config::identification::ID_EMPTY); } return false; } return true; } static bool staffDischarge(Inventory_t &item) { bool identified = false; item.misc_use--; uint32_t flags = item.flags; while (flags != 0) { switch ((StaffSpellTypes) (getAndClearFirstBit(flags) + 1)) { case StaffSpellTypes::light: identified = spellLightArea(py.row, py.col); break; case StaffSpellTypes::detect_doors_stairs: identified = spellDetectSecretDoorssWithinVicinity(); break; case StaffSpellTypes::trap_location: identified = spellDetectTrapsWithinVicinity(); break; case StaffSpellTypes::treasure_location: identified = spellDetectTreasureWithinVicinity(); break; case StaffSpellTypes::object_location: identified = spellDetectObjectsWithinVicinity(); break; case StaffSpellTypes::teleportation: playerTeleport(100); identified = true; break; case StaffSpellTypes::earthquakes: identified = true; spellEarthquake(); break; case StaffSpellTypes::summoning: identified = false; for (int i = 0; i < randomNumber(4); i++) { int y = py.row; int x = py.col; identified |= monsterSummon(y, x, false); } break; case StaffSpellTypes::destruction: identified = true; spellDestroyArea(py.row, py.col); break; case StaffSpellTypes::starlight: identified = true; spellStarlite(py.row, py.col); break; case StaffSpellTypes::haste_monsters: identified = spellSpeedAllMonsters(1); break; case StaffSpellTypes::slow_monsters: identified = spellSpeedAllMonsters(-1); break; case StaffSpellTypes::sleep_monsters: identified = spellSleepAllMonsters(); break; case StaffSpellTypes::cure_light_wounds: identified = spellChangePlayerHitPoints(randomNumber(8)); break; case StaffSpellTypes::detect_invisible: identified = spellDetectInvisibleCreaturesWithinVicinity(); break; case StaffSpellTypes::speed: if (py.flags.fast == 0) { identified = true; } py.flags.fast += randomNumber(30) + 15; break; case StaffSpellTypes::slowness: if (py.flags.slow == 0) { identified = true; } py.flags.slow += randomNumber(30) + 15; break; case StaffSpellTypes::mass_polymorph: identified = spellMassPolymorph(); break; case StaffSpellTypes::remove_curse: if (spellRemoveCurseFromAllItems()) { if (py.flags.blind < 1) { printMessage("The staff glows blue for a moment.."); } identified = true; } break; case StaffSpellTypes::detect_evil: identified = spellDetectEvil(); break; case StaffSpellTypes::curing: if (playerCureBlindness() || playerCurePoison() || playerCureConfusion()) { identified = true; } break; case StaffSpellTypes::dispel_evil: identified = spellDispelCreature(config::monsters::defense::CD_EVIL, 60); break; case StaffSpellTypes::darkness: identified = spellDarkenArea(py.row, py.col); break; case StaffSpellTypes::store_bought_flag: // store bought flag break; default: printMessage("Internal error in staffs()"); break; } } return identified; } // Use a staff. -RAK- void staffUse() { game.player_free_turn = true; int item_pos_start, item_pos_end; if (!staffPlayerIsCarrying(item_pos_start, item_pos_end)) { return; } int item_id; if (!inventoryGetInputForItemId(item_id, "Use which staff?", item_pos_start, item_pos_end, CNIL, CNIL)) { return; } // From here on player uses up a turn game.player_free_turn = false; Inventory_t &item = inventory[item_id]; if (!staffPlayerCanUse(item)) { return; } bool identified = staffDischarge(item); if (identified) { if (!itemSetColorlessAsIdentified(item.category_id, item.sub_category_id, item.identification)) { // round half-way case up py.misc.exp += (item.depth_first_found + (py.misc.level >> 1)) / py.misc.level; displayCharacterExperience(); itemIdentify(inventory[item_id], item_id); } } else if (!itemSetColorlessAsIdentified(item.category_id, item.sub_category_id, item.identification)) { itemSetAsTried(item); } itemChargesRemainingDescription(item_id); } enum class WandSpellTypes { light = 1, lightning_bolt, frost_bolt, fire_bolt, stone_to_mud, polymorph, heal_monster, haste_monster, slow_monster, confuse_monster, sleep_monster, drain_life, trap_door_destruction, magic_missile, wall_building, clone_monster, teleport_away, disarming, lightning_ball, cold_ball, fire_ball, stinking_cloud, acid_ball, wonder, }; static bool wandDischarge(Inventory_t &item, int direction) { // decrement "use" variable item.misc_use--; bool identified = false; uint32_t flags = item.flags; while (flags != 0) { int y = py.row; int x = py.col; // Wand types switch ((WandSpellTypes) (getAndClearFirstBit(flags) + 1)) { case WandSpellTypes::light: printMessage("A line of blue shimmering light appears."); spellLightLine(py.col, py.row, direction); identified = true; break; case WandSpellTypes::lightning_bolt: spellFireBolt(y, x, direction, diceRoll(Dice_t{4, 8}), magic_spell_flags::GF_LIGHTNING, spell_names[8]); identified = true; break; case WandSpellTypes::frost_bolt: spellFireBolt(y, x, direction, diceRoll(Dice_t{6, 8}), magic_spell_flags::GF_FROST, spell_names[14]); identified = true; break; case WandSpellTypes::fire_bolt: spellFireBolt(y, x, direction, diceRoll(Dice_t{9, 8}), magic_spell_flags::GF_FIRE, spell_names[22]); identified = true; break; case WandSpellTypes::stone_to_mud: identified = spellWallToMud(y, x, direction); break; case WandSpellTypes::polymorph: identified = spellPolymorphMonster(y, x, direction); break; case WandSpellTypes::heal_monster: identified = spellChangeMonsterHitPoints(y, x, direction, -diceRoll(Dice_t{4, 6})); break; case WandSpellTypes::haste_monster: identified = spellSpeedMonster(y, x, direction, 1); break; case WandSpellTypes::slow_monster: identified = spellSpeedMonster(y, x, direction, -1); break; case WandSpellTypes::confuse_monster: identified = spellConfuseMonster(y, x, direction); break; case WandSpellTypes::sleep_monster: identified = spellSleepMonster(y, x, direction); break; case WandSpellTypes::drain_life: identified = spellDrainLifeFromMonster(y, x, direction); break; case WandSpellTypes::trap_door_destruction: identified = spellDestroyDoorsTrapsInDirection(y, x, direction); break; case WandSpellTypes::magic_missile: spellFireBolt(y, x, direction, diceRoll(Dice_t{2, 6}), magic_spell_flags::GF_MAGIC_MISSILE, spell_names[0]); identified = true; break; case WandSpellTypes::wall_building: identified = spellBuildWall(y, x, direction); break; case WandSpellTypes::clone_monster: identified = spellCloneMonster(y, x, direction); break; case WandSpellTypes::teleport_away: identified = spellTeleportAwayMonsterInDirection(y, x, direction); break; case WandSpellTypes::disarming: identified = spellDisarmAllInDirection(y, x, direction); break; case WandSpellTypes::lightning_ball: spellFireBall(y, x, direction, 32, magic_spell_flags::GF_LIGHTNING, "Lightning Ball"); identified = true; break; case WandSpellTypes::cold_ball: spellFireBall(y, x, direction, 48, magic_spell_flags::GF_FROST, "Cold Ball"); identified = true; break; case WandSpellTypes::fire_ball: spellFireBall(y, x, direction, 72, magic_spell_flags::GF_FIRE, spell_names[28]); identified = true; break; case WandSpellTypes::stinking_cloud: spellFireBall(y, x, direction, 12, magic_spell_flags::GF_POISON_GAS, spell_names[6]); identified = true; break; case WandSpellTypes::acid_ball: spellFireBall(y, x, direction, 60, magic_spell_flags::GF_ACID, "Acid Ball"); identified = true; break; case WandSpellTypes::wonder: flags = (uint32_t) (1L << (randomNumber(23) - 1)); break; default: printMessage("Internal error in wands()"); break; } } return identified; } // Wands for the aiming. void wandAim() { game.player_free_turn = true; if (py.unique_inventory_items == 0) { printMessage("But you are not carrying anything."); return; } int item_pos_start, item_pos_end; if (!inventoryFindRange(TV_WAND, TV_NEVER, item_pos_start, item_pos_end)) { printMessage("You are not carrying any wands."); return; } int item_id; if (!inventoryGetInputForItemId(item_id, "Aim which wand?", item_pos_start, item_pos_end, CNIL, CNIL)) { return; } game.player_free_turn = false; int direction; if (!getDirectionWithMemory(CNIL, direction)) { return; } if (py.flags.confused > 0) { printMessage("You are confused."); direction = getRandomDirection(); } Inventory_t &item = inventory[item_id]; int player_class_lev_adj = class_level_adj[py.misc.class_id][py_class_level_adj::CLASS_DEVICE] * py.misc.level / 3; int chance = py.misc.saving_throw + playerStatAdjustmentWisdomIntelligence(py_attrs::A_INT) - (int) item.depth_first_found + player_class_lev_adj; if (py.flags.confused > 0) { chance = chance / 2; } if (chance < config::player::PLAYER_USE_DEVICE_DIFFICULTY && randomNumber(config::player::PLAYER_USE_DEVICE_DIFFICULTY - chance + 1) == 1) { chance = config::player::PLAYER_USE_DEVICE_DIFFICULTY; // Give everyone a slight chance } if (chance <= 0) { chance = 1; } if (randomNumber(chance) < config::player::PLAYER_USE_DEVICE_DIFFICULTY) { printMessage("You failed to use the wand properly."); return; } if (item.misc_use < 1) { printMessage("The wand has no charges left."); if (!spellItemIdentified(item)) { itemAppendToInscription(item, config::identification::ID_EMPTY); } return; } bool identified = wandDischarge(item, direction); if (identified) { if (!itemSetColorlessAsIdentified(item.category_id, item.sub_category_id, item.identification)) { // round half-way case up py.misc.exp += (item.depth_first_found + (py.misc.level >> 1)) / py.misc.level; displayCharacterExperience(); itemIdentify(inventory[item_id], item_id); } } else if (!itemSetColorlessAsIdentified(item.category_id, item.sub_category_id, item.identification)) { itemSetAsTried(item); } itemChargesRemainingDescription(item_id); } umoria-5.7.10+20181022/src/dungeon.h0000644000175000017500000000670513363422757015370 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. #pragma once // Dungeon size parameters constexpr uint8_t MAX_HEIGHT = 66; // Multiple of 11; >= 22 constexpr uint8_t MAX_WIDTH = 198; // Multiple of 33; >= 66 constexpr uint8_t SCREEN_HEIGHT = 22; constexpr uint8_t SCREEN_WIDTH = 66; constexpr uint8_t QUART_HEIGHT = (SCREEN_HEIGHT / 4); constexpr uint8_t QUART_WIDTH = (SCREEN_WIDTH / 4); // DungeonObject_t is a base data object. // This holds data for any non-living object in the game such as // stairs, rubble, doors, gold, potions, weapons, wands, etc. typedef struct { const char *name; // Object name uint32_t flags; // Special flags uint8_t category_id; // Category number (tval) uint8_t sprite; // Character representation - ASCII symbol (tchar) int16_t misc_use; // Misc. use variable (p1) int32_t cost; // Cost of item uint8_t sub_category_id; // Sub-category number (subval) uint8_t items_count; // Number of items uint16_t weight; // Weight int16_t to_hit; // Plusses to hit int16_t to_damage; // Plusses to damage int16_t ac; // Normal AC int16_t to_ac; // Plusses to AC Dice_t damage; // Damage when hits uint8_t depth_first_found; // Dungeon level item first found } DungeonObject_t; typedef struct { // Dungeon size is either just big enough for town level, or the whole dungeon itself int16_t height; int16_t width; Panel_t panel; // Current turn of the game int32_t game_turn; // The current dungeon level int16_t current_level; // A `true` value means a new level will be generated on next loop iteration bool generate_new_level; // Floor definitions Tile_t floor[MAX_HEIGHT][MAX_WIDTH]; } Dungeon_t; extern Dungeon_t dg; extern DungeonObject_t game_objects[MAX_OBJECTS_IN_GAME]; void dungeonDisplayMap(); bool coordInBounds(Coord_t const &coord); int coordDistanceBetween(Coord_t const &from, Coord_t const &to); int coordWallsNextTo(Coord_t const &coord); int coordCorridorWallsNextTo(Coord_t const &coord); char caveGetTileSymbol(Coord_t const &coord); bool caveTileVisible(Coord_t const &coord); void dungeonSetTrap(Coord_t const &coord, int sub_type_id); void trapChangeVisibility(Coord_t const &coord); void dungeonPlaceRubble(Coord_t const &coord); void dungeonPlaceGold(Coord_t const &coord); void dungeonPlaceRandomObjectAt(Coord_t const &coord, bool must_be_small); void dungeonAllocateAndPlaceObject(bool (*set_function)(int), int object_type, int number); void dungeonPlaceRandomObjectNear(Coord_t coord, int tries); void dungeonMoveCreatureRecord(Coord_t const &from, Coord_t const &to); void dungeonLightRoom(Coord_t const &coord); void dungeonLiteSpot(Coord_t const &coord); void dungeonMoveCharacterLight(Coord_t const &from, Coord_t const &to); void dungeonDeleteMonster(int id); void dungeonDeleteMonsterFix1(int id); void dungeonDeleteMonsterFix2(int id); int dungeonSummonObject(Coord_t coord, int amount, int object_type); bool dungeonDeleteObject(Coord_t const &coord); // generate the dungeon void generateCave(); // Line of Sight bool los(int from_y, int from_x, int to_y, int to_x); void look(); umoria-5.7.10+20181022/src/version.h0000644000175000017500000000106213363422757015405 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. // Current version number of Umoria // CMake will extract this information, if you rename these variables // then you must also update the CMakeLists.txt. constexpr uint8_t CURRENT_VERSION_MAJOR = 5; constexpr uint8_t CURRENT_VERSION_MINOR = 7; constexpr uint8_t CURRENT_VERSION_PATCH = 10; umoria-5.7.10+20181022/src/player_magic.cpp0000644000175000017500000000736613363422757016724 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. // Player magic functions #include "headers.h" // Cure players confusion -RAK- bool playerCureConfusion() { if (py.flags.confused > 1) { py.flags.confused = 1; return true; } return false; } // Cure players blindness -RAK- bool playerCureBlindness() { if (py.flags.blind > 1) { py.flags.blind = 1; return true; } return false; } // Cure poisoning -RAK- bool playerCurePoison() { if (py.flags.poisoned > 1) { py.flags.poisoned = 1; return true; } return false; } // Cure the players fear -RAK- bool playerRemoveFear() { if (py.flags.afraid > 1) { py.flags.afraid = 1; return true; } return false; } // Evil creatures don't like this. -RAK- bool playerProtectEvil() { bool is_protected = py.flags.protect_evil == 0; py.flags.protect_evil += randomNumber(25) + 3 * py.misc.level; return is_protected; } // Bless -RAK- void playerBless(int adjustment) { py.flags.blessed += adjustment; } // Detect Invisible for period of time -RAK- void playerDetectInvisible(int adjustment) { py.flags.detect_invisible += adjustment; } // Special damage due to magical abilities of object -RAK- int itemMagicAbilityDamage(Inventory_t const &item, int total_damage, int monster_id) { bool is_ego_weapon = (item.flags & config::treasure::flags::TR_EGO_WEAPON) != 0; bool is_projectile = item.category_id >= TV_SLING_AMMO && item.category_id <= TV_ARROW; bool is_hafted_sword = item.category_id >= TV_HAFTED && item.category_id <= TV_SWORD; bool is_flask = item.category_id == TV_FLASK; if (is_ego_weapon && (is_projectile || is_hafted_sword || is_flask)) { Creature_t const &creature = creatures_list[monster_id]; Recall_t &memory = creature_recall[monster_id]; // Slay Dragon if (((creature.defenses & config::monsters::defense::CD_DRAGON) != 0) && ((item.flags & config::treasure::flags::TR_SLAY_DRAGON) != 0u)) { memory.defenses |= config::monsters::defense::CD_DRAGON; return total_damage * 4; } // Slay Undead if (((creature.defenses & config::monsters::defense::CD_UNDEAD) != 0) && ((item.flags & config::treasure::flags::TR_SLAY_UNDEAD) != 0u)) { memory.defenses |= config::monsters::defense::CD_UNDEAD; return total_damage * 3; } // Slay Animal if (((creature.defenses & config::monsters::defense::CD_ANIMAL) != 0) && ((item.flags & config::treasure::flags::TR_SLAY_ANIMAL) != 0u)) { memory.defenses |= config::monsters::defense::CD_ANIMAL; return total_damage * 2; } // Slay Evil if (((creature.defenses & config::monsters::defense::CD_EVIL) != 0) && ((item.flags & config::treasure::flags::TR_SLAY_EVIL) != 0u)) { memory.defenses |= config::monsters::defense::CD_EVIL; return total_damage * 2; } // Frost if (((creature.defenses & config::monsters::defense::CD_FROST) != 0) && ((item.flags & config::treasure::flags::TR_FROST_BRAND) != 0u)) { memory.defenses |= config::monsters::defense::CD_FROST; return total_damage * 3 / 2; } // Fire if (((creature.defenses & config::monsters::defense::CD_FIRE) != 0) && ((item.flags & config::treasure::flags::TR_FLAME_TONGUE) != 0u)) { memory.defenses |= config::monsters::defense::CD_FIRE; return total_damage * 3 / 2; } } return total_damage; } umoria-5.7.10+20181022/src/data_stores.cpp0000644000175000017500000000414413363422757016567 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. // Store data // clang-format off #include "headers.h" // Buying and selling adjustments for character race VS store owner race uint8_t race_gold_adjustments[PLAYER_MAX_RACES][PLAYER_MAX_RACES] = { //Hum, HfE, Elf, Hal, Gno, Dwa, HfO, HfT { 100, 105, 105, 110, 113, 115, 120, 125 }, // Human { 110, 100, 100, 105, 110, 120, 125, 130 }, // Half-Elf { 110, 105, 100, 105, 110, 120, 125, 130 }, // Elf { 115, 110, 105, 95, 105, 110, 115, 130 }, // Halfling { 115, 115, 110, 105, 95, 110, 115, 130 }, // Gnome { 115, 120, 120, 110, 110, 95, 125, 135 }, // Dwarf { 115, 120, 125, 115, 115, 130, 110, 115 }, // Half-Orc { 110, 115, 115, 110, 110, 130, 110, 110 }, // Half-Troll }; // game_objects[] index of objects that may appear in the store uint16_t store_choices[MAX_STORES][STORE_MAX_ITEM_TYPES] = { // General Store { 366, 365, 364, 84, 84, 365, 123, 366, 365, 350, 349, 348, 347, 346, 346, 345, 345, 345, 344, 344, 344, 344, 344, 344, 344, 344 }, // Armory { 94, 95, 96, 109, 103, 104, 105, 106, 110, 111, 112, 114, 116, 124, 125, 126, 127, 129, 103, 104, 124, 125, 91, 92, 95, 96 }, // Weaponsmith { 29, 30, 34, 37, 45, 49, 57, 58, 59, 65, 67, 68, 73, 74, 75, 77, 79, 80, 81, 83, 29, 30, 80, 83, 80, 83 }, // Temple { 322, 323, 324, 325, 180, 180, 233, 237, 240, 241, 361, 362, 57, 58, 59, 260, 358, 359, 265, 237, 237, 240, 240, 241, 323, 359 }, // Alchemy shop { 173, 174, 175, 351, 351, 352, 353, 354, 355, 356, 357, 206, 227, 230, 236, 252, 253, 352, 353, 354, 355, 356, 359, 363, 359, 359 }, // Magic-User store { 318, 141, 142, 153, 164, 167, 168, 140, 319, 320, 320, 321, 269, 270, 282, 286, 287, 292, 293, 294, 295, 308, 269, 290, 319, 282 }, }; umoria-5.7.10+20181022/src/curses.h0000644000175000017500000000074713363422757015235 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. // Sets up curses for the correct system. // clang-format off #ifdef _WIN32 // this is defined in Windows and also in ncurses #undef KEY_EVENT #include #else #include #endif umoria-5.7.10+20181022/src/main.cpp0000644000175000017500000000713113363422757015202 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. // Initialization, main() function and main loop #include "headers.h" #include "version.h" static bool parseGameSeed(const char *argv, uint32_t &seed); static const char *usage_instructions = R"( Usage: umoria [OPTIONS] SAVEGAME SAVEGAME is an optional save game filename (default: game.sav) Options: -n Force start of new game -r Use classic roguelike keys: hjkl -d Display high scores and exit -s NUMBER Game Seed, as a decimal number (max: 2147483647) -v Print version info and exit -h Display this message )"; // Initialize, restore, and get the ball rolling. -RAK- int main(int argc, char *argv[]) { uint32_t seed = 0; bool new_game = false; bool roguelike_keys = false; // call this routine to grab a file pointer to the high score file // and prepare things to relinquish setuid privileges if (!initializeScoreFile()) { std::cerr << "Can't open score file '" << config::files::scores << "'\n"; return 1; } // Make sure we have access to all files -MRC- if (!checkFilePermissions()) { return 1; } if (!terminalInitialize()) { return 1; } // check for user interface option for (--argc, ++argv; argc > 0 && argv[0][0] == '-'; --argc, ++argv) { switch (argv[0][1]) { case 'v': terminalRestore(); printf("%d.%d.%d\n", CURRENT_VERSION_MAJOR, CURRENT_VERSION_MINOR, CURRENT_VERSION_PATCH); return 0; case 'n': new_game = true; break; case 'r': // This will force the use of roguelike keys, // ignoring the saved game file. roguelike_keys = true; break; case 'd': showScoresScreen(); exitProgram(); break; case 's': // No NUMBER provided? if (argv[1] == nullptr) { break; } // Move onto the NUMBER value --argc; ++argv; if (!parseGameSeed(argv[0], seed)) { terminalRestore(); printf("Game seed must be a decimal number between 1 and 2147483647\n"); return -1; } break; case 'w': game.to_be_wizard = true; break; default: terminalRestore(); printf("Robert A. Koeneke's classic dungeon crawler.\n"); printf("Umoria %d.%d.%d is released under a GPL v2 license.\n", CURRENT_VERSION_MAJOR, CURRENT_VERSION_MINOR, CURRENT_VERSION_PATCH); printf("%s", usage_instructions); return 0; } } // Auto-restart of saved file if (argv[0] != CNIL) { // (void) strcpy(config::files::save_game, argv[0]); config::files::save_game = argv[0]; } startMoria(seed, new_game, roguelike_keys); return 0; } static bool parseGameSeed(const char *argv, uint32_t &seed) { int value; if (!stringToNumber(argv, value)) { return false; } if (value <= 0 || value > MAX_LONG) { return false; } seed = (uint32_t) value; return true; } umoria-5.7.10+20181022/src/game_death.cpp0000644000175000017500000001077013363422757016337 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. // Code executed when player dies #include "headers.h" // Prints the gravestone of the character -RAK- static void printTomb() { displayDeathFile(config::files::death_tomb); std::string text; text = std::string(py.misc.name); putString(text.c_str(), Coord_t{6, (int) (26 - text.length() / 2)}); if (!game.total_winner) { text = playerRankTitle(); } else { text = "Magnificent"; } putString(text.c_str(), Coord_t{8, (int) (26 - text.length() / 2)}); if (!game.total_winner) { text = classes[py.misc.class_id].title; } else if (playerIsMale()) { text = "*King*"; } else { text = "*Queen*"; } putString(text.c_str(), Coord_t{10, (int) (26 - text.length() / 2)}); text = std::to_string(py.misc.level); putString(text.c_str(), Coord_t{11, 30}); text = std::to_string(py.misc.exp) + " Exp"; putString(text.c_str(), Coord_t{12, (int) (26 - text.length() / 2)}); text = std::to_string(py.misc.au) + " Au"; putString(text.c_str(), Coord_t{13, (int) (26 - text.length() / 2)}); text = std::to_string(dg.current_level); putString(text.c_str(), Coord_t{14, 34}); text = std::string(game.character_died_from); putString(text.c_str(), Coord_t{16, (int) (26 - text.length() / 2)}); char day[11]; humanDateString(day); text = std::string(day); putString(text.c_str(), Coord_t{17, (int) (26 - text.length() / 2)}); retry: flushInputBuffer(); putString("(ESC to abort, return to print on screen, or file name)", Coord_t{23, 0}); putString("Character record?", Coord_t{22, 0}); vtype_t str = {'\0'}; if (getStringInput(str, Coord_t{22, 18}, 60)) { for (auto &item : inventory) { itemSetAsIdentified(item.category_id, item.sub_category_id); spellItemIdentifyAndRemoveRandomInscription(item); } playerRecalculateBonuses(); if (str[0] != 0) { if (!outputPlayerCharacterToFile(str)) { goto retry; } } else { clearScreen(); printCharacter(); putString("Type ESC to skip the inventory:", Coord_t{23, 0}); if (getKeyInput() != ESCAPE) { clearScreen(); printMessage("You are using:"); (void) displayEquipment(true, 0); printMessage(CNIL); printMessage("You are carrying:"); clearToBottom(1); (void) displayInventory(0, py.unique_inventory_items - 1, true, 0, CNIL); printMessage(CNIL); } } } } // Let the player know they did good. static void printCrown() { displayDeathFile(config::files::death_royal); if (playerIsMale()) { putString("King!", Coord_t{17, 45}); } else { putString("Queen!", Coord_t{17, 45}); } flushInputBuffer(); waitForContinueKey(23); } // Change the player into a King! -RAK- static void kingly() { // Change the character attributes. dg.current_level = 0; (void) strcpy(game.character_died_from, "Ripe Old Age"); (void) spellRestorePlayerLevels(); py.misc.level += PLAYER_MAX_LEVEL; py.misc.au += 250000L; py.misc.max_exp += 5000000L; py.misc.exp = py.misc.max_exp; printCrown(); } // What happens upon dying -RAK- // Handles the gravestone and top-twenty routines -RAK- void endGame() { printMessage(CNIL); // flush all input flushInputBuffer(); // If the game has been saved, then save sets turn back to -1, // which inhibits the printing of the tomb. if (dg.game_turn >= 0) { if (game.total_winner) { kingly(); } printTomb(); } // Save the memory at least. if (game.character_generated && !game.character_saved) { (void) saveGame(); } // add score to score file if applicable if (game.character_generated) { // Clear `game.character_saved`, strange thing to do, but it prevents // getKeyInput() from recursively calling endGame() when there has // been an eof on stdin detected. game.character_saved = false; recordNewHighScore(); showScoresScreen(); } eraseLine(Coord_t{23, 0}); exitProgram(); } umoria-5.7.10+20181022/src/store.h0000644000175000017500000000511013363422757015052 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. #pragma once #include constexpr uint8_t MAX_OWNERS = 18; // Number of owners to choose from constexpr uint8_t MAX_STORES = 6; // Number of different stores constexpr uint8_t STORE_MAX_DISCRETE_ITEMS = 24; // Max number of discrete objects in inventory constexpr uint8_t STORE_MAX_ITEM_TYPES = 26; // Number of items to choose stock from constexpr uint8_t COST_ADJUSTMENT = 100; // Adjust prices for buying and selling // InventoryRecord_t data for a store inventory item typedef struct { int32_t cost; Inventory_t item; } InventoryRecord_t; // Store_t holds all the data for any given store in the game typedef struct { int32_t turns_left_before_closing; int16_t insults_counter; uint8_t owner_id; uint8_t unique_items_counter; uint16_t good_purchases; uint16_t bad_purchases; InventoryRecord_t inventory[STORE_MAX_DISCRETE_ITEMS]; } Store_t; // Owner_t holds data about a given store owner typedef struct { const char *name; int16_t max_cost; uint8_t max_inflate; uint8_t min_inflate; uint8_t haggles_per; uint8_t race; uint8_t max_insults; } Owner_t; extern uint8_t race_gold_adjustments[PLAYER_MAX_RACES][PLAYER_MAX_RACES]; extern Owner_t store_owners[MAX_OWNERS]; extern Store_t stores[MAX_STORES]; extern uint16_t store_choices[MAX_STORES][STORE_MAX_ITEM_TYPES]; extern bool (*store_buy[MAX_STORES])(int); extern const char *speech_sale_accepted[14]; extern const char *speech_selling_haggle_final[3]; extern const char *speech_selling_haggle[16]; extern const char *speech_buying_haggle_final[3]; extern const char *speech_buying_haggle[15]; extern const char *speech_insulted_haggling_done[5]; extern const char *speech_get_out_of_my_store[5]; extern const char *speech_haggling_try_again[10]; extern const char *speech_sorry[5]; // store void storeInitializeOwners(); void storeEnter(int store_id); // store_inventory void storeMaintenance(); int32_t storeItemValue(Inventory_t const &item); int32_t storeItemSellPrice(Store_t const &store, int32_t &min_price, int32_t &max_price, Inventory_t const &item); bool storeCheckPlayerItemsCount(Store_t const &store, Inventory_t const &item); void storeCarryItem(int store_id, int &index_id, Inventory_t &item); void storeDestroyItem(int store_id, int item_id, bool only_one_of); umoria-5.7.10+20181022/src/recall.h0000644000175000017500000000211313363422757015160 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. #pragma once // Recall_t holds the player's known knowledge for any given monster, aka memories typedef struct { uint32_t movement; uint32_t spells; uint16_t kills; uint16_t deaths; uint16_t defenses; uint8_t wake; uint8_t ignore; uint8_t attacks[MON_MAX_ATTACKS]; } Recall_t; extern Recall_t creature_recall[MON_MAX_CREATURES]; // Monster memories. -CJS- extern const char *recall_description_attack_type[25]; extern const char *recall_description_attack_method[20]; extern const char *recall_description_how_much[8]; extern const char *recall_description_move[6]; extern const char *recall_description_spell[15]; extern const char *recall_description_breath[5]; extern const char *recall_description_weakness[6]; int memoryRecall(int monster_id); void recallMonsterAttributes(char command); umoria-5.7.10+20181022/src/monster.h0000644000175000017500000000714613363422757015420 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. #pragma once // Monster_t is created for any living monster found on the current dungeon level typedef struct { int16_t hp; // Hit points int16_t sleep_count; // Inactive counter int16_t speed; // Movement speed uint16_t creature_id; // Pointer into creature // Note: fy, fx, and cdis constrain dungeon size to less than 256 by 256 uint8_t y; // Y Pointer into map uint8_t x; // X Pointer into map uint8_t distance_from_player; // Current distance from player bool lit; uint8_t stunned_amount; uint8_t confused_amount; } Monster_t; // Creature_t is a base data object. // Holds the base game data for any given creature in the game such // as: Kobold, Orc, Giant Red Ant, Quasit, Young Black Dragon, etc. typedef struct { const char *name; // Description of creature uint32_t movement; // Bit field uint32_t spells; // Creature spells uint16_t defenses; // Bit field uint16_t kill_exp_value; // Exp value for kill uint8_t sleep_counter; // Inactive counter / 10 uint8_t area_affect_radius; // Area affect radius uint8_t ac; // AC uint8_t speed; // Movement speed+10 (NOTE: +10 so that it can be an unsigned int) uint8_t sprite; // Character representation (cchar) Dice_t hit_die; // Creatures hit die uint8_t damage[4]; // Type attack and damage uint8_t level; // Level of creature } Creature_t; // MonsterAttack_t is a base data object. // Holds the data for a monster's attack and damage type typedef struct { uint8_t type_id; uint8_t description_id; Dice_t dice; } MonsterAttack_t; // Creature constants constexpr uint16_t MON_MAX_CREATURES = 279; // Number of creatures defined for univ constexpr uint8_t MON_ATTACK_TYPES = 215; // Number of monster attack types. // With MON_TOTAL_ALLOCATIONS set to 101, it is possible to get compacting // monsters messages while breeding/cloning monsters. constexpr uint8_t MON_TOTAL_ALLOCATIONS = 125; // Max that can be allocated constexpr uint8_t MON_MAX_LEVELS = 40; // Maximum level of creatures constexpr uint8_t MON_MAX_ATTACKS = 4; // Max num attacks (used in mons memory) -CJS- extern int hack_monptr; extern Creature_t creatures_list[MON_MAX_CREATURES]; extern Monster_t monsters[MON_TOTAL_ALLOCATIONS]; extern int16_t monster_levels[MON_MAX_LEVELS + 1]; extern MonsterAttack_t monster_attacks[MON_ATTACK_TYPES]; extern Monster_t blank_monster; extern int16_t next_free_monster_id; extern int16_t monster_multiply_total; void monsterUpdateVisibility(int monster_id); bool monsterMultiply(int y, int x, int creatureID, int monsterID); void updateMonsters(bool attack); uint32_t monsterDeath(int y, int x, uint32_t flags); int monsterTakeHit(int monster_id, int damage); void printMonsterActionText(const std::string &name, const std::string &action); std::string monsterNameDescription(const std::string &real_name, bool is_lit); bool monsterSleep(int y, int x); // monster management bool compactMonsters(); bool monsterPlaceNew(int y, int x, int creature_id, bool sleeping); void monsterPlaceWinning(); void monsterPlaceNewWithinDistance(int number, int distance_from_source, bool sleeping); bool monsterSummon(int &y, int &x, bool sleeping); bool monsterSummonUndead(int &y, int &x); umoria-5.7.10+20181022/src/inventory.cpp0000644000175000017500000004531413363422757016320 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. // Inventory and inventory items #include "headers.h" Inventory_t inventory[PLAYER_INVENTORY_SIZE]; uint32_t inventoryCollectAllItemFlags() { uint32_t flags = 0; for (int i = player_equipment::EQUIPMENT_WIELD; i < player_equipment::EQUIPMENT_LIGHT; i++) { flags |= inventory[i].flags; } return flags; } // Destroy an item in the inventory -RAK- void inventoryDestroyItem(int item_id) { Inventory_t &item = inventory[item_id]; if (item.items_count > 1 && item.sub_category_id <= ITEM_SINGLE_STACK_MAX) { item.items_count--; py.inventory_weight -= item.weight; } else { py.inventory_weight -= item.weight * item.items_count; for (int i = item_id; i < py.unique_inventory_items - 1; i++) { inventory[i] = inventory[i + 1]; } inventoryItemCopyTo(config::dungeon::objects::OBJ_NOTHING, inventory[py.unique_inventory_items - 1]); py.unique_inventory_items--; } py.flags.status |= config::player::status::PY_STR_WGT; } // Copies the object in the second argument over the first argument. // However, the second always gets a number of one except for ammo etc. void inventoryTakeOneItem(Inventory_t *to_item, Inventory_t *from_item) { *to_item = *from_item; if (to_item->items_count > 1 && to_item->sub_category_id >= ITEM_SINGLE_STACK_MIN && to_item->sub_category_id <= ITEM_SINGLE_STACK_MAX) { to_item->items_count = 1; } } // Drops an item from inventory to given location -RAK- void inventoryDropItem(int item_id, bool drop_all) { if (dg.floor[py.row][py.col].treasure_id != 0) { (void) dungeonDeleteObject(Coord_t{py.row, py.col});; } int treasureID = popt(); Inventory_t &item = inventory[item_id]; treasure_list[treasureID] = item; dg.floor[py.row][py.col].treasure_id = (uint8_t) treasureID; if (item_id >= player_equipment::EQUIPMENT_WIELD) { playerTakeOff(item_id, -1); } else { if (drop_all || item.items_count == 1) { py.inventory_weight -= item.weight * item.items_count; py.unique_inventory_items--; while (item_id < py.unique_inventory_items) { inventory[item_id] = inventory[item_id + 1]; item_id++; } inventoryItemCopyTo(config::dungeon::objects::OBJ_NOTHING, inventory[py.unique_inventory_items]); } else { treasure_list[treasureID].items_count = 1; py.inventory_weight -= item.weight; item.items_count--; } obj_desc_t prt1 = {'\0'}; obj_desc_t prt2 = {'\0'}; itemDescription(prt1, treasure_list[treasureID], true); (void) sprintf(prt2, "Dropped %s", prt1); printMessage(prt2); } py.flags.status |= config::player::status::PY_STR_WGT; } // Destroys a type of item on a given percent chance -RAK- static int inventoryDamageItem(bool (*item_type)(Inventory_t *), int chance_percentage) { int damage = 0; for (int i = 0; i < py.unique_inventory_items; i++) { if ((*item_type)(&inventory[i]) && randomNumber(100) < chance_percentage) { inventoryDestroyItem(i); damage++; } } return damage; } bool inventoryDiminishLightAttack(bool noticed) { Inventory_t &item = inventory[player_equipment::EQUIPMENT_LIGHT]; if (item.misc_use > 0) { item.misc_use -= (250 + randomNumber(250)); if (item.misc_use < 1) { item.misc_use = 1; } if (py.flags.blind < 1) { printMessage("Your light dims."); } else { noticed = false; } } else { noticed = false; } return noticed; } bool inventoryDiminishChargesAttack(uint8_t creature_level, int16_t &monster_hp, bool noticed) { Inventory_t &item = inventory[randomNumber(py.unique_inventory_items) - 1]; bool has_charges = item.category_id == TV_STAFF || item.category_id == TV_WAND; if (has_charges && item.misc_use > 0) { monster_hp += creature_level * item.misc_use; item.misc_use = 0; if (!spellItemIdentified(item)) { itemAppendToInscription(item, config::identification::ID_EMPTY); } printMessage("Energy drains from your pack!"); } else { noticed = false; } return noticed; } bool executeDisenchantAttack() { int item_id; switch (randomNumber(7)) { case 1: item_id = player_equipment::EQUIPMENT_WIELD; break; case 2: item_id = player_equipment::EQUIPMENT_BODY; break; case 3: item_id = player_equipment::EQUIPMENT_ARM; break; case 4: item_id = player_equipment::EQUIPMENT_OUTER; break; case 5: item_id = player_equipment::EQUIPMENT_HANDS; break; case 6: item_id = player_equipment::EQUIPMENT_HEAD; break; case 7: item_id = player_equipment::EQUIPMENT_FEET; break; default: return false; } bool success = false; Inventory_t &item = inventory[item_id]; if (item.to_hit > 0) { item.to_hit -= randomNumber(2); // don't send it below zero if (item.to_hit < 0) { item.to_hit = 0; } success = true; } if (item.to_damage > 0) { item.to_damage -= randomNumber(2); // don't send it below zero if (item.to_damage < 0) { item.to_damage = 0; } success = true; } if (item.to_ac > 0) { item.to_ac -= randomNumber(2); // don't send it below zero if (item.to_ac < 0) { item.to_ac = 0; } success = true; } return success; } // this code must be identical to the inventoryCarryItem() code below bool inventoryCanCarryItemCount(Inventory_t const &item) { if (py.unique_inventory_items < player_equipment::EQUIPMENT_WIELD) { return true; } if (item.sub_category_id < ITEM_SINGLE_STACK_MIN) { return false; } for (int i = 0; i < py.unique_inventory_items; i++) { bool same_character = inventory[i].category_id == item.category_id; bool same_category = inventory[i].sub_category_id == item.sub_category_id; // make sure the number field doesn't overflow bool same_number = inventory[i].items_count + item.items_count < 256; // they always stack (sub_category_id < 192), or else they have same `misc_use` bool same_group = item.sub_category_id < ITEM_GROUP_MIN || inventory[i].misc_use == item.misc_use; // only stack if both or neither are identified // TODO(cook): is it correct that they should be equal to each other, regardless of true/false value? bool inventory_item_is_colorless = itemSetColorlessAsIdentified(inventory[i].category_id, inventory[i].sub_category_id, inventory[i].identification); bool item_is_colorless = itemSetColorlessAsIdentified(item.category_id, item.sub_category_id, item.identification); bool identification = inventory_item_is_colorless == item_is_colorless; if (same_character && same_category && same_number && same_group && identification) { return true; } } return false; } // return false if picking up an object would change the players speed bool inventoryCanCarryItem(Inventory_t const &item) { int limit = playerCarryingLoadLimit(); int newWeight = item.items_count * item.weight + py.inventory_weight; if (limit < newWeight) { limit = newWeight / (limit + 1); } else { limit = 0; } return py.pack_heaviness == limit; } // Add an item to players inventory. Return the // item position for a description if needed. -RAK- // this code must be identical to the inventoryCanCarryItemCount() code above int inventoryCarryItem(Inventory_t &new_item) { bool is_known = itemSetColorlessAsIdentified(new_item.category_id, new_item.sub_category_id, new_item.identification); bool is_always_known = objectPositionOffset(new_item.category_id, new_item.sub_category_id) == -1; int slot_id; // Now, check to see if player can carry object for (slot_id = 0; slot_id < PLAYER_INVENTORY_SIZE; slot_id++) { Inventory_t &item = inventory[slot_id]; bool is_same_category = new_item.category_id == item.category_id && new_item.sub_category_id == item.sub_category_id; bool not_too_many_items = int(item.items_count + new_item.items_count) < 256; // only stack if both or neither are identified bool same_known_status = itemSetColorlessAsIdentified(item.category_id, item.sub_category_id, item.identification) == is_known; if (is_same_category && new_item.sub_category_id >= ITEM_SINGLE_STACK_MIN && not_too_many_items && (new_item.sub_category_id < ITEM_GROUP_MIN || item.misc_use == new_item.misc_use) && same_known_status) { item.items_count += new_item.items_count; break; } if ((is_same_category && is_always_known) || new_item.category_id > item.category_id) { // For items which are always `is_known`, i.e. never have a 'color', // insert them into the inventory in sorted order. for (int i = py.unique_inventory_items - 1; i >= slot_id; i--) { inventory[i + 1] = inventory[i]; } inventory[slot_id] = new_item; py.unique_inventory_items++; break; } } py.inventory_weight += new_item.items_count * new_item.weight; py.flags.status |= config::player::status::PY_STR_WGT; return slot_id; } // Finds range of item in inventory list -RAK- bool inventoryFindRange(int item_id_start, int item_id_end, int &j, int &k) { j = -1; k = -1; bool at_end_of_range = false; for (int i = 0; i < py.unique_inventory_items; i++) { auto item_id = (int) inventory[i].category_id; if (!at_end_of_range) { if (item_id == item_id_start || item_id == item_id_end) { at_end_of_range = true; j = i; } } else { if (item_id != item_id_start && item_id != item_id_end) { k = i - 1; break; } } } if (at_end_of_range && k == -1) { k = py.unique_inventory_items - 1; } return at_end_of_range; } void inventoryItemCopyTo(int from_item_id, Inventory_t &to_item) { DungeonObject_t const &from = game_objects[from_item_id]; to_item.id = (uint16_t) from_item_id; to_item.special_name_id = special_name_ids::SN_NULL; to_item.inscription[0] = '\0'; to_item.flags = from.flags; to_item.category_id = from.category_id; to_item.sprite = from.sprite; to_item.misc_use = from.misc_use; to_item.cost = from.cost; to_item.sub_category_id = from.sub_category_id; to_item.items_count = from.items_count; to_item.weight = from.weight; to_item.to_hit = from.to_hit; to_item.to_damage = from.to_damage; to_item.ac = from.ac; to_item.to_ac = from.to_ac; to_item.damage.dice = from.damage.dice; to_item.damage.sides = from.damage.sides; to_item.depth_first_found = from.depth_first_found; to_item.identification = 0; } // AC gets worse -RAK- // Note: This routine affects magical AC bonuses so // that stores can detect the damage. static bool damageMinusAC(uint32_t typ_dam) { int itemsCount = 0; int items[6]; if (inventory[player_equipment::EQUIPMENT_BODY].category_id != TV_NOTHING) { items[itemsCount] = player_equipment::EQUIPMENT_BODY; itemsCount++; } if (inventory[player_equipment::EQUIPMENT_ARM].category_id != TV_NOTHING) { items[itemsCount] = player_equipment::EQUIPMENT_ARM; itemsCount++; } if (inventory[player_equipment::EQUIPMENT_OUTER].category_id != TV_NOTHING) { items[itemsCount] = player_equipment::EQUIPMENT_OUTER; itemsCount++; } if (inventory[player_equipment::EQUIPMENT_HANDS].category_id != TV_NOTHING) { items[itemsCount] = player_equipment::EQUIPMENT_HANDS; itemsCount++; } if (inventory[player_equipment::EQUIPMENT_HEAD].category_id != TV_NOTHING) { items[itemsCount] = player_equipment::EQUIPMENT_HEAD; itemsCount++; } // also affect boots if (inventory[player_equipment::EQUIPMENT_FEET].category_id != TV_NOTHING) { items[itemsCount] = player_equipment::EQUIPMENT_FEET; itemsCount++; } bool minus = false; if (itemsCount == 0) { return minus; } int itemID = items[randomNumber(itemsCount) - 1]; obj_desc_t description = {'\0'}; obj_desc_t msg = {'\0'}; if ((inventory[itemID].flags & typ_dam) != 0u) { minus = true; itemDescription(description, inventory[itemID], false); (void) sprintf(msg, "Your %s resists damage!", description); printMessage(msg); } else if (inventory[itemID].ac + inventory[itemID].to_ac > 0) { minus = true; itemDescription(description, inventory[itemID], false); (void) sprintf(msg, "Your %s is damaged!", description); printMessage(msg); inventory[itemID].to_ac--; playerRecalculateBonuses(); } return minus; } // Functions to emulate the original Pascal sets bool setNull(Inventory_t *item) { (void) item; // silence warnings return false; } static bool setCorrodableItems(Inventory_t *item) { switch (item->category_id) { case TV_SWORD: case TV_HELM: case TV_SHIELD: case TV_HARD_ARMOR: case TV_WAND: return true; default: return false; } } static bool setFlammableItems(Inventory_t *item) { switch (item->category_id) { case TV_ARROW: case TV_BOW: case TV_HAFTED: case TV_POLEARM: case TV_BOOTS: case TV_GLOVES: case TV_CLOAK: case TV_SOFT_ARMOR: // Items of (RF) should not be destroyed. return (item->flags & config::treasure::flags::TR_RES_FIRE) == 0; case TV_STAFF: case TV_SCROLL1: case TV_SCROLL2: return true; default: return false; } } static bool setAcidAffectedItems(Inventory_t *item) { switch (item->category_id) { case TV_MISC: case TV_CHEST: return true; case TV_BOLT: case TV_ARROW: case TV_BOW: case TV_HAFTED: case TV_POLEARM: case TV_BOOTS: case TV_GLOVES: case TV_CLOAK: case TV_SOFT_ARMOR: return (item->flags & config::treasure::flags::TR_RES_ACID) == 0; default: return false; } } bool setFrostDestroyableItems(Inventory_t *item) { return (item->category_id == TV_POTION1 || item->category_id == TV_POTION2 || item->category_id == TV_FLASK); } bool setLightningDestroyableItems(Inventory_t *item) { return (item->category_id == TV_RING || item->category_id == TV_WAND || item->category_id == TV_SPIKE); } bool setAcidDestroyableItems(Inventory_t *item) { switch (item->category_id) { case TV_ARROW: case TV_BOW: case TV_HAFTED: case TV_POLEARM: case TV_BOOTS: case TV_GLOVES: case TV_CLOAK: case TV_HELM: case TV_SHIELD: case TV_HARD_ARMOR: case TV_SOFT_ARMOR: return (item->flags & config::treasure::flags::TR_RES_ACID) == 0; case TV_STAFF: case TV_SCROLL1: case TV_SCROLL2: case TV_FOOD: case TV_OPEN_DOOR: case TV_CLOSED_DOOR: return true; default: return false; } } bool setFireDestroyableItems(Inventory_t *item) { switch (item->category_id) { case TV_ARROW: case TV_BOW: case TV_HAFTED: case TV_POLEARM: case TV_BOOTS: case TV_GLOVES: case TV_CLOAK: case TV_SOFT_ARMOR: return (item->flags & config::treasure::flags::TR_RES_FIRE) == 0; case TV_STAFF: case TV_SCROLL1: case TV_SCROLL2: case TV_POTION1: case TV_POTION2: case TV_FLASK: case TV_FOOD: case TV_OPEN_DOOR: case TV_CLOSED_DOOR: return true; default: return false; } } // Corrode the unsuspecting person's armor -RAK- void damageCorrodingGas(const char *creature_name) { if (!damageMinusAC((uint32_t) config::treasure::flags::TR_RES_ACID)) { playerTakesHit(randomNumber(8), creature_name); } if (inventoryDamageItem(setCorrodableItems, 5) > 0) { printMessage("There is an acrid smell coming from your pack."); } } // Poison gas the idiot. -RAK- void damagePoisonedGas(int damage, const char *creature_name) { playerTakesHit(damage, creature_name); py.flags.poisoned += 12 + randomNumber(damage); } // Burn the fool up. -RAK- void damageFire(int damage, const char *creature_name) { if (py.flags.resistant_to_fire) { damage = damage / 3; } if (py.flags.heat_resistance > 0) { damage = damage / 3; } playerTakesHit(damage, creature_name); if (inventoryDamageItem(setFlammableItems, 3) > 0) { printMessage("There is smoke coming from your pack!"); } } // Freeze them to death. -RAK- void damageCold(int damage, const char *creature_name) { if (py.flags.resistant_to_cold) { damage = damage / 3; } if (py.flags.cold_resistance > 0) { damage = damage / 3; } playerTakesHit(damage, creature_name); if (inventoryDamageItem(setFrostDestroyableItems, 5) > 0) { printMessage("Something shatters inside your pack!"); } } // Lightning bolt the sucker away. -RAK- void damageLightningBolt(int damage, const char *creature_name) { if (py.flags.resistant_to_light) { damage = damage / 3; } playerTakesHit(damage, creature_name); if (inventoryDamageItem(setLightningDestroyableItems, 3) > 0) { printMessage("There are sparks coming from your pack!"); } } // Throw acid on the hapless victim -RAK- void damageAcid(int damage, const char *creature_name) { int flag = 0; if (damageMinusAC((uint32_t) config::treasure::flags::TR_RES_ACID)) { flag = 1; } if (py.flags.resistant_to_acid) { flag += 2; } playerTakesHit(damage / (flag + 1), creature_name); if (inventoryDamageItem(setAcidAffectedItems, 3) > 0) { printMessage("There is an acrid smell coming from your pack!"); } } umoria-5.7.10+20181022/src/player_bash.cpp0000644000175000017500000001722013363422757016547 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. // The running algorithm: -CJS- #include "headers.h" #include "dice.h" static void playerBashAttack(int y, int x); static void playerBashPosition(int y, int x); static void playerBashClosedDoor(int y, int x, int dir, Tile_t &tile, Inventory_t &item); static void playerBashClosedChest(Inventory_t &item); // Bash open a door or chest -RAK- // Note: Affected by strength and weight of character // // For a closed door, `misc_use` is positive if locked; negative if stuck. A disarm spell // unlocks and unjams doors! // // For an open door, `misc_use` is positive for a broken door. // // A closed door can be opened - harder if locked. Any door might be bashed open // (and thereby broken). Bashing a door is (potentially) faster! You move into the // door way. To open a stuck door, it must be bashed. A closed door can be jammed // (which makes it stuck if previously locked). // // Creatures can also open doors. A creature with open door ability will (if not // in the line of sight) move though a closed or secret door with no changes. If // in the line of sight, closed door are opened, & secret door revealed. Whether // in the line of sight or not, such a creature may unlock or unstick a door. // // A creature with no such ability will attempt to bash a non-secret door. void playerBash() { int dir; if (!getDirectionWithMemory(CNIL, dir)) { return; } if (py.flags.confused > 0) { printMessage("You are confused."); dir = getRandomDirection(); } int y = py.row; int x = py.col; (void) playerMovePosition(dir, y, x); Tile_t &tile = dg.floor[y][x]; if (tile.creature_id > 1) { playerBashPosition(y, x); return; } if (tile.treasure_id != 0) { Inventory_t &item = treasure_list[tile.treasure_id]; if (item.category_id == TV_CLOSED_DOOR) { playerBashClosedDoor(y, x, dir, tile, item); } else if (item.category_id == TV_CHEST) { playerBashClosedChest(item); } else { // Can't give free turn, or else player could try // directions until they find the invisible creature printMessage("You bash it, but nothing interesting happens."); } return; } if (tile.feature_id < MIN_CAVE_WALL) { printMessage("You bash at empty space."); return; } // same message for wall as for secret door printMessage("You bash it, but nothing interesting happens."); } // Make a bash attack on someone. -CJS- // Used to be part of bash above. static void playerBashAttack(int y, int x) { int monster_id = dg.floor[y][x].creature_id; Monster_t &monster = monsters[monster_id]; Creature_t const &creature = creatures_list[monster.creature_id]; monster.sleep_count = 0; // Does the player know what they're fighting? vtype_t name = {'\0'}; if (!monster.lit) { (void) strcpy(name, "it"); } else { (void) sprintf(name, "the %s", creature.name); } int base_to_hit = py.stats.used[py_attrs::A_STR]; base_to_hit += inventory[player_equipment::EQUIPMENT_ARM].weight / 2; base_to_hit += py.misc.weight / 10; if (!monster.lit) { base_to_hit /= 2; base_to_hit -= py.stats.used[py_attrs::A_DEX] * (BTH_PER_PLUS_TO_HIT_ADJUST - 1); base_to_hit -= py.misc.level * class_level_adj[py.misc.class_id][py_class_level_adj::CLASS_BTH] / 2; } if (playerTestBeingHit(base_to_hit, (int) py.misc.level, (int) py.stats.used[py_attrs::A_DEX], (int) creature.ac, py_class_level_adj::CLASS_BTH)) { vtype_t msg = {'\0'}; (void) sprintf(msg, "You hit %s.", name); printMessage(msg); int damage = diceRoll(inventory[player_equipment::EQUIPMENT_ARM].damage); damage = playerWeaponCriticalBlow(inventory[player_equipment::EQUIPMENT_ARM].weight / 4 + py.stats.used[py_attrs::A_STR], 0, damage, py_class_level_adj::CLASS_BTH); damage += py.misc.weight / 60; damage += 3; if (damage < 0) { damage = 0; } // See if we done it in. if (monsterTakeHit(monster_id, damage) >= 0) { (void) sprintf(msg, "You have slain %s.", name); printMessage(msg); displayCharacterExperience(); } else { name[0] = (char) toupper((int) name[0]); // Capitalize // Can not stun Balrog int avg_max_hp; if ((creature.defenses & config::monsters::defense::CD_MAX_HP) != 0) { avg_max_hp = maxDiceRoll(creature.hit_die); } else { // TODO: use maxDiceRoll(), just be careful about the bit shift avg_max_hp = (creature.hit_die.dice * (creature.hit_die.sides + 1)) >> 1; } if (100 + randomNumber(400) + randomNumber(400) > monster.hp + avg_max_hp) { monster.stunned_amount += randomNumber(3) + 1; if (monster.stunned_amount > 24) { monster.stunned_amount = 24; } (void) sprintf(msg, "%s appears stunned!", name); } else { (void) sprintf(msg, "%s ignores your bash!", name); } printMessage(msg); } } else { vtype_t msg = {'\0'}; (void) sprintf(msg, "You miss %s.", name); printMessage(msg); } if (randomNumber(150) > py.stats.used[py_attrs::A_DEX]) { printMessage("You are off balance."); py.flags.paralysis = (int16_t) (1 + randomNumber(2)); } } static void playerBashPosition(int y, int x) { // Is a Coward? if (py.flags.afraid > 0) { printMessage("You are afraid!"); return; } playerBashAttack(y, x); } static void playerBashClosedDoor(int y, int x, int dir, Tile_t &tile, Inventory_t &item) { printMessageNoCommandInterrupt("You smash into the door!"); int chance = py.stats.used[py_attrs::A_STR] + py.misc.weight / 2; // Use (roughly) similar method as for monsters. auto abs_misc_use = (int) std::abs((std::intmax_t) item.misc_use); if (randomNumber(chance * (20 + abs_misc_use)) < 10 * (chance - abs_misc_use)) { printMessage("The door crashes open!"); inventoryItemCopyTo(config::dungeon::objects::OBJ_OPEN_DOOR, treasure_list[tile.treasure_id]); // 50% chance of breaking door item.misc_use = (int16_t) (1 - randomNumber(2)); tile.feature_id = TILE_CORR_FLOOR; if (py.flags.confused == 0) { playerMove(dir, false); } else { dungeonLiteSpot(Coord_t{y, x}); } return; } if (randomNumber(150) > py.stats.used[py_attrs::A_DEX]) { printMessage("You are off-balance."); py.flags.paralysis = (int16_t) (1 + randomNumber(2)); return; } if (game.command_count == 0) { printMessage("The door holds firm."); } } static void playerBashClosedChest(Inventory_t &item) { if (randomNumber(10) == 1) { printMessage("You have destroyed the chest."); printMessage("and its contents!"); item.id = config::dungeon::objects::OBJ_RUINED_CHEST; item.flags = 0; return; } if (((item.flags & config::treasure::chests::CH_LOCKED) != 0u) && randomNumber(10) == 1) { printMessage("The lock breaks open!"); item.flags &= ~config::treasure::chests::CH_LOCKED; return; } printMessageNoCommandInterrupt("The chest holds firm."); } umoria-5.7.10+20181022/src/game_save.cpp0000644000175000017500000012164513363422757016214 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. // Save and restore games and monster memory info #include "headers.h" #include "version.h" #include // For debugging the save file code on systems with broken compilers. #define DEBUG(x) DEBUG(static FILE *logfile) static bool _save_char(const std::string &filename); static bool sv_write(); static void wr_bool(bool value); static void wr_byte(uint8_t value); static void wr_short(uint16_t value); static void wr_long(uint32_t value); static void wr_bytes(uint8_t *value, int count); static void wr_string(char *str); static void wr_shorts(uint16_t *value, int count); static void wr_item(Inventory_t &item); static void wr_monster(Monster_t const &monster); static uint8_t get_byte(); static bool rd_bool(); static uint8_t rd_byte(); static uint16_t rd_short(); static uint32_t rd_long(); static void rd_bytes(uint8_t *value, int count); static void rd_string(char *str); static void rd_shorts(uint16_t *value, int count); static void rd_item(Inventory_t &item); static void rd_monster(Monster_t &monster); // these are used for the save file, to avoid having to pass them to every procedure static FILE *fileptr; static uint8_t xor_byte; static int from_savefile; // can overwrite old save file when save static uint32_t start_time; // time that play started // This save package was brought to by -JWT- // and -RAK- // and has been completely rewritten for UNIX by -JEW- // and has been completely rewritten again by -CJS- // and completely rewritten again! for portability by -JEW- static bool sv_write() { // clear the game.character_is_dead flag when creating a HANGUP save file, // so that player can see tombstone when restart if (eof_flag != 0) { game.character_is_dead = false; } uint32_t l = 0; if (config::options::run_cut_corners) { l |= 0x1; } if (config::options::run_examine_corners) { l |= 0x2; } if (config::options::run_print_self) { l |= 0x4; } if (config::options::find_bound) { l |= 0x8; } if (config::options::prompt_to_pickup) { l |= 0x10; } if (config::options::use_roguelike_keys) { l |= 0x20; } if (config::options::show_inventory_weights) { l |= 0x40; } if (config::options::highlight_seams) { l |= 0x80; } if (config::options::run_ignore_doors) { l |= 0x100; } if (config::options::error_beep_sound) { l |= 0x200; } if (config::options::display_counts) { l |= 0x400; } if (game.character_is_dead) { // Sign bit l |= 0x80000000L; } if (game.total_winner) { l |= 0x40000000L; } for (int i = 0; i < MON_MAX_CREATURES; i++) { Recall_t &r = creature_recall[i]; if (r.movement || r.defenses || r.kills || r.spells || r.deaths || r.attacks[0] || r.attacks[1] || r.attacks[2] || r.attacks[3]) { wr_short((uint16_t) i); wr_long(r.movement); wr_long(r.spells); wr_short(r.kills); wr_short(r.deaths); wr_short(r.defenses); wr_byte(r.wake); wr_byte(r.ignore); wr_bytes(r.attacks, MON_MAX_ATTACKS); } } // sentinel to indicate no more monster info wr_short((uint16_t) 0xFFFF); wr_long(l); wr_string(py.misc.name); wr_bool(py.misc.gender); wr_long((uint32_t) py.misc.au); wr_long((uint32_t) py.misc.max_exp); wr_long((uint32_t) py.misc.exp); wr_short(py.misc.exp_fraction); wr_short(py.misc.age); wr_short(py.misc.height); wr_short(py.misc.weight); wr_short(py.misc.level); wr_short(py.misc.max_dungeon_depth); wr_short((uint16_t) py.misc.chance_in_search); wr_short((uint16_t) py.misc.fos); wr_short((uint16_t) py.misc.bth); wr_short((uint16_t) py.misc.bth_with_bows); wr_short((uint16_t) py.misc.mana); wr_short((uint16_t) py.misc.max_hp); wr_short((uint16_t) py.misc.plusses_to_hit); wr_short((uint16_t) py.misc.plusses_to_damage); wr_short((uint16_t) py.misc.ac); wr_short((uint16_t) py.misc.magical_ac); wr_short((uint16_t) py.misc.display_to_hit); wr_short((uint16_t) py.misc.display_to_damage); wr_short((uint16_t) py.misc.display_ac); wr_short((uint16_t) py.misc.display_to_ac); wr_short((uint16_t) py.misc.disarm); wr_short((uint16_t) py.misc.saving_throw); wr_short((uint16_t) py.misc.social_class); wr_short((uint16_t) py.misc.stealth_factor); wr_byte(py.misc.class_id); wr_byte(py.misc.race_id); wr_byte(py.misc.hit_die); wr_byte(py.misc.experience_factor); wr_short((uint16_t) py.misc.current_mana); wr_short(py.misc.current_mana_fraction); wr_short((uint16_t) py.misc.current_hp); wr_short(py.misc.current_hp_fraction); for (auto &entry : py.misc.history) { wr_string(entry); } wr_bytes(py.stats.max, 6); wr_bytes(py.stats.current, 6); wr_shorts((uint16_t *) py.stats.modified, 6); wr_bytes(py.stats.used, 6); wr_long(py.flags.status); wr_short((uint16_t) py.flags.rest); wr_short((uint16_t) py.flags.blind); wr_short((uint16_t) py.flags.paralysis); wr_short((uint16_t) py.flags.confused); wr_short((uint16_t) py.flags.food); wr_short((uint16_t) py.flags.food_digested); wr_short((uint16_t) py.flags.protection); wr_short((uint16_t) py.flags.speed); wr_short((uint16_t) py.flags.fast); wr_short((uint16_t) py.flags.slow); wr_short((uint16_t) py.flags.afraid); wr_short((uint16_t) py.flags.poisoned); wr_short((uint16_t) py.flags.image); wr_short((uint16_t) py.flags.protect_evil); wr_short((uint16_t) py.flags.invulnerability); wr_short((uint16_t) py.flags.heroism); wr_short((uint16_t) py.flags.super_heroism); wr_short((uint16_t) py.flags.blessed); wr_short((uint16_t) py.flags.heat_resistance); wr_short((uint16_t) py.flags.cold_resistance); wr_short((uint16_t) py.flags.detect_invisible); wr_short((uint16_t) py.flags.word_of_recall); wr_short((uint16_t) py.flags.see_infra); wr_short((uint16_t) py.flags.timed_infra); wr_bool(py.flags.see_invisible); wr_bool(py.flags.teleport); wr_bool(py.flags.free_action); wr_bool(py.flags.slow_digest); wr_bool(py.flags.aggravate); wr_bool(py.flags.resistant_to_fire); wr_bool(py.flags.resistant_to_cold); wr_bool(py.flags.resistant_to_acid); wr_bool(py.flags.regenerate_hp); wr_bool(py.flags.resistant_to_light); wr_bool(py.flags.free_fall); wr_bool(py.flags.sustain_str); wr_bool(py.flags.sustain_int); wr_bool(py.flags.sustain_wis); wr_bool(py.flags.sustain_con); wr_bool(py.flags.sustain_dex); wr_bool(py.flags.sustain_chr); wr_bool(py.flags.confuse_monster); wr_byte(py.flags.new_spells_to_learn); wr_short((uint16_t) missiles_counter); wr_long((uint32_t) dg.game_turn); wr_short((uint16_t) py.unique_inventory_items); for (int i = 0; i < py.unique_inventory_items; i++) { wr_item(inventory[i]); } for (int i = player_equipment::EQUIPMENT_WIELD; i < PLAYER_INVENTORY_SIZE; i++) { wr_item(inventory[i]); } wr_short((uint16_t) py.inventory_weight); wr_short((uint16_t) py.equipment_count); wr_long(py.flags.spells_learnt); wr_long(py.flags.spells_worked); wr_long(py.flags.spells_forgotten); wr_bytes(py.flags.spells_learned_order, 32); wr_bytes(objects_identified, OBJECT_IDENT_SIZE); wr_long(game.magic_seed); wr_long(game.town_seed); wr_short((uint16_t) last_message_id); for (auto &message : messages) { wr_string(message); } // this indicates 'cheating' if it is a one wr_short((uint16_t) panic_save); wr_short((uint16_t) game.total_winner); wr_short((uint16_t) game.noscore); wr_shorts(py.base_hp_levels, PLAYER_MAX_LEVEL); for (auto &store : stores) { wr_long((uint32_t) store.turns_left_before_closing); wr_short((uint16_t) store.insults_counter); wr_byte(store.owner_id); wr_byte(store.unique_items_counter); wr_short(store.good_purchases); wr_short(store.bad_purchases); for (int j = 0; j < store.unique_items_counter; j++) { wr_long((uint32_t) store.inventory[j].cost); wr_item(store.inventory[j].item); } } // save the current time in the save file l = getCurrentUnixTime(); if (l < start_time) { // someone is messing with the clock!, // assume that we have been playing for 1 day l = (uint32_t) (start_time + 86400L); } wr_long(l); // put game.character_died_from string in save file wr_string(game.character_died_from); // put the max_score in the save file l = (uint32_t) (playerCalculateTotalPoints()); wr_long(l); // put the date_of_birth in the save file wr_long((uint32_t) py.misc.date_of_birth); // only level specific info follows, this allows characters to be // resurrected, the dungeon level info is not needed for a resurrection if (game.character_is_dead) { return !((ferror(fileptr) != 0) || fflush(fileptr) == EOF); } wr_short((uint16_t) dg.current_level); wr_short((uint16_t) py.row); wr_short((uint16_t) py.col); wr_short((uint16_t) monster_multiply_total); wr_short((uint16_t) dg.height); wr_short((uint16_t) dg.width); wr_short((uint16_t) dg.panel.max_rows); wr_short((uint16_t) dg.panel.max_cols); for (int i = 0; i < MAX_HEIGHT; i++) { for (int j = 0; j < MAX_WIDTH; j++) { if (dg.floor[i][j].creature_id != 0) { wr_byte((uint8_t) i); wr_byte((uint8_t) j); wr_byte(dg.floor[i][j].creature_id); } } } // marks end of creature_id info wr_byte((uint8_t) 0xFF); for (int i = 0; i < MAX_HEIGHT; i++) { for (int j = 0; j < MAX_WIDTH; j++) { if (dg.floor[i][j].treasure_id != 0) { wr_byte((uint8_t) i); wr_byte((uint8_t) j); wr_byte(dg.floor[i][j].treasure_id); } } } // marks end of treasure_id info wr_byte((uint8_t) 0xFF); // must set counter to zero, note that code may write out two bytes unnecessarily int count = 0; uint8_t prev_char = 0; for (int y = 0; y < MAX_HEIGHT; y++) { for (int x = 0; x < MAX_WIDTH; x++) { Tile_t const &tile = dg.floor[y][x]; auto char_tmp = (uint8_t) (tile.feature_id | (tile.perma_lit_room << 4) | (tile.field_mark << 5) | (tile.permanent_light << 6) | (tile.temporary_light << 7)); if (char_tmp != prev_char || count == MAX_UCHAR) { wr_byte((uint8_t) count); wr_byte(prev_char); prev_char = char_tmp; count = 1; } else { count++; } } } // save last entry wr_byte((uint8_t) count); wr_byte(prev_char); wr_short((uint16_t) current_treasure_id); for (int i = config::treasure::MIN_TREASURE_LIST_ID; i < current_treasure_id; i++) { wr_item(treasure_list[i]); } wr_short((uint16_t) next_free_monster_id); for (int i = config::monsters::MON_MIN_INDEX_ID; i < next_free_monster_id; i++) { wr_monster(monsters[i]); } return !((ferror(fileptr) != 0) || fflush(fileptr) == EOF); } // Set up prior to actual save, do the save, then clean up bool saveGame() { vtype_t input = {'\0'}; std::string output; while (!_save_char(config::files::save_game)) { output = "Save file '" + config::files::save_game + "' fails."; printMessage(output.c_str()); int i = 0; if (access(config::files::save_game.c_str(), 0) < 0 || !getInputConfirmation("File exists. Delete old save file?") || (i = unlink(config::files::save_game.c_str())) < 0) { if (i < 0) { output = "Can't delete '" + config::files::save_game + "'"; printMessage(output.c_str()); } putStringClearToEOL("New Save file [ESC to give up]:", Coord_t{0, 0}); if (!getStringInput(input, Coord_t{0, 31}, 45)) { return false; } if (input[0] != 0) { // (void) strcpy(config::files::save_game, input); config::files::save_game = input; } } output = "Saving with '" + config::files::save_game + "'..."; putStringClearToEOL(output, Coord_t{0, 0}); } return true; } static bool _save_char(const std::string &filename) { if (game.character_saved) { return true; // Nothing to save. } putQIO(); playerDisturb(1, 0); // Turn off resting and searching. playerChangeSpeed(-py.pack_heaviness); // Fix the speed py.pack_heaviness = 0; bool ok = false; fileptr = nullptr; // Do not assume it has been init'ed int fd = open(filename.c_str(), O_RDWR | O_CREAT | O_EXCL, 0600); if (fd < 0 && access(filename.c_str(), 0) >= 0 && ((from_savefile != 0) || (game.wizard_mode && getInputConfirmation("Can't make new save file. Overwrite old?")))) { (void) chmod(filename.c_str(), 0600); fd = open(filename.c_str(), O_RDWR | O_TRUNC, 0600); } if (fd >= 0) { (void) close(fd); fileptr = fopen(config::files::save_game.c_str(), "wb"); } DEBUG(logfile = fopen("IO_LOG", "a")); DEBUG(fprintf(logfile, "Saving data to %s\n", config::files::save_game)); if (fileptr != nullptr) { xor_byte = 0; wr_byte(CURRENT_VERSION_MAJOR); xor_byte = 0; wr_byte(CURRENT_VERSION_MINOR); xor_byte = 0; wr_byte(CURRENT_VERSION_PATCH); xor_byte = 0; auto char_tmp = (uint8_t) (randomNumber(256) - 1); wr_byte(char_tmp); // Note that xor_byte is now equal to char_tmp ok = sv_write(); DEBUG(fclose(logfile)); if (fclose(fileptr) == EOF) { ok = false; } } if (!ok) { if (fd >= 0) { (void) unlink(filename.c_str()); } std::string output; if (fd >= 0) { output = "Error writing to file '" + filename + "'"; } else { output = "Can't create new file '" + filename + "'"; } printMessage(output.c_str()); return false; } game.character_saved = true; dg.game_turn = -1; return true; } // Certain checks are omitted for the wizard. -CJS- bool loadGame(bool &generate) { Tile_t *tile = nullptr; int c; uint32_t time_saved = 0; uint8_t version_maj = 0; uint8_t version_min = 0; uint8_t patch_level = 0; generate = true; int fd = -1; int total_count = 0; // Not required for Mac, because the file name is obtained through a dialog. // There is no way for a nonexistent file to be specified. -BS- if (access(config::files::save_game.c_str(), 0) != 0) { printMessage("Save file does not exist."); return false; // Don't bother with messages here. File absent. } clearScreen(); std::string filename = "Save file '" + config::files::save_game + "' present. Attempting restore."; putString(filename.c_str(), Coord_t{23, 0}); // FIXME: check this if/else logic! -- MRC if (dg.game_turn >= 0) { printMessage("IMPOSSIBLE! Attempt to restore while still alive!"); } else if ((fd = open(config::files::save_game.c_str(), O_RDONLY, 0)) < 0 && (chmod(config::files::save_game.c_str(), 0400) < 0 || (fd = open(config::files::save_game.c_str(), O_RDONLY, 0)) < 0)) { // Allow restoring a file belonging to someone else, if we can delete it. // Hence first try to read without doing a chmod. printMessage("Can't open file for reading."); } else { dg.game_turn = -1; bool ok = true; (void) close(fd); fd = -1; // Make sure it isn't closed again fileptr = fopen(config::files::save_game.c_str(), "rb"); if (fileptr == nullptr) { goto error; } putStringClearToEOL("Restoring Memory...", Coord_t{0, 0}); putQIO(); DEBUG(logfile = fopen("IO_LOG", "a")); DEBUG(fprintf(logfile, "Reading data from %s\n", config::files::save_game)); // Note: setting these xor_byte is correct! xor_byte = 0; version_maj = rd_byte(); xor_byte = 0; version_min = rd_byte(); xor_byte = 0; patch_level = rd_byte(); xor_byte = get_byte(); if (!validGameVersion(version_maj, version_min, patch_level)) { putStringClearToEOL("Sorry. This save file is from a different version of umoria.", Coord_t{2, 0}); goto error; } uint16_t uint16_t_tmp; uint32_t l; uint16_t_tmp = rd_short(); while (uint16_t_tmp != 0xFFFF) { if (uint16_t_tmp >= MON_MAX_CREATURES) { goto error; } Recall_t &memory = creature_recall[uint16_t_tmp]; memory.movement = rd_long(); memory.spells = rd_long(); memory.kills = rd_short(); memory.deaths = rd_short(); memory.defenses = rd_short(); memory.wake = rd_byte(); memory.ignore = rd_byte(); rd_bytes(memory.attacks, MON_MAX_ATTACKS); uint16_t_tmp = rd_short(); } l = rd_long(); config::options::run_cut_corners = (l & 0x1) != 0; config::options::run_examine_corners = (l & 0x2) != 0; config::options::run_print_self = (l & 0x4) != 0; config::options::find_bound = (l & 0x8) != 0; config::options::prompt_to_pickup = (l & 0x10) != 0; config::options::use_roguelike_keys = (l & 0x20) != 0; config::options::show_inventory_weights = (l & 0x40) != 0; config::options::highlight_seams = (l & 0x80) != 0; config::options::run_ignore_doors = (l & 0x100) != 0; config::options::error_beep_sound = (l & 0x200) != 0; config::options::display_counts = (l & 0x400) != 0; // Don't allow resurrection of game.total_winner characters. It causes // problems because the character level is out of the allowed range. if (game.to_be_wizard && ((l & 0x40000000L) != 0)) { printMessage("Sorry, this character is retired from moria."); printMessage("You can not resurrect a retired character."); } else if (game.to_be_wizard && ((l & 0x80000000L) != 0) && getInputConfirmation("Resurrect a dead character?")) { l &= ~0x80000000L; } if ((l & 0x80000000L) == 0) { rd_string(py.misc.name); py.misc.gender = rd_bool(); py.misc.au = rd_long(); py.misc.max_exp = rd_long(); py.misc.exp = rd_long(); py.misc.exp_fraction = rd_short(); py.misc.age = rd_short(); py.misc.height = rd_short(); py.misc.weight = rd_short(); py.misc.level = rd_short(); py.misc.max_dungeon_depth = rd_short(); py.misc.chance_in_search = rd_short(); py.misc.fos = rd_short(); py.misc.bth = rd_short(); py.misc.bth_with_bows = rd_short(); py.misc.mana = rd_short(); py.misc.max_hp = rd_short(); py.misc.plusses_to_hit = rd_short(); py.misc.plusses_to_damage = rd_short(); py.misc.ac = rd_short(); py.misc.magical_ac = rd_short(); py.misc.display_to_hit = rd_short(); py.misc.display_to_damage = rd_short(); py.misc.display_ac = rd_short(); py.misc.display_to_ac = rd_short(); py.misc.disarm = rd_short(); py.misc.saving_throw = rd_short(); py.misc.social_class = rd_short(); py.misc.stealth_factor = rd_short(); py.misc.class_id = rd_byte(); py.misc.race_id = rd_byte(); py.misc.hit_die = rd_byte(); py.misc.experience_factor = rd_byte(); py.misc.current_mana = rd_short(); py.misc.current_mana_fraction = rd_short(); py.misc.current_hp = rd_short(); py.misc.current_hp_fraction = rd_short(); for (auto &entry : py.misc.history) { rd_string(entry); } rd_bytes(py.stats.max, 6); rd_bytes(py.stats.current, 6); rd_shorts((uint16_t *) py.stats.modified, 6); rd_bytes(py.stats.used, 6); py.flags.status = rd_long(); py.flags.rest = rd_short(); py.flags.blind = rd_short(); py.flags.paralysis = rd_short(); py.flags.confused = rd_short(); py.flags.food = rd_short(); py.flags.food_digested = rd_short(); py.flags.protection = rd_short(); py.flags.speed = rd_short(); py.flags.fast = rd_short(); py.flags.slow = rd_short(); py.flags.afraid = rd_short(); py.flags.poisoned = rd_short(); py.flags.image = rd_short(); py.flags.protect_evil = rd_short(); py.flags.invulnerability = rd_short(); py.flags.heroism = rd_short(); py.flags.super_heroism = rd_short(); py.flags.blessed = rd_short(); py.flags.heat_resistance = rd_short(); py.flags.cold_resistance = rd_short(); py.flags.detect_invisible = rd_short(); py.flags.word_of_recall = rd_short(); py.flags.see_infra = rd_short(); py.flags.timed_infra = rd_short(); py.flags.see_invisible = rd_bool(); py.flags.teleport = rd_bool(); py.flags.free_action = rd_bool(); py.flags.slow_digest = rd_bool(); py.flags.aggravate = rd_bool(); py.flags.resistant_to_fire = rd_bool(); py.flags.resistant_to_cold = rd_bool(); py.flags.resistant_to_acid = rd_bool(); py.flags.regenerate_hp = rd_bool(); py.flags.resistant_to_light = rd_bool(); py.flags.free_fall = rd_bool(); py.flags.sustain_str = rd_bool(); py.flags.sustain_int = rd_bool(); py.flags.sustain_wis = rd_bool(); py.flags.sustain_con = rd_bool(); py.flags.sustain_dex = rd_bool(); py.flags.sustain_chr = rd_bool(); py.flags.confuse_monster = rd_bool(); py.flags.new_spells_to_learn = rd_byte(); missiles_counter = rd_short(); dg.game_turn = rd_long(); py.unique_inventory_items = rd_short(); if (py.unique_inventory_items > player_equipment::EQUIPMENT_WIELD) { goto error; } for (int i = 0; i < py.unique_inventory_items; i++) { rd_item(inventory[i]); } for (int i = player_equipment::EQUIPMENT_WIELD; i < PLAYER_INVENTORY_SIZE; i++) { rd_item(inventory[i]); } py.inventory_weight = rd_short(); py.equipment_count = rd_short(); py.flags.spells_learnt = rd_long(); py.flags.spells_worked = rd_long(); py.flags.spells_forgotten = rd_long(); rd_bytes(py.flags.spells_learned_order, 32); rd_bytes(objects_identified, OBJECT_IDENT_SIZE); game.magic_seed = rd_long(); game.town_seed = rd_long(); last_message_id = rd_short(); for (auto &message : messages) { rd_string(message); } uint16_t panic_save_short; uint16_t total_winner_short; panic_save_short = rd_short(); total_winner_short = rd_short(); panic_save = panic_save_short != 0; game.total_winner = total_winner_short != 0; game.noscore = rd_short(); rd_shorts(py.base_hp_levels, PLAYER_MAX_LEVEL); for (auto &store : stores) { store.turns_left_before_closing = rd_long(); store.insults_counter = rd_short(); store.owner_id = rd_byte(); store.unique_items_counter = rd_byte(); store.good_purchases = rd_short(); store.bad_purchases = rd_short(); if (store.unique_items_counter > STORE_MAX_DISCRETE_ITEMS) { goto error; } for (int j = 0; j < store.unique_items_counter; j++) { store.inventory[j].cost = rd_long(); rd_item(store.inventory[j].item); } } time_saved = rd_long(); rd_string(game.character_died_from); py.max_score = rd_long(); py.misc.date_of_birth = rd_long(); } c = getc(fileptr); if (c == EOF || ((l & 0x80000000L) != 0)) { if ((l & 0x80000000L) == 0) { if (!game.to_be_wizard || dg.game_turn < 0) { goto error; } putStringClearToEOL("Attempting a resurrection!", Coord_t{0, 0}); if (py.misc.current_hp < 0) { py.misc.current_hp = 0; py.misc.current_hp_fraction = 0; } // don't let them starve to death immediately if (py.flags.food < 0) { py.flags.food = 0; } // don't let them immediately die of poison again if (py.flags.poisoned > 1) { py.flags.poisoned = 1; } dg.current_level = 0; // Resurrect on the town level. game.character_generated = true; // set `noscore` to indicate a resurrection, and don't enter wizard mode game.to_be_wizard = false; game.noscore |= 0x1; } else { // Make sure that this message is seen, since it is a bit // more interesting than the other messages. printMessage("Restoring Memory of a departed spirit..."); dg.game_turn = -1; } putQIO(); goto closefiles; } if (ungetc(c, fileptr) == EOF) { goto error; } putStringClearToEOL("Restoring Character...", Coord_t{0, 0}); putQIO(); // only level specific info should follow, // not present for dead characters dg.current_level = rd_short(); py.row = rd_short(); py.col = rd_short(); monster_multiply_total = rd_short(); dg.height = rd_short(); dg.width = rd_short(); dg.panel.max_rows = rd_short(); dg.panel.max_cols = rd_short(); uint8_t char_tmp, ychar, xchar, count; // read in the creature ptr info char_tmp = rd_byte(); while (char_tmp != 0xFF) { ychar = char_tmp; xchar = rd_byte(); char_tmp = rd_byte(); if (xchar > MAX_WIDTH || ychar > MAX_HEIGHT) { goto error; } dg.floor[ychar][xchar].creature_id = char_tmp; char_tmp = rd_byte(); } // read in the treasure ptr info char_tmp = rd_byte(); while (char_tmp != 0xFF) { ychar = char_tmp; xchar = rd_byte(); char_tmp = rd_byte(); if (xchar > MAX_WIDTH || ychar > MAX_HEIGHT) { goto error; } dg.floor[ychar][xchar].treasure_id = char_tmp; char_tmp = rd_byte(); } // read in the rest of the cave info tile = &dg.floor[0][0]; total_count = 0; while (total_count != MAX_HEIGHT * MAX_WIDTH) { count = rd_byte(); char_tmp = rd_byte(); for (int i = count; i > 0; i--) { if (tile >= &dg.floor[MAX_HEIGHT][0]) { goto error; } tile->feature_id = (uint8_t) (char_tmp & 0xF); tile->perma_lit_room = (bool) ((char_tmp >> 4) & 0x1); tile->field_mark = (bool) ((char_tmp >> 5) & 0x1); tile->permanent_light = (bool) ((char_tmp >> 6) & 0x1); tile->temporary_light = (bool) ((char_tmp >> 7) & 0x1); tile++; } total_count += count; } current_treasure_id = rd_short(); if (current_treasure_id > LEVEL_MAX_OBJECTS) { goto error; } for (int i = config::treasure::MIN_TREASURE_LIST_ID; i < current_treasure_id; i++) { rd_item(treasure_list[i]); } next_free_monster_id = rd_short(); if (next_free_monster_id > MON_TOTAL_ALLOCATIONS) { goto error; } for (int i = config::monsters::MON_MIN_INDEX_ID; i < next_free_monster_id; i++) { rd_monster(monsters[i]); } generate = false; // We have restored a cave - no need to generate. if (ferror(fileptr) != 0) { goto error; } if (dg.game_turn < 0) { error: ok = false; // Assume bad data. } else { // don't overwrite the killed by string if character is dead if (py.misc.current_hp >= 0) { (void) strcpy(game.character_died_from, "(alive and well)"); } game.character_generated = true; } closefiles: DEBUG(fclose(logfile)); if (fileptr != nullptr) { if (fclose(fileptr) < 0) { ok = false; } } if (fd >= 0) { (void) close(fd); } if (!ok) { printMessage("Error during reading of file."); } else { // let the user overwrite the old save file when save/quit from_savefile = 1; if (panic_save) { printMessage("This game is from a panic save. Score will not be added to scoreboard."); } else if ((!game.noscore) & 0x04) { printMessage("This character is already on the scoreboard; it will not be scored again."); game.noscore |= 0x4; } if (dg.game_turn >= 0) { // Only if a full restoration. py.weapon_is_heavy = false; py.pack_heaviness = 0; playerStrength(); // rotate store inventory, depending on how old the save file // is foreach day old (rounded up), call storeMaintenance // calculate age in seconds start_time = getCurrentUnixTime(); uint32_t age; // check for reasonable values of time here ... if (start_time < time_saved) { age = 0; } else { age = start_time - time_saved; } age = (uint32_t) ((age + 43200L) / 86400L); // age in days if (age > 10) { age = 10; // in case save file is very old } for (int i = 0; i < (int) age; i++) { storeMaintenance(); } } if (game.noscore != 0) { printMessage("This save file cannot be used to get on the score board."); } if (validGameVersion(version_maj, version_min, patch_level) && !isCurrentGameVersion(version_maj, version_min, patch_level)) { std::string msg = "Save file version "; msg += std::to_string(version_maj) + "." + std::to_string(version_min); msg += " accepted on game version "; msg += std::to_string(CURRENT_VERSION_MAJOR) + "." + std::to_string(CURRENT_VERSION_MINOR) + "."; printMessage(msg.c_str()); } // if false: only restored options and monster memory. return dg.game_turn >= 0; } } dg.game_turn = -1; putStringClearToEOL("Please try again without that save file.", Coord_t{1, 0}); // TODO: just check for a key press instead of calling printMessage? // We have messages for the player to read, this will ask for a keypress printMessage(CNIL); exitProgram(); return false; // not reached } static void wr_bool(bool value) { wr_byte((uint8_t) value); } static void wr_byte(uint8_t value) { xor_byte ^= value; (void) putc((int) xor_byte, fileptr); DEBUG(fprintf(logfile, "BYTE: %02X = %d\n", (int) xor_byte, (int) value)); } static void wr_short(uint16_t value) { xor_byte ^= (value & 0xFF); (void) putc((int) xor_byte, fileptr); DEBUG(fprintf(logfile, "SHORT: %02X", (int) xor_byte)); xor_byte ^= ((value >> 8) & 0xFF); (void) putc((int) xor_byte, fileptr); DEBUG(fprintf(logfile, " %02X = %d\n", (int) xor_byte, (int) value)); } static void wr_long(uint32_t value) { xor_byte ^= (value & 0xFF); (void) putc((int) xor_byte, fileptr); DEBUG(fprintf(logfile, "LONG: %02X", (int) xor_byte)); xor_byte ^= ((value >> 8) & 0xFF); (void) putc((int) xor_byte, fileptr); DEBUG(fprintf(logfile, " %02X", (int) xor_byte)); xor_byte ^= ((value >> 16) & 0xFF); (void) putc((int) xor_byte, fileptr); DEBUG(fprintf(logfile, " %02X", (int) xor_byte)); xor_byte ^= ((value >> 24) & 0xFF); (void) putc((int) xor_byte, fileptr); DEBUG(fprintf(logfile, " %02X = %ld\n", (int) xor_byte, (int32_t) value)); } static void wr_bytes(uint8_t *value, int count) { uint8_t *ptr; DEBUG(fprintf(logfile, "%d BYTES:", count)); ptr = value; for (int i = 0; i < count; i++) { xor_byte ^= *ptr++; (void) putc((int) xor_byte, fileptr); DEBUG(fprintf(logfile, " %02X = %d", (int) xor_byte, (int) (ptr[-1]))); } DEBUG(fprintf(logfile, "\n")); } static void wr_string(char *str) { DEBUG(char *s = str); DEBUG(fprintf(logfile, "STRING:")); while (*str != '\0') { xor_byte ^= *str++; (void) putc((int) xor_byte, fileptr); DEBUG(fprintf(logfile, " %02X", (int) xor_byte)); } xor_byte ^= *str; (void) putc((int) xor_byte, fileptr); DEBUG(fprintf(logfile, " %02X = \"%s\"\n", (int) xor_byte, s)); } static void wr_shorts(uint16_t *value, int count) { DEBUG(fprintf(logfile, "%d SHORTS:", count)); uint16_t *sptr = value; for (int i = 0; i < count; i++) { xor_byte ^= (*sptr & 0xFF); (void) putc((int) xor_byte, fileptr); DEBUG(fprintf(logfile, " %02X", (int) xor_byte)); xor_byte ^= ((*sptr++ >> 8) & 0xFF); (void) putc((int) xor_byte, fileptr); DEBUG(fprintf(logfile, " %02X = %d", (int) xor_byte, (int) sptr[-1])); } DEBUG(fprintf(logfile, "\n")); } static void wr_item(Inventory_t &item) { DEBUG(fprintf(logfile, "ITEM:\n")); wr_short(item.id); wr_byte(item.special_name_id); wr_string(item.inscription); wr_long(item.flags); wr_byte(item.category_id); wr_byte(item.sprite); wr_short((uint16_t) item.misc_use); wr_long((uint32_t) item.cost); wr_byte(item.sub_category_id); wr_byte(item.items_count); wr_short(item.weight); wr_short((uint16_t) item.to_hit); wr_short((uint16_t) item.to_damage); wr_short((uint16_t) item.ac); wr_short((uint16_t) item.to_ac); wr_byte(item.damage.dice); wr_byte(item.damage.sides); wr_byte(item.depth_first_found); wr_byte(item.identification); } static void wr_monster(Monster_t const &monster) { DEBUG(fprintf(logfile, "MONSTER:\n")); wr_short((uint16_t) monster.hp); wr_short((uint16_t) monster.sleep_count); wr_short((uint16_t) monster.speed); wr_short(monster.creature_id); wr_byte(monster.y); wr_byte(monster.x); wr_byte(monster.distance_from_player); wr_bool(monster.lit); wr_byte(monster.stunned_amount); wr_byte(monster.confused_amount); } // get_byte reads a single byte from a file, without any xor_byte encryption static uint8_t get_byte() { return (uint8_t) (getc(fileptr) & 0xFF); } static bool rd_bool() { return (bool) rd_byte(); } static uint8_t rd_byte() { auto c = get_byte(); uint8_t decoded_byte = c ^xor_byte; xor_byte = c; DEBUG(fprintf(logfile, "BYTE: %02X = %d\n", (int) c, decoded_byte)); return decoded_byte; } static uint16_t rd_short() { auto c = get_byte(); uint16_t decoded_int = c ^xor_byte; xor_byte = get_byte(); decoded_int |= (uint16_t) (c ^ xor_byte) << 8; DEBUG(fprintf(logfile, "SHORT: %02X %02X = %d\n", (int) c, (int) xor_byte, decoded_int)); return decoded_int; } static uint32_t rd_long() { auto c = get_byte(); uint32_t decoded_long = c ^xor_byte; xor_byte = get_byte(); decoded_long |= (uint32_t) (c ^ xor_byte) << 8; DEBUG(fprintf(logfile, "LONG: %02X %02X ", (int) c, (int) xor_byte)); c = get_byte(); decoded_long |= (uint32_t) (c ^ xor_byte) << 16; xor_byte = get_byte(); decoded_long |= (uint32_t) (c ^ xor_byte) << 24; DEBUG(fprintf(logfile, "%02X %02X = %ld\n", (int) c, (int) xor_byte, decoded_long)); return decoded_long; } static void rd_bytes(uint8_t *value, int count) { DEBUG(fprintf(logfile, "%d BYTES:", count)); uint8_t *ptr = value; for (int i = 0; i < count; i++) { auto c = get_byte(); *ptr++ = c ^ xor_byte; xor_byte = c; DEBUG(fprintf(logfile, " %02X = %d", (int) c, (int) ptr[-1])); } DEBUG(fprintf(logfile, "\n")); } static void rd_string(char *str) { DEBUG(char *s = str); DEBUG(fprintf(logfile, "STRING: ")); do { auto c = get_byte(); *str = c ^ xor_byte; xor_byte = c; DEBUG(fprintf(logfile, "%02X ", (int) c)); } while (*str++ != '\0'); DEBUG(fprintf(logfile, "= \"%s\"\n", s)); } static void rd_shorts(uint16_t *value, int count) { DEBUG(fprintf(logfile, "%d SHORTS:", count)); uint16_t *sptr = value; for (int i = 0; i < count; i++) { auto c = get_byte(); uint16_t s = c ^xor_byte; xor_byte = get_byte(); s |= (uint16_t) (c ^ xor_byte) << 8; *sptr++ = s; DEBUG(fprintf(logfile, " %02X %02X = %d", (int) c, (int) xor_byte, (int) s)); } DEBUG(fprintf(logfile, "\n")); } static void rd_item(Inventory_t &item) { DEBUG(fprintf(logfile, "ITEM:\n")); item.id = rd_short(); item.special_name_id = rd_byte(); rd_string(item.inscription); item.flags = rd_long(); item.category_id = rd_byte(); item.sprite = rd_byte(); item.misc_use = rd_short(); item.cost = rd_long(); item.sub_category_id = rd_byte(); item.items_count = rd_byte(); item.weight = rd_short(); item.to_hit = rd_short(); item.to_damage = rd_short(); item.ac = rd_short(); item.to_ac = rd_short(); item.damage.dice = rd_byte(); item.damage.sides = rd_byte(); item.depth_first_found = rd_byte(); item.identification = rd_byte(); } static void rd_monster(Monster_t &monster) { DEBUG(fprintf(logfile, "MONSTER:\n")); monster.hp = rd_short(); monster.sleep_count = rd_short(); monster.speed = rd_short(); monster.creature_id = rd_short(); monster.y = rd_byte(); monster.x = rd_byte(); monster.distance_from_player = rd_byte(); monster.lit = rd_bool(); monster.stunned_amount = rd_byte(); monster.confused_amount = rd_byte(); } // functions called from death.c to implement the score file // set the local fileptr to the score file fileptr void setFileptr(FILE *file) { fileptr = file; } void saveHighScore(HighScore_t const &score) { DEBUG(logfile = fopen("IO_LOG", "a")); DEBUG(fprintf(logfile, "Saving score:\n")); // Save the encryption byte for robustness. wr_byte(xor_byte); wr_long((uint32_t) score.points); wr_long((uint32_t) score.birth_date); wr_short((uint16_t) score.uid); wr_short((uint16_t) score.mhp); wr_short((uint16_t) score.chp); wr_byte(score.dungeon_depth); wr_byte(score.level); wr_byte(score.deepest_dungeon_depth); wr_byte(score.gender); wr_byte(score.race); wr_byte(score.character_class); wr_bytes((uint8_t *) score.name, PLAYER_NAME_SIZE); wr_bytes((uint8_t *) score.died_from, 25); DEBUG(fclose(logfile)); } void readHighScore(HighScore_t &score) { DEBUG(logfile = fopen("IO_LOG", "a")); DEBUG(fprintf(logfile, "Reading score:\n")); // Read the encryption byte. xor_byte = get_byte(); score.points = rd_long(); score.birth_date = rd_long(); score.uid = rd_short(); score.mhp = rd_short(); score.chp = rd_short(); score.dungeon_depth = rd_byte(); score.level = rd_byte(); score.deepest_dungeon_depth = rd_byte(); score.gender = rd_byte(); score.race = rd_byte(); score.character_class = rd_byte(); rd_bytes((uint8_t *) score.name, PLAYER_NAME_SIZE); rd_bytes((uint8_t *) score.died_from, 25); DEBUG(fclose(logfile)); } umoria-5.7.10+20181022/src/identification.cpp0000644000175000017500000007206613363422757017260 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. // Handle object identification and descriptions // ...mostly string handling code #include "headers.h" char magic_item_titles[MAX_TITLES][10]; // Identified objects flags uint8_t objects_identified[OBJECT_IDENT_SIZE]; static const char *objectDescription(char command) { // every printing ASCII character is listed here, in the // order in which they appear in the ASCII character set. switch (command) { case ' ': return " - An open pit."; case '!': return "! - A potion."; case '"': return "\" - An amulet, periapt, or necklace."; case '#': return "# - A stone wall."; case '$': return "$ - Treasure."; case '%': if (!config::options::highlight_seams) { return "% - Not used."; } return "% - A magma or quartz vein."; case '&': return "& - Treasure chest."; case '\'': return "' - An open door."; case '(': return "( - Soft armor."; case ')': return ") - A shield."; case '*': return "* - Gems."; case '+': return "+ - A closed door."; case ',': return ", - Food or mushroom patch."; case '-': return "- - A wand"; case '.': return ". - Floor."; case '/': return "/ - A pole weapon."; // case '0': // return "0 - Not used."; case '1': return "1 - Entrance to General Store."; case '2': return "2 - Entrance to Armory."; case '3': return "3 - Entrance to Weaponsmith."; case '4': return "4 - Entrance to Temple."; case '5': return "5 - Entrance to Alchemy shop."; case '6': return "6 - Entrance to Magic-Users store."; // case '7': // return "7 - Not used."; // case '8': // return "8 - Not used."; // case '9': // return "9 - Not used."; case ':': return ": - Rubble."; case ';': return "; - A loose rock."; case '<': return "< - An up staircase."; case '=': return "= - A ring."; case '>': return "> - A down staircase."; case '?': return "? - A scroll."; case '@': return py.misc.name; case 'A': return "A - Giant Ant Lion."; case 'B': return "B - The Balrog."; case 'C': return "C - Gelatinous Cube."; case 'D': return "D - An Ancient Dragon (Beware)."; case 'E': return "E - Elemental."; case 'F': return "F - Giant Fly."; case 'G': return "G - Ghost."; case 'H': return "H - Hobgoblin."; // case 'I': // return "I - Invisible Stalker."; case 'J': return "J - Jelly."; case 'K': return "K - Killer Beetle."; case 'L': return "L - Lich."; case 'M': return "M - Mummy."; // case 'N': // return "N - Not used."; case 'O': return "O - Ooze."; case 'P': return "P - Giant humanoid."; case 'Q': return "Q - Quylthulg (Pulsing Flesh Mound)."; case 'R': return "R - Reptile."; case 'S': return "S - Giant Scorpion."; case 'T': return "T - Troll."; case 'U': return "U - Umber Hulk."; case 'V': return "V - Vampire."; case 'W': return "W - Wight or Wraith."; case 'X': return "X - Xorn."; case 'Y': return "Y - Yeti."; // case 'Z': // return "Z - Not used."; case '[': return "[ - Hard armor."; case '\\': return "\\ - A hafted weapon."; case ']': return "] - Misc. armor."; case '^': return "^ - A trap."; case '_': return "_ - A staff."; // case '`': // return "` - Not used."; case 'a': return "a - Giant Ant."; case 'b': return "b - Giant Bat."; case 'c': return "c - Giant Centipede."; case 'd': return "d - Dragon."; case 'e': return "e - Floating Eye."; case 'f': return "f - Giant Frog."; case 'g': return "g - Golem."; case 'h': return "h - Harpy."; case 'i': return "i - Icky Thing."; case 'j': return "j - Jackal."; case 'k': return "k - Kobold."; case 'l': return "l - Giant Louse."; case 'm': return "m - Mold."; case 'n': return "n - Naga."; case 'o': return "o - Orc or Ogre."; case 'p': return "p - Person (Humanoid)."; case 'q': return "q - Quasit."; case 'r': return "r - Rodent."; case 's': return "s - Skeleton."; case 't': return "t - Giant Tick."; // case 'u': // return "u - Not used."; // case 'v': // return "v - Not used."; case 'w': return "w - Worm or Worm Mass."; // case 'x': // return "x - Not used."; case 'y': return "y - Yeek."; case 'z': return "z - Zombie."; case '{': return "{ - Arrow, bolt, or bullet."; case '|': return "| - A sword or dagger."; case '}': return "} - Bow, crossbow, or sling."; case '~': return "~ - Miscellaneous item."; default: return "Not Used."; } } void identifyGameObject() { char command; if (!getCommand("Enter character to be identified :", command)) { return; } putStringClearToEOL(objectDescription(command), Coord_t{0, 0}); recallMonsterAttributes(command); } // Initialize all Potions, wands, staves, scrolls, etc. void magicInitializeItemNames() { int id; seedSet(game.magic_seed); // The first 3 entries for colors are fixed, (slime & apple juice, water) for (int i = 3; i < MAX_COLORS; i++) { id = randomNumber(MAX_COLORS - 3) + 2; const char *color = colors[i]; colors[i] = colors[id]; colors[id] = color; } for (auto &w : woods) { id = randomNumber(MAX_WOODS) - 1; const char *wood = w; w = woods[id]; woods[id] = wood; } for (auto &m : metals) { id = randomNumber(MAX_METALS) - 1; const char *metal = m; m = metals[id]; metals[id] = metal; } for (auto &r : rocks) { id = randomNumber(MAX_ROCKS) - 1; const char *rock = r; r = rocks[id]; rocks[id] = rock; } for (auto &a : amulets) { id = randomNumber(MAX_AMULETS) - 1; const char *amulet = a; a = amulets[id]; amulets[id] = amulet; } for (auto &m : mushrooms) { id = randomNumber(MAX_MUSHROOMS) - 1; const char *mushroom = m; m = mushrooms[id]; mushrooms[id] = mushroom; } int k; vtype_t title = {'\0'}; for (auto &item_title : magic_item_titles) { title[0] = '\0'; k = randomNumber(2) + 1; for (int i = 0; i < k; i++) { for (int s = randomNumber(2); s > 0; s--) { (void) strcat(title, syllables[randomNumber(MAX_SYLLABLES) - 1]); } if (i < k - 1) { (void) strcat(title, " "); } } if (title[8] == ' ') { title[8] = '\0'; } else { title[9] = '\0'; } (void) strcpy(item_title, title); } seedResetToOldSeed(); } int16_t objectPositionOffset(int category_id, int sub_category_id) { switch (category_id) { case TV_AMULET: return 0; case TV_RING: return 1; case TV_STAFF: return 2; case TV_WAND: return 3; case TV_SCROLL1: case TV_SCROLL2: return 4; case TV_POTION1: case TV_POTION2: return 5; case TV_FOOD: if ((sub_category_id & (ITEM_SINGLE_STACK_MIN - 1)) < MAX_MUSHROOMS) { return 6; } return -1; default: return -1; } } static void clearObjectTriedFlag(int16_t id) { objects_identified[id] &= ~config::identification::OD_TRIED; } static void setObjectTriedFlag(int16_t id) { objects_identified[id] |= config::identification::OD_TRIED; } static bool isObjectKnown(int16_t id) { return (objects_identified[id] & config::identification::OD_KNOWN1) != 0; } // Remove "Secret" symbol for identity of object void itemSetAsIdentified(int category_id, int sub_category_id) { int16_t id = objectPositionOffset(category_id, sub_category_id); if (id < 0) { return; } id <<= 6; id += (uint8_t) (sub_category_id & (ITEM_SINGLE_STACK_MIN - 1)); objects_identified[id] |= config::identification::OD_KNOWN1; // clear the tried flag, since it is now known clearObjectTriedFlag(id); } // Remove an automatically generated inscription. -CJS- static void unsample(Inventory_t &item) { // this also used to clear config::identification::ID_DAMD flag, but I think it should remain set item.identification &= ~(config::identification::ID_MAGIK | config::identification::ID_EMPTY); int16_t id = objectPositionOffset(item.category_id, item.sub_category_id); if (id < 0) { return; } id <<= 6; id += (uint8_t) (item.sub_category_id & (ITEM_SINGLE_STACK_MIN - 1)); // clear the tried flag, since it is now known clearObjectTriedFlag(id); } // Remove "Secret" symbol for identity of plusses void spellItemIdentifyAndRemoveRandomInscription(Inventory_t &item) { unsample(item); item.identification |= config::identification::ID_KNOWN2; } bool spellItemIdentified(Inventory_t const &item) { return (item.identification & config::identification::ID_KNOWN2) != 0; } void spellItemRemoveIdentification(Inventory_t &item) { item.identification &= ~config::identification::ID_KNOWN2; } void itemIdentificationClearEmpty(Inventory_t &item) { item.identification &= ~config::identification::ID_EMPTY; } void itemIdentifyAsStoreBought(Inventory_t &item) { item.identification |= config::identification::ID_STORE_BOUGHT; spellItemIdentifyAndRemoveRandomInscription(item); } static bool itemStoreBought(int identification) { return (identification & config::identification::ID_STORE_BOUGHT) != 0; } // Items which don't have a 'color' are always known / itemSetAsIdentified(), // so that they can be carried in order in the inventory. bool itemSetColorlessAsIdentified(int category_id, int sub_category_id, int identification) { int16_t id = objectPositionOffset(category_id, sub_category_id); if (id < 0) { return config::identification::OD_KNOWN1 != 0u; } if (itemStoreBought(identification)) { return config::identification::OD_KNOWN1 != 0u; } id <<= 6; id += (uint8_t) (sub_category_id & (ITEM_SINGLE_STACK_MIN - 1)); return isObjectKnown(id); } // Somethings been sampled -CJS- void itemSetAsTried(Inventory_t const &item) { int16_t id = objectPositionOffset(item.category_id, item.sub_category_id); if (id < 0) { return; } id <<= 6; id += (uint8_t) (item.sub_category_id & (ITEM_SINGLE_STACK_MIN - 1)); setObjectTriedFlag(id); } // Somethings been identified. // Extra complexity by CJS so that it can merge store/dungeon objects when appropriate. void itemIdentify(Inventory_t &item, int &item_id) { if ((item.flags & config::treasure::flags::TR_CURSED) != 0u) { itemAppendToInscription(item, config::identification::ID_DAMD); } if (itemSetColorlessAsIdentified(item.category_id, item.sub_category_id, item.identification)) { return; } itemSetAsIdentified(item.category_id, item.sub_category_id); int x1 = item.category_id; int x2 = item.sub_category_id; // no merging possible if (x2 < ITEM_SINGLE_STACK_MIN || x2 >= ITEM_GROUP_MIN) { return; } int j; for (int i = 0; i < py.unique_inventory_items; i++) { Inventory_t const &t_ptr = inventory[i]; if (t_ptr.category_id == x1 && t_ptr.sub_category_id == x2 && i != item_id && ((int) t_ptr.items_count + (int) item.items_count) < 256) { // make *item_id the smaller number if (item_id > i) { j = item_id; item_id = i; i = j; } printMessage("You combine similar objects from the shop and dungeon."); inventory[item_id].items_count += inventory[i].items_count; py.unique_inventory_items--; for (j = i; j < py.unique_inventory_items; j++) { inventory[j] = inventory[j + 1]; } inventoryItemCopyTo(config::dungeon::objects::OBJ_NOTHING, inventory[j]); } } } // If an object has lost magical properties, // remove the appropriate portion of the name. -CJS- void itemRemoveMagicNaming(Inventory_t &item) { item.special_name_id = special_name_ids::SN_NULL; } int bowDamageValue(int16_t misc_use) { if (misc_use == 1 || misc_use == 2) { return 2; } if (misc_use == 3 || misc_use == 5) { return 3; } if (misc_use == 4 || misc_use == 6) { return 4; } return -1; } // determines how the `item.misc_use` field is printed enum class ItemMiscUse { ignored, charges, plusses, light, flags, z_plusses, }; // Set the `description` for an inventory item. // The `add_prefix` param indicates that an article must be added. // Note that since out_val can easily exceed 80 characters, itemDescription // must always be called with a obj_desc_t as the first parameter. void itemDescription(obj_desc_t description, Inventory_t const &item, bool add_prefix) { int indexx = item.sub_category_id & (ITEM_SINGLE_STACK_MIN - 1); // base name, modifier string const char *basenm = game_objects[item.id].name; const char *modstr = CNIL; vtype_t damstr = {'\0'}; damstr[0] = '\0'; bool append_name = false; bool modify = !itemSetColorlessAsIdentified(item.category_id, item.sub_category_id, item.identification); ItemMiscUse misc_type = ItemMiscUse::ignored; switch (item.category_id) { case TV_MISC: case TV_CHEST: break; case TV_SLING_AMMO: case TV_BOLT: case TV_ARROW: (void) sprintf(damstr, " (%dd%d)", item.damage.dice, item.damage.sides); break; case TV_LIGHT: misc_type = ItemMiscUse::light; break; case TV_SPIKE: break; case TV_BOW: (void) sprintf(damstr, " (x%d)", bowDamageValue(item.misc_use)); break; case TV_HAFTED: case TV_POLEARM: case TV_SWORD: (void) sprintf(damstr, " (%dd%d)", item.damage.dice, item.damage.sides); misc_type = ItemMiscUse::flags; break; case TV_DIGGING: misc_type = ItemMiscUse::z_plusses; (void) sprintf(damstr, " (%dd%d)", item.damage.sides, item.damage.sides); break; case TV_BOOTS: case TV_GLOVES: case TV_CLOAK: case TV_HELM: case TV_SHIELD: case TV_HARD_ARMOR: case TV_SOFT_ARMOR: break; case TV_AMULET: if (modify) { basenm = "& %s Amulet"; modstr = amulets[indexx]; } else { basenm = "& Amulet"; append_name = true; } misc_type = ItemMiscUse::plusses; break; case TV_RING: if (modify) { basenm = "& %s Ring"; modstr = rocks[indexx]; } else { basenm = "& Ring"; append_name = true; } misc_type = ItemMiscUse::plusses; break; case TV_STAFF: if (modify) { basenm = "& %s Staff"; modstr = woods[indexx]; } else { basenm = "& Staff"; append_name = true; } misc_type = ItemMiscUse::charges; break; case TV_WAND: if (modify) { basenm = "& %s Wand"; modstr = metals[indexx]; } else { basenm = "& Wand"; append_name = true; } misc_type = ItemMiscUse::charges; break; case TV_SCROLL1: case TV_SCROLL2: if (modify) { basenm = "& Scroll~ titled \"%s\""; modstr = magic_item_titles[indexx]; } else { basenm = "& Scroll~"; append_name = true; } break; case TV_POTION1: case TV_POTION2: if (modify) { basenm = "& %s Potion~"; modstr = colors[indexx]; } else { basenm = "& Potion~"; append_name = true; } break; case TV_FLASK: break; case TV_FOOD: if (modify) { if (indexx <= 15) { basenm = "& %s Mushroom~"; } else if (indexx <= 20) { basenm = "& Hairy %s Mold~"; } if (indexx <= 20) { modstr = mushrooms[indexx]; } } else { append_name = true; if (indexx <= 15) { basenm = "& Mushroom~"; } else if (indexx <= 20) { basenm = "& Hairy Mold~"; } else { // Ordinary food does not have a name appended. append_name = false; } } break; case TV_MAGIC_BOOK: modstr = basenm; basenm = "& Book~ of Magic Spells %s"; break; case TV_PRAYER_BOOK: modstr = basenm; basenm = "& Holy Book~ of Prayers %s"; break; case TV_OPEN_DOOR: case TV_CLOSED_DOOR: case TV_SECRET_DOOR: case TV_RUBBLE: break; case TV_GOLD: case TV_INVIS_TRAP: case TV_VIS_TRAP: case TV_UP_STAIR: case TV_DOWN_STAIR: (void) strcpy(description, game_objects[item.id].name); (void) strcat(description, "."); return; case TV_STORE_DOOR: (void) sprintf(description, "the entrance to the %s.", game_objects[item.id].name); return; default: (void) strcpy(description, "Error in objdes()"); return; } obj_desc_t tmp_val = {'\0'}; if (modstr != CNIL) { (void) sprintf(tmp_val, basenm, modstr); } else { (void) strcpy(tmp_val, basenm); } if (append_name) { (void) strcat(tmp_val, " of "); (void) strcat(tmp_val, game_objects[item.id].name); } if (item.items_count != 1) { insertStringIntoString(tmp_val, "ch~", "ches"); insertStringIntoString(tmp_val, "~", "s"); } else { insertStringIntoString(tmp_val, "~", CNIL); } if (!add_prefix) { if (strncmp("some", tmp_val, 4) == 0) { (void) strcpy(description, &tmp_val[5]); } else if (tmp_val[0] == '&') { // eliminate the '& ' at the beginning (void) strcpy(description, &tmp_val[2]); } else { (void) strcpy(description, tmp_val); } return; } vtype_t tmp_str = {'\0'}; // TODO(cook): `spellItemIdentified()` is called several times in this // function, but `item` is immutable, so we should be able to call and // assign it once, then use that value everywhere below. if (item.special_name_id != special_name_ids::SN_NULL && spellItemIdentified(item)) { (void) strcat(tmp_val, " "); (void) strcat(tmp_val, special_item_names[item.special_name_id]); } if (damstr[0] != '\0') { (void) strcat(tmp_val, damstr); } if (spellItemIdentified(item)) { auto abs_to_hit = (int) std::abs((std::intmax_t) item.to_hit); auto abs_to_damage = (int) std::abs((std::intmax_t) item.to_damage); if ((item.identification & config::identification::ID_SHOW_HIT_DAM) != 0) { (void) sprintf(tmp_str, " (%c%d,%c%d)", (item.to_hit < 0) ? '-' : '+', abs_to_hit, (item.to_damage < 0) ? '-' : '+', abs_to_damage); } else if (item.to_hit != 0) { (void) sprintf(tmp_str, " (%c%d)", (item.to_hit < 0) ? '-' : '+', abs_to_hit); } else if (item.to_damage != 0) { (void) sprintf(tmp_str, " (%c%d)", (item.to_damage < 0) ? '-' : '+', abs_to_damage); } else { tmp_str[0] = '\0'; } (void) strcat(tmp_val, tmp_str); } // Crowns have a zero base AC, so make a special test for them. auto abs_to_ac = (int) std::abs((std::intmax_t) item.to_ac); if (item.ac != 0 || item.category_id == TV_HELM) { (void) sprintf(tmp_str, " [%d", item.ac); (void) strcat(tmp_val, tmp_str); if (spellItemIdentified(item)) { // originally used %+d, but several machines don't support it (void) sprintf(tmp_str, ",%c%d", (item.to_ac < 0) ? '-' : '+', abs_to_ac); (void) strcat(tmp_val, tmp_str); } (void) strcat(tmp_val, "]"); } else if (item.to_ac != 0 && spellItemIdentified(item)) { // originally used %+d, but several machines don't support it (void) sprintf(tmp_str, " [%c%d]", (item.to_ac < 0) ? '-' : '+', abs_to_ac); (void) strcat(tmp_val, tmp_str); } // override defaults, check for `misc_type` flags in the `item.identification` field if ((item.identification & config::identification::ID_NO_SHOW_P1) != 0) { misc_type = ItemMiscUse::ignored; } else if ((item.identification & config::identification::ID_SHOW_P1) != 0) { misc_type = ItemMiscUse::z_plusses; } tmp_str[0] = '\0'; if (misc_type == ItemMiscUse::light) { (void) sprintf(tmp_str, " with %d turns of light", item.misc_use); } else if (misc_type == ItemMiscUse::ignored) { // NOOP } else if (spellItemIdentified(item)) { auto abs_misc_use = (int) std::abs((std::intmax_t) item.misc_use); if (misc_type == ItemMiscUse::z_plusses) { // originally used %+d, but several machines don't support it (void) sprintf(tmp_str, " (%c%d)", (item.misc_use < 0) ? '-' : '+', abs_misc_use); } else if (misc_type == ItemMiscUse::charges) { (void) sprintf(tmp_str, " (%d charges)", item.misc_use); } else if (item.misc_use != 0) { if (misc_type == ItemMiscUse::plusses) { (void) sprintf(tmp_str, " (%c%d)", (item.misc_use < 0) ? '-' : '+', abs_misc_use); } else if (misc_type == ItemMiscUse::flags) { if ((item.flags & config::treasure::flags::TR_STR) != 0u) { (void) sprintf(tmp_str, " (%c%d to STR)", (item.misc_use < 0) ? '-' : '+', abs_misc_use); } else if ((item.flags & config::treasure::flags::TR_STEALTH) != 0u) { (void) sprintf(tmp_str, " (%c%d to stealth)", (item.misc_use < 0) ? '-' : '+', abs_misc_use); } } } } (void) strcat(tmp_val, tmp_str); // ampersand is always the first character if (tmp_val[0] == '&') { // use &tmp_val[1], so that & does not appear in output if (item.items_count > 1) { (void) sprintf(description, "%d%s", (int) item.items_count, &tmp_val[1]); } else if (item.items_count < 1) { (void) sprintf(description, "%s%s", "no more", &tmp_val[1]); } else if (isVowel(tmp_val[2])) { (void) sprintf(description, "an%s", &tmp_val[1]); } else { (void) sprintf(description, "a%s", &tmp_val[1]); } } else if (item.items_count < 1) { // handle 'no more' case specially // check for "some" at start if (strncmp("some", tmp_val, 4) == 0) { (void) sprintf(description, "no more %s", &tmp_val[5]); } else { // here if no article (void) sprintf(description, "no more %s", tmp_val); } } else { (void) strcpy(description, tmp_val); } tmp_str[0] = '\0'; if ((indexx = objectPositionOffset(item.category_id, item.sub_category_id)) >= 0) { indexx <<= 6; indexx += (item.sub_category_id & (ITEM_SINGLE_STACK_MIN - 1)); // don't print tried string for store bought items if (((objects_identified[indexx] & config::identification::OD_TRIED) != 0) && !itemStoreBought(item.identification)) { (void) strcat(tmp_str, "tried "); } } if ((item.identification & (config::identification::ID_MAGIK | config::identification::ID_EMPTY | config::identification::ID_DAMD)) != 0) { if ((item.identification & config::identification::ID_MAGIK) != 0) { (void) strcat(tmp_str, "magik "); } if ((item.identification & config::identification::ID_EMPTY) != 0) { (void) strcat(tmp_str, "empty "); } if ((item.identification & config::identification::ID_DAMD) != 0) { (void) strcat(tmp_str, "damned "); } } if (item.inscription[0] != '\0') { (void) strcat(tmp_str, item.inscription); } else if ((indexx = (int) strlen(tmp_str)) > 0) { // remove the extra blank at the end tmp_str[indexx - 1] = '\0'; } if (tmp_str[0] != 0) { (void) sprintf(tmp_val, " {%s}", tmp_str); (void) strcat(description, tmp_val); } (void) strcat(description, "."); } // Describe number of remaining charges. -RAK- void itemChargesRemainingDescription(int item_id) { if (!spellItemIdentified(inventory[item_id])) { return; } int rem_num = inventory[item_id].misc_use; vtype_t out_val = {'\0'}; (void) sprintf(out_val, "You have %d charges remaining.", rem_num); printMessage(out_val); } // Describe amount of item remaining. -RAK- void itemTypeRemainingCountDescription(int item_id) { Inventory_t &item = inventory[item_id]; item.items_count--; obj_desc_t tmp_str = {'\0'}; itemDescription(tmp_str, item, true); item.items_count++; // the string already has a dot at the end. obj_desc_t out_val = {'\0'}; (void) sprintf(out_val, "You have %s", tmp_str); printMessage(out_val); } // Add a comment to an object description. -CJS- void itemInscribe() { if (py.unique_inventory_items == 0 && py.equipment_count == 0) { printMessage("You are not carrying anything to inscribe."); return; } int item_id; if (!inventoryGetInputForItemId(item_id, "Which one? ", 0, PLAYER_INVENTORY_SIZE, CNIL, CNIL)) { return; } obj_desc_t msg = {'\0'}; itemDescription(msg, inventory[item_id], true); obj_desc_t inscription = {'\0'}; (void) sprintf(inscription, "Inscribing %s", msg); printMessage(inscription); if (inventory[item_id].inscription[0] != '\0') { (void) sprintf(inscription, "Replace %s New inscription:", inventory[item_id].inscription); } else { (void) strcpy(inscription, "Inscription: "); } int msg_len = 78 - (int) strlen(msg); if (msg_len > 12) { msg_len = 12; } putStringClearToEOL(inscription, Coord_t{0, 0}); if (getStringInput(inscription, Coord_t{0, (int) strlen(inscription)}, msg_len)) { itemReplaceInscription(inventory[item_id], inscription); } } // Append an additional comment to an object description. -CJS- void itemAppendToInscription(Inventory_t &item, uint8_t item_ident_type) { item.identification |= item_ident_type; } // Replace any existing comment in an object description with a new one. -CJS- void itemReplaceInscription(Inventory_t &item, const char *inscription) { (void) strcpy(item.inscription, inscription); } void objectBlockedByMonster(int monster_id) { vtype_t description = {'\0'}; vtype_t msg = {'\0'}; Monster_t const &monster = monsters[monster_id]; const char *name = creatures_list[monster.creature_id].name; if (monster.lit) { (void) sprintf(description, "The %s", name); } else { (void) strcpy(description, "Something"); } (void) sprintf(msg, "%s is in your way!", description); printMessage(msg); } umoria-5.7.10+20181022/src/data_tables.cpp0000644000175000017500000001554713363422757016533 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. // Data tables for attack/RNG/etc. // clang-format off #include "headers.h" // Following are arrays for descriptive pieces const char *colors[MAX_COLORS] = { // Do not move the first three "Icky Green", "Light Brown", "Clear", "Azure", "Blue", "Blue Speckled", "Black", "Brown", "Brown Speckled", "Bubbling", "Chartreuse", "Cloudy", "Copper Speckled", "Crimson", "Cyan", "Dark Blue", "Dark Green", "Dark Red", "Gold Speckled", "Green", "Green Speckled", "Grey", "Grey Speckled", "Hazy", "Indigo", "Light Blue", "Light Green", "Magenta", "Metallic Blue", "Metallic Red", "Metallic Green", "Metallic Purple", "Misty", "Orange", "Orange Speckled", "Pink", "Pink Speckled", "Puce", "Purple", "Purple Speckled", "Red", "Red Speckled", "Silver Speckled", "Smoky", "Tangerine", "Violet", "Vermilion", "White", "Yellow", }; const char *mushrooms[MAX_MUSHROOMS] = { "Blue", "Black", "Black Spotted", "Brown", "Dark Blue", "Dark Green", "Dark Red", "Ecru", "Furry", "Green", "Grey", "Light Blue", "Light Green", "Plaid", "Red", "Slimy", "Tan", "White", "White Spotted", "Wooden", "Wrinkled", "Yellow", }; const char *woods[MAX_WOODS] = { "Aspen", "Balsa", "Banyan", "Birch", "Cedar", "Cottonwood", "Cypress", "Dogwood", "Elm", "Eucalyptus", "Hemlock", "Hickory", "Ironwood", "Locust", "Mahogany", "Maple", "Mulberry", "Oak", "Pine", "Redwood", "Rosewood", "Spruce", "Sycamore", "Teak", "Walnut", }; const char *metals[MAX_METALS] = { "Aluminum", "Cast Iron", "Chromium", "Copper", "Gold", "Iron", "Magnesium", "Molybdenum", "Nickel", "Rusty", "Silver", "Steel", "Tin", "Titanium", "Tungsten", "Zirconium", "Zinc", "Aluminum-Plated", "Copper-Plated", "Gold-Plated", "Nickel-Plated", "Silver-Plated", "Steel-Plated", "Tin-Plated", "Zinc-Plated", }; const char *rocks[MAX_ROCKS] = { "Alexandrite", "Amethyst", "Aquamarine", "Azurite", "Beryl", "Bloodstone", "Calcite", "Carnelian", "Corundum", "Diamond", "Emerald", "Fluorite", "Garnet", "Granite", "Jade", "Jasper", "Lapis Lazuli", "Malachite", "Marble", "Moonstone", "Onyx", "Opal", "Pearl", "Quartz", "Quartzite", "Rhodonite", "Ruby", "Sapphire", "Tiger Eye", "Topaz", "Turquoise", "Zircon" }; const char *amulets[MAX_AMULETS] = { "Amber", "Driftwood", "Coral", "Agate", "Ivory", "Obsidian", "Bone", "Brass", "Bronze", "Pewter", "Tortoise Shell", }; const char *syllables[MAX_SYLLABLES] = { "a", "ab", "ag", "aks", "ala", "an", "ankh", "app", "arg", "arze", "ash", "aus", "ban", "bar", "bat", "bek", "bie", "bin", "bit", "bjor", "blu", "bot", "bu", "byt", "comp", "con", "cos", "cre", "dalf", "dan", "den", "doe", "dok", "eep", "el", "eng", "er", "ere", "erk", "esh", "evs", "fa", "fid", "for", "fri", "fu", "gan", "gar", "glen", "gop", "gre", "ha", "he", "hyd", "i", "ing", "ion", "ip", "ish", "it", "ite", "iv", "jo", "kho", "kli", "klis", "la", "lech", "man", "mar", "me", "mi", "mic", "mik", "mon", "mung", "mur", "nej", "nelg", "nep", "ner", "nes", "nis", "nih", "nin", "o", "od", "ood", "org", "orn", "ox", "oxy", "pay", "pet", "ple", "plu", "po", "pot", "prok", "re", "rea", "rhov", "ri", "ro", "rog", "rok", "rol", "sa", "san", "sat", "see", "sef", "seh", "shu", "ski", "sna", "sne", "snik", "sno", "so", "sol", "sri", "sta", "sun", "ta", "tab", "tem", "ther", "ti", "tox", "trol", "tue", "turs", "u", "ulk", "um", "un", "uni", "ur", "val", "viv", "vly", "vom", "wah", "wed", "werg", "wex", "whon", "wun", "x", "yerg", "yp", "zun", }; // used to calculate the number of blows the player gets in combat uint8_t blows_table[7][6] = { // STR/W: 9 18 67 107 117 118 : DEX { 1, 1, 1, 1, 1, 1 }, // <2 { 1, 1, 1, 1, 2, 2 }, // <3 { 1, 1, 1, 2, 2, 3 }, // <4 { 1, 1, 2, 2, 3, 3 }, // <5 { 1, 2, 2, 3, 3, 4 }, // <7 { 1, 2, 2, 3, 4, 4 }, // <9 { 2, 2, 3, 3, 4, 4 }, // >9 }; // this table is used to generate a pseudo-normal distribution. See // the function randomNumberNormalDistribution() in misc1.c, this is much faster than calling // transcendental function to calculate a true normal distribution. uint16_t normal_table[NORMAL_TABLE_SIZE] = { 206, 613, 1022, 1430, 1838, 2245, 2652, 3058, 3463, 3867, 4271, 4673, 5075, 5475, 5874, 6271, 6667, 7061, 7454, 7845, 8234, 8621, 9006, 9389, 9770, 10148, 10524, 10898, 11269, 11638, 12004, 12367, 12727, 13085, 13440, 13792, 14140, 14486, 14828, 15168, 15504, 15836, 16166, 16492, 16814, 17133, 17449, 17761, 18069, 18374, 18675, 18972, 19266, 19556, 19842, 20124, 20403, 20678, 20949, 21216, 21479, 21738, 21994, 22245, 22493, 22737, 22977, 23213, 23446, 23674, 23899, 24120, 24336, 24550, 24759, 24965, 25166, 25365, 25559, 25750, 25937, 26120, 26300, 26476, 26649, 26818, 26983, 27146, 27304, 27460, 27612, 27760, 27906, 28048, 28187, 28323, 28455, 28585, 28711, 28835, 28955, 29073, 29188, 29299, 29409, 29515, 29619, 29720, 29818, 29914, 30007, 30098, 30186, 30272, 30356, 30437, 30516, 30593, 30668, 30740, 30810, 30879, 30945, 31010, 31072, 31133, 31192, 31249, 31304, 31358, 31410, 31460, 31509, 31556, 31601, 31646, 31688, 31730, 31770, 31808, 31846, 31882, 31917, 31950, 31983, 32014, 32044, 32074, 32102, 32129, 32155, 32180, 32205, 32228, 32251, 32273, 32294, 32314, 32333, 32352, 32370, 32387, 32404, 32420, 32435, 32450, 32464, 32477, 32490, 32503, 32515, 32526, 32537, 32548, 32558, 32568, 32577, 32586, 32595, 32603, 32611, 32618, 32625, 32632, 32639, 32645, 32651, 32657, 32662, 32667, 32672, 32677, 32682, 32686, 32690, 32694, 32698, 32702, 32705, 32708, 32711, 32714, 32717, 32720, 32722, 32725, 32727, 32729, 32731, 32733, 32735, 32737, 32739, 32740, 32742, 32743, 32745, 32746, 32747, 32748, 32749, 32750, 32751, 32752, 32753, 32754, 32755, 32756, 32757, 32757, 32758, 32758, 32759, 32760, 32760, 32761, 32761, 32761, 32762, 32762, 32763, 32763, 32763, 32764, 32764, 32764, 32764, 32765, 32765, 32765, 32765, 32766, 32766, 32766, 32766, 32766, }; umoria-5.7.10+20181022/src/monster.cpp0000644000175000017500000017240713363422757015756 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. // Handle monster movement and attacks #include "headers.h" // A horrible hack, needed because compact_monster() is called from // deep within updateMonsters() via monsterPlaceNew() and monsterSummon() int hack_monptr = -1; static bool executeAttackOnPlayer(uint8_t creature_level, int16_t &monster_hp, int monster_id, int attack_type, int damage, vtype_t death_description, bool noticed); static bool monsterIsVisible(Monster_t const &monster) { bool visible = false; Tile_t const &tile = dg.floor[monster.y][monster.x]; Creature_t const &creature = creatures_list[monster.creature_id]; if (tile.permanent_light || tile.temporary_light || ((py.running_tracker != 0) && monster.distance_from_player < 2 && py.carrying_light)) { // Normal sight. if ((creature.movement & config::monsters::move::CM_INVISIBLE) == 0) { visible = true; } else if (py.flags.see_invisible) { visible = true; creature_recall[monster.creature_id].movement |= config::monsters::move::CM_INVISIBLE; } } else if (py.flags.see_infra > 0 && monster.distance_from_player <= py.flags.see_infra && ((creature.defenses & config::monsters::defense::CD_INFRA) != 0)) { // Infra vision. visible = true; creature_recall[monster.creature_id].defenses |= config::monsters::defense::CD_INFRA; } return visible; } // Updates screen when monsters move about -RAK- void monsterUpdateVisibility(int monster_id) { bool visible = false; Monster_t &monster = monsters[monster_id]; if (monster.distance_from_player <= config::monsters::MON_MAX_SIGHT && ((py.flags.status & config::player::status::PY_BLIND) == 0u) && coordInsidePanel(Coord_t{monster.y, monster.x})) { if (game.wizard_mode) { // Wizard sight. visible = true; } else if (los(py.row, py.col, monster.y, monster.x)) { visible = monsterIsVisible(monster); } } if (visible) { // Light it up. if (!monster.lit) { playerDisturb(1, 0); monster.lit = true; dungeonLiteSpot(Coord_t{monster.y, monster.x}); // notify inventoryExecuteCommand() screen_has_changed = true; } } else if (monster.lit) { // Turn it off. monster.lit = false; dungeonLiteSpot(Coord_t{monster.y, monster.x}); // notify inventoryExecuteCommand() screen_has_changed = true; } } // Given speed, returns number of moves this turn. -RAK- // NOTE: Player must always move at least once per iteration, // a slowed player is handled by moving monsters faster static int monsterMovementRate(int16_t speed) { if (speed > 0) { if (py.flags.rest != 0) { return 1; } return speed; } // speed must be negative here int rate = 0; if ((dg.game_turn % (2 - speed)) == 0) { rate = 1; }; return rate; } // Makes sure a new creature gets lit up. -CJS- static bool monsterMakeVisible(int y, int x) { int monster_id = dg.floor[y][x].creature_id; if (monster_id <= 1) { return false; } monsterUpdateVisibility(monster_id); return monsters[monster_id].lit; } // Choose correct directions for monster movement -RAK- static void monsterGetMoveDirection(int monster_id, int *directions) { int ay, ax, movement; int y = monsters[monster_id].y - py.row; int x = monsters[monster_id].x - py.col; if (y < 0) { movement = 8; ay = -y; } else { movement = 0; ay = y; } if (x > 0) { movement += 4; ax = x; } else { ax = -x; } // this has the advantage of preventing the diamond maneuver, also faster if (ay > (ax << 1)) { movement += 2; } else if (ax > (ay << 1)) { movement++; } switch (movement) { case 0: directions[0] = 9; if (ay > ax) { directions[1] = 8; directions[2] = 6; directions[3] = 7; directions[4] = 3; } else { directions[1] = 6; directions[2] = 8; directions[3] = 3; directions[4] = 7; } break; case 1: case 9: directions[0] = 6; if (y < 0) { directions[1] = 3; directions[2] = 9; directions[3] = 2; directions[4] = 8; } else { directions[1] = 9; directions[2] = 3; directions[3] = 8; directions[4] = 2; } break; case 2: case 6: directions[0] = 8; if (x < 0) { directions[1] = 9; directions[2] = 7; directions[3] = 6; directions[4] = 4; } else { directions[1] = 7; directions[2] = 9; directions[3] = 4; directions[4] = 6; } break; case 4: directions[0] = 7; if (ay > ax) { directions[1] = 8; directions[2] = 4; directions[3] = 9; directions[4] = 1; } else { directions[1] = 4; directions[2] = 8; directions[3] = 1; directions[4] = 9; } break; case 5: case 13: directions[0] = 4; if (y < 0) { directions[1] = 1; directions[2] = 7; directions[3] = 2; directions[4] = 8; } else { directions[1] = 7; directions[2] = 1; directions[3] = 8; directions[4] = 2; } break; case 8: directions[0] = 3; if (ay > ax) { directions[1] = 2; directions[2] = 6; directions[3] = 1; directions[4] = 9; } else { directions[1] = 6; directions[2] = 2; directions[3] = 9; directions[4] = 1; } break; case 10: case 14: directions[0] = 2; if (x < 0) { directions[1] = 3; directions[2] = 1; directions[3] = 6; directions[4] = 4; } else { directions[1] = 1; directions[2] = 3; directions[3] = 4; directions[4] = 6; } break; case 12: directions[0] = 1; if (ay > ax) { directions[1] = 2; directions[2] = 4; directions[3] = 3; directions[4] = 7; } else { directions[1] = 4; directions[2] = 2; directions[3] = 7; directions[4] = 3; } break; default: break; } } static void monsterPrintAttackDescription(char *msg, int attack_id) { switch (attack_id) { case 1: printMessage(strcat(msg, "hits you.")); break; case 2: printMessage(strcat(msg, "bites you.")); break; case 3: printMessage(strcat(msg, "claws you.")); break; case 4: printMessage(strcat(msg, "stings you.")); break; case 5: printMessage(strcat(msg, "touches you.")); break; #if 0 case 6: msg_print(strcat(msg, "kicks you.")); break; #endif case 7: printMessage(strcat(msg, "gazes at you.")); break; case 8: printMessage(strcat(msg, "breathes on you.")); break; case 9: printMessage(strcat(msg, "spits on you.")); break; case 10: printMessage(strcat(msg, "makes a horrible wail.")); break; #if 0 case 11: msg_print(strcat(msg, "embraces you.")); break; #endif case 12: printMessage(strcat(msg, "crawls on you.")); break; case 13: printMessage(strcat(msg, "releases a cloud of spores.")); break; case 14: printMessage(strcat(msg, "begs you for money.")); break; case 15: printMessage("You've been slimed!"); break; case 16: printMessage(strcat(msg, "crushes you.")); break; case 17: printMessage(strcat(msg, "tramples you.")); break; case 18: printMessage(strcat(msg, "drools on you.")); break; case 19: switch (randomNumber(9)) { case 1: printMessage(strcat(msg, "insults you!")); break; case 2: printMessage(strcat(msg, "insults your mother!")); break; case 3: printMessage(strcat(msg, "gives you the finger!")); break; case 4: printMessage(strcat(msg, "humiliates you!")); break; case 5: printMessage(strcat(msg, "wets on your leg!")); break; case 6: printMessage(strcat(msg, "defiles you!")); break; case 7: printMessage(strcat(msg, "dances around you!")); break; case 8: printMessage(strcat(msg, "makes obscene gestures!")); break; case 9: printMessage(strcat(msg, "moons you!!!")); break; default: break; } break; case 99: printMessage(strcat(msg, "is repelled.")); break; default: break; } } static void monsterConfuseOnAttack(Creature_t const &creature, Monster_t &monster, int attack_type, vtype_t monster_name, bool visible) { if (py.flags.confuse_monster && attack_type != 99) { printMessage("Your hands stop glowing."); py.flags.confuse_monster = false; vtype_t msg = {'\0'}; if (randomNumber(MON_MAX_LEVELS) < creature.level || ((creature.defenses & config::monsters::defense::CD_NO_SLEEP) != 0)) { (void) sprintf(msg, "%sis unaffected.", monster_name); } else { (void) sprintf(msg, "%sappears confused.", monster_name); if (monster.confused_amount != 0u) { monster.confused_amount += 3; } else { monster.confused_amount = (uint8_t) (2 + randomNumber(16)); } } printMessage(msg); if (visible && !game.character_is_dead && randomNumber(4) == 1) { creature_recall[monster.creature_id].defenses |= creature.defenses & config::monsters::defense::CD_NO_SLEEP; } } } // Make an attack on the player (chuckle.) -RAK- static void monsterAttackPlayer(int monster_id) { // don't beat a dead body! if (game.character_is_dead) { return; } Monster_t &monster = monsters[monster_id]; Creature_t const &creature = creatures_list[monster.creature_id]; vtype_t name = {'\0'}; if (!monster.lit) { (void) strcpy(name, "It "); } else { (void) sprintf(name, "The %s ", creature.name); } vtype_t death_description = {'\0'}; playerDiedFromString(&death_description, creature.name, creature.movement); int attack_counter = 0; for (auto &damage_type_id : creature.damage) { if (damage_type_id == 0 || game.character_is_dead) break; uint8_t attack_type = monster_attacks[damage_type_id].type_id; uint8_t attack_desc = monster_attacks[damage_type_id].description_id; Dice_t dice = monster_attacks[damage_type_id].dice; if (py.flags.protect_evil > 0 && ((creature.defenses & config::monsters::defense::CD_EVIL) != 0) && py.misc.level + 1 > creature.level) { if (monster.lit) { creature_recall[monster.creature_id].defenses |= config::monsters::defense::CD_EVIL; } attack_type = 99; attack_desc = 99; } if (playerTestAttackHits(attack_type, creature.level)) { playerDisturb(1, 0); // can not strcat to name because the creature may have multiple attacks. vtype_t description = {'\0'}; (void) strcpy(description, name); monsterPrintAttackDescription(description, attack_desc); // always fail to notice attack if creature invisible, set notice // and visible here since creature may be visible when attacking // and then teleport afterwards (becoming effectively invisible) bool notice = true; bool visible = true; if (!monster.lit) { visible = false; notice = false; } int damage = diceRoll(dice); notice = executeAttackOnPlayer(creature.level, monster.hp, monster_id, attack_type, damage, death_description, notice); // Moved here from monsterMove, so that monster only confused if it // actually hits. A monster that has been repelled has not hit // the player, so it should not be confused. monsterConfuseOnAttack(creature, monster, attack_desc, name, visible); // increase number of attacks if notice true, or if visible and // had previously noticed the attack (in which case all this does // is help player learn damage), note that in the second case do // not increase attacks if creature repelled (no damage done) if ((notice || (visible && creature_recall[monster.creature_id].attacks[attack_counter] != 0 && attack_type != 99)) && creature_recall[monster.creature_id].attacks[attack_counter] < MAX_UCHAR) { creature_recall[monster.creature_id].attacks[attack_counter]++; } if (game.character_is_dead && creature_recall[monster.creature_id].deaths < MAX_SHORT) { creature_recall[monster.creature_id].deaths++; } } else { if ((attack_desc >= 1 && attack_desc <= 3) || attack_desc == 6) { playerDisturb(1, 0); vtype_t description = {'\0'}; (void) strcpy(description, name); printMessage(strcat(description, "misses you.")); } } if (attack_counter < MON_MAX_ATTACKS - 1) { attack_counter++; } else { break; } } } static void monsterOpenDoor(Tile_t &tile, int16_t monster_hp, uint32_t move_bits, bool &do_turn, bool &do_move, uint32_t &rcmove, int y, int x) { Inventory_t &item = treasure_list[tile.treasure_id]; // Creature can open doors. if ((move_bits & config::monsters::move::CM_OPEN_DOOR) != 0u) { bool door_is_stuck = false; if (item.category_id == TV_CLOSED_DOOR) { do_turn = true; if (item.misc_use == 0) { // Closed doors do_move = true; } else if (item.misc_use > 0) { // Locked doors if (randomNumber((monster_hp + 1) * (50 + item.misc_use)) < 40 * (monster_hp - 10 - item.misc_use)) { item.misc_use = 0; } } else if (item.misc_use < 0) { // Stuck doors if (randomNumber((monster_hp + 1) * (50 - item.misc_use)) < 40 * (monster_hp - 10 + item.misc_use)) { printMessage("You hear a door burst open!"); playerDisturb(1, 0); door_is_stuck = true; do_move = true; } } } else if (item.category_id == TV_SECRET_DOOR) { do_turn = true; do_move = true; } if (do_move) { inventoryItemCopyTo(config::dungeon::objects::OBJ_OPEN_DOOR, item); // 50% chance of breaking door if (door_is_stuck) { item.misc_use = (int16_t) (1 - randomNumber(2)); } tile.feature_id = TILE_CORR_FLOOR; dungeonLiteSpot(Coord_t{y, x}); rcmove |= config::monsters::move::CM_OPEN_DOOR; do_move = false; } } else if (item.category_id == TV_CLOSED_DOOR) { // Creature can not open doors, must bash them do_turn = true; auto abs_misc_use = (int) std::abs((std::intmax_t) item.misc_use); if (randomNumber((monster_hp + 1) * (80 + abs_misc_use)) < 40 * (monster_hp - 20 - abs_misc_use)) { inventoryItemCopyTo(config::dungeon::objects::OBJ_OPEN_DOOR, item); // 50% chance of breaking door item.misc_use = (int16_t) (1 - randomNumber(2)); tile.feature_id = TILE_CORR_FLOOR; dungeonLiteSpot(Coord_t{y, x}); printMessage("You hear a door burst open!"); playerDisturb(1, 0); } } } static void glyphOfWardingProtection(uint16_t creature_id, uint32_t move_bits, bool &do_move, bool &do_turn, int y, int x) { if (randomNumber(config::treasure::OBJECTS_RUNE_PROTECTION) < creatures_list[creature_id].level) { if (y == py.row && x == py.col) { printMessage("The rune of protection is broken!"); } (void) dungeonDeleteObject(Coord_t{y, x});; return; } do_move = false; // If the creature moves only to attack, don't let it // move if the glyph prevents it from attacking if ((move_bits & config::monsters::move::CM_ATTACK_ONLY) != 0u) { do_turn = true; } } static void monsterMovesOnPlayer(Monster_t const &monster, uint8_t creature_id, int monster_id, uint32_t move_bits, bool &do_move, bool &do_turn, uint32_t &rcmove, int y, int x) { if (creature_id == 1) { // if the monster is not lit, must call monsterUpdateVisibility, it // may be faster than character, and hence could have // just moved next to character this same turn. if (!monster.lit) { monsterUpdateVisibility(monster_id); } monsterAttackPlayer(monster_id); do_move = false; do_turn = true; } else if (creature_id > 1 && (y != monster.y || x != monster.x)) { // Creature is attempting to move on other creature? // Creature eats other creatures? if (((move_bits & config::monsters::move::CM_EATS_OTHER) != 0u) && creatures_list[monster.creature_id].kill_exp_value >= creatures_list[monsters[creature_id].creature_id].kill_exp_value) { if (monsters[creature_id].lit) { rcmove |= config::monsters::move::CM_EATS_OTHER; } // It ate an already processed monster. Handle normally. if (monster_id < creature_id) { dungeonDeleteMonster((int) creature_id); } else { // If it eats this monster, an already processed // monster will take its place, causing all kinds // of havoc. Delay the kill a bit. dungeonDeleteMonsterFix1((int) creature_id); } } else { do_move = false; } } } static void monsterAllowedToMove(Monster_t &monster, uint32_t move_bits, bool &do_turn, uint32_t &rcmove, int y, int x) { // Pick up or eat an object if ((move_bits & config::monsters::move::CM_PICKS_UP) != 0u) { uint8_t treasure_id = dg.floor[y][x].treasure_id; if (treasure_id != 0 && treasure_list[treasure_id].category_id <= TV_MAX_OBJECT) { rcmove |= config::monsters::move::CM_PICKS_UP; (void) dungeonDeleteObject(Coord_t{y, x});; } } // Move creature record dungeonMoveCreatureRecord(Coord_t{monster.y, monster.x}, Coord_t{y, x}); if (monster.lit) { monster.lit = false; dungeonLiteSpot(Coord_t{monster.y, monster.x}); } monster.y = (uint8_t) y; monster.x = (uint8_t) x; monster.distance_from_player = (uint8_t) coordDistanceBetween(Coord_t{py.row, py.col}, Coord_t{y, x}); do_turn = true; } // Make the move if possible, five choices -RAK- static void makeMove(int monster_id, int *directions, uint32_t &rcmove) { bool do_turn = false; bool do_move = false; Monster_t &monster = monsters[monster_id]; uint32_t move_bits = creatures_list[monster.creature_id].movement; // Up to 5 attempts at moving, give up. for (int i = 0; !do_turn && i < 5; i++) { // Get new position int y = monster.y; int x = monster.x; (void) playerMovePosition(directions[i], y, x); Tile_t &tile = dg.floor[y][x]; if (tile.feature_id == TILE_BOUNDARY_WALL) { continue; } // Floor is open? if (tile.feature_id <= MAX_OPEN_SPACE) { do_move = true; } else if ((move_bits & config::monsters::move::CM_PHASE) != 0u) { // Creature moves through walls? do_move = true; rcmove |= config::monsters::move::CM_PHASE; } else if (tile.treasure_id != 0) { // Creature can open doors? monsterOpenDoor(tile, monster.hp, move_bits, do_turn, do_move, rcmove, y, x); } // Glyph of warding present? if (do_move && tile.treasure_id != 0 && treasure_list[tile.treasure_id].category_id == TV_VIS_TRAP && treasure_list[tile.treasure_id].sub_category_id == 99) { glyphOfWardingProtection(monster.creature_id, move_bits, do_move, do_turn, y, x); } // Creature has attempted to move on player? if (do_move) { monsterMovesOnPlayer(monster, tile.creature_id, monster_id, move_bits, do_move, do_turn, rcmove, y, x); } // Creature has been allowed move. if (do_move) { monsterAllowedToMove(monster, move_bits, do_turn, rcmove, y, x); } } } static bool monsterCanCastSpells(Monster_t const &monster, uint32_t spells) { // 1 in x chance of casting spell if (randomNumber((int) (spells & config::monsters::spells::CS_FREQ)) != 1) { return false; } // Must be within certain range bool within_range = monster.distance_from_player <= config::monsters::MON_MAX_SPELL_CAST_DISTANCE; // Must have unobstructed Line-Of-Sight bool unobstructed = los(py.row, py.col, monster.y, monster.x); return within_range && unobstructed; } void monsterExecuteCastingOfSpell(Monster_t &monster, int monster_id, int spell_id, uint8_t level, vtype_t monster_name, vtype_t death_description) { int y, x; // Cast the spell. switch (spell_id) { case 5: // Teleport Short spellTeleportAwayMonster(monster_id, 5); break; case 6: // Teleport Long spellTeleportAwayMonster(monster_id, config::monsters::MON_MAX_SIGHT); break; case 7: // Teleport To spellTeleportPlayerTo((int) monster.y, (int) monster.x); break; case 8: // Light Wound if (playerSavingThrow()) { printMessage("You resist the effects of the spell."); } else { playerTakesHit(diceRoll(Dice_t{3, 8}), death_description); } break; case 9: // Serious Wound if (playerSavingThrow()) { printMessage("You resist the effects of the spell."); } else { playerTakesHit(diceRoll(Dice_t{8, 8}), death_description); } break; case 10: // Hold Person if (py.flags.free_action) { printMessage("You are unaffected."); } else if (playerSavingThrow()) { printMessage("You resist the effects of the spell."); } else if (py.flags.paralysis > 0) { py.flags.paralysis += 2; } else { py.flags.paralysis = (int16_t) (randomNumber(5) + 4); } break; case 11: // Cause Blindness if (playerSavingThrow()) { printMessage("You resist the effects of the spell."); } else if (py.flags.blind > 0) { py.flags.blind += 6; } else { py.flags.blind += 12 + randomNumber(3); } break; case 12: // Cause Confuse if (playerSavingThrow()) { printMessage("You resist the effects of the spell."); } else if (py.flags.confused > 0) { py.flags.confused += 2; } else { py.flags.confused = (int16_t) (randomNumber(5) + 3); } break; case 13: // Cause Fear if (playerSavingThrow()) { printMessage("You resist the effects of the spell."); } else if (py.flags.afraid > 0) { py.flags.afraid += 2; } else { py.flags.afraid = (int16_t) (randomNumber(5) + 3); } break; case 14: // Summon Monster (void) strcat(monster_name, "magically summons a monster!"); printMessage(monster_name); y = py.row; x = py.col; // in case compact_monster() is called,it needs monster_id hack_monptr = monster_id; (void) monsterSummon(y, x, false); hack_monptr = -1; monsterUpdateVisibility((int) dg.floor[y][x].creature_id); break; case 15: // Summon Undead (void) strcat(monster_name, "magically summons an undead!"); printMessage(monster_name); y = py.row; x = py.col; // in case compact_monster() is called,it needs monster_id hack_monptr = monster_id; (void) monsterSummonUndead(y, x); hack_monptr = -1; monsterUpdateVisibility((int) dg.floor[y][x].creature_id); break; case 16: // Slow Person if (py.flags.free_action) { printMessage("You are unaffected."); } else if (playerSavingThrow()) { printMessage("You resist the effects of the spell."); } else if (py.flags.slow > 0) { py.flags.slow += 2; } else { py.flags.slow = (int16_t) (randomNumber(5) + 3); } break; case 17: // Drain Mana if (py.misc.current_mana > 0) { playerDisturb(1, 0); vtype_t msg = {'\0'}; (void) sprintf(msg, "%sdraws psychic energy from you!", monster_name); printMessage(msg); if (monster.lit) { (void) sprintf(msg, "%sappears healthier.", monster_name); printMessage(msg); } int r1 = (randomNumber((int) level) >> 1) + 1; if (r1 > py.misc.current_mana) { r1 = py.misc.current_mana; py.misc.current_mana = 0; py.misc.current_mana_fraction = 0; } else { py.misc.current_mana -= r1; } printCharacterCurrentMana(); monster.hp += 6 * (r1); } break; case 20: // Breath Light (void) strcat(monster_name, "breathes lightning."); printMessage(monster_name); spellBreath(py.row, py.col, monster_id, (monster.hp / 4), magic_spell_flags::GF_LIGHTNING, death_description); break; case 21: // Breath Gas (void) strcat(monster_name, "breathes gas."); printMessage(monster_name); spellBreath(py.row, py.col, monster_id, (monster.hp / 3), magic_spell_flags::GF_POISON_GAS, death_description); break; case 22: // Breath Acid (void) strcat(monster_name, "breathes acid."); printMessage(monster_name); spellBreath(py.row, py.col, monster_id, (monster.hp / 3), magic_spell_flags::GF_ACID, death_description); break; case 23: // Breath Frost (void) strcat(monster_name, "breathes frost."); printMessage(monster_name); spellBreath(py.row, py.col, monster_id, (monster.hp / 3), magic_spell_flags::GF_FROST, death_description); break; case 24: // Breath Fire (void) strcat(monster_name, "breathes fire."); printMessage(monster_name); spellBreath(py.row, py.col, monster_id, (monster.hp / 3), magic_spell_flags::GF_FIRE, death_description); break; default: (void) strcat(monster_name, "cast unknown spell."); printMessage(monster_name); } } // Creatures can cast spells too. (Dragon Breath) -RAK- // castSpellGetId = true if creature changes position // return true (took_turn) if creature casts a spell static bool monsterCastSpell(int monster_id) { if (game.character_is_dead) { return false; } Monster_t &monster = monsters[monster_id]; Creature_t const &creature = creatures_list[monster.creature_id]; if (!monsterCanCastSpells(monster, creature.spells)) { return false; } // Creature is going to cast a spell // Check to see if monster should be lit. monsterUpdateVisibility(monster_id); // Describe the attack vtype_t name = {'\0'}; if (monster.lit) { (void) sprintf(name, "The %s ", creature.name); } else { (void) strcpy(name, "It "); } vtype_t death_description = {'\0'}; playerDiedFromString(&death_description, creature.name, creature.movement); // Extract all possible spells into spell_choice int spell_choice[30]; auto spell_flags = (uint32_t) (creature.spells & ~config::monsters::spells::CS_FREQ); int id = 0; while (spell_flags != 0) { spell_choice[id] = getAndClearFirstBit(spell_flags); id++; } // Choose a spell to cast int thrown_spell = spell_choice[randomNumber(id) - 1]; thrown_spell++; // all except spellTeleportAwayMonster() and drain mana spells always disturb if (thrown_spell > 6 && thrown_spell != 17) { playerDisturb(1, 0); } // save some code/data space here, with a small time penalty if ((thrown_spell < 14 && thrown_spell > 6) || thrown_spell == 16) { (void) strcat(name, "casts a spell."); printMessage(name); } monsterExecuteCastingOfSpell(monster, monster_id, thrown_spell, creature.level, name, death_description); if (monster.lit) { creature_recall[monster.creature_id].spells |= 1L << (thrown_spell - 1); if ((creature_recall[monster.creature_id].spells & config::monsters::spells::CS_FREQ) != config::monsters::spells::CS_FREQ) { creature_recall[monster.creature_id].spells++; } if (game.character_is_dead && creature_recall[monster.creature_id].deaths < MAX_SHORT) { creature_recall[monster.creature_id].deaths++; } } return true; } // Places creature adjacent to given location -RAK- // Rats and Flys are fun! bool monsterMultiply(int y, int x, int creature_id, int monster_id) { for (int i = 0; i <= 18; i++) { int pos_y = y - 2 + randomNumber(3); int pos_x = x - 2 + randomNumber(3); // don't create a new creature on top of the old one, that // causes invincible/invisible creatures to appear. if (coordInBounds(Coord_t{pos_y, pos_x}) && (pos_y != y || pos_x != x)) { Tile_t const &tile = dg.floor[pos_y][pos_x]; if (tile.feature_id <= MAX_OPEN_SPACE && tile.treasure_id == 0 && tile.creature_id != 1) { // Creature there already? if (tile.creature_id > 1) { // Some critters are cannibalistic! bool cannibalistic = (creatures_list[creature_id].movement & config::monsters::move::CM_EATS_OTHER) != 0; // Check the experience level -CJS- bool experienced = creatures_list[creature_id].kill_exp_value >= creatures_list[monsters[tile.creature_id].creature_id].kill_exp_value; if (cannibalistic && experienced) { // It ate an already processed monster. Handle * normally. if (monster_id < tile.creature_id) { dungeonDeleteMonster((int) tile.creature_id); } else { // If it eats this monster, an already processed // monster will take its place, causing all kinds // of havoc. Delay the kill a bit. dungeonDeleteMonsterFix1((int) tile.creature_id); } // in case compact_monster() is called, it needs monster_id. hack_monptr = monster_id; // Place_monster() may fail if monster list full. bool result = monsterPlaceNew(pos_y, pos_x, creature_id, false); hack_monptr = -1; if (!result) { return false; } monster_multiply_total++; return monsterMakeVisible(pos_y, pos_x); } } else { // All clear, place a monster // in case compact_monster() is called,it needs monster_id hack_monptr = monster_id; // Place_monster() may fail if monster list full. bool result = monsterPlaceNew(pos_y, pos_x, creature_id, false); hack_monptr = -1; if (!result) { return false; } monster_multiply_total++; return monsterMakeVisible(pos_y, pos_x); } } } } return false; } static void monsterMultiplyCritter(Monster_t const &monster, int monster_id, uint32_t &rcmove) { int counter = 0; for (int y = monster.y - 1; y <= monster.y + 1; y++) { for (int x = monster.x - 1; x <= monster.x + 1; x++) { if (coordInBounds(Coord_t{y, x}) && (dg.floor[y][x].creature_id > 1)) { counter++; } } } // can't call randomNumber with a value of zero, increment // counter to allow creature multiplication. if (counter == 0) { counter++; } if (counter < 4 && randomNumber(counter * config::monsters::MON_MULTIPLY_ADJUST) == 1) { if (monsterMultiply(monster.y, monster.x, monster.creature_id, monster_id)) { rcmove |= config::monsters::move::CM_MULTIPLY; } } } static void monsterMoveOutOfWall(Monster_t const &monster, int monster_id, uint32_t &rcmove) { // If the monster is already dead, don't kill it again! // This can happen for monsters moving faster than the player. They // will get multiple moves, but should not if they die on the first // move. This is only a problem for monsters stuck in rock. if (monster.hp < 0) { return; } int id = 0; int dir = 1; int directions[9]; // Note direction of for loops matches direction of keypad from 1 to 9 // Do not allow attack against the player. // Must cast y-1 to signed int, so that a negative value // of i will fail the comparison. for (int y = monster.y + 1; y >= (monster.y - 1); y--) { for (int x = monster.x - 1; x <= monster.x + 1; x++) { if (dir != 5 && dg.floor[y][x].feature_id <= MAX_OPEN_SPACE && dg.floor[y][x].creature_id != 1) { directions[id++] = dir; } dir++; } } if (id != 0) { // put a random direction first dir = randomNumber(id) - 1; int saved_id = directions[0]; directions[0] = directions[dir]; directions[dir] = saved_id; // this can only fail if directions[0] has a rune of protection makeMove(monster_id, directions, rcmove); } // if still in a wall, let it dig itself out, but also apply some more damage if (dg.floor[monster.y][monster.x].feature_id >= MIN_CAVE_WALL) { // in case the monster dies, may need to callfix1_delete_monster() // instead of delete_monsters() hack_monptr = monster_id; int i = monsterTakeHit(monster_id, diceRoll(Dice_t{8, 8})); hack_monptr = -1; if (i >= 0) { printMessage("You hear a scream muffled by rock!"); displayCharacterExperience(); } else { printMessage("A creature digs itself out from the rock!"); (void) playerTunnelWall((int) monster.y, (int) monster.x, 1, 0); } } } // Undead only get confused from turn undead, so they should flee static void monsterMoveUndead(Creature_t const &creature, int monster_id, uint32_t &rcmove) { int directions[9]; monsterGetMoveDirection(monster_id, directions); directions[0] = 10 - directions[0]; directions[1] = 10 - directions[1]; directions[2] = 10 - directions[2]; directions[3] = randomNumber(9); // May attack only if cornered directions[4] = randomNumber(9); // don't move if it's is not supposed to move! if ((creature.movement & config::monsters::move::CM_ATTACK_ONLY) == 0u) { makeMove(monster_id, directions, rcmove); } } static void monsterMoveConfused(Creature_t const &creature, int monster_id, uint32_t &rcmove) { int directions[9]; directions[0] = randomNumber(9); directions[1] = randomNumber(9); directions[2] = randomNumber(9); directions[3] = randomNumber(9); directions[4] = randomNumber(9); // don't move if it's is not supposed to move! if ((creature.movement & config::monsters::move::CM_ATTACK_ONLY) == 0u) { makeMove(monster_id, directions, rcmove); } } static bool monsterDoMove(int monster_id, uint32_t &rcmove, Monster_t &monster, Creature_t const &creature) { // Creature is confused or undead turned? if (monster.confused_amount != 0u) { if ((creature.defenses & config::monsters::defense::CD_UNDEAD) != 0) { monsterMoveUndead(creature, monster_id, rcmove); } else { monsterMoveConfused(creature, monster_id, rcmove); } monster.confused_amount--; return true; } // Creature may cast a spell if ((creature.spells & config::monsters::spells::CS_FREQ) != 0u) { return monsterCastSpell(monster_id); } return false; } static void monsterMoveRandomly(int monster_id, uint32_t &rcmove, int randomness) { int directions[9]; directions[0] = randomNumber(9); directions[1] = randomNumber(9); directions[2] = randomNumber(9); directions[3] = randomNumber(9); directions[4] = randomNumber(9); rcmove |= randomness; makeMove(monster_id, directions, rcmove); } static void monsterMoveNormally(int monster_id, uint32_t &rcmove) { int directions[9]; if (randomNumber(200) == 1) { directions[0] = randomNumber(9); directions[1] = randomNumber(9); directions[2] = randomNumber(9); directions[3] = randomNumber(9); directions[4] = randomNumber(9); } else { monsterGetMoveDirection(monster_id, directions); } rcmove |= config::monsters::move::CM_MOVE_NORMAL; makeMove(monster_id, directions, rcmove); } static void monsterAttackWithoutMoving(int monster_id, uint32_t &rcmove, uint8_t distance_from_player) { int directions[9]; if (distance_from_player < 2) { monsterGetMoveDirection(monster_id, directions); makeMove(monster_id, directions, rcmove); } else { // Learn that the monster does does not move when // it should have moved, but didn't. rcmove |= config::monsters::move::CM_ATTACK_ONLY; } } // Move the critters about the dungeon -RAK- static void monsterMove(int monster_id, uint32_t &rcmove) { Monster_t &monster = monsters[monster_id]; Creature_t const &creature = creatures_list[monster.creature_id]; // Does the critter multiply? // rest could be negative, to be safe, only use mod with positive values. auto abs_rest_period = (int) std::abs((std::intmax_t) py.flags.rest); if (((creature.movement & config::monsters::move::CM_MULTIPLY) != 0u) && config::monsters::MON_MAX_MULTIPLY_PER_LEVEL >= monster_multiply_total && (abs_rest_period % config::monsters::MON_MULTIPLY_ADJUST) == 0) { monsterMultiplyCritter(monster, monster_id, rcmove); } // if in wall, must immediately escape to a clear area // then monster movement finished if (((creature.movement & config::monsters::move::CM_PHASE) == 0u) && dg.floor[monster.y][monster.x].feature_id >= MIN_CAVE_WALL) { monsterMoveOutOfWall(monster, monster_id, rcmove); return; } if (monsterDoMove(monster_id, rcmove, monster, creature)) { return; } // 75% random movement if (((creature.movement & config::monsters::move::CM_75_RANDOM) != 0u) && randomNumber(100) < 75) { monsterMoveRandomly(monster_id, rcmove, config::monsters::move::CM_75_RANDOM); return; } // 40% random movement if (((creature.movement & config::monsters::move::CM_40_RANDOM) != 0u) && randomNumber(100) < 40) { monsterMoveRandomly(monster_id, rcmove, config::monsters::move::CM_40_RANDOM); return; } // 20% random movement if (((creature.movement & config::monsters::move::CM_20_RANDOM) != 0u) && randomNumber(100) < 20) { monsterMoveRandomly(monster_id, rcmove, config::monsters::move::CM_20_RANDOM); return; } // Normal movement if ((creature.movement & config::monsters::move::CM_MOVE_NORMAL) != 0u) { monsterMoveNormally(monster_id, rcmove); return; } // Attack, but don't move if ((creature.movement & config::monsters::move::CM_ATTACK_ONLY) != 0u) { monsterAttackWithoutMoving(monster_id, rcmove, monster.distance_from_player); return; } if (((creature.movement & config::monsters::move::CM_ONLY_MAGIC) != 0u) && monster.distance_from_player < 2) { // A little hack for Quylthulgs, so that one will eventually // notice that they have no physical attacks. if (creature_recall[monster.creature_id].attacks[0] < MAX_UCHAR) { creature_recall[monster.creature_id].attacks[0]++; } // Another little hack for Quylthulgs, so that one can // eventually learn their speed. if (creature_recall[monster.creature_id].attacks[0] > 20) { creature_recall[monster.creature_id].movement |= config::monsters::move::CM_ONLY_MAGIC; } } } static void memoryUpdateRecall(Monster_t const &monster, bool wake, bool ignore, int rcmove) { if (!monster.lit) { return; } Recall_t &memory = creature_recall[monster.creature_id]; if (wake) { if (memory.wake < MAX_UCHAR) { memory.wake++; } } else if (ignore) { if (memory.ignore < MAX_UCHAR) { memory.ignore++; } } memory.movement |= rcmove; } static void monsterAttackingUpdate(Monster_t &monster, int monster_id, int moves) { for (int i = moves; i > 0; i--) { bool wake = false; bool ignore = false; uint32_t rcmove = 0; // Monsters trapped in rock must be given a turn also, // so that they will die/dig out immediately. if (monster.lit || monster.distance_from_player <= creatures_list[monster.creature_id].area_affect_radius || (((creatures_list[monster.creature_id].movement & config::monsters::move::CM_PHASE) == 0u) && dg.floor[monster.y][monster.x].feature_id >= MIN_CAVE_WALL)) { if (monster.sleep_count > 0) { if (py.flags.aggravate) { monster.sleep_count = 0; } else if ((py.flags.rest == 0 && py.flags.paralysis < 1) || (randomNumber(50) == 1)) { int notice = randomNumber(1024); if (notice * notice * notice <= (1L << (29 - py.misc.stealth_factor))) { monster.sleep_count -= (100 / monster.distance_from_player); if (monster.sleep_count > 0) { ignore = true; } else { wake = true; // force it to be exactly zero monster.sleep_count = 0; } } } } if (monster.stunned_amount != 0) { // NOTE: Balrog = 100*100 = 10000, it always recovers instantly if (randomNumber(5000) < creatures_list[monster.creature_id].level * creatures_list[monster.creature_id].level) { monster.stunned_amount = 0; } else { monster.stunned_amount--; } if (monster.stunned_amount == 0) { if (monster.lit) { vtype_t msg = {'\0'}; (void) sprintf(msg, "The %s ", creatures_list[monster.creature_id].name); printMessage(strcat(msg, "recovers and glares at you.")); } } } if ((monster.sleep_count == 0) && (monster.stunned_amount == 0)) { monsterMove(monster_id, rcmove); } } monsterUpdateVisibility(monster_id); memoryUpdateRecall(monster, wake, ignore, rcmove); } } // Creatures movement and attacking are done from here -RAK- void updateMonsters(bool attack) { // Process the monsters for (int id = next_free_monster_id - 1; id >= config::monsters::MON_MIN_INDEX_ID && !game.character_is_dead; id--) { Monster_t &monster = monsters[id]; // Get rid of an eaten/breathed on monster. Note: Be sure not to // process this monster. This is necessary because we can't delete // monsters while scanning the monsters here. if (monster.hp < 0) { dungeonDeleteMonsterFix2(id); continue; } monster.distance_from_player = (uint8_t) coordDistanceBetween(Coord_t{py.row, py.col}, Coord_t{monster.y, monster.x}); // Attack is argument passed to CREATURE if (attack) { int moves = monsterMovementRate(monster.speed); if (moves <= 0) { monsterUpdateVisibility(id); } else { monsterAttackingUpdate(monster, id, moves); } } else { monsterUpdateVisibility(id); } // Get rid of an eaten/breathed on monster. This is necessary because // we can't delete monsters while scanning the monsters here. // This monster may have been killed during monsterMove(). if (monster.hp < 0) { dungeonDeleteMonsterFix2(id); continue; } } } // Decreases monsters hit points and deletes monster if needed. // (Picking on my babies.) -RAK- int monsterTakeHit(int monster_id, int damage) { Monster_t &monster = monsters[monster_id]; Creature_t const &creature = creatures_list[monster.creature_id]; monster.sleep_count = 0; monster.hp -= damage; if (monster.hp >= 0) { return -1; } uint32_t treasure_flags = monsterDeath((int) monster.y, (int) monster.x, creature.movement); Recall_t &memory = creature_recall[monster.creature_id]; if ((py.flags.blind < 1 && monster.lit) || ((creature.movement & config::monsters::move::CM_WIN) != 0u)) { auto tmp = (uint32_t) ((memory.movement & config::monsters::move::CM_TREASURE) >> config::monsters::move::CM_TR_SHIFT); if (tmp > ((treasure_flags & config::monsters::move::CM_TREASURE) >> config::monsters::move::CM_TR_SHIFT)) { treasure_flags = (uint32_t) ((treasure_flags & ~config::monsters::move::CM_TREASURE) | (tmp << config::monsters::move::CM_TR_SHIFT)); } memory.movement = (uint32_t) ((memory.movement & ~config::monsters::move::CM_TREASURE) | treasure_flags); if (memory.kills < MAX_SHORT) { memory.kills++; } } playerGainKillExperience(creature); // can't call displayCharacterExperience() here, as that would result in "new level" // message appearing before "monster dies" message. int m_take_hit = monster.creature_id; // in case this is called from within updateMonsters(), this is a horrible // hack, the monsters/updateMonsters() code needs to be rewritten. if (hack_monptr < monster_id) { dungeonDeleteMonster(monster_id); } else { dungeonDeleteMonsterFix1(monster_id); } return m_take_hit; } static int monsterDeathItemDropType(uint32_t flags) { int object; if ((flags & config::monsters::move::CM_CARRY_OBJ) != 0u) { object = 1; } else { object = 0; } if ((flags & config::monsters::move::CM_CARRY_GOLD) != 0u) { object += 2; } if ((flags & config::monsters::move::CM_SMALL_OBJ) != 0u) { object += 4; } return object; } static int monsterDeathItemDropCount(uint32_t flags) { int count = 0; if (((flags & config::monsters::move::CM_60_RANDOM) != 0u) && randomNumber(100) < 60) { count++; } if (((flags & config::monsters::move::CM_90_RANDOM) != 0u) && randomNumber(100) < 90) { count++; } if ((flags & config::monsters::move::CM_1D2_OBJ) != 0u) { count += randomNumber(2); } if ((flags & config::monsters::move::CM_2D2_OBJ) != 0u) { count += diceRoll(Dice_t{2, 2}); } if ((flags & config::monsters::move::CM_4D2_OBJ) != 0u) { count += diceRoll(Dice_t{4, 2}); } return count; } // Allocates objects upon a creatures death -RAK- // Oh well, another creature bites the dust. Reward the // victor based on flags set in the main creature record. // // Returns a mask of bits from the given flags which indicates what the // monster is seen to have dropped. This may be added to monster memory. uint32_t monsterDeath(int y, int x, uint32_t flags) { int item_type = monsterDeathItemDropType(flags); int item_count = monsterDeathItemDropCount(flags); uint32_t dropped_item_id = 0; if (item_count > 0) { dropped_item_id = (uint32_t) dungeonSummonObject(Coord_t{y, x}, item_count, item_type); } // maybe the player died in mid-turn if (((flags & config::monsters::move::CM_WIN) != 0u) && !game.character_is_dead) { game.total_winner = true; printCharacterWinner(); printMessage("*** CONGRATULATIONS *** You have won the game."); printMessage("You cannot save this game, but you may retire when ready."); } if (dropped_item_id == 0) { return 0; } uint32_t return_flags = 0; if ((dropped_item_id & 255) != 0u) { return_flags |= config::monsters::move::CM_CARRY_OBJ; if ((item_type & 0x04) != 0) { return_flags |= config::monsters::move::CM_SMALL_OBJ; } } if (dropped_item_id >= 256) { return_flags |= config::monsters::move::CM_CARRY_GOLD; } int number_of_items = (dropped_item_id % 256) + (dropped_item_id / 256); number_of_items = number_of_items << config::monsters::move::CM_TR_SHIFT; return return_flags | number_of_items; } void printMonsterActionText(const std::string &name, const std::string &action) { printMessage((name + " " + action).c_str()); } std::string monsterNameDescription(const std::string &real_name, bool is_lit) { if (is_lit) { return "The " + real_name; } return "It"; } // Sleep creatures adjacent to player -RAK- bool monsterSleep(int y, int x) { bool asleep = false; for (int row = y - 1; row <= y + 1 && row < MAX_HEIGHT; row++) { for (int col = x - 1; col <= x + 1 && col < MAX_WIDTH; col++) { uint8_t monster_id = dg.floor[row][col].creature_id; if (monster_id <= 1) { continue; } Monster_t &monster = monsters[monster_id]; Creature_t const &creature = creatures_list[monster.creature_id]; auto name = monsterNameDescription(creature.name, monster.lit); if (randomNumber(MON_MAX_LEVELS) < creature.level || ((creature.defenses & config::monsters::defense::CD_NO_SLEEP) != 0)) { if (monster.lit && ((creature.defenses & config::monsters::defense::CD_NO_SLEEP) != 0)) { creature_recall[monster.creature_id].defenses |= config::monsters::defense::CD_NO_SLEEP; } printMonsterActionText(name, "is unaffected."); } else { monster.sleep_count = 500; asleep = true; printMonsterActionText(name, "falls asleep."); } } } return asleep; } static bool executeAttackOnPlayer(uint8_t creature_level, int16_t &monster_hp, int monster_id, int attack_type, int damage, vtype_t death_description, bool noticed) { int item_pos_start; int item_pos_end; int32_t gold; switch (attack_type) { case 1: // Normal attack // round half-way case down damage -= ((py.misc.ac + py.misc.magical_ac) * damage) / 200; playerTakesHit(damage, death_description); break; case 2: // Lose Strength playerTakesHit(damage, death_description); if (py.flags.sustain_str) { printMessage("You feel weaker for a moment, but it passes."); } else if (randomNumber(2) == 1) { printMessage("You feel weaker."); (void) playerStatRandomDecrease(py_attrs::A_STR); } else { noticed = false; } break; case 3: // Confusion attack playerTakesHit(damage, death_description); if (randomNumber(2) == 1) { if (py.flags.confused < 1) { printMessage("You feel confused."); py.flags.confused += randomNumber((int) creature_level); } else { noticed = false; } py.flags.confused += 3; } else { noticed = false; } break; case 4: // Fear attack playerTakesHit(damage, death_description); if (playerSavingThrow()) { printMessage("You resist the effects!"); } else if (py.flags.afraid < 1) { printMessage("You are suddenly afraid!"); py.flags.afraid += 3 + randomNumber((int) creature_level); } else { py.flags.afraid += 3; noticed = false; } break; case 5: // Fire attack printMessage("You are enveloped in flames!"); damageFire(damage, death_description); break; case 6: // Acid attack printMessage("You are covered in acid!"); damageAcid(damage, death_description); break; case 7: // Cold attack printMessage("You are covered with frost!"); damageCold(damage, death_description); break; case 8: // Lightning attack printMessage("Lightning strikes you!"); damageLightningBolt(damage, death_description); break; case 9: // Corrosion attack printMessage("A stinging red gas swirls about you."); damageCorrodingGas(death_description); playerTakesHit(damage, death_description); break; case 10: // Blindness attack playerTakesHit(damage, death_description); if (py.flags.blind < 1) { py.flags.blind += 10 + randomNumber((int) creature_level); printMessage("Your eyes begin to sting."); } else { py.flags.blind += 5; noticed = false; } break; case 11: // Paralysis attack playerTakesHit(damage, death_description); if (playerSavingThrow()) { printMessage("You resist the effects!"); } else if (py.flags.paralysis < 1) { if (py.flags.free_action) { printMessage("You are unaffected."); } else { py.flags.paralysis = (int16_t) (randomNumber((int) creature_level) + 3); printMessage("You are paralyzed."); } } else { noticed = false; } break; case 12: // Steal Money if (py.flags.paralysis < 1 && randomNumber(124) < py.stats.used[py_attrs::A_DEX]) { printMessage("You quickly protect your money pouch!"); } else { gold = (py.misc.au / 10) + randomNumber(25); if (gold > py.misc.au) { py.misc.au = 0; } else { py.misc.au -= gold; } printMessage("Your purse feels lighter."); printCharacterGoldValue(); } if (randomNumber(2) == 1) { printMessage("There is a puff of smoke!"); spellTeleportAwayMonster(monster_id, config::monsters::MON_MAX_SIGHT); } break; case 13: // Steal Object if (py.flags.paralysis < 1 && randomNumber(124) < py.stats.used[py_attrs::A_DEX]) { printMessage("You grab hold of your backpack!"); } else { inventoryDestroyItem(randomNumber(py.unique_inventory_items) - 1); printMessage("Your backpack feels lighter."); } if (randomNumber(2) == 1) { printMessage("There is a puff of smoke!"); spellTeleportAwayMonster(monster_id, config::monsters::MON_MAX_SIGHT); } break; case 14: // Poison playerTakesHit(damage, death_description); printMessage("You feel very sick."); py.flags.poisoned += randomNumber((int) creature_level) + 5; break; case 15: // Lose dexterity playerTakesHit(damage, death_description); if (py.flags.sustain_dex) { printMessage("You feel clumsy for a moment, but it passes."); } else { printMessage("You feel more clumsy."); (void) playerStatRandomDecrease(py_attrs::A_DEX); } break; case 16: // Lose constitution playerTakesHit(damage, death_description); if (py.flags.sustain_con) { printMessage("Your body resists the effects of the disease."); } else { printMessage("Your health is damaged!"); (void) playerStatRandomDecrease(py_attrs::A_CON); } break; case 17: // Lose intelligence playerTakesHit(damage, death_description); printMessage("You have trouble thinking clearly."); if (py.flags.sustain_int) { printMessage("But your mind quickly clears."); } else { (void) playerStatRandomDecrease(py_attrs::A_INT); } break; case 18: // Lose wisdom playerTakesHit(damage, death_description); if (py.flags.sustain_wis) { printMessage("Your wisdom is sustained."); } else { printMessage("Your wisdom is drained."); (void) playerStatRandomDecrease(py_attrs::A_WIS); } break; case 19: // Lose experience printMessage("You feel your life draining away!"); spellLoseEXP(damage + (py.misc.exp / 100) * config::monsters::MON_PLAYER_EXP_DRAINED_PER_HIT); break; case 20: // Aggravate monster (void) spellAggravateMonsters(20); break; case 21: // Disenchant if (executeDisenchantAttack()) { printMessage("There is a static feeling in the air."); playerRecalculateBonuses(); } else { noticed = false; } break; case 22: // Eat food if (inventoryFindRange(TV_FOOD, TV_NEVER, item_pos_start, item_pos_end)) { inventoryDestroyItem(item_pos_start); printMessage("It got at your rations!"); } else { noticed = false; } break; case 23: // Eat light noticed = inventoryDiminishLightAttack(noticed); break; case 24: // Eat charges noticed = inventoryDiminishChargesAttack(creature_level, monster_hp, noticed); break; case 99: noticed = false; break; default: noticed = false; break; } return noticed; } umoria-5.7.10+20181022/src/player.cpp0000644000175000017500000014602413363422757015557 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. // Player specific variable definitions #include "headers.h" // Player record for most player related info Player_t py = Player_t{}; // Handle teleport traps bool teleport_player; static void playerResetFlags() { py.flags.see_invisible = false; py.flags.teleport = false; py.flags.free_action = false; py.flags.slow_digest = false; py.flags.aggravate = false; py.flags.sustain_str = false; py.flags.sustain_int = false; py.flags.sustain_wis = false; py.flags.sustain_con = false; py.flags.sustain_dex = false; py.flags.sustain_chr = false; py.flags.resistant_to_fire = false; py.flags.resistant_to_acid = false; py.flags.resistant_to_cold = false; py.flags.regenerate_hp = false; py.flags.resistant_to_light = false; py.flags.free_fall = false; } bool playerIsMale() { return py.misc.gender; } void playerSetGender(bool is_male) { py.misc.gender = is_male; } const char *playerGetGenderLabel() { if (playerIsMale()) { return "Male"; } return "Female"; } // Given direction "dir", returns new row, column location -RAK- bool playerMovePosition(int dir, int &new_y, int &new_x) { int new_row; int new_col; switch (dir) { case 1: new_row = new_y + 1; new_col = new_x - 1; break; case 2: new_row = new_y + 1; new_col = new_x; break; case 3: new_row = new_y + 1; new_col = new_x + 1; break; case 4: new_row = new_y; new_col = new_x - 1; break; case 5: new_row = new_y; new_col = new_x; break; case 6: new_row = new_y; new_col = new_x + 1; break; case 7: new_row = new_y - 1; new_col = new_x - 1; break; case 8: new_row = new_y - 1; new_col = new_x; break; case 9: new_row = new_y - 1; new_col = new_x + 1; break; default: new_row = 0; new_col = 0; break; } bool can_move = false; if (new_row >= 0 && new_row < dg.height && new_col >= 0 && new_col < dg.width) { new_y = new_row; new_x = new_col; can_move = true; } return can_move; } // Teleport the player to a new location -RAK- void playerTeleport(int new_distance) { int new_y, new_x; do { new_y = randomNumber(dg.height) - 1; new_x = randomNumber(dg.width) - 1; while (coordDistanceBetween(Coord_t{new_y, new_x}, Coord_t{py.row, py.col}) > new_distance) { new_y += (py.row - new_y) / 2; new_x += (py.col - new_x) / 2; } } while (dg.floor[new_y][new_x].feature_id >= MIN_CLOSED_SPACE || dg.floor[new_y][new_x].creature_id >= 2); dungeonMoveCreatureRecord(Coord_t{py.row, py.col}, Coord_t{new_y, new_x}); for (int y = py.row - 1; y <= py.row + 1; y++) { for (int x = py.col - 1; x <= py.col + 1; x++) { dg.floor[y][x].temporary_light = false; dungeonLiteSpot(Coord_t{y, x}); } } dungeonLiteSpot(Coord_t{py.row, py.col}); py.row = (int16_t) new_y; py.col = (int16_t) new_x; dungeonResetView(); updateMonsters(false); teleport_player = false; } // Returns true if player has no light -RAK- bool playerNoLight() { return !dg.floor[py.row][py.col].temporary_light && !dg.floor[py.row][py.col].permanent_light; } // Something happens to disturb the player. -CJS- // The first arg indicates a major disturbance, which affects search. // The second arg indicates a light change. void playerDisturb(int major_disturbance, int light_disturbance) { game.command_count = 0; if ((major_disturbance != 0) && ((py.flags.status & config::player::status::PY_SEARCH) != 0u)) { playerSearchOff(); } if (py.flags.rest != 0) { playerRestOff(); } if ((light_disturbance != 0) || (py.running_tracker != 0)) { py.running_tracker = 0; dungeonResetView(); } flushInputBuffer(); } // Search Mode enhancement -RAK- void playerSearchOn() { playerChangeSpeed(1); py.flags.status |= config::player::status::PY_SEARCH; printCharacterMovementState(); printCharacterSpeed(); py.flags.food_digested++; } void playerSearchOff() { dungeonResetView(); playerChangeSpeed(-1); py.flags.status &= ~config::player::status::PY_SEARCH; printCharacterMovementState(); printCharacterSpeed(); py.flags.food_digested--; } // Resting allows a player to safely restore his hp -RAK- void playerRestOn() { int rest_num; if (game.command_count > 0) { rest_num = game.command_count; game.command_count = 0; } else { rest_num = 0; vtype_t rest_str = {'\0'}; putStringClearToEOL("Rest for how long? ", Coord_t{0, 0}); if (getStringInput(rest_str, Coord_t{0, 19}, 5)) { if (rest_str[0] == '*') { rest_num = -MAX_SHORT; } else { (void) stringToNumber(rest_str, rest_num); } } } // check for reasonable value, must be positive number // in range of a short, or must be -MAX_SHORT if (rest_num == -MAX_SHORT || (rest_num > 0 && rest_num <= MAX_SHORT)) { if ((py.flags.status & config::player::status::PY_SEARCH) != 0u) { playerSearchOff(); } py.flags.rest = (int16_t) rest_num; py.flags.status |= config::player::status::PY_REST; printCharacterMovementState(); py.flags.food_digested--; putStringClearToEOL("Press any key to stop resting...", Coord_t{0, 0}); putQIO(); return; } // Something went wrong if (rest_num != 0) { printMessage("Invalid rest count."); } messageLineClear(); game.player_free_turn = true; } void playerRestOff() { py.flags.rest = 0; py.flags.status &= ~config::player::status::PY_REST; printCharacterMovementState(); // flush last message, or delete "press any key" message printMessage(CNIL); py.flags.food_digested++; } // For "DIED_FROM" string void playerDiedFromString(vtype_t *description, const char *monster_name, uint32_t move) { if ((move & config::monsters::move::CM_WIN) != 0u) { (void) sprintf(*description, "The %s", monster_name); } else if (isVowel(monster_name[0])) { (void) sprintf(*description, "an %s", monster_name); } else { (void) sprintf(*description, "a %s", monster_name); } } bool playerTestAttackHits(int attack_id, uint8_t level) { bool success = false; switch (attack_id) { case 1: // Normal attack if (playerTestBeingHit(60, (int) level, 0, py.misc.ac + py.misc.magical_ac, CLASS_MISC_HIT)) { success = true; } break; case 2: // Lose Strength if (playerTestBeingHit(-3, (int) level, 0, py.misc.ac + py.misc.magical_ac, CLASS_MISC_HIT)) { success = true; } break; case 3: // Confusion attack if (playerTestBeingHit(10, (int) level, 0, py.misc.ac + py.misc.magical_ac, CLASS_MISC_HIT)) { success = true; } break; case 4: // Fear attack if (playerTestBeingHit(10, (int) level, 0, py.misc.ac + py.misc.magical_ac, CLASS_MISC_HIT)) { success = true; } break; case 5: // Fire attack if (playerTestBeingHit(10, (int) level, 0, py.misc.ac + py.misc.magical_ac, CLASS_MISC_HIT)) { success = true; } break; case 6: // Acid attack if (playerTestBeingHit(0, (int) level, 0, py.misc.ac + py.misc.magical_ac, CLASS_MISC_HIT)) { success = true; } break; case 7: // Cold attack if (playerTestBeingHit(10, (int) level, 0, py.misc.ac + py.misc.magical_ac, CLASS_MISC_HIT)) { success = true; } break; case 8: // Lightning attack if (playerTestBeingHit(10, (int) level, 0, py.misc.ac + py.misc.magical_ac, CLASS_MISC_HIT)) { success = true; } break; case 9: // Corrosion attack if (playerTestBeingHit(0, (int) level, 0, py.misc.ac + py.misc.magical_ac, CLASS_MISC_HIT)) { success = true; } break; case 10: // Blindness attack if (playerTestBeingHit(2, (int) level, 0, py.misc.ac + py.misc.magical_ac, CLASS_MISC_HIT)) { success = true; } break; case 11: // Paralysis attack if (playerTestBeingHit(2, (int) level, 0, py.misc.ac + py.misc.magical_ac, CLASS_MISC_HIT)) { success = true; } break; case 12: // Steal Money if (playerTestBeingHit(5, (int) level, 0, (int) py.misc.level, CLASS_MISC_HIT) && py.misc.au > 0) { success = true; } break; case 13: // Steal Object if (playerTestBeingHit(2, (int) level, 0, (int) py.misc.level, CLASS_MISC_HIT) && py.unique_inventory_items > 0) { success = true; } break; case 14: // Poison if (playerTestBeingHit(5, (int) level, 0, py.misc.ac + py.misc.magical_ac, CLASS_MISC_HIT)) { success = true; } break; case 15: // Lose dexterity if (playerTestBeingHit(0, (int) level, 0, py.misc.ac + py.misc.magical_ac, CLASS_MISC_HIT)) { success = true; } break; case 16: // Lose constitution if (playerTestBeingHit(0, (int) level, 0, py.misc.ac + py.misc.magical_ac, CLASS_MISC_HIT)) { success = true; } break; case 17: // Lose intelligence if (playerTestBeingHit(2, (int) level, 0, py.misc.ac + py.misc.magical_ac, CLASS_MISC_HIT)) { success = true; } break; case 18: // Lose wisdom if (playerTestBeingHit(2, (int) level, 0, py.misc.ac + py.misc.magical_ac, CLASS_MISC_HIT)) { success = true; } break; case 19: // Lose experience if (playerTestBeingHit(5, (int) level, 0, py.misc.ac + py.misc.magical_ac, CLASS_MISC_HIT)) { success = true; } break; case 20: // Aggravate monsters success = true; break; case 21: // Disenchant if (playerTestBeingHit(20, (int) level, 0, py.misc.ac + py.misc.magical_ac, CLASS_MISC_HIT)) { success = true; } break; case 22: // Eat food if (playerTestBeingHit(5, (int) level, 0, py.misc.ac + py.misc.magical_ac, CLASS_MISC_HIT)) { success = true; } break; case 23: // Eat light if (playerTestBeingHit(5, (int) level, 0, py.misc.ac + py.misc.magical_ac, CLASS_MISC_HIT)) { success = true; } break; case 24: // Eat charges // check to make sure an object exists if (playerTestBeingHit(15, (int) level, 0, py.misc.ac + py.misc.magical_ac, CLASS_MISC_HIT) && py.unique_inventory_items > 0) { success = true; } break; case 99: success = true; break; default: break; } return success; } // Changes speed of monsters relative to player -RAK- // Note: When the player is sped up or slowed down, I simply change // the speed of all the monsters. This greatly simplified the logic. void playerChangeSpeed(int speed) { py.flags.speed += speed; py.flags.status |= config::player::status::PY_SPEED; for (int i = next_free_monster_id - 1; i >= config::monsters::MON_MIN_INDEX_ID; i--) { monsters[i].speed += speed; } } // Player bonuses -RAK- // // When an item is worn or taken off, this re-adjusts the player bonuses. // Factor = 1 : wear // Factor = -1 : removed // // Only calculates properties with cumulative effect. Properties that // depend on everything being worn are recalculated by playerRecalculateBonuses() -CJS- void playerAdjustBonusesForItem(Inventory_t const &item, int factor) { int amount = item.misc_use * factor; if ((item.flags & config::treasure::flags::TR_STATS) != 0u) { for (int i = 0; i < 6; i++) { if (((1 << i) & item.flags) != 0u) { playerStatBoost(i, amount); } } } if ((item.flags & config::treasure::flags::TR_SEARCH) != 0u) { py.misc.chance_in_search += amount; py.misc.fos -= amount; } if ((item.flags & config::treasure::flags::TR_STEALTH) != 0u) { py.misc.stealth_factor += amount; } if ((item.flags & config::treasure::flags::TR_SPEED) != 0u) { playerChangeSpeed(-amount); } if (((item.flags & config::treasure::flags::TR_BLIND) != 0u) && factor > 0) { py.flags.blind += 1000; } if (((item.flags & config::treasure::flags::TR_TIMID) != 0u) && factor > 0) { py.flags.afraid += 50; } if ((item.flags & config::treasure::flags::TR_INFRA) != 0u) { py.flags.see_infra += amount; } } static void playerRecalculateBonusesFromInventory() { for (int i = player_equipment::EQUIPMENT_WIELD; i < player_equipment::EQUIPMENT_LIGHT; i++) { Inventory_t const &item = inventory[i]; if (item.category_id != TV_NOTHING) { py.misc.plusses_to_hit += item.to_hit; // Bows can't damage. -CJS- if (item.category_id != TV_BOW) { py.misc.plusses_to_damage += item.to_damage; } py.misc.magical_ac += item.to_ac; py.misc.ac += item.ac; if (spellItemIdentified(item)) { py.misc.display_to_hit += item.to_hit; // Bows can't damage. -CJS- if (item.category_id != TV_BOW) { py.misc.display_to_damage += item.to_damage; } py.misc.display_to_ac += item.to_ac; py.misc.display_ac += item.ac; } else if ((item.flags & config::treasure::flags::TR_CURSED) == 0u) { // Base AC values should always be visible, // as long as the item is not cursed. py.misc.display_ac += item.ac; } } } } static void playerRecalculateSustainStatsFromInventory() { for (int i = player_equipment::EQUIPMENT_WIELD; i < player_equipment::EQUIPMENT_LIGHT; i++) { if ((inventory[i].flags & config::treasure::flags::TR_SUST_STAT) == 0u) { continue; } switch (inventory[i].misc_use) { case 1: py.flags.sustain_str = true; break; case 2: py.flags.sustain_int = true; break; case 3: py.flags.sustain_wis = true; break; case 4: py.flags.sustain_con = true; break; case 5: py.flags.sustain_dex = true; break; case 6: py.flags.sustain_chr = true; break; default: break; } } } // Recalculate the effect of all the stuff we use. -CJS- void playerRecalculateBonuses() { // Temporarily adjust food_digested if (py.flags.slow_digest) { py.flags.food_digested++; } if (py.flags.regenerate_hp) { py.flags.food_digested -= 3; } int savedDisplayAC = py.misc.display_ac; playerResetFlags(); // Real values py.misc.plusses_to_hit = (int16_t) playerToHitAdjustment(); py.misc.plusses_to_damage = (int16_t) playerDamageAdjustment(); py.misc.magical_ac = (int16_t) playerArmorClassAdjustment(); py.misc.ac = 0; // Display values py.misc.display_to_hit = py.misc.plusses_to_hit; py.misc.display_to_damage = py.misc.plusses_to_damage; py.misc.display_ac = 0; py.misc.display_to_ac = py.misc.magical_ac; playerRecalculateBonusesFromInventory(); py.misc.display_ac += py.misc.display_to_ac; if (py.weapon_is_heavy) { py.misc.display_to_hit += (py.stats.used[py_attrs::A_STR] * 15 - inventory[player_equipment::EQUIPMENT_WIELD].weight); } // Add in temporary spell increases if (py.flags.invulnerability > 0) { py.misc.ac += 100; py.misc.display_ac += 100; } if (py.flags.blessed > 0) { py.misc.ac += 2; py.misc.display_ac += 2; } if (py.flags.detect_invisible > 0) { py.flags.see_invisible = true; } // can't print AC here because might be in a store if (savedDisplayAC != py.misc.display_ac) { py.flags.status |= config::player::status::PY_ARMOR; } uint32_t item_flags = inventoryCollectAllItemFlags(); if ((item_flags & config::treasure::flags::TR_SLOW_DIGEST) != 0u) { py.flags.slow_digest = true; } if ((item_flags & config::treasure::flags::TR_AGGRAVATE) != 0u) { py.flags.aggravate = true; } if ((item_flags & config::treasure::flags::TR_TELEPORT) != 0u) { py.flags.teleport = true; } if ((item_flags & config::treasure::flags::TR_REGEN) != 0u) { py.flags.regenerate_hp = true; } if ((item_flags & config::treasure::flags::TR_RES_FIRE) != 0u) { py.flags.resistant_to_fire = true; } if ((item_flags & config::treasure::flags::TR_RES_ACID) != 0u) { py.flags.resistant_to_acid = true; } if ((item_flags & config::treasure::flags::TR_RES_COLD) != 0u) { py.flags.resistant_to_cold = true; } if ((item_flags & config::treasure::flags::TR_FREE_ACT) != 0u) { py.flags.free_action = true; } if ((item_flags & config::treasure::flags::TR_SEE_INVIS) != 0u) { py.flags.see_invisible = true; } if ((item_flags & config::treasure::flags::TR_RES_LIGHT) != 0u) { py.flags.resistant_to_light = true; } if ((item_flags & config::treasure::flags::TR_FFALL) != 0u) { py.flags.free_fall = true; } playerRecalculateSustainStatsFromInventory(); // Reset food_digested values if (py.flags.slow_digest) { py.flags.food_digested--; } if (py.flags.regenerate_hp) { py.flags.food_digested += 3; } } // Remove item from equipment list -RAK- void playerTakeOff(int item_id, int pack_position_id) { py.flags.status |= config::player::status::PY_STR_WGT; Inventory_t &item = inventory[item_id]; py.inventory_weight -= item.weight * item.items_count; py.equipment_count--; const char *p = nullptr; if (item_id == player_equipment::EQUIPMENT_WIELD || item_id == player_equipment::EQUIPMENT_AUX) { p = "Was wielding "; } else if (item_id == player_equipment::EQUIPMENT_LIGHT) { p = "Light source was "; } else { p = "Was wearing "; } obj_desc_t description = {'\0'}; itemDescription(description, item, true); obj_desc_t msg = {'\0'}; if (pack_position_id >= 0) { (void) sprintf(msg, "%s%s (%c)", p, description, 'a' + pack_position_id); } else { (void) sprintf(msg, "%s%s", p, description); } printMessage(msg); // For secondary weapon if (item_id != player_equipment::EQUIPMENT_AUX) { playerAdjustBonusesForItem(item, -1); } inventoryItemCopyTo(config::dungeon::objects::OBJ_NOTHING, item); } // Attacker's level and plusses, defender's AC -RAK- bool playerTestBeingHit(int base_to_hit, int level, int plus_to_hit, int armor_class, int attack_type_id) { playerDisturb(1, 0); // `plus_to_hit` could be less than 0 if player wielding weapon too heavy for them int hit_chance = base_to_hit + plus_to_hit * BTH_PER_PLUS_TO_HIT_ADJUST + (level * class_level_adj[py.misc.class_id][attack_type_id]); // always miss 1 out of 20, always hit 1 out of 20 int die = randomNumber(20); // normal hit return (die != 1 && (die == 20 || (hit_chance > 0 && randomNumber(hit_chance) > armor_class))); } // Decreases players hit points and sets game.character_is_dead flag if necessary -RAK- void playerTakesHit(int damage, const char *creature_name_label) { if (py.flags.invulnerability > 0) { damage = 0; } py.misc.current_hp -= damage; if (py.misc.current_hp >= 0) { printCharacterCurrentHitPoints(); return; } if (!game.character_is_dead) { game.character_is_dead = true; (void) strcpy(game.character_died_from, creature_name_label); game.total_winner = false; } dg.generate_new_level = true; } // Searches for hidden things. -RAK- void playerSearch(int y, int x, int chance) { if (py.flags.confused > 0) { chance = chance / 10; } if (py.flags.blind > 0 || playerNoLight()) { chance = chance / 10; } if (py.flags.image > 0) { chance = chance / 10; } for (int i = y - 1; i <= y + 1; i++) { for (int j = x - 1; j <= x + 1; j++) { // always coordInBounds() here if (randomNumber(100) >= chance) { continue; } if (dg.floor[i][j].treasure_id == 0) { continue; } // Search for hidden objects Inventory_t &item = treasure_list[dg.floor[i][j].treasure_id]; if (item.category_id == TV_INVIS_TRAP) { // Trap on floor? obj_desc_t description = {'\0'}; itemDescription(description, item, true); obj_desc_t msg = {'\0'}; (void) sprintf(msg, "You have found %s", description); printMessage(msg); trapChangeVisibility(Coord_t{i, j}); playerEndRunning(); } else if (item.category_id == TV_SECRET_DOOR) { // Secret door? printMessage("You have found a secret door."); trapChangeVisibility(Coord_t{i, j}); playerEndRunning(); } else if (item.category_id == TV_CHEST) { // Chest is trapped? // mask out the treasure bits if ((item.flags & config::treasure::chests::CH_TRAPPED) > 1) { if (!spellItemIdentified(item)) { spellItemIdentifyAndRemoveRandomInscription(item); printMessage("You have discovered a trap on the chest!"); } else { printMessage("The chest is trapped!"); } } } } } } // Computes current weight limit -RAK- int playerCarryingLoadLimit() { int weight_cap = py.stats.used[py_attrs::A_STR] * config::player::PLAYER_WEIGHT_CAP + py.misc.weight; if (weight_cap > 3000) { weight_cap = 3000; } return weight_cap; } // Are we strong enough for the current pack and weapon? -CJS- void playerStrength() { Inventory_t const &item = inventory[player_equipment::EQUIPMENT_WIELD]; if (item.category_id != TV_NOTHING && py.stats.used[py_attrs::A_STR] * 15 < item.weight) { if (!py.weapon_is_heavy) { printMessage("You have trouble wielding such a heavy weapon."); py.weapon_is_heavy = true; playerRecalculateBonuses(); } } else if (py.weapon_is_heavy) { py.weapon_is_heavy = false; if (item.category_id != TV_NOTHING) { printMessage("You are strong enough to wield your weapon."); } playerRecalculateBonuses(); } int limit = playerCarryingLoadLimit(); if (limit < py.inventory_weight) { limit = py.inventory_weight / (limit + 1); } else { limit = 0; } if (py.pack_heaviness != limit) { if (py.pack_heaviness < limit) { printMessage("Your pack is so heavy that it slows you down."); } else { printMessage("You move more easily under the weight of your pack."); } playerChangeSpeed(limit - py.pack_heaviness); py.pack_heaviness = (int16_t) limit; } py.flags.status &= ~config::player::status::PY_STR_WGT; } static bool playerCanRead() { if (py.flags.blind > 0) { printMessage("You can't see to read your spell book!"); return false; } if (playerNoLight()) { printMessage("You have no light to read by."); return false; } return true; } static int lastKnownSpell() { for (int last_known = 0; last_known < 32; last_known++) { if (py.flags.spells_learned_order[last_known] == 99) { return last_known; } } // We should never actually reach this, but just in case... -MRC- return 0; } static uint32_t playerDetermineLearnableSpells() { uint32_t spell_flag = 0; for (int i = 0; i < py.unique_inventory_items; i++) { if (inventory[i].category_id == TV_MAGIC_BOOK) { spell_flag |= inventory[i].flags; } } return spell_flag; } // gain spells when player wants to -JW- void playerGainSpells() { // Priests don't need light because they get spells from their god, so only // fail when can't see if player has config::spells::SPELL_TYPE_MAGE spells. This check is done below. if (py.flags.confused > 0) { printMessage("You are too confused."); return; } int new_spells = py.flags.new_spells_to_learn; int diff_spells = 0; // TODO(cook) move access to `magic_spells[]` directly to the for loop it's used in, below? Spell_t *spells = &magic_spells[py.misc.class_id - 1][0]; int stat, offset; if (classes[py.misc.class_id].class_to_use_mage_spells == config::spells::SPELL_TYPE_MAGE) { // People with config::spells::SPELL_TYPE_MAGE spells can't learn spell_bank if they can't read their books. if (!playerCanRead()) { return; } stat = py_attrs::A_INT; offset = config::spells::NAME_OFFSET_SPELLS; } else { stat = py_attrs::A_WIS; offset = config::spells::NAME_OFFSET_PRAYERS; } int last_known = lastKnownSpell(); if (new_spells == 0) { vtype_t tmp_str = {'\0'}; (void) sprintf(tmp_str, "You can't learn any new %ss!", (stat == py_attrs::A_INT ? "spell" : "prayer")); printMessage(tmp_str); game.player_free_turn = true; return; } uint32_t spell_flag; // determine which spells player can learn // mages need the book to learn a spell, priests do not need the book if (stat == py_attrs::A_INT) { spell_flag = playerDetermineLearnableSpells(); } else { spell_flag = 0x7FFFFFFF; } // clear bits for spells already learned spell_flag &= ~py.flags.spells_learnt; int spell_id = 0; int spell_bank[31]; uint32_t mask = 0x1; for (int i = 0; spell_flag != 0u; mask <<= 1, i++) { if ((spell_flag & mask) != 0u) { spell_flag &= ~mask; if (spells[i].level_required <= py.misc.level) { spell_bank[spell_id] = i; spell_id++; } } } if (new_spells > spell_id) { printMessage("You seem to be missing a book."); diff_spells = new_spells - spell_id; new_spells = spell_id; } if (new_spells == 0) { // do nothing } else if (stat == py_attrs::A_INT) { // get to choose which mage spells will be learned terminalSaveScreen(); displaySpellsList(spell_bank, spell_id, false, -1); char query; while ((new_spells != 0) && getCommand("Learn which spell?", query)) { int c = query - 'a'; // test j < 23 in case i is greater than 22, only 22 spells // are actually shown on the screen, so limit choice to those if (c >= 0 && c < spell_id && c < 22) { new_spells--; py.flags.spells_learnt |= 1L << spell_bank[c]; py.flags.spells_learned_order[last_known++] = (uint8_t) spell_bank[c]; for (; c <= spell_id - 1; c++) { spell_bank[c] = spell_bank[c + 1]; } spell_id--; eraseLine(Coord_t{c + 1, 31}); displaySpellsList(spell_bank, spell_id, false, -1); } else { terminalBellSound(); } } terminalRestoreScreen(); } else { // pick a prayer at random while (new_spells != 0) { int id = randomNumber(spell_id) - 1; py.flags.spells_learnt |= 1L << spell_bank[id]; py.flags.spells_learned_order[last_known++] = (uint8_t) spell_bank[id]; vtype_t tmp_str = {'\0'}; (void) sprintf(tmp_str, "You have learned the prayer of %s.", spell_names[spell_bank[id] + offset]); printMessage(tmp_str); for (; id <= spell_id - 1; id++) { spell_bank[id] = spell_bank[id + 1]; } spell_id--; new_spells--; } } py.flags.new_spells_to_learn = (uint8_t) (new_spells + diff_spells); if (py.flags.new_spells_to_learn == 0) { py.flags.status |= config::player::status::PY_STUDY; } // set the mana for first level characters when they learn their first spell. if (py.misc.mana == 0) { playerGainMana(stat); } } static int newMana(int stat) { int levels = py.misc.level - classes[py.misc.class_id].min_level_for_spell_casting + 1; switch (playerStatAdjustmentWisdomIntelligence(stat)) { case 1: case 2: return 1 * levels; case 3: return 3 * levels / 2; case 4: return 2 * levels; case 5: return 5 * levels / 2; case 6: return 3 * levels; case 7: return 4 * levels; default: return 0; } } // Gain some mana if you know at least one spell -RAK- void playerGainMana(int stat) { if (py.flags.spells_learnt != 0) { int new_mana = newMana(stat); // increment mana by one, so that first level chars have 2 mana if (new_mana > 0) { new_mana++; } // mana can be zero when creating character if (py.misc.mana != new_mana) { if (py.misc.mana != 0) { // change current mana proportionately to change of max mana, // divide first to avoid overflow, little loss of accuracy int32_t value = (((int32_t) py.misc.current_mana << 16) + py.misc.current_mana_fraction) / py.misc.mana * new_mana; py.misc.current_mana = (int16_t) (value >> 16); py.misc.current_mana_fraction = (uint16_t) (value & 0xFFFF); } else { py.misc.current_mana = (int16_t) new_mana; py.misc.current_mana_fraction = 0; } py.misc.mana = (int16_t) new_mana; // can't print mana here, may be in store or inventory mode py.flags.status |= config::player::status::PY_MANA; } } else if (py.misc.mana != 0) { py.misc.mana = 0; py.misc.current_mana = 0; // can't print mana here, may be in store or inventory mode py.flags.status |= config::player::status::PY_MANA; } } // Critical hits, Nasty way to die. -RAK- int playerWeaponCriticalBlow(int weapon_weight, int plus_to_hit, int damage, int attack_type_id) { int critical = damage; // Weight of weapon, plusses to hit, and character level all // contribute to the chance of a critical if (randomNumber(5000) <= weapon_weight + 5 * plus_to_hit + (class_level_adj[py.misc.class_id][attack_type_id] * py.misc.level)) { weapon_weight += randomNumber(650); if (weapon_weight < 400) { critical = 2 * damage + 5; printMessage("It was a good hit! (x2 damage)"); } else if (weapon_weight < 700) { critical = 3 * damage + 10; printMessage("It was an excellent hit! (x3 damage)"); } else if (weapon_weight < 900) { critical = 4 * damage + 15; printMessage("It was a superb hit! (x4 damage)"); } else { critical = 5 * damage + 20; printMessage("It was a *GREAT* hit! (x5 damage)"); } } return critical; } // Saving throws for player character. -RAK- bool playerSavingThrow() { int class_level_adjustment = class_level_adj[py.misc.class_id][py_class_level_adj::CLASS_SAVE] * py.misc.level / 3; int saving = py.misc.saving_throw + playerStatAdjustmentWisdomIntelligence(py_attrs::A_WIS) + class_level_adjustment; return randomNumber(100) <= saving; } void playerGainKillExperience(Creature_t const &creature) { int32_t exp = creature.kill_exp_value * creature.level; int32_t quotient = exp / py.misc.level; int32_t remainder = exp % py.misc.level; remainder *= 0x10000L; remainder /= py.misc.level; remainder += py.misc.exp_fraction; if (remainder >= 0x10000L) { quotient++; py.misc.exp_fraction = (uint16_t) (remainder - 0x10000L); } else { py.misc.exp_fraction = (uint16_t) remainder; } py.misc.exp += quotient; } static void playerCalculateToHitBlows(int weapon_id, int weapon_weight, int &blows, int &total_to_hit) { if (weapon_id != TV_NOTHING) { // Proper weapon blows = playerAttackBlows(weapon_weight, total_to_hit); } else { // Bare hands? blows = 2; total_to_hit = -3; } // Fix for arrows if (weapon_id >= TV_SLING_AMMO && weapon_id <= TV_SPIKE) { blows = 1; } total_to_hit += py.misc.plusses_to_hit; } static int playerCalculateBaseToHit(bool creatureLit, int tot_tohit) { if (creatureLit) { return py.misc.bth; } // creature not lit, make it more difficult to hit int bth; bth = py.misc.bth / 2; bth -= tot_tohit * (BTH_PER_PLUS_TO_HIT_ADJUST - 1); bth -= py.misc.level * class_level_adj[py.misc.class_id][py_class_level_adj::CLASS_BTH] / 2; return bth; } // Player attacks a (poor, defenseless) creature -RAK- static void playerAttackMonster(int y, int x) { int creature_id = dg.floor[y][x].creature_id; Monster_t &monster = monsters[creature_id]; Creature_t const &creature = creatures_list[monster.creature_id]; Inventory_t &item = inventory[player_equipment::EQUIPMENT_WIELD]; monster.sleep_count = 0; // Does the player know what they're fighting? vtype_t name = {'\0'}; if (!monster.lit) { (void) strcpy(name, "it"); } else { (void) sprintf(name, "the %s", creature.name); } int blows, total_to_hit; playerCalculateToHitBlows(item.category_id, item.weight, blows, total_to_hit); int base_to_hit = playerCalculateBaseToHit(monster.lit, total_to_hit); int damage; vtype_t msg = {'\0'}; // Loop for number of blows, trying to hit the critter. // Note: blows will always be greater than 0 at the start of the loop -MRC- for (int i = blows; i > 0; i--) { if (!playerTestBeingHit(base_to_hit, (int) py.misc.level, total_to_hit, (int) creature.ac, py_class_level_adj::CLASS_BTH)) { (void) sprintf(msg, "You miss %s.", name); printMessage(msg); continue; } (void) sprintf(msg, "You hit %s.", name); printMessage(msg); if (item.category_id != TV_NOTHING) { damage = diceRoll(item.damage); damage = itemMagicAbilityDamage(item, damage, monster.creature_id); damage = playerWeaponCriticalBlow((int) item.weight, total_to_hit, damage, py_class_level_adj::CLASS_BTH); } else { // Bare hands!? damage = diceRoll(Dice_t{1, 1}); damage = playerWeaponCriticalBlow(1, 0, damage, py_class_level_adj::CLASS_BTH); } damage += py.misc.plusses_to_damage; if (damage < 0) { damage = 0; } if (py.flags.confuse_monster) { py.flags.confuse_monster = false; printMessage("Your hands stop glowing."); if (((creature.defenses & config::monsters::defense::CD_NO_SLEEP) != 0) || randomNumber(MON_MAX_LEVELS) < creature.level) { (void) sprintf(msg, "%s is unaffected.", name); } else { (void) sprintf(msg, "%s appears confused.", name); if (monster.confused_amount != 0u) { monster.confused_amount += 3; } else { monster.confused_amount = (uint8_t) (2 + randomNumber(16)); } } printMessage(msg); if (monster.lit && randomNumber(4) == 1) { creature_recall[monster.creature_id].defenses |= creature.defenses & config::monsters::defense::CD_NO_SLEEP; } } // See if we done it in. if (monsterTakeHit(creature_id, damage) >= 0) { (void) sprintf(msg, "You have slain %s.", name); printMessage(msg); displayCharacterExperience(); return; } // Use missiles up if (item.category_id >= TV_SLING_AMMO && item.category_id <= TV_SPIKE) { item.items_count--; py.inventory_weight -= item.weight; py.flags.status |= config::player::status::PY_STR_WGT; if (item.items_count == 0) { py.equipment_count--; playerAdjustBonusesForItem(item, -1); inventoryItemCopyTo(config::dungeon::objects::OBJ_NOTHING, item); playerRecalculateBonuses(); } } } } static int16_t playerLockPickingSkill() { int16_t skill = py.misc.disarm; skill += 2; skill *= playerDisarmAdjustment(); skill += playerStatAdjustmentWisdomIntelligence(py_attrs::A_INT); skill += class_level_adj[py.misc.class_id][py_class_level_adj::CLASS_DISARM] * py.misc.level / 3; return skill; } static void openClosedDoor(int y, int x) { Tile_t &tile = dg.floor[y][x]; Inventory_t &item = treasure_list[tile.treasure_id]; if (item.misc_use > 0) { // It's locked. if (py.flags.confused > 0) { printMessage("You are too confused to pick the lock."); } else if (playerLockPickingSkill() - item.misc_use > randomNumber(100)) { printMessage("You have picked the lock."); py.misc.exp++; displayCharacterExperience(); item.misc_use = 0; } else { printMessageNoCommandInterrupt("You failed to pick the lock."); } } else if (item.misc_use < 0) { // It's stuck printMessage("It appears to be stuck."); } if (item.misc_use == 0) { inventoryItemCopyTo(config::dungeon::objects::OBJ_OPEN_DOOR, treasure_list[tile.treasure_id]); tile.feature_id = TILE_CORR_FLOOR; dungeonLiteSpot(Coord_t{y, x}); game.command_count = 0; } } static void openClosedChest(int y, int x) { Tile_t const &tile = dg.floor[y][x]; Inventory_t &item = treasure_list[tile.treasure_id]; bool success = false; if ((item.flags & config::treasure::chests::CH_LOCKED) != 0u) { if (py.flags.confused > 0) { printMessage("You are too confused to pick the lock."); } else if (playerLockPickingSkill() - item.depth_first_found > randomNumber(100)) { printMessage("You have picked the lock."); py.misc.exp += item.depth_first_found; displayCharacterExperience(); success = true; } else { printMessageNoCommandInterrupt("You failed to pick the lock."); } } else { success = true; } if (success) { item.flags &= ~config::treasure::chests::CH_LOCKED; item.special_name_id = special_name_ids::SN_EMPTY; spellItemIdentifyAndRemoveRandomInscription(item); item.cost = 0; } // Was chest still trapped? if ((item.flags & config::treasure::chests::CH_LOCKED) != 0) { return; } // Oh, yes it was... (Snicker) chestTrap(y, x); if (tile.treasure_id != 0) { // Chest treasure is allocated as if a creature had been killed. // clear the cursed chest/monster win flag, so that people // can not win by opening a cursed chest treasure_list[tile.treasure_id].flags &= ~config::treasure::flags::TR_CURSED; (void) monsterDeath(y, x, treasure_list[tile.treasure_id].flags); treasure_list[tile.treasure_id].flags = 0; } } // Opens a closed door or closed chest. -RAK- void playerOpenClosedObject() { int dir; if (!getDirectionWithMemory(CNIL, dir)) { return; } int y = py.row; int x = py.col; (void) playerMovePosition(dir, y, x); bool no_object = false; Tile_t const &tile = dg.floor[y][x]; Inventory_t const &item = treasure_list[tile.treasure_id]; if (tile.creature_id > 1 && tile.treasure_id != 0 && (item.category_id == TV_CLOSED_DOOR || item.category_id == TV_CHEST)) { objectBlockedByMonster(tile.creature_id); } else if (tile.treasure_id != 0) { if (item.category_id == TV_CLOSED_DOOR) { openClosedDoor(y, x); } else if (item.category_id == TV_CHEST) { openClosedChest(y, x); } else { no_object = true; } } else { no_object = true; } if (no_object) { game.player_free_turn = true; printMessage("I do not see anything you can open there."); } } // Closes an open door. -RAK- void playerCloseDoor() { int dir; if (!getDirectionWithMemory(CNIL, dir)) { return; } int y = py.row; int x = py.col; (void) playerMovePosition(dir, y, x); Tile_t &tile = dg.floor[y][x]; Inventory_t &item = treasure_list[tile.treasure_id]; bool no_object = false; if (tile.treasure_id != 0) { if (item.category_id == TV_OPEN_DOOR) { if (tile.creature_id == 0) { if (item.misc_use == 0) { inventoryItemCopyTo(config::dungeon::objects::OBJ_CLOSED_DOOR, item); tile.feature_id = TILE_BLOCKED_FLOOR; dungeonLiteSpot(Coord_t{y, x}); } else { printMessage("The door appears to be broken."); } } else { objectBlockedByMonster(tile.creature_id); } } else { no_object = true; } } else { no_object = true; } if (no_object) { game.player_free_turn = true; printMessage("I do not see anything you can close there."); } } // Tunneling through real wall: 10, 11, 12 -RAK- // Used by TUNNEL and WALL_TO_MUD bool playerTunnelWall(int y, int x, int digging_ability, int digging_chance) { if (digging_ability <= digging_chance) { return false; } Tile_t &tile = dg.floor[y][x]; if (tile.perma_lit_room) { // Should become a room space, check to see whether // it should be TILE_LIGHT_FLOOR or TILE_DARK_FLOOR. bool found = false; for (int yy = y - 1; yy <= y + 1 && yy < MAX_HEIGHT; yy++) { for (int xx = x - 1; xx <= x + 1 && xx < MAX_WIDTH; xx++) { if (dg.floor[yy][xx].feature_id <= MAX_CAVE_ROOM) { tile.feature_id = dg.floor[yy][xx].feature_id; tile.permanent_light = dg.floor[yy][xx].permanent_light; found = true; break; } } } if (!found) { tile.feature_id = TILE_CORR_FLOOR; tile.permanent_light = false; } } else { // should become a corridor space tile.feature_id = TILE_CORR_FLOOR; tile.permanent_light = false; } tile.field_mark = false; if (coordInsidePanel(Coord_t{y, x}) && (tile.temporary_light || tile.permanent_light) && tile.treasure_id != 0) { printMessage("You have found something!"); } dungeonLiteSpot(Coord_t{y, x}); return true; } // let the player attack the creature void playerAttackPosition(int y, int x) { // Is a Coward? if (py.flags.afraid > 0) { printMessage("You are too afraid!"); return; } playerAttackMonster(y, x); } // check to see if know any spells greater than level, eliminate them static void eliminateKnownSpellsGreaterThanLevel(Spell_t *msp_ptr, const char *p, int offset) { uint32_t mask = 0x80000000L; for (int i = 31; mask != 0u; mask >>= 1, i--) { if ((mask & py.flags.spells_learnt) != 0u) { if (msp_ptr[i].level_required > py.misc.level) { py.flags.spells_learnt &= ~mask; py.flags.spells_forgotten |= mask; vtype_t msg = {'\0'}; (void) sprintf(msg, "You have forgotten the %s of %s.", p, spell_names[i + offset]); printMessage(msg); } else { break; } } } } static int numberOfSpellsAllowed(int stat) { int levels = py.misc.level - classes[py.misc.class_id].min_level_for_spell_casting + 1; int allowed; switch (playerStatAdjustmentWisdomIntelligence(stat)) { case 1: case 2: case 3: allowed = 1 * levels; break; case 4: case 5: allowed = 3 * levels / 2; break; case 6: allowed = 2 * levels; break; case 7: allowed = 5 * levels / 2; break; default: allowed = 0; break; } return allowed; } static int numberOfSpellsKnown() { int known = 0; for (uint32_t mask = 0x1; mask != 0u; mask <<= 1) { if ((mask & py.flags.spells_learnt) != 0u) { known++; } } return known; } // remember forgotten spells while forgotten spells exist of new_spells_to_learn positive, // remember the spells in the order that they were learned static int rememberForgottenSpells(Spell_t *msp_ptr, int allowedSpells, int newSpells, const char *p, int offset) { uint32_t mask; for (int n = 0; ((py.flags.spells_forgotten != 0u) && (newSpells != 0) && (n < allowedSpells) && (n < 32)); n++) { // orderID is (i+1)th spell learned int orderID = py.flags.spells_learned_order[n]; // shifting by amounts greater than number of bits in long gives // an undefined result, so don't shift for unknown spells if (orderID == 99) { mask = 0x0; } else { mask = (uint32_t) (1L << orderID); } if ((mask & py.flags.spells_forgotten) != 0u) { if (msp_ptr[orderID].level_required <= py.misc.level) { newSpells--; py.flags.spells_forgotten &= ~mask; py.flags.spells_learnt |= mask; vtype_t msg = {'\0'}; (void) sprintf(msg, "You have remembered the %s of %s.", p, spell_names[orderID + offset]); printMessage(msg); } else { allowedSpells++; } } } return newSpells; } // determine which spells player can learn must check all spells here, // in gain_spell() we actually check if the books are present static int learnableSpells(Spell_t *msp_ptr, int newSpells) { auto spell_flag = (uint32_t) (0x7FFFFFFFL & ~py.flags.spells_learnt); int id = 0; uint32_t mask = 0x1; for (int i = 0; spell_flag != 0u; mask <<= 1, i++) { if ((spell_flag & mask) != 0u) { spell_flag &= ~mask; if (msp_ptr[i].level_required <= py.misc.level) { id++; } } } if (newSpells > id) { newSpells = id; } return newSpells; } // forget spells until new_spells_to_learn zero or no more spells know, // spells are forgotten in the opposite order that they were learned // NOTE: newSpells is always a negative value static void forgetSpells(int newSpells, const char *p, int offset) { uint32_t mask; for (int i = 31; (newSpells != 0) && (py.flags.spells_learnt != 0u); i--) { // orderID is the (i+1)th spell learned int orderID = py.flags.spells_learned_order[i]; // shifting by amounts greater than number of bits in long gives // an undefined result, so don't shift for unknown spells if (orderID == 99) { mask = 0x0; } else { mask = (uint32_t) (1L << orderID); } if ((mask & py.flags.spells_learnt) != 0u) { py.flags.spells_learnt &= ~mask; py.flags.spells_forgotten |= mask; newSpells++; vtype_t msg = {'\0'}; (void) sprintf(msg, "You have forgotten the %s of %s.", p, spell_names[orderID + offset]); printMessage(msg); } } } // calculate number of spells player should have, and // learn forget spells until that number is met -JEW- void playerCalculateAllowedSpellsCount(int stat) { Spell_t &spell = magic_spells[py.misc.class_id - 1][0]; const char *magic_type_str = nullptr; int offset; if (stat == py_attrs::A_INT) { magic_type_str = "spell"; offset = config::spells::NAME_OFFSET_SPELLS; } else { magic_type_str = "prayer"; offset = config::spells::NAME_OFFSET_PRAYERS; } // check to see if know any spells greater than level, eliminate them eliminateKnownSpellsGreaterThanLevel(&spell, magic_type_str, offset); // calc number of spells allowed int num_allowed = numberOfSpellsAllowed(stat); int num_known = numberOfSpellsKnown(); int new_spells = num_allowed - num_known; if (new_spells > 0) { new_spells = rememberForgottenSpells(&spell, num_allowed, new_spells, magic_type_str, offset); // If `new_spells_to_learn` is still greater than zero if (new_spells > 0) { new_spells = learnableSpells(&spell, new_spells); } } else if (new_spells < 0) { forgetSpells(new_spells, magic_type_str, offset); new_spells = 0; } if (new_spells != py.flags.new_spells_to_learn) { if (new_spells > 0 && py.flags.new_spells_to_learn == 0) { vtype_t msg = {'\0'}; (void) sprintf(msg, "You can learn some new %ss now.", magic_type_str); printMessage(msg); } py.flags.new_spells_to_learn = (uint8_t) new_spells; py.flags.status |= config::player::status::PY_STUDY; } } char *playerRankTitle() { const char *p = nullptr; if (py.misc.level < 1) { p = "Babe in arms"; } else if (py.misc.level <= PLAYER_MAX_LEVEL) { p = class_rank_titles[py.misc.class_id][py.misc.level - 1]; } else if (playerIsMale()) { p = "**KING**"; } else { p = "**QUEEN**"; } return (char *) p; } umoria-5.7.10+20181022/src/character.cpp0000644000175000017500000004107213363422757016214 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. // Generate a new player character #include "headers.h" // Generates character's stats -JWT- static void characterGenerateStats() { int total; int dice[18]; do { total = 0; for (auto i = 0; i < 18; i++) { // Roll 3,4,5 sided dice once each dice[i] = randomNumber(3 + i % 3); total += dice[i]; } } while (total <= 42 || total >= 54); for (auto i = 0; i < 6; i++) { py.stats.max[i] = uint8_t(5 + dice[3 * i] + dice[3 * i + 1] + dice[3 * i + 2]); } } static uint8_t decrementStat(int16_t const adjustment, uint8_t const current_stat) { auto stat = current_stat; for (auto i = 0; i > adjustment; i--) { if (stat > 108) { stat--; } else if (stat > 88) { stat += -randomNumber(6) - 2; } else if (stat > 18) { stat += -randomNumber(15) - 5; if (stat < 18) { stat = 18; } } else if (stat > 3) { stat--; } } return stat; } static uint8_t incrementStat(int16_t const adjustment, uint8_t const current_stat) { auto stat = current_stat; for (auto i = 0; i < adjustment; i++) { if (stat < 18) { stat++; } else if (stat < 88) { stat += randomNumber(15) + 5; } else if (stat < 108) { stat += randomNumber(6) + 2; } else if (stat < 118) { stat++; } } return stat; } // Changes stats by given amount -JWT- // During character creation we adjust player stats based // on their Race and Class...with a little randomness! static uint8_t createModifyPlayerStat(uint8_t const stat, int16_t const adjustment) { if (adjustment < 0) { return decrementStat(adjustment, stat); } return incrementStat(adjustment, stat); } // generate all stats and modify for race. needed in a separate // module so looping of character selection would be allowed -RGM- static void characterGenerateStatsAndRace() { Race_t const &race = character_races[py.misc.race_id]; characterGenerateStats(); py.stats.max[py_attrs::A_STR] = createModifyPlayerStat(py.stats.max[py_attrs::A_STR], race.str_adjustment); py.stats.max[py_attrs::A_INT] = createModifyPlayerStat(py.stats.max[py_attrs::A_INT], race.int_adjustment); py.stats.max[py_attrs::A_WIS] = createModifyPlayerStat(py.stats.max[py_attrs::A_WIS], race.wis_adjustment); py.stats.max[py_attrs::A_DEX] = createModifyPlayerStat(py.stats.max[py_attrs::A_DEX], race.dex_adjustment); py.stats.max[py_attrs::A_CON] = createModifyPlayerStat(py.stats.max[py_attrs::A_CON], race.con_adjustment); py.stats.max[py_attrs::A_CHR] = createModifyPlayerStat(py.stats.max[py_attrs::A_CHR], race.chr_adjustment); py.misc.level = 1; for (auto i = 0; i < 6; i++) { py.stats.current[i] = py.stats.max[i]; playerSetAndUseStat(i); } py.misc.chance_in_search = race.search_chance_base; py.misc.bth = race.base_to_hit; py.misc.bth_with_bows = race.base_to_hit_bows; py.misc.fos = race.fos; py.misc.stealth_factor = race.stealth; py.misc.saving_throw = race.saving_throw_base; py.misc.hit_die = race.hit_points_base; py.misc.plusses_to_damage = (int16_t) playerDamageAdjustment(); py.misc.plusses_to_hit = (int16_t) playerToHitAdjustment(); py.misc.magical_ac = 0; py.misc.ac = (int16_t) playerArmorClassAdjustment(); py.misc.experience_factor = race.exp_factor_base; py.flags.see_infra = race.infra_vision; } // Prints a list of the available races: Human, Elf, etc., // shown during the character creation screens. static void displayCharacterRaces() { clearToBottom(20); putString("Choose a race (? for Help):", Coord_t{20, 2}); auto y = 21; auto x = 2; for (auto i = 0; i < PLAYER_MAX_RACES; i++) { char description[80]; (void) sprintf(description, "%c) %s", i + 'a', character_races[i].name); putString(description, Coord_t{y, x}); x += 15; if (x > 70) { x = 2; y++; } } } // Allows player to select a race -JWT- static void characterChooseRace() { displayCharacterRaces(); int race_id = 0; while (true) { moveCursor(Coord_t{20, 30}); char const input = getKeyInput(); race_id = input - 'a'; if (race_id < PLAYER_MAX_RACES && race_id >= 0) { break; } if (input == '?') { displayTextHelpFile(config::files::welcome_screen); } else { terminalBellSound(); } } py.misc.race_id = (uint8_t) race_id; putString(character_races[race_id].name, Coord_t{3, 15}); } // Will print the history of a character -JWT- static void displayCharacterHistory() { putString("Character Background", Coord_t{14, 27}); for (auto i = 0; i < 4; i++) { putStringClearToEOL(py.misc.history[i], Coord_t{i + 15, 10}); } } // Clear the previous history strings static void playerClearHistory() { for (auto &entry : py.misc.history) { entry[0] = '\0'; } } // Get the racial history, determines social class -RAK- // // Assumptions: // - Each race has init history beginning at (race-1)*3+1 // - All history parts are in ascending order static void characterGetHistory() { auto history_id = py.misc.race_id * 3 + 1; auto social_class = randomNumber(4); char history_block[240]; history_block[0] = '\0'; auto background_id = 0; // Get a block of history text do { bool flag = false; while (!flag) { if (character_backgrounds[background_id].chart == history_id) { int test_roll = randomNumber(100); while (test_roll > character_backgrounds[background_id].roll) { background_id++; } Background_t const &background = character_backgrounds[background_id]; (void) strcat(history_block, background.info); social_class += background.bonus - 50; if (history_id > background.next) { background_id = 0; } history_id = background.next; flag = true; } else { background_id++; } } } while (history_id >= 1); playerClearHistory(); // Process block of history text for pretty output int cursor_start = 0; auto cursor_end = (int) strlen(history_block) - 1; while (history_block[cursor_end] == ' ') { cursor_end--; } int line_number = 0; int new_cursor_start = 0; int current_cursor_position = 0; bool flag = false; while (!flag) { while (history_block[cursor_start] == ' ') { cursor_start++; } current_cursor_position = cursor_end - cursor_start + 1; if (current_cursor_position > 60) { current_cursor_position = 60; while (history_block[cursor_start + current_cursor_position - 1] != ' ') { current_cursor_position--; } new_cursor_start = cursor_start + current_cursor_position; while (history_block[cursor_start + current_cursor_position - 1] == ' ') { current_cursor_position--; } } else { flag = true; } (void) strncpy(py.misc.history[line_number], &history_block[cursor_start], (size_t) current_cursor_position); py.misc.history[line_number][current_cursor_position] = '\0'; line_number++; cursor_start = new_cursor_start; } // Compute social class for player if (social_class > 100) { social_class = 100; } else if (social_class < 1) { social_class = 1; } py.misc.social_class = (int16_t) social_class; } // Gets the character's gender -JWT- static void characterSetGender() { clearToBottom(20); putString("Choose a sex (? for Help):", Coord_t{20, 2}); putString("m) Male f) Female", Coord_t{21, 2}); bool is_set = false; while (!is_set) { moveCursor(Coord_t{20, 29}); // speed not important here char const input = getKeyInput(); if (input == 'f' || input == 'F') { playerSetGender(false); putString("Female", Coord_t{4, 15}); is_set = true; } else if (input == 'm' || input == 'M') { playerSetGender(true); putString("Male", Coord_t{4, 15}); is_set = true; } else if (input == '?') { displayTextHelpFile(config::files::welcome_screen); } else { terminalBellSound(); } } } // Computes character's age, height, and weight -JWT- static void characterSetAgeHeightWeight() { Race_t const &race = character_races[py.misc.race_id]; py.misc.age = uint16_t(race.base_age + randomNumber(race.max_age)); int height_base, height_mod, weight_base, weight_mod; if (playerIsMale()) { height_base = race.male_height_base; height_mod = race.male_height_mod; weight_base = race.male_weight_base; weight_mod = race.male_weight_mod; } else { height_base = race.female_height_base; height_mod = race.female_height_mod; weight_base = race.female_weight_base; weight_mod = race.female_weight_mod; } py.misc.height = (uint16_t) randomNumberNormalDistribution(height_base, height_mod); py.misc.weight = (uint16_t) randomNumberNormalDistribution(weight_base, weight_mod); py.misc.disarm = race.disarm_chance_base + playerDisarmAdjustment(); } // Prints the classes for a given race: Rogue, Mage, Priest, etc., // shown during the character creation screens. static int displayRaceClasses(int const race_id, uint8_t *class_list) { auto y = 21; auto x = 2; auto class_id = 0; char description[80]; uint32_t mask = 0x1; clearToBottom(20); putString("Choose a class (? for Help):", Coord_t{20, 2}); for (uint8_t i = 0; i < PLAYER_MAX_CLASSES; i++) { if ((character_races[race_id].classes_bit_field & mask) != 0u) { (void) sprintf(description, "%c) %s", class_id + 'a', classes[i].title); putString(description, Coord_t{y, x}); class_list[class_id] = i; x += 15; if (x > 70) { x = 2; y++; } class_id++; } mask <<= 1; } return class_id; } static void generateCharacterClass(uint8_t const class_id) { py.misc.class_id = class_id; Class_t const &klass = classes[py.misc.class_id]; clearToBottom(20); putString(klass.title, Coord_t{5, 15}); // Adjust the stats for the class adjustment -RAK- py.stats.max[py_attrs::A_STR] = createModifyPlayerStat(py.stats.max[py_attrs::A_STR], klass.strength); py.stats.max[py_attrs::A_INT] = createModifyPlayerStat(py.stats.max[py_attrs::A_INT], klass.intelligence); py.stats.max[py_attrs::A_WIS] = createModifyPlayerStat(py.stats.max[py_attrs::A_WIS], klass.wisdom); py.stats.max[py_attrs::A_DEX] = createModifyPlayerStat(py.stats.max[py_attrs::A_DEX], klass.dexterity); py.stats.max[py_attrs::A_CON] = createModifyPlayerStat(py.stats.max[py_attrs::A_CON], klass.constitution); py.stats.max[py_attrs::A_CHR] = createModifyPlayerStat(py.stats.max[py_attrs::A_CHR], klass.charisma); for (auto i = 0; i < 6; i++) { py.stats.current[i] = py.stats.max[i]; playerSetAndUseStat(i); } // Real values py.misc.plusses_to_damage = (int16_t) playerDamageAdjustment(); py.misc.plusses_to_hit = (int16_t) playerToHitAdjustment(); py.misc.magical_ac = (int16_t) playerArmorClassAdjustment(); py.misc.ac = 0; // Displayed values py.misc.display_to_damage = py.misc.plusses_to_damage; py.misc.display_to_hit = py.misc.plusses_to_hit; py.misc.display_to_ac = py.misc.magical_ac; py.misc.display_ac = py.misc.ac + py.misc.display_to_ac; // now set misc stats, do this after setting stats because of playerStatAdjustmentConstitution() for hit-points py.misc.hit_die += klass.hit_points; py.misc.max_hp = (int16_t) (playerStatAdjustmentConstitution() + py.misc.hit_die); py.misc.current_hp = py.misc.max_hp; py.misc.current_hp_fraction = 0; // Initialize hit_points array. // Put bounds on total possible hp, only succeed // if it is within 1/8 of average value. auto min_value = (PLAYER_MAX_LEVEL * 3 / 8 * (py.misc.hit_die - 1)) + PLAYER_MAX_LEVEL; auto max_value = (PLAYER_MAX_LEVEL * 5 / 8 * (py.misc.hit_die - 1)) + PLAYER_MAX_LEVEL; py.base_hp_levels[0] = py.misc.hit_die; do { for (auto i = 1; i < PLAYER_MAX_LEVEL; i++) { py.base_hp_levels[i] = (uint16_t) randomNumber(py.misc.hit_die); py.base_hp_levels[i] += py.base_hp_levels[i - 1]; } } while (py.base_hp_levels[PLAYER_MAX_LEVEL - 1] < min_value || py.base_hp_levels[PLAYER_MAX_LEVEL - 1] > max_value); py.misc.bth += klass.base_to_hit; py.misc.bth_with_bows += klass.base_to_hit_with_bows; // RAK py.misc.chance_in_search += klass.searching; py.misc.disarm += klass.disarm_traps; py.misc.fos += klass.fos; py.misc.stealth_factor += klass.stealth; py.misc.saving_throw += klass.saving_throw; py.misc.experience_factor += klass.experience_factor; } // Gets a character class -JWT- static void characterGetClass() { uint8_t class_list[PLAYER_MAX_CLASSES]; for (auto &entry : class_list) { entry = 0; } auto class_count = displayRaceClasses(py.misc.race_id, class_list); // Reset the class ID py.misc.class_id = 0; bool is_set = false; while (!is_set) { moveCursor(Coord_t{20, 31}); char const input = getKeyInput(); int class_id = input - 'a'; if (class_id < class_count && class_id >= 0) { is_set = true; generateCharacterClass(class_list[class_id]); } else if (input == '?') { displayTextHelpFile(config::files::welcome_screen); } else { terminalBellSound(); } } } // Given a stat value, return a monetary value, // which affects the amount of gold a player has. static int monetaryValueCalculatedFromStat(uint8_t const stat) { return 5 * (stat - 10); } static void playerCalculateStartGold() { auto value = monetaryValueCalculatedFromStat(py.stats.max[py_attrs::A_STR]); value += monetaryValueCalculatedFromStat(py.stats.max[py_attrs::A_INT]); value += monetaryValueCalculatedFromStat(py.stats.max[py_attrs::A_WIS]); value += monetaryValueCalculatedFromStat(py.stats.max[py_attrs::A_CON]); value += monetaryValueCalculatedFromStat(py.stats.max[py_attrs::A_DEX]); // Social Class adjustment auto new_gold = py.misc.social_class * 6 + randomNumber(25) + 325; // Stat adjustment new_gold -= value; // Charisma adjustment new_gold += monetaryValueCalculatedFromStat(py.stats.max[py_attrs::A_CHR]); // She charmed the banker into it! -CJS- if (!playerIsMale()) { new_gold += 50; } // Minimum if (new_gold < 80) { new_gold = 80; } py.misc.au = new_gold; } // Main Character Creation Routine -JWT- void characterCreate() { printCharacterInformation(); characterChooseRace(); characterSetGender(); // here we start a loop giving a player a choice of characters -RGM- bool done = false; while (!done) { characterGenerateStatsAndRace(); characterGetHistory(); characterSetAgeHeightWeight(); displayCharacterHistory(); printCharacterVitalStatistics(); printCharacterStats(); clearToBottom(20); putString("Hit space to re-roll or ESC to accept characteristics: ", Coord_t{20, 2}); while (true) { char const input = getKeyInput(); if (input == ESCAPE) { done = true; break; } else if (input == ' ') { break; } else { terminalBellSound(); } } } characterGetClass(); playerCalculateStartGold(); printCharacterStats(); printCharacterLevelExperience(); printCharacterAbilities(); getCharacterName(); putStringClearToEOL("[ press any key to continue, or Q to exit ]", Coord_t{23, 17}); if (getKeyInput() == 'Q') { exitProgram(); } eraseLine(Coord_t{23, 0}); } umoria-5.7.10+20181022/src/rng.h0000644000175000017500000000056413363422757014514 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. #pragma once // rng.cpp uint32_t getRandomSeed(); void setRandomSeed(uint32_t seed); int32_t rnd(); umoria-5.7.10+20181022/src/data_creatures.cpp0000644000175000017500000013620613363422757017252 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. // Creatures must be defined here // clang-format off #include "headers.h" // See types.h under Creature_t for a complete list of all variables // for creatures. Some of the less obvious are explained below. // // Hit points: #1, #2: // where #2 is the range of each roll and // #1 is the number of added up rolls to make. // // Example: a creature with 5 eight-sided hit // die is given {5,8}. // // Attack types: // 1 Normal attack // 2 Poison Strength // 3 Confusion attack // 4 Fear attack // 5 Fire attack // 6 Acid attack // 7 Cold attack // 8 Lightning attack // 9 Corrosion attack // 10 Blindness attack // 11 Paralysis attack // 12 Steal Money // 13 Steal Object // 14 Poison // 15 Lose dexterity // 16 Lose constitution // 17 Lose intelligence // 18 Lose wisdom // 19 Lose experience // 20 Aggravation // 21 Disenchants // 22 Eats food // 23 Eats light // 24 Eats charges // 99 Blank // // Attack descriptions: // 1 hits you. // 2 bites you. // 3 claws you. // 4 stings you. // 5 touches you. // 6 kicks you. // 7 gazes at you. // 8 breathes on you. // 9 spits on you. // 10 makes a horrible wail. // 11 embraces you. // 12 crawls on you. // 13 releases a cloud of spores. // 14 begs you for money. // 15 You've been slimed. // 16 crushes you. // 17 tramples you. // 18 drools on you. // 19 insults you. // 99 is repelled. // // Example: For a creature which bites for 1d6, then stings // for 2d4 and loss of dex you would use: // {1,2,1,6},{15,4,2,4} // // CMOVE flags: // Movement. 00000001 Move only to attack // . 00000002 Move, attack normal // . 00000008 20% random movement // . 00000010 40% random movement // . 00000020 75% random movement // Special + 00010000 Invisible movement // + 00020000 Move through door // + 00040000 Move through wall // + 00080000 Move through creatures // + 00100000 Picks up objects // + 00200000 Multiply monster // Carries = 01000000 Carries objects. // = 02000000 Carries gold. // = 04000000 Has 60% of time. // = 08000000 Has 90% of time. // = 10000000 1d2 objects/gold. // = 20000000 2d2 objects/gold. // = 40000000 4d2 objects/gold. // Special ~ 80000000 Win-the-Game creature. // // SPELL Flags: // Frequency 000001 1 These add up to x. Then // (1 in x). 000002 2 if RANDINT(X) = 1 the // . 000004 4 creature casts a spell. // . 000008 8 // Spells = 000010 Teleport short (blink) // = 000020 Teleport long // = 000040 Teleport player to monster // = 000080 Cause light wound // = 000100 Cause serious wound // = 000200 Hold person (Paralysis) // = 000400 Cause blindness // = 000800 Cause confusion // = 001000 Cause fear // = 002000 Summon monster // = 004000 Summon undead // = 008000 Slow Person // = 010000 Drain Mana // = 020000 Not Used // = 040000 Not Used // Breath/ + 080000 Breathe/Resist Lightning // Resist + 100000 Breathe/Resist Gas // + 200000 Breathe/Resist Acid // + 400000 Breathe/Resist Frost // + 800000 Breathe/Resist Fire // // CDEFENSE flags: // 0001 Hurt by Slay Dragon. // 0002 Hurt by Slay Animal. // 0004 Hurt by Slay Evil. // 0008 Hurt by Slay Undead. // 0010 Hurt by Frost. // 0020 Hurt by Fire. // 0040 Hurt by Poison. // 0080 Hurt by Acid. // 0100 Hurt by Light-Wand. // 0200 Hurt by Stone-to-Mud. // 0400 Not used. // 0800 Not used. // 1000 Cannot be charmed or slept. // 2000 Can be seen with infra-vision. // 4000 Max Hit points. // 8000 Not used. // // // Sleep (sleep) : A measure in turns of how fast creature // will notice player (on the average). // Area of affect (area_affect_radius) : Max range that creature is able to // "notice" the player. Creature_t creatures_list[MON_MAX_CREATURES] = { {"Filthy Street Urchin", 0x0012000AL, 0x00000000L, 0x2034, 0, 40, 4, 1, 11, 'p', { 1, 4}, { 72, 148, 0, 0}, 0}, {"Blubbering Idiot", 0x0012000AL, 0x00000000L, 0x2030, 0, 0, 6, 1, 11, 'p', { 1, 2}, { 79, 0, 0, 0}, 0}, {"Pitiful-Looking Beggar", 0x0012000AL, 0x00000000L, 0x2030, 0, 40, 10, 1, 11, 'p', { 1, 4}, { 72, 0, 0, 0}, 0}, {"Mangy-Looking Leper", 0x0012000AL, 0x00000000L, 0x2030, 0, 50, 10, 1, 11, 'p', { 1, 1}, { 72, 0, 0, 0}, 0}, {"Squint-Eyed Rogue", 0x07120002L, 0x00000000L, 0x2034, 0, 99, 10, 8, 11, 'p', { 2, 8}, { 5, 149, 0, 0}, 0}, {"Singing, Happy Drunk", 0x06120038L, 0x00000000L, 0x2030, 0, 0, 10, 1, 11, 'p', { 2, 3}, { 72, 0, 0, 0}, 0}, {"Mean-Looking Mercenary", 0x0B12000AL, 0x00000000L, 0x2034, 0, 250, 10, 20, 11, 'p', { 5, 8}, { 9, 0, 0, 0}, 0}, {"Battle-Scarred Veteran", 0x0B12000AL, 0x00000000L, 0x2030, 0, 250, 10, 30, 11, 'p', { 7, 8}, { 15, 0, 0, 0}, 0}, {"Grey Mushroom patch", 0x00000001L, 0x00000000L, 0x10A0, 1, 0, 2, 1, 11, ',', { 1, 2}, { 91, 0, 0, 0}, 1}, {"Giant Yellow Centipede", 0x00000002L, 0x00000000L, 0x0002, 2, 30, 8, 12, 11, 'c', { 2, 6}, { 26, 60, 0, 0}, 1}, {"Giant White Centipede", 0x0000000AL, 0x00000000L, 0x0002, 2, 40, 7, 10, 11, 'c', { 3, 5}, { 25, 59, 0, 0}, 1}, {"White Icky-Thing", 0x00000012L, 0x00000000L, 0x0020, 2, 10, 12, 7, 11, 'i', { 3, 5}, { 63, 0, 0, 0}, 1}, {"Clear Icky-Thing", 0x00010012L, 0x00000000L, 0x0020, 1, 10, 12, 6, 11, 'i', { 2, 5}, { 63, 0, 0, 0}, 1}, {"Giant White Mouse", 0x0020000AL, 0x00000000L, 0x2072, 1, 20, 8, 4, 11, 'r', { 1, 3}, { 25, 0, 0, 0}, 1}, {"Large Brown Snake", 0x0000000AL, 0x00000000L, 0x00B2, 3, 99, 4, 35, 10, 'R', { 4, 6}, { 26, 73, 0, 0}, 1}, {"Large White Snake", 0x00000012L, 0x00000000L, 0x00B2, 2, 99, 4, 30, 11, 'R', { 3, 6}, { 24, 0, 0, 0}, 1}, {"Kobold", 0x07820002L, 0x00000000L, 0x2030, 5, 10, 20, 16, 11, 'k', { 3, 7}, { 5, 0, 0, 0}, 1}, {"White Worm mass", 0x00200022L, 0x00000000L, 0x01B2, 2, 10, 7, 1, 10, 'w', { 4, 4}, {173, 0, 0, 0}, 1}, {"Floating Eye", 0x00000001L, 0x0001000DL, 0x2100, 1, 10, 2, 6, 11, 'e', { 3, 6}, {146, 0, 0, 0}, 1}, {"Shrieker Mushroom patch", 0x00000001L, 0x00000000L, 0x10A0, 1, 0, 2, 1, 11, ',', { 1, 1}, {203, 0, 0, 0}, 2}, {"Blubbering Icky-Thing", 0x0B980012L, 0x00000000L, 0x0020, 8, 10, 14, 4, 11, 'i', { 5, 8}, {174, 210, 0, 0}, 2}, {"Metallic Green Centipede", 0x00000012L, 0x00000000L, 0x0002, 3, 10, 5, 4, 12, 'c', { 4, 4}, { 68, 0, 0, 0}, 2}, {"Novice Warrior", 0x07020002L, 0x00000000L, 0x2030, 6, 5, 20, 16, 11, 'p', { 9, 4}, { 6, 0, 0, 0}, 2}, {"Novice Rogue", 0x07120002L, 0x00000000L, 0x2034, 6, 5, 20, 12, 11, 'p', { 8, 4}, { 5, 148, 0, 0}, 2}, {"Novice Priest", 0x07020002L, 0x0000108CL, 0x2030, 7, 5, 20, 10, 11, 'p', { 7, 4}, { 4, 0, 0, 0}, 2}, {"Novice Mage", 0x07020002L, 0x0000089CL, 0x2030, 7, 5, 20, 6, 11, 'p', { 6, 4}, { 3, 0, 0, 0}, 2}, {"Yellow Mushroom patch", 0x00000001L, 0x00000000L, 0x10A0, 2, 0, 2, 1, 11, ',', { 1, 1}, {100, 0, 0, 0}, 2}, {"White Jelly", 0x00000001L, 0x00000000L, 0x11A0, 10, 99, 2, 1, 12, 'J', { 8, 8}, {168, 0, 0, 0}, 2}, {"Giant Green Frog", 0x0000000AL, 0x00000000L, 0x00A2, 6, 30, 12, 8, 11, 'f', { 2, 8}, { 26, 0, 0, 0}, 2}, {"Giant Black Ant", 0x0000000AL, 0x00000000L, 0x0002, 8, 80, 8, 20, 11, 'a', { 3, 6}, { 27, 0, 0, 0}, 2}, {"White Harpy", 0x00000012L, 0x00000000L, 0x2034, 5, 10, 16, 17, 11, 'h', { 2, 5}, { 49, 49, 25, 0}, 2}, {"Blue Yeek", 0x07020002L, 0x00000000L, 0x2030, 4, 10, 18, 14, 11, 'y', { 2, 6}, { 4, 0, 0, 0}, 2}, {"Green Worm mass", 0x00200022L, 0x00000000L, 0x0132, 3, 10, 7, 3, 10, 'w', { 6, 4}, {140, 0, 0, 0}, 2}, {"Large Black Snake", 0x0000000AL, 0x00000000L, 0x00B2, 9, 75, 5, 38, 10, 'R', { 4, 8}, { 27, 74, 0, 0}, 2}, {"Poltergeist", 0x0F95003AL, 0x0000001FL, 0x110C, 6, 10, 8, 15, 13, 'G', { 2, 5}, { 93, 0, 0, 0}, 3}, {"Metallic Blue Centipede", 0x00000012L, 0x00000000L, 0x0002, 7, 15, 6, 6, 12, 'c', { 4, 5}, { 69, 0, 0, 0}, 3}, {"Giant White Louse", 0x00200022L, 0x00000000L, 0x01F2, 1, 10, 6, 5, 12, 'l', { 1, 1}, { 24, 0, 0, 0}, 3}, {"Black Naga", 0x0710000AL, 0x00000000L, 0x20E4, 20, 120, 16, 40, 11, 'n', { 6, 8}, { 75, 0, 0, 0}, 3}, {"Spotted Mushroom patch", 0x00000001L, 0x00000000L, 0x10A0, 3, 0, 2, 1, 11, ',', { 1, 1}, {175, 0, 0, 0}, 3}, {"Yellow Jelly", 0x00000001L, 0x0001000FL, 0x11A0, 12, 99, 2, 1, 12, 'J', { 10, 8}, {169, 0, 0, 0}, 3}, {"Scruffy-Looking Hobbit", 0x07920002L, 0x00000000L, 0x2034, 4, 10, 16, 8, 11, 'p', { 3, 5}, { 3, 148, 0, 0}, 3}, {"Huge Brown Bat", 0x00000022L, 0x00000000L, 0x2162, 4, 40, 8, 12, 13, 'b', { 2, 6}, { 25, 0, 0, 0}, 3}, {"Giant White Ant", 0x00000002L, 0x00000000L, 0x0002, 7, 80, 8, 16, 11, 'a', { 3, 6}, { 27, 0, 0, 0}, 3}, {"Yellow Mold", 0x00000001L, 0x00000000L, 0x10A0, 9, 99, 2, 10, 11, 'm', { 8, 8}, { 3, 0, 0, 0}, 3}, {"Metallic Red Centipede", 0x0000000AL, 0x00000000L, 0x0002, 12, 20, 8, 9, 12, 'c', { 4, 8}, { 69, 0, 0, 0}, 3}, {"Yellow Worm mass", 0x00200022L, 0x00000000L, 0x01B2, 4, 10, 7, 4, 10, 'w', { 4, 8}, {182, 0, 0, 0}, 3}, {"Large Green Snake", 0x0000000AL, 0x00000000L, 0x00B2, 10, 70, 5, 40, 10, 'R', { 6, 8}, { 27, 74, 0, 0}, 3}, {"Radiation Eye", 0x00000001L, 0x0001000BL, 0x2100, 6, 10, 2, 6, 11, 'e', { 3, 6}, { 88, 0, 0, 0}, 3}, {"Drooling Harpy", 0x00000012L, 0x00000000L, 0x2034, 7, 10, 16, 22, 11, 'h', { 2, 8}, { 49, 49, 25, 79}, 3}, {"Silver Mouse", 0x0020000AL, 0x00000000L, 0x0072, 1, 10, 8, 5, 11, 'r', { 1, 1}, {212, 0, 0, 0}, 4}, {"Black Mushroom patch", 0x00000001L, 0x00000000L, 0x10A0, 8, 0, 2, 1, 11, ',', { 8, 8}, { 71, 0, 0, 0}, 4}, {"Blue Jelly", 0x00000001L, 0x00400000L, 0x11A0, 14, 99, 2, 1, 11, 'J', { 12, 8}, {125, 0, 0, 0}, 4}, {"Creeping Copper Coins", 0x12000002L, 0x00000000L, 0x1000, 9, 10, 3, 24, 10, '$', { 7, 8}, { 3, 170, 0, 0}, 4}, {"Giant White Rat", 0x0020000AL, 0x00000000L, 0x2072, 1, 30, 8, 7, 11, 'r', { 2, 2}, {153, 0, 0, 0}, 4}, {"Giant Black Centipede", 0x0000000AL, 0x00000000L, 0x0002, 11, 30, 8, 20, 11, 'c', { 5, 8}, { 25, 59, 0, 0}, 4}, {"Giant Blue Centipede", 0x00000002L, 0x00000000L, 0x0002, 10, 50, 8, 20, 11, 'c', { 4, 8}, { 26, 61, 0, 0}, 4}, {"Blue Worm mass", 0x00200022L, 0x00400000L, 0x01A2, 5, 10, 7, 12, 10, 'w', { 5, 8}, {129, 0, 0, 0}, 4}, {"Large Grey Snake", 0x0000000AL, 0x00000000L, 0x00B2, 14, 50, 6, 41, 10, 'R', { 6, 8}, { 28, 75, 0, 0}, 4}, {"Jackal", 0x00000012L, 0x00000000L, 0x2032, 8, 30, 12, 16, 11, 'j', { 3, 8}, { 29, 0, 0, 0}, 4}, {"Green Naga", 0x0710000AL, 0x00200000L, 0x2064, 30, 120, 18, 40, 11, 'n', { 9, 8}, { 75, 118, 0, 0}, 5}, {"Green Glutton Ghost", 0x0F950032L, 0x0000003FL, 0x110C, 15, 10, 10, 20, 13, 'G', { 3, 6}, {211, 0, 0, 0}, 5}, {"White Mushroom patch", 0x00000001L, 0x00000000L, 0x10A0, 5, 0, 2, 1, 11, ',', { 1, 1}, {147, 0, 0, 0}, 5}, {"Green Jelly", 0x00000001L, 0x00200000L, 0x1120, 18, 99, 2, 1, 12, 'J', { 22, 8}, {136, 0, 0, 0}, 5}, {"Skeleton Kobold", 0x00020002L, 0x00000000L, 0x100C, 12, 40, 20, 26, 11, 's', { 5, 8}, { 5, 0, 0, 0}, 5}, {"Silver Jelly", 0x00000001L, 0x00000000L, 0x10A0, 15, 40, 2, 25, 11, 'J', { 20, 8}, {213, 0, 0, 0}, 5}, {"Giant Black Frog", 0x0000000AL, 0x00000000L, 0x00A2, 12, 40, 12, 18, 11, 'f', { 4, 8}, { 29, 0, 0, 0}, 5}, {"Grey Icky-Thing", 0x00000012L, 0x00000000L, 0x0020, 10, 15, 14, 12, 11, 'i', { 4, 8}, { 66, 0, 0, 0}, 5}, {"Disenchanter Eye", 0x00000001L, 0x00010009L, 0x2100, 20, 10, 2, 10, 10, 'e', { 7, 8}, {207, 0, 0, 0}, 5}, {"Black Yeek", 0x07020002L, 0x00000000L, 0x2030, 8, 10, 18, 16, 11, 'y', { 2, 8}, { 4, 0, 0, 0}, 5}, {"Red Worm mass", 0x00200022L, 0x00800000L, 0x2192, 6, 10, 7, 12, 10, 'w', { 5, 8}, {111, 0, 0, 0}, 5}, {"Giant House Fly", 0x00000022L, 0x00000000L, 0x0062, 10, 20, 12, 16, 13, 'F', { 3, 8}, { 25, 0, 0, 0}, 5}, {"Copperhead Snake", 0x00000012L, 0x00000000L, 0x00B2, 15, 1, 6, 20, 11, 'R', { 4, 6}, {158, 0, 0, 0}, 5}, {"Rot Jelly", 0x00000001L, 0x00000000L, 0x10A0, 15, 99, 2, 30, 11, 'J', { 20, 8}, {209, 0, 0, 0}, 5}, {"Purple Mushroom patch", 0x00000001L, 0x00000000L, 0x10A0, 12, 0, 2, 1, 12, ',', { 1, 1}, {183, 0, 0, 0}, 6}, {"Brown Mold", 0x00000001L, 0x00000000L, 0x10A0, 20, 99, 2, 12, 11, 'm', { 15, 8}, { 89, 0, 0, 0}, 6}, {"Giant Brown Bat", 0x0000001AL, 0x00000000L, 0x2162, 10, 30, 10, 15, 13, 'b', { 3, 8}, { 26, 0, 0, 0}, 6}, {"Creeping Silver Coins", 0x16000002L, 0x00000000L, 0x1000, 18, 10, 4, 30, 10, '$', { 12, 8}, { 5, 171, 0, 0}, 6}, {"Orc", 0x0B020002L, 0x00000000L, 0x2034, 16, 30, 20, 32, 11, 'o', { 9, 8}, { 7, 0, 0, 0}, 6}, {"Grey Harpy", 0x00000012L, 0x00000000L, 0x2034, 14, 10, 16, 20, 12, 'h', { 3, 8}, { 50, 50, 25, 0}, 6}, {"Blue Icky-Thing", 0x00000012L, 0x00400000L, 0x0020, 12, 20, 14, 14, 11, 'i', { 4, 8}, {126, 0, 0, 0}, 6}, {"Rattlesnake", 0x00000012L, 0x00000000L, 0x00B2, 20, 1, 6, 24, 11, 'R', { 6, 7}, {159, 0, 0, 0}, 6}, {"Bloodshot Eye", 0x00000001L, 0x00010007L, 0x2100, 15, 10, 2, 6, 11, 'e', { 4, 8}, {143, 0, 0, 0}, 7}, {"Red Naga", 0x0710000AL, 0x00000000L, 0x20E4, 40, 120, 20, 40, 11, 'n', { 11, 8}, { 76, 82, 0, 0}, 7}, {"Red Jelly", 0x00000001L, 0x00000000L, 0x11A0, 26, 99, 2, 1, 11, 'J', { 26, 8}, { 87, 0, 0, 0}, 7}, {"Giant Red Frog", 0x0000000AL, 0x00000000L, 0x00A2, 16, 50, 12, 16, 11, 'f', { 5, 8}, { 83, 0, 0, 0}, 7}, {"Green Icky-Thing", 0x00000012L, 0x00000000L, 0x0020, 18, 20, 14, 12, 11, 'i', { 5, 8}, {137, 0, 0, 0}, 7}, {"Zombie Kobold", 0x00020002L, 0x00000000L, 0x102C, 14, 30, 20, 14, 11, 'z', { 6, 8}, { 1, 1, 0, 0}, 7}, {"Lost Soul", 0x0F95001AL, 0x0001002FL, 0x110C, 18, 10, 12, 10, 11, 'G', { 2, 8}, { 11, 185, 0, 0}, 7}, {"Greedy Little Gnome", 0x0B920002L, 0x00000000L, 0x2034, 13, 10, 18, 14, 11, 'p', { 3, 8}, { 6, 149, 0, 0}, 7}, {"Giant Green Fly", 0x00000022L, 0x00000000L, 0x0062, 15, 20, 12, 14, 12, 'F', { 3, 8}, { 27, 0, 0, 0}, 7}, {"Brown Yeek", 0x07020002L, 0x00000000L, 0x2030, 11, 10, 18, 18, 11, 'y', { 3, 8}, { 5, 0, 0, 0}, 8}, {"Green Mold", 0x00000001L, 0x00000000L, 0x10A0, 28, 75, 2, 14, 11, 'm', { 21, 8}, { 94, 0, 0, 0}, 8}, {"Skeleton Orc", 0x00020002L, 0x00000000L, 0x100C, 26, 40, 20, 36, 11, 's', { 10, 8}, { 14, 0, 0, 0}, 8}, {"Seedy Looking Human", 0x13020002L, 0x00000000L, 0x2034, 22, 20, 20, 26, 11, 'p', { 8, 8}, { 17, 0, 0, 0}, 8}, {"Red Icky-Thing", 0x00000012L, 0x00200000L, 0x0020, 22, 20, 14, 18, 12, 'i', { 4, 8}, { 64, 117, 0, 0}, 8}, {"Bandit", 0x13120002L, 0x00000000L, 0x2034, 26, 10, 20, 24, 11, 'p', { 8, 8}, { 13, 148, 0, 0}, 8}, {"Yeti", 0x00020002L, 0x00400000L, 0x2024, 30, 10, 20, 24, 11, 'Y', { 11, 8}, { 51, 51, 27, 0}, 9}, {"Bloodshot Icky-Thing", 0x00000012L, 0x0001000BL, 0x0020, 24, 20, 14, 18, 11, 'i', { 7, 8}, { 65, 139, 0, 0}, 9}, {"Giant Grey Rat", 0x0020000AL, 0x00000000L, 0x2072, 2, 20, 8, 12, 11, 'r', { 2, 3}, {154, 0, 0, 0}, 9}, {"Black Harpy", 0x0000000AL, 0x00000000L, 0x2034, 19, 10, 16, 22, 12, 'h', { 3, 8}, { 50, 50, 26, 0}, 9}, {"Giant Black Bat", 0x00000012L, 0x00000000L, 0x2162, 16, 25, 12, 18, 13, 'b', { 2, 8}, { 29, 0, 0, 0}, 9}, {"Clear Yeek", 0x07030002L, 0x00000000L, 0x0030, 14, 10, 18, 24, 11, 'y', { 3, 6}, { 4, 0, 0, 0}, 9}, {"Orc Shaman", 0x0B020002L, 0x00008085L, 0x2034, 30, 20, 20, 15, 11, 'o', { 7, 8}, { 5, 0, 0, 0}, 9}, {"Giant Red Ant", 0x00000002L, 0x00000000L, 0x0002, 22, 60, 12, 34, 11, 'a', { 4, 8}, { 27, 85, 0, 0}, 9}, {"King Cobra", 0x00000012L, 0x00000000L, 0x00B2, 28, 1, 8, 30, 11, 'R', { 8, 8}, {144, 161, 0, 0}, 9}, {"Clear Mushroom patch", 0x00210001L, 0x00000000L, 0x10A0, 1, 0, 4, 1, 12, ',', { 1, 1}, { 70, 0, 0, 0}, 10}, {"Giant White Tick", 0x0000000AL, 0x00000000L, 0x0022, 27, 20, 12, 40, 10, 't', { 15, 8}, {160, 0, 0, 0}, 10}, {"Hairy Mold", 0x00000001L, 0x00000000L, 0x10A0, 32, 70, 2, 15, 11, 'm', { 15, 8}, {151, 0, 0, 0}, 10}, {"Disenchanter Mold", 0x00000001L, 0x0001000BL, 0x10A0, 40, 70, 2, 20, 11, 'm', { 16, 8}, {206, 0, 0, 0}, 10}, {"Giant Red Centipede", 0x00000002L, 0x00000000L, 0x0002, 24, 50, 12, 26, 12, 'c', { 3, 8}, { 25, 164, 0, 0}, 10}, {"Creeping Gold Coins", 0x1A000002L, 0x00000000L, 0x1000, 32, 10, 5, 36, 10, '$', { 18, 8}, { 14, 172, 0, 0}, 10}, {"Giant Fruit Fly", 0x00200022L, 0x00000000L, 0x0062, 4, 10, 8, 14, 12, 'F', { 2, 2}, { 25, 0, 0, 0}, 10}, {"Brigand", 0x13120002L, 0x00000000L, 0x2034, 35, 10, 20, 32, 11, 'p', { 9, 8}, { 13, 149, 0, 0}, 10}, {"Orc Zombie", 0x00020002L, 0x00000000L, 0x102C, 30, 25, 20, 24, 11, 'z', { 11, 8}, { 3, 3, 0, 0}, 11}, {"Orc Warrior", 0x0B020002L, 0x00000000L, 0x2034, 34, 25, 20, 36, 11, 'o', { 11, 8}, { 15, 0, 0, 0}, 11}, {"Vorpal Bunny", 0x0020000AL, 0x00000000L, 0x2072, 2, 30, 8, 10, 12, 'r', { 2, 3}, { 28, 0, 0, 0}, 11}, {"Nasty Little Gnome", 0x13820002L, 0x000020B5L, 0x2034, 32, 10, 18, 10, 11, 'p', { 4, 8}, { 4, 0, 0, 0}, 11}, {"Hobgoblin", 0x0F020002L, 0x00000000L, 0x2034, 38, 30, 20, 38, 11, 'H', { 12, 8}, { 9, 0, 0, 0}, 11}, {"Black Mamba", 0x00000012L, 0x00000000L, 0x00B2, 40, 1, 10, 32, 12, 'R', { 10, 8}, {163, 0, 0, 0}, 12}, {"Grape Jelly", 0x00000001L, 0x0001000BL, 0x11A0, 60, 99, 2, 1, 11, 'J', { 52, 8}, {186, 0, 0, 0}, 12}, {"Master Yeek", 0x07020002L, 0x00008018L, 0x2030, 28, 10, 18, 24, 11, 'y', { 5, 8}, { 7, 0, 0, 0}, 12}, {"Priest", 0x13020002L, 0x00000285L, 0x2030, 36, 40, 20, 22, 11, 'p', { 7, 8}, { 12, 0, 0, 0}, 12}, {"Giant Clear Ant", 0x00010002L, 0x00000000L, 0x0002, 24, 60, 12, 18, 11, 'a', { 3, 7}, { 27, 0, 0, 0}, 12}, {"Air Spirit", 0x00030022L, 0x00000000L, 0x1000, 40, 20, 12, 20, 13, 'E', { 5, 8}, { 2, 0, 0, 0}, 12}, {"Skeleton Human", 0x00020002L, 0x00000000L, 0x100C, 38, 30, 20, 30, 11, 's', { 12, 8}, { 7, 0, 0, 0}, 12}, {"Human Zombie", 0x00020002L, 0x00000000L, 0x102C, 34, 20, 20, 24, 11, 'z', { 11, 8}, { 3, 3, 0, 0}, 12}, {"Moaning Spirit", 0x0F15000AL, 0x0001002FL, 0x110C, 44, 10, 14, 20, 11, 'G', { 4, 8}, { 99, 178, 0, 0}, 12}, {"Swordsman", 0x13020002L, 0x00000000L, 0x2030, 40, 20, 20, 34, 11, 'p', { 11, 8}, { 18, 0, 0, 0}, 12}, {"Killer Brown Beetle", 0x0000000AL, 0x00000000L, 0x0002, 38, 30, 10, 40, 11, 'K', { 13, 8}, { 41, 0, 0, 0}, 13}, {"Ogre", 0x07020002L, 0x00000000L, 0x2034, 42, 30, 20, 32, 11, 'o', { 13, 8}, { 16, 0, 0, 0}, 13}, {"Giant Red Speckled Frog", 0x0000000AL, 0x00000000L, 0x00A2, 32, 30, 12, 20, 11, 'f', { 6, 8}, { 41, 0, 0, 0}, 13}, {"Magic User", 0x13020002L, 0x00002413L, 0x2030, 35, 10, 20, 10, 11, 'p', { 7, 8}, { 11, 0, 0, 0}, 13}, {"Black Orc", 0x0B020002L, 0x00000000L, 0x2034, 40, 20, 20, 36, 11, 'o', { 12, 8}, { 17, 0, 0, 0}, 13}, {"Giant Long-Eared Bat", 0x00000012L, 0x00000000L, 0x2162, 20, 20, 12, 20, 13, 'b', { 5, 8}, { 27, 50, 50, 0}, 13}, {"Giant Gnat", 0x00200022L, 0x00000000L, 0x0062, 1, 10, 8, 4, 13, 'F', { 1, 2}, { 24, 0, 0, 0}, 13}, {"Killer Green Beetle", 0x0000000AL, 0x00000000L, 0x0002, 46, 30, 12, 45, 11, 'K', { 16, 8}, { 43, 0, 0, 0}, 14}, {"Giant Flea", 0x00200022L, 0x00000000L, 0x0062, 1, 10, 8, 25, 12, 'F', { 2, 2}, { 25, 0, 0, 0}, 14}, {"Giant White Dragon Fly", 0x00000012L, 0x0040000AL, 0x0062, 54, 50, 20, 20, 11, 'F', { 5, 8}, {122, 0, 0, 0}, 14}, {"Hill Giant", 0x07020002L, 0x00000000L, 0x2034, 52, 50, 20, 36, 11, 'P', { 16, 8}, { 19, 0, 0, 0}, 14}, {"Skeleton Hobgoblin", 0x00020002L, 0x00000000L, 0x100C, 46, 30, 20, 34, 11, 's', { 13, 8}, { 14, 0, 0, 0}, 14}, {"Flesh Golem", 0x00000002L, 0x00000000L, 0x1030, 48, 10, 12, 10, 11, 'g', { 12, 8}, { 5, 5, 0, 0}, 14}, {"White Dragon Bat", 0x00000012L, 0x00400004L, 0x0162, 40, 50, 12, 20, 13, 'b', { 2, 6}, {121, 0, 0, 0}, 14}, {"Giant Black Louse", 0x00200012L, 0x00000000L, 0x01F2, 1, 10, 6, 7, 12, 'l', { 1, 1}, { 25, 0, 0, 0}, 14}, {"Guardian Naga", 0x1710000AL, 0x00000000L, 0x20E4, 60, 120, 20, 50, 11, 'n', { 24, 8}, { 77, 31, 0, 0}, 15}, {"Giant Grey Bat", 0x00000012L, 0x00000000L, 0x2162, 22, 15, 12, 22, 13, 'b', { 4, 8}, { 29, 50, 50, 0}, 15}, {"Giant Clear Centipede", 0x00010002L, 0x00000000L, 0x0002, 30, 30, 12, 30, 11, 'c', { 5, 8}, { 34, 62, 0, 0}, 15}, {"Giant Yellow Tick", 0x0000000AL, 0x00000000L, 0x0022, 48, 20, 12, 48, 10, 't', { 20, 8}, {162, 0, 0, 0}, 15}, {"Giant Ebony Ant", 0x00200002L, 0x00000000L, 0x0002, 3, 60, 12, 24, 11, 'a', { 3, 4}, { 33, 0, 0, 0}, 15}, {"Frost Giant", 0x07020002L, 0x00400000L, 0x0024, 54, 50, 20, 38, 11, 'P', { 17, 8}, {120, 0, 0, 0}, 15}, {"Clay Golem", 0x00000002L, 0x00000000L, 0x1200, 50, 10, 12, 20, 11, 'g', { 14, 8}, { 7, 7, 0, 0}, 15}, {"Huge White Bat", 0x00200012L, 0x00000000L, 0x2162, 3, 40, 7, 12, 12, 'b', { 3, 8}, { 29, 0, 0, 0}, 15}, {"Giant Tan Bat", 0x00000012L, 0x00000000L, 0x2162, 18, 40, 12, 18, 12, 'b', { 3, 8}, { 95, 49, 49, 0}, 15}, {"Violet Mold", 0x00000001L, 0x00010009L, 0x10A0, 50, 70, 2, 15, 11, 'm', { 17, 8}, {145, 0, 0, 0}, 15}, {"Umber Hulk", 0x00020002L, 0x00000000L, 0x2124, 75, 10, 20, 20, 11, 'U', { 20, 8}, { 92, 5, 5, 36}, 16}, {"Gelatinous Cube", 0x2F98000AL, 0x00200000L, 0x1020, 36, 1, 12, 18, 10, 'C', { 45, 8}, {115, 0, 0, 0}, 16}, {"Giant Black Rat", 0x0020000AL, 0x00000000L, 0x2072, 3, 20, 8, 16, 11, 'r', { 3, 4}, {155, 0, 0, 0}, 16}, {"Giant Green Dragon Fly", 0x00000012L, 0x0010000AL, 0x0032, 58, 50, 20, 20, 11, 'F', { 5, 8}, {156, 0, 0, 0}, 16}, {"Fire Giant", 0x07020002L, 0x00800000L, 0x2014, 62, 50, 20, 40, 11, 'P', { 20, 8}, {102, 0, 0, 0}, 16}, {"Green Dragon Bat", 0x00000012L, 0x00100004L, 0x2112, 44, 50, 12, 22, 13, 'b', { 2, 7}, {153, 0, 0, 0}, 16}, {"Quasit", 0x1183000AL, 0x000010FAL, 0x1004, 48, 20, 20, 30, 11, 'q', { 5, 8}, {176, 51, 51, 0}, 16}, {"Troll", 0x0F020002L, 0x00000000L, 0x2024, 64, 40, 20, 40, 11, 'T', { 17, 8}, { 3, 3, 29, 0}, 17}, {"Water Spirit", 0x0000000AL, 0x00000000L, 0x1020, 58, 40, 12, 28, 12, 'E', { 8, 8}, { 13, 0, 0, 0}, 17}, {"Giant Brown Scorpion", 0x0000000AL, 0x00000000L, 0x0002, 62, 20, 12, 44, 11, 'S', { 11, 8}, { 34, 86, 0, 0}, 17}, {"Earth Spirit", 0x0016000AL, 0x00000000L, 0x1200, 64, 50, 10, 40, 11, 'E', { 13, 8}, { 7, 7, 0, 0}, 17}, {"Fire Spirit", 0x0000000AL, 0x00800000L, 0x3010, 66, 20, 16, 30, 12, 'E', { 10, 8}, {101, 0, 0, 0}, 18}, {"Uruk-Hai Orc", 0x0B020002L, 0x00000000L, 0x2034, 68, 20, 20, 42, 11, 'o', { 14, 8}, { 18, 0, 0, 0}, 18}, {"Stone Giant", 0x07020002L, 0x00000000L, 0x2204, 80, 50, 20, 40, 11, 'P', { 22, 8}, { 20, 0, 0, 0}, 18}, {"Stone Golem", 0x00000002L, 0x00000000L, 0x1200, 100, 10, 12, 75, 10, 'g', { 28, 8}, { 9, 9, 0, 0}, 19}, {"Grey Ooze", 0x07980022L, 0x00400000L, 0x10A0, 40, 1, 15, 10, 11, 'O', { 6, 8}, {127, 0, 0, 0}, 19}, {"Disenchanter Ooze", 0x07980022L, 0x00000000L, 0x10B0, 50, 1, 15, 15, 11, 'O', { 6, 8}, {205, 0, 0, 0}, 19}, {"Giant Spotted Rat", 0x0020000AL, 0x00000000L, 0x2072, 3, 20, 8, 20, 11, 'r', { 4, 3}, {155, 0, 0, 0}, 19}, {"Mummified Kobold", 0x0B820002L, 0x00000000L, 0x102C, 46, 75, 20, 24, 11, 'M', { 13, 8}, { 5, 5, 0, 0}, 19}, {"Killer Black Beetle", 0x0000000AL, 0x00000000L, 0x0002, 75, 30, 12, 46, 11, 'K', { 18, 8}, { 44, 0, 0, 0}, 19}, {"Red Mold", 0x00000001L, 0x00800000L, 0x3090, 64, 70, 2, 16, 11, 'm', { 17, 8}, {108, 0, 0, 0}, 19}, {"Quylthulg", 0x00010004L, 0x00002017L, 0x5000, 200, 0, 10, 1, 11, 'Q', { 4, 8}, { 0, 0, 0, 0}, 20}, {"Giant Red Bat", 0x00000012L, 0x00000000L, 0x2162, 40, 20, 12, 24, 12, 'b', { 5, 8}, { 30, 51, 51, 0}, 20}, {"Giant Black Dragon Fly", 0x00000012L, 0x00200009L, 0x0072, 58, 50, 20, 22, 11, 'F', { 4, 8}, {141, 0, 0, 0}, 20}, {"Cloud Giant", 0x07020002L, 0x00080000L, 0x2034, 125, 50, 20, 44, 11, 'P', { 24, 8}, {130, 0, 0, 0}, 20}, {"Black Dragon Bat", 0x00000012L, 0x00200004L, 0x2152, 50, 50, 12, 24, 13, 'b', { 2, 8}, {112, 0, 0, 0}, 21}, {"Blue Dragon Bat", 0x00000012L, 0x00080004L, 0x2052, 54, 50, 12, 26, 13, 'b', { 3, 6}, {131, 0, 0, 0}, 21}, {"Mummified Orc", 0x0B020002L, 0x00000000L, 0x102C, 56, 75, 20, 28, 11, 'M', { 14, 8}, { 13, 13, 0, 0}, 21}, {"Killer Boring Beetle", 0x0000000AL, 0x00000000L, 0x0002, 70, 30, 12, 48, 11, 'K', { 18, 8}, { 44, 0, 0, 0}, 21}, {"Killer Stag Beetle", 0x0000000AL, 0x00000000L, 0x0002, 80, 30, 12, 50, 11, 'K', { 20, 8}, { 41, 10, 0, 0}, 22}, {"Black Mold", 0x00000081L, 0x00000000L, 0x10A0, 68, 50, 2, 18, 11, 'm', { 15, 8}, { 21, 0, 0, 0}, 22}, {"Iron Golem", 0x00000002L, 0x00000000L, 0x1080, 160, 10, 12, 99, 9, 'g', { 80, 8}, { 10, 10, 0, 0}, 22}, {"Giant Yellow Scorpion", 0x0000000AL, 0x00000000L, 0x0002, 60, 20, 12, 38, 11, 'S', { 12, 8}, { 31, 167, 0, 0}, 22}, {"Green Ooze", 0x07BA0012L, 0x00200000L, 0x1030, 6, 1, 15, 5, 10, 'O', { 4, 8}, {116, 0, 0, 0}, 22}, {"Black Ooze", 0x07BA0012L, 0x0001000BL, 0x1030, 7, 1, 10, 6, 9, 'O', { 6, 8}, {138, 0, 0, 0}, 23}, {"Warrior", 0x13020002L, 0x00000000L, 0x2030, 60, 40, 20, 40, 11, 'p', { 15, 8}, { 18, 0, 0, 0}, 23}, {"Red Dragon Bat", 0x00000012L, 0x00800004L, 0x2152, 60, 50, 12, 28, 13, 'b', { 3, 8}, {105, 0, 0, 0}, 23}, {"Killer Blue Beetle", 0x0000000AL, 0x00000000L, 0x0002, 85, 30, 14, 50, 11, 'K', { 20, 8}, { 44, 0, 0, 0}, 23}, {"Giant Silver Ant", 0x0000000AL, 0x00200000L, 0x0002, 45, 60, 10, 38, 11, 'a', { 6, 8}, {114, 0, 0, 0}, 23}, {"Crimson Mold", 0x00000001L, 0x00000000L, 0x10A0, 65, 50, 2, 18, 11, 'm', { 16, 8}, { 2, 97, 0, 0}, 23}, {"Forest Wight", 0x0F02000AL, 0x0000100FL, 0x112C, 140, 30, 20, 30, 11, 'W', { 12, 8}, { 5, 5, 187, 0}, 24}, {"Berzerker", 0x13020002L, 0x00000000L, 0x2030, 65, 10, 20, 20, 11, 'p', { 15, 8}, { 7, 7, 0, 0}, 24}, {"Mummified Human", 0x0B020002L, 0x00000000L, 0x102C, 70, 60, 20, 34, 11, 'M', { 17, 8}, { 13, 13, 0, 0}, 24}, {"Banshee", 0x0F15001AL, 0x0001002FL, 0x110C, 60, 10, 20, 24, 12, 'G', { 6, 8}, { 99, 188, 0, 0}, 24}, {"Giant Troll", 0x0F020002L, 0x00000000L, 0x2024, 85, 50, 20, 40, 11, 'T', { 19, 8}, { 5, 5, 41, 0}, 25}, {"Giant Brown Tick", 0x0000000AL, 0x00000000L, 0x0022, 70, 20, 12, 50, 10, 't', { 18, 8}, {157, 142, 0, 0}, 25}, {"Killer Red Beetle", 0x0000000AL, 0x00000000L, 0x0002, 85, 30, 14, 50, 11, 'K', { 20, 8}, { 84, 0, 0, 0}, 25}, {"Wooden Mold", 0x00000001L, 0x00000000L, 0x10A0, 100, 50, 2, 50, 11, 'm', { 25, 8}, {171, 0, 0, 0}, 25}, {"Giant Blue Dragon Fly", 0x00000012L, 0x00080009L, 0x0072, 75, 50, 20, 24, 11, 'F', { 6, 8}, { 29, 0, 0, 0}, 25}, {"Giant Grey Ant Lion", 0x0008000AL, 0x00000000L, 0x0032, 90, 40, 10, 40, 11, 'A', { 19, 8}, { 39, 0, 0, 0}, 26}, {"Disenchanter Bat", 0x00000012L, 0x00000000L, 0x2162, 75, 1, 14, 24, 13, 'b', { 4, 8}, {204, 0, 0, 0}, 26}, {"Giant Fire Tick", 0x0000000AL, 0x00800000L, 0x2012, 90, 20, 14, 54, 11, 't', { 16, 8}, {109, 0, 0, 0}, 26}, {"White Wraith", 0x0F02000AL, 0x0000100CL, 0x112C, 165, 10, 20, 40, 11, 'W', { 15, 8}, { 5, 5, 189, 0}, 26}, {"Giant Black Scorpion", 0x0000000AL, 0x00000000L, 0x0002, 85, 20, 12, 50, 11, 'S', { 13, 8}, { 32, 167, 0, 0}, 26}, {"Clear Ooze", 0x0799000AL, 0x00000000L, 0x10B0, 12, 1, 10, 14, 11, 'O', { 4, 8}, { 90, 0, 0, 0}, 26}, {"Killer Fire Beetle", 0x0000000AL, 0x00800000L, 0x2012, 95, 30, 14, 45, 11, 'K', { 13, 8}, { 41, 110, 0, 0}, 27}, {"Vampire", 0x17020002L, 0x00001209L, 0x112C, 175, 10, 20, 45, 11, 'V', { 20, 8}, { 5, 5, 190, 0}, 27}, {"Giant Red Dragon Fly", 0x00000012L, 0x00800008L, 0x2052, 75, 50, 20, 24, 11, 'F', { 7, 8}, { 96, 0, 0, 0}, 27}, {"Shimmering Mold", 0x00000081L, 0x00080000L, 0x10A0, 180, 50, 2, 24, 11, 'm', { 32, 8}, {135, 0, 0, 0}, 27}, {"Black Knight", 0x13020002L, 0x0000010FL, 0x2034, 140, 10, 20, 60, 11, 'p', { 25, 8}, { 23, 0, 0, 0}, 28}, {"Mage", 0x13020002L, 0x00002C73L, 0x2030, 150, 10, 20, 30, 11, 'p', { 10, 8}, { 14, 0, 0, 0}, 28}, {"Ice Troll", 0x0F020002L, 0x00400000L, 0x0024, 160, 50, 20, 46, 11, 'T', { 22, 8}, { 4, 4, 123, 0}, 28}, {"Giant Purple Worm", 0x0000000AL, 0x00200000L, 0x2032, 400, 30, 14, 65, 11, 'w', { 65, 8}, { 7, 113, 166, 0}, 29}, {"Young Blue Dragon", 0x1F00000AL, 0x0008100BL, 0x2005, 300, 70, 20, 50, 11, 'd', { 33, 8}, { 52, 52, 29, 0}, 29}, {"Young White Dragon", 0x1F00000AL, 0x0040100BL, 0x0025, 275, 70, 20, 50, 11, 'd', { 32, 8}, { 52, 52, 29, 0}, 29}, {"Young Green Dragon", 0x1F00000AL, 0x0010100BL, 0x2005, 290, 70, 20, 50, 11, 'd', { 32, 8}, { 52, 52, 29, 0}, 29}, {"Giant Fire Bat", 0x00000012L, 0x00800000L, 0x2152, 85, 10, 14, 30, 12, 'b', { 5, 8}, {106, 52, 52, 0}, 29}, {"Giant Glowing Rat", 0x0020000AL, 0x00080000L, 0x2072, 4, 20, 8, 24, 11, 'r', { 3, 3}, {132, 0, 0, 0}, 29}, // Now things are going to get tough. // Some of the creatures have Max hit points, denoted in their CDEFENSE flags as the '4000' bit set {"Skeleton Troll", 0x00020002L, 0x00000000L, 0x500C, 225, 20, 20, 55, 11, 's', { 14, 8}, { 5, 5, 41, 0}, 30}, {"Giant Lightning Bat", 0x00000012L, 0x00080000L, 0x2042, 80, 10, 15, 34, 12, 'b', { 8, 8}, {133, 53, 53, 0}, 30}, {"Giant Static Ant", 0x0000000AL, 0x00080000L, 0x0002, 80, 60, 10, 40, 11, 'a', { 8, 8}, {134, 0, 0, 0}, 30}, {"Grave Wight", 0x0F02000AL, 0x0000110AL, 0x512C, 325, 30, 20, 35, 11, 'W', { 12, 8}, { 6, 6, 191, 0}, 30}, {"Killer Slicer Beetle", 0x0000000AL, 0x00000000L, 0x0002, 200, 30, 14, 55, 11, 'K', { 22, 8}, { 48, 0, 0, 0}, 30}, {"Giant White Ant Lion", 0x0008000AL, 0x00400000L, 0x0022, 175, 40, 12, 45, 11, 'A', { 20, 8}, {124, 0, 0, 0}, 30}, {"Ghost", 0x1715000AL, 0x0001002FL, 0x510C, 350, 10, 20, 30, 12, 'G', { 13, 8}, { 99, 192, 184, 0}, 31}, {"Giant Black Ant Lion", 0x0008000AL, 0x00200000L, 0x0032, 170, 40, 14, 45, 11, 'A', { 23, 8}, { 39, 119, 0, 0}, 31}, {"Death Watch Beetle", 0x0000000AL, 0x00000000L, 0x0002, 190, 30, 16, 60, 11, 'K', { 25, 8}, { 47, 67, 0, 0}, 31}, {"Ogre Mage", 0x0B020002L, 0x0000A355L, 0x6034, 250, 30, 20, 42, 11, 'o', { 14, 8}, { 19, 0, 0, 0}, 31}, {"Two-Headed Troll", 0x0F020002L, 0x00000000L, 0x6024, 275, 50, 20, 48, 11, 'T', { 14, 8}, { 7, 7, 29, 29}, 32}, {"Invisible Stalker", 0x00030022L, 0x00000000L, 0x1000, 200, 20, 20, 46, 13, 'E', { 19, 8}, { 5, 0, 0, 0}, 32}, {"Giant Hunter Ant", 0x00000002L, 0x00000000L, 0x0002, 150, 1, 16, 40, 11, 'a', { 12, 8}, { 46, 0, 0, 0}, 32}, {"Ninja", 0x13020002L, 0x00000000L, 0x6034, 300, 10, 20, 65, 11, 'p', { 15, 8}, {152, 80, 0, 0}, 32}, {"Barrow Wight", 0x0F02000AL, 0x00001308L, 0x512C, 375, 10, 20, 40, 11, 'W', { 13, 8}, { 7, 7, 193, 0}, 33}, {"Skeleton 2-Headed Troll", 0x00020002L, 0x00000000L, 0x500C, 325, 20, 20, 48, 11, 's', { 20, 8}, { 8, 8, 28, 28}, 33}, {"Water Elemental", 0x0008000AL, 0x00000000L, 0x1020, 325, 50, 12, 36, 11, 'E', { 25, 8}, { 9, 9, 0, 0}, 33}, {"Fire Elemental", 0x0008000AL, 0x00800000L, 0x3010, 350, 70, 16, 40, 10, 'E', { 25, 8}, {103, 0, 0, 0}, 33}, {"Lich", 0x1F020002L, 0x00019F75L, 0x510C, 750, 60, 20, 50, 11, 'L', { 25, 8}, {179, 194, 214, 0}, 34}, {"Master Vampire", 0x17020002L, 0x00001307L, 0x512C, 700, 10, 20, 55, 11, 'V', { 23, 8}, { 5, 5, 195, 0}, 34}, {"Spirit Troll", 0x17150002L, 0x00000000L, 0x510C, 425, 10, 20, 40, 11, 'G', { 15, 8}, { 53, 53, 29, 185}, 34}, {"Giant Red Scorpion", 0x0000000AL, 0x00000000L, 0x0002, 275, 40, 12, 50, 12, 'S', { 18, 8}, { 29, 165, 0, 0}, 34}, {"Earth Elemental", 0x001E000AL, 0x00000000L, 0x1200, 375, 90, 10, 60, 10, 'E', { 30, 8}, { 22, 22, 0, 0}, 34}, {"Young Black Dragon", 0x1F00000AL, 0x0020100BL, 0x6005, 600, 50, 20, 55, 11, 'd', { 32, 8}, { 53, 53, 29, 0}, 35}, {"Young Red Dragon", 0x1F00000AL, 0x0080100AL, 0x6015, 650, 50, 20, 60, 11, 'd', { 36, 8}, { 54, 54, 37, 0}, 35}, {"Necromancer", 0x13020002L, 0x00005763L, 0x6030, 600, 10, 20, 40, 11, 'p', { 17, 8}, { 15, 0, 0, 0}, 35}, {"Mummified Troll", 0x0F020002L, 0x00000000L, 0x502C, 400, 50, 20, 38, 11, 'M', { 18, 8}, { 15, 15, 0, 0}, 35}, {"Giant Red Ant Lion", 0x0008000AL, 0x00800000L, 0x2012, 350, 40, 14, 48, 11, 'A', { 23, 8}, {107, 0, 0, 0}, 35}, {"Mature White Dragon", 0x2F00000AL, 0x0040100AL, 0x4025, 1000, 70, 20, 65, 11, 'd', { 48, 8}, { 54, 54, 37, 0}, 35}, {"Xorn", 0x00160002L, 0x00000000L, 0x4200, 650, 10, 20, 80, 11, 'X', { 20, 8}, { 5, 5, 5, 0}, 36}, {"Giant Mottled Ant Lion", 0x0008000AL, 0x00000000L, 0x0032, 350, 40, 14, 50, 12, 'A', { 24, 8}, { 38, 0, 0, 0}, 36}, {"Grey Wraith", 0x0F02000AL, 0x00001308L, 0x512C, 700, 10, 20, 50, 11, 'W', { 23, 8}, { 9, 9, 196, 0}, 36}, {"Young Multi-Hued Dragon", 0x4F00000AL, 0x00F81005L, 0x6005, 1250, 50, 20, 55, 11, 'd', { 40, 8}, { 55, 55, 38, 0}, 36}, {"Mature Blue Dragon", 0x2F00000AL, 0x00081009L, 0x6005, 1200, 70, 20, 75, 11, 'd', { 48, 8}, { 54, 54, 38, 0}, 36}, {"Mature Green Dragon", 0x2F00000AL, 0x0010100AL, 0x6005, 1100, 70, 20, 70, 11, 'd', { 48, 8}, { 52, 52, 29, 0}, 36}, {"Iridescent Beetle", 0x0000000AL, 0x00000000L, 0x0002, 850, 30, 16, 60, 11, 'K', { 32, 8}, { 45, 10, 146, 0}, 37}, {"King Vampire", 0x17020002L, 0x00001307L, 0x512C, 1000, 10, 20, 65, 11, 'V', { 38, 8}, { 5, 5, 198, 0}, 37}, {"King Lich", 0x1F020002L, 0x00019F73L, 0x510C, 1400, 50, 20, 65, 11, 'L', { 52, 8}, {180, 197, 214, 0}, 37}, {"Mature Red Dragon", 0x2F00000AL, 0x00801808L, 0x6015, 1400, 30, 20, 80, 11, 'd', { 60, 8}, { 56, 56, 39, 0}, 37}, {"Mature Black Dragon", 0x2F00000AL, 0x00201009L, 0x6005, 1350, 30, 20, 55, 11, 'd', { 58, 8}, { 54, 54, 38, 0}, 37}, {"Mature Multi-Hued Dragon", 0x6F00000AL, 0x00F81A05L, 0x6005, 1650, 50, 20, 65, 11, 'd', { 80, 8}, { 56, 56, 39, 0}, 38}, {"Ancient White Dragon", 0x4F000002L, 0x00401A09L, 0x4025, 1500, 80, 20, 80, 12, 'D', { 88, 8}, { 54, 54, 37, 0}, 38}, {"Emperor Wight", 0x1B02000AL, 0x00001306L, 0x512C, 1600, 10, 20, 40, 12, 'W', { 48, 8}, { 10, 10, 199, 0}, 38}, {"Black Wraith", 0x1F02000AL, 0x00001307L, 0x512C, 1700, 10, 20, 55, 11, 'W', { 50, 8}, { 10, 10, 200, 0}, 38}, {"Nether Wraith", 0x1F07000AL, 0x00005316L, 0x512C, 2100, 10, 20, 55, 11, 'W', { 58, 8}, { 10, 10, 202, 0}, 39}, {"Sorcerer", 0x1F020002L, 0x0000FF73L, 0x6030, 2150, 10, 20, 50, 12, 'p', { 30, 8}, { 16, 0, 0, 0}, 39}, {"Ancient Blue Dragon", 0x4F000002L, 0x00081A08L, 0x6005, 2500, 80, 20, 90, 12, 'D', { 87, 8}, { 55, 55, 39, 0}, 39}, {"Ancient Green Dragon", 0x4F000002L, 0x00101A09L, 0x6005, 2400, 80, 20, 85, 12, 'D', { 90, 8}, { 54, 54, 38, 0}, 39}, {"Ancient Black Dragon", 0x4F000002L, 0x00201A07L, 0x6005, 2500, 70, 20, 90, 12, 'D', { 90, 8}, { 55, 55, 38, 0}, 39}, {"Crystal Ooze", 0x07BB000AL, 0x00400000L, 0x10A0, 8, 1, 10, 30, 9, 'O', { 12, 8}, {128, 0, 0, 0}, 40}, {"Disenchanter Worm", 0x00200022L, 0x00000000L, 0x01B2, 30, 10, 7, 5, 10, 'w', { 10, 8}, {208, 0, 0, 0}, 40}, {"Rotting Quylthulg", 0x00010004L, 0x00004014L, 0x5000, 1000, 0, 20, 1, 12, 'Q', { 12, 8}, { 0, 0, 0, 0}, 40}, {"Ancient Red Dragon", 0x6F000002L, 0x00801E06L, 0x6015, 2750, 70, 20, 100, 12, 'D', {105, 8}, { 56, 56, 40, 0}, 40}, {"Death Quasit", 0x1103000AL, 0x000010FAL, 0x1004, 1000, 0, 20, 80, 13, 'q', { 55, 8}, {177, 58, 58, 0}, 40}, {"Emperor Lich", 0x2F020002L, 0x00019F72L, 0x510C, 10000, 50, 20, 75, 12, 'L', { 38, 40}, {181, 201, 214, 0}, 40}, {"Ancient Multi-Hued Dragon", 0x7F000002L, 0x00F89E05L, 0x6005, 12000, 70, 20, 100, 12, 'D', { 52, 40}, { 57, 57, 42, 0}, 40}, // Winning creatures should follow here. // Winning creatures are denoted by the 32 bit in CMOVE. // Iggy is not a win creature, just a royal pain in the ass. {"Evil Iggy", 0x7F130002L, 0x0001D713L, 0x5004, 18000, 0, 30, 80, 12, 'p', { 60, 40}, { 81, 150, 0, 0}, 50}, // Here is the only actual win creature. {"Balrog", 0xFF1F0002L, 0x0081C743L, 0x5004, 55000L, 0, 40, 125, 13, 'B', { 75, 40}, {104, 78, 214, 0}, 100}, }; // ERROR: attack #35 is no longer used MonsterAttack_t monster_attacks[MON_ATTACK_TYPES] = { // 0 {0, 0, {0, 0}}, {1, 1, {1, 2}}, {1, 1, {1, 3}}, {1, 1, {1, 4}}, {1, 1, {1, 5}}, {1, 1, {1, 6}}, {1, 1, {1, 7}}, {1, 1, {1, 8}}, {1, 1, {1, 9}}, {1, 1, {1, 10}}, {1, 1, {1, 12}}, {1, 1, {2, 2}}, {1, 1, {2, 3}}, {1, 1, {2, 4}}, {1, 1, {2, 5}}, {1, 1, {2, 6}}, {1, 1, {2, 8}}, {1, 1, {3, 4}}, {1, 1, {3, 5}}, {1, 1, {3, 6}}, // 20 {1, 1, {3, 8}}, {1, 1, {4, 3}}, {1, 1, {4, 6}}, {1, 1, {5, 5}}, {1, 2, {1, 1}}, {1, 2, {1, 2}}, {1, 2, {1, 3}}, {1, 2, {1, 4}}, {1, 2, {1, 5}}, {1, 2, {1, 6}}, {1, 2, {1, 7}}, {1, 2, {1, 8}}, {1, 2, {1, 10}}, {1, 2, {2, 3}}, {1, 2, {2, 4}}, {1, 2, {2, 5}}, // Note: this attack (#35) is no longer used {1, 2, {2, 6}}, {1, 2, {2, 8}}, {1, 2, {2, 10}}, {1, 2, {2, 12}}, // 40 {1, 2, {2, 14}}, {1, 2, {3, 4}}, {1, 2, {3, 12}}, {1, 2, {4, 4}}, {1, 2, {4, 5}}, {1, 2, {4, 6}}, {1, 2, {4, 8}}, {1, 2, {5, 4}}, {1, 2, {5, 8}}, {1, 3, {1, 1}}, {1, 3, {1, 2}}, {1, 3, {1, 3}}, {1, 3, {1, 4}}, {1, 3, {1, 5}}, {1, 3, {1, 8}}, {1, 3, {1, 9}}, {1, 3, {1, 10}}, {1, 3, {1, 12}}, {1, 3, {3, 3}}, {1, 4, {1, 2}}, // 60 {1, 4, {1, 3}}, {1, 4, {1, 4}}, {1, 4, {2, 4}}, {1, 5, {1, 2}}, {1, 5, {1, 3}}, {1, 5, {1, 4}}, {1, 5, {1, 5}}, {1, 10, {5, 6}}, {1, 12, {1, 1}}, {1, 12, {1, 2}}, {1, 13, {1, 1}}, {1, 13, {1, 3}}, {1, 14, {0, 0}}, {1, 16, {1, 4}}, {1, 16, {1, 6}}, {1, 16, {1, 8}}, {1, 16, {1, 10}}, {1, 16, {2, 8}}, {1, 17, {8, 12}}, {1, 18, {0, 0}}, // 80 {2, 1, {3, 4}}, {2, 1, {4, 6}}, {2, 2, {1, 4}}, {2, 2, {2, 4}}, {2, 2, {4, 4}}, {2, 4, {1, 4}}, {2, 4, {1, 7}}, {2, 5, {1, 5}}, {2, 7, {1, 6}}, {3, 1, {1, 4}}, {3, 5, {1, 8}}, {3, 13, {1, 4}}, {3, 7, {0, 0}}, {4, 1, {1, 1}}, {4, 1, {1, 4}}, {4, 2, {1, 2}}, {4, 2, {1, 6}}, {4, 5, {0, 0}}, {4, 7, {0, 0}}, {4, 10, {0, 0}}, // 100 {4, 13, {1, 6}}, {5, 1, {2, 6}}, {5, 1, {3, 7}}, {5, 1, {4, 6}}, {5, 1, {10, 12}}, {5, 2, {1, 3}}, {5, 2, {3, 6}}, {5, 2, {3, 12}}, {5, 5, {4, 4}}, {5, 9, {3, 7}}, {5, 9, {4, 5}}, {5, 12, {1, 6}}, {6, 2, {1, 3}}, {6, 2, {2, 8}}, {6, 2, {4, 4}}, {6, 5, {1, 10}}, {6, 5, {2, 3}}, {6, 8, {1, 5}}, {6, 9, {2, 6}}, {6, 9, {3, 6}}, // 120 {7, 1, {3, 6}}, {7, 2, {1, 3}}, {7, 2, {1, 6}}, {7, 2, {3, 6}}, {7, 2, {3, 10}}, {7, 5, {1, 6}}, {7, 5, {2, 3}}, {7, 5, {2, 6}}, {7, 5, {4, 4}}, {7, 12, {1, 4}}, {8, 1, {3, 8}}, {8, 2, {1, 3}}, {8, 2, {2, 6}}, {8, 2, {3, 8}}, {8, 2, {5, 5}}, {8, 5, {5, 4}}, {9, 5, {1, 2}}, {9, 5, {2, 5}}, {9, 5, {2, 6}}, {9, 8, {2, 4}}, // 140 {9, 12, {1, 3}}, {10, 2, {1, 6}}, {10, 4, {1, 1}}, {10, 7, {2, 6}}, {10, 9, {1, 2}}, {11, 1, {1, 2}}, {11, 7, {0, 0}}, {11, 13, {2, 4}}, {12, 5, {0, 0}}, {13, 5, {0, 0}}, {13, 19, {0, 0}}, {14, 1, {1, 3}}, {14, 1, {3, 4}}, {14, 2, {1, 3}}, {14, 2, {1, 4}}, {14, 2, {1, 5}}, {14, 2, {1, 6}}, {14, 2, {1, 10}}, {14, 2, {2, 4}}, {14, 2, {2, 5}}, // 160 {14, 2, {2, 6}}, {14, 2, {3, 4}}, {14, 2, {3, 9}}, {14, 2, {4, 4}}, {14, 4, {1, 2}}, {14, 4, {1, 4}}, {14, 4, {1, 8}}, {14, 4, {2, 5}}, {14, 5, {1, 2}}, {14, 5, {1, 3}}, {14, 5, {2, 4}}, {14, 5, {2, 6}}, {14, 5, {3, 5}}, {14, 12, {1, 2}}, {14, 12, {1, 4}}, {14, 13, {2, 4}}, {15, 2, {1, 6}}, {15, 2, {3, 6}}, {15, 5, {1, 8}}, {15, 5, {2, 8}}, // 180 {15, 5, {2, 10}}, {15, 5, {2, 12}}, {15, 12, {1, 3}}, {16, 13, {1, 2}}, {17, 3, {1, 10}}, {18, 5, {0, 0}}, {19, 5, {5, 8}}, {19, 5, {12, 8}}, {19, 5, {14, 8}}, {19, 5, {15, 8}}, {19, 5, {18, 8}}, {19, 5, {20, 8}}, {19, 5, {22, 8}}, {19, 5, {26, 8}}, {19, 5, {30, 8}}, {19, 5, {32, 8}}, {19, 5, {34, 8}}, {19, 5, {36, 8}}, {19, 5, {38, 8}}, {19, 5, {42, 8}}, // 200 {19, 5, {44, 8}}, {19, 5, {46, 8}}, {19, 5, {52, 8}}, {20, 10, {0, 0}}, {21, 1, {0, 0}}, {21, 5, {0, 0}}, {21, 5, {1, 6}}, {21, 7, {0, 0}}, {21, 12, {1, 4}}, {22, 5, {2, 3}}, {22, 12, {0, 0}}, {22, 15, {1, 1}}, // 212 {23, 1, {1, 1}}, {23, 5, {1, 3}}, {24, 5, {0, 0}}, }; umoria-5.7.10+20181022/src/treasure.cpp0000644000175000017500000011224513363422757016113 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. // Magical treasure #include "headers.h" Inventory_t treasure_list[LEVEL_MAX_OBJECTS]; // Current treasure heap ptr int16_t current_treasure_id; int16_t missiles_counter = 0; // Counter for missiles // Should the object be enchanted -RAK- static bool magicShouldBeEnchanted(int chance) { return randomNumber(100) <= chance; } // Enchant a bonus based on degree desired -RAK- static int magicEnchantmentBonus(int base, int max_standard, int level) { int stand_deviation = (config::treasure::LEVEL_STD_OBJECT_ADJUST * level / 100) + config::treasure::LEVEL_MIN_OBJECT_STD; // Check for level > max_standard since that may have generated an overflow. if (stand_deviation > max_standard || level > max_standard) { stand_deviation = max_standard; } // abs may be a macro, don't call it with randomNumberNormalDistribution() as a parameter auto abs_distribution = (int) std::abs((std::intmax_t) randomNumberNormalDistribution(0, stand_deviation)); int bonus = (abs_distribution / 10) + base; if (bonus < base) { return base; } return bonus; } static void magicalArmor(Inventory_t &item, int special, int level) { item.to_ac += magicEnchantmentBonus(1, 30, level); if (!magicShouldBeEnchanted(special)) { return; } switch (randomNumber(9)) { case 1: item.flags |= (config::treasure::flags::TR_RES_LIGHT | config::treasure::flags::TR_RES_COLD | config::treasure::flags::TR_RES_ACID | config::treasure::flags::TR_RES_FIRE); item.special_name_id = special_name_ids::SN_R; item.to_ac += 5; item.cost += 2500; break; case 2: // Resist Acid item.flags |= config::treasure::flags::TR_RES_ACID; item.special_name_id = special_name_ids::SN_RA; item.cost += 1000; break; case 3: case 4: // Resist Fire item.flags |= config::treasure::flags::TR_RES_FIRE; item.special_name_id = special_name_ids::SN_RF; item.cost += 600; break; case 5: case 6: // Resist Cold item.flags |= config::treasure::flags::TR_RES_COLD; item.special_name_id = special_name_ids::SN_RC; item.cost += 600; break; case 7: case 8: case 9: // Resist Lightning item.flags |= config::treasure::flags::TR_RES_LIGHT; item.special_name_id = special_name_ids::SN_RL; item.cost += 500; break; default: // Do not apply any special magic break; } } static void cursedArmor(Inventory_t &item, int level) { item.to_ac -= magicEnchantmentBonus(1, 40, level); item.cost = 0; item.flags |= config::treasure::flags::TR_CURSED; } static void magicalSword(Inventory_t &item, int special, int level) { item.to_hit += magicEnchantmentBonus(0, 40, level); // Magical damage bonus now proportional to weapon base damage int damageBonus = maxDiceRoll(item.damage); item.to_damage += magicEnchantmentBonus(0, 4 * damageBonus, damageBonus * level / 10); // the 3*special/2 is needed because weapons are not as common as // before change to treasure distribution, this helps keep same // number of ego weapons same as before, see also missiles if (magicShouldBeEnchanted(3 * special / 2)) { switch (randomNumber(16)) { case 1: // Holy Avenger item.flags |= (config::treasure::flags::TR_SEE_INVIS | config::treasure::flags::TR_SUST_STAT | config::treasure::flags::TR_SLAY_UNDEAD | config::treasure::flags::TR_SLAY_EVIL | config::treasure::flags::TR_STR); item.to_hit += 5; item.to_damage += 5; item.to_ac += randomNumber(4); // the value in `misc_use` is used for strength increase // `misc_use` is also used for sustain stat item.misc_use = (int16_t) randomNumber(4); item.special_name_id = special_name_ids::SN_HA; item.cost += item.misc_use * 500; item.cost += 10000; break; case 2: // Defender item.flags |= (config::treasure::flags::TR_FFALL | config::treasure::flags::TR_RES_LIGHT | config::treasure::flags::TR_SEE_INVIS | config::treasure::flags::TR_FREE_ACT | config::treasure::flags::TR_RES_COLD | config::treasure::flags::TR_RES_ACID | config::treasure::flags::TR_RES_FIRE | config::treasure::flags::TR_REGEN | config::treasure::flags::TR_STEALTH); item.to_hit += 3; item.to_damage += 3; item.to_ac += 5 + randomNumber(5); item.special_name_id = special_name_ids::SN_DF; // the value in `misc_use` is used for stealth item.misc_use = (int16_t) randomNumber(3); item.cost += item.misc_use * 500; item.cost += 7500; break; case 3: case 4: // Slay Animal item.flags |= config::treasure::flags::TR_SLAY_ANIMAL; item.to_hit += 2; item.to_damage += 2; item.special_name_id = special_name_ids::SN_SA; item.cost += 3000; break; case 5: case 6: // Slay Dragon item.flags |= config::treasure::flags::TR_SLAY_DRAGON; item.to_hit += 3; item.to_damage += 3; item.special_name_id = special_name_ids::SN_SD; item.cost += 4000; break; case 7: case 8: // Slay Evil item.flags |= config::treasure::flags::TR_SLAY_EVIL; item.to_hit += 3; item.to_damage += 3; item.special_name_id = special_name_ids::SN_SE; item.cost += 4000; break; case 9: case 10: // Slay Undead item.flags |= (config::treasure::flags::TR_SEE_INVIS | config::treasure::flags::TR_SLAY_UNDEAD); item.to_hit += 3; item.to_damage += 3; item.special_name_id = special_name_ids::SN_SU; item.cost += 5000; break; case 11: case 12: case 13: // Flame Tongue item.flags |= config::treasure::flags::TR_FLAME_TONGUE; item.to_hit++; item.to_damage += 3; item.special_name_id = special_name_ids::SN_FT; item.cost += 2000; break; case 14: case 15: case 16: // Frost Brand item.flags |= config::treasure::flags::TR_FROST_BRAND; item.to_hit++; item.to_damage++; item.special_name_id = special_name_ids::SN_FB; item.cost += 1200; break; default: break; } } } static void cursedSword(Inventory_t &item, int level) { item.to_hit -= magicEnchantmentBonus(1, 55, level); // Magical damage bonus now proportional to weapon base damage int damageBonus = maxDiceRoll(item.damage); item.to_damage -= magicEnchantmentBonus(1, 11 * damageBonus / 2, damageBonus * level / 10); item.flags |= config::treasure::flags::TR_CURSED; item.cost = 0; } static void magicalBow(Inventory_t &item, int level) { item.to_hit += magicEnchantmentBonus(1, 30, level); // add damage. -CJS- item.to_damage += magicEnchantmentBonus(1, 20, level); } static void cursedBow(Inventory_t &item, int level) { item.to_hit -= magicEnchantmentBonus(1, 50, level); // add damage. -CJS- item.to_damage -= magicEnchantmentBonus(1, 30, level); item.flags |= config::treasure::flags::TR_CURSED; item.cost = 0; } static void magicalDiggingTool(Inventory_t &item, int level) { item.misc_use += magicEnchantmentBonus(0, 25, level); } static void cursedDiggingTool(Inventory_t &item, int level) { item.misc_use = (int16_t) -magicEnchantmentBonus(1, 30, level); item.cost = 0; item.flags |= config::treasure::flags::TR_CURSED; } static void magicalGloves(Inventory_t &item, int special, int level) { item.to_ac += magicEnchantmentBonus(1, 20, level); if (!magicShouldBeEnchanted(special)) { return; } if (randomNumber(2) == 1) { item.flags |= config::treasure::flags::TR_FREE_ACT; item.special_name_id = special_name_ids::SN_FREE_ACTION; item.cost += 1000; } else { item.identification |= config::identification::ID_SHOW_HIT_DAM; item.to_hit += 1 + randomNumber(3); item.to_damage += 1 + randomNumber(3); item.special_name_id = special_name_ids::SN_SLAYING; item.cost += (item.to_hit + item.to_damage) * 250; } } static void cursedGloves(Inventory_t &item, int special, int level) { if (magicShouldBeEnchanted(special)) { if (randomNumber(2) == 1) { item.flags |= config::treasure::flags::TR_DEX; item.special_name_id = special_name_ids::SN_CLUMSINESS; } else { item.flags |= config::treasure::flags::TR_STR; item.special_name_id = special_name_ids::SN_WEAKNESS; } item.identification |= config::identification::ID_SHOW_P1; item.misc_use = (int16_t) -magicEnchantmentBonus(1, 10, level); } item.to_ac -= magicEnchantmentBonus(1, 40, level); item.flags |= config::treasure::flags::TR_CURSED; item.cost = 0; } static void magicalBoots(Inventory_t &item, int special, int level) { item.to_ac += magicEnchantmentBonus(1, 20, level); if (!magicShouldBeEnchanted(special)) { return; } int magicType = randomNumber(12); if (magicType > 5) { item.flags |= config::treasure::flags::TR_FFALL; item.special_name_id = special_name_ids::SN_SLOW_DESCENT; item.cost += 250; } else if (magicType == 1) { item.flags |= config::treasure::flags::TR_SPEED; item.special_name_id = special_name_ids::SN_SPEED; item.identification |= config::identification::ID_SHOW_P1; item.misc_use = 1; item.cost += 5000; } else { // 2 - 5 item.flags |= config::treasure::flags::TR_STEALTH; item.identification |= config::identification::ID_SHOW_P1; item.misc_use = (int16_t) randomNumber(3); item.special_name_id = special_name_ids::SN_STEALTH; item.cost += 500; } } static void cursedBoots(Inventory_t &item, int level) { int magicType = randomNumber(3); switch (magicType) { case 1: item.flags |= config::treasure::flags::TR_SPEED; item.special_name_id = special_name_ids::SN_SLOWNESS; item.identification |= config::identification::ID_SHOW_P1; item.misc_use = -1; break; case 2: item.flags |= config::treasure::flags::TR_AGGRAVATE; item.special_name_id = special_name_ids::SN_NOISE; break; default: item.special_name_id = special_name_ids::SN_GREAT_MASS; item.weight = (uint16_t) (item.weight * 5); break; } item.cost = 0; item.to_ac -= magicEnchantmentBonus(2, 45, level); item.flags |= config::treasure::flags::TR_CURSED; } static void magicalHelms(Inventory_t &item, int special, int level) { item.to_ac += magicEnchantmentBonus(1, 20, level); if (!magicShouldBeEnchanted(special)) { return; } if (item.sub_category_id < 6) { item.identification |= config::identification::ID_SHOW_P1; int magicType = randomNumber(3); switch (magicType) { case 1: item.misc_use = (int16_t) randomNumber(2); item.flags |= config::treasure::flags::TR_INT; item.special_name_id = special_name_ids::SN_INTELLIGENCE; item.cost += item.misc_use * 500; break; case 2: item.misc_use = (int16_t) randomNumber(2); item.flags |= config::treasure::flags::TR_WIS; item.special_name_id = special_name_ids::SN_WISDOM; item.cost += item.misc_use * 500; break; default: item.misc_use = (int16_t) (1 + randomNumber(4)); item.flags |= config::treasure::flags::TR_INFRA; item.special_name_id = special_name_ids::SN_INFRAVISION; item.cost += item.misc_use * 250; } return; } switch (randomNumber(6)) { case 1: item.identification |= config::identification::ID_SHOW_P1; item.misc_use = (int16_t) randomNumber(3); item.flags |= (config::treasure::flags::TR_FREE_ACT | config::treasure::flags::TR_CON | config::treasure::flags::TR_DEX | config::treasure::flags::TR_STR); item.special_name_id = special_name_ids::SN_MIGHT; item.cost += 1000 + item.misc_use * 500; break; case 2: item.identification |= config::identification::ID_SHOW_P1; item.misc_use = (int16_t) randomNumber(3); item.flags |= (config::treasure::flags::TR_CHR | config::treasure::flags::TR_WIS); item.special_name_id = special_name_ids::SN_LORDLINESS; item.cost += 1000 + item.misc_use * 500; break; case 3: item.identification |= config::identification::ID_SHOW_P1; item.misc_use = (int16_t) randomNumber(3); item.flags |= (config::treasure::flags::TR_RES_LIGHT | config::treasure::flags::TR_RES_COLD | config::treasure::flags::TR_RES_ACID | config::treasure::flags::TR_RES_FIRE | config::treasure::flags::TR_INT); item.special_name_id = special_name_ids::SN_MAGI; item.cost += 3000 + item.misc_use * 500; break; case 4: item.identification |= config::identification::ID_SHOW_P1; item.misc_use = (int16_t) randomNumber(3); item.flags |= config::treasure::flags::TR_CHR; item.special_name_id = special_name_ids::SN_BEAUTY; item.cost += 750; break; case 5: item.identification |= config::identification::ID_SHOW_P1; item.misc_use = (int16_t) (5 * (1 + randomNumber(4))); item.flags |= (config::treasure::flags::TR_SEE_INVIS | config::treasure::flags::TR_SEARCH); item.special_name_id = special_name_ids::SN_SEEING; item.cost += 1000 + item.misc_use * 100; break; case 6: item.flags |= config::treasure::flags::TR_REGEN; item.special_name_id = special_name_ids::SN_REGENERATION; item.cost += 1500; break; default: break; } } static void cursedHelms(Inventory_t &item, int special, int level) { item.to_ac -= magicEnchantmentBonus(1, 45, level); item.flags |= config::treasure::flags::TR_CURSED; item.cost = 0; if (!magicShouldBeEnchanted(special)) { return; } switch (randomNumber(7)) { case 1: item.identification |= config::identification::ID_SHOW_P1; item.misc_use = (int16_t) -randomNumber(5); item.flags |= config::treasure::flags::TR_INT; item.special_name_id = special_name_ids::SN_STUPIDITY; break; case 2: item.identification |= config::identification::ID_SHOW_P1; item.misc_use = (int16_t) -randomNumber(5); item.flags |= config::treasure::flags::TR_WIS; item.special_name_id = special_name_ids::SN_DULLNESS; break; case 3: item.flags |= config::treasure::flags::TR_BLIND; item.special_name_id = special_name_ids::SN_BLINDNESS; break; case 4: item.flags |= config::treasure::flags::TR_TIMID; item.special_name_id = special_name_ids::SN_TIMIDNESS; break; case 5: item.identification |= config::identification::ID_SHOW_P1; item.misc_use = (int16_t) -randomNumber(5); item.flags |= config::treasure::flags::TR_STR; item.special_name_id = special_name_ids::SN_WEAKNESS; break; case 6: item.flags |= config::treasure::flags::TR_TELEPORT; item.special_name_id = special_name_ids::SN_TELEPORTATION; break; case 7: item.identification |= config::identification::ID_SHOW_P1; item.misc_use = (int16_t) -randomNumber(5); item.flags |= config::treasure::flags::TR_CHR; item.special_name_id = special_name_ids::SN_UGLINESS; break; default: return; } } static void processRings(Inventory_t &item, int level, int cursed) { switch (item.sub_category_id) { case 0: case 1: case 2: case 3: if (magicShouldBeEnchanted(cursed)) { item.misc_use = (int16_t) -magicEnchantmentBonus(1, 20, level); item.flags |= config::treasure::flags::TR_CURSED; item.cost = -item.cost; } else { item.misc_use = (int16_t) magicEnchantmentBonus(1, 10, level); item.cost += item.misc_use * 100; } break; case 4: if (magicShouldBeEnchanted(cursed)) { item.misc_use = (int16_t) -randomNumber(3); item.flags |= config::treasure::flags::TR_CURSED; item.cost = -item.cost; } else { item.misc_use = 1; } break; case 5: item.misc_use = (int16_t) (5 * magicEnchantmentBonus(1, 20, level)); item.cost += item.misc_use * 50; if (magicShouldBeEnchanted(cursed)) { item.misc_use = -item.misc_use; item.flags |= config::treasure::flags::TR_CURSED; item.cost = -item.cost; } break; case 19: // Increase damage item.to_damage += magicEnchantmentBonus(1, 20, level); item.cost += item.to_damage * 100; if (magicShouldBeEnchanted(cursed)) { item.to_damage = -item.to_damage; item.flags |= config::treasure::flags::TR_CURSED; item.cost = -item.cost; } break; case 20: // Increase To-Hit item.to_hit += magicEnchantmentBonus(1, 20, level); item.cost += item.to_hit * 100; if (magicShouldBeEnchanted(cursed)) { item.to_hit = -item.to_hit; item.flags |= config::treasure::flags::TR_CURSED; item.cost = -item.cost; } break; case 21: // Protection item.to_ac += magicEnchantmentBonus(1, 20, level); item.cost += item.to_ac * 100; if (magicShouldBeEnchanted(cursed)) { item.to_ac = -item.to_ac; item.flags |= config::treasure::flags::TR_CURSED; item.cost = -item.cost; } break; case 24: case 25: case 26: case 27: case 28: case 29: item.identification |= config::identification::ID_NO_SHOW_P1; break; case 30: // Slaying item.identification |= config::identification::ID_SHOW_HIT_DAM; item.to_damage += magicEnchantmentBonus(1, 25, level); item.to_hit += magicEnchantmentBonus(1, 25, level); item.cost += (item.to_hit + item.to_damage) * 100; if (magicShouldBeEnchanted(cursed)) { item.to_hit = -item.to_hit; item.to_damage = -item.to_damage; item.flags |= config::treasure::flags::TR_CURSED; item.cost = -item.cost; } break; default: break; } } static void processAmulets(Inventory_t &item, int level, int cursed) { if (item.sub_category_id < 2) { if (magicShouldBeEnchanted(cursed)) { item.misc_use = (int16_t) -magicEnchantmentBonus(1, 20, level); item.flags |= config::treasure::flags::TR_CURSED; item.cost = -item.cost; } else { item.misc_use = (int16_t) magicEnchantmentBonus(1, 10, level); item.cost += item.misc_use * 100; } } else if (item.sub_category_id == 2) { item.misc_use = (int16_t) (5 * magicEnchantmentBonus(1, 25, level)); if (magicShouldBeEnchanted(cursed)) { item.misc_use = -item.misc_use; item.cost = -item.cost; item.flags |= config::treasure::flags::TR_CURSED; } else { item.cost += 50 * item.misc_use; } } else if (item.sub_category_id == 8) { // amulet of the magi is never cursed item.misc_use = (int16_t) (5 * magicEnchantmentBonus(1, 25, level)); item.cost += 20 * item.misc_use; } } static int wandMagic(uint8_t id) { switch (id) { case 0: return randomNumber(10) + 6; case 1: return randomNumber(8) + 6; case 2: return randomNumber(5) + 6; case 3: return randomNumber(8) + 6; case 4: return randomNumber(4) + 3; case 5: return randomNumber(8) + 6; case 6: return randomNumber(20) + 12; case 7: return randomNumber(20) + 12; case 8: return randomNumber(10) + 6; case 9: return randomNumber(12) + 6; case 10: return randomNumber(10) + 12; case 11: return randomNumber(3) + 3; case 12: return randomNumber(8) + 6; case 13: return randomNumber(10) + 6; case 14: return randomNumber(5) + 3; case 15: return randomNumber(5) + 3; case 16: return randomNumber(5) + 6; case 17: return randomNumber(5) + 4; case 18: return randomNumber(8) + 4; case 19: return randomNumber(6) + 2; case 20: return randomNumber(4) + 2; case 21: return randomNumber(8) + 6; case 22: return randomNumber(5) + 2; case 23: return randomNumber(12) + 12; default: return -1; } } static int staffMagic(uint8_t id) { switch (id) { case 0: return randomNumber(20) + 12; case 1: return randomNumber(8) + 6; case 2: return randomNumber(5) + 6; case 3: return randomNumber(20) + 12; case 4: return randomNumber(15) + 6; case 5: return randomNumber(4) + 5; case 6: return randomNumber(5) + 3; case 7: return randomNumber(3) + 1; case 8: return randomNumber(3) + 1; case 9: return randomNumber(5) + 6; case 10: return randomNumber(10) + 12; case 11: return randomNumber(5) + 6; case 12: return randomNumber(5) + 6; case 13: return randomNumber(5) + 6; case 14: return randomNumber(10) + 12; case 15: return randomNumber(3) + 4; case 16: return randomNumber(5) + 6; case 17: return randomNumber(5) + 6; case 18: return randomNumber(3) + 4; case 19: return randomNumber(10) + 12; case 20: return randomNumber(3) + 4; case 21: return randomNumber(3) + 4; case 22: return randomNumber(10) + 6; default: return -1; } } static void magicalCloak(Inventory_t &item, int special, int level) { if (!magicShouldBeEnchanted(special)) { item.to_ac += magicEnchantmentBonus(1, 20, level); return; } if (randomNumber(2) == 1) { item.special_name_id = special_name_ids::SN_PROTECTION; item.to_ac += magicEnchantmentBonus(2, 40, level); item.cost += 250; return; } item.to_ac += magicEnchantmentBonus(1, 20, level); item.identification |= config::identification::ID_SHOW_P1; item.misc_use = (int16_t) randomNumber(3); item.flags |= config::treasure::flags::TR_STEALTH; item.special_name_id = special_name_ids::SN_STEALTH; item.cost += 500; } static void cursedCloak(Inventory_t &item, int level) { int magicType = randomNumber(3); switch (magicType) { case 1: item.flags |= config::treasure::flags::TR_AGGRAVATE; item.special_name_id = special_name_ids::SN_IRRITATION; item.to_ac -= magicEnchantmentBonus(1, 10, level); item.identification |= config::identification::ID_SHOW_HIT_DAM; item.to_hit -= magicEnchantmentBonus(1, 10, level); item.to_damage -= magicEnchantmentBonus(1, 10, level); item.cost = 0; break; case 2: item.special_name_id = special_name_ids::SN_VULNERABILITY; item.to_ac -= magicEnchantmentBonus(10, 100, level + 50); item.cost = 0; break; default: item.special_name_id = special_name_ids::SN_ENVELOPING; item.to_ac -= magicEnchantmentBonus(1, 10, level); item.identification |= config::identification::ID_SHOW_HIT_DAM; item.to_hit -= magicEnchantmentBonus(2, 40, level + 10); item.to_damage -= magicEnchantmentBonus(2, 40, level + 10); item.cost = 0; break; } item.flags |= config::treasure::flags::TR_CURSED; } static void magicalChests(Inventory_t &item, int level) { int magicType = randomNumber(level + 4); switch (magicType) { case 1: item.flags = 0; item.special_name_id = special_name_ids::SN_EMPTY; break; case 2: item.flags |= config::treasure::chests::CH_LOCKED; item.special_name_id = special_name_ids::SN_LOCKED; break; case 3: case 4: item.flags |= (config::treasure::chests::CH_LOSE_STR | config::treasure::chests::CH_LOCKED); item.special_name_id = special_name_ids::SN_POISON_NEEDLE; break; case 5: case 6: item.flags |= (config::treasure::chests::CH_POISON | config::treasure::chests::CH_LOCKED); item.special_name_id = special_name_ids::SN_POISON_NEEDLE; break; case 7: case 8: case 9: item.flags |= (config::treasure::chests::CH_PARALYSED | config::treasure::chests::CH_LOCKED); item.special_name_id = special_name_ids::SN_GAS_TRAP; break; case 10: case 11: item.flags |= (config::treasure::chests::CH_EXPLODE | config::treasure::chests::CH_LOCKED); item.special_name_id = special_name_ids::SN_EXPLOSION_DEVICE; break; case 12: case 13: case 14: item.flags |= (config::treasure::chests::CH_SUMMON | config::treasure::chests::CH_LOCKED); item.special_name_id = special_name_ids::SN_SUMMONING_RUNES; break; case 15: case 16: case 17: item.flags |= (config::treasure::chests::CH_PARALYSED | config::treasure::chests::CH_POISON | config::treasure::chests::CH_LOSE_STR | config::treasure::chests::CH_LOCKED); item.special_name_id = special_name_ids::SN_MULTIPLE_TRAPS; break; default: item.flags |= (config::treasure::chests::CH_SUMMON | config::treasure::chests::CH_EXPLODE | config::treasure::chests::CH_LOCKED); item.special_name_id = special_name_ids::SN_MULTIPLE_TRAPS; break; } } static void magicalProjectiles(Inventory_t &item, int special, int level) { item.to_hit += magicEnchantmentBonus(1, 35, level); item.to_damage += magicEnchantmentBonus(1, 35, level); // see comment for weapons if (magicShouldBeEnchanted(3 * special / 2)) { switch (randomNumber(10)) { case 1: case 2: case 3: item.special_name_id = special_name_ids::SN_SLAYING; item.to_hit += 5; item.to_damage += 5; item.cost += 20; break; case 4: case 5: item.flags |= config::treasure::flags::TR_FLAME_TONGUE; item.to_hit += 2; item.to_damage += 4; item.special_name_id = special_name_ids::SN_FIRE; item.cost += 25; break; case 6: case 7: item.flags |= config::treasure::flags::TR_SLAY_EVIL; item.to_hit += 3; item.to_damage += 3; item.special_name_id = special_name_ids::SN_SLAY_EVIL; item.cost += 25; break; case 8: case 9: item.flags |= config::treasure::flags::TR_SLAY_ANIMAL; item.to_hit += 2; item.to_damage += 2; item.special_name_id = special_name_ids::SN_SLAY_ANIMAL; item.cost += 30; break; case 10: item.flags |= config::treasure::flags::TR_SLAY_DRAGON; item.to_hit += 3; item.to_damage += 3; item.special_name_id = special_name_ids::SN_DRAGON_SLAYING; item.cost += 35; break; default: break; } } } static void cursedProjectiles(Inventory_t &item, int level) { item.to_hit -= magicEnchantmentBonus(5, 55, level); item.to_damage -= magicEnchantmentBonus(5, 55, level); item.flags |= config::treasure::flags::TR_CURSED; item.cost = 0; } // Chance of treasure having magic abilities -RAK- // Chance increases with each dungeon level void magicTreasureMagicalAbility(int item_id, int level) { int chance = config::treasure::OBJECT_BASE_MAGIC + level; if (chance > config::treasure::OBJECT_MAX_BASE_MAGIC) { chance = config::treasure::OBJECT_MAX_BASE_MAGIC; } int special = chance / config::treasure::OBJECT_CHANCE_SPECIAL; int cursed = (10 * chance) / config::treasure::OBJECT_CHANCE_CURSED; int magicAmount; Inventory_t &item = treasure_list[item_id]; // some objects appear multiple times in the game_objects with different // levels, this is to make the object occur more often, however, for // consistency, must set the level of these duplicates to be the same // as the object with the lowest level // Depending on treasure type, it can have certain magical properties switch (item.category_id) { case TV_SHIELD: case TV_HARD_ARMOR: case TV_SOFT_ARMOR: if (magicShouldBeEnchanted(chance)) { magicalArmor(item, special, level); } else if (magicShouldBeEnchanted(cursed)) { cursedArmor(item, level); } break; case TV_HAFTED: case TV_POLEARM: case TV_SWORD: // always show to_hit/to_damage values if identified item.identification |= config::identification::ID_SHOW_HIT_DAM; if (magicShouldBeEnchanted(chance)) { magicalSword(item, special, level); } else if (magicShouldBeEnchanted(cursed)) { cursedSword(item, level); } break; case TV_BOW: // always show to_hit/to_damage values if identified item.identification |= config::identification::ID_SHOW_HIT_DAM; if (magicShouldBeEnchanted(chance)) { magicalBow(item, level); } else if (magicShouldBeEnchanted(cursed)) { cursedBow(item, level); } break; case TV_DIGGING: // always show to_hit/to_damage values if identified item.identification |= config::identification::ID_SHOW_HIT_DAM; if (magicShouldBeEnchanted(chance)) { if (randomNumber(3) < 3) { magicalDiggingTool(item, level); } else { cursedDiggingTool(item, level); } } break; case TV_GLOVES: if (magicShouldBeEnchanted(chance)) { magicalGloves(item, special, level); } else if (magicShouldBeEnchanted(cursed)) { cursedGloves(item, special, level); } break; case TV_BOOTS: if (magicShouldBeEnchanted(chance)) { magicalBoots(item, special, level); } else if (magicShouldBeEnchanted(cursed)) { cursedBoots(item, level); } break; case TV_HELM: // give crowns a higher chance for magic if (item.sub_category_id >= 6 && item.sub_category_id <= 8) { chance += (int) (item.cost / 100); special += special; } if (magicShouldBeEnchanted(chance)) { magicalHelms(item, special, level); } else if (magicShouldBeEnchanted(cursed)) { cursedHelms(item, special, level); } break; case TV_RING: processRings(item, level, cursed); break; case TV_AMULET: processAmulets(item, level, cursed); break; case TV_LIGHT: // `sub_category_id` should be even for store, odd for dungeon // Dungeon found ones will be partially charged if ((item.sub_category_id % 2) == 1) { item.misc_use = (int16_t) randomNumber(item.misc_use); item.sub_category_id -= 1; } break; case TV_WAND: magicAmount = wandMagic(item.sub_category_id); if (magicAmount != -1) { item.misc_use = (uint16_t) magicAmount; } break; case TV_STAFF: magicAmount = staffMagic(item.sub_category_id); if (magicAmount != -1) { item.misc_use = (uint16_t) magicAmount; } // Change the level the items was first found on value if (item.sub_category_id == 7) { item.depth_first_found = 10; } else if (item.sub_category_id == 22) { item.depth_first_found = 5; } break; case TV_CLOAK: if (magicShouldBeEnchanted(chance)) { magicalCloak(item, special, level); } else if (magicShouldBeEnchanted(cursed)) { cursedCloak(item, level); } break; case TV_CHEST: magicalChests(item, level); break; case TV_SLING_AMMO: case TV_SPIKE: case TV_BOLT: case TV_ARROW: if (item.category_id == TV_SLING_AMMO || item.category_id == TV_BOLT || item.category_id == TV_ARROW) { // always show to_hit/to_damage values if identified item.identification |= config::identification::ID_SHOW_HIT_DAM; if (magicShouldBeEnchanted(chance)) { magicalProjectiles(item, special, level); } else if (magicShouldBeEnchanted(cursed)) { cursedProjectiles(item, level); } } item.items_count = 0; for (int i = 0; i < 7; i++) { item.items_count += randomNumber(6); } if (missiles_counter == MAX_SHORT) { missiles_counter = -MAX_SHORT - 1; } else { missiles_counter++; } item.misc_use = missiles_counter; break; case TV_FOOD: // make sure all food rations have the same level if (item.sub_category_id == 90) { item.depth_first_found = 0; } // give all Elvish waybread the same level if (item.sub_category_id == 92) { item.depth_first_found = 6; } break; case TV_SCROLL1: if (item.sub_category_id == 67) { // give all identify scrolls the same level item.depth_first_found = 1; } else if (item.sub_category_id == 69) { // scroll of light item.depth_first_found = 0; } else if (item.sub_category_id == 80) { // scroll of trap detection item.depth_first_found = 5; } else if (item.sub_category_id == 81) { // scroll of door/stair location item.depth_first_found = 5; } break; case TV_POTION1: // cure light if (item.sub_category_id == 76) { item.depth_first_found = 0; } break; default: break; } } umoria-5.7.10+20181022/src/config.h0000644000175000017500000003022113363422757015164 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. // Basic Configuration namespace config { namespace files { extern const std::string splash_screen; extern const std::string welcome_screen; extern const std::string license; extern const std::string versions_history; extern const std::string help; extern const std::string help_wizard; extern const std::string help_roguelike; extern const std::string help_roguelike_wizard; extern const std::string death_tomb; extern const std::string death_royal; extern const std::string scores; extern std::string save_game; } namespace options { extern bool display_counts; extern bool find_bound; extern bool run_cut_corners; extern bool run_examine_corners; extern bool run_ignore_doors; extern bool run_print_self; extern bool highlight_seams; extern bool prompt_to_pickup; extern bool use_roguelike_keys; extern bool show_inventory_weights; extern bool error_beep_sound; } namespace dungeon { extern const uint8_t DUN_RANDOM_DIR; extern const uint8_t DUN_DIR_CHANGE; extern const uint8_t DUN_TUNNELING; extern const uint8_t DUN_ROOMS_MEAN; extern const uint8_t DUN_ROOM_DOORS; extern const uint8_t DUN_TUNNEL_DOORS; extern const uint8_t DUN_STREAMER_DENSITY; extern const uint8_t DUN_STREAMER_WIDTH; extern const uint8_t DUN_MAGMA_STREAMER; extern const uint8_t DUN_MAGMA_TREASURE; extern const uint8_t DUN_QUARTZ_STREAMER; extern const uint8_t DUN_QUARTZ_TREASURE; extern const uint16_t DUN_UNUSUAL_ROOMS; namespace objects { extern const uint16_t OBJ_OPEN_DOOR; extern const uint16_t OBJ_CLOSED_DOOR; extern const uint16_t OBJ_SECRET_DOOR; extern const uint16_t OBJ_UP_STAIR; extern const uint16_t OBJ_DOWN_STAIR; extern const uint16_t OBJ_STORE_DOOR; extern const uint16_t OBJ_TRAP_LIST; extern const uint16_t OBJ_RUBBLE; extern const uint16_t OBJ_MUSH; extern const uint16_t OBJ_SCARE_MON; extern const uint16_t OBJ_GOLD_LIST; extern const uint16_t OBJ_NOTHING; extern const uint16_t OBJ_RUINED_CHEST; extern const uint16_t OBJ_WIZARD; extern const uint8_t MAX_GOLD_TYPES; extern const uint8_t MAX_TRAPS; extern const uint8_t LEVEL_OBJECTS_PER_ROOM; extern const uint8_t LEVEL_OBJECTS_PER_CORRIDOR; extern const uint8_t LEVEL_TOTAL_GOLD_AND_GEMS; } } namespace treasure { extern const uint8_t MIN_TREASURE_LIST_ID; extern const uint8_t TREASURE_CHANCE_OF_GREAT_ITEM; extern const uint8_t LEVEL_STD_OBJECT_ADJUST; extern const uint8_t LEVEL_MIN_OBJECT_STD; extern const uint8_t LEVEL_TOWN_OBJECTS; extern const uint8_t OBJECT_BASE_MAGIC; extern const uint8_t OBJECT_MAX_BASE_MAGIC; extern const uint8_t OBJECT_CHANCE_SPECIAL; extern const uint8_t OBJECT_CHANCE_CURSED; extern const uint16_t OBJECT_LAMP_MAX_CAPACITY; extern const uint8_t OBJECT_BOLTS_MAX_RANGE; extern const uint16_t OBJECTS_RUNE_PROTECTION; namespace flags { extern const uint32_t TR_STATS; extern const uint32_t TR_STR; extern const uint32_t TR_INT; extern const uint32_t TR_WIS; extern const uint32_t TR_DEX; extern const uint32_t TR_CON; extern const uint32_t TR_CHR; extern const uint32_t TR_SEARCH; extern const uint32_t TR_SLOW_DIGEST; extern const uint32_t TR_STEALTH; extern const uint32_t TR_AGGRAVATE; extern const uint32_t TR_TELEPORT; extern const uint32_t TR_REGEN; extern const uint32_t TR_SPEED; extern const uint32_t TR_EGO_WEAPON; extern const uint32_t TR_SLAY_DRAGON; extern const uint32_t TR_SLAY_ANIMAL; extern const uint32_t TR_SLAY_EVIL; extern const uint32_t TR_SLAY_UNDEAD; extern const uint32_t TR_FROST_BRAND; extern const uint32_t TR_FLAME_TONGUE; extern const uint32_t TR_RES_FIRE; extern const uint32_t TR_RES_ACID; extern const uint32_t TR_RES_COLD; extern const uint32_t TR_SUST_STAT; extern const uint32_t TR_FREE_ACT; extern const uint32_t TR_SEE_INVIS; extern const uint32_t TR_RES_LIGHT; extern const uint32_t TR_FFALL; extern const uint32_t TR_BLIND; extern const uint32_t TR_TIMID; extern const uint32_t TR_TUNNEL; extern const uint32_t TR_INFRA; extern const uint32_t TR_CURSED; } namespace chests { extern const uint32_t CH_LOCKED; extern const uint32_t CH_TRAPPED; extern const uint32_t CH_LOSE_STR; extern const uint32_t CH_POISON; extern const uint32_t CH_PARALYSED; extern const uint32_t CH_EXPLODE; extern const uint32_t CH_SUMMON; } } namespace monsters { extern const uint8_t MON_CHANCE_OF_NEW; extern const uint8_t MON_MAX_SIGHT; extern const uint8_t MON_MAX_SPELL_CAST_DISTANCE; extern const uint8_t MON_MAX_MULTIPLY_PER_LEVEL; extern const uint8_t MON_MULTIPLY_ADJUST; extern const uint8_t MON_CHANCE_OF_NASTY; extern const uint8_t MON_MIN_PER_LEVEL; extern const uint8_t MON_MIN_TOWNSFOLK_DAY; extern const uint8_t MON_MIN_TOWNSFOLK_NIGHT; extern const uint8_t MON_ENDGAME_MONSTERS; extern const uint8_t MON_ENDGAME_LEVEL; extern const uint8_t MON_SUMMONED_LEVEL_ADJUST; extern const uint8_t MON_PLAYER_EXP_DRAINED_PER_HIT; extern const uint8_t MON_MIN_INDEX_ID; extern const uint8_t SCARE_MONSTER; namespace move { extern const uint32_t CM_ALL_MV_FLAGS; extern const uint32_t CM_ATTACK_ONLY; extern const uint32_t CM_MOVE_NORMAL; extern const uint32_t CM_ONLY_MAGIC; extern const uint32_t CM_RANDOM_MOVE; extern const uint32_t CM_20_RANDOM; extern const uint32_t CM_40_RANDOM; extern const uint32_t CM_75_RANDOM; extern const uint32_t CM_SPECIAL; extern const uint32_t CM_INVISIBLE; extern const uint32_t CM_OPEN_DOOR; extern const uint32_t CM_PHASE; extern const uint32_t CM_EATS_OTHER; extern const uint32_t CM_PICKS_UP; extern const uint32_t CM_MULTIPLY; extern const uint32_t CM_SMALL_OBJ; extern const uint32_t CM_CARRY_OBJ; extern const uint32_t CM_CARRY_GOLD; extern const uint32_t CM_TREASURE; extern const uint32_t CM_TR_SHIFT; extern const uint32_t CM_60_RANDOM; extern const uint32_t CM_90_RANDOM; extern const uint32_t CM_1D2_OBJ; extern const uint32_t CM_2D2_OBJ; extern const uint32_t CM_4D2_OBJ; extern const uint32_t CM_WIN; } namespace spells { extern const uint32_t CS_FREQ; extern const uint32_t CS_SPELLS; extern const uint32_t CS_TEL_SHORT; extern const uint32_t CS_TEL_LONG; extern const uint32_t CS_TEL_TO; extern const uint32_t CS_LGHT_WND; extern const uint32_t CS_SER_WND; extern const uint32_t CS_HOLD_PER; extern const uint32_t CS_BLIND; extern const uint32_t CS_CONFUSE; extern const uint32_t CS_FEAR; extern const uint32_t CS_SUMMON_MON; extern const uint32_t CS_SUMMON_UND; extern const uint32_t CS_SLOW_PER; extern const uint32_t CS_DRAIN_MANA; extern const uint32_t CS_BREATHE; extern const uint32_t CS_BR_LIGHT; extern const uint32_t CS_BR_GAS; extern const uint32_t CS_BR_ACID; extern const uint32_t CS_BR_FROST; extern const uint32_t CS_BR_FIRE; } // creature defense flags namespace defense { extern const uint16_t CD_DRAGON; extern const uint16_t CD_ANIMAL; extern const uint16_t CD_EVIL; extern const uint16_t CD_UNDEAD; extern const uint16_t CD_WEAKNESS; extern const uint16_t CD_FROST; extern const uint16_t CD_FIRE; extern const uint16_t CD_POISON; extern const uint16_t CD_ACID; extern const uint16_t CD_LIGHT; extern const uint16_t CD_STONE; extern const uint16_t CD_NO_SLEEP; extern const uint16_t CD_INFRA; extern const uint16_t CD_MAX_HP; } } namespace player { extern const int32_t PLAYER_MAX_EXP; extern const uint8_t PLAYER_USE_DEVICE_DIFFICULTY; extern const uint16_t PLAYER_FOOD_FULL; extern const uint16_t PLAYER_FOOD_MAX; extern const uint16_t PLAYER_FOOD_FAINT; extern const uint16_t PLAYER_FOOD_WEAK; extern const uint16_t PLAYER_FOOD_ALERT; extern const uint8_t PLAYER_REGEN_FAINT; extern const uint8_t PLAYER_REGEN_WEAK; extern const uint8_t PLAYER_REGEN_NORMAL; extern const uint16_t PLAYER_REGEN_HPBASE; extern const uint16_t PLAYER_REGEN_MNBASE; extern const uint8_t PLAYER_WEIGHT_CAP; namespace status { extern const uint32_t PY_HUNGRY; extern const uint32_t PY_WEAK; extern const uint32_t PY_BLIND; extern const uint32_t PY_CONFUSED; extern const uint32_t PY_FEAR; extern const uint32_t PY_POISONED; extern const uint32_t PY_FAST; extern const uint32_t PY_SLOW; extern const uint32_t PY_SEARCH; extern const uint32_t PY_REST; extern const uint32_t PY_STUDY; extern const uint32_t PY_INVULN; extern const uint32_t PY_HERO; extern const uint32_t PY_SHERO; extern const uint32_t PY_BLESSED; extern const uint32_t PY_DET_INV; extern const uint32_t PY_TIM_INFRA; extern const uint32_t PY_SPEED; extern const uint32_t PY_STR_WGT; extern const uint32_t PY_PARALYSED; extern const uint32_t PY_REPEAT; extern const uint32_t PY_ARMOR; extern const uint32_t PY_STATS; extern const uint32_t PY_STR; // these 6 stat flags must be adjacent extern const uint32_t PY_INT; extern const uint32_t PY_WIS; extern const uint32_t PY_DEX; extern const uint32_t PY_CON; extern const uint32_t PY_CHR; extern const uint32_t PY_HP; extern const uint32_t PY_MANA; } } namespace identification { extern const uint8_t OD_TRIED; extern const uint8_t OD_KNOWN1; extern const uint8_t ID_MAGIK; extern const uint8_t ID_DAMD; extern const uint8_t ID_EMPTY; extern const uint8_t ID_KNOWN2; extern const uint8_t ID_STORE_BOUGHT; extern const uint8_t ID_SHOW_HIT_DAM; extern const uint8_t ID_NO_SHOW_P1; extern const uint8_t ID_SHOW_P1; } namespace spells { extern const uint8_t SPELL_TYPE_NONE; extern const uint8_t SPELL_TYPE_MAGE; extern const uint8_t SPELL_TYPE_PRIEST; extern const uint8_t NAME_OFFSET_SPELLS; extern const uint8_t NAME_OFFSET_PRAYERS; } namespace stores { extern const uint8_t STORE_MAX_AUTO_BUY_ITEMS; extern const uint8_t STORE_MIN_AUTO_SELL_ITEMS; extern const uint8_t STORE_STOCK_TURN_AROUND; } } umoria-5.7.10+20181022/src/data_treasure.cpp0000644000175000017500000016445613363422757017117 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. // Treasure data // clang-format off #include "headers.h" // Object description: Objects are defined here. Each object has // the following attributes: // // Descriptor : Name of item and formats. // & is replaced with 'a', 'an', or a number. // ~ is replaced with null or 's'. // Character : Character that represents the item. // Type value : Value representing the type of object. // Sub value : separate value for each item of a type. // 0 - 63: object can not stack // 64 - 127: dungeon object, can stack with other D object // 128 - 191: unused, previously for store items // 192: stack with other iff have same `misc_use` value, always // treated as individual objects // 193 - 255: object can stack with others iff they have // the same `misc_use` value, usually considered one group // Objects which have two type values, e.g. potions and // scrolls, need to have distinct `sub_category_id`s for // each item regardless of its category_id // Damage : amount of damage item can cause. // Weight : relative weight of an item. // Number : number of items appearing in group. // To hit : magical plusses to hit. // To damage : magical plusses to damage. // AC : objects relative armor class. // 1 is worse than 5 is worse than 10 etc. // To AC : Magical bonuses to AC. // misc_use : Catch all for magical abilities such as // plusses to strength, minuses to searching. // Flags : Abilities of object. Each ability is a // bit. Bits 1-31 are used. (Signed integer) // Level : Minimum level on which item can be found. // Cost : Relative cost of item. // // Special Abilities can be added to item by magicInitializeItemNames(), // found in misc.c. // // Scrolls, Potions, and Food: // Flags is used to define a function which reading/quaffing // will cause. Most scrolls and potions have only one bit // set. Potions will generally have some food value, found // in `misc_use`. // // Wands and Staffs: // Flags defines a function, `misc_use` contains number of charges // for item. `misc_use` is set in magicInitializeItemNames() in misc.c. // // Chests: // Traps are added randomly by magicInitializeItemNames() in misc.c. // Object list (All objects must be defined here) // Dungeon items from 0 to MAX_DUNGEON_OBJECTS DungeonObject_t game_objects[MAX_OBJECTS_IN_GAME] = { {"Poison", 0x00000001L, TV_FOOD, ',', 500, 0, 64, 1, 1, 0, 0, 0, 0, {0, 0}, 7}, // 0 {"Blindness", 0x00000002L, TV_FOOD, ',', 500, 0, 65, 1, 1, 0, 0, 0, 0, {0, 0}, 9}, // 1 {"Paranoia", 0x00000004L, TV_FOOD, ',', 500, 0, 66, 1, 1, 0, 0, 0, 0, {0, 0}, 9}, // 2 {"Confusion", 0x00000008L, TV_FOOD, ',', 500, 0, 67, 1, 1, 0, 0, 0, 0, {0, 0}, 7}, // 3 {"Hallucination", 0x00000010L, TV_FOOD, ',', 500, 0, 68, 1, 1, 0, 0, 0, 0, {0, 0}, 13}, // 4 {"Cure Poison", 0x00000020L, TV_FOOD, ',', 500, 60, 69, 1, 1, 0, 0, 0, 0, {0, 0}, 8}, // 5 {"Cure Blindness", 0x00000040L, TV_FOOD, ',', 500, 50, 70, 1, 1, 0, 0, 0, 0, {0, 0}, 10}, // 6 {"Cure Paranoia", 0x00000080L, TV_FOOD, ',', 500, 25, 71, 1, 1, 0, 0, 0, 0, {0, 0}, 12}, // 7 {"Cure Confusion", 0x00000100L, TV_FOOD, ',', 500, 50, 72, 1, 1, 0, 0, 0, 0, {0, 0}, 6}, // 8 {"Weakness", 0x04000200L, TV_FOOD, ',', 500, 0, 73, 1, 1, 0, 0, 0, 0, {0, 0}, 7}, // 9 {"Unhealth", 0x04000400L, TV_FOOD, ',', 500, 50, 74, 1, 1, 0, 0, 0, 0, {10, 10}, 15}, // 10 {"Restore Constitution", 0x00010000L, TV_FOOD, ',', 500, 350, 75, 1, 1, 0, 0, 0, 0, {0, 0}, 20}, // 11 {"First-Aid", 0x00200000L, TV_FOOD, ',', 500, 5, 76, 1, 1, 0, 0, 0, 0, {0, 0}, 6}, // 12 {"Minor Cures", 0x00400000L, TV_FOOD, ',', 500, 20, 77, 1, 1, 0, 0, 0, 0, {0, 0}, 7}, // 13 {"Light Cures", 0x00800000L, TV_FOOD, ',', 500, 30, 78, 1, 1, 0, 0, 0, 0, {0, 0}, 10}, // 14 {"Restoration", 0x001F8000L, TV_FOOD, ',', 500, 1000, 79, 1, 1, 0, 0, 0, 0, {0, 0}, 30}, // 15 {"Poison", 0x00000001L, TV_FOOD, ',', 1200, 0, 80, 1, 1, 0, 0, 0, 0, {0, 0}, 15}, // 16 {"Hallucination", 0x00000010L, TV_FOOD, ',', 1200, 0, 81, 1, 1, 0, 0, 0, 0, {0, 0}, 18}, // 17 {"Cure Poison", 0x00000020L, TV_FOOD, ',', 1200, 75, 82, 1, 1, 0, 0, 0, 0, {0, 0}, 19}, // 18 {"Unhealth", 0x04000400L, TV_FOOD, ',', 1200, 75, 83, 1, 1, 0, 0, 0, 0, {10, 12}, 28}, // 19 {"Major Cures", 0x02000000L, TV_FOOD, ',', 1200, 75, 84, 1, 2, 0, 0, 0, 0, {0, 0}, 16}, // 20 {"& Ration~ of Food", 0x00000000L, TV_FOOD, ',', 5000, 3, 90, 1, 10, 0, 0, 0, 0, {0, 0}, 0}, // 21 {"& Ration~ of Food", 0x00000000L, TV_FOOD, ',', 5000, 3, 90, 1, 10, 0, 0, 0, 0, {0, 0}, 5}, // 22 {"& Ration~ of Food", 0x00000000L, TV_FOOD, ',', 5000, 3, 90, 1, 10, 0, 0, 0, 0, {0, 0}, 10}, // 23 {"& Slime Mold~", 0x00000000L, TV_FOOD, ',', 3000, 2, 91, 1, 5, 0, 0, 0, 0, {0, 0}, 1}, // 24 {"& Piece~ of Elvish Waybread", 0x02000020L, TV_FOOD, ',', 7500, 25, 92, 1, 3, 0, 0, 0, 0, {0, 0}, 6}, // 25 {"& Piece~ of Elvish Waybread", 0x02000020L, TV_FOOD, ',', 7500, 25, 92, 1, 3, 0, 0, 0, 0, {0, 0}, 12}, // 26 {"& Piece~ of Elvish Waybread", 0x02000020L, TV_FOOD, ',', 7500, 25, 92, 1, 3, 0, 0, 0, 0, {0, 0}, 20}, // 27 {"& Dagger (Main Gauche)", 0x00000000L, TV_SWORD, '|', 0, 25, 1, 1, 30, 0, 0, 0, 0, {1, 5}, 2}, // 28 {"& Dagger (Misericorde)", 0x00000000L, TV_SWORD, '|', 0, 10, 2, 1, 15, 0, 0, 0, 0, {1, 4}, 0}, // 29 {"& Dagger (Stiletto)", 0x00000000L, TV_SWORD, '|', 0, 10, 3, 1, 12, 0, 0, 0, 0, {1, 4}, 0}, // 30 {"& Dagger (Bodkin)", 0x00000000L, TV_SWORD, '|', 0, 10, 4, 1, 20, 0, 0, 0, 0, {1, 4}, 1}, // 31 {"& Broken Dagger", 0x00000000L, TV_SWORD, '|', 0, 0, 5, 1, 15, -2, -2, 0, 0, {1, 1}, 0}, // 32 {"& Backsword", 0x00000000L, TV_SWORD, '|', 0, 150, 6, 1, 95, 0, 0, 0, 0, {1, 9}, 7}, // 33 {"& Bastard Sword", 0x00000000L, TV_SWORD, '|', 0, 350, 7, 1, 140, 0, 0, 0, 0, {3, 4}, 14}, // 34 {"& Thrusting Sword (Bilbo)", 0x00000000L, TV_SWORD, '|', 0, 60, 8, 1, 80, 0, 0, 0, 0, {1, 6}, 4}, // 35 {"& Thrusting Sword (Baselard)", 0x00000000L, TV_SWORD, '|', 0, 80, 9, 1, 100, 0, 0, 0, 0, {1, 7}, 5}, // 36 {"& Broadsword", 0x00000000L, TV_SWORD, '|', 0, 255, 10, 1, 150, 0, 0, 0, 0, {2, 5}, 9}, // 37 {"& Two-Handed Sword (Claymore)", 0x00000000L, TV_SWORD, '|', 0, 775, 11, 1, 200, 0, 0, 0, 0, {3, 6}, 30}, // 38 {"& Cutlass", 0x00000000L, TV_SWORD, '|', 0, 85, 12, 1, 110, 0, 0, 0, 0, {1, 7}, 7}, // 39 {"& Two-Handed Sword (Espadon)", 0x00000000L, TV_SWORD, '|', 0, 655, 13, 1, 180, 0, 0, 0, 0, {3, 6}, 35}, // 40 {"& Executioner's Sword", 0x00000000L, TV_SWORD, '|', 0, 850, 14, 1, 260, 0, 0, 0, 0, {4, 5}, 40}, // 41 {"& Two-Handed Sword (Flamberge)", 0x00000000L, TV_SWORD, '|', 0, 1000, 15, 1, 240, 0, 0, 0, 0, {4, 5}, 45}, // 42 {"& Foil", 0x00000000L, TV_SWORD, '|', 0, 35, 16, 1, 30, 0, 0, 0, 0, {1, 5}, 2}, // 43 {"& Katana", 0x00000000L, TV_SWORD, '|', 0, 400, 17, 1, 120, 0, 0, 0, 0, {3, 4}, 18}, // 44 {"& Longsword", 0x00000000L, TV_SWORD, '|', 0, 200, 18, 1, 130, 0, 0, 0, 0, {1, 10}, 12}, // 45 {"& Two-Handed Sword (No-Dachi)", 0x00000000L, TV_SWORD, '|', 0, 675, 19, 1, 200, 0, 0, 0, 0, {4, 4}, 45}, // 46 {"& Rapier", 0x00000000L, TV_SWORD, '|', 0, 42, 20, 1, 40, 0, 0, 0, 0, {1, 6}, 4}, // 47 {"& Sabre", 0x00000000L, TV_SWORD, '|', 0, 50, 21, 1, 50, 0, 0, 0, 0, {1, 7}, 5}, // 48 {"& Small Sword", 0x00000000L, TV_SWORD, '|', 0, 48, 22, 1, 75, 0, 0, 0, 0, {1, 6}, 5}, // 49 {"& Two-Handed Sword (Zweihander)", 0x00000000L, TV_SWORD, '|', 0, 1500, 23, 1, 280, 0, 0, 0, 0, {4, 6}, 50}, // 50 {"& Broken Sword", 0x00000000L, TV_SWORD, '|', 0, 0, 24, 1, 75, -2, -2, 0, 0, {1, 1}, 0}, // 51 {"& Ball and Chain", 0x00000000L, TV_HAFTED, '\\', 0, 200, 1, 1, 150, 0, 0, 0, 0, {2, 4}, 20}, // 52 {"& Cat-o'-Nine-Tails", 0x00000000L, TV_HAFTED, '\\', 0, 14, 2, 1, 40, 0, 0, 0, 0, {1, 4}, 3}, // 53 {"& Wooden Club", 0x00000000L, TV_HAFTED, '\\', 0, 10, 3, 1, 100, 0, 0, 0, 0, {1, 3}, 0}, // 54 {"& Flail", 0x00000000L, TV_HAFTED, '\\', 0, 353, 4, 1, 150, 0, 0, 0, 0, {2, 6}, 12}, // 55 {"& Two-Handed Great Flail", 0x00000000L, TV_HAFTED, '\\', 0, 590, 5, 1, 280, 0, 0, 0, 0, {3, 6}, 45}, // 56 {"& Morningstar", 0x00000000L, TV_HAFTED, '\\', 0, 396, 6, 1, 150, 0, 0, 0, 0, {2, 6}, 10}, // 57 {"& Mace", 0x00000000L, TV_HAFTED, '\\', 0, 130, 7, 1, 120, 0, 0, 0, 0, {2, 4}, 6}, // 58 {"& War Hammer", 0x00000000L, TV_HAFTED, '\\', 0, 225, 8, 1, 120, 0, 0, 0, 0, {3, 3}, 5}, // 59 {"& Lead-Filled Mace", 0x00000000L, TV_HAFTED, '\\', 0, 502, 9, 1, 180, 0, 0, 0, 0, {3, 4}, 15}, // 60 {"& Awl-Pike", 0x00000000L, TV_POLEARM, '/', 0, 200, 1, 1, 160, 0, 0, 0, 0, {1, 8}, 8}, // 61 {"& Beaked Axe", 0x00000000L, TV_POLEARM, '/', 0, 408, 2, 1, 180, 0, 0, 0, 0, {2, 6}, 15}, // 62 {"& Fauchard", 0x00000000L, TV_POLEARM, '/', 0, 326, 3, 1, 170, 0, 0, 0, 0, {1, 10}, 17}, // 63 {"& Glaive", 0x00000000L, TV_POLEARM, '/', 0, 363, 4, 1, 190, 0, 0, 0, 0, {2, 6}, 20}, // 64 {"& Halberd", 0x00000000L, TV_POLEARM, '/', 0, 430, 5, 1, 190, 0, 0, 0, 0, {3, 4}, 22}, // 65 {"& Lucerne Hammer", 0x00000000L, TV_POLEARM, '/', 0, 376, 6, 1, 120, 0, 0, 0, 0, {2, 5}, 11}, // 66 {"& Pike", 0x00000000L, TV_POLEARM, '/', 0, 358, 7, 1, 160, 0, 0, 0, 0, {2, 5}, 15}, // 67 {"& Spear", 0x00000000L, TV_POLEARM, '/', 0, 36, 8, 1, 50, 0, 0, 0, 0, {1, 6}, 5}, // 68 {"& Lance", 0x00000000L, TV_POLEARM, '/', 0, 230, 9, 1, 300, 0, 0, 0, 0, {2, 8}, 10}, // 69 {"& Javelin", 0x00000000L, TV_POLEARM, '/', 0, 18, 10, 1, 30, 0, 0, 0, 0, {1, 4}, 4}, // 70 {"& Battle Axe (Balestarius)", 0x00000000L, TV_POLEARM, '/', 0, 500, 11, 1, 180, 0, 0, 0, 0, {2, 8}, 30}, // 71 {"& Battle Axe (European)", 0x00000000L, TV_POLEARM, '/', 0, 334, 12, 1, 170, 0, 0, 0, 0, {3, 4}, 13}, // 72 {"& Broad Axe", 0x00000000L, TV_POLEARM, '/', 0, 304, 13, 1, 160, 0, 0, 0, 0, {2, 6}, 17}, // 73 {"& Short Bow", 0x00000000L, TV_BOW, '}', 2, 50, 1, 1, 30, 0, 0, 0, 0, {0, 0}, 3}, // 74 {"& Long Bow", 0x00000000L, TV_BOW, '}', 3, 120, 2, 1, 40, 0, 0, 0, 0, {0, 0}, 10}, // 75 {"& Composite Bow", 0x00000000L, TV_BOW, '}', 4, 240, 3, 1, 40, 0, 0, 0, 0, {0, 0}, 40}, // 76 {"& Light Crossbow", 0x00000000L, TV_BOW, '}', 5, 140, 10, 1, 110, 0, 0, 0, 0, {0, 0}, 15}, // 77 {"& Heavy Crossbow", 0x00000000L, TV_BOW, '}', 6, 300, 11, 1, 200, 0, 0, 0, 0, {1, 1}, 30}, // 78 {"& Sling", 0x00000000L, TV_BOW, '}', 1, 5, 20, 1, 5, 0, 0, 0, 0, {0, 0}, 1}, // 79 {"& Arrow~", 0x00000000L, TV_ARROW, '{', 0, 1, 193, 1, 2, 0, 0, 0, 0, {1, 4}, 2}, // 80 {"& Bolt~", 0x00000000L, TV_BOLT, '{', 0, 2, 193, 1, 3, 0, 0, 0, 0, {1, 5}, 2}, // 81 {"& Rounded Pebble~", 0x00000000L, TV_SLING_AMMO, '{', 0, 1, 193, 1, 4, 0, 0, 0, 0, {1, 2}, 0}, // 82 {"& Iron Shot~", 0x00000000L, TV_SLING_AMMO, '{', 0, 2, 194, 1, 5, 0, 0, 0, 0, {1, 3}, 3}, // 83 {"& Iron Spike~", 0x00000000L, TV_SPIKE, '~', 0, 1, 193, 1, 10, 0, 0, 0, 0, {1, 1}, 1}, // 84 {"& Brass Lantern~", 0x00000000L, TV_LIGHT, '~', 7500, 35, 1, 1, 50, 0, 0, 0, 0, {1, 1}, 1}, // 85 {"& Wooden Torch~", 0x00000000L, TV_LIGHT, '~', 4000, 2, 193, 1, 30, 0, 0, 0, 0, {1, 1}, 1}, // 86 {"& Orcish Pick", 0x20000000L, TV_DIGGING, '\\', 2, 500, 2, 1, 180, 0, 0, 0, 0, {1, 3}, 20}, // 87 {"& Dwarven Pick", 0x20000000L, TV_DIGGING, '\\', 3, 1200, 3, 1, 200, 0, 0, 0, 0, {1, 4}, 50}, // 88 {"& Gnomish Shovel", 0x20000000L, TV_DIGGING, '\\', 1, 100, 5, 1, 50, 0, 0, 0, 0, {1, 2}, 20}, // 89 {"& Dwarven Shovel", 0x20000000L, TV_DIGGING, '\\', 2, 250, 6, 1, 120, 0, 0, 0, 0, {1, 3}, 40}, // 90 {"& Pair of Soft Leather Shoes", 0x00000000L, TV_BOOTS, ']', 0, 4, 1, 1, 5, 0, 0, 1, 0, {0, 0}, 1}, // 91 {"& Pair of Soft Leather Boots", 0x00000000L, TV_BOOTS, ']', 0, 7, 2, 1, 20, 0, 0, 2, 0, {1, 1}, 4}, // 92 {"& Pair of Hard Leather Boots", 0x00000000L, TV_BOOTS, ']', 0, 12, 3, 1, 40, 0, 0, 3, 0, {1, 1}, 6}, // 93 {"& Soft Leather Cap", 0x00000000L, TV_HELM, ']', 0, 4, 1, 1, 10, 0, 0, 1, 0, {0, 0}, 2}, // 94 {"& Hard Leather Cap", 0x00000000L, TV_HELM, ']', 0, 12, 2, 1, 15, 0, 0, 2, 0, {0, 0}, 4}, // 95 {"& Metal Cap", 0x00000000L, TV_HELM, ']', 0, 30, 3, 1, 20, 0, 0, 3, 0, {1, 1}, 7}, // 96 {"& Iron Helm", 0x00000000L, TV_HELM, ']', 0, 75, 4, 1, 75, 0, 0, 5, 0, {1, 3}, 20}, // 97 {"& Steel Helm", 0x00000000L, TV_HELM, ']', 0, 200, 5, 1, 60, 0, 0, 6, 0, {1, 3}, 40}, // 98 {"& Silver Crown", 0x00000000L, TV_HELM, ']', 0, 500, 6, 1, 20, 0, 0, 0, 0, {1, 1}, 44}, // 99 {"& Golden Crown", 0x00000000L, TV_HELM, ']', 0, 1000, 7, 1, 30, 0, 0, 0, 0, {1, 2}, 47}, // 100 {"& Jewel-Encrusted Crown", 0x00000000L, TV_HELM, ']', 0, 2000, 8, 1, 40, 0, 0, 0, 0, {1, 3}, 50}, // 101 {"& Robe", 0x00000000L, TV_SOFT_ARMOR, '(', 0, 4, 1, 1, 20, 0, 0, 2, 0, {0, 0}, 1}, // 102 {"Soft Leather Armor", 0x00000000L, TV_SOFT_ARMOR, '(', 0, 18, 2, 1, 80, 0, 0, 4, 0, {0, 0}, 2}, // 103 {"Soft Studded Leather", 0x00000000L, TV_SOFT_ARMOR, '(', 0, 35, 3, 1, 90, 0, 0, 5, 0, {1, 1}, 3}, // 104 {"Hard Leather Armor", 0x00000000L, TV_SOFT_ARMOR, '(', 0, 55, 4, 1, 100, -1, 0, 6, 0, {1, 1}, 5}, // 105 {"Hard Studded Leather", 0x00000000L, TV_SOFT_ARMOR, '(', 0, 100, 5, 1, 110, -1, 0, 7, 0, {1, 2}, 7}, // 106 {"Woven Cord Armor", 0x00000000L, TV_SOFT_ARMOR, '(', 0, 45, 6, 1, 150, -1, 0, 6, 0, {0, 0}, 7}, // 107 {"Soft Leather Ring Mail", 0x00000000L, TV_SOFT_ARMOR, '(', 0, 160, 7, 1, 130, -1, 0, 6, 0, {1, 2}, 10}, // 108 {"Hard Leather Ring Mail", 0x00000000L, TV_SOFT_ARMOR, '(', 0, 230, 8, 1, 150, -2, 0, 8, 0, {1, 3}, 12}, // 109 {"Leather Scale Mail", 0x00000000L, TV_SOFT_ARMOR, '(', 0, 330, 9, 1, 140, -1, 0, 11, 0, {1, 1}, 14}, // 110 {"Metal Scale Mail", 0x00000000L, TV_HARD_ARMOR, '[', 0, 430, 1, 1, 250, -2, 0, 13, 0, {1, 4}, 24}, // 111 {"Chain Mail", 0x00000000L, TV_HARD_ARMOR, '[', 0, 530, 2, 1, 220, -2, 0, 14, 0, {1, 4}, 26}, // 112 {"Rusty Chain Mail", 0x00000000L, TV_HARD_ARMOR, '[', 0, 0, 3, 1, 220, -5, 0, 14, -8, {1, 4}, 26}, // 113 {"Double Chain Mail", 0x00000000L, TV_HARD_ARMOR, '[', 0, 630, 4, 1, 260, -2, 0, 15, 0, {1, 4}, 28}, // 114 {"Augmented Chain Mail", 0x00000000L, TV_HARD_ARMOR, '[', 0, 675, 5, 1, 270, -2, 0, 16, 0, {1, 4}, 30}, // 115 {"Bar Chain Mail", 0x00000000L, TV_HARD_ARMOR, '[', 0, 720, 6, 1, 280, -2, 0, 18, 0, {1, 4}, 34}, // 116 {"Metal Brigandine Armor", 0x00000000L, TV_HARD_ARMOR, '[', 0, 775, 7, 1, 290, -3, 0, 19, 0, {1, 4}, 36}, // 117 {"Laminated Armor", 0x00000000L, TV_HARD_ARMOR, '[', 0, 825, 8, 1, 300, -3, 0, 20, 0, {1, 4}, 38}, // 118 {"Partial Plate Armor", 0x00000000L, TV_HARD_ARMOR, '[', 0, 900, 9, 1, 320, -3, 0, 22, 0, {1, 6}, 42}, // 119 {"Metal Lamellar Armor", 0x00000000L, TV_HARD_ARMOR, '[', 0, 950, 10, 1, 340, -3, 0, 23, 0, {1, 6}, 44}, // 120 {"Full Plate Armor", 0x00000000L, TV_HARD_ARMOR, '[', 0, 1050, 11, 1, 380, -3, 0, 25, 0, {2, 4}, 48}, // 121 {"Ribbed Plate Armor", 0x00000000L, TV_HARD_ARMOR, '[', 0, 1200, 12, 1, 380, -3, 0, 28, 0, {2, 4}, 50}, // 122 {"& Cloak", 0x00000000L, TV_CLOAK, '(', 0, 3, 1, 1, 10, 0, 0, 1, 0, {0, 0}, 1}, // 123 {"& Set of Leather Gloves", 0x00000000L, TV_GLOVES, ']', 0, 3, 1, 1, 5, 0, 0, 1, 0, {0, 0}, 1}, // 124 {"& Set of Gauntlets", 0x00000000L, TV_GLOVES, ']', 0, 35, 2, 1, 25, 0, 0, 2, 0, {1, 1}, 12}, // 125 {"& Small Leather Shield", 0x00000000L, TV_SHIELD, ')', 0, 30, 1, 1, 50, 0, 0, 2, 0, {1, 1}, 3}, // 126 {"& Medium Leather Shield", 0x00000000L, TV_SHIELD, ')', 0, 60, 2, 1, 75, 0, 0, 3, 0, {1, 2}, 8}, // 127 {"& Large Leather Shield", 0x00000000L, TV_SHIELD, ')', 0, 120, 3, 1, 100, 0, 0, 4, 0, {1, 2}, 15}, // 128 {"& Small Metal Shield", 0x00000000L, TV_SHIELD, ')', 0, 50, 4, 1, 65, 0, 0, 3, 0, {1, 2}, 10}, // 129 {"& Medium Metal Shield", 0x00000000L, TV_SHIELD, ')', 0, 125, 5, 1, 90, 0, 0, 4, 0, {1, 3}, 20}, // 130 {"& Large Metal Shield", 0x00000000L, TV_SHIELD, ')', 0, 200, 6, 1, 120, 0, 0, 5, 0, {1, 3}, 30}, // 131 {"Strength", 0x00000001L, TV_RING, '=', 0, 400, 0, 1, 2, 0, 0, 0, 0, {0, 0}, 30}, // 132 {"Dexterity", 0x00000008L, TV_RING, '=', 0, 400, 1, 1, 2, 0, 0, 0, 0, {0, 0}, 30}, // 133 {"Constitution", 0x00000010L, TV_RING, '=', 0, 400, 2, 1, 2, 0, 0, 0, 0, {0, 0}, 30}, // 134 {"Intelligence", 0x00000002L, TV_RING, '=', 0, 400, 3, 1, 2, 0, 0, 0, 0, {0, 0}, 30}, // 135 {"Speed", 0x00001000L, TV_RING, '=', 0, 3000, 4, 1, 2, 0, 0, 0, 0, {0, 0}, 50}, // 136 {"Searching", 0x00000040L, TV_RING, '=', 0, 250, 5, 1, 2, 0, 0, 0, 0, {0, 0}, 7}, // 137 {"Teleportation", 0x80000400L, TV_RING, '=', 0, 0, 6, 1, 2, 0, 0, 0, 0, {0, 0}, 7}, // 138 {"Slow Digestion", 0x00000080L, TV_RING, '=', 0, 200, 7, 1, 2, 0, 0, 0, 0, {0, 0}, 7}, // 139 {"Resist Fire", 0x00080000L, TV_RING, '=', 0, 250, 8, 1, 2, 0, 0, 0, 0, {0, 0}, 14}, // 140 {"Resist Cold", 0x00200000L, TV_RING, '=', 0, 250, 9, 1, 2, 0, 0, 0, 0, {0, 0}, 14}, // 141 {"Feather Falling", 0x04000000L, TV_RING, '=', 0, 200, 10, 1, 2, 0, 0, 0, 0, {0, 0}, 7}, // 142 {"Adornment", 0x00000000L, TV_RING, '=', 0, 20, 11, 1, 2, 0, 0, 0, 0, {0, 0}, 7}, // 143 // was a ring of adornment, sub_category_id = 12 here {"& Arrow~", 0x00000000L, TV_ARROW, '{', 0, 1, 193, 1, 2, 0, 0, 0, 0, {1, 4}, 15}, // 144 {"Weakness", 0x80000001L, TV_RING, '=', -5, 0, 13, 1, 2, 0, 0, 0, 0, {0, 0}, 7}, // 145 {"Lordly Protection (FIRE)", 0x00080000L, TV_RING, '=', 0, 1200, 14, 1, 2, 0, 0, 0, 5, {0, 0}, 50}, // 146 {"Lordly Protection (ACID)", 0x00100000L, TV_RING, '=', 0, 1200, 15, 1, 2, 0, 0, 0, 5, {0, 0}, 50}, // 147 {"Lordly Protection (COLD)", 0x00200000L, TV_RING, '=', 0, 1200, 16, 1, 2, 0, 0, 0, 5, {0, 0}, 50}, // 148 {"WOE", 0x80000644L, TV_RING, '=', -5, 0, 17, 1, 2, 0, 0, 0, -3, {0, 0}, 50}, // 149 {"Stupidity", 0x80000002L, TV_RING, '=', -5, 0, 18, 1, 2, 0, 0, 0, 0, {0, 0}, 7}, // 150 {"Increase Damage", 0x00000000L, TV_RING, '=', 0, 100, 19, 1, 2, 0, 0, 0, 0, {0, 0}, 20}, // 151 {"Increase To-Hit", 0x00000000L, TV_RING, '=', 0, 100, 20, 1, 2, 0, 0, 0, 0, {0, 0}, 20}, // 152 {"Protection", 0x00000000L, TV_RING, '=', 0, 100, 21, 1, 2, 0, 0, 0, 0, {0, 0}, 7}, // 153 {"Aggravate Monster", 0x80000200L, TV_RING, '=', 0, 0, 22, 1, 2, 0, 0, 0, 0, {0, 0}, 7}, // 154 {"See Invisible", 0x01000000L, TV_RING, '=', 0, 500, 23, 1, 2, 0, 0, 0, 0, {0, 0}, 40}, // 155 {"Sustain Strength", 0x00400000L, TV_RING, '=', 1, 750, 24, 1, 2, 0, 0, 0, 0, {0, 0}, 44}, // 156 {"Sustain Intelligence", 0x00400000L, TV_RING, '=', 2, 600, 25, 1, 2, 0, 0, 0, 0, {0, 0}, 44}, // 157 {"Sustain Wisdom", 0x00400000L, TV_RING, '=', 3, 600, 26, 1, 2, 0, 0, 0, 0, {0, 0}, 44}, // 158 {"Sustain Constitution", 0x00400000L, TV_RING, '=', 4, 750, 27, 1, 2, 0, 0, 0, 0, {0, 0}, 44}, // 159 {"Sustain Dexterity", 0x00400000L, TV_RING, '=', 5, 750, 28, 1, 2, 0, 0, 0, 0, {0, 0}, 44}, // 160 {"Sustain Charisma", 0x00400000L, TV_RING, '=', 6, 500, 29, 1, 2, 0, 0, 0, 0, {0, 0}, 44}, // 161 {"Slaying", 0x00000000L, TV_RING, '=', 0, 1000, 30, 1, 2, 0, 0, 0, 0, {0, 0}, 50}, // 162 {"Wisdom", 0x00000004L, TV_AMULET, '"', 0, 300, 0, 1, 3, 0, 0, 0, 0, {0, 0}, 20}, // 163 {"Charisma", 0x00000020L, TV_AMULET, '"', 0, 250, 1, 1, 3, 0, 0, 0, 0, {0, 0}, 20}, // 164 {"Searching", 0x00000040L, TV_AMULET, '"', 0, 250, 2, 1, 3, 0, 0, 0, 0, {0, 0}, 14}, // 165 {"Teleportation", 0x80000400L, TV_AMULET, '"', 0, 0, 3, 1, 3, 0, 0, 0, 0, {0, 0}, 14}, // 166 {"Slow Digestion", 0x00000080L, TV_AMULET, '"', 0, 200, 4, 1, 3, 0, 0, 0, 0, {0, 0}, 14}, // 167 {"Resist Acid", 0x00100000L, TV_AMULET, '"', 0, 250, 5, 1, 3, 0, 0, 0, 0, {0, 0}, 24}, // 168 {"Adornment", 0x00000000L, TV_AMULET, '"', 0, 20, 6, 1, 3, 0, 0, 0, 0, {0, 0}, 16}, // 169 // was an amulet of adornment here, sub_category_id = 7 {"& Bolt~", 0x00000000L, TV_BOLT, '{', 0, 2, 193, 1, 3, 0, 0, 0, 0, {1, 5}, 25}, // 170 {"the Magi", 0x01800040L, TV_AMULET, '"', 0, 5000, 8, 1, 3, 0, 0, 0, 3, {0, 0}, 50}, // 171 {"DOOM", 0x8000007FL, TV_AMULET, '"', -5, 0, 9, 1, 3, 0, 0, 0, 0, {0, 0}, 50}, // 172 {"Enchant Weapon To-Hit", 0x00000001L, TV_SCROLL1, '?', 0, 125, 64, 1, 5, 0, 0, 0, 0, {0, 0}, 12}, // 173 {"Enchant Weapon To-Dam", 0x00000002L, TV_SCROLL1, '?', 0, 125, 65, 1, 5, 0, 0, 0, 0, {0, 0}, 12}, // 174 {"Enchant Armor", 0x00000004L, TV_SCROLL1, '?', 0, 125, 66, 1, 5, 0, 0, 0, 0, {0, 0}, 12}, // 175 {"Identify", 0x00000008L, TV_SCROLL1, '?', 0, 50, 67, 1, 5, 0, 0, 0, 0, {0, 0}, 1}, // 176 {"Identify", 0x00000008L, TV_SCROLL1, '?', 0, 50, 67, 1, 5, 0, 0, 0, 0, {0, 0}, 5}, // 177 {"Identify", 0x00000008L, TV_SCROLL1, '?', 0, 50, 67, 1, 5, 0, 0, 0, 0, {0, 0}, 10}, // 178 {"Identify", 0x00000008L, TV_SCROLL1, '?', 0, 50, 67, 1, 5, 0, 0, 0, 0, {0, 0}, 30}, // 179 {"Remove Curse", 0x00000010L, TV_SCROLL1, '?', 0, 100, 68, 1, 5, 0, 0, 0, 0, {0, 0}, 7}, // 180 {"Light", 0x00000020L, TV_SCROLL1, '?', 0, 15, 69, 1, 5, 0, 0, 0, 0, {0, 0}, 0}, // 181 {"Light", 0x00000020L, TV_SCROLL1, '?', 0, 15, 69, 1, 5, 0, 0, 0, 0, {0, 0}, 3}, // 182 {"Light", 0x00000020L, TV_SCROLL1, '?', 0, 15, 69, 1, 5, 0, 0, 0, 0, {0, 0}, 7}, // 183 {"Summon Monster", 0x00000040L, TV_SCROLL1, '?', 0, 0, 70, 1, 5, 0, 0, 0, 0, {0, 0}, 1}, // 184 {"Phase Door", 0x00000080L, TV_SCROLL1, '?', 0, 15, 71, 1, 5, 0, 0, 0, 0, {0, 0}, 1}, // 185 {"Teleport", 0x00000100L, TV_SCROLL1, '?', 0, 40, 72, 1, 5, 0, 0, 0, 0, {0, 0}, 10}, // 186 {"Teleport Level", 0x00000200L, TV_SCROLL1, '?', 0, 50, 73, 1, 5, 0, 0, 0, 0, {0, 0}, 20}, // 187 {"Monster Confusion", 0x00000400L, TV_SCROLL1, '?', 0, 30, 74, 1, 5, 0, 0, 0, 0, {0, 0}, 5}, // 188 {"Magic Mapping", 0x00000800L, TV_SCROLL1, '?', 0, 40, 75, 1, 5, 0, 0, 0, 0, {0, 0}, 5}, // 189 {"Sleep Monster", 0x00001000L, TV_SCROLL1, '?', 0, 35, 76, 1, 5, 0, 0, 0, 0, {0, 0}, 5}, // 190 {"Rune of Protection", 0x00002000L, TV_SCROLL1, '?', 0, 500, 77, 1, 5, 0, 0, 0, 0, {0, 0}, 50}, // 191 {"Treasure Detection", 0x00004000L, TV_SCROLL1, '?', 0, 15, 78, 1, 5, 0, 0, 0, 0, {0, 0}, 0}, // 192 {"Object Detection", 0x00008000L, TV_SCROLL1, '?', 0, 15, 79, 1, 5, 0, 0, 0, 0, {0, 0}, 0}, // 193 {"Trap Detection", 0x00010000L, TV_SCROLL1, '?', 0, 35, 80, 1, 5, 0, 0, 0, 0, {0, 0}, 5}, // 194 {"Trap Detection", 0x00010000L, TV_SCROLL1, '?', 0, 35, 80, 1, 5, 0, 0, 0, 0, {0, 0}, 8}, // 195 {"Trap Detection", 0x00010000L, TV_SCROLL1, '?', 0, 35, 80, 1, 5, 0, 0, 0, 0, {0, 0}, 12}, // 196 {"Door/Stair Location", 0x00020000L, TV_SCROLL1, '?', 0, 35, 81, 1, 5, 0, 0, 0, 0, {0, 0}, 5}, // 197 {"Door/Stair Location", 0x00020000L, TV_SCROLL1, '?', 0, 35, 81, 1, 5, 0, 0, 0, 0, {0, 0}, 10}, // 198 {"Door/Stair Location", 0x00020000L, TV_SCROLL1, '?', 0, 35, 81, 1, 5, 0, 0, 0, 0, {0, 0}, 15}, // 199 {"Mass Genocide", 0x00040000L, TV_SCROLL1, '?', 0, 1000, 82, 1, 5, 0, 0, 0, 0, {0, 0}, 50}, // 200 {"Detect Invisible", 0x00080000L, TV_SCROLL1, '?', 0, 15, 83, 1, 5, 0, 0, 0, 0, {0, 0}, 1}, // 201 {"Aggravate Monster", 0x00100000L, TV_SCROLL1, '?', 0, 0, 84, 1, 5, 0, 0, 0, 0, {0, 0}, 5}, // 202 {"Trap Creation", 0x00200000L, TV_SCROLL1, '?', 0, 0, 85, 1, 5, 0, 0, 0, 0, {0, 0}, 12}, // 203 {"Trap/Door Destruction", 0x00400000L, TV_SCROLL1, '?', 0, 50, 86, 1, 5, 0, 0, 0, 0, {0, 0}, 12}, // 204 {"Door Creation", 0x00800000L, TV_SCROLL1, '?', 0, 100, 87, 1, 5, 0, 0, 0, 0, {0, 0}, 12}, // 205 {"Recharging", 0x01000000L, TV_SCROLL1, '?', 0, 200, 88, 1, 5, 0, 0, 0, 0, {0, 0}, 40}, // 206 {"Genocide", 0x02000000L, TV_SCROLL1, '?', 0, 750, 89, 1, 5, 0, 0, 0, 0, {0, 0}, 35}, // 207 {"Darkness", 0x04000000L, TV_SCROLL1, '?', 0, 0, 90, 1, 5, 0, 0, 0, 0, {0, 0}, 1}, // 208 {"Protection from Evil", 0x08000000L, TV_SCROLL1, '?', 0, 100, 91, 1, 5, 0, 0, 0, 0, {0, 0}, 30}, // 209 {"Create Food", 0x10000000L, TV_SCROLL1, '?', 0, 10, 92, 1, 5, 0, 0, 0, 0, {0, 0}, 5}, // 210 {"Dispel Undead", 0x20000000L, TV_SCROLL1, '?', 0, 200, 93, 1, 5, 0, 0, 0, 0, {0, 0}, 40}, // 211 {"*Enchant Weapon*", 0x00000001L, TV_SCROLL2, '?', 0, 500, 94, 1, 5, 0, 0, 0, 0, {0, 0}, 50}, // 212 {"Curse Weapon", 0x00000002L, TV_SCROLL2, '?', 0, 0, 95, 1, 5, 0, 0, 0, 0, {0, 0}, 50}, // 213 {"*Enchant Armor*", 0x00000004L, TV_SCROLL2, '?', 0, 500, 96, 1, 5, 0, 0, 0, 0, {0, 0}, 50}, // 214 {"Curse Armor", 0x00000008L, TV_SCROLL2, '?', 0, 0, 97, 1, 5, 0, 0, 0, 0, {0, 0}, 50}, // 215 {"Summon Undead", 0x00000010L, TV_SCROLL2, '?', 0, 0, 98, 1, 5, 0, 0, 0, 0, {0, 0}, 15}, // 216 {"Blessing", 0x00000020L, TV_SCROLL2, '?', 0, 15, 99, 1, 5, 0, 0, 0, 0, {0, 0}, 1}, // 217 {"Holy Chant", 0x00000040L, TV_SCROLL2, '?', 0, 40, 100, 1, 5, 0, 0, 0, 0, {0, 0}, 12}, // 218 {"Holy Prayer", 0x00000080L, TV_SCROLL2, '?', 0, 80, 101, 1, 5, 0, 0, 0, 0, {0, 0}, 24}, // 219 {"Word-of-Recall", 0x00000100L, TV_SCROLL2, '?', 0, 150, 102, 1, 5, 0, 0, 0, 0, {0, 0}, 5}, // 220 {"*Destruction*", 0x00000200L, TV_SCROLL2, '?', 0, 750, 103, 1, 5, 0, 0, 0, 0, {0, 0}, 40}, // 221 // SMJ, AJ, Water must be sub_category_id 64-66 resp. for itemDescription to work {"Slime Mold Juice", 0x30000000L, TV_POTION1, '!', 400, 2, 64, 1, 4, 0, 0, 0, 0, {1, 1}, 0}, // 222 {"Apple Juice", 0x00000000L, TV_POTION1, '!', 250, 1, 65, 1, 4, 0, 0, 0, 0, {1, 1}, 0}, // 223 {"Water", 0x00000000L, TV_POTION1, '!', 200, 0, 66, 1, 4, 0, 0, 0, 0, {1, 1}, 0}, // 224 {"Strength", 0x00000001L, TV_POTION1, '!', 50, 300, 67, 1, 4, 0, 0, 0, 0, {1, 1}, 25}, // 225 {"Weakness", 0x00000002L, TV_POTION1, '!', 0, 0, 68, 1, 4, 0, 0, 0, 0, {1, 1}, 3}, // 226 {"Restore Strength", 0x00000004L, TV_POTION1, '!', 0, 300, 69, 1, 4, 0, 0, 0, 0, {1, 1}, 40}, // 227 {"Intelligence", 0x00000008L, TV_POTION1, '!', 0, 300, 70, 1, 4, 0, 0, 0, 0, {1, 1}, 25}, // 228 {"Lose Intelligence", 0x00000010L, TV_POTION1, '!', 0, 0, 71, 1, 4, 0, 0, 0, 0, {1, 1}, 25}, // 229 {"Restore Intelligence", 0x00000020L, TV_POTION1, '!', 0, 300, 72, 1, 4, 0, 0, 0, 0, {1, 1}, 40}, // 230 {"Wisdom", 0x00000040L, TV_POTION1, '!', 0, 300, 73, 1, 4, 0, 0, 0, 0, {1, 1}, 25}, // 231 {"Lose Wisdom", 0x00000080L, TV_POTION1, '!', 0, 0, 74, 1, 4, 0, 0, 0, 0, {1, 1}, 25}, // 232 {"Restore Wisdom", 0x00000100L, TV_POTION1, '!', 0, 300, 75, 1, 4, 0, 0, 0, 0, {1, 1}, 40}, // 233 {"Charisma", 0x00000200L, TV_POTION1, '!', 0, 300, 76, 1, 4, 0, 0, 0, 0, {1, 1}, 25}, // 234 {"Ugliness", 0x00000400L, TV_POTION1, '!', 0, 0, 77, 1, 4, 0, 0, 0, 0, {1, 1}, 25}, // 235 {"Restore Charisma", 0x00000800L, TV_POTION1, '!', 0, 300, 78, 1, 4, 0, 0, 0, 0, {1, 1}, 40}, // 236 {"Cure Light Wounds", 0x10001000L, TV_POTION1, '!', 50, 15, 79, 1, 4, 0, 0, 0, 0, {1, 1}, 0}, // 237 {"Cure Light Wounds", 0x10001000L, TV_POTION1, '!', 50, 15, 79, 1, 4, 0, 0, 0, 0, {1, 1}, 1}, // 238 {"Cure Light Wounds", 0x10001000L, TV_POTION1, '!', 50, 15, 79, 1, 4, 0, 0, 0, 0, {1, 1}, 2}, // 239 {"Cure Serious Wounds", 0x30002000L, TV_POTION1, '!', 100, 40, 80, 1, 4, 0, 0, 0, 0, {1, 1}, 3}, // 240 {"Cure Critical Wounds", 0x70004000L, TV_POTION1, '!', 100, 100, 81, 1, 4, 0, 0, 0, 0, {1, 1}, 5}, // 241 {"Healing", 0x70008000L, TV_POTION1, '!', 200, 200, 82, 1, 4, 0, 0, 0, 0, {1, 1}, 12}, // 242 {"Constitution", 0x00010000L, TV_POTION1, '!', 50, 300, 83, 1, 4, 0, 0, 0, 0, {1, 1}, 25}, // 243 {"Gain Experience", 0x00020000L, TV_POTION1, '!', 0, 2500, 84, 1, 4, 0, 0, 0, 0, {1, 1}, 50}, // 244 {"Sleep", 0x00040000L, TV_POTION1, '!', 100, 0, 85, 1, 4, 0, 0, 0, 0, {1, 1}, 0}, // 245 {"Blindness", 0x00080000L, TV_POTION1, '!', 0, 0, 86, 1, 4, 0, 0, 0, 0, {1, 1}, 0}, // 246 {"Confusion", 0x00100000L, TV_POTION1, '!', 50, 0, 87, 1, 4, 0, 0, 0, 0, {1, 1}, 0}, // 247 {"Poison", 0x00200000L, TV_POTION1, '!', 0, 0, 88, 1, 4, 0, 0, 0, 0, {1, 1}, 3}, // 248 {"Haste Self", 0x00400000L, TV_POTION1, '!', 0, 75, 89, 1, 4, 0, 0, 0, 0, {1, 1}, 1}, // 249 {"Slowness", 0x00800000L, TV_POTION1, '!', 50, 0, 90, 1, 4, 0, 0, 0, 0, {1, 1}, 1}, // 250 {"Dexterity", 0x02000000L, TV_POTION1, '!', 0, 300, 91, 1, 4, 0, 0, 0, 0, {1, 1}, 25}, // 251 {"Restore Dexterity", 0x04000000L, TV_POTION1, '!', 0, 300, 92, 1, 4, 0, 0, 0, 0, {1, 1}, 40}, // 252 {"Restore Constitution", 0x68000000L, TV_POTION1, '!', 0, 300, 93, 1, 4, 0, 0, 0, 0, {1, 1}, 40}, // 253 {"Lose Experience", 0x00000002L, TV_POTION2, '!', 0, 0, 95, 1, 4, 0, 0, 0, 0, {1, 1}, 10}, // 254 {"Salt Water", 0x00000004L, TV_POTION2, '!', 0, 0, 96, 1, 4, 0, 0, 0, 0, {1, 1}, 0}, // 255 {"Invulnerability", 0x00000008L, TV_POTION2, '!', 0, 1000, 97, 1, 4, 0, 0, 0, 0, {1, 1}, 40}, // 256 {"Heroism", 0x00000010L, TV_POTION2, '!', 0, 35, 98, 1, 4, 0, 0, 0, 0, {1, 1}, 1}, // 257 {"Super Heroism", 0x00000020L, TV_POTION2, '!', 0, 100, 99, 1, 4, 0, 0, 0, 0, {1, 1}, 3}, // 258 {"Boldness", 0x00000040L, TV_POTION2, '!', 0, 10, 100, 1, 4, 0, 0, 0, 0, {1, 1}, 1}, // 259 {"Restore Life Levels", 0x00000080L, TV_POTION2, '!', 0, 400, 101, 1, 4, 0, 0, 0, 0, {1, 1}, 40}, // 260 {"Resist Heat", 0x00000100L, TV_POTION2, '!', 0, 30, 102, 1, 4, 0, 0, 0, 0, {1, 1}, 1}, // 261 {"Resist Cold", 0x00000200L, TV_POTION2, '!', 0, 30, 103, 1, 4, 0, 0, 0, 0, {1, 1}, 1}, // 262 {"Detect Invisible", 0x00000400L, TV_POTION2, '!', 0, 50, 104, 1, 4, 0, 0, 0, 0, {1, 1}, 3}, // 263 {"Slow Poison", 0x00000800L, TV_POTION2, '!', 0, 25, 105, 1, 4, 0, 0, 0, 0, {1, 1}, 1}, // 264 {"Neutralize Poison", 0x00001000L, TV_POTION2, '!', 0, 75, 106, 1, 4, 0, 0, 0, 0, {1, 1}, 5}, // 265 {"Restore Mana", 0x00002000L, TV_POTION2, '!', 0, 350, 107, 1, 4, 0, 0, 0, 0, {1, 1}, 25}, // 266 {"Infra-Vision", 0x00004000L, TV_POTION2, '!', 0, 20, 108, 1, 4, 0, 0, 0, 0, {1, 1}, 3}, // 267 {"& Flask~ of Oil", 0x00040000L, TV_FLASK, '!', 7500, 3, 64, 1, 10, 0, 0, 0, 0, {2, 6}, 1}, // 268 {"Light", 0x00000001L, TV_WAND, '-', 0, 200, 0, 1, 10, 0, 0, 0, 0, {1, 1}, 2}, // 269 {"Lightning Bolts", 0x00000002L, TV_WAND, '-', 0, 600, 1, 1, 10, 0, 0, 0, 0, {1, 1}, 15}, // 270 {"Frost Bolts", 0x00000004L, TV_WAND, '-', 0, 800, 2, 1, 10, 0, 0, 0, 0, {1, 1}, 20}, // 271 {"Fire Bolts", 0x00000008L, TV_WAND, '-', 0, 1000, 3, 1, 10, 0, 0, 0, 0, {1, 1}, 30}, // 272 {"Stone-to-Mud", 0x00000010L, TV_WAND, '-', 0, 300, 4, 1, 10, 0, 0, 0, 0, {1, 1}, 12}, // 273 {"Polymorph", 0x00000020L, TV_WAND, '-', 0, 400, 5, 1, 10, 0, 0, 0, 0, {1, 1}, 20}, // 274 {"Heal Monster", 0x00000040L, TV_WAND, '-', 0, 0, 6, 1, 10, 0, 0, 0, 0, {1, 1}, 2}, // 275 {"Haste Monster", 0x00000080L, TV_WAND, '-', 0, 0, 7, 1, 10, 0, 0, 0, 0, {1, 1}, 2}, // 276 {"Slow Monster", 0x00000100L, TV_WAND, '-', 0, 500, 8, 1, 10, 0, 0, 0, 0, {1, 1}, 2}, // 277 {"Confuse Monster", 0x00000200L, TV_WAND, '-', 0, 400, 9, 1, 10, 0, 0, 0, 0, {1, 1}, 2}, // 278 {"Sleep Monster", 0x00000400L, TV_WAND, '-', 0, 500, 10, 1, 10, 0, 0, 0, 0, {1, 1}, 7}, // 279 {"Drain Life", 0x00000800L, TV_WAND, '-', 0, 1200, 11, 1, 10, 0, 0, 0, 0, {1, 1}, 50}, // 280 {"Trap/Door Destruction", 0x00001000L, TV_WAND, '-', 0, 500, 12, 1, 10, 0, 0, 0, 0, {1, 1}, 12}, // 281 {"Magic Missile", 0x00002000L, TV_WAND, '-', 0, 200, 13, 1, 10, 0, 0, 0, 0, {1, 1}, 2}, // 282 {"Wall Building", 0x00004000L, TV_WAND, '-', 0, 400, 14, 1, 10, 0, 0, 0, 0, {1, 1}, 25}, // 283 {"Clone Monster", 0x00008000L, TV_WAND, '-', 0, 0, 15, 1, 10, 0, 0, 0, 0, {1, 1}, 15}, // 284 {"Teleport Away", 0x00010000L, TV_WAND, '-', 0, 350, 16, 1, 10, 0, 0, 0, 0, {1, 1}, 20}, // 285 {"Disarming", 0x00020000L, TV_WAND, '-', 0, 500, 17, 1, 10, 0, 0, 0, 0, {1, 1}, 20}, // 286 {"Lightning Balls", 0x00040000L, TV_WAND, '-', 0, 1200, 18, 1, 10, 0, 0, 0, 0, {1, 1}, 35}, // 287 {"Cold Balls", 0x00080000L, TV_WAND, '-', 0, 1500, 19, 1, 10, 0, 0, 0, 0, {1, 1}, 40}, // 288 {"Fire Balls", 0x00100000L, TV_WAND, '-', 0, 1800, 20, 1, 10, 0, 0, 0, 0, {1, 1}, 50}, // 289 {"Stinking Cloud", 0x00200000L, TV_WAND, '-', 0, 400, 21, 1, 10, 0, 0, 0, 0, {1, 1}, 5}, // 290 {"Acid Balls", 0x00400000L, TV_WAND, '-', 0, 1650, 22, 1, 10, 0, 0, 0, 0, {1, 1}, 48}, // 291 {"Wonder", 0x00800000L, TV_WAND, '-', 0, 250, 23, 1, 10, 0, 0, 0, 0, {1, 1}, 2}, // 292 {"Light", 0x00000001L, TV_STAFF, '_', 0, 250, 0, 1, 50, 0, 0, 0, 0, {1, 2}, 5}, // 293 {"Door/Stair Location", 0x00000002L, TV_STAFF, '_', 0, 350, 1, 1, 50, 0, 0, 0, 0, {1, 2}, 10}, // 294 {"Trap Location", 0x00000004L, TV_STAFF, '_', 0, 350, 2, 1, 50, 0, 0, 0, 0, {1, 2}, 10}, // 295 {"Treasure Location", 0x00000008L, TV_STAFF, '_', 0, 200, 3, 1, 50, 0, 0, 0, 0, {1, 2}, 5}, // 296 {"Object Location", 0x00000010L, TV_STAFF, '_', 0, 200, 4, 1, 50, 0, 0, 0, 0, {1, 2}, 5}, // 297 {"Teleportation", 0x00000020L, TV_STAFF, '_', 0, 800, 5, 1, 50, 0, 0, 0, 0, {1, 2}, 20}, // 298 {"Earthquakes", 0x00000040L, TV_STAFF, '_', 0, 350, 6, 1, 50, 0, 0, 0, 0, {1, 2}, 40}, // 299 {"Summoning", 0x00000080L, TV_STAFF, '_', 0, 0, 7, 1, 50, 0, 0, 0, 0, {1, 2}, 10}, // 300 {"Summoning", 0x00000080L, TV_STAFF, '_', 0, 0, 7, 1, 50, 0, 0, 0, 0, {1, 2}, 50}, // 301 {"*Destruction*", 0x00000200L, TV_STAFF, '_', 0, 2500, 8, 1, 50, 0, 0, 0, 0, {1, 2}, 50}, // 302 {"Starlight", 0x00000400L, TV_STAFF, '_', 0, 400, 9, 1, 50, 0, 0, 0, 0, {1, 2}, 20}, // 303 {"Haste Monsters", 0x00000800L, TV_STAFF, '_', 0, 0, 10, 1, 50, 0, 0, 0, 0, {1, 2}, 10}, // 304 {"Slow Monsters", 0x00001000L, TV_STAFF, '_', 0, 800, 11, 1, 50, 0, 0, 0, 0, {1, 2}, 10}, // 305 {"Sleep Monsters", 0x00002000L, TV_STAFF, '_', 0, 700, 12, 1, 50, 0, 0, 0, 0, {1, 2}, 10}, // 306 {"Cure Light Wounds", 0x00004000L, TV_STAFF, '_', 0, 200, 13, 1, 50, 0, 0, 0, 0, {1, 2}, 5}, // 307 {"Detect Invisible", 0x00008000L, TV_STAFF, '_', 0, 200, 14, 1, 50, 0, 0, 0, 0, {1, 2}, 5}, // 308 {"Speed", 0x00010000L, TV_STAFF, '_', 0, 1000, 15, 1, 50, 0, 0, 0, 0, {1, 2}, 40}, // 309 {"Slowness", 0x00020000L, TV_STAFF, '_', 0, 0, 16, 1, 50, 0, 0, 0, 0, {1, 2}, 40}, // 310 {"Mass Polymorph", 0x00040000L, TV_STAFF, '_', 0, 750, 17, 1, 50, 0, 0, 0, 0, {1, 2}, 46}, // 311 {"Remove Curse", 0x00080000L, TV_STAFF, '_', 0, 500, 18, 1, 50, 0, 0, 0, 0, {1, 2}, 47}, // 312 {"Detect Evil", 0x00100000L, TV_STAFF, '_', 0, 350, 19, 1, 50, 0, 0, 0, 0, {1, 2}, 20}, // 313 {"Curing", 0x00200000L, TV_STAFF, '_', 0, 1000, 20, 1, 50, 0, 0, 0, 0, {1, 2}, 25}, // 314 {"Dispel Evil", 0x00400000L, TV_STAFF, '_', 0, 1200, 21, 1, 50, 0, 0, 0, 0, {1, 2}, 49}, // 315 {"Darkness", 0x01000000L, TV_STAFF, '_', 0, 0, 22, 1, 50, 0, 0, 0, 0, {1, 2}, 50}, // 316 {"Darkness", 0x01000000L, TV_STAFF, '_', 0, 0, 22, 1, 50, 0, 0, 0, 0, {1, 2}, 5}, // 317 {"[Beginners-Magick]", 0x0000007FL, TV_MAGIC_BOOK, '?', 0, 25, 64, 1, 30, 0, 0, 0, 0, {1, 1}, 40}, // 318 {"[Magick I]", 0x0000FF80L, TV_MAGIC_BOOK, '?', 0, 100, 65, 1, 30, 0, 0, 0, 0, {1, 1}, 40}, // 319 {"[Magick II]", 0x00FF0000L, TV_MAGIC_BOOK, '?', 0, 400, 66, 1, 30, 0, 0, 0, 0, {1, 1}, 40}, // 320 {"[The Mages' Guide to Power]", 0x7F000000L, TV_MAGIC_BOOK, '?', 0, 800, 67, 1, 30, 0, 0, 0, 0, {1, 1}, 40}, // 321 {"[Beginners Handbook]", 0x000000FFL, TV_PRAYER_BOOK, '?', 0, 25, 64, 1, 30, 0, 0, 0, 0, {1, 1}, 40}, // 322 {"[Words of Wisdom]", 0x0000FF00L, TV_PRAYER_BOOK, '?', 0, 100, 65, 1, 30, 0, 0, 0, 0, {1, 1}, 40}, // 323 {"[Chants and Blessings]", 0x01FF0000L, TV_PRAYER_BOOK, '?', 0, 400, 66, 1, 30, 0, 0, 0, 0, {1, 1}, 40}, // 324 {"[Exorcisms and Dispellings]", 0x7E000000L, TV_PRAYER_BOOK, '?', 0, 800, 67, 1, 30, 0, 0, 0, 0, {1, 1}, 40}, // 325 {"& Small Wooden Chest", 0x13800000L, TV_CHEST, '&', 0, 20, 1, 1, 250, 0, 0, 0, 0, {2, 3}, 7}, // 326 {"& Large Wooden Chest", 0x17800000L, TV_CHEST, '&', 0, 60, 4, 1, 500, 0, 0, 0, 0, {2, 5}, 15}, // 327 {"& Small Iron Chest", 0x17800000L, TV_CHEST, '&', 0, 100, 7, 1, 500, 0, 0, 0, 0, {2, 4}, 25}, // 328 {"& Large Iron Chest", 0x23800000L, TV_CHEST, '&', 0, 150, 10, 1, 1000, 0, 0, 0, 0, {2, 6}, 35}, // 329 {"& Small Steel Chest", 0x1B800000L, TV_CHEST, '&', 0, 200, 13, 1, 500, 0, 0, 0, 0, {2, 4}, 45}, // 330 {"& Large Steel Chest", 0x33800000L, TV_CHEST, '&', 0, 250, 16, 1, 1000, 0, 0, 0, 0, {2, 6}, 50}, // 331 {"& Rat Skeleton", 0x00000000L, TV_MISC, 's', 0, 0, 1, 1, 10, 0, 0, 0, 0, {1, 1}, 1}, // 332 {"& Giant Centipede Skeleton", 0x00000000L, TV_MISC, 's', 0, 0, 2, 1, 25, 0, 0, 0, 0, {1, 1}, 1}, // 333 {"some Filthy Rags", 0x00000000L, TV_SOFT_ARMOR, '~', 0, 0, 63, 1, 20, 0, 0, 1, 0, {0, 0}, 0}, // 334 {"& empty bottle", 0x00000000L, TV_MISC, '!', 0, 0, 4, 1, 2, 0, 0, 0, 0, {1, 1}, 0}, // 335 {"some shards of pottery", 0x00000000L, TV_MISC, '~', 0, 0, 5, 1, 5, 0, 0, 0, 0, {1, 1}, 0}, // 336 {"& Human Skeleton", 0x00000000L, TV_MISC, 's', 0, 0, 7, 1, 60, 0, 0, 0, 0, {1, 1}, 1}, // 337 {"& Dwarf Skeleton", 0x00000000L, TV_MISC, 's', 0, 0, 8, 1, 50, 0, 0, 0, 0, {1, 1}, 1}, // 338 {"& Elf Skeleton", 0x00000000L, TV_MISC, 's', 0, 0, 9, 1, 40, 0, 0, 0, 0, {1, 1}, 1}, // 339 {"& Gnome Skeleton", 0x00000000L, TV_MISC, 's', 0, 0, 10, 1, 25, 0, 0, 0, 0, {1, 1}, 1}, // 340 {"& broken set of teeth", 0x00000000L, TV_MISC, 's', 0, 0, 11, 1, 3, 0, 0, 0, 0, {1, 1}, 0}, // 341 {"& large broken bone", 0x00000000L, TV_MISC, 's', 0, 0, 12, 1, 2, 0, 0, 0, 0, {1, 1}, 0}, // 342 {"& broken stick", 0x00000000L, TV_MISC, '~', 0, 0, 13, 1, 3, 0, 0, 0, 0, {1, 1}, 0}, // 343 // end of Dungeon items // Store items, which are not also dungeon items, some of these // can be found above, except that the number is >1 below. {"& Ration~ of Food", 0x00000000L, TV_FOOD, ',', 5000, 3, 90, 5, 10, 0, 0, 0, 0, {0, 0}, 0}, // 344 {"& Hard Biscuit~", 0x00000000L, TV_FOOD, ',', 500, 1, 93, 5, 2, 0, 0, 0, 0, {0, 0}, 0}, // 345 {"& Strip~ of Beef Jerky", 0x00000000L, TV_FOOD, ',', 1750, 2, 94, 5, 4, 0, 0, 0, 0, {0, 0}, 0}, // 346 {"& Pint~ of Fine Ale", 0x00000000L, TV_FOOD, ',', 500, 1, 95, 3, 10, 0, 0, 0, 0, {0, 0}, 0}, // 347 {"& Pint~ of Fine Wine", 0x00000000L, TV_FOOD, ',', 400, 2, 96, 1, 10, 0, 0, 0, 0, {0, 0}, 0}, // 348 {"& Pick", 0x20000000L, TV_DIGGING, '\\', 1, 50, 1, 1, 150, 0, 0, 0, 0, {1, 3}, 0}, // 349 {"& Shovel", 0x20000000L, TV_DIGGING, '\\', 0, 15, 4, 1, 60, 0, 0, 0, 0, {1, 2}, 0}, // 350 {"Identify", 0x00000008L, TV_SCROLL1, '?', 0, 50, 67, 2, 5, 0, 0, 0, 0, {0, 0}, 0}, // 351 {"Light", 0x00000020L, TV_SCROLL1, '?', 0, 15, 69, 3, 5, 0, 0, 0, 0, {0, 0}, 0}, // 352 {"Phase Door", 0x00000080L, TV_SCROLL1, '?', 0, 15, 71, 2, 5, 0, 0, 0, 0, {0, 0}, 0}, // 353 {"Magic Mapping", 0x00000800L, TV_SCROLL1, '?', 0, 40, 75, 2, 5, 0, 0, 0, 0, {0, 0}, 0}, // 354 {"Treasure Detection", 0x00004000L, TV_SCROLL1, '?', 0, 15, 78, 2, 5, 0, 0, 0, 0, {0, 0}, 0}, // 355 {"Object Detection", 0x00008000L, TV_SCROLL1, '?', 0, 15, 79, 2, 5, 0, 0, 0, 0, {0, 0}, 0}, // 356 {"Detect Invisible", 0x00080000L, TV_SCROLL1, '?', 0, 15, 83, 2, 5, 0, 0, 0, 0, {0, 0}, 0}, // 357 {"Blessing", 0x00000020L, TV_SCROLL2, '?', 0, 15, 99, 2, 5, 0, 0, 0, 0, {0, 0}, 0}, // 358 {"Word-of-Recall", 0x00000100L, TV_SCROLL2, '?', 0, 150, 102, 3, 5, 0, 0, 0, 0, {0, 0}, 0}, // 359 {"Cure Light Wounds", 0x10001000L, TV_POTION1, '!', 50, 15, 79, 2, 4, 0, 0, 0, 0, {1, 1}, 0}, // 360 {"Heroism", 0x00000010L, TV_POTION2, '!', 0, 35, 98, 2, 4, 0, 0, 0, 0, {1, 1}, 0}, // 361 {"Boldness", 0x00000040L, TV_POTION2, '!', 0, 10, 100, 2, 4, 0, 0, 0, 0, {1, 1}, 0}, // 362 {"Slow Poison", 0x00000800L, TV_POTION2, '!', 0, 25, 105, 2, 4, 0, 0, 0, 0, {1, 1}, 0}, // 363 {"& Brass Lantern~", 0x00000000L, TV_LIGHT, '~', 7500, 35, 0, 1, 50, 0, 0, 0, 0, {1, 1}, 1}, // 364 {"& Wooden Torch~", 0x00000000L, TV_LIGHT, '~', 4000, 2, 192, 5, 30, 0, 0, 0, 0, {1, 1}, 1}, // 365 {"& Flask~ of Oil", 0x00040000L, TV_FLASK, '!', 7500, 3, 64, 5, 10, 0, 0, 0, 0, {2, 6}, 1}, // 366 // end store items // start doors // Secret door must have same sub_category_id as closed door in // TRAP_LISTB. See CHANGE_TRAP. Must use & because of stone_to_mud. {"& open door", 0x00000000L, TV_OPEN_DOOR, '\'', 0, 0, 1, 1, 0, 0, 0, 0, 0, {1, 1}, 0}, // 367 {"& closed door", 0x00000000L, TV_CLOSED_DOOR, '+', 0, 0, 19, 1, 0, 0, 0, 0, 0, {1, 1}, 0}, // 368 {"& secret door", 0x00000000L, TV_SECRET_DOOR, '#', 0, 0, 19, 1, 0, 0, 0, 0, 0, {1, 1}, 0}, // 369 // end doors // stairs {"an up staircase", 0x00000000L, TV_UP_STAIR, '<', 0, 0, 1, 1, 0, 0, 0, 0, 0, {1, 1}, 0}, // 370 {"a down staircase", 0x00000000L, TV_DOWN_STAIR, '>', 0, 0, 1, 1, 0, 0, 0, 0, 0, {1, 1}, 0}, // 371 // store door // Stores are just special traps {"General Store", 0x00000000L, TV_STORE_DOOR, '1', 0, 0, 101, 1, 0, 0, 0, 0, 0, {0, 0}, 0}, // 372 {"Armory", 0x00000000L, TV_STORE_DOOR, '2', 0, 0, 102, 1, 0, 0, 0, 0, 0, {0, 0}, 0}, // 373 {"Weapon Smiths", 0x00000000L, TV_STORE_DOOR, '3', 0, 0, 103, 1, 0, 0, 0, 0, 0, {0, 0}, 0}, // 374 {"Temple", 0x00000000L, TV_STORE_DOOR, '4', 0, 0, 104, 1, 0, 0, 0, 0, 0, {0, 0}, 0}, // 375 {"Alchemy Shop", 0x00000000L, TV_STORE_DOOR, '5', 0, 0, 105, 1, 0, 0, 0, 0, 0, {0, 0}, 0}, // 376 {"Magic Shop", 0x00000000L, TV_STORE_DOOR, '6', 0, 0, 106, 1, 0, 0, 0, 0, 0, {0, 0}, 0}, // 377 // end store door // Traps are just Nasty treasures. // Traps: Level represents the relative difficulty of disarming; // and `misc_use` represents the experienced gained when disarmed {"an open pit", 0x00000000L, TV_VIS_TRAP, ' ', 1, 0, 1, 1, 0, 0, 0, 0, 0, {2, 6}, 50}, // 378 {"an arrow trap", 0x00000000L, TV_INVIS_TRAP, '^', 3, 0, 2, 1, 0, 0, 0, 0, 0, {1, 8}, 90}, // 379 {"a covered pit", 0x00000000L, TV_INVIS_TRAP, '^', 2, 0, 3, 1, 0, 0, 0, 0, 0, {2, 6}, 60}, // 380 {"a trap door", 0x00000000L, TV_INVIS_TRAP, '^', 5, 0, 4, 1, 0, 0, 0, 0, 0, {2, 8}, 75}, // 381 {"a gas trap", 0x00000000L, TV_INVIS_TRAP, '^', 3, 0, 5, 1, 0, 0, 0, 0, 0, {1, 4}, 95}, // 382 {"a loose rock", 0x00000000L, TV_INVIS_TRAP, ';', 0, 0, 6, 1, 0, 0, 0, 0, 0, {0, 0}, 10}, // 383 {"a dart trap", 0x00000000L, TV_INVIS_TRAP, '^', 5, 0, 7, 1, 0, 0, 0, 0, 0, {1, 4}, 110}, // 384 {"a strange rune", 0x00000000L, TV_INVIS_TRAP, '^', 5, 0, 8, 1, 0, 0, 0, 0, 0, {0, 0}, 90}, // 385 {"some loose rock", 0x00000000L, TV_INVIS_TRAP, '^', 5, 0, 9, 1, 0, 0, 0, 0, 0, {2, 6}, 90}, // 386 {"a gas trap", 0x00000000L, TV_INVIS_TRAP, '^', 10, 0, 10, 1, 0, 0, 0, 0, 0, {1, 4}, 105}, // 387 {"a strange rune", 0x00000000L, TV_INVIS_TRAP, '^', 5, 0, 11, 1, 0, 0, 0, 0, 0, {0, 0}, 90}, // 388 {"a blackened spot", 0x00000000L, TV_INVIS_TRAP, '^', 10, 0, 12, 1, 0, 0, 0, 0, 0, {4, 6}, 110}, // 389 {"some corroded rock", 0x00000000L, TV_INVIS_TRAP, '^', 10, 0, 13, 1, 0, 0, 0, 0, 0, {4, 6}, 110}, // 390 {"a gas trap", 0x00000000L, TV_INVIS_TRAP, '^', 5, 0, 14, 1, 0, 0, 0, 0, 0, {2, 6}, 105}, // 391 {"a gas trap", 0x00000000L, TV_INVIS_TRAP, '^', 5, 0, 15, 1, 0, 0, 0, 0, 0, {1, 4}, 110}, // 392 {"a gas trap", 0x00000000L, TV_INVIS_TRAP, '^', 5, 0, 16, 1, 0, 0, 0, 0, 0, {1, 8}, 105}, // 393 {"a dart trap", 0x00000000L, TV_INVIS_TRAP, '^', 5, 0, 17, 1, 0, 0, 0, 0, 0, {1, 8}, 110}, // 394 {"a dart trap", 0x00000000L, TV_INVIS_TRAP, '^', 5, 0, 18, 1, 0, 0, 0, 0, 0, {1, 8}, 110}, // 395 // rubble {"some rubble", 0x00000000L, TV_RUBBLE, ':', 0, 0, 1, 1, 0, 0, 0, 0, 0, {0, 0}, 0}, // 396 // mush {"& Pint~ of Fine Grade Mush", 0x00000000L, TV_FOOD, ',', 1500, 1, 97, 1, 1, 0, 0, 0, 0, {1, 1}, 1}, // 397 // Special trap {"a strange rune", 0x00000000L, TV_VIS_TRAP, '^', 0, 0, 99, 1, 0, 0, 0, 0, 0, {0, 0}, 10}, // 398 // Gold list (All types of gold and gems are defined here) {"copper", 0x00000000L, TV_GOLD, '$', 0, 3, 1, 1, 0, 0, 0, 0, 0, {0, 0}, 1}, // 399 {"copper", 0x00000000L, TV_GOLD, '$', 0, 4, 2, 1, 0, 0, 0, 0, 0, {0, 0}, 1}, // 400 {"copper", 0x00000000L, TV_GOLD, '$', 0, 5, 3, 1, 0, 0, 0, 0, 0, {0, 0}, 1}, // 401 {"silver", 0x00000000L, TV_GOLD, '$', 0, 6, 4, 1, 0, 0, 0, 0, 0, {0, 0}, 1}, // 402 {"silver", 0x00000000L, TV_GOLD, '$', 0, 7, 5, 1, 0, 0, 0, 0, 0, {0, 0}, 1}, // 403 {"silver", 0x00000000L, TV_GOLD, '$', 0, 8, 6, 1, 0, 0, 0, 0, 0, {0, 0}, 1}, // 404 {"garnets", 0x00000000L, TV_GOLD, '*', 0, 9, 7, 1, 0, 0, 0, 0, 0, {0, 0}, 1}, // 405 {"garnets", 0x00000000L, TV_GOLD, '*', 0, 10, 8, 1, 0, 0, 0, 0, 0, {0, 0}, 1}, // 406 {"gold", 0x00000000L, TV_GOLD, '$', 0, 12, 9, 1, 0, 0, 0, 0, 0, {0, 0}, 1}, // 407 {"gold", 0x00000000L, TV_GOLD, '$', 0, 14, 10, 1, 0, 0, 0, 0, 0, {0, 0}, 1}, // 408 {"gold", 0x00000000L, TV_GOLD, '$', 0, 16, 11, 1, 0, 0, 0, 0, 0, {0, 0}, 1}, // 409 {"opals", 0x00000000L, TV_GOLD, '*', 0, 18, 12, 1, 0, 0, 0, 0, 0, {0, 0}, 1}, // 410 {"sapphires", 0x00000000L, TV_GOLD, '*', 0, 20, 13, 1, 0, 0, 0, 0, 0, {0, 0}, 1}, // 411 {"gold", 0x00000000L, TV_GOLD, '$', 0, 24, 14, 1, 0, 0, 0, 0, 0, {0, 0}, 1}, // 412 {"rubies", 0x00000000L, TV_GOLD, '*', 0, 28, 15, 1, 0, 0, 0, 0, 0, {0, 0}, 1}, // 413 {"diamonds", 0x00000000L, TV_GOLD, '*', 0, 32, 16, 1, 0, 0, 0, 0, 0, {0, 0}, 1}, // 414 {"emeralds", 0x00000000L, TV_GOLD, '*', 0, 40, 17, 1, 0, 0, 0, 0, 0, {0, 0}, 1}, // 415 {"mithril", 0x00000000L, TV_GOLD, '$', 0, 80, 18, 1, 0, 0, 0, 0, 0, {0, 0}, 1}, // 416 // nothing, used as inventory place holder // must be stackable, so that can be picked up by inventoryCarryItem {"nothing", 0x00000000L, TV_NOTHING, ' ', 0, 0, 64, 0, 0, 0, 0, 0, 0, {0, 0}, 0}, // 417 // these next two are needed only for the names {"& ruined chest", 0x00000000L, TV_CHEST, '&', 0, 0, 0, 1, 250, 0, 0, 0, 0, {0, 0}, 0}, // 418 {"", 0x00000000L, TV_NOTHING, ' ', 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0}, 0} // 419 }; const char *special_item_names[special_name_ids::SN_ARRAY_SIZE] = { CNIL, "(R)", "(RA)", "(RF)", "(RC)", "(RL)", "(HA)", "(DF)", "(SA)", "(SD)", "(SE)", "(SU)", "(FT)", "(FB)", "of Free Action", "of Slaying", "of Clumsiness", "of Weakness", "of Slow Descent", "of Speed", "of Stealth", "of Slowness", "of Noise", "of Great Mass", "of Intelligence", "of Wisdom", "of Infra-Vision", "of Might", "of Lordliness", "of the Magi", "of Beauty", "of Seeing", "of Regeneration", "of Stupidity", "of Dullness", "of Blindness", "of Timidness", "of Teleportation", "of Ugliness", "of Protection", "of Irritation", "of Vulnerability", "of Enveloping", "of Fire", "of Slay Evil", "of Dragon Slaying", "(Empty)", "(Locked)", "(Poison Needle)", "(Gas Trap)", "(Explosion Device)", "(Summoning Runes)", "(Multiple Traps)", "(Disarmed)", "(Unlocked)", "of Slay Animal" }; umoria-5.7.10+20181022/src/scrolls.h0000644000175000017500000000046013363422757015402 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. #pragma once void scrollRead(); umoria-5.7.10+20181022/src/scores.cpp0000644000175000017500000002127413363422757015560 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. // Handle reading, writing, and displaying of high scores. #include "headers.h" #include "version.h" // High score file pointer FILE *highscore_fp; static uint8_t highScoreGenderLabel() { if (playerIsMale()) { return 'M'; } return 'F'; } // Enters a players name on the top twenty list -JWT- void recordNewHighScore() { clearScreen(); if (game.noscore != 0) { return; } if (panic_save) { printMessage("Sorry, scores for games restored from panic save files are not saved."); return; } HighScore_t new_entry{}; new_entry.points = playerCalculateTotalPoints(); new_entry.birth_date = py.misc.date_of_birth; new_entry.uid = 0; // NOTE: do we not want to use `getuid()`? -MRC- new_entry.mhp = py.misc.max_hp; new_entry.chp = py.misc.current_hp; new_entry.dungeon_depth = (uint8_t) dg.current_level; new_entry.level = (uint8_t) py.misc.level; new_entry.deepest_dungeon_depth = (uint8_t) py.misc.max_dungeon_depth; new_entry.gender = highScoreGenderLabel(); new_entry.race = py.misc.race_id; new_entry.character_class = py.misc.class_id; (void) strcpy(new_entry.name, py.misc.name); char *tmp = game.character_died_from; if ('a' == *tmp) { if ('n' == *(++tmp)) { tmp++; } while (isspace(*tmp) != 0) { tmp++; } } (void) strcpy(new_entry.died_from, tmp); if ((highscore_fp = fopen(config::files::scores.c_str(), "rb+")) == nullptr) { printMessage(("Error opening score file '" + config::files::scores + "'.").c_str()); printMessage(CNIL); return; } // Search file to find where to insert this character, if uid != 0 and // find same uid/gender/race/class combo then exit without saving this score. // Seek to the beginning of the file just to be safe. (void) fseek(highscore_fp, (long) 0, SEEK_SET); // Read version numbers from the score file, and check for validity. auto version_maj = (uint8_t) getc(highscore_fp); auto version_min = (uint8_t) getc(highscore_fp); auto patch_level = (uint8_t) getc(highscore_fp); // If this is a new score file, it should be empty. // Write the current version numbers to the score file. if (feof(highscore_fp) != 0) { // Seek to the beginning of the file just to be safe. (void) fseek(highscore_fp, (long) 0, SEEK_SET); (void) putc(CURRENT_VERSION_MAJOR, highscore_fp); (void) putc(CURRENT_VERSION_MINOR, highscore_fp); (void) putc(CURRENT_VERSION_PATCH, highscore_fp); // must fseek() before can change read/write mode (void) fseek(highscore_fp, (long) 0, SEEK_CUR); } else if (!validGameVersion(version_maj, version_min, patch_level)) { // No need to print a message, a subsequent call to // showScoresScreen() will print a message. (void) fclose(highscore_fp); return; } // set the static fileptr in save.c to the high score file pointer setFileptr(highscore_fp); HighScore_t old_entry{}; HighScore_t entry{}; int i = 0; off_t curpos = ftell(highscore_fp); readHighScore(old_entry); while (feof(highscore_fp) == 0) { if (new_entry.points >= old_entry.points) { break; } // under unix, only allow one gender/race/class combo per person, // on single user system, allow any number of entries, but try to // prevent multiple entries per character by checking for case when // birthdate/gender/race/class are the same, and game.character_died_from // of score file entry is "(saved)" if (((new_entry.uid != 0 && new_entry.uid == old_entry.uid) || (new_entry.uid == 0 && (strcmp(old_entry.died_from, "(saved)") == 0) && new_entry.birth_date == old_entry.birth_date)) && new_entry.gender == old_entry.gender && new_entry.race == old_entry.race && new_entry.character_class == old_entry.character_class) { (void) fclose(highscore_fp); return; } // only allow one thousand scores in the score file if (++i >= MAX_HIGH_SCORE_ENTRIES) { (void) fclose(highscore_fp); return; } curpos = ftell(highscore_fp); readHighScore(old_entry); } if (feof(highscore_fp) != 0) { // write out new_entry at end of file (void) fseek(highscore_fp, curpos, SEEK_SET); saveHighScore(new_entry); } else { entry = new_entry; while (feof(highscore_fp) == 0) { (void) fseek(highscore_fp, -(long) sizeof(HighScore_t) - (long) sizeof(char), SEEK_CUR); saveHighScore(entry); // under unix, only allow one gender/race/class combo per person, // on single user system, allow any number of entries, but try to // prevent multiple entries per character by checking for case when // birth_date/gender/race/class are the same, and game.character_died_from // of score file entry is "(saved)" if (((new_entry.uid != 0 && new_entry.uid == old_entry.uid) || (new_entry.uid == 0 && (strcmp(old_entry.died_from, "(saved)") == 0) && new_entry.birth_date == old_entry.birth_date)) && new_entry.gender == old_entry.gender && new_entry.race == old_entry.race && new_entry.character_class == old_entry.character_class) { break; } entry = old_entry; // must fseek() before can change read/write mode (void) fseek(highscore_fp, (long) 0, SEEK_CUR); curpos = ftell(highscore_fp); readHighScore(old_entry); } if (feof(highscore_fp) != 0) { (void) fseek(highscore_fp, curpos, SEEK_SET); saveHighScore(entry); } } (void) fclose(highscore_fp); } void showScoresScreen() { if ((highscore_fp = fopen(config::files::scores.c_str(), "rb")) == nullptr) { printMessage(("Error opening score file '" + config::files::scores + "'.").c_str()); printMessage(CNIL); return; } (void) fseek(highscore_fp, (off_t) 0, SEEK_SET); // Read version numbers from the score file, and check for validity. auto version_maj = (uint8_t) getc(highscore_fp); auto version_min = (uint8_t) getc(highscore_fp); auto patch_level = (uint8_t) getc(highscore_fp); // If score data present, check if a valid game version if (feof(highscore_fp) == 0 && !validGameVersion(version_maj, version_min, patch_level)) { printMessage("Sorry. This score file is from a different version of umoria."); printMessage(CNIL); (void) fclose(highscore_fp); return; } // set the static fileptr in save.c to the high score file pointer setFileptr(highscore_fp); HighScore_t score{}; readHighScore(score); char input; char msg[100]; int i = 0; int rank = 1; while (feof(highscore_fp) == 0) { i = 1; clearScreen(); // Put twenty scores on each page, on lines 2 through 21. while ((feof(highscore_fp) == 0) && i < 21) { (void) sprintf( msg, "%-4d%8d %-19.19s %c %-10.10s %-7.7s%3d %-22.22s", rank, score.points, score.name, score.gender, character_races[score.race].name, classes[score.character_class].title, score.level, score.died_from ); i++; putStringClearToEOL(msg, Coord_t{i, 0}); rank++; readHighScore(score); } putStringClearToEOL("Rank Points Name Sex Race Class Lvl Killed By", Coord_t{0, 0}); eraseLine(Coord_t{1, 0}); putStringClearToEOL("[ press any key to continue ]", Coord_t{23, 23}); input = getKeyInput(); if (input == ESCAPE) { break; } } (void) fclose(highscore_fp); } // Calculates the total number of points earned -JWT- int32_t playerCalculateTotalPoints() { int32_t total = py.misc.max_exp + (100 * py.misc.max_dungeon_depth); total += py.misc.au / 100; for (auto &item : inventory) { total += storeItemValue(item); } total += dg.current_level * 50; // Don't ever let the score decrease from one save to the next. if (py.max_score > total) { return py.max_score; } return total; } umoria-5.7.10+20181022/src/monster_manager.cpp0000644000175000017500000002317713363422757017447 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. // Monster management: generation, placement, cleanup #include "headers.h" Monster_t monsters[MON_TOTAL_ALLOCATIONS]; int16_t monster_levels[MON_MAX_LEVELS + 1]; // Values for a blank monster Monster_t blank_monster = {0, 0, 0, 0, 0, 0, 0, false, 0, 0}; int16_t next_free_monster_id; // ID for the next available monster ptr int16_t monster_multiply_total; // Total number of reproduction's of creatures // Returns a pointer to next free space -RAK- // Returns -1 if could not allocate a monster. static int popm() { if (next_free_monster_id == MON_TOTAL_ALLOCATIONS) { if (!compactMonsters()) { return -1; } } return next_free_monster_id++; } // Places a monster at given location -RAK- bool monsterPlaceNew(int y, int x, int creature_id, bool sleeping) { int monster_id = popm(); // Check for case where could not allocate space for the monster if (monster_id == -1) { return false; } Monster_t &monster = monsters[monster_id]; monster.y = (uint8_t) y; monster.x = (uint8_t) x; monster.creature_id = (uint16_t) creature_id; if ((creatures_list[creature_id].defenses & config::monsters::defense::CD_MAX_HP) != 0) { monster.hp = (int16_t) maxDiceRoll(creatures_list[creature_id].hit_die); } else { monster.hp = (int16_t) diceRoll(creatures_list[creature_id].hit_die); } // the creatures_list[] speed value is 10 greater, so that it can be a uint8_t monster.speed = (int16_t) (creatures_list[creature_id].speed - 10 + py.flags.speed); monster.stunned_amount = 0; monster.distance_from_player = (uint8_t) coordDistanceBetween(Coord_t{py.row, py.col}, Coord_t{y, x}); monster.lit = false; dg.floor[y][x].creature_id = (uint8_t) monster_id; if (sleeping) { if (creatures_list[creature_id].sleep_counter == 0) { monster.sleep_count = 0; } else { monster.sleep_count = (int16_t) ((creatures_list[creature_id].sleep_counter * 2) + randomNumber((int) creatures_list[creature_id].sleep_counter * 10)); } } else { monster.sleep_count = 0; } return true; } // Places a monster at given location -RAK- void monsterPlaceWinning() { if (game.total_winner) { return; } int y, x; do { y = randomNumber(dg.height - 2); x = randomNumber(dg.width - 2); } while ((dg.floor[y][x].feature_id >= MIN_CLOSED_SPACE) || (dg.floor[y][x].creature_id != 0) || (dg.floor[y][x].treasure_id != 0) || (coordDistanceBetween(Coord_t{y, x}, Coord_t{py.row, py.col}) <= config::monsters::MON_MAX_SIGHT)); int creature_id = randomNumber(config::monsters::MON_ENDGAME_MONSTERS) - 1 + monster_levels[MON_MAX_LEVELS]; // TODO: duplicate code -MRC- // The following code is now exactly the same as monsterPlaceNew() except here // we `abort()` on failed placement, and do not set `monster->lit = false`. // Perhaps we can find a way to call `monsterPlaceNew()` instead of // duplicating everything here. int monster_id = popm(); // Check for case where could not allocate space for the win monster, this should never happen. if (monster_id == -1) { abort(); } Monster_t &monster = monsters[monster_id]; monster.y = (uint8_t) y; monster.x = (uint8_t) x; monster.creature_id = (uint16_t) creature_id; if ((creatures_list[creature_id].defenses & config::monsters::defense::CD_MAX_HP) != 0) { monster.hp = (int16_t) maxDiceRoll(creatures_list[creature_id].hit_die); } else { monster.hp = (int16_t) diceRoll(creatures_list[creature_id].hit_die); } // the creatures_list speed value is 10 greater, so that it can be a uint8_t monster.speed = (int16_t) (creatures_list[creature_id].speed - 10 + py.flags.speed); monster.stunned_amount = 0; monster.distance_from_player = (uint8_t) coordDistanceBetween(Coord_t{py.row, py.col}, Coord_t{y, x}); dg.floor[y][x].creature_id = (uint8_t) monster_id; monster.sleep_count = 0; } // Return a monster suitable to be placed at a given level. This // makes high level monsters (up to the given level) slightly more // common than low level monsters at any given level. -CJS- static int monsterGetOneSuitableForLevel(int level) { if (level == 0) { return randomNumber(monster_levels[0]) - 1; } if (level > MON_MAX_LEVELS) { level = MON_MAX_LEVELS; } if (randomNumber(config::monsters::MON_CHANCE_OF_NASTY) == 1) { auto abs_distribution = (int) std::abs((std::intmax_t) randomNumberNormalDistribution(0, 4)); level += abs_distribution + 1; if (level > MON_MAX_LEVELS) { level = MON_MAX_LEVELS; } } else { // This code has been added to make it slightly more likely to get // the higher level monsters. Originally a uniform distribution over // all monsters of level less than or equal to the dungeon level. // This distribution makes a level n monster occur approx 2/n% of the // time on level n, and 1/n*n% are 1st level. int num = monster_levels[level] - monster_levels[0]; int i = randomNumber(num) - 1; int j = randomNumber(num) - 1; if (j > i) { i = j; } level = creatures_list[i + monster_levels[0]].level; } return randomNumber(monster_levels[level] - monster_levels[level - 1]) - 1 + monster_levels[level - 1]; } // Allocates a random monster -RAK- void monsterPlaceNewWithinDistance(int number, int distance_from_source, bool sleeping) { int y, x; for (int i = 0; i < number; i++) { do { y = randomNumber(dg.height - 2); x = randomNumber(dg.width - 2); } while (dg.floor[y][x].feature_id >= MIN_CLOSED_SPACE || dg.floor[y][x].creature_id != 0 || coordDistanceBetween(Coord_t{y, x}, Coord_t{py.row, py.col}) <= distance_from_source); int l = monsterGetOneSuitableForLevel(dg.current_level); // Dragons are always created sleeping here, // so as to give the player a sporting chance. if (creatures_list[l].sprite == 'd' || creatures_list[l].sprite == 'D') { sleeping = true; } // Place_monster() should always return true here. // It does not matter if it fails though. (void) monsterPlaceNew(y, x, l, sleeping); } } static bool placeMonsterAdjacentTo(int monsterID, int &y, int &x, bool slp) { bool placed = false; for (int i = 0; i <= 9; i++) { int yy = y - 2 + randomNumber(3); int xx = x - 2 + randomNumber(3); if (coordInBounds(Coord_t{yy, xx})) { if (dg.floor[yy][xx].feature_id <= MAX_OPEN_SPACE && dg.floor[yy][xx].creature_id == 0) { // Place_monster() should always return true here. if (!monsterPlaceNew(yy, xx, monsterID, slp)) { return false; } y = yy; x = xx; placed = true; i = 9; } } } return placed; } // Places creature adjacent to given location -RAK- bool monsterSummon(int &y, int &x, bool sleeping) { int monster_id = monsterGetOneSuitableForLevel(dg.current_level + config::monsters::MON_SUMMONED_LEVEL_ADJUST); return placeMonsterAdjacentTo(monster_id, y, x, sleeping); } // Places undead adjacent to given location -RAK- bool monsterSummonUndead(int &y, int &x) { int monster_id; int max_levels = monster_levels[MON_MAX_LEVELS]; do { monster_id = randomNumber(max_levels) - 1; for (int i = 0; i <= 19;) { if ((creatures_list[monster_id].defenses & config::monsters::defense::CD_UNDEAD) != 0) { i = 20; max_levels = 0; } else { monster_id++; if (monster_id > max_levels) { i = 20; } else { i++; } } } } while (max_levels != 0); return placeMonsterAdjacentTo(monster_id, y, x, false); } // Compact monsters -RAK- // Return true if any monsters were deleted, false if could not delete any monsters. bool compactMonsters() { printMessage("Compacting monsters..."); int cur_dis = 66; bool delete_any = false; while (!delete_any) { for (int i = next_free_monster_id - 1; i >= config::monsters::MON_MIN_INDEX_ID; i--) { if (cur_dis < monsters[i].distance_from_player && randomNumber(3) == 1) { if ((creatures_list[monsters[i].creature_id].movement & config::monsters::move::CM_WIN) != 0u) { // Never compact away the Balrog!! } else if (hack_monptr < i) { // in case this is called from within updateMonsters(), this is a horrible // hack, the monsters/updateMonsters() code needs to be rewritten. dungeonDeleteMonster(i); delete_any = true; } else { // dungeonDeleteMonsterFix1() does not decrement next_free_monster_id, // so don't set delete_any if this was called. dungeonDeleteMonsterFix1(i); } } } if (!delete_any) { cur_dis -= 6; // Can't delete any monsters, return failure. if (cur_dis < 0) { return false; } } } return true; } umoria-5.7.10+20181022/src/recall.cpp0000644000175000017500000005501513363422757015524 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. // Monster memory info -CJS- #include "headers.h" // Monster memories Recall_t creature_recall[MON_MAX_CREATURES]; static vtype_t roff_buffer = {'\0'}; // Line buffer. static char *roff_buffer_pointer = nullptr; // Pointer into line buffer. static int roff_print_line; // Place to print line now being loaded. #define plural(c, ss, sp) ((c) == 1 ? (ss) : (sp)) // Number of kills needed for information. // the higher the level of the monster, the fewer the attacks you need, // the more damage an attack does, the more attacks you need. #define knowdamage(l, a, d) ((4 + (l)) * (a) > 80 * (d)) // Print out strings, filling up lines as we go. static void memoryPrint(const char *p) { while (*p != 0) { *roff_buffer_pointer = *p; if (*p == '\n' || roff_buffer_pointer >= roff_buffer + sizeof(roff_buffer) - 1) { char *q = roff_buffer_pointer; if (*p != '\n') { while (*q != ' ') { q--; } } *q = 0; putStringClearToEOL(roff_buffer, Coord_t{roff_print_line, 0}); roff_print_line++; char *r = roff_buffer; while (q < roff_buffer_pointer) { q++; *r = *q; r++; } roff_buffer_pointer = r; } else { roff_buffer_pointer++; } p++; } } // Do we know anything about this monster? static bool memoryMonsterKnown(Recall_t const &memory) { if (game.wizard_mode) { return true; } if ((memory.movement != 0u) || (memory.defenses != 0u) || (memory.kills != 0u) || (memory.spells != 0u) || (memory.deaths != 0u)) { return true; } for (uint8_t attack : memory.attacks) { if (attack != 0u) { return true; } } return false; } static void memoryWizardModeInit(Recall_t &memory, Creature_t const &creature) { memory.kills = (uint16_t) MAX_SHORT; memory.wake = memory.ignore = MAX_UCHAR; uint32_t move = (uint32_t) ((creature.movement & config::monsters::move::CM_4D2_OBJ) != 0) * 8; move += (uint32_t) ((creature.movement & config::monsters::move::CM_2D2_OBJ) != 0) * 4; move += (uint32_t) ((creature.movement & config::monsters::move::CM_1D2_OBJ) != 0) * 2; move += (uint32_t) ((creature.movement & config::monsters::move::CM_90_RANDOM) != 0); move += (uint32_t) ((creature.movement & config::monsters::move::CM_60_RANDOM) != 0); memory.movement = (uint32_t) ((creature.movement & ~config::monsters::move::CM_TREASURE) | (move << config::monsters::move::CM_TR_SHIFT)); memory.defenses = creature.defenses; if ((creature.spells & config::monsters::spells::CS_FREQ) != 0u) { memory.spells = (uint32_t) (creature.spells | config::monsters::spells::CS_FREQ); } else { memory.spells = creature.spells; } for (int i = 0; i < MON_MAX_ATTACKS; i++) { if (creature.damage[i] == 0) break; memory.attacks[i] = MAX_UCHAR; } // A little hack to enable the display of info for Quylthulgs. if ((memory.movement & config::monsters::move::CM_ONLY_MAGIC) != 0u) { memory.attacks[0] = MAX_UCHAR; } } // Conflict history. static void memoryConflictHistory(uint16_t deaths, uint16_t kills) { vtype_t desc = {'\0'}; if (deaths != 0u) { (void) sprintf(desc, "%d of the contributors to your monster memory %s", deaths, plural(deaths, "has", "have")); memoryPrint(desc); memoryPrint(" been killed by this creature, and "); if (kills == 0) { memoryPrint("it is not ever known to have been defeated."); } else { (void) sprintf(desc, "at least %d of the beasts %s been exterminated.", kills, plural(kills, "has", "have")); memoryPrint(desc); } } else if (kills != 0u) { (void) sprintf(desc, "At least %d of these creatures %s", kills, plural(kills, "has", "have")); memoryPrint(desc); memoryPrint(" been killed by contributors to your monster memory."); } else { memoryPrint("No known battles to the death are recalled."); } } // Immediately obvious. static bool memoryDepthFoundAt(uint8_t level, uint16_t kills) { bool known = false; if (level == 0) { known = true; memoryPrint(" It lives in the town"); } else if (kills != 0u) { known = true; // The Balrog is a level 100 monster, but appears at 50 feet. if (level > config::monsters::MON_ENDGAME_LEVEL) { level = config::monsters::MON_ENDGAME_LEVEL; } vtype_t desc = {'\0'}; (void) sprintf(desc, " It is normally found at depths of %d feet", level * 50); memoryPrint(desc); } return known; } static bool memoryMovement(uint32_t rc_move, int monster_speed, bool is_known) { // the creatures_list speed value is 10 greater, so that it can be a uint8_t monster_speed -= 10; if ((rc_move & config::monsters::move::CM_ALL_MV_FLAGS) != 0u) { if (is_known) { memoryPrint(", and"); } else { memoryPrint(" It"); is_known = true; } memoryPrint(" moves"); if ((rc_move & config::monsters::move::CM_RANDOM_MOVE) != 0u) { memoryPrint(recall_description_how_much[(rc_move & config::monsters::move::CM_RANDOM_MOVE) >> 3]); memoryPrint(" erratically"); } if (monster_speed == 1) { memoryPrint(" at normal speed"); } else { if ((rc_move & config::monsters::move::CM_RANDOM_MOVE) != 0u) { memoryPrint(", and"); } if (monster_speed <= 0) { if (monster_speed == -1) { memoryPrint(" very"); } else if (monster_speed < -1) { memoryPrint(" incredibly"); } memoryPrint(" slowly"); } else { if (monster_speed == 3) { memoryPrint(" very"); } else if (monster_speed > 3) { memoryPrint(" unbelievably"); } memoryPrint(" quickly"); } } } if ((rc_move & config::monsters::move::CM_ATTACK_ONLY) != 0u) { if (is_known) { memoryPrint(", but"); } else { memoryPrint(" It"); is_known = true; } memoryPrint(" does not deign to chase intruders"); } if ((rc_move & config::monsters::move::CM_ONLY_MAGIC) != 0u) { if (is_known) { memoryPrint(", but"); } else { memoryPrint(" It"); is_known = true; } memoryPrint(" always moves and attacks by using magic"); } return is_known; } // Kill it once to know experience, and quality (evil, undead, monstrous). // The quality of being a dragon is obvious. static void memoryKillPoints(uint16_t creature_defense, uint16_t monster_exp, uint8_t level) { memoryPrint(" A kill of this"); if ((creature_defense & config::monsters::defense::CD_ANIMAL) != 0) { memoryPrint(" natural"); } if ((creature_defense & config::monsters::defense::CD_EVIL) != 0) { memoryPrint(" evil"); } if ((creature_defense & config::monsters::defense::CD_UNDEAD) != 0) { memoryPrint(" undead"); } // calculate the integer exp part, can be larger than 64K when first // level character looks at Balrog info, so must store in long int32_t quotient = (int32_t) monster_exp * level / py.misc.level; // calculate the fractional exp part scaled by 100, // must use long arithmetic to avoid overflow int remainder = (uint32_t) ((((int32_t) monster_exp * level % py.misc.level) * (int32_t) 1000 / py.misc.level + 5) / 10); char plural; if (quotient == 1 && remainder == 0) { plural = '\0'; } else { plural = 's'; } vtype_t desc = {'\0'}; (void) sprintf(desc, " creature is worth %d.%02d point%c", quotient, remainder, plural); memoryPrint(desc); const char *p, *q; if (py.misc.level / 10 == 1) { p = "th"; } else { int ord = py.misc.level % 10; if (ord == 1) { p = "st"; } else if (ord == 2) { p = "nd"; } else if (ord == 3) { p = "rd"; } else { p = "th"; } } if (py.misc.level == 8 || py.misc.level == 11 || py.misc.level == 18) { q = "n"; } else { q = ""; } (void) sprintf(desc, " for a%s %d%s level character.", q, py.misc.level, p); memoryPrint(desc); } // Spells known, if have been used against us. // Breath weapons or resistance might be known only because we cast spells at it. static void memoryMagicSkills(uint32_t memory_spell_flags, uint32_t monster_spell_flags, uint32_t creature_spell_flags) { bool known = true; uint32_t spell_flags = memory_spell_flags; for (int i = 0; (spell_flags & config::monsters::spells::CS_BREATHE) != 0u; i++) { if ((spell_flags & (config::monsters::spells::CS_BR_LIGHT << i)) != 0u) { spell_flags &= ~(config::monsters::spells::CS_BR_LIGHT << i); if (known) { if ((monster_spell_flags & config::monsters::spells::CS_FREQ) != 0u) { memoryPrint(" It can breathe "); } else { memoryPrint(" It is resistant to "); } known = false; } else if ((spell_flags & config::monsters::spells::CS_BREATHE) != 0u) { memoryPrint(", "); } else { memoryPrint(" and "); } memoryPrint(recall_description_breath[i]); } } known = true; for (int i = 0; (spell_flags & config::monsters::spells::CS_SPELLS) != 0u; i++) { if ((spell_flags & (config::monsters::spells::CS_TEL_SHORT << i)) != 0u) { spell_flags &= ~(config::monsters::spells::CS_TEL_SHORT << i); if (known) { if ((memory_spell_flags & config::monsters::spells::CS_BREATHE) != 0u) { memoryPrint(", and is also"); } else { memoryPrint(" It is"); } memoryPrint(" magical, casting spells which "); known = false; } else if ((spell_flags & config::monsters::spells::CS_SPELLS) != 0u) { memoryPrint(", "); } else { memoryPrint(" or "); } memoryPrint(recall_description_spell[i]); } } if ((memory_spell_flags & (config::monsters::spells::CS_BREATHE | config::monsters::spells::CS_SPELLS)) != 0u) { // Could offset by level if ((monster_spell_flags & config::monsters::spells::CS_FREQ) > 5) { vtype_t temp = {'\0'}; (void) sprintf(temp, "; 1 time in %d", creature_spell_flags & config::monsters::spells::CS_FREQ); memoryPrint(temp); } memoryPrint("."); } } // Do we know how hard they are to kill? Armor class, hit die. static void memoryKillDifficulty(Creature_t const &creature, uint32_t monster_kills) { // the higher the level of the monster, the fewer the kills you need // Original knowarmor macro inlined if (monster_kills <= 304u / (4u + creature.level)) { return; } vtype_t description = {'\0'}; (void) sprintf(description, " It has an armor rating of %d", creature.ac); memoryPrint(description); (void) sprintf(description, " and a%s life rating of %dd%d.", ((creature.defenses & config::monsters::defense::CD_MAX_HP) != 0 ? " maximized" : ""), creature.hit_die.dice, creature.hit_die.sides); memoryPrint(description); } // Do we know how clever they are? Special abilities. static void memorySpecialAbilities(uint32_t move) { bool known = true; for (int i = 0; (move & config::monsters::move::CM_SPECIAL) != 0u; i++) { if ((move & (config::monsters::move::CM_INVISIBLE << i)) != 0u) { move &= ~(config::monsters::move::CM_INVISIBLE << i); if (known) { memoryPrint(" It can "); known = false; } else if ((move & config::monsters::move::CM_SPECIAL) != 0u) { memoryPrint(", "); } else { memoryPrint(" and "); } memoryPrint(recall_description_move[i]); } } if (!known) { memoryPrint("."); } } // Do we know its special weaknesses? Most defenses flags. static void memoryWeaknesses(uint32_t defense) { bool known = true; for (int i = 0; (defense & config::monsters::defense::CD_WEAKNESS) != 0u; i++) { if ((defense & (config::monsters::defense::CD_FROST << i)) != 0u) { defense &= ~(config::monsters::defense::CD_FROST << i); if (known) { memoryPrint(" It is susceptible to "); known = false; } else if ((defense & config::monsters::defense::CD_WEAKNESS) != 0u) { memoryPrint(", "); } else { memoryPrint(" and "); } memoryPrint(recall_description_weakness[i]); } } if (!known) { memoryPrint("."); } } // Do we know how aware it is? static void memoryAwareness(Creature_t const &creature, Recall_t const &memory) { if (memory.wake * memory.wake > creature.sleep_counter || memory.ignore == MAX_UCHAR || (creature.sleep_counter == 0 && memory.kills >= 10)) { memoryPrint(" It "); if (creature.sleep_counter > 200) { memoryPrint("prefers to ignore"); } else if (creature.sleep_counter > 95) { memoryPrint("pays very little attention to"); } else if (creature.sleep_counter > 75) { memoryPrint("pays little attention to"); } else if (creature.sleep_counter > 45) { memoryPrint("tends to overlook"); } else if (creature.sleep_counter > 25) { memoryPrint("takes quite a while to see"); } else if (creature.sleep_counter > 10) { memoryPrint("takes a while to see"); } else if (creature.sleep_counter > 5) { memoryPrint("is fairly observant of"); } else if (creature.sleep_counter > 3) { memoryPrint("is observant of"); } else if (creature.sleep_counter > 1) { memoryPrint("is very observant of"); } else if (creature.sleep_counter != 0) { memoryPrint("is vigilant for"); } else { memoryPrint("is ever vigilant for"); } vtype_t text = {'\0'}; (void) sprintf(text, " intruders, which it may notice from %d feet.", 10 * creature.area_affect_radius); memoryPrint(text); } } // Do we know what it might carry? static void memoryLootCarried(uint32_t creature_move, uint32_t memory_move) { if ((memory_move & (config::monsters::move::CM_CARRY_OBJ | config::monsters::move::CM_CARRY_GOLD)) == 0u) { return; } memoryPrint(" It may"); auto carrying_chance = (uint32_t) ((memory_move & config::monsters::move::CM_TREASURE) >> config::monsters::move::CM_TR_SHIFT); if (carrying_chance == 1) { if ((creature_move & config::monsters::move::CM_TREASURE) == config::monsters::move::CM_60_RANDOM) { memoryPrint(" sometimes"); } else { memoryPrint(" often"); } } else if (carrying_chance == 2 && (creature_move & config::monsters::move::CM_TREASURE) == (config::monsters::move::CM_60_RANDOM | config::monsters::move::CM_90_RANDOM)) { memoryPrint(" often"); } memoryPrint(" carry"); const char *p = nullptr; if ((memory_move & config::monsters::move::CM_SMALL_OBJ) != 0u) { p = " small objects"; } else { p = " objects"; } if (carrying_chance == 1) { if ((memory_move & config::monsters::move::CM_SMALL_OBJ) != 0u) { p = " a small object"; } else { p = " an object"; } } else if (carrying_chance == 2) { memoryPrint(" one or two"); } else { vtype_t msg = {'\0'}; (void) sprintf(msg, " up to %d", carrying_chance); memoryPrint(msg); } if ((memory_move & config::monsters::move::CM_CARRY_OBJ) != 0u) { memoryPrint(p); if ((memory_move & config::monsters::move::CM_CARRY_GOLD) != 0u) { memoryPrint(" or treasure"); if (carrying_chance > 1) { memoryPrint("s"); } } memoryPrint("."); } else if (carrying_chance != 1) { memoryPrint(" treasures."); } else { memoryPrint(" treasure."); } } static void memoryAttackNumberAndDamage(Recall_t const &memory, Creature_t const &creature) { // We know about attacks it has used on us, and maybe the damage they do. // known_attacks is the total number of known attacks, used for punctuation int known_attacks = 0; for (uint8_t attack : memory.attacks) { if (attack != 0u) { known_attacks++; } } // attack_count counts the attacks as printed, used for punctuation int attack_count = 0; for (int i = 0; i < MON_MAX_ATTACKS; i++) { int attack_id = creature.damage[i]; if (attack_id == 0) break; // don't print out unknown attacks if (memory.attacks[i] == 0u) { continue; } uint8_t attack_type = monster_attacks[attack_id].type_id; uint8_t attack_description_id = monster_attacks[attack_id].description_id; Dice_t dice = monster_attacks[attack_id].dice; attack_count++; if (attack_count == 1) { memoryPrint(" It can "); } else if (attack_count == known_attacks) { memoryPrint(", and "); } else { memoryPrint(", "); } if (attack_description_id > 19) { attack_description_id = 0; } memoryPrint(recall_description_attack_method[attack_description_id]); if (attack_type != 1 || (dice.dice > 0 && dice.sides > 0)) { memoryPrint(" to "); if (attack_type > 24) { attack_type = 0; } memoryPrint(recall_description_attack_type[attack_type]); if ((dice.dice != 0) && (dice.sides != 0)) { if (knowdamage(creature.level, memory.attacks[i], dice.dice * dice.sides)) { // Loss of experience if (attack_type == 19) { memoryPrint(" by"); } else { memoryPrint(" with damage"); } vtype_t msg = {'\0'}; (void) sprintf(msg, " %dd%d", dice.dice, dice.sides); memoryPrint(msg); } } } } if (attack_count != 0) { memoryPrint("."); } else if (known_attacks > 0 && memory.attacks[0] >= 10) { memoryPrint(" It has no physical attacks."); } else { memoryPrint(" Nothing is known about its attack."); } } // Print out what we have discovered about this monster. int memoryRecall(int monster_id) { Recall_t &memory = creature_recall[monster_id]; Creature_t const &creature = creatures_list[monster_id]; Recall_t saved_memory{}; if (game.wizard_mode) { saved_memory = memory; memoryWizardModeInit(memory, creature); } roff_print_line = 0; roff_buffer_pointer = roff_buffer; auto spells = (uint32_t) (memory.spells & creature.spells & ~config::monsters::spells::CS_FREQ); // the config::monsters::move::CM_WIN property is always known, set it if a win monster auto move = (uint32_t) (memory.movement | (creature.movement & config::monsters::move::CM_WIN)); uint16_t defense = memory.defenses & creature.defenses; bool known; // Start the paragraph for the core monster description vtype_t msg = {'\0'}; (void) sprintf(msg, "The %s:\n", creature.name); memoryPrint(msg); memoryConflictHistory(memory.deaths, memory.kills); known = memoryDepthFoundAt(creature.level, memory.kills); known = memoryMovement(move, creature.speed, known); // Finish off the paragraph with a period! if (known) { memoryPrint("."); } if (memory.kills != 0u) { memoryKillPoints(creature.defenses, creature.kill_exp_value, creature.level); } memoryMagicSkills(spells, memory.spells, creature.spells); memoryKillDifficulty(creature, memory.kills); memorySpecialAbilities(move); memoryWeaknesses(defense); if ((defense & config::monsters::defense::CD_INFRA) != 0) { memoryPrint(" It is warm blooded"); } if ((defense & config::monsters::defense::CD_NO_SLEEP) != 0) { if ((defense & config::monsters::defense::CD_INFRA) != 0) { memoryPrint(", and"); } else { memoryPrint(" It"); } memoryPrint(" cannot be charmed or slept"); } if ((defense & (config::monsters::defense::CD_NO_SLEEP | config::monsters::defense::CD_INFRA)) != 0) { memoryPrint("."); } memoryAwareness(creature, memory); memoryLootCarried(creature.movement, move); memoryAttackNumberAndDamage(memory, creature); // Always know the win creature. if ((creature.movement & config::monsters::move::CM_WIN) != 0u) { memoryPrint(" Killing one of these wins the game!"); } memoryPrint("\n"); putStringClearToEOL("--pause--", Coord_t{roff_print_line, 0}); if (game.wizard_mode) { memory = saved_memory; } return getKeyInput(); } // Allow access to monster memory. -CJS- void recallMonsterAttributes(char command) { int n = 0; char query; for (int i = MON_MAX_CREATURES - 1; i >= 0; i--) { if (creatures_list[i].sprite == command && memoryMonsterKnown(creature_recall[i])) { if (n == 0) { putString("You recall those details? [y/n]", Coord_t{0, 40}); query = getKeyInput(); if (query != 'y' && query != 'Y') { break; } eraseLine(Coord_t{0, 40}); terminalSaveScreen(); } n++; query = (char) memoryRecall(i); terminalRestoreScreen(); if (query == ESCAPE) { break; } } } } umoria-5.7.10+20181022/src/dice.cpp0000644000175000017500000000114413363422757015160 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. #include "headers.h" // generates damage for 2d6 style dice rolls int diceRoll(Dice_t const &dice) { auto sum = 0; for (auto i = 0; i < dice.dice; i++) { sum += randomNumber(dice.sides); } return sum; } // Returns max dice roll value -RAK- int maxDiceRoll(Dice_t const &dice) { return dice.dice * dice.sides; } umoria-5.7.10+20181022/src/treasure.h0000644000175000017500000000573413363422757015564 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. #pragma once // defines for treasure type values (tval) constexpr int8_t TV_NEVER = -1; // used by find_range() for non-search constexpr uint8_t TV_NOTHING = 0; constexpr uint8_t TV_MISC = 1; constexpr uint8_t TV_CHEST = 2; // min tval for wearable items, all items between TV_MIN_WEAR and // TV_MAX_WEAR use the same flag bits, see the TR_* defines. constexpr uint8_t TV_MIN_WEAR = 10; // items tested for enchantments, i.e. the MAGIK inscription, see the enchanted() procedure. constexpr uint8_t TV_MIN_ENCHANT = 10; constexpr uint8_t TV_SLING_AMMO = 10; constexpr uint8_t TV_BOLT = 11; constexpr uint8_t TV_ARROW = 12; constexpr uint8_t TV_SPIKE = 13; constexpr uint8_t TV_LIGHT = 15; constexpr uint8_t TV_BOW = 20; constexpr uint8_t TV_HAFTED = 21; constexpr uint8_t TV_POLEARM = 22; constexpr uint8_t TV_SWORD = 23; constexpr uint8_t TV_DIGGING = 25; constexpr uint8_t TV_BOOTS = 30; constexpr uint8_t TV_GLOVES = 31; constexpr uint8_t TV_CLOAK = 32; constexpr uint8_t TV_HELM = 33; constexpr uint8_t TV_SHIELD = 34; constexpr uint8_t TV_HARD_ARMOR = 35; constexpr uint8_t TV_SOFT_ARMOR = 36; // max tval that uses the TR_* flags constexpr uint8_t TV_MAX_ENCHANT = 39; constexpr uint8_t TV_AMULET = 40; constexpr uint8_t TV_RING = 45; constexpr uint8_t TV_MAX_WEAR = 50; // max tval for wearable items constexpr uint8_t TV_STAFF = 55; constexpr uint8_t TV_WAND = 65; constexpr uint8_t TV_SCROLL1 = 70; constexpr uint8_t TV_SCROLL2 = 71; constexpr uint8_t TV_POTION1 = 75; constexpr uint8_t TV_POTION2 = 76; constexpr uint8_t TV_FLASK = 77; constexpr uint8_t TV_FOOD = 80; constexpr uint8_t TV_MAGIC_BOOK = 90; constexpr uint8_t TV_PRAYER_BOOK = 91; constexpr uint8_t TV_MAX_OBJECT = 99; // objects with tval above this are never picked up by monsters constexpr uint8_t TV_GOLD = 100; constexpr uint8_t TV_MAX_PICK_UP = 100; // objects with higher tvals can not be picked up constexpr uint8_t TV_INVIS_TRAP = 101; // objects between TV_MIN_VISIBLE and TV_MAX_VISIBLE are always visible, // i.e. the cave fm flag is set when they are present constexpr uint8_t TV_MIN_VISIBLE = 102; constexpr uint8_t TV_VIS_TRAP = 102; constexpr uint8_t TV_RUBBLE = 103; // following objects are never deleted when trying to create another one during level generation constexpr uint8_t TV_MIN_DOORS = 104; constexpr uint8_t TV_OPEN_DOOR = 104; constexpr uint8_t TV_CLOSED_DOOR = 105; constexpr uint8_t TV_UP_STAIR = 107; constexpr uint8_t TV_DOWN_STAIR = 108; constexpr uint8_t TV_SECRET_DOOR = 109; constexpr uint8_t TV_STORE_DOOR = 110; constexpr uint8_t TV_MAX_VISIBLE = 110; extern Inventory_t treasure_list[LEVEL_MAX_OBJECTS]; // Converted to uint16_t when saving the game. extern int16_t missiles_counter; void magicTreasureMagicalAbility(int item_id, int level); umoria-5.7.10+20181022/src/mage_spells.h0000644000175000017500000000107613363422757016220 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. #pragma once // spell types used by get_flags(), breathe(), fire_bolt() and fire_ball() enum magic_spell_flags { GF_MAGIC_MISSILE, GF_LIGHTNING, GF_POISON_GAS, GF_ACID, GF_FROST, GF_FIRE, GF_HOLY_ORB, }; void getAndCastMagicSpell(); int spellChanceOfSuccess(int spell_id); umoria-5.7.10+20181022/src/types.h0000644000175000017500000000231113363422757015062 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. // Custom types for working with strings // maximum unsigned char: 255 constexpr uint8_t MAX_UCHAR = std::numeric_limits::max(); // maximum short signed int: 32767 constexpr int16_t MAX_SHORT = std::numeric_limits::max(); // maximum long signed int: 0x7FFFFFFFL constexpr int32_t MAX_LONG = std::numeric_limits::max(); // This used to be NULL, but that was technically incorrect. // CNIL is used instead of null to help avoid lint errors. constexpr char *CNIL = 0; // Many of the character fields used to be fixed length, which // greatly increased the size of the executable. Many fixed // length fields have been replaced with variable length ones. constexpr uint8_t MORIA_MESSAGE_SIZE = 80; typedef char vtype_t[MORIA_MESSAGE_SIZE]; // Note: since its output can easily exceed 80 characters, // an object description must always be called with an // obj_desc_t type as the first parameter. typedef char obj_desc_t[160]; umoria-5.7.10+20181022/src/mage_spells.cpp0000644000175000017500000002213713363422757016554 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. // Code for mage spells #include "headers.h" // names based on spell_names[62] in data_player.cpp enum class MageSpellTypes { magic_missile = 1, detect_monsters, phase_door, light_area, cure_light_wounds, find_hidden_traps_doors, stinking_cloud, confusion, lightning_bolt, trap_door_destruction, sleep_i, cure_poison, teleport_self, remove_curse, frost_bolt, wall_to_mud, create_food, recharge_item_1, sleep_ii, polymorph_other, identify_item, sleep_iii, fire_bolt, speed_monster, frost_ball, recharge_item_ii, teleport_other, haste_self, fire_ball, word_of_destruction, genocide, }; static bool canReadSpells() { if (py.flags.blind > 0) { printMessage("You can't see to read your spell book!"); return false; } if (playerNoLight()) { printMessage("You have no light to read by."); return false; } if (py.flags.confused > 0) { printMessage("You are too confused."); return false; } if (classes[py.misc.class_id].class_to_use_mage_spells != config::spells::SPELL_TYPE_MAGE) { printMessage("You can't cast spells!"); return false; } return true; } static void castSpell(int spell_id) { int dir; switch ((MageSpellTypes) spell_id) { case MageSpellTypes::magic_missile : if (getDirectionWithMemory(CNIL, dir)) { spellFireBolt(py.row, py.col, dir, diceRoll(Dice_t{2, 6}), magic_spell_flags::GF_MAGIC_MISSILE, spell_names[0]); } break; case MageSpellTypes::detect_monsters: (void) spellDetectMonsters(); break; case MageSpellTypes::phase_door: playerTeleport(10); break; case MageSpellTypes::light_area: (void) spellLightArea(py.row, py.col); break; case MageSpellTypes::cure_light_wounds: (void) spellChangePlayerHitPoints(diceRoll(Dice_t{4, 4})); break; case MageSpellTypes::find_hidden_traps_doors: (void) spellDetectSecretDoorssWithinVicinity(); (void) spellDetectTrapsWithinVicinity(); break; case MageSpellTypes::stinking_cloud: if (getDirectionWithMemory(CNIL, dir)) { spellFireBall(py.row, py.col, dir, 12, magic_spell_flags::GF_POISON_GAS, spell_names[6]); } break; case MageSpellTypes::confusion: if (getDirectionWithMemory(CNIL, dir)) { (void) spellConfuseMonster(py.row, py.col, dir); } break; case MageSpellTypes::lightning_bolt: if (getDirectionWithMemory(CNIL, dir)) { spellFireBolt(py.row, py.col, dir, diceRoll(Dice_t{4, 8}), magic_spell_flags::GF_LIGHTNING, spell_names[8]); } break; case MageSpellTypes::trap_door_destruction: (void) spellDestroyAdjacentDoorsTraps(); break; case MageSpellTypes::sleep_i: if (getDirectionWithMemory(CNIL, dir)) { (void) spellSleepMonster(py.row, py.col, dir); } break; case MageSpellTypes::cure_poison: (void) playerCurePoison(); break; case MageSpellTypes::teleport_self: playerTeleport((py.misc.level * 5)); break; case MageSpellTypes::remove_curse: for (int id = 22; id < PLAYER_INVENTORY_SIZE; id++) { inventory[id].flags = (uint32_t) (inventory[id].flags & ~config::treasure::flags::TR_CURSED); } break; case MageSpellTypes::frost_bolt: if (getDirectionWithMemory(CNIL, dir)) { spellFireBolt(py.row, py.col, dir, diceRoll(Dice_t{6, 8}), magic_spell_flags::GF_FROST, spell_names[14]); } break; case MageSpellTypes::wall_to_mud: if (getDirectionWithMemory(CNIL, dir)) { (void) spellWallToMud(py.row, py.col, dir); } break; case MageSpellTypes::create_food: spellCreateFood(); break; case MageSpellTypes::recharge_item_1: (void) spellRechargeItem(20); break; case MageSpellTypes::sleep_ii: (void) monsterSleep(py.row, py.col); break; case MageSpellTypes::polymorph_other: if (getDirectionWithMemory(CNIL, dir)) { (void) spellPolymorphMonster(py.row, py.col, dir); } break; case MageSpellTypes::identify_item: (void) spellIdentifyItem(); break; case MageSpellTypes::sleep_iii: (void) spellSleepAllMonsters(); break; case MageSpellTypes::fire_bolt: if (getDirectionWithMemory(CNIL, dir)) { spellFireBolt(py.row, py.col, dir, diceRoll(Dice_t{9, 8}), magic_spell_flags::GF_FIRE, spell_names[22]); } break; case MageSpellTypes::speed_monster: if (getDirectionWithMemory(CNIL, dir)) { (void) spellSpeedMonster(py.row, py.col, dir, -1); } break; case MageSpellTypes::frost_ball: if (getDirectionWithMemory(CNIL, dir)) { spellFireBall(py.row, py.col, dir, 48, magic_spell_flags::GF_FROST, spell_names[24]); } break; case MageSpellTypes::recharge_item_ii: (void) spellRechargeItem(60); break; case MageSpellTypes::teleport_other: if (getDirectionWithMemory(CNIL, dir)) { (void) spellTeleportAwayMonsterInDirection(py.row, py.col, dir); } break; case MageSpellTypes::haste_self: py.flags.fast += randomNumber(20) + py.misc.level; break; case MageSpellTypes::fire_ball: if (getDirectionWithMemory(CNIL, dir)) { spellFireBall(py.row, py.col, dir, 72, magic_spell_flags::GF_FIRE, spell_names[28]); } break; case MageSpellTypes::word_of_destruction: spellDestroyArea(py.row, py.col); break; case MageSpellTypes::genocide: (void) spellGenocide(); break; default: break; } } // Throw a magic spell -RAK- void getAndCastMagicSpell() { game.player_free_turn = true; if (!canReadSpells()) { return; } int i, j; if (!inventoryFindRange(TV_MAGIC_BOOK, TV_NEVER, i, j)) { printMessage("But you are not carrying any spell-books!"); return; } int item_val; if (!inventoryGetInputForItemId(item_val, "Use which spell-book?", i, j, CNIL, CNIL)) { return; } int choice, chance; int result = castSpellGetId("Cast which spell?", item_val, choice, chance); if (result < 0) { printMessage("You don't know any spells in that book."); return; } if (result == 0) { return; } game.player_free_turn = false; Spell_t const &magic_spell = magic_spells[py.misc.class_id - 1][choice]; if (randomNumber(100) < chance) { printMessage("You failed to get the spell off!"); } else { castSpell(choice + 1); if ((py.flags.spells_worked & (1L << choice)) == 0) { py.misc.exp += magic_spell.exp_gain_for_learning << 2; py.flags.spells_worked |= (1L << choice); displayCharacterExperience(); } } if (magic_spell.mana_required > py.misc.current_mana) { printMessage("You faint from the effort!"); py.flags.paralysis = (int16_t) randomNumber((5 * (magic_spell.mana_required - py.misc.current_mana))); py.misc.current_mana = 0; py.misc.current_mana_fraction = 0; if (randomNumber(3) == 1) { printMessage("You have damaged your health!"); (void) playerStatRandomDecrease(py_attrs::A_CON); } } else { py.misc.current_mana -= magic_spell.mana_required; } printCharacterCurrentMana(); } // Returns spell chance of failure for class_to_use_mage_spells -RAK- int spellChanceOfSuccess(int spell_id) { Spell_t const &spell = magic_spells[py.misc.class_id - 1][spell_id]; int chance = spell.failure_chance - 3 * (py.misc.level - spell.level_required); int stat; if (classes[py.misc.class_id].class_to_use_mage_spells == config::spells::SPELL_TYPE_MAGE) { stat = py_attrs::A_INT; } else { stat = py_attrs::A_WIS; } chance -= 3 * (playerStatAdjustmentWisdomIntelligence(stat) - 1); if (spell.mana_required > py.misc.current_mana) { chance += 5 * (spell.mana_required - py.misc.current_mana); } if (chance > 95) { chance = 95; } else if (chance < 5) { chance = 5; } return chance; } umoria-5.7.10+20181022/src/ui.h0000644000175000017500000001037713363422757014346 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. #pragma once // Panel_t holds data about a screen panel (the dungeon display) // Screen panels calculated from the dungeon/screen dimensions typedef struct { int row; int col; int top; int bottom; int left; int right; int col_prt; int row_prt; int16_t max_rows; int16_t max_cols; } Panel_t; typedef struct { int32_t y; int32_t x; } Coord_t; // message line location constexpr uint8_t MSG_LINE = 0; // How many messages to save in the buffer -CJS- constexpr uint8_t MESSAGE_HISTORY_SIZE = 22; // Column for stats constexpr uint8_t STAT_COLUMN = 0; constexpr char CTRL_KEY(char x) { return static_cast((x) & 0x1F); } #undef DELETE constexpr char DELETE = 0x7f; #undef ESCAPE constexpr char ESCAPE = '\033'; // ESCAPE character -CJS- extern bool screen_has_changed; extern bool message_ready_to_print; extern vtype_t messages[MESSAGE_HISTORY_SIZE]; extern int16_t last_message_id; // UI - IO // TODO: should we use the the same Coord_t for the dungeon and UI? bool terminalInitialize(); void terminalRestore(); void terminalSaveScreen(); void terminalRestoreScreen(); void terminalBellSound(); void putQIO(); void flushInputBuffer(); void clearScreen(); void clearToBottom(int row); void moveCursor(Coord_t coords); void addChar(char ch, Coord_t coords); void putString(const char *out_str, Coord_t coords); void putStringClearToEOL(const std::string &str, Coord_t coords); void eraseLine(Coord_t coords); void panelMoveCursor(Coord_t coords); void panelPutTile(char ch, Coord_t coords); void messageLinePrintMessage(std::string message); void messageLineClear(); void printMessage(const char *msg); void printMessageNoCommandInterrupt(const std::string &msg); char getKeyInput(); bool getCommand(const std::string &prompt, char &command); bool getStringInput(char *in_str, Coord_t coords, int slen); bool getInputConfirmation(const std::string &prompt); void waitForContinueKey(int line_number); bool checkForNonBlockingKeyPress(int microseconds); void getDefaultPlayerName(char *buffer); bool checkFilePermissions(); #ifndef _WIN32 // call functions which expand tilde before calling open/fopen #define open topen #define fopen tfopen FILE *tfopen(const char *file, const char *mode); int topen(const char *file, int flags, int mode); bool tilde(const char *file, char *expanded); #endif // UI bool coordOutsidePanel(Coord_t coord, bool force); bool coordInsidePanel(Coord_t coord); void drawDungeonPanel(); void drawCavePanel(); void dungeonResetView(); void statsAsString(uint8_t stat, char *stat_string); void displayCharacterStats(int stat); void printCharacterTitle(); void printCharacterLevel(); void printCharacterCurrentMana(); void printCharacterMaxHitPoints(); void printCharacterCurrentHitPoints(); void printCharacterCurrentArmorClass(); void printCharacterGoldValue(); void printCharacterCurrentDepth(); void printCharacterHungerStatus(); void printCharacterBlindStatus(); void printCharacterConfusedState(); void printCharacterFearState(); void printCharacterPoisonedState(); void printCharacterMovementState(); void printCharacterSpeed(); void printCharacterStudyInstruction(); void printCharacterWinner(); void printCharacterStatsBlock(); void printCharacterInformation(); void printCharacterStats(); const char *statRating(int y, int x); void printCharacterVitalStatistics(); void printCharacterLevelExperience(); void printCharacterAbilities(); void printCharacter(); void getCharacterName(); void changeCharacterName(); void displaySpellsList(const int *spell_ids, int number_of_choices, bool comment, int non_consecutive); void displayCharacterExperience(); // UI Inventory/Equipment int displayInventory(int item_id_start, int item_id_end, bool weighted, int column, const char *mask); const char *playerItemWearingDescription(int body_location); int displayEquipment(bool weighted, int column); void inventoryExecuteCommand(char command); bool inventoryGetInputForItemId(int &command_key_id, const char *prompt, int item_id_start, int item_id_end, char *mask, const char *message); umoria-5.7.10+20181022/src/game.cpp0000644000175000017500000002210513363422757015165 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. // Game initialization and maintenance related functions #include "headers.h" #include "version.h" // holds the previous rnd state static uint32_t old_seed; Game_t game = Game_t{}; // gets a new random seed for the random number generator void seedsInitialize(uint32_t seed) { uint32_t clock_var; if (seed == 0) { clock_var = getCurrentUnixTime(); } else { clock_var = seed; } game.magic_seed = (int32_t) clock_var; clock_var += 8762; game.town_seed = (int32_t) clock_var; clock_var += 113452L; setRandomSeed(clock_var); // make it a little more random for (clock_var = (uint32_t) randomNumber(100); clock_var != 0; clock_var--) { (void) rnd(); } } // change to different random number generator state void seedSet(uint32_t seed) { old_seed = getRandomSeed(); // want reproducible state here setRandomSeed(seed); } // restore the normal random generator state void seedResetToOldSeed() { setRandomSeed(old_seed); } // Generates a random integer x where 1<=X<=MAXVAL -RAK- int randomNumber(int const max) { return (rnd() % max) + 1; } // Generates a random integer number of NORMAL distribution -RAK- int randomNumberNormalDistribution(int mean, int standard) { // alternate randomNumberNormalDistribution() code, slower but much smaller since no table // 2 per 1,000,000 will be > 4*SD, max is 5*SD // // tmp = diceRoll(8, 99); // mean 400, SD 81 // tmp = (tmp - 400) * standard / 81; // return tmp + mean; int tmp = randomNumber(MAX_SHORT); // off scale, assign random value between 4 and 5 times SD if (tmp == MAX_SHORT) { int offset = 4 * standard + randomNumber(standard); // one half are negative if (randomNumber(2) == 1) { offset = -offset; } return mean + offset; } // binary search normal normal_table to get index that // matches tmp this takes up to 8 iterations. int low = 0; int iindex = NORMAL_TABLE_SIZE >> 1; int high = NORMAL_TABLE_SIZE; while (true) { if (normal_table[iindex] == tmp || high == low + 1) { break; } if (normal_table[iindex] > tmp) { high = iindex; iindex = low + ((iindex - low) >> 1); } else { low = iindex; iindex = iindex + ((high - iindex) >> 1); } } // might end up one below target, check that here if (normal_table[iindex] < tmp) { iindex = iindex + 1; } // normal_table is based on SD of 64, so adjust the // index value here, round the half way case up. int offset = ((standard * iindex) + (NORMAL_TABLE_SD >> 1)) / NORMAL_TABLE_SD; // one half should be negative if (randomNumber(2) == 1) { offset = -offset; } return mean + offset; } static struct { const char *o_prompt; bool *o_var; } game_options[] = {{"Running: cut known corners", &config::options::run_cut_corners}, {"Running: examine potential corners", &config::options::run_examine_corners}, {"Running: print self during run", &config::options::run_print_self}, {"Running: stop when map sector changes", &config::options::find_bound}, {"Running: run through open doors", &config::options::run_ignore_doors}, {"Prompt to pick up objects", &config::options::prompt_to_pickup}, {"Rogue like commands", &config::options::use_roguelike_keys}, {"Show weights in inventory", &config::options::show_inventory_weights}, {"Highlight and notice mineral seams", &config::options::highlight_seams}, {"Beep for invalid character", &config::options::error_beep_sound}, {"Display rest/repeat counts", &config::options::display_counts}, {nullptr, nullptr}, }; // Set or unset various boolean config::options::display_counts -CJS- void setGameOptions() { putStringClearToEOL(" ESC when finished, y/n to set options, or - to move cursor", Coord_t{0, 0}); int max; for (max = 0; game_options[max].o_prompt != nullptr; max++) { vtype_t str = {'\0'}; (void) sprintf(str, "%-38s: %s", game_options[max].o_prompt, (*game_options[max].o_var ? "yes" : "no ")); putStringClearToEOL(str, Coord_t{max + 1, 0}); } eraseLine(Coord_t{max + 1, 0}); int optionID = 0; while (true) { moveCursor(Coord_t{optionID + 1, 40}); switch (getKeyInput()) { case ESCAPE: return; case '-': if (optionID > 0) { optionID--; } else { optionID = max - 1; } break; case ' ': case '\n': case '\r': if (optionID + 1 < max) { optionID++; } else { optionID = 0; } break; case 'y': case 'Y': putString("yes", Coord_t{optionID + 1, 40}); *game_options[optionID].o_var = true; if (optionID + 1 < max) { optionID++; } else { optionID = 0; } break; case 'n': case 'N': putString("no ", Coord_t{optionID + 1, 40}); *game_options[optionID].o_var = false; if (optionID + 1 < max) { optionID++; } else { optionID = 0; } break; default: terminalBellSound(); break; } } } // Support for Umoria 5.2.2 up to 5.7.x. // The save file format was frozen as of version 5.2.2. bool validGameVersion(uint8_t major, uint8_t minor, uint8_t patch) { if (major != 5) { return false; } if (minor < 2) { return false; } if (minor == 2 && patch < 2) { return false; } return minor <= 7; } bool isCurrentGameVersion(uint8_t major, uint8_t minor, uint8_t patch) { return major == CURRENT_VERSION_MAJOR && minor == CURRENT_VERSION_MINOR && patch == CURRENT_VERSION_PATCH; } int getRandomDirection() { int dir; do { dir = randomNumber(9); } while (dir == 5); return dir; } // map roguelike direction commands into numbers static char mapRoguelikeKeysToKeypad(char command) { switch (command) { case 'h': return '4'; case 'y': return '7'; case 'k': return '8'; case 'u': return '9'; case 'l': return '6'; case 'n': return '3'; case 'j': return '2'; case 'b': return '1'; case '.': return '5'; default: return command; } } // Prompts for a direction -RAK- // Direction memory added, for repeated commands. -CJS bool getDirectionWithMemory(char *prompt, int &direction) { static char prev_dir; // Direction memory. -CJS- // used in counted commands. -CJS- if (game.use_last_direction) { direction = prev_dir; return true; } if (prompt == CNIL) { prompt = (char *) "Which direction?"; } char command; while (true) { // Don't end a counted command. -CJS- int save = game.command_count; if (!getCommand(prompt, command)) { game.player_free_turn = true; return false; } game.command_count = save; if (config::options::use_roguelike_keys) { command = mapRoguelikeKeysToKeypad(command); } if (command >= '1' && command <= '9' && command != '5') { prev_dir = command - '0'; direction = prev_dir; return true; } terminalBellSound(); } } // Similar to getDirectionWithMemory(), except that no memory exists, // and it is allowed to enter the null direction. -CJS- bool getAllDirections(const char *prompt, int &direction) { char command; while (true) { if (!getCommand(prompt, command)) { game.player_free_turn = true; return false; } if (config::options::use_roguelike_keys) { command = mapRoguelikeKeysToKeypad(command); } if (command >= '1' && command <= '9') { direction = command - '0'; return true; } terminalBellSound(); } } // Restore the terminal and exit void exitProgram() { flushInputBuffer(); terminalRestore(); exit(0); } umoria-5.7.10+20181022/src/data_recall.cpp0000644000175000017500000000477413363422757016523 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. // Player's memory: monster descriptions const char *recall_description_attack_type[25] = { "do something undefined", "attack", "weaken", "confuse", "terrify", "shoot flames", "shoot acid", "freeze", "shoot lightning", "corrode", "blind", "paralyse", "steal money", "steal things", "poison", "reduce dexterity", "reduce constitution", "drain intelligence", "drain wisdom", "lower experience", "call for help", "disenchant", "eat your food", "absorb light", "absorb charges", }; const char *recall_description_attack_method[20] = { "make an undefined advance", "hit", "bite", "claw", "sting", "touch", "kick", "gaze", "breathe", "spit", "wail", "embrace", "crawl on you", "release spores", "beg", "slime you", "crush", "trample", "drool", "insult", }; const char *recall_description_how_much[8] = { " not at all", " a bit", "", " quite", " very", " most", " highly", " extremely", }; const char *recall_description_move[6] = { "move invisibly", "open doors", "pass through walls", "kill weaker creatures", "pick up objects", "breed explosively", }; const char *recall_description_spell[15] = { "teleport short distances", "teleport long distances", "teleport its prey", "cause light wounds", "cause serious wounds", "paralyse its prey", "induce blindness", "confuse", "terrify", "summon a monster", "summon the undead", "slow its prey", "drain mana", "unknown 1", "unknown 2", }; const char *recall_description_breath[5] = { "lightning", "poison gases", "acid", "frost", "fire", }; const char *recall_description_weakness[6] = { "frost", "fire", "poison", "acid", "bright light", "rock remover", }; umoria-5.7.10+20181022/src/player_stats.cpp0000644000175000017500000003062013363422757016767 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. // Functions related to Player stats #include "headers.h" // I don't really like this, but for now, it's better than being a global -MRC- void playerInitializeBaseExperienceLevels() { // TODO: load from external data file uint32_t levels[PLAYER_MAX_LEVEL] = { 10, 25, 45, 70, 100, 140, 200, 280, 380, 500, 650, 850, 1100, 1400, 1800, 2300, 2900, 3600, 4400, 5400, 6800, 8400, 10200, 12500, 17500, 25000, 35000L, 50000L, 75000L, 100000L, 150000L, 200000L, 300000L, 400000L, 500000L, 750000L, 1500000L, 2500000L, 5000000L, 10000000L, }; for (auto i = 0; i < PLAYER_MAX_LEVEL; i++) { py.base_exp_levels[i] = levels[i]; } } // Calculate the players hit points void playerCalculateHitPoints() { int hp = py.base_hp_levels[py.misc.level - 1] + (playerStatAdjustmentConstitution() * py.misc.level); // Always give at least one point per level + 1 if (hp < (py.misc.level + 1)) { hp = py.misc.level + 1; } if ((py.flags.status & config::player::status::PY_HERO) != 0u) { hp += 10; } if ((py.flags.status & config::player::status::PY_SHERO) != 0u) { hp += 20; } // MHP can equal zero while character is being created if (hp != py.misc.max_hp && py.misc.max_hp != 0) { // Change current hit points proportionately to change of MHP, // divide first to avoid overflow, little loss of accuracy int32_t value = (((int32_t) py.misc.current_hp << 16) + py.misc.current_hp_fraction) / py.misc.max_hp * hp; py.misc.current_hp = (int16_t) (value >> 16); py.misc.current_hp_fraction = (uint16_t) (value & 0xFFFF); py.misc.max_hp = (int16_t) hp; // can't print hit points here, may be in store or inventory mode py.flags.status |= config::player::status::PY_HP; } } static int playerAttackBlowsDexterity(int dexterity) { int dex; if (dexterity < 10) { dex = 0; } else if (dexterity < 19) { dex = 1; } else if (dexterity < 68) { dex = 2; } else if (dexterity < 108) { dex = 3; } else if (dexterity < 118) { dex = 4; } else { dex = 5; } return dex; } static int playerAttackBlowsStrength(int strength, int weight) { int adj_weight = (strength * 10 / weight); int str; if (adj_weight < 2) { str = 0; } else if (adj_weight < 3) { str = 1; } else if (adj_weight < 4) { str = 2; } else if (adj_weight < 5) { str = 3; } else if (adj_weight < 7) { str = 4; } else if (adj_weight < 9) { str = 5; } else { str = 6; } return str; } // Weapon weight VS strength and dexterity -RAK- int playerAttackBlows(int weight, int &weight_to_hit) { weight_to_hit = 0; int player_strength = py.stats.used[py_attrs::A_STR]; if (player_strength * 15 < weight) { weight_to_hit = player_strength * 15 - weight; return 1; } int dexterity = playerAttackBlowsDexterity(py.stats.used[py_attrs::A_DEX]); int strength = playerAttackBlowsStrength(player_strength, weight); return (int) blows_table[strength][dexterity]; } // Adjustment for wisdom/intelligence -JWT- int playerStatAdjustmentWisdomIntelligence(int stat) { int value = py.stats.used[stat]; int adjustment; if (value > 117) { adjustment = 7; } else if (value > 107) { adjustment = 6; } else if (value > 87) { adjustment = 5; } else if (value > 67) { adjustment = 4; } else if (value > 17) { adjustment = 3; } else if (value > 14) { adjustment = 2; } else if (value > 7) { adjustment = 1; } else { adjustment = 0; } return adjustment; } // Adjustment for charisma -RAK- // Percent decrease or increase in price of goods int playerStatAdjustmentCharisma() { int charisma = py.stats.used[py_attrs::A_CHR]; if (charisma > 117) { return 90; } if (charisma > 107) { return 92; } if (charisma > 87) { return 94; } if (charisma > 67) { return 96; } if (charisma > 18) { return 98; } switch (charisma) { case 18: return 100; case 17: return 101; case 16: return 102; case 15: return 103; case 14: return 104; case 13: return 106; case 12: return 108; case 11: return 110; case 10: return 112; case 9: return 114; case 8: return 116; case 7: return 118; case 6: return 120; case 5: return 122; case 4: return 125; case 3: return 130; default: return 100; } } // Returns a character's adjustment to hit points -JWT- int playerStatAdjustmentConstitution() { int con = py.stats.used[py_attrs::A_CON]; if (con < 7) { return (con - 7); } if (con < 17) { return 0; } if (con == 17) { return 1; } if (con < 94) { return 2; } if (con < 117) { return 3; } return 4; } static uint8_t playerModifyStat(int stat, int16_t amount) { uint8_t new_stat = py.stats.current[stat]; int loop = (amount < 0 ? -amount : amount); for (int i = 0; i < loop; i++) { if (amount > 0) { if (new_stat < 18) { new_stat++; } else if (new_stat < 108) { new_stat += 10; } else { new_stat = 118; } } else { if (new_stat > 27) { new_stat -= 10; } else if (new_stat > 18) { new_stat = 18; } else if (new_stat > 3) { new_stat--; } } } return new_stat; } // Set the value of the stat which is actually used. -CJS- void playerSetAndUseStat(int stat) { py.stats.used[stat] = playerModifyStat(stat, py.stats.modified[stat]); if (stat == py_attrs::A_STR) { py.flags.status |= config::player::status::PY_STR_WGT; playerRecalculateBonuses(); } else if (stat == py_attrs::A_DEX) { playerRecalculateBonuses(); } else if (stat == py_attrs::A_INT && classes[py.misc.class_id].class_to_use_mage_spells == config::spells::SPELL_TYPE_MAGE) { playerCalculateAllowedSpellsCount(py_attrs::A_INT); playerGainMana(py_attrs::A_INT); } else if (stat == py_attrs::A_WIS && classes[py.misc.class_id].class_to_use_mage_spells == config::spells::SPELL_TYPE_PRIEST) { playerCalculateAllowedSpellsCount(py_attrs::A_WIS); playerGainMana(py_attrs::A_WIS); } else if (stat == py_attrs::A_CON) { playerCalculateHitPoints(); } } // Increases a stat by one randomized level -RAK- bool playerStatRandomIncrease(int stat) { int new_stat = py.stats.current[stat]; if (new_stat >= 118) { return false; } if (new_stat < 18) { new_stat++; } else if (new_stat < 116) { // stat increases by 1/6 to 1/3 of difference from max int gain = ((118 - new_stat) / 3 + 1) >> 1; new_stat += randomNumber(gain) + gain; } else { new_stat++; } py.stats.current[stat] = (uint8_t) new_stat; if (new_stat > py.stats.max[stat]) { py.stats.max[stat] = (uint8_t) new_stat; } playerSetAndUseStat(stat); displayCharacterStats(stat); return true; } // Decreases a stat by one randomized level -RAK- bool playerStatRandomDecrease(int stat) { int new_stat = py.stats.current[stat]; if (new_stat <= 3) { return false; } if (new_stat < 19) { new_stat--; } else if (new_stat < 117) { int loss = (((118 - new_stat) >> 1) + 1) >> 1; new_stat += -randomNumber(loss) - loss; if (new_stat < 18) { new_stat = 18; } } else { new_stat--; } py.stats.current[stat] = (uint8_t) new_stat; playerSetAndUseStat(stat); displayCharacterStats(stat); return true; } // Restore a stat. Return true only if this actually makes a difference. bool playerStatRestore(int stat) { int new_stat = py.stats.max[stat] - py.stats.current[stat]; if (new_stat == 0) { return false; } py.stats.current[stat] += new_stat; playerSetAndUseStat(stat); displayCharacterStats(stat); return true; } // Boost a stat artificially (by wearing something). If the display // argument is true, then increase is shown on the screen. void playerStatBoost(int stat, int amount) { py.stats.modified[stat] += amount; playerSetAndUseStat(stat); // can not call displayCharacterStats() here: // might be in a store, // might be in inventoryExecuteCommand() py.flags.status |= (config::player::status::PY_STR << stat); } // Returns a character's adjustment to hit. -JWT- int playerToHitAdjustment() { int total; int dexterity = py.stats.used[py_attrs::A_DEX]; if (dexterity < 4) { total = -3; } else if (dexterity < 6) { total = -2; } else if (dexterity < 8) { total = -1; } else if (dexterity < 16) { total = 0; } else if (dexterity < 17) { total = 1; } else if (dexterity < 18) { total = 2; } else if (dexterity < 69) { total = 3; } else if (dexterity < 118) { total = 4; } else { total = 5; } int strength = py.stats.used[py_attrs::A_STR]; if (strength < 4) { total -= 3; } else if (strength < 5) { total -= 2; } else if (strength < 7) { total -= 1; } else if (strength < 18) { total -= 0; } else if (strength < 94) { total += 1; } else if (strength < 109) { total += 2; } else if (strength < 117) { total += 3; } else { total += 4; } return total; } // Returns a character's adjustment to armor class -JWT- int playerArmorClassAdjustment() { int stat = py.stats.used[py_attrs::A_DEX]; int adjustment; if (stat < 4) { adjustment = -4; } else if (stat == 4) { adjustment = -3; } else if (stat == 5) { adjustment = -2; } else if (stat == 6) { adjustment = -1; } else if (stat < 15) { adjustment = 0; } else if (stat < 18) { adjustment = 1; } else if (stat < 59) { adjustment = 2; } else if (stat < 94) { adjustment = 3; } else if (stat < 117) { adjustment = 4; } else { adjustment = 5; } return adjustment; } // Returns a character's adjustment to disarm -RAK- int16_t playerDisarmAdjustment() { auto stat = py.stats.used[py_attrs::A_DEX]; int16_t adjustment = 0; if (stat < 4) { adjustment = -8; } else if (stat == 4) { adjustment = -6; } else if (stat == 5) { adjustment = -4; } else if (stat == 6) { adjustment = -2; } else if (stat == 7) { adjustment = -1; } else if (stat < 13) { adjustment = 0; } else if (stat < 16) { adjustment = 1; } else if (stat < 18) { adjustment = 2; } else if (stat < 59) { adjustment = 4; } else if (stat < 94) { adjustment = 5; } else if (stat < 117) { adjustment = 6; } else { adjustment = 8; } return adjustment; } // Returns a character's adjustment to damage -JWT- int playerDamageAdjustment() { int stat = py.stats.used[py_attrs::A_STR]; int adjustment; if (stat < 4) { adjustment = -2; } else if (stat < 5) { adjustment = -1; } else if (stat < 16) { adjustment = 0; } else if (stat < 17) { adjustment = 1; } else if (stat < 18) { adjustment = 2; } else if (stat < 94) { adjustment = 3; } else if (stat < 109) { adjustment = 4; } else if (stat < 117) { adjustment = 5; } else { adjustment = 6; } return adjustment; } umoria-5.7.10+20181022/src/player_tunnel.cpp0000644000175000017500000001335013363422757017137 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. // Player throw functions #include "headers.h" // Don't let the player tunnel somewhere illegal, this is necessary to // prevent the player from getting a free attack by trying to tunnel // somewhere where it has no effect. static bool playerCanTunnel(int treasure_id, int tile_id) { if (tile_id < MIN_CAVE_WALL && (treasure_id == 0 || (treasure_list[treasure_id].category_id != TV_RUBBLE && treasure_list[treasure_id].category_id != TV_SECRET_DOOR))) { game.player_free_turn = true; if (treasure_id == 0) { printMessage("Tunnel through what? Empty air?!?"); } else { printMessage("You can't tunnel through that."); } return false; } return true; } // Compute the digging ability of player; based on strength, and type of tool used static int playerDiggingAbility(Inventory_t const &weapon) { int diggingAbility = py.stats.used[py_attrs::A_STR]; if ((weapon.flags & config::treasure::flags::TR_TUNNEL) != 0u) { diggingAbility += 25 + weapon.misc_use * 50; } else { diggingAbility += maxDiceRoll(weapon.damage) + weapon.to_hit + weapon.to_damage; // divide by two so that digging without shovel isn't too easy diggingAbility >>= 1; } // If this weapon is too heavy for the player to wield properly, // then also make it harder to dig with it. if (py.weapon_is_heavy) { diggingAbility += (py.stats.used[py_attrs::A_STR] * 15) - weapon.weight; if (diggingAbility < 0) { diggingAbility = 0; } } return diggingAbility; } static void dungeonDigGraniteWall(int y, int x, int digging_ability) { int i = randomNumber(1200) + 80; if (playerTunnelWall(y, x, digging_ability, i)) { printMessage("You have finished the tunnel."); } else { printMessageNoCommandInterrupt("You tunnel into the granite wall."); } } static void dungeonDigMagmaWall(int y, int x, int digging_ability) { int i = randomNumber(600) + 10; if (playerTunnelWall(y, x, digging_ability, i)) { printMessage("You have finished the tunnel."); } else { printMessageNoCommandInterrupt("You tunnel into the magma intrusion."); } } static void dungeonDigQuartzWall(int y, int x, int digging_ability) { int i = randomNumber(400) + 10; if (playerTunnelWall(y, x, digging_ability, i)) { printMessage("You have finished the tunnel."); } else { printMessageNoCommandInterrupt("You tunnel into the quartz vein."); } } static void dungeonDigRubble(int y, int x, int digging_ability) { if (digging_ability > randomNumber(180)) { (void) dungeonDeleteObject(Coord_t{y, x});; printMessage("You have removed the rubble."); if (randomNumber(10) == 1) { dungeonPlaceRandomObjectAt(Coord_t{y, x}, false); if (caveTileVisible(Coord_t{y, x})) { printMessage("You have found something!"); } } dungeonLiteSpot(Coord_t{y, x}); } else { printMessageNoCommandInterrupt("You dig in the rubble."); } } // Dig regular walls; Granite, magma intrusion, quartz vein // Don't forget the boundary walls, made of titanium (255) // Return `true` if a wall was dug at static bool dungeonDigAtLocation(int y, int x, uint8_t wall_type, int digging_ability) { switch (wall_type) { case TILE_GRANITE_WALL: dungeonDigGraniteWall(y, x, digging_ability); break; case TILE_MAGMA_WALL: dungeonDigMagmaWall(y, x, digging_ability); break; case TILE_QUARTZ_WALL: dungeonDigQuartzWall(y, x, digging_ability); break; case TILE_BOUNDARY_WALL: printMessage("This seems to be permanent rock."); break; default: return false; } return true; } // Tunnels through rubble and walls -RAK- // Must take into account: secret doors, special tools void playerTunnel(int direction) { // Confused? 75% random movement if (py.flags.confused > 0 && randomNumber(4) > 1) { direction = randomNumber(9); } int y = py.row; int x = py.col; (void) playerMovePosition(direction, y, x); Tile_t const &tile = dg.floor[y][x]; Inventory_t &item = inventory[player_equipment::EQUIPMENT_WIELD]; if (!playerCanTunnel(tile.treasure_id, tile.feature_id)) { return; } if (tile.creature_id > 1) { objectBlockedByMonster(tile.creature_id); playerAttackPosition(y, x); return; } if (item.category_id != TV_NOTHING) { int diggingAbility = playerDiggingAbility(item); if (!dungeonDigAtLocation(y, x, tile.feature_id, diggingAbility)) { // Is there an object in the way? (Rubble and secret doors) if (tile.treasure_id != 0) { if (treasure_list[tile.treasure_id].category_id == TV_RUBBLE) { dungeonDigRubble(y, x, diggingAbility); } else if (treasure_list[tile.treasure_id].category_id == TV_SECRET_DOOR) { // Found secret door! printMessageNoCommandInterrupt("You tunnel into the granite wall."); playerSearch(py.row, py.col, py.misc.chance_in_search); } else { abort(); } } else { abort(); } } return; } printMessage("You dig with your hands, making no progress."); } umoria-5.7.10+20181022/src/character.h0000644000175000017500000000665513363422757015671 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. #pragma once // Race type for the generated player character typedef struct { const char *name; // Type of race int16_t str_adjustment; // adjustments int16_t int_adjustment; int16_t wis_adjustment; int16_t dex_adjustment; int16_t con_adjustment; int16_t chr_adjustment; uint8_t base_age; // Base age of character uint8_t max_age; // Maximum age of character uint8_t male_height_base; // base height for males uint8_t male_height_mod; // mod height for males uint8_t male_weight_base; // base weight for males uint8_t male_weight_mod; // mod weight for males uint8_t female_height_base; // base height females uint8_t female_height_mod; // mod height for females uint8_t female_weight_base; // base weight for female uint8_t female_weight_mod; // mod weight for females int16_t disarm_chance_base; // base chance to disarm int16_t search_chance_base; // base chance for search int16_t stealth; // Stealth of character int16_t fos; // frequency of auto search int16_t base_to_hit; // adj base chance to hit int16_t base_to_hit_bows; // adj base to hit with bows int16_t saving_throw_base; // Race base for saving throw uint8_t hit_points_base; // Base hit points for race uint8_t infra_vision; // See infra-red uint8_t exp_factor_base; // Base experience factor uint8_t classes_bit_field; // Bit field for class types } Race_t; // Class type for the generated player character typedef struct { const char *title; // type of class uint8_t hit_points; // Adjust hit points uint8_t disarm_traps; // mod disarming traps uint8_t searching; // modifier to searching uint8_t stealth; // modifier to stealth uint8_t fos; // modifier to freq-of-search uint8_t base_to_hit; // modifier to base to hit uint8_t base_to_hit_with_bows; // modifier to base to hit - bows uint8_t saving_throw; // Class modifier to save int16_t strength; // Class modifier for strength int16_t intelligence; // Class modifier for intelligence int16_t wisdom; // Class modifier for wisdom int16_t dexterity; // Class modifier for dexterity int16_t constitution; // Class modifier for constitution int16_t charisma; // Class modifier for charisma uint8_t class_to_use_mage_spells; // class use mage spells uint8_t experience_factor; // Class experience factor uint8_t min_level_for_spell_casting; // First level where class can use spells. } Class_t; // Class background for the generated player character typedef struct { const char *info; // History information uint8_t roll; // Die roll needed for history uint8_t chart; // Table number uint8_t next; // Pointer to next table uint8_t bonus; // Bonus to the Social Class+50 } Background_t; void characterCreate(); umoria-5.7.10+20181022/src/store_inventory.cpp0000644000175000017500000003162613363422757017535 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. // Store: updating store inventory, pricing objects #include "headers.h" Store_t stores[MAX_STORES]; static void storeItemInsert(int store_id, int pos, int32_t i_cost, Inventory_t *item); static void storeItemCreate(int store_id, int16_t max_cost); static int32_t getWeaponArmorBuyPrice(Inventory_t const &item); static int32_t getAmmoBuyPrice(Inventory_t const &item); static int32_t getPotionScrollBuyPrice(Inventory_t const &item); static int32_t getFoodBuyPrice(Inventory_t const &item); static int32_t getRingAmuletBuyPrice(Inventory_t const &item); static int32_t getWandStaffBuyPrice(Inventory_t const &item); static int32_t getPickShovelBuyPrice(Inventory_t const &item); // Initialize and up-keep the store's inventory. -RAK- void storeMaintenance() { for (int store_id = 0; store_id < MAX_STORES; store_id++) { Store_t &store = stores[store_id]; store.insults_counter = 0; if (store.unique_items_counter >= config::stores::STORE_MIN_AUTO_SELL_ITEMS) { int turnaround = randomNumber(config::stores::STORE_STOCK_TURN_AROUND); if (store.unique_items_counter >= config::stores::STORE_MAX_AUTO_BUY_ITEMS) { turnaround += 1 + store.unique_items_counter - config::stores::STORE_MAX_AUTO_BUY_ITEMS; } while (--turnaround >= 0) { storeDestroyItem(store_id, randomNumber(store.unique_items_counter) - 1, false); } } if (store.unique_items_counter <= config::stores::STORE_MAX_AUTO_BUY_ITEMS) { int turnaround = randomNumber(config::stores::STORE_STOCK_TURN_AROUND); if (store.unique_items_counter < config::stores::STORE_MIN_AUTO_SELL_ITEMS) { turnaround += config::stores::STORE_MIN_AUTO_SELL_ITEMS - store.unique_items_counter; } int16_t max_cost = store_owners[store.owner_id].max_cost; while (--turnaround >= 0) { storeItemCreate(store_id, max_cost); } } } } // Returns the value for any given object -RAK- int32_t storeItemValue(Inventory_t const &item) { int32_t value; if ((item.identification & config::identification::ID_DAMD) != 0) { // don't purchase known cursed items value = 0; } else if ((item.category_id >= TV_BOW && item.category_id <= TV_SWORD) || (item.category_id >= TV_BOOTS && item.category_id <= TV_SOFT_ARMOR)) { value = getWeaponArmorBuyPrice(item); } else if (item.category_id >= TV_SLING_AMMO && item.category_id <= TV_SPIKE) { value = getAmmoBuyPrice(item); } else if (item.category_id == TV_SCROLL1 || item.category_id == TV_SCROLL2 || item.category_id == TV_POTION1 || item.category_id == TV_POTION2) { value = getPotionScrollBuyPrice(item); } else if (item.category_id == TV_FOOD) { value = getFoodBuyPrice(item); } else if (item.category_id == TV_AMULET || item.category_id == TV_RING) { value = getRingAmuletBuyPrice(item); } else if (item.category_id == TV_STAFF || item.category_id == TV_WAND) { value = getWandStaffBuyPrice(item); } else if (item.category_id == TV_DIGGING) { value = getPickShovelBuyPrice(item); } else { value = item.cost; } // Multiply value by number of items if it is a group stack item. // Do not include torches here. if (item.sub_category_id > ITEM_GROUP_MIN) { value = value * item.items_count; } return value; } static int32_t getWeaponArmorBuyPrice(Inventory_t const &item) { if (!spellItemIdentified(item)) { return game_objects[item.id].cost; } if (item.category_id >= TV_BOW && item.category_id <= TV_SWORD) { if (item.to_hit < 0 || item.to_damage < 0 || item.to_ac < 0) { return 0; } return item.cost + (item.to_hit + item.to_damage + item.to_ac) * 100; } if (item.to_ac < 0) { return 0; } return item.cost + item.to_ac * 100; } static int32_t getAmmoBuyPrice(Inventory_t const &item) { if (!spellItemIdentified(item)) { return game_objects[item.id].cost; } if (item.to_hit < 0 || item.to_damage < 0 || item.to_ac < 0) { return 0; } // use 5, because missiles generally appear in groups of 20, // so 20 * 5 == 100, which is comparable to weapon bonus above return item.cost + (item.to_hit + item.to_damage + item.to_ac) * 5; } static int32_t getPotionScrollBuyPrice(Inventory_t const &item) { if (!itemSetColorlessAsIdentified(item.category_id, item.sub_category_id, item.identification)) { return 20; } return item.cost; } static int32_t getFoodBuyPrice(Inventory_t const &item) { if (item.sub_category_id < ITEM_SINGLE_STACK_MIN + MAX_MUSHROOMS && !itemSetColorlessAsIdentified(item.category_id, item.sub_category_id, item.identification)) { return 1; } return item.cost; } static int32_t getRingAmuletBuyPrice(Inventory_t const &item) { // player does not know what type of ring/amulet this is if (!itemSetColorlessAsIdentified(item.category_id, item.sub_category_id, item.identification)) { return 45; } // player knows what type of ring, but does not know whether it // is cursed or not, if refuse to buy cursed objects here, then // player can use this to 'identify' cursed objects if (!spellItemIdentified(item)) { return game_objects[item.id].cost; } return item.cost; } static int32_t getWandStaffBuyPrice(Inventory_t const &item) { if (!itemSetColorlessAsIdentified(item.category_id, item.sub_category_id, item.identification)) { if (item.category_id == TV_WAND) { return 50; } return 70; } if (spellItemIdentified(item)) { return item.cost + (item.cost / 20) * item.misc_use; } return item.cost; } static int32_t getPickShovelBuyPrice(Inventory_t const &item) { if (!spellItemIdentified(item)) { return game_objects[item.id].cost; } if (item.misc_use < 0) { return 0; } // some digging tools start with non-zero `misc_use` values, so only // multiply the plusses by 100, make sure result is positive int32_t value = item.cost + (item.misc_use - game_objects[item.id].misc_use) * 100; if (value < 0) { value = 0; } return value; } // Asking price for an item -RAK- int32_t storeItemSellPrice(Store_t const &store, int32_t &min_price, int32_t &max_price, Inventory_t const &item) { int32_t price = storeItemValue(item); // check `item.cost` in case it is cursed, check `price` in case it is damaged // don't let the item get into the store inventory if (item.cost < 1 || price < 1) { return 0; } Owner_t const &owner = store_owners[store.owner_id]; price = price * race_gold_adjustments[owner.race][py.misc.race_id] / 100; if (price < 1) { price = 1; } max_price = price * owner.max_inflate / 100; min_price = price * owner.min_inflate / 100; if (min_price > max_price) { min_price = max_price; } return price; } // Check to see if they will be carrying too many objects -RAK- bool storeCheckPlayerItemsCount(Store_t const &store, Inventory_t const &item) { if (store.unique_items_counter < STORE_MAX_DISCRETE_ITEMS) { return true; } if (item.sub_category_id < ITEM_SINGLE_STACK_MIN) { return false; } bool store_check = false; for (int i = 0; i < store.unique_items_counter; i++) { Inventory_t const &store_item = store.inventory[i].item; // note: items with sub_category_id of gte ITEM_SINGLE_STACK_MAX only stack // if their `sub_category_id`s match if (store_item.category_id == item.category_id && store_item.sub_category_id == item.sub_category_id && (int) (store_item.items_count + item.items_count) < 256 && (item.sub_category_id < ITEM_GROUP_MIN || store_item.misc_use == item.misc_use)) { store_check = true; } } return store_check; } // Insert INVEN_MAX at given location static void storeItemInsert(int store_id, int pos, int32_t i_cost, Inventory_t *item) { Store_t &store = stores[store_id]; for (int i = store.unique_items_counter - 1; i >= pos; i--) { store.inventory[i + 1] = store.inventory[i]; } store.inventory[pos].item = *item; store.inventory[pos].cost = -i_cost; store.unique_items_counter++; } // Add the item in INVEN_MAX to stores inventory. -RAK- void storeCarryItem(int store_id, int &index_id, Inventory_t &item) { index_id = -1; Store_t &store = stores[store_id]; int32_t item_cost, dummy; if (storeItemSellPrice(store, dummy, item_cost, item) < 1) { return; } int item_id = 0; int item_num = item.items_count; int item_category = item.category_id; int item_sub_catory = item.sub_category_id; bool flag = false; do { Inventory_t &store_item = store.inventory[item_id].item; if (item_category == store_item.category_id) { if (item_sub_catory == store_item.sub_category_id && // Adds to other item item_sub_catory >= ITEM_SINGLE_STACK_MIN && (item_sub_catory < ITEM_GROUP_MIN || store_item.misc_use == item.misc_use)) { index_id = item_id; store_item.items_count += item_num; // must set new cost for group items, do this only for items // strictly greater than group_min, not for torches, this // must be recalculated for entire group if (item_sub_catory > ITEM_GROUP_MIN) { (void) storeItemSellPrice(store, dummy, item_cost, store_item); store.inventory[item_id].cost = -item_cost; } else if (store_item.items_count > 24) { // must let group objects (except torches) stack over 24 // since there may be more than 24 in the group store_item.items_count = 24; } flag = true; } } else if (item_category > store_item.category_id) { // Insert into list storeItemInsert(store_id, item_id, item_cost, &item); flag = true; index_id = item_id; } item_id++; } while (item_id < store.unique_items_counter && !flag); // Becomes last item in list if (!flag) { storeItemInsert(store_id, (int) store.unique_items_counter, item_cost, &item); index_id = store.unique_items_counter - 1; } } // Destroy an item in the stores inventory. Note that if // `only_one_of` is false, an entire slot is destroyed -RAK- void storeDestroyItem(int store_id, int item_id, bool only_one_of) { Store_t &store = stores[store_id]; Inventory_t &store_item = store.inventory[item_id].item; int number; // for single stackable objects, only destroy one half on average, // this will help ensure that general store and alchemist have // reasonable selection of objects if (store_item.sub_category_id >= ITEM_SINGLE_STACK_MIN && store_item.sub_category_id <= ITEM_SINGLE_STACK_MAX) { if (only_one_of) { number = 1; } else { number = randomNumber((int) store_item.items_count); } } else { number = store_item.items_count; } if (number != store_item.items_count) { store_item.items_count -= number; } else { for (int i = item_id; i < store.unique_items_counter - 1; i++) { store.inventory[i] = store.inventory[i + 1]; } inventoryItemCopyTo(config::dungeon::objects::OBJ_NOTHING, store.inventory[store.unique_items_counter - 1].item); store.inventory[store.unique_items_counter - 1].cost = 0; store.unique_items_counter--; } } // Creates an item and inserts it into store's inven -RAK- static void storeItemCreate(int store_id, int16_t max_cost) { int free_id = popt(); for (int tries = 0; tries <= 3; tries++) { int id = store_choices[store_id][randomNumber(STORE_MAX_ITEM_TYPES) - 1]; inventoryItemCopyTo(id, treasure_list[free_id]); magicTreasureMagicalAbility(free_id, config::treasure::LEVEL_TOWN_OBJECTS); Inventory_t &item = treasure_list[free_id]; if (storeCheckPlayerItemsCount(stores[store_id], item)) { // Item must be good: cost > 0. if (item.cost > 0 && item.cost < max_cost) { // equivalent to calling spellIdentifyItem(), // except will not change the objects_identified array. itemIdentifyAsStoreBought(item); int dummy; storeCarryItem(store_id, dummy, item); tries = 10; } } } pusht((uint8_t) free_id); } umoria-5.7.10+20181022/src/wizard.cpp0000644000175000017500000003533613363422757015566 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. // Version history and info, and wizard mode debugging aids. #include "headers.h" #include "dice.h" #include // lets anyone enter wizard mode after a disclaimer... -JEW- bool enterWizardMode() { bool answer = false; if (game.noscore == 0) { printMessage("Wizard mode is for debugging and experimenting."); answer = getInputConfirmation("The game will not be scored if you enter wizard mode. Are you sure?"); } if ((game.noscore != 0) || answer) { game.noscore |= 0x2; game.wizard_mode = true; return true; } return false; } // Light up the dungeon -RAK- void wizardLightUpDungeon() { bool flag; flag = !dg.floor[py.row][py.col].permanent_light; for (int y = 0; y < dg.height; y++) { for (int x = 0; x < dg.width; x++) { if (dg.floor[y][x].feature_id <= MAX_CAVE_FLOOR) { for (int yy = y - 1; yy <= y + 1; yy++) { for (int xx = x - 1; xx <= x + 1; xx++) { dg.floor[yy][xx].permanent_light = flag; if (!flag) { dg.floor[yy][xx].field_mark = false; } } } } } } drawDungeonPanel(); } // Wizard routine for gaining on stats -RAK- void wizardCharacterAdjustment() { int number; vtype_t input = {'\0'}; putStringClearToEOL("(3 - 118) Strength = ", Coord_t{0, 0}); if (getStringInput(input, Coord_t{0, 25}, 3)) { bool valid_number = stringToNumber(input, number); if (valid_number && number > 2 && number < 119) { py.stats.max[py_attrs::A_STR] = (uint8_t) number; (void) playerStatRestore(py_attrs::A_STR); } } else { return; } putStringClearToEOL("(3 - 118) Intelligence = ", Coord_t{0, 0}); if (getStringInput(input, Coord_t{0, 25}, 3)) { bool valid_number = stringToNumber(input, number); if (valid_number && number > 2 && number < 119) { py.stats.max[py_attrs::A_INT] = (uint8_t) number; (void) playerStatRestore(py_attrs::A_INT); } } else { return; } putStringClearToEOL("(3 - 118) Wisdom = ", Coord_t{0, 0}); if (getStringInput(input, Coord_t{0, 25}, 3)) { bool valid_number = stringToNumber(input, number); if (valid_number && number > 2 && number < 119) { py.stats.max[py_attrs::A_WIS] = (uint8_t) number; (void) playerStatRestore(py_attrs::A_WIS); } } else { return; } putStringClearToEOL("(3 - 118) Dexterity = ", Coord_t{0, 0}); if (getStringInput(input, Coord_t{0, 25}, 3)) { bool valid_number = stringToNumber(input, number); if (valid_number && number > 2 && number < 119) { py.stats.max[py_attrs::A_DEX] = (uint8_t) number; (void) playerStatRestore(py_attrs::A_DEX); } } else { return; } putStringClearToEOL("(3 - 118) Constitution = ", Coord_t{0, 0}); if (getStringInput(input, Coord_t{0, 25}, 3)) { bool valid_number = stringToNumber(input, number); if (valid_number && number > 2 && number < 119) { py.stats.max[py_attrs::A_CON] = (uint8_t) number; (void) playerStatRestore(py_attrs::A_CON); } } else { return; } putStringClearToEOL("(3 - 118) Charisma = ", Coord_t{0, 0}); if (getStringInput(input, Coord_t{0, 25}, 3)) { bool valid_number = stringToNumber(input, number); if (valid_number && number > 2 && number < 119) { py.stats.max[py_attrs::A_CHR] = (uint8_t) number; (void) playerStatRestore(py_attrs::A_CHR); } } else { return; } putStringClearToEOL("(1 - 32767) Hit points = ", Coord_t{0, 0}); if (getStringInput(input, Coord_t{0, 25}, 5)) { bool valid_number = stringToNumber(input, number); if (valid_number && number > 0 && number <= MAX_SHORT) { py.misc.max_hp = (int16_t) number; py.misc.current_hp = (int16_t) number; py.misc.current_hp_fraction = 0; printCharacterMaxHitPoints(); printCharacterCurrentHitPoints(); } } else { return; } putStringClearToEOL("(0 - 32767) Mana = ", Coord_t{0, 0}); if (getStringInput(input, Coord_t{0, 25}, 5)) { bool valid_number = stringToNumber(input, number); if (valid_number && number > -1 && number <= MAX_SHORT) { py.misc.mana = (int16_t) number; py.misc.current_mana = (int16_t) number; py.misc.current_mana_fraction = 0; printCharacterCurrentMana(); } } else { return; } (void) sprintf(input, "Current=%d Gold = ", py.misc.au); number = (int) strlen(input); putStringClearToEOL(input, Coord_t{0, 0}); if (getStringInput(input, Coord_t{0, number}, 7)) { int new_gold; bool valid_number = stringToNumber(input, new_gold); if (valid_number && new_gold > -1) { py.misc.au = new_gold; printCharacterGoldValue(); } } else { return; } (void) sprintf(input, "Current=%d (0-200) Searching = ", py.misc.chance_in_search); number = (int) strlen(input); putStringClearToEOL(input, Coord_t{0, 0}); if (getStringInput(input, Coord_t{0, number}, 3)) { int new_gold; bool valid_number = stringToNumber(input, new_gold); if (valid_number && number > -1 && number < 201) { py.misc.chance_in_search = (int16_t) number; } } else { return; } (void) sprintf(input, "Current=%d (-1-18) Stealth = ", py.misc.stealth_factor); number = (int) strlen(input); putStringClearToEOL(input, Coord_t{0, 0}); if (getStringInput(input, Coord_t{0, number}, 3)) { bool valid_number = stringToNumber(input, number); if (valid_number && number > -2 && number < 19) { py.misc.stealth_factor = (int16_t) number; } } else { return; } (void) sprintf(input, "Current=%d (0-200) Disarming = ", py.misc.disarm); number = (int) strlen(input); putStringClearToEOL(input, Coord_t{0, 0}); if (getStringInput(input, Coord_t{0, number}, 3)) { bool valid_number = stringToNumber(input, number); if (valid_number && number > -1 && number < 201) { py.misc.disarm = (int16_t) number; } } else { return; } (void) sprintf(input, "Current=%d (0-100) Save = ", py.misc.saving_throw); number = (int) strlen(input); putStringClearToEOL(input, Coord_t{0, 0}); if (getStringInput(input, Coord_t{0, number}, 3)) { bool valid_number = stringToNumber(input, number); if (valid_number && number > -1 && number < 201) { py.misc.saving_throw = (int16_t) number; } } else { return; } (void) sprintf(input, "Current=%d (0-200) Base to hit = ", py.misc.bth); number = (int) strlen(input); putStringClearToEOL(input, Coord_t{0, 0}); if (getStringInput(input, Coord_t{0, number}, 3)) { bool valid_number = stringToNumber(input, number); if (valid_number && number > -1 && number < 201) { py.misc.bth = (int16_t) number; } } else { return; } (void) sprintf(input, "Current=%d (0-200) Bows/Throwing = ", py.misc.bth_with_bows); number = (int) strlen(input); putStringClearToEOL(input, Coord_t{0, 0}); if (getStringInput(input, Coord_t{0, number}, 3)) { bool valid_number = stringToNumber(input, number); if (valid_number && number > -1 && number < 201) { py.misc.bth_with_bows = (int16_t) number; } } else { return; } (void) sprintf(input, "Current=%d Weight = ", py.misc.weight); number = (int) strlen(input); putStringClearToEOL(input, Coord_t{0, 0}); if (getStringInput(input, Coord_t{0, number}, 3)) { bool valid_number = stringToNumber(input, number); if (valid_number && number > -1) { py.misc.weight = (uint16_t) number; } } else { return; } while (getCommand("Alter speed? (+/-)", *input)) { if (*input == '+') { playerChangeSpeed(-1); } else if (*input == '-') { playerChangeSpeed(1); } else { break; } printCharacterSpeed(); } } // Request user input to get the array index of the `game_objects[]` static bool wizardRequestObjectId(int &id, const std::string &label, int start_id, int end_id) { std::ostringstream id_str; id_str << start_id << "-" << end_id; std::string msg = label + " ID (" + id_str.str() + "): "; putStringClearToEOL(msg, Coord_t{0, 0}); vtype_t input = {0}; if (!getStringInput(input, Coord_t{0, (int) msg.length()}, 3)) { return false; } int given_id; if (!stringToNumber(input, given_id)) { return false; } if (given_id < start_id || given_id > end_id) { putStringClearToEOL("Invalid ID. Must be " + id_str.str(), Coord_t{0, 0}); return false; } id = given_id; return true; } // Simplified wizard routine for creating an object void wizardGenerateObject() { int id; if (!wizardRequestObjectId(id, "Dungeon/Store object", 0, 366)) return; for (int i = 0; i < 10; i++) { int j = py.row - 3 + randomNumber(5); int k = py.col - 4 + randomNumber(7); if (coordInBounds(Coord_t{j, k}) && dg.floor[j][k].feature_id <= MAX_CAVE_FLOOR && dg.floor[j][k].treasure_id == 0) { // delete any object at location, before call popt() if (dg.floor[j][k].treasure_id != 0) { (void) dungeonDeleteObject(Coord_t{j, k}); } // place the object int free_treasure_id = popt(); dg.floor[j][k].treasure_id = (uint8_t) free_treasure_id; inventoryItemCopyTo(id, treasure_list[free_treasure_id]); magicTreasureMagicalAbility(free_treasure_id, dg.current_level); // auto identify the item itemIdentify(treasure_list[free_treasure_id], free_treasure_id); i = 9; } } } // Wizard routine for creating objects -RAK- void wizardCreateObjects() { int number; vtype_t input = {0}; printMessage("Warning: This routine can cause a fatal error."); Inventory_t forge{}; Inventory_t &item = forge; item.id = config::dungeon::objects::OBJ_WIZARD; item.special_name_id = 0; itemReplaceInscription(item, "wizard item"); item.identification = config::identification::ID_KNOWN2 | config::identification::ID_STORE_BOUGHT; putStringClearToEOL("Tval : ", Coord_t{0, 0}); if (!getStringInput(input, Coord_t{0, 9}, 3)) { return; } if (stringToNumber(input, number)) { item.category_id = (uint8_t) number; } putStringClearToEOL("Tchar : ", Coord_t{0, 0}); if (!getStringInput(input, Coord_t{0, 9}, 1)) { return; } item.sprite = (uint8_t) input[0]; putStringClearToEOL("Subval : ", Coord_t{0, 0}); if (!getStringInput(input, Coord_t{0, 9}, 5)) { return; } if (stringToNumber(input, number)) { item.sub_category_id = (uint8_t) number; } putStringClearToEOL("Weight : ", Coord_t{0, 0}); if (!getStringInput(input, Coord_t{0, 9}, 5)) { return; } if (stringToNumber(input, number)) { item.weight = (uint16_t) number; } putStringClearToEOL("Number : ", Coord_t{0, 0}); if (!getStringInput(input, Coord_t{0, 9}, 5)) { return; } if (stringToNumber(input, number)) { item.items_count = (uint8_t) number; } putStringClearToEOL("Damage (dice): ", Coord_t{0, 0}); if (!getStringInput(input, Coord_t{0, 15}, 3)) { return; } if (stringToNumber(input, number)) { item.damage.dice = (uint8_t) number; } putStringClearToEOL("Damage (sides): ", Coord_t{0, 0}); if (!getStringInput(input, Coord_t{0, 16}, 3)) { return; } if (stringToNumber(input, number)) { item.damage.sides = (uint8_t) number; } putStringClearToEOL("+To hit: ", Coord_t{0, 0}); if (!getStringInput(input, Coord_t{0, 9}, 3)) { return; } if (stringToNumber(input, number)) { item.to_hit = (int16_t) number; } putStringClearToEOL("+To dam: ", Coord_t{0, 0}); if (!getStringInput(input, Coord_t{0, 9}, 3)) { return; } if (stringToNumber(input, number)) { item.to_damage = (int16_t) number; } putStringClearToEOL("AC : ", Coord_t{0, 0}); if (!getStringInput(input, Coord_t{0, 9}, 3)) { return; } if (stringToNumber(input, number)) { item.ac = (int16_t) number; } putStringClearToEOL("+To AC : ", Coord_t{0, 0}); if (!getStringInput(input, Coord_t{0, 9}, 3)) { return; } if (stringToNumber(input, number)) { item.to_ac = (int16_t) number; } putStringClearToEOL("P1 : ", Coord_t{0, 0}); if (!getStringInput(input, Coord_t{0, 9}, 5)) { return; } if (stringToNumber(input, number)) { item.misc_use = (int16_t) number; } putStringClearToEOL("Flags (In HEX): ", Coord_t{0, 0}); if (!getStringInput(input, Coord_t{0, 16}, 8)) { return; } // can't be constant string, this causes problems with // the GCC compiler and some scanf routines. char pattern[4]; (void) strcpy(pattern, "%lx"); int32_t input_number; (void) sscanf(input, pattern, &input_number); item.flags = (uint32_t) input_number; putStringClearToEOL("Cost : ", Coord_t{0, 0}); if (!getStringInput(input, Coord_t{0, 9}, 8)) { return; } if (stringToNumber(input, input_number)) { item.cost = input_number; } putStringClearToEOL("Level : ", Coord_t{0, 0}); if (!getStringInput(input, Coord_t{0, 10}, 3)) { return; } if (stringToNumber(input, number)) { item.depth_first_found = (uint8_t) number; } if (getInputConfirmation("Allocate?")) { // delete object first if any, before call popt() Tile_t &tile = dg.floor[py.row][py.col]; if (tile.treasure_id != 0) { (void) dungeonDeleteObject(Coord_t{py.row, py.col});; } number = popt(); treasure_list[number] = forge; tile.treasure_id = (uint8_t) number; printMessage("Allocated."); } else { printMessage("Aborted."); } } umoria-5.7.10+20181022/src/ui.cpp0000644000175000017500000006105213363422757014675 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. #include "headers.h" static const char *stat_names[] = {"STR : ", "INT : ", "WIS : ", "DEX : ", "CON : ", "CHR : ",}; #define BLANK_LENGTH 24 static char blank_string[] = " "; // Track screen changes for inventory commands bool screen_has_changed = false; bool message_ready_to_print; // Set with first message vtype_t messages[MESSAGE_HISTORY_SIZE]; // Saved message history -CJS- int16_t last_message_id = 0; // Index of last message held in saved messages array // Calculates current boundaries -RAK- static void panelBounds() { dg.panel.top = dg.panel.row * (SCREEN_HEIGHT / 2); dg.panel.bottom = dg.panel.top + SCREEN_HEIGHT - 1; dg.panel.row_prt = dg.panel.top - 1; dg.panel.left = dg.panel.col * (SCREEN_WIDTH / 2); dg.panel.right = dg.panel.left + SCREEN_WIDTH - 1; dg.panel.col_prt = dg.panel.left - 13; } // Given an row (y) and col (x), this routine detects -RAK- // when a move off the screen has occurred and figures new borders. // `force` forces the panel bounds to be recalculated, useful for 'W'here. bool coordOutsidePanel(Coord_t coord, bool force) { int row = dg.panel.row; int col = dg.panel.col; if (force || coord.y < dg.panel.top + 2 || coord.y > dg.panel.bottom - 2) { row = (coord.y - SCREEN_HEIGHT / 4) / (SCREEN_HEIGHT / 2); if (row > dg.panel.max_rows) { row = dg.panel.max_rows; } else if (row < 0) { row = 0; } } if (force || coord.x < dg.panel.left + 3 || coord.x > dg.panel.right - 3) { col = ((coord.x - SCREEN_WIDTH / 4) / (SCREEN_WIDTH / 2)); if (col > dg.panel.max_cols) { col = dg.panel.max_cols; } else if (col < 0) { col = 0; } } if (row != dg.panel.row || col != dg.panel.col) { dg.panel.row = row; dg.panel.col = col; panelBounds(); // stop movement if any if (config::options::find_bound) { playerEndRunning(); } // Yes, the coordinates are beyond the current panel boundary return true; } return false; } // Is the given coordinate within the screen panel boundaries -RAK- bool coordInsidePanel(Coord_t coord) { bool valid_y = coord.y >= dg.panel.top && coord.y <= dg.panel.bottom; bool valid_x = coord.x >= dg.panel.left && coord.x <= dg.panel.right; return valid_y && valid_x; } // Prints the map of the dungeon -RAK- void drawDungeonPanel() { int line = 1; // Top to bottom for (int y = dg.panel.top; y <= dg.panel.bottom; y++) { eraseLine(Coord_t{line++, 13}); // Left to right for (int x = dg.panel.left; x <= dg.panel.right; x++) { char ch = caveGetTileSymbol(Coord_t{y, x}); if (ch != ' ') { panelPutTile(ch, Coord_t{y, x}); } } } } // Draws entire screen -RAK- void drawCavePanel() { clearScreen(); printCharacterStatsBlock(); drawDungeonPanel(); printCharacterCurrentDepth(); } // We need to reset the view of things. -CJS- void dungeonResetView() { Tile_t const &tile = dg.floor[py.row][py.col]; // Check for new panel if (coordOutsidePanel(Coord_t{py.row, py.col}, false)) { drawDungeonPanel(); } // Move the light source dungeonMoveCharacterLight(Coord_t{py.row, py.col}, Coord_t{py.row, py.col}); // A room of light should be lit. if (tile.feature_id == TILE_LIGHT_FLOOR) { if (py.flags.blind < 1 && !tile.permanent_light) { dungeonLightRoom(Coord_t{py.row, py.col}); } return; } // In doorway of light-room? if (tile.perma_lit_room && py.flags.blind < 1) { for (int i = py.row - 1; i <= py.row + 1; i++) { for (int j = py.col - 1; j <= py.col + 1; j++) { if (dg.floor[i][j].feature_id == TILE_LIGHT_FLOOR && !dg.floor[i][j].permanent_light) { dungeonLightRoom(Coord_t{i, j}); } } } } } // Converts stat num into string -RAK- void statsAsString(uint8_t stat, char *stat_string) { if (stat <= 18) { (void) sprintf(stat_string, "%6d", stat); return; } int part1 = 18; int part2 = stat - 18; if (part2 == 100) { (void) strcpy(stat_string, "18/100"); return; } (void) sprintf(stat_string, " %2d/%02d", part1, part2); } // Print character stat in given row, column -RAK- void displayCharacterStats(int stat) { char text[7]; statsAsString(py.stats.used[stat], text); putString(stat_names[stat], Coord_t{6 + stat, STAT_COLUMN}); putString(text, Coord_t{6 + stat, STAT_COLUMN + 6}); } // Print character info in given row, column -RAK- // The longest title is 13 characters, so only pad to 13 static void printCharacterInfoInField(const char *info, int row, int column) { // blank out the current field space putString(&blank_string[BLANK_LENGTH - 13], Coord_t{row, column}); putString(info, Coord_t{row, column}); } // Print long number with header at given row, column static void printHeaderLongNumber(const char *header, int32_t num, int row, int column) { vtype_t str = {'\0'}; (void) sprintf(str, "%s: %6d", header, num); putString(str, Coord_t{row, column}); } // Print long number (7 digits of space) with header at given row, column static void printHeaderLongNumber7Spaces(const char *header, int32_t num, int row, int column) { vtype_t str = {'\0'}; (void) sprintf(str, "%s: %7d", header, num); putString(str, Coord_t{row, column}); } // Print number with header at given row, column -RAK- static void printHeaderNumber(const char *header, int num, int row, int column) { vtype_t str = {'\0'}; (void) sprintf(str, "%s: %6d", header, num); putString(str, Coord_t{row, column}); } // Print long number at given row, column static void printLongNumber(int32_t num, int row, int column) { vtype_t str = {'\0'}; (void) sprintf(str, "%6d", num); putString(str, Coord_t{row, column}); } // Print number at given row, column -RAK- static void printNumber(int num, int row, int column) { vtype_t str = {'\0'}; (void) sprintf(str, "%6d", num); putString(str, Coord_t{row, column}); } // Prints title of character -RAK- void printCharacterTitle() { printCharacterInfoInField(playerRankTitle(), 4, STAT_COLUMN); } // Prints level -RAK- void printCharacterLevel() { printNumber((int) py.misc.level, 13, STAT_COLUMN + 6); } // Prints players current mana points. -RAK- void printCharacterCurrentMana() { printNumber(py.misc.current_mana, 15, STAT_COLUMN + 6); } // Prints Max hit points -RAK- void printCharacterMaxHitPoints() { printNumber(py.misc.max_hp, 16, STAT_COLUMN + 6); } // Prints players current hit points -RAK- void printCharacterCurrentHitPoints() { printNumber(py.misc.current_hp, 17, STAT_COLUMN + 6); } // prints current AC -RAK- void printCharacterCurrentArmorClass() { printNumber(py.misc.display_ac, 19, STAT_COLUMN + 6); } // Prints current gold -RAK- void printCharacterGoldValue() { printLongNumber(py.misc.au, 20, STAT_COLUMN + 6); } // Prints depth in stat area -RAK- void printCharacterCurrentDepth() { vtype_t depths = {'\0'}; int depth = dg.current_level * 50; if (depth == 0) { (void) strcpy(depths, "Town level"); } else { (void) sprintf(depths, "%d feet", depth); } putStringClearToEOL(depths, Coord_t{23, 65}); } // Prints status of hunger -RAK- void printCharacterHungerStatus() { if ((py.flags.status & config::player::status::PY_WEAK) != 0u) { putString("Weak ", Coord_t{23, 0}); } else if ((py.flags.status & config::player::status::PY_HUNGRY) != 0u) { putString("Hungry", Coord_t{23, 0}); } else { putString(&blank_string[BLANK_LENGTH - 6], Coord_t{23, 0}); } } // Prints Blind status -RAK- void printCharacterBlindStatus() { if ((py.flags.status & config::player::status::PY_BLIND) != 0u) { putString("Blind", Coord_t{23, 7}); } else { putString(&blank_string[BLANK_LENGTH - 5], Coord_t{23, 7}); } } // Prints Confusion status -RAK- void printCharacterConfusedState() { if ((py.flags.status & config::player::status::PY_CONFUSED) != 0u) { putString("Confused", Coord_t{23, 13}); } else { putString(&blank_string[BLANK_LENGTH - 8], Coord_t{23, 13}); } } // Prints Fear status -RAK- void printCharacterFearState() { if ((py.flags.status & config::player::status::PY_FEAR) != 0u) { putString("Afraid", Coord_t{23, 22}); } else { putString(&blank_string[BLANK_LENGTH - 6], Coord_t{23, 22}); } } // Prints Poisoned status -RAK- void printCharacterPoisonedState() { if ((py.flags.status & config::player::status::PY_POISONED) != 0u) { putString("Poisoned", Coord_t{23, 29}); } else { putString(&blank_string[BLANK_LENGTH - 8], Coord_t{23, 29}); } } // Prints Searching, Resting, Paralysis, or 'count' status -RAK- void printCharacterMovementState() { py.flags.status &= ~config::player::status::PY_REPEAT; if (py.flags.paralysis > 1) { putString("Paralysed", Coord_t{23, 38}); return; } if ((py.flags.status & config::player::status::PY_REST) != 0u) { char restString[16]; if (py.flags.rest < 0) { (void) strcpy(restString, "Rest *"); } else if (config::options::display_counts) { (void) sprintf(restString, "Rest %-5d", py.flags.rest); } else { (void) strcpy(restString, "Rest"); } putString(restString, Coord_t{23, 38}); return; } if (game.command_count > 0) { char repeatString[16]; if (config::options::display_counts) { (void) sprintf(repeatString, "Repeat %-3d", game.command_count); } else { (void) strcpy(repeatString, "Repeat"); } py.flags.status |= config::player::status::PY_REPEAT; putString(repeatString, Coord_t{23, 38}); if ((py.flags.status & config::player::status::PY_SEARCH) != 0u) { putString("Search", Coord_t{23, 38}); } return; } if ((py.flags.status & config::player::status::PY_SEARCH) != 0u) { putString("Searching", Coord_t{23, 38}); return; } // "repeat 999" is 10 characters putString(&blank_string[BLANK_LENGTH - 10], Coord_t{23, 38}); } // Prints the speed of a character. -CJS- void printCharacterSpeed() { int speed = py.flags.speed; // Search mode. if ((py.flags.status & config::player::status::PY_SEARCH) != 0u) { speed--; } if (speed > 1) { putString("Very Slow", Coord_t{23, 49}); } else if (speed == 1) { putString("Slow ", Coord_t{23, 49}); } else if (speed == 0) { putString(&blank_string[BLANK_LENGTH - 9], Coord_t{23, 49}); } else if (speed == -1) { putString("Fast ", Coord_t{23, 49}); } else { putString("Very Fast", Coord_t{23, 49}); } } void printCharacterStudyInstruction() { py.flags.status &= ~config::player::status::PY_STUDY; if (py.flags.new_spells_to_learn == 0) { putString(&blank_string[BLANK_LENGTH - 5], Coord_t{23, 59}); } else { putString("Study", Coord_t{23, 59}); } } // Prints winner status on display -RAK- void printCharacterWinner() { if ((game.noscore & 0x2) != 0) { if (game.wizard_mode) { putString("Is wizard ", Coord_t{22, 0}); } else { putString("Was wizard ", Coord_t{22, 0}); } } else if ((game.noscore & 0x1) != 0) { putString("Resurrected", Coord_t{22, 0}); } else if ((game.noscore & 0x4) != 0) { putString("Duplicate", Coord_t{22, 0}); } else if (game.total_winner) { putString("*Winner* ", Coord_t{22, 0}); } } // Prints character-screen info -RAK- void printCharacterStatsBlock() { printCharacterInfoInField(character_races[py.misc.race_id].name, 2, STAT_COLUMN); printCharacterInfoInField(classes[py.misc.class_id].title, 3, STAT_COLUMN); printCharacterInfoInField(playerRankTitle(), 4, STAT_COLUMN); for (int i = 0; i < 6; i++) { displayCharacterStats(i); } printHeaderNumber("LEV ", (int) py.misc.level, 13, STAT_COLUMN); printHeaderLongNumber("EXP ", py.misc.exp, 14, STAT_COLUMN); printHeaderNumber("MANA", py.misc.current_mana, 15, STAT_COLUMN); printHeaderNumber("MHP ", py.misc.max_hp, 16, STAT_COLUMN); printHeaderNumber("CHP ", py.misc.current_hp, 17, STAT_COLUMN); printHeaderNumber("AC ", py.misc.display_ac, 19, STAT_COLUMN); printHeaderLongNumber("GOLD", py.misc.au, 20, STAT_COLUMN); printCharacterWinner(); uint32_t status = py.flags.status; if (((config::player::status::PY_HUNGRY | config::player::status::PY_WEAK) & status) != 0u) { printCharacterHungerStatus(); } if ((status & config::player::status::PY_BLIND) != 0u) { printCharacterBlindStatus(); } if ((status & config::player::status::PY_CONFUSED) != 0u) { printCharacterConfusedState(); } if ((status & config::player::status::PY_FEAR) != 0u) { printCharacterFearState(); } if ((status & config::player::status::PY_POISONED) != 0u) { printCharacterPoisonedState(); } if (((config::player::status::PY_SEARCH | config::player::status::PY_REST) & status) != 0u) { printCharacterMovementState(); } // if speed non zero, print it, modify speed if Searching int16_t speed = py.flags.speed - (int16_t) ((status & config::player::status::PY_SEARCH) >> 8); if (speed != 0) { printCharacterSpeed(); } // display the study field printCharacterStudyInstruction(); } // Prints the following information on the screen. -JWT- void printCharacterInformation() { clearScreen(); putString("Name :", Coord_t{2, 1}); putString("Race :", Coord_t{3, 1}); putString("Sex :", Coord_t{4, 1}); putString("Class :", Coord_t{5, 1}); if (!game.character_generated) { return; } putString(py.misc.name, Coord_t{2, 15}); putString(character_races[py.misc.race_id].name, Coord_t{3, 15}); putString((playerGetGenderLabel()), Coord_t{4, 15}); putString(classes[py.misc.class_id].title, Coord_t{5, 15}); } // Prints the following information on the screen. -JWT- void printCharacterStats() { for (int i = 0; i < 6; i++) { vtype_t buf = {'\0'}; statsAsString(py.stats.used[i], buf); putString(stat_names[i], Coord_t{2 + i, 61}); putString(buf, Coord_t{2 + i, 66}); if (py.stats.max[i] > py.stats.current[i]) { statsAsString(py.stats.max[i], buf); putString(buf, Coord_t{2 + i, 73}); } } printHeaderNumber("+ To Hit ", py.misc.display_to_hit, 9, 1); printHeaderNumber("+ To Damage ", py.misc.display_to_damage, 10, 1); printHeaderNumber("+ To AC ", py.misc.display_to_ac, 11, 1); printHeaderNumber(" Total AC ", py.misc.display_ac, 12, 1); } // Returns a rating of x depending on y -JWT- const char *statRating(int y, int x) { switch (x / y) { case -3: case -2: case -1: return "Very Bad"; case 0: case 1: return "Bad"; case 2: return "Poor"; case 3: case 4: return "Fair"; case 5: return "Good"; case 6: return "Very Good"; case 7: case 8: return "Excellent"; default: return "Superb"; } } // Prints age, height, weight, and SC -JWT- void printCharacterVitalStatistics() { printHeaderNumber("Age ", (int) py.misc.age, 2, 38); printHeaderNumber("Height ", (int) py.misc.height, 3, 38); printHeaderNumber("Weight ", (int) py.misc.weight, 4, 38); printHeaderNumber("Social Class ", (int) py.misc.social_class, 5, 38); } // Prints the following information on the screen. -JWT- void printCharacterLevelExperience() { printHeaderLongNumber7Spaces("Level ", (int32_t) py.misc.level, 9, 28); printHeaderLongNumber7Spaces("Experience ", py.misc.exp, 10, 28); printHeaderLongNumber7Spaces("Max Exp ", py.misc.max_exp, 11, 28); if (py.misc.level >= PLAYER_MAX_LEVEL) { putStringClearToEOL("Exp to Adv.: *******", Coord_t{12, 28}); } else { printHeaderLongNumber7Spaces("Exp to Adv.", (int32_t) (py.base_exp_levels[py.misc.level - 1] * py.misc.experience_factor / 100), 12, 28); } printHeaderLongNumber7Spaces("Gold ", py.misc.au, 13, 28); printHeaderNumber("Max Hit Points ", py.misc.max_hp, 9, 52); printHeaderNumber("Cur Hit Points ", py.misc.current_hp, 10, 52); printHeaderNumber("Max Mana ", py.misc.mana, 11, 52); printHeaderNumber("Cur Mana ", py.misc.current_mana, 12, 52); } // Prints ratings on certain abilities -RAK- void printCharacterAbilities() { clearToBottom(14); int xbth = py.misc.bth + py.misc.plusses_to_hit * BTH_PER_PLUS_TO_HIT_ADJUST + (class_level_adj[py.misc.class_id][py_class_level_adj::CLASS_BTH] * py.misc.level); int xbthb = py.misc.bth_with_bows + py.misc.plusses_to_hit * BTH_PER_PLUS_TO_HIT_ADJUST + (class_level_adj[py.misc.class_id][py_class_level_adj::CLASS_BTHB] * py.misc.level); // this results in a range from 0 to 29 int xfos = 40 - py.misc.fos; if (xfos < 0) { xfos = 0; } int xsrh = py.misc.chance_in_search; // this results in a range from 0 to 9 int xstl = py.misc.stealth_factor + 1; int xdis = py.misc.disarm + 2 * playerDisarmAdjustment() + playerStatAdjustmentWisdomIntelligence(py_attrs::A_INT) + (class_level_adj[py.misc.class_id][py_class_level_adj::CLASS_DISARM] * py.misc.level / 3); int xsave = py.misc.saving_throw + playerStatAdjustmentWisdomIntelligence(py_attrs::A_WIS) + (class_level_adj[py.misc.class_id][py_class_level_adj::CLASS_SAVE] * py.misc.level / 3); int xdev = py.misc.saving_throw + playerStatAdjustmentWisdomIntelligence(py_attrs::A_INT) + (class_level_adj[py.misc.class_id][py_class_level_adj::CLASS_DEVICE] * py.misc.level / 3); vtype_t xinfra = {'\0'}; (void) sprintf(xinfra, "%d feet", py.flags.see_infra * 10); putString("(Miscellaneous Abilities)", Coord_t{15, 25}); putString("Fighting :", Coord_t{16, 1}); putString(statRating(12, xbth), Coord_t{16, 15}); putString("Bows/Throw :", Coord_t{17, 1}); putString(statRating(12, xbthb), Coord_t{17, 15}); putString("Saving Throw:", Coord_t{18, 1}); putString(statRating(6, xsave), Coord_t{18, 15}); putString("Stealth :", Coord_t{16, 28}); putString(statRating(1, xstl), Coord_t{16, 42}); putString("Disarming :", Coord_t{17, 28}); putString(statRating(8, xdis), Coord_t{17, 42}); putString("Magic Device:", Coord_t{18, 28}); putString(statRating(6, xdev), Coord_t{18, 42}); putString("Perception :", Coord_t{16, 55}); putString(statRating(3, xfos), Coord_t{16, 69}); putString("Searching :", Coord_t{17, 55}); putString(statRating(6, xsrh), Coord_t{17, 69}); putString("Infra-Vision:", Coord_t{18, 55}); putString(xinfra, Coord_t{18, 69}); } // Used to display the character on the screen. -RAK- void printCharacter() { printCharacterInformation(); printCharacterVitalStatistics(); printCharacterStats(); printCharacterLevelExperience(); printCharacterAbilities(); } // Gets a name for the character -JWT- void getCharacterName() { putStringClearToEOL("Enter your player's name [press when finished]", Coord_t{21, 2}); putString(&blank_string[BLANK_LENGTH - 23], Coord_t{2, 15}); if (!getStringInput(py.misc.name, Coord_t{2, 15}, 23) || py.misc.name[0] == 0) { getDefaultPlayerName(py.misc.name); putString(py.misc.name, Coord_t{2, 15}); } clearToBottom(20); } // Changes the name of the character -JWT- void changeCharacterName() { vtype_t temp = {'\0'}; bool flag = false; printCharacter(); while (!flag) { putStringClearToEOL("ile character description. hange character name.", Coord_t{21, 2}); switch (getKeyInput()) { case 'c': getCharacterName(); flag = true; break; case 'f': putStringClearToEOL("File name:", Coord_t{0, 0}); if (getStringInput(temp, Coord_t{0, 10}, 60) && (temp[0] != 0)) { if (outputPlayerCharacterToFile(temp)) { flag = true; } } break; case ESCAPE: case ' ': case '\n': case '\r': flag = true; break; default: terminalBellSound(); break; } } } // Print list of spells -RAK- // if non_consecutive is -1: spells numbered consecutively from 'a' to 'a'+num // >=0: spells numbered by offset from non_consecutive void displaySpellsList(const int *spell_ids, int number_of_choices, bool comment, int non_consecutive) { int col; if (comment) { col = 22; } else { col = 31; } int consecutive_offset = (classes[py.misc.class_id].class_to_use_mage_spells == config::spells::SPELL_TYPE_MAGE ? config::spells::NAME_OFFSET_SPELLS : config::spells::NAME_OFFSET_PRAYERS); eraseLine(Coord_t{1, col}); putString("Name", Coord_t{1, col + 5}); putString("Lv Mana Fail", Coord_t{1, col + 35}); // only show the first 22 choices if (number_of_choices > 22) { number_of_choices = 22; } for (int i = 0; i < number_of_choices; i++) { int spell_id = spell_ids[i]; Spell_t const &spell = magic_spells[py.misc.class_id - 1][spell_id]; const char *p = nullptr; if (!comment) { p = ""; } else if ((py.flags.spells_forgotten & (1L << spell_id)) != 0) { p = " forgotten"; } else if ((py.flags.spells_learnt & (1L << spell_id)) == 0) { p = " unknown"; } else if ((py.flags.spells_worked & (1L << spell_id)) == 0) { p = " untried"; } else { p = ""; } // determine whether or not to leave holes in character choices, non_consecutive -1 // when learning spells, consecutive_offset>=0 when asking which spell to cast. char spell_char; if (non_consecutive == -1) { spell_char = (char) ('a' + i); } else { spell_char = (char) ('a' + spell_id - non_consecutive); } vtype_t out_val = {'\0'}; (void) sprintf(out_val, " %c) %-30s%2d %4d %3d%%%s", spell_char, spell_names[spell_id + consecutive_offset], spell.level_required, spell.mana_required, spellChanceOfSuccess(spell_id), p); putStringClearToEOL(out_val, Coord_t{2 + i, col}); } } // Increases hit points and level -RAK- static void playerGainLevel() { py.misc.level++; vtype_t msg = {'\0'}; (void) sprintf(msg, "Welcome to level %d.", (int) py.misc.level); printMessage(msg); playerCalculateHitPoints(); int32_t new_exp = py.base_exp_levels[py.misc.level - 1] * py.misc.experience_factor / 100; if (py.misc.exp > new_exp) { // lose some of the 'extra' exp when gaining several levels at once int32_t dif_exp = py.misc.exp - new_exp; py.misc.exp = new_exp + (dif_exp / 2); } printCharacterLevel(); printCharacterTitle(); Class_t const &player_class = classes[py.misc.class_id]; if (player_class.class_to_use_mage_spells == config::spells::SPELL_TYPE_MAGE) { playerCalculateAllowedSpellsCount(py_attrs::A_INT); playerGainMana(py_attrs::A_INT); } else if (player_class.class_to_use_mage_spells == config::spells::SPELL_TYPE_PRIEST) { playerCalculateAllowedSpellsCount(py_attrs::A_WIS); playerGainMana(py_attrs::A_WIS); } } // Prints experience -RAK- void displayCharacterExperience() { if (py.misc.exp > config::player::PLAYER_MAX_EXP) { py.misc.exp = config::player::PLAYER_MAX_EXP; } while ((py.misc.level < PLAYER_MAX_LEVEL) && (signed) (py.base_exp_levels[py.misc.level - 1] * py.misc.experience_factor / 100) <= py.misc.exp) { playerGainLevel(); } if (py.misc.exp > py.misc.max_exp) { py.misc.max_exp = py.misc.exp; } printLongNumber(py.misc.exp, 14, STAT_COLUMN + 6); } umoria-5.7.10+20181022/src/data_store_owners.cpp0000644000175000017500000001257513363422757020010 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. // Store owner's speech text // clang-format off #include "headers.h" // Store owners have different characteristics for pricing and haggling // Note: Store owners should be added in groups, one for each store Owner_t store_owners[MAX_OWNERS] = { {"Erick the Honest (Human) General Store", 250, 175, 108, 4, 0, 12}, {"Mauglin the Grumpy (Dwarf) Armory", 32000, 200, 112, 4, 5, 5}, {"Arndal Beast-Slayer (Half-Elf) Weaponsmith", 10000, 185, 110, 5, 1, 8}, {"Hardblow the Humble (Human) Temple", 3500, 175, 109, 6, 0, 15}, {"Ga-nat the Greedy (Gnome) Alchemist", 12000, 220, 115, 4, 4, 9}, {"Valeria Starshine (Elf) Magic Shop", 32000, 175, 110, 5, 2, 11}, {"Andy the Friendly (Halfling) General Store", 200, 170, 108, 5, 3, 15}, {"Darg-Low the Grim (Human) Armory", 10000, 190, 111, 4, 0, 9}, {"Oglign Dragon-Slayer (Dwarf) Weaponsmith", 32000, 195, 112, 4, 5, 8}, {"Gunnar the Paladin (Human) Temple", 5000, 185, 110, 5, 0, 23}, {"Mauser the Chemist (Half-Elf) Alchemist", 10000, 190, 111, 5, 1, 8}, {"Gopher the Great! (Gnome) Magic Shop", 20000, 215, 113, 6, 4, 10}, {"Lyar-el the Comely (Elf) General Store", 300, 165, 107, 6, 2, 18}, {"Mauglim the Horrible (Half-Orc) Armory", 3000, 200, 113, 5, 6, 9}, {"Ithyl-Mak the Beastly (Half-Troll) Weaponsmith", 3000, 210, 115, 6, 7, 8}, {"Delilah the Pure (Half-Elf) Temple", 25000, 180, 107, 6, 1, 20}, {"Wizzle the Chaotic (Halfling) Alchemist", 10000, 190, 110, 6, 3, 8}, {"Inglorian the Mage (Human?) Magic Shop", 32000, 200, 110, 7, 0, 10}, }; const char *speech_sale_accepted[14] = { "Done!", "Accepted!", "Fine.", "Agreed!", "Ok.", "Taken!", "You drive a hard bargain, but taken.", "You'll force me bankrupt, but it's a deal.", "Sigh. I'll take it.", "My poor sick children may starve, but done!", "Finally! I accept.", "Robbed again.", "A pleasure to do business with you!", "My spouse will skin me, but accepted.", }; const char *speech_selling_haggle_final[3] = { "%A2 is my final offer; take it or leave it.", "I'll give you no more than %A2.", "My patience grows thin. %A2 is final.", }; const char *speech_selling_haggle[16] = { "%A1 for such a fine item? HA! No less than %A2.", "%A1 is an insult! Try %A2 gold pieces.", "%A1?!? You would rob my poor starving children?", "Why, I'll take no less than %A2 gold pieces.", "Ha! No less than %A2 gold pieces.", "Thou knave! No less than %A2 gold pieces.", "%A1 is far too little, how about %A2?", "I paid more than %A1 for it myself, try %A2.", "%A1? Are you mad?!? How about %A2 gold pieces?", "As scrap this would bring %A1. Try %A2 in gold.", "May the fleas of 1000 Orcs molest you. I want %A2.", "My mother you can get for %A1, this costs %A2.", "May your chickens grow lips. I want %A2 in gold!", "Sell this for such a pittance? Give me %A2 gold.", "May the Balrog find you tasty! %A2 gold pieces?", "Your mother was a Troll! %A2 or I'll tell.", }; const char *speech_buying_haggle_final[3] = { "I'll pay no more than %A1; take it or leave it.", "You'll get no more than %A1 from me.", "%A1 and that's final.", }; const char *speech_buying_haggle[15] = { "%A2 for that piece of junk? No more than %A1.", "For %A2 I could own ten of those. Try %A1.", "%A2? NEVER! %A1 is more like it.", "Let's be reasonable. How about %A1 gold pieces?", "%A1 gold for that junk, no more.", "%A1 gold pieces and be thankful for it!", "%A1 gold pieces and not a copper more.", "%A2 gold? HA! %A1 is more like it.", "Try about %A1 gold.", "I wouldn't pay %A2 for your children, try %A1.", "*CHOKE* For that!? Let's say %A1.", "How about %A1?", "That looks war surplus! Say %A1 gold.", "I'll buy it as scrap for %A1.", "%A2 is too much, let us say %A1 gold.", }; const char *speech_insulted_haggling_done[5] = { "ENOUGH! You have abused me once too often!", "THAT DOES IT! You shall waste my time no more!", "This is getting nowhere. I'm going home!", "BAH! No more shall you insult me!", "Begone! I have had enough abuse for one day.", }; const char *speech_get_out_of_my_store[5] = { "Out of my place!", "out... Out... OUT!!!", "Come back tomorrow.", "Leave my place. Begone!", "Come back when thou art richer.", }; const char *speech_haggling_try_again[10] = { "You will have to do better than that!", "That's an insult!", "Do you wish to do business or not?", "Hah! Try again.", "Ridiculous!", "You've got to be kidding!", "You'd better be kidding!", "You try my patience.", "I don't hear you.", "Hmmm, nice weather we're having.", }; const char *speech_sorry[5] = { "I must have heard you wrong.", "What was that?", "I'm sorry, say that again.", "What did you say?", "Sorry, what was that again?", }; umoria-5.7.10+20181022/src/spells.cpp0000644000175000017500000023344713363422757015573 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. // Player/creature spells, breaths, wands, scrolls, etc. code #include "headers.h" // Returns spell pointer -RAK- static bool spellGetId(int *spell_ids, int number_of_choices, int &spell_id, int &spell_chance, const char *prompt, int first_spell) { spell_id = -1; vtype_t str = {'\0'}; (void) sprintf(str, "(Spells %c-%c, *=List, =exit) %s", spell_ids[0] + 'a' - first_spell, spell_ids[number_of_choices - 1] + 'a' - first_spell, prompt); bool spell_found = false; bool redraw = false; int offset = (classes[py.misc.class_id].class_to_use_mage_spells == config::spells::SPELL_TYPE_MAGE ? config::spells::NAME_OFFSET_SPELLS : config::spells::NAME_OFFSET_PRAYERS); char choice; while (!spell_found && getCommand(str, choice)) { if (isupper((int) choice) != 0) { spell_id = choice - 'A' + first_spell; // verify that this is in spells[], at most 22 entries in class_to_use_mage_spells[] int test_spell_id; for (test_spell_id = 0; test_spell_id < number_of_choices; test_spell_id++) { if (spell_id == spell_ids[test_spell_id]) { break; } } if (test_spell_id == number_of_choices) { spell_id = -2; } else { Spell_t const &spell = magic_spells[py.misc.class_id - 1][spell_id]; vtype_t tmp_str = {'\0'}; (void) sprintf(tmp_str, "Cast %s (%d mana, %d%% fail)?", spell_names[spell_id + offset], spell.mana_required, spellChanceOfSuccess(spell_id)); if (getInputConfirmation(tmp_str)) { spell_found = true; } else { spell_id = -1; } } } else if (islower((int) choice) != 0) { spell_id = choice - 'a' + first_spell; // verify that this is in spells[], at most 22 entries in class_to_use_mage_spells[] int test_spell_id; for (test_spell_id = 0; test_spell_id < number_of_choices; test_spell_id++) { if (spell_id == spell_ids[test_spell_id]) { break; } } if (test_spell_id == number_of_choices) { spell_id = -2; } else { spell_found = true; } } else if (choice == '*') { // only do this drawing once if (!redraw) { terminalSaveScreen(); redraw = true; displaySpellsList(spell_ids, number_of_choices, false, first_spell); } } else if (isalpha((int) choice) != 0) { spell_id = -2; } else { spell_id = -1; terminalBellSound(); } if (spell_id == -2) { vtype_t tmp_str = {'\0'}; (void) sprintf(tmp_str, "You don't know that %s.", (offset == config::spells::NAME_OFFSET_SPELLS ? "spell" : "prayer")); printMessage(tmp_str); } } if (redraw) { terminalRestoreScreen(); } messageLineClear(); if (spell_found) { spell_chance = spellChanceOfSuccess(spell_id); } return spell_found; } // Return spell number and failure chance -RAK- // returns -1 if no spells in book // returns 1 if choose a spell in book to cast // returns 0 if don't choose a spell, i.e. exit with an escape // TODO: split into two functions; getting spell ID and casting an actual spell int castSpellGetId(const char *prompt, int item_id, int &spell_id, int &spell_chance) { // NOTE: `flags` gets set again, since getAndClearFirstBit modified it uint32_t flags = inventory[item_id].flags; int first_spell = getAndClearFirstBit(flags); flags = inventory[item_id].flags & py.flags.spells_learnt; // TODO(cook) move access to `magic_spells[]` directly to the for loop it's used in, below? Spell_t *spells = magic_spells[py.misc.class_id - 1]; int spell_count = 0; int spell_list[31]; while (flags != 0u) { int pos = getAndClearFirstBit(flags); if (spells[pos].level_required <= py.misc.level) { spell_list[spell_count] = pos; spell_count++; } } if (spell_count == 0) { return -1; } int result = 0; if (spellGetId(spell_list, spell_count, spell_id, spell_chance, prompt, first_spell)) { result = 1; } if ((result != 0) && magic_spells[py.misc.class_id - 1][spell_id].mana_required > py.misc.current_mana) { if (classes[py.misc.class_id].class_to_use_mage_spells == config::spells::SPELL_TYPE_MAGE) { result = (int) getInputConfirmation("You summon your limited strength to cast this one! Confirm?"); } else { result = (int) getInputConfirmation("The gods may think you presumptuous for this! Confirm?"); } } return result; } // Following are spell procedure/functions -RAK- // These routines are commonly used in the scroll, potion, wands, and // staves routines, and are occasionally called from other areas. // Now included are creature spells also. -RAK // Detect any treasure on the current panel -RAK- bool spellDetectTreasureWithinVicinity() { bool detected = false; for (int y = dg.panel.top; y <= dg.panel.bottom; y++) { for (int x = dg.panel.left; x <= dg.panel.right; x++) { Tile_t &tile = dg.floor[y][x]; if (tile.treasure_id != 0 && treasure_list[tile.treasure_id].category_id == TV_GOLD && !caveTileVisible(Coord_t{y, x})) { tile.field_mark = true; dungeonLiteSpot(Coord_t{y, x}); detected = true; } } } return detected; } // Detect all objects on the current panel -RAK- bool spellDetectObjectsWithinVicinity() { bool detected = false; for (int y = dg.panel.top; y <= dg.panel.bottom; y++) { for (int x = dg.panel.left; x <= dg.panel.right; x++) { Tile_t &tile = dg.floor[y][x]; if (tile.treasure_id != 0 && treasure_list[tile.treasure_id].category_id < TV_MAX_OBJECT && !caveTileVisible(Coord_t{y, x})) { tile.field_mark = true; dungeonLiteSpot(Coord_t{y, x}); detected = true; } } } return detected; } // Locates and displays traps on current panel -RAK- bool spellDetectTrapsWithinVicinity() { bool detected = false; for (int y = dg.panel.top; y <= dg.panel.bottom; y++) { for (int x = dg.panel.left; x <= dg.panel.right; x++) { Tile_t &tile = dg.floor[y][x]; if (tile.treasure_id == 0) { continue; } if (treasure_list[tile.treasure_id].category_id == TV_INVIS_TRAP) { tile.field_mark = true; trapChangeVisibility(Coord_t{y, x}); detected = true; } else if (treasure_list[tile.treasure_id].category_id == TV_CHEST) { Inventory_t &item = treasure_list[tile.treasure_id]; spellItemIdentifyAndRemoveRandomInscription(item); } } } return detected; } // Locates and displays all secret doors on current panel -RAK- bool spellDetectSecretDoorssWithinVicinity() { bool detected = false; for (int y = dg.panel.top; y <= dg.panel.bottom; y++) { for (int x = dg.panel.left; x <= dg.panel.right; x++) { Tile_t &tile = dg.floor[y][x]; if (tile.treasure_id == 0) { continue; } if (treasure_list[tile.treasure_id].category_id == TV_SECRET_DOOR) { // Secret doors tile.field_mark = true; trapChangeVisibility(Coord_t{y, x}); detected = true; } else if ((treasure_list[tile.treasure_id].category_id == TV_UP_STAIR || treasure_list[tile.treasure_id].category_id == TV_DOWN_STAIR) && !tile.field_mark) { // Staircases tile.field_mark = true; dungeonLiteSpot(Coord_t{y, x}); detected = true; } } } return detected; } // Locates and displays all invisible creatures on current panel -RAK- bool spellDetectInvisibleCreaturesWithinVicinity() { bool detected = false; for (int id = next_free_monster_id - 1; id >= config::monsters::MON_MIN_INDEX_ID; id--) { Monster_t &monster = monsters[id]; if (coordInsidePanel(Coord_t{monster.y, monster.x}) && ((creatures_list[monster.creature_id].movement & config::monsters::move::CM_INVISIBLE) != 0u)) { monster.lit = true; // works correctly even if hallucinating panelPutTile((char) creatures_list[monster.creature_id].sprite, Coord_t{monster.y, monster.x}); detected = true; } } if (detected) { printMessage("You sense the presence of invisible creatures!"); printMessage(CNIL); // must unlight every monster just lighted updateMonsters(false); } return detected; } // Light an area: -RAK- // 1. If corridor light immediate area // 2. If room light entire room plus immediate area. bool spellLightArea(int y, int x) { if (py.flags.blind < 1) { printMessage("You are surrounded by a white light."); } // NOTE: this is not changed anywhere. A bug or correct? -MRC- bool lit = true; if (dg.floor[y][x].perma_lit_room && dg.current_level > 0) { dungeonLightRoom(Coord_t{y, x}); } // Must always light immediate area, because one might be standing on // the edge of a room, or next to a destroyed area, etc. for (int i = y - 1; i <= y + 1; i++) { for (int j = x - 1; j <= x + 1; j++) { dg.floor[i][j].permanent_light = true; dungeonLiteSpot(Coord_t{i, j}); } } return lit; } // Darken an area, opposite of light area -RAK- bool spellDarkenArea(int y, int x) { bool darkened = false; if (dg.floor[y][x].perma_lit_room && dg.current_level > 0) { int half_height = (SCREEN_HEIGHT / 2); int half_width = (SCREEN_WIDTH / 2); int start_row = (y / half_height) * half_height + 1; int start_col = (x / half_width) * half_width + 1; int end_row = start_row + half_height - 1; int end_col = start_col + half_width - 1; for (int row = start_row; row <= end_row; row++) { for (int col = start_col; col <= end_col; col++) { Tile_t &tile = dg.floor[row][col]; if (tile.perma_lit_room && tile.feature_id <= MAX_CAVE_FLOOR) { tile.permanent_light = false; tile.feature_id = TILE_DARK_FLOOR; dungeonLiteSpot(Coord_t{row, col}); if (!caveTileVisible(Coord_t{row, col})) { darkened = true; } } } } } else { for (int row = y - 1; row <= y + 1; row++) { for (int col = x - 1; col <= x + 1; col++) { Tile_t &tile = dg.floor[row][col]; if (tile.feature_id == TILE_CORR_FLOOR && tile.permanent_light) { // permanent_light could have been set by star-lite wand, etc tile.permanent_light = false; darkened = true; } } } } if (darkened && py.flags.blind < 1) { printMessage("Darkness surrounds you."); } return darkened; } static void dungeonLightAreaAroundFloorTile(int y, int x) { for (int yy = y - 1; yy <= y + 1; yy++) { for (int xx = x - 1; xx <= x + 1; xx++) { Tile_t &tile = dg.floor[yy][xx]; if (tile.feature_id >= MIN_CAVE_WALL) { tile.permanent_light = true; } else if (tile.treasure_id != 0 && treasure_list[tile.treasure_id].category_id >= TV_MIN_VISIBLE && treasure_list[tile.treasure_id].category_id <= TV_MAX_VISIBLE) { tile.field_mark = true; } } } } // Map the current area plus some -RAK- void spellMapCurrentArea() { int row_min = dg.panel.top - randomNumber(10); int row_max = dg.panel.bottom + randomNumber(10); int col_min = dg.panel.left - randomNumber(20); int col_max = dg.panel.right + randomNumber(20); for (int y = row_min; y <= row_max; y++) { for (int x = col_min; x <= col_max; x++) { if (coordInBounds(Coord_t{y, x}) && dg.floor[y][x].feature_id <= MAX_CAVE_FLOOR) { dungeonLightAreaAroundFloorTile(y, x); } } } drawDungeonPanel(); } // Identify an object -RAK- bool spellIdentifyItem() { int item_id; if (!inventoryGetInputForItemId(item_id, "Item you wish identified?", 0, PLAYER_INVENTORY_SIZE, CNIL, CNIL)) { return false; } itemIdentify(inventory[item_id], item_id); Inventory_t &item = inventory[item_id]; spellItemIdentifyAndRemoveRandomInscription(item); obj_desc_t description = {'\0'}; itemDescription(description, item, true); obj_desc_t msg = {'\0'}; if (item_id >= player_equipment::EQUIPMENT_WIELD) { playerRecalculateBonuses(); (void) sprintf(msg, "%s: %s", playerItemWearingDescription(item_id), description); } else { (void) sprintf(msg, "%c %s", item_id + 97, description); } printMessage(msg); return true; } // Get all the monsters on the level pissed off. -RAK- bool spellAggravateMonsters(int affect_distance) { bool aggravated = false; for (int id = next_free_monster_id - 1; id >= config::monsters::MON_MIN_INDEX_ID; id--) { Monster_t &monster = monsters[id]; monster.sleep_count = 0; if (monster.distance_from_player <= affect_distance && monster.speed < 2) { monster.speed++; aggravated = true; } } if (aggravated) { printMessage("You hear a sudden stirring in the distance!"); } return aggravated; } // Surround the fool with traps (chuckle) -RAK- bool spellSurroundPlayerWithTraps() { for (int y = py.row - 1; y <= py.row + 1; y++) { for (int x = py.col - 1; x <= py.col + 1; x++) { // Don't put a trap under the player, since this can lead to // strange situations, e.g. falling through a trap door while // trying to rest, setting off a falling rock trap and ending // up under the rock. if (y == py.row && x == py.col) { continue; } Tile_t const &tile = dg.floor[y][x]; if (tile.feature_id <= MAX_CAVE_FLOOR) { if (tile.treasure_id != 0) { (void) dungeonDeleteObject(Coord_t{y, x});; } dungeonSetTrap(Coord_t{y, x}, randomNumber(config::dungeon::objects::MAX_TRAPS) - 1); // don't let player gain exp from the newly created traps treasure_list[tile.treasure_id].misc_use = 0; // open pits are immediately visible, so call dungeonLiteSpot dungeonLiteSpot(Coord_t{y, x}); } } } // traps are always placed, so just return true return true; } // Surround the player with doors. -RAK- bool spellSurroundPlayerWithDoors() { bool created = false; for (int y = py.row - 1; y <= py.row + 1; y++) { for (int x = py.col - 1; x <= py.col + 1; x++) { // Don't put a door under the player! if (y == py.row && x == py.col) { continue; } Tile_t &tile = dg.floor[y][x]; if (tile.feature_id <= MAX_CAVE_FLOOR) { if (tile.treasure_id != 0) { (void) dungeonDeleteObject(Coord_t{y, x});; } int free_id = popt(); tile.feature_id = TILE_BLOCKED_FLOOR; tile.treasure_id = (uint8_t) free_id; inventoryItemCopyTo(config::dungeon::objects::OBJ_CLOSED_DOOR, treasure_list[free_id]); dungeonLiteSpot(Coord_t{y, x}); created = true; } } } return created; } // Destroys any adjacent door(s)/trap(s) -RAK- bool spellDestroyAdjacentDoorsTraps() { bool destroyed = false; for (int y = py.row - 1; y <= py.row + 1; y++) { for (int x = py.col - 1; x <= py.col + 1; x++) { Tile_t const &tile = dg.floor[y][x]; if (tile.treasure_id == 0) { continue; } Inventory_t &item = treasure_list[tile.treasure_id]; if ((item.category_id >= TV_INVIS_TRAP && item.category_id <= TV_CLOSED_DOOR && item.category_id != TV_RUBBLE) || item.category_id == TV_SECRET_DOOR) { if (dungeonDeleteObject(Coord_t{y, x})) { destroyed = true; } } else if (item.category_id == TV_CHEST && item.flags != 0) { // destroy traps on chest and unlock item.flags &= ~(config::treasure::chests::CH_TRAPPED | config::treasure::chests::CH_LOCKED); item.special_name_id = special_name_ids::SN_UNLOCKED; destroyed = true; printMessage("You have disarmed the chest."); spellItemIdentifyAndRemoveRandomInscription(item); } } } return destroyed; } // Display all creatures on the current panel -RAK- bool spellDetectMonsters() { bool detected = false; for (int id = next_free_monster_id - 1; id >= config::monsters::MON_MIN_INDEX_ID; id--) { Monster_t &monster = monsters[id]; if (coordInsidePanel(Coord_t{monster.y, monster.x}) && (creatures_list[monster.creature_id].movement & config::monsters::move::CM_INVISIBLE) == 0) { monster.lit = true; detected = true; // works correctly even if hallucinating panelPutTile((char) creatures_list[monster.creature_id].sprite, Coord_t{monster.y, monster.x}); } } if (detected) { printMessage("You sense the presence of monsters!"); printMessage(CNIL); // must unlight every monster just lighted updateMonsters(false); } return detected; } // Update monster when light line spell touches it. static void spellLightLineTouchesMonster(int monster_id) { Monster_t const &monster = monsters[monster_id]; Creature_t const &creature = creatures_list[monster.creature_id]; // light up and draw monster monsterUpdateVisibility(monster_id); auto name = monsterNameDescription(creature.name, monster.lit); if ((creature.defenses & config::monsters::defense::CD_LIGHT) != 0) { if (monster.lit) { creature_recall[monster.creature_id].defenses |= config::monsters::defense::CD_LIGHT; } if (monsterTakeHit(monster_id, diceRoll(Dice_t{2, 8})) >= 0) { printMonsterActionText(name, "shrivels away in the light!"); displayCharacterExperience(); } else { printMonsterActionText(name, "cringes from the light!"); } } } // Leave a line of light in given dir, blue light can sometimes hurt creatures. -RAK- void spellLightLine(int x, int y, int direction) { int distance = 0; bool finished = false; while (!finished) { Tile_t &tile = dg.floor[y][x]; if (distance > config::treasure::OBJECT_BOLTS_MAX_RANGE || tile.feature_id >= MIN_CLOSED_SPACE) { (void) playerMovePosition(direction, y, x); finished = true; continue; // we're done here, break out of the loop } if (!tile.permanent_light && !tile.temporary_light) { // set permanent_light so that dungeonLiteSpot will work tile.permanent_light = true; if (tile.feature_id == TILE_LIGHT_FLOOR) { if (coordInsidePanel(Coord_t{y, x})) { dungeonLightRoom(Coord_t{y, x}); } } else { dungeonLiteSpot(Coord_t{y, x}); } } // set permanent_light in case temporary_light was true above tile.permanent_light = true; if (tile.creature_id > 1) { spellLightLineTouchesMonster((int) tile.creature_id); } // move must be at end because want to light up current spot (void) playerMovePosition(direction, y, x); distance++; } } // Light line in all directions -RAK- void spellStarlite(int y, int x) { if (py.flags.blind < 1) { printMessage("The end of the staff bursts into a blue shimmering light."); } for (int dir = 1; dir <= 9; dir++) { if (dir != 5) { spellLightLine(x, y, dir); } } } // Disarms all traps/chests in a given direction -RAK- bool spellDisarmAllInDirection(int y, int x, int direction) { int distance = 0; bool disarmed = false; Tile_t *tile = nullptr; do { tile = &dg.floor[y][x]; // note, must continue up to and including the first non open space, // because secret doors have feature_id greater than MAX_OPEN_SPACE if (tile->treasure_id != 0) { Inventory_t &item = treasure_list[tile->treasure_id]; if (item.category_id == TV_INVIS_TRAP || item.category_id == TV_VIS_TRAP) { if (dungeonDeleteObject(Coord_t{y, x})) { disarmed = true; } } else if (item.category_id == TV_CLOSED_DOOR) { // Locked or jammed doors become merely closed. item.misc_use = 0; } else if (item.category_id == TV_SECRET_DOOR) { tile->field_mark = true; trapChangeVisibility(Coord_t{y, x}); disarmed = true; } else if (item.category_id == TV_CHEST && item.flags != 0) { disarmed = true; printMessage("Click!"); item.flags &= ~(config::treasure::chests::CH_TRAPPED | config::treasure::chests::CH_LOCKED); item.special_name_id = special_name_ids::SN_UNLOCKED; spellItemIdentifyAndRemoveRandomInscription(item); } } // move must be at end because want to light up current spot (void) playerMovePosition(direction, y, x); distance++; } while (distance <= config::treasure::OBJECT_BOLTS_MAX_RANGE && tile->feature_id <= MAX_OPEN_SPACE); return disarmed; } // Return flags for given type area affect -RAK- static void spellGetAreaAffectFlags(int spell_type, uint32_t &weapon_type, int &harm_type, bool (**destroy)(Inventory_t *)) { switch (spell_type) { case magic_spell_flags::GF_MAGIC_MISSILE: weapon_type = 0; harm_type = 0; *destroy = setNull; break; case magic_spell_flags::GF_LIGHTNING: weapon_type = config::monsters::spells::CS_BR_LIGHT; harm_type = config::monsters::defense::CD_LIGHT; *destroy = setLightningDestroyableItems; break; case magic_spell_flags::GF_POISON_GAS: weapon_type = config::monsters::spells::CS_BR_GAS; harm_type = config::monsters::defense::CD_POISON; *destroy = setNull; break; case magic_spell_flags::GF_ACID: weapon_type = config::monsters::spells::CS_BR_ACID; harm_type = config::monsters::defense::CD_ACID; *destroy = setAcidDestroyableItems; break; case magic_spell_flags::GF_FROST: weapon_type = config::monsters::spells::CS_BR_FROST; harm_type = config::monsters::defense::CD_FROST; *destroy = setFrostDestroyableItems; break; case magic_spell_flags::GF_FIRE: weapon_type = config::monsters::spells::CS_BR_FIRE; harm_type = config::monsters::defense::CD_FIRE; *destroy = setFireDestroyableItems; break; case magic_spell_flags::GF_HOLY_ORB: weapon_type = 0; harm_type = config::monsters::defense::CD_EVIL; *destroy = setNull; break; default: printMessage("ERROR in spellGetAreaAffectFlags()\n"); } } static void printBoltStrikesMonsterMessage(Creature_t const &creature, const std::string &bolt_name, bool is_lit) { std::string monster_name; if (is_lit) { monster_name = "the " + std::string(creature.name); } else { monster_name = "it"; } std::string msg = "The " + bolt_name + " strikes " + monster_name + "."; printMessage(msg.c_str()); } // Light up, draw, and check for monster damage when Fire Bolt touches it. static void spellFireBoltTouchesMonster(Tile_t &tile, int damage, int harm_type, uint32_t weapon_id, const std::string &bolt_name) { Monster_t const &monster = monsters[tile.creature_id]; Creature_t const &creature = creatures_list[monster.creature_id]; // light up monster and draw monster, temporarily set // permanent_light so that `monsterUpdateVisibility()` will work bool saved_lit_status = tile.permanent_light; tile.permanent_light = true; monsterUpdateVisibility((int) tile.creature_id); tile.permanent_light = saved_lit_status; // draw monster and clear previous bolt putQIO(); printBoltStrikesMonsterMessage(creature, bolt_name, monster.lit); if ((harm_type & creature.defenses) != 0) { damage = damage * 2; if (monster.lit) { creature_recall[monster.creature_id].defenses |= harm_type; } } else if ((weapon_id & creature.spells) != 0u) { damage = damage / 4; if (monster.lit) { creature_recall[monster.creature_id].spells |= weapon_id; } } auto name = monsterNameDescription(creature.name, monster.lit); if (monsterTakeHit((int) tile.creature_id, damage) >= 0) { printMonsterActionText(name, "dies in a fit of agony."); displayCharacterExperience(); } else if (damage > 0) { printMonsterActionText(name, "screams in agony."); } } // Shoot a bolt in a given direction -RAK- void spellFireBolt(int y, int x, int direction, int damage_hp, int spell_type, const std::string &spell_name) { bool (*dummy)(Inventory_t *); int harm_type = 0; uint32_t weapon_type; spellGetAreaAffectFlags(spell_type, weapon_type, harm_type, &dummy); int distance = 0; bool finished = false; while (!finished) { int old_y = y; int old_x = x; (void) playerMovePosition(direction, y, x); distance++; Tile_t &tile = dg.floor[y][x]; dungeonLiteSpot(Coord_t{old_y, old_x}); if (distance > config::treasure::OBJECT_BOLTS_MAX_RANGE || tile.feature_id >= MIN_CLOSED_SPACE) { finished = true; continue; // we're done here, break out of the loop } if (tile.creature_id > 1) { finished = true; spellFireBoltTouchesMonster(tile, damage_hp, harm_type, weapon_type, spell_name); } else if (coordInsidePanel(Coord_t{y, x}) && py.flags.blind < 1) { panelPutTile('*', Coord_t{y, x}); // show the bolt putQIO(); } } } // Shoot a ball in a given direction. Note that balls have an area affect. -RAK- void spellFireBall(int y, int x, int direction, int damage_hp, int spell_type, const std::string &spell_name) { int total_hits = 0; int total_kills = 0; int max_distance = 2; bool (*destroy)(Inventory_t *); int harm_type; uint32_t weapon_type; spellGetAreaAffectFlags(spell_type, weapon_type, harm_type, &destroy); int distance = 0; bool finished = false; while (!finished) { int old_y = y; int old_x = x; (void) playerMovePosition(direction, y, x); distance++; dungeonLiteSpot(Coord_t{old_y, old_x}); if (distance > config::treasure::OBJECT_BOLTS_MAX_RANGE) { finished = true; continue; } Tile_t *tile = &dg.floor[y][x]; if (tile->feature_id >= MIN_CLOSED_SPACE || tile->creature_id > 1) { finished = true; if (tile->feature_id >= MIN_CLOSED_SPACE) { y = old_y; x = old_x; } // The ball hits and explodes. // The explosion. for (int row = y - max_distance; row <= y + max_distance; row++) { for (int col = x - max_distance; col <= x + max_distance; col++) { if (coordInBounds(Coord_t{row, col}) && coordDistanceBetween(Coord_t{y, x}, Coord_t{row, col}) <= max_distance && los(y, x, row, col)) { tile = &dg.floor[row][col]; if (tile->treasure_id != 0 && (*destroy)(&treasure_list[tile->treasure_id])) { (void) dungeonDeleteObject(Coord_t{row, col}); } if (tile->feature_id <= MAX_OPEN_SPACE) { if (tile->creature_id > 1) { Monster_t const &monster = monsters[tile->creature_id]; Creature_t const &creature = creatures_list[monster.creature_id]; // lite up creature if visible, temp set permanent_light so that monsterUpdateVisibility works bool saved_lit_status = tile->permanent_light; tile->permanent_light = true; monsterUpdateVisibility((int) tile->creature_id); total_hits++; int damage = damage_hp; if ((harm_type & creature.defenses) != 0) { damage = damage * 2; if (monster.lit) { creature_recall[monster.creature_id].defenses |= harm_type; } } else if ((weapon_type & creature.spells) != 0u) { damage = damage / 4; if (monster.lit) { creature_recall[monster.creature_id].spells |= weapon_type; } } damage = (damage / (coordDistanceBetween(Coord_t{row, col}, Coord_t{y, x}) + 1)); if (monsterTakeHit((int) tile->creature_id, damage) >= 0) { total_kills++; } tile->permanent_light = saved_lit_status; } else if (coordInsidePanel(Coord_t{row, col}) && py.flags.blind < 1) { panelPutTile('*', Coord_t{row, col}); } } } } } // show ball of whatever putQIO(); for (int row = (y - 2); row <= (y + 2); row++) { for (int col = (x - 2); col <= (x + 2); col++) { if (coordInBounds(Coord_t{row, col}) && coordInsidePanel(Coord_t{row, col}) && coordDistanceBetween(Coord_t{y, x}, Coord_t{row, col}) <= max_distance) { dungeonLiteSpot(Coord_t{row, col}); } } } // End explosion. if (total_hits == 1) { printMessage(("The " + spell_name + " envelops a creature!").c_str()); } else if (total_hits > 1) { printMessage(("The " + spell_name + " envelops several creatures!").c_str()); } if (total_kills == 1) { printMessage("There is a scream of agony!"); } else if (total_kills > 1) { printMessage("There are several screams of agony!"); } if (total_kills >= 0) { displayCharacterExperience(); } // End ball hitting. } else if (coordInsidePanel(Coord_t{y, x}) && py.flags.blind < 1) { panelPutTile('*', Coord_t{y, x}); // show bolt putQIO(); } } } // Breath weapon works like a spellFireBall(), but affects the player. // Note the area affect. -RAK- void spellBreath(int y, int x, int monster_id, int damage_hp, int spell_type, const std::string &spell_name) { int max_distance = 2; bool (*destroy)(Inventory_t *); int harm_type; uint32_t weapon_type; spellGetAreaAffectFlags(spell_type, weapon_type, harm_type, &destroy); for (int row = y - 2; row <= y + 2; row++) { for (int col = x - 2; col <= x + 2; col++) { if (coordInBounds(Coord_t{row, col}) && coordDistanceBetween(Coord_t{y, x}, Coord_t{row, col}) <= max_distance && los(y, x, row, col)) { Tile_t const &tile = dg.floor[row][col]; if (tile.treasure_id != 0 && (*destroy)(&treasure_list[tile.treasure_id])) { (void) dungeonDeleteObject(Coord_t{row, col}); } if (tile.feature_id <= MAX_OPEN_SPACE) { // must test status bit, not py.flags.blind here, flag could have // been set by a previous monster, but the breath should still // be visible until the blindness takes effect if (coordInsidePanel(Coord_t{row, col}) && ((py.flags.status & config::player::status::PY_BLIND) == 0u)) { panelPutTile('*', Coord_t{row, col}); } if (tile.creature_id > 1) { Monster_t &monster = monsters[tile.creature_id]; Creature_t const &creature = creatures_list[monster.creature_id]; int damage = damage_hp; if ((harm_type & creature.defenses) != 0) { damage = damage * 2; } else if ((weapon_type & creature.spells) != 0u) { damage = (damage / 4); } damage = (damage / (coordDistanceBetween(Coord_t{row, col}, Coord_t{y, x}) + 1)); // can not call monsterTakeHit here, since player does not // get experience for kill monster.hp = (int16_t) (monster.hp - damage); monster.sleep_count = 0; if (monster.hp < 0) { uint32_t treasure_id = monsterDeath((int) monster.y, (int) monster.x, creature.movement); if (monster.lit) { auto tmp = (uint32_t) ((creature_recall[monster.creature_id].movement & config::monsters::move::CM_TREASURE) >> config::monsters::move::CM_TR_SHIFT); if (tmp > ((treasure_id & config::monsters::move::CM_TREASURE) >> config::monsters::move::CM_TR_SHIFT)) { treasure_id = (uint32_t) ((treasure_id & ~config::monsters::move::CM_TREASURE) | (tmp << config::monsters::move::CM_TR_SHIFT)); } creature_recall[monster.creature_id].movement = (uint32_t) (treasure_id | (creature_recall[monster.creature_id].movement & ~config::monsters::move::CM_TREASURE)); } // It ate an already processed monster. Handle normally. if (monster_id < tile.creature_id) { dungeonDeleteMonster((int) tile.creature_id); } else { // If it eats this monster, an already processed monster // will take its place, causing all kinds of havoc. // Delay the kill a bit. dungeonDeleteMonsterFix1((int) tile.creature_id); } } } else if (tile.creature_id == 1) { int damage = (damage_hp / (coordDistanceBetween(Coord_t{row, col}, Coord_t{y, x}) + 1)); // let's do at least one point of damage // prevents randomNumber(0) problem with damagePoisonedGas, also if (damage == 0) { damage = 1; } switch (spell_type) { case magic_spell_flags::GF_LIGHTNING: damageLightningBolt(damage, spell_name.c_str()); break; case magic_spell_flags::GF_POISON_GAS: damagePoisonedGas(damage, spell_name.c_str()); break; case magic_spell_flags::GF_ACID: damageAcid(damage, spell_name.c_str()); break; case magic_spell_flags::GF_FROST: damageCold(damage, spell_name.c_str()); break; case magic_spell_flags::GF_FIRE: damageFire(damage, spell_name.c_str()); break; default: break; } } } } } } // show the ball of gas putQIO(); for (int row = (y - 2); row <= (y + 2); row++) { for (int col = (x - 2); col <= (x + 2); col++) { if (coordInBounds(Coord_t{row, col}) && coordInsidePanel(Coord_t{row, col}) && coordDistanceBetween(Coord_t{y, x}, Coord_t{row, col}) <= max_distance) { dungeonLiteSpot(Coord_t{row, col}); } } } } // Recharge a wand, staff, or rod. Sometimes the item breaks. -RAK- bool spellRechargeItem(int number_of_charges) { int item_pos_start, item_pos_end; if (!inventoryFindRange(TV_STAFF, TV_WAND, item_pos_start, item_pos_end)) { printMessage("You have nothing to recharge."); return false; } int item_id; if (!inventoryGetInputForItemId(item_id, "Recharge which item?", item_pos_start, item_pos_end, CNIL, CNIL)) { return false; } Inventory_t &item = inventory[item_id]; // recharge I = recharge(20) = 1/6 failure for empty 10th level wand // recharge II = recharge(60) = 1/10 failure for empty 10th level wand // // make it harder to recharge high level, and highly charged wands, // note that `fail_chance` can be negative, so check its value before // trying to call randomNumber(). int fail_chance = number_of_charges + 50 - (int) item.depth_first_found - item.misc_use; // Automatic failure. if (fail_chance < 19) { fail_chance = 1; } else { fail_chance = randomNumber(fail_chance / 10); } if (fail_chance == 1) { printMessage("There is a bright flash of light."); inventoryDestroyItem(item_id); } else { number_of_charges = (number_of_charges / (item.depth_first_found + 2)) + 1; item.misc_use += 2 + randomNumber(number_of_charges); if (spellItemIdentified(item)) { spellItemRemoveIdentification(item); } itemIdentificationClearEmpty(item); } return true; } // Increase or decrease a creatures hit points -RAK- bool spellChangeMonsterHitPoints(int y, int x, int direction, int damage_hp) { int distance = 0; bool changed = false; bool finished = false; while (!finished) { (void) playerMovePosition(direction, y, x); distance++; Tile_t const &tile = dg.floor[y][x]; if (distance > config::treasure::OBJECT_BOLTS_MAX_RANGE || tile.feature_id >= MIN_CLOSED_SPACE) { finished = true; continue; } if (tile.creature_id > 1) { finished = true; Monster_t const &monster = monsters[tile.creature_id]; Creature_t const &creature = creatures_list[monster.creature_id]; auto name = monsterNameDescription(creature.name, monster.lit); if (monsterTakeHit((int) tile.creature_id, damage_hp) >= 0) { printMonsterActionText(name, "dies in a fit of agony."); displayCharacterExperience(); } else if (damage_hp > 0) { printMonsterActionText(name, "screams in agony."); } changed = true; } } return changed; } // Drains life; note it must be living. -RAK- bool spellDrainLifeFromMonster(int y, int x, int direction) { int distance = 0; bool drained = false; bool finished = false; while (!finished) { (void) playerMovePosition(direction, y, x); distance++; Tile_t const &tile = dg.floor[y][x]; if (distance > config::treasure::OBJECT_BOLTS_MAX_RANGE || tile.feature_id >= MIN_CLOSED_SPACE) { finished = true; continue; } if (tile.creature_id > 1) { finished = true; Monster_t const &monster = monsters[tile.creature_id]; Creature_t const &creature = creatures_list[monster.creature_id]; if ((creature.defenses & config::monsters::defense::CD_UNDEAD) == 0) { auto name = monsterNameDescription(creature.name, monster.lit); if (monsterTakeHit((int) tile.creature_id, 75) >= 0) { printMonsterActionText(name, "dies in a fit of agony."); displayCharacterExperience(); } else { printMonsterActionText(name, "screams in agony."); } drained = true; } else { creature_recall[monster.creature_id].defenses |= config::monsters::defense::CD_UNDEAD; } } } return drained; } // Increase or decrease a creatures speed -RAK- // NOTE: cannot slow a winning creature (BALROG) bool spellSpeedMonster(int y, int x, int direction, int speed) { int distance = 0; bool changed = false; bool finished = false; while (!finished) { (void) playerMovePosition(direction, y, x); distance++; Tile_t const &tile = dg.floor[y][x]; if (distance > config::treasure::OBJECT_BOLTS_MAX_RANGE || tile.feature_id >= MIN_CLOSED_SPACE) { finished = true; continue; } if (tile.creature_id > 1) { finished = true; Monster_t &monster = monsters[tile.creature_id]; Creature_t const &creature = creatures_list[monster.creature_id]; auto name = monsterNameDescription(creature.name, monster.lit); if (speed > 0) { monster.speed += speed; monster.sleep_count = 0; changed = true; printMonsterActionText(name, "starts moving faster."); } else if (randomNumber(MON_MAX_LEVELS) > creature.level) { monster.speed += speed; monster.sleep_count = 0; changed = true; printMonsterActionText(name, "starts moving slower."); } else { monster.sleep_count = 0; printMonsterActionText(name, "is unaffected."); } } } return changed; } // Confuse a creature -RAK- bool spellConfuseMonster(int y, int x, int direction) { int distance = 0; bool confused = false; bool finished = false; while (!finished) { (void) playerMovePosition(direction, y, x); distance++; Tile_t const &tile = dg.floor[y][x]; if (distance > config::treasure::OBJECT_BOLTS_MAX_RANGE || tile.feature_id >= MIN_CLOSED_SPACE) { finished = true; continue; } if (tile.creature_id > 1) { finished = true; Monster_t &monster = monsters[tile.creature_id]; Creature_t const &creature = creatures_list[monster.creature_id]; auto name = monsterNameDescription(creature.name, monster.lit); if (randomNumber(MON_MAX_LEVELS) < creature.level || ((creature.defenses & config::monsters::defense::CD_NO_SLEEP) != 0)) { if (monster.lit && ((creature.defenses & config::monsters::defense::CD_NO_SLEEP) != 0)) { creature_recall[monster.creature_id].defenses |= config::monsters::defense::CD_NO_SLEEP; } // Monsters which resisted the attack should wake up. // Monsters with innate resistance ignore the attack. if ((creature.defenses & config::monsters::defense::CD_NO_SLEEP) == 0) { monster.sleep_count = 0; } printMonsterActionText(name, "is unaffected."); } else { if (monster.confused_amount != 0u) { monster.confused_amount += 3; } else { monster.confused_amount = (uint8_t) (2 + randomNumber(16)); } monster.sleep_count = 0; confused = true; printMonsterActionText(name, "appears confused."); } } } return confused; } // Sleep a creature. -RAK- bool spellSleepMonster(int y, int x, int direction) { int distance = 0; bool asleep = false; bool finished = false; while (!finished) { (void) playerMovePosition(direction, y, x); distance++; Tile_t const &tile = dg.floor[y][x]; if (distance > config::treasure::OBJECT_BOLTS_MAX_RANGE || tile.feature_id >= MIN_CLOSED_SPACE) { finished = true; continue; } if (tile.creature_id > 1) { finished = true; Monster_t &monster = monsters[tile.creature_id]; Creature_t const &creature = creatures_list[monster.creature_id]; auto name = monsterNameDescription(creature.name, monster.lit); if (randomNumber(MON_MAX_LEVELS) < creature.level || ((creature.defenses & config::monsters::defense::CD_NO_SLEEP) != 0)) { if (monster.lit && ((creature.defenses & config::monsters::defense::CD_NO_SLEEP) != 0)) { creature_recall[monster.creature_id].defenses |= config::monsters::defense::CD_NO_SLEEP; } printMonsterActionText(name, "is unaffected."); } else { monster.sleep_count = 500; asleep = true; printMonsterActionText(name, "falls asleep."); } } } return asleep; } // Turn stone to mud, delete wall. -RAK- bool spellWallToMud(int y, int x, int direction) { int distance = 0; bool turned = false; bool finished = false; while (!finished) { (void) playerMovePosition(direction, y, x); distance++; Tile_t const &tile = dg.floor[y][x]; // note, this ray can move through walls as it turns them to mud if (distance == config::treasure::OBJECT_BOLTS_MAX_RANGE) { finished = true; } if (tile.feature_id >= MIN_CAVE_WALL && tile.feature_id != TILE_BOUNDARY_WALL) { finished = true; (void) playerTunnelWall(y, x, 1, 0); if (caveTileVisible(Coord_t{y, x})) { turned = true; printMessage("The wall turns into mud."); } } else if (tile.treasure_id != 0 && tile.feature_id >= MIN_CLOSED_SPACE) { finished = true; if (coordInsidePanel(Coord_t{y, x}) && caveTileVisible(Coord_t{y, x})) { turned = true; obj_desc_t description = {'\0'}; itemDescription(description, treasure_list[tile.treasure_id], false); obj_desc_t out_val = {'\0'}; (void) sprintf(out_val, "The %s turns into mud.", description); printMessage(out_val); } if (treasure_list[tile.treasure_id].category_id == TV_RUBBLE) { (void) dungeonDeleteObject(Coord_t{y, x});; if (randomNumber(10) == 1) { dungeonPlaceRandomObjectAt(Coord_t{y, x}, false); if (caveTileVisible(Coord_t{y, x})) { printMessage("You have found something!"); } } dungeonLiteSpot(Coord_t{y, x}); } else { (void) dungeonDeleteObject(Coord_t{y, x});; } } if (tile.creature_id > 1) { Monster_t const &monster = monsters[tile.creature_id]; Creature_t const &creature = creatures_list[monster.creature_id]; if ((creature.defenses & config::monsters::defense::CD_STONE) != 0) { auto name = monsterNameDescription(creature.name, monster.lit); // Should get these messages even if the monster is not visible. int creature_id = monsterTakeHit((int) tile.creature_id, 100); if (creature_id >= 0) { creature_recall[creature_id].defenses |= config::monsters::defense::CD_STONE; printMonsterActionText(name, "dissolves!"); displayCharacterExperience(); // print msg before calling prt_exp } else { creature_recall[monster.creature_id].defenses |= config::monsters::defense::CD_STONE; printMonsterActionText(name, "grunts in pain!"); } finished = true; } } } return turned; } // Destroy all traps and doors in a given direction -RAK- bool spellDestroyDoorsTrapsInDirection(int y, int x, int direction) { bool destroyed = false; int distance = 0; Tile_t *tile = nullptr; do { (void) playerMovePosition(direction, y, x); distance++; tile = &dg.floor[y][x]; // must move into first closed spot, as it might be a secret door if (tile->treasure_id != 0) { Inventory_t &item = treasure_list[tile->treasure_id]; if (item.category_id == TV_INVIS_TRAP || item.category_id == TV_CLOSED_DOOR || item.category_id == TV_VIS_TRAP || item.category_id == TV_OPEN_DOOR || item.category_id == TV_SECRET_DOOR) { if (dungeonDeleteObject(Coord_t{y, x})) { destroyed = true; printMessage("There is a bright flash of light!"); } } else if (item.category_id == TV_CHEST && item.flags != 0) { destroyed = true; printMessage("Click!"); item.flags &= ~(config::treasure::chests::CH_TRAPPED | config::treasure::chests::CH_LOCKED); item.special_name_id = special_name_ids::SN_UNLOCKED; spellItemIdentifyAndRemoveRandomInscription(item); } } } while ((distance <= config::treasure::OBJECT_BOLTS_MAX_RANGE) || tile->feature_id <= MAX_OPEN_SPACE); return destroyed; } // Polymorph a monster -RAK- // NOTE: cannot polymorph a winning creature (BALROG) bool spellPolymorphMonster(int y, int x, int direction) { int distance = 0; bool morphed = false; bool finished = false; while (!finished) { (void) playerMovePosition(direction, y, x); distance++; Tile_t const &tile = dg.floor[y][x]; if (distance > config::treasure::OBJECT_BOLTS_MAX_RANGE || tile.feature_id >= MIN_CLOSED_SPACE) { finished = true; continue; } if (tile.creature_id > 1) { Monster_t const &monster = monsters[tile.creature_id]; Creature_t const &creature = creatures_list[monster.creature_id]; if (randomNumber(MON_MAX_LEVELS) > creature.level) { finished = true; dungeonDeleteMonster((int) tile.creature_id); // Place_monster() should always return true here. morphed = monsterPlaceNew(y, x, randomNumber(monster_levels[MON_MAX_LEVELS] - monster_levels[0]) - 1 + monster_levels[0], false); // don't test tile.field_mark here, only permanent_light/temporary_light if (morphed && coordInsidePanel(Coord_t{y, x}) && (tile.temporary_light || tile.permanent_light)) { morphed = true; } } else { auto name = monsterNameDescription(creature.name, monster.lit); printMonsterActionText(name, "is unaffected."); } } } return morphed; } // Create a wall. -RAK- bool spellBuildWall(int y, int x, int direction) { int distance = 0; bool built = false; bool finished = false; while (!finished) { (void) playerMovePosition(direction, y, x); distance++; Tile_t &tile = dg.floor[y][x]; if (distance > config::treasure::OBJECT_BOLTS_MAX_RANGE || tile.feature_id >= MIN_CLOSED_SPACE) { finished = true; continue; // we're done here, break out of the loop } if (tile.treasure_id != 0) { (void) dungeonDeleteObject(Coord_t{y, x});; } if (tile.creature_id > 1) { finished = true; Monster_t &monster = monsters[tile.creature_id]; Creature_t const &creature = creatures_list[monster.creature_id]; if ((creature.movement & config::monsters::move::CM_PHASE) == 0u) { // monster does not move, can't escape the wall int damage; if ((creature.movement & config::monsters::move::CM_ATTACK_ONLY) != 0u) { // this will kill everything damage = 3000; } else { damage = diceRoll(Dice_t{4, 8}); } auto name = monsterNameDescription(creature.name, monster.lit); printMonsterActionText(name, "wails out in pain!"); if (monsterTakeHit((int) tile.creature_id, damage) >= 0) { printMonsterActionText(name, "is embedded in the rock."); displayCharacterExperience(); } } else if (creature.sprite == 'E' || creature.sprite == 'X') { // must be an earth elemental, an earth spirit, // or a Xorn to increase its hit points monster.hp += diceRoll(Dice_t{4, 8}); } } tile.feature_id = TILE_MAGMA_WALL; tile.field_mark = false; // Permanently light this wall if it is lit by player's lamp. tile.permanent_light = (tile.temporary_light || tile.permanent_light); dungeonLiteSpot(Coord_t{y, x}); built = true; } return built; } // Replicate a creature -RAK- bool spellCloneMonster(int y, int x, int direction) { int distance = 0; bool finished = false; while (!finished) { (void) playerMovePosition(direction, y, x); distance++; Tile_t const &tile = dg.floor[y][x]; if (distance > config::treasure::OBJECT_BOLTS_MAX_RANGE || tile.feature_id >= MIN_CLOSED_SPACE) { finished = true; } else if (tile.creature_id > 1) { monsters[tile.creature_id].sleep_count = 0; // monptr of 0 is safe here, since can't reach here from creatures return monsterMultiply(y, x, (int) monsters[tile.creature_id].creature_id, 0); } } return false; } // Move the creature record to a new location -RAK- void spellTeleportAwayMonster(int monster_id, int distance_from_player) { int y, x; int counter = 0; Monster_t &monster = monsters[monster_id]; do { do { y = monster.y + (randomNumber(2 * distance_from_player + 1) - (distance_from_player + 1)); x = monster.x + (randomNumber(2 * distance_from_player + 1) - (distance_from_player + 1)); } while (!coordInBounds(Coord_t{y, x})); counter++; if (counter > 9) { counter = 0; distance_from_player += 5; } } while ((dg.floor[y][x].feature_id >= MIN_CLOSED_SPACE) || (dg.floor[y][x].creature_id != 0)); dungeonMoveCreatureRecord(Coord_t{monster.y, monster.x}, Coord_t{y, x}); dungeonLiteSpot(Coord_t{monster.y, monster.x}); monster.y = (uint8_t) y; monster.x = (uint8_t) x; // this is necessary, because the creature is // not currently visible in its new position. monster.lit = false; monster.distance_from_player = (uint8_t) coordDistanceBetween(Coord_t{py.row, py.col}, Coord_t{y, x}); monsterUpdateVisibility(monster_id); } // Teleport player to spell casting creature -RAK- void spellTeleportPlayerTo(int y, int x) { int to_y, to_x; int distance = 1; int counter = 0; do { to_y = y + (randomNumber(2 * distance + 1) - (distance + 1)); to_x = x + (randomNumber(2 * distance + 1) - (distance + 1)); counter++; if (counter > 9) { counter = 0; distance++; } } while (!coordInBounds(Coord_t{to_y, to_x}) || (dg.floor[to_y][to_x].feature_id >= MIN_CLOSED_SPACE) || (dg.floor[to_y][to_x].creature_id >= 2)); dungeonMoveCreatureRecord(Coord_t{py.row, py.col}, Coord_t{to_y, to_x}); for (int row = py.row - 1; row <= py.row + 1; row++) { for (int col = py.col - 1; col <= py.col + 1; col++) { dg.floor[row][col].temporary_light = false; dungeonLiteSpot(Coord_t{row, col}); } } dungeonLiteSpot(Coord_t{py.row, py.col}); py.row = (int16_t) to_y; py.col = (int16_t) to_x; dungeonResetView(); // light creatures updateMonsters(false); } // Teleport all creatures in a given direction away -RAK- bool spellTeleportAwayMonsterInDirection(int y, int x, int direction) { int distance = 0; bool teleported = false; bool finished = false; while (!finished) { (void) playerMovePosition(direction, y, x); distance++; Tile_t const &tile = dg.floor[y][x]; if (distance > config::treasure::OBJECT_BOLTS_MAX_RANGE || tile.feature_id >= MIN_CLOSED_SPACE) { finished = true; continue; } if (tile.creature_id > 1) { // wake it up monsters[tile.creature_id].sleep_count = 0; spellTeleportAwayMonster((int) tile.creature_id, config::monsters::MON_MAX_SIGHT); teleported = true; } } return teleported; } // Delete all creatures within max_sight distance -RAK- // NOTE : Winning creatures cannot be killed by genocide. bool spellMassGenocide() { bool killed = false; for (int id = next_free_monster_id - 1; id >= config::monsters::MON_MIN_INDEX_ID; id--) { Monster_t const &monster = monsters[id]; Creature_t const &creature = creatures_list[monster.creature_id]; if (monster.distance_from_player <= config::monsters::MON_MAX_SIGHT && (creature.movement & config::monsters::move::CM_WIN) == 0) { killed = true; dungeonDeleteMonster(id); } } return killed; } // Delete all creatures of a given type from level. -RAK- // This does not keep creatures of type from appearing later. // NOTE : Winning creatures can not be killed by genocide. bool spellGenocide() { char creature_char; if (!getCommand("Which type of creature do you wish exterminated?", creature_char)) { return false; } bool killed = false; for (int id = next_free_monster_id - 1; id >= config::monsters::MON_MIN_INDEX_ID; id--) { Monster_t const &monster = monsters[id]; Creature_t const &creature = creatures_list[monster.creature_id]; if (creature_char == creatures_list[monster.creature_id].sprite) { if ((creature.movement & config::monsters::move::CM_WIN) == 0) { killed = true; dungeonDeleteMonster(id); } else { // genocide is a powerful spell, so we will let the player // know the names of the creatures they did not destroy, // this message makes no sense otherwise printMessage(("The " + std::string(creature.name) + " is unaffected.").c_str()); } } } return killed; } // Change speed of any creature . -RAK- // NOTE: cannot slow a winning creature (BALROG) bool spellSpeedAllMonsters(int speed) { bool speedy = false; for (int id = next_free_monster_id - 1; id >= config::monsters::MON_MIN_INDEX_ID; id--) { Monster_t &monster = monsters[id]; Creature_t const &creature = creatures_list[monster.creature_id]; auto name = monsterNameDescription(creature.name, monster.lit); if (monster.distance_from_player > config::monsters::MON_MAX_SIGHT || !los(py.row, py.col, monster.y, monster.x)) { continue; // do nothing } if (speed > 0) { monster.speed += speed; monster.sleep_count = 0; if (monster.lit) { speedy = true; printMonsterActionText(name, "starts moving faster."); } } else if (randomNumber(MON_MAX_LEVELS) > creature.level) { monster.speed += speed; monster.sleep_count = 0; if (monster.lit) { speedy = true; printMonsterActionText(name, "starts moving slower."); } } else if (monster.lit) { monster.sleep_count = 0; printMonsterActionText(name, "is unaffected."); } } return speedy; } // Sleep any creature . -RAK- bool spellSleepAllMonsters() { bool asleep = false; for (int id = next_free_monster_id - 1; id >= config::monsters::MON_MIN_INDEX_ID; id--) { Monster_t &monster = monsters[id]; Creature_t const &creature = creatures_list[monster.creature_id]; auto name = monsterNameDescription(creature.name, monster.lit); if (monster.distance_from_player > config::monsters::MON_MAX_SIGHT || !los(py.row, py.col, monster.y, monster.x)) { continue; // do nothing } if (randomNumber(MON_MAX_LEVELS) < creature.level || ((creature.defenses & config::monsters::defense::CD_NO_SLEEP) != 0)) { if (monster.lit) { if ((creature.defenses & config::monsters::defense::CD_NO_SLEEP) != 0) { creature_recall[monster.creature_id].defenses |= config::monsters::defense::CD_NO_SLEEP; } printMonsterActionText(name, "is unaffected."); } } else { monster.sleep_count = 500; if (monster.lit) { asleep = true; printMonsterActionText(name, "falls asleep."); } } } return asleep; } // Polymorph any creature that player can see. -RAK- // NOTE: cannot polymorph a winning creature (BALROG) bool spellMassPolymorph() { bool morphed = false; for (int id = next_free_monster_id - 1; id >= config::monsters::MON_MIN_INDEX_ID; id--) { Monster_t const &monster = monsters[id]; if (monster.distance_from_player <= config::monsters::MON_MAX_SIGHT) { Creature_t const &creature = creatures_list[monster.creature_id]; if ((creature.movement & config::monsters::move::CM_WIN) == 0) { int y = monster.y; int x = monster.x; dungeonDeleteMonster(id); // Place_monster() should always return true here. morphed = monsterPlaceNew(y, x, randomNumber(monster_levels[MON_MAX_LEVELS] - monster_levels[0]) - 1 + monster_levels[0], false); } } } return morphed; } // Display evil creatures on current panel -RAK- bool spellDetectEvil() { bool detected = false; for (int id = next_free_monster_id - 1; id >= config::monsters::MON_MIN_INDEX_ID; id--) { Monster_t &monster = monsters[id]; if (coordInsidePanel(Coord_t{monster.y, monster.x}) && ((creatures_list[monster.creature_id].defenses & config::monsters::defense::CD_EVIL) != 0)) { monster.lit = true; detected = true; // works correctly even if hallucinating panelPutTile((char) creatures_list[monster.creature_id].sprite, Coord_t{monster.y, monster.x}); } } if (detected) { printMessage("You sense the presence of evil!"); printMessage(CNIL); // must unlight every monster just lighted updateMonsters(false); } return detected; } // Change players hit points in some manner -RAK- bool spellChangePlayerHitPoints(int adjustment) { if (py.misc.current_hp >= py.misc.max_hp) { return false; } py.misc.current_hp += adjustment; if (py.misc.current_hp > py.misc.max_hp) { py.misc.current_hp = py.misc.max_hp; py.misc.current_hp_fraction = 0; } printCharacterCurrentHitPoints(); adjustment = adjustment / 5; if (adjustment < 3) { if (adjustment == 0) { printMessage("You feel a little better."); } else { printMessage("You feel better."); } } else { if (adjustment < 7) { printMessage("You feel much better."); } else { printMessage("You feel very good."); } } return true; } static void earthquakeHitsMonster(int monsterID) { Monster_t &monster = monsters[monsterID]; Creature_t const &creature = creatures_list[monster.creature_id]; if ((creature.movement & config::monsters::move::CM_PHASE) == 0u) { int damage; if ((creature.movement & config::monsters::move::CM_ATTACK_ONLY) != 0u) { // this will kill everything damage = 3000; } else { damage = diceRoll(Dice_t{4, 8}); } auto name = monsterNameDescription(creature.name, monster.lit); printMonsterActionText(name, "wails out in pain!"); if (monsterTakeHit(monsterID, damage) >= 0) { printMonsterActionText(name, "is embedded in the rock."); displayCharacterExperience(); } } else if (creature.sprite == 'E' || creature.sprite == 'X') { // must be an earth elemental or an earth spirit, or a // Xorn increase its hit points monster.hp += diceRoll(Dice_t{4, 8}); } } // This is a fun one. In a given block, pick some walls and // turn them into open spots. Pick some open spots and dg.game_turn // them into walls. An "Earthquake" effect. -RAK- void spellEarthquake() { for (int y = py.row - 8; y <= py.row + 8; y++) { for (int x = py.col - 8; x <= py.col + 8; x++) { if ((y != py.row || x != py.col) && coordInBounds(Coord_t{y, x}) && randomNumber(8) == 1) { Tile_t &tile = dg.floor[y][x]; if (tile.treasure_id != 0) { (void) dungeonDeleteObject(Coord_t{y, x});; } if (tile.creature_id > 1) { earthquakeHitsMonster(tile.creature_id); } if (tile.feature_id >= MIN_CAVE_WALL && tile.feature_id != TILE_BOUNDARY_WALL) { tile.feature_id = TILE_CORR_FLOOR; tile.permanent_light = false; tile.field_mark = false; } else if (tile.feature_id <= MAX_CAVE_FLOOR) { int tmp = randomNumber(10); if (tmp < 6) { tile.feature_id = TILE_QUARTZ_WALL; } else if (tmp < 9) { tile.feature_id = TILE_MAGMA_WALL; } else { tile.feature_id = TILE_GRANITE_WALL; } tile.field_mark = false; } dungeonLiteSpot(Coord_t{y, x}); } } } } // Create some high quality mush for the player. -RAK- void spellCreateFood() { // Note: must take reference to this location as dungeonPlaceRandomObjectAt() // below, changes the tile values. Tile_t const &tile = dg.floor[py.row][py.col]; // take no action here, don't want to destroy object under player if (tile.treasure_id != 0) { // set player_free_turn so that scroll/spell points won't be used game.player_free_turn = true; printMessage("There is already an object under you."); return; } dungeonPlaceRandomObjectAt(Coord_t{py.row, py.col}, false); inventoryItemCopyTo(config::dungeon::objects::OBJ_MUSH, treasure_list[tile.treasure_id]); } // Attempts to destroy a type of creature. Success depends on // the creatures level VS. the player's level -RAK- bool spellDispelCreature(int creature_defense, int damage) { bool dispelled = false; for (int id = next_free_monster_id - 1; id >= config::monsters::MON_MIN_INDEX_ID; id--) { Monster_t const &monster = monsters[id]; if (monster.distance_from_player <= config::monsters::MON_MAX_SIGHT && ((creature_defense & creatures_list[monster.creature_id].defenses) != 0) && los(py.row, py.col, monster.y, monster.x)) { Creature_t const &creature = creatures_list[monster.creature_id]; creature_recall[monster.creature_id].defenses |= creature_defense; dispelled = true; auto name = monsterNameDescription(creature.name, monster.lit); int hit = monsterTakeHit(id, randomNumber(damage)); // Should get these messages even if the monster is not visible. if (hit >= 0) { printMonsterActionText(name, "dissolves!"); } else { printMonsterActionText(name, "shudders."); } if (hit >= 0) { displayCharacterExperience(); } } } return dispelled; } // Attempt to turn (confuse) undead creatures. -RAK- bool spellTurnUndead() { bool turned = false; for (int id = next_free_monster_id - 1; id >= config::monsters::MON_MIN_INDEX_ID; id--) { Monster_t &monster = monsters[id]; Creature_t const &creature = creatures_list[monster.creature_id]; if (monster.distance_from_player <= config::monsters::MON_MAX_SIGHT && ((creature.defenses & config::monsters::defense::CD_UNDEAD) != 0) && los(py.row, py.col, monster.y, monster.x)) { auto name = monsterNameDescription(creature.name, monster.lit); if (py.misc.level + 1 > creature.level || randomNumber(5) == 1) { if (monster.lit) { creature_recall[monster.creature_id].defenses |= config::monsters::defense::CD_UNDEAD; turned = true; printMonsterActionText(name, "runs frantically!"); } monster.confused_amount = (uint8_t) py.misc.level; } else if (monster.lit) { printMonsterActionText(name, "is unaffected."); } } } return turned; } // Leave a glyph of warding. Creatures will not pass over! -RAK- void spellWardingGlyph() { if (dg.floor[py.row][py.col].treasure_id == 0) { int free_id = popt(); dg.floor[py.row][py.col].treasure_id = (uint8_t) free_id; inventoryItemCopyTo(config::dungeon::objects::OBJ_SCARE_MON, treasure_list[free_id]); } } // Lose a strength point. -RAK- void spellLoseSTR() { if (!py.flags.sustain_str) { (void) playerStatRandomDecrease(py_attrs::A_STR); printMessage("You feel very sick."); } else { printMessage("You feel sick for a moment, it passes."); } } // Lose an intelligence point. -RAK- void spellLoseINT() { if (!py.flags.sustain_int) { (void) playerStatRandomDecrease(py_attrs::A_INT); printMessage("You become very dizzy."); } else { printMessage("You become dizzy for a moment, it passes."); } } // Lose a wisdom point. -RAK- void spellLoseWIS() { if (!py.flags.sustain_wis) { (void) playerStatRandomDecrease(py_attrs::A_WIS); printMessage("You feel very naive."); } else { printMessage("You feel naive for a moment, it passes."); } } // Lose a dexterity point. -RAK- void spellLoseDEX() { if (!py.flags.sustain_dex) { (void) playerStatRandomDecrease(py_attrs::A_DEX); printMessage("You feel very sore."); } else { printMessage("You feel sore for a moment, it passes."); } } // Lose a constitution point. -RAK- void spellLoseCON() { if (!py.flags.sustain_con) { (void) playerStatRandomDecrease(py_attrs::A_CON); printMessage("You feel very sick."); } else { printMessage("You feel sick for a moment, it passes."); } } // Lose a charisma point. -RAK- void spellLoseCHR() { if (!py.flags.sustain_chr) { (void) playerStatRandomDecrease(py_attrs::A_CHR); printMessage("Your skin starts to itch."); } else { printMessage("Your skin starts to itch, but feels better now."); } } // Lose experience -RAK- void spellLoseEXP(int32_t adjustment) { if (adjustment > py.misc.exp) { py.misc.exp = 0; } else { py.misc.exp -= adjustment; } displayCharacterExperience(); int exp = 0; while ((signed) (py.base_exp_levels[exp] * py.misc.experience_factor / 100) <= py.misc.exp) { exp++; } // increment exp once more, because level 1 exp is stored in player_base_exp_levels[0] exp++; if (py.misc.level != exp) { py.misc.level = (uint16_t) exp; playerCalculateHitPoints(); Class_t const &character_class = classes[py.misc.class_id]; if (character_class.class_to_use_mage_spells == config::spells::SPELL_TYPE_MAGE) { playerCalculateAllowedSpellsCount(py_attrs::A_INT); playerGainMana(py_attrs::A_INT); } else if (character_class.class_to_use_mage_spells == config::spells::SPELL_TYPE_PRIEST) { playerCalculateAllowedSpellsCount(py_attrs::A_WIS); playerGainMana(py_attrs::A_WIS); } printCharacterLevel(); printCharacterTitle(); } } // Slow Poison -RAK- bool spellSlowPoison() { if (py.flags.poisoned > 0) { py.flags.poisoned = (int16_t) (py.flags.poisoned / 2); if (py.flags.poisoned < 1) { py.flags.poisoned = 1; } printMessage("The effect of the poison has been reduced."); return true; } return false; } static void replaceSpot(int y, int x, int typ) { Tile_t &tile = dg.floor[y][x]; switch (typ) { case 1: case 2: case 3: tile.feature_id = TILE_CORR_FLOOR; break; case 4: case 7: case 10: tile.feature_id = TILE_GRANITE_WALL; break; case 5: case 8: case 11: tile.feature_id = TILE_MAGMA_WALL; break; case 6: case 9: case 12: tile.feature_id = TILE_QUARTZ_WALL; break; default: break; } tile.permanent_light = false; tile.field_mark = false; tile.perma_lit_room = false; // this is no longer part of a room if (tile.treasure_id != 0) { (void) dungeonDeleteObject(Coord_t{y, x});; } if (tile.creature_id > 1) { dungeonDeleteMonster((int) tile.creature_id); } } // The spell of destruction. -RAK- // NOTE: // Winning creatures that are deleted will be considered as teleporting to another level. // This will NOT win the game. void spellDestroyArea(int y, int x) { if (dg.current_level > 0) { for (int pos_y = y - 15; pos_y <= y + 15; pos_y++) { for (int pos_x = x - 15; pos_x <= x + 15; pos_x++) { if (coordInBounds(Coord_t{pos_y, pos_x}) && dg.floor[pos_y][pos_x].feature_id != TILE_BOUNDARY_WALL) { int distance = coordDistanceBetween(Coord_t{pos_y, pos_x}, Coord_t{y, x}); // clear player's spot, but don't put wall there if (distance == 0) { replaceSpot(pos_y, pos_x, 1); } else if (distance < 13) { replaceSpot(pos_y, pos_x, randomNumber(6)); } else if (distance < 16) { replaceSpot(pos_y, pos_x, randomNumber(9)); } } } } } printMessage("There is a searing blast of light!"); py.flags.blind += 10 + randomNumber(10); } // Enchants a plus onto an item. -RAK- // `limit` param is the maximum bonus allowed; usually 10, // but weapon's maximum damage when enchanting melee weapons to damage. bool spellEnchantItem(int16_t &plusses, int16_t max_bonus_limit) { // avoid randomNumber(0) call if (max_bonus_limit <= 0) { return false; } int chance = 0; if (plusses > 0) { chance = plusses; // very rarely allow enchantment over limit if (randomNumber(100) == 1) { chance = randomNumber(chance) - 1; } } if (randomNumber(max_bonus_limit) > chance) { plusses += 1; return true; } return false; } // Removes curses from items in inventory -RAK- bool spellRemoveCurseFromAllItems() { bool removed = false; for (int id = player_equipment::EQUIPMENT_WIELD; id <= player_equipment::EQUIPMENT_OUTER; id++) { if ((inventory[id].flags & config::treasure::flags::TR_CURSED) != 0u) { inventory[id].flags &= ~config::treasure::flags::TR_CURSED; playerRecalculateBonuses(); removed = true; } } return removed; } // Restores any drained experience -RAK- bool spellRestorePlayerLevels() { if (py.misc.max_exp > py.misc.exp) { printMessage("You feel your life energies returning."); // this while loop is not redundant, ptr_exp may reduce the exp level while (py.misc.exp < py.misc.max_exp) { py.misc.exp = py.misc.max_exp; displayCharacterExperience(); } return true; } return false; } umoria-5.7.10+20181022/src/store.cpp0000644000175000017500000010747413363422757015425 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. // Store: entering, command interpreter, buying, selling #include "headers.h" // Save the store's last increment value. static int16_t store_last_increment; static bool storeNoNeedToBargain(Store_t const &store, int32_t min_price); static void storeUpdateBargainInfo(Store_t &store, int32_t price, int32_t min_price); // Initializes the stores with owners -RAK- void storeInitializeOwners() { int count = MAX_OWNERS / MAX_STORES; for (int store_id = 0; store_id < MAX_STORES; store_id++) { Store_t &store = stores[store_id]; store.owner_id = (uint8_t) (MAX_STORES * (randomNumber(count) - 1) + store_id); store.insults_counter = 0; store.turns_left_before_closing = 0; store.unique_items_counter = 0; store.good_purchases = 0; store.bad_purchases = 0; for (auto &item : store.inventory) { inventoryItemCopyTo(config::dungeon::objects::OBJ_NOTHING, item.item); item.cost = 0; } } } // Comments vary. -RAK- // Comment one : Finished haggling static void printSpeechFinishedHaggling() { printMessage(speech_sale_accepted[randomNumber(14) - 1]); } // %A1 is offer, %A2 is asking. static void printSpeechSellingHaggle(int32_t offer, int32_t asking, int final) { vtype_t comment = {'\0'}; if (final > 0) { (void) strcpy(comment, speech_selling_haggle_final[randomNumber(3) - 1]); } else { (void) strcpy(comment, speech_selling_haggle[randomNumber(16) - 1]); } insertNumberIntoString(comment, "%A1", offer, false); insertNumberIntoString(comment, "%A2", asking, false); printMessage(comment); } static void printSpeechBuyingHaggle(int32_t offer, int32_t asking, int final) { vtype_t comment = {'\0'}; if (final > 0) { (void) strcpy(comment, speech_buying_haggle_final[randomNumber(3) - 1]); } else { (void) strcpy(comment, speech_buying_haggle[randomNumber(15) - 1]); } insertNumberIntoString(comment, "%A1", offer, false); insertNumberIntoString(comment, "%A2", asking, false); printMessage(comment); } // Kick 'da bum out. -RAK- static void printSpeechGetOutOfMyStore() { int comment = randomNumber(5) - 1; printMessage(speech_insulted_haggling_done[comment]); printMessage(speech_get_out_of_my_store[comment]); } static void printSpeechTryAgain() { printMessage(speech_haggling_try_again[randomNumber(10) - 1]); } static void printSpeechSorry() { printMessage(speech_sorry[randomNumber(5) - 1]); } // Displays the set of commands -RAK- static void displayStoreCommands() { putStringClearToEOL("You may:", Coord_t{20, 0}); putStringClearToEOL(" p) Purchase an item. b) Browse store's inventory.", Coord_t{21, 0}); putStringClearToEOL(" s) Sell an item. i/e/t/w/x) Inventory/Equipment Lists.", Coord_t{22, 0}); putStringClearToEOL("ESC) Exit from Building. ^R) Redraw the screen.", Coord_t{23, 0}); } // Displays the set of commands -RAK- static void displayStoreHaggleCommands(int haggle_type) { if (haggle_type == -1) { putStringClearToEOL("Specify an asking-price in gold pieces.", Coord_t{21, 0}); } else { putStringClearToEOL("Specify an offer in gold pieces.", Coord_t{21, 0}); } putStringClearToEOL("ESC) Quit Haggling.", Coord_t{22, 0}); eraseLine(Coord_t{23, 0}); // clear last line } // Displays a store's inventory -RAK- static void displayStoreInventory(Store_t &store, int item_pos_start) { int item_pos_end = ((item_pos_start / 12) + 1) * 12; if (item_pos_end > store.unique_items_counter) { item_pos_end = store.unique_items_counter; } int item_identifier; for (item_identifier = (item_pos_start % 12); item_pos_start < item_pos_end; item_identifier++) { Inventory_t &item = store.inventory[item_pos_start].item; // Save the current number of items int32_t current_item_count = item.items_count; if (item.sub_category_id >= ITEM_SINGLE_STACK_MIN && item.sub_category_id <= ITEM_SINGLE_STACK_MAX) { item.items_count = 1; } obj_desc_t description = {'\0'}; itemDescription(description, item, true); // Restore the number of items item.items_count = (uint8_t) current_item_count; obj_desc_t msg = {'\0'}; (void) sprintf(msg, "%c) %s", 'a' + item_identifier, description); putStringClearToEOL(msg, Coord_t{item_identifier + 5, 0}); current_item_count = store.inventory[item_pos_start].cost; if (current_item_count <= 0) { int32_t value = -current_item_count; value = value * playerStatAdjustmentCharisma() / 100; if (value <= 0) { value = 1; } (void) sprintf(msg, "%9d", value); } else { (void) sprintf(msg, "%9d [Fixed]", current_item_count); } putStringClearToEOL(msg, Coord_t{item_identifier + 5, 59}); item_pos_start++; } if (item_identifier < 12) { for (int i = 0; i < (11 - item_identifier + 1); i++) { // clear remaining lines eraseLine(Coord_t{i + item_identifier + 5, 0}); } } if (store.unique_items_counter > 12) { putString("- cont. -", Coord_t{17, 60}); } else { eraseLine(Coord_t{17, 60}); } } // Re-displays only a single cost -RAK- static void displaySingleCost(int store_id, int item_id) { int cost = stores[store_id].inventory[item_id].cost; vtype_t msg = {'\0'}; if (cost < 0) { int32_t c = -cost; c = c * playerStatAdjustmentCharisma() / 100; (void) sprintf(msg, "%d", c); } else { (void) sprintf(msg, "%9d [Fixed]", cost); } putStringClearToEOL(msg, Coord_t{(item_id % 12) + 5, 59}); } // Displays players gold -RAK- static void displayPlayerRemainingGold() { vtype_t msg = {'\0'}; (void) sprintf(msg, "Gold Remaining : %d", py.misc.au); putStringClearToEOL(msg, Coord_t{18, 17}); } // Displays store -RAK- static void displayStore(Store_t &store, const char *owner_name, int current_top_item_id) { clearScreen(); putString(owner_name, Coord_t{3, 9}); putString("Item", Coord_t{4, 3}); putString("Asking Price", Coord_t{4, 60}); displayPlayerRemainingGold(); displayStoreCommands(); displayStoreInventory(store, current_top_item_id); } // Get the ID of a store item and return it's value -RAK- static bool storeGetItemID(int &item_id, const char *prompt, int item_pos_start, int item_pos_end) { item_id = -1; vtype_t msg = {'\0'}; (void) sprintf(msg, "(Items %c-%c, ESC to exit) %s", item_pos_start + 'a', item_pos_end + 'a', prompt); char command; bool item_found = false; while (getCommand(msg, command)) { command -= 'a'; if (command >= item_pos_start && command <= item_pos_end) { item_found = true; item_id = command; break; } terminalBellSound(); } messageLineClear(); return item_found; } // Increase the insult counter and get angry if too many -RAK- static bool storeIncreaseInsults(int store_id) { Store_t &store = stores[store_id]; store.insults_counter++; if (store.insults_counter > store_owners[store.owner_id].max_insults) { printSpeechGetOutOfMyStore(); store.insults_counter = 0; store.bad_purchases++; store.turns_left_before_closing = dg.game_turn + 2500 + randomNumber(2500); return true; } return false; } // Decrease insults -RAK- static void storeDecreaseInsults(int store_id) { if (stores[store_id].insults_counter != 0) { stores[store_id].insults_counter--; } } // Have insulted while haggling -RAK- static bool storeHaggleInsults(int store_id) { if (storeIncreaseInsults(store_id)) { return true; } printSpeechTryAgain(); // keep insult separate from rest of haggle printMessage(CNIL); return false; } static bool storeGetHaggle(const char *comment, int32_t &new_offer, int num_offer) { if (num_offer == 0) { store_last_increment = 0; } bool increment = false; auto comment_len = (int) strlen(comment); int save_comment_len = comment_len; char *p = nullptr; vtype_t msg = {'\0'}; vtype_t default_offer = {'\0'}; bool flag = true; int32_t offer_adjust = 0; while (flag && offer_adjust == 0) { putStringClearToEOL(comment, Coord_t{0, 0}); if ((num_offer != 0) && store_last_increment != 0) { auto abs_store_last_increment = (int) std::abs((std::intmax_t) store_last_increment); (void) sprintf(default_offer, "[%c%d] ", (store_last_increment < 0) ? '-' : '+', abs_store_last_increment); putStringClearToEOL(default_offer, Coord_t{0, save_comment_len}); comment_len = save_comment_len + (int) strlen(default_offer); } if (!getStringInput(msg, Coord_t{0, comment_len}, 40)) { flag = false; } for (p = msg; *p == ' '; p++) { // fast forward to next space character } if (*p == '+' || *p == '-') { increment = true; } if ((num_offer != 0) && increment) { stringToNumber(msg, offer_adjust); // Don't accept a zero here. Turn off increment if it was zero // because a zero will not exit. This can be zero if the user // did not type a number after the +/- sign. if (offer_adjust == 0) { increment = false; } else { store_last_increment = (int16_t) offer_adjust; } } else if ((num_offer != 0) && *msg == '\0') { offer_adjust = store_last_increment; increment = true; } else { stringToNumber(msg, offer_adjust); } // don't allow incremental haggling, if player has not made an offer yet if (flag && num_offer == 0 && increment) { printMessage("You haven't even made your first offer yet!"); offer_adjust = 0; increment = false; } } if (flag) { if (increment) { new_offer += offer_adjust; } else { new_offer = offer_adjust; } } else { messageLineClear(); } return flag; } static int storeReceiveOffer(int store_id, const char *comment, int32_t &new_offer, int32_t last_offer, int num_offer, int factor) { int receive = 0; bool success = false; while (!success) { if (storeGetHaggle(comment, new_offer, num_offer)) { if (new_offer * factor >= last_offer * factor) { success = true; } else if (storeHaggleInsults(store_id)) { receive = 2; success = true; } else { // new_offer rejected, reset new_offer so that incremental // haggling works correctly new_offer = last_offer; } } else { receive = 1; success = true; } } return receive; } // Haggling routine -RAK- static int storePurchaseHaggle(int store_id, int32_t &price, Inventory_t const &item) { bool did_not_haggle = false; price = 0; int purchase = 0; int final_flag = 0; Store_t const &store = stores[store_id]; Owner_t const &owner = store_owners[store.owner_id]; int32_t max_sell, min_sell; int32_t cost = storeItemSellPrice(store, min_sell, max_sell, item); max_sell = max_sell * playerStatAdjustmentCharisma() / 100; if (max_sell <= 0) { max_sell = 1; } min_sell = min_sell * playerStatAdjustmentCharisma() / 100; if (min_sell <= 0) { min_sell = 1; } // cast max_inflate to signed so that subtraction works correctly int32_t max_buy = cost * (200 - (int) owner.max_inflate) / 100; if (max_buy <= 0) { max_buy = 1; } int32_t min_per = owner.haggles_per; int32_t max_per = min_per * 3; displayStoreHaggleCommands(1); int32_t current_asking_price = max_sell; int32_t final_asking_price = min_sell; int32_t min_offer = max_buy; int32_t last_offer = min_offer; int32_t new_offer = 0; int num_offer = 0; // this prevents incremental haggling on first try const char *comment = "Asking"; // go right to final price if player has bargained well if (storeNoNeedToBargain(stores[store_id], final_asking_price)) { printMessage("After a long bargaining session, you agree upon the price."); current_asking_price = min_sell; comment = "Final offer"; did_not_haggle = true; // Set up automatic increment, so that a return will accept the final price. store_last_increment = (int16_t) min_sell; num_offer = 1; } vtype_t msg = {'\0'}; bool flag = false; while (!flag) { bool loop_flag; do { loop_flag = true; (void) sprintf(msg, "%s : %d", comment, current_asking_price); putString(msg, Coord_t{1, 0}); purchase = storeReceiveOffer(store_id, "What do you offer? ", new_offer, last_offer, num_offer, 1); if (purchase != 0) { flag = true; } else { if (new_offer > current_asking_price) { printSpeechSorry(); // rejected, reset new_offer for incremental haggling new_offer = last_offer; // If the automatic increment is large enough to overflow, // then the player must have made a mistake. Clear it // because it is useless. if (last_offer + store_last_increment > current_asking_price) { store_last_increment = 0; } } else if (new_offer == current_asking_price) { flag = true; price = new_offer; } else { loop_flag = false; } } } while (!flag && loop_flag); if (!flag) { int32_t x1 = (new_offer - last_offer) * 100 / (current_asking_price - last_offer); if (x1 < min_per) { flag = storeHaggleInsults(store_id); if (flag) { purchase = 2; } } else if (x1 > max_per) { x1 = x1 * 75 / 100; if (x1 < max_per) { x1 = max_per; } } int32_t x2 = x1 + randomNumber(5) - 3; int32_t x3 = ((current_asking_price - new_offer) * x2 / 100) + 1; // don't let the price go up if (x3 < 0) { x3 = 0; } current_asking_price -= x3; if (current_asking_price < final_asking_price) { current_asking_price = final_asking_price; comment = "Final Offer"; // Set the automatic haggle increment so that RET will give // a new_offer equal to the final_asking_price price. store_last_increment = (int16_t) (final_asking_price - new_offer); final_flag++; if (final_flag > 3) { if (storeIncreaseInsults(store_id)) { purchase = 2; } else { purchase = 1; } flag = true; } } else if (new_offer >= current_asking_price) { flag = true; price = new_offer; } if (!flag) { last_offer = new_offer; num_offer++; // enable incremental haggling eraseLine(Coord_t{1, 0}); (void) sprintf(msg, "Your last offer : %d", last_offer); putString(msg, Coord_t{1, 39}); printSpeechSellingHaggle(last_offer, current_asking_price, final_flag); // If the current increment would take you over the store's // price, then decrease it to an exact match. if (current_asking_price - last_offer < store_last_increment) { store_last_increment = (int16_t) (current_asking_price - last_offer); } } } } // update bargaining info if (purchase == 0 && !did_not_haggle) { storeUpdateBargainInfo(stores[store_id], price, final_asking_price); } return purchase; } // Haggling routine -RAK- static int storeSellHaggle(int store_id, int32_t &price, Inventory_t const &item) { int32_t max_gold = 0; int32_t min_per = 0; int32_t max_per = 0; int32_t max_sell = 0; int32_t min_buy = 0; int32_t max_buy = 0; bool flag = false; bool did_not_haggle = false; price = 0; int sell = 0; int final_flag = 0; Store_t const &store = stores[store_id]; int32_t cost = storeItemValue(item); if (cost < 1) { sell = 3; flag = true; } else { Owner_t const &owner = store_owners[store.owner_id]; cost = cost * (200 - playerStatAdjustmentCharisma()) / 100; cost = cost * (200 - race_gold_adjustments[owner.race][py.misc.race_id]) / 100; if (cost < 1) { cost = 1; } max_sell = cost * owner.max_inflate / 100; // cast max_inflate to signed so that subtraction works correctly max_buy = cost * (200 - (int) owner.max_inflate) / 100; min_buy = cost * (200 - owner.min_inflate) / 100; if (min_buy < 1) { min_buy = 1; } if (max_buy < 1) { max_buy = 1; } if (min_buy < max_buy) { min_buy = max_buy; } min_per = owner.haggles_per; max_per = min_per * 3; max_gold = owner.max_cost; } int32_t current_askin_price; int32_t final_asking_price = 0; const char *comment = nullptr; if (!flag) { displayStoreHaggleCommands(-1); int num_offer = 0; // this prevents incremental haggling on first try if (max_buy > max_gold) { final_flag = 1; comment = "Final Offer"; // Disable the automatic haggle increment on RET. store_last_increment = 0; current_askin_price = max_gold; final_asking_price = max_gold; printMessage("I am sorry, but I have not the money to afford such a fine item."); did_not_haggle = true; } else { current_askin_price = max_buy; final_asking_price = min_buy; if (final_asking_price > max_gold) { final_asking_price = max_gold; } comment = "Offer"; // go right to final price if player has bargained well if (storeNoNeedToBargain(stores[store_id], final_asking_price)) { printMessage("After a long bargaining session, you agree upon the price."); current_askin_price = final_asking_price; comment = "Final offer"; did_not_haggle = true; // Set up automatic increment, so that a return // will accept the final price. store_last_increment = (int16_t) final_asking_price; num_offer = 1; } } int32_t min_offer = max_sell; int32_t last_offer = min_offer; int32_t new_offer = 0; if (current_askin_price < 1) { current_askin_price = 1; } do { bool loop_flag; do { loop_flag = true; vtype_t msg = {'\0'}; (void) sprintf(msg, "%s : %d", comment, current_askin_price); putString(msg, Coord_t{1, 0}); sell = storeReceiveOffer(store_id, "What price do you ask? ", new_offer, last_offer, num_offer, -1); if (sell != 0) { flag = true; } else { if (new_offer < current_askin_price) { printSpeechSorry(); // rejected, reset new_offer for incremental haggling new_offer = last_offer; // If the automatic increment is large enough to // overflow, then the player must have made a mistake. // Clear it because it is useless. if (last_offer + store_last_increment < current_askin_price) { store_last_increment = 0; } } else if (new_offer == current_askin_price) { flag = true; price = new_offer; } else { loop_flag = false; } } } while (!flag && loop_flag); if (!flag) { int32_t x1 = (last_offer - new_offer) * 100 / (last_offer - current_askin_price); if (x1 < min_per) { flag = storeHaggleInsults(store_id); if (flag) { sell = 2; } } else if (x1 > max_per) { x1 = x1 * 75 / 100; if (x1 < max_per) { x1 = max_per; } } int32_t x2 = x1 + randomNumber(5) - 3; int32_t x3 = ((new_offer - current_askin_price) * x2 / 100) + 1; // don't let the price go down if (x3 < 0) { x3 = 0; } current_askin_price += x3; if (current_askin_price > final_asking_price) { current_askin_price = final_asking_price; comment = "Final Offer"; // Set the automatic haggle increment so that RET will give // a new_offer equal to the final_asking_price price. store_last_increment = (int16_t) (final_asking_price - new_offer); final_flag++; if (final_flag > 3) { if (storeIncreaseInsults(store_id)) { sell = 2; } else { sell = 1; } flag = true; } } else if (new_offer <= current_askin_price) { flag = true; price = new_offer; } if (!flag) { last_offer = new_offer; num_offer++; // enable incremental haggling eraseLine(Coord_t{1, 0}); vtype_t msg = {'\0'}; (void) sprintf(msg, "Your last bid %d", last_offer); putString(msg, Coord_t{1, 39}); printSpeechBuyingHaggle(current_askin_price, last_offer, final_flag); // If the current decrement would take you under the store's // price, then increase it to an exact match. if (current_askin_price - last_offer > store_last_increment) { store_last_increment = (int16_t) (current_askin_price - last_offer); } } } } while (!flag); } // update bargaining info if (sell == 0 && !did_not_haggle) { storeUpdateBargainInfo(stores[store_id], price, final_asking_price); } return sell; } // Get the number of store items to display on the screen static int storeItemsToDisplay(int store_counter, int current_top_item_id) { if (current_top_item_id == 12) { return store_counter - 1 - 12; } if (store_counter > 11) { return 11; } return store_counter - 1; } // Buy an item from a store -RAK- static bool storePurchaseAnItem(int store_id, int ¤t_top_item_id) { Store_t &store = stores[store_id]; if (store.unique_items_counter < 1) { printMessage("I am currently out of stock."); return false; } int item_id; int item_count = storeItemsToDisplay(store.unique_items_counter, current_top_item_id); if (!storeGetItemID(item_id, "Which item are you interested in? ", 0, item_count)) { return false; } // Get the item number to be bought item_id = item_id + current_top_item_id; // true item_id Inventory_t sell_item{}; inventoryTakeOneItem(&sell_item, &store.inventory[item_id].item); if (!inventoryCanCarryItemCount(sell_item)) { putStringClearToEOL("You cannot carry that many different items.", Coord_t{0, 0}); return false; } int choice = 0; int32_t price; bool purchased = false; if (store.inventory[item_id].cost > 0) { price = store.inventory[item_id].cost; } else { choice = storePurchaseHaggle(store_id, price, sell_item); } if (choice == 0) { if (py.misc.au >= price) { printSpeechFinishedHaggling(); storeDecreaseInsults(store_id); py.misc.au -= price; int new_item_id = inventoryCarryItem(sell_item); int saved_store_counter = store.unique_items_counter; storeDestroyItem(store_id, item_id, true); obj_desc_t description = {'\0'}; itemDescription(description, inventory[new_item_id], true); obj_desc_t msg = {'\0'}; (void) sprintf(msg, "You have %s (%c)", description, new_item_id + 'a'); putStringClearToEOL(msg, Coord_t{0, 0}); playerStrength(); if (current_top_item_id >= store.unique_items_counter) { current_top_item_id = 0; displayStoreInventory(stores[store_id], current_top_item_id); } else { InventoryRecord_t &store_item = store.inventory[item_id]; if (saved_store_counter == store.unique_items_counter) { if (store_item.cost < 0) { store_item.cost = price; displaySingleCost(store_id, item_id); } } else { displayStoreInventory(stores[store_id], item_id); } } displayPlayerRemainingGold(); } else { if (storeIncreaseInsults(store_id)) { purchased = true; } else { printSpeechFinishedHaggling(); printMessage("Liar! You have not the gold!"); } } } else if (choice == 2) { purchased = true; } // Less intuitive, but looks better here than in storePurchaseHaggle. displayStoreCommands(); eraseLine(Coord_t{1, 0}); return purchased; } // Functions to emulate the original Pascal sets static bool setGeneralStoreItems(int item_id) { switch (item_id) { case TV_DIGGING: case TV_BOOTS: case TV_CLOAK: case TV_FOOD: case TV_FLASK: case TV_LIGHT: case TV_SPIKE: return true; default: return false; } } static bool setArmoryItems(int item_id) { switch (item_id) { case TV_BOOTS: case TV_GLOVES: case TV_HELM: case TV_SHIELD: case TV_HARD_ARMOR: case TV_SOFT_ARMOR: return true; default: return false; } } static bool setWeaponsmithItems(int item_id) { switch (item_id) { case TV_SLING_AMMO: case TV_BOLT: case TV_ARROW: case TV_BOW: case TV_HAFTED: case TV_POLEARM: case TV_SWORD: return true; default: return false; } } static bool setTempleItems(int item_id) { switch (item_id) { case TV_HAFTED: case TV_SCROLL1: case TV_SCROLL2: case TV_POTION1: case TV_POTION2: case TV_PRAYER_BOOK: return true; default: return false; } } static bool setAlchemistItems(int item_id) { switch (item_id) { case TV_SCROLL1: case TV_SCROLL2: case TV_POTION1: case TV_POTION2: return true; default: return false; } } static bool setMagicShopItems(int item_id) { switch (item_id) { case TV_AMULET: case TV_RING: case TV_STAFF: case TV_WAND: case TV_SCROLL1: case TV_SCROLL2: case TV_POTION1: case TV_POTION2: case TV_MAGIC_BOOK: return true; default: return false; } } // Each store will buy only certain items, based on TVAL bool (*store_buy[MAX_STORES])(int) = { setGeneralStoreItems, setArmoryItems, setWeaponsmithItems, setTempleItems, setAlchemistItems, setMagicShopItems, }; // Sell an item to the store -RAK- static bool storeSellAnItem(int store_id, int ¤t_top_item_id) { int first_item = py.unique_inventory_items; int last_item = -1; char mask[player_equipment::EQUIPMENT_WIELD]; for (int counter = 0; counter < py.unique_inventory_items; counter++) { int flag = (*store_buy[store_id])(inventory[counter].category_id); mask[counter] = (char) flag; if (flag) { if (counter < first_item) { first_item = counter; } if (counter > last_item) { last_item = counter; } } } if (last_item == -1) { printMessage("You have nothing to sell to this store!"); return false; } int item_id; if (!inventoryGetInputForItemId(item_id, "Which one? ", first_item, last_item, mask, "I do not buy such items.")) { return false; } Inventory_t sold_item{}; inventoryTakeOneItem(&sold_item, &inventory[item_id]); obj_desc_t description = {'\0'}; itemDescription(description, sold_item, true); obj_desc_t msg = {'\0'}; (void) sprintf(msg, "Selling %s (%c)", description, item_id + 'a'); printMessage(msg); if (!storeCheckPlayerItemsCount(stores[store_id], sold_item)) { printMessage("I have not the room in my store to keep it."); return false; } int32_t price; bool sold = false; int choice = storeSellHaggle(store_id, price, sold_item); if (choice == 0) { printSpeechFinishedHaggling(); storeDecreaseInsults(store_id); py.misc.au += price; // identify object in inventory to set objects_identified array itemIdentify(inventory[item_id], item_id); // retake sold_item so that it will be identified inventoryTakeOneItem(&sold_item, &inventory[item_id]); // call spellItemIdentifyAndRemoveRandomInscription for store item, so charges/pluses are known spellItemIdentifyAndRemoveRandomInscription(sold_item); inventoryDestroyItem(item_id); itemDescription(description, sold_item, true); (void) sprintf(msg, "You've sold %s", description); printMessage(msg); int item_pos_id; storeCarryItem(store_id, item_pos_id, sold_item); playerStrength(); if (item_pos_id >= 0) { if (item_pos_id < 12) { if (current_top_item_id < 12) { displayStoreInventory(stores[store_id], item_pos_id); } else { current_top_item_id = 0; displayStoreInventory(stores[store_id], current_top_item_id); } } else if (current_top_item_id > 11) { displayStoreInventory(stores[store_id], item_pos_id); } else { current_top_item_id = 12; displayStoreInventory(stores[store_id], current_top_item_id); } } displayPlayerRemainingGold(); } else if (choice == 2) { sold = true; } else if (choice == 3) { printMessage("How dare you!"); printMessage("I will not buy that!"); sold = storeIncreaseInsults(store_id); } // Less intuitive, but looks better here than in storeSellHaggle. eraseLine(Coord_t{1, 0}); displayStoreCommands(); return sold; } // Entering a store -RAK- void storeEnter(int store_id) { Store_t const &store = stores[store_id]; if (store.turns_left_before_closing >= dg.game_turn) { printMessage("The doors are locked."); return; } int current_top_item_id = 0; displayStore(stores[store_id], store_owners[store.owner_id].name, current_top_item_id); bool exit_store = false; while (!exit_store) { moveCursor(Coord_t{20, 9}); // clear the msg flag just like we do in dungeon.c message_ready_to_print = false; char command; if (getCommand("", command)) { int saved_chr; switch (command) { case 'b': if (current_top_item_id == 0) { if (store.unique_items_counter > 12) { current_top_item_id = 12; displayStoreInventory(stores[store_id], current_top_item_id); } else { printMessage("Entire inventory is shown."); } } else { current_top_item_id = 0; displayStoreInventory(stores[store_id], current_top_item_id); } break; case 'E': case 'e': // Equipment List case 'I': case 'i': // Inventory case 'T': case 't': // Take off case 'W': case 'w': // Wear case 'X': case 'x': // Switch weapon saved_chr = py.stats.used[py_attrs::A_CHR]; do { inventoryExecuteCommand(command); command = game.doing_inventory_command; } while (command != 0); // redisplay store prices if charisma changes if (saved_chr != py.stats.used[py_attrs::A_CHR]) { displayStoreInventory(stores[store_id], current_top_item_id); } game.player_free_turn = false; // No free moves here. -CJS- break; case 'p': exit_store = storePurchaseAnItem(store_id, current_top_item_id); break; case 's': exit_store = storeSellAnItem(store_id, current_top_item_id); break; default: terminalBellSound(); break; } } else { exit_store = true; } } // Can't save and restore the screen because inventoryExecuteCommand() does that. drawCavePanel(); } // eliminate need to bargain if player has haggled well in the past -DJB- static bool storeNoNeedToBargain(Store_t const &store, int32_t min_price) { if (store.good_purchases == MAX_SHORT) { return true; } int bargain_record = (store.good_purchases - 3 * store.bad_purchases - 5); return ((bargain_record > 0) && ((int32_t) bargain_record * (int32_t) bargain_record > min_price / 50)); } // update the bargain info -DJB- static void storeUpdateBargainInfo(Store_t &store, int32_t price, int32_t min_price) { if (min_price > 9) { if (price == min_price) { if (store.good_purchases < MAX_SHORT) { store.good_purchases++; } } else { if (store.bad_purchases < MAX_SHORT) { store.bad_purchases++; } } } } umoria-5.7.10+20181022/src/wizard.h0000644000175000017500000000065513363422757015227 0ustar arielariel// Copyright (c) 1981-86 Robert A. Koeneke // Copyright (c) 1987-94 James E. Wilson // // This work is free software released under the GNU General Public License // version 2.0, and comes with ABSOLUTELY NO WARRANTY. // // See LICENSE and AUTHORS for more information. #pragma once bool enterWizardMode(); void wizardLightUpDungeon(); void wizardCharacterAdjustment(); void wizardGenerateObject(); void wizardCreateObjects(); umoria-5.7.10+20181022/CHANGELOG.md0000644000175000017500000002010313363422757014566 0ustar arielariel# Umoria CHANGELOG - this file tracks changes since v5.6.0 ## HEAD ## 5.7.10 (2018-02-18) ### Bug Fixes - `xor_byte` decryption was not being performed correctly on `score.dat` files. Introduced with commit: 676cdfed6c274279fa889c079e84788adc954cac (`readHighScore()` function). Note: `game.sav` files were **not affected** by this bug. ### Code - Delete `constant.h`, moving many constants into `config.h` and the rest into their related headers. Things are looking pretty ugly at the moment but perhaps it's a better starting point for further refactoring. - Change `config.h` to namespace'd constants - perhaps not a good approach but let's see! ## 5.7.9 (2018-01-20) - Add AUTHORS file containing all known author information. This removes contributors section from `versions.txt`. ### Bug Fixes - Kill experience points now calculated correctly. When extracting a method the wrong `int` type was used when calculating the `creature.kill_exp_value * creature.level`. This bug was introduced in Umoria `5.7.3` with the commit: ccfa74783ad67eb3276ff3eca0f2509599012d33 ### Code Continuing the process of moving related functions to the same file, plus other changes. Highlights: - `types.h` now has just the two core `vtype_t` and `obj_desc_t` types. - The numbered `misc` and `moria` files no longer exist! - Moved `sets.cpp` functions elsewhere, allowing for most to become `static`. - `externs.h` is now empty, so deleted! - Add consistent `const` in various places. - Use more `Coord_t` in Dungeon related functions. ## 5.7.8 (2017-12-24) - Improve _Wizard Mode_ help files. - Easier item creation in _Wizard Mode_ (inspired by Bmoria). - Change `[Press any key to continue.]` to `[ press any key to continue ]`, and any variations, as it looks clearer. ### Code There are two main areas of focus for this release. The first was to create more objects to move the numerous global variables on to. E.g. `Dungeon_t`. The second area of focus has been to start grouping related functions and variables in the same file. Ex. most player functions are moved to `player.cpp`, or a new `player_xxx.cpp` file has been created (run, stats, tunnel, etc.). The LOS and look functions are located in `los.cpp`. Grouping globals and functions together like this should make their usage and their relationships more obvious. These locations are by no means final, but are a useful first pass. - Grouping related functions together in the same file. - Move more Player globals to the `Player_t` struct. - Create a `Dungeon_t` and put all dungeon related globals here. - Create a `Dice_t` instead of using an array. - Started replacing `char *` with `std::string`. - Simpler display of _death_ screens (using `death_tomb.txt`, `death_royal.txt` files). ## 5.7.7 (2017-11-07) ### Bug Fixes - Game loading fix where shops data was being read twice, and the second time resulted in incorrect data. Oops! Broken during save game refactor: https://github.com/dungeons-of-moria/umoria/commit/ce2c756 ## 5.7.6 (2017-11-05) ### Bug Fixes - When compiling on Windows/MinGW, the OS was not being detected correctly. https://github.com/dungeons-of-moria/umoria/commit/3811bcd - Now load cave tiles correctly (the `lit` status values), as a previous refactoring broke loading of pre-5.7 saves. https://github.com/dungeons-of-moria/umoria/commit/219f350 ### Code - _Pass by Value_ and _Pass by Reference_ major refactoring. - Replace "magic numbers" with ENUMs, in `staffs.cpp`. - Use `Coord_t` instead of `y`/`x` values in various coordinated related functions. ## 5.7.5 (2017-09-05) ### Notable changes - CLI: Added a parameter to provide a custom game seed value, using `-s`. - CLI: Display high scores flag now changed to `-d`, because of the game seed. - CLI: Remove `-o` and just use the one arg (`-r`) for specifying (forcing) the use of roguelike keys (`hjkl`). - Renamed the data/help files. E.g. `owizcmds.hlp` > `help.txt`. - Support only save/score files for Umoria `5.2.2` up to `5.7.x`. ### Bug Fixes - `TV_NEVER` was an unsigned int, but should be signed as it's being given a `-1` value. [https://github.com/dungeons-of-moria/umoria/commit/8c3d1d2] - The `monsterTakeHit()` check is now correct in `spellLightLineTouchesMonster()`. [https://github.com/dungeons-of-moria/umoria/commit/b26547d] - When player was in _Run/Find_ mode, the `find_count` was not being dereferenced, so `playerEndRunning()` would not be called correctly. [https://github.com/dungeons-of-moria/umoria/commit/95dc308] ### Code - Add an optimized "release" build, with an `-O2` optimization flag. - Setting version number in version.h now updates data files automatically, via CMake. - Release date in `data/versions.txt` is now set automatically via CMake. - Rename field names for all structs in `types.h`. - Rename many constants for better clarity on their purpose. - Move the game options globals into a Config_t struct. - Refactor `main.cpp` to contain [mostly] just CLI arg parsing, and some basic game initialization. All other game logic moved to `moria.cpp`. - Lots of clang-tidy based refactoring. ## 5.7.4 (2017-08-06) ### Bug Fixes - The create food spell was creating random items instead of just food. ### Code The main focus of this release was to rename all functions, function variables, and global variables. The old naming did not reveal their purpose very well, which made understanding the code more difficult. These are not meant to be final names, they have been chosen to aid future refactoring tasks. Additionally: * The `variable` file was renamed to `globals`. * Many defines (mainly in `constant.h`) changed to a `constexpr`. ## 5.7.3 (2017-06-21) ### Bug Fixes - Various repeat commands (e.g tunnelling) were broken in the 5.7.1 release. - Remove `curs_set(0)` as a visible cursor is needed in menus! ### Documentation - Add a `CODE_OF_CONDUCT`. - Add a `CONTRIBUTING` guide. ### Code _Extract Method_ refactoring was the main focus of this release. Along the way many other refactorings were made, including: - Refactor `if/else` statements: * Add guard clauses / early returns where possible. * Remove `else` when an `if` returns. * Makes many easier to understand (e.g. less indentation). - Refactor `do/while` statements, to be just `while` statements where possible. - Remove unneeded braces for `if` statement expressions. - Rename many variables to make their function more obvious, but there's more to do in this area. - Generally lots of small refactoring. ## 5.7.2 (2017-03-12) - Disable terminal control chars (e.g. CTRL-C) - Make splash screen logo a little nicer. ## 5.7.1 (2017-01-27) - Improved CLI: adds _help_ and _version_ information (`-h` and `-v`). - Lots of spelling fixes, mostly source code comments, but a few in-game also. - Updates to the manual, FAQ, and historical/errors documents. - Compiles to C++, with all warnings fixed! (`-Wall`, `-Wextra`, `-Werror`, `-Wshadow`) - Now uses CMake for Mac/Linux build - Windows/MinGW still uses a normal Makefile. ## 5.7.0 (2016-11-27) Lots of code clean-up and standardization, along with removing support for outdated systems. The main feature of this release is support for Windows and macOS. ### Notable changes - **Windows**, **macOS** and **Linux** support. - Renaming binary from `moria` to `umoria`, save file to `game.sav`, and scores to `scores.dat`. - Use `clang-format`/`-tidy` to clean up the code formatting. - Moves all standard library header includes into one file. - Replaces custom types (e.g. `int8u`, `int16u`, etc.) with their equivalent C standard types. - Introduce the `_Bool` type. - Converts deprecated K&R style function declarations. - Refactor all comments. - Reorganise all old document files, create a `historical` directory. ### Deprecated - Remove support for discontinued computers and OS: Atari ST, Amiga, MS DOS, "Classic" Mac OS (pre OS X), VMS, System III, etc., etc. ## 5.6.0 (2008-10-13) Umoria is released under a new GPL v2 license. More information is available on the [free-moria](http://free-moria.sourceforge.net/) website. All previous changes can be found in the [historical/CHANGELOG](historical/CHANGELOG). umoria-5.7.10+20181022/.clang-format0000644000175000017500000000513013363422757015333 0ustar arielariel--- Language: Cpp BasedOnStyle: LLVM AccessModifierOffset: -2 AlignAfterOpenBracket: Align AlignConsecutiveAssignments: false AlignConsecutiveDeclarations: false AlignEscapedNewlinesLeft: false AlignOperands: true AlignTrailingComments: true AllowAllParametersOfDeclarationOnNextLine: true AllowShortBlocksOnASingleLine: false AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: false AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: false BinPackArguments: true BinPackParameters: true BraceWrapping: AfterClass: false AfterControlStatement: false AfterEnum: false AfterFunction: false AfterNamespace: false AfterObjCDeclaration: false AfterStruct: false AfterUnion: false BeforeCatch: false BeforeElse: false IndentBraces: false BreakBeforeBinaryOperators: None BreakBeforeBraces: Attach BreakBeforeTernaryOperators: true BreakConstructorInitializersBeforeComma: false BreakStringLiterals: false ColumnLimit: 180 CommentPragmas: '^ IWYU pragma:' ConstructorInitializerAllOnOneLineOrOnePerLine: false ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 Cpp11BracedListStyle: true DerivePointerAlignment: false DisableFormat: false ExperimentalAutoDetectBinPacking: false ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] IncludeCategories: - Regex: '^"(llvm|llvm-c|clang|clang-c)/' Priority: 2 - Regex: '^(<|"(gtest|isl|json)/)' Priority: 3 - Regex: '.*' Priority: 1 IncludeIsMainRegex: '$' IndentCaseLabels: true IndentWidth: 4 IndentWrappedFunctionNames: false KeepEmptyLinesAtTheStartOfBlocks: false MacroBlockBegin: '' MacroBlockEnd: '' MaxEmptyLinesToKeep: 1 NamespaceIndentation: None ObjCBlockIndentWidth: 4 ObjCSpaceAfterProperty: false ObjCSpaceBeforeProtocolList: true PenaltyBreakBeforeFirstCallParameter: 19 PenaltyBreakComment: 300 PenaltyBreakFirstLessLess: 120 PenaltyBreakString: 1000 PenaltyExcessCharacter: 1000000 PenaltyReturnTypeOnItsOwnLine: 60 PointerAlignment: Right ReflowComments: true SortIncludes: false SpaceAfterCStyleCast: true SpaceBeforeAssignmentOperators: true SpaceBeforeParens: ControlStatements SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 1 SpacesInAngles: false SpacesInContainerLiterals: true SpacesInCStyleCastParentheses: false SpacesInParentheses: false SpacesInSquareBrackets: false Standard: Cpp11 TabWidth: 4 UseTab: Never ... umoria-5.7.10+20181022/docs/0000755000175000017500000000000013363422757013711 5ustar arielarielumoria-5.7.10+20181022/docs/manual.md0000644000175000017500000031663213363422757015523 0ustar arielariel# The Dungeons of Moria _Robert A. Koeneke's classic roguelike dungeon crawler._ Contents 1. Introduction 2. Running The Game 3. The Character 4. Adventuring 5. Symbols On The Screen 6. Commands 7. The Town Level 8. Within The Dungeon 9. Attacking and Being Attacked 10. Objects Found In The Dungeon 11. Winning The Game 12. Upon Death and Dying 13. Wizards 14. Contributors 15. Umoria Licence ## 1. Introduction The game of *moria* is a single player dungeon simulation. A player may choose from a number of races and classes when creating a character, and then *run* that character over a period of days, weeks, even months, attempting to win the game by defeating the Balrog which lurks in the deeper levels. The character will begin the adventure on the town level where supplies, weapons, armor, and magical devices can be acquired by bartering with various shop owners. After preparing for this adventure, the character can descend into the dungeons of *moria* where fantastic adventures await! Before beginning an adventure, this document should be read carefully. The game of *moria* is a complicated game (but not as complicated as other roguelikes), and will require a dedicated player to win. ## 2. Running The Game umoria [ -h ] [ -v ] [ -r ] [ -d ] [ -n ] [ -w ] [ -s ] [ SAVEGAME ] By default, *moria* will save and restore games from a file called `game.sav` in the directory where the game is located. The save file can also be explicitly specified on the command line. When the `-n` option is used, *moria* will create a new game, ignoring any save file which may already exist. This works best when a save file name is specified on the command line, as this will prevent *moria* from trying to overwrite the default save file (if it exists) when saving a game. Movement in various directions is accomplished by pressing the numeric keypad keys. When `-r` is specified, movement is accomplished in the same way as the original _Rogue_ game (`hjkl`). This option will override defaults stored in the save file. When `-d` is specified, *moria* displays all of the scores in the score file and exits. When `-w` is specified, *moria* will start up in wizard mode. Dead characters can be resurrected using this option when starting the game. Resurrected characters are teleported to the town level and given zero hit-points. Wizard mode is intended for debugging the game, and for experimenting with new features. *Using wizard mode to win the game is considered cheating. Games played with wizard mode are not scored.* To make random events happen in a predictable manner a `seed` number can be given with the `-s` option (only for new games). Use `-v` to show the current version of Umoria. Use `-h` to show the help screen. ## 3. The Character All characters have six main attributes which modify their basic abilities. These six attributes, called *stats*, are *strength*, *intelligence*, *wisdom*, *dexterity*, *constitution*, and *charisma*. Stats may vary from a minimum of 3 to a maximum of 18. At the highest level, stats are further qualified by a number from zero to one hundred, so that the highest value is actually 18/100. A value of 18/100 can be thought of as equivalent to 19, and 18/00 (not actually used) is equivalent to 18. Because adventurers of interest tend to be better than average characters, *moria* stats will average about 13, and are further adjusted by race and class. Some races are just naturally better at being certain classes, as will be shown later. In addition to the more visible stats, each character has certain abilities which are mainly determined by their race, class, and level, but are also modified by their stats. The abilities are *fighting*, *throwing/bows*, *saving throw*, *stealth*, *disarming*, *magical devices*, *perception*, *searching*, and *infra-vision*. Characters will be assigned an early history, with money and a social class based on that history. Starting money is assigned based on history, charisma, and somewhat upon the average of a character’s stats. A character with below average stats will receive extra money to help them survive the first adventure. Each character will also have physical attributes such as race, height, weight, sex, and a physical description. None of these, except weight, play any part in the game other than to give the player a "feeling" for their character. Weight is used for computing carrying capacity and also for bashing. Finally, each character is assigned *hit-points* based on their race, class, and constitution. Spell casters will also receive *mana* which is expended when casting spells. Mana is based on Wisdom for Priests and Intelligence for Mages. ### 3.1 Character Stats #### `STR` Strength is important in fighting with weapons and hand to hand combat. A high strength can improve the chances of hitting, and the amount of damage done with each hit. Characters with low strengths may receive penalties. Strength is also useful in tunneling, body and shield bashing, and in carrying heavy items. #### `INT` Intelligence is the prime stat of a mage, or magician. A high intelligence increases a mage’s chances of learning spells, and it also increases the amount of mana a mage has. No spell may be learned by mages with intelligences under 8. Intelligence also modifies a character’s chance of disarming traps, picking locks, and using magic devices. #### `WIS` Wisdom is the prime stat of a priest. A high wisdom increases the chance of receiving new spells from a priest’s deity, and it also increases the amount of mana a priest has. No spell may be learned by priests with wisdom under 8. Wisdom also modifies a character’s chance of resisting magical spells cast upon their person. #### `DEX` Dexterity is a combination of agility and quickness. A high dexterity may allow a character to get multiple blows with lighter weapons, thus greatly increasing kill power, and may increase the chances of hitting with any weapon and dodging blows from enemies. Dexterity is also useful in picking locks, disarming traps, and protection from pick pockets. #### `CON` Constitution is a character’s ability to resist damage to the body, and to recover from damage received. Therefore a character with a high constitution will receive more hit points, and be more resistant to poisons. #### `CHR` Charisma represents a character’s personality, as well as physical looks. A character with a high charisma will receive better prices from store owners, whereas a character with a very low charisma will be robbed blind. A high charisma will also mean more starting money for the character. ### 3.2 Character Sex The player may choose to be either a male or a female character. Only height and weight are affected by a character’s gender. Female characters tend to be somewhat smaller and lighter than their male counterparts. No adjustments to stats or abilities are made because of the gender of a character. Female characters start out with slightly more money than male characters to help offset the weight penalty. ### 3.3 Character Abilities Characters possess nine different abilities which can help them to survive. The starting abilities of a character are based upon race and class. Abilities may be adjusted by high or low stats, and may increase with the level of the character. #### `Fighting` Fighting is the ability to hit and do damage with weapons or fists. Normally a character gets a single blow from any weapon, but if their dexterity and strength are high enough, they may receive more blows per round with lighter weapons. Strength and dexterity both modify the ability to hit an opponent. This skill increases with the level of the character. #### `Throwing/Bows` Using ranged missile weapons and throwing objects is included in this skill. Different stats apply to different weapons, but this ability may modify the distance an object is thrown/fired, the amount of damage done, and the ability to hit a creature. This skill increases with the level of the character. #### `Saving Throw` A Saving Throw is the ability of a character to resist the effects of a spell cast on them by another person or creature. This does not include spells cast on the character by their own stupidity, such as quaffing a nasty potion. This ability increases with the level of the character, but then most high level creatures are better at casting spells, so it tends to even out. A high wisdom also increases this ability. #### `Stealth` The ability to move silently about is very useful. Characters with good stealth can usually surprise their opponents, gaining the first blow. Also, creatures may fail to notice a stealthy character entirely, allowing a character to avoid certain fights. This skill is based entirely upon race and class, and will never improve unless magically enhanced. #### `Disarming` Disarming is the ability to remove traps (safely), and includes picking locks on traps and doors. A successful disarming will gain the character some experience. A trap must be found before it can be disarmed. Dexterity and intelligence both modify the ability to disarm, and this ability increases with the level of the character. #### `Using Magical Devices` Using a magical device such as a wand or staff requires experience and knowledge. Spell users such as mages and priests are therefore much better at using a magical device than say a warrior. This skill is modified by intelligence, and increases with the level of the character. #### `Perception` Perception is the ability to notice something without actively seeking it out. This skill is based entirely upon race and class, and will never improve unless magically enhanced. #### `Searching` To search is to actively look for secret doors, floor traps, and traps on chests. Rogues are the best at searching, but mages, rangers, and priests are also good at it. This skill is based entirely upon race and class, and will never improve unless magically enhanced. #### `Infra-Vision` Infra-Vision is the ability to see heat sources. Since most of the dungeon is cool or cold, infra-vision will not allow the character to see walls and objects. Infra-Vision will allow a character to see any warm-blooded creatures up to a certain distance. This ability works equally well with or without a light source. The majority of *moria*’s creatures are cold-blooded, and will not be detected unless lit up by a light source. All non-human races have innate infra-vision ability. Humans can gain infra-vision only if it is magically enhanced. ### 3.4 Choosing A Race There are eight different races that can be chosen from in *moria*. Some races are restricted as to what profession they may be, and each race has its own adjustments to a character’s stats and abilities. #### `Human` The human is the base character, all other races are compared to this race. Humans can choose any class, and are average at everything. Humans tend to go up levels faster than any other race, because of their shorter life spans. No racial adjustments occur to characters choosing human. #### `Half-Elf` Half-elves tend to be smarter and faster than a human, but not as strong. Half-elves are slightly better at searching, disarming, perception, stealth, and magic, but they are not as good at hand weapons. Half-elves may choose any class. #### `Elf` Elves are better magicians then humans, but not as good at fighting. They tend to be smarter and faster than either humans or half-elves, and also have better wisdom. Elves are better at searching, disarming, perception, stealth, and magic, but they are not as good at hand weapons. Elves may choose any class except Paladin. #### `Halfling` Halflings, or Hobbits, are very good at bows, throwing, and have good saving throws. They also are very good at searching, disarming, perception, and stealth; so they make excellent thieves (but prefer to be called burglars...). They will be much weaker than humans, and no good at bashing. Halflings have fair infra-vision, so they can detect warm creatures at a distance. Halflings can choose between being a warrior, mage, or rogue. #### `Gnome` Gnomes are smaller than dwarfs, but larger than Halflings. They, like the Halflings, live in the earth in burrow-like homes. Gnomes are practical jokers, so if they can kill something in a humorous way, so much the better. Gnomes make excellent mages, and have very good saving throws. They are good at searching, disarming, perception, and stealth. They have lower strength than humans so they are not very good at fighting with hand weapons. Gnomes have fair infra-vision, so they can detect warm creatures at a distance. A gnome may choose between being a warrior, mage, priest, or rogue. #### `Dwarf` Dwarves are the headstrong miners and fighters of legend. Since dungeons are the natural home of a dwarf, they are excellent choices for a warrior or priest. Dwarves tend to be stronger and have higher constitutions, but are slower and less intelligent than humans. Because they are so headstrong and are somewhat wise, they resist spells which are cast on them. Dwarves also have good infra-vision because they live underground. They do have one big drawback though. Dwarves are loudmouthed and proud, singing in loud voices, arguing with themselves for no good reason, screaming out challenges at imagined foes. In other words, Dwarves have a miserable stealth. #### `Half-Orc` Half-Orcs make excellent warriors, and decent priests, but are terrible at magic. They are as bad as Dwarves at stealth, and horrible at searching, disarming, and perception. Half-Orcs are, let’s face it, ugly. They tend to pay more for goods in town. Half-Orcs do make good priests and rogues, for the simple reason that Half-Orcs tend to have great constitutions and lots of hit-points. #### `Half-Troll` Half-Trolls are incredibly strong, and have the highest hit points of any character race. They are also very stupid and slow. They will make great warriors and iffy priests. They are bad at searching, disarming, perception, and stealth. They are so ugly that a Half-Orc grimaces in their presence. They also happen to be fun to run... #### 3.4.1 Race versus Skills and Stats Stat, hit dice, and experience points per level modifications due to race are listed in the following table. Str Int Wis Dex Con Chr Hit Dice Rqd Exp/level Human 0 0 0 0 0 0 10 +0% Half-Elf -1 +1 0 +1 -1 +1 9 +10% Elf -1 +2 +1 +1 -2 +1 8 +20% Halfling -2 +2 +1 +3 +1 +1 6 +10% Gnome -1 +2 0 +2 +1 -2 7 +25% Dwarf +2 -3 +1 -2 +2 -3 9 +20% Half-Orc +2 -1 0 0 +1 -4 10 +10% Half-Troll +4 -4 -2 -4 +3 -6 12 +20% Racial abilities as compared to each other, with 1 the lowest, or worst, and 10 the highest, or best, are listed in the following table. Disarm Search Stealth Percep Fight Bows Save Infra Human 5 5 5 5 5 5 5 None Half-Elf 6 7 7 6 4 6 6 20 feet Elf 8 9 7 7 3 9 7 30 feet Halfling 10 10 10 10 1 10 10 40 feet Gnome 9 7 9 9 2 8 9 30 feet Dwarf 6 8 3 5 9 5 8 50 feet Half-Orc 3 5 3 2 8 3 3 30 feet Half-Troll 1 1 1 1 10 1 1 30 feet ### 3.5 Choosing A Class Once a race has been chosen, the player will need to pick a class. Some classes will not be available to certain races, for instance, a Half-Troll cannot become a Paladin. Players who are new to the game should run a warrior or rogue. Spell casting generally requires a more experienced player that is familiar with survival techniques. #### `Warrior` Warriors are hack-and-slash characters, who solve most of their problems by cutting them to pieces, but occasionally falls back on the help of magical devices. Their prime stats are Strength and Constitution, and a good Dexterity can really help at times. A Warrior will be good at Fighting and Throwing/Bows, but bad at most other skills. #### `Mage` Mages must live by their wits. They cannot hope to simply hack their way through the dungeon, and so must therefore use their magic to defeat, deceive, confuse, and escape. Mages are not really complete without an assortment of magical devices to use in addition to their spells. They can master the higher level magical devices far easier than anyone else, and has the best saving throw to resist effects of spells cast at them. Intelligence and Dexterity are their primary stats. There is no rule that says a mages cannot become good fighters, but spells are their true realm. #### `Priest` Priests are characters of holy devotion. They explore the dungeon only to destroy the evil that lurks within, and if treasure just happens to fall into their pack, well, so much more to the glory of their temple! Priests receive their spells from a deity, and therefore do not choose which spells are learned. They are familiar with magical devices, preferring to call them instruments of god, but is not as good as a mage in their use. Priests have good saving throws, and make decent fighters, preferring blunt weapons over edged ones. Wisdom and Charisma are the priest’s primary stats. #### `Rogue` Rogues are characters that prefer to live by their cunning, but is capable of fighting their way out of a tight spot. They are the master of traps and locks, no device being impossible to overcome. A rogue has a high stealth allowing them to sneak around many creatures without having to fight, or sneak up and get the first blow. A rogue’s perception is higher than any other class, and many times they will notice a trap or secret door before having to search. A rogue is better than a warrior or paladin with magical devices, but still cannot rely on their performance. Rogues can also learn a few spells, but not the powerful offensive spells mages can use. A rogue’s primary stats are Intelligence and Dexterity. #### `Ranger` A Ranger is a warrior/mage. They are good fighters, and the best of the classes with a missile weapon such as a bow. Rangers learn spells much more slowly than a mage, but is capable of learning all but the most powerful spell. Because rangers are really a dual class character, more experience is required for them to advance. Rangers have good stealth, good perception, good searching, good saving throw, and is good with magical devices. Their primary stats are Intelligence and Dexterity. #### `Paladin` A Paladin is a warrior/priest. They are very good fighters, second only to the warrior class, but are not very good at missile weapons. They receive prayers at a slower pace then the priest, but can eventually learn all the prayers. Because paladins are dual class characters, it requires more experience to advance them. A paladin lacks much in the way of abilities. They are poor at stealth, perception, searching, and magical devices. They have a decent saving throw due to their divine alliance. A paladin’s primary stats are Strength and Charisma. #### 3.5.1 Race Versus Class Warrior Mage Priest Rogue Ranger Paladin Human Yes Yes Yes Yes Yes Yes Half-Elf Yes Yes Yes Yes Yes Yes Elf Yes Yes Yes Yes Yes No Halfling Yes Yes No Yes No No Gnome Yes Yes Yes Yes No No Dwarf Yes No Yes No No No Half-Orc Yes No Yes Yes No No Half-Troll Yes No Yes No No No #### 3.5.2 Class Versus Skills Class abilities as compared to each other, with 1 as the lowest, or worst, and 10 as the highest, or best are shown in the following table. Save Stea- Magic Extra Fight Bows Throw lth Disarm Device Percep Search Exp/lev Warrior 10 6 3 2 4 3 2 2 +0% Mage 2 1 10 5 6 10 8 5 +30% Priest 4 3 6 5 3 8 4 4 +20% Rogue 8 9 7 10 10 6 10 10 +0% Ranger 6 10 8 7 6 7 6 6 +40% Paladin 9 5 4 2 2 4 2 2 +35% ### 3.6 Experience All characters receive experience during the game. Experience determines the level, which determines hit-points, mana points, spells, abilities, etc. The amount of experience required to advance a level increases as the game is played, according to race and class. #### 3.6.1 Getting Experience There are many ways to gain experience. This list shows a few. - Defeating monsters - Disarming traps - Picking locks - Using a scroll, potion, staff, or wand, for the first time and discovering what it did - Casting a spell successfully for the first time - Drinking a potion of gain experience ## 4. Adventuring After the character has been created, their dungeon adventure can begin. Symbols appearing on the screen represent the dungeon walls, floor, objects, features, and creatures lurking about. In order to direct the character through their adventure, the player will enter single character commands. *Moria* symbols and commands each have a help section devoted to them. The player should review these sections before attempting an adventure. Finally, a description of the town level and some general help on adventuring are included. ## 5. Symbols On The Screen Symbols on the map can be broken down into three categories: 1. Features of the dungeon such as walls, floor, doors, and traps. 2. Objects which can be picked up such as treasure, weapons, armor, magical devices, etc. 3. Creatures which may or may not move about the dungeon, but are mostly harmful to the character’s well-being. Some symbols can be in more than one category. Also note that treasure may be embedded in a wall, and the wall must be removed before the treasure can be picked up. It will not be necessary to remember all of the symbols and their meanings. A simple command, the `/`, will identify any symbol appearing on the map. See the section on commands for further help. ### Features . A floor space, or hidden trap 1 Entrance to General Store # A wall 2 Entrance to Armory ' An open door 3 Entrance to Weapon Smith + A closed door 4 Entrance to Temple ^ A trap 5 Entrance to Alchemy Shop < A staircase up 6 Entrance to Magic Shop > A staircase down : Obstructing rubble ; A loose floor stone An open pit (Blank) % A mineral vein @ The character ### Objects ! A flask or potion ? A scroll " An amulet [ Hard armor $ Money (Can be embedded) \ A hafted weapon & A chest ] Misc. armor ( Soft armor _ A staff ) A shield { Missile (arrow, bolt, pebble) * Gems (Can be embedded) | Sword or dagger - A wand } Missile arm (Bow, X-bow, sling) / A pole-arm ~ Misc = A ring , Food s A skeleton ### Creatures a Giant Ant A Giant Ant Lion b Giant Bat B The Balrog c Giant Centipede C Gelatinous Cube d Dragon D Ancient Dragon e Floating Eye E Elemental f Giant Frog F Fly g Golem G Ghost h Harpy H Hobgoblin i Icky-Thing I j Jackal J Jelly k Kobold K Killer Beetle l Giant Louse L Lich m Mold M Mummy n Naga N o Orc or Ogre O Ooze p Human(oid) P Giant Human(oid) q Quasit Q Quylthulg r Rodent R Reptile s Skeleton S Scorpion t Giant Tick T Troll u U Umber Hulk v V Vampire w Worm or Worm Mass W Wight or Wraith x X Xorn y Yeek Y Yeti z Zombie Z $ Creeping Coins , Mushroom Patch Here is what the screen looks like after a character has been adventuring in *moria* for a while: # ' # Elf # ############'# Mage # # # # Mage (2nd) # # # # STR : 9 # # INT : 18/78 # # WIS : 13 # # DEX : 16 # # CON : 8 # # CHR : 15 # # ####+#### # LEV : 27 #........ # EXP : 38487 #.......### MANA: 66/66 #....@..# HP :123/123 #........ #.......# #.......# AC : 55 ####.#'## GOLD: 23868 Study 1000 feet The top line is where messages appear about what’s happening in the game. The bottom line is a status line, that shows what dungeon depth the character is at, and other things like if the character is hungry, afraid, poisoned, or moving especially slow or fast. On the left side of the screen the character’s stats can be seen. Everything else, is the map of the dungeon. This picture shows an elf mage character (`@`) in a lit room at 1000 feet below the surface. The walls of the room (`#`) can be seen, and the empty lit tiles within it (`.`). The room has one closed door (`+`), and one open door (`'`), and three other ways to leave. The character has arrived to this room through a tunnel or hall coming from the top of the screen. The tunnel is not lit like the room is. The tunnel will be lit by the character’s light source as it travels through. There are no monsters visible. From the statistics on the left side of the screen, it can be seen that the character is a `Mage (2nd)`, which is another way of saying that it is at level 27. The reason that this character is at level 27 (`LEV`) is because it has 38487 experience points (`EXP`). The character has 123 of 123 hit-points available. `HP` shows the current hit points and then the maximum hit-points. This spell-caster has exactly 66 `MANA` (spell-casting) points to spend as spells are cast. It can also be seen that this mage has some spells to learn. The word `Study` in the bottom right shows this. The character’s current levels of strength, intelligence, wisdom, dexterity, constitution and charisma can also be seen. The character doesn't have 18 of 78 intelligence, they have 18 78/100 intelligence, which is more than just 18. Finally, the character’s armor class (`AC`) is currently at 55, and has a great number of gold pieces. ## 6. Commands All commands are entered by pressing a single key. Some commands are capital or control characters, which require the player to hold down the shift or control key while pressing another key. As a special feature, control keys may be entered in a single keystroke, or in two keystrokes, with a `^` character first. There are two command sets: the original command set which is the default (e.g. the keypad based command set), and the roguelike command set. The roguelike command is generally more convenient, especially if the keyboard lacks a keypad. The following tables summarize the two command sets. Certain commands may be preceded by an optional count, and certain commands must be followed by a direction. These conditions are indicated in the tables by `@` for an optional count, and `~` for a direction. If a particular command requires additional keystrokes, then they will be prompted for. Original command summary. --------- ----------------------------- ------------ ------------------------- Β Β a Aim and fire a wand @Β BΒ ~ Bash (object/creature) Β Β b Browse a book Β Β C Change name Β Β cΒ ~ Close a door @Β DΒ ~ Disarm a trap/chest Β Β d Drop an item Β Β E Eat some food Β Β e Equipment list Β Β F Fill lap with oil Β Β f Fire/Throw an item Β Β G Gain new magic spells Β Β i Inventory list Β Β L Locate with map @Β jΒ ~ Jam a door with spike Β Β M Map shown reduced size Β Β lΒ ~ Look given direction @Β R Rest for a period Β Β m Magic spell casting Β Β S Search Mode @Β oΒ ~ Open a door/chest @Β TΒ ~ Tunnel in a direction Β Β p Pray Β Β V View scoreboard Β Β q Quaff a potion Β Β = Set options Β Β r Read a scroll Β Β ? Command quick reference @Β s Search for trap or door Β Β { Inscribe an object Β Β t Take off an item @Β -Β ~ Move without pickup Β Β u Use a staff Β Β .Β ~ Run in direction Β Β v Version, credits and manual Β Β / Identify a character Β Β w Wear/Wield an item Β Β CTRL-K Quit the game Β Β x Exchange weapon @Β CTRL-P Repeat the last message Β Β < Go up an up staircase Β Β CTRL-X Save character and quit Β Β > Go down a down staircase @Β ~ for movement --------- ----------------------------- ------------ ------------------------- Roguelike command summary. ------------ ------------------------- ------------ ----------------------- Β Β cΒ ~ Close a door Β Β C Character description Β Β d Drop an item @Β DΒ ~ Disarm a trap/chest Β Β e Equipment list Β Β E Eat some food @Β fΒ ~ Force/bash item/monster Β Β F Fill lamp with oil Β Β i Inventory list Β Β G Gain new magic spells @Β oΒ ~ Open a door/chest Β Β P Peruse a book Β Β p Pray Β Β Q Quit the game Β Β q Quaff a potion @Β R Rest for a period Β Β r Read a scroll @Β SΒ ~ Spike a door @Β s Search for trap or door Β Β T Take off an item Β Β t Throw an item Β Β V View scores Β Β v Version, and manual Β Β W Where: locate self Β Β w Wear/Wield an item Β Β X Exchange weapon Β Β xΒ ~ Examine surroundings Β Β Z Zap a staff Β Β z Zap a wand Β Β # Search Mode Β Β = Set options Β Β < Go up an up staircase Β Β / Identify a character Β Β > Go down a down stair @Β CTRL-P Previous message review Β Β { Inscribe an object @Β -Β ~ Move without pickup Β Β ? Type this page @Β CTRLΒ ~ Tunnel in a direction Β Β CTRL-X Save game and exit @Β SHFTΒ ~ Run in direction @Β ~ for movement ------------ ------------------------- ------------ ----------------------- ### 6.1 Special Keys Certain commands may be entered at any time input is accepted. The special character `CTRL+R`, entered as a single keystroke, will always refresh the screen. This may be used at any prompt for input, and is otherwise ignored. When playing on a UNIX or similar system, then there are some additional special characters used by *moria*. The special character `CTRL+C` will interrupt *moria*, and asks the player if they really want the character to die and quit the game. Should the player choose for the character not to die, *moria* merely continues as before, except that resting, running, repeated commands, etc will be terminated. The game can be suspended with `CTRL+Z`, and return to the original command shell. In this case, *moria* is not terminated, and may be restarted at any time from the shell. Alternatively, the special command `!` is available to run any normal shell command. When it is complete, *moria* will restart. For many input requests or queries, the special character `ESCAPE` will abort the command. For the "`-more-`" message prompts, any of `SPACE`, `ESCAPE`, `RETURN` (`CTRL+Shift+M`), or `LINEFEED` (`CTRL+J`) can be used to continue after pausing to read the displayed message. It is possible to give control character commands in two keystrokes, by pressing a `^` followed by the appropriate letter of the alphabet. This is useful when running *moria* in circumstances where control characters are intercepted by some external process, or by the operating system. ### 6.2 Direction For the original style command set, a direction is given by a digit which is in the appropriate orientation on the keypad. For the roguelike command set, a direction is given by one of the letters `hykulnjb`. Again, the relative position of the keys on the keyboard gives a clue as to the direction. The digit `5` for the original commands, and the period `.` for roguelike commands, is a null direction indicator. This means to stay in one place, or when in a look command to look in all directions. +---------------------------+---------------------------+ | | | | Original Directions | Roguelike Directions | | | | | \ | / | \ | / | | 7 8 9 | y k u | | | | | - 4 6 - | - h l - | | | | | 1 2 3 | b j n | | / | \ | / | \ | | | | +---------------------------+---------------------------+ Movement is accomplished by specifying a direction immediately. Simply press the appropriate key and the character will move one step in that direction. The character can only move onto and through floor spots, and only if they contain no creatures or obstructing objects such as a closed door. Other commands that require a direction will prompt for it. Moving the character one step at a time can be time consuming and boring, so a faster method has been supplied. For the original keypad based command set, by using the Run command `.`, the character may move in a direction until something interesting happens. For instance, by pressing the period key `.` followed by the direction 8, the character would continue to move up the screen, only coming to a stop after at least one condition is satisfied. For the roguelike command set, typing a shifted directional letter will move the character in that direction until something interesting happens. The stopping conditions are described more completely in the run command description below. ### 6.3 Command Counts Some commands can be executed a fixed number of times by preceding them with a count. Counted commands will execute until the count expires, or until any key is pressed, or until something significant happens, such as being attacked. Thus, a counted command doesn't work to attack another creature. While the command is being repeated, the number of times left to be repeated will flash by on the command line at the bottom of the screen. To give a count to a command in the roguelike mode, type the number in digits, then the command. A count of zero defaults to a count of 99. To give a count to a command in the original mode, press `#`, followed by the digits. To count a movement command (which is itself a digit), type a space after the number, and then give the command. Counted commands are very useful for searching or tunneling, as they automatically terminate on success, or if the character is attacked. A counted command or a Run can be terminated a counted command by pressing any key. This character is ignored, but it is safest to use a `SPACE` or `ESCAPE` which are always ignored as commands. ### 6.4 Selection of objects Many commands will also prompt for a particular object to be used. For example, the command to read a scroll will ask which of the scrolls that the character is carrying is to be read. In such cases, the selection is made by pressing a key for that letter of the alphabet; when selecting from the backpack, the player may also type a digit to select the item whose inscription is that digit. The prompt will indicate the possible letters, and will also allow the `*` key to be pressed, which causes all of the available options to be described (e.g. it lists all of the scrolls that can be read). The particular object may be selected by an upper case or a lower case letter. If lower case or a digit is used, the selection takes place immediately. If upper case is used, then the particular option is described, and you are given the option of confirming or retracting that choice. Upper case selection is thus safer, but requires an extra keystroke. ### 6.5 Command descriptions In the following command descriptions, the original style key is given. If the roguelike key for that command is different, then it will be shown following the original key. #### `B (f ) - Bash.` The bash command includes breaking open doors and chests, or bashing an opponent. The bashing ability increases with weight and strength. In addition, when bashing an opponent, it will either perform a body bash, or, when wielding a shield, perform a shield bash which is more effective. Bashing a door can throw the character off-balance, but this will not generally be a problem. Doors that have been jammed closed with spikes can only be opened by bashing. Locked doors may also be bashed open. Bashing a door open will permanently break it. Bashing a creature affects both the character and the opponent. Depending on dexterity, the character may or may not be thrown off-balance allowing free moves to the opponent. If the bash is successful, the opponent may be thrown off-balance, thus giving the character some free hits or a chance to flee. Huge creatures such as ancient dragons will be difficult or even impossible to bash successfully. A character automatically performs a shield bash instead of a body bash, when they are currently wearing a shield. A shield bash adds the damage of a shield to that of the bash, so it is more effective. Size and material both affect the damage that a shield will do. Bashing can be done with a command-count, but if the character is town off-balance, the count will be reset straight away. #### `C - Display character (on screen or saving to a file.)` This command allows the player to either display the character details on the terminal screen, or to save an entire character info listing to a file. The character’s history, equipment, and inventory list are also included when saving to a file. #### `D - Disarm a trap.` The character can attempt to disarm floor traps, or trapped chests. When the character tries and fails to disarm a trap, there is a chance that the blunder will set it off. Traps on chests can only be disarmed after firstly finding the trap with the search command. This command can have a count. #### `E - Eat some food.` A character must eat occasionally to remain effective. As a character grows hungry, a message will appear at the bottom of the screen saying "`Hungry`". When a character remains hungry for too long, they will become weak, and eventually start fainting, and finally die of starvation. #### `F - Fill a lamp or lantern with oil.` When the character is using a lamp for a light source, and has a flask of oil in the pack, they may refill the lamp by using this command. A lamp is capable of a maximum of 15000 turns of light, and each flask has 7500 turns of oil contained in it. #### `G - Gain new spells.` This command causes the character to learn new spells. When the character is able to learn some spells, the word "`Study`" appears on the status line at the bottom of the screen. Mages, rogues, and rangers must have the magic books containing new spells to be able to learn them. Priests and Paladins are given their prayers by their gods, and hence do not need a holy book before learning the prayers in it. They do need the book in order to use the prayers. #### `L (W) - Location on map.` The location command allows the player to look at all parts of the current dungeon level. The displayed view of the dungeon is shifted to bring the character’s current position as close to the center as possible. The map can then be shifted using any of the eight possible directions. Each shift moves the view point by one half screen. The top line displays a map section number, each map section having a height and width one half that of the display, and indicates the direction of the display from the character’s current position. When this command is exited and the character is not on the display, then the display is centered again. #### `M - Map shown reduced size.` This command will show the entire map, reduced by a factor of nine, on the screen. Since nine places map into every character on the screen, only the major dungeon features will be visible. This is especially useful for finding where the stairs are in relation to the character’s current position. It is also useful for identifying unexplored areas. #### `R - Rest for a number of turns.` The character may rest one turn with the null movement command. Resting for longer periods of time is accomplished by using the Rest command, followed by the number of turns to rest. Resting will continue until the specified duration has expired, or something to wake the character happens, such as a creature wandering by, or getting hungry, or some disability like blindness expiring. It is sometimes a good idea to rest a beat-up character until they regain some of their hit-points, but be sure to have plenty of food if you rest often. The character can be awakened by pressing any key. Space is best, since if the rest ends just before the character is typed, the space is ignored as a command. It is also possible to rest by typing the count first, and using either the Rest or the null movement command. When `*` is given for the rest count, the character will rest until both hit-points and mana reach their maximum values. As above, the character will immediately be awakened if anything interesting happens. #### `S (#) - Search mode toggle.` The Searching toggle will take the character into and out of search mode. When first pressed, the message "`Searching`" will appear on the status line at the bottom of the screen. The character is now taking two turns for each command, one for the command and one turn to search. This means that the character is taking twice the time to move about the dungeon, and therefore twice the food. If a creature should happen by or attack, search mode will automatically toggled off. Search mode can also be turned off by again pressing the `S` (or `#`) key. #### `T (CTRL+) - Tunnel through rock.` Tunneling (Mining) is a very useful art in the dungeons of *moria*. There are four kinds of rock: 1. Permanent Rock 2. Granite Rock 3. Magma Intrusion 4. Quartz Veins Permanent Rock is exactly that, permanent. Granite is very hard, therefore hard to dig through, and contains no valuable metals. Magma and Quartz veins are softer and sometimes bear valuable metals and gems, shown as a `$` or a `*` symbol. When the character can't move over these symbols, it means they are embedded in the rock, and the tunnel command must be used to dig them out. There is a game option which causes magma and quartz to be displayed differently than other rock types. Tunneling can be **VERY** difficult by hand, so when digging be sure to wield either a shovel or a pick. Magical shovels and picks can be found which allow the wielder to dig much faster than normal, and a good strength also helps. Tunneling can have a count. #### `V - View scoreboard.` This command will display the contents of the score board on the screen. On a multi-user system, pressing `V` the first time will show only those scores from the score board that belong to the current user, and pressing `V` again will show all users’ scores. #### `a (z ) - Aim a wand.` Wands must be aimed in a direction to be used. Wands are magical devices and therefore use the Magical Devices ability of the character. They will either affect the first object/creature encountered, or affect anything in a given direction, depending upon the wand. An obstruction such as a door or wall will generally stop the effects of a wand from traveling further. #### `b (P) - Browse a book.` Books can only be read if the character is of its realm. Therefore a magic user could read a magic book, but not a holy book. Warriors will not be able to read either kind of book. When the browse command is used, all of the spells or prayers contained therein are displayed, along with information such as their level, the amount of mana used up in casting them, and whether or not you know the spell or prayer. There are a total of 31 different magical spells in four books, and 31 different prayers in four books. #### `c - Close a door.` Non-intelligent and certain other creatures will not be able to open a door. Therefore shutting doors can be a life saver. To close a door, the character must be adjacent to an open door, and broken doors cannot be closed. Bashing a closed door will break it and leave it permanently open. #### `d - Drop an object from the inventory.` Objects can be dropped onto the floor beneath the character if that floor spot does not already contain an object. Doors and traps are considered objects in this sense. If the character has several objects of the same kind, the player will be prompted for dropping one or all of them. It is possible to directly drop things which you are wielding or wearing. #### `e - Display a list of equipment being used.` Use the Equipment command to display a list of objects currently being worn or wielded by the character. Each object has a specific place where it is placed, and that only one object of each type may be used at any one time, excepting rings of which two can be worn, one on each hand. #### `f (t ) - Fire/Throw an object/use a missile weapon.` Any object carried by the character can be thrown. Depending upon the weight of an object, it may travel across a room or drop down beside the character. When a stackable object like an arrow is thrown, only one will be thrown at a time. When throwing at a creature, the chance of hitting the creature is determined by the character’s pluses to hit, the ability at throwing, and the object’s pluses to hit. Once the creature is hit, the object may or may not do any actual damage to it. Certain objects in the dungeon can do great amounts of damage when thrown, but it’s for the player to figure out the obscure ones. Oil flasks are considered to be lit before thrown; therefore, they will do fire damage to a creature if they hit it. To use a bow with arrows, simply wield the bow and throw the arrows. Extra pluses to damage and hitting are gained by wielding the proper weapon and throwing the corresponding ammo. A heavy crossbow with bolts for example, is a killer... #### `i - Display a list of objects being carried.` This command displays an inventory of all objects being carried, but not currently in use (e.g. being worn or wielded). The character may carry up to 22 different kinds of objects in the pack. Depending upon strength, the character will be able carry many identical objects before hitting the weight limit. After the weight limit is exceeded the character will move slower due to the encumbrance. #### `j (S ) - Jam a door with an iron spike.` Most humanoid and many intelligent creatures can simply open a closed door, and can eventually get through a locked door. Therefore the character may also spike a door in order to jam it. Each spike used on a door will increase its strength, although as more spikes are jammed, the less effect each additional spike has. It is very easy to jam a door so much as to make it impossible for the character to bash it down, so spike doors wisely. The bigger a creature is, the easier it can bash a door down. Therefore twenty or more spikes might be necessary to slow down a dragon, where one spike would slow down a Kobold. This command can be counted. #### `l (x ) - Look in a direction.` The Look command is useful in identifying the exact type of object or creature shown on the screen. Also, if a creature is on top of an object, the look command will describe both. Creatures and objects can be seen up to 200 feet away (20 spaces or tiles). The Look command can be used freely without the creatures getting a turn to move against the character. Looking in a particular direction sees everything within a cone of vision which just overlaps the cones of the two adjacent directions. Looking with the null direction `5` (or `.`) sees everything which there is to be seen. The character is able to access monster memories with this command. When the character sees a creature, the player has the option to view a short paragraph of information about prior experiences with that creature. See also the section on being attacked. #### `m - Cast a magic spell.` To cast a spell, a character must have previously learned it, and must also have in the inventory a magical book from which the spell may be read. Each spell has a chance of failure which starts out fairly large but decreases as a character gains levels. If a character does not have enough mana, the chance of failure is greatly increased, and gambles on losing a point of constitution. The player will be prompted for confirmation before trying to cast a spell when there is insufficient mana. Since a character must read the spell from a book, they cannot be blind or confused when casting a spell, and there must be some light present. #### `o - Open a door, chest, or lock.` To open an object such as a door or chest, the Open command can be used. If the object is locked, the Open command will attempt to pick the lock, based on the character’s disarm ability. If an object is trapped and the character opens it, the trap will be set off. This command can be counted, because the locked object may require several tries to open. #### `p - Read a prayer.` To pray effectively, a character must have learned the prayer, and must also have in the inventory a holy book from which the prayer may be read. Each prayer has a chance of being ignored which starts out fairly large but decreases as a character gains levels. If a character does not have enough mana, the chance of failure is greatly increased, and gambles on losing a point of constitution. The player will be prompted for confirmation before trying to pray when there is insufficient mana. Since a character must read the prayer from a book, they cannot be blind or confused when praying, and there must be some light present. #### `q - Quaff a potion.` To drink a potion use the Quaff command. A potion affects the character in some manner. The effects of the potion may be immediately noticed, or they may be subtle and unnoticed. #### `r - Read a scroll.` To read a scroll use the Read command. Most scroll spells either affect the player or the area around the character; a few cases such as identify scrolls act on other objects. Two scrolls, the identify scroll and the recharge scroll, have titles which can be read without setting them off, and by pressing ESCAPE can be saved for future use. #### `s - Search general area one turn.` The Search command can be used to locate hidden traps and secret doors about the character. More than a single turn of searching will be required in most cases. The character should always search a chest before trying to open it because they are generally trapped. This command can be counted, and the counted search ends as soon as anything is found. #### `t (T) - Take off a piece of equipment.` Use the Take Off command to remove an object from use, and return it to the character’s pack, or inventory. Occasionally the character will have a cursed item which cannot be removed. Cursed items are always bad, and can only be taken off after removing the curse. #### `u (Z) - Use/Zap a staff.` The Use command will activate a staff. Like scrolls, most staves have an area effect. Because staves are generally more powerful than most other items, they are also harder to use correctly. #### `v - Display current version of game.` The Version command displays the credits for the current version of *moria*. #### `w - Wear or wield an item being carried.` To wear or wield an object in the character’s inventory, use the Wear/Wield command. If another object is already in use for the same function, it is automatically removed first; if the character is wearing two rings, the player is given a choice of which one to remove. An object’s bonuses cannot be gained until it is worn or wielded. #### `x (X) - Exchange primary and secondary weapons.` A secondary weapon is any weapon which may be needed often. Instead of searching through the inventory, the character may use the exchange command to keep the weapon ready (e.g. slung over the character’s back). For instance, if the bow was going to be used primarily, but needed a sword for close combat, the character could wield the sword, use the exchange command to make it the secondary weapon, then wield the bow. If the sword was suddenly needed, simply use the exchange command to switch between the bow and the sword. #### `/ - Identify a character shown on screen.` Use the identify command to find out what a symbol displayed on the screen stands for. For instance, by pressing `/.`, the player can find out that the `.` stands for a floor spot, or tile. When used with a creature, the identify command will only tell what class of creature the symbol stands for, not the specific creature; therefore, use the look command for this information. When identifying a symbol for a creature in the character’s monster memory, the player has the option to view a paragraph of information on those creatures identified by the given character. Several creatures may be identified in this way. Typing ESCAPE after the paragraph for any creature will abort back to command level. See also the section on being attacked. #### `? - Display a list of commands.` The `?` command displays a quick reference help page on the screen. #### `- - Move without pickup.` This is followed by a move command, and causes the character to move over an object without automatically picking it up. This command can be counted. #### `= - Set options.` This is a free move, to set various game options in *moria*. The available options are: 1. Cut known corners when running. This is on by default, and the only reason for switching it off would be if the character had the search mode on and wished to look for secret doors in the extremity of every corner. 2. Examine potential corners when running. This is on by default, and allows the character to run along an unknown curving corridor. If however, the character is running from a creature, the player may wish to switch this option off, because the creature will cut the corner. 3. Display self during a run. This is off by default, which gives faster screen updating. 4. Stop when map sector changes. This is off by default, but can be switched on to stop running whenever a new part of the dungeon appears in view. 5. Treat open doors as empty space while running. This is off by default, in which case the character stops whenever they run up to an open door. 6. Prompt to pick up objects. This is off by default, in which case stepping over an object automatically causes the character to pick it up. With the option on, the player gets prompted in all such cases with a description of the object to see if it should be put into the character’s pack. 7. Roguelike command set. This option controls the command set in use. It is off by default. 8. Show weights in inventory. This is off by default: switching it on causes the inventory and equipment listings to include the weight of all objects. This may be useful to know if the character’s pack is getting too heavy. 9. Highlight and notice mineral seams. This is off by default. Switching it on causes quartz and magma to be displayed as `%` instead of `#`; also, it causes the look command to treat them as interesting objects. This is handy when mining. Setting this option does not immediately highlight all minerals, but only those which are subsequently displayed. To display all minerals, just move the map around a bit with the *Where* (or *Locate*) command. 10. Beep for invalid character. This is on by default. When this option is on, the program will beep for most invalid characters, such as trying to choose a spell that hasn't been learned yet. When off, there are no such beeps. 11. Display rest/repeat counts. This is on by default. When on, the program will progressively display the remaining turns left while resting, and for repeated commands. For those trying to play over a very slow connection, or for those playing on very slow computers, turning this off will make resting and repeated commands work much faster. 12. Disable haggling. This is off by default. When on, all haggling will be disabled at the cost of a 10 percent tax on the final price on items you would otherwise have had to haggle for, and all prices displayed in the stores will be the actual prices you have to pay. The setting of all these options persist in the save file, even after the character dies. #### `^P - Previous message.` The Control-P command will redisplay the last message displayed on the message line at the top of the screen. A second such command will display all of the saved messages. This command can be given a count to specify the number of previous messages to View. At present, only 22 messages are saved. #### `^K (^Q) - Kill the character.` To exit the game without saving the character (i.e. killing the character) use the Control-K command. Once exited in this manner, the character is nonrecoverable. #### `^X - Save the character and exit the game.` To save the game so that it can be restarted later, use the Control-X command. Save files will also be generated if the game crashes due to a system error. When the character dies, a reduced save file is produced containing only the monster memory, and the option settings. #### `{ - Inscribe an object.` This command can be used to inscribe any short string on an object. Inscriptions are limited to twelve characters. The inscription applies only to the particular object, it is not automatically transferred to all similar objects. Under certain circumstances, *moria* will itself inscribe objects: if they have been discovered to be cursed or enchanted, or if they have been sampled without being identified. In this last case, *moria* does in fact carefully inscribe every such item. If the inscription on an item is a single digit, that digit can be used to refer to it when using, wearing, or wielding an item from inside the pack. For example, if a shovel is kept in the pack with the inscription `1`, you can switch to the shovel by wielding item `1` without checking the full inventory list to find out which item the shovel is. #### `! - Shell out of game.` Use the Shell command `!` to temporarily exit the game to execute UNIX or DOS commands. The game can be reentered by typing exit to end the spawned process. #### `< - Go up an up staircase.` If the character moves onto an up staircase the `<` command will take the character to go up one level (e.g. one level of dungeon depth). There is always one staircase going up on every level except for the town level (this does not mean it’s easy to find). Going up a staircase will always take the character to a new dungeon area except for the town level, which remains the same for the duration of the current character’s game. #### `> - Go down a down staircase.` If the character is on top of a down staircase the `>` command may be used to go down one level of dungeon depth. There are always two or three staircases going down on each level, except the town level which has only one. Going down will always take the character to a new dungeon area. #### `. (shift ) - Move in direction.` The Run command will move the character in the indicated direction until either a choice needs to be made between two directions, or something interesting happens. There are options which determine behavior at corners, and at screen boundaries. More precisely, the conditions which stop a run are as follows: 1. A creature appears on the screen, one already on the screen moves, or a creature attacks or casts a spell at the character. 2. The character moves next to an object, or a feature such as a door or trap. 3. The character comes to the end of open space, or the end of a passage, or a junction of passages, or a hole in a wall. 4. Anything typed during a run causes the run to stop. The keyboard key causing this to occur is ignored. It is best to use a space, which is ignored as a command, just in case the run stops just before the key is pressed. 5. Various changes of state, such as recovery from fear or loss of heroism, will stop a run. Corners are more complex. A corner allows a choice between adjacent rectangular and diagonal directions. If the character can see walls which ensure that the diagonal gives a faster traversal, then action is determined by the "cut corners" options. If it is set, then the character moves diagonally through the corner. This gives maximum speed (as is nice when fleeing a hidden creature). On the other hand, this option should not be set if more careful coverage is desired (as when searching) so that the character take two moves through the corner. At a potential corner, where walls are not yet visible ahead of the rectangular direction, the "examine corners" option is considered. If set, the character will move straight into the corner, which will light up all the corner and so determine where to go from there. This allows the character to follow corners in new passages. If the option is not set, the character stops. This allows highly cautious running where the character stops at all potential choice points. If the character moves off the screen while running, then a new section of the dungeon is displayed and the run continues. However, if the "stop when map changes" option is set, the character will stop. Again, this is an option for nervous players; after all, there may be a dragon on the new screen, and running into a dragon can be bad for the character’s health. ## 7. The Town Level The town level is where the character will begin the adventure. The town consists of six buildings, each with an entrance, some townspeople, and a wall which surrounds the town. At the start of the game it will be daytime in the town, but later on the character may return to find that darkness has fallen. (Note that some spells may act differently in the town level.) ### 7.1 Townspeople The town contains many different kinds of people. There are the street urchins, young children who will mob an adventurer for money, and seem to come out of the woodwork when excited. Blubbering Idiots are a constant annoyance, but not harmful. Public drunks wander about the town singing, and are of no threat to anyone. Sneaky rogues hang about watching for a likely victim to mug. And finally, what town would be complete without a swarm of half-drunk warriors, who take offense or become annoyed just for the fun of it. Most of the townspeople should be avoided by the largest possible distance when wandering from store to store. Fights will break out though, so be prepared. Since the character grew up in this world of intrigue, no experience is awarded for killing on the town level. ### 7.2 Supplies The character will begin the adventure with some supplies already in the backpack. Use the Inventory `i` command to check what these supplies are. It will be necessary to buy other supplies before continuing into the dungeon, however, so be sure to enter each of the stores. ### 7.3 Town Buildings The character may enter any of the stores, if they are open, and barter with the owner for items that can be afforded. When bartering, the player enters prices you will pay (or accept) for some object. An absolute amount can be entered, or an amount preceded with a plus or minus sign to give a positive or negative increment on the previous offer. When an increment has been previously given, pressing RETURN will use the previous increment. But be warned that the owners can easily be insulted, and may even throw the character out for a while if you insult them too often. To enter a store, simply move onto the entrance represented by the numbers `1` through `6`. If the character consistently bargains well in a store, that is, you reach the final offer much more often than not, then the store owner will eventually recognize that the character is a superb haggler, and will go directly to the final offer instead of haggling with you. Items which cost less than 10 gold pieces do not count, as haggling well with these items is usually either very easy or almost impossible. Once inside a store, the store inventory will appear on the screen along with a set of options for the character. The store’s inventory can be browsed if there is more than one page to View. Items can be sold to, or purchased from the character’s backpack. Regular inventory and equipment commands can be executed in a store. Although the wear, take off, and exchange commands do not appear in the options, they will still work, but were excluded to keep the options simple. Stores do not always have everything in stock. As the game progresses, they may get new items, so check from time to time. Also, if you sell them an item, it may get sold to a customer while you are adventuring, so don't always expect to be able to get back everything you have sold. Store owners will not buy harmful or useless items. If an object is unidentified, they will pay some base price for it. Once they have bought it they will immediately identify the object. If it is a good object, they will add it to their inventory. If it was a bad bargain, they simply throw the item away. In any case, the player may receive some knowledge of the item if another is encountered. #### `The General Store` The General Store sells foods, drinks, some clothing, torches, lamps, oil, shovels, picks, and spikes. All of these items, and some others, can be sold back to the General store for money. The entrance to the General Store is a `1`. #### `The Armory` The Armory is where the town’s armor is fashioned. All sorts of protective gear may be bought and sold here. The entrance to the Armory is a `2`. #### `The Weaponsmith's Shop` The Weaponsmith’s Shop is where the town’s weapons are fashioned. Hand and missile weapons may be purchased and sold here, along with arrows, bolts, and shots. The entrance to the Weaponsmith’s is a `3`. #### `The Temple` The Temple deals in healing and restoration potions, as well as bless scrolls, word of recall scrolls, some approved priestly weapons, etc. The entrance to the Temple is a `4`. #### `The Alchemy Shop` The Alchemy Shop deals in all manner of potions and scrolls. The entrance to the Alchemy Shop is a `5`. #### `The Magic User's Shop` The Magic User’s Shop is the most expensive of all the stores. It deals in all sorts of rings, wands, amulets, and staves. The entrance to the Magic Shop is a `6`. ## 8. Within The Dungeon Once the character is adequately supplied with food, light, armor, and weapons, it is time to enter the dungeon. Move on top of the `>` symbol and use the down `>` command. The character enters a maze of interconnecting staircases and finally passes through a one-way door. The character is now on the first level of the dungeon (`50 feet`), and must survive many horrible and challenging encounters to find the treasure lying about. There are two sources for light once inside the dungeon: permanent light which has been magically placed within rooms, and a light source carried by the character. If neither is present, the character will be unable to map or see any attackers. Lack of light will also affect searching, picking locks, disarming, and casting spells. A character must wield a torch or lamp in order to supply their own light. Once a torch or lamp has only 50 or less turns left before burning out, the message "`Your light is growing faint`" will be displayed at random intervals. Once a torch is burnt out, it is useless and can be dropped. A lamp or lantern can be refilled with oil by using the Fill `F` command, however the character must be carrying extra oil to refill a lantern. ## 9. Attacking and Being Attacked Attacking is simple in *moria*. When moving into a creature, the character attacks it. The character can attack from a distance by firing a missile, or by magical means such as aiming a wand. Creatures attack in the same way; if they move into the character, they attack. Some creatures can also cast spells from a distance, and others can breathe fire or worse from a distance. Creatures moving in walls cannot be attacked by wands and other magic attacks normally stopped by walls. The character can attack a creature in a wall normally though by trying to move into the wall space containing the creature. However, in order to attack an invisible creature in a wall, the character must tunnel into the wall containing the creature. Trying to move into the wall will bump the character’s head, and they will look quite silly. If the character is wielding a weapon, the damage for the weapon is used when a creature is hit. Otherwise, the character gets two fist strikes. Very strong creatures can do a lot of damage with their fists... The character may have a primary weapon, and a secondary weapon which is kept on the belt or shoulder for immediate use. Weapons can be switched with the exchange command. Be sure to wield the proper weapon when fighting. Hitting a dragon over the head with a bow will simply make it mad, and get the character killed. Missile weapons, such as bows, can be wielded, and then the proper missile, in this case an arrow, can be fired across the room into a target. Missiles can be used without the proper missile weapon, but used together they have a greater range and do far more damage. Hits and misses are determined by ability to hit versus armor class. A hit is a strike that does some damage; a miss may in fact reach a target, but fails to do any damage. Higher armor classes make it harder to do damage, and so lead to more misses. In *Moria*, your character can wear any armor or wield any weapon – it is not limited by race or class. ### 9.1 Monster Memories There are hundreds of different creatures in the mines of *moria*, many of which look the same on the screen. The exact species of a creature can be discovered by looking at it. It is also very difficult to keep track of the capabilities of various creatures. Rather than forcing the player to keep notes, *moria* automatically keeps track of prior experiences with a particular creature. This is called the *monster memory*. The monster memory recalls the particular attacks of each creature which the character has suffered, as well as recalling if the character has observed them to multiply or move erratically, or drop treasure, or many other attributes. If the character has killed enough of a particular creature, or suffered enough attacks, recalling the monster memory may also provide information not otherwise available, such as a armor class or hit dice. These are not explained, but may be useful to give the relative danger of each creature. This memory can be passed on to a new character even after the character dies, by means of a reduced save file. ### 9.2 The Character’s Weapon Carrying a weapon in a backpack doesn't do much good. The character must wield a weapon before it can be used in a fight. A secondary weapon can be kept by wielding it and then using the exchange command. A secondary weapon is not in use, simply ready to be switched with the current weapon if needed. Weapons have two main characteristics, their ability to hit and their ability to do damage, expressed as `(+#,+#)`. A normal weapon would be `(+0,+0)`. Many weapons in *moria* have magical bonuses to hit and/or do damage. Some weapons are cursed, and have penalties that hurt the character. Cursed weapons cannot be taken off until the curse is lifted. *Moria* assumes that the youth of the character spent in the rough environment near the dungeons has taught the relative merits of different weapons, and displays as part of their description the damage dice which define their capabilities. The ability to damage is added to the dice roll for that weapon. The dice used for a given weapon is displayed as `#d#`. The first number indicates how many dice to roll, and the second indicates how many sides they have. A `2d6` weapon will give damage from 2 to 12, plus any damage bonus. The weight of a weapon is also a consideration. Heavy weapons may hit harder, but they are also harder to use. Depending on the strength stat and the weight of the weapon, the character may get several hits in a single turn. Missile booster weapons, such as bows, have their characteristics added to those of the missile used, if the proper weapon/missile combination is used. Also, these weapons will multiply the base damage of the missile by a number from 2 to 4, depending on the strength of the weapon. This multiplier is displayed as `(x#)`. Although the character receives any magical bonuses an unidentified weapon may possess when you wield it, those bonuses will not be added in to the displayed values of to-hit and to-dam on the character sheet. Weapons must be identified before the displayed values reflect the real values used. Finally, some rare weapons have special abilities. These are called ego weapons, and are feared by great and meek. An ego sword must be wielded to receive benefit of its abilities. Special weapons are denoted by the following abbreviations: #### `DF - Defender.` A magical weapon that helps wielders defend themselves, thus increasing their armor class, and protecting them against damage from fire, frost, acid, lightning, and falls. This weapon also will increase stealth, let the character see invisible creatures, protect from paralyzation attacks, and help regenerate hit-points and mana faster. As a result of the regeneration ability, the character will use up food faster than normal while wielding such a weapon. #### `FB - Frost Brand.` A magical weapon of ice that delivers a cold critical to heat-based creatures. It will inflict one and a half times the normal damage when used against a heat-based creature. #### `FT - Flame Tongue.` A magical weapon of flame that delivers a heat critical to cold-based creatures. It will inflict one and a half times the normal damage when used against cold-based or inflammable creatures. #### `HA - Holy Avenger.` A Holy Avenger is one of the most powerful of weapons. A Holy Avenger will increase the strength and the armor class of the wielder. This weapon will do extra damage when used against evil and undead creatures, and will also give the ability to see invisible creatures. #### `SA - Slay Animal.` A Slay Animal weapon is a special-purpose weapon whose sole intent is to destroy all the dangerous animals in the world. An animal is any creature natural to the world. Therefore an orc would not be an animal, but a giant snake would be. This will inflict twice the normal amount of damage when used against an animal. #### `SD - Slay Dragon.` A Slay Dragon weapon is a special-purpose weapon whose sole intent is to destroy dragon-kind. Therefore, when used against a dragon, the amount of damage done is four times the normal amount. #### `SE - Slay Evil.` A Slay Evil weapon is a special-purpose weapon whose sole intent is to destroy all forms of evil. When used against an evil creature, either alive or undead, the damage done twice the normal amount. #### `SU - Slay Undead.` A Slay Undead weapon is a special-purpose weapon whose sole intent is to destroy all forms of undead. This weapon is hated and feared by the intelligent undead, for a single blow from this weapon will inflict three times the normal amount of damage. This weapon also gives the ability to see invisible creatures, which is especially useful against undead, since many of them are normally invisible. ### 9.3 Body and Shield Bashes Weight is the primary factor in being able to bash something, but strength plays a role too. After bashing, a character may be off-balance for several rounds depending upon the dexterity stat. Doors can be broken down by bashing them. Once a door is bashed open, it is forever useless and cannot be closed. Chests too may be bashed open, but be warned that the careless smashing of a chest often ruins the contents. Bashing open a chest will not disarm any traps it may contain, but does allow the strong and ignorant to see what is inside. Finally, a creature may be bashed. If a shield is currently being worn, the bash is a shield bash and will do more damage. In either case, a bash may throw an opponent off-balance for a number of rounds, allowing the character to get in a free hit or more. If the character is thrown off-balance, the opponent may get free hits. This is a risky attack. ### 9.4 The Character’s Armor Class Armor class is a number that describes the amount and the quality of armor being worn. Armor class will generally run from about 0 to 60, but could become negative or greater than 60 in rare cases. The larger the armor class, the more protective it is. A negative armor class would actually help the character get hit. Armor protects in three manners. One, it makes the character harder to be hit for damage. A hit for no damage is the same as a miss. Two, good armor will absorb some of the damage that the character would have taken. An armor class of 30 absorbs 15% of damage. Three, acid damage is reduced by wearing body armor. It is obvious that a high armor class is a must for surviving the lower levels of *moria*. Each piece of armor has an armor class adjustment, and a magical bonus. Armor bought in town will have these values displayed with its description. Armor that is found within the dungeon must be identified before these values will be displayed. All armor always has the base armor class displayed, to which the bonus is added. It is always possible to figure this out anyway, by watching the effect it has on the displayed armor class. Armor class values are always displayed between a set of brackets as `[#]` or `[#,+#]`. The first value is the armor class of the item. The second number is the magical bonus of the item which is only displayed if known, and will always have a sign preceding the value. There are a few cases where the form `[+#]` is used, meaning the object has no armor class, only a magical armor bonus if worn. Body armor may also have a `(-#)` displayed in parentheses; this is a penalty to hit, because the bulk of the armor makes it more difficult to swing a weapon freely. Some pieces of armor will possess special abilities denoted by the following abbreviations: #### `RA - Resist Acid.` A character using such an object will take only one-third normal damage from any acid thrown upon them. In addition, armor so enchanted will resist the acid’s effects and not be damaged by it. #### `RC - Resist Cold.` A character using a resist cold object will take only one-third damage from frost and cold. #### `RF - Resist Fire.` A character using a resist fire object will take only one-third damage from heat and fire. #### `RL - Resist Lightning.` A character using a resist lightning object will take only one-third damage from electrical attacks. #### `R - Resistance.` A character wearing armor with this ability will have resistance to Acid, Cold, Fire, and Lightning as explained in each part above. ### 9.5 Crowns Some crowns also have special magical abilities that improve chances in a battle. #### `Crown of Might` This is the great crown of the warriors. This crown increases the strength, dexterity, and constitution of the wearer. Any attempt to paralyze or slow the character will fail. #### `Crown of the Magi` This is the great crown of the wizards. The wearer will have an increased intelligence, and will also be given resistance against fire, frost, acid, and lightning. #### `Crown of Lordliness` This is the great crown of the priests. The wearer will have an increased wisdom and charisma. #### `Crown of Seeing` This is the great crown of the rogues. The wearer will be able to see even invisible creatures, and will have an increased ability to locate traps and secret doors. #### `Crown of Regeneration` This crown will help you regenerate hit-points and mana more quickly than normal, allowing the character to fight longer before needing to rest. The character will use food faster than normal while wearing this crown because of the regenerative effects. #### `Crown of Beauty` This crown looks impressive, and will increase charisma, but is otherwise not useful. ### 9.6 Magic Spells All characters except warriors are able to learn some form of magic spells. There are two kinds of magic in the game: 1. Magic Spells 2. Prayers Magic spells can be casted by mages, rogues, and rangers. Prayers can be prayed by priests and paladins. Each spell/prayer has a minimum level required for learning it, a mana score, which is the mana points required to cast it, and a failure percentage. Mana points are determined by the character’s experience level, and the level of a key stat. For mages, rogues, and rangers, the key stat is intelligence, for priests and paladins the key stat is wisdom. The effect of the key stat is shown by the following table. Stat Level Factor 3-7 0 8-17 1 18-18/49 3/2 18/50-18/69 2 18/70-18/89 5/2 18/90-18/99 3 18/100 4 The mana score is the factor times the experience level plus 1. If the key stat is 7 or less, the mana score will be zero (not one) and the character will not be able to use any spells. When attempting to cast a spell that calls for more mana than the character has, the rate of failure is much greater than normal. The character will faint for a few turns afterward, and there is a chance at damaging health too. #### 9.6.1 Priest Spells Priest spells are received from the character’s deity. When the `G` command is issued to learn new spells, spells are chosen randomly from the spells that the character is able to cast. The character need not have the book the spell is in to learn it, because the god gave it to the character, but the book is needed to cast the spell. Failure percentages and spell effectiveness are based on wisdom for priests and paladins. #### 9.6.1.1 Priest Spell Levels And Mana This is a table of all the spells, with the mana and level of achievement for Priests and Paladins. Priest Paladin (Beginner's Handbook) Lv Mana Lv Mana A Detect Evil 1 1 1 1 B Cure Light Wounds 1 2 2 2 C Bless 1 2 3 3 D Remove Fear 1 2 5 3 E Call Light 3 2 5 4 F Find Traps 3 3 7 5 G Detect Doors/Stairs 3 3 7 5 H Slow Poison 3 3 9 7 (Words of Wisdom) A Blind Creature 5 4 9 7 B Portal 5 4 9 8 C Cure Medium Wounds 5 4 11 9 D Chant 5 5 11 10 E Sanctuary 7 5 11 10 F Create Food 7 5 13 10 G Remove Curse 7 6 13 11 H Resist Heat and Cold 7 7 15 13 (Chants and Blessings) A Neutralize Poison 9 6 15 15 B Orb of Draining 9 7 17 15 C Cure Serious Wounds 9 7 17 15 D Sense Invisible 11 8 19 15 E Protection from Evil 11 8 19 15 F Earthquake 11 9 21 17 G Sense Surroundings 13 10 23 17 H Cure Critical Wounds 13 11 25 20 I Turn Undead 15 12 27 21 (Exorcisms and Dispellings) A Prayer 15 14 29 22 B Dispel Undead 17 14 31 24 C Heal 21 16 33 28 D Dispel Evil 25 20 35 32 E Resist Poison Gas 31 45 -- -- F Glyph of Warding 33 24 37 36 G Holy Word 39 32 39 38 #### 9.6.1.2 Priest Spell Descriptions This is a short description of each of the spells, listed alphabetically. #### `Bless` Improves armor class and fighting ability for a short period of time. #### `Blind Creature` Blinds a creature for a short period of time. #### `Call Light` Lights up an area. #### `Chant` Improves armor class and fighting ability for a medium period of time. #### `Create Food` Causes a food item to be dropped at the character`s feet. #### `Cure Critical Wounds` Cures a very large number of hit-points. #### `Cure Light Wounds` Cures a small number of hit-points. #### `Cure Medium Wounds` Cures a medium number of hit-points. #### `Cure Serious Wounds` Cures a large number of hit-points. #### `Detect Doors/Stairs` Finds all the doors and stairs on the screen. #### `Detect Evil` Finds all the evil creatures on the screen. #### `Dispel Evil` Attempts to destroy the evil creature. #### `Dispel Undead` Attempts to destroy the undead creature. #### `Earthquake` Randomly toggles corridors into walls and vice versa. #### `Find Traps` Locates all the traps on the screen. #### `Glyph of Warding` Leaves a *Glyph* that monsters won`t pass over. Appears as a trap. #### `Heal` Restores 200 Hit Points. #### `Holy Word` Dispels evil, removes fear, cures poison, restores 1000 hit-points, restores all stats, and invulnerability for 3 turns. #### `Neutralize Poison` Cures you of poison. #### `Orb of Draining` Offensive spell that drains levels from monsters. #### `Portal` Teleports the character a short distance away. #### `Prayer` Improves armor class and fighting ability for a long period of time. #### `Protection from Evil` Causes evil creatures to do less damage. #### `Remove Curse` Removes {damned} objects that you are welding. #### `Remove Fear` Negates the fear placed on the character by an enemy. #### `Resist Heat and Cold` Reduce damage from heat or cold attacks. #### `Resist Poison Gas` Permanently reduces damage from poison gas attacks. #### `Sanctuary` Causes neighboring monsters to fall asleep for a while. #### `Sense Invisible` Finds all invisible creatures on the screen. #### `Sense Surroundings` Maps the dungeon appearing on the screen. #### `Slow Poison` Reduces the rate hit-points are lost due to poison. #### `Turn Undead` Attempts to cause undead creatures to flee. #### 9.6.2 Mage Spells Mage Spells are more powerful and offensive in nature than priest spells. This offsets the fact that magicians are generally weaker than any other class. Because mage spells are learned though study, the correct magic book must be present in the character’s pack to learn and cast a spell. Learning spells can be banked up. For example: a second level mage who had learned Magic Missile and can learn one more spell. If the mage does not wish to learn Detect Monsters, Phase Door or Light Area, the mage can wait until the third level and learn both the Cure Light Wounds and Stinking Cloud, both third level spells. Spell failure and effectiveness is based on intelligence for mages, rangers, and rogues. Rangers can learn all but the most powerful offensive spell. Rogues cannot learn any offensive spell. #### 9.6.2.1 Mage Spell Levels And Mana Mage Ranger Rogue (Beginners-Magick) Lv Mana Lv Mana Lv Mana A Magic Missile 1 1 3 1 -- -- B Detect Monsters 1 1 3 2 5 1 C Phase Door 1 2 3 2 7 2 D Light Area 1 2 5 3 9 3 E Cure Light Wounds 3 3 5 3 11 4 F Find Hidden Traps/Doors 3 3 5 4 13 5 G Stinking Cloud 3 3 7 5 -- -- (Magik I) A Confusion 3 4 7 6 15 6 B Lightning Bolt 5 4 9 7 -- -- C Trap/Door Destruction 5 5 9 8 17 7 D Sleep I 5 5 11 8 19 8 E Cure Poison 5 5 11 9 21 9 F Teleport Self 7 6 13 10 -- -- G Remove Curse 7 6 13 11 23 10 H Frost Bolt 7 6 15 12 -- -- I Turn Stone to Mud 9 7 15 13 -- -- (Magik II) A Create Food 9 7 17 17 25 12 B Recharge Item I 9 7 17 17 27 15 C Sleep II 9 7 21 17 -- -- D Polymorph Other 11 7 21 19 -- -- E Identify 11 7 23 25 29 18 F Sleep III 13 7 23 20 -- -- G Fire Bolt 15 9 25 20 -- -- H Slow Monster 17 9 25 21 -- -- (Mages Guide to Power) A Frost Ball 19 12 27 21 -- -- B Recharge Item II 21 12 29 23 -- -- C Teleport Other 23 12 31 25 -- -- D Haste Self 25 12 33 25 -- -- E Fire Ball 29 18 35 25 -- -- F Resist Poison Gas 31 45 -- -- -- -- G Word of Destruction 33 21 37 30 -- -- H Genocide 37 25 -- -- -- -- Note: Rangers don't get spells until 3rd level, Rogues 5th level. #### 9.6.2.2 Mage Spell Descriptions #### `Confusion` Confuses a monster for a short time. #### `Create Food` Causes a food item to be dropped at the character`s feet. #### `Cure Light Wounds` Restores a small number of hit-points. #### `Cure Poison` Neutralizes the poison running through the character`s veins. #### `Detect Monsters` Displays all the monsters on the screen. #### `Find Hidden Traps/Doors` Locates all the secret traps and doors. #### `Fire Ball` Shoots a ball of flame toward a monster. #### `Fire Bolt` Shoots a bolt of flame toward a monster. #### `Frost Ball` Shoots a ball of frost toward a monster. #### `Frost Bolt` Shoots a bolt of frost toward a monster. #### `Genocide` Destroys a particular monster on the level. #### `Haste Self` Causes the character to move faster temporarily. #### `Identify` Identifies an unknown object in the pack. #### `Light Area` Illuminates the area with light. #### `Lightning Bolt` Shoots a bolt of lightning at the enemy. #### `Magic Missile` Traditional bolt of magic used to damage enemies. #### `Phase Door` Teleports the character a short distance. #### `Polymorph Other` Polymorphs a monster into a different creature. #### `Recharge Item I and II` Recharges a staff, or wand. #### `Remove Curse` Allows {damned} items to be taken off. #### `Resist Poison Gas` Permanently reduces damage from poison gas attacks. #### `Sleep I` Causes a specified monster to fall asleep. #### `Sleep II` Causes neighboring monsters to fall asleep. #### `Sleep III` Causes all monsters in range to fall asleep. #### `Slow Monster` Causes a monster to move slower. #### `Stinking Cloud` Shoots a ball of noxious vapors to do damage. #### `Teleport Self` Teleports the character to a new place on the level. #### `Teleport Other` Teleports an enemy to a new place on the level. #### `Trap/Door Destruction` Destroys all neighboring doors and traps. #### `Turn Stone to Mud` Causes a wall (or other stone object) to melt. #### `Word of Destruction` Destroys the entire screen. For spells that come in numbered versions (Sleep I, II, III, etc), the higher numbers have a higher effectiveness, but greater change of spell failure and greater Mana cost. #### 9.6.3 Using Offensive Spells Against Monsters Monsters have a chance to save themselves from damage caused by offensive spells, just like the character has a chance to be saved from damage by monster spells. This chance is greater for higher level monsters than for lower level monsters. Also, some spells will never work against monsters whose level is higher than the character’s experience level. Many monsters are immune to certain kinds of attack, and will suffer little or no damage from such attacks. For example, a fire-breathing dragon will suffer little damage from a fire ball, but will suffer greatly from a frost ball. Also, undead creatures will not be affected by sleep spells, since they never sleep. ## 10. Objects Found In The Dungeon The mines are full of objects just waiting to be picked up and used. How did they get there? Well, the main source for useful items are all the foolish adventurers that proceeded into the dungeon beforehand. They get killed, and the helpful creatures scatter the various treasure throughout the dungeon. Most cursed items are placed there by the joyful evil sorcerers, who enjoy a good joke when it gets someone killed. The character picks up objects by moving on top of them. Up to 22 different items can be carried in the backpack while wearing and wielding many others. Although limited to 22 different items, several items of each kind can be carried, restricted only by the amount of weight the character can carry. The weight limit is determined by the strength stat. Only one object may occupy a given floor location, which may or may not also contain one creature. Doors, traps, and staircases are considered objects for this purpose. When carrying more weight than the character is able, they will move more slowly than normal until the extra weight is dropped. If picking up an object would take the character over their weight limit, then the player will be asked whether or not the object should be picked up. It is a good idea to leave the object alone when fleeing from a monster. Many objects found within the dungeon have special commands for their use. Wands must be Aimed, staves must be Used, scrolls must be Read, and potions must be Quaffed. In any case, the character must first be able to carry an object before it can be used. Some objects, such as chests, are very complex. Chests contain other objects and may be trapped, and/or locked. Read the list of commands carefully for a further understanding of chests. One item in particular will be discussed here. The scroll of "Word-of-Recall" can be found within the dungeon, or bought at the temple in town. It acts in two manners, depending upon the character’s location. If read within the dungeon, it will teleport the character back to town. If read in town, it will teleport the character back down to the deepest level of the dungeon on which the character has previously been. This makes the scroll very useful for getting back to the deeper levels of *moria*. Once the scroll has been read, it takes a while for the spell to act, so don't expect it to save the character in a crisis. The game provides some automatic inscriptions to help keep track of possessions. Wands and staves which are known to be empty will be inscribed with "`empty`". Objects which have been tried at least once, but haven't been identified yet will be inscribed with "`tried`". Cursed objects are inscribed with "`damned`". Also, occasionally the character will notice that something in the pack or equipment list seems to be magical. High level characters are much more likely to notice this than beginning characters. When the character does notice this, the item in question will be inscribed with "`magik`". And lastly, a final warning: not all objects are what they seem. Skeletons lying peacefully about the dungeon have been known to get up... ### 10.1 Cursed Objects Some objects, mainly armor and weapons, have had curses laid upon them. These horrible objects will look like any other normal item, but will detract from the character’s stats or abilities if worn. They will also be impossible to remove until a remove curse is done. When a cursed item is worn or wielded, the character will immediately feel that something has gone wrong. The item will also be inscribed "`damned`". ### 10.2 Mining Much of the treasure within the dungeon can be found only by mining it out of the walls. Many rich strikes exist within each level, but must be found and mined. Quartz veins are the richest, yielding the most metals and gems, but magma veins will have some hordes hidden within. Mining is virtually impossible without a pick or shovel. Picks and shovels have an additional magical ability expressed as `(+#)`. The higher the number, the better the magical digging ability of the tool. A pick or shovel also has pluses to hit and damage, and can be used as a weapon. When a vein of quartz or magma is located, the character should wield a pick or shovel and begin digging out a section. When that section is removed, another section of the vein can be located, and begin the process again. Since granite rock is much harder to dig through, it is much faster to follow the vein exactly and dig around the granite. There is a game option for highlighting magma and quartz. If the character has a scroll or staff of treasure location, all strikes of treasure within a vein can be immediately shown on the screen. This makes mining much easier and more profitable. It is sometimes possible to get a character trapped within the dungeon by using various magical spells and items. So it is a very good idea to always carry some kind of digging tool, even when tunneling for treasure is not a goal. ### 10.3 Staircases, Secret Doors, Passages and Rooms Staircases are the manner in which the character gets deeper, or climbs out of the dungeon. The symbols for the up and down staircases are the same as the commands to use them. A `<` represents an up staircase and a `>` represents a down staircase. The character must move on top of the staircase before they can be used. Each level has at least one up staircase, and at least two down staircases. There are no exceptions to this rule. The character may have trouble finding some well hidden secret doors, but the stairs are there. Many secret doors are used within the dungeon to confuse and demoralize adventurers foolish enough to enter. But with some luck, and lots of concentration, these secret doors can be found. Secret doors will sometimes hide rooms or corridors, or even entire sections of that level of the dungeon. Sometimes they simply hide small empty closets or even dead ends. Creatures in the dungeon will generally know and use these secret doors. If they leave one open, the character will be able to go right through it. If they close it behind them the character will have to search for the catch first. Once a secret door has been discovered, it is drawn as a known door and no more searching will be required to use it. ## 11. Winning The Game Once the character has progressed into killing dragons with but a mean glance and snap of the fingers, they may be ready to take on the Balrog. The Balrog will appear on most levels after level 49 (a depth of 2450 feet), so don't go down there until the character is ready to take on the biggest and baddest creature in the game. The Balrog cannot be killed in some of the easier methods used on normal creatures. The Balrog will cunningly teleport to another level if a spell such as destruction is cast. Also, the Balrog cannot be polymorphed, slept, confused, or commit genocide. Magical spells like _Coldball_ are effective, as are weapons, but the Balrog is difficult to kill, and if allowed to escape to another level it can heal itself. If the character should actually survive the attempt of killing the Balrog, they will receive the status of `WINNER`. Since the toughest creature alive has been defeated, the character is ready to retire and cannot be saved. When you quit the game, the character receives a surprise bonus score. ## 12. Upon Death and Dying If the character falls below 0 hit-points, they have died and cannot be restored. A tombstone showing information about the character will be displayed. A record of the character will be provided, along with all of the equipment (identified) either on the screen or saved in a file. The character will leave behind a reduced save file, which contains only the monster memory and the game option choices. It may be restored, in which case the new character is generated exactly as if the file was not there, but the new character will find the monster memory containing all the experience of past incarnations. ## 13. Wizards There are rumors of *moria* Wizards which, if asked nicely, can explain details of the *moria* game that seem complicated to beginners. ## 14. Contributors The following people have contributed to *moria*: - Robert Alan Koeneke (Creator of Moria) - Jimmey Wayne Todd - added character generator - added skills - added history - James E. Wilson (Creator of Umoria 4.87) - ported moria from VMS to unix - David J. Grabiner (Maintainer of Umoria 5.5.2) - many bug fixes - Lars Hegelund - many bug fixes - Ben Asselstine - changed build system to autoconf - added manual in texinfo format - D. G. Kneller - reduced map display - 16 bit integers - Christopher J. Stuart - recall - options - inventory - running code - William Setzer - object naming code - Dan Bernstein - UNIX hangup signal fix - many bug fixes - Joseph Hall - line of sight code - Brian Johnson - linux support [Ed note: GNU+Linux support] - Eric Bazin - merge monster memories ## 15. Umoria Licence ``` Umoria is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Umoria is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Umoria. If not, see . ``` umoria-5.7.10+20181022/docs/faq.md0000644000175000017500000005113213363422757015004 0ustar arielariel# Umoria: Frequently Asked Questions _The most common questions, asked by both beginners and others..._ This FAQ relates to Umoria 5.7 and is split into three sections: 1. General Information 2. Game Play - Non-Spoiler Questions 3. Game Play - Spoilers! ## 1. General Information ### How do I get the Umoria? The best place to start is the [Umoria.org](http://umoria.org) website. **The Game**: Umoria is available for Windows, macOS and Linux, and can be downloaded from the [Umoria download page](http://umoria.org/download). **Source Code**: The main source code repository is currently hosted on Github at [github.com/dungeons-of-moria/umoria](https://github.com/dungeons-of-moria/umoria) If you're interested in Robert A. Koeneke's original VMS Pascal _Moria_, developed 1982-86, then please visit the [Dungeons of Moria](https://github.com/dungeons-of-moria) Github page, which also includes a few other Pascal ports of Moria, including _Imoria_ and _Moria UB_. ### How do I run the game? Umoria 5.7 is a terminal/console game. Here is a very brief explanation on running the game, however, for more detailed information please visit the Umoria.org [download and installation page](http://umoria.org/download). **macOS / Linux** To run the game you must first open the _Terminal_ application on your system, navigate to the `umoria` directory, and then enter the `./umoria` command. **Windows** Like with macOS, you can open the Windows console application (`cmd.exe` or PowerShell) and launch the game from there. However, you can also navigate to the folder in _Windows Explorer_ and double click on the `umoria.exe` file name to start the game. ### I think I've found a bug, what should I do? When you are reporting a suspected bug, make sure to give the version number and the system you are playing on, and provide as much information as possible. Here are the preferred methods for reporting the bug: * Email `info@umoria.org` * [Submit an issue](https://github.com/dungeons-of-moria/umoria/issues) to the source code repository. If you have a patch for the bug, please submit a _Pull Request_ to the [source code repository on Github](https://github.com/dungeons-of-moria/umoria). More information can be found in our [CONTRIBUTING](https://github.com/dungeons-of-moria/umoria/blob/master/CONTRIBUTING.md) guide. ## 2. Game Play - Non-Spoiler Questions ### How does resistance work, and are two items of resistance cumulative? Resist heat/cold potions and spells give temporary resistance to heat or cold. All other resistance items give permanent resistance. Two permanent resistances _are not_ cumulative, and two temporary resistances _are_ cumulative only in duration. Fire and cold do `1/3` damage if you have single resistance, `1/9` if you have double. Acid does `1/2` damage if you have any armor to corrode, `1/3` if you have resistance but no armor, and `1/4` if you have resistance and armor. Lightning does `1/3` damage if you have resistance. **_There is no resistance against poison gas._** ### How does speed work? _Do you get faster if you are already **Very Fast** and get another speed item?_ **Very Fast** is the highest speed that can be displayed, but if you are fortunate enough to find several speed items, you can get still faster. Permanent speed items (rings and boots) are cumulative, and temporary speed (potions, spells, and staffs) can add one more point to your speed. Multiple uses of temporary speed are cumulative only in duration. ### How does experience work in Umoria? _Contribution by Mike Marcelais, North Carolina School of Science And Math._ All characters receive experience during the game. Experience determines your level, which determines hit points, mana points, spells, abilities, etc. The amount of experience required to advance a level is a base value (shown below) plus a penalty for race and class. #### Calculating Experience Levels Base Experience Lv Exp to Adv Lv Exp to Adv Lv Exp to Adv 1 10 14 1,400 27 35,000 2 25 15 1,800 28 50,000 3 45 16 2,300 29 75,000 4 70 17 2,900 30 100,000 5 100 18 3,600 31 150,000 6 140 19 4,400 32 200,000 7 200 20 5,400 33 300,000 8 280 21 6,800 34 400,000 9 380 22 8,400 35 500,000 10 500 23 10,200 36 750,000 11 650 24 12,500 37 1,500,000 12 850 25 17,500 38 2,500,000 13 1,100 26 25,000 39 5,000,000 Maximum level is `40` and maximum experience is `9,999,999`. There are percent penalties for various races and classes to help even them out. The table below lists all the penalties. Human 0% Half-Elf 10% Elf 20% Halfling 10% Gnome 25% Dwarf 20% Half-Orc 10% Half-Troll 20% Warrior 0% Mage 30% Priest 20% Rogue 0% Ranger 40% Paladin 35% As an example, for a 10th level _Gnomish Mage_ to achieve the 11th level, they need: base gnome mage 500 * 1.25 * 1.30 = 812.5 Note: Even for the worst case (_Gnomish Ranger_) it is still possible to achieve the 40th level: `5,000,000 * 1.25 * 1.40 = 8,750,000` experience. #### Gaining Experience There are many ways to gain experience. This list shows a few. 1. Defeating monsters 2. Disarming traps 3. Picking locks 4. Using a scroll, potion, staff, wand, or rod for the first time and discovering what it did. 5. Casting a spell successfully for the first time. 6. Drinking a potion of gain experience or gain level #### Experience Titles Each experience level has a title which is displayed under your name and class. Below is a listing of all the titles for each level and class. ``` Warrior | Mage | Priest | Rogue | Ranger | Paladin -------------+---------------+---------------+---------------+---------------+------------ 1. Rookie Novice Believer Vagabond Runner(1st) Gallant 2. Private Apprentice Acolyte(1st) Footpad Runner(2nd) Keeper(1st) 3. Soldier Trickster-1 Acolyte(2nd) Cutpurse Runner(3rd) Keeper(2nd) 4. Mercenary Trickster-2 Acolyte(3rd) Robber Strider(1st) Keeper(3rd) 5. Veteran(1st) Trickster-3 Adept(1st) Burglar Strider(2nd) Keeper(4th) 6. Veteran(2nd) Cabalist-1 Adept(2nd) Filcher Strider(3rd) Keeper(5th) 7. Veteran(3rd) Cabalist-2 Adept(3rd) Sharper Scout(1st) Keeper(6th) 8. Warrior(1st) Cabalist-3 Priest(1st) Magsman Scout(2nd) Keeper(7th) 9. Warrior(2nd) Visionist Priest(2nd) Common Rogue Scout(3rd) Keeper(8th) 10. Warrior(3rd) Phantasmist Priest(3rd) Rogue(1st) Scout(4th) Keeper(9th) 11. Warrior(4th) Shadowist Priest(4th) Rogue(2nd) Scout(5th) Protector-1 12. Swordsman-1 Spellbinder Priest(5th) Rogue(3rd) Courser(1st) Protector-2 13. Swordsman-2 Illusionist Priest(6th) Rogue(4th) Courser(2nd) Protector-3 14. Swordsman-3 Evoker(1st) Priest(7th) Rogue(5th) Courser(3rd) Protector-4 15. Hero Evoker(2nd) Priest(8th) Rogue(6th) Courser(4th) Protector-5 16. Swashbuckler Evoker(3rd) Priest(9th) Rogue(7th) Courser(5th) Protector-6 17. Myrmidon Evoker(4th) Curate(1st) Rogue(8th) Tracker(1st) Protector-7 18. Champion-1 Conjurer Curate(2nd) Rogue(9th) Tracker(2nd) Protector-8 19. Champion-2 Theurgist Curate(3rd) Master Rogue Tracker(3rd) Defender-1 20. Champion-3 Thaumaturge Curate(4th) Expert Rogue Tracker(4th) Defender-2 21. Superhero Magician Curate(5th) Senior Rogue Tracker(5th) Defender-3 22. Knight Enchanter Curate(6th) Chief Rogue Tracker(6th) Defender-4 23. Superior Knt Warlock Curate(7th) Prime Rogue Tracker(7th) Defender-5 24. Gallant Knt Sorcerer Curate(8th) Low Thief Tracker(8th) Defender-6 25. Knt Errant Necromancer Curate(9th) Thief(1st) Tracker(9th) Defender-7 26. Guardian Knt Mage(1st) Canon(1st) Thief(2nd) Guide(1st) Defender-8 27. Baron Mage(2nd) Canon(2nd) Thief(3rd) Guide(2nd) Warder(1st) 28. Duke Mage(3rd) Canon(3rd) Thief(4th) Guide(3rd) Warder(2nd) 29. Lord(1st) Mage(4th) Canon(4th) Thief(5th) Guide(4th) Warder(3rd) 30. Lord(2nd) Mage(5th) Canon(5th) Thief(6th) Guide(5th) Warder(4th) 31. Lord(3rd) Wizard(1st) Low Lama Thief(7th) Guide(6th) Warder(5th) 32. Lord(4th) Wizard(2nd) Lama-1 Thief(8th) Guide(7th) Warder(6th) 33. Lord(5th) Wizard(3rd) Lama-2 Thief(9th) Guide(8th) Warder(7th) 34. Lord(6th) Wizard(4th) Lama-3 High Thief Guide(9th) Warder(8th) 35. Lord(7th) Wizard(5th) High Lama Master Thief Pathfinder-1 Warder(9th) 36. Lord(8th) Wizard(6th) Great Lama Executioner Pathfinder-2 Guardian 37. Lord(9th) Wizard(7th) Patriarch Low Assassin Pathfinder-3 Chevalier 38. Lord Gallant Wizard(8th) High Priest Assassin Ranger Justiciar 39. Lord Keeper Wizard(9th) Great Priest High Assassin High Ranger Paladin 40. Lord Noble Wizard Lord Noble Priest Guildmaster Ranger Lord High Lord ``` ## 3. Game Play - Spoilers! _**WARNING: the information given below contains many spoilers, read at your own peril.**_ ### What are the special abilities of ego weapons? _Crowns? Amulet of the Magi? Cloak of Protection?_ Amulet of the Magi free action, see invisible, searching, +3 AC. Cloak of Protection no special ability, just a larger bonus than usual. _Ego weapons:_ (HA) Holy Avenger +(1-4) str, +(1-4) AC, (SE), (SU), sustain stat, see invisible. (DF) Defender stealth, regeneration, free action, see invisible, feather fall, RF, RC, RL, RA, +(6-10) to AC (SM) Slay Monster Damage (x 2) vs. monsters, see invisible. (SA) Slay Animal Damage (x 2) vs. animals, see invisible. (SD) Slay Dragon Damage (x 4) vs. dragons. (SE) Slay Evil Damage (x 2) vs. evil monsters. (SU) Slay Undead Damage (x 3) vs. undead, see invisible. (FT) Flame Tongue Damage (x 1.5) vs. monsters harmed by fire. (FB) Frost Brand Damage (x 1.5) vs. monsters harmed by cold. A (HA) is: +1 to strength, sustains strength +2 intelligence +3 wisdom +4 constitution (not dexterity). Crown of the Magi +(1-3) int, (RF), (RC), (RA), (RL) Crown of Lordliness +(1-3) wis, chr. Crown of Might +(1-3) str, dex, con, free action. Crown of Seeing see invisible, +(10-25) searching, +(2-5) to search. Crown of Regeneration Regeneration. Crown of Beauty +(1-3) charisma. **Regeneration** lets you recover mana and hit points at `1.5` times the normal rate, but also makes you use up food much more quickly. **Free action** prevents you from being slowed or paralyzed by monsters. ### How much damage do spells and wands do? Spell Name | Damage ----------------------+------- Magic Missile 2d6 Stinking Cloud 12 Lightning Bolt 4d8 Lightning Ball 32 Frost Bolt 6d8 Frost/Cold Ball 48 Acid Ball 60 Fire Bolt 9d8 Fire Ball 72 Wand of Drain Life 75 A _wand of wall building_ will do `4d8` damage to any creature buried in the wall (except one that moves through walls), and will kill any immobile creature. On the creatures' next turn, it will attempt to move out of the wall, and if it is unable to do so, it will take `10d8` damage and dig its way out. Wand of Light / Staff of Starlight 2d8 (if sensitive) Stone to Mud 100 (if sensitive) Orb of Draining 3d6 + caster's level, double to evil creatures Dispel Undead/Evil 1-60 from scroll or staff; 1 up to triple caster's level from spell Holy Word Dispel evil for 1 up to quadruple caster's level ### What does spell Y do? Non-obvious spell effects. #### Mage Spells - **Phase Door**: short-range teleport. - **Find Hidden Traps/Doors**: also detects stairs. - **Sleep I**: sleep one monster in a given direction. - **Recharge Item I**: fewer charges than Recharge Item II, and more likely to fail. - **Sleep II**: sleep all monsters adjacent to player. - **Sleep III**: sleep all monsters with a line of sight to player (including invisible ones). - **Word of Destruction**: obliterates everything within 15 spaces of the player; Balrog will teleport to another level. #### Priest Spells - **Bless**: `+2` to AC, and `+5` to chance to hit (equivalent to `+1-2/3` bonus on weapon) for a short time. - **Blind Creature**: blinded creatures wander around confused until they recover. - **Portal**: medium-range teleport. - **Chant**: double duration Bless. - **Sanctuary**: sleep creatures adjacent to player. - **Protection from Evil**: prevents any evil creature of the player's level or lower from attacking the player. - **Earthquake**: causes random walls and ceilings in the area to collapse, possibly injuring anything nearby. - **Turn Undead**: all undead of the player's level or lower, and some of higher level, will attempt to flee. - **Prayer**: quadruple duration Bless. - **Dispel Undead/Evil**: affects all undead/evil within line of sight, damage is from `1` up to `3x` player's level, `1-60` from scroll or staff. - **Glyph of Warding**: creates a glyph which monsters cannot enter, but have a small chance of breaking. - **Holy Word**: heals player completely, cures poison and fear, and dispels evil for `1` to `4x` player's level. Also restores all stats, and makes player invulnerable for `3` turns. ### On what level do you find X? _Where important objects are found._ Just `1/12` of items are chosen as if you were on a deeper level, which has (`current level/50`) chance of being level `50`; this check is not made in the town. This affects only the type of item, not its enchantment. Items become somewhat less common as you go deeper than the indicated levels; however, if you can survive down there, this is compensated for by the fact that there are more treasures on deeper levels. Item type | Level ----------------------------+------------------------------------- Ego weapons, special armor, Progressively more common as you get boots, gloves, helmets deeper, all the way to level 55 Healing potion 12 Gain stat potions 25 Restore mana potion 25 Invulnerability potion 40 Gain experience potion 50 Genocide scroll 35 Destruction scroll 40 Rune of Protection scroll 50 Mass Genocide scroll 50 Amulets of wisdom, charisma 20 Gain str/int/dex/con rings 30 Amulet of the magi 50 Ring of speed 50 Staff of speed 40 Staff of mass polymorph 46 Staff of dispel evil 49 Staff of destruction 50 Wand of clone monster 15 Wand of drain life 50 ### How many attacks can I get with this weapon? Here is the table for the number of blows for a given strength and dexterity. If your strength or dexterity is `18+xx`, that is stored as `18/xx`; thus, for example, you need an `18/90` strength to use the bottom row of the table with a Katana (`12 pounds`). If you don't know the weight of a weapon, set `Show weights in inventory`, found in the game options menu. ```c int8u blows_table[7][6] = { /* STR/W: 9 18 67 107 117 118 : DEX */ /* <2 */ { 1, 1, 1, 1, 1, 1 }, /* <3 */ { 1, 1, 1, 1, 2, 2 }, /* <4 */ { 1, 1, 1, 2, 2, 3 }, /* <5 */ { 1, 1, 2, 2, 3, 3 }, /* <7 */ { 1, 2, 2, 3, 3, 4 }, /* <9 */ { 1, 2, 2, 3, 4, 4 }, /* >9 */ { 2, 2, 3, 3, 4, 4 } }; ``` ### How do you kill an Ancient Multi-Hued Dragon? Usually, you don't want to try; one gas breath from a full-strength `AMHD` does `693` damage, with no resistance. If you can get to speed `3` (one permanent speed item, and either another permanent speed item or a haste self spell or staff), you can try the following technique. First, create (or find in a maze room) a wall with one open space on all four sides. . .#. . Stand on one side, with the dragon on the other side. When the dragon moves adjacent to you, attack it once, and then hide behind the pillar. The dragon can't see you, so it won't breathe, and will instead chase you to another side. Now attack once, and hide again, and so on until the dragon is finished. ### How do you kill an Emperor Lich? You can kill an Emperor Lich if you can get to speed 2, which is its speed. A Mage or Ranger can do this with the spell of haste self; anyone else needs a staff of speed, potion of haste self, or permanent speed item. You will also need about `10` _cure critical wounds_ or _cure serious wounds_ potions, and some item giving you _free action_. You also need some ranged spell attack. Liches take double damage from lightning, so the spell of lightning bolt or wand of lightning balls is a good choice. Rogues and Warriors will need several wands, with a total of about 30 charges to guarantee that they can kill the Lich with them. A Priest or Paladin has _Orb of Draining_, which is even better. Now, try to line up with the Lich while you are not adjacent to it, either in a room or a corridor. This gives you a chance to cast your spell. The Lich will get one action. If it cast a spell and you resisted, or the spell didn't do anything harmful, you have another chance. If you were confused or blinded, drink a cure wounds potion; the Lich isn't adjacent to you, so it can't hurt you. If the Lich moved and is now adjacent to you, move back. Try to avoid getting cornered, and phase door or portal away if you are. A priest can make this easier by putting down a _Glyph of Warding_, but this must be done **before** the Lich chases you across the glyph -- don't stand on the glyph; it isn't foolproof. If you run low on mana and don't have a wand, teleport out and come back later to finish the job. A priest with _Glyph of Warding_ can also set up the following configuration (the exact length doesn't matter as long as you are within spell range): #L##### #^^...@ ####### The Lich cannot cast spells from this position, because it cannot see you. As long as it doesn't break the glyphs, you are safe, and can fire an _Orb of Draining_ down the corridor; the Lich will take some damage each time. If the Lich breaks either glyph, run or teleport out, and continue the battle elsewhere. I do not advise trying this technique against an AMHD; it will probably break a glyph before the battle is over, and if your teleport spell fails, or if you haven't hasted yourself, the AMHD gets a chance to breathe. An Emperor Lich has `1520 HP` (hit points), plus anything additional that it gains by draining mana (6 points per mana point drained) and charges (40 points per charge). Never let it attack you in melee, because it can destroy your wands, healing itself in the process, as well as draining your experience and dexterity. If you can get to speed `3`, faster than the Lich, it is easy to kill; just fight, move back, fight, move back, and so on. You will still need a lot of cure wounds potions, unless you let it chase you around a pillar, as in the AMHD technique. ## End Note Much of this information was included in the original Umoria sources - last updated in 2006 - and covered versions from 4.8 to 5.5 of the game. I've tried to remove all information that is not relevant to the current game, however, there may be incorrect/inconsistent information still present. If you find something that needs changing, please let us know (`info@umoria.org`) and it will be fixed. umoria-5.7.10+20181022/AUTHORS0000644000175000017500000000542613363422757014040 0ustar arielariel# Moria / Umoria Authors Robert Alan Koeneke created Moria from 1981-86. James E. Wilson took the VMS Moria sources and created Umoria from 1987-94. In 2008 Moria/Umoria was re-licensed under the GNU General Public License version 2.0. All new additions to the code will fall under this license. ## Umoria 5.7.x Michael R. Cook [-MRC-] (GPL v2) Umoria restoration project: Windows and macOS support deprecation of old computer systems (Amiga, Atari ST, etc.) major refactoring of the source code ## VMS Moria 0.1 - 4.8 / Umoria 4.8 - 5.6 Many people have contributed to the game since Koeneke released Moria in 1983. Their names, license details, and contributions summary are presented below. Robert Alan Koeneke [-RAK-] (GPL v2) Moria creator James E. Wilson [-JEW-] (GPL v2) Umoria creator David J. Grabiner [-DJG-] (GPL v2) handle large items in chests as maintainer Jimmey Wayne Todd Jr. [-JWT-] character generation (race, class, stats, etc.) character adjustments (AC, HP, INT, WIS, damage, to hit) calculate total player points print player character/history game save/load code add player to scores various other features and functions D. G. Kneller (Public Domain) MSDOS Moria port reduced map display 16 bit integers Christopher J. Stuart [-CJS-] (GPL v2) recall options inventory running code Curtis McCauley (GPL v2) Macintosh Moria port for MPW C Stephen A. Jacobs (GPL v2) Atari ST Moria port for MW C William Setzer (GPL v2) object naming code Dan Bernstein [-DJB-] (Public Domain) "no need to bargain" functionality update bargin info UNIX hangup signal fix many bug fixes Corey Gehman (GPL v2) Amiga Moria port Ralph Waters (GPL v2) VMS support code IBM-PC Turbo C bug fixes Joshua Delahunty (GPL v2) VMS support code Todd Pierzina (GPL v2) VMS support code Joseph Hall (Public Domain) line of sight code monster compiler Eric Vaitl (GPL v2) PC-Curses replacement for Turbo-C Scott Kolodzieski (GPL v2) Atari ST port for GNU C Hildo Biersma (Public Domain) Atari ST port for Turbo C Ben Schreiber (GPL v2) Macintosh port for Think C Carlton Hommel (Public Domain) the 'print items' utility Wayne Schlitt (GPL v2) the 'calculate hits' utility Brian W. Johnson (GPL v2) Linux support Ronald Cook (Public Domain) lattice C support Eric Bazin (Public Domain) merge monster memories Berthold Gunreben (GPL v2) HP-UX support Andrew Chernov (Public Domain) 386 BSD support Harry Johnston (GPL v2) specify an item by its numeric inscription Rene Weber [-RJW-] (GPL v2) compiling and doc updates for Umoria v5.6 The following signature is currently unknown but listed here for completeness. -RGM- (contributed > v4.8.5.2 and <= 4.8.7 ) created get_all_stats() for character class loop umoria-5.7.10+20181022/CMakeLists.txt0000644000175000017500000002257313363422757015532 0ustar arielarielcmake_minimum_required(VERSION 3.6 FATAL_ERROR) # Do not allow in-source builds if (${CMAKE_SOURCE_DIR} STREQUAL "${PROJECT_SOURCE_DIR}/src") message(FATAL_ERROR "CMake generation is not allowed within the source directory!") endif () # # Set a default build type # set(default_build_type "Release") if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) message(STATUS "Setting build type to '${default_build_type}' as none was specified.") set(CMAKE_BUILD_TYPE "${default_build_type}" CACHE STRING "Choose the type of build." FORCE) # Set the possible values of build type for cmake-gui set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo") else () message(STATUS "Build type set to '${CMAKE_BUILD_TYPE}'") endif () # Compiler settings (this must come before calling project) set(CMAKE_CXX_COMPILER g++) set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED on) project(umoria) # # Core set of warnings # set(cxx_warnings "-Wall") set(cxx_warnings "${cxx_warnings} -Wextra") set(cxx_warnings "${cxx_warnings} -Wpedantic") set(cxx_warnings "${cxx_warnings} -Wshadow") set(cxx_warnings "${cxx_warnings} -Werror") set(cxx_warnings "${cxx_warnings} -pedantic-errors") set(cxx_warnings "${cxx_warnings} -Weffc++ ") # Additional warnings set(cxx_warnings "${cxx_warnings} -Wcast-align") set(cxx_warnings "${cxx_warnings} -Wdisabled-optimization") set(cxx_warnings "${cxx_warnings} -Wfloat-equal") set(cxx_warnings "${cxx_warnings} -Winline") set(cxx_warnings "${cxx_warnings} -Winvalid-pch") set(cxx_warnings "${cxx_warnings} -Wmissing-format-attribute") set(cxx_warnings "${cxx_warnings} -Wmissing-include-dirs") set(cxx_warnings "${cxx_warnings} -Wpacked") set(cxx_warnings "${cxx_warnings} -Wredundant-decls") set(cxx_warnings "${cxx_warnings} -Wswitch-default") set(cxx_warnings "${cxx_warnings} -Wswitch-enum") set(cxx_warnings "${cxx_warnings} -Wunreachable-code") set(cxx_warnings "${cxx_warnings} -Wwrite-strings") # Some very strict warnings, that will be nice to use, but require some hefty refactoring # set(cxx_warnings "${cxx_warnings} -Wcast-qual") # set(cxx_warnings "${cxx_warnings} -Wconversion") # set(cxx_warnings "${cxx_warnings} -Wformat=2") # set(cxx_warnings "${cxx_warnings} -Wpadded") # set(cxx_warnings "${cxx_warnings} -Wstrict-overflow") # set(cxx_warnings "${cxx_warnings} -fno-strict-aliasing") # Temporary support for GCC 8. if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") set(cxx_warnings "${cxx_warnings} -Wno-format-overflow") endif() # # Set the flags and warnings for the debug/release builds # set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g3 -O0 ${cxx_warnings}") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2 ${cxx_warnings}") # # Source files and directories # set(source_dir ${PROJECT_SOURCE_DIR}/src) set( source_files ${source_dir}/character.h ${source_dir}/config.h ${source_dir}/curses.h ${source_dir}/dice.h ${source_dir}/dungeon.h ${source_dir}/dungeon_tile.h ${source_dir}/game.h ${source_dir}/headers.h ${source_dir}/helpers.h ${source_dir}/identification.h ${source_dir}/inventory.h ${source_dir}/mage_spells.h ${source_dir}/monster.h ${source_dir}/player.h ${source_dir}/recall.h ${source_dir}/rng.h ${source_dir}/scores.h ${source_dir}/scrolls.h ${source_dir}/spells.h ${source_dir}/staves.h ${source_dir}/store.h ${source_dir}/treasure.h ${source_dir}/types.h ${source_dir}/ui.h ${source_dir}/version.h ${source_dir}/wizard.h ${source_dir}/config.cpp ${source_dir}/helpers.cpp ${source_dir}/rng.cpp ${source_dir}/main.cpp ${source_dir}/data_creatures.cpp ${source_dir}/data_player.cpp ${source_dir}/data_recall.cpp ${source_dir}/data_store_owners.cpp ${source_dir}/data_stores.cpp ${source_dir}/data_tables.cpp ${source_dir}/data_treasure.cpp ${source_dir}/character.cpp ${source_dir}/dice.cpp ${source_dir}/dungeon.cpp ${source_dir}/dungeon_generate.cpp ${source_dir}/dungeon_los.cpp ${source_dir}/game.cpp ${source_dir}/game_death.cpp ${source_dir}/game_files.cpp ${source_dir}/game_objects.cpp ${source_dir}/game_run.cpp ${source_dir}/game_save.cpp ${source_dir}/identification.cpp ${source_dir}/inventory.cpp ${source_dir}/mage_spells.cpp ${source_dir}/monster.cpp ${source_dir}/monster_manager.cpp ${source_dir}/player.cpp ${source_dir}/player_bash.cpp ${source_dir}/player_eat.cpp ${source_dir}/player_magic.cpp ${source_dir}/player_move.cpp ${source_dir}/player_pray.cpp ${source_dir}/player_quaff.cpp ${source_dir}/player_run.cpp ${source_dir}/player_stats.cpp ${source_dir}/player_throw.cpp ${source_dir}/player_traps.cpp ${source_dir}/player_tunnel.cpp ${source_dir}/recall.cpp ${source_dir}/scores.cpp ${source_dir}/scrolls.cpp ${source_dir}/spells.cpp ${source_dir}/staves.cpp ${source_dir}/store.cpp ${source_dir}/store_inventory.cpp ${source_dir}/treasure.cpp ${source_dir}/ui.cpp ${source_dir}/ui_inventory.cpp ${source_dir}/ui_io.cpp ${source_dir}/wizard.cpp ) # # Set up the install paths and files # set(build_dir "umoria") set(data_dir "${build_dir}/data") file(MAKE_DIRECTORY ${build_dir}) file(MAKE_DIRECTORY ${data_dir}) set(CMAKE_INSTALL_PREFIX ${CMAKE_BINARY_DIR}) set(EXECUTABLE_OUTPUT_PATH ${build_dir}) set(RUNTIME_OUTPUT_DIRECTORY ${build_dir}) # Core game data files set( data_files ${PROJECT_SOURCE_DIR}/data/help.txt ${PROJECT_SOURCE_DIR}/data/help_wizard.txt ${PROJECT_SOURCE_DIR}/data/rl_help.txt ${PROJECT_SOURCE_DIR}/data/rl_help_wizard.txt ${PROJECT_SOURCE_DIR}/data/splash.txt ${PROJECT_SOURCE_DIR}/data/versions.txt ${PROJECT_SOURCE_DIR}/data/welcome.txt ${PROJECT_SOURCE_DIR}/data/death_tomb.txt ${PROJECT_SOURCE_DIR}/data/death_royal.txt ) file(COPY ${data_files} DESTINATION "${data_dir}") # Various support files (readme, etc.) set( support_files ${PROJECT_SOURCE_DIR}/data/scores.dat ${PROJECT_SOURCE_DIR}/docs/manual.md ${PROJECT_SOURCE_DIR}/docs/faq.md ${PROJECT_SOURCE_DIR}/AUTHORS ${PROJECT_SOURCE_DIR}/LICENSE ) file(COPY ${support_files} DESTINATION "${build_dir}") # # Extract the Umoria version number from version.h # file(STRINGS "${source_dir}/version.h" VERSION_HEADER) string(REGEX MATCH "CURRENT_VERSION_MAJOR = ([0-9]+);" ${VERSION_HEADER}) set(umoria_version_major ${CMAKE_MATCH_1}) string(REGEX MATCH "CURRENT_VERSION_MINOR = ([0-9]+);" ${VERSION_HEADER}) set(umoria_version_minor ${CMAKE_MATCH_1}) string(REGEX MATCH "CURRENT_VERSION_PATCH = ([0-9]+);" ${VERSION_HEADER}) set(umoria_version_patch ${CMAKE_MATCH_1}) set(umoria_version "${umoria_version_major}.${umoria_version_minor}.${umoria_version_patch}") # # Update the data files with the current version number and date # # Fetch release date from CHANGELOG if it's there :-) file(READ "${PROJECT_SOURCE_DIR}/CHANGELOG.md" changelog) string(REGEX MATCH "## ${umoria_version} \\(([0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9])\\)" results ${changelog}) list(LENGTH results match_count) if (match_count EQUAL 0) string(TIMESTAMP current_date "%Y-%m-%d" UTC) else () set(current_date ${CMAKE_MATCH_1}) endif () set( data_files_to_update ${build_dir}/data/splash.txt ${build_dir}/data/versions.txt ) foreach (data_file ${data_files_to_update}) file(READ ${data_file} content) string(REGEX REPLACE "{{ CURRENT_VERSION }}" "${umoria_version}" content "${content}") string(REGEX REPLACE "{{ RELEASE_DATE }}" "${current_date}" content "${content}") file(WRITE ${data_file} ${content}) endforeach () # All of the game resource files set(resources ${data_files} ${support_files}) # Also add resources to the target so they are visible in the IDE add_executable(umoria ${source_files} ${resources}) # This is horrible, but needed bacause `find_package()` doesn't use the # include/lib inside the /mingw32 or /mingw64 directories, and with # `ncurses-devel` installed, it won't compile. if ((MSYS OR MINGW) AND "$ENV{MINGW}" STREQUAL "") message(FATAL_ERROR "You must set the MINGW environment variable ('mingw64' or 'mingw32'). Example: MINGW=mingw64 cmake .") message(FATAL_ERROR "This will be the directory used for locating the ncurses library files.") elseif ((MSYS OR MINGW) AND NOT "$ENV{MINGW}" STREQUAL "") message(STATUS "NOTE: Configuring build for Windows release...") # Make the ncurses library static set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -static -lpthread") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -static -lpthread") set(CURSES_INCLUDE_DIR "/$ENV{MINGW}/include/") set(CURSES_LIBRARIES "/$ENV{MINGW}/lib/libncurses.a") else () message(STATUS "NOTE: Configuring build for macOS/Linux release...") find_package(Curses REQUIRED) endif () include_directories(${CURSES_INCLUDE_DIR}) target_link_libraries(umoria ${CURSES_LIBRARIES}) # Build and install the umoria binary install(TARGETS umoria DESTINATION ${build_dir}) umoria-5.7.10+20181022/CODE_OF_CONDUCT.md0000644000175000017500000000625313363422757015566 0ustar arielariel# Umoria Contributor Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [info@umoria.org](mailto:info@umoria.org). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ umoria-5.7.10+20181022/data/0000755000175000017500000000000013363422757013672 5ustar arielarielumoria-5.7.10+20181022/data/death_tomb.txt0000644000175000017500000000202013363422757016533 0ustar arielariel _______________________ / \ ___ / \ ___ / \ ___ / RIP \ \ : : / \ / \ : _;,,,;_ : : / \,;_ _;,,,;_ | the | ___ | | / \ | | : : | | _;,,,;_ ____ | Level : | / \ | | : : | | : : | Died on Level : | _;,,,,;_ | killed by | | | | | *| * * * * * * | * ________)/\\_)_/___(\/___(//_\)/_\//__\\(/_|_)_______ umoria-5.7.10+20181022/data/splash.txt0000644000175000017500000000140713363422757015727 0ustar arielariel Robert A. Koeneke's classic roguelike ## ## ####### ######## #### ### ### ### ## ## ## ## ## ## ## #### #### ## ## ## ## ## ## ## ## ### ## ## ## ######## ## ## ## ## ## ## ## ## ## ## ######### ## ## ## ## ## ## ## ## ## ## ## ####### ## ## #### ## ## Umoria {{ CURRENT_VERSION }} Umoria is free software released under GPL v2 and comes with ABSOLUTELY NO WARRANTY For more information: https://github.com/dungeons-of-moria/umoria umoria-5.7.10+20181022/data/death_royal.txt0000644000175000017500000000131013363422757016721 0ustar arielariel # ##### # ,,, $$$ ,,, ,,=$ "$$$$$" $=,, ,$$ $$$ $$, *> <*> <* $$ $$$ $$ "$$ $$$ $$" "$$ $$$ $$" *#########*#########* *#########*#########* Veni, Vidi, Vici! I came, I saw, I conquered! All Hail the Mighty umoria-5.7.10+20181022/data/help_wizard.txt0000644000175000017500000000071013363422757016741 0ustar arielariel: - Map area (panel) ^A - Cure all maladies and remove curses ^B - Print random objects sample to file ^D - Jump to depth # (level) ^E - Edit character stats ^F - Vanquish monsters within sight ^G - Generate random items ^H - Wizard Help ^I - Identify an item ^L - Wizard light ^T - Teleport player randomly ^U - Summon random monster ^W - Wizard mode on/off + - Gain experience % - Generate a dungeon item @ - Create an object *CAN CAUSE FATAL ERROR* umoria-5.7.10+20181022/data/versions.txt0000644000175000017500000000266313363422757016312 0ustar arielariel Umoria {{ CURRENT_VERSION }} ({{ RELEASE_DATE }}) Previous major releases: 2016-11-27 : v5.7.0 2008-10-13 : v5.6.0 1994-07-21 : v5.5.2 1992-08-13 : v5.5.0 1991-10-12 : v5.4.0 1991-03-25 : v5.3.0 1990-05-09 : v5.2.0 1989-02-26 : v5.0.0 1988-05-26 : v4.87 1987-10-26 : v4.85 Maintainer: David J. Grabiner (grabiner@alumni.princeton.edu) Umoria 5.7.x developed and maintained by Michael R. Cook. Bug reports and contributions can be made to the Umoria repository: https://github.com/dungeons-of-moria/umoria Umoria is free software released under GPL v2.0 VMS Moria Version 4.8 Version 4.0 : 1985-01-20 Version 3.0 : 1984-11-20 Version 2.0 : 1984-07-10 Version 1.0 : 1984-05-01 Version 0.1 : 1983-03-25 VMS Moria Modules : V1.0 Dungeon Generator - RAK Character Generator - RAK & JWT Moria Module - RAK Miscellaneous - RAK & JWT V2.0 Town Level & Misc - RAK V3.0 Internal Help & Misc - RAK V4.0 Source Release Version - RAK Umoria is free software released under GNU General Public License v2.0 See LICENSE and AUTHORS for more details Copyright (c) Robert A. Koeneke, James E. Wilson umoria-5.7.10+20181022/data/rl_help_wizard.txt0000644000175000017500000000071013363422757017436 0ustar arielariel\ - Wizard Help : - Map area (panel) * - Wizard light ^A - Cure all maladies and remove curses ^D - Jump to depth # (level) ^E - Edit character stats ^F - Vanquish monsters within sight ^G - Generate random items ^I - Identify an item ^O - Print random objects sample to file ^T - Teleport player randomly ^W - Wizard mode on/off + - Gain experience & - Summon random monster % - Generate a dungeon item @ - Create an object *CAN CAUSE FATAL ERROR* umoria-5.7.10+20181022/data/help.txt0000644000175000017500000000601313363422757015363 0ustar arielarielCommand summary: (@ is optional count, ~ is direction, ^R redraws, ESC aborts) a Aim and fire a wand | @ B ~ Bash item or monster b Browse a book | C Character description c ~ Close a door | @ D ~ Disarm a trap/chest d Drop an item | E Eat some food e Equipment list | F Fill lamp with oil f Fire/Throw an item | G Gain new magic spells i Inventory list | L Locate with map @ j ~ Jam a door with spike | M Map (shown reduced size) l ~ Look given direction | @ R Rest (count or *=restore) m Magic spell casting | S Search mode @ o ~ Open a door/chest | @ T ~ Tunnel in a direction p Pray | V View scoreboard q Quaff a potion | = Set options r Read a scroll | ? View this page @ s Search for traps or doors | { Inscribe an object t Take off an item | @ - Move without pickup u Use a staff | . ~ Run in direction v Version info and credits | / Identify a character w Wear/Wield an item | CTRL-K Quit the game x Exchange weapon | @ CTRL-P Repeat the last message < Go up an up-staircase | CTRL-X Save character and quit > Go down a down-staircase | @ ~ For movement Directions: 7 8 9 4 5 6 [5 to rest] 1 2 3 To give a count to a command, type a '#', followed by the digits. A count of 0 defaults to a count of 99. Counts only work with some commands, and will be terminated by the same things that end a rest or a run. In particular, typing any character during the execution of a counted command will terminate the command. To count a movement command, hit space after the number, and you will be prompted for the command, which may be a digit. Counted searches or tunnels will terminate on success, or if you are attacked. A count with control-P will specify the number of previous messages to display. Control-R will redraw the screen whenever it is input, not only at command level. Control commands may be entered with a single key stroke, or with two key strokes by typing ^ and then a letter. Type ESCAPE to abort the look command at any point. Some commands will prompt for a spell, or an inventory item. Selection is by an alphabetic character - entering a capital causes a desription to be printed, and the selection may be aborted. Typing `R*' will make you rest until both your mana and your hp reach their maximum values. Umoria is free software, and you are welcome to distribute it under certain conditions; for details, type control-V. Umoria comes with ABSOLUTELY NO WARRANTY; for details, type control-V and view sections 15-17 of the License. umoria-5.7.10+20181022/data/welcome.txt0000644000175000017500000000065313363422757016072 0ustar arielariel Welcome to Moria! Your first task is to generate a character. You must specify a gender, a race, and a class. Anytime before you have made these specifications, you can interrupt with control-C. You can also specify the race as often as you like by entering '%' to the class request. This will also regenerate your attributes. If this is your first time playing, you may want to read the manual first. umoria-5.7.10+20181022/data/scores.dat0000644000175000017500000000011413363422757015656 0ustar arielariel////&Kp8ΛΘΒΒΘΘΘΙΙ„„…ρ”η““³“³“³““““““γ榦¦¦¦b¦₯μ‚φ“α“ζ–β‹ε‚‚ͺNK #ΗΒ‚‚‚‚‚umoria-5.7.10+20181022/data/rl_help.txt0000644000175000017500000000626313363422757016067 0ustar arielarielCommand summary: (@ is optional count, ~ is direction, ^R redraws, ESC aborts) c ~ Close a door | C Character description d Drop an item | @ D ~ Disarm a trap/chest e Equipment list | E Eat some food @ f ~ Force (bash) item or mons.| F Fill lamp with oil i Inventory list | G Gain new magic spells m Magic spell casting | M Map (shown reduced size) @ o ~ Open a door/chest | P Peruse a book p Pray | Q Quit the game q Quaff a potion | @ R Rest (count or *=restore) r Read a scroll | @ S ~ Spike a door @ s Search for traps or doors | T Take off an item t Throw an item | V View scores v Version info and credits | W Where: locate self w Wear/Wield an item | X Exchange weapon x ~ Examine surroundings | Z Zap a staff z Zap a wand | # Search mode = Set options | < Go up an up-staircase / Identify a character | > Go down a down-staircase @ CTRL-P Previous message review | { Inscribe an object @ - ~ Move without pickup | ? View this page @ CTRL ~ Tunnel in a direction | CTRL-X Save character and quit @ SHIFT ~ Run in direction | @ ~ For movement Directions: y k u h . l [. to rest] b j n To give a count to a command, type the number in digits, then the command. A count of 0 defaults to a count of 99. Counts only work with some commands, and will be terminated by the same things that end a rest or a run. In particular, typing any character during the execution of a counted command will terminate the command. Counted searches or tunnels will terminate on success, or if you are attacked. A count with control-P will specify the number of previous messages to display. Control-R will redraw the screen whenever it is input, not only at command level. Control commands may be entered with a single key stroke, or with two key strokes by typing ^ and then a letter. Type ESCAPE to abort the look command at any point. Some commands will prompt for a spell, or an inventory item. Selection is by an alphabetic character - entering a capital causes a desription to be printed, and the selection may be aborted. Typing `R*' will make you rest until both your mana and your hp reach their maximum values. Using repeat counts with the tunnel left command does not work well, because tunnel left is the backspace character, and will delete the number you have just typed in. To avoid this, you can either enter ^H as two characters (^ and then H), or you can type ' ' (i.e. the space character) after the number at which point you will get a command prompt and backspace will work correctly. Umoria is free software, and you are welcome to distribute it under certain conditions; for details, type control-V. Umoria comes with ABSOLUTELY NO WARRANTY; for details, type control-V and view sections 15-17 of the License.