umoria-5.7.13/ 0000755 0001750 0001750 00000000000 13720166710 012060 5 ustar ariel ariel umoria-5.7.13/.clang-format 0000644 0001750 0001750 00000005127 13720166710 014440 0 ustar ariel ariel ---
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: None
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.13/CONTRIBUTING.md 0000644 0001750 0001750 00000012775 13720166710 014325 0 ustar ariel ariel # 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.13/.gitignore 0000644 0001750 0001750 00000001347 13720166710 014055 0 ustar ariel ariel .idea
# CMake
Makefile
CMakeCache.txt
CMakeFiles
CMakeScripts
cmake_install.cmake
install_manifest.txt
CPackConfig.cmake
CPackSourceConfig.cmake
CTestTestfile.cmake
cmake-build-debug
cmake-build-release
_CPack_Packages
# Prerequisites
*.d
# Object files
*.o
*.ko
*.obj
*.elf
# Linker output
*.ilk
*.map
*.exp
# Precompiled Headers
*.gch
*.pch
# Libraries
*.lib
*.a
*.la
*.lo
# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib
# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex
# Debug files
*.dSYM/
*.su
*.idb
*.pdb
# Kernel Module Compile Results
*.mod*
*.cmd
modules.order
Module.symvers
Mkfile.old
dkms.conf
*.cbp
# build directories
umoria.xcodeproj
nbproject
umoria
compile_commands.json
*.gz
*.zip
.DS_Store
umoria-5.7.13/AUTHORS 0000644 0001750 0001750 00000005426 13720166710 013137 0 ustar ariel ariel # 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.13/historical/ 0000755 0001750 0001750 00000000000 13720166710 014221 5 ustar ariel ariel umoria-5.7.13/historical/errors.md 0000644 0001750 0001750 00000106733 13720166710 016071 0 ustar ariel ariel # 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.13/historical/CHANGELOG 0000644 0001750 0001750 00000501644 13720166710 015445 0 ustar ariel ariel # 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.13/historical/dragons.md 0000644 0001750 0001750 00000005107 13720166710 016203 0 ustar ariel ariel # 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.13/historical/README.md 0000644 0001750 0001750 00000004240 13720166710 015500 0 ustar ariel ariel # 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.13/historical/history.md 0000644 0001750 0001750 00000037673 13720166710 016264 0 ustar ariel ariel # 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.13/historical/features.md 0000644 0001750 0001750 00000030060 13720166710 016360 0 ustar ariel ariel # 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.13/historical/faq.md 0000644 0001750 0001750 00000051132 13720166710 015314 0 ustar ariel ariel # 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.13/historical/manual.md 0000644 0001750 0001750 00000316632 13720166710 016033 0 ustar ariel ariel # 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.13/LICENSE 0000644 0001750 0001750 00000043254 13720166710 013075 0 ustar ariel ariel 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.13/CMakeLists.txt 0000644 0001750 0001750 00000021433 13720166710 014623 0 ustar ariel ariel cmake_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")
set(EXECUTABLE_OUTPUT_PATH ${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/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}/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
data/splash.txt
data/versions.txt
)
foreach (data_file ${data_files_to_update})
configure_file(${data_file}.in ${build_dir}/${data_file})
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...")
set(CURSES_NEED_NCURSES TRUE)
find_package(Curses REQUIRED)
endif ()
include_directories(${CURSES_INCLUDE_DIR})
target_link_libraries(umoria ${CURSES_LIBRARIES})
umoria-5.7.13/README.md 0000644 0001750 0001750 00000010436 13720166710 013343 0 ustar ariel ariel # 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 developed 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. Moria was also an
inspiration for one the most commercially successful action roguelike games,
_Diablo_!
Supported Platforms:
- Windows
- macOS
- Linux (Ubuntu/Debian)
Compiling and limited testing done for:
- NetBSD 8.1 (with gcc 5.5)
- Fedora 32
## Umoria Restoration Release: v5.7
The main focus of the `v5.7` release is to provide support for the three main
operating systems: Windows, macOS, and Linux. Support for all other outdated
computer systems such as MS DOS, "Classic" Mac OS (pre OSX), Amiga, and
Atari ST has been removed.
_Note: there are no intentional game play changes in the 5.7.x releases._
A great deal of _code restoration_ has been undertaken in the hope of aiding
future development of the game. Examples of refactoring completed so far include
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 and Clang.
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 `7.x`, and `8.1`, with
`ncurses 6.x`, although recent earlier versions should also work fine. You will
require these along with `CMake` and the C++ build tools for your system.
### macOS and Linux
Change to the `umoria` game directory and enter the following commands at the
terminal:
$ cmake .
$ make
To perform an out-of-source build, type the following:
$ mkdir build && cd build
$ cmake ..
$ make
An `umoria` directory will be created in the current directory containing the
game binary and data files, which can then be moved to any other location, such
as the `home` directory.
### Windows
MinGW is used to provide GCC and GNU 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, `pacman` can be used to install `GCC`, `ncurses`, and the
`make`/`cmake` build tools.
At present an environment variable for the MinGW system being compiled on will
need to be specified. 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
To perform an out-of-source build, type the following:
$ mkdir build
$ cd build
$ MINGW=mingw64 cmake ..
$ make
As with the macOS/Linux builds, the files will be installed into an `umoria` directory.
## Historical Documents
Most of the original documents included in the Umoria 5.6 sources have been
placed in 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.
There is also the original Moria Manual and FAQ. Although these are a little
outdated now, they are certainly worth reading as they contain a lot of
interesting and useful information.
## 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.13/src/ 0000755 0001750 0001750 00000000000 13720166710 012647 5 ustar ariel ariel umoria-5.7.13/src/wizard.cpp 0000644 0001750 0001750 00000041617 13720166710 014664 0 ustar ariel ariel // 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
// 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;
}
void wizardCureAll() {
(void) spellRemoveCurseFromAllItems();
(void) playerCureBlindness();
(void) playerCureConfusion();
(void) playerCurePoison();
(void) playerRemoveFear();
(void) playerStatRestore(PlayerAttr::A_STR);
(void) playerStatRestore(PlayerAttr::A_INT);
(void) playerStatRestore(PlayerAttr::A_WIS);
(void) playerStatRestore(PlayerAttr::A_CON);
(void) playerStatRestore(PlayerAttr::A_DEX);
(void) playerStatRestore(PlayerAttr::A_CHR);
if (py.flags.slow > 1) {
py.flags.slow = 1;
}
if (py.flags.image > 1) {
py.flags.image = 1;
}
}
// Generate random items
void wizardDropRandomItems() {
int i;
if (game.command_count > 0) {
i = game.command_count;
game.command_count = 0;
} else {
i = 1;
}
dungeonPlaceRandomObjectNear(py.pos, i);
drawDungeonPanel();
}
// Go up/down to specified depth
void wizardJumpLevel() {
int i;
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();
}
}
// Increase Experience
void wizardGainExperience() {
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();
}
// Summon a random monster
void wizardSummonMonster() {
Coord_t coord = Coord_t{py.pos.y, py.pos.x};
(void) monsterSummon(coord, true);
updateMonsters(false);
}
// Light up the dungeon -RAK-
void wizardLightUpDungeon() {
bool flag;
flag = !dg.floor[py.pos.y][py.pos.x].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[PlayerAttr::A_STR] = (uint8_t) number;
(void) playerStatRestore(PlayerAttr::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[PlayerAttr::A_INT] = (uint8_t) number;
(void) playerStatRestore(PlayerAttr::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[PlayerAttr::A_WIS] = (uint8_t) number;
(void) playerStatRestore(PlayerAttr::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[PlayerAttr::A_DEX] = (uint8_t) number;
(void) playerStatRestore(PlayerAttr::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[PlayerAttr::A_CON] = (uint8_t) number;
(void) playerStatRestore(PlayerAttr::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[PlayerAttr::A_CHR] = (uint8_t) number;
(void) playerStatRestore(PlayerAttr::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 <= SHRT_MAX) {
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 <= SHRT_MAX) {
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;
}
Coord_t coord = Coord_t{0, 0};
for (int i = 0; i < 10; i++) {
coord.y = py.pos.y - 3 + randomNumber(5);
coord.x = py.pos.x - 4 + randomNumber(7);
if (coordInBounds(coord) && dg.floor[coord.y][coord.x].feature_id <= MAX_CAVE_FLOOR && dg.floor[coord.y][coord.x].treasure_id == 0) {
// delete any object at location, before call popt()
if (dg.floor[coord.y][coord.x].treasure_id != 0) {
(void) dungeonDeleteObject(coord);
}
// place the object
int free_treasure_id = popt();
dg.floor[coord.y][coord.x].treasure_id = (uint8_t) free_treasure_id;
inventoryItemCopyTo(id, game.treasure.list[free_treasure_id]);
magicTreasureMagicalAbility(free_treasure_id, dg.current_level);
// auto identify the item
itemIdentify(game.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.pos.y][py.pos.x];
if (tile.treasure_id != 0) {
(void) dungeonDeleteObject(py.pos);
}
number = popt();
game.treasure.list[number] = forge;
tile.treasure_id = (uint8_t) number;
printMessage("Allocated.");
} else {
printMessage("Aborted.");
}
}
umoria-5.7.13/src/staves.cpp 0000644 0001750 0001750 00000036277 13720166710 014677 0 ustar ariel ariel // 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 {
StaffLight = 1, // TODO: name would clash with PlayerEquipment::Light
DetectDoorsStairs,
TrapLocation,
TreasureLocation,
ObjectLocation,
Teleportation,
Earthquakes,
Summoning,
// skipping 9
Destruction = 10,
Starlight,
HasteMonsters,
SlowMonsters,
SleepMonsters,
CureLightWounds,
DetectInvisible,
Speed,
Slowness,
MassPolymorph,
RemoveCurse,
DetectEvil,
Curing,
DispelEvil,
// skipping 24
Darkness = 25,
// skipping lots...
StoreBoughtFlag = 32,
};
static bool staffPlayerIsCarrying(int &item_pos_start, int &item_pos_end) {
if (py.pack.unique_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(PlayerAttr::A_INT);
chance -= item.depth_first_found - 5;
chance += class_level_adj[py.misc.class_id][PlayerClassLevelAdj::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::StaffLight:
identified = spellLightArea(py.pos);
break;
case StaffSpellTypes::DetectDoorsStairs:
identified = spellDetectSecretDoorssWithinVicinity();
break;
case StaffSpellTypes::TrapLocation:
identified = spellDetectTrapsWithinVicinity();
break;
case StaffSpellTypes::TreasureLocation:
identified = spellDetectTreasureWithinVicinity();
break;
case StaffSpellTypes::ObjectLocation:
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++) {
Coord_t coord = py.pos;
identified |= monsterSummon(coord, false);
}
break;
case StaffSpellTypes::Destruction:
identified = true;
spellDestroyArea(py.pos);
break;
case StaffSpellTypes::Starlight:
identified = true;
spellStarlite(py.pos);
break;
case StaffSpellTypes::HasteMonsters:
identified = spellSpeedAllMonsters(1);
break;
case StaffSpellTypes::SlowMonsters:
identified = spellSpeedAllMonsters(-1);
break;
case StaffSpellTypes::SleepMonsters:
identified = spellSleepAllMonsters();
break;
case StaffSpellTypes::CureLightWounds:
identified = spellChangePlayerHitPoints(randomNumber(8));
break;
case StaffSpellTypes::DetectInvisible:
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::MassPolymorph:
identified = spellMassPolymorph();
break;
case StaffSpellTypes::RemoveCurse:
if (spellRemoveCurseFromAllItems()) {
if (py.flags.blind < 1) {
printMessage("The staff glows blue for a moment..");
}
identified = true;
}
break;
case StaffSpellTypes::DetectEvil:
identified = spellDetectEvil();
break;
case StaffSpellTypes::Curing:
if (playerCureBlindness() || playerCurePoison() || playerCureConfusion()) {
identified = true;
}
break;
case StaffSpellTypes::DispelEvil:
identified = spellDispelCreature(config::monsters::defense::CD_EVIL, 60);
break;
case StaffSpellTypes::Darkness:
identified = spellDarkenArea(py.pos);
break;
case StaffSpellTypes::StoreBoughtFlag:
// store bought flag
break;
default:
// All cases are handled, so this should never be reached!
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 = py.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(py.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 {
WandLight = 1, // TODO: name would clash with PlayerEquipment::Light
LightningBolt,
FrostBolt,
FireBolt,
StoneToMud,
Polymorph,
HealMonster,
HasteMonster,
SlowMonster,
ConfuseMonster,
SleepMonster,
DrainLife,
TrapDoorDestruction,
WandMagicMissile, // TODO: name would clash with MagicSpellFlags::MagicMissile
WallBuilding,
CloneMonster,
TeleportAway,
Disarming,
LightningBall,
ColdBall,
FireBall,
StinkingCloud,
AcidBall,
Wonder,
};
static bool wandDischarge(Inventory_t &item, int direction) {
// decrement "use" variable
item.misc_use--;
bool identified = false;
uint32_t flags = item.flags;
Coord_t coord = Coord_t{0, 0};
while (flags != 0) {
coord.y = py.pos.y;
coord.x = py.pos.x;
// Wand types
switch ((WandSpellTypes)(getAndClearFirstBit(flags) + 1)) {
case WandSpellTypes::WandLight:
printMessage("A line of blue shimmering light appears.");
spellLightLine(py.pos, direction);
identified = true;
break;
case WandSpellTypes::LightningBolt:
spellFireBolt(coord, direction, diceRoll(Dice_t{4, 8}), MagicSpellFlags::Lightning, spell_names[8]);
identified = true;
break;
case WandSpellTypes::FrostBolt:
spellFireBolt(coord, direction, diceRoll(Dice_t{6, 8}), MagicSpellFlags::Frost, spell_names[14]);
identified = true;
break;
case WandSpellTypes::FireBolt:
spellFireBolt(coord, direction, diceRoll(Dice_t{9, 8}), MagicSpellFlags::Fire, spell_names[22]);
identified = true;
break;
case WandSpellTypes::StoneToMud:
identified = spellWallToMud(coord, direction);
break;
case WandSpellTypes::Polymorph:
identified = spellPolymorphMonster(coord, direction);
break;
case WandSpellTypes::HealMonster:
identified = spellChangeMonsterHitPoints(coord, direction, -diceRoll(Dice_t{4, 6}));
break;
case WandSpellTypes::HasteMonster:
identified = spellSpeedMonster(coord, direction, 1);
break;
case WandSpellTypes::SlowMonster:
identified = spellSpeedMonster(coord, direction, -1);
break;
case WandSpellTypes::ConfuseMonster:
identified = spellConfuseMonster(coord, direction);
break;
case WandSpellTypes::SleepMonster:
identified = spellSleepMonster(coord, direction);
break;
case WandSpellTypes::DrainLife:
identified = spellDrainLifeFromMonster(coord, direction);
break;
case WandSpellTypes::TrapDoorDestruction:
identified = spellDestroyDoorsTrapsInDirection(coord, direction);
break;
case WandSpellTypes::WandMagicMissile:
spellFireBolt(coord, direction, diceRoll(Dice_t{2, 6}), MagicSpellFlags::MagicMissile, spell_names[0]);
identified = true;
break;
case WandSpellTypes::WallBuilding:
identified = spellBuildWall(coord, direction);
break;
case WandSpellTypes::CloneMonster:
identified = spellCloneMonster(coord, direction);
break;
case WandSpellTypes::TeleportAway:
identified = spellTeleportAwayMonsterInDirection(coord, direction);
break;
case WandSpellTypes::Disarming:
identified = spellDisarmAllInDirection(coord, direction);
break;
case WandSpellTypes::LightningBall:
spellFireBall(coord, direction, 32, MagicSpellFlags::Lightning, "Lightning Ball");
identified = true;
break;
case WandSpellTypes::ColdBall:
spellFireBall(coord, direction, 48, MagicSpellFlags::Frost, "Cold Ball");
identified = true;
break;
case WandSpellTypes::FireBall:
spellFireBall(coord, direction, 72, MagicSpellFlags::Fire, spell_names[28]);
identified = true;
break;
case WandSpellTypes::StinkingCloud:
spellFireBall(coord, direction, 12, MagicSpellFlags::PoisonGas, spell_names[6]);
identified = true;
break;
case WandSpellTypes::AcidBall:
spellFireBall(coord, direction, 60, MagicSpellFlags::Acid, "Acid Ball");
identified = true;
break;
case WandSpellTypes::Wonder:
flags = (uint32_t)(1L << (randomNumber(23) - 1));
break;
default:
// All cases are handled, so this should never be reached!
printMessage("Internal error in wands()");
break;
}
}
return identified;
}
// Wands for the aiming.
void wandAim() {
game.player_free_turn = true;
if (py.pack.unique_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 = py.inventory[item_id];
int player_class_lev_adj = class_level_adj[py.misc.class_id][PlayerClassLevelAdj::DEVICE] * py.misc.level / 3;
int chance = py.misc.saving_throw + playerStatAdjustmentWisdomIntelligence(PlayerAttr::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(py.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.13/src/data_player.cpp 0000644 0001750 0001750 00000057475 13720166710 015662 0 ustar ariel ariel // 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.13/src/dungeon_los.cpp 0000644 0001750 0001750 00000045724 13720166710 015703 0 ustar ariel ariel // 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(Coord_t from, Coord_t to) {
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.pos.y + los_fyx * (ray x) + los_fyy * (ray y)
dungeon x = py.pos.x + 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(Coord_t coord, 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(Coord_t{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);
}
}
los_rocks_and_objects++;
} 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(Coord_t{y, x}, 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(Coord_t{y, x}, 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(Coord_t{y, x}, transparent)) {
return true;
}
} while (transparent);
}
}
static bool lookSee(Coord_t coord, bool &transparent) {
if (coord.x < 0 || coord.y < 0 || coord.y > coord.x) {
obj_desc_t error_message = {'\0'};
(void) sprintf(error_message, "Illegal call to lookSee(%d, %d)", coord.y, coord.x);
printMessage(error_message);
}
const char *description = nullptr;
if (coord.x == 0 && coord.y == 0) {
description = "You are on";
} else {
description = "You see";
}
int j = py.pos.x + los_fxx * coord.x + los_fxy * coord.y;
coord.y = py.pos.y + los_fyx * coord.x + los_fyy * coord.y;
coord.x = j;
if (!coordInsidePanel(coord)) {
transparent = false;
return false;
}
Tile_t const &tile = dg.floor[coord.y][coord.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);
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 (game.treasure.list[tile.treasure_id].category_id == TV_SECRET_DOOR) {
goto granite;
}
if (los_rocks_and_objects == 0 && game.treasure.list[tile.treasure_id].category_id != TV_INVIS_TRAP) {
obj_desc_t obj_string = {'\0'};
itemDescription(obj_string, game.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);
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);
query = getKeyInput();
}
}
}
if (msg[0] != 0) {
los_num_places_seen++;
if (query == ESCAPE) {
return true;
}
}
return false;
}
umoria-5.7.13/src/rng.cpp 0000644 0001750 0001750 00000006426 13720166710 014151 0 ustar ariel ariel // 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 = INT_MAX; // 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.13/src/inventory.h 0000644 0001750 0001750 00000010533 13720166710 015057 0 ustar ariel ariel // 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 PlayerEquipment {
Wield = 22, // must be first item in equipment list
Head,
Neck,
Body,
Arm,
Hands,
Right,
Left,
Feet,
Outer,
Light,
Auxiliary,
};
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.13/src/dice.h 0000644 0001750 0001750 00000000645 13720166710 013731 0 ustar ariel ariel // 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.13/src/scrolls.cpp 0000644 0001750 0001750 00000044336 13720166710 015046 0 ustar ariel ariel // 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.pack.unique_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 (py.inventory[PlayerEquipment::Body].category_id != TV_NOTHING) {
items[item_count] = PlayerEquipment::Body;
item_count++;
}
if (py.inventory[PlayerEquipment::Arm].category_id != TV_NOTHING) {
items[item_count] = PlayerEquipment::Arm;
item_count++;
}
if (py.inventory[PlayerEquipment::Outer].category_id != TV_NOTHING) {
items[item_count] = PlayerEquipment::Outer;
item_count++;
}
if (py.inventory[PlayerEquipment::Hands].category_id != TV_NOTHING) {
items[item_count] = PlayerEquipment::Hands;
item_count++;
}
if (py.inventory[PlayerEquipment::Head].category_id != TV_NOTHING) {
items[item_count] = PlayerEquipment::Head;
item_count++;
}
// also enchant boots
if (py.inventory[PlayerEquipment::Feet].category_id != TV_NOTHING) {
items[item_count] = PlayerEquipment::Feet;
item_count++;
}
int item_id = 0;
if (item_count > 0) {
item_id = items[randomNumber(item_count) - 1];
}
if ((py.inventory[PlayerEquipment::Body].flags & config::treasure::flags::TR_CURSED) != 0u) {
item_id = PlayerEquipment::Body;
} else if ((py.inventory[PlayerEquipment::Arm].flags & config::treasure::flags::TR_CURSED) != 0u) {
item_id = PlayerEquipment::Arm;
} else if ((py.inventory[PlayerEquipment::Outer].flags & config::treasure::flags::TR_CURSED) != 0u) {
item_id = PlayerEquipment::Outer;
} else if ((py.inventory[PlayerEquipment::Head].flags & config::treasure::flags::TR_CURSED) != 0u) {
item_id = PlayerEquipment::Head;
} else if ((py.inventory[PlayerEquipment::Hands].flags & config::treasure::flags::TR_CURSED) != 0u) {
item_id = PlayerEquipment::Hands;
} else if ((py.inventory[PlayerEquipment::Feet].flags & config::treasure::flags::TR_CURSED) != 0u) {
item_id = PlayerEquipment::Feet;
}
return item_id;
}
static bool scrollEnchantWeaponToHit() {
Inventory_t &item = py.inventory[PlayerEquipment::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 = py.inventory[PlayerEquipment::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 = py.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 = &py.inventory[item_id];
while (item_id > 0 && (item->category_id != TV_SCROLL1 || item->flags != 0x00000008)) {
item_id--;
item = &py.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;
Coord_t coord = Coord_t{0, 0};
for (int i = 0; i < randomNumber(3); i++) {
coord.y = py.pos.y;
coord.x = py.pos.x;
identified |= monsterSummon(coord, 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 = py.inventory[PlayerEquipment::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 = py.inventory[PlayerEquipment::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 = py.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 (py.inventory[PlayerEquipment::Body].category_id != TV_NOTHING && randomNumber(4) == 1) {
item_id = PlayerEquipment::Body;
} else if (py.inventory[PlayerEquipment::Arm].category_id != TV_NOTHING && randomNumber(3) == 1) {
item_id = PlayerEquipment::Arm;
} else if (py.inventory[PlayerEquipment::Outer].category_id != TV_NOTHING && randomNumber(3) == 1) {
item_id = PlayerEquipment::Outer;
} else if (py.inventory[PlayerEquipment::Head].category_id != TV_NOTHING && randomNumber(3) == 1) {
item_id = PlayerEquipment::Head;
} else if (py.inventory[PlayerEquipment::Hands].category_id != TV_NOTHING && randomNumber(3) == 1) {
item_id = PlayerEquipment::Hands;
} else if (py.inventory[PlayerEquipment::Feet].category_id != TV_NOTHING && randomNumber(3) == 1) {
item_id = PlayerEquipment::Feet;
} else if (py.inventory[PlayerEquipment::Body].category_id != TV_NOTHING) {
item_id = PlayerEquipment::Body;
} else if (py.inventory[PlayerEquipment::Arm].category_id != TV_NOTHING) {
item_id = PlayerEquipment::Arm;
} else if (py.inventory[PlayerEquipment::Outer].category_id != TV_NOTHING) {
item_id = PlayerEquipment::Outer;
} else if (py.inventory[PlayerEquipment::Head].category_id != TV_NOTHING) {
item_id = PlayerEquipment::Head;
} else if (py.inventory[PlayerEquipment::Hands].category_id != TV_NOTHING) {
item_id = PlayerEquipment::Hands;
} else if (py.inventory[PlayerEquipment::Feet].category_id != TV_NOTHING) {
item_id = PlayerEquipment::Feet;
} else {
item_id = 0;
}
if (item_id <= 0) {
return false;
}
Inventory_t &item = py.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;
Coord_t coord = Coord_t{0, 0};
for (int i = 0; i < randomNumber(3); i++) {
coord.y = py.pos.y;
coord.x = py.pos.x;
identified |= monsterSummonUndead(coord);
}
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 = &py.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.pos);
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.pos);
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.pos);
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.pos);
identified = true;
break;
default:
printMessage("Internal error in scroll()");
break;
}
}
item = &py.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(py.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.13/src/ui.cpp 0000644 0001750 0001750 00000061674 13720166710 014006 0 ustar ariel ariel // 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) {
Coord_t panel = Coord_t{dg.panel.row, dg.panel.col};
if (force || coord.y < dg.panel.top + 2 || coord.y > dg.panel.bottom - 2) {
panel.y = (coord.y - SCREEN_HEIGHT / 4) / (SCREEN_HEIGHT / 2);
if (panel.y > dg.panel.max_rows) {
panel.y = dg.panel.max_rows;
} else if (panel.y < 0) {
panel.y = 0;
}
}
if (force || coord.x < dg.panel.left + 3 || coord.x > dg.panel.right - 3) {
panel.x = ((coord.x - SCREEN_WIDTH / 4) / (SCREEN_WIDTH / 2));
if (panel.x > dg.panel.max_cols) {
panel.x = dg.panel.max_cols;
} else if (panel.x < 0) {
panel.x = 0;
}
}
if (panel.y != dg.panel.row || panel.x != dg.panel.col) {
dg.panel.row = panel.y;
dg.panel.col = panel.x;
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;
Coord_t coord = Coord_t{0, 0};
// Top to bottom
for (coord.y = dg.panel.top; coord.y <= dg.panel.bottom; coord.y++) {
eraseLine(Coord_t{line, 13});
line++;
// Left to right
for (coord.x = dg.panel.left; coord.x <= dg.panel.right; coord.x++) {
char ch = caveGetTileSymbol(coord);
if (ch != ' ') {
panelPutTile(ch, coord);
}
}
}
}
// 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.pos.y][py.pos.x];
// Check for new panel
if (coordOutsidePanel(py.pos, false)) {
drawDungeonPanel();
}
// Move the light source
dungeonMoveCharacterLight(py.pos, py.pos);
// A room of light should be lit.
if (tile.feature_id == TILE_LIGHT_FLOOR) {
if (py.flags.blind < 1 && !tile.permanent_light) {
dungeonLightRoom(py.pos);
}
return;
}
// In doorway of light-room?
if (tile.perma_lit_room && py.flags.blind < 1) {
for (int i = py.pos.y - 1; i <= py.pos.y + 1; i++) {
for (int j = py.pos.x - 1; j <= py.pos.x + 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
void statsAsString(uint8_t stat, char *stat_string) {
int percentile = stat - 18;
if (stat <= 18) {
(void) sprintf(stat_string, "%6d", stat);
} else if (percentile == 100) {
(void) strcpy(stat_string, "18/100");
} else {
(void) sprintf(stat_string, " 18/%02d", percentile);
}
}
// 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, Coord_t coord) {
// blank out the current field space
putString(&blank_string[BLANK_LENGTH - 13], coord);
putString(info, coord);
}
// Print long number with header at given row, column
static void printHeaderLongNumber(const char *header, int32_t num, Coord_t coord) {
vtype_t str = {'\0'};
(void) sprintf(str, "%s: %6d", header, num);
putString(str, coord);
}
// Print long number (7 digits of space) with header at given row, column
static void printHeaderLongNumber7Spaces(const char *header, int32_t num, Coord_t coord) {
vtype_t str = {'\0'};
(void) sprintf(str, "%s: %7d", header, num);
putString(str, coord);
}
// Print number with header at given row, column -RAK-
static void printHeaderNumber(const char *header, int num, Coord_t coord) {
vtype_t str = {'\0'};
(void) sprintf(str, "%s: %6d", header, num);
putString(str, coord);
}
// Print long number at given row, column
static void printLongNumber(int32_t num, Coord_t coord) {
vtype_t str = {'\0'};
(void) sprintf(str, "%6d", num);
putString(str, coord);
}
// Print number at given row, column -RAK-
static void printNumber(int num, Coord_t coord) {
vtype_t str = {'\0'};
(void) sprintf(str, "%6d", num);
putString(str, coord);
}
// Prints title of character -RAK-
void printCharacterTitle() {
printCharacterInfoInField(playerRankTitle(), Coord_t{4, STAT_COLUMN});
}
// Prints level -RAK-
void printCharacterLevel() {
printNumber((int) py.misc.level, Coord_t{13, STAT_COLUMN + 6});
}
// Prints players current mana points. -RAK-
void printCharacterCurrentMana() {
printNumber(py.misc.current_mana, Coord_t{15, STAT_COLUMN + 6});
}
// Prints Max hit points -RAK-
void printCharacterMaxHitPoints() {
printNumber(py.misc.max_hp, Coord_t{16, STAT_COLUMN + 6});
}
// Prints players current hit points -RAK-
void printCharacterCurrentHitPoints() {
printNumber(py.misc.current_hp, Coord_t{17, STAT_COLUMN + 6});
}
// prints current AC -RAK-
void printCharacterCurrentArmorClass() {
printNumber(py.misc.display_ac, Coord_t{19, STAT_COLUMN + 6});
}
// Prints current gold -RAK-
void printCharacterGoldValue() {
printLongNumber(py.misc.au, Coord_t{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 rest_string[16];
if (py.flags.rest < 0) {
(void) strcpy(rest_string, "Rest *");
} else if (config::options::display_counts) {
(void) sprintf(rest_string, "Rest %-5d", py.flags.rest);
} else {
(void) strcpy(rest_string, "Rest");
}
putString(rest_string, Coord_t{23, 38});
return;
}
if (game.command_count > 0) {
char repeat_string[16];
if (config::options::display_counts) {
(void) sprintf(repeat_string, "Repeat %-3d", game.command_count);
} else {
(void) strcpy(repeat_string, "Repeat");
}
py.flags.status |= config::player::status::PY_REPEAT;
putString(repeat_string, 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, Coord_t{2, STAT_COLUMN});
printCharacterInfoInField(classes[py.misc.class_id].title, Coord_t{3, STAT_COLUMN});
printCharacterInfoInField(playerRankTitle(), Coord_t{4, STAT_COLUMN});
for (int i = 0; i < 6; i++) {
displayCharacterStats(i);
}
printHeaderNumber("LEV ", (int) py.misc.level, Coord_t{13, STAT_COLUMN});
printHeaderLongNumber("EXP ", py.misc.exp, Coord_t{14, STAT_COLUMN});
printHeaderNumber("MANA", py.misc.current_mana, Coord_t{15, STAT_COLUMN});
printHeaderNumber("MHP ", py.misc.max_hp, Coord_t{16, STAT_COLUMN});
printHeaderNumber("CHP ", py.misc.current_hp, Coord_t{17, STAT_COLUMN});
printHeaderNumber("AC ", py.misc.display_ac, Coord_t{19, STAT_COLUMN});
printHeaderLongNumber("GOLD", py.misc.au, Coord_t{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, Coord_t{9, 1});
printHeaderNumber("+ To Damage ", py.misc.display_to_damage, Coord_t{10, 1});
printHeaderNumber("+ To AC ", py.misc.display_to_ac, Coord_t{11, 1});
printHeaderNumber(" Total AC ", py.misc.display_ac, Coord_t{12, 1});
}
// Returns a rating of x depending on y -JWT-
const char *statRating(Coord_t coord) {
switch (coord.x / coord.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, Coord_t{2, 38});
printHeaderNumber("Height ", (int) py.misc.height, Coord_t{3, 38});
printHeaderNumber("Weight ", (int) py.misc.weight, Coord_t{4, 38});
printHeaderNumber("Social Class ", (int) py.misc.social_class, Coord_t{5, 38});
}
// Prints the following information on the screen. -JWT-
void printCharacterLevelExperience() {
printHeaderLongNumber7Spaces("Level ", (int32_t) py.misc.level, Coord_t{9, 28});
printHeaderLongNumber7Spaces("Experience ", py.misc.exp, Coord_t{10, 28});
printHeaderLongNumber7Spaces("Max Exp ", py.misc.max_exp, Coord_t{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), Coord_t{12, 28});
}
printHeaderLongNumber7Spaces("Gold ", py.misc.au, Coord_t{13, 28});
printHeaderNumber("Max Hit Points ", py.misc.max_hp, Coord_t{9, 52});
printHeaderNumber("Cur Hit Points ", py.misc.current_hp, Coord_t{10, 52});
printHeaderNumber("Max Mana ", py.misc.mana, Coord_t{11, 52});
printHeaderNumber("Cur Mana ", py.misc.current_mana, Coord_t{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][PlayerClassLevelAdj::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][PlayerClassLevelAdj::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(PlayerAttr::A_INT) +
(class_level_adj[py.misc.class_id][PlayerClassLevelAdj::DISARM] * py.misc.level / 3);
int xsave =
py.misc.saving_throw + playerStatAdjustmentWisdomIntelligence(PlayerAttr::A_WIS) + (class_level_adj[py.misc.class_id][PlayerClassLevelAdj::SAVE] * py.misc.level / 3);
int xdev =
py.misc.saving_throw + playerStatAdjustmentWisdomIntelligence(PlayerAttr::A_INT) + (class_level_adj[py.misc.class_id][PlayerClassLevelAdj::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(Coord_t{12, xbth}), Coord_t{16, 15});
putString("Bows/Throw :", Coord_t{17, 1});
putString(statRating(Coord_t{12, xbthb}), Coord_t{17, 15});
putString("Saving Throw:", Coord_t{18, 1});
putString(statRating(Coord_t{6, xsave}), Coord_t{18, 15});
putString("Stealth :", Coord_t{16, 28});
putString(statRating(Coord_t{1, xstl}), Coord_t{16, 42});
putString("Disarming :", Coord_t{17, 28});
putString(statRating(Coord_t{8, xdis}), Coord_t{17, 42});
putString("Magic Device:", Coord_t{18, 28});
putString(statRating(Coord_t{6, xdev}), Coord_t{18, 42});
putString("Perception :", Coord_t{16, 55});
putString(statRating(Coord_t{3, xfos}), Coord_t{16, 69});
putString("Searching :", Coord_t{17, 55});
putString(statRating(Coord_t{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;
if (classes[py.misc.class_id].class_to_use_mage_spells == config::spells::SPELL_TYPE_MAGE) {
consecutive_offset = config::spells::NAME_OFFSET_SPELLS;
} else {
consecutive_offset = 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(PlayerAttr::A_INT);
playerGainMana(PlayerAttr::A_INT);
} else if (player_class.class_to_use_mage_spells == config::spells::SPELL_TYPE_PRIEST) {
playerCalculateAllowedSpellsCount(PlayerAttr::A_WIS);
playerGainMana(PlayerAttr::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, Coord_t{14, STAT_COLUMN + 6});
}
umoria-5.7.13/src/monster.cpp 0000644 0001750 0001750 00000173420 13720166710 015051 0 ustar ariel ariel // 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.pos.y][monster.pos.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.pos.y, monster.pos.x})) {
if (game.wizard_mode) {
// Wizard sight.
visible = true;
} else if (los(py.pos, monster.pos)) {
visible = monsterIsVisible(monster);
}
}
if (visible) {
// Light it up.
if (!monster.lit) {
playerDisturb(1, 0);
monster.lit = true;
dungeonLiteSpot(Coord_t{monster.pos.y, monster.pos.x});
// notify inventoryExecuteCommand()
screen_has_changed = true;
}
} else if (monster.lit) {
// Turn it off.
monster.lit = false;
dungeonLiteSpot(Coord_t{monster.pos.y, monster.pos.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(Coord_t coord) {
int monster_id = dg.floor[coord.y][coord.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].pos.y - py.pos.y;
int x = monsters[monster_id].pos.x - py.pos.x;
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] < UCHAR_MAX) {
creature_recall[monster.creature_id].attacks[attack_counter]++;
}
if (game.character_is_dead && creature_recall[monster.creature_id].deaths < SHRT_MAX) {
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, Coord_t coord) {
Inventory_t &item = game.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);
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);
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, Coord_t coord) {
if (randomNumber(config::treasure::OBJECTS_RUNE_PROTECTION) < creatures_list[creature_id].level) {
if (coord.y == py.pos.y && coord.x == py.pos.x) {
printMessage("The rune of protection is broken!");
}
(void) dungeonDeleteObject(coord);
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, Coord_t coord) {
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 && (coord.y != monster.pos.y || coord.x != monster.pos.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, Coord_t coord) {
// Pick up or eat an object
if ((move_bits & config::monsters::move::CM_PICKS_UP) != 0u) {
uint8_t treasure_id = dg.floor[coord.y][coord.x].treasure_id;
if (treasure_id != 0 && game.treasure.list[treasure_id].category_id <= TV_MAX_OBJECT) {
rcmove |= config::monsters::move::CM_PICKS_UP;
(void) dungeonDeleteObject(coord);
}
}
// Move creature record
dungeonMoveCreatureRecord(Coord_t{monster.pos.y, monster.pos.x}, coord);
if (monster.lit) {
monster.lit = false;
dungeonLiteSpot(Coord_t{monster.pos.y, monster.pos.x});
}
monster.pos.y = coord.y;
monster.pos.x = coord.x;
monster.distance_from_player = (uint8_t) coordDistanceBetween(py.pos, coord);
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.
Coord_t coord = Coord_t{0, 0};
for (int i = 0; !do_turn && i < 5; i++) {
// Get new position
coord.y = monster.pos.y;
coord.x = monster.pos.x;
(void) playerMovePosition(directions[i], coord);
Tile_t &tile = dg.floor[coord.y][coord.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, coord);
}
// Glyph of warding present?
if (do_move && tile.treasure_id != 0 && game.treasure.list[tile.treasure_id].category_id == TV_VIS_TRAP && game.treasure.list[tile.treasure_id].sub_category_id == 99) {
glyphOfWardingProtection(monster.creature_id, move_bits, do_move, do_turn, coord);
}
// Creature has attempted to move on player?
if (do_move) {
monsterMovesOnPlayer(monster, tile.creature_id, monster_id, move_bits, do_move, do_turn, rcmove, coord);
}
// Creature has been allowed move.
if (do_move) {
monsterAllowedToMove(monster, move_bits, do_turn, rcmove, coord);
}
}
}
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.pos, monster.pos);
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) {
Coord_t coord = py.pos; // only used for cases 14 and 15.
// 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(Coord_t{monster.pos.y, monster.pos.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);
coord.y = py.pos.y;
coord.x = py.pos.x;
// in case compact_monster() is called,it needs monster_id
hack_monptr = monster_id;
(void) monsterSummon(coord, false);
hack_monptr = -1;
monsterUpdateVisibility((int) dg.floor[coord.y][coord.x].creature_id);
break;
case 15: // Summon Undead
(void) strcat(monster_name, "magically summons an undead!");
printMessage(monster_name);
coord.y = py.pos.y;
coord.x = py.pos.x;
// in case compact_monster() is called,it needs monster_id
hack_monptr = monster_id;
(void) monsterSummonUndead(coord);
hack_monptr = -1;
monsterUpdateVisibility((int) dg.floor[coord.y][coord.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 num = (randomNumber((int) level) >> 1) + 1;
if (num > py.misc.current_mana) {
num = py.misc.current_mana;
py.misc.current_mana = 0;
py.misc.current_mana_fraction = 0;
} else {
py.misc.current_mana -= num;
}
printCharacterCurrentMana();
monster.hp += 6 * (num);
}
break;
case 20: // Breath Light
(void) strcat(monster_name, "breathes lightning.");
printMessage(monster_name);
spellBreath(py.pos, monster_id, (monster.hp / 4), MagicSpellFlags::Lightning, death_description);
break;
case 21: // Breath Gas
(void) strcat(monster_name, "breathes gas.");
printMessage(monster_name);
spellBreath(py.pos, monster_id, (monster.hp / 3), MagicSpellFlags::PoisonGas, death_description);
break;
case 22: // Breath Acid
(void) strcat(monster_name, "breathes acid.");
printMessage(monster_name);
spellBreath(py.pos, monster_id, (monster.hp / 3), MagicSpellFlags::Acid, death_description);
break;
case 23: // Breath Frost
(void) strcat(monster_name, "breathes frost.");
printMessage(monster_name);
spellBreath(py.pos, monster_id, (monster.hp / 3), MagicSpellFlags::Frost, death_description);
break;
case 24: // Breath Fire
(void) strcat(monster_name, "breathes fire.");
printMessage(monster_name);
spellBreath(py.pos, monster_id, (monster.hp / 3), MagicSpellFlags::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 < SHRT_MAX) {
creature_recall[monster.creature_id].deaths++;
}
}
return true;
}
// Places creature adjacent to given location -RAK-
// Rats and Flys are fun!
bool monsterMultiply(Coord_t coord, int creature_id, int monster_id) {
Coord_t position = Coord_t{0, 0};
for (int i = 0; i <= 18; i++) {
position.y = coord.y - 2 + randomNumber(3);
position.x = coord.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(position) && (position.y != coord.y || position.x != coord.x)) {
Tile_t const &tile = dg.floor[position.y][position.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(position, creature_id, false);
hack_monptr = -1;
if (!result) {
return false;
}
monster_multiply_total++;
return monsterMakeVisible(position);
}
} 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(position, creature_id, false);
hack_monptr = -1;
if (!result) {
return false;
}
monster_multiply_total++;
return monsterMakeVisible(position);
}
}
}
}
return false;
}
static void monsterMultiplyCritter(Monster_t const &monster, int monster_id, uint32_t &rcmove) {
int counter = 0;
Coord_t coord = Coord_t{0, 0};
for (coord.y = monster.pos.y - 1; coord.y <= monster.pos.y + 1; coord.y++) {
for (coord.x = monster.pos.x - 1; coord.x <= monster.pos.x + 1; coord.x++) {
if (coordInBounds(coord) && (dg.floor[coord.y][coord.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(Coord_t{monster.pos.y, monster.pos.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.pos.y + 1; y >= (monster.pos.y - 1); y--) {
for (int x = monster.pos.x - 1; x <= monster.pos.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;
id++;
}
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.pos.y][monster.pos.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(Coord_t{monster.pos.y, monster.pos.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.pos.y][monster.pos.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] < UCHAR_MAX) {
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, uint32_t rcmove) {
if (!monster.lit) {
return;
}
Recall_t &memory = creature_recall[monster.creature_id];
if (wake) {
if (memory.wake < UCHAR_MAX) {
memory.wake++;
}
} else if (ignore) {
if (memory.ignore < UCHAR_MAX) {
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.pos.y][monster.pos.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(py.pos, Coord_t{monster.pos.y, monster.pos.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(Coord_t{monster.pos.y, monster.pos.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 < SHRT_MAX) {
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(Coord_t coord, 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, 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(Coord_t coord) {
bool asleep = false;
for (int y = coord.y - 1; y <= coord.y + 1 && y < MAX_HEIGHT; y++) {
for (int x = coord.x - 1; x <= coord.x + 1 && x < MAX_WIDTH; x++) {
uint8_t monster_id = dg.floor[y][x].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(PlayerAttr::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[PlayerAttr::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[PlayerAttr::A_DEX]) {
printMessage("You grab hold of your backpack!");
} else {
inventoryDestroyItem(randomNumber(py.pack.unique_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(PlayerAttr::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(PlayerAttr::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(PlayerAttr::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(PlayerAttr::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;
// NOTE: default handles this case
// case 99:
// noticed = false;
// break;
default:
noticed = false;
break;
}
return noticed;
}
umoria-5.7.13/src/curses.h 0000644 0001750 0001750 00000001017 13720166710 014323 0 ustar ariel ariel // 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
#elif __NetBSD__
#include
#else
#include
#endif
umoria-5.7.13/src/player.h 0000644 0001750 0001750 00000026305 13720166710 014322 0 ustar ariel ariel // 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 PlayerClassLevelAdj {
BTH,
BTHB,
DEVICE,
DISARM,
SAVE,
};
// Attribute indexes -CJS-
enum PlayerAttr {
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{};
Coord_t pos{}; // location in dungeon
char prev_dir = ' '; // Direction memory. -CJS-
// 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 = 0; // Tracker for number of turns taken during one run cycle
bool temporary_light_only = false; // Track if temporary light about player
int32_t max_score = 0; // Maximum score attained
struct {
int16_t unique_items = 0; // unique_inventory_items in pack
int16_t weight = 0; // Weight of currently carried items
int16_t heaviness = 0; // Heaviness of pack - used to calculate if pack is too heavy -CJS-
} pack;
Inventory_t inventory[PLAYER_INVENTORY_SIZE]{};
int16_t equipment_count = 0; // Number of equipped items
bool weapon_is_heavy = false; // Weapon is too heavy -CJS-
bool carrying_light = false; // `true` when player is carrying light
} Player_t;
extern Player_t py;
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, Coord_t &coord);
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(Coord_t coord, 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(Coord_t coord, int digging_ability, int digging_chance);
void playerAttackPosition(Coord_t coord);
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, Coord_t coord);
// 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);
int16_t playerToHitAdjustment();
int16_t playerArmorClassAdjustment();
int16_t playerDisarmAdjustment();
int16_t playerDamageAdjustment();
// player_throw.cpp
void playerThrowItem();
// player_traps.cpp
void playerDisarmTrap();
void chestTrap(Coord_t coord);
// player_tunnel.cpp
void playerTunnel(int direction);
// player_quaff.cpp
void quaff();
// player_pray.cpp
void pray();
umoria-5.7.13/src/config.cpp 0000644 0001750 0001750 00000042313 13720166710 014623 0 ustar ariel ariel // 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.
// clang-format off
#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";
} // namespace files
// 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
} // namespace options
// 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)
} // namespace objects
} // namespace dungeon
// 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;
} // namespace flags
// 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 chests
} // namespace treasure
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;
} // namespace move
// 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;
} // namespace spells
// 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 defense
} // namespace monsters
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 status
} // namespace player
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 identification
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 spells
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
} // namespace stores
} // namespace config
umoria-5.7.13/src/game.h 0000644 0001750 0001750 00000007756 13720166710 013750 0 ustar ariel ariel // 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
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
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 teleport_player = false; // Handle teleport traps
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.
struct {
int16_t current_id = 0; // Current treasure heap ptr
Inventory_t list[LEVEL_MAX_OBJECTS]{};
} treasure;
} Game_t;
extern Game_t game;
extern int16_t sorted_objects[MAX_DUNGEON_OBJECTS];
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();
void abortProgram(const char *msg);
// 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);
umoria-5.7.13/src/rng.h 0000644 0001750 0001750 00000000564 13720166710 013613 0 ustar ariel ariel // 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.13/src/dungeon_tile.h 0000644 0001750 0001750 00000003557 13720166710 015506 0 ustar ariel ariel // 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.13/src/version.h 0000644 0001750 0001750 00000001062 13720166710 014504 0 ustar ariel ariel // 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 = 13;
umoria-5.7.13/src/scrolls.h 0000644 0001750 0001750 00000000460 13720166710 014501 0 ustar ariel ariel // 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.13/src/scores.h 0000644 0001750 0001750 00000002136 13720166710 014320 0 ustar ariel ariel // 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.13/src/store.cpp 0000644 0001750 0001750 00000114155 13720166710 014516 0 ustar ariel ariel // 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 storeUpdateBargainingSkills(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_line_num;
for (item_line_num = (item_pos_start % 12); item_pos_start < item_pos_end; item_line_num++) {
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_line_num, description);
putStringClearToEOL(msg, Coord_t{item_line_num + 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_line_num + 5, 59});
item_pos_start++;
}
if (item_line_num < 12) {
for (int i = 0; i < (11 - item_line_num + 1); i++) {
// clear remaining lines
eraseLine(Coord_t{i + item_line_num + 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-
// Returns true if the item was found.
static bool storeGetItemId(int &item_id, const char *prompt, int item_pos_start, int item_pos_end) {
item_id = -1;
bool item_found = false;
vtype_t msg = {'\0'};
(void) sprintf(msg, "(Items %c-%c, ESC to exit) %s", item_pos_start + 'a', item_pos_end + 'a', prompt);
char key_char;
while (getCommand(msg, key_char)) {
key_char -= 'a';
if (key_char >= item_pos_start && key_char <= item_pos_end) {
item_found = true;
item_id = key_char;
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) {
return false;
}
// customer angered the store owner with too many insults!
printSpeechGetOutOfMyStore();
store.insults_counter = 0;
store.bad_purchases++;
store.turns_left_before_closing = dg.game_turn + 2500 + randomNumber(2500);
return true;
}
// 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-
// Returns true if the store owner was angered.
static bool storeHaggleInsults(int store_id) {
if (storeIncreaseInsults(store_id)) {
return true;
}
printSpeechTryAgain();
// keep insult separate from rest of haggle
printMessage(CNIL);
return false;
}
// Returns true if the customer made a valid offer
static bool storeGetHaggle(const char *prompt, int32_t &new_offer, int offer_count) {
bool valid_offer = true;
if (offer_count == 0) {
store_last_increment = 0;
}
bool increment = false;
int32_t adjustment = 0;
auto prompt_len = (int) strlen(prompt);
int start_len = prompt_len;
char *p = nullptr;
vtype_t msg = {'\0'};
vtype_t last_offer_str = {'\0'};
// Get a customers new offer
while (valid_offer && adjustment == 0) {
putStringClearToEOL(prompt, Coord_t{0, 0});
if ((offer_count != 0) && store_last_increment != 0) {
auto abs_store_last_increment = (int) std::abs((std::intmax_t) store_last_increment);
(void) sprintf(last_offer_str, "[%c%d] ", (store_last_increment < 0) ? '-' : '+', abs_store_last_increment);
putStringClearToEOL(last_offer_str, Coord_t{0, start_len});
prompt_len = start_len + (int) strlen(last_offer_str);
}
if (!getStringInput(msg, Coord_t{0, prompt_len}, 40)) {
// customer aborted, i.e. pressed escape
valid_offer = false;
}
for (p = msg; *p == ' '; p++) {
// fast forward to next space character
}
if (*p == '+' || *p == '-') {
increment = true;
}
if ((offer_count != 0) && increment) {
stringToNumber(msg, adjustment);
// 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 (adjustment == 0) {
increment = false;
} else {
store_last_increment = (int16_t) adjustment;
}
} else if ((offer_count != 0) && *msg == '\0') {
adjustment = store_last_increment;
increment = true;
} else {
stringToNumber(msg, adjustment);
}
// don't allow incremental haggling, if player has not made an offer yet
if (valid_offer && offer_count == 0 && increment) {
printMessage("You haven't even made your first offer yet!");
adjustment = 0;
increment = false;
}
}
if (valid_offer) {
if (increment) {
new_offer += adjustment;
} else {
new_offer = adjustment;
}
} else {
messageLineClear();
}
return valid_offer;
}
// The status of the customer bid.
// Note: a received bid may still result in a rejected offer.
enum class BidState {
Received = 0, // the big was received successfully
Rejected, // the bid was rejected, or cancelled by the customer
Offended, // customer tried to sell an undesirable item
Insulted, // the store owner was insulted too many times by the bid
};
static BidState storeReceiveOffer(int store_id, const char *prompt, int32_t &new_offer, int32_t last_offer, int offer_count, int factor) {
BidState status = BidState::Received;
bool done = false;
while (!done) {
if (storeGetHaggle(prompt, new_offer, offer_count)) {
// customer submitted valid offer
if (new_offer * factor >= last_offer * factor) {
done = true;
} else if (storeHaggleInsults(store_id)) {
// customer angered the store owner!
status = BidState::Insulted;
done = true;
} else {
// new_offer rejected, reset new_offer so that incremental
// haggling works correctly
new_offer = last_offer;
}
} else {
// customer aborted offer
status = BidState::Rejected;
done = true;
}
}
return status;
}
static void storePurchaseCustomerAdjustment(int32_t &min_sell, int32_t &max_sell) {
int charisma = playerStatAdjustmentCharisma();
max_sell = max_sell * charisma / 100;
if (max_sell <= 0) {
max_sell = 1;
}
min_sell = min_sell * charisma / 100;
if (min_sell <= 0) {
min_sell = 1;
}
}
// Haggling routine -RAK-
static BidState storePurchaseHaggle(int store_id, int32_t &price, Inventory_t const &item) {
BidState status = BidState::Received;
int32_t new_price = 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);
storePurchaseCustomerAdjustment(min_sell, max_sell);
// 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;
}
displayStoreHaggleCommands(1);
int32_t final_asking_price = min_sell;
int32_t current_asking_price = max_sell;
const char *comment = "Asking";
bool accepted_without_haggle = false;
int offers_count = 0; // this prevents incremental haggling on first try
// 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";
accepted_without_haggle = true;
// Set up automatic increment, so that a return will accept the final price.
store_last_increment = (int16_t) min_sell;
offers_count = 1;
}
int32_t min_offer = max_buy;
int32_t last_offer = min_offer;
int32_t new_offer = 0;
int32_t min_per = owner.haggles_per;
int32_t max_per = min_per * 3;
int final_flag = 0;
bool rejected = false;
bool bidding_open;
while (!rejected) {
do {
bidding_open = true;
vtype_t msg = {'\0'};
(void) sprintf(msg, "%s : %d", comment, current_asking_price);
putString(msg, Coord_t{1, 0});
status = storeReceiveOffer(store_id, "What do you offer? ", new_offer, last_offer, offers_count, 1);
if (status != BidState::Received) {
rejected = true;
} else {
// review the received bid
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) {
rejected = true;
new_price = new_offer;
} else {
bidding_open = false;
}
}
} while (!rejected && bidding_open);
if (!rejected) {
int32_t adjustment = (new_offer - last_offer) * 100 / (current_asking_price - last_offer);
if (adjustment < min_per) {
rejected = storeHaggleInsults(store_id);
if (rejected) {
status = BidState::Insulted;
}
} else if (adjustment > max_per) {
adjustment = adjustment * 75 / 100;
if (adjustment < max_per) {
adjustment = max_per;
}
}
adjustment = ((current_asking_price - new_offer) * (adjustment + randomNumber(5) - 3) / 100) + 1;
// don't let the price go up
if (adjustment > 0) {
current_asking_price -= adjustment;
}
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)) {
status = BidState::Insulted;
} else {
status = BidState::Rejected;
}
rejected = true;
}
} else if (new_offer >= current_asking_price) {
rejected = true;
new_price = new_offer;
}
if (!rejected) {
last_offer = new_offer;
offers_count++; // enable incremental haggling
eraseLine(Coord_t{1, 0});
vtype_t msg = {'\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 (status == BidState::Received && !accepted_without_haggle) {
storeUpdateBargainingSkills(stores[store_id], new_price, final_asking_price);
}
price = new_price; // update callers price before returning
return status;
}
static void storeSellCustomerAdjustment(Owner_t const &owner, int32_t &cost, int32_t &min_buy, int32_t &max_buy, int32_t &max_sell) {
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;
}
}
// Haggling routine -RAK-
static BidState storeSellHaggle(int store_id, int32_t &price, Inventory_t const &item) {
BidState status = BidState::Received;
int32_t new_price = 0;
Store_t const &store = stores[store_id];
int32_t cost = storeItemValue(item);
bool rejected = false;
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;
if (cost < 1) {
status = BidState::Offended;
rejected = true;
} else {
Owner_t const &owner = store_owners[store.owner_id];
storeSellCustomerAdjustment(owner, cost, min_buy, max_buy, max_sell);
min_per = owner.haggles_per;
max_per = min_per * 3;
max_gold = owner.max_cost;
}
int32_t final_asking_price = 0;
int32_t current_asking_price = 0;
int final_flag = 0;
const char *comment = nullptr;
bool accepted_without_haggle = false;
if (!rejected) {
displayStoreHaggleCommands(-1);
int offer_count = 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_asking_price = max_gold;
final_asking_price = max_gold;
printMessage("I am sorry, but I have not the money to afford such a fine item.");
accepted_without_haggle = true;
} else {
current_asking_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_asking_price = final_asking_price;
comment = "Final offer";
accepted_without_haggle = true;
// Set up automatic increment, so that a return
// will accept the final price.
store_last_increment = (int16_t) final_asking_price;
offer_count = 1;
}
}
int32_t min_offer = max_sell;
int32_t last_offer = min_offer;
int32_t new_offer = 0;
if (current_asking_price < 1) {
current_asking_price = 1;
}
bool bidding_open;
do {
do {
bidding_open = true;
vtype_t msg = {'\0'};
(void) sprintf(msg, "%s : %d", comment, current_asking_price);
putString(msg, Coord_t{1, 0});
status = storeReceiveOffer(store_id, "What price do you ask? ", new_offer, last_offer, offer_count, -1);
if (status != BidState::Received) {
rejected = true;
} else {
// review the received bid
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) {
rejected = true;
new_price = new_offer;
} else {
bidding_open = false;
}
}
} while (!rejected && bidding_open);
if (!rejected) {
int32_t adjustment = (last_offer - new_offer) * 100 / (last_offer - current_asking_price);
if (adjustment < min_per) {
rejected = storeHaggleInsults(store_id);
if (rejected) {
status = BidState::Insulted;
}
} else if (adjustment > max_per) {
adjustment = adjustment * 75 / 100;
if (adjustment < max_per) {
adjustment = max_per;
}
}
adjustment = ((new_offer - current_asking_price) * (adjustment + randomNumber(5) - 3) / 100) + 1;
// don't let the price go down
if (adjustment > 0) {
current_asking_price += adjustment;
}
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)) {
status = BidState::Insulted;
} else {
status = BidState::Rejected;
}
rejected = true;
}
} else if (new_offer <= current_asking_price) {
rejected = true;
new_price = new_offer;
}
if (!rejected) {
last_offer = new_offer;
offer_count++; // 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_asking_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_asking_price - last_offer > store_last_increment) {
store_last_increment = (int16_t)(current_asking_price - last_offer);
}
}
}
} while (!rejected);
}
// update bargaining info
if (status == BidState::Received && !accepted_without_haggle) {
storeUpdateBargainingSkills(stores[store_id], new_price, final_asking_price);
}
price = new_price; // update callers price before returning
return status;
}
// 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-
// Returns true is the owner kicks out the customer
static bool storePurchaseAnItem(int store_id, int ¤t_top_item_id) {
bool kick_customer = false; // don't kick them out of the store!
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;
}
BidState status = BidState::Received;
int32_t price;
if (store.inventory[item_id].cost > 0) {
price = store.inventory[item_id].cost;
} else {
status = storePurchaseHaggle(store_id, price, sell_item);
}
if (status == BidState::Insulted) {
kick_customer = true;
} else if (status == BidState::Received) {
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, py.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)) {
kick_customer = true;
} else {
printSpeechFinishedHaggling();
printMessage("Liar! You have not the gold!");
}
}
}
// Less intuitive, but looks better here than in storePurchaseHaggle.
displayStoreCommands();
eraseLine(Coord_t{1, 0});
return kick_customer;
}
// Functions to emulate the original Pascal sets
static bool setGeneralStoreItems(uint8_t 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(uint8_t 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(uint8_t 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(uint8_t 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(uint8_t 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(uint8_t 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])(uint8_t) = {
setGeneralStoreItems, setArmoryItems, setWeaponsmithItems, setTempleItems, setAlchemistItems, setMagicShopItems,
};
// Sell an item to the store -RAK-
// Returns true is the owner kicks out the customer
static bool storeSellAnItem(int store_id, int ¤t_top_item_id) {
bool kick_customer = false; // don't kick them out of the store!
int first_item = py.pack.unique_items;
int last_item = -1;
char mask[PlayerEquipment::Wield];
for (int counter = 0; counter < py.pack.unique_items; counter++) {
bool flag = (*store_buy[store_id])(py.inventory[counter].category_id);
if (flag) {
mask[counter] = 1;
if (counter < first_item) {
first_item = counter;
}
if (counter > last_item) {
last_item = counter;
}
} else {
mask[counter] = 0;
}
}
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, &py.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;
BidState status = storeSellHaggle(store_id, price, sold_item);
if (status == BidState::Insulted) {
kick_customer = true;
} else if (status == BidState::Offended) {
printMessage("How dare you!");
printMessage("I will not buy that!");
kick_customer = storeIncreaseInsults(store_id);
} else if (status == BidState::Received) {
// bid received, and accepted!
printSpeechFinishedHaggling();
storeDecreaseInsults(store_id);
py.misc.au += price;
// identify object in inventory to set objects_identified array
itemIdentify(py.inventory[item_id], item_id);
// retake sold_item so that it will be identified
inventoryTakeOneItem(&sold_item, &py.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();
}
// Less intuitive, but looks better here than in storeSellHaggle.
eraseLine(Coord_t{1, 0});
displayStoreCommands();
return kick_customer;
}
// 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[PlayerAttr::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[PlayerAttr::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 == SHRT_MAX) {
return true;
}
int record = (store.good_purchases - 3 * store.bad_purchases - 5);
return ((record > 0) && (record * record > min_price / 50));
}
// update the bargain info -DJB-
static void storeUpdateBargainingSkills(Store_t &store, int32_t price, int32_t min_price) {
if (min_price < 10) {
return;
}
if (price == min_price) {
if (store.good_purchases < SHRT_MAX) {
store.good_purchases++;
}
} else {
if (store.bad_purchases < SHRT_MAX) {
store.bad_purchases++;
}
}
}
umoria-5.7.13/src/recall.h 0000644 0001750 0001750 00000002113 13720166710 014257 0 ustar ariel ariel // 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.13/src/monster.h 0000644 0001750 0001750 00000007134 13720166710 014514 0 ustar ariel ariel // 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
Coord_t pos; // (y,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(Coord_t coord, int creature_id, int monster_id);
void updateMonsters(bool attack);
uint32_t monsterDeath(Coord_t coord, 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(Coord_t coord);
// monster management
bool compactMonsters();
bool monsterPlaceNew(Coord_t coord, int creature_id, bool sleeping);
void monsterPlaceWinning();
void monsterPlaceNewWithinDistance(int number, int distance_from_source, bool sleeping);
bool monsterSummon(Coord_t &coord, bool sleeping);
bool monsterSummonUndead(Coord_t &coord);
umoria-5.7.13/src/inventory.cpp 0000644 0001750 0001750 00000045006 13720166710 015415 0 ustar ariel ariel // 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"
uint32_t inventoryCollectAllItemFlags() {
uint32_t flags = 0;
for (int i = PlayerEquipment::Wield; i < PlayerEquipment::Light; i++) {
flags |= py.inventory[i].flags;
}
return flags;
}
// Destroy an item in the inventory -RAK-
void inventoryDestroyItem(int item_id) {
Inventory_t &item = py.inventory[item_id];
if (item.items_count > 1 && item.sub_category_id <= ITEM_SINGLE_STACK_MAX) {
item.items_count--;
py.pack.weight -= item.weight;
} else {
py.pack.weight -= item.weight * item.items_count;
for (int i = item_id; i < py.pack.unique_items - 1; i++) {
py.inventory[i] = py.inventory[i + 1];
}
inventoryItemCopyTo(config::dungeon::objects::OBJ_NOTHING, py.inventory[py.pack.unique_items - 1]);
py.pack.unique_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.pos.y][py.pos.x].treasure_id != 0) {
(void) dungeonDeleteObject(py.pos);
}
int treasure_id = popt();
Inventory_t &item = py.inventory[item_id];
game.treasure.list[treasure_id] = item;
dg.floor[py.pos.y][py.pos.x].treasure_id = (uint8_t) treasure_id;
if (item_id >= PlayerEquipment::Wield) {
playerTakeOff(item_id, -1);
} else {
if (drop_all || item.items_count == 1) {
py.pack.weight -= item.weight * item.items_count;
py.pack.unique_items--;
while (item_id < py.pack.unique_items) {
py.inventory[item_id] = py.inventory[item_id + 1];
item_id++;
}
inventoryItemCopyTo(config::dungeon::objects::OBJ_NOTHING, py.inventory[py.pack.unique_items]);
} else {
game.treasure.list[treasure_id].items_count = 1;
py.pack.weight -= item.weight;
item.items_count--;
}
obj_desc_t prt1 = {'\0'};
obj_desc_t prt2 = {'\0'};
itemDescription(prt1, game.treasure.list[treasure_id], 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.pack.unique_items; i++) {
if ((*item_type)(&py.inventory[i]) && randomNumber(100) < chance_percentage) {
inventoryDestroyItem(i);
damage++;
}
}
return damage;
}
bool inventoryDiminishLightAttack(bool noticed) {
Inventory_t &item = py.inventory[PlayerEquipment::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 = py.inventory[randomNumber(py.pack.unique_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 = PlayerEquipment::Wield;
break;
case 2:
item_id = PlayerEquipment::Body;
break;
case 3:
item_id = PlayerEquipment::Arm;
break;
case 4:
item_id = PlayerEquipment::Outer;
break;
case 5:
item_id = PlayerEquipment::Hands;
break;
case 6:
item_id = PlayerEquipment::Head;
break;
case 7:
item_id = PlayerEquipment::Feet;
break;
default:
return false;
}
bool success = false;
Inventory_t &item = py.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.pack.unique_items < PlayerEquipment::Wield) {
return true;
}
if (item.sub_category_id < ITEM_SINGLE_STACK_MIN) {
return false;
}
for (int i = 0; i < py.pack.unique_items; i++) {
bool same_character = py.inventory[i].category_id == item.category_id;
bool same_category = py.inventory[i].sub_category_id == item.sub_category_id;
// make sure the number field doesn't overflow
// NOTE: convert to bigger types before addition -MRC-
bool same_number = uint16_t(py.inventory[i].items_count) + uint16_t(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 || py.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(py.inventory[i].category_id, py.inventory[i].sub_category_id, py.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 new_weight = item.items_count * item.weight + py.pack.weight;
if (limit < new_weight) {
limit = new_weight / (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 = py.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.pack.unique_items - 1; i >= slot_id; i--) {
py.inventory[i + 1] = py.inventory[i];
}
py.inventory[slot_id] = new_item;
py.pack.unique_items++;
break;
}
}
py.pack.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.pack.unique_items; i++) {
auto item_id = (int) py.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.pack.unique_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 = SpecialNameIds::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 items_count = 0;
uint8_t items[6];
if (py.inventory[PlayerEquipment::Body].category_id != TV_NOTHING) {
items[items_count] = PlayerEquipment::Body;
items_count++;
}
if (py.inventory[PlayerEquipment::Arm].category_id != TV_NOTHING) {
items[items_count] = PlayerEquipment::Arm;
items_count++;
}
if (py.inventory[PlayerEquipment::Outer].category_id != TV_NOTHING) {
items[items_count] = PlayerEquipment::Outer;
items_count++;
}
if (py.inventory[PlayerEquipment::Hands].category_id != TV_NOTHING) {
items[items_count] = PlayerEquipment::Hands;
items_count++;
}
if (py.inventory[PlayerEquipment::Head].category_id != TV_NOTHING) {
items[items_count] = PlayerEquipment::Head;
items_count++;
}
// also affect boots
if (py.inventory[PlayerEquipment::Feet].category_id != TV_NOTHING) {
items[items_count] = PlayerEquipment::Feet;
items_count++;
}
bool minus = false;
if (items_count == 0) {
return minus;
}
uint8_t item_id = items[randomNumber(items_count) - 1];
obj_desc_t description = {'\0'};
obj_desc_t msg = {'\0'};
if ((py.inventory[item_id].flags & typ_dam) != 0u) {
minus = true;
itemDescription(description, py.inventory[item_id], false);
(void) sprintf(msg, "Your %s resists damage!", description);
printMessage(msg);
} else if (py.inventory[item_id].ac + py.inventory[item_id].to_ac > 0) {
minus = true;
itemDescription(description, py.inventory[item_id], false);
(void) sprintf(msg, "Your %s is damaged!", description);
printMessage(msg);
py.inventory[item_id].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(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(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.13/src/data_treasure.cpp 0000644 0001750 0001750 00000164456 13720166710 016216 0 ustar ariel ariel // 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[SpecialNameIds::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.13/src/player_run.cpp 0000644 0001750 0001750 00000035257 13720166710 015547 0 ustar ariel ariel // 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, Coord_t coord) {
// check to see if movement there possible
if (!playerMovePosition(dir, coord)) {
return true;
}
char c = caveGetTileSymbol(coord);
return c == '#' || c == '%';
}
// Do we see anything? Used in running. -CJS-
static bool playerSeeNothing(int dir, Coord_t coord) {
// check to see if movement there possible
return playerMovePosition(dir, coord) && caveGetTileSymbol(coord) == ' ';
}
static void findRunningBreak(int dir, Coord_t coord) {
bool deep_left = false;
bool deep_right = false;
bool short_left = false;
bool short_right = false;
int cycle_index = chome[dir];
if (playerCanSeeDungeonWall(cycle[cycle_index + 1], py.pos)) {
find_breakleft = true;
short_left = true;
} else if (playerCanSeeDungeonWall(cycle[cycle_index + 1], coord)) {
find_breakleft = true;
deep_left = true;
}
if (playerCanSeeDungeonWall(cycle[cycle_index - 1], py.pos)) {
find_breakright = true;
short_right = true;
} else if (playerCanSeeDungeonWall(cycle[cycle_index - 1], coord)) {
find_breakright = true;
deep_right = true;
}
if (find_breakleft && find_breakright) {
find_openarea = false;
// a hack to allow angled corridor entry
if ((dir & 1) != 0) {
if (deep_left && !deep_right) {
find_prevdir = cycle[cycle_index - 1];
} else if (deep_right && !deep_left) {
find_prevdir = cycle[cycle_index + 1];
}
} else if (playerCanSeeDungeonWall(cycle[cycle_index], coord)) {
// 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 (short_left && !short_right) {
find_prevdir = cycle[cycle_index - 2];
} else if (short_right && !short_left) {
find_prevdir = cycle[cycle_index + 2];
}
}
} else {
find_openarea = true;
}
}
void playerFindInitialize(int direction) {
Coord_t coord = py.pos;
if (!playerMovePosition(direction, coord)) {
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, coord);
}
}
// 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(py.pos), py.pos);
}
playerMove(direction, true);
if (py.running_tracker == 0) {
game.command_count = 0;
}
}
void playerRunAndFind() {
uint8_t tracker = py.running_tracker;
py.running_tracker++;
// prevent infinite loops in find mode, will stop after moving 100 times
if (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(py.pos, py.pos);
}
static bool areaAffectStopLookingAtSquares(int i, int dir, int new_dir, Coord_t coord, int &check_dir, int &dir_a, int &dir_b) {
Tile_t const &tile = dg.floor[coord.y][coord.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 tile_id = game.treasure.list[tile.treasure_id].category_id;
if (tile_id != TV_INVIS_TRAP && tile_id != TV_SECRET_DOOR && (tile_id != 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 (dir_a == 0) {
// The first new direction.
dir_a = new_dir;
} else if (dir_b != 0) {
// Three new directions. STOP.
playerEndRunning();
return true;
} else if (dir_a != cycle[chome[dir] + i - 1]) {
// If not adjacent to prev, STOP
playerEndRunning();
return true;
} else {
// Two adjacent choices. Make dir_b the diagonal, and
// remember the other diagonal adjacent to the first option.
if ((new_dir & 1) == 1) {
check_dir = cycle[chome[dir] + i - 2];
dir_b = new_dir;
} else {
check_dir = cycle[chome[dir] + i + 1];
dir_b = dir_a;
dir_a = 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, Coord_t coord) {
if (py.flags.blind >= 1) {
return;
}
int check_dir = 0;
int dir_a = 0;
int dir_b = 0;
direction = find_prevdir;
int max = (direction & 1) + 1;
Coord_t spot = Coord_t{0, 0};
// Look at every newly adjacent square.
for (int i = -max; i <= max; i++) {
int new_dir = cycle[chome[direction] + i];
spot.y = coord.y;
spot.x = coord.x;
// Objects player can see (Including doors?) cause a stop.
if (playerMovePosition(new_dir, spot)) {
areaAffectStopLookingAtSquares(i, direction, new_dir, spot, check_dir, dir_a, dir_b);
}
}
if (find_openarea) {
return;
}
// choose a direction.
if (dir_b == 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 (dir_a != 0) {
find_direction = dir_a;
}
if (dir_b == 0) {
find_prevdir = dir_a;
} else {
find_prevdir = dir_b;
}
return;
}
// Two options!
Coord_t location = Coord_t{coord.y, coord.x};
(void) playerMovePosition(dir_a, location);
if (!playerCanSeeDungeonWall(dir_a, location) || !playerCanSeeDungeonWall(check_dir, location)) {
// Don't see that it is closed off. This could be a
// potential corner or an intersection.
if (config::options::run_examine_corners && playerSeeNothing(dir_a, location) && playerSeeNothing(dir_b, location)) {
// Can not see anything ahead and in the direction we are
// turning, assume that it is a potential corner.
find_direction = dir_a;
find_prevdir = dir_b;
} 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 = dir_b;
find_prevdir = dir_b;
} else {
// This corner is seen to be enclosed, and we deliberately
// go the long way.
find_direction = dir_a;
find_prevdir = dir_b;
}
}
umoria-5.7.13/src/helpers.cpp 0000644 0001750 0001750 00000010016 13720166710 015013 0 ustar ariel ariel // 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 (int) LONG_MIN: // underflow
case (int) 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, 11, "%a %b %d", datetime);
#else
strftime(day, 11, "%a %b %e", datetime);
#endif
}
umoria-5.7.13/src/data_recall.cpp 0000644 0001750 0001750 00000004124 13720166710 015607 0 ustar ariel ariel // 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.13/src/mage_spells.cpp 0000644 0001750 0001750 00000021650 13720166710 015652 0 ustar ariel ariel // 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 MageSpellId {
MagicMissile = 1,
DetectMonsters,
PhaseDoor,
LightArea,
CureLightWounds,
FindHiddenTrapsDoors,
StinkingCloud,
Confusion,
LightningBolt,
TrapDoorDestruction,
Sleep1,
CurePoison,
TeleportSelf,
RemoveCurse,
FrostBolt,
WallToMud,
CreateFood,
RechargeItem1,
Sleep2,
PolymorphOther,
IdentifyItem,
Sleep3,
FireBolt,
SpeedMonster,
FrostBall,
RechargeItem2,
TeleportOther,
HasteSelf,
FireBall,
WordOfDestruction,
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 ((MageSpellId) spell_id) {
case MageSpellId::MagicMissile:
if (getDirectionWithMemory(CNIL, dir)) {
spellFireBolt(py.pos, dir, diceRoll(Dice_t{2, 6}), MagicSpellFlags::MagicMissile, spell_names[0]);
}
break;
case MageSpellId::DetectMonsters:
(void) spellDetectMonsters();
break;
case MageSpellId::PhaseDoor:
playerTeleport(10);
break;
case MageSpellId::LightArea:
(void) spellLightArea(py.pos);
break;
case MageSpellId::CureLightWounds:
(void) spellChangePlayerHitPoints(diceRoll(Dice_t{4, 4}));
break;
case MageSpellId::FindHiddenTrapsDoors:
(void) spellDetectSecretDoorssWithinVicinity();
(void) spellDetectTrapsWithinVicinity();
break;
case MageSpellId::StinkingCloud:
if (getDirectionWithMemory(CNIL, dir)) {
spellFireBall(py.pos, dir, 12, MagicSpellFlags::PoisonGas, spell_names[6]);
}
break;
case MageSpellId::Confusion:
if (getDirectionWithMemory(CNIL, dir)) {
(void) spellConfuseMonster(py.pos, dir);
}
break;
case MageSpellId::LightningBolt:
if (getDirectionWithMemory(CNIL, dir)) {
spellFireBolt(py.pos, dir, diceRoll(Dice_t{4, 8}), MagicSpellFlags::Lightning, spell_names[8]);
}
break;
case MageSpellId::TrapDoorDestruction:
(void) spellDestroyAdjacentDoorsTraps();
break;
case MageSpellId::Sleep1:
if (getDirectionWithMemory(CNIL, dir)) {
(void) spellSleepMonster(py.pos, dir);
}
break;
case MageSpellId::CurePoison:
(void) playerCurePoison();
break;
case MageSpellId::TeleportSelf:
playerTeleport((py.misc.level * 5));
break;
case MageSpellId::RemoveCurse:
for (int id = 22; id < PLAYER_INVENTORY_SIZE; id++) {
py.inventory[id].flags = (uint32_t)(py.inventory[id].flags & ~config::treasure::flags::TR_CURSED);
}
break;
case MageSpellId::FrostBolt:
if (getDirectionWithMemory(CNIL, dir)) {
spellFireBolt(py.pos, dir, diceRoll(Dice_t{6, 8}), MagicSpellFlags::Frost, spell_names[14]);
}
break;
case MageSpellId::WallToMud:
if (getDirectionWithMemory(CNIL, dir)) {
(void) spellWallToMud(py.pos, dir);
}
break;
case MageSpellId::CreateFood:
spellCreateFood();
break;
case MageSpellId::RechargeItem1:
(void) spellRechargeItem(20);
break;
case MageSpellId::Sleep2:
(void) monsterSleep(py.pos);
break;
case MageSpellId::PolymorphOther:
if (getDirectionWithMemory(CNIL, dir)) {
(void) spellPolymorphMonster(py.pos, dir);
}
break;
case MageSpellId::IdentifyItem:
(void) spellIdentifyItem();
break;
case MageSpellId::Sleep3:
(void) spellSleepAllMonsters();
break;
case MageSpellId::FireBolt:
if (getDirectionWithMemory(CNIL, dir)) {
spellFireBolt(py.pos, dir, diceRoll(Dice_t{9, 8}), MagicSpellFlags::Fire, spell_names[22]);
}
break;
case MageSpellId::SpeedMonster:
if (getDirectionWithMemory(CNIL, dir)) {
(void) spellSpeedMonster(py.pos, dir, -1);
}
break;
case MageSpellId::FrostBall:
if (getDirectionWithMemory(CNIL, dir)) {
spellFireBall(py.pos, dir, 48, MagicSpellFlags::Frost, spell_names[24]);
}
break;
case MageSpellId::RechargeItem2:
(void) spellRechargeItem(60);
break;
case MageSpellId::TeleportOther:
if (getDirectionWithMemory(CNIL, dir)) {
(void) spellTeleportAwayMonsterInDirection(py.pos, dir);
}
break;
case MageSpellId::HasteSelf:
py.flags.fast += randomNumber(20) + py.misc.level;
break;
case MageSpellId::FireBall:
if (getDirectionWithMemory(CNIL, dir)) {
spellFireBall(py.pos, dir, 72, MagicSpellFlags::Fire, spell_names[28]);
}
break;
case MageSpellId::WordOfDestruction:
spellDestroyArea(py.pos);
break;
case MageSpellId::Genocide:
(void) spellGenocide();
break;
default:
// All cases are handled, so this should never be reached!
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 (!game.player_free_turn && (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 (game.player_free_turn) {
return;
}
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(PlayerAttr::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 = PlayerAttr::A_INT;
} else {
stat = PlayerAttr::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.13/src/store_inventory.cpp 0000644 0001750 0001750 00000032044 13720166710 016627 0 ustar ariel ariel // 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;
}
turnaround--;
while (turnaround >= 0) {
storeDestroyItem(store_id, randomNumber(store.unique_items_counter) - 1, false);
turnaround--;
}
}
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;
turnaround--;
while (turnaround >= 0) {
storeItemCreate(store_id, max_cost);
turnaround--;
}
}
}
}
// 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_catagory = 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_catagory == store_item.sub_category_id && // Adds to other item
item_sub_catagory >= ITEM_SINGLE_STACK_MIN && (item_sub_catagory < 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_catagory > 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;
uint8_t 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 = (uint8_t) 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, game.treasure.list[free_id]);
magicTreasureMagicalAbility(free_id, config::treasure::LEVEL_TOWN_OBJECTS);
Inventory_t &item = game.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.13/src/game_objects.cpp 0000644 0001750 0001750 00000013362 13720166710 016002 0 ustar ariel ariel // 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;
Coord_t coord = Coord_t{0, 0};
while (counter <= 0) {
for (coord.y = 0; coord.y < dg.height; coord.y++) {
for (coord.x = 0; coord.x < dg.width; coord.x++) {
if (dg.floor[coord.y][coord.x].treasure_id != 0 && coordDistanceBetween(coord, py.pos) > current_distance) {
int chance;
switch (game.treasure.list[dg.floor[coord.y][coord.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);
counter++;
}
}
}
}
if (counter == 0) {
current_distance -= 6;
}
}
if (current_distance < 66) {
drawDungeonPanel();
}
}
// Gives pointer to next free space -RAK-
int popt() {
if (game.treasure.current_id == LEVEL_MAX_OBJECTS) {
compactObjects();
}
return game.treasure.current_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 != game.treasure.current_id - 1) {
game.treasure.list[treasure_id] = game.treasure.list[game.treasure.current_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 == game.treasure.current_id - 1) {
dg.floor[y][x].treasure_id = treasure_id;
}
}
}
}
game.treasure.current_id--;
inventoryItemCopyTo(config::dungeon::objects::OBJ_NOTHING, game.treasure.list[game.treasure.current_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 found_level = game_objects[sorted_objects[object_id]].depth_first_found;
if (found_level == 0) {
object_id = randomNumber(treasure_levels[0]) - 1;
} else {
object_id = randomNumber(treasure_levels[found_level] - treasure_levels[found_level - 1]) - 1 + treasure_levels[found_level - 1];
}
}
} while (must_be_small && itemBiggerThanChest(game_objects[sorted_objects[object_id]]));
return object_id;
}
umoria-5.7.13/src/store.h 0000644 0001750 0001750 00000005063 13720166710 014160 0 ustar ariel ariel // 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
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])(uint8_t);
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.13/src/data_store_owners.cpp 0000644 0001750 0001750 00000012575 13720166710 017107 0 ustar ariel ariel // 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.13/src/ui.h 0000644 0001750 0001750 00000010250 13720166710 013433 0 ustar ariel ariel // 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;
// 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;
extern int eof_flag;
extern bool panic_save;
// UI - IO
bool terminalInitialize();
void terminalRestore();
void terminalSaveScreen();
void terminalRestoreScreen();
ssize_t terminalBellSound();
void putQIO();
void flushInputBuffer();
void clearScreen();
void clearToBottom(int row);
void moveCursor(Coord_t coord);
void addChar(char ch, Coord_t coord);
void putString(const char *out_str, Coord_t coord);
void putStringClearToEOL(const std::string &str, Coord_t coord);
void eraseLine(Coord_t coord);
void panelMoveCursor(Coord_t coord);
void panelPutTile(char ch, Coord_t coord);
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 coord, 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(Coord_t coord);
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.13/src/data_tables.cpp 0000644 0001750 0001750 00000015550 13720166710 015624 0 ustar ariel ariel // 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.13/src/player_magic.cpp 0000644 0001750 0001750 00000007366 13720166710 016023 0 ustar ariel ariel // 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.13/src/identification.cpp 0000644 0001750 0001750 00000072365 13720166710 016361 0 ustar ariel ariel // 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 cat_id = item.category_id;
int sub_cat_id = item.sub_category_id;
// no merging possible
if (sub_cat_id < ITEM_SINGLE_STACK_MIN || sub_cat_id >= ITEM_GROUP_MIN) {
return;
}
int j;
for (int i = 0; i < py.pack.unique_items; i++) {
Inventory_t const &t_ptr = py.inventory[i];
if (t_ptr.category_id == cat_id && t_ptr.sub_category_id == sub_cat_id && 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.");
py.inventory[item_id].items_count += py.inventory[i].items_count;
py.pack.unique_items--;
for (j = i; j < py.pack.unique_items; j++) {
py.inventory[j] = py.inventory[j + 1];
}
inventoryItemCopyTo(config::dungeon::objects::OBJ_NOTHING, py.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 = SpecialNameIds::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,
ZPlusses,
};
// 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::ZPlusses;
(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 != SpecialNameIds::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::ZPlusses;
}
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::ZPlusses) {
// 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(py.inventory[item_id])) {
return;
}
int rem_num = py.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 = py.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.pack.unique_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, py.inventory[item_id], true);
obj_desc_t inscription = {'\0'};
(void) sprintf(inscription, "Inscribing %s", msg);
printMessage(inscription);
if (py.inventory[item_id].inscription[0] != '\0') {
(void) sprintf(inscription, "Replace %s New inscription:", py.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(py.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.13/src/recall.cpp 0000644 0001750 0001750 00000055604 13720166710 014627 0 ustar ariel ariel // 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) SHRT_MAX;
memory.wake = memory.ignore = UCHAR_MAX;
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] = UCHAR_MAX;
}
// A little hack to enable the display of info for Quylthulgs.
if ((memory.movement & config::monsters::move::CM_ONLY_MAGIC) != 0u) {
memory.attacks[0] = UCHAR_MAX;
}
}
// 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 == UCHAR_MAX || (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.13/src/game_save.cpp 0000644 0001750 0001750 00000120475 13720166710 015313 0 ustar ariel ariel // 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 saveChar(const std::string &filename);
static bool svWrite();
static void wrBool(bool value);
static void wrByte(uint8_t value);
static void wrShort(uint16_t value);
static void wrLong(uint32_t value);
static void wrBytes(uint8_t *value, int count);
static void wrString(char *str);
static void wrShorts(uint16_t *value, int count);
static void wrItem(Inventory_t &item);
static void wrMonster(Monster_t const &monster);
static uint8_t getByte();
static bool rdBool();
static uint8_t rdByte();
static uint16_t rdShort();
static uint32_t rdLong();
static void rdBytes(uint8_t *value, int count);
static void rdString(char *str);
static void rdShorts(uint16_t *value, int count);
static void rdItem(Inventory_t &item);
static void rdMonster(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_save_file; // 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-
// Set up prior to actual save, do the save, then clean up
bool saveGame() {
vtype_t input = {'\0'};
std::string output;
while (!saveChar(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 svWrite() {
// 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]) {
wrShort((uint16_t) i);
wrLong(r.movement);
wrLong(r.spells);
wrShort(r.kills);
wrShort(r.deaths);
wrShort(r.defenses);
wrByte(r.wake);
wrByte(r.ignore);
wrBytes(r.attacks, MON_MAX_ATTACKS);
}
}
// sentinel to indicate no more monster info
wrShort((uint16_t) 0xFFFF);
wrLong(l);
wrString(py.misc.name);
wrBool(py.misc.gender);
wrLong((uint32_t) py.misc.au);
wrLong((uint32_t) py.misc.max_exp);
wrLong((uint32_t) py.misc.exp);
wrShort(py.misc.exp_fraction);
wrShort(py.misc.age);
wrShort(py.misc.height);
wrShort(py.misc.weight);
wrShort(py.misc.level);
wrShort(py.misc.max_dungeon_depth);
wrShort((uint16_t) py.misc.chance_in_search);
wrShort((uint16_t) py.misc.fos);
wrShort((uint16_t) py.misc.bth);
wrShort((uint16_t) py.misc.bth_with_bows);
wrShort((uint16_t) py.misc.mana);
wrShort((uint16_t) py.misc.max_hp);
wrShort((uint16_t) py.misc.plusses_to_hit);
wrShort((uint16_t) py.misc.plusses_to_damage);
wrShort((uint16_t) py.misc.ac);
wrShort((uint16_t) py.misc.magical_ac);
wrShort((uint16_t) py.misc.display_to_hit);
wrShort((uint16_t) py.misc.display_to_damage);
wrShort((uint16_t) py.misc.display_ac);
wrShort((uint16_t) py.misc.display_to_ac);
wrShort((uint16_t) py.misc.disarm);
wrShort((uint16_t) py.misc.saving_throw);
wrShort((uint16_t) py.misc.social_class);
wrShort((uint16_t) py.misc.stealth_factor);
wrByte(py.misc.class_id);
wrByte(py.misc.race_id);
wrByte(py.misc.hit_die);
wrByte(py.misc.experience_factor);
wrShort((uint16_t) py.misc.current_mana);
wrShort(py.misc.current_mana_fraction);
wrShort((uint16_t) py.misc.current_hp);
wrShort(py.misc.current_hp_fraction);
for (auto &entry : py.misc.history) {
wrString(entry);
}
wrBytes(py.stats.max, 6);
wrBytes(py.stats.current, 6);
wrShorts((uint16_t *) py.stats.modified, 6);
wrBytes(py.stats.used, 6);
wrLong(py.flags.status);
wrShort((uint16_t) py.flags.rest);
wrShort((uint16_t) py.flags.blind);
wrShort((uint16_t) py.flags.paralysis);
wrShort((uint16_t) py.flags.confused);
wrShort((uint16_t) py.flags.food);
wrShort((uint16_t) py.flags.food_digested);
wrShort((uint16_t) py.flags.protection);
wrShort((uint16_t) py.flags.speed);
wrShort((uint16_t) py.flags.fast);
wrShort((uint16_t) py.flags.slow);
wrShort((uint16_t) py.flags.afraid);
wrShort((uint16_t) py.flags.poisoned);
wrShort((uint16_t) py.flags.image);
wrShort((uint16_t) py.flags.protect_evil);
wrShort((uint16_t) py.flags.invulnerability);
wrShort((uint16_t) py.flags.heroism);
wrShort((uint16_t) py.flags.super_heroism);
wrShort((uint16_t) py.flags.blessed);
wrShort((uint16_t) py.flags.heat_resistance);
wrShort((uint16_t) py.flags.cold_resistance);
wrShort((uint16_t) py.flags.detect_invisible);
wrShort((uint16_t) py.flags.word_of_recall);
wrShort((uint16_t) py.flags.see_infra);
wrShort((uint16_t) py.flags.timed_infra);
wrBool(py.flags.see_invisible);
wrBool(py.flags.teleport);
wrBool(py.flags.free_action);
wrBool(py.flags.slow_digest);
wrBool(py.flags.aggravate);
wrBool(py.flags.resistant_to_fire);
wrBool(py.flags.resistant_to_cold);
wrBool(py.flags.resistant_to_acid);
wrBool(py.flags.regenerate_hp);
wrBool(py.flags.resistant_to_light);
wrBool(py.flags.free_fall);
wrBool(py.flags.sustain_str);
wrBool(py.flags.sustain_int);
wrBool(py.flags.sustain_wis);
wrBool(py.flags.sustain_con);
wrBool(py.flags.sustain_dex);
wrBool(py.flags.sustain_chr);
wrBool(py.flags.confuse_monster);
wrByte(py.flags.new_spells_to_learn);
wrShort((uint16_t) missiles_counter);
wrLong((uint32_t) dg.game_turn);
wrShort((uint16_t) py.pack.unique_items);
for (int i = 0; i < py.pack.unique_items; i++) {
wrItem(py.inventory[i]);
}
for (int i = PlayerEquipment::Wield; i < PLAYER_INVENTORY_SIZE; i++) {
wrItem(py.inventory[i]);
}
wrShort((uint16_t) py.pack.weight);
wrShort((uint16_t) py.equipment_count);
wrLong(py.flags.spells_learnt);
wrLong(py.flags.spells_worked);
wrLong(py.flags.spells_forgotten);
wrBytes(py.flags.spells_learned_order, 32);
wrBytes(objects_identified, OBJECT_IDENT_SIZE);
wrLong(game.magic_seed);
wrLong(game.town_seed);
wrShort((uint16_t) last_message_id);
for (auto &message : messages) {
wrString(message);
}
// this indicates 'cheating' if it is a one
wrShort((uint16_t) panic_save);
wrShort((uint16_t) game.total_winner);
wrShort((uint16_t) game.noscore);
wrShorts(py.base_hp_levels, PLAYER_MAX_LEVEL);
for (auto &store : stores) {
wrLong((uint32_t) store.turns_left_before_closing);
wrShort((uint16_t) store.insults_counter);
wrByte(store.owner_id);
wrByte(store.unique_items_counter);
wrShort(store.good_purchases);
wrShort(store.bad_purchases);
for (int j = 0; j < store.unique_items_counter; j++) {
wrLong((uint32_t) store.inventory[j].cost);
wrItem(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);
}
wrLong(l);
// put game.character_died_from string in save file
wrString(game.character_died_from);
// put the max_score in the save file
l = (uint32_t)(playerCalculateTotalPoints());
wrLong(l);
// put the date_of_birth in the save file
wrLong((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);
}
wrShort((uint16_t) dg.current_level);
wrShort((uint16_t) py.pos.y);
wrShort((uint16_t) py.pos.x);
wrShort((uint16_t) monster_multiply_total);
wrShort((uint16_t) dg.height);
wrShort((uint16_t) dg.width);
wrShort((uint16_t) dg.panel.max_rows);
wrShort((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) {
wrByte((uint8_t) i);
wrByte((uint8_t) j);
wrByte(dg.floor[i][j].creature_id);
}
}
}
// marks end of creature_id info
wrByte((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) {
wrByte((uint8_t) i);
wrByte((uint8_t) j);
wrByte(dg.floor[i][j].treasure_id);
}
}
}
// marks end of treasure_id info
wrByte((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 (auto &row : dg.floor) {
for (auto tile : row) {
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 == UCHAR_MAX) {
wrByte((uint8_t) count);
wrByte(prev_char);
prev_char = char_tmp;
count = 1;
} else {
count++;
}
}
}
// save last entry
wrByte((uint8_t) count);
wrByte(prev_char);
wrShort((uint16_t) game.treasure.current_id);
for (int i = config::treasure::MIN_TREASURE_LIST_ID; i < game.treasure.current_id; i++) {
wrItem(game.treasure.list[i]);
}
wrShort((uint16_t) next_free_monster_id);
for (int i = config::monsters::MON_MIN_INDEX_ID; i < next_free_monster_id; i++) {
wrMonster(monsters[i]);
}
return !((ferror(fileptr) != 0) || fflush(fileptr) == EOF);
}
static bool saveChar(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_save_file != 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;
wrByte(CURRENT_VERSION_MAJOR);
xor_byte = 0;
wrByte(CURRENT_VERSION_MINOR);
xor_byte = 0;
wrByte(CURRENT_VERSION_PATCH);
xor_byte = 0;
auto char_tmp = (uint8_t)(randomNumber(256) - 1);
wrByte(char_tmp);
// Note that xor_byte is now equal to char_tmp
ok = svWrite();
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 = rdByte();
xor_byte = 0;
version_min = rdByte();
xor_byte = 0;
patch_level = rdByte();
xor_byte = getByte();
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 uint_16_t_tmp;
uint32_t l;
uint_16_t_tmp = rdShort();
while (uint_16_t_tmp != 0xFFFF) {
if (uint_16_t_tmp >= MON_MAX_CREATURES) {
goto error;
}
Recall_t &memory = creature_recall[uint_16_t_tmp];
memory.movement = rdLong();
memory.spells = rdLong();
memory.kills = rdShort();
memory.deaths = rdShort();
memory.defenses = rdShort();
memory.wake = rdByte();
memory.ignore = rdByte();
rdBytes(memory.attacks, MON_MAX_ATTACKS);
uint_16_t_tmp = rdShort();
}
l = rdLong();
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) {
rdString(py.misc.name);
py.misc.gender = rdBool();
py.misc.au = rdLong();
py.misc.max_exp = rdLong();
py.misc.exp = rdLong();
py.misc.exp_fraction = rdShort();
py.misc.age = rdShort();
py.misc.height = rdShort();
py.misc.weight = rdShort();
py.misc.level = rdShort();
py.misc.max_dungeon_depth = rdShort();
py.misc.chance_in_search = rdShort();
py.misc.fos = rdShort();
py.misc.bth = rdShort();
py.misc.bth_with_bows = rdShort();
py.misc.mana = rdShort();
py.misc.max_hp = rdShort();
py.misc.plusses_to_hit = rdShort();
py.misc.plusses_to_damage = rdShort();
py.misc.ac = rdShort();
py.misc.magical_ac = rdShort();
py.misc.display_to_hit = rdShort();
py.misc.display_to_damage = rdShort();
py.misc.display_ac = rdShort();
py.misc.display_to_ac = rdShort();
py.misc.disarm = rdShort();
py.misc.saving_throw = rdShort();
py.misc.social_class = rdShort();
py.misc.stealth_factor = rdShort();
py.misc.class_id = rdByte();
py.misc.race_id = rdByte();
py.misc.hit_die = rdByte();
py.misc.experience_factor = rdByte();
py.misc.current_mana = rdShort();
py.misc.current_mana_fraction = rdShort();
py.misc.current_hp = rdShort();
py.misc.current_hp_fraction = rdShort();
for (auto &entry : py.misc.history) {
rdString(entry);
}
rdBytes(py.stats.max, 6);
rdBytes(py.stats.current, 6);
rdShorts((uint16_t *) py.stats.modified, 6);
rdBytes(py.stats.used, 6);
py.flags.status = rdLong();
py.flags.rest = rdShort();
py.flags.blind = rdShort();
py.flags.paralysis = rdShort();
py.flags.confused = rdShort();
py.flags.food = rdShort();
py.flags.food_digested = rdShort();
py.flags.protection = rdShort();
py.flags.speed = rdShort();
py.flags.fast = rdShort();
py.flags.slow = rdShort();
py.flags.afraid = rdShort();
py.flags.poisoned = rdShort();
py.flags.image = rdShort();
py.flags.protect_evil = rdShort();
py.flags.invulnerability = rdShort();
py.flags.heroism = rdShort();
py.flags.super_heroism = rdShort();
py.flags.blessed = rdShort();
py.flags.heat_resistance = rdShort();
py.flags.cold_resistance = rdShort();
py.flags.detect_invisible = rdShort();
py.flags.word_of_recall = rdShort();
py.flags.see_infra = rdShort();
py.flags.timed_infra = rdShort();
py.flags.see_invisible = rdBool();
py.flags.teleport = rdBool();
py.flags.free_action = rdBool();
py.flags.slow_digest = rdBool();
py.flags.aggravate = rdBool();
py.flags.resistant_to_fire = rdBool();
py.flags.resistant_to_cold = rdBool();
py.flags.resistant_to_acid = rdBool();
py.flags.regenerate_hp = rdBool();
py.flags.resistant_to_light = rdBool();
py.flags.free_fall = rdBool();
py.flags.sustain_str = rdBool();
py.flags.sustain_int = rdBool();
py.flags.sustain_wis = rdBool();
py.flags.sustain_con = rdBool();
py.flags.sustain_dex = rdBool();
py.flags.sustain_chr = rdBool();
py.flags.confuse_monster = rdBool();
py.flags.new_spells_to_learn = rdByte();
missiles_counter = rdShort();
dg.game_turn = rdLong();
py.pack.unique_items = rdShort();
if (py.pack.unique_items > PlayerEquipment::Wield) {
goto error;
}
for (int i = 0; i < py.pack.unique_items; i++) {
rdItem(py.inventory[i]);
}
for (int i = PlayerEquipment::Wield; i < PLAYER_INVENTORY_SIZE; i++) {
rdItem(py.inventory[i]);
}
py.pack.weight = rdShort();
py.equipment_count = rdShort();
py.flags.spells_learnt = rdLong();
py.flags.spells_worked = rdLong();
py.flags.spells_forgotten = rdLong();
rdBytes(py.flags.spells_learned_order, 32);
rdBytes(objects_identified, OBJECT_IDENT_SIZE);
game.magic_seed = rdLong();
game.town_seed = rdLong();
last_message_id = rdShort();
for (auto &message : messages) {
rdString(message);
}
uint16_t panic_save_short;
uint16_t total_winner_short;
panic_save_short = rdShort();
total_winner_short = rdShort();
panic_save = panic_save_short != 0;
game.total_winner = total_winner_short != 0;
game.noscore = rdShort();
rdShorts(py.base_hp_levels, PLAYER_MAX_LEVEL);
for (auto &store : stores) {
store.turns_left_before_closing = rdLong();
store.insults_counter = rdShort();
store.owner_id = rdByte();
store.unique_items_counter = rdByte();
store.good_purchases = rdShort();
store.bad_purchases = rdShort();
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 = rdLong();
rdItem(store.inventory[j].item);
}
}
time_saved = rdLong();
rdString(game.character_died_from);
py.max_score = rdLong();
py.misc.date_of_birth = rdLong();
}
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 = rdShort();
py.pos.y = rdShort();
py.pos.x = rdShort();
monster_multiply_total = rdShort();
dg.height = rdShort();
dg.width = rdShort();
dg.panel.max_rows = rdShort();
dg.panel.max_cols = rdShort();
uint8_t char_tmp, ychar, xchar, count;
// read in the creature ptr info
char_tmp = rdByte();
while (char_tmp != 0xFF) {
ychar = char_tmp;
xchar = rdByte();
char_tmp = rdByte();
if (xchar > MAX_WIDTH || ychar > MAX_HEIGHT) {
goto error;
}
dg.floor[ychar][xchar].creature_id = char_tmp;
char_tmp = rdByte();
}
// read in the treasure ptr info
char_tmp = rdByte();
while (char_tmp != 0xFF) {
ychar = char_tmp;
xchar = rdByte();
char_tmp = rdByte();
if (xchar > MAX_WIDTH || ychar > MAX_HEIGHT) {
goto error;
}
dg.floor[ychar][xchar].treasure_id = char_tmp;
char_tmp = rdByte();
}
// read in the rest of the cave info
tile = &dg.floor[0][0];
total_count = 0;
while (total_count != MAX_HEIGHT * MAX_WIDTH) {
count = rdByte();
char_tmp = rdByte();
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;
}
game.treasure.current_id = rdShort();
if (game.treasure.current_id > LEVEL_MAX_OBJECTS) {
goto error;
}
for (int i = config::treasure::MIN_TREASURE_LIST_ID; i < game.treasure.current_id; i++) {
rdItem(game.treasure.list[i]);
}
next_free_monster_id = rdShort();
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++) {
rdMonster(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_save_file = 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});
// We have messages for the player to read, this will ask for a keypress
printMessage(CNIL);
exitProgram();
return false; // not reached
}
static void wrBool(bool value) {
wrByte((uint8_t) value);
}
static void wrByte(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 wrShort(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 wrLong(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 wrBytes(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 wrString(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 wrShorts(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 wrItem(Inventory_t &item) {
DEBUG(fprintf(logfile, "ITEM:\n"))
wrShort(item.id);
wrByte(item.special_name_id);
wrString(item.inscription);
wrLong(item.flags);
wrByte(item.category_id);
wrByte(item.sprite);
wrShort((uint16_t) item.misc_use);
wrLong((uint32_t) item.cost);
wrByte(item.sub_category_id);
wrByte(item.items_count);
wrShort(item.weight);
wrShort((uint16_t) item.to_hit);
wrShort((uint16_t) item.to_damage);
wrShort((uint16_t) item.ac);
wrShort((uint16_t) item.to_ac);
wrByte(item.damage.dice);
wrByte(item.damage.sides);
wrByte(item.depth_first_found);
wrByte(item.identification);
}
static void wrMonster(Monster_t const &monster) {
DEBUG(fprintf(logfile, "MONSTER:\n"))
wrShort((uint16_t) monster.hp);
wrShort((uint16_t) monster.sleep_count);
wrShort((uint16_t) monster.speed);
wrShort(monster.creature_id);
wrByte((uint8_t) monster.pos.y);
wrByte((uint8_t) monster.pos.x);
wrByte(monster.distance_from_player);
wrBool(monster.lit);
wrByte(monster.stunned_amount);
wrByte(monster.confused_amount);
}
// get_byte reads a single byte from a file, without any xor_byte encryption
static uint8_t getByte() {
return (uint8_t)(getc(fileptr) & 0xFF);
}
static bool rdBool() {
return (bool) rdByte();
}
static uint8_t rdByte() {
auto c = getByte();
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 rdShort() {
auto c = getByte();
uint16_t decoded_int = c ^ xor_byte;
xor_byte = getByte();
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 rdLong() {
auto c = getByte();
uint32_t decoded_long = c ^ xor_byte;
xor_byte = getByte();
decoded_long |= (uint32_t)(c ^ xor_byte) << 8;
DEBUG(fprintf(logfile, "LONG: %02X %02X ", (int) c, (int) xor_byte))
c = getByte();
decoded_long |= (uint32_t)(c ^ xor_byte) << 16;
xor_byte = getByte();
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 rdBytes(uint8_t *value, int count) {
DEBUG(fprintf(logfile, "%d BYTES:", count))
uint8_t *ptr = value;
for (int i = 0; i < count; i++) {
auto c = getByte();
*ptr++ = c ^ xor_byte;
xor_byte = c;
DEBUG(fprintf(logfile, " %02X = %d", (int) c, (int) ptr[-1]))
}
DEBUG(fprintf(logfile, "\n"))
}
static void rdString(char *str) {
DEBUG(char *s = str)
DEBUG(fprintf(logfile, "STRING: "))
do {
auto c = getByte();
*str = c ^ xor_byte;
xor_byte = c;
DEBUG(fprintf(logfile, "%02X ", (int) c))
} while (*str++ != '\0');
DEBUG(fprintf(logfile, "= \"%s\"\n", s))
}
static void rdShorts(uint16_t *value, int count) {
DEBUG(fprintf(logfile, "%d SHORTS:", count))
uint16_t *sptr = value;
for (int i = 0; i < count; i++) {
auto c = getByte();
uint16_t s = c ^ xor_byte;
xor_byte = getByte();
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 rdItem(Inventory_t &item) {
DEBUG(fprintf(logfile, "ITEM:\n"))
item.id = rdShort();
item.special_name_id = rdByte();
rdString(item.inscription);
item.flags = rdLong();
item.category_id = rdByte();
item.sprite = rdByte();
item.misc_use = rdShort();
item.cost = rdLong();
item.sub_category_id = rdByte();
item.items_count = rdByte();
item.weight = rdShort();
item.to_hit = rdShort();
item.to_damage = rdShort();
item.ac = rdShort();
item.to_ac = rdShort();
item.damage.dice = rdByte();
item.damage.sides = rdByte();
item.depth_first_found = rdByte();
item.identification = rdByte();
}
static void rdMonster(Monster_t &monster) {
DEBUG(fprintf(logfile, "MONSTER:\n"))
monster.hp = rdShort();
monster.sleep_count = rdShort();
monster.speed = rdShort();
monster.creature_id = rdShort();
monster.pos.y = rdByte();
monster.pos.x = rdByte();
monster.distance_from_player = rdByte();
monster.lit = rdBool();
monster.stunned_amount = rdByte();
monster.confused_amount = rdByte();
}
// 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.
wrByte(xor_byte);
wrLong((uint32_t) score.points);
wrLong((uint32_t) score.birth_date);
wrShort((uint16_t) score.uid);
wrShort((uint16_t) score.mhp);
wrShort((uint16_t) score.chp);
wrByte(score.dungeon_depth);
wrByte(score.level);
wrByte(score.deepest_dungeon_depth);
wrByte(score.gender);
wrByte(score.race);
wrByte(score.character_class);
wrBytes((uint8_t *) score.name, PLAYER_NAME_SIZE);
wrBytes((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 = getByte();
score.points = rdLong();
score.birth_date = rdLong();
score.uid = rdShort();
score.mhp = rdShort();
score.chp = rdShort();
score.dungeon_depth = rdByte();
score.level = rdByte();
score.deepest_dungeon_depth = rdByte();
score.gender = rdByte();
score.race = rdByte();
score.character_class = rdByte();
rdBytes((uint8_t *) score.name, PLAYER_NAME_SIZE);
rdBytes((uint8_t *) score.died_from, 25);
DEBUG(fclose(logfile))
}
umoria-5.7.13/src/spells.h 0000644 0001750 0001750 00000007032 13720166710 014324 0 ustar ariel ariel // 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 MagicSpellFlags {
MagicMissile,
Lightning,
PoisonGas,
Acid,
Frost,
Fire,
HolyOrb,
};
// 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(Coord_t coord);
bool spellDarkenArea(Coord_t coord);
void spellMapCurrentArea();
bool spellIdentifyItem();
bool spellAggravateMonsters(int affect_distance);
bool spellSurroundPlayerWithTraps();
bool spellSurroundPlayerWithDoors();
bool spellDestroyAdjacentDoorsTraps();
bool spellDetectMonsters();
void spellLightLine(Coord_t coord, int direction);
void spellStarlite(Coord_t coord);
bool spellDisarmAllInDirection(Coord_t coord, int direction);
void spellFireBolt(Coord_t coord, int direction, int damage_hp, int spell_type, const std::string &spell_name);
void spellFireBall(Coord_t coord, int direction, int damage_hp, int spell_type, const std::string &spell_name);
void spellBreath(Coord_t coord, int monster_id, int damage_hp, int spell_type, const std::string &spell_name);
bool spellRechargeItem(int number_of_charges);
bool spellChangeMonsterHitPoints(Coord_t coord, int direction, int damage_hp);
bool spellDrainLifeFromMonster(Coord_t coord, int direction);
bool spellSpeedMonster(Coord_t coord, int direction, int speed);
bool spellConfuseMonster(Coord_t coord, int direction);
bool spellSleepMonster(Coord_t coord, int direction);
bool spellWallToMud(Coord_t coord, int direction);
bool spellDestroyDoorsTrapsInDirection(Coord_t coord, int direction);
bool spellPolymorphMonster(Coord_t coord, int direction);
bool spellBuildWall(Coord_t coord, int direction);
bool spellCloneMonster(Coord_t coord, int direction);
void spellTeleportAwayMonster(int monster_id, int distance_from_player);
void spellTeleportPlayerTo(Coord_t coord);
bool spellTeleportAwayMonsterInDirection(Coord_t coord, 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(Coord_t coord);
bool spellEnchantItem(int16_t &plusses, int16_t max_bonus_limit);
bool spellRemoveCurseFromAllItems();
bool spellRestorePlayerLevels();
umoria-5.7.13/src/player_move.cpp 0000644 0001750 0001750 00000040655 13720166710 015707 0 ustar ariel ariel // 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, Coord_t coord) {
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, 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(Coord_t coord) {
(void) dungeonDeleteObject(coord);
dungeonPlaceRandomObjectAt(coord, 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(PlayerAttr::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(Coord_t coord) {
game.teleport_player = true;
printMessage("You hit a teleport trap!");
// Light up the teleport trap, before we teleport away.
dungeonMoveCharacterLight(coord, coord);
}
static void trapRockfall(Coord_t coord, int dam) {
playerTakesHit(dam, "a falling rock");
(void) dungeonDeleteObject(coord);
dungeonPlaceRubble(coord);
printMessage("You are hit by falling rock.");
}
static void trapCorrodeGas() {
printMessage("A strange red gas surrounds you.");
damageCorrodingGas("corrosion gas");
}
static void trapSummonMonster(Coord_t coord) {
// Rune disappears.
(void) dungeonDeleteObject(coord);
int num = 2 + randomNumber(3);
Coord_t location = Coord_t{0, 0};
for (int i = 0; i < num; i++) {
location.y = coord.y;
location.x = coord.x;
(void) monsterSummon(location, false);
}
}
static void trapFire(int dam) {
printMessage("You are enveloped in flames!");
damageFire(dam, "a fire trap");
}
static void trapAcid(int dam) {
printMessage("You are splashed with acid!");
damageAcid(dam, "an acid trap");
}
static void trapPoisonGas(int dam) {
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(PlayerAttr::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 {
OpenPit = 1,
ArrowPit,
CoveredPit,
TrapDoor,
SleepingGas,
HiddenObject,
DartOfStr,
Teleport,
Rockfall,
CorrodingGas,
SummonMonster,
FireTrap,
AcidTrap,
PoisonGasTrap, // TODO: name would clash with MagicSpellFlags::PoisonGas
BlindingGas,
ConfuseGas,
SlowDart,
DartOfCon,
SecretDoor,
ScareMonster = 99,
GeneralStore = 101,
Armory,
Weaponsmith,
Temple,
Alchemist,
MagicShop,
};
// Player hit a trap. (Chuckle) -RAK-
static void playerStepsOnTrap(Coord_t coord) {
playerEndRunning();
trapChangeVisibility(coord);
Inventory_t const &item = game.treasure.list[dg.floor[coord.y][coord.x].treasure_id];
int damage = diceRoll(item.damage);
switch ((TrapTypes) item.sub_category_id) {
case TrapTypes::OpenPit:
trapOpenPit(item, damage);
break;
case TrapTypes::ArrowPit:
trapArrow(item, damage);
break;
case TrapTypes::CoveredPit:
trapCoveredPit(item, damage, coord);
break;
case TrapTypes::TrapDoor:
trapDoor(item, damage);
break;
case TrapTypes::SleepingGas:
trapSleepingGas();
break;
case TrapTypes::HiddenObject:
trapHiddenObject(coord);
break;
case TrapTypes::DartOfStr:
trapStrengthDart(item, damage);
break;
case TrapTypes::Teleport:
trapTeleport(coord);
break;
case TrapTypes::Rockfall:
trapRockfall(coord, damage);
break;
case TrapTypes::CorrodingGas:
trapCorrodeGas();
break;
case TrapTypes::SummonMonster:
trapSummonMonster(coord);
break;
case TrapTypes::FireTrap:
trapFire(damage);
break;
case TrapTypes::AcidTrap:
trapAcid(damage);
break;
case TrapTypes::PoisonGasTrap:
trapPoisonGas(damage);
break;
case TrapTypes::BlindingGas:
trapBlindGas();
break;
case TrapTypes::ConfuseGas:
trapConfuseGas();
break;
case TrapTypes::SlowDart:
trapSlowDart(item, damage);
break;
case TrapTypes::DartOfCon:
trapConstitutionDart(item, damage);
break;
case TrapTypes::SecretDoor:
case TrapTypes::ScareMonster:
break;
// Town level traps are special, the stores.
case TrapTypes::GeneralStore:
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::MagicShop:
storeEnter(5);
break;
default:
// All cases are handled, so this should never be reached!
printMessage("Unknown trap value.");
break;
}
}
static bool playerRandomMovement(int dir) {
// Never random if sitting
if (dir == 5) {
return false;
}
// 75% random movement
bool player_random_move = randomNumber(4) > 1;
bool player_is_confused = py.flags.confused > 0;
return player_is_confused && player_random_move;
}
// 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(Coord_t coord, bool pickup) {
Inventory_t &item = game.treasure.list[dg.floor[coord.y][coord.x].treasure_id];
int tile_flags = game.treasure.list[dg.floor[coord.y][coord.x].treasure_id].category_id;
if (tile_flags > TV_MAX_PICK_UP) {
if (tile_flags == TV_INVIS_TRAP || tile_flags == TV_VIS_TRAP || tile_flags == TV_STORE_DOOR) {
// OOPS!
playerStepsOnTrap(coord);
}
return;
}
obj_desc_t description = {'\0'};
obj_desc_t msg = {'\0'};
playerEndRunning();
// There's GOLD in them thar hills!
if (tile_flags == 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);
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, py.inventory[locn], true);
(void) sprintf(msg, "You have %s (%c)", description, locn + 'a');
printMessage(msg);
(void) dungeonDeleteObject(coord);
}
} 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();
}
Coord_t coord = py.pos;
// Legal move?
if (!playerMovePosition(direction, coord)) {
return;
}
Tile_t const &tile = dg.floor[coord.y][coord.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
Coord_t old_coord = py.pos;
py.pos.y = coord.y;
py.pos.x = coord.x;
// Move character record (-1)
dungeonMoveCreatureRecord(old_coord, py.pos);
// Check for new panel
if (coordOutsidePanel(py.pos, false)) {
drawDungeonPanel();
}
// Check to see if they should stop
if (py.running_tracker != 0) {
playerAreaAffect(direction, py.pos);
}
// 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.pos, 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(py.pos);
}
} else if (tile.perma_lit_room && py.flags.blind < 1) {
// In doorway of light-room?
for (int row = (py.pos.y - 1); row <= (py.pos.y + 1); row++) {
for (int col = (py.pos.x - 1); col <= (py.pos.x + 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(old_coord, py.pos);
// An object is beneath them.
if (tile.treasure_id != 0) {
carry(py.pos, do_pickup);
// if stepped on falling rock trap, and space contains
// rubble, then step back into a clear area
if (game.treasure.list[tile.treasure_id].category_id == TV_RUBBLE) {
dungeonMoveCreatureRecord(py.pos, old_coord);
dungeonMoveCharacterLight(py.pos, old_coord);
py.pos.y = old_coord.y;
py.pos.x = old_coord.x;
// check to see if we have stepped back onto another trap, if so, set it off
uint8_t id = dg.floor[py.pos.y][py.pos.x].treasure_id;
if (id != 0) {
int val = game.treasure.list[id].category_id;
if (val == TV_INVIS_TRAP || val == TV_VIS_TRAP || val == TV_STORE_DOOR) {
playerStepsOnTrap(py.pos);
}
}
}
}
} else {
// Can't move onto floor space
if ((py.running_tracker == 0) && tile.treasure_id != 0) {
if (game.treasure.list[tile.treasure_id].category_id == TV_RUBBLE) {
printMessage("There is rubble blocking your way.");
} else if (game.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(coord);
}
}
}
umoria-5.7.13/src/treasure.h 0000644 0001750 0001750 00000005565 13720166710 014665 0 ustar ariel ariel // 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 int16_t missiles_counter;
void magicTreasureMagicalAbility(int item_id, int level);
umoria-5.7.13/src/player_tunnel.cpp 0000644 0001750 0001750 00000013361 13720166710 016240 0 ustar ariel ariel // 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 || (game.treasure.list[treasure_id].category_id != TV_RUBBLE && game.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 digging_ability = py.stats.used[PlayerAttr::A_STR];
if ((weapon.flags & config::treasure::flags::TR_TUNNEL) != 0u) {
digging_ability += 25 + weapon.misc_use * 50;
} else {
digging_ability += maxDiceRoll(weapon.damage) + weapon.to_hit + weapon.to_damage;
// divide by two so that digging without shovel isn't too easy
digging_ability >>= 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) {
digging_ability += (py.stats.used[PlayerAttr::A_STR] * 15) - weapon.weight;
if (digging_ability < 0) {
digging_ability = 0;
}
}
return digging_ability;
}
static void dungeonDigGraniteWall(Coord_t coord, int digging_ability) {
int i = randomNumber(1200) + 80;
if (playerTunnelWall(coord, digging_ability, i)) {
printMessage("You have finished the tunnel.");
} else {
printMessageNoCommandInterrupt("You tunnel into the granite wall.");
}
}
static void dungeonDigMagmaWall(Coord_t coord, int digging_ability) {
int i = randomNumber(600) + 10;
if (playerTunnelWall(coord, digging_ability, i)) {
printMessage("You have finished the tunnel.");
} else {
printMessageNoCommandInterrupt("You tunnel into the magma intrusion.");
}
}
static void dungeonDigQuartzWall(Coord_t coord, int digging_ability) {
int i = randomNumber(400) + 10;
if (playerTunnelWall(coord, digging_ability, i)) {
printMessage("You have finished the tunnel.");
} else {
printMessageNoCommandInterrupt("You tunnel into the quartz vein.");
}
}
static void dungeonDigRubble(Coord_t coord, int digging_ability) {
if (digging_ability > randomNumber(180)) {
(void) dungeonDeleteObject(coord);
printMessage("You have removed the rubble.");
if (randomNumber(10) == 1) {
dungeonPlaceRandomObjectAt(coord, false);
if (caveTileVisible(coord)) {
printMessage("You have found something!");
}
}
dungeonLiteSpot(coord);
} 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(Coord_t coord, uint8_t wall_type, int digging_ability) {
switch (wall_type) {
case TILE_GRANITE_WALL:
dungeonDigGraniteWall(coord, digging_ability);
break;
case TILE_MAGMA_WALL:
dungeonDigMagmaWall(coord, digging_ability);
break;
case TILE_QUARTZ_WALL:
dungeonDigQuartzWall(coord, 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);
}
Coord_t coord = py.pos;
(void) playerMovePosition(direction, coord);
Tile_t const &tile = dg.floor[coord.y][coord.x];
Inventory_t &item = py.inventory[PlayerEquipment::Wield];
if (!playerCanTunnel(tile.treasure_id, tile.feature_id)) {
return;
}
if (tile.creature_id > 1) {
objectBlockedByMonster(tile.creature_id);
playerAttackPosition(coord);
return;
}
if (item.category_id != TV_NOTHING) {
int digging_ability = playerDiggingAbility(item);
if (!dungeonDigAtLocation(coord, tile.feature_id, digging_ability)) {
// Is there an object in the way? (Rubble and secret doors)
if (tile.treasure_id != 0) {
if (game.treasure.list[tile.treasure_id].category_id == TV_RUBBLE) {
dungeonDigRubble(coord, digging_ability);
} else if (game.treasure.list[tile.treasure_id].category_id == TV_SECRET_DOOR) {
// Found secret door!
printMessageNoCommandInterrupt("You tunnel into the granite wall.");
playerSearch(py.pos, py.misc.chance_in_search);
} else {
abort();
}
} else {
abort();
}
}
return;
}
printMessage("You dig with your hands, making no progress.");
}
umoria-5.7.13/src/game.cpp 0000644 0001750 0001750 00000021775 13720166710 014300 0 ustar ariel ariel // 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(SHRT_MAX);
// off scale, assign random value between 4 and 5 times SD
if (tmp == SHRT_MAX) {
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 option_id = 0;
while (true) {
moveCursor(Coord_t{option_id + 1, 40});
switch (getKeyInput()) {
case ESCAPE:
return;
case '-':
if (option_id > 0) {
option_id--;
} else {
option_id = max - 1;
}
break;
case ' ':
case '\n':
case '\r':
if (option_id + 1 < max) {
option_id++;
} else {
option_id = 0;
}
break;
case 'y':
case 'Y':
putString("yes", Coord_t{option_id + 1, 40});
*game_options[option_id].o_var = true;
if (option_id + 1 < max) {
option_id++;
} else {
option_id = 0;
}
break;
case 'n':
case 'N':
putString("no ", Coord_t{option_id + 1, 40});
*game_options[option_id].o_var = false;
if (option_id + 1 < max) {
option_id++;
} else {
option_id = 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) {
// used in counted commands. -CJS-
if (game.use_last_direction) {
direction = py.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') {
py.prev_dir = command - '0';
direction = py.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);
}
// Abort the program with a message displayed on the terminal.
void abortProgram(const char *msg) {
flushInputBuffer();
terminalRestore();
printf("Program was manually aborted with the message:\n");
printf("%s\n", msg);
exit(0);
}
umoria-5.7.13/src/player_stats.cpp 0000644 0001750 0001750 00000030462 13720166710 016072 0 ustar ariel ariel // 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[PlayerAttr::A_STR];
if (player_strength * 15 < weight) {
weight_to_hit = player_strength * 15 - weight;
return 1;
}
int dexterity = playerAttackBlowsDexterity(py.stats.used[PlayerAttr::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[PlayerAttr::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[PlayerAttr::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 == PlayerAttr::A_STR) {
py.flags.status |= config::player::status::PY_STR_WGT;
playerRecalculateBonuses();
} else if (stat == PlayerAttr::A_DEX) {
playerRecalculateBonuses();
} else if (stat == PlayerAttr::A_INT && classes[py.misc.class_id].class_to_use_mage_spells == config::spells::SPELL_TYPE_MAGE) {
playerCalculateAllowedSpellsCount(PlayerAttr::A_INT);
playerGainMana(PlayerAttr::A_INT);
} else if (stat == PlayerAttr::A_WIS && classes[py.misc.class_id].class_to_use_mage_spells == config::spells::SPELL_TYPE_PRIEST) {
playerCalculateAllowedSpellsCount(PlayerAttr::A_WIS);
playerGainMana(PlayerAttr::A_WIS);
} else if (stat == PlayerAttr::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 < 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 < 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-
int16_t playerToHitAdjustment() {
int16_t total;
int dexterity = py.stats.used[PlayerAttr::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[PlayerAttr::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-
int16_t playerArmorClassAdjustment() {
int stat = py.stats.used[PlayerAttr::A_DEX];
int16_t 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[PlayerAttr::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-
int16_t playerDamageAdjustment() {
int stat = py.stats.used[PlayerAttr::A_STR];
int16_t 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.13/src/helpers.h 0000644 0001750 0001750 00000001503 13720166710 014461 0 ustar ariel ariel // 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.13/src/data_stores.cpp 0000644 0001750 0001750 00000004150 13720166710 015663 0 ustar ariel ariel // 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.13/src/types.h 0000644 0001750 0001750 00000001646 13720166710 014173 0 ustar ariel ariel // 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.
// This used to be NULL, but that was technically incorrect.
// CNIL is used instead of null to help avoid lint errors.
constexpr char *CNIL = nullptr;
// 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];
typedef struct {
int y;
int x;
} Coord_t;
umoria-5.7.13/src/player_pray.cpp 0000644 0001750 0001750 00000021026 13720166710 015703 0 ustar ariel ariel // 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.pack.unique_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 {
DetectEvil = 1,
CureLightWounds,
Bless,
RemoveFear,
CallLight,
FindTraps,
DetectDoorsStairs,
SlowPoison,
BlindCreature,
Portal,
CureMediumWounds,
Chant,
Sanctuary,
CreateFood,
RemoveCurse,
ResistHeadCold,
NeutralizePoison,
OrbOfDraining,
CureSeriousWounds,
SenseInvisible,
ProtectFromEvil,
Earthquake,
SenseSurroundings,
CureCriticalWounds,
TurnUndead,
Prayer,
DispelUndead,
Heal,
DispelEvil,
GlyphOfWarding,
HolyWord,
};
// Recite a prayers.
static void playerRecitePrayer(int prayer_type) {
int dir;
switch ((PriestSpellTypes)(prayer_type + 1)) {
case PriestSpellTypes::DetectEvil:
(void) spellDetectEvil();
break;
case PriestSpellTypes::CureLightWounds:
(void) spellChangePlayerHitPoints(diceRoll(Dice_t{3, 3}));
break;
case PriestSpellTypes::Bless:
playerBless(randomNumber(12) + 12);
break;
case PriestSpellTypes::RemoveFear:
(void) playerRemoveFear();
break;
case PriestSpellTypes::CallLight:
(void) spellLightArea(py.pos);
break;
case PriestSpellTypes::FindTraps:
(void) spellDetectTrapsWithinVicinity();
break;
case PriestSpellTypes::DetectDoorsStairs:
(void) spellDetectSecretDoorssWithinVicinity();
break;
case PriestSpellTypes::SlowPoison:
(void) spellSlowPoison();
break;
case PriestSpellTypes::BlindCreature:
if (getDirectionWithMemory(CNIL, dir)) {
(void) spellConfuseMonster(py.pos, dir);
}
break;
case PriestSpellTypes::Portal:
playerTeleport((py.misc.level * 3));
break;
case PriestSpellTypes::CureMediumWounds:
(void) spellChangePlayerHitPoints(diceRoll(Dice_t{4, 4}));
break;
case PriestSpellTypes::Chant:
playerBless(randomNumber(24) + 24);
break;
case PriestSpellTypes::Sanctuary:
(void) monsterSleep(py.pos);
break;
case PriestSpellTypes::CreateFood:
spellCreateFood();
break;
case PriestSpellTypes::RemoveCurse:
for (auto &entry : py.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::ResistHeadCold:
py.flags.heat_resistance += randomNumber(10) + 10;
py.flags.cold_resistance += randomNumber(10) + 10;
break;
case PriestSpellTypes::NeutralizePoison:
(void) playerCurePoison();
break;
case PriestSpellTypes::OrbOfDraining:
if (getDirectionWithMemory(CNIL, dir)) {
spellFireBall(py.pos, dir, (diceRoll(Dice_t{3, 6}) + py.misc.level), MagicSpellFlags::HolyOrb, "Black Sphere");
}
break;
case PriestSpellTypes::CureSeriousWounds:
(void) spellChangePlayerHitPoints(diceRoll(Dice_t{8, 4}));
break;
case PriestSpellTypes::SenseInvisible:
playerDetectInvisible(randomNumber(24) + 24);
break;
case PriestSpellTypes::ProtectFromEvil:
(void) playerProtectEvil();
break;
case PriestSpellTypes::Earthquake:
spellEarthquake();
break;
case PriestSpellTypes::SenseSurroundings:
spellMapCurrentArea();
break;
case PriestSpellTypes::CureCriticalWounds:
(void) spellChangePlayerHitPoints(diceRoll(Dice_t{16, 4}));
break;
case PriestSpellTypes::TurnUndead:
(void) spellTurnUndead();
break;
case PriestSpellTypes::Prayer:
playerBless(randomNumber(48) + 48);
break;
case PriestSpellTypes::DispelUndead:
(void) spellDispelCreature(config::monsters::defense::CD_UNDEAD, (3 * py.misc.level));
break;
case PriestSpellTypes::Heal:
(void) spellChangePlayerHitPoints(200);
break;
case PriestSpellTypes::DispelEvil:
(void) spellDispelCreature(config::monsters::defense::CD_EVIL, (3 * py.misc.level));
break;
case PriestSpellTypes::GlyphOfWarding:
spellWardingGlyph();
break;
case PriestSpellTypes::HolyWord:
(void) playerRemoveFear();
(void) playerCurePoison();
(void) spellChangePlayerHitPoints(1000);
for (int i = PlayerAttr::A_STR; i <= PlayerAttr::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:
// All cases are handled, so this should never be reached!
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;
}
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;
if (randomNumber(100) < chance) {
printMessage("You lost your concentration!");
} else {
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) {
return;
}
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(PlayerAttr::A_CON);
}
} else {
py.misc.current_mana -= spell.mana_required;
}
printCharacterCurrentMana();
}
umoria-5.7.13/src/headers.h 0000644 0001750 0001750 00000003016 13720166710 014433 0 ustar ariel ariel // 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__ || __NetBSD__
#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 "inventory.h" // before game.h
#include "game.h" // before dungeon.h
#include "dungeon_tile.h"
#include "dungeon.h"
#include "helpers.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.13/src/staves.h 0000644 0001750 0001750 00000000476 13720166710 014334 0 ustar ariel ariel // 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.13/src/config.h 0000644 0001750 0001750 00000030246 13720166710 014272 0 ustar ariel ariel // 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
// clang-format off
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.13/src/treasure.cpp 0000644 0001750 0001750 00000111706 13720166710 015213 0 ustar ariel ariel // 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"
// 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 = SpecialNameIds::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 = SpecialNameIds::SN_RA;
item.cost += 1000;
break;
case 3:
case 4: // Resist Fire
item.flags |= config::treasure::flags::TR_RES_FIRE;
item.special_name_id = SpecialNameIds::SN_RF;
item.cost += 600;
break;
case 5:
case 6: // Resist Cold
item.flags |= config::treasure::flags::TR_RES_COLD;
item.special_name_id = SpecialNameIds::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 = SpecialNameIds::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 damage_bonus = maxDiceRoll(item.damage);
item.to_damage += magicEnchantmentBonus(0, 4 * damage_bonus, damage_bonus * 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 = SpecialNameIds::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 = SpecialNameIds::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 = SpecialNameIds::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 = SpecialNameIds::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 = SpecialNameIds::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 = SpecialNameIds::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 = SpecialNameIds::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 = SpecialNameIds::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 damage_bonus = maxDiceRoll(item.damage);
item.to_damage -= magicEnchantmentBonus(1, 11 * damage_bonus / 2, damage_bonus * 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 = SpecialNameIds::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 = SpecialNameIds::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 = SpecialNameIds::SN_CLUMSINESS;
} else {
item.flags |= config::treasure::flags::TR_STR;
item.special_name_id = SpecialNameIds::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 magic_type = randomNumber(12);
if (magic_type > 5) {
item.flags |= config::treasure::flags::TR_FFALL;
item.special_name_id = SpecialNameIds::SN_SLOW_DESCENT;
item.cost += 250;
} else if (magic_type == 1) {
item.flags |= config::treasure::flags::TR_SPEED;
item.special_name_id = SpecialNameIds::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 = SpecialNameIds::SN_STEALTH;
item.cost += 500;
}
}
static void cursedBoots(Inventory_t &item, int level) {
int magic_type = randomNumber(3);
switch (magic_type) {
case 1:
item.flags |= config::treasure::flags::TR_SPEED;
item.special_name_id = SpecialNameIds::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 = SpecialNameIds::SN_NOISE;
break;
default:
item.special_name_id = SpecialNameIds::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 magic_type = randomNumber(3);
switch (magic_type) {
case 1:
item.misc_use = (int16_t) randomNumber(2);
item.flags |= config::treasure::flags::TR_INT;
item.special_name_id = SpecialNameIds::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 = SpecialNameIds::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 = SpecialNameIds::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 = SpecialNameIds::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 = SpecialNameIds::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 = SpecialNameIds::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 = SpecialNameIds::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 = SpecialNameIds::SN_SEEING;
item.cost += 1000 + item.misc_use * 100;
break;
case 6:
item.flags |= config::treasure::flags::TR_REGEN;
item.special_name_id = SpecialNameIds::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 = SpecialNameIds::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 = SpecialNameIds::SN_DULLNESS;
break;
case 3:
item.flags |= config::treasure::flags::TR_BLIND;
item.special_name_id = SpecialNameIds::SN_BLINDNESS;
break;
case 4:
item.flags |= config::treasure::flags::TR_TIMID;
item.special_name_id = SpecialNameIds::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 = SpecialNameIds::SN_WEAKNESS;
break;
case 6:
item.flags |= config::treasure::flags::TR_TELEPORT;
item.special_name_id = SpecialNameIds::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 = SpecialNameIds::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:
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:
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:
case 8:
return randomNumber(3) + 1;
case 9:
return randomNumber(5) + 6;
case 10:
return randomNumber(10) + 12;
case 11:
case 12:
case 13:
return randomNumber(5) + 6;
case 14:
return randomNumber(10) + 12;
case 15:
return randomNumber(3) + 4;
case 16:
case 17:
return randomNumber(5) + 6;
case 18:
return randomNumber(3) + 4;
case 19:
return randomNumber(10) + 12;
case 20:
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 = SpecialNameIds::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 = SpecialNameIds::SN_STEALTH;
item.cost += 500;
}
static void cursedCloak(Inventory_t &item, int level) {
int magic_type = randomNumber(3);
switch (magic_type) {
case 1:
item.flags |= config::treasure::flags::TR_AGGRAVATE;
item.special_name_id = SpecialNameIds::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 = SpecialNameIds::SN_VULNERABILITY;
item.to_ac -= magicEnchantmentBonus(10, 100, level + 50);
item.cost = 0;
break;
default:
item.special_name_id = SpecialNameIds::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 magic_type = randomNumber(level + 4);
switch (magic_type) {
case 1:
item.flags = 0;
item.special_name_id = SpecialNameIds::SN_EMPTY;
break;
case 2:
item.flags |= config::treasure::chests::CH_LOCKED;
item.special_name_id = SpecialNameIds::SN_LOCKED;
break;
case 3:
case 4:
item.flags |= (config::treasure::chests::CH_LOSE_STR | config::treasure::chests::CH_LOCKED);
item.special_name_id = SpecialNameIds::SN_POISON_NEEDLE;
break;
case 5:
case 6:
item.flags |= (config::treasure::chests::CH_POISON | config::treasure::chests::CH_LOCKED);
item.special_name_id = SpecialNameIds::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 = SpecialNameIds::SN_GAS_TRAP;
break;
case 10:
case 11:
item.flags |= (config::treasure::chests::CH_EXPLODE | config::treasure::chests::CH_LOCKED);
item.special_name_id = SpecialNameIds::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 = SpecialNameIds::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 = SpecialNameIds::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 = SpecialNameIds::SN_MULTIPLE_TRAPS;
break;
}
}
static void magicalProjectileAdjustment(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 = SpecialNameIds::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 = SpecialNameIds::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 = SpecialNameIds::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 = SpecialNameIds::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 = SpecialNameIds::SN_DRAGON_SLAYING;
item.cost += 35;
break;
default:
break;
}
}
}
static void cursedProjectileAdjustment(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;
}
// Counter for missiles
// Note: converted to uint16_t when saving the game.
int16_t missiles_counter = 0;
static void magicalProjectile(Inventory_t &item, int special, int level, int chance, int cursed) {
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)) {
magicalProjectileAdjustment(item, special, level);
} else if (magicShouldBeEnchanted(cursed)) {
cursedProjectileAdjustment(item, level);
}
}
item.items_count = 0;
for (int i = 0; i < 7; i++) {
item.items_count += randomNumber(6);
}
if (missiles_counter == SHRT_MAX) {
missiles_counter = -SHRT_MAX - 1;
} else {
missiles_counter++;
}
item.misc_use = missiles_counter;
}
// 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 magic_amount;
Inventory_t &item = game.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:
magic_amount = wandMagic(item.sub_category_id);
if (magic_amount != -1) {
item.misc_use = (uint16_t) magic_amount;
}
break;
case TV_STAFF:
magic_amount = staffMagic(item.sub_category_id);
if (magic_amount != -1) {
item.misc_use = (uint16_t) magic_amount;
}
// 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:
magicalProjectile(item, special, level, chance, cursed);
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.13/src/wizard.h 0000644 0001750 0001750 00000001062 13720166710 014317 0 ustar ariel ariel // 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 wizardCureAll();
void wizardDropRandomItems();
void wizardJumpLevel();
void wizardGainExperience();
void wizardSummonMonster();
void wizardLightUpDungeon();
void wizardCharacterAdjustment();
void wizardGenerateObject();
void wizardCreateObjects();
umoria-5.7.13/src/player_bash.cpp 0000644 0001750 0001750 00000017214 13720166710 015651 0 ustar ariel ariel // 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(Coord_t coord);
static void playerBashPosition(Coord_t coord);
static void playerBashClosedDoor(Coord_t coord, 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();
}
Coord_t coord = py.pos;
(void) playerMovePosition(dir, coord);
Tile_t &tile = dg.floor[coord.y][coord.x];
if (tile.creature_id > 1) {
playerBashPosition(coord);
return;
}
if (tile.treasure_id != 0) {
Inventory_t &item = game.treasure.list[tile.treasure_id];
if (item.category_id == TV_CLOSED_DOOR) {
playerBashClosedDoor(coord, 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(Coord_t coord) {
int monster_id = dg.floor[coord.y][coord.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[PlayerAttr::A_STR];
base_to_hit += py.inventory[PlayerEquipment::Arm].weight / 2;
base_to_hit += py.misc.weight / 10;
if (!monster.lit) {
base_to_hit /= 2;
base_to_hit -= py.stats.used[PlayerAttr::A_DEX] * (BTH_PER_PLUS_TO_HIT_ADJUST - 1);
base_to_hit -= py.misc.level * class_level_adj[py.misc.class_id][PlayerClassLevelAdj::BTH] / 2;
}
if (playerTestBeingHit(base_to_hit, (int) py.misc.level, (int) py.stats.used[PlayerAttr::A_DEX], (int) creature.ac, PlayerClassLevelAdj::BTH)) {
vtype_t msg = {'\0'};
(void) sprintf(msg, "You hit %s.", name);
printMessage(msg);
int damage = diceRoll(py.inventory[PlayerEquipment::Arm].damage);
damage = playerWeaponCriticalBlow(py.inventory[PlayerEquipment::Arm].weight / 4 + py.stats.used[PlayerAttr::A_STR], 0, damage, PlayerClassLevelAdj::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[PlayerAttr::A_DEX]) {
printMessage("You are off balance.");
py.flags.paralysis = (int16_t)(1 + randomNumber(2));
}
}
static void playerBashPosition(Coord_t coord) {
// Is a Coward?
if (py.flags.afraid > 0) {
printMessage("You are afraid!");
return;
}
playerBashAttack(coord);
}
static void playerBashClosedDoor(Coord_t coord, int dir, Tile_t &tile, Inventory_t &item) {
printMessageNoCommandInterrupt("You smash into the door!");
int chance = py.stats.used[PlayerAttr::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, game.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);
}
return;
}
if (randomNumber(150) > py.stats.used[PlayerAttr::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.13/src/dungeon.h 0000644 0001750 0001750 00000006766 13720166710 014476 0 ustar ariel ariel // 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 RATIO = 3; // Size ratio of the Map screen
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(Coord_t from, Coord_t to);
void look();
umoria-5.7.13/src/monster_manager.cpp 0000644 0001750 0001750 00000023771 13720166710 016546 0 ustar ariel ariel // 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, Coord_t{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(Coord_t coord, 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.pos.y = coord.y;
monster.pos.x = coord.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(py.pos, coord);
monster.lit = false;
dg.floor[coord.y][coord.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;
}
Coord_t coord = Coord_t{0, 0};
do {
coord.y = randomNumber(dg.height - 2);
coord.x = randomNumber(dg.width - 2);
} while (dg.floor[coord.y][coord.x].feature_id >= MIN_CLOSED_SPACE || //
dg.floor[coord.y][coord.x].creature_id != 0 || //
dg.floor[coord.y][coord.x].treasure_id != 0 || //
coordDistanceBetween(coord, py.pos) <= 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.pos.y = coord.y;
monster.pos.x = coord.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(py.pos, coord);
dg.floor[coord.y][coord.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) {
Coord_t position = Coord_t{0, 0};
for (int i = 0; i < number; i++) {
do {
position.y = randomNumber(dg.height - 2);
position.x = randomNumber(dg.width - 2);
} while (dg.floor[position.y][position.x].feature_id >= MIN_CLOSED_SPACE || //
dg.floor[position.y][position.x].creature_id != 0 || //
coordDistanceBetween(position, py.pos) <= 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(position, l, sleeping);
}
}
static bool placeMonsterAdjacentTo(int monster_id, Coord_t &coord, bool slp) {
bool placed = false;
Coord_t position = Coord_t{0, 0};
for (int i = 0; i <= 9; i++) {
position.y = coord.y - 2 + randomNumber(3);
position.x = coord.x - 2 + randomNumber(3);
if (coordInBounds(position)) {
if (dg.floor[position.y][position.x].feature_id <= MAX_OPEN_SPACE && dg.floor[position.y][position.x].creature_id == 0) {
// Place_monster() should always return true here.
if (!monsterPlaceNew(position, monster_id, slp)) {
return false;
}
coord.y = position.y;
coord.x = position.x;
placed = true;
i = 9;
}
}
}
return placed;
}
// Places creature adjacent to given location -RAK-
bool monsterSummon(Coord_t &coord, bool sleeping) {
int monster_id = monsterGetOneSuitableForLevel(dg.current_level + config::monsters::MON_SUMMONED_LEVEL_ADJUST);
return placeMonsterAdjacentTo(monster_id, coord, sleeping);
}
// Places undead adjacent to given location -RAK-
bool monsterSummonUndead(Coord_t &coord) {
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, coord, 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.13/src/spells.cpp 0000644 0001750 0001750 00000236647 13720166710 014677 0 ustar ariel ariel // 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 = py.inventory[item_id].flags;
int first_spell = getAndClearFirstBit(flags);
// Get flags again since getAndClearFirstBit modified variable.
flags = py.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;
Coord_t coord = Coord_t{0, 0};
for (coord.y = dg.panel.top; coord.y <= dg.panel.bottom; coord.y++) {
for (coord.x = dg.panel.left; coord.x <= dg.panel.right; coord.x++) {
Tile_t &tile = dg.floor[coord.y][coord.x];
if (tile.treasure_id != 0 && game.treasure.list[tile.treasure_id].category_id == TV_GOLD && !caveTileVisible(coord)) {
tile.field_mark = true;
dungeonLiteSpot(coord);
detected = true;
}
}
}
return detected;
}
// Detect all objects on the current panel -RAK-
bool spellDetectObjectsWithinVicinity() {
bool detected = false;
Coord_t coord = Coord_t{0, 0};
for (coord.y = dg.panel.top; coord.y <= dg.panel.bottom; coord.y++) {
for (coord.x = dg.panel.left; coord.x <= dg.panel.right; coord.x++) {
Tile_t &tile = dg.floor[coord.y][coord.x];
if (tile.treasure_id != 0 && game.treasure.list[tile.treasure_id].category_id < TV_MAX_OBJECT && !caveTileVisible(coord)) {
tile.field_mark = true;
dungeonLiteSpot(coord);
detected = true;
}
}
}
return detected;
}
// Locates and displays traps on current panel -RAK-
bool spellDetectTrapsWithinVicinity() {
bool detected = false;
Coord_t coord = Coord_t{0, 0};
for (coord.y = dg.panel.top; coord.y <= dg.panel.bottom; coord.y++) {
for (coord.x = dg.panel.left; coord.x <= dg.panel.right; coord.x++) {
Tile_t &tile = dg.floor[coord.y][coord.x];
if (tile.treasure_id == 0) {
continue;
}
if (game.treasure.list[tile.treasure_id].category_id == TV_INVIS_TRAP) {
tile.field_mark = true;
trapChangeVisibility(coord);
detected = true;
} else if (game.treasure.list[tile.treasure_id].category_id == TV_CHEST) {
Inventory_t &item = game.treasure.list[tile.treasure_id];
spellItemIdentifyAndRemoveRandomInscription(item);
}
}
}
return detected;
}
// Locates and displays all secret doors on current panel -RAK-
bool spellDetectSecretDoorssWithinVicinity() {
bool detected = false;
Coord_t coord = Coord_t{0, 0};
for (coord.y = dg.panel.top; coord.y <= dg.panel.bottom; coord.y++) {
for (coord.x = dg.panel.left; coord.x <= dg.panel.right; coord.x++) {
Tile_t &tile = dg.floor[coord.y][coord.x];
if (tile.treasure_id == 0) {
continue;
}
if (game.treasure.list[tile.treasure_id].category_id == TV_SECRET_DOOR) {
// Secret doors
tile.field_mark = true;
trapChangeVisibility(coord);
detected = true;
} else if ((game.treasure.list[tile.treasure_id].category_id == TV_UP_STAIR || game.treasure.list[tile.treasure_id].category_id == TV_DOWN_STAIR) && !tile.field_mark) {
// Staircases
tile.field_mark = true;
dungeonLiteSpot(coord);
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.pos.y, monster.pos.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.pos.y, monster.pos.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(Coord_t coord) {
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[coord.y][coord.x].perma_lit_room && dg.current_level > 0) {
dungeonLightRoom(coord);
}
// Must always light immediate area, because one might be standing on
// the edge of a room, or next to a destroyed area, etc.
Coord_t spot = Coord_t{0, 0};
for (spot.y = coord.y - 1; spot.y <= coord.y + 1; spot.y++) {
for (spot.x = coord.x - 1; spot.x <= coord.x + 1; spot.x++) {
dg.floor[spot.y][spot.x].permanent_light = true;
dungeonLiteSpot(spot);
}
}
return lit;
}
// Darken an area, opposite of light area -RAK-
bool spellDarkenArea(Coord_t coord) {
bool darkened = false;
Coord_t spot = Coord_t{0, 0};
if (dg.floor[coord.y][coord.x].perma_lit_room && dg.current_level > 0) {
int half_height = (SCREEN_HEIGHT / 2);
int half_width = (SCREEN_WIDTH / 2);
int start_row = (coord.y / half_height) * half_height + 1;
int start_col = (coord.x / half_width) * half_width + 1;
int end_row = start_row + half_height - 1;
int end_col = start_col + half_width - 1;
for (spot.y = start_row; spot.y <= end_row; spot.y++) {
for (spot.x = start_col; spot.x <= end_col; spot.x++) {
Tile_t &tile = dg.floor[spot.y][spot.x];
if (tile.perma_lit_room && tile.feature_id <= MAX_CAVE_FLOOR) {
tile.permanent_light = false;
tile.feature_id = TILE_DARK_FLOOR;
dungeonLiteSpot(spot);
if (!caveTileVisible(spot)) {
darkened = true;
}
}
}
}
} else {
for (spot.y = coord.y - 1; spot.y <= coord.y + 1; spot.y++) {
for (spot.x = coord.x - 1; spot.x <= coord.x + 1; spot.x++) {
Tile_t &tile = dg.floor[spot.y][spot.x];
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(Coord_t coord) {
Coord_t spot = Coord_t{0, 0};
for (spot.y = coord.y - 1; spot.y <= coord.y + 1; spot.y++) {
for (spot.x = coord.x - 1; spot.x <= coord.x + 1; spot.x++) {
Tile_t &tile = dg.floor[spot.y][spot.x];
if (tile.feature_id >= MIN_CAVE_WALL) {
tile.permanent_light = true;
} else if (tile.treasure_id != 0 && game.treasure.list[tile.treasure_id].category_id >= TV_MIN_VISIBLE &&
game.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);
Coord_t coord = Coord_t{0, 0};
for (coord.y = row_min; coord.y <= row_max; coord.y++) {
for (coord.x = col_min; coord.x <= col_max; coord.x++) {
if (coordInBounds(coord) && dg.floor[coord.y][coord.x].feature_id <= MAX_CAVE_FLOOR) {
dungeonLightAreaAroundFloorTile(coord);
}
}
}
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(py.inventory[item_id], item_id);
Inventory_t &item = py.inventory[item_id];
spellItemIdentifyAndRemoveRandomInscription(item);
obj_desc_t description = {'\0'};
itemDescription(description, item, true);
obj_desc_t msg = {'\0'};
if (item_id >= PlayerEquipment::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() {
Coord_t coord = Coord_t{0, 0};
for (coord.y = py.pos.y - 1; coord.y <= py.pos.y + 1; coord.y++) {
for (coord.x = py.pos.x - 1; coord.x <= py.pos.x + 1; coord.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 (coord.y == py.pos.y && coord.x == py.pos.x) {
continue;
}
Tile_t const &tile = dg.floor[coord.y][coord.x];
if (tile.feature_id <= MAX_CAVE_FLOOR) {
if (tile.treasure_id != 0) {
(void) dungeonDeleteObject(coord);
}
dungeonSetTrap(coord, randomNumber(config::dungeon::objects::MAX_TRAPS) - 1);
// don't let player gain exp from the newly created traps
game.treasure.list[tile.treasure_id].misc_use = 0;
// open pits are immediately visible, so call dungeonLiteSpot
dungeonLiteSpot(coord);
}
}
}
// traps are always placed, so just return true
return true;
}
// Surround the player with doors. -RAK-
bool spellSurroundPlayerWithDoors() {
bool created = false;
Coord_t coord = Coord_t{0, 0};
for (coord.y = py.pos.y - 1; coord.y <= py.pos.y + 1; coord.y++) {
for (coord.x = py.pos.x - 1; coord.x <= py.pos.x + 1; coord.x++) {
// Don't put a door under the player!
if (coord.y == py.pos.y && coord.x == py.pos.x) {
continue;
}
Tile_t &tile = dg.floor[coord.y][coord.x];
if (tile.feature_id <= MAX_CAVE_FLOOR) {
if (tile.treasure_id != 0) {
(void) dungeonDeleteObject(coord);
}
int free_id = popt();
tile.feature_id = TILE_BLOCKED_FLOOR;
tile.treasure_id = (uint8_t) free_id;
inventoryItemCopyTo(config::dungeon::objects::OBJ_CLOSED_DOOR, game.treasure.list[free_id]);
dungeonLiteSpot(coord);
created = true;
}
}
}
return created;
}
// Destroys any adjacent door(s)/trap(s) -RAK-
bool spellDestroyAdjacentDoorsTraps() {
bool destroyed = false;
Coord_t coord = Coord_t{0, 0};
for (coord.y = py.pos.y - 1; coord.y <= py.pos.y + 1; coord.y++) {
for (coord.x = py.pos.x - 1; coord.x <= py.pos.x + 1; coord.x++) {
Tile_t const &tile = dg.floor[coord.y][coord.x];
if (tile.treasure_id == 0) {
continue;
}
Inventory_t &item = game.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)) {
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 = SpecialNameIds::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.pos.y, monster.pos.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.pos.y, monster.pos.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(Coord_t coord, int direction) {
int distance = 0;
bool finished = false;
Coord_t tmp_coord = Coord_t{0, 0};
while (!finished) {
Tile_t &tile = dg.floor[coord.y][coord.x];
if (distance > config::treasure::OBJECT_BOLTS_MAX_RANGE || tile.feature_id >= MIN_CLOSED_SPACE) {
(void) playerMovePosition(direction, coord);
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;
// coord y/x need to be maintained, so copy them
tmp_coord.y = coord.y;
tmp_coord.x = coord.x;
if (tile.feature_id == TILE_LIGHT_FLOOR) {
if (coordInsidePanel(tmp_coord)) {
dungeonLightRoom(tmp_coord);
}
} else {
dungeonLiteSpot(tmp_coord);
}
}
// 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 tmp_coord
(void) playerMovePosition(direction, coord);
distance++;
}
}
// Light line in all directions -RAK-
void spellStarlite(Coord_t coord) {
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(coord, dir);
}
}
}
// Disarms all traps/chests in a given direction -RAK-
bool spellDisarmAllInDirection(Coord_t coord, int direction) {
int distance = 0;
bool disarmed = false;
Tile_t *tile = nullptr;
do {
tile = &dg.floor[coord.y][coord.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 = game.treasure.list[tile->treasure_id];
if (item.category_id == TV_INVIS_TRAP || item.category_id == TV_VIS_TRAP) {
if (dungeonDeleteObject(coord)) {
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);
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 = SpecialNameIds::SN_UNLOCKED;
spellItemIdentifyAndRemoveRandomInscription(item);
}
}
// move must be at end because want to light up current spot
(void) playerMovePosition(direction, coord);
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 MagicSpellFlags::MagicMissile:
weapon_type = 0;
harm_type = 0;
*destroy = setNull;
break;
case MagicSpellFlags::Lightning:
weapon_type = config::monsters::spells::CS_BR_LIGHT;
harm_type = config::monsters::defense::CD_LIGHT;
*destroy = setLightningDestroyableItems;
break;
case MagicSpellFlags::PoisonGas:
weapon_type = config::monsters::spells::CS_BR_GAS;
harm_type = config::monsters::defense::CD_POISON;
*destroy = setNull;
break;
case MagicSpellFlags::Acid:
weapon_type = config::monsters::spells::CS_BR_ACID;
harm_type = config::monsters::defense::CD_ACID;
*destroy = setAcidDestroyableItems;
break;
case MagicSpellFlags::Frost:
weapon_type = config::monsters::spells::CS_BR_FROST;
harm_type = config::monsters::defense::CD_FROST;
*destroy = setFrostDestroyableItems;
break;
case MagicSpellFlags::Fire:
weapon_type = config::monsters::spells::CS_BR_FIRE;
harm_type = config::monsters::defense::CD_FIRE;
*destroy = setFireDestroyableItems;
break;
case MagicSpellFlags::HolyOrb:
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(Coord_t coord, 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);
Coord_t old_coord = Coord_t{0, 0};
int distance = 0;
bool finished = false;
while (!finished) {
old_coord.y = coord.y;
old_coord.x = coord.x;
(void) playerMovePosition(direction, coord);
distance++;
Tile_t &tile = dg.floor[coord.y][coord.x];
dungeonLiteSpot(old_coord);
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) && py.flags.blind < 1) {
panelPutTile('*', coord);
// show the bolt
putQIO();
}
}
}
// Shoot a ball in a given direction. Note that balls have an area affect. -RAK-
void spellFireBall(Coord_t coord, 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);
Coord_t old_coord = Coord_t{0, 0};
Coord_t spot = Coord_t{0, 0};
int distance = 0;
bool finished = false;
while (!finished) {
old_coord.y = coord.y;
old_coord.x = coord.x;
(void) playerMovePosition(direction, coord);
distance++;
dungeonLiteSpot(old_coord);
if (distance > config::treasure::OBJECT_BOLTS_MAX_RANGE) {
finished = true;
continue;
}
Tile_t *tile = &dg.floor[coord.y][coord.x];
if (tile->feature_id >= MIN_CLOSED_SPACE || tile->creature_id > 1) {
finished = true;
if (tile->feature_id >= MIN_CLOSED_SPACE) {
coord.y = old_coord.y;
coord.x = old_coord.x;
}
// The ball hits and explodes.
// The explosion.
for (int row = coord.y - max_distance; row <= coord.y + max_distance; row++) {
for (int col = coord.x - max_distance; col <= coord.x + max_distance; col++) {
spot.y = row;
spot.x = col;
if (coordInBounds(spot) && coordDistanceBetween(coord, spot) <= max_distance && los(coord, spot)) {
tile = &dg.floor[spot.y][spot.x];
if (tile->treasure_id != 0 && (*destroy)(&game.treasure.list[tile->treasure_id])) {
(void) dungeonDeleteObject(spot);
}
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(spot, coord) + 1));
if (monsterTakeHit((int) tile->creature_id, damage) >= 0) {
total_kills++;
}
tile->permanent_light = saved_lit_status;
} else if (coordInsidePanel(spot) && py.flags.blind < 1) {
panelPutTile('*', spot);
}
}
}
}
}
// show ball of whatever
putQIO();
for (int row = (coord.y - 2); row <= (coord.y + 2); row++) {
for (int col = (coord.x - 2); col <= (coord.x + 2); col++) {
spot.y = row;
spot.x = col;
if (coordInBounds(spot) && coordInsidePanel(spot) && coordDistanceBetween(coord, spot) <= max_distance) {
dungeonLiteSpot(spot);
}
}
}
// 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) && py.flags.blind < 1) {
panelPutTile('*', coord);
// show bolt
putQIO();
}
}
}
// Breath weapon works like a spellFireBall(), but affects the player.
// Note the area affect. -RAK-
void spellBreath(Coord_t coord, 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);
Coord_t location = Coord_t{0, 0};
for (location.y = coord.y - 2; location.y <= coord.y + 2; location.y++) {
for (location.x = coord.x - 2; location.x <= coord.x + 2; location.x++) {
if (coordInBounds(location) && coordDistanceBetween(coord, location) <= max_distance && los(coord, location)) {
Tile_t const &tile = dg.floor[location.y][location.x];
if (tile.treasure_id != 0 && (*destroy)(&game.treasure.list[tile.treasure_id])) {
(void) dungeonDeleteObject(location);
}
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(location) && ((py.flags.status & config::player::status::PY_BLIND) == 0u)) {
panelPutTile('*', location);
}
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(location, coord) + 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(Coord_t{monster.pos.y, monster.pos.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(location, coord) + 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 MagicSpellFlags::Lightning:
damageLightningBolt(damage, spell_name.c_str());
break;
case MagicSpellFlags::PoisonGas:
damagePoisonedGas(damage, spell_name.c_str());
break;
case MagicSpellFlags::Acid:
damageAcid(damage, spell_name.c_str());
break;
case MagicSpellFlags::Frost:
damageCold(damage, spell_name.c_str());
break;
case MagicSpellFlags::Fire:
damageFire(damage, spell_name.c_str());
break;
default:
break;
}
}
}
}
}
}
// show the ball of gas
putQIO();
Coord_t spot = Coord_t{0, 0};
for (spot.y = (coord.y - 2); spot.y <= (coord.y + 2); spot.y++) {
for (spot.x = (coord.x - 2); spot.x <= (coord.x + 2); spot.x++) {
if (coordInBounds(spot) && coordInsidePanel(spot) && coordDistanceBetween(coord, spot) <= max_distance) {
dungeonLiteSpot(spot);
}
}
}
}
// 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 = py.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(Coord_t coord, int direction, int damage_hp) {
int distance = 0;
bool changed = false;
bool finished = false;
while (!finished) {
(void) playerMovePosition(direction, coord);
distance++;
Tile_t const &tile = dg.floor[coord.y][coord.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(Coord_t coord, int direction) {
int distance = 0;
bool drained = false;
bool finished = false;
while (!finished) {
(void) playerMovePosition(direction, coord);
distance++;
Tile_t const &tile = dg.floor[coord.y][coord.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(Coord_t coord, int direction, int speed) {
int distance = 0;
bool changed = false;
bool finished = false;
while (!finished) {
(void) playerMovePosition(direction, coord);
distance++;
Tile_t const &tile = dg.floor[coord.y][coord.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(Coord_t coord, int direction) {
int distance = 0;
bool confused = false;
bool finished = false;
while (!finished) {
(void) playerMovePosition(direction, coord);
distance++;
Tile_t const &tile = dg.floor[coord.y][coord.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(Coord_t coord, int direction) {
int distance = 0;
bool asleep = false;
bool finished = false;
while (!finished) {
(void) playerMovePosition(direction, coord);
distance++;
Tile_t const &tile = dg.floor[coord.y][coord.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(Coord_t coord, int direction) {
int distance = 0;
bool turned = false;
bool finished = false;
while (!finished) {
(void) playerMovePosition(direction, coord);
distance++;
Tile_t const &tile = dg.floor[coord.y][coord.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(coord, 1, 0);
if (caveTileVisible(coord)) {
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) && caveTileVisible(coord)) {
turned = true;
obj_desc_t description = {'\0'};
itemDescription(description, game.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 (game.treasure.list[tile.treasure_id].category_id == TV_RUBBLE) {
(void) dungeonDeleteObject(coord);
if (randomNumber(10) == 1) {
dungeonPlaceRandomObjectAt(coord, false);
if (caveTileVisible(coord)) {
printMessage("You have found something!");
}
}
dungeonLiteSpot(coord);
} else {
(void) dungeonDeleteObject(coord);
}
}
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(Coord_t coord, int direction) {
bool destroyed = false;
int distance = 0;
Tile_t *tile = nullptr;
do {
(void) playerMovePosition(direction, coord);
distance++;
tile = &dg.floor[coord.y][coord.x];
// must move into first closed spot, as it might be a secret door
if (tile->treasure_id != 0) {
Inventory_t &item = game.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)) {
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 = SpecialNameIds::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(Coord_t coord, int direction) {
int distance = 0;
bool morphed = false;
bool finished = false;
while (!finished) {
(void) playerMovePosition(direction, coord);
distance++;
Tile_t const &tile = dg.floor[coord.y][coord.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(coord, 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) && (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(Coord_t coord, int direction) {
int distance = 0;
bool built = false;
bool finished = false;
while (!finished) {
(void) playerMovePosition(direction, coord);
distance++;
Tile_t &tile = dg.floor[coord.y][coord.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);
}
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);
built = true;
}
return built;
}
// Replicate a creature -RAK-
bool spellCloneMonster(Coord_t coord, int direction) {
int distance = 0;
bool finished = false;
while (!finished) {
(void) playerMovePosition(direction, coord);
distance++;
Tile_t const &tile = dg.floor[coord.y][coord.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(coord, (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 counter = 0;
Coord_t coord = Coord_t{0, 0};
Monster_t &monster = monsters[monster_id];
do {
do {
coord.y = monster.pos.y + (randomNumber(2 * distance_from_player + 1) - (distance_from_player + 1));
coord.x = monster.pos.x + (randomNumber(2 * distance_from_player + 1) - (distance_from_player + 1));
} while (!coordInBounds(coord));
counter++;
if (counter > 9) {
counter = 0;
distance_from_player += 5;
}
} while ((dg.floor[coord.y][coord.x].feature_id >= MIN_CLOSED_SPACE) || (dg.floor[coord.y][coord.x].creature_id != 0));
dungeonMoveCreatureRecord(Coord_t{monster.pos.y, monster.pos.x}, coord);
dungeonLiteSpot(Coord_t{monster.pos.y, monster.pos.x});
monster.pos.y = coord.y;
monster.pos.x = coord.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(py.pos, coord);
monsterUpdateVisibility(monster_id);
}
// Teleport player to spell casting creature -RAK-
void spellTeleportPlayerTo(Coord_t coord) {
int distance = 1;
int counter = 0;
Coord_t rnd_coord = Coord_t{0, 0};
do {
rnd_coord.y = coord.y + (randomNumber(2 * distance + 1) - (distance + 1));
rnd_coord.x = coord.x + (randomNumber(2 * distance + 1) - (distance + 1));
counter++;
if (counter > 9) {
counter = 0;
distance++;
}
} while (!coordInBounds(rnd_coord) || (dg.floor[rnd_coord.y][rnd_coord.x].feature_id >= MIN_CLOSED_SPACE) || (dg.floor[rnd_coord.y][rnd_coord.x].creature_id >= 2));
dungeonMoveCreatureRecord(py.pos, rnd_coord);
Coord_t spot = Coord_t{0, 0};
for (spot.y = py.pos.y - 1; spot.y <= py.pos.y + 1; spot.y++) {
for (spot.x = py.pos.x - 1; spot.x <= py.pos.x + 1; spot.x++) {
dg.floor[spot.y][spot.x].temporary_light = false;
dungeonLiteSpot(spot);
}
}
dungeonLiteSpot(py.pos);
py.pos.y = rnd_coord.y;
py.pos.x = rnd_coord.x;
dungeonResetView();
// light creatures
updateMonsters(false);
}
// Teleport all creatures in a given direction away -RAK-
bool spellTeleportAwayMonsterInDirection(Coord_t coord, int direction) {
int distance = 0;
bool teleported = false;
bool finished = false;
while (!finished) {
(void) playerMovePosition(direction, coord);
distance++;
Tile_t const &tile = dg.floor[coord.y][coord.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.pos, monster.pos)) {
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.pos, monster.pos)) {
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;
Coord_t coord = Coord_t{0, 0};
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) {
coord.y = monster.pos.y;
coord.x = monster.pos.x;
dungeonDeleteMonster(id);
// Place_monster() should always return true here.
morphed = monsterPlaceNew(coord, 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.pos.y, monster.pos.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.pos.y, monster.pos.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 monster_id) {
Monster_t &monster = monsters[monster_id];
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(monster_id, 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() {
Coord_t coord = Coord_t{0, 0};
for (coord.y = py.pos.y - 8; coord.y <= py.pos.y + 8; coord.y++) {
for (coord.x = py.pos.x - 8; coord.x <= py.pos.x + 8; coord.x++) {
if ((coord.y != py.pos.y || coord.x != py.pos.x) && coordInBounds(coord) && randomNumber(8) == 1) {
Tile_t &tile = dg.floor[coord.y][coord.x];
if (tile.treasure_id != 0) {
(void) dungeonDeleteObject(coord);
}
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);
}
}
}
}
// 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.pos.y][py.pos.x];
// 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(py.pos, false);
inventoryItemCopyTo(config::dungeon::objects::OBJ_MUSH, game.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.pos, monster.pos)) {
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.pos, monster.pos)) {
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.pos.y][py.pos.x].treasure_id == 0) {
int free_id = popt();
dg.floor[py.pos.y][py.pos.x].treasure_id = (uint8_t) free_id;
inventoryItemCopyTo(config::dungeon::objects::OBJ_SCARE_MON, game.treasure.list[free_id]);
}
}
// Lose a strength point. -RAK-
void spellLoseSTR() {
if (!py.flags.sustain_str) {
(void) playerStatRandomDecrease(PlayerAttr::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(PlayerAttr::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(PlayerAttr::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(PlayerAttr::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(PlayerAttr::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(PlayerAttr::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(PlayerAttr::A_INT);
playerGainMana(PlayerAttr::A_INT);
} else if (character_class.class_to_use_mage_spells == config::spells::SPELL_TYPE_PRIEST) {
playerCalculateAllowedSpellsCount(PlayerAttr::A_WIS);
playerGainMana(PlayerAttr::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(Coord_t coord, int typ) {
Tile_t &tile = dg.floor[coord.y][coord.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);
}
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(Coord_t coord) {
if (dg.current_level > 0) {
Coord_t spot = Coord_t{0, 0};
for (spot.y = coord.y - 15; spot.y <= coord.y + 15; spot.y++) {
for (spot.x = coord.x - 15; spot.x <= coord.x + 15; spot.x++) {
if (coordInBounds(spot) && dg.floor[spot.y][spot.x].feature_id != TILE_BOUNDARY_WALL) {
int distance = coordDistanceBetween(spot, coord);
// clear player's spot, but don't put wall there
if (distance == 0) {
replaceSpot(spot, 1);
} else if (distance < 13) {
replaceSpot(spot, randomNumber(6));
} else if (distance < 16) {
replaceSpot(spot, 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 = PlayerEquipment::Wield; id <= PlayerEquipment::Outer; id++) {
if ((py.inventory[id].flags & config::treasure::flags::TR_CURSED) != 0u) {
py.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.13/src/game_run.cpp 0000644 0001750 0001750 00000212202 13720166710 015147 0 ustar ariel ariel // 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) {
// Enable roguelike keys by default - this will be overridden by the
// setting in the game save file.
config::options::use_roguelike_keys = true;
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;
}
// 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(PlayerAttr::A_INT);
playerGainMana(PlayerAttr::A_INT);
} else if (classes[py.misc.class_id].class_to_use_mage_spells == config::spells::SPELL_TYPE_PRIEST) {
playerCalculateAllowedSpellsCount(PlayerAttr::A_WIS);
clearScreen(); // force out the 'learn prayer' message
playerGainMana(PlayerAttr::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();
putString("Press ? for help", Coord_t{0, 63});
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 : py.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;
game.teleport_player = false;
monster_multiply_total = 0;
dg.floor[py.pos.y][py.pos.x].creature_id = 1;
}
// Check light status for dungeon setup
static void playerInitializePlayerLight() {
py.carrying_light = (py.inventory[PlayerEquipment::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 = py.inventory[PlayerEquipment::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.pack.unique_items) {
i = 22;
}
Inventory_t &item = py.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) {
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 &last_input_command) {
putStringClearToEOL("Repeat count:", Coord_t{0, 0});
if (last_input_command == '#') {
last_input_command = '0';
}
char text_buffer[8];
int repeat_count = 0;
while (true) {
if (last_input_command == DELETE || last_input_command == CTRL_KEY('H')) {
repeat_count /= 10;
(void) sprintf(text_buffer, "%d", (int16_t) repeat_count);
putStringClearToEOL(text_buffer, Coord_t{0, 14});
} else if (last_input_command >= '0' && last_input_command <= '9') {
if (repeat_count > 99) {
terminalBellSound();
} else {
repeat_count = repeat_count * 10 + last_input_command - '0';
(void) sprintf(text_buffer, "%d", repeat_count);
putStringClearToEOL(text_buffer, Coord_t{0, 14});
}
} else {
break;
}
last_input_command = 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 (last_input_command == ' ') {
putStringClearToEOL("Command:", Coord_t{0, 20});
last_input_command = getKeyInput();
}
return repeat_count;
}
static char parseAlternateCtrlInput(char last_input_command) {
if (game.command_count > 0) {
printCharacterMovementState();
}
if (getCommand("Control-", last_input_command)) {
if (last_input_command >= 'A' && last_input_command <= 'Z') {
last_input_command -= 'A' - 1;
} else if (last_input_command >= 'a' && last_input_command <= 'z') {
last_input_command -= 'a' - 1;
} else {
last_input_command = ' ';
printMessage("Type ^ for a control char");
}
} else {
last_input_command = ' ';
}
return last_input_command;
}
// Accept a command and execute it
static void executeInputCommands(char &command, int &find_count) {
char last_input_command = 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(py.pos);
message_ready_to_print = false;
if (game.command_count > 0) {
game.use_last_direction = true;
} else {
last_input_command = getKeyInput();
// Get a count for a command.
int repeat_count = 0;
if ((config::options::use_roguelike_keys && last_input_command >= '0' && last_input_command <= '9') || (!config::options::use_roguelike_keys && last_input_command == '#')) {
repeat_count = getCommandRepeatCount(last_input_command);
}
// Another way of typing control codes -CJS-
if (last_input_command == '^') {
last_input_command = parseAlternateCtrlInput(last_input_command);
}
// move cursor to player char again, in case it moved
panelMoveCursor(py.pos);
// Commands are always converted to rogue form. -CJS-
if (!config::options::use_roguelike_keys) {
last_input_command = originalCommands(last_input_command);
}
if (repeat_count > 0) {
if (!validCountCommand(last_input_command)) {
game.player_free_turn = true;
last_input_command = ' ';
printMessage("Invalid command with a count.");
} else {
game.command_count = repeat_count;
printCharacterMovementState();
}
}
}
// Flash the message line.
messageLineClear();
panelMoveCursor(py.pos);
putQIO();
doCommand(last_input_command);
// 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 = last_input_command;
}
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':
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 cmd = *command;
// hack for move without pickup. Map '-' to a movement command.
if (cmd != '-') {
return true;
}
int direction;
// Save current game.command_count as getDirectionWithMemory() may change it
int count_save = game.command_count;
if (getDirectionWithMemory(CNIL, direction)) {
// Restore game.command_count
game.command_count = count_save;
switch (direction) {
case 1:
cmd = 'b';
break;
case 2:
cmd = 'j';
break;
case 3:
cmd = 'n';
break;
case 4:
cmd = 'h';
break;
case 6:
cmd = 'l';
break;
case 7:
cmd = 'y';
break;
case 8:
cmd = 'k';
break;
case 9:
cmd = 'u';
break;
default:
cmd = '~';
break;
}
} else {
cmd = ' ';
}
*command = cmd;
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 line_number = 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{line_number, 0});
waitForContinueKey(line_number);
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;
}
Coord_t player_coord = py.pos;
if (coordOutsidePanel(player_coord, true)) {
drawDungeonPanel();
}
int dir_val;
vtype_t out_val = {'\0'};
vtype_t tmp_str = {'\0'};
Coord_t old_panel = Coord_t{dg.panel.row, dg.panel.col};
Coord_t panel = Coord_t{0, 0};
while (true) {
panel.y = dg.panel.row;
panel.x = dg.panel.col;
if (panel.y == old_panel.y && panel.x == old_panel.x) {
tmp_str[0] = '\0';
} else {
(void) sprintf(tmp_str, //
"%s%s of", //
panel.y < old_panel.y ? " North" : panel.y > old_panel.y ? " South" : "", //
panel.x < old_panel.x ? " West" : panel.x > old_panel.x ? " East" : "" //
);
}
(void) sprintf(out_val, "Map sector [%d,%d], which is%s your sector. Look which direction?", panel.y, panel.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) {
player_coord.x += ((dir_val - 1) % 3 - 1) * SCREEN_WIDTH / 2;
player_coord.y -= ((dir_val - 1) / 3 - 1) * SCREEN_HEIGHT / 2;
if (player_coord.x < 0 || player_coord.y < 0 || player_coord.x >= dg.width || player_coord.y >= dg.width) {
printMessage("You've gone past the end of your map.");
player_coord.x -= ((dir_val - 1) % 3 - 1) * SCREEN_WIDTH / 2;
player_coord.y += ((dir_val - 1) / 3 - 1) * SCREEN_HEIGHT / 2;
break;
}
if (coordOutsidePanel(player_coord, true)) {
drawDungeonPanel();
break;
}
}
}
// Move to a new panel - but only if really necessary.
if (coordOutsidePanel(py.pos, false)) {
drawDungeonPanel();
}
}
static void commandToggleSearch() {
if ((py.flags.status & config::player::status::PY_SEARCH) != 0u) {
playerSearchOff();
} else {
playerSearchOn();
}
}
static void doWizardCommands(char command) {
switch (command) {
case CTRL_KEY('A'):
// Cure all!
wizardCureAll();
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
wizardDropRandomItems();
break;
case CTRL_KEY('D'):
// Go up/down to specified depth
wizardJumpLevel();
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
wizardGainExperience();
break;
case '&':
// Summon a random monster
wizardSummonMonster();
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-
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.pos, 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 = SHRT_MAX;
}
// 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 = SHRT_MAX;
}
// 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;
}
return (0x07ffe980L & item.flags) != 0;
}
// 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 = py.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 = py.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.pos.y][py.pos.x].treasure_id;
if (tile_id != 0 && game.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.pos.y][py.pos.x].treasure_id;
if (tile_id != 0 && game.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;
Coord_t coord = py.pos;
int direction;
if (!getDirectionWithMemory(CNIL, direction)) {
return;
}
(void) playerMovePosition(direction, coord);
Tile_t const &tile = dg.floor[coord.y][coord.x];
if (tile.treasure_id == 0) {
printMessage("That isn't a door!");
return;
}
Inventory_t &item = game.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 (py.inventory[item_pos_start].items_count > 1) {
py.inventory[item_pos_start].items_count--;
py.pack.weight -= py.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 (py.inventory[PlayerEquipment::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 = py.inventory[PlayerEquipment::Light];
item.misc_use += py.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 last_input_command = {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(last_input_command, find_count);
} else {
// if paralyzed, resting, or dead, flush output
// but first move the cursor onto the player, for aesthetics
panelMoveCursor(py.pos);
putQIO();
}
// Teleport?
if (game.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.13/src/identification.h 0000644 0001750 0001750 00000006457 13720166710 016025 0 ustar ariel ariel // 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 SpecialNameIds {
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[SpecialNameIds::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.13/src/player.cpp 0000644 0001750 0001750 00000144015 13720166710 014654 0 ustar ariel ariel // 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{};
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, Coord_t &coord) {
Coord_t new_coord = Coord_t{0, 0};
switch (dir) {
case 1:
new_coord.y = coord.y + 1;
new_coord.x = coord.x - 1;
break;
case 2:
new_coord.y = coord.y + 1;
new_coord.x = coord.x;
break;
case 3:
new_coord.y = coord.y + 1;
new_coord.x = coord.x + 1;
break;
case 4:
new_coord.y = coord.y;
new_coord.x = coord.x - 1;
break;
case 5:
new_coord.y = coord.y;
new_coord.x = coord.x;
break;
case 6:
new_coord.y = coord.y;
new_coord.x = coord.x + 1;
break;
case 7:
new_coord.y = coord.y - 1;
new_coord.x = coord.x - 1;
break;
case 8:
new_coord.y = coord.y - 1;
new_coord.x = coord.x;
break;
case 9:
new_coord.y = coord.y - 1;
new_coord.x = coord.x + 1;
break;
default:
new_coord.y = 0;
new_coord.x = 0;
break;
}
bool can_move = false;
if (new_coord.y >= 0 && new_coord.y < dg.height && new_coord.x >= 0 && new_coord.x < dg.width) {
coord = new_coord;
can_move = true;
}
return can_move;
}
// Teleport the player to a new location -RAK-
void playerTeleport(int new_distance) {
Coord_t location = Coord_t{0, 0};
do {
location.y = randomNumber(dg.height) - 1;
location.x = randomNumber(dg.width) - 1;
while (coordDistanceBetween(location, py.pos) > new_distance) {
location.y += (py.pos.y - location.y) / 2;
location.x += (py.pos.x - location.x) / 2;
}
} while (dg.floor[location.y][location.x].feature_id >= MIN_CLOSED_SPACE || dg.floor[location.y][location.x].creature_id >= 2);
dungeonMoveCreatureRecord(py.pos, location);
Coord_t spot = Coord_t{0, 0};
for (spot.y = py.pos.y - 1; spot.y <= py.pos.y + 1; spot.y++) {
for (spot.x = py.pos.x - 1; spot.x <= py.pos.x + 1; spot.x++) {
dg.floor[spot.y][spot.x].temporary_light = false;
dungeonLiteSpot(spot);
}
}
dungeonLiteSpot(py.pos);
py.pos.y = location.y;
py.pos.x = location.x;
dungeonResetView();
updateMonsters(false);
game.teleport_player = false;
}
// Returns true if player has no light -RAK-
bool playerNoLight() {
return !dg.floor[py.pos.y][py.pos.x].temporary_light && !dg.floor[py.pos.y][py.pos.x].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 = -SHRT_MAX;
} 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 == -SHRT_MAX || (rest_num > 0 && rest_num <= SHRT_MAX)) {
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
case 4: // Fear attack
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
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
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.pack.unique_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
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
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
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.pack.unique_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 = PlayerEquipment::Wield; i < PlayerEquipment::Light; i++) {
Inventory_t const &item = py.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 = PlayerEquipment::Wield; i < PlayerEquipment::Light; i++) {
if ((py.inventory[i].flags & config::treasure::flags::TR_SUST_STAT) == 0u) {
continue;
}
switch (py.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 saved_display_ac = py.misc.display_ac;
playerResetFlags();
// Real values
py.misc.plusses_to_hit = playerToHitAdjustment();
py.misc.plusses_to_damage = playerDamageAdjustment();
py.misc.magical_ac = 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[PlayerAttr::A_STR] * 15 - py.inventory[PlayerEquipment::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 (saved_display_ac != 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 = py.inventory[item_id];
py.pack.weight -= item.weight * item.items_count;
py.equipment_count--;
const char *p = nullptr;
if (item_id == PlayerEquipment::Wield || item_id == PlayerEquipment::Auxiliary) {
p = "Was wielding ";
} else if (item_id == PlayerEquipment::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 != PlayerEquipment::Auxiliary) {
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(Coord_t coord, 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;
}
Coord_t spot = Coord_t{0, 0};
for (spot.y = coord.y - 1; spot.y <= coord.y + 1; spot.y++) {
for (spot.x = coord.x - 1; spot.x <= coord.x + 1; spot.x++) {
// always coordInBounds() here
if (randomNumber(100) >= chance) {
continue;
}
if (dg.floor[spot.y][spot.x].treasure_id == 0) {
continue;
}
// Search for hidden objects
Inventory_t &item = game.treasure.list[dg.floor[spot.y][spot.x].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(spot);
playerEndRunning();
} else if (item.category_id == TV_SECRET_DOOR) {
// Secret door?
printMessage("You have found a secret door.");
trapChangeVisibility(spot);
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[PlayerAttr::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 = py.inventory[PlayerEquipment::Wield];
if (item.category_id != TV_NOTHING && py.stats.used[PlayerAttr::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.pack.weight) {
limit = py.pack.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.pack.unique_items; i++) {
if (py.inventory[i].category_id == TV_MAGIC_BOOK) {
spell_flag |= py.inventory[i].flags;
}
}
return spell_flag;
}
// gain spells when player wants to -JW-
void playerGainSpells() {
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;
// Priests don't need light because they get spells from their god, so only
// fail when can't see if player has SPELL_TYPE_MAGE spells. This check is done below.
if (classes[py.misc.class_id].class_to_use_mage_spells == config::spells::SPELL_TYPE_MAGE) {
// People with SPELL_TYPE_MAGE spells can't learn spell_bank if they can't read their books.
if (!playerCanRead()) {
return;
}
stat = PlayerAttr::A_INT;
offset = config::spells::NAME_OFFSET_SPELLS;
} else {
stat = PlayerAttr::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 == PlayerAttr::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 == PlayerAttr::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 == PlayerAttr::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];
last_known++;
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];
last_known++;
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][PlayerClassLevelAdj::SAVE] * py.misc.level / 3;
int saving = py.misc.saving_throw + playerStatAdjustmentWisdomIntelligence(PlayerAttr::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 creature_lit, int tot_tohit) {
if (creature_lit) {
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][PlayerClassLevelAdj::BTH] / 2;
return bth;
}
// Player attacks a (poor, defenseless) creature -RAK-
static void playerAttackMonster(Coord_t coord) {
int creature_id = dg.floor[coord.y][coord.x].creature_id;
Monster_t &monster = monsters[creature_id];
Creature_t const &creature = creatures_list[monster.creature_id];
Inventory_t &item = py.inventory[PlayerEquipment::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, PlayerClassLevelAdj::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, PlayerClassLevelAdj::BTH);
} else {
// Bare hands!?
damage = diceRoll(Dice_t{1, 1});
damage = playerWeaponCriticalBlow(1, 0, damage, PlayerClassLevelAdj::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.pack.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(PlayerAttr::A_INT);
skill += class_level_adj[py.misc.class_id][PlayerClassLevelAdj::DISARM] * py.misc.level / 3;
return skill;
}
static void openClosedDoor(Coord_t coord) {
Tile_t &tile = dg.floor[coord.y][coord.x];
Inventory_t &item = game.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, game.treasure.list[tile.treasure_id]);
tile.feature_id = TILE_CORR_FLOOR;
dungeonLiteSpot(coord);
game.command_count = 0;
}
}
static void openClosedChest(Coord_t coord) {
Tile_t const &tile = dg.floor[coord.y][coord.x];
Inventory_t &item = game.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 = SpecialNameIds::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(coord);
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
game.treasure.list[tile.treasure_id].flags &= ~config::treasure::flags::TR_CURSED;
(void) monsterDeath(coord, game.treasure.list[tile.treasure_id].flags);
game.treasure.list[tile.treasure_id].flags = 0;
}
}
// Opens a closed door or closed chest. -RAK-
void playerOpenClosedObject() {
int dir;
if (!getDirectionWithMemory(CNIL, dir)) {
return;
}
Coord_t coord = py.pos;
(void) playerMovePosition(dir, coord);
bool no_object = false;
Tile_t const &tile = dg.floor[coord.y][coord.x];
Inventory_t const &item = game.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(coord);
} else if (item.category_id == TV_CHEST) {
openClosedChest(coord);
} 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;
}
Coord_t coord = py.pos;
(void) playerMovePosition(dir, coord);
Tile_t &tile = dg.floor[coord.y][coord.x];
Inventory_t &item = game.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);
} 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(Coord_t coord, int digging_ability, int digging_chance) {
if (digging_ability <= digging_chance) {
return false;
}
Tile_t &tile = dg.floor[coord.y][coord.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 y = coord.y - 1; y <= coord.y + 1 && y < MAX_HEIGHT; y++) {
for (int x = coord.x - 1; x <= coord.x + 1 && x < MAX_WIDTH; x++) {
if (dg.floor[y][x].feature_id <= MAX_CAVE_ROOM) {
tile.feature_id = dg.floor[y][x].feature_id;
tile.permanent_light = dg.floor[y][x].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) && (tile.temporary_light || tile.permanent_light) && tile.treasure_id != 0) {
printMessage("You have found something!");
}
dungeonLiteSpot(coord);
return true;
}
// let the player attack the creature
void playerAttackPosition(Coord_t coord) {
// Is a Coward?
if (py.flags.afraid > 0) {
printMessage("You are too afraid!");
return;
}
playerAttackMonster(coord);
}
// 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 allowed_spells, int new_spells, const char *p, int offset) {
uint32_t mask;
for (int n = 0; ((py.flags.spells_forgotten != 0u) && (new_spells != 0) && (n < allowed_spells) && (n < 32)); n++) {
// order ID is (i+1)th spell learned
int order_id = 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 (order_id == 99) {
mask = 0x0;
} else {
mask = (uint32_t)(1L << order_id);
}
if ((mask & py.flags.spells_forgotten) != 0u) {
if (msp_ptr[order_id].level_required <= py.misc.level) {
new_spells--;
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[order_id + offset]);
printMessage(msg);
} else {
allowed_spells++;
}
}
}
return new_spells;
}
// 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 new_spells) {
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 (new_spells > id) {
new_spells = id;
}
return new_spells;
}
// 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 new_spells, const char *p, int offset) {
uint32_t mask;
for (int i = 31; (new_spells != 0) && (py.flags.spells_learnt != 0u); i--) {
// orderID is the (i+1)th spell learned
int order_id = 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 (order_id == 99) {
mask = 0x0;
} else {
mask = (uint32_t)(1L << order_id);
}
if ((mask & py.flags.spells_learnt) != 0u) {
py.flags.spells_learnt &= ~mask;
py.flags.spells_forgotten |= mask;
new_spells++;
vtype_t msg = {'\0'};
(void) sprintf(msg, "You have forgotten the %s of %s.", p, spell_names[order_id + 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 == PlayerAttr::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.13/src/ui_io.cpp 0000644 0001750 0001750 00000040676 13720166710 014474 0 ustar ariel ariel // 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() {
// cbreak(); // use raw() instead as it disables Ctrl chars
raw(); // disable control characters. I.e. Ctrl-C does not work!
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);
}
ssize_t terminalBellSound() {
putQIO();
// The player can turn off beeps if they find them annoying.
if (config::options::error_beep_sound) {
return write(1, "\007", 1);
}
return 0;
}
// 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 coord) {
(void) move(coord.y, coord.x);
}
void addChar(char ch, Coord_t coord) {
if (mvaddch(coord.y, coord.x, ch) == ERR) {
abort();
}
}
// Dump IO to buffer -RAK-
void putString(const char *out_str, Coord_t coord) {
// truncate the string, to make sure that it won't go past right edge of screen.
if (coord.x > 79) {
coord.x = 79;
}
vtype_t str = {'\0'};
(void) strncpy(str, out_str, (size_t)(79 - coord.x));
str[79 - coord.x] = '\0';
if (mvaddstr(coord.y, coord.x, str) == ERR) {
abort();
}
}
// Outputs a line to a given y, x position -RAK-
void putStringClearToEOL(const std::string &str, Coord_t coord) {
if (coord.y == MSG_LINE && message_ready_to_print) {
printMessage(CNIL);
}
(void) move(coord.y, coord.x);
clrtoeol();
putString(str.c_str(), coord);
}
// Clears given line of text -RAK-
void eraseLine(Coord_t coord) {
if (coord.y == MSG_LINE && message_ready_to_print) {
printMessage(CNIL);
}
(void) move(coord.y, coord.x);
clrtoeol();
}
// Moves the cursor to a given interpolated y, x position -RAK-
void panelMoveCursor(Coord_t coord) {
// Real coords convert to screen positions
coord.y -= dg.panel.row_prt;
coord.x -= dg.panel.col_prt;
if (move(coord.y, coord.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 coord) {
// Real coords convert to screen positions
coord.y -= dg.panel.row_prt;
coord.x -= dg.panel.col_prt;
if (mvaddch(coord.y, coord.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 coord = 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(coord.y, coord.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 coord = currentCursorPosition();
// move to beginning of message line, and clear it
move(0, 0);
clrtoeol();
// restore cursor to old position
move(coord.y, coord.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 coord, int slen) {
(void) move(coord.y, coord.x);
for (int i = slen; i > 0; i--) {
(void) addch(' ');
}
(void) move(coord.y, coord.x);
int start_col = coord.x;
int end_col = coord.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 (coord.x > start_col) {
coord.x--;
putString(" ", coord);
moveCursor(coord);
*--p = '\0';
}
break;
default:
if ((isprint(key) == 0) || coord.x > end_col) {
terminalBellSound();
} else {
mvaddch(coord.y, coord.x, (char) key);
*p++ = (char) key;
coord.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 *) nullptr, (fd_set *) nullptr, &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 *default_name = "X";
#ifdef _WIN32
unsigned long bufCharCount = PLAYER_NAME_SIZE;
if (!GetUserName(buffer, &bufCharCount)) {
(void) strcpy(buffer, default_name);
}
#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, default_name);
}
#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) - 1) {
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.13/src/dungeon_generate.cpp 0000644 0001750 0001750 00000125571 13720166710 016677 0 ustar ariel ariel // 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 &vertical, int &horizontal, Coord_t start, Coord_t end) {
if (start.y < end.y) {
vertical = 1;
} else if (start.y == end.y) {
vertical = 0;
} else {
vertical = -1;
}
if (start.x < end.x) {
horizontal = 1;
} else if (start.x == end.x) {
horizontal = 0;
} else {
horizontal = -1;
}
if (vertical != 0 && horizontal != 0) {
if (randomNumber(2) == 1) {
vertical = 0;
} else {
horizontal = 0;
}
}
}
// Chance of wandering direction
static void chanceOfRandomDirection(int &vertical, int &horizontal) {
int direction = randomNumber(4);
if (direction < 3) {
horizontal = 0;
vertical = -3 + (direction << 1); // direction=1 -> y=-1; direction=2 -> y=1
} else {
vertical = 0;
horizontal = -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
Coord_t coord = Coord_t{
(dg.height / 2) + 11 - randomNumber(23),
(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++) {
Coord_t spot = Coord_t{
coord.y + randomNumber(t1) - t2,
coord.x + randomNumber(t1) - t2,
};
if (coordInBounds(spot)) {
if (dg.floor[spot.y][spot.x].feature_id == TILE_GRANITE_WALL) {
dg.floor[spot.y][spot.x].feature_id = rock_type;
if (randomNumber(chance_of_treasure) == 1) {
dungeonPlaceGold(spot);
}
}
}
}
} while (playerMovePosition(dir, coord));
}
static void dungeonPlaceOpenDoor(Coord_t coord) {
int cur_pos = popt();
dg.floor[coord.y][coord.x].treasure_id = (uint8_t) cur_pos;
inventoryItemCopyTo(config::dungeon::objects::OBJ_OPEN_DOOR, game.treasure.list[cur_pos]);
dg.floor[coord.y][coord.x].feature_id = TILE_CORR_FLOOR;
}
static void dungeonPlaceBrokenDoor(Coord_t coord) {
int cur_pos = popt();
dg.floor[coord.y][coord.x].treasure_id = (uint8_t) cur_pos;
inventoryItemCopyTo(config::dungeon::objects::OBJ_OPEN_DOOR, game.treasure.list[cur_pos]);
dg.floor[coord.y][coord.x].feature_id = TILE_CORR_FLOOR;
game.treasure.list[cur_pos].misc_use = 1;
}
static void dungeonPlaceClosedDoor(Coord_t coord) {
int cur_pos = popt();
dg.floor[coord.y][coord.x].treasure_id = (uint8_t) cur_pos;
inventoryItemCopyTo(config::dungeon::objects::OBJ_CLOSED_DOOR, game.treasure.list[cur_pos]);
dg.floor[coord.y][coord.x].feature_id = TILE_BLOCKED_FLOOR;
}
static void dungeonPlaceLockedDoor(Coord_t coord) {
int cur_pos = popt();
dg.floor[coord.y][coord.x].treasure_id = (uint8_t) cur_pos;
inventoryItemCopyTo(config::dungeon::objects::OBJ_CLOSED_DOOR, game.treasure.list[cur_pos]);
dg.floor[coord.y][coord.x].feature_id = TILE_BLOCKED_FLOOR;
game.treasure.list[cur_pos].misc_use = (int16_t)(randomNumber(10) + 10);
}
static void dungeonPlaceStuckDoor(Coord_t coord) {
int cur_pos = popt();
dg.floor[coord.y][coord.x].treasure_id = (uint8_t) cur_pos;
inventoryItemCopyTo(config::dungeon::objects::OBJ_CLOSED_DOOR, game.treasure.list[cur_pos]);
dg.floor[coord.y][coord.x].feature_id = TILE_BLOCKED_FLOOR;
game.treasure.list[cur_pos].misc_use = (int16_t)(-randomNumber(10) - 10);
}
static void dungeonPlaceSecretDoor(Coord_t coord) {
int cur_pos = popt();
dg.floor[coord.y][coord.x].treasure_id = (uint8_t) cur_pos;
inventoryItemCopyTo(config::dungeon::objects::OBJ_SECRET_DOOR, game.treasure.list[cur_pos]);
dg.floor[coord.y][coord.x].feature_id = TILE_BLOCKED_FLOOR;
}
static void dungeonPlaceDoor(Coord_t coord) {
int door_type = randomNumber(3);
if (door_type == 1) {
if (randomNumber(4) == 1) {
dungeonPlaceBrokenDoor(coord);
} else {
dungeonPlaceOpenDoor(coord);
}
} else if (door_type == 2) {
door_type = randomNumber(12);
if (door_type > 3) {
dungeonPlaceClosedDoor(coord);
} else if (door_type == 3) {
dungeonPlaceStuckDoor(coord);
} else {
dungeonPlaceLockedDoor(coord);
}
} else {
dungeonPlaceSecretDoor(coord);
}
}
// Place an up staircase at given y, x -RAK-
static void dungeonPlaceUpStairs(Coord_t coord) {
if (dg.floor[coord.y][coord.x].treasure_id != 0) {
(void) dungeonDeleteObject(coord);
}
int cur_pos = popt();
dg.floor[coord.y][coord.x].treasure_id = (uint8_t) cur_pos;
inventoryItemCopyTo(config::dungeon::objects::OBJ_UP_STAIR, game.treasure.list[cur_pos]);
}
// Place a down staircase at given y, x -RAK-
static void dungeonPlaceDownStairs(Coord_t coord) {
if (dg.floor[coord.y][coord.x].treasure_id != 0) {
(void) dungeonDeleteObject(coord);
}
int cur_pos = popt();
dg.floor[coord.y][coord.x].treasure_id = (uint8_t) cur_pos;
inventoryItemCopyTo(config::dungeon::objects::OBJ_DOWN_STAIR, game.treasure.list[cur_pos]);
}
// Places a staircase 1=up, 2=down -RAK-
static void dungeonPlaceStairs(int stair_type, int number, int walls) {
Coord_t coord1 = Coord_t{0, 0};
Coord_t coord2 = Coord_t{0, 0};
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.
coord1.y = randomNumber(dg.height - 14);
coord1.x = randomNumber(dg.width - 14);
coord2.y = coord1.y + 12;
coord2.x = coord1.x + 12;
do {
do {
if (dg.floor[coord1.y][coord1.x].feature_id <= MAX_OPEN_SPACE && dg.floor[coord1.y][coord1.x].treasure_id == 0 && coordWallsNextTo(coord1) >= walls) {
placed = true;
if (stair_type == 1) {
dungeonPlaceUpStairs(coord1);
} else {
dungeonPlaceDownStairs(coord1);
}
}
coord1.x++;
} while ((coord1.x != coord2.x) && (!placed));
coord1.x = coord2.x - 12;
coord1.y++;
} while ((coord1.y != coord2.y) && (!placed));
j++;
} while ((!placed) && (j <= 30));
walls--;
}
}
}
// Place a trap with a given displacement of point -RAK-
static void dungeonPlaceVaultTrap(Coord_t coord, Coord_t displacement, int number) {
Coord_t spot = Coord_t{0, 0};
for (int i = 0; i < number; i++) {
bool placed = false;
for (int count = 0; !placed && count <= 5; count++) {
spot.y = coord.y - displacement.y - 1 + randomNumber(2 * displacement.y + 1);
spot.x = coord.x - displacement.x - 1 + randomNumber(2 * displacement.x + 1);
if (dg.floor[spot.y][spot.x].feature_id != TILE_NULL_WALL && dg.floor[spot.y][spot.x].feature_id <= MAX_CAVE_FLOOR && dg.floor[spot.y][spot.x].treasure_id == 0) {
dungeonSetTrap(spot, randomNumber(config::dungeon::objects::MAX_TRAPS) - 1);
placed = true;
}
}
}
}
// Place a trap with a given displacement of point -RAK-
static void dungeonPlaceVaultMonster(Coord_t coord, int number) {
Coord_t spot = Coord_t{0, 0};
for (int i = 0; i < number; i++) {
spot.y = coord.y;
spot.x = coord.x;
(void) monsterSummon(spot, true);
}
}
// Builds a room at a row, column coordinate -RAK-
static void dungeonBuildRoom(Coord_t coord) {
uint8_t floor = dungeonFloorTileForLevel();
int height = coord.y - randomNumber(4);
int depth = coord.y + randomNumber(3);
int left = coord.x - randomNumber(11);
int right = coord.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.
int y, x;
for (y = height; y <= depth; y++) {
for (x = left; x <= right; x++) {
dg.floor[y][x].feature_id = floor;
dg.floor[y][x].perma_lit_room = true;
}
}
for (y = height - 1; y <= depth + 1; y++) {
dg.floor[y][left - 1].feature_id = TILE_GRANITE_WALL;
dg.floor[y][left - 1].perma_lit_room = true;
dg.floor[y][right + 1].feature_id = TILE_GRANITE_WALL;
dg.floor[y][right + 1].perma_lit_room = true;
}
for (x = left; x <= right; x++) {
dg.floor[height - 1][x].feature_id = TILE_GRANITE_WALL;
dg.floor[height - 1][x].perma_lit_room = true;
dg.floor[depth + 1][x].feature_id = TILE_GRANITE_WALL;
dg.floor[depth + 1][x].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(Coord_t coord) {
uint8_t floor = dungeonFloorTileForLevel();
int limit = 1 + randomNumber(2);
for (int count = 0; count < limit; count++) {
int height = coord.y - randomNumber(4);
int depth = coord.y + randomNumber(3);
int left = coord.x - randomNumber(11);
int right = coord.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.
int y, x;
for (y = height; y <= depth; y++) {
for (x = left; x <= right; x++) {
dg.floor[y][x].feature_id = floor;
dg.floor[y][x].perma_lit_room = true;
}
}
for (y = (height - 1); y <= (depth + 1); y++) {
if (dg.floor[y][left - 1].feature_id != floor) {
dg.floor[y][left - 1].feature_id = TILE_GRANITE_WALL;
dg.floor[y][left - 1].perma_lit_room = true;
}
if (dg.floor[y][right + 1].feature_id != floor) {
dg.floor[y][right + 1].feature_id = TILE_GRANITE_WALL;
dg.floor[y][right + 1].perma_lit_room = true;
}
}
for (x = left; x <= right; x++) {
if (dg.floor[height - 1][x].feature_id != floor) {
dg.floor[height - 1][x].feature_id = TILE_GRANITE_WALL;
dg.floor[height - 1][x].perma_lit_room = true;
}
if (dg.floor[depth + 1][x].feature_id != floor) {
dg.floor[depth + 1][x].feature_id = TILE_GRANITE_WALL;
dg.floor[depth + 1][x].perma_lit_room = true;
}
}
}
}
static void dungeonPlaceRandomSecretDoor(Coord_t coord, int depth, int height, int left, int right) {
switch (randomNumber(4)) {
case 1:
dungeonPlaceSecretDoor(Coord_t{height - 1, coord.x});
break;
case 2:
dungeonPlaceSecretDoor(Coord_t{depth + 1, coord.x});
break;
case 3:
dungeonPlaceSecretDoor(Coord_t{coord.y, left - 1});
break;
default:
dungeonPlaceSecretDoor(Coord_t{coord.y, right + 1});
break;
}
}
static void dungeonPlaceVault(Coord_t coord) {
for (int y = coord.y - 1; y <= coord.y + 1; y++) {
dg.floor[y][coord.x - 1].feature_id = TMP1_WALL;
dg.floor[y][coord.x + 1].feature_id = TMP1_WALL;
}
dg.floor[coord.y - 1][coord.x].feature_id = TMP1_WALL;
dg.floor[coord.y + 1][coord.x].feature_id = TMP1_WALL;
}
static void dungeonPlaceTreasureVault(Coord_t coord, int depth, int height, int left, int right) {
dungeonPlaceRandomSecretDoor(coord, depth, height, left, right);
dungeonPlaceVault(coord);
// Place a locked door
int offset = randomNumber(4);
if (offset < 3) {
// 1 -> y-1; 2 -> y+1
dungeonPlaceLockedDoor(Coord_t{coord.y - 3 + (offset << 1), coord.x});
} else {
dungeonPlaceLockedDoor(Coord_t{coord.y, coord.x - 7 + (offset << 1)});
}
}
static void dungeonPlaceInnerPillars(Coord_t coord) {
int y, x;
for (y = coord.y - 1; y <= coord.y + 1; y++) {
for (x = coord.x - 1; x <= coord.x + 1; x++) {
dg.floor[y][x].feature_id = TMP1_WALL;
}
}
if (randomNumber(2) != 1) {
return;
}
int offset = randomNumber(2);
for (y = coord.y - 1; y <= coord.y + 1; y++) {
for (x = coord.x - 5 - offset; x <= coord.x - 3 - offset; x++) {
dg.floor[y][x].feature_id = TMP1_WALL;
}
}
for (y = coord.y - 1; y <= coord.y + 1; y++) {
for (x = coord.x + 3 + offset; x <= coord.x + 5 + offset; x++) {
dg.floor[y][x].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(Coord_t coord, int depth, int height, int left, int right) {
for (int y = height; y <= depth; y++) {
dg.floor[y][coord.x].feature_id = TMP1_WALL;
}
for (int x = left; x <= right; x++) {
dg.floor[coord.y][x].feature_id = TMP1_WALL;
}
// place random secret door
if (randomNumber(2) == 1) {
int offset = randomNumber(10);
dungeonPlaceSecretDoor(Coord_t{height - 1, coord.x - offset});
dungeonPlaceSecretDoor(Coord_t{height - 1, coord.x + offset});
dungeonPlaceSecretDoor(Coord_t{depth + 1, coord.x - offset});
dungeonPlaceSecretDoor(Coord_t{depth + 1, coord.x + offset});
} else {
int offset = randomNumber(3);
dungeonPlaceSecretDoor(Coord_t{coord.y + offset, left - 1});
dungeonPlaceSecretDoor(Coord_t{coord.y - offset, left - 1});
dungeonPlaceSecretDoor(Coord_t{coord.y + offset, right + 1});
dungeonPlaceSecretDoor(Coord_t{coord.y - offset, 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,
TreasureVault,
Pillars,
Maze,
FourSmallRooms,
};
// Builds a type 2 unusual room at a row, column coordinate -RAK-
static void dungeonBuildRoomWithInnerRooms(Coord_t coord) {
uint8_t floor = dungeonFloorTileForLevel();
int height = coord.y - 4;
int depth = coord.y + 4;
int left = coord.x - 11;
int right = coord.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(coord, depth, height, left, right);
dungeonPlaceVaultMonster(coord, 1);
break;
case InnerRoomTypes::TreasureVault:
dungeonPlaceTreasureVault(coord, depth, height, left, right);
// Guard the treasure well
dungeonPlaceVaultMonster(coord, 2 + randomNumber(3));
// If the monsters don't get 'em.
dungeonPlaceVaultTrap(coord, Coord_t{4, 10}, 2 + randomNumber(3));
break;
case InnerRoomTypes::Pillars:
dungeonPlaceRandomSecretDoor(coord, depth, height, left, right);
dungeonPlaceInnerPillars(coord);
if (randomNumber(3) != 1) {
break;
}
// Inner rooms
for (int i = coord.x - 5; i <= coord.x + 5; i++) {
dg.floor[coord.y - 1][i].feature_id = TMP1_WALL;
dg.floor[coord.y + 1][i].feature_id = TMP1_WALL;
}
dg.floor[coord.y][coord.x - 5].feature_id = TMP1_WALL;
dg.floor[coord.y][coord.x + 5].feature_id = TMP1_WALL;
dungeonPlaceSecretDoor(Coord_t{coord.y - 3 + (randomNumber(2) << 1), coord.x - 3});
dungeonPlaceSecretDoor(Coord_t{coord.y - 3 + (randomNumber(2) << 1), coord.x + 3});
if (randomNumber(3) == 1) {
dungeonPlaceRandomObjectAt(Coord_t{coord.y, coord.x - 2}, false);
}
if (randomNumber(3) == 1) {
dungeonPlaceRandomObjectAt(Coord_t{coord.y, coord.x + 2}, false);
}
dungeonPlaceVaultMonster(Coord_t{coord.y, coord.x - 2}, randomNumber(2));
dungeonPlaceVaultMonster(Coord_t{coord.y, coord.x + 2}, randomNumber(2));
break;
case InnerRoomTypes::Maze:
dungeonPlaceRandomSecretDoor(coord, depth, height, left, right);
dungeonPlaceMazeInsideRoom(depth, height, left, right);
// Monsters just love mazes.
dungeonPlaceVaultMonster(Coord_t{coord.y, coord.x - 5}, randomNumber(3));
dungeonPlaceVaultMonster(Coord_t{coord.y, coord.x + 5}, randomNumber(3));
// Traps make them entertaining.
dungeonPlaceVaultTrap(Coord_t{coord.y, coord.x - 3}, Coord_t{2, 8}, randomNumber(3));
dungeonPlaceVaultTrap(Coord_t{coord.y, coord.x + 3}, Coord_t{2, 8}, randomNumber(3));
// Mazes should have some treasure too..
for (int i = 0; i < 3; i++) {
dungeonPlaceRandomObjectNear(coord, 1);
}
break;
case InnerRoomTypes::FourSmallRooms:
dungeonPlaceFourSmallRooms(coord, depth, height, left, right);
// Treasure in each one.
dungeonPlaceRandomObjectNear(coord, 2 + randomNumber(2));
// Gotta have some monsters.
dungeonPlaceVaultMonster(Coord_t{coord.y + 2, coord.x - 4}, randomNumber(2));
dungeonPlaceVaultMonster(Coord_t{coord.y + 2, coord.x + 4}, randomNumber(2));
dungeonPlaceVaultMonster(Coord_t{coord.y - 2, coord.x - 4}, randomNumber(2));
dungeonPlaceVaultMonster(Coord_t{coord.y - 2, coord.x + 4}, randomNumber(2));
break;
default:
// All cases are handled, so this should never be reached!
break;
}
}
static void dungeonPlaceLargeMiddlePillar(Coord_t coord) {
for (int y = coord.y - 1; y <= coord.y + 1; y++) {
for (int x = coord.x - 1; x <= coord.x + 1; x++) {
dg.floor[y][x].feature_id = TMP1_WALL;
}
}
}
// Builds a room at a row, column coordinate -RAK-
// Type 3 unusual rooms are cross shaped
static void dungeonBuildRoomCrossShaped(Coord_t coord) {
uint8_t floor = dungeonFloorTileForLevel();
int random_offset = 2 + randomNumber(2);
int height = coord.y - random_offset;
int depth = coord.y + random_offset;
int left = coord.x - 1;
int right = coord.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 = coord.y - 1;
depth = coord.y + 1;
left = coord.x - random_offset;
right = coord.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(coord);
break;
case 2: // Inner treasure vault
dungeonPlaceVault(coord);
// Place a secret door
random_offset = randomNumber(4);
if (random_offset < 3) {
dungeonPlaceSecretDoor(Coord_t{coord.y - 3 + (random_offset << 1), coord.x});
} else {
dungeonPlaceSecretDoor(Coord_t{coord.y, coord.x - 7 + (random_offset << 1)});
}
// Place a treasure in the vault
dungeonPlaceRandomObjectAt(coord, false);
// Let's guard the treasure well.
dungeonPlaceVaultMonster(coord, 2 + randomNumber(2));
// Traps naturally
dungeonPlaceVaultTrap(coord, Coord_t{4, 4}, 1 + randomNumber(3));
break;
case 3:
if (randomNumber(3) == 1) {
dg.floor[coord.y - 1][coord.x - 2].feature_id = TMP1_WALL;
dg.floor[coord.y + 1][coord.x - 2].feature_id = TMP1_WALL;
dg.floor[coord.y - 1][coord.x + 2].feature_id = TMP1_WALL;
dg.floor[coord.y + 1][coord.x + 2].feature_id = TMP1_WALL;
dg.floor[coord.y - 2][coord.x - 1].feature_id = TMP1_WALL;
dg.floor[coord.y - 2][coord.x + 1].feature_id = TMP1_WALL;
dg.floor[coord.y + 2][coord.x - 1].feature_id = TMP1_WALL;
dg.floor[coord.y + 2][coord.x + 1].feature_id = TMP1_WALL;
if (randomNumber(3) == 1) {
dungeonPlaceSecretDoor(Coord_t{coord.y, coord.x - 2});
dungeonPlaceSecretDoor(Coord_t{coord.y, coord.x + 2});
dungeonPlaceSecretDoor(Coord_t{coord.y - 2, coord.x});
dungeonPlaceSecretDoor(Coord_t{coord.y + 2, coord.x});
}
} else if (randomNumber(3) == 1) {
dg.floor[coord.y][coord.x].feature_id = TMP1_WALL;
dg.floor[coord.y - 1][coord.x].feature_id = TMP1_WALL;
dg.floor[coord.y + 1][coord.x].feature_id = TMP1_WALL;
dg.floor[coord.y][coord.x - 1].feature_id = TMP1_WALL;
dg.floor[coord.y][coord.x + 1].feature_id = TMP1_WALL;
} else if (randomNumber(3) == 1) {
dg.floor[coord.y][coord.x].feature_id = TMP1_WALL;
}
break;
// handled by the default case
// case 4:
// // no special feature!
// break;
default:
break;
}
}
// Constructs a tunnel between two points
static void dungeonBuildTunnel(Coord_t start, Coord_t 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 = start.y;
int start_col = start.x;
int tunnel_index = 0;
int wall_index = 0;
int y_direction, x_direction;
pickCorrectDirection(y_direction, x_direction, start, 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(y_direction, x_direction);
} else {
pickCorrectDirection(y_direction, x_direction, start, end);
}
}
int tmp_row = start.y + y_direction;
int tmp_col = start.x + x_direction;
while (!coordInBounds(Coord_t{tmp_row, tmp_col})) {
if (randomNumber(config::dungeon::DUN_RANDOM_DIR) == 1) {
chanceOfRandomDirection(y_direction, x_direction);
} else {
pickCorrectDirection(y_direction, x_direction, start, end);
}
tmp_row = start.y + y_direction;
tmp_col = start.x + x_direction;
}
switch (dg.floor[tmp_row][tmp_col].feature_id) {
case TILE_NULL_WALL:
start.y = tmp_row;
start.x = tmp_col;
if (tunnel_index < 1000) {
tunnels_tk[tunnel_index].y = start.y;
tunnels_tk[tunnel_index].x = start.x;
tunnel_index++;
}
door_flag = false;
break;
case TMP2_WALL:
// do nothing
break;
case TILE_GRANITE_WALL:
start.y = tmp_row;
start.x = tmp_col;
if (wall_index < 1000) {
walls_tk[wall_index].y = start.y;
walls_tk[wall_index].x = start.x;
wall_index++;
}
for (int y = start.y - 1; y <= start.y + 1; y++) {
for (int x = start.x - 1; x <= start.x + 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:
start.y = tmp_row;
start.x = tmp_col;
if (!door_flag) {
if (door_index < 100) {
doors_tk[door_index].y = start.y;
doors_tk[door_index].x = start.x;
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 = start.y - start_row;
if (tmp_row < 0) {
tmp_row = -tmp_row;
}
tmp_col = start.x - 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
start.y = tmp_row;
start.x = tmp_col;
}
} while ((start.y != end.y || start.x != end.x) && !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(Coord_t{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(Coord_t coord) {
if (coordCorridorWallsNextTo(coord) > 2) {
bool vertical = dg.floor[coord.y - 1][coord.x].feature_id >= MIN_CAVE_WALL && dg.floor[coord.y + 1][coord.x].feature_id >= MIN_CAVE_WALL;
bool horizontal = dg.floor[coord.y][coord.x - 1].feature_id >= MIN_CAVE_WALL && dg.floor[coord.y][coord.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(Coord_t coord) {
if (dg.floor[coord.y][coord.x].feature_id == TILE_CORR_FLOOR && randomNumber(100) > config::dungeon::DUN_TUNNEL_DOORS && dungeonIsNextTo(coord)) {
dungeonPlaceDoor(coord);
}
}
// Returns random co-ordinates -RAK-
static void dungeonNewSpot(Coord_t &coord) {
Tile_t *tile = nullptr;
Coord_t position = Coord_t{0, 0};
do {
position.y = (int32_t) randomNumber(dg.height - 2);
position.x = (int32_t) randomNumber(dg.width - 2);
tile = &dg.floor[position.y][position.x];
} while (tile->feature_id >= MIN_CLOSED_SPACE || tile->creature_id != 0 || tile->treasure_id != 0);
coord.y = position.y;
coord.x = position.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;
Coord_t locations[400];
for (int row = 0; row < row_rooms; row++) {
for (int col = 0; col < col_rooms; col++) {
if (room_map[row][col]) {
locations[location_id].y = (int32_t)(row * (SCREEN_HEIGHT >> 1) + QUART_HEIGHT);
locations[location_id].x = (int32_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(locations[location_id]);
} else if (room_type == 2) {
dungeonBuildRoomWithInnerRooms(locations[location_id]);
} else {
dungeonBuildRoomCrossShaped(locations[location_id]);
}
} else {
dungeonBuildRoom(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;
int32_t y = locations[pick1].y;
int32_t x = locations[pick1].x;
locations[pick1].y = locations[pick2].y;
locations[pick1].x = locations[pick2].x;
locations[pick2].y = y;
locations[pick2].x = x;
}
door_index = 0;
// move zero entry to location_id, so that can call dungeonBuildTunnel all location_id times
locations[location_id].y = locations[0].y;
locations[location_id].x = locations[0].x;
for (int i = 0; i < location_id; i++) {
dungeonBuildTunnel(locations[i + 1], locations[i]);
}
// 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(Coord_t{doors_tk[i].y, doors_tk[i].x - 1});
dungeonPlaceDoorIfNextToTwoWalls(Coord_t{doors_tk[i].y, doors_tk[i].x + 1});
dungeonPlaceDoorIfNextToTwoWalls(Coord_t{doors_tk[i].y - 1, doors_tk[i].x});
dungeonPlaceDoorIfNextToTwoWalls(Coord_t{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
Coord_t coord = Coord_t{0, 0};
dungeonNewSpot(coord);
py.pos.y = coord.y;
py.pos.x = coord.x;
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, Coord_t coord) {
int yval = coord.y * 10 + 5;
int xval = coord.x * 16 + 16;
int height = yval - randomNumber(3);
int depth = yval + randomNumber(4);
int left = xval - randomNumber(6);
int right = xval + randomNumber(6);
int y, x;
for (y = height; y <= depth; y++) {
for (x = left; x <= right; x++) {
dg.floor[y][x].feature_id = TILE_BOUNDARY_WALL;
}
}
int tmp = randomNumber(4);
if (tmp < 3) {
y = randomNumber(depth - height) + height - 1;
if (tmp == 1) {
x = left;
} else {
x = right;
}
} else {
x = randomNumber(right - left) + left - 1;
if (tmp == 3) {
y = depth;
} else {
y = height;
}
}
dg.floor[y][x].feature_id = TILE_CORR_FLOOR;
int cur_pos = popt();
dg.floor[y][x].treasure_id = (uint8_t) cur_pos;
inventoryItemCopyTo(config::dungeon::objects::OBJ_STORE_DOOR + store_id, game.treasure.list[cur_pos]);
}
// Link all free space in treasure list together
static void treasureLinker() {
for (auto &item : game.treasure.list) {
inventoryItemCopyTo(config::dungeon::objects::OBJ_NOTHING, item);
}
game.treasure.current_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], Coord_t{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
Coord_t coord = Coord_t{0, 0};
dungeonNewSpot(coord);
py.pos.y = coord.y;
py.pos.x = coord.x;
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.pos.y = -1;
py.pos.x = -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.13/src/game_death.cpp 0000644 0001750 0001750 00000010762 13720166710 015437 0 ustar ariel ariel // 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 : py.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.pack.unique_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.13/src/player_traps.cpp 0000644 0001750 0001750 00000014260 13720166710 016063 0 ustar ariel ariel // 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(PlayerAttr::A_INT);
ability += class_level_adj[py.misc.class_id][PlayerClassLevelAdj::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(Coord_t coord, 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);
// 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(Coord_t coord, 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 = SpecialNameIds::SN_LOCKED;
} else {
item.special_name_id = SpecialNameIds::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(coord);
}
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;
}
Coord_t coord = py.pos;
(void) playerMovePosition(dir, coord);
Tile_t const &tile = dg.floor[coord.y][coord.x];
bool no_disarm = false;
if (tile.creature_id > 1 && tile.treasure_id != 0 &&
(game.treasure.list[tile.treasure_id].category_id == TV_VIS_TRAP || game.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 = game.treasure.list[tile.treasure_id];
if (item.category_id == TV_VIS_TRAP) {
playerDisarmFloorTrap(coord, disarm_ability, item.depth_first_found, dir, item.misc_use);
} else if (item.category_id == TV_CHEST) {
playerDisarmChestTrap(coord, 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(PlayerAttr::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(Coord_t coord) {
Coord_t position = Coord_t{0, 0};
for (int i = 0; i < 3; i++) {
position.y = coord.y;
position.x = coord.x;
(void) monsterSummon(position, false);
}
}
static void chestExplode(Coord_t coord) {
printMessage("There is a sudden explosion!");
(void) dungeonDeleteObject(coord);
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(Coord_t coord) {
uint32_t flags = game.treasure.list[dg.floor[coord.y][coord.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(coord);
}
if ((flags & config::treasure::chests::CH_EXPLODE) != 0u) {
chestExplode(coord);
}
}
umoria-5.7.13/src/mage_spells.h 0000644 0001750 0001750 00000000542 13720166710 015314 0 ustar ariel ariel // 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 getAndCastMagicSpell();
int spellChanceOfSuccess(int spell_id);
umoria-5.7.13/src/character.h 0000644 0001750 0001750 00000006630 13720166710 014761 0 ustar ariel ariel // 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.13/src/player_eat.cpp 0000644 0001750 0001750 00000021411 13720166710 015477 0 ustar ariel ariel // 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,
CurePoison,
CureBlindness,
CureParanoia,
CureConfusion,
Weakness,
Unhealth,
// 12-15 are no longer used
RestoreSTR = 16,
RestoreCON,
RestoreINT,
RestoreWIS,
RestoreDEX,
RestoreCHR,
FirstAid,
MinorCures,
LightCures,
// 25 no longer used
MajorCures = 26,
PoisonousFood,
};
// Eat some food. -RAK-
void playerEat() {
game.player_free_turn = true;
if (py.pack.unique_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 = &py.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::CurePoison:
identified = playerCurePoison();
break;
case FoodMagicTypes::CureBlindness:
identified = playerCureBlindness();
break;
case FoodMagicTypes::CureParanoia:
if (py.flags.afraid > 1) {
py.flags.afraid = 1;
identified = true;
}
break;
case FoodMagicTypes::CureConfusion:
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::RestoreSTR:
if (playerStatRestore(PlayerAttr::A_STR)) {
printMessage("You feel your strength returning.");
identified = true;
}
break;
case FoodMagicTypes::RestoreCON:
if (playerStatRestore(PlayerAttr::A_CON)) {
printMessage("You feel your health returning.");
identified = true;
}
break;
case FoodMagicTypes::RestoreINT:
if (playerStatRestore(PlayerAttr::A_INT)) {
printMessage("Your head spins a moment.");
identified = true;
}
break;
case FoodMagicTypes::RestoreWIS:
if (playerStatRestore(PlayerAttr::A_WIS)) {
printMessage("You feel your wisdom returning.");
identified = true;
}
break;
case FoodMagicTypes::RestoreDEX:
if (playerStatRestore(PlayerAttr::A_DEX)) {
printMessage("You feel more dexterous.");
identified = true;
}
break;
case FoodMagicTypes::RestoreCHR:
if (playerStatRestore(PlayerAttr::A_CHR)) {
printMessage("Your skin stops itching.");
identified = true;
}
break;
case FoodMagicTypes::FirstAid:
identified = spellChangePlayerHitPoints(randomNumber(6));
break;
case FoodMagicTypes::MinorCures:
identified = spellChangePlayerHitPoints(randomNumber(12));
break;
case FoodMagicTypes::LightCures:
identified = spellChangePlayerHitPoints(randomNumber(18));
break;
#if 0 // 25 is no longer used
case 25:
identified = hp_player(damroll(3, 6));
break;
#endif
case FoodMagicTypes::MajorCures:
identified = spellChangePlayerHitPoints(diceRoll(Dice_t{3, 12}));
break;
case FoodMagicTypes::PoisonousFood:
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:
// All cases are handled, so this should never be reached!
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(py.inventory[item_id], item_id);
item = &py.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.13/src/data_creatures.cpp 0000644 0001750 0001750 00000136206 13720166710 016351 0 ustar ariel ariel // 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.13/src/ui_inventory.cpp 0000644 0001750 0001750 00000125455 13720166710 016121 0 ustar ariel ariel // 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 item_id) {
int total_weight = py.inventory[item_id].weight * py.inventory[item_id].items_count;
int quotient = total_weight / 10;
int remainder = total_weight % 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, py.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 PlayerEquipment::Wield:
return "wielding";
case PlayerEquipment::Head:
return "wearing on your head";
case PlayerEquipment::Neck:
return "wearing around your neck";
case PlayerEquipment::Body:
return "wearing on your body";
case PlayerEquipment::Arm:
return "wearing on your arm";
case PlayerEquipment::Hands:
return "wearing on your hands";
case PlayerEquipment::Right:
return "wearing on your right hand";
case PlayerEquipment::Left:
return "wearing on your left hand";
case PlayerEquipment::Feet:
return "wearing on your feet";
case PlayerEquipment::Outer:
return "wearing about your body";
case PlayerEquipment::Light:
return "using to light the way";
case PlayerEquipment::Auxiliary:
return "holding ready by your side";
default:
return "carrying in your pack";
}
}
static const char *itemPositionDescription(int position_id, uint16_t weight) {
switch (position_id) {
case PlayerEquipment::Wield:
if (py.stats.used[PlayerAttr::A_STR] * 15 < weight) {
return "Just lifting";
}
return "Wielding";
case PlayerEquipment::Head:
return "On head";
case PlayerEquipment::Neck:
return "Around neck";
case PlayerEquipment::Body:
return "On body";
case PlayerEquipment::Arm:
return "On arm";
case PlayerEquipment::Hands:
return "On hands";
case PlayerEquipment::Right:
return "On right hand";
case PlayerEquipment::Left:
return "On left hand";
case PlayerEquipment::Feet:
return "On feet";
case PlayerEquipment::Outer:
return "About body";
case PlayerEquipment::Light:
return "Light source";
case PlayerEquipment::Auxiliary:
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 - PlayerEquipment::Wield];
int len = 79 - column;
int lim;
if (weighted) {
lim = 52;
} else {
lim = 60;
}
// Range of equipment
int line = 0;
for (int i = PlayerEquipment::Wield; i < PLAYER_INVENTORY_SIZE; i++) {
if (py.inventory[i].category_id == TV_NOTHING) {
continue;
}
// Get position
const char *position_description = itemPositionDescription(i, py.inventory[i].weight);
obj_desc_t description = {'\0'};
itemDescription(description, py.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 = PlayerEquipment::Wield; i < PLAYER_INVENTORY_SIZE; i++) {
if (py.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 uiCommandDisplayInventoryScreen(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.pack.unique_items - 1, config::options::show_inventory_weights, screen_left, CNIL);
line = py.pack.unique_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;
}
line++;
while (line <= screen_base) {
eraseLine(Coord_t{line, screen_left});
line++;
}
}
// 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, py.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 saved_state = screen_state;
screen_state = WRONG_SCR;
uiCommandDisplayInventoryScreen(saved_state);
return;
}
screen_left = 50;
screen_base = 0;
// this forces exit of inventoryExecuteCommand() if selecting is not set true
screen_state = BLANK_SCR;
}
static bool uiCommandInventoryTakeOffItem(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.pack.unique_items >= PlayerEquipment::Wield && (game.doing_inventory_command == 0)) {
printMessage("You will have to drop something first.");
return selecting;
}
if (screen_state != BLANK_SCR) {
uiCommandDisplayInventoryScreen(EQUIP_SCR);
}
return true;
}
static bool uiCommandInventoryDropItem(char *command, bool selecting) {
if (py.pack.unique_items == 0 && py.equipment_count == 0) {
printMessage("But you're not carrying anything.");
return selecting;
}
if (dg.floor[py.pos.y][py.pos.x].treasure_id != 0) {
printMessage("There's no room to drop anything here.");
return selecting;
}
if ((screen_state == EQUIP_SCR && py.equipment_count > 0) || py.pack.unique_items == 0) {
if (screen_state != BLANK_SCR) {
uiCommandDisplayInventoryScreen(EQUIP_SCR);
}
*command = 'r'; // Remove - or take off and drop.
} else if (screen_state != BLANK_SCR) {
uiCommandDisplayInventoryScreen(INVEN_SCR);
}
return true;
}
static bool uiCommandInventoryWearWieldItem(bool selecting) {
// Note: simple loop to get the global wear_low value
for (wear_low = 0; wear_low < py.pack.unique_items && py.inventory[wear_low].category_id > TV_MAX_WEAR; wear_low++)
;
// Note: simple loop to get the global wear_high value
for (wear_high = wear_low; wear_high < py.pack.unique_items && py.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) {
uiCommandDisplayInventoryScreen(WEAR_SCR);
}
return true;
}
static void uiCommandInventoryUnwieldItem() {
if (py.inventory[PlayerEquipment::Wield].category_id == TV_NOTHING && py.inventory[PlayerEquipment::Auxiliary].category_id == TV_NOTHING) {
printMessage("But you are wielding no weapons.");
return;
}
if ((py.inventory[PlayerEquipment::Wield].flags & config::treasure::flags::TR_CURSED) != 0u) {
obj_desc_t description = {'\0'};
itemDescription(description, py.inventory[PlayerEquipment::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 saved_item = py.inventory[PlayerEquipment::Auxiliary];
py.inventory[PlayerEquipment::Auxiliary] = py.inventory[PlayerEquipment::Wield];
py.inventory[PlayerEquipment::Wield] = saved_item;
if (screen_state == EQUIP_SCR) {
screen_left = displayEquipment(config::options::show_inventory_weights, screen_left);
}
playerAdjustBonusesForItem(py.inventory[PlayerEquipment::Auxiliary], -1); // Subtract bonuses
playerAdjustBonusesForItem(py.inventory[PlayerEquipment::Wield], 1); // Add bonuses
if (py.inventory[PlayerEquipment::Wield].category_id != TV_NOTHING) {
obj_desc_t msg_label = {'\0'};
(void) strcpy(msg_label, "Primary weapon : ");
obj_desc_t description = {'\0'};
itemDescription(description, py.inventory[PlayerEquipment::Wield], true);
printMessage(strcat(msg_label, 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 && ((py.inventory[m].inscription[0] != which) || (py.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 *str, int from, int to, const char *swap, char command, const char *prompt) {
from = from + 'a';
to = to + 'a';
const char *list_items = "";
if (screen_state == BLANK_SCR) {
list_items = ", * to list";
}
const char *digits = "";
if (command == 'w' || command == 'd') {
digits = ", 0-9";
}
(void) sprintf(str, "(%c-%c%s%s%s, space to break, ESC to exit) %s which one?", from, to, list_items, swap, digits, prompt);
}
static void drawInventoryScreenForCommand(char command) {
if (command == 't' || command == 'r') {
uiCommandDisplayInventoryScreen(EQUIP_SCR);
} else if (command == 'w' && screen_state != INVEN_SCR) {
uiCommandDisplayInventoryScreen(WEAR_SCR);
} else {
uiCommandDisplayInventoryScreen(INVEN_SCR);
}
}
static void swapInventoryScreenForDrop() {
if (screen_state == EQUIP_SCR) {
uiCommandDisplayInventoryScreen(INVEN_SCR);
} else if (screen_state == INVEN_SCR) {
uiCommandDisplayInventoryScreen(EQUIP_SCR);
}
}
static int inventoryGetSlotToWearEquipment(int item) {
int slot;
// Slot for equipment
switch (py.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 = PlayerEquipment::Wield;
break;
case TV_LIGHT:
slot = PlayerEquipment::Light;
break;
case TV_BOOTS:
slot = PlayerEquipment::Feet;
break;
case TV_GLOVES:
slot = PlayerEquipment::Hands;
break;
case TV_CLOAK:
slot = PlayerEquipment::Outer;
break;
case TV_HELM:
slot = PlayerEquipment::Head;
break;
case TV_SHIELD:
slot = PlayerEquipment::Arm;
break;
case TV_HARD_ARMOR:
case TV_SOFT_ARMOR:
slot = PlayerEquipment::Body;
break;
case TV_AMULET:
slot = PlayerEquipment::Neck;
break;
case TV_RING:
if (py.inventory[PlayerEquipment::Right].category_id == TV_NOTHING) {
slot = PlayerEquipment::Right;
} else if (py.inventory[PlayerEquipment::Left].category_id == TV_NOTHING) {
slot = PlayerEquipment::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 = PlayerEquipment::Left;
} else if (query == 'r') {
slot = PlayerEquipment::Right;
} else {
if (query == 'L') {
slot = PlayerEquipment::Left;
} else if (query == 'R') {
slot = PlayerEquipment::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 item_id) {
obj_desc_t description = {'\0'};
itemDescription(description, py.inventory[item_id], false);
obj_desc_t item_text = {'\0'};
(void) sprintf(item_text, "The %s you are ", description);
if (item_id == PlayerEquipment::Head) {
(void) strcat(item_text, "wielding ");
} else {
(void) strcat(item_text, "wearing ");
}
printMessage(strcat(item_text, "appears to be cursed."));
}
static bool selectItemCommands(char *command, char *which, bool selecting) {
int item_to_take_off;
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.pack.unique_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.pack.unique_items > 0) {
swap = ", / for Inven";
}
}
}
}
if (from > to) {
selecting = false;
continue;
}
obj_desc_t heading_text = {'\0'};
buildCommandHeading(heading_text, from, to, swap, *command, prompt);
// Abort everything.
if (!getCommand(heading_text, *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.
item_to_take_off = item_id;
item_id = 21;
do {
item_id++;
if (py.inventory[item_id].category_id != TV_NOTHING) {
item_to_take_off--;
}
} while (item_to_take_off >= 0);
if ((isupper((int) *which) != 0) && !verify((char *) prompt, item_id)) {
item_id = -1;
} else if ((py.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(py.inventory[item_id])) {
if (dg.floor[py.pos.y][py.pos.x].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.pack.unique_items == 0 && py.equipment_count == 0) {
py.pack.weight = 0;
}
} else {
slot = inventoryCarryItem(py.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 && py.inventory[slot].category_id != TV_NOTHING) {
if ((py.inventory[slot].flags & config::treasure::flags::TR_CURSED) != 0u) {
inventoryItemIsCursedMessage(slot);
item_id = -1;
} else if (py.inventory[item_id].sub_category_id == ITEM_GROUP_MIN && py.inventory[item_id].items_count > 1 && !inventoryCanCarryItemCount(py.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 saved_item = py.inventory[item_id];
Inventory_t *item = &saved_item;
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.pack.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 = &py.inventory[slot];
if (item->category_id != TV_NOTHING) {
int saved_counter = py.pack.unique_items;
item_to_take_off = inventoryCarryItem(*item);
// If item removed did not stack with anything
// in inventory, then increment wear_high.
if (py.pack.unique_items != saved_counter) {
wear_high++;
}
playerTakeOff(slot, item_to_take_off);
}
// third, wear new item
*item = saved_item;
py.equipment_count++;
playerAdjustBonusesForItem(*item, 1);
const char *text = nullptr;
if (slot == PlayerEquipment::Wield) {
text = "You are wielding";
} else if (slot == PlayerEquipment::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.
item_to_take_off = PlayerEquipment::Wield;
item_id = 0;
while (item_to_take_off != slot) {
if (py.inventory[item_to_take_off++].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 == PlayerEquipment::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 (py.inventory[item_id].items_count > 1) {
obj_desc_t description = {'\0'};
itemDescription(description, py.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.pack.unique_items == 0 && py.equipment_count == 0) {
py.pack.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 w_quotient = py.pack.weight / 10;
int w_remainder = py.pack.weight % 10;
if (!config::options::show_inventory_weights || py.pack.unique_items == 0) {
(void) sprintf(msg, "You are carrying %d.%d pounds. In your pack there is %s", w_quotient, w_remainder, (py.pack.unique_items == 0 ? "nothing." : "-"));
} else {
int l_quotient = playerCarryingLoadLimit() / 10;
int l_remainder = playerCarryingLoadLimit() % 10;
(void) sprintf(msg, "You are carrying %d.%d pounds. Your capacity is %d.%d pounds. In your pack is -", w_quotient, w_remainder, l_quotient, l_remainder);
}
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 uiCommandDisplayInventory() {
if (py.pack.unique_items == 0) {
printMessage("You are not carrying anything.");
} else {
uiCommandDisplayInventoryScreen(INVEN_SCR);
}
}
static void uiCommandDisplayEquipment() {
if (py.equipment_count == 0) {
printMessage("You are not using any equipment.");
} else {
uiCommandDisplayInventoryScreen(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':
uiCommandDisplayInventory();
break;
case 'e':
uiCommandDisplayEquipment();
break;
case 't':
selecting = uiCommandInventoryTakeOffItem(selecting);
break;
case 'd':
selecting = uiCommandInventoryDropItem(&command, selecting);
break;
case 'w':
selecting = uiCommandInventoryWearWieldItem(selecting);
break;
case 'x':
uiCommandInventoryUnwieldItem();
break;
case ' ':
// Dummy command to return again to main prompt.
break;
case '?':
uiCommandDisplayInventoryScreen(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();
}
enum class PackMenu {
CloseMenu,
Equipment,
Inventory,
};
// Switch between Equipment/Inventory menu.
// Returns true when menu has changed
static bool inventorySwitchPackMenu(vtype_t &prompt, PackMenu &menu, bool menu_active, int &item_id_end) {
bool changed = false;
if (menu == PackMenu::Inventory) {
if (py.equipment_count == 0) {
putStringClearToEOL("But you're not using anything -more-", Coord_t{0, 0});
(void) getKeyInput();
} else {
menu = PackMenu::Equipment;
changed = true;
if (menu_active) {
item_id_end = py.equipment_count;
while (item_id_end < py.pack.unique_items) {
item_id_end++;
eraseLine(Coord_t{item_id_end, 0});
}
}
item_id_end = py.equipment_count - 1;
}
putStringClearToEOL(prompt, Coord_t{0, 0});
} else {
if (py.pack.unique_items == 0) {
putStringClearToEOL("But you're not carrying anything -more-", Coord_t{0, 0});
(void) getKeyInput();
} else {
menu = PackMenu::Inventory;
changed = true;
if (menu_active) {
item_id_end = py.pack.unique_items;
while (item_id_end < py.equipment_count) {
item_id_end++;
eraseLine(Coord_t{item_id_end, 0});
}
}
item_id_end = py.pack.unique_items - 1;
}
}
return changed;
}
// 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) {
PackMenu menu = PackMenu::Inventory;
bool pack_full = false;
if (item_id_end > PlayerEquipment::Wield) {
pack_full = true;
if (py.pack.unique_items == 0) {
menu = PackMenu::Equipment;
item_id_end = py.equipment_count - 1;
} else {
item_id_end = py.pack.unique_items - 1;
}
}
if (py.pack.unique_items < 1 && (!pack_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 menu_active = false;
do {
if (menu_active) {
if (menu == PackMenu::Inventory) {
(void) displayInventory(item_id_start, item_id_end, false, 80, mask);
} else {
(void) displayEquipment(false, 80);
}
}
vtype_t description = {'\0'};
if (pack_full) {
(void) sprintf(description, //
"(%s: %c-%c,%s%s / for %s, or ESC) %s", //
(menu == PackMenu::Inventory ? "Inven" : "Equip"), //
item_id_start + 'a', //
item_id_end + 'a', //
(menu == PackMenu::Inventory ? " 0-9," : ""), //
(menu_active ? "" : " * to see,"), //
(menu == PackMenu::Inventory ? "Equip" : "Inven"), //
prompt //
);
} else {
(void) sprintf(description, //
"(Items %c-%c,%s%s ESC to exit) %s", //
item_id_start + 'a', //
item_id_end + 'a', //
(menu == PackMenu::Inventory ? " 0-9," : ""), //
(menu_active ? "" : " * for inventory list,"), //
prompt //
);
}
putStringClearToEOL(description, Coord_t{0, 0});
bool done = false;
while (!done) {
char which = getKeyInput();
switch (which) {
case ESCAPE:
menu = PackMenu::CloseMenu;
done = true;
game.player_free_turn = true;
break;
case '/':
done = inventorySwitchPackMenu(description, menu, menu_active, item_id_end);
break;
case '*': // activate menu if required
if (!menu_active) {
done = true;
terminalSaveScreen();
menu_active = true;
}
break;
default:
// look for item whose inscription matches "which"
if (which >= '0' && which <= '9' && menu != PackMenu::Equipment) { // TODO: menu == PackMenu::Inventory ???
int m;
// Note: loop to find the inventory item
for (m = item_id_start; m < PlayerEquipment::Wield && (py.inventory[m].inscription[0] != which || py.inventory[m].inscription[1] != '\0'); m++)
;
if (m < PlayerEquipment::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 (menu == PackMenu::Equipment) {
item_id_start = 21;
item_id_end = command_key_id;
do {
// Note: a simple loop to find first inventory item
item_id_start++;
while (py.inventory[item_id_start].category_id == TV_NOTHING) {
item_id_start++;
}
item_id_end--;
} while (item_id_end >= 0);
command_key_id = item_id_start;
}
if ((isupper((int) which) != 0) && !verify("Try", command_key_id)) {
menu = PackMenu::CloseMenu;
done = true;
game.player_free_turn = true;
break;
}
menu = PackMenu::CloseMenu;
done = true;
item_found = true;
} else if (message != nullptr) {
printMessage(message);
// Set `done` to force redraw of the question.
done = true;
} else {
terminalBellSound();
}
break;
}
}
} while (menu != PackMenu::CloseMenu);
if (menu_active) {
terminalRestoreScreen();
}
messageLineClear();
return item_found;
}
umoria-5.7.13/src/dungeon.cpp 0000644 0001750 0001750 00000050633 13720166710 015021 0 ustar ariel ariel // 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
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 || game.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 && game.treasure.list[tile.treasure_id].category_id != TV_INVIS_TRAP) {
return game.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, game.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 = game.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, game.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, game.treasure.list[free_treasure_id]);
game.treasure.list[free_treasure_id].cost += (8L * (int32_t) randomNumber((int) game.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], game.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) {
Coord_t coord = Coord_t{0, 0};
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 {
coord.y = randomNumber(dg.height) - 1;
coord.x = randomNumber(dg.width) - 1;
} while (!(*set_function)(dg.floor[coord.y][coord.x].feature_id) || dg.floor[coord.y][coord.x].treasure_id != 0 || (coord.y == py.pos.y && coord.x == py.pos.x));
switch (object_type) {
case 1:
dungeonSetTrap(coord, 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);
break;
case 4:
dungeonPlaceGold(coord);
break;
case 5:
dungeonPlaceRandomObjectAt(coord, 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;
Coord_t location = Coord_t{0, 0};
for (location.y = top; location.y <= bottom; location.y++) {
for (location.x = left; location.x <= right; location.x++) {
Tile_t &tile = dg.floor[location.y][location.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 = game.treasure.list[tile.treasure_id].category_id;
if (treasure_id >= TV_MIN_VISIBLE && treasure_id <= TV_MAX_VISIBLE) {
tile.field_mark = true;
}
}
panelPutTile(caveGetTileSymbol(location), location);
}
}
}
}
// 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 sub1MoveLight(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 = game.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;
}
Coord_t coord = Coord_t{0, 0};
for (coord.y = top; coord.y <= bottom; coord.y++) {
// Leftmost to rightmost do
for (coord.x = left; coord.x <= right; coord.x++) {
panelPutTile(caveGetTileSymbol(coord), coord);
}
}
}
// When blinded, move only the player symbol.
// With no light, movement becomes involved.
static void sub3MoveLight(Coord_t const &from, Coord_t const &to) {
if (py.temporary_light_only) {
Coord_t coord = Coord_t{0, 0};
for (coord.y = from.y - 1; coord.y <= from.y + 1; coord.y++) {
for (coord.x = from.x - 1; coord.x <= from.x + 1; coord.x++) {
dg.floor[coord.y][coord.x].temporary_light = false;
panelPutTile(caveGetTileSymbol(coord), coord);
}
}
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) {
sub3MoveLight(from, to);
} else {
sub1MoveLight(from, to);
}
}
// Deletes a monster entry from the level -RAK-
void dungeonDeleteMonster(int id) {
Monster_t *monster = &monsters[id];
dg.floor[monster->pos.y][monster->pos.x].creature_id = 0;
if (monster->lit) {
dungeonLiteSpot(Coord_t{monster->pos.y, monster->pos.x});
}
int last_id = next_free_monster_id - 1;
if (id != last_id) {
monster = &monsters[last_id];
dg.floor[monster->pos.y][monster->pos.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.pos.y][monster.pos.x].creature_id = 0;
if (monster.lit) {
dungeonLiteSpot(Coord_t{monster.pos.y, monster.pos.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].pos.y;
int x = monsters[last_id].pos.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, at)) {
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.13/src/main.cpp 0000644 0001750 0001750 00000006462 13720166710 014307 0 ustar ariel ariel // 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
-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;
// 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 '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);
return 0;
}
static bool parseGameSeed(const char *argv, uint32_t &seed) {
int value;
if (!stringToNumber(argv, value)) {
return false;
}
if (value <= 0 || value > INT_MAX) {
return false;
}
seed = (uint32_t) value;
return true;
}
umoria-5.7.13/src/player_throw.cpp 0000644 0001750 0001750 00000023071 13720166710 016075 0 ustar ariel ariel // 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 = &py.inventory[item_id];
*treasure = *item;
if (item->items_count > 1) {
treasure->items_count = 1;
item->items_count--;
py.pack.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 (py.inventory[PlayerEquipment::Wield].category_id != TV_NOTHING) {
plus_to_hit -= py.inventory[PlayerEquipment::Wield].to_hit;
}
distance = (((py.stats.used[PlayerAttr::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 (py.inventory[PlayerEquipment::Wield].category_id != TV_BOW) {
return;
}
switch (py.inventory[PlayerEquipment::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 * py.inventory[PlayerEquipment::Wield].to_hit;
damage += py.inventory[PlayerEquipment::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 * py.inventory[PlayerEquipment::Wield].to_hit;
damage += py.inventory[PlayerEquipment::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 * py.inventory[PlayerEquipment::Wield].to_hit;
damage += py.inventory[PlayerEquipment::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 * py.inventory[PlayerEquipment::Wield].to_hit;
damage += py.inventory[PlayerEquipment::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 * py.inventory[PlayerEquipment::Wield].to_hit;
damage += py.inventory[PlayerEquipment::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 * py.inventory[PlayerEquipment::Wield].to_hit;
damage += py.inventory[PlayerEquipment::Wield].to_damage;
damage = damage * 4;
distance = 35;
}
break;
default:
// NOOP
break;
}
}
static void inventoryDropOrThrowItem(Coord_t coord, Inventory_t *item) {
Coord_t position = Coord_t{coord.y, coord.x};
bool flag = false;
if (randomNumber(10) > 1) {
for (int k = 0; !flag && k <= 9;) {
if (coordInBounds(position)) {
if (dg.floor[position.y][position.x].feature_id <= MAX_OPEN_SPACE && dg.floor[position.y][position.x].treasure_id == 0) {
flag = true;
}
}
if (!flag) {
position.y = coord.y + randomNumber(3) - 2;
position.x = coord.x + randomNumber(3) - 2;
k++;
}
}
}
if (flag) {
int cur_pos = popt();
dg.floor[position.y][position.x].treasure_id = (uint8_t) cur_pos;
game.treasure.list[cur_pos] = *item;
dungeonLiteSpot(position);
} 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.pack.unique_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.pack.unique_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 current_distance = 0;
Coord_t coord = py.pos;
Coord_t old_coord = py.pos;
bool flag = false;
while (!flag) {
(void) playerMovePosition(dir, coord);
if (current_distance + 1 > tdis) {
break;
}
current_distance++;
dungeonLiteSpot(old_coord);
Tile_t const &tile = dg.floor[coord.y][coord.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][PlayerClassLevelAdj::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, PlayerClassLevelAdj::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, PlayerClassLevelAdj::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_coord, &thrown_item);
}
} else {
// do not test tile.field_mark here
if (coordInsidePanel(coord) && py.flags.blind < 1 && (tile.temporary_light || tile.permanent_light)) {
panelPutTile(tile_char, coord);
putQIO(); // show object moving
}
}
} else {
flag = true;
inventoryDropOrThrowItem(old_coord, &thrown_item);
}
old_coord.y = coord.y;
old_coord.x = coord.x;
}
}
umoria-5.7.13/src/player_quaff.cpp 0000644 0001750 0001750 00000033173 13720166710 016040 0 ustar ariel ariel // 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,
RestoreStrength,
Intelligence,
LoseIntelligence,
RestoreIntelligence,
Wisdom,
LoseWisdom,
RestoreWisdom,
Charisma,
Ugliness,
RestoreCharisma,
CureLightWounds,
CureSeriousWounds,
CureCriticalWounds,
Healing,
Constitution,
GainExperience,
Sleep,
Blindness,
Confusion,
Poison,
HasteSelf,
Slowness,
// 25 not used
Dexterity = 26,
RestoreDexterity,
RestoreConstitution,
CureBlindness,
CureConfusion,
CurePoison,
// 32 not used
// 33 not used
LoseExperience = 34,
SaltWater,
Invulnerability,
Heroism,
SuperHeroism,
Boldness,
RestoreLifeLevels,
ResistHeat,
ResistCold,
DetectInvisible,
SlowPoison,
NeutralizePoison,
RestoreMana,
InfraVision,
};
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(PlayerAttr::A_STR)) {
printMessage("Wow! What bulging muscles!");
identified = true;
}
break;
case PotionSpellTypes::Weakness:
spellLoseSTR();
identified = true;
break;
case PotionSpellTypes::RestoreStrength:
if (playerStatRestore(PlayerAttr::A_STR)) {
printMessage("You feel warm all over.");
identified = true;
}
break;
case PotionSpellTypes::Intelligence:
if (playerStatRandomIncrease(PlayerAttr::A_INT)) {
printMessage("Aren't you brilliant!");
identified = true;
}
break;
case PotionSpellTypes::LoseIntelligence:
spellLoseINT();
identified = true;
break;
case PotionSpellTypes::RestoreIntelligence:
if (playerStatRestore(PlayerAttr::A_INT)) {
printMessage("You have have a warm feeling.");
identified = true;
}
break;
case PotionSpellTypes::Wisdom:
if (playerStatRandomIncrease(PlayerAttr::A_WIS)) {
printMessage("You suddenly have a profound thought!");
identified = true;
}
break;
case PotionSpellTypes::LoseWisdom:
spellLoseWIS();
identified = true;
break;
case PotionSpellTypes::RestoreWisdom:
if (playerStatRestore(PlayerAttr::A_WIS)) {
printMessage("You feel your wisdom returning.");
identified = true;
}
break;
case PotionSpellTypes::Charisma:
if (playerStatRandomIncrease(PlayerAttr::A_CHR)) {
printMessage("Gee, ain't you cute!");
identified = true;
}
break;
case PotionSpellTypes::Ugliness:
spellLoseCHR();
identified = true;
break;
case PotionSpellTypes::RestoreCharisma:
if (playerStatRestore(PlayerAttr::A_CHR)) {
printMessage("You feel your looks returning.");
identified = true;
}
break;
case PotionSpellTypes::CureLightWounds:
identified = spellChangePlayerHitPoints(diceRoll(Dice_t{2, 7}));
break;
case PotionSpellTypes::CureSeriousWounds:
identified = spellChangePlayerHitPoints(diceRoll(Dice_t{4, 7}));
break;
case PotionSpellTypes::CureCriticalWounds:
identified = spellChangePlayerHitPoints(diceRoll(Dice_t{6, 7}));
break;
case PotionSpellTypes::Healing:
identified = spellChangePlayerHitPoints(1000);
break;
case PotionSpellTypes::Constitution:
if (playerStatRandomIncrease(PlayerAttr::A_CON)) {
printMessage("You feel tingly for a moment.");
identified = true;
}
break;
case PotionSpellTypes::GainExperience:
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::HasteSelf:
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(PlayerAttr::A_DEX)) {
printMessage("You feel more limber!");
identified = true;
}
break;
case PotionSpellTypes::RestoreDexterity:
if (playerStatRestore(PlayerAttr::A_DEX)) {
printMessage("You feel less clumsy.");
identified = true;
}
break;
case PotionSpellTypes::RestoreConstitution:
if (playerStatRestore(PlayerAttr::A_CON)) {
printMessage("You feel your health returning!");
identified = true;
}
break;
case PotionSpellTypes::CureBlindness:
identified = playerCureBlindness();
break;
case PotionSpellTypes::CureConfusion:
identified = playerCureConfusion();
break;
case PotionSpellTypes::CurePoison:
identified = playerCurePoison();
break;
// case 33: break; // this is no longer useful, now that there is a 'G'ain magic spells command
case PotionSpellTypes::LoseExperience:
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 > SHRT_MAX) {
auto scale = (int32_t)(INT_MAX / 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::SaltWater:
(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::SuperHeroism:
if (py.flags.super_heroism == 0) {
identified = true;
}
py.flags.super_heroism += randomNumber(25) + 25;
break;
case PotionSpellTypes::Boldness:
identified = playerRemoveFear();
break;
case PotionSpellTypes::RestoreLifeLevels:
identified = spellRestorePlayerLevels();
break;
case PotionSpellTypes::ResistHeat:
if (py.flags.heat_resistance == 0) {
identified = true;
}
py.flags.heat_resistance += randomNumber(10) + 10;
break;
case PotionSpellTypes::ResistCold:
if (py.flags.cold_resistance == 0) {
identified = true;
}
py.flags.cold_resistance += randomNumber(10) + 10;
break;
case PotionSpellTypes::DetectInvisible:
if (py.flags.detect_invisible == 0) {
identified = true;
}
playerDetectInvisible(randomNumber(12) + 12);
break;
case PotionSpellTypes::SlowPoison:
identified = spellSlowPoison();
break;
case PotionSpellTypes::NeutralizePoison:
identified = playerCurePoison();
break;
case PotionSpellTypes::RestoreMana:
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::InfraVision:
if (py.flags.timed_infra == 0) {
printMessage("Your eyes begin to tingle.");
identified = true;
}
py.flags.timed_infra += 100 + randomNumber(100);
break;
default:
// All cases are handled, so this should never be reached!
printMessage("Internal error in potion()");
break;
}
}
return identified;
}
// Potions for the quaffing -RAK-
void quaff() {
game.player_free_turn = true;
if (py.pack.unique_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 = &py.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(py.inventory[item_id], item_id);
item = &py.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.13/src/scores.cpp 0000644 0001750 0001750 00000022161 13720166710 014653 0 ustar ariel ariel // 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
i++;
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 : py.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.13/src/character.cpp 0000644 0001750 0001750 00000041142 13720166710 015311 0 ustar ariel ariel // 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[PlayerAttr::A_STR] = createModifyPlayerStat(py.stats.max[PlayerAttr::A_STR], race.str_adjustment);
py.stats.max[PlayerAttr::A_INT] = createModifyPlayerStat(py.stats.max[PlayerAttr::A_INT], race.int_adjustment);
py.stats.max[PlayerAttr::A_WIS] = createModifyPlayerStat(py.stats.max[PlayerAttr::A_WIS], race.wis_adjustment);
py.stats.max[PlayerAttr::A_DEX] = createModifyPlayerStat(py.stats.max[PlayerAttr::A_DEX], race.dex_adjustment);
py.stats.max[PlayerAttr::A_CON] = createModifyPlayerStat(py.stats.max[PlayerAttr::A_CON], race.con_adjustment);
py.stats.max[PlayerAttr::A_CHR] = createModifyPlayerStat(py.stats.max[PlayerAttr::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 = playerDamageAdjustment();
py.misc.plusses_to_hit = playerToHitAdjustment();
py.misc.magical_ac = 0;
py.misc.ac = 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});
Coord_t coord = Coord_t{21, 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);
coord.x += 15;
if (coord.x > 70) {
coord.x = 2;
coord.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 (int 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(uint8_t const race_id, uint8_t *class_list) {
Coord_t coord = Coord_t{21, 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);
class_list[class_id] = i;
coord.x += 15;
if (coord.x > 70) {
coord.x = 2;
coord.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[PlayerAttr::A_STR] = createModifyPlayerStat(py.stats.max[PlayerAttr::A_STR], klass.strength);
py.stats.max[PlayerAttr::A_INT] = createModifyPlayerStat(py.stats.max[PlayerAttr::A_INT], klass.intelligence);
py.stats.max[PlayerAttr::A_WIS] = createModifyPlayerStat(py.stats.max[PlayerAttr::A_WIS], klass.wisdom);
py.stats.max[PlayerAttr::A_DEX] = createModifyPlayerStat(py.stats.max[PlayerAttr::A_DEX], klass.dexterity);
py.stats.max[PlayerAttr::A_CON] = createModifyPlayerStat(py.stats.max[PlayerAttr::A_CON], klass.constitution);
py.stats.max[PlayerAttr::A_CHR] = createModifyPlayerStat(py.stats.max[PlayerAttr::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 = playerDamageAdjustment();
py.misc.plusses_to_hit = playerToHitAdjustment();
py.misc.magical_ac = 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[PlayerAttr::A_STR]);
value += monetaryValueCalculatedFromStat(py.stats.max[PlayerAttr::A_INT]);
value += monetaryValueCalculatedFromStat(py.stats.max[PlayerAttr::A_WIS]);
value += monetaryValueCalculatedFromStat(py.stats.max[PlayerAttr::A_CON]);
value += monetaryValueCalculatedFromStat(py.stats.max[PlayerAttr::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[PlayerAttr::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.13/src/dice.cpp 0000644 0001750 0001750 00000001144 13720166710 014257 0 ustar ariel ariel // 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.13/src/game_files.cpp 0000644 0001750 0001750 00000034423 13720166710 015454 0 ustar ariel ariel // 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 *screen_file = fopen(config::files::splash_screen.c_str(), "r");
if (screen_file != nullptr) {
clearScreen();
for (int i = 0; fgets(in_line, 80, screen_file) != CNIL; i++) {
putString(in_line, Coord_t{i, 0});
}
waitForContinueKey(23);
(void) fclose(screen_file);
}
}
// 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], game.treasure.list[treasure_id]);
magicTreasureMagicalAbility(treasure_id, level);
Inventory_t &item = game.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 *char_file) {
putStringClearToEOL("Writing character sheet...", Coord_t{0, 0});
putQIO();
const char *colon = ":";
const char *blank = " ";
vtype_t stat_description = {'\0'};
(void) fprintf(char_file, "%c\n\n", CTRL_KEY('L'));
(void) fprintf(char_file, " Name%9s %-23s", colon, py.misc.name);
(void) fprintf(char_file, " Age%11s %6d", colon, (int) py.misc.age);
statsAsString(py.stats.used[PlayerAttr::A_STR], stat_description);
(void) fprintf(char_file, " STR : %s\n", stat_description);
(void) fprintf(char_file, " Race%9s %-23s", colon, character_races[py.misc.race_id].name);
(void) fprintf(char_file, " Height%8s %6d", colon, (int) py.misc.height);
statsAsString(py.stats.used[PlayerAttr::A_INT], stat_description);
(void) fprintf(char_file, " INT : %s\n", stat_description);
(void) fprintf(char_file, " Sex%10s %-23s", colon, (playerGetGenderLabel()));
(void) fprintf(char_file, " Weight%8s %6d", colon, (int) py.misc.weight);
statsAsString(py.stats.used[PlayerAttr::A_WIS], stat_description);
(void) fprintf(char_file, " WIS : %s\n", stat_description);
(void) fprintf(char_file, " Class%8s %-23s", colon, classes[py.misc.class_id].title);
(void) fprintf(char_file, " Social Class : %6d", py.misc.social_class);
statsAsString(py.stats.used[PlayerAttr::A_DEX], stat_description);
(void) fprintf(char_file, " DEX : %s\n", stat_description);
(void) fprintf(char_file, " Title%8s %-23s", colon, playerRankTitle());
(void) fprintf(char_file, "%22s", blank);
statsAsString(py.stats.used[PlayerAttr::A_CON], stat_description);
(void) fprintf(char_file, " CON : %s\n", stat_description);
(void) fprintf(char_file, "%34s", blank);
(void) fprintf(char_file, "%26s", blank);
statsAsString(py.stats.used[PlayerAttr::A_CHR], stat_description);
(void) fprintf(char_file, " CHR : %s\n\n", stat_description);
(void) fprintf(char_file, " + To Hit : %6d", py.misc.display_to_hit);
(void) fprintf(char_file, "%7sLevel : %7d", blank, (int) py.misc.level);
(void) fprintf(char_file, " Max Hit Points : %6d\n", py.misc.max_hp);
(void) fprintf(char_file, " + To Damage : %6d", py.misc.display_to_damage);
(void) fprintf(char_file, "%7sExperience : %7d", blank, py.misc.exp);
(void) fprintf(char_file, " Cur Hit Points : %6d\n", py.misc.current_hp);
(void) fprintf(char_file, " + To AC : %6d", py.misc.display_to_ac);
(void) fprintf(char_file, "%7sMax Exp : %7d", blank, py.misc.max_exp);
(void) fprintf(char_file, " Max Mana%8s %6d\n", colon, py.misc.mana);
(void) fprintf(char_file, " Total AC : %6d", py.misc.display_ac);
if (py.misc.level >= PLAYER_MAX_LEVEL) {
(void) fprintf(char_file, "%7sExp to Adv : *******", blank);
} else {
(void) fprintf(char_file, "%7sExp to Adv : %7d", blank, (int32_t)(py.base_exp_levels[py.misc.level - 1] * py.misc.experience_factor / 100));
}
(void) fprintf(char_file, " Cur Mana%8s %6d\n", colon, py.misc.current_mana);
(void) fprintf(char_file, "%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][PlayerClassLevelAdj::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][PlayerClassLevelAdj::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(PlayerAttr::A_INT) + //
(class_level_adj[py.misc.class_id][PlayerClassLevelAdj::DISARM] * py.misc.level / 3);
int xsave = py.misc.saving_throw + playerStatAdjustmentWisdomIntelligence(PlayerAttr::A_WIS) + //
(class_level_adj[py.misc.class_id][PlayerClassLevelAdj::SAVE] * py.misc.level / 3);
int xdev = py.misc.saving_throw + playerStatAdjustmentWisdomIntelligence(PlayerAttr::A_INT) + //
(class_level_adj[py.misc.class_id][PlayerClassLevelAdj::DEVICE] * py.misc.level / 3);
vtype_t xinfra = {'\0'};
(void) sprintf(xinfra, "%d feet", py.flags.see_infra * 10);
(void) fprintf(char_file, "(Miscellaneous Abilities)\n\n");
(void) fprintf(char_file, " Fighting : %-10s", statRating(Coord_t{12, xbth}));
(void) fprintf(char_file, " Stealth : %-10s", statRating(Coord_t{1, xstl}));
(void) fprintf(char_file, " Perception : %s\n", statRating(Coord_t{3, xfos}));
(void) fprintf(char_file, " Bows/Throw : %-10s", statRating(Coord_t{12, xbthb}));
(void) fprintf(char_file, " Disarming : %-10s", statRating(Coord_t{8, xdis}));
(void) fprintf(char_file, " Searching : %s\n", statRating(Coord_t{6, xsrh}));
(void) fprintf(char_file, " Saving Throw: %-10s", statRating(Coord_t{6, xsave}));
(void) fprintf(char_file, " Magic Device: %-10s", statRating(Coord_t{6, xdev}));
(void) fprintf(char_file, " Infra-Vision: %s\n\n", xinfra);
// Write out the character's history
(void) fprintf(char_file, "Character Background\n");
for (auto &entry : py.misc.history) {
(void) fprintf(char_file, " %s\n", entry);
}
}
static const char *equipmentPlacementDescription(int item_id) {
switch (item_id) {
case PlayerEquipment::Wield:
return "You are wielding";
case PlayerEquipment::Head:
return "Worn on head";
case PlayerEquipment::Neck:
return "Worn around neck";
case PlayerEquipment::Body:
return "Worn on body";
case PlayerEquipment::Arm:
return "Worn on shield arm";
case PlayerEquipment::Hands:
return "Worn on hands";
case PlayerEquipment::Right:
return "Right ring finger";
case PlayerEquipment::Left:
return "Left ring finger";
case PlayerEquipment::Feet:
return "Worn on feet";
case PlayerEquipment::Outer:
return "Worn about body";
case PlayerEquipment::Light:
return "Light source is";
case PlayerEquipment::Auxiliary:
return "Secondary weapon";
default:
return "*Unknown value*";
}
}
// Write out the equipment list.
static void writeEquipmentListToFile(FILE *equip_file) {
(void) fprintf(equip_file, "\n [Character's Equipment List]\n\n");
if (py.equipment_count == 0) {
(void) fprintf(equip_file, " Character has no equipment in use.\n");
return;
}
obj_desc_t description = {'\0'};
int item_slot_id = 0;
for (int i = PlayerEquipment::Wield; i < PLAYER_INVENTORY_SIZE; i++) {
if (py.inventory[i].category_id == TV_NOTHING) {
continue;
}
itemDescription(description, py.inventory[i], true);
(void) fprintf(equip_file, " %c) %-19s: %s\n", item_slot_id + 'a', equipmentPlacementDescription(i), description);
item_slot_id++;
}
(void) fprintf(equip_file, "%c\n\n", CTRL_KEY('L'));
}
// Write out the character's inventory.
static void writeInventoryToFile(FILE *inv_file) {
(void) fprintf(inv_file, " [General Inventory List]\n\n");
if (py.pack.unique_items == 0) {
(void) fprintf(inv_file, " Character has no objects in inventory.\n");
return;
}
obj_desc_t description = {'\0'};
for (int i = 0; i < py.pack.unique_items; i++) {
itemDescription(description, py.inventory[i], true);
(void) fprintf(inv_file, "%c) %s\n", i + 'a', description);
}
(void) fprintf(inv_file, "%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.13/CHANGELOG.md 0000644 0001750 0001750 00000023523 13720166710 013676 0 ustar ariel ariel # Umoria CHANGELOG
Tracking all changes since the 5.6.0 release.
## HEAD
## 5.7.13 (2020-08-22)
- Bugfix in `getAndCastMagicSpell()`: losing mana when cancelling a mage spell
## 5.7.12 (2020-05-28)
- Bugfix in `pray()`: should loose mana when failing to recite a prayer.
- Add `Press ? for help` to the message bar on game start/load.
- Use roguelike keys by default, and remove setting option from the CLI.
- Player (`row`,`col`) and Monster (`y`,`x`) now use `Coord_t` for their positions.
- Use `Coord_t` in all functions that used `y, x` coordinates.
- Reorganise some game arrays and variables, placing them on an object:
* put `inventory` onto `Player_t`
* put `treasure_list` and `current_treasure_id` onto `Game_t`
- Type changes:
* `rcmove` variable in `memoryUpdateRecall()` signature is now an `uint32_t` like everywhere else.
* Change `store_buy` function signature type to use `uint8_t`, and return a `bool`.
* Various other types changes.
- Typo fixes: `item_sub_catory`, `current_askin_price`.
- Various `clang-format` and `clang-tidy` updates.
- Move the Manual and FAQ to the `historical` directory and remove from release.
It seems that these are very much out of date - possibly for any 5.x version - so
it makes sense to move them back in with the rest of the historical documents.
- Various compiler fixes.
## 5.7.11 (2020-02-26)
- Rename several `ui_inventory.cpp` functions to avoid name clashes (bugfix).
- Player ToHit/Armor/Damage Adjustment functions now return `int16` types.
- Minor style changes.
- Various `CMakeLists.txt` updates:
* Allow out-of-source builds.
* Use `configure_file` to set variables in `splash.txt` and `versions.txt`.
* Remove unneeded `make install`.
* Fix for finding ncurses on Linux/macOS.
* GCC 8 support.
- Update `AUTHORS`: add more known features from `-JWT-`.
## 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.13/CODE_OF_CONDUCT.md 0000644 0001750 0001750 00000006253 13720166710 014665 0 ustar ariel ariel # 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.13/data/ 0000755 0001750 0001750 00000000000 13720166710 012771 5 ustar ariel ariel umoria-5.7.13/data/scores.dat 0000644 0001750 0001750 00000000114 13720166710 014755 0 ustar ariel ariel ////&Kp8ΛΘΒΒΘΘΘΙΙ
ρη³³³γ榦¦¦¦b¦₯μφαζβεͺNK#ΗΒ umoria-5.7.13/data/rl_help.txt 0000644 0001750 0001750 00000006262 13720166710 015165 0 ustar ariel ariel Command 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.
umoria-5.7.13/data/rl_help_wizard.txt 0000644 0001750 0001750 00000000710 13720166710 016535 0 ustar ariel ariel \ - 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.13/data/death_tomb.txt 0000644 0001750 0001750 00000002020 13720166710 015632 0 ustar ariel ariel
_______________________
/ \ ___
/ \ ___ / \ ___
/ RIP \ \ : : / \
/ \ : _;,,,;_ : :
/ \,;_ _;,,,;_
| the | ___
| | / \
| | : :
| | _;,,,;_ ____
| Level : | / \
| | : :
| | : :
| Died on Level : | _;,,,,;_
| killed by |
| |
| |
*| * * * * * * | *
________)/\\_)_/___(\/___(//_\)/_\//__\\(/_|_)_______
umoria-5.7.13/data/welcome.txt 0000644 0001750 0001750 00000000653 13720166710 015171 0 ustar ariel ariel 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.13/data/splash.txt.in 0000644 0001750 0001750 00000001464 13720166710 015436 0 ustar ariel ariel
Robert A. Koeneke's classic roguelike
D U N G E O N S O F
## ## ####### ######## #### ###
### ### ## ## ## ## ## ## ##
#### #### ## ## ## ## ## ## ##
## ### ## ## ## ######## ## ## ##
## ## ## ## ## ## ## #########
## ## ## ## ## ## ## ## ##
## ## ####### ## ## #### ## ##
Umoria ${umoria_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.13/data/versions.txt.in 0000644 0001750 0001750 00000002654 13720166710 016016 0 ustar ariel ariel Umoria ${umoria_version} (${current_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.13/data/help_wizard.txt 0000644 0001750 0001750 00000000710 13720166710 016040 0 ustar ariel ariel : - 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.13/data/help.txt 0000644 0001750 0001750 00000006012 13720166710 014461 0 ustar ariel ariel Command 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.13/data/death_royal.txt 0000644 0001750 0001750 00000001310 13720166710 016020 0 ustar ariel ariel
#
#####
#
,,, $$$ ,,,
,,=$ "$$$$$" $=,,
,$$ $$$ $$,
*> <*> <*
$$ $$$ $$
"$$ $$$ $$"
"$$ $$$ $$"
*#########*#########*
*#########*#########*
Veni, Vidi, Vici!
I came, I saw, I conquered!
All Hail the Mighty